Updated on 2026-08-14

This commit is contained in:
Tangem 2024-09-25 19:52:49 +05:00
parent 5d8461a4f2
commit bc707a571e
15 changed files with 248 additions and 796 deletions

View file

@ -21,7 +21,7 @@ import java.math.BigDecimal
object NotificationsFactory {
fun MutableList<NotificationUM>.addFeeUnreachableNotification(
feeError: GetFeeError,
feeError: GetFeeError?,
tokenName: String,
onReload: () -> Unit,
) {

View file

@ -1,19 +0,0 @@
package com.tangem.features.send.impl.presentation.errors
import com.tangem.domain.transaction.error.GetFeeError
import com.tangem.features.send.impl.presentation.state.fee.FeeSelectorState
internal class FeeErrorStateMapper {
fun getFeeError(loadFeeError: GetFeeError?, tokenName: String): FeeSelectorState.Error {
return when (loadFeeError) {
GetFeeError.BlockchainErrors.TronActivationError -> FeeSelectorState.Error.TronAccountActivationError(
tokenName,
)
is GetFeeError.DataError,
GetFeeError.UnknownError,
null,
-> FeeSelectorState.Error.NetworkError
}
}
}

View file

@ -1,250 +0,0 @@
package com.tangem.features.send.impl.presentation.state
import com.tangem.blockchain.common.Blockchain
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 com.tangem.features.send.impl.R
import java.math.BigDecimal
internal sealed class SendNotification(val config: NotificationConfig) {
sealed class Error(
title: TextReference,
subtitle: TextReference,
iconResId: Int = R.drawable.ic_alert_24,
buttonState: NotificationConfig.ButtonsState? = null,
onCloseClick: (() -> Unit)? = null,
) : SendNotification(
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.common_ok),
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),
)
}
sealed class Warning(
title: TextReference,
subtitle: TextReference,
buttonsState: NotificationConfig.ButtonsState? = null,
onCloseClick: (() -> Unit)? = null,
) : SendNotification(
config = NotificationConfig(
title = title,
subtitle = subtitle,
iconResId = R.drawable.img_attention_20,
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,
),
)
}
}

View file

@ -3,6 +3,7 @@ package com.tangem.features.send.impl.presentation.state
import com.tangem.blockchain.common.TransactionData
import com.tangem.common.ui.amountScreen.converters.AmountStateConverter
import com.tangem.common.ui.amountScreen.models.AmountState
import com.tangem.common.ui.notifications.NotificationUM
import com.tangem.core.ui.components.currency.icon.converter.CryptoCurrencyToIconStateConverter
import com.tangem.core.ui.event.consumedEvent
import com.tangem.domain.appcurrency.model.AppCurrency
@ -164,14 +165,14 @@ internal class SendStateFactory(
)
}
fun getSendNotificationState(notifications: ImmutableList<SendNotification>): SendUiState {
fun getSendNotificationState(notifications: ImmutableList<NotificationUM>): SendUiState {
val state = currentStateProvider()
val sendState = state.sendState ?: return state
val reducedBy = sendState.reduceAmountBy.takeIf {
notifications.none {
it is SendNotification.Error.ExistentialDeposit ||
it is SendNotification.Error.TransactionLimitError ||
it is SendNotification.Warning.HighFeeError
it is NotificationUM.Error.ExistentialDeposit ||
it is NotificationUM.Error.TransactionLimitError ||
it is NotificationUM.Warning.HighFeeError
}
}
return state.copy(
@ -199,10 +200,10 @@ internal class SendStateFactory(
private fun isPrimaryButtonEnabled(
state: SendUiState,
isSending: Boolean,
notifications: ImmutableList<SendNotification>,
notifications: ImmutableList<NotificationUM>,
): Boolean {
val feeState = state.getFeeState(stateRouterProvider().isEditState) ?: return false
val hasErrorNotifications = notifications.any { it is SendNotification.Error }
val hasErrorNotifications = notifications.any { it is NotificationUM.Error }
return !hasErrorNotifications && !isSending && feeState.feeSelectorState is FeeSelectorState.Content
}
//endregion

View file

@ -4,6 +4,7 @@ import androidx.compose.runtime.Immutable
import androidx.compose.runtime.Stable
import com.tangem.blockchain.common.transaction.Fee
import com.tangem.common.ui.amountScreen.models.AmountState
import com.tangem.common.ui.notifications.NotificationUM
import com.tangem.core.ui.event.StateEvent
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.features.send.impl.presentation.domain.SendRecipientListContent
@ -112,7 +113,7 @@ internal sealed class SendStates {
val appCurrency: AppCurrency,
val isFeeApproximate: Boolean,
val isCustomSelected: Boolean,
val notifications: ImmutableList<SendNotification>,
val notifications: ImmutableList<NotificationUM>,
val isTronToken: Boolean,
) : SendStates()
@ -129,7 +130,7 @@ internal sealed class SendStates {
val reduceAmountBy: BigDecimal?,
val isFromConfirmation: Boolean,
val showTapHelp: Boolean,
val notifications: ImmutableList<SendNotification>,
val notifications: ImmutableList<NotificationUM>,
) : SendStates()
}

View file

@ -1,31 +1,35 @@
package com.tangem.features.send.impl.presentation.state.confirm
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.BlockchainSdkError
import com.tangem.blockchain.common.transaction.Fee
import com.tangem.blockchain.common.transaction.TransactionFee
import com.tangem.blockchainsdk.utils.fromNetworkId
import com.tangem.blockchainsdk.utils.minimalAmount
import com.tangem.common.ui.amountScreen.models.AmountFieldModel
import com.tangem.common.ui.amountScreen.models.AmountState
import com.tangem.common.ui.amountScreen.utils.getFiatString
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.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.ui.extensions.networkIconResId
import com.tangem.core.ui.utils.BigDecimalFormatter
import com.tangem.core.ui.utils.parseBigDecimal
import com.tangem.core.ui.utils.parseToBigDecimal
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.tokens.GetBalanceNotEnoughForFeeWarningUseCase
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.GetCurrencyCheckUseCase
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.tokens.model.warnings.CryptoCurrencyWarning
import com.tangem.domain.tokens.repository.CurrencyChecksRepository
import com.tangem.domain.tokens.model.warnings.CryptoCurrencyCheck
import com.tangem.domain.transaction.error.GetFeeError
import com.tangem.domain.transaction.usecase.ValidateTransactionUseCase
import com.tangem.domain.utils.convertToSdkAmount
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.features.send.impl.R
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.features.send.impl.presentation.analytics.SendAnalyticEvents
import com.tangem.features.send.impl.presentation.state.*
import com.tangem.features.send.impl.presentation.state.SendStates
import com.tangem.features.send.impl.presentation.state.SendUiState
import com.tangem.features.send.impl.presentation.state.SendUiStateType
import com.tangem.features.send.impl.presentation.state.StateRouter
import com.tangem.features.send.impl.presentation.state.fee.*
import com.tangem.features.send.impl.presentation.viewmodel.SendClickIntents
import com.tangem.lib.crypto.BlockchainUtils
@ -41,32 +45,33 @@ import kotlinx.coroutines.flow.map
import java.math.BigDecimal
@Suppress("LongParameterList", "LargeClass")
internal class SendNotificationFactory(
internal class SendNotificationFactory constructor(
private val analyticsEventHandler: AnalyticsEventHandler,
private val validateTransactionUseCase: ValidateTransactionUseCase,
private val getCurrencyCheckUseCase: GetCurrencyCheckUseCase,
private val getBalanceNotEnoughForFeeWarningUseCase: GetBalanceNotEnoughForFeeWarningUseCase,
private val cryptoCurrencyStatusProvider: Provider<CryptoCurrencyStatus>,
private val feeCryptoCurrencyStatusProvider: Provider<CryptoCurrencyStatus?>,
private val currentStateProvider: Provider<SendUiState>,
private val userWalletProvider: Provider<UserWallet>,
private val currencyChecksRepository: CurrencyChecksRepository,
private val stateRouterProvider: Provider<StateRouter>,
private val isSubtractAvailableProvider: Provider<Boolean>,
private val appCurrencyProvider: Provider<AppCurrency>,
private val clickIntents: SendClickIntents,
private val analyticsEventHandler: AnalyticsEventHandler,
private val getBalanceNotEnoughForFeeWarningUseCase: GetBalanceNotEnoughForFeeWarningUseCase,
private val validateTransactionUseCase: ValidateTransactionUseCase,
private val userWalletId: UserWalletId,
) {
fun create(): Flow<ImmutableList<SendNotification>> = stateRouterProvider().currentState
fun create(): Flow<ImmutableList<NotificationUM>> = stateRouterProvider().currentState
.filter { it.type == SendUiStateType.Send }
.map {
val cryptoCurrencyStatus = cryptoCurrencyStatusProvider()
val state = currentStateProvider()
val isEditState = stateRouterProvider().isEditState
val balance = cryptoCurrencyStatusProvider().value.amount.orZero()
val balance = cryptoCurrencyStatus.value.amount.orZero()
val sendState = state.sendState ?: return@map persistentListOf()
val feeState = state.getFeeState(isEditState) ?: return@map persistentListOf()
val amountState = state.getAmountState(isEditState) as? AmountState.Data ?: return@map persistentListOf()
val recipientAddress = state.recipientState?.addressTextField?.value.orEmpty()
val amountValue = amountState.amountTextField.cryptoAmount.value.orZero()
val feeValue = feeState.fee?.amount?.value.orZero()
val reduceAmountBy = sendState.reduceAmountBy.orZero()
@ -78,42 +83,38 @@ internal class SendNotificationFactory(
reduceAmountBy = reduceAmountBy,
)
val sendingAmount = checkAndCalculateSubtractedAmount(
isAmountSubtractAvailable = isFeeCoverage,
cryptoCurrencyStatus = cryptoCurrencyStatusProvider(),
isAmountSubtractAvailable = isSubtractAvailableProvider(),
cryptoCurrencyStatus = cryptoCurrencyStatus,
amountValue = amountValue,
feeValue = feeValue,
reduceAmountBy = reduceAmountBy,
)
val feeError = (feeState.feeSelectorState as? FeeSelectorState.Error)?.error
val currencyCheck = getCurrencyCheckUseCase(
userWalletId = userWalletId,
currencyStatus = cryptoCurrencyStatus,
amount = amountValue,
fee = feeValue,
)
buildList {
// errors
addFeeUnreachableNotification(feeState.feeSelectorState)
addExceedBalanceNotification(feeValue, sendingAmount)
addExceedsBalanceNotification(feeState.fee)
addDustWarningNotificationForSpecificBlockchains(feeValue, sendingAmount)
addTransactionLimitErrorNotification(feeValue, sendingAmount)
addReserveAmountErrorNotification(recipientAddress, sendingAmount)
// warnings
addExistentialWarningNotification(feeValue, amountValue)
addFeeCoverageNotification(
isFeeCoverage = isFeeCoverage,
amountField = amountState.amountTextField,
sendingValue = sendingAmount,
)
addHighFeeWarningNotification(amountValue, sendState.ignoreAmountReduce)
addTooHighNotification(feeState.feeSelectorState)
addTooLowNotification(feeState)
// blockchain specific
addValidateTransactionNotifications(
addErrorNotifications(
feeError = feeError,
sendingAmount = sendingAmount,
fee = feeState.fee,
state = state,
feeValue = feeValue,
currencyCheck = currencyCheck,
)
addWarningNotifications(
amountState = amountState,
feeState = feeState,
sendState = sendState,
sendingAmount = sendingAmount,
isFeeCoverage = isFeeCoverage,
currencyCheck = currencyCheck,
)
}.toImmutableList()
}
fun dismissNotificationState(clazz: Class<out SendNotification>, isIgnored: Boolean = false): SendUiState {
fun dismissNotificationState(clazz: Class<out NotificationUM>, isIgnored: Boolean = false): SendUiState {
val state = currentStateProvider()
val sendState = state.sendState ?: return state
val notificationsToRemove = sendState.notifications.filterIsInstance(clazz)
@ -128,161 +129,120 @@ internal class SendNotificationFactory(
)
}
private fun MutableList<SendNotification>.addFeeUnreachableNotification(feeSelectorState: FeeSelectorState) {
when (feeSelectorState) {
is FeeSelectorState.Error.TronAccountActivationError -> add(
SendNotification.Warning.TronAccountNotActivated(
feeSelectorState.tokenName,
),
)
is FeeSelectorState.Error.NetworkError -> add(
SendNotification.Warning.NetworkFeeUnreachable(clickIntents::feeReload),
)
else -> {
/* do nothing */
}
}
}
private fun MutableList<SendNotification>.addExceedBalanceNotification(
feeAmount: BigDecimal,
receivedAmount: BigDecimal,
) {
val cryptoCurrencyStatus = cryptoCurrencyStatusProvider()
val balance = cryptoCurrencyStatus.value.amount ?: BigDecimal.ZERO
if (!isSubtractAvailableProvider()) return
val showNotification = receivedAmount + feeAmount > balance
if (showNotification) {
add(SendNotification.Error.TotalExceedsBalance)
}
}
private suspend fun MutableList<SendNotification>.addReserveAmountErrorNotification(
recipientAddress: String,
private suspend fun MutableList<NotificationUM>.addErrorNotifications(
feeError: GetFeeError?,
sendingAmount: BigDecimal,
feeValue: BigDecimal,
currencyCheck: CryptoCurrencyCheck,
) {
val userWalletId = userWalletProvider().walletId
val cryptoCurrency = cryptoCurrencyStatusProvider().currency
val isAccountFunded = currencyChecksRepository.checkIfAccountFunded(
userWalletId,
cryptoCurrency.network,
recipientAddress,
)
val minimumAmount = currencyChecksRepository.getReserveAmount(userWalletId, cryptoCurrency.network)
if (!isAccountFunded && minimumAmount != null && minimumAmount > sendingAmount) {
add(
SendNotification.Error.ReserveAmount(
BigDecimalFormatter.formatCryptoAmount(
cryptoAmount = minimumAmount,
cryptoCurrency = cryptoCurrency,
),
),
)
}
}
private suspend fun MutableList<SendNotification>.addTransactionLimitErrorNotification(
feeAmount: BigDecimal,
receivedAmount: BigDecimal,
) {
val userWalletId = userWalletProvider().walletId
val cryptoCurrency = cryptoCurrencyStatusProvider().currency
val utxoLimit = currencyChecksRepository.checkUtxoAmountLimit(
userWalletId = userWalletId,
network = cryptoCurrency.network,
amount = receivedAmount,
fee = feeAmount,
)
if (utxoLimit != null) {
add(
SendNotification.Error.TransactionLimitError(
cryptoCurrency = cryptoCurrency.name,
utxoLimit = utxoLimit.maxLimit.toPlainString(),
amountLimit = BigDecimalFormatter.formatCryptoAmount(
cryptoAmount = utxoLimit.maxAmount,
cryptoCurrency = cryptoCurrency,
),
onConfirmClick = {
clickIntents.onAmountReduceClick(
reduceAmountTo = utxoLimit.maxAmount,
clazz = SendNotification.Error.TransactionLimitError::class.java,
)
},
),
)
}
}
private suspend fun MutableList<SendNotification>.addExistentialWarningNotification(
feeAmount: BigDecimal,
receivedAmount: BigDecimal,
) {
val userWalletId = userWalletProvider().walletId
val cryptoCurrencyStatus = cryptoCurrencyStatusProvider()
val cryptoCurrency = cryptoCurrencyStatus.currency
val balance = cryptoCurrencyStatus.value.amount ?: return
val spendingAmount = if (cryptoCurrency is CryptoCurrency.Token) {
feeAmount
} else {
receivedAmount
}
val currencyDeposit = currencyChecksRepository.getExistentialDeposit(
userWalletId,
cryptoCurrency.network,
val currency = cryptoCurrencyStatusProvider().currency
val currencyWarning = getBalanceNotEnoughForFeeWarningUseCase(
fee = feeValue,
userWalletId = userWalletId,
tokenStatus = cryptoCurrencyStatus,
coinStatus = feeCryptoCurrencyStatusProvider() ?: cryptoCurrencyStatus,
).getOrNull()
addFeeUnreachableNotification(
feeError = feeError,
tokenName = currency.name,
onReload = clickIntents::feeReload,
)
val diff = balance.minus(spendingAmount)
if (currencyDeposit != null && diff >= BigDecimal.ZERO && currencyDeposit > diff) {
add(
SendNotification.Error.ExistentialDeposit(
deposit = BigDecimalFormatter.formatCryptoAmountUncapped(
cryptoAmount = currencyDeposit,
cryptoCurrency = cryptoCurrency,
addExceedBalanceNotification(
feeAmount = feeValue,
sendingAmount = sendingAmount,
isSubtractionAvailable = isSubtractAvailableProvider(),
cryptoCurrencyStatus = cryptoCurrencyStatus,
)
addExceedsBalanceNotification(
cryptoCurrencyWarning = currencyWarning,
cryptoCurrencyStatus = cryptoCurrencyStatus,
shouldMergeFeeNetworkName = BlockchainUtils.isArbitrum(currency.network.backendId),
onClick = clickIntents::onTokenDetailsClick,
onAnalyticsEvent = {
analyticsEventHandler.send(
SendAnalyticEvents.NoticeNotEnoughFee(
token = cryptoCurrencyStatus.currency.symbol,
blockchain = cryptoCurrencyStatus.currency.network.name,
),
onConfirmClick = {
clickIntents.onAmountReduceClick(
reduceAmountByDiff = currencyDeposit.minus(diff),
reduceAmountBy = currencyDeposit,
clazz = SendNotification.Error.ExistentialDeposit::class.java,
)
},
),
)
},
)
if (!BlockchainUtils.isCardano(currency.id.value)) {
addDustWarningNotification(
dustValue = currencyCheck.dustValue,
feeValue = feeValue,
sendingAmount = sendingAmount,
cryptoCurrencyStatus = cryptoCurrencyStatus,
feeCurrencyStatus = feeCryptoCurrencyStatusProvider(),
)
}
addTransactionLimitErrorNotification(
utxoLimit = currencyCheck.utxoAmountLimit,
cryptoCurrency = currency,
onReduceClick = clickIntents::onAmountReduceToClick,
)
addReserveAmountErrorNotification(
reserveAmount = currencyCheck.reserveAmount,
sendingAmount = sendingAmount,
cryptoCurrency = currency,
isAccountFunded = false,
)
}
private fun MutableList<SendNotification>.addFeeCoverageNotification(
private suspend fun MutableList<NotificationUM>.addWarningNotifications(
amountState: AmountState.Data,
feeState: SendStates.FeeState,
sendState: SendStates.SendState,
sendingAmount: BigDecimal,
isFeeCoverage: Boolean,
amountField: AmountFieldModel,
sendingValue: BigDecimal,
currencyCheck: CryptoCurrencyCheck,
) {
if (isFeeCoverage) {
analyticsEventHandler.send(SendAnalyticEvents.NoticeFeeCoverage)
val cryptoCurrencyStatus = cryptoCurrencyStatusProvider()
val cryptoCurrency = cryptoCurrencyStatus.currency
val fiatRate = cryptoCurrencyStatus.value.fiatRate
val amountValue = amountField.cryptoAmount.value ?: return
val cryptoDiff = amountValue.minus(sendingValue)
add(
SendNotification.Warning.FeeCoverageNotification(
cryptoAmount = BigDecimalFormatter.formatCryptoAmountUncapped(
cryptoAmount = cryptoDiff,
cryptoCurrency = cryptoCurrency,
),
fiatAmount = getFiatString(
value = cryptoDiff,
rate = fiatRate,
appCurrency = appCurrencyProvider(),
),
),
)
val cryptoCurrencyStatus = cryptoCurrencyStatusProvider()
val currency = cryptoCurrencyStatus.currency
val amountValue = amountState.amountTextField.cryptoAmount.value
val validationError = amountValue?.let {
validateTransactionUseCase(
userWalletId = userWalletId,
amount = amountValue.convertToSdkAmount(cryptoCurrencyStatus.currency),
fee = feeState.fee,
memo = null,
destination = "",
network = cryptoCurrencyStatus.currency.network,
).leftOrNull()
}
addExistentialWarningNotification(
existentialDeposit = currencyCheck.existentialDeposit,
feeAmount = feeState.fee?.amount?.value.orZero(),
receivedAmount = sendingAmount,
cryptoCurrencyStatus = cryptoCurrencyStatus,
onReduceClick = clickIntents::onAmountReduceByClick,
)
addFeeCoverageNotification(
isFeeCoverage = isFeeCoverage,
amountField = amountState.amountTextField,
sendingValue = sendingAmount,
appCurrency = appCurrencyProvider(),
cryptoCurrencyStatus = cryptoCurrencyStatus,
)
addValidateTransactionNotifications(
dustValue = currencyCheck.dustValue.orZero(),
fee = feeState.fee,
validationError = validationError,
cryptoCurrency = currency,
onReduceClick = clickIntents::onAmountReduceToClick,
)
addHighFeeWarningNotification(
amountState.amountTextField.cryptoAmount.value.orZero(),
sendState.ignoreAmountReduce,
)
addTooHighNotification(feeState.feeSelectorState)
addTooLowNotification(feeState)
}
private fun MutableList<SendNotification>.addHighFeeWarningNotification(
private fun MutableList<NotificationUM>.addHighFeeWarningNotification(
sendAmount: BigDecimal,
ignoreAmountReduce: Boolean,
) {
@ -293,61 +253,32 @@ internal class SendNotificationFactory(
val isTotalBalance = sendAmount >= balance && balance > threshold
if (!ignoreAmountReduce && isTotalBalance && isTezos) {
add(
SendNotification.Warning.HighFeeError(
NotificationUM.Warning.HighFeeError(
currencyName = cryptoCurrencyStatus.currency.name,
amount = threshold.toPlainString(),
onConfirmClick = {
clickIntents.onAmountReduceClick(
clickIntents.onAmountReduceByClick(
reduceAmountBy = threshold,
clazz = SendNotification.Warning.HighFeeError::class.java,
reduceAmountByDiff = threshold,
notification = NotificationUM.Warning.HighFeeError::class.java,
)
},
onCloseClick = {
clickIntents.onNotificationCancel(SendNotification.Warning.HighFeeError::class.java)
clickIntents.onNotificationCancel(NotificationUM.Warning.HighFeeError::class.java)
},
),
)
}
}
private suspend fun MutableList<SendNotification>.addDustWarningNotificationForSpecificBlockchains(
feeValue: BigDecimal,
sendingAmount: BigDecimal,
) {
val isCardano = BlockchainUtils.isCardano(cryptoCurrencyStatusProvider().currency.network.id.value)
if (!isCardano) {
addDustWarningNotification(feeValue, sendingAmount)
}
}
private fun checkDustLimits(feeAmount: BigDecimal, receivedAmount: BigDecimal, dustValue: BigDecimal): Boolean {
val cryptoCurrencyStatus = cryptoCurrencyStatusProvider()
val feeCurrencyStatus = feeCryptoCurrencyStatusProvider() ?: return false
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
}
private fun MutableList<SendNotification>.addTooLowNotification(feeState: SendStates.FeeState) {
private fun MutableList<NotificationUM>.addTooLowNotification(feeState: SendStates.FeeState) {
val feeSelectorState = feeState.feeSelectorState as? FeeSelectorState.Content ?: return
val multipleFees = feeSelectorState.fees as? TransactionFee.Choosable ?: return
val minimumValue = multipleFees.minimum.amount.value ?: return
val customAmount = feeSelectorState.customValues.firstOrNull() ?: return
val customValue = customAmount.value.parseToBigDecimal(customAmount.decimals)
if (feeSelectorState.selectedFee == FeeType.Custom && minimumValue > customValue) {
add(SendNotification.Warning.FeeTooLow)
add(NotificationUM.Warning.FeeTooLow)
analyticsEventHandler.send(
SendAnalyticEvents.NoticeTransactionDelays(
cryptoCurrencyStatusProvider().currency.symbol,
@ -356,206 +287,11 @@ internal class SendNotificationFactory(
}
}
private fun MutableList<SendNotification>.addTooHighNotification(feeSelectorState: FeeSelectorState) {
private fun MutableList<NotificationUM>.addTooHighNotification(feeSelectorState: FeeSelectorState) {
if (feeSelectorState !is FeeSelectorState.Content) return
checkIfFeeTooHigh(feeSelectorState) { diff ->
add(SendNotification.Warning.TooHigh(diff))
}
}
private suspend fun MutableList<SendNotification>.addExceedsBalanceNotification(fee: Fee?) {
val feeValue = fee?.amount?.value ?: BigDecimal.ZERO
val userWalletId = userWalletProvider().walletId
val cryptoCurrencyStatus = cryptoCurrencyStatusProvider()
val feeCurrencyStatus = feeCryptoCurrencyStatusProvider() ?: return
val warning = getBalanceNotEnoughForFeeWarningUseCase(
fee = feeValue,
userWalletId = userWalletId,
tokenStatus = cryptoCurrencyStatus,
coinStatus = feeCurrencyStatus,
).getOrNull() ?: return
val mergeFeeNetworkName = cryptoCurrencyStatus.shouldMergeFeeNetworkName()
when (warning) {
is CryptoCurrencyWarning.BalanceNotEnoughForFee -> {
add(
SendNotification.Error.ExceedsBalance(
networkIconId = warning.coinCurrency.networkIconResId,
networkName = warning.coinCurrency.name,
currencyName = cryptoCurrencyStatus.currency.name,
feeName = warning.coinCurrency.name,
feeSymbol = warning.coinCurrency.symbol,
mergeFeeNetworkName = mergeFeeNetworkName,
onClick = {
clickIntents.onTokenDetailsClick(
userWalletId = userWalletId,
currency = warning.coinCurrency,
)
},
),
)
analyticsEventHandler.send(
SendAnalyticEvents.NoticeNotEnoughFee(
token = cryptoCurrencyStatus.currency.symbol,
blockchain = cryptoCurrencyStatus.currency.network.name,
),
)
}
is CryptoCurrencyWarning.CustomTokenNotEnoughForFee -> {
val currency = warning.feeCurrency
add(
SendNotification.Error.ExceedsBalance(
networkIconId = currency?.networkIconResId ?: R.drawable.ic_alert_24,
currencyName = warning.currency.name,
feeName = warning.feeCurrencyName,
feeSymbol = warning.feeCurrencySymbol,
networkName = warning.networkName,
mergeFeeNetworkName = mergeFeeNetworkName,
onClick = currency?.let {
{
clickIntents.onTokenDetailsClick(
userWalletId,
currency,
)
}
},
),
)
analyticsEventHandler.send(
SendAnalyticEvents.NoticeNotEnoughFee(
token = warning.currency.symbol,
blockchain = warning.networkName,
),
)
}
else -> Unit
}
}
// workaround for networks that users have misunderstanding
private fun CryptoCurrencyStatus.shouldMergeFeeNetworkName(): Boolean {
return Blockchain.fromNetworkId(this.currency.network.backendId) == Blockchain.Arbitrum
}
private suspend fun MutableList<SendNotification>.addValidateTransactionNotifications(
sendingAmount: BigDecimal,
fee: Fee?,
state: SendUiState,
) {
val sendingCurrency = cryptoCurrencyStatusProvider().currency
validateTransactionUseCase(
amount = sendingAmount.convertToSdkAmount(sendingCurrency),
fee = fee ?: return,
memo = state.recipientState?.memoTextField?.value,
destination = requireNotNull(state.recipientState?.addressTextField?.value),
userWalletId = userWalletProvider().walletId,
network = sendingCurrency.network,
).fold(
ifLeft = {
when (it) {
is BlockchainSdkError.Cardano -> addCardanoTransactionValidationError(
error = it,
sendingCurrency = sendingCurrency,
)
is BlockchainSdkError.Koinos -> addKoinosTransactionValidationError(error = it)
else -> return
}
},
ifRight = {
(fee as? Fee.CardanoToken)?.let {
add(
SendNotification.Cardano.MinAdaValueCharged(
tokenName = sendingCurrency.name,
minAdaValue = it.minAdaValue.parseBigDecimal(sendingCurrency.decimals),
),
)
}
},
)
}
private suspend fun MutableList<SendNotification>.addCardanoTransactionValidationError(
error: BlockchainSdkError.Cardano,
sendingCurrency: CryptoCurrency,
) {
when (error) {
BlockchainSdkError.Cardano.InsufficientMinAdaBalanceToSendToken -> {
add(SendNotification.Cardano.InsufficientBalanceToTransferToken(sendingCurrency.name))
}
BlockchainSdkError.Cardano.InsufficientRemainingBalanceToWithdrawTokens -> {
when (sendingCurrency) {
is CryptoCurrency.Coin -> SendNotification.Cardano.InsufficientBalanceToTransferCoin
is CryptoCurrency.Token -> {
SendNotification.Cardano.InsufficientBalanceToTransferToken(sendingCurrency.name)
}
}.let(::add)
}
BlockchainSdkError.Cardano.InsufficientRemainingBalance,
BlockchainSdkError.Cardano.InsufficientSendingAdaAmount,
-> {
val dustValue = currencyChecksRepository.getDustValue(
userWalletId = userWalletProvider().walletId,
network = sendingCurrency.network,
) ?: return
add(
SendNotification.Error.MinimumAmountError(
amount = dustValue.parseBigDecimal(sendingCurrency.decimals),
),
)
}
}
}
private fun MutableList<SendNotification>.addKoinosTransactionValidationError(error: BlockchainSdkError.Koinos) {
when (error) {
is BlockchainSdkError.Koinos.InsufficientBalance -> {
add(SendNotification.Koinos.InsufficientBalance)
}
is BlockchainSdkError.Koinos.InsufficientMana -> {
add(
SendNotification.Koinos.InsufficientRecoverableMana(
mana = error.manaBalance ?: BigDecimal.ZERO,
maxMana = error.maxMana ?: BigDecimal.ZERO,
),
)
}
is BlockchainSdkError.Koinos.ManaFeeExceedsBalance -> {
add(
SendNotification.Koinos.ManaExceedsBalance(
availableKoinForTransfer = error.availableKoinForTransfer,
onReduceClick = {
clickIntents.onAmountReduceClick(
reduceAmountTo = error.availableKoinForTransfer,
clazz = SendNotification.Koinos.InsufficientRecoverableMana::class.java,
)
},
),
)
}
else -> {}
}
}
private suspend fun MutableList<SendNotification>.addDustWarningNotification(
feeValue: BigDecimal,
sendingAmount: BigDecimal,
) {
val cryptoCurrencyStatus = cryptoCurrencyStatusProvider()
val dustValue = currencyChecksRepository.getDustValue(
userWalletProvider().walletId,
cryptoCurrencyStatus.currency.network,
) ?: return
if (checkDustLimits(feeValue, sendingAmount, dustValue)) {
add(
SendNotification.Error.MinimumAmountError(
amount = dustValue.parseBigDecimal(cryptoCurrencyStatus.currency.decimals),
),
)
add(NotificationUM.Warning.TooHigh(diff))
}
}
}

View file

@ -1,6 +1,6 @@
package com.tangem.features.send.impl.presentation.state.fee
import com.tangem.features.send.impl.presentation.state.SendNotification
import com.tangem.common.ui.notifications.NotificationsFactory.addFeeUnreachableNotification
import com.tangem.features.send.impl.presentation.state.SendUiState
import com.tangem.features.send.impl.presentation.state.SendUiStateType
import com.tangem.features.send.impl.presentation.state.StateRouter
@ -25,24 +25,10 @@ internal class FeeNotificationFactory(
val feeState = state.getFeeState(stateRouterProvider().isEditState) ?: return@map persistentListOf()
buildList {
addFeeUnreachableNotification(
feeState.feeSelectorState,
feeError = (feeState.feeSelectorState as? FeeSelectorState.Error)?.error,
tokenName = state.cryptoCurrencyName,
onReload = clickIntents::feeReload,
)
}.toImmutableList()
}
private fun MutableList<SendNotification>.addFeeUnreachableNotification(feeSelectorState: FeeSelectorState) {
when (feeSelectorState) {
is FeeSelectorState.Error.TronAccountActivationError -> add(
SendNotification.Warning.TronAccountNotActivated(
feeSelectorState.tokenName,
),
)
is FeeSelectorState.Error.NetworkError -> add(
SendNotification.Warning.NetworkFeeUnreachable(clickIntents::feeReload),
)
else -> {
/* do nothing */
}
}
}
}

View file

@ -2,6 +2,7 @@ package com.tangem.features.send.impl.presentation.state.fee
import androidx.compose.runtime.Immutable
import com.tangem.blockchain.common.transaction.TransactionFee
import com.tangem.domain.transaction.error.GetFeeError
import com.tangem.features.send.impl.presentation.state.fields.SendTextField
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
@ -17,10 +18,9 @@ internal sealed class FeeSelectorState {
data object Loading : FeeSelectorState()
sealed class Error : FeeSelectorState() {
data object NetworkError : Error()
data class TronAccountActivationError(val tokenName: String) : Error()
}
data class Error(
val error: GetFeeError?,
) : FeeSelectorState()
}
enum class FeeType {

View file

@ -4,12 +4,13 @@ import com.tangem.blockchain.common.Token
import com.tangem.blockchain.common.transaction.TransactionFee
import com.tangem.common.extensions.isZero
import com.tangem.common.ui.amountScreen.models.AmountState
import com.tangem.common.ui.notifications.NotificationUM
import com.tangem.core.ui.utils.parseToBigDecimal
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.tokens.model.AmountType
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.transaction.error.GetFeeError
import com.tangem.domain.transaction.usecase.IsFeeApproximateUseCase
import com.tangem.features.send.impl.presentation.state.SendNotification
import com.tangem.features.send.impl.presentation.state.SendStates
import com.tangem.features.send.impl.presentation.state.SendUiState
import com.tangem.features.send.impl.presentation.state.StateRouter
@ -102,13 +103,13 @@ internal class FeeStateFactory(
)
}
fun onFeeOnErrorState(feeError: FeeSelectorState.Error): SendUiState {
fun onFeeOnErrorState(feeError: GetFeeError?): SendUiState {
val state = currentStateProvider()
val isEditState = stateRouterProvider().isEditState
return state.copyWrapped(
isEditState = isEditState,
feeState = state.getFeeState(isEditState)?.copy(
feeSelectorState = feeError,
feeSelectorState = FeeSelectorState.Error(feeError),
),
sendState = state.sendState?.copy(
isPrimaryButtonEnabled = false,
@ -152,7 +153,7 @@ internal class FeeStateFactory(
)
}
fun getFeeNotificationState(notifications: ImmutableList<SendNotification>): SendUiState {
fun getFeeNotificationState(notifications: ImmutableList<NotificationUM>): SendUiState {
val state = currentStateProvider()
val isEditState = stateRouterProvider().isEditState
val feeState = state.getFeeState(isEditState) ?: return state
@ -167,7 +168,7 @@ internal class FeeStateFactory(
private fun isPrimaryButtonEnabled(
feeState: SendStates.FeeState,
notifications: ImmutableList<SendNotification>,
notifications: ImmutableList<NotificationUM>,
): Boolean {
val feeSelectorState = feeState.feeSelectorState as? FeeSelectorState.Content ?: return false
val customValue = feeSelectorState.customValues.firstOrNull()
@ -178,7 +179,7 @@ internal class FeeStateFactory(
} else {
false
}
val noErrors = notifications.none { it is SendNotification.Error }
val noErrors = notifications.none { it is NotificationUM.Error }
return noErrors && (isNotEmptyCustom || isNotCustom)
}

View file

@ -103,6 +103,6 @@ internal object FeeStatePreviewData {
)
val errorFeeState = feeState.copy(
feeSelectorState = FeeSelectorState.Error.NetworkError,
feeSelectorState = FeeSelectorState.Error(null),
)
}

View file

@ -1,9 +1,8 @@
package com.tangem.features.send.impl.presentation.state.previewdata
import com.tangem.common.ui.notifications.NotificationUM
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.features.send.impl.presentation.analytics.EnterAddressSource
import com.tangem.features.send.impl.presentation.state.SendNotification
import com.tangem.features.send.impl.presentation.state.fee.FeeType
import com.tangem.features.send.impl.presentation.viewmodel.SendClickIntents
import java.math.BigDecimal
@ -24,11 +23,12 @@ internal object SendClickIntentsStub : SendClickIntents {
override fun onFailedTxEmailClick(errorMessage: String) {}
override fun onTokenDetailsClick(userWalletId: UserWalletId, currency: CryptoCurrency) {}
override fun onTokenDetailsClick(currency: CryptoCurrency) {}
override fun onAmountValueChange(value: String) {}
override fun onCurrencyChangeClick(isFiat: Boolean) {}
override fun onAmountNext() {}
override fun onMaxValueClick() {}
@ -61,13 +61,13 @@ internal object SendClickIntentsStub : SendClickIntents {
override fun onShareClick() {}
override fun onAmountReduceClick(
reduceAmountBy: BigDecimal?,
reduceAmountByDiff: BigDecimal?,
reduceAmountTo: BigDecimal?,
clazz: Class<out SendNotification>,
) {
}
override fun onAmountReduceByClick(
reduceAmountBy: BigDecimal,
reduceAmountByDiff: BigDecimal,
notification: Class<out NotificationUM>,
) {}
override fun onNotificationCancel(clazz: Class<out SendNotification>) {}
override fun onAmountReduceToClick(reduceAmountTo: BigDecimal, notification: Class<out NotificationUM>) {}
override fun onNotificationCancel(clazz: Class<out NotificationUM>) {}
}

View file

@ -5,14 +5,14 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyListScope
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.ui.Modifier
import com.tangem.common.ui.notifications.NotificationUM
import com.tangem.core.ui.components.notifications.Notification
import com.tangem.core.ui.res.TangemTheme
import com.tangem.features.send.impl.presentation.state.SendNotification
import kotlinx.collections.immutable.ImmutableList
@OptIn(ExperimentalFoundationApi::class)
internal fun LazyListScope.notifications(
notifications: ImmutableList<SendNotification>,
notifications: ImmutableList<NotificationUM>,
modifier: Modifier = Modifier,
hasPaddingAbove: Boolean = false,
isClickDisabled: Boolean = false,
@ -33,17 +33,18 @@ internal fun LazyListScope.notifications(
.padding(top = topPadding)
.animateItemPlacement(),
containerColor = when (item) {
is SendNotification.Error.ExceedsBalance,
is SendNotification.Warning.NetworkFeeUnreachable,
is SendNotification.Warning.HighFeeError,
is NotificationUM.Error.TokenExceedsBalance,
is NotificationUM.Warning.NetworkFeeUnreachable,
is NotificationUM.Warning.HighFeeError,
-> TangemTheme.colors.background.action
else -> TangemTheme.colors.button.disabled
},
iconTint = when (item) {
is SendNotification.Error.ExceedsBalance,
is SendNotification.Warning,
is NotificationUM.Error.TokenExceedsBalance,
is NotificationUM.Warning,
-> null
is SendNotification.Error -> TangemTheme.colors.icon.warning
is NotificationUM.Error -> TangemTheme.colors.icon.warning
is NotificationUM.Info -> null
},
isEnabled = !isClickDisabled,
)

View file

@ -1,10 +1,9 @@
package com.tangem.features.send.impl.presentation.viewmodel
import com.tangem.common.ui.amountScreen.AmountScreenClickIntents
import com.tangem.common.ui.notifications.NotificationUM
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.features.send.impl.presentation.analytics.EnterAddressSource
import com.tangem.features.send.impl.presentation.state.SendNotification
import com.tangem.features.send.impl.presentation.state.fee.FeeType
import java.math.BigDecimal
@ -25,7 +24,7 @@ internal interface SendClickIntents : AmountScreenClickIntents {
fun onFailedTxEmailClick(errorMessage: String)
fun onTokenDetailsClick(userWalletId: UserWalletId, currency: CryptoCurrency)
fun onTokenDetailsClick(currency: CryptoCurrency)
// region Recipient
fun onRecipientAddressValueChange(value: String, type: EnterAddressSource? = null)
@ -58,13 +57,14 @@ internal interface SendClickIntents : AmountScreenClickIntents {
fun onShareClick()
fun onAmountReduceClick(
reduceAmountBy: BigDecimal? = null,
reduceAmountByDiff: BigDecimal? = reduceAmountBy,
reduceAmountTo: BigDecimal? = null,
clazz: Class<out SendNotification>,
fun onAmountReduceByClick(
reduceAmountBy: BigDecimal,
reduceAmountByDiff: BigDecimal,
notification: Class<out NotificationUM>,
)
fun onNotificationCancel(clazz: Class<out SendNotification>)
fun onAmountReduceToClick(reduceAmountTo: BigDecimal, notification: Class<out NotificationUM>)
fun onNotificationCancel(clazz: Class<out NotificationUM>)
// endregion
}

View file

@ -11,6 +11,7 @@ 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.models.AmountState
import com.tangem.common.ui.notifications.NotificationUM
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.ui.utils.parseBigDecimal
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
@ -28,7 +29,6 @@ import com.tangem.domain.tokens.*
import com.tangem.domain.tokens.error.CurrencyStatusError
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.tokens.repository.CurrencyChecksRepository
import com.tangem.domain.transaction.error.GetFeeError
import com.tangem.domain.transaction.error.ValidateAddressError
import com.tangem.domain.transaction.usecase.*
@ -47,7 +47,6 @@ import com.tangem.features.send.impl.presentation.analytics.SendAnalyticEvents
import com.tangem.features.send.impl.presentation.analytics.SendScreenSource
import com.tangem.features.send.impl.presentation.analytics.utils.SendScreenAnalyticSender
import com.tangem.features.send.impl.presentation.domain.AvailableWallet
import com.tangem.features.send.impl.presentation.errors.FeeErrorStateMapper
import com.tangem.features.send.impl.presentation.state.*
import com.tangem.features.send.impl.presentation.state.amount.AmountStateFactory
import com.tangem.features.send.impl.presentation.state.confirm.SendNotificationFactory
@ -99,7 +98,7 @@ internal class SendViewModel @Inject constructor(
private val validateWalletMemoUseCase: ValidateWalletMemoUseCase,
@DelayedWork private val coroutineScope: CoroutineScope,
validateTransactionUseCase: ValidateTransactionUseCase,
currencyChecksRepository: CurrencyChecksRepository,
getCurrencyCheckUseCase: GetCurrencyCheckUseCase,
isFeeApproximateUseCase: IsFeeApproximateUseCase,
getBalanceNotEnoughForFeeWarningUseCase: GetBalanceNotEnoughForFeeWarningUseCase,
savedStateHandle: SavedStateHandle,
@ -124,8 +123,6 @@ internal class SendViewModel @Inject constructor(
var stateRouter: StateRouter by Delegates.notNull()
private set
private val feeErrorHandler = FeeErrorStateMapper()
private val stateFactory = SendStateFactory(
clickIntents = this,
stateRouterProvider = Provider { stateRouter },
@ -174,18 +171,18 @@ internal class SendViewModel @Inject constructor(
)
private val sendNotificationFactory = SendNotificationFactory(
analyticsEventHandler = analyticsEventHandler,
validateTransactionUseCase = validateTransactionUseCase,
getCurrencyCheckUseCase = getCurrencyCheckUseCase,
getBalanceNotEnoughForFeeWarningUseCase = getBalanceNotEnoughForFeeWarningUseCase,
cryptoCurrencyStatusProvider = Provider { cryptoCurrencyStatus },
feeCryptoCurrencyStatusProvider = Provider { feeCryptoCurrencyStatus },
currentStateProvider = Provider { uiState.value },
userWalletProvider = Provider { userWallet },
stateRouterProvider = Provider { stateRouter },
isSubtractAvailableProvider = Provider { isAmountSubtractAvailable },
appCurrencyProvider = Provider(selectedAppCurrencyFlow::value),
currencyChecksRepository = currencyChecksRepository,
clickIntents = this,
analyticsEventHandler = analyticsEventHandler,
getBalanceNotEnoughForFeeWarningUseCase = getBalanceNotEnoughForFeeWarningUseCase,
validateTransactionUseCase = validateTransactionUseCase,
userWalletId = userWalletId,
)
private val sendScreenAnalyticSender by lazy(LazyThreadSafetyMode.NONE) {
@ -537,8 +534,7 @@ internal class SendViewModel @Inject constructor(
)
}
override fun onTokenDetailsClick(userWalletId: UserWalletId, currency: CryptoCurrency) =
innerRouter.openTokenDetails(userWalletId, currency)
override fun onTokenDetailsClick(currency: CryptoCurrency) = innerRouter.openTokenDetails(userWalletId, currency)
private fun onFeeNext(): Boolean {
val feeState = uiState.value.getFeeState(stateRouter.isEditState)
@ -730,7 +726,7 @@ internal class SendViewModel @Inject constructor(
private fun onFeeLoadFailed(isShowStatus: Boolean, loadFeeError: GetFeeError?) {
if (isShowStatus) {
uiState.value = feeStateFactory.onFeeOnErrorState(
feeErrorHandler.getFeeError(loadFeeError, cryptoCurrency.name),
loadFeeError,
)
}
}
@ -814,26 +810,27 @@ internal class SendViewModel @Inject constructor(
analyticsEventHandler.send(SendAnalyticEvents.ShareButtonClicked)
}
override fun onAmountReduceClick(
reduceAmountBy: BigDecimal?,
reduceAmountByDiff: BigDecimal?,
reduceAmountTo: BigDecimal?,
clazz: Class<out SendNotification>,
) {
uiState.value = when {
reduceAmountBy != null && reduceAmountByDiff != null -> amountStateFactory.getOnAmountReduceByState(
reduceAmountBy = reduceAmountBy,
reduceAmountByDiff = reduceAmountByDiff,
)
reduceAmountTo != null -> amountStateFactory.getOnAmountReduceToState(reduceAmountTo)
else -> return
}
uiState.value = sendNotificationFactory.dismissNotificationState(clazz)
override fun onAmountReduceToClick(reduceAmountTo: BigDecimal, notification: Class<out NotificationUM>) {
uiState.value = amountStateFactory.getOnAmountReduceToState(reduceAmountTo)
uiState.value = sendNotificationFactory.dismissNotificationState(notification)
updateNotifications()
}
override fun onNotificationCancel(clazz: Class<out SendNotification>) {
override fun onAmountReduceByClick(
reduceAmountBy: BigDecimal,
reduceAmountByDiff: BigDecimal,
notification: Class<out NotificationUM>,
) {
uiState.value = amountStateFactory.getOnAmountReduceByState(
reduceAmountBy = reduceAmountBy,
reduceAmountByDiff = reduceAmountByDiff,
)
uiState.value = sendNotificationFactory.dismissNotificationState(notification)
updateNotifications()
}
override fun onNotificationCancel(clazz: Class<out NotificationUM>) {
uiState.value = sendNotificationFactory.dismissNotificationState(clazz = clazz, isIgnored = true)
}
@ -972,7 +969,7 @@ internal class SendViewModel @Inject constructor(
private fun onCheckFeeUpdate() {
val sendState = uiState.value.sendState ?: return
val isSuccess = sendState.isSuccess
val noErrorNotifications = sendState.notifications.none { it is SendNotification.Error }
val noErrorNotifications = sendState.notifications.none { it is NotificationUM.Error }
if (!isSuccess && noErrorNotifications) {
viewModelScope.launch {

View file

@ -136,13 +136,11 @@ internal class AddStakingNotificationsTransformer(
val cryptoCurrency = cryptoCurrencyStatus.currency
val network = cryptoCurrency.network
if (feeError != null) {
addFeeUnreachableNotification(
feeError = feeError,
tokenName = cryptoCurrencyStatusProvider().currency.name,
onReload = onReload,
)
}
addFeeUnreachableNotification(
feeError = feeError,
tokenName = cryptoCurrencyStatusProvider().currency.name,
onReload = onReload,
)
addStakeExceedBalanceNotification(
feeAmount = feeValue,
sendingAmount = sendingAmount,