Android: wall mode, so a tablet opens onto the CI wall

b2aad05 · gmorell · 2026-11-03 19:29

15 files +40 -5
Message
{commit_body(@commit)}

Files changed

modified android/README.md
+1 −1
@@ -16,7 +16,7 @@ B612-mono shell, so they feel like one product family.
16 16 | **Issues** | List by state, read with server-rendered markdown, comment, quote-reply, close and reopen |
17 17 | **Pull requests** | List by state (`merged` is its own state), read description + reviews + discussion + line comments, comment, approve / request changes, merge — with the repo's merge blockers spelled out |
18 18 | **Wiki** | Page index and rendered pages (read-only) |
19 −| **Live CI wall** | Every job running right now, a card each, tiled to fill a tablet or TV the way the web's `/users/ci/live` does: up to six across, then the newest columns stack. Each card has its repo, job, clock, step bar and the tail of its log. Yours or one organization's; oldest or newest first (the chips, or R). Keeps the screen on. A TV lists the app on its home screen and opens straight onto the wall; elsewhere it's in the repo list's menu, or long-press the launcher icon |
19 +| **Live CI wall** | Every job running right now, a card each, tiled to fill a tablet or TV the way the web's `/users/ci/live` does: up to six across, then the newest columns stack. Each card has its repo, job, clock, step bar and the tail of its log. Yours or one organization's; oldest or newest first (the chips, or R). Keeps the screen on. A TV lists the app on its home screen and opens straight onto the wall; elsewhere it's in the repo list's menu, or long-press the launcher icon. Tick **Wall mode** in that menu and a tablet opens onto the wall too; Back from there goes on to the repos |
20 20 | **Home-screen widget** | The Plasma widget's strip on a phone: the logo and one status circle per watched repository, or one per organization folded into a single circle. Stretch it taller and the recent runs list underneath — tap a circle to filter them, the logo for every run, a run to open it. Polls every 15 minutes, every 30 s while a run is in flight |
21 21
22 22 The summary and description F-Droid shows come from
modified android/app/src/main/java/org/neiam/gitgud/app/MainActivity.kt
+16 −4
@@ -62,7 +62,8 @@ class MainActivity : ComponentActivity() {
62 62 val initialPair = parseIntent(intent)
63 63 link = parseLink(intent)
64 64 // Opened from a TV's home screen: a TV is there to be the wall.
65 − val startOnWall = intent?.hasCategory(Intent.CATEGORY_LEANBACK_LAUNCHER) == true
65 + val fromTv = intent?.hasCategory(Intent.CATEGORY_LEANBACK_LAUNCHER) == true
66 + val startOnWall = fromTv || prefs.wallOnLaunch
66 67
67 68 setContent {
68 69 // Nothing stored means follow the OS, the same default as the
@@ -84,6 +85,7 @@ class MainActivity : ComponentActivity() {
84 85 initialPair = initialPair,
85 86 link = link,
86 87 startOnWall = startOnWall,
88 + fromTv = fromTv,
87 89 currentTheme = theme,
88 90 currentThemeKey = themeKey,
89 91 onPickTheme = { picked ->
@@ -152,6 +154,7 @@ fun GitGudApp(
152 154 initialPair: PairPayload?,
153 155 link: DeepLink? = null,
154 156 startOnWall: Boolean = false,
157 + fromTv: Boolean = false,
155 158 currentTheme: AppTheme,
156 159 currentThemeKey: String,
157 160 onPickTheme: (String) -> Unit,
@@ -197,6 +200,8 @@ fun GitGudApp(
197 200 onPickTheme = onPickTheme,
198 201 onOpenRepo = { owner, name -> nav.navigate("repo/$owner/$name") },
199 202 onOpenWall = { nav.navigate("wall") },
203 + wallOnLaunch = prefs.wallOnLaunch,
204 + onWallOnLaunch = { prefs.wallOnLaunch = it },
200 205 onUnpair = {
201 206 tokens.clear()
202 207 nav.navigate("pair") { popUpTo(0) }
@@ -208,9 +213,16 @@ fun GitGudApp(
208 213 CiWallScreen(
209 214 tokens = tokens,
210 215 prefs = prefs,
211 − // The wall is a TV's first screen: Back from there leaves
212 − // the app rather than finding nothing to pop.
213 − onBack = { if (!nav.popBackStack()) activity?.finish() },
216 + // When the wall is the first screen, Back leaves the app on
217 + // a TV (as its other apps do), and goes on to the repo list
218 + // on a tablet in wall mode, so the rest is still reachable.
219 + onBack = {
220 + when {
221 + nav.popBackStack() -> Unit
222 + fromTv -> activity?.finish()
223 + else -> nav.navigate("repos") { popUpTo("wall") { inclusive = true } }
224 + }
225 + },
214 226 )
215 227 }
216 228
modified android/app/src/main/java/org/neiam/gitgud/app/data/PrefsStore.kt
+9 −0
@@ -52,6 +52,14 @@ class PrefsStore(context: Context) {
52 52 get() = prefs.getBoolean(KEY_WALL_NEWEST, false)
53 53 set(value) = prefs.edit().putBoolean(KEY_WALL_NEWEST, value).apply()
54 54
55 + /**
56 + * Wall mode: the app opens onto the CI wall rather than the repo
57 + * list — for a tablet mounted on a wall. A TV always does.
58 + */
59 + var wallOnLaunch: Boolean
60 + get() = prefs.getBoolean(KEY_WALL_ON_LAUNCH, false)
61 + set(value) = prefs.edit().putBoolean(KEY_WALL_ON_LAUNCH, value).apply()
62 +
55 63 private fun put(key: String, value: String?) {
56 64 prefs.edit().also { e ->
57 65 if (value == null) e.remove(key) else e.putString(key, value)
@@ -63,5 +71,6 @@ class PrefsStore(context: Context) {
63 71 private const val KEY_REPO_SORT = "repo_sort"
64 72 private const val KEY_WALL_SCOPE = "wall_scope"
65 73 private const val KEY_WALL_NEWEST = "wall_newest_first"
74 + private const val KEY_WALL_ON_LAUNCH = "wall_on_launch"
66 75 }
67 76 }
modified android/app/src/main/java/org/neiam/gitgud/app/ui/RepoListScreen.kt
+14 −0
@@ -25,6 +25,7 @@ import androidx.compose.material.icons.filled.Refresh
25 25 import androidx.compose.material.icons.automirrored.filled.Sort
26 26 import androidx.compose.material3.AlertDialog
27 27 import androidx.compose.material3.Card
28 +import androidx.compose.material3.Checkbox
28 29 import androidx.compose.material3.DropdownMenu
29 30 import androidx.compose.material3.DropdownMenuItem
30 31 import androidx.compose.material3.ExperimentalMaterial3Api
@@ -84,6 +85,8 @@ fun RepoListScreen(
84 85 onPickTheme: (String) -> Unit,
85 86 onOpenRepo: (owner: String, name: String) -> Unit,
86 87 onOpenWall: () -> Unit,
88 + wallOnLaunch: Boolean,
89 + onWallOnLaunch: (Boolean) -> Unit,
87 90 onUnpair: () -> Unit,
88 91 ) {
89 92 val scope = rememberCoroutineScope()
@@ -94,6 +97,7 @@ fun RepoListScreen(
94 97 var sortPickerOpen by remember { mutableStateOf(false) }
95 98 var sort by remember { mutableStateOf(RepoSort.byKey(prefs.repoSortKey)) }
96 99 var reloadKey by remember { mutableStateOf(0) }
100 + var wallMode by remember { mutableStateOf(wallOnLaunch) }
97 101
98 102 LaunchedEffect(reloadKey) {
99 103 val creds = tokens.load()
@@ -128,6 +132,16 @@ fun RepoListScreen(
128 132 onOpenWall()
129 133 },
130 134 )
135 + DropdownMenuItem(
136 + text = { Text("Wall mode: open on the wall") },
137 + trailingIcon = {
138 + Checkbox(checked = wallMode, onCheckedChange = null)
139 + },
140 + onClick = {
141 + wallMode = !wallMode
142 + onWallOnLaunch(wallMode)
143 + },
144 + )
131 145 DropdownMenuItem(
132 146 text = { Text("Sort: ${sort.label}") },
133 147 leadingIcon = { Icon(Icons.AutoMirrored.Filled.Sort, contentDescription = null) },

Parents: 78cc6fd