084684b358fd

fix: Resume launcher shortcuts after unlock (v2.14.50)

time
2026-08-14 22:48 UTC · 2 months ago
machine
koh2
parent
5cec09d854c5
4 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 −5
VERSION+1 −1
@@ -1 +1 @@1-2.14.491+2.14.50

view file

app/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) {

view file

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)

view file

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         }

view file