Updated on 2026-08-14

This commit is contained in:
Tangem 2026-05-18 20:19:17 +05:00
commit 4ce200b8d4
9 changed files with 58 additions and 30 deletions

View file

@ -185,6 +185,7 @@ internal class SwapModel @Inject constructor(
appCurrencyProvider = Provider(selectedAppCurrencyFlow::value),
isAccountsModeProvider = Provider { isAccountsMode },
iGaslessFeeSupportedForNetwork = iGaslessFeeSupportedForNetwork,
appRouter = appRouter,
)
private val inputNumberFormatter = InputNumberFormatter(
@ -1499,16 +1500,6 @@ internal class SwapModel @Inject constructor(
)
}
},
onBuyClick = {
val fromSwapCurrencyStatus = dataState.fromSwapCurrencyStatus ?: return@UiActions
val feePaidCryptoCurrency = dataState.feePaidCryptoCurrency ?: return@UiActions
val route = AppRoute.CurrencyDetails(
userWalletId = fromSwapCurrencyStatus.userWalletId,
currency = feePaidCryptoCurrency.currency,
)
appRouter.push(route)
},
onRetryClick = {
startLoadingQuotesFromLastState()
},

View file

@ -1,5 +1,7 @@
package com.tangem.feature.swap.model
import com.tangem.common.routing.AppRoute
import com.tangem.common.routing.AppRouter
import com.tangem.common.ui.notifications.NotificationUM
import com.tangem.common.ui.notifications.NotificationsFactory.addDustWarningNotification
import com.tangem.common.ui.notifications.NotificationsFactory.addExistentialWarningNotification
@ -106,6 +108,7 @@ internal class SwapNotificationsFactory(
selectedFeeType: FeeType,
providerName: String,
hideFee: Boolean,
appRouter: AppRouter,
): ImmutableList<NotificationUM> {
val warnings = buildList {
maybeAddRentExemptionError(quoteModel)
@ -113,7 +116,12 @@ internal class SwapNotificationsFactory(
maybeAddNeedReserveToCreateAccountWarning(quoteModel)
maybeAddPermissionNeededWarning(quoteModel, providerName)
maybeAddNetworkFeeCoverageWarning(quoteModel, selectedFeeType)
maybeAddUnableCoverFeeWarning(quoteModel, feeCryptoCurrencyStatus, hideFee)
maybeAddUnableCoverFeeWarning(
quoteModel = quoteModel,
feeCryptoCurrencyStatus = feeCryptoCurrencyStatus,
hideFee = hideFee,
appRouter = appRouter,
)
maybeAddTransactionInProgressWarning(quoteModel)
maybeAddPriceImpactNotification(quoteModel.priceImpact)
}
@ -299,17 +307,20 @@ internal class SwapNotificationsFactory(
}
}
@Suppress("CyclomaticComplexMethod")
private fun MutableList<NotificationUM>.maybeAddUnableCoverFeeWarning(
quoteModel: SwapState.QuotesLoadedState,
feeCryptoCurrencyStatus: CryptoCurrencyStatus?,
hideFee: Boolean,
appRouter: AppRouter,
) {
if (hideFee) return
val fromCurrency = quoteModel.fromTokenInfo.swapCurrencyStatus.currency
if (hideFee || feeCryptoCurrencyStatus == null) return
val fromSwapCurrency = quoteModel.fromTokenInfo.swapCurrencyStatus
val fromCurrency = fromSwapCurrency.currency
val feeEnoughState = quoteModel.preparedSwapConfigState.feeState as? SwapFeeState.NotEnough
val shouldShowCoverWarning = !quoteModel.preparedSwapConfigState.isBalanceEnough &&
quoteModel.permissionState !is PermissionDataState.PermissionLoading &&
feeCryptoCurrencyStatus?.currency != fromCurrency
feeCryptoCurrencyStatus.currency != fromCurrency
val isCEXProvider = quoteModel.swapProvider.type == ExchangeProviderType.CEX
@ -320,13 +331,25 @@ internal class SwapNotificationsFactory(
if (shouldShowCoverWarning && !isGaslessAvailable || isNotEnoughFee) {
add(
SwapNotificationUM.Error.UnableToCoverFeeWarning(
fromToken = fromCurrency,
feeCurrency = feeCryptoCurrencyStatus?.currency,
currencyName = feeEnoughState?.currencyName ?: fromCurrency.network.name,
currencySymbol = feeEnoughState?.currencySymbol ?: fromCurrency.network.currencySymbol,
onConfirmClick = actions.onBuyClick,
),
if (fromCurrency.id == feeCryptoCurrencyStatus.currency.id) {
SwapNotificationUM.Error.InsufficientFunds
} else {
val route = AppRoute.CurrencyDetails(
userWalletId = fromSwapCurrency.userWalletId,
currency = feeCryptoCurrencyStatus.currency,
)
SwapNotificationUM.Error.UnableToCoverFeeWarning(
fromToken = fromCurrency,
feeCurrency = feeCryptoCurrencyStatus.currency,
currencyName = feeEnoughState?.currencyName ?: fromCurrency.network.name,
currencySymbol = feeEnoughState?.currencySymbol ?: fromCurrency.network.currencySymbol,
onConfirmClick = if (!appRouter.stack.contains(route)) {
{ appRouter.push(route) }
} else {
null
},
)
},
)
}
}

View file

@ -20,7 +20,6 @@ internal data class UiActions(
val onSelectFeeType: (TxFee.Legacy) -> Unit,
val onProviderClick: (String) -> Unit,
val onProviderSelect: (String) -> Unit,
val onBuyClick: () -> Unit,
val onSelectTokenClick: (TokenSelectionDirection) -> Unit,
val onSuccess: () -> Unit,
val onLinkClick: (String) -> Unit,

View file

@ -55,12 +55,17 @@ internal object SwapNotificationUM {
),
)
data object InsufficientFunds : Error(
title = resourceReference(R.string.swapping_insufficient_funds),
subtitle = resourceReference(R.string.swapping_insufficient_funds_description),
)
data class UnableToCoverFeeWarning(
val fromToken: CryptoCurrency,
val currencyName: String,
val currencySymbol: String,
val feeCurrency: CryptoCurrency?,
val onConfirmClick: () -> Unit,
val feeCurrency: CryptoCurrency,
val onConfirmClick: (() -> Unit)?,
) : Error(
title = resourceReference(
R.string.warning_express_not_enough_fee_for_token_tx_title,
@ -71,7 +76,7 @@ internal object SwapNotificationUM {
wrappedList(currencyName, currencySymbol),
),
iconResId = fromToken.networkIconResId,
buttonState = feeCurrency?.let {
buttonState = onConfirmClick?.let {
NotificationConfig.ButtonsState.SecondaryButtonConfig(
text = resourceReference(R.string.common_buy_currency, wrappedList(currencySymbol)),
onClick = onConfirmClick,

View file

@ -3,6 +3,7 @@ package com.tangem.feature.swap.ui
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.TextRange
import androidx.compose.ui.text.input.TextFieldValue
import com.tangem.common.routing.AppRouter
import com.tangem.common.ui.account.AccountIconUM
import com.tangem.common.ui.account.AccountTitleUM
import com.tangem.common.ui.account.CryptoPortfolioIconConverter
@ -58,6 +59,7 @@ internal class StateBuilder(
private val appCurrencyProvider: Provider<AppCurrency>,
private val isAccountsModeProvider: Provider<Boolean>,
private val iGaslessFeeSupportedForNetwork: IsGaslessFeeSupportedForNetwork,
private val appRouter: AppRouter,
) {
private val iconStateConverter by lazy(::CryptoCurrencyToIconStateConverter)
@ -463,6 +465,7 @@ internal class StateBuilder(
selectedFeeType = selectedFeeType,
providerName = swapProvider.name,
hideFee = hideFee,
appRouter = appRouter,
)
val fromAccountTitleUM = when {

View file

@ -1,6 +1,7 @@
package com.tangem.feature.swap
import com.google.common.truth.Truth.assertThat
import com.tangem.common.routing.AppRouter
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.express.models.ExpressError
import com.tangem.domain.models.account.Account
@ -31,6 +32,7 @@ internal class StateBuilderInitialStateTest {
private val appCurrencyProvider: Provider<AppCurrency> = mockk()
private val isAccountsModeProvider: Provider<Boolean> = mockk()
private val iGaslessFeeSupportedForNetwork: IsGaslessFeeSupportedForNetwork = mockk()
private val appRouter: AppRouter = mockk()
private lateinit var sut: StateBuilder
@ -48,6 +50,7 @@ internal class StateBuilderInitialStateTest {
appCurrencyProvider = appCurrencyProvider,
isAccountsModeProvider = isAccountsModeProvider,
iGaslessFeeSupportedForNetwork = iGaslessFeeSupportedForNetwork,
appRouter = appRouter
)
}

View file

@ -1,6 +1,7 @@
package com.tangem.feature.swap
import com.google.common.truth.Truth.assertThat
import com.tangem.common.routing.AppRouter
import com.tangem.core.ui.extensions.stringReference
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.models.wallet.UserWallet
@ -26,6 +27,7 @@ internal class StateBuilderPairsTest {
private val appCurrencyProvider: Provider<AppCurrency> = mockk()
private val isAccountsModeProvider: Provider<Boolean> = mockk()
private val iGaslessFeeSupportedForNetwork: IsGaslessFeeSupportedForNetwork = mockk()
private val appRouter: AppRouter = mockk()
private lateinit var sut: StateBuilder
@ -53,6 +55,7 @@ internal class StateBuilderPairsTest {
appCurrencyProvider = appCurrencyProvider,
isAccountsModeProvider = isAccountsModeProvider,
iGaslessFeeSupportedForNetwork = iGaslessFeeSupportedForNetwork,
appRouter = appRouter,
)
}

View file

@ -1,8 +1,8 @@
package com.tangem.feature.swap
import com.google.common.truth.Truth.assertThat
import com.tangem.common.routing.AppRouter
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.swap.models.SwapCurrencyStatus
@ -14,12 +14,10 @@ import com.tangem.feature.swap.domain.models.ui.*
import com.tangem.feature.swap.models.*
import com.tangem.feature.swap.models.states.FeeItemState
import com.tangem.feature.swap.models.states.ProviderState
import com.tangem.feature.swap.models.states.SwapNotificationUM
import com.tangem.feature.swap.ui.StateBuilder
import com.tangem.utils.Provider
import io.mockk.every
import io.mockk.mockk
import kotlinx.collections.immutable.persistentListOf
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
@ -32,6 +30,7 @@ internal class StateBuilderQuotesTest {
private val appCurrencyProvider: Provider<AppCurrency> = mockk()
private val isAccountsModeProvider: Provider<Boolean> = mockk()
private val iGaslessFeeSupportedForNetwork: IsGaslessFeeSupportedForNetwork = mockk()
private val appRouter: AppRouter = mockk()
private lateinit var sut: StateBuilder
@ -60,6 +59,7 @@ internal class StateBuilderQuotesTest {
appCurrencyProvider = appCurrencyProvider,
isAccountsModeProvider = isAccountsModeProvider,
iGaslessFeeSupportedForNetwork = iGaslessFeeSupportedForNetwork,
appRouter = appRouter,
)
}

View file

@ -1,16 +1,15 @@
package com.tangem.feature.swap
import com.google.common.truth.Truth.assertThat
import com.tangem.common.routing.AppRouter
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.swap.models.SwapCurrencyStatus
import com.tangem.domain.transaction.usecase.gasless.IsGaslessFeeSupportedForNetwork
import com.tangem.feature.swap.domain.models.domain.*
import com.tangem.feature.swap.domain.models.ui.*
import com.tangem.feature.swap.model.SwapProcessDataState
import com.tangem.feature.swap.models.*
import com.tangem.feature.swap.models.states.FeeItemState
import com.tangem.feature.swap.models.states.ProviderState
import com.tangem.feature.swap.models.states.SwapNotificationUM
import com.tangem.feature.swap.ui.StateBuilder
@ -31,6 +30,7 @@ internal class StateBuilderSwapDataTest {
private val appCurrencyProvider: Provider<AppCurrency> = mockk()
private val isAccountsModeProvider: Provider<Boolean> = mockk()
private val iGaslessFeeSupportedForNetwork: IsGaslessFeeSupportedForNetwork = mockk()
private val appRouter: AppRouter = mockk()
private lateinit var sut: StateBuilder
@ -59,6 +59,7 @@ internal class StateBuilderSwapDataTest {
appCurrencyProvider = appCurrencyProvider,
isAccountsModeProvider = isAccountsModeProvider,
iGaslessFeeSupportedForNetwork = iGaslessFeeSupportedForNetwork,
appRouter = appRouter,
)
}