list: tweak header/divider object hierarchy

Make a new generic Header/Divider superclass that all
headers derive.

This allows disc headers to be recognized generically
in places like the grid layout manager.
This commit is contained in:
Alexander Capehart 2024-10-17 09:57:47 -06:00
parent 1ee5645780
commit 9883cf1c91
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
10 changed files with 47 additions and 39 deletions

View file

@ -31,10 +31,10 @@ import kotlin.math.min
import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.FragmentDetailBinding
import org.oxycblt.auxio.detail.list.DetailListAdapter
import org.oxycblt.auxio.list.Divider
import org.oxycblt.auxio.list.Header
import org.oxycblt.auxio.list.ListFragment
import org.oxycblt.auxio.list.ListViewModel
import org.oxycblt.auxio.list.PlainDivider
import org.oxycblt.auxio.list.PlainHeader
import org.oxycblt.auxio.music.Music
import org.oxycblt.auxio.music.MusicParent
import org.oxycblt.auxio.music.MusicViewModel
@ -91,7 +91,7 @@ abstract class DetailFragment<P : MusicParent, C : Music> :
detailModel.artistSongList.value.getOrElse(it - 1) {
return@setFullWidthLookup false
}
item is Divider || item is Header
item is PlainDivider || item is PlainHeader
} else {
true
}

View file

@ -34,10 +34,10 @@ import org.oxycblt.auxio.detail.list.DiscHeader
import org.oxycblt.auxio.detail.list.EditHeader
import org.oxycblt.auxio.detail.list.SortHeader
import org.oxycblt.auxio.list.BasicHeader
import org.oxycblt.auxio.list.Divider
import org.oxycblt.auxio.list.Header
import org.oxycblt.auxio.list.Item
import org.oxycblt.auxio.list.ListSettings
import org.oxycblt.auxio.list.PlainDivider
import org.oxycblt.auxio.list.PlainHeader
import org.oxycblt.auxio.list.adapter.UpdateInstructions
import org.oxycblt.auxio.list.sort.Sort
import org.oxycblt.auxio.music.Album
@ -531,7 +531,7 @@ constructor(
list: MutableStateFlow<List<Item>>,
instructions: MutableEvent<UpdateInstructions>,
replace: Int?,
songHeader: (Int) -> Header = { SortHeader(it) }
songHeader: (Int) -> PlainHeader = { SortHeader(it) }
) {
if (detail == null) {
parent.value = null
@ -547,7 +547,7 @@ constructor(
if (section is DetailSection.Songs) songHeader(section.stringRes)
else BasicHeader(section.stringRes)
if (newList.isNotEmpty()) {
newList.add(Divider(header))
newList.add(PlainDivider(header))
}
newList.add(header)
section.items
@ -555,7 +555,7 @@ constructor(
is DetailSection.Discs -> {
val header = SortHeader(section.stringRes)
if (newList.isNotEmpty()) {
newList.add(Divider(header))
newList.add(PlainDivider(header))
}
newList.add(header)
buildList<Item> {
@ -600,7 +600,7 @@ constructor(
val list = mutableListOf<Item>()
if (edited.isNotEmpty()) {
val header = EditHeader(R.string.lbl_songs)
list.add(Divider(header))
list.add(PlainDivider(header))
list.add(header)
list.addAll(edited)
}

View file

@ -29,6 +29,8 @@ import org.oxycblt.auxio.IntegerTable
import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.ItemAlbumSongBinding
import org.oxycblt.auxio.databinding.ItemDiscHeaderBinding
import org.oxycblt.auxio.list.Divider
import org.oxycblt.auxio.list.Header
import org.oxycblt.auxio.list.Item
import org.oxycblt.auxio.list.SelectableListListener
import org.oxycblt.auxio.list.adapter.SelectionIndicatorAdapter
@ -100,9 +102,9 @@ class AlbumDetailListAdapter(private val listener: Listener<Song>) :
*
* @author Alexander Capehart (OxygenCobalt)
*/
data class DiscHeader(val inner: Disc?) : Item
data class DiscHeader(val inner: Disc?) : Header
data class DiscDivider(val anchor: DiscHeader?) : Item
data class DiscDivider(override val anchor: DiscHeader?) : Divider<DiscHeader>
/**
* A [RecyclerView.ViewHolder] that displays a [DiscHeader] to delimit different disc groups. Use

View file

@ -27,9 +27,9 @@ import androidx.recyclerview.widget.RecyclerView
import org.oxycblt.auxio.IntegerTable
import org.oxycblt.auxio.databinding.ItemSortHeaderBinding
import org.oxycblt.auxio.list.BasicHeader
import org.oxycblt.auxio.list.Divider
import org.oxycblt.auxio.list.Header
import org.oxycblt.auxio.list.Item
import org.oxycblt.auxio.list.PlainDivider
import org.oxycblt.auxio.list.PlainHeader
import org.oxycblt.auxio.list.SelectableListListener
import org.oxycblt.auxio.list.adapter.SelectionIndicatorAdapter
import org.oxycblt.auxio.list.adapter.SimpleDiffCallback
@ -55,7 +55,7 @@ abstract class DetailListAdapter(
override fun getItemViewType(position: Int) =
when (getItem(position)) {
// Implement support for headers and sort headers
is Divider -> DividerViewHolder.VIEW_TYPE
is PlainDivider -> DividerViewHolder.VIEW_TYPE
is BasicHeader -> BasicHeaderViewHolder.VIEW_TYPE
is SortHeader -> SortHeaderViewHolder.VIEW_TYPE
else -> super.getItemViewType(position)
@ -91,7 +91,7 @@ abstract class DetailListAdapter(
object : SimpleDiffCallback<Item>() {
override fun areContentsTheSame(oldItem: Item, newItem: Item): Boolean {
return when {
oldItem is Divider && newItem is Divider ->
oldItem is PlainDivider && newItem is PlainDivider ->
DividerViewHolder.DIFF_CALLBACK.areContentsTheSame(oldItem, newItem)
oldItem is BasicHeader && newItem is BasicHeader ->
BasicHeaderViewHolder.DIFF_CALLBACK.areContentsTheSame(oldItem, newItem)
@ -110,7 +110,7 @@ abstract class DetailListAdapter(
* @param titleRes The string resource to use as the header title
* @author Alexander Capehart (OxygenCobalt)
*/
data class SortHeader(@StringRes override val titleRes: Int) : Header
data class SortHeader(@StringRes override val titleRes: Int) : PlainHeader
/**
* A [RecyclerView.ViewHolder] that displays a [SortHeader] and it's actions. Use [from] to create

View file

@ -33,8 +33,8 @@ import org.oxycblt.auxio.IntegerTable
import org.oxycblt.auxio.databinding.ItemEditHeaderBinding
import org.oxycblt.auxio.databinding.ItemEditableSongBinding
import org.oxycblt.auxio.list.EditableListListener
import org.oxycblt.auxio.list.Header
import org.oxycblt.auxio.list.Item
import org.oxycblt.auxio.list.PlainHeader
import org.oxycblt.auxio.list.adapter.PlayingIndicatorAdapter
import org.oxycblt.auxio.list.adapter.SelectionIndicatorAdapter
import org.oxycblt.auxio.list.adapter.SimpleDiffCallback
@ -140,12 +140,12 @@ class PlaylistDetailListAdapter(private val listener: Listener) :
}
/**
* A [Header] variant that displays an edit button.
* A [PlainHeader] variant that displays an edit button.
*
* @param titleRes The string resource to use as the header title
* @author Alexander Capehart (OxygenCobalt)
*/
data class EditHeader(@StringRes override val titleRes: Int) : Header
data class EditHeader(@StringRes override val titleRes: Int) : PlainHeader
/**
* Displays an [EditHeader] and it's actions. Use [from] to create an instance.

View file

@ -24,12 +24,14 @@ import androidx.annotation.StringRes
/** A marker for something that is a RecyclerView item. Has no functionality on it's own. */
interface Item
interface Header : Item
/**
* A "header" used for delimiting groups of data.
*
* @author Alexander Capehart (OxygenCobalt)
*/
interface Header : Item {
interface PlainHeader : Header {
/** The string resource used for the header's title. */
val titleRes: Int
}
@ -40,12 +42,16 @@ interface Header : Item {
* @param titleRes The string resource used for the header's title.
* @author Alexander Capehart (OxygenCobalt)
*/
data class BasicHeader(@StringRes override val titleRes: Int) : Header
data class BasicHeader(@StringRes override val titleRes: Int) : PlainHeader
interface Divider<T> : Item {
val anchor: T?
}
/**
* A divider decoration used to delimit groups of data.
*
* @param anchor The [Header] this divider should be next to in a list. Used as a way to preserve
* divider continuity during list updates.
* @param anchor The [PlainHeader] this divider should be next to in a list. Used as a way to
* preserve divider continuity during list updates.
*/
data class Divider(val anchor: Header?) : Item
data class PlainDivider(override val anchor: PlainHeader?) : Divider<PlainHeader>

View file

@ -27,7 +27,7 @@ import org.oxycblt.auxio.databinding.ItemHeaderBinding
import org.oxycblt.auxio.databinding.ItemParentBinding
import org.oxycblt.auxio.databinding.ItemSongBinding
import org.oxycblt.auxio.list.BasicHeader
import org.oxycblt.auxio.list.Divider
import org.oxycblt.auxio.list.PlainDivider
import org.oxycblt.auxio.list.SelectableListListener
import org.oxycblt.auxio.list.adapter.SelectionIndicatorAdapter
import org.oxycblt.auxio.list.adapter.SimpleDiffCallback
@ -360,7 +360,7 @@ class BasicHeaderViewHolder private constructor(private val binding: ItemHeaderB
}
/**
* A [RecyclerView.ViewHolder] that displays a [Divider]. Use [from] to create an instance.
* A [RecyclerView.ViewHolder] that displays a [PlainDivider]. Use [from] to create an instance.
*
* @author Alexander Capehart (OxygenCobalt)
*/
@ -381,8 +381,8 @@ class DividerViewHolder private constructor(divider: MaterialDivider) :
/** A comparator that can be used with DiffUtil. */
val DIFF_CALLBACK =
object : SimpleDiffCallback<Divider>() {
override fun areContentsTheSame(oldItem: Divider, newItem: Divider) =
object : SimpleDiffCallback<PlainDivider>() {
override fun areContentsTheSame(oldItem: PlainDivider, newItem: PlainDivider) =
oldItem.anchor == newItem.anchor
}
}

View file

@ -21,8 +21,8 @@ package org.oxycblt.auxio.search
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import org.oxycblt.auxio.list.BasicHeader
import org.oxycblt.auxio.list.Divider
import org.oxycblt.auxio.list.Item
import org.oxycblt.auxio.list.PlainDivider
import org.oxycblt.auxio.list.SelectableListListener
import org.oxycblt.auxio.list.adapter.SelectionIndicatorAdapter
import org.oxycblt.auxio.list.adapter.SimpleDiffCallback
@ -57,7 +57,7 @@ class SearchAdapter(private val listener: SelectableListListener<Music>) :
is Artist -> ArtistViewHolder.VIEW_TYPE
is Genre -> GenreViewHolder.VIEW_TYPE
is Playlist -> PlaylistViewHolder.VIEW_TYPE
is Divider -> DividerViewHolder.VIEW_TYPE
is PlainDivider -> DividerViewHolder.VIEW_TYPE
is BasicHeader -> BasicHeaderViewHolder.VIEW_TYPE
else -> super.getItemViewType(position)
}
@ -102,7 +102,7 @@ class SearchAdapter(private val listener: SelectableListListener<Music>) :
GenreViewHolder.DIFF_CALLBACK.areContentsTheSame(oldItem, newItem)
oldItem is Playlist && newItem is Playlist ->
PlaylistViewHolder.DIFF_CALLBACK.areContentsTheSame(oldItem, newItem)
oldItem is Divider && newItem is Divider ->
oldItem is PlainDivider && newItem is PlainDivider ->
DividerViewHolder.DIFF_CALLBACK.areContentsTheSame(oldItem, newItem)
oldItem is BasicHeader && newItem is BasicHeader ->
BasicHeaderViewHolder.DIFF_CALLBACK.areContentsTheSame(oldItem, newItem)

View file

@ -38,11 +38,11 @@ import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.FragmentSearchBinding
import org.oxycblt.auxio.detail.DetailViewModel
import org.oxycblt.auxio.detail.Show
import org.oxycblt.auxio.list.Divider
import org.oxycblt.auxio.list.Header
import org.oxycblt.auxio.list.Item
import org.oxycblt.auxio.list.ListFragment
import org.oxycblt.auxio.list.ListViewModel
import org.oxycblt.auxio.list.PlainDivider
import org.oxycblt.auxio.list.PlainHeader
import org.oxycblt.auxio.list.menu.Menu
import org.oxycblt.auxio.music.Album
import org.oxycblt.auxio.music.Artist
@ -153,7 +153,7 @@ class SearchFragment : ListFragment<Music, FragmentSearchBinding>() {
searchModel.searchResults.value.getOrElse(it) {
return@setFullWidthLookup false
}
item is Divider || item is Header
item is PlainDivider || item is PlainHeader
}
}

View file

@ -30,8 +30,8 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.yield
import org.oxycblt.auxio.R
import org.oxycblt.auxio.list.BasicHeader
import org.oxycblt.auxio.list.Divider
import org.oxycblt.auxio.list.Item
import org.oxycblt.auxio.list.PlainDivider
import org.oxycblt.auxio.list.sort.Sort
import org.oxycblt.auxio.music.MusicRepository
import org.oxycblt.auxio.music.MusicType
@ -152,7 +152,7 @@ constructor(
logD("Adding ${it.size} albums to search results")
val header = BasicHeader(R.string.lbl_albums)
if (isNotEmpty()) {
add(Divider(header))
add(PlainDivider(header))
}
add(header)
@ -162,7 +162,7 @@ constructor(
logD("Adding ${it.size} playlists to search results")
val header = BasicHeader(R.string.lbl_playlists)
if (isNotEmpty()) {
add(Divider(header))
add(PlainDivider(header))
}
add(header)
@ -172,7 +172,7 @@ constructor(
logD("Adding ${it.size} genres to search results")
val header = BasicHeader(R.string.lbl_genres)
if (isNotEmpty()) {
add(Divider(header))
add(PlainDivider(header))
}
add(header)
@ -182,7 +182,7 @@ constructor(
logD("Adding ${it.size} songs to search results")
val header = BasicHeader(R.string.lbl_songs)
if (isNotEmpty()) {
add(Divider(header))
add(PlainDivider(header))
}
add(header)