recycler: spin off data into separate class
Spin off the data instances into their own class called BackingData. This is to isolate the sane adapters that rely on one type of diffing from the insane adapters that use synchronous and asynchronous diffing simultaniously. It also allows some of the more esoteric adapters to implement their own backing data without much trouble or leaky abstractions.
This commit is contained in:
parent
ee1a234e76
commit
8100f294d7
22 changed files with 346 additions and 204 deletions
|
@ -22,6 +22,8 @@ import coil.size.Size
|
||||||
import coil.size.pxOrElse
|
import coil.size.pxOrElse
|
||||||
import coil.transform.Transformation
|
import coil.transform.Transformation
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
|
import org.oxycblt.auxio.util.logD
|
||||||
|
import org.oxycblt.auxio.util.logE
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A transformation that performs a center crop-style transformation on an image, however unlike the
|
* A transformation that performs a center crop-style transformation on an image, however unlike the
|
||||||
|
@ -45,8 +47,13 @@ class SquareFrameTransform : Transformation {
|
||||||
val dst = Bitmap.createBitmap(input, x, y, dstSize, dstSize)
|
val dst = Bitmap.createBitmap(input, x, y, dstSize, dstSize)
|
||||||
|
|
||||||
if (dstSize != desiredWidth || dstSize != desiredHeight) {
|
if (dstSize != desiredWidth || dstSize != desiredHeight) {
|
||||||
// Desired size differs from the cropped size, resize the bitmap.
|
logD("RETARD YOU STUPID FUCKING IDIOT $desiredWidth $desiredHeight")
|
||||||
return Bitmap.createScaledBitmap(dst, desiredWidth, desiredHeight, true)
|
try {
|
||||||
|
// Desired size differs from the cropped size, resize the bitmap.
|
||||||
|
return Bitmap.createScaledBitmap(dst, desiredWidth, desiredHeight, true)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
logE(e.stackTraceToString())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return dst
|
return dst
|
||||||
|
|
|
@ -73,14 +73,16 @@ class AlbumDetailFragment : DetailFragment(), AlbumDetailItemListener {
|
||||||
requireBinding().detailRecycler.apply {
|
requireBinding().detailRecycler.apply {
|
||||||
adapter = detailAdapter
|
adapter = detailAdapter
|
||||||
applySpans { pos ->
|
applySpans { pos ->
|
||||||
val item = detailAdapter.currentList[pos]
|
val item = detailAdapter.data.currentList[pos]
|
||||||
item is Header || item is SortHeader || item is Album
|
item is Header || item is SortHeader || item is Album
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// -- VIEWMODEL SETUP ---
|
// -- VIEWMODEL SETUP ---
|
||||||
|
|
||||||
detailModel.albumData.observe(viewLifecycleOwner) { list -> detailAdapter.submitList(list) }
|
detailModel.albumData.observe(viewLifecycleOwner) { list ->
|
||||||
|
detailAdapter.data.submitList(list)
|
||||||
|
}
|
||||||
|
|
||||||
detailModel.navToItem.observe(viewLifecycleOwner) { item ->
|
detailModel.navToItem.observe(viewLifecycleOwner) { item ->
|
||||||
handleNavigation(item, detailAdapter)
|
handleNavigation(item, detailAdapter)
|
||||||
|
@ -168,7 +170,7 @@ class AlbumDetailFragment : DetailFragment(), AlbumDetailItemListener {
|
||||||
/** Scroll to an song using its [id]. */
|
/** Scroll to an song using its [id]. */
|
||||||
private fun scrollToItem(id: Long, adapter: AlbumDetailAdapter) {
|
private fun scrollToItem(id: Long, adapter: AlbumDetailAdapter) {
|
||||||
// Calculate where the item for the currently played song is
|
// Calculate where the item for the currently played song is
|
||||||
val pos = adapter.currentList.indexOfFirst { it.id == id && it is Song }
|
val pos = adapter.data.currentList.indexOfFirst { it.id == id && it is Song }
|
||||||
|
|
||||||
if (pos != -1) {
|
if (pos != -1) {
|
||||||
val binding = requireBinding()
|
val binding = requireBinding()
|
||||||
|
|
|
@ -55,7 +55,7 @@ class ArtistDetailFragment : DetailFragment(), DetailItemListener {
|
||||||
adapter = detailAdapter
|
adapter = detailAdapter
|
||||||
applySpans { pos ->
|
applySpans { pos ->
|
||||||
// If the item is an ActionHeader we need to also make the item full-width
|
// If the item is an ActionHeader we need to also make the item full-width
|
||||||
val item = detailAdapter.currentList[pos]
|
val item = detailAdapter.data.currentList[pos]
|
||||||
item is Header || item is SortHeader || item is Artist
|
item is Header || item is SortHeader || item is Artist
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -63,7 +63,7 @@ class ArtistDetailFragment : DetailFragment(), DetailItemListener {
|
||||||
// --- VIEWMODEL SETUP ---
|
// --- VIEWMODEL SETUP ---
|
||||||
|
|
||||||
detailModel.artistData.observe(viewLifecycleOwner) { list ->
|
detailModel.artistData.observe(viewLifecycleOwner) { list ->
|
||||||
detailAdapter.submitList(list)
|
detailAdapter.data.submitList(list)
|
||||||
}
|
}
|
||||||
|
|
||||||
detailModel.navToItem.observe(viewLifecycleOwner, ::handleNavigation)
|
detailModel.navToItem.observe(viewLifecycleOwner, ::handleNavigation)
|
||||||
|
|
|
@ -53,14 +53,16 @@ class GenreDetailFragment : DetailFragment(), DetailItemListener {
|
||||||
binding.detailRecycler.apply {
|
binding.detailRecycler.apply {
|
||||||
adapter = detailAdapter
|
adapter = detailAdapter
|
||||||
applySpans { pos ->
|
applySpans { pos ->
|
||||||
val item = detailAdapter.currentList[pos]
|
val item = detailAdapter.data.currentList[pos]
|
||||||
item is Header || item is SortHeader || item is Genre
|
item is Header || item is SortHeader || item is Genre
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- VIEWMODEL SETUP ---
|
// --- VIEWMODEL SETUP ---
|
||||||
|
|
||||||
detailModel.genreData.observe(viewLifecycleOwner) { list -> detailAdapter.submitList(list) }
|
detailModel.genreData.observe(viewLifecycleOwner) { list ->
|
||||||
|
detailAdapter.data.submitList(list)
|
||||||
|
}
|
||||||
|
|
||||||
detailModel.navToItem.observe(viewLifecycleOwner, ::handleNavigation)
|
detailModel.navToItem.observe(viewLifecycleOwner, ::handleNavigation)
|
||||||
|
|
||||||
|
|
|
@ -30,8 +30,8 @@ import org.oxycblt.auxio.music.Song
|
||||||
import org.oxycblt.auxio.music.toDuration
|
import org.oxycblt.auxio.music.toDuration
|
||||||
import org.oxycblt.auxio.ui.BindingViewHolder
|
import org.oxycblt.auxio.ui.BindingViewHolder
|
||||||
import org.oxycblt.auxio.ui.Item
|
import org.oxycblt.auxio.ui.Item
|
||||||
import org.oxycblt.auxio.ui.ItemDiffCallback
|
|
||||||
import org.oxycblt.auxio.ui.MenuItemListener
|
import org.oxycblt.auxio.ui.MenuItemListener
|
||||||
|
import org.oxycblt.auxio.ui.SimpleItemCallback
|
||||||
import org.oxycblt.auxio.util.getPluralSafe
|
import org.oxycblt.auxio.util.getPluralSafe
|
||||||
import org.oxycblt.auxio.util.inflater
|
import org.oxycblt.auxio.util.inflater
|
||||||
import org.oxycblt.auxio.util.textSafe
|
import org.oxycblt.auxio.util.textSafe
|
||||||
|
@ -98,7 +98,7 @@ class AlbumDetailAdapter(listener: AlbumDetailItemListener) :
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val DIFFER =
|
private val DIFFER =
|
||||||
object : ItemDiffCallback<Item>() {
|
object : SimpleItemCallback<Item>() {
|
||||||
override fun areItemsTheSame(oldItem: Item, newItem: Item): Boolean {
|
override fun areItemsTheSame(oldItem: Item, newItem: Item): Boolean {
|
||||||
return when {
|
return when {
|
||||||
oldItem is Album && newItem is Album ->
|
oldItem is Album && newItem is Album ->
|
||||||
|
@ -154,7 +154,7 @@ private class AlbumDetailViewHolder private constructor(private val binding: Ite
|
||||||
}
|
}
|
||||||
|
|
||||||
val DIFFER =
|
val DIFFER =
|
||||||
object : ItemDiffCallback<Album>() {
|
object : SimpleItemCallback<Album>() {
|
||||||
override fun areItemsTheSame(oldItem: Album, newItem: Album) =
|
override fun areItemsTheSame(oldItem: Album, newItem: Album) =
|
||||||
oldItem.resolvedName == newItem.resolvedName &&
|
oldItem.resolvedName == newItem.resolvedName &&
|
||||||
oldItem.resolvedArtistName == newItem.resolvedArtistName &&
|
oldItem.resolvedArtistName == newItem.resolvedArtistName &&
|
||||||
|
@ -214,7 +214,7 @@ private class AlbumSongViewHolder private constructor(private val binding: ItemA
|
||||||
}
|
}
|
||||||
|
|
||||||
val DIFFER =
|
val DIFFER =
|
||||||
object : ItemDiffCallback<Song>() {
|
object : SimpleItemCallback<Song>() {
|
||||||
override fun areItemsTheSame(oldItem: Song, newItem: Song) =
|
override fun areItemsTheSame(oldItem: Song, newItem: Song) =
|
||||||
oldItem.resolvedName == newItem.resolvedName &&
|
oldItem.resolvedName == newItem.resolvedName &&
|
||||||
oldItem.duration == newItem.duration
|
oldItem.duration == newItem.duration
|
||||||
|
|
|
@ -32,8 +32,8 @@ import org.oxycblt.auxio.music.Song
|
||||||
import org.oxycblt.auxio.ui.ArtistViewHolder
|
import org.oxycblt.auxio.ui.ArtistViewHolder
|
||||||
import org.oxycblt.auxio.ui.BindingViewHolder
|
import org.oxycblt.auxio.ui.BindingViewHolder
|
||||||
import org.oxycblt.auxio.ui.Item
|
import org.oxycblt.auxio.ui.Item
|
||||||
import org.oxycblt.auxio.ui.ItemDiffCallback
|
|
||||||
import org.oxycblt.auxio.ui.MenuItemListener
|
import org.oxycblt.auxio.ui.MenuItemListener
|
||||||
|
import org.oxycblt.auxio.ui.SimpleItemCallback
|
||||||
import org.oxycblt.auxio.util.context
|
import org.oxycblt.auxio.util.context
|
||||||
import org.oxycblt.auxio.util.getPluralSafe
|
import org.oxycblt.auxio.util.getPluralSafe
|
||||||
import org.oxycblt.auxio.util.inflater
|
import org.oxycblt.auxio.util.inflater
|
||||||
|
@ -123,7 +123,7 @@ class ArtistDetailAdapter(listener: DetailItemListener) :
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val DIFFER =
|
private val DIFFER =
|
||||||
object : ItemDiffCallback<Item>() {
|
object : SimpleItemCallback<Item>() {
|
||||||
override fun areItemsTheSame(oldItem: Item, newItem: Item): Boolean {
|
override fun areItemsTheSame(oldItem: Item, newItem: Item): Boolean {
|
||||||
return when {
|
return when {
|
||||||
oldItem is Artist && newItem is Artist ->
|
oldItem is Artist && newItem is Artist ->
|
||||||
|
@ -209,7 +209,7 @@ private constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
val DIFFER =
|
val DIFFER =
|
||||||
object : ItemDiffCallback<Album>() {
|
object : SimpleItemCallback<Album>() {
|
||||||
override fun areItemsTheSame(oldItem: Album, newItem: Album) =
|
override fun areItemsTheSame(oldItem: Album, newItem: Album) =
|
||||||
oldItem.resolvedName == newItem.resolvedName && oldItem.year == newItem.year
|
oldItem.resolvedName == newItem.resolvedName && oldItem.year == newItem.year
|
||||||
}
|
}
|
||||||
|
@ -249,7 +249,7 @@ private constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
val DIFFER =
|
val DIFFER =
|
||||||
object : ItemDiffCallback<Song>() {
|
object : SimpleItemCallback<Song>() {
|
||||||
override fun areItemsTheSame(oldItem: Song, newItem: Song) =
|
override fun areItemsTheSame(oldItem: Song, newItem: Song) =
|
||||||
oldItem.resolvedName == newItem.resolvedName &&
|
oldItem.resolvedName == newItem.resolvedName &&
|
||||||
oldItem.resolvedAlbumName == newItem.resolvedAlbumName
|
oldItem.resolvedAlbumName == newItem.resolvedAlbumName
|
||||||
|
|
|
@ -25,13 +25,14 @@ import androidx.recyclerview.widget.DiffUtil
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import org.oxycblt.auxio.IntegerTable
|
import org.oxycblt.auxio.IntegerTable
|
||||||
import org.oxycblt.auxio.databinding.ItemSortHeaderBinding
|
import org.oxycblt.auxio.databinding.ItemSortHeaderBinding
|
||||||
|
import org.oxycblt.auxio.ui.AsyncBackingData
|
||||||
import org.oxycblt.auxio.ui.BindingViewHolder
|
import org.oxycblt.auxio.ui.BindingViewHolder
|
||||||
import org.oxycblt.auxio.ui.Header
|
import org.oxycblt.auxio.ui.Header
|
||||||
import org.oxycblt.auxio.ui.Item
|
import org.oxycblt.auxio.ui.Item
|
||||||
import org.oxycblt.auxio.ui.ItemDiffCallback
|
|
||||||
import org.oxycblt.auxio.ui.MenuItemListener
|
import org.oxycblt.auxio.ui.MenuItemListener
|
||||||
import org.oxycblt.auxio.ui.MultiAdapter
|
import org.oxycblt.auxio.ui.MultiAdapter
|
||||||
import org.oxycblt.auxio.ui.NewHeaderViewHolder
|
import org.oxycblt.auxio.ui.NewHeaderViewHolder
|
||||||
|
import org.oxycblt.auxio.ui.SimpleItemCallback
|
||||||
import org.oxycblt.auxio.util.context
|
import org.oxycblt.auxio.util.context
|
||||||
import org.oxycblt.auxio.util.getViewHolderAt
|
import org.oxycblt.auxio.util.getViewHolderAt
|
||||||
import org.oxycblt.auxio.util.inflater
|
import org.oxycblt.auxio.util.inflater
|
||||||
|
@ -41,7 +42,7 @@ import org.oxycblt.auxio.util.textSafe
|
||||||
abstract class DetailAdapter<L : DetailItemListener>(
|
abstract class DetailAdapter<L : DetailItemListener>(
|
||||||
listener: L,
|
listener: L,
|
||||||
diffCallback: DiffUtil.ItemCallback<Item>
|
diffCallback: DiffUtil.ItemCallback<Item>
|
||||||
) : MultiAdapter<L>(listener, diffCallback) {
|
) : MultiAdapter<L>(listener) {
|
||||||
abstract fun onHighlightViewHolder(viewHolder: Highlightable, item: Item)
|
abstract fun onHighlightViewHolder(viewHolder: Highlightable, item: Item)
|
||||||
|
|
||||||
protected inline fun <reified T : Item> highlightItem(
|
protected inline fun <reified T : Item> highlightItem(
|
||||||
|
@ -54,7 +55,7 @@ abstract class DetailAdapter<L : DetailItemListener>(
|
||||||
|
|
||||||
// Use existing data instead of having to re-sort it.
|
// Use existing data instead of having to re-sort it.
|
||||||
// We also have to account for the album count when searching for the ViewHolder.
|
// We also have to account for the album count when searching for the ViewHolder.
|
||||||
val pos = mCurrentList.indexOfFirst { item -> item.id == newItem.id && item is T }
|
val pos = data.currentList.indexOfFirst { item -> item.id == newItem.id && item is T }
|
||||||
|
|
||||||
// Check if the ViewHolder for this song is visible, if it is then highlight it.
|
// Check if the ViewHolder for this song is visible, if it is then highlight it.
|
||||||
// If the ViewHolder is not visible, then the adapter should take care of it if
|
// If the ViewHolder is not visible, then the adapter should take care of it if
|
||||||
|
@ -70,6 +71,8 @@ abstract class DetailAdapter<L : DetailItemListener>(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suppress("LeakingThis") override val data = AsyncBackingData(this, diffCallback)
|
||||||
|
|
||||||
override fun getCreatorFromItem(item: Item) =
|
override fun getCreatorFromItem(item: Item) =
|
||||||
when (item) {
|
when (item) {
|
||||||
is Header -> NewHeaderViewHolder.CREATOR
|
is Header -> NewHeaderViewHolder.CREATOR
|
||||||
|
@ -97,7 +100,7 @@ abstract class DetailAdapter<L : DetailItemListener>(
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val DIFFER =
|
val DIFFER =
|
||||||
object : ItemDiffCallback<Item>() {
|
object : SimpleItemCallback<Item>() {
|
||||||
override fun areItemsTheSame(oldItem: Item, newItem: Item): Boolean {
|
override fun areItemsTheSame(oldItem: Item, newItem: Item): Boolean {
|
||||||
return when {
|
return when {
|
||||||
oldItem is Header && newItem is Header ->
|
oldItem is Header && newItem is Header ->
|
||||||
|
@ -134,7 +137,7 @@ class SortHeaderViewHolder(private val binding: ItemSortHeaderBinding) :
|
||||||
}
|
}
|
||||||
|
|
||||||
val DIFFER =
|
val DIFFER =
|
||||||
object : ItemDiffCallback<SortHeader>() {
|
object : SimpleItemCallback<SortHeader>() {
|
||||||
override fun areItemsTheSame(oldItem: SortHeader, newItem: SortHeader) =
|
override fun areItemsTheSame(oldItem: SortHeader, newItem: SortHeader) =
|
||||||
oldItem.string == newItem.string
|
oldItem.string == newItem.string
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,8 +29,8 @@ import org.oxycblt.auxio.music.Genre
|
||||||
import org.oxycblt.auxio.music.Song
|
import org.oxycblt.auxio.music.Song
|
||||||
import org.oxycblt.auxio.ui.BindingViewHolder
|
import org.oxycblt.auxio.ui.BindingViewHolder
|
||||||
import org.oxycblt.auxio.ui.Item
|
import org.oxycblt.auxio.ui.Item
|
||||||
import org.oxycblt.auxio.ui.ItemDiffCallback
|
|
||||||
import org.oxycblt.auxio.ui.MenuItemListener
|
import org.oxycblt.auxio.ui.MenuItemListener
|
||||||
|
import org.oxycblt.auxio.ui.SimpleItemCallback
|
||||||
import org.oxycblt.auxio.ui.SongViewHolder
|
import org.oxycblt.auxio.ui.SongViewHolder
|
||||||
import org.oxycblt.auxio.util.context
|
import org.oxycblt.auxio.util.context
|
||||||
import org.oxycblt.auxio.util.getPluralSafe
|
import org.oxycblt.auxio.util.getPluralSafe
|
||||||
|
@ -100,7 +100,7 @@ class GenreDetailAdapter(listener: DetailItemListener) :
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val DIFFER =
|
val DIFFER =
|
||||||
object : ItemDiffCallback<Item>() {
|
object : SimpleItemCallback<Item>() {
|
||||||
override fun areItemsTheSame(oldItem: Item, newItem: Item): Boolean {
|
override fun areItemsTheSame(oldItem: Item, newItem: Item): Boolean {
|
||||||
return when {
|
return when {
|
||||||
oldItem is Genre && newItem is Genre ->
|
oldItem is Genre && newItem is Genre ->
|
||||||
|
@ -137,7 +137,7 @@ private class GenreDetailViewHolder private constructor(private val binding: Ite
|
||||||
}
|
}
|
||||||
|
|
||||||
val DIFFER =
|
val DIFFER =
|
||||||
object : ItemDiffCallback<Genre>() {
|
object : SimpleItemCallback<Genre>() {
|
||||||
override fun areItemsTheSame(oldItem: Genre, newItem: Genre) =
|
override fun areItemsTheSame(oldItem: Genre, newItem: Genre) =
|
||||||
oldItem.resolvedName == newItem.resolvedName &&
|
oldItem.resolvedName == newItem.resolvedName &&
|
||||||
oldItem.songs.size == newItem.songs.size &&
|
oldItem.songs.size == newItem.songs.size &&
|
||||||
|
|
|
@ -18,17 +18,17 @@
|
||||||
package org.oxycblt.auxio.home.list
|
package org.oxycblt.auxio.home.list
|
||||||
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import androidx.lifecycle.LiveData
|
|
||||||
import androidx.navigation.fragment.findNavController
|
import androidx.navigation.fragment.findNavController
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import org.oxycblt.auxio.R
|
import org.oxycblt.auxio.R
|
||||||
import org.oxycblt.auxio.home.HomeFragmentDirections
|
import org.oxycblt.auxio.home.HomeFragmentDirections
|
||||||
import org.oxycblt.auxio.music.Album
|
import org.oxycblt.auxio.music.Album
|
||||||
import org.oxycblt.auxio.ui.AlbumViewHolder
|
import org.oxycblt.auxio.ui.AlbumViewHolder
|
||||||
import org.oxycblt.auxio.ui.BindingViewHolder
|
|
||||||
import org.oxycblt.auxio.ui.DisplayMode
|
import org.oxycblt.auxio.ui.DisplayMode
|
||||||
import org.oxycblt.auxio.ui.Item
|
import org.oxycblt.auxio.ui.Item
|
||||||
import org.oxycblt.auxio.ui.MenuItemListener
|
import org.oxycblt.auxio.ui.MenuItemListener
|
||||||
import org.oxycblt.auxio.ui.MonoAdapter
|
import org.oxycblt.auxio.ui.MonoAdapter
|
||||||
|
import org.oxycblt.auxio.ui.PrimitiveBackingData
|
||||||
import org.oxycblt.auxio.ui.Sort
|
import org.oxycblt.auxio.ui.Sort
|
||||||
import org.oxycblt.auxio.ui.newMenu
|
import org.oxycblt.auxio.ui.newMenu
|
||||||
import org.oxycblt.auxio.ui.sliceArticle
|
import org.oxycblt.auxio.ui.sliceArticle
|
||||||
|
@ -38,10 +38,16 @@ import org.oxycblt.auxio.ui.sliceArticle
|
||||||
* @author
|
* @author
|
||||||
*/
|
*/
|
||||||
class AlbumListFragment : HomeListFragment<Album>() {
|
class AlbumListFragment : HomeListFragment<Album>() {
|
||||||
override val recyclerId: Int = R.id.home_album_list
|
private val homeAdapter = AlbumAdapter(this)
|
||||||
override val homeAdapter = AlbumAdapter(this)
|
|
||||||
override val homeData: LiveData<List<Album>>
|
override fun setupRecycler(recycler: RecyclerView) {
|
||||||
get() = homeModel.albums
|
recycler.apply {
|
||||||
|
id = R.id.home_album_list
|
||||||
|
adapter = homeAdapter
|
||||||
|
}
|
||||||
|
|
||||||
|
homeModel.albums.observe(viewLifecycleOwner) { list -> homeAdapter.data.submitList(list) }
|
||||||
|
}
|
||||||
|
|
||||||
override fun getPopup(pos: Int): String? {
|
override fun getPopup(pos: Int): String? {
|
||||||
val album = homeModel.albums.value!![pos]
|
val album = homeModel.albums.value!![pos]
|
||||||
|
@ -72,8 +78,8 @@ class AlbumListFragment : HomeListFragment<Album>() {
|
||||||
}
|
}
|
||||||
|
|
||||||
class AlbumAdapter(listener: MenuItemListener) :
|
class AlbumAdapter(listener: MenuItemListener) :
|
||||||
MonoAdapter<Album, MenuItemListener, AlbumViewHolder>(listener, AlbumViewHolder.DIFFER) {
|
MonoAdapter<Album, MenuItemListener, AlbumViewHolder>(listener) {
|
||||||
override val creator: BindingViewHolder.Creator<AlbumViewHolder>
|
override val data = PrimitiveBackingData<Album>(this)
|
||||||
get() = AlbumViewHolder.CREATOR
|
override val creator = AlbumViewHolder.CREATOR
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,8 +18,8 @@
|
||||||
package org.oxycblt.auxio.home.list
|
package org.oxycblt.auxio.home.list
|
||||||
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import androidx.lifecycle.LiveData
|
|
||||||
import androidx.navigation.fragment.findNavController
|
import androidx.navigation.fragment.findNavController
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import org.oxycblt.auxio.R
|
import org.oxycblt.auxio.R
|
||||||
import org.oxycblt.auxio.home.HomeFragmentDirections
|
import org.oxycblt.auxio.home.HomeFragmentDirections
|
||||||
import org.oxycblt.auxio.music.Artist
|
import org.oxycblt.auxio.music.Artist
|
||||||
|
@ -27,6 +27,7 @@ import org.oxycblt.auxio.ui.ArtistViewHolder
|
||||||
import org.oxycblt.auxio.ui.Item
|
import org.oxycblt.auxio.ui.Item
|
||||||
import org.oxycblt.auxio.ui.MenuItemListener
|
import org.oxycblt.auxio.ui.MenuItemListener
|
||||||
import org.oxycblt.auxio.ui.MonoAdapter
|
import org.oxycblt.auxio.ui.MonoAdapter
|
||||||
|
import org.oxycblt.auxio.ui.PrimitiveBackingData
|
||||||
import org.oxycblt.auxio.ui.newMenu
|
import org.oxycblt.auxio.ui.newMenu
|
||||||
import org.oxycblt.auxio.ui.sliceArticle
|
import org.oxycblt.auxio.ui.sliceArticle
|
||||||
|
|
||||||
|
@ -35,10 +36,16 @@ import org.oxycblt.auxio.ui.sliceArticle
|
||||||
* @author
|
* @author
|
||||||
*/
|
*/
|
||||||
class ArtistListFragment : HomeListFragment<Artist>() {
|
class ArtistListFragment : HomeListFragment<Artist>() {
|
||||||
override val recyclerId: Int = R.id.home_artist_list
|
private val homeAdapter = ArtistAdapter(this)
|
||||||
override val homeAdapter = ArtistAdapter(this)
|
|
||||||
override val homeData: LiveData<List<Artist>>
|
override fun setupRecycler(recycler: RecyclerView) {
|
||||||
get() = homeModel.artists
|
recycler.apply {
|
||||||
|
id = R.id.home_artist_list
|
||||||
|
adapter = homeAdapter
|
||||||
|
}
|
||||||
|
|
||||||
|
homeModel.artists.observe(viewLifecycleOwner) { list -> homeAdapter.data.submitList(list) }
|
||||||
|
}
|
||||||
|
|
||||||
override fun getPopup(pos: Int) =
|
override fun getPopup(pos: Int) =
|
||||||
homeModel.artists.value!![pos].resolvedName.sliceArticle().first().uppercase()
|
homeModel.artists.value!![pos].resolvedName.sliceArticle().first().uppercase()
|
||||||
|
@ -53,7 +60,8 @@ class ArtistListFragment : HomeListFragment<Artist>() {
|
||||||
}
|
}
|
||||||
|
|
||||||
class ArtistAdapter(listener: MenuItemListener) :
|
class ArtistAdapter(listener: MenuItemListener) :
|
||||||
MonoAdapter<Artist, MenuItemListener, ArtistViewHolder>(listener, ArtistViewHolder.DIFFER) {
|
MonoAdapter<Artist, MenuItemListener, ArtistViewHolder>(listener) {
|
||||||
|
override val data = PrimitiveBackingData<Artist>(this)
|
||||||
override val creator = ArtistViewHolder.CREATOR
|
override val creator = ArtistViewHolder.CREATOR
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,8 +18,8 @@
|
||||||
package org.oxycblt.auxio.home.list
|
package org.oxycblt.auxio.home.list
|
||||||
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import androidx.lifecycle.LiveData
|
|
||||||
import androidx.navigation.fragment.findNavController
|
import androidx.navigation.fragment.findNavController
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import org.oxycblt.auxio.R
|
import org.oxycblt.auxio.R
|
||||||
import org.oxycblt.auxio.home.HomeFragmentDirections
|
import org.oxycblt.auxio.home.HomeFragmentDirections
|
||||||
import org.oxycblt.auxio.music.Genre
|
import org.oxycblt.auxio.music.Genre
|
||||||
|
@ -27,6 +27,7 @@ import org.oxycblt.auxio.ui.GenreViewHolder
|
||||||
import org.oxycblt.auxio.ui.Item
|
import org.oxycblt.auxio.ui.Item
|
||||||
import org.oxycblt.auxio.ui.MenuItemListener
|
import org.oxycblt.auxio.ui.MenuItemListener
|
||||||
import org.oxycblt.auxio.ui.MonoAdapter
|
import org.oxycblt.auxio.ui.MonoAdapter
|
||||||
|
import org.oxycblt.auxio.ui.PrimitiveBackingData
|
||||||
import org.oxycblt.auxio.ui.newMenu
|
import org.oxycblt.auxio.ui.newMenu
|
||||||
import org.oxycblt.auxio.ui.sliceArticle
|
import org.oxycblt.auxio.ui.sliceArticle
|
||||||
|
|
||||||
|
@ -35,10 +36,16 @@ import org.oxycblt.auxio.ui.sliceArticle
|
||||||
* @author
|
* @author
|
||||||
*/
|
*/
|
||||||
class GenreListFragment : HomeListFragment<Genre>() {
|
class GenreListFragment : HomeListFragment<Genre>() {
|
||||||
override val recyclerId = R.id.home_genre_list
|
private val homeAdapter = GenreAdapter(this)
|
||||||
override val homeAdapter = GenreAdapter(this)
|
|
||||||
override val homeData: LiveData<List<Genre>>
|
override fun setupRecycler(recycler: RecyclerView) {
|
||||||
get() = homeModel.genres
|
recycler.apply {
|
||||||
|
id = R.id.home_genre_list
|
||||||
|
adapter = homeAdapter
|
||||||
|
}
|
||||||
|
|
||||||
|
homeModel.genres.observe(viewLifecycleOwner) { list -> homeAdapter.data.submitList(list) }
|
||||||
|
}
|
||||||
|
|
||||||
override fun getPopup(pos: Int) =
|
override fun getPopup(pos: Int) =
|
||||||
homeModel.genres.value!![pos].resolvedName.sliceArticle().first().uppercase()
|
homeModel.genres.value!![pos].resolvedName.sliceArticle().first().uppercase()
|
||||||
|
@ -53,7 +60,8 @@ class GenreListFragment : HomeListFragment<Genre>() {
|
||||||
}
|
}
|
||||||
|
|
||||||
class GenreAdapter(listener: MenuItemListener) :
|
class GenreAdapter(listener: MenuItemListener) :
|
||||||
MonoAdapter<Genre, MenuItemListener, GenreViewHolder>(listener, GenreViewHolder.DIFFER) {
|
MonoAdapter<Genre, MenuItemListener, GenreViewHolder>(listener) {
|
||||||
|
override val data = PrimitiveBackingData<Genre>(this)
|
||||||
override val creator = GenreViewHolder.CREATOR
|
override val creator = GenreViewHolder.CREATOR
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,17 +21,14 @@ import android.os.Bundle
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.fragment.app.activityViewModels
|
import androidx.fragment.app.activityViewModels
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import org.oxycblt.auxio.databinding.FragmentHomeListBinding
|
import org.oxycblt.auxio.databinding.FragmentHomeListBinding
|
||||||
import org.oxycblt.auxio.home.HomeViewModel
|
import org.oxycblt.auxio.home.HomeViewModel
|
||||||
import org.oxycblt.auxio.home.fastscroll.FastScrollRecyclerView
|
import org.oxycblt.auxio.home.fastscroll.FastScrollRecyclerView
|
||||||
import org.oxycblt.auxio.playback.PlaybackViewModel
|
import org.oxycblt.auxio.playback.PlaybackViewModel
|
||||||
import org.oxycblt.auxio.ui.BindingViewHolder
|
|
||||||
import org.oxycblt.auxio.ui.Item
|
import org.oxycblt.auxio.ui.Item
|
||||||
import org.oxycblt.auxio.ui.MenuItemListener
|
import org.oxycblt.auxio.ui.MenuItemListener
|
||||||
import org.oxycblt.auxio.ui.MonoAdapter
|
|
||||||
import org.oxycblt.auxio.ui.ViewBindingFragment
|
import org.oxycblt.auxio.ui.ViewBindingFragment
|
||||||
import org.oxycblt.auxio.util.applySpans
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A Base [Fragment] implementing the base features shared across all list fragments in the home UI.
|
* A Base [Fragment] implementing the base features shared across all list fragments in the home UI.
|
||||||
|
@ -42,11 +39,7 @@ abstract class HomeListFragment<T : Item> :
|
||||||
MenuItemListener,
|
MenuItemListener,
|
||||||
FastScrollRecyclerView.PopupProvider,
|
FastScrollRecyclerView.PopupProvider,
|
||||||
FastScrollRecyclerView.OnFastScrollListener {
|
FastScrollRecyclerView.OnFastScrollListener {
|
||||||
/** The popup provider to use for the fast scroller view. */
|
abstract fun setupRecycler(recycler: RecyclerView)
|
||||||
abstract val recyclerId: Int
|
|
||||||
abstract val homeAdapter:
|
|
||||||
MonoAdapter<T, MenuItemListener, out BindingViewHolder<T, MenuItemListener>>
|
|
||||||
abstract val homeData: LiveData<List<T>>
|
|
||||||
|
|
||||||
protected val homeModel: HomeViewModel by activityViewModels()
|
protected val homeModel: HomeViewModel by activityViewModels()
|
||||||
protected val playbackModel: PlaybackViewModel by activityViewModels()
|
protected val playbackModel: PlaybackViewModel by activityViewModels()
|
||||||
|
@ -55,18 +48,9 @@ abstract class HomeListFragment<T : Item> :
|
||||||
FragmentHomeListBinding.inflate(inflater)
|
FragmentHomeListBinding.inflate(inflater)
|
||||||
|
|
||||||
override fun onBindingCreated(binding: FragmentHomeListBinding, savedInstanceState: Bundle?) {
|
override fun onBindingCreated(binding: FragmentHomeListBinding, savedInstanceState: Bundle?) {
|
||||||
binding.homeRecycler.apply {
|
setupRecycler(binding.homeRecycler)
|
||||||
id = recyclerId
|
|
||||||
adapter = homeAdapter
|
|
||||||
applySpans()
|
|
||||||
}
|
|
||||||
|
|
||||||
binding.homeRecycler.popupProvider = this
|
binding.homeRecycler.popupProvider = this
|
||||||
binding.homeRecycler.onDragListener = this
|
binding.homeRecycler.onDragListener = this
|
||||||
|
|
||||||
homeData.observe(viewLifecycleOwner) { list ->
|
|
||||||
homeAdapter.submitListHard(list.toMutableList())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroyBinding(binding: FragmentHomeListBinding) {
|
override fun onDestroyBinding(binding: FragmentHomeListBinding) {
|
||||||
|
|
|
@ -18,13 +18,14 @@
|
||||||
package org.oxycblt.auxio.home.list
|
package org.oxycblt.auxio.home.list
|
||||||
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import org.oxycblt.auxio.R
|
import org.oxycblt.auxio.R
|
||||||
import org.oxycblt.auxio.music.Song
|
import org.oxycblt.auxio.music.Song
|
||||||
import org.oxycblt.auxio.ui.DisplayMode
|
import org.oxycblt.auxio.ui.DisplayMode
|
||||||
import org.oxycblt.auxio.ui.Item
|
import org.oxycblt.auxio.ui.Item
|
||||||
import org.oxycblt.auxio.ui.MenuItemListener
|
import org.oxycblt.auxio.ui.MenuItemListener
|
||||||
import org.oxycblt.auxio.ui.MonoAdapter
|
import org.oxycblt.auxio.ui.MonoAdapter
|
||||||
|
import org.oxycblt.auxio.ui.PrimitiveBackingData
|
||||||
import org.oxycblt.auxio.ui.SongViewHolder
|
import org.oxycblt.auxio.ui.SongViewHolder
|
||||||
import org.oxycblt.auxio.ui.Sort
|
import org.oxycblt.auxio.ui.Sort
|
||||||
import org.oxycblt.auxio.ui.newMenu
|
import org.oxycblt.auxio.ui.newMenu
|
||||||
|
@ -35,10 +36,16 @@ import org.oxycblt.auxio.ui.sliceArticle
|
||||||
* @author
|
* @author
|
||||||
*/
|
*/
|
||||||
class SongListFragment : HomeListFragment<Song>() {
|
class SongListFragment : HomeListFragment<Song>() {
|
||||||
override val recyclerId = R.id.home_song_list
|
private val homeAdapter = SongsAdapter(this)
|
||||||
override val homeAdapter = SongsAdapter(this)
|
|
||||||
override val homeData: LiveData<List<Song>>
|
override fun setupRecycler(recycler: RecyclerView) {
|
||||||
get() = homeModel.songs
|
recycler.apply {
|
||||||
|
id = R.id.home_song_list
|
||||||
|
adapter = homeAdapter
|
||||||
|
}
|
||||||
|
|
||||||
|
homeModel.songs.observe(viewLifecycleOwner) { list -> homeAdapter.data.submitList(list) }
|
||||||
|
}
|
||||||
|
|
||||||
override fun getPopup(pos: Int): String {
|
override fun getPopup(pos: Int): String {
|
||||||
val song = homeModel.songs.value!![pos]
|
val song = homeModel.songs.value!![pos]
|
||||||
|
@ -71,7 +78,8 @@ class SongListFragment : HomeListFragment<Song>() {
|
||||||
}
|
}
|
||||||
|
|
||||||
inner class SongsAdapter(listener: MenuItemListener) :
|
inner class SongsAdapter(listener: MenuItemListener) :
|
||||||
MonoAdapter<Song, MenuItemListener, SongViewHolder>(listener, SongViewHolder.DIFFER) {
|
MonoAdapter<Song, MenuItemListener, SongViewHolder>(listener) {
|
||||||
|
override val data = PrimitiveBackingData<Song>(this)
|
||||||
override val creator = SongViewHolder.CREATOR
|
override val creator = SongViewHolder.CREATOR
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,6 +30,7 @@ import org.oxycblt.auxio.coil.bindAlbumCover
|
||||||
import org.oxycblt.auxio.databinding.ItemQueueSongBinding
|
import org.oxycblt.auxio.databinding.ItemQueueSongBinding
|
||||||
import org.oxycblt.auxio.music.Song
|
import org.oxycblt.auxio.music.Song
|
||||||
import org.oxycblt.auxio.ui.BindingViewHolder
|
import org.oxycblt.auxio.ui.BindingViewHolder
|
||||||
|
import org.oxycblt.auxio.ui.HybridBackingData
|
||||||
import org.oxycblt.auxio.ui.MonoAdapter
|
import org.oxycblt.auxio.ui.MonoAdapter
|
||||||
import org.oxycblt.auxio.ui.SongViewHolder
|
import org.oxycblt.auxio.ui.SongViewHolder
|
||||||
import org.oxycblt.auxio.util.disableDropShadowCompat
|
import org.oxycblt.auxio.util.disableDropShadowCompat
|
||||||
|
@ -37,9 +38,9 @@ import org.oxycblt.auxio.util.inflater
|
||||||
import org.oxycblt.auxio.util.stateList
|
import org.oxycblt.auxio.util.stateList
|
||||||
import org.oxycblt.auxio.util.textSafe
|
import org.oxycblt.auxio.util.textSafe
|
||||||
|
|
||||||
class NewQueueAdapter(listener: QueueItemListener) :
|
class QueueAdapter(listener: QueueItemListener) :
|
||||||
MonoAdapter<Song, QueueItemListener, QueueSongViewHolder>(
|
MonoAdapter<Song, QueueItemListener, QueueSongViewHolder>(listener) {
|
||||||
listener, QueueSongViewHolder.DIFFER) {
|
override val data = HybridBackingData(this, QueueSongViewHolder.DIFFER)
|
||||||
override val creator = QueueSongViewHolder.CREATOR
|
override val creator = QueueSongViewHolder.CREATOR
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -40,7 +40,7 @@ import org.oxycblt.auxio.util.logD
|
||||||
*/
|
*/
|
||||||
class QueueDragCallback(
|
class QueueDragCallback(
|
||||||
private val playbackModel: PlaybackViewModel,
|
private val playbackModel: PlaybackViewModel,
|
||||||
private val queueAdapter: NewQueueAdapter
|
private val queueAdapter: QueueAdapter
|
||||||
) : ItemTouchHelper.Callback() {
|
) : ItemTouchHelper.Callback() {
|
||||||
private var shouldLift = true
|
private var shouldLift = true
|
||||||
|
|
||||||
|
@ -154,12 +154,12 @@ class QueueDragCallback(
|
||||||
val from = viewHolder.bindingAdapterPosition
|
val from = viewHolder.bindingAdapterPosition
|
||||||
val to = target.bindingAdapterPosition
|
val to = target.bindingAdapterPosition
|
||||||
|
|
||||||
return playbackModel.moveQueueDataItems(from, to) { queueAdapter.moveItems(from, to) }
|
return playbackModel.moveQueueDataItems(from, to) { queueAdapter.data.moveItems(from, to) }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
|
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
|
||||||
playbackModel.removeQueueDataItem(viewHolder.bindingAdapterPosition) {
|
playbackModel.removeQueueDataItem(viewHolder.bindingAdapterPosition) {
|
||||||
queueAdapter.removeItem(viewHolder.bindingAdapterPosition)
|
queueAdapter.data.removeItem(viewHolder.bindingAdapterPosition)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -36,7 +36,7 @@ import org.oxycblt.auxio.util.requireAttached
|
||||||
*/
|
*/
|
||||||
class QueueFragment : ViewBindingFragment<FragmentQueueBinding>(), QueueItemListener {
|
class QueueFragment : ViewBindingFragment<FragmentQueueBinding>(), QueueItemListener {
|
||||||
private val playbackModel: PlaybackViewModel by activityViewModels()
|
private val playbackModel: PlaybackViewModel by activityViewModels()
|
||||||
private var queueAdapter = NewQueueAdapter(this)
|
private var queueAdapter = QueueAdapter(this)
|
||||||
private var touchHelper: ItemTouchHelper? = null
|
private var touchHelper: ItemTouchHelper? = null
|
||||||
private var callback: QueueDragCallback? = null
|
private var callback: QueueDragCallback? = null
|
||||||
|
|
||||||
|
@ -72,7 +72,7 @@ class QueueFragment : ViewBindingFragment<FragmentQueueBinding>(), QueueItemList
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
queueAdapter.submitList(queue.toMutableList())
|
queueAdapter.data.submitList(queue.toMutableList())
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun requireTouchHelper(): ItemTouchHelper {
|
private fun requireTouchHelper(): ItemTouchHelper {
|
||||||
|
|
|
@ -24,17 +24,19 @@ import org.oxycblt.auxio.music.Genre
|
||||||
import org.oxycblt.auxio.music.Song
|
import org.oxycblt.auxio.music.Song
|
||||||
import org.oxycblt.auxio.ui.AlbumViewHolder
|
import org.oxycblt.auxio.ui.AlbumViewHolder
|
||||||
import org.oxycblt.auxio.ui.ArtistViewHolder
|
import org.oxycblt.auxio.ui.ArtistViewHolder
|
||||||
|
import org.oxycblt.auxio.ui.AsyncBackingData
|
||||||
import org.oxycblt.auxio.ui.GenreViewHolder
|
import org.oxycblt.auxio.ui.GenreViewHolder
|
||||||
import org.oxycblt.auxio.ui.Header
|
import org.oxycblt.auxio.ui.Header
|
||||||
import org.oxycblt.auxio.ui.Item
|
import org.oxycblt.auxio.ui.Item
|
||||||
import org.oxycblt.auxio.ui.ItemDiffCallback
|
|
||||||
import org.oxycblt.auxio.ui.MenuItemListener
|
import org.oxycblt.auxio.ui.MenuItemListener
|
||||||
import org.oxycblt.auxio.ui.MultiAdapter
|
import org.oxycblt.auxio.ui.MultiAdapter
|
||||||
import org.oxycblt.auxio.ui.NewHeaderViewHolder
|
import org.oxycblt.auxio.ui.NewHeaderViewHolder
|
||||||
|
import org.oxycblt.auxio.ui.SimpleItemCallback
|
||||||
import org.oxycblt.auxio.ui.SongViewHolder
|
import org.oxycblt.auxio.ui.SongViewHolder
|
||||||
|
|
||||||
class NeoSearchAdapter(listener: MenuItemListener) :
|
class SearchAdapter(listener: MenuItemListener) : MultiAdapter<MenuItemListener>(listener) {
|
||||||
MultiAdapter<MenuItemListener>(listener, DIFFER) {
|
override val data = AsyncBackingData(this, DIFFER)
|
||||||
|
|
||||||
override fun getCreatorFromItem(item: Item) =
|
override fun getCreatorFromItem(item: Item) =
|
||||||
when (item) {
|
when (item) {
|
||||||
is Song -> SongViewHolder.CREATOR
|
is Song -> SongViewHolder.CREATOR
|
||||||
|
@ -72,7 +74,7 @@ class NeoSearchAdapter(listener: MenuItemListener) :
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val DIFFER =
|
private val DIFFER =
|
||||||
object : ItemDiffCallback<Item>() {
|
object : SimpleItemCallback<Item>() {
|
||||||
override fun areItemsTheSame(oldItem: Item, newItem: Item) =
|
override fun areItemsTheSame(oldItem: Item, newItem: Item) =
|
||||||
when {
|
when {
|
||||||
oldItem is Song && newItem is Song ->
|
oldItem is Song && newItem is Song ->
|
||||||
|
|
|
@ -58,7 +58,7 @@ class SearchFragment : ViewBindingFragment<FragmentSearchBinding>(), MenuItemLis
|
||||||
private val playbackModel: PlaybackViewModel by activityViewModels()
|
private val playbackModel: PlaybackViewModel by activityViewModels()
|
||||||
private val detailModel: DetailViewModel by activityViewModels()
|
private val detailModel: DetailViewModel by activityViewModels()
|
||||||
|
|
||||||
private val searchAdapter = NeoSearchAdapter(this)
|
private val searchAdapter = SearchAdapter(this)
|
||||||
private var imm: InputMethodManager? = null
|
private var imm: InputMethodManager? = null
|
||||||
private var launchedKeyboard = false
|
private var launchedKeyboard = false
|
||||||
|
|
||||||
|
@ -103,7 +103,7 @@ class SearchFragment : ViewBindingFragment<FragmentSearchBinding>(), MenuItemLis
|
||||||
|
|
||||||
binding.searchRecycler.apply {
|
binding.searchRecycler.apply {
|
||||||
adapter = searchAdapter
|
adapter = searchAdapter
|
||||||
applySpans { pos -> searchAdapter.currentList[pos] is Header }
|
applySpans { pos -> searchAdapter.data.currentList[pos] is Header }
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- VIEWMODEL SETUP ---
|
// --- VIEWMODEL SETUP ---
|
||||||
|
@ -161,7 +161,7 @@ class SearchFragment : ViewBindingFragment<FragmentSearchBinding>(), MenuItemLis
|
||||||
|
|
||||||
val binding = requireBinding()
|
val binding = requireBinding()
|
||||||
|
|
||||||
searchAdapter.submitList(results.toMutableList()) {
|
searchAdapter.data.submitList(results.toMutableList()) {
|
||||||
// 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.
|
||||||
|
|
|
@ -26,31 +26,212 @@ import androidx.recyclerview.widget.DiffUtil
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An adapter enabling both asynchronous list updates and synchronous list updates.
|
* An adapter for one viewholder tied to one type of data. All functionality is derived from the
|
||||||
*
|
* overridden values.
|
||||||
* DiffUtil is a joke. The animations are chaotic and gaudy, it does not preserve the scroll
|
|
||||||
* position of the RecyclerView, it refuses to play along with item movements, and the speed gains
|
|
||||||
* are minimal. We would rather want to use the slower yet more reliable notifyX in nearly all
|
|
||||||
* cases, however DiffUtil does have some use in places such as search, so we still want the ability
|
|
||||||
* to use a differ while also having access to the basic adapter primitives as well. This class
|
|
||||||
* achieves it through some terrible reflection magic, and is more or less the base for all adapters
|
|
||||||
* in the app.
|
|
||||||
*
|
|
||||||
* TODO: Delegate data management to the internal adapters so that we can isolate the horrible hacks
|
|
||||||
* to the specific adapters that use need them.
|
|
||||||
*/
|
*/
|
||||||
abstract class HybridAdapter<T, VH : RecyclerView.ViewHolder>(
|
abstract class MonoAdapter<T, L, VH : BindingViewHolder<T, L>>(private val listener: L) :
|
||||||
diffCallback: DiffUtil.ItemCallback<T>
|
RecyclerView.Adapter<VH>() {
|
||||||
) : RecyclerView.Adapter<VH>() {
|
/** The data that the adapter will source to bind viewholders. */
|
||||||
protected var mCurrentList = mutableListOf<T>()
|
abstract val data: BackingData<T>
|
||||||
|
/** The creator instance that all viewholders will be derived from. */
|
||||||
|
protected abstract val creator: BindingViewHolder.Creator<VH>
|
||||||
|
|
||||||
|
override fun getItemCount(): Int = data.getItemCount()
|
||||||
|
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =
|
||||||
|
creator.create(parent.context)
|
||||||
|
|
||||||
|
override fun onBindViewHolder(viewHolder: VH, position: Int) {
|
||||||
|
viewHolder.bind(data.getItem(position), listener)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private typealias AnyCreator = BindingViewHolder.Creator<out RecyclerView.ViewHolder>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An adapter for many viewholders tied to many types of data. Deriving this is more complicated
|
||||||
|
* than [MonoAdapter], as less overrides can be provided "for free".
|
||||||
|
*/
|
||||||
|
abstract class MultiAdapter<L>(private val listener: L) :
|
||||||
|
RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
||||||
|
|
||||||
|
/** The data that the adapter will source to bind viewholders. */
|
||||||
|
abstract val data: BackingData<Item>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get any creator from the given item. This is used to derive the view type. If there is no
|
||||||
|
* creator for the given item, return null.
|
||||||
|
*/
|
||||||
|
protected abstract fun getCreatorFromItem(item: Item): AnyCreator?
|
||||||
|
/**
|
||||||
|
* Get any creator from the given view type. This is used to create the viewholder itself.
|
||||||
|
* Ideally, one should compare the viewType to every creator's view type and return the one that
|
||||||
|
* matches. In cases where the view type is unexpected, return null.
|
||||||
|
*/
|
||||||
|
protected abstract fun getCreatorFromViewType(viewType: Int): AnyCreator?
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bind the given viewholder to an item. Casting must be done on the consumer's end due to
|
||||||
|
* bounds on [BindingViewHolder].
|
||||||
|
*/
|
||||||
|
protected abstract fun onBind(viewHolder: RecyclerView.ViewHolder, item: Item, listener: L)
|
||||||
|
|
||||||
|
override fun getItemCount(): Int = data.getItemCount()
|
||||||
|
|
||||||
|
override fun getItemViewType(position: Int) =
|
||||||
|
requireNotNull(getCreatorFromItem(data.getItem(position))) {
|
||||||
|
"Unable to get view type for item ${data.getItem(position)}"
|
||||||
|
}
|
||||||
|
.viewType
|
||||||
|
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =
|
||||||
|
requireNotNull(getCreatorFromViewType(viewType)) {
|
||||||
|
"Unable to create viewholder for view type $viewType"
|
||||||
|
}
|
||||||
|
.create(parent.context)
|
||||||
|
|
||||||
|
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
||||||
|
onBind(holder, data.getItem(position), listener)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A variation of [RecyclerView.ViewHolder] that enables ViewBinding. This is be used to provide a
|
||||||
|
* universal surface for binding data to a ViewHolder, and can be used with [MonoAdapter] to get an
|
||||||
|
* entire adapter implementation for free.
|
||||||
|
*/
|
||||||
|
abstract class BindingViewHolder<T, L>(root: View) : RecyclerView.ViewHolder(root) {
|
||||||
|
abstract fun bind(item: T, listener: L)
|
||||||
|
|
||||||
|
init {
|
||||||
|
// Force the layout to *actually* be the screen width
|
||||||
|
root.layoutParams =
|
||||||
|
RecyclerView.LayoutParams(
|
||||||
|
RecyclerView.LayoutParams.MATCH_PARENT, RecyclerView.LayoutParams.WRAP_CONTENT)
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Creator<VH : RecyclerView.ViewHolder> {
|
||||||
|
val viewType: Int
|
||||||
|
fun create(context: Context): VH
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** An interface for detecting if an item has been clicked once. */
|
||||||
|
interface ItemClickListener {
|
||||||
|
/** Called when an item is clicked once. */
|
||||||
|
fun onItemClick(item: Item)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** An interface for detecting if an item has had it's menu opened. */
|
||||||
|
interface MenuItemListener : ItemClickListener {
|
||||||
|
/** Called when an item desires to open a menu relating to it. */
|
||||||
|
fun onOpenMenu(item: Item, anchor: View)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The base for all items in Auxio. Any datatype can derive this type and gain some behavior not
|
||||||
|
* provided for free by the normal adapter implementations, such as certain types of diffing.
|
||||||
|
*/
|
||||||
|
abstract class Item {
|
||||||
|
/** A unique ID for this item. ***THIS IS NOT A MEDIASTORE ID!** */
|
||||||
|
abstract val id: Long
|
||||||
|
}
|
||||||
|
|
||||||
|
/** A data object used solely for the "Header" UI element. */
|
||||||
|
data class Header(
|
||||||
|
override val id: Long,
|
||||||
|
/** The string resource used for the header. */
|
||||||
|
@StringRes val string: Int
|
||||||
|
) : Item()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents data that backs a [MonoAdapter] or [MultiAdapter]. This can be implemented by any
|
||||||
|
* datatype to customize the organization or editing of data in a way that works best for the
|
||||||
|
* specific adapter.
|
||||||
|
*/
|
||||||
|
abstract class BackingData<T> {
|
||||||
|
/** Get an item at [position]. */
|
||||||
|
abstract fun getItem(position: Int): T
|
||||||
|
/** Get the total length of the backing data. */
|
||||||
|
abstract fun getItemCount(): Int
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A list-backed [BackingData] that is modified using adapter primitives. Useful in cases where
|
||||||
|
* [AsyncBackingData] is not preferable due to bugs involving diffing.
|
||||||
|
*/
|
||||||
|
class PrimitiveBackingData<T>(private val adapter: RecyclerView.Adapter<*>) : BackingData<T>() {
|
||||||
|
private var mCurrentList = mutableListOf<T>()
|
||||||
|
/** The current list backing this adapter. */
|
||||||
val currentList: List<T>
|
val currentList: List<T>
|
||||||
get() = mCurrentList
|
get() = mCurrentList
|
||||||
|
|
||||||
// Probably okay to leak this here since it's just a callback.
|
override fun getItem(position: Int): T = mCurrentList[position]
|
||||||
@Suppress("LeakingThis") private val differ = AsyncListDiffer(this, diffCallback)
|
override fun getItemCount(): Int = mCurrentList.size
|
||||||
|
|
||||||
protected fun getItem(position: Int): T = mCurrentList[position]
|
/**
|
||||||
|
* Update the list with a [newList]. This calls [RecyclerView.Adapter.notifyDataSetChanged]
|
||||||
|
* internally, which is inefficient but also the most reliable update callback.
|
||||||
|
*/
|
||||||
|
@Suppress("NotifyDatasetChanged")
|
||||||
|
fun submitList(newList: List<T>) {
|
||||||
|
mCurrentList = newList.toMutableList()
|
||||||
|
adapter.notifyDataSetChanged()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Move an item from [from] to [to]. This calls [RecyclerView.Adapter.notifyItemMoved]
|
||||||
|
* internally.
|
||||||
|
*/
|
||||||
|
fun moveItems(from: Int, to: Int) {
|
||||||
|
mCurrentList.add(to, mCurrentList.removeAt(from))
|
||||||
|
adapter.notifyItemMoved(from, to)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A list-backed [BackingData] that is modified with [AsyncListDiffer]. This is useful in cases
|
||||||
|
* where data updates are rapid-fire and unpredictable, and where the benefits of asynchronously
|
||||||
|
* diffing the adapter outweigh the shortcomings.
|
||||||
|
*/
|
||||||
|
class AsyncBackingData<T>(
|
||||||
|
adapter: RecyclerView.Adapter<*>,
|
||||||
|
diffCallback: DiffUtil.ItemCallback<T>
|
||||||
|
) : BackingData<T>() {
|
||||||
|
private var differ = AsyncListDiffer(adapter, diffCallback)
|
||||||
|
/** The current list backing this adapter. */
|
||||||
|
val currentList: List<T>
|
||||||
|
get() = differ.currentList
|
||||||
|
|
||||||
|
override fun getItem(position: Int): T = differ.currentList[position]
|
||||||
|
override fun getItemCount(): Int = differ.currentList.size
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Submit a list for [AsyncListDiffer] to calculate. Any previous calls of [submitList] will be
|
||||||
|
* dropped.
|
||||||
|
*/
|
||||||
|
fun submitList(newList: List<T>, onDone: () -> Unit = {}) {
|
||||||
|
differ.submitList(newList, onDone)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A list-backed [BackingData] that can be modified with both adapter primitives and
|
||||||
|
* [AsyncListDiffer]. Never use this class unless absolutely necessary, such as when dealing with
|
||||||
|
* item dragging. This is mostly because the class is a terrible hacky mess that could easily crash
|
||||||
|
* the app if you are not careful with it. You have been warned.
|
||||||
|
*/
|
||||||
|
class HybridBackingData<T>(
|
||||||
|
private val adapter: RecyclerView.Adapter<*>,
|
||||||
|
diffCallback: DiffUtil.ItemCallback<T>
|
||||||
|
) : BackingData<T>() {
|
||||||
|
private var mCurrentList = mutableListOf<T>()
|
||||||
|
val currentList: List<T>
|
||||||
|
get() = mCurrentList
|
||||||
|
|
||||||
|
private val differ = AsyncListDiffer(adapter, diffCallback)
|
||||||
|
|
||||||
|
override fun getItem(position: Int): T = mCurrentList[position]
|
||||||
override fun getItemCount(): Int = mCurrentList.size
|
override fun getItemCount(): Int = mCurrentList.size
|
||||||
|
|
||||||
fun submitList(newData: List<T>, onDone: () -> Unit = {}) {
|
fun submitList(newData: List<T>, onDone: () -> Unit = {}) {
|
||||||
|
@ -60,25 +241,25 @@ abstract class HybridAdapter<T, VH : RecyclerView.ViewHolder>(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("NotifyDatasetChanged")
|
// @Suppress("NotifyDatasetChanged")
|
||||||
fun submitListHard(newList: List<T>) {
|
// fun submitListHard(newList: List<T>) {
|
||||||
if (newList != mCurrentList) {
|
// if (newList != mCurrentList) {
|
||||||
mCurrentList = newList.toMutableList()
|
// mCurrentList = newList.toMutableList()
|
||||||
differ.rewriteListUnsafe(mCurrentList)
|
// differ.rewriteListUnsafe(mCurrentList)
|
||||||
notifyDataSetChanged()
|
// adapter.notifyDataSetChanged()
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
fun moveItems(from: Int, to: Int) {
|
fun moveItems(from: Int, to: Int) {
|
||||||
mCurrentList.add(to, mCurrentList.removeAt(from))
|
mCurrentList.add(to, mCurrentList.removeAt(from))
|
||||||
differ.rewriteListUnsafe(mCurrentList)
|
differ.rewriteListUnsafe(mCurrentList)
|
||||||
notifyItemMoved(from, to)
|
adapter.notifyItemMoved(from, to)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun removeItem(at: Int) {
|
fun removeItem(at: Int) {
|
||||||
mCurrentList.removeAt(at)
|
mCurrentList.removeAt(at)
|
||||||
differ.rewriteListUnsafe(mCurrentList)
|
differ.rewriteListUnsafe(mCurrentList)
|
||||||
notifyItemRemoved(at)
|
adapter.notifyItemRemoved(at)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -108,87 +289,13 @@ abstract class HybridAdapter<T, VH : RecyclerView.ViewHolder>(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract class MonoAdapter<T, L, VH : BindingViewHolder<T, L>>(
|
/**
|
||||||
private val listener: L,
|
* A base [DiffUtil.ItemCallback] that automatically provides an implementation of
|
||||||
diffCallback: DiffUtil.ItemCallback<T>
|
* [areContentsTheSame] any object that is derived from [Item].
|
||||||
) : HybridAdapter<T, VH>(diffCallback) {
|
*/
|
||||||
protected abstract val creator: BindingViewHolder.Creator<VH>
|
abstract class SimpleItemCallback<T : Item> : DiffUtil.ItemCallback<T>() {
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =
|
|
||||||
creator.create(parent.context)
|
|
||||||
|
|
||||||
override fun onBindViewHolder(viewHolder: VH, position: Int) {
|
|
||||||
viewHolder.bind(getItem(position), listener)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract class MultiAdapter<L>(private val listener: L, diffCallback: DiffUtil.ItemCallback<Item>) :
|
|
||||||
HybridAdapter<Item, RecyclerView.ViewHolder>(diffCallback) {
|
|
||||||
abstract fun getCreatorFromItem(
|
|
||||||
item: Item
|
|
||||||
): BindingViewHolder.Creator<out RecyclerView.ViewHolder>?
|
|
||||||
abstract fun getCreatorFromViewType(
|
|
||||||
viewType: Int
|
|
||||||
): BindingViewHolder.Creator<out RecyclerView.ViewHolder>?
|
|
||||||
abstract fun onBind(viewHolder: RecyclerView.ViewHolder, item: Item, listener: L)
|
|
||||||
|
|
||||||
override fun getItemViewType(position: Int) =
|
|
||||||
requireNotNull(getCreatorFromItem(getItem(position))) {
|
|
||||||
"Unable to get view type for item ${getItem(position)}"
|
|
||||||
}
|
|
||||||
.viewType
|
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =
|
|
||||||
requireNotNull(getCreatorFromViewType(viewType)) {
|
|
||||||
"Unable to create viewholder for view type $viewType"
|
|
||||||
}
|
|
||||||
.create(parent.context)
|
|
||||||
|
|
||||||
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
|
||||||
onBind(holder, getItem(position), listener)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** The base for all items in Auxio. */
|
|
||||||
abstract class Item {
|
|
||||||
/** A unique ID for this item. ***THIS IS NOT A MEDIASTORE ID!** */
|
|
||||||
abstract val id: Long
|
|
||||||
}
|
|
||||||
|
|
||||||
/** A data object used solely for the "Header" UI element. */
|
|
||||||
data class Header(
|
|
||||||
override val id: Long,
|
|
||||||
/** The string resource used for the header. */
|
|
||||||
@StringRes val string: Int
|
|
||||||
) : Item()
|
|
||||||
|
|
||||||
abstract class ItemDiffCallback<T : Item> : DiffUtil.ItemCallback<T>() {
|
|
||||||
override fun areContentsTheSame(oldItem: T, newItem: T): Boolean {
|
override fun areContentsTheSame(oldItem: T, newItem: T): Boolean {
|
||||||
if (oldItem.javaClass != newItem.javaClass) return false
|
if (oldItem.javaClass != newItem.javaClass) return false
|
||||||
return oldItem.id == newItem.id
|
return oldItem.id == newItem.id
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ItemClickListener {
|
|
||||||
fun onItemClick(item: Item)
|
|
||||||
}
|
|
||||||
|
|
||||||
interface MenuItemListener : ItemClickListener {
|
|
||||||
fun onOpenMenu(item: Item, anchor: View)
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract class BindingViewHolder<T, L>(root: View) : RecyclerView.ViewHolder(root) {
|
|
||||||
abstract fun bind(item: T, listener: L)
|
|
||||||
|
|
||||||
init {
|
|
||||||
// Force the layout to *actually* be the screen width
|
|
||||||
root.layoutParams =
|
|
||||||
RecyclerView.LayoutParams(
|
|
||||||
RecyclerView.LayoutParams.MATCH_PARENT, RecyclerView.LayoutParams.WRAP_CONTENT)
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Creator<VH : RecyclerView.ViewHolder> {
|
|
||||||
val viewType: Int
|
|
||||||
fun create(context: Context): VH
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -35,6 +35,7 @@ import org.oxycblt.auxio.util.getPluralSafe
|
||||||
import org.oxycblt.auxio.util.inflater
|
import org.oxycblt.auxio.util.inflater
|
||||||
import org.oxycblt.auxio.util.textSafe
|
import org.oxycblt.auxio.util.textSafe
|
||||||
|
|
||||||
|
/** The shared ViewHolder for a [Song]. */
|
||||||
class SongViewHolder private constructor(private val binding: ItemSongBinding) :
|
class SongViewHolder private constructor(private val binding: ItemSongBinding) :
|
||||||
BindingViewHolder<Song, MenuItemListener>(binding.root) {
|
BindingViewHolder<Song, MenuItemListener>(binding.root) {
|
||||||
override fun bind(item: Song, listener: MenuItemListener) {
|
override fun bind(item: Song, listener: MenuItemListener) {
|
||||||
|
@ -61,7 +62,7 @@ class SongViewHolder private constructor(private val binding: ItemSongBinding) :
|
||||||
}
|
}
|
||||||
|
|
||||||
val DIFFER =
|
val DIFFER =
|
||||||
object : ItemDiffCallback<Song>() {
|
object : SimpleItemCallback<Song>() {
|
||||||
override fun areItemsTheSame(oldItem: Song, newItem: Song) =
|
override fun areItemsTheSame(oldItem: Song, newItem: Song) =
|
||||||
oldItem.resolvedName == newItem.resolvedName &&
|
oldItem.resolvedName == newItem.resolvedName &&
|
||||||
oldItem.resolvedArtistName == oldItem.resolvedArtistName
|
oldItem.resolvedArtistName == oldItem.resolvedArtistName
|
||||||
|
@ -69,7 +70,7 @@ class SongViewHolder private constructor(private val binding: ItemSongBinding) :
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** The Shared ViewHolder for a [Album]. Instantiation should be done with [from]. */
|
/** The Shared ViewHolder for a [Album]. */
|
||||||
class AlbumViewHolder
|
class AlbumViewHolder
|
||||||
private constructor(
|
private constructor(
|
||||||
private val binding: ItemParentBinding,
|
private val binding: ItemParentBinding,
|
||||||
|
@ -99,7 +100,7 @@ private constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
val DIFFER =
|
val DIFFER =
|
||||||
object : ItemDiffCallback<Album>() {
|
object : SimpleItemCallback<Album>() {
|
||||||
override fun areItemsTheSame(oldItem: Album, newItem: Album) =
|
override fun areItemsTheSame(oldItem: Album, newItem: Album) =
|
||||||
oldItem.resolvedName == newItem.resolvedName &&
|
oldItem.resolvedName == newItem.resolvedName &&
|
||||||
oldItem.resolvedArtistName == newItem.resolvedArtistName
|
oldItem.resolvedArtistName == newItem.resolvedArtistName
|
||||||
|
@ -139,7 +140,7 @@ class ArtistViewHolder private constructor(private val binding: ItemParentBindin
|
||||||
}
|
}
|
||||||
|
|
||||||
val DIFFER =
|
val DIFFER =
|
||||||
object : ItemDiffCallback<Artist>() {
|
object : SimpleItemCallback<Artist>() {
|
||||||
override fun areItemsTheSame(oldItem: Artist, newItem: Artist) =
|
override fun areItemsTheSame(oldItem: Artist, newItem: Artist) =
|
||||||
oldItem.resolvedName == newItem.resolvedName &&
|
oldItem.resolvedName == newItem.resolvedName &&
|
||||||
oldItem.albums.size == newItem.albums.size &&
|
oldItem.albums.size == newItem.albums.size &&
|
||||||
|
@ -179,7 +180,7 @@ private constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
val DIFFER =
|
val DIFFER =
|
||||||
object : ItemDiffCallback<Genre>() {
|
object : SimpleItemCallback<Genre>() {
|
||||||
override fun areItemsTheSame(oldItem: Genre, newItem: Genre): Boolean =
|
override fun areItemsTheSame(oldItem: Genre, newItem: Genre): Boolean =
|
||||||
oldItem.resolvedName == newItem.resolvedName &&
|
oldItem.resolvedName == newItem.resolvedName &&
|
||||||
oldItem.songs.size == newItem.songs.size
|
oldItem.songs.size == newItem.songs.size
|
||||||
|
@ -187,7 +188,7 @@ private constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** The Shared ViewHolder for a [Header]. Instantiation should be done with [from] */
|
/** The Shared ViewHolder for a [Header]. */
|
||||||
class NewHeaderViewHolder private constructor(private val binding: ItemHeaderBinding) :
|
class NewHeaderViewHolder private constructor(private val binding: ItemHeaderBinding) :
|
||||||
BindingViewHolder<Header, Unit>(binding.root) {
|
BindingViewHolder<Header, Unit>(binding.root) {
|
||||||
|
|
||||||
|
@ -206,7 +207,7 @@ class NewHeaderViewHolder private constructor(private val binding: ItemHeaderBin
|
||||||
}
|
}
|
||||||
|
|
||||||
val DIFFER =
|
val DIFFER =
|
||||||
object : ItemDiffCallback<Header>() {
|
object : SimpleItemCallback<Header>() {
|
||||||
override fun areItemsTheSame(oldItem: Header, newItem: Header): Boolean =
|
override fun areItemsTheSame(oldItem: Header, newItem: Header): Boolean =
|
||||||
oldItem.string == newItem.string
|
oldItem.string == newItem.string
|
||||||
}
|
}
|
||||||
|
|
|
@ -66,6 +66,7 @@ class WidgetProvider : AppWidgetProvider() {
|
||||||
}
|
}
|
||||||
|
|
||||||
loadWidgetBitmap(context, song) { bitmap ->
|
loadWidgetBitmap(context, song) { bitmap ->
|
||||||
|
logD(bitmap == null)
|
||||||
val state =
|
val state =
|
||||||
WidgetState(
|
WidgetState(
|
||||||
song,
|
song,
|
||||||
|
|
|
@ -100,9 +100,11 @@ Attempting to use it as a `MediaStore` ID will result in errors.
|
||||||
- Any field or method beginning with `internal` is off-limits. These fields are meant for use within `MusicLoader` and generally
|
- Any field or method beginning with `internal` is off-limits. These fields are meant for use within `MusicLoader` and generally
|
||||||
provide poor UX to the user. The only reason they are public is to make the loading process not have to rely on separate "Raw"
|
provide poor UX to the user. The only reason they are public is to make the loading process not have to rely on separate "Raw"
|
||||||
objects.
|
objects.
|
||||||
- Generally, `name` is used when saving music data to storage, while `resolvedName` is used when displaying music data to the user.
|
- Generally, `rawName` is used when doing internal work, such as saving music data, while `resolvedName` is used when displaying music data to the user.
|
||||||
- For `Song` instances in particular, prefer `resolvedAlbumName` and `resolvedArtistName` over `album.resolvedName` and `album.artist.resolvedName`
|
- For `Song` instances in particular, prefer `resolvedAlbumName` and `resolvedArtistName` over `album.resolvedName` and `album.artist.resolvedName`,
|
||||||
- For `Album` instances in particular, prefer `resolvedArtistName` over `artist.resolvedName`
|
as these resolve the name in context of the song.
|
||||||
|
- For `Album` instances in particular, prefer `resolvedArtistName` over `artist.resolvedName`, which don't actually do anything but add consistency
|
||||||
|
to the `Song` function
|
||||||
|
|
||||||
#### Music Access
|
#### Music Access
|
||||||
All music on a system is asynchronously loaded into the shared object `MusicStore`. Because of this, **`MusicStore` may not be available at all times**.
|
All music on a system is asynchronously loaded into the shared object `MusicStore`. Because of this, **`MusicStore` may not be available at all times**.
|
||||||
|
|
Loading…
Reference in a new issue