From bcb3f20a097c3dc5803a52cf5c80977e18c5ef07 Mon Sep 17 00:00:00 2001 From: austinried <4966622+austinried@users.noreply.github.com> Date: Thu, 24 Sep 2026 03:45:11 +0000 Subject: [PATCH] apply the prefetched theme on open and soften the hero darkening --- .../main/kotlin/com/subtracks/ui/AppRoot.kt | 25 +++++++++++++++---- .../subtracks/ui/library/AlbumDetailScreen.kt | 4 ++- .../subtracks/ui/theme/ArtworkSeedCache.kt | 2 ++ .../com/subtracks/ui/theme/ArtworkTheme.kt | 6 +++-- 4 files changed, 29 insertions(+), 8 deletions(-) diff --git a/app/src/main/kotlin/com/subtracks/ui/AppRoot.kt b/app/src/main/kotlin/com/subtracks/ui/AppRoot.kt index c8602397..794e85e7 100644 --- a/app/src/main/kotlin/com/subtracks/ui/AppRoot.kt +++ b/app/src/main/kotlin/com/subtracks/ui/AppRoot.kt @@ -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( diff --git a/app/src/main/kotlin/com/subtracks/ui/library/AlbumDetailScreen.kt b/app/src/main/kotlin/com/subtracks/ui/library/AlbumDetailScreen.kt index cfd898fb..8c404e34 100644 --- a/app/src/main/kotlin/com/subtracks/ui/library/AlbumDetailScreen.kt +++ b/app/src/main/kotlin/com/subtracks/ui/library/AlbumDetailScreen.kt @@ -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 }, diff --git a/app/src/main/kotlin/com/subtracks/ui/theme/ArtworkSeedCache.kt b/app/src/main/kotlin/com/subtracks/ui/theme/ArtworkSeedCache.kt index e425b576..1f5bbd2e 100644 --- a/app/src/main/kotlin/com/subtracks/ui/theme/ArtworkSeedCache.kt +++ b/app/src/main/kotlin/com/subtracks/ui/theme/ArtworkSeedCache.kt @@ -44,6 +44,8 @@ object ArtworkSeedCache { this.store = store } + fun cached(cacheKey: String): Pair? = cache.get(cacheKey) + suspend fun seeds( context: Context, ref: CoverArtRef, diff --git a/app/src/main/kotlin/com/subtracks/ui/theme/ArtworkTheme.kt b/app/src/main/kotlin/com/subtracks/ui/theme/ArtworkTheme.kt index b2160eea..cc329779 100644 --- a/app/src/main/kotlin/com/subtracks/ui/theme/ArtworkTheme.kt +++ b/app/src/main/kotlin/com/subtracks/ui/theme/ArtworkTheme.kt @@ -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) }