Updated on 2026-08-14
This commit is contained in:
parent
6f047b8607
commit
7fe2a4d823
6 changed files with 89 additions and 28 deletions
|
|
@ -2,6 +2,7 @@ package com.tangem.core.ui.components.fields
|
|||
|
||||
import androidx.compose.animation.*
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.foundation.Canvas
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
|
|
@ -36,9 +37,9 @@ fun PinTextField(
|
|||
value: String,
|
||||
length: Int,
|
||||
isPasswordVisual: Boolean,
|
||||
pinTextColor: PinTextColor,
|
||||
onValueChange: (String) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
wrongCode: Boolean = false,
|
||||
) {
|
||||
val focusRequester = remember { FocusRequester() }
|
||||
val textFieldValue = remember(value) {
|
||||
|
|
@ -72,7 +73,7 @@ fun PinTextField(
|
|||
CellDecoration(
|
||||
length = length,
|
||||
isPasswordVisual = isPasswordVisual,
|
||||
wrongCode = wrongCode,
|
||||
pinTextColor = pinTextColor,
|
||||
value = value,
|
||||
)
|
||||
},
|
||||
|
|
@ -84,17 +85,25 @@ fun PinTextField(
|
|||
}
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
enum class PinTextColor {
|
||||
Primary,
|
||||
WrongCode,
|
||||
Success,
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber", "LongMethod")
|
||||
@Composable
|
||||
private fun CellDecoration(
|
||||
length: Int,
|
||||
wrongCode: Boolean,
|
||||
pinTextColor: PinTextColor,
|
||||
value: String,
|
||||
modifier: Modifier = Modifier,
|
||||
isPasswordVisual: Boolean = false,
|
||||
) {
|
||||
val textMeasurer = rememberTextMeasurer()
|
||||
val width = textMeasurer.measure("0")
|
||||
val minSize = textMeasurer.measure("0")
|
||||
val minWidth = maxOf(minSize.size.width.dp + 8.dp, 24.dp + 3.dp) // 24.dp is the minimum width of a pin cell
|
||||
val minHeight = maxOf(minSize.size.height.dp, 48.dp) // 48.dp is the minimum height of a pin cell
|
||||
|
||||
Row(
|
||||
modifier = modifier,
|
||||
|
|
@ -107,6 +116,18 @@ private fun CellDecoration(
|
|||
""
|
||||
}
|
||||
|
||||
val color = when (pinTextColor) {
|
||||
PinTextColor.Primary -> {
|
||||
if (isPasswordVisual) {
|
||||
TangemTheme.colors.icon.informative
|
||||
} else {
|
||||
TangemTheme.colors.text.primary1
|
||||
}
|
||||
}
|
||||
PinTextColor.WrongCode -> TangemTheme.colors.icon.warning
|
||||
PinTextColor.Success -> TangemTheme.colors.icon.accent
|
||||
}
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.background(
|
||||
|
|
@ -119,26 +140,34 @@ private fun CellDecoration(
|
|||
targetState = char,
|
||||
transitionSpec = {
|
||||
(
|
||||
fadeIn(animationSpec = tween(220, delayMillis = 90)) +
|
||||
slideInVertically(animationSpec = tween(330, delayMillis = 0))
|
||||
fadeIn(animationSpec = tween(90, delayMillis = 90)) +
|
||||
slideInVertically(animationSpec = tween(220, 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 = if (wrongCode) {
|
||||
TangemTheme.colors.text.warning
|
||||
} else {
|
||||
TangemTheme.colors.text.primary1
|
||||
},
|
||||
textAlign = TextAlign.Center,
|
||||
lineHeight = 48.sp,
|
||||
)
|
||||
if (isPasswordVisual && text.isNotEmpty()) {
|
||||
Canvas(
|
||||
Modifier.sizeIn(minWidth = minWidth, minHeight = minHeight),
|
||||
) {
|
||||
drawCircle(
|
||||
color = color,
|
||||
radius = 4.dp.toPx(),
|
||||
center = center,
|
||||
)
|
||||
}
|
||||
} else {
|
||||
Text(
|
||||
modifier = Modifier.sizeIn(minWidth = minWidth, minHeight = minHeight),
|
||||
text = text,
|
||||
style = TangemTheme.typography.h3,
|
||||
color = color,
|
||||
textAlign = TextAlign.Center,
|
||||
lineHeight = 48.sp,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -152,10 +181,18 @@ private fun Preview() {
|
|||
var text by remember { mutableStateOf("123") }
|
||||
|
||||
Column {
|
||||
PinTextField(
|
||||
value = text,
|
||||
onValueChange = { text = it },
|
||||
isPasswordVisual = true,
|
||||
pinTextColor = PinTextColor.Success,
|
||||
length = 6,
|
||||
)
|
||||
PinTextField(
|
||||
value = text,
|
||||
onValueChange = { text = it },
|
||||
isPasswordVisual = false,
|
||||
pinTextColor = PinTextColor.Primary,
|
||||
length = 6,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import androidx.compose.ui.tooling.preview.Preview
|
|||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.core.res.R
|
||||
import com.tangem.core.ui.components.PrimaryButton
|
||||
import com.tangem.core.ui.components.fields.PinTextColor
|
||||
import com.tangem.core.ui.components.fields.PinTextField
|
||||
import com.tangem.core.ui.extensions.stringResourceSafe
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
|
|
@ -75,6 +76,7 @@ internal fun AccessCode(state: AccessCodeUM, modifier: Modifier = Modifier) {
|
|||
length = state.accessCodeLength,
|
||||
isPasswordVisual = true,
|
||||
value = state.accessCode,
|
||||
pinTextColor = PinTextColor.Primary,
|
||||
onValueChange = state.onAccessCodeChange,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,8 +26,7 @@ internal class DefaultHotAccessCodeRequestComponent @AssistedInject constructor(
|
|||
}
|
||||
|
||||
override suspend fun successfulAuthentication() {
|
||||
// TODO handle successful authentication
|
||||
// TODO add delay
|
||||
model.successfulAuthentication()
|
||||
}
|
||||
|
||||
override suspend fun requestPassword(hasBiometry: Boolean): HotWalletPasswordRequester.Result {
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package com.tangem.features.hotwallet.accesscoderequest
|
|||
|
||||
import com.tangem.core.decompose.di.ModelScoped
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.core.ui.components.fields.PinTextColor
|
||||
import com.tangem.domain.wallets.hot.HotWalletPasswordRequester
|
||||
import com.tangem.features.hotwallet.accesscode.ACCESS_CODE_LENGTH
|
||||
import com.tangem.features.hotwallet.accesscoderequest.entity.HotAccessCodeRequestUM
|
||||
|
|
@ -45,13 +46,23 @@ internal class HotAccessCodeRequestModel @Inject constructor(
|
|||
suspend fun wrongAccessCode() {
|
||||
uiState.update {
|
||||
it.copy(
|
||||
wrongAccessCode = true,
|
||||
accessCodeColor = PinTextColor.WrongCode,
|
||||
onAccessCodeChange = {},
|
||||
)
|
||||
}
|
||||
delay(timeMillis = 500) // Delay to show the wrong access code state
|
||||
}
|
||||
|
||||
suspend fun successfulAuthentication() {
|
||||
uiState.update {
|
||||
it.copy(
|
||||
accessCodeColor = PinTextColor.Success,
|
||||
onAccessCodeChange = {},
|
||||
)
|
||||
}
|
||||
delay(timeMillis = 200) // Delay to show the success state
|
||||
}
|
||||
|
||||
private fun getInitialState() = HotAccessCodeRequestUM(
|
||||
onDismiss = ::dismiss,
|
||||
onAccessCodeChange = ::onAccessCodeChange,
|
||||
|
|
@ -66,7 +77,10 @@ internal class HotAccessCodeRequestModel @Inject constructor(
|
|||
if (accessCode.length > ACCESS_CODE_LENGTH) return
|
||||
|
||||
uiState.update {
|
||||
it.copy(accessCode = accessCode, wrongAccessCode = false)
|
||||
it.copy(
|
||||
accessCode = accessCode,
|
||||
accessCodeColor = PinTextColor.Primary,
|
||||
)
|
||||
}
|
||||
|
||||
if (accessCode.length == ACCESS_CODE_LENGTH) {
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
package com.tangem.features.hotwallet.accesscoderequest.entity
|
||||
|
||||
import com.tangem.core.ui.components.fields.PinTextColor
|
||||
|
||||
internal data class HotAccessCodeRequestUM(
|
||||
val isShown: Boolean = false,
|
||||
val accessCode: String = "",
|
||||
val wrongAccessCode: Boolean = false,
|
||||
val accessCodeColor: PinTextColor = PinTextColor.Primary,
|
||||
val useBiometricVisible: Boolean = true,
|
||||
val useBiometricClick: () -> Unit = {},
|
||||
val onAccessCodeChange: (String) -> Unit = {},
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import com.tangem.core.ui.components.SpacerH
|
|||
import com.tangem.core.ui.components.SpacerH24
|
||||
import com.tangem.core.ui.components.appbar.TangemTopAppBar
|
||||
import com.tangem.core.ui.components.appbar.models.TopAppBarButtonUM
|
||||
import com.tangem.core.ui.components.fields.PinTextColor
|
||||
import com.tangem.core.ui.components.fields.PinTextField
|
||||
import com.tangem.core.ui.extensions.stringResourceSafe
|
||||
import com.tangem.core.ui.haptic.TangemHapticEffect
|
||||
|
|
@ -85,7 +86,7 @@ internal fun HotAccessCodeRequestFullScreenContent(state: HotAccessCodeRequestUM
|
|||
length = 6,
|
||||
isPasswordVisual = true,
|
||||
value = state.accessCode,
|
||||
wrongCode = state.wrongAccessCode,
|
||||
pinTextColor = state.accessCodeColor,
|
||||
onValueChange = state.onAccessCodeChange,
|
||||
)
|
||||
}
|
||||
|
|
@ -106,9 +107,15 @@ internal fun HotAccessCodeRequestFullScreenContent(state: HotAccessCodeRequestUM
|
|||
|
||||
val hapticManager = LocalHapticManager.current
|
||||
|
||||
LaunchedEffect(state.wrongAccessCode) {
|
||||
if (state.wrongAccessCode) {
|
||||
hapticManager.perform(TangemHapticEffect.View.Reject)
|
||||
LaunchedEffect(state.accessCodeColor) {
|
||||
when (state.accessCodeColor) {
|
||||
PinTextColor.WrongCode -> {
|
||||
hapticManager.perform(TangemHapticEffect.View.Reject)
|
||||
}
|
||||
PinTextColor.Success -> {
|
||||
hapticManager.perform(TangemHapticEffect.View.Confirm)
|
||||
}
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue