From 139df9e5f491e7cac74cf0ce5d8881c5de9bfd45 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 10 Nov 2023 12:39:55 +0800 Subject: [PATCH] Updated on 2026-08-14 --- .../presentation/common/WalletPreviewData.kt | 9 ++- .../common/component/TokenItem.kt | 69 +++++++++-------- .../component/token/TokenCryptoAmount.kt | 6 +- .../common/component/token/TokenFiatAmount.kt | 2 +- .../{TokenPriceChange.kt => TokenPrice.kt} | 76 ++++++++++++------- .../common/state/TokenItemState.kt | 30 ++++---- ...ryptoCurrencyStatusToTokenItemConverter.kt | 25 ++++-- 7 files changed, 130 insertions(+), 87 deletions(-) rename features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/component/token/{TokenPriceChange.kt => TokenPrice.kt} (56%) diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/WalletPreviewData.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/WalletPreviewData.kt index d0829385fe..e4f7243ad9 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/WalletPreviewData.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/WalletPreviewData.kt @@ -2,10 +2,10 @@ package com.tangem.feature.wallet.presentation.common import androidx.paging.PagingData import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig +import com.tangem.core.ui.components.currency.tokenicon.TokenIconState import com.tangem.core.ui.components.marketprice.MarketPriceBlockState import com.tangem.core.ui.components.marketprice.PriceChangeState import com.tangem.core.ui.components.marketprice.PriceChangeType -import com.tangem.core.ui.components.currency.tokenicon.TokenIconState import com.tangem.core.ui.components.transactions.state.TransactionState import com.tangem.core.ui.components.transactions.state.TxHistoryState import com.tangem.core.ui.event.consumedEvent @@ -121,7 +121,7 @@ internal object WalletPreviewData { titleState = TokenItemState.TitleState.Content(text = "Polygon", hasPending = true), fiatAmountState = TokenItemState.FiatAmountState.Content(text = "321 $"), cryptoAmountState = TokenItemState.CryptoAmountState.Content(text = "5,412 MATIC"), - priceChangeState = TokenItemState.PriceChangeState.Unknown, + cryptoPriceState = TokenItemState.CryptoPriceState.Unknown, onItemClick = {}, onItemLongClick = {}, ) @@ -141,8 +141,9 @@ internal object WalletPreviewData { titleState = TokenItemState.TitleState.Content(text = "Polygon"), fiatAmountState = TokenItemState.FiatAmountState.Content(text = "321 $"), cryptoAmountState = TokenItemState.CryptoAmountState.Content(text = "5,412 MATIC"), - priceChangeState = TokenItemState.PriceChangeState.Content( - valueInPercent = "2.0%", + cryptoPriceState = TokenItemState.CryptoPriceState.Content( + price = "312 USD", + priceChangePercent = "2.0%", type = PriceChangeType.UP, ), onItemClick = {}, diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/component/TokenItem.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/component/TokenItem.kt index fbc0f7af7b..ac0fac75db 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/component/TokenItem.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/component/TokenItem.kt @@ -13,8 +13,8 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider import androidx.compose.ui.unit.Constraints -import com.tangem.core.ui.components.marketprice.PriceChangeType import com.tangem.core.ui.components.currency.tokenicon.TokenIcon +import com.tangem.core.ui.components.marketprice.PriceChangeType import com.tangem.core.ui.extensions.rememberHapticFeedback import com.tangem.core.ui.res.TangemTheme import com.tangem.feature.wallet.presentation.common.WalletPreviewData @@ -23,11 +23,11 @@ import com.tangem.feature.wallet.presentation.common.state.TokenItemState import org.burnoutcrew.reorderable.ReorderableLazyListState import kotlin.math.max -private const val TITLE_MIN_WIDTH_COEFFICIENT = 0.22 -private const val PRICE_CHANGE_MIN_WIDTH_COEFFICIENT = 0.16 +private const val TITLE_MIN_WIDTH_COEFFICIENT = 0.3 +private const val PRICE_MIN_WIDTH_COEFFICIENT = 0.32 private enum class LayoutId { - ICON, TITLE, FIAT_AMOUNT, CRYPTO_AMOUNT, PRICE_CHANGE, NON_FIAT_CONTENT + ICON, TITLE, FIAT_AMOUNT, CRYPTO_AMOUNT, CRYPTO_PRICE, NON_FIAT_CONTENT } @Composable @@ -37,6 +37,8 @@ internal fun TokenItem( modifier: Modifier = Modifier, reorderableTokenListState: ReorderableLazyListState? = null, ) { + val betweenRowsMargin = TangemTheme.dimens.spacing2 + CustomContainer( state = state, modifier = modifier @@ -50,7 +52,7 @@ internal fun TokenItem( modifier = Modifier .layoutId(layoutId = LayoutId.TITLE) .padding(horizontal = TangemTheme.dimens.spacing8) - .padding(bottom = TangemTheme.dimens.spacing2), + .padding(bottom = betweenRowsMargin), ) TokenFiatAmount( @@ -58,20 +60,20 @@ internal fun TokenItem( isBalanceHidden = isBalanceHidden, modifier = Modifier .layoutId(layoutId = LayoutId.FIAT_AMOUNT) - .padding(bottom = TangemTheme.dimens.spacing2), + .padding(bottom = betweenRowsMargin), + ) + + TokenPrice( + state = state.cryptoPriceState, + modifier = Modifier + .layoutId(layoutId = LayoutId.CRYPTO_PRICE) + .padding(horizontal = TangemTheme.dimens.spacing8), ) TokenCryptoAmount( state = state.cryptoAmountState, isBalanceHidden = isBalanceHidden, - modifier = Modifier - .layoutId(layoutId = LayoutId.CRYPTO_AMOUNT) - .padding(horizontal = TangemTheme.dimens.spacing8), - ) - - TokenPriceChange( - state = state.priceChangeState, - modifier = Modifier.layoutId(layoutId = LayoutId.PRICE_CHANGE), + modifier = Modifier.layoutId(layoutId = LayoutId.CRYPTO_AMOUNT), ) NonFiatContentBlock( @@ -117,10 +119,10 @@ private fun CustomContainer(state: TokenItemState, modifier: Modifier = Modifier val layoutWidth = constraints.maxWidth val layoutPadding = with(density) { dimens.size14.roundToPx() } - val layoutWidthWithPaddings = layoutWidth - 2 * layoutPadding + val layoutWidthWithoutPaddings = layoutWidth - 2 * layoutPadding val titleMinWidth = (layoutWidth * TITLE_MIN_WIDTH_COEFFICIENT).toInt() - val priceChangeMinWidth = (layoutWidth * PRICE_CHANGE_MIN_WIDTH_COEFFICIENT).toInt() + val priceChangeMinWidth = (layoutWidth * PRICE_MIN_WIDTH_COEFFICIENT).toInt() val icon = measurables.measure(layoutId = LayoutId.ICON, constraints = constraints) @@ -154,32 +156,32 @@ private fun CustomContainer(state: TokenItemState, modifier: Modifier = Modifier -> { fiatAmount = measurables.measureFiatAmount( state = state, - maxWidth = layoutWidthWithPaddings - icon.width - titleMinWidth, + maxWidth = layoutWidthWithoutPaddings - icon.width - titleMinWidth, defaultConstraints = constraints, ) cryptoAmount = measurables.measureCryptoAmount( state = state, - maxWidth = layoutWidthWithPaddings - icon.width - priceChangeMinWidth, + maxWidth = layoutWidthWithoutPaddings - icon.width - priceChangeMinWidth, defaultConstraints = constraints, ) - firstRowRemainingFreeSpace = layoutWidthWithPaddings - icon.width - fiatAmount.width - secondRowRemainingFreeSpace = layoutWidthWithPaddings - icon.width - cryptoAmount.width + firstRowRemainingFreeSpace = layoutWidthWithoutPaddings - icon.width - fiatAmount.width + secondRowRemainingFreeSpace = layoutWidthWithoutPaddings - icon.width - cryptoAmount.width } is TokenItemState.Draggable -> { cryptoAmount = measurables.measureCryptoAmount( state = state, - maxWidth = layoutWidthWithPaddings - icon.width - nonFiatContent.width, + maxWidth = layoutWidthWithoutPaddings - icon.width - nonFiatContent.width, defaultConstraints = constraints, ) - firstRowRemainingFreeSpace = layoutWidthWithPaddings - icon.width - nonFiatContent.width + firstRowRemainingFreeSpace = layoutWidthWithoutPaddings - icon.width - nonFiatContent.width } is TokenItemState.NoAddress, is TokenItemState.Unreachable, -> { - firstRowRemainingFreeSpace = layoutWidthWithPaddings - icon.width - nonFiatContent.width + firstRowRemainingFreeSpace = layoutWidthWithoutPaddings - icon.width - nonFiatContent.width } } @@ -223,18 +225,18 @@ private fun CustomContainer(state: TokenItemState, modifier: Modifier = Modifier }, ) - cryptoAmount?.placeRelative( - x = layoutPadding + icon.width, - y = layoutHeight - cryptoAmount.height - layoutPadding, - ) - fiatAmount?.placeRelative(x = layoutWidth - fiatAmount.width - layoutPadding, y = layoutPadding) priceChange?.placeRelative( - x = layoutWidth - priceChange.width - layoutPadding, + x = layoutPadding + icon.width, y = layoutHeight - priceChange.height - layoutPadding, ) + cryptoAmount?.placeRelative( + x = layoutWidth - cryptoAmount.width - layoutPadding, + y = layoutHeight - cryptoAmount.height - layoutPadding, + ) + nonFiatContent.placeRelative( x = layoutWidth - nonFiatContent.width - layoutPadding, y = (layoutHeight - nonFiatContent.height).div(other = 2), @@ -301,7 +303,7 @@ private fun List.measurePriceChange( defaultConstraints: Constraints, ): Placeable { return measure( - layoutId = LayoutId.PRICE_CHANGE, + layoutId = LayoutId.CRYPTO_PRICE, constraints = when (state) { is TokenItemState.Content, -> createDynamicConstrains(minWidth = minWidth, remainingFreeSpace = remainingFreeSpace) @@ -377,7 +379,7 @@ private fun calculateLayoutHeight( @Preview(widthDp = 360) @Composable -private fun Preview_CustomTokenItem_InLight(@PreviewParameter(TokenItemStateProvider::class) state: TokenItemState) { +private fun Preview_TokenItem_InLight(@PreviewParameter(TokenItemStateProvider::class) state: TokenItemState) { TangemTheme(isDark = false) { TokenItem(state = state, isBalanceHidden = false) } @@ -393,8 +395,9 @@ private class TokenItemStateProvider : CollectionPreviewParameterProvider { - PriceChangeBlock(modifier = modifier, type = state.type, text = state.valueInPercent) + PriceBlock( + modifier = modifier, + price = state.price, + type = state.type, + priceChangePercent = state.priceChangePercent, + ) } is TokenPriceChangeState.Unknown -> { - PriceChangeBlock(modifier = modifier) + PriceText(text = TokenItemState.UNKNOWN_AMOUNT_SIGN, modifier = modifier) } is TokenPriceChangeState.Loading -> { RectangleShimmer(modifier = modifier.placeholderSize(), radius = TangemTheme.dimens.radius4) @@ -39,24 +46,42 @@ internal fun TokenPriceChange(state: TokenPriceChangeState?, modifier: Modifier } @Composable -private fun PriceChangeBlock(modifier: Modifier = Modifier, type: PriceChangeType? = null, text: String? = null) { - Row( - modifier = modifier, - horizontalArrangement = Arrangement.End, - verticalAlignment = Alignment.CenterVertically, - ) { - PriceChangeIcon(type = type) - SpacerW4() - PriceChangeText(type = type, text = text) +private fun PriceBlock( + price: String, + modifier: Modifier = Modifier, + type: PriceChangeType? = null, + priceChangePercent: String? = null, +) { + Row(modifier = modifier, verticalAlignment = Alignment.CenterVertically) { + PriceText(text = price, modifier = Modifier.weight(weight = 1f, fill = false)) + + SpacerW6() + + if (type != null) { + PriceChangeIcon(type = type) + SpacerW4() + } + + PriceChangeText(type = type, text = priceChangePercent) } } -@OptIn(ExperimentalAnimationApi::class) @Composable -private fun PriceChangeIcon(type: PriceChangeType?) { - AnimatedContent(targetState = type, label = "Update the price change's arrow") { animatedType -> - animatedType ?: return@AnimatedContent +private fun PriceText(text: String, modifier: Modifier = Modifier) { + AnimatedContent(targetState = text, label = "Update the price text", modifier = modifier) { animatedText -> + Text( + text = animatedText, + color = TangemTheme.colors.text.tertiary, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + style = TangemTheme.typography.caption2, + ) + } +} +@Composable +private fun PriceChangeIcon(type: PriceChangeType) { + AnimatedContent(targetState = type, label = "Update the price change's arrow") { animatedType -> Icon( painter = painterResource( id = when (animatedType) { @@ -73,26 +98,25 @@ private fun PriceChangeIcon(type: PriceChangeType?) { } } -@OptIn(ExperimentalAnimationApi::class) @Composable -private fun PriceChangeText(type: PriceChangeType?, text: String?) { - AnimatedContent(targetState = text, label = "Update the price change's text") { animatedText -> +private fun PriceChangeText(type: PriceChangeType?, text: String?, modifier: Modifier = Modifier) { + AnimatedContent(targetState = text, modifier = modifier, label = "Update the price change's text") { animatedText -> Text( text = animatedText ?: TokenItemState.UNKNOWN_AMOUNT_SIGN, color = when (type) { PriceChangeType.UP -> TangemTheme.colors.text.accent PriceChangeType.DOWN -> TangemTheme.colors.text.warning - null -> TangemTheme.colors.text.primary1 + null -> TangemTheme.colors.text.tertiary }, overflow = TextOverflow.Ellipsis, maxLines = 1, - style = TangemTheme.typography.body2, + style = TangemTheme.typography.caption2, ) } } private fun Modifier.placeholderSize(): Modifier = composed { return@composed this - .padding(vertical = TangemTheme.dimens.spacing3) - .size(width = TangemTheme.dimens.size40, height = TangemTheme.dimens.size12) + .padding(vertical = TangemTheme.dimens.spacing2) + .size(width = TangemTheme.dimens.size52, height = TangemTheme.dimens.size12) } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/state/TokenItemState.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/state/TokenItemState.kt index 52bdd7abed..80fdc10cdc 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/state/TokenItemState.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/state/TokenItemState.kt @@ -1,8 +1,8 @@ package com.tangem.feature.wallet.presentation.common.state import androidx.compose.runtime.Immutable -import com.tangem.core.ui.components.marketprice.PriceChangeType import com.tangem.core.ui.components.currency.tokenicon.TokenIconState +import com.tangem.core.ui.components.marketprice.PriceChangeType /** Token item state */ @Immutable @@ -18,7 +18,7 @@ internal sealed class TokenItemState { abstract val cryptoAmountState: CryptoAmountState? - abstract val priceChangeState: PriceChangeState? + abstract val cryptoPriceState: CryptoPriceState? /** Loading token state */ data class Loading( @@ -28,7 +28,7 @@ internal sealed class TokenItemState { ) : TokenItemState() { override val fiatAmountState: FiatAmountState = FiatAmountState.Loading override val cryptoAmountState: CryptoAmountState = CryptoAmountState.Loading - override val priceChangeState: PriceChangeState = PriceChangeState.Loading + override val cryptoPriceState: CryptoPriceState = CryptoPriceState.Loading } /** Locked token state */ @@ -37,7 +37,7 @@ internal sealed class TokenItemState { override val titleState: TitleState = TitleState.Locked override val fiatAmountState: FiatAmountState = FiatAmountState.Locked override val cryptoAmountState: CryptoAmountState = CryptoAmountState.Locked - override val priceChangeState: PriceChangeState = PriceChangeState.Locked + override val cryptoPriceState: CryptoPriceState = CryptoPriceState.Locked } /** @@ -55,7 +55,7 @@ internal sealed class TokenItemState { override val titleState: TitleState, override val fiatAmountState: FiatAmountState, override val cryptoAmountState: CryptoAmountState.Content, - override val priceChangeState: PriceChangeState?, + override val cryptoPriceState: CryptoPriceState, val onItemClick: () -> Unit, val onItemLongClick: () -> Unit, ) : TokenItemState() @@ -74,7 +74,7 @@ internal sealed class TokenItemState { override val cryptoAmountState: CryptoAmountState, ) : TokenItemState() { override val fiatAmountState: FiatAmountState? = null - override val priceChangeState: PriceChangeState? = null + override val cryptoPriceState: CryptoPriceState? = null } /** @@ -95,7 +95,7 @@ internal sealed class TokenItemState { ) : TokenItemState() { override val fiatAmountState: FiatAmountState? = null override val cryptoAmountState: CryptoAmountState? = null - override val priceChangeState: PriceChangeState? = null + override val cryptoPriceState: CryptoPriceState? = null } /** @@ -114,7 +114,7 @@ internal sealed class TokenItemState { ) : TokenItemState() { override val fiatAmountState: FiatAmountState? = null override val cryptoAmountState: CryptoAmountState? = null - override val priceChangeState: PriceChangeState? = null + override val cryptoPriceState: CryptoPriceState? = null } @Immutable @@ -147,15 +147,19 @@ internal sealed class TokenItemState { object Locked : CryptoAmountState() } - sealed class PriceChangeState { + sealed class CryptoPriceState { - data class Content(val valueInPercent: String, val type: PriceChangeType) : PriceChangeState() + data class Content( + val price: String, + val priceChangePercent: String?, + val type: PriceChangeType?, + ) : CryptoPriceState() - object Unknown : PriceChangeState() + object Unknown : CryptoPriceState() - object Loading : PriceChangeState() + object Loading : CryptoPriceState() - object Locked : PriceChangeState() + object Locked : CryptoPriceState() } companion object { diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/CryptoCurrencyStatusToTokenItemConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/CryptoCurrencyStatusToTokenItemConverter.kt index df51275678..9ae6cfcdfb 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/CryptoCurrencyStatusToTokenItemConverter.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/CryptoCurrencyStatusToTokenItemConverter.kt @@ -1,12 +1,12 @@ package com.tangem.feature.wallet.presentation.wallet.utils import com.tangem.common.Provider +import com.tangem.core.ui.components.currency.tokenicon.converter.CryptoCurrencyToIconStateConverter import com.tangem.core.ui.components.marketprice.PriceChangeType import com.tangem.core.ui.utils.BigDecimalFormatter import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.feature.wallet.presentation.common.state.TokenItemState -import com.tangem.core.ui.components.currency.tokenicon.converter.CryptoCurrencyToIconStateConverter import com.tangem.feature.wallet.presentation.wallet.viewmodels.WalletClickIntents import com.tangem.utils.converter.Converter import java.math.BigDecimal @@ -53,7 +53,7 @@ internal class CryptoCurrencyStatusToTokenItemConverter( text = getFormattedFiatAmount(), ), cryptoAmountState = TokenItemState.CryptoAmountState.Content(text = getFormattedAmount()), - priceChangeState = getPriceChangeConfig(), + cryptoPriceState = getCryptoPriceState(), onItemClick = { clickIntents.onTokenItemClick(currency) }, onItemLongClick = { clickIntents.onTokenItemLongClick(cryptoCurrencyStatus = this) }, ) @@ -87,12 +87,14 @@ internal class CryptoCurrencyStatusToTokenItemConverter( onItemLongClick = { clickIntents.onTokenItemLongClick(cryptoCurrencyStatus = this) }, ) - private fun CryptoCurrencyStatus.getPriceChangeConfig(): TokenItemState.PriceChangeState { + private fun CryptoCurrencyStatus.getCryptoPriceState(): TokenItemState.CryptoPriceState { + val fiatRate = value.fiatRate val priceChange = value.priceChange - return if (priceChange != null) { - TokenItemState.PriceChangeState.Content( - valueInPercent = BigDecimalFormatter.formatPercent( + return if (fiatRate != null && priceChange != null) { + TokenItemState.CryptoPriceState.Content( + price = fiatRate.getFormattedCryptoPrice(), + priceChangePercent = BigDecimalFormatter.formatPercent( percent = priceChange, useAbsoluteValue = true, maxFractionDigits = 1, @@ -101,10 +103,19 @@ internal class CryptoCurrencyStatusToTokenItemConverter( type = priceChange.getPriceChangeType(), ) } else { - TokenItemState.PriceChangeState.Unknown + TokenItemState.CryptoPriceState.Unknown } } + private fun BigDecimal.getFormattedCryptoPrice(): String { + val appCurrency = appCurrencyProvider() + return BigDecimalFormatter.formatFiatAmount( + fiatAmount = this, + fiatCurrencyCode = appCurrency.code, + fiatCurrencySymbol = appCurrency.symbol, + ) + } + private fun BigDecimal.getPriceChangeType(): PriceChangeType { return if (this > BigDecimal.ZERO) PriceChangeType.UP else PriceChangeType.DOWN }