Updated on 2026-08-14

This commit is contained in:
Tangem 2026-07-30 21:50:33 +05:00
parent dce35fc26b
commit 430eced63a
10 changed files with 126 additions and 80 deletions

View file

@ -3,6 +3,7 @@ package com.tangem.feature.wallet.child.wallet.model.intents
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.analytics.models.event.MainScreenAnalyticsEvent
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.ui.UiMessageSender
import com.tangem.domain.exchange.RampStateManager
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.models.wallet.isLocked
@ -18,6 +19,7 @@ import com.tangem.feature.wallet.presentation.wallet.domain.WalletContentFetcher
import com.tangem.feature.wallet.presentation.wallet.domain.unwrap
import com.tangem.feature.wallet.presentation.wallet.loaders.WalletScreenContentLoader
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletAlertUM
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletUM
import com.tangem.feature.wallet.presentation.wallet.state.transformers.SetRefreshStateTransformer
import kotlinx.coroutines.CoroutineScope
@ -46,6 +48,7 @@ internal class WalletClickIntents @Inject constructor(
private val tangemPayIntents: TangemPayClickIntentsImplementor,
private val yieldSupplyApyUpdateUseCase: YieldSupplyApyUpdateUseCase,
private val analyticsEventHandler: AnalyticsEventHandler,
private val uiMessageSender: UiMessageSender,
) : BaseWalletClickIntents(),
WalletCardClickIntents by walletCardClickIntentsImplementor,
WalletWarningsClickIntents by warningsClickIntentsImplementer,
@ -108,6 +111,13 @@ internal class WalletClickIntents @Inject constructor(
fun onTransferClick(userWalletId: UserWalletId) {
analyticsEventHandler.send(MainScreenAnalyticsEvent.ButtonTransfer())
val selectedWallet = stateController.getSelectedWalletUM() as? WalletUM.Content
if (selectedWallet?.areActionsAvailable == false) {
uiMessageSender.send(WalletAlertUM.unavailableOperation())
return
}
router.openTransfer(userWalletId)
}

View file

@ -8,6 +8,7 @@ import com.tangem.core.ui.ds.button.TangemButtonUM
import com.tangem.core.ui.ds.image.TangemIconUM
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.res.TangemTheme
import com.tangem.feature.wallet.impl.R
/**
@ -26,7 +27,16 @@ internal sealed class WalletActionButtons(
val buttonUM: TangemButtonUM
get() = TangemButtonUM(
text = text,
tangemIconUM = TangemIconUM.Icon(iconRes = iconRes),
tangemIconUM = TangemIconUM.Icon(
iconRes = iconRes,
tintReference = {
if (isEnabled) {
TangemTheme.colors2.graphic.neutral.primary
} else {
TangemTheme.colors2.graphic.neutral.quaternary
}
},
),
type = TangemButtonType.Secondary,
shape = TangemButtonShape.Rounded,
onClick = onClick,

View file

@ -40,6 +40,7 @@ internal sealed interface WalletUM {
override val type: WalletType,
override val tangemPayMainUM: TangemPayMainUM,
override val virtualAccountMainUM: VirtualAccountMainUM,
val areActionsAvailable: Boolean = false,
) : WalletUM
data class Locked(

View file

@ -3,8 +3,6 @@ package com.tangem.feature.wallet.presentation.wallet.state.transformers
import com.tangem.core.ui.components.containers.pullToRefresh.PullToRefreshConfig
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.feature.wallet.presentation.wallet.state.model.*
import com.tangem.feature.wallet.presentation.wallet.state.utils.disableButtons
import com.tangem.feature.wallet.presentation.wallet.state.utils.enableButtons
import kotlinx.collections.immutable.PersistentList
import kotlinx.collections.immutable.mutate
@ -36,15 +34,9 @@ internal class SetRefreshStateTransformer(
override fun transform(walletUM: WalletUM): WalletUM {
return when (walletUM) {
is WalletUM.Content -> {
val tokensListUM = walletUM.tokensListUM.toUpdatedState()
walletUM.copy(
pullToRefreshConfig = walletUM.pullToRefreshConfig.toUpdatedState(isRefreshing),
tokensListUM = tokensListUM,
buttons = if (tokensListUM is WalletTokensListUM.Empty) {
walletUM.disableButtons()
} else {
walletUM.enableButtons()
},
tokensListUM = walletUM.tokensListUM.toUpdatedState(),
)
}
is WalletUM.Locked -> walletUM

View file

@ -12,6 +12,7 @@ import com.tangem.domain.tokens.error.TokenListError
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
import com.tangem.feature.wallet.presentation.wallet.domain.WalletAdditionalInfoFactory
import com.tangem.feature.wallet.presentation.wallet.state.model.*
import com.tangem.feature.wallet.presentation.wallet.state.utils.createWalletActionButtons
import com.tangem.feature.wallet.presentation.wallet.state.utils.disableButtons
import com.tangem.utils.logging.TangemLogger
import java.math.BigDecimal
@ -64,7 +65,14 @@ internal class SetTokenListErrorTransformer(
clickIntents.onAssetsDiscoveryManageClick(walletUM.walletsBalanceUM.id)
},
),
buttons = walletUM.disableButtons(),
areActionsAvailable = false,
buttons = createWalletActionButtons(
userWallet = selectedWallet,
clickIntents = clickIntents,
isAddFundsEnabled = false,
isSwapEnabled = false,
isTransferEnabled = false,
),
)
}
is WalletUM.Locked -> {

View file

@ -2,6 +2,8 @@ package com.tangem.feature.wallet.presentation.wallet.state.transformers
import com.tangem.common.ui.tokens.TokenConverterParams
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.models.StatusSource
import com.tangem.domain.models.TotalFiatBalance
import com.tangem.domain.models.account.AccountStatus
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.wallet.UserWallet
@ -9,7 +11,7 @@ import com.tangem.domain.staking.model.StakingAvailability
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
import com.tangem.feature.wallet.presentation.wallet.state.model.*
import com.tangem.feature.wallet.presentation.wallet.state.transformers.converter.*
import com.tangem.feature.wallet.presentation.wallet.state.utils.disableButtons
import com.tangem.feature.wallet.presentation.wallet.state.utils.createWalletActionButtons
import com.tangem.feature.wallet.presentation.wallet.state.utils.enableButtons
import com.tangem.features.tangempay.entity.TangemPayMainUM
import com.tangem.features.virtualaccount.main.entity.VirtualAccountMainUM
@ -68,16 +70,21 @@ internal class SetTokenListTransformer(
return when (walletUM) {
is WalletUM.Content -> {
val tokensListUM = toLoadedState()
val isTokensEmpty = tokensListUM is WalletTokensListUM.Empty
val areActionsAvailable = getTotalFiatBalance().areActionsAvailable()
walletUM.copy(
walletsBalanceUM = walletUM.walletsBalanceUM.toLoadedState2(),
tangemPayMainUM = walletUM.tangemPayMainUM.toLoadedState(),
virtualAccountMainUM = walletUM.virtualAccountMainUM.toLoadedVirtualState(),
tokensListUM = tokensListUM,
buttons = if (tokensListUM is WalletTokensListUM.Empty) {
walletUM.disableButtons()
} else {
walletUM.enableButtons()
},
areActionsAvailable = areActionsAvailable,
buttons = createWalletActionButtons(
userWallet = userWallet,
clickIntents = clickIntents,
isAddFundsEnabled = !isTokensEmpty,
isSwapEnabled = !isTokensEmpty && areActionsAvailable,
isTransferEnabled = !isTokensEmpty,
),
)
}
is WalletUM.Locked -> {
@ -87,6 +94,14 @@ internal class SetTokenListTransformer(
}
}
private fun getTotalFiatBalance(): TotalFiatBalance = when (params) {
is TokenConverterParams.Account -> params.accountList.totalFiatBalance
is TokenConverterParams.Wallet -> params.tokenList.totalFiatBalance
}
private fun TotalFiatBalance.areActionsAvailable(): Boolean =
(this as? TotalFiatBalance.Loaded)?.source != StatusSource.ONLY_CACHE
private fun WalletCardState.toLoadedState(): WalletCardState {
val fiatBalance = when (params) {
is TokenConverterParams.Account -> params.accountList.totalFiatBalance

View file

@ -1,11 +1,7 @@
package com.tangem.feature.wallet.presentation.wallet.state.utils
import com.tangem.core.ui.ds.button.TangemButtonUM
import com.tangem.core.ui.ds.image.TangemIconUM
import com.tangem.core.ui.res.TangemTheme
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletManageButton
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletUM
import kotlinx.collections.immutable.PersistentList
import kotlinx.collections.immutable.toPersistentList
@ -17,27 +13,6 @@ internal fun WalletState.MultiCurrency.Content.disableButtons(): PersistentList<
return changeAvailability(enabled = false)
}
internal fun WalletUM.Content.enableButtons(): PersistentList<TangemButtonUM> {
return buttons.map { it.withEnabled(isEnabled = true) }.toPersistentList()
}
internal fun WalletUM.Content.disableButtons(): PersistentList<TangemButtonUM> {
return buttons.map { it.withEnabled(isEnabled = false) }.toPersistentList()
}
private fun TangemButtonUM.withEnabled(isEnabled: Boolean): TangemButtonUM {
val refreshedIcon = (tangemIconUM as? TangemIconUM.Icon)?.copy(
tint = {
if (isEnabled) {
TangemTheme.colors2.graphic.neutral.primary
} else {
TangemTheme.colors2.graphic.neutral.quaternary
}
},
) ?: tangemIconUM
return copy(isEnabled = isEnabled, tangemIconUM = refreshedIcon)
}
private fun WalletState.MultiCurrency.Content.changeAvailability(enabled: Boolean): PersistentList<WalletManageButton> {
return buttons
.map { action ->

View file

@ -0,0 +1,43 @@
package com.tangem.feature.wallet.presentation.wallet.state.utils
import com.tangem.core.ui.ds.button.TangemButtonUM
import com.tangem.domain.card.common.util.cardTypesResolver
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletActionButtons
import com.tangem.utils.extensions.addIf
import kotlinx.collections.immutable.PersistentList
import kotlinx.collections.immutable.toPersistentList
internal fun createWalletActionButtons(
userWallet: UserWallet,
clickIntents: WalletClickIntents,
isAddFundsEnabled: Boolean,
isSwapEnabled: Boolean,
isTransferEnabled: Boolean,
): PersistentList<TangemButtonUM> {
val isSingleWalletWithToken = userWallet is UserWallet.Cold &&
userWallet.scanResponse.cardTypesResolver.isSingleWalletWithToken()
return buildList {
add(
WalletActionButtons.AddFunds(
isEnabled = isAddFundsEnabled,
onClick = { clickIntents.onAddFundsClick(userWalletId = userWallet.walletId) },
).buttonUM,
)
addIf(
condition = !userWallet.isSingleWallet() && !isSingleWalletWithToken,
element = WalletActionButtons.Swap(
isEnabled = isSwapEnabled,
onClick = { clickIntents.onMultiWalletSwapClick(userWalletId = userWallet.walletId) },
).buttonUM,
)
add(
WalletActionButtons.Transfer(
isEnabled = isTransferEnabled,
onClick = { clickIntents.onTransferClick(userWalletId = userWallet.walletId) },
).buttonUM,
)
}.toPersistentList()
}

View file

@ -18,11 +18,9 @@ import com.tangem.feature.wallet.presentation.wallet.domain.WalletImageResolver
import com.tangem.feature.wallet.presentation.wallet.state.model.*
import com.tangem.features.tangempay.entity.TangemPayMainUM
import com.tangem.features.virtualaccount.main.entity.VirtualAccountMainUM
import com.tangem.utils.extensions.addIf
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.PersistentList
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toPersistentList
import kotlinx.coroutines.flow.MutableStateFlow
/**
@ -174,33 +172,13 @@ internal class WalletLoadingStateFactory(
}
private fun createWalletActions(userWallet: UserWallet): PersistentList<TangemButtonUM> {
val isSingleWalletWithToken =
userWallet is UserWallet.Cold && userWallet.scanResponse.cardTypesResolver.isSingleWalletWithToken()
return buildList {
add(
WalletActionButtons.AddFunds(
isEnabled = false,
onClick = { clickIntents.onAddFundsClick(userWalletId = userWallet.walletId) },
).buttonUM,
return createWalletActionButtons(
userWallet = userWallet,
clickIntents = clickIntents,
isAddFundsEnabled = false,
isSwapEnabled = false,
isTransferEnabled = false,
)
addIf(
condition = !userWallet.isSingleWallet() && !isSingleWalletWithToken,
element = WalletActionButtons.Swap(
isEnabled = false,
onClick = {
clickIntents.onMultiWalletSwapClick(userWalletId = userWallet.walletId)
},
).buttonUM,
)
add(
WalletActionButtons.Transfer(
isEnabled = false,
onClick = {
clickIntents.onTransferClick(userWalletId = userWallet.walletId)
},
).buttonUM,
)
}.toPersistentList()
}
private fun createDimmedButtons(): PersistentList<WalletManageButton> {

View file

@ -44,19 +44,33 @@ class SetTokenListTransformerTest {
val result = transformer.transform(walletUM(buttonEnabled = true)) as WalletUM.Content
assertThat(result.tokensListUM).isInstanceOf(WalletTokensListUM.Empty::class.java)
assertThat(result.buttons).hasSize(1)
assertThat(result.buttons.single().isEnabled).isFalse()
assertThat(result.buttons.map { it.isEnabled }).containsExactly(false, false, false)
}
@Test
fun `GIVEN non-empty account list WHEN transform THEN tokens not Empty and buttons enabled`() {
val transformer = createTransformer(accountList = nonEmptyAccountList())
fun `GIVEN actual balance WHEN transform THEN tokens not Empty and all buttons enabled`() {
val transformer = createTransformer(
accountList = nonEmptyAccountList(TotalFiatBalance.Loaded(BigDecimal.ZERO, StatusSource.ACTUAL)),
)
val result = transformer.transform(walletUM(buttonEnabled = false)) as WalletUM.Content
assertThat(result.tokensListUM).isNotInstanceOf(WalletTokensListUM.Empty::class.java)
assertThat(result.buttons).hasSize(1)
assertThat(result.buttons.single().isEnabled).isTrue()
assertThat(result.areActionsAvailable).isTrue()
assertThat(result.buttons.map { it.isEnabled }).containsExactly(true, true, true)
}
@Test
fun `GIVEN only cached balance WHEN transform THEN Swap disabled while AddFunds and Transfer enabled`() {
val transformer = createTransformer(
accountList = nonEmptyAccountList(TotalFiatBalance.Loaded(BigDecimal.ZERO, StatusSource.ONLY_CACHE)),
)
val result = transformer.transform(walletUM(buttonEnabled = false)) as WalletUM.Content
assertThat(result.areActionsAvailable).isFalse()
// buttons order: AddFunds, Swap, Transfer
assertThat(result.buttons.map { it.isEnabled }).containsExactly(true, false, true)
}
private fun createTransformer(accountList: AccountStatusList): SetTokenListTransformer {
@ -88,11 +102,11 @@ class SetTokenListTransformerTest {
groupType = TokensGroupType.NONE,
)
private fun nonEmptyAccountList(): AccountStatusList {
private fun nonEmptyAccountList(totalFiatBalance: TotalFiatBalance): AccountStatusList {
val token = createToken()
val tokenList = TokenList.Ungrouped(
currencies = listOf(createLoadedStatus(token)),
totalFiatBalance = TotalFiatBalance.Loaded(BigDecimal.ZERO, StatusSource.ACTUAL),
totalFiatBalance = totalFiatBalance,
sortedBy = TokensSortType.NONE,
)
return AccountStatusList(
@ -100,7 +114,7 @@ class SetTokenListTransformerTest {
accountStatuses = listOf(mainCryptoPortfolioStatus(tokenList = tokenList)),
totalAccounts = 1,
totalArchivedAccounts = 0,
totalFiatBalance = TotalFiatBalance.Loading,
totalFiatBalance = totalFiatBalance,
sortType = TokensSortType.NONE,
groupType = TokensGroupType.NONE,
)