From 94579f4af822b9c1c97497300316319034910b54 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 8 Apr 2026 12:30:19 +0400 Subject: [PATCH] Updated on 2026-08-14 --- .../field/AmountFieldChangeTransformer.kt | 27 ++---- .../field/AmountFieldQuoteTransformer.kt | 92 +++++++++++++++++++ .../AmountFieldSetMaxAmountTransformer.kt | 23 ++--- .../ui/amountScreen/utils/AmountUtils.kt | 26 +++++- .../SwapAmountSelectQuoteTransformer.kt | 18 ++-- 5 files changed, 138 insertions(+), 48 deletions(-) create mode 100644 common/ui/src/main/java/com/tangem/common/ui/amountScreen/converters/field/AmountFieldQuoteTransformer.kt diff --git a/common/ui/src/main/java/com/tangem/common/ui/amountScreen/converters/field/AmountFieldChangeTransformer.kt b/common/ui/src/main/java/com/tangem/common/ui/amountScreen/converters/field/AmountFieldChangeTransformer.kt index c4a063c28b..4abc44b25d 100644 --- a/common/ui/src/main/java/com/tangem/common/ui/amountScreen/converters/field/AmountFieldChangeTransformer.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/amountScreen/converters/field/AmountFieldChangeTransformer.kt @@ -3,18 +3,13 @@ package com.tangem.common.ui.amountScreen.converters.field import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.KeyboardType -import com.tangem.common.ui.R import com.tangem.common.ui.amountScreen.models.AmountState import com.tangem.common.ui.amountScreen.models.EnterAmountBoundary import com.tangem.common.ui.amountScreen.utils.checkExceedBalance +import com.tangem.common.ui.amountScreen.utils.getAmountValidationError import com.tangem.common.ui.amountScreen.utils.getCryptoValue import com.tangem.common.ui.amountScreen.utils.getFiatValue import com.tangem.common.ui.amountScreen.utils.getKeyboardAction -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.format.bigdecimal.crypto -import com.tangem.core.ui.format.bigdecimal.format import com.tangem.core.ui.utils.parseToBigDecimal import com.tangem.domain.models.currency.CryptoCurrencyStatus import com.tangem.utils.isNullOrZero @@ -72,20 +67,12 @@ class AmountFieldChangeTransformer( value = cryptoValue, fiatValue = fiatValue, isError = isCheckFailed, - error = when { - isExceedBalance -> resourceReference(R.string.common_insufficient_balance) - isLessThanMinimumIfProvided -> { - val minimumAmount = minimumTransactionAmount.amount.format { - crypto(cryptoCurrencyStatus.currency) - } - - resourceReference( - R.string.transfer_notification_invalid_minimum_transaction_amount_text, - wrappedList(minimumAmount, minimumAmount), - ) - } - else -> TextReference.EMPTY - }, + error = getAmountValidationError( + isExceedBalance = isExceedBalance, + isLessThanMinimum = isLessThanMinimumIfProvided, + minimumTransactionAmount = minimumTransactionAmount, + cryptoCurrency = cryptoCurrencyStatus.currency, + ), cryptoAmount = amountTextField.cryptoAmount.copy(value = decimalCryptoValue), fiatAmount = amountTextField.fiatAmount.copy(value = decimalFiatValue), keyboardOptions = KeyboardOptions( diff --git a/common/ui/src/main/java/com/tangem/common/ui/amountScreen/converters/field/AmountFieldQuoteTransformer.kt b/common/ui/src/main/java/com/tangem/common/ui/amountScreen/converters/field/AmountFieldQuoteTransformer.kt new file mode 100644 index 0000000000..d02d284c0d --- /dev/null +++ b/common/ui/src/main/java/com/tangem/common/ui/amountScreen/converters/field/AmountFieldQuoteTransformer.kt @@ -0,0 +1,92 @@ +package com.tangem.common.ui.amountScreen.converters.field + +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.text.input.KeyboardType +import com.tangem.common.ui.amountScreen.models.AmountState +import com.tangem.common.ui.amountScreen.models.EnterAmountBoundary +import com.tangem.common.ui.amountScreen.utils.getAmountValidationError +import com.tangem.common.ui.amountScreen.utils.getKeyboardAction +import com.tangem.core.ui.utils.parseBigDecimal +import com.tangem.domain.models.currency.CryptoCurrencyStatus +import com.tangem.utils.isNullOrZero +import com.tangem.utils.transformer.Transformer +import java.math.BigDecimal +import java.math.RoundingMode + +/** + * @property cryptoAmount the crypto amount to set, or `null` to clear the field + * @property maxEnterAmount maximum allowed amount boundary (for balance validation) + * @property minimumTransactionAmount minimum allowed amount boundary (optional) + */ +class AmountFieldQuoteTransformer( + private val cryptoCurrencyStatus: CryptoCurrencyStatus, + private val cryptoAmount: BigDecimal?, + private val maxEnterAmount: EnterAmountBoundary, + private val minimumTransactionAmount: EnterAmountBoundary?, +) : Transformer { + + override fun transform(prevState: AmountState): AmountState { + if (prevState !is AmountState.Data) return prevState + if (cryptoAmount == null) return prevState.emptyState(maxEnterAmount.fiatRate) + + val textField = prevState.amountTextField + val cryptoDecimals = textField.cryptoAmount.decimals + val fiatDecimals = textField.fiatAmount.decimals + val fiatRate = maxEnterAmount.fiatRate + val cryptoString = cryptoAmount.parseBigDecimal(cryptoDecimals) + val fiatValue = fiatRate?.let { cryptoAmount.multiply(it).setScale(fiatDecimals, RoundingMode.HALF_UP) } + val fiatString = fiatValue?.parseBigDecimal(fiatDecimals).orEmpty() + + val isExceedBalance = if (textField.isFiatValue) { + val maxFiat = maxEnterAmount.fiatAmount + maxFiat != null && fiatValue != null && fiatValue > maxFiat + } else { + val maxCrypto = maxEnterAmount.amount + maxCrypto != null && cryptoAmount > maxCrypto + } + val isLessThanMinimum = minimumTransactionAmount?.amount?.let { cryptoAmount < it } == true + val isZero = cryptoAmount.isNullOrZero() + val isCheckFailed = isExceedBalance || isLessThanMinimum + + return prevState.copy( + isPrimaryButtonEnabled = !isZero && !isCheckFailed, + reduceAmountBy = BigDecimal.ZERO, + amountTextField = textField.copy( + value = cryptoString, + fiatValue = fiatString, + cryptoAmount = textField.cryptoAmount.copy(value = cryptoAmount), + fiatAmount = textField.fiatAmount.copy(value = fiatValue), + isError = isCheckFailed, + error = getAmountValidationError( + isExceedBalance = isExceedBalance, + isLessThanMinimum = isLessThanMinimum, + minimumTransactionAmount = minimumTransactionAmount, + cryptoCurrency = cryptoCurrencyStatus.currency, + ), + keyboardOptions = KeyboardOptions( + imeAction = getKeyboardAction(isCheckFailed, cryptoAmount), + keyboardType = KeyboardType.Number, + ), + ), + ) + } + + private fun AmountState.Data.emptyState(fiatRate: BigDecimal?): AmountState.Data { + return copy( + isPrimaryButtonEnabled = false, + reduceAmountBy = BigDecimal.ZERO, + amountTextField = amountTextField.copy( + value = "", + fiatValue = "", + cryptoAmount = amountTextField.cryptoAmount.copy(value = BigDecimal.ZERO), + fiatAmount = amountTextField.fiatAmount.copy(value = if (fiatRate != null) BigDecimal.ZERO else null), + isError = false, + keyboardOptions = KeyboardOptions( + imeAction = ImeAction.None, + keyboardType = KeyboardType.Number, + ), + ), + ) + } +} \ No newline at end of file diff --git a/common/ui/src/main/java/com/tangem/common/ui/amountScreen/converters/field/AmountFieldSetMaxAmountTransformer.kt b/common/ui/src/main/java/com/tangem/common/ui/amountScreen/converters/field/AmountFieldSetMaxAmountTransformer.kt index db245b6b30..a38dbee052 100644 --- a/common/ui/src/main/java/com/tangem/common/ui/amountScreen/converters/field/AmountFieldSetMaxAmountTransformer.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/amountScreen/converters/field/AmountFieldSetMaxAmountTransformer.kt @@ -2,15 +2,10 @@ package com.tangem.common.ui.amountScreen.converters.field import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.ui.text.input.KeyboardType -import com.tangem.common.ui.R import com.tangem.common.ui.amountScreen.models.AmountState import com.tangem.common.ui.amountScreen.models.EnterAmountBoundary +import com.tangem.common.ui.amountScreen.utils.getAmountValidationError import com.tangem.common.ui.amountScreen.utils.getKeyboardAction -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.format.bigdecimal.crypto -import com.tangem.core.ui.format.bigdecimal.format import com.tangem.core.ui.utils.parseBigDecimal import com.tangem.domain.models.currency.CryptoCurrencyStatus import com.tangem.utils.extensions.isZero @@ -52,16 +47,12 @@ class AmountFieldSetMaxAmountTransformer( value = cryptoValue, fiatValue = fiatValue, isError = isLessThanMinimumIfProvided, - error = when { - isLessThanMinimumIfProvided -> { - val minimumAmount = minAmount.amount.format { crypto(cryptoCurrencyStatus.currency) } - resourceReference( - R.string.transfer_notification_invalid_minimum_transaction_amount_text, - wrappedList(minimumAmount, minimumAmount), - ) - } - else -> TextReference.EMPTY - }, + error = getAmountValidationError( + isExceedBalance = false, + isLessThanMinimum = isLessThanMinimumIfProvided, + minimumTransactionAmount = minAmount, + cryptoCurrency = cryptoCurrencyStatus.currency, + ), cryptoAmount = amountTextField.cryptoAmount.copy(value = decimalCryptoValue), fiatAmount = amountTextField.fiatAmount.copy(value = decimalFiatValue), keyboardOptions = KeyboardOptions( diff --git a/common/ui/src/main/java/com/tangem/common/ui/amountScreen/utils/AmountUtils.kt b/common/ui/src/main/java/com/tangem/common/ui/amountScreen/utils/AmountUtils.kt index 990e4bc9c1..9473a96363 100644 --- a/common/ui/src/main/java/com/tangem/common/ui/amountScreen/utils/AmountUtils.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/amountScreen/utils/AmountUtils.kt @@ -1,10 +1,17 @@ package com.tangem.common.ui.amountScreen.utils import androidx.compose.ui.text.input.ImeAction +import com.tangem.common.ui.R import com.tangem.common.ui.amountScreen.models.AmountFieldModel import com.tangem.common.ui.amountScreen.models.EnterAmountBoundary +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.format.bigdecimal.crypto +import com.tangem.core.ui.format.bigdecimal.format import com.tangem.core.ui.utils.parseBigDecimal import com.tangem.core.ui.utils.parseToBigDecimal +import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.utils.extensions.isZero import java.math.BigDecimal import java.math.RoundingMode @@ -56,4 +63,21 @@ internal fun getKeyboardAction(isCheckFailed: Boolean, decimalCryptoValue: BigDe ImeAction.Done } else { ImeAction.None - } \ No newline at end of file + } + +internal fun getAmountValidationError( + isExceedBalance: Boolean, + isLessThanMinimum: Boolean, + minimumTransactionAmount: EnterAmountBoundary?, + cryptoCurrency: CryptoCurrency, +): TextReference = when { + isExceedBalance -> resourceReference(R.string.common_insufficient_balance) + isLessThanMinimum -> { + val minimumAmount = minimumTransactionAmount?.amount.format { crypto(cryptoCurrency) } + resourceReference( + R.string.transfer_notification_invalid_minimum_transaction_amount_text, + wrappedList(minimumAmount, minimumAmount), + ) + } + else -> TextReference.EMPTY +} \ No newline at end of file diff --git a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/model/transformers/SwapAmountSelectQuoteTransformer.kt b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/model/transformers/SwapAmountSelectQuoteTransformer.kt index 8caee7dee2..19a5e22946 100644 --- a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/model/transformers/SwapAmountSelectQuoteTransformer.kt +++ b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/model/transformers/SwapAmountSelectQuoteTransformer.kt @@ -1,11 +1,10 @@ package com.tangem.features.swap.v2.impl.amount.model.transformers -import com.tangem.common.ui.amountScreen.converters.field.AmountFieldChangeTransformer +import com.tangem.common.ui.amountScreen.converters.field.AmountFieldQuoteTransformer import com.tangem.common.ui.amountScreen.models.AmountState import com.tangem.common.ui.amountScreen.models.EnterAmountBoundary import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.resourceReference -import com.tangem.core.ui.utils.parseBigDecimal import com.tangem.domain.swap.models.SwapAmountType import com.tangem.features.swap.v2.impl.R import com.tangem.features.swap.v2.impl.amount.entity.SwapAmountFieldUM @@ -93,13 +92,11 @@ internal class SwapAmountSelectQuoteTransformer( isAmountEmpty = false, displayAmount = fromAmount, ).copy( - amountField = AmountFieldChangeTransformer( + amountField = AmountFieldQuoteTransformer( cryptoCurrencyStatus = prevState.primaryCryptoCurrencyStatus, + cryptoAmount = fromAmount, maxEnterAmount = primaryMaximumAmountBoundary, minimumTransactionAmount = primaryMinimumAmountBoundary, - value = fromAmount.parseBigDecimal( - prevState.primaryCryptoCurrencyStatus.currency.decimals, - ), ).transform(fromField.amountField), ) } ?: prevState.primaryAmount @@ -123,11 +120,11 @@ internal class SwapAmountSelectQuoteTransformer( cryptoCurrencyStatus = prevState.primaryCryptoCurrencyStatus, isAmountEmpty = true, ).copy( - amountField = AmountFieldChangeTransformer( + amountField = AmountFieldQuoteTransformer( cryptoCurrencyStatus = prevState.primaryCryptoCurrencyStatus, + cryptoAmount = null, maxEnterAmount = primaryMaximumAmountBoundary, minimumTransactionAmount = primaryMinimumAmountBoundary, - value = "", ).transform(primarySwapAmountField.amountField), ) } @@ -163,12 +160,11 @@ internal class SwapAmountSelectQuoteTransformer( val transformedSecondaryAmountField = if (isSecondarySelected && toAmount == null) { secondarySwapAmountField.amountField } else { - AmountFieldChangeTransformer( + AmountFieldQuoteTransformer( cryptoCurrencyStatus = prevState.secondaryCryptoCurrencyStatus, + cryptoAmount = toAmount, maxEnterAmount = secondaryMaximumAmountBoundary, minimumTransactionAmount = secondaryMinimumAmountBoundary, - value = toAmount?.parseBigDecimal(prevState.secondaryCryptoCurrencyStatus.currency.decimals) - .orEmpty(), ).transform(secondarySwapAmountField.amountField) } val insufficientFundsError = if (isSecondarySelected && fromAmount != null) {