Updated on 2026-08-14

This commit is contained in:
Tangem 2026-04-01 16:41:56 +04:00
parent fdc485fbca
commit a7a4d5733f

View file

@ -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<CryptoCurrencyStatus?, Account.CryptoPortfolio?> {
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<SwapProvider> {
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
}