From 7945ad6c5787f5dc3c9e4a1e731c281ac917960f Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 14 Feb 2023 16:38:34 +0300 Subject: [PATCH] Updated on 2026-08-14 --- .../tangem/core/ui/utils/InputFormatting.kt | 29 -------- .../core/ui/utils/InputNumberFormatter.kt | 68 +++++++++++++++++++ .../java/com/tangem/utils/FormatExtensions.kt | 62 +++++------------ .../feature/swap/domain/SwapInteractorImpl.kt | 8 ++- .../feature/swap/models/SwapStateHolder.kt | 4 +- .../feature/swap/ui/AutosizeTextField.kt | 13 ++-- .../tangem/feature/swap/ui/StateBuilder.kt | 23 ++++--- .../feature/swap/ui/SwapScreenContent.kt | 9 +-- .../tangem/feature/swap/ui/TransactionCard.kt | 15 ++-- .../feature/swap/viewmodels/SwapViewModel.kt | 24 +++++-- 10 files changed, 146 insertions(+), 109 deletions(-) delete mode 100644 core/ui/src/main/java/com/tangem/core/ui/utils/InputFormatting.kt create mode 100644 core/ui/src/main/java/com/tangem/core/ui/utils/InputNumberFormatter.kt diff --git a/core/ui/src/main/java/com/tangem/core/ui/utils/InputFormatting.kt b/core/ui/src/main/java/com/tangem/core/ui/utils/InputFormatting.kt deleted file mode 100644 index e588b3585c..0000000000 --- a/core/ui/src/main/java/com/tangem/core/ui/utils/InputFormatting.kt +++ /dev/null @@ -1,29 +0,0 @@ -package com.tangem.core.ui.utils - -/** - * Formats input [String] for InputField, to remove wrong symbols, letters etc - * Use [decimals] for cut this number symbols after floating point - * - * Example (with 8 decimals): - * input string - ab123.46377372ab53 - * result string 123.46377372 - */ -fun getValidatedNumberWithFixedDecimals(text: String, decimals: Int): String { - val comma = ',' - val dot = '.' - val filteredChars = text.replace(comma, dot).filterIndexed { index, c -> - val isOneOrZeroPoint = c == dot && index != 0 && text.count { it == dot } <= 1 - val isIndexPointIndex = c == dot && index != 0 && text.indexOf(dot) == index - c.isDigit() || isIndexPointIndex || isOneOrZeroPoint - } - // If dot is present, take first 3 digits before decimal and first decimals digits after decimal - return if (filteredChars.count { it == dot } == 1) { - val beforeDecimal = filteredChars.substringBefore(dot) - val afterDecimal = filteredChars.substringAfter(dot) - beforeDecimal + dot + afterDecimal.take(decimals) - } - // If there is no dot, just take all digits - else { - filteredChars - } -} \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/utils/InputNumberFormatter.kt b/core/ui/src/main/java/com/tangem/core/ui/utils/InputNumberFormatter.kt new file mode 100644 index 0000000000..988e827efd --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/utils/InputNumberFormatter.kt @@ -0,0 +1,68 @@ +package com.tangem.core.ui.utils + +import java.text.DecimalFormat + +class InputNumberFormatter( + numberFormat: DecimalFormat, +) { + + private val symbols = numberFormat.decimalFormatSymbols + + /** + * Formats input [String] for InputField, to remove wrong symbols, letters etc + * Use [decimals] for cut this number symbols after floating point + * + * Example (with 8 decimals): + * input string - ab123.46377372ab53 + * result string 123.46377372 + */ + fun getValidatedNumberWithFixedDecimals(text: String, decimals: Int): String { + val thousandsSeparator = symbols.groupingSeparator + val decimalSeparator = symbols.decimalSeparator + + if (text.startsWith("0") && text.length > 1 && text[1] != decimalSeparator) { + return "0" + } + + val filteredChars = text.replace(thousandsSeparator.toString(), "").filterIndexed { index, c -> + val isOneOrZeroPoint = c == decimalSeparator && index != 0 && text.count { it == decimalSeparator } <= 1 + val isIndexPointIndex = c == decimalSeparator && index != 0 && text.indexOf(decimalSeparator) == index + c.isDigit() || isIndexPointIndex || isOneOrZeroPoint + } + // If dot is present, take first digits before decimal and first decimals digits after decimal + return if (filteredChars.count { it == decimalSeparator } == 1) { + val beforeDecimal = filteredChars.substringBefore(decimalSeparator) + val afterDecimal = filteredChars.substringAfter(decimalSeparator) + beforeDecimal + decimalSeparator + afterDecimal.take(decimals) + } + // If there is no dot, just take all digits + else { + filteredChars + } + } + + fun formatWithThousands(text: String, decimals: Int): String { + val thousandsSeparator = symbols.groupingSeparator + val decimalSeparator = symbols.decimalSeparator + return if (text.count { it == decimalSeparator } == 1) { + val beforeDecimal = text.substringBefore(decimalSeparator) + .reversed() + .chunked(TEXT_CHUNK_THOUSAND) + .joinToString(thousandsSeparator.toString()) + .reversed() + val afterDecimal = text.substringAfter(decimalSeparator) + beforeDecimal + decimalSeparator + afterDecimal.take(decimals) + } + // If there is no dot, just take all digits + else { + text.reversed() + .chunked(TEXT_CHUNK_THOUSAND) + .joinToString(thousandsSeparator.toString()) + .reversed() + } + } + + companion object { + private const val TEXT_CHUNK_THOUSAND = 3 + } +} \ No newline at end of file diff --git a/core/utils/src/main/java/com/tangem/utils/FormatExtensions.kt b/core/utils/src/main/java/com/tangem/utils/FormatExtensions.kt index 1e96e28844..8621eb2521 100644 --- a/core/utils/src/main/java/com/tangem/utils/FormatExtensions.kt +++ b/core/utils/src/main/java/com/tangem/utils/FormatExtensions.kt @@ -3,24 +3,23 @@ package com.tangem.utils import java.math.BigDecimal import java.math.RoundingMode import java.text.DecimalFormat -import java.text.DecimalFormatSymbols +import java.text.NumberFormat import java.util.* // todo determine where to place this extensions fun BigDecimal.toFormattedString( decimals: Int, roundingMode: RoundingMode = RoundingMode.DOWN, - locale: Locale = Locale.US, + locale: Locale = Locale.getDefault(), ): String { - val symbols = DecimalFormatSymbols(locale) - val df = DecimalFormat().apply { - decimalFormatSymbols = symbols + val formatter = NumberFormat.getInstance(locale) as? DecimalFormat + val df = formatter?.apply { maximumFractionDigits = decimals minimumFractionDigits = 0 - isGroupingUsed = false + isGroupingUsed = true this.roundingMode = roundingMode } - return df.format(this) + return df?.format(this) ?: this.toPlainString() } @Suppress("MagicNumber") @@ -48,44 +47,17 @@ fun BigDecimal.toFiatString( formatWithSpaces: Boolean = false, ): String { val fiatValue = rateValue.multiply(this) - return fiatValue.toFormattedFiatValue(fiatCurrencyName, formatWithSpaces) -} - -fun BigDecimal.toFormattedFiatValue( - fiatCurrencyName: String, - formatWithSpaces: Boolean = false, -): String { - val fiatValue = this.setScale(2, RoundingMode.HALF_UP) - .let { if (formatWithSpaces) it.formatWithSpaces() else it } - return " $fiatValue  $fiatCurrencyName" -} - -@Suppress("MagicNumber") -fun BigDecimal.formatWithSpaces(): String { - val str = this.toString() - var integerStr = str.substringBefore('.') - val reminderStr = str.substringAfter('.') - val packets = arrayListOf() - - var index: Int = integerStr.length - while (0 < index) { - if (index <= 3) { - packets.add(integerStr) - break - } - index -= 3 - packets.add(integerStr.substring(startIndex = index)) - integerStr = integerStr.substring(startIndex = 0, endIndex = index) + val formatter = NumberFormat.getInstance(Locale.getDefault()) as? DecimalFormat + val df = formatter?.apply { + maximumFractionDigits = 2 + minimumFractionDigits = 0 + isGroupingUsed = true + this.roundingMode = roundingMode } - - return buildString { - packets.reversed().forEachIndexed { index, packet -> - append(packet) - if (index != packets.lastIndex) append(' ') - } - if (reminderStr.isNotBlank()) { - append('.') - append(reminderStr) - } + val formatted = if (formatWithSpaces) { + "${df?.format(fiatValue)} $fiatCurrencyName" + } else { + "${df?.format(fiatValue)}$fiatCurrencyName" } + return formatted } \ No newline at end of file diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt index 60d78b3919..25f533f4b1 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt @@ -146,7 +146,7 @@ internal class SwapInteractorImpl @Inject constructor( amountToSwap: String, ): SwapState { syncWalletBalanceForTokens(networkId, listOf(fromToken, toToken)) - val amountDecimal = amountToSwap.toBigDecimalOrNull() + val amountDecimal = toBigDecimalOrNull(amountToSwap) if (amountDecimal == null || amountDecimal.compareTo(BigDecimal.ZERO) == 0) { return createEmptyAmountState(fromToken, toToken) } @@ -196,7 +196,7 @@ internal class SwapInteractorImpl @Inject constructor( currencyToGet: Currency, amountToSwap: String, ): TxState { - val amount = requireNotNull(amountToSwap.toBigDecimalOrNull()) { "wrong amount format, use only digits" } + val amount = requireNotNull(toBigDecimalOrNull(amountToSwap)) { "wrong amount format, use only digits" } val estimatedGas = increaseByPercents(TWENTY_FIVE_PERCENTS, swapData.transaction.gas.toIntOrNull() ?: DEFAULT_GAS) val fee = transactionManager.calculateFee( @@ -596,6 +596,10 @@ internal class SwapInteractorImpl @Inject constructor( return value * (percents / 100 + 1) } + private fun toBigDecimalOrNull(amountToSwap: String): BigDecimal? { + return amountToSwap.replace(",", ".").toBigDecimalOrNull() + } + companion object { private const val DEFAULT_SLIPPAGE = 2 private const val ZERO_BALANCE = "0" diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/SwapStateHolder.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/SwapStateHolder.kt index 0910eefc5f..6c431cae0a 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/SwapStateHolder.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/SwapStateHolder.kt @@ -1,5 +1,7 @@ package com.tangem.feature.swap.models +import androidx.compose.ui.text.input.TextFieldValue + data class SwapStateHolder( val sendCardData: SwapCardData, val receiveCardData: SwapCardData, @@ -30,9 +32,9 @@ data class SwapStateHolder( data class SwapCardData( val type: TransactionCardType, - val amount: String?, val amountEquivalent: String?, val coinId: String?, + val amountTextFieldValue: TextFieldValue?, val tokenIconUrl: String, val tokenCurrency: String, val balance: String, diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/AutosizeTextField.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/AutosizeTextField.kt index 7ffc2cc4f1..c9c56536e8 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/AutosizeTextField.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/AutosizeTextField.kt @@ -24,12 +24,13 @@ import androidx.compose.ui.text.ParagraphIntrinsics import androidx.compose.ui.text.font.createFontFamilyResolver import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.text.input.TextFieldValue import com.tangem.core.ui.res.TangemTheme @Suppress("MagicNumber", "LongMethod") @Composable internal fun AutoSizeTextField( - amount: String, + textFieldValue: TextFieldValue, onAmountChange: (String) -> Unit, onFocusChange: (Boolean) -> Unit, modifier: Modifier = Modifier, @@ -40,7 +41,7 @@ internal fun AutoSizeTextField( var shrunkFontSize = TangemTheme.typography.h2.fontSize val calculateIntrinsics = @Composable { ParagraphIntrinsics( - text = amount, + text = textFieldValue.text, style = TangemTheme.typography.h2.copy( color = TangemTheme.colors.text.primary1, fontSize = shrunkFontSize, @@ -63,8 +64,10 @@ internal fun AutoSizeTextField( ) CompositionLocalProvider(LocalTextSelectionColors provides customTextSelectionColors) { BasicTextField( - value = amount, - onValueChange = onAmountChange, + value = textFieldValue, + onValueChange = { + onAmountChange.invoke(it.text) + }, singleLine = true, modifier = Modifier .fillMaxWidth() @@ -76,7 +79,7 @@ internal fun AutoSizeTextField( ), keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), decorationBox = { innerTextField -> - if (amount.isBlank()) { + if (textFieldValue.text.isBlank()) { Text( text = "0", color = TangemTheme.colors.text.disabled, diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt index 34ce93172d..9eac1b9c92 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt @@ -1,5 +1,7 @@ package com.tangem.feature.swap.ui +import androidx.compose.ui.text.TextRange +import androidx.compose.ui.text.input.TextFieldValue import com.tangem.feature.swap.converters.TokensDataConverter import com.tangem.feature.swap.domain.models.DataError import com.tangem.feature.swap.domain.models.domain.Currency @@ -37,8 +39,8 @@ internal class StateBuilder(val actions: UiActions) { blockchainId = networkInfo.blockchainId, sendCardData = SwapCardData( type = TransactionCardType.SendCard(actions.onAmountChanged, actions.onAmountSelected), - amount = null, amountEquivalent = null, + amountTextFieldValue = null, tokenIconUrl = initialCurrency.logoUrl, tokenCurrency = initialCurrency.symbol, coinId = initialCurrency.id, @@ -48,10 +50,10 @@ internal class StateBuilder(val actions: UiActions) { ), receiveCardData = SwapCardData( type = TransactionCardType.ReceiveCard(), - amount = null, amountEquivalent = null, tokenIconUrl = "", tokenCurrency = "", + amountTextFieldValue = null, canSelectAnotherToken = false, balance = "", isNotNativeToken = false, @@ -80,7 +82,7 @@ internal class StateBuilder(val actions: UiActions) { return uiStateHolder.copy( sendCardData = SwapCardData( type = requireNotNull(uiStateHolder.sendCardData.type as? TransactionCardType.SendCard), - amount = uiStateHolder.sendCardData.amount, + amountTextFieldValue = uiStateHolder.sendCardData.amountTextFieldValue, amountEquivalent = null, tokenIconUrl = fromToken.logoUrl, tokenCurrency = fromToken.symbol, @@ -91,7 +93,7 @@ internal class StateBuilder(val actions: UiActions) { ), receiveCardData = SwapCardData( type = TransactionCardType.ReceiveCard(), - amount = null, + amountTextFieldValue = null, amountEquivalent = null, tokenIconUrl = toToken.logoUrl, tokenCurrency = toToken.symbol, @@ -130,7 +132,7 @@ internal class StateBuilder(val actions: UiActions) { return uiStateHolder.copy( sendCardData = SwapCardData( type = requireNotNull(uiStateHolder.sendCardData.type as? TransactionCardType.SendCard), - amount = uiStateHolder.sendCardData.amount, + amountTextFieldValue = uiStateHolder.sendCardData.amountTextFieldValue, amountEquivalent = quoteModel.fromTokenInfo.tokenFiatBalance, tokenIconUrl = uiStateHolder.sendCardData.tokenIconUrl, coinId = quoteModel.fromTokenInfo.coinId, @@ -141,7 +143,7 @@ internal class StateBuilder(val actions: UiActions) { ), receiveCardData = SwapCardData( type = TransactionCardType.ReceiveCard(), - amount = quoteModel.toTokenInfo.tokenAmount.formatToUIRepresentation(), + amountTextFieldValue = TextFieldValue(quoteModel.toTokenInfo.tokenAmount.formatToUIRepresentation()), amountEquivalent = quoteModel.toTokenInfo.tokenFiatBalance, tokenIconUrl = uiStateHolder.receiveCardData.tokenIconUrl, coinId = quoteModel.toTokenInfo.coinId, @@ -172,7 +174,7 @@ internal class StateBuilder(val actions: UiActions) { return uiStateHolder.copy( sendCardData = SwapCardData( type = requireNotNull(uiStateHolder.sendCardData.type as? TransactionCardType.SendCard), - amount = uiStateHolder.sendCardData.amount, + amountTextFieldValue = uiStateHolder.sendCardData.amountTextFieldValue, amountEquivalent = emptyAmountState.zeroAmountEquivalent, tokenIconUrl = uiStateHolder.sendCardData.tokenIconUrl, coinId = uiStateHolder.sendCardData.coinId, @@ -183,7 +185,7 @@ internal class StateBuilder(val actions: UiActions) { ), receiveCardData = SwapCardData( type = TransactionCardType.ReceiveCard(), - amount = "0", + amountTextFieldValue = TextFieldValue("0"), amountEquivalent = emptyAmountState.zeroAmountEquivalent, tokenIconUrl = uiStateHolder.receiveCardData.tokenIconUrl, coinId = uiStateHolder.receiveCardData.coinId, @@ -252,7 +254,10 @@ internal class StateBuilder(val actions: UiActions) { fun updateSwapAmount(uiState: SwapStateHolder, amount: String): SwapStateHolder { return uiState.copy( sendCardData = uiState.sendCardData.copy( - amount = amount, + amountTextFieldValue = TextFieldValue( + text = amount, + selection = TextRange(amount.length), + ), ), ) } diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreenContent.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreenContent.kt index d684b65c7b..a47d9eeac3 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreenContent.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreenContent.kt @@ -27,6 +27,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.constraintlayout.compose.ConstraintLayout @@ -163,7 +164,7 @@ private fun MainInfo(state: SwapStateHolder) { TransactionCard( type = state.sendCardData.type, balance = state.sendCardData.balance, - amount = state.sendCardData.amount, + textFieldValue = state.sendCardData.amountTextFieldValue, amountEquivalent = state.sendCardData.amountEquivalent, tokenIconUrl = state.sendCardData.tokenIconUrl, tokenCurrency = state.sendCardData.tokenCurrency, @@ -180,7 +181,7 @@ private fun MainInfo(state: SwapStateHolder) { TransactionCard( type = state.receiveCardData.type, balance = state.receiveCardData.balance, - amount = state.receiveCardData.amount, + textFieldValue = state.receiveCardData.amountTextFieldValue, amountEquivalent = state.receiveCardData.amountEquivalent, tokenIconUrl = state.receiveCardData.tokenIconUrl, tokenCurrency = state.receiveCardData.tokenCurrency, @@ -360,7 +361,7 @@ private fun MainButton(state: SwapStateHolder, onPermissionWarningClick: () -> U private val sendCard = SwapCardData( type = TransactionCardType.SendCard({}) {}, - amount = "1 000 000", + amountTextFieldValue = TextFieldValue(), amountEquivalent = "1 000 000", tokenIconUrl = "", tokenCurrency = "DAI", @@ -372,7 +373,7 @@ private val sendCard = SwapCardData( private val receiveCard = SwapCardData( type = TransactionCardType.ReceiveCard(), - amount = "1 000 000", + amountTextFieldValue = TextFieldValue(), amountEquivalent = "1 000 000", tokenIconUrl = "", tokenCurrency = "DAI", diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/TransactionCard.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/TransactionCard.kt index 4a62b9f5d0..7bcea09007 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/TransactionCard.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/TransactionCard.kt @@ -32,6 +32,7 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp @@ -55,8 +56,8 @@ fun TransactionCard( balance: String, tokenIconUrl: String, tokenCurrency: String, - amount: String?, amountEquivalent: String?, + textFieldValue: TextFieldValue?, modifier: Modifier = Modifier, @DrawableRes iconPlaceholder: Int? = null, @DrawableRes networkIconRes: Int? = null, @@ -79,8 +80,8 @@ fun TransactionCard( Content( type = type, - amount = amount, amountEquivalent = amountEquivalent, + textFieldValue = textFieldValue, ) } @@ -169,8 +170,8 @@ private fun Header( @Composable private fun Content( type: TransactionCardType, - amount: String?, amountEquivalent: String?, + textFieldValue: TextFieldValue?, ) { Row( modifier = Modifier @@ -192,9 +193,9 @@ private fun Content( val sumTextModifier = Modifier.defaultMinSize(minHeight = TangemTheme.dimens.size32) when (type) { is TransactionCardType.ReceiveCard -> { - if (amount != null) { + if (textFieldValue != null) { ResizableText( - text = amount, + text = textFieldValue.text, color = TangemTheme.colors.text.primary1, style = TangemTheme.typography.h2, fontSizeRange = FontSizeRange(min = 16.sp, max = TangemTheme.typography.h2.fontSize), @@ -216,7 +217,7 @@ private fun Content( is TransactionCardType.SendCard -> { AutoSizeTextField( modifier = sumTextModifier, - amount = amount ?: "", + textFieldValue = textFieldValue ?: TextFieldValue(), onAmountChange = { type.onAmountChanged(it) }, onFocusChange = type.onFocusChanged, ) @@ -376,13 +377,13 @@ private fun Preview_SwapMainCard_InDarkTheme() { private fun TransactionCardPreview() { TransactionCard( type = TransactionCardType.SendCard({}) {}, - amount = "1 000 000", amountEquivalent = "1 000 000", tokenIconUrl = "", tokenCurrency = "DAI", networkIconRes = R.drawable.img_polygon_22, onChangeTokenClick = {}, balance = "123", + textFieldValue = TextFieldValue(), ) } diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt index bc58f26eb1..a8933475a0 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt @@ -8,7 +8,7 @@ import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.tangem.core.analytics.api.AnalyticsEventHandler -import com.tangem.core.ui.utils.getValidatedNumberWithFixedDecimals +import com.tangem.core.ui.utils.InputNumberFormatter import com.tangem.feature.swap.analytics.SwapEvents import com.tangem.feature.swap.domain.BlockchainInteractor import com.tangem.feature.swap.domain.SwapInteractor @@ -32,6 +32,9 @@ import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.launch import kotlinx.serialization.decodeFromString import kotlinx.serialization.json.Json +import java.text.DecimalFormat +import java.text.NumberFormat +import java.util.* import javax.inject.Inject import kotlin.properties.Delegates @@ -53,6 +56,8 @@ internal class SwapViewModel @Inject constructor( private val stateBuilder = StateBuilder( actions = createUiActions(), ) + private val inputNumberFormatter = + InputNumberFormatter(NumberFormat.getInstance(Locale.getDefault()) as DecimalFormat) private val amountDebouncer = Debouncer() private val singleTaskScheduler = SingleTaskScheduler() @@ -332,9 +337,12 @@ internal class SwapViewModel @Inject constructor( toCurrency = newToToken, ) isOrderReversed = !isOrderReversed - lastAmount.value = - cutAmountWithDecimals(blockchainInteractor.getTokenDecimals(newFromToken), lastAmount.value) - uiState = stateBuilder.updateSwapAmount(uiState, lastAmount.value) + val decimals = blockchainInteractor.getTokenDecimals(newFromToken) + lastAmount.value = cutAmountWithDecimals(decimals, lastAmount.value) + uiState = stateBuilder.updateSwapAmount( + uiState, + inputNumberFormatter.formatWithThousands(lastAmount.value, decimals), + ) startLoadingQuotes(newFromToken, newToToken, lastAmount.value) } } @@ -343,9 +351,11 @@ internal class SwapViewModel @Inject constructor( val fromToken = dataState.fromCurrency val toToken = dataState.toCurrency if (fromToken != null && toToken != null) { - val cutValue = cutAmountWithDecimals(blockchainInteractor.getTokenDecimals(fromToken), value) - uiState = stateBuilder.updateSwapAmount(uiState, cutValue) + val decimals = blockchainInteractor.getTokenDecimals(fromToken) + val cutValue = cutAmountWithDecimals(decimals, value) lastAmount.value = cutValue + uiState = + stateBuilder.updateSwapAmount(uiState, inputNumberFormatter.formatWithThousands(cutValue, decimals)) amountDebouncer.debounce(DEBOUNCE_AMOUNT_DELAY, viewModelScope) { startLoadingQuotes(fromToken, toToken, lastAmount.value) } @@ -367,7 +377,7 @@ internal class SwapViewModel @Inject constructor( } private fun cutAmountWithDecimals(maxDecimals: Int, amount: String): String { - return getValidatedNumberWithFixedDecimals(amount, maxDecimals) + return inputNumberFormatter.getValidatedNumberWithFixedDecimals(amount, maxDecimals) } private fun makeDefaultAlert() {