33package com.smarttoolfactory.tutorial3_1navigation
44
55import android.annotation.SuppressLint
6+ import android.os.Bundle
7+ import androidx.activity.compose.BackHandler
8+ import androidx.collection.forEach
69import androidx.compose.animation.AnimatedContentTransitionScope.SlideDirection
710import androidx.compose.animation.core.tween
811import androidx.compose.foundation.background
12+ import androidx.compose.foundation.border
913import androidx.compose.foundation.layout.Arrangement
1014import androidx.compose.foundation.layout.Column
1115import androidx.compose.foundation.layout.PaddingValues
@@ -15,6 +19,8 @@ import androidx.compose.foundation.layout.height
1519import androidx.compose.foundation.layout.padding
1620import androidx.compose.foundation.lazy.LazyColumn
1721import androidx.compose.foundation.lazy.items
22+ import androidx.compose.foundation.pager.HorizontalPager
23+ import androidx.compose.foundation.pager.rememberPagerState
1824import androidx.compose.foundation.shape.RoundedCornerShape
1925import androidx.compose.material3.Button
2026import androidx.compose.material3.ExperimentalMaterial3Api
@@ -27,9 +33,12 @@ import androidx.compose.material3.Text
2733import androidx.compose.material3.TopAppBar
2834import androidx.compose.material3.TopAppBarDefaults
2935import androidx.compose.runtime.Composable
36+ import androidx.compose.runtime.LaunchedEffect
3037import androidx.compose.runtime.collectAsState
3138import androidx.compose.runtime.getValue
3239import androidx.compose.runtime.mutableIntStateOf
40+ import androidx.compose.runtime.mutableStateListOf
41+ import androidx.compose.runtime.mutableStateOf
3342import androidx.compose.runtime.remember
3443import androidx.compose.runtime.saveable.rememberSaveable
3544import androidx.compose.runtime.setValue
@@ -46,15 +55,19 @@ import androidx.navigation.NavController
4655import androidx.navigation.NavDestination
4756import androidx.navigation.NavDestination.Companion.hasRoute
4857import androidx.navigation.NavDestination.Companion.hierarchy
58+ import androidx.navigation.NavGraph
59+ import androidx.navigation.NavGraph.Companion.findStartDestination
4960import androidx.navigation.NavGraphBuilder
5061import androidx.navigation.NavHostController
5162import androidx.navigation.compose.NavHost
5263import androidx.navigation.compose.composable
5364import androidx.navigation.compose.currentBackStackEntryAsState
5465import androidx.navigation.compose.navigation
5566import androidx.navigation.compose.rememberNavController
67+ import androidx.navigation.get
5668import androidx.navigation.toRoute
5769
70+ @SuppressLint(" RestrictedApi" )
5871@Preview
5972@Composable
6073fun Tutorial3_2Screen () {
@@ -106,12 +119,13 @@ fun Tutorial3_2Screen() {
106119 }
107120}
108121
122+ @SuppressLint(" RestrictedApi" )
109123@Composable
110124private fun MainContainer (
111- onScreenClick : (
125+ onGoToProfileScreen : (
112126 route: Any ,
113127 navBackStackEntry: NavBackStackEntry ,
114- ) -> Unit ,
128+ ) -> Unit
115129) {
116130 val items = remember {
117131 bottomRouteDataList()
@@ -141,11 +155,9 @@ private fun MainContainer(
141155 tonalElevation = 4 .dp
142156 ) {
143157 items.forEach { item: BottomRouteData ->
144-
145158 // Checks destination's route with type safety
146159 val selected =
147160 currentDestination?.hierarchy?.any { it.hasRoute(item.route::class ) } == true
148-
149161 NavigationBarItem (
150162 selected = selected,
151163 icon = {
@@ -155,38 +167,19 @@ private fun MainContainer(
155167 )
156168 },
157169 onClick = {
158- // Returns current destinations by parent-child relationship
159- currentDestination?.hierarchy?.forEach { destination: NavDestination ->
160- println (" HIERARCHY: destination: $destination " )
161- }
170+ nestedNavController.navigate(route = item.route) {
171+ launchSingleTop = true
162172
163- // This is for not opening same screen if current destination
164- // is equal to target destination
165- if (selected.not ()) {
166-
167- nestedNavController.navigate(route = item.route) {
168- launchSingleTop = true
169-
170- // 🔥 If restoreState = true and saveState = true are commented
171- // routes other than Home1 are not saved
172- restoreState = true
173-
174- // Pop up backstack to the first destination and save state.
175- // This makes going back
176- // to the start destination when pressing back in any other bottom tab.
177- popUpTo(findStartDestination(nestedNavController.graph).id) {
178- saveState = true
179- }
180-
181- val startDestinationRoute =
182- nestedNavController.graph.startDestinationRoute
183- val startDestinationRecursive =
184- findStartDestination(nestedNavController.graph).route
185- println (
186- " 🔥 startDestinationRoute: $startDestinationRoute , " +
187- " startDestinationRecursive: $startDestinationRecursive \n " +
188- " navigating target route: ${item.route} "
189- )
173+ // 🔥 If restoreState = true and saveState = true are commented
174+ // routes other than Home1 are not saved
175+ restoreState = true
176+
177+
178+ // Pop up backstack to the first destination and save state.
179+ // This makes going back
180+ // to the start destination when pressing back in any other bottom tab.
181+ popUpTo(findStartDestination(nestedNavController.graph).id) {
182+ saveState = true
190183 }
191184 }
192185 }
@@ -198,28 +191,39 @@ private fun MainContainer(
198191 NavHost (
199192 modifier = Modifier .padding(paddingValues),
200193 navController = nestedNavController,
201- startDestination = BottomNavigationRoute .HomeRoute
194+ startDestination = BottomNavigationRoute .HomeGraph
202195 ) {
203- addBottomNavigationGraph(nestedNavController) { route, navBackStackEntry ->
204- onScreenClick(route, navBackStackEntry)
205- }
196+ addBottomNavigationGraph(
197+ nestedNavController = nestedNavController,
198+ onGoToProfileScreen = { route, navBackStackEntry ->
199+ onGoToProfileScreen(route, navBackStackEntry)
200+ },
201+ onBottomScreenClick = { route, navBackStackEntry ->
202+ nestedNavController.navigate(route)
203+ }
204+ )
206205 }
207206 }
208207}
209208
209+ /* *
210+ * @param onGoToProfileScreen lambda for navigating Profile screen from current screen with top NavHostController
211+ * @param onBottomScreenClick lambda for navigating with [nestedNavController] in BottomNavigation
212+ */
210213private fun NavGraphBuilder.addBottomNavigationGraph (
211214 nestedNavController : NavHostController ,
212- onScreenClick : (route: Any , navBackStackEntry: NavBackStackEntry ) -> Unit ,
215+ onGoToProfileScreen : (route: Any , navBackStackEntry: NavBackStackEntry ) -> Unit ,
216+ onBottomScreenClick : (route: Any , navBackStackEntry: NavBackStackEntry ) -> Unit ,
213217) {
214- navigation<BottomNavigationRoute .HomeRoute >(
218+ navigation<BottomNavigationRoute .HomeGraph >(
215219 startDestination = BottomNavigationRoute .HomeRoute1
216220 ) {
217221 composable<BottomNavigationRoute .HomeRoute1 > { from: NavBackStackEntry ->
218222 Screen (
219223 text = " Home Screen1" ,
220224 navController = nestedNavController,
221225 onClick = {
222- nestedNavController.navigate (BottomNavigationRoute .HomeRoute2 )
226+ onBottomScreenClick (BottomNavigationRoute .HomeRoute2 , from )
223227 }
224228 )
225229 }
@@ -229,7 +233,7 @@ private fun NavGraphBuilder.addBottomNavigationGraph(
229233 text = " Home Screen2" ,
230234 navController = nestedNavController,
231235 onClick = {
232- nestedNavController.navigate (BottomNavigationRoute .HomeRoute3 )
236+ onBottomScreenClick (BottomNavigationRoute .HomeRoute3 , from )
233237 }
234238 )
235239 }
@@ -242,15 +246,15 @@ private fun NavGraphBuilder.addBottomNavigationGraph(
242246 }
243247 }
244248
245- navigation<BottomNavigationRoute .SettingsRoute >(
249+ navigation<BottomNavigationRoute .SettingsGraph >(
246250 startDestination = BottomNavigationRoute .SettingsRoute1
247251 ) {
248252 composable<BottomNavigationRoute .SettingsRoute1 > { from: NavBackStackEntry ->
249253 Screen (
250254 text = " Settings Screen" ,
251255 navController = nestedNavController,
252256 onClick = {
253- nestedNavController.navigate (BottomNavigationRoute .SettingsRoute2 )
257+ onBottomScreenClick (BottomNavigationRoute .SettingsRoute2 , from )
254258 }
255259 )
256260 }
@@ -260,7 +264,7 @@ private fun NavGraphBuilder.addBottomNavigationGraph(
260264 text = " Settings Screen2" ,
261265 navController = nestedNavController,
262266 onClick = {
263- nestedNavController.navigate (BottomNavigationRoute .SettingsRoute3 )
267+ onBottomScreenClick (BottomNavigationRoute .SettingsRoute3 , from )
264268 }
265269 )
266270 }
@@ -278,7 +282,7 @@ private fun NavGraphBuilder.addBottomNavigationGraph(
278282 text = " Favorites Screen" ,
279283 navController = nestedNavController,
280284 onClick = {
281- onScreenClick (
285+ onGoToProfileScreen (
282286 Profile (" Favorites" ),
283287 from
284288 )
@@ -291,7 +295,7 @@ private fun NavGraphBuilder.addBottomNavigationGraph(
291295 text = " Notifications Screen" ,
292296 navController = nestedNavController,
293297 onClick = {
294- onScreenClick (
298+ onGoToProfileScreen (
295299 Profile (" Notifications" ),
296300 from
297301 )
@@ -302,7 +306,7 @@ private fun NavGraphBuilder.addBottomNavigationGraph(
302306
303307@SuppressLint(" RestrictedApi" )
304308@Composable
305- private fun Screen (
309+ fun Screen (
306310 text : String ,
307311 navController : NavController ,
308312 onClick : (() -> Unit )? = null,
@@ -349,28 +353,83 @@ private fun Screen(
349353
350354 val currentBackStack: List <NavBackStackEntry > by navController.currentBackStack.collectAsState()
351355
352- LazyColumn (
353- modifier = Modifier .fillMaxSize(),
354- verticalArrangement = Arrangement .spacedBy( 8 .dp)
355- ) {
356+ val pagerState = rememberPagerState {
357+ 2
358+ }
359+ HorizontalPager (state = pagerState ) { page ->
356360
357- // Don't do looped operations in actual code, it's for demonstration
358- items(items = currentBackStack.reversed()) {
361+ val headerText = if (page == 0 ) " Current Back stack(reversed) " else " Current hierarchy "
362+ Column {
359363 Text (
360- text = it.destination.route
361- ?.replace(" $packageName ." , " " )
362- ?.replace(
363- " BottomNavigationRoute." ,
364- " "
365- ) ? : it.destination.displayName,
366364 modifier = Modifier
367- .shadow(4 .dp, RoundedCornerShape (8 .dp))
368- .background(Color .White )
369365 .fillMaxWidth()
370- .padding(16 .dp),
371- fontSize = 18 .sp
366+ .padding(bottom = 8 .dp),
367+ text = headerText,
368+ fontSize = 20 .sp,
369+ fontWeight = FontWeight .Bold
372370 )
371+
372+ val destinations = if (page == 0 ) {
373+ currentBackStack.reversed().map { it.destination }
374+ } else {
375+ navController.currentDestination?.hierarchy?.toList() ? : listOf ()
376+ }
377+
378+ LazyColumn (
379+ modifier = Modifier .fillMaxSize(),
380+ verticalArrangement = Arrangement .spacedBy(8 .dp)
381+ ) {
382+
383+ items(items = destinations) { destination: NavDestination ->
384+
385+ if (destination is NavGraph ) {
386+ MainText (destination, packageName)
387+ destination.nodes.forEach { _, value ->
388+ SubItemText (value, packageName)
389+ }
390+
391+ } else {
392+ MainText (destination, packageName)
393+ }
394+ }
395+ }
396+
373397 }
374398 }
375399 }
376400}
401+
402+ @SuppressLint(" RestrictedApi" )
403+ @Composable
404+ private fun SubItemText (value : NavDestination , packageName : String? ) {
405+ Text (
406+ text = value.route
407+ ?.replace(" $packageName ." , " " )
408+ ?.replace(" BottomNavigationRoute." , " " )
409+ ? : value.displayName,
410+ modifier = Modifier
411+ .padding(start = 8 .dp, bottom = 2 .dp)
412+ .shadow(2 .dp, RoundedCornerShape (8 .dp))
413+ .background(Color .White )
414+ .fillMaxWidth()
415+ .padding(8 .dp),
416+ fontSize = 12 .sp
417+ )
418+ }
419+
420+ @SuppressLint(" RestrictedApi" )
421+ @Composable
422+ private fun MainText (destination : NavDestination , packageName : String? ) {
423+ Text (
424+ text = destination.route
425+ ?.replace(" $packageName ." , " " )
426+ ?.replace(" BottomNavigationRoute." , " " )
427+ ? : destination.displayName,
428+ modifier = Modifier
429+ .shadow(4 .dp, RoundedCornerShape (8 .dp))
430+ .background(Color .White )
431+ .fillMaxWidth()
432+ .padding(16 .dp),
433+ fontSize = 18 .sp
434+ )
435+ }
0 commit comments