summaryrefslogtreecommitdiff
path: root/wallet/src/main/java/net/taler
diff options
context:
space:
mode:
authorIván Ávalos <avalos@disroot.org>2023-09-15 00:01:49 -0600
committerTorsten Grote <t@grobox.de>2023-09-26 18:30:52 +0200
commit66d96c5b18c878f545c2081ed5526271dd39125b (patch)
treea9ad3ab4f4bbf160eb2e5201c0360da65ab38a00 /wallet/src/main/java/net/taler
parent138ea1388d4da7b6ca50a16e369d8e45a670089f (diff)
downloadtaler-android-66d96c5b18c878f545c2081ed5526271dd39125b.tar.gz
taler-android-66d96c5b18c878f545c2081ed5526271dd39125b.tar.bz2
taler-android-66d96c5b18c878f545c2081ed5526271dd39125b.zip
[wallet] Improved AmountInputField with a VisualTransformation
Diffstat (limited to 'wallet/src/main/java/net/taler')
-rw-r--r--wallet/src/main/java/net/taler/wallet/ReceiveFundsFragment.kt2
-rw-r--r--wallet/src/main/java/net/taler/wallet/SendFundsFragment.kt2
-rw-r--r--wallet/src/main/java/net/taler/wallet/compose/AmountInputField.kt82
-rw-r--r--wallet/src/main/java/net/taler/wallet/deposit/PayToUriFragment.kt2
4 files changed, 69 insertions, 19 deletions
diff --git a/wallet/src/main/java/net/taler/wallet/ReceiveFundsFragment.kt b/wallet/src/main/java/net/taler/wallet/ReceiveFundsFragment.kt
index 1511128..e560a71 100644
--- a/wallet/src/main/java/net/taler/wallet/ReceiveFundsFragment.kt
+++ b/wallet/src/main/java/net/taler/wallet/ReceiveFundsFragment.kt
@@ -124,7 +124,7 @@ private fun ReceiveFundsIntro(
.fillMaxWidth()
.verticalScroll(scrollState),
) {
- var text by rememberSaveable { mutableStateOf("") }
+ var text by rememberSaveable { mutableStateOf("0") }
var isError by rememberSaveable { mutableStateOf(false) }
Row(
verticalAlignment = Alignment.CenterVertically,
diff --git a/wallet/src/main/java/net/taler/wallet/SendFundsFragment.kt b/wallet/src/main/java/net/taler/wallet/SendFundsFragment.kt
index 2e5eb52..b33e53b 100644
--- a/wallet/src/main/java/net/taler/wallet/SendFundsFragment.kt
+++ b/wallet/src/main/java/net/taler/wallet/SendFundsFragment.kt
@@ -105,7 +105,7 @@ private fun SendFundsIntro(
.fillMaxWidth()
.verticalScroll(scrollState),
) {
- var text by rememberSaveable { mutableStateOf("") }
+ var text by rememberSaveable { mutableStateOf("0") }
var isError by rememberSaveable { mutableStateOf(false) }
var insufficientBalance by rememberSaveable { mutableStateOf(false) }
Row(
diff --git a/wallet/src/main/java/net/taler/wallet/compose/AmountInputField.kt b/wallet/src/main/java/net/taler/wallet/compose/AmountInputField.kt
index df82546..a9503d7 100644
--- a/wallet/src/main/java/net/taler/wallet/compose/AmountInputField.kt
+++ b/wallet/src/main/java/net/taler/wallet/compose/AmountInputField.kt
@@ -22,18 +22,24 @@ import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.LocalTextStyle
import androidx.compose.material3.OutlinedTextField
-import androidx.compose.material3.Text
import androidx.compose.material3.TextFieldColors
import androidx.compose.material3.TextFieldDefaults
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Shape
+import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.input.KeyboardType
+import androidx.compose.ui.text.input.OffsetMapping
+import androidx.compose.ui.text.input.TransformedText
import androidx.compose.ui.text.input.VisualTransformation
import net.taler.common.Amount
+import java.text.DecimalFormat
@OptIn(ExperimentalMaterial3Api::class)
@Composable
@@ -49,28 +55,20 @@ fun AmountInputField(
trailingIcon: @Composable (() -> Unit)? = null,
supportingText: @Composable (() -> Unit)? = null,
isError: Boolean = false,
- visualTransformation: VisualTransformation = VisualTransformation.None,
keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
keyboardActions: KeyboardActions = KeyboardActions.Default,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
shape: Shape = TextFieldDefaults.outlinedShape,
colors: TextFieldColors = TextFieldDefaults.outlinedTextFieldColors()
) {
+ val decimalSeparator = DecimalFormat().decimalFormatSymbols.decimalSeparator
+ var intermediate by remember { mutableStateOf(value) }
OutlinedTextField(
- value = when {
- value == "0" -> ""
- value.startsWith("0.") -> value.trimStart('0')
- value.endsWith(".0") -> value.trimEnd('0')
- else -> value
- },
+ value = intermediate,
onValueChange = { input ->
- val filtered = when {
- input.isEmpty() -> "0"
- input.startsWith(".") -> "0${input}"
- input.endsWith(".") -> "${input}0"
- else -> input
- }
+ val filtered = transformOutput(input, decimalSeparator, '.')
if (Amount.isValidAmountStr(filtered)) {
+ intermediate = transformInput(input, decimalSeparator, '.')
onValueChange(filtered)
}
},
@@ -79,12 +77,11 @@ fun AmountInputField(
readOnly = readOnly,
textStyle = textStyle.copy(fontFamily = FontFamily.Monospace),
label = label,
- placeholder = { Text("0") },
leadingIcon = leadingIcon,
trailingIcon = trailingIcon,
supportingText = supportingText,
isError = isError,
- visualTransformation = visualTransformation,
+ visualTransformation = AmountInputVisualTransformation(decimalSeparator),
keyboardOptions = keyboardOptions.copy(keyboardType = KeyboardType.Decimal),
keyboardActions = keyboardActions,
singleLine = true,
@@ -93,4 +90,57 @@ fun AmountInputField(
shape = shape,
colors = colors,
)
+}
+
+private class AmountInputVisualTransformation(
+ private val decimalSeparator: Char,
+): VisualTransformation {
+
+ override fun filter(text: AnnotatedString): TransformedText {
+ val value = text.text
+ val output = transformOutput(value, '.', decimalSeparator)
+ val newText = AnnotatedString(output)
+ return TransformedText(newText, CursorOffsetMapping(
+ unmaskedText = text.toString(),
+ maskedText = newText.toString().replace(decimalSeparator, '.'),
+ ))
+ }
+
+ private class CursorOffsetMapping(
+ private val unmaskedText: String,
+ private val maskedText: String,
+ ): OffsetMapping {
+ override fun originalToTransformed(offset: Int) = when {
+ unmaskedText.startsWith('.') -> if (offset == 0) 0 else (offset + 1) // ".x" -> "0.x"
+ else -> offset
+ }
+
+ override fun transformedToOriginal(offset: Int) = when {
+ unmaskedText == "" -> 0 // "0" -> ""
+ unmaskedText == "." -> if (offset < 1) 0 else 1 // "0.0" -> "."
+ unmaskedText.startsWith('.') -> if (offset < 1) 0 else (offset - 1) // "0.x" -> ".x"
+ unmaskedText.endsWith('.') && offset == maskedText.length -> offset - 1 // "x.0" -> "x."
+ else -> offset // "x" -> "x"
+ }
+ }
+}
+
+private fun transformInput(
+ input: String,
+ inputDecimalSeparator: Char = '.',
+ outputDecimalSeparator: Char = '.',
+) = input.trim().replace(inputDecimalSeparator, outputDecimalSeparator)
+
+private fun transformOutput(
+ input: String,
+ inputDecimalSeparator: Char = '.',
+ outputDecimalSeparator: Char = '.',
+) = transformInput(input, inputDecimalSeparator, outputDecimalSeparator).let {
+ when {
+ it.isEmpty() -> "0"
+ it == "$outputDecimalSeparator" -> "0${outputDecimalSeparator}0"
+ it.startsWith(outputDecimalSeparator) -> "0$it"
+ it.endsWith(outputDecimalSeparator) -> "${it}0"
+ else -> it
+ }
} \ No newline at end of file
diff --git a/wallet/src/main/java/net/taler/wallet/deposit/PayToUriFragment.kt b/wallet/src/main/java/net/taler/wallet/deposit/PayToUriFragment.kt
index d4c9f6c..4bc91e1 100644
--- a/wallet/src/main/java/net/taler/wallet/deposit/PayToUriFragment.kt
+++ b/wallet/src/main/java/net/taler/wallet/deposit/PayToUriFragment.kt
@@ -131,7 +131,7 @@ private fun PayToComposable(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(16.dp),
) {
- var amountText by rememberSaveable { mutableStateOf("") }
+ var amountText by rememberSaveable { mutableStateOf("0") }
var amountError by rememberSaveable { mutableStateOf("") }
var currency by rememberSaveable { mutableStateOf(currencies[0]) }
val focusRequester = remember { FocusRequester() }