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:
parent
029a4b1ff6
commit
cc72ebc251
8 changed files with 76 additions and 62 deletions
|
@ -1,6 +1,8 @@
|
||||||
package org.oxycblt.auxio.music
|
package org.oxycblt.auxio.music
|
||||||
|
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
|
import android.widget.ImageButton
|
||||||
|
import androidx.annotation.DrawableRes
|
||||||
|
|
||||||
// --- MUSIC MODELS ---
|
// --- MUSIC MODELS ---
|
||||||
|
|
||||||
|
@ -189,10 +191,20 @@ data class Genre(
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A data object used solely for the "Header" UI element. Inherits [BaseModel].
|
* 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(
|
data class Header(
|
||||||
override val id: Long = -1,
|
override val id: Long = -1,
|
||||||
override val name: String = "",
|
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()
|
) : BaseModel()
|
||||||
|
|
|
@ -16,9 +16,10 @@ import org.oxycblt.auxio.logD
|
||||||
* @author OxygenCobalt
|
* @author OxygenCobalt
|
||||||
*
|
*
|
||||||
* FIXME: Here's a catalog of problems that I already know about with this abomination
|
* 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]
|
* - 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) {
|
class MusicLoader(private val context: Context) {
|
||||||
var genres = mutableListOf<Genre>()
|
var genres = mutableListOf<Genre>()
|
||||||
|
@ -100,7 +101,7 @@ class MusicLoader(private val context: Context) {
|
||||||
Albums.ARTIST, // 2
|
Albums.ARTIST, // 2
|
||||||
Albums.FIRST_YEAR, // 4
|
Albums.FIRST_YEAR, // 4
|
||||||
),
|
),
|
||||||
"", null,
|
null, null,
|
||||||
Albums.DEFAULT_SORT_ORDER
|
Albums.DEFAULT_SORT_ORDER
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -3,19 +3,18 @@ package org.oxycblt.auxio.playback.queue
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.view.MotionEvent
|
import android.view.MotionEvent
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.appcompat.widget.TooltipCompat
|
|
||||||
import androidx.recyclerview.widget.AsyncListDiffer
|
import androidx.recyclerview.widget.AsyncListDiffer
|
||||||
import androidx.recyclerview.widget.ItemTouchHelper
|
import androidx.recyclerview.widget.ItemTouchHelper
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import org.oxycblt.auxio.R
|
|
||||||
import org.oxycblt.auxio.databinding.ItemActionHeaderBinding
|
import org.oxycblt.auxio.databinding.ItemActionHeaderBinding
|
||||||
import org.oxycblt.auxio.databinding.ItemQueueSongBinding
|
import org.oxycblt.auxio.databinding.ItemQueueSongBinding
|
||||||
import org.oxycblt.auxio.logE
|
import org.oxycblt.auxio.logE
|
||||||
|
import org.oxycblt.auxio.music.ActionHeader
|
||||||
import org.oxycblt.auxio.music.BaseModel
|
import org.oxycblt.auxio.music.BaseModel
|
||||||
import org.oxycblt.auxio.music.Header
|
import org.oxycblt.auxio.music.Header
|
||||||
import org.oxycblt.auxio.music.Song
|
import org.oxycblt.auxio.music.Song
|
||||||
import org.oxycblt.auxio.playback.PlaybackViewModel
|
|
||||||
import org.oxycblt.auxio.recycler.DiffCallback
|
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.BaseViewHolder
|
||||||
import org.oxycblt.auxio.recycler.viewholders.HeaderViewHolder
|
import org.oxycblt.auxio.recycler.viewholders.HeaderViewHolder
|
||||||
import org.oxycblt.auxio.ui.inflater
|
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.
|
* The single adapter for both the Next Queue and the User Queue.
|
||||||
* @param touchHelper The [ItemTouchHelper] ***containing*** [QueueDragCallback] to be used
|
* @param touchHelper The [ItemTouchHelper] ***containing*** [QueueDragCallback] to be used
|
||||||
* @param playbackModel The [PlaybackViewModel] to dispatch updates to.
|
|
||||||
* @author OxygenCobalt
|
* @author OxygenCobalt
|
||||||
*/
|
*/
|
||||||
class QueueAdapter(
|
class QueueAdapter(
|
||||||
private val touchHelper: ItemTouchHelper,
|
private val touchHelper: ItemTouchHelper
|
||||||
private val playbackModel: PlaybackViewModel,
|
|
||||||
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
||||||
private var data = mutableListOf<BaseModel>()
|
private var data = mutableListOf<BaseModel>()
|
||||||
private var listDiffer = AsyncListDiffer(this, DiffCallback())
|
private var listDiffer = AsyncListDiffer(this, DiffCallback())
|
||||||
|
@ -38,14 +35,11 @@ class QueueAdapter(
|
||||||
override fun getItemViewType(position: Int): Int {
|
override fun getItemViewType(position: Int): Int {
|
||||||
val item = data[position]
|
val item = data[position]
|
||||||
|
|
||||||
return if (item is Header) {
|
return when (item) {
|
||||||
if (item.isAction) {
|
is Header -> HeaderViewHolder.ITEM_TYPE
|
||||||
USER_QUEUE_HEADER_ITEM_TYPE
|
is ActionHeader -> ActionHeaderViewHolder.ITEM_TYPE
|
||||||
} else {
|
is Song -> QUEUE_SONG_ITEM_TYPE
|
||||||
HeaderViewHolder.ITEM_TYPE
|
else -> -1
|
||||||
}
|
|
||||||
} else {
|
|
||||||
QUEUE_SONG_ITEM_TYPE
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,7 +47,7 @@ class QueueAdapter(
|
||||||
return when (viewType) {
|
return when (viewType) {
|
||||||
HeaderViewHolder.ITEM_TYPE -> HeaderViewHolder.from(parent.context)
|
HeaderViewHolder.ITEM_TYPE -> HeaderViewHolder.from(parent.context)
|
||||||
|
|
||||||
USER_QUEUE_HEADER_ITEM_TYPE -> UserQueueHeaderViewHolder(
|
ActionHeaderViewHolder.ITEM_TYPE -> ActionHeaderViewHolder(
|
||||||
ItemActionHeaderBinding.inflate(parent.context.inflater)
|
ItemActionHeaderBinding.inflate(parent.context.inflater)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -68,12 +62,8 @@ class QueueAdapter(
|
||||||
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
||||||
when (val item = data[position]) {
|
when (val item = data[position]) {
|
||||||
is Song -> (holder as QueueSongViewHolder).bind(item)
|
is Song -> (holder as QueueSongViewHolder).bind(item)
|
||||||
|
is Header -> (holder as HeaderViewHolder).bind(item)
|
||||||
is Header -> if (item.isAction) {
|
is ActionHeader -> (holder as ActionHeaderViewHolder).bind(item)
|
||||||
(holder as UserQueueHeaderViewHolder).bind(item)
|
|
||||||
} else {
|
|
||||||
(holder as HeaderViewHolder).bind(item)
|
|
||||||
}
|
|
||||||
|
|
||||||
else -> logE("Bad data given to QueueAdapter.")
|
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 {
|
companion object {
|
||||||
const val QUEUE_SONG_ITEM_TYPE = 0xA005
|
const val QUEUE_SONG_ITEM_TYPE = 0xA005
|
||||||
const val USER_QUEUE_HEADER_ITEM_TYPE = 0xA006
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,7 @@ import androidx.navigation.fragment.findNavController
|
||||||
import androidx.recyclerview.widget.ItemTouchHelper
|
import androidx.recyclerview.widget.ItemTouchHelper
|
||||||
import org.oxycblt.auxio.R
|
import org.oxycblt.auxio.R
|
||||||
import org.oxycblt.auxio.databinding.FragmentQueueBinding
|
import org.oxycblt.auxio.databinding.FragmentQueueBinding
|
||||||
|
import org.oxycblt.auxio.music.ActionHeader
|
||||||
import org.oxycblt.auxio.music.BaseModel
|
import org.oxycblt.auxio.music.BaseModel
|
||||||
import org.oxycblt.auxio.music.Header
|
import org.oxycblt.auxio.music.Header
|
||||||
import org.oxycblt.auxio.playback.PlaybackViewModel
|
import org.oxycblt.auxio.playback.PlaybackViewModel
|
||||||
|
@ -36,7 +37,7 @@ class QueueFragment : Fragment() {
|
||||||
|
|
||||||
val callback = QueueDragCallback(playbackModel)
|
val callback = QueueDragCallback(playbackModel)
|
||||||
val helper = ItemTouchHelper(callback)
|
val helper = ItemTouchHelper(callback)
|
||||||
val queueAdapter = QueueAdapter(helper, playbackModel)
|
val queueAdapter = QueueAdapter(helper)
|
||||||
var lastShuffle = playbackModel.isShuffling.value
|
var lastShuffle = playbackModel.isShuffling.value
|
||||||
|
|
||||||
callback.addQueueAdapter(queueAdapter)
|
callback.addQueueAdapter(queueAdapter)
|
||||||
|
@ -136,10 +137,13 @@ class QueueFragment : Fragment() {
|
||||||
val nextQueue = playbackModel.nextItemsInQueue.value!!
|
val nextQueue = playbackModel.nextItemsInQueue.value!!
|
||||||
|
|
||||||
if (userQueue.isNotEmpty()) {
|
if (userQueue.isNotEmpty()) {
|
||||||
queue += Header(
|
queue += ActionHeader(
|
||||||
id = -2,
|
id = -2,
|
||||||
name = getString(R.string.label_next_user_queue),
|
name = getString(R.string.label_next_user_queue),
|
||||||
isAction = true
|
icon = R.drawable.ic_clear,
|
||||||
|
action = {
|
||||||
|
playbackModel.clearUserQueue()
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
queue += userQueue
|
queue += userQueue
|
||||||
|
@ -147,9 +151,7 @@ class QueueFragment : Fragment() {
|
||||||
|
|
||||||
if (nextQueue.isNotEmpty()) {
|
if (nextQueue.isNotEmpty()) {
|
||||||
queue += Header(
|
queue += Header(
|
||||||
id = -3,
|
id = -3, name = getString(R.string.format_next_from, getParentName()),
|
||||||
name = getString(R.string.format_next_from, getParentName()),
|
|
||||||
isAction = false
|
|
||||||
)
|
)
|
||||||
|
|
||||||
queue += nextQueue
|
queue += nextQueue
|
||||||
|
|
|
@ -2,11 +2,13 @@ package org.oxycblt.auxio.recycler.viewholders
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
import org.oxycblt.auxio.databinding.ItemActionHeaderBinding
|
||||||
import org.oxycblt.auxio.databinding.ItemAlbumBinding
|
import org.oxycblt.auxio.databinding.ItemAlbumBinding
|
||||||
import org.oxycblt.auxio.databinding.ItemArtistBinding
|
import org.oxycblt.auxio.databinding.ItemArtistBinding
|
||||||
import org.oxycblt.auxio.databinding.ItemGenreBinding
|
import org.oxycblt.auxio.databinding.ItemGenreBinding
|
||||||
import org.oxycblt.auxio.databinding.ItemHeaderBinding
|
import org.oxycblt.auxio.databinding.ItemHeaderBinding
|
||||||
import org.oxycblt.auxio.databinding.ItemSongBinding
|
import org.oxycblt.auxio.databinding.ItemSongBinding
|
||||||
|
import org.oxycblt.auxio.music.ActionHeader
|
||||||
import org.oxycblt.auxio.music.Album
|
import org.oxycblt.auxio.music.Album
|
||||||
import org.oxycblt.auxio.music.Artist
|
import org.oxycblt.auxio.music.Artist
|
||||||
import org.oxycblt.auxio.music.Genre
|
import org.oxycblt.auxio.music.Genre
|
||||||
|
@ -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)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -33,6 +33,8 @@ class SearchAdapter(
|
||||||
is Album -> AlbumViewHolder.ITEM_TYPE
|
is Album -> AlbumViewHolder.ITEM_TYPE
|
||||||
is Song -> SongViewHolder.ITEM_TYPE
|
is Song -> SongViewHolder.ITEM_TYPE
|
||||||
is Header -> HeaderViewHolder.ITEM_TYPE
|
is Header -> HeaderViewHolder.ITEM_TYPE
|
||||||
|
|
||||||
|
else -> -1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
|
|
||||||
<variable
|
<variable
|
||||||
name="header"
|
name="header"
|
||||||
type="org.oxycblt.auxio.music.Header" />
|
type="org.oxycblt.auxio.music.ActionHeader" />
|
||||||
</data>
|
</data>
|
||||||
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
|
|
@ -44,7 +44,7 @@ To prevent any strange bugs, all integer representations must be unique. A table
|
||||||
0xA004 | HeaderViewHolder
|
0xA004 | HeaderViewHolder
|
||||||
|
|
||||||
0xA005 | QueueSongViewHolder
|
0xA005 | QueueSongViewHolder
|
||||||
0xA006 | UserQueueHeaderViewHolder
|
0xA006 | ActionHeaderViewHolder
|
||||||
|
|
||||||
0xA007 | AlbumHeaderViewHolder
|
0xA007 | AlbumHeaderViewHolder
|
||||||
0xA008 | AlbumSongViewHolder
|
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.
|
├──.coil # Fetchers and utilities for Coil, contains binding adapters than be used in the user interface.
|
||||||
├──.database # Databases and their items for Auxio
|
├──.database # Databases and their items for Auxio
|
||||||
├──.detail # UIs for more album/artist/genre details
|
├──.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
|
├──.library # Library UI
|
||||||
├──.loading # Loading UI
|
├──.loading # Loading UI
|
||||||
├──.music # Music storage and loading
|
├──.music # Music storage and loading
|
||||||
|
|
Loading…
Reference in a new issue