Skip to content

Instantly share code, notes, and snippets.

@fergdev
Created September 28, 2025 18:13
Show Gist options
  • Select an option

  • Save fergdev/264e71b5b53b2aa5df5d81d862ac7464 to your computer and use it in GitHub Desktop.

Select an option

Save fergdev/264e71b5b53b2aa5df5d81d862ac7464 to your computer and use it in GitHub Desktop.
Pin input compose
@file:Suppress("FunctionName")
package com.fergdev.fcommon.ui
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Backspace
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import org.jetbrains.compose.ui.tooling.preview.Preview
@Preview
@Composable
fun CodeInputDemo() {
var code by remember { mutableStateOf("") }
Surface(color = MaterialTheme.colorScheme.background) {
Column(
modifier = Modifier
.fillMaxSize()
.padding(24.dp),
verticalArrangement = Arrangement.spacedBy(24.dp, Alignment.CenterVertically),
horizontalAlignment = Alignment.CenterHorizontally
) {
Text("Enter Code", style = MaterialTheme.typography.headlineSmall)
CodeDots(
value = code,
length = 6,
)
Keypad(
onDigit = { d ->
if (code.length < 6) code += d
},
onDot = {
if (code.length < 6) code += "."
},
onBackspace = {
if (code.isNotEmpty()) code = code.dropLast(1)
}
)
if (code.length == 6) {
Button(onClick = { /* verify code */ }) {
Text("Continue")
}
}
}
}
}
/** Simple dots showing code length/progress */
@Composable
fun CodeDots(
value: String,
length: Int,
dotSize: Dp = 14.dp,
spacing: Dp = 12.dp,
shape: Shape = CircleShape
) {
Row(horizontalArrangement = Arrangement.spacedBy(spacing)) {
repeat(length) { idx ->
val filled = idx < value.length
Box(
modifier = Modifier
.size(dotSize * 2)
.clip(shape)
.background(
if (filled) MaterialTheme.colorScheme.primary
else MaterialTheme.colorScheme.surfaceVariant
),
contentAlignment = Alignment.Center
) {
Box(
modifier = Modifier
.size(dotSize)
.clip(shape)
.background(MaterialTheme.colorScheme.onPrimary.takeIf { filled }
?: MaterialTheme.colorScheme.outlineVariant)
)
}
}
}
}
@Composable
fun Keypad(
onDigit: (String) -> Unit,
onDot: (() -> Unit)? = null,
onBackspace: () -> Unit,
modifier: Modifier = Modifier,
keySize: Dp = 72.dp,
) {
val rows = listOf(
listOf(Key.Num("1"), Key.Num("2"), Key.Num("3")),
listOf(Key.Num("4"), Key.Num("5"), Key.Num("6")),
listOf(Key.Num("7"), Key.Num("8"), Key.Num("9")),
listOf(
if (onDot != null) Key.Dot else Key.Blank,
Key.Num("0"),
Key.Backspace
)
)
Column(
modifier = modifier,
verticalArrangement = Arrangement.spacedBy(16.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
rows.forEach { row ->
Row(
horizontalArrangement = Arrangement.spacedBy(16.dp),
verticalAlignment = Alignment.CenterVertically
) {
row.forEach { key ->
when (key) {
is Key.Num -> CircleKey(
label = key.value,
size = keySize,
onClick = { onDigit(key.value) }
)
Key.Dot -> CircleKey(
label = ".",
size = keySize,
onClick = { onDot?.invoke() }
)
Key.Backspace -> CircleIconKey(
size = keySize,
onClick = onBackspace
)
Key.Blank -> Spacer(Modifier.size(keySize))
}
}
}
}
}
}
private sealed interface Key {
data class Num(val value: String) : Key
data object Dot : Key
data object Backspace : Key
data object Blank : Key
}
@Composable
private fun CircleKey(
label: String,
size: Dp,
onClick: () -> Unit
) {
FilledTonalButton(
onClick = onClick,
modifier = Modifier.size(size),
shape = CircleShape,
contentPadding = PaddingValues(0.dp)
) {
Text(
label,
fontSize = 22.sp,
fontWeight = FontWeight.Medium,
textAlign = TextAlign.Center
)
}
}
@Composable
private fun CircleIconKey(
size: Dp,
onClick: () -> Unit
) {
OutlinedButton(
onClick = onClick,
modifier = Modifier.size(size),
shape = CircleShape,
contentPadding = PaddingValues(0.dp)
) {
Icon(
imageVector = Icons.Outlined.Backspace,
contentDescription = "Backspace"
)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment