Updated on 2026-08-14

This commit is contained in:
Tangem 2024-04-24 14:38:19 +05:00
commit b66f9fc50b
13 changed files with 70 additions and 35 deletions

View file

@ -64,6 +64,8 @@ fun AmountTextField(
keyboardActions: KeyboardActions = KeyboardActions.Default,
isEnabled: Boolean = true,
isAutoResize: Boolean = false,
isValuePasted: Boolean = false,
onValuePastedTriggerDismiss: () -> Unit = {},
@FloatRange(from = 0.0, to = 1.0, fromInclusive = false, toInclusive = false)
reduceFactor: Double = 0.9,
) {
@ -101,6 +103,8 @@ fun AmountTextField(
fontSize = fontSize,
textDirection = TextDirection.ContentOrLtr,
),
isValuePasted = isValuePasted,
onValuePastedTriggerDismiss = onValuePastedTriggerDismiss,
color = textColor,
keyboardOptions = keyboardOptions,
keyboardActions = keyboardActions,

View file

@ -39,15 +39,13 @@ fun SimpleTextField(
color: Color = TangemTheme.colors.text.primary1,
textStyle: TextStyle = TangemTheme.typography.body2.copy(color = color),
readOnly: Boolean = false,
isValuePasted: Boolean = false,
onValuePastedTriggerDismiss: () -> Unit = {},
decorationBox: (@Composable (innerTextField: @Composable () -> Unit) -> Unit)? = null,
) {
val proxyValue by remember(value) { derivedStateOf { value } }
var textFieldValueState by remember {
mutableStateOf(
TextFieldValue(
text = value,
selection = getValueRange(value),
),
)
mutableStateOf(TextFieldValue(text = value))
}
val focusRequester = remember { FocusRequester.Default }
val customTextSelectionColors = TextSelectionColors(
@ -55,36 +53,28 @@ fun SimpleTextField(
backgroundColor = TangemTheme.colors.text.accent.copy(alpha = 0.3f),
)
val textFieldValue = textFieldValueState.copy(text = value)
val isSelectionChanged by remember {
derivedStateOf {
textFieldValue.selection != textFieldValueState.selection ||
textFieldValue.composition != textFieldValueState.composition ||
textFieldValue.text != textFieldValueState.text
}
var lastTextValue by remember(proxyValue, isValuePasted) {
textFieldValueState = textFieldValueState.copy(
text = proxyValue,
selection = if (isValuePasted) {
TextRange(proxyValue.length, proxyValue.length)
} else {
textFieldValueState.selection
},
)
mutableStateOf(proxyValue)
}
LaunchedEffect(key1 = isSelectionChanged) {
if (isSelectionChanged) {
textFieldValueState = textFieldValue
// resets paste value cursor trigger
LaunchedEffect(key1 = isValuePasted) {
if (isValuePasted) {
onValuePastedTriggerDismiss()
}
}
var lastTextValue by remember(value) {
val isSelectionLastIndex = textFieldValueState.selection.end == textFieldValueState.text.lastIndex
if (textFieldValueState.text.isBlank() || isSelectionLastIndex) {
textFieldValueState = textFieldValueState.copy(
text = value,
selection = getValueRange(value),
)
}
mutableStateOf(value)
}
CompositionLocalProvider(LocalTextSelectionColors provides customTextSelectionColors) {
BasicTextField(
value = textFieldValue,
value = textFieldValueState,
onValueChange = { newTextFieldValueState ->
textFieldValueState = newTextFieldValueState
@ -114,11 +104,6 @@ fun SimpleTextField(
}
}
private fun getValueRange(value: String) = when {
value.isEmpty() -> TextRange.Zero
else -> TextRange(value.length, value.length)
}
@Composable
private fun SimpleTextPlaceholder(
placeholder: TextReference?,

View file

@ -33,6 +33,11 @@ internal class AmountStateFactory(
cryptoCurrencyStatusProvider = cryptoCurrencyStatusProvider,
)
}
private val amountPasteConverter by lazy(LazyThreadSafetyMode.NONE) {
SendAmountPastedTriggerDismissConverter(
currentStateProvider = currentStateProvider,
)
}
fun getOnAmountValueChange(value: String) = amountFieldChangeConverter.convert(value)
@ -41,4 +46,6 @@ internal class AmountStateFactory(
}
fun getOnCurrencyChangedState(isFiat: Boolean) = amountCurrencyConverter.convert(isFiat)
fun getOnAmountPastedTriggerDismiss() = amountPasteConverter.convert(false)
}

View file

@ -24,6 +24,7 @@ internal class SendAmountCurrencyConverter(
amountState = amountState.copy(
amountTextField = amountTextField.copy(
isFiatValue = value,
isValuePasted = true,
),
),
)

View file

@ -0,0 +1,21 @@
package com.tangem.features.send.impl.presentation.state.amount
import com.tangem.features.send.impl.presentation.state.SendUiState
import com.tangem.utils.Provider
import com.tangem.utils.converter.Converter
internal class SendAmountPastedTriggerDismissConverter(
private val currentStateProvider: Provider<SendUiState>,
) : Converter<Boolean, SendUiState> {
override fun convert(value: Boolean): SendUiState {
val state = currentStateProvider()
val amountState = state.amountState ?: return state
return state.copy(
amountState = amountState.copy(
amountTextField = amountState.amountTextField.copy(
isValuePasted = false,
),
),
)
}
}

View file

@ -53,8 +53,10 @@ internal class SendAmountFieldConverter(
cryptoAmount = cryptoAmount,
fiatAmount = getAppCurrencyAmount(fiatDecimal, appCurrencyProvider()),
isError = false,
error = TextReference.Res(R.string.swapping_insufficient_funds),
error = TextReference.Res(R.string.send_validation_amount_exceeds_balance),
isFiatUnavailable = fiatRate == null,
isValuePasted = false,
onValuePastedTriggerDismiss = clickIntents::onAmountPasteTriggerDismiss,
)
}

View file

@ -35,6 +35,7 @@ internal class SendAmountFieldMaxAmountConverter(
amountState = amountState.copy(
isPrimaryButtonEnabled = true,
amountTextField = amountTextField.copy(
isValuePasted = true,
value = cryptoValue,
fiatValue = fiatValue,
isError = false,

View file

@ -28,6 +28,8 @@ internal sealed class SendTextField {
val isFiatValue: Boolean,
val fiatValue: String,
val isFiatUnavailable: Boolean,
val isValuePasted: Boolean,
val onValuePastedTriggerDismiss: () -> Unit,
val isError: Boolean,
val error: TextReference,
) : SendTextField()

View file

@ -45,6 +45,8 @@ internal object AmountStatePreviewData {
isFiatUnavailable = false,
isError = false,
error = TextReference.EMPTY,
isValuePasted = false,
onValuePastedTriggerDismiss = {},
),
isSegmentedButtonsEnabled = true,
)

View file

@ -30,6 +30,8 @@ internal object SendClickIntentsStub : SendClickIntents {
override fun onMaxValueClick() {}
override fun onAmountPasteTriggerDismiss() {}
override fun onRecipientAddressValueChange(value: String, type: EnterAddressSource?) {}
override fun onRecipientMemoValueChange(value: String) {}

View file

@ -53,6 +53,8 @@ internal fun AmountField(sendField: SendTextField.AmountField, appCurrencyCode:
textAlign = TextAlign.Center,
),
isAutoResize = true,
isValuePasted = sendField.isValuePasted,
onValuePastedTriggerDismiss = sendField.onValuePastedTriggerDismiss,
modifier = Modifier
.focusRequester(requester)
.padding(

View file

@ -30,6 +30,8 @@ internal interface SendClickIntents {
fun onCurrencyChangeClick(isFiat: Boolean)
fun onMaxValueClick()
fun onAmountPasteTriggerDismiss()
// endregion
// region Recipient

View file

@ -551,6 +551,10 @@ internal class SendViewModel @Inject constructor(
uiState = amountStateFactory.getOnMaxAmountClick()
analyticsEventHandler.send(SendAnalyticEvents.MaxAmountButtonClicked)
}
override fun onAmountPasteTriggerDismiss() {
uiState = amountStateFactory.getOnAmountPastedTriggerDismiss()
}
// endregion
// region recipient state clicks