Updated on 2026-08-14
This commit is contained in:
parent
8eb575a1c2
commit
2c11ec0405
14 changed files with 406 additions and 253 deletions
|
|
@ -157,7 +157,7 @@ fun TangemMessageBanner(
|
||||||
TangemMessageBanner(
|
TangemMessageBanner(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
variant = state.variant,
|
variant = state.variant,
|
||||||
showGlowRing = state.isShowGlowRing,
|
showGlowRing = state.shouldShowGlowRing,
|
||||||
secondaryButton = state.secondaryButton,
|
secondaryButton = state.secondaryButton,
|
||||||
primaryButton = state.primaryButton,
|
primaryButton = state.primaryButton,
|
||||||
) {
|
) {
|
||||||
|
|
@ -341,7 +341,7 @@ object TangemMessageBanner {
|
||||||
* @param title Banner headline.
|
* @param title Banner headline.
|
||||||
* @param variant Visual appearance — background color + glow ring.
|
* @param variant Visual appearance — background color + glow ring.
|
||||||
* @param contentAlign Horizontal alignment of the text block.
|
* @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.
|
* background.
|
||||||
* @param description Secondary line under the [title]. `null` hides it.
|
* @param description Secondary line under the [title]. `null` hides it.
|
||||||
* @param secondaryButton Start action. `null` hides it.
|
* @param secondaryButton Start action. `null` hides it.
|
||||||
|
|
@ -353,7 +353,7 @@ object TangemMessageBanner {
|
||||||
val title: TextReference,
|
val title: TextReference,
|
||||||
val variant: Variant = Variant.Default,
|
val variant: Variant = Variant.Default,
|
||||||
val contentAlign: ContentAlign = ContentAlign.Start,
|
val contentAlign: ContentAlign = ContentAlign.Start,
|
||||||
val isShowGlowRing: Boolean = true,
|
val shouldShowGlowRing: Boolean = true,
|
||||||
val description: TextReference? = null,
|
val description: TextReference? = null,
|
||||||
val secondaryButton: Button? = null,
|
val secondaryButton: Button? = null,
|
||||||
val primaryButton: Button? = null,
|
val primaryButton: Button? = null,
|
||||||
|
|
|
||||||
|
|
@ -4,28 +4,29 @@ import androidx.compose.runtime.Immutable
|
||||||
import com.tangem.core.ui.ds.row.token.TangemTokenRowUM
|
import com.tangem.core.ui.ds.row.token.TangemTokenRowUM
|
||||||
import com.tangem.core.ui.ds.tabs.TangemSegmentUM
|
import com.tangem.core.ui.ds.tabs.TangemSegmentUM
|
||||||
import com.tangem.core.ui.ds.tabs.TangemSegmentedPickerUM
|
import com.tangem.core.ui.ds.tabs.TangemSegmentedPickerUM
|
||||||
import com.tangem.core.ui.extensions.TextReference
|
import com.tangem.features.foryou.impl.components.state.MarketChartUM
|
||||||
import com.tangem.features.commonfeatures.api.choosetoken.model.WalletListUM
|
import com.tangem.features.foryou.impl.model.ForYouNotification
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
|
|
||||||
internal data class ForYouUM(
|
internal data class ForYouUM(
|
||||||
val walletListUM: WalletListUM,
|
|
||||||
val portfolioReviewUM: PortfolioReviewUM,
|
val portfolioReviewUM: PortfolioReviewUM,
|
||||||
|
val notifications: ImmutableList<ForYouNotification>,
|
||||||
)
|
)
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
internal sealed interface PortfolioReviewUM {
|
internal sealed interface PortfolioReviewUM {
|
||||||
val tokenList: ImmutableList<ForYouTokenListItemUM>
|
val tokenList: ImmutableList<ForYouTokenListItemUM>
|
||||||
|
val marketChartUM: MarketChartUM
|
||||||
|
|
||||||
data class Loading(
|
data class Loading(
|
||||||
override val tokenList: ImmutableList<ForYouTokenListItemUM>,
|
override val tokenList: ImmutableList<ForYouTokenListItemUM>,
|
||||||
|
override val marketChartUM: MarketChartUM.NoData,
|
||||||
) : PortfolioReviewUM
|
) : PortfolioReviewUM
|
||||||
|
|
||||||
data class Content(
|
data class Content(
|
||||||
override val tokenList: ImmutableList<ForYouTokenListItemUM>,
|
override val tokenList: ImmutableList<ForYouTokenListItemUM>,
|
||||||
|
override val marketChartUM: MarketChartUM,
|
||||||
val periodPickerUM: TangemSegmentedPickerUM,
|
val periodPickerUM: TangemSegmentedPickerUM,
|
||||||
val assetCount: TextReference,
|
|
||||||
val topHoldingPercent: TextReference,
|
|
||||||
val onPeriodClick: (TangemSegmentUM) -> Unit,
|
val onPeriodClick: (TangemSegmentUM) -> Unit,
|
||||||
) : PortfolioReviewUM
|
) : PortfolioReviewUM
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,27 +2,20 @@ package com.tangem.features.foryou.impl.model
|
||||||
|
|
||||||
import androidx.compose.runtime.Stable
|
import androidx.compose.runtime.Stable
|
||||||
import arrow.core.getOrElse
|
import arrow.core.getOrElse
|
||||||
import com.tangem.common.ui.userwallet.converter.WalletIconUMConverter
|
|
||||||
import com.tangem.core.decompose.di.ModelScoped
|
import com.tangem.core.decompose.di.ModelScoped
|
||||||
import com.tangem.core.decompose.model.Model
|
import com.tangem.core.decompose.model.Model
|
||||||
import com.tangem.core.ui.ds.row.token.TangemTokenRowUM
|
import com.tangem.core.ui.ds.row.token.TangemTokenRowUM
|
||||||
import com.tangem.core.ui.ds.tabs.TangemSegmentUM
|
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.account.status.supplier.MultiAccountStatusListSupplier
|
||||||
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
|
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
|
||||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||||
import com.tangem.domain.common.wallets.UserWalletsListRepository
|
import com.tangem.domain.common.wallets.UserWalletsListRepository
|
||||||
import com.tangem.domain.models.TotalFiatBalance
|
import com.tangem.features.foryou.impl.components.state.MarketChartUM
|
||||||
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.entity.ForYouTokenListItemUM
|
import com.tangem.features.foryou.impl.entity.ForYouTokenListItemUM
|
||||||
import com.tangem.features.foryou.impl.entity.ForYouUM
|
import com.tangem.features.foryou.impl.entity.ForYouUM
|
||||||
import com.tangem.features.foryou.impl.entity.PortfolioReviewUM
|
import com.tangem.features.foryou.impl.entity.PortfolioReviewUM
|
||||||
import com.tangem.features.foryou.impl.model.transformer.SetPortfolioReviewTransformer
|
import com.tangem.features.foryou.impl.model.transformer.SetPortfolioReviewTransformer
|
||||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||||
import com.tangem.utils.extensions.orZero
|
|
||||||
import com.tangem.utils.transformer.update
|
import com.tangem.utils.transformer.update
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
import kotlinx.collections.immutable.toPersistentList
|
import kotlinx.collections.immutable.toPersistentList
|
||||||
|
|
@ -36,19 +29,17 @@ internal class ForYouModel @Inject constructor(
|
||||||
multiAccountStatusListSupplier: MultiAccountStatusListSupplier,
|
multiAccountStatusListSupplier: MultiAccountStatusListSupplier,
|
||||||
override val dispatchers: CoroutineDispatcherProvider,
|
override val dispatchers: CoroutineDispatcherProvider,
|
||||||
private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
|
private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
|
||||||
private val walletIconUMConverter: WalletIconUMConverter,
|
|
||||||
private val getWalletIconUseCase: GetWalletIconUseCase,
|
|
||||||
) : Model() {
|
) : Model() {
|
||||||
|
|
||||||
private val locallySelectedWalletId = MutableStateFlow<UserWalletId?>(value = null)
|
|
||||||
private val expandedAssetIds = MutableStateFlow<Set<String>>(value = emptySet())
|
private val expandedAssetIds = MutableStateFlow<Set<String>>(value = emptySet())
|
||||||
private val selectedAppCurrencyFlow: StateFlow<AppCurrency> = createSelectedAppCurrencyFlow()
|
private val selectedAppCurrencyFlow: StateFlow<AppCurrency> = createSelectedAppCurrencyFlow()
|
||||||
|
|
||||||
val uiState: StateFlow<ForYouUM>
|
val uiState: StateFlow<ForYouUM>
|
||||||
field = MutableStateFlow<ForYouUM>(
|
field = MutableStateFlow<ForYouUM>(
|
||||||
ForYouUM(
|
ForYouUM(
|
||||||
walletListUM = WalletListUM(persistentListOf()),
|
notifications = persistentListOf(),
|
||||||
portfolioReviewUM = PortfolioReviewUM.Loading(
|
portfolioReviewUM = PortfolioReviewUM.Loading(
|
||||||
|
marketChartUM = MarketChartUM.NoData,
|
||||||
tokenList = buildList<ForYouTokenListItemUM> {
|
tokenList = buildList<ForYouTokenListItemUM> {
|
||||||
repeat(4) { index ->
|
repeat(4) { index ->
|
||||||
add(
|
add(
|
||||||
|
|
@ -69,35 +60,14 @@ internal class ForYouModel @Inject constructor(
|
||||||
|
|
||||||
init {
|
init {
|
||||||
combine(
|
combine(
|
||||||
flow = userWalletsListRepository.userWallets,
|
flow = userWalletsListRepository.selectedUserWallet,
|
||||||
flow2 = userWalletsListRepository.selectedUserWallet,
|
flow2 = multiAccountStatusListSupplier.invokeAsMap(),
|
||||||
flow3 = multiAccountStatusListSupplier.invokeAsMap(),
|
flow3 = expandedAssetIds,
|
||||||
flow4 = locallySelectedWalletId,
|
) { globalSelectedWallet, accountStatusList, expanded ->
|
||||||
flow5 = expandedAssetIds,
|
// TODO For You add choose portfolio flow
|
||||||
) { 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()
|
|
||||||
|
|
||||||
uiState.update(
|
uiState.update(
|
||||||
SetPortfolioReviewTransformer(
|
SetPortfolioReviewTransformer(
|
||||||
walletListUM = WalletListUM(
|
accountStatusList = accountStatusList[globalSelectedWallet?.walletId],
|
||||||
items = if (tabs.size != 1) tabs.toPersistentList() else persistentListOf(),
|
|
||||||
),
|
|
||||||
currencies = currencies,
|
|
||||||
totalFiatBalance = totalFiatBalance,
|
|
||||||
appCurrency = selectedAppCurrencyFlow.value,
|
appCurrency = selectedAppCurrencyFlow.value,
|
||||||
expandedAssetIds = expanded,
|
expandedAssetIds = expanded,
|
||||||
expandClick = ::onExpandClick,
|
expandClick = ::onExpandClick,
|
||||||
|
|
@ -119,10 +89,6 @@ internal class ForYouModel @Inject constructor(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onTabClick(walletId: UserWalletId) {
|
|
||||||
locallySelectedWalletId.value = walletId
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun onExpandClick(assetId: String) {
|
private fun onExpandClick(assetId: String) {
|
||||||
expandedAssetIds.update { ids ->
|
expandedAssetIds.update { ids ->
|
||||||
if (assetId in ids) ids - assetId else ids + assetId
|
if (assetId in ids) ids - assetId else ids + assetId
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ internal sealed class ForYouNotification(val state: TangemMessageBanner.State) {
|
||||||
tintReference = { TangemTheme.colors3.icon.primary },
|
tintReference = { TangemTheme.colors3.icon.primary },
|
||||||
),
|
),
|
||||||
variant = TangemMessageBanner.Variant.Warning,
|
variant = TangemMessageBanner.Variant.Warning,
|
||||||
isShowGlowRing = false,
|
shouldShowGlowRing = false,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -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<Pair<List<CryptoCurrencyStatus>, BigDecimal>>,
|
||||||
|
) : Converter<TotalFiatBalance?, MarketChartUM> {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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.TangemBadgeSize
|
||||||
import com.tangem.core.ui.ds.badge.TangemBadgeType
|
import com.tangem.core.ui.ds.badge.TangemBadgeType
|
||||||
import com.tangem.core.ui.ds.badge.TangemBadgeUM
|
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.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.domain.models.currency.CryptoCurrencyStatus
|
||||||
import com.tangem.utils.extensions.isZero
|
import com.tangem.utils.extensions.isZero
|
||||||
import java.math.BigDecimal
|
import java.math.BigDecimal
|
||||||
|
|
@ -19,7 +14,7 @@ import java.math.RoundingMode
|
||||||
* Formatting helpers shared by the For You portfolio-review converters.
|
* 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
|
* 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
|
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
|
* Computes this fiat amount as a share of [totalFiatBalance]. Returns `null` when the share cannot be
|
||||||
* share cannot be computed (no amount, or a zero total / amount).
|
* computed (no amount, or a zero total / amount).
|
||||||
*/
|
*/
|
||||||
internal fun BigDecimal?.toForYouPercentText(totalFiatBalance: BigDecimal): TextReference {
|
internal fun BigDecimal?.toForYouPercent(totalFiatBalance: BigDecimal): BigDecimal? {
|
||||||
if (this == null || totalFiatBalance.isZero() || isZero()) return TextReference.EMPTY
|
if (this == null || totalFiatBalance.isZero() || isZero()) return null
|
||||||
return stringReference(divide(totalFiatBalance, RoundingMode.HALF_UP).format { percent() })
|
return divide(totalFiatBalance, RoundingMode.HALF_UP)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO For You: replace this placeholder with the real price-change badge once the design is wired.
|
// TODO For You: replace this placeholder with the real price-change badge once the design is wired.
|
||||||
|
|
|
||||||
|
|
@ -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.image.TangemIconUM
|
||||||
import com.tangem.core.ui.ds.row.token.TangemTokenRowUM
|
import com.tangem.core.ui.ds.row.token.TangemTokenRowUM
|
||||||
import com.tangem.core.ui.extensions.TextReference
|
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.resourceReference
|
||||||
import com.tangem.core.ui.extensions.stringReference
|
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.appcurrency.model.AppCurrency
|
||||||
import com.tangem.domain.models.currency.CryptoCurrency
|
import com.tangem.domain.models.currency.CryptoCurrency
|
||||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
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).
|
* Modelled on `TokenListStateConverter` (a list converter delegating to a per-item converter).
|
||||||
*/
|
*/
|
||||||
@Suppress("LongParameterList")
|
|
||||||
internal class ForYouTokenListConverter(
|
internal class ForYouTokenListConverter(
|
||||||
private val appCurrency: AppCurrency,
|
private val appCurrency: AppCurrency,
|
||||||
private val totalFiatBalance: BigDecimal,
|
private val totalFiatBalance: BigDecimal,
|
||||||
private val expandedAssetIds: Set<String>,
|
private val expandedAssetIds: Set<String>,
|
||||||
private val expandClick: (assetId: String) -> Unit,
|
private val expandClick: (assetId: String) -> Unit,
|
||||||
private val otherAssetCount: Int,
|
private val otherAssets: List<Pair<List<CryptoCurrencyStatus>, BigDecimal>>,
|
||||||
private val otherFiatBalance: BigDecimal,
|
|
||||||
) : Converter<List<CryptoCurrencyStatus>, ImmutableList<ForYouTokenListItemUM>> {
|
) : Converter<List<CryptoCurrencyStatus>, ImmutableList<ForYouTokenListItemUM>> {
|
||||||
|
|
||||||
private val iconConverter = CryptoCurrencyToIconStateConverter()
|
private val iconConverter = CryptoCurrencyToIconStateConverter()
|
||||||
|
|
@ -48,7 +50,7 @@ internal class ForYouTokenListConverter(
|
||||||
.map { (assetId, currencies) -> createListItem(assetId, currencies) }
|
.map { (assetId, currencies) -> createListItem(assetId, currencies) }
|
||||||
|
|
||||||
// Assets beyond the top ones are collapsed into a single non-expandable "Other" row at the bottom.
|
// 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()
|
assetItems + createOtherItem()
|
||||||
} else {
|
} else {
|
||||||
assetItems
|
assetItems
|
||||||
|
|
@ -87,16 +89,16 @@ internal class ForYouTokenListConverter(
|
||||||
val asset = currencies.first()
|
val asset = currencies.first()
|
||||||
val assetFiatBalance = currencies.sumOf { it.value.fiatAmount.orZero() }
|
val assetFiatBalance = currencies.sumOf { it.value.fiatAmount.orZero() }
|
||||||
|
|
||||||
val subtitle = if (networkCount > 1) {
|
val endContent = rowConverter.toEndContent(statuses = currencies, fiatAmount = assetFiatBalance)
|
||||||
stringReference("$networkCount networks")
|
|
||||||
} else {
|
val onlyCryptoCurrency = currencies.firstOrNull()?.currency
|
||||||
val onlyCryptoCurrency = currencies.firstOrNull()?.currency
|
val isMain = onlyCryptoCurrency is CryptoCurrency.Coin
|
||||||
val isMain = onlyCryptoCurrency is CryptoCurrency.Coin
|
|
||||||
when {
|
val subtitle = when {
|
||||||
isMain -> resourceReference(R.string.common_main_network)
|
networkCount > 1 -> pluralReference(R.plurals.common_networks_count, networkCount)
|
||||||
onlyCryptoCurrency != null -> stringReference(onlyCryptoCurrency.network.standardType.name)
|
isMain -> resourceReference(R.string.common_main_network)
|
||||||
else -> TextReference.EMPTY
|
onlyCryptoCurrency != null -> stringReference(onlyCryptoCurrency.network.standardType.name)
|
||||||
}
|
else -> TextReference.EMPTY
|
||||||
}
|
}
|
||||||
|
|
||||||
return TangemTokenRowUM.Content(
|
return TangemTokenRowUM.Content(
|
||||||
|
|
@ -109,38 +111,44 @@ internal class ForYouTokenListConverter(
|
||||||
subtitleUM = TangemTokenRowUM.SubtitleUM.Content(
|
subtitleUM = TangemTokenRowUM.SubtitleUM.Content(
|
||||||
text = subtitle,
|
text = subtitle,
|
||||||
),
|
),
|
||||||
topEndContentUM = TangemTokenRowUM.EndContentUM.Content(
|
topEndContentUM = endContent.top,
|
||||||
text = assetFiatBalance.toForYouFiatText(appCurrency),
|
bottomEndContentUM = endContent.bottom,
|
||||||
),
|
|
||||||
bottomEndContentUM = TangemTokenRowUM.EndContentUM.Content(
|
|
||||||
text = assetFiatBalance.toForYouPercentText(totalFiatBalance),
|
|
||||||
),
|
|
||||||
onItemClick = { expandClick(assetId) },
|
onItemClick = { expandClick(assetId) },
|
||||||
onItemLongClick = null,
|
onItemLongClick = null,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createOtherItem(): ForYouTokenListItemUM = ForYouTokenListItemUM(
|
private fun createOtherItem(): ForYouTokenListItemUM {
|
||||||
tokenRowUM = TangemTokenRowUM.Content(
|
val otherAssetsBalance = otherAssets.sumOf { (_, assetBalance) -> assetBalance }
|
||||||
id = OTHER_ROW_ID,
|
return ForYouTokenListItemUM(
|
||||||
headIconUM = TangemIconUM.Currency(CurrencyIconState.Empty()),
|
tokenRowUM = TangemTokenRowUM.Content(
|
||||||
titleUM = TangemTokenRowUM.TitleUM.Content(text = stringReference("Other")),
|
id = OTHER_ROW_ID,
|
||||||
subtitleUM = TangemTokenRowUM.SubtitleUM.Content(
|
headIconUM = TangemIconUM.Currency(CurrencyIconState.Empty()),
|
||||||
text = stringReference(if (otherAssetCount > 1) "$otherAssetCount assets" else "1 asset"),
|
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(
|
tokenList = persistentListOf(),
|
||||||
text = otherFiatBalance.toForYouFiatText(appCurrency),
|
isExpanded = false,
|
||||||
),
|
isExpandable = false,
|
||||||
bottomEndContentUM = TangemTokenRowUM.EndContentUM.Content(
|
)
|
||||||
text = otherFiatBalance.toForYouPercentText(totalFiatBalance),
|
}
|
||||||
),
|
|
||||||
onItemClick = null,
|
|
||||||
onItemLongClick = null,
|
|
||||||
),
|
|
||||||
tokenList = persistentListOf(),
|
|
||||||
isExpanded = false,
|
|
||||||
isExpandable = false,
|
|
||||||
)
|
|
||||||
|
|
||||||
private companion object {
|
private companion object {
|
||||||
const val OTHER_ROW_ID = "for_you_other_assets"
|
const val OTHER_ROW_ID = "for_you_other_assets"
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,25 @@
|
||||||
package com.tangem.features.foryou.impl.model.converter
|
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.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.image.TangemIconUM
|
||||||
import com.tangem.core.ui.ds.row.token.TangemTokenRowUM
|
import com.tangem.core.ui.ds.row.token.TangemTokenRowUM
|
||||||
import com.tangem.core.ui.extensions.stringReference
|
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.crypto
|
||||||
|
import com.tangem.core.ui.format.bigdecimal.fiat
|
||||||
import com.tangem.core.ui.format.bigdecimal.format
|
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.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.domain.models.currency.CryptoCurrencyStatus
|
||||||
import com.tangem.utils.StringsSigns
|
import com.tangem.utils.StringsSigns
|
||||||
import com.tangem.utils.extensions.orZero
|
import com.tangem.utils.extensions.orZero
|
||||||
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
import java.math.BigDecimal
|
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
|
* 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
|
* 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.
|
* 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(
|
internal class ForYouTokenRowConverter(
|
||||||
private val appCurrency: AppCurrency,
|
private val appCurrency: AppCurrency,
|
||||||
|
|
@ -36,26 +56,170 @@ internal class ForYouTokenRowConverter(
|
||||||
val currency = representative.currency
|
val currency = representative.currency
|
||||||
val cryptoAmount = statuses.sumOf { it.value.amount.orZero() }
|
val cryptoAmount = statuses.sumOf { it.value.amount.orZero() }
|
||||||
val fiatAmount = statuses.sumOf { it.value.fiatAmount.orZero() }
|
val fiatAmount = statuses.sumOf { it.value.fiatAmount.orZero() }
|
||||||
|
val state = statuses.classify()
|
||||||
|
|
||||||
return TangemTokenRowUM.Content(
|
return TangemTokenRowUM.Content(
|
||||||
id = currency.id.value,
|
id = currency.id.value,
|
||||||
headIconUM = TangemIconUM.Currency(iconConverter.convert(representative)),
|
headIconUM = TangemIconUM.Currency(iconConverter.convert(representative)),
|
||||||
titleUM = TangemTokenRowUM.TitleUM.Content(
|
titleUM = toRowTitle(currency),
|
||||||
text = stringReference(currency.symbol),
|
subtitleUM = toRowSubtitle(state, currency, cryptoAmount),
|
||||||
badge = forYouPlaceholderBadge(),
|
topEndContentUM = toRowTopEnd(state, fiatAmount),
|
||||||
),
|
bottomEndContentUM = toRowBottomEnd(state, fiatAmount),
|
||||||
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),
|
|
||||||
),
|
|
||||||
onItemClick = null,
|
onItemClick = null,
|
||||||
onItemLongClick = 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<CryptoCurrencyStatus>, 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<CryptoCurrencyStatus>.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<StatusSource>.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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -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.TangemSegmentUM
|
||||||
import com.tangem.core.ui.ds.tabs.TangemSegmentedPickerUM
|
import com.tangem.core.ui.ds.tabs.TangemSegmentedPickerUM
|
||||||
import com.tangem.core.ui.extensions.stringReference
|
import com.tangem.core.ui.extensions.stringReference
|
||||||
import com.tangem.core.ui.format.bigdecimal.format
|
import com.tangem.domain.account.models.AccountStatusList
|
||||||
import com.tangem.core.ui.format.bigdecimal.percent
|
|
||||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
import com.tangem.domain.models.StatusSource
|
||||||
import com.tangem.features.commonfeatures.api.choosetoken.model.WalletListUM
|
import com.tangem.domain.models.TotalFiatBalance
|
||||||
import com.tangem.features.foryou.impl.entity.ForYouUM
|
import com.tangem.features.foryou.impl.entity.ForYouUM
|
||||||
import com.tangem.features.foryou.impl.entity.PortfolioReviewUM
|
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.ForYouTokenListConverter
|
||||||
import com.tangem.features.foryou.impl.model.converter.forYouGroupKey
|
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.isZero
|
||||||
import com.tangem.utils.extensions.orZero
|
import com.tangem.utils.extensions.orZero
|
||||||
import com.tangem.utils.transformer.Transformer
|
import com.tangem.utils.transformer.Transformer
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
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
|
* Builds the [ForYouUM] state for the For You screen: the outdated-data notifications plus the portfolio
|
||||||
* review (asset count, top-holding share, period picker and the grouped token list).
|
* review (market chart, period picker and the grouped token list).
|
||||||
*
|
*
|
||||||
* The token list is delegated to [ForYouTokenListConverter]; the period picker selection is carried
|
* The token list is delegated to [ForYouTokenListConverter] and the market chart to
|
||||||
* over from the previous state so it is not reset on every balance refresh.
|
* [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
|
* Modelled on `SetTokenListTransformer` (a transformer that rebuilds the state while delegating the
|
||||||
* token-list construction to a dedicated converter).
|
* token-list construction to a dedicated converter).
|
||||||
*/
|
*/
|
||||||
@Suppress("LongParameterList")
|
@Suppress("LongParameterList")
|
||||||
internal class SetPortfolioReviewTransformer(
|
internal class SetPortfolioReviewTransformer(
|
||||||
private val walletListUM: WalletListUM,
|
private val accountStatusList: AccountStatusList?,
|
||||||
private val currencies: List<CryptoCurrencyStatus>,
|
|
||||||
private val totalFiatBalance: BigDecimal,
|
|
||||||
private val appCurrency: AppCurrency,
|
private val appCurrency: AppCurrency,
|
||||||
private val expandedAssetIds: Set<String>,
|
private val expandedAssetIds: Set<String>,
|
||||||
private val expandClick: (assetId: String) -> Unit,
|
private val expandClick: (assetId: String) -> Unit,
|
||||||
|
|
@ -42,10 +39,18 @@ internal class SetPortfolioReviewTransformer(
|
||||||
) : Transformer<ForYouUM> {
|
) : Transformer<ForYouUM> {
|
||||||
|
|
||||||
override fun transform(prevState: ForYouUM): ForYouUM {
|
override fun transform(prevState: ForYouUM): ForYouUM {
|
||||||
// Drop empty networks, then aggregate the rest into assets (the same token across networks shares
|
val currencies = accountStatusList?.flattenCurrencies().orEmpty()
|
||||||
// its forYouGroupKey) and rank assets by their *summed* fiat balance.
|
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
|
val rankedAssets = currencies
|
||||||
.filterNot { it.value.fiatAmount.orZero().isZero() }
|
.filterNot { it.value.fiatAmount?.isZero() == true }
|
||||||
.groupBy { it.forYouGroupKey() }
|
.groupBy { it.forYouGroupKey() }
|
||||||
.map { (_, networks) -> networks to networks.sumOf { it.value.fiatAmount.orZero() } }
|
.map { (_, networks) -> networks to networks.sumOf { it.value.fiatAmount.orZero() } }
|
||||||
.sortedByDescending { (_, assetBalance) -> assetBalance }
|
.sortedByDescending { (_, assetBalance) -> assetBalance }
|
||||||
|
|
@ -55,40 +60,38 @@ internal class SetPortfolioReviewTransformer(
|
||||||
val topAssets = rankedAssets.take(TOP_HOLDINGS_COUNT)
|
val topAssets = rankedAssets.take(TOP_HOLDINGS_COUNT)
|
||||||
val otherAssets = rankedAssets.drop(TOP_HOLDINGS_COUNT)
|
val otherAssets = rankedAssets.drop(TOP_HOLDINGS_COUNT)
|
||||||
val topCurrencies = topAssets.flatMap { (networks, _) -> networks }
|
val topCurrencies = topAssets.flatMap { (networks, _) -> networks }
|
||||||
val topBalance = topAssets.sumOf { (_, assetBalance) -> assetBalance }
|
|
||||||
|
|
||||||
val tokenList = ForYouTokenListConverter(
|
val tokenList = ForYouTokenListConverter(
|
||||||
appCurrency = appCurrency,
|
appCurrency = appCurrency,
|
||||||
totalFiatBalance = totalFiatBalance,
|
totalFiatBalance = totalFiatBalance,
|
||||||
expandedAssetIds = expandedAssetIds,
|
expandedAssetIds = expandedAssetIds,
|
||||||
expandClick = expandClick,
|
expandClick = expandClick,
|
||||||
otherAssetCount = otherAssets.size,
|
otherAssets = otherAssets,
|
||||||
otherFiatBalance = otherAssets.sumOf { (_, assetBalance) -> assetBalance },
|
|
||||||
).convert(topCurrencies)
|
).convert(topCurrencies)
|
||||||
|
|
||||||
|
val marketChartUM = ForYouMarketChartConverter(
|
||||||
|
appCurrency = appCurrency,
|
||||||
|
topAssets = topAssets,
|
||||||
|
).convert(accountStatusList?.totalFiatBalance)
|
||||||
|
|
||||||
return prevState.copy(
|
return prevState.copy(
|
||||||
walletListUM = walletListUM,
|
notifications = if (loadedBalance?.source == StatusSource.ONLY_CACHE) {
|
||||||
|
persistentListOf(ForYouNotification.UsedOutdatedData)
|
||||||
|
} else {
|
||||||
|
persistentListOf()
|
||||||
|
},
|
||||||
portfolioReviewUM = PortfolioReviewUM.Content(
|
portfolioReviewUM = PortfolioReviewUM.Content(
|
||||||
assetCount = stringReference("${rankedAssets.size} assets"), // TODO For You lokalize
|
|
||||||
topHoldingPercent = stringReference("Top holding ${topHoldingPercent(topBalance)}"),
|
|
||||||
periodPickerUM = when (prevState.portfolioReviewUM) {
|
periodPickerUM = when (prevState.portfolioReviewUM) {
|
||||||
is PortfolioReviewUM.Content -> prevState.portfolioReviewUM.periodPickerUM
|
is PortfolioReviewUM.Content -> prevState.portfolioReviewUM.periodPickerUM
|
||||||
is PortfolioReviewUM.Loading -> createPeriodPicker()
|
is PortfolioReviewUM.Loading -> createPeriodPicker()
|
||||||
},
|
},
|
||||||
tokenList = tokenList,
|
tokenList = tokenList,
|
||||||
|
marketChartUM = marketChartUM,
|
||||||
onPeriodClick = onPeriodClick,
|
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 {
|
private fun createPeriodPicker(): TangemSegmentedPickerUM {
|
||||||
// TODO For you replace with data from backend
|
// TODO For you replace with data from backend
|
||||||
val day = TangemSegmentUM(id = "0", title = stringReference("Day"))
|
val day = TangemSegmentUM(id = "0", title = stringReference("Day"))
|
||||||
|
|
|
||||||
|
|
@ -16,17 +16,16 @@ import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||||
import androidx.compose.ui.unit.Dp
|
import androidx.compose.ui.unit.Dp
|
||||||
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.SpacerH
|
||||||
import com.tangem.core.ui.components.bottomsheets.state.BottomSheetState
|
import com.tangem.core.ui.components.bottomsheets.state.BottomSheetState
|
||||||
import com.tangem.core.ui.ds.image.DeviceIconUM
|
import com.tangem.core.ui.ds2.messagebanner.TangemMessageBanner
|
||||||
import com.tangem.core.ui.extensions.stringReference
|
import com.tangem.core.ui.extensions.conditional
|
||||||
import com.tangem.core.ui.res.LocalMainBottomSheetColor
|
import com.tangem.core.ui.res.LocalMainBottomSheetColor
|
||||||
import com.tangem.core.ui.res.TangemTheme
|
import com.tangem.core.ui.res.TangemTheme
|
||||||
import com.tangem.core.ui.res.TangemThemePreviewRedesign
|
import com.tangem.core.ui.res.TangemThemePreviewRedesign
|
||||||
import com.tangem.features.commonfeatures.api.choosetoken.model.WalletListUM
|
import com.tangem.features.foryou.impl.model.ForYouNotification
|
||||||
import com.tangem.features.commonfeatures.api.choosetoken.model.WalletTabUM
|
|
||||||
import com.tangem.features.foryou.impl.entity.ForYouUM
|
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.foryou.impl.ui.preview.ForYouPortfolioReviewPreviewData
|
||||||
import com.tangem.features.promobanners.api.PromoBannersBlockComponent
|
import com.tangem.features.promobanners.api.PromoBannersBlockComponent
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
|
|
@ -54,15 +53,27 @@ internal fun ForYouContent(
|
||||||
.padding(top = contentPadding.calculateTopPadding())
|
.padding(top = contentPadding.calculateTopPadding())
|
||||||
.drawBehind { drawRect(background.value) },
|
.drawBehind { drawRect(background.value) },
|
||||||
) {
|
) {
|
||||||
WalletTabsBlock(walletList = forYouUM.walletListUM)
|
|
||||||
|
|
||||||
SpacerH(12.dp)
|
|
||||||
|
|
||||||
promoBannersBlockComponent.ContentWithPadding(
|
promoBannersBlockComponent.ContentWithPadding(
|
||||||
modifier = Modifier,
|
modifier = Modifier.padding(top = 12.dp),
|
||||||
horizontalItemPadding = 16.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(
|
ForYouPortfolioReview(
|
||||||
portfolioReviewUM = forYouUM.portfolioReviewUM,
|
portfolioReviewUM = forYouUM.portfolioReviewUM,
|
||||||
modifier = Modifier.padding(horizontal = 16.dp),
|
modifier = Modifier.padding(horizontal = 16.dp),
|
||||||
|
|
@ -98,17 +109,7 @@ private class ForYouContentPreviewProvider : PreviewParameterProvider<ForYouUM>
|
||||||
override val values: Sequence<ForYouUM>
|
override val values: Sequence<ForYouUM>
|
||||||
get() = sequenceOf(
|
get() = sequenceOf(
|
||||||
ForYouUM(
|
ForYouUM(
|
||||||
walletListUM = WalletListUM(
|
notifications = persistentListOf(ForYouNotification.UsedOutdatedData),
|
||||||
items = persistentListOf(
|
|
||||||
WalletTabUM(
|
|
||||||
text = stringReference("Wallet 1"),
|
|
||||||
count = stringReference("1"),
|
|
||||||
isSelected = true,
|
|
||||||
onClick = {},
|
|
||||||
deviceIcon = DeviceIconUM.Mobile,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
portfolioReviewUM = ForYouPortfolioReviewPreviewData.reviewContent,
|
portfolioReviewUM = ForYouPortfolioReviewPreviewData.reviewContent,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -11,20 +11,23 @@ import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||||
import androidx.compose.ui.unit.dp
|
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.components.SpacerH
|
||||||
import com.tangem.core.ui.ds.image.TangemIconUM
|
import com.tangem.core.ui.ds.image.TangemIconUM
|
||||||
import com.tangem.core.ui.ds.row.token.TangemTokenRowUM
|
import com.tangem.core.ui.ds.row.token.TangemTokenRowUM
|
||||||
import com.tangem.core.ui.ds.tabs.TangemSegmentedPicker
|
import com.tangem.core.ui.ds.tabs.TangemSegmentedPicker
|
||||||
import com.tangem.core.ui.ds2.badge.TangemBadge
|
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.stringReference
|
||||||
|
import com.tangem.core.ui.extensions.stringResourceSafe
|
||||||
import com.tangem.core.ui.res.TangemTheme
|
import com.tangem.core.ui.res.TangemTheme
|
||||||
import com.tangem.core.ui.res.TangemThemePreviewRedesign
|
import com.tangem.core.ui.res.TangemThemePreviewRedesign
|
||||||
import com.tangem.core.ui.res.generated.icons.Icons
|
import com.tangem.core.ui.res.generated.icons.Icons
|
||||||
import com.tangem.core.ui.res.generated.icons.ic_chevron_down_16
|
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.ForYouTokenListItemUM
|
||||||
import com.tangem.features.foryou.impl.entity.PortfolioReviewUM
|
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.components.ForYouPortfolioTokenList
|
||||||
import com.tangem.features.foryou.impl.ui.preview.ForYouPortfolioReviewPreviewData
|
import com.tangem.features.foryou.impl.ui.preview.ForYouPortfolioReviewPreviewData
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
|
|
@ -39,7 +42,7 @@ internal fun ForYouPortfolioReview(portfolioReviewUM: PortfolioReviewUM, modifie
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = "Portfolio Review", // TODO For You
|
text = stringResourceSafe(R.string.for_you_portfolio_review_title),
|
||||||
style = TangemTheme.typography3.heading.small,
|
style = TangemTheme.typography3.heading.small,
|
||||||
color = TangemTheme.colors3.text.primary,
|
color = TangemTheme.colors3.text.primary,
|
||||||
)
|
)
|
||||||
|
|
@ -51,8 +54,13 @@ internal fun ForYouPortfolioReview(portfolioReviewUM: PortfolioReviewUM, modifie
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
SpacerH(16.dp)
|
SpacerH(16.dp)
|
||||||
ForYouMarketChartContent(portfolioReviewUM)
|
|
||||||
|
MarketChart(
|
||||||
|
marketChart = portfolioReviewUM.marketChartUM,
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
)
|
||||||
SpacerH(8.dp)
|
SpacerH(8.dp)
|
||||||
|
|
||||||
when (portfolioReviewUM) {
|
when (portfolioReviewUM) {
|
||||||
is PortfolioReviewUM.Content -> {
|
is PortfolioReviewUM.Content -> {
|
||||||
TangemSegmentedPicker(
|
TangemSegmentedPicker(
|
||||||
|
|
@ -60,7 +68,7 @@ internal fun ForYouPortfolioReview(portfolioReviewUM: PortfolioReviewUM, modifie
|
||||||
onClick = portfolioReviewUM.onPeriodClick,
|
onClick = portfolioReviewUM.onPeriodClick,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
is PortfolioReviewUM.Loading -> RectangleShimmer(
|
is PortfolioReviewUM.Loading -> TangemShimmer(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.height(40.dp),
|
.height(40.dp),
|
||||||
|
|
@ -92,6 +100,7 @@ private class ForYouPortfolioReviewPreviewProvider : PreviewParameterProvider<Po
|
||||||
get() = sequenceOf(
|
get() = sequenceOf(
|
||||||
ForYouPortfolioReviewPreviewData.reviewContent,
|
ForYouPortfolioReviewPreviewData.reviewContent,
|
||||||
PortfolioReviewUM.Loading(
|
PortfolioReviewUM.Loading(
|
||||||
|
marketChartUM = MarketChartUM.NoData,
|
||||||
tokenList = buildList {
|
tokenList = buildList {
|
||||||
repeat(4) { index ->
|
repeat(4) { index ->
|
||||||
add(
|
add(
|
||||||
|
|
|
||||||
|
|
@ -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),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -46,7 +46,7 @@ import com.tangem.core.ui.extensions.*
|
||||||
import com.tangem.core.ui.res.TangemColorPalette
|
import com.tangem.core.ui.res.TangemColorPalette
|
||||||
import com.tangem.core.ui.res.TangemTheme
|
import com.tangem.core.ui.res.TangemTheme
|
||||||
import com.tangem.core.ui.res.generated.icons.Icons
|
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.ProvideSharedTransitionScope
|
||||||
import com.tangem.core.ui.utils.lazyListItemPosition
|
import com.tangem.core.ui.utils.lazyListItemPosition
|
||||||
import com.tangem.core.ui.utils.sharedBoundsSafely
|
import com.tangem.core.ui.utils.sharedBoundsSafely
|
||||||
|
|
@ -129,7 +129,7 @@ private fun PortfolioAssetItem(listItem: ForYouTokenListItemUM, index: Int, oute
|
||||||
targetState = listItem.isExpanded,
|
targetState = listItem.isExpanded,
|
||||||
transitionSpec = { portfolioAssetExpandFadeAnimation() },
|
transitionSpec = { portfolioAssetExpandFadeAnimation() },
|
||||||
) { isExpandedWrapped ->
|
) { isExpandedWrapped ->
|
||||||
val composables = remember {
|
val composables = remember(isExpandedWrapped) {
|
||||||
SharedTokenRowComposables(
|
SharedTokenRowComposables(
|
||||||
icon = { modifier ->
|
icon = { modifier ->
|
||||||
PortfolioSharedAssetIcon(
|
PortfolioSharedAssetIcon(
|
||||||
|
|
@ -307,7 +307,7 @@ private fun ForYouPortfolioListHeader(
|
||||||
}
|
}
|
||||||
SpacerWMax()
|
SpacerWMax()
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.ic_error_20,
|
imageVector = Icons.ic_chevron_collapse_20,
|
||||||
tint = TangemTheme.colors3.icon.primary,
|
tint = TangemTheme.colors3.icon.primary,
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -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.TangemSegmentUM
|
||||||
import com.tangem.core.ui.ds.tabs.TangemSegmentedPickerUM
|
import com.tangem.core.ui.ds.tabs.TangemSegmentedPickerUM
|
||||||
import com.tangem.core.ui.extensions.stringReference
|
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.ForYouTokenListItemUM
|
||||||
import com.tangem.features.foryou.impl.entity.PortfolioReviewUM
|
import com.tangem.features.foryou.impl.entity.PortfolioReviewUM
|
||||||
import com.tangem.utils.StringsSigns.DOT
|
import com.tangem.utils.StringsSigns.DOT
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
|
import java.math.BigDecimal
|
||||||
|
|
||||||
internal object ForYouPortfolioReviewPreviewData {
|
internal object ForYouPortfolioReviewPreviewData {
|
||||||
|
|
||||||
val reviewContent = PortfolioReviewUM.Content(
|
val reviewContent = PortfolioReviewUM.Content(
|
||||||
assetCount = stringReference("5 assets"),
|
|
||||||
topHoldingPercent = stringReference("Top holding 42%"),
|
|
||||||
periodPickerUM = TangemSegmentedPickerUM(
|
periodPickerUM = TangemSegmentedPickerUM(
|
||||||
items = persistentListOf(
|
items = persistentListOf(
|
||||||
TangemSegmentUM(id = "0", title = stringReference("Day")),
|
TangemSegmentUM(id = "0", title = stringReference("Day")),
|
||||||
|
|
@ -31,6 +34,27 @@ internal object ForYouPortfolioReviewPreviewData {
|
||||||
isAltSurface = true,
|
isAltSurface = true,
|
||||||
),
|
),
|
||||||
onPeriodClick = {},
|
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(
|
tokenList = persistentListOf(
|
||||||
ForYouTokenListItemUM(
|
ForYouTokenListItemUM(
|
||||||
tokenRowUM = TangemTokenRowUM.Content(
|
tokenRowUM = TangemTokenRowUM.Content(
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue