This commit was merged in pull request #51.
This commit is contained in:
@@ -13,18 +13,23 @@ import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.navigation.NavHostController
|
||||
import androidx.navigation.compose.NavHost
|
||||
import androidx.navigation.compose.composable
|
||||
import androidx.navigation.compose.currentBackStackEntryAsState
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import kotlinx.coroutines.launch
|
||||
import org.jetbrains.compose.ui.tooling.preview.Preview
|
||||
import org.shahondin1624.lib.components.TestTags
|
||||
import org.shahondin1624.lib.components.UiConstants
|
||||
import org.shahondin1624.lib.components.charactermodel.attributespage.AttributesPage
|
||||
import org.shahondin1624.lib.components.settings.SettingsPage
|
||||
import org.shahondin1624.model.EXAMPLE_CHARACTER
|
||||
import org.shahondin1624.navigation.AppRoutes
|
||||
import org.shahondin1624.theme.AppTheme
|
||||
import org.shahondin1624.theme.LocalThemeIsDark
|
||||
import org.shahondin1624.theme.LocalWindowSizeClass
|
||||
@@ -40,20 +45,44 @@ fun App(
|
||||
val windowSizeClass = WindowSizeClass.fromWidth(maxWidth)
|
||||
|
||||
CompositionLocalProvider(LocalWindowSizeClass provides windowSizeClass) {
|
||||
AppContent()
|
||||
val navController = rememberNavController()
|
||||
AppContent(navController)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun AppContent() {
|
||||
private fun AppContent(navController: NavHostController) {
|
||||
val windowSizeClass = LocalWindowSizeClass.current
|
||||
|
||||
when (windowSizeClass) {
|
||||
WindowSizeClass.Compact -> CompactNavigation()
|
||||
WindowSizeClass.Medium -> MediumNavigation()
|
||||
WindowSizeClass.Expanded -> ExpandedNavigation()
|
||||
WindowSizeClass.Compact -> CompactNavigation(navController)
|
||||
WindowSizeClass.Medium -> MediumNavigation(navController)
|
||||
WindowSizeClass.Expanded -> ExpandedNavigation(navController)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current route from the NavController's back stack.
|
||||
*/
|
||||
@Composable
|
||||
private fun currentRoute(navController: NavHostController): String? {
|
||||
val navBackStackEntry by navController.currentBackStackEntryAsState()
|
||||
return navBackStackEntry?.destination?.route
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigate to a route, popping back to the start destination to avoid
|
||||
* building up a deep back stack.
|
||||
*/
|
||||
private fun navigateTo(navController: NavHostController, route: String) {
|
||||
navController.navigate(route) {
|
||||
popUpTo(AppRoutes.CHARACTER_SHEET) {
|
||||
saveState = true
|
||||
}
|
||||
launchSingleTop = true
|
||||
restoreState = true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,19 +91,27 @@ private fun AppContent() {
|
||||
*/
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun CompactNavigation() {
|
||||
private fun CompactNavigation(navController: NavHostController) {
|
||||
val drawerState = rememberDrawerState(initialValue = DrawerValue.Closed)
|
||||
val scope = rememberCoroutineScope()
|
||||
val currentRoute = currentRoute(navController)
|
||||
|
||||
ModalNavigationDrawer(
|
||||
drawerState = drawerState,
|
||||
drawerContent = {
|
||||
ModalDrawerSheet {
|
||||
DrawerContent()
|
||||
DrawerContent(
|
||||
currentRoute = currentRoute,
|
||||
onNavigate = { route ->
|
||||
navigateTo(navController, route)
|
||||
scope.launch { drawerState.close() }
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
) {
|
||||
MainScaffold(
|
||||
navController = navController,
|
||||
showMenuButton = true,
|
||||
onMenuClick = {
|
||||
scope.launch {
|
||||
@@ -90,7 +127,9 @@ private fun CompactNavigation() {
|
||||
*/
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun MediumNavigation() {
|
||||
private fun MediumNavigation(navController: NavHostController) {
|
||||
val currentRoute = currentRoute(navController)
|
||||
|
||||
Row(modifier = Modifier.fillMaxSize()) {
|
||||
NavigationRail(
|
||||
modifier = Modifier.testTag(TestTags.NAV_RAIL)
|
||||
@@ -99,19 +138,20 @@ private fun MediumNavigation() {
|
||||
NavigationRailItem(
|
||||
icon = { Icon(Icons.Default.Person, contentDescription = null) },
|
||||
label = { Text("Character") },
|
||||
selected = true,
|
||||
onClick = { /* TODO: Navigate */ },
|
||||
selected = currentRoute == AppRoutes.CHARACTER_SHEET,
|
||||
onClick = { navigateTo(navController, AppRoutes.CHARACTER_SHEET) },
|
||||
modifier = Modifier.testTag(TestTags.NAV_CHARACTER_SHEET)
|
||||
)
|
||||
NavigationRailItem(
|
||||
icon = { Icon(Icons.Default.Settings, contentDescription = null) },
|
||||
label = { Text("Settings") },
|
||||
selected = false,
|
||||
onClick = { /* TODO: Navigate to settings */ },
|
||||
selected = currentRoute == AppRoutes.SETTINGS,
|
||||
onClick = { navigateTo(navController, AppRoutes.SETTINGS) },
|
||||
modifier = Modifier.testTag(TestTags.NAV_SETTINGS)
|
||||
)
|
||||
}
|
||||
MainScaffold(
|
||||
navController = navController,
|
||||
showMenuButton = false,
|
||||
onMenuClick = {},
|
||||
modifier = Modifier.weight(1f)
|
||||
@@ -124,15 +164,21 @@ private fun MediumNavigation() {
|
||||
*/
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun ExpandedNavigation() {
|
||||
private fun ExpandedNavigation(navController: NavHostController) {
|
||||
val currentRoute = currentRoute(navController)
|
||||
|
||||
PermanentNavigationDrawer(
|
||||
drawerContent = {
|
||||
PermanentDrawerSheet(modifier = Modifier.testTag(TestTags.NAV_PERMANENT_DRAWER)) {
|
||||
DrawerContent()
|
||||
DrawerContent(
|
||||
currentRoute = currentRoute,
|
||||
onNavigate = { route -> navigateTo(navController, route) }
|
||||
)
|
||||
}
|
||||
}
|
||||
) {
|
||||
MainScaffold(
|
||||
navController = navController,
|
||||
showMenuButton = false,
|
||||
onMenuClick = {}
|
||||
)
|
||||
@@ -143,7 +189,10 @@ private fun ExpandedNavigation() {
|
||||
* Shared drawer content used by Compact (ModalDrawerSheet) and Expanded (PermanentDrawerSheet).
|
||||
*/
|
||||
@Composable
|
||||
private fun DrawerContent() {
|
||||
private fun DrawerContent(
|
||||
currentRoute: String?,
|
||||
onNavigate: (String) -> Unit
|
||||
) {
|
||||
Spacer(Modifier.height(16.dp))
|
||||
Text(
|
||||
"Menu",
|
||||
@@ -156,8 +205,8 @@ private fun DrawerContent() {
|
||||
NavigationDrawerItem(
|
||||
icon = { Icon(Icons.Default.Person, contentDescription = null) },
|
||||
label = { Text("Character Sheet") },
|
||||
selected = true,
|
||||
onClick = { /* TODO: Navigate */ },
|
||||
selected = currentRoute == AppRoutes.CHARACTER_SHEET,
|
||||
onClick = { onNavigate(AppRoutes.CHARACTER_SHEET) },
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 12.dp, vertical = 4.dp)
|
||||
.testTag(TestTags.NAV_CHARACTER_SHEET)
|
||||
@@ -166,8 +215,8 @@ private fun DrawerContent() {
|
||||
NavigationDrawerItem(
|
||||
icon = { Icon(Icons.Default.Settings, contentDescription = null) },
|
||||
label = { Text("Settings") },
|
||||
selected = false,
|
||||
onClick = { /* TODO: Navigate to settings */ },
|
||||
selected = currentRoute == AppRoutes.SETTINGS,
|
||||
onClick = { onNavigate(AppRoutes.SETTINGS) },
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 12.dp, vertical = 4.dp)
|
||||
.testTag(TestTags.NAV_SETTINGS)
|
||||
@@ -175,8 +224,9 @@ private fun DrawerContent() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Main scaffold with top app bar and content area.
|
||||
* Main scaffold with top app bar and routed content area.
|
||||
*
|
||||
* @param navController the NavHostController for page routing
|
||||
* @param showMenuButton whether to show the hamburger menu icon (hidden when nav is persistent)
|
||||
* @param onMenuClick callback for hamburger menu button click
|
||||
* @param modifier optional modifier (e.g., weight in Row for Medium layout)
|
||||
@@ -184,11 +234,11 @@ private fun DrawerContent() {
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun MainScaffold(
|
||||
navController: NavHostController,
|
||||
showMenuButton: Boolean,
|
||||
onMenuClick: () -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
val character = EXAMPLE_CHARACTER
|
||||
var isDark by LocalThemeIsDark.current
|
||||
|
||||
Scaffold(
|
||||
@@ -235,14 +285,25 @@ private fun MainScaffold(
|
||||
.windowInsetsPadding(WindowInsets.safeDrawing),
|
||||
contentAlignment = Alignment.TopCenter
|
||||
) {
|
||||
Column(
|
||||
NavHost(
|
||||
navController = navController,
|
||||
startDestination = AppRoutes.CHARACTER_SHEET,
|
||||
modifier = Modifier
|
||||
.widthIn(max = UiConstants.MAX_CONTENT_WIDTH)
|
||||
.fillMaxWidth()
|
||||
.padding(contentPadding),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
.padding(contentPadding)
|
||||
) {
|
||||
AttributesPage(character)
|
||||
composable(AppRoutes.CHARACTER_SHEET) {
|
||||
Column(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
AttributesPage(EXAMPLE_CHARACTER)
|
||||
}
|
||||
}
|
||||
composable(AppRoutes.SETTINGS) {
|
||||
SettingsPage()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,2 +1,32 @@
|
||||
package org.shahondin1624.lib.components.settings
|
||||
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
/**
|
||||
* Placeholder settings page.
|
||||
* Will be expanded in story 5.3 (Theme Selection).
|
||||
*/
|
||||
@Composable
|
||||
fun SettingsPage() {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(16.dp)
|
||||
.testTag("settings_page"),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Text(
|
||||
text = "Settings",
|
||||
style = MaterialTheme.typography.headlineMedium
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
package org.shahondin1624.navigation
|
||||
|
||||
/**
|
||||
* Route definitions for the app's navigation graph.
|
||||
*/
|
||||
object AppRoutes {
|
||||
const val CHARACTER_SHEET = "character_sheet"
|
||||
const val SETTINGS = "settings"
|
||||
}
|
||||
Reference in New Issue
Block a user