@@ -9,27 +9,87 @@ import androidx.compose.material3.*
99import androidx.compose.runtime.*
1010import androidx.compose.ui.Alignment
1111import androidx.compose.ui.Modifier
12+ import androidx.compose.ui.focus.FocusRequester
13+ import androidx.compose.ui.focus.focusRequester
1214import androidx.compose.ui.text.input.KeyboardType
1315import androidx.compose.ui.text.input.PasswordVisualTransformation
16+ import androidx.compose.ui.text.style.TextAlign
1417import androidx.compose.ui.unit.dp
18+ import androidx.compose.ui.unit.sp
1519import androidx.lifecycle.viewmodel.compose.viewModel
1620import com.secure.privacyfirst.viewmodel.PasswordViewModel
1721import kotlinx.coroutines.launch
1822
23+ @Composable
24+ fun PinBox (
25+ value : String ,
26+ onChange : (String ) -> Unit ,
27+ current : FocusRequester ,
28+ next : FocusRequester ? ,
29+ previous : FocusRequester ?
30+ ) {
31+ OutlinedTextField (
32+ value = value,
33+ onValueChange = { new ->
34+ val digit = new.take(1 ).filter { it.isDigit() }
35+ onChange(digit)
36+
37+ if (digit.isNotEmpty()) {
38+ // Move forward when entering a digit
39+ next?.requestFocus()
40+ } else if (new.isEmpty() && value.isNotEmpty()) {
41+ // Backspace pressed - cleared current digit, move to previous
42+ previous?.requestFocus()
43+ }
44+ },
45+ singleLine = true ,
46+ visualTransformation = PasswordVisualTransformation (),
47+ keyboardOptions = KeyboardOptions (keyboardType = KeyboardType .Number ),
48+ modifier = Modifier
49+ .size(60 .dp)
50+ .focusRequester(current),
51+ shape = RoundedCornerShape (10 .dp),
52+ textStyle = LocalTextStyle .current.copy(
53+ fontSize = 20 .sp,
54+ textAlign = TextAlign .Center
55+ )
56+ )
57+ }
58+
1959@OptIn(ExperimentalMaterial3Api ::class )
2060@Composable
2161fun SetupPinScreen (
2262 onBackClick : () -> Unit ,
2363 onPinSet : () -> Unit ,
2464 viewModel : PasswordViewModel = viewModel()
2565) {
26- var pin by remember { mutableStateOf(" " ) }
27- var confirmPin by remember { mutableStateOf(" " ) }
66+ var pin1 by remember { mutableStateOf(" " ) }
67+ var pin2 by remember { mutableStateOf(" " ) }
68+ var pin3 by remember { mutableStateOf(" " ) }
69+ var pin4 by remember { mutableStateOf(" " ) }
70+
71+ var confirmPin1 by remember { mutableStateOf(" " ) }
72+ var confirmPin2 by remember { mutableStateOf(" " ) }
73+ var confirmPin3 by remember { mutableStateOf(" " ) }
74+ var confirmPin4 by remember { mutableStateOf(" " ) }
75+
76+ val r1 = remember { FocusRequester () }
77+ val r2 = remember { FocusRequester () }
78+ val r3 = remember { FocusRequester () }
79+ val r4 = remember { FocusRequester () }
80+ val cr1 = remember { FocusRequester () }
81+ val cr2 = remember { FocusRequester () }
82+ val cr3 = remember { FocusRequester () }
83+ val cr4 = remember { FocusRequester () }
84+
2885 var error by remember { mutableStateOf(" " ) }
2986 var isLoading by remember { mutableStateOf(false ) }
3087 val scope = rememberCoroutineScope()
3188 val isPinSet by viewModel.isPinSet.collectAsState()
3289
90+ val pin = pin1 + pin2 + pin3 + pin4
91+ val confirmPin = confirmPin1 + confirmPin2 + confirmPin3 + confirmPin4
92+
3393 LaunchedEffect (Unit ) {
3494 // Check if PIN is already set
3595 if (isPinSet) {
@@ -80,38 +140,44 @@ fun SetupPinScreen(
80140
81141 Spacer (modifier = Modifier .height(8 .dp))
82142
83- OutlinedTextField (
84- value = pin,
85- onValueChange = { new ->
86- val filtered = new.filter { it.isDigit() }.take(4 )
87- pin = filtered
88- if (error.isNotEmpty()) error = " "
89- },
90- label = { Text (" Enter PIN" ) },
91- placeholder = { Text (" ••••" ) },
92- keyboardOptions = KeyboardOptions (keyboardType = KeyboardType .NumberPassword ),
93- visualTransformation = PasswordVisualTransformation (),
94- singleLine = true ,
95- modifier = Modifier .fillMaxWidth(),
96- isError = error.isNotEmpty()
143+ Text (
144+ text = " Enter PIN" ,
145+ style = MaterialTheme .typography.bodyMedium,
146+ color = MaterialTheme .colorScheme.onSurface
97147 )
98148
99- OutlinedTextField (
100- value = confirmPin,
101- onValueChange = { new ->
102- val filtered = new.filter { it.isDigit() }.take(4 )
103- confirmPin = filtered
104- if (error.isNotEmpty()) error = " "
105- },
106- label = { Text (" Confirm PIN" ) },
107- placeholder = { Text (" ••••" ) },
108- keyboardOptions = KeyboardOptions (keyboardType = KeyboardType .NumberPassword ),
109- visualTransformation = PasswordVisualTransformation (),
110- singleLine = true ,
149+ Spacer (modifier = Modifier .height(8 .dp))
150+
151+ Row (
111152 modifier = Modifier .fillMaxWidth(),
112- isError = error.isNotEmpty()
153+ horizontalArrangement = Arrangement .SpaceEvenly
154+ ) {
155+ PinBox (pin1, { pin1 = it; if (error.isNotEmpty()) error = " " }, r1, r2, null )
156+ PinBox (pin2, { pin2 = it; if (error.isNotEmpty()) error = " " }, r2, r3, r1)
157+ PinBox (pin3, { pin3 = it; if (error.isNotEmpty()) error = " " }, r3, r4, r2)
158+ PinBox (pin4, { pin4 = it; if (error.isNotEmpty()) error = " " }, r4, cr1, r3)
159+ }
160+
161+ Spacer (modifier = Modifier .height(16 .dp))
162+
163+ Text (
164+ text = " Confirm PIN" ,
165+ style = MaterialTheme .typography.bodyMedium,
166+ color = MaterialTheme .colorScheme.onSurface
113167 )
114168
169+ Spacer (modifier = Modifier .height(8 .dp))
170+
171+ Row (
172+ modifier = Modifier .fillMaxWidth(),
173+ horizontalArrangement = Arrangement .SpaceEvenly
174+ ) {
175+ PinBox (confirmPin1, { confirmPin1 = it; if (error.isNotEmpty()) error = " " }, cr1, cr2, null )
176+ PinBox (confirmPin2, { confirmPin2 = it; if (error.isNotEmpty()) error = " " }, cr2, cr3, cr1)
177+ PinBox (confirmPin3, { confirmPin3 = it; if (error.isNotEmpty()) error = " " }, cr3, cr4, cr2)
178+ PinBox (confirmPin4, { confirmPin4 = it; if (error.isNotEmpty()) error = " " }, cr4, null , cr3)
179+ }
180+
115181 if (error.isNotEmpty()) {
116182 Text (
117183 text = error,
0 commit comments