This commit was merged in pull request #50.
This commit is contained in:
@@ -40,18 +40,29 @@ fun App(
|
||||
val windowSizeClass = WindowSizeClass.fromWidth(maxWidth)
|
||||
|
||||
CompositionLocalProvider(LocalWindowSizeClass provides windowSizeClass) {
|
||||
AppContent(onThemeChanged = {})
|
||||
AppContent()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun AppContent(
|
||||
onThemeChanged: @Composable (isDark: Boolean) -> Unit = {}
|
||||
) {
|
||||
val character = EXAMPLE_CHARACTER
|
||||
var isDark by LocalThemeIsDark.current
|
||||
private fun AppContent() {
|
||||
val windowSizeClass = LocalWindowSizeClass.current
|
||||
|
||||
when (windowSizeClass) {
|
||||
WindowSizeClass.Compact -> CompactNavigation()
|
||||
WindowSizeClass.Medium -> MediumNavigation()
|
||||
WindowSizeClass.Expanded -> ExpandedNavigation()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Compact layout: ModalNavigationDrawer with hamburger menu button.
|
||||
*/
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun CompactNavigation() {
|
||||
val drawerState = rememberDrawerState(initialValue = DrawerValue.Closed)
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
@@ -59,100 +70,179 @@ private fun AppContent(
|
||||
drawerState = drawerState,
|
||||
drawerContent = {
|
||||
ModalDrawerSheet {
|
||||
Spacer(Modifier.height(16.dp))
|
||||
Text(
|
||||
"Menu",
|
||||
modifier = Modifier.padding(16.dp),
|
||||
style = MaterialTheme.typography.titleLarge
|
||||
)
|
||||
HorizontalDivider()
|
||||
Spacer(Modifier.height(8.dp))
|
||||
|
||||
NavigationDrawerItem(
|
||||
icon = { Icon(Icons.Default.Person, contentDescription = null) },
|
||||
label = { Text("Character Sheet") },
|
||||
selected = true,
|
||||
onClick = {
|
||||
scope.launch { drawerState.close() }
|
||||
},
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 12.dp, vertical = 4.dp)
|
||||
.testTag(TestTags.NAV_CHARACTER_SHEET)
|
||||
)
|
||||
|
||||
NavigationDrawerItem(
|
||||
icon = { Icon(Icons.Default.Settings, contentDescription = null) },
|
||||
label = { Text("Settings") },
|
||||
selected = false,
|
||||
onClick = {
|
||||
scope.launch { drawerState.close() }
|
||||
// TODO: Navigate to settings
|
||||
},
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 12.dp, vertical = 4.dp)
|
||||
.testTag(TestTags.NAV_SETTINGS)
|
||||
)
|
||||
DrawerContent()
|
||||
}
|
||||
}
|
||||
) {
|
||||
Scaffold(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
title = { Text("Shadowrun Character Sheet") },
|
||||
navigationIcon = {
|
||||
MainScaffold(
|
||||
showMenuButton = true,
|
||||
onMenuClick = {
|
||||
scope.launch {
|
||||
if (drawerState.isClosed) drawerState.open() else drawerState.close()
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Medium layout: NavigationRail on the left side with main content.
|
||||
*/
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun MediumNavigation() {
|
||||
Row(modifier = Modifier.fillMaxSize()) {
|
||||
NavigationRail(
|
||||
modifier = Modifier.testTag(TestTags.NAV_RAIL)
|
||||
) {
|
||||
Spacer(Modifier.height(8.dp))
|
||||
NavigationRailItem(
|
||||
icon = { Icon(Icons.Default.Person, contentDescription = null) },
|
||||
label = { Text("Character") },
|
||||
selected = true,
|
||||
onClick = { /* TODO: Navigate */ },
|
||||
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 */ },
|
||||
modifier = Modifier.testTag(TestTags.NAV_SETTINGS)
|
||||
)
|
||||
}
|
||||
MainScaffold(
|
||||
showMenuButton = false,
|
||||
onMenuClick = {},
|
||||
modifier = Modifier.weight(1f)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Expanded layout: PermanentNavigationDrawer always visible.
|
||||
*/
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun ExpandedNavigation() {
|
||||
PermanentNavigationDrawer(
|
||||
drawerContent = {
|
||||
PermanentDrawerSheet(modifier = Modifier.testTag(TestTags.NAV_PERMANENT_DRAWER)) {
|
||||
DrawerContent()
|
||||
}
|
||||
}
|
||||
) {
|
||||
MainScaffold(
|
||||
showMenuButton = false,
|
||||
onMenuClick = {}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared drawer content used by Compact (ModalDrawerSheet) and Expanded (PermanentDrawerSheet).
|
||||
*/
|
||||
@Composable
|
||||
private fun DrawerContent() {
|
||||
Spacer(Modifier.height(16.dp))
|
||||
Text(
|
||||
"Menu",
|
||||
modifier = Modifier.padding(16.dp),
|
||||
style = MaterialTheme.typography.titleLarge
|
||||
)
|
||||
HorizontalDivider()
|
||||
Spacer(Modifier.height(8.dp))
|
||||
|
||||
NavigationDrawerItem(
|
||||
icon = { Icon(Icons.Default.Person, contentDescription = null) },
|
||||
label = { Text("Character Sheet") },
|
||||
selected = true,
|
||||
onClick = { /* TODO: Navigate */ },
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 12.dp, vertical = 4.dp)
|
||||
.testTag(TestTags.NAV_CHARACTER_SHEET)
|
||||
)
|
||||
|
||||
NavigationDrawerItem(
|
||||
icon = { Icon(Icons.Default.Settings, contentDescription = null) },
|
||||
label = { Text("Settings") },
|
||||
selected = false,
|
||||
onClick = { /* TODO: Navigate to settings */ },
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 12.dp, vertical = 4.dp)
|
||||
.testTag(TestTags.NAV_SETTINGS)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Main scaffold with top app bar and content area.
|
||||
*
|
||||
* @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)
|
||||
*/
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun MainScaffold(
|
||||
showMenuButton: Boolean,
|
||||
onMenuClick: () -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
val character = EXAMPLE_CHARACTER
|
||||
var isDark by LocalThemeIsDark.current
|
||||
|
||||
Scaffold(
|
||||
modifier = modifier.fillMaxSize(),
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
title = { Text("Shadowrun Character Sheet") },
|
||||
modifier = Modifier.testTag(TestTags.TOP_APP_BAR),
|
||||
navigationIcon = {
|
||||
if (showMenuButton) {
|
||||
IconButton(
|
||||
onClick = {
|
||||
scope.launch {
|
||||
if (drawerState.isClosed) {
|
||||
drawerState.open()
|
||||
} else {
|
||||
drawerState.close()
|
||||
}
|
||||
}
|
||||
},
|
||||
onClick = onMenuClick,
|
||||
modifier = Modifier.testTag(TestTags.MENU_BUTTON)
|
||||
) {
|
||||
Icon(Icons.Default.Menu, contentDescription = "Menu")
|
||||
}
|
||||
},
|
||||
actions = {
|
||||
IconButton(
|
||||
onClick = { isDark = !isDark },
|
||||
modifier = Modifier.testTag(TestTags.THEME_TOGGLE)
|
||||
) {
|
||||
Icon(
|
||||
imageVector = if (isDark) {
|
||||
Icons.Default.LightMode
|
||||
} else {
|
||||
Icons.Default.DarkMode
|
||||
},
|
||||
contentDescription = "Toggle ${if (isDark) "Light" else "Dark"} mode"
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
) { paddingValues ->
|
||||
val windowSizeClass = LocalWindowSizeClass.current
|
||||
val contentPadding = UiConstants.Spacing.large(windowSizeClass)
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(paddingValues)
|
||||
.windowInsetsPadding(WindowInsets.safeDrawing),
|
||||
contentAlignment = Alignment.TopCenter
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.widthIn(max = UiConstants.MAX_CONTENT_WIDTH)
|
||||
.fillMaxWidth()
|
||||
.padding(contentPadding),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
AttributesPage(character)
|
||||
},
|
||||
actions = {
|
||||
IconButton(
|
||||
onClick = { isDark = !isDark },
|
||||
modifier = Modifier.testTag(TestTags.THEME_TOGGLE)
|
||||
) {
|
||||
Icon(
|
||||
imageVector = if (isDark) {
|
||||
Icons.Default.LightMode
|
||||
} else {
|
||||
Icons.Default.DarkMode
|
||||
},
|
||||
contentDescription = "Toggle ${if (isDark) "Light" else "Dark"} mode"
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
) { paddingValues ->
|
||||
val windowSizeClass = LocalWindowSizeClass.current
|
||||
val contentPadding = UiConstants.Spacing.large(windowSizeClass)
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(paddingValues)
|
||||
.windowInsetsPadding(WindowInsets.safeDrawing),
|
||||
contentAlignment = Alignment.TopCenter
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.widthIn(max = UiConstants.MAX_CONTENT_WIDTH)
|
||||
.fillMaxWidth()
|
||||
.padding(contentPadding),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
AttributesPage(character)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,8 @@ object TestTags {
|
||||
// --- Navigation items ---
|
||||
const val NAV_CHARACTER_SHEET = "nav_character_sheet"
|
||||
const val NAV_SETTINGS = "nav_settings"
|
||||
const val NAV_RAIL = "nav_rail"
|
||||
const val NAV_PERMANENT_DRAWER = "nav_permanent_drawer"
|
||||
|
||||
// --- Dice / roll buttons ---
|
||||
fun rollButton(name: String): String = "roll_button_${name.lowercase().replace(" ", "_")}"
|
||||
|
||||
@@ -30,6 +30,8 @@ class TestTagsTest {
|
||||
fun navTagsAreDefined() {
|
||||
assertEquals("nav_character_sheet", TestTags.NAV_CHARACTER_SHEET)
|
||||
assertEquals("nav_settings", TestTags.NAV_SETTINGS)
|
||||
assertEquals("nav_rail", TestTags.NAV_RAIL)
|
||||
assertEquals("nav_permanent_drawer", TestTags.NAV_PERMANENT_DRAWER)
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -49,6 +51,8 @@ class TestTagsTest {
|
||||
TestTags.PANEL_DAMAGE_MONITOR,
|
||||
TestTags.NAV_CHARACTER_SHEET,
|
||||
TestTags.NAV_SETTINGS,
|
||||
TestTags.NAV_RAIL,
|
||||
TestTags.NAV_PERMANENT_DRAWER,
|
||||
TestTags.TOP_APP_BAR,
|
||||
TestTags.THEME_TOGGLE,
|
||||
TestTags.MENU_BUTTON
|
||||
|
||||
Reference in New Issue
Block a user