diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/MiddleEllipsisText.kt b/core/ui/src/main/java/com/tangem/core/ui/components/MiddleEllipsisText.kt index 9bdff5f987..e67fbf3de2 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/MiddleEllipsisText.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/MiddleEllipsisText.kt @@ -6,7 +6,6 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Modifier -import androidx.compose.ui.geometry.Rect import androidx.compose.ui.graphics.Color import androidx.compose.ui.layout.SubcomposeLayout import androidx.compose.ui.text.TextLayoutResult @@ -18,10 +17,12 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextDecoration import androidx.compose.ui.unit.Constraints import androidx.compose.ui.unit.TextUnit +import com.tangem.core.ui.components.atoms.text.BoundCounter /** * https://stackoverflow.com/questions/69083061/how-to-make-middle-ellipsis-in-text-with-jetpack-compose */ +@Deprecated("Use EllipsisText with TextEllipsis.Middle ellipsis instead") @Suppress("LongMethod") @Composable fun MiddleEllipsisText( @@ -138,38 +139,4 @@ fun MiddleEllipsisText( private const val ELLIPSIS_CHARACTERS_COUNT = 3 private const val ELLIPSIS_CHARACTER = '.' -private val ellipsisText = List(ELLIPSIS_CHARACTERS_COUNT) { ELLIPSIS_CHARACTER }.joinToString(separator = "") - -private class BoundCounter( - private val text: String, - private val textLayoutResult: TextLayoutResult, - private val charPosition: (Int) -> Int, -) { - var string = "" - private set - var width = 0f - private set - - private var _nextCharWidth: Float? = null - private var invalidCharsCount = 0 - - fun widthWithNextChar(): Float = width + nextCharWidth() - - private fun nextCharWidth(): Float = _nextCharWidth ?: run { - var boundingBox: Rect - // invalidCharsCount fixes this bug: https://issuetracker.google.com/issues/197146630 - invalidCharsCount-- - do { - boundingBox = textLayoutResult - .getBoundingBox(charPosition(string.count() + ++invalidCharsCount)) - } while (boundingBox.right == 0f) - _nextCharWidth = boundingBox.width - boundingBox.width - } - - fun addNextChar() { - string += text[charPosition(string.count())] - width += nextCharWidth() - _nextCharWidth = null - } -} \ No newline at end of file +private val ellipsisText = List(ELLIPSIS_CHARACTERS_COUNT) { ELLIPSIS_CHARACTER }.joinToString(separator = "") \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/atoms/text/BoundCounter.kt b/core/ui/src/main/java/com/tangem/core/ui/components/atoms/text/BoundCounter.kt new file mode 100644 index 0000000000..6a97038b3d --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/components/atoms/text/BoundCounter.kt @@ -0,0 +1,38 @@ +package com.tangem.core.ui.components.atoms.text + +import androidx.compose.ui.geometry.Rect +import androidx.compose.ui.text.TextLayoutResult + +internal class BoundCounter( + private val text: String, + private val textLayoutResult: TextLayoutResult, + private val charPosition: (Int) -> Int, +) { + var string = "" + private set + var width = 0f + private set + + private var _nextCharWidth: Float? = null + private var invalidCharsCount = 0 + + fun widthWithNextChar(): Float = width + nextCharWidth() + + private fun nextCharWidth(): Float = _nextCharWidth ?: run { + var boundingBox: Rect + // invalidCharsCount fixes this bug: https://issuetracker.google.com/issues/197146630 + invalidCharsCount-- + do { + boundingBox = textLayoutResult + .getBoundingBox(charPosition(string.count() + ++invalidCharsCount)) + } while (boundingBox.right == 0f) + _nextCharWidth = boundingBox.width + boundingBox.width + } + + fun addNextChar() { + string += text[charPosition(string.count())] + width += nextCharWidth() + _nextCharWidth = null + } +} \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/atoms/text/EllipsisText.kt b/core/ui/src/main/java/com/tangem/core/ui/components/atoms/text/EllipsisText.kt new file mode 100644 index 0000000000..d95b813ee1 --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/components/atoms/text/EllipsisText.kt @@ -0,0 +1,242 @@ +package com.tangem.core.ui.components.atoms.text + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.material.LocalTextStyle +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.SubcomposeLayout +import androidx.compose.ui.text.TextLayoutResult +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.font.FontStyle +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextDecoration +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import androidx.compose.ui.unit.Constraints +import androidx.compose.ui.unit.TextUnit +import com.tangem.core.ui.res.TangemTheme + +sealed class TextEllipsis { + + object Middle : TextEllipsis() + + object End : TextEllipsis() + + data class OffsetEnd( + val offsetEnd: Int = 0, + val hasSeparator: Boolean = true, + ) : TextEllipsis() +} + +/** + * https://stackoverflow.com/questions/69083061/how-to-make-middle-ellipsis-in-text-with-jetpack-compose + * + * Customized Text with ellipsis. Ellipsis can be placed in: Middle, End or OffsetEnd (OffsetEnd with separator). + * + * * OffsetEnd can be useful to display big amounts with currency symbol. OffsetEnd 0 is equal to End. + */ +@Suppress("LongMethod") +@Composable +fun EllipsisText( + text: String, + modifier: Modifier = Modifier, + color: Color = Color.Unspecified, + fontSize: TextUnit = TextUnit.Unspecified, + fontStyle: FontStyle? = null, + fontWeight: FontWeight? = null, + fontFamily: FontFamily? = null, + letterSpacing: TextUnit = TextUnit.Unspecified, + textDecoration: TextDecoration? = null, + textAlign: TextAlign? = null, + lineHeight: TextUnit = TextUnit.Unspecified, + softWrap: Boolean = true, + onTextLayout: (TextLayoutResult) -> Unit = {}, + style: TextStyle = LocalTextStyle.current, + ellipsis: TextEllipsis = TextEllipsis.End, +) { + val ellipsisText = remember(text) { + if (ellipsis is TextEllipsis.OffsetEnd && ellipsis.hasSeparator) { + ELLIPSIS_TEXT_WITH_SEPARATOR + } else { + ELLIPSIS_TEXT + } + } + + // some letters, like "r", will have less width when placed right before "." + // adding a space to prevent such case + val layoutText = remember(text) { "$text $ellipsisText" } + val textLayoutResultState = remember(layoutText) { + mutableStateOf(null) + } + SubcomposeLayout(modifier) { constraints -> + // result is ignored - we only need to fill our textLayoutResult + subcompose("measure") { + Text( + text = layoutText, + color = color, + fontSize = fontSize, + fontStyle = fontStyle, + fontWeight = fontWeight, + fontFamily = fontFamily, + letterSpacing = letterSpacing, + textDecoration = textDecoration, + textAlign = textAlign, + lineHeight = lineHeight, + softWrap = softWrap, + maxLines = 1, + onTextLayout = { textLayoutResultState.value = it }, + style = style, + ) + }.first().measure(Constraints()) + // to allow smart cast + val textLayoutResult = textLayoutResultState.value + ?: // shouldn't happen - onTextLayout is called before subcompose finishes + return@SubcomposeLayout layout(0, 0) {} + val placeable = subcompose("visible") { + val finalText = remember(text, textLayoutResult, constraints.maxWidth) { + if ( + text.isEmpty() || + textLayoutResult.getBoundingBox(text.indices.last).right <= constraints.maxWidth + ) { + // text not including ellipsis fits on the first line. + return@remember text + } + + var ellipsisWidth = 0f + layoutText.indices.toList() + .takeLast(ellipsisText.length) + .forEach widthLet@{ + ellipsisWidth += textLayoutResult.getBoundingBox(it).width + } + + val availableWidth = constraints.maxWidth - ellipsisWidth + val startCounter = BoundCounter(text, textLayoutResult) { it } + val endCounter = BoundCounter(text, textLayoutResult) { text.indices.last - it } + + when (ellipsis) { + TextEllipsis.Middle -> { + middleEllipsisText( + availableWidth, + startCounter, + endCounter, + ) + } + TextEllipsis.End -> { + offsetEndEllipsisText( + availableWidth = availableWidth, + startCounter = startCounter, + endCounter = endCounter, + ) + } + is TextEllipsis.OffsetEnd -> { + offsetEndEllipsisText( + availableWidth = availableWidth, + startCounter = startCounter, + endCounter = endCounter, + offsetEnd = ellipsis.offsetEnd, + withSeparator = ellipsis.hasSeparator, + ) + } + } + } + Text( + text = finalText, + color = color, + fontSize = fontSize, + fontStyle = fontStyle, + fontWeight = fontWeight, + fontFamily = fontFamily, + letterSpacing = letterSpacing, + textDecoration = textDecoration, + textAlign = textAlign, + lineHeight = lineHeight, + softWrap = softWrap, + onTextLayout = onTextLayout, + style = style, + ) + }[0].measure(constraints) + layout(placeable.width, placeable.height) { + placeable.place(0, 0) + } + } +} + +private const val ELLIPSIS_SEPARATOR = " " +private const val ELLIPSIS_TEXT = "..." +private const val ELLIPSIS_TEXT_WITH_SEPARATOR = ELLIPSIS_TEXT.plus(ELLIPSIS_SEPARATOR) + +private fun middleEllipsisText(availableWidth: Float, startCounter: BoundCounter, endCounter: BoundCounter): String { + while (availableWidth - startCounter.width - endCounter.width > 0) { + val possibleEndWidth = endCounter.widthWithNextChar() + if ( + startCounter.width >= possibleEndWidth && + availableWidth - startCounter.width - possibleEndWidth >= 0 + ) { + endCounter.addNextChar() + } else if (availableWidth - startCounter.widthWithNextChar() - endCounter.width >= 0) { + startCounter.addNextChar() + } else { + break + } + } + return startCounter.string.trimEnd() + ELLIPSIS_TEXT + endCounter.string.reversed().trimStart() +} + +private fun offsetEndEllipsisText( + availableWidth: Float, + startCounter: BoundCounter, + endCounter: BoundCounter, + offsetEnd: Int = 0, + withSeparator: Boolean = false, +): String { + while (availableWidth - startCounter.width - endCounter.width > 0) { + val possibleEndWidth = endCounter.widthWithNextChar() + if ( + offsetEnd > endCounter.string.length && + availableWidth - startCounter.width - possibleEndWidth >= 0 + ) { + endCounter.addNextChar() + } else if (availableWidth - startCounter.widthWithNextChar() - endCounter.width >= 0) { + startCounter.addNextChar() + } else { + break + } + } + val ellipsis = if (withSeparator) ELLIPSIS_TEXT_WITH_SEPARATOR else ELLIPSIS_TEXT + + return startCounter.string.trimEnd() + ellipsis + endCounter.string.reversed().trimStart() +} + +//region Preview +@Preview(widthDp = 200) +@Composable +private fun EllipsisTexPreview(@PreviewParameter(EllipsisTexPreviewParameterProvider::class) ellipsis: TextEllipsis) { + TangemTheme { + EllipsisText( + text = "11111111111111111111111111111111111111111111111111 END", + ellipsis = ellipsis, + modifier = Modifier + .background(TangemTheme.colors.background.primary) + .fillMaxWidth(), + ) + } +} + +private class EllipsisTexPreviewParameterProvider : PreviewParameterProvider { + override val values: Sequence + get() = sequenceOf( + TextEllipsis.Middle, + TextEllipsis.End, + TextEllipsis.OffsetEnd("TEXT".length), + TextEllipsis.OffsetEnd("TEXT".length, false), + ) +} +//endregion \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowApprox.kt b/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowApprox.kt index 3b085833a3..bd69b6feb9 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowApprox.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowApprox.kt @@ -3,13 +3,13 @@ package com.tangem.core.ui.components.inputrow import androidx.compose.foundation.background import androidx.compose.foundation.layout.* import androidx.compose.material3.Icon -import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.painterResource import androidx.compose.ui.tooling.preview.Preview import com.tangem.core.ui.R +import com.tangem.core.ui.components.atoms.text.EllipsisText import com.tangem.core.ui.components.currency.tokenicon.TokenIcon import com.tangem.core.ui.components.currency.tokenicon.TokenIconState import com.tangem.core.ui.components.inputrow.inner.DividerContainer @@ -56,6 +56,7 @@ fun InputRowApprox( iconState = leftIcon, title = leftTitle, subtitle = leftSubtitle, + modifier = Modifier.weight(1f), ) Icon( painter = painterResource(id = R.drawable.ic_approx_24), @@ -71,6 +72,7 @@ fun InputRowApprox( iconState = rightIcon, title = rightTitle, subtitle = rightSubtitle, + modifier = Modifier.weight(1f), ) } } @@ -97,12 +99,12 @@ private fun InputRowApproxItem( start = TangemTheme.dimens.spacing12, ), ) { - Text( + EllipsisText( text = title.resolveReference(), style = TangemTheme.typography.body2, color = TangemTheme.colors.text.primary1, ) - Text( + EllipsisText( text = subtitle.resolveReference(), style = TangemTheme.typography.caption2, color = TangemTheme.colors.text.tertiary, @@ -123,7 +125,7 @@ private fun InputRowApproxPreview_Light() { leftTitle = TextReference.Str("Left title"), leftSubtitle = TextReference.Str("Left subtitle"), rightIcon = TokenIconState.Loading, - rightTitle = TextReference.Str("Right title"), + rightTitle = TextReference.Str("Right title Right title Right title Right title Right title"), rightSubtitle = TextReference.Str("Right subtitle"), modifier = Modifier .background(TangemTheme.colors.background.action), @@ -137,7 +139,7 @@ private fun InputRowApproxPreview_Dark() { TangemTheme(isDark = true) { InputRowApprox( leftIcon = TokenIconState.Loading, - leftTitle = TextReference.Str("Left title"), + leftTitle = TextReference.Str("Left title Left title Left title Left title Left title"), leftSubtitle = TextReference.Str("Left subtitle"), rightIcon = TokenIconState.Loading, rightTitle = TextReference.Str("Right title"), diff --git a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/repository/MockQuotesRepository.kt b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/repository/MockQuotesRepository.kt index 6d1c1e1a27..ae960f191a 100644 --- a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/repository/MockQuotesRepository.kt +++ b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/repository/MockQuotesRepository.kt @@ -20,4 +20,9 @@ internal class MockQuotesRepository( override suspend fun getQuotesSync(currenciesIds: Set, refresh: Boolean): Set { return getQuotesUpdates(currenciesIds).first() } + + override suspend fun getQuoteSync(currencyId: CryptoCurrency.ID): Quote { + return quotes.map { it.getOrElse { e -> throw e } }.first() + .first { it.rawCurrencyId == currencyId.rawCurrencyId } + } } \ No newline at end of file diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/TokenDetailsPreviewData.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/TokenDetailsPreviewData.kt index 2a25900715..458e547586 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/TokenDetailsPreviewData.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/TokenDetailsPreviewData.kt @@ -101,6 +101,7 @@ internal object TokenDetailsPreviewData { ), dialogConfig = null, pendingTxs = persistentListOf(), + swapTxs = persistentListOf(), pullToRefreshConfig = pullToRefreshConfig, bottomSheetConfig = null, isBalanceHidden = false, diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/SwapTransactionsState.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/SwapTransactionsState.kt index 8b4fb5c8a5..9941c1bbda 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/SwapTransactionsState.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/SwapTransactionsState.kt @@ -2,21 +2,25 @@ package com.tangem.feature.tokendetails.presentation.tokendetails.state import com.tangem.core.ui.components.currency.tokenicon.TokenIconState import com.tangem.feature.swap.domain.models.domain.ExchangeStatus +import com.tangem.feature.swap.domain.models.domain.SwapProvider +import com.tangem.feature.tokendetails.presentation.tokendetails.state.components.ExchangeStatusNotifications import kotlinx.collections.immutable.PersistentList -import java.math.BigDecimal internal data class SwapTransactionsState( val txId: String, - val providerId: Int, + val provider: SwapProvider, val txUrl: String? = null, - val rate: BigDecimal, val timestamp: Long, - val status: PersistentList, + val statuses: PersistentList, val activeStatus: ExchangeStatus?, + val fiatSymbol: String, + val notification: ExchangeStatusNotifications? = null, val toCryptoAmount: String, + val toCryptoSymbol: String, val toFiatAmount: String, val toCurrencyIcon: TokenIconState, val fromCryptoAmount: String, + val fromCryptoSymbol: String, val fromFiatAmount: String, val fromCurrencyIcon: TokenIconState, val onClick: () -> Unit, @@ -25,7 +29,6 @@ internal data class SwapTransactionsState( internal class ExchangeStatusState( val status: ExchangeStatus, - val text: String, val isActive: Boolean, val isDone: Boolean, ) \ No newline at end of file diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/TokenDetailsState.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/TokenDetailsState.kt index 15f290adde..f9f5dfcabd 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/TokenDetailsState.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/TokenDetailsState.kt @@ -19,6 +19,7 @@ internal data class TokenDetailsState( val marketPriceBlockState: MarketPriceBlockState, val notifications: ImmutableList, val pendingTxs: PersistentList, + val swapTxs: PersistentList, val txHistoryState: TxHistoryState, val dialogConfig: TokenDetailsDialogConfig?, val pullToRefreshConfig: TokenDetailsPullToRefreshConfig, diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/components/ExchangeStatusNotifications.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/components/ExchangeStatusNotifications.kt new file mode 100644 index 0000000000..1da8fd0672 --- /dev/null +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/components/ExchangeStatusNotifications.kt @@ -0,0 +1,38 @@ +package com.tangem.feature.tokendetails.presentation.tokendetails.state.components + +import androidx.compose.runtime.Immutable +import com.tangem.core.ui.components.notifications.NotificationConfig +import com.tangem.core.ui.extensions.TextReference +import com.tangem.features.tokendetails.impl.R + +@Immutable +internal sealed class ExchangeStatusNotifications(val config: NotificationConfig) { + + data class NeedVerification( + val onGoToProviderClick: () -> Unit, + ) : ExchangeStatusNotifications( + config = NotificationConfig( + title = TextReference.Res(R.string.express_exchange_notification_verification_title), + subtitle = TextReference.Res(R.string.express_exchange_notification_verification_text), + iconResId = R.drawable.ic_alert_triangle_20, + buttonsState = NotificationConfig.ButtonsState.SecondaryButtonConfig( + text = TextReference.Res(R.string.express_go_to_provider), + onClick = onGoToProviderClick, + ), + ), + ) + + data class Failed( + val onGoToProviderClick: () -> Unit, + ) : ExchangeStatusNotifications( + config = NotificationConfig( + title = TextReference.Res(R.string.express_exchange_notification_failed_title), + subtitle = TextReference.Res(R.string.express_exchange_notification_failed_text), + iconResId = R.drawable.ic_alert_circle_24, + buttonsState = NotificationConfig.ButtonsState.SecondaryButtonConfig( + text = TextReference.Res(R.string.express_go_to_provider), + onClick = onGoToProviderClick, + ), + ), + ) +} \ No newline at end of file diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsSkeletonStateConverter.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsSkeletonStateConverter.kt index e92142e90c..30d410450e 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsSkeletonStateConverter.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsSkeletonStateConverter.kt @@ -45,6 +45,7 @@ internal class TokenDetailsSkeletonStateConverter( marketPriceBlockState = MarketPriceBlockState.Loading(value.symbol), notifications = persistentListOf(), pendingTxs = persistentListOf(), + swapTxs = persistentListOf(), txHistoryState = TxHistoryState.Content( contentItems = MutableStateFlow( value = TxHistoryState.getDefaultLoadingTransactions(clickIntents::onExploreClick), diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/TokenDetailsScreen.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/TokenDetailsScreen.kt index 9b4e73ef9a..57f5e40688 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/TokenDetailsScreen.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/TokenDetailsScreen.kt @@ -2,7 +2,9 @@ package com.tangem.feature.tokendetails.presentation.tokendetails.ui import androidx.activity.compose.BackHandler import androidx.compose.foundation.ExperimentalFoundationApi -import androidx.compose.foundation.layout.* +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.material.ExperimentalMaterialApi @@ -40,6 +42,9 @@ import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.T import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.TokenDetailsDialogs import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.TokenDetailsTopAppBar import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.TokenInfoBlock +import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.exchange.ExchangeStatusBottomSheet +import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.exchange.ExchangeStatusBottomSheetConfig +import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.exchange.swapTransactionsItems // TODO: Split to blocks [REDACTED_JIRA] @Suppress("LongMethod") @@ -117,6 +122,11 @@ internal fun TokenDetailsScreen(state: TokenDetailsState) { ) } + swapTransactionsItems( + state.swapTxs, + itemModifier, + ) + txHistoryItems( state = state.txHistoryState, isBalanceHidden = state.isBalanceHidden, @@ -141,6 +151,9 @@ internal fun TokenDetailsScreen(state: TokenDetailsState) { is ChooseAddressBottomSheetConfig -> { ChooseAddressBottomSheet(config = config) } + is ExchangeStatusBottomSheetConfig -> { + ExchangeStatusBottomSheet(config = config) + } } } diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/exchange/ExchangeStatusBlock.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/exchange/ExchangeStatusBlock.kt index a583d1cb56..b9d7dd77ee 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/exchange/ExchangeStatusBlock.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/exchange/ExchangeStatusBlock.kt @@ -26,7 +26,7 @@ import kotlinx.collections.immutable.PersistentList @Composable internal fun ExchangeStatusBlock( - status: PersistentList, + statuses: PersistentList, onClick: () -> Unit, modifier: Modifier = Modifier, ) { @@ -70,10 +70,10 @@ internal fun ExchangeStatusBlock( } } - status.forEachIndexed { index, item -> + statuses.forEachIndexed { index, item -> ExchangeStatusStep( stepStatus = item, - isLast = index == status.lastIndex, + isLast = index == statuses.lastIndex, ) } } @@ -128,7 +128,7 @@ private fun ExchangeStatusStepText(stepStatus: ExchangeStatusState) { } Text( - text = stepStatus.text, + text = getStatusText(stepStatus)?.let { stringResource(it) }.orEmpty(), style = TangemTheme.typography.body2, color = textColor, modifier = Modifier @@ -206,4 +206,34 @@ private fun ExchangeStepSeparator() { shape = CircleShape, ), ) +} + +private fun getStatusText(stepStatus: ExchangeStatusState) = when (stepStatus.status) { + ExchangeStatus.Failed -> R.string.express_exchange_status_failed + ExchangeStatus.Verifying -> if (stepStatus.isDone) { + R.string.express_exchange_status_verified + } else { + R.string.express_exchange_status_verifying + } + ExchangeStatus.New, ExchangeStatus.Waiting -> if (stepStatus.isDone) { + R.string.express_exchange_status_received + } else { + R.string.express_exchange_status_receiving + } + ExchangeStatus.Confirming -> if (stepStatus.isDone) { + R.string.express_exchange_status_confirmed + } else { + R.string.express_exchange_status_confirming + } + ExchangeStatus.Exchanging -> if (stepStatus.isDone) { + R.string.express_exchange_status_exchanged + } else { + R.string.express_exchange_status_exchanging + } + ExchangeStatus.Sending -> if (stepStatus.isDone) { + R.string.express_exchange_status_sent + } else { + R.string.express_exchange_status_sending + } + else -> null } \ No newline at end of file diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/exchange/ExchangeStatusBottomSheet.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/exchange/ExchangeStatusBottomSheet.kt index 5705f00a63..b32c69f48e 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/exchange/ExchangeStatusBottomSheet.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/exchange/ExchangeStatusBottomSheet.kt @@ -1,5 +1,6 @@ package com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.exchange +import androidx.compose.animation.AnimatedContent import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.padding import androidx.compose.material3.Text @@ -15,6 +16,7 @@ import com.tangem.core.ui.components.SpacerH24 import com.tangem.core.ui.components.bottomsheets.TangemBottomSheet import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent +import com.tangem.core.ui.components.notifications.Notification import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.utils.toDateFormat @@ -64,17 +66,25 @@ private fun ExchangeStatusBottomSheetContent(content: ExchangeStatusBottomSheetC toFiatAmount = TextReference.Str(config.toFiatAmount), ) SpacerH12() - // todo replace with real provider data ExchangeProvider( - providerName = TextReference.Str(config.providerId.toString()), - providerType = TextReference.Str("CEX"), - imageUrl = "https://s3.eu-central-1.amazonaws.com/tangem.api/express/changenow_512.png", + providerName = TextReference.Str(config.provider.name), + providerType = TextReference.Str(config.provider.type.name), + imageUrl = config.provider.imageLarge, ) SpacerH12() ExchangeStatusBlock( - status = config.status, + statuses = config.statuses, onClick = config.onGoToProviderClick, ) + AnimatedContent( + targetState = config.notification, + label = "Exchange Status Notification Change", + ) { + it?.let { + SpacerH12() + Notification(config = it.config) + } + } SpacerH24() } } diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/exchange/ExchangeStatusItems.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/exchange/ExchangeStatusItems.kt index 95ac42480e..4ea9a31b08 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/exchange/ExchangeStatusItems.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/exchange/ExchangeStatusItems.kt @@ -17,7 +17,10 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.constraintlayout.compose.ConstraintLayout +import androidx.constraintlayout.compose.Dimension import androidx.constraintlayout.compose.Visibility +import com.tangem.core.ui.components.atoms.text.EllipsisText +import com.tangem.core.ui.components.atoms.text.TextEllipsis import com.tangem.core.ui.components.currency.tokenicon.TokenIcon import com.tangem.core.ui.components.currency.tokenicon.TokenIconState import com.tangem.core.ui.res.TangemTheme @@ -46,11 +49,13 @@ internal fun LazyListScope.swapTransactionsItems( } ExchangeStatusItem( - providerName = item.providerId.toString(), + providerName = item.provider.name, fromTokenIconState = item.fromCurrencyIcon, toTokenIconState = item.toCurrencyIcon, fromAmount = item.fromCryptoAmount, + fromSymbol = item.fromCryptoSymbol, toAmount = item.toCryptoAmount, + toSymbol = item.toCryptoSymbol, onClick = item.onClick, infoIconRes = iconRes, infoIconTint = tint, @@ -67,7 +72,9 @@ private fun ExchangeStatusItem( fromTokenIconState: TokenIconState, toTokenIconState: TokenIconState, fromAmount: String, + fromSymbol: String, toAmount: String, + toSymbol: String, onClick: () -> Unit, modifier: Modifier = Modifier, @DrawableRes infoIconRes: Int? = null, @@ -104,14 +111,17 @@ private fun ExchangeStatusItem( bottom.linkTo(parent.bottom) }, ) - Text( + EllipsisText( text = fromAmount, style = TangemTheme.typography.body2, color = TangemTheme.colors.text.primary1, + ellipsis = TextEllipsis.OffsetEnd(fromSymbol.length), modifier = Modifier.constrainAs(fromRef) { start.linkTo(fromIconRef.end, padding6) top.linkTo(titleRef.bottom, padding6) + end.linkTo(swapIconRef.start) bottom.linkTo(parent.bottom) + width = Dimension.fillToConstraints }, ) Icon( @@ -123,6 +133,7 @@ private fun ExchangeStatusItem( .constrainAs(swapIconRef) { start.linkTo(fromRef.end, padding6) top.linkTo(titleRef.bottom, padding6) + end.linkTo(toIconRef.start) bottom.linkTo(parent.bottom) }, ) @@ -134,17 +145,21 @@ private fun ExchangeStatusItem( .constrainAs(toIconRef) { start.linkTo(swapIconRef.end, padding6) top.linkTo(titleRef.bottom, padding6) + end.linkTo(toRef.start) bottom.linkTo(parent.bottom) }, ) - Text( + EllipsisText( text = toAmount, style = TangemTheme.typography.body2, color = TangemTheme.colors.text.primary1, + ellipsis = TextEllipsis.OffsetEnd(toSymbol.length), modifier = Modifier.constrainAs(toRef) { start.linkTo(toIconRef.end, padding6) top.linkTo(titleRef.bottom, padding6) + end.linkTo(infoIconRef.start, padding6) bottom.linkTo(parent.bottom) + width = Dimension.fillToConstraints }, ) Icon(