diff --git a/app/src/main/java/com/sadellie/unitto/UnittoApp.kt b/app/src/main/java/com/sadellie/unitto/UnittoApp.kt index 59fd6b80..62e9b7b2 100644 --- a/app/src/main/java/com/sadellie/unitto/UnittoApp.kt +++ b/app/src/main/java/com/sadellie/unitto/UnittoApp.kt @@ -21,6 +21,7 @@ package com.sadellie.unitto import androidx.compose.animation.core.tween import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Calculate +import androidx.compose.material.icons.filled.Settings import androidx.compose.material.icons.filled.SwapHoriz import androidx.compose.material3.DrawerValue import androidx.compose.material3.MaterialTheme @@ -35,6 +36,7 @@ import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle +import androidx.navigation.NavDestination.Companion.hierarchy import androidx.navigation.NavGraph.Companion.findStartDestination import androidx.navigation.compose.currentBackStackEntryAsState import androidx.navigation.compose.rememberNavController @@ -76,9 +78,29 @@ internal fun UnittoApp() { TopLevelDestinations.Calculator to Icons.Default.Calculate, TopLevelDestinations.Converter to Icons.Default.SwapHoriz ) + val additionalTabs = listOf( + TopLevelDestinations.Settings to Icons.Default.Settings + ) val navBackStackEntry by navController.currentBackStackEntryAsState() - val currentRoute by remember(navBackStackEntry?.destination) { - derivedStateOf { navBackStackEntry?.destination?.route } + val currentRoute: TopLevelDestinations? by remember(navBackStackEntry?.destination) { + derivedStateOf { + val hierarchyRoutes = navBackStackEntry?.destination?.hierarchy?.map { it.route } + ?: emptySequence() + + (mainTabs + additionalTabs) + .map { it.first } + .firstOrNull { + hierarchyRoutes.contains(it.route) + } + } + } + val gesturesEnabled: Boolean by remember(navBackStackEntry?.destination) { + derivedStateOf { + // Will be true for routes like + // [null, calculator_route, settings_graph, settings_route, themes_route] + // We disable drawer drag gesture when we are too deep + navController.backQueue.size <= 4 + } } Themmo( @@ -88,18 +110,19 @@ internal fun UnittoApp() { ) { val statusBarColor = when (currentRoute) { // Match text field container color - TopLevelDestinations.Calculator.route -> MaterialTheme.colorScheme.surfaceVariant + TopLevelDestinations.Calculator -> MaterialTheme.colorScheme.surfaceVariant else -> MaterialTheme.colorScheme.background } val navigationBarColor = MaterialTheme.colorScheme.background ModalNavigationDrawer( drawerState = drawerState, - gesturesEnabled = true, + gesturesEnabled = gesturesEnabled, drawerContent = { UnittoDrawerSheet( modifier = Modifier, mainTabs = mainTabs, + additionalTabs = additionalTabs, currentDestination = currentRoute ) { drawerScope.launch { drawerState.close() } diff --git a/app/src/main/java/com/sadellie/unitto/UnittoNavigation.kt b/app/src/main/java/com/sadellie/unitto/UnittoNavigation.kt index d0b7fa32..88f95407 100644 --- a/app/src/main/java/com/sadellie/unitto/UnittoNavigation.kt +++ b/app/src/main/java/com/sadellie/unitto/UnittoNavigation.kt @@ -19,6 +19,7 @@ package com.sadellie.unitto import androidx.compose.runtime.Composable +import androidx.navigation.NavGraph.Companion.findStartDestination import androidx.navigation.NavHostController import androidx.navigation.compose.NavHost import com.sadellie.unitto.feature.calculator.navigation.calculatorScreen @@ -50,10 +51,20 @@ internal fun UnittoNavigation( navController = navController, startDestination = startDestination ) { + fun navigateToSettings() { + navController.navigateToSettings { + popUpTo(navController.graph.findStartDestination().id) { + saveState = true + } + launchSingleTop = true + restoreState = true + } + } + converterScreen( navigateToLeftScreen = navController::navigateToLeftSide, navigateToRightScreen = navController::navigateToRightSide, - navigateToSettings = navController::navigateToSettings, + navigateToSettings = ::navigateToSettings, navigateToMenu = openDrawer, viewModel = converterViewModel ) @@ -75,12 +86,13 @@ internal fun UnittoNavigation( settingGraph( settingsViewModel = settingsViewModel, themmoController = themmoController, - navController = navController + navController = navController, + menuButtonClick = openDrawer ) calculatorScreen( navigateToMenu = openDrawer, - navigateToSettings = navController::navigateToSettings + navigateToSettings = ::navigateToSettings ) epochScreen(navigateToMenu = openDrawer) diff --git a/core/base/src/main/java/com/sadellie/unitto/core/base/TopLevelDestinations.kt b/core/base/src/main/java/com/sadellie/unitto/core/base/TopLevelDestinations.kt index fdd79d27..5d4bff94 100644 --- a/core/base/src/main/java/com/sadellie/unitto/core/base/TopLevelDestinations.kt +++ b/core/base/src/main/java/com/sadellie/unitto/core/base/TopLevelDestinations.kt @@ -38,6 +38,11 @@ sealed class TopLevelDestinations( route = "epoch_route", name = R.string.epoch_converter ) + + object Settings : TopLevelDestinations( + route = "settings_graph", + name = R.string.settings_screen + ) } val TOP_LEVEL_DESTINATIONS: Map by lazy { diff --git a/core/ui/src/main/java/com/sadellie/unitto/core/ui/common/UnittoDrawerSheet.kt b/core/ui/src/main/java/com/sadellie/unitto/core/ui/common/UnittoDrawerSheet.kt index 5e3d35c4..e791a3ee 100644 --- a/core/ui/src/main/java/com/sadellie/unitto/core/ui/common/UnittoDrawerSheet.kt +++ b/core/ui/src/main/java/com/sadellie/unitto/core/ui/common/UnittoDrawerSheet.kt @@ -21,12 +21,16 @@ package com.sadellie.unitto.core.ui.common import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Calculate +import androidx.compose.material3.Divider import androidx.compose.material3.ModalDrawerSheet import androidx.compose.material3.NavigationDrawerItemDefaults import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.sadellie.unitto.core.base.TopLevelDestinations @@ -34,7 +38,8 @@ import com.sadellie.unitto.core.base.TopLevelDestinations fun UnittoDrawerSheet( modifier: Modifier, mainTabs: List>, - currentDestination: String?, + additionalTabs: List>, + currentDestination: TopLevelDestinations?, onItemClick: (String) -> Unit ) { ModalDrawerSheet( @@ -49,9 +54,41 @@ fun UnittoDrawerSheet( modifier = Modifier.padding(NavigationDrawerItemDefaults.ItemPadding), destination = destination, icon = icon, - selected = destination.route == currentDestination, + selected = destination == currentDestination, + onClick = onItemClick + ) + } + + Divider(Modifier.padding(28.dp, 16.dp)) + + additionalTabs.forEach { (destination, icon) -> + UnittoDrawerItem( + modifier = Modifier.padding(NavigationDrawerItemDefaults.ItemPadding), + destination = destination, + icon = icon, + selected = destination == currentDestination, onClick = onItemClick ) } } } + +@Preview +@Composable +private fun PreviewUnittoDrawerSheet() { + UnittoDrawerSheet( + modifier = Modifier, + mainTabs = listOf( + TopLevelDestinations.Calculator to Icons.Default.Calculate, + TopLevelDestinations.Calculator to Icons.Default.Calculate, + TopLevelDestinations.Settings to Icons.Default.Calculate + ), + additionalTabs = listOf( + TopLevelDestinations.Calculator to Icons.Default.Calculate, + TopLevelDestinations.Calculator to Icons.Default.Calculate, + TopLevelDestinations.Calculator to Icons.Default.Calculate + ), + currentDestination = TopLevelDestinations.Settings, + onItemClick = {} + ) +} diff --git a/core/ui/src/main/java/com/sadellie/unitto/core/ui/common/UnittoLargeTopAppBar.kt b/core/ui/src/main/java/com/sadellie/unitto/core/ui/common/UnittoScreenWithLargeTopBar.kt similarity index 80% rename from core/ui/src/main/java/com/sadellie/unitto/core/ui/common/UnittoLargeTopAppBar.kt rename to core/ui/src/main/java/com/sadellie/unitto/core/ui/common/UnittoScreenWithLargeTopBar.kt index db5862d9..4cddcc93 100644 --- a/core/ui/src/main/java/com/sadellie/unitto/core/ui/common/UnittoLargeTopAppBar.kt +++ b/core/ui/src/main/java/com/sadellie/unitto/core/ui/common/UnittoScreenWithLargeTopBar.kt @@ -29,16 +29,16 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.input.nestedscroll.nestedScroll /** - * Commonly used LargeTopAppBar with scroll behavior. + * Template screen. Uses [Scaffold] and [LargeTopAppBar] * - * @param title Text that is displayed in top bar. - * @param navigateUpAction Action when user click arrow button at the top. - * @param content Content that can be scrolled. Don't forget to use padding values. + * @param title See [LargeTopAppBar] + * @param navigationIcon See [LargeTopAppBar] + * @param content See [Scaffold] */ @Composable -fun UnittoLargeTopAppBar( +fun UnittoScreenWithLargeTopBar( title: String, - navigateUpAction: () -> Unit, + navigationIcon: @Composable () -> Unit, content: @Composable (PaddingValues) -> Unit ) { val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior( @@ -52,9 +52,7 @@ fun UnittoLargeTopAppBar( title = { Text(text = title) }, - navigationIcon = { - NavigateUpButton { navigateUpAction() } - }, + navigationIcon = navigationIcon, scrollBehavior = scrollBehavior ) }, diff --git a/feature/settings/src/main/java/com/sadellie/unitto/feature/settings/AboutScreen.kt b/feature/settings/src/main/java/com/sadellie/unitto/feature/settings/AboutScreen.kt index af3905ee..95d49580 100644 --- a/feature/settings/src/main/java/com/sadellie/unitto/feature/settings/AboutScreen.kt +++ b/feature/settings/src/main/java/com/sadellie/unitto/feature/settings/AboutScreen.kt @@ -46,7 +46,8 @@ import androidx.compose.ui.res.stringResource import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.sadellie.unitto.core.base.BuildConfig import com.sadellie.unitto.core.ui.R -import com.sadellie.unitto.core.ui.common.UnittoLargeTopAppBar +import com.sadellie.unitto.core.ui.common.NavigateUpButton +import com.sadellie.unitto.core.ui.common.UnittoScreenWithLargeTopBar import com.sadellie.unitto.core.ui.openLink @Composable @@ -60,9 +61,9 @@ internal fun AboutScreen( var aboutItemClick: Int by rememberSaveable { mutableStateOf(0) } var showDialog: Boolean by rememberSaveable { mutableStateOf(false) } - UnittoLargeTopAppBar( + UnittoScreenWithLargeTopBar( title = stringResource(R.string.about_unitto), - navigateUpAction = navigateUpAction + navigationIcon = { NavigateUpButton(navigateUpAction) } ) { padding -> LazyColumn(contentPadding = padding) { // CURRENCY RATE NOTE diff --git a/feature/settings/src/main/java/com/sadellie/unitto/feature/settings/SettingsScreen.kt b/feature/settings/src/main/java/com/sadellie/unitto/feature/settings/SettingsScreen.kt index 0facf278..c843c389 100644 --- a/feature/settings/src/main/java/com/sadellie/unitto/feature/settings/SettingsScreen.kt +++ b/feature/settings/src/main/java/com/sadellie/unitto/feature/settings/SettingsScreen.kt @@ -49,7 +49,8 @@ import com.sadellie.unitto.core.base.SEPARATORS import com.sadellie.unitto.core.base.TOP_LEVEL_DESTINATIONS import com.sadellie.unitto.core.ui.R import com.sadellie.unitto.core.ui.common.Header -import com.sadellie.unitto.core.ui.common.UnittoLargeTopAppBar +import com.sadellie.unitto.core.ui.common.MenuButton +import com.sadellie.unitto.core.ui.common.UnittoScreenWithLargeTopBar import com.sadellie.unitto.core.ui.common.UnittoListItem import com.sadellie.unitto.core.ui.openLink import com.sadellie.unitto.feature.settings.components.AlertDialogWithList @@ -60,7 +61,7 @@ import com.sadellie.unitto.feature.settings.navigation.unitsGroupRoute @Composable internal fun SettingsScreen( viewModel: SettingsViewModel, - navigateUpAction: () -> Unit, + menuButtonClick: () -> Unit, navControllerAction: (String) -> Unit ) { val mContext = LocalContext.current @@ -69,9 +70,9 @@ internal fun SettingsScreen( mutableStateOf(DialogState.NONE) } - UnittoLargeTopAppBar( + UnittoScreenWithLargeTopBar( title = stringResource(R.string.settings_screen), - navigateUpAction = navigateUpAction + navigationIcon = { MenuButton(menuButtonClick) } ) { padding -> LazyColumn(contentPadding = padding) { diff --git a/feature/settings/src/main/java/com/sadellie/unitto/feature/settings/ThemesScreen.kt b/feature/settings/src/main/java/com/sadellie/unitto/feature/settings/ThemesScreen.kt index ba37c6c7..492c9a4a 100644 --- a/feature/settings/src/main/java/com/sadellie/unitto/feature/settings/ThemesScreen.kt +++ b/feature/settings/src/main/java/com/sadellie/unitto/feature/settings/ThemesScreen.kt @@ -31,9 +31,10 @@ import androidx.compose.material.icons.filled.Palette import androidx.compose.material3.Icon import androidx.compose.runtime.Composable import androidx.compose.ui.res.stringResource -import com.sadellie.unitto.core.ui.common.UnittoLargeTopAppBar +import com.sadellie.unitto.core.ui.common.UnittoScreenWithLargeTopBar import com.sadellie.unitto.core.ui.common.UnittoListItem import com.sadellie.unitto.core.ui.R +import com.sadellie.unitto.core.ui.common.NavigateUpButton import io.github.sadellie.themmo.ThemingMode import io.github.sadellie.themmo.ThemmoController @@ -43,9 +44,9 @@ internal fun ThemesScreen( themmoController: ThemmoController, viewModel: SettingsViewModel ) { - UnittoLargeTopAppBar( + UnittoScreenWithLargeTopBar( title = stringResource(R.string.theme_setting), - navigateUpAction = navigateUpAction + navigationIcon = { NavigateUpButton(navigateUpAction) } ) { paddingValues -> LazyColumn(contentPadding = paddingValues) { item { diff --git a/feature/settings/src/main/java/com/sadellie/unitto/feature/settings/ThirdPartyLicensesScreen.kt b/feature/settings/src/main/java/com/sadellie/unitto/feature/settings/ThirdPartyLicensesScreen.kt index 5dd1cfb8..ec5abdab 100644 --- a/feature/settings/src/main/java/com/sadellie/unitto/feature/settings/ThirdPartyLicensesScreen.kt +++ b/feature/settings/src/main/java/com/sadellie/unitto/feature/settings/ThirdPartyLicensesScreen.kt @@ -37,7 +37,8 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.sadellie.unitto.core.ui.R -import com.sadellie.unitto.core.ui.common.UnittoLargeTopAppBar +import com.sadellie.unitto.core.ui.common.NavigateUpButton +import com.sadellie.unitto.core.ui.common.UnittoScreenWithLargeTopBar import com.sadellie.unitto.core.ui.openLink import com.sadellie.unitto.data.licenses.ALL_LIBRARIES @@ -53,9 +54,9 @@ internal fun ThirdPartyLicensesScreen( ) { val mContext = LocalContext.current - UnittoLargeTopAppBar( + UnittoScreenWithLargeTopBar( title = stringResource(R.string.third_party_licenses), - navigateUpAction = navigateUpAction + navigationIcon = { NavigateUpButton(navigateUpAction) } ) { padding -> LazyColumn( verticalArrangement = Arrangement.spacedBy(16.dp), diff --git a/feature/settings/src/main/java/com/sadellie/unitto/feature/settings/UnitGroupsScreen.kt b/feature/settings/src/main/java/com/sadellie/unitto/feature/settings/UnitGroupsScreen.kt index b13c6767..f2834887 100644 --- a/feature/settings/src/main/java/com/sadellie/unitto/feature/settings/UnitGroupsScreen.kt +++ b/feature/settings/src/main/java/com/sadellie/unitto/feature/settings/UnitGroupsScreen.kt @@ -48,8 +48,9 @@ import androidx.compose.ui.draw.clip import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.sadellie.unitto.core.ui.common.Header -import com.sadellie.unitto.core.ui.common.UnittoLargeTopAppBar +import com.sadellie.unitto.core.ui.common.UnittoScreenWithLargeTopBar import com.sadellie.unitto.core.ui.R +import com.sadellie.unitto.core.ui.common.NavigateUpButton import org.burnoutcrew.reorderable.ReorderableItem import org.burnoutcrew.reorderable.detectReorder import org.burnoutcrew.reorderable.detectReorderAfterLongPress @@ -61,9 +62,9 @@ internal fun UnitGroupsScreen( viewModel: SettingsViewModel, navigateUpAction: () -> Unit ) { - UnittoLargeTopAppBar( + UnittoScreenWithLargeTopBar( title = stringResource(R.string.unit_groups_setting), - navigateUpAction = navigateUpAction + navigationIcon = { NavigateUpButton(navigateUpAction) } ) { paddingValues -> val shownUnits = viewModel.shownUnitGroups.collectAsState() diff --git a/feature/settings/src/main/java/com/sadellie/unitto/feature/settings/navigation/SettingsNavigation.kt b/feature/settings/src/main/java/com/sadellie/unitto/feature/settings/navigation/SettingsNavigation.kt index 6289314a..9949afb9 100644 --- a/feature/settings/src/main/java/com/sadellie/unitto/feature/settings/navigation/SettingsNavigation.kt +++ b/feature/settings/src/main/java/com/sadellie/unitto/feature/settings/navigation/SettingsNavigation.kt @@ -21,8 +21,10 @@ package com.sadellie.unitto.feature.settings.navigation import androidx.navigation.NavController import androidx.navigation.NavGraphBuilder import androidx.navigation.NavHostController +import androidx.navigation.NavOptionsBuilder import androidx.navigation.compose.composable import androidx.navigation.compose.navigation +import com.sadellie.unitto.core.base.TopLevelDestinations import com.sadellie.unitto.feature.settings.AboutScreen import com.sadellie.unitto.feature.settings.SettingsScreen import com.sadellie.unitto.feature.settings.SettingsViewModel @@ -31,15 +33,15 @@ import com.sadellie.unitto.feature.settings.ThirdPartyLicensesScreen import com.sadellie.unitto.feature.settings.UnitGroupsScreen import io.github.sadellie.themmo.ThemmoController -const val settingsGraph = "settings_graph" +private val settingsGraph: String by lazy { TopLevelDestinations.Settings.route } private const val settingsRoute = "settings_route" internal const val themesRoute = "themes_route" internal const val unitsGroupRoute = "units_group_route" internal const val thirdPartyRoute = "third_party_route" internal const val aboutRoute = "about_route" -fun NavController.navigateToSettings() { - navigate(settingsRoute) +fun NavController.navigateToSettings(builder: NavOptionsBuilder.() -> Unit) { + navigate(settingsRoute, builder) } fun NavController.navigateToUnitGroups() { @@ -49,13 +51,14 @@ fun NavController.navigateToUnitGroups() { fun NavGraphBuilder.settingGraph( settingsViewModel: SettingsViewModel, themmoController: ThemmoController, - navController: NavHostController + navController: NavHostController, + menuButtonClick: () -> Unit ) { navigation(settingsRoute, settingsGraph) { composable(settingsRoute) { SettingsScreen( viewModel = settingsViewModel, - navigateUpAction = { navController.navigateUp() } + menuButtonClick = menuButtonClick ) { route -> navController.navigate(route) } }