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

@ -14,6 +14,7 @@ import com.tangem.datasource.local.preferences.PreferencesKeys
import com.tangem.datasource.local.preferences.utils.getObjectList import com.tangem.datasource.local.preferences.utils.getObjectList
import com.tangem.datasource.local.preferences.utils.getObjectListSync import com.tangem.datasource.local.preferences.utils.getObjectListSync
import com.tangem.datasource.local.preferences.utils.getObjectMap import com.tangem.datasource.local.preferences.utils.getObjectMap
import com.tangem.domain.models.account.AccountId
import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.UserWalletId import com.tangem.domain.models.wallet.UserWalletId
@ -54,7 +55,7 @@ internal class DefaultSwapTransactionRepository(
storeTransactionState( storeTransactionState(
txId = transaction.txId, txId = transaction.txId,
status = it, status = it,
refundTokenCurrency = null, accountWithCurrency = null,
) )
} }
appPreferencesStore.editData { mutablePreferences -> appPreferencesStore.editData { mutablePreferences ->
@ -177,7 +178,7 @@ internal class DefaultSwapTransactionRepository(
override suspend fun storeTransactionState( override suspend fun storeTransactionState(
txId: String, txId: String,
status: SwapStatusModel, status: SwapStatusModel,
refundTokenCurrency: CryptoCurrency?, accountWithCurrency: Pair<AccountId?, CryptoCurrency>?,
) { ) {
appPreferencesStore.editData { mutablePreferences -> appPreferencesStore.editData { mutablePreferences ->
val savedMap = mutablePreferences.getObjectMap<SwapStatusDTO>( val savedMap = mutablePreferences.getObjectMap<SwapStatusDTO>(
@ -187,8 +188,8 @@ internal class DefaultSwapTransactionRepository(
val updatesMap = savedMap.toMutableMap() val updatesMap = savedMap.toMutableMap()
updatesMap[txId] = savedStatusConverter.convert( updatesMap[txId] = savedStatusConverter.convert(
status.copy( status.copy(
refundTokensResponse = refundTokenCurrency?.let { refundTokensResponse = accountWithCurrency?.let { (accountId, currency) ->
userTokensResponseFactory.createResponseToken(refundTokenCurrency) userTokensResponseFactory.createResponseToken(currency, accountId)
}, },
), ),
) )

View file

@ -25,10 +25,12 @@ internal class SavedSwapTransactionListConverter(
fromCryptoCurrencyId = value.fromCryptoCurrencyId, fromCryptoCurrencyId = value.fromCryptoCurrencyId,
toCryptoCurrencyId = value.toCryptoCurrencyId, toCryptoCurrencyId = value.toCryptoCurrencyId,
fromTokensResponse = userTokensResponseFactory.createResponseToken( fromTokensResponse = userTokensResponseFactory.createResponseToken(
value.fromCryptoCurrency, currency = value.fromCryptoCurrency,
accountId = null,
), ),
toTokensResponse = userTokensResponseFactory.createResponseToken( toTokensResponse = userTokensResponseFactory.createResponseToken(
value.toCryptoCurrency, currency = value.toCryptoCurrency,
accountId = null,
), ),
transactions = savedSwapTransactionConverter.convertList(value.transactions), transactions = savedSwapTransactionConverter.convertList(value.transactions),
) )
@ -74,8 +76,11 @@ internal class SavedSwapTransactionListConverter(
userWalletId = userWalletId.stringValue, userWalletId = userWalletId.stringValue,
fromCryptoCurrencyId = fromCryptoCurrency.id.value, fromCryptoCurrencyId = fromCryptoCurrency.id.value,
toCryptoCurrencyId = toCryptoCurrency.id.value, toCryptoCurrencyId = toCryptoCurrency.id.value,
fromTokensResponse = userTokensResponseFactory.createResponseToken(fromCryptoCurrency), fromTokensResponse = userTokensResponseFactory.createResponseToken(
toTokensResponse = userTokensResponseFactory.createResponseToken(toCryptoCurrency), currency = fromCryptoCurrency,
accountId = null,
),
toTokensResponse = userTokensResponseFactory.createResponseToken(currency = toCryptoCurrency, accountId = null),
transactions = tokenTransactions, transactions = tokenTransactions,
) )
} }

View file

@ -57,8 +57,16 @@ class SaveCryptoCurrenciesUseCase(
suspend operator fun invoke( suspend operator fun invoke(
accountId: AccountId, accountId: AccountId,
add: List<CryptoCurrency>, add: CryptoCurrency? = null,
remove: List<CryptoCurrency>, remove: CryptoCurrency? = null,
): Either<Throwable, Unit> {
return invoke(accountId = accountId, add = listOfNotNull(add), remove = listOfNotNull(remove))
}
suspend operator fun invoke(
accountId: AccountId,
add: List<CryptoCurrency> = emptyList(),
remove: List<CryptoCurrency> = emptyList(),
): Either<Throwable, Unit> = eitherOn(dispatchers.default) { ): Either<Throwable, Unit> = eitherOn(dispatchers.default) {
if (add.isEmpty() && remove.isEmpty()) { if (add.isEmpty() && remove.isEmpty()) {
Timber.d("No currencies to add or remove, skipping") Timber.d("No currencies to add or remove, skipping")
@ -84,6 +92,34 @@ class SaveCryptoCurrenciesUseCase(
} }
} }
suspend fun add(
accountId: AccountId,
networkId: String,
contractAddress: String,
): Either<Throwable, CryptoCurrency> = eitherOn(dispatchers.default) {
val userWalletId = accountId.userWalletId
withContext(NonCancellable) {
val account = getAccount(accountId = accountId)
val foundToken = account.cryptoCurrencies
.filterIsInstance<CryptoCurrency.Token>()
.firstOrNull {
it.network.backendId == networkId &&
!it.isCustom &&
it.contractAddress.equals(contractAddress, true)
}
if (foundToken != null) return@withContext foundToken
val tokenToAdd = findToken(userWalletId, contractAddress, networkId)
refreshBalances(userWalletId = userWalletId, currencies = listOf(tokenToAdd)).joinAll()
tokenToAdd
}
}
private suspend fun Raise<Throwable>.getAccount(accountId: AccountId): Account.CryptoPortfolio { private suspend fun Raise<Throwable>.getAccount(accountId: AccountId): Account.CryptoPortfolio {
val accountList = singleAccountListSupplier.getSyncOrNull( val accountList = singleAccountListSupplier.getSyncOrNull(
params = SingleAccountListProducer.Params(userWalletId = accountId.userWalletId), params = SingleAccountListProducer.Params(userWalletId = accountId.userWalletId),
@ -181,6 +217,23 @@ class SaveCryptoCurrenciesUseCase(
return destination return destination
} }
private suspend fun Raise<Throwable>.findToken(
userWalletId: UserWalletId,
networkId: String,
contractAddress: String,
): CryptoCurrency.Token {
return catch(
block = {
currenciesRepository.createTokenCurrency(
userWalletId = userWalletId,
networkId = networkId,
contractAddress = contractAddress,
)
},
catch = ::raise,
)
}
private suspend fun refreshBalances(userWalletId: UserWalletId, currencies: List<CryptoCurrency>): List<Job> { private suspend fun refreshBalances(userWalletId: UserWalletId, currencies: List<CryptoCurrency>): List<Job> {
if (currencies.isEmpty()) return emptyList() if (currencies.isEmpty()) return emptyList()

View file

@ -1,5 +1,6 @@
package com.tangem.domain.swap package com.tangem.domain.swap
import com.tangem.domain.models.account.AccountId
import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.UserWalletId import com.tangem.domain.models.wallet.UserWalletId
@ -59,9 +60,13 @@ interface SwapTransactionRepository {
* *
* @param txId transaction id to update * @param txId transaction id to update
* @param status new transaction status * @param status new transaction status
* @param refundTokenCurrency refund token * @param accountWithCurrency account id with refund token
*/ */
suspend fun storeTransactionState(txId: String, status: SwapStatusModel, refundTokenCurrency: CryptoCurrency?) suspend fun storeTransactionState(
txId: String,
status: SwapStatusModel,
accountWithCurrency: Pair<AccountId?, CryptoCurrency>? = null,
)
/** /**
* Save last swapped crypto currency token * Save last swapped crypto currency token

View file

@ -9,12 +9,14 @@ import com.tangem.domain.tokens.model.remove.RemoveCurrencyError
import com.tangem.domain.tokens.repository.CurrenciesRepository import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.walletmanager.WalletManagersFacade import com.tangem.domain.walletmanager.WalletManagersFacade
@Deprecated("Use SaveCryptoCurrenciesUseCase")
class RemoveCurrencyUseCase( class RemoveCurrencyUseCase(
private val currenciesRepository: CurrenciesRepository, private val currenciesRepository: CurrenciesRepository,
private val walletManagersFacade: WalletManagersFacade, private val walletManagersFacade: WalletManagersFacade,
private val multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier, private val multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier,
) { ) {
@Deprecated("Use SaveCryptoCurrenciesUseCase")
suspend operator fun invoke( suspend operator fun invoke(
userWalletId: UserWalletId, userWalletId: UserWalletId,
currency: CryptoCurrency, currency: CryptoCurrency,

View file

@ -7,6 +7,7 @@ import com.tangem.datasource.local.preferences.PreferencesKeys
import com.tangem.datasource.local.preferences.utils.getObjectList import com.tangem.datasource.local.preferences.utils.getObjectList
import com.tangem.datasource.local.preferences.utils.getObjectListSync import com.tangem.datasource.local.preferences.utils.getObjectListSync
import com.tangem.datasource.local.preferences.utils.getObjectMap import com.tangem.datasource.local.preferences.utils.getObjectMap
import com.tangem.domain.models.account.AccountId
import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.UserWalletId import com.tangem.domain.models.wallet.UserWalletId
@ -40,7 +41,7 @@ internal class DefaultSwapTransactionRepository(
storeTransactionState( storeTransactionState(
txId = transaction.txId, txId = transaction.txId,
status = it, status = it,
refundTokenCurrency = null, accountWithCurrency = null,
) )
} }
appPreferencesStore.editData { mutablePreferences -> appPreferencesStore.editData { mutablePreferences ->
@ -181,7 +182,7 @@ internal class DefaultSwapTransactionRepository(
override suspend fun storeTransactionState( override suspend fun storeTransactionState(
txId: String, txId: String,
status: ExchangeStatusModel, status: ExchangeStatusModel,
refundTokenCurrency: CryptoCurrency?, accountWithCurrency: Pair<AccountId?, CryptoCurrency>?,
) { ) {
appPreferencesStore.editData { mutablePreferences -> appPreferencesStore.editData { mutablePreferences ->
val savedMap = mutablePreferences.getObjectMap<ExchangeStatusModel>( val savedMap = mutablePreferences.getObjectMap<ExchangeStatusModel>(
@ -190,8 +191,8 @@ internal class DefaultSwapTransactionRepository(
val updatesMap = savedMap.toMutableMap() val updatesMap = savedMap.toMutableMap()
updatesMap[txId] = status.copy( updatesMap[txId] = status.copy(
refundTokensResponse = refundTokenCurrency?.let { refundTokensResponse = accountWithCurrency?.let { (accountId, currency) ->
userTokensResponseFactory.createResponseToken(refundTokenCurrency) userTokensResponseFactory.createResponseToken(currency, accountId)
}, },
) )

View file

@ -22,10 +22,12 @@ internal class SavedSwapTransactionListConverter(
fromCryptoCurrencyId = value.fromCryptoCurrencyId, fromCryptoCurrencyId = value.fromCryptoCurrencyId,
toCryptoCurrencyId = value.toCryptoCurrencyId, toCryptoCurrencyId = value.toCryptoCurrencyId,
fromTokensResponse = userTokensResponseFactory.createResponseToken( fromTokensResponse = userTokensResponseFactory.createResponseToken(
value.fromCryptoCurrency, currency = value.fromCryptoCurrency,
accountId = null,
), ),
toTokensResponse = userTokensResponseFactory.createResponseToken( toTokensResponse = userTokensResponseFactory.createResponseToken(
value.toCryptoCurrency, currency = value.toCryptoCurrency,
accountId = null,
), ),
transactions = value.transactions, transactions = value.transactions,
) )
@ -82,8 +84,11 @@ internal class SavedSwapTransactionListConverter(
userWalletId = userWalletId.stringValue, userWalletId = userWalletId.stringValue,
fromCryptoCurrencyId = fromCryptoCurrency.id.value, fromCryptoCurrencyId = fromCryptoCurrency.id.value,
toCryptoCurrencyId = toCryptoCurrency.id.value, toCryptoCurrencyId = toCryptoCurrency.id.value,
fromTokensResponse = userTokensResponseFactory.createResponseToken(fromCryptoCurrency), fromTokensResponse = userTokensResponseFactory.createResponseToken(
toTokensResponse = userTokensResponseFactory.createResponseToken(toCryptoCurrency), currency = fromCryptoCurrency,
accountId = null,
),
toTokensResponse = userTokensResponseFactory.createResponseToken(currency = toCryptoCurrency, accountId = null),
transactions = tokenTransactions, transactions = tokenTransactions,
) )
} }

View file

@ -1,5 +1,6 @@
package com.tangem.feature.swap.domain package com.tangem.feature.swap.domain
import com.tangem.domain.models.account.AccountId
import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.UserWalletId import com.tangem.domain.models.wallet.UserWalletId
@ -29,7 +30,11 @@ interface SwapTransactionRepository {
txId: String, txId: String,
) )
suspend fun storeTransactionState(txId: String, status: ExchangeStatusModel, refundTokenCurrency: CryptoCurrency?) suspend fun storeTransactionState(
txId: String,
status: ExchangeStatusModel,
accountWithCurrency: Pair<AccountId?, CryptoCurrency>? = null,
)
suspend fun storeLastSwappedCryptoCurrencyId(userWalletId: UserWalletId, cryptoCurrencyId: CryptoCurrency.ID) suspend fun storeLastSwappedCryptoCurrencyId(userWalletId: UserWalletId, cryptoCurrencyId: CryptoCurrency.ID)

View file

@ -57,35 +57,36 @@ dependencies {
implementation(projects.core.decompose) implementation(projects.core.decompose)
implementation(projects.common.ui) implementation(projects.common.ui)
implementation(projects.libs.blockchainSdk)
implementation(projects.libs.crypto) implementation(projects.libs.crypto)
/** Domain modules */ /** Domain modules */
implementation(projects.domain.account.status)
implementation(projects.domain.appCurrency) implementation(projects.domain.appCurrency)
implementation(projects.domain.appCurrency.models) implementation(projects.domain.appCurrency.models)
implementation(projects.domain.balanceHiding)
implementation(projects.domain.balanceHiding.models)
implementation(projects.domain.card) implementation(projects.domain.card)
implementation(projects.domain.demo) implementation(projects.domain.demo)
implementation(projects.domain.legacy) implementation(projects.domain.legacy)
implementation(projects.libs.blockchainSdk)
implementation(projects.domain.models)
implementation(projects.domain.settings)
implementation(projects.domain.tokens)
implementation(projects.domain.tokens.models)
implementation(projects.domain.txhistory)
implementation(projects.domain.txhistory.models)
implementation(projects.domain.wallets)
implementation(projects.domain.wallets.models)
implementation(projects.domain.balanceHiding)
implementation(projects.domain.balanceHiding.models)
implementation(projects.domain.transaction)
implementation(projects.domain.transaction.models)
implementation(projects.domain.staking)
implementation(projects.domain.markets.models) implementation(projects.domain.markets.models)
implementation(projects.domain.models)
implementation(projects.domain.notifications.models)
implementation(projects.domain.onramp) implementation(projects.domain.onramp)
implementation(projects.domain.onramp.models) implementation(projects.domain.onramp.models)
implementation(projects.domain.promo) implementation(projects.domain.promo)
implementation(projects.domain.promo.models) implementation(projects.domain.promo.models)
implementation(projects.domain.quotes) implementation(projects.domain.quotes)
implementation(projects.domain.notifications.models) implementation(projects.domain.settings)
implementation(projects.domain.staking)
implementation(projects.domain.tokens)
implementation(projects.domain.tokens.models)
implementation(projects.domain.transaction)
implementation(projects.domain.transaction.models)
implementation(projects.domain.txhistory)
implementation(projects.domain.txhistory.models)
implementation(projects.domain.wallets)
implementation(projects.domain.wallets.models)
/** Temp dependency to swap domain */ /** Temp dependency to swap domain */
implementation(projects.features.swap.domain) implementation(projects.features.swap.domain)

View file

@ -3,6 +3,7 @@ package com.tangem.feature.tokendetails.presentation.tokendetails.model
import androidx.compose.runtime.Stable import androidx.compose.runtime.Stable
import arrow.core.getOrElse import arrow.core.getOrElse
import arrow.core.merge import arrow.core.merge
import arrow.core.right
import com.arkivanov.decompose.router.slot.SlotNavigation import com.arkivanov.decompose.router.slot.SlotNavigation
import com.arkivanov.decompose.router.slot.activate import com.arkivanov.decompose.router.slot.activate
import com.arkivanov.decompose.router.slot.dismiss 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.TangemHapticEffect
import com.tangem.core.ui.haptic.VibratorHapticManager import com.tangem.core.ui.haptic.VibratorHapticManager
import com.tangem.core.ui.message.SnackbarMessage 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.GetSelectedAppCurrencyUseCase
import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase
import com.tangem.domain.card.common.util.cardTypesResolver import com.tangem.domain.card.common.util.cardTypesResolver
import com.tangem.domain.demo.IsDemoCardUseCase import com.tangem.domain.demo.IsDemoCardUseCase
import com.tangem.domain.models.TokenReceiveNotification 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.CryptoCurrency
import com.tangem.domain.models.currency.CryptoCurrencyStatus import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.network.NetworkAddress import com.tangem.domain.models.network.NetworkAddress
@ -141,6 +146,9 @@ internal class TokenDetailsModel @Inject constructor(
private val saveViewedYieldSupplyWarningUseCase: SaveViewedYieldSupplyWarningUseCase, private val saveViewedYieldSupplyWarningUseCase: SaveViewedYieldSupplyWarningUseCase,
private val saveViewedTokenReceiveWarningUseCase: SaveViewedTokenReceiveWarningUseCase, private val saveViewedTokenReceiveWarningUseCase: SaveViewedTokenReceiveWarningUseCase,
private val needShowYieldSupplyDepositedWarningUseCase: NeedShowYieldSupplyDepositedWarningUseCase, private val needShowYieldSupplyDepositedWarningUseCase: NeedShowYieldSupplyDepositedWarningUseCase,
private val accountsFeatureToggles: AccountsFeatureToggles,
private val getAccountCryptoCurrencyStatusUseCase: GetAccountCurrencyStatusUseCase,
private val saveCryptoCurrenciesUseCase: SaveCryptoCurrenciesUseCase,
) : Model(), TokenDetailsClickIntents, YieldSupplyDepositedWarningComponent.ModelCallback { ) : Model(), TokenDetailsClickIntents, YieldSupplyDepositedWarningComponent.ModelCallback {
private val params = paramsContainer.require<TokenDetailsComponent.Params>() private val params = paramsContainer.require<TokenDetailsComponent.Params>()
@ -158,6 +166,7 @@ internal class TokenDetailsModel @Inject constructor(
private val selectedAppCurrencyFlow: StateFlow<AppCurrency> = createSelectedAppCurrencyFlow() private val selectedAppCurrencyFlow: StateFlow<AppCurrency> = createSelectedAppCurrencyFlow()
private var cryptoCurrencyStatus: CryptoCurrencyStatus? = null private var cryptoCurrencyStatus: CryptoCurrencyStatus? = null
private var account: Account.CryptoPortfolio? = null
private var isBalanceLoadedEventSent = false private var isBalanceLoadedEventSent = false
private var expressTxStatusTaskScheduler = SingleTaskScheduler<PersistentList<ExpressTransactionStateUM>>() private var expressTxStatusTaskScheduler = SingleTaskScheduler<PersistentList<ExpressTransactionStateUM>>()
@ -228,15 +237,26 @@ internal class TokenDetailsModel @Inject constructor(
private fun initButtons() { private fun initButtons() {
// we need also init buttons before start all loading to avoid buttons blocking // we need also init buttons before start all loading to avoid buttons blocking
modelScope.launch { modelScope.launch {
val currentCryptoCurrencyStatus = getSingleCryptoCurrencyStatusUseCase.invokeMultiWalletSync( val currentCryptoCurrencyStatus = if (accountsFeatureToggles.isFeatureEnabled) {
userWalletId = userWalletId, getAccountCryptoCurrencyStatusUseCase.invokeSync(
cryptoCurrencyId = cryptoCurrency.id, userWalletId = userWalletId,
isSingleWalletWithTokens = false, currency = cryptoCurrency,
).getOrNull() )
currentCryptoCurrencyStatus?.let { .onSome { account = it.account }
cryptoCurrencyStatus = it .getOrNull()
updateButtons(it) ?.status
updateWarnings(it) } 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() { private fun subscribeOnCurrencyStatusUpdates() {
getSingleCryptoCurrencyStatusUseCase.invokeMultiWallet( if (accountsFeatureToggles.isFeatureEnabled) {
userWalletId = userWalletId, getAccountCryptoCurrencyStatusUseCase(userWalletId, cryptoCurrency)
currencyId = cryptoCurrency.id, .onEach { account = it.account }
isSingleWalletWithTokens = userWallet is UserWallet.Cold && .map { it.status.right() }
userWallet.scanResponse.cardTypesResolver.isSingleWalletWithToken(), } else {
) getSingleCryptoCurrencyStatusUseCase.invokeMultiWallet(
userWalletId = userWalletId,
currencyId = cryptoCurrency.id,
isSingleWalletWithTokens = userWallet is UserWallet.Cold &&
userWallet.scanResponse.cardTypesResolver.isSingleWalletWithToken(),
)
}
.distinctUntilChanged() .distinctUntilChanged()
.onEach { maybeCurrencyStatus -> .onEach { maybeCurrencyStatus ->
internalUiState.value = stateFactory.getCurrencyLoadedBalanceState(maybeCurrencyStatus) internalUiState.value = stateFactory.getCurrencyLoadedBalanceState(maybeCurrencyStatus)
@ -553,7 +579,7 @@ internal class TokenDetailsModel @Inject constructor(
} }
override fun onReceiveClick(unavailabilityReason: ScenarioUnavailabilityReason) { override fun onReceiveClick(unavailabilityReason: ScenarioUnavailabilityReason) {
val networkAddress = cryptoCurrencyStatus?.value?.networkAddress ?: return cryptoCurrencyStatus?.value?.networkAddress ?: return
analyticsEventsHandler.send( analyticsEventsHandler.send(
TokenScreenAnalyticsEvent.ButtonWithParams.ButtonReceive( TokenScreenAnalyticsEvent.ButtonWithParams.ButtonReceive(
@ -689,7 +715,18 @@ internal class TokenDetailsModel @Inject constructor(
override fun onHideConfirmed() { override fun onHideConfirmed() {
modelScope.launch { 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) } .onLeft { Timber.e(it) }
.onRight { router.popBackStack() } .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.core.analytics.api.AnalyticsEventHandler
import com.tangem.datasource.local.swap.ExpressAnalyticsStatus import com.tangem.datasource.local.swap.ExpressAnalyticsStatus
import com.tangem.datasource.local.swap.SwapTransactionStatusStore 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.appcurrency.model.AppCurrency
import com.tangem.domain.models.account.AccountId
import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.quote.QuoteStatus import com.tangem.domain.models.quote.QuoteStatus
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.quotes.QuotesRepository import com.tangem.domain.quotes.QuotesRepository
import com.tangem.domain.tokens.AddCryptoCurrenciesUseCase import com.tangem.domain.tokens.AddCryptoCurrenciesUseCase
import com.tangem.domain.tokens.model.analytics.TokenExchangeAnalyticsEvent 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.SwapTransactionRepository
import com.tangem.feature.swap.domain.api.SwapRepository import com.tangem.feature.swap.domain.api.SwapRepository
import com.tangem.feature.swap.domain.models.domain.* 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.Flow
import kotlinx.coroutines.flow.conflate import kotlinx.coroutines.flow.conflate
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import timber.log.Timber
@Suppress("LongParameterList") @Suppress("LongParameterList")
internal class ExchangeStatusFactory @AssistedInject constructor( internal class ExchangeStatusFactory @AssistedInject constructor(
@ -34,6 +39,9 @@ internal class ExchangeStatusFactory @AssistedInject constructor(
private val swapRepository: SwapRepository, private val swapRepository: SwapRepository,
private val quotesRepository: QuotesRepository, private val quotesRepository: QuotesRepository,
private val addCryptoCurrenciesUseCase: AddCryptoCurrenciesUseCase, private val addCryptoCurrenciesUseCase: AddCryptoCurrenciesUseCase,
private val accountsFeatureToggles: AccountsFeatureToggles,
private val getAccountCurrencyStatusUseCase: GetAccountCurrencyStatusUseCase,
private val saveCryptoCurrenciesUseCase: SaveCryptoCurrenciesUseCase,
private val swapTransactionStatusStore: SwapTransactionStatusStore, private val swapTransactionStatusStore: SwapTransactionStatusStore,
private val analyticsEventsHandler: AnalyticsEventHandler, private val analyticsEventsHandler: AnalyticsEventHandler,
@Assisted private val clickIntents: TokenDetailsClickIntents, @Assisted private val clickIntents: TokenDetailsClickIntents,
@ -111,9 +119,41 @@ internal class ExchangeStatusFactory @AssistedInject constructor(
ifRight = { statusModel -> ifRight = { statusModel ->
sendStatusUpdateAnalytics(statusModel, provider) 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) statusModel.copy(refundCurrency = refundTokenCurrency)
}, },
) )
@ -135,7 +175,7 @@ internal class ExchangeStatusFactory @AssistedInject constructor(
/** /**
* For now do it only for dex-bridge provider * For now do it only for dex-bridge provider
*/ */
private suspend fun addRefundCurrencyIfNeeded( private suspend fun addRefundCurrencyIfNeededLegacy(
status: ExchangeStatusModel?, status: ExchangeStatusModel?,
type: ExchangeProviderType, type: ExchangeProviderType,
): CryptoCurrency? { ): CryptoCurrency? {
@ -153,6 +193,27 @@ internal class ExchangeStatusFactory @AssistedInject constructor(
return null 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( private fun getExchangeStatusState(
savedTransactions: List<SavedSwapTransactionListModel>?, savedTransactions: List<SavedSwapTransactionListModel>?,
quoteStatuses: Set<QuoteStatus>, quoteStatuses: Set<QuoteStatus>,

View file

@ -22,6 +22,9 @@ import com.tangem.core.ui.extensions.WrappedList
import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.haptic.TangemHapticEffect import com.tangem.core.ui.haptic.TangemHapticEffect
import com.tangem.core.ui.haptic.VibratorHapticManager import com.tangem.core.ui.haptic.VibratorHapticManager
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.GetSelectedAppCurrencyUseCase
import com.tangem.domain.appcurrency.extenstions.unwrap import com.tangem.domain.appcurrency.extenstions.unwrap
import com.tangem.domain.core.lce.Lce import com.tangem.domain.core.lce.Lce
@ -71,6 +74,7 @@ import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import timber.log.Timber
import java.math.BigDecimal import java.math.BigDecimal
import javax.inject.Inject import javax.inject.Inject
@ -151,6 +155,9 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
private val saveViewedYieldSupplyWarningUseCase: SaveViewedYieldSupplyWarningUseCase, private val saveViewedYieldSupplyWarningUseCase: SaveViewedYieldSupplyWarningUseCase,
private val isCryptoCurrencyCoinCouldHide: IsCryptoCurrencyCoinCouldHideUseCase, private val isCryptoCurrencyCoinCouldHide: IsCryptoCurrencyCoinCouldHideUseCase,
private val removeCurrencyUseCase: RemoveCurrencyUseCase, private val removeCurrencyUseCase: RemoveCurrencyUseCase,
private val accountsFeatureToggles: AccountsFeatureToggles,
private val getAccountCurrencyStatusUseCase: GetAccountCurrencyStatusUseCase,
private val saveCryptoCurrenciesUseCase: SaveCryptoCurrenciesUseCase,
) : BaseWalletClickIntents(), WalletCurrencyActionsClickIntents { ) : BaseWalletClickIntents(), WalletCurrencyActionsClickIntents {
override fun onSendClick( override fun onSendClick(
@ -340,7 +347,23 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
override fun onPerformHideToken(userWalletId: UserWalletId, cryptoCurrencyStatus: CryptoCurrencyStatus) { override fun onPerformHideToken(userWalletId: UserWalletId, cryptoCurrencyStatus: CryptoCurrencyStatus) {
modelScope.launch(dispatchers.io) { modelScope.launch(dispatchers.io) {
removeCurrencyUseCase(userWalletId, cryptoCurrencyStatus.currency) if (accountsFeatureToggles.isFeatureEnabled) {
val accountId = getAccountCurrencyStatusUseCase.invokeSync(
userWalletId = userWalletId,
currency = cryptoCurrencyStatus.currency,
)
.map { it.account.accountId }
.getOrNull()
if (accountId == null) {
Timber.e("Account ID is null, cannot hide currency ${cryptoCurrencyStatus.currency.id}")
return@launch
}
saveCryptoCurrenciesUseCase(accountId = accountId, remove = cryptoCurrencyStatus.currency)
} else {
removeCurrencyUseCase(userWalletId, cryptoCurrencyStatus.currency)
}
.fold( .fold(
ifLeft = { ifLeft = {
walletEventSender.send( walletEventSender.send(