diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/NavigationEffects.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/NavigationEffects.kt index 7b113393d..cf4a4ee7e 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/NavigationEffects.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/NavigationEffects.kt @@ -34,19 +34,22 @@ import androidx.navigation.NavGraphBuilder import androidx.navigation.compose.composable import androidx.navigation.toRoute -// Per-entry hint stored on NavBackStackEntry.savedStateHandle by -// Nav.navBottomBar. When present, composableFromEnd skips the horizontal -// slide so bottom-bar taps fall back to the NavHost-level fade. -const val SKIP_SLIDE_ANIMATION_KEY = "skipSlideAnimation" +// Per-entry hint stamped by Nav.navBottomBar marking that the entry was +// reached via a bottom-nav tab. Used in two places: +// - composableFromEnd skips the horizontal slide on tab entries so +// bottom-bar taps fall back to the NavHost-level fade. +// - Nav.canPop hides the back arrow on tab roots even though Home +// sits below them in the stack. +const val BOTTOM_NAV_ROOT_KEY = "bottomNavRoot" -fun NavBackStackEntry.skipsSlideAnimation(): Boolean = savedStateHandle.get(SKIP_SLIDE_ANIMATION_KEY) == true +fun NavBackStackEntry.isBottomNavRoot(): Boolean = savedStateHandle.get(BOTTOM_NAV_ROOT_KEY) == true inline fun NavGraphBuilder.composableFromEnd(noinline content: @Composable AnimatedContentScope.(NavBackStackEntry) -> Unit) { composable( - enterTransition = { if (targetState.skipsSlideAnimation()) null else slideInHorizontallyFromEnd }, - exitTransition = { if (targetState.skipsSlideAnimation()) null else scaleOut }, - popEnterTransition = { if (initialState.skipsSlideAnimation()) null else scaleIn }, - popExitTransition = { if (initialState.skipsSlideAnimation()) null else slideOutHorizontallyToEnd }, + enterTransition = { if (targetState.isBottomNavRoot()) null else slideInHorizontallyFromEnd }, + exitTransition = { if (targetState.isBottomNavRoot()) null else scaleOut }, + popEnterTransition = { if (initialState.isBottomNavRoot()) null else scaleIn }, + popExitTransition = { if (initialState.isBottomNavRoot()) null else slideOutHorizontallyToEnd }, content = content, ) } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/navs/Nav.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/navs/Nav.kt index 89f05c7ab..3d2a94b4f 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/navs/Nav.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/navs/Nav.kt @@ -24,9 +24,9 @@ import android.annotation.SuppressLint import androidx.compose.material3.DrawerState import androidx.compose.material3.DrawerValue import androidx.compose.runtime.Stable -import androidx.navigation.NavGraph.Companion.findStartDestination import androidx.navigation.NavHostController -import com.vitorpamplona.amethyst.ui.navigation.SKIP_SLIDE_ANIMATION_KEY +import com.vitorpamplona.amethyst.ui.navigation.BOTTOM_NAV_ROOT_KEY +import com.vitorpamplona.amethyst.ui.navigation.isBottomNavRoot import com.vitorpamplona.amethyst.ui.navigation.routes.Route import com.vitorpamplona.amethyst.ui.navigation.routes.getRouteWithArguments import kotlinx.coroutines.CoroutineScope @@ -79,22 +79,25 @@ class Nav( override fun navBottomBar(route: Route) { navigationScope.launch { controller.navigate(route) { - // Clear the back stack down to and including the graph's start - // destination so a bottom-nav tap leaves only the new route in - // the stack. Without inclusive=true, Home would remain below - // the new tab and canPop() would wrongly show a back arrow. - popUpTo(controller.graph.findStartDestination().id) { - inclusive = true + // Clear sibling bottom-nav entries but keep Home (the start + // destination) below, so back-swipe from any tab returns to + // Home and back-swipe from Home leaves the app. + popUpTo(Route.Home) { + inclusive = false } launchSingleTop = true } - // Stamp the entry so composableFromEnd's transition lambdas can - // skip the slide animation when this entry enters or exits. - controller.getBackStackEntry(route).savedStateHandle[SKIP_SLIDE_ANIMATION_KEY] = true + // Mark this entry as a tab root: hides the back arrow in canPop + // and skips the horizontal slide in composableFromEnd. + controller.getBackStackEntry(route).savedStateHandle[BOTTOM_NAV_ROOT_KEY] = true } } - override fun canPop(): Boolean = controller.previousBackStackEntry != null + override fun canPop(): Boolean { + val current = controller.currentBackStackEntry ?: return false + if (current.isBottomNavRoot()) return false + return controller.previousBackStackEntry != null + } override fun popBack() { navigationScope.launch {