From a7a4d5733f7ed9047abb48b68767112c0de6c27c Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 1 Apr 2026 16:41:56 +0400 Subject: [PATCH] Updated on 2026-08-14 --- .../tangem/feature/swap/model/SwapModel.kt | 101 ++++++++++++------ 1 file changed, 69 insertions(+), 32 deletions(-) diff --git a/features/swap/impl/src/main/java/com/tangem/feature/swap/model/SwapModel.kt b/features/swap/impl/src/main/java/com/tangem/feature/swap/model/SwapModel.kt index 03c229e5c6..df4d6c93e7 100644 --- a/features/swap/impl/src/main/java/com/tangem/feature/swap/model/SwapModel.kt +++ b/features/swap/impl/src/main/java/com/tangem/feature/swap/model/SwapModel.kt @@ -38,6 +38,7 @@ import com.tangem.datasource.local.appsflyer.AppsFlyerStore import com.tangem.domain.account.status.model.AccountCryptoCurrencyStatus import com.tangem.domain.account.status.supplier.SingleAccountStatusListSupplier import com.tangem.domain.account.status.usecase.GetAccountCurrencyStatusUseCase +import com.tangem.domain.account.status.usecase.GetFeePaidCryptoCurrencyStatusSyncUseCase import com.tangem.domain.account.status.utils.CryptoCurrencyStatusOperations.getCryptoCurrencyStatus import com.tangem.domain.account.usecase.IsAccountsModeEnabledUseCase import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase @@ -61,6 +62,7 @@ import com.tangem.domain.models.network.Network import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.models.wallet.UserWalletId import com.tangem.domain.pay.WithdrawalResult +import com.tangem.domain.promo.ShouldShowStoriesUseCase import com.tangem.domain.promo.models.StoryContentIds import com.tangem.domain.settings.usercountry.GetUserCountryUseCase import com.tangem.domain.settings.usercountry.models.UserCountry @@ -68,8 +70,6 @@ import com.tangem.domain.settings.usercountry.models.needApplyFCARestrictions import com.tangem.domain.tangempay.GetTangemPayCurrencyStatusUseCase import com.tangem.domain.tangempay.GetTangemPayCustomerIdUseCase import com.tangem.domain.tangempay.TangemPayWithdrawUseCase -import com.tangem.domain.account.status.usecase.GetFeePaidCryptoCurrencyStatusSyncUseCase -import com.tangem.domain.promo.ShouldShowStoriesUseCase import com.tangem.domain.tokens.GetMinimumTransactionAmountSyncUseCase import com.tangem.domain.tokens.UpdateDelayedNetworkStatusUseCase import com.tangem.domain.transaction.error.GetFeeError @@ -229,7 +229,7 @@ internal class SwapModel @Inject constructor( val feeSelectorRepository = FeeSelectorRepository() // shows currency order (direct - swap initial to selected, reversed = selected to initial) - private var isOrderReversed by mutableStateOf(false) + private val isOrderReversed = MutableStateFlow(value = params.isInitialReverseOrder) private val lastAmount = mutableStateOf(INITIAL_AMOUNT) private val lastReducedBalanceBy = mutableStateOf(BigDecimal.ZERO) private val swapRouter: SwapRouter = SwapRouter(router = router) @@ -538,20 +538,22 @@ internal class SwapModel @Inject constructor( selectedAccountCurrency?.status to selectedAccountCurrency?.account } - applyInitialTokenChoice( + val isApplied = applyInitialTokenChoice( state = state, selectedCurrency = selectedCurrency, selectedAccount = selectedAccount, isReverseFromTo = isReverseFromTo, ) - val fromCryptoCurrency = if (isOrderReversed) { - dataState.toCryptoCurrency - } else { - dataState.fromCryptoCurrency - } + // assume that fromCryptoCurrency selected according reverse flag, + // so update fee paid currency according to it + val fromCryptoCurrency = dataState.fromCryptoCurrency - if (fromCryptoCurrency != null) { + if (isApplied && fromCryptoCurrency != null) { + Timber.i( + "updateFeePaidCryptoCurrencyFor: id = ${fromCryptoCurrency.currency.id}, " + + "isReverseFromTo: $isReverseFromTo", + ) updateFeePaidCryptoCurrencyFor(fromCryptoCurrency) } else { Timber.e("updateFeePaidCryptoCurrencyFor failed: fromCryptoCurrency is null") @@ -594,7 +596,7 @@ internal class SwapModel @Inject constructor( state = state, selectedCurrency = addedToken, selectedAccount = addedAccount, - isReverseFromTo = isOrderReversed, + isReverseFromTo = isOrderReversed.value, ) subscribeToCoinBalanceUpdatesIfNeeded() @@ -624,29 +626,34 @@ internal class SwapModel @Inject constructor( } } + /** + * returns true if tokens are selected and dataState is updated, + * false if selected token is null and alert is shown with error message + */ private fun applyInitialTokenChoice( state: TokensDataStateExpress, selectedCurrency: CryptoCurrencyStatus?, selectedAccount: Account.CryptoPortfolio?, isReverseFromTo: Boolean, - ) { + ): Boolean { // exceptional case if (selectedCurrency == null) { + Timber.e("No available tokens to swap for ${initialCurrencyFrom.symbol}") analyticsEventHandler.send(SwapEvents.NoticeNoAvailableTokensToSwap()) uiState = stateBuilder.createNoAvailableTokensToSwapState( uiStateHolder = uiState, fromToken = initialFromStatus, ) - return + return false } - isOrderReversed = isReverseFromTo - val (fromCurrencyStatus, toCurrencyStatus) = if (isOrderReversed) { + isOrderReversed.value = isReverseFromTo + val (fromCurrencyStatus, toCurrencyStatus) = if (isOrderReversed.value) { selectedCurrency to initialFromStatus } else { initialFromStatus to selectedCurrency } val (fromAccount, toAccount) = if (canUseFromAccountCurrencyStatus) { - if (isOrderReversed) { + if (isOrderReversed.value) { selectedAccount to requireNotNull(fromAccountCurrencyStatus).account } else { requireNotNull(fromAccountCurrencyStatus).account to selectedAccount @@ -670,7 +677,7 @@ internal class SwapModel @Inject constructor( toAccount = toAccount, ) ) { - return + return true } startLoadingQuotes( @@ -682,10 +689,11 @@ internal class SwapModel @Inject constructor( reduceBalanceBy = lastReducedBalanceBy.value, toProvidersList = findSwapProviders(fromCurrencyStatus, toCurrencyStatus), ) + return true } private fun updateTokensState(tokenDataState: TokensDataStateExpress) { - val tokensDataState = if (isOrderReversed) tokenDataState.fromGroup else tokenDataState.toGroup + val tokensDataState = if (isOrderReversed.value) tokenDataState.fromGroup else tokenDataState.toGroup uiState = stateBuilder.addTokensToStateV2( uiState = uiState, @@ -1278,7 +1286,7 @@ internal class SwapModel @Inject constructor( searchQueryState.value = searchQuery val tokenDataState = dataState.tokensDataState ?: return@debounce - val group = if (isOrderReversed) { + val group = if (isOrderReversed.value) { tokenDataState.fromGroup } else { tokenDataState.toGroup @@ -1308,7 +1316,7 @@ internal class SwapModel @Inject constructor( ) } - val filteredTokenDataState = if (isOrderReversed) { + val filteredTokenDataState = if (isOrderReversed.value) { tokenDataState.copy( fromGroup = tokenDataState.fromGroup.copy( available = available, @@ -1355,7 +1363,7 @@ internal class SwapModel @Inject constructor( val fromAccount: Account.CryptoPortfolio? val toToken: CryptoCurrencyStatus val toAccount: Account.CryptoPortfolio? - if (isOrderReversed) { + if (isOrderReversed.value) { fromToken = foundToken fromAccount = foundAccount toToken = initialFromStatus @@ -1412,6 +1420,10 @@ internal class SwapModel @Inject constructor( return } modelScope.launch { + Timber.i( + "updateFeePaidCryptoCurrencyFor: id = ${fromToken.currency.id}, " + + "isOrderReversed: ${isOrderReversed.value}", + ) updateFeePaidCryptoCurrencyFor(fromToken) startLoadingQuotes( fromToken = fromToken, @@ -1431,7 +1443,7 @@ internal class SwapModel @Inject constructor( tokens: TokensDataStateExpress, id: String, ): Pair { - val accountCryptoCurrencyStatus = if (isOrderReversed) { + val accountCryptoCurrencyStatus = if (isOrderReversed.value) { tokens.fromGroup } else { tokens.toGroup @@ -1463,7 +1475,23 @@ internal class SwapModel @Inject constructor( feePaidCryptoCurrency = getFeePaidCryptoCurrencyStatusSyncUseCase( userWalletId = userWalletId, cryptoCurrencyStatus = currencyStatus, - ).getOrNull() ?: currencyStatus, + ) + .onLeft { + Timber.e( + "Coin balance: Unable to get fee paid crypto currency status for " + + "${currencyStatus.currency.id}", + ) + } + .onRight { status -> + if (status == null) { + Timber.e( + "Coin balance: Fee paid crypto currency status is null " + + "for ${currencyStatus.currency.id}", + ) + } + } + .getOrNull() + ?: currencyStatus, ) } @@ -1509,7 +1537,11 @@ internal class SwapModel @Inject constructor( toCryptoCurrency = newToToken, toAccount = newToAccount, ) - isOrderReversed = !isOrderReversed + isOrderReversed.value = !isOrderReversed.value + Timber.i( + "updateFeePaidCryptoCurrencyFor: id = ${newFromToken.currency.id}, " + + "isOrderReversed: ${isOrderReversed.value}", + ) updateFeePaidCryptoCurrencyFor(newFromToken) dataState.tokensDataState?.let { tokensDataState -> updateTokensState(tokensDataState) @@ -1946,13 +1978,13 @@ internal class SwapModel @Inject constructor( } private fun findSwapProviders(fromToken: CryptoCurrencyStatus, toToken: CryptoCurrencyStatus): List { - val groupToFind = if (isOrderReversed) { + val groupToFind = if (isOrderReversed.value) { dataState.tokensDataState?.fromGroup } else { dataState.tokensDataState?.toGroup } ?: return emptyList() - val idToFind = if (isOrderReversed) { + val idToFind = if (isOrderReversed.value) { fromToken.currency.id.value } else { toToken.currency.id.value @@ -1978,8 +2010,8 @@ internal class SwapModel @Inject constructor( fromAccount: Account.CryptoPortfolio?, toAccount: Account.CryptoPortfolio?, ): Boolean { - val selectedCurrency = if (isOrderReversed) fromToken else toToken - if (isTokenAvailableForSwap(state, selectedCurrency, isOrderReversed)) return false + val selectedCurrency = if (isOrderReversed.value) fromToken else toToken + if (isTokenAvailableForSwap(state, selectedCurrency, isOrderReversed.value)) return false analyticsEventHandler.send( SwapEvents.NoticeUnavailableToSwapPair( @@ -2039,13 +2071,13 @@ internal class SwapModel @Inject constructor( val from = dataState.fromCryptoCurrency ?: return false val to = dataState.toCryptoCurrency ?: return false - val currenciesGroup = if (isOrderReversed) { + val currenciesGroup = if (isOrderReversed.value) { dataState.tokensDataState?.toGroup } else { dataState.tokensDataState?.fromGroup } ?: return false - val chosen = if (isOrderReversed) from else to + val chosen = if (isOrderReversed.value) from else to return currenciesGroup.accountCurrencyList.flatMap { accountSwapAvailability -> accountSwapAvailability.currencyList.map { accountSwapCurrency -> @@ -2310,7 +2342,10 @@ internal class SwapModel @Inject constructor( val feeStateUM = feeSelectorRepository.state.value as? FeeSelectorUM.Content if (feeStateUM == null) { - Timber.e("getSelectedFeeState: FeeSelectorUM is not Content: $feeStateUM, returning Legacy state") + Timber.e( + "getSelectedFeeState: FeeSelectorUM is not Content: ${feeSelectorRepository.state.value}, " + + "returning Legacy state", + ) return TxFeeSealedState.Legacy( txFeeState = TxFeeState.Empty, selectedFee = dataState.selectedFee?.feeType ?: FeeType.NORMAL, @@ -2332,7 +2367,9 @@ internal class SwapModel @Inject constructor( val feeStateUM = feeSelectorRepository.state.value as? FeeSelectorUM.Content if (feeStateUM == null) { - Timber.e("getSelectedFee: FeeSelectorUM is not Content: $feeStateUM, returning null") + Timber.e( + "getSelectedFee: FeeSelectorUM is not Content: ${feeSelectorRepository.state.value}, returning null", + ) return null }