From d3ca29be3b7f86f2c631e316aeca4fc9bf653aa3 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 5 Jun 2024 13:53:53 +0500 Subject: [PATCH] Updated on 2026-08-14 --- .../amountScreen/AmountScreenClickIntents.kt | 24 ++++ .../ui/amountScreen/AmountScreenContent.kt | 70 +++++++++ .../converters/AmountCurrencyTransformer.kt | 42 ++++++ .../AmountPastedTriggerDismissTransformer.kt | 17 +++ .../converters/AmountReduceByTransformer.kt | 66 +++++++++ .../converters/AmountReduceToTransformer.kt | 55 +++++++ .../converters/AmountStateConverter.kt | 82 +++++++++++ .../field/AmountFieldChangeTransformer.kt | 82 +++++++++++ .../converters/field/AmountFieldConverter.kt | 82 +++++++++++ .../field/AmountFieldMaxAmountTransformer.kt | 53 +++++++ .../ui/amountScreen/models/AmountField.kt | 39 +++++ .../models/AmountSegmentedButtonsConfig.kt | 21 +++ .../ui/amountScreen/models/AmountState.kt | 32 +++++ .../preview/AmountScreenClickIntentsStub.kt | 16 +++ .../preview/AmountStatePreviewData.kt | 87 +++++++++++ .../ui/amountScreen/ui/AmountButtons.kt | 124 ++++++++++++++++ .../common/ui/amountScreen/ui/AmountField.kt | 136 ++++++++++++++++++ .../amountScreen/ui/AmountFieldContainer.kt | 69 +++++++++ .../ui/amountScreen/utils/AmountUtils.kt | 59 ++++++++ 19 files changed, 1156 insertions(+) create mode 100644 common/ui/src/main/java/com/tangem/common/ui/amountScreen/AmountScreenClickIntents.kt create mode 100644 common/ui/src/main/java/com/tangem/common/ui/amountScreen/AmountScreenContent.kt create mode 100644 common/ui/src/main/java/com/tangem/common/ui/amountScreen/converters/AmountCurrencyTransformer.kt create mode 100644 common/ui/src/main/java/com/tangem/common/ui/amountScreen/converters/AmountPastedTriggerDismissTransformer.kt create mode 100644 common/ui/src/main/java/com/tangem/common/ui/amountScreen/converters/AmountReduceByTransformer.kt create mode 100644 common/ui/src/main/java/com/tangem/common/ui/amountScreen/converters/AmountReduceToTransformer.kt create mode 100644 common/ui/src/main/java/com/tangem/common/ui/amountScreen/converters/AmountStateConverter.kt create mode 100644 common/ui/src/main/java/com/tangem/common/ui/amountScreen/converters/field/AmountFieldChangeTransformer.kt create mode 100644 common/ui/src/main/java/com/tangem/common/ui/amountScreen/converters/field/AmountFieldConverter.kt create mode 100644 common/ui/src/main/java/com/tangem/common/ui/amountScreen/converters/field/AmountFieldMaxAmountTransformer.kt create mode 100644 common/ui/src/main/java/com/tangem/common/ui/amountScreen/models/AmountField.kt create mode 100644 common/ui/src/main/java/com/tangem/common/ui/amountScreen/models/AmountSegmentedButtonsConfig.kt create mode 100644 common/ui/src/main/java/com/tangem/common/ui/amountScreen/models/AmountState.kt create mode 100644 common/ui/src/main/java/com/tangem/common/ui/amountScreen/preview/AmountScreenClickIntentsStub.kt create mode 100644 common/ui/src/main/java/com/tangem/common/ui/amountScreen/preview/AmountStatePreviewData.kt create mode 100644 common/ui/src/main/java/com/tangem/common/ui/amountScreen/ui/AmountButtons.kt create mode 100644 common/ui/src/main/java/com/tangem/common/ui/amountScreen/ui/AmountField.kt create mode 100644 common/ui/src/main/java/com/tangem/common/ui/amountScreen/ui/AmountFieldContainer.kt create mode 100644 common/ui/src/main/java/com/tangem/common/ui/amountScreen/utils/AmountUtils.kt diff --git a/common/ui/src/main/java/com/tangem/common/ui/amountScreen/AmountScreenClickIntents.kt b/common/ui/src/main/java/com/tangem/common/ui/amountScreen/AmountScreenClickIntents.kt new file mode 100644 index 0000000000..a913361583 --- /dev/null +++ b/common/ui/src/main/java/com/tangem/common/ui/amountScreen/AmountScreenClickIntents.kt @@ -0,0 +1,24 @@ +package com.tangem.common.ui.amountScreen + +/** Amount screen clicks */ +interface AmountScreenClickIntents { + + /** On amount [value] changed */ + fun onAmountValueChange(value: String) + + /** Click triggered on value paste */ + fun onAmountPasteTriggerDismiss() + + /** On max amount click */ + fun onMaxValueClick() + + /** + * On currency change from crypto currency to app currency clicked + * + * @param isFiat indicates currency to change + */ + fun onCurrencyChangeClick(isFiat: Boolean) + + /** On next screen click */ + fun onAmountNext() +} \ No newline at end of file diff --git a/common/ui/src/main/java/com/tangem/common/ui/amountScreen/AmountScreenContent.kt b/common/ui/src/main/java/com/tangem/common/ui/amountScreen/AmountScreenContent.kt new file mode 100644 index 0000000000..3d228be20b --- /dev/null +++ b/common/ui/src/main/java/com/tangem/common/ui/amountScreen/AmountScreenContent.kt @@ -0,0 +1,70 @@ +package com.tangem.common.ui.amountScreen + +import android.content.res.Configuration +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import com.tangem.common.ui.amountScreen.ui.amountField +import com.tangem.common.ui.amountScreen.ui.buttons +import com.tangem.common.ui.amountScreen.models.AmountState +import com.tangem.common.ui.amountScreen.preview.AmountScreenClickIntentsStub +import com.tangem.common.ui.amountScreen.preview.AmountStatePreviewData +import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemThemePreview + +/** + * Amount screen with field + * * @param amountState amount state + * @param isBalanceHiding flag hidden balances + * @param clickIntents amount screen clicks + */ +@Composable +fun AmountScreenContent(amountState: AmountState, isBalanceHiding: Boolean, clickIntents: AmountScreenClickIntents) { + // Do not put fillMaxSize() in here + LazyColumn( + modifier = Modifier + .background(TangemTheme.colors.background.tertiary) + .padding( + start = TangemTheme.dimens.spacing16, + end = TangemTheme.dimens.spacing16, + bottom = TangemTheme.dimens.spacing16, + ), + ) { + amountField(amountState = amountState, isBalanceHiding = isBalanceHiding) + buttons( + segmentedButtonConfig = amountState.segmentedButtonConfig, + clickIntents = clickIntents, + isSegmentedButtonsEnabled = amountState.isSegmentedButtonsEnabled, + selectedButton = amountState.selectedButton, + ) + } +} + +// region Preview +@Preview(showBackground = true, widthDp = 360) +@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES) +@Composable +private fun SendAmountContentPreview( + @PreviewParameter(SendAmountContentPreviewProvider::class) amountState: AmountState, +) { + TangemThemePreview { + AmountScreenContent( + amountState = amountState, + isBalanceHiding = false, + clickIntents = AmountScreenClickIntentsStub, + ) + } +} + +private class SendAmountContentPreviewProvider : PreviewParameterProvider { + override val values: Sequence + get() = sequenceOf( + AmountStatePreviewData.amountState, + ) +} +// endregion \ No newline at end of file diff --git a/common/ui/src/main/java/com/tangem/common/ui/amountScreen/converters/AmountCurrencyTransformer.kt b/common/ui/src/main/java/com/tangem/common/ui/amountScreen/converters/AmountCurrencyTransformer.kt new file mode 100644 index 0000000000..d4264afd5c --- /dev/null +++ b/common/ui/src/main/java/com/tangem/common/ui/amountScreen/converters/AmountCurrencyTransformer.kt @@ -0,0 +1,42 @@ +package com.tangem.common.ui.amountScreen.converters + +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.text.input.KeyboardType +import com.tangem.common.ui.amountScreen.models.AmountState +import com.tangem.domain.tokens.model.CryptoCurrencyStatus +import com.tangem.utils.Provider +import com.tangem.utils.isNullOrZero +import com.tangem.utils.transformer.Transformer + +/** + * Selected currency change from crypto currency to app currency and vice versa + * + * @property cryptoCurrencyStatusProvider current cryptocurrency status provider + */ +class AmountCurrencyTransformer( + private val cryptoCurrencyStatusProvider: Provider, +) : Transformer { + override fun transform(prevState: AmountState, value: Boolean): AmountState { + val amountTextField = prevState.amountTextField + val cryptoCurrencyStatus = cryptoCurrencyStatusProvider() + + val isValidFiatRate = cryptoCurrencyStatus.value.fiatRate.isNullOrZero() + val isDoneActionEnabled = prevState.isPrimaryButtonEnabled + return if (amountTextField.isFiatValue == value && !isValidFiatRate) { + prevState + } else { + return prevState.copy( + amountTextField = amountTextField.copy( + isFiatValue = value, + isValuePasted = true, + keyboardOptions = KeyboardOptions( + imeAction = if (isDoneActionEnabled) ImeAction.Done else ImeAction.None, + keyboardType = KeyboardType.Number, + ), + ), + selectedButton = prevState.segmentedButtonConfig.indexOfFirst { it.isFiat == value }, + ) + } + } +} \ No newline at end of file diff --git a/common/ui/src/main/java/com/tangem/common/ui/amountScreen/converters/AmountPastedTriggerDismissTransformer.kt b/common/ui/src/main/java/com/tangem/common/ui/amountScreen/converters/AmountPastedTriggerDismissTransformer.kt new file mode 100644 index 0000000000..4946b2d297 --- /dev/null +++ b/common/ui/src/main/java/com/tangem/common/ui/amountScreen/converters/AmountPastedTriggerDismissTransformer.kt @@ -0,0 +1,17 @@ +package com.tangem.common.ui.amountScreen.converters + +import com.tangem.common.ui.amountScreen.models.AmountState +import com.tangem.utils.transformer.Transformer + +/** + * Dismisses indication on pasted value + */ +class AmountPastedTriggerDismissTransformer : Transformer { + override fun transform(prevState: AmountState, value: Boolean): AmountState { + return prevState.copy( + amountTextField = prevState.amountTextField.copy( + isValuePasted = false, + ), + ) + } +} \ No newline at end of file diff --git a/common/ui/src/main/java/com/tangem/common/ui/amountScreen/converters/AmountReduceByTransformer.kt b/common/ui/src/main/java/com/tangem/common/ui/amountScreen/converters/AmountReduceByTransformer.kt new file mode 100644 index 0000000000..a10cc83951 --- /dev/null +++ b/common/ui/src/main/java/com/tangem/common/ui/amountScreen/converters/AmountReduceByTransformer.kt @@ -0,0 +1,66 @@ +package com.tangem.common.ui.amountScreen.converters + +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.ui.text.input.KeyboardType +import com.tangem.common.ui.amountScreen.models.AmountState +import com.tangem.common.ui.amountScreen.utils.checkExceedBalance +import com.tangem.common.ui.amountScreen.utils.getFiatValue +import com.tangem.common.ui.amountScreen.utils.getKeyboardAction +import com.tangem.core.ui.utils.parseBigDecimal +import com.tangem.domain.tokens.model.CryptoCurrencyStatus +import com.tangem.utils.Provider +import com.tangem.utils.isNullOrZero +import com.tangem.utils.transformer.Transformer +import java.math.BigDecimal + +/** + * Reduces amount by specific value + * + * @property cryptoCurrencyStatusProvider current cryptocurrency status provider + */ +class AmountReduceByTransformer( + private val cryptoCurrencyStatusProvider: Provider, +) : Transformer { + override fun transform(prevState: AmountState, value: ReduceByData): AmountState { + val cryptoCurrencyStatus = cryptoCurrencyStatusProvider() + val amountTextField = prevState.amountTextField + val cryptoDecimals = amountTextField.cryptoAmount.decimals + val fiatDecimals = amountTextField.fiatAmount.decimals + val amountValue = prevState.amountTextField.cryptoAmount.value ?: return prevState + + val decimalCryptoValue = amountValue.minus(value.reduceAmountByDiff) + val cryptoValue = decimalCryptoValue.parseBigDecimal(cryptoDecimals) + val (fiatValue, decimalFiatValue) = cryptoValue.getFiatValue( + fiatRate = cryptoCurrencyStatus.value.fiatRate, + isFiatValue = false, + decimals = fiatDecimals, + ) + + val checkValue = if (amountTextField.isFiatValue) fiatValue else cryptoValue + val isExceedBalance = checkValue.checkExceedBalance(cryptoCurrencyStatus, amountTextField) + val isZero = if (amountTextField.isFiatValue) { + decimalFiatValue.isNullOrZero() + } else { + decimalCryptoValue.isNullOrZero() + } + return prevState.copy( + isPrimaryButtonEnabled = !isExceedBalance && !isZero, + amountTextField = amountTextField.copy( + value = cryptoValue, + fiatValue = fiatValue, + isError = isExceedBalance, + cryptoAmount = amountTextField.cryptoAmount.copy(value = decimalCryptoValue), + fiatAmount = amountTextField.fiatAmount.copy(value = decimalFiatValue), + keyboardOptions = KeyboardOptions( + imeAction = getKeyboardAction(isExceedBalance, decimalCryptoValue), + keyboardType = KeyboardType.Number, + ), + ), + ) + } + + data class ReduceByData( + val reduceAmountBy: BigDecimal, + val reduceAmountByDiff: BigDecimal, + ) +} \ No newline at end of file diff --git a/common/ui/src/main/java/com/tangem/common/ui/amountScreen/converters/AmountReduceToTransformer.kt b/common/ui/src/main/java/com/tangem/common/ui/amountScreen/converters/AmountReduceToTransformer.kt new file mode 100644 index 0000000000..e49b22924e --- /dev/null +++ b/common/ui/src/main/java/com/tangem/common/ui/amountScreen/converters/AmountReduceToTransformer.kt @@ -0,0 +1,55 @@ +package com.tangem.common.ui.amountScreen.converters + +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.ui.text.input.KeyboardType +import com.tangem.common.ui.amountScreen.models.AmountState +import com.tangem.common.ui.amountScreen.utils.checkExceedBalance +import com.tangem.common.ui.amountScreen.utils.getFiatValue +import com.tangem.common.ui.amountScreen.utils.getKeyboardAction +import com.tangem.core.ui.utils.parseBigDecimal +import com.tangem.domain.tokens.model.CryptoCurrencyStatus +import com.tangem.utils.Provider +import com.tangem.utils.isNullOrZero +import com.tangem.utils.transformer.Transformer +import java.math.BigDecimal + +/** + * Reduces amount to specific value + * + * @property cryptoCurrencyStatusProvider current cryptocurrency status provider + */ +class AmountReduceToTransformer( + private val cryptoCurrencyStatusProvider: Provider, +) : Transformer { + override fun transform(prevState: AmountState, value: BigDecimal): AmountState { + val cryptoCurrencyStatus = cryptoCurrencyStatusProvider() + val amountTextField = prevState.amountTextField + val cryptoDecimals = amountTextField.cryptoAmount.decimals + val fiatDecimals = amountTextField.fiatAmount.decimals + + val cryptoValue = value.parseBigDecimal(cryptoDecimals) + val (fiatValue, decimalFiatValue) = cryptoValue.getFiatValue( + fiatRate = cryptoCurrencyStatus.value.fiatRate, + isFiatValue = false, + decimals = fiatDecimals, + ) + + val checkValue = if (amountTextField.isFiatValue) fiatValue else cryptoValue + val isExceedBalance = checkValue.checkExceedBalance(cryptoCurrencyStatus, amountTextField) + val isZero = if (amountTextField.isFiatValue) decimalFiatValue.isNullOrZero() else value.isNullOrZero() + return prevState.copy( + isPrimaryButtonEnabled = !isExceedBalance && !isZero, + amountTextField = amountTextField.copy( + value = cryptoValue, + fiatValue = fiatValue, + isError = isExceedBalance, + cryptoAmount = amountTextField.cryptoAmount.copy(value = value), + fiatAmount = amountTextField.fiatAmount.copy(value = decimalFiatValue), + keyboardOptions = KeyboardOptions( + imeAction = getKeyboardAction(isExceedBalance, value), + keyboardType = KeyboardType.Number, + ), + ), + ) + } +} \ No newline at end of file diff --git a/common/ui/src/main/java/com/tangem/common/ui/amountScreen/converters/AmountStateConverter.kt b/common/ui/src/main/java/com/tangem/common/ui/amountScreen/converters/AmountStateConverter.kt new file mode 100644 index 0000000000..31be99af9a --- /dev/null +++ b/common/ui/src/main/java/com/tangem/common/ui/amountScreen/converters/AmountStateConverter.kt @@ -0,0 +1,82 @@ +package com.tangem.common.ui.amountScreen.converters + +import com.tangem.common.ui.R +import com.tangem.common.ui.amountScreen.AmountScreenClickIntents +import com.tangem.common.ui.amountScreen.converters.field.AmountFieldConverter +import com.tangem.common.ui.amountScreen.models.AmountState +import com.tangem.common.ui.amountScreen.models.AmountSegmentedButtonsConfig +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.utils.Provider +import com.tangem.utils.converter.Converter +import com.tangem.utils.isNullOrZero +import kotlinx.collections.immutable.persistentListOf + +/** + * Converts initial [String] to [AmountState] + * + * @property clickIntents amount screen clicks + * @property appCurrencyProvider selected app currency provider + * @property userWalletProvider selected user wallet provider + * @property cryptoCurrencyStatusProvider current cryptocurrency status provider + * @property iconStateConverter currency icon converter + */ +class AmountStateConverter( + private val clickIntents: AmountScreenClickIntents, + private val appCurrencyProvider: Provider, + private val userWalletProvider: Provider, + private val cryptoCurrencyStatusProvider: Provider, + private val iconStateConverter: CryptoCurrencyToIconStateConverter, +) : Converter { + + private val amountFieldConverter by lazy(LazyThreadSafetyMode.NONE) { + AmountFieldConverter( + clickIntents = clickIntents, + cryptoCurrencyStatusProvider = cryptoCurrencyStatusProvider, + appCurrencyProvider = appCurrencyProvider, + ) + } + + override fun convert(value: String): AmountState { + val userWallet = userWalletProvider() + val appCurrency = appCurrencyProvider() + val status = cryptoCurrencyStatusProvider() + val fiat = formatFiatAmount(status.value.fiatAmount, appCurrency.code, appCurrency.symbol) + val crypto = formatCryptoAmount(status.value.amount, status.currency.symbol, status.currency.decimals) + val noFeeRate = status.value.fiatRate.isNullOrZero() + + return AmountState( + walletName = userWallet.name, + walletBalance = resourceReference(R.string.send_wallet_balance_format, wrappedList(crypto, fiat)), + tokenIconState = iconStateConverter.convert(status), + amountTextField = amountFieldConverter.convert(value), + isPrimaryButtonEnabled = false, + appCurrencyCode = appCurrency.code, + segmentedButtonConfig = persistentListOf( + AmountSegmentedButtonsConfig( + title = stringReference(status.currency.symbol), + iconState = iconStateConverter.convertCustom( + value = status, + forceGrayscale = noFeeRate, + showCustomTokenBadge = false, + ), + isFiat = false, + ), + AmountSegmentedButtonsConfig( + title = stringReference(appCurrency.code), + iconUrl = appCurrency.iconSmallUrl, + isFiat = true, + ), + ), + isSegmentedButtonsEnabled = !noFeeRate, + selectedButton = 0, + ) + } +} \ No newline at end of file diff --git a/common/ui/src/main/java/com/tangem/common/ui/amountScreen/converters/field/AmountFieldChangeTransformer.kt b/common/ui/src/main/java/com/tangem/common/ui/amountScreen/converters/field/AmountFieldChangeTransformer.kt new file mode 100644 index 0000000000..d8c937246f --- /dev/null +++ b/common/ui/src/main/java/com/tangem/common/ui/amountScreen/converters/field/AmountFieldChangeTransformer.kt @@ -0,0 +1,82 @@ +package com.tangem.common.ui.amountScreen.converters.field + +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.ui.text.input.KeyboardType +import com.tangem.common.ui.amountScreen.models.AmountState +import com.tangem.common.ui.amountScreen.utils.checkExceedBalance +import com.tangem.common.ui.amountScreen.utils.getCryptoValue +import com.tangem.common.ui.amountScreen.utils.getFiatValue +import com.tangem.common.ui.amountScreen.utils.getKeyboardAction +import com.tangem.core.ui.utils.parseToBigDecimal +import com.tangem.domain.tokens.model.CryptoCurrencyStatus +import com.tangem.utils.Provider +import com.tangem.utils.isNullOrZero +import com.tangem.utils.transformer.Transformer +import java.math.BigDecimal + +/** + * Amount value change + * + * @property cryptoCurrencyStatusProvider current cryptocurrency status provider + */ +class AmountFieldChangeTransformer( + private val cryptoCurrencyStatusProvider: Provider, +) : Transformer { + + override fun transform(prevState: AmountState, value: String): AmountState { + val cryptoCurrencyStatus = cryptoCurrencyStatusProvider() + val amountTextField = prevState.amountTextField + + if (value.isEmpty()) return prevState.emptyState() + val cryptoDecimals = amountTextField.cryptoAmount.decimals + val fiatDecimals = amountTextField.fiatAmount.decimals + + val trimmedValue = value.trim() + val cryptoValue = trimmedValue.getCryptoValue( + fiatRate = cryptoCurrencyStatus.value.fiatRate, + isFiatValue = amountTextField.isFiatValue, + decimals = cryptoDecimals, + ) + val decimalCryptoValue = cryptoValue.parseToBigDecimal(cryptoDecimals) + val (fiatValue, decimalFiatValue) = trimmedValue.getFiatValue( + fiatRate = cryptoCurrencyStatus.value.fiatRate, + isFiatValue = amountTextField.isFiatValue, + decimals = fiatDecimals, + ) + + val checkValue = if (amountTextField.isFiatValue) fiatValue else cryptoValue + val isExceedBalance = checkValue.checkExceedBalance(cryptoCurrencyStatus, amountTextField) + val isZero = if (amountTextField.isFiatValue) { + decimalFiatValue.isNullOrZero() + } else { + decimalCryptoValue.isNullOrZero() + } + return prevState.copy( + isPrimaryButtonEnabled = !isExceedBalance && !isZero, + amountTextField = amountTextField.copy( + value = cryptoValue, + fiatValue = fiatValue, + isError = isExceedBalance, + cryptoAmount = amountTextField.cryptoAmount.copy(value = decimalCryptoValue), + fiatAmount = amountTextField.fiatAmount.copy(value = decimalFiatValue), + keyboardOptions = KeyboardOptions( + imeAction = getKeyboardAction(isExceedBalance, decimalCryptoValue), + keyboardType = KeyboardType.Number, + ), + ), + ) + } + + private fun AmountState.emptyState(): AmountState { + return copy( + isPrimaryButtonEnabled = false, + amountTextField = amountTextField.copy( + value = "", + fiatValue = "", + cryptoAmount = amountTextField.cryptoAmount.copy(value = BigDecimal.ZERO), + fiatAmount = amountTextField.fiatAmount.copy(value = BigDecimal.ZERO), + isError = false, + ), + ) + } +} \ No newline at end of file diff --git a/common/ui/src/main/java/com/tangem/common/ui/amountScreen/converters/field/AmountFieldConverter.kt b/common/ui/src/main/java/com/tangem/common/ui/amountScreen/converters/field/AmountFieldConverter.kt new file mode 100644 index 0000000000..7c8ed55206 --- /dev/null +++ b/common/ui/src/main/java/com/tangem/common/ui/amountScreen/converters/field/AmountFieldConverter.kt @@ -0,0 +1,82 @@ +package com.tangem.common.ui.amountScreen.converters.field + +import androidx.compose.foundation.text.KeyboardActions +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.text.input.KeyboardType +import com.tangem.common.ui.R +import com.tangem.common.ui.amountScreen.AmountScreenClickIntents +import com.tangem.common.ui.amountScreen.models.AmountField +import com.tangem.core.ui.extensions.TextReference +import com.tangem.core.ui.utils.parseBigDecimal +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.utils.Provider +import com.tangem.utils.converter.Converter +import com.tangem.utils.isNullOrZero +import java.math.BigDecimal + +/** + * Converts initial [String] to [AmountField] + * + * @property clickIntents amount screen clicks + * @property appCurrencyProvider selected app currency provider + * @property cryptoCurrencyStatusProvider current cryptocurrency status provider + */ +class AmountFieldConverter( + private val clickIntents: AmountScreenClickIntents, + private val cryptoCurrencyStatusProvider: Provider, + private val appCurrencyProvider: Provider, +) : Converter { + + override fun convert(value: String): AmountField { + val cryptoCurrencyStatus = cryptoCurrencyStatusProvider() + val cryptoDecimal = value.toBigDecimalOrNull() ?: BigDecimal.ZERO + val cryptoAmount = cryptoDecimal.convertToAmount(cryptoCurrencyStatus.currency) + val fiatRate = cryptoCurrencyStatus.value.fiatRate + 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.isNullOrZero() + return AmountField( + value = value, + fiatValue = fiatValue, + onValueChange = clickIntents::onAmountValueChange, + keyboardOptions = KeyboardOptions( + imeAction = if (isDoneActionEnabled) ImeAction.Done else ImeAction.None, + keyboardType = KeyboardType.Number, + ), + keyboardActions = KeyboardActions( + onDone = { clickIntents.onAmountNext() }, + ), + isFiatValue = false, + cryptoAmount = cryptoAmount, + fiatAmount = getAppCurrencyAmount(fiatDecimal, appCurrencyProvider()), + isError = false, + error = TextReference.Res(R.string.send_validation_amount_exceeds_balance), + isFiatUnavailable = fiatRate == null, + isValuePasted = false, + onValuePastedTriggerDismiss = clickIntents::onAmountPasteTriggerDismiss, + ) + } + + private fun getAppCurrencyAmount(fiatValue: BigDecimal?, appCurrency: AppCurrency) = Amount( + currencySymbol = appCurrency.symbol, + value = fiatValue, + decimals = FIAT_DECIMALS, + type = AmountType.FiatType(appCurrency.code), + ) + + private companion object { + private const val FIAT_DECIMALS = 2 + } +} \ No newline at end of file diff --git a/common/ui/src/main/java/com/tangem/common/ui/amountScreen/converters/field/AmountFieldMaxAmountTransformer.kt b/common/ui/src/main/java/com/tangem/common/ui/amountScreen/converters/field/AmountFieldMaxAmountTransformer.kt new file mode 100644 index 0000000000..d18b31c41a --- /dev/null +++ b/common/ui/src/main/java/com/tangem/common/ui/amountScreen/converters/field/AmountFieldMaxAmountTransformer.kt @@ -0,0 +1,53 @@ +package com.tangem.common.ui.amountScreen.converters.field + +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.text.input.KeyboardType +import com.tangem.common.ui.amountScreen.models.AmountState +import com.tangem.core.ui.utils.parseBigDecimal +import com.tangem.domain.tokens.model.CryptoCurrencyStatus +import com.tangem.utils.Provider +import com.tangem.utils.isNullOrZero +import com.tangem.utils.transformer.Transformer +import java.math.RoundingMode + +/** + * Selects maximum amount value + * + * @property cryptoCurrencyStatusProvider current cryptocurrency status provider + */ +class AmountFieldMaxAmountTransformer( + private val cryptoCurrencyStatusProvider: Provider, +) : Transformer { + + override fun transform(prevState: AmountState, value: Unit): AmountState { + val cryptoCurrencyStatus = cryptoCurrencyStatusProvider() + val amountTextField = prevState.amountTextField + + val cryptoDecimals = amountTextField.cryptoAmount.decimals + val fiatDecimals = amountTextField.fiatAmount.decimals + val decimalCryptoValue = cryptoCurrencyStatus.value.amount + val decimalFiatValue = cryptoCurrencyStatus.value.fiatAmount + + if (decimalCryptoValue.isNullOrZero()) return prevState + + val isDoneActionEnabled = !decimalCryptoValue.isNullOrZero() + val cryptoValue = decimalCryptoValue?.parseBigDecimal(cryptoDecimals).orEmpty() + val fiatValue = decimalFiatValue?.parseBigDecimal(fiatDecimals, roundingMode = RoundingMode.HALF_UP).orEmpty() + return prevState.copy( + isPrimaryButtonEnabled = true, + amountTextField = amountTextField.copy( + isValuePasted = true, + value = cryptoValue, + fiatValue = fiatValue, + isError = false, + cryptoAmount = amountTextField.cryptoAmount.copy(value = decimalCryptoValue), + fiatAmount = amountTextField.fiatAmount.copy(value = decimalFiatValue), + keyboardOptions = KeyboardOptions( + imeAction = if (isDoneActionEnabled) ImeAction.Done else ImeAction.None, + keyboardType = KeyboardType.Number, + ), + ), + ) + } +} \ No newline at end of file diff --git a/common/ui/src/main/java/com/tangem/common/ui/amountScreen/models/AmountField.kt b/common/ui/src/main/java/com/tangem/common/ui/amountScreen/models/AmountField.kt new file mode 100644 index 0000000000..252b68473b --- /dev/null +++ b/common/ui/src/main/java/com/tangem/common/ui/amountScreen/models/AmountField.kt @@ -0,0 +1,39 @@ +package com.tangem.common.ui.amountScreen.models + +import androidx.compose.foundation.text.KeyboardActions +import androidx.compose.foundation.text.KeyboardOptions +import com.tangem.core.ui.extensions.TextReference +import com.tangem.domain.tokens.model.Amount + +/** + * Model for amount field + * + * @param value entered value + * @param onValueChange on value change + * @param keyboardOptions keyboard options + * @param keyboardActions keyboard actions + * @param cryptoAmount value as amount + * @param fiatAmount value in fiat as amount + * @param isFiatValue indicates if app currency or crypto currency is selected + * @param fiatValue value in fiat + * @param isFiatUnavailable indicates if fiat rates are unavailable + * @param isValuePasted indicated if value was pasted + * @param onValuePastedTriggerDismiss on value pasted action + * @param isError indicates is value invalid + * @param error error text + */ +data class AmountField( + val value: String, + val onValueChange: (String) -> Unit, + val keyboardOptions: KeyboardOptions, + val keyboardActions: KeyboardActions, + val cryptoAmount: Amount, + val fiatAmount: Amount, + val isFiatValue: Boolean, + val fiatValue: String, + val isFiatUnavailable: Boolean, + val isValuePasted: Boolean, + val onValuePastedTriggerDismiss: () -> Unit, + val isError: Boolean, + val error: TextReference, +) \ No newline at end of file diff --git a/common/ui/src/main/java/com/tangem/common/ui/amountScreen/models/AmountSegmentedButtonsConfig.kt b/common/ui/src/main/java/com/tangem/common/ui/amountScreen/models/AmountSegmentedButtonsConfig.kt new file mode 100644 index 0000000000..733ae20d2e --- /dev/null +++ b/common/ui/src/main/java/com/tangem/common/ui/amountScreen/models/AmountSegmentedButtonsConfig.kt @@ -0,0 +1,21 @@ +package com.tangem.common.ui.amountScreen.models + +import androidx.compose.runtime.Immutable +import com.tangem.core.ui.components.currency.tokenicon.TokenIconState +import com.tangem.core.ui.extensions.TextReference + +/** + * Segmented buttons config + * + * @param title button title + * @param iconState currency icon state + * @param iconUrl currency icon url + * @param isFiat is fiat currency + */ +@Immutable +data class AmountSegmentedButtonsConfig( + val title: TextReference, + val iconState: TokenIconState? = null, + val iconUrl: String? = null, + val isFiat: Boolean, +) \ No newline at end of file diff --git a/common/ui/src/main/java/com/tangem/common/ui/amountScreen/models/AmountState.kt b/common/ui/src/main/java/com/tangem/common/ui/amountScreen/models/AmountState.kt new file mode 100644 index 0000000000..702a0bc968 --- /dev/null +++ b/common/ui/src/main/java/com/tangem/common/ui/amountScreen/models/AmountState.kt @@ -0,0 +1,32 @@ +package com.tangem.common.ui.amountScreen.models + +import androidx.compose.runtime.Stable +import com.tangem.core.ui.components.currency.tokenicon.TokenIconState +import com.tangem.core.ui.extensions.TextReference +import kotlinx.collections.immutable.PersistentList + +/** + * Model for amount state + * + * @param isPrimaryButtonEnabled indicates if next state button enabled + * @param walletName user wallet name + * @param walletBalance user crypto currency balance in wallet + * @param tokenIconState crypto currency icon state + * @param segmentedButtonConfig currency switcher config + * @param selectedButton selected currency index + * @param isSegmentedButtonsEnabled indicates if currency switches is enabled + * @param amountTextField amount field state + * @param appCurrencyCode app currency code + */ +@Stable +data class AmountState( + val isPrimaryButtonEnabled: Boolean, + val walletName: String, + val walletBalance: TextReference, + val tokenIconState: TokenIconState, + val segmentedButtonConfig: PersistentList, + val selectedButton: Int, + val isSegmentedButtonsEnabled: Boolean, + val amountTextField: AmountField, + val appCurrencyCode: String, +) \ No newline at end of file diff --git a/common/ui/src/main/java/com/tangem/common/ui/amountScreen/preview/AmountScreenClickIntentsStub.kt b/common/ui/src/main/java/com/tangem/common/ui/amountScreen/preview/AmountScreenClickIntentsStub.kt new file mode 100644 index 0000000000..9509a15aed --- /dev/null +++ b/common/ui/src/main/java/com/tangem/common/ui/amountScreen/preview/AmountScreenClickIntentsStub.kt @@ -0,0 +1,16 @@ +package com.tangem.common.ui.amountScreen.preview + +import com.tangem.common.ui.amountScreen.AmountScreenClickIntents + +object AmountScreenClickIntentsStub : AmountScreenClickIntents { + + override fun onAmountValueChange(value: String) {} + + override fun onCurrencyChangeClick(isFiat: Boolean) {} + + override fun onMaxValueClick() {} + + override fun onAmountPasteTriggerDismiss() {} + + override fun onAmountNext() {} +} \ No newline at end of file diff --git a/common/ui/src/main/java/com/tangem/common/ui/amountScreen/preview/AmountStatePreviewData.kt b/common/ui/src/main/java/com/tangem/common/ui/amountScreen/preview/AmountStatePreviewData.kt new file mode 100644 index 0000000000..db17019b58 --- /dev/null +++ b/common/ui/src/main/java/com/tangem/common/ui/amountScreen/preview/AmountStatePreviewData.kt @@ -0,0 +1,87 @@ +package com.tangem.common.ui.amountScreen.preview + +import androidx.compose.foundation.text.KeyboardActions +import androidx.compose.foundation.text.KeyboardOptions +import com.tangem.common.ui.amountScreen.models.AmountField +import com.tangem.common.ui.amountScreen.models.AmountSegmentedButtonsConfig +import com.tangem.common.ui.amountScreen.models.AmountState +import com.tangem.core.ui.components.currency.tokenicon.TokenIconState +import com.tangem.core.ui.extensions.TextReference +import com.tangem.core.ui.extensions.stringReference +import com.tangem.domain.tokens.model.Amount +import com.tangem.domain.tokens.model.AmountType +import kotlinx.collections.immutable.persistentListOf +import java.math.BigDecimal + +object AmountStatePreviewData { + + val amountState = AmountState( + isPrimaryButtonEnabled = false, + walletName = "Family Wallet", + walletBalance = stringReference("2 130,88 USDT (2 129,92 \$)"), + tokenIconState = TokenIconState.Loading, + segmentedButtonConfig = persistentListOf( + AmountSegmentedButtonsConfig( + title = stringReference("USDT"), + iconState = TokenIconState.Locked, + isFiat = false, + ), + AmountSegmentedButtonsConfig( + title = stringReference("USD"), + isFiat = true, + ), + ), + appCurrencyCode = "usd", + amountTextField = AmountField( + value = "", + onValueChange = {}, + keyboardOptions = KeyboardOptions.Default, + keyboardActions = KeyboardActions.Default, + cryptoAmount = Amount( + currencySymbol = "USDT", + value = BigDecimal.ZERO, + decimals = 18, + type = AmountType.CoinType, + ), + fiatAmount = Amount( + currencySymbol = "$", + value = BigDecimal.ZERO, + decimals = 2, + type = AmountType.CoinType, + ), + isFiatValue = false, + fiatValue = "123.123", + isFiatUnavailable = false, + isError = false, + error = TextReference.EMPTY, + isValuePasted = false, + onValuePastedTriggerDismiss = {}, + ), + isSegmentedButtonsEnabled = true, + selectedButton = 0, + ) + + val amountWithValueState = amountState.copy( + amountTextField = amountState.amountTextField.copy( + value = "100.00", + cryptoAmount = amountState.amountTextField.cryptoAmount.copy( + value = BigDecimal("100.00"), + ), + fiatValue = "99.98", + fiatAmount = amountState.amountTextField.fiatAmount.copy( + value = BigDecimal("99.98"), + ), + ), + ) + + val amountWithValueFiatState = amountWithValueState.copy( + amountTextField = amountWithValueState.amountTextField.copy(isFiatValue = false), + ) + + val amountErrorState = amountWithValueState.copy( + amountTextField = amountWithValueState.amountTextField.copy( + isError = true, + error = stringReference("Insufficient funds for transfer"), + ), + ) +} \ No newline at end of file diff --git a/common/ui/src/main/java/com/tangem/common/ui/amountScreen/ui/AmountButtons.kt b/common/ui/src/main/java/com/tangem/common/ui/amountScreen/ui/AmountButtons.kt new file mode 100644 index 0000000000..03bf6b4b69 --- /dev/null +++ b/common/ui/src/main/java/com/tangem/common/ui/amountScreen/ui/AmountButtons.kt @@ -0,0 +1,124 @@ +package com.tangem.common.ui.amountScreen.ui + +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.LazyListScope +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.hapticfeedback.HapticFeedbackType +import androidx.compose.ui.platform.LocalHapticFeedback +import androidx.compose.ui.res.stringResource +import com.tangem.common.ui.R +import com.tangem.common.ui.amountScreen.AmountScreenClickIntents +import com.tangem.common.ui.amountScreen.models.AmountSegmentedButtonsConfig +import com.tangem.core.ui.components.SpacerWMax +import com.tangem.core.ui.components.buttons.segmentedbutton.SegmentedButtons +import com.tangem.core.ui.components.currency.fiaticon.FiatIcon +import com.tangem.core.ui.components.currency.tokenicon.TokenIcon +import com.tangem.core.ui.extensions.resolveReference +import com.tangem.core.ui.res.TangemTheme +import kotlinx.collections.immutable.PersistentList + +private const val AMOUNT_BUTTONS_KEY = "amountButtonsKey" + +internal fun LazyListScope.buttons( + segmentedButtonConfig: PersistentList, + clickIntents: AmountScreenClickIntents, + isSegmentedButtonsEnabled: Boolean, + selectedButton: Int, +) { + item( + key = AMOUNT_BUTTONS_KEY, + ) { + val hapticFeedback = LocalHapticFeedback.current + Row( + modifier = Modifier.padding(top = TangemTheme.dimens.spacing12), + ) { + if (segmentedButtonConfig.isNotEmpty()) { + SegmentedButtons( + modifier = Modifier + .weight(1f) + .height(TangemTheme.dimens.size40), + config = segmentedButtonConfig, + showIndication = false, + onClick = { + hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress) + clickIntents.onCurrencyChangeClick(it.isFiat) + }, + initialSelectedItem = segmentedButtonConfig.getOrNull(selectedButton), + isEnabled = isSegmentedButtonsEnabled, + ) { + SendAmountCurrencyButton( + button = it, + isSegmentedButtonsEnabled = isSegmentedButtonsEnabled, + ) + } + } else { + SpacerWMax() + } + Text( + text = stringResource(R.string.send_max_amount), + style = TangemTheme.typography.button, + color = TangemTheme.colors.text.primary1, + modifier = Modifier + .padding(start = TangemTheme.dimens.spacing8) + .height(TangemTheme.dimens.size40) + .clip(shape = RoundedCornerShape(TangemTheme.dimens.radius26)) + .background(TangemTheme.colors.button.secondary) + .clickable { + hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress) + clickIntents.onMaxValueClick() + } + .padding( + vertical = TangemTheme.dimens.spacing10, + horizontal = TangemTheme.dimens.spacing34, + ), + ) + } + } +} + +@Composable +private fun SendAmountCurrencyButton(button: AmountSegmentedButtonsConfig, isSegmentedButtonsEnabled: Boolean) { + Row( + modifier = Modifier + .fillMaxSize() + .padding( + horizontal = TangemTheme.dimens.spacing10, + ), + horizontalArrangement = Arrangement.Center, + verticalAlignment = Alignment.CenterVertically, + ) { + val iconModifier = Modifier + .size(TangemTheme.dimens.size18) + .padding(horizontal = TangemTheme.dimens.spacing1) + if (button.isFiat) { + FiatIcon( + url = button.iconUrl, + size = TangemTheme.dimens.size18, + isGrayscale = !isSegmentedButtonsEnabled, + modifier = iconModifier, + ) + } else if (button.iconState != null) { + TokenIcon( + state = button.iconState, + shouldDisplayNetwork = false, + modifier = iconModifier, + ) + } + Text( + text = button.title.resolveReference(), + color = TangemTheme.colors.text.primary1, + style = TangemTheme.typography.button, + modifier = Modifier + .padding( + start = TangemTheme.dimens.spacing8, + ), + ) + } +} \ No newline at end of file diff --git a/common/ui/src/main/java/com/tangem/common/ui/amountScreen/ui/AmountField.kt b/common/ui/src/main/java/com/tangem/common/ui/amountScreen/ui/AmountField.kt new file mode 100644 index 0000000000..0d67797f50 --- /dev/null +++ b/common/ui/src/main/java/com/tangem/common/ui/amountScreen/ui/AmountField.kt @@ -0,0 +1,136 @@ +package com.tangem.common.ui.amountScreen.ui + +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.requiredHeightIn +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment.Companion.BottomCenter +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 androidx.compose.ui.text.style.TextDirection +import com.tangem.common.ui.amountScreen.models.AmountField +import com.tangem.core.ui.components.fields.AmountTextField +import com.tangem.core.ui.components.fields.visualtransformations.AmountVisualTransformation +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.BigDecimalFormatter +import com.tangem.core.ui.utils.rememberDecimalFormat +import kotlinx.coroutines.delay + +@Composable +internal fun AmountField(sendField: AmountField, appCurrencyCode: String) { + val decimalFormat = rememberDecimalFormat() + val isFiatValue = sendField.isFiatValue + val currencyCode = if (isFiatValue) appCurrencyCode else null + val (primaryAmount, primaryValue) = if (isFiatValue) { + sendField.fiatAmount to sendField.fiatValue + } else { + sendField.cryptoAmount to sendField.value + } + val requester = remember { FocusRequester() } + + AmountTextField( + value = primaryValue, + decimals = primaryAmount.decimals, + visualTransformation = AmountVisualTransformation( + decimals = primaryAmount.decimals, + symbol = primaryAmount.currencySymbol, + currencyCode = currencyCode, + decimalFormat = decimalFormat, + ), + onValueChange = sendField.onValueChange, + keyboardOptions = sendField.keyboardOptions, + keyboardActions = sendField.keyboardActions, + textStyle = TangemTheme.typography.h2.copy( + color = TangemTheme.colors.text.primary1, + textAlign = TextAlign.Center, + ), + isAutoResize = true, + isValuePasted = sendField.isValuePasted, + onValuePastedTriggerDismiss = sendField.onValuePastedTriggerDismiss, + modifier = Modifier + .focusRequester(requester) + .padding( + top = TangemTheme.dimens.spacing24, + start = TangemTheme.dimens.spacing12, + end = TangemTheme.dimens.spacing12, + ) + .requiredHeightIn(min = TangemTheme.dimens.size32), + ) + + LaunchedEffect(key1 = Unit) { + delay(timeMillis = 200) + requester.requestFocus() + } + + AmountSecondary(sendField, appCurrencyCode) +} + +@Composable +private fun AmountSecondary(sendField: AmountField, appCurrencyCode: String) { + val secondaryAmount = if (sendField.isFiatValue) sendField.cryptoAmount else sendField.fiatAmount + Box( + modifier = Modifier + .padding( + top = TangemTheme.dimens.spacing8, + start = TangemTheme.dimens.spacing12, + end = TangemTheme.dimens.spacing12, + ), + ) { + val text = if (sendField.isFiatValue) { + BigDecimalFormatter.formatCryptoAmount( + cryptoAmount = secondaryAmount.value, + cryptoCurrency = secondaryAmount.currencySymbol, + decimals = secondaryAmount.decimals, + ) + } else { + BigDecimalFormatter.formatFiatAmount( + fiatAmount = secondaryAmount.value, + fiatCurrencySymbol = secondaryAmount.currencySymbol, + fiatCurrencyCode = appCurrencyCode, + ) + } + Text( + text = text, + style = TangemTheme.typography.caption2.copy(textDirection = TextDirection.ContentOrLtr), + color = TangemTheme.colors.text.tertiary, + textAlign = TextAlign.Center, + modifier = Modifier + .align(BottomCenter) + .padding(bottom = TangemTheme.dimens.spacing32), + ) + AmountFieldError( + isError = sendField.isError, + error = sendField.error, + modifier = Modifier + .align(BottomCenter) + .padding(bottom = TangemTheme.dimens.spacing12), + ) + } +} + +@Composable +private fun AmountFieldError(isError: Boolean, error: TextReference, modifier: Modifier = Modifier) { + AnimatedVisibility( + visible = isError, + enter = fadeIn(), + exit = fadeOut(), + modifier = modifier, + ) { + Text( + text = error.resolveReference(), + style = TangemTheme.typography.caption2, + color = TangemTheme.colors.text.warning, + textAlign = TextAlign.Center, + ) + } +} \ No newline at end of file diff --git a/common/ui/src/main/java/com/tangem/common/ui/amountScreen/ui/AmountFieldContainer.kt b/common/ui/src/main/java/com/tangem/common/ui/amountScreen/ui/AmountFieldContainer.kt new file mode 100644 index 0000000000..add0475c98 --- /dev/null +++ b/common/ui/src/main/java/com/tangem/common/ui/amountScreen/ui/AmountFieldContainer.kt @@ -0,0 +1,69 @@ +package com.tangem.common.ui.amountScreen.ui + +import androidx.compose.animation.AnimatedContent +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyListScope +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Text +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.text.style.TextAlign +import com.tangem.common.ui.amountScreen.models.AmountState +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.utils.Strings.STARS + +private const val AMOUNT_FIELD_KEY = "amountFieldKey" + +internal fun LazyListScope.amountField( + amountState: AmountState, + isBalanceHiding: Boolean, + modifier: Modifier = Modifier, +) { + item(key = AMOUNT_FIELD_KEY) { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + modifier = modifier + .fillMaxWidth() + .clip(RoundedCornerShape(TangemTheme.dimens.radius16)) + .background(TangemTheme.colors.background.action), + ) { + Text( + text = amountState.walletName, + style = TangemTheme.typography.subtitle2, + color = TangemTheme.colors.text.tertiary, + modifier = Modifier + .padding(top = TangemTheme.dimens.spacing14), + ) + + val balance = if (isBalanceHiding) STARS else amountState.walletBalance.resolveReference() + AnimatedContent( + targetState = balance, + label = "Hide Balance Animation", + ) { + Text( + text = it, + style = TangemTheme.typography.caption2, + color = TangemTheme.colors.text.tertiary, + textAlign = TextAlign.Center, + modifier = Modifier + .padding(top = TangemTheme.dimens.spacing2), + ) + } + TokenIcon( + state = amountState.tokenIconState, + modifier = Modifier + .padding(top = TangemTheme.dimens.spacing32), + ) + AmountField( + sendField = amountState.amountTextField, + appCurrencyCode = amountState.appCurrencyCode, + ) + } + } +} \ No newline at end of file diff --git a/common/ui/src/main/java/com/tangem/common/ui/amountScreen/utils/AmountUtils.kt b/common/ui/src/main/java/com/tangem/common/ui/amountScreen/utils/AmountUtils.kt new file mode 100644 index 0000000000..2ef758b1a7 --- /dev/null +++ b/common/ui/src/main/java/com/tangem/common/ui/amountScreen/utils/AmountUtils.kt @@ -0,0 +1,59 @@ +package com.tangem.common.ui.amountScreen.utils + +import androidx.compose.ui.text.input.ImeAction +import com.tangem.common.ui.amountScreen.models.AmountField +import com.tangem.core.ui.utils.parseBigDecimal +import com.tangem.core.ui.utils.parseToBigDecimal +import com.tangem.domain.tokens.model.CryptoCurrencyStatus +import com.tangem.utils.isNullOrZero +import java.math.BigDecimal +import java.math.RoundingMode + +internal fun String.getCryptoValue(fiatRate: BigDecimal?, isFiatValue: Boolean, decimals: Int): String { + return if (isFiatValue && fiatRate != null) { + parseToBigDecimal(decimals).divide(fiatRate, decimals, RoundingMode.DOWN) + .parseBigDecimal(decimals) + } else { + this + } +} + +internal fun String.getFiatValue( + fiatRate: BigDecimal?, + isFiatValue: Boolean, + decimals: Int, +): Pair { + return if (fiatRate != null) { + val fiatValue = if (!isFiatValue) { + parseToBigDecimal(decimals).multiply(fiatRate).parseBigDecimal(decimals) + } else { + this + } + val decimalFiatValue = fiatValue.parseToBigDecimal(decimals) + fiatValue to decimalFiatValue + } else { + "" to null + } +} + +internal fun String.checkExceedBalance( + cryptoCurrencyStatus: CryptoCurrencyStatus, + amountTextField: AmountField, +): Boolean { + val currencyCryptoAmount = cryptoCurrencyStatus.value.amount ?: BigDecimal.ZERO + val currencyFiatAmount = cryptoCurrencyStatus.value.fiatAmount ?: BigDecimal.ZERO + val fiatDecimal = parseToBigDecimal(amountTextField.fiatAmount.decimals) + val cryptoDecimal = parseToBigDecimal(amountTextField.cryptoAmount.decimals) + return if (amountTextField.isFiatValue) { + fiatDecimal > currencyFiatAmount + } else { + cryptoDecimal > currencyCryptoAmount + } +} + +internal fun getKeyboardAction(isExceedBalance: Boolean, decimalCryptoValue: BigDecimal) = + if (!isExceedBalance && !decimalCryptoValue.isNullOrZero()) { + ImeAction.Done + } else { + ImeAction.None + } \ No newline at end of file