Updated on 2026-08-14
This commit is contained in:
commit
5ca489c08b
6 changed files with 105 additions and 78 deletions
|
|
@ -146,38 +146,14 @@ internal class DefaultSingleAccountStatusListProducer @AssistedInject constructo
|
||||||
flattenCurrency = flattenCurrency,
|
flattenCurrency = flattenCurrency,
|
||||||
)
|
)
|
||||||
|
|
||||||
val accounts = accountListFlow.value.accounts
|
val specialStatusesFlow: Flow<Map<AccountId, AccountStatus>> = combine(
|
||||||
val hasPaymentAccount = accounts.any { it is Account.Payment }
|
paymentAccountStatusSupplier.invoke(userWalletId = walletId),
|
||||||
val hasVirtualAccount = accounts.any { it is Account.Virtual }
|
virtualAccountStatusSupplier.invoke(userWalletId = walletId),
|
||||||
logger.i("flattenFlow[$walletId]: payment=$hasPaymentAccount, virtual=$hasVirtualAccount")
|
) { paymentStatus, virtualStatus ->
|
||||||
val specialStatusFlows = buildList<Flow<AccountStatus>> {
|
mapOf(
|
||||||
if (hasPaymentAccount) {
|
paymentStatus.accountId to paymentStatus,
|
||||||
add(
|
virtualStatus.accountId to virtualStatus,
|
||||||
paymentAccountStatusSupplier.invoke(userWalletId = walletId)
|
)
|
||||||
.onEach { status ->
|
|
||||||
logger.i(
|
|
||||||
"flattenFlow[$walletId]: paymentAccountStatus emitted " +
|
|
||||||
"valueType=${status.value::class.simpleName}",
|
|
||||||
)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (hasVirtualAccount) {
|
|
||||||
add(
|
|
||||||
virtualAccountStatusSupplier.invoke(userWalletId = walletId)
|
|
||||||
.onEach { status ->
|
|
||||||
logger.i(
|
|
||||||
"flattenFlow[$walletId]: virtualAccountStatus emitted " +
|
|
||||||
"valueType=${status.value::class.simpleName}",
|
|
||||||
)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
val specialStatusesFlow: Flow<Map<AccountId, AccountStatus>> = if (specialStatusFlows.isEmpty()) {
|
|
||||||
flowOf(emptyMap())
|
|
||||||
} else {
|
|
||||||
combine(specialStatusFlows) { statuses -> statuses.associateBy(AccountStatus::accountId) }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
combine(
|
combine(
|
||||||
|
|
|
||||||
|
|
@ -4,11 +4,7 @@ import androidx.compose.ui.text.SpanStyle
|
||||||
import com.tangem.common.getRewardStakingBalance
|
import com.tangem.common.getRewardStakingBalance
|
||||||
import com.tangem.common.getTotalStakingBalance
|
import com.tangem.common.getTotalStakingBalance
|
||||||
import com.tangem.common.ui.earn.EarnBlockUM
|
import com.tangem.common.ui.earn.EarnBlockUM
|
||||||
import com.tangem.core.ui.extensions.TextReference
|
import com.tangem.core.ui.extensions.*
|
||||||
import com.tangem.core.ui.extensions.orMaskWithStars
|
|
||||||
import com.tangem.core.ui.extensions.resourceReference
|
|
||||||
import com.tangem.core.ui.extensions.stringReference
|
|
||||||
import com.tangem.core.ui.extensions.wrappedList
|
|
||||||
import com.tangem.core.ui.format.bigdecimal.*
|
import com.tangem.core.ui.format.bigdecimal.*
|
||||||
import com.tangem.core.ui.res.TangemTheme
|
import com.tangem.core.ui.res.TangemTheme
|
||||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||||
|
|
@ -20,10 +16,12 @@ import com.tangem.domain.staking.model.StakingEntryInfo
|
||||||
import com.tangem.domain.staking.model.StakingOption
|
import com.tangem.domain.staking.model.StakingOption
|
||||||
import com.tangem.domain.staking.model.common.RewardInfo
|
import com.tangem.domain.staking.model.common.RewardInfo
|
||||||
import com.tangem.domain.staking.model.common.RewardType
|
import com.tangem.domain.staking.model.common.RewardType
|
||||||
|
import com.tangem.domain.staking.model.optionOrNull
|
||||||
import com.tangem.feature.tokendetails.presentation.tokendetails.model.TokenDetailsClickIntents
|
import com.tangem.feature.tokendetails.presentation.tokendetails.model.TokenDetailsClickIntents
|
||||||
import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsUM
|
import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsUM
|
||||||
import com.tangem.features.tokendetails.impl.R
|
import com.tangem.features.tokendetails.impl.R
|
||||||
import com.tangem.lib.crypto.BlockchainUtils.isStakingRewardUnavailable
|
import com.tangem.lib.crypto.BlockchainUtils.isStakingRewardUnavailable
|
||||||
|
import com.tangem.utils.StringsSigns
|
||||||
import com.tangem.utils.isNullOrZero
|
import com.tangem.utils.isNullOrZero
|
||||||
import com.tangem.utils.transformer.Transformer
|
import com.tangem.utils.transformer.Transformer
|
||||||
import java.math.BigDecimal
|
import java.math.BigDecimal
|
||||||
|
|
@ -217,6 +215,7 @@ internal class UpdateStakingNotificationTransformer(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suppress("LongMethod")
|
||||||
private fun getRewardSubtitle(
|
private fun getRewardSubtitle(
|
||||||
status: CryptoCurrencyStatus,
|
status: CryptoCurrencyStatus,
|
||||||
stakingRewardAmount: BigDecimal?,
|
stakingRewardAmount: BigDecimal?,
|
||||||
|
|
@ -267,13 +266,23 @@ internal class UpdateStakingNotificationTransformer(
|
||||||
}
|
}
|
||||||
RewardBlockType.RewardsRequirementsError,
|
RewardBlockType.RewardsRequirementsError,
|
||||||
RewardBlockType.Rewards,
|
RewardBlockType.Rewards,
|
||||||
-> resourceReference(
|
-> {
|
||||||
R.string.staking_details_rewards_to_claim,
|
val rewardAmount = stakingRewardAmount.format { fiat(appCurrency.code, appCurrency.symbol) }
|
||||||
wrappedList(
|
.orMaskWithStars(isBalanceHidden)
|
||||||
stakingRewardAmount.format { fiat(appCurrency.code, appCurrency.symbol) }
|
val rateReference = rewardRateReference()
|
||||||
.orMaskWithStars(isBalanceHidden),
|
if (rateReference != null) {
|
||||||
),
|
combinedReference(
|
||||||
)
|
rateReference,
|
||||||
|
stringReference(" ${StringsSigns.DOT} "),
|
||||||
|
stringReference(rewardAmount),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
resourceReference(
|
||||||
|
R.string.staking_details_rewards_to_claim,
|
||||||
|
wrappedList(rewardAmount),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val isAccent = rewardBlockType == RewardBlockType.Rewards ||
|
val isAccent = rewardBlockType == RewardBlockType.Rewards ||
|
||||||
|
|
@ -285,6 +294,21 @@ internal class UpdateStakingNotificationTransformer(
|
||||||
tone = if (isAccent) EarnBlockUM.SubtitleUM.Tone.Accent else EarnBlockUM.SubtitleUM.Tone.Disabled,
|
tone = if (isAccent) EarnBlockUM.SubtitleUM.Tone.Accent else EarnBlockUM.SubtitleUM.Tone.Disabled,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun rewardRateReference(): TextReference? {
|
||||||
|
val rewardInfo = stakingAvailability.optionOrNull?.displayRewardInfo ?: return null
|
||||||
|
val rateTypeResId = when (rewardInfo.type) {
|
||||||
|
RewardType.APR -> CoreResR.string.staking_details_apr
|
||||||
|
RewardType.APY,
|
||||||
|
RewardType.UNKNOWN,
|
||||||
|
-> CoreResR.string.staking_details_apy
|
||||||
|
}
|
||||||
|
return combinedReference(
|
||||||
|
stringReference(rewardInfo.rate.format { percent() }),
|
||||||
|
stringReference(" "),
|
||||||
|
resourceReference(rateTypeResId),
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun StakingBalance.Data?.hasPendingBalances(): Boolean = when (this) {
|
private fun StakingBalance.Data?.hasPendingBalances(): Boolean = when (this) {
|
||||||
|
|
|
||||||
|
|
@ -11,29 +11,21 @@ import com.tangem.domain.models.StatusSource
|
||||||
import com.tangem.domain.models.currency.CryptoCurrency
|
import com.tangem.domain.models.currency.CryptoCurrency
|
||||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||||
import com.tangem.domain.models.network.Network
|
import com.tangem.domain.models.network.Network
|
||||||
import com.tangem.domain.models.staking.BalanceItem
|
import com.tangem.domain.models.staking.*
|
||||||
import com.tangem.domain.models.staking.BalanceType
|
import com.tangem.domain.models.staking.BalanceType
|
||||||
import com.tangem.domain.models.staking.StakingBalance
|
|
||||||
import com.tangem.domain.models.staking.StakingID
|
|
||||||
import com.tangem.domain.models.staking.YieldBalanceItem
|
|
||||||
import com.tangem.domain.models.staking.YieldToken
|
|
||||||
import com.tangem.domain.staking.model.StakingAvailability
|
import com.tangem.domain.staking.model.StakingAvailability
|
||||||
import com.tangem.domain.staking.model.StakingEntryInfo
|
import com.tangem.domain.staking.model.StakingEntryInfo
|
||||||
import com.tangem.domain.staking.model.StakingOption
|
import com.tangem.domain.staking.model.StakingOption
|
||||||
import com.tangem.utils.StringsSigns.THREE_STARS
|
|
||||||
import com.tangem.feature.tokendetails.presentation.tokendetails.model.TokenDetailsClickIntents
|
import com.tangem.feature.tokendetails.presentation.tokendetails.model.TokenDetailsClickIntents
|
||||||
import com.tangem.feature.tokendetails.presentation.tokendetails.state.AddFundsUM
|
import com.tangem.feature.tokendetails.presentation.tokendetails.state.*
|
||||||
import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsBalanceBlockUM
|
|
||||||
import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsTopAppBarUM
|
|
||||||
import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsTopAppBarUM.TitleState
|
import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsTopAppBarUM.TitleState
|
||||||
import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsUM
|
import com.tangem.utils.StringsSigns.THREE_STARS
|
||||||
import com.tangem.feature.tokendetails.presentation.tokendetails.state.TransferUM
|
|
||||||
import com.tangem.feature.tokendetails.presentation.tokendetails.state.ZeroBalanceActionsUM
|
|
||||||
import io.mockk.every
|
import io.mockk.every
|
||||||
import io.mockk.mockk
|
import io.mockk.mockk
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
import java.math.BigDecimal
|
import java.math.BigDecimal
|
||||||
|
import com.tangem.core.res.R as CoreResR
|
||||||
|
|
||||||
class UpdateStakingNotificationTransformerTest {
|
class UpdateStakingNotificationTransformerTest {
|
||||||
|
|
||||||
|
|
@ -194,8 +186,40 @@ class UpdateStakingNotificationTransformerTest {
|
||||||
assertThat(rewardFormatArg(subtitle.text)).isNotEqualTo(THREE_STARS)
|
assertThat(rewardFormatArg(subtitle.text)).isNotEqualTo(THREE_STARS)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun rewardFormatArg(text: TextReference): Any? =
|
@Test
|
||||||
(text as TextReference.Res).formatArgs.data.firstOrNull()
|
fun `GIVEN rewards to claim AND rate known WHEN transform THEN subtitle shows rate and reward amount`() {
|
||||||
|
// Arrange
|
||||||
|
val status = buildStatus(
|
||||||
|
networkRawId = "ethereum",
|
||||||
|
symbol = "ETH",
|
||||||
|
isCoin = false,
|
||||||
|
stakingBalance = stakeKitBalance(staked = BigDecimal("100"), rewards = BigDecimal("5")),
|
||||||
|
)
|
||||||
|
val transformer = createTransformer(
|
||||||
|
availability = availableOption(BigDecimal("4.2")),
|
||||||
|
entryInfo = StakingEntryInfo(tokenSymbol = "ETH"),
|
||||||
|
status = status,
|
||||||
|
isBalanceHidden = false,
|
||||||
|
)
|
||||||
|
|
||||||
|
// Act
|
||||||
|
val result = transformer.transform(initialState())
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
val content = result.earnBlockState as EarnBlockUM.Content
|
||||||
|
val subtitle = content.subtitleUM as EarnBlockUM.SubtitleUM.Text
|
||||||
|
val combined = subtitle.text as TextReference.Combined
|
||||||
|
val rateLabel = combined.refs.data.first() as TextReference.Combined
|
||||||
|
val apyLabel = rateLabel.refs.data.filterIsInstance<TextReference.Res>().first()
|
||||||
|
assertThat(apyLabel.id).isEqualTo(CoreResR.string.staking_details_apy)
|
||||||
|
assertThat((combined.refs.data.last() as TextReference.Str).value).isNotEqualTo(THREE_STARS)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun rewardFormatArg(text: TextReference): Any? = when (text) {
|
||||||
|
is TextReference.Res -> text.formatArgs.data.firstOrNull()
|
||||||
|
is TextReference.Combined -> (text.refs.data.last() as? TextReference.Str)?.value
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
|
||||||
private fun createTransformer(
|
private fun createTransformer(
|
||||||
availability: StakingAvailability,
|
availability: StakingAvailability,
|
||||||
|
|
@ -265,17 +289,16 @@ class UpdateStakingNotificationTransformerTest {
|
||||||
)
|
)
|
||||||
|
|
||||||
private fun availableOption(apy: BigDecimal): StakingAvailability.Available {
|
private fun availableOption(apy: BigDecimal): StakingAvailability.Available {
|
||||||
val option = mockk<StakingOption>(relaxed = true) {
|
return StakingAvailability.Available(option = stakingOption(apy))
|
||||||
every { this@mockk.apy } returns apy
|
|
||||||
}
|
|
||||||
return StakingAvailability.Available(option = option)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun fullOption(apy: BigDecimal): StakingAvailability.Full {
|
private fun fullOption(apy: BigDecimal): StakingAvailability.Full {
|
||||||
val option = mockk<StakingOption>(relaxed = true) {
|
return StakingAvailability.Full(option = stakingOption(apy))
|
||||||
every { this@mockk.apy } returns apy
|
}
|
||||||
}
|
|
||||||
return StakingAvailability.Full(option = option)
|
/** Deterministic concrete [StakingOption] whose [displayRewardInfo] resolves to [apy] with an APY rate. */
|
||||||
|
private fun stakingOption(apy: BigDecimal): StakingOption.P2PEthPool = mockk(relaxed = true) {
|
||||||
|
every { this@mockk.apy } returns apy
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun buildStatusWithStake(stakedAmount: BigDecimal): CryptoCurrencyStatus {
|
private fun buildStatusWithStake(stakedAmount: BigDecimal): CryptoCurrencyStatus {
|
||||||
|
|
|
||||||
|
|
@ -7,12 +7,7 @@ import com.tangem.domain.models.wallet.UserWallet
|
||||||
import com.tangem.domain.models.wallet.UserWalletId
|
import com.tangem.domain.models.wallet.UserWalletId
|
||||||
import com.tangem.domain.models.wallet.isLocked
|
import com.tangem.domain.models.wallet.isLocked
|
||||||
import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase
|
import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase
|
||||||
import com.tangem.feature.wallet.presentation.wallet.state.model.NOT_INITIALIZED_WALLET_INDEX
|
import com.tangem.feature.wallet.presentation.wallet.state.model.*
|
||||||
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletCardState
|
|
||||||
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletNotification
|
|
||||||
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletScreenState
|
|
||||||
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState
|
|
||||||
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletType
|
|
||||||
import com.tangem.utils.logging.TangemLogger
|
import com.tangem.utils.logging.TangemLogger
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
|
@ -271,7 +266,7 @@ internal class WalletsUpdateActionResolver @Inject constructor(
|
||||||
private fun WalletScreenState.incompleteActivationWalletIds(): List<UserWalletId> {
|
private fun WalletScreenState.incompleteActivationWalletIds(): List<UserWalletId> {
|
||||||
return wallets.mapNotNull { wallet ->
|
return wallets.mapNotNull { wallet ->
|
||||||
if (wallet.warnings.any { it is WalletNotification.FinishWalletActivation } ||
|
if (wallet.warnings.any { it is WalletNotification.FinishWalletActivation } ||
|
||||||
wallet.walletCardState is WalletState.MultiCurrency &&
|
wallet is WalletState.MultiCurrency &&
|
||||||
wallet.walletCardState.additionalInfo?.isHotBackedUp == false
|
wallet.walletCardState.additionalInfo?.isHotBackedUp == false
|
||||||
) {
|
) {
|
||||||
wallet.walletCardState.id
|
wallet.walletCardState.id
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,10 @@ import com.tangem.core.ui.components.marketprice.PriceChangeType
|
||||||
import com.tangem.core.ui.ds.badge.*
|
import com.tangem.core.ui.ds.badge.*
|
||||||
import com.tangem.core.ui.ds.image.TangemIconUM
|
import com.tangem.core.ui.ds.image.TangemIconUM
|
||||||
import com.tangem.core.ui.ds.row.token.TangemTokenRowUM
|
import com.tangem.core.ui.ds.row.token.TangemTokenRowUM
|
||||||
import com.tangem.core.ui.extensions.*
|
import com.tangem.core.ui.extensions.resourceReference
|
||||||
|
import com.tangem.core.ui.extensions.stringReference
|
||||||
|
import com.tangem.core.ui.extensions.styledResourceReference
|
||||||
|
import com.tangem.core.ui.extensions.wrappedList
|
||||||
import com.tangem.core.ui.format.bigdecimal.*
|
import com.tangem.core.ui.format.bigdecimal.*
|
||||||
import com.tangem.core.ui.res.TangemTheme
|
import com.tangem.core.ui.res.TangemTheme
|
||||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||||
|
|
@ -133,7 +136,11 @@ internal class WalletTokenCurrencyItemConverter(
|
||||||
is CryptoCurrencyStatus.NoQuote,
|
is CryptoCurrencyStatus.NoQuote,
|
||||||
is CryptoCurrencyStatus.NoAccount,
|
is CryptoCurrencyStatus.NoAccount,
|
||||||
-> TangemTokenRowUM.SubtitleUM.Content(
|
-> TangemTokenRowUM.SubtitleUM.Content(
|
||||||
text = TextReference.EMPTY,
|
text = stringReference(
|
||||||
|
currencyStatus.value.fiatRate.format {
|
||||||
|
fiat(fiatCurrencyCode = appCurrency.code, fiatCurrencySymbol = appCurrency.symbol)
|
||||||
|
},
|
||||||
|
),
|
||||||
priceChangeUM = PriceChangeState.Content(
|
priceChangeUM = PriceChangeState.Content(
|
||||||
type = PriceChangeType.fromBigDecimal(currencyStatus.value.priceChange.orZero()),
|
type = PriceChangeType.fromBigDecimal(currencyStatus.value.priceChange.orZero()),
|
||||||
valueInPercent = currencyStatus.value.priceChange.format { percent() },
|
valueInPercent = currencyStatus.value.priceChange.format { percent() },
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import com.tangem.core.ui.extensions.resourceReference
|
||||||
import com.tangem.core.ui.extensions.stringReference
|
import com.tangem.core.ui.extensions.stringReference
|
||||||
import com.tangem.core.ui.extensions.wrappedList
|
import com.tangem.core.ui.extensions.wrappedList
|
||||||
import com.tangem.features.yield.supply.impl.main.entity.YieldSupplyUM
|
import com.tangem.features.yield.supply.impl.main.entity.YieldSupplyUM
|
||||||
|
import com.tangem.utils.StringsSigns
|
||||||
import com.tangem.utils.converter.Converter
|
import com.tangem.utils.converter.Converter
|
||||||
import com.tangem.core.res.R as CoreResR
|
import com.tangem.core.res.R as CoreResR
|
||||||
import com.tangem.core.ui.R as CoreUiR
|
import com.tangem.core.ui.R as CoreUiR
|
||||||
|
|
@ -80,9 +81,10 @@ internal class YieldSupplyToEarnBlockConverter : Converter<YieldSupplyUM, EarnBl
|
||||||
iconUM = buildTitleIcon(value),
|
iconUM = buildTitleIcon(value),
|
||||||
),
|
),
|
||||||
subtitleUM = EarnBlockUM.SubtitleUM.Text(
|
subtitleUM = EarnBlockUM.SubtitleUM.Text(
|
||||||
text = resourceReference(
|
text = combinedReference(
|
||||||
id = CoreResR.string.yield_module_average_apy,
|
resourceReference(CoreResR.string.yield_module_earn_sheet_current_apy_title),
|
||||||
formatArgs = wrappedList(value.apy),
|
stringReference(StringsSigns.WHITE_SPACE),
|
||||||
|
stringReference(value.apy + StringsSigns.PERCENT),
|
||||||
),
|
),
|
||||||
style = EarnBlockUM.SubtitleUM.Style.Small,
|
style = EarnBlockUM.SubtitleUM.Style.Small,
|
||||||
tone = EarnBlockUM.SubtitleUM.Tone.Accent,
|
tone = EarnBlockUM.SubtitleUM.Tone.Accent,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue