diff options
| -rw-r--r-- | app/src/main/java/org/terst/listen/ui/SubscriptionRow.kt | 87 |
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. |
