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),
|
modifier = Modifier.background(NsBackground).background(bgGlow),
|
||||||
bottomBar = {
|
bottomBar = {
|
||||||
// MiniPlayer shows on EVERY screen (incl. album/artist/playlist detail) so playback
|
// MiniPlayer shows on EVERY screen (incl. album/artist/playlist detail) so playback
|
||||||
// is always reachable; the tab bar stays top-level-only.
|
// is always reachable; the tab bar stays top-level-only. On detail screens there is no
|
||||||
Column {
|
// 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.
|
// ABS audio routes through MusicService — one notification, one mini player.
|
||||||
// When an ABS book is loaded, tapping the mini player opens the ABS overlay
|
// When an ABS book is loaded, tapping the mini player opens the ABS overlay
|
||||||
// instead of the music NowPlaying screen.
|
// 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
|
// 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.
|
// (e.g. drilling into a real album route) so that screen's own back works normally.
|
||||||
val overlayKind = when {
|
val overlayKind = when {
|
||||||
state.artistDetail != null && state.partialAlbum != null -> 4 // artist → partial album
|
state.artistDetail != null && state.partialAlbum != null -> 5 // artist → partial album
|
||||||
state.artistDetail != null && state.albumTracks != null -> 3 // artist → iTunes 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.artistDetail != null -> 2 // iTunes artist
|
||||||
state.albumTracks != null -> 1 // album straight from search
|
state.albumTracks != null -> 1 // album straight from search
|
||||||
else -> 0
|
else -> 0
|
||||||
@@ -71,8 +72,9 @@ fun ExploreScreen(
|
|||||||
DisposableEffect(overlayKind) {
|
DisposableEffect(overlayKind) {
|
||||||
onOverlayBackChange(
|
onOverlayBackChange(
|
||||||
when (overlayKind) {
|
when (overlayKind) {
|
||||||
4 -> vm::dismissPartialAlbum
|
5 -> vm::dismissPartialAlbum
|
||||||
3 -> vm::dismissAlbum
|
4 -> vm::dismissAlbum
|
||||||
|
3 -> vm::closeArtistAllAlbums
|
||||||
2 -> vm::closeArtistDetail
|
2 -> vm::closeArtistDetail
|
||||||
1 -> vm::dismissAlbum
|
1 -> vm::dismissAlbum
|
||||||
else -> null
|
else -> null
|
||||||
@@ -137,6 +139,9 @@ fun ExploreScreen(
|
|||||||
onItunesAlbumOpen = vm::loadAlbum,
|
onItunesAlbumOpen = vm::loadAlbum,
|
||||||
onItunesTrackPreview = vm::togglePreview,
|
onItunesTrackPreview = vm::togglePreview,
|
||||||
previewingId = state.previewingId,
|
previewingId = state.previewingId,
|
||||||
|
allAlbumsOpen = state.artistAllAlbums,
|
||||||
|
onOpenAllAlbums = vm::openArtistAllAlbums,
|
||||||
|
onCloseAllAlbums = vm::closeArtistAllAlbums,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -96,21 +96,44 @@ fun ItunesArtistScreen(
|
|||||||
onItunesAlbumOpen: (ItunesItem) -> Unit,
|
onItunesAlbumOpen: (ItunesItem) -> Unit,
|
||||||
onItunesTrackPreview: (ItunesItem) -> Unit,
|
onItunesTrackPreview: (ItunesItem) -> Unit,
|
||||||
previewingId: Long?,
|
previewingId: Long?,
|
||||||
|
allAlbumsOpen: String?,
|
||||||
|
onOpenAllAlbums: (String) -> Unit,
|
||||||
|
onCloseAllAlbums: () -> Unit,
|
||||||
) {
|
) {
|
||||||
val artist = artistState.artist
|
val artist = artistState.artist
|
||||||
val cats = if (!artistState.isLoading)
|
val cats = if (!artistState.isLoading)
|
||||||
categorizeAlbums(artistState.ndAlbums, artistState.iAlbums)
|
categorizeAlbums(artistState.ndAlbums, artistState.iAlbums)
|
||||||
else null
|
else null
|
||||||
|
|
||||||
var showAllAlbums by remember { mutableStateOf(false) }
|
// "All albums" state is hoisted (ExploreViewModel.artistAllAlbums) so it survives an album
|
||||||
var allAlbumsTitle by remember { mutableStateOf("") }
|
// drill-down and back; entries are re-derived from cats for whichever section is open.
|
||||||
var allAlbumEntries by remember { mutableStateOf<List<AllAlbumEntry>>(emptyList()) }
|
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(
|
AllAlbumsOverlay(
|
||||||
title = allAlbumsTitle,
|
title = allAlbumsOpen,
|
||||||
entries = allAlbumEntries,
|
entries = allAlbumEntries,
|
||||||
onBack = { showAllAlbums = false },
|
onBack = onCloseAllAlbums,
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -224,13 +247,7 @@ fun ItunesArtistScreen(
|
|||||||
if (c.downloaded.isNotEmpty()) {
|
if (c.downloaded.isNotEmpty()) {
|
||||||
item {
|
item {
|
||||||
ArtistSectionHeader("In Library", c.downloaded.size,
|
ArtistSectionHeader("In Library", c.downloaded.size,
|
||||||
onShowAll = if (c.downloaded.size > 4) ({
|
onShowAll = if (c.downloaded.size > 4) ({ onOpenAllAlbums("In Library") }) else null
|
||||||
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
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
item {
|
item {
|
||||||
@@ -255,20 +272,7 @@ fun ItunesArtistScreen(
|
|||||||
if (c.partial.isNotEmpty()) {
|
if (c.partial.isNotEmpty()) {
|
||||||
item {
|
item {
|
||||||
ArtistSectionHeader("Unvollständig", c.partial.size,
|
ArtistSectionHeader("Unvollständig", c.partial.size,
|
||||||
onShowAll = if (c.partial.size > 4) ({
|
onShowAll = if (c.partial.size > 4) ({ onOpenAllAlbums("Unvollständig") }) else null
|
||||||
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
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
item {
|
item {
|
||||||
@@ -295,20 +299,7 @@ fun ItunesArtistScreen(
|
|||||||
if (c.notDownloaded.isNotEmpty()) {
|
if (c.notDownloaded.isNotEmpty()) {
|
||||||
item {
|
item {
|
||||||
ArtistSectionHeader("Nicht heruntergeladen", c.notDownloaded.size,
|
ArtistSectionHeader("Nicht heruntergeladen", c.notDownloaded.size,
|
||||||
onShowAll = if (c.notDownloaded.size > 4) ({
|
onShowAll = if (c.notDownloaded.size > 4) ({ onOpenAllAlbums("Nicht heruntergeladen") }) else null
|
||||||
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
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
item {
|
item {
|
||||||
|
|||||||
@@ -50,6 +50,9 @@ data class ExploreUiState(
|
|||||||
val albumItem: ItunesItem? = null,
|
val albumItem: ItunesItem? = null,
|
||||||
// Artist detail (iTunes)
|
// Artist detail (iTunes)
|
||||||
val artistDetail: ItunesArtistState? = null,
|
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)
|
// Partial album detail (opened from artist's "Unvollständig" section)
|
||||||
val partialAlbum: PartialAlbumState? = null,
|
val partialAlbum: PartialAlbumState? = null,
|
||||||
// Download sheet
|
// Download sheet
|
||||||
@@ -168,9 +171,13 @@ class ExploreViewModel(app: Application) : AndroidViewModel(app) {
|
|||||||
_state.value = _state.value.copy(albumTracks = null, albumItem = null)
|
_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) {
|
fun openArtistDetail(artist: ItunesItem) {
|
||||||
_state.value = _state.value.copy(
|
_state.value = _state.value.copy(
|
||||||
artistDetail = ItunesArtistState(artist = artist, isLoading = true),
|
artistDetail = ItunesArtistState(artist = artist, isLoading = true),
|
||||||
|
artistAllAlbums = null,
|
||||||
)
|
)
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
val artistId = artist.artistId
|
val artistId = artist.artistId
|
||||||
@@ -195,7 +202,7 @@ class ExploreViewModel(app: Application) : AndroidViewModel(app) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun closeArtistDetail() {
|
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) {
|
fun loadPartialAlbum(nd: Album, ia: ItunesItem) {
|
||||||
|
|||||||
Reference in new issue
Block a user