From d4738ee16513b86d9e66480ea10b0b19a7f9809c Mon Sep 17 00:00:00 2001 From: Peter Stone Date: Sat, 14 Nov 2026 07:25:36 +0000 Subject: Hold a show to play it, and put the second row the right way round Two of Peter's, both on surfaces that already had the pieces. Holding a show in the library plays what it is offering, the same gesture the episode list has carried since the row buttons changed. Playing a show's newest episode meant opening the show first, which is two taps for the thing you want most often. A show offering nothing does nothing when held, rather than something arbitrary -- tapping still opens it either way. The transport's second row reads sleep, speed, ad. It was the reverse, which was my ordering and not a considered one. Sleep is set deliberately before settling down so it takes the near edge; the ad button is hit mid-listen when something slips through, so it sits furthest out where a stray press costs the least. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_018AwnALZL6bD4VBtATDVv3B --- .../java/org/terst/listen/ui/LibraryRowTest.kt | 42 ++++++++++++++++++++++ .../java/org/terst/listen/ui/EpisodeTransport.kt | 8 +++-- .../java/org/terst/listen/ui/SubscriptionRow.kt | 13 +++++-- 3 files changed, 59 insertions(+), 4 deletions(-) (limited to 'app') diff --git a/app/src/androidTest/java/org/terst/listen/ui/LibraryRowTest.kt b/app/src/androidTest/java/org/terst/listen/ui/LibraryRowTest.kt index a66337f..af45221 100644 --- a/app/src/androidTest/java/org/terst/listen/ui/LibraryRowTest.kt +++ b/app/src/androidTest/java/org/terst/listen/ui/LibraryRowTest.kt @@ -4,7 +4,9 @@ import androidx.compose.ui.test.assertIsDisplayed import androidx.compose.ui.test.junit4.createComposeRule import androidx.compose.ui.test.onNodeWithContentDescription import androidx.compose.ui.test.onNodeWithText +import androidx.compose.ui.test.longClick import androidx.compose.ui.test.performClick +import androidx.compose.ui.test.performTouchInput import androidx.test.ext.junit.runners.AndroidJUnit4 import org.junit.Assert.assertEquals import org.junit.Rule @@ -107,6 +109,46 @@ class LibraryRowTest { assertEquals(1, queuedNext) } + /** + * Press and hold a show to play what it is offering. + * + * The same gesture the episode list already had, and for the same reason: the row's three + * buttons are the deliberate ones, and "just play it" wants no width at all. Without this, + * playing the newest episode of a show from the library meant opening the show first. + */ + @Test + fun holdingAShowPlaysTheEpisodeItOffers() { + var played = 0 + compose.setContent { + SubscriptionRow( + listing = SubscriptionListing(show("s", "Zebra Hour"), waiting = 1), + latest = episode("e", "s", "The one about stripes"), + actions = noActions().copy(play = { played++ }), + ) {} + } + + compose.onNodeWithText("The one about stripes").performTouchInput { longClick() } + + assertEquals(1, played) + } + + /** A tap still opens the show, so holding has not replaced the ordinary gesture. */ + @Test + fun tappingAShowStillOpensIt() { + var opened = 0 + compose.setContent { + SubscriptionRow( + listing = SubscriptionListing(show("s", "Zebra Hour"), waiting = 1), + latest = episode("e", "s", "The one about stripes"), + actions = noActions(), + ) { opened++ } + } + + compose.onNodeWithText("Zebra Hour").performClick() + + assertEquals(1, opened) + } + /** * Marking it played is the control that was missing, and the reason the row changed: saying * "not now" without opening the show is what sends a long library back to being scrollable. diff --git a/app/src/main/java/org/terst/listen/ui/EpisodeTransport.kt b/app/src/main/java/org/terst/listen/ui/EpisodeTransport.kt index b8185bf..837b98a 100644 --- a/app/src/main/java/org/terst/listen/ui/EpisodeTransport.kt +++ b/app/src/main/java/org/terst/listen/ui/EpisodeTransport.kt @@ -112,13 +112,17 @@ fun EpisodeTransport( horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically, ) { + // Sleep, speed, ad -- Peter's order, and it was backwards. Sleep is the one set + // deliberately before settling down, so it gets the near edge; the ad button is + // pressed mid-listen when something slips through, so it sits furthest out where a + // stray press costs least. + SleepChip(sleep, onSleep) + SpeedChip(speed, actions.setSpeed) // A circle-and-slash, the same mark the notification uses, so the two read as one // control in two places rather than two features. IconButton(onClick = actions.reportAd, enabled = playback.isPlaying) { Icon(Icons.Default.Block, contentDescription = "This is an ad") } - SpeedChip(speed, actions.setSpeed) - SleepChip(sleep, onSleep) } } } diff --git a/app/src/main/java/org/terst/listen/ui/SubscriptionRow.kt b/app/src/main/java/org/terst/listen/ui/SubscriptionRow.kt index 7b793bb..c8b2dd5 100644 --- a/app/src/main/java/org/terst/listen/ui/SubscriptionRow.kt +++ b/app/src/main/java/org/terst/listen/ui/SubscriptionRow.kt @@ -1,7 +1,8 @@ package org.terst.listen.ui import androidx.compose.foundation.background -import androidx.compose.foundation.clickable +import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer @@ -45,6 +46,7 @@ private const val CAUGHT_UP_ALPHA = 0.38f * refresh looks exactly like a feed with nothing new, and that is the one case where "nothing * waiting" is a lie worth shouting about. */ +@OptIn(ExperimentalFoundationApi::class) @Composable internal fun SubscriptionRow( listing: SubscriptionListing, @@ -56,7 +58,14 @@ internal fun SubscriptionRow( val alpha = if (listing.hasWaiting) 1f else CAUGHT_UP_ALPHA Row( - Modifier.fillMaxWidth().clickable(onClick = onClick).padding(start = 16.dp, end = 4.dp), + Modifier + .fillMaxWidth() + // Tap opens the show; press and hold plays what it is offering. The same pair the + // episode list carries, for the same reason -- the three buttons are the deliberate + // actions, and "just play it" should cost no width. Null when the show is offering + // nothing, so holding an empty row does nothing rather than something arbitrary. + .combinedClickable(onClick = onClick, onLongClick = actions?.play) + .padding(start = 16.dp, end = 4.dp), verticalAlignment = Alignment.Top, ) { AsyncImage( -- cgit v1.2.3