mirror of
https://github.com/austinried/subtracks.git
synced 2026-11-11 07:29:14 +01:00
polish the themed screens: comment line, brighter mini player, art preloading
This commit is contained in:
@@ -384,6 +384,13 @@ class PlaybackController(
|
|||||||
thumbnail: Boolean = false,
|
thumbnail: Boolean = false,
|
||||||
): CoverArtRef? = sourceRepository.coverArt(item?.coverArtId, thumbnail)
|
): 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(
|
private fun play(
|
||||||
entries: List<QueueEntry>,
|
entries: List<QueueEntry>,
|
||||||
startPosition: Long,
|
startPosition: Long,
|
||||||
|
|||||||
@@ -213,6 +213,7 @@ fun HeroHeader(
|
|||||||
topInset: Dp,
|
topInset: Dp,
|
||||||
controlsModifier: Modifier = Modifier,
|
controlsModifier: Modifier = Modifier,
|
||||||
thumbnailRef: CoverArtRef? = null,
|
thumbnailRef: CoverArtRef? = null,
|
||||||
|
comment: String? = null,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
@@ -252,6 +253,17 @@ fun HeroHeader(
|
|||||||
overflow = TextOverflow.Ellipsis,
|
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))
|
Spacer(Modifier.height(20.dp))
|
||||||
Row(
|
Row(
|
||||||
modifier = controlsModifier,
|
modifier = controlsModifier,
|
||||||
|
|||||||
@@ -160,7 +160,7 @@ fun ArtistDetailScreen(
|
|||||||
ref = art,
|
ref = art,
|
||||||
name = artist?.name.orEmpty(),
|
name = artist?.name.orEmpty(),
|
||||||
thumbnailRef = artThumbnail,
|
thumbnailRef = artThumbnail,
|
||||||
showPlaceholder = false,
|
showPlaceholder = art == null,
|
||||||
modifier = Modifier.fillMaxWidth().height(ART_HEIGHT),
|
modifier = Modifier.fillMaxWidth().height(ART_HEIGHT),
|
||||||
)
|
)
|
||||||
Text(
|
Text(
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import com.subtracks.ui.components.CoverArt
|
|||||||
import com.subtracks.ui.components.EmptyState
|
import com.subtracks.ui.components.EmptyState
|
||||||
import com.subtracks.ui.components.LoadingState
|
import com.subtracks.ui.components.LoadingState
|
||||||
import com.subtracks.ui.components.rememberViewportFill
|
import com.subtracks.ui.components.rememberViewportFill
|
||||||
|
import com.subtracks.ui.theme.PrefetchArtworkSeeds
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ArtistsContent(
|
fun ArtistsContent(
|
||||||
@@ -57,6 +58,7 @@ fun ArtistsContent(
|
|||||||
items(count = items.itemCount, key = items.itemKey { it.id }) { index ->
|
items(count = items.itemCount, key = items.itemKey { it.id }) { index ->
|
||||||
val artist = items[index]
|
val artist = items[index]
|
||||||
if (artist != null) {
|
if (artist != null) {
|
||||||
|
PrefetchArtworkSeeds(coverArt(artist.coverArt, true))
|
||||||
ListItem(
|
ListItem(
|
||||||
headlineContent = {
|
headlineContent = {
|
||||||
Text(
|
Text(
|
||||||
|
|||||||
@@ -65,7 +65,8 @@ fun PlaylistDetailScreen(
|
|||||||
art = coverArt(playlist?.coverArt, false),
|
art = coverArt(playlist?.coverArt, false),
|
||||||
thumbnailRef = coverArt(playlist?.coverArt, true),
|
thumbnailRef = coverArt(playlist?.coverArt, true),
|
||||||
name = playlist?.name.orEmpty(),
|
name = playlist?.name.orEmpty(),
|
||||||
subtitle = playlistSubtitle(playlist),
|
subtitle = playlistSummary(playlist),
|
||||||
|
comment = playlist?.comment,
|
||||||
hasSongs = songs.itemCount > 0,
|
hasSongs = songs.itemCount > 0,
|
||||||
onPlay = { onSongClick(0) },
|
onPlay = { onSongClick(0) },
|
||||||
onShuffle = {},
|
onShuffle = {},
|
||||||
@@ -93,12 +94,11 @@ fun PlaylistDetailScreen(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun playlistSubtitle(playlist: Playlist?): String {
|
private fun playlistSummary(playlist: Playlist?): String {
|
||||||
playlist ?: return ""
|
playlist ?: return ""
|
||||||
return listOfNotNull(
|
return listOfNotNull(
|
||||||
"${playlist.songCount} ${if (playlist.songCount == 1L) "song" else "songs"}",
|
"${playlist.songCount} ${if (playlist.songCount == 1L) "song" else "songs"}",
|
||||||
formatDuration(playlist.duration),
|
formatDuration(playlist.duration),
|
||||||
playlist.comment?.takeIf { it.isNotBlank() },
|
|
||||||
).joinToString(" $DOT ")
|
).joinToString(" $DOT ")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ fun MiniPlayer(
|
|||||||
val item = state.item ?: return
|
val item = state.item ?: return
|
||||||
ArtworkTheme(artwork) {
|
ArtworkTheme(artwork) {
|
||||||
Surface(
|
Surface(
|
||||||
color = MaterialTheme.colorScheme.surfaceContainerHigh,
|
color = artwork?.accents?.firstOrNull() ?: MaterialTheme.colorScheme.surfaceContainerHigh,
|
||||||
contentColor = MaterialTheme.colorScheme.onSurface,
|
contentColor = MaterialTheme.colorScheme.onSurface,
|
||||||
modifier =
|
modifier =
|
||||||
modifier.fillMaxWidth().clickable(
|
modifier.fillMaxWidth().clickable(
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.subtracks.ui.playback
|
package com.subtracks.ui.playback
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.basicMarquee
|
import androidx.compose.foundation.basicMarquee
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
@@ -34,6 +35,7 @@ import androidx.compose.material3.TopAppBar
|
|||||||
import androidx.compose.material3.TopAppBarDefaults
|
import androidx.compose.material3.TopAppBarDefaults
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.CompositionLocalProvider
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableFloatStateOf
|
import androidx.compose.runtime.mutableFloatStateOf
|
||||||
import androidx.compose.runtime.mutableStateOf
|
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.graphics.Color
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
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.data.model.CoverArtRef
|
||||||
import com.subtracks.playback.PlaybackController
|
import com.subtracks.playback.PlaybackController
|
||||||
import com.subtracks.playback.PlaybackState
|
import com.subtracks.playback.PlaybackState
|
||||||
import com.subtracks.ui.components.CoverArt
|
import com.subtracks.ui.components.CoverArt
|
||||||
import com.subtracks.ui.theme.ArtworkColors
|
import com.subtracks.ui.theme.ArtworkColors
|
||||||
|
import com.subtracks.ui.theme.ArtworkSeedCache
|
||||||
import com.subtracks.ui.theme.ArtworkTheme
|
import com.subtracks.ui.theme.ArtworkTheme
|
||||||
import com.subtracks.ui.theme.HeroGradient
|
import com.subtracks.ui.theme.HeroGradient
|
||||||
import com.subtracks.ui.theme.rememberArtworkColors
|
import com.subtracks.ui.theme.rememberArtworkColors
|
||||||
@@ -63,11 +69,22 @@ fun NowPlayingRoute(
|
|||||||
controller: PlaybackController = koinInject(),
|
controller: PlaybackController = koinInject(),
|
||||||
) {
|
) {
|
||||||
val state by controller.state.collectAsStateWithLifecycle()
|
val state by controller.state.collectAsStateWithLifecycle()
|
||||||
|
val context = LocalPlatformContext.current
|
||||||
val art = controller.coverArt(state.item)
|
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(
|
NowPlayingScreen(
|
||||||
state = state,
|
state = state,
|
||||||
coverArt = art,
|
coverArt = art,
|
||||||
artwork = rememberArtworkColors(art),
|
thumbnailRef = thumbnail,
|
||||||
|
artwork = rememberArtworkColors(thumbnail ?: art),
|
||||||
onBack = onBack,
|
onBack = onBack,
|
||||||
onQueue = onQueue,
|
onQueue = onQueue,
|
||||||
onPlayPause = controller::togglePlayPause,
|
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)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun NowPlayingScreen(
|
fun NowPlayingScreen(
|
||||||
@@ -90,6 +121,7 @@ fun NowPlayingScreen(
|
|||||||
onNext: () -> Unit,
|
onNext: () -> Unit,
|
||||||
onPrevious: () -> Unit,
|
onPrevious: () -> Unit,
|
||||||
onSeek: (Long) -> Unit,
|
onSeek: (Long) -> Unit,
|
||||||
|
thumbnailRef: CoverArtRef? = null,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
val playButtonSize = 96.dp
|
val playButtonSize = 96.dp
|
||||||
@@ -133,6 +165,7 @@ fun NowPlayingScreen(
|
|||||||
CoverArt(
|
CoverArt(
|
||||||
ref = coverArt,
|
ref = coverArt,
|
||||||
name = state.item?.title.orEmpty(),
|
name = state.item?.title.orEmpty(),
|
||||||
|
thumbnailRef = thumbnailRef,
|
||||||
modifier =
|
modifier =
|
||||||
Modifier
|
Modifier
|
||||||
.fillMaxHeight()
|
.fillMaxHeight()
|
||||||
|
|||||||
Reference in New Issue
Block a user