Updated on 2026-08-14

This commit is contained in:
Tangem 2025-06-17 11:20:44 +03:00
parent 7c91472d99
commit 56157412ac
2 changed files with 160 additions and 0 deletions

View file

@ -0,0 +1,159 @@
package com.tangem.core.ui.components.fields
import androidx.compose.animation.*
import androidx.compose.animation.core.tween
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.text.TextRange
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.text.rememberTextMeasurer
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.utils.StringsSigns.PASSWORD_VISUAL_CHAR
import kotlin.random.Random
@Composable
fun PinTextField(
value: String,
length: Int,
isPasswordVisual: Boolean,
onValueChange: (String) -> Unit,
modifier: Modifier = Modifier,
) {
val focusRequester = remember { FocusRequester() }
val textFieldValue = remember(value) {
TextFieldValue(value, selection = TextRange(value.length))
}
BasicTextField(
value = textFieldValue,
onValueChange = {
if (it.text.length <= length) {
onValueChange(it.text)
}
},
modifier = modifier
.clickable {
focusRequester.requestFocus()
}
.focusRequester(focusRequester),
keyboardOptions = KeyboardOptions.Default.copy(
keyboardType = KeyboardType.Number,
imeAction = ImeAction.Done,
),
singleLine = true,
textStyle = TangemTheme.typography.body1,
maxLines = 1,
cursorBrush = SolidColor(TangemTheme.colors.text.primary1),
decorationBox = { innerTextField ->
Box(modifier = Modifier.width(0.dp)) {
innerTextField()
}
CellDecoration(
length = length,
isPasswordVisual = isPasswordVisual,
value = value,
)
},
)
}
@Suppress("MagicNumber")
@Composable
private fun CellDecoration(
length: Int,
value: String,
modifier: Modifier = Modifier,
isPasswordVisual: Boolean = false,
) {
val textMeasurer = rememberTextMeasurer()
val width = textMeasurer.measure("0")
Row(
modifier = modifier,
horizontalArrangement = Arrangement.spacedBy(12.dp),
) {
repeat(length) {
val char = if (it < value.length) {
if (isPasswordVisual) PASSWORD_VISUAL_CHAR.toString() else value[it].toString()
} else {
""
}
Box(
modifier = Modifier
.background(
color = TangemTheme.colors.background.tertiary,
shape = RoundedCornerShape(8.dp),
),
contentAlignment = Alignment.Center,
) {
AnimatedContent(
targetState = char,
transitionSpec = {
(
fadeIn(animationSpec = tween(220, delayMillis = 90)) +
slideInVertically(animationSpec = tween(330, delayMillis = 0))
)
.togetherWith(
fadeOut(animationSpec = tween(90)) + slideOutVertically(tween(220)),
)
},
) { text ->
Text(
modifier = Modifier.sizeIn(minWidth = width.size.width.dp + 8.dp, minHeight = 48.dp),
text = text,
style = TangemTheme.typography.h3,
color = TangemTheme.colors.text.primary1,
textAlign = TextAlign.Center,
lineHeight = 48.sp,
)
}
}
}
}
}
@Preview(showBackground = true)
@Composable
private fun Preview() {
TangemThemePreview {
var text by remember { mutableStateOf("123") }
Column {
PinTextField(
value = text,
onValueChange = { text = it },
isPasswordVisual = false,
length = 6,
)
Row {
Button(onClick = { text += Random.nextInt(0, 10).toString() }) {
Text("Add Character")
}
Button(onClick = { text = text.dropLast(1) }) {
Text("Delete")
}
}
}
}
}

View file

@ -13,4 +13,5 @@ object StringsSigns {
const val NON_BREAKING_SPACE = '\u00A0'
const val PERCENT = "%"
const val THREE_STARS = "\u2217\u2217\u2217"
const val PASSWORD_VISUAL_CHAR = '\u2022'
}