Updated on 2026-08-14
This commit is contained in:
commit
5da271e45b
26 changed files with 1252 additions and 105 deletions
|
|
@ -436,4 +436,10 @@ internal object TokensDomainModule {
|
|||
fun provideCheckHasLinkedTokensUseCase(currenciesRepository: CurrenciesRepository): CheckHasLinkedTokensUseCase {
|
||||
return CheckHasLinkedTokensUseCase(currenciesRepository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideGetCurrencyCheckUseCase(currencyChecksRepository: CurrencyChecksRepository): GetCurrencyCheckUseCase {
|
||||
return GetCurrencyCheckUseCase(currencyChecksRepository)
|
||||
}
|
||||
}
|
||||
|
|
@ -35,6 +35,7 @@ dependencies {
|
|||
implementation(projects.domain.transaction.models)
|
||||
implementation(projects.domain.wallets.models)
|
||||
|
||||
implementation(deps.tangem.card.core)
|
||||
implementation(deps.tangem.blockchain) {
|
||||
exclude(module = "joda-time")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,251 @@
|
|||
package com.tangem.common.ui.notifications
|
||||
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.common.ui.R
|
||||
import com.tangem.core.ui.components.notifications.NotificationConfig
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.wrappedList
|
||||
import com.tangem.core.ui.utils.BigDecimalFormatter
|
||||
import java.math.BigDecimal
|
||||
|
||||
sealed class NotificationUM(val config: NotificationConfig) {
|
||||
|
||||
open class Error(
|
||||
title: TextReference,
|
||||
subtitle: TextReference,
|
||||
iconResId: Int = R.drawable.ic_alert_24,
|
||||
buttonState: NotificationConfig.ButtonsState? = null,
|
||||
onCloseClick: (() -> Unit)? = null,
|
||||
) : NotificationUM(
|
||||
config = NotificationConfig(
|
||||
title = title,
|
||||
subtitle = subtitle,
|
||||
iconResId = iconResId,
|
||||
buttonsState = buttonState,
|
||||
onCloseClick = onCloseClick,
|
||||
),
|
||||
) {
|
||||
|
||||
data object TotalExceedsBalance : Error(
|
||||
title = resourceReference(R.string.send_notification_exceed_balance_title),
|
||||
subtitle = resourceReference(R.string.send_notification_exceed_balance_text),
|
||||
)
|
||||
|
||||
data object InvalidAmount : Error(
|
||||
title = resourceReference(R.string.send_notification_invalid_amount_title),
|
||||
subtitle = resourceReference(R.string.send_notification_invalid_amount_text),
|
||||
)
|
||||
|
||||
data class MinimumAmountError(val amount: String) : Error(
|
||||
title = resourceReference(R.string.send_notification_invalid_amount_title),
|
||||
subtitle = resourceReference(
|
||||
R.string.send_notification_invalid_minimum_amount_text,
|
||||
wrappedList(amount, amount),
|
||||
),
|
||||
)
|
||||
|
||||
data class TransactionLimitError(
|
||||
val cryptoCurrency: String,
|
||||
val utxoLimit: String,
|
||||
val amountLimit: String,
|
||||
val onConfirmClick: () -> Unit,
|
||||
) : Error(
|
||||
title = resourceReference(R.string.send_notification_transaction_limit_title),
|
||||
subtitle = resourceReference(
|
||||
R.string.send_notification_transaction_limit_text,
|
||||
wrappedList(cryptoCurrency, utxoLimit, amountLimit),
|
||||
),
|
||||
buttonState = NotificationConfig.ButtonsState.PrimaryButtonConfig(
|
||||
text = resourceReference(R.string.send_notification_leave_button, wrappedList(amountLimit)),
|
||||
onClick = onConfirmClick,
|
||||
),
|
||||
)
|
||||
|
||||
data class ExceedsBalance(
|
||||
val networkIconId: Int,
|
||||
val currencyName: String,
|
||||
val feeName: String,
|
||||
val feeSymbol: String,
|
||||
val networkName: String,
|
||||
val mergeFeeNetworkName: Boolean = false,
|
||||
val onClick: (() -> Unit)? = null,
|
||||
) : Error(
|
||||
title = resourceReference(
|
||||
id = R.string.warning_send_blocked_funds_for_fee_title,
|
||||
wrappedList(feeName),
|
||||
),
|
||||
subtitle = resourceReference(
|
||||
id = R.string.warning_send_blocked_funds_for_fee_message,
|
||||
formatArgs = wrappedList(currencyName, networkName, currencyName, feeName, feeSymbol),
|
||||
),
|
||||
iconResId = networkIconId,
|
||||
buttonState = onClick?.let {
|
||||
NotificationConfig.ButtonsState.SecondaryButtonConfig(
|
||||
text = resourceReference(
|
||||
R.string.common_buy_currency,
|
||||
wrappedList(
|
||||
if (mergeFeeNetworkName) {
|
||||
"$currencyName ($feeSymbol)"
|
||||
} else {
|
||||
feeName
|
||||
},
|
||||
),
|
||||
),
|
||||
onClick = onClick,
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
data class ExistentialDeposit(val deposit: String, val onConfirmClick: () -> Unit) : Error(
|
||||
title = resourceReference(R.string.send_notification_existential_deposit_title),
|
||||
subtitle = resourceReference(R.string.send_notification_existential_deposit_text, wrappedList(deposit)),
|
||||
buttonState = NotificationConfig.ButtonsState.PrimaryButtonConfig(
|
||||
text = resourceReference(R.string.send_notification_leave_button, wrappedList(deposit)),
|
||||
onClick = onConfirmClick,
|
||||
),
|
||||
)
|
||||
|
||||
data class ReserveAmount(val amount: String) : Error(
|
||||
title = resourceReference(
|
||||
id = R.string.send_notification_invalid_reserve_amount_title,
|
||||
wrappedList(amount),
|
||||
),
|
||||
subtitle = resourceReference(id = R.string.send_notification_invalid_reserve_amount_text),
|
||||
)
|
||||
}
|
||||
|
||||
open class Warning(
|
||||
title: TextReference,
|
||||
subtitle: TextReference,
|
||||
iconResId: Int = R.drawable.img_attention_20,
|
||||
buttonsState: NotificationConfig.ButtonsState? = null,
|
||||
onCloseClick: (() -> Unit)? = null,
|
||||
) : NotificationUM(
|
||||
config = NotificationConfig(
|
||||
title = title,
|
||||
subtitle = subtitle,
|
||||
iconResId = iconResId,
|
||||
buttonsState = buttonsState,
|
||||
onCloseClick = onCloseClick,
|
||||
),
|
||||
) {
|
||||
data class HighFeeError(
|
||||
val currencyName: String,
|
||||
val amount: String,
|
||||
val onConfirmClick: () -> Unit,
|
||||
val onCloseClick: () -> 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.send_notification_reduce_by, wrappedList(amount)),
|
||||
onClick = onConfirmClick,
|
||||
),
|
||||
onCloseClick = onCloseClick,
|
||||
)
|
||||
|
||||
data object FeeTooLow : Warning(
|
||||
title = resourceReference(id = R.string.send_notification_transaction_delay_title),
|
||||
subtitle = resourceReference(id = R.string.send_notification_transaction_delay_text),
|
||||
)
|
||||
|
||||
data class TooHigh(
|
||||
val value: String,
|
||||
) : Warning(
|
||||
title = resourceReference(id = R.string.send_notification_fee_too_high_title),
|
||||
subtitle = resourceReference(id = R.string.send_notification_fee_too_high_text, wrappedList(value)),
|
||||
)
|
||||
|
||||
data class NetworkFeeUnreachable(val onRefresh: () -> Unit) : Warning(
|
||||
title = resourceReference(R.string.send_fee_unreachable_error_title),
|
||||
subtitle = resourceReference(R.string.send_fee_unreachable_error_text),
|
||||
buttonsState = NotificationConfig.ButtonsState.SecondaryButtonConfig(
|
||||
text = resourceReference(R.string.warning_button_refresh),
|
||||
onClick = onRefresh,
|
||||
),
|
||||
)
|
||||
|
||||
data class TronAccountNotActivated(val tokenName: String) : Warning(
|
||||
title = resourceReference(R.string.send_fee_unreachable_error_title),
|
||||
subtitle = resourceReference(
|
||||
R.string.send_tron_account_activation_error,
|
||||
wrappedList(tokenName),
|
||||
),
|
||||
)
|
||||
|
||||
data class FeeCoverageNotification(val cryptoAmount: String, val fiatAmount: String) : Warning(
|
||||
title = resourceReference(R.string.send_network_fee_warning_title),
|
||||
subtitle = resourceReference(
|
||||
R.string.common_network_fee_warning_content,
|
||||
wrappedList(cryptoAmount, fiatAmount),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
sealed interface Cardano {
|
||||
|
||||
data class MinAdaValueCharged(val tokenName: String, val minAdaValue: String) : Warning(
|
||||
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),
|
||||
),
|
||||
)
|
||||
|
||||
data object InsufficientBalanceToTransferCoin : Error(
|
||||
title = resourceReference(id = R.string.cardano_max_amount_has_token_title),
|
||||
subtitle = resourceReference(id = R.string.cardano_max_amount_has_token_description),
|
||||
)
|
||||
|
||||
data class InsufficientBalanceToTransferToken(val tokenName: String) : Error(
|
||||
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),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
sealed interface Koinos {
|
||||
data class InsufficientRecoverableMana(
|
||||
val mana: BigDecimal,
|
||||
val maxMana: BigDecimal,
|
||||
) : Error(
|
||||
title = resourceReference(R.string.koinos_insufficient_mana_to_send_koin_title),
|
||||
subtitle = resourceReference(
|
||||
R.string.koinos_insufficient_mana_to_send_koin_description,
|
||||
formatArgs = wrappedList(
|
||||
BigDecimalFormatter.formatCryptoAmountShorted(mana, "", Blockchain.Koinos.decimals()),
|
||||
BigDecimalFormatter.formatCryptoAmountShorted(maxMana, "", Blockchain.Koinos.decimals()),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
data object InsufficientBalance : Error(
|
||||
title = resourceReference(R.string.koinos_insufficient_balance_to_send_koin_title),
|
||||
subtitle = resourceReference(R.string.koinos_insufficient_balance_to_send_koin_description),
|
||||
)
|
||||
|
||||
data class ManaExceedsBalance(
|
||||
val availableKoinForTransfer: BigDecimal,
|
||||
val onReduceClick: () -> Unit,
|
||||
) : Error(
|
||||
title = resourceReference(R.string.koinos_mana_exceeds_koin_balance_title),
|
||||
subtitle = resourceReference(
|
||||
R.string.koinos_mana_exceeds_koin_balance_description,
|
||||
formatArgs = wrappedList(
|
||||
BigDecimalFormatter.formatCryptoAmount(
|
||||
availableKoinForTransfer,
|
||||
Blockchain.Koinos.currency,
|
||||
Blockchain.Koinos.decimals(),
|
||||
),
|
||||
),
|
||||
),
|
||||
buttonState = NotificationConfig.ButtonsState.PrimaryButtonConfig(
|
||||
text = resourceReference(R.string.send_notification_reduce_to, wrappedList(availableKoinForTransfer)),
|
||||
onClick = onReduceClick,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,362 @@
|
|||
package com.tangem.common.ui.notifications
|
||||
|
||||
import com.tangem.blockchain.common.BlockchainSdkError
|
||||
import com.tangem.blockchain.common.transaction.Fee
|
||||
import com.tangem.common.ui.R
|
||||
import com.tangem.common.ui.amountScreen.models.AmountFieldModel
|
||||
import com.tangem.common.ui.amountScreen.utils.getFiatString
|
||||
import com.tangem.core.ui.extensions.networkIconResId
|
||||
import com.tangem.core.ui.utils.BigDecimalFormatter
|
||||
import com.tangem.core.ui.utils.parseBigDecimal
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.domain.tokens.model.blockchains.UtxoAmountLimit
|
||||
import com.tangem.domain.tokens.model.warnings.CryptoCurrencyWarning
|
||||
import com.tangem.domain.transaction.error.GetFeeError
|
||||
import java.math.BigDecimal
|
||||
|
||||
@Suppress("LargeClass")
|
||||
object NotificationsFactory {
|
||||
|
||||
fun MutableList<NotificationUM>.addFeeUnreachableNotification(
|
||||
feeError: GetFeeError,
|
||||
tokenName: String,
|
||||
onReload: () -> Unit,
|
||||
) {
|
||||
when (feeError) {
|
||||
is GetFeeError.BlockchainErrors.TronActivationError -> add(
|
||||
NotificationUM.Warning.TronAccountNotActivated(tokenName),
|
||||
)
|
||||
is GetFeeError.DataError,
|
||||
is GetFeeError.UnknownError,
|
||||
-> add(
|
||||
NotificationUM.Warning.NetworkFeeUnreachable(onReload),
|
||||
)
|
||||
else -> {
|
||||
/* do nothing */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun MutableList<NotificationUM>.addExceedBalanceNotification(
|
||||
feeAmount: BigDecimal,
|
||||
sendingAmount: BigDecimal,
|
||||
isSubtractionAvailable: Boolean,
|
||||
cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
) {
|
||||
val balance = cryptoCurrencyStatus.value.amount ?: BigDecimal.ZERO
|
||||
|
||||
if (!isSubtractionAvailable) return
|
||||
|
||||
val showNotification = sendingAmount + feeAmount > balance
|
||||
if (showNotification) {
|
||||
add(NotificationUM.Error.TotalExceedsBalance)
|
||||
}
|
||||
}
|
||||
|
||||
fun MutableList<NotificationUM>.addReserveAmountErrorNotification(
|
||||
reserveAmount: BigDecimal?,
|
||||
sendingAmount: BigDecimal,
|
||||
cryptoCurrency: CryptoCurrency,
|
||||
isAccountFunded: Boolean,
|
||||
) {
|
||||
if (!isAccountFunded && reserveAmount != null && reserveAmount > sendingAmount) {
|
||||
add(
|
||||
NotificationUM.Error.ReserveAmount(
|
||||
BigDecimalFormatter.formatCryptoAmount(
|
||||
cryptoAmount = sendingAmount,
|
||||
cryptoCurrency = cryptoCurrency,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun MutableList<NotificationUM>.addTransactionLimitErrorNotification(
|
||||
utxoLimit: UtxoAmountLimit?,
|
||||
cryptoCurrency: CryptoCurrency,
|
||||
onReduceClick: (
|
||||
reduceAmountTo: BigDecimal,
|
||||
notification: Class<out NotificationUM>,
|
||||
) -> Unit,
|
||||
) {
|
||||
if (utxoLimit != null) {
|
||||
add(
|
||||
NotificationUM.Error.TransactionLimitError(
|
||||
cryptoCurrency = cryptoCurrency.name,
|
||||
utxoLimit = utxoLimit.maxLimit.toPlainString(),
|
||||
amountLimit = BigDecimalFormatter.formatCryptoAmount(
|
||||
cryptoAmount = utxoLimit.maxAmount,
|
||||
cryptoCurrency = cryptoCurrency,
|
||||
),
|
||||
onConfirmClick = {
|
||||
onReduceClick(
|
||||
utxoLimit.maxAmount,
|
||||
NotificationUM.Error.TransactionLimitError::class.java,
|
||||
)
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun MutableList<NotificationUM>.addExistentialWarningNotification(
|
||||
existentialDeposit: BigDecimal?,
|
||||
feeAmount: BigDecimal,
|
||||
receivedAmount: BigDecimal,
|
||||
cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
onReduceClick: (
|
||||
reduceAmountBy: BigDecimal,
|
||||
reduceAmountByDiff: BigDecimal,
|
||||
notification: Class<out NotificationUM>,
|
||||
) -> Unit,
|
||||
) {
|
||||
val cryptoCurrency = cryptoCurrencyStatus.currency
|
||||
val balance = cryptoCurrencyStatus.value.amount ?: return
|
||||
val spendingAmount = if (cryptoCurrency is CryptoCurrency.Token) {
|
||||
feeAmount
|
||||
} else {
|
||||
receivedAmount
|
||||
}
|
||||
val diff = balance.minus(spendingAmount)
|
||||
if (existentialDeposit != null && diff >= BigDecimal.ZERO && existentialDeposit > diff) {
|
||||
add(
|
||||
NotificationUM.Error.ExistentialDeposit(
|
||||
deposit = BigDecimalFormatter.formatCryptoAmountUncapped(
|
||||
cryptoAmount = existentialDeposit,
|
||||
cryptoCurrency = cryptoCurrency,
|
||||
),
|
||||
onConfirmClick = {
|
||||
onReduceClick(
|
||||
existentialDeposit,
|
||||
existentialDeposit.minus(diff),
|
||||
NotificationUM.Error.ExistentialDeposit::class.java,
|
||||
)
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun MutableList<NotificationUM>.addFeeCoverageNotification(
|
||||
isFeeCoverage: Boolean,
|
||||
amountField: AmountFieldModel,
|
||||
sendingValue: BigDecimal,
|
||||
appCurrency: AppCurrency,
|
||||
cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
) {
|
||||
val cryptoCurrency = cryptoCurrencyStatus.currency
|
||||
val fiatRate = cryptoCurrencyStatus.value.fiatRate
|
||||
val amountValue = amountField.cryptoAmount.value ?: return
|
||||
|
||||
val cryptoDiff = amountValue.minus(sendingValue)
|
||||
if (isFeeCoverage) {
|
||||
add(
|
||||
NotificationUM.Warning.FeeCoverageNotification(
|
||||
cryptoAmount = BigDecimalFormatter.formatCryptoAmountUncapped(
|
||||
cryptoAmount = cryptoDiff,
|
||||
cryptoCurrency = cryptoCurrency,
|
||||
),
|
||||
fiatAmount = getFiatString(
|
||||
value = cryptoDiff,
|
||||
rate = fiatRate,
|
||||
appCurrency = appCurrency,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun MutableList<NotificationUM>.addDustWarningNotification(
|
||||
dustValue: BigDecimal?,
|
||||
feeValue: BigDecimal,
|
||||
sendingAmount: BigDecimal,
|
||||
cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
feeCurrencyStatus: CryptoCurrencyStatus?,
|
||||
) {
|
||||
if (dustValue == null) return
|
||||
val isExceedsLimit = checkDustLimits(
|
||||
feeAmount = feeValue,
|
||||
receivedAmount = sendingAmount,
|
||||
dustValue = dustValue,
|
||||
cryptoCurrencyStatus = cryptoCurrencyStatus,
|
||||
feeCurrencyStatus = feeCurrencyStatus,
|
||||
)
|
||||
if (isExceedsLimit) {
|
||||
add(
|
||||
NotificationUM.Error.MinimumAmountError(
|
||||
amount = dustValue.parseBigDecimal(cryptoCurrencyStatus.currency.decimals),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun MutableList<NotificationUM>.addExceedsBalanceNotification(
|
||||
cryptoCurrencyWarning: CryptoCurrencyWarning?,
|
||||
cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
shouldMergeFeeNetworkName: Boolean,
|
||||
onClick: (CryptoCurrency) -> Unit,
|
||||
onAnalyticsEvent: (CryptoCurrency) -> Unit,
|
||||
) {
|
||||
when (cryptoCurrencyWarning) {
|
||||
is CryptoCurrencyWarning.BalanceNotEnoughForFee -> {
|
||||
add(
|
||||
NotificationUM.Error.ExceedsBalance(
|
||||
networkIconId = cryptoCurrencyWarning.coinCurrency.networkIconResId,
|
||||
networkName = cryptoCurrencyWarning.coinCurrency.name,
|
||||
currencyName = cryptoCurrencyStatus.currency.name,
|
||||
feeName = cryptoCurrencyWarning.coinCurrency.name,
|
||||
feeSymbol = cryptoCurrencyWarning.coinCurrency.symbol,
|
||||
mergeFeeNetworkName = shouldMergeFeeNetworkName,
|
||||
onClick = {
|
||||
onClick(cryptoCurrencyWarning.coinCurrency)
|
||||
},
|
||||
),
|
||||
)
|
||||
onAnalyticsEvent(cryptoCurrencyStatus.currency)
|
||||
}
|
||||
is CryptoCurrencyWarning.CustomTokenNotEnoughForFee -> {
|
||||
val currency = cryptoCurrencyWarning.feeCurrency
|
||||
add(
|
||||
NotificationUM.Error.ExceedsBalance(
|
||||
networkIconId = currency?.networkIconResId ?: R.drawable.ic_alert_24,
|
||||
currencyName = cryptoCurrencyWarning.currency.name,
|
||||
feeName = cryptoCurrencyWarning.feeCurrencyName,
|
||||
feeSymbol = cryptoCurrencyWarning.feeCurrencySymbol,
|
||||
networkName = cryptoCurrencyWarning.networkName,
|
||||
mergeFeeNetworkName = shouldMergeFeeNetworkName,
|
||||
onClick = {
|
||||
currency?.let {
|
||||
onClick(currency)
|
||||
}
|
||||
},
|
||||
),
|
||||
)
|
||||
onAnalyticsEvent(cryptoCurrencyWarning.currency)
|
||||
}
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
|
||||
fun MutableList<NotificationUM>.addValidateTransactionNotifications(
|
||||
dustValue: BigDecimal,
|
||||
fee: Fee?,
|
||||
validationError: Throwable?,
|
||||
cryptoCurrency: CryptoCurrency,
|
||||
onReduceClick: (
|
||||
reduceAmountTo: BigDecimal,
|
||||
notification: Class<out NotificationUM>,
|
||||
) -> Unit,
|
||||
) {
|
||||
when (validationError) {
|
||||
is BlockchainSdkError.Cardano -> addCardanoTransactionValidationError(
|
||||
error = validationError,
|
||||
sendingCurrency = cryptoCurrency,
|
||||
dustValue = dustValue,
|
||||
)
|
||||
is BlockchainSdkError.Koinos -> addKoinosTransactionValidationError(
|
||||
error = validationError,
|
||||
onReduceClick = onReduceClick,
|
||||
)
|
||||
null -> (fee as? Fee.CardanoToken)?.let {
|
||||
add(
|
||||
NotificationUM.Cardano.MinAdaValueCharged(
|
||||
tokenName = cryptoCurrency.name,
|
||||
minAdaValue = it.minAdaValue.parseBigDecimal(cryptoCurrency.decimals),
|
||||
),
|
||||
)
|
||||
}
|
||||
else -> return
|
||||
}
|
||||
}
|
||||
|
||||
private fun MutableList<NotificationUM>.addCardanoTransactionValidationError(
|
||||
error: BlockchainSdkError.Cardano,
|
||||
sendingCurrency: CryptoCurrency,
|
||||
dustValue: BigDecimal?,
|
||||
) {
|
||||
when (error) {
|
||||
BlockchainSdkError.Cardano.InsufficientMinAdaBalanceToSendToken -> {
|
||||
add(NotificationUM.Cardano.InsufficientBalanceToTransferToken(sendingCurrency.name))
|
||||
}
|
||||
BlockchainSdkError.Cardano.InsufficientRemainingBalanceToWithdrawTokens -> {
|
||||
when (sendingCurrency) {
|
||||
is CryptoCurrency.Coin -> NotificationUM.Cardano.InsufficientBalanceToTransferCoin
|
||||
is CryptoCurrency.Token -> {
|
||||
NotificationUM.Cardano.InsufficientBalanceToTransferToken(sendingCurrency.name)
|
||||
}
|
||||
}.let(::add)
|
||||
}
|
||||
BlockchainSdkError.Cardano.InsufficientRemainingBalance,
|
||||
BlockchainSdkError.Cardano.InsufficientSendingAdaAmount,
|
||||
-> {
|
||||
dustValue?.let {
|
||||
add(
|
||||
NotificationUM.Error.MinimumAmountError(
|
||||
amount = it.parseBigDecimal(sendingCurrency.decimals),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun MutableList<NotificationUM>.addKoinosTransactionValidationError(
|
||||
error: BlockchainSdkError.Koinos,
|
||||
onReduceClick: (
|
||||
reduceAmountTo: BigDecimal,
|
||||
notification: Class<out NotificationUM>,
|
||||
) -> Unit,
|
||||
) {
|
||||
when (error) {
|
||||
is BlockchainSdkError.Koinos.InsufficientBalance -> {
|
||||
add(NotificationUM.Koinos.InsufficientBalance)
|
||||
}
|
||||
is BlockchainSdkError.Koinos.InsufficientMana -> {
|
||||
add(
|
||||
NotificationUM.Koinos.InsufficientRecoverableMana(
|
||||
mana = error.manaBalance ?: BigDecimal.ZERO,
|
||||
maxMana = error.maxMana ?: BigDecimal.ZERO,
|
||||
),
|
||||
)
|
||||
}
|
||||
is BlockchainSdkError.Koinos.ManaFeeExceedsBalance -> {
|
||||
add(
|
||||
NotificationUM.Koinos.ManaExceedsBalance(
|
||||
availableKoinForTransfer = error.availableKoinForTransfer,
|
||||
onReduceClick = {
|
||||
onReduceClick(
|
||||
error.availableKoinForTransfer,
|
||||
NotificationUM.Koinos.InsufficientRecoverableMana::class.java,
|
||||
)
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkDustLimits(
|
||||
feeAmount: BigDecimal,
|
||||
receivedAmount: BigDecimal,
|
||||
dustValue: BigDecimal,
|
||||
cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
feeCurrencyStatus: CryptoCurrencyStatus?,
|
||||
): Boolean {
|
||||
val change = when (cryptoCurrencyStatus.currency) {
|
||||
is CryptoCurrency.Coin -> {
|
||||
val balance = cryptoCurrencyStatus.value.amount ?: BigDecimal.ZERO
|
||||
balance - (feeAmount + receivedAmount)
|
||||
}
|
||||
is CryptoCurrency.Token -> {
|
||||
val balance = feeCurrencyStatus?.value?.amount ?: BigDecimal.ZERO
|
||||
balance - feeAmount
|
||||
}
|
||||
}
|
||||
|
||||
val isChangeLowerThanDust = change < dustValue && change > BigDecimal.ZERO
|
||||
return receivedAmount < dustValue || isChangeLowerThanDust
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
package com.tangem.domain.tokens.model.warnings
|
||||
|
||||
import com.tangem.domain.tokens.model.blockchains.UtxoAmountLimit
|
||||
import java.math.BigDecimal
|
||||
|
||||
data class CryptoCurrencyCheck(
|
||||
val dustValue: BigDecimal?,
|
||||
val reserveAmount: BigDecimal?,
|
||||
val existentialDeposit: BigDecimal?,
|
||||
val utxoAmountLimit: UtxoAmountLimit?,
|
||||
)
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
package com.tangem.domain.tokens
|
||||
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.domain.tokens.model.warnings.CryptoCurrencyCheck
|
||||
import com.tangem.domain.tokens.repository.CurrencyChecksRepository
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import java.math.BigDecimal
|
||||
|
||||
class GetCurrencyCheckUseCase(
|
||||
private val currencyChecksRepository: CurrencyChecksRepository,
|
||||
) {
|
||||
|
||||
suspend operator fun invoke(
|
||||
userWalletId: UserWalletId,
|
||||
currencyStatus: CryptoCurrencyStatus,
|
||||
amount: BigDecimal?,
|
||||
fee: BigDecimal?,
|
||||
): CryptoCurrencyCheck {
|
||||
val network = currencyStatus.currency.network
|
||||
val dustValue = currencyChecksRepository.getDustValue(userWalletId, network)
|
||||
val reserveAmount = currencyChecksRepository.getReserveAmount(userWalletId, network)
|
||||
val existentialDeposit = currencyChecksRepository.getExistentialDeposit(userWalletId, network)
|
||||
val utxoAmountLimit = if (amount != null && fee != null) {
|
||||
currencyChecksRepository.checkUtxoAmountLimit(
|
||||
userWalletId = userWalletId,
|
||||
network = network,
|
||||
amount = amount,
|
||||
fee = fee,
|
||||
)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
return CryptoCurrencyCheck(
|
||||
dustValue = dustValue,
|
||||
reserveAmount = reserveAmount,
|
||||
existentialDeposit = existentialDeposit,
|
||||
utxoAmountLimit = utxoAmountLimit,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,8 +1,9 @@
|
|||
package com.tangem.features.staking.impl.di
|
||||
|
||||
import com.tangem.common.routing.AppRouter
|
||||
import com.tangem.core.navigation.url.UrlOpener
|
||||
import com.tangem.features.staking.impl.navigation.DefaultStakingRouter
|
||||
import com.tangem.features.staking.api.navigation.StakingRouter
|
||||
import com.tangem.features.staking.impl.navigation.DefaultStakingRouter
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
|
|
@ -18,9 +19,10 @@ internal object StakingRouterModule {
|
|||
|
||||
@Provides
|
||||
@ActivityScoped
|
||||
fun provideStakingRouter(urlOpener: UrlOpener): StakingRouter {
|
||||
fun provideStakingRouter(urlOpener: UrlOpener, router: AppRouter): StakingRouter {
|
||||
return DefaultStakingRouter(
|
||||
urlOpener = urlOpener,
|
||||
router = router,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,15 +1,33 @@
|
|||
package com.tangem.features.staking.impl.navigation
|
||||
|
||||
import androidx.fragment.app.Fragment
|
||||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.common.routing.AppRouter
|
||||
import com.tangem.core.navigation.url.UrlOpener
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.features.staking.impl.presentation.StakingFragment
|
||||
|
||||
internal class DefaultStakingRouter(
|
||||
private val urlOpener: UrlOpener,
|
||||
private val router: AppRouter,
|
||||
) : InnerStakingRouter {
|
||||
override fun getEntryFragment(): Fragment = StakingFragment.create()
|
||||
|
||||
override fun openUrl(url: String) {
|
||||
urlOpener.openUrl(url)
|
||||
}
|
||||
|
||||
override fun openTokenDetails(userWalletId: UserWalletId, currency: CryptoCurrency) {
|
||||
router.pop { isSuccess ->
|
||||
if (isSuccess) {
|
||||
router.push(
|
||||
AppRoute.CurrencyDetails(
|
||||
userWalletId = userWalletId,
|
||||
currency = currency,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,8 +1,12 @@
|
|||
package com.tangem.features.staking.impl.navigation
|
||||
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.features.staking.api.navigation.StakingRouter
|
||||
|
||||
interface InnerStakingRouter : StakingRouter {
|
||||
|
||||
fun openUrl(url: String)
|
||||
|
||||
fun openTokenDetails(userWalletId: UserWalletId, currency: CryptoCurrency)
|
||||
}
|
||||
|
|
@ -1,61 +1,56 @@
|
|||
package com.tangem.features.staking.impl.presentation.state
|
||||
|
||||
import com.tangem.common.ui.notifications.NotificationUM
|
||||
import com.tangem.core.ui.components.notifications.NotificationConfig
|
||||
import com.tangem.core.ui.extensions.*
|
||||
import com.tangem.features.staking.impl.R
|
||||
|
||||
internal sealed class StakingNotification(val config: NotificationConfig) {
|
||||
internal object StakingNotification {
|
||||
|
||||
sealed class Error(
|
||||
title: TextReference,
|
||||
subtitle: TextReference,
|
||||
iconResId: Int = R.drawable.ic_alert_24,
|
||||
buttonState: NotificationConfig.ButtonsState? = null,
|
||||
onCloseClick: (() -> Unit)? = null,
|
||||
) : StakingNotification(
|
||||
config = NotificationConfig(
|
||||
title = title,
|
||||
subtitle = subtitle,
|
||||
iconResId = iconResId,
|
||||
buttonsState = buttonState,
|
||||
onCloseClick = onCloseClick,
|
||||
),
|
||||
) : NotificationUM.Error(
|
||||
title = title,
|
||||
subtitle = subtitle,
|
||||
iconResId = R.drawable.ic_alert_24,
|
||||
buttonState = buttonState,
|
||||
onCloseClick = onCloseClick,
|
||||
) {
|
||||
data class StakedPositionNotFoundError(val message: String) : Error(
|
||||
data class StakedPositionNotFoundError(val message: String) : StakingNotification.Error(
|
||||
title = stringReference(message),
|
||||
subtitle = stringReference(message),
|
||||
)
|
||||
|
||||
data class Common(val subtitle: TextReference) : Error(
|
||||
data class Common(val subtitle: TextReference) : StakingNotification.Error(
|
||||
title = resourceReference(R.string.common_error),
|
||||
subtitle = subtitle,
|
||||
)
|
||||
}
|
||||
|
||||
sealed class Warning(
|
||||
title: TextReference,
|
||||
subtitle: TextReference,
|
||||
buttonsState: NotificationConfig.ButtonsState? = null,
|
||||
onCloseClick: (() -> Unit)? = null,
|
||||
) : StakingNotification(
|
||||
config = NotificationConfig(
|
||||
title = title,
|
||||
subtitle = subtitle,
|
||||
iconResId = R.drawable.ic_alert_circle_24,
|
||||
buttonsState = buttonsState,
|
||||
onCloseClick = onCloseClick,
|
||||
),
|
||||
) : NotificationUM.Warning(
|
||||
title = title,
|
||||
subtitle = subtitle,
|
||||
iconResId = R.drawable.ic_alert_circle_24,
|
||||
buttonsState = buttonsState,
|
||||
onCloseClick = onCloseClick,
|
||||
) {
|
||||
data class EarnRewards(
|
||||
val subtitleText: TextReference,
|
||||
) : Warning(
|
||||
) : StakingNotification.Warning(
|
||||
title = resourceReference(R.string.staking_notification_earn_rewards_title),
|
||||
subtitle = subtitleText,
|
||||
)
|
||||
|
||||
data class Unstake(
|
||||
val cooldownPeriodDays: Int,
|
||||
) : Warning(
|
||||
) : StakingNotification.Warning(
|
||||
title = resourceReference(R.string.common_unstake),
|
||||
subtitle = resourceReference(
|
||||
R.string.staking_notification_unstake_text,
|
||||
|
|
@ -72,6 +67,6 @@ internal sealed class StakingNotification(val config: NotificationConfig) {
|
|||
data class TransactionInProgress(
|
||||
val title: TextReference,
|
||||
val description: TextReference,
|
||||
) : Warning(title = title, subtitle = description)
|
||||
) : StakingNotification.Warning(title = title, subtitle = description)
|
||||
}
|
||||
}
|
||||
|
|
@ -3,6 +3,7 @@ package com.tangem.features.staking.impl.presentation.state
|
|||
import androidx.compose.runtime.Immutable
|
||||
import com.tangem.common.ui.amountScreen.models.AmountState
|
||||
import com.tangem.common.ui.navigationButtons.NavigationButtonsState
|
||||
import com.tangem.common.ui.notifications.NotificationUM
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
|
||||
import com.tangem.core.ui.components.list.RoundedListWithDividersItemData
|
||||
import com.tangem.core.ui.event.StateEvent
|
||||
|
|
@ -13,6 +14,7 @@ import com.tangem.features.staking.impl.presentation.state.bottomsheet.InfoType
|
|||
import com.tangem.features.staking.impl.presentation.state.events.StakingEvent
|
||||
import com.tangem.features.staking.impl.presentation.viewmodel.StakingClickIntents
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import java.math.BigDecimal
|
||||
|
||||
/**
|
||||
* Ui states of the staking screen
|
||||
|
|
@ -84,10 +86,11 @@ internal sealed class StakingStates {
|
|||
val feeState: FeeState,
|
||||
val validatorState: ValidatorState,
|
||||
val pendingAction: PendingAction?,
|
||||
val notifications: ImmutableList<StakingNotification>,
|
||||
val notifications: ImmutableList<NotificationUM>,
|
||||
val footerText: String,
|
||||
val transactionDoneState: TransactionDoneState,
|
||||
val isApprovalNeeded: Boolean,
|
||||
val reduceAmountBy: BigDecimal?,
|
||||
) : ConfirmationState()
|
||||
|
||||
data class Empty(
|
||||
|
|
|
|||
|
|
@ -89,5 +89,6 @@ internal object ConfirmationStatePreviewData {
|
|||
transactionDoneState = TransactionDoneState.Empty,
|
||||
pendingAction = null,
|
||||
isApprovalNeeded = false,
|
||||
reduceAmountBy = null,
|
||||
)
|
||||
}
|
||||
|
|
@ -1,11 +1,14 @@
|
|||
package com.tangem.features.staking.impl.presentation.state.stub
|
||||
|
||||
import com.tangem.common.ui.notifications.NotificationUM
|
||||
import com.tangem.domain.staking.model.stakekit.PendingAction
|
||||
import com.tangem.domain.staking.model.stakekit.Yield
|
||||
import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.features.staking.impl.presentation.state.BalanceState
|
||||
import com.tangem.features.staking.impl.presentation.state.bottomsheet.InfoType
|
||||
import com.tangem.features.staking.impl.presentation.viewmodel.StakingClickIntents
|
||||
import java.math.BigDecimal
|
||||
|
||||
@Suppress("TooManyFunctions")
|
||||
internal object StakingClickIntentsStub : StakingClickIntents {
|
||||
|
|
@ -49,4 +52,18 @@ internal object StakingClickIntentsStub : StakingClickIntents {
|
|||
override fun onFailedTxEmailClick(errorMessage: String) {}
|
||||
|
||||
override fun onActiveStake(activeStake: BalanceState) {}
|
||||
|
||||
override fun getFee(pendingAction: PendingAction?) {}
|
||||
|
||||
override fun onAmountReduceByClick(
|
||||
reduceAmountBy: BigDecimal,
|
||||
reduceAmountByDiff: BigDecimal,
|
||||
notification: Class<out NotificationUM>,
|
||||
) {}
|
||||
|
||||
override fun onAmountReduceToClick(reduceAmountTo: BigDecimal, notification: Class<out NotificationUM>) {}
|
||||
|
||||
override fun onNotificationCancel(notification: Class<out NotificationUM>) {}
|
||||
|
||||
override fun openTokenDetails(cryptoCurrency: CryptoCurrency) {}
|
||||
}
|
||||
|
|
@ -1,28 +1,37 @@
|
|||
package com.tangem.features.staking.impl.presentation.state.transformers
|
||||
|
||||
import com.tangem.common.ui.notifications.NotificationUM
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.domain.staking.model.stakekit.StakingError
|
||||
import com.tangem.features.staking.impl.presentation.state.*
|
||||
import com.tangem.features.staking.impl.presentation.state.FeeState
|
||||
import com.tangem.features.staking.impl.presentation.state.StakingNotification
|
||||
import com.tangem.features.staking.impl.presentation.state.StakingStates
|
||||
import com.tangem.features.staking.impl.presentation.state.StakingUiState
|
||||
import com.tangem.utils.transformer.Transformer
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
|
||||
internal class AddStakingErrorTransformer(
|
||||
private val error: StakingError,
|
||||
private val error: StakingError? = null,
|
||||
) : Transformer<StakingUiState> {
|
||||
|
||||
override fun transform(prevState: StakingUiState): StakingUiState {
|
||||
val confirmationState =
|
||||
prevState.confirmationState as? StakingStates.ConfirmationState.Data ?: return prevState
|
||||
|
||||
val notifications = buildList {
|
||||
addAll(confirmationState.notifications)
|
||||
error?.let { add(convertToNotification(it)) }
|
||||
}.toPersistentList()
|
||||
|
||||
return prevState.copy(
|
||||
confirmationState = confirmationState.copy(
|
||||
notifications = (confirmationState.notifications + convertToNotification(error)).toPersistentList(),
|
||||
notifications = notifications,
|
||||
feeState = FeeState.Error,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun convertToNotification(error: StakingError): StakingNotification {
|
||||
private fun convertToNotification(error: StakingError): NotificationUM {
|
||||
return when (error) {
|
||||
is StakingError.StakedPositionNotFoundError -> StakingNotification.Error.StakedPositionNotFoundError(
|
||||
message = error.toString(),
|
||||
|
|
|
|||
|
|
@ -0,0 +1,189 @@
|
|||
package com.tangem.features.staking.impl.presentation.state.transformers
|
||||
|
||||
import com.tangem.common.ui.amountScreen.models.AmountState
|
||||
import com.tangem.common.ui.notifications.NotificationUM
|
||||
import com.tangem.common.ui.notifications.NotificationsFactory.addDustWarningNotification
|
||||
import com.tangem.common.ui.notifications.NotificationsFactory.addExceedBalanceNotification
|
||||
import com.tangem.common.ui.notifications.NotificationsFactory.addExceedsBalanceNotification
|
||||
import com.tangem.common.ui.notifications.NotificationsFactory.addExistentialWarningNotification
|
||||
import com.tangem.common.ui.notifications.NotificationsFactory.addFeeCoverageNotification
|
||||
import com.tangem.common.ui.notifications.NotificationsFactory.addFeeUnreachableNotification
|
||||
import com.tangem.common.ui.notifications.NotificationsFactory.addReserveAmountErrorNotification
|
||||
import com.tangem.common.ui.notifications.NotificationsFactory.addTransactionLimitErrorNotification
|
||||
import com.tangem.common.ui.notifications.NotificationsFactory.addValidateTransactionNotifications
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.domain.tokens.model.warnings.CryptoCurrencyCheck
|
||||
import com.tangem.domain.tokens.model.warnings.CryptoCurrencyWarning
|
||||
import com.tangem.domain.transaction.error.GetFeeError
|
||||
import com.tangem.features.staking.impl.presentation.state.FeeState
|
||||
import com.tangem.features.staking.impl.presentation.state.StakingNotification
|
||||
import com.tangem.features.staking.impl.presentation.state.StakingStates
|
||||
import com.tangem.features.staking.impl.presentation.state.StakingUiState
|
||||
import com.tangem.features.staking.impl.presentation.state.utils.checkAndCalculateSubtractedAmount
|
||||
import com.tangem.features.staking.impl.presentation.state.utils.checkFeeCoverage
|
||||
import com.tangem.lib.crypto.BlockchainUtils
|
||||
import com.tangem.utils.Provider
|
||||
import com.tangem.utils.extensions.orZero
|
||||
import com.tangem.utils.transformer.Transformer
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import java.math.BigDecimal
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
internal class AddStakingNotificationsTransformer(
|
||||
private val cryptoCurrencyStatusProvider: Provider<CryptoCurrencyStatus>,
|
||||
private val appCurrencyProvider: Provider<AppCurrency>,
|
||||
private val feeCryptoCurrencyStatus: CryptoCurrencyStatus?,
|
||||
private val currencyWarning: CryptoCurrencyWarning?,
|
||||
private val validatorError: Throwable?,
|
||||
private val feeError: GetFeeError?,
|
||||
private val currencyCheck: CryptoCurrencyCheck,
|
||||
private val isSubtractAvailable: Boolean,
|
||||
) : Transformer<StakingUiState> {
|
||||
override fun transform(prevState: StakingUiState): StakingUiState {
|
||||
val cryptoCurrencyStatus = cryptoCurrencyStatusProvider()
|
||||
val balance = cryptoCurrencyStatus.value.amount.orZero()
|
||||
|
||||
val confirmationState = prevState.confirmationState as? StakingStates.ConfirmationState.Data ?: return prevState
|
||||
val amountState = prevState.amountState as? AmountState.Data ?: return prevState
|
||||
val feeState = confirmationState.feeState as? FeeState.Content
|
||||
|
||||
val amountValue = amountState.amountTextField.cryptoAmount.value.orZero()
|
||||
val feeValue = feeState?.fee?.amount?.value.orZero()
|
||||
val reduceAmountBy = confirmationState.reduceAmountBy.orZero()
|
||||
|
||||
val isEnterAction = prevState.actionType == StakingActionCommonType.ENTER
|
||||
val isFeeCoverage = checkFeeCoverage(
|
||||
amountValue = amountValue,
|
||||
feeValue = feeValue,
|
||||
balance = balance,
|
||||
isSubtractAvailable = isSubtractAvailable,
|
||||
reduceAmountBy = reduceAmountBy,
|
||||
)
|
||||
val sendingAmount = checkAndCalculateSubtractedAmount(
|
||||
isAmountSubtractAvailable = isSubtractAvailable,
|
||||
cryptoCurrencyStatus = cryptoCurrencyStatus,
|
||||
amountValue = amountValue,
|
||||
feeValue = feeValue,
|
||||
reduceAmountBy = reduceAmountBy,
|
||||
)
|
||||
|
||||
val notifications = buildList {
|
||||
// errors
|
||||
addErrorNotifications(
|
||||
prevState = prevState,
|
||||
feeError = feeError,
|
||||
sendingAmount = sendingAmount,
|
||||
onReload = { prevState.clickIntents.getFee(confirmationState.pendingAction) },
|
||||
feeValue = feeValue,
|
||||
)
|
||||
// warnings
|
||||
addWarningNotifications(
|
||||
prevState = prevState,
|
||||
amountState = amountState,
|
||||
feeState = feeState,
|
||||
sendingAmount = sendingAmount,
|
||||
isFeeCoverage = isFeeCoverage && isEnterAction,
|
||||
)
|
||||
|
||||
addAll(confirmationState.notifications)
|
||||
}.toImmutableList()
|
||||
|
||||
return prevState.copy(
|
||||
confirmationState = confirmationState.copy(
|
||||
notifications = notifications.toImmutableList(),
|
||||
isPrimaryButtonEnabled = !notifications.any { it is StakingNotification.Error },
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun MutableList<NotificationUM>.addErrorNotifications(
|
||||
prevState: StakingUiState,
|
||||
onReload: () -> Unit,
|
||||
feeError: GetFeeError?,
|
||||
sendingAmount: BigDecimal,
|
||||
feeValue: BigDecimal,
|
||||
) {
|
||||
val cryptoCurrencyStatus = cryptoCurrencyStatusProvider()
|
||||
val cryptoCurrency = cryptoCurrencyStatus.currency
|
||||
val network = cryptoCurrency.network
|
||||
|
||||
if (feeError != null) {
|
||||
addFeeUnreachableNotification(
|
||||
feeError = feeError,
|
||||
tokenName = cryptoCurrencyStatusProvider().currency.name,
|
||||
onReload = onReload,
|
||||
)
|
||||
}
|
||||
addExceedBalanceNotification(
|
||||
feeAmount = feeValue,
|
||||
sendingAmount = sendingAmount,
|
||||
isSubtractionAvailable = isSubtractAvailable,
|
||||
cryptoCurrencyStatus = cryptoCurrencyStatus,
|
||||
)
|
||||
addExceedsBalanceNotification(
|
||||
cryptoCurrencyWarning = currencyWarning,
|
||||
cryptoCurrencyStatus = cryptoCurrencyStatus,
|
||||
shouldMergeFeeNetworkName = BlockchainUtils.isArbitrum(network.backendId),
|
||||
onClick = prevState.clickIntents::openTokenDetails,
|
||||
onAnalyticsEvent = { /* [REDACTED_TODO_COMMENT] */ },
|
||||
)
|
||||
if (!BlockchainUtils.isCardano(network.id.value)) {
|
||||
addDustWarningNotification(
|
||||
dustValue = currencyCheck.dustValue,
|
||||
feeValue = feeValue,
|
||||
sendingAmount = sendingAmount,
|
||||
cryptoCurrencyStatus = cryptoCurrencyStatus,
|
||||
feeCurrencyStatus = feeCryptoCurrencyStatus,
|
||||
)
|
||||
}
|
||||
addTransactionLimitErrorNotification(
|
||||
utxoLimit = currencyCheck.utxoAmountLimit,
|
||||
cryptoCurrency = cryptoCurrency,
|
||||
onReduceClick = prevState.clickIntents::onAmountReduceToClick,
|
||||
)
|
||||
addReserveAmountErrorNotification(
|
||||
reserveAmount = currencyCheck.reserveAmount,
|
||||
sendingAmount = sendingAmount,
|
||||
cryptoCurrency = cryptoCurrency,
|
||||
isAccountFunded = false,
|
||||
)
|
||||
}
|
||||
|
||||
private fun MutableList<NotificationUM>.addWarningNotifications(
|
||||
prevState: StakingUiState,
|
||||
amountState: AmountState.Data,
|
||||
feeState: FeeState.Content?,
|
||||
sendingAmount: BigDecimal,
|
||||
isFeeCoverage: Boolean,
|
||||
) {
|
||||
val cryptoCurrencyStatus = cryptoCurrencyStatusProvider()
|
||||
val appCurrency = appCurrencyProvider()
|
||||
val cryptoCurrency = cryptoCurrencyStatus.currency
|
||||
|
||||
addExistentialWarningNotification(
|
||||
existentialDeposit = currencyCheck.existentialDeposit,
|
||||
feeAmount = feeState?.fee?.amount?.value.orZero(),
|
||||
receivedAmount = sendingAmount,
|
||||
cryptoCurrencyStatus = cryptoCurrencyStatus,
|
||||
onReduceClick = prevState.clickIntents::onAmountReduceByClick,
|
||||
)
|
||||
addFeeCoverageNotification(
|
||||
isFeeCoverage = isFeeCoverage,
|
||||
amountField = amountState.amountTextField,
|
||||
sendingValue = sendingAmount,
|
||||
appCurrency = appCurrency,
|
||||
cryptoCurrencyStatus = cryptoCurrencyStatus,
|
||||
)
|
||||
|
||||
// blockchain specific
|
||||
addValidateTransactionNotifications(
|
||||
dustValue = currencyCheck.dustValue.orZero(),
|
||||
fee = feeState?.fee,
|
||||
validationError = validatorError,
|
||||
cryptoCurrency = cryptoCurrency,
|
||||
onReduceClick = prevState.clickIntents::onAmountReduceToClick,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
package com.tangem.features.staking.impl.presentation.state.transformers
|
||||
|
||||
import com.tangem.common.ui.notifications.NotificationUM
|
||||
import com.tangem.features.staking.impl.presentation.state.StakingStates
|
||||
import com.tangem.features.staking.impl.presentation.state.StakingUiState
|
||||
import com.tangem.utils.transformer.Transformer
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
|
||||
internal class DismissStakingNotificationsStateTransformer(
|
||||
private val notification: Class<out NotificationUM>,
|
||||
) : Transformer<StakingUiState> {
|
||||
override fun transform(prevState: StakingUiState): StakingUiState {
|
||||
val confirmationState = prevState.confirmationState as? StakingStates.ConfirmationState.Data
|
||||
val updatedNotifications = confirmationState?.notifications
|
||||
?.filterNot { it::class == notification }?.toPersistentList()
|
||||
?: persistentListOf()
|
||||
|
||||
return prevState.copy(
|
||||
confirmationState = confirmationState?.copy(
|
||||
notifications = updatedNotifications,
|
||||
) ?: StakingStates.ConfirmationState.Empty(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.features.staking.impl.presentation.state.transformers
|
||||
|
||||
import com.tangem.common.ui.notifications.NotificationUM
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.wrappedList
|
||||
import com.tangem.domain.staking.model.stakekit.Yield
|
||||
|
|
@ -34,11 +35,12 @@ internal class SetConfirmationStateLoadingTransformer(
|
|||
transactionDoneState = TransactionDoneState.Empty,
|
||||
pendingAction = null,
|
||||
isApprovalNeeded = false,
|
||||
reduceAmountBy = null,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun getNotifications(prevState: StakingUiState): ImmutableList<StakingNotification> {
|
||||
private fun getNotifications(prevState: StakingUiState): ImmutableList<NotificationUM> {
|
||||
return persistentListOf(
|
||||
if (prevState.actionType == StakingActionCommonType.EXIT) {
|
||||
StakingNotification.Warning.Unstake(
|
||||
|
|
|
|||
|
|
@ -214,6 +214,7 @@ internal class SetInitialDataStateTransformer(
|
|||
transactionDoneState = TransactionDoneState.Empty,
|
||||
pendingAction = null,
|
||||
isApprovalNeeded = isApprovalNeeded,
|
||||
reduceAmountBy = null,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,19 @@
|
|||
package com.tangem.features.staking.impl.presentation.state.transformers.amount
|
||||
|
||||
import com.tangem.common.ui.amountScreen.converters.AmountReduceByTransformer
|
||||
import com.tangem.common.ui.amountScreen.converters.AmountReduceByTransformer.ReduceByData
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.features.staking.impl.presentation.state.StakingUiState
|
||||
import com.tangem.utils.transformer.Transformer
|
||||
|
||||
internal class AmountReduceByStateTransformer(
|
||||
private val cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
private val value: ReduceByData,
|
||||
) : Transformer<StakingUiState> {
|
||||
|
||||
override fun transform(prevState: StakingUiState): StakingUiState {
|
||||
return prevState.copy(
|
||||
amountState = AmountReduceByTransformer(cryptoCurrencyStatus, value).transform(prevState.amountState),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
package com.tangem.features.staking.impl.presentation.state.transformers.amount
|
||||
|
||||
import com.tangem.common.ui.amountScreen.converters.AmountReduceToTransformer
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.features.staking.impl.presentation.state.StakingUiState
|
||||
import com.tangem.utils.transformer.Transformer
|
||||
import java.math.BigDecimal
|
||||
|
||||
internal class AmountReduceToStateTransformer(
|
||||
private val cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
private val value: BigDecimal,
|
||||
) : Transformer<StakingUiState> {
|
||||
override fun transform(prevState: StakingUiState): StakingUiState {
|
||||
return prevState.copy(
|
||||
amountState = AmountReduceToTransformer(cryptoCurrencyStatus, value).transform(prevState.amountState),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
package com.tangem.features.staking.impl.presentation.state.utils
|
||||
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import java.math.BigDecimal
|
||||
|
||||
/**
|
||||
* Check and calculates subtracted amount
|
||||
*/
|
||||
internal fun checkAndCalculateSubtractedAmount(
|
||||
isAmountSubtractAvailable: Boolean,
|
||||
cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
amountValue: BigDecimal,
|
||||
feeValue: BigDecimal,
|
||||
reduceAmountBy: BigDecimal,
|
||||
): BigDecimal {
|
||||
val balance = cryptoCurrencyStatus.value.amount ?: return amountValue
|
||||
val isFeeCoverage = checkFeeCoverage(
|
||||
isSubtractAvailable = isAmountSubtractAvailable,
|
||||
balance = balance,
|
||||
amountValue = amountValue,
|
||||
feeValue = feeValue,
|
||||
reduceAmountBy = reduceAmountBy,
|
||||
)
|
||||
return if (isFeeCoverage) {
|
||||
balance.minus(reduceAmountBy).minus(feeValue)
|
||||
} else {
|
||||
amountValue
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if sending amount with fee is greater than balance
|
||||
*/
|
||||
internal fun checkFeeCoverage(
|
||||
isSubtractAvailable: Boolean,
|
||||
balance: BigDecimal,
|
||||
amountValue: BigDecimal,
|
||||
feeValue: BigDecimal,
|
||||
reduceAmountBy: BigDecimal?,
|
||||
): Boolean {
|
||||
if (!isSubtractAvailable) return false
|
||||
val reducedBy = balance - (reduceAmountBy ?: BigDecimal.ZERO)
|
||||
return reducedBy < amountValue + feeValue && reducedBy > feeValue && reducedBy >= amountValue
|
||||
}
|
||||
|
|
@ -5,6 +5,7 @@ import androidx.compose.material3.CircularProgressIndicator
|
|||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.key
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.tangem.common.ui.notifications.NotificationUM
|
||||
import com.tangem.core.ui.components.CardWithIcon
|
||||
import com.tangem.core.ui.components.notifications.Notification
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
|
|
@ -13,7 +14,7 @@ import com.tangem.features.staking.impl.presentation.state.StakingNotification
|
|||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
@Composable
|
||||
internal fun NotificationsBlock(notifications: ImmutableList<StakingNotification>) {
|
||||
internal fun NotificationsBlock(notifications: ImmutableList<NotificationUM>) {
|
||||
notifications.forEach { notification ->
|
||||
key(notification) {
|
||||
if (notification is StakingNotification.Warning.TransactionInProgress) {
|
||||
|
|
@ -32,8 +33,15 @@ internal fun NotificationsBlock(notifications: ImmutableList<StakingNotification
|
|||
Notification(
|
||||
config = notification.config,
|
||||
iconTint = when (notification) {
|
||||
is StakingNotification.Error -> TangemTheme.colors.icon.warning
|
||||
is StakingNotification.Warning -> TangemTheme.colors.icon.accent
|
||||
|
||||
is NotificationUM.Error.ExceedsBalance,
|
||||
is NotificationUM.Warning,
|
||||
-> null
|
||||
|
||||
is StakingNotification.Error,
|
||||
is NotificationUM.Error,
|
||||
-> TangemTheme.colors.icon.warning
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,16 @@
|
|||
package com.tangem.features.staking.impl.presentation.viewmodel
|
||||
|
||||
import com.tangem.common.ui.amountScreen.AmountScreenClickIntents
|
||||
import com.tangem.common.ui.notifications.NotificationUM
|
||||
import com.tangem.domain.staking.model.stakekit.PendingAction
|
||||
import com.tangem.domain.staking.model.stakekit.Yield
|
||||
import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.features.staking.impl.presentation.state.BalanceState
|
||||
import com.tangem.features.staking.impl.presentation.state.bottomsheet.InfoType
|
||||
import java.math.BigDecimal
|
||||
|
||||
@Suppress("TooManyFunctions")
|
||||
internal interface StakingClickIntents : AmountScreenClickIntents {
|
||||
|
||||
fun onBackClick()
|
||||
|
|
@ -21,6 +25,8 @@ internal interface StakingClickIntents : AmountScreenClickIntents {
|
|||
|
||||
fun onInfoClick(infoType: InfoType)
|
||||
|
||||
fun getFee(pendingAction: PendingAction?)
|
||||
|
||||
override fun onAmountNext() = onNextClick(actionType = null)
|
||||
|
||||
fun openValidators()
|
||||
|
|
@ -35,9 +41,21 @@ internal interface StakingClickIntents : AmountScreenClickIntents {
|
|||
|
||||
fun onApprovalClick()
|
||||
|
||||
fun onAmountReduceByClick(
|
||||
reduceAmountBy: BigDecimal,
|
||||
reduceAmountByDiff: BigDecimal,
|
||||
notification: Class<out NotificationUM>,
|
||||
)
|
||||
|
||||
fun onAmountReduceToClick(reduceAmountTo: BigDecimal, notification: Class<out NotificationUM>)
|
||||
|
||||
fun onNotificationCancel(notification: Class<out NotificationUM>)
|
||||
|
||||
fun onExploreClick()
|
||||
|
||||
fun onShareClick()
|
||||
|
||||
fun onFailedTxEmailClick(errorMessage: String)
|
||||
|
||||
fun openTokenDetails(cryptoCurrency: CryptoCurrency)
|
||||
}
|
||||
|
|
@ -12,7 +12,9 @@ import com.tangem.blockchain.common.transaction.Fee
|
|||
import com.tangem.blockchain.common.transaction.TransactionFee
|
||||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.common.routing.bundle.unbundle
|
||||
import com.tangem.common.ui.amountScreen.converters.AmountReduceByTransformer
|
||||
import com.tangem.common.ui.amountScreen.models.AmountState
|
||||
import com.tangem.common.ui.notifications.NotificationUM
|
||||
import com.tangem.core.ui.clipboard.ClipboardManager
|
||||
import com.tangem.core.ui.haptic.TangemHapticEffect
|
||||
import com.tangem.core.ui.haptic.VibratorHapticManager
|
||||
|
|
@ -32,16 +34,15 @@ import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType
|
|||
import com.tangem.domain.staking.model.stakekit.transaction.ActionParams
|
||||
import com.tangem.domain.staking.model.stakekit.transaction.StakingTransaction
|
||||
import com.tangem.domain.staking.model.stakekit.transaction.StakingTransactionType
|
||||
import com.tangem.domain.tokens.FetchPendingTransactionsUseCase
|
||||
import com.tangem.domain.tokens.GetCryptoCurrencyStatusSyncUseCase
|
||||
import com.tangem.domain.tokens.GetFeePaidCryptoCurrencyStatusSyncUseCase
|
||||
import com.tangem.domain.tokens.UpdateDelayedNetworkStatusUseCase
|
||||
import com.tangem.domain.tokens.*
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.domain.transaction.error.GetFeeError
|
||||
import com.tangem.domain.transaction.usecase.*
|
||||
import com.tangem.domain.txhistory.usecase.GetExplorerTransactionUrlUseCase
|
||||
import com.tangem.domain.txhistory.usecase.GetTxHistoryItemsCountUseCase
|
||||
import com.tangem.domain.txhistory.usecase.GetTxHistoryItemsUseCase
|
||||
import com.tangem.domain.utils.convertToSdkAmount
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
|
||||
|
|
@ -50,10 +51,7 @@ import com.tangem.features.staking.impl.presentation.state.*
|
|||
import com.tangem.features.staking.impl.presentation.state.bottomsheet.InfoType
|
||||
import com.tangem.features.staking.impl.presentation.state.events.StakingEventFactory
|
||||
import com.tangem.features.staking.impl.presentation.state.transformers.*
|
||||
import com.tangem.features.staking.impl.presentation.state.transformers.amount.AmountChangeStateTransformer
|
||||
import com.tangem.features.staking.impl.presentation.state.transformers.amount.AmountCurrencyChangeStateTransformer
|
||||
import com.tangem.features.staking.impl.presentation.state.transformers.amount.AmountMaxValueStateTransformer
|
||||
import com.tangem.features.staking.impl.presentation.state.transformers.amount.AmountPasteDismissStateTransformer
|
||||
import com.tangem.features.staking.impl.presentation.state.transformers.amount.*
|
||||
import com.tangem.features.staking.impl.presentation.state.transformers.approval.SetApprovalBottomSheetInProgressTransformer
|
||||
import com.tangem.features.staking.impl.presentation.state.transformers.approval.SetApprovalInProgressTransformer
|
||||
import com.tangem.features.staking.impl.presentation.state.transformers.approval.SetConfirmationStateAssentApprovalTransformer
|
||||
|
|
@ -103,6 +101,10 @@ internal class StakingViewModel @Inject constructor(
|
|||
private val feedbackManager: FeedbackManager,
|
||||
private val getCardInfoUseCase: GetCardInfoUseCase,
|
||||
private val saveBlockchainErrorUseCase: SaveBlockchainErrorUseCase,
|
||||
private val getBalanceNotEnoughForFeeWarningUseCase: GetBalanceNotEnoughForFeeWarningUseCase,
|
||||
private val validateTransactionUseCase: ValidateTransactionUseCase,
|
||||
private val getCurrencyCheckUseCase: GetCurrencyCheckUseCase,
|
||||
private val isAmountSubtractAvailableUseCase: IsAmountSubtractAvailableUseCase,
|
||||
@DelayedWork private val coroutineScope: CoroutineScope,
|
||||
savedStateHandle: SavedStateHandle,
|
||||
) : ViewModel(), DefaultLifecycleObserver, StakingClickIntents {
|
||||
|
|
@ -140,6 +142,7 @@ internal class StakingViewModel @Inject constructor(
|
|||
)
|
||||
|
||||
private var stakingApproval: StakingApproval = StakingApproval.Empty
|
||||
private var isAmountSubtractAvailable: Boolean = false
|
||||
private val allowanceTaskScheduler = SingleTaskScheduler<BigDecimal>()
|
||||
|
||||
private val transactionsInProgress: CopyOnWriteArrayList<StakingTransaction> = CopyOnWriteArrayList()
|
||||
|
|
@ -171,6 +174,57 @@ internal class StakingViewModel @Inject constructor(
|
|||
stakingStateRouter.onNextClick()
|
||||
}
|
||||
|
||||
override fun getFee(pendingAction: PendingAction?) {
|
||||
viewModelScope.launch {
|
||||
stateController.update(
|
||||
SetConfirmationStateLoadingTransformer(
|
||||
yield = yield,
|
||||
),
|
||||
)
|
||||
val cryptoCurrencyValue = cryptoCurrencyStatus.value
|
||||
val confirmationState = value.confirmationState as? StakingStates.ConfirmationState.Data
|
||||
?: error("No confirmation state")
|
||||
val validatorState = confirmationState.validatorState as? ValidatorState.Content
|
||||
?: error("No validator provided")
|
||||
|
||||
val amount = (value.amountState as? AmountState.Data)?.amountTextField?.cryptoAmount?.value
|
||||
?: error("No amount provided")
|
||||
val sourceAddress = cryptoCurrencyValue.networkAddress?.defaultAddress?.value
|
||||
?: error("No available address")
|
||||
val validatorAddress = validatorState.chosenValidator.address
|
||||
|
||||
val approval = stakingApproval as? StakingApproval.Needed
|
||||
if (approval != null) {
|
||||
val allowance = getAllowanceUseCase(
|
||||
userWalletId = userWalletId,
|
||||
cryptoCurrency = cryptoCurrencyStatus.currency,
|
||||
spenderAddress = approval.spenderAddress,
|
||||
).getOrElse { BigDecimal.ZERO }
|
||||
|
||||
if (allowance < amount) {
|
||||
getApproveFee(
|
||||
amount = amount,
|
||||
validatorAddress = validatorAddress,
|
||||
)
|
||||
} else {
|
||||
estimateGas(
|
||||
pendingAction = pendingAction,
|
||||
amount = amount,
|
||||
sourceAddress = sourceAddress,
|
||||
validatorAddress = validatorAddress,
|
||||
)
|
||||
}
|
||||
} else {
|
||||
estimateGas(
|
||||
pendingAction = pendingAction,
|
||||
amount = amount,
|
||||
sourceAddress = sourceAddress,
|
||||
validatorAddress = validatorAddress,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleOnNextConfirmationClick() {
|
||||
if (isAssentState()) {
|
||||
viewModelScope.launch {
|
||||
|
|
@ -236,57 +290,6 @@ internal class StakingViewModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun getFee(pendingAction: PendingAction?) {
|
||||
viewModelScope.launch {
|
||||
stateController.update(
|
||||
SetConfirmationStateLoadingTransformer(
|
||||
yield = yield,
|
||||
),
|
||||
)
|
||||
val cryptoCurrencyValue = cryptoCurrencyStatus.value
|
||||
val confirmationState = value.confirmationState as? StakingStates.ConfirmationState.Data
|
||||
?: error("No confirmation state")
|
||||
val validatorState = confirmationState.validatorState as? ValidatorState.Content
|
||||
?: error("No validator provided")
|
||||
|
||||
val amount = (value.amountState as? AmountState.Data)?.amountTextField?.cryptoAmount?.value
|
||||
?: error("No amount provided")
|
||||
val sourceAddress = cryptoCurrencyValue.networkAddress?.defaultAddress?.value
|
||||
?: error("No available address")
|
||||
val validatorAddress = validatorState.chosenValidator.address
|
||||
|
||||
val approval = stakingApproval as? StakingApproval.Needed
|
||||
if (approval != null) {
|
||||
val allowance = getAllowanceUseCase(
|
||||
userWalletId = userWalletId,
|
||||
cryptoCurrency = cryptoCurrencyStatus.currency,
|
||||
spenderAddress = approval.spenderAddress,
|
||||
).getOrElse { BigDecimal.ZERO }
|
||||
|
||||
if (allowance < amount) {
|
||||
getApproveFee(
|
||||
amount = amount,
|
||||
validatorAddress = validatorAddress,
|
||||
)
|
||||
} else {
|
||||
estimateGas(
|
||||
pendingAction = pendingAction,
|
||||
amount = amount,
|
||||
sourceAddress = sourceAddress,
|
||||
validatorAddress = validatorAddress,
|
||||
)
|
||||
}
|
||||
} else {
|
||||
estimateGas(
|
||||
pendingAction = pendingAction,
|
||||
amount = amount,
|
||||
sourceAddress = sourceAddress,
|
||||
validatorAddress = validatorAddress,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun estimateGas(
|
||||
pendingAction: PendingAction?,
|
||||
amount: BigDecimal,
|
||||
|
|
@ -310,7 +313,6 @@ internal class StakingViewModel @Inject constructor(
|
|||
stateController.update(AddStakingErrorTransformer(it))
|
||||
return
|
||||
}
|
||||
|
||||
stateController.update(
|
||||
SetConfirmationStateAssentTransformer(
|
||||
appCurrencyProvider = Provider { appCurrency },
|
||||
|
|
@ -325,24 +327,30 @@ internal class StakingViewModel @Inject constructor(
|
|||
action = pendingAction,
|
||||
),
|
||||
)
|
||||
updateNotifications()
|
||||
}
|
||||
|
||||
private suspend fun getApproveFee(amount: BigDecimal, validatorAddress: String) {
|
||||
val approvalFee = getFeeUseCase(
|
||||
getFeeUseCase(
|
||||
amount = amount,
|
||||
destination = validatorAddress,
|
||||
userWallet = userWallet,
|
||||
cryptoCurrency = cryptoCurrencyStatus.currency,
|
||||
).getOrElse {
|
||||
return stakingEventFactory.createGenericErrorAlert(it.toString())
|
||||
}
|
||||
|
||||
stateController.update(
|
||||
SetConfirmationStateAssentApprovalTransformer(
|
||||
appCurrencyProvider = Provider { appCurrency },
|
||||
feeCryptoCurrencyStatus = feeCryptoCurrencyStatus,
|
||||
fee = approvalFee,
|
||||
),
|
||||
).fold(
|
||||
ifRight = { fee ->
|
||||
stateController.update(
|
||||
SetConfirmationStateAssentApprovalTransformer(
|
||||
appCurrencyProvider = Provider { appCurrency },
|
||||
feeCryptoCurrencyStatus = feeCryptoCurrencyStatus,
|
||||
fee = fee,
|
||||
),
|
||||
)
|
||||
updateNotifications()
|
||||
},
|
||||
ifLeft = {
|
||||
stateController.update(AddStakingErrorTransformer())
|
||||
updateNotifications(it)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -499,6 +507,85 @@ internal class StakingViewModel @Inject constructor(
|
|||
}.saveIn(approvalJobHolder)
|
||||
}
|
||||
|
||||
private fun updateNotifications(feeError: GetFeeError? = null) {
|
||||
viewModelScope.launch {
|
||||
val confirmationState = value.confirmationState as? StakingStates.ConfirmationState.Data
|
||||
val feeState = confirmationState?.feeState as? FeeState.Content
|
||||
val amountState = value.amountState as? AmountState.Data
|
||||
|
||||
val amount = amountState?.amountTextField?.cryptoAmount?.value
|
||||
val fee = feeState?.fee?.amount?.value
|
||||
val currencyWarning = if (feeCryptoCurrencyStatus != null && fee != null) {
|
||||
getBalanceNotEnoughForFeeWarningUseCase(
|
||||
fee = fee,
|
||||
userWalletId = userWalletId,
|
||||
tokenStatus = cryptoCurrencyStatus,
|
||||
coinStatus = feeCryptoCurrencyStatus ?: cryptoCurrencyStatus,
|
||||
).getOrNull()
|
||||
} else {
|
||||
null
|
||||
}
|
||||
val validation = amount?.let {
|
||||
validateTransactionUseCase(
|
||||
userWalletId = userWalletId,
|
||||
amount = amount.convertToSdkAmount(cryptoCurrencyStatus.currency),
|
||||
fee = feeState?.fee,
|
||||
memo = null,
|
||||
destination = "",
|
||||
network = cryptoCurrencyStatus.currency.network,
|
||||
).leftOrNull()
|
||||
}
|
||||
|
||||
val currencyStatus = getCurrencyCheckUseCase(
|
||||
userWalletId = userWalletId,
|
||||
currencyStatus = cryptoCurrencyStatus,
|
||||
amount = amount,
|
||||
fee = fee,
|
||||
)
|
||||
stateController.update(
|
||||
AddStakingNotificationsTransformer(
|
||||
cryptoCurrencyStatusProvider = Provider { cryptoCurrencyStatus },
|
||||
appCurrencyProvider = Provider { appCurrency },
|
||||
feeCryptoCurrencyStatus = feeCryptoCurrencyStatus,
|
||||
currencyWarning = currencyWarning,
|
||||
validatorError = validation,
|
||||
currencyCheck = currencyStatus,
|
||||
isSubtractAvailable = isAmountSubtractAvailable,
|
||||
feeError = feeError,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onAmountReduceByClick(
|
||||
reduceAmountBy: BigDecimal,
|
||||
reduceAmountByDiff: BigDecimal,
|
||||
notification: Class<out NotificationUM>,
|
||||
) {
|
||||
AmountReduceByStateTransformer(
|
||||
cryptoCurrencyStatus = cryptoCurrencyStatus,
|
||||
value = AmountReduceByTransformer.ReduceByData(
|
||||
reduceAmountBy = reduceAmountBy,
|
||||
reduceAmountByDiff = reduceAmountByDiff,
|
||||
),
|
||||
)
|
||||
onNotificationCancel(notification)
|
||||
}
|
||||
|
||||
override fun onAmountReduceToClick(reduceAmountTo: BigDecimal, notification: Class<out NotificationUM>) {
|
||||
stateController.update(
|
||||
AmountReduceToStateTransformer(
|
||||
cryptoCurrencyStatus = cryptoCurrencyStatus,
|
||||
value = reduceAmountTo,
|
||||
),
|
||||
)
|
||||
onNotificationCancel(notification)
|
||||
}
|
||||
|
||||
override fun onNotificationCancel(notification: Class<out NotificationUM>) {
|
||||
stateController.update(DismissStakingNotificationsStateTransformer(notification))
|
||||
}
|
||||
|
||||
private fun awaitForAllowance(pendingAction: PendingAction?) {
|
||||
val approval = stakingApproval as? StakingApproval.Needed ?: return
|
||||
allowanceTaskScheduler.scheduleTask(
|
||||
|
|
@ -586,6 +673,10 @@ internal class StakingViewModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
override fun openTokenDetails(cryptoCurrency: CryptoCurrency) {
|
||||
innerRouter.openTokenDetails(userWalletId, cryptoCurrency)
|
||||
}
|
||||
|
||||
fun setRouter(router: InnerStakingRouter, stateRouter: StakingStateRouter) {
|
||||
innerRouter = router
|
||||
this.stakingStateRouter = stateRouter
|
||||
|
|
@ -612,6 +703,7 @@ internal class StakingViewModel @Inject constructor(
|
|||
cryptoCurrencyStatus = it
|
||||
|
||||
setupApprovalNeeded()
|
||||
checkIfSubtractAvailable()
|
||||
|
||||
stateController.update(
|
||||
transformer = SetInitialDataStateTransformer(
|
||||
|
|
@ -779,6 +871,11 @@ internal class StakingViewModel @Inject constructor(
|
|||
InnerConfirmationStakingState.ASSENT
|
||||
}
|
||||
|
||||
private suspend fun checkIfSubtractAvailable() {
|
||||
isAmountSubtractAvailable = isAmountSubtractAvailableUseCase(userWalletId, cryptoCurrencyStatus.currency)
|
||||
.getOrElse { false }
|
||||
}
|
||||
|
||||
private data class FullTransactionData(
|
||||
val stakeKitTransaction: StakingTransaction,
|
||||
val tangemTransaction: TransactionData.Compiled,
|
||||
|
|
|
|||
|
|
@ -56,6 +56,11 @@ object BlockchainUtils {
|
|||
return Blockchain.fromNetworkId(networkId)?.isSupportedInApp() ?: false
|
||||
}
|
||||
|
||||
fun isArbitrum(networkId: String): Boolean {
|
||||
val blockchain = Blockchain.fromId(networkId)
|
||||
return blockchain == Blockchain.Arbitrum
|
||||
}
|
||||
|
||||
data class BlockchainInfo(
|
||||
val blockchainId: String,
|
||||
val name: String,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue