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
|
package com.tangem.common.ui.notifications
|
||||||
|
|
||||||
import com.tangem.blockchain.common.BlockchainSdkError
|
import com.tangem.blockchain.common.BlockchainSdkError
|
||||||
import com.tangem.blockchain.common.transaction.Fee
|
|
||||||
import com.tangem.common.ui.R
|
import com.tangem.common.ui.R
|
||||||
import com.tangem.common.ui.amountScreen.models.AmountFieldModel
|
import com.tangem.common.ui.amountScreen.models.AmountFieldModel
|
||||||
import com.tangem.common.ui.amountScreen.utils.getFiatString
|
import com.tangem.common.ui.amountScreen.utils.getFiatString
|
||||||
|
|
@ -248,9 +247,9 @@ object NotificationsFactory {
|
||||||
|
|
||||||
fun MutableList<NotificationUM>.addValidateTransactionNotifications(
|
fun MutableList<NotificationUM>.addValidateTransactionNotifications(
|
||||||
dustValue: BigDecimal,
|
dustValue: BigDecimal,
|
||||||
fee: Fee?,
|
|
||||||
validationError: Throwable?,
|
validationError: Throwable?,
|
||||||
cryptoCurrency: CryptoCurrency,
|
cryptoCurrency: CryptoCurrency,
|
||||||
|
minAdaValue: BigDecimal?, // TODO revert to Fee, after swap TxFee refactored
|
||||||
onReduceClick: (
|
onReduceClick: (
|
||||||
reduceAmountTo: BigDecimal,
|
reduceAmountTo: BigDecimal,
|
||||||
notification: Class<out NotificationUM>,
|
notification: Class<out NotificationUM>,
|
||||||
|
|
@ -266,11 +265,11 @@ object NotificationsFactory {
|
||||||
error = validationError,
|
error = validationError,
|
||||||
onReduceClick = onReduceClick,
|
onReduceClick = onReduceClick,
|
||||||
)
|
)
|
||||||
null -> (fee as? Fee.CardanoToken)?.let {
|
null -> minAdaValue?.let {
|
||||||
add(
|
add(
|
||||||
NotificationUM.Cardano.MinAdaValueCharged(
|
NotificationUM.Cardano.MinAdaValueCharged(
|
||||||
tokenName = cryptoCurrency.name,
|
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
|
package com.tangem.features.send.impl.presentation.state.confirm
|
||||||
|
|
||||||
import com.tangem.blockchain.common.Blockchain
|
import com.tangem.blockchain.common.Blockchain
|
||||||
|
import com.tangem.blockchain.common.transaction.Fee
|
||||||
import com.tangem.blockchain.common.transaction.TransactionFee
|
import com.tangem.blockchain.common.transaction.TransactionFee
|
||||||
import com.tangem.blockchainsdk.utils.minimalAmount
|
import com.tangem.blockchainsdk.utils.minimalAmount
|
||||||
import com.tangem.common.ui.amountScreen.models.AmountState
|
import com.tangem.common.ui.amountScreen.models.AmountState
|
||||||
|
|
@ -246,7 +247,7 @@ internal class SendNotificationFactory(
|
||||||
)
|
)
|
||||||
addValidateTransactionNotifications(
|
addValidateTransactionNotifications(
|
||||||
dustValue = currencyCheck.dustValue.orZero(),
|
dustValue = currencyCheck.dustValue.orZero(),
|
||||||
fee = feeState.fee,
|
minAdaValue = (feeState.fee as? Fee.CardanoToken)?.minAdaValue,
|
||||||
validationError = validationError,
|
validationError = validationError,
|
||||||
cryptoCurrency = currency,
|
cryptoCurrency = currency,
|
||||||
onReduceClick = clickIntents::onAmountReduceToClick,
|
onReduceClick = clickIntents::onAmountReduceToClick,
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
package com.tangem.features.staking.impl.presentation.state.transformers.notifications
|
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.amountScreen.models.AmountState
|
||||||
import com.tangem.common.ui.notifications.NotificationUM
|
import com.tangem.common.ui.notifications.NotificationUM
|
||||||
import com.tangem.common.ui.notifications.NotificationsFactory.addDustWarningNotification
|
import com.tangem.common.ui.notifications.NotificationsFactory.addDustWarningNotification
|
||||||
|
|
@ -212,7 +213,7 @@ internal class AddStakingNotificationsTransformer(
|
||||||
// blockchain specific
|
// blockchain specific
|
||||||
addValidateTransactionNotifications(
|
addValidateTransactionNotifications(
|
||||||
dustValue = currencyCheck.dustValue.orZero(),
|
dustValue = currencyCheck.dustValue.orZero(),
|
||||||
fee = feeState?.fee,
|
minAdaValue = (feeState?.fee as? Fee.CardanoToken)?.minAdaValue,
|
||||||
validationError = validatorError,
|
validationError = validatorError,
|
||||||
cryptoCurrency = cryptoCurrency,
|
cryptoCurrency = cryptoCurrency,
|
||||||
onReduceClick = prevState.clickIntents::onAmountReduceToClick,
|
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
|
package com.tangem.feature.swap.domain.models.ui
|
||||||
|
|
||||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
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.ExpressDataError
|
||||||
import com.tangem.feature.swap.domain.models.SwapAmount
|
import com.tangem.feature.swap.domain.models.SwapAmount
|
||||||
import com.tangem.feature.swap.domain.models.domain.*
|
import com.tangem.feature.swap.domain.models.domain.*
|
||||||
|
|
@ -26,7 +27,9 @@ sealed interface SwapState {
|
||||||
val permissionState: PermissionDataState = PermissionDataState.Empty,
|
val permissionState: PermissionDataState = PermissionDataState.Empty,
|
||||||
val swapDataModel: SwapDataModel? = null,
|
val swapDataModel: SwapDataModel? = null,
|
||||||
val txFee: TxFeeState,
|
val txFee: TxFeeState,
|
||||||
val warnings: List<Warning> = emptyList(),
|
val currencyCheck: CryptoCurrencyCheck? = null,
|
||||||
|
val validationResult: Throwable? = null,
|
||||||
|
val minAdaValue: BigDecimal?,
|
||||||
val swapProvider: SwapProvider,
|
val swapProvider: SwapProvider,
|
||||||
) : SwapState
|
) : SwapState
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,23 +2,23 @@ package com.tangem.feature.swap.domain
|
||||||
|
|
||||||
import arrow.core.Either
|
import arrow.core.Either
|
||||||
import arrow.core.getOrElse
|
import arrow.core.getOrElse
|
||||||
import com.tangem.blockchain.common.*
|
|
||||||
import com.tangem.blockchain.common.Amount
|
import com.tangem.blockchain.common.Amount
|
||||||
import com.tangem.blockchain.common.AmountType
|
import com.tangem.blockchain.common.AmountType
|
||||||
import com.tangem.blockchain.common.Blockchain.*
|
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.Fee
|
||||||
import com.tangem.blockchain.common.transaction.TransactionFee
|
import com.tangem.blockchain.common.transaction.TransactionFee
|
||||||
import com.tangem.blockchainsdk.utils.fromNetworkId
|
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.BigDecimalFormatter
|
||||||
import com.tangem.core.ui.utils.parseBigDecimal
|
|
||||||
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
|
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
|
||||||
import com.tangem.domain.appcurrency.extenstions.unwrap
|
import com.tangem.domain.appcurrency.extenstions.unwrap
|
||||||
import com.tangem.domain.appcurrency.repository.AppCurrencyRepository
|
import com.tangem.domain.appcurrency.repository.AppCurrencyRepository
|
||||||
import com.tangem.domain.demo.IsDemoCardUseCase
|
import com.tangem.domain.demo.IsDemoCardUseCase
|
||||||
import com.tangem.domain.tokens.GetCryptoCurrencyStatusesSyncUseCase
|
import com.tangem.domain.tokens.GetCryptoCurrencyStatusesSyncUseCase
|
||||||
|
import com.tangem.domain.tokens.GetCurrencyCheckUseCase
|
||||||
import com.tangem.domain.tokens.model.*
|
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.CurrenciesRepository
|
||||||
import com.tangem.domain.tokens.repository.CurrencyChecksRepository
|
import com.tangem.domain.tokens.repository.CurrencyChecksRepository
|
||||||
import com.tangem.domain.tokens.repository.QuotesRepository
|
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.domain.*
|
||||||
import com.tangem.feature.swap.domain.models.toStringWithRightOffset
|
import com.tangem.feature.swap.domain.models.toStringWithRightOffset
|
||||||
import com.tangem.feature.swap.domain.models.ui.*
|
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.TransactionManager
|
||||||
import com.tangem.lib.crypto.UserWalletManager
|
import com.tangem.lib.crypto.UserWalletManager
|
||||||
import com.tangem.lib.crypto.models.ProxyAmount
|
import com.tangem.lib.crypto.models.ProxyAmount
|
||||||
|
|
@ -71,6 +70,7 @@ internal class SwapInteractorImpl @AssistedInject constructor(
|
||||||
private val validateTransactionUseCase: ValidateTransactionUseCase,
|
private val validateTransactionUseCase: ValidateTransactionUseCase,
|
||||||
private val estimateFeeUseCase: EstimateFeeUseCase,
|
private val estimateFeeUseCase: EstimateFeeUseCase,
|
||||||
private val getUserWalletUseCase: GetUserWalletUseCase,
|
private val getUserWalletUseCase: GetUserWalletUseCase,
|
||||||
|
private val getCurrencyCheckUseCase: GetCurrencyCheckUseCase,
|
||||||
private val amountFormatter: AmountFormatter,
|
private val amountFormatter: AmountFormatter,
|
||||||
@Assisted private val userWalletId: UserWalletId,
|
@Assisted private val userWalletId: UserWalletId,
|
||||||
) : SwapInteractor {
|
) : SwapInteractor {
|
||||||
|
|
@ -386,111 +386,30 @@ internal class SwapInteractorImpl @AssistedInject constructor(
|
||||||
fromTokenStatus: CryptoCurrencyStatus,
|
fromTokenStatus: CryptoCurrencyStatus,
|
||||||
amount: SwapAmount,
|
amount: SwapAmount,
|
||||||
feeState: TxFeeState,
|
feeState: TxFeeState,
|
||||||
minAdaValue: BigDecimal?,
|
): CryptoCurrencyCheck {
|
||||||
): 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
|
|
||||||
|
|
||||||
val fee = when (feeState) {
|
val fee = when (feeState) {
|
||||||
TxFeeState.Empty -> BigDecimal.ZERO
|
TxFeeState.Empty -> BigDecimal.ZERO
|
||||||
is TxFeeState.MultipleFeeState -> feeState.priorityFee.feeValue
|
is TxFeeState.MultipleFeeState -> feeState.priorityFee.feeValue
|
||||||
is TxFeeState.SingleFeeState -> feeState.fee.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) {
|
return currencyCheck
|
||||||
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()))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun manageTransactionValidationWarnings(
|
private suspend fun manageTransactionValidationWarnings(
|
||||||
warnings: MutableList<Warning>,
|
fromToken: CryptoCurrencyStatus,
|
||||||
fromToken: CryptoCurrency,
|
|
||||||
amount: SwapAmount,
|
amount: SwapAmount,
|
||||||
feeState: TxFeeState,
|
feeState: TxFeeState,
|
||||||
userWalletId: UserWalletId,
|
userWalletId: UserWalletId,
|
||||||
minAdaValue: BigDecimal?,
|
): Throwable? {
|
||||||
) {
|
val currency = fromToken.currency
|
||||||
val fee = Fee.Common(
|
val fee = Fee.Common(
|
||||||
amount = Amount(
|
amount = Amount(
|
||||||
value = when (feeState) {
|
value = when (feeState) {
|
||||||
|
|
@ -498,66 +417,20 @@ internal class SwapInteractorImpl @AssistedInject constructor(
|
||||||
is TxFeeState.MultipleFeeState -> feeState.normalFee.feeValue
|
is TxFeeState.MultipleFeeState -> feeState.normalFee.feeValue
|
||||||
is TxFeeState.SingleFeeState -> feeState.fee.feeValue
|
is TxFeeState.SingleFeeState -> feeState.fee.feeValue
|
||||||
},
|
},
|
||||||
blockchain = Blockchain.fromId(fromToken.network.id.value),
|
blockchain = Blockchain.fromId(currency.network.id.value),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
validateTransactionUseCase(
|
val result = validateTransactionUseCase(
|
||||||
amount = amount.value.convertToSdkAmount(fromToken),
|
amount = amount.value.convertToSdkAmount(currency),
|
||||||
fee = fee,
|
fee = fee,
|
||||||
memo = null,
|
memo = null,
|
||||||
destination = getTokenAddress(fromToken),
|
destination = getTokenAddress(fromToken.currency),
|
||||||
userWalletId = userWalletId,
|
userWalletId = userWalletId,
|
||||||
network = fromToken.network,
|
network = currency.network,
|
||||||
).fold(
|
).leftOrNull()
|
||||||
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),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun addCardanoTransactionValidationError(
|
return result
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun onSwap(
|
override suspend fun onSwap(
|
||||||
|
|
@ -1238,12 +1111,18 @@ internal class SwapInteractorImpl @AssistedInject constructor(
|
||||||
txFeeState = txFee,
|
txFeeState = txFee,
|
||||||
provider = provider,
|
provider = provider,
|
||||||
).copy(
|
).copy(
|
||||||
warnings = manageWarnings(
|
currencyCheck = manageWarnings(
|
||||||
fromTokenStatus = fromToken,
|
fromTokenStatus = fromToken,
|
||||||
amount = amount,
|
amount = amount,
|
||||||
feeState = txFee,
|
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) {
|
when (provider.type) {
|
||||||
|
|
@ -1478,11 +1357,16 @@ internal class SwapInteractorImpl @AssistedInject constructor(
|
||||||
)
|
)
|
||||||
swapState.copy(
|
swapState.copy(
|
||||||
permissionState = PermissionDataState.Empty,
|
permissionState = PermissionDataState.Empty,
|
||||||
warnings = manageWarnings(
|
currencyCheck = manageWarnings(
|
||||||
fromTokenStatus = fromToken,
|
fromTokenStatus = fromToken,
|
||||||
amount = amount,
|
amount = amount,
|
||||||
feeState = txFeeState,
|
feeState = txFeeState,
|
||||||
minAdaValue = null, // no ADA in DEX
|
),
|
||||||
|
validationResult = manageTransactionValidationWarnings(
|
||||||
|
fromToken = fromToken,
|
||||||
|
amount = amount,
|
||||||
|
feeState = txFeeState,
|
||||||
|
userWalletId = userWalletId,
|
||||||
),
|
),
|
||||||
preparedSwapConfigState = preparedSwapConfigState,
|
preparedSwapConfigState = preparedSwapConfigState,
|
||||||
)
|
)
|
||||||
|
|
@ -1578,6 +1462,7 @@ internal class SwapInteractorImpl @AssistedInject constructor(
|
||||||
swapDataModel = swapData,
|
swapDataModel = swapData,
|
||||||
txFee = txFeeState,
|
txFee = txFeeState,
|
||||||
swapProvider = provider,
|
swapProvider = provider,
|
||||||
|
minAdaValue = null,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,9 @@ package com.tangem.feature.swap.models
|
||||||
import androidx.annotation.DrawableRes
|
import androidx.annotation.DrawableRes
|
||||||
import androidx.compose.ui.text.input.TextFieldValue
|
import androidx.compose.ui.text.input.TextFieldValue
|
||||||
import com.tangem.common.ui.bottomsheet.permission.state.GiveTxPermissionState
|
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.R
|
||||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
|
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.StateEvent
|
||||||
import com.tangem.core.ui.event.consumedEvent
|
import com.tangem.core.ui.event.consumedEvent
|
||||||
import com.tangem.core.ui.extensions.TextReference
|
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.FeeItemState
|
||||||
import com.tangem.feature.swap.models.states.ProviderState
|
import com.tangem.feature.swap.models.states.ProviderState
|
||||||
import com.tangem.feature.swap.models.states.events.SwapEvent
|
import com.tangem.feature.swap.models.states.events.SwapEvent
|
||||||
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
|
|
||||||
internal data class SwapStateHolder(
|
internal data class SwapStateHolder(
|
||||||
val sendCardData: SwapCardState,
|
val sendCardData: SwapCardState,
|
||||||
val receiveCardData: SwapCardState,
|
val receiveCardData: SwapCardState,
|
||||||
val blockchainId: String, // not the same as networkId, its local id in app
|
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 event: StateEvent<SwapEvent> = consumedEvent(),
|
||||||
val changeCardsButtonState: ChangeCardsButtonState = ChangeCardsButtonState.ENABLED,
|
val changeCardsButtonState: ChangeCardsButtonState = ChangeCardsButtonState.ENABLED,
|
||||||
val providerState: ProviderState,
|
val providerState: ProviderState,
|
||||||
|
|
@ -106,35 +109,6 @@ data class LegalState(
|
||||||
val onClick: (String) -> Unit,
|
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 {
|
enum class ChangeCardsButtonState {
|
||||||
ENABLED, DISABLED, UPDATE_IN_PROGRESS
|
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 androidx.compose.ui.text.input.TextFieldValue
|
||||||
import com.tangem.common.ui.alerts.models.AlertDemoModeUM
|
import com.tangem.common.ui.alerts.models.AlertDemoModeUM
|
||||||
import com.tangem.common.ui.bottomsheet.permission.state.*
|
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.bottomsheets.TangemBottomSheetConfig
|
||||||
import com.tangem.core.ui.components.currency.icon.converter.CryptoCurrencyToIconStateConverter
|
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.consumedEvent
|
||||||
import com.tangem.core.ui.event.triggeredEvent
|
import com.tangem.core.ui.event.triggeredEvent
|
||||||
import com.tangem.core.ui.extensions.*
|
import com.tangem.core.ui.extensions.*
|
||||||
import com.tangem.core.ui.format.bigdecimal.*
|
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.BigDecimalFormatter
|
||||||
import com.tangem.core.ui.utils.parseBigDecimal
|
import com.tangem.core.ui.utils.parseBigDecimal
|
||||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
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.converters.TokensDataConverter
|
||||||
import com.tangem.feature.swap.domain.models.ExpressDataError
|
import com.tangem.feature.swap.domain.models.ExpressDataError
|
||||||
import com.tangem.feature.swap.domain.models.SwapAmount
|
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.domain.models.ui.*
|
||||||
import com.tangem.feature.swap.models.*
|
import com.tangem.feature.swap.models.*
|
||||||
import com.tangem.feature.swap.models.states.*
|
import com.tangem.feature.swap.models.states.*
|
||||||
import com.tangem.feature.swap.models.states.events.SwapEvent
|
import com.tangem.feature.swap.models.states.events.SwapEvent
|
||||||
import com.tangem.feature.swap.presentation.R
|
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.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.feature.swap.viewmodels.SwapProcessDataState
|
||||||
import com.tangem.utils.Provider
|
import com.tangem.utils.Provider
|
||||||
import com.tangem.utils.StringsSigns.DASH_SIGN
|
import com.tangem.utils.StringsSigns.DASH_SIGN
|
||||||
import com.tangem.utils.StringsSigns.PERCENT
|
import com.tangem.utils.StringsSigns.PERCENT
|
||||||
import com.tangem.utils.StringsSigns.TILDE_SIGN
|
import com.tangem.utils.StringsSigns.TILDE_SIGN
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
import kotlinx.collections.immutable.toImmutableList
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
import java.math.BigDecimal
|
import java.math.BigDecimal
|
||||||
import java.math.RoundingMode
|
import java.math.RoundingMode
|
||||||
|
|
@ -60,6 +66,10 @@ internal class StateBuilder(
|
||||||
appCurrencyProvider = appCurrencyProvider,
|
appCurrencyProvider = appCurrencyProvider,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
private val notificationsFactory by lazy(LazyThreadSafetyMode.NONE) {
|
||||||
|
SwapNotificationsFactory(actions)
|
||||||
|
}
|
||||||
|
|
||||||
fun createInitialLoadingState(
|
fun createInitialLoadingState(
|
||||||
initialCurrencyFrom: CryptoCurrency,
|
initialCurrencyFrom: CryptoCurrency,
|
||||||
initialCurrencyTo: CryptoCurrency?,
|
initialCurrencyTo: CryptoCurrency?,
|
||||||
|
|
@ -110,6 +120,7 @@ internal class StateBuilder(
|
||||||
providerState = ProviderState.Empty(),
|
providerState = ProviderState.Empty(),
|
||||||
shouldShowMaxAmount = false,
|
shouldShowMaxAmount = false,
|
||||||
priceImpact = PriceImpact.Empty(),
|
priceImpact = PriceImpact.Empty(),
|
||||||
|
isInsufficientFunds = false,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -141,18 +152,7 @@ internal class StateBuilder(
|
||||||
),
|
),
|
||||||
canSelectAnotherToken = true,
|
canSelectAnotherToken = true,
|
||||||
),
|
),
|
||||||
warnings = listOf(
|
notifications = notificationsFactory.getNotAvailableStateNotifications(fromToken.currency.name),
|
||||||
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,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
fee = FeeItemState.Empty,
|
fee = FeeItemState.Empty,
|
||||||
swapButton = SwapButton(
|
swapButton = SwapButton(
|
||||||
enabled = false,
|
enabled = false,
|
||||||
|
|
@ -211,7 +211,7 @@ internal class StateBuilder(
|
||||||
networkIconRes = getActiveIconRes(toToken.network.id.value),
|
networkIconRes = getActiveIconRes(toToken.network.id.value),
|
||||||
isBalanceHidden = isBalanceHiddenProvider(),
|
isBalanceHidden = isBalanceHiddenProvider(),
|
||||||
),
|
),
|
||||||
warnings = emptyList(),
|
notifications = persistentListOf(),
|
||||||
fee = FeeItemState.Empty,
|
fee = FeeItemState.Empty,
|
||||||
swapButton = SwapButton(enabled = false, onClick = {}),
|
swapButton = SwapButton(enabled = false, onClick = {}),
|
||||||
providerState = ProviderState.Loading(),
|
providerState = ProviderState.Loading(),
|
||||||
|
|
@ -235,6 +235,7 @@ internal class StateBuilder(
|
||||||
uiStateHolder: SwapStateHolder,
|
uiStateHolder: SwapStateHolder,
|
||||||
quoteModel: SwapState.QuotesLoadedState,
|
quoteModel: SwapState.QuotesLoadedState,
|
||||||
fromToken: CryptoCurrency,
|
fromToken: CryptoCurrency,
|
||||||
|
feeCryptoCurrencyStatus: CryptoCurrencyStatus?,
|
||||||
swapProvider: SwapProvider,
|
swapProvider: SwapProvider,
|
||||||
bestRatedProviderId: String,
|
bestRatedProviderId: String,
|
||||||
isNeedBestRateBadge: Boolean,
|
isNeedBestRateBadge: Boolean,
|
||||||
|
|
@ -243,9 +244,10 @@ internal class StateBuilder(
|
||||||
): SwapStateHolder {
|
): SwapStateHolder {
|
||||||
if (uiStateHolder.sendCardData !is SwapCardState.SwapCardData) return uiStateHolder
|
if (uiStateHolder.sendCardData !is SwapCardState.SwapCardData) return uiStateHolder
|
||||||
if (uiStateHolder.receiveCardData !is SwapCardState.SwapCardData) return uiStateHolder
|
if (uiStateHolder.receiveCardData !is SwapCardState.SwapCardData) return uiStateHolder
|
||||||
val warnings = getWarningsForSuccessState(
|
val notifications = notificationsFactory.getConfirmationStateNotifications(
|
||||||
quoteModel = quoteModel,
|
quoteModel = quoteModel,
|
||||||
fromToken = fromToken,
|
fromToken = fromToken,
|
||||||
|
feeCryptoCurrencyStatus = feeCryptoCurrencyStatus,
|
||||||
selectedFeeType = selectedFeeType,
|
selectedFeeType = selectedFeeType,
|
||||||
providerName = swapProvider.name,
|
providerName = swapProvider.name,
|
||||||
)
|
)
|
||||||
|
|
@ -304,7 +306,8 @@ internal class StateBuilder(
|
||||||
balance = toCurrencyStatus.getFormattedAmount(isNeedSymbol = false),
|
balance = toCurrencyStatus.getFormattedAmount(isNeedSymbol = false),
|
||||||
isBalanceHidden = isBalanceHiddenProvider(),
|
isBalanceHidden = isBalanceHiddenProvider(),
|
||||||
),
|
),
|
||||||
warnings = warnings,
|
isInsufficientFunds = isInsufficientFundsCondition(quoteModel),
|
||||||
|
notifications = notifications,
|
||||||
permissionState = convertPermissionState(
|
permissionState = convertPermissionState(
|
||||||
lastPermissionState = uiStateHolder.permissionState,
|
lastPermissionState = uiStateHolder.permissionState,
|
||||||
permissionDataState = quoteModel.permissionState,
|
permissionDataState = quoteModel.permissionState,
|
||||||
|
|
@ -314,7 +317,7 @@ internal class StateBuilder(
|
||||||
),
|
),
|
||||||
fee = feeState,
|
fee = feeState,
|
||||||
swapButton = SwapButton(
|
swapButton = SwapButton(
|
||||||
enabled = getSwapButtonEnabled(quoteModel),
|
enabled = getSwapButtonEnabled(notifications),
|
||||||
onClick = actions.onSwapClick,
|
onClick = actions.onSwapClick,
|
||||||
),
|
),
|
||||||
changeCardsButtonState = if (isReverseSwapPossible) {
|
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 {
|
private fun isInsufficientFundsCondition(quoteModel: SwapState.QuotesLoadedState): Boolean {
|
||||||
return !quoteModel.preparedSwapConfigState.isBalanceEnough &&
|
return !quoteModel.preparedSwapConfigState.isBalanceEnough &&
|
||||||
quoteModel.preparedSwapConfigState.includeFeeInAmount !is IncludeFeeInAmount.Included
|
quoteModel.preparedSwapConfigState.includeFeeInAmount !is IncludeFeeInAmount.Included
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getSwapButtonEnabled(quoteModel: SwapState.QuotesLoadedState): Boolean {
|
private fun getSwapButtonEnabled(notifications: ImmutableList<NotificationUM>): Boolean {
|
||||||
val status = quoteModel.toTokenInfo.cryptoCurrencyStatus.value
|
return notifications.none {
|
||||||
if (status is CryptoCurrencyStatus.NoAccount) {
|
it is SwapNotificationUM.Error || it is NotificationUM.Error ||
|
||||||
val amount = quoteModel.toTokenInfo.tokenAmount.value
|
it is SwapNotificationUM.Warning.ExpressError || it is SwapNotificationUM.Warning.ExpressGeneralError ||
|
||||||
val amountToCreateAccount = status.amountToCreateAccount
|
it is SwapNotificationUM.Warning.NoAvailableTokensToSwap ||
|
||||||
|
it is SwapNotificationUM.Warning.NeedReserveToCreateAccount
|
||||||
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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -653,15 +392,13 @@ internal class StateBuilder(
|
||||||
): SwapStateHolder {
|
): SwapStateHolder {
|
||||||
if (uiStateHolder.sendCardData !is SwapCardState.SwapCardData) return uiStateHolder
|
if (uiStateHolder.sendCardData !is SwapCardState.SwapCardData) return uiStateHolder
|
||||||
if (uiStateHolder.receiveCardData !is SwapCardState.SwapCardData) return uiStateHolder
|
if (uiStateHolder.receiveCardData !is SwapCardState.SwapCardData) return uiStateHolder
|
||||||
val warnings = mutableListOf<SwapWarning>()
|
val notifications = notificationsFactory.getQuotesErrorStateNotifications(
|
||||||
warnings.add(getWarningForError(expressDataError, fromToken.cryptoCurrencyStatus.currency))
|
expressDataError = expressDataError,
|
||||||
if (includeFeeInAmount is IncludeFeeInAmount.Included && uiStateHolder.fee is FeeItemState.Content) {
|
fromToken = fromToken.cryptoCurrencyStatus.currency,
|
||||||
val feeCoverageNotification = createNetworkFeeCoverageNotificationConfig(
|
feeItem = uiStateHolder.fee,
|
||||||
uiStateHolder.fee.amountCrypto,
|
includeFeeInAmount = includeFeeInAmount,
|
||||||
uiStateHolder.fee.amountFiatFormatted,
|
)
|
||||||
)
|
|
||||||
warnings.add(SwapWarning.GeneralWarning(feeCoverageNotification))
|
|
||||||
}
|
|
||||||
val providerState = getProviderStateForError(
|
val providerState = getProviderStateForError(
|
||||||
swapProvider = swapProvider,
|
swapProvider = swapProvider,
|
||||||
fromToken = fromToken.cryptoCurrencyStatus.currency,
|
fromToken = fromToken.cryptoCurrencyStatus.currency,
|
||||||
|
|
@ -700,7 +437,7 @@ internal class StateBuilder(
|
||||||
balance = fromToken.cryptoCurrencyStatus.getFormattedAmount(isNeedSymbol = false),
|
balance = fromToken.cryptoCurrencyStatus.getFormattedAmount(isNeedSymbol = false),
|
||||||
),
|
),
|
||||||
receiveCardData = receiveCardData,
|
receiveCardData = receiveCardData,
|
||||||
warnings = warnings,
|
notifications = notifications,
|
||||||
permissionState = GiveTxPermissionState.Empty,
|
permissionState = GiveTxPermissionState.Empty,
|
||||||
fee = FeeItemState.Empty,
|
fee = FeeItemState.Empty,
|
||||||
swapButton = SwapButton(
|
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(
|
fun createQuotesEmptyAmountState(
|
||||||
uiStateHolder: SwapStateHolder,
|
uiStateHolder: SwapStateHolder,
|
||||||
emptyAmountState: SwapState.EmptyAmountState,
|
emptyAmountState: SwapState.EmptyAmountState,
|
||||||
|
|
@ -843,7 +529,8 @@ internal class StateBuilder(
|
||||||
balance = toTokenStatus?.getFormattedAmount(isNeedSymbol = false) ?: DASH_SIGN,
|
balance = toTokenStatus?.getFormattedAmount(isNeedSymbol = false) ?: DASH_SIGN,
|
||||||
isBalanceHidden = isBalanceHiddenProvider(),
|
isBalanceHidden = isBalanceHiddenProvider(),
|
||||||
),
|
),
|
||||||
warnings = emptyList(),
|
notifications = persistentListOf(),
|
||||||
|
isInsufficientFunds = false,
|
||||||
fee = FeeItemState.Empty,
|
fee = FeeItemState.Empty,
|
||||||
swapButton = SwapButton(
|
swapButton = SwapButton(
|
||||||
enabled = false,
|
enabled = false,
|
||||||
|
|
@ -1002,19 +689,8 @@ internal class StateBuilder(
|
||||||
|
|
||||||
fun createInitialErrorState(uiState: SwapStateHolder, code: Int, onRefreshClick: () -> Unit): SwapStateHolder {
|
fun createInitialErrorState(uiState: SwapStateHolder, code: Int, onRefreshClick: () -> Unit): SwapStateHolder {
|
||||||
return uiState.copy(
|
return uiState.copy(
|
||||||
warnings = listOf(
|
isInsufficientFunds = false,
|
||||||
SwapWarning.GeneralWarning(
|
notifications = notificationsFactory.getInitialErrorStateNotifications(code, onRefreshClick),
|
||||||
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,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1051,17 +727,9 @@ internal class StateBuilder(
|
||||||
}
|
}
|
||||||
|
|
||||||
fun loadingPermissionState(uiState: SwapStateHolder): SwapStateHolder {
|
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(
|
return uiState.copy(
|
||||||
permissionState = GiveTxPermissionState.InProgress,
|
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 clearAlert(uiState: SwapStateHolder): SwapStateHolder = uiState.copy(event = consumedEvent())
|
||||||
|
|
||||||
fun addWarning(uiState: SwapStateHolder, message: TextReference?, onClick: () -> Unit): SwapStateHolder {
|
fun addNotification(uiState: SwapStateHolder, message: TextReference?, onClick: () -> Unit): SwapStateHolder {
|
||||||
val renewWarnings = uiState.warnings.toMutableList()
|
return uiState.copy(
|
||||||
renewWarnings.add(
|
notifications = notificationsFactory.getGeneralErrorStateNotifications(
|
||||||
SwapWarning.GenericWarning(
|
notifications = uiState.notifications,
|
||||||
message = message,
|
message = message,
|
||||||
onClick = onClick,
|
onClick = onClick,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
return uiState.copy(
|
|
||||||
warnings = renewWarnings,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun convertPermissionState(
|
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 {
|
fun showPermissionBottomSheet(uiState: SwapStateHolder, onDismiss: () -> Unit): SwapStateHolder {
|
||||||
val permissionState = uiState.permissionState
|
val permissionState = uiState.permissionState
|
||||||
if (permissionState is GiveTxPermissionState.ReadyForRequest) {
|
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(
|
fun showSelectFeeBottomSheet(
|
||||||
uiState: SwapStateHolder,
|
uiState: SwapStateHolder,
|
||||||
selectedFee: FeeType,
|
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> {
|
private fun TxFeeState.MultipleFeeState.toFeeItemState(): ImmutableList<FeeItemState.Content> {
|
||||||
return listOf(
|
return listOf(
|
||||||
FeeItemState.Content(
|
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 {
|
private fun getShortAddressValue(fullAddress: String): String {
|
||||||
check(fullAddress.length > ADDRESS_MIN_LENGTH) { "Invalid address" }
|
check(fullAddress.length > ADDRESS_MIN_LENGTH) { "Invalid address" }
|
||||||
val firstAddressPart = fullAddress.substring(startIndex = 0, endIndex = ADDRESS_FIRST_PART_LENGTH)
|
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) }
|
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 {
|
private fun getFormattedFiatAmount(amount: BigDecimal?): String {
|
||||||
val appCurrency = appCurrencyProvider()
|
val appCurrency = appCurrencyProvider()
|
||||||
|
|
||||||
|
|
@ -1735,10 +1235,6 @@ internal class StateBuilder(
|
||||||
return this.divide(to, min(rateDecimals, MAX_DECIMALS_TO_SHOW), RoundingMode.HALF_UP)
|
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 {
|
private fun getLocaleName(): String {
|
||||||
return if (Locale.getDefault().language == "ru") {
|
return if (Locale.getDefault().language == "ru") {
|
||||||
RU_LOCALE
|
RU_LOCALE
|
||||||
|
|
|
||||||
|
|
@ -24,9 +24,9 @@ import androidx.compose.ui.text.withStyle
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.constraintlayout.compose.ConstraintLayout
|
import androidx.constraintlayout.compose.ConstraintLayout
|
||||||
import com.tangem.common.ui.bottomsheet.permission.state.GiveTxPermissionState
|
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.*
|
||||||
import com.tangem.core.ui.components.notifications.Notification
|
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.getActiveIconResByCoinId
|
||||||
import com.tangem.core.ui.extensions.orMaskWithStars
|
import com.tangem.core.ui.extensions.orMaskWithStars
|
||||||
import com.tangem.core.ui.extensions.resolveReference
|
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.*
|
||||||
import com.tangem.feature.swap.models.states.FeeItemState
|
import com.tangem.feature.swap.models.states.FeeItemState
|
||||||
import com.tangem.feature.swap.models.states.ProviderState
|
import com.tangem.feature.swap.models.states.ProviderState
|
||||||
|
import com.tangem.feature.swap.models.states.SwapNotificationUM
|
||||||
import com.tangem.feature.swap.presentation.R
|
import com.tangem.feature.swap.presentation.R
|
||||||
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
|
|
||||||
@Suppress("LongMethod")
|
@Suppress("LongMethod")
|
||||||
@Composable
|
@Composable
|
||||||
|
|
@ -69,7 +71,7 @@ internal fun SwapScreenContent(state: SwapStateHolder, modifier: Modifier = Modi
|
||||||
|
|
||||||
FeeItemBlock(state = state.fee)
|
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)
|
MainButton(state = state, onPermissionWarningClick = state.onShowPermissionBottomSheet)
|
||||||
|
|
||||||
|
|
@ -302,69 +304,27 @@ private fun SwapButton(state: SwapStateHolder, modifier: Modifier = Modifier) {
|
||||||
|
|
||||||
@Suppress("LongMethod", "CyclomaticComplexMethod")
|
@Suppress("LongMethod", "CyclomaticComplexMethod")
|
||||||
@Composable
|
@Composable
|
||||||
private fun SwapWarnings(warnings: List<SwapWarning>) {
|
private fun SwapNotifications(notifications: List<NotificationUM>) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.background(color = TangemTheme.colors.background.secondary)
|
.background(color = TangemTheme.colors.background.secondary)
|
||||||
.fillMaxWidth(),
|
.fillMaxWidth(),
|
||||||
) {
|
) {
|
||||||
warnings.forEach { warning ->
|
notifications.forEach { notification ->
|
||||||
when (warning) {
|
when (notification) {
|
||||||
is SwapWarning.PermissionNeeded -> {
|
is SwapNotificationUM.Error.GenericError -> {
|
||||||
Notification(
|
|
||||||
config = warning.notificationConfig,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
is SwapWarning.GenericWarning -> {
|
|
||||||
val message = warning.message?.resolveReference()
|
|
||||||
?: stringResource(id = R.string.common_unknown_error)
|
|
||||||
RefreshableWarningCard(
|
RefreshableWarningCard(
|
||||||
title = stringResource(id = R.string.common_warning),
|
title = notification.title.resolveReference(),
|
||||||
description = message,
|
description = notification.config.subtitle.resolveReference(),
|
||||||
onClick = warning.onClick,
|
onClick = notification.onConfirmClick,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
is SwapWarning.NoAvailableTokensToSwap -> {
|
is SwapNotificationUM.Error.ApprovalInProgressWarning,
|
||||||
Notification(
|
is SwapNotificationUM.Error.TransactionInProgressWarning,
|
||||||
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 -> {
|
|
||||||
CardWithIcon(
|
CardWithIcon(
|
||||||
title = warning.title.resolveReference(),
|
title = notification.config.title?.resolveReference().orEmpty(),
|
||||||
description = warning.description.resolveReference(),
|
description = notification.config.subtitle.resolveReference(),
|
||||||
icon = {
|
icon = {
|
||||||
CircularProgressIndicator(
|
CircularProgressIndicator(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
|
@ -375,8 +335,20 @@ private fun SwapWarnings(warnings: List<SwapWarning>) {
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
is SwapWarning.Cardano -> Notification(config = warning.notificationConfig)
|
else -> {
|
||||||
SwapWarning.InsufficientFunds -> Unit
|
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()
|
SpacerH8()
|
||||||
}
|
}
|
||||||
|
|
@ -387,7 +359,7 @@ private fun SwapWarnings(warnings: List<SwapWarning>) {
|
||||||
private fun MainButton(state: SwapStateHolder, onPermissionWarningClick: () -> Unit) {
|
private fun MainButton(state: SwapStateHolder, onPermissionWarningClick: () -> Unit) {
|
||||||
// order is important
|
// order is important
|
||||||
when {
|
when {
|
||||||
state.warnings.any { it is SwapWarning.InsufficientFunds } -> {
|
state.isInsufficientFunds -> {
|
||||||
PrimaryButton(
|
PrimaryButton(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
text = stringResource(id = R.string.swapping_insufficient_funds),
|
text = stringResource(id = R.string.swapping_insufficient_funds),
|
||||||
|
|
@ -395,7 +367,7 @@ private fun MainButton(state: SwapStateHolder, onPermissionWarningClick: () -> U
|
||||||
onClick = state.swapButton.onClick,
|
onClick = state.swapButton.onClick,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
state.warnings.any { it is SwapWarning.PermissionNeeded } -> {
|
state.notifications.any { it is SwapNotificationUM.Info.PermissionNeeded } -> {
|
||||||
PrimaryButton(
|
PrimaryButton(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
text = stringResource(id = R.string.give_permission_title),
|
text = stringResource(id = R.string.give_permission_title),
|
||||||
|
|
@ -459,21 +431,12 @@ private val state = SwapStateHolder(
|
||||||
isClickable = true,
|
isClickable = true,
|
||||||
onClick = {},
|
onClick = {},
|
||||||
),
|
),
|
||||||
warnings = listOf(
|
notifications = persistentListOf(
|
||||||
SwapWarning.PermissionNeeded(
|
SwapNotificationUM.Info.PermissionNeeded(
|
||||||
notificationConfig = NotificationConfig(
|
providerName = "Provider",
|
||||||
title = stringReference("Give Permission"),
|
fromTokenSymbol = "POL",
|
||||||
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,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
|
SwapNotificationUM.Warning.NoAvailableTokensToSwap("POLYGON"),
|
||||||
),
|
),
|
||||||
swapButton = SwapButton(enabled = true, onClick = {}),
|
swapButton = SwapButton(enabled = true, onClick = {}),
|
||||||
onRefresh = {},
|
onRefresh = {},
|
||||||
|
|
@ -484,6 +447,7 @@ private val state = SwapStateHolder(
|
||||||
providerState = ProviderState.Loading(),
|
providerState = ProviderState.Loading(),
|
||||||
priceImpact = PriceImpact.Empty(),
|
priceImpact = PriceImpact.Empty(),
|
||||||
shouldShowMaxAmount = true,
|
shouldShowMaxAmount = true,
|
||||||
|
isInsufficientFunds = false,
|
||||||
tosState = TosState(
|
tosState = TosState(
|
||||||
tosLink = LegalState(
|
tosLink = LegalState(
|
||||||
title = stringReference("Terms of Use"),
|
title = stringReference("Terms of Use"),
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ data class SwapProcessDataState(
|
||||||
// Initial network id
|
// Initial network id
|
||||||
val fromCryptoCurrency: CryptoCurrencyStatus? = null,
|
val fromCryptoCurrency: CryptoCurrencyStatus? = null,
|
||||||
val toCryptoCurrency: CryptoCurrencyStatus? = null,
|
val toCryptoCurrency: CryptoCurrencyStatus? = null,
|
||||||
|
val feePaidCryptoCurrency: CryptoCurrencyStatus? = null,
|
||||||
// Amount from input
|
// Amount from input
|
||||||
val amount: String? = null,
|
val amount: String? = null,
|
||||||
val approveDataModel: RequestApproveStateData? = 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.GetCryptoCurrencyStatusSyncUseCase
|
||||||
import com.tangem.domain.tokens.GetCurrencyStatusUpdatesUseCase
|
import com.tangem.domain.tokens.GetCurrencyStatusUpdatesUseCase
|
||||||
import com.tangem.domain.tokens.GetMinimumTransactionAmountSyncUseCase
|
import com.tangem.domain.tokens.GetMinimumTransactionAmountSyncUseCase
|
||||||
|
import com.tangem.domain.tokens.GetFeePaidCryptoCurrencyStatusSyncUseCase
|
||||||
import com.tangem.domain.tokens.UpdateDelayedNetworkStatusUseCase
|
import com.tangem.domain.tokens.UpdateDelayedNetworkStatusUseCase
|
||||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
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.domain.*
|
||||||
import com.tangem.feature.swap.domain.models.ui.*
|
import com.tangem.feature.swap.domain.models.ui.*
|
||||||
import com.tangem.feature.swap.models.SwapStateHolder
|
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.UiActions
|
||||||
|
import com.tangem.feature.swap.models.states.SwapNotificationUM
|
||||||
import com.tangem.feature.swap.presentation.R
|
import com.tangem.feature.swap.presentation.R
|
||||||
import com.tangem.feature.swap.router.SwapNavScreen
|
import com.tangem.feature.swap.router.SwapNavScreen
|
||||||
import com.tangem.feature.swap.router.SwapRouter
|
import com.tangem.feature.swap.router.SwapRouter
|
||||||
|
|
@ -81,6 +82,7 @@ internal class SwapViewModel @Inject constructor(
|
||||||
private val getCryptoCurrencyStatusUseCase: GetCryptoCurrencyStatusSyncUseCase,
|
private val getCryptoCurrencyStatusUseCase: GetCryptoCurrencyStatusSyncUseCase,
|
||||||
private val updateDelayedCurrencyStatusUseCase: UpdateDelayedNetworkStatusUseCase,
|
private val updateDelayedCurrencyStatusUseCase: UpdateDelayedNetworkStatusUseCase,
|
||||||
private val getCurrencyStatusUpdatesUseCase: GetCurrencyStatusUpdatesUseCase,
|
private val getCurrencyStatusUpdatesUseCase: GetCurrencyStatusUpdatesUseCase,
|
||||||
|
private val getFeePaidCryptoCurrencyStatusSyncUseCase: GetFeePaidCryptoCurrencyStatusSyncUseCase,
|
||||||
private val getUserWalletUseCase: GetUserWalletUseCase,
|
private val getUserWalletUseCase: GetUserWalletUseCase,
|
||||||
private val getCardInfoUseCase: GetCardInfoUseCase,
|
private val getCardInfoUseCase: GetCardInfoUseCase,
|
||||||
private val saveBlockchainErrorUseCase: SaveBlockchainErrorUseCase,
|
private val saveBlockchainErrorUseCase: SaveBlockchainErrorUseCase,
|
||||||
|
|
@ -400,7 +402,7 @@ internal class SwapViewModel @Inject constructor(
|
||||||
},
|
},
|
||||||
onError = {
|
onError = {
|
||||||
Timber.e("Error when loading quotes: $it")
|
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,
|
uiStateHolder = uiState,
|
||||||
quoteModel = state,
|
quoteModel = state,
|
||||||
fromToken = fromToken.currency,
|
fromToken = fromToken.currency,
|
||||||
|
feeCryptoCurrencyStatus = dataState.feePaidCryptoCurrency,
|
||||||
swapProvider = provider,
|
swapProvider = provider,
|
||||||
bestRatedProviderId = bestRatedProviderId,
|
bestRatedProviderId = bestRatedProviderId,
|
||||||
isNeedBestRateBadge = dataState.lastLoadedSwapStates.consideredProvidersStates().size > 1,
|
isNeedBestRateBadge = dataState.lastLoadedSwapStates.consideredProvidersStates().size > 1,
|
||||||
selectedFeeType = dataState.selectedFee?.feeType ?: FeeType.NORMAL,
|
selectedFeeType = dataState.selectedFee?.feeType ?: FeeType.NORMAL,
|
||||||
isReverseSwapPossible = isReverseSwapPossible(),
|
isReverseSwapPossible = isReverseSwapPossible(),
|
||||||
)
|
)
|
||||||
if (uiState.warnings.any { it is SwapWarning.UnableToCoverFeeWarning }) {
|
if (uiState.notifications.any { it is SwapNotificationUM.Error.UnableToCoverFeeWarning }) {
|
||||||
analyticsEventHandler.send(
|
analyticsEventHandler.send(
|
||||||
SwapEvents.NoticeNotEnoughFee(
|
SwapEvents.NoticeNotEnoughFee(
|
||||||
token = initialCurrencyFrom.symbol,
|
token = initialCurrencyFrom.symbol,
|
||||||
|
|
@ -838,6 +841,13 @@ internal class SwapViewModel @Inject constructor(
|
||||||
.onEach {
|
.onEach {
|
||||||
Timber.d("${coin.id} balance is ${it.value.amount}")
|
Timber.d("${coin.id} balance is ${it.value.amount}")
|
||||||
|
|
||||||
|
dataState = dataState.copy(
|
||||||
|
feePaidCryptoCurrency = getFeePaidCryptoCurrencyStatusSyncUseCase(
|
||||||
|
userWalletId = userWalletId,
|
||||||
|
cryptoCurrencyStatus = it,
|
||||||
|
).getOrNull() ?: it,
|
||||||
|
)
|
||||||
|
|
||||||
uiState = if (isFromCurrency) {
|
uiState = if (isFromCurrency) {
|
||||||
dataState = dataState.copy(fromCryptoCurrency = it)
|
dataState = dataState.copy(fromCryptoCurrency = it)
|
||||||
stateBuilder.updateSendCurrencyBalance(uiState, 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.compatibility.l2BlockchainsList
|
||||||
import com.tangem.blockchainsdk.utils.ExcludedBlockchains
|
import com.tangem.blockchainsdk.utils.ExcludedBlockchains
|
||||||
import com.tangem.blockchainsdk.utils.fromNetworkId
|
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.converter.XrpTaggedAddressConverter
|
||||||
import com.tangem.lib.crypto.models.XrpTaggedAddress
|
import com.tangem.lib.crypto.models.XrpTaggedAddress
|
||||||
|
import java.math.BigDecimal
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* !!!IMPORTANT!!!
|
* !!!IMPORTANT!!!
|
||||||
|
|
@ -116,6 +119,8 @@ object BlockchainUtils {
|
||||||
return l2BlockchainsList.contains(blockchain)
|
return l2BlockchainsList.contains(blockchain)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getTezosThreshold(): BigDecimal = Blockchain.Tezos.minimalAmount()
|
||||||
|
|
||||||
private fun getNetworkStandardName(blockchain: Blockchain): String {
|
private fun getNetworkStandardName(blockchain: Blockchain): String {
|
||||||
return when (blockchain) {
|
return when (blockchain) {
|
||||||
Blockchain.Ethereum, Blockchain.EthereumTestnet -> "ERC20"
|
Blockchain.Ethereum, Blockchain.EthereumTestnet -> "ERC20"
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue