Skip to content

Instantly share code, notes, and snippets.

@MagicalMeghan
Last active May 15, 2025 19:03
Show Gist options
  • Save MagicalMeghan/62f072c15e0dec67c9aeb90acfe79410 to your computer and use it in GitHub Desktop.
Save MagicalMeghan/62f072c15e0dec67c9aeb90acfe79410 to your computer and use it in GitHub Desktop.
Gists for "Mastering Text Input" talk for Google IO 25
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.imePadding
import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.statusBarsPadding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.text.BasicText
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.text.TextAutoSize
import androidx.compose.foundation.text.input.InputTransformation
import androidx.compose.foundation.text.input.OutputTransformation
import androidx.compose.foundation.text.input.TextFieldBuffer
import androidx.compose.foundation.text.input.TextFieldLineLimits
import androidx.compose.foundation.text.input.TextFieldState
import androidx.compose.foundation.text.input.insert
import androidx.compose.foundation.text.input.maxLength
import androidx.compose.foundation.text.input.rememberTextFieldState
import androidx.compose.foundation.text.input.then
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Button
import androidx.compose.material3.LocalTextStyle
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.autofill.ContentType
import androidx.compose.ui.draw.clip
import androidx.compose.ui.semantics.contentType
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.TextUnit
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.core.text.isDigitsOnly
@Preview
@Composable
fun CollarCustomizerGist(modifier: Modifier = Modifier) {
Column(
modifier = modifier
.imePadding()
.statusBarsPadding()
.verticalScroll(
rememberScrollState()
),
horizontalAlignment = Alignment.CenterHorizontally
) {
val dogNameState = rememberTextFieldState()
val phoneState = rememberTextFieldState()
Box(
modifier = Modifier
.size(300.dp)
.clip(CircleShape)
) {
Column(modifier = Modifier
.padding(50.dp)
.fillMaxSize()) {
BasicText(
text = "${dogNameState.text}",
autoSize = TextAutoSize.StepBased(),
style = LocalTextStyle.current.copy(
lineHeight = TextUnit.Unspecified,
textAlign = TextAlign.Center,
fontSize = 24.sp
),
modifier = Modifier.weight(3f)
)
BasicText(
text = phoneState.outputTransformed(PhoneNumberOutputTransformation()),
autoSize = TextAutoSize.StepBased(),
style = LocalTextStyle.current.copy(
lineHeight = TextUnit.Unspecified,
textAlign = TextAlign.Center,
fontSize = 24.sp
),
modifier = Modifier.weight(1f)
)
}
}
Column(
modifier = Modifier
.fillMaxWidth()
.navigationBarsPadding(),
horizontalAlignment = Alignment.CenterHorizontally
) {
OutlinedTextField(
state = dogNameState,
lineLimits = TextFieldLineLimits.SingleLine,
placeholder = { Text("Pet Name") },
)
OutlinedTextField(
state = phoneState,
lineLimits = TextFieldLineLimits.SingleLine,
placeholder = { Text("Phone Number") },
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Phone),
modifier = Modifier
.semantics { contentType = ContentType.PhoneNumber },
inputTransformation = InputTransformation.maxLength(10).then {
if (!asCharSequence().isDigitsOnly()) {
revertAllChanges()
}
},
outputTransformation = PhoneNumberOutputTransformation()
)
Button(
onClick = { /* doSomething() */ },
) {
Row(verticalAlignment = Alignment.CenterVertically) {
Text("Submit")
}
}
}
}
}
class PhoneNumberOutputTransformation : OutputTransformation {
override fun TextFieldBuffer.transformOutput() {
if (length > 0) insert(0, "(")
if (length > 4) insert(4, ")")
if (length > 8) insert(8, "-")
}
}
fun TextFieldState.outputTransformed(outputTransformation: OutputTransformation): String {
var buffer: TextFieldBuffer? = null
edit { buffer = this }
with(outputTransformation) {
buffer?.transformOutput()
}
return buffer?.asCharSequence()?.toString() ?: ""
}
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.text.input.TextFieldLineLimits
import androidx.compose.foundation.text.input.TextObfuscationMode.Companion.RevealLastTyped
import androidx.compose.foundation.text.input.TextObfuscationMode.Companion.Visible
import androidx.compose.foundation.text.input.rememberTextFieldState
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Password
import androidx.compose.material.icons.filled.Pets
import androidx.compose.material.icons.filled.Visibility
import androidx.compose.material.icons.filled.VisibilityOff
import androidx.compose.material3.Icon
import androidx.compose.material3.OutlinedSecureTextField
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text
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.autofill.ContentType
import androidx.compose.ui.draw.clip
import androidx.compose.ui.semantics.contentType
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.tooling.preview.Preview
@Preview
@Composable
fun LoginGist(
modifier: Modifier = Modifier
) {
var passwordVisible by remember { mutableStateOf(false) }
Column(modifier = modifier) {
OutlinedTextField(
state = rememberTextFieldState(),
lineLimits = TextFieldLineLimits.SingleLine,
placeholder = { Text("Username") },
leadingIcon = { Icon(Icons.Filled.Pets, contentDescription = null) },
modifier = Modifier
.semantics { contentType = ContentType.Username },
)
OutlinedSecureTextField(
state = rememberTextFieldState(),
placeholder = { Text("Password") },
leadingIcon = {
Icon(
Icons.Filled.Password,
contentDescription = null,
)
},
modifier = Modifier
.semantics { contentType = ContentType.Password },
trailingIcon = {
Icon(
if (passwordVisible) Icons.Filled.Visibility else Icons.Filled.VisibilityOff,
contentDescription = "Toggle password visibility",
modifier = Modifier.clip(CircleShape).clickable { passwordVisible = !passwordVisible }
)
},
textObfuscationMode = if (passwordVisible) Visible else RevealLastTyped
)
}
}
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.text.input.TextFieldLineLimits
import androidx.compose.foundation.text.input.rememberTextFieldState
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Password
import androidx.compose.material.icons.filled.Pets
import androidx.compose.material3.Icon
import androidx.compose.material3.OutlinedSecureTextField
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.autofill.ContentType
import androidx.compose.ui.semantics.contentType
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.sp
@Preview
@Composable
fun RegistrationGist(
modifier: Modifier = Modifier
) {
Column(modifier = modifier) {
OutlinedTextField(
state = rememberTextFieldState(),
lineLimits = TextFieldLineLimits.SingleLine,
placeholder = { Text("Username") },
leadingIcon = { Icon(Icons.Filled.Pets, contentDescription = null) },
modifier = Modifier.semantics { contentType = ContentType.NewUsername }
)
val passwordState = rememberTextFieldState()
val isError = passwordState.text.length in 1..7
OutlinedSecureTextField(
state = passwordState,
placeholder = { Text("Password", style = TextStyle(fontSize = 16.sp)) },
leadingIcon = { Icon(Icons.Filled.Password, contentDescription = null) },
isError = isError,
supportingText = {
if (isError) {
Text("Password must be at least 8 characters")
}
},
modifier = Modifier.semantics { contentType = ContentType.NewPassword }
)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment