From 2c11ec0405cb3a7351bee9248b79e663f975a54e Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 7 Jul 2026 18:12:38 +0500 Subject: [PATCH] Updated on 2026-08-14 --- .../ds2/messagebanner/TangemMessageBanner.kt | 6 +- .../features/foryou/impl/entity/ForYouUM.kt | 11 +- .../features/foryou/impl/model/ForYouModel.kt | 52 +---- .../foryou/impl/model/ForYouNotification.kt | 2 +- .../converter/ForYouMarketChartConverter.kt | 55 +++++ .../converter/ForYouPortfolioFormatters.kt | 22 +- .../converter/ForYouTokenListConverter.kt | 88 ++++---- .../converter/ForYouTokenRowConverter.kt | 194 ++++++++++++++++-- .../SetPortfolioReviewTransformer.kt | 65 +++--- .../features/foryou/impl/ui/ForYouContent.kt | 43 ++-- .../foryou/impl/ui/ForYouPortfolioReview.kt | 19 +- .../ui/components/ForYouMarketChartContent.kt | 68 ------ .../ui/components/ForYouPortfolioTokenList.kt | 6 +- .../ForYouPortfolioReviewPreviewData.kt | 28 ++- 14 files changed, 406 insertions(+), 253 deletions(-) create mode 100644 features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/model/converter/ForYouMarketChartConverter.kt delete mode 100644 features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/ui/components/ForYouMarketChartContent.kt diff --git a/core/ui/src/main/java/com/tangem/core/ui/ds2/messagebanner/TangemMessageBanner.kt b/core/ui/src/main/java/com/tangem/core/ui/ds2/messagebanner/TangemMessageBanner.kt index 7cf3172777..570ff8a126 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/ds2/messagebanner/TangemMessageBanner.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/ds2/messagebanner/TangemMessageBanner.kt @@ -157,7 +157,7 @@ fun TangemMessageBanner( TangemMessageBanner( modifier = modifier, variant = state.variant, - showGlowRing = state.isShowGlowRing, + showGlowRing = state.shouldShowGlowRing, secondaryButton = state.secondaryButton, primaryButton = state.primaryButton, ) { @@ -341,7 +341,7 @@ object TangemMessageBanner { * @param title Banner headline. * @param variant Visual appearance — background color + glow ring. * @param contentAlign Horizontal alignment of the text block. - * @param isShowGlowRing Whether the glow ring is drawn around the banner. `false` shows only the + * @param shouldShowGlowRing Whether the glow ring is drawn around the banner. `false` shows only the * background. * @param description Secondary line under the [title]. `null` hides it. * @param secondaryButton Start action. `null` hides it. @@ -353,7 +353,7 @@ object TangemMessageBanner { val title: TextReference, val variant: Variant = Variant.Default, val contentAlign: ContentAlign = ContentAlign.Start, - val isShowGlowRing: Boolean = true, + val shouldShowGlowRing: Boolean = true, val description: TextReference? = null, val secondaryButton: Button? = null, val primaryButton: Button? = null, diff --git a/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/entity/ForYouUM.kt b/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/entity/ForYouUM.kt index 2e284a4c7f..d1832b2bcb 100644 --- a/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/entity/ForYouUM.kt +++ b/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/entity/ForYouUM.kt @@ -4,28 +4,29 @@ import androidx.compose.runtime.Immutable import com.tangem.core.ui.ds.row.token.TangemTokenRowUM import com.tangem.core.ui.ds.tabs.TangemSegmentUM import com.tangem.core.ui.ds.tabs.TangemSegmentedPickerUM -import com.tangem.core.ui.extensions.TextReference -import com.tangem.features.commonfeatures.api.choosetoken.model.WalletListUM +import com.tangem.features.foryou.impl.components.state.MarketChartUM +import com.tangem.features.foryou.impl.model.ForYouNotification import kotlinx.collections.immutable.ImmutableList internal data class ForYouUM( - val walletListUM: WalletListUM, val portfolioReviewUM: PortfolioReviewUM, + val notifications: ImmutableList, ) @Immutable internal sealed interface PortfolioReviewUM { val tokenList: ImmutableList + val marketChartUM: MarketChartUM data class Loading( override val tokenList: ImmutableList, + override val marketChartUM: MarketChartUM.NoData, ) : PortfolioReviewUM data class Content( override val tokenList: ImmutableList, + override val marketChartUM: MarketChartUM, val periodPickerUM: TangemSegmentedPickerUM, - val assetCount: TextReference, - val topHoldingPercent: TextReference, val onPeriodClick: (TangemSegmentUM) -> Unit, ) : PortfolioReviewUM } diff --git a/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/model/ForYouModel.kt b/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/model/ForYouModel.kt index 2ec2831339..3a1b5f1bfd 100644 --- a/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/model/ForYouModel.kt +++ b/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/model/ForYouModel.kt @@ -2,27 +2,20 @@ package com.tangem.features.foryou.impl.model import androidx.compose.runtime.Stable import arrow.core.getOrElse -import com.tangem.common.ui.userwallet.converter.WalletIconUMConverter import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.decompose.model.Model import com.tangem.core.ui.ds.row.token.TangemTokenRowUM import com.tangem.core.ui.ds.tabs.TangemSegmentUM -import com.tangem.core.ui.extensions.stringReference import com.tangem.domain.account.status.supplier.MultiAccountStatusListSupplier import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.common.wallets.UserWalletsListRepository -import com.tangem.domain.models.TotalFiatBalance -import com.tangem.domain.models.wallet.UserWalletId -import com.tangem.domain.wallets.usecase.GetWalletIconUseCase -import com.tangem.features.commonfeatures.api.choosetoken.model.WalletListUM -import com.tangem.features.commonfeatures.api.choosetoken.model.WalletTabUM +import com.tangem.features.foryou.impl.components.state.MarketChartUM import com.tangem.features.foryou.impl.entity.ForYouTokenListItemUM import com.tangem.features.foryou.impl.entity.ForYouUM import com.tangem.features.foryou.impl.entity.PortfolioReviewUM import com.tangem.features.foryou.impl.model.transformer.SetPortfolioReviewTransformer import com.tangem.utils.coroutines.CoroutineDispatcherProvider -import com.tangem.utils.extensions.orZero import com.tangem.utils.transformer.update import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toPersistentList @@ -36,19 +29,17 @@ internal class ForYouModel @Inject constructor( multiAccountStatusListSupplier: MultiAccountStatusListSupplier, override val dispatchers: CoroutineDispatcherProvider, private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase, - private val walletIconUMConverter: WalletIconUMConverter, - private val getWalletIconUseCase: GetWalletIconUseCase, ) : Model() { - private val locallySelectedWalletId = MutableStateFlow(value = null) private val expandedAssetIds = MutableStateFlow>(value = emptySet()) private val selectedAppCurrencyFlow: StateFlow = createSelectedAppCurrencyFlow() val uiState: StateFlow field = MutableStateFlow( ForYouUM( - walletListUM = WalletListUM(persistentListOf()), + notifications = persistentListOf(), portfolioReviewUM = PortfolioReviewUM.Loading( + marketChartUM = MarketChartUM.NoData, tokenList = buildList { repeat(4) { index -> add( @@ -69,35 +60,14 @@ internal class ForYouModel @Inject constructor( init { combine( - flow = userWalletsListRepository.userWallets, - flow2 = userWalletsListRepository.selectedUserWallet, - flow3 = multiAccountStatusListSupplier.invokeAsMap(), - flow4 = locallySelectedWalletId, - flow5 = expandedAssetIds, - ) { wallets, globalSelectedWallet, accountStatusList, locallySelected, expanded -> - val selectedId = locallySelected ?: globalSelectedWallet?.walletId - val tabs = wallets.orEmpty().map { wallet -> - WalletTabUM( - text = stringReference(wallet.name), - count = null, - isSelected = wallet.walletId == selectedId, - onClick = { onTabClick(wallet.walletId) }, - deviceIcon = walletIconUMConverter.convert(getWalletIconUseCase(wallet)), - ) - } - - val selectedAccountStatusList = accountStatusList[selectedId] - val currencies = selectedAccountStatusList?.flattenCurrencies().orEmpty() - val loadedBalance = selectedAccountStatusList?.totalFiatBalance as? TotalFiatBalance.Loaded - val totalFiatBalance = loadedBalance?.amount.orZero() - + flow = userWalletsListRepository.selectedUserWallet, + flow2 = multiAccountStatusListSupplier.invokeAsMap(), + flow3 = expandedAssetIds, + ) { globalSelectedWallet, accountStatusList, expanded -> + // TODO For You add choose portfolio flow uiState.update( SetPortfolioReviewTransformer( - walletListUM = WalletListUM( - items = if (tabs.size != 1) tabs.toPersistentList() else persistentListOf(), - ), - currencies = currencies, - totalFiatBalance = totalFiatBalance, + accountStatusList = accountStatusList[globalSelectedWallet?.walletId], appCurrency = selectedAppCurrencyFlow.value, expandedAssetIds = expanded, expandClick = ::onExpandClick, @@ -119,10 +89,6 @@ internal class ForYouModel @Inject constructor( ) } - private fun onTabClick(walletId: UserWalletId) { - locallySelectedWalletId.value = walletId - } - private fun onExpandClick(assetId: String) { expandedAssetIds.update { ids -> if (assetId in ids) ids - assetId else ids + assetId diff --git a/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/model/ForYouNotification.kt b/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/model/ForYouNotification.kt index 612fe713bb..e3903bf09c 100644 --- a/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/model/ForYouNotification.kt +++ b/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/model/ForYouNotification.kt @@ -20,7 +20,7 @@ internal sealed class ForYouNotification(val state: TangemMessageBanner.State) { tintReference = { TangemTheme.colors3.icon.primary }, ), variant = TangemMessageBanner.Variant.Warning, - isShowGlowRing = false, + shouldShowGlowRing = false, ), ) } \ No newline at end of file diff --git a/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/model/converter/ForYouMarketChartConverter.kt b/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/model/converter/ForYouMarketChartConverter.kt new file mode 100644 index 0000000000..e767adc242 --- /dev/null +++ b/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/model/converter/ForYouMarketChartConverter.kt @@ -0,0 +1,55 @@ +package com.tangem.features.foryou.impl.model.converter + +import com.tangem.core.ui.extensions.stringReference +import com.tangem.core.ui.format.bigdecimal.fiat +import com.tangem.core.ui.format.bigdecimal.format +import com.tangem.core.ui.format.bigdecimal.percent +import com.tangem.domain.appcurrency.model.AppCurrency +import com.tangem.domain.models.TotalFiatBalance +import com.tangem.domain.models.currency.CryptoCurrencyStatus +import com.tangem.features.foryou.impl.components.state.* +import com.tangem.utils.converter.Converter +import com.tangem.utils.extensions.orZero +import kotlinx.collections.immutable.toPersistentList +import java.math.BigDecimal + +internal class ForYouMarketChartConverter( + private val appCurrency: AppCurrency, + private val topAssets: List, BigDecimal>>, +) : Converter { + override fun convert(value: TotalFiatBalance?): MarketChartUM { + val topBalance = topAssets.sumOf { (_, assetBalance) -> assetBalance } + return when (value) { + is TotalFiatBalance.Loaded -> MarketChartUM.Loaded( + donutChart = DonutChartUM.Loaded( + totalAmount = value.amount.format { + fiat( + fiatCurrencySymbol = appCurrency.symbol, + fiatCurrencyCode = appCurrency.code, + ) + }, + donutSegmentList = topAssets.mapIndexed { index, (currencies, segmentBalance) -> + val segmentWeight = segmentBalance.toForYouPercent(value.amount).orZero() + DonutSegmentUM( + color = DonutSegmentColor.entries.getOrNull(index) ?: DonutSegmentColor.Brand, + weight = segmentWeight, + title = stringReference(currencies.firstOrNull()?.currency?.name.orEmpty()), + fiatValue = stringReference(segmentBalance.format { + fiat( + fiatCurrencyCode = appCurrency.code, + fiatCurrencySymbol = appCurrency.symbol, + ) + }), + ) + }.toPersistentList(), + ), + aiInsight = AiInsightUM.Hide, + topHoldingPercent = stringReference(topBalance.toForYouPercent(value.amount).format { percent() }), + ) + TotalFiatBalance.Loading, + TotalFiatBalance.Failed, + null, + -> MarketChartUM.NoData + } + } +} \ No newline at end of file diff --git a/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/model/converter/ForYouPortfolioFormatters.kt b/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/model/converter/ForYouPortfolioFormatters.kt index 511ececb33..5596541fff 100644 --- a/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/model/converter/ForYouPortfolioFormatters.kt +++ b/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/model/converter/ForYouPortfolioFormatters.kt @@ -4,12 +4,7 @@ import com.tangem.core.ui.ds.badge.TangemBadgeColor import com.tangem.core.ui.ds.badge.TangemBadgeSize import com.tangem.core.ui.ds.badge.TangemBadgeType import com.tangem.core.ui.ds.badge.TangemBadgeUM -import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.stringReference -import com.tangem.core.ui.format.bigdecimal.fiat -import com.tangem.core.ui.format.bigdecimal.format -import com.tangem.core.ui.format.bigdecimal.percent -import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.models.currency.CryptoCurrencyStatus import com.tangem.utils.extensions.isZero import java.math.BigDecimal @@ -19,7 +14,7 @@ import java.math.RoundingMode * Formatting helpers shared by the For You portfolio-review converters. * * Kept null-safe so that non-[CryptoCurrencyStatus.Loaded] states (which carry no fiat amount) degrade - * to a dash / empty instead of throwing. + * to `null` instead of throwing. */ /** @@ -29,18 +24,13 @@ import java.math.RoundingMode */ internal fun CryptoCurrencyStatus.forYouGroupKey(): String = currency.id.rawCurrencyId?.value ?: currency.id.value -/** Formats a (possibly null) fiat amount in [appCurrency]; a `null` amount renders as a dash. */ -internal fun BigDecimal?.toForYouFiatText(appCurrency: AppCurrency): TextReference = stringReference( - format { fiat(fiatCurrencyCode = appCurrency.code, fiatCurrencySymbol = appCurrency.symbol) }, -) - /** - * Formats this fiat amount as a share of [totalFiatBalance]. Returns [TextReference.EMPTY] when the - * share cannot be computed (no amount, or a zero total / amount). + * Computes this fiat amount as a share of [totalFiatBalance]. Returns `null` when the share cannot be + * computed (no amount, or a zero total / amount). */ -internal fun BigDecimal?.toForYouPercentText(totalFiatBalance: BigDecimal): TextReference { - if (this == null || totalFiatBalance.isZero() || isZero()) return TextReference.EMPTY - return stringReference(divide(totalFiatBalance, RoundingMode.HALF_UP).format { percent() }) +internal fun BigDecimal?.toForYouPercent(totalFiatBalance: BigDecimal): BigDecimal? { + if (this == null || totalFiatBalance.isZero() || isZero()) return null + return divide(totalFiatBalance, RoundingMode.HALF_UP) } // TODO For You: replace this placeholder with the real price-change badge once the design is wired. diff --git a/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/model/converter/ForYouTokenListConverter.kt b/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/model/converter/ForYouTokenListConverter.kt index 21ba003146..562dfeb547 100644 --- a/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/model/converter/ForYouTokenListConverter.kt +++ b/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/model/converter/ForYouTokenListConverter.kt @@ -5,8 +5,12 @@ import com.tangem.core.ui.components.currency.icon.CurrencyIconState import com.tangem.core.ui.ds.image.TangemIconUM import com.tangem.core.ui.ds.row.token.TangemTokenRowUM import com.tangem.core.ui.extensions.TextReference +import com.tangem.core.ui.extensions.pluralReference import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.extensions.stringReference +import com.tangem.core.ui.format.bigdecimal.fiat +import com.tangem.core.ui.format.bigdecimal.format +import com.tangem.core.ui.format.bigdecimal.percent import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.currency.CryptoCurrencyStatus @@ -29,14 +33,12 @@ import java.math.BigDecimal * * Modelled on `TokenListStateConverter` (a list converter delegating to a per-item converter). */ -@Suppress("LongParameterList") internal class ForYouTokenListConverter( private val appCurrency: AppCurrency, private val totalFiatBalance: BigDecimal, private val expandedAssetIds: Set, private val expandClick: (assetId: String) -> Unit, - private val otherAssetCount: Int, - private val otherFiatBalance: BigDecimal, + private val otherAssets: List, BigDecimal>>, ) : Converter, ImmutableList> { private val iconConverter = CryptoCurrencyToIconStateConverter() @@ -48,7 +50,7 @@ internal class ForYouTokenListConverter( .map { (assetId, currencies) -> createListItem(assetId, currencies) } // Assets beyond the top ones are collapsed into a single non-expandable "Other" row at the bottom. - return if (otherAssetCount > 0) { + return if (otherAssets.count() > 0) { assetItems + createOtherItem() } else { assetItems @@ -87,16 +89,16 @@ internal class ForYouTokenListConverter( val asset = currencies.first() val assetFiatBalance = currencies.sumOf { it.value.fiatAmount.orZero() } - val subtitle = if (networkCount > 1) { - stringReference("$networkCount networks") - } else { - val onlyCryptoCurrency = currencies.firstOrNull()?.currency - val isMain = onlyCryptoCurrency is CryptoCurrency.Coin - when { - isMain -> resourceReference(R.string.common_main_network) - onlyCryptoCurrency != null -> stringReference(onlyCryptoCurrency.network.standardType.name) - else -> TextReference.EMPTY - } + val endContent = rowConverter.toEndContent(statuses = currencies, fiatAmount = assetFiatBalance) + + val onlyCryptoCurrency = currencies.firstOrNull()?.currency + val isMain = onlyCryptoCurrency is CryptoCurrency.Coin + + val subtitle = when { + networkCount > 1 -> pluralReference(R.plurals.common_networks_count, networkCount) + isMain -> resourceReference(R.string.common_main_network) + onlyCryptoCurrency != null -> stringReference(onlyCryptoCurrency.network.standardType.name) + else -> TextReference.EMPTY } return TangemTokenRowUM.Content( @@ -109,38 +111,44 @@ internal class ForYouTokenListConverter( subtitleUM = TangemTokenRowUM.SubtitleUM.Content( text = subtitle, ), - topEndContentUM = TangemTokenRowUM.EndContentUM.Content( - text = assetFiatBalance.toForYouFiatText(appCurrency), - ), - bottomEndContentUM = TangemTokenRowUM.EndContentUM.Content( - text = assetFiatBalance.toForYouPercentText(totalFiatBalance), - ), + topEndContentUM = endContent.top, + bottomEndContentUM = endContent.bottom, onItemClick = { expandClick(assetId) }, onItemLongClick = null, ) } - private fun createOtherItem(): ForYouTokenListItemUM = ForYouTokenListItemUM( - tokenRowUM = TangemTokenRowUM.Content( - id = OTHER_ROW_ID, - headIconUM = TangemIconUM.Currency(CurrencyIconState.Empty()), - titleUM = TangemTokenRowUM.TitleUM.Content(text = stringReference("Other")), - subtitleUM = TangemTokenRowUM.SubtitleUM.Content( - text = stringReference(if (otherAssetCount > 1) "$otherAssetCount assets" else "1 asset"), + private fun createOtherItem(): ForYouTokenListItemUM { + val otherAssetsBalance = otherAssets.sumOf { (_, assetBalance) -> assetBalance } + return ForYouTokenListItemUM( + tokenRowUM = TangemTokenRowUM.Content( + id = OTHER_ROW_ID, + headIconUM = TangemIconUM.Currency(CurrencyIconState.Empty()), + titleUM = TangemTokenRowUM.TitleUM.Content(text = resourceReference(R.string.common_other)), + subtitleUM = TangemTokenRowUM.SubtitleUM.Content( + text = pluralReference(R.plurals.common_assets, otherAssets.count()), + ), + topEndContentUM = TangemTokenRowUM.EndContentUM.Content( + text = stringReference( + otherAssetsBalance.format { + fiat( + fiatCurrencyCode = appCurrency.code, + fiatCurrencySymbol = appCurrency.symbol, + ) + }, + ), + ), + bottomEndContentUM = TangemTokenRowUM.EndContentUM.Content( + text = stringReference(otherAssetsBalance.toForYouPercent(totalFiatBalance).format { percent() }), + ), + onItemClick = null, + onItemLongClick = null, ), - topEndContentUM = TangemTokenRowUM.EndContentUM.Content( - text = otherFiatBalance.toForYouFiatText(appCurrency), - ), - bottomEndContentUM = TangemTokenRowUM.EndContentUM.Content( - text = otherFiatBalance.toForYouPercentText(totalFiatBalance), - ), - onItemClick = null, - onItemLongClick = null, - ), - tokenList = persistentListOf(), - isExpanded = false, - isExpandable = false, - ) + tokenList = persistentListOf(), + isExpanded = false, + isExpandable = false, + ) + } private companion object { const val OTHER_ROW_ID = "for_you_other_assets" diff --git a/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/model/converter/ForYouTokenRowConverter.kt b/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/model/converter/ForYouTokenRowConverter.kt index 5a917c91a5..c9ae8985eb 100644 --- a/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/model/converter/ForYouTokenRowConverter.kt +++ b/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/model/converter/ForYouTokenRowConverter.kt @@ -1,15 +1,25 @@ package com.tangem.features.foryou.impl.model.converter +import androidx.compose.ui.text.SpanStyle import com.tangem.common.ui.components.currency.icon.converter.CryptoCurrencyToIconStateConverter +import com.tangem.core.ui.R import com.tangem.core.ui.ds.image.TangemIconUM import com.tangem.core.ui.ds.row.token.TangemTokenRowUM import com.tangem.core.ui.extensions.stringReference +import com.tangem.core.ui.extensions.styledResourceReference import com.tangem.core.ui.format.bigdecimal.crypto +import com.tangem.core.ui.format.bigdecimal.fiat import com.tangem.core.ui.format.bigdecimal.format +import com.tangem.core.ui.format.bigdecimal.percent +import com.tangem.core.ui.res.TangemTheme import com.tangem.domain.appcurrency.model.AppCurrency +import com.tangem.domain.models.StatusSource +import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.currency.CryptoCurrencyStatus import com.tangem.utils.StringsSigns import com.tangem.utils.extensions.orZero +import kotlinx.collections.immutable.persistentListOf +import kotlinx.collections.immutable.toImmutableList import java.math.BigDecimal /** @@ -18,6 +28,16 @@ import java.math.BigDecimal * The input is all [CryptoCurrencyStatus]es of one asset on the *same* network (the asset may be held in * several accounts on that network). They are aggregated into one row — the crypto amount and fiat balance * are the per-network totals — so a network never appears twice within an asset's expanded breakdown. + * + * 1. all [CryptoCurrencyStatus.Loading] → a Loading row; + * 2. any [CryptoCurrencyStatus.MissedDerivation] → "no address" treatment (missing address dominates — + * the balance for that portion can't be trusted); + * 3. any [CryptoCurrencyStatus.Unreachable] / [CryptoCurrencyStatus.NoAmount] → "unreachable" treatment; + * 4. otherwise a normal content row summing the loaded/custom/no-quote/no-account amounts. + * + * [CryptoCurrencyStatus.Loading] entries inside an otherwise-resolved group are ignored for + * classification (they contribute nothing yet). The cache/flicker indicators derive from the most + * conservative [CryptoCurrencyStatus.Sources.total] across the contributing statuses. */ internal class ForYouTokenRowConverter( private val appCurrency: AppCurrency, @@ -36,26 +56,170 @@ internal class ForYouTokenRowConverter( val currency = representative.currency val cryptoAmount = statuses.sumOf { it.value.amount.orZero() } val fiatAmount = statuses.sumOf { it.value.fiatAmount.orZero() } + val state = statuses.classify() + return TangemTokenRowUM.Content( id = currency.id.value, headIconUM = TangemIconUM.Currency(iconConverter.convert(representative)), - titleUM = TangemTokenRowUM.TitleUM.Content( - text = stringReference(currency.symbol), - badge = forYouPlaceholderBadge(), - ), - subtitleUM = TangemTokenRowUM.SubtitleUM.Content( - text = stringReference( - "${currency.network.name} ${StringsSigns.DOT} ${cryptoAmount.format { crypto( - cryptoCurrency = currency, - ) }}", - ), - ), - topEndContentUM = TangemTokenRowUM.EndContentUM.Content(text = fiatAmount.toForYouFiatText(appCurrency)), - bottomEndContentUM = TangemTokenRowUM.EndContentUM.Content( - text = fiatAmount.toForYouPercentText(totalFiatBalance), - ), + titleUM = toRowTitle(currency), + subtitleUM = toRowSubtitle(state, currency, cryptoAmount), + topEndContentUM = toRowTopEnd(state, fiatAmount), + bottomEndContentUM = toRowBottomEnd(state, fiatAmount), onItemClick = null, onItemLongClick = null, ) } + + /** + * Maps the aggregate status of [statuses] onto a row's top/bottom end content, rendering the given + * pre-summed [fiatAmount]. Reflects the same cache-flicker / could-not-refresh / no-address / + * unreachable treatment as [convertNetworkGroup], so the asset-level row surfaces the combined status + * of its holdings — analogous to how `AccountCryptoPortfolioItemStateConverter` reflects a + * `TotalFiatBalance`'s status on the account row. + * + * Callers must handle the all-[CryptoCurrencyStatus.Loading] case (a Loading row) before calling this. + */ + fun toEndContent(statuses: List, fiatAmount: BigDecimal): EndContent { + val state = statuses.classify() + return EndContent( + top = toRowTopEnd(state, fiatAmount), + bottom = toRowBottomEnd(state, fiatAmount), + ) + } + + /** Title: For You always shows the asset symbol with the placeholder price-change badge. */ + private fun toRowTitle(currency: CryptoCurrency): TangemTokenRowUM.TitleUM = TangemTokenRowUM.TitleUM.Content( + text = stringReference(currency.symbol), + badge = forYouPlaceholderBadge(), + ) + + /** + * Subtitle: `network • amount` for resolved states, error messaging otherwise. Kept single-line to + * match For You's style (no separate price-change line as in the wallet). + */ + private fun toRowSubtitle( + state: RowState, + currency: CryptoCurrency, + cryptoAmount: BigDecimal, + ): TangemTokenRowUM.SubtitleUM = when (state) { + is RowState.Normal -> TangemTokenRowUM.SubtitleUM.Content( + text = stringReference( + "${currency.network.name} ${StringsSigns.DOT} ${ + cryptoAmount.format { + crypto( + cryptoCurrency = currency, + ) + } + }", + ), + isFlickering = state.isFlickering, + ) + RowState.NoAddress -> TangemTokenRowUM.SubtitleUM.Content( + text = stringReference("${currency.network.name} ${StringsSigns.DOT} ${StringsSigns.DASH_SIGN}"), + ) + RowState.Unreachable -> TangemTokenRowUM.SubtitleUM.Content( + text = stringReference(currency.network.name), + ) + } + + /** Top-end: fiat total for resolved states, dash / unreachable treatment otherwise. */ + private fun toRowTopEnd(state: RowState, fiatAmount: BigDecimal): TangemTokenRowUM.EndContentUM = when (state) { + is RowState.Normal -> TangemTokenRowUM.EndContentUM.Content( + text = stringReference( + fiatAmount.format { + fiat( + fiatCurrencyCode = appCurrency.code, + fiatCurrencySymbol = appCurrency.symbol, + ) + }, + ), + isFlickering = state.isFlickering, + startIcons = buildList { + if (state.isOnlyCache) { + add( + TangemIconUM.Icon( + iconRes = R.drawable.ic_error_sync_default_24, + tintReference = { TangemTheme.colors3.icon.tertiary }, + ), + ) + } + }.toImmutableList(), + ) + RowState.NoAddress, + RowState.Unreachable, + -> TangemTokenRowUM.EndContentUM.Content(text = stringReference(StringsSigns.DASH_SIGN)) + } + + /** Bottom-end: percentage share for resolved states, no-address / unreachable treatment otherwise. */ + private fun toRowBottomEnd(state: RowState, fiatAmount: BigDecimal): TangemTokenRowUM.EndContentUM = when (state) { + is RowState.Normal -> TangemTokenRowUM.EndContentUM.Content( + text = stringReference(fiatAmount.toForYouPercent(totalFiatBalance).format { percent() }), + isFlickering = state.isFlickering, + ) + RowState.NoAddress -> attentionEndContent(R.string.common_no_address) + RowState.Unreachable -> attentionEndContent(R.string.common_unreachable) + } + + private fun attentionEndContent(textRes: Int): TangemTokenRowUM.EndContentUM = + TangemTokenRowUM.EndContentUM.Content( + text = styledResourceReference( + id = textRes, + spanStyleReference = { SpanStyle(color = TangemTheme.colors3.text.status.warning) }, + ), + endIcons = persistentListOf( + TangemIconUM.Icon( + iconRes = R.drawable.ic_attention_default_24, + tintReference = { TangemTheme.colors3.icon.status.warning }, + ), + ), + ) + + /** + * Collapses a mixed group into a single [RowState]. Loading-only groups are handled earlier, so a + * group reaching here has at least one non-loading status. See the class KDoc for the priority rule. + */ + private fun List.classify(): RowState { + val resolved = filterNot { it.value is CryptoCurrencyStatus.Loading }.map { it.value } + return when { + resolved.any { it is CryptoCurrencyStatus.MissedDerivation } -> RowState.NoAddress + resolved.any { + it is CryptoCurrencyStatus.Unreachable || it is CryptoCurrencyStatus.NoAmount + } -> RowState.Unreachable + else -> { + val worstSource = resolved.map { it.sources.total }.worst() + RowState.Normal( + isFlickering = worstSource == StatusSource.CACHE, + isOnlyCache = worstSource == StatusSource.ONLY_CACHE, + ) + } + } + } + + /** + * The most conservative status across the group: any [StatusSource.ONLY_CACHE] (could-not-refresh) + * dominates a [StatusSource.CACHE] (still refreshing), which in turn dominates [StatusSource.ACTUAL]. + */ + private fun List.worst(): StatusSource = when { + any { it == StatusSource.ONLY_CACHE } -> StatusSource.ONLY_CACHE + any { it == StatusSource.CACHE } -> StatusSource.CACHE + else -> StatusSource.ACTUAL + } + + /** The top and bottom end content of a token row, produced together from one classified group. */ + data class EndContent( + val top: TangemTokenRowUM.EndContentUM, + val bottom: TangemTokenRowUM.EndContentUM, + ) + + /** Rendering-relevant collapse of the group's per-currency-status states. */ + private sealed interface RowState { + /** Loaded / Custom / NoQuote / NoAccount — normal amounts, with cache/flicker indicators. */ + data class Normal(val isFlickering: Boolean, val isOnlyCache: Boolean) : RowState + + /** At least one MissedDerivation — no blockchain address obtained. */ + data object NoAddress : RowState + + /** At least one Unreachable / NoAmount — network could not be reached. */ + data object Unreachable : RowState + } } \ No newline at end of file diff --git a/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/model/transformer/SetPortfolioReviewTransformer.kt b/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/model/transformer/SetPortfolioReviewTransformer.kt index e9dc792f7c..99874a142f 100644 --- a/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/model/transformer/SetPortfolioReviewTransformer.kt +++ b/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/model/transformer/SetPortfolioReviewTransformer.kt @@ -3,38 +3,35 @@ package com.tangem.features.foryou.impl.model.transformer import com.tangem.core.ui.ds.tabs.TangemSegmentUM import com.tangem.core.ui.ds.tabs.TangemSegmentedPickerUM import com.tangem.core.ui.extensions.stringReference -import com.tangem.core.ui.format.bigdecimal.format -import com.tangem.core.ui.format.bigdecimal.percent +import com.tangem.domain.account.models.AccountStatusList import com.tangem.domain.appcurrency.model.AppCurrency -import com.tangem.domain.models.currency.CryptoCurrencyStatus -import com.tangem.features.commonfeatures.api.choosetoken.model.WalletListUM +import com.tangem.domain.models.StatusSource +import com.tangem.domain.models.TotalFiatBalance import com.tangem.features.foryou.impl.entity.ForYouUM import com.tangem.features.foryou.impl.entity.PortfolioReviewUM +import com.tangem.features.foryou.impl.model.ForYouNotification +import com.tangem.features.foryou.impl.model.converter.ForYouMarketChartConverter import com.tangem.features.foryou.impl.model.converter.ForYouTokenListConverter import com.tangem.features.foryou.impl.model.converter.forYouGroupKey -import com.tangem.utils.StringsSigns import com.tangem.utils.extensions.isZero import com.tangem.utils.extensions.orZero import com.tangem.utils.transformer.Transformer import kotlinx.collections.immutable.persistentListOf -import java.math.BigDecimal -import java.math.RoundingMode /** - * Builds the [ForYouUM] state for the For You screen: the wallet tabs plus the portfolio - * review (asset count, top-holding share, period picker and the grouped token list). + * Builds the [ForYouUM] state for the For You screen: the outdated-data notifications plus the portfolio + * review (market chart, period picker and the grouped token list). * - * The token list is delegated to [ForYouTokenListConverter]; the period picker selection is carried - * over from the previous state so it is not reset on every balance refresh. + * The token list is delegated to [ForYouTokenListConverter] and the market chart to + * [ForYouMarketChartConverter]; the period picker selection is carried over from the previous state so + * it is not reset on every balance refresh. * * Modelled on `SetTokenListTransformer` (a transformer that rebuilds the state while delegating the * token-list construction to a dedicated converter). */ @Suppress("LongParameterList") internal class SetPortfolioReviewTransformer( - private val walletListUM: WalletListUM, - private val currencies: List, - private val totalFiatBalance: BigDecimal, + private val accountStatusList: AccountStatusList?, private val appCurrency: AppCurrency, private val expandedAssetIds: Set, private val expandClick: (assetId: String) -> Unit, @@ -42,10 +39,18 @@ internal class SetPortfolioReviewTransformer( ) : Transformer { override fun transform(prevState: ForYouUM): ForYouUM { - // Drop empty networks, then aggregate the rest into assets (the same token across networks shares - // its forYouGroupKey) and rank assets by their *summed* fiat balance. + val currencies = accountStatusList?.flattenCurrencies().orEmpty() + val loadedBalance = accountStatusList?.totalFiatBalance as? TotalFiatBalance.Loaded + val totalFiatBalance = loadedBalance?.amount.orZero() + + // Drop only assets we positively know are empty — a resolved, priced zero fiat balance. Currencies + // whose fiat we couldn't determine (unreachable / no-address / no-quote / still-loading — i.e. any + // non-content status, which all carry a null fiatAmount) are kept so the converter can still render + // them with the appropriate treatment instead of hiding a token the user actually holds. + // Then aggregate the rest into assets (the same token across networks shares its forYouGroupKey) + // and rank assets by their *summed* fiat balance. val rankedAssets = currencies - .filterNot { it.value.fiatAmount.orZero().isZero() } + .filterNot { it.value.fiatAmount?.isZero() == true } .groupBy { it.forYouGroupKey() } .map { (_, networks) -> networks to networks.sumOf { it.value.fiatAmount.orZero() } } .sortedByDescending { (_, assetBalance) -> assetBalance } @@ -55,40 +60,38 @@ internal class SetPortfolioReviewTransformer( val topAssets = rankedAssets.take(TOP_HOLDINGS_COUNT) val otherAssets = rankedAssets.drop(TOP_HOLDINGS_COUNT) val topCurrencies = topAssets.flatMap { (networks, _) -> networks } - val topBalance = topAssets.sumOf { (_, assetBalance) -> assetBalance } val tokenList = ForYouTokenListConverter( appCurrency = appCurrency, totalFiatBalance = totalFiatBalance, expandedAssetIds = expandedAssetIds, expandClick = expandClick, - otherAssetCount = otherAssets.size, - otherFiatBalance = otherAssets.sumOf { (_, assetBalance) -> assetBalance }, + otherAssets = otherAssets, ).convert(topCurrencies) + val marketChartUM = ForYouMarketChartConverter( + appCurrency = appCurrency, + topAssets = topAssets, + ).convert(accountStatusList?.totalFiatBalance) + return prevState.copy( - walletListUM = walletListUM, + notifications = if (loadedBalance?.source == StatusSource.ONLY_CACHE) { + persistentListOf(ForYouNotification.UsedOutdatedData) + } else { + persistentListOf() + }, portfolioReviewUM = PortfolioReviewUM.Content( - assetCount = stringReference("${rankedAssets.size} assets"), // TODO For You lokalize - topHoldingPercent = stringReference("Top holding ${topHoldingPercent(topBalance)}"), periodPickerUM = when (prevState.portfolioReviewUM) { is PortfolioReviewUM.Content -> prevState.portfolioReviewUM.periodPickerUM is PortfolioReviewUM.Loading -> createPeriodPicker() }, tokenList = tokenList, + marketChartUM = marketChartUM, onPeriodClick = onPeriodClick, ), ) } - private fun topHoldingPercent(topBalance: BigDecimal): String { - return if (!totalFiatBalance.isZero() && !topBalance.isZero()) { - topBalance.divide(totalFiatBalance, RoundingMode.HALF_UP).format { percent() } - } else { - StringsSigns.DASH_SIGN - } - } - private fun createPeriodPicker(): TangemSegmentedPickerUM { // TODO For you replace with data from backend val day = TangemSegmentUM(id = "0", title = stringReference("Day")) diff --git a/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/ui/ForYouContent.kt b/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/ui/ForYouContent.kt index baec193e24..f4d1a5f9f6 100644 --- a/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/ui/ForYouContent.kt +++ b/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/ui/ForYouContent.kt @@ -16,17 +16,16 @@ import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.PreviewParameterProvider import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp +import androidx.compose.ui.util.fastForEachIndexed import com.tangem.core.ui.components.SpacerH import com.tangem.core.ui.components.bottomsheets.state.BottomSheetState -import com.tangem.core.ui.ds.image.DeviceIconUM -import com.tangem.core.ui.extensions.stringReference +import com.tangem.core.ui.ds2.messagebanner.TangemMessageBanner +import com.tangem.core.ui.extensions.conditional import com.tangem.core.ui.res.LocalMainBottomSheetColor import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreviewRedesign -import com.tangem.features.commonfeatures.api.choosetoken.model.WalletListUM -import com.tangem.features.commonfeatures.api.choosetoken.model.WalletTabUM +import com.tangem.features.foryou.impl.model.ForYouNotification import com.tangem.features.foryou.impl.entity.ForYouUM -import com.tangem.features.foryou.impl.ui.components.WalletTabsBlock import com.tangem.features.foryou.impl.ui.preview.ForYouPortfolioReviewPreviewData import com.tangem.features.promobanners.api.PromoBannersBlockComponent import kotlinx.collections.immutable.persistentListOf @@ -54,15 +53,27 @@ internal fun ForYouContent( .padding(top = contentPadding.calculateTopPadding()) .drawBehind { drawRect(background.value) }, ) { - WalletTabsBlock(walletList = forYouUM.walletListUM) - - SpacerH(12.dp) - promoBannersBlockComponent.ContentWithPadding( - modifier = Modifier, + modifier = Modifier.padding(top = 12.dp), horizontalItemPadding = 16.dp, ) + forYouUM.notifications.fastForEachIndexed { index, notification -> + key(notification.state) { + TangemMessageBanner( + state = notification.state, + modifier = Modifier + .padding(horizontal = 16.dp) + .conditional(index == 0) { + padding(top = 12.dp) + } + .conditional(index == forYouUM.notifications.lastIndex) { + padding(bottom = 48.dp) + }, + ) + } + } + ForYouPortfolioReview( portfolioReviewUM = forYouUM.portfolioReviewUM, modifier = Modifier.padding(horizontal = 16.dp), @@ -98,17 +109,7 @@ private class ForYouContentPreviewProvider : PreviewParameterProvider override val values: Sequence get() = sequenceOf( ForYouUM( - walletListUM = WalletListUM( - items = persistentListOf( - WalletTabUM( - text = stringReference("Wallet 1"), - count = stringReference("1"), - isSelected = true, - onClick = {}, - deviceIcon = DeviceIconUM.Mobile, - ), - ), - ), + notifications = persistentListOf(ForYouNotification.UsedOutdatedData), portfolioReviewUM = ForYouPortfolioReviewPreviewData.reviewContent, ), ) diff --git a/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/ui/ForYouPortfolioReview.kt b/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/ui/ForYouPortfolioReview.kt index 39470899b6..5cf487bab4 100644 --- a/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/ui/ForYouPortfolioReview.kt +++ b/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/ui/ForYouPortfolioReview.kt @@ -11,20 +11,23 @@ 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.dp -import com.tangem.core.ui.components.RectangleShimmer import com.tangem.core.ui.components.SpacerH import com.tangem.core.ui.ds.image.TangemIconUM import com.tangem.core.ui.ds.row.token.TangemTokenRowUM import com.tangem.core.ui.ds.tabs.TangemSegmentedPicker import com.tangem.core.ui.ds2.badge.TangemBadge +import com.tangem.core.ui.ds2.shimmers.TangemShimmer import com.tangem.core.ui.extensions.stringReference +import com.tangem.core.ui.extensions.stringResourceSafe import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreviewRedesign import com.tangem.core.ui.res.generated.icons.Icons import com.tangem.core.ui.res.generated.icons.ic_chevron_down_16 +import com.tangem.features.foryou.impl.R +import com.tangem.features.foryou.impl.components.MarketChart +import com.tangem.features.foryou.impl.components.state.MarketChartUM import com.tangem.features.foryou.impl.entity.ForYouTokenListItemUM import com.tangem.features.foryou.impl.entity.PortfolioReviewUM -import com.tangem.features.foryou.impl.ui.components.ForYouMarketChartContent import com.tangem.features.foryou.impl.ui.components.ForYouPortfolioTokenList import com.tangem.features.foryou.impl.ui.preview.ForYouPortfolioReviewPreviewData import kotlinx.collections.immutable.persistentListOf @@ -39,7 +42,7 @@ internal fun ForYouPortfolioReview(portfolioReviewUM: PortfolioReviewUM, modifie verticalAlignment = Alignment.CenterVertically, ) { Text( - text = "Portfolio Review", // TODO For You + text = stringResourceSafe(R.string.for_you_portfolio_review_title), style = TangemTheme.typography3.heading.small, color = TangemTheme.colors3.text.primary, ) @@ -51,8 +54,13 @@ internal fun ForYouPortfolioReview(portfolioReviewUM: PortfolioReviewUM, modifie ) } SpacerH(16.dp) - ForYouMarketChartContent(portfolioReviewUM) + + MarketChart( + marketChart = portfolioReviewUM.marketChartUM, + modifier = Modifier.fillMaxWidth(), + ) SpacerH(8.dp) + when (portfolioReviewUM) { is PortfolioReviewUM.Content -> { TangemSegmentedPicker( @@ -60,7 +68,7 @@ internal fun ForYouPortfolioReview(portfolioReviewUM: PortfolioReviewUM, modifie onClick = portfolioReviewUM.onPeriodClick, ) } - is PortfolioReviewUM.Loading -> RectangleShimmer( + is PortfolioReviewUM.Loading -> TangemShimmer( modifier = Modifier .fillMaxWidth() .height(40.dp), @@ -92,6 +100,7 @@ private class ForYouPortfolioReviewPreviewProvider : PreviewParameterProvider add( diff --git a/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/ui/components/ForYouMarketChartContent.kt b/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/ui/components/ForYouMarketChartContent.kt deleted file mode 100644 index f7fe51595f..0000000000 --- a/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/ui/components/ForYouMarketChartContent.kt +++ /dev/null @@ -1,68 +0,0 @@ -package com.tangem.features.foryou.impl.ui.components - -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.* -import androidx.compose.foundation.shape.CircleShape -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.graphics.Color.Companion.Cyan -import androidx.compose.ui.unit.dp -import com.tangem.core.ui.components.SpacerH -import com.tangem.core.ui.components.TextShimmer -import com.tangem.core.ui.extensions.resolveReference -import com.tangem.core.ui.res.TangemTheme -import com.tangem.features.foryou.impl.entity.PortfolioReviewUM - -@Composable -internal fun ForYouMarketChartContent(portfolioReviewUM: PortfolioReviewUM) { - Column( - modifier = Modifier - .fillMaxWidth() - .clip(RoundedCornerShape(24.dp)) - .background(TangemTheme.colors3.bg.secondary) - .padding(16.dp), - ) { - Box( - modifier = Modifier - .align(Alignment.CenterHorizontally) - .padding( - top = 16.dp, - end = 32.dp, - start = 32.dp, - bottom = 32.dp, - ) - .background(Cyan, CircleShape) - .size(200.dp), - ) - SpacerH(16.dp) - - when (portfolioReviewUM) { - is PortfolioReviewUM.Content -> { - Text( - text = portfolioReviewUM.assetCount.resolveReference(), - style = TangemTheme.typography3.heading.small, - color = TangemTheme.colors3.text.secondary, - ) - Text( - text = portfolioReviewUM.topHoldingPercent.resolveReference(), - style = TangemTheme.typography3.heading.small, - color = TangemTheme.colors3.text.primary, - ) - } - is PortfolioReviewUM.Loading -> { - TextShimmer( - style = TangemTheme.typography3.heading.small, - modifier = Modifier.width(50.dp), - ) - TextShimmer( - style = TangemTheme.typography3.heading.small, - modifier = Modifier.width(75.dp), - ) - } - } - } -} \ No newline at end of file diff --git a/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/ui/components/ForYouPortfolioTokenList.kt b/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/ui/components/ForYouPortfolioTokenList.kt index 428abb6f90..b3ef98fcd2 100644 --- a/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/ui/components/ForYouPortfolioTokenList.kt +++ b/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/ui/components/ForYouPortfolioTokenList.kt @@ -46,7 +46,7 @@ import com.tangem.core.ui.extensions.* import com.tangem.core.ui.res.TangemColorPalette import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.generated.icons.Icons -import com.tangem.core.ui.res.generated.icons.ic_error_20 +import com.tangem.core.ui.res.generated.icons.ic_chevron_collapse_20 import com.tangem.core.ui.utils.ProvideSharedTransitionScope import com.tangem.core.ui.utils.lazyListItemPosition import com.tangem.core.ui.utils.sharedBoundsSafely @@ -129,7 +129,7 @@ private fun PortfolioAssetItem(listItem: ForYouTokenListItemUM, index: Int, oute targetState = listItem.isExpanded, transitionSpec = { portfolioAssetExpandFadeAnimation() }, ) { isExpandedWrapped -> - val composables = remember { + val composables = remember(isExpandedWrapped) { SharedTokenRowComposables( icon = { modifier -> PortfolioSharedAssetIcon( @@ -307,7 +307,7 @@ private fun ForYouPortfolioListHeader( } SpacerWMax() Icon( - imageVector = Icons.ic_error_20, + imageVector = Icons.ic_chevron_collapse_20, tint = TangemTheme.colors3.icon.primary, contentDescription = null, ) diff --git a/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/ui/preview/ForYouPortfolioReviewPreviewData.kt b/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/ui/preview/ForYouPortfolioReviewPreviewData.kt index e1647620f7..5bb66c7949 100644 --- a/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/ui/preview/ForYouPortfolioReviewPreviewData.kt +++ b/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/ui/preview/ForYouPortfolioReviewPreviewData.kt @@ -10,16 +10,19 @@ import com.tangem.core.ui.ds.row.token.TangemTokenRowUM import com.tangem.core.ui.ds.tabs.TangemSegmentUM import com.tangem.core.ui.ds.tabs.TangemSegmentedPickerUM import com.tangem.core.ui.extensions.stringReference +import com.tangem.features.foryou.impl.components.state.DonutChartUM +import com.tangem.features.foryou.impl.components.state.DonutSegmentColor +import com.tangem.features.foryou.impl.components.state.DonutSegmentUM +import com.tangem.features.foryou.impl.components.state.MarketChartUM import com.tangem.features.foryou.impl.entity.ForYouTokenListItemUM import com.tangem.features.foryou.impl.entity.PortfolioReviewUM import com.tangem.utils.StringsSigns.DOT import kotlinx.collections.immutable.persistentListOf +import java.math.BigDecimal internal object ForYouPortfolioReviewPreviewData { val reviewContent = PortfolioReviewUM.Content( - assetCount = stringReference("5 assets"), - topHoldingPercent = stringReference("Top holding 42%"), periodPickerUM = TangemSegmentedPickerUM( items = persistentListOf( TangemSegmentUM(id = "0", title = stringReference("Day")), @@ -31,6 +34,27 @@ internal object ForYouPortfolioReviewPreviewData { isAltSurface = true, ), onPeriodClick = {}, + marketChartUM = MarketChartUM.Loaded( + donutChart = DonutChartUM.Loaded( + totalAmount = "10000$", + // Colours are assigned in segment order (rank), matching the transformer's palette-by-index. + donutSegmentList = persistentListOf( + DonutSegmentUM( + color = DonutSegmentColor.Brand, + weight = BigDecimal("0.55"), + title = stringReference("Ethereum"), + fiatValue = stringReference("\$5,720.22"), + ), + DonutSegmentUM( + color = DonutSegmentColor.Green, + weight = BigDecimal("0.45"), + title = stringReference("Solana"), + fiatValue = stringReference("\$728.30"), + ), + ), + ), + topHoldingPercent = stringReference("Top holding 42%"), + ), tokenList = persistentListOf( ForYouTokenListItemUM( tokenRowUM = TangemTokenRowUM.Content(