Updated on 2026-08-14

This commit is contained in:
Tangem 2025-10-14 15:36:53 +04:00
parent 5e6bf6aaae
commit aaf4419ebe
12 changed files with 257 additions and 58 deletions

View file

@ -3,6 +3,7 @@ package com.tangem.feature.tokendetails.presentation.tokendetails.model
import androidx.compose.runtime.Stable
import arrow.core.getOrElse
import arrow.core.merge
import arrow.core.right
import com.arkivanov.decompose.router.slot.SlotNavigation
import com.arkivanov.decompose.router.slot.activate
import com.arkivanov.decompose.router.slot.dismiss
@ -31,12 +32,16 @@ import com.tangem.core.ui.extensions.wrappedList
import com.tangem.core.ui.haptic.TangemHapticEffect
import com.tangem.core.ui.haptic.VibratorHapticManager
import com.tangem.core.ui.message.SnackbarMessage
import com.tangem.domain.account.featuretoggle.AccountsFeatureToggles
import com.tangem.domain.account.status.usecase.GetAccountCurrencyStatusUseCase
import com.tangem.domain.account.status.usecase.SaveCryptoCurrenciesUseCase
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase
import com.tangem.domain.card.common.util.cardTypesResolver
import com.tangem.domain.demo.IsDemoCardUseCase
import com.tangem.domain.models.TokenReceiveNotification
import com.tangem.domain.models.account.Account
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.network.NetworkAddress
@ -141,6 +146,9 @@ internal class TokenDetailsModel @Inject constructor(
private val saveViewedYieldSupplyWarningUseCase: SaveViewedYieldSupplyWarningUseCase,
private val saveViewedTokenReceiveWarningUseCase: SaveViewedTokenReceiveWarningUseCase,
private val needShowYieldSupplyDepositedWarningUseCase: NeedShowYieldSupplyDepositedWarningUseCase,
private val accountsFeatureToggles: AccountsFeatureToggles,
private val getAccountCryptoCurrencyStatusUseCase: GetAccountCurrencyStatusUseCase,
private val saveCryptoCurrenciesUseCase: SaveCryptoCurrenciesUseCase,
) : Model(), TokenDetailsClickIntents, YieldSupplyDepositedWarningComponent.ModelCallback {
private val params = paramsContainer.require<TokenDetailsComponent.Params>()
@ -158,6 +166,7 @@ internal class TokenDetailsModel @Inject constructor(
private val selectedAppCurrencyFlow: StateFlow<AppCurrency> = createSelectedAppCurrencyFlow()
private var cryptoCurrencyStatus: CryptoCurrencyStatus? = null
private var account: Account.CryptoPortfolio? = null
private var isBalanceLoadedEventSent = false
private var expressTxStatusTaskScheduler = SingleTaskScheduler<PersistentList<ExpressTransactionStateUM>>()
@ -228,15 +237,26 @@ internal class TokenDetailsModel @Inject constructor(
private fun initButtons() {
// we need also init buttons before start all loading to avoid buttons blocking
modelScope.launch {
val currentCryptoCurrencyStatus = getSingleCryptoCurrencyStatusUseCase.invokeMultiWalletSync(
userWalletId = userWalletId,
cryptoCurrencyId = cryptoCurrency.id,
isSingleWalletWithTokens = false,
).getOrNull()
currentCryptoCurrencyStatus?.let {
cryptoCurrencyStatus = it
updateButtons(it)
updateWarnings(it)
val currentCryptoCurrencyStatus = if (accountsFeatureToggles.isFeatureEnabled) {
getAccountCryptoCurrencyStatusUseCase.invokeSync(
userWalletId = userWalletId,
currency = cryptoCurrency,
)
.onSome { account = it.account }
.getOrNull()
?.status
} else {
getSingleCryptoCurrencyStatusUseCase.invokeMultiWalletSync(
userWalletId = userWalletId,
cryptoCurrencyId = cryptoCurrency.id,
isSingleWalletWithTokens = false,
).getOrNull()
}
currentCryptoCurrencyStatus?.let { status ->
cryptoCurrencyStatus = status
updateButtons(currencyStatus = status)
updateWarnings(cryptoCurrencyStatus = status)
}
}
}
@ -308,12 +328,18 @@ internal class TokenDetailsModel @Inject constructor(
}
private fun subscribeOnCurrencyStatusUpdates() {
getSingleCryptoCurrencyStatusUseCase.invokeMultiWallet(
userWalletId = userWalletId,
currencyId = cryptoCurrency.id,
isSingleWalletWithTokens = userWallet is UserWallet.Cold &&
userWallet.scanResponse.cardTypesResolver.isSingleWalletWithToken(),
)
if (accountsFeatureToggles.isFeatureEnabled) {
getAccountCryptoCurrencyStatusUseCase(userWalletId, cryptoCurrency)
.onEach { account = it.account }
.map { it.status.right() }
} else {
getSingleCryptoCurrencyStatusUseCase.invokeMultiWallet(
userWalletId = userWalletId,
currencyId = cryptoCurrency.id,
isSingleWalletWithTokens = userWallet is UserWallet.Cold &&
userWallet.scanResponse.cardTypesResolver.isSingleWalletWithToken(),
)
}
.distinctUntilChanged()
.onEach { maybeCurrencyStatus ->
internalUiState.value = stateFactory.getCurrencyLoadedBalanceState(maybeCurrencyStatus)
@ -553,7 +579,7 @@ internal class TokenDetailsModel @Inject constructor(
}
override fun onReceiveClick(unavailabilityReason: ScenarioUnavailabilityReason) {
val networkAddress = cryptoCurrencyStatus?.value?.networkAddress ?: return
cryptoCurrencyStatus?.value?.networkAddress ?: return
analyticsEventsHandler.send(
TokenScreenAnalyticsEvent.ButtonWithParams.ButtonReceive(
@ -689,7 +715,18 @@ internal class TokenDetailsModel @Inject constructor(
override fun onHideConfirmed() {
modelScope.launch {
removeCurrencyUseCase.invoke(userWalletId, cryptoCurrency)
if (accountsFeatureToggles.isFeatureEnabled) {
val accountId = account?.accountId
if (accountId == null) {
Timber.e("Account ID is null, cannot hide currency ${cryptoCurrency.id}")
return@launch
}
saveCryptoCurrenciesUseCase(accountId = accountId, remove = cryptoCurrency)
} else {
removeCurrencyUseCase(userWalletId, cryptoCurrency)
}
.onLeft { Timber.e(it) }
.onRight { router.popBackStack() }
}

View file

@ -4,13 +4,17 @@ import com.tangem.common.ui.expressStatus.ExpressStatusBottomSheetConfig
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.datasource.local.swap.ExpressAnalyticsStatus
import com.tangem.datasource.local.swap.SwapTransactionStatusStore
import com.tangem.domain.account.featuretoggle.AccountsFeatureToggles
import com.tangem.domain.account.status.usecase.GetAccountCurrencyStatusUseCase
import com.tangem.domain.account.status.usecase.SaveCryptoCurrenciesUseCase
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.models.account.AccountId
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.quote.QuoteStatus
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.quotes.QuotesRepository
import com.tangem.domain.tokens.AddCryptoCurrenciesUseCase
import com.tangem.domain.tokens.model.analytics.TokenExchangeAnalyticsEvent
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.feature.swap.domain.SwapTransactionRepository
import com.tangem.feature.swap.domain.api.SwapRepository
import com.tangem.feature.swap.domain.models.domain.*
@ -27,6 +31,7 @@ import kotlinx.collections.immutable.persistentListOf
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.conflate
import kotlinx.coroutines.flow.map
import timber.log.Timber
@Suppress("LongParameterList")
internal class ExchangeStatusFactory @AssistedInject constructor(
@ -34,6 +39,9 @@ internal class ExchangeStatusFactory @AssistedInject constructor(
private val swapRepository: SwapRepository,
private val quotesRepository: QuotesRepository,
private val addCryptoCurrenciesUseCase: AddCryptoCurrenciesUseCase,
private val accountsFeatureToggles: AccountsFeatureToggles,
private val getAccountCurrencyStatusUseCase: GetAccountCurrencyStatusUseCase,
private val saveCryptoCurrenciesUseCase: SaveCryptoCurrenciesUseCase,
private val swapTransactionStatusStore: SwapTransactionStatusStore,
private val analyticsEventsHandler: AnalyticsEventHandler,
@Assisted private val clickIntents: TokenDetailsClickIntents,
@ -111,9 +119,41 @@ internal class ExchangeStatusFactory @AssistedInject constructor(
ifRight = { statusModel ->
sendStatusUpdateAnalytics(statusModel, provider)
val refundTokenCurrency = addRefundCurrencyIfNeeded(statusModel, provider.type)
val accountId = if (accountsFeatureToggles.isFeatureEnabled) {
getAccountCurrencyStatusUseCase.invokeSync(
userWalletId = userWallet.walletId,
currency = cryptoCurrency,
)
.map { it.account.accountId }
.getOrNull()
} else {
null
}
swapTransactionRepository.storeTransactionState(txId, statusModel, refundTokenCurrency)
val refundTokenCurrency = if (accountsFeatureToggles.isFeatureEnabled) {
if (accountId != null) {
addRefundCurrencyIfNeededNew(
accountId = accountId,
status = statusModel,
type = provider.type,
)
} else {
Timber.e("Account ID is null, cannot add refund currency ${cryptoCurrency.id}")
null
}
} else {
addRefundCurrencyIfNeededLegacy(status = statusModel, type = provider.type)
}
swapTransactionRepository.storeTransactionState(
txId = txId,
status = statusModel,
accountWithCurrency = if (refundTokenCurrency != null) {
Pair(accountId, refundTokenCurrency)
} else {
null
},
)
statusModel.copy(refundCurrency = refundTokenCurrency)
},
)
@ -135,7 +175,7 @@ internal class ExchangeStatusFactory @AssistedInject constructor(
/**
* For now do it only for dex-bridge provider
*/
private suspend fun addRefundCurrencyIfNeeded(
private suspend fun addRefundCurrencyIfNeededLegacy(
status: ExchangeStatusModel?,
type: ExchangeProviderType,
): CryptoCurrency? {
@ -153,6 +193,27 @@ internal class ExchangeStatusFactory @AssistedInject constructor(
return null
}
private suspend fun addRefundCurrencyIfNeededNew(
accountId: AccountId,
status: ExchangeStatusModel?,
type: ExchangeProviderType,
): CryptoCurrency? {
status ?: return null
if (type != ExchangeProviderType.DEX_BRIDGE) return null
val refundNetwork = status.refundNetwork
val refundContractAddress = status.refundContractAddress
if (refundNetwork == null || refundContractAddress == null) return null
return saveCryptoCurrenciesUseCase.add(
accountId = accountId,
contractAddress = refundContractAddress,
networkId = refundNetwork,
)
.onLeft(Timber::e)
.getOrNull()
}
private fun getExchangeStatusState(
savedTransactions: List<SavedSwapTransactionListModel>?,
quoteStatuses: Set<QuoteStatus>,