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.getValue
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.testTag
|
import androidx.compose.ui.platform.testTag
|
||||||
import androidx.compose.ui.unit.dp
|
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 kotlinx.coroutines.launch
|
||||||
import org.jetbrains.compose.ui.tooling.preview.Preview
|
import org.jetbrains.compose.ui.tooling.preview.Preview
|
||||||
import org.shahondin1624.lib.components.TestTags
|
import org.shahondin1624.lib.components.TestTags
|
||||||
import org.shahondin1624.lib.components.UiConstants
|
import org.shahondin1624.lib.components.UiConstants
|
||||||
import org.shahondin1624.lib.components.charactermodel.attributespage.AttributesPage
|
import org.shahondin1624.lib.components.charactermodel.attributespage.AttributesPage
|
||||||
|
import org.shahondin1624.lib.components.settings.SettingsPage
|
||||||
import org.shahondin1624.model.EXAMPLE_CHARACTER
|
import org.shahondin1624.model.EXAMPLE_CHARACTER
|
||||||
|
import org.shahondin1624.navigation.AppRoutes
|
||||||
import org.shahondin1624.theme.AppTheme
|
import org.shahondin1624.theme.AppTheme
|
||||||
import org.shahondin1624.theme.LocalThemeIsDark
|
import org.shahondin1624.theme.LocalThemeIsDark
|
||||||
import org.shahondin1624.theme.LocalWindowSizeClass
|
import org.shahondin1624.theme.LocalWindowSizeClass
|
||||||
@@ -40,20 +45,44 @@ fun App(
|
|||||||
val windowSizeClass = WindowSizeClass.fromWidth(maxWidth)
|
val windowSizeClass = WindowSizeClass.fromWidth(maxWidth)
|
||||||
|
|
||||||
CompositionLocalProvider(LocalWindowSizeClass provides windowSizeClass) {
|
CompositionLocalProvider(LocalWindowSizeClass provides windowSizeClass) {
|
||||||
AppContent()
|
val navController = rememberNavController()
|
||||||
|
AppContent(navController)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
private fun AppContent() {
|
private fun AppContent(navController: NavHostController) {
|
||||||
val windowSizeClass = LocalWindowSizeClass.current
|
val windowSizeClass = LocalWindowSizeClass.current
|
||||||
|
|
||||||
when (windowSizeClass) {
|
when (windowSizeClass) {
|
||||||
WindowSizeClass.Compact -> CompactNavigation()
|
WindowSizeClass.Compact -> CompactNavigation(navController)
|
||||||
WindowSizeClass.Medium -> MediumNavigation()
|
WindowSizeClass.Medium -> MediumNavigation(navController)
|
||||||
WindowSizeClass.Expanded -> ExpandedNavigation()
|
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)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
private fun CompactNavigation() {
|
private fun CompactNavigation(navController: NavHostController) {
|
||||||
val drawerState = rememberDrawerState(initialValue = DrawerValue.Closed)
|
val drawerState = rememberDrawerState(initialValue = DrawerValue.Closed)
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
|
val currentRoute = currentRoute(navController)
|
||||||
|
|
||||||
ModalNavigationDrawer(
|
ModalNavigationDrawer(
|
||||||
drawerState = drawerState,
|
drawerState = drawerState,
|
||||||
drawerContent = {
|
drawerContent = {
|
||||||
ModalDrawerSheet {
|
ModalDrawerSheet {
|
||||||
DrawerContent()
|
DrawerContent(
|
||||||
|
currentRoute = currentRoute,
|
||||||
|
onNavigate = { route ->
|
||||||
|
navigateTo(navController, route)
|
||||||
|
scope.launch { drawerState.close() }
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
MainScaffold(
|
MainScaffold(
|
||||||
|
navController = navController,
|
||||||
showMenuButton = true,
|
showMenuButton = true,
|
||||||
onMenuClick = {
|
onMenuClick = {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
@@ -90,7 +127,9 @@ private fun CompactNavigation() {
|
|||||||
*/
|
*/
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
private fun MediumNavigation() {
|
private fun MediumNavigation(navController: NavHostController) {
|
||||||
|
val currentRoute = currentRoute(navController)
|
||||||
|
|
||||||
Row(modifier = Modifier.fillMaxSize()) {
|
Row(modifier = Modifier.fillMaxSize()) {
|
||||||
NavigationRail(
|
NavigationRail(
|
||||||
modifier = Modifier.testTag(TestTags.NAV_RAIL)
|
modifier = Modifier.testTag(TestTags.NAV_RAIL)
|
||||||
@@ -99,19 +138,20 @@ private fun MediumNavigation() {
|
|||||||
NavigationRailItem(
|
NavigationRailItem(
|
||||||
icon = { Icon(Icons.Default.Person, contentDescription = null) },
|
icon = { Icon(Icons.Default.Person, contentDescription = null) },
|
||||||
label = { Text("Character") },
|
label = { Text("Character") },
|
||||||
selected = true,
|
selected = currentRoute == AppRoutes.CHARACTER_SHEET,
|
||||||
onClick = { /* TODO: Navigate */ },
|
onClick = { navigateTo(navController, AppRoutes.CHARACTER_SHEET) },
|
||||||
modifier = Modifier.testTag(TestTags.NAV_CHARACTER_SHEET)
|
modifier = Modifier.testTag(TestTags.NAV_CHARACTER_SHEET)
|
||||||
)
|
)
|
||||||
NavigationRailItem(
|
NavigationRailItem(
|
||||||
icon = { Icon(Icons.Default.Settings, contentDescription = null) },
|
icon = { Icon(Icons.Default.Settings, contentDescription = null) },
|
||||||
label = { Text("Settings") },
|
label = { Text("Settings") },
|
||||||
selected = false,
|
selected = currentRoute == AppRoutes.SETTINGS,
|
||||||
onClick = { /* TODO: Navigate to settings */ },
|
onClick = { navigateTo(navController, AppRoutes.SETTINGS) },
|
||||||
modifier = Modifier.testTag(TestTags.NAV_SETTINGS)
|
modifier = Modifier.testTag(TestTags.NAV_SETTINGS)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
MainScaffold(
|
MainScaffold(
|
||||||
|
navController = navController,
|
||||||
showMenuButton = false,
|
showMenuButton = false,
|
||||||
onMenuClick = {},
|
onMenuClick = {},
|
||||||
modifier = Modifier.weight(1f)
|
modifier = Modifier.weight(1f)
|
||||||
@@ -124,15 +164,21 @@ private fun MediumNavigation() {
|
|||||||
*/
|
*/
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
private fun ExpandedNavigation() {
|
private fun ExpandedNavigation(navController: NavHostController) {
|
||||||
|
val currentRoute = currentRoute(navController)
|
||||||
|
|
||||||
PermanentNavigationDrawer(
|
PermanentNavigationDrawer(
|
||||||
drawerContent = {
|
drawerContent = {
|
||||||
PermanentDrawerSheet(modifier = Modifier.testTag(TestTags.NAV_PERMANENT_DRAWER)) {
|
PermanentDrawerSheet(modifier = Modifier.testTag(TestTags.NAV_PERMANENT_DRAWER)) {
|
||||||
DrawerContent()
|
DrawerContent(
|
||||||
|
currentRoute = currentRoute,
|
||||||
|
onNavigate = { route -> navigateTo(navController, route) }
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
MainScaffold(
|
MainScaffold(
|
||||||
|
navController = navController,
|
||||||
showMenuButton = false,
|
showMenuButton = false,
|
||||||
onMenuClick = {}
|
onMenuClick = {}
|
||||||
)
|
)
|
||||||
@@ -143,7 +189,10 @@ private fun ExpandedNavigation() {
|
|||||||
* Shared drawer content used by Compact (ModalDrawerSheet) and Expanded (PermanentDrawerSheet).
|
* Shared drawer content used by Compact (ModalDrawerSheet) and Expanded (PermanentDrawerSheet).
|
||||||
*/
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
private fun DrawerContent() {
|
private fun DrawerContent(
|
||||||
|
currentRoute: String?,
|
||||||
|
onNavigate: (String) -> Unit
|
||||||
|
) {
|
||||||
Spacer(Modifier.height(16.dp))
|
Spacer(Modifier.height(16.dp))
|
||||||
Text(
|
Text(
|
||||||
"Menu",
|
"Menu",
|
||||||
@@ -156,8 +205,8 @@ private fun DrawerContent() {
|
|||||||
NavigationDrawerItem(
|
NavigationDrawerItem(
|
||||||
icon = { Icon(Icons.Default.Person, contentDescription = null) },
|
icon = { Icon(Icons.Default.Person, contentDescription = null) },
|
||||||
label = { Text("Character Sheet") },
|
label = { Text("Character Sheet") },
|
||||||
selected = true,
|
selected = currentRoute == AppRoutes.CHARACTER_SHEET,
|
||||||
onClick = { /* TODO: Navigate */ },
|
onClick = { onNavigate(AppRoutes.CHARACTER_SHEET) },
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(horizontal = 12.dp, vertical = 4.dp)
|
.padding(horizontal = 12.dp, vertical = 4.dp)
|
||||||
.testTag(TestTags.NAV_CHARACTER_SHEET)
|
.testTag(TestTags.NAV_CHARACTER_SHEET)
|
||||||
@@ -166,8 +215,8 @@ private fun DrawerContent() {
|
|||||||
NavigationDrawerItem(
|
NavigationDrawerItem(
|
||||||
icon = { Icon(Icons.Default.Settings, contentDescription = null) },
|
icon = { Icon(Icons.Default.Settings, contentDescription = null) },
|
||||||
label = { Text("Settings") },
|
label = { Text("Settings") },
|
||||||
selected = false,
|
selected = currentRoute == AppRoutes.SETTINGS,
|
||||||
onClick = { /* TODO: Navigate to settings */ },
|
onClick = { onNavigate(AppRoutes.SETTINGS) },
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(horizontal = 12.dp, vertical = 4.dp)
|
.padding(horizontal = 12.dp, vertical = 4.dp)
|
||||||
.testTag(TestTags.NAV_SETTINGS)
|
.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 showMenuButton whether to show the hamburger menu icon (hidden when nav is persistent)
|
||||||
* @param onMenuClick callback for hamburger menu button click
|
* @param onMenuClick callback for hamburger menu button click
|
||||||
* @param modifier optional modifier (e.g., weight in Row for Medium layout)
|
* @param modifier optional modifier (e.g., weight in Row for Medium layout)
|
||||||
@@ -184,11 +234,11 @@ private fun DrawerContent() {
|
|||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
private fun MainScaffold(
|
private fun MainScaffold(
|
||||||
|
navController: NavHostController,
|
||||||
showMenuButton: Boolean,
|
showMenuButton: Boolean,
|
||||||
onMenuClick: () -> Unit,
|
onMenuClick: () -> Unit,
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier
|
||||||
) {
|
) {
|
||||||
val character = EXAMPLE_CHARACTER
|
|
||||||
var isDark by LocalThemeIsDark.current
|
var isDark by LocalThemeIsDark.current
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
@@ -235,14 +285,25 @@ private fun MainScaffold(
|
|||||||
.windowInsetsPadding(WindowInsets.safeDrawing),
|
.windowInsetsPadding(WindowInsets.safeDrawing),
|
||||||
contentAlignment = Alignment.TopCenter
|
contentAlignment = Alignment.TopCenter
|
||||||
) {
|
) {
|
||||||
Column(
|
NavHost(
|
||||||
|
navController = navController,
|
||||||
|
startDestination = AppRoutes.CHARACTER_SHEET,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.widthIn(max = UiConstants.MAX_CONTENT_WIDTH)
|
.widthIn(max = UiConstants.MAX_CONTENT_WIDTH)
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(contentPadding),
|
.padding(contentPadding)
|
||||||
horizontalAlignment = Alignment.CenterHorizontally
|
|
||||||
) {
|
) {
|
||||||
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
|
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