nav: swap the full slide-back for the predictive-back peek

Back was a full-width horizontal slide-off — too heavy. Replace it with
floret-kit's Modifier.predictiveBack on each pop-able destination (task list,
detail, edit) so the leaving screen scales/rounds with the gesture and the held
screen is revealed unmoved; NavHost now does no pop animation. Settings applies
it at the hub only (enabled = section == null) so its inner section back still
returns to the hub. Forward stays a calm fade-in.

Bumps the floret-kit submodule.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-28 14:31:20 +02:00
parent 63c6191676
commit 37662e83bb
3 changed files with 14 additions and 8 deletions

View File

@@ -1,11 +1,9 @@
package de.jeanlucmakiola.agendula.ui.navigation package de.jeanlucmakiola.agendula.ui.navigation
import de.jeanlucmakiola.floret.identity.rememberNavSlideSpec import de.jeanlucmakiola.floret.identity.predictiveBack
import androidx.compose.animation.EnterTransition import androidx.compose.animation.EnterTransition
import androidx.compose.animation.ExitTransition import androidx.compose.animation.ExitTransition
import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.slideOutHorizontally
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
@@ -35,7 +33,6 @@ import de.jeanlucmakiola.agendula.ui.tasklist.TaskListViewModel
@Composable @Composable
fun AgendulaNavHost(modifier: Modifier = Modifier) { fun AgendulaNavHost(modifier: Modifier = Modifier) {
val nav = rememberNavController() val nav = rememberNavController()
val slide = rememberNavSlideSpec()
NavHost( NavHost(
navController = nav, navController = nav,
@@ -43,12 +40,14 @@ fun AgendulaNavHost(modifier: Modifier = Modifier) {
modifier = modifier, modifier = modifier,
// Only the screen on top moves; the background is always held static. // 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. // Forward is a plain fade-in over the held screen — calm, not a slide.
// Back (and the predictive-back drag, which drives popExit) is the snappy // Back is the system predictive-back peek: each destination applies
// slide-out to the right, revealing the held screen unmoved. // 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).
enterTransition = { fadeIn() }, enterTransition = { fadeIn() },
exitTransition = { ExitTransition.None }, exitTransition = { ExitTransition.None },
popEnterTransition = { EnterTransition.None }, popEnterTransition = { EnterTransition.None },
popExitTransition = { slideOutHorizontally(slide) { it } + fadeOut() }, popExitTransition = { ExitTransition.None },
) { ) {
composable(Dest.LISTS) { composable(Dest.LISTS) {
ListsScreen( ListsScreen(
@@ -72,6 +71,7 @@ fun AgendulaNavHost(modifier: Modifier = Modifier) {
LaunchedEffect(filter) { vm.bind(filter) } LaunchedEffect(filter) { vm.bind(filter) }
TaskListScreen( TaskListScreen(
modifier = Modifier.predictiveBack(onBack = { nav.popBackStack() }),
filter = filter, filter = filter,
viewModel = vm, viewModel = vm,
onOpenTask = { taskId -> nav.navigate(Dest.TaskDetail.build(taskId)) }, onOpenTask = { taskId -> nav.navigate(Dest.TaskDetail.build(taskId)) },
@@ -89,6 +89,7 @@ fun AgendulaNavHost(modifier: Modifier = Modifier) {
LaunchedEffect(taskId) { vm.bind(taskId) } LaunchedEffect(taskId) { vm.bind(taskId) }
TaskDetailScreen( TaskDetailScreen(
modifier = Modifier.predictiveBack(onBack = { nav.popBackStack() }),
viewModel = vm, viewModel = vm,
onEdit = { nav.navigate(Dest.TaskEdit.buildEdit(taskId)) }, onEdit = { nav.navigate(Dest.TaskEdit.buildEdit(taskId)) },
onDeleted = { nav.popBackStack() }, onDeleted = { nav.popBackStack() },
@@ -117,6 +118,7 @@ fun AgendulaNavHost(modifier: Modifier = Modifier) {
} }
TaskEditScreen( TaskEditScreen(
modifier = Modifier.predictiveBack(onBack = { nav.popBackStack() }),
viewModel = vm, viewModel = vm,
onSaved = { nav.popBackStack() }, onSaved = { nav.popBackStack() },
onBack = { nav.popBackStack() }, onBack = { nav.popBackStack() },

View File

@@ -93,6 +93,7 @@ import de.jeanlucmakiola.floret.components.Position
import de.jeanlucmakiola.agendula.ui.common.ReminderLeadPicker import de.jeanlucmakiola.agendula.ui.common.ReminderLeadPicker
import de.jeanlucmakiola.floret.components.pastelize import de.jeanlucmakiola.floret.components.pastelize
import de.jeanlucmakiola.floret.components.positionOf import de.jeanlucmakiola.floret.components.positionOf
import de.jeanlucmakiola.floret.identity.predictiveBack
import de.jeanlucmakiola.floret.locale.AppLanguage import de.jeanlucmakiola.floret.locale.AppLanguage
import de.jeanlucmakiola.floret.reminders.ReminderOverride import de.jeanlucmakiola.floret.reminders.ReminderOverride
import de.jeanlucmakiola.agendula.ui.common.reminderLeadTimeLabel import de.jeanlucmakiola.agendula.ui.common.reminderLeadTimeLabel
@@ -128,6 +129,9 @@ fun SettingsScreen(
Box( Box(
modifier = modifier 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() .fillMaxSize()
.background(MaterialTheme.colorScheme.surface), .background(MaterialTheme.colorScheme.surface),
) { ) {