084684b358fd
fix: Resume launcher shortcuts after unlock (v2.14.50)
- time
- 2026-08-14 22:48 UTC · 2 months ago
- machine
- koh2
- parent
- 5cec09d854c5
↓ peach-android-084684b3.tar.gzatlas at this save
files changed4 files
VERSION+1 −1app/src/main/java/com/peachpasswords/ui/PeachAppNavHost.kt+8 −1app/src/main/java/com/peachpasswords/ui/lock/LockScreen.kt+6 −3app/src/test/java/com/peachpasswords/ui/lock/LockScreenRoutingTest.kt+15 −5app/src/main/java/com/peachpasswords/ui/PeachAppNavHost.kt+8 −1
@@ -126,7 +126,10 @@126126 // but the action is now recognised rather than silently dropped.127127 PendingAction.PasskeyUnlock -> null128128 } ?: return@LaunchedEffect129- navController.navigate(route) { launchSingleTop = true }129+ navController.navigate(route) {130+ launchSingleTop = true131+ popUpTo(PeachNav.Lock.route) { inclusive = true }132+ }130133 }131134 132135 val whatsNewNotes by updateVm.whatsNew.collectAsStateWithLifecycle() // Held until the vault is open: the changelog is harmless, but shoving it in@@ -229,6 +232,10 @@229232 // When an autofill nav request is pending, LockScreen must not230233 // also route to the last-viewed entry (see LaunchedEffect below).231234 autofillPending = newEntryRequest != null,235+ // Launcher shortcuts are also post-unlock destinations. Let the236+ // PendingAction effect below own the route so LockScreen cannot237+ // race it with its ordinary last-viewed-entry navigation.238+ shortcutPending = pendingAction != null,232239 )233240 }234241 composable(PeachNav.VaultList.route) {app/src/main/java/com/peachpasswords/ui/lock/LockScreen.kt+6 −3
@@ -82,7 +82,7 @@8282 internal sealed class UnlockRoute {8383 /** A freshly created vault must walk through recovery-word setup before the list. */8484 data object RecoverySetup : UnlockRoute()85- /** An autofill nav request is in flight; the NavHost owns the destination. */85+ /** A queued post-unlock navigation is in flight; the NavHost owns the destination. */8686 data object Defer : UnlockRoute()8787 /** Ordinary unlock: route to the post-unlock destination the ViewModel computed. */8888 data class PostUnlock(val destination: PeachNav) : UnlockRoute()@@ -100,10 +100,11 @@100100 internal fun routeOnUnlock(101101 state: LockUiState,102102 autofillPending: Boolean,103+ shortcutPending: Boolean = false,103104 postUnlock: () -> PeachNav,104105 ): UnlockRoute = when {105106 state.created -> UnlockRoute.RecoverySetup106- autofillPending -> UnlockRoute.Defer107+ autofillPending || shortcutPending -> UnlockRoute.Defer107108 else -> UnlockRoute.PostUnlock(postUnlock())108109 }109110 @@ -121,6 +122,8 @@121122 * the LockScreen one wins because [onUnlocked] uses popUpTo(0) inclusive.122123 */123124 autofillPending: Boolean = false,125+ /** True when a launcher shortcut is waiting for this unlock transition. */126+ shortcutPending: Boolean = false,124127 vm: LockViewModel = hiltViewModel(),125128 ) {126129 val state by vm.state.collectAsState()@@ -148,7 +151,7 @@148151 // cooperative instead of racing.149152 LaunchedEffect(state.unlocked) {150153 if (!state.unlocked) return@LaunchedEffect151- when (val route = routeOnUnlock(state, autofillPending, vm::postUnlockDestination)) {154+ when (val route = routeOnUnlock(state, autofillPending, shortcutPending, vm::postUnlockDestination)) {152155 UnlockRoute.RecoverySetup -> onNavigate(PeachNav.RecoverySetup)153156 UnlockRoute.Defer -> Unit154157 is UnlockRoute.PostUnlock -> onUnlocked(route.destination)app/src/test/java/com/peachpasswords/ui/lock/LockScreenRoutingTest.kt+15 −5
@@ -31,11 +31,11 @@3131 val dest = PeachNav.VaultList // any sentinel; must not be consulted3232 assertEquals(3333 UnlockRoute.RecoverySetup,34- routeOnUnlock(state(created = true), autofillPending = false) { dest },34+ routeOnUnlock(state(created = true), autofillPending = false, shortcutPending = false) { dest },3535 )3636 assertEquals(3737 UnlockRoute.RecoverySetup,38- routeOnUnlock(state(created = true), autofillPending = true) { dest },38+ routeOnUnlock(state(created = true), autofillPending = true, shortcutPending = false) { dest },3939 )4040 }4141 @@ -44,7 +44,7 @@4444 // LockScreen must NOT route to the post-unlock destination or the NavHost's4545 // editor navigation gets clobbered with popUpTo(0).4646 var postUnlockCalls = 047- val route = routeOnUnlock(state(created = false), autofillPending = true) {47+ val route = routeOnUnlock(state(created = false), autofillPending = true, shortcutPending = false) {4848 postUnlockCalls++4949 PeachNav.VaultList5050 }@@ -56,11 +56,21 @@5656 )5757 }5858 59+ @Test fun launcher_shortcut_unlock_defers_to_the_navhost() {60+ var postUnlockCalls = 061+ val route = routeOnUnlock(state(created = false), autofillPending = false, shortcutPending = true) {62+ postUnlockCalls++63+ PeachNav.VaultList64+ }65+ assertEquals(UnlockRoute.Defer, route)66+ assertEquals(0, postUnlockCalls)67+ }68+5969 @Test fun ordinary_unlock_routes_through_the_viewmodel_destination() {6070 // The base case: a normal unlock lands on the destination the ViewModel6171 // computed (last-viewed entry, or the vault list, or recovery setup).6272 val expected = PeachNav.EntryDetail("abc-123")63- val route = routeOnUnlock(state(created = false), autofillPending = false) { expected }73+ val route = routeOnUnlock(state(created = false), autofillPending = false, shortcutPending = false) { expected }6474 assertTrue(route is UnlockRoute.PostUnlock)6575 assertEquals(expected, (route as UnlockRoute.PostUnlock).destination)6676 }@@ -69,7 +79,7 @@6979 // Sanity: postUnlock is invoked exactly once per ordinary unlock so the7080 // ViewModel-side resolution work (which can be non-trivial) is bounded.7181 var calls = 072- val route = routeOnUnlock(state(created = false), autofillPending = false) {82+ val route = routeOnUnlock(state(created = false), autofillPending = false, shortcutPending = false) {7383 calls++7484 PeachNav.VaultList7585 }