polish the themed screens: comment line, brighter mini player, art preloading

This commit is contained in:
austinried
2026-10-04 00:29:54 +00:00
parent f70d1ba27e
commit 39e2c5d09e
7 changed files with 60 additions and 6 deletions
@@ -384,6 +384,13 @@ class PlaybackController(
thumbnail: Boolean = false,
): CoverArtRef? = sourceRepository.coverArt(item?.coverArtId, thumbnail)
suspend fun upcomingCoverArt(thumbnail: Boolean = false): CoverArtRef? {
val snap = snapshot ?: return null
val position = currentPosition() ?: return null
val next = queueRepository.itemAt(snap, position + 1) ?: return null
return sourceRepository.coverArt(next.toQueueItem().coverArtId, thumbnail)
}
private fun play(
entries: List<QueueEntry>,
startPosition: Long,
@@ -213,6 +213,7 @@ fun HeroHeader(
topInset: Dp,
controlsModifier: Modifier = Modifier,
thumbnailRef: CoverArtRef? = null,
comment: String? = null,
modifier: Modifier = Modifier,
) {
Column(
@@ -252,6 +253,17 @@ fun HeroHeader(
overflow = TextOverflow.Ellipsis,
)
}
if (!comment.isNullOrBlank()) {
Spacer(Modifier.height(4.dp))
Text(
text = comment,
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant,
textAlign = TextAlign.Center,
maxLines = 2,
overflow = TextOverflow.Ellipsis,
)
}
Spacer(Modifier.height(20.dp))
Row(
modifier = controlsModifier,
@@ -160,7 +160,7 @@ fun ArtistDetailScreen(
ref = art,
name = artist?.name.orEmpty(),
thumbnailRef = artThumbnail,
showPlaceholder = false,
showPlaceholder = art == null,
modifier = Modifier.fillMaxWidth().height(ART_HEIGHT),
)
Text(
@@ -28,6 +28,7 @@ import com.subtracks.ui.components.CoverArt
import com.subtracks.ui.components.EmptyState
import com.subtracks.ui.components.LoadingState
import com.subtracks.ui.components.rememberViewportFill
import com.subtracks.ui.theme.PrefetchArtworkSeeds
@Composable
fun ArtistsContent(
@@ -57,6 +58,7 @@ fun ArtistsContent(
items(count = items.itemCount, key = items.itemKey { it.id }) { index ->
val artist = items[index]
if (artist != null) {
PrefetchArtworkSeeds(coverArt(artist.coverArt, true))
ListItem(
headlineContent = {
Text(
@@ -65,7 +65,8 @@ fun PlaylistDetailScreen(
art = coverArt(playlist?.coverArt, false),
thumbnailRef = coverArt(playlist?.coverArt, true),
name = playlist?.name.orEmpty(),
subtitle = playlistSubtitle(playlist),
subtitle = playlistSummary(playlist),
comment = playlist?.comment,
hasSongs = songs.itemCount > 0,
onPlay = { onSongClick(0) },
onShuffle = {},
@@ -93,12 +94,11 @@ fun PlaylistDetailScreen(
)
}
private fun playlistSubtitle(playlist: Playlist?): String {
private fun playlistSummary(playlist: Playlist?): String {
playlist ?: return ""
return listOfNotNull(
"${playlist.songCount} ${if (playlist.songCount == 1L) "song" else "songs"}",
formatDuration(playlist.duration),
playlist.comment?.takeIf { it.isNotBlank() },
).joinToString(" $DOT ")
}
@@ -46,7 +46,7 @@ fun MiniPlayer(
val item = state.item ?: return
ArtworkTheme(artwork) {
Surface(
color = MaterialTheme.colorScheme.surfaceContainerHigh,
color = artwork?.accents?.firstOrNull() ?: MaterialTheme.colorScheme.surfaceContainerHigh,
contentColor = MaterialTheme.colorScheme.onSurface,
modifier =
modifier.fillMaxWidth().clickable(
@@ -1,5 +1,6 @@
package com.subtracks.ui.playback
import android.content.Context
import androidx.compose.foundation.background
import androidx.compose.foundation.basicMarquee
import androidx.compose.foundation.layout.Arrangement
@@ -34,6 +35,7 @@ import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.mutableStateOf
@@ -45,11 +47,15 @@ import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import coil3.SingletonImageLoader
import coil3.compose.LocalPlatformContext
import coil3.request.ImageRequest
import com.subtracks.data.model.CoverArtRef
import com.subtracks.playback.PlaybackController
import com.subtracks.playback.PlaybackState
import com.subtracks.ui.components.CoverArt
import com.subtracks.ui.theme.ArtworkColors
import com.subtracks.ui.theme.ArtworkSeedCache
import com.subtracks.ui.theme.ArtworkTheme
import com.subtracks.ui.theme.HeroGradient
import com.subtracks.ui.theme.rememberArtworkColors
@@ -63,11 +69,22 @@ fun NowPlayingRoute(
controller: PlaybackController = koinInject(),
) {
val state by controller.state.collectAsStateWithLifecycle()
val context = LocalPlatformContext.current
val art = controller.coverArt(state.item)
val thumbnail = controller.coverArt(state.item, thumbnail = true)
LaunchedEffect(state.item?.id, state.hasNext) {
if (!state.hasNext) return@LaunchedEffect
controller.upcomingCoverArt(thumbnail = true)?.let {
ArtworkSeedCache.prefetch(context, it)
prefetchImage(context, it)
}
controller.upcomingCoverArt()?.let { prefetchImage(context, it) }
}
NowPlayingScreen(
state = state,
coverArt = art,
artwork = rememberArtworkColors(art),
thumbnailRef = thumbnail,
artwork = rememberArtworkColors(thumbnail ?: art),
onBack = onBack,
onQueue = onQueue,
onPlayPause = controller::togglePlayPause,
@@ -78,6 +95,20 @@ fun NowPlayingRoute(
)
}
private fun prefetchImage(
context: Context,
ref: CoverArtRef,
) {
SingletonImageLoader.get(context).enqueue(
ImageRequest
.Builder(context)
.data(ref.url)
.memoryCacheKey(ref.cacheKey)
.diskCacheKey(ref.cacheKey)
.build(),
)
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun NowPlayingScreen(
@@ -90,6 +121,7 @@ fun NowPlayingScreen(
onNext: () -> Unit,
onPrevious: () -> Unit,
onSeek: (Long) -> Unit,
thumbnailRef: CoverArtRef? = null,
modifier: Modifier = Modifier,
) {
val playButtonSize = 96.dp
@@ -133,6 +165,7 @@ fun NowPlayingScreen(
CoverArt(
ref = coverArt,
name = state.item?.title.orEmpty(),
thumbnailRef = thumbnailRef,
modifier =
Modifier
.fillMaxHeight()