Compare commits

..
Author SHA1 Message Date
makiolaj 2ca4335e09 fix: ignore task swipes that start at the screen edge (#90) 2026-10-28 12:05:31 +01:00
3 changed files with 70 additions and 3 deletions
@@ -0,0 +1,66 @@
package de.jeanlucmakiola.agendula.ui.common
import androidx.compose.foundation.gestures.awaitEachGesture
import androidx.compose.foundation.gestures.awaitFirstDown
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.systemGestures
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.pointer.PointerEventPass
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.layout.positionInWindow
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.platform.LocalWindowInfo
import androidx.compose.ui.unit.dp
import kotlin.math.abs
/** How far past the system back-gesture zone a horizontal swipe is still ignored. */
private val EDGE_MARGIN = 24.dp
/**
* Swallows horizontal swipes that start near the screen's left or right edge,
* before a swipe box below it sees them (#90). A back gesture started just
* inside the system's gesture zone otherwise reaches the app as a row swipe.
* Taps and vertical scrolls are left alone.
*/
@Composable
fun Modifier.ignoringEdgeSwipes(): Modifier {
val density = LocalDensity.current
val layoutDirection = LocalLayoutDirection.current
val windowWidth = LocalWindowInfo.current.containerSize.width
val insets = WindowInsets.systemGestures
val margin = with(density) { EDGE_MARGIN.roundToPx() }
val leftEdge = insets.getLeft(density, layoutDirection) + margin
val rightEdge = windowWidth - insets.getRight(density, layoutDirection) - margin
var offsetX by remember { mutableFloatStateOf(0f) }
return this
.onGloballyPositioned { offsetX = it.positionInWindow().x }
.pointerInput(leftEdge, rightEdge) {
val slop = viewConfiguration.touchSlop
awaitEachGesture {
val down = awaitFirstDown(requireUnconsumed = false, pass = PointerEventPass.Initial)
val x = offsetX + down.position.x
if (x > leftEdge && x < rightEdge) return@awaitEachGesture
var horizontal = false
while (true) {
val change = awaitPointerEvent(PointerEventPass.Initial).changes
.firstOrNull { it.id == down.id } ?: break
if (!horizontal) {
val delta = change.position - down.position
when {
abs(delta.x) > slop -> horizontal = true
abs(delta.y) > slop -> break
}
}
if (horizontal) change.consume()
if (!change.pressed) break
}
}
}
}
@@ -112,6 +112,7 @@ import de.jeanlucmakiola.agendula.domain.TaskFilter
import de.jeanlucmakiola.agendula.domain.TaskSection
import de.jeanlucmakiola.agendula.domain.TaskSections
import de.jeanlucmakiola.agendula.ui.common.LocalUse24HourFormat
import de.jeanlucmakiola.agendula.ui.common.ignoringEdgeSwipes
import de.jeanlucmakiola.agendula.ui.common.priorityAccent
import de.jeanlucmakiola.agendula.ui.lists.ListEditorSheet
import de.jeanlucmakiola.agendula.ui.lists.listWriteFailureMessage
@@ -655,7 +656,7 @@ private fun TaskRow(
SwipeToDismissBox(
state = dismissState,
modifier = modifier.fillMaxWidth().padding(horizontal = 16.dp).then(gap),
modifier = modifier.ignoringEdgeSwipes().fillMaxWidth().padding(horizontal = 16.dp).then(gap),
// dismissDirection (not targetValue) tracks the live drag, so the colour +
// icon reveal as you swipe rather than only once the threshold is crossed.
backgroundContent = { SwipeBackground(dismissState.dismissDirection, task.isCompleted) },
@@ -1037,7 +1038,7 @@ private fun SubtaskRow(
val gap = if (lastInRun) Modifier else Modifier.padding(bottom = 2.dp)
SwipeToDismissBox(
state = dismissState,
modifier = modifier.fillMaxWidth().padding(horizontal = 16.dp).then(gap),
modifier = modifier.ignoringEdgeSwipes().fillMaxWidth().padding(horizontal = 16.dp).then(gap),
backgroundContent = { SwipeBackground(dismissState.dismissDirection, task.isCompleted) },
) {
Surface(
+1 -1
View File
@@ -54,7 +54,7 @@ xpp3 = "1.1.6"
# Parses the raw SRV/TXT answers for RFC 6764 discovery; the lookup itself goes
# through Android's DnsResolver, so it follows the network and Private DNS.
# BSD-3 — it goes on the attribution screen with the rest.
dnsjava = "3.6.5"
dnsjava = "3.6.3"
# WebDAV-Push delivery. The connector only: DAVx5 also ships the embedded FCM
# distributor, which pulls in Play Services and would get us flagged on F-Droid.
unifiedpush = "3.3.5"