make the now playing art and artist names tap targets

Tapping the cover art on now playing opens its album and tapping the artist name opens
the artist, matching the context menu actions; the album detail subtitle does the same
for its artist. Both are inert when the queue item has no album/artist id.

Artist detail's album cells also stop drawing the letter placeholder underneath: with a
thumbnail underlay and the full image on top, the letter only showed because the default
`showPlaceholder = true` was left on, so a cold cell flashed a letter before the art.
They now match the hero's `showPlaceholder = art == null`.
This commit is contained in:
austinried
2026-10-04 00:30:00 +00:00
parent edd6d850dd
commit b55e5cc5e8
5 changed files with 138 additions and 7 deletions
@@ -201,6 +201,7 @@ fun HeroHeader(
controlsModifier: Modifier = Modifier,
thumbnailRef: CoverArtRef? = null,
comment: String? = null,
onSubtitleClick: (() -> Unit)? = null,
modifier: Modifier = Modifier,
) {
Column(
@@ -235,6 +236,7 @@ fun HeroHeader(
textAlign = TextAlign.Center,
maxLines = 2,
overflow = TextOverflow.Ellipsis,
modifier = if (onSubtitleClick != null) Modifier.clickable { onSubtitleClick() } else Modifier,
)
}
if (!comment.isNullOrBlank()) {
@@ -80,6 +80,11 @@ fun AlbumDetailRoute(
onShuffle = viewModel::shuffle,
onPlay = viewModel::playAll,
onMore = { album?.let { contextMenuHost?.show(MenuTarget.Album(it, viewModel.coverArt(it.coverArt, true)), actions) } },
onArtistClick =
album
?.takeIf { !it.albumArtist.isNullOrBlank() }
?.let { it.artistId }
?.let { id -> { onViewArtist(id) } },
playingSongId = playback.item?.id.takeIf { context?.kind == QueueKind.Album && context.refId == albumId },
)
}
@@ -98,6 +103,7 @@ fun AlbumDetailScreen(
onPlay: () -> Unit = { onSongClick(0) },
onDownload: () -> Unit = {},
onMore: () -> Unit = {},
onArtistClick: (() -> Unit)? = null,
playingSongId: String? = null,
modifier: Modifier = Modifier,
) {
@@ -124,15 +130,17 @@ fun AlbumDetailScreen(
title = album?.name.orEmpty(),
onBack = onBack,
header = { controlsModifier, topInset ->
val artistName = album?.albumArtist?.takeIf { it.isNotBlank() }
HeroHeader(
art = coverArt(album?.coverArt, false),
thumbnailRef = coverArt(album?.coverArt, true),
name = album?.name.orEmpty(),
subtitle =
listOfNotNull(
album?.albumArtist?.takeIf { it.isNotBlank() },
artistName,
album?.year?.takeIf { it > 0 }?.toString(),
).joinToString(" $DOT "),
onSubtitleClick = onArtistClick,
hasSongs = songs.isNotEmpty(),
onPlay = onPlay,
onShuffle = onShuffle,
@@ -270,7 +270,9 @@ fun ArtistDetailScreen(
}
}
itemsIndexed(albums, key = { _, album -> album.id }) { index, album ->
PrefetchArtworkSeeds(coverArt(album.coverArt, true))
val art = coverArt(album.coverArt, false)
val thumbnail = coverArt(album.coverArt, true)
PrefetchArtworkSeeds(thumbnail)
Column(
modifier =
Modifier
@@ -279,14 +281,15 @@ fun ArtistDetailScreen(
end = if (index % 2 == 1) 16.dp else 0.dp,
).combinedClickable(
onClick = { onAlbumClick(album) },
onLongClick = { onAlbumLongClick(MenuTarget.Album(album, coverArt(album.coverArt, true))) },
onLongClick = { onAlbumLongClick(MenuTarget.Album(album, thumbnail)) },
).onSizeChanged { albumHeightPx = it.height.toFloat() },
) {
Box {
CoverArt(
ref = coverArt(album.coverArt, false),
ref = art,
name = album.name,
thumbnailRef = coverArt(album.coverArt, true),
thumbnailRef = thumbnail,
showPlaceholder = art == null,
modifier = Modifier.fillMaxWidth().aspectRatio(1f).clip(RoundedCornerShape(2.dp)),
)
StarredBadge(
@@ -3,6 +3,7 @@ package com.subtracks.ui.playback
import android.content.Context
import androidx.compose.foundation.background
import androidx.compose.foundation.basicMarquee
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
@@ -50,6 +51,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.text.PlatformTextStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.LineHeightStyle
@@ -80,6 +82,8 @@ import com.subtracks.ui.theme.HeroGradient
import com.subtracks.ui.theme.rememberArtworkColors
import org.koin.compose.koinInject
const val NOW_PLAYING_COVER_TAG = "now-playing-cover"
@Composable
fun NowPlayingRoute(
onBack: () -> Unit,
@@ -139,6 +143,8 @@ fun NowPlayingRoute(
onShuffle = controller::toggleShuffle,
onRepeat = controller::cycleRepeat,
onMore = { song?.let { contextMenuHost?.show(MenuTarget.Song(it, thumbnail), actions) } },
onAlbumClick = song?.albumId?.let { id -> { onViewAlbum(id) } },
onArtistClick = song?.artistId?.let { id -> { onViewArtist(id) } },
onSeek = controller::seekTo,
modifier = modifier,
)
@@ -182,6 +188,8 @@ fun NowPlayingScreen(
onShuffle: () -> Unit = {},
onRepeat: () -> Unit = {},
onMore: () -> Unit = {},
onAlbumClick: (() -> Unit)? = null,
onArtistClick: (() -> Unit)? = null,
onSeek: (Long) -> Unit,
thumbnailRef: CoverArtRef? = null,
modifier: Modifier = Modifier,
@@ -247,7 +255,13 @@ fun NowPlayingScreen(
horizontalAlignment = Alignment.CenterHorizontally,
) {
Box(
modifier = Modifier.weight(1f).fillMaxWidth().padding(top = 2.dp),
modifier =
Modifier
.weight(1f)
.fillMaxWidth()
.padding(top = 2.dp)
.clickable(enabled = onAlbumClick != null) { onAlbumClick?.invoke() }
.testTag(NOW_PLAYING_COVER_TAG),
contentAlignment = Alignment.Center,
) {
CoverArt(
@@ -286,7 +300,13 @@ fun NowPlayingScreen(
modifier = Modifier.basicMarquee(),
)
}
Box(Modifier.height(subtitleHeight), contentAlignment = Alignment.Center) {
Box(
modifier =
Modifier
.height(subtitleHeight)
.clickable(enabled = onArtistClick != null) { onArtistClick?.invoke() },
contentAlignment = Alignment.Center,
) {
Text(
text = state.item?.artist.orEmpty(),
style = subtitleStyle,