Updated on 2026-08-14

This commit is contained in:
Tangem 2025-09-29 15:59:01 +07:00
parent 71e8f40909
commit 3b65bef8a8
17 changed files with 346 additions and 128 deletions

View file

@ -4,6 +4,7 @@ import arrow.core.getOrElse
import com.tangem.common.ui.expressStatus.ExpressStatusBottomSheetConfig
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.domain.models.PortfolioId
import com.tangem.domain.models.account.Account
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.wallet.UserWallet
@ -44,9 +45,9 @@ internal interface WalletContentClickIntents {
fun onDismissMarketsOnboarding()
fun onTokenItemClick(currencyStatus: CryptoCurrencyStatus)
fun onTokenItemClick(portfolioId: PortfolioId, currencyStatus: CryptoCurrencyStatus)
fun onTokenItemLongClick(cryptoCurrencyStatus: CryptoCurrencyStatus)
fun onTokenItemLongClick(portfolioId: PortfolioId, cryptoCurrencyStatus: CryptoCurrencyStatus)
fun onAccountExpandClick(account: Account)
@ -137,13 +138,13 @@ internal class WalletContentClickIntentsImplementor @Inject constructor(
}
}
override fun onTokenItemClick(currencyStatus: CryptoCurrencyStatus) {
router.openTokenDetails(stateHolder.getSelectedWalletId(), currencyStatus)
override fun onTokenItemClick(portfolioId: PortfolioId, currencyStatus: CryptoCurrencyStatus) {
router.openTokenDetails(portfolioId, currencyStatus)
}
override fun onTokenItemLongClick(cryptoCurrencyStatus: CryptoCurrencyStatus) {
override fun onTokenItemLongClick(portfolioId: PortfolioId, cryptoCurrencyStatus: CryptoCurrencyStatus) {
modelScope.launch(dispatchers.main) {
val userWalletId = stateHolder.getSelectedWalletId()
val userWalletId = portfolioId.userWalletId
val userWallet = getUserWalletUseCase(userWalletId).getOrElse {
Timber.e(
"""
@ -159,7 +160,7 @@ internal class WalletContentClickIntentsImplementor @Inject constructor(
getCryptoCurrencyActionsUseCase(userWallet = userWallet, cryptoCurrencyStatus = cryptoCurrencyStatus)
.take(count = 1)
.collectLatest {
showActionsBottomSheet(it, userWallet)
showActionsBottomSheet(it, userWallet, portfolioId)
}
}
}
@ -174,12 +175,17 @@ internal class WalletContentClickIntentsImplementor @Inject constructor(
accountDependencies.expandedAccountsHolder.collapseAccount(userWalletId, account.accountId)
}
private fun showActionsBottomSheet(tokenActionsState: TokenActionsState, userWallet: UserWallet) {
private fun showActionsBottomSheet(
tokenActionsState: TokenActionsState,
userWallet: UserWallet,
portfolioId: PortfolioId,
) {
stateHolder.showBottomSheet(
ActionsBottomSheetConfig(
actions = MultiWalletCurrencyActionsConverter(
userWallet = userWallet,
clickIntents = currencyActionsClickIntents,
portfolioId = portfolioId,
).convert(tokenActionsState),
),
userWallet.walletId,

View file

@ -29,6 +29,7 @@ import com.tangem.domain.core.utils.lceError
import com.tangem.domain.demo.IsDemoCardUseCase
import com.tangem.domain.exchange.RampStateManager
import com.tangem.domain.markets.TokenMarketParams
import com.tangem.domain.models.PortfolioId
import com.tangem.domain.models.ReceiveAddressModel
import com.tangem.domain.models.TokenReceiveConfig
import com.tangem.domain.models.currency.CryptoCurrency
@ -44,8 +45,6 @@ import com.tangem.domain.redux.ReduxStateHolder
import com.tangem.domain.staking.model.stakekit.Yield
import com.tangem.domain.tokens.GetSingleCryptoCurrencyStatusUseCase
import com.tangem.domain.tokens.GetViewedTokenReceiveWarningUseCase
import com.tangem.domain.tokens.IsCryptoCurrencyCoinCouldHideUseCase
import com.tangem.domain.tokens.RemoveCurrencyUseCase
import com.tangem.domain.tokens.legacy.TradeCryptoAction
import com.tangem.domain.tokens.model.ScenarioUnavailabilityReason
import com.tangem.domain.tokens.model.analytics.TokenReceiveAnalyticsEvent
@ -67,6 +66,7 @@ import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletTokensListState
import com.tangem.feature.wallet.presentation.wallet.state.transformers.CloseBottomSheetTransformer
import com.tangem.feature.wallet.presentation.wallet.state.utils.WalletEventSender
import com.tangem.feature.wallet.presentation.wallet.utils.WalletFeatureUseCasesFacade
import com.tangem.features.tokenreceive.TokenReceiveFeatureToggle
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.collections.immutable.toImmutableList
@ -78,29 +78,41 @@ import javax.inject.Inject
interface WalletCurrencyActionsClickIntents {
fun onSendClick(cryptoCurrencyStatus: CryptoCurrencyStatus, unavailabilityReason: ScenarioUnavailabilityReason)
fun onSellClick(cryptoCurrencyStatus: CryptoCurrencyStatus, unavailabilityReason: ScenarioUnavailabilityReason)
fun onBuyClick(cryptoCurrencyStatus: CryptoCurrencyStatus, unavailabilityReason: ScenarioUnavailabilityReason)
fun onSwapClick(
fun onSendClick(
portfolioId: PortfolioId,
cryptoCurrencyStatus: CryptoCurrencyStatus,
userWalletId: UserWalletId,
unavailabilityReason: ScenarioUnavailabilityReason,
)
fun onReceiveClick(cryptoCurrencyStatus: CryptoCurrencyStatus, event: AnalyticsEvent? = null)
fun onSellClick(cryptoCurrencyStatus: CryptoCurrencyStatus, unavailabilityReason: ScenarioUnavailabilityReason)
fun onStakeClick(cryptoCurrencyStatus: CryptoCurrencyStatus, yield: Yield?)
fun onBuyClick(
portfolioId: PortfolioId,
cryptoCurrencyStatus: CryptoCurrencyStatus,
unavailabilityReason: ScenarioUnavailabilityReason,
)
fun onSwapClick(
cryptoCurrencyStatus: CryptoCurrencyStatus,
portfolioId: PortfolioId,
unavailabilityReason: ScenarioUnavailabilityReason,
)
fun onReceiveClick(
portfolioId: PortfolioId,
cryptoCurrencyStatus: CryptoCurrencyStatus,
event: AnalyticsEvent? = null,
)
fun onStakeClick(portfolioId: PortfolioId, cryptoCurrencyStatus: CryptoCurrencyStatus, yield: Yield?)
fun onCopyAddressLongClick(cryptoCurrencyStatus: CryptoCurrencyStatus): TextReference?
fun onCopyAddressClick(cryptoCurrencyStatus: CryptoCurrencyStatus)
fun onCopyAddressClick(portfolioId: PortfolioId, cryptoCurrencyStatus: CryptoCurrencyStatus)
fun onHideTokensClick(cryptoCurrencyStatus: CryptoCurrencyStatus)
fun onHideTokensClick(portfolioId: PortfolioId, cryptoCurrencyStatus: CryptoCurrencyStatus)
fun onPerformHideToken(cryptoCurrencyStatus: CryptoCurrencyStatus)
fun onPerformHideToken(portfolioId: PortfolioId, cryptoCurrencyStatus: CryptoCurrencyStatus)
fun onExploreClick()
@ -122,8 +134,7 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
private val walletManagersFacade: WalletManagersFacade,
private val isDemoCardUseCase: IsDemoCardUseCase,
private val getSingleCryptoCurrencyStatusUseCase: GetSingleCryptoCurrencyStatusUseCase,
private val isCryptoCurrencyCoinCouldHide: IsCryptoCurrencyCoinCouldHideUseCase,
private val removeCurrencyUseCase: RemoveCurrencyUseCase,
private val useCasesFacade: WalletFeatureUseCasesFacade,
private val getExploreUrlUseCase: GetExploreUrlUseCase,
private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
private val getStoryContentUseCase: GetStoryContentUseCase,
@ -141,11 +152,10 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
) : BaseWalletClickIntents(), WalletCurrencyActionsClickIntents {
override fun onSendClick(
portfolioId: PortfolioId,
cryptoCurrencyStatus: CryptoCurrencyStatus,
unavailabilityReason: ScenarioUnavailabilityReason,
) {
val userWallet = getSelectedWalletSyncUseCase.unwrap() ?: return
analyticsEventHandler.send(
event = TokenScreenAnalyticsEvent.ButtonWithParams.ButtonSend(
token = cryptoCurrencyStatus.currency.symbol,
@ -156,17 +166,25 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
if (handleUnavailabilityReason(unavailabilityReason)) return
stateHolder.update(CloseBottomSheetTransformer(userWalletId = userWallet.walletId))
stateHolder.update(CloseBottomSheetTransformer(userWalletId = portfolioId.userWalletId))
val route = AppRoute.Send(
currency = cryptoCurrencyStatus.currency,
userWalletId = userWallet.walletId,
portfolioId = portfolioId,
)
appRouter.push(route)
}
override fun onReceiveClick(cryptoCurrencyStatus: CryptoCurrencyStatus, event: AnalyticsEvent?) {
val userWalletId = stateHolder.getSelectedWalletId()
override fun onReceiveClick(
portfolioId: PortfolioId,
cryptoCurrencyStatus: CryptoCurrencyStatus,
event: AnalyticsEvent?,
) {
val userWalletId = portfolioId.userWalletId
if (portfolioId is PortfolioId.Account) {
// todo account find address
TODO("account")
}
analyticsEventHandler.send(
event = TokenScreenAnalyticsEvent.ButtonWithParams.ButtonReceive(
@ -240,7 +258,12 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
)
}
override fun onCopyAddressClick(cryptoCurrencyStatus: CryptoCurrencyStatus) {
override fun onCopyAddressClick(portfolioId: PortfolioId, cryptoCurrencyStatus: CryptoCurrencyStatus) {
val userWalletId = portfolioId.userWalletId
if (portfolioId is PortfolioId.Account) {
// todo account find address
TODO("account")
}
analyticsEventHandler.send(
event = TokenReceiveNewAnalyticsEvent.ButtonCopyAddress(
token = cryptoCurrencyStatus.currency.symbol,
@ -251,10 +274,10 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
modelScope.launch(dispatchers.main) {
walletManagersFacade.getDefaultAddress(
userWalletId = stateHolder.getSelectedWalletId(),
userWalletId = userWalletId,
network = cryptoCurrencyStatus.currency.network,
)?.let { address ->
stateHolder.update(CloseBottomSheetTransformer(userWalletId = stateHolder.getSelectedWalletId()))
stateHolder.update(CloseBottomSheetTransformer(userWalletId = userWalletId))
clipboardManager.setText(text = address, isSensitive = true)
walletEventSender.send(event = WalletEvent.CopyAddress)
@ -262,7 +285,7 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
}
}
override fun onHideTokensClick(cryptoCurrencyStatus: CryptoCurrencyStatus) {
override fun onHideTokensClick(portfolioId: PortfolioId, cryptoCurrencyStatus: CryptoCurrencyStatus) {
analyticsEventHandler.send(
event = TokenScreenAnalyticsEvent.ButtonRemoveToken(cryptoCurrencyStatus.currency.symbol),
)
@ -270,18 +293,20 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
modelScope.launch(dispatchers.main) {
walletEventSender.send(
event = WalletEvent.ShowAlert(
state = getHideTokeAlertConfig(stateHolder.getSelectedWalletId(), cryptoCurrencyStatus),
state = getHideTokeAlertConfig(portfolioId, cryptoCurrencyStatus),
),
)
}
}
private suspend fun getHideTokeAlertConfig(
userWalletId: UserWalletId,
portfolioId: PortfolioId,
cryptoCurrencyStatus: CryptoCurrencyStatus,
): WalletAlertState.DefaultAlert {
val currency = cryptoCurrencyStatus.currency
return if (currency is CryptoCurrency.Coin && !isCryptoCurrencyCoinCouldHide(userWalletId, currency)) {
val isCryptoCurrencyCoinCouldHide = currency is CryptoCurrency.Coin &&
!useCasesFacade.isCryptoCurrencyCoinCouldHide(portfolioId = portfolioId, cryptoCurrencyCoin = currency)
return if (isCryptoCurrencyCoinCouldHide) {
WalletAlertState.DefaultAlert(
title = resourceReference(
id = R.string.token_details_unable_hide_alert_title,
@ -306,16 +331,14 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
formatArgs = WrappedList(listOf(cryptoCurrencyStatus.currency.name)),
),
message = resourceReference(R.string.token_details_hide_alert_message),
onConfirmClick = { onPerformHideToken(cryptoCurrencyStatus) },
onConfirmClick = { onPerformHideToken(portfolioId, cryptoCurrencyStatus) },
)
}
}
override fun onPerformHideToken(cryptoCurrencyStatus: CryptoCurrencyStatus) {
val userWalletId = stateHolder.getSelectedWalletId()
override fun onPerformHideToken(portfolioId: PortfolioId, cryptoCurrencyStatus: CryptoCurrencyStatus) {
modelScope.launch(dispatchers.io) {
removeCurrencyUseCase(userWalletId, cryptoCurrencyStatus.currency)
useCasesFacade.removeCurrencyUseCase(portfolioId, cryptoCurrencyStatus.currency)
.fold(
ifLeft = {
walletEventSender.send(
@ -323,7 +346,7 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
)
},
ifRight = {
stateHolder.update(CloseBottomSheetTransformer(userWalletId = userWalletId))
stateHolder.update(CloseBottomSheetTransformer(userWalletId = portfolioId.userWalletId))
},
)
}
@ -356,11 +379,10 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
}
override fun onBuyClick(
portfolioId: PortfolioId,
cryptoCurrencyStatus: CryptoCurrencyStatus,
unavailabilityReason: ScenarioUnavailabilityReason,
) {
val userWallet = getSelectedWalletSyncUseCase.unwrap() ?: return
analyticsEventHandler.send(
event = TokenScreenAnalyticsEvent.ButtonWithParams.ButtonBuy(
token = cryptoCurrencyStatus.currency.symbol,
@ -373,7 +395,7 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
appRouter.push(
AppRoute.Onramp(
userWalletId = userWallet.walletId,
portfolioId = portfolioId,
currency = cryptoCurrencyStatus.currency,
source = OnrampSource.TOKEN_LONG_TAP,
),
@ -382,7 +404,7 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
override fun onSwapClick(
cryptoCurrencyStatus: CryptoCurrencyStatus,
userWalletId: UserWalletId,
portfolioId: PortfolioId,
unavailabilityReason: ScenarioUnavailabilityReason,
) {
analyticsEventHandler.send(
@ -398,7 +420,7 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
appRouter.push(
AppRoute.Swap(
currencyFrom = cryptoCurrencyStatus.currency,
userWalletId = userWalletId,
portfolioId = portfolioId,
screenSource = AnalyticsParam.ScreensSources.LongTap.value,
),
)
@ -440,17 +462,15 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
}
}
override fun onStakeClick(cryptoCurrencyStatus: CryptoCurrencyStatus, yield: Yield?) {
val userWallet = getSelectedWalletSyncUseCase.unwrap() ?: return
stateHolder.update(CloseBottomSheetTransformer(userWalletId = userWallet.walletId))
override fun onStakeClick(portfolioId: PortfolioId, cryptoCurrencyStatus: CryptoCurrencyStatus, yield: Yield?) {
stateHolder.update(CloseBottomSheetTransformer(userWalletId = portfolioId.userWalletId))
modelScope.launch {
val userWalletId = stateHolder.getSelectedWalletId()
val cryptoCurrency = cryptoCurrencyStatus.currency
appRouter.push(
AppRoute.Staking(
userWalletId = userWalletId,
portfolioId = portfolioId,
cryptoCurrencyId = cryptoCurrency.id,
yieldId = yield?.id ?: return@launch,
),

View file

@ -7,6 +7,7 @@ import com.tangem.common.routing.AppRoute.ManageTokens.Source
import com.tangem.common.routing.AppRouter
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.navigation.url.UrlOpener
import com.tangem.domain.models.PortfolioId
import com.tangem.domain.models.TokenReceiveConfig
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.scan.ScanResponse
@ -64,12 +65,12 @@ internal class DefaultWalletRouter @Inject constructor(
urlOpener.openUrl(url)
}
override fun openTokenDetails(userWalletId: UserWalletId, currencyStatus: CryptoCurrencyStatus) {
override fun openTokenDetails(portfolioId: PortfolioId, currencyStatus: CryptoCurrencyStatus) {
val networkAddress = currencyStatus.value.networkAddress
if (networkAddress != null && networkAddress.defaultAddress.value.isNotEmpty()) {
router.push(
AppRoute.CurrencyDetails(
userWalletId = userWalletId,
portfolioId = portfolioId,
currency = currencyStatus.currency,
),
)

View file

@ -2,6 +2,7 @@ package com.tangem.feature.wallet.presentation.router
import androidx.compose.runtime.Stable
import com.arkivanov.decompose.router.slot.SlotNavigation
import com.tangem.domain.models.PortfolioId
import com.tangem.domain.models.TokenReceiveConfig
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.scan.ScanResponse
@ -39,7 +40,7 @@ internal interface InnerWalletRouter {
fun openUrl(url: String)
/** Open token details screen */
fun openTokenDetails(userWalletId: UserWalletId, currencyStatus: CryptoCurrencyStatus)
fun openTokenDetails(portfolioId: PortfolioId, currencyStatus: CryptoCurrencyStatus)
/** Open stories screen */
fun openStoriesScreen()

View file

@ -12,6 +12,7 @@ import com.tangem.domain.txhistory.usecase.GetTxHistoryItemsUseCase
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.wallets.usecase.ShouldSaveUserWalletsUseCase
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.analytics.utils.WalletWarningsAnalyticsSender
import com.tangem.feature.wallet.presentation.wallet.domain.GetSingleWalletWarningsFactory
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController
@ -35,6 +36,7 @@ internal class SingleWalletContentLoader(
private val shouldSaveUserWalletsUseCase: ShouldSaveUserWalletsUseCase,
private val analyticsEventHandler: AnalyticsEventHandler,
private val walletWarningsAnalyticsSender: WalletWarningsAnalyticsSender,
private val accountDependencies: AccountDependencies,
) : WalletContentLoader(id = userWallet.walletId) {
override fun create(): List<WalletSubscriber> {
@ -53,6 +55,7 @@ internal class SingleWalletContentLoader(
clickIntents = clickIntents,
getSingleCryptoCurrencyStatusUseCase = getSingleCryptoCurrencyStatusUseCase,
getCryptoCurrencyActionsUseCase = getCryptoCurrencyActionsUseCase,
accountDependencies = accountDependencies,
),
SingleWalletNotificationsSubscriber(
userWallet = userWallet,

View file

@ -13,6 +13,7 @@ import com.tangem.domain.txhistory.usecase.GetTxHistoryItemsUseCase
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.wallets.usecase.ShouldSaveUserWalletsUseCase
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.analytics.utils.WalletWarningsAnalyticsSender
import com.tangem.feature.wallet.presentation.wallet.domain.GetSingleWalletWarningsFactory
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController
@ -34,12 +35,14 @@ internal class SingleWalletContentLoaderFactory @Inject constructor(
private val shouldSaveUserWalletsUseCase: ShouldSaveUserWalletsUseCase,
private val analyticsEventHandler: AnalyticsEventHandler,
private val walletWarningsAnalyticsSender: WalletWarningsAnalyticsSender,
private val accountDependencies: AccountDependencies,
) {
fun create(userWallet: UserWallet.Cold, clickIntents: WalletClickIntents, isRefresh: Boolean): WalletContentLoader {
return SingleWalletContentLoader(
userWallet = userWallet,
clickIntents = clickIntents,
accountDependencies = accountDependencies,
isRefresh = isRefresh,
stateHolder = stateHolder,
getSingleCryptoCurrencyStatusUseCase = getSingleCryptoCurrencyStatusUseCase,

View file

@ -1,6 +1,7 @@
package com.tangem.feature.wallet.presentation.wallet.state.transformers
import com.tangem.domain.card.common.util.cardTypesResolver
import com.tangem.domain.models.PortfolioId
import com.tangem.domain.tokens.model.ScenarioUnavailabilityReason
import com.tangem.domain.tokens.model.TokenActionsState
import com.tangem.domain.models.wallet.UserWallet
@ -14,6 +15,7 @@ import timber.log.Timber
internal class SetCryptoCurrencyActionsTransformer(
private val tokenActionsState: TokenActionsState,
private val userWallet: UserWallet,
private val portfolioId: PortfolioId,
private val clickIntents: WalletClickIntents,
) : WalletStateTransformer(userWallet.walletId) {
@ -48,6 +50,7 @@ internal class SetCryptoCurrencyActionsTransformer(
dimContent = action.unavailabilityReason != ScenarioUnavailabilityReason.None,
onClick = {
clickIntents.onBuyClick(
portfolioId = portfolioId,
cryptoCurrencyStatus = cryptoCurrencyStatus,
unavailabilityReason = action.unavailabilityReason,
)
@ -59,7 +62,7 @@ internal class SetCryptoCurrencyActionsTransformer(
enabled = true,
dimContent = action.unavailabilityReason != ScenarioUnavailabilityReason.None,
onClick = {
clickIntents.onReceiveClick(cryptoCurrencyStatus = cryptoCurrencyStatus)
clickIntents.onReceiveClick(portfolioId, cryptoCurrencyStatus = cryptoCurrencyStatus)
},
onLongClick = {
clickIntents.onCopyAddressLongClick(cryptoCurrencyStatus = cryptoCurrencyStatus)
@ -84,6 +87,7 @@ internal class SetCryptoCurrencyActionsTransformer(
dimContent = action.unavailabilityReason != ScenarioUnavailabilityReason.None,
onClick = {
clickIntents.onSendClick(
portfolioId = portfolioId,
cryptoCurrencyStatus = cryptoCurrencyStatus,
unavailabilityReason = action.unavailabilityReason,
)

View file

@ -9,6 +9,7 @@ import com.tangem.core.ui.format.bigdecimal.crypto
import com.tangem.core.ui.format.bigdecimal.fiat
import com.tangem.core.ui.format.bigdecimal.format
import com.tangem.domain.card.common.util.getCardsCount
import com.tangem.domain.models.PortfolioId
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.visa.exception.RefreshTokenExpiredException
@ -148,7 +149,11 @@ internal class SetVisaInfoTransformer(
enabled = true,
dimContent = false,
onClick = {
clickIntents.onReceiveClick(cryptoCurrencyStatus, MainScreenAnalyticsEvent.ButtonReceive)
clickIntents.onReceiveClick(
portfolioId = PortfolioId(userWalletId), // todo account Visa use Main account?
cryptoCurrencyStatus = cryptoCurrencyStatus,
event = MainScreenAnalyticsEvent.ButtonReceive,
)
},
onLongClick = {
clickIntents.onCopyAddressLongClick(cryptoCurrencyStatus)

View file

@ -1,11 +1,13 @@
package com.tangem.feature.wallet.presentation.wallet.state.transformers
import com.tangem.domain.account.models.AccountStatusList
import com.tangem.domain.models.PortfolioId
import com.tangem.domain.models.account.AccountId
import com.tangem.domain.models.tokenlist.TokenList
sealed interface TokenConverterParams {
data class Wallet(
val portfolioId: PortfolioId,
val tokenList: TokenList,
) : TokenConverterParams

View file

@ -3,6 +3,7 @@ package com.tangem.feature.wallet.presentation.wallet.state.transformers.convert
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.domain.card.common.util.cardTypesResolver
import com.tangem.domain.models.PortfolioId
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.tokens.model.ScenarioUnavailabilityReason
@ -17,6 +18,7 @@ import kotlinx.collections.immutable.toImmutableList
internal class MultiWalletCurrencyActionsConverter(
private val userWallet: UserWallet,
private val portfolioId: PortfolioId,
private val clickIntents: WalletCurrencyActionsClickIntents,
) : Converter<TokenActionsState, ImmutableList<TokenActionButtonConfig>> {
@ -46,7 +48,7 @@ internal class MultiWalletCurrencyActionsConverter(
if (actionsState is TokenActionsState.ActionState.Send && cryptoCurrencyStatus.value.amount.isNullOrZero()) {
return null
}
val noneReason = ScenarioUnavailabilityReason.None
val title: TextReference
val icon: Int
val action: () -> Unit
@ -54,27 +56,27 @@ internal class MultiWalletCurrencyActionsConverter(
is TokenActionsState.ActionState.Buy -> {
title = resourceReference(R.string.common_buy)
icon = R.drawable.ic_plus_24
action = { clickIntents.onBuyClick(cryptoCurrencyStatus, ScenarioUnavailabilityReason.None) }
action = { clickIntents.onBuyClick(portfolioId, cryptoCurrencyStatus, noneReason) }
}
is TokenActionsState.ActionState.Receive -> {
title = resourceReference(R.string.common_receive)
icon = R.drawable.ic_arrow_down_24
action = { clickIntents.onReceiveClick(cryptoCurrencyStatus) }
action = { clickIntents.onReceiveClick(portfolioId, cryptoCurrencyStatus) }
}
is TokenActionsState.ActionState.Stake -> {
title = resourceReference(R.string.common_stake)
icon = R.drawable.ic_staking_24
action = { clickIntents.onStakeClick(cryptoCurrencyStatus, actionsState.yield) }
action = { clickIntents.onStakeClick(portfolioId, cryptoCurrencyStatus, actionsState.yield) }
}
is TokenActionsState.ActionState.Sell -> {
title = resourceReference(R.string.common_sell)
icon = R.drawable.ic_currency_24
action = { clickIntents.onSellClick(cryptoCurrencyStatus, ScenarioUnavailabilityReason.None) }
action = { clickIntents.onSellClick(cryptoCurrencyStatus, noneReason) }
}
is TokenActionsState.ActionState.Send -> {
title = resourceReference(R.string.common_send)
icon = R.drawable.ic_arrow_up_24
action = { clickIntents.onSendClick(cryptoCurrencyStatus, ScenarioUnavailabilityReason.None) }
action = { clickIntents.onSendClick(portfolioId, cryptoCurrencyStatus, noneReason) }
}
is TokenActionsState.ActionState.Swap -> {
title = resourceReference(R.string.swapping_swap_action)
@ -82,20 +84,20 @@ internal class MultiWalletCurrencyActionsConverter(
action = {
clickIntents.onSwapClick(
cryptoCurrencyStatus = cryptoCurrencyStatus,
userWalletId = userWallet.walletId,
unavailabilityReason = ScenarioUnavailabilityReason.None,
portfolioId = portfolioId,
unavailabilityReason = noneReason,
)
}
}
is TokenActionsState.ActionState.CopyAddress -> {
title = resourceReference(R.string.common_copy_address)
icon = R.drawable.ic_copy_24
action = { clickIntents.onCopyAddressClick(cryptoCurrencyStatus) }
action = { clickIntents.onCopyAddressClick(portfolioId, cryptoCurrencyStatus) }
}
is TokenActionsState.ActionState.HideToken -> {
title = resourceReference(R.string.token_details_hide_token)
icon = R.drawable.ic_hide_24
action = { clickIntents.onHideTokensClick(cryptoCurrencyStatus) }
action = { clickIntents.onHideTokensClick(portfolioId, cryptoCurrencyStatus) }
}
is TokenActionsState.ActionState.Analytics -> {
title = resourceReference(R.string.common_analytics)
@ -109,7 +111,7 @@ internal class MultiWalletCurrencyActionsConverter(
iconResId = icon,
onClick = action,
isWarning = actionsState is TokenActionsState.ActionState.HideToken,
enabled = actionsState.unavailabilityReason == ScenarioUnavailabilityReason.None,
enabled = actionsState.unavailabilityReason == noneReason,
)
}
}

View file

@ -8,8 +8,10 @@ import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.wrappedList
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.card.common.util.cardTypesResolver
import com.tangem.domain.models.PortfolioId
import com.tangem.domain.models.TotalFiatBalance
import com.tangem.domain.models.account.Account
import com.tangem.domain.models.account.AccountId
import com.tangem.domain.models.account.AccountStatus
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.tokenlist.TokenList
@ -34,34 +36,46 @@ internal class TokenListStateConverter(
private val clickIntents: WalletClickIntents,
) : Converter<WalletTokensListState, WalletTokensListState> {
private val tokenStatusConverter = when (params) {
is TokenConverterParams.Account, // todo account Click with accountId param
is TokenConverterParams.Wallet,
-> TokenItemStateConverter(
appCurrency = appCurrency,
onItemClick = { _, status -> clickIntents.onTokenItemClick(status) },
onItemLongClick = { _, status -> clickIntents.onTokenItemLongClick(status) },
)
}
private val onTokenClick: (accountId: AccountId?, currencyStatus: CryptoCurrencyStatus) -> Unit =
{ accountId, currencyStatus ->
val id = accountId?.let { PortfolioId(accountId) } ?: PortfolioId(selectedWallet.walletId)
clickIntents.onTokenItemClick(id, currencyStatus)
}
private val onTokenLongClick: (accountId: AccountId?, currencyStatus: CryptoCurrencyStatus) -> Unit =
{ accountId, currencyStatus ->
val id = accountId?.let { PortfolioId(accountId) } ?: PortfolioId(selectedWallet.walletId)
clickIntents.onTokenItemLongClick(id, currencyStatus)
}
private fun tokenStatusConverter(accountId: AccountId? = null) = TokenItemStateConverter(
appCurrency = appCurrency,
onItemClick = { _, status -> onTokenClick(accountId, status) },
onItemLongClick = { _, status -> onTokenLongClick(accountId, status) },
)
override fun convert(value: WalletTokensListState): WalletTokensListState {
return when (params) {
is TokenConverterParams.Account -> convertAccountList(params)
is TokenConverterParams.Wallet -> convertTokenList(params.tokenList)
is TokenConverterParams.Wallet -> convertTokenList(
tokenConverter = tokenStatusConverter((params.portfolioId as? PortfolioId.Account)?.accountId),
tokenList = params.tokenList,
)
}
}
private fun convertTokenList(tokenList: TokenList): WalletTokensListState = when (tokenList) {
is TokenList.Empty -> WalletTokensListState.Empty
is TokenList.GroupedByNetwork -> WalletTokensListState.ContentState.Content(
items = tokenList.toGroupedItems(),
organizeTokensButtonConfig = getOrganizeTokensButtonState(tokenList = tokenList),
)
is TokenList.Ungrouped -> WalletTokensListState.ContentState.Content(
items = tokenList.toUngroupedItems(),
organizeTokensButtonConfig = getOrganizeTokensButtonState(tokenList = tokenList),
)
}
private fun convertTokenList(tokenConverter: TokenItemStateConverter, tokenList: TokenList): WalletTokensListState =
when (tokenList) {
is TokenList.Empty -> WalletTokensListState.Empty
is TokenList.GroupedByNetwork -> WalletTokensListState.ContentState.Content(
items = tokenList.toGroupedItems(tokenConverter),
organizeTokensButtonConfig = getOrganizeTokensButtonState(tokenList = tokenList),
)
is TokenList.Ungrouped -> WalletTokensListState.ContentState.Content(
items = tokenList.toUngroupedItems(tokenConverter),
organizeTokensButtonConfig = getOrganizeTokensButtonState(tokenList = tokenList),
)
}
private fun convertAccountList(params: TokenConverterParams.Account): WalletTokensListState {
val accountList = params.accountList
@ -85,7 +99,8 @@ internal class TokenListStateConverter(
onItemClick = onItemClick,
)
val accountItem = converter.convert(tokenList.totalFiatBalance)
val tokensListState = convertTokenList(tokenList)
val tokenConverter = tokenStatusConverter(account.accountId)
val tokensListState = convertTokenList(tokenConverter, tokenList)
val items = when (tokensListState) {
is WalletTokensListState.ContentState.PortfolioContent -> tokensListState.items
is WalletTokensListState.ContentState.Content -> tokensListState.items
@ -112,19 +127,26 @@ internal class TokenListStateConverter(
)
}
private fun TokenList.GroupedByNetwork.toGroupedItems(): PersistentList<TokensListItemUM> {
private fun TokenList.GroupedByNetwork.toGroupedItems(
tokenConverter: TokenItemStateConverter,
): PersistentList<TokensListItemUM> {
return groups.fold(initial = persistentListOf()) { acc, group ->
acc.mutate { it.addGroup(group) }
acc.mutate { it.addGroup(tokenConverter, group) }
}
}
private fun TokenList.Ungrouped.toUngroupedItems(): PersistentList<TokensListItemUM> {
private fun TokenList.Ungrouped.toUngroupedItems(
tokenConverter: TokenItemStateConverter,
): PersistentList<TokensListItemUM> {
return currencies.fold(initial = persistentListOf()) { acc, token ->
acc.mutate { it.addToken(token) }
acc.mutate { it.addToken(tokenConverter, token) }
}
}
private fun MutableList<TokensListItemUM>.addGroup(group: NetworkGroup): List<TokensListItemUM> {
private fun MutableList<TokensListItemUM>.addGroup(
tokenConverter: TokenItemStateConverter,
group: NetworkGroup,
): List<TokensListItemUM> {
val groupTitle = TokensListItemUM.GroupTitle(
id = group.network.hashCode(),
text = resourceReference(
@ -134,13 +156,16 @@ internal class TokenListStateConverter(
)
add(groupTitle)
group.currencies.forEach { token -> addToken(token) }
group.currencies.forEach { token -> addToken(tokenConverter, token) }
return this
}
private fun MutableList<TokensListItemUM>.addToken(token: CryptoCurrencyStatus): List<TokensListItemUM> {
val tokenItemState = tokenStatusConverter.convert(token)
private fun MutableList<TokensListItemUM>.addToken(
tokenConverter: TokenItemStateConverter,
token: CryptoCurrencyStatus,
): List<TokensListItemUM> {
val tokenItemState = tokenConverter.convert(token)
add(TokensListItemUM.Token(tokenItemState))
return this

View file

@ -7,6 +7,7 @@ import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.core.lce.Lce
import com.tangem.domain.core.lce.LceFlow
import com.tangem.domain.core.utils.getOrElse
import com.tangem.domain.models.PortfolioId
import com.tangem.domain.models.account.AccountStatus
import com.tangem.domain.models.tokenlist.TokenList
import com.tangem.domain.models.wallet.UserWallet
@ -72,13 +73,16 @@ internal abstract class BasicTokenListSubscriber : WalletSubscriber() {
coroutineScope.launch { startCheck(maybeTokenList) }
},
flow2 = appCurrencyFlow(),
transform = { maybeTokenList, appCurrency -> singleAccountTransform(maybeTokenList, appCurrency) },
transform = { maybeTokenList, appCurrency ->
singleAccountTransform(maybeTokenList, appCurrency, PortfolioId(userWallet.walletId))
},
)
}
private suspend fun singleAccountTransform(
maybeTokenList: Lce<TokenListError, TokenList>,
appCurrency: AppCurrency,
portfolioId: PortfolioId,
) {
val tokenList = maybeTokenList.getOrElse(
ifLoading = { maybeContent ->
@ -102,8 +106,7 @@ internal abstract class BasicTokenListSubscriber : WalletSubscriber() {
return
},
)
updateContent(TokenConverterParams.Wallet(tokenList), appCurrency)
updateContent(TokenConverterParams.Wallet(portfolioId, tokenList), appCurrency)
walletWithFundsChecker.check(tokenList)
}
@ -150,7 +153,7 @@ internal abstract class BasicTokenListSubscriber : WalletSubscriber() {
}
suspend fun singleAccountTransform(maybeTokenList: Lce<TokenListError, TokenList>) =
this.singleAccountTransform(maybeTokenList, appCurrency)
this.singleAccountTransform(maybeTokenList, appCurrency, PortfolioId(mainAccount.account.accountId))
when {
!isAccountMode -> when (mainAccount.tokenList.flattenCurrencies().isEmpty()) {

View file

@ -1,10 +1,13 @@
package com.tangem.feature.wallet.presentation.wallet.subscribers
import com.tangem.domain.models.PortfolioId
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.tokens.GetCryptoCurrencyActionsUseCase
import com.tangem.domain.tokens.GetSingleCryptoCurrencyStatusUseCase
import com.tangem.domain.tokens.model.TokenActionsState
import com.tangem.domain.models.wallet.UserWallet
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.domain.collectLatest
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController
import com.tangem.feature.wallet.presentation.wallet.state.transformers.SetCryptoCurrencyActionsTransformer
@ -17,28 +20,39 @@ internal class SingleWalletButtonsSubscriber(
private val clickIntents: WalletClickIntents,
private val getSingleCryptoCurrencyStatusUseCase: GetSingleCryptoCurrencyStatusUseCase,
private val getCryptoCurrencyActionsUseCase: GetCryptoCurrencyActionsUseCase,
private val accountDependencies: AccountDependencies,
) : WalletSubscriber() {
override fun create(coroutineScope: CoroutineScope): Flow<TokenActionsState> {
return channelFlow {
getSingleCryptoCurrencyStatusUseCase.collectLatest(userWalletId = userWallet.walletId) { status ->
getCryptoCurrencyActionsUseCase(userWallet = userWallet, cryptoCurrencyStatus = status)
.conflate()
.distinctUntilChanged()
.firstOrNull()
getCryptoCurrencyActionsUseCase(userWallet = userWallet, status = status)
?.let { send(it) }
}
}
.onEach(::updateContent)
.onEach { actions ->
val portfolioId = when (accountDependencies.accountsFeatureToggles.isFeatureEnabled) {
true -> TODO("account") // get main account id for single wallet
false -> PortfolioId(userWallet.walletId)
}
updateContent(actions, portfolioId)
}
}
private fun updateContent(tokenActionsState: TokenActionsState) {
private fun updateContent(tokenActionsState: TokenActionsState, portfolioId: PortfolioId) {
stateHolder.update(
SetCryptoCurrencyActionsTransformer(
tokenActionsState = tokenActionsState,
userWallet = userWallet,
clickIntents = clickIntents,
portfolioId = portfolioId,
),
)
}
private suspend fun getCryptoCurrencyActionsUseCase(userWallet: UserWallet, status: CryptoCurrencyStatus) =
this.getCryptoCurrencyActionsUseCase(userWallet = userWallet, cryptoCurrencyStatus = status)
.conflate()
.distinctUntilChanged()
.firstOrNull()
}

View file

@ -0,0 +1,24 @@
package com.tangem.feature.wallet.presentation.wallet.utils
import com.tangem.domain.models.PortfolioId
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.tokens.IsCryptoCurrencyCoinCouldHideUseCase
import com.tangem.domain.tokens.RemoveCurrencyUseCase
import javax.inject.Inject
class WalletFeatureUseCasesFacade @Inject constructor(
private val isCryptoCurrencyCoinCouldHide: IsCryptoCurrencyCoinCouldHideUseCase,
private val removeCurrencyUseCase: RemoveCurrencyUseCase,
) {
suspend fun isCryptoCurrencyCoinCouldHide(portfolioId: PortfolioId, cryptoCurrencyCoin: CryptoCurrency.Coin) =
when (portfolioId) {
is PortfolioId.Account -> TODO("account")
is PortfolioId.Wallet -> isCryptoCurrencyCoinCouldHide(portfolioId.userWalletId, cryptoCurrencyCoin)
}
suspend fun removeCurrencyUseCase(portfolioId: PortfolioId, currency: CryptoCurrency) = when (portfolioId) {
is PortfolioId.Account -> TODO("account")
is PortfolioId.Wallet -> removeCurrencyUseCase(portfolioId.userWalletId, currency)
}
}