diff --git a/app/src/main/java/org/oxycblt/auxio/detail/AlbumDetailFragment.kt b/app/src/main/java/org/oxycblt/auxio/detail/AlbumDetailFragment.kt index 4fe81945a..69c5b40fc 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/AlbumDetailFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/AlbumDetailFragment.kt @@ -69,6 +69,7 @@ class AlbumDetailFragment : DetailFragment(), AlbumDetailAdapter.Listener { // -- VIEWMODEL SETUP --- + launch { detailModel.currentAlbum.collect(::handleItemChange) } launch { detailModel.albumData.collect(detailAdapter.data::submitList) } launch { navModel.exploreNavigationItem.collect(::handleNavigation) } launch { playbackModel.song.collectWith(playbackModel.parent, ::updatePlayback) } @@ -127,6 +128,12 @@ class AlbumDetailFragment : DetailFragment(), AlbumDetailAdapter.Listener { unlikelyToBeNull(detailModel.currentAlbum.value).artist.id)) } + private fun handleItemChange(album: Album?) { + if (album == null) { + findNavController().navigateUp() + } + } + private fun handleNavigation(item: Music?) { val binding = requireBinding() when (item) { diff --git a/app/src/main/java/org/oxycblt/auxio/detail/ArtistDetailFragment.kt b/app/src/main/java/org/oxycblt/auxio/detail/ArtistDetailFragment.kt index e6f8c8179..ad63576c3 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/ArtistDetailFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/ArtistDetailFragment.kt @@ -66,6 +66,7 @@ class ArtistDetailFragment : DetailFragment(), DetailAdapter.Listener { // --- VIEWMODEL SETUP --- + launch { detailModel.currentArtist.collect(::handleItemChange) } launch { detailModel.artistData.collect(detailAdapter.data::submitList) } launch { navModel.exploreNavigationItem.collect(::handleNavigation) } launch { playbackModel.song.collectWith(playbackModel.parent, ::updatePlayback) } @@ -104,6 +105,12 @@ class ArtistDetailFragment : DetailFragment(), DetailAdapter.Listener { }) } + private fun handleItemChange(artist: Artist?) { + if (artist == null) { + findNavController().navigateUp() + } + } + private fun handleNavigation(item: Music?) { val binding = requireBinding() diff --git a/app/src/main/java/org/oxycblt/auxio/detail/DetailViewModel.kt b/app/src/main/java/org/oxycblt/auxio/detail/DetailViewModel.kt index 3e1c5bf4d..6e05ffa5e 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/DetailViewModel.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/DetailViewModel.kt @@ -41,7 +41,7 @@ import org.oxycblt.auxio.util.unlikelyToBeNull * - Menu triggers for each fragment * @author OxygenCobalt */ -class DetailViewModel : ViewModel() { +class DetailViewModel : ViewModel(), MusicStore.Callback { private val musicStore = MusicStore.getInstance() private val settingsManager = SettingsManager.getInstance() @@ -116,6 +116,10 @@ class DetailViewModel : ViewModel() { refreshGenreData(genre) } + init { + musicStore.addCallback(this) + } + private fun refreshAlbumData(album: Album) { logD("Refreshing album data") val data = mutableListOf(album) @@ -157,4 +161,38 @@ class DetailViewModel : ViewModel() { data.addAll(genreSort.songs(genre.songs)) _genreData.value = data } + + // --- CALLBACKS --- + + override fun onLibraryChanged(library: MusicStore.Library?) { + if (library != null) { + val album = currentAlbum.value + if (album != null) { + val newAlbum = library.sanitize(album).also { _currentAlbum.value = it } + if (newAlbum != null) { + refreshAlbumData(newAlbum) + } + } + + val artist = currentArtist.value + if (artist != null) { + val newArtist = library.sanitize(artist).also { _currentArtist.value = it } + if (newArtist != null) { + refreshArtistData(newArtist) + } + } + + val genre = currentGenre.value + if (genre != null) { + val newGenre = library.sanitize(genre).also { _currentGenre.value = it } + if (newGenre != null) { + refreshGenreData(newGenre) + } + } + } + } + + override fun onCleared() { + musicStore.removeCallback(this) + } } diff --git a/app/src/main/java/org/oxycblt/auxio/detail/GenreDetailFragment.kt b/app/src/main/java/org/oxycblt/auxio/detail/GenreDetailFragment.kt index 6c8c911a4..346eb5a77 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/GenreDetailFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/GenreDetailFragment.kt @@ -65,6 +65,7 @@ class GenreDetailFragment : DetailFragment(), DetailAdapter.Listener { // --- VIEWMODEL SETUP --- + launch { detailModel.currentGenre.collect(::handleItemChange) } launch { detailModel.genreData.collect(detailAdapter.data::submitList) } launch { navModel.exploreNavigationItem.collect(::handleNavigation) } launch { playbackModel.song.collectWith(playbackModel.parent, ::updatePlayback) } @@ -101,6 +102,12 @@ class GenreDetailFragment : DetailFragment(), DetailAdapter.Listener { showItem = { it != R.id.option_sort_disc && it != R.id.option_sort_track }) } + private fun handleItemChange(genre: Genre?) { + if (genre == null) { + findNavController().navigateUp() + } + } + private fun handleNavigation(item: Music?) { when (item) { is Song -> { diff --git a/app/src/main/java/org/oxycblt/auxio/music/Indexer.kt b/app/src/main/java/org/oxycblt/auxio/music/Indexer.kt index 8ed8f1a21..79beedc43 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/Indexer.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/Indexer.kt @@ -148,7 +148,8 @@ class Indexer { // If we have canceled the loading process, we want to revert to a previous completion // whenever possible to prevent state inconsistency. val state = - indexingState?.let { State.Indexing(it) } ?: lastResponse?.let { State.Complete(it) } + indexingState?.let { State.Indexing(it) } + ?: lastResponse?.let { State.Complete(it) } for (callback in callbacks) { callback.onIndexerStateChanged(state) @@ -180,7 +181,8 @@ class Indexer { emitIndexing(Indexing.Indeterminate, generation) // Establish the backend to use when initially loading songs. - val mediaStoreBackend = when { + val mediaStoreBackend = + when { Build.VERSION.SDK_INT >= Build.VERSION_CODES.R -> Api30MediaStoreBackend() Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q -> Api29MediaStoreBackend() else -> Api21MediaStoreBackend() @@ -229,7 +231,9 @@ class Indexer { "Successfully queried media database " + "in ${System.currentTimeMillis() - start}ms") - backend.buildSongs(context, cursor) { indexing -> emitIndexing(indexing, generation) } + backend.buildSongs(context, cursor) { indexing -> + emitIndexing(indexing, generation) + } } // Deduplicate songs to prevent (most) deformed music clones diff --git a/app/src/main/java/org/oxycblt/auxio/music/MusicStore.kt b/app/src/main/java/org/oxycblt/auxio/music/MusicStore.kt index 8ce615090..30f71b6bf 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/MusicStore.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/MusicStore.kt @@ -77,6 +77,12 @@ class MusicStore private constructor() { songs.find { it.fileName == displayName } } + + /** "Sanitize" a music object from a previous library iteration. */ + fun sanitize(song: Song) = songs.find { it.id == song.id } + fun sanitize(album: Album) = albums.find { it.id == album.id } + fun sanitize(artist: Artist) = artists.find { it.id == artist.id } + fun sanitize(genre: Genre) = genres.find { it.id == genre.id } } /** A callback for awaiting the loading of music. */ diff --git a/app/src/main/java/org/oxycblt/auxio/music/excluded/ExcludedDirectory.kt b/app/src/main/java/org/oxycblt/auxio/music/excluded/ExcludedDirectory.kt index f068f8724..1b28e06a1 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/excluded/ExcludedDirectory.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/excluded/ExcludedDirectory.kt @@ -21,6 +21,13 @@ import android.os.Build import java.io.File import org.oxycblt.auxio.util.logW +/** + * Represents a directory excluded from the music loading process. This is a in-code + * representation of a typical document tree URI scheme, designed to not only provide + * support for external volumes, but also provide it in a way compatible with older + * android versions. + * @author OxygenCobalt + */ data class ExcludedDirectory(val volume: Volume, val relativePath: String) { override fun toString(): String = "${volume}:$relativePath" @@ -50,6 +57,9 @@ data class ExcludedDirectory(val volume: Volume, val relativePath: String) { val volume = Volume.fromString(split.getOrNull(0) ?: return null) if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q && volume is Volume.Secondary) { + // While Android Q provides a stable way of accessing volumes, we can't trust + // that DATA provides a stable volume scheme on older versions, so external + // volumes are not supported. logW("Cannot use secondary volumes below API 29") return null } diff --git a/app/src/main/java/org/oxycblt/auxio/settings/AboutFragment.kt b/app/src/main/java/org/oxycblt/auxio/settings/AboutFragment.kt index 38ee5b2b8..f04d4c241 100644 --- a/app/src/main/java/org/oxycblt/auxio/settings/AboutFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/settings/AboutFragment.kt @@ -81,8 +81,7 @@ class AboutFragment : ViewBindingFragment() { } private fun updateAlbumCount(albums: List) { - requireBinding().aboutAlbumCount.textSafe = - getString(R.string.fmt_album_count, albums.size) + requireBinding().aboutAlbumCount.textSafe = getString(R.string.fmt_album_count, albums.size) } private fun updateArtistCount(artists: List) { @@ -91,8 +90,7 @@ class AboutFragment : ViewBindingFragment() { } private fun updateGenreCount(genres: List) { - requireBinding().aboutGenreCount.textSafe = - getString(R.string.fmt_genre_count, genres.size) + requireBinding().aboutGenreCount.textSafe = getString(R.string.fmt_genre_count, genres.size) } /** Go through the process of opening a [link] in a browser. */