Updated on 2026-08-14
This commit is contained in:
parent
a107ddd36e
commit
a00ea0a438
14 changed files with 696 additions and 843 deletions
|
|
@ -1,7 +1,6 @@
|
|||
package com.tangem.common.ui.notifications
|
||||
|
||||
import com.tangem.blockchain.common.BlockchainSdkError
|
||||
import com.tangem.blockchain.common.transaction.Fee
|
||||
import com.tangem.common.ui.R
|
||||
import com.tangem.common.ui.amountScreen.models.AmountFieldModel
|
||||
import com.tangem.common.ui.amountScreen.utils.getFiatString
|
||||
|
|
@ -248,9 +247,9 @@ object NotificationsFactory {
|
|||
|
||||
fun MutableList<NotificationUM>.addValidateTransactionNotifications(
|
||||
dustValue: BigDecimal,
|
||||
fee: Fee?,
|
||||
validationError: Throwable?,
|
||||
cryptoCurrency: CryptoCurrency,
|
||||
minAdaValue: BigDecimal?, // TODO revert to Fee, after swap TxFee refactored
|
||||
onReduceClick: (
|
||||
reduceAmountTo: BigDecimal,
|
||||
notification: Class<out NotificationUM>,
|
||||
|
|
@ -266,11 +265,11 @@ object NotificationsFactory {
|
|||
error = validationError,
|
||||
onReduceClick = onReduceClick,
|
||||
)
|
||||
null -> (fee as? Fee.CardanoToken)?.let {
|
||||
null -> minAdaValue?.let {
|
||||
add(
|
||||
NotificationUM.Cardano.MinAdaValueCharged(
|
||||
tokenName = cryptoCurrency.name,
|
||||
minAdaValue = it.minAdaValue.parseBigDecimal(cryptoCurrency.decimals),
|
||||
minAdaValue = minAdaValue.parseBigDecimal(cryptoCurrency.decimals),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package com.tangem.features.send.impl.presentation.state.confirm
|
||||
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.transaction.Fee
|
||||
import com.tangem.blockchain.common.transaction.TransactionFee
|
||||
import com.tangem.blockchainsdk.utils.minimalAmount
|
||||
import com.tangem.common.ui.amountScreen.models.AmountState
|
||||
|
|
@ -246,7 +247,7 @@ internal class SendNotificationFactory(
|
|||
)
|
||||
addValidateTransactionNotifications(
|
||||
dustValue = currencyCheck.dustValue.orZero(),
|
||||
fee = feeState.fee,
|
||||
minAdaValue = (feeState.fee as? Fee.CardanoToken)?.minAdaValue,
|
||||
validationError = validationError,
|
||||
cryptoCurrency = currency,
|
||||
onReduceClick = clickIntents::onAmountReduceToClick,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.features.staking.impl.presentation.state.transformers.notifications
|
||||
|
||||
import com.tangem.blockchain.common.transaction.Fee
|
||||
import com.tangem.common.ui.amountScreen.models.AmountState
|
||||
import com.tangem.common.ui.notifications.NotificationUM
|
||||
import com.tangem.common.ui.notifications.NotificationsFactory.addDustWarningNotification
|
||||
|
|
@ -212,7 +213,7 @@ internal class AddStakingNotificationsTransformer(
|
|||
// blockchain specific
|
||||
addValidateTransactionNotifications(
|
||||
dustValue = currencyCheck.dustValue.orZero(),
|
||||
fee = feeState?.fee,
|
||||
minAdaValue = (feeState?.fee as? Fee.CardanoToken)?.minAdaValue,
|
||||
validationError = validatorError,
|
||||
cryptoCurrency = cryptoCurrency,
|
||||
onReduceClick = prevState.clickIntents::onAmountReduceToClick,
|
||||
|
|
|
|||
|
|
@ -1,24 +0,0 @@
|
|||
package com.tangem.feature.swap.domain.models.domain
|
||||
|
||||
import java.math.BigDecimal
|
||||
|
||||
sealed class Warning {
|
||||
|
||||
data class ExistentialDepositWarning(
|
||||
val existentialDeposit: BigDecimal,
|
||||
val minAvailableAmount: BigDecimal,
|
||||
) : Warning()
|
||||
|
||||
data class MinAmountWarning(val dustValue: BigDecimal) : Warning()
|
||||
|
||||
data class ReduceAmountWarning(val tezosFeeThreshold: BigDecimal) : Warning()
|
||||
|
||||
sealed class Cardano : Warning() {
|
||||
|
||||
data class MinAdaValueCharged(val tokenName: String, val minAdaValue: String) : Cardano()
|
||||
|
||||
data object InsufficientBalanceToTransferCoin : Cardano()
|
||||
|
||||
data class InsufficientBalanceToTransferToken(val tokenName: String) : Cardano()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
package com.tangem.feature.swap.domain.models.ui
|
||||
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.domain.tokens.model.warnings.CryptoCurrencyCheck
|
||||
import com.tangem.feature.swap.domain.models.ExpressDataError
|
||||
import com.tangem.feature.swap.domain.models.SwapAmount
|
||||
import com.tangem.feature.swap.domain.models.domain.*
|
||||
|
|
@ -26,7 +27,9 @@ sealed interface SwapState {
|
|||
val permissionState: PermissionDataState = PermissionDataState.Empty,
|
||||
val swapDataModel: SwapDataModel? = null,
|
||||
val txFee: TxFeeState,
|
||||
val warnings: List<Warning> = emptyList(),
|
||||
val currencyCheck: CryptoCurrencyCheck? = null,
|
||||
val validationResult: Throwable? = null,
|
||||
val minAdaValue: BigDecimal?,
|
||||
val swapProvider: SwapProvider,
|
||||
) : SwapState
|
||||
|
||||
|
|
|
|||
|
|
@ -2,23 +2,23 @@ package com.tangem.feature.swap.domain
|
|||
|
||||
import arrow.core.Either
|
||||
import arrow.core.getOrElse
|
||||
import com.tangem.blockchain.common.*
|
||||
import com.tangem.blockchain.common.Amount
|
||||
import com.tangem.blockchain.common.AmountType
|
||||
import com.tangem.blockchain.common.Blockchain.*
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.TransactionExtras
|
||||
import com.tangem.blockchain.common.transaction.Fee
|
||||
import com.tangem.blockchain.common.transaction.TransactionFee
|
||||
import com.tangem.blockchainsdk.utils.fromNetworkId
|
||||
import com.tangem.blockchainsdk.utils.minimalAmount
|
||||
import com.tangem.core.ui.utils.BigDecimalFormatter
|
||||
import com.tangem.core.ui.utils.parseBigDecimal
|
||||
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
|
||||
import com.tangem.domain.appcurrency.extenstions.unwrap
|
||||
import com.tangem.domain.appcurrency.repository.AppCurrencyRepository
|
||||
import com.tangem.domain.demo.IsDemoCardUseCase
|
||||
import com.tangem.domain.tokens.GetCryptoCurrencyStatusesSyncUseCase
|
||||
import com.tangem.domain.tokens.GetCurrencyCheckUseCase
|
||||
import com.tangem.domain.tokens.model.*
|
||||
import com.tangem.domain.tokens.model.FeePaidCurrency
|
||||
import com.tangem.domain.tokens.model.warnings.CryptoCurrencyCheck
|
||||
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
||||
import com.tangem.domain.tokens.repository.CurrencyChecksRepository
|
||||
import com.tangem.domain.tokens.repository.QuotesRepository
|
||||
|
|
@ -36,7 +36,6 @@ import com.tangem.feature.swap.domain.models.SwapAmount
|
|||
import com.tangem.feature.swap.domain.models.domain.*
|
||||
import com.tangem.feature.swap.domain.models.toStringWithRightOffset
|
||||
import com.tangem.feature.swap.domain.models.ui.*
|
||||
import com.tangem.lib.crypto.BlockchainUtils
|
||||
import com.tangem.lib.crypto.TransactionManager
|
||||
import com.tangem.lib.crypto.UserWalletManager
|
||||
import com.tangem.lib.crypto.models.ProxyAmount
|
||||
|
|
@ -71,6 +70,7 @@ internal class SwapInteractorImpl @AssistedInject constructor(
|
|||
private val validateTransactionUseCase: ValidateTransactionUseCase,
|
||||
private val estimateFeeUseCase: EstimateFeeUseCase,
|
||||
private val getUserWalletUseCase: GetUserWalletUseCase,
|
||||
private val getCurrencyCheckUseCase: GetCurrencyCheckUseCase,
|
||||
private val amountFormatter: AmountFormatter,
|
||||
@Assisted private val userWalletId: UserWalletId,
|
||||
) : SwapInteractor {
|
||||
|
|
@ -386,111 +386,30 @@ internal class SwapInteractorImpl @AssistedInject constructor(
|
|||
fromTokenStatus: CryptoCurrencyStatus,
|
||||
amount: SwapAmount,
|
||||
feeState: TxFeeState,
|
||||
minAdaValue: BigDecimal?,
|
||||
): List<Warning> {
|
||||
val fromToken = fromTokenStatus.currency
|
||||
val warnings = mutableListOf<Warning>()
|
||||
manageExistentialDepositWarning(warnings, userWalletId, amount, fromToken, feeState)
|
||||
manageDustWarning(warnings, feeState, userWalletId, fromTokenStatus, amount)
|
||||
manageReduceAmountWarning(warnings, fromTokenStatus, amount)
|
||||
manageTransactionValidationWarnings(
|
||||
warnings = warnings,
|
||||
fromToken = fromToken,
|
||||
amount = amount,
|
||||
feeState = feeState,
|
||||
userWalletId = userWalletId,
|
||||
minAdaValue = minAdaValue,
|
||||
)
|
||||
return warnings
|
||||
}
|
||||
|
||||
private suspend fun manageExistentialDepositWarning(
|
||||
warnings: MutableList<Warning>,
|
||||
userWalletId: UserWalletId,
|
||||
amount: SwapAmount,
|
||||
fromToken: CryptoCurrency,
|
||||
txFee: TxFeeState,
|
||||
) {
|
||||
val existentialDeposit = currencyChecksRepository.getExistentialDeposit(userWalletId, fromToken.network)
|
||||
if (existentialDeposit != null) {
|
||||
val nativeBalance = userWalletManager.getNativeTokenBalance(
|
||||
fromToken.network.backendId,
|
||||
fromToken.network.derivationPath.value,
|
||||
) ?: ProxyAmount.empty()
|
||||
// ignore if amount is bigger than balance
|
||||
if (amount.value > nativeBalance.value) {
|
||||
return
|
||||
}
|
||||
val fee = when (txFee) {
|
||||
TxFeeState.Empty -> BigDecimal.ZERO
|
||||
is TxFeeState.MultipleFeeState -> txFee.priorityFee.feeValue
|
||||
is TxFeeState.SingleFeeState -> txFee.fee.feeValue
|
||||
}
|
||||
val minAvailableAmount = nativeBalance.value - existentialDeposit - fee
|
||||
if (nativeBalance.value.minus(amount.value + fee) < existentialDeposit) {
|
||||
warnings.add(Warning.ExistentialDepositWarning(existentialDeposit, minAvailableAmount))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun manageDustWarning(
|
||||
warnings: MutableList<Warning>,
|
||||
feeState: TxFeeState,
|
||||
userWalletId: UserWalletId,
|
||||
fromTokenStatus: CryptoCurrencyStatus,
|
||||
amount: SwapAmount,
|
||||
) {
|
||||
if (BlockchainUtils.isCardano(fromTokenStatus.currency.network.id.value)) return
|
||||
|
||||
): CryptoCurrencyCheck {
|
||||
val fee = when (feeState) {
|
||||
TxFeeState.Empty -> BigDecimal.ZERO
|
||||
is TxFeeState.MultipleFeeState -> feeState.priorityFee.feeValue
|
||||
is TxFeeState.SingleFeeState -> feeState.fee.feeValue
|
||||
}
|
||||
|
||||
val dustValue = currencyChecksRepository.getDustValue(userWalletId, fromTokenStatus.currency.network) ?: return
|
||||
val currencyCheck = getCurrencyCheckUseCase(
|
||||
userWalletId = userWalletId,
|
||||
currencyStatus = fromTokenStatus,
|
||||
amount = amount.value,
|
||||
fee = fee,
|
||||
)
|
||||
|
||||
val change = when (fromTokenStatus.currency) {
|
||||
is CryptoCurrency.Coin -> {
|
||||
val balance = fromTokenStatus.value.amount ?: BigDecimal.ZERO
|
||||
balance - (fee + amount.value)
|
||||
}
|
||||
is CryptoCurrency.Token -> {
|
||||
val nativeTokenBalance = userWalletManager.getNativeTokenBalance(
|
||||
fromTokenStatus.currency.network.id.value,
|
||||
fromTokenStatus.currency.network.derivationPath.value,
|
||||
)
|
||||
|
||||
nativeTokenBalance?.value?.minus(fee) ?: BigDecimal.ZERO
|
||||
}
|
||||
}
|
||||
|
||||
val isChangeLowerThanDust = change < dustValue && change > BigDecimal.ZERO
|
||||
|
||||
if (amount.value < dustValue || isChangeLowerThanDust) {
|
||||
warnings.add(Warning.MinAmountWarning(dustValue))
|
||||
}
|
||||
}
|
||||
|
||||
private fun manageReduceAmountWarning(
|
||||
warnings: MutableList<Warning>,
|
||||
fromTokenStatus: CryptoCurrencyStatus,
|
||||
amount: SwapAmount,
|
||||
) {
|
||||
val isTezos = fromTokenStatus.currency.network.id.value == Blockchain.Tezos.id
|
||||
if (isTezos && amount.value == fromTokenStatus.value.amount) {
|
||||
warnings.add(Warning.ReduceAmountWarning(Blockchain.Tezos.minimalAmount()))
|
||||
}
|
||||
return currencyCheck
|
||||
}
|
||||
|
||||
private suspend fun manageTransactionValidationWarnings(
|
||||
warnings: MutableList<Warning>,
|
||||
fromToken: CryptoCurrency,
|
||||
fromToken: CryptoCurrencyStatus,
|
||||
amount: SwapAmount,
|
||||
feeState: TxFeeState,
|
||||
userWalletId: UserWalletId,
|
||||
minAdaValue: BigDecimal?,
|
||||
) {
|
||||
): Throwable? {
|
||||
val currency = fromToken.currency
|
||||
val fee = Fee.Common(
|
||||
amount = Amount(
|
||||
value = when (feeState) {
|
||||
|
|
@ -498,66 +417,20 @@ internal class SwapInteractorImpl @AssistedInject constructor(
|
|||
is TxFeeState.MultipleFeeState -> feeState.normalFee.feeValue
|
||||
is TxFeeState.SingleFeeState -> feeState.fee.feeValue
|
||||
},
|
||||
blockchain = Blockchain.fromId(fromToken.network.id.value),
|
||||
blockchain = Blockchain.fromId(currency.network.id.value),
|
||||
),
|
||||
)
|
||||
|
||||
validateTransactionUseCase(
|
||||
amount = amount.value.convertToSdkAmount(fromToken),
|
||||
val result = validateTransactionUseCase(
|
||||
amount = amount.value.convertToSdkAmount(currency),
|
||||
fee = fee,
|
||||
memo = null,
|
||||
destination = getTokenAddress(fromToken),
|
||||
destination = getTokenAddress(fromToken.currency),
|
||||
userWalletId = userWalletId,
|
||||
network = fromToken.network,
|
||||
).fold(
|
||||
ifLeft = {
|
||||
addCardanoTransactionValidationError(
|
||||
warnings = warnings,
|
||||
error = it as? BlockchainSdkError.Cardano ?: return@fold,
|
||||
fromToken = fromToken,
|
||||
userWalletId = userWalletId,
|
||||
)
|
||||
},
|
||||
ifRight = {
|
||||
minAdaValue?.let {
|
||||
warnings.add(
|
||||
Warning.Cardano.MinAdaValueCharged(
|
||||
tokenName = fromToken.name,
|
||||
minAdaValue = minAdaValue.parseBigDecimal(fromToken.decimals),
|
||||
),
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
network = currency.network,
|
||||
).leftOrNull()
|
||||
|
||||
private suspend fun addCardanoTransactionValidationError(
|
||||
warnings: MutableList<Warning>,
|
||||
error: BlockchainSdkError.Cardano,
|
||||
fromToken: CryptoCurrency,
|
||||
userWalletId: UserWalletId,
|
||||
) {
|
||||
when (error) {
|
||||
BlockchainSdkError.Cardano.InsufficientMinAdaBalanceToSendToken -> {
|
||||
Warning.Cardano.InsufficientBalanceToTransferToken(fromToken.name)
|
||||
}
|
||||
BlockchainSdkError.Cardano.InsufficientRemainingBalanceToWithdrawTokens -> {
|
||||
when (fromToken) {
|
||||
is CryptoCurrency.Coin -> Warning.Cardano.InsufficientBalanceToTransferCoin
|
||||
is CryptoCurrency.Token -> {
|
||||
Warning.Cardano.InsufficientBalanceToTransferToken(fromToken.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
BlockchainSdkError.Cardano.InsufficientRemainingBalance,
|
||||
BlockchainSdkError.Cardano.InsufficientSendingAdaAmount,
|
||||
-> {
|
||||
val dustValue = currencyChecksRepository.getDustValue(userWalletId, fromToken.network) ?: return
|
||||
|
||||
Warning.MinAmountWarning(dustValue)
|
||||
}
|
||||
}
|
||||
.let(warnings::add) // add warning to the list
|
||||
return result
|
||||
}
|
||||
|
||||
override suspend fun onSwap(
|
||||
|
|
@ -1238,12 +1111,18 @@ internal class SwapInteractorImpl @AssistedInject constructor(
|
|||
txFeeState = txFee,
|
||||
provider = provider,
|
||||
).copy(
|
||||
warnings = manageWarnings(
|
||||
currencyCheck = manageWarnings(
|
||||
fromTokenStatus = fromToken,
|
||||
amount = amount,
|
||||
feeState = txFee,
|
||||
minAdaValue = (transactionFee?.normal as? Fee.CardanoToken)?.minAdaValue,
|
||||
),
|
||||
validationResult = manageTransactionValidationWarnings(
|
||||
fromToken = fromToken,
|
||||
amount = amount,
|
||||
feeState = txFee,
|
||||
userWalletId = userWalletId,
|
||||
),
|
||||
minAdaValue = (transactionFee?.normal as? Fee.CardanoToken)?.minAdaValue,
|
||||
)
|
||||
|
||||
when (provider.type) {
|
||||
|
|
@ -1478,11 +1357,16 @@ internal class SwapInteractorImpl @AssistedInject constructor(
|
|||
)
|
||||
swapState.copy(
|
||||
permissionState = PermissionDataState.Empty,
|
||||
warnings = manageWarnings(
|
||||
currencyCheck = manageWarnings(
|
||||
fromTokenStatus = fromToken,
|
||||
amount = amount,
|
||||
feeState = txFeeState,
|
||||
minAdaValue = null, // no ADA in DEX
|
||||
),
|
||||
validationResult = manageTransactionValidationWarnings(
|
||||
fromToken = fromToken,
|
||||
amount = amount,
|
||||
feeState = txFeeState,
|
||||
userWalletId = userWalletId,
|
||||
),
|
||||
preparedSwapConfigState = preparedSwapConfigState,
|
||||
)
|
||||
|
|
@ -1578,6 +1462,7 @@ internal class SwapInteractorImpl @AssistedInject constructor(
|
|||
swapDataModel = swapData,
|
||||
txFee = txFeeState,
|
||||
swapProvider = provider,
|
||||
minAdaValue = null,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,9 +3,9 @@ package com.tangem.feature.swap.models
|
|||
import androidx.annotation.DrawableRes
|
||||
import androidx.compose.ui.text.input.TextFieldValue
|
||||
import com.tangem.common.ui.bottomsheet.permission.state.GiveTxPermissionState
|
||||
import com.tangem.common.ui.notifications.NotificationUM
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
|
||||
import com.tangem.core.ui.components.notifications.NotificationConfig
|
||||
import com.tangem.core.ui.event.StateEvent
|
||||
import com.tangem.core.ui.event.consumedEvent
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
|
|
@ -14,12 +14,15 @@ import com.tangem.feature.swap.domain.models.ui.PriceImpact
|
|||
import com.tangem.feature.swap.models.states.FeeItemState
|
||||
import com.tangem.feature.swap.models.states.ProviderState
|
||||
import com.tangem.feature.swap.models.states.events.SwapEvent
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
||||
internal data class SwapStateHolder(
|
||||
val sendCardData: SwapCardState,
|
||||
val receiveCardData: SwapCardState,
|
||||
val blockchainId: String, // not the same as networkId, its local id in app
|
||||
val warnings: List<SwapWarning> = emptyList(),
|
||||
val notifications: ImmutableList<NotificationUM> = persistentListOf(),
|
||||
val isInsufficientFunds: Boolean,
|
||||
val event: StateEvent<SwapEvent> = consumedEvent(),
|
||||
val changeCardsButtonState: ChangeCardsButtonState = ChangeCardsButtonState.ENABLED,
|
||||
val providerState: ProviderState,
|
||||
|
|
@ -106,35 +109,6 @@ data class LegalState(
|
|||
val onClick: (String) -> Unit,
|
||||
)
|
||||
|
||||
sealed interface SwapWarning {
|
||||
data class PermissionNeeded(val notificationConfig: NotificationConfig) : SwapWarning
|
||||
data object InsufficientFunds : SwapWarning
|
||||
data class NoAvailableTokensToSwap(val notificationConfig: NotificationConfig) : SwapWarning
|
||||
data class GenericWarning(
|
||||
val title: TextReference? = null,
|
||||
val message: TextReference? = null,
|
||||
val onClick: () -> Unit,
|
||||
) : SwapWarning
|
||||
|
||||
data class GeneralError(val notificationConfig: NotificationConfig) : SwapWarning
|
||||
data class UnableToCoverFeeWarning(val notificationConfig: NotificationConfig) : SwapWarning
|
||||
data class GeneralWarning(val notificationConfig: NotificationConfig) : SwapWarning
|
||||
data class GeneralInformational(val notificationConfig: NotificationConfig) : SwapWarning
|
||||
data class TransactionInProgressWarning(val title: TextReference, val description: TextReference) : SwapWarning
|
||||
data class NeedReserveToCreateAccount(val notificationConfig: NotificationConfig) : SwapWarning
|
||||
data class ReduceAmount(val notificationConfig: NotificationConfig) : SwapWarning
|
||||
|
||||
sealed interface Cardano : SwapWarning {
|
||||
val notificationConfig: NotificationConfig
|
||||
|
||||
data class MinAdaValueCharged(override val notificationConfig: NotificationConfig) : Cardano
|
||||
|
||||
data class InsufficientBalanceToTransferCoin(override val notificationConfig: NotificationConfig) : Cardano
|
||||
|
||||
data class InsufficientBalanceToTransferToken(override val notificationConfig: NotificationConfig) : Cardano
|
||||
}
|
||||
}
|
||||
|
||||
enum class ChangeCardsButtonState {
|
||||
ENABLED, DISABLED, UPDATE_IN_PROGRESS
|
||||
}
|
||||
|
|
@ -0,0 +1,205 @@
|
|||
package com.tangem.feature.swap.models.states
|
||||
|
||||
import com.tangem.common.ui.R
|
||||
import com.tangem.common.ui.notifications.NotificationUM
|
||||
import com.tangem.core.ui.components.notifications.NotificationConfig
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.networkIconResId
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.wrappedList
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.feature.swap.domain.models.ExpressDataError
|
||||
import com.tangem.feature.swap.utils.getExpressErrorMessage
|
||||
import com.tangem.feature.swap.utils.getExpressErrorTitle
|
||||
|
||||
internal object SwapNotificationUM {
|
||||
|
||||
sealed class Error(
|
||||
title: TextReference,
|
||||
subtitle: TextReference,
|
||||
iconResId: Int = R.drawable.ic_alert_24,
|
||||
buttonState: NotificationConfig.ButtonsState? = null,
|
||||
onCloseClick: (() -> Unit)? = null,
|
||||
) : NotificationUM.Error(
|
||||
title = title,
|
||||
subtitle = subtitle,
|
||||
iconResId = iconResId,
|
||||
buttonState = buttonState,
|
||||
onCloseClick = onCloseClick,
|
||||
) {
|
||||
data class GenericError(
|
||||
val title: TextReference = resourceReference(id = R.string.common_warning),
|
||||
val subtitle: TextReference?,
|
||||
val onConfirmClick: () -> Unit,
|
||||
) : Error(
|
||||
title = title,
|
||||
subtitle = subtitle ?: resourceReference(id = R.string.common_unknown_error),
|
||||
buttonState = NotificationConfig.ButtonsState.PrimaryButtonConfig(
|
||||
text = resourceReference(R.string.common_ok),
|
||||
onClick = onConfirmClick,
|
||||
),
|
||||
)
|
||||
|
||||
data object ApprovalInProgressWarning : Error(
|
||||
title = resourceReference(R.string.warning_express_approval_in_progress_title),
|
||||
subtitle = resourceReference(R.string.warning_express_approval_in_progress_message),
|
||||
)
|
||||
|
||||
data class TransactionInProgressWarning(
|
||||
val currencySymbol: String,
|
||||
) : Error(
|
||||
title = resourceReference(R.string.warning_express_active_transaction_title),
|
||||
subtitle = resourceReference(
|
||||
id = R.string.warning_express_active_transaction_message,
|
||||
formatArgs = wrappedList(currencySymbol),
|
||||
),
|
||||
)
|
||||
|
||||
data class UnableToCoverFeeWarning(
|
||||
val fromToken: CryptoCurrency,
|
||||
val currencyName: String,
|
||||
val currencySymbol: String,
|
||||
val feeCurrency: CryptoCurrency?,
|
||||
val onConfirmClick: (CryptoCurrency) -> Unit,
|
||||
) : Error(
|
||||
title = resourceReference(
|
||||
R.string.warning_express_not_enough_fee_for_token_tx_title,
|
||||
wrappedList(fromToken.network.name),
|
||||
),
|
||||
subtitle = resourceReference(
|
||||
R.string.warning_express_not_enough_fee_for_token_tx_description,
|
||||
wrappedList(currencyName, currencySymbol),
|
||||
),
|
||||
iconResId = fromToken.networkIconResId,
|
||||
buttonState = feeCurrency?.let {
|
||||
NotificationConfig.ButtonsState.SecondaryButtonConfig(
|
||||
text = resourceReference(R.string.common_buy_currency, wrappedList(currencySymbol)),
|
||||
onClick = { onConfirmClick(it) },
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
data class MinimalAmountError(
|
||||
val amount: String,
|
||||
) : Error(
|
||||
title = resourceReference(
|
||||
id = R.string.warning_express_too_minimal_amount_title,
|
||||
formatArgs = wrappedList(amount),
|
||||
),
|
||||
subtitle = resourceReference(R.string.warning_express_wrong_amount_description),
|
||||
)
|
||||
|
||||
data class MaximumAmountError(
|
||||
val amount: String,
|
||||
) : Error(
|
||||
title = resourceReference(
|
||||
id = R.string.warning_express_too_maximum_amount_title,
|
||||
formatArgs = wrappedList(amount),
|
||||
),
|
||||
subtitle = resourceReference(R.string.warning_express_wrong_amount_description),
|
||||
iconResId = R.drawable.ic_alert_circle_24,
|
||||
)
|
||||
}
|
||||
|
||||
sealed class Warning(
|
||||
title: TextReference,
|
||||
subtitle: TextReference,
|
||||
iconResId: Int = R.drawable.img_attention_20,
|
||||
buttonsState: NotificationConfig.ButtonsState? = null,
|
||||
onCloseClick: (() -> Unit)? = null,
|
||||
) : NotificationUM.Warning(
|
||||
title = title,
|
||||
subtitle = subtitle,
|
||||
iconResId = iconResId,
|
||||
buttonsState = buttonsState,
|
||||
onCloseClick = onCloseClick,
|
||||
) {
|
||||
data class NoAvailableTokensToSwap(
|
||||
val tokenName: String,
|
||||
) : Warning(
|
||||
title = resourceReference(
|
||||
com.tangem.feature.swap.presentation.R.string.warning_express_no_exchangeable_coins_title,
|
||||
),
|
||||
subtitle = resourceReference(
|
||||
id = com.tangem.feature.swap.presentation.R.string.warning_express_no_exchangeable_coins_description,
|
||||
formatArgs = wrappedList(tokenName),
|
||||
),
|
||||
)
|
||||
|
||||
data class NeedReserveToCreateAccount(
|
||||
val amount: String,
|
||||
val token: String,
|
||||
) : Warning(
|
||||
title = resourceReference(
|
||||
id = R.string.send_notification_invalid_reserve_amount_title,
|
||||
formatArgs = wrappedList("$amount $token"),
|
||||
),
|
||||
subtitle = resourceReference(R.string.send_notification_invalid_reserve_amount_text),
|
||||
)
|
||||
|
||||
data class ReduceAmount(
|
||||
val currencyName: String,
|
||||
val amount: String,
|
||||
val onConfirmClick: () -> Unit,
|
||||
) : Warning(
|
||||
title = resourceReference(R.string.send_notification_high_fee_title),
|
||||
subtitle = resourceReference(
|
||||
R.string.send_notification_high_fee_text,
|
||||
wrappedList(currencyName, amount),
|
||||
),
|
||||
buttonsState = NotificationConfig.ButtonsState.PrimaryButtonConfig(
|
||||
text = resourceReference(
|
||||
R.string.xtz_withdrawal_message_reduce,
|
||||
wrappedList(amount),
|
||||
),
|
||||
onClick = onConfirmClick,
|
||||
),
|
||||
)
|
||||
|
||||
data class ExpressError(
|
||||
val expressDataError: ExpressDataError,
|
||||
val onConfirmClick: () -> Unit,
|
||||
) : Warning(
|
||||
title = getExpressErrorTitle(expressDataError),
|
||||
subtitle = getExpressErrorMessage(expressDataError),
|
||||
buttonsState = NotificationConfig.ButtonsState.SecondaryButtonConfig(
|
||||
text = resourceReference(R.string.warning_button_refresh),
|
||||
onClick = onConfirmClick,
|
||||
),
|
||||
)
|
||||
|
||||
data class ExpressGeneralError(
|
||||
val code: Int,
|
||||
val onConfirmClick: () -> Unit,
|
||||
) : Warning(
|
||||
title = TextReference.Res(R.string.warning_express_refresh_required_title),
|
||||
subtitle = TextReference.Res(R.string.express_error_code, wrappedList(code)),
|
||||
buttonsState = NotificationConfig.ButtonsState.PrimaryButtonConfig(
|
||||
text = TextReference.Res(R.string.warning_button_refresh),
|
||||
onClick = onConfirmClick,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
sealed class Info(
|
||||
title: TextReference,
|
||||
subtitle: TextReference,
|
||||
iconResId: Int = R.drawable.ic_alert_circle_24,
|
||||
) : NotificationUM.Info(
|
||||
title = title,
|
||||
subtitle = subtitle,
|
||||
iconResId = iconResId,
|
||||
) {
|
||||
data class PermissionNeeded(
|
||||
val providerName: String,
|
||||
val fromTokenSymbol: String,
|
||||
) : Info(
|
||||
title = resourceReference(R.string.express_provider_permission_needed),
|
||||
subtitle = resourceReference(
|
||||
id = R.string.give_permission_swap_subtitle,
|
||||
formatArgs = wrappedList(providerName, fromTokenSymbol),
|
||||
),
|
||||
iconResId = R.drawable.ic_locked_24,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,333 @@
|
|||
package com.tangem.feature.swap.presentation
|
||||
|
||||
import com.tangem.common.ui.notifications.NotificationUM
|
||||
import com.tangem.common.ui.notifications.NotificationsFactory.addDustWarningNotification
|
||||
import com.tangem.common.ui.notifications.NotificationsFactory.addExistentialWarningNotification
|
||||
import com.tangem.common.ui.notifications.NotificationsFactory.addReserveAmountErrorNotification
|
||||
import com.tangem.common.ui.notifications.NotificationsFactory.addValidateTransactionNotifications
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.format.bigdecimal.crypto
|
||||
import com.tangem.core.ui.format.bigdecimal.format
|
||||
import com.tangem.core.ui.utils.parseBigDecimal
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.feature.swap.domain.models.ExpressDataError
|
||||
import com.tangem.feature.swap.domain.models.SwapAmount
|
||||
import com.tangem.feature.swap.domain.models.domain.IncludeFeeInAmount
|
||||
import com.tangem.feature.swap.domain.models.domain.SwapFeeState
|
||||
import com.tangem.feature.swap.domain.models.ui.*
|
||||
import com.tangem.feature.swap.models.UiActions
|
||||
import com.tangem.feature.swap.models.states.FeeItemState
|
||||
import com.tangem.feature.swap.models.states.SwapNotificationUM
|
||||
import com.tangem.lib.crypto.BlockchainUtils
|
||||
import com.tangem.lib.crypto.BlockchainUtils.getTezosThreshold
|
||||
import com.tangem.lib.crypto.BlockchainUtils.isTezos
|
||||
import com.tangem.utils.extensions.orZero
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
|
||||
internal class SwapNotificationsFactory(
|
||||
private val actions: UiActions,
|
||||
) {
|
||||
|
||||
fun getInitialErrorStateNotifications(code: Int, onRefreshClick: () -> Unit): ImmutableList<NotificationUM> {
|
||||
return persistentListOf(
|
||||
SwapNotificationUM.Warning.ExpressGeneralError(
|
||||
code = code,
|
||||
onConfirmClick = onRefreshClick,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fun getGeneralErrorStateNotifications(
|
||||
notifications: ImmutableList<NotificationUM>,
|
||||
message: TextReference?,
|
||||
onClick: () -> Unit,
|
||||
): ImmutableList<NotificationUM> {
|
||||
val updatedNotifications = notifications.toMutableList()
|
||||
updatedNotifications.add(
|
||||
SwapNotificationUM.Error.GenericError(
|
||||
subtitle = message,
|
||||
onConfirmClick = onClick,
|
||||
),
|
||||
)
|
||||
|
||||
return updatedNotifications.toPersistentList()
|
||||
}
|
||||
|
||||
fun getNotAvailableStateNotifications(fromCurrencyName: String): ImmutableList<NotificationUM> {
|
||||
return persistentListOf(
|
||||
SwapNotificationUM.Warning.NoAvailableTokensToSwap(fromCurrencyName),
|
||||
)
|
||||
}
|
||||
|
||||
fun getQuotesErrorStateNotifications(
|
||||
expressDataError: ExpressDataError,
|
||||
fromToken: CryptoCurrency,
|
||||
feeItem: FeeItemState,
|
||||
includeFeeInAmount: IncludeFeeInAmount,
|
||||
): ImmutableList<NotificationUM> {
|
||||
return buildList {
|
||||
add(getWarningForError(expressDataError, fromToken, actions.onRetryClick))
|
||||
if (includeFeeInAmount is IncludeFeeInAmount.Included && feeItem is FeeItemState.Content) {
|
||||
add(
|
||||
NotificationUM.Warning.FeeCoverageNotification(
|
||||
feeItem.amountCrypto,
|
||||
feeItem.amountFiatFormatted,
|
||||
),
|
||||
)
|
||||
}
|
||||
}.toPersistentList()
|
||||
}
|
||||
|
||||
fun getApprovalInProgressStateNotification(
|
||||
notifications: ImmutableList<NotificationUM>,
|
||||
): ImmutableList<NotificationUM> {
|
||||
val updatedNotifications = notifications
|
||||
.filterNot { it is SwapNotificationUM.Info.PermissionNeeded }
|
||||
.toMutableList()
|
||||
|
||||
updatedNotifications.add(0, SwapNotificationUM.Error.ApprovalInProgressWarning)
|
||||
|
||||
return updatedNotifications.toPersistentList()
|
||||
}
|
||||
|
||||
fun getConfirmationStateNotifications(
|
||||
quoteModel: SwapState.QuotesLoadedState,
|
||||
fromToken: CryptoCurrency,
|
||||
feeCryptoCurrencyStatus: CryptoCurrencyStatus?,
|
||||
selectedFeeType: FeeType,
|
||||
providerName: String,
|
||||
): ImmutableList<NotificationUM> {
|
||||
val warnings = buildList {
|
||||
maybeAddDomainWarnings(quoteModel, feeCryptoCurrencyStatus, selectedFeeType)
|
||||
maybeAddNeedReserveToCreateAccountWarning(quoteModel)
|
||||
maybeAddPermissionNeededWarning(quoteModel, fromToken, providerName)
|
||||
maybeAddNetworkFeeCoverageWarning(quoteModel, selectedFeeType)
|
||||
maybeAddUnableCoverFeeWarning(quoteModel, fromToken)
|
||||
maybeAddTransactionInProgressWarning(quoteModel)
|
||||
}
|
||||
return warnings.toPersistentList()
|
||||
}
|
||||
|
||||
private fun MutableList<NotificationUM>.maybeAddTransactionInProgressWarning(
|
||||
quoteModel: SwapState.QuotesLoadedState,
|
||||
) {
|
||||
if (quoteModel.permissionState is PermissionDataState.PermissionLoading) {
|
||||
add(SwapNotificationUM.Error.ApprovalInProgressWarning)
|
||||
} else if (quoteModel.preparedSwapConfigState.hasOutgoingTransaction) {
|
||||
add(
|
||||
SwapNotificationUM.Error.TransactionInProgressWarning(
|
||||
currencySymbol = quoteModel.fromTokenInfo.cryptoCurrencyStatus.currency.network.currencySymbol,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun MutableList<NotificationUM>.maybeAddDomainWarnings(
|
||||
quoteModel: SwapState.QuotesLoadedState,
|
||||
feeCryptoCurrencyStatus: CryptoCurrencyStatus?,
|
||||
selectedFeeType: FeeType,
|
||||
) {
|
||||
val fromCurrencyStatus = quoteModel.fromTokenInfo.cryptoCurrencyStatus
|
||||
val includeFeeInAmount = quoteModel.preparedSwapConfigState.includeFeeInAmount
|
||||
val amountToRequest = if (includeFeeInAmount is IncludeFeeInAmount.Included) {
|
||||
includeFeeInAmount.amountSubtractFee
|
||||
} else {
|
||||
quoteModel.fromTokenInfo.tokenAmount
|
||||
}
|
||||
val fee = when (val feeState = quoteModel.txFee) {
|
||||
TxFeeState.Empty -> null
|
||||
is TxFeeState.MultipleFeeState -> if (feeState.normalFee.feeType == selectedFeeType) {
|
||||
feeState.normalFee
|
||||
} else {
|
||||
feeState.priorityFee
|
||||
}
|
||||
is TxFeeState.SingleFeeState -> feeState.fee
|
||||
}
|
||||
val isCardano = BlockchainUtils.isCardano(fromCurrencyStatus.currency.network.id.value)
|
||||
// blockchain specific
|
||||
|
||||
addExistentialWarningNotification(
|
||||
existentialDeposit = quoteModel.currencyCheck?.existentialDeposit,
|
||||
feeAmount = fee?.feeValue.orZero(),
|
||||
receivedAmount = amountToRequest.value,
|
||||
cryptoCurrencyStatus = fromCurrencyStatus,
|
||||
onReduceClick = { _, reduceByDiff, _ ->
|
||||
actions.onLeaveExistentialDeposit(
|
||||
amountToRequest.copy(
|
||||
value = amountToRequest.value.minus(reduceByDiff).minus(fee?.feeValue.orZero()),
|
||||
),
|
||||
)
|
||||
},
|
||||
)
|
||||
addValidateTransactionNotifications(
|
||||
dustValue = quoteModel.currencyCheck?.dustValue.orZero(),
|
||||
validationError = quoteModel.validationResult,
|
||||
cryptoCurrency = fromCurrencyStatus.currency,
|
||||
minAdaValue = quoteModel.minAdaValue,
|
||||
onReduceClick = { reduceTo, _ ->
|
||||
actions.onLeaveExistentialDeposit(amountToRequest.copy(value = reduceTo))
|
||||
},
|
||||
)
|
||||
if (!isCardano) {
|
||||
addDustWarningNotification(
|
||||
dustValue = quoteModel.currencyCheck?.dustValue,
|
||||
feeValue = fee?.feeValue.orZero(),
|
||||
sendingAmount = amountToRequest.value,
|
||||
cryptoCurrencyStatus = fromCurrencyStatus,
|
||||
feeCurrencyStatus = feeCryptoCurrencyStatus,
|
||||
)
|
||||
}
|
||||
addReserveAmountErrorNotification(
|
||||
reserveAmount = quoteModel.currencyCheck?.reserveAmount,
|
||||
sendingAmount = amountToRequest.value,
|
||||
cryptoCurrency = fromCurrencyStatus.currency,
|
||||
isAccountFunded = false,
|
||||
)
|
||||
addReduceAmountNotification(
|
||||
cryptoCurrencyStatus = fromCurrencyStatus,
|
||||
fromAmount = amountToRequest,
|
||||
onReduceAmount = actions.onReduceAmount,
|
||||
)
|
||||
}
|
||||
|
||||
private fun MutableList<NotificationUM>.maybeAddNeedReserveToCreateAccountWarning(
|
||||
quoteModel: SwapState.QuotesLoadedState,
|
||||
) {
|
||||
val status = quoteModel.toTokenInfo.cryptoCurrencyStatus.value
|
||||
if (status is CryptoCurrencyStatus.NoAccount) {
|
||||
val amount = quoteModel.toTokenInfo.tokenAmount.value
|
||||
val amountToCreateAccount = status.amountToCreateAccount
|
||||
val currencyTo = quoteModel.fromTokenInfo.cryptoCurrencyStatus.currency
|
||||
if (amount < amountToCreateAccount) {
|
||||
add(
|
||||
SwapNotificationUM.Warning.NeedReserveToCreateAccount(
|
||||
status.amountToCreateAccount.parseBigDecimal(currencyTo.decimals),
|
||||
currencyTo.symbol,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun MutableList<NotificationUM>.maybeAddPermissionNeededWarning(
|
||||
quoteModel: SwapState.QuotesLoadedState,
|
||||
fromToken: CryptoCurrency,
|
||||
providerName: String,
|
||||
) {
|
||||
if (!quoteModel.preparedSwapConfigState.isAllowedToSpend &&
|
||||
quoteModel.preparedSwapConfigState.feeState is SwapFeeState.Enough &&
|
||||
quoteModel.permissionState is PermissionDataState.PermissionReadyForRequest
|
||||
) {
|
||||
add(
|
||||
SwapNotificationUM.Info.PermissionNeeded(
|
||||
providerName = fromToken.symbol,
|
||||
fromTokenSymbol = providerName,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun MutableList<NotificationUM>.maybeAddNetworkFeeCoverageWarning(
|
||||
quoteModel: SwapState.QuotesLoadedState,
|
||||
selectedFeeType: FeeType,
|
||||
) {
|
||||
when (quoteModel.preparedSwapConfigState.includeFeeInAmount) {
|
||||
is IncludeFeeInAmount.Included -> {
|
||||
val fee = selectFeeByType(selectedFeeType, quoteModel.txFee) ?: return
|
||||
if (needShowNetworkFeeCoverageWarningShow(quoteModel)) {
|
||||
add(
|
||||
NotificationUM.Warning.FeeCoverageNotification(
|
||||
fee.feeCryptoFormattedWithNative,
|
||||
fee.feeFiatFormattedWithNative,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
|
||||
private fun selectFeeByType(feeType: FeeType, txFeeState: TxFeeState): TxFee? {
|
||||
return when (txFeeState) {
|
||||
TxFeeState.Empty -> null
|
||||
is TxFeeState.SingleFeeState -> txFeeState.fee
|
||||
is TxFeeState.MultipleFeeState -> when (feeType) {
|
||||
FeeType.NORMAL -> txFeeState.normalFee
|
||||
FeeType.PRIORITY -> txFeeState.priorityFee
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun MutableList<NotificationUM>.maybeAddUnableCoverFeeWarning(
|
||||
quoteModel: SwapState.QuotesLoadedState,
|
||||
fromToken: CryptoCurrency,
|
||||
) {
|
||||
val feeEnoughState = quoteModel.preparedSwapConfigState.feeState as? SwapFeeState.NotEnough ?: return
|
||||
val needShowCoverWarning = quoteModel.preparedSwapConfigState.isBalanceEnough &&
|
||||
quoteModel.permissionState !is PermissionDataState.PermissionLoading &&
|
||||
feeEnoughState.feeCurrency != fromToken
|
||||
if (needShowCoverWarning) {
|
||||
add(
|
||||
SwapNotificationUM.Error.UnableToCoverFeeWarning(
|
||||
fromToken = fromToken,
|
||||
feeCurrency = feeEnoughState.feeCurrency,
|
||||
currencyName = feeEnoughState.currencyName ?: fromToken.network.name,
|
||||
currencySymbol = feeEnoughState.currencySymbol ?: fromToken.network.currencySymbol,
|
||||
onConfirmClick = actions.onBuyClick,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun MutableList<NotificationUM>.addReduceAmountNotification(
|
||||
cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
fromAmount: SwapAmount,
|
||||
onReduceAmount: (SwapAmount) -> Unit,
|
||||
) {
|
||||
val isTezos = isTezos(cryptoCurrencyStatus.currency.network.id.value)
|
||||
if (isTezos && fromAmount.value == cryptoCurrencyStatus.value.amount) {
|
||||
add(
|
||||
SwapNotificationUM.Warning.ReduceAmount(
|
||||
currencyName = cryptoCurrencyStatus.currency.name,
|
||||
amount = getTezosThreshold().toPlainString(),
|
||||
onConfirmClick = {
|
||||
val patchedAmount = fromAmount.copy(
|
||||
value = fromAmount.value - getTezosThreshold(),
|
||||
)
|
||||
onReduceAmount(patchedAmount)
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getWarningForError(
|
||||
expressDataError: ExpressDataError,
|
||||
fromToken: CryptoCurrency,
|
||||
onRetryClick: () -> Unit,
|
||||
): NotificationUM {
|
||||
return when (expressDataError) {
|
||||
is ExpressDataError.ExchangeTooSmallAmountError -> SwapNotificationUM.Error.MinimalAmountError(
|
||||
expressDataError.amount.value.format {
|
||||
crypto(fromToken.symbol, fromToken.decimals)
|
||||
},
|
||||
)
|
||||
is ExpressDataError.ExchangeTooBigAmountError -> SwapNotificationUM.Error.MaximumAmountError(
|
||||
expressDataError.amount.value.format {
|
||||
crypto(fromToken.symbol, fromToken.decimals)
|
||||
},
|
||||
)
|
||||
else -> SwapNotificationUM.Warning.ExpressError(
|
||||
expressDataError,
|
||||
onConfirmClick = onRetryClick,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun needShowNetworkFeeCoverageWarningShow(quoteModel: SwapState.QuotesLoadedState): Boolean {
|
||||
return quoteModel.currencyCheck?.existentialDeposit == null
|
||||
}
|
||||
}
|
||||
|
|
@ -4,13 +4,16 @@ import androidx.compose.ui.text.TextRange
|
|||
import androidx.compose.ui.text.input.TextFieldValue
|
||||
import com.tangem.common.ui.alerts.models.AlertDemoModeUM
|
||||
import com.tangem.common.ui.bottomsheet.permission.state.*
|
||||
import com.tangem.common.ui.notifications.NotificationUM
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
|
||||
import com.tangem.core.ui.components.currency.icon.converter.CryptoCurrencyToIconStateConverter
|
||||
import com.tangem.core.ui.components.notifications.NotificationConfig
|
||||
import com.tangem.core.ui.event.consumedEvent
|
||||
import com.tangem.core.ui.event.triggeredEvent
|
||||
import com.tangem.core.ui.extensions.*
|
||||
import com.tangem.core.ui.format.bigdecimal.*
|
||||
import com.tangem.core.ui.format.bigdecimal.anyDecimals
|
||||
import com.tangem.core.ui.format.bigdecimal.crypto
|
||||
import com.tangem.core.ui.format.bigdecimal.format
|
||||
import com.tangem.core.ui.utils.BigDecimalFormatter
|
||||
import com.tangem.core.ui.utils.parseBigDecimal
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
|
|
@ -20,21 +23,24 @@ import com.tangem.feature.swap.converters.SwapTransactionErrorStateConverter
|
|||
import com.tangem.feature.swap.converters.TokensDataConverter
|
||||
import com.tangem.feature.swap.domain.models.ExpressDataError
|
||||
import com.tangem.feature.swap.domain.models.SwapAmount
|
||||
import com.tangem.feature.swap.domain.models.domain.*
|
||||
import com.tangem.feature.swap.domain.models.domain.ExchangeProviderType
|
||||
import com.tangem.feature.swap.domain.models.domain.IncludeFeeInAmount
|
||||
import com.tangem.feature.swap.domain.models.domain.NetworkInfo
|
||||
import com.tangem.feature.swap.domain.models.domain.SwapProvider
|
||||
import com.tangem.feature.swap.domain.models.ui.*
|
||||
import com.tangem.feature.swap.models.*
|
||||
import com.tangem.feature.swap.models.states.*
|
||||
import com.tangem.feature.swap.models.states.events.SwapEvent
|
||||
import com.tangem.feature.swap.presentation.R
|
||||
import com.tangem.feature.swap.presentation.SwapNotificationsFactory
|
||||
import com.tangem.feature.swap.utils.formatToUIRepresentation
|
||||
import com.tangem.feature.swap.utils.getExpressErrorMessage
|
||||
import com.tangem.feature.swap.utils.getExpressErrorTitle
|
||||
import com.tangem.feature.swap.viewmodels.SwapProcessDataState
|
||||
import com.tangem.utils.Provider
|
||||
import com.tangem.utils.StringsSigns.DASH_SIGN
|
||||
import com.tangem.utils.StringsSigns.PERCENT
|
||||
import com.tangem.utils.StringsSigns.TILDE_SIGN
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import java.math.BigDecimal
|
||||
import java.math.RoundingMode
|
||||
|
|
@ -60,6 +66,10 @@ internal class StateBuilder(
|
|||
appCurrencyProvider = appCurrencyProvider,
|
||||
)
|
||||
|
||||
private val notificationsFactory by lazy(LazyThreadSafetyMode.NONE) {
|
||||
SwapNotificationsFactory(actions)
|
||||
}
|
||||
|
||||
fun createInitialLoadingState(
|
||||
initialCurrencyFrom: CryptoCurrency,
|
||||
initialCurrencyTo: CryptoCurrency?,
|
||||
|
|
@ -110,6 +120,7 @@ internal class StateBuilder(
|
|||
providerState = ProviderState.Empty(),
|
||||
shouldShowMaxAmount = false,
|
||||
priceImpact = PriceImpact.Empty(),
|
||||
isInsufficientFunds = false,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -141,18 +152,7 @@ internal class StateBuilder(
|
|||
),
|
||||
canSelectAnotherToken = true,
|
||||
),
|
||||
warnings = listOf(
|
||||
SwapWarning.NoAvailableTokensToSwap(
|
||||
notificationConfig = NotificationConfig(
|
||||
title = resourceReference(R.string.warning_express_no_exchangeable_coins_title),
|
||||
subtitle = resourceReference(
|
||||
id = R.string.warning_express_no_exchangeable_coins_description,
|
||||
formatArgs = wrappedList(fromToken.currency.name),
|
||||
),
|
||||
iconResId = R.drawable.img_attention_20,
|
||||
),
|
||||
),
|
||||
),
|
||||
notifications = notificationsFactory.getNotAvailableStateNotifications(fromToken.currency.name),
|
||||
fee = FeeItemState.Empty,
|
||||
swapButton = SwapButton(
|
||||
enabled = false,
|
||||
|
|
@ -211,7 +211,7 @@ internal class StateBuilder(
|
|||
networkIconRes = getActiveIconRes(toToken.network.id.value),
|
||||
isBalanceHidden = isBalanceHiddenProvider(),
|
||||
),
|
||||
warnings = emptyList(),
|
||||
notifications = persistentListOf(),
|
||||
fee = FeeItemState.Empty,
|
||||
swapButton = SwapButton(enabled = false, onClick = {}),
|
||||
providerState = ProviderState.Loading(),
|
||||
|
|
@ -235,6 +235,7 @@ internal class StateBuilder(
|
|||
uiStateHolder: SwapStateHolder,
|
||||
quoteModel: SwapState.QuotesLoadedState,
|
||||
fromToken: CryptoCurrency,
|
||||
feeCryptoCurrencyStatus: CryptoCurrencyStatus?,
|
||||
swapProvider: SwapProvider,
|
||||
bestRatedProviderId: String,
|
||||
isNeedBestRateBadge: Boolean,
|
||||
|
|
@ -243,9 +244,10 @@ internal class StateBuilder(
|
|||
): SwapStateHolder {
|
||||
if (uiStateHolder.sendCardData !is SwapCardState.SwapCardData) return uiStateHolder
|
||||
if (uiStateHolder.receiveCardData !is SwapCardState.SwapCardData) return uiStateHolder
|
||||
val warnings = getWarningsForSuccessState(
|
||||
val notifications = notificationsFactory.getConfirmationStateNotifications(
|
||||
quoteModel = quoteModel,
|
||||
fromToken = fromToken,
|
||||
feeCryptoCurrencyStatus = feeCryptoCurrencyStatus,
|
||||
selectedFeeType = selectedFeeType,
|
||||
providerName = swapProvider.name,
|
||||
)
|
||||
|
|
@ -304,7 +306,8 @@ internal class StateBuilder(
|
|||
balance = toCurrencyStatus.getFormattedAmount(isNeedSymbol = false),
|
||||
isBalanceHidden = isBalanceHiddenProvider(),
|
||||
),
|
||||
warnings = warnings,
|
||||
isInsufficientFunds = isInsufficientFundsCondition(quoteModel),
|
||||
notifications = notifications,
|
||||
permissionState = convertPermissionState(
|
||||
lastPermissionState = uiStateHolder.permissionState,
|
||||
permissionDataState = quoteModel.permissionState,
|
||||
|
|
@ -314,7 +317,7 @@ internal class StateBuilder(
|
|||
),
|
||||
fee = feeState,
|
||||
swapButton = SwapButton(
|
||||
enabled = getSwapButtonEnabled(quoteModel),
|
||||
enabled = getSwapButtonEnabled(notifications),
|
||||
onClick = actions.onSwapClick,
|
||||
),
|
||||
changeCardsButtonState = if (isReverseSwapPossible) {
|
||||
|
|
@ -363,281 +366,17 @@ internal class StateBuilder(
|
|||
)
|
||||
}
|
||||
|
||||
@Suppress("LongMethod", "CyclomaticComplexMethod")
|
||||
private fun getWarningsForSuccessState(
|
||||
quoteModel: SwapState.QuotesLoadedState,
|
||||
fromToken: CryptoCurrency,
|
||||
selectedFeeType: FeeType,
|
||||
providerName: String,
|
||||
): List<SwapWarning> {
|
||||
val warnings = mutableListOf<SwapWarning>()
|
||||
maybeAddDomainWarnings(quoteModel, warnings)
|
||||
maybeAddNeedReserveToCreateAccountWarning(quoteModel, warnings)
|
||||
maybeAddPermissionNeededWarning(quoteModel, warnings, fromToken, providerName)
|
||||
maybeAddNetworkFeeCoverageWarning(quoteModel, warnings, selectedFeeType)
|
||||
maybeAddUnableCoverFeeWarning(quoteModel, fromToken, warnings)
|
||||
maybeAddInsufficientFundsWarning(quoteModel, warnings)
|
||||
maybeAddTransactionInProgressWarning(quoteModel, warnings)
|
||||
return warnings
|
||||
}
|
||||
|
||||
private fun maybeAddTransactionInProgressWarning(
|
||||
quoteModel: SwapState.QuotesLoadedState,
|
||||
warnings: MutableList<SwapWarning>,
|
||||
) {
|
||||
if (quoteModel.permissionState is PermissionDataState.PermissionLoading) {
|
||||
warnings.add(
|
||||
SwapWarning.TransactionInProgressWarning(
|
||||
title = resourceReference(R.string.warning_express_approval_in_progress_title),
|
||||
description = resourceReference(R.string.warning_express_approval_in_progress_message),
|
||||
),
|
||||
)
|
||||
} else if (quoteModel.preparedSwapConfigState.hasOutgoingTransaction) {
|
||||
warnings.add(
|
||||
SwapWarning.TransactionInProgressWarning(
|
||||
title = resourceReference(R.string.warning_express_active_transaction_title),
|
||||
description = resourceReference(
|
||||
id = R.string.warning_express_active_transaction_message,
|
||||
formatArgs = wrappedList(
|
||||
quoteModel.fromTokenInfo.cryptoCurrencyStatus.currency.network.currencySymbol,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun maybeAddDomainWarnings(quoteModel: SwapState.QuotesLoadedState, warnings: MutableList<SwapWarning>) {
|
||||
quoteModel.warnings.forEach {
|
||||
when (it) {
|
||||
is Warning.ExistentialDepositWarning -> {
|
||||
warnings.add(createLeaveExistentialDepositWarning(quoteModel, it))
|
||||
}
|
||||
is Warning.MinAmountWarning -> {
|
||||
warnings.add(
|
||||
SwapWarning.GeneralError(
|
||||
NotificationConfig(
|
||||
title = resourceReference(R.string.send_notification_invalid_amount_title),
|
||||
subtitle = resourceReference(
|
||||
R.string.warning_express_dust_message,
|
||||
wrappedList(
|
||||
it.dustValue.toPlainString(),
|
||||
it.dustValue.toPlainString(),
|
||||
),
|
||||
),
|
||||
iconResId = R.drawable.ic_alert_circle_24,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
is Warning.ReduceAmountWarning -> {
|
||||
warnings.add(
|
||||
SwapWarning.ReduceAmount(
|
||||
notificationConfig = createReduceAmountNotificationConfig(
|
||||
currencyName = quoteModel.fromTokenInfo.cryptoCurrencyStatus.currency.name,
|
||||
amount = it.tezosFeeThreshold.toPlainString(),
|
||||
onConfirmClick = {
|
||||
val fromAmount = quoteModel.fromTokenInfo.tokenAmount
|
||||
val patchedAmount = fromAmount.copy(
|
||||
value = fromAmount.value - it.tezosFeeThreshold,
|
||||
)
|
||||
actions.onReduceAmount(patchedAmount)
|
||||
},
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
Warning.Cardano.InsufficientBalanceToTransferCoin -> {
|
||||
warnings.add(createInsufficientBalanceToTransferCoin())
|
||||
}
|
||||
is Warning.Cardano.InsufficientBalanceToTransferToken -> {
|
||||
warnings.add(createInsufficientBalanceToTransferToken(tokenName = it.tokenName))
|
||||
}
|
||||
is Warning.Cardano.MinAdaValueCharged -> {
|
||||
warnings.add(createMinAdaValueCharged(minAdaValue = it.minAdaValue, tokenName = it.tokenName))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun maybeAddNeedReserveToCreateAccountWarning(
|
||||
quoteModel: SwapState.QuotesLoadedState,
|
||||
warnings: MutableList<SwapWarning>,
|
||||
) {
|
||||
val status = quoteModel.toTokenInfo.cryptoCurrencyStatus.value
|
||||
if (status is CryptoCurrencyStatus.NoAccount) {
|
||||
val amount = quoteModel.toTokenInfo.tokenAmount.value
|
||||
val amountToCreateAccount = status.amountToCreateAccount
|
||||
|
||||
if (amount < amountToCreateAccount) {
|
||||
warnings.add(
|
||||
SwapWarning.NeedReserveToCreateAccount(
|
||||
notificationConfig = createActivateAccountNotificationConfig(
|
||||
status.amountToCreateAccount,
|
||||
quoteModel.toTokenInfo.cryptoCurrencyStatus.currency.symbol,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun createLeaveExistentialDepositWarning(
|
||||
quoteModel: SwapState.QuotesLoadedState,
|
||||
domainWarning: Warning.ExistentialDepositWarning,
|
||||
): SwapWarning {
|
||||
val fromCurrency = quoteModel.fromTokenInfo.cryptoCurrencyStatus.currency
|
||||
val deposit = domainWarning.existentialDeposit.format { crypto(fromCurrency).uncapped() }
|
||||
|
||||
return SwapWarning.GeneralError(
|
||||
NotificationConfig(
|
||||
title = resourceReference(R.string.send_notification_existential_deposit_title),
|
||||
subtitle = resourceReference(
|
||||
R.string.send_notification_existential_deposit_text,
|
||||
wrappedList(deposit),
|
||||
),
|
||||
iconResId = R.drawable.ic_alert_circle_24,
|
||||
buttonsState = NotificationConfig.ButtonsState.PrimaryButtonConfig(
|
||||
text = resourceReference(R.string.common_ok),
|
||||
onClick = {
|
||||
actions.onLeaveExistentialDeposit(
|
||||
SwapAmount(
|
||||
domainWarning.minAvailableAmount,
|
||||
fromCurrency.decimals,
|
||||
),
|
||||
)
|
||||
},
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun maybeAddPermissionNeededWarning(
|
||||
quoteModel: SwapState.QuotesLoadedState,
|
||||
warnings: MutableList<SwapWarning>,
|
||||
fromToken: CryptoCurrency,
|
||||
providerName: String,
|
||||
) {
|
||||
if (!quoteModel.preparedSwapConfigState.isAllowedToSpend &&
|
||||
quoteModel.preparedSwapConfigState.feeState is SwapFeeState.Enough &&
|
||||
quoteModel.permissionState is PermissionDataState.PermissionReadyForRequest
|
||||
) {
|
||||
warnings.add(
|
||||
SwapWarning.PermissionNeeded(
|
||||
createPermissionNotificationConfig(fromToken.symbol, providerName),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun maybeAddNetworkFeeCoverageWarning(
|
||||
quoteModel: SwapState.QuotesLoadedState,
|
||||
warnings: MutableList<SwapWarning>,
|
||||
selectedFeeType: FeeType,
|
||||
) {
|
||||
when (quoteModel.preparedSwapConfigState.includeFeeInAmount) {
|
||||
is IncludeFeeInAmount.Included -> {
|
||||
val fee = selectFeeByType(selectedFeeType, quoteModel.txFee) ?: return
|
||||
if (needShowNetworkFeeCoverageWarningShow(quoteModel)) {
|
||||
warnings.add(
|
||||
SwapWarning.GeneralWarning(
|
||||
createNetworkFeeCoverageNotificationConfig(
|
||||
fee.feeCryptoFormattedWithNative,
|
||||
fee.feeFiatFormattedWithNative,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
|
||||
private fun needShowNetworkFeeCoverageWarningShow(quoteModel: SwapState.QuotesLoadedState): Boolean {
|
||||
return quoteModel.warnings.none { it is Warning.ExistentialDepositWarning }
|
||||
}
|
||||
|
||||
private fun selectFeeByType(feeType: FeeType, txFeeState: TxFeeState): TxFee? {
|
||||
return when (txFeeState) {
|
||||
TxFeeState.Empty -> null
|
||||
is TxFeeState.SingleFeeState -> txFeeState.fee
|
||||
is TxFeeState.MultipleFeeState -> when (feeType) {
|
||||
FeeType.NORMAL -> txFeeState.normalFee
|
||||
FeeType.PRIORITY -> txFeeState.priorityFee
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun maybeAddUnableCoverFeeWarning(
|
||||
quoteModel: SwapState.QuotesLoadedState,
|
||||
fromToken: CryptoCurrency,
|
||||
warnings: MutableList<SwapWarning>,
|
||||
) {
|
||||
val feeEnoughState = quoteModel.preparedSwapConfigState.feeState as? SwapFeeState.NotEnough ?: return
|
||||
val needShowCoverWarning = quoteModel.preparedSwapConfigState.isBalanceEnough &&
|
||||
quoteModel.permissionState !is PermissionDataState.PermissionLoading &&
|
||||
feeEnoughState.feeCurrency != fromToken
|
||||
if (needShowCoverWarning) {
|
||||
warnings.add(
|
||||
SwapWarning.UnableToCoverFeeWarning(
|
||||
createUnableToCoverFeeNotificationConfig(
|
||||
fromToken = fromToken,
|
||||
feeCurrency = feeEnoughState.feeCurrency,
|
||||
currencyName = feeEnoughState.currencyName ?: fromToken.network.name,
|
||||
currencySymbol = feeEnoughState.currencySymbol ?: fromToken.network.currencySymbol,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun maybeAddInsufficientFundsWarning(
|
||||
quoteModel: SwapState.QuotesLoadedState,
|
||||
warnings: MutableList<SwapWarning>,
|
||||
) {
|
||||
// check isBalanceEnough, but for dex includeFeeInAmount always Excluded
|
||||
if (isInsufficientFundsCondition(quoteModel)) {
|
||||
warnings.add(SwapWarning.InsufficientFunds)
|
||||
}
|
||||
}
|
||||
|
||||
private fun isInsufficientFundsCondition(quoteModel: SwapState.QuotesLoadedState): Boolean {
|
||||
return !quoteModel.preparedSwapConfigState.isBalanceEnough &&
|
||||
quoteModel.preparedSwapConfigState.includeFeeInAmount !is IncludeFeeInAmount.Included
|
||||
}
|
||||
|
||||
private fun getSwapButtonEnabled(quoteModel: SwapState.QuotesLoadedState): Boolean {
|
||||
val status = quoteModel.toTokenInfo.cryptoCurrencyStatus.value
|
||||
if (status is CryptoCurrencyStatus.NoAccount) {
|
||||
val amount = quoteModel.toTokenInfo.tokenAmount.value
|
||||
val amountToCreateAccount = status.amountToCreateAccount
|
||||
|
||||
if (amount < amountToCreateAccount) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
val preparedSwapConfigState = quoteModel.preparedSwapConfigState
|
||||
// check has has outgoing transaction
|
||||
if (preparedSwapConfigState.hasOutgoingTransaction) return false
|
||||
|
||||
// check has MinAmountWarning warning
|
||||
val hasCriticalWarning = quoteModel.warnings.any {
|
||||
it is Warning.MinAmountWarning ||
|
||||
it is Warning.Cardano.InsufficientBalanceToTransferCoin ||
|
||||
it is Warning.Cardano.InsufficientBalanceToTransferToken ||
|
||||
it is Warning.ExistentialDepositWarning
|
||||
}
|
||||
|
||||
if (hasCriticalWarning) return false
|
||||
|
||||
return when (preparedSwapConfigState.includeFeeInAmount) {
|
||||
IncludeFeeInAmount.BalanceNotEnough -> false
|
||||
IncludeFeeInAmount.Excluded ->
|
||||
preparedSwapConfigState.isAllowedToSpend &&
|
||||
preparedSwapConfigState.isBalanceEnough &&
|
||||
preparedSwapConfigState.feeState is SwapFeeState.Enough
|
||||
is IncludeFeeInAmount.Included -> true
|
||||
private fun getSwapButtonEnabled(notifications: ImmutableList<NotificationUM>): Boolean {
|
||||
return notifications.none {
|
||||
it is SwapNotificationUM.Error || it is NotificationUM.Error ||
|
||||
it is SwapNotificationUM.Warning.ExpressError || it is SwapNotificationUM.Warning.ExpressGeneralError ||
|
||||
it is SwapNotificationUM.Warning.NoAvailableTokensToSwap ||
|
||||
it is SwapNotificationUM.Warning.NeedReserveToCreateAccount
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -653,15 +392,13 @@ internal class StateBuilder(
|
|||
): SwapStateHolder {
|
||||
if (uiStateHolder.sendCardData !is SwapCardState.SwapCardData) return uiStateHolder
|
||||
if (uiStateHolder.receiveCardData !is SwapCardState.SwapCardData) return uiStateHolder
|
||||
val warnings = mutableListOf<SwapWarning>()
|
||||
warnings.add(getWarningForError(expressDataError, fromToken.cryptoCurrencyStatus.currency))
|
||||
if (includeFeeInAmount is IncludeFeeInAmount.Included && uiStateHolder.fee is FeeItemState.Content) {
|
||||
val feeCoverageNotification = createNetworkFeeCoverageNotificationConfig(
|
||||
uiStateHolder.fee.amountCrypto,
|
||||
uiStateHolder.fee.amountFiatFormatted,
|
||||
)
|
||||
warnings.add(SwapWarning.GeneralWarning(feeCoverageNotification))
|
||||
}
|
||||
val notifications = notificationsFactory.getQuotesErrorStateNotifications(
|
||||
expressDataError = expressDataError,
|
||||
fromToken = fromToken.cryptoCurrencyStatus.currency,
|
||||
feeItem = uiStateHolder.fee,
|
||||
includeFeeInAmount = includeFeeInAmount,
|
||||
)
|
||||
|
||||
val providerState = getProviderStateForError(
|
||||
swapProvider = swapProvider,
|
||||
fromToken = fromToken.cryptoCurrencyStatus.currency,
|
||||
|
|
@ -700,7 +437,7 @@ internal class StateBuilder(
|
|||
balance = fromToken.cryptoCurrencyStatus.getFormattedAmount(isNeedSymbol = false),
|
||||
),
|
||||
receiveCardData = receiveCardData,
|
||||
warnings = warnings,
|
||||
notifications = notifications,
|
||||
permissionState = GiveTxPermissionState.Empty,
|
||||
fee = FeeItemState.Empty,
|
||||
swapButton = SwapButton(
|
||||
|
|
@ -754,57 +491,6 @@ internal class StateBuilder(
|
|||
}
|
||||
}
|
||||
|
||||
private fun getWarningForError(expressDataError: ExpressDataError, fromToken: CryptoCurrency): SwapWarning {
|
||||
val providerErrorMessage = getExpressErrorMessage(expressDataError)
|
||||
val providerErrorTitle = getExpressErrorTitle(expressDataError)
|
||||
return when (expressDataError) {
|
||||
is ExpressDataError.ExchangeTooSmallAmountError -> SwapWarning.GeneralError(
|
||||
notificationConfig = NotificationConfig(
|
||||
title = resourceReference(
|
||||
id = R.string.warning_express_too_minimal_amount_title,
|
||||
formatArgs = wrappedList(expressDataError.amount.getFormattedCryptoAmount(fromToken)),
|
||||
),
|
||||
subtitle = resourceReference(R.string.warning_express_wrong_amount_description),
|
||||
iconResId = R.drawable.ic_alert_circle_24,
|
||||
),
|
||||
)
|
||||
is ExpressDataError.ExchangeTooBigAmountError -> SwapWarning.GeneralError(
|
||||
notificationConfig = NotificationConfig(
|
||||
title = resourceReference(
|
||||
id = R.string.warning_express_too_maximum_amount_title,
|
||||
formatArgs = wrappedList(expressDataError.amount.getFormattedCryptoAmount(fromToken)),
|
||||
),
|
||||
subtitle = resourceReference(R.string.warning_express_wrong_amount_description),
|
||||
iconResId = R.drawable.ic_alert_circle_24,
|
||||
),
|
||||
)
|
||||
is ExpressDataError.ProviderDifferentAmountError -> SwapWarning.GeneralError(
|
||||
notificationConfig = NotificationConfig(
|
||||
title = resourceReference(id = R.string.common_error),
|
||||
subtitle = resourceReference(
|
||||
R.string.express_error_provider_amount_roundup,
|
||||
formatArgs = wrappedList(
|
||||
expressDataError.code,
|
||||
expressDataError.fromProviderAmount.format { simple(decimals = expressDataError.decimals) },
|
||||
),
|
||||
),
|
||||
iconResId = R.drawable.ic_alert_circle_24,
|
||||
),
|
||||
)
|
||||
else -> SwapWarning.GeneralWarning(
|
||||
notificationConfig = NotificationConfig(
|
||||
title = providerErrorTitle,
|
||||
subtitle = providerErrorMessage,
|
||||
iconResId = R.drawable.img_attention_20,
|
||||
buttonsState = NotificationConfig.ButtonsState.SecondaryButtonConfig(
|
||||
text = resourceReference(R.string.warning_button_refresh),
|
||||
onClick = actions.onRetryClick,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun createQuotesEmptyAmountState(
|
||||
uiStateHolder: SwapStateHolder,
|
||||
emptyAmountState: SwapState.EmptyAmountState,
|
||||
|
|
@ -843,7 +529,8 @@ internal class StateBuilder(
|
|||
balance = toTokenStatus?.getFormattedAmount(isNeedSymbol = false) ?: DASH_SIGN,
|
||||
isBalanceHidden = isBalanceHiddenProvider(),
|
||||
),
|
||||
warnings = emptyList(),
|
||||
notifications = persistentListOf(),
|
||||
isInsufficientFunds = false,
|
||||
fee = FeeItemState.Empty,
|
||||
swapButton = SwapButton(
|
||||
enabled = false,
|
||||
|
|
@ -1002,19 +689,8 @@ internal class StateBuilder(
|
|||
|
||||
fun createInitialErrorState(uiState: SwapStateHolder, code: Int, onRefreshClick: () -> Unit): SwapStateHolder {
|
||||
return uiState.copy(
|
||||
warnings = listOf(
|
||||
SwapWarning.GeneralWarning(
|
||||
notificationConfig = NotificationConfig(
|
||||
title = TextReference.Res(R.string.warning_express_refresh_required_title),
|
||||
subtitle = TextReference.Res(R.string.express_error_code, wrappedList(code)),
|
||||
iconResId = R.drawable.ic_alert_triangle_20,
|
||||
buttonsState = NotificationConfig.ButtonsState.PrimaryButtonConfig(
|
||||
text = TextReference.Res(R.string.warning_button_refresh),
|
||||
onClick = onRefreshClick,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
isInsufficientFunds = false,
|
||||
notifications = notificationsFactory.getInitialErrorStateNotifications(code, onRefreshClick),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -1051,17 +727,9 @@ internal class StateBuilder(
|
|||
}
|
||||
|
||||
fun loadingPermissionState(uiState: SwapStateHolder): SwapStateHolder {
|
||||
val warnings = uiState.warnings.filterNot { it is SwapWarning.PermissionNeeded }.toMutableList()
|
||||
warnings.add(
|
||||
0,
|
||||
SwapWarning.TransactionInProgressWarning(
|
||||
title = resourceReference(R.string.warning_express_approval_in_progress_title),
|
||||
description = resourceReference(R.string.warning_express_approval_in_progress_message),
|
||||
),
|
||||
)
|
||||
return uiState.copy(
|
||||
permissionState = GiveTxPermissionState.InProgress,
|
||||
warnings = warnings,
|
||||
notifications = notificationsFactory.getApprovalInProgressStateNotification(uiState.notifications),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -1211,17 +879,14 @@ internal class StateBuilder(
|
|||
|
||||
fun clearAlert(uiState: SwapStateHolder): SwapStateHolder = uiState.copy(event = consumedEvent())
|
||||
|
||||
fun addWarning(uiState: SwapStateHolder, message: TextReference?, onClick: () -> Unit): SwapStateHolder {
|
||||
val renewWarnings = uiState.warnings.toMutableList()
|
||||
renewWarnings.add(
|
||||
SwapWarning.GenericWarning(
|
||||
fun addNotification(uiState: SwapStateHolder, message: TextReference?, onClick: () -> Unit): SwapStateHolder {
|
||||
return uiState.copy(
|
||||
notifications = notificationsFactory.getGeneralErrorStateNotifications(
|
||||
notifications = uiState.notifications,
|
||||
message = message,
|
||||
onClick = onClick,
|
||||
),
|
||||
)
|
||||
return uiState.copy(
|
||||
warnings = renewWarnings,
|
||||
)
|
||||
}
|
||||
|
||||
private fun convertPermissionState(
|
||||
|
|
@ -1272,17 +937,6 @@ internal class StateBuilder(
|
|||
}
|
||||
}
|
||||
|
||||
fun showWebViewBottomSheet(uiState: SwapStateHolder, url: String, onDismiss: () -> Unit): SwapStateHolder {
|
||||
val config = WebViewBottomSheetConfig(url = url)
|
||||
return uiState.copy(
|
||||
bottomSheetConfig = TangemBottomSheetConfig(
|
||||
isShow = true,
|
||||
onDismissRequest = onDismiss,
|
||||
content = config,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fun showPermissionBottomSheet(uiState: SwapStateHolder, onDismiss: () -> Unit): SwapStateHolder {
|
||||
val permissionState = uiState.permissionState
|
||||
if (permissionState is GiveTxPermissionState.ReadyForRequest) {
|
||||
|
|
@ -1368,21 +1022,6 @@ internal class StateBuilder(
|
|||
}
|
||||
}
|
||||
|
||||
fun updateSelectedProvider(uiState: SwapStateHolder, selectedProviderId: String): SwapStateHolder {
|
||||
val config = uiState.bottomSheetConfig?.content as? ChooseProviderBottomSheetConfig
|
||||
return if (config != null) {
|
||||
uiState.copy(
|
||||
bottomSheetConfig = uiState.bottomSheetConfig.copy(
|
||||
content = config.copy(
|
||||
selectedProviderId = selectedProviderId,
|
||||
),
|
||||
),
|
||||
)
|
||||
} else {
|
||||
uiState
|
||||
}
|
||||
}
|
||||
|
||||
fun showSelectFeeBottomSheet(
|
||||
uiState: SwapStateHolder,
|
||||
selectedFee: FeeType,
|
||||
|
|
@ -1420,21 +1059,6 @@ internal class StateBuilder(
|
|||
}
|
||||
}
|
||||
|
||||
fun updateSelectedFeeBottomSheet(uiState: SwapStateHolder, selectedFee: FeeType): SwapStateHolder {
|
||||
val config = uiState.bottomSheetConfig?.content as? ChooseFeeBottomSheetConfig
|
||||
return if (config != null) {
|
||||
uiState.copy(
|
||||
bottomSheetConfig = uiState.bottomSheetConfig.copy(
|
||||
content = config.copy(
|
||||
selectedFee = selectedFee,
|
||||
),
|
||||
),
|
||||
)
|
||||
} else {
|
||||
uiState
|
||||
}
|
||||
}
|
||||
|
||||
private fun TxFeeState.MultipleFeeState.toFeeItemState(): ImmutableList<FeeItemState.Content> {
|
||||
return listOf(
|
||||
FeeItemState.Content(
|
||||
|
|
@ -1483,122 +1107,6 @@ internal class StateBuilder(
|
|||
}
|
||||
}
|
||||
|
||||
// region warnings
|
||||
private fun createPermissionNotificationConfig(fromTokenSymbol: String, providerName: String): NotificationConfig {
|
||||
return NotificationConfig(
|
||||
title = resourceReference(R.string.express_provider_permission_needed),
|
||||
subtitle = resourceReference(
|
||||
id = R.string.give_permission_swap_subtitle,
|
||||
formatArgs = wrappedList(providerName, fromTokenSymbol),
|
||||
),
|
||||
iconResId = R.drawable.ic_locked_24,
|
||||
)
|
||||
}
|
||||
|
||||
private fun createActivateAccountNotificationConfig(amount: BigDecimal, token: String): NotificationConfig {
|
||||
return NotificationConfig(
|
||||
title = resourceReference(
|
||||
id = R.string.send_notification_invalid_reserve_amount_title,
|
||||
formatArgs = wrappedList("$amount $token"),
|
||||
),
|
||||
subtitle = resourceReference(R.string.send_notification_invalid_reserve_amount_text),
|
||||
iconResId = R.drawable.img_attention_20,
|
||||
)
|
||||
}
|
||||
|
||||
private fun createReduceAmountNotificationConfig(
|
||||
currencyName: String,
|
||||
amount: String,
|
||||
onConfirmClick: () -> Unit,
|
||||
): NotificationConfig {
|
||||
return NotificationConfig(
|
||||
title = resourceReference(R.string.send_notification_high_fee_title),
|
||||
subtitle = resourceReference(R.string.send_notification_high_fee_text, wrappedList(currencyName, amount)),
|
||||
iconResId = R.drawable.img_attention_20,
|
||||
buttonsState = NotificationConfig.ButtonsState.PrimaryButtonConfig(
|
||||
text = resourceReference(R.string.xtz_withdrawal_message_reduce, wrappedList(amount)),
|
||||
onClick = onConfirmClick,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun createUnableToCoverFeeNotificationConfig(
|
||||
fromToken: CryptoCurrency,
|
||||
feeCurrency: CryptoCurrency?,
|
||||
currencyName: String,
|
||||
currencySymbol: String,
|
||||
): NotificationConfig {
|
||||
val buttonState = feeCurrency?.let {
|
||||
NotificationConfig.ButtonsState.SecondaryButtonConfig(
|
||||
text = resourceReference(R.string.common_buy_currency, wrappedList(currencySymbol)),
|
||||
onClick = { actions.onBuyClick(it) },
|
||||
)
|
||||
}
|
||||
return NotificationConfig(
|
||||
title = resourceReference(
|
||||
R.string.warning_express_not_enough_fee_for_token_tx_title,
|
||||
wrappedList(fromToken.network.name),
|
||||
),
|
||||
subtitle = resourceReference(
|
||||
R.string.warning_express_not_enough_fee_for_token_tx_description,
|
||||
wrappedList(currencyName, currencySymbol),
|
||||
),
|
||||
iconResId = fromToken.networkIconResId,
|
||||
buttonsState = buttonState,
|
||||
)
|
||||
}
|
||||
|
||||
private fun createNetworkFeeCoverageNotificationConfig(
|
||||
cryptoAmount: String,
|
||||
fiatAmount: String,
|
||||
): NotificationConfig {
|
||||
return NotificationConfig(
|
||||
title = resourceReference(R.string.send_network_fee_warning_title),
|
||||
subtitle = resourceReference(
|
||||
R.string.common_network_fee_warning_content,
|
||||
wrappedList(cryptoAmount, fiatAmount),
|
||||
),
|
||||
iconResId = R.drawable.img_attention_20,
|
||||
)
|
||||
}
|
||||
|
||||
private fun createMinAdaValueCharged(minAdaValue: String, tokenName: String): SwapWarning {
|
||||
return SwapWarning.Cardano.MinAdaValueCharged(
|
||||
NotificationConfig(
|
||||
title = resourceReference(id = R.string.cardano_coin_will_be_send_with_token_title),
|
||||
subtitle = resourceReference(
|
||||
id = R.string.cardano_coin_will_be_send_with_token_description,
|
||||
formatArgs = wrappedList(minAdaValue, tokenName),
|
||||
),
|
||||
iconResId = R.drawable.img_attention_20,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun createInsufficientBalanceToTransferCoin(): SwapWarning {
|
||||
return SwapWarning.Cardano.InsufficientBalanceToTransferCoin(
|
||||
NotificationConfig(
|
||||
title = resourceReference(id = R.string.cardano_max_amount_has_token_title),
|
||||
subtitle = resourceReference(id = R.string.cardano_max_amount_has_token_description),
|
||||
iconResId = R.drawable.ic_alert_circle_24,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun createInsufficientBalanceToTransferToken(tokenName: String): SwapWarning {
|
||||
return SwapWarning.Cardano.InsufficientBalanceToTransferToken(
|
||||
NotificationConfig(
|
||||
title = resourceReference(id = R.string.cardano_insufficient_balance_to_send_token_title),
|
||||
subtitle = resourceReference(
|
||||
id = R.string.cardano_insufficient_balance_to_send_token_description,
|
||||
formatArgs = wrappedList(tokenName),
|
||||
),
|
||||
iconResId = R.drawable.ic_alert_circle_24,
|
||||
),
|
||||
)
|
||||
}
|
||||
// end region
|
||||
|
||||
private fun getShortAddressValue(fullAddress: String): String {
|
||||
check(fullAddress.length > ADDRESS_MIN_LENGTH) { "Invalid address" }
|
||||
val firstAddressPart = fullAddress.substring(startIndex = 0, endIndex = ADDRESS_FIRST_PART_LENGTH)
|
||||
|
|
@ -1712,14 +1220,6 @@ internal class StateBuilder(
|
|||
return amount.format { crypto(symbol, currency.decimals) }
|
||||
}
|
||||
|
||||
@Suppress("UnusedPrivateMember")
|
||||
private fun CryptoCurrencyStatus.getFormattedFiatAmount(): String {
|
||||
val fiatAmount = value.fiatAmount ?: return DASH_SIGN
|
||||
val appCurrency = appCurrencyProvider()
|
||||
|
||||
return BigDecimalFormatter.formatFiatAmount(fiatAmount, appCurrency.code, appCurrency.symbol)
|
||||
}
|
||||
|
||||
private fun getFormattedFiatAmount(amount: BigDecimal?): String {
|
||||
val appCurrency = appCurrencyProvider()
|
||||
|
||||
|
|
@ -1735,10 +1235,6 @@ internal class StateBuilder(
|
|||
return this.divide(to, min(rateDecimals, MAX_DECIMALS_TO_SHOW), RoundingMode.HALF_UP)
|
||||
}
|
||||
|
||||
private fun toBigDecimalOrNull(text: String): BigDecimal? {
|
||||
return text.replace(",", ".").toBigDecimalOrNull()
|
||||
}
|
||||
|
||||
private fun getLocaleName(): String {
|
||||
return if (Locale.getDefault().language == "ru") {
|
||||
RU_LOCALE
|
||||
|
|
|
|||
|
|
@ -24,9 +24,9 @@ import androidx.compose.ui.text.withStyle
|
|||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.constraintlayout.compose.ConstraintLayout
|
||||
import com.tangem.common.ui.bottomsheet.permission.state.GiveTxPermissionState
|
||||
import com.tangem.common.ui.notifications.NotificationUM
|
||||
import com.tangem.core.ui.components.*
|
||||
import com.tangem.core.ui.components.notifications.Notification
|
||||
import com.tangem.core.ui.components.notifications.NotificationConfig
|
||||
import com.tangem.core.ui.extensions.getActiveIconResByCoinId
|
||||
import com.tangem.core.ui.extensions.orMaskWithStars
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
|
|
@ -38,7 +38,9 @@ import com.tangem.feature.swap.domain.models.ui.PriceImpact
|
|||
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.presentation.R
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
||||
@Suppress("LongMethod")
|
||||
@Composable
|
||||
|
|
@ -69,7 +71,7 @@ internal fun SwapScreenContent(state: SwapStateHolder, modifier: Modifier = Modi
|
|||
|
||||
FeeItemBlock(state = state.fee)
|
||||
|
||||
if (state.warnings.isNotEmpty()) SwapWarnings(warnings = state.warnings)
|
||||
if (state.notifications.isNotEmpty()) SwapNotifications(notifications = state.notifications)
|
||||
|
||||
MainButton(state = state, onPermissionWarningClick = state.onShowPermissionBottomSheet)
|
||||
|
||||
|
|
@ -302,69 +304,27 @@ private fun SwapButton(state: SwapStateHolder, modifier: Modifier = Modifier) {
|
|||
|
||||
@Suppress("LongMethod", "CyclomaticComplexMethod")
|
||||
@Composable
|
||||
private fun SwapWarnings(warnings: List<SwapWarning>) {
|
||||
private fun SwapNotifications(notifications: List<NotificationUM>) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.background(color = TangemTheme.colors.background.secondary)
|
||||
.fillMaxWidth(),
|
||||
) {
|
||||
warnings.forEach { warning ->
|
||||
when (warning) {
|
||||
is SwapWarning.PermissionNeeded -> {
|
||||
Notification(
|
||||
config = warning.notificationConfig,
|
||||
)
|
||||
}
|
||||
is SwapWarning.GenericWarning -> {
|
||||
val message = warning.message?.resolveReference()
|
||||
?: stringResource(id = R.string.common_unknown_error)
|
||||
notifications.forEach { notification ->
|
||||
when (notification) {
|
||||
is SwapNotificationUM.Error.GenericError -> {
|
||||
RefreshableWarningCard(
|
||||
title = stringResource(id = R.string.common_warning),
|
||||
description = message,
|
||||
onClick = warning.onClick,
|
||||
title = notification.title.resolveReference(),
|
||||
description = notification.config.subtitle.resolveReference(),
|
||||
onClick = notification.onConfirmClick,
|
||||
)
|
||||
}
|
||||
is SwapWarning.NoAvailableTokensToSwap -> {
|
||||
Notification(
|
||||
config = warning.notificationConfig,
|
||||
)
|
||||
}
|
||||
is SwapWarning.GeneralError -> {
|
||||
Notification(
|
||||
config = warning.notificationConfig,
|
||||
iconTint = TangemTheme.colors.icon.warning,
|
||||
)
|
||||
}
|
||||
is SwapWarning.UnableToCoverFeeWarning -> {
|
||||
Notification(
|
||||
config = warning.notificationConfig,
|
||||
)
|
||||
}
|
||||
is SwapWarning.GeneralWarning -> {
|
||||
Notification(
|
||||
config = warning.notificationConfig,
|
||||
)
|
||||
}
|
||||
is SwapWarning.GeneralInformational -> {
|
||||
Notification(
|
||||
config = warning.notificationConfig,
|
||||
iconTint = TangemTheme.colors.icon.accent,
|
||||
)
|
||||
}
|
||||
is SwapWarning.NeedReserveToCreateAccount -> {
|
||||
Notification(
|
||||
config = warning.notificationConfig,
|
||||
)
|
||||
}
|
||||
is SwapWarning.ReduceAmount -> {
|
||||
Notification(
|
||||
config = warning.notificationConfig,
|
||||
)
|
||||
}
|
||||
is SwapWarning.TransactionInProgressWarning -> {
|
||||
is SwapNotificationUM.Error.ApprovalInProgressWarning,
|
||||
is SwapNotificationUM.Error.TransactionInProgressWarning,
|
||||
-> {
|
||||
CardWithIcon(
|
||||
title = warning.title.resolveReference(),
|
||||
description = warning.description.resolveReference(),
|
||||
title = notification.config.title?.resolveReference().orEmpty(),
|
||||
description = notification.config.subtitle.resolveReference(),
|
||||
icon = {
|
||||
CircularProgressIndicator(
|
||||
modifier = Modifier
|
||||
|
|
@ -375,8 +335,20 @@ private fun SwapWarnings(warnings: List<SwapWarning>) {
|
|||
},
|
||||
)
|
||||
}
|
||||
is SwapWarning.Cardano -> Notification(config = warning.notificationConfig)
|
||||
SwapWarning.InsufficientFunds -> Unit
|
||||
else -> {
|
||||
Notification(
|
||||
config = notification.config,
|
||||
iconTint = when (notification) {
|
||||
is SwapNotificationUM.Error.UnableToCoverFeeWarning,
|
||||
is NotificationUM.Error.TokenExceedsBalance,
|
||||
is NotificationUM.Error.ExceedsBalance,
|
||||
is NotificationUM.Info,
|
||||
is NotificationUM.Warning,
|
||||
-> null
|
||||
is NotificationUM.Error -> TangemTheme.colors.icon.warning
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
SpacerH8()
|
||||
}
|
||||
|
|
@ -387,7 +359,7 @@ private fun SwapWarnings(warnings: List<SwapWarning>) {
|
|||
private fun MainButton(state: SwapStateHolder, onPermissionWarningClick: () -> Unit) {
|
||||
// order is important
|
||||
when {
|
||||
state.warnings.any { it is SwapWarning.InsufficientFunds } -> {
|
||||
state.isInsufficientFunds -> {
|
||||
PrimaryButton(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
text = stringResource(id = R.string.swapping_insufficient_funds),
|
||||
|
|
@ -395,7 +367,7 @@ private fun MainButton(state: SwapStateHolder, onPermissionWarningClick: () -> U
|
|||
onClick = state.swapButton.onClick,
|
||||
)
|
||||
}
|
||||
state.warnings.any { it is SwapWarning.PermissionNeeded } -> {
|
||||
state.notifications.any { it is SwapNotificationUM.Info.PermissionNeeded } -> {
|
||||
PrimaryButton(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
text = stringResource(id = R.string.give_permission_title),
|
||||
|
|
@ -459,21 +431,12 @@ private val state = SwapStateHolder(
|
|||
isClickable = true,
|
||||
onClick = {},
|
||||
),
|
||||
warnings = listOf(
|
||||
SwapWarning.PermissionNeeded(
|
||||
notificationConfig = NotificationConfig(
|
||||
title = stringReference("Give Permission"),
|
||||
subtitle = stringReference("To continue swapping you need to give permission to Tangem"),
|
||||
iconResId = R.drawable.ic_locked_24,
|
||||
),
|
||||
),
|
||||
SwapWarning.NoAvailableTokensToSwap(
|
||||
notificationConfig = NotificationConfig(
|
||||
title = stringReference("No tokens"),
|
||||
subtitle = stringReference("Swap tokens not available"),
|
||||
iconResId = R.drawable.img_attention_20,
|
||||
),
|
||||
notifications = persistentListOf(
|
||||
SwapNotificationUM.Info.PermissionNeeded(
|
||||
providerName = "Provider",
|
||||
fromTokenSymbol = "POL",
|
||||
),
|
||||
SwapNotificationUM.Warning.NoAvailableTokensToSwap("POLYGON"),
|
||||
),
|
||||
swapButton = SwapButton(enabled = true, onClick = {}),
|
||||
onRefresh = {},
|
||||
|
|
@ -484,6 +447,7 @@ private val state = SwapStateHolder(
|
|||
providerState = ProviderState.Loading(),
|
||||
priceImpact = PriceImpact.Empty(),
|
||||
shouldShowMaxAmount = true,
|
||||
isInsufficientFunds = false,
|
||||
tosState = TosState(
|
||||
tosLink = LegalState(
|
||||
title = stringReference("Terms of Use"),
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ data class SwapProcessDataState(
|
|||
// Initial network id
|
||||
val fromCryptoCurrency: CryptoCurrencyStatus? = null,
|
||||
val toCryptoCurrency: CryptoCurrencyStatus? = null,
|
||||
val feePaidCryptoCurrency: CryptoCurrencyStatus? = null,
|
||||
// Amount from input
|
||||
val amount: String? = null,
|
||||
val approveDataModel: RequestApproveStateData? = null,
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ import com.tangem.domain.feedback.models.FeedbackEmailType
|
|||
import com.tangem.domain.tokens.GetCryptoCurrencyStatusSyncUseCase
|
||||
import com.tangem.domain.tokens.GetCurrencyStatusUpdatesUseCase
|
||||
import com.tangem.domain.tokens.GetMinimumTransactionAmountSyncUseCase
|
||||
import com.tangem.domain.tokens.GetFeePaidCryptoCurrencyStatusSyncUseCase
|
||||
import com.tangem.domain.tokens.UpdateDelayedNetworkStatusUseCase
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
|
|
@ -44,8 +45,8 @@ import com.tangem.feature.swap.domain.models.SwapAmount
|
|||
import com.tangem.feature.swap.domain.models.domain.*
|
||||
import com.tangem.feature.swap.domain.models.ui.*
|
||||
import com.tangem.feature.swap.models.SwapStateHolder
|
||||
import com.tangem.feature.swap.models.SwapWarning
|
||||
import com.tangem.feature.swap.models.UiActions
|
||||
import com.tangem.feature.swap.models.states.SwapNotificationUM
|
||||
import com.tangem.feature.swap.presentation.R
|
||||
import com.tangem.feature.swap.router.SwapNavScreen
|
||||
import com.tangem.feature.swap.router.SwapRouter
|
||||
|
|
@ -81,6 +82,7 @@ internal class SwapViewModel @Inject constructor(
|
|||
private val getCryptoCurrencyStatusUseCase: GetCryptoCurrencyStatusSyncUseCase,
|
||||
private val updateDelayedCurrencyStatusUseCase: UpdateDelayedNetworkStatusUseCase,
|
||||
private val getCurrencyStatusUpdatesUseCase: GetCurrencyStatusUpdatesUseCase,
|
||||
private val getFeePaidCryptoCurrencyStatusSyncUseCase: GetFeePaidCryptoCurrencyStatusSyncUseCase,
|
||||
private val getUserWalletUseCase: GetUserWalletUseCase,
|
||||
private val getCardInfoUseCase: GetCardInfoUseCase,
|
||||
private val saveBlockchainErrorUseCase: SaveBlockchainErrorUseCase,
|
||||
|
|
@ -400,7 +402,7 @@ internal class SwapViewModel @Inject constructor(
|
|||
},
|
||||
onError = {
|
||||
Timber.e("Error when loading quotes: $it")
|
||||
uiState = stateBuilder.addWarning(uiState, null) { startLoadingQuotesFromLastState() }
|
||||
uiState = stateBuilder.addNotification(uiState, null) { startLoadingQuotesFromLastState() }
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
@ -415,13 +417,14 @@ internal class SwapViewModel @Inject constructor(
|
|||
uiStateHolder = uiState,
|
||||
quoteModel = state,
|
||||
fromToken = fromToken.currency,
|
||||
feeCryptoCurrencyStatus = dataState.feePaidCryptoCurrency,
|
||||
swapProvider = provider,
|
||||
bestRatedProviderId = bestRatedProviderId,
|
||||
isNeedBestRateBadge = dataState.lastLoadedSwapStates.consideredProvidersStates().size > 1,
|
||||
selectedFeeType = dataState.selectedFee?.feeType ?: FeeType.NORMAL,
|
||||
isReverseSwapPossible = isReverseSwapPossible(),
|
||||
)
|
||||
if (uiState.warnings.any { it is SwapWarning.UnableToCoverFeeWarning }) {
|
||||
if (uiState.notifications.any { it is SwapNotificationUM.Error.UnableToCoverFeeWarning }) {
|
||||
analyticsEventHandler.send(
|
||||
SwapEvents.NoticeNotEnoughFee(
|
||||
token = initialCurrencyFrom.symbol,
|
||||
|
|
@ -838,6 +841,13 @@ internal class SwapViewModel @Inject constructor(
|
|||
.onEach {
|
||||
Timber.d("${coin.id} balance is ${it.value.amount}")
|
||||
|
||||
dataState = dataState.copy(
|
||||
feePaidCryptoCurrency = getFeePaidCryptoCurrencyStatusSyncUseCase(
|
||||
userWalletId = userWalletId,
|
||||
cryptoCurrencyStatus = it,
|
||||
).getOrNull() ?: it,
|
||||
)
|
||||
|
||||
uiState = if (isFromCurrency) {
|
||||
dataState = dataState.copy(fromCryptoCurrency = it)
|
||||
stateBuilder.updateSendCurrencyBalance(uiState, it)
|
||||
|
|
|
|||
|
|
@ -5,8 +5,11 @@ import com.tangem.blockchain.common.Blockchain
|
|||
import com.tangem.blockchainsdk.compatibility.l2BlockchainsList
|
||||
import com.tangem.blockchainsdk.utils.ExcludedBlockchains
|
||||
import com.tangem.blockchainsdk.utils.fromNetworkId
|
||||
import com.tangem.blockchainsdk.utils.isSupportedInApp
|
||||
import com.tangem.blockchainsdk.utils.minimalAmount
|
||||
import com.tangem.lib.crypto.converter.XrpTaggedAddressConverter
|
||||
import com.tangem.lib.crypto.models.XrpTaggedAddress
|
||||
import java.math.BigDecimal
|
||||
|
||||
/**
|
||||
* !!!IMPORTANT!!!
|
||||
|
|
@ -116,6 +119,8 @@ object BlockchainUtils {
|
|||
return l2BlockchainsList.contains(blockchain)
|
||||
}
|
||||
|
||||
fun getTezosThreshold(): BigDecimal = Blockchain.Tezos.minimalAmount()
|
||||
|
||||
private fun getNetworkStandardName(blockchain: Blockchain): String {
|
||||
return when (blockchain) {
|
||||
Blockchain.Ethereum, Blockchain.EthereumTestnet -> "ERC20"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue