diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/fields/AmountTextField.kt b/core/ui/src/main/java/com/tangem/core/ui/components/fields/AmountTextField.kt index d5263bfded..59c9b5143d 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/fields/AmountTextField.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/fields/AmountTextField.kt @@ -22,6 +22,24 @@ import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.utils.* import java.text.DecimalFormat +/** + * Simple text field for amount input. + * Validates and trims input text using [DecimalFormat]. Formats visual output using [AmountVisualTransformation]. + * Can display aligned placeholder and currency symbol [symbol]. + * + * @param value initial text + * @param decimals number of decimal places + * @param onValueChange callback + * @param textStyle text and placeholder styles + * @param modifier modifier + * @param symbol currency symbol + * @param color text color + * @param placeholderAlignment alignment of placeholder + * @param showPlaceholder show placeholder + * @param keyboardOptions keyboard options + * + * @see [SimpleTextField] for standard text field + */ @Composable fun AmountTextField( value: String, diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendStateFactory.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendStateFactory.kt index 185a62b42a..23b9ce0dfb 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendStateFactory.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendStateFactory.kt @@ -14,6 +14,7 @@ import com.tangem.domain.wallets.models.UserWallet import com.tangem.domain.wallets.usecase.ValidateWalletMemoUseCase import com.tangem.features.send.impl.R import com.tangem.features.send.impl.presentation.domain.AvailableWallet +import com.tangem.features.send.impl.presentation.state.amount.SendAmountCurrencyConverter import com.tangem.features.send.impl.presentation.state.amount.SendAmountStateConverter import com.tangem.features.send.impl.presentation.state.fee.SendFeeStateConverter import com.tangem.features.send.impl.presentation.state.fields.SendAmountFieldChangeConverter @@ -39,8 +40,25 @@ internal class SendStateFactory( ) { private val iconStateConverter by lazy(::CryptoCurrencyToIconStateConverter) - private val amountFieldConverter by lazy { SendAmountFieldConverter(clickIntents) } - private val amountFieldChangeConverter by lazy { SendAmountFieldChangeConverter(currentStateProvider) } + private val amountFieldConverter by lazy { + SendAmountFieldConverter( + clickIntents = clickIntents, + cryptoCurrencyStatusProvider = cryptoCurrencyStatusProvider, + appCurrencyProvider = appCurrencyProvider, + ) + } + private val amountFieldChangeConverter by lazy { + SendAmountFieldChangeConverter( + currentStateProvider = currentStateProvider, + cryptoCurrencyStatusProvider = cryptoCurrencyStatusProvider, + ) + } + private val amountCurrencyConverter by lazy { + SendAmountCurrencyConverter( + currentStateProvider = currentStateProvider, + cryptoCurrencyStatusProvider = cryptoCurrencyStatusProvider, + ) + } private val amountStateConverter by lazy { SendAmountStateConverter( @@ -57,11 +75,7 @@ internal class SendStateFactory( cryptoCurrencyStatusProvider = cryptoCurrencyStatusProvider, ) } - private val feeStateConverter by lazy { - SendFeeStateConverter( - cryptoCurrencyStatusProvider = cryptoCurrencyStatusProvider, - ) - } + private val feeStateConverter by lazy { SendFeeStateConverter() } private val recipientListStateConverter by lazy { SendRecipientListConverter( @@ -90,16 +104,7 @@ internal class SendStateFactory( //region amount state clicks fun getOnAmountValueChange(value: String) = amountFieldChangeConverter.convert(value) - fun getOnCurrencyChangedState(isFiat: Boolean): SendUiState { - val state = currentStateProvider() - val amountState = state.amountState ?: return state - - return if (amountState.isFiatValue == isFiat) { - state - } else { - return state.copy(amountState = amountState.copy(isFiatValue = isFiat)) - } - } + fun getOnCurrencyChangedState(isFiat: Boolean) = amountCurrencyConverter.convert(isFiat) //endregion //region recipient diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendUiState.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendUiState.kt index f02b6ddf13..ea8245e94c 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendUiState.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendUiState.kt @@ -6,8 +6,7 @@ import androidx.paging.PagingData import com.tangem.blockchain.common.transaction.Fee import com.tangem.core.ui.components.currency.tokenicon.TokenIconState import com.tangem.core.ui.event.StateEvent -import com.tangem.domain.appcurrency.model.AppCurrency -import com.tangem.domain.tokens.model.CryptoCurrencyStatus +import com.tangem.core.ui.extensions.TextReference import com.tangem.features.send.impl.presentation.domain.SendRecipientListContent import com.tangem.features.send.impl.presentation.state.amount.SendAmountSegmentedButtonsConfig import com.tangem.features.send.impl.presentation.state.fee.FeeSelectorState @@ -46,15 +45,11 @@ internal sealed class SendStates { data class AmountState( override val type: SendUiStateType = SendUiStateType.Amount, override val isPrimaryButtonEnabled: Boolean, - val cryptoCurrencyStatus: CryptoCurrencyStatus, - val appCurrency: AppCurrency, val walletName: String, - val walletBalance: String, + val walletBalance: TextReference, val tokenIconState: TokenIconState, - val isFiatValue: Boolean, val segmentedButtonConfig: PersistentList, - val amountTextField: SendTextField.Amount, - val amountValue: BigDecimal, + val amountTextField: SendTextField.AmountField, ) : SendStates() /** Recipient state */ @@ -72,14 +67,13 @@ internal sealed class SendStates { data class FeeState( override val type: SendUiStateType = SendUiStateType.Fee, override val isPrimaryButtonEnabled: Boolean = false, - val cryptoCurrencyStatus: CryptoCurrencyStatus, - val feeSelectorState: FeeSelectorState = FeeSelectorState.Loading, - val isSubtract: Boolean = false, - val isUserSubtracted: Boolean = false, - val fee: Fee? = null, - val receivedAmountValue: BigDecimal = BigDecimal.ZERO, - val receivedAmount: String = "", - val notifications: ImmutableList = persistentListOf(), + val feeSelectorState: FeeSelectorState, + val isSubtract: Boolean, + val isUserSubtracted: Boolean, + val fee: Fee?, + val receivedAmountValue: BigDecimal, + val receivedAmount: String, + val notifications: ImmutableList, ) : SendStates() /** Send state */ diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/SendAmountStateConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/SendAmountStateConverter.kt index 3420a483f4..5bda8cc991 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/SendAmountStateConverter.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/SendAmountStateConverter.kt @@ -1,18 +1,20 @@ package com.tangem.features.send.impl.presentation.state.amount import com.tangem.core.ui.components.currency.tokenicon.converter.CryptoCurrencyToIconStateConverter +import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.extensions.stringReference +import com.tangem.core.ui.extensions.wrappedList import com.tangem.core.ui.utils.BigDecimalFormatter.formatCryptoAmount import com.tangem.core.ui.utils.BigDecimalFormatter.formatFiatAmount import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.wallets.models.UserWallet +import com.tangem.features.send.impl.R import com.tangem.features.send.impl.presentation.state.SendStates import com.tangem.features.send.impl.presentation.state.fields.SendAmountFieldConverter import com.tangem.utils.Provider import com.tangem.utils.converter.Converter import kotlinx.collections.immutable.persistentListOf -import java.math.BigDecimal internal class SendAmountStateConverter( private val appCurrencyProvider: Provider, @@ -30,15 +32,11 @@ internal class SendAmountStateConverter( val crypto = formatCryptoAmount(status.value.amount, status.currency.symbol, status.currency.decimals) return SendStates.AmountState( - appCurrency = appCurrency, - cryptoCurrencyStatus = status, walletName = userWallet.name, - walletBalance = "$crypto ($fiat)", + walletBalance = resourceReference(R.string.send_wallet_balance_format, wrappedList(crypto, fiat)), tokenIconState = iconStateConverter.convert(status), amountTextField = sendAmountFieldConverter.convert(Unit), - isFiatValue = false, isPrimaryButtonEnabled = false, - amountValue = BigDecimal.ZERO, segmentedButtonConfig = persistentListOf( SendAmountSegmentedButtonsConfig( title = stringReference(status.currency.symbol), diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeCalculation.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeCalculation.kt index de65f8a987..cd0877210f 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeCalculation.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeCalculation.kt @@ -2,6 +2,7 @@ package com.tangem.features.send.impl.presentation.state.fee import com.tangem.blockchain.common.transaction.Fee import com.tangem.blockchain.common.transaction.TransactionFee +import com.tangem.blockchain.extensions.toBigDecimalOrDefault import com.tangem.features.send.impl.presentation.state.SendUiState import java.math.BigDecimal @@ -9,7 +10,7 @@ import java.math.BigDecimal * Calculate receiving amount when fee is subtracted from sending amount */ internal fun calculateReceiveAmount(state: SendUiState, feeAmount: Fee): BigDecimal { - val amountValue = state.amountState?.amountValue ?: BigDecimal.ZERO + val amountValue = state.amountState?.amountTextField?.cryptoAmount?.value ?: BigDecimal.ZERO val fee = feeAmount.amount.value ?: return BigDecimal.ZERO return amountValue.minus(fee) } @@ -25,8 +26,7 @@ internal fun FeeSelectorState.Content.getFee(): Fee { FeeType.MARKET -> fees.normal FeeType.FAST -> fees.priority FeeType.CUSTOM -> { - val feeAmount = - customValues.firstOrNull()?.value?.let { BigDecimal(it.ifEmpty { "0" }) } ?: BigDecimal.ZERO + val feeAmount = customValues.firstOrNull()?.value.toBigDecimalOrDefault() Fee.Common( fees.normal.amount.copy( value = feeAmount, diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeStateFactory.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeStateFactory.kt index 9ad8b30568..8a1073cad3 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeStateFactory.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeStateFactory.kt @@ -76,7 +76,7 @@ internal class FeeStateFactory( feeState = updatedState.copy( notifications = feeNotificationFactory( feeState = updatedState, - amountValue = state.amountState?.amountValue, + amountValue = state.amountState?.amountTextField?.cryptoAmount?.value, ), isPrimaryButtonEnabled = updatedState.isPrimaryButtonEnabled(), ), @@ -92,7 +92,7 @@ internal class FeeStateFactory( feeState = updatedState?.copy( notifications = feeNotificationFactory( feeState = updatedState, - amountValue = state.amountState?.amountValue, + amountValue = state.amountState?.amountTextField?.cryptoAmount?.value, ), ), ) @@ -119,7 +119,7 @@ internal class FeeStateFactory( feeState = updatedState.copy( notifications = feeNotificationFactory( feeState = updatedState, - amountValue = state.amountState?.amountValue, + amountValue = state.amountState?.amountTextField?.cryptoAmount?.value, ), isPrimaryButtonEnabled = updatedState.isPrimaryButtonEnabled(), ), @@ -150,7 +150,7 @@ internal class FeeStateFactory( feeState = updatedState.copy( notifications = feeNotificationFactory( feeState = updatedState, - amountValue = state.amountState?.amountValue, + amountValue = state.amountState?.amountTextField?.cryptoAmount?.value, ), isPrimaryButtonEnabled = updatedState.isPrimaryButtonEnabled(), ), @@ -174,7 +174,7 @@ internal class FeeStateFactory( feeState = updatedState.copy( notifications = feeNotificationFactory( feeState = updatedState, - amountValue = state.amountState?.amountValue, + amountValue = state.amountState?.amountTextField?.cryptoAmount?.value, ), isPrimaryButtonEnabled = updatedState.isPrimaryButtonEnabled(), ), @@ -203,7 +203,7 @@ internal class FeeStateFactory( private fun checkAutoSubtract(state: SendUiState, fee: Fee, balance: BigDecimal): Boolean { val feeState = state.feeState ?: return false - val amountValue = state.amountState?.amountValue ?: BigDecimal.ZERO + val amountValue = state.amountState?.amountTextField?.cryptoAmount?.value ?: BigDecimal.ZERO val feeAmount = fee.amount.value ?: BigDecimal.ZERO return if (feeState.isUserSubtracted) { feeState.isSubtract diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/SendFeeCustomFieldConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/SendFeeCustomFieldConverter.kt index ff9da4a71f..d6d6168f37 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/SendFeeCustomFieldConverter.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/SendFeeCustomFieldConverter.kt @@ -4,13 +4,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.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.domain.appcurrency.model.AppCurrency +import com.tangem.features.send.impl.R import com.tangem.features.send.impl.presentation.state.fields.SendTextField import com.tangem.features.send.impl.presentation.viewmodel.SendClickIntents import com.tangem.utils.Provider import com.tangem.utils.converter.Converter +import com.tangem.utils.toFormattedString import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf @@ -31,16 +34,24 @@ internal class SendFeeCustomFieldConverter( return persistentListOf( SendTextField.CustomFee( - value = ethereumFee.amount.value.toString(), + value = ethereumFee.amount.value?.toFormattedString(ethereumFee.amount.decimals).orEmpty(), + decimals = ethereumFee.amount.decimals, + symbol = ethereumFee.amount.currencySymbol, onValueChange = { clickIntents.onCustomFeeValueChange(0, it) }, keyboardOptions = KeyboardOptions( imeAction = ImeAction.Next, keyboardType = KeyboardType.Number, ), - label = TextReference.Str(maxFeeFiat), + title = resourceReference(R.string.send_max_fee), + footer = resourceReference(R.string.send_max_fee_footer), + label = stringReference(maxFeeFiat), ), SendTextField.CustomFee( value = ethereumFee.gasPrice.toString(), + decimals = 0, + symbol = ETHEREUM_UNIT, + title = resourceReference(R.string.send_gas_price), + footer = resourceReference(R.string.send_gas_price_footer), onValueChange = { clickIntents.onCustomFeeValueChange(1, it) }, keyboardOptions = KeyboardOptions( imeAction = ImeAction.Next, @@ -49,6 +60,10 @@ internal class SendFeeCustomFieldConverter( ), SendTextField.CustomFee( value = ethereumFee.gasLimit.toString(), + decimals = 0, + symbol = null, + title = resourceReference(R.string.send_gas_limit), + footer = resourceReference(R.string.send_gas_limit_footer), onValueChange = { clickIntents.onCustomFeeValueChange(2, it) }, keyboardOptions = KeyboardOptions( imeAction = ImeAction.Done, @@ -57,4 +72,8 @@ internal class SendFeeCustomFieldConverter( ), ) } + + companion object { + private const val ETHEREUM_UNIT = "GWEI" + } } \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/SendFeeStateConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/SendFeeStateConverter.kt index d87f1a2795..ccd5f4ae89 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/SendFeeStateConverter.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/SendFeeStateConverter.kt @@ -1,17 +1,21 @@ package com.tangem.features.send.impl.presentation.state.fee -import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.features.send.impl.presentation.state.SendStates -import com.tangem.utils.Provider import com.tangem.utils.converter.Converter +import kotlinx.collections.immutable.persistentListOf +import java.math.BigDecimal -internal class SendFeeStateConverter( - private val cryptoCurrencyStatusProvider: Provider, -) : Converter { +internal class SendFeeStateConverter : Converter { override fun convert(value: Unit): SendStates.FeeState { return SendStates.FeeState( - cryptoCurrencyStatus = cryptoCurrencyStatusProvider(), + feeSelectorState = FeeSelectorState.Loading, + isSubtract = false, + isUserSubtracted = false, + fee = null, + receivedAmountValue = BigDecimal.ZERO, + receivedAmount = "", + notifications = persistentListOf(), ) } } \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldChangeConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldChangeConverter.kt index b21b9540ac..7a6c696c1a 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldChangeConverter.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldChangeConverter.kt @@ -1,59 +1,49 @@ package com.tangem.features.send.impl.presentation.state.fields -import com.tangem.blockchain.extensions.toBigDecimalOrDefault +import com.tangem.core.ui.utils.parseBigDecimal +import com.tangem.core.ui.utils.parseToBigDecimal import com.tangem.domain.tokens.model.CryptoCurrency -import com.tangem.features.send.impl.presentation.state.SendStates +import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.features.send.impl.presentation.state.SendUiState import com.tangem.utils.Provider import com.tangem.utils.converter.Converter import java.math.BigDecimal -import java.text.NumberFormat +import java.math.RoundingMode internal class SendAmountFieldChangeConverter( private val currentStateProvider: Provider, + private val cryptoCurrencyStatusProvider: Provider, ) : Converter { override fun convert(value: String): SendUiState { val state = currentStateProvider() val amountState = state.amountState ?: return state + val amountTextField = amountState.amountTextField val feeState = state.feeState ?: return state - if (value.checkDecimalSeparatorDuplicate()) return state if (value.isEmpty()) return state.emptyState() - val fiatRate = amountState.cryptoCurrencyStatus.value.fiatRate + val cryptoDecimals = amountTextField.cryptoAmount.decimals + val fiatDecimals = amountTextField.fiatAmount.decimals + val trimmedValue = value.trim() - val cryptoValue = if (amountState.isFiatValue) { - if (value.isNotBlank()) { - trimmedValue.toBigDecimal().divide(fiatRate)?.stripTrailingZeros()?.toPlainString().orEmpty() - } else { - DEFAULT_VALUE - } - } else { - trimmedValue - } + 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 = if (!amountState.isFiatValue) { - if (value.isNotBlank()) { - trimmedValue.toBigDecimal().multiply(fiatRate)?.stripTrailingZeros()?.toPlainString().orEmpty() - } else { - DEFAULT_VALUE - } - } else { - trimmedValue - } - - val checkValue = if (amountState.isFiatValue) fiatValue else cryptoValue - val isExceedBalance = checkValue.checkExceedBalance(amountState) - val isMaxAmount = checkValue.checkMaxAmount(amountState) + val checkValue = if (amountTextField.isFiatValue) fiatValue else cryptoValue + val isExceedBalance = checkValue.checkExceedBalance(amountTextField) + val isMaxAmount = checkValue.checkMaxAmount(amountTextField) return state.copy( amountState = amountState.copy( isPrimaryButtonEnabled = !isExceedBalance, - amountTextField = amountState.amountTextField.copy( + amountTextField = amountTextField.copy( value = cryptoValue, fiatValue = fiatValue, isError = isExceedBalance, + cryptoAmount = amountTextField.cryptoAmount.copy(value = decimalCryptoValue), + fiatAmount = amountTextField.fiatAmount.copy(value = decimalFiatValue), ), - amountValue = cryptoValue.toBigDecimalOrDefault(), ), feeState = feeState.copy( isSubtract = isMaxAmount, @@ -61,58 +51,63 @@ 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): String { + val cryptoCurrencyStatus = cryptoCurrencyStatusProvider() + val fiatRate = cryptoCurrencyStatus.value.fiatRate + return if (!isFiatValue && fiatRate != null) { + parseToBigDecimal(decimals).multiply(fiatRate).parseBigDecimal(decimals) + } else { + this + } + } + private fun SendUiState.emptyState(): SendUiState { return copy( amountState = amountState?.copy( isPrimaryButtonEnabled = false, amountTextField = amountState.amountTextField.copy( - value = if (!amountState.isFiatValue) "" else DEFAULT_VALUE, - fiatValue = if (amountState.isFiatValue) "" else DEFAULT_VALUE, + value = "", + fiatValue = "", isError = false, ), ), ) } - private fun String.checkDecimalSeparatorDuplicate(): Boolean { - val regex = TRIM_REGEX.toRegex() - val decimalSeparatorCount = regex.findAll(this).count() - - return decimalSeparatorCount > 1 - } - - private fun String.checkExceedBalance(state: SendStates.AmountState): Boolean { - val currencyCryptoAmount = state.cryptoCurrencyStatus.value.amount ?: BigDecimal.ZERO - val currencyFiatAmount = state.cryptoCurrencyStatus.value.fiatAmount ?: BigDecimal.ZERO - return if (state.isFiatValue) { - toBigDecimal() > currencyFiatAmount + 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 + return if (amountTextField.isFiatValue) { + parseToBigDecimal(amountTextField.fiatAmount.decimals) > currencyFiatAmount } else { - toBigDecimal() > currencyCryptoAmount + parseToBigDecimal(amountTextField.cryptoAmount.decimals) > currencyCryptoAmount } } - private fun String.checkMaxAmount(state: SendStates.AmountState): Boolean { + private fun String.checkMaxAmount(amountTextField: SendTextField.AmountField): Boolean { + val cryptoCurrencyStatus = cryptoCurrencyStatusProvider() + // If current currency is Token - if (state.cryptoCurrencyStatus.currency is CryptoCurrency.Token) return false + if (cryptoCurrencyStatus.currency is CryptoCurrency.Token) return false - val currencyCryptoAmount = state.cryptoCurrencyStatus.value.amount ?: BigDecimal.ZERO - val currencyFiatAmount = state.cryptoCurrencyStatus.value.fiatAmount ?: BigDecimal.ZERO - return if (state.isFiatValue) { - toBigDecimal() == currencyFiatAmount + val currencyCryptoAmount = cryptoCurrencyStatus.value.amount ?: BigDecimal.ZERO + val currencyFiatAmount = cryptoCurrencyStatus.value.fiatAmount ?: BigDecimal.ZERO + return if (amountTextField.isFiatValue) { + parseToBigDecimal(amountTextField.fiatAmount.decimals) == currencyFiatAmount } else { - toBigDecimal() == currencyCryptoAmount + parseToBigDecimal(amountTextField.cryptoAmount.decimals) == currencyCryptoAmount } } - - private fun String.trim(): String { - var trimmedValue = this - if (length > 1 && firstOrNull() == '0' && get(1).isDigit()) trimmedValue = drop(1) - - return trimmedValue.replace(TRIM_REGEX.toRegex(), ".") - } - - companion object { - private val DEFAULT_VALUE = NumberFormat.getInstance().format(0.00) - private const val TRIM_REGEX = "[.,]" - } } \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldConverter.kt index 9ab26bd83d..25a63f78f8 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldConverter.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldConverter.kt @@ -4,31 +4,47 @@ import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.KeyboardType import com.tangem.core.ui.extensions.TextReference +import com.tangem.domain.appcurrency.model.AppCurrency +import com.tangem.domain.tokens.model.Amount +import com.tangem.domain.tokens.model.AmountType +import com.tangem.domain.tokens.model.CryptoCurrencyStatus +import com.tangem.domain.tokens.model.convertToAmount import com.tangem.features.send.impl.R import com.tangem.features.send.impl.presentation.viewmodel.SendClickIntents +import com.tangem.utils.Provider import com.tangem.utils.converter.Converter -import java.text.NumberFormat +import java.math.BigDecimal + +private const val FIAT_DECIMALS = 2 internal class SendAmountFieldConverter( private val clickIntents: SendClickIntents, -) : Converter { + private val cryptoCurrencyStatusProvider: Provider, + private val appCurrencyProvider: Provider, +) : Converter { - override fun convert(value: Unit): SendTextField.Amount { - return SendTextField.Amount( + override fun convert(value: Unit): SendTextField.AmountField { + val cryptoCurrencyStatus = cryptoCurrencyStatusProvider() + return SendTextField.AmountField( value = "", - fiatValue = DEFAULT_VALUE, + fiatValue = "", onValueChange = clickIntents::onAmountValueChange, keyboardOptions = KeyboardOptions( imeAction = ImeAction.Next, keyboardType = KeyboardType.Number, ), - placeholder = TextReference.Str(DEFAULT_VALUE), + isFiatValue = false, + cryptoAmount = BigDecimal.ZERO.convertToAmount(cryptoCurrencyStatus.currency), + fiatAmount = getAppCurrencyAmount(appCurrencyProvider()), isError = false, error = TextReference.Res(R.string.swapping_insufficient_funds), ) } - companion object { - private val DEFAULT_VALUE = NumberFormat.getInstance().format(0.00) - } + private fun getAppCurrencyAmount(appCurrency: AppCurrency) = Amount( + currencySymbol = appCurrency.symbol, + value = BigDecimal.ZERO, + decimals = FIAT_DECIMALS, + type = AmountType.FiatType(appCurrency.code), + ) } \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendTextField.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendTextField.kt index 456359a16c..42b2ac5b0d 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendTextField.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendTextField.kt @@ -3,6 +3,7 @@ package com.tangem.features.send.impl.presentation.state.fields import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.runtime.Immutable import com.tangem.core.ui.extensions.TextReference +import com.tangem.domain.tokens.model.Amount @Immutable internal sealed class SendTextField { @@ -16,14 +17,13 @@ internal sealed class SendTextField { /** Keyboard options */ abstract val keyboardOptions: KeyboardOptions - // /** Placeholder (hint) */ - // abstract val placeholder: TextReference - - data class Amount( + data class AmountField( override val value: String, override val onValueChange: (String) -> Unit, override val keyboardOptions: KeyboardOptions, - val placeholder: TextReference, + val cryptoAmount: Amount, + val fiatAmount: Amount, + val isFiatValue: Boolean, val fiatValue: String, val isError: Boolean, val error: TextReference, @@ -53,6 +53,10 @@ internal sealed class SendTextField { override val value: String, override val onValueChange: (String) -> Unit, override val keyboardOptions: KeyboardOptions, + val symbol: String?, + val decimals: Int, + val title: TextReference, + val footer: TextReference, val label: TextReference? = null, ) : SendTextField() } \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/AmountField.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/AmountField.kt index f6471c020d..c2ca7a26d8 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/AmountField.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/AmountField.kt @@ -3,48 +3,49 @@ package com.tangem.features.send.impl.presentation.ui.amount import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut -import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.ColumnScope import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.text.BasicTextField -import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.runtime.remember -import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment.Companion.BottomCenter -import androidx.compose.ui.Alignment.Companion.CenterHorizontally +import androidx.compose.ui.Alignment.Companion.TopCenter import androidx.compose.ui.Modifier -import androidx.compose.ui.focus.FocusRequester -import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.text.style.TextAlign -import com.tangem.core.ui.components.fields.visualtransformations.AmountVisualTransformation +import com.tangem.core.ui.components.fields.AmountTextField import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.resolveReference import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.utils.defaultFormat +import com.tangem.core.ui.utils.rememberDecimalFormat import com.tangem.features.send.impl.presentation.state.fields.SendTextField @Composable -internal fun ColumnScope.AmountField( - sendField: SendTextField.Amount, - cryptoSymbol: String, - fiatSymbol: String, - isFiat: Boolean, -) { - val value = if (isFiat) sendField.fiatValue else sendField.value - val secondaryValue = if (!isFiat) sendField.fiatValue else sendField.value - val symbol = if (isFiat) fiatSymbol else cryptoSymbol - val secondarySymbol = if (!isFiat) fiatSymbol else cryptoSymbol +internal fun AmountField(sendField: SendTextField.AmountField, isFiat: Boolean) { + val decimalFormat = rememberDecimalFormat() + val (primaryValue, secondaryValue) = if (isFiat) { + sendField.fiatValue to sendField.value + } else { + sendField.value to sendField.fiatValue + } - AmountFieldInner( - value = value, - placeholder = sendField.placeholder, - symbol = symbol, + val (primaryAmount, secondaryAmount) = if (!isFiat) { + sendField.cryptoAmount to sendField.fiatAmount + } else { + sendField.fiatAmount to sendField.cryptoAmount + } + + AmountTextField( + value = primaryValue, + decimals = primaryAmount.decimals, + symbol = primaryAmount.currencySymbol, onValueChange = sendField.onValueChange, keyboardOptions = sendField.keyboardOptions, + textStyle = TangemTheme.typography.h2.copy( + color = TangemTheme.colors.text.primary1, + textAlign = TextAlign.Center, + ), + placeholderAlignment = TopCenter, modifier = Modifier - .align(CenterHorizontally) .padding( top = TangemTheme.dimens.spacing24, start = TangemTheme.dimens.spacing12, @@ -54,15 +55,15 @@ internal fun ColumnScope.AmountField( Box( modifier = Modifier - .align(CenterHorizontally) .padding( top = TangemTheme.dimens.spacing8, start = TangemTheme.dimens.spacing12, end = TangemTheme.dimens.spacing12, ), ) { + val text = "${secondaryValue.ifEmpty { decimalFormat.defaultFormat() }} ${secondaryAmount.currencySymbol}" Text( - text = "$secondaryValue $secondarySymbol", + text = text, style = TangemTheme.typography.caption2, color = TangemTheme.colors.text.tertiary, textAlign = TextAlign.Center, @@ -80,47 +81,6 @@ internal fun ColumnScope.AmountField( } } -@Composable -private fun AmountFieldInner( - value: String, - placeholder: TextReference, - symbol: String, - onValueChange: (String) -> Unit, - keyboardOptions: KeyboardOptions, - modifier: Modifier = Modifier, -) { - val focusRequester = remember { FocusRequester() } - BasicTextField( - value = value, - onValueChange = onValueChange, - modifier = modifier - .focusRequester(focusRequester) - .background(TangemTheme.colors.background.action), - textStyle = TangemTheme.typography.h2.copy( - color = TangemTheme.colors.text.primary1, - textAlign = TextAlign.Center, - ), - keyboardOptions = keyboardOptions, - singleLine = true, - visualTransformation = AmountVisualTransformation(symbol), - decorationBox = { innerTextField -> - Box { - if (value.isBlank()) { - Text( - text = "${placeholder.resolveReference()} $symbol", - style = TangemTheme.typography.h2, - color = TangemTheme.colors.text.disabled, - textAlign = TextAlign.Center, - modifier = Modifier - .align(Alignment.TopCenter), - ) - } - innerTextField() - } - }, - ) -} - @Composable private fun AmountFieldError(isError: Boolean, error: TextReference, modifier: Modifier = Modifier) { AnimatedVisibility( diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/AmountFieldContainer.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/AmountFieldContainer.kt index 4f95280719..afd7b30b6e 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/AmountFieldContainer.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/AmountFieldContainer.kt @@ -12,12 +12,14 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.text.style.TextAlign import com.tangem.core.ui.components.currency.tokenicon.TokenIcon +import com.tangem.core.ui.extensions.resolveReference import com.tangem.core.ui.res.TangemTheme import com.tangem.features.send.impl.presentation.state.SendStates @Composable internal fun AmountFieldContainer(amountState: SendStates.AmountState, modifier: Modifier = Modifier) { Column( + horizontalAlignment = Alignment.CenterHorizontally, modifier = modifier .fillMaxWidth() .padding( @@ -33,29 +35,24 @@ internal fun AmountFieldContainer(amountState: SendStates.AmountState, modifier: style = TangemTheme.typography.subtitle2, color = TangemTheme.colors.text.tertiary, modifier = Modifier - .padding(top = TangemTheme.dimens.spacing14) - .align(Alignment.CenterHorizontally), + .padding(top = TangemTheme.dimens.spacing14), ) Text( - text = amountState.walletBalance, + text = amountState.walletBalance.resolveReference(), style = TangemTheme.typography.caption2, color = TangemTheme.colors.text.tertiary, textAlign = TextAlign.Center, modifier = Modifier - .padding(top = TangemTheme.dimens.spacing2) - .align(Alignment.CenterHorizontally), + .padding(top = TangemTheme.dimens.spacing2), ) TokenIcon( state = amountState.tokenIconState, modifier = Modifier - .padding(top = TangemTheme.dimens.spacing32) - .align(Alignment.CenterHorizontally), + .padding(top = TangemTheme.dimens.spacing32), ) AmountField( sendField = amountState.amountTextField, - isFiat = amountState.isFiatValue, - cryptoSymbol = amountState.cryptoCurrencyStatus.currency.symbol, - fiatSymbol = amountState.appCurrency.symbol, + isFiat = amountState.amountTextField.isFiatValue, ) } } \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/fee/SendCustomFeeEthereum.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/fee/SendCustomFeeEthereum.kt index 69bc85a649..a5eba38a01 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/fee/SendCustomFeeEthereum.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/fee/SendCustomFeeEthereum.kt @@ -5,86 +5,64 @@ import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import androidx.compose.ui.res.stringResource -import com.tangem.core.ui.components.fields.visualtransformations.AmountVisualTransformation -import com.tangem.core.ui.components.inputrow.InputRowEnter -import com.tangem.core.ui.components.inputrow.InputRowEnterInfo -import com.tangem.core.ui.extensions.TextReference +import com.tangem.core.ui.components.inputrow.InputRowEnterAmount +import com.tangem.core.ui.components.inputrow.InputRowEnterInfoAmount +import com.tangem.core.ui.extensions.resolveReference import com.tangem.core.ui.res.TangemTheme -import com.tangem.features.send.impl.R import com.tangem.features.send.impl.presentation.state.fee.FeeType import com.tangem.features.send.impl.presentation.state.fields.SendTextField import com.tangem.features.send.impl.presentation.ui.common.FooterContainer import kotlinx.collections.immutable.ImmutableList -private const val ETHEREUM_UNIT = "GWEI" - @Composable internal fun SendCustomFeeEthereum( customValues: ImmutableList, selectedFee: FeeType, - symbol: String, modifier: Modifier = Modifier, ) { if (selectedFee == FeeType.CUSTOM && customValues.isNotEmpty()) { - val fee = customValues[0] - val gasPrice = customValues[1] - val gasLimit = customValues[2] - Column( verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12), modifier = modifier, ) { - FooterContainer( - footer = stringResource(R.string.send_max_fee_footer), - ) { - InputRowEnterInfo( - text = fee.value, - title = TextReference.Res(R.string.send_max_fee), - info = fee.label, - visualTransformation = AmountVisualTransformation(symbol), - keyboardOptions = fee.keyboardOptions, - onValueChange = fee.onValueChange, - isSingleLine = true, - modifier = Modifier - .background( - color = TangemTheme.colors.background.action, - shape = TangemTheme.shapes.roundedCornersXMedium, - ), - ) - } - FooterContainer( - footer = stringResource(R.string.send_gas_price_footer), - ) { - InputRowEnter( - text = gasPrice.value, - title = TextReference.Res(R.string.send_gas_price), - onValueChange = gasPrice.onValueChange, - visualTransformation = AmountVisualTransformation(ETHEREUM_UNIT), - keyboardOptions = fee.keyboardOptions, - isSingleLine = true, - modifier = Modifier - .background( - color = TangemTheme.colors.background.action, - shape = TangemTheme.shapes.roundedCornersXMedium, - ), - ) - } - FooterContainer( - footer = stringResource(R.string.send_gas_limit_footer), - ) { - InputRowEnter( - text = gasLimit.value, - title = TextReference.Res(R.string.send_gas_limit), - onValueChange = gasLimit.onValueChange, - keyboardOptions = fee.keyboardOptions, - isSingleLine = true, - modifier = Modifier - .background( - color = TangemTheme.colors.background.action, - shape = TangemTheme.shapes.roundedCornersXMedium, - ), - ) + repeat(customValues.size) { index -> + val value = customValues[index] + FooterContainer( + footer = value.footer.resolveReference(), + ) { + if (value.label != null) { + InputRowEnterInfoAmount( + text = value.value, + decimals = value.decimals, + symbol = value.symbol, + title = value.title, + info = value.label, + keyboardOptions = value.keyboardOptions, + onValueChange = value.onValueChange, + showDivider = false, + modifier = Modifier + .background( + color = TangemTheme.colors.background.action, + shape = TangemTheme.shapes.roundedCornersXMedium, + ), + ) + } else { + InputRowEnterAmount( + text = value.value, + decimals = value.decimals, + title = value.title, + symbol = value.symbol, + onValueChange = value.onValueChange, + keyboardOptions = value.keyboardOptions, + showDivider = false, + modifier = Modifier + .background( + color = TangemTheme.colors.background.action, + shape = TangemTheme.shapes.roundedCornersXMedium, + ), + ) + } + } } } } diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/fee/SendSpeedAndFeeContent.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/fee/SendSpeedAndFeeContent.kt index 2bd84ea7ba..c490f4b84b 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/fee/SendSpeedAndFeeContent.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/fee/SendSpeedAndFeeContent.kt @@ -43,10 +43,7 @@ internal fun SendSpeedAndFeeContent(state: SendStates.FeeState?, clickIntents: S clickIntents = clickIntents, ) } - customFee( - feeSendState = feeSendState, - cryptoCurrencySymbol = state.cryptoCurrencyStatus.currency.symbol, - ) + customFee(feeSendState) notifications(notifications) subtractButton( receivedAmount = state.receivedAmount, @@ -84,11 +81,7 @@ internal fun LazyListScope.notifications(configs: ImmutableList? { val amountState = uiState.amountState ?: return null val recipientState = uiState.recipientState ?: return null - val amount = amountState.amountTextField.value.toBigDecimal() + val amount = amountState.amountTextField.cryptoAmount.value ?: return null return getFeeUseCase.invoke( amount = amount, @@ -495,7 +500,7 @@ internal class SendViewModel @Inject constructor( val feeSelectorState = feeState.feeSelectorState as? FeeSelectorState.Content ?: return val memo = uiState.recipientState?.memoTextField?.value val fee = feeSelectorState.getFee() - val amountValue = uiState.amountState?.amountValue ?: return + val amountValue = uiState.amountState?.amountTextField?.cryptoAmount?.value ?: return val amountToSend = if (feeState.isSubtract) feeState.receivedAmountValue else amountValue @@ -616,7 +621,6 @@ internal class SendViewModel @Inject constructor( companion object { private const val XRP_X_ADDRESS = 'X' - private const val DEFAULT_VALUE = "0.00" private const val CHECK_FEE_UPDATE_DELAY = 60_000L private const val BALANCE_UPDATE_DELAY = 10_000L }