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 }