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.R
import org.oxycblt.auxio.databinding.FragmentDetailBinding import org.oxycblt.auxio.databinding.FragmentDetailBinding
import org.oxycblt.auxio.detail.list.DetailListAdapter 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.ListFragment
import org.oxycblt.auxio.list.ListViewModel 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.Music
import org.oxycblt.auxio.music.MusicParent import org.oxycblt.auxio.music.MusicParent
import org.oxycblt.auxio.music.MusicViewModel import org.oxycblt.auxio.music.MusicViewModel
@ -91,7 +91,7 @@ abstract class DetailFragment<P : MusicParent, C : Music> :
detailModel.artistSongList.value.getOrElse(it - 1) { detailModel.artistSongList.value.getOrElse(it - 1) {
return@setFullWidthLookup false return@setFullWidthLookup false
} }
item is Divider || item is Header item is PlainDivider || item is PlainHeader
} else { } else {
true 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.EditHeader
import org.oxycblt.auxio.detail.list.SortHeader import org.oxycblt.auxio.detail.list.SortHeader
import org.oxycblt.auxio.list.BasicHeader 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.Item
import org.oxycblt.auxio.list.ListSettings 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.adapter.UpdateInstructions
import org.oxycblt.auxio.list.sort.Sort import org.oxycblt.auxio.list.sort.Sort
import org.oxycblt.auxio.music.Album import org.oxycblt.auxio.music.Album
@ -531,7 +531,7 @@ constructor(
list: MutableStateFlow<List<Item>>, list: MutableStateFlow<List<Item>>,
instructions: MutableEvent<UpdateInstructions>, instructions: MutableEvent<UpdateInstructions>,
replace: Int?, replace: Int?,
songHeader: (Int) -> Header = { SortHeader(it) } songHeader: (Int) -> PlainHeader = { SortHeader(it) }
) { ) {
if (detail == null) { if (detail == null) {
parent.value = null parent.value = null
@ -547,7 +547,7 @@ constructor(
if (section is DetailSection.Songs) songHeader(section.stringRes) if (section is DetailSection.Songs) songHeader(section.stringRes)
else BasicHeader(section.stringRes) else BasicHeader(section.stringRes)
if (newList.isNotEmpty()) { if (newList.isNotEmpty()) {
newList.add(Divider(header)) newList.add(PlainDivider(header))
} }
newList.add(header) newList.add(header)
section.items section.items
@ -555,7 +555,7 @@ constructor(
is DetailSection.Discs -> { is DetailSection.Discs -> {
val header = SortHeader(section.stringRes) val header = SortHeader(section.stringRes)
if (newList.isNotEmpty()) { if (newList.isNotEmpty()) {
newList.add(Divider(header)) newList.add(PlainDivider(header))
} }
newList.add(header) newList.add(header)
buildList<Item> { buildList<Item> {
@ -600,7 +600,7 @@ constructor(
val list = mutableListOf<Item>() val list = mutableListOf<Item>()
if (edited.isNotEmpty()) { if (edited.isNotEmpty()) {
val header = EditHeader(R.string.lbl_songs) val header = EditHeader(R.string.lbl_songs)
list.add(Divider(header)) list.add(PlainDivider(header))
list.add(header) list.add(header)
list.addAll(edited) list.addAll(edited)
} }

View file

@ -29,6 +29,8 @@ import org.oxycblt.auxio.IntegerTable
import org.oxycblt.auxio.R import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.ItemAlbumSongBinding import org.oxycblt.auxio.databinding.ItemAlbumSongBinding
import org.oxycblt.auxio.databinding.ItemDiscHeaderBinding 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.Item
import org.oxycblt.auxio.list.SelectableListListener import org.oxycblt.auxio.list.SelectableListListener
import org.oxycblt.auxio.list.adapter.SelectionIndicatorAdapter import org.oxycblt.auxio.list.adapter.SelectionIndicatorAdapter
@ -100,9 +102,9 @@ class AlbumDetailListAdapter(private val listener: Listener<Song>) :
* *
* @author Alexander Capehart (OxygenCobalt) * @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 * 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.IntegerTable
import org.oxycblt.auxio.databinding.ItemSortHeaderBinding import org.oxycblt.auxio.databinding.ItemSortHeaderBinding
import org.oxycblt.auxio.list.BasicHeader 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.Item
import org.oxycblt.auxio.list.PlainDivider
import org.oxycblt.auxio.list.PlainHeader
import org.oxycblt.auxio.list.SelectableListListener import org.oxycblt.auxio.list.SelectableListListener
import org.oxycblt.auxio.list.adapter.SelectionIndicatorAdapter import org.oxycblt.auxio.list.adapter.SelectionIndicatorAdapter
import org.oxycblt.auxio.list.adapter.SimpleDiffCallback import org.oxycblt.auxio.list.adapter.SimpleDiffCallback
@ -55,7 +55,7 @@ abstract class DetailListAdapter(
override fun getItemViewType(position: Int) = override fun getItemViewType(position: Int) =
when (getItem(position)) { when (getItem(position)) {
// Implement support for headers and sort headers // Implement support for headers and sort headers
is Divider -> DividerViewHolder.VIEW_TYPE is PlainDivider -> DividerViewHolder.VIEW_TYPE
is BasicHeader -> BasicHeaderViewHolder.VIEW_TYPE is BasicHeader -> BasicHeaderViewHolder.VIEW_TYPE
is SortHeader -> SortHeaderViewHolder.VIEW_TYPE is SortHeader -> SortHeaderViewHolder.VIEW_TYPE
else -> super.getItemViewType(position) else -> super.getItemViewType(position)
@ -91,7 +91,7 @@ abstract class DetailListAdapter(
object : SimpleDiffCallback<Item>() { object : SimpleDiffCallback<Item>() {
override fun areContentsTheSame(oldItem: Item, newItem: Item): Boolean { override fun areContentsTheSame(oldItem: Item, newItem: Item): Boolean {
return when { return when {
oldItem is Divider && newItem is Divider -> oldItem is PlainDivider && newItem is PlainDivider ->
DividerViewHolder.DIFF_CALLBACK.areContentsTheSame(oldItem, newItem) DividerViewHolder.DIFF_CALLBACK.areContentsTheSame(oldItem, newItem)
oldItem is BasicHeader && newItem is BasicHeader -> oldItem is BasicHeader && newItem is BasicHeader ->
BasicHeaderViewHolder.DIFF_CALLBACK.areContentsTheSame(oldItem, newItem) BasicHeaderViewHolder.DIFF_CALLBACK.areContentsTheSame(oldItem, newItem)
@ -110,7 +110,7 @@ abstract class DetailListAdapter(
* @param titleRes The string resource to use as the header title * @param titleRes The string resource to use as the header title
* @author Alexander Capehart (OxygenCobalt) * @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 * 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.ItemEditHeaderBinding
import org.oxycblt.auxio.databinding.ItemEditableSongBinding import org.oxycblt.auxio.databinding.ItemEditableSongBinding
import org.oxycblt.auxio.list.EditableListListener import org.oxycblt.auxio.list.EditableListListener
import org.oxycblt.auxio.list.Header
import org.oxycblt.auxio.list.Item 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.PlayingIndicatorAdapter
import org.oxycblt.auxio.list.adapter.SelectionIndicatorAdapter import org.oxycblt.auxio.list.adapter.SelectionIndicatorAdapter
import org.oxycblt.auxio.list.adapter.SimpleDiffCallback 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 * @param titleRes The string resource to use as the header title
* @author Alexander Capehart (OxygenCobalt) * @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. * 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. */ /** A marker for something that is a RecyclerView item. Has no functionality on it's own. */
interface Item interface Item
interface Header : Item
/** /**
* A "header" used for delimiting groups of data. * A "header" used for delimiting groups of data.
* *
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
interface Header : Item { interface PlainHeader : Header {
/** The string resource used for the header's title. */ /** The string resource used for the header's title. */
val titleRes: Int val titleRes: Int
} }
@ -40,12 +42,16 @@ interface Header : Item {
* @param titleRes The string resource used for the header's title. * @param titleRes The string resource used for the header's title.
* @author Alexander Capehart (OxygenCobalt) * @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. * 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 * @param anchor The [PlainHeader] this divider should be next to in a list. Used as a way to
* divider continuity during list updates. * 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.ItemParentBinding
import org.oxycblt.auxio.databinding.ItemSongBinding import org.oxycblt.auxio.databinding.ItemSongBinding
import org.oxycblt.auxio.list.BasicHeader 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.SelectableListListener
import org.oxycblt.auxio.list.adapter.SelectionIndicatorAdapter import org.oxycblt.auxio.list.adapter.SelectionIndicatorAdapter
import org.oxycblt.auxio.list.adapter.SimpleDiffCallback 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) * @author Alexander Capehart (OxygenCobalt)
*/ */
@ -381,8 +381,8 @@ class DividerViewHolder private constructor(divider: MaterialDivider) :
/** A comparator that can be used with DiffUtil. */ /** A comparator that can be used with DiffUtil. */
val DIFF_CALLBACK = val DIFF_CALLBACK =
object : SimpleDiffCallback<Divider>() { object : SimpleDiffCallback<PlainDivider>() {
override fun areContentsTheSame(oldItem: Divider, newItem: Divider) = override fun areContentsTheSame(oldItem: PlainDivider, newItem: PlainDivider) =
oldItem.anchor == newItem.anchor oldItem.anchor == newItem.anchor
} }
} }

View file

@ -21,8 +21,8 @@ package org.oxycblt.auxio.search
import android.view.ViewGroup import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import org.oxycblt.auxio.list.BasicHeader import org.oxycblt.auxio.list.BasicHeader
import org.oxycblt.auxio.list.Divider
import org.oxycblt.auxio.list.Item import org.oxycblt.auxio.list.Item
import org.oxycblt.auxio.list.PlainDivider
import org.oxycblt.auxio.list.SelectableListListener import org.oxycblt.auxio.list.SelectableListListener
import org.oxycblt.auxio.list.adapter.SelectionIndicatorAdapter import org.oxycblt.auxio.list.adapter.SelectionIndicatorAdapter
import org.oxycblt.auxio.list.adapter.SimpleDiffCallback import org.oxycblt.auxio.list.adapter.SimpleDiffCallback
@ -57,7 +57,7 @@ class SearchAdapter(private val listener: SelectableListListener<Music>) :
is Artist -> ArtistViewHolder.VIEW_TYPE is Artist -> ArtistViewHolder.VIEW_TYPE
is Genre -> GenreViewHolder.VIEW_TYPE is Genre -> GenreViewHolder.VIEW_TYPE
is Playlist -> PlaylistViewHolder.VIEW_TYPE is Playlist -> PlaylistViewHolder.VIEW_TYPE
is Divider -> DividerViewHolder.VIEW_TYPE is PlainDivider -> DividerViewHolder.VIEW_TYPE
is BasicHeader -> BasicHeaderViewHolder.VIEW_TYPE is BasicHeader -> BasicHeaderViewHolder.VIEW_TYPE
else -> super.getItemViewType(position) else -> super.getItemViewType(position)
} }
@ -102,7 +102,7 @@ class SearchAdapter(private val listener: SelectableListListener<Music>) :
GenreViewHolder.DIFF_CALLBACK.areContentsTheSame(oldItem, newItem) GenreViewHolder.DIFF_CALLBACK.areContentsTheSame(oldItem, newItem)
oldItem is Playlist && newItem is Playlist -> oldItem is Playlist && newItem is Playlist ->
PlaylistViewHolder.DIFF_CALLBACK.areContentsTheSame(oldItem, newItem) PlaylistViewHolder.DIFF_CALLBACK.areContentsTheSame(oldItem, newItem)
oldItem is Divider && newItem is Divider -> oldItem is PlainDivider && newItem is PlainDivider ->
DividerViewHolder.DIFF_CALLBACK.areContentsTheSame(oldItem, newItem) DividerViewHolder.DIFF_CALLBACK.areContentsTheSame(oldItem, newItem)
oldItem is BasicHeader && newItem is BasicHeader -> oldItem is BasicHeader && newItem is BasicHeader ->
BasicHeaderViewHolder.DIFF_CALLBACK.areContentsTheSame(oldItem, newItem) 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.databinding.FragmentSearchBinding
import org.oxycblt.auxio.detail.DetailViewModel import org.oxycblt.auxio.detail.DetailViewModel
import org.oxycblt.auxio.detail.Show 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.Item
import org.oxycblt.auxio.list.ListFragment import org.oxycblt.auxio.list.ListFragment
import org.oxycblt.auxio.list.ListViewModel 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.list.menu.Menu
import org.oxycblt.auxio.music.Album import org.oxycblt.auxio.music.Album
import org.oxycblt.auxio.music.Artist import org.oxycblt.auxio.music.Artist
@ -153,7 +153,7 @@ class SearchFragment : ListFragment<Music, FragmentSearchBinding>() {
searchModel.searchResults.value.getOrElse(it) { searchModel.searchResults.value.getOrElse(it) {
return@setFullWidthLookup false 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 kotlinx.coroutines.yield
import org.oxycblt.auxio.R import org.oxycblt.auxio.R
import org.oxycblt.auxio.list.BasicHeader import org.oxycblt.auxio.list.BasicHeader
import org.oxycblt.auxio.list.Divider
import org.oxycblt.auxio.list.Item import org.oxycblt.auxio.list.Item
import org.oxycblt.auxio.list.PlainDivider
import org.oxycblt.auxio.list.sort.Sort import org.oxycblt.auxio.list.sort.Sort
import org.oxycblt.auxio.music.MusicRepository import org.oxycblt.auxio.music.MusicRepository
import org.oxycblt.auxio.music.MusicType import org.oxycblt.auxio.music.MusicType
@ -152,7 +152,7 @@ constructor(
logD("Adding ${it.size} albums to search results") logD("Adding ${it.size} albums to search results")
val header = BasicHeader(R.string.lbl_albums) val header = BasicHeader(R.string.lbl_albums)
if (isNotEmpty()) { if (isNotEmpty()) {
add(Divider(header)) add(PlainDivider(header))
} }
add(header) add(header)
@ -162,7 +162,7 @@ constructor(
logD("Adding ${it.size} playlists to search results") logD("Adding ${it.size} playlists to search results")
val header = BasicHeader(R.string.lbl_playlists) val header = BasicHeader(R.string.lbl_playlists)
if (isNotEmpty()) { if (isNotEmpty()) {
add(Divider(header)) add(PlainDivider(header))
} }
add(header) add(header)
@ -172,7 +172,7 @@ constructor(
logD("Adding ${it.size} genres to search results") logD("Adding ${it.size} genres to search results")
val header = BasicHeader(R.string.lbl_genres) val header = BasicHeader(R.string.lbl_genres)
if (isNotEmpty()) { if (isNotEmpty()) {
add(Divider(header)) add(PlainDivider(header))
} }
add(header) add(header)
@ -182,7 +182,7 @@ constructor(
logD("Adding ${it.size} songs to search results") logD("Adding ${it.size} songs to search results")
val header = BasicHeader(R.string.lbl_songs) val header = BasicHeader(R.string.lbl_songs)
if (isNotEmpty()) { if (isNotEmpty()) {
add(Divider(header)) add(PlainDivider(header))
} }
add(header) add(header)