Skip to content

Commit 098c3dd

Browse files
committed
fix inconsistency with pin length and fix backspace during pin entry
1 parent 0f9a14b commit 098c3dd

File tree

2 files changed

+109
-35
lines changed

2 files changed

+109
-35
lines changed

app/src/main/java/com/secure/privacyfirst/ui/screens/AuthScreen.kt

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -154,14 +154,22 @@ fun AuthScreen(
154154
value: String,
155155
onChange: (String) -> Unit,
156156
current: FocusRequester,
157-
next: FocusRequester?
157+
next: FocusRequester?,
158+
previous: FocusRequester?
158159
) {
159160
OutlinedTextField(
160161
value = value,
161162
onValueChange = { new ->
162163
val digit = new.take(1).filter { it.isDigit() }
163164
onChange(digit)
164-
if (digit.isNotEmpty()) next?.requestFocus()
165+
166+
if (digit.isNotEmpty()) {
167+
// Move forward when entering a digit
168+
next?.requestFocus()
169+
} else if (new.isEmpty() && value.isNotEmpty()) {
170+
// Backspace pressed - cleared current digit, move to previous
171+
previous?.requestFocus()
172+
}
165173
},
166174
singleLine = true,
167175
visualTransformation = PasswordVisualTransformation(),
@@ -177,10 +185,10 @@ fun AuthScreen(
177185
)
178186
}
179187

180-
PinDigit(d1, { d1 = it }, r1, r2)
181-
PinDigit(d2, { d2 = it }, r2, r3)
182-
PinDigit(d3, { d3 = it }, r3, r4)
183-
PinDigit(d4, { d4 = it }, r4, null)
188+
PinDigit(d1, { d1 = it }, r1, r2, null)
189+
PinDigit(d2, { d2 = it }, r2, r3, r1)
190+
PinDigit(d3, { d3 = it }, r3, r4, r2)
191+
PinDigit(d4, { d4 = it }, r4, null, r3)
184192
}
185193
Spacer(modifier = Modifier.height(20.dp))
186194
if (error.isNotEmpty()) {

app/src/main/java/com/secure/privacyfirst/ui/screens/SetupPinScreen.kt

Lines changed: 95 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -9,27 +9,87 @@ import androidx.compose.material3.*
99
import androidx.compose.runtime.*
1010
import androidx.compose.ui.Alignment
1111
import androidx.compose.ui.Modifier
12+
import androidx.compose.ui.focus.FocusRequester
13+
import androidx.compose.ui.focus.focusRequester
1214
import androidx.compose.ui.text.input.KeyboardType
1315
import androidx.compose.ui.text.input.PasswordVisualTransformation
16+
import androidx.compose.ui.text.style.TextAlign
1417
import androidx.compose.ui.unit.dp
18+
import androidx.compose.ui.unit.sp
1519
import androidx.lifecycle.viewmodel.compose.viewModel
1620
import com.secure.privacyfirst.viewmodel.PasswordViewModel
1721
import 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
2161
fun 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

Comments
 (0)