789289291437
add the four create-vault pieces missing vs Lock.tsx: blocker message on the submit button (reuses the existing createValidationError, suppressed while the field is empty) so a disabled button explains itself instead of silently doing nothing; show/hide reveal toggle via a new masked param on UnderlineField; static At-least-12-characters hint that the strength bar replaces once typing starts; and back-to-onboarding by keeping Onboarding on the nav backstack (system back gesture, no custom button) with the four post-vault escape points switched from popUpTo(Lock) to the popUpTo(0) pattern already used by the panic route, so a returning user with no Onboarding entry still clears correctly. Live-verified all three blocker states, reveal toggle, hint-to-strength swap, back-to-card-3, and full create to recovery-words to vault-list with the pre-vault stack cleared.
- time
- 2026-07-24 18:52 UTC · 3 months ago
- machine
- koh2
- agent
- claude-opus-5
- session
- create-vault surface parity
- parent
- 7e62540bd7b5
↓ peach-android-78928929.tar.gzatlas at this save
files changed2 files
app/src/main/java/com/peachpasswords/ui/PeachAppNavHost.kt+5 −7app/src/main/java/com/peachpasswords/ui/lock/LockScreen.kt+41 −5app/src/main/java/com/peachpasswords/ui/PeachAppNavHost.kt+5 −7
@@ -129,9 +129,7 @@129129 composable(PeachNav.Onboarding.route) {130130 OnboardingScreen(131131 onFinished = {132- navController.navigate(PeachNav.Lock.route) {133- popUpTo(PeachNav.Onboarding.route) { inclusive = true }134- }132+ navController.navigate(PeachNav.Lock.route)135133 },136134 onRestore = { nav(PeachNav.Import) },137135 )@@ -140,7 +138,7 @@140138 LockScreen(141139 onUnlocked = { dest ->142140 navController.navigate(dest.route) {143- popUpTo(PeachNav.Lock.route) { inclusive = true }141+ popUpTo(0) { inclusive = true }144142 }145143 },146144 onNavigate = nav,@@ -232,7 +230,7 @@232230 onBack = back,233231 onVaultCreated = {234232 navController.navigate(PeachNav.RecoverySetup.route) {235- popUpTo(PeachNav.Lock.route) { inclusive = true }233+ popUpTo(0) { inclusive = true }236234 }237235 },238236 )@@ -253,7 +251,7 @@253251 requireConfirm = true,254252 onDone = {255253 navController.navigate(PeachNav.VaultList.route) {256- popUpTo(PeachNav.Lock.route) { inclusive = true }254+ popUpTo(0) { inclusive = true }257255 }258256 },259257 )@@ -265,7 +263,7 @@265263 RecoveryWordEntryScreen(266264 onSuccess = {267265 navController.navigate(PeachNav.VaultList.route) {268- popUpTo(PeachNav.Lock.route) { inclusive = true }266+ popUpTo(0) { inclusive = true }269267 }270268 },271269 onBack = back,app/src/main/java/com/peachpasswords/ui/lock/LockScreen.kt+41 −5
@@ -44,6 +44,7 @@4444 import androidx.compose.ui.text.input.ImeAction4545 import androidx.compose.ui.text.input.KeyboardType4646 import androidx.compose.ui.text.input.PasswordVisualTransformation47+import androidx.compose.ui.text.input.VisualTransformation4748 import androidx.compose.ui.text.style.TextAlign4849 import androidx.compose.ui.unit.IntOffset4950 import androidx.compose.ui.unit.dp@@ -80,7 +81,13 @@8081 8182 var password by remember { mutableStateOf("") }8283 var confirm by remember { mutableStateOf("") }84+ var reveal by remember { mutableStateOf(false) }8385 val createScore = remember(password) { scorePassword(password) }86+ // Why the create button is disabled, shown on the button itself. Without87+ // it the button just silently does nothing (browser parity: Lock.tsx's88+ // createBlockerMessage). Null before typing — no scolding on an empty form.89+ val createBlocker = if (state.hasVault || password.isEmpty()) null90+ else createValidationError(password, confirm, createScore)8491 8592 LaunchedEffect(state.unlocked) {8693 if (state.unlocked) {@@ -164,7 +171,21 @@164171 GlassPanel(165172 modifier = Modifier.offset { IntOffset(shake.value.dp.roundToPx(), 0) },166173 ) {167- FieldLabel(if (state.hasVault) "master password" else "create a master password")174+ Row(verticalAlignment = Alignment.CenterVertically) {175+ FieldLabel(if (state.hasVault) "master password" else "create a master password")176+ if (!state.hasVault) {177+ Spacer(Modifier.weight(1f))178+ Text(179+ text = if (reveal) "hide" else "show",180+ color = accent,181+ style = PeachType.labelMono.copy(fontSize = 11.sp),182+ modifier = Modifier183+ .clip(RoundedCornerShape(6.dp))184+ .clickable { reveal = !reveal }185+ .padding(horizontal = 6.dp, vertical = 4.dp),186+ )187+ }188+ }168189 Spacer(Modifier.height(4.dp))169190 UnderlineField(170191 value = password,@@ -173,6 +194,7 @@173194 textStyle = if (state.hasVault) PeachType.unlockInput else mono18Spaced(),174195 imeAction = if (state.hasVault) ImeAction.Go else ImeAction.Next,175196 onImeAction = { if (state.hasVault) vm.unlock(password) },197+ masked = state.hasVault || !reveal,176198 )177199 178200 if (!state.hasVault) {@@ -186,10 +208,18 @@186208 textStyle = mono18Spaced(),187209 imeAction = ImeAction.Go,188210 onImeAction = { vm.create(password, confirm) },211+ masked = !reveal,189212 )190213 191- if (password.isNotEmpty()) {192- Spacer(Modifier.height(14.dp))214+ Spacer(Modifier.height(14.dp))215+ if (password.isEmpty()) {216+ Text(217+ text = "At least $MIN_PASSWORD_LENGTH characters. The vault is created once both fields match and pass the strength check.",218+ color = peachColors.T2,219+ style = PeachType.hint,220+ textAlign = TextAlign.Center,221+ )222+ } else {193223 StrengthBar(score = createScore)194224 Spacer(Modifier.height(6.dp))195225 Text(@@ -212,7 +242,12 @@212242 213243 Spacer(Modifier.height(4.dp))214244 AccentButton(215- label = if (state.hasVault) "unlock" else "create vault",245+ label = when {246+ state.hasVault -> "unlock"247+ state.loading -> "creating..."248+ createBlocker != null -> createBlocker249+ else -> "create vault"250+ },216251 enabled = !state.loading && (state.hasVault || canSubmitCreateVault(password, confirm, createScore)),217252 onClick = { if (state.hasVault) vm.unlock(password) else vm.create(password, confirm) },218253 )@@ -283,6 +318,7 @@283318 textStyle: TextStyle,284319 imeAction: ImeAction,285320 onImeAction: () -> Unit,321+ masked: Boolean = true,286322 ) {287323 val accent = LocalPeachAccent.current288324 val interaction = remember { MutableInteractionSource() }@@ -294,7 +330,7 @@294330 singleLine = true,295331 textStyle = textStyle.copy(color = peachColors.T0, textAlign = TextAlign.Center),296332 cursorBrush = SolidColor(accent),297- visualTransformation = PasswordVisualTransformation(),333+ visualTransformation = if (masked) PasswordVisualTransformation() else VisualTransformation.None,298334 keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password, imeAction = imeAction),299335 keyboardActions = KeyboardActions(onGo = { onImeAction() }, onNext = { onImeAction() }),300336 interactionSource = interaction,