blob: 7478f643a99bf4dab77b93fcce91298d0810b420 [file] [log] [blame]
/*
* Copyright 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.compose.foundation.demos.text
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.interaction.collectIsDraggedAsState
import androidx.compose.foundation.interaction.collectIsFocusedAsState
import androidx.compose.foundation.interaction.collectIsPressedAsState
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.input.OffsetMapping
import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.text.input.TransformedText
import androidx.compose.ui.text.input.VisualTransformation
import androidx.compose.ui.text.intl.LocaleList
import androidx.compose.ui.text.toUpperCase
/**
* The offset translator used for credit card input field.
*
* @see creditCardFilter
*/
private val creditCardOffsetTranslator = object : OffsetMapping {
override fun originalToTransformed(offset: Int): Int {
if (offset <= 3) return offset
if (offset <= 7) return offset + 1
if (offset <= 11) return offset + 2
if (offset <= 16) return offset + 3
return 19
}
override fun transformedToOriginal(offset: Int): Int {
if (offset <= 4) return offset
if (offset <= 9) return offset - 1
if (offset <= 14) return offset - 2
if (offset <= 19) return offset - 3
return 16
}
}
/**
* The visual filter for credit card input field.
*
* This filter converts up to 16 digits to hyphen connected 4 digits string.
* For example, "1234567890123456" will be shown as "1234-5678-9012-3456".
*/
private val creditCardFilter = VisualTransformation { text ->
val trimmed = if (text.text.length >= 16) text.text.substring(0..15) else text.text
var out = ""
for (i in 0 until trimmed.length) {
out += trimmed[i]
if (i % 4 == 3 && i != 15) out += "-"
}
TransformedText(AnnotatedString(out), creditCardOffsetTranslator)
}
/**
* The offset translator which works for all offset keep remains the same.
*/
private val identityTranslator = object : OffsetMapping {
override fun originalToTransformed(offset: Int): Int = offset
override fun transformedToOriginal(offset: Int): Int = offset
}
/**
* The visual filter for capitalization.
*
* This filer converts ASCII characters to capital form.
*/
private class CapitalizeTransformation(
val locale: LocaleList = LocaleList("en-US")
) : VisualTransformation {
override fun filter(text: AnnotatedString): TransformedText {
// Note: identityTranslator doesn't work for some locale, e.g. Turkish
return TransformedText(AnnotatedString(text.text).toUpperCase(locale), identityTranslator)
}
}
/**
* The offset translator for phone number
*
* @see phoneNumberFilter
*/
private fun phoneNumberOffsetTranslator(text: String) = object : OffsetMapping {
override fun originalToTransformed(offset: Int): Int {
return when (offset) {
0 -> 1
1 -> 2
2 -> 3
3 -> 6
4 -> 7
5 -> 8
6 -> 10
7 -> 11
8 -> 12
9 -> 13
else -> 14
}
}
override fun transformedToOriginal(offset: Int): Int {
return when (offset) {
0 -> 0
1 -> 0
2 -> 1
3 -> 2
4 -> 3
5 -> 3
6 -> 3
7 -> 4
8 -> 5
9 -> 6
10 -> 6
11 -> 7
12 -> 8
13 -> 9
else -> 10
}.coerceAtMost(text.length)
}
}
/**
* The visual filter for phone number.
*
* This filter converts up to 10 digits to phone number form.
* For example, "1234567890" will be shown as "(123) 456-7890".
*/
private val phoneNumberFilter = VisualTransformation { text ->
val trimmed = if (text.text.length >= 10) text.text.substring(0..9) else text.text
val filled = trimmed + "_".repeat(10 - trimmed.length)
val res = "(" + filled.substring(0..2) + ") " + filled.substring(3..5) + "-" +
filled.substring(6..9)
TransformedText(AnnotatedString(text = res), phoneNumberOffsetTranslator(text.text))
}
private val emailFilter = VisualTransformation { text ->
if (text.text.indexOf("@") == -1) {
TransformedText(
AnnotatedString(text = text.text + "@gmail.com"),
emailOffsetTranslator(text.text)
)
} else {
TransformedText(text, identityTranslator)
}
}
private fun emailOffsetTranslator(text: String) = object : OffsetMapping {
override fun originalToTransformed(offset: Int): Int {
return (offset).coerceAtMost(text.length + 10)
}
override fun transformedToOriginal(offset: Int): Int {
return if (offset <= text.length) {
offset
} else {
(offset - 10).coerceAtMost(text.length).coerceAtLeast(0)
}
}
}
@Composable
fun VariousInputFieldDemo() {
LazyColumn {
item {
TagLine(tag = "Capitalization")
VariousEditLine(
keyboardType = KeyboardType.Ascii,
onValueChange = { old, new ->
if (new.any { !it.isLetterOrDigit() }) old else new
},
visualTransformation = CapitalizeTransformation()
)
}
item {
TagLine(tag = "Capitalization (Turkish)")
VariousEditLine(
keyboardType = KeyboardType.Ascii,
onValueChange = { old, new ->
if (new.any { !it.isLetterOrDigit() }) old else new
},
visualTransformation = CapitalizeTransformation(LocaleList("tr"))
)
}
item {
TagLine(tag = "Password")
VariousEditLine(
keyboardType = KeyboardType.Password,
onValueChange = { old, new ->
if (new.any { !it.isLetterOrDigit() }) old else new
},
visualTransformation = PasswordVisualTransformation()
)
}
item {
TagLine(tag = "Phone Number")
VariousEditLine(
keyboardType = KeyboardType.Number,
onValueChange = { old, new ->
if (new.length > 10 || new.any { !it.isDigit() }) old else new
},
visualTransformation = phoneNumberFilter
)
}
item {
TagLine(tag = "Credit Card")
VariousEditLine(
keyboardType = KeyboardType.Number,
onValueChange = { old, new ->
if (new.length > 16 || new.any { !it.isDigit() }) old else new
},
visualTransformation = creditCardFilter
)
}
item {
TagLine(tag = "Email Suggestion")
VariousEditLine(
keyboardType = KeyboardType.Email,
visualTransformation = emailFilter
)
}
item {
TagLine(tag = "Editfield with Hint Text")
HintEditText {
Text(
text = "Hint Text",
color = Color(0xFF888888),
style = TextStyle(fontSize = fontSize8)
)
}
}
item {
TagLine(tag = "TextField MutableInteractionSource")
InteractionSourceTextField()
}
}
}
@Composable
private fun VariousEditLine(
keyboardType: KeyboardType = KeyboardType.Text,
imeAction: ImeAction = ImeAction.Default,
onValueChange: (String, String) -> String = { _, new -> new },
visualTransformation: VisualTransformation
) {
val state = rememberSaveable { mutableStateOf("") }
BasicTextField(
modifier = demoTextFieldModifiers,
value = state.value,
singleLine = true,
keyboardOptions = KeyboardOptions(
keyboardType = keyboardType,
imeAction = imeAction
),
visualTransformation = visualTransformation,
onValueChange = {
val value = onValueChange(state.value, it)
state.value = value
},
textStyle = TextStyle(fontSize = fontSize8)
)
}
@Composable
private fun HintEditText(content: @Composable () -> Unit) {
val state = rememberSaveable { mutableStateOf("") }
Box(demoTextFieldModifiers) {
BasicTextField(
modifier = Modifier.fillMaxWidth(),
value = state.value,
onValueChange = { state.value = it },
textStyle = TextStyle(fontSize = fontSize8)
)
if (state.value.isEmpty()) {
content()
}
}
}
@Composable
private fun InteractionSourceTextField() {
val state = rememberSaveable(stateSaver = TextFieldValue.Saver) {
mutableStateOf(TextFieldValue())
}
val interactionSource = remember { MutableInteractionSource() }
Column(demoTextFieldModifiers) {
Text(
"Pressed?: ${interactionSource.collectIsPressedAsState().value}",
fontSize = fontSize4
)
Text(
"Focused?: ${interactionSource.collectIsFocusedAsState().value}",
fontSize = fontSize4
)
Text(
"Dragged?: ${interactionSource.collectIsDraggedAsState().value}",
fontSize = fontSize4
)
BasicTextField(
modifier = Modifier.fillMaxWidth(),
value = state.value,
singleLine = true,
interactionSource = interactionSource,
onValueChange = { state.value = it },
textStyle = TextStyle(fontSize = fontSize8)
)
}
}