560cbfdb55f0

add horizontal swipe between deck entries and vertical slide-down-to-collapse on the vault list search bar, completing the entry-deck/vault-list symbiotic relationship - swipe left/right on EntryDetailScreen resolves via vm.adjacentDestination on drag end (120px threshold, mirrors the existing tap-arrow logic); VaultListScreen gains an optional onBack wired only when it was pushed on top of an entry (previousBackStackEntry != null), with a drag-down gesture on its search bar popping back to that exact entry, symmetric with the existing slide-up-to-open gesture; live-verified all four gestures on-device with a fresh seeded test vault

time
2026-07-24 12:07 UTC · 3 months ago
machine
koh2
agent
claude-sonnet-5
session
critical-corrections-c1-c7
parent
6e842eac315e
3 files app/src/main/java/com/peachpasswords/ui/PeachAppNavHost.kt+10 −1app/src/main/java/com/peachpasswords/ui/vault/EntryDetailScreen.kt+22 −1app/src/main/java/com/peachpasswords/ui/vault/VaultListScreen.kt+29 −2
app/src/main/java/com/peachpasswords/ui/PeachAppNavHost.kt+10 −1
@@ -145,7 +145,16 @@145145                 onNavigate = nav,146146             )147147         }148-        composable(PeachNav.VaultList.route) { VaultListScreen(onNavigate = nav) }148+        composable(PeachNav.VaultList.route) {149+            // C1: non-null only when the list was pushed on top of an entry150+            // (slide-up-to-open from EntryDetailSearchStrip) — slide back151+            // down pops back to that exact entry instance.152+            val hasOrigin = navController.previousBackStackEntry != null153+            VaultListScreen(154+                onNavigate = nav,155+                onBack = if (hasOrigin) ({ navController.popBackStack() }) else null,156+            )157+        }149158 150159         composable(151160             route = PeachNav.EntryDetail.ROUTE,

view file

app/src/main/java/com/peachpasswords/ui/vault/EntryDetailScreen.kt+22 −1
@@ -11,6 +11,7 @@1111 import androidx.compose.foundation.background1212 import androidx.compose.foundation.border1313 import androidx.compose.foundation.clickable14+import androidx.compose.foundation.gestures.detectHorizontalDragGestures1415 import androidx.compose.foundation.gestures.detectVerticalDragGestures1516 import androidx.compose.foundation.layout.Arrangement1617 import androidx.compose.foundation.layout.Box@@ -124,7 +125,27 @@124125 125126     val e = entry126127     ShellBackground {127-        Box(Modifier.fillMaxSize().statusBarsPadding()) {128+        Box(129+            Modifier.fillMaxSize().statusBarsPadding()130+                // C1: swipe left/right between deck entries, mirroring the131+                // prev/next arrows in EntryDetailSearchStrip. Resolved on132+                // drag end (not per-event) so an in-progress vertical scroll133+                // gesture on the content below can't accidentally trigger it.134+                .pointerInput(vm) {135+                    var dragAccum = 0f136+                    detectHorizontalDragGestures(137+                        onDragStart = { dragAccum = 0f },138+                        onDragEnd = {139+                            when {140+                                dragAccum <= -120f -> vm.adjacentDestination(1)?.let(onNavigate)141+                                dragAccum >= 120f -> vm.adjacentDestination(-1)?.let(onNavigate)142+                            }143+                            dragAccum = 0f144+                        },145+                        onDragCancel = { dragAccum = 0f },146+                    ) { _, dragAmount -> dragAccum += dragAmount }147+                },148+        ) {128149             Column(129150                 Modifier.fillMaxSize()130151                     .padding(bottom = 100.dp)

view file

app/src/main/java/com/peachpasswords/ui/vault/VaultListScreen.kt+29 −2
@@ -13,6 +13,7 @@1313 import androidx.compose.foundation.clickable1414 import androidx.compose.foundation.gestures.detectHorizontalDragGestures1515 import androidx.compose.foundation.gestures.detectTapGestures16+import androidx.compose.foundation.gestures.detectVerticalDragGestures1617 import androidx.compose.foundation.interaction.MutableInteractionSource1718 import androidx.compose.foundation.interaction.collectIsPressedAsState1819 import androidx.compose.foundation.layout.Arrangement@@ -112,6 +113,10 @@112113 @Composable113114 fun VaultListScreen(114115     onNavigate: (PeachNav) -> Unit,116+    // C1: non-null when the list was reached by sliding up from an entry's117+    // search strip — slide back down to collapse to that entry. Null when118+    // the list itself is the deck root (e.g. empty vault, nothing to open).119+    onBack: (() -> Unit)? = null,115120     vm: VaultListViewModel = hiltViewModel(),116121 ) {117122     val entries by vm.entries.collectAsState()@@ -178,6 +183,7 @@178183                 value = search,179184                 onChange = vm::setSearch,180185                 count = if (search.isNotBlank()) "${entries.size}/$total" else pluralEntries(total),186+                onCollapse = onBack,181187             )182188             if (allTags.isNotEmpty()) {183189                 TagFilterStrip(@@ -416,7 +422,7 @@416422 private fun pluralEntries(total: Int): String = if (total == 1) "1 entry" else "$total entries"417423 418424 @Composable419-private fun SearchBar(value: String, onChange: (String) -> Unit, count: String) {425+private fun SearchBar(value: String, onChange: (String) -> Unit, count: String, onCollapse: (() -> Unit)? = null) {420426     val accent = LocalPeachAccent.current421427     Column(Modifier.fillMaxWidth()) {422428         Row(@@ -426,7 +432,28 @@426432                 .clip(RoundedCornerShape(12.dp))427433                 .background(peachColors.S2)428434                 .border(1.dp, peachColors.T3, RoundedCornerShape(12.dp))429-                .padding(horizontal = PeachSpacing.md, vertical = PeachSpacing.sm),435+                .padding(horizontal = PeachSpacing.md, vertical = PeachSpacing.sm)436+                // C1: inverse of EntryDetailSearchStrip's slide-up-to-open —437+                // slide the list's own search bar down to collapse back to438+                // the entry it was opened from.439+                .then(440+                    if (onCollapse != null) {441+                        Modifier.pointerInput(onCollapse) {442+                            var collapsed = false443+                            detectVerticalDragGestures(444+                                onDragEnd = { collapsed = false },445+                                onDragCancel = { collapsed = false },446+                            ) { _, dragAmount ->447+                                if (!collapsed && dragAmount > 4f) {448+                                    collapsed = true449+                                    onCollapse()450+                                }451+                            }452+                        }453+                    } else {454+                        Modifier455+                    },456+                ),430457             verticalAlignment = Alignment.CenterVertically,431458         ) {432459             Icon(Icons.Rounded.Search, contentDescription = null, tint = peachColors.T2, modifier = Modifier.size(14.dp))

view file