Updated on 2026-08-14

This commit is contained in:
Tangem 2026-06-25 14:47:02 +04:00
parent f229849259
commit dacc49b17e
7 changed files with 241 additions and 5 deletions

View file

@ -78,6 +78,7 @@ import com.tangem.domain.tangempay.TangemPayWithdrawWithSwapUseCase
import com.tangem.domain.tokens.GetMinimumTransactionAmountSyncUseCase
import com.tangem.domain.tokens.UpdateDelayedNetworkStatusUseCase
import com.tangem.domain.transaction.error.GetFeeError
import com.tangem.domain.transaction.models.AssetRequirementsCondition
import com.tangem.domain.transaction.models.TransactionFeeExtended
import com.tangem.domain.transaction.usecase.gasless.IsGaslessFeeSupportedForNetwork
import com.tangem.domain.txhistory.usecase.GetExplorerTransactionUrlUseCase
@ -650,7 +651,7 @@ internal class SwapModel @Inject constructor(
pairs = dataState.pairs,
)
if (toProvidersList.isEmpty()) {
handleSwapNotSupported(
handlePairUnavailable(
fromSwapCurrencyStatus = newFromSwapCurrencyStatus,
toSwapCurrencyStatus = newToSwapCurrencyStatus,
)
@ -714,7 +715,7 @@ internal class SwapModel @Inject constructor(
pairs = pairs,
)
if (providerList.isEmpty()) {
handleSwapNotSupported(
handlePairUnavailable(
fromSwapCurrencyStatus = fromSwapCurrencyStatus,
toSwapCurrencyStatus = toSwapCurrencyStatus,
)
@ -927,6 +928,16 @@ internal class SwapModel @Inject constructor(
)
return
}
if (toProvidersList.isEmpty()) {
modelScope.launch {
handlePairUnavailable(
fromSwapCurrencyStatus = fromSwapCurrencyStatus,
toSwapCurrencyStatus = toSwapCurrencyStatus,
)
}
return
}
if (!isSilent) {
uiState = stateBuilder.createQuotesLoadingState(
fromSwapCurrencyStatus = fromSwapCurrencyStatus,
@ -1318,6 +1329,15 @@ internal class SwapModel @Inject constructor(
return
}
modelScope.launch(dispatchers.main) {
val toRequirement = swapInteractor.getUnfulfilledReceiveRequirement(toSwapCurrencyStatus)
if (toRequirement != null) {
handleDestinationRequirementBlocked(
fromSwapCurrencyStatus = fromSwapCurrencyStatus,
toSwapCurrencyStatus = toSwapCurrencyStatus,
requirement = toRequirement,
)
return@launch
}
runCatching(dispatchers.io) {
swapInteractor.onSwap(
fromSwapCurrencyStatus = fromSwapCurrencyStatus,
@ -2238,6 +2258,50 @@ internal class SwapModel @Inject constructor(
)
}
private suspend fun handlePairUnavailable(
fromSwapCurrencyStatus: SwapCurrencyStatus,
toSwapCurrencyStatus: SwapCurrencyStatus,
) {
val toRequirement = swapInteractor.getUnfulfilledReceiveRequirement(toSwapCurrencyStatus)
if (toRequirement != null) {
handleDestinationRequirementBlocked(
fromSwapCurrencyStatus = fromSwapCurrencyStatus,
toSwapCurrencyStatus = toSwapCurrencyStatus,
requirement = toRequirement,
)
} else {
handleSwapNotSupported(
fromSwapCurrencyStatus = fromSwapCurrencyStatus,
toSwapCurrencyStatus = toSwapCurrencyStatus,
)
}
}
private fun handleDestinationRequirementBlocked(
fromSwapCurrencyStatus: SwapCurrencyStatus,
toSwapCurrencyStatus: SwapCurrencyStatus,
requirement: AssetRequirementsCondition,
) {
singleTaskScheduler.cancelTask()
lastReducedBalanceBy.value = BigDecimal.ZERO
lastAmount.value = INITIAL_AMOUNT
isFiatInput.value = false
uiState = stateBuilder.createDestinationRequirementBlockedState(
uiStateHolder = uiState,
fromSwapCurrencyStatus = fromSwapCurrencyStatus,
toSwapCurrencyStatus = toSwapCurrencyStatus,
requirement = requirement,
onAssociateClick = {
appRouter.push(
AppRoute.CurrencyDetails(
userWalletId = toSwapCurrencyStatus.userWalletId,
currency = toSwapCurrencyStatus.currency,
),
)
},
)
}
private fun handleSwapNotSupported(
fromSwapCurrencyStatus: SwapCurrencyStatus,
toSwapCurrencyStatus: SwapCurrencyStatus,

View file

@ -20,6 +20,7 @@ import com.tangem.domain.express.models.ExpressError
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.transaction.error.GetFeeError
import com.tangem.domain.transaction.models.AssetRequirementsCondition
import com.tangem.domain.transaction.usecase.gasless.IsGaslessFeeSupportedForNetwork
import com.tangem.feature.swap.domain.models.ExpressDataError
import com.tangem.feature.swap.domain.models.SwapAmount
@ -78,6 +79,19 @@ internal class SwapNotificationsFactory(
)
}
fun getDestinationRequirementNotifications(
requirement: AssetRequirementsCondition,
onAssociateClick: () -> Unit,
): ImmutableList<NotificationUM> {
val notification = when (requirement) {
is AssetRequirementsCondition.RequiredTrustline ->
SwapNotificationUM.Warning.TokenTrustlineRequired(onAssociateClick)
else ->
SwapNotificationUM.Warning.TokenAssociationRequired(onAssociateClick)
}
return persistentListOf(notification)
}
fun getQuotesErrorStateNotifications(
expressDataError: ExpressDataError,
fromToken: CryptoCurrency,

View file

@ -149,6 +149,28 @@ internal object SwapNotificationUM {
),
)
data class TokenAssociationRequired(
val onAssociateClick: () -> Unit,
) : Warning(
title = resourceReference(R.string.warning_hedera_missing_token_association_title),
subtitle = resourceReference(R.string.warning_receive_blocked_hedera_token_association_required_message),
buttonsState = NotificationConfig.ButtonsState.SecondaryButtonConfig(
text = resourceReference(R.string.warning_hedera_missing_token_association_button_title),
onClick = onAssociateClick,
),
)
data class TokenTrustlineRequired(
val onAssociateClick: () -> Unit,
) : Warning(
title = resourceReference(R.string.warning_token_trustline_title),
subtitle = resourceReference(R.string.warning_receive_blocked_token_trustline_required_message),
buttonsState = NotificationConfig.ButtonsState.SecondaryButtonConfig(
text = resourceReference(R.string.warning_token_trustline_button_title),
onClick = onAssociateClick,
),
)
data class NeedReserveToCreateAccount(
val receiveAmount: String,
val receiveToken: String,

View file

@ -35,6 +35,7 @@ import com.tangem.domain.swap.models.PredefinedPercentAmount
import com.tangem.domain.swap.models.SwapCurrencyStatus
import com.tangem.domain.tokens.model.Amount
import com.tangem.domain.transaction.error.GetFeeError
import com.tangem.domain.transaction.models.AssetRequirementsCondition
import com.tangem.domain.transaction.usecase.gasless.IsGaslessFeeSupportedForNetwork
import com.tangem.feature.swap.converters.SwapProviderResolver
import com.tangem.feature.swap.converters.SwapProviderStateBuilder
@ -455,6 +456,34 @@ internal class StateBuilder(
uiStateHolder: SwapStateHolder,
fromSwapCurrencyStatus: SwapCurrencyStatus,
toSwapCurrencyStatus: SwapCurrencyStatus,
): SwapStateHolder = createBlockedSwapState(
uiStateHolder = uiStateHolder,
fromSwapCurrencyStatus = fromSwapCurrencyStatus,
toSwapCurrencyStatus = toSwapCurrencyStatus,
notifications = notificationsFactory.getSwapNotSupportedNotifications(),
)
fun createDestinationRequirementBlockedState(
uiStateHolder: SwapStateHolder,
fromSwapCurrencyStatus: SwapCurrencyStatus,
toSwapCurrencyStatus: SwapCurrencyStatus,
requirement: AssetRequirementsCondition,
onAssociateClick: () -> Unit,
): SwapStateHolder = createBlockedSwapState(
uiStateHolder = uiStateHolder,
fromSwapCurrencyStatus = fromSwapCurrencyStatus,
toSwapCurrencyStatus = toSwapCurrencyStatus,
notifications = notificationsFactory.getDestinationRequirementNotifications(
requirement = requirement,
onAssociateClick = onAssociateClick,
),
)
private fun createBlockedSwapState(
uiStateHolder: SwapStateHolder,
fromSwapCurrencyStatus: SwapCurrencyStatus,
toSwapCurrencyStatus: SwapCurrencyStatus,
notifications: ImmutableList<NotificationUM>,
): SwapStateHolder {
if (uiStateHolder.sendCardData !is SwapCardState.SwapCardData) return uiStateHolder
return uiStateHolder.copy(
@ -482,7 +511,7 @@ internal class StateBuilder(
isBalanceHidden = isBalanceHiddenProvider(),
appCurrency = appCurrencyProvider(),
),
notifications = notificationsFactory.getSwapNotSupportedNotifications(),
notifications = notifications,
swapButton = SwapButton(
walletInteractionIcon = walletInterationIcon(fromSwapCurrencyStatus.userWallet),
isEnabled = false,