summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPeter Stone <thepeterstone@gmail.com>2026-10-28 07:37:22 +0000
committerPeter Stone <thepeterstone@gmail.com>2026-10-28 07:37:22 +0000
commitbe2402198cc77e799a54ab25ec5384b675a4ba1d (patch)
treec18963a7657ffad6ca5a9e5a927a1fb366bce3b9
parentb6112749708cced3c9f0d1dd7fb782214cc42e41 (diff)
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 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018AwnALZL6bD4VBtATDVv3B
-rw-r--r--app/src/main/java/org/terst/listen/ui/SubscriptionRow.kt87
1 files changed, 45 insertions, 42 deletions
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.