From 4eead599f344f7bbc97ca2281dda33a081677f04 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 7 Feb 2024 17:45:33 +0300 Subject: [PATCH] Updated on 2026-08-14 --- .../ui/components/fields/SimpleTextField.kt | 77 ++++++++++++------- .../state/SendEventStateFactory.kt | 4 +- .../presentation/state/SendStateFactory.kt | 32 ++------ .../state/amount/AmountStateFactory.kt | 44 +++++++++++ .../presentation/state/fee/FeeStateFactory.kt | 4 +- .../SendAmountFieldMaxAmountConverter.kt | 44 +++++++++++ .../presentation/ui/amount/AmountField.kt | 6 +- .../presentation/viewmodel/SendViewModel.kt | 25 +++--- 8 files changed, 163 insertions(+), 73 deletions(-) create mode 100644 features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/AmountStateFactory.kt create mode 100644 features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldMaxAmountConverter.kt diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/fields/SimpleTextField.kt b/core/ui/src/main/java/com/tangem/core/ui/components/fields/SimpleTextField.kt index a85e0eaafc..4a3fdbff20 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/fields/SimpleTextField.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/fields/SimpleTextField.kt @@ -45,26 +45,31 @@ fun SimpleTextField( mutableStateOf( TextFieldValue( text = value, - selection = when { - value.isEmpty() -> TextRange.Zero - else -> TextRange(value.length, value.length) - }, + selection = getValueRange(value), ), ) } val focusRequester = remember { FocusRequester.Default } val customTextSelectionColors = TextSelectionColors( - handleColor = TangemTheme.colors.text.secondary, - backgroundColor = TangemTheme.colors.text.secondary.copy(alpha = 0.4f), + handleColor = TangemTheme.colors.text.accent, + backgroundColor = TangemTheme.colors.text.accent.copy(alpha = 0.3f), ) val textFieldValue = textFieldValueState.copy(text = value) - SideEffect { - if (textFieldValue.selection != textFieldValueState.selection || - textFieldValue.composition != textFieldValueState.composition - ) { - textFieldValueState = textFieldValue + val isSelectionChanged by remember { + derivedStateOf { + textFieldValue.selection != textFieldValueState.selection || + textFieldValue.composition != textFieldValueState.composition || + textFieldValue.text != textFieldValueState.text + } + } + + LaunchedEffect(key1 = isSelectionChanged) { + if (isSelectionChanged) { + textFieldValueState = textFieldValue.copy( + selection = getValueRange(value), + ) } } @@ -91,24 +96,44 @@ fun SimpleTextField( keyboardOptions = keyboardOptions, keyboardActions = keyboardActions, decorationBox = decorationBox ?: { textValue -> - Box { - if (value.isBlank() && placeholder != null) { - AnimatedContent( - targetState = placeholder, - label = "Placeholder Change Animation", - ) { - Text( - text = it.resolveReference(), - style = textStyle, - color = TangemTheme.colors.text.disabled, - ) - } - } - textValue() - } + SimpleTextPlaceholder( + placeholder = placeholder, + value = value, + textStyle = textStyle, + textValue = textValue, + ) }, modifier = modifier .focusRequester(focusRequester), ) } +} + +private fun getValueRange(value: String) = when { + value.isEmpty() -> TextRange.Zero + else -> TextRange(value.length, value.length) +} + +@Composable +private fun SimpleTextPlaceholder( + placeholder: TextReference?, + value: String, + textStyle: TextStyle, + textValue: @Composable () -> Unit, +) { + Box { + if (value.isBlank() && placeholder != null) { + AnimatedContent( + targetState = placeholder, + label = "Placeholder Change Animation", + ) { + Text( + text = it.resolveReference(), + style = textStyle, + color = TangemTheme.colors.text.disabled, + ) + } + } + textValue() + } } \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendEventStateFactory.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendEventStateFactory.kt index a175478e41..2faa56655f 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendEventStateFactory.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendEventStateFactory.kt @@ -23,7 +23,9 @@ internal class SendEventStateFactory( private val clickIntents: SendClickIntents, private val feeStateFactory: FeeStateFactory, ) { - private val sendTransactionErrorConverter by lazy { SendTransactionAlertConverter(clickIntents) } + private val sendTransactionErrorConverter by lazy(LazyThreadSafetyMode.NONE) { + SendTransactionAlertConverter(clickIntents) + } fun onConsumeEventState(): SendUiState { return currentStateProvider().copy(event = consumedEvent()) 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 31cfb3ef91..57a1a698ae 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,10 +14,8 @@ 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 import com.tangem.features.send.impl.presentation.state.fields.SendAmountFieldConverter import com.tangem.features.send.impl.presentation.state.recipient.SendRecipientListConverter import com.tangem.features.send.impl.presentation.state.recipient.SendRecipientStateConverter @@ -38,29 +36,17 @@ internal class SendStateFactory( private val validateWalletMemoUseCase: ValidateWalletMemoUseCase, private val getExplorerTransactionUrlUseCase: GetExplorerTransactionUrlUseCase, ) { - private val iconStateConverter by lazy(::CryptoCurrencyToIconStateConverter) - private val amountFieldConverter by lazy { + + private val amountFieldConverter by lazy(LazyThreadSafetyMode.NONE) { 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 { + private val amountStateConverter by lazy(LazyThreadSafetyMode.NONE) { SendAmountStateConverter( appCurrencyProvider = appCurrencyProvider, iconStateConverter = iconStateConverter, @@ -69,20 +55,20 @@ internal class SendStateFactory( cryptoCurrencyStatusProvider = cryptoCurrencyStatusProvider, ) } - private val recipientStateConverter by lazy { + private val recipientStateConverter by lazy(LazyThreadSafetyMode.NONE) { SendRecipientStateConverter( clickIntents = clickIntents, cryptoCurrencyStatusProvider = cryptoCurrencyStatusProvider, ) } - private val feeStateConverter by lazy { + private val feeStateConverter by lazy(LazyThreadSafetyMode.NONE) { SendFeeStateConverter( appCurrencyProvider = appCurrencyProvider, cryptoCurrencyStatusProvider = cryptoCurrencyStatusProvider, ) } - private val recipientListStateConverter by lazy { + private val recipientListStateConverter by lazy(LazyThreadSafetyMode.NONE) { SendRecipientListConverter( currentStateProvider = currentStateProvider, cryptoCurrencyStatusProvider = cryptoCurrencyStatusProvider, @@ -122,12 +108,6 @@ internal class SendStateFactory( } //endregion - //region amount state clicks - fun getOnAmountValueChange(value: String) = amountFieldChangeConverter.convert(value) - - fun getOnCurrencyChangedState(isFiat: Boolean) = amountCurrencyConverter.convert(isFiat) - //endregion - //region recipient fun onLoadedRecipientList( wallets: List, diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/AmountStateFactory.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/AmountStateFactory.kt new file mode 100644 index 0000000000..6f56b6aff3 --- /dev/null +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/AmountStateFactory.kt @@ -0,0 +1,44 @@ +package com.tangem.features.send.impl.presentation.state.amount + +import com.tangem.domain.tokens.model.CryptoCurrencyStatus +import com.tangem.features.send.impl.presentation.state.SendUiState +import com.tangem.features.send.impl.presentation.state.fields.SendAmountFieldChangeConverter +import com.tangem.features.send.impl.presentation.state.fields.SendAmountFieldMaxAmountConverter +import com.tangem.utils.Provider + +/** + * Factory to produce amount state for [SendUiState] + */ +internal class AmountStateFactory( + private val currentStateProvider: Provider, + private val cryptoCurrencyStatusProvider: Provider, +) { + + private val amountFieldChangeConverter by lazy(LazyThreadSafetyMode.NONE) { + SendAmountFieldChangeConverter( + currentStateProvider = currentStateProvider, + cryptoCurrencyStatusProvider = cryptoCurrencyStatusProvider, + ) + } + private val amountFieldMaxAmountConverter by lazy(LazyThreadSafetyMode.NONE) { + SendAmountFieldMaxAmountConverter( + currentStateProvider = currentStateProvider, + cryptoCurrencyStatusProvider = cryptoCurrencyStatusProvider, + ) + } + + private val amountCurrencyConverter by lazy(LazyThreadSafetyMode.NONE) { + SendAmountCurrencyConverter( + currentStateProvider = currentStateProvider, + cryptoCurrencyStatusProvider = cryptoCurrencyStatusProvider, + ) + } + + fun getOnAmountValueChange(value: String) = amountFieldChangeConverter.convert(value) + + fun getOnMaxAmountClick(): SendUiState { + return amountFieldMaxAmountConverter.convert(Unit) + } + + fun getOnCurrencyChangedState(isFiat: Boolean) = amountCurrencyConverter.convert(isFiat) +} \ No newline at end of file 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 fcbfd66705..91ad0f8eba 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 @@ -27,7 +27,7 @@ internal class FeeStateFactory( private val appCurrencyProvider: Provider, private val isFeeApproximateUseCase: IsFeeApproximateUseCase, ) { - private val customFeeFieldConverter by lazy { + private val customFeeFieldConverter by lazy(LazyThreadSafetyMode.NONE) { SendFeeCustomFieldConverter( clickIntents = clickIntents, appCurrencyProvider = appCurrencyProvider, @@ -35,7 +35,7 @@ internal class FeeStateFactory( ) } - val feeConverter by lazy { + val feeConverter by lazy(LazyThreadSafetyMode.NONE) { FeeConverter( clickIntents = clickIntents, appCurrencyProvider = appCurrencyProvider, diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldMaxAmountConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldMaxAmountConverter.kt new file mode 100644 index 0000000000..6ca3cb3d93 --- /dev/null +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldMaxAmountConverter.kt @@ -0,0 +1,44 @@ +package com.tangem.features.send.impl.presentation.state.fields + +import com.tangem.core.ui.utils.parseBigDecimal +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 + +internal class SendAmountFieldMaxAmountConverter( + private val currentStateProvider: Provider, + private val cryptoCurrencyStatusProvider: Provider, +) : Converter { + + override fun convert(value: Unit): SendUiState { + val state = currentStateProvider() + val cryptoCurrencyStatus = cryptoCurrencyStatusProvider() + val amountState = state.amountState ?: return state + val amountTextField = amountState.amountTextField + val feeState = state.feeState ?: return state + + val cryptoDecimals = amountTextField.cryptoAmount.decimals + val fiatDecimals = amountTextField.fiatAmount.decimals + val decimalCryptoValue = cryptoCurrencyStatus.value.amount + val decimalFiatValue = cryptoCurrencyStatus.value.fiatAmount + + val cryptoValue = decimalCryptoValue?.parseBigDecimal(cryptoDecimals).orEmpty() + val fiatValue = decimalFiatValue?.parseBigDecimal(fiatDecimals).orEmpty() + return state.copy( + amountState = amountState.copy( + isPrimaryButtonEnabled = true, + amountTextField = amountTextField.copy( + value = cryptoValue, + fiatValue = fiatValue, + isError = false, + cryptoAmount = amountTextField.cryptoAmount.copy(value = decimalCryptoValue), + fiatAmount = amountTextField.fiatAmount.copy(value = decimalFiatValue), + ), + ), + feeState = feeState.copy( + isSubtract = true, + ), + ) + } +} \ 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 b18159fd89..bd6b8b54e1 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 @@ -28,10 +28,10 @@ internal fun AmountField(sendField: SendTextField.AmountField, isFiat: Boolean) sendField.value to sendField.fiatValue } - val (primaryAmount, secondaryAmount) = if (!isFiat) { - sendField.cryptoAmount to sendField.fiatAmount - } else { + val (primaryAmount, secondaryAmount) = if (isFiat) { sendField.fiatAmount to sendField.cryptoAmount + } else { + sendField.cryptoAmount to sendField.fiatAmount } AmountTextField( diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt index cb977c38f3..d6e32da750 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt @@ -9,8 +9,6 @@ import arrow.core.Either import arrow.core.getOrElse import com.tangem.blockchain.common.TransactionData import com.tangem.blockchain.common.transaction.TransactionFee -import com.tangem.common.extensions.isZero -import com.tangem.core.ui.utils.parseBigDecimal import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase @@ -38,6 +36,7 @@ import com.tangem.features.send.api.navigation.SendRouter import com.tangem.features.send.impl.navigation.InnerSendRouter import com.tangem.features.send.impl.presentation.domain.AvailableWallet import com.tangem.features.send.impl.presentation.state.* +import com.tangem.features.send.impl.presentation.state.amount.AmountStateFactory import com.tangem.features.send.impl.presentation.state.fee.FeeNotificationFactory import com.tangem.features.send.impl.presentation.state.fee.FeeSelectorState import com.tangem.features.send.impl.presentation.state.fee.FeeStateFactory @@ -110,6 +109,11 @@ internal class SendViewModel @Inject constructor( getExplorerTransactionUrlUseCase = getExplorerTransactionUrlUseCase, ) + private val amountStateFactory = AmountStateFactory( + currentStateProvider = Provider { uiState }, + cryptoCurrencyStatusProvider = Provider { cryptoCurrencyStatus }, + ) + private val feeStateFactory = FeeStateFactory( clickIntents = this, currentStateProvider = Provider { uiState }, @@ -403,24 +407,15 @@ internal class SendViewModel @Inject constructor( // region amount state clicks override fun onCurrencyChangeClick(isFiat: Boolean) { - uiState = stateFactory.getOnCurrencyChangedState(isFiat) + uiState = amountStateFactory.getOnCurrencyChangedState(isFiat) } override fun onAmountValueChange(value: String) { - uiState = stateFactory.getOnAmountValueChange(value) + uiState = amountStateFactory.getOnAmountValueChange(value) } override fun onMaxValueClick() { - val amountState = uiState.amountState ?: return - val amountTextField = amountState.amountTextField - val (amount, decimals) = if (amountTextField.isFiatValue) { - cryptoCurrencyStatus.value.fiatAmount to amountTextField.fiatAmount.decimals - } else { - cryptoCurrencyStatus.value.amount to amountTextField.cryptoAmount.decimals - } - if (amount != null && !amount.isZero()) { - onAmountValueChange(amount.parseBigDecimal(decimals)) - } + uiState = amountStateFactory.getOnMaxAmountClick() } // endregion @@ -558,7 +553,7 @@ internal class SendViewModel @Inject constructor( override fun onExploreClick(txUrl: String) = innerRouter.openUrl(txUrl) override fun onAmountReduceClick(reducedAmount: String) { - uiState = stateFactory.getOnAmountValueChange(reducedAmount) + uiState = amountStateFactory.getOnAmountValueChange(reducedAmount) uiState = sendNotificationFactory.dismissHighFeeWarningState() loadFee() }