11package to.bitkit.ui.components
22
3+ import androidx.compose.foundation.Image
4+ import androidx.compose.foundation.background
5+ import androidx.compose.foundation.border
36import androidx.compose.foundation.layout.Box
47import androidx.compose.foundation.layout.Column
8+ import androidx.compose.foundation.layout.Row
59import androidx.compose.foundation.layout.fillMaxSize
10+ import androidx.compose.foundation.layout.height
11+ import androidx.compose.foundation.layout.navigationBarsPadding
612import androidx.compose.foundation.layout.padding
13+ import androidx.compose.foundation.layout.size
14+ import androidx.compose.foundation.shape.CircleShape
715import androidx.compose.runtime.Composable
16+ import androidx.compose.runtime.LaunchedEffect
817import androidx.compose.runtime.getValue
918import androidx.compose.runtime.mutableStateOf
19+ import androidx.compose.runtime.remember
1020import androidx.compose.runtime.saveable.rememberSaveable
1121import androidx.compose.runtime.setValue
1222import androidx.compose.ui.Alignment
1323import androidx.compose.ui.Modifier
24+ import androidx.compose.ui.draw.clip
25+ import androidx.compose.ui.layout.ContentScale
26+ import androidx.compose.ui.res.painterResource
1427import androidx.compose.ui.res.stringResource
1528import androidx.compose.ui.tooling.preview.Preview
1629import androidx.compose.ui.unit.dp
1730import androidx.lifecycle.compose.collectAsStateWithLifecycle
31+ import kotlinx.coroutines.delay
1832import to.bitkit.R
19- import to.bitkit.ui.appViewModel
2033import to.bitkit.ui.theme.AppThemeSurface
34+ import to.bitkit.ui.theme.Colors
2135import to.bitkit.ui.utils.rememberBiometricAuthSupported
36+ import to.bitkit.viewmodels.AppViewModel
37+
38+ private const val PIN_LENGTH = 4
2239
2340@Composable
2441fun AuthCheckView (
2542 onSuccess : (() -> Unit )? = null,
2643 isBiometrySupported : Boolean = rememberBiometricAuthSupported(),
44+ showLogoOnPin : Boolean ,
45+ appViewModel : AppViewModel ,
2746) {
28- val app = appViewModel ? : return
29- val isBiometricsEnabled by app.isBiometricEnabled.collectAsStateWithLifecycle()
47+ val isBiometricsEnabled by appViewModel.isBiometricEnabled.collectAsStateWithLifecycle()
3048
3149 AuthCheckViewContent (
3250 onSuccess = onSuccess,
3351 isBiometricsEnabled = isBiometricsEnabled,
3452 isBiometrySupported = isBiometrySupported,
53+ showLogoOnPin = showLogoOnPin,
3554 )
3655}
3756
@@ -40,35 +59,106 @@ private fun AuthCheckViewContent(
4059 onSuccess : (() -> Unit )? = null,
4160 isBiometricsEnabled : Boolean ,
4261 isBiometrySupported : Boolean ,
62+ showLogoOnPin : Boolean ,
4363) {
4464 var showBio by rememberSaveable { mutableStateOf(isBiometricsEnabled) }
4565
4666 Box (
4767 contentAlignment = Alignment .Center ,
48- modifier = Modifier .fillMaxSize()
68+ modifier = Modifier
69+ .fillMaxSize()
70+ .navigationBarsPadding()
4971 ) {
50- Column (
51- horizontalAlignment = Alignment .CenterHorizontally ,
52- ) {
53-
72+ Column {
5473 if (showBio && isBiometrySupported) {
5574 BiometricsView (
5675 onSuccess = { onSuccess?.invoke() },
5776 onFailure = { showBio = false },
5877 )
5978 } else {
60- Subtitle (text = " TODO: Pin code auth" )
61- PrimaryButton (
62- text = stringResource(R .string.common__skip),
63- onClick = { onSuccess?.invoke() },
64- fullWidth = false ,
65- modifier = Modifier .padding(top = 24 .dp),
79+ PinPad (
80+ showLogo = showLogoOnPin,
81+ onSuccess = onSuccess,
6682 )
6783 }
6884 }
6985 }
7086}
7187
88+ @Composable
89+ private fun PinPad (
90+ showLogo : Boolean = false,
91+ onSuccess : (() -> Unit )? ,
92+ ) {
93+ var pin by remember { mutableStateOf(" " ) }
94+
95+ LaunchedEffect (pin) {
96+ if (pin.length == PIN_LENGTH ) {
97+ delay(500 ) // Simulate pin check delay
98+ // TODO: Implement actual PIN verification logic here
99+ onSuccess?.invoke()
100+ pin = " "
101+ }
102+ }
103+
104+ Column (
105+ horizontalAlignment = Alignment .CenterHorizontally ,
106+ ) {
107+ Box (
108+ contentAlignment = Alignment .BottomCenter ,
109+ modifier = Modifier .weight(1f )
110+ ) {
111+ if (showLogo) {
112+ Image (
113+ painter = painterResource(R .drawable.bitkit_logo),
114+ contentDescription = null ,
115+ contentScale = ContentScale .Fit ,
116+ modifier = Modifier .size(256 .dp)
117+ )
118+ }
119+ }
120+ Subtitle (text = stringResource(R .string.security__pin_enter))
121+ PinDots (
122+ pin = pin,
123+ modifier = Modifier .padding(vertical = 32 .dp),
124+ )
125+ PinNumberPad (
126+ modifier = Modifier .height(310 .dp),
127+ onPress = { key ->
128+ if (key == KEY_DELETE ) {
129+ if (pin.isNotEmpty()) {
130+ pin = pin.dropLast(1 )
131+ }
132+ } else if (pin.length < PIN_LENGTH ) {
133+ pin + = key
134+ }
135+ },
136+ )
137+ }
138+ }
139+
140+
141+ @Composable
142+ private fun PinDots (
143+ pin : String ,
144+ modifier : Modifier = Modifier ,
145+ ) {
146+ Row (
147+ modifier = modifier,
148+ ) {
149+ repeat(PIN_LENGTH ) { index ->
150+ Box (
151+ modifier = Modifier
152+ .padding(horizontal = 12 .dp)
153+ .size(20 .dp)
154+ .clip(CircleShape )
155+ .border(1 .dp, Colors .Brand , CircleShape )
156+ .background(if (index < pin.length) Colors .Brand else Colors .Brand08 )
157+ )
158+ }
159+ }
160+ }
161+
72162@Preview(showBackground = true )
73163@Composable
74164private fun PreviewBio () {
@@ -77,6 +167,7 @@ private fun PreviewBio() {
77167 onSuccess = {},
78168 isBiometricsEnabled = true ,
79169 isBiometrySupported = true ,
170+ showLogoOnPin = true ,
80171 )
81172 }
82173}
@@ -89,6 +180,7 @@ private fun PreviewPin() {
89180 onSuccess = {},
90181 isBiometricsEnabled = false ,
91182 isBiometrySupported = true ,
183+ showLogoOnPin = true ,
92184 )
93185 }
94186}
0 commit comments