diff --git a/android/app/src/main/kotlin/com/novasonic/app/ui/AppNavigation.kt b/android/app/src/main/kotlin/com/novasonic/app/ui/AppNavigation.kt index 13a768a..a2c693a 100644 --- a/android/app/src/main/kotlin/com/novasonic/app/ui/AppNavigation.kt +++ b/android/app/src/main/kotlin/com/novasonic/app/ui/AppNavigation.kt @@ -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. diff --git a/android/app/src/main/kotlin/com/novasonic/app/ui/screen/ExploreScreen.kt b/android/app/src/main/kotlin/com/novasonic/app/ui/screen/ExploreScreen.kt index 66019dc..f305195 100644 --- a/android/app/src/main/kotlin/com/novasonic/app/ui/screen/ExploreScreen.kt +++ b/android/app/src/main/kotlin/com/novasonic/app/ui/screen/ExploreScreen.kt @@ -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, ) } } diff --git a/android/app/src/main/kotlin/com/novasonic/app/ui/screen/ItunesArtistScreen.kt b/android/app/src/main/kotlin/com/novasonic/app/ui/screen/ItunesArtistScreen.kt index eab8bd8..7a693c7 100644 --- a/android/app/src/main/kotlin/com/novasonic/app/ui/screen/ItunesArtistScreen.kt +++ b/android/app/src/main/kotlin/com/novasonic/app/ui/screen/ItunesArtistScreen.kt @@ -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>(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 = 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 { diff --git a/android/app/src/main/kotlin/com/novasonic/app/viewmodel/ExploreViewModel.kt b/android/app/src/main/kotlin/com/novasonic/app/viewmodel/ExploreViewModel.kt index 961308c..abc3b65 100644 --- a/android/app/src/main/kotlin/com/novasonic/app/viewmodel/ExploreViewModel.kt +++ b/android/app/src/main/kotlin/com/novasonic/app/viewmodel/ExploreViewModel.kt @@ -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) {