home: replace list only when resorting

Leverage the new UpdateInstructions system to allow the home view to
diff the lists on music updates, and then replace the lists on
resorting events.

This just improves quality-of-life overall.
This commit is contained in:
Alexander Capehart 2023-01-16 11:19:06 -07:00
parent 6e02929982
commit e7ff7293c0
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
7 changed files with 93 additions and 26 deletions

View file

@ -145,7 +145,7 @@ class HomeFragment :
// --- VIEWMODEL SETUP --- // --- VIEWMODEL SETUP ---
collect(homeModel.shouldRecreate, ::handleRecreate) collect(homeModel.shouldRecreate, ::handleRecreate)
collectImmediately(homeModel.currentTabMode, ::updateCurrentTab) collectImmediately(homeModel.currentTabMode, ::updateCurrentTab)
collectImmediately(homeModel.songLists, homeModel.isFastScrolling, ::updateFab) collectImmediately(homeModel.songsList, homeModel.isFastScrolling, ::updateFab)
collectImmediately(musicModel.indexerState, ::updateIndexerState) collectImmediately(musicModel.indexerState, ::updateIndexerState)
collect(navModel.exploreNavigationItem, ::handleNavigation) collect(navModel.exploreNavigationItem, ::handleNavigation)
collectImmediately(selectionModel.selected, ::updateSelection) collectImmediately(selectionModel.selected, ::updateSelection)

View file

@ -22,6 +22,7 @@ import androidx.lifecycle.AndroidViewModel
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import org.oxycblt.auxio.home.tabs.Tab import org.oxycblt.auxio.home.tabs.Tab
import org.oxycblt.auxio.list.UpdateInstructions
import org.oxycblt.auxio.music.* import org.oxycblt.auxio.music.*
import org.oxycblt.auxio.music.MusicStore import org.oxycblt.auxio.music.MusicStore
import org.oxycblt.auxio.music.library.Library import org.oxycblt.auxio.music.library.Library
@ -42,13 +43,19 @@ class HomeViewModel(application: Application) :
private val _songsList = MutableStateFlow(listOf<Song>()) private val _songsList = MutableStateFlow(listOf<Song>())
/** A list of [Song]s, sorted by the preferred [Sort], to be shown in the home view. */ /** A list of [Song]s, sorted by the preferred [Sort], to be shown in the home view. */
val songLists: StateFlow<List<Song>> val songsList: StateFlow<List<Song>>
get() = _songsList get() = _songsList
/** Specifies how to update [songsList] when it changes. */
var songsListInstructions: UpdateInstructions? = null
private set
private val _albumsLists = MutableStateFlow(listOf<Album>()) private val _albumsLists = MutableStateFlow(listOf<Album>())
/** A list of [Album]s, sorted by the preferred [Sort], to be shown in the home view. */ /** A list of [Album]s, sorted by the preferred [Sort], to be shown in the home view. */
val albumsList: StateFlow<List<Album>> val albumsList: StateFlow<List<Album>>
get() = _albumsLists get() = _albumsLists
/** Specifies how to update [albumsList] when it changes. */
var albumsListInstructions: UpdateInstructions? = null
private set
private val _artistsList = MutableStateFlow(listOf<Artist>()) private val _artistsList = MutableStateFlow(listOf<Artist>())
/** /**
@ -58,11 +65,17 @@ class HomeViewModel(application: Application) :
*/ */
val artistsList: MutableStateFlow<List<Artist>> val artistsList: MutableStateFlow<List<Artist>>
get() = _artistsList get() = _artistsList
/** Specifies how to update [artistsList] when it changes. */
var artistsListInstructions: UpdateInstructions? = null
private set
private val _genresList = MutableStateFlow(listOf<Genre>()) private val _genresList = MutableStateFlow(listOf<Genre>())
/** A list of [Genre]s, sorted by the preferred [Sort], to be shown in the home view. */ /** A list of [Genre]s, sorted by the preferred [Sort], to be shown in the home view. */
val genresList: StateFlow<List<Genre>> val genresList: StateFlow<List<Genre>>
get() = _genresList get() = _genresList
/** Specifies how to update [genresList] when it changes. */
var genresListInstructions: UpdateInstructions? = null
private set
/** The [MusicMode] to use when playing a [Song] from the UI. */ /** The [MusicMode] to use when playing a [Song] from the UI. */
val playbackMode: MusicMode val playbackMode: MusicMode
@ -107,8 +120,11 @@ class HomeViewModel(application: Application) :
logD("Library changed, refreshing library") logD("Library changed, refreshing library")
// Get the each list of items in the library to use as our list data. // Get the each list of items in the library to use as our list data.
// Applying the preferred sorting to them. // Applying the preferred sorting to them.
songsListInstructions = UpdateInstructions.DIFF
_songsList.value = musicSettings.songSort.songs(library.songs) _songsList.value = musicSettings.songSort.songs(library.songs)
albumsListInstructions = UpdateInstructions.DIFF
_albumsLists.value = musicSettings.albumSort.albums(library.albums) _albumsLists.value = musicSettings.albumSort.albums(library.albums)
artistsListInstructions = UpdateInstructions.DIFF
_artistsList.value = _artistsList.value =
musicSettings.artistSort.artists( musicSettings.artistSort.artists(
if (homeSettings.shouldHideCollaborators) { if (homeSettings.shouldHideCollaborators) {
@ -117,6 +133,7 @@ class HomeViewModel(application: Application) :
} else { } else {
library.artists library.artists
}) })
genresListInstructions = UpdateInstructions.DIFF
_genresList.value = musicSettings.genreSort.genres(library.genres) _genresList.value = musicSettings.genreSort.genres(library.genres)
} }
} }
@ -133,23 +150,6 @@ class HomeViewModel(application: Application) :
onLibraryChanged(musicStore.library) onLibraryChanged(musicStore.library)
} }
/**
* Update [currentTabMode] to reflect a new ViewPager2 position
* @param pagerPos The new position of the ViewPager2 instance.
*/
fun synchronizeTabPosition(pagerPos: Int) {
logD("Updating current tab to ${currentTabModes[pagerPos]}")
_currentTabMode.value = currentTabModes[pagerPos]
}
/**
* Mark the recreation process as complete.
* @see shouldRecreate
*/
fun finishRecreate() {
_shouldRecreate.value = false
}
/** /**
* Get the preferred [Sort] for a given [Tab]. * Get the preferred [Sort] for a given [Tab].
* @param tabMode The [MusicMode] of the [Tab] desired. * @param tabMode The [MusicMode] of the [Tab] desired.
@ -173,23 +173,70 @@ class HomeViewModel(application: Application) :
when (_currentTabMode.value) { when (_currentTabMode.value) {
MusicMode.SONGS -> { MusicMode.SONGS -> {
musicSettings.songSort = sort musicSettings.songSort = sort
songsListInstructions = UpdateInstructions.REPLACE
_songsList.value = sort.songs(_songsList.value) _songsList.value = sort.songs(_songsList.value)
} }
MusicMode.ALBUMS -> { MusicMode.ALBUMS -> {
musicSettings.albumSort = sort musicSettings.albumSort = sort
albumsListInstructions = UpdateInstructions.REPLACE
_albumsLists.value = sort.albums(_albumsLists.value) _albumsLists.value = sort.albums(_albumsLists.value)
} }
MusicMode.ARTISTS -> { MusicMode.ARTISTS -> {
musicSettings.artistSort = sort musicSettings.artistSort = sort
artistsListInstructions = UpdateInstructions.REPLACE
_artistsList.value = sort.artists(_artistsList.value) _artistsList.value = sort.artists(_artistsList.value)
} }
MusicMode.GENRES -> { MusicMode.GENRES -> {
musicSettings.genreSort = sort musicSettings.genreSort = sort
genresListInstructions = UpdateInstructions.REPLACE
_genresList.value = sort.genres(_genresList.value) _genresList.value = sort.genres(_genresList.value)
} }
} }
} }
/** Signal that the specified [UpdateInstructions] in [songsListInstructions] were performed. */
fun finishSongsListInstructions() {
songsListInstructions = null
}
/**
* Signal that the specified [UpdateInstructions] in [albumsListInstructions] were performed.
*/
fun finishAlbumsListInstructions() {
albumsListInstructions = null
}
/**
* Signal that the specified [UpdateInstructions] in [artistsListInstructions] were performed.
*/
fun finishArtistsListInstructions() {
artistsListInstructions = null
}
/**
* Signal that the specified [UpdateInstructions] in [genresListInstructions] were performed.
*/
fun finishGenresListInstructions() {
genresListInstructions = null
}
/**
* Update [currentTabMode] to reflect a new ViewPager2 position
* @param pagerPos The new position of the ViewPager2 instance.
*/
fun synchronizeTabPosition(pagerPos: Int) {
logD("Updating current tab to ${currentTabModes[pagerPos]}")
_currentTabMode.value = currentTabModes[pagerPos]
}
/**
* Mark the recreation process as complete.
* @see shouldRecreate
*/
fun finishRecreate() {
_shouldRecreate.value = false
}
/** /**
* Update whether the user is fast scrolling or not in the home view. * Update whether the user is fast scrolling or not in the home view.
* @param isFastScrolling true if the user is currently fast scrolling, false otherwise. * @param isFastScrolling true if the user is currently fast scrolling, false otherwise.

View file

@ -66,7 +66,7 @@ class AlbumListFragment :
listener = this@AlbumListFragment listener = this@AlbumListFragment
} }
collectImmediately(homeModel.albumsList, albumAdapter::replaceList) collectImmediately(homeModel.albumsList, ::updateList)
collectImmediately(selectionModel.selected, ::updateSelection) collectImmediately(selectionModel.selected, ::updateSelection)
collectImmediately(playbackModel.parent, playbackModel.isPlaying, ::updatePlayback) collectImmediately(playbackModel.parent, playbackModel.isPlaying, ::updatePlayback)
} }
@ -130,6 +130,11 @@ class AlbumListFragment :
openMusicMenu(anchor, R.menu.menu_album_actions, item) openMusicMenu(anchor, R.menu.menu_album_actions, item)
} }
private fun updateList(albums: List<Album>) {
albumAdapter.submitList(albums, homeModel.albumsListInstructions ?: UpdateInstructions.DIFF)
homeModel.finishAlbumsListInstructions()
}
private fun updateSelection(selection: List<Music>) { private fun updateSelection(selection: List<Music>) {
albumAdapter.setSelected(selection.filterIsInstanceTo(mutableSetOf())) albumAdapter.setSelected(selection.filterIsInstanceTo(mutableSetOf()))
} }

View file

@ -64,7 +64,7 @@ class ArtistListFragment :
listener = this@ArtistListFragment listener = this@ArtistListFragment
} }
collectImmediately(homeModel.artistsList, artistAdapter::replaceList) collectImmediately(homeModel.artistsList, ::updateList)
collectImmediately(selectionModel.selected, ::updateSelection) collectImmediately(selectionModel.selected, ::updateSelection)
collectImmediately(playbackModel.parent, playbackModel.isPlaying, ::updatePlayback) collectImmediately(playbackModel.parent, playbackModel.isPlaying, ::updatePlayback)
} }
@ -108,6 +108,12 @@ class ArtistListFragment :
openMusicMenu(anchor, R.menu.menu_artist_actions, item) openMusicMenu(anchor, R.menu.menu_artist_actions, item)
} }
private fun updateList(artists: List<Artist>) {
artistAdapter.submitList(
artists, homeModel.artistsListInstructions ?: UpdateInstructions.DIFF)
homeModel.finishArtistsListInstructions()
}
private fun updateSelection(selection: List<Music>) { private fun updateSelection(selection: List<Music>) {
artistAdapter.setSelected(selection.filterIsInstanceTo(mutableSetOf())) artistAdapter.setSelected(selection.filterIsInstanceTo(mutableSetOf()))
} }

View file

@ -63,7 +63,7 @@ class GenreListFragment :
listener = this@GenreListFragment listener = this@GenreListFragment
} }
collectImmediately(homeModel.genresList, genreAdapter::replaceList) collectImmediately(homeModel.genresList, ::updateList)
collectImmediately(selectionModel.selected, ::updateSelection) collectImmediately(selectionModel.selected, ::updateSelection)
collectImmediately(playbackModel.parent, playbackModel.isPlaying, ::updatePlayback) collectImmediately(playbackModel.parent, playbackModel.isPlaying, ::updatePlayback)
} }
@ -107,6 +107,12 @@ class GenreListFragment :
openMusicMenu(anchor, R.menu.menu_artist_actions, item) openMusicMenu(anchor, R.menu.menu_artist_actions, item)
} }
private fun updateList(artists: List<Genre>) {
genreAdapter.submitList(
artists, homeModel.genresListInstructions ?: UpdateInstructions.DIFF)
homeModel.finishGenresListInstructions()
}
private fun updateSelection(selection: List<Music>) { private fun updateSelection(selection: List<Music>) {
genreAdapter.setSelected(selection.filterIsInstanceTo(mutableSetOf())) genreAdapter.setSelected(selection.filterIsInstanceTo(mutableSetOf()))
} }

View file

@ -69,7 +69,7 @@ class SongListFragment :
listener = this@SongListFragment listener = this@SongListFragment
} }
collectImmediately(homeModel.songLists, songAdapter::replaceList) collectImmediately(homeModel.songsList, ::updateList)
collectImmediately(selectionModel.selected, ::updateSelection) collectImmediately(selectionModel.selected, ::updateSelection)
collectImmediately( collectImmediately(
playbackModel.song, playbackModel.parent, playbackModel.isPlaying, ::updatePlayback) playbackModel.song, playbackModel.parent, playbackModel.isPlaying, ::updatePlayback)
@ -85,7 +85,7 @@ class SongListFragment :
} }
override fun getPopup(pos: Int): String? { override fun getPopup(pos: Int): String? {
val song = homeModel.songLists.value[pos] val song = homeModel.songsList.value[pos]
// Change how we display the popup depending on the current sort mode. // Change how we display the popup depending on the current sort mode.
// Note: We don't use the more correct individual artist name here, as sorts are largely // Note: We don't use the more correct individual artist name here, as sorts are largely
// based off the names of the parent objects and not the child objects. // based off the names of the parent objects and not the child objects.
@ -137,6 +137,11 @@ class SongListFragment :
openMusicMenu(anchor, R.menu.menu_song_actions, item) openMusicMenu(anchor, R.menu.menu_song_actions, item)
} }
private fun updateList(songs: List<Song>) {
songAdapter.submitList(songs, homeModel.songsListInstructions ?: UpdateInstructions.DIFF)
homeModel.finishSongsListInstructions()
}
private fun updateSelection(selection: List<Music>) { private fun updateSelection(selection: List<Music>) {
songAdapter.setSelected(selection.filterIsInstanceTo(mutableSetOf())) songAdapter.setSelected(selection.filterIsInstanceTo(mutableSetOf()))
} }

View file

@ -18,7 +18,6 @@
package org.oxycblt.auxio.util package org.oxycblt.auxio.util
import android.app.PendingIntent import android.app.PendingIntent
import android.content.ActivityNotFoundException
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.res.ColorStateList import android.content.res.ColorStateList
@ -27,7 +26,6 @@ import android.os.Build
import android.util.TypedValue import android.util.TypedValue
import android.view.LayoutInflater import android.view.LayoutInflater
import android.widget.Toast import android.widget.Toast
import androidx.activity.result.ActivityResultLauncher
import androidx.annotation.AttrRes import androidx.annotation.AttrRes
import androidx.annotation.ColorRes import androidx.annotation.ColorRes
import androidx.annotation.DimenRes import androidx.annotation.DimenRes