-
Notifications
You must be signed in to change notification settings - Fork 5.4k
Migrate Jetsnack to Navigation 3 #1647
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
51ac93d
056afd1
9308c4c
0cfa719
b02f458
3c9c8cb
967f393
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -20,38 +20,44 @@ | |
|
|
||
| package com.example.jetsnack.ui | ||
|
|
||
| import androidx.compose.animation.AnimatedVisibilityScope | ||
| import androidx.compose.animation.AnimatedVisibility | ||
| import androidx.compose.animation.ExperimentalSharedTransitionApi | ||
| import androidx.compose.animation.SharedTransitionLayout | ||
| import androidx.compose.animation.SharedTransitionScope | ||
| import androidx.compose.animation.fadeIn | ||
| import androidx.compose.animation.fadeOut | ||
| import androidx.compose.animation.slideInVertically | ||
| import androidx.compose.animation.slideOutVertically | ||
| import androidx.compose.animation.togetherWith | ||
| import androidx.compose.foundation.layout.consumeWindowInsets | ||
| import androidx.compose.foundation.layout.padding | ||
| import androidx.compose.foundation.layout.systemBarsPadding | ||
| import androidx.compose.material3.SnackbarHost | ||
| import androidx.compose.runtime.Composable | ||
| import androidx.compose.runtime.CompositionLocalProvider | ||
| import androidx.compose.runtime.compositionLocalOf | ||
| import androidx.compose.runtime.getValue | ||
| import androidx.compose.ui.Modifier | ||
| import androidx.compose.ui.tooling.preview.Preview | ||
| import androidx.navigation.NavBackStackEntry | ||
| import androidx.navigation.NavType | ||
| import androidx.navigation.compose.NavHost | ||
| import androidx.navigation.compose.currentBackStackEntryAsState | ||
| import androidx.navigation.navArgument | ||
| import androidx.navigation3.runtime.entryProvider | ||
| import androidx.navigation3.runtime.rememberNavBackStack | ||
| import androidx.navigation3.ui.NavDisplay | ||
| import com.example.jetsnack.ui.components.JetsnackScaffold | ||
| import com.example.jetsnack.ui.components.JetsnackSnackbar | ||
| import com.example.jetsnack.ui.components.rememberJetsnackScaffoldState | ||
| import com.example.jetsnack.ui.home.Feed | ||
| import com.example.jetsnack.ui.home.HomeSections | ||
| import com.example.jetsnack.ui.home.JetsnackBottomBar | ||
| import com.example.jetsnack.ui.home.addHomeGraph | ||
| import com.example.jetsnack.ui.home.composableWithCompositionLocal | ||
| import com.example.jetsnack.ui.navigation.MainDestinations | ||
| import com.example.jetsnack.ui.navigation.rememberJetsnackNavController | ||
| import com.example.jetsnack.ui.home.Profile | ||
| import com.example.jetsnack.ui.home.cart.Cart | ||
| import com.example.jetsnack.ui.home.search.Search | ||
| import com.example.jetsnack.ui.navigation.CartKey | ||
| import com.example.jetsnack.ui.navigation.FeedKey | ||
| import com.example.jetsnack.ui.navigation.ProfileKey | ||
| import com.example.jetsnack.ui.navigation.SearchKey | ||
| import com.example.jetsnack.ui.navigation.SnackDetailKey | ||
| import com.example.jetsnack.ui.navigation.addHomeSection | ||
| import com.example.jetsnack.ui.navigation.addSnackDetail | ||
| import com.example.jetsnack.ui.navigation.currentHomeSectionKey | ||
| import com.example.jetsnack.ui.snackdetail.SnackDetail | ||
| import com.example.jetsnack.ui.snackdetail.nonSpatialExpressiveSpring | ||
| import com.example.jetsnack.ui.snackdetail.spatialExpressiveSpring | ||
|
|
@@ -61,110 +67,101 @@ import com.example.jetsnack.ui.theme.JetsnackTheme | |
| @Composable | ||
| fun JetsnackApp() { | ||
| JetsnackTheme { | ||
| val jetsnackNavController = rememberJetsnackNavController() | ||
|
|
||
| val backStack = rememberNavBackStack(FeedKey) | ||
| val jetsnackScaffoldState = rememberJetsnackScaffoldState() | ||
|
|
||
| SharedTransitionLayout { | ||
| CompositionLocalProvider( | ||
| LocalSharedTransitionScope provides this, | ||
| ) { | ||
| NavHost( | ||
| navController = jetsnackNavController.navController, | ||
| startDestination = MainDestinations.HOME_ROUTE, | ||
| ) { | ||
| composableWithCompositionLocal( | ||
| route = MainDestinations.HOME_ROUTE, | ||
| ) { backStackEntry -> | ||
| MainContainer( | ||
| onSnackSelected = jetsnackNavController::navigateToSnackDetail, | ||
| ) | ||
| } | ||
| JetsnackScaffold( | ||
| bottomBar = { | ||
| val showBottomBar = backStack.last() !is SnackDetailKey | ||
|
|
||
| composableWithCompositionLocal( | ||
| "${MainDestinations.SNACK_DETAIL_ROUTE}/" + | ||
| "{${MainDestinations.SNACK_ID_KEY}}" + | ||
| "?origin={${MainDestinations.ORIGIN}}", | ||
| arguments = listOf( | ||
| navArgument(MainDestinations.SNACK_ID_KEY) { | ||
| type = NavType.LongType | ||
| AnimatedVisibility( | ||
| visible = showBottomBar, | ||
| enter = fadeIn(nonSpatialExpressiveSpring()) + slideInVertically( | ||
| spatialExpressiveSpring(), | ||
| ) { | ||
| it | ||
| }, | ||
| ), | ||
|
|
||
| ) { backStackEntry -> | ||
| val arguments = requireNotNull(backStackEntry.arguments) | ||
| val snackId = arguments.getLong(MainDestinations.SNACK_ID_KEY) | ||
| val origin = arguments.getString(MainDestinations.ORIGIN) | ||
| SnackDetail( | ||
| snackId, | ||
| origin = origin ?: "", | ||
| upPress = jetsnackNavController::upPress, | ||
| exit = fadeOut(nonSpatialExpressiveSpring()) + slideOutVertically( | ||
| spatialExpressiveSpring(), | ||
| ) { | ||
| it | ||
| }, | ||
| ) { | ||
| JetsnackBottomBar( | ||
| tabs = HomeSections.entries.toTypedArray(), | ||
| currentKey = backStack.currentHomeSectionKey(), | ||
| onItemClick = { navKey -> backStack.addHomeSection(navKey) }, | ||
| modifier = Modifier | ||
| .renderInSharedTransitionScopeOverlay( | ||
| zIndexInOverlay = 1f, | ||
| ), | ||
| ) | ||
| } | ||
| }, | ||
| snackbarHost = { | ||
| SnackbarHost( | ||
| hostState = it, | ||
| modifier = Modifier.systemBarsPadding(), | ||
| snackbar = { snackbarData -> JetsnackSnackbar(snackbarData) }, | ||
| ) | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| }, | ||
| snackBarHostState = jetsnackScaffoldState.snackBarHostState, | ||
| ) { padding -> | ||
|
|
||
| @Composable | ||
| fun MainContainer(modifier: Modifier = Modifier, onSnackSelected: (Long, String, NavBackStackEntry) -> Unit) { | ||
| val jetsnackScaffoldState = rememberJetsnackScaffoldState() | ||
| val nestedNavController = rememberJetsnackNavController() | ||
| val navBackStackEntry by nestedNavController.navController.currentBackStackEntryAsState() | ||
| val currentRoute = navBackStackEntry?.destination?.route | ||
| val sharedTransitionScope = LocalSharedTransitionScope.current | ||
| ?: throw IllegalStateException("No SharedElementScope found") | ||
| val animatedVisibilityScope = LocalNavAnimatedVisibilityScope.current | ||
| ?: throw IllegalStateException("No SharedElementScope found") | ||
| JetsnackScaffold( | ||
| bottomBar = { | ||
| with(animatedVisibilityScope) { | ||
| with(sharedTransitionScope) { | ||
| JetsnackBottomBar( | ||
| tabs = HomeSections.entries.toTypedArray(), | ||
| currentRoute = currentRoute ?: HomeSections.FEED.route, | ||
| navigateToRoute = nestedNavController::navigateToBottomBarRoute, | ||
| modifier = Modifier | ||
| .renderInSharedTransitionScopeOverlay( | ||
| zIndexInOverlay = 1f, | ||
| ) | ||
| .animateEnterExit( | ||
| enter = fadeIn(nonSpatialExpressiveSpring()) + slideInVertically( | ||
| spatialExpressiveSpring(), | ||
| ) { | ||
| it | ||
| }, | ||
| exit = fadeOut(nonSpatialExpressiveSpring()) + slideOutVertically( | ||
| spatialExpressiveSpring(), | ||
| ) { | ||
| it | ||
| }, | ||
| ), | ||
| val modifier = Modifier | ||
| .padding(padding) | ||
| .consumeWindowInsets(padding) | ||
|
|
||
| val transitionSpec = fadeIn(nonSpatialExpressiveSpring()) togetherWith | ||
| fadeOut(nonSpatialExpressiveSpring()) | ||
|
|
||
| NavDisplay( | ||
| backStack = backStack, | ||
| onBack = { backStack.removeLastOrNull() }, | ||
| sharedTransitionScope = this@SharedTransitionLayout, | ||
| entryProvider = entryProvider { | ||
| entry<FeedKey> { | ||
| Feed( | ||
| onSnackClick = backStack.addSnackDetail(), | ||
| modifier = modifier, | ||
| ) | ||
| } | ||
| entry<CartKey> { | ||
| Cart( | ||
| onSnackClick = backStack.addSnackDetail(), | ||
| modifier = modifier, | ||
| ) | ||
| } | ||
| entry<SearchKey> { | ||
| Search( | ||
| onSnackClick = backStack.addSnackDetail(), | ||
| modifier = modifier, | ||
| ) | ||
| } | ||
| entry<ProfileKey> { | ||
| Profile(modifier) | ||
| } | ||
| entry<SnackDetailKey> { key -> | ||
| SnackDetail( | ||
| key.snackId, | ||
| origin = key.origin, | ||
| upPress = { backStack.removeLastOrNull() }, | ||
| ) | ||
| } | ||
| }, | ||
| transitionSpec = { transitionSpec }, | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure why, but comparing between develop + this branch, the transition between the Feed and Cart composables looks a bit quicker, and doesn't seem like its performing a fadeIn or fadeOut. Doesn't look bad, I'm just wondering if there was a change missed here. |
||
| popTransitionSpec = { transitionSpec }, | ||
| predictivePopTransitionSpec = { transitionSpec }, | ||
| ) | ||
| } | ||
| } | ||
| }, | ||
| modifier = modifier, | ||
| snackbarHost = { | ||
| SnackbarHost( | ||
| hostState = it, | ||
| modifier = Modifier.systemBarsPadding(), | ||
| snackbar = { snackbarData -> JetsnackSnackbar(snackbarData) }, | ||
| ) | ||
| }, | ||
| snackBarHostState = jetsnackScaffoldState.snackBarHostState, | ||
| ) { padding -> | ||
| NavHost( | ||
| navController = nestedNavController.navController, | ||
| startDestination = HomeSections.FEED.route, | ||
| ) { | ||
| addHomeGraph( | ||
| onSnackSelected = onSnackSelected, | ||
| modifier = Modifier | ||
| .padding(padding) | ||
| .consumeWindowInsets(padding), | ||
| ) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| val LocalNavAnimatedVisibilityScope = compositionLocalOf<AnimatedVisibilityScope?> { null } | ||
| val LocalSharedTransitionScope = compositionLocalOf<SharedTransitionScope?> { null } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Within the Feed view, we should add a check on the TopAppBar to only slide in / slide out when its going towards snackdetail and coming back from snack detail, otherwise it now performs the animation between every screen transition, due to the new single instance of a navigation scope.