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.PlaylistSort
import com.subtracks.data.prefs.SongSort import com.subtracks.data.prefs.SongSort
import com.subtracks.data.prefs.StarredFilter 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 org.koin.compose.viewmodel.koinViewModel
import kotlin.math.abs import kotlin.math.abs
import kotlin.math.roundToInt import kotlin.math.roundToInt
@@ -167,6 +172,7 @@ fun LibraryRoute(
onOpenSettings: () -> Unit, onOpenSettings: () -> Unit,
bottomInset: Dp, bottomInset: Dp,
viewModel: LibraryViewModel = koinViewModel(), viewModel: LibraryViewModel = koinViewModel(),
playbackController: PlaybackController = koinInject(),
) { ) {
var selectedTab by rememberSaveable { mutableStateOf(LibraryTab.Albums) } var selectedTab by rememberSaveable { mutableStateOf(LibraryTab.Albums) }
var previousTab 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() val term by viewModel.search(tab.listTab()).collectAsStateWithLifecycle()
"${query.sort}|${query.descending}|${query.starred}|$term" "${query.sort}|${query.descending}|${query.starred}|$term"
} }
val playback by playbackController.state.collectAsStateWithLifecycle()
val artwork = rememberArtworkColors(playbackController.coverArt(playback.item, thumbnail = true))
LibraryScreen( LibraryScreen(
selectedTab = selectedTab, selectedTab = selectedTab,
onTabSelected = { selectedTab = it }, onTabSelected = { selectedTab = it },
syncing = syncing, syncing = syncing,
bottomInset = bottomInset, bottomInset = bottomInset,
artwork = artwork,
albums = viewModel.albums.collectAsLazyPagingItems(), albums = viewModel.albums.collectAsLazyPagingItems(),
artists = viewModel.artists.collectAsLazyPagingItems(), artists = viewModel.artists.collectAsLazyPagingItems(),
songs = viewModel.songs.collectAsLazyPagingItems(), songs = viewModel.songs.collectAsLazyPagingItems(),
@@ -257,6 +266,7 @@ fun LibraryScreen(
onClearFilters: () -> Unit = {}, onClearFilters: () -> Unit = {},
search: String = "", search: String = "",
onSearchChange: (String) -> Unit = {}, onSearchChange: (String) -> Unit = {},
artwork: ArtworkColors? = null,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
val pagerState = val pagerState =
@@ -302,7 +312,7 @@ fun LibraryScreen(
modifier = modifier =
Modifier Modifier
.fillMaxSize() .fillMaxSize()
.background(MaterialTheme.colorScheme.background) .background(artwork?.let(::librarySurfaceColor) ?: MaterialTheme.colorScheme.background)
.nestedScroll(scrollBehavior.nestedScrollConnection), .nestedScroll(scrollBehavior.nestedScrollConnection),
) { ) {
Column( Column(
@@ -419,8 +429,8 @@ fun LibraryScreen(
if (!searchActive) { if (!searchActive) {
FloatingActionButton( FloatingActionButton(
onClick = { showOptions = true }, onClick = { showOptions = true },
containerColor = MaterialTheme.colorScheme.surfaceContainerHigh, containerColor = artwork?.scheme?.primary ?: MaterialTheme.colorScheme.surfaceContainerHigh,
contentColor = MaterialTheme.colorScheme.onBackground, contentColor = artwork?.scheme?.onPrimary ?: MaterialTheme.colorScheme.onBackground,
modifier = Modifier.align(Alignment.BottomEnd).padding(16.dp), modifier = Modifier.align(Alignment.BottomEnd).padding(16.dp),
) { ) {
Box { Box {
@@ -433,7 +443,7 @@ fun LibraryScreen(
.offset(x = 4.dp, y = (-4).dp) .offset(x = 4.dp, y = (-4).dp)
.size(8.dp) .size(8.dp)
.clip(CircleShape) .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_END_SCREENS = 1.5f
private const val DARKEN_KNEE = 0.2f private const val DARKEN_KNEE = 0.2f
private const val PLAYER_SURFACE_SATURATION_FACTOR = 1.6f private const val PLAYER_SURFACE_SATURATION_FACTOR = 1.6f
private const val LIBRARY_SURFACE_LIGHTNESS = 0.10f
@Immutable @Immutable
data class ArtworkColors( 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) 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( fun heroBarColor(
artwork: ArtworkColors?, artwork: ArtworkColors?,
scrollPx: Float, scrollPx: Float,
@@ -117,6 +117,44 @@ class AppScreenshotTest {
composeRule.onRoot().captureRoboImage("src/test/screenshots/library_playlists.png") 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 @Test
fun albumDetail() { fun albumDetail() {
composeRule.setContent { composeRule.setContent {
@@ -396,6 +434,7 @@ class AppScreenshotTest {
onSongClick = {}, onSongClick = {},
onSync = {}, onSync = {},
onOpenSettings = {}, onOpenSettings = {},
artwork = artworkColorsFromSeed(Color.rgb(120, 80, 200)),
) )
} }
} }