apply the prefetched theme on open and soften the hero darkening

This commit is contained in:
austinried
2026-10-04 00:29:53 +00:00
parent f32135d8d2
commit bcb3f20a09
4 changed files with 29 additions and 8 deletions
@@ -68,11 +68,14 @@ private object Routes {
const val LIBRARY = "library"
const val SETTINGS = "settings"
const val ADD_SERVER = "add-server"
const val ALBUM_DETAIL = "album/{albumId}"
const val ALBUM_DETAIL = "album/{albumId}?coverArt={coverArt}"
const val ARTIST_DETAIL = "artist/{artistId}"
const val PLAYLIST_DETAIL = "playlist/{playlistId}"
fun album(id: String) = "album/${Uri.encode(id)}"
fun album(
id: String,
coverArt: String? = null,
) = "album/${Uri.encode(id)}?coverArt=${Uri.encode(coverArt.orEmpty())}"
fun artist(id: String) = "artist/${Uri.encode(id)}"
@@ -135,7 +138,7 @@ private fun MainNavigation() {
) {
composable(Routes.LIBRARY) {
LibraryRoute(
onAlbumClick = { album -> navController.navigate(Routes.album(album.id)) },
onAlbumClick = { album -> navController.navigate(Routes.album(album.id, album.coverArt)) },
onArtistClick = { artist -> navController.navigate(Routes.artist(artist.id)) },
onPlaylistClick = { playlist -> navController.navigate(Routes.playlist(playlist.id)) },
onOpenSettings = { navController.navigate(Routes.SETTINGS) },
@@ -150,10 +153,22 @@ private fun MainNavigation() {
}
composable(
route = Routes.ALBUM_DETAIL,
arguments = listOf(navArgument("albumId") { type = NavType.StringType }),
arguments =
listOf(
navArgument("albumId") { type = NavType.StringType },
navArgument("coverArt") {
type = NavType.StringType
defaultValue = ""
},
),
) { entry ->
AlbumDetailRoute(
albumId = entry.arguments?.getString("albumId").orEmpty(),
coverArtId =
entry.arguments
?.getString("coverArt")
.orEmpty()
.ifEmpty { null },
onBack = { navController.popBackStack() },
)
}
@@ -164,7 +179,7 @@ private fun MainNavigation() {
ArtistDetailRoute(
artistId = entry.arguments?.getString("artistId").orEmpty(),
onBack = { navController.popBackStack() },
onAlbumClick = { album -> navController.navigate(Routes.album(album.id)) },
onAlbumClick = { album -> navController.navigate(Routes.album(album.id, album.coverArt)) },
)
}
composable(
@@ -89,6 +89,7 @@ private const val THEME_TRANSITION_MS = 100
@Composable
fun AlbumDetailRoute(
albumId: String,
coverArtId: String?,
onBack: () -> Unit,
viewModel: AlbumDetailViewModel = koinViewModel(key = albumId) { parametersOf(albumId) },
playbackController: PlaybackController = koinInject(),
@@ -97,11 +98,12 @@ fun AlbumDetailRoute(
val songs by viewModel.songs.collectAsStateWithLifecycle(initialValue = emptyList())
val playback by playbackController.state.collectAsStateWithLifecycle()
val context = playback.context
val shortcutArt = remember(coverArtId) { coverArtId?.let { viewModel.coverArt(it, true) } }
AlbumDetailScreen(
album = album,
songs = songs,
coverArt = viewModel::coverArt,
artwork = rememberArtworkColors(viewModel.coverArt(album?.coverArt, true), THEME_TRANSITION_MS),
artwork = rememberArtworkColors(shortcutArt ?: viewModel.coverArt(album?.coverArt, true), THEME_TRANSITION_MS),
onBack = onBack,
onSongClick = viewModel::play,
playingSongId = playback.item?.id.takeIf { context?.kind == QueueKind.Album && context.refId == albumId },
@@ -44,6 +44,8 @@ object ArtworkSeedCache {
this.store = store
}
fun cached(cacheKey: String): Pair<Int, Int?>? = cache.get(cacheKey)
suspend fun seeds(
context: Context,
ref: CoverArtRef,
@@ -27,7 +27,7 @@ private const val BLOB_COUNT = 4
private const val ACCENT_MAX_LUMINANCE = 0.18f
private const val PRIMARY_MIN_LUMINANCE = 0.30f
private const val MIN_GRADIENT_SATURATION = 0.30f
private const val HERO_DARKEN_MAX = 1.0f
private const val HERO_DARKEN_MAX = 0.88f
private const val DARKEN_START_SCREENS = 0.5f
private const val DARKEN_END_SCREENS = 1.5f
private const val DARKEN_KNEE = 0.2f
@@ -166,8 +166,10 @@ fun rememberArtworkColors(
ref: CoverArtRef?,
durationMillis: Int = ARTWORK_THEME_TRANSITION_MS,
): ArtworkColors {
val cached = remember(ref?.cacheKey) { ref?.cacheKey?.let(ArtworkSeedCache::cached) }
val seeds by rememberArtworkSeed(ref)
val target = remember(seeds) { seeds?.let { (primary, secondary) -> artworkColorsFromSeeds(primary, secondary) } }
val effective = seeds ?: cached
val target = remember(effective) { effective?.let { (primary, secondary) -> artworkColorsFromSeeds(primary, secondary) } }
return rememberAnimatedArtworkColors(target, durationMillis)
}