a4ab61925a3e

fix settings card collapse gesture and vault list peach mark - C2's drag-to-collapse never fired because the focused card's own verticalScroll consumed all drag touches before the outer raw detectDragGestures on the card container ever saw them; replaced with a NestedScrollConnection on the scrollable body that watches for leftover downward drag once scrollState is already at 0, accumulates past a 60dp threshold, then hands off to the deck's collapse callback - same physical metaphor as a bottom sheet; also added trailing bottom spacer inside each card's scrollable body so bottom-of-card content (Pro card's CTA button) doesn't sit flush against the fanned tab stacked beneath it; and added the missing peach brand mark icon (PeachMark) next to the vault list's top bar wordmark, matching the browser header - live-verified card focus/collapse cycle and the peach icon on-device

time
2026-07-24 12:19 UTC · 3 months ago
machine
koh2
agent
claude-sonnet-5
session
critical-corrections-c1-c7
parent
560cbfdb55f0
2 files app/src/main/java/com/peachpasswords/ui/settings/SettingsScreen.kt+39 −1app/src/main/java/com/peachpasswords/ui/vault/VaultListScreen.kt+4 −0
app/src/main/java/com/peachpasswords/ui/settings/SettingsScreen.kt+39 −1
@@ -30,6 +30,9 @@3030 import androidx.compose.foundation.shape.RoundedCornerShape3131 import androidx.compose.foundation.rememberScrollState3232 import androidx.compose.foundation.verticalScroll33+import androidx.compose.ui.input.nestedscroll.NestedScrollConnection34+import androidx.compose.ui.input.nestedscroll.NestedScrollSource35+import androidx.compose.ui.input.nestedscroll.nestedScroll3336 import androidx.compose.runtime.Composable3437 import androidx.compose.runtime.LaunchedEffect3538 import androidx.compose.runtime.getValue@@ -323,6 +326,7 @@323326                                     deckOpen = false324327                                 }325328                             },329+                            onCollapseRequest = { deckOpen = true },326330                             enabled = !concealed,327331                         )328332                     }@@ -347,6 +351,7 @@347351     body: @Composable () -> Unit,348352     modifier: Modifier,349353     onClick: () -> Unit,354+    onCollapseRequest: () -> Unit,350355     enabled: Boolean,351356 ) {352357     val interaction = remember { MutableInteractionSource() }@@ -406,12 +411,45 @@406411                 )407412                 // Body scrolls independently; only revealed at full height in408413                 // focus mode, but kept mounted so deck-mode shows a preview.414+                // Collapse-by-scroll: once the body is already at its scroll415+                // top, a further downward drag has nowhere to go inside the416+                // scrollable — hand that leftover delta to the deck instead417+                // of letting it disappear as a no-op overscroll.418+                val scrollState = rememberScrollState()419+                val density = LocalDensity.current420+                val collapseThresholdPx = with(density) { DRAG_THRESHOLD.toPx() }421+                val nestedScrollConnection = remember(scrollState, focused) {422+                    object : NestedScrollConnection {423+                        var collapseAccum = 0f424+                        override fun onPostScroll(425+                            consumed: Offset,426+                            available: Offset,427+                            source: NestedScrollSource,428+                        ): Offset {429+                            if (!focused || source != NestedScrollSource.Drag || scrollState.value != 0 || available.y <= 0f) {430+                                collapseAccum = 0f431+                                return Offset.Zero432+                            }433+                            collapseAccum += available.y434+                            if (collapseAccum >= collapseThresholdPx) {435+                                collapseAccum = 0f436+                                onCollapseRequest()437+                            }438+                            return Offset(0f, available.y)439+                        }440+                    }441+                }409442                 Column(410443                     Modifier.fillMaxWidth().weight(1f)411444                         .padding(top = 14.dp)412-                        .verticalScroll(rememberScrollState()),445+                        .nestedScroll(nestedScrollConnection)446+                        .verticalScroll(scrollState),413447                 ) {414448                     body()449+                    // Trailing breathing room so bottom-of-card content (e.g. the450+                    // Pro card's CTA button) never sits flush against the fanned451+                    // tab stacked directly beneath it.452+                    Spacer(Modifier.height(PeachSpacing.xl))415453                 }416454             }417455         } else {

view file

app/src/main/java/com/peachpasswords/ui/vault/VaultListScreen.kt+4 −0
@@ -97,6 +97,7 @@9797 import com.peachpasswords.ui.PeachNav9898 import com.peachpasswords.ui.components.PeachEmptyOrEndState9999 import com.peachpasswords.ui.components.PeachFilterPills100+import com.peachpasswords.ui.components.PeachMark100101 import com.peachpasswords.ui.components.PeachSegment101102 import com.peachpasswords.ui.components.PeachTopBar102103 import com.peachpasswords.ui.components.FaviconInitials@@ -384,6 +385,9 @@384385         modifier = Modifier385386             .statusBarsPadding()386387             .padding(horizontal = PeachSpacing.md, vertical = PeachSpacing.xs),388+        leading = {389+            PeachMark(modifier = Modifier.padding(end = PeachSpacing.xs))390+        },387391         actions = {388392             Box(Modifier.size(6.dp).clip(CircleShape).background(syncDotColor))389393             TopBarIcon(

view file