diff options
| author | Peter Stone <thepeterstone@gmail.com> | 2026-10-31 18:36:17 +0000 |
|---|---|---|
| committer | Peter Stone <thepeterstone@gmail.com> | 2026-10-31 18:36:17 +0000 |
| commit | 639e7ca1e2e0dfd6b07d82af4feb62c233e74066 (patch) | |
| tree | 3ce6de989b2321fb0662d9ad824bc3fabc82946d | |
| parent | 5664a342960151be9ebecf5d36119f464f17cc32 (diff) | |
Let an episode's notes use the space the transport freed
Reviewing the episode screen on a device: the notes are cut at 140 characters
and the bottom three-fifths of the screen is empty.
The two-line rule was protecting something real -- long notes used to push the
transport off the screen. That reason went away when the player and episode
screens merged and the notes moved *below* the transport. Nothing is underneath
them now, so there is nothing left to push away, and the rule had been quietly
costing the screen ever since.
Ten lines, which is what the gap the old rule left behind would hold, and the
tap still opens the rest for anything longer.
Three tests failed on the change, all fixtures with the old budget baked into
their text -- "First. Second. Third." for "a third line is cut". Their intent
was always "more than the budget is cut", so they are now sized from the
constant and cannot rot the same way again. The case the old rule got wrong --
a description that fits the budget being shown whole -- is now a test of its
own.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018AwnALZL6bD4VBtATDVv3B
3 files changed, 46 insertions, 13 deletions
diff --git a/app/src/main/java/org/terst/listen/ui/DescriptionPreview.kt b/app/src/main/java/org/terst/listen/ui/DescriptionPreview.kt index 2569408..667a4db 100644 --- a/app/src/main/java/org/terst/listen/ui/DescriptionPreview.kt +++ b/app/src/main/java/org/terst/listen/ui/DescriptionPreview.kt @@ -3,16 +3,26 @@ package org.terst.listen.ui /** * How much of a description shows before the listener asks for the rest. * - * A show with long notes used to push the transport off the screen, so the notes start as about - * two lines. The rule is line count *and* a character budget: stripped HTML is often one long - * paragraph, which two newline-delimited lines would not limit at all. + * It used to be two lines, because a show with long notes pushed the transport off the screen. + * That reason is gone: when the player and the episode screen merged, the notes moved *below* + * the transport, so there is nothing underneath them left to push away. What the old limit + * actually did on a real episode was cut the notes at 140 characters and leave the bottom + * three-fifths of the screen empty. + * + * So the budget is now what the space will hold -- about ten lines on a phone, measured against + * the gap the old rule left behind -- and the tap still opens the rest for notes longer than + * that. The rule is line count *and* a character budget, because stripped HTML is often one + * long paragraph that no number of newline-delimited lines would limit at all. */ object DescriptionPreview { - private const val MAX_LINES = 2 + /** Shared with the Text that renders it, so the two cannot disagree about the cut. */ + const val PREVIEW_LINES = 10 + + private const val MAX_LINES = PREVIEW_LINES - /** Roughly two lines of body text on a phone. */ - const val MAX_CHARS = 140 + /** Roughly ten lines of body text on a phone. */ + const val MAX_CHARS = 700 private const val ELLIPSIS = "…" diff --git a/app/src/main/java/org/terst/listen/ui/EpisodeDescription.kt b/app/src/main/java/org/terst/listen/ui/EpisodeDescription.kt index a3d9ea3..90dc78e 100644 --- a/app/src/main/java/org/terst/listen/ui/EpisodeDescription.kt +++ b/app/src/main/java/org/terst/listen/ui/EpisodeDescription.kt @@ -11,7 +11,7 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.text.style.TextOverflow -/** About two lines of the notes; a tap shows the rest, and another tap folds it back. */ +/** As much of the notes as the screen holds; a tap shows the rest, and another folds it back. */ @Composable fun ExpandableDescription(text: String) { val preview = DescriptionPreview.collapse(text) @@ -20,7 +20,7 @@ fun ExpandableDescription(text: String) { Text( if (showAll) text else preview.text, style = MaterialTheme.typography.bodyMedium, - maxLines = if (showAll) Int.MAX_VALUE else 2, + maxLines = if (showAll) Int.MAX_VALUE else DescriptionPreview.PREVIEW_LINES, overflow = TextOverflow.Ellipsis, modifier = if (preview.truncated) Modifier.clickable { expanded = !expanded } else Modifier, ) diff --git a/app/src/test/java/org/terst/listen/ui/DescriptionPreviewTest.kt b/app/src/test/java/org/terst/listen/ui/DescriptionPreviewTest.kt index 6e9d9c1..4a61403 100644 --- a/app/src/test/java/org/terst/listen/ui/DescriptionPreviewTest.kt +++ b/app/src/test/java/org/terst/listen/ui/DescriptionPreviewTest.kt @@ -5,8 +5,23 @@ import org.junit.Assert.assertFalse import org.junit.Assert.assertTrue import org.junit.Test +/** + * How much of an episode's notes shows before the listener asks for the rest. + * + * The fixtures are sized from the constants rather than written out, because they were not: the + * budget went from two lines to ten when the transport moved above the notes, and three tests + * that were really about "more than the budget is cut" had the old budget baked into their + * text. + */ class DescriptionPreviewTest { + /** One line more than fits, so the cut is exercised whatever the budget becomes. */ + private fun linesBeyondTheBudget(): String = + (1..DescriptionPreview.PREVIEW_LINES + 1).joinToString("\n") { "Line $it." } + + private fun keptLines(): String = + (1..DescriptionPreview.PREVIEW_LINES).joinToString("\n") { "Line $it." } + @Test fun `a short description is shown whole`() { val p = DescriptionPreview.collapse("A short note.") @@ -22,12 +37,20 @@ class DescriptionPreviewTest { } @Test - fun `a third line is cut and the rest is offered`() { - val p = DescriptionPreview.collapse("First.\nSecond.\nThird.") - assertEquals("First.\nSecond.…", p.text) + fun `a line beyond the budget is cut and the rest is offered`() { + val p = DescriptionPreview.collapse(linesBeyondTheBudget()) + assertEquals(keptLines() + "…", p.text) assertTrue(p.truncated) } + /** Everything within the budget is shown, which is the case the old two-line rule failed. */ + @Test + fun `a description that fits the budget is shown whole`() { + val p = DescriptionPreview.collapse(keptLines()) + assertEquals(keptLines(), p.text) + assertFalse(p.truncated) + } + @Test fun `blank lines do not use up the allowance`() { val p = DescriptionPreview.collapse("First.\n\n\nSecond.") @@ -37,7 +60,7 @@ class DescriptionPreviewTest { @Test fun `one long paragraph is cut at a word boundary within the budget`() { - val long = "word ".repeat(100).trim() + val long = "word ".repeat(DescriptionPreview.MAX_CHARS).trim() val p = DescriptionPreview.collapse(long) assertTrue(p.truncated) assertTrue(p.text.endsWith("word…")) @@ -46,7 +69,7 @@ class DescriptionPreviewTest { @Test fun `an unbroken string is hard cut rather than dropped`() { - val p = DescriptionPreview.collapse("x".repeat(500)) + val p = DescriptionPreview.collapse("x".repeat(DescriptionPreview.MAX_CHARS * 2)) assertTrue(p.truncated) assertEquals(DescriptionPreview.MAX_CHARS + 1, p.text.length) } |
