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
↓ peach-android-560cbfdb.tar.gzatlas at this save
files changed3 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 −2app/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,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)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))