tint the library background and fab from the current artwork

This commit is contained in:
austinried
2026-10-04 00:29:56 +00:00
parent a633a6c290
commit 8ab4cbb0d0
3 changed files with 59 additions and 4 deletions
@@ -97,6 +97,11 @@ import com.subtracks.data.prefs.ListQuery
import com.subtracks.data.prefs.PlaylistSort
import com.subtracks.data.prefs.SongSort
import com.subtracks.data.prefs.StarredFilter
import com.subtracks.playback.PlaybackController
import com.subtracks.ui.theme.ArtworkColors
import com.subtracks.ui.theme.librarySurfaceColor
import com.subtracks.ui.theme.rememberArtworkColors
import org.koin.compose.koinInject
import org.koin.compose.viewmodel.koinViewModel
import kotlin.math.abs
import kotlin.math.roundToInt
@@ -167,6 +172,7 @@ fun LibraryRoute(
onOpenSettings: () -> Unit,
bottomInset: Dp,
viewModel: LibraryViewModel = koinViewModel(),
playbackController: PlaybackController = koinInject(),
) {
var selectedTab by rememberSaveable { mutableStateOf(LibraryTab.Albums) }
var previousTab by rememberSaveable { mutableStateOf(LibraryTab.Albums) }
@@ -187,11 +193,14 @@ fun LibraryRoute(
val term by viewModel.search(tab.listTab()).collectAsStateWithLifecycle()
"${query.sort}|${query.descending}|${query.starred}|$term"
}
val playback by playbackController.state.collectAsStateWithLifecycle()
val artwork = rememberArtworkColors(playbackController.coverArt(playback.item, thumbnail = true))
LibraryScreen(
selectedTab = selectedTab,
onTabSelected = { selectedTab = it },
syncing = syncing,
bottomInset = bottomInset,
artwork = artwork,
albums = viewModel.albums.collectAsLazyPagingItems(),
artists = viewModel.artists.collectAsLazyPagingItems(),
songs = viewModel.songs.collectAsLazyPagingItems(),
@@ -257,6 +266,7 @@ fun LibraryScreen(
onClearFilters: () -> Unit = {},
search: String = "",
onSearchChange: (String) -> Unit = {},
artwork: ArtworkColors? = null,
modifier: Modifier = Modifier,
) {
val pagerState =
@@ -302,7 +312,7 @@ fun LibraryScreen(
modifier =
Modifier
.fillMaxSize()
.background(MaterialTheme.colorScheme.background)
.background(artwork?.let(::librarySurfaceColor) ?: MaterialTheme.colorScheme.background)
.nestedScroll(scrollBehavior.nestedScrollConnection),
) {
Column(
@@ -419,8 +429,8 @@ fun LibraryScreen(
if (!searchActive) {
FloatingActionButton(
onClick = { showOptions = true },
containerColor = MaterialTheme.colorScheme.surfaceContainerHigh,
contentColor = MaterialTheme.colorScheme.onBackground,
containerColor = artwork?.scheme?.primary ?: MaterialTheme.colorScheme.surfaceContainerHigh,
contentColor = artwork?.scheme?.onPrimary ?: MaterialTheme.colorScheme.onBackground,
modifier = Modifier.align(Alignment.BottomEnd).padding(16.dp),
) {
Box {
@@ -433,7 +443,7 @@ fun LibraryScreen(
.offset(x = 4.dp, y = (-4).dp)
.size(8.dp)
.clip(CircleShape)
.background(MaterialTheme.colorScheme.primary),
.background(artwork?.scheme?.onPrimary ?: MaterialTheme.colorScheme.primary),
)
}
}
@@ -33,6 +33,7 @@ private const val DARKEN_START_SCREENS = 0.5f
private const val DARKEN_END_SCREENS = 1.5f
private const val DARKEN_KNEE = 0.2f
private const val PLAYER_SURFACE_SATURATION_FACTOR = 1.6f
private const val LIBRARY_SURFACE_LIGHTNESS = 0.10f
@Immutable
data class ArtworkColors(
@@ -211,6 +212,11 @@ fun playerSurfaceColor(artwork: ArtworkColors): Color {
return Color.hsl(hue, (saturation * PLAYER_SURFACE_SATURATION_FACTOR).coerceIn(0f, 1f), lightness)
}
fun librarySurfaceColor(artwork: ArtworkColors): Color {
val (hue, saturation, _) = artwork.scheme.surfaceContainerHigh.toHsl()
return Color.hsl(hue, saturation, LIBRARY_SURFACE_LIGHTNESS)
}
fun heroBarColor(
artwork: ArtworkColors?,
scrollPx: Float,
@@ -117,6 +117,44 @@ class AppScreenshotTest {
composeRule.onRoot().captureRoboImage("src/test/screenshots/library_playlists.png")
}
@Test
fun libraryWithMiniPlayer() {
composeRule.setContent {
SubtracksTheme {
val artwork = artworkColorsFromSeed(Color.rgb(120, 80, 200))
Column(Modifier.fillMaxSize()) {
LibraryScreen(
selectedTab = LibraryTab.Albums,
onTabSelected = {},
albums = remember { flowOf(PagingData.from(Fixtures.albums)) }.collectAsLazyPagingItems(),
artists = remember { flowOf(PagingData.from(Fixtures.artists)) }.collectAsLazyPagingItems(),
songs = remember { flowOf(PagingData.from(Fixtures.songItems)) }.collectAsLazyPagingItems(),
playlists = remember { flowOf(PagingData.from(Fixtures.playlists)) }.collectAsLazyPagingItems(),
coverArt = { id, _ -> id?.let { CoverArtRef(it, "test:$it") } },
onAlbumClick = {},
onArtistClick = {},
onPlaylistClick = {},
onSongClick = {},
onSync = {},
onOpenSettings = {},
artwork = artwork,
modifier = Modifier.weight(1f),
)
MiniPlayer(
state = Fixtures.playbackState(),
coverArt = CoverArtRef("art-al-kid-a", "test:art-al-kid-a"),
artwork = artwork,
onExpand = {},
onPlayPause = {},
onNext = {},
)
}
}
}
awaitTag(ALBUM_COVER_TAG)
composeRule.onRoot().captureRoboImage("src/test/screenshots/library_with_mini_player.png")
}
@Test
fun albumDetail() {
composeRule.setContent {
@@ -396,6 +434,7 @@ class AppScreenshotTest {
onSongClick = {},
onSync = {},
onOpenSettings = {},
artwork = artworkColorsFromSeed(Color.rgb(120, 80, 200)),
)
}
}