overlay a translucent header onto the library content

This commit is contained in:
austinried
2026-10-04 00:29:57 +00:00
parent 40f20eaa40
commit ab39fc259e
6 changed files with 125 additions and 81 deletions
@@ -43,6 +43,7 @@ fun AlbumsContent(
filtered: Boolean = false, filtered: Boolean = false,
onClearFilters: () -> Unit = {}, onClearFilters: () -> Unit = {},
resetKey: Any? = null, resetKey: Any? = null,
topInset: Dp = 0.dp,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
when { when {
@@ -65,7 +66,7 @@ fun AlbumsContent(
LazyVerticalGrid( LazyVerticalGrid(
state = gridState, state = gridState,
columns = GridCells.Fixed(3), columns = GridCells.Fixed(3),
contentPadding = PaddingValues(start = 8.dp, top = 8.dp, end = 8.dp, bottom = bottomInset + 8.dp), contentPadding = PaddingValues(start = 8.dp, top = topInset + 8.dp, end = 8.dp, bottom = bottomInset + 8.dp),
horizontalArrangement = Arrangement.spacedBy(4.dp), horizontalArrangement = Arrangement.spacedBy(4.dp),
verticalArrangement = Arrangement.spacedBy(4.dp), verticalArrangement = Arrangement.spacedBy(4.dp),
modifier = modifier.fillMaxSize(), modifier = modifier.fillMaxSize(),
@@ -41,6 +41,7 @@ fun ArtistsContent(
filtered: Boolean = false, filtered: Boolean = false,
onClearFilters: () -> Unit = {}, onClearFilters: () -> Unit = {},
resetKey: Any? = null, resetKey: Any? = null,
topInset: Dp = 0.dp,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
when { when {
@@ -63,7 +64,7 @@ fun ArtistsContent(
LazyColumn( LazyColumn(
state = listState, state = listState,
modifier = modifier.fillMaxSize(), modifier = modifier.fillMaxSize(),
contentPadding = PaddingValues(bottom = bottomInset), contentPadding = PaddingValues(top = topInset, bottom = bottomInset),
) { ) {
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]
@@ -91,6 +91,7 @@ import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.layout.boundsInParent import androidx.compose.ui.layout.boundsInParent
import androidx.compose.ui.layout.layout import androidx.compose.ui.layout.layout
import androidx.compose.ui.layout.onGloballyPositioned import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.semantics.clearAndSetSemantics import androidx.compose.ui.semantics.clearAndSetSemantics
import androidx.compose.ui.unit.Constraints import androidx.compose.ui.unit.Constraints
@@ -312,33 +313,18 @@ fun LibraryScreen(
val filtersActive = listQuery.starred != StarredFilter.Any || search.isNotEmpty() val filtersActive = listQuery.starred != StarredFilter.Any || search.isNotEmpty()
val listBottomInset = bottomInset + if (searchActive) SEARCH_BAR_CLEARANCE else FAB_CLEARANCE val listBottomInset = bottomInset + if (searchActive) SEARCH_BAR_CLEARANCE else FAB_CLEARANCE
Box(modifier.fillMaxSize()) { var headerHeightPx by remember { mutableFloatStateOf(0f) }
Column( val listTopInset = with(density) { headerHeightPx.toDp() }
Box(
modifier = modifier =
Modifier modifier
.fillMaxSize() .fillMaxSize()
.background(artwork?.let(::librarySurfaceColor) ?: MaterialTheme.colorScheme.background), .background(artwork?.let(::librarySurfaceColor) ?: MaterialTheme.colorScheme.background),
) { ) {
Column(
modifier =
Modifier
.fillMaxWidth()
.background(artwork?.let(::playerSurfaceColor) ?: MaterialTheme.colorScheme.background)
.padding(top = statusBarTop + 2.dp, bottom = 2.dp),
) {
LibraryTabs(
pagerState = pagerState,
onTabSelected = onTabSelected,
syncing = syncing,
onSync = onSync,
onOpenSettings = onOpenSettings,
artwork = artwork,
)
}
HorizontalPager( HorizontalPager(
state = pagerState, state = pagerState,
modifier = Modifier.fillMaxWidth().weight(1f), modifier = Modifier.fillMaxSize(),
) { page -> ) { page ->
val pageTab = LibraryTab.entries[page] val pageTab = LibraryTab.entries[page]
val resetKey = resetKeys[pageTab] val resetKey = resetKeys[pageTab]
@@ -352,6 +338,7 @@ fun LibraryScreen(
filtered = filtersActive, filtered = filtersActive,
onClearFilters = onClearFilters, onClearFilters = onClearFilters,
resetKey = resetKey, resetKey = resetKey,
topInset = listTopInset,
) )
} }
@@ -364,6 +351,7 @@ fun LibraryScreen(
filtered = filtersActive, filtered = filtersActive,
onClearFilters = onClearFilters, onClearFilters = onClearFilters,
resetKey = resetKey, resetKey = resetKey,
topInset = listTopInset,
) )
} }
@@ -377,6 +365,7 @@ fun LibraryScreen(
filtered = filtersActive, filtered = filtersActive,
onClearFilters = onClearFilters, onClearFilters = onClearFilters,
resetKey = resetKey, resetKey = resetKey,
topInset = listTopInset,
) )
} }
@@ -389,10 +378,31 @@ fun LibraryScreen(
filtered = filtersActive, filtered = filtersActive,
onClearFilters = onClearFilters, onClearFilters = onClearFilters,
resetKey = resetKey, resetKey = resetKey,
topInset = listTopInset,
) )
} }
} }
} }
Column(
modifier =
Modifier
.fillMaxWidth()
.align(Alignment.TopStart)
.onSizeChanged { headerHeightPx = it.height.toFloat() }
.background(
(artwork?.let(::playerSurfaceColor) ?: MaterialTheme.colorScheme.background)
.copy(alpha = HEADER_ALPHA),
).padding(top = statusBarTop + 2.dp, bottom = 2.dp),
) {
LibraryTabs(
pagerState = pagerState,
onTabSelected = onTabSelected,
syncing = syncing,
onSync = onSync,
onOpenSettings = onOpenSettings,
artwork = artwork,
)
} }
if (searchActive) { if (searchActive) {
@@ -582,6 +592,7 @@ private fun ListOptionsSheet(
} }
private val FAB_CLEARANCE = 80.dp private val FAB_CLEARANCE = 80.dp
private const val HEADER_ALPHA = 0.72f
private val SEARCH_BAR_CLEARANCE = 80.dp private val SEARCH_BAR_CLEARANCE = 80.dp
@Composable @Composable
@@ -40,6 +40,7 @@ fun PlaylistsContent(
filtered: Boolean = false, filtered: Boolean = false,
onClearFilters: () -> Unit = {}, onClearFilters: () -> Unit = {},
resetKey: Any? = null, resetKey: Any? = null,
topInset: Dp = 0.dp,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
when { when {
@@ -62,7 +63,7 @@ fun PlaylistsContent(
LazyColumn( LazyColumn(
state = listState, state = listState,
modifier = modifier.fillMaxSize(), modifier = modifier.fillMaxSize(),
contentPadding = PaddingValues(bottom = bottomInset), contentPadding = PaddingValues(top = topInset, bottom = bottomInset),
) { ) {
items(count = items.itemCount, key = items.itemKey { it.id }) { index -> items(count = items.itemCount, key = items.itemKey { it.id }) { index ->
val playlist = items[index] val playlist = items[index]
@@ -52,6 +52,7 @@ fun SongsContent(
filtered: Boolean = false, filtered: Boolean = false,
onClearFilters: () -> Unit = {}, onClearFilters: () -> Unit = {},
resetKey: Any? = null, resetKey: Any? = null,
topInset: Dp = 0.dp,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
when { when {
@@ -74,7 +75,7 @@ fun SongsContent(
LazyColumn( LazyColumn(
state = listState, state = listState,
modifier = modifier.fillMaxSize(), modifier = modifier.fillMaxSize(),
contentPadding = PaddingValues(bottom = bottomInset), contentPadding = PaddingValues(top = topInset, bottom = bottomInset),
) { ) {
items(count = items.itemCount, key = items.itemKey { it.song.id }) { index -> items(count = items.itemCount, key = items.itemKey { it.song.id }) { index ->
val item = items[index] val item = items[index]
@@ -96,6 +96,35 @@ class AppScreenshotTest {
composeRule.onRoot().captureRoboImage("src/test/screenshots/library_albums.png") composeRule.onRoot().captureRoboImage("src/test/screenshots/library_albums.png")
} }
@Test
fun libraryScrolledUnderHeader() {
val albums = (1..60).map { Fixtures.albums.first().copy(id = "scrolled-$it", name = "Album $it") }
composeRule.setContent {
SubtracksTheme {
LibraryScreen(
selectedTab = LibraryTab.Albums,
onTabSelected = {},
albums = remember { flowOf(PagingData.from(albums)) }.collectAsLazyPagingItems(),
artists = remember { flowOf(PagingData.empty<Artist>()) }.collectAsLazyPagingItems(),
songs = remember { flowOf(PagingData.empty<SongListItem>()) }.collectAsLazyPagingItems(),
playlists = remember { flowOf(PagingData.empty<Playlist>()) }.collectAsLazyPagingItems(),
coverArt = { id, _ -> id?.let { CoverArtRef(it, "test:$it") } },
onAlbumClick = {},
onArtistClick = {},
onPlaylistClick = {},
onSongClick = {},
onSync = {},
onOpenSettings = {},
artwork = artworkColorsFromSeed(Color.rgb(120, 80, 200)),
)
}
}
awaitTag(ALBUM_COVER_TAG)
composeRule.onRoot().performTouchInput { swipeUp(startY = centerY + 600f, endY = centerY - 600f, durationMillis = 400) }
composeRule.waitForIdle()
composeRule.onRoot().captureRoboImage("src/test/screenshots/library_scrolled_under_header.png")
}
@Test @Test
fun libraryArtists() { fun libraryArtists() {
setLibraryContent(LibraryTab.Artists) setLibraryContent(LibraryTab.Artists)