Remove the drag-right-to-go-back gesture

The screen no longer follows a horizontal drag. Back is the arrow at the
top left and the platform's own edge gesture, both unchanged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
irisandClaude Opus 5 committed 2026-09-03 23:41:58 -04:00
1 parent 9fdab777b4
commit 8881a40919
3 files changed
+11 -85

No files matched your search

@@ -198,21 +198,17 @@ fun AppRoot(
// row key. Only reachable since a notification can move straight from one session to
// another; every other way here passes through [Screen.Main], which disposes it anyway.
key(here.summary.id) {
// The gesture goes on a box around the screen rather than inside it, so it is the
// outermost thing in the tree and everything within has already had its chance at
// the drag. See [swipeBack]. No imePadding here, for the reason above.
Box(Modifier.swipeBack(goToMain)) {
SessionScreen(
settings = current,
summary = here.summary,
onBack = goToMain,
share = share,
onShareTaken = { share = null },
)
}
// No imePadding here, for the reason above.
SessionScreen(
settings = current,
summary = here.summary,
onBack = goToMain,
share = share,
onShareTaken = { share = null },
)
}
is Screen.Spawn ->
Box(Modifier.imePadding().swipeBack(goToMain)) {
Box(Modifier.imePadding()) {
SpawnScreen(
settings = current,
onSpawned = { spawned ->
@@ -223,7 +219,7 @@ fun AppRoot(
)
}
is Screen.Settings ->
Box(Modifier.imePadding().swipeBack(goToMain)) {
Box(Modifier.imePadding()) {
SettingsScreen(
existing = current,
onSaved = { saved ->
@@ -1,70 +0,0 @@
package com.example.aiapp
import androidx.compose.animation.core.Animatable
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.gestures.draggable
import androidx.compose.foundation.gestures.rememberDraggableState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import kotlinx.coroutines.launch
/**
* Dragging the screen to the right to step back to the one behind it.
*
* The platform's own back gesture is a swipe from the very edge, and only from there; on a phone
* held in one hand the way back from a session is either that narrow strip or the arrow at the top
* left, which is the far corner from the thumb. This is the same movement from anywhere on the
* screen.
*
* **It loses every argument.** The gesture is a plain horizontal [draggable] on the outside of the
* screen, so anything inside that wants horizontal drags has already taken them by the time this
* would see them: pointer events reach the innermost node first, and a drag a child has consumed
* never crosses this modifier's touch slop. That is what keeps a wide code fence, a table scrolled
* sideways or a text selection working -- they are the components the reader meant, and this is
* only what is left over. Vertical drags are not its orientation, so the transcript scrolls
* untouched.
*
* The screen follows the finger rather than jumping at the end, because a gesture with no feedback
* cannot be aborted: the reader has to be able to see it starting and change their mind. Released
* short of [SWIPE_BACK_TRAVEL] it slides back and nothing happens. Right rather than left, and only
* right, since there is nothing forward of these screens to go to.
*/
@Composable
fun Modifier.swipeBack(onBack: () -> Unit): Modifier {
val offset = remember { Animatable(0f) }
val scope = rememberCoroutineScope()
val travel = with(LocalDensity.current) { SWIPE_BACK_TRAVEL.toPx() }
return draggable(
state =
rememberDraggableState { delta ->
// Rightward only: a leftward drag stays at zero rather than lifting the
// screen off its left edge, which would look like a gesture that does
// something and does not.
scope.launch { offset.snapTo((offset.value + delta).coerceAtLeast(0f)) }
},
orientation = Orientation.Horizontal,
onDragStopped = {
if (offset.value >= travel) {
onBack()
// Straight back rather than animated: the screen this was moving is being
// replaced, and animating it home first would show the old one sliding back
// into place after the new one had arrived.
offset.snapTo(0f)
} else {
offset.animateTo(0f)
}
},
)
// Read inside the block, so following the finger is a draw-phase change and costs no
// recomposition of the screen being dragged.
.graphicsLayer { translationX = offset.value }
}
/** How far the screen has to be pulled for letting go to mean "back" rather than "never mind". */
private val SWIPE_BACK_TRAVEL: Dp = 96.dp