Update action header structure

Create a seperate ActionHeader object for headers with actions attached alongside a new viewholder to accompany it.
This allows action headers to be created easier without creating your own viewholder.
This commit is contained in:
OxygenCobalt 2021-04-25 17:15:20 -06:00
parent 029a4b1ff6
commit cc72ebc251
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
8 changed files with 76 additions and 62 deletions

View file

@ -1,6 +1,8 @@
package org.oxycblt.auxio.music
import android.net.Uri
import android.widget.ImageButton
import androidx.annotation.DrawableRes
// --- MUSIC MODELS ---
@ -189,10 +191,20 @@ data class Genre(
/**
* A data object used solely for the "Header" UI element. Inherits [BaseModel].
* @property isAction Value that marks whether this header should have an action attached to it.
*/
data class Header(
override val id: Long = -1,
override val name: String = "",
val isAction: Boolean = false
) : BaseModel()
/**
* A data object for a header with an action button. Inherits [BaseModel].
* @property icon The icon ot apply for this header
* @property action The callback that will be called when the action button is clicked.
*/
data class ActionHeader(
override val id: Long = -1,
override val name: String = "",
@DrawableRes val icon: Int,
val action: (button: ImageButton) -> Unit,
) : BaseModel()

View file

@ -16,9 +16,10 @@ import org.oxycblt.auxio.logD
* @author OxygenCobalt
*
* FIXME: Here's a catalog of problems that I already know about with this abomination
* - Does not support the album artist tag
* - All loading is done at startup [Not efficent for large libraries, would require massive arch retooling to fix]
* - Genre system is a bottleneck [Nothing I can do about it, MediaStore is garbage]
* - Does not support the album artist tag [Nothing I can do that doesn't involve rolling my own loader]
* - Genre system is a bottleneck [See Above]
* Blame MediaStore, loading anything on this platform is a nightmare.
*/
class MusicLoader(private val context: Context) {
var genres = mutableListOf<Genre>()
@ -100,7 +101,7 @@ class MusicLoader(private val context: Context) {
Albums.ARTIST, // 2
Albums.FIRST_YEAR, // 4
),
"", null,
null, null,
Albums.DEFAULT_SORT_ORDER
)

View file

@ -3,19 +3,18 @@ package org.oxycblt.auxio.playback.queue
import android.annotation.SuppressLint
import android.view.MotionEvent
import android.view.ViewGroup
import androidx.appcompat.widget.TooltipCompat
import androidx.recyclerview.widget.AsyncListDiffer
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView
import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.ItemActionHeaderBinding
import org.oxycblt.auxio.databinding.ItemQueueSongBinding
import org.oxycblt.auxio.logE
import org.oxycblt.auxio.music.ActionHeader
import org.oxycblt.auxio.music.BaseModel
import org.oxycblt.auxio.music.Header
import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.recycler.DiffCallback
import org.oxycblt.auxio.recycler.viewholders.ActionHeaderViewHolder
import org.oxycblt.auxio.recycler.viewholders.BaseViewHolder
import org.oxycblt.auxio.recycler.viewholders.HeaderViewHolder
import org.oxycblt.auxio.ui.inflater
@ -23,12 +22,10 @@ import org.oxycblt.auxio.ui.inflater
/**
* The single adapter for both the Next Queue and the User Queue.
* @param touchHelper The [ItemTouchHelper] ***containing*** [QueueDragCallback] to be used
* @param playbackModel The [PlaybackViewModel] to dispatch updates to.
* @author OxygenCobalt
*/
class QueueAdapter(
private val touchHelper: ItemTouchHelper,
private val playbackModel: PlaybackViewModel,
private val touchHelper: ItemTouchHelper
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
private var data = mutableListOf<BaseModel>()
private var listDiffer = AsyncListDiffer(this, DiffCallback())
@ -38,14 +35,11 @@ class QueueAdapter(
override fun getItemViewType(position: Int): Int {
val item = data[position]
return if (item is Header) {
if (item.isAction) {
USER_QUEUE_HEADER_ITEM_TYPE
} else {
HeaderViewHolder.ITEM_TYPE
}
} else {
QUEUE_SONG_ITEM_TYPE
return when (item) {
is Header -> HeaderViewHolder.ITEM_TYPE
is ActionHeader -> ActionHeaderViewHolder.ITEM_TYPE
is Song -> QUEUE_SONG_ITEM_TYPE
else -> -1
}
}
@ -53,7 +47,7 @@ class QueueAdapter(
return when (viewType) {
HeaderViewHolder.ITEM_TYPE -> HeaderViewHolder.from(parent.context)
USER_QUEUE_HEADER_ITEM_TYPE -> UserQueueHeaderViewHolder(
ActionHeaderViewHolder.ITEM_TYPE -> ActionHeaderViewHolder(
ItemActionHeaderBinding.inflate(parent.context.inflater)
)
@ -68,12 +62,8 @@ class QueueAdapter(
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when (val item = data[position]) {
is Song -> (holder as QueueSongViewHolder).bind(item)
is Header -> if (item.isAction) {
(holder as UserQueueHeaderViewHolder).bind(item)
} else {
(holder as HeaderViewHolder).bind(item)
}
is Header -> (holder as HeaderViewHolder).bind(item)
is ActionHeader -> (holder as ActionHeaderViewHolder).bind(item)
else -> logE("Bad data given to QueueAdapter.")
}
@ -158,34 +148,7 @@ class QueueAdapter(
}
}
/**
* ViewHolder for the **user queue header**. Has the clear queue button.
*/
inner class UserQueueHeaderViewHolder(
private val binding: ItemActionHeaderBinding
) : BaseViewHolder<Header>(binding) {
init {
binding.headerButton.apply {
contentDescription = context.getString(R.string.description_clear_user_queue)
TooltipCompat.setTooltipText(this, contentDescription)
}
}
override fun onBind(data: Header) {
binding.header = data
binding.headerButton.apply {
setImageResource(R.drawable.ic_clear)
setOnClickListener {
playbackModel.clearUserQueue()
}
}
}
}
companion object {
const val QUEUE_SONG_ITEM_TYPE = 0xA005
const val USER_QUEUE_HEADER_ITEM_TYPE = 0xA006
}
}

View file

@ -13,6 +13,7 @@ import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.ItemTouchHelper
import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.FragmentQueueBinding
import org.oxycblt.auxio.music.ActionHeader
import org.oxycblt.auxio.music.BaseModel
import org.oxycblt.auxio.music.Header
import org.oxycblt.auxio.playback.PlaybackViewModel
@ -36,7 +37,7 @@ class QueueFragment : Fragment() {
val callback = QueueDragCallback(playbackModel)
val helper = ItemTouchHelper(callback)
val queueAdapter = QueueAdapter(helper, playbackModel)
val queueAdapter = QueueAdapter(helper)
var lastShuffle = playbackModel.isShuffling.value
callback.addQueueAdapter(queueAdapter)
@ -136,10 +137,13 @@ class QueueFragment : Fragment() {
val nextQueue = playbackModel.nextItemsInQueue.value!!
if (userQueue.isNotEmpty()) {
queue += Header(
queue += ActionHeader(
id = -2,
name = getString(R.string.label_next_user_queue),
isAction = true
icon = R.drawable.ic_clear,
action = {
playbackModel.clearUserQueue()
}
)
queue += userQueue
@ -147,9 +151,7 @@ class QueueFragment : Fragment() {
if (nextQueue.isNotEmpty()) {
queue += Header(
id = -3,
name = getString(R.string.format_next_from, getParentName()),
isAction = false
id = -3, name = getString(R.string.format_next_from, getParentName()),
)
queue += nextQueue

View file

@ -2,11 +2,13 @@ package org.oxycblt.auxio.recycler.viewholders
import android.content.Context
import android.view.View
import org.oxycblt.auxio.databinding.ItemActionHeaderBinding
import org.oxycblt.auxio.databinding.ItemAlbumBinding
import org.oxycblt.auxio.databinding.ItemArtistBinding
import org.oxycblt.auxio.databinding.ItemGenreBinding
import org.oxycblt.auxio.databinding.ItemHeaderBinding
import org.oxycblt.auxio.databinding.ItemSongBinding
import org.oxycblt.auxio.music.ActionHeader
import org.oxycblt.auxio.music.Album
import org.oxycblt.auxio.music.Artist
import org.oxycblt.auxio.music.Genre
@ -170,3 +172,35 @@ class HeaderViewHolder(private val binding: ItemHeaderBinding) : BaseViewHolder<
}
}
}
/**
* The Shared ViewHolder for a [ActionHeader]. Instantiation should be done with [from]
*/
class ActionHeaderViewHolder(
private val binding: ItemActionHeaderBinding
) : BaseViewHolder<ActionHeader>(binding) {
override fun onBind(data: ActionHeader) {
binding.header = data
binding.headerButton.apply {
setImageResource(data.icon)
setOnClickListener {
data.action(binding.headerButton)
}
}
}
companion object {
const val ITEM_TYPE = 0xA006
/**
* Create an instance of [HeaderViewHolder]
*/
fun from(context: Context): HeaderViewHolder {
return HeaderViewHolder(
ItemHeaderBinding.inflate(context.inflater)
)
}
}
}

View file

@ -33,6 +33,8 @@ class SearchAdapter(
is Album -> AlbumViewHolder.ITEM_TYPE
is Song -> SongViewHolder.ITEM_TYPE
is Header -> HeaderViewHolder.ITEM_TYPE
else -> -1
}
}

View file

@ -8,7 +8,7 @@
<variable
name="header"
type="org.oxycblt.auxio.music.Header" />
type="org.oxycblt.auxio.music.ActionHeader" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout

View file

@ -44,7 +44,7 @@ To prevent any strange bugs, all integer representations must be unique. A table
0xA004 | HeaderViewHolder
0xA005 | QueueSongViewHolder
0xA006 | UserQueueHeaderViewHolder
0xA006 | ActionHeaderViewHolder
0xA007 | AlbumHeaderViewHolder
0xA008 | AlbumSongViewHolder
@ -89,7 +89,7 @@ org.oxycblt.auxio # Main UI's and logging utilities
├──.coil # Fetchers and utilities for Coil, contains binding adapters than be used in the user interface.
├──.database # Databases and their items for Auxio
├──.detail # UIs for more album/artist/genre details
└──.adapters # RecyclerView adapters for the detail UIs, which display the header information and items
└──.adapters # RecyclerView adapters for the detail UIs, which display the header information and items
├──.library # Library UI
├──.loading # Loading UI
├──.music # Music storage and loading