From e356f020f383723802953e0a2aa32d8d011440a7 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 20 Aug 2024 18:21:13 +0400 Subject: [PATCH] Updated on 2026-08-14 --- .../core/ui/components/token/TokenItem.kt | 68 +++++++++- .../components/token/internal/TokenPrice.kt | 55 ++++++-- .../components/token/state/TokenItemState.kt | 126 +++++++++++------- .../common/preview/WalletScreenPreviewData.kt | 6 +- .../converter/TokenItemStateConverter.kt | 8 +- 5 files changed, 191 insertions(+), 72 deletions(-) diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/token/TokenItem.kt b/core/ui/src/main/java/com/tangem/core/ui/components/token/TokenItem.kt index 8bdd28b235..027db6c7d6 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/token/TokenItem.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/token/TokenItem.kt @@ -37,12 +37,43 @@ private enum class LayoutId { ICON, TITLE, FIAT_AMOUNT, CRYPTO_AMOUNT, CRYPTO_PRICE, NON_FIAT_CONTENT } +/** + * Token item for non reorderable list + * + * @param state token item state + * @param isBalanceHidden flag that shows/hides balance + * @param modifier modifier + * + * @see Figma Component + */ +@Composable +fun TokenItem(state: TokenItemState, isBalanceHidden: Boolean, modifier: Modifier = Modifier) { + TokenItem( + state = state, + isBalanceHidden = isBalanceHidden, + modifier = modifier, + reorderableTokenListState = null, + ) +} + +/** + * Token item for reorderable list + * + * @param state token item state + * @param isBalanceHidden flag that shows/hides balance + * @param reorderableTokenListState reorderable token list state + * @param modifier modifier + * + * @see Figma Component + */ @Composable fun TokenItem( state: TokenItemState, isBalanceHidden: Boolean, + reorderableTokenListState: ReorderableLazyListState?, modifier: Modifier = Modifier, - reorderableTokenListState: ReorderableLazyListState? = null, ) { val betweenRowsMargin = TangemTheme.dimens.spacing2 @@ -76,7 +107,7 @@ fun TokenItem( ) TokenPrice( - state = state.cryptoPriceState, + state = state.subtitleState, modifier = Modifier .layoutId(layoutId = LayoutId.CRYPTO_PRICE) .padding(end = TangemTheme.dimens.spacing8), @@ -195,6 +226,10 @@ private fun CustomContainer(state: TokenItemState, modifier: Modifier = Modifier is TokenItemState.Unreachable, -> { firstRowRemainingFreeSpace = layoutWidthWithoutPaddings - icon.width - nonFiatContent.width + + if (state.subtitleState != null) { + secondRowRemainingFreeSpace = layoutWidthWithoutPaddings - icon.width - nonFiatContent.width + } } } @@ -232,7 +267,13 @@ private fun CustomContainer(state: TokenItemState, modifier: Modifier = Modifier y = when (state) { is TokenItemState.NoAddress, is TokenItemState.Unreachable, - -> (layoutHeight - title.height).div(other = 2) + -> { + if (state.subtitleState == null) { + (layoutHeight - title.height).div(other = 2) + } else { + verticalPadding + } + } else -> verticalPadding }, ) @@ -411,7 +452,7 @@ private class TokenItemStateProvider : CollectionPreviewParameterProvider { + is TokenPriceState.CryptoPriceContent -> { PriceBlock( modifier = modifier, price = state.price, @@ -32,13 +37,12 @@ internal fun TokenPrice(state: TokenPriceChangeState?, modifier: Modifier = Modi priceChangePercent = state.priceChangePercent, ) } - is TokenPriceChangeState.Unknown -> { - PriceText(text = DASH_SIGN, modifier = modifier) - } - is TokenPriceChangeState.Loading -> { + is TokenPriceState.TextContent -> PriceText(text = state.value, modifier = modifier) + is TokenPriceState.Unknown -> PriceText(text = DASH_SIGN, modifier = modifier) + is TokenPriceState.Loading -> { RectangleShimmer(modifier = modifier.placeholderSize(), radius = TangemTheme.dimens.radius4) } - is TokenPriceChangeState.Locked -> { + is TokenPriceState.Locked -> { LockedRectangle(modifier = modifier.placeholderSize()) } null -> Unit @@ -122,4 +126,37 @@ private fun Modifier.placeholderSize(): Modifier = composed { return@composed this .padding(vertical = TangemTheme.dimens.spacing2) .size(width = TangemTheme.dimens.size52, height = TangemTheme.dimens.size12) -} \ No newline at end of file +} + +@Preview(showBackground = true) +@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES) +@Composable +private fun Preview(@PreviewParameter(TokenPriceChangeStateProvider::class) state: TokenPriceState) { + TangemThemePreview { + TokenPrice(state = state) + } +} + +private class TokenPriceChangeStateProvider : CollectionPreviewParameterProvider( + collection = listOf( + TokenPriceState.CryptoPriceContent( + price = "1.234", + priceChangePercent = "2.5%", + type = PriceChangeType.UP, + ), + TokenPriceState.CryptoPriceContent( + price = "1.234", + priceChangePercent = "2.5%", + type = PriceChangeType.DOWN, + ), + TokenPriceState.CryptoPriceContent( + price = "1.234", + priceChangePercent = "2.5%", + type = PriceChangeType.NEUTRAL, + ), + TokenPriceState.TextContent(value = "Subtitle"), + TokenPriceState.Unknown, + TokenPriceState.Loading, + TokenPriceState.Locked, + ), +) \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/token/state/TokenItemState.kt b/core/ui/src/main/java/com/tangem/core/ui/components/token/state/TokenItemState.kt index 832b849235..9c014e8067 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/token/state/TokenItemState.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/token/state/TokenItemState.kt @@ -4,58 +4,78 @@ import androidx.compose.runtime.Immutable import com.tangem.core.ui.components.currency.icon.CurrencyIconState import com.tangem.core.ui.components.marketprice.PriceChangeType -/** Token item state */ +/** TokenItem component state */ @Immutable sealed class TokenItemState { + /** Unique id */ abstract val id: String + /** Token icon state */ abstract val iconState: CurrencyIconState + /** Token title state (in one row with [fiatAmountState]) */ abstract val titleState: TitleState + /** Token subtitle state (under [titleState] and in one row with [cryptoAmountState]) */ + abstract val subtitleState: SubtitleState? + + /** Token fiat amount state (in one row with [titleState]) */ abstract val fiatAmountState: FiatAmountState? + /** Token crypto amount state (under [fiatAmountState] and in one row with [subtitleState]) */ abstract val cryptoAmountState: CryptoAmountState? - abstract val cryptoPriceState: CryptoPriceState? - - /** Loading token state */ + /** + * Loading token state + * + * @property id unique id + * @property iconState token icon state + * @property titleState token title + * @property subtitleState token subtitle + */ data class Loading( override val id: String, override val iconState: CurrencyIconState, override val titleState: TitleState.Content, + override val subtitleState: SubtitleState = SubtitleState.Loading, ) : TokenItemState() { override val fiatAmountState: FiatAmountState = FiatAmountState.Loading override val cryptoAmountState: CryptoAmountState = CryptoAmountState.Loading - override val cryptoPriceState: CryptoPriceState = CryptoPriceState.Loading } - /** Locked token state */ + /** + * Locked token state + * + * @property id unique id + */ data class Locked(override val id: String) : TokenItemState() { override val iconState: CurrencyIconState = CurrencyIconState.Locked override val titleState: TitleState = TitleState.Locked + override val subtitleState: SubtitleState = SubtitleState.Locked override val fiatAmountState: FiatAmountState = FiatAmountState.Locked override val cryptoAmountState: CryptoAmountState = CryptoAmountState.Locked - override val cryptoPriceState: CryptoPriceState = CryptoPriceState.Locked } /** * Content token state * - * @property id unique id - * @property iconState token icon state - * @property titleState token name - * @property onItemClick callback which will be called when an item is clicked - * @property onItemLongClick callback which will be called when an item is long clicked + * @property id unique id + * @property iconState token icon state + * @property titleState token title + * @property subtitleState token subtitle + * @property fiatAmountState token fiat amount + * @property cryptoAmountState token crypto amount + * @property onItemClick callback which will be called when an item is clicked + * @property onItemLongClick callback which will be called when an item is long clicked */ data class Content( override val id: String, override val iconState: CurrencyIconState, override val titleState: TitleState, + override val subtitleState: SubtitleState, override val fiatAmountState: FiatAmountState, override val cryptoAmountState: CryptoAmountState.Content, - override val cryptoPriceState: CryptoPriceState, val onItemClick: () -> Unit, val onItemLongClick: () -> Unit, ) : TokenItemState() @@ -63,9 +83,10 @@ sealed class TokenItemState { /** * Draggable token state * - * @property id unique id - * @property iconState token icon state - * @property titleState token name + * @property id unique id + * @property iconState token icon state + * @property titleState token title + * @property cryptoAmountState token crypto amount */ data class Draggable( override val id: String, @@ -73,48 +94,50 @@ sealed class TokenItemState { override val titleState: TitleState, override val cryptoAmountState: CryptoAmountState, ) : TokenItemState() { + override val subtitleState: SubtitleState? = null override val fiatAmountState: FiatAmountState? = null - override val cryptoPriceState: CryptoPriceState? = null } /** * Unreachable token state * - * @property id token id - * @property iconState token icon state - * @property titleState token name - * @property onItemClick callback which will be called when an item is clicked - * @property onItemLongClick callback which will be called when an item is long clicked + * @property id unique id + * @property iconState token icon state + * @property titleState token title + * @property subtitleState token subtitle + * @property onItemClick callback which will be called when an item is clicked + * @property onItemLongClick callback which will be called when an item is long clicked */ data class Unreachable( override val id: String, override val iconState: CurrencyIconState, override val titleState: TitleState, + override val subtitleState: SubtitleState? = null, val onItemClick: () -> Unit, val onItemLongClick: () -> Unit, ) : TokenItemState() { override val fiatAmountState: FiatAmountState? = null override val cryptoAmountState: CryptoAmountState? = null - override val cryptoPriceState: CryptoPriceState? = null } /** * No derivation address state * - * @property id token id - * @property iconState token icon state - * @property titleState token name - * @property onItemLongClick callback which will be called when an item is long clicked + * @property id unique id + * @property iconState token icon state + * @property titleState token title + * @property subtitleState token subtitle + * @property onItemLongClick callback which will be called when an item is long clicked */ data class NoAddress( override val id: String, override val iconState: CurrencyIconState, override val titleState: TitleState, + override val subtitleState: SubtitleState? = null, val onItemLongClick: () -> Unit, ) : TokenItemState() { override val fiatAmountState: FiatAmountState? = null override val cryptoAmountState: CryptoAmountState? = null - override val cryptoPriceState: CryptoPriceState? = null } @Immutable @@ -122,9 +145,27 @@ sealed class TokenItemState { data class Content(val text: String, val hasPending: Boolean = false) : TitleState() - object Loading : TitleState() + data object Loading : TitleState() - object Locked : TitleState() + data object Locked : TitleState() + } + + @Immutable + sealed class SubtitleState { + + data class CryptoPriceContent( + val price: String, + val priceChangePercent: String, + val type: PriceChangeType, + ) : SubtitleState() + + data class TextContent(val value: String) : SubtitleState() + + data object Unknown : SubtitleState() + + data object Loading : SubtitleState() + + data object Locked : SubtitleState() } @Immutable @@ -134,34 +175,19 @@ sealed class TokenItemState { val hasStaked: Boolean = false, ) : FiatAmountState() - object Loading : FiatAmountState() + data object Loading : FiatAmountState() - object Locked : FiatAmountState() + data object Locked : FiatAmountState() } @Immutable sealed class CryptoAmountState { data class Content(val text: String) : CryptoAmountState() - object Unreachable : CryptoAmountState() + data object Unreachable : CryptoAmountState() - object Loading : CryptoAmountState() + data object Loading : CryptoAmountState() - object Locked : CryptoAmountState() - } - - sealed class CryptoPriceState { - - data class Content( - val price: String, - val priceChangePercent: String?, - val type: PriceChangeType?, - ) : CryptoPriceState() - - object Unknown : CryptoPriceState() - - object Loading : CryptoPriceState() - - object Locked : CryptoPriceState() + data object Locked : CryptoAmountState() } } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/preview/WalletScreenPreviewData.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/preview/WalletScreenPreviewData.kt index 12dcfa641f..63585b4bbe 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/preview/WalletScreenPreviewData.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/preview/WalletScreenPreviewData.kt @@ -20,7 +20,7 @@ internal object WalletScreenPreviewData { titleState = TokenItemState.TitleState.Content(text = "Bitcoin"), fiatAmountState = TokenItemState.FiatAmountState.Content(text = "12 368,14 \$"), cryptoAmountState = TokenItemState.CryptoAmountState.Content(text = "0,35853044 BTC"), - cryptoPriceState = TokenItemState.CryptoPriceState.Content( + subtitleState = TokenItemState.SubtitleState.CryptoPriceContent( price = "34 496,75 \$", priceChangePercent = "0,43 %", type = PriceChangeType.DOWN, @@ -46,7 +46,7 @@ internal object WalletScreenPreviewData { titleState = TokenItemState.TitleState.Content(text = "Ethereum"), fiatAmountState = TokenItemState.FiatAmountState.Content(text = "3 340,79 \$"), cryptoAmountState = TokenItemState.CryptoAmountState.Content(text = "1,856660295 ETH"), - cryptoPriceState = TokenItemState.CryptoPriceState.Content( + subtitleState = TokenItemState.SubtitleState.CryptoPriceContent( price = "1 799,41 \$", priceChangePercent = "5,16 %", type = PriceChangeType.UP, @@ -68,7 +68,7 @@ internal object WalletScreenPreviewData { titleState = TokenItemState.TitleState.Content(text = "Shiba Inu"), fiatAmountState = TokenItemState.FiatAmountState.Content(text = "48,64 \$"), cryptoAmountState = TokenItemState.CryptoAmountState.Content(text = "6 200 220,00 SHIB"), - cryptoPriceState = TokenItemState.CryptoPriceState.Content( + subtitleState = TokenItemState.SubtitleState.CryptoPriceContent( price = "0.01 \$", priceChangePercent = "1,34 %", type = PriceChangeType.DOWN, diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/TokenItemStateConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/TokenItemStateConverter.kt index e47f61c804..e9752df81b 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/TokenItemStateConverter.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/TokenItemStateConverter.kt @@ -59,7 +59,7 @@ internal class TokenItemStateConverter( hasStaked = !getStakedBalance().isZero(), ), cryptoAmountState = TokenItemState.CryptoAmountState.Content(text = getFormattedAmount()), - cryptoPriceState = getCryptoPriceState(), + subtitleState = getCryptoPriceState(), onItemClick = { clickIntents.onTokenItemClick(this) }, onItemLongClick = { clickIntents.onTokenItemLongClick(cryptoCurrencyStatus = this) }, ) @@ -97,12 +97,12 @@ internal class TokenItemStateConverter( onItemLongClick = { clickIntents.onTokenItemLongClick(cryptoCurrencyStatus = this) }, ) - private fun CryptoCurrencyStatus.getCryptoPriceState(): TokenItemState.CryptoPriceState { + private fun CryptoCurrencyStatus.getCryptoPriceState(): TokenItemState.SubtitleState { val fiatRate = value.fiatRate val priceChange = value.priceChange return if (fiatRate != null && priceChange != null) { - TokenItemState.CryptoPriceState.Content( + TokenItemState.SubtitleState.CryptoPriceContent( price = fiatRate.getFormattedCryptoPrice(), priceChangePercent = BigDecimalFormatter.formatPercent( percent = priceChange, @@ -111,7 +111,7 @@ internal class TokenItemStateConverter( type = priceChange.getPriceChangeType(), ) } else { - TokenItemState.CryptoPriceState.Unknown + TokenItemState.SubtitleState.Unknown } }