metadata: stack the miniplayer above the library tabs

Render the miniplayer in a column below the screen instead of overlaying it, so
the library tab bar and the list-options FAB are no longer covered. Use Ugress
for the artist screen, drop Various Artists from the home artist rows, and have
the album screen play its first track so the row indicator and the miniplayer
colours match the album.
This commit is contained in:
austinried
2026-10-04 00:30:08 +00:00
parent 0e96b5872c
commit 9976fbeb20
7 changed files with 49 additions and 32 deletions
@@ -5,7 +5,7 @@ import android.graphics.Bitmap
import android.graphics.BitmapFactory import android.graphics.BitmapFactory
import android.graphics.Color import android.graphics.Color
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.Surface import androidx.compose.material3.Surface
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@@ -13,7 +13,6 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.test.junit4.v2.createAndroidComposeRule import androidx.compose.ui.test.junit4.v2.createAndroidComposeRule
import androidx.compose.ui.test.onRoot import androidx.compose.ui.test.onRoot
@@ -141,14 +140,19 @@ class DemoScreenshotTest {
} }
Screen.Albums -> { Screen.Albums -> {
Box(Modifier.fillMaxSize()) { Column(Modifier.fillMaxSize()) {
AlbumsScreen(demo.albums, demo.coverArt, demo.artwork(demo.albums.first().coverArt)) AlbumsScreen(
demo.MiniPlayerOverlay(Modifier.align(Alignment.BottomCenter)) demo.albums,
demo.coverArt,
demo.artwork(demo.albums.first().coverArt),
modifier = Modifier.weight(1f),
)
demo.MiniPlayerOverlay(demo.nowPlayingSong, demo.nowPlayingAlbum)
} }
} }
Screen.AlbumDetail -> { Screen.AlbumDetail -> {
Box(Modifier.fillMaxSize()) { Column(Modifier.fillMaxSize()) {
AlbumDetailScreen( AlbumDetailScreen(
album = demo.album, album = demo.album,
songs = demo.albumSongs, songs = demo.albumSongs,
@@ -156,10 +160,10 @@ class DemoScreenshotTest {
artwork = demo.artwork(demo.album.coverArt), artwork = demo.artwork(demo.album.coverArt),
onBack = {}, onBack = {},
onSongClick = {}, onSongClick = {},
playingSongId = demo.nowPlayingSong.id, playingSongId = demo.albumSongs.first().id,
modifier = Modifier.fillMaxSize(), modifier = Modifier.weight(1f),
) )
demo.MiniPlayerOverlay(Modifier.align(Alignment.BottomCenter)) demo.MiniPlayerOverlay(demo.albumSongs.first(), demo.album)
} }
} }
@@ -178,7 +182,7 @@ class DemoScreenshotTest {
} }
Screen.Artists -> { Screen.Artists -> {
Box(Modifier.fillMaxSize()) { Column(Modifier.fillMaxSize()) {
LibraryScreen( LibraryScreen(
selectedTab = LibraryTab.Artists, selectedTab = LibraryTab.Artists,
onTabSelected = {}, onTabSelected = {},
@@ -192,14 +196,14 @@ class DemoScreenshotTest {
onSync = {}, onSync = {},
onOpenSettings = {}, onOpenSettings = {},
artwork = demo.artwork(demo.artists.first().coverArt), artwork = demo.artwork(demo.artists.first().coverArt),
modifier = Modifier.fillMaxSize(), modifier = Modifier.weight(1f),
) )
demo.MiniPlayerOverlay(Modifier.align(Alignment.BottomCenter)) demo.MiniPlayerOverlay(demo.nowPlayingSong, demo.nowPlayingAlbum)
} }
} }
Screen.Search -> { Screen.Search -> {
Box(Modifier.fillMaxSize()) { Column(Modifier.fillMaxSize()) {
LibraryScreen( LibraryScreen(
selectedTab = LibraryTab.Albums, selectedTab = LibraryTab.Albums,
onTabSelected = {}, onTabSelected = {},
@@ -218,9 +222,9 @@ class DemoScreenshotTest {
search = DEMO_SEARCH, search = DEMO_SEARCH,
onSearchChange = {}, onSearchChange = {},
artwork = demo.artwork(demo.searchAlbums.first().coverArt), artwork = demo.artwork(demo.searchAlbums.first().coverArt),
modifier = Modifier.fillMaxSize(), modifier = Modifier.weight(1f),
) )
demo.MiniPlayerOverlay(Modifier.align(Alignment.BottomCenter)) demo.MiniPlayerOverlay(demo.nowPlayingSong, demo.nowPlayingAlbum)
} }
} }
} }
@@ -242,6 +246,7 @@ class DemoScreenshotTest {
albums: List<Album>, albums: List<Album>,
coverArt: (String?, Boolean) -> CoverArtRef?, coverArt: (String?, Boolean) -> CoverArtRef?,
artwork: ArtworkColors, artwork: ArtworkColors,
modifier: Modifier = Modifier,
) { ) {
LibraryScreen( LibraryScreen(
selectedTab = LibraryTab.Albums, selectedTab = LibraryTab.Albums,
@@ -259,7 +264,7 @@ class DemoScreenshotTest {
sortOptions = sortOptionsFor(LibraryTab.Albums), sortOptions = sortOptionsFor(LibraryTab.Albums),
starredSupported = true, starredSupported = true,
artwork = artwork, artwork = artwork,
modifier = Modifier.fillMaxSize(), modifier = modifier,
) )
} }
@@ -276,12 +281,16 @@ class DemoScreenshotTest {
} }
@Composable @Composable
private fun DemoData.MiniPlayerOverlay(modifier: Modifier = Modifier) { private fun DemoData.MiniPlayerOverlay(
song: Song,
album: Album,
modifier: Modifier = Modifier,
) {
MiniPlayer( MiniPlayer(
state = playbackState, state = playbackFor(song, album),
positionMs = positionMs, positionMs = positionFor(song),
coverArt = coverArt(nowPlayingAlbum.coverArt, true), coverArt = coverArt(album.coverArt, true),
artwork = artwork(nowPlayingAlbum.coverArt), artwork = artwork(album.coverArt),
onExpand = {}, onExpand = {},
onPlayPause = {}, onPlayPause = {},
onNext = {}, onNext = {},
@@ -308,23 +317,29 @@ class DemoScreenshotTest {
val engine: FakeImageLoaderEngine, val engine: FakeImageLoaderEngine,
private val bitmaps: Map<String, Bitmap>, private val bitmaps: Map<String, Bitmap>,
) { ) {
val playbackState = val playbackState = playbackFor(nowPlayingSong, nowPlayingAlbum)
val positionMs: Long = positionFor(nowPlayingSong)
fun playbackFor(
song: Song,
album: Album,
): PlaybackState =
PlaybackState( PlaybackState(
item = item =
QueueItem( QueueItem(
id = nowPlayingSong.id, id = song.id,
title = nowPlayingSong.title, title = song.title,
artist = nowPlayingSong.artist, artist = song.artist,
album = nowPlayingSong.album, album = song.album,
coverArtId = nowPlayingAlbum.coverArt, coverArtId = album.coverArt,
), ),
isPlaying = true, isPlaying = true,
durationMs = (nowPlayingSong.duration ?: 0) * 1000, durationMs = (song.duration ?: 0) * 1000,
hasNext = true, hasNext = true,
hasPrevious = false, hasPrevious = false,
) )
val positionMs: Long = (nowPlayingSong.duration ?: 0) * 1000 * 35 / 100 fun positionFor(song: Song): Long = (song.duration ?: 0) * 1000 * 35 / 100
fun artwork(coverArtId: String?): ArtworkColors { fun artwork(coverArtId: String?): ArtworkColors {
val bitmap = coverArtId?.let(bitmaps::get) ?: return artworkColorsFromSeeds(FALLBACK_SEED, null) val bitmap = coverArtId?.let(bitmaps::get) ?: return artworkColorsFromSeeds(FALLBACK_SEED, null)
@@ -381,16 +396,18 @@ class DemoScreenshotTest {
val homePlayingAlbum = albums.firstOrNull { it.name == "Shaking The Habitual" } ?: albums.last() val homePlayingAlbum = albums.firstOrNull { it.name == "Shaking The Habitual" } ?: albums.last()
val homePlayingSong = dao.songsByAlbum(1, homePlayingAlbum.id).first().first() val homePlayingSong = dao.songsByAlbum(1, homePlayingAlbum.id).first().first()
val artist = val artist =
artists.maxByOrNull { it.albumCount } artists.firstOrNull { it.name == "Ugress" }
?: artists.maxByOrNull { it.albumCount }
?: artists.first() ?: artists.first()
val artistAlbums = dao.albumsForArtist(1, artist.id).first() val artistAlbums = dao.albumsForArtist(1, artist.id).first()
fun namedArtists(artists: List<Artist>) = artists.filterNot { it.name.equals("Various Artists", ignoreCase = true) }
val feed = val feed =
HomeFeed( HomeFeed(
recentlyPlayedAlbums = dao.recentlyPlayedAlbums(1, 10).first(), recentlyPlayedAlbums = dao.recentlyPlayedAlbums(1, 10).first(),
recentlyPlayedArtists = dao.recentlyPlayedArtists(1, 10).first(), recentlyPlayedArtists = namedArtists(dao.recentlyPlayedArtists(1, 10).first()),
mostPlayedAlbums = dao.mostPlayedAlbums(1, 10).first(), mostPlayedAlbums = dao.mostPlayedAlbums(1, 10).first(),
mostPlayedArtists = dao.mostPlayedArtists(1, 10).first(), mostPlayedArtists = namedArtists(dao.mostPlayedArtists(1, 10).first()),
genres = dao.genresByMostPlayed(1).first(), genres = dao.genresByMostPlayed(1).first(),
decades = dao.decades(1).first(), decades = dao.decades(1).first(),
recentlyStarredSongs = dao.recentlyStarredSongs(1, 10).first(), recentlyStarredSongs = dao.recentlyStarredSongs(1, 10).first(),
Binary file not shown.

Before

Width:  |  Height:  |  Size: 483 KiB

After

Width:  |  Height:  |  Size: 484 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 993 KiB

After

Width:  |  Height:  |  Size: 867 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 476 KiB

After

Width:  |  Height:  |  Size: 496 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 MiB

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 161 KiB

After

Width:  |  Height:  |  Size: 159 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 313 KiB

After

Width:  |  Height:  |  Size: 319 KiB