diff --git a/features/for-you/impl/build.gradle.kts b/features/for-you/impl/build.gradle.kts index fcdb2d8087..d7d88b403c 100644 --- a/features/for-you/impl/build.gradle.kts +++ b/features/for-you/impl/build.gradle.kts @@ -39,6 +39,8 @@ dependencies { api(projects.domain.appCurrency) api(projects.domain.common) api(projects.domain.wallets) + api(projects.domain.earn) + api(projects.domain.yieldSupply) implementation(projects.domain.account) implementation(projects.domain.models) 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 564a3fa05a..2291181eaf 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 @@ -14,6 +14,8 @@ internal data class ForYouUM( val portfolioReviewUM: PortfolioReviewUM, val earnOpportunities: EarnOpportunitiesUM, val notifications: ImmutableList, + val periodPickerUM: TangemSegmentedPickerUM, + val onPeriodClick: (tangemSegmentUM: TangemSegmentUM) -> Unit, ) @Immutable @@ -29,20 +31,32 @@ internal sealed interface PortfolioReviewUM { data class Content( override val tokenList: ImmutableList, override val marketChartUM: MarketChartUM, - val periodPickerUM: TangemSegmentedPickerUM, - val onPeriodClick: (TangemSegmentUM) -> Unit, ) : PortfolioReviewUM } +/** + * State of the earn-opportunities section. [tokenList] holds either the user's earn-eligible holdings + * or top-earn suggestions, depending on which content state was picked (see + * `ForYouEarnOpportunitiesConverter` for the selection rules). + */ @Immutable internal sealed interface EarnOpportunitiesUM { val tokenList: ImmutableList + /** Skeleton rows shown until the first real emission. */ data class Loading( override val tokenList: ImmutableList, ) : EarnOpportunitiesUM + /** + * @property subtitleRes section subtitle matching the picked state (nothing eligible / all active / + * potential rewards) + * @property potentialReward header value: the total projected yearly reward for eligible holdings, + * or the best suggestion's rate when the user holds nothing eligible; `null` when not applicable + * @property potentialRewardType label of [potentialReward]'s rate kind (APR/APY); only set alongside + * a rate-based reward + */ data class Content( override val tokenList: ImmutableList, @param:StringRes val subtitleRes: Int, 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 8164f5d156..561d48003a 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,25 +2,39 @@ package com.tangem.features.foryou.impl.model import androidx.compose.runtime.Stable import arrow.core.getOrElse +import arrow.core.left +import arrow.core.right import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.ParamsContainer 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.domain.account.status.supplier.MultiAccountStatusListSupplier +import com.tangem.domain.account.status.usecase.IsAccountsModeEnabledUseCase import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.common.wallets.UserWalletsListRepository +import com.tangem.domain.earn.EarnErrorResolver +import com.tangem.domain.earn.model.EarnTokensBatchingContext +import com.tangem.domain.earn.model.EarnTokensListConfig +import com.tangem.domain.earn.usecase.GetEarnTokensBatchFlowUseCase import com.tangem.domain.models.currency.CryptoCurrency +import com.tangem.domain.models.earn.EarnTopToken import com.tangem.domain.models.wallet.UserWalletId +import com.tangem.domain.staking.usecase.StakingAvailabilityListUseCase +import com.tangem.domain.yield.supply.usecase.YieldSupplyApyFlowUseCase import com.tangem.features.foryou.ForYouComponent import com.tangem.features.foryou.impl.components.state.MarketChartUM -import com.tangem.features.foryou.impl.entity.EarnOpportunitiesUM -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.entity.* +import com.tangem.features.foryou.impl.model.converter.TOP_EARN_TOKENS_BATCH_SIZE +import com.tangem.features.foryou.impl.model.converter.earnOpportunities.ForYouEarnOpportunitiesConverter +import com.tangem.features.foryou.impl.model.converter.portfolioReview.ForYouPortfolioReviewConverter import com.tangem.features.foryou.impl.model.transformer.SetPortfolioReviewTransformer +import com.tangem.pagination.BatchAction +import com.tangem.pagination.PaginationStatus import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import com.tangem.utils.coroutines.combine6 import com.tangem.utils.transformer.update import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toPersistentList @@ -29,26 +43,34 @@ import javax.inject.Inject @Stable @ModelScoped +@Suppress("LongParameterList") internal class ForYouModel @Inject constructor( paramsContainer: ParamsContainer, userWalletsListRepository: UserWalletsListRepository, multiAccountStatusListSupplier: MultiAccountStatusListSupplier, + yieldSupplyApyFlowUseCase: YieldSupplyApyFlowUseCase, override val dispatchers: CoroutineDispatcherProvider, private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase, + private val getEarnTokensBatchFlowUseCase: GetEarnTokensBatchFlowUseCase, + private val stakingAvailabilityListUseCase: StakingAvailabilityListUseCase, + private val isAccountsModeEnabledUseCase: IsAccountsModeEnabledUseCase, + private val earnErrorResolver: EarnErrorResolver, ) : Model() { private val params = paramsContainer.require() - private val expandedAssetIds = MutableStateFlow>(value = emptySet()) + private val expandedPortfolioReviewAssetIds = MutableStateFlow>(value = emptySet()) + private val expandedEarnOpportunitiesAssetIds = MutableStateFlow>(value = emptySet()) private val selectedAppCurrencyFlow: StateFlow = createSelectedAppCurrencyFlow() val uiState: StateFlow field = MutableStateFlow( ForYouUM( notifications = persistentListOf(), + periodPickerUM = TangemSegmentedPickerUM(persistentListOf()), earnOpportunities = EarnOpportunitiesUM.Loading( tokenList = buildList { - repeat(4) { index -> + repeat(5) { index -> add( ForYouTokenListItemUM( tokenRowUM = TangemTokenRowUM.Loading( @@ -62,6 +84,7 @@ internal class ForYouModel @Inject constructor( } }.toPersistentList(), ), + onPeriodClick = ::onPeriodClick, portfolioReviewUM = PortfolioReviewUM.Loading( marketChartUM = MarketChartUM.NoData, tokenList = buildList { @@ -83,21 +106,51 @@ internal class ForYouModel @Inject constructor( ) init { - combine( - flow = userWalletsListRepository.selectedUserWallet, + combine6( + flow1 = userWalletsListRepository.selectedUserWallet, flow2 = multiAccountStatusListSupplier.invokeAsMap(), - flow3 = expandedAssetIds, - ) { globalSelectedWallet, accountStatusList, expanded -> + flow3 = expandedPortfolioReviewAssetIds, + flow4 = expandedEarnOpportunitiesAssetIds, + flow5 = yieldSupplyApyFlowUseCase(), + flow6 = createTopEarnTokensFlow(), + ) { + globalSelectedWallet, accountList, + expandedPortfolioReview, expandedEarnOpportunities, + yieldAvailability, topEarnTokens, + -> + + val stakingAvailability = accountList.flatMap { (userWalletId, accountStatusList) -> + stakingAvailabilityListUseCase.invokeSync( + userWalletId = userWalletId, + cryptoCurrencyList = accountStatusList.flattenCurrencies().map { it.currency }, + ).entries + }.associate { it.key to it.value } + // TODO For You add choose portfolio flow - val selectedWalletId = globalSelectedWallet?.walletId + val accountStatusList = accountList[globalSelectedWallet?.walletId] + + val portfolioReviewUM = ForYouPortfolioReviewConverter( + appCurrency = selectedAppCurrencyFlow.value, + expandedAssetIds = expandedPortfolioReview, + expandClick = ::onExpandPortfolioReviewClick, + onTokenClick = ::onPortfolioReviewTokenClick, + ).convert(accountStatusList) + + val earnOpportunitiesUM = ForYouEarnOpportunitiesConverter( + appCurrency = selectedAppCurrencyFlow.value, + isAccountsModeEnabled = isAccountsModeEnabledUseCase.invokeSync(), + yieldSupplyAvailability = yieldAvailability, + yieldStakingAvailability = stakingAvailability, + topEarnTokens = topEarnTokens, + expandedAssetIds = expandedEarnOpportunities, + expandClick = ::onExpandEarnOpportunitiesClick, + ).convert(accountStatusList) + uiState.update( SetPortfolioReviewTransformer( - accountStatusList = accountStatusList[selectedWalletId], - appCurrency = selectedAppCurrencyFlow.value, - expandedAssetIds = expanded, - expandClick = ::onExpandClick, - onPeriodClick = ::onPeriodClick, - onTokenClick = { currency -> onTokenClick(selectedWalletId, currency) }, + accountStatusList = accountStatusList, + portfolioReviewUM = portfolioReviewUM, + earnOpportunitiesUM = earnOpportunitiesUM, ), ) } @@ -105,6 +158,40 @@ internal class ForYouModel @Inject constructor( .launchIn(modelScope) } + /** + * Fetches the top-earn suggestions as a single batch of [TOP_EARN_TOKENS_BATCH_SIZE] tokens (a + * one-shot [BatchAction.Reload]; no further paging). Emits `null` while the initial load is in + * flight, a resolved error on failure, and the flattened token list on success — so the earn + * section can distinguish "not loaded yet" from "loaded empty". + */ + private fun createTopEarnTokensFlow(): Flow { + val actionsFlow = MutableSharedFlow>(replay = 1) + + val batchFlow = getEarnTokensBatchFlowUseCase( + context = EarnTokensBatchingContext( + actionsFlow = actionsFlow, + coroutineScope = modelScope, + ), + batchSize = TOP_EARN_TOKENS_BATCH_SIZE, + ) + + actionsFlow.tryEmit( + BatchAction.Reload( + requestParams = EarnTokensListConfig(type = null, networks = null, isForEarn = false), + ), + ) + + return batchFlow.state.map { state -> + when (val status = state.status) { + is PaginationStatus.None, + is PaginationStatus.InitialLoading, + -> null + is PaginationStatus.InitialLoadingError -> earnErrorResolver.resolve(status.throwable).left() + else -> state.data.flatMap { batch -> batch.data }.right() + } + } + } + private fun createSelectedAppCurrencyFlow(): StateFlow { return getSelectedAppCurrencyUseCase().map { maybeAppCurrency -> maybeAppCurrency.getOrElse { AppCurrency.Default } @@ -115,13 +202,19 @@ internal class ForYouModel @Inject constructor( ) } - private fun onTokenClick(selectedWalletId: UserWalletId?, currency: CryptoCurrency) { + private fun onPortfolioReviewTokenClick(selectedWalletId: UserWalletId?, currency: CryptoCurrency) { val walletId = selectedWalletId ?: return params.callbacks.onTokenClick(walletId, currency) } - private fun onExpandClick(assetId: String) { - expandedAssetIds.update { ids -> + private fun onExpandPortfolioReviewClick(assetId: String) { + expandedPortfolioReviewAssetIds.update { ids -> + if (assetId in ids) ids - assetId else ids + assetId + } + } + + private fun onExpandEarnOpportunitiesClick(assetId: String) { + expandedEarnOpportunitiesAssetIds.update { ids -> if (assetId in ids) ids - assetId else ids + assetId } } @@ -129,11 +222,9 @@ internal class ForYouModel @Inject constructor( private fun onPeriodClick(tangemSegmentUM: TangemSegmentUM) { uiState.update { state -> state.copy( - portfolioReviewUM = (state.portfolioReviewUM as? PortfolioReviewUM.Content)?.copy( - periodPickerUM = state.portfolioReviewUM.periodPickerUM.copy( - initialSelectedItem = tangemSegmentUM, - ), - ) ?: state.portfolioReviewUM, + periodPickerUM = state.periodPickerUM.copy( + initialSelectedItem = tangemSegmentUM, + ), ) } } diff --git a/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/model/converter/ForYouUtils.kt b/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/model/converter/ForYouUtils.kt index 6b2425900a..d667614973 100644 --- a/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/model/converter/ForYouUtils.kt +++ b/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/model/converter/ForYouUtils.kt @@ -15,7 +15,11 @@ import java.math.RoundingMode /** Number of suggested top-earn tokens shown in the earn-opportunities block. */ internal const val FOR_YOU_TOP_EARN_TOKENS_COUNT = 5 -/** Batch size for getting top earn tokens in single and only page */ +/** + * Batch size for the top-earn-tokens request. Only the first batch is ever fetched (the section shows + * at most [FOR_YOU_TOP_EARN_TOKENS_COUNT] rows), but it is requested larger so that filtering out + * already-active tokens still leaves enough candidates to fill the list. + */ internal const val TOP_EARN_TOKENS_BATCH_SIZE = 30 /** Divisor converting backend percent values (5.5) to fractions (0.055). */ @@ -68,11 +72,11 @@ internal data class EarnApyInfo( /** * Earn-eligible currencies of one account with their resolved rates. * - * @property accountPotentialReward sum of [EarnApyInfo.potentialRewards] over [earnCurrencues]; + * @property accountPotentialReward sum of [EarnApyInfo.potentialRewards] over [earnCurrencies]; * accounts are ordered by it, descending */ internal data class EarnOpportunities( val account: Account.CryptoPortfolio, - val earnCurrencues: Map, + val earnCurrencies: Map, val accountPotentialReward: BigDecimal, ) \ No newline at end of file diff --git a/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/model/converter/earnOpportunities/ForYouEarnOpportunitiesConverter.kt b/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/model/converter/earnOpportunities/ForYouEarnOpportunitiesConverter.kt index a95b2a2f69..8f4a348934 100644 --- a/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/model/converter/earnOpportunities/ForYouEarnOpportunitiesConverter.kt +++ b/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/model/converter/earnOpportunities/ForYouEarnOpportunitiesConverter.kt @@ -75,7 +75,7 @@ internal class ForYouEarnOpportunitiesConverter( EarnOpportunities( account = cryptoAccountStatus.account, - earnCurrencues = tokenList.toMap(), + earnCurrencies = tokenList.toMap(), accountPotentialReward = accountPotentialReward, ) } @@ -84,10 +84,14 @@ internal class ForYouEarnOpportunitiesConverter( return when { data.isEmpty() -> { - ForYouEarnOpportunitiesNoTokensConverter(topEarnTokens).convert(data) + ForYouEarnOpportunitiesNoTokensConverter( + topEarnTokens = topEarnTokens, + ).convert(data) } - data.all { earn -> earn.earnCurrencues.all { entry -> entry.value.isActive } } -> { - ForYouEarnOpportunitiesTokensActiveConverter(topEarnTokens).convert(data) + data.all { earn -> earn.earnCurrencies.all { entry -> entry.value.isActive } } -> { + ForYouEarnOpportunitiesTokensActiveConverter( + topEarnTokens = topEarnTokens, + ).convert(data) } else -> { ForYouEarnOpportunitiesPotentialRewardsConverter( @@ -134,7 +138,7 @@ internal class ForYouEarnOpportunitiesConverter( currencyStatus = cryptoCurrencyStatus, stakingApyMap = stakingApyMap, ) - if (stakingInfo.rate != null) { + if (stakingInfo != null) { return EarnApyInfo( isActive = stakingInfo.isActive, apy = stakingInfo.rate, @@ -154,10 +158,9 @@ internal class ForYouEarnOpportunitiesConverter( private fun findStakingRate( currencyStatus: CryptoCurrencyStatus, stakingApyMap: Map, - ): StakingLocalInfo { + ): StakingLocalInfo? { val availability = stakingApyMap[currencyStatus.currency] - val option = availability?.optionOrNull - ?: return StakingLocalInfo(rate = null, isActive = false, rewardType = null) + val option = availability?.optionOrNull ?: return null val stakingBalance = currencyStatus.value.stakingBalance as? StakingBalance.Data val stakeKitBalance = stakingBalance as? StakingBalance.Data.StakeKit @@ -166,7 +169,7 @@ internal class ForYouEarnOpportunitiesConverter( // Full = no free capacity: show the badge only for tokens that already have a stake. if (availability is StakingAvailability.Full && !isActive) { - return StakingLocalInfo(rate = null, isActive = false, rewardType = null) + return null } val rateInfo = when (option) { @@ -196,18 +199,18 @@ internal class ForYouEarnOpportunitiesConverter( } .maxByOrNull { it.rate } } - } + } ?: return null return StakingLocalInfo( - rate = rateInfo?.rate, + rate = rateInfo.rate, isActive = isActive, - rewardType = rateInfo?.type, + rewardType = rateInfo.type, ) } private data class StakingLocalInfo( - val rate: BigDecimal?, + val rate: BigDecimal, val isActive: Boolean, - val rewardType: RewardType?, + val rewardType: RewardType, ) } \ No newline at end of file diff --git a/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/model/converter/earnOpportunities/ForYouEarnOpportunitiesPotentialRewardsConverter.kt b/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/model/converter/earnOpportunities/ForYouEarnOpportunitiesPotentialRewardsConverter.kt index 47d65c42b3..b93413e988 100644 --- a/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/model/converter/earnOpportunities/ForYouEarnOpportunitiesPotentialRewardsConverter.kt +++ b/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/model/converter/earnOpportunities/ForYouEarnOpportunitiesPotentialRewardsConverter.kt @@ -20,6 +20,15 @@ import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toPersistentList import java.math.BigDecimal +/** + * Earn-opportunities state for a portfolio with tokens that could earn but don't yet: renders the + * user's earn-eligible holdings with their projected yearly rewards, headed by the total across + * accounts ([EarnOpportunitiesUM.Content.potentialReward]). + * + * With accounts mode on, each account becomes one expandable row (children delegated to + * [ForYouEarnOpportunitiesTokenRowConverter], expansion keyed by account id); with it off, the + * tokens are rendered as flat non-expandable rows. + */ internal class ForYouEarnOpportunitiesPotentialRewardsConverter( private val appCurrency: AppCurrency, private val isAccountsModeEnabled: Boolean, @@ -51,16 +60,16 @@ internal class ForYouEarnOpportunitiesPotentialRewardsConverter( tokenRowUM = createAssetRow( account = earnData.account, potentialReward = earnData.accountPotentialReward, - tokenCount = earnData.earnCurrencues.size, + tokenCount = earnData.earnCurrencies.size, ), - tokenList = rowConverter.convertList(earnData.earnCurrencues.toList()) + tokenList = rowConverter.convertList(earnData.earnCurrencies.toList()) .toPersistentList(), isExpanded = earnData.account.accountId.value in expandedAssetIds, isExpandable = true, ), ) } else { - earnData.earnCurrencues.map { token -> + earnData.earnCurrencies.map { token -> ForYouTokenListItemUM( tokenRowUM = rowConverter.convert(token.toPair()), tokenList = persistentListOf(), diff --git a/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/model/converter/earnOpportunities/ForYouEarnOpportunitiesTokenRowConverter.kt b/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/model/converter/earnOpportunities/ForYouEarnOpportunitiesTokenRowConverter.kt index 7e9b237516..cb60890b9d 100644 --- a/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/model/converter/earnOpportunities/ForYouEarnOpportunitiesTokenRowConverter.kt +++ b/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/model/converter/earnOpportunities/ForYouEarnOpportunitiesTokenRowConverter.kt @@ -22,6 +22,13 @@ import com.tangem.utils.extensions.orZero import kotlinx.collections.immutable.toImmutableList import java.math.BigDecimal +/** + * Maps one earn-eligible portfolio holding to a token row: network subtitle, projected yearly earn + * (`fiat balance * rate`) as the top end and the rate itself as the styled bottom end. + * + * Non-resolved statuses degrade the same way as in the portfolio review: loading → skeleton row, + * no-quote / no-address / unreachable → dashes, stale cache → error-sync icon on both ends. + */ internal class ForYouEarnOpportunitiesTokenRowConverter( private val appCurrency: AppCurrency, ) : Converter, TangemTokenRowUM> { diff --git a/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/model/converter/earnOpportunities/ForYouEarnOpportunitiesTokensActiveConverter.kt b/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/model/converter/earnOpportunities/ForYouEarnOpportunitiesTokensActiveConverter.kt index dbe05ab54a..2c0aaadc55 100644 --- a/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/model/converter/earnOpportunities/ForYouEarnOpportunitiesTokensActiveConverter.kt +++ b/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/model/converter/earnOpportunities/ForYouEarnOpportunitiesTokensActiveConverter.kt @@ -24,7 +24,7 @@ internal class ForYouEarnOpportunitiesTokensActiveConverter( override fun convert(value: List): EarnOpportunitiesUM { val activeAssetKeys = value - .flatMap { opportunities -> opportunities.earnCurrencues.keys } + .flatMap { opportunities -> opportunities.earnCurrencies.keys } .map { status -> status.currency.forYouEarnAssetKey() } .toSet() 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/portfolioReview/ForYouPortfolioReviewConverter.kt similarity index 58% rename from features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/model/converter/ForYouTokenListConverter.kt rename to features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/model/converter/portfolioReview/ForYouPortfolioReviewConverter.kt index 1b9f893650..6fd651133f 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/portfolioReview/ForYouPortfolioReviewConverter.kt @@ -1,4 +1,4 @@ -package com.tangem.features.foryou.impl.model.converter +package com.tangem.features.foryou.impl.model.converter.portfolioReview import com.tangem.common.ui.components.currency.icon.converter.CryptoCurrencyToIconStateConverter import com.tangem.core.ui.components.currency.icon.CurrencyIconState @@ -8,14 +8,21 @@ import com.tangem.core.ui.extensions.* 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.account.models.AccountStatusList import com.tangem.domain.appcurrency.model.AppCurrency +import com.tangem.domain.models.TotalFiatBalance import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.currency.CryptoCurrencyStatus +import com.tangem.domain.models.wallet.UserWalletId import com.tangem.features.foryou.impl.R import com.tangem.features.foryou.impl.entity.ForYouTokenListItemUM +import com.tangem.features.foryou.impl.entity.PortfolioReviewUM +import com.tangem.features.foryou.impl.model.converter.forYouGroupKey +import com.tangem.features.foryou.impl.model.converter.forYouPlaceholderBadge +import com.tangem.features.foryou.impl.model.converter.toForYouPercent import com.tangem.utils.converter.Converter +import com.tangem.utils.extensions.isZero import com.tangem.utils.extensions.orZero -import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toPersistentList import java.math.BigDecimal @@ -25,41 +32,78 @@ import java.math.BigDecimal * (see [forYouGroupKey]) and maps each group to a [ForYouTokenListItemUM] — an aggregate asset row plus, * when the asset spans more than one network, its per-network child rows. * - * The child rows are grouped by network (delegated to [ForYouTokenRowConverter]) so a network appears + * The child rows are grouped by network (delegated to [ForYouPortfolioReviewTokenRowConverter]) so a network appears * once per asset even if the asset is held on it in several accounts; * * Modelled on `TokenListStateConverter` (a list converter delegating to a per-item converter). */ -internal class ForYouTokenListConverter( +internal class ForYouPortfolioReviewConverter( private val appCurrency: AppCurrency, - private val totalFiatBalance: BigDecimal, private val expandedAssetIds: Set, private val expandClick: (assetId: String) -> Unit, - private val otherAssets: List, BigDecimal>>, - private val onTokenClick: (CryptoCurrency) -> Unit, -) : Converter, ImmutableList> { + private val onTokenClick: (UserWalletId, CryptoCurrency) -> Unit, +) : Converter { private val iconConverter = CryptoCurrencyToIconStateConverter() - private val rowConverter = ForYouTokenRowConverter( - appCurrency = appCurrency, - totalFiatBalance = totalFiatBalance, - onTokenClick = onTokenClick, - ) - override fun convert(value: List): ImmutableList { - val assetItems = value + override fun convert(value: AccountStatusList?): PortfolioReviewUM { + val currencies = value?.flattenCurrencies().orEmpty() + val loadedBalance = value?.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?.isZero() == true } .groupBy { it.forYouGroupKey() } - .map { (assetId, currencies) -> createListItem(assetId, currencies) } + .map { (_, networks) -> networks to networks.sumOf { it.value.fiatAmount.orZero() } } + .sortedByDescending { (_, assetBalance) -> assetBalance } + + // The top assets are shown individually (each flattened back to its networks so the converter can + // regroup them by network); the remaining assets are collapsed into a single "Other" row. + val topAssets = rankedAssets.take(TOP_HOLDINGS_COUNT) + val otherAssets = rankedAssets.drop(TOP_HOLDINGS_COUNT) + val topCurrencies = topAssets.flatMap { (networks, _) -> networks } + + val assetItems = topCurrencies + .groupBy { it.forYouGroupKey() } + .map { (assetId, currencies) -> + createListItem( + userWalletId = value?.userWalletId, + assetId = assetId, + currencies = currencies, + totalFiatBalance = totalFiatBalance, + ) + } // Assets beyond the top ones are collapsed into a single non-expandable "Other" row at the bottom. - return if (otherAssets.count() > 0) { - assetItems + createOtherItem() + val tokenList = if (otherAssets.count() > 0) { + assetItems + createOtherItem(otherAssets, totalFiatBalance) } else { assetItems }.toPersistentList() + + val marketChartUM = ForYouPortfolioReviewMarketChartConverter( + appCurrency = appCurrency, + topAssets = topAssets, + ).convert(value?.totalFiatBalance) + + return PortfolioReviewUM.Content( + tokenList = tokenList, + marketChartUM = marketChartUM, + ) } - private fun createListItem(assetId: String, currencies: List): ForYouTokenListItemUM { + private fun createListItem( + userWalletId: UserWalletId?, + assetId: String, + currencies: List, + totalFiatBalance: BigDecimal, + ): ForYouTokenListItemUM { // Group the asset's holdings by blockchain (network.id.rawId, derivation-independent) so each // network appears once even when the asset is held across several accounts/derivations on it, // summing those balances. Order by balance so the expanded breakdown reads top-down. @@ -67,11 +111,21 @@ internal class ForYouTokenListConverter( .groupBy { it.currency.network.id.rawId } .values .sortedByDescending { group -> group.sumOf { it.value.fiatAmount.orZero() } } + + val rowConverter = ForYouPortfolioReviewTokenRowConverter( + userWalletId = userWalletId, + appCurrency = appCurrency, + totalFiatBalance = totalFiatBalance, + onTokenClick = onTokenClick, + ) + return ForYouTokenListItemUM( tokenRowUM = createAssetRow( + userWalletId = userWalletId, assetId = assetId, currencies = currencies, networkCount = networkGroups.size, + totalFiatBalance = totalFiatBalance, ), tokenList = networkGroups.map(rowConverter::convertNetworkGroup).toPersistentList(), isExpanded = assetId in expandedAssetIds, @@ -81,8 +135,10 @@ internal class ForYouTokenListConverter( private fun createAssetRow( assetId: String, + userWalletId: UserWalletId?, currencies: List, networkCount: Int, + totalFiatBalance: BigDecimal, ): TangemTokenRowUM { if (currencies.all { it.value is CryptoCurrencyStatus.Loading }) { return TangemTokenRowUM.Loading(id = assetId) @@ -91,6 +147,12 @@ internal class ForYouTokenListConverter( val asset = currencies.first() val assetFiatBalance = currencies.sumOf { it.value.fiatAmount.orZero() } + val rowConverter = ForYouPortfolioReviewTokenRowConverter( + userWalletId = userWalletId, + appCurrency = appCurrency, + totalFiatBalance = totalFiatBalance, + onTokenClick = onTokenClick, + ) val endContent = rowConverter.toEndContent(statuses = currencies, fiatAmount = assetFiatBalance) val onlyCryptoCurrency = currencies.firstOrNull()?.currency @@ -120,7 +182,10 @@ internal class ForYouTokenListConverter( ) } - private fun createOtherItem(): ForYouTokenListItemUM { + private fun createOtherItem( + otherAssets: List, BigDecimal>>, + totalFiatBalance: BigDecimal, + ): ForYouTokenListItemUM { val otherAssetsBalance = otherAssets.sumOf { (_, assetBalance) -> assetBalance } return ForYouTokenListItemUM( tokenRowUM = TangemTokenRowUM.Content( @@ -158,5 +223,6 @@ internal class ForYouTokenListConverter( private companion object { const val OTHER_ROW_ID = "for_you_other_assets" + const val TOP_HOLDINGS_COUNT = 4 } } \ 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/portfolioReview/ForYouPortfolioReviewMarketChartConverter.kt similarity index 81% rename from features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/model/converter/ForYouMarketChartConverter.kt rename to features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/model/converter/portfolioReview/ForYouPortfolioReviewMarketChartConverter.kt index e767adc242..c727d3de6e 100644 --- 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/portfolioReview/ForYouPortfolioReviewMarketChartConverter.kt @@ -1,4 +1,4 @@ -package com.tangem.features.foryou.impl.model.converter +package com.tangem.features.foryou.impl.model.converter.portfolioReview import com.tangem.core.ui.extensions.stringReference import com.tangem.core.ui.format.bigdecimal.fiat @@ -7,13 +7,18 @@ 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.features.foryou.impl.components.state.AiInsightUM +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.model.converter.toForYouPercent import com.tangem.utils.converter.Converter import com.tangem.utils.extensions.orZero import kotlinx.collections.immutable.toPersistentList import java.math.BigDecimal -internal class ForYouMarketChartConverter( +internal class ForYouPortfolioReviewMarketChartConverter( private val appCurrency: AppCurrency, private val topAssets: List, BigDecimal>>, ) : Converter { 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/portfolioReview/ForYouPortfolioReviewTokenRowConverter.kt similarity index 93% rename from features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/model/converter/ForYouTokenRowConverter.kt rename to features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/model/converter/portfolioReview/ForYouPortfolioReviewTokenRowConverter.kt index 1a9f41f22a..41a34e5d3a 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/portfolioReview/ForYouPortfolioReviewTokenRowConverter.kt @@ -1,4 +1,4 @@ -package com.tangem.features.foryou.impl.model.converter +package com.tangem.features.foryou.impl.model.converter.portfolioReview import androidx.compose.ui.text.SpanStyle import com.tangem.common.ui.components.currency.icon.converter.CryptoCurrencyToIconStateConverter @@ -16,6 +16,9 @@ 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.wallet.UserWalletId +import com.tangem.features.foryou.impl.model.converter.forYouPlaceholderBadge +import com.tangem.features.foryou.impl.model.converter.toForYouPercent import com.tangem.utils.StringsSigns import com.tangem.utils.extensions.orZero import kotlinx.collections.immutable.persistentListOf @@ -39,10 +42,11 @@ import java.math.BigDecimal * 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 ForYouPortfolioReviewTokenRowConverter( private val appCurrency: AppCurrency, + private val userWalletId: UserWalletId?, private val totalFiatBalance: BigDecimal, - private val onTokenClick: (CryptoCurrency) -> Unit, + private val onTokenClick: (UserWalletId, CryptoCurrency) -> Unit, ) { private val iconConverter = CryptoCurrencyToIconStateConverter() @@ -66,7 +70,7 @@ internal class ForYouTokenRowConverter( subtitleUM = toRowSubtitle(state, currency, cryptoAmount), topEndContentUM = toRowTopEnd(state, fiatAmount), bottomEndContentUM = toRowBottomEnd(state, fiatAmount), - onItemClick = { onTokenClick(currency) }, + onItemClick = { if (userWalletId != null) onTokenClick(userWalletId, currency) }, onItemLongClick = null, ) } @@ -88,9 +92,9 @@ internal class ForYouTokenRowConverter( ) } - /** Title: For You always shows the asset symbol with the placeholder price-change badge. */ + /** Title: For You always shows the asset name with the placeholder price-change badge. */ private fun toRowTitle(currency: CryptoCurrency): TangemTokenRowUM.TitleUM = TangemTokenRowUM.TitleUM.Content( - text = stringReference(currency.symbol), + text = stringReference(currency.name), badge = forYouPlaceholderBadge(), ) 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 b07e6bc239..da9d96179e 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 @@ -4,94 +4,48 @@ 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.domain.account.models.AccountStatusList -import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.models.StatusSource import com.tangem.domain.models.TotalFiatBalance -import com.tangem.domain.models.currency.CryptoCurrency +import com.tangem.features.foryou.impl.entity.EarnOpportunitiesUM 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.extensions.isZero -import com.tangem.utils.extensions.orZero +import com.tangem.features.foryou.impl.model.converter.portfolioReview.ForYouPortfolioReviewConverter import com.tangem.utils.transformer.Transformer import kotlinx.collections.immutable.persistentListOf /** - * 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). + * Applies one combined emission to the [ForYouUM] state: sets the pre-built portfolio-review and + * earn-opportunities sections (see [ForYouPortfolioReviewConverter] and + * `ForYouEarnOpportunitiesConverter`) and derives the outdated-data notification from + * [accountStatusList]'s total-balance source. * - * 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. + + * subsequent refreshes the previous picker is carried over so the user's selection is not reset. * * Modelled on `SetTokenListTransformer` (a transformer that rebuilds the state while delegating the - * token-list construction to a dedicated converter). + * section construction to dedicated converters). */ -@Suppress("LongParameterList") internal class SetPortfolioReviewTransformer( private val accountStatusList: AccountStatusList?, - private val appCurrency: AppCurrency, - private val expandedAssetIds: Set, - private val expandClick: (assetId: String) -> Unit, - private val onPeriodClick: (TangemSegmentUM) -> Unit, - private val onTokenClick: (CryptoCurrency) -> Unit, + private val portfolioReviewUM: PortfolioReviewUM, + private val earnOpportunitiesUM: EarnOpportunitiesUM, ) : Transformer { override fun transform(prevState: ForYouUM): ForYouUM { - 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?.isZero() == true } - .groupBy { it.forYouGroupKey() } - .map { (_, networks) -> networks to networks.sumOf { it.value.fiatAmount.orZero() } } - .sortedByDescending { (_, assetBalance) -> assetBalance } - - // The top assets are shown individually (each flattened back to its networks so the converter can - // regroup them by network); the remaining assets are collapsed into a single "Other" row. - val topAssets = rankedAssets.take(TOP_HOLDINGS_COUNT) - val otherAssets = rankedAssets.drop(TOP_HOLDINGS_COUNT) - val topCurrencies = topAssets.flatMap { (networks, _) -> networks } - - val tokenList = ForYouTokenListConverter( - appCurrency = appCurrency, - totalFiatBalance = totalFiatBalance, - expandedAssetIds = expandedAssetIds, - expandClick = expandClick, - otherAssets = otherAssets, - onTokenClick = onTokenClick, - ).convert(topCurrencies) - - val marketChartUM = ForYouMarketChartConverter( - appCurrency = appCurrency, - topAssets = topAssets, - ).convert(accountStatusList?.totalFiatBalance) - return prevState.copy( notifications = if (loadedBalance?.source == StatusSource.ONLY_CACHE) { persistentListOf(ForYouNotification.UsedOutdatedData) } else { persistentListOf() }, - portfolioReviewUM = PortfolioReviewUM.Content( - periodPickerUM = when (prevState.portfolioReviewUM) { - is PortfolioReviewUM.Content -> prevState.portfolioReviewUM.periodPickerUM - is PortfolioReviewUM.Loading -> createPeriodPicker() - }, - tokenList = tokenList, - marketChartUM = marketChartUM, - onPeriodClick = onPeriodClick, - ), + earnOpportunities = earnOpportunitiesUM, + portfolioReviewUM = portfolioReviewUM, + periodPickerUM = when (prevState.portfolioReviewUM) { + is PortfolioReviewUM.Loading -> createPeriodPicker() + is PortfolioReviewUM.Content -> prevState.periodPickerUM + }, ) } @@ -109,8 +63,4 @@ internal class SetPortfolioReviewTransformer( isAltSurface = true, ) } - - private companion object { - const val TOP_HOLDINGS_COUNT = 4 - } } \ No newline at end of file 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 7314fb2afc..63a964717d 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 @@ -19,8 +19,11 @@ 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.tabs.TangemSegmentUM +import com.tangem.core.ui.ds.tabs.TangemSegmentedPickerUM import com.tangem.core.ui.ds2.messagebanner.TangemMessageBanner import com.tangem.core.ui.extensions.conditional +import com.tangem.core.ui.extensions.stringReference import com.tangem.core.ui.res.LocalMainBottomSheetColor import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreviewRedesign @@ -78,6 +81,15 @@ internal fun ForYouContent( ForYouPortfolioReview( portfolioReviewUM = forYouUM.portfolioReviewUM, + periodPickerUM = forYouUM.periodPickerUM, + onPeriodClick = forYouUM.onPeriodClick, + modifier = Modifier.padding(horizontal = 16.dp), + ) + + SpacerH(48.dp) + + ForYouEarnOpportunities( + earnOpportunitiesUM = forYouUM.earnOpportunities, modifier = Modifier.padding(horizontal = 16.dp), ) @@ -114,6 +126,17 @@ private class ForYouContentPreviewProvider : PreviewParameterProvider notifications = persistentListOf(ForYouNotification.UsedOutdatedData), earnOpportunities = ForYouEarnOpportunitiesPreviewData.tokensRewards, portfolioReviewUM = ForYouPortfolioReviewPreviewData.reviewContent, + periodPickerUM = TangemSegmentedPickerUM( + items = persistentListOf( + TangemSegmentUM(id = "0", title = stringReference("Day")), + TangemSegmentUM(id = "1", title = stringReference("Week")), + TangemSegmentUM(id = "2", title = stringReference("Month")), + ), + initialSelectedItem = TangemSegmentUM(id = "0", title = stringReference("Day")), + isFixed = true, + isAltSurface = true, + ), + onPeriodClick = {}, ), ) } 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 5cf487bab4..e6b07656e9 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 @@ -14,7 +14,9 @@ import androidx.compose.ui.unit.dp 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.TangemSegmentUM import com.tangem.core.ui.ds.tabs.TangemSegmentedPicker +import com.tangem.core.ui.ds.tabs.TangemSegmentedPickerUM import com.tangem.core.ui.ds2.badge.TangemBadge import com.tangem.core.ui.ds2.shimmers.TangemShimmer import com.tangem.core.ui.extensions.stringReference @@ -34,7 +36,12 @@ import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toPersistentList @Composable -internal fun ForYouPortfolioReview(portfolioReviewUM: PortfolioReviewUM, modifier: Modifier = Modifier) { +internal fun ForYouPortfolioReview( + periodPickerUM: TangemSegmentedPickerUM, + onPeriodClick: (TangemSegmentUM) -> Unit, + portfolioReviewUM: PortfolioReviewUM, + modifier: Modifier = Modifier, +) { Column(modifier = modifier) { Row( modifier = Modifier.fillMaxWidth(), @@ -64,8 +71,8 @@ internal fun ForYouPortfolioReview(portfolioReviewUM: PortfolioReviewUM, modifie when (portfolioReviewUM) { is PortfolioReviewUM.Content -> { TangemSegmentedPicker( - tangemSegmentedPickerUM = portfolioReviewUM.periodPickerUM, - onClick = portfolioReviewUM.onPeriodClick, + tangemSegmentedPickerUM = periodPickerUM, + onClick = onPeriodClick, ) } is PortfolioReviewUM.Loading -> TangemShimmer( @@ -91,6 +98,17 @@ private fun ForYouPortfolioReview_Review( ForYouPortfolioReview( portfolioReviewUM = params, modifier = Modifier.background(TangemTheme.colors3.bg.primary), + periodPickerUM = TangemSegmentedPickerUM( + items = persistentListOf( + TangemSegmentUM(id = "0", title = stringReference("Day")), + TangemSegmentUM(id = "1", title = stringReference("Week")), + TangemSegmentUM(id = "2", title = stringReference("Month")), + ), + initialSelectedItem = TangemSegmentUM(id = "0", title = stringReference("Day")), + isFixed = true, + isAltSurface = true, + ), + onPeriodClick = {}, ) } } 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 5bb66c7949..75aea81cde 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 @@ -7,8 +7,6 @@ import com.tangem.core.ui.ds.badge.TangemBadgeType import com.tangem.core.ui.ds.badge.TangemBadgeUM import com.tangem.core.ui.ds.image.TangemIconUM 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 @@ -23,17 +21,6 @@ import java.math.BigDecimal internal object ForYouPortfolioReviewPreviewData { val reviewContent = PortfolioReviewUM.Content( - periodPickerUM = TangemSegmentedPickerUM( - items = persistentListOf( - TangemSegmentUM(id = "0", title = stringReference("Day")), - TangemSegmentUM(id = "1", title = stringReference("Week")), - TangemSegmentUM(id = "2", title = stringReference("Month")), - ), - initialSelectedItem = TangemSegmentUM(id = "0", title = stringReference("Day")), - isFixed = true, - isAltSurface = true, - ), - onPeriodClick = {}, marketChartUM = MarketChartUM.Loaded( donutChart = DonutChartUM.Loaded( totalAmount = "10000$",