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),
|
.background(MaterialTheme.colorScheme.surfaceVariant),
|
||||||
contentAlignment = Alignment.Center,
|
contentAlignment = Alignment.Center,
|
||||||
) {
|
) {
|
||||||
if (album.coverUrl.isEmpty()) {
|
if (album.id.isEmpty()) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Lucide.Disc3,
|
imageVector = Lucide.Disc3,
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
@@ -231,7 +231,7 @@ private fun AlbumCover(album: AlbumRef) {
|
|||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
AsyncImage(
|
AsyncImage(
|
||||||
model = album.coverUrl,
|
model = album.displayCoverUrl,
|
||||||
contentDescription = album.title,
|
contentDescription = album.title,
|
||||||
modifier = Modifier.fillMaxSize(),
|
modifier = Modifier.fillMaxSize(),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.fabledsword.minstrel.player.ui
|
package com.fabledsword.minstrel.player.ui
|
||||||
|
|
||||||
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
@@ -21,10 +22,12 @@ import androidx.compose.runtime.getValue
|
|||||||
import androidx.compose.ui.Alignment
|
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.layout.ContentScale
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.hilt.navigation.compose.hiltViewModel
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
|
import coil3.compose.AsyncImage
|
||||||
import com.composables.icons.lucide.Lucide
|
import com.composables.icons.lucide.Lucide
|
||||||
import com.composables.icons.lucide.Music
|
import com.composables.icons.lucide.Music
|
||||||
import com.composables.icons.lucide.Pause
|
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
|
* fresh install. Tapping the body navigates to the full
|
||||||
* NowPlayingScreen via [onExpandClick].
|
* NowPlayingScreen via [onExpandClick].
|
||||||
*
|
*
|
||||||
* Cover art is a placeholder Lucide icon for now — TrackRef carries
|
* Cover comes from TrackRef.coverUrl (derived from albumId via the
|
||||||
* no coverUrl; covers come from the AlbumRef join we'll add in a
|
* BaseUrlInterceptor placeholder); falls back to the Lucide.Music
|
||||||
* later 5.x slice. AsyncImage / Coil wire-up lands then.
|
* icon when the album binding is missing.
|
||||||
*/
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
fun MiniPlayer(
|
fun MiniPlayer(
|
||||||
@@ -69,14 +72,24 @@ fun MiniPlayer(
|
|||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.size(COVER_SIZE_DP.dp)
|
.size(COVER_SIZE_DP.dp)
|
||||||
.clip(RoundedCornerShape(FabledSwordFlatTokens.radiusSm)),
|
.clip(RoundedCornerShape(FabledSwordFlatTokens.radiusSm))
|
||||||
|
.background(MaterialTheme.colorScheme.surfaceVariant),
|
||||||
contentAlignment = Alignment.Center,
|
contentAlignment = Alignment.Center,
|
||||||
) {
|
) {
|
||||||
Icon(
|
if (track.coverUrl.isEmpty()) {
|
||||||
imageVector = Lucide.Music,
|
Icon(
|
||||||
contentDescription = null,
|
imageVector = Lucide.Music,
|
||||||
tint = MaterialTheme.colorScheme.onSurfaceVariant,
|
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))
|
Spacer(Modifier.width(12.dp))
|
||||||
Column(modifier = Modifier.weight(1f)) {
|
Column(modifier = Modifier.weight(1f)) {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.fabledsword.minstrel.player.ui
|
package com.fabledsword.minstrel.player.ui
|
||||||
|
|
||||||
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
@@ -24,12 +25,14 @@ import androidx.compose.runtime.getValue
|
|||||||
import androidx.compose.ui.Alignment
|
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.layout.ContentScale
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.hilt.navigation.compose.hiltViewModel
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import androidx.navigation.NavHostController
|
import androidx.navigation.NavHostController
|
||||||
|
import coil3.compose.AsyncImage
|
||||||
import com.composables.icons.lucide.ListMusic
|
import com.composables.icons.lucide.ListMusic
|
||||||
import com.composables.icons.lucide.Lucide
|
import com.composables.icons.lucide.Lucide
|
||||||
import com.composables.icons.lucide.Music
|
import com.composables.icons.lucide.Music
|
||||||
@@ -75,7 +78,7 @@ fun NowPlayingScreen(
|
|||||||
verticalArrangement = Arrangement.Center,
|
verticalArrangement = Arrangement.Center,
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
) {
|
) {
|
||||||
CoverPlaceholder()
|
NowPlayingCover(coverUrl = track.coverUrl, contentDescription = track.title)
|
||||||
Spacer(Modifier.height(24.dp))
|
Spacer(Modifier.height(24.dp))
|
||||||
TrackHeader(
|
TrackHeader(
|
||||||
title = track.title,
|
title = track.title,
|
||||||
@@ -112,21 +115,31 @@ private fun ViewQueueButton(onClick: () -> Unit) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun CoverPlaceholder() {
|
private fun NowPlayingCover(coverUrl: String, contentDescription: String) {
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.widthIn(max = COVER_MAX_WIDTH_DP.dp)
|
.widthIn(max = COVER_MAX_WIDTH_DP.dp)
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.aspectRatio(1f)
|
.aspectRatio(1f)
|
||||||
.clip(RoundedCornerShape(FabledSwordFlatTokens.radiusLg)),
|
.clip(RoundedCornerShape(FabledSwordFlatTokens.radiusLg))
|
||||||
|
.background(MaterialTheme.colorScheme.surfaceVariant),
|
||||||
contentAlignment = Alignment.Center,
|
contentAlignment = Alignment.Center,
|
||||||
) {
|
) {
|
||||||
Icon(
|
if (coverUrl.isEmpty()) {
|
||||||
imageVector = Lucide.Music,
|
Icon(
|
||||||
contentDescription = null,
|
imageVector = Lucide.Music,
|
||||||
tint = MaterialTheme.colorScheme.onSurfaceVariant,
|
contentDescription = null,
|
||||||
modifier = Modifier.size(96.dp),
|
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