list: move instructions into recycler

Move the instructions enum (which is only concerned with recyclerview
semantics) into the list.recycler module instead of list.
This commit is contained in:
Alexander Capehart 2023-01-16 17:47:07 -07:00
parent d38da9b892
commit ad9d2f2d9e
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
18 changed files with 90 additions and 91 deletions

View file

@ -31,7 +31,7 @@ import org.oxycblt.auxio.databinding.FragmentDetailBinding
import org.oxycblt.auxio.detail.recycler.AlbumDetailAdapter import org.oxycblt.auxio.detail.recycler.AlbumDetailAdapter
import org.oxycblt.auxio.list.Item import org.oxycblt.auxio.list.Item
import org.oxycblt.auxio.list.ListFragment import org.oxycblt.auxio.list.ListFragment
import org.oxycblt.auxio.list.UpdateInstructions import org.oxycblt.auxio.list.recycler.BasicInstructions
import org.oxycblt.auxio.music.Album import org.oxycblt.auxio.music.Album
import org.oxycblt.auxio.music.Artist import org.oxycblt.auxio.music.Artist
import org.oxycblt.auxio.music.Music import org.oxycblt.auxio.music.Music
@ -260,7 +260,7 @@ class AlbumDetailFragment :
} }
private fun updateList(items: List<Item>) { private fun updateList(items: List<Item>) {
detailAdapter.submitList(items, UpdateInstructions.DIFF) detailAdapter.submitList(items, BasicInstructions.DIFF)
} }
private fun updateSelection(selected: List<Music>) { private fun updateSelection(selected: List<Music>) {

View file

@ -31,7 +31,7 @@ import org.oxycblt.auxio.detail.recycler.ArtistDetailAdapter
import org.oxycblt.auxio.detail.recycler.DetailAdapter import org.oxycblt.auxio.detail.recycler.DetailAdapter
import org.oxycblt.auxio.list.Item import org.oxycblt.auxio.list.Item
import org.oxycblt.auxio.list.ListFragment import org.oxycblt.auxio.list.ListFragment
import org.oxycblt.auxio.list.UpdateInstructions import org.oxycblt.auxio.list.recycler.BasicInstructions
import org.oxycblt.auxio.music.Album import org.oxycblt.auxio.music.Album
import org.oxycblt.auxio.music.Artist import org.oxycblt.auxio.music.Artist
import org.oxycblt.auxio.music.Music import org.oxycblt.auxio.music.Music
@ -236,7 +236,7 @@ class ArtistDetailFragment :
} }
private fun updateList(items: List<Item>) { private fun updateList(items: List<Item>) {
detailAdapter.submitList(items, UpdateInstructions.DIFF) detailAdapter.submitList(items, BasicInstructions.DIFF)
} }
private fun updateSelection(selected: List<Music>) { private fun updateSelection(selected: List<Music>) {

View file

@ -31,7 +31,7 @@ import org.oxycblt.auxio.detail.recycler.DetailAdapter
import org.oxycblt.auxio.detail.recycler.GenreDetailAdapter import org.oxycblt.auxio.detail.recycler.GenreDetailAdapter
import org.oxycblt.auxio.list.Item import org.oxycblt.auxio.list.Item
import org.oxycblt.auxio.list.ListFragment import org.oxycblt.auxio.list.ListFragment
import org.oxycblt.auxio.list.UpdateInstructions import org.oxycblt.auxio.list.recycler.BasicInstructions
import org.oxycblt.auxio.music.Album import org.oxycblt.auxio.music.Album
import org.oxycblt.auxio.music.Artist import org.oxycblt.auxio.music.Artist
import org.oxycblt.auxio.music.Genre import org.oxycblt.auxio.music.Genre
@ -219,7 +219,7 @@ class GenreDetailFragment :
} }
private fun updateList(items: List<Item>) { private fun updateList(items: List<Item>) {
detailAdapter.submitList(items, UpdateInstructions.DIFF) detailAdapter.submitList(items, BasicInstructions.DIFF)
} }
private fun updateSelection(selected: List<Music>) { private fun updateSelection(selected: List<Music>) {

View file

@ -28,7 +28,7 @@ import org.oxycblt.auxio.detail.SortHeader
import org.oxycblt.auxio.list.Header import org.oxycblt.auxio.list.Header
import org.oxycblt.auxio.list.Item import org.oxycblt.auxio.list.Item
import org.oxycblt.auxio.list.SelectableListListener import org.oxycblt.auxio.list.SelectableListListener
import org.oxycblt.auxio.list.UpdateInstructions import org.oxycblt.auxio.list.recycler.BasicInstructions
import org.oxycblt.auxio.list.recycler.* import org.oxycblt.auxio.list.recycler.*
import org.oxycblt.auxio.music.Music import org.oxycblt.auxio.music.Music
import org.oxycblt.auxio.util.context import org.oxycblt.auxio.util.context
@ -45,7 +45,7 @@ abstract class DetailAdapter(
private val listener: Listener<*>, private val listener: Listener<*>,
diffCallback: DiffUtil.ItemCallback<Item> diffCallback: DiffUtil.ItemCallback<Item>
) : ) :
SelectionIndicatorAdapter<Item, UpdateInstructions, RecyclerView.ViewHolder>( SelectionIndicatorAdapter<Item, BasicInstructions, RecyclerView.ViewHolder>(
ListDiffer.Async(diffCallback)), ListDiffer.Async(diffCallback)),
AuxioRecyclerView.SpanSizeLookup { AuxioRecyclerView.SpanSizeLookup {

View file

@ -22,7 +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.list.recycler.BasicInstructions
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
@ -46,7 +46,7 @@ class HomeViewModel(application: Application) :
val songsList: StateFlow<List<Song>> val songsList: StateFlow<List<Song>>
get() = _songsList get() = _songsList
/** Specifies how to update [songsList] when it changes. */ /** Specifies how to update [songsList] when it changes. */
var songsListInstructions: UpdateInstructions? = null var songsListInstructions: BasicInstructions? = null
private set private set
private val _albumsLists = MutableStateFlow(listOf<Album>()) private val _albumsLists = MutableStateFlow(listOf<Album>())
@ -54,7 +54,7 @@ class HomeViewModel(application: Application) :
val albumsList: StateFlow<List<Album>> val albumsList: StateFlow<List<Album>>
get() = _albumsLists get() = _albumsLists
/** Specifies how to update [albumsList] when it changes. */ /** Specifies how to update [albumsList] when it changes. */
var albumsListInstructions: UpdateInstructions? = null var albumsListInstructions: BasicInstructions? = null
private set private set
private val _artistsList = MutableStateFlow(listOf<Artist>()) private val _artistsList = MutableStateFlow(listOf<Artist>())
@ -66,7 +66,7 @@ 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. */ /** Specifies how to update [artistsList] when it changes. */
var artistsListInstructions: UpdateInstructions? = null var artistsListInstructions: BasicInstructions? = null
private set private set
private val _genresList = MutableStateFlow(listOf<Genre>()) private val _genresList = MutableStateFlow(listOf<Genre>())
@ -74,7 +74,7 @@ class HomeViewModel(application: Application) :
val genresList: StateFlow<List<Genre>> val genresList: StateFlow<List<Genre>>
get() = _genresList get() = _genresList
/** Specifies how to update [genresList] when it changes. */ /** Specifies how to update [genresList] when it changes. */
var genresListInstructions: UpdateInstructions? = null var genresListInstructions: BasicInstructions? = null
private set private set
/** The [MusicMode] to use when playing a [Song] from the UI. */ /** The [MusicMode] to use when playing a [Song] from the UI. */
@ -120,11 +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 songsListInstructions = BasicInstructions.DIFF
_songsList.value = musicSettings.songSort.songs(library.songs) _songsList.value = musicSettings.songSort.songs(library.songs)
albumsListInstructions = UpdateInstructions.DIFF albumsListInstructions = BasicInstructions.DIFF
_albumsLists.value = musicSettings.albumSort.albums(library.albums) _albumsLists.value = musicSettings.albumSort.albums(library.albums)
artistsListInstructions = UpdateInstructions.DIFF artistsListInstructions = BasicInstructions.DIFF
_artistsList.value = _artistsList.value =
musicSettings.artistSort.artists( musicSettings.artistSort.artists(
if (homeSettings.shouldHideCollaborators) { if (homeSettings.shouldHideCollaborators) {
@ -133,7 +133,7 @@ class HomeViewModel(application: Application) :
} else { } else {
library.artists library.artists
}) })
genresListInstructions = UpdateInstructions.DIFF genresListInstructions = BasicInstructions.DIFF
_genresList.value = musicSettings.genreSort.genres(library.genres) _genresList.value = musicSettings.genreSort.genres(library.genres)
} }
} }
@ -173,48 +173,48 @@ class HomeViewModel(application: Application) :
when (_currentTabMode.value) { when (_currentTabMode.value) {
MusicMode.SONGS -> { MusicMode.SONGS -> {
musicSettings.songSort = sort musicSettings.songSort = sort
songsListInstructions = UpdateInstructions.REPLACE songsListInstructions = BasicInstructions.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 albumsListInstructions = BasicInstructions.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 artistsListInstructions = BasicInstructions.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 genresListInstructions = BasicInstructions.REPLACE
_genresList.value = sort.genres(_genresList.value) _genresList.value = sort.genres(_genresList.value)
} }
} }
} }
/** Signal that the specified [UpdateInstructions] in [songsListInstructions] were performed. */ /** Signal that the specified [BasicInstructions] in [songsListInstructions] were performed. */
fun finishSongsListInstructions() { fun finishSongsListInstructions() {
songsListInstructions = null songsListInstructions = null
} }
/** /**
* Signal that the specified [UpdateInstructions] in [albumsListInstructions] were performed. * Signal that the specified [BasicInstructions] in [albumsListInstructions] were performed.
*/ */
fun finishAlbumsListInstructions() { fun finishAlbumsListInstructions() {
albumsListInstructions = null albumsListInstructions = null
} }
/** /**
* Signal that the specified [UpdateInstructions] in [artistsListInstructions] were performed. * Signal that the specified [BasicInstructions] in [artistsListInstructions] were performed.
*/ */
fun finishArtistsListInstructions() { fun finishArtistsListInstructions() {
artistsListInstructions = null artistsListInstructions = null
} }
/** /**
* Signal that the specified [UpdateInstructions] in [genresListInstructions] were performed. * Signal that the specified [BasicInstructions] in [genresListInstructions] were performed.
*/ */
fun finishGenresListInstructions() { fun finishGenresListInstructions() {
genresListInstructions = null genresListInstructions = null

View file

@ -33,6 +33,7 @@ import org.oxycblt.auxio.list.ListFragment
import org.oxycblt.auxio.list.recycler.AlbumViewHolder import org.oxycblt.auxio.list.recycler.AlbumViewHolder
import org.oxycblt.auxio.list.recycler.ListDiffer import org.oxycblt.auxio.list.recycler.ListDiffer
import org.oxycblt.auxio.list.recycler.SelectionIndicatorAdapter import org.oxycblt.auxio.list.recycler.SelectionIndicatorAdapter
import org.oxycblt.auxio.list.recycler.BasicInstructions
import org.oxycblt.auxio.music.* import org.oxycblt.auxio.music.*
import org.oxycblt.auxio.music.library.Sort import org.oxycblt.auxio.music.library.Sort
import org.oxycblt.auxio.playback.formatDurationMs import org.oxycblt.auxio.playback.formatDurationMs
@ -131,7 +132,7 @@ class AlbumListFragment :
} }
private fun updateList(albums: List<Album>) { private fun updateList(albums: List<Album>) {
albumAdapter.submitList(albums, homeModel.albumsListInstructions ?: UpdateInstructions.DIFF) albumAdapter.submitList(albums, homeModel.albumsListInstructions ?: BasicInstructions.DIFF)
homeModel.finishAlbumsListInstructions() homeModel.finishAlbumsListInstructions()
} }
@ -149,7 +150,7 @@ class AlbumListFragment :
* @param listener An [SelectableListListener] to bind interactions to. * @param listener An [SelectableListListener] to bind interactions to.
*/ */
private class AlbumAdapter(private val listener: SelectableListListener<Album>) : private class AlbumAdapter(private val listener: SelectableListListener<Album>) :
SelectionIndicatorAdapter<Album, UpdateInstructions, AlbumViewHolder>( SelectionIndicatorAdapter<Album, BasicInstructions, AlbumViewHolder>(
ListDiffer.Async(AlbumViewHolder.DIFF_CALLBACK)) { ListDiffer.Async(AlbumViewHolder.DIFF_CALLBACK)) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =

View file

@ -31,6 +31,7 @@ import org.oxycblt.auxio.list.ListFragment
import org.oxycblt.auxio.list.recycler.ArtistViewHolder import org.oxycblt.auxio.list.recycler.ArtistViewHolder
import org.oxycblt.auxio.list.recycler.ListDiffer import org.oxycblt.auxio.list.recycler.ListDiffer
import org.oxycblt.auxio.list.recycler.SelectionIndicatorAdapter import org.oxycblt.auxio.list.recycler.SelectionIndicatorAdapter
import org.oxycblt.auxio.list.recycler.BasicInstructions
import org.oxycblt.auxio.music.Artist import org.oxycblt.auxio.music.Artist
import org.oxycblt.auxio.music.Music import org.oxycblt.auxio.music.Music
import org.oxycblt.auxio.music.MusicMode import org.oxycblt.auxio.music.MusicMode
@ -110,7 +111,7 @@ class ArtistListFragment :
private fun updateList(artists: List<Artist>) { private fun updateList(artists: List<Artist>) {
artistAdapter.submitList( artistAdapter.submitList(
artists, homeModel.artistsListInstructions ?: UpdateInstructions.DIFF) artists, homeModel.artistsListInstructions ?: BasicInstructions.DIFF)
homeModel.finishArtistsListInstructions() homeModel.finishArtistsListInstructions()
} }
@ -128,7 +129,7 @@ class ArtistListFragment :
* @param listener An [SelectableListListener] to bind interactions to. * @param listener An [SelectableListListener] to bind interactions to.
*/ */
private class ArtistAdapter(private val listener: SelectableListListener<Artist>) : private class ArtistAdapter(private val listener: SelectableListListener<Artist>) :
SelectionIndicatorAdapter<Artist, UpdateInstructions, ArtistViewHolder>( SelectionIndicatorAdapter<Artist, BasicInstructions, ArtistViewHolder>(
ListDiffer.Async(ArtistViewHolder.DIFF_CALLBACK)) { ListDiffer.Async(ArtistViewHolder.DIFF_CALLBACK)) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =

View file

@ -31,6 +31,7 @@ import org.oxycblt.auxio.list.ListFragment
import org.oxycblt.auxio.list.recycler.GenreViewHolder import org.oxycblt.auxio.list.recycler.GenreViewHolder
import org.oxycblt.auxio.list.recycler.ListDiffer import org.oxycblt.auxio.list.recycler.ListDiffer
import org.oxycblt.auxio.list.recycler.SelectionIndicatorAdapter import org.oxycblt.auxio.list.recycler.SelectionIndicatorAdapter
import org.oxycblt.auxio.list.recycler.BasicInstructions
import org.oxycblt.auxio.music.Genre import org.oxycblt.auxio.music.Genre
import org.oxycblt.auxio.music.Music import org.oxycblt.auxio.music.Music
import org.oxycblt.auxio.music.MusicMode import org.oxycblt.auxio.music.MusicMode
@ -109,7 +110,7 @@ class GenreListFragment :
private fun updateList(artists: List<Genre>) { private fun updateList(artists: List<Genre>) {
genreAdapter.submitList( genreAdapter.submitList(
artists, homeModel.genresListInstructions ?: UpdateInstructions.DIFF) artists, homeModel.genresListInstructions ?: BasicInstructions.DIFF)
homeModel.finishGenresListInstructions() homeModel.finishGenresListInstructions()
} }
@ -127,7 +128,7 @@ class GenreListFragment :
* @param listener An [SelectableListListener] to bind interactions to. * @param listener An [SelectableListListener] to bind interactions to.
*/ */
private class GenreAdapter(private val listener: SelectableListListener<Genre>) : private class GenreAdapter(private val listener: SelectableListListener<Genre>) :
SelectionIndicatorAdapter<Genre, UpdateInstructions, GenreViewHolder>( SelectionIndicatorAdapter<Genre, BasicInstructions, GenreViewHolder>(
ListDiffer.Async(GenreViewHolder.DIFF_CALLBACK)) { ListDiffer.Async(GenreViewHolder.DIFF_CALLBACK)) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =
GenreViewHolder.from(parent) GenreViewHolder.from(parent)

View file

@ -33,6 +33,7 @@ import org.oxycblt.auxio.list.ListFragment
import org.oxycblt.auxio.list.recycler.ListDiffer import org.oxycblt.auxio.list.recycler.ListDiffer
import org.oxycblt.auxio.list.recycler.SelectionIndicatorAdapter import org.oxycblt.auxio.list.recycler.SelectionIndicatorAdapter
import org.oxycblt.auxio.list.recycler.SongViewHolder import org.oxycblt.auxio.list.recycler.SongViewHolder
import org.oxycblt.auxio.list.recycler.BasicInstructions
import org.oxycblt.auxio.music.Music import org.oxycblt.auxio.music.Music
import org.oxycblt.auxio.music.MusicMode import org.oxycblt.auxio.music.MusicMode
import org.oxycblt.auxio.music.MusicParent import org.oxycblt.auxio.music.MusicParent
@ -138,7 +139,7 @@ class SongListFragment :
} }
private fun updateList(songs: List<Song>) { private fun updateList(songs: List<Song>) {
songAdapter.submitList(songs, homeModel.songsListInstructions ?: UpdateInstructions.DIFF) songAdapter.submitList(songs, homeModel.songsListInstructions ?: BasicInstructions.DIFF)
homeModel.finishSongsListInstructions() homeModel.finishSongsListInstructions()
} }
@ -160,7 +161,7 @@ class SongListFragment :
* @param listener An [SelectableListListener] to bind interactions to. * @param listener An [SelectableListListener] to bind interactions to.
*/ */
private class SongAdapter(private val listener: SelectableListListener<Song>) : private class SongAdapter(private val listener: SelectableListListener<Song>) :
SelectionIndicatorAdapter<Song, UpdateInstructions, SongViewHolder>( SelectionIndicatorAdapter<Song, BasicInstructions, SongViewHolder>(
ListDiffer.Async(SongViewHolder.DIFF_CALLBACK)) { ListDiffer.Async(SongViewHolder.DIFF_CALLBACK)) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =

View file

@ -1,36 +0,0 @@
/*
* Copyright (c) 2023 Auxio Project
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.oxycblt.auxio.list
/**
* Represents the specific way to update a list of items.
* @author Alexander Capehart (OxygenCobalt)
*/
enum class UpdateInstructions {
/**
* (A)synchronously diff the list. This should be used for small diffs with little item
* movement.
*/
DIFF,
/**
* Synchronously remove the current list and replace it with a new one. This should be used for
* large diffs with that would cause erratic scroll behavior or in-efficiency.
*/
REPLACE
}

View file

@ -24,7 +24,6 @@ import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListUpdateCallback import androidx.recyclerview.widget.ListUpdateCallback
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import java.lang.reflect.Field import java.lang.reflect.Field
import org.oxycblt.auxio.list.UpdateInstructions
import org.oxycblt.auxio.util.lazyReflectedField import org.oxycblt.auxio.util.lazyReflectedField
import org.oxycblt.auxio.util.requireIs import org.oxycblt.auxio.util.requireIs
@ -39,7 +38,7 @@ interface ListDiffer<T, I> {
/** /**
* Dynamically determine how to update the list based on the given instructions. * Dynamically determine how to update the list based on the given instructions.
* @param newList The new list of [T] items to show. * @param newList The new list of [T] items to show.
* @param instructions The [UpdateInstructions] specifying how to update the list. * @param instructions The [BasicInstructions] specifying how to update the list.
* @param onDone Called when the update process is completed. * @param onDone Called when the update process is completed.
*/ */
fun submitList(newList: List<T>, instructions: I, onDone: () -> Unit) fun submitList(newList: List<T>, instructions: I, onDone: () -> Unit)
@ -63,8 +62,8 @@ interface ListDiffer<T, I> {
* internal list. * internal list.
*/ */
class Async<T>(private val diffCallback: DiffUtil.ItemCallback<T>) : class Async<T>(private val diffCallback: DiffUtil.ItemCallback<T>) :
Factory<T, UpdateInstructions>() { Factory<T, BasicInstructions>() {
override fun new(adapter: RecyclerView.Adapter<*>): ListDiffer<T, UpdateInstructions> = override fun new(adapter: RecyclerView.Adapter<*>): ListDiffer<T, BasicInstructions> =
RealAsyncListDiffer(AdapterListUpdateCallback(adapter), diffCallback) RealAsyncListDiffer(AdapterListUpdateCallback(adapter), diffCallback)
} }
@ -75,21 +74,39 @@ interface ListDiffer<T, I> {
* internal list. * internal list.
*/ */
class Blocking<T>(private val diffCallback: DiffUtil.ItemCallback<T>) : class Blocking<T>(private val diffCallback: DiffUtil.ItemCallback<T>) :
Factory<T, UpdateInstructions>() { Factory<T, BasicInstructions>() {
override fun new(adapter: RecyclerView.Adapter<*>): ListDiffer<T, UpdateInstructions> = override fun new(adapter: RecyclerView.Adapter<*>): ListDiffer<T, BasicInstructions> =
RealBlockingListDiffer(AdapterListUpdateCallback(adapter), diffCallback) RealBlockingListDiffer(AdapterListUpdateCallback(adapter), diffCallback)
} }
} }
private abstract class RealListDiffer<T>() : ListDiffer<T, UpdateInstructions> { /**
* Represents the specific way to update a list of items.
* @author Alexander Capehart (OxygenCobalt)
*/
enum class BasicInstructions {
/**
* (A)synchronously diff the list. This should be used for small diffs with little item
* movement.
*/
DIFF,
/**
* Synchronously remove the current list and replace it with a new one. This should be used for
* large diffs with that would cause erratic scroll behavior or in-efficiency.
*/
REPLACE
}
private abstract class RealListDiffer<T>() : ListDiffer<T, BasicInstructions> {
override fun submitList( override fun submitList(
newList: List<T>, newList: List<T>,
instructions: UpdateInstructions, instructions: BasicInstructions,
onDone: () -> Unit onDone: () -> Unit
) { ) {
when (instructions) { when (instructions) {
UpdateInstructions.DIFF -> diffList(newList, onDone) BasicInstructions.DIFF -> diffList(newList, onDone)
UpdateInstructions.REPLACE -> replaceList(newList, onDone) BasicInstructions.REPLACE -> replaceList(newList, onDone)
} }
} }

View file

@ -27,7 +27,7 @@ import com.google.android.material.shape.MaterialShapeDrawable
import org.oxycblt.auxio.R import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.ItemQueueSongBinding import org.oxycblt.auxio.databinding.ItemQueueSongBinding
import org.oxycblt.auxio.list.EditableListListener import org.oxycblt.auxio.list.EditableListListener
import org.oxycblt.auxio.list.UpdateInstructions import org.oxycblt.auxio.list.recycler.BasicInstructions
import org.oxycblt.auxio.list.recycler.DiffAdapter import org.oxycblt.auxio.list.recycler.DiffAdapter
import org.oxycblt.auxio.list.recycler.ListDiffer import org.oxycblt.auxio.list.recycler.ListDiffer
import org.oxycblt.auxio.list.recycler.PlayingIndicatorAdapter import org.oxycblt.auxio.list.recycler.PlayingIndicatorAdapter
@ -41,7 +41,7 @@ import org.oxycblt.auxio.util.*
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
class QueueAdapter(private val listener: EditableListListener<Song>) : class QueueAdapter(private val listener: EditableListListener<Song>) :
DiffAdapter<Song, UpdateInstructions, QueueSongViewHolder>( DiffAdapter<Song, BasicInstructions, QueueSongViewHolder>(
ListDiffer.Blocking(QueueSongViewHolder.DIFF_CALLBACK)) { ListDiffer.Blocking(QueueSongViewHolder.DIFF_CALLBACK)) {
// Since PlayingIndicator adapter relies on an item value, we cannot use it for this // Since PlayingIndicator adapter relies on an item value, we cannot use it for this
// adapter, as one item can appear at several points in the UI. Use a similar implementation // adapter, as one item can appear at several points in the UI. Use a similar implementation

View file

@ -27,7 +27,7 @@ import androidx.recyclerview.widget.RecyclerView
import kotlin.math.min import kotlin.math.min
import org.oxycblt.auxio.databinding.FragmentQueueBinding import org.oxycblt.auxio.databinding.FragmentQueueBinding
import org.oxycblt.auxio.list.EditableListListener import org.oxycblt.auxio.list.EditableListListener
import org.oxycblt.auxio.list.UpdateInstructions import org.oxycblt.auxio.list.recycler.BasicInstructions
import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.playback.PlaybackViewModel import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.ui.ViewBindingFragment import org.oxycblt.auxio.ui.ViewBindingFragment
@ -101,7 +101,7 @@ class QueueFragment : ViewBindingFragment<FragmentQueueBinding>(), EditableListL
// Replace or diff the queue depending on the type of change it is. // Replace or diff the queue depending on the type of change it is.
val instructions = queueModel.instructions val instructions = queueModel.instructions
queueAdapter.submitList(queue, instructions?.update ?: UpdateInstructions.DIFF) queueAdapter.submitList(queue, instructions?.update ?: BasicInstructions.DIFF)
// Update position in list (and thus past/future items) // Update position in list (and thus past/future items)
queueAdapter.setPosition(index, isPlaying) queueAdapter.setPosition(index, isPlaying)

View file

@ -20,7 +20,7 @@ package org.oxycblt.auxio.playback.queue
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import org.oxycblt.auxio.list.UpdateInstructions import org.oxycblt.auxio.list.recycler.BasicInstructions
import org.oxycblt.auxio.music.MusicParent import org.oxycblt.auxio.music.MusicParent
import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.playback.state.PlaybackStateManager import org.oxycblt.auxio.playback.state.PlaybackStateManager
@ -57,7 +57,7 @@ class QueueViewModel : ViewModel(), PlaybackStateManager.Listener {
override fun onQueueChanged(queue: Queue, change: Queue.ChangeResult) { override fun onQueueChanged(queue: Queue, change: Queue.ChangeResult) {
// Queue changed trivially due to item mo -> Diff queue, stay at current index. // Queue changed trivially due to item mo -> Diff queue, stay at current index.
instructions = Instructions(UpdateInstructions.DIFF, null) instructions = Instructions(BasicInstructions.DIFF, null)
_queue.value = queue.resolve() _queue.value = queue.resolve()
if (change != Queue.ChangeResult.MAPPING) { if (change != Queue.ChangeResult.MAPPING) {
// Index changed, make sure it remains updated without actually scrolling to it. // Index changed, make sure it remains updated without actually scrolling to it.
@ -67,14 +67,14 @@ class QueueViewModel : ViewModel(), PlaybackStateManager.Listener {
override fun onQueueReordered(queue: Queue) { override fun onQueueReordered(queue: Queue) {
// Queue changed completely -> Replace queue, update index // Queue changed completely -> Replace queue, update index
instructions = Instructions(UpdateInstructions.REPLACE, queue.index) instructions = Instructions(BasicInstructions.REPLACE, queue.index)
_queue.value = queue.resolve() _queue.value = queue.resolve()
_index.value = queue.index _index.value = queue.index
} }
override fun onNewPlayback(queue: Queue, parent: MusicParent?) { override fun onNewPlayback(queue: Queue, parent: MusicParent?) {
// Entirely new queue -> Replace queue, update index // Entirely new queue -> Replace queue, update index
instructions = Instructions(UpdateInstructions.REPLACE, queue.index) instructions = Instructions(BasicInstructions.REPLACE, queue.index)
_queue.value = queue.resolve() _queue.value = queue.resolve()
_index.value = queue.index _index.value = queue.index
} }
@ -124,5 +124,5 @@ class QueueViewModel : ViewModel(), PlaybackStateManager.Listener {
instructions = null instructions = null
} }
class Instructions(val update: UpdateInstructions?, val scrollTo: Int?) class Instructions(val update: BasicInstructions?, val scrollTo: Int?)
} }

View file

@ -29,7 +29,7 @@ import org.oxycblt.auxio.music.*
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
class SearchAdapter(private val listener: SelectableListListener<Music>) : class SearchAdapter(private val listener: SelectableListListener<Music>) :
SelectionIndicatorAdapter<Item, UpdateInstructions, RecyclerView.ViewHolder>( SelectionIndicatorAdapter<Item, BasicInstructions, RecyclerView.ViewHolder>(
ListDiffer.Async(DIFF_CALLBACK)), ListDiffer.Async(DIFF_CALLBACK)),
AuxioRecyclerView.SpanSizeLookup { AuxioRecyclerView.SpanSizeLookup {

View file

@ -31,7 +31,7 @@ import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.FragmentSearchBinding import org.oxycblt.auxio.databinding.FragmentSearchBinding
import org.oxycblt.auxio.list.Item import org.oxycblt.auxio.list.Item
import org.oxycblt.auxio.list.ListFragment import org.oxycblt.auxio.list.ListFragment
import org.oxycblt.auxio.list.UpdateInstructions import org.oxycblt.auxio.list.recycler.BasicInstructions
import org.oxycblt.auxio.music.Album import org.oxycblt.auxio.music.Album
import org.oxycblt.auxio.music.Artist import org.oxycblt.auxio.music.Artist
import org.oxycblt.auxio.music.Genre import org.oxycblt.auxio.music.Genre
@ -154,7 +154,7 @@ class SearchFragment : ListFragment<Music, FragmentSearchBinding>() {
// Don't show the RecyclerView (and it's stray overscroll effects) when there // Don't show the RecyclerView (and it's stray overscroll effects) when there
// are no results. // are no results.
binding.searchRecycler.isInvisible = results.isEmpty() binding.searchRecycler.isInvisible = results.isEmpty()
searchAdapter.submitList(results.toMutableList(), UpdateInstructions.DIFF) { searchAdapter.submitList(results.toMutableList(), BasicInstructions.DIFF) {
// I would make it so that the position is only scrolled back to the top when // I would make it so that the position is only scrolled back to the top when
// the query actually changes instead of once every re-creation event, but sadly // the query actually changes instead of once every re-creation event, but sadly
// that doesn't seem possible. // that doesn't seem possible.

View file

@ -13,4 +13,10 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
tools:text="Songs" /> tools:text="Songs" />
<com.google.android.material.divider.MaterialDivider
android:id="@+id/header_divider"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent" />
</LinearLayout> </LinearLayout>

View file

@ -12,6 +12,7 @@
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/spacing_mid_medium" android:layout_marginEnd="@dimen/spacing_mid_medium"
app:layout_constraintBottom_toTopOf="@id/header_divider"
app:layout_constraintEnd_toStartOf="@+id/header_button" app:layout_constraintEnd_toStartOf="@+id/header_button"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
@ -25,7 +26,14 @@
android:layout_marginEnd="@dimen/spacing_mid_medium" android:layout_marginEnd="@dimen/spacing_mid_medium"
android:contentDescription="@string/lbl_sort" android:contentDescription="@string/lbl_sort"
app:icon="@drawable/ic_sort_24" app:icon="@drawable/ic_sort_24"
app:layout_constraintBottom_toTopOf="@id/header_divider"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.divider.MaterialDivider
android:id="@+id/header_divider"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>