From e5e1d134a7ca73d3e5245d5cdcfe6d333b036157 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 8 Aug 2026 17:32:35 +0000 Subject: [PATCH] One centred column for the main menu The games moved a level down when the menus became a tree, so the top screen now holds three sections and two buttons. Spread across an adaptive grid that read as a page half empty rather than as a menu. One column, held in the middle, capped at 340dp so a tablet gets a menu rather than a row of very wide buttons. Join and Scorekeeper sit below the sections and run the same width, so the column stays a column all the way down rather than turning into a wrapped row at the top. The grid is still what the section screens use, where there are enough games for it to earn its place. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01Urn6eEBA6XzrnfoibL7faC --- .../java/org/prolibertate/games/ui/AppRoot.kt | 66 +++++++++++-------- 1 file changed, 37 insertions(+), 29 deletions(-) diff --git a/app/src/main/java/org/prolibertate/games/ui/AppRoot.kt b/app/src/main/java/org/prolibertate/games/ui/AppRoot.kt index 59e8693..5c2c3bd 100644 --- a/app/src/main/java/org/prolibertate/games/ui/AppRoot.kt +++ b/app/src/main/java/org/prolibertate/games/ui/AppRoot.kt @@ -2,14 +2,15 @@ package org.prolibertate.games.ui import androidx.activity.compose.BackHandler import androidx.compose.foundation.background +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.ExperimentalLayoutApi -import androidx.compose.foundation.layout.FlowRow import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.widthIn import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.GridItemSpan import androidx.compose.foundation.lazy.grid.LazyVerticalGrid @@ -214,7 +215,7 @@ private fun AppContent( // Main menu // --------------------------------------------------------------------------- -@OptIn(ExperimentalMaterial3Api::class, ExperimentalLayoutApi::class) +@OptIn(ExperimentalMaterial3Api::class) @Composable fun MainMenuScreen( showComingSoon: Boolean, @@ -233,45 +234,52 @@ fun MainMenuScreen( ) } ) { padding -> - Column( + // Only the top of the tree. Trick-taking is reached through Card Games, + // which is where somebody looking for it would go. + val sections = GameMenu.entries.filter { + it.isTopLevel && GameCatalog.hasAnything(it, includeComingSoon = showComingSoon) + } + + // One column, held in the middle. There are only ever a handful of + // entries here now that the games themselves live a level down, and a + // short list spread across a grid reads as a page half empty rather + // than as a menu. Capped in width so it does not become a row of very + // wide buttons on a tablet. + Box( modifier = Modifier .fillMaxSize() .padding(padding) .padding(horizontal = 12.dp), + contentAlignment = Alignment.Center, ) { - // Wraps rather than running off the edge: two buttons and a narrow - // phone would otherwise push the second one past the screen. - FlowRow( - modifier = Modifier.fillMaxWidth().padding(vertical = 8.dp), - horizontalArrangement = Arrangement.spacedBy(8.dp), - verticalArrangement = Arrangement.spacedBy(4.dp), - ) { - OutlinedButton(onClick = onJoinGame) { Text("Join a game nearby") } - // Not a game, and deliberately not in the catalogue: it keeps - // score for whatever is being played on the actual table. - OutlinedButton(onClick = onScorekeeper) { Text("Scorekeeper") } - } - - // The column count follows the available width, so a phone in - // portrait gets two and a tablet in landscape gets four or more. - LazyVerticalGrid( - columns = GridCells.Adaptive(minSize = 172.dp), + Column( + modifier = Modifier + .widthIn(max = 340.dp) + .fillMaxWidth() + .verticalScroll(rememberScrollState()), verticalArrangement = Arrangement.spacedBy(8.dp), - horizontalArrangement = Arrangement.spacedBy(8.dp), - modifier = Modifier.fillMaxSize(), + horizontalAlignment = Alignment.CenterHorizontally, ) { - // Only the top of the tree. Trick-taking is reached through - // Card Games, which is where somebody looking for it would go. - val sections = GameMenu.entries.filter { - it.isTopLevel && GameCatalog.hasAnything(it, includeComingSoon = showComingSoon) - } - items(sections) { section -> + sections.forEach { section -> SectionTile( menu = section, count = countIn(section, showComingSoon), onClick = { onPickSection(section) }, ) } + + // Below the games, and full width like them, so the column + // stays one column all the way down. + OutlinedButton( + onClick = onJoinGame, + modifier = Modifier.fillMaxWidth().padding(top = 8.dp), + ) { Text("Join a game nearby") } + // Not a game, and deliberately not in the catalogue: it keeps + // score for whatever is being played on the actual table. + OutlinedButton( + onClick = onScorekeeper, + modifier = Modifier.fillMaxWidth(), + ) { Text("Scorekeeper") } } } }