diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml
index 254dd85c1e..e29baf80c0 100644
--- a/core/res/src/main/res/values-ru/strings.xml
+++ b/core/res/src/main/res/values-ru/strings.xml
@@ -447,6 +447,8 @@
Сумма
Вычесть из суммы отправки
Сумма к получению %s
+ Транзакция не выполнена
+ Причина: %1$s\Код:%2$s
%1$s в %2$s
Адрес
Код назначения
@@ -481,8 +483,8 @@
Включенная комиссия превышает сумму перевода, что приводит к отрицательному значению
Недопустимая сумма
Минимальная сумма отправки - %1$s. Пожалуйста, убедитесь, что остаток после отправки также не будет меньше %1$s.
- Пожалуйста, пополните свой баланс, чтобы продолжить
- Сумма резерва не может быть менее %1$s
+ Адрес получателя не активирован. Пожалуйста, измените сумму отправки, чтобы продолжить.
+ Сумма отправки не может быть менее %1$s
Обратите внимание, что при определенных параметрах комиссии возможны задержки по вашей транзакции
Возможны задержки по транзакции
Необязательное
diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml
index f4187be85b..0186ecb9ff 100644
--- a/core/res/src/main/res/values/strings.xml
+++ b/core/res/src/main/res/values/strings.xml
@@ -446,6 +446,8 @@
Amount
Subtract from send amount
The recipient will receive %s
+ The transaction is not completed
+ Reason: %1$s\nCode:%2$s
Confirm
%1$s at %2$s
Address
@@ -486,10 +488,14 @@
The included commission exceeds the transfer amount, leading to a negative value
Invalid amount
The minimum sending amount is %1$s. Please ensure that the remaining balance after sending will not be less than %1$s.
- Please top up your balance to continue
- The balance amount must be at least %1$s
+ Target account is not created. Please change the amount to send.
+ The amount to send must be at least %1$s
Kindly be aware that your transaction may experience delays under specific fee settings
Transaction delays are possible
+ Transaction limitation
+ Due to %1$s limitations only %2$s UTXOs can fit in a single transaction. This means you can only send %3$s or less. You need to reduce the amount.
+ Existential deposit
+ The account will be wiped from the blockchain if a balance goes below the existential deposit. Please ensure that the remaining balance after sending will not be less than %s.
Optional
Change
Decline
diff --git a/domain/legacy/src/main/java/com/tangem/domain/walletmanager/DefaultWalletManagersFacade.kt b/domain/legacy/src/main/java/com/tangem/domain/walletmanager/DefaultWalletManagersFacade.kt
index 1eaefab578..c29a7a5b33 100644
--- a/domain/legacy/src/main/java/com/tangem/domain/walletmanager/DefaultWalletManagersFacade.kt
+++ b/domain/legacy/src/main/java/com/tangem/domain/walletmanager/DefaultWalletManagersFacade.kt
@@ -145,11 +145,9 @@ class DefaultWalletManagersFacade(
contractAddress: String?,
): String {
val blockchain = Blockchain.fromId(network.id.value)
-
val walletManager = getOrCreateWalletManager(
userWalletId = userWalletId,
- blockchain = blockchain,
- derivationPath = network.derivationPath.value,
+ network = network,
)
requireNotNull(walletManager) {
@@ -165,15 +163,13 @@ class DefaultWalletManagersFacade(
}
override suspend fun getTxHistoryState(userWalletId: UserWalletId, currency: CryptoCurrency): TxHistoryState {
- val blockchain = Blockchain.fromId(currency.network.id.value)
val walletManager = getOrCreateWalletManager(
userWalletId = userWalletId,
- blockchain = blockchain,
- derivationPath = currency.network.derivationPath.value,
+ network = currency.network,
)
requireNotNull(walletManager) {
- "Unable to get a wallet manager for blockchain: $blockchain"
+ "Unable to get a wallet manager for blockchain: ${currency.network}"
}
return walletManager
@@ -193,15 +189,13 @@ class DefaultWalletManagersFacade(
page: Int,
pageSize: Int,
): PaginationWrapper {
- val blockchain = Blockchain.fromId(currency.network.id.value)
val walletManager = getOrCreateWalletManager(
userWalletId = userWalletId,
- blockchain = blockchain,
- derivationPath = currency.network.derivationPath.value,
+ network = currency.network,
)
requireNotNull(walletManager) {
- "Unable to get a wallet manager for blockchain: $blockchain"
+ "Unable to get a wallet manager for blockchain: ${currency.network}"
}
val itemsResult = walletManager.getTransactionsHistory(
@@ -301,6 +295,7 @@ class DefaultWalletManagersFacade(
}
}
+ @Deprecated("Will be removed in future")
override suspend fun getOrCreateWalletManager(
userWalletId: UserWalletId,
blockchain: Blockchain,
@@ -326,33 +321,33 @@ class DefaultWalletManagersFacade(
return walletManager
}
+ @Deprecated("Will be removed in future")
override suspend fun getStoredWalletManagers(userWalletId: UserWalletId): List {
return walletManagersStore.getAllSync(userWalletId)
}
+ @Deprecated(
+ "Use NetworkAddress from CryptoCurrencyStatus",
+ ReplaceWith("cryptoCurrencyStatus.value.networkAddress"),
+ )
override suspend fun getAddress(userWalletId: UserWalletId, network: Network): List {
return getAddresses(userWalletId, network).sortedBy { it.type }
}
+ @Deprecated("Use NetworkAddress from CryptoCurrencyStatus")
override suspend fun getAddresses(userWalletId: UserWalletId, network: Network): Set {
- val blockchain = Blockchain.fromId(network.id.value)
-
- return getOrCreateWalletManager(
+ val manager = getOrCreateWalletManager(
userWalletId = userWalletId,
- blockchain = blockchain,
- derivationPath = network.derivationPath.value,
+ network = network,
)
- ?.wallet
- ?.addresses
- .orEmpty()
+
+ return manager?.wallet?.addresses.orEmpty()
}
override suspend fun getRentInfo(userWalletId: UserWalletId, network: Network): CryptoCurrencyWarning.Rent? {
- val blockchain = Blockchain.fromId(network.id.value)
val manager = getOrCreateWalletManager(
userWalletId = userWalletId,
- blockchain = blockchain,
- derivationPath = network.derivationPath.value,
+ network = network,
)
if (manager !is RentProvider) return null
@@ -381,17 +376,52 @@ class DefaultWalletManagersFacade(
}
}
+ @Deprecated("Will be removed in future")
override suspend fun getExistentialDeposit(userWalletId: UserWalletId, network: Network): BigDecimal? {
- val blockchain = Blockchain.fromId(network.id.value)
val manager = getOrCreateWalletManager(
userWalletId = userWalletId,
- blockchain = blockchain,
- derivationPath = network.derivationPath.value,
+ network = network,
)
return if (manager is ExistentialDepositProvider) manager.getExistentialDeposit() else null
}
+ @Deprecated("Will be removed in future")
+ override suspend fun getReserveAmount(userWalletId: UserWalletId, network: Network): BigDecimal? {
+ val manager = getOrCreateWalletManager(
+ userWalletId = userWalletId,
+ network = network,
+ )
+
+ return if (manager is ReserveAmountProvider) manager.getReserveAmount() else null
+ }
+
+ @Deprecated("Will be removed in future")
+ override suspend fun checkIfAccountFunded(userWalletId: UserWalletId, network: Network, address: String): Boolean {
+ val manager = getOrCreateWalletManager(
+ userWalletId = userWalletId,
+ network = network,
+ )
+
+ return if (manager is ReserveAmountProvider) manager.isAccountFunded(address) else true
+ }
+
+ @Deprecated("Will be removed in future")
+ override suspend fun checkUtxoAmountLimit(
+ userWalletId: UserWalletId,
+ network: Network,
+ amount: BigDecimal,
+ fee: BigDecimal,
+ ): UtxoAmountLimit? {
+ val manager = getOrCreateWalletManager(
+ userWalletId = userWalletId,
+ network = network,
+ )
+
+ return if (manager is UtxoAmountLimitProvider) manager.checkUtxoAmountLimit(amount, fee) else null
+ }
+
+ @Deprecated("Will be removed in future")
override fun getAll(userWalletId: UserWalletId): Flow> {
return walletManagersStore.getAll(userWalletId)
}
@@ -404,8 +434,7 @@ class DefaultWalletManagersFacade(
return either {
val walletManager = getOrCreateWalletManager(
userWalletId = userWalletId,
- blockchain = Blockchain.fromId(network.id.value),
- derivationPath = network.derivationPath.value,
+ network = network,
)
val validator = ensureNotNull(walletManager as? SignatureCountValidator) {
@@ -419,17 +448,16 @@ class DefaultWalletManagersFacade(
}
}
+ @Deprecated("Will be removed in future")
override suspend fun getFee(
amount: Amount,
destination: String,
userWalletId: UserWalletId,
network: Network,
): Result? {
- val blockchain = Blockchain.fromId(network.id.value)
val walletManager = getOrCreateWalletManager(
userWalletId = userWalletId,
- blockchain = blockchain,
- derivationPath = network.derivationPath.value,
+ network = network,
)
return (walletManager as? TransactionSender)?.getFee(
amount = amount,
@@ -457,21 +485,21 @@ class DefaultWalletManagersFacade(
)
}
+ @Deprecated("Will be removed in future")
override suspend fun validateTransaction(
amount: Amount,
fee: Amount?,
userWalletId: UserWalletId,
network: Network,
): EnumSet? {
- val blockchain = Blockchain.fromId(network.id.value)
val walletManager = getOrCreateWalletManager(
userWalletId = userWalletId,
- blockchain = blockchain,
- derivationPath = network.derivationPath.value,
+ network = network,
)
return walletManager?.validateTransaction(amount, fee)
}
+ @Deprecated("Will be removed in future")
override suspend fun createTransaction(
amount: Amount,
fee: Fee,
@@ -480,27 +508,24 @@ class DefaultWalletManagersFacade(
userWalletId: UserWalletId,
network: Network,
): TransactionData? {
- val blockchain = Blockchain.fromId(network.id.value)
val walletManager = getOrCreateWalletManager(
userWalletId = userWalletId,
- blockchain = blockchain,
- derivationPath = network.derivationPath.value,
+ network = network,
)
return walletManager?.createTransaction(amount, fee, destination)
}
+ @Deprecated("Will be removed in future")
override suspend fun sendTransaction(
txData: TransactionData,
signer: CommonSigner,
userWalletId: UserWalletId,
network: Network,
): SimpleResult {
- val blockchain = Blockchain.fromId(network.id.value)
val walletManager = getOrCreateWalletManager(
userWalletId = userWalletId,
- blockchain = blockchain,
- derivationPath = network.derivationPath.value,
+ network = network,
)
return (walletManager as TransactionSender).send(txData, signer)
}
@@ -537,4 +562,13 @@ class DefaultWalletManagersFacade(
walletManager.addTokens(tokensToAdd)
}
+
+ private suspend fun getOrCreateWalletManager(userWalletId: UserWalletId, network: Network): WalletManager? {
+ val blockchain = Blockchain.fromId(network.id.value)
+ return getOrCreateWalletManager(
+ userWalletId = userWalletId,
+ blockchain = blockchain,
+ derivationPath = network.derivationPath.value,
+ )
+ }
}
\ No newline at end of file
diff --git a/domain/legacy/src/main/java/com/tangem/domain/walletmanager/WalletManagersFacade.kt b/domain/legacy/src/main/java/com/tangem/domain/walletmanager/WalletManagersFacade.kt
index 922c354dde..d8647329a8 100644
--- a/domain/legacy/src/main/java/com/tangem/domain/walletmanager/WalletManagersFacade.kt
+++ b/domain/legacy/src/main/java/com/tangem/domain/walletmanager/WalletManagersFacade.kt
@@ -139,9 +139,41 @@ interface WalletManagersFacade {
/**
* Returns value which indicates if the account balance drops below the existential deposit value, it will be
* deactivated and any remaining funds will be destroyed.
+ *
+ * [REDACTED_TODO_COMMENT]
*/
+ @Deprecated("Will be removed in future")
suspend fun getExistentialDeposit(userWalletId: UserWalletId, network: Network): BigDecimal?
+ /**
+ * Returns reserve amount which is required to create an account
+ *
+ * [REDACTED_TODO_COMMENT]
+ */
+ @Deprecated("Will be removed in future")
+ suspend fun getReserveAmount(userWalletId: UserWalletId, network: Network): BigDecimal?
+
+ /**
+ * Returns true if account with [address] was reserved with minimum amount
+ *
+ * [REDACTED_TODO_COMMENT]
+ */
+ @Deprecated("Will be removed in future")
+ suspend fun checkIfAccountFunded(userWalletId: UserWalletId, network: Network, address: String): Boolean
+
+ /**
+ * Checks if transaction amount is within the UTXO limit
+ *
+ * [REDACTED_TODO_COMMENT]
+ */
+ @Deprecated("Will be removed in future")
+ suspend fun checkUtxoAmountLimit(
+ userWalletId: UserWalletId,
+ network: Network,
+ amount: BigDecimal,
+ fee: BigDecimal,
+ ): UtxoAmountLimit?
+
@Deprecated("Will be removed in future")
fun getAll(userWalletId: UserWalletId): Flow>
diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/domain/SendNotification.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/domain/SendNotification.kt
deleted file mode 100644
index 720270d86d..0000000000
--- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/domain/SendNotification.kt
+++ /dev/null
@@ -1,10 +0,0 @@
-package com.tangem.features.send.impl.presentation.domain
-
-sealed class SendNotification {
-
- sealed class Info(val message: String) : SendNotification()
-
- sealed class Critical(val message: String) : SendNotification()
-
- sealed class Error(val message: String) : SendNotification()
-}
\ No newline at end of file
diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendNotification.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendNotification.kt
new file mode 100644
index 0000000000..6d2975319a
--- /dev/null
+++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendNotification.kt
@@ -0,0 +1,77 @@
+package com.tangem.features.send.impl.presentation.state
+
+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.features.send.impl.R
+
+internal sealed class SendNotification(val config: NotificationConfig) {
+
+ sealed class Error(
+ title: TextReference,
+ subtitle: TextReference,
+ buttonState: NotificationConfig.ButtonsState? = null,
+ ) : SendNotification(
+ config = NotificationConfig(
+ title = title,
+ subtitle = subtitle,
+ iconResId = R.drawable.ic_alert_24,
+ buttonsState = buttonState,
+ ),
+ ) {
+
+ object TotalExceedsBalance : Error(
+ title = resourceReference(R.string.send_notification_exceed_balance_title),
+ subtitle = resourceReference(R.string.send_notification_exceed_balance_text),
+ )
+
+ 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)),
+ )
+
+ data class ReserveAmountError(val amount: String) : Error(
+ title = resourceReference(R.string.send_notification_invalid_reserve_amount_title, wrappedList(amount)),
+ subtitle = resourceReference(R.string.send_notification_invalid_reserve_amount_text),
+ )
+
+ data class TransactionLimitError(
+ val cryptoCurrency: String,
+ val utxoLimit: String,
+ val amountLimit: String,
+ ) : Error(
+ title = resourceReference(R.string.send_notifiaction_transaction_limit_title),
+ subtitle = resourceReference(
+ R.string.send_notifiaction_transaction_limit_text,
+ wrappedList(cryptoCurrency, utxoLimit, amountLimit),
+ ),
+ )
+ }
+
+ sealed class Warning(
+ title: TextReference,
+ subtitle: TextReference,
+ ) : SendNotification(
+ config = NotificationConfig(
+ title = title,
+ subtitle = subtitle,
+ iconResId = R.drawable.img_attention_20,
+ ),
+ ) {
+ data class HighFeeError(val amount: String) : Warning(
+ title = resourceReference(R.string.send_notification_high_fee_title),
+ subtitle = resourceReference(R.string.send_notification_high_fee_text, wrappedList(amount)),
+ )
+
+ data class ExistentialDeposit(val deposit: String) : Warning(
+ title = resourceReference(R.string.send_notification_existential_deposit_title),
+ subtitle = resourceReference(R.string.send_notification_existential_deposit_text, wrappedList(deposit)),
+ )
+ }
+}
\ No newline at end of file
diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendNotificationFactory.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendNotificationFactory.kt
new file mode 100644
index 0000000000..ce29bd2c60
--- /dev/null
+++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendNotificationFactory.kt
@@ -0,0 +1,188 @@
+package com.tangem.features.send.impl.presentation.state
+
+import com.tangem.blockchain.common.Blockchain
+import com.tangem.core.ui.utils.BigDecimalFormatter
+import com.tangem.domain.tokens.model.CryptoCurrency
+import com.tangem.domain.tokens.model.CryptoCurrencyStatus
+import com.tangem.domain.walletmanager.WalletManagersFacade
+import com.tangem.domain.wallets.models.UserWallet
+import com.tangem.utils.Provider
+import kotlinx.collections.immutable.ImmutableList
+import kotlinx.collections.immutable.persistentListOf
+import kotlinx.collections.immutable.toImmutableList
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.map
+import java.math.BigDecimal
+
+internal class SendNotificationFactory(
+ private val cryptoCurrencyStatusProvider: Provider,
+ private val coinCryptoCurrencyStatusProvider: Provider,
+ private val currentStateProvider: Provider,
+ private val userWalletProvider: Provider,
+ private val walletManagersFacade: WalletManagersFacade,
+) {
+
+ fun create(): Flow> = currentStateProvider().currentState
+ .filter { it == SendUiStateType.Send }
+ .map {
+ val state = currentStateProvider()
+ val feeState = state.feeState ?: return@map persistentListOf()
+ val recipientState = state.recipientState ?: return@map persistentListOf()
+ val feeAmount = feeState.fee?.amount?.value ?: BigDecimal.ZERO
+ buildList {
+ // errors
+ addExceedBalanceNotification(feeAmount, feeState.receivedAmountValue)
+ addInvalidAmountNotification(feeState.isSubtract, feeState.receivedAmountValue)
+ addMinimumAmountErrorNotification(feeAmount, feeState.receivedAmountValue)
+ addReserveAmountErrorNotification(recipientState.addressTextField.value)
+ addTransactionLimitErrorNotification(feeAmount, feeState.receivedAmountValue)
+ // warnings
+ addExistentialWarningNotification(feeAmount, feeState.receivedAmountValue)
+ addHighFeeWarningNotification()
+ }.toImmutableList()
+ }
+
+ private fun MutableList.addExceedBalanceNotification(
+ feeAmount: BigDecimal,
+ receivedAmount: BigDecimal,
+ ) {
+ val cryptoCurrencyStatus = cryptoCurrencyStatusProvider()
+ val coinCryptoCurrencyStatus = coinCryptoCurrencyStatusProvider()
+ val cryptoAmount = cryptoCurrencyStatus.value.amount ?: BigDecimal.ZERO
+ val coinCryptoAmount = coinCryptoCurrencyStatus.value.amount ?: BigDecimal.ZERO
+
+ val showNotification = if (cryptoCurrencyStatus.currency is CryptoCurrency.Token) {
+ receivedAmount > cryptoAmount || feeAmount > coinCryptoAmount
+ } else {
+ receivedAmount + feeAmount > cryptoAmount
+ }
+
+ if (showNotification) {
+ add(SendNotification.Error.TotalExceedsBalance)
+ }
+ }
+
+ private fun MutableList.addInvalidAmountNotification(
+ isSubtractAmount: Boolean,
+ receivedAmount: BigDecimal,
+ ) {
+ if (isSubtractAmount && receivedAmount <= BigDecimal.ZERO) {
+ add(SendNotification.Error.InvalidAmount)
+ }
+ }
+
+ private fun MutableList.addMinimumAmountErrorNotification(
+ feeAmount: BigDecimal,
+ receivedAmount: BigDecimal,
+ ) {
+ val coinCryptoCurrencyStatus = coinCryptoCurrencyStatusProvider()
+
+ val totalAmount = feeAmount + receivedAmount
+ val balance = coinCryptoCurrencyStatus.value.amount ?: BigDecimal.ZERO
+
+ // TODO Move Blockchain check elsewhere
+ when (coinCryptoCurrencyStatus.currency.network.id.value) {
+ Blockchain.Cardano.id -> {
+ if (receivedAmount > BigDecimal.ONE || balance - totalAmount < BigDecimal.ONE) {
+ add(SendNotification.Error.MinimumAmountError(CARDANO_MINIMUM))
+ }
+ }
+ Blockchain.Dogecoin.id -> {
+ val minimum = BigDecimal(DOGECOIN_MINIMUM)
+ if (receivedAmount > minimum || balance - totalAmount < minimum) {
+ add(SendNotification.Error.MinimumAmountError(DOGECOIN_MINIMUM))
+ }
+ }
+ else -> Unit
+ }
+ }
+
+ private suspend fun MutableList.addReserveAmountErrorNotification(recipientAddress: String) {
+ val userWalletId = userWalletProvider().walletId
+ val cryptoCurrency = cryptoCurrencyStatusProvider().currency
+ val isAccountFunded = walletManagersFacade.checkIfAccountFunded(
+ userWalletId,
+ cryptoCurrency.network,
+ recipientAddress,
+ )
+ val minimumAmount = walletManagersFacade.getReserveAmount(userWalletId, cryptoCurrency.network)
+ if (!isAccountFunded && minimumAmount != null && minimumAmount > BigDecimal.ZERO) {
+ add(
+ SendNotification.Error.ReserveAmountError(
+ BigDecimalFormatter.formatCryptoAmount(
+ cryptoAmount = minimumAmount,
+ cryptoCurrency = cryptoCurrency,
+ ),
+ ),
+ )
+ }
+ }
+
+ private suspend fun MutableList.addTransactionLimitErrorNotification(
+ feeAmount: BigDecimal,
+ receivedAmount: BigDecimal,
+ ) {
+ val userWalletId = userWalletProvider().walletId
+ val cryptoCurrency = cryptoCurrencyStatusProvider().currency
+ val utxoLimit = walletManagersFacade.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,
+ ),
+ ),
+ )
+ }
+ }
+
+ private suspend fun MutableList.addExistentialWarningNotification(
+ feeAmount: BigDecimal,
+ receivedAmount: BigDecimal,
+ ) {
+ val userWalletId = userWalletProvider().walletId
+ val cryptoCurrency = cryptoCurrencyStatusProvider().currency
+ val spendingAmount = if (cryptoCurrency is CryptoCurrency.Token) {
+ feeAmount
+ } else {
+ feeAmount + receivedAmount
+ }
+ val currencyDeposit = walletManagersFacade.getExistentialDeposit(
+ userWalletId,
+ cryptoCurrency.network,
+ )
+ if (currencyDeposit != null && currencyDeposit > spendingAmount) {
+ add(
+ SendNotification.Warning.ExistentialDeposit(
+ BigDecimalFormatter.formatCryptoAmount(
+ cryptoAmount = currencyDeposit,
+ cryptoCurrency = cryptoCurrency,
+ ),
+ ),
+ )
+ }
+ }
+
+ private fun MutableList.addHighFeeWarningNotification() {
+ // TODO Move Blockchain check elsewhere
+ if (cryptoCurrencyStatusProvider().currency.network.id.value == Blockchain.Tezos.id) {
+ add(SendNotification.Warning.HighFeeError(TEZOS_FEE_THRESHOLD))
+ }
+ }
+
+ companion object {
+ private const val CARDANO_MINIMUM = "1"
+ private const val DOGECOIN_MINIMUM = "0.01"
+ private const val TEZOS_FEE_THRESHOLD = "0.01"
+ }
+}
\ No newline at end of file
diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendStateFactory.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendStateFactory.kt
index 608acd39e8..177b470fda 100644
--- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendStateFactory.kt
+++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendStateFactory.kt
@@ -24,6 +24,7 @@ import com.tangem.features.send.impl.presentation.state.recipient.SendRecipientS
import com.tangem.features.send.impl.presentation.viewmodel.SendClickIntents
import com.tangem.utils.Provider
import com.tangem.utils.isNullOrZero
+import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.flow.MutableStateFlow
@@ -238,7 +239,7 @@ internal class SendStateFactory(
)
val fee = feeSelectorState.getFee()
- val receivedAmount = calculateReceiveAmount(state, fee)
+ val receivedAmount = calculateReceiveAmount(state, fee, feeState.isSubtract)
val updatedState = feeState.copy(
feeSelectorState = feeSelectorState,
fee = fee,
@@ -260,7 +261,7 @@ internal class SendStateFactory(
val updatedFeeSelectorState = feeSelectorState.copy(selectedFee = feeType)
val fee = updatedFeeSelectorState.getFee()
- val receivedAmount = calculateReceiveAmount(state, fee)
+ val receivedAmount = calculateReceiveAmount(state, fee, feeState.isSubtract)
val updatedState = feeState.copy(
fee = fee,
@@ -288,7 +289,7 @@ internal class SendStateFactory(
)
val fee = updatedFeeSelectorState.getFee()
- val receivedAmount = calculateReceiveAmount(state, fee)
+ val receivedAmount = calculateReceiveAmount(state, fee, feeState.isSubtract)
val updatedState = feeState.copy(
feeSelectorState = updatedFeeSelectorState,
@@ -310,7 +311,7 @@ internal class SendStateFactory(
val feeSelectorState = feeState.feeSelectorState as? FeeSelectorState.Content ?: return state
val fee = feeSelectorState.getFee()
- val receivedAmount = calculateReceiveAmount(state, fee)
+ val receivedAmount = calculateReceiveAmount(state, fee, value)
val updatedState = feeState.copy(
isSubtract = value,
fee = fee,
@@ -375,5 +376,16 @@ internal class SendStateFactory(
),
)
}
+
+ fun getSendNotificationState(notifications: ImmutableList): SendUiState {
+ val state = currentStateProvider()
+ val hasErrorNotifications = notifications.any { it is SendNotification.Error }
+ return state.copy(
+ sendState = state.sendState.copy(
+ isPrimaryButtonEnabled = !hasErrorNotifications,
+ notifications = notifications,
+ ),
+ )
+ }
//endregion
}
\ No newline at end of file
diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendUiState.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendUiState.kt
index a64cbfa738..fb2918c11d 100644
--- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendUiState.kt
+++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendUiState.kt
@@ -86,6 +86,7 @@ internal sealed class SendStates {
val isSuccess: Boolean = false,
val transactionDate: Long = 0L,
val txUrl: String = "",
+ val notifications: ImmutableList = persistentListOf(),
) : SendStates()
}
diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeCalculation.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeCalculation.kt
index dfbbab99e8..1805ce3f6a 100644
--- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeCalculation.kt
+++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeCalculation.kt
@@ -8,10 +8,14 @@ import java.math.BigDecimal
/**
* Calculate receiving amount when fee is subtracted from sending amount
*/
-internal fun calculateReceiveAmount(uiState: SendUiState, feeAmount: Fee): BigDecimal {
+internal fun calculateReceiveAmount(uiState: SendUiState, feeAmount: Fee, isSubtract: Boolean): BigDecimal {
val amount = uiState.amountState?.amountTextField?.value ?: return BigDecimal.ZERO
val fee = feeAmount.amount.value ?: return BigDecimal.ZERO
- return BigDecimal(amount).minus(fee)
+ return if (isSubtract) {
+ amount.toBigDecimal().minus(fee)
+ } else {
+ amount.toBigDecimal()
+ }
}
/**
diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendNavigationButtons.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendNavigationButtons.kt
index 9b11c51df3..525578a157 100644
--- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendNavigationButtons.kt
+++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendNavigationButtons.kt
@@ -187,6 +187,7 @@ private fun isButtonEnabled(currentState: State, uiState: SendU
SendUiStateType.Amount -> uiState.amountState?.isPrimaryButtonEnabled ?: false
SendUiStateType.Recipient -> uiState.recipientState?.isPrimaryButtonEnabled ?: false
SendUiStateType.Fee -> uiState.feeState?.isPrimaryButtonEnabled ?: false
+ SendUiStateType.Send -> uiState.sendState.isPrimaryButtonEnabled
else -> true
}
}
\ No newline at end of file
diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/SendContent.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/SendContent.kt
index 479fc9843a..8bb21e14d3 100644
--- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/SendContent.kt
+++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/SendContent.kt
@@ -1,10 +1,13 @@
package com.tangem.features.send.impl.presentation.ui.send
import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.LazyListScope
+import androidx.compose.foundation.lazy.items
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
@@ -18,14 +21,17 @@ import com.tangem.blockchain.extensions.toBigDecimalOrDefault
import com.tangem.core.ui.components.inputrow.InputRowDefault
import com.tangem.core.ui.components.inputrow.InputRowImage
import com.tangem.core.ui.components.inputrow.InputRowRecipientDefault
+import com.tangem.core.ui.components.notifications.Notification
import com.tangem.core.ui.components.transactions.TransactionDoneTitle
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.utils.BigDecimalFormatter
import com.tangem.core.ui.utils.BigDecimalFormatter.formatCryptoAmount
import com.tangem.features.send.impl.R
+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 kotlinx.collections.immutable.ImmutableList
@Suppress("LongMethod")
@Composable
@@ -75,6 +81,7 @@ internal fun SendContent(uiState: SendUiState) {
)
}
}
+ notifications(sendState.notifications)
}
}
@@ -178,4 +185,24 @@ private fun FeeBlock(feeState: SendStates.FeeState, isSuccess: Boolean, onClick:
.background(TangemTheme.colors.background.action)
.clickable(enabled = !isSuccess) { onClick() },
)
+}
+
+@OptIn(ExperimentalFoundationApi::class)
+internal fun LazyListScope.notifications(configs: ImmutableList, modifier: Modifier = Modifier) {
+ items(
+ items = configs,
+ key = { it::class.java },
+ contentType = { it::class.java },
+ itemContent = {
+ Notification(
+ config = it.config,
+ modifier = modifier.animateItemPlacement(),
+ containerColor = TangemTheme.colors.button.disabled,
+ iconTint = when (it) {
+ is SendNotification.Error -> TangemTheme.colors.icon.warning
+ is SendNotification.Warning -> null
+ },
+ )
+ },
+ )
}
\ No newline at end of file
diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt
index 36e8b54f06..394c03f652 100644
--- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt
+++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt
@@ -96,6 +96,14 @@ internal class SendViewModel @Inject constructor(
getExplorerTransactionUrlUseCase = getExplorerTransactionUrlUseCase,
)
+ private val sendNotificationFactory = SendNotificationFactory(
+ cryptoCurrencyStatusProvider = Provider { cryptoCurrencyStatus },
+ coinCryptoCurrencyStatusProvider = Provider { coinCryptoCurrencyStatus },
+ currentStateProvider = Provider { uiState },
+ userWalletProvider = Provider { userWallet },
+ walletManagersFacade = walletManagersFacade,
+ )
+
var uiState: SendUiState by mutableStateOf(stateFactory.getInitialState())
private set
@@ -107,6 +115,7 @@ internal class SendViewModel @Inject constructor(
private var recipientsJobHolder = JobHolder()
private var feeJobHolder = JobHolder()
private var addressValidationJobHolder = JobHolder()
+ private var sendNotificationsJobHolder = JobHolder()
override fun onCreate(owner: LifecycleOwner) {
subscribeOnCurrencyStatusUpdates(owner)
@@ -146,6 +155,7 @@ internal class SendViewModel @Inject constructor(
coinCryptoCurrencyStatus = it
getWalletsAndRecent()
uiState = stateFactory.getReadyState()
+ updateNotifications()
}
}
.flowOn(dispatchers.main)
@@ -300,6 +310,16 @@ internal class SendViewModel @Inject constructor(
}.saveIn(feeJobHolder)
}
+ private fun updateNotifications() {
+ sendNotificationFactory.create()
+ .conflate()
+ .distinctUntilChanged()
+ .onEach { uiState = stateFactory.getSendNotificationState(notifications = it) }
+ .flowOn(dispatchers.main)
+ .launchIn(viewModelScope)
+ .saveIn(sendNotificationsJobHolder)
+ }
+
// region screen state navigation
override fun popBackStack() = stateRouter.popBackStack()
override fun onBackClick() = stateRouter.onBackClick()
@@ -376,7 +396,7 @@ internal class SendViewModel @Inject constructor(
}
return false
}
-// endregion
+ // endregion
// region fee
override fun onFeeSelectorClick(feeType: FeeType) {
@@ -410,18 +430,13 @@ internal class SendViewModel @Inject constructor(
override fun onExploreClick(txUrl: String) = innerRouter.openUrl(txUrl)
private suspend fun verifyAndSendTransaction() {
- val amount = uiState.amountState?.amountTextField?.value ?: return
val recipient = uiState.recipientState?.addressTextField?.value ?: return
val feeState = uiState.feeState ?: return
val feeSelectorState = feeState.feeSelectorState as? FeeSelectorState.Content ?: return
val memo = uiState.recipientState?.memoTextField?.value
val fee = feeSelectorState.getFee()
- val amountToSend = if (feeState.isSubtract) {
- feeState.receivedAmountValue.convertToAmount(cryptoCurrency)
- } else {
- amount.toBigDecimal().convertToAmount(cryptoCurrency)
- }
+ val amountToSend = feeState.receivedAmountValue.convertToAmount(cryptoCurrency)
// todo add error handling [[REDACTED_JIRA]]
// val transactionErrors = walletManagersFacade.validateTransaction(