Updated on 2026-08-14

This commit is contained in:
Tangem 2024-02-07 17:45:33 +03:00
parent 657e7cd3d0
commit 4eead599f3
8 changed files with 163 additions and 73 deletions

View file

@ -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()
}
}

View file

@ -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())

View file

@ -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<AvailableWallet?>,

View file

@ -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<SendUiState>,
private val cryptoCurrencyStatusProvider: Provider<CryptoCurrencyStatus>,
) {
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)
}

View file

@ -27,7 +27,7 @@ internal class FeeStateFactory(
private val appCurrencyProvider: Provider<AppCurrency>,
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,

View file

@ -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<SendUiState>,
private val cryptoCurrencyStatusProvider: Provider<CryptoCurrencyStatus>,
) : Converter<Unit, SendUiState> {
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,
),
)
}
}

View file

@ -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(

View file

@ -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()
}