detail: respond to automatic rescanning
Make the detail UIs respond to automatic rescanning events. This currently has no effect, but is instead a preparation step for the seamless addition of automatic rescanning.
This commit is contained in:
parent
0a18883a6a
commit
8d7aa7936b
8 changed files with 85 additions and 8 deletions
|
@ -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) {
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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<Item>(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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 -> {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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. */
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -81,8 +81,7 @@ class AboutFragment : ViewBindingFragment<FragmentAboutBinding>() {
|
|||
}
|
||||
|
||||
private fun updateAlbumCount(albums: List<Album>) {
|
||||
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<Artist>) {
|
||||
|
@ -91,8 +90,7 @@ class AboutFragment : ViewBindingFragment<FragmentAboutBinding>() {
|
|||
}
|
||||
|
||||
private fun updateGenreCount(genres: List<Genre>) {
|
||||
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. */
|
||||
|
|
Loading…
Reference in a new issue