0baf831b58f8
C1 chrome fix: EntryDetailScreen's onBack is now nullable - null when this entry is the post-unlock landing surface (no previous back-stack entry, detected via navController.previousBackStackEntry), showing a lock-vault icon instead of a misleading back-to-list arrow. Real back arrow still shows correctly when an entry is reached by tapping a vault-list row (a genuine push). Both cases live-verified
- time
- 2026-07-24 06:44 UTC · 3 months ago
- machine
- koh2
- agent
- claude-sonnet-5
- session
- critical-corrections-c1-c7
- parent
- d1828261164d
↓ peach-android-0baf831b.tar.gzatlas at this save
files changed3 files
app/src/main/java/com/peachpasswords/ui/PeachAppNavHost.kt+9 −1app/src/main/java/com/peachpasswords/ui/vault/EntryDetailScreen.kt+40 −13app/src/main/java/com/peachpasswords/ui/vault/EntryDetailViewModel.kt+10 −0app/src/main/java/com/peachpasswords/ui/PeachAppNavHost.kt+9 −1
@@ -151,10 +151,18 @@151151 route = PeachNav.EntryDetail.ROUTE,152152 arguments = listOf(navArgument("id") { type = NavType.StringType }),153153 ) { backStack ->154+ // C1: when this entry is the post-unlock landing surface, its155+ // unlock navigation popped Lock off the stack (see the Lock156+ // composable below), so there's no previous entry — it's the157+ // deck's root, not a screen pushed from the vault list, and158+ // shouldn't offer a "back to list" affordance that implies159+ // otherwise. Only entries reached by tapping a vault-list row160+ // (a real push) get a real back target.161+ val isRoot = navController.previousBackStackEntry == null154162 EntryDetailScreen(155163 id = backStack.arguments?.getString("id").orEmpty(),156164 onNavigate = nav,157- onBack = { navController.navigate(PeachNav.VaultList.route) },165+ onBack = if (isRoot) null else ({ navController.navigate(PeachNav.VaultList.route) }),158166 )159167 }160168 composable(app/src/main/java/com/peachpasswords/ui/vault/EntryDetailScreen.kt+40 −13
@@ -39,6 +39,7 @@3939 import androidx.compose.material.icons.automirrored.rounded.ArrowBack4040 import androidx.compose.material.icons.automirrored.rounded.ArrowForward4141 import androidx.compose.material.icons.outlined.DeleteOutline42+import androidx.compose.material.icons.outlined.Lock4243 import androidx.compose.material3.Icon4344 import androidx.compose.material3.Text4445 import androidx.compose.runtime.Composable@@ -97,7 +98,10 @@9798 fun EntryDetailScreen(9899 id: String,99100 onNavigate: (PeachNav) -> Unit,100- onBack: () -> Unit,101+ // Null when this entry is the post-unlock landing surface (C1: entry deck102+ // is primary, not a child of the vault list) — there's genuinely nothing103+ // to go "back" to, so the top chrome shows a lock affordance instead.104+ onBack: (() -> Unit)?,101105 vm: EntryDetailViewModel = hiltViewModel(),102106 ) {103107 val entry by vm.entry.collectAsState()@@ -131,8 +135,13 @@131135 entry = e,132136 confirmingDelete = confirmingDelete,133137 onBack = onBack,138+ onLock = vm::lock,134139 onDelete = {135- if (confirmingDelete) vm.delete(onDone = onBack) else confirmingDelete = true140+ if (confirmingDelete) {141+ vm.delete(onDone = onBack ?: { onNavigate(PeachNav.VaultList) })142+ } else {143+ confirmingDelete = true144+ }136145 },137146 )138147 Spacer(Modifier.height(PeachSpacing.lg))@@ -364,7 +373,8 @@364373 private fun EntryDetailTopBar(365374 entry: com.peachpasswords.vault.VaultEntry?,366375 confirmingDelete: Boolean,367- onBack: () -> Unit,376+ onBack: (() -> Unit)?,377+ onLock: () -> Unit,368378 onDelete: () -> Unit,369379 ) {370380 val domain = remember(entry?.url, entry?.title) { entry?.displayDomain().orEmpty() }@@ -376,16 +386,33 @@376386 else -> entry.title377387 },378388 leading = {379- Icon(380- Icons.AutoMirrored.Rounded.ArrowBack,381- contentDescription = "back",382- tint = peachColors.T0,383- modifier = Modifier384- .size(40.dp)385- .clip(CircleShape)386- .clickable(onClick = onBack)387- .padding(PeachSpacing.sm),388- )389+ if (onBack != null) {390+ Icon(391+ Icons.AutoMirrored.Rounded.ArrowBack,392+ contentDescription = "back",393+ tint = peachColors.T0,394+ modifier = Modifier395+ .size(40.dp)396+ .clip(CircleShape)397+ .clickable(onClick = onBack)398+ .padding(PeachSpacing.sm),399+ )400+ } else {401+ // This entry is the post-unlock landing surface, not pushed402+ // from the vault list — there's nothing to go "back" to, so403+ // the chrome offers the one action that actually makes sense404+ // here instead (lock vault list's own affordance).405+ Icon(406+ Icons.Outlined.Lock,407+ contentDescription = "Lock vault",408+ tint = peachColors.T0,409+ modifier = Modifier410+ .size(40.dp)411+ .clip(CircleShape)412+ .clickable(onClick = onLock)413+ .padding(PeachSpacing.sm),414+ )415+ }389416 if (entry != null && !confirmingDelete) {390417 FaviconInitials(source = entry.url ?: entry.title, size = 16.dp)391418 }app/src/main/java/com/peachpasswords/ui/vault/EntryDetailViewModel.kt+10 −0
@@ -12,6 +12,7 @@1212 import com.peachpasswords.vault.LastViewedEntryStore1313 import com.peachpasswords.vault.TOTPGenerator1414 import com.peachpasswords.vault.VaultEntry15+import com.peachpasswords.vault.VaultLockTimer1516 import com.peachpasswords.vault.VaultRepository1617 import dagger.hilt.android.lifecycle.HiltViewModel1718 import dagger.hilt.android.qualifiers.ApplicationContext@@ -36,9 +37,18 @@3637 @ApplicationContext private val appContext: Context,3738 private val lastViewed: LastViewedEntryStore,3839 private val license: LicenseRepository,40+ private val lockTimer: VaultLockTimer,3941 savedStateHandle: SavedStateHandle,4042 ) : ViewModel() {4143 private val id: String = savedStateHandle.get<String>("id").orEmpty()44+45+ /** Lock the vault directly from the entry-deck landing surface (C1: this46+ * screen has no "back" when it's the post-unlock root, so it needs its47+ * own lock affordance rather than relying on a parent screen's). */48+ fun lock() {49+ lockTimer.cancel()50+ vault.lock()51+ }4252 4353 init {4454 // Remember this as the entry to reopen on the next unlock (brief Fix 3).