add a starred indicator and drop the album from the now playing subtitle

A small primary star shows next to the title on album/playlist detail song rows, the queue
and the artists tab, and in the corner of the album grid. The now playing track info stays
centred.
This commit is contained in:
austinried
2026-10-04 00:29:59 +00:00
parent d703b8ec5b
commit 525ab87c9e
5 changed files with 62 additions and 19 deletions
@@ -17,8 +17,11 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.statusBars import androidx.compose.foundation.layout.statusBars
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.Star
import androidx.compose.material3.Button import androidx.compose.material3.Button
import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@@ -301,6 +304,22 @@ fun FilteredEmptyState(
} }
} }
@Composable
fun StarredBadge(
starred: Boolean,
modifier: Modifier = Modifier,
size: Dp = 14.dp,
) {
if (starred) {
Icon(
imageVector = Icons.Rounded.Star,
contentDescription = "Starred",
tint = MaterialTheme.colorScheme.primary,
modifier = modifier.size(size),
)
}
}
@Composable @Composable
fun Modifier.statusBarScrim(): Modifier { fun Modifier.statusBarScrim(): Modifier {
val top = with(LocalDensity.current) { WindowInsets.statusBars.getTop(this).toDp() } val top = with(LocalDensity.current) { WindowInsets.statusBars.getTop(this).toDp() }
@@ -2,18 +2,21 @@ package com.subtracks.ui.library
import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.GridItemSpan import androidx.compose.foundation.lazy.grid.GridItemSpan
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.rememberLazyGridState import androidx.compose.foundation.lazy.grid.rememberLazyGridState
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.platform.testTag import androidx.compose.ui.platform.testTag
@@ -30,6 +33,7 @@ import com.subtracks.ui.components.FilteredEmptyState
import com.subtracks.ui.components.LoadingState import com.subtracks.ui.components.LoadingState
import com.subtracks.ui.components.MenuTarget import com.subtracks.ui.components.MenuTarget
import com.subtracks.ui.components.ResetScrollOnChange import com.subtracks.ui.components.ResetScrollOnChange
import com.subtracks.ui.components.StarredBadge
import com.subtracks.ui.components.rememberViewportFill import com.subtracks.ui.components.rememberViewportFill
import com.subtracks.ui.theme.PrefetchArtworkSeeds import com.subtracks.ui.theme.PrefetchArtworkSeeds
@@ -84,19 +88,26 @@ fun AlbumsContent(
if (album != null) { if (album != null) {
val art = coverArt(album.coverArt, true) val art = coverArt(album.coverArt, true)
PrefetchArtworkSeeds(art) PrefetchArtworkSeeds(art)
CoverArt( Box {
ref = art, CoverArt(
name = album.name, ref = art,
modifier = name = album.name,
Modifier modifier =
.fillMaxWidth() Modifier
.aspectRatio(1f) .fillMaxWidth()
.clip(RoundedCornerShape(2.dp)) .aspectRatio(1f)
.combinedClickable( .clip(RoundedCornerShape(2.dp))
onClick = { onAlbumClick(album) }, .combinedClickable(
onLongClick = { onLongClick(MenuTarget.Album(album, art)) }, onClick = { onAlbumClick(album) },
).testTag(ALBUM_COVER_TAG), onLongClick = { onLongClick(MenuTarget.Album(album, art)) },
) ).testTag(ALBUM_COVER_TAG),
)
StarredBadge(
starred = album.starred != null,
size = 16.dp,
modifier = Modifier.align(Alignment.TopEnd).padding(6.dp),
)
}
} }
} }
item(span = { GridItemSpan(maxLineSpan) }) { item(span = { GridItemSpan(maxLineSpan) }) {
@@ -1,7 +1,9 @@
package com.subtracks.ui.library package com.subtracks.ui.library
import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
@@ -13,6 +15,7 @@ import androidx.compose.material3.ListItem
import androidx.compose.material3.ListItemDefaults import androidx.compose.material3.ListItemDefaults
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
@@ -30,6 +33,7 @@ import com.subtracks.ui.components.FilteredEmptyState
import com.subtracks.ui.components.LoadingState import com.subtracks.ui.components.LoadingState
import com.subtracks.ui.components.MenuTarget import com.subtracks.ui.components.MenuTarget
import com.subtracks.ui.components.ResetScrollOnChange import com.subtracks.ui.components.ResetScrollOnChange
import com.subtracks.ui.components.StarredBadge
import com.subtracks.ui.components.rememberViewportFill import com.subtracks.ui.components.rememberViewportFill
import com.subtracks.ui.theme.PrefetchArtworkSeeds import com.subtracks.ui.theme.PrefetchArtworkSeeds
@@ -75,11 +79,18 @@ fun ArtistsContent(
PrefetchArtworkSeeds(coverArt(artist.coverArt, true)) PrefetchArtworkSeeds(coverArt(artist.coverArt, true))
ListItem( ListItem(
headlineContent = { headlineContent = {
Text( Row(
text = artist.name, verticalAlignment = Alignment.CenterVertically,
maxLines = 1, horizontalArrangement = Arrangement.spacedBy(8.dp),
overflow = TextOverflow.Ellipsis, ) {
) Text(
text = artist.name,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
modifier = Modifier.weight(1f, fill = false),
)
StarredBadge(artist.starred != null)
}
}, },
supportingContent = { supportingContent = {
Text( Text(
@@ -24,6 +24,7 @@ import androidx.compose.ui.unit.dp
import com.subtracks.data.model.CoverArtRef import com.subtracks.data.model.CoverArtRef
import com.subtracks.data.model.Song import com.subtracks.data.model.Song
import com.subtracks.ui.components.CoverArt import com.subtracks.ui.components.CoverArt
import com.subtracks.ui.components.StarredBadge
@Composable @Composable
fun SongRow( fun SongRow(
@@ -80,6 +81,7 @@ fun SongRow(
overflow = TextOverflow.Ellipsis, overflow = TextOverflow.Ellipsis,
modifier = Modifier.weight(1f, fill = false), modifier = Modifier.weight(1f, fill = false),
) )
StarredBadge(song.starred != null)
} }
}, },
supportingContent = { supportingContent = {
@@ -288,7 +288,7 @@ fun NowPlayingScreen(
} }
Box(Modifier.height(subtitleHeight), contentAlignment = Alignment.Center) { Box(Modifier.height(subtitleHeight), contentAlignment = Alignment.Center) {
Text( Text(
text = listOfNotNull(state.item?.artist, state.item?.album).joinToString(" • "), text = state.item?.artist.orEmpty(),
style = subtitleStyle, style = subtitleStyle,
color = MaterialTheme.colorScheme.onSurfaceVariant, color = MaterialTheme.colorScheme.onSurfaceVariant,
maxLines = 1, maxLines = 1,