nav: fix blank back-gesture preview — let navigation drive predictive back

The per-screen Modifier.predictiveBack scaled the leaving screen with a
graphicsLayer, but NavHost only composes one back-stack entry at a time, so
nothing was drawn behind the shrinking screen — the gesture revealed the white
window background instead of the previous screen.

Drive predictive back through navigation-compose instead: it composes the
destination being revealed and seeks popExit with the gesture. popExit is now a
scaleOut + fade peek, so the leaving screen shrinks to reveal the real previous
screen held static. Drops the predictiveBack modifier from the destinations and
Settings (its inner section BackHandler is unchanged). The kit's predictiveBack
stays for overlay-style screens.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-28 19:15:24 +02:00
parent 37e3c4e659
commit 9d134be621
2 changed files with 6 additions and 13 deletions

View File

@@ -1,9 +1,10 @@
package de.jeanlucmakiola.agendula.ui.navigation
import de.jeanlucmakiola.floret.identity.predictiveBack
import androidx.compose.animation.EnterTransition
import androidx.compose.animation.ExitTransition
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.scaleOut
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Modifier
@@ -40,14 +41,13 @@ fun AgendulaNavHost(modifier: Modifier = Modifier) {
modifier = modifier,
// Only the screen on top moves; the background is always held static.
// Forward is a plain fade-in over the held screen — calm, not a slide.
// Back is the system predictive-back peek: each destination applies
// Modifier.predictiveBack, so the leaving screen scales/rounds with the
// gesture and the held screen is revealed unmoved. NavHost itself does no
// pop animation (the screen owns the motion).
// Back is the predictive-back peek: navigation-compose composes the
// screen being revealed underneath and seeks popExit with the gesture, so
// the leaving screen scales down to reveal the (unmoved) previous screen.
enterTransition = { fadeIn() },
exitTransition = { ExitTransition.None },
popEnterTransition = { EnterTransition.None },
popExitTransition = { ExitTransition.None },
popExitTransition = { scaleOut(targetScale = 0.9f) + fadeOut() },
) {
composable(Dest.LISTS) {
ListsScreen(
@@ -71,7 +71,6 @@ fun AgendulaNavHost(modifier: Modifier = Modifier) {
LaunchedEffect(filter) { vm.bind(filter) }
TaskListScreen(
modifier = Modifier.predictiveBack(onBack = { nav.popBackStack() }),
filter = filter,
viewModel = vm,
onOpenTask = { taskId -> nav.navigate(Dest.TaskDetail.build(taskId)) },
@@ -89,7 +88,6 @@ fun AgendulaNavHost(modifier: Modifier = Modifier) {
LaunchedEffect(taskId) { vm.bind(taskId) }
TaskDetailScreen(
modifier = Modifier.predictiveBack(onBack = { nav.popBackStack() }),
viewModel = vm,
onEdit = { nav.navigate(Dest.TaskEdit.buildEdit(taskId)) },
onDeleted = { nav.popBackStack() },
@@ -118,7 +116,6 @@ fun AgendulaNavHost(modifier: Modifier = Modifier) {
}
TaskEditScreen(
modifier = Modifier.predictiveBack(onBack = { nav.popBackStack() }),
viewModel = vm,
onSaved = { nav.popBackStack() },
onBack = { nav.popBackStack() },

View File

@@ -93,7 +93,6 @@ import de.jeanlucmakiola.floret.components.pastelize
import de.jeanlucmakiola.floret.components.positionOf
import de.jeanlucmakiola.floret.identity.collapseExit
import de.jeanlucmakiola.floret.identity.expandEnter
import de.jeanlucmakiola.floret.identity.predictiveBack
import de.jeanlucmakiola.floret.reminders.ReminderOverride
import de.jeanlucmakiola.agendula.ui.common.reminderLeadTimeLabel
@@ -128,9 +127,6 @@ fun SettingsScreen(
Box(
modifier = modifier
// At the hub, the back gesture previews the pop to the lists overview;
// in a section the BackHandler above takes it (section -> hub) instead.
.predictiveBack(onBack = onBack, enabled = section == null)
.fillMaxSize()
.background(MaterialTheme.colorScheme.surface),
) {