fix(android): playbar clears system nav bar on detail screens; Erkunden all-albums survives drill-down
- On detail screens (album/artist/playlist) the MiniPlayer was the bottom-most bar with no NavigationBar to absorb the system-navigation inset, so it sat behind the phone's gesture/nav bar. Pad the bar with navigationBarsPadding() when the tab bar is absent. - The Erkunden (iTunes) artist "show all albums" view used local remember state, so drilling into an album and back reset it to the main artist view. Hoist the open section into ExploreViewModel.artistAllAlbums so it survives both nav-route and overlay round-trips, and wire its back into the root overlay-back handler. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
1 parent
e50b4dc93c
commit
9270d31608
4 files changed
+53
-48
No files matched your search
@@ -169,8 +169,10 @@ fun AppNavigation() {
|
||||
modifier = Modifier.background(NsBackground).background(bgGlow),
|
||||
bottomBar = {
|
||||
// MiniPlayer shows on EVERY screen (incl. album/artist/playlist detail) so playback
|
||||
// is always reachable; the tab bar stays top-level-only.
|
||||
Column {
|
||||
// is always reachable; the tab bar stays top-level-only. On detail screens there is no
|
||||
// NavigationBar to absorb the system-navigation inset, so pad the bar itself — otherwise
|
||||
// the MiniPlayer sits behind the phone's gesture/nav bar.
|
||||
Column(modifier = if (isTopLevel) Modifier else Modifier.navigationBarsPadding()) {
|
||||
// ABS audio routes through MusicService — one notification, one mini player.
|
||||
// When an ABS book is loaded, tapping the mini player opens the ABS overlay
|
||||
// instead of the music NowPlaying screen.
|
||||
|
||||
@@ -62,8 +62,9 @@ fun ExploreScreen(
|
||||
// Report the current overlay's close action up; clear it when this screen leaves composition
|
||||
// (e.g. drilling into a real album route) so that screen's own back works normally.
|
||||
val overlayKind = when {
|
||||
state.artistDetail != null && state.partialAlbum != null -> 4 // artist → partial album
|
||||
state.artistDetail != null && state.albumTracks != null -> 3 // artist → iTunes album
|
||||
state.artistDetail != null && state.partialAlbum != null -> 5 // artist → partial album
|
||||
state.artistDetail != null && state.albumTracks != null -> 4 // artist → iTunes album
|
||||
state.artistDetail != null && state.artistAllAlbums != null -> 3 // artist → all-albums grid
|
||||
state.artistDetail != null -> 2 // iTunes artist
|
||||
state.albumTracks != null -> 1 // album straight from search
|
||||
else -> 0
|
||||
@@ -71,8 +72,9 @@ fun ExploreScreen(
|
||||
DisposableEffect(overlayKind) {
|
||||
onOverlayBackChange(
|
||||
when (overlayKind) {
|
||||
4 -> vm::dismissPartialAlbum
|
||||
3 -> vm::dismissAlbum
|
||||
5 -> vm::dismissPartialAlbum
|
||||
4 -> vm::dismissAlbum
|
||||
3 -> vm::closeArtistAllAlbums
|
||||
2 -> vm::closeArtistDetail
|
||||
1 -> vm::dismissAlbum
|
||||
else -> null
|
||||
@@ -137,6 +139,9 @@ fun ExploreScreen(
|
||||
onItunesAlbumOpen = vm::loadAlbum,
|
||||
onItunesTrackPreview = vm::togglePreview,
|
||||
previewingId = state.previewingId,
|
||||
allAlbumsOpen = state.artistAllAlbums,
|
||||
onOpenAllAlbums = vm::openArtistAllAlbums,
|
||||
onCloseAllAlbums = vm::closeArtistAllAlbums,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,21 +96,44 @@ fun ItunesArtistScreen(
|
||||
onItunesAlbumOpen: (ItunesItem) -> Unit,
|
||||
onItunesTrackPreview: (ItunesItem) -> Unit,
|
||||
previewingId: Long?,
|
||||
allAlbumsOpen: String?,
|
||||
onOpenAllAlbums: (String) -> Unit,
|
||||
onCloseAllAlbums: () -> Unit,
|
||||
) {
|
||||
val artist = artistState.artist
|
||||
val cats = if (!artistState.isLoading)
|
||||
categorizeAlbums(artistState.ndAlbums, artistState.iAlbums)
|
||||
else null
|
||||
|
||||
var showAllAlbums by remember { mutableStateOf(false) }
|
||||
var allAlbumsTitle by remember { mutableStateOf("") }
|
||||
var allAlbumEntries by remember { mutableStateOf<List<AllAlbumEntry>>(emptyList()) }
|
||||
// "All albums" state is hoisted (ExploreViewModel.artistAllAlbums) so it survives an album
|
||||
// drill-down and back; entries are re-derived from cats for whichever section is open.
|
||||
val allAlbumEntries: List<AllAlbumEntry> = when (allAlbumsOpen) {
|
||||
"In Library" -> cats?.downloaded?.map { (nd, _) ->
|
||||
AllAlbumEntry(name = nd.name, year = nd.year?.toString(), artUrl = coverUrl(nd.coverArt), onClick = { onAlbumClick(nd) })
|
||||
}.orEmpty()
|
||||
"Unvollständig" -> cats?.partial?.map { (nd, ia, counts) ->
|
||||
AllAlbumEntry(
|
||||
name = nd.name, year = nd.year?.toString(),
|
||||
artUrl = ia.artworkUrl100?.replace("100x100bb", "300x300bb") ?: coverUrl(nd.coverArt),
|
||||
badge = "${counts.first}/${counts.second}",
|
||||
onClick = { onPartialAlbumOpen(nd, ia) }, onDownload = { onDownload(ia) },
|
||||
)
|
||||
}.orEmpty()
|
||||
"Nicht heruntergeladen" -> cats?.notDownloaded?.map { ia ->
|
||||
AllAlbumEntry(
|
||||
name = ia.collectionName ?: "—", year = ia.releaseDate?.take(4),
|
||||
artUrl = ia.artworkUrl100?.replace("100x100bb", "300x300bb"), dimmed = true,
|
||||
onClick = { onItunesAlbumOpen(ia) }, onDownload = { onDownload(ia) },
|
||||
)
|
||||
}.orEmpty()
|
||||
else -> emptyList()
|
||||
}
|
||||
|
||||
if (showAllAlbums) {
|
||||
if (allAlbumsOpen != null) {
|
||||
AllAlbumsOverlay(
|
||||
title = allAlbumsTitle,
|
||||
title = allAlbumsOpen,
|
||||
entries = allAlbumEntries,
|
||||
onBack = { showAllAlbums = false },
|
||||
onBack = onCloseAllAlbums,
|
||||
)
|
||||
return
|
||||
}
|
||||
@@ -224,13 +247,7 @@ fun ItunesArtistScreen(
|
||||
if (c.downloaded.isNotEmpty()) {
|
||||
item {
|
||||
ArtistSectionHeader("In Library", c.downloaded.size,
|
||||
onShowAll = if (c.downloaded.size > 4) ({
|
||||
allAlbumsTitle = "In Library"
|
||||
allAlbumEntries = c.downloaded.map { (nd, _) ->
|
||||
AllAlbumEntry(name = nd.name, year = nd.year?.toString(), artUrl = coverUrl(nd.coverArt), onClick = { onAlbumClick(nd) })
|
||||
}
|
||||
showAllAlbums = true
|
||||
}) else null
|
||||
onShowAll = if (c.downloaded.size > 4) ({ onOpenAllAlbums("In Library") }) else null
|
||||
)
|
||||
}
|
||||
item {
|
||||
@@ -255,20 +272,7 @@ fun ItunesArtistScreen(
|
||||
if (c.partial.isNotEmpty()) {
|
||||
item {
|
||||
ArtistSectionHeader("Unvollständig", c.partial.size,
|
||||
onShowAll = if (c.partial.size > 4) ({
|
||||
allAlbumsTitle = "Unvollständig"
|
||||
allAlbumEntries = c.partial.map { (nd, ia, counts) ->
|
||||
AllAlbumEntry(
|
||||
name = nd.name,
|
||||
year = nd.year?.toString(),
|
||||
artUrl = ia.artworkUrl100?.replace("100x100bb", "300x300bb") ?: coverUrl(nd.coverArt),
|
||||
badge = "${counts.first}/${counts.second}",
|
||||
onClick = { onPartialAlbumOpen(nd, ia) },
|
||||
onDownload = { onDownload(ia) },
|
||||
)
|
||||
}
|
||||
showAllAlbums = true
|
||||
}) else null
|
||||
onShowAll = if (c.partial.size > 4) ({ onOpenAllAlbums("Unvollständig") }) else null
|
||||
)
|
||||
}
|
||||
item {
|
||||
@@ -295,20 +299,7 @@ fun ItunesArtistScreen(
|
||||
if (c.notDownloaded.isNotEmpty()) {
|
||||
item {
|
||||
ArtistSectionHeader("Nicht heruntergeladen", c.notDownloaded.size,
|
||||
onShowAll = if (c.notDownloaded.size > 4) ({
|
||||
allAlbumsTitle = "Nicht heruntergeladen"
|
||||
allAlbumEntries = c.notDownloaded.map { ia ->
|
||||
AllAlbumEntry(
|
||||
name = ia.collectionName ?: "—",
|
||||
year = ia.releaseDate?.take(4),
|
||||
artUrl = ia.artworkUrl100?.replace("100x100bb", "300x300bb"),
|
||||
dimmed = true,
|
||||
onClick = { onItunesAlbumOpen(ia) },
|
||||
onDownload = { onDownload(ia) },
|
||||
)
|
||||
}
|
||||
showAllAlbums = true
|
||||
}) else null
|
||||
onShowAll = if (c.notDownloaded.size > 4) ({ onOpenAllAlbums("Nicht heruntergeladen") }) else null
|
||||
)
|
||||
}
|
||||
item {
|
||||
|
||||
@@ -50,6 +50,9 @@ data class ExploreUiState(
|
||||
val albumItem: ItunesItem? = null,
|
||||
// Artist detail (iTunes)
|
||||
val artistDetail: ItunesArtistState? = null,
|
||||
// Which "all albums" section is expanded inside the iTunes artist overlay (by title), or null.
|
||||
// Hoisted here so it survives drilling into an album (nav route OR overlay) and coming back.
|
||||
val artistAllAlbums: String? = null,
|
||||
// Partial album detail (opened from artist's "Unvollständig" section)
|
||||
val partialAlbum: PartialAlbumState? = null,
|
||||
// Download sheet
|
||||
@@ -168,9 +171,13 @@ class ExploreViewModel(app: Application) : AndroidViewModel(app) {
|
||||
_state.value = _state.value.copy(albumTracks = null, albumItem = null)
|
||||
}
|
||||
|
||||
fun openArtistAllAlbums(title: String) { _state.value = _state.value.copy(artistAllAlbums = title) }
|
||||
fun closeArtistAllAlbums() { _state.value = _state.value.copy(artistAllAlbums = null) }
|
||||
|
||||
fun openArtistDetail(artist: ItunesItem) {
|
||||
_state.value = _state.value.copy(
|
||||
artistDetail = ItunesArtistState(artist = artist, isLoading = true),
|
||||
artistAllAlbums = null,
|
||||
)
|
||||
viewModelScope.launch {
|
||||
val artistId = artist.artistId
|
||||
@@ -195,7 +202,7 @@ class ExploreViewModel(app: Application) : AndroidViewModel(app) {
|
||||
}
|
||||
|
||||
fun closeArtistDetail() {
|
||||
_state.value = _state.value.copy(artistDetail = null, partialAlbum = null)
|
||||
_state.value = _state.value.copy(artistDetail = null, partialAlbum = null, artistAllAlbums = null)
|
||||
}
|
||||
|
||||
fun loadPartialAlbum(nd: Album, ia: ItunesItem) {
|
||||
|
||||
Reference in new issue
Block a user