Updated on 2026-08-14

This commit is contained in:
Tangem 2024-04-27 14:55:50 +05:00
commit 4098d61678
10 changed files with 118 additions and 95 deletions

View file

@ -73,7 +73,7 @@ fun SelectorRowItem(
color = TangemTheme.colors.text.primary1,
modifier = Modifier.padding(start = TangemTheme.dimens.spacing8),
)
if (preDot != null && postDot != null) {
if (preDot != null) {
SelectorValueContent(
preDot = preDot,
postDot = postDot,
@ -97,7 +97,7 @@ fun SelectorRowItem(
@Composable
private fun RowScope.SelectorValueContent(
preDot: TextReference,
postDot: TextReference,
postDot: TextReference?,
ellipsizeOffset: Int? = null,
) {
val ellipsis = if (ellipsizeOffset == null) {
@ -115,18 +115,20 @@ private fun RowScope.SelectorValueContent(
.weight(1f)
.padding(start = TangemTheme.dimens.spacing4),
)
Text(
text = "",
style = TangemTheme.typography.caption2,
color = TangemTheme.colors.text.primary1,
textAlign = TextAlign.Center,
modifier = Modifier.padding(horizontal = TangemTheme.dimens.spacing4),
)
Text(
text = postDot.resolveReference(),
style = TangemTheme.typography.body2,
color = TangemTheme.colors.text.tertiary,
)
if (postDot != null) {
Text(
text = "",
style = TangemTheme.typography.caption2,
color = TangemTheme.colors.text.primary1,
textAlign = TextAlign.Center,
modifier = Modifier.padding(horizontal = TangemTheme.dimens.spacing4),
)
Text(
text = postDot.resolveReference(),
style = TangemTheme.typography.body2,
color = TangemTheme.colors.text.tertiary,
)
}
}
@Preview

View file

@ -2,6 +2,7 @@ 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
@ -95,5 +96,12 @@ internal fun checkIfFeeTooHigh(feeSelectorState: FeeSelectorState.Content, onSho
return isShow
}
/**
* Checks if fee exceeds fee paid currency balance
*/
fun checkExceedBalance(feeBalance: BigDecimal?, feeAmount: BigDecimal?): Boolean {
return feeAmount == null || feeBalance == null || feeAmount.isZero() || feeAmount > feeBalance
}
private val FEE_MAX_DIFF = BigDecimal("5")
private const val ZERO_DECIMALS = 0

View file

@ -5,18 +5,16 @@ import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardType
import com.tangem.blockchain.common.transaction.Fee
import com.tangem.common.extensions.isZero
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.utils.BigDecimalFormatter
import com.tangem.core.ui.utils.parseBigDecimal
import com.tangem.core.ui.utils.parseToBigDecimal
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.features.send.impl.R
import com.tangem.features.send.impl.presentation.state.StateRouter
import com.tangem.features.send.impl.presentation.state.fee.checkExceedBalance
import com.tangem.features.send.impl.presentation.state.fields.SendTextField
import com.tangem.features.send.impl.presentation.utils.getFiatReference
import com.tangem.features.send.impl.presentation.viewmodel.SendClickIntents
import com.tangem.lib.crypto.BlockchainUtils.isBitcoin
import com.tangem.utils.Provider
@ -35,6 +33,7 @@ internal class BitcoinCustomFeeConverter(
override fun convert(value: Fee.Bitcoin): ImmutableList<SendTextField.CustomFee> {
val feeValue = value.amount.value
val feeCurrency = feeCryptoCurrencyStatusProvider()?.value
val network = feeCryptoCurrencyStatusProvider()?.currency?.network?.id?.value
return if (network != null && isBitcoin(network)) {
persistentListOf(
@ -49,7 +48,11 @@ internal class BitcoinCustomFeeConverter(
),
title = resourceReference(R.string.send_max_fee),
footer = resourceReference(R.string.send_max_fee_footer),
label = getFeeFormatted(feeValue),
label = getFiatReference(
rate = feeCurrency?.fiatRate,
value = feeValue,
appCurrency = appCurrencyProvider(),
),
keyboardActions = KeyboardActions(),
isReadonly = true,
),
@ -65,7 +68,15 @@ internal class BitcoinCustomFeeConverter(
footer = resourceReference(R.string.send_satoshi_per_byte_text),
onValueChange = { clickIntents.onCustomFeeValueChange(FEE_SATOSHI_INDEX, it) },
keyboardOptions = KeyboardOptions(
imeAction = if (checkExceedBalance(feeValue)) ImeAction.None else ImeAction.Done,
imeAction = if (checkExceedBalance(
feeBalance = feeCurrency?.amount,
feeAmount = feeValue,
)
) {
ImeAction.None
} else {
ImeAction.Done
},
keyboardType = KeyboardType.Number,
),
keyboardActions = KeyboardActions(
@ -104,7 +115,11 @@ internal class BitcoinCustomFeeConverter(
FEE_AMOUNT_INDEX,
this[FEE_AMOUNT_INDEX].copy(
value = newFeeAmount.parseBigDecimal(this[FEE_AMOUNT_INDEX].decimals),
label = getFeeFormatted(newFeeAmount),
label = getFiatReference(
rate = feeCryptoCurrencyStatusProvider()?.value?.fiatRate,
value = newFeeAmount,
appCurrency = appCurrencyProvider(),
),
),
)
set(index, this[index].copy(value = value))
@ -112,26 +127,6 @@ internal class BitcoinCustomFeeConverter(
}.toImmutableList()
}
private fun getFeeFormatted(fee: BigDecimal?): TextReference {
val appCurrency = appCurrencyProvider()
val rate = feeCryptoCurrencyStatusProvider()?.value?.fiatRate
val fiatFee = rate?.let { fee?.multiply(it) }
return stringReference(
BigDecimalFormatter.formatFiatAmount(
fiatAmount = fiatFee,
fiatCurrencyCode = appCurrency.code,
fiatCurrencySymbol = appCurrency.symbol,
),
)
}
private fun checkExceedBalance(feeAmount: BigDecimal?): Boolean {
val cryptoCurrencyStatus = feeCryptoCurrencyStatusProvider()
val currencyCryptoAmount = cryptoCurrencyStatus?.value?.amount ?: BigDecimal.ZERO
return feeAmount == null || feeAmount.isZero() || feeAmount > currencyCryptoAmount
}
private fun toSatoshiPerByte(amount: BigDecimal?, decimals: Int, txSize: BigDecimal): BigDecimal? {
val newFeeAmount = amount?.movePointRight(decimals)
return newFeeAmount?.divide(

View file

@ -5,24 +5,21 @@ import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardType
import com.tangem.blockchain.common.transaction.Fee
import com.tangem.common.extensions.isZero
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.utils.BigDecimalFormatter
import com.tangem.core.ui.utils.parseBigDecimal
import com.tangem.core.ui.utils.parseToBigDecimal
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.features.send.impl.R
import com.tangem.features.send.impl.presentation.state.StateRouter
import com.tangem.features.send.impl.presentation.state.fee.checkExceedBalance
import com.tangem.features.send.impl.presentation.state.fields.SendTextField
import com.tangem.features.send.impl.presentation.utils.getFiatReference
import com.tangem.features.send.impl.presentation.viewmodel.SendClickIntents
import com.tangem.utils.Provider
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList
import java.math.BigDecimal
import java.math.RoundingMode
internal class EthereumCustomFeeConverter(
@ -34,6 +31,7 @@ internal class EthereumCustomFeeConverter(
override fun convert(value: Fee.Ethereum): ImmutableList<SendTextField.CustomFee> {
val feeValue = value.amount.value
val feeCurrency = feeCryptoCurrencyStatusProvider()?.value
return persistentListOf(
SendTextField.CustomFee(
value = feeValue?.parseBigDecimal(value.amount.decimals).orEmpty(),
@ -46,7 +44,11 @@ internal class EthereumCustomFeeConverter(
),
title = resourceReference(R.string.send_max_fee),
footer = resourceReference(R.string.send_max_fee_footer),
label = getFeeFormatted(feeValue),
label = getFiatReference(
rate = feeCurrency?.fiatRate,
value = feeValue,
appCurrency = appCurrencyProvider(),
),
keyboardActions = KeyboardActions(),
),
SendTextField.CustomFee(
@ -70,7 +72,16 @@ internal class EthereumCustomFeeConverter(
footer = resourceReference(R.string.send_gas_limit_footer),
onValueChange = { clickIntents.onCustomFeeValueChange(GAS_LIMIT, it) },
keyboardOptions = KeyboardOptions(
imeAction = if (checkExceedBalance(feeValue)) ImeAction.None else ImeAction.Done,
imeAction = if (
checkExceedBalance(
feeBalance = feeCurrency?.amount,
feeAmount = feeValue,
)
) {
ImeAction.None
} else {
ImeAction.Done
},
keyboardType = KeyboardType.Number,
),
keyboardActions = KeyboardActions(
@ -106,26 +117,6 @@ internal class EthereumCustomFeeConverter(
}.toImmutableList()
}
private fun getFeeFormatted(fee: BigDecimal?): TextReference {
val appCurrency = appCurrencyProvider()
val rate = feeCryptoCurrencyStatusProvider()?.value?.fiatRate
val fiatFee = rate?.let { fee?.multiply(it) }
return stringReference(
BigDecimalFormatter.formatFiatAmount(
fiatAmount = fiatFee,
fiatCurrencyCode = appCurrency.code,
fiatCurrencySymbol = appCurrency.symbol,
),
)
}
private fun checkExceedBalance(feeAmount: BigDecimal?): Boolean {
val cryptoCurrencyStatus = feeCryptoCurrencyStatusProvider()
val currencyCryptoAmount = cryptoCurrencyStatus?.value?.amount ?: BigDecimal.ZERO
return feeAmount == null || feeAmount.isZero() || feeAmount > currencyCryptoAmount
}
private fun MutableList<SendTextField.CustomFee>.setEmpty(index: Int) {
set(index, this[index].copy(value = ""))
}
@ -144,7 +135,11 @@ internal class EthereumCustomFeeConverter(
index,
this[index].copy(
value = value,
label = getFeeFormatted(newFeeAmountDecimal),
label = getFiatReference(
rate = feeCryptoCurrencyStatusProvider()?.value?.fiatRate,
value = newFeeAmountDecimal,
appCurrency = appCurrencyProvider(),
),
),
)
}
@ -163,7 +158,11 @@ internal class EthereumCustomFeeConverter(
FEE_AMOUNT,
this[FEE_AMOUNT].copy(
value = newFeeAmount.parseBigDecimal(this[FEE_AMOUNT].decimals),
label = getFeeFormatted(newFeeAmount),
label = getFiatReference(
rate = feeCryptoCurrencyStatusProvider()?.value?.fiatRate,
value = newFeeAmount,
appCurrency = appCurrencyProvider(),
),
),
)
set(index, this[index].copy(value = value))
@ -183,15 +182,23 @@ internal class EthereumCustomFeeConverter(
FEE_AMOUNT,
this[FEE_AMOUNT].copy(
value = newFeeAmount.parseBigDecimal(this[FEE_AMOUNT].decimals),
label = getFeeFormatted(newFeeAmount),
label = getFiatReference(
rate = feeCryptoCurrencyStatusProvider()?.value?.fiatRate,
value = newFeeAmount,
appCurrency = appCurrencyProvider(),
),
),
)
val isNotExceedBalance = checkExceedBalance(
feeBalance = feeCryptoCurrencyStatusProvider()?.value?.amount,
feeAmount = newFeeAmount,
)
set(
index,
this[index].copy(
value = value,
keyboardOptions = KeyboardOptions(
imeAction = if (!checkExceedBalance(newFeeAmount)) ImeAction.None else ImeAction.Done,
imeAction = if (!isNotExceedBalance) ImeAction.None else ImeAction.Done,
keyboardType = KeyboardType.Number,
),
),

View file

@ -11,6 +11,7 @@ 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
import java.math.RoundingMode
@ -31,13 +32,15 @@ internal class SendAmountFieldChangeConverter(
val trimmedValue = value.trim()
val cryptoValue = trimmedValue.getCryptoValue(amountTextField.isFiatValue, cryptoDecimals)
val fiatValue = trimmedValue.getFiatValue(amountTextField.isFiatValue, fiatDecimals)
val decimalCryptoValue = cryptoValue.parseToBigDecimal(cryptoDecimals)
val decimalFiatValue = fiatValue.parseToBigDecimal(fiatDecimals)
val (fiatValue, decimalFiatValue) = trimmedValue.getFiatValue(
isFiatValue = amountTextField.isFiatValue,
decimals = fiatDecimals,
)
val checkValue = if (amountTextField.isFiatValue) fiatValue else cryptoValue
val isExceedBalance = checkValue.checkExceedBalance(amountTextField)
val isZero = if (amountTextField.isFiatValue) decimalFiatValue.isZero() else decimalCryptoValue.isZero()
val isZero = if (amountTextField.isFiatValue) decimalFiatValue.isNullOrZero() else decimalCryptoValue.isZero()
return state.copyWrapped(
isEditState = isEditState,
amountState = amountState.copy(
@ -68,13 +71,18 @@ internal class SendAmountFieldChangeConverter(
}
}
private fun String.getFiatValue(isFiatValue: Boolean, decimals: Int): String {
val cryptoCurrencyStatus = cryptoCurrencyStatusProvider()
val fiatRate = cryptoCurrencyStatus.value.fiatRate
return if (!isFiatValue && fiatRate != null) {
parseToBigDecimal(decimals).multiply(fiatRate).parseBigDecimal(decimals)
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 {
this
"" to null
}
}

View file

@ -18,6 +18,7 @@ import com.tangem.features.send.impl.presentation.state.StateRouter
import com.tangem.features.send.impl.presentation.viewmodel.SendClickIntents
import com.tangem.utils.Provider
import com.tangem.utils.converter.Converter
import com.tangem.utils.isNullOrZero
import java.math.BigDecimal
private const val FIAT_DECIMALS = 2
@ -34,12 +35,14 @@ internal class SendAmountFieldConverter(
val cryptoDecimal = value.toBigDecimalOrDefault()
val cryptoAmount = cryptoDecimal.convertToAmount(cryptoCurrencyStatus.currency)
val fiatRate = cryptoCurrencyStatus.value.fiatRate
val (fiatValue, fiatDecimal) = if (value.isEmpty()) {
"" to BigDecimal.ZERO
} else {
val fiatDecimal = fiatRate?.multiply(cryptoDecimal) ?: BigDecimal.ZERO
val fiatValue = fiatDecimal.parseBigDecimal(FIAT_DECIMALS)
fiatValue to fiatDecimal
val (fiatValue, fiatDecimal) = when {
fiatRate.isNullOrZero() -> "" to null
value.isEmpty() -> "" to BigDecimal.ZERO
else -> {
val fiatDecimal = fiatRate?.multiply(cryptoDecimal)
val fiatValue = fiatDecimal?.parseBigDecimal(FIAT_DECIMALS).orEmpty()
fiatValue to fiatDecimal
}
}
val isDoneActionEnabled = !cryptoDecimal.isZero()
return SendTextField.AmountField(
@ -64,7 +67,7 @@ internal class SendAmountFieldConverter(
)
}
private fun getAppCurrencyAmount(fiatValue: BigDecimal, appCurrency: AppCurrency) = Amount(
private fun getAppCurrencyAmount(fiatValue: BigDecimal?, appCurrency: AppCurrency) = Amount(
currencySymbol = appCurrency.symbol,
value = fiatValue,
decimals = FIAT_DECIMALS,

View file

@ -54,7 +54,7 @@ internal fun SendSpeedSelectorItem(
onSelect = onSelect,
modifier = modifier,
preDot = getCryptoReference(amount, state.isFeeApproximate),
postDot = getFiatReference(amount, state.rate, state.appCurrency),
postDot = getFiatReference(amount?.value, state.rate, state.appCurrency),
ellipsizeOffset = amount?.currencySymbol?.length,
isSelected = content?.selectedFee == feeType,
showDivider = showDivider,

View file

@ -31,8 +31,8 @@ internal fun AmountBlock(
val amount = amountState.amountTextField
val cryptoAmount = BigDecimalFormatter.formatWithSymbol(amount.value, amount.cryptoAmount.currencySymbol)
val fiatAmount = BigDecimalFormatter.formatFiatEditableAmount(
fiatAmount = amount.fiatValue,
val fiatAmount = BigDecimalFormatter.formatFiatAmount(
fiatAmount = amount.fiatAmount.value,
fiatCurrencySymbol = amount.fiatAmount.currencySymbol,
fiatCurrencyCode = amountState.appCurrencyCode,
)

View file

@ -60,7 +60,7 @@ internal fun FeeBlock(feeState: SendStates.FeeState, isSuccess: Boolean, onClick
titleRes = title,
iconRes = icon,
preDot = getCryptoReference(feeAmount, feeState.isFeeApproximate),
postDot = feeAmount?.let { getFiatReference(it, feeState.rate, feeState.appCurrency) },
postDot = getFiatReference(feeAmount?.value, feeState.rate, feeState.appCurrency),
ellipsizeOffset = feeAmount?.currencySymbol?.length,
isSelected = true,
showDivider = false,

View file

@ -22,11 +22,11 @@ internal fun getCryptoReference(amount: Amount?, isFeeApproximate: Boolean): Tex
)
}
internal fun getFiatReference(amount: Amount?, rate: BigDecimal?, appCurrency: AppCurrency): TextReference? {
if (amount == null) return null
internal fun getFiatReference(value: BigDecimal?, rate: BigDecimal?, appCurrency: AppCurrency): TextReference? {
if (value == null || rate == null) return null
return stringReference(
BigDecimalFormatter.formatFiatAmount(
fiatAmount = rate?.let { amount.value?.multiply(it) },
fiatAmount = value.multiply(rate),
fiatCurrencyCode = appCurrency.code,
fiatCurrencySymbol = appCurrency.symbol,
),