Updated on 2026-08-14

This commit is contained in:
Tangem 2024-05-20 19:31:28 +05:00
commit f0c1615a35
8 changed files with 60 additions and 57 deletions

View file

@ -28,6 +28,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import com.tangem.core.ui.components.fields.visualtransformations.AmountVisualTransformation
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.utils.*
import java.math.BigDecimal
import java.text.DecimalFormat
/**
@ -123,32 +124,49 @@ fun AmountTextField(
private fun prepareEnter(oldValue: String, newValue: String, decimalFormat: DecimalFormat, decimals: Int): String {
val decimalSymbol = decimalFormat.decimalFormatSymbols.decimalSeparator
return if (decimalFormat.isValidSymbols(newValue)) {
val parsedValue = newValue.parseBigDecimalOrNull()?.toPlainString()
?: if (newValue.isBlank()) "" else oldValue
val replacedWithSymbol = if (parsedValue.findLast { it != decimalSymbol } != null) {
when {
parsedValue.findLast { it == COMMA_SEPARATOR } != null -> {
parsedValue.replace(COMMA_SEPARATOR, decimalSymbol)
}
parsedValue.findLast { it == POINT_SEPARATOR } != null -> {
parsedValue.replace(POINT_SEPARATOR, decimalSymbol)
}
else -> parsedValue
}
} else {
parsedValue
}
val joinedSymbol = if (newValue.endsWith(COMMA_SEPARATOR) || newValue.endsWith(POINT_SEPARATOR)) {
replacedWithSymbol.plus(decimalSymbol)
} else {
replacedWithSymbol
}
decimalFormat.getValidatedNumberWithFixedDecimals(joinedSymbol, decimals)
val parsedDecimal = newValue.parseBigDecimalOrNull()
val parsedValue = parsedDecimal?.toPlainString() ?: if (newValue.isBlank()) "" else oldValue
val replacedWithSymbol = parsedValue.replaceDecimalSymbol(decimalSymbol)
val joinedSymbol = replacedWithSymbol.preserveDecimalSymbol(newValue, decimalSymbol)
val withPreservedZeros = joinedSymbol.preserveTrailingZeros(newValue, parsedDecimal, decimalSymbol)
decimalFormat.getValidatedNumberWithFixedDecimals(withPreservedZeros, decimals)
} else {
oldValue
}
}
private fun String.replaceDecimalSymbol(decimalSymbol: Char) = if (this.findLast { it != decimalSymbol } != null) {
when {
this.findLast { it == COMMA_SEPARATOR } != null -> {
this.replace(COMMA_SEPARATOR, decimalSymbol)
}
this.findLast { it == POINT_SEPARATOR } != null -> {
this.replace(POINT_SEPARATOR, decimalSymbol)
}
else -> this
}
} else {
this
}
private fun String.preserveDecimalSymbol(newValue: String, decimalSymbol: Char) = if (
newValue.endsWith(COMMA_SEPARATOR) || newValue.endsWith(POINT_SEPARATOR)
) {
this.plus(decimalSymbol)
} else {
this
}
private fun String.preserveTrailingZeros(newValue: String, parsedDecimal: BigDecimal?, decimalSymbol: Char): String {
val trailingZeros = newValue.split(decimalSymbol).getOrNull(1)?.takeLastWhile { it == '0' }.orEmpty()
return if (parsedDecimal?.scale() == 0 && trailingZeros.isNotEmpty()) {
"$this$decimalSymbol$trailingZeros"
} else {
this.plus(trailingZeros)
}
}
private fun DecimalFormat.isValidSymbols(text: String): Boolean {
return checkDecimalSeparatorDuplicate(text)
}

View file

@ -296,6 +296,7 @@ internal class SendStateFactory(
balance = balance,
amountValue = amountValue,
feeValue = feeValue,
reduceAmountBy = state.sendState?.reduceAmountBy,
),
)
}
@ -331,6 +332,12 @@ internal class SendStateFactory(
fun getSendNotificationState(notifications: ImmutableList<SendNotification>): 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
}
}
return state.copy(
sendState = sendState.copy(
isPrimaryButtonEnabled = isPrimaryButtonEnabled(
@ -338,6 +345,7 @@ internal class SendStateFactory(
isSending = sendState.isSending,
notifications = notifications,
),
reduceAmountBy = reducedBy,
notifications = notifications,
showTapHelp = sendState.showTapHelp && notifications.isEmpty(),
),

View file

@ -142,7 +142,6 @@ internal sealed class SendStates {
val txUrl: String,
val ignoreAmountReduce: Boolean,
val reduceAmountBy: BigDecimal?,
val reduceAmountTo: BigDecimal?,
val isFromConfirmation: Boolean,
val showTapHelp: Boolean,
val notifications: ImmutableList<SendNotification>,

View file

@ -38,9 +38,6 @@ internal class SendAmountReduceToConverter(
val isZero = if (amountTextField.isFiatValue) decimalFiatValue.isNullOrZero() else value.isZero()
return state.copyWrapped(
isEditState = isEditState,
sendState = state.sendState?.copy(
reduceAmountBy = value,
),
amountState = amountState.copy(
isPrimaryButtonEnabled = !isExceedBalance && !isZero,
amountTextField = amountTextField.copy(

View file

@ -17,7 +17,6 @@ internal class SendConfirmStateConverter(
txUrl = "",
ignoreAmountReduce = false,
reduceAmountBy = null,
reduceAmountTo = null,
isFromConfirmation = true,
showTapHelp = isTapHelpPreviewEnabledProvider(),
notifications = persistentListOf(),

View file

@ -60,18 +60,20 @@ internal class SendNotificationFactory(
val amountValue = amountState.amountTextField.cryptoAmount.value ?: BigDecimal.ZERO
val feeValue = feeState.fee?.amount?.value ?: BigDecimal.ZERO
val reduceAmountBy = sendState.reduceAmountBy ?: BigDecimal.ZERO
val isFeeCoverage = checkFeeCoverage(
isSubtractAvailable = isSubtractAvailableProvider(),
balance = balance,
amountValue = amountValue,
feeValue = feeValue,
reduceAmountBy = reduceAmountBy,
)
val sendingAmount = checkAndCalculateSubtractedAmount(
isAmountSubtractAvailable = isFeeCoverage,
cryptoCurrencyStatus = cryptoCurrencyStatusProvider(),
amountValue = amountValue,
feeValue = feeValue,
reduceAmountBy = sendState.reduceAmountBy,
reduceAmountBy = reduceAmountBy,
)
buildList {
// errors
@ -211,7 +213,7 @@ internal class SendNotificationFactory(
),
onConfirmClick = {
clickIntents.onAmountReduceClick(
reduceAmountBy = currencyDeposit,
reduceAmountBy = currencyDeposit.minus(diff),
clazz = SendNotification.Error.ExistentialDeposit::class.java,
)
},

View file

@ -16,7 +16,7 @@ internal fun checkAndCalculateSubtractedAmount(
cryptoCurrencyStatus: CryptoCurrencyStatus,
amountValue: BigDecimal,
feeValue: BigDecimal,
reduceAmountBy: BigDecimal?,
reduceAmountBy: BigDecimal,
): BigDecimal {
val balance = cryptoCurrencyStatus.value.amount ?: return amountValue
val isFeeCoverage = checkFeeCoverage(
@ -24,17 +24,12 @@ internal fun checkAndCalculateSubtractedAmount(
balance = balance,
amountValue = amountValue,
feeValue = feeValue,
reduceAmountBy = reduceAmountBy,
)
val subtractedAmount = calculateSubtractedAmount(
isFeeCoverage = isFeeCoverage,
cryptoCurrencyStatus = cryptoCurrencyStatus,
amountValue = amountValue,
feeValue = feeValue,
)
return if (reduceAmountBy != null) {
subtractedAmount.minus(reduceAmountBy)
return if (isFeeCoverage) {
balance.minus(reduceAmountBy).minus(feeValue)
} else {
subtractedAmount
amountValue.minus(reduceAmountBy)
}
}
@ -46,26 +41,11 @@ internal fun checkFeeCoverage(
balance: BigDecimal,
amountValue: BigDecimal,
feeValue: BigDecimal,
reduceAmountBy: BigDecimal?,
): Boolean {
if (!isSubtractAvailable) return false
return balance < amountValue + feeValue && balance > feeValue && balance >= amountValue
}
/**
* Calculates subtracted amount
*/
private fun calculateSubtractedAmount(
isFeeCoverage: Boolean,
cryptoCurrencyStatus: CryptoCurrencyStatus,
amountValue: BigDecimal,
feeValue: BigDecimal,
): BigDecimal {
val balance = cryptoCurrencyStatus.value.amount ?: return amountValue
return if (isFeeCoverage) {
minOf(amountValue, balance.minus(feeValue))
} else {
amountValue
}
val amountWithReduced = (reduceAmountBy ?: BigDecimal.ZERO) + amountValue
return balance < amountWithReduced + feeValue && balance > feeValue && balance >= amountWithReduced
}
/**

View file

@ -823,7 +823,7 @@ internal class SendViewModel @Inject constructor(
cryptoCurrencyStatus = cryptoCurrencyStatus,
amountValue = amountValue,
feeValue = feeValue,
reduceAmountBy = uiState.sendState?.reduceAmountBy,
reduceAmountBy = uiState.sendState?.reduceAmountBy ?: BigDecimal.ZERO,
)
viewModelScope.launch(dispatchers.main) {