Updated on 2026-08-14

This commit is contained in:
Tangem 2024-04-08 13:05:29 +05:00
commit 15ba41b158
8 changed files with 54 additions and 44 deletions

View file

@ -7,7 +7,6 @@ import androidx.compose.ui.text.input.VisualTransformation
import com.tangem.core.ui.utils.BigDecimalFormatter
import com.tangem.core.ui.utils.defaultFormat
import com.tangem.core.ui.utils.formatWithThousands
import com.tangem.core.ui.utils.parseToBigDecimal
import java.text.DecimalFormat
class AmountVisualTransformation(
@ -23,21 +22,16 @@ class AmountVisualTransformation(
decimals,
)
formattedAmount = formattedAmount.ifEmpty { decimalFormat.defaultFormat() }
val decimalValue = text.text.parseToBigDecimal(decimals)
val formattedText = if (formattedAmount.isNotEmpty() && symbol != null) {
AnnotatedString(
if (currencyCode != null) {
BigDecimalFormatter.formatFiatAmount(
fiatAmount = decimalValue,
BigDecimalFormatter.formatFiatEditableAmount(
fiatAmount = formattedAmount,
fiatCurrencyCode = currencyCode,
fiatCurrencySymbol = symbol,
)
} else {
BigDecimalFormatter.formatCryptoAmountUncapped(
cryptoAmount = decimalValue,
cryptoSymbol = symbol,
decimals = decimals,
)
BigDecimalFormatter.formatWithSymbol(formattedAmount, symbol)
},
)
} else {

View file

@ -1,8 +1,10 @@
package com.tangem.core.ui.utils
import com.tangem.domain.tokens.model.CryptoCurrency
import timber.log.Timber
import java.math.BigDecimal
import java.math.RoundingMode
import java.text.DecimalFormat
import java.text.NumberFormat
import java.util.Currency
import java.util.Locale
@ -38,30 +40,6 @@ object BigDecimalFormatter {
}
}
fun formatCryptoAmountUncapped(
cryptoAmount: BigDecimal?,
cryptoSymbol: String,
decimals: Int,
locale: Locale = Locale.getDefault(),
): String {
if (cryptoAmount == null) return EMPTY_BALANCE_SIGN
val formatter = NumberFormat.getNumberInstance(locale).apply {
maximumFractionDigits = decimals
minimumFractionDigits = minOf(2, decimals)
isGroupingUsed = true
roundingMode = RoundingMode.DOWN
}
return formatter.format(cryptoAmount).let {
if (cryptoSymbol.isEmpty()) {
it
} else {
it + "\u2009$cryptoSymbol"
}
}
}
fun formatCryptoAmount(
cryptoAmount: BigDecimal?,
cryptoCurrency: CryptoCurrency,
@ -90,6 +68,26 @@ object BigDecimalFormatter {
.replace(formatterCurrency.getSymbol(locale), fiatCurrencySymbol)
}
fun formatFiatEditableAmount(
fiatAmount: String?,
fiatCurrencyCode: String,
fiatCurrencySymbol: String,
locale: Locale = Locale.getDefault(),
): String {
if (fiatAmount == null) return EMPTY_BALANCE_SIGN
val formatterCurrency = getCurrency(fiatCurrencyCode)
val numberFormatter = NumberFormat.getCurrencyInstance(locale).apply {
currency = formatterCurrency
}
val formatter = requireNotNull(numberFormatter as? DecimalFormat) {
Timber.e("NumberFormat is null")
return EMPTY_BALANCE_SIGN
}
return "${formatter.positivePrefix}$fiatAmount${formatter.positiveSuffix}"
.replace(formatterCurrency.getSymbol(locale), fiatCurrencySymbol)
}
fun formatPercent(
percent: BigDecimal,
useAbsoluteValue: Boolean,
@ -107,6 +105,8 @@ object BigDecimalFormatter {
return formatter.format(value)
}
fun formatWithSymbol(amount: String, symbol: String) = "$amount\u2009$symbol"
private fun getCurrency(code: String): Currency {
return runCatching { Currency.getInstance(code) }
.getOrElse { e ->

View file

@ -77,6 +77,7 @@ internal sealed class SendStates {
val rate: BigDecimal?,
val appCurrency: AppCurrency,
val isFeeApproximate: Boolean,
val isCustomSelected: Boolean,
val notifications: ImmutableList<SendNotification>,
) : SendStates()

View file

@ -56,18 +56,26 @@ internal class FeeStateFactory(
fun onFeeOnLoadedState(fees: TransactionFee): SendUiState {
val state = currentStateProvider()
val feeState = state.feeState ?: return state
val feeSelectorState = (feeState.feeSelectorState as? FeeSelectorState.Content)?.copy(
val feeSelectorState = feeState.feeSelectorState as? FeeSelectorState.Content
val isCustomWasSelected = if (feeState.isCustomSelected) {
feeSelectorState?.customValues ?: persistentListOf()
} else {
customFeeFieldConverter.convert(fees.normal)
}
val updatedFeeSelectorState = feeSelectorState?.copy(
fees = fees,
customValues = isCustomWasSelected,
) ?: FeeSelectorState.Content(
fees = fees,
customValues = customFeeFieldConverter.convert(fees.normal),
)
val fee = feeConverter.convert(feeSelectorState)
val fee = feeConverter.convert(updatedFeeSelectorState)
return state.copy(
amountState = state.amountState?.copy(isFeeLoading = false),
feeState = feeState.copy(
feeSelectorState = feeSelectorState,
feeSelectorState = updatedFeeSelectorState,
fee = fee,
isFeeApproximate = isFeeApproximate(fee),
),
@ -91,9 +99,11 @@ internal class FeeStateFactory(
val updatedFeeSelectorState = feeSelectorState.copy(selectedFee = feeType)
val fee = feeConverter.convert(updatedFeeSelectorState)
val isCustomFeeWasSelected = feeState.isCustomSelected || updatedFeeSelectorState.selectedFee == FeeType.Custom
return state.copy(
feeState = feeState.copy(
fee = fee,
isCustomSelected = isCustomFeeWasSelected,
feeSelectorState = updatedFeeSelectorState,
),
)

View file

@ -20,6 +20,7 @@ internal class SendFeeStateConverter(
rate = feeCryptoCurrencyStatusProvider().value.fiatRate,
appCurrency = appCurrencyProvider(),
isFeeApproximate = false,
isCustomSelected = false,
)
}
}

View file

@ -45,7 +45,7 @@ internal class SendAmountFieldChangeConverter(
cryptoAmount = amountTextField.cryptoAmount.copy(value = decimalCryptoValue),
fiatAmount = amountTextField.fiatAmount.copy(value = decimalFiatValue),
keyboardOptions = KeyboardOptions(
imeAction = if (!isExceedBalance) ImeAction.Done else ImeAction.None,
imeAction = getKeyboardAction(isExceedBalance, decimalCryptoValue),
keyboardType = KeyboardType.Number,
),
),
@ -103,4 +103,11 @@ internal class SendAmountFieldChangeConverter(
cryptoDecimal > currencyCryptoAmount
}
}
private fun getKeyboardAction(isExceedBalance: Boolean, decimalCryptoValue: BigDecimal) =
if (!isExceedBalance && !decimalCryptoValue.isZero()) {
ImeAction.Done
} else {
ImeAction.None
}
}

View file

@ -48,6 +48,7 @@ internal object FeeStatePreviewData {
),
isFeeApproximate = false,
notifications = persistentListOf(),
isCustomSelected = false,
)
val errorFeeState = feeState.copy(

View file

@ -30,13 +30,9 @@ internal fun AmountBlock(
) {
val amount = amountState.amountTextField
val cryptoAmount = BigDecimalFormatter.formatCryptoAmount(
cryptoAmount = amount.cryptoAmount.value,
cryptoCurrency = amount.cryptoAmount.currencySymbol,
decimals = amount.cryptoAmount.decimals,
)
val fiatAmount = BigDecimalFormatter.formatFiatAmount(
fiatAmount = amount.fiatAmount.value,
val cryptoAmount = BigDecimalFormatter.formatWithSymbol(amount.value, amount.cryptoAmount.currencySymbol)
val fiatAmount = BigDecimalFormatter.formatFiatEditableAmount(
fiatAmount = amount.fiatValue,
fiatCurrencySymbol = amount.fiatAmount.currencySymbol,
fiatCurrencyCode = amountState.appCurrencyCode,
)