feat(android): covers on AlbumDetail header + MiniPlayer + NowPlaying
Three more "shows the placeholder icon when a cover exists" spots
fixed. Same patterns as Phase 124 (AlbumCard) and earlier track-row
covers — uses the existing displayCoverUrl / TrackRef.coverUrl
properties that route through BaseUrlInterceptor + Coil.
Modified:
- library/ui/AlbumDetailScreen.kt — AlbumCover() branches on
`album.id.isEmpty()` instead of `coverUrl.isEmpty()`, paints
via album.displayCoverUrl. Same cached-only-album story as
AlbumCard.
- player/ui/MiniPlayer.kt — drops the "placeholder until 5.x"
comment, renders track.coverUrl via AsyncImage with the
Lucide.Music fallback. Cover box gets a surfaceVariant
background so failed loads degrade to a tinted square. Picks
up cover for the now-playing track in the persistent mini bar.
- player/ui/NowPlayingScreen.kt — renames CoverPlaceholder() →
NowPlayingCover(coverUrl, contentDescription). The full-screen
player's large 320dp cover now shows actual art instead of a
96dp music glyph. Same surfaceVariant + fallback pattern.
Search track results stay the lone remaining cover-less surface;
splitting Search's generic TextRow for per-track covers is the
biggest remaining polish.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -222,7 +222,7 @@ private fun AlbumCover(album: AlbumRef) {
|
||||
.background(MaterialTheme.colorScheme.surfaceVariant),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
if (album.coverUrl.isEmpty()) {
|
||||
if (album.id.isEmpty()) {
|
||||
Icon(
|
||||
imageVector = Lucide.Disc3,
|
||||
contentDescription = null,
|
||||
@@ -231,7 +231,7 @@ private fun AlbumCover(album: AlbumRef) {
|
||||
)
|
||||
} else {
|
||||
AsyncImage(
|
||||
model = album.coverUrl,
|
||||
model = album.displayCoverUrl,
|
||||
contentDescription = album.title,
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.fabledsword.minstrel.player.ui
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
@@ -21,10 +22,12 @@ import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import coil3.compose.AsyncImage
|
||||
import com.composables.icons.lucide.Lucide
|
||||
import com.composables.icons.lucide.Music
|
||||
import com.composables.icons.lucide.Pause
|
||||
@@ -41,9 +44,9 @@ private const val COVER_SIZE_DP = 48
|
||||
* fresh install. Tapping the body navigates to the full
|
||||
* NowPlayingScreen via [onExpandClick].
|
||||
*
|
||||
* Cover art is a placeholder Lucide icon for now — TrackRef carries
|
||||
* no coverUrl; covers come from the AlbumRef join we'll add in a
|
||||
* later 5.x slice. AsyncImage / Coil wire-up lands then.
|
||||
* Cover comes from TrackRef.coverUrl (derived from albumId via the
|
||||
* BaseUrlInterceptor placeholder); falls back to the Lucide.Music
|
||||
* icon when the album binding is missing.
|
||||
*/
|
||||
@Composable
|
||||
fun MiniPlayer(
|
||||
@@ -69,14 +72,24 @@ fun MiniPlayer(
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(COVER_SIZE_DP.dp)
|
||||
.clip(RoundedCornerShape(FabledSwordFlatTokens.radiusSm)),
|
||||
.clip(RoundedCornerShape(FabledSwordFlatTokens.radiusSm))
|
||||
.background(MaterialTheme.colorScheme.surfaceVariant),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Lucide.Music,
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
if (track.coverUrl.isEmpty()) {
|
||||
Icon(
|
||||
imageVector = Lucide.Music,
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
} else {
|
||||
AsyncImage(
|
||||
model = track.coverUrl,
|
||||
contentDescription = track.title,
|
||||
modifier = Modifier.size(COVER_SIZE_DP.dp),
|
||||
contentScale = ContentScale.Crop,
|
||||
)
|
||||
}
|
||||
}
|
||||
Spacer(Modifier.width(12.dp))
|
||||
Column(modifier = Modifier.weight(1f)) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.fabledsword.minstrel.player.ui
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
@@ -24,12 +25,14 @@ import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.navigation.NavHostController
|
||||
import coil3.compose.AsyncImage
|
||||
import com.composables.icons.lucide.ListMusic
|
||||
import com.composables.icons.lucide.Lucide
|
||||
import com.composables.icons.lucide.Music
|
||||
@@ -75,7 +78,7 @@ fun NowPlayingScreen(
|
||||
verticalArrangement = Arrangement.Center,
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
CoverPlaceholder()
|
||||
NowPlayingCover(coverUrl = track.coverUrl, contentDescription = track.title)
|
||||
Spacer(Modifier.height(24.dp))
|
||||
TrackHeader(
|
||||
title = track.title,
|
||||
@@ -112,21 +115,31 @@ private fun ViewQueueButton(onClick: () -> Unit) {
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun CoverPlaceholder() {
|
||||
private fun NowPlayingCover(coverUrl: String, contentDescription: String) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.widthIn(max = COVER_MAX_WIDTH_DP.dp)
|
||||
.fillMaxWidth()
|
||||
.aspectRatio(1f)
|
||||
.clip(RoundedCornerShape(FabledSwordFlatTokens.radiusLg)),
|
||||
.clip(RoundedCornerShape(FabledSwordFlatTokens.radiusLg))
|
||||
.background(MaterialTheme.colorScheme.surfaceVariant),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Lucide.Music,
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier.size(96.dp),
|
||||
)
|
||||
if (coverUrl.isEmpty()) {
|
||||
Icon(
|
||||
imageVector = Lucide.Music,
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier.size(96.dp),
|
||||
)
|
||||
} else {
|
||||
AsyncImage(
|
||||
model = coverUrl,
|
||||
contentDescription = contentDescription,
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
contentScale = ContentScale.Crop,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user