Updated on 2026-08-14

This commit is contained in:
Tangem 2024-05-02 13:46:07 +05:00
parent 6acd8d98d1
commit 224f40c488
11 changed files with 160 additions and 67 deletions

View file

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

View file

@ -6,6 +6,7 @@ import com.tangem.features.send.impl.presentation.state.StateRouter
import com.tangem.features.send.impl.presentation.state.fields.SendAmountFieldChangeConverter
import com.tangem.features.send.impl.presentation.state.fields.SendAmountFieldMaxAmountConverter
import com.tangem.utils.Provider
import java.math.BigDecimal
/**
* Factory to produce amount state for [SendUiState]
@ -44,9 +45,18 @@ internal class AmountStateFactory(
currentStateProvider = currentStateProvider,
)
}
private val amountReducedConverter by lazy {
SendAmountReducedConverter(
stateRouterProvider = stateRouterProvider,
currentStateProvider = currentStateProvider,
cryptoCurrencyStatusProvider = cryptoCurrencyStatusProvider,
)
}
fun getOnAmountValueChange(value: String) = amountFieldChangeConverter.convert(value)
fun getOnAmountReducedState(reduceAmountBy: BigDecimal) = amountReducedConverter.convert(reduceAmountBy)
fun getOnMaxAmountClick(): SendUiState {
return amountFieldMaxAmountConverter.convert(Unit)
}

View file

@ -0,0 +1,59 @@
package com.tangem.features.send.impl.presentation.state.amount
import androidx.compose.ui.text.input.ImeAction
import com.tangem.common.extensions.isZero
import com.tangem.core.ui.utils.parseBigDecimal
import com.tangem.core.ui.utils.parseToBigDecimal
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.features.send.impl.presentation.state.fields.SendTextField
import java.math.BigDecimal
import java.math.RoundingMode
internal fun String.getCryptoValue(fiatRate: BigDecimal?, isFiatValue: Boolean, decimals: Int): String {
return if (isFiatValue && fiatRate != null) {
parseToBigDecimal(decimals).divide(fiatRate, decimals, RoundingMode.DOWN)
.parseBigDecimal(decimals)
} else {
this
}
}
internal fun String.getFiatValue(
fiatRate: BigDecimal?,
isFiatValue: Boolean,
decimals: Int,
): Pair<String, BigDecimal?> {
return if (fiatRate != null) {
val fiatValue = if (!isFiatValue) {
parseToBigDecimal(decimals).multiply(fiatRate).parseBigDecimal(decimals)
} else {
this
}
val decimalFiatValue = fiatValue.parseToBigDecimal(decimals)
fiatValue to decimalFiatValue
} else {
"" to null
}
}
internal fun String.checkExceedBalance(
cryptoCurrencyStatus: CryptoCurrencyStatus,
amountTextField: SendTextField.AmountField,
): Boolean {
val currencyCryptoAmount = cryptoCurrencyStatus.value.amount ?: BigDecimal.ZERO
val currencyFiatAmount = cryptoCurrencyStatus.value.fiatAmount ?: BigDecimal.ZERO
val fiatDecimal = parseToBigDecimal(amountTextField.fiatAmount.decimals)
val cryptoDecimal = parseToBigDecimal(amountTextField.cryptoAmount.decimals)
return if (amountTextField.isFiatValue) {
fiatDecimal > currencyFiatAmount
} else {
cryptoDecimal > currencyCryptoAmount
}
}
internal fun getKeyboardAction(isExceedBalance: Boolean, decimalCryptoValue: BigDecimal) =
if (!isExceedBalance && !decimalCryptoValue.isZero()) {
ImeAction.Done
} else {
ImeAction.None
}

View file

@ -0,0 +1,62 @@
package com.tangem.features.send.impl.presentation.state.amount
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.ui.text.input.KeyboardType
import com.tangem.common.extensions.isZero
import com.tangem.core.ui.utils.parseBigDecimal
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.features.send.impl.presentation.state.SendUiState
import com.tangem.features.send.impl.presentation.state.StateRouter
import com.tangem.utils.Provider
import com.tangem.utils.converter.Converter
import com.tangem.utils.isNullOrZero
import java.math.BigDecimal
internal class SendAmountReducedConverter(
private val stateRouterProvider: Provider<StateRouter>,
private val currentStateProvider: Provider<SendUiState>,
private val cryptoCurrencyStatusProvider: Provider<CryptoCurrencyStatus>,
) : Converter<BigDecimal, SendUiState> {
override fun convert(value: BigDecimal): SendUiState {
val state = currentStateProvider()
val cryptoCurrencyStatus = cryptoCurrencyStatusProvider()
val isEditState = stateRouterProvider().isEditState
val amountState = state.getAmountState(isEditState) ?: return state
val amountTextField = amountState.amountTextField
val cryptoDecimals = amountTextField.cryptoAmount.decimals
val fiatDecimals = amountTextField.fiatAmount.decimals
val amountValue = amountState.amountTextField.cryptoAmount.value ?: return state
val decimalCryptoValue = amountValue.minus(value)
val cryptoValue = decimalCryptoValue.parseBigDecimal(cryptoDecimals)
val (fiatValue, decimalFiatValue) = cryptoValue.getFiatValue(
fiatRate = cryptoCurrencyStatus.value.fiatRate,
isFiatValue = false,
decimals = fiatDecimals,
)
val checkValue = if (amountTextField.isFiatValue) fiatValue else cryptoValue
val isExceedBalance = checkValue.checkExceedBalance(cryptoCurrencyStatus, amountTextField)
val isZero = if (amountTextField.isFiatValue) decimalFiatValue.isNullOrZero() else decimalCryptoValue.isZero()
return state.copyWrapped(
isEditState = isEditState,
sendState = state.sendState?.copy(
reduceAmountBy = value,
),
amountState = amountState.copy(
isPrimaryButtonEnabled = !isExceedBalance && !isZero,
amountTextField = amountTextField.copy(
value = cryptoValue,
fiatValue = fiatValue,
isError = isExceedBalance,
cryptoAmount = amountTextField.cryptoAmount.copy(value = decimalCryptoValue),
fiatAmount = amountTextField.fiatAmount.copy(value = decimalFiatValue),
keyboardOptions = KeyboardOptions(
imeAction = getKeyboardAction(isExceedBalance, decimalCryptoValue),
keyboardType = KeyboardType.Number,
),
),
),
)
}
}

View file

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

View file

@ -93,6 +93,7 @@ internal class SendNotificationFactory(
return state.copy(
sendState = sendState.copy(
ignoreAmountReduce = isIgnored,
reduceAmountBy = if (isIgnored) null else sendState.reduceAmountBy,
notifications = updatedNotifications.toImmutableList(),
),
)
@ -225,8 +226,7 @@ internal class SendNotificationFactory(
SendNotification.Warning.HighFeeError(
amount = threshold.toPlainString(),
onConfirmClick = {
val reduceTo = sendAmount.minus(threshold)
clickIntents.onAmountReduceClick(reduceTo, SendNotification.Warning.HighFeeError::class.java)
clickIntents.onAmountReduceClick(threshold, SendNotification.Warning.HighFeeError::class.java)
},
onCloseClick = {
clickIntents.onNotificationCancel(SendNotification.Warning.HighFeeError::class.java)

View file

@ -1,13 +1,10 @@
package com.tangem.features.send.impl.presentation.state.fee
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.transaction.TransactionFee
import com.tangem.common.extensions.isZero
import com.tangem.core.ui.utils.parseBigDecimal
import com.tangem.core.ui.utils.parseToBigDecimal
import com.tangem.domain.common.extensions.minimalAmount
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.lib.crypto.BlockchainUtils
import java.math.BigDecimal
import java.math.RoundingMode
@ -19,6 +16,7 @@ internal fun checkAndCalculateSubtractedAmount(
cryptoCurrencyStatus: CryptoCurrencyStatus,
amountValue: BigDecimal,
feeValue: BigDecimal,
reduceAmountBy: BigDecimal?,
): BigDecimal {
val balance = cryptoCurrencyStatus.value.amount ?: return amountValue
val isFeeCoverage = checkFeeCoverage(
@ -27,12 +25,17 @@ internal fun checkAndCalculateSubtractedAmount(
amountValue = amountValue,
feeValue = feeValue,
)
return calculateSubtractedAmount(
val subtractedAmount = calculateSubtractedAmount(
isFeeCoverage = isFeeCoverage,
cryptoCurrencyStatus = cryptoCurrencyStatus,
amountValue = amountValue,
feeValue = feeValue,
)
return if (reduceAmountBy != null) {
subtractedAmount.minus(reduceAmountBy)
} else {
subtractedAmount
}
}
/**
@ -59,12 +62,7 @@ internal fun calculateSubtractedAmount(
): BigDecimal {
val balance = cryptoCurrencyStatus.value.amount ?: return amountValue
return if (isFeeCoverage) {
var subtractedValue = minOf(amountValue, balance.minus(feeValue))
if (BlockchainUtils.isTezos(cryptoCurrencyStatus.currency.network.id.value)) {
val threshold = Blockchain.Tezos.minimalAmount()
subtractedValue = -threshold
}
subtractedValue
minOf(amountValue, balance.minus(feeValue))
} else {
amountValue
}

View file

@ -1,19 +1,20 @@
package com.tangem.features.send.impl.presentation.state.fields
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardType
import com.tangem.common.extensions.isZero
import com.tangem.core.ui.utils.parseBigDecimal
import com.tangem.core.ui.utils.parseToBigDecimal
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.features.send.impl.presentation.state.SendUiState
import com.tangem.features.send.impl.presentation.state.StateRouter
import com.tangem.features.send.impl.presentation.state.amount.checkExceedBalance
import com.tangem.features.send.impl.presentation.state.amount.getCryptoValue
import com.tangem.features.send.impl.presentation.state.amount.getFiatValue
import com.tangem.features.send.impl.presentation.state.amount.getKeyboardAction
import com.tangem.utils.Provider
import com.tangem.utils.converter.Converter
import com.tangem.utils.isNullOrZero
import java.math.BigDecimal
import java.math.RoundingMode
internal class SendAmountFieldChangeConverter(
private val stateRouterProvider: Provider<StateRouter>,
@ -22,6 +23,7 @@ internal class SendAmountFieldChangeConverter(
) : Converter<String, SendUiState> {
override fun convert(value: String): SendUiState {
val state = currentStateProvider()
val cryptoCurrencyStatus = cryptoCurrencyStatusProvider()
val isEditState = stateRouterProvider().isEditState
val amountState = state.getAmountState(isEditState) ?: return state
val amountTextField = amountState.amountTextField
@ -31,15 +33,20 @@ internal class SendAmountFieldChangeConverter(
val fiatDecimals = amountTextField.fiatAmount.decimals
val trimmedValue = value.trim()
val cryptoValue = trimmedValue.getCryptoValue(amountTextField.isFiatValue, cryptoDecimals)
val cryptoValue = trimmedValue.getCryptoValue(
fiatRate = cryptoCurrencyStatus.value.fiatRate,
isFiatValue = amountTextField.isFiatValue,
decimals = cryptoDecimals,
)
val decimalCryptoValue = cryptoValue.parseToBigDecimal(cryptoDecimals)
val (fiatValue, decimalFiatValue) = trimmedValue.getFiatValue(
fiatRate = cryptoCurrencyStatus.value.fiatRate,
isFiatValue = amountTextField.isFiatValue,
decimals = fiatDecimals,
)
val checkValue = if (amountTextField.isFiatValue) fiatValue else cryptoValue
val isExceedBalance = checkValue.checkExceedBalance(amountTextField)
val isExceedBalance = checkValue.checkExceedBalance(cryptoCurrencyStatus, amountTextField)
val isZero = if (amountTextField.isFiatValue) decimalFiatValue.isNullOrZero() else decimalCryptoValue.isZero()
return state.copyWrapped(
isEditState = isEditState,
@ -60,32 +67,6 @@ internal class SendAmountFieldChangeConverter(
)
}
private fun String.getCryptoValue(isFiatValue: Boolean, decimals: Int): String {
val cryptoCurrencyStatus = cryptoCurrencyStatusProvider()
val fiatRate = cryptoCurrencyStatus.value.fiatRate
return if (isFiatValue && fiatRate != null) {
parseToBigDecimal(decimals).divide(fiatRate, decimals, RoundingMode.DOWN)
.parseBigDecimal(decimals)
} else {
this
}
}
private fun String.getFiatValue(isFiatValue: Boolean, decimals: Int): Pair<String, BigDecimal?> {
val fiatRate = cryptoCurrencyStatusProvider().value.fiatRate
return if (fiatRate != null) {
val fiatValue = if (!isFiatValue) {
parseToBigDecimal(decimals).multiply(fiatRate).parseBigDecimal(decimals)
} else {
this
}
val decimalFiatValue = fiatValue.parseToBigDecimal(decimals)
fiatValue to decimalFiatValue
} else {
"" to null
}
}
private fun SendUiState.emptyState(): SendUiState {
val isEditState = stateRouterProvider().isEditState
val amountState = getAmountState(isEditState) ?: return this
@ -104,24 +85,4 @@ internal class SendAmountFieldChangeConverter(
),
)
}
private fun String.checkExceedBalance(amountTextField: SendTextField.AmountField): Boolean {
val cryptoCurrencyStatus = cryptoCurrencyStatusProvider()
val currencyCryptoAmount = cryptoCurrencyStatus.value.amount ?: BigDecimal.ZERO
val currencyFiatAmount = cryptoCurrencyStatus.value.fiatAmount ?: BigDecimal.ZERO
val fiatDecimal = parseToBigDecimal(amountTextField.fiatAmount.decimals)
val cryptoDecimal = parseToBigDecimal(amountTextField.cryptoAmount.decimals)
return if (amountTextField.isFiatValue) {
fiatDecimal > currencyFiatAmount
} else {
cryptoDecimal > currencyCryptoAmount
}
}
private fun getKeyboardAction(isExceedBalance: Boolean, decimalCryptoValue: BigDecimal) =
if (!isExceedBalance && !decimalCryptoValue.isZero()) {
ImeAction.Done
} else {
ImeAction.None
}
}

View file

@ -60,7 +60,7 @@ internal object SendClickIntentsStub : SendClickIntents {
override fun onShareClick() {}
override fun onAmountReduceClick(reducedAmount: BigDecimal, clazz: Class<out SendNotification>) {}
override fun onAmountReduceClick(reduceAmountBy: BigDecimal, clazz: Class<out SendNotification>) {}
override fun onNotificationCancel(clazz: Class<out SendNotification>) {}
}

View file

@ -67,7 +67,7 @@ internal interface SendClickIntents {
fun onShareClick()
fun onAmountReduceClick(reducedAmount: BigDecimal, clazz: Class<out SendNotification>)
fun onAmountReduceClick(reduceAmountBy: BigDecimal, clazz: Class<out SendNotification>)
fun onNotificationCancel(clazz: Class<out SendNotification>)
// endregion

View file

@ -797,8 +797,8 @@ internal class SendViewModel @Inject constructor(
analyticsEventHandler.send(SendAnalyticEvents.ShareButtonClicked)
}
override fun onAmountReduceClick(reducedAmount: BigDecimal, clazz: Class<out SendNotification>) {
uiState = amountStateFactory.getOnAmountValueChange(reducedAmount.parseBigDecimal(cryptoCurrency.decimals))
override fun onAmountReduceClick(reduceAmountBy: BigDecimal, clazz: Class<out SendNotification>) {
uiState = amountStateFactory.getOnAmountReducedState(reduceAmountBy)
uiState = sendNotificationFactory.dismissNotificationState(clazz)
feeReload()
}
@ -820,6 +820,7 @@ internal class SendViewModel @Inject constructor(
cryptoCurrencyStatus = cryptoCurrencyStatus,
amountValue = amountValue,
feeValue = feeValue,
reduceAmountBy = uiState.sendState?.reduceAmountBy,
)
viewModelScope.launch(dispatchers.main) {