Updated on 2026-08-14
This commit is contained in:
parent
657e7cd3d0
commit
4eead599f3
8 changed files with 163 additions and 73 deletions
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
@ -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())
|
||||
|
|
|
|||
|
|
@ -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?>,
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue