Updated on 2026-08-14
This commit is contained in:
parent
2a689bbbe4
commit
f8da5c0d58
13 changed files with 455 additions and 66 deletions
|
|
@ -447,6 +447,8 @@
|
|||
<string name="send_amount_label">Сумма</string>
|
||||
<string name="send_amount_substract">Вычесть из суммы отправки</string>
|
||||
<string name="send_amount_substract_footer">Сумма к получению %s</string>
|
||||
<string name="send_alert_transaction_failed_title">Транзакция не выполнена</string>
|
||||
<string name="send_alert_transaction_failed_text">Причина: %1$s\Код:%2$s</string>
|
||||
<string name="send_date_format">%1$s в %2$s</string>
|
||||
<string name="send_destination_hint_address">Адрес</string>
|
||||
<string name="send_destination_tag_field">Код назначения</string>
|
||||
|
|
@ -481,8 +483,8 @@
|
|||
<string name="send_notification_invalid_amount_text">Включенная комиссия превышает сумму перевода, что приводит к отрицательному значению</string>
|
||||
<string name="send_notification_invalid_amount_title">Недопустимая сумма</string>
|
||||
<string name="send_notification_invalid_minimum_amount_text">Минимальная сумма отправки - %1$s. Пожалуйста, убедитесь, что остаток после отправки также не будет меньше %1$s.</string>
|
||||
<string name="send_notification_invalid_reserve_amount_text">Пожалуйста, пополните свой баланс, чтобы продолжить</string>
|
||||
<string name="send_notification_invalid_reserve_amount_title">Сумма резерва не может быть менее %1$s</string>
|
||||
<string name="send_notification_invalid_reserve_amount_text">Адрес получателя не активирован. Пожалуйста, измените сумму отправки, чтобы продолжить.</string>
|
||||
<string name="send_notification_invalid_reserve_amount_title">Сумма отправки не может быть менее %1$s</string>
|
||||
<string name="send_notification_transaction_delay_text">Обратите внимание, что при определенных параметрах комиссии возможны задержки по вашей транзакции</string>
|
||||
<string name="send_notification_transaction_delay_title">Возможны задержки по транзакции</string>
|
||||
<string name="send_optional_field">Необязательное</string>
|
||||
|
|
|
|||
|
|
@ -446,6 +446,8 @@
|
|||
<string name="send_amount_label">Amount</string>
|
||||
<string name="send_amount_substract">Subtract from send amount</string>
|
||||
<string name="send_amount_substract_footer">The recipient will receive %s</string>
|
||||
<string name="send_alert_transaction_failed_title">The transaction is not completed</string>
|
||||
<string name="send_alert_transaction_failed_text">Reason: %1$s\nCode:%2$s</string>
|
||||
<string name="send_confirm_label">Confirm</string>
|
||||
<string name="send_date_format">%1$s at %2$s</string>
|
||||
<string name="send_destination_hint_address">Address</string>
|
||||
|
|
@ -486,10 +488,14 @@
|
|||
<string name="send_notification_invalid_amount_text">The included commission exceeds the transfer amount, leading to a negative value</string>
|
||||
<string name="send_notification_invalid_amount_title">Invalid amount</string>
|
||||
<string name="send_notification_invalid_minimum_amount_text">The minimum sending amount is %1$s. Please ensure that the remaining balance after sending will not be less than %1$s.</string>
|
||||
<string name="send_notification_invalid_reserve_amount_text">Please top up your balance to continue</string>
|
||||
<string name="send_notification_invalid_reserve_amount_title">The balance amount must be at least %1$s</string>
|
||||
<string name="send_notification_invalid_reserve_amount_text">Target account is not created. Please change the amount to send.</string>
|
||||
<string name="send_notification_invalid_reserve_amount_title">The amount to send must be at least %1$s</string>
|
||||
<string name="send_notification_transaction_delay_text">Kindly be aware that your transaction may experience delays under specific fee settings</string>
|
||||
<string name="send_notification_transaction_delay_title">Transaction delays are possible</string>
|
||||
<string name="send_notifiaction_transaction_limit_title">Transaction limitation</string>
|
||||
<string name="send_notifiaction_transaction_limit_text">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.</string>
|
||||
<string name="send_notification_existential_deposit_title">Existential deposit</string>
|
||||
<string name="send_notification_existential_deposit_text">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.</string>
|
||||
<string name="send_optional_field">Optional</string>
|
||||
<string name="send_qrcode_alert_change">Change</string>
|
||||
<string name="send_qrcode_alert_decline">Decline</string>
|
||||
|
|
|
|||
|
|
@ -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<TxHistoryItem> {
|
||||
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<WalletManager> {
|
||||
return walletManagersStore.getAllSync(userWalletId)
|
||||
}
|
||||
|
||||
@Deprecated(
|
||||
"Use NetworkAddress from CryptoCurrencyStatus",
|
||||
ReplaceWith("cryptoCurrencyStatus.value.networkAddress"),
|
||||
)
|
||||
override suspend fun getAddress(userWalletId: UserWalletId, network: Network): List<Address> {
|
||||
return getAddresses(userWalletId, network).sortedBy { it.type }
|
||||
}
|
||||
|
||||
@Deprecated("Use NetworkAddress from CryptoCurrencyStatus")
|
||||
override suspend fun getAddresses(userWalletId: UserWalletId, network: Network): Set<Address> {
|
||||
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<List<WalletManager>> {
|
||||
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<TransactionFee>? {
|
||||
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<TransactionError>? {
|
||||
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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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<List<WalletManager>>
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
|
|
@ -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)),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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<CryptoCurrencyStatus>,
|
||||
private val coinCryptoCurrencyStatusProvider: Provider<CryptoCurrencyStatus>,
|
||||
private val currentStateProvider: Provider<SendUiState>,
|
||||
private val userWalletProvider: Provider<UserWallet>,
|
||||
private val walletManagersFacade: WalletManagersFacade,
|
||||
) {
|
||||
|
||||
fun create(): Flow<ImmutableList<SendNotification>> = 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<SendNotification>.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<SendNotification>.addInvalidAmountNotification(
|
||||
isSubtractAmount: Boolean,
|
||||
receivedAmount: BigDecimal,
|
||||
) {
|
||||
if (isSubtractAmount && receivedAmount <= BigDecimal.ZERO) {
|
||||
add(SendNotification.Error.InvalidAmount)
|
||||
}
|
||||
}
|
||||
|
||||
private fun MutableList<SendNotification>.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<SendNotification>.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<SendNotification>.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<SendNotification>.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<SendNotification>.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"
|
||||
}
|
||||
}
|
||||
|
|
@ -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<SendNotification>): SendUiState {
|
||||
val state = currentStateProvider()
|
||||
val hasErrorNotifications = notifications.any { it is SendNotification.Error }
|
||||
return state.copy(
|
||||
sendState = state.sendState.copy(
|
||||
isPrimaryButtonEnabled = !hasErrorNotifications,
|
||||
notifications = notifications,
|
||||
),
|
||||
)
|
||||
}
|
||||
//endregion
|
||||
}
|
||||
|
|
@ -86,6 +86,7 @@ internal sealed class SendStates {
|
|||
val isSuccess: Boolean = false,
|
||||
val transactionDate: Long = 0L,
|
||||
val txUrl: String = "",
|
||||
val notifications: ImmutableList<SendNotification> = persistentListOf(),
|
||||
) : SendStates()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -187,6 +187,7 @@ private fun isButtonEnabled(currentState: State<SendUiStateType>, 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
|
||||
}
|
||||
}
|
||||
|
|
@ -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<SendNotification>, 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
|
||||
},
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
@ -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(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue