Updated on 2026-08-14
This commit is contained in:
parent
d894698a9b
commit
c744af9a6a
17 changed files with 402 additions and 109 deletions
|
|
@ -908,9 +908,11 @@
|
|||
<item quantity="one">%d asset</item>
|
||||
<item quantity="other">%d assets</item>
|
||||
</plurals>
|
||||
<string name="market_chart_bubble_no_amount">No amount
on tokens</string>
|
||||
<string name="market_chart_bubble_no_data">No data</string>
|
||||
<string name="market_chart_bubble_total_value">Total value</string>
|
||||
<string name="market_chart_can_not_load_data">Can’t load data</string>
|
||||
<string name="market_chart_no_amount">You don’t have any tokens with amount</string>
|
||||
<string name="market_chart_top_holding">Top holding %s</string>
|
||||
<string name="markets_about_coin_header">About coin</string>
|
||||
<string name="markets_add_to_my_portfolio_description">To buy, exchange, or receive this asset, add it to your portfolio</string>
|
||||
|
|
|
|||
|
|
@ -69,7 +69,14 @@ internal class DefaultForYouComponent @AssistedInject constructor(
|
|||
childFactory = { config, componentContext ->
|
||||
when (config) {
|
||||
ForYouBottomSheetConfig.AddToPortfolio -> portfolioSelectorChild(componentContext)
|
||||
is ForYouBottomSheetConfig.ManageFunds -> manageFundsChild(config, componentContext)
|
||||
is ForYouBottomSheetConfig.ManageFunds -> manageFundsChild(
|
||||
componentContext = componentContext,
|
||||
launchMode = ManageFundsComponent.LaunchMode.FilteredByRawId(config.rawCurrencyId),
|
||||
)
|
||||
is ForYouBottomSheetConfig.AddFunds -> manageFundsChild(
|
||||
componentContext = componentContext,
|
||||
launchMode = ManageFundsComponent.LaunchMode.ChooseToken(config.userWalletId),
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
|
|
@ -129,12 +136,12 @@ internal class DefaultForYouComponent @AssistedInject constructor(
|
|||
)
|
||||
|
||||
private fun manageFundsChild(
|
||||
config: ForYouBottomSheetConfig.ManageFunds,
|
||||
launchMode: ManageFundsComponent.LaunchMode,
|
||||
componentContext: ComponentContext,
|
||||
): ComposableBottomSheetComponent = manageFundsComponentFactory.create(
|
||||
context = childByContext(componentContext),
|
||||
params = ManageFundsComponent.Params(
|
||||
launchMode = ManageFundsComponent.LaunchMode.FilteredByRawId(config.rawCurrencyId),
|
||||
launchMode = launchMode,
|
||||
onDismiss = { model.bottomSheetNavigation.dismiss() },
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import androidx.compose.ui.input.pointer.pointerInput
|
|||
import androidx.compose.ui.layout.boundsInWindow
|
||||
import androidx.compose.ui.layout.onGloballyPositioned
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
|
|
@ -51,13 +52,16 @@ internal fun MarketChart(marketChart: MarketChartUM, modifier: Modifier = Modifi
|
|||
Column(modifier = Modifier.fillMaxWidth()) {
|
||||
DonutChartBlock(marketChart.donutChart, cardBoundsInWindow)
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
if (marketChart is MarketChartUM.Loaded) {
|
||||
TopHoldingBlock(
|
||||
assetCount = marketChart.assetCount,
|
||||
topHoldingPercent = marketChart.topHoldingPercent,
|
||||
)
|
||||
} else {
|
||||
CantLoadDataBlock()
|
||||
when (marketChart) {
|
||||
is MarketChartUM.Loaded -> {
|
||||
TopHoldingBlock(
|
||||
assetCount = marketChart.assetCount,
|
||||
topHoldingPercent = marketChart.topHoldingPercent,
|
||||
)
|
||||
}
|
||||
is MarketChartUM.NoData -> {
|
||||
CantLoadDataBlock(text = marketChart.title)
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
|
@ -113,36 +117,40 @@ private fun ColumnScope.DonutChartBlock(donutChartUM: DonutChartUM, cardBoundsIn
|
|||
},
|
||||
segments = segments,
|
||||
) {
|
||||
if (donutChartUM is DonutChartUM.Loaded) {
|
||||
Text(
|
||||
text = donutChartUM.totalAmount,
|
||||
color = TangemTheme.colors3.text.primary,
|
||||
style = TangemTheme.typography3.body.medium,
|
||||
maxLines = 1,
|
||||
autoSize = TextAutoSize.StepBased(
|
||||
minFontSize = 8.sp,
|
||||
maxFontSize = TangemTheme.typography3.body.medium.fontSize,
|
||||
),
|
||||
)
|
||||
Text(
|
||||
text = stringResourceSafe(R.string.market_chart_bubble_total_value),
|
||||
color = TangemTheme.colors3.text.secondary,
|
||||
style = TangemTheme.typography3.caption.medium,
|
||||
maxLines = 1,
|
||||
autoSize = TextAutoSize.StepBased(
|
||||
maxFontSize = TangemTheme.typography3.caption.medium.fontSize,
|
||||
),
|
||||
)
|
||||
} else {
|
||||
Text(
|
||||
text = stringResourceSafe(R.string.market_chart_bubble_no_data),
|
||||
color = TangemTheme.colors3.text.secondary,
|
||||
style = TangemTheme.typography3.body.medium,
|
||||
maxLines = 1,
|
||||
autoSize = TextAutoSize.StepBased(
|
||||
maxFontSize = TangemTheme.typography3.caption.medium.fontSize,
|
||||
),
|
||||
)
|
||||
when (donutChartUM) {
|
||||
is DonutChartUM.Loaded -> {
|
||||
Text(
|
||||
text = donutChartUM.totalAmount,
|
||||
color = TangemTheme.colors3.text.primary,
|
||||
style = TangemTheme.typography3.body.medium,
|
||||
maxLines = 1,
|
||||
autoSize = TextAutoSize.StepBased(
|
||||
minFontSize = 8.sp,
|
||||
maxFontSize = TangemTheme.typography3.body.medium.fontSize,
|
||||
),
|
||||
)
|
||||
Text(
|
||||
text = stringResourceSafe(R.string.market_chart_bubble_total_value),
|
||||
color = TangemTheme.colors3.text.secondary,
|
||||
style = TangemTheme.typography3.caption.medium,
|
||||
maxLines = 1,
|
||||
autoSize = TextAutoSize.StepBased(
|
||||
maxFontSize = TangemTheme.typography3.caption.medium.fontSize,
|
||||
),
|
||||
)
|
||||
}
|
||||
is DonutChartUM.NoData -> {
|
||||
Text(
|
||||
text = donutChartUM.title.resolveReference(),
|
||||
color = TangemTheme.colors3.text.secondary,
|
||||
style = TangemTheme.typography3.body.medium,
|
||||
maxLines = 2,
|
||||
textAlign = TextAlign.Center,
|
||||
autoSize = TextAutoSize.StepBased(
|
||||
maxFontSize = TangemTheme.typography3.caption.medium.fontSize,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -233,10 +241,10 @@ private fun ColumnScope.TopHoldingBlock(assetCount: Int, topHoldingPercent: Text
|
|||
}
|
||||
|
||||
@Composable
|
||||
private fun ColumnScope.CantLoadDataBlock() {
|
||||
private fun ColumnScope.CantLoadDataBlock(text: TextReference) {
|
||||
Text(
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
text = stringResourceSafe(R.string.market_chart_can_not_load_data),
|
||||
text = text.resolveReference(),
|
||||
color = TangemTheme.colors3.text.secondary,
|
||||
style = TangemTheme.typography3.heading.small,
|
||||
)
|
||||
|
|
@ -306,7 +314,10 @@ private fun previewMarketChartState(scenario: MarketChartPreviewScenario): Marke
|
|||
),
|
||||
),
|
||||
)
|
||||
MarketChartPreviewScenario.NO_DATA -> MarketChartUM.NoData
|
||||
MarketChartPreviewScenario.NO_DATA -> MarketChartUM.NoData(
|
||||
title = resourceReference(R.string.market_chart_can_not_load_data),
|
||||
donutText = resourceReference(R.string.market_chart_bubble_no_data),
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
|
|
|
|||
|
|
@ -21,8 +21,11 @@ internal sealed class MarketChartUM(
|
|||
val assetCount: Int = donutChart.donutSegmentList.size
|
||||
}
|
||||
|
||||
data object NoData : MarketChartUM(
|
||||
donutChart = DonutChartUM.NoData,
|
||||
data class NoData(
|
||||
val title: TextReference,
|
||||
private val donutText: TextReference,
|
||||
) : MarketChartUM(
|
||||
donutChart = DonutChartUM.NoData(title = donutText),
|
||||
aiInsight = AiInsightUM.Hide,
|
||||
)
|
||||
}
|
||||
|
|
@ -36,7 +39,9 @@ internal sealed class DonutChartUM(
|
|||
override val donutSegmentList: ImmutableList<DonutSegmentUM>,
|
||||
) : DonutChartUM(donutSegmentList = donutSegmentList)
|
||||
|
||||
data object NoData : DonutChartUM(donutSegmentList = persistentListOf())
|
||||
data class NoData(
|
||||
val title: TextReference,
|
||||
) : DonutChartUM(donutSegmentList = persistentListOf())
|
||||
}
|
||||
|
||||
@Immutable
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package com.tangem.features.foryou.impl.entity
|
||||
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
|
||||
internal sealed interface ForYouBottomSheetConfig {
|
||||
|
||||
|
|
@ -9,4 +10,8 @@ internal sealed interface ForYouBottomSheetConfig {
|
|||
data class ManageFunds(
|
||||
val rawCurrencyId: CryptoCurrency.RawID,
|
||||
) : ForYouBottomSheetConfig
|
||||
|
||||
data class AddFunds(
|
||||
val userWalletId: UserWalletId,
|
||||
) : ForYouBottomSheetConfig
|
||||
}
|
||||
|
|
@ -31,6 +31,7 @@ internal sealed interface PortfolioReviewUM {
|
|||
data class Content(
|
||||
override val tokenList: ImmutableList<ForYouTokenListItemUM>,
|
||||
override val marketChartUM: MarketChartUM,
|
||||
val onAddFundsClick: (() -> Unit)?,
|
||||
) : PortfolioReviewUM
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ 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.core.ui.extensions.resourceReference
|
||||
import com.tangem.domain.account.status.supplier.MultiAccountStatusListSupplier
|
||||
import com.tangem.domain.account.status.usecase.IsAccountsModeEnabledUseCase
|
||||
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
|
||||
|
|
@ -32,6 +33,7 @@ import com.tangem.domain.staking.usecase.StakingAvailabilityListUseCase
|
|||
import com.tangem.domain.yield.supply.usecase.YieldSupplyApyFlowUseCase
|
||||
import com.tangem.features.commonfeatures.api.addtoportfolio.AddToPortfolioManager
|
||||
import com.tangem.features.foryou.ForYouComponent
|
||||
import com.tangem.features.foryou.impl.R
|
||||
import com.tangem.features.foryou.impl.components.state.MarketChartUM
|
||||
import com.tangem.features.foryou.impl.entity.*
|
||||
import com.tangem.features.foryou.impl.model.converter.TOP_EARN_TOKENS_BATCH_SIZE
|
||||
|
|
@ -106,7 +108,10 @@ internal class ForYouModel @Inject constructor(
|
|||
),
|
||||
onPeriodClick = ::onPeriodClick,
|
||||
portfolioReviewUM = PortfolioReviewUM.Loading(
|
||||
marketChartUM = MarketChartUM.NoData,
|
||||
marketChartUM = MarketChartUM.NoData(
|
||||
title = resourceReference(R.string.market_chart_can_not_load_data),
|
||||
donutText = resourceReference(R.string.market_chart_bubble_no_data),
|
||||
),
|
||||
tokenList = buildList<ForYouTokenListItemUM> {
|
||||
repeat(4) { index ->
|
||||
add(
|
||||
|
|
@ -154,6 +159,7 @@ internal class ForYouModel @Inject constructor(
|
|||
expandedAssetIds = expandedPortfolioReview,
|
||||
expandClick = ::onExpandPortfolioReviewClick,
|
||||
onTokenClick = ::onPortfolioReviewTokenClick,
|
||||
onAddFundsClick = ::onAddFundsClick,
|
||||
).convert(accountStatusList)
|
||||
|
||||
val earnOpportunitiesUM = ForYouEarnOpportunitiesConverter(
|
||||
|
|
@ -363,4 +369,8 @@ internal class ForYouModel @Inject constructor(
|
|||
},
|
||||
)
|
||||
}
|
||||
|
||||
private fun onAddFundsClick(userWalletId: UserWalletId) {
|
||||
bottomSheetNavigation.activate(ForYouBottomSheetConfig.AddFunds(userWalletId))
|
||||
}
|
||||
}
|
||||
|
|
@ -15,8 +15,10 @@ 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.components.state.MarketChartUM
|
||||
import com.tangem.features.foryou.impl.entity.ForYouTokenListItemUM
|
||||
import com.tangem.features.foryou.impl.entity.PortfolioReviewUM
|
||||
import com.tangem.features.foryou.impl.model.converter.FOR_YOU_TOP_EARN_TOKENS_COUNT
|
||||
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
|
||||
|
|
@ -42,6 +44,7 @@ internal class ForYouPortfolioReviewConverter(
|
|||
private val expandedAssetIds: Set<String>,
|
||||
private val expandClick: (assetId: String) -> Unit,
|
||||
private val onTokenClick: (UserWalletId, CryptoCurrency) -> Unit,
|
||||
private val onAddFundsClick: (UserWalletId) -> Unit,
|
||||
) : Converter<AccountStatusList?, PortfolioReviewUM> {
|
||||
|
||||
private val iconConverter = CryptoCurrencyToIconStateConverter()
|
||||
|
|
@ -51,6 +54,26 @@ internal class ForYouPortfolioReviewConverter(
|
|||
val loadedBalance = value?.totalFiatBalance as? TotalFiatBalance.Loaded
|
||||
val totalFiatBalance = loadedBalance?.amount.orZero()
|
||||
|
||||
if (currencies.all { it.value.fiatAmount?.isZero() == true }) {
|
||||
return PortfolioReviewUM.Content(
|
||||
tokenList = currencies.take(FOR_YOU_TOP_EARN_TOKENS_COUNT)
|
||||
.groupBy { it.forYouGroupKey() }
|
||||
.map { (assetId, currencies) ->
|
||||
createListItem(
|
||||
userWalletId = value?.userWalletId,
|
||||
assetId = assetId,
|
||||
currencies = currencies,
|
||||
totalFiatBalance = totalFiatBalance,
|
||||
)
|
||||
}.toPersistentList(),
|
||||
marketChartUM = MarketChartUM.NoData(
|
||||
title = resourceReference(R.string.market_chart_no_amount),
|
||||
donutText = resourceReference(R.string.market_chart_bubble_no_amount),
|
||||
),
|
||||
onAddFundsClick = { value?.userWalletId?.let(onAddFundsClick) },
|
||||
)
|
||||
}
|
||||
|
||||
// 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
|
||||
|
|
@ -95,6 +118,7 @@ internal class ForYouPortfolioReviewConverter(
|
|||
return PortfolioReviewUM.Content(
|
||||
tokenList = tokenList,
|
||||
marketChartUM = marketChartUM,
|
||||
onAddFundsClick = null,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -127,7 +151,7 @@ internal class ForYouPortfolioReviewConverter(
|
|||
networkCount = networkGroups.size,
|
||||
totalFiatBalance = totalFiatBalance,
|
||||
),
|
||||
tokenList = networkGroups.map(rowConverter::convertNetworkGroup).toPersistentList(),
|
||||
tokenList = networkGroups.map(rowConverter::convert).toPersistentList(),
|
||||
isExpanded = assetId in expandedAssetIds,
|
||||
isExpandable = true,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.features.foryou.impl.model.converter.portfolioReview
|
||||
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.core.ui.format.bigdecimal.fiat
|
||||
import com.tangem.core.ui.format.bigdecimal.format
|
||||
|
|
@ -7,6 +8,7 @@ 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.R
|
||||
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
|
||||
|
|
@ -54,7 +56,10 @@ internal class ForYouPortfolioReviewMarketChartConverter(
|
|||
TotalFiatBalance.Loading,
|
||||
TotalFiatBalance.Failed,
|
||||
null,
|
||||
-> MarketChartUM.NoData
|
||||
-> MarketChartUM.NoData(
|
||||
title = resourceReference(R.string.market_chart_can_not_load_data),
|
||||
donutText = resourceReference(R.string.market_chart_bubble_no_data),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -20,6 +20,7 @@ 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.converter.Converter
|
||||
import com.tangem.utils.extensions.orZero
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
|
|
@ -47,21 +48,20 @@ internal class ForYouPortfolioReviewTokenRowConverter(
|
|||
private val userWalletId: UserWalletId?,
|
||||
private val totalFiatBalance: BigDecimal,
|
||||
private val onTokenClick: (UserWalletId, CryptoCurrency) -> Unit,
|
||||
) {
|
||||
) : Converter<List<CryptoCurrencyStatus>, TangemTokenRowUM> {
|
||||
|
||||
private val iconConverter = CryptoCurrencyToIconStateConverter()
|
||||
|
||||
/** Builds one row for all [statuses] of a single asset on the same network. */
|
||||
fun convertNetworkGroup(statuses: List<CryptoCurrencyStatus>): TangemTokenRowUM {
|
||||
val representative = statuses.first()
|
||||
if (statuses.all { it.value is CryptoCurrencyStatus.Loading }) {
|
||||
override fun convert(value: List<CryptoCurrencyStatus>): TangemTokenRowUM {
|
||||
val representative = value.first()
|
||||
if (value.all { it.value is CryptoCurrencyStatus.Loading }) {
|
||||
return TangemTokenRowUM.Loading(id = representative.currency.id.value)
|
||||
}
|
||||
|
||||
val currency = representative.currency
|
||||
val cryptoAmount = statuses.sumOf { it.value.amount.orZero() }
|
||||
val fiatAmount = statuses.sumOf { it.value.fiatAmount.orZero() }
|
||||
val state = statuses.classify()
|
||||
val cryptoAmount = value.sumOf { it.value.amount.orZero() }
|
||||
val fiatAmount = value.sumOf { it.value.fiatAmount.orZero() }
|
||||
val state = value.classify()
|
||||
|
||||
return TangemTokenRowUM.Content(
|
||||
id = currency.id.value,
|
||||
|
|
@ -78,7 +78,7 @@ internal class ForYouPortfolioReviewTokenRowConverter(
|
|||
/**
|
||||
* 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
|
||||
* unreachable treatment as [convert], 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.
|
||||
*
|
||||
|
|
@ -158,7 +158,7 @@ internal class ForYouPortfolioReviewTokenRowConverter(
|
|||
/** 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() }),
|
||||
text = stringReference(fiatAmount.toForYouPercent(totalFiatBalance).orZero().format { percent() }),
|
||||
isFlickering = state.isFlickering,
|
||||
)
|
||||
RowState.NoAddress -> attentionEndContent(R.string.common_no_address)
|
||||
|
|
|
|||
|
|
@ -58,7 +58,11 @@ internal fun ForYouContent(
|
|||
.drawBehind { drawRect(background.value) },
|
||||
) {
|
||||
promoBannersBlockComponent.ContentWithPadding(
|
||||
modifier = Modifier.padding(top = 12.dp),
|
||||
modifier = Modifier
|
||||
.padding(top = 12.dp)
|
||||
.conditional(forYouUM.notifications.isEmpty()) {
|
||||
padding(bottom = 48.dp)
|
||||
},
|
||||
walletId = null,
|
||||
horizontalItemPadding = 16.dp,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -13,12 +13,13 @@ import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
|||
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.button.TangemButton
|
||||
import com.tangem.core.ui.ds2.shimmers.TangemShimmer
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.core.ui.extensions.stringResourceSafe
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
|
|
@ -27,13 +28,10 @@ import com.tangem.core.ui.res.generated.icons.Icons
|
|||
import com.tangem.core.ui.res.generated.icons.ic_chevron_down_16
|
||||
import com.tangem.features.foryou.impl.R
|
||||
import com.tangem.features.foryou.impl.components.MarketChart
|
||||
import com.tangem.features.foryou.impl.components.state.MarketChartUM
|
||||
import com.tangem.features.foryou.impl.entity.ForYouTokenListItemUM
|
||||
import com.tangem.features.foryou.impl.entity.PortfolioReviewUM
|
||||
import com.tangem.features.foryou.impl.ui.components.ForYouPortfolioTokenList
|
||||
import com.tangem.features.foryou.impl.ui.preview.ForYouPortfolioReviewPreviewData
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
|
||||
@Composable
|
||||
internal fun ForYouPortfolioReview(
|
||||
|
|
@ -84,6 +82,18 @@ internal fun ForYouPortfolioReview(
|
|||
}
|
||||
|
||||
ForYouPortfolioTokenList(tokenList = portfolioReviewUM.tokenList)
|
||||
|
||||
if (portfolioReviewUM is PortfolioReviewUM.Content && portfolioReviewUM.onAddFundsClick != null) {
|
||||
TangemButton(
|
||||
text = resourceReference(R.string.common_add_funds),
|
||||
onClick = portfolioReviewUM.onAddFundsClick,
|
||||
variant = TangemButton.Variant.Secondary,
|
||||
size = TangemButton.Size.X9,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(top = 8.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -117,23 +127,8 @@ private class ForYouPortfolioReviewPreviewProvider : PreviewParameterProvider<Po
|
|||
override val values: Sequence<PortfolioReviewUM>
|
||||
get() = sequenceOf(
|
||||
ForYouPortfolioReviewPreviewData.reviewContent,
|
||||
PortfolioReviewUM.Loading(
|
||||
marketChartUM = MarketChartUM.NoData,
|
||||
tokenList = buildList {
|
||||
repeat(4) { index ->
|
||||
add(
|
||||
ForYouTokenListItemUM(
|
||||
tokenRowUM = TangemTokenRowUM.Loading(
|
||||
id = index.toString(),
|
||||
),
|
||||
tokenList = persistentListOf(),
|
||||
isExpanded = false,
|
||||
isExpandable = false,
|
||||
),
|
||||
)
|
||||
}
|
||||
}.toPersistentList(),
|
||||
),
|
||||
ForYouPortfolioReviewPreviewData.loadingState,
|
||||
ForYouPortfolioReviewPreviewData.zeroPortfolioState,
|
||||
)
|
||||
}
|
||||
// endregion
|
||||
|
|
@ -7,7 +7,9 @@ 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.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.features.foryou.impl.R
|
||||
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
|
||||
|
|
@ -16,6 +18,7 @@ import com.tangem.features.foryou.impl.entity.ForYouTokenListItemUM
|
|||
import com.tangem.features.foryou.impl.entity.PortfolioReviewUM
|
||||
import com.tangem.utils.StringsSigns.DOT
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
import java.math.BigDecimal
|
||||
|
||||
internal object ForYouPortfolioReviewPreviewData {
|
||||
|
|
@ -151,5 +154,70 @@ internal object ForYouPortfolioReviewPreviewData {
|
|||
isExpandable = false,
|
||||
),
|
||||
),
|
||||
onAddFundsClick = null,
|
||||
)
|
||||
|
||||
val loadingState = PortfolioReviewUM.Loading(
|
||||
marketChartUM = MarketChartUM.NoData(
|
||||
title = resourceReference(R.string.market_chart_can_not_load_data),
|
||||
donutText = resourceReference(R.string.market_chart_bubble_no_data),
|
||||
),
|
||||
tokenList = buildList {
|
||||
repeat(5) { index ->
|
||||
add(
|
||||
ForYouTokenListItemUM(
|
||||
tokenRowUM = TangemTokenRowUM.Loading(
|
||||
id = index.toString(),
|
||||
),
|
||||
tokenList = persistentListOf(),
|
||||
isExpanded = false,
|
||||
isExpandable = false,
|
||||
),
|
||||
)
|
||||
}
|
||||
}.toPersistentList(),
|
||||
)
|
||||
|
||||
val zeroPortfolioState = PortfolioReviewUM.Content(
|
||||
marketChartUM = MarketChartUM.NoData(
|
||||
title = stringReference("You don’t have any tokens with amount"),
|
||||
donutText = stringReference("No amount on tokens"),
|
||||
),
|
||||
tokenList = buildList {
|
||||
repeat(5) { index ->
|
||||
add(
|
||||
ForYouTokenListItemUM(
|
||||
tokenRowUM = TangemTokenRowUM.Content(
|
||||
id = "token_$index",
|
||||
headIconUM = TangemIconUM.Currency(CurrencyIconState.Loading),
|
||||
titleUM = TangemTokenRowUM.TitleUM.Content(
|
||||
text = stringReference("Token $index"),
|
||||
badge = TangemBadgeUM(
|
||||
text = stringReference("Positive"),
|
||||
size = TangemBadgeSize.X4,
|
||||
type = TangemBadgeType.Tinted,
|
||||
color = TangemBadgeColor.Green,
|
||||
),
|
||||
),
|
||||
subtitleUM = TangemTokenRowUM.SubtitleUM.Content(
|
||||
text = stringReference("Some network"),
|
||||
),
|
||||
topEndContentUM = TangemTokenRowUM.EndContentUM.Content(
|
||||
text = stringReference("\$0"),
|
||||
),
|
||||
bottomEndContentUM = TangemTokenRowUM.EndContentUM.Content(
|
||||
text = stringReference("0.00%"),
|
||||
),
|
||||
onItemClick = {},
|
||||
onItemLongClick = { _, _ -> },
|
||||
),
|
||||
tokenList = persistentListOf(),
|
||||
isExpanded = false,
|
||||
isExpandable = false,
|
||||
),
|
||||
)
|
||||
}
|
||||
}.toPersistentList(),
|
||||
onAddFundsClick = { },
|
||||
)
|
||||
}
|
||||
|
|
@ -108,7 +108,7 @@ internal class ForYouModelTest {
|
|||
val loading = model.uiState.value.portfolioReviewUM as PortfolioReviewUM.Loading
|
||||
assertThat(loading.tokenList).hasSize(4)
|
||||
assertThat(loading.tokenList.all { it.tokenRowUM is TangemTokenRowUM.Loading }).isTrue()
|
||||
assertThat(loading.marketChartUM).isEqualTo(MarketChartUM.NoData)
|
||||
assertThat(loading.marketChartUM).isInstanceOf(MarketChartUM.NoData::class.java)
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
|||
|
|
@ -341,7 +341,7 @@ internal class ForYouPortfolioReviewConverterTest {
|
|||
val result = createConverter().convert(statusList) as PortfolioReviewUM.Content
|
||||
|
||||
// Assert
|
||||
assertThat(result.marketChartUM).isEqualTo(MarketChartUM.NoData)
|
||||
assertThat(result.marketChartUM).isInstanceOf(MarketChartUM.NoData::class.java)
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -350,7 +350,155 @@ internal class ForYouPortfolioReviewConverterTest {
|
|||
val result = createConverter().convert(null) as PortfolioReviewUM.Content
|
||||
|
||||
// Assert
|
||||
assertThat(result.marketChartUM).isEqualTo(MarketChartUM.NoData)
|
||||
assertThat(result.marketChartUM).isInstanceOf(MarketChartUM.NoData::class.java)
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
inner class ZeroBalancePortfolio {
|
||||
|
||||
@Test
|
||||
fun `GIVEN all currencies have zero fiat WHEN convert THEN market chart is the no-amount NoData`() {
|
||||
// Arrange
|
||||
val statuses = listOf(
|
||||
createStatus(
|
||||
createCoin(rawCurrencyId = "btc", symbol = "BTC", networkId = "bitcoin"),
|
||||
loadedValue(BigDecimal.ZERO, BigDecimal.ZERO),
|
||||
),
|
||||
createStatus(
|
||||
createCoin(rawCurrencyId = "eth", symbol = "ETH", networkId = "ethereum"),
|
||||
loadedValue(BigDecimal.ZERO, BigDecimal.ZERO),
|
||||
),
|
||||
)
|
||||
|
||||
// Act
|
||||
val result = convert(statuses, totalFiatBalance = BigDecimal.ZERO)
|
||||
|
||||
// Assert — the zero-balance treatment, not the generic can-not-load-data chart
|
||||
assertThat(result.marketChartUM).isEqualTo(
|
||||
MarketChartUM.NoData(
|
||||
title = resourceReference(R.string.market_chart_no_amount),
|
||||
donutText = resourceReference(R.string.market_chart_bubble_no_amount),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN all currencies have zero fiat WHEN add funds clicked THEN callback receives the wallet id`() {
|
||||
// Arrange
|
||||
val statuses = listOf(
|
||||
createStatus(
|
||||
createCoin(rawCurrencyId = "btc", symbol = "BTC", networkId = "bitcoin"),
|
||||
loadedValue(BigDecimal.ZERO, BigDecimal.ZERO),
|
||||
),
|
||||
)
|
||||
var addFundsWalletId: UserWalletId? = null
|
||||
val converter = createConverter(onAddFundsClick = { addFundsWalletId = it })
|
||||
|
||||
// Act
|
||||
val result = converter.convert(
|
||||
accountStatusList(statuses, BigDecimal.ZERO),
|
||||
) as PortfolioReviewUM.Content
|
||||
result.onAddFundsClick?.invoke()
|
||||
|
||||
// Assert
|
||||
assertThat(result.onAddFundsClick).isNotNull()
|
||||
assertThat(addFundsWalletId).isEqualTo(UserWalletId("01"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN a non-zero balance WHEN convert THEN add funds action is absent`() {
|
||||
// Arrange
|
||||
val statuses = listOf(
|
||||
createStatus(
|
||||
createCoin(rawCurrencyId = "btc", symbol = "BTC", networkId = "bitcoin"),
|
||||
loadedValue(BigDecimal.ONE, BigDecimal("100")),
|
||||
),
|
||||
)
|
||||
|
||||
// Act
|
||||
val result = convert(statuses, totalFiatBalance = BigDecimal("100"))
|
||||
|
||||
// Assert
|
||||
assertThat(result.onAddFundsClick).isNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN null account status list WHEN add funds clicked THEN callback is not invoked`() {
|
||||
// Arrange — without a wallet there is nowhere to add funds, so the click must be a no-op
|
||||
var clicked = false
|
||||
val converter = createConverter(onAddFundsClick = { clicked = true })
|
||||
|
||||
// Act
|
||||
val result = converter.convert(null) as PortfolioReviewUM.Content
|
||||
result.onAddFundsClick?.invoke()
|
||||
|
||||
// Assert
|
||||
assertThat(clicked).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN more than five zero-fiat currencies WHEN convert THEN list is capped with no Other row`() {
|
||||
// Arrange — 7 distinct zero-balance assets; the zero-balance branch shows the first 5
|
||||
// as-is instead of ranking and collapsing the excess into an "Other" row
|
||||
val statuses = (1..7).map { index ->
|
||||
createStatus(
|
||||
createCoin(rawCurrencyId = "asset-$index", symbol = "A$index", networkId = "net-$index"),
|
||||
loadedValue(BigDecimal.ZERO, BigDecimal.ZERO),
|
||||
)
|
||||
}
|
||||
|
||||
// Act
|
||||
val result = convert(statuses, totalFiatBalance = BigDecimal.ZERO)
|
||||
|
||||
// Assert
|
||||
assertThat(result.tokenList.map { it.tokenRowUM.id })
|
||||
.containsExactly("asset-1", "asset-2", "asset-3", "asset-4", "asset-5")
|
||||
.inOrder()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN zero-fiat asset on several networks WHEN convert THEN grouped into one expandable item`() {
|
||||
// Arrange — the same asset (shared rawCurrencyId) with zero balances on two networks
|
||||
val onEth = createToken(rawCurrencyId = "usdc", symbol = "USDC", networkId = "ethereum")
|
||||
val onSol = createToken(rawCurrencyId = "usdc", symbol = "USDC", networkId = "solana")
|
||||
val statuses = listOf(
|
||||
createStatus(onEth, loadedValue(BigDecimal.ZERO, BigDecimal.ZERO)),
|
||||
createStatus(onSol, loadedValue(BigDecimal.ZERO, BigDecimal.ZERO)),
|
||||
)
|
||||
|
||||
// Act
|
||||
val result = convert(statuses, totalFiatBalance = BigDecimal.ZERO)
|
||||
|
||||
// Assert
|
||||
val item = result.tokenList.single()
|
||||
assertThat(item.tokenRowUM.id).isEqualTo("usdc")
|
||||
assertThat(item.tokenList).hasSize(2)
|
||||
assertThat(item.isExpandable).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN zero and null fiat currencies mixed WHEN convert THEN zero-balance treatment is not applied`() {
|
||||
// Arrange — an unreachable holding has an *unknown* balance, not a resolved zero, so the
|
||||
// portfolio must not collapse into the add-funds empty state
|
||||
val statuses = listOf(
|
||||
createStatus(
|
||||
createCoin(rawCurrencyId = "eth", symbol = "ETH", networkId = "ethereum"),
|
||||
loadedValue(BigDecimal.ZERO, BigDecimal.ZERO),
|
||||
),
|
||||
createStatus(
|
||||
createCoin(rawCurrencyId = "btc", symbol = "BTC", networkId = "bitcoin"),
|
||||
unreachableValue(),
|
||||
),
|
||||
)
|
||||
|
||||
// Act
|
||||
val result = convert(statuses, totalFiatBalance = BigDecimal.ZERO)
|
||||
|
||||
// Assert — falls through to the ranked branch: no add-funds action, the resolved zero is
|
||||
// dropped, the unknown-balance holding stays visible
|
||||
assertThat(result.onAddFundsClick).isNull()
|
||||
assertThat(result.tokenList.map { it.tokenRowUM.id }).containsExactly("btc")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -364,11 +512,13 @@ internal class ForYouPortfolioReviewConverterTest {
|
|||
expandedAssetIds: Set<String> = emptySet(),
|
||||
expandClick: (String) -> Unit = {},
|
||||
onTokenClick: (UserWalletId, CryptoCurrency) -> Unit = { _, _ -> },
|
||||
onAddFundsClick: (UserWalletId) -> Unit = {},
|
||||
): ForYouPortfolioReviewConverter = ForYouPortfolioReviewConverter(
|
||||
appCurrency = appCurrency,
|
||||
expandedAssetIds = expandedAssetIds,
|
||||
expandClick = expandClick,
|
||||
onTokenClick = onTokenClick,
|
||||
onAddFundsClick = onAddFundsClick,
|
||||
)
|
||||
|
||||
private fun accountStatusList(
|
||||
|
|
|
|||
|
|
@ -25,24 +25,24 @@ internal class ForYouPortfolioReviewTokenRowConverterTest {
|
|||
private val appCurrency: AppCurrency = AppCurrency.Default
|
||||
|
||||
@Nested
|
||||
inner class ConvertNetworkGroup {
|
||||
inner class Convert {
|
||||
|
||||
@Test
|
||||
fun `GIVEN all statuses Loading WHEN convertNetworkGroup THEN row is Loading with representative id`() {
|
||||
fun `GIVEN all statuses Loading WHEN convert THEN row is Loading with representative id`() {
|
||||
// Arrange
|
||||
val currency = createCurrency(id = "coin-eth", symbol = "ETH")
|
||||
val statuses = listOf(createStatus(currency, CryptoCurrencyStatus.Loading))
|
||||
val converter = createConverter(totalFiatBalance = BigDecimal("1000"))
|
||||
|
||||
// Act
|
||||
val result = converter.convertNetworkGroup(statuses)
|
||||
val result = converter.convert(statuses)
|
||||
|
||||
// Assert
|
||||
assertThat(result).isEqualTo(TangemTokenRowUM.Loading(id = "coin-eth"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN single loaded status WHEN convertNetworkGroup THEN row is Content with its amounts`() {
|
||||
fun `GIVEN single loaded status WHEN convert THEN row is Content with its amounts`() {
|
||||
// Arrange
|
||||
val currency = createCurrency(id = "coin-eth", symbol = "ETH", networkName = "Ethereum")
|
||||
val statuses = listOf(
|
||||
|
|
@ -51,7 +51,7 @@ internal class ForYouPortfolioReviewTokenRowConverterTest {
|
|||
val converter = createConverter(totalFiatBalance = BigDecimal("1000"))
|
||||
|
||||
// Act
|
||||
val result = converter.convertNetworkGroup(statuses) as TangemTokenRowUM.Content
|
||||
val result = converter.convert(statuses) as TangemTokenRowUM.Content
|
||||
|
||||
// Assert
|
||||
assertThat(result.id).isEqualTo("coin-eth")
|
||||
|
|
@ -62,7 +62,7 @@ internal class ForYouPortfolioReviewTokenRowConverterTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN several statuses of the same asset on one network WHEN convertNetworkGroup THEN amounts are summed`() {
|
||||
fun `GIVEN several statuses of the same asset on one network WHEN convert THEN amounts are summed`() {
|
||||
// Arrange — same asset held in two accounts on the same network aggregates into one row
|
||||
val currency = createCurrency(id = "coin-eth", symbol = "ETH", networkName = "Ethereum")
|
||||
val statuses = listOf(
|
||||
|
|
@ -72,7 +72,7 @@ internal class ForYouPortfolioReviewTokenRowConverterTest {
|
|||
val converter = createConverter(totalFiatBalance = BigDecimal("1000"))
|
||||
|
||||
// Act
|
||||
val result = converter.convertNetworkGroup(statuses) as TangemTokenRowUM.Content
|
||||
val result = converter.convert(statuses) as TangemTokenRowUM.Content
|
||||
|
||||
// Assert
|
||||
val topEnd = result.topEndContentUM as TangemTokenRowUM.EndContentUM.Content
|
||||
|
|
@ -80,7 +80,7 @@ internal class ForYouPortfolioReviewTokenRowConverterTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN mixed Loading and Loaded statuses WHEN convertNetworkGroup THEN row is Content`() {
|
||||
fun `GIVEN mixed Loading and Loaded statuses WHEN convert THEN row is Content`() {
|
||||
// Arrange — not *all* statuses are Loading, so it should not collapse to a Loading row
|
||||
val currency = createCurrency(id = "coin-eth", symbol = "ETH")
|
||||
val statuses = listOf(
|
||||
|
|
@ -90,14 +90,14 @@ internal class ForYouPortfolioReviewTokenRowConverterTest {
|
|||
val converter = createConverter(totalFiatBalance = BigDecimal("1000"))
|
||||
|
||||
// Act
|
||||
val result = converter.convertNetworkGroup(statuses)
|
||||
val result = converter.convert(statuses)
|
||||
|
||||
// Assert
|
||||
assertThat(result).isInstanceOf(TangemTokenRowUM.Content::class.java)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN loaded status from cache WHEN convertNetworkGroup THEN content flickers`() {
|
||||
fun `GIVEN loaded status from cache WHEN convert THEN content flickers`() {
|
||||
// Arrange
|
||||
val currency = createCurrency(id = "coin-eth", symbol = "ETH", networkName = "Ethereum")
|
||||
val statuses = listOf(
|
||||
|
|
@ -109,7 +109,7 @@ internal class ForYouPortfolioReviewTokenRowConverterTest {
|
|||
val converter = createConverter(totalFiatBalance = BigDecimal("1000"))
|
||||
|
||||
// Act
|
||||
val result = converter.convertNetworkGroup(statuses) as TangemTokenRowUM.Content
|
||||
val result = converter.convert(statuses) as TangemTokenRowUM.Content
|
||||
|
||||
// Assert
|
||||
val topEnd = result.topEndContentUM as TangemTokenRowUM.EndContentUM.Content
|
||||
|
|
@ -120,7 +120,7 @@ internal class ForYouPortfolioReviewTokenRowConverterTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN loaded status only-cache WHEN convertNetworkGroup THEN error-sync start icon shown`() {
|
||||
fun `GIVEN loaded status only-cache WHEN convert THEN error-sync start icon shown`() {
|
||||
// Arrange
|
||||
val currency = createCurrency(id = "coin-eth", symbol = "ETH", networkName = "Ethereum")
|
||||
val statuses = listOf(
|
||||
|
|
@ -136,7 +136,7 @@ internal class ForYouPortfolioReviewTokenRowConverterTest {
|
|||
val converter = createConverter(totalFiatBalance = BigDecimal("1000"))
|
||||
|
||||
// Act
|
||||
val result = converter.convertNetworkGroup(statuses) as TangemTokenRowUM.Content
|
||||
val result = converter.convert(statuses) as TangemTokenRowUM.Content
|
||||
|
||||
// Assert
|
||||
val topEnd = result.topEndContentUM as TangemTokenRowUM.EndContentUM.Content
|
||||
|
|
@ -145,14 +145,14 @@ internal class ForYouPortfolioReviewTokenRowConverterTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN missed derivation status WHEN convertNetworkGroup THEN no-address treatment`() {
|
||||
fun `GIVEN missed derivation status WHEN convert THEN no-address treatment`() {
|
||||
// Arrange
|
||||
val currency = createCurrency(id = "coin-eth", symbol = "ETH", networkName = "Ethereum")
|
||||
val statuses = listOf(createStatus(currency, missedDerivationValue()))
|
||||
val converter = createConverter(totalFiatBalance = BigDecimal("1000"))
|
||||
|
||||
// Act
|
||||
val result = converter.convertNetworkGroup(statuses) as TangemTokenRowUM.Content
|
||||
val result = converter.convert(statuses) as TangemTokenRowUM.Content
|
||||
|
||||
// Assert — top-end is a dash, bottom-end carries the attention "no address" icon
|
||||
val topEnd = result.topEndContentUM as TangemTokenRowUM.EndContentUM.Content
|
||||
|
|
@ -162,14 +162,14 @@ internal class ForYouPortfolioReviewTokenRowConverterTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN unreachable status WHEN convertNetworkGroup THEN dash on top and attention icon on bottom`() {
|
||||
fun `GIVEN unreachable status WHEN convert THEN dash on top and attention icon on bottom`() {
|
||||
// Arrange
|
||||
val currency = createCurrency(id = "coin-eth", symbol = "ETH", networkName = "Ethereum")
|
||||
val statuses = listOf(createStatus(currency, unreachableValue()))
|
||||
val converter = createConverter(totalFiatBalance = BigDecimal("1000"))
|
||||
|
||||
// Act
|
||||
val result = converter.convertNetworkGroup(statuses) as TangemTokenRowUM.Content
|
||||
val result = converter.convert(statuses) as TangemTokenRowUM.Content
|
||||
|
||||
// Assert — top-end is a bare dash, the attention "unreachable" icon lives on the bottom end
|
||||
val topEnd = result.topEndContentUM as TangemTokenRowUM.EndContentUM.Content
|
||||
|
|
@ -179,7 +179,7 @@ internal class ForYouPortfolioReviewTokenRowConverterTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN mixed Loaded and Unreachable WHEN convertNetworkGroup THEN collapses to unreachable`() {
|
||||
fun `GIVEN mixed Loaded and Unreachable WHEN convert THEN collapses to unreachable`() {
|
||||
// Arrange — one account resolved, another unreachable: the row must surface the error state
|
||||
val currency = createCurrency(id = "coin-eth", symbol = "ETH", networkName = "Ethereum")
|
||||
val statuses = listOf(
|
||||
|
|
@ -189,7 +189,7 @@ internal class ForYouPortfolioReviewTokenRowConverterTest {
|
|||
val converter = createConverter(totalFiatBalance = BigDecimal("1000"))
|
||||
|
||||
// Act
|
||||
val result = converter.convertNetworkGroup(statuses) as TangemTokenRowUM.Content
|
||||
val result = converter.convert(statuses) as TangemTokenRowUM.Content
|
||||
|
||||
// Assert — the unreachable treatment (attention icon on the bottom end) wins over the loaded amount
|
||||
val bottomEnd = result.bottomEndContentUM as TangemTokenRowUM.EndContentUM.Content
|
||||
|
|
@ -212,7 +212,7 @@ internal class ForYouPortfolioReviewTokenRowConverterTest {
|
|||
)
|
||||
|
||||
// Act
|
||||
val result = converter.convertNetworkGroup(statuses) as TangemTokenRowUM.Content
|
||||
val result = converter.convert(statuses) as TangemTokenRowUM.Content
|
||||
result.onItemClick?.invoke()
|
||||
|
||||
// Assert
|
||||
|
|
@ -234,7 +234,7 @@ internal class ForYouPortfolioReviewTokenRowConverterTest {
|
|||
)
|
||||
|
||||
// Act
|
||||
val result = converter.convertNetworkGroup(statuses) as TangemTokenRowUM.Content
|
||||
val result = converter.convert(statuses) as TangemTokenRowUM.Content
|
||||
result.onItemClick?.invoke()
|
||||
|
||||
// Assert
|
||||
|
|
@ -242,7 +242,7 @@ internal class ForYouPortfolioReviewTokenRowConverterTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN mixed MissedDerivation and Unreachable WHEN convertNetworkGroup THEN missed-derivation wins`() {
|
||||
fun `GIVEN mixed MissedDerivation and Unreachable WHEN convert THEN missed-derivation wins`() {
|
||||
// Arrange — missed derivation is the most severe terminal state and dominates
|
||||
val currency = createCurrency(id = "coin-eth", symbol = "ETH", networkName = "Ethereum")
|
||||
val statuses = listOf(
|
||||
|
|
@ -252,7 +252,7 @@ internal class ForYouPortfolioReviewTokenRowConverterTest {
|
|||
val converter = createConverter(totalFiatBalance = BigDecimal("1000"))
|
||||
|
||||
// Act
|
||||
val result = converter.convertNetworkGroup(statuses) as TangemTokenRowUM.Content
|
||||
val result = converter.convert(statuses) as TangemTokenRowUM.Content
|
||||
|
||||
// Assert — top-end is a dash (no-address treatment), not an unreachable label
|
||||
val topEnd = result.topEndContentUM as TangemTokenRowUM.EndContentUM.Content
|
||||
|
|
|
|||
|
|
@ -137,7 +137,8 @@ internal class SetPortfolioReviewTransformerTest {
|
|||
|
||||
private fun contentPortfolioReview(): PortfolioReviewUM.Content = PortfolioReviewUM.Content(
|
||||
tokenList = persistentListOf(),
|
||||
marketChartUM = MarketChartUM.NoData,
|
||||
marketChartUM = noDataChart(),
|
||||
onAddFundsClick = null,
|
||||
)
|
||||
|
||||
private fun contentEarnOpportunities(): EarnOpportunitiesUM.Content = EarnOpportunitiesUM.Content(
|
||||
|
|
@ -148,6 +149,11 @@ internal class SetPortfolioReviewTransformerTest {
|
|||
onAllEarnTokensClick = {},
|
||||
)
|
||||
|
||||
private fun noDataChart(): MarketChartUM.NoData = MarketChartUM.NoData(
|
||||
title = stringReference("No data"),
|
||||
donutText = stringReference("No data"),
|
||||
)
|
||||
|
||||
private fun accountStatusList(totalFiatBalance: TotalFiatBalance): AccountStatusList = mockk {
|
||||
every { this@mockk.totalFiatBalance } returns totalFiatBalance
|
||||
}
|
||||
|
|
@ -158,7 +164,7 @@ internal class SetPortfolioReviewTransformerTest {
|
|||
private fun loadingState(): ForYouUM = ForYouUM(
|
||||
portfolioReviewUM = PortfolioReviewUM.Loading(
|
||||
tokenList = persistentListOf(),
|
||||
marketChartUM = MarketChartUM.NoData,
|
||||
marketChartUM = noDataChart(),
|
||||
),
|
||||
earnOpportunities = EarnOpportunitiesUM.Loading(tokenList = persistentListOf()),
|
||||
notifications = persistentListOf(),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue