summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
authorPeter Stone <thepeterstone@gmail.com>2026-11-02 07:48:34 +0000
committerPeter Stone <thepeterstone@gmail.com>2026-11-02 07:48:34 +0000
commitb9b91ba21c5a2d333b45d8c1c73ea77c6d1cd3b9 (patch)
treee2e45395acf914db46b710f53cf9e920c2e35977 /app
parent2cc252a27a2b1ec801522e942d255ca9a9b95392 (diff)
The empty queue says it is empty, and Up Next is one name
Two things found by walking the queue screen, which was the one screen the UI pass had not reached. It was the only list in the app that went blank. Every other one explains itself when there is nothing to show -- EmptyLibrary, EmptyBedtime, EmptyRules, the chapter list, the transcript -- but removing the last episode from the queue left a back arrow, a title and nothing, which reads as a screen that failed rather than one with nothing in it. Worse, the empty queue screen is only reachable *by* emptying it: with nothing queued the library hides its Up Next heading and the now-playing bar is absent, so there is no way in. Blank was therefore the only state that screen ever showed after the last removal. It now names the state and says where episodes come from, since none of the three ways in are on that screen. Test written first and watched failing on the missing text, not on a compile error, then confirmed green; then read back off the emulator by queueing an episode, opening the queue and removing it. The second is wording. The queue is called "Up Next" in the Android Auto browse tree, in the queue screen's title, in the now-playing bar's icon and in the banner after queueing something -- and "Up next" in the library heading directly above the row it describes. One name. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018AwnALZL6bD4VBtATDVv3B
Diffstat (limited to 'app')
-rw-r--r--app/src/androidTest/java/org/terst/listen/ui/EmptyQueueTest.kt38
-rw-r--r--app/src/main/java/org/terst/listen/ui/LibraryScreen.kt2
-rw-r--r--app/src/main/java/org/terst/listen/ui/QueueScreen.kt27
3 files changed, 66 insertions, 1 deletions
diff --git a/app/src/androidTest/java/org/terst/listen/ui/EmptyQueueTest.kt b/app/src/androidTest/java/org/terst/listen/ui/EmptyQueueTest.kt
new file mode 100644
index 0000000..3641939
--- /dev/null
+++ b/app/src/androidTest/java/org/terst/listen/ui/EmptyQueueTest.kt
@@ -0,0 +1,38 @@
+package org.terst.listen.ui
+
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithText
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * What the queue screen says when the queue is empty.
+ *
+ * It said nothing at all until now -- the one list screen in the app without an empty state, so
+ * removing the last episode left a bar, a title and a blank page. Pinned as a test because the
+ * failure is silent: a screen that renders nothing looks the same as a screen that crashed.
+ */
+@RunWith(AndroidJUnit4::class)
+class EmptyQueueTest {
+
+ @get:Rule
+ val compose = createComposeRule()
+
+ @Test
+ fun itNamesTheStateRatherThanLeavingTheScreenBlank() {
+ compose.setContent { EmptyQueue() }
+
+ compose.onNodeWithText("Nothing queued").assertIsDisplayed()
+ }
+
+ /** The queue is filled from elsewhere, so the empty state has to say from where. */
+ @Test
+ fun itSaysWhereEpisodesComeFrom() {
+ compose.setContent { EmptyQueue() }
+
+ compose.onNodeWithText("Queue", substring = true).assertIsDisplayed()
+ }
+}
diff --git a/app/src/main/java/org/terst/listen/ui/LibraryScreen.kt b/app/src/main/java/org/terst/listen/ui/LibraryScreen.kt
index a926ce6..bc0e6fc 100644
--- a/app/src/main/java/org/terst/listen/ui/LibraryScreen.kt
+++ b/app/src/main/java/org/terst/listen/ui/LibraryScreen.kt
@@ -195,7 +195,7 @@ internal fun LibraryList(
val bedtime = content.bedtime
LazyColumn(Modifier.fillMaxSize()) {
if (queue.isNotEmpty()) {
- item { SectionHeading("Up next \u00b7 ${queue.size}") }
+ item { SectionHeading("Up Next \u00b7 ${queue.size}") }
item { UpNextRow(queue.first(), onOpenQueue) }
}
if (subscriptions.isEmpty()) {
diff --git a/app/src/main/java/org/terst/listen/ui/QueueScreen.kt b/app/src/main/java/org/terst/listen/ui/QueueScreen.kt
index 119f6ee..4665132 100644
--- a/app/src/main/java/org/terst/listen/ui/QueueScreen.kt
+++ b/app/src/main/java/org/terst/listen/ui/QueueScreen.kt
@@ -2,6 +2,7 @@ package org.terst.listen.ui
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
@@ -77,6 +78,10 @@ fun QueueScreen(vm: ListenViewModel, player: PlayerViewModel, nav: QueueNav) {
},
bottomBar = { PlayerBar(player, nav.openEpisode, nav.openQueue) },
) { padding ->
+ if (localOrder.isEmpty()) {
+ EmptyQueue(Modifier.padding(padding))
+ return@Scaffold
+ }
LazyColumn(state = lazyListState, modifier = Modifier.padding(padding).fillMaxSize()) {
items(localOrder, key = { it.id }) { episode ->
ReorderableItem(reorderableState, key = episode.id) {
@@ -97,6 +102,28 @@ fun QueueScreen(vm: ListenViewModel, player: PlayerViewModel, nav: QueueNav) {
}
}
+/**
+ * What the screen says with nothing in it.
+ *
+ * The queue was the one list in the app that went blank instead -- a bar, a title and nothing,
+ * which reads as a screen that failed rather than one with nothing to show. Says where episodes
+ * come from, because none of the three ways in are on this screen.
+ */
+@Composable
+internal fun EmptyQueue(modifier: Modifier = Modifier) {
+ Column(
+ modifier.fillMaxSize().padding(24.dp),
+ verticalArrangement = Arrangement.spacedBy(8.dp),
+ ) {
+ Text("Nothing queued", style = MaterialTheme.typography.titleMedium)
+ Text(
+ "Queue an episode from a show's list or the library. Rules and slow-drip releases " +
+ "add to it too, and anything you play goes to the top.",
+ style = MaterialTheme.typography.bodyMedium,
+ )
+ }
+}
+
@Composable
private fun ReorderableCollectionItemScope.QueueRow(
episode: EpisodeEntity,