230fc12025fd

fix: long-press enters multi-select — unify tap gestures (v2.9.28)

Reported ~5x; a prior emulator-only 'fix' gave a false green. Root cause: the vault entry row ran TWO independent tap detectors on the same pointer — Modifier.clickable(onClick) plus a separate detectTapGestures(onLongPress). They raced for the gesture and clickable's detector tended to win, so the separate onLongPress never reliably fired on a real finger. - Replaced both with a single Modifier.combinedClickable(onClick, onLongClick), the established pattern already used in NotesModeScreen. It arbitrates click vs long-press correctly and keeps the interactionSource that drives the pressed row background; swipe-to-copy detector is untouched - Removed the now-unused detectTapGestures import - compileDebugKotlin green. NOTE: needs June's on-device confirmation — this bug cannot be honestly verified via emulator/adb gestural injection

time
2026-08-01 22:48 UTC · 2 months ago
machine
koh2
parent
15358ca53de8
2 files VERSION+1 −1app/src/main/java/com/peachpasswords/ui/vault/VaultListScreen.kt+23 −20
VERSION+1 −1
@@ -1 +1 @@1-2.9.271+2.9.28

view file

app/src/main/java/com/peachpasswords/ui/vault/VaultListScreen.kt+23 −20
@@ -11,8 +11,9 @@1111 import androidx.compose.foundation.background1212 import androidx.compose.foundation.border1313 import androidx.compose.foundation.clickable14+import androidx.compose.foundation.combinedClickable15+import androidx.compose.foundation.ExperimentalFoundationApi1416 import androidx.compose.foundation.gestures.detectHorizontalDragGestures15-import androidx.compose.foundation.gestures.detectTapGestures1617 import androidx.compose.foundation.gestures.detectVerticalDragGestures1718 import androidx.compose.foundation.interaction.MutableInteractionSource1819 import androidx.compose.foundation.interaction.collectIsPressedAsState@@ -562,6 +563,7 @@562563     }563564 }564565 566+@OptIn(ExperimentalFoundationApi::class)565567 @Composable566568 private fun EntryRow(567569     entry: VaultEntry,@@ -593,25 +595,26 @@593595             .fillMaxWidth()594596             .clip(RoundedCornerShape(7.dp))595597             .background(rowBg)596-            // Modifier order matters here, even though it looks incidental.597-            // detectTapGestures needs to be the OUTERMOST pointer modifier so598-            // its onLongPress gets first chance to fire (~500ms with no599-            // movement past its slop). The downstream detectHorizontalDragGestures600-            // has a smaller slop and will claim a touch with even a few pixels601-            // of motion during a long-press, swallowing the gesture — that is602-            // why long-press on a real device "did nothing" with the prior603-            // arrangement, where the swipe detector was outermost. Putting604-            // tap detection first lets it decide intent before movement605-            // arbitration kicks in.606-            .pointerInput(entry.id, multiSelect) {607-                detectTapGestures(608-                    onLongPress = {609-                        PeachHaptics.onLongPress(context)610-                        onLongPress()611-                    },612-                )613-            }614-            .clickable(interactionSource = interaction, indication = null, onClick = onTap)598+            // Click + long-press live in ONE combinedClickable, not a599+            // Modifier.clickable plus a separate detectTapGestures(onLongPress).600+            // Those ran two independent tap detectors on the same pointer that601+            // raced for the gesture — clickable's detector tended to win, so the602+            // separate onLongPress never reliably fired and long-press-to-multi-603+            // select "did nothing" on a real device (re-reported ~5x; an emulator604+            // input-injection "fix" once gave a false green). combinedClickable605+            // arbitrates click vs long-press correctly, keeps the interactionSource606+            // that drives the pressed row background, and coexists with the607+            // horizontal swipe-to-copy detector below.608+            .combinedClickable(609+                interactionSource = interaction,610+                indication = null,611+                onClick = onTap,612+                onLongClickLabel = "Select",613+                onLongClick = {614+                    PeachHaptics.onLongPress(context)615+                    onLongPress()616+                },617+            )615618             .pointerInput(entry.id, canSwipeCopy) {616619                 if (!canSwipeCopy) return@pointerInput617620                 var dx = 0f

view file