Updated on 2026-08-14

This commit is contained in:
Tangem 2026-02-25 22:40:08 +07:00
commit 7fe610ec37
26 changed files with 322 additions and 202 deletions

View file

@ -36,11 +36,8 @@ internal class AccountTokenItemConverter(
isExpanded = true,
isCollapsable = false,
tokens = value.currencyList.map { accountSwapCurrency ->
if (accountSwapCurrency.isAvailable) {
createAvailableItemConverter()
} else {
createUnavailableItemConverter()
}.convert(accountSwapCurrency.cryptoCurrencyStatus)
createAvailableItemConverter()
.convert(accountSwapCurrency.cryptoCurrencyStatus)
}.map(TokensListItemUM::Token).toPersistentList(),
)
}

View file

@ -17,6 +17,7 @@ import com.tangem.feature.swap.models.TokenToSelectState
import com.tangem.feature.swap.presentation.R
import com.tangem.utils.Provider
import com.tangem.utils.converter.Converter
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList
internal class TokensDataConverter(
@ -31,14 +32,9 @@ internal class TokensDataConverter(
val availableTitle = TokenToSelectState.Title(
resourceReference(R.string.exchange_tokens_available_tokens_header),
)
val unavailableTitle = TokenToSelectState.Title(
resourceReference(
R.string.exchange_tokens_unavailable_tokens_header,
wrappedList(value.fromCurrency.name),
),
)
val allTokens = group.available + group.unavailable
return SwapSelectTokenStateHolder(
availableTokens = group.available.map { tokenWithBalanceToTokenToSelect(it, true) }
availableTokens = allTokens.map { tokenWithBalanceToTokenToSelect(it, true) }
.toMutableList()
.apply {
if (this.isNotEmpty()) {
@ -46,14 +42,7 @@ internal class TokensDataConverter(
}
}
.toImmutableList(),
unavailableTokens = group.unavailable.map { tokenWithBalanceToTokenToSelect(it, false) }
.toMutableList()
.apply {
if (this.isNotEmpty()) {
this.add(0, unavailableTitle)
}
}
.toImmutableList(),
unavailableTokens = persistentListOf(),
tokensListData = TokenListUMData.EmptyList,
onSearchEntered = onSearchEntered,
onTokenSelected = onTokenSelected,

View file

@ -46,11 +46,8 @@ internal class TokensDataConverterV2(
} else {
val tokensList = accountList.flatMap { (_, currencyList) ->
currencyList.asSequence().map { accountSwapCurrency ->
if (accountSwapCurrency.isAvailable) {
accountListItemConverter.createAvailableItemConverter()
} else {
accountListItemConverter.createUnavailableItemConverter()
}.convert(accountSwapCurrency.cryptoCurrencyStatus)
accountListItemConverter.createAvailableItemConverter()
.convert(accountSwapCurrency.cryptoCurrencyStatus)
}.map(TokensListItemUM::Token).toPersistentList()
}.toPersistentList()

View file

@ -645,21 +645,14 @@ internal class SwapModel @Inject constructor(
tokensDataState = state,
)
if (!isTokenAvailableForSwap(state, selectedCurrency, isReverseFromTo)) {
analyticsEventHandler.send(
SwapEvents.NoticeUnavailableToSwapPair(
sendToken = fromCurrencyStatus.currency.symbol,
receiveToken = toCurrencyStatus.currency.symbol,
sendBlockchain = fromCurrencyStatus.currency.network.name,
receiveBlockchain = toCurrencyStatus.currency.network.name,
),
)
uiState = stateBuilder.createSwapNotSupportedState(
uiStateHolder = uiState,
if (handleSwapNotSupported(
state = state,
fromToken = fromCurrencyStatus,
toToken = toCurrencyStatus,
fromAccount = fromAccount,
toAccount = toAccount,
)
) {
return
}
@ -1397,6 +1390,17 @@ internal class SwapModel @Inject constructor(
toAccount = toAccount,
selectedProvider = null,
)
swapRouter.openScreen(SwapNavScreen.Main)
if (handleSwapNotSupported(
state = tokens,
fromToken = fromToken,
toToken = toToken,
fromAccount = fromAccount,
toAccount = toAccount,
)
) {
return
}
modelScope.launch {
updateFeePaidCryptoCurrencyFor(fromToken)
}
@ -1409,7 +1413,6 @@ internal class SwapModel @Inject constructor(
reduceBalanceBy = lastReducedBalanceBy.value,
toProvidersList = findSwapProviders(fromToken, toToken),
)
swapRouter.openScreen(SwapNavScreen.Main)
updateTokensState(tokens)
}
}
@ -2011,6 +2014,37 @@ internal class SwapModel @Inject constructor(
.orEmpty()
}
/**
* @return true if swap is not supported and UI was updated to show error state
*/
private fun handleSwapNotSupported(
state: TokensDataStateExpress,
fromToken: CryptoCurrencyStatus,
toToken: CryptoCurrencyStatus,
fromAccount: Account.CryptoPortfolio?,
toAccount: Account.CryptoPortfolio?,
): Boolean {
val selectedCurrency = if (isOrderReversed) fromToken else toToken
if (isTokenAvailableForSwap(state, selectedCurrency, isOrderReversed)) return false
analyticsEventHandler.send(
SwapEvents.NoticeUnavailableToSwapPair(
sendToken = fromToken.currency.symbol,
receiveToken = toToken.currency.symbol,
sendBlockchain = fromToken.currency.network.name,
receiveBlockchain = toToken.currency.network.name,
),
)
uiState = stateBuilder.createSwapNotSupportedState(
uiStateHolder = uiState,
fromToken = fromToken,
toToken = toToken,
fromAccount = fromAccount,
toAccount = toAccount,
)
return true
}
private fun isTokenAvailableForSwap(
state: TokensDataStateExpress,
selectedCurrency: CryptoCurrencyStatus,

View file

@ -64,9 +64,9 @@ internal class SwapNotificationsFactory(
)
}
fun getSwapNotSupportedNotifications(tokenName: String): ImmutableList<NotificationUM> {
fun getSwapNotSupportedNotifications(): ImmutableList<NotificationUM> {
return persistentListOf(
SwapNotificationUM.Warning.SwapNotSupported(tokenName),
SwapNotificationUM.Warning.SwapNotSupported,
)
}

View file

@ -132,15 +132,12 @@ internal object SwapNotificationUM {
),
)
data class SwapNotSupported(
val tokenName: String,
) : Warning(
data object SwapNotSupported : Warning(
title = resourceReference(
id = com.tangem.feature.swap.presentation.R.string.express_swap_not_supported_title,
formatArgs = wrappedList(tokenName),
id = com.tangem.feature.swap.presentation.R.string.warning_express_unsupported_pair_title,
),
subtitle = resourceReference(
com.tangem.feature.swap.presentation.R.string.express_swap_not_supported_text,
com.tangem.feature.swap.presentation.R.string.warning_express_unsupported_pair_description,
),
)

View file

@ -197,12 +197,15 @@ internal class StateBuilder(
uiStateHolder: SwapStateHolder,
fromToken: CryptoCurrencyStatus,
toToken: CryptoCurrencyStatus,
fromAccount: Account.CryptoPortfolio?,
toAccount: Account.CryptoPortfolio?,
): SwapStateHolder {
if (uiStateHolder.sendCardData !is SwapCardState.SwapCardData) return uiStateHolder
return uiStateHolder.copy(
sendCardData = SwapCardState.SwapCardData(
type = requireNotNull(uiStateHolder.sendCardData.type as? TransactionCardType.Inputtable),
type = requireNotNull(uiStateHolder.sendCardData.type as? TransactionCardType.Inputtable).copy(
accountTitleUM = getFromCardAccountTitle(fromAccount),
),
amountTextFieldValue = null,
amountEquivalent = "0 ${appCurrencyProvider.invoke().symbol}",
token = fromToken,
@ -233,7 +236,7 @@ internal class StateBuilder(
networkIconRes = getActiveIconRes(toToken.currency.network.rawId),
isBalanceHidden = isBalanceHiddenProvider(),
),
notifications = notificationsFactory.getSwapNotSupportedNotifications(toToken.currency.name),
notifications = notificationsFactory.getSwapNotSupportedNotifications(),
fee = FeeItemState.Empty,
swapButton = SwapButton(
walletInteractionIcon = walletInterationIcon(userWalletProvider()),