From be2402198cc77e799a54ab25ec5384b675a4ba1d Mon Sep 17 00:00:00 2001 From: Peter Stone Date: Wed, 28 Oct 2026 07:37:22 +0000 Subject: The library row carries the show's artwork, not a dot The dot said only whether anything was waiting. The count beside the title says that and says how much, so the dot was the row's least informative element sitting in its most prominent position. The artwork spans both lines, which is what makes it read as belonging to the show rather than to the episode named underneath it, and it carries the caught-up dimming with the rest of the row. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_018AwnALZL6bD4VBtATDVv3B --- .../java/org/terst/listen/ui/SubscriptionRow.kt | 87 +++++++++++----------- 1 file changed, 45 insertions(+), 42 deletions(-) (limited to 'app/src') 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 1ffa4a9..49ea000 100644 --- a/app/src/main/java/org/terst/listen/ui/SubscriptionRow.kt +++ b/app/src/main/java/org/terst/listen/ui/SubscriptionRow.kt @@ -2,14 +2,13 @@ package org.terst.listen.ui import androidx.compose.foundation.background import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size -import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.PlayArrow import androidx.compose.material.icons.filled.PlaylistAdd @@ -18,9 +17,11 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.alpha import androidx.compose.ui.draw.clip import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp +import coil3.compose.AsyncImage import org.terst.listen.data.EpisodeEntity import org.terst.listen.data.SubscriptionEntity import org.terst.listen.data.SubscriptionListing @@ -52,48 +53,52 @@ internal fun SubscriptionRow( val subscription = listing.subscription val alpha = if (listing.hasWaiting) 1f else CAUGHT_UP_ALPHA - Column(Modifier.fillMaxWidth().clickable(onClick = onClick).padding(start = 16.dp, end = 4.dp)) { - ShowHeadline(listing, alpha) - if (latest != null && actions != null) LatestEpisodeLine(latest, actions) + Row( + Modifier.fillMaxWidth().clickable(onClick = onClick).padding(start = 16.dp, end = 4.dp), + verticalAlignment = Alignment.Top, + ) { + AsyncImage( + model = subscription.imageUrl, + contentDescription = null, + modifier = Modifier + .padding(top = 12.dp) + .size(SHOW_ICON) + .clip(RoundedCornerShape(8.dp)) + .background(MaterialTheme.colorScheme.surfaceVariant) + .alpha(alpha), + ) + Column(Modifier.weight(1f).padding(start = 12.dp)) { + ShowHeadline(listing, alpha) + if (latest != null && actions != null) LatestEpisodeLine(latest, actions) - // A feed that has quietly stopped working is otherwise indistinguishable from one with - // nothing new, which is the whole reason the error is stored rather than logged. - subscription.lastError?.let { - Text( - "Last refresh failed: $it", - style = MaterialTheme.typography.bodySmall, - color = MaterialTheme.colorScheme.error, - maxLines = 2, - overflow = TextOverflow.Ellipsis, - modifier = Modifier.padding(start = WAITING_DOT + 12.dp, bottom = 4.dp), - ) + // A feed that has quietly stopped working is otherwise indistinguishable from one + // with nothing new, which is the whole reason the error is stored rather than logged. + subscription.lastError?.let { + Text( + "Last refresh failed: $it", + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.error, + maxLines = 2, + overflow = TextOverflow.Ellipsis, + modifier = Modifier.padding(bottom = 4.dp), + ) + } + Spacer(Modifier.size(6.dp)) } - Spacer(Modifier.size(6.dp)) } } -/** The show's own line: a dot for whether anything is waiting, its name, and how much. */ +/** The show's own line: its name, and how much is waiting on it. */ @Composable private fun ShowHeadline(listing: SubscriptionListing, alpha: Float) { Row(Modifier.fillMaxWidth().padding(top = 10.dp), verticalAlignment = Alignment.CenterVertically) { - // A dot rather than artwork. Forty-three shows is a list to scan, not a shelf to - // browse, and the one thing worth seeing per row is whether there is anything in it. - Box( - Modifier - .size(WAITING_DOT) - .clip(CircleShape) - .background( - if (listing.hasWaiting) MaterialTheme.colorScheme.primary - else MaterialTheme.colorScheme.surfaceVariant - ) - ) Text( listing.subscription.title, style = MaterialTheme.typography.bodyLarge, color = MaterialTheme.colorScheme.onSurface.copy(alpha = alpha), maxLines = 1, overflow = TextOverflow.Ellipsis, - modifier = Modifier.weight(1f).padding(start = 12.dp), + modifier = Modifier.weight(1f), ) Text( if (listing.hasWaiting) listing.waiting.toString() else "\u2014", @@ -104,18 +109,10 @@ private fun ShowHeadline(listing: SubscriptionListing, alpha: Float) { } } -/** - * The newest waiting episode, indented under the show's name and offered directly. - * - * Indented to the show title's text, not the dot: the dot belongs to the show, and lining the - * episode up with it would read as a second show rather than as this one's contents. - */ +/** The newest waiting episode, under the show's name and offered directly. */ @Composable private fun LatestEpisodeLine(latest: EpisodeEntity, actions: EpisodeActions) { - Row( - Modifier.fillMaxWidth().padding(start = WAITING_DOT + 12.dp), - verticalAlignment = Alignment.CenterVertically, - ) { + Row(Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) { Text( latest.title, style = MaterialTheme.typography.bodySmall, @@ -130,8 +127,14 @@ private fun LatestEpisodeLine(latest: EpisodeEntity, actions: EpisodeActions) { } } -/** How big the "something waiting" dot is. Small enough to read as punctuation, not a control. */ -private val WAITING_DOT = 8.dp +/** + * How big the show's artwork is. + * + * Tall enough to carry both lines of the row, which is what makes the artwork read as belonging + * to the show rather than to the episode named under it. It replaced a dot that said only + * whether anything was waiting -- the count beside the title says that, and says how much. + */ +private val SHOW_ICON = 44.dp /** * The controls the library row offers on a show's newest waiting episode. -- cgit v1.2.3