Navigation.kt (5721B) - raw
1 package me.rhunk.snapenhance.ui.manager 2 3 import androidx.compose.animation.core.animateFloatAsState 4 import androidx.compose.animation.core.tween 5 import androidx.compose.animation.fadeIn 6 import androidx.compose.animation.fadeOut 7 import androidx.compose.foundation.layout.* 8 import androidx.compose.material.icons.Icons 9 import androidx.compose.material.icons.automirrored.filled.ArrowBack 10 import androidx.compose.material3.* 11 import androidx.compose.runtime.Composable 12 import androidx.compose.runtime.getValue 13 import androidx.compose.runtime.remember 14 import androidx.compose.ui.Modifier 15 import androidx.compose.ui.graphics.graphicsLayer 16 import androidx.compose.ui.text.style.TextAlign 17 import androidx.compose.ui.unit.dp 18 import androidx.compose.ui.unit.lerp 19 import androidx.compose.ui.unit.sp 20 import androidx.navigation.NavHostController 21 import androidx.navigation.compose.NavHost 22 import androidx.navigation.compose.composable 23 import androidx.navigation.compose.currentBackStackEntryAsState 24 import androidx.navigation.navigation 25 import me.rhunk.snapenhance.RemoteSideContext 26 27 @OptIn(ExperimentalMaterial3Api::class) 28 class Navigation( 29 private val context: RemoteSideContext, 30 private val navController: NavHostController, 31 val routes: Routes = Routes(context).also { 32 it.navController = navController 33 } 34 ){ 35 @Composable 36 fun TopBar() { 37 val navBackStackEntry by navController.currentBackStackEntryAsState() 38 val currentRoute = remember(navBackStackEntry) { routes.getCurrentRoute(navBackStackEntry) } 39 40 val canGoBack = remember(navBackStackEntry) { currentRoute?.let { 41 !it.routeInfo.primary || it.routeInfo.childIds.contains(routes.currentDestination) 42 } == true } 43 44 TopAppBar(title = { 45 currentRoute?.apply { 46 title?.invoke() ?: routeInfo.translatedKey?.value?.let { 47 Text(text = it) 48 } 49 } 50 }, navigationIcon = { 51 val backButtonAnimation by animateFloatAsState(if (canGoBack) 1f else 0f, 52 label = "backButtonAnimation" 53 ) 54 55 Box( 56 modifier = Modifier 57 .graphicsLayer { alpha = backButtonAnimation } 58 .width(lerp(0.dp, 48.dp, backButtonAnimation)) 59 .height(48.dp) 60 ) { 61 IconButton( 62 onClick = { 63 if (canGoBack) { 64 navController.popBackStack() 65 } 66 } 67 ) { 68 Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = null) 69 } 70 } 71 }, actions = { 72 currentRoute?.topBarActions?.invoke(this) 73 }) 74 } 75 76 @Composable 77 fun BottomBar() { 78 val navBackStackEntry by navController.currentBackStackEntryAsState() 79 val currentRoute = remember(navBackStackEntry) { routes.getCurrentRoute(navBackStackEntry) } 80 val primaryRoutes = remember { routes.getRoutes().filter { it.routeInfo.showInNavBar } } 81 82 NavigationBar { 83 primaryRoutes.forEach { route -> 84 NavigationBarItem( 85 alwaysShowLabel = true, 86 icon = { 87 Icon(imageVector = route.routeInfo.icon, contentDescription = null) 88 }, 89 label = { 90 Text( 91 textAlign = TextAlign.Center, 92 softWrap = false, 93 fontSize = 12.sp, 94 modifier = Modifier.wrapContentWidth(unbounded = true), 95 text = remember(context.translation.loadedLocale) { context.translation["manager.routes.${route.routeInfo.key.substringBefore("/")}"] }, 96 ) 97 }, 98 selected = currentRoute == route, 99 onClick = { 100 route.navigateReset() 101 } 102 ) 103 } 104 } 105 } 106 107 @Composable 108 fun FloatingActionButton() { 109 val navBackStackEntry by navController.currentBackStackEntryAsState() 110 remember(navBackStackEntry) { routes.getCurrentRoute(navBackStackEntry) }?.floatingActionButton?.invoke() 111 } 112 113 @Composable 114 fun Content(paddingValues: PaddingValues, startDestination: String) { 115 NavHost( 116 navController = navController, 117 startDestination = startDestination, 118 Modifier.padding(paddingValues), 119 enterTransition = { fadeIn(tween(100)) }, 120 exitTransition = { fadeOut(tween(100)) } 121 ) { 122 routes.getRoutes().filter { it.parentRoute == null }.forEach { route -> 123 val children = routes.getRoutes().filter { it.parentRoute == route } 124 if (children.isEmpty()) { 125 composable(route.routeInfo.id) { 126 route.content.invoke(it) 127 } 128 route.customComposables.invoke(this) 129 } else { 130 navigation("main_" + route.routeInfo.id, route.routeInfo.id) { 131 composable("main_" + route.routeInfo.id) { 132 route.content.invoke(it) 133 } 134 children.forEach { child -> 135 composable(child.routeInfo.id) { 136 child.content.invoke(it) 137 } 138 } 139 route.customComposables.invoke(this) 140 } 141 } 142 } 143 } 144 } 145 }