Updated on 2026-08-14

This commit is contained in:
Tangem 2025-12-10 13:24:16 +05:00
parent 76c129f205
commit a13a54accd
32 changed files with 665 additions and 35 deletions

View file

@ -21,6 +21,7 @@ import com.tangem.domain.tokens.model.details.NavigationAction
import com.tangem.domain.txhistory.usecase.GetExplorerTransactionUrlUseCase
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
import com.tangem.domain.yield.supply.usecase.YieldSupplyEnterStatusUseCase
import com.tangem.domain.yield.supply.usecase.YieldSupplySetShouldShowMainPromoUseCase
import com.tangem.feature.wallet.presentation.account.AccountDependencies
import com.tangem.feature.wallet.presentation.wallet.domain.OnrampStatusFactory
import com.tangem.feature.wallet.presentation.wallet.domain.unwrap
@ -52,6 +53,8 @@ internal interface WalletContentClickIntents {
fun onApyLabelClick(userWalletId: UserWalletId, currencyStatus: CryptoCurrencyStatus, apy: String)
fun onYieldPromoCloseClick()
fun onAccountExpandClick(account: Account)
fun onAccountCollapseClick(account: Account)
@ -89,6 +92,7 @@ internal class WalletContentClickIntentsImplementor @Inject constructor(
private val hotWalletFeatureToggles: HotWalletFeatureToggles,
private val accountDependencies: AccountDependencies,
private val yieldSupplyEnterStatusUseCase: YieldSupplyEnterStatusUseCase,
private val yieldSupplySetShouldShowMainPromoUseCase: YieldSupplySetShouldShowMainPromoUseCase,
) : BaseWalletClickIntents(), WalletContentClickIntents {
override fun onDetailsClick() {
@ -182,6 +186,12 @@ internal class WalletContentClickIntentsImplementor @Inject constructor(
}
}
override fun onYieldPromoCloseClick() {
modelScope.launch {
yieldSupplySetShouldShowMainPromoUseCase(false)
}
}
override fun onAccountExpandClick(account: Account) {
val userWalletId = stateHolder.getSelectedWalletId()
accountDependencies.expandedAccountsHolder.expandAccount(userWalletId, account.accountId)

View file

@ -11,6 +11,7 @@ import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.wallets.repository.WalletsRepository
import com.tangem.domain.wallets.usecase.ShouldSaveUserWalletsUseCase
import com.tangem.domain.yield.supply.usecase.YieldSupplyApyFlowUseCase
import com.tangem.domain.yield.supply.usecase.YieldSupplyGetShouldShowMainPromoUseCase
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
import com.tangem.feature.wallet.presentation.wallet.analytics.utils.TokenListAnalyticsSender
import com.tangem.feature.wallet.presentation.wallet.analytics.utils.WalletWarningsAnalyticsSender
@ -45,6 +46,7 @@ internal class MultiWalletContentLoader(
private val currenciesRepository: CurrenciesRepository,
private val yieldSupplyApyFlowUseCase: YieldSupplyApyFlowUseCase,
private val stakingApyFlowUseCase: StakingApyFlowUseCase,
private val yieldSupplyGetShouldShowMainPromoUseCase: YieldSupplyGetShouldShowMainPromoUseCase,
private val hotWalletFeatureToggles: HotWalletFeatureToggles,
private val tangemPayFeatureToggles: TangemPayFeatureToggles,
private val tangemPayMainSubscriberFactory: TangemPayMainSubscriber.Factory,
@ -63,6 +65,7 @@ internal class MultiWalletContentLoader(
applyTokenListSortingUseCase = applyTokenListSortingUseCase,
yieldSupplyApyFlowUseCase = yieldSupplyApyFlowUseCase,
stakingApyFlowUseCase = stakingApyFlowUseCase,
yieldSupplyGetShouldShowMainPromoUseCase = yieldSupplyGetShouldShowMainPromoUseCase,
).let(::add)
WalletNFTListSubscriber(

View file

@ -11,6 +11,7 @@ import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.wallets.repository.WalletsRepository
import com.tangem.domain.wallets.usecase.ShouldSaveUserWalletsUseCase
import com.tangem.domain.yield.supply.usecase.YieldSupplyApyFlowUseCase
import com.tangem.domain.yield.supply.usecase.YieldSupplyGetShouldShowMainPromoUseCase
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
import com.tangem.feature.wallet.presentation.wallet.analytics.utils.TokenListAnalyticsSender
import com.tangem.feature.wallet.presentation.wallet.analytics.utils.WalletWarningsAnalyticsSender
@ -44,6 +45,7 @@ internal class MultiWalletContentLoaderFactory @Inject constructor(
private val currenciesRepository: CurrenciesRepository,
private val yieldSupplyApyFlowUseCase: YieldSupplyApyFlowUseCase,
private val stakingApyFlowUseCase: StakingApyFlowUseCase,
private val yieldSupplyGetShouldShowMainPromoUseCase: YieldSupplyGetShouldShowMainPromoUseCase,
private val hotWalletFeatureToggles: HotWalletFeatureToggles,
private val tangemPayFeatureToggles: TangemPayFeatureToggles,
private val tangemPayMainSubscriberFactory: TangemPayMainSubscriber.Factory,
@ -72,6 +74,7 @@ internal class MultiWalletContentLoaderFactory @Inject constructor(
hotWalletFeatureToggles = hotWalletFeatureToggles,
tangemPayFeatureToggles = tangemPayFeatureToggles,
tangemPayMainSubscriberFactory = tangemPayMainSubscriberFactory,
yieldSupplyGetShouldShowMainPromoUseCase = yieldSupplyGetShouldShowMainPromoUseCase,
)
}
}

View file

@ -6,6 +6,7 @@ import com.tangem.domain.promo.GetStoryContentUseCase
import com.tangem.domain.staking.usecase.StakingApyFlowUseCase
import com.tangem.domain.wallets.usecase.ShouldSaveUserWalletsUseCase
import com.tangem.domain.yield.supply.usecase.YieldSupplyApyFlowUseCase
import com.tangem.domain.yield.supply.usecase.YieldSupplyGetShouldShowMainPromoUseCase
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
import com.tangem.feature.wallet.presentation.wallet.analytics.utils.TokenListAnalyticsSender
import com.tangem.feature.wallet.presentation.wallet.analytics.utils.WalletWarningsAnalyticsSender
@ -35,6 +36,7 @@ internal class SingleWalletWithTokenContentLoader(
private val yieldSupplyApyFlowUseCase: YieldSupplyApyFlowUseCase,
private val stakingApyFlowUseCase: StakingApyFlowUseCase,
private val hotWalletFeatureToggles: HotWalletFeatureToggles,
private val yieldSupplyGetShouldShowMainPromoUseCase: YieldSupplyGetShouldShowMainPromoUseCase,
) : WalletContentLoader(id = userWallet.walletId) {
override fun create(): List<WalletSubscriber> {
@ -49,6 +51,7 @@ internal class SingleWalletWithTokenContentLoader(
getSelectedAppCurrencyUseCase = getSelectedAppCurrencyUseCase,
yieldSupplyApyFlowUseCase = yieldSupplyApyFlowUseCase,
stakingApyFlowUseCase = stakingApyFlowUseCase,
yieldSupplyGetShouldShowMainPromoUseCase = yieldSupplyGetShouldShowMainPromoUseCase,
).let(::add)
MultiWalletWarningsSubscriber(
userWallet = userWallet,

View file

@ -7,6 +7,7 @@ import com.tangem.domain.promo.GetStoryContentUseCase
import com.tangem.domain.staking.usecase.StakingApyFlowUseCase
import com.tangem.domain.wallets.usecase.ShouldSaveUserWalletsUseCase
import com.tangem.domain.yield.supply.usecase.YieldSupplyApyFlowUseCase
import com.tangem.domain.yield.supply.usecase.YieldSupplyGetShouldShowMainPromoUseCase
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
import com.tangem.feature.wallet.presentation.wallet.analytics.utils.TokenListAnalyticsSender
import com.tangem.feature.wallet.presentation.wallet.analytics.utils.WalletWarningsAnalyticsSender
@ -36,6 +37,7 @@ internal class SingleWalletWithTokenContentLoaderFactory @Inject constructor(
private val yieldSupplyApyFlowUseCase: YieldSupplyApyFlowUseCase,
private val stakingApyFlowUseCase: StakingApyFlowUseCase,
private val hotWalletFeatureToggles: HotWalletFeatureToggles,
private val yieldSupplyGetShouldShowMainPromoUseCase: YieldSupplyGetShouldShowMainPromoUseCase,
) {
fun create(userWallet: UserWallet.Cold, clickIntents: WalletClickIntents): SingleWalletWithTokenContentLoader {
@ -55,6 +57,7 @@ internal class SingleWalletWithTokenContentLoaderFactory @Inject constructor(
yieldSupplyApyFlowUseCase = yieldSupplyApyFlowUseCase,
stakingApyFlowUseCase = stakingApyFlowUseCase,
hotWalletFeatureToggles = hotWalletFeatureToggles,
yieldSupplyGetShouldShowMainPromoUseCase = yieldSupplyGetShouldShowMainPromoUseCase,
)
}
}

View file

@ -11,14 +11,16 @@ import com.tangem.feature.wallet.presentation.wallet.state.transformers.converte
import com.tangem.feature.wallet.presentation.wallet.state.transformers.converter.TokenListStateConverter
import com.tangem.feature.wallet.presentation.wallet.state.utils.enableButtons
import timber.log.Timber
import java.math.BigDecimal
internal class SetTokenListTransformer(
private val params: TokenConverterParams,
private val userWallet: UserWallet,
private val appCurrency: AppCurrency,
private val clickIntents: WalletClickIntents,
private val yieldSupplyApyMap: Map<String, String> = emptyMap(),
private val yieldSupplyApyMap: Map<String, BigDecimal> = emptyMap(),
private val stakingApyMap: Map<String, List<Yield.Validator>> = emptyMap(),
private val shouldShowMainPromo: Boolean,
) : WalletStateTransformer(userWallet.walletId) {
override fun transform(prevState: WalletState): WalletState {
@ -63,6 +65,7 @@ internal class SetTokenListTransformer(
clickIntents = clickIntents,
yieldModuleApyMap = yieldSupplyApyMap,
stakingApyMap = stakingApyMap,
shouldShowMainPromo = shouldShowMainPromo,
).convert(value = this)
}
}

View file

@ -28,17 +28,25 @@ import kotlinx.collections.immutable.PersistentList
import kotlinx.collections.immutable.mutate
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toPersistentList
import java.math.BigDecimal
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletTokensListState.OrganizeTokensButtonConfig as WalletOrganizeTokensButtonConfig
@Suppress("LongParameterList")
internal class TokenListStateConverter(
private val appCurrency: AppCurrency,
private val params: TokenConverterParams,
private val selectedWallet: UserWallet,
private val clickIntents: WalletClickIntents,
private val yieldModuleApyMap: Map<String, String>,
private val yieldModuleApyMap: Map<String, BigDecimal>,
private val stakingApyMap: Map<String, List<Yield.Validator>>,
private val shouldShowMainPromo: Boolean,
) : Converter<WalletTokensListState, WalletTokensListState> {
private val yieldSupplyPromoBannerKeyConverter = YieldSupplyPromoBannerKeyConverter(
yieldModuleApyMap,
shouldShowMainPromo,
)
private val onTokenClick: (accountId: AccountId?, currencyStatus: CryptoCurrencyStatus) -> Unit =
{ accountId, currencyStatus ->
clickIntents.onTokenItemClick(selectedWallet.walletId, currencyStatus)
@ -57,10 +65,12 @@ internal class TokenListStateConverter(
private fun tokenStatusConverter(accountId: AccountId? = null) = TokenItemStateConverter(
appCurrency = appCurrency,
yieldModuleApyMap = yieldModuleApyMap,
yieldSupplyPromoBannerKey = yieldSupplyPromoBannerKeyConverter.convert(params),
stakingApyMap = stakingApyMap,
onItemClick = { _, status -> onTokenClick(accountId, status) },
onItemLongClick = { _, status -> onTokenLongClick(accountId, status) },
onApyLabelClick = { status, apy -> onApyLabelClick(status, apy) },
onYieldPromoCloseClick = clickIntents::onYieldPromoCloseClick,
)
override fun convert(value: WalletTokensListState): WalletTokensListState {

View file

@ -0,0 +1,48 @@
package com.tangem.feature.wallet.presentation.wallet.state.transformers.converter
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.currency.yieldSupplyKey
import com.tangem.feature.wallet.presentation.wallet.state.transformers.TokenConverterParams
import com.tangem.lib.crypto.BlockchainUtils
import com.tangem.utils.converter.Converter
import java.math.BigDecimal
internal class YieldSupplyPromoBannerKeyConverter(
private val yieldModuleApyMap: Map<String, BigDecimal>,
private val shouldShowMainPromo: Boolean,
) : Converter<TokenConverterParams, String?> {
override fun convert(value: TokenConverterParams): String? {
if (!shouldShowMainPromo) return null
val currencies = when (value) {
is TokenConverterParams.Wallet -> value.tokenList.flattenCurrencies()
is TokenConverterParams.Account -> value.accountList.flattenCurrencies()
}.filter { status ->
status.value is CryptoCurrencyStatus.Loaded ||
status.value is CryptoCurrencyStatus.Custom
}
val tokens = currencies.filter { it.currency is CryptoCurrency.Token }
if (tokens.any { it.value.yieldSupplyStatus?.isActive == true }) return null
if (yieldModuleApyMap.isEmpty()) return null
val max = tokens.asSequence()
.mapNotNull { status ->
val token = status.currency as? CryptoCurrency.Token ?: return@mapNotNull null
val tokenKey = token.yieldSupplyKey()
val shouldIgnoreCase = BlockchainUtils.isCaseInsensitiveContractAddress(token.network.rawId)
val matchedKey = yieldModuleApyMap.keys.firstOrNull { mapKey ->
mapKey.equals(tokenKey, shouldIgnoreCase)
} ?: return@mapNotNull null
status to matchedKey
}
.maxByOrNull { (status, _) -> status.value.amount ?: BigDecimal.ZERO }
return max?.second
}
}

View file

@ -5,16 +5,18 @@ import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.staking.model.stakekit.Yield
import com.tangem.domain.staking.usecase.StakingApyFlowUseCase
import com.tangem.domain.yield.supply.usecase.YieldSupplyApyFlowUseCase
import com.tangem.domain.yield.supply.usecase.YieldSupplyGetShouldShowMainPromoUseCase
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
import com.tangem.feature.wallet.presentation.account.AccountDependencies
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController
import com.tangem.utils.coroutines.combine6
import com.tangem.utils.coroutines.combine7
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.distinctUntilChanged
import java.math.BigDecimal
/**
* Subscriber that monitors account list related data and updates the wallet state accordingly.
@ -30,19 +32,21 @@ internal class AccountListSubscriber @AssistedInject constructor(
override val clickIntents: WalletClickIntents,
private val yieldSupplyApyFlowUseCase: YieldSupplyApyFlowUseCase,
private val stakingApyFlowUseCase: StakingApyFlowUseCase,
private val yieldSupplyGetShouldShowMainPromoUseCase: YieldSupplyGetShouldShowMainPromoUseCase,
) : BasicAccountListSubscriber() {
override fun create(coroutineScope: CoroutineScope): Flow<*> = combine6(
override fun create(coroutineScope: CoroutineScope): Flow<*> = combine7(
flow1 = getAccountStatusListFlow(),
flow2 = getAppCurrencyFlow(),
flow3 = accountDependencies.expandedAccountsHolder.expandedAccounts(userWallet),
flow4 = accountDependencies.isAccountsModeEnabledUseCase(),
flow5 = yieldSupplyApyFlow(),
flow6 = stakingApyFlow(),
flow7 = yieldSupplyGetShouldShowMainPromoFlow(),
transform = ::updateState,
)
private fun yieldSupplyApyFlow(): Flow<Map<String, String>> {
private fun yieldSupplyApyFlow(): Flow<Map<String, BigDecimal>> {
return yieldSupplyApyFlowUseCase().distinctUntilChanged()
}
@ -50,6 +54,10 @@ internal class AccountListSubscriber @AssistedInject constructor(
return stakingApyFlowUseCase().distinctUntilChanged()
}
private fun yieldSupplyGetShouldShowMainPromoFlow(): Flow<Boolean> {
return yieldSupplyGetShouldShowMainPromoUseCase().distinctUntilChanged()
}
@AssistedFactory
interface Factory {
fun create(userWallet: UserWallet): AccountListSubscriber

View file

@ -20,6 +20,7 @@ import com.tangem.feature.wallet.presentation.wallet.state.transformers.TokenCon
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.distinctUntilChanged
import timber.log.Timber
import java.math.BigDecimal
/**
* Basic implementation of [WalletSubscriber] for wallet with accounts.
@ -46,8 +47,9 @@ internal abstract class BasicAccountListSubscriber : BasicWalletSubscriber() {
appCurrency: AppCurrency,
expandedAccounts: Set<AccountId>,
isAccountMode: Boolean,
yieldSupplyApyMap: Map<String, String> = emptyMap(),
yieldSupplyApyMap: Map<String, BigDecimal> = emptyMap(),
stakingApyMap: Map<String, List<Yield.Validator>> = emptyMap(),
shouldShowMainPromo: Boolean = false,
) {
val accountFlattenCurrencies = accountList.flattenCurrencies()
val mainAccount = accountList.mainAccount
@ -67,6 +69,7 @@ internal abstract class BasicAccountListSubscriber : BasicWalletSubscriber() {
portfolioId = PortfolioId(mainAccount.accountId),
yieldSupplyApyMap = yieldSupplyApyMap,
stakingApyMap = stakingApyMap,
shouldShowMainPromo = shouldShowMainPromo,
)
}
isAccountMode -> {
@ -82,7 +85,13 @@ internal abstract class BasicAccountListSubscriber : BasicWalletSubscriber() {
} else {
val convertParams = TokenConverterParams.Account(accountList, expandedAccounts)
updateContent(convertParams, appCurrency, yieldSupplyApyMap, stakingApyMap)
updateContent(
params = convertParams,
appCurrency = appCurrency,
yieldSupplyApyMap = yieldSupplyApyMap,
stakingApyMap = stakingApyMap,
shouldShowMainPromo = shouldShowMainPromo,
)
}
}
}
@ -92,8 +101,9 @@ internal abstract class BasicAccountListSubscriber : BasicWalletSubscriber() {
maybeTokenList: Lce<TokenListError, TokenList>,
appCurrency: AppCurrency,
portfolioId: PortfolioId,
yieldSupplyApyMap: Map<String, String> = emptyMap(),
yieldSupplyApyMap: Map<String, BigDecimal> = emptyMap(),
stakingApyMap: Map<String, List<Yield.Validator>> = emptyMap(),
shouldShowMainPromo: Boolean,
) {
val tokenList = maybeTokenList.getOrElse(
ifLoading = { maybeContent ->
@ -123,14 +133,16 @@ internal abstract class BasicAccountListSubscriber : BasicWalletSubscriber() {
appCurrency = appCurrency,
yieldSupplyApyMap = yieldSupplyApyMap,
stakingApyMap = stakingApyMap,
shouldShowMainPromo = shouldShowMainPromo,
)
}
private fun updateContent(
params: TokenConverterParams,
appCurrency: AppCurrency,
yieldSupplyApyMap: Map<String, String> = emptyMap(),
yieldSupplyApyMap: Map<String, BigDecimal> = emptyMap(),
stakingApyMap: Map<String, List<Yield.Validator>> = emptyMap(),
shouldShowMainPromo: Boolean,
) {
stateController.update(
SetTokenListTransformer(
@ -140,6 +152,7 @@ internal abstract class BasicAccountListSubscriber : BasicWalletSubscriber() {
clickIntents = clickIntents,
yieldSupplyApyMap = yieldSupplyApyMap,
stakingApyMap = stakingApyMap,
shouldShowMainPromo = shouldShowMainPromo,
),
)
}

View file

@ -15,6 +15,7 @@ import com.tangem.domain.staking.model.stakekit.Yield
import com.tangem.domain.staking.usecase.StakingApyFlowUseCase
import com.tangem.domain.tokens.error.TokenListError
import com.tangem.domain.yield.supply.usecase.YieldSupplyApyFlowUseCase
import com.tangem.domain.yield.supply.usecase.YieldSupplyGetShouldShowMainPromoUseCase
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
import com.tangem.feature.wallet.presentation.wallet.analytics.utils.TokenListAnalyticsSender
import com.tangem.feature.wallet.presentation.wallet.domain.WalletWithFundsChecker
@ -28,6 +29,7 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
import timber.log.Timber
import java.math.BigDecimal
@Deprecated("Use AccountListSubscriber instead")
@Suppress("LongParameterList")
@ -40,6 +42,7 @@ internal abstract class BasicTokenListSubscriber(
private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
private val yieldSupplyApyFlowUseCase: YieldSupplyApyFlowUseCase,
private val stakingApyFlowUseCase: StakingApyFlowUseCase,
private val yieldSupplyGetShouldShowMainPromoUseCase: YieldSupplyGetShouldShowMainPromoUseCase,
) : WalletSubscriber() {
private val sendAnalyticsJobHolder = JobHolder()
@ -69,7 +72,8 @@ internal abstract class BasicTokenListSubscriber(
flow2 = appCurrencyFlow(),
flow3 = yieldSupplyApyFlow(),
flow4 = stakingApyFlow(),
transform = { maybeTokenList, appCurrency, yieldSupplyApyMap, stakingApyMap ->
flow5 = yieldSupplyGetShouldShowMainPromoFlow(),
transform = { maybeTokenList, appCurrency, yieldSupplyApyMap, stakingApyMap, shouldShowMainPromo ->
val tokenList = maybeTokenList.getOrElse(
ifLoading = { maybeContent ->
val isRefreshing = stateHolder.getWalletState(userWallet.walletId)
@ -98,6 +102,7 @@ internal abstract class BasicTokenListSubscriber(
appCurrency = appCurrency,
yieldSupplyApyMap = yieldSupplyApyMap,
stakingApyMap = stakingApyMap,
shouldShowMainPromo = shouldShowMainPromo,
)
walletWithFundsChecker.check(tokenList)
@ -122,8 +127,9 @@ internal abstract class BasicTokenListSubscriber(
private fun updateContent(
params: TokenConverterParams,
appCurrency: AppCurrency,
yieldSupplyApyMap: Map<String, String>,
yieldSupplyApyMap: Map<String, BigDecimal>,
stakingApyMap: Map<String, List<Yield.Validator>>,
shouldShowMainPromo: Boolean,
) {
stateHolder.update(
SetTokenListTransformer(
@ -133,6 +139,7 @@ internal abstract class BasicTokenListSubscriber(
clickIntents = clickIntents,
yieldSupplyApyMap = yieldSupplyApyMap,
stakingApyMap = stakingApyMap,
shouldShowMainPromo = shouldShowMainPromo,
),
)
}
@ -146,9 +153,12 @@ internal abstract class BasicTokenListSubscriber(
}
.distinctUntilChanged()
private fun yieldSupplyApyFlow(): Flow<Map<String, String>> = yieldSupplyApyFlowUseCase()
private fun yieldSupplyApyFlow(): Flow<Map<String, BigDecimal>> = yieldSupplyApyFlowUseCase()
.distinctUntilChanged()
private fun stakingApyFlow(): Flow<Map<String, List<Yield.Validator>>> = stakingApyFlowUseCase()
.distinctUntilChanged()
private fun yieldSupplyGetShouldShowMainPromoFlow(): Flow<Boolean> = yieldSupplyGetShouldShowMainPromoUseCase()
.distinctUntilChanged()
}

View file

@ -12,6 +12,7 @@ import com.tangem.domain.staking.usecase.StakingApyFlowUseCase
import com.tangem.domain.tokens.ApplyTokenListSortingUseCase
import com.tangem.domain.tokens.error.TokenListError
import com.tangem.domain.yield.supply.usecase.YieldSupplyApyFlowUseCase
import com.tangem.domain.yield.supply.usecase.YieldSupplyGetShouldShowMainPromoUseCase
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
import com.tangem.feature.wallet.presentation.wallet.analytics.utils.TokenListAnalyticsSender
import com.tangem.feature.wallet.presentation.wallet.domain.MultiWalletTokenListStore
@ -32,6 +33,7 @@ internal class MultiWalletTokenListSubscriber(
getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
yieldSupplyApyFlowUseCase: YieldSupplyApyFlowUseCase,
stakingApyFlowUseCase: StakingApyFlowUseCase,
yieldSupplyGetShouldShowMainPromoUseCase: YieldSupplyGetShouldShowMainPromoUseCase,
) : BasicTokenListSubscriber(
userWallet = userWallet,
stateHolder = stateHolder,
@ -41,6 +43,7 @@ internal class MultiWalletTokenListSubscriber(
getSelectedAppCurrencyUseCase = getSelectedAppCurrencyUseCase,
yieldSupplyApyFlowUseCase = yieldSupplyApyFlowUseCase,
stakingApyFlowUseCase = stakingApyFlowUseCase,
yieldSupplyGetShouldShowMainPromoUseCase = yieldSupplyGetShouldShowMainPromoUseCase,
) {
override fun tokenListFlow(coroutineScope: CoroutineScope): LceFlow<TokenListError, TokenList> {

View file

@ -8,6 +8,7 @@ import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.staking.usecase.StakingApyFlowUseCase
import com.tangem.domain.tokens.error.TokenListError
import com.tangem.domain.yield.supply.usecase.YieldSupplyApyFlowUseCase
import com.tangem.domain.yield.supply.usecase.YieldSupplyGetShouldShowMainPromoUseCase
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
import com.tangem.feature.wallet.presentation.wallet.analytics.utils.TokenListAnalyticsSender
import com.tangem.feature.wallet.presentation.wallet.domain.MultiWalletTokenListStore
@ -27,6 +28,7 @@ internal class SingleWalletWithTokenListSubscriber(
getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
yieldSupplyApyFlowUseCase: YieldSupplyApyFlowUseCase,
stakingApyFlowUseCase: StakingApyFlowUseCase,
yieldSupplyGetShouldShowMainPromoUseCase: YieldSupplyGetShouldShowMainPromoUseCase,
) : BasicTokenListSubscriber(
userWallet = userWallet,
stateHolder = stateHolder,
@ -36,6 +38,7 @@ internal class SingleWalletWithTokenListSubscriber(
getSelectedAppCurrencyUseCase = getSelectedAppCurrencyUseCase,
yieldSupplyApyFlowUseCase = yieldSupplyApyFlowUseCase,
stakingApyFlowUseCase = stakingApyFlowUseCase,
yieldSupplyGetShouldShowMainPromoUseCase = yieldSupplyGetShouldShowMainPromoUseCase,
) {
override fun tokenListFlow(coroutineScope: CoroutineScope): LceFlow<TokenListError, TokenList> {

View file

@ -0,0 +1,208 @@
package com.tangem.feature.wallet.presentation.wallet.state.transformers.converter
import com.google.common.truth.Truth.assertThat
import com.tangem.domain.models.PortfolioId
import com.tangem.domain.models.StatusSource
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.currency.yieldSupplyKey
import com.tangem.domain.models.network.Network
import com.tangem.domain.models.network.NetworkAddress
import com.tangem.domain.models.tokenlist.TokenList
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.models.yield.supply.YieldSupplyStatus
import com.tangem.feature.wallet.presentation.wallet.state.transformers.TokenConverterParams
import org.junit.Test
import java.math.BigDecimal
class YieldSupplyPromoBannerKeyConverterTest {
@Test
fun `GIVEN promo disabled WHEN convert THEN return null`() {
val token = createToken(networkId = "ethereum", backendId = "ethereum", contract = "0xABCDEF")
val status = createStatus(token = token, amount = BigDecimal.ONE, isYieldActive = false)
val tokenList = ungroupedTokenList(status)
val params = TokenConverterParams.Wallet(
portfolioId = PortfolioId.Wallet(UserWalletId("00")),
tokenList = tokenList,
)
val converter = YieldSupplyPromoBannerKeyConverter(
yieldModuleApyMap = mapOf(token.yieldSupplyKey() to BigDecimal("0.10")),
shouldShowMainPromo = false,
)
val result = converter.convert(params)
assertThat(result).isNull()
}
@Test
fun `GIVEN empty apy map WHEN convert THEN return null`() {
val token = createToken(networkId = "ethereum", backendId = "ethereum", contract = "0xA1")
val status = createStatus(token = token, amount = BigDecimal("2.0"), isYieldActive = false)
val params = TokenConverterParams.Wallet(
portfolioId = PortfolioId.Wallet(UserWalletId("00")),
tokenList = ungroupedTokenList(status),
)
val converter = YieldSupplyPromoBannerKeyConverter(
yieldModuleApyMap = emptyMap(),
shouldShowMainPromo = true,
)
val result = converter.convert(params)
assertThat(result).isNull()
}
@Test
fun `GIVEN active yield token present WHEN convert THEN return null`() {
val token = createToken(networkId = "ethereum", backendId = "ethereum", contract = "0xAA")
val statusActive = createStatus(token = token, amount = BigDecimal("5"), isYieldActive = true)
val params = TokenConverterParams.Wallet(
portfolioId = PortfolioId.Wallet(UserWalletId("00")),
tokenList = ungroupedTokenList(statusActive),
)
val converter = YieldSupplyPromoBannerKeyConverter(
yieldModuleApyMap = mapOf(token.yieldSupplyKey() to BigDecimal("0.12")),
shouldShowMainPromo = true,
)
val result = converter.convert(params)
assertThat(result).isNull()
}
@Test
fun `GIVEN multiple candidates EVM case insensitive WHEN convert THEN return key of max amount`() {
val evmNetworkId = "ETH"
val tokenSmall = createToken(networkId = evmNetworkId, backendId = evmNetworkId, contract = "0xAbCd")
val tokenBig = createToken(networkId = evmNetworkId, backendId = evmNetworkId, contract = "0xBEEF")
val statusSmall = createStatus(token = tokenSmall, amount = BigDecimal("1.00"), isYieldActive = false)
val statusBig = createStatus(token = tokenBig, amount = BigDecimal("10.00"), isYieldActive = false)
val apyMap = mapOf(
"${tokenSmall.network.backendId}_${tokenSmall.contractAddress.lowercase()}" to BigDecimal("0.05"),
"${tokenBig.network.backendId}_${tokenBig.contractAddress.uppercase()}" to BigDecimal("0.15"),
)
val params = TokenConverterParams.Wallet(
portfolioId = PortfolioId.Wallet(UserWalletId("00")),
tokenList = ungroupedTokenList(statusSmall, statusBig),
)
val converter = YieldSupplyPromoBannerKeyConverter(
yieldModuleApyMap = apyMap,
shouldShowMainPromo = true,
)
val result = converter.convert(params)
val expectedKey = "${tokenBig.network.backendId}_${tokenBig.contractAddress.uppercase()}"
assertThat(result).isEqualTo(expectedKey)
}
@Test
fun `GIVEN non evm case sensitive mismatch WHEN convert THEN return null`() {
val nonEvmId = "xrp"
val token = createToken(networkId = nonEvmId, backendId = nonEvmId, contract = "rAbC123")
val status = createStatus(token = token, amount = BigDecimal("3"), isYieldActive = false)
val mismatchedKey = "${token.network.backendId}_${token.contractAddress.lowercase()}"
val apyMap = mapOf(mismatchedKey to BigDecimal("0.07"))
val params = TokenConverterParams.Wallet(
portfolioId = PortfolioId.Wallet(UserWalletId("00")),
tokenList = ungroupedTokenList(status),
)
val converter = YieldSupplyPromoBannerKeyConverter(
yieldModuleApyMap = apyMap,
shouldShowMainPromo = true,
)
val result = converter.convert(params)
assertThat(result).isNull()
}
private fun ungroupedTokenList(vararg statuses: CryptoCurrencyStatus): TokenList.Ungrouped {
return TokenList.Ungrouped(
totalFiatBalance = com.tangem.domain.models.TotalFiatBalance.Loaded(
amount = BigDecimal.ZERO,
source = StatusSource.ACTUAL,
),
sortedBy = com.tangem.domain.models.TokensSortType.NONE,
currencies = statuses.toList(),
)
}
private fun createStatus(
token: CryptoCurrency.Token,
amount: BigDecimal,
isYieldActive: Boolean,
): CryptoCurrencyStatus {
val networkAddress = NetworkAddress.Single(
defaultAddress = NetworkAddress.Address(
value = "addr",
type = NetworkAddress.Address.Type.Primary,
),
)
val value = CryptoCurrencyStatus.Custom(
amount = amount,
fiatAmount = null,
fiatRate = null,
priceChange = null,
yieldBalance = null,
yieldSupplyStatus = if (isYieldActive) {
YieldSupplyStatus(
isActive = true,
isInitialized = true,
isAllowedToSpend = true,
effectiveProtocolBalance = null,
)
} else {
null
},
hasCurrentNetworkTransactions = false,
pendingTransactions = emptySet(),
networkAddress = networkAddress,
sources = CryptoCurrencyStatus.Sources(),
)
return CryptoCurrencyStatus(
currency = token,
value = value,
)
}
private fun createToken(networkId: String, backendId: String, contract: String): CryptoCurrency.Token {
val network = Network(
id = Network.ID(value = networkId, derivationPath = Network.DerivationPath.None),
backendId = backendId,
name = backendId,
currencySymbol = "SYM",
derivationPath = Network.DerivationPath.None,
isTestnet = false,
standardType = when (backendId) {
"ethereum" -> Network.StandardType.ERC20
else -> Network.StandardType.Unspecified("UNSPEC")
},
hasFiatFeeRate = false,
canHandleTokens = true,
transactionExtrasType = Network.TransactionExtrasType.NONE,
nameResolvingType = Network.NameResolvingType.NONE,
)
return CryptoCurrency.Token(
id = CryptoCurrency.ID(
prefix = CryptoCurrency.ID.Prefix.TOKEN_PREFIX,
body = CryptoCurrency.ID.Body.NetworkId(networkId),
suffix = CryptoCurrency.ID.Suffix.ContractAddress(contract),
),
network = network,
name = "Token",
symbol = "TKN",
decimals = 18,
iconUrl = null,
isCustom = false,
contractAddress = contract,
)
}
}