Updated on 2026-08-14
This commit is contained in:
parent
7c91472d99
commit
56157412ac
2 changed files with 160 additions and 0 deletions
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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'
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue