From cc72ebc25172ac8445026b3ffc159259d7292d72 Mon Sep 17 00:00:00 2001 From: OxygenCobalt Date: Sun, 25 Apr 2021 17:15:20 -0600 Subject: [PATCH] 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. --- .../java/org/oxycblt/auxio/music/Models.kt | 16 ++++- .../org/oxycblt/auxio/music/MusicLoader.kt | 7 ++- .../auxio/playback/queue/QueueAdapter.kt | 59 ++++--------------- .../auxio/playback/queue/QueueFragment.kt | 14 +++-- .../recycler/viewholders/ModelHolders.kt | 34 +++++++++++ .../org/oxycblt/auxio/search/SearchAdapter.kt | 2 + .../main/res/layout/item_action_header.xml | 2 +- info/ARCHITECTURE.md | 4 +- 8 files changed, 76 insertions(+), 62 deletions(-) diff --git a/app/src/main/java/org/oxycblt/auxio/music/Models.kt b/app/src/main/java/org/oxycblt/auxio/music/Models.kt index a2ad9e633..69fbfd84e 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/Models.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/Models.kt @@ -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() diff --git a/app/src/main/java/org/oxycblt/auxio/music/MusicLoader.kt b/app/src/main/java/org/oxycblt/auxio/music/MusicLoader.kt index 7e79e2047..a65ff62af 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/MusicLoader.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/MusicLoader.kt @@ -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() @@ -100,7 +101,7 @@ class MusicLoader(private val context: Context) { Albums.ARTIST, // 2 Albums.FIRST_YEAR, // 4 ), - "", null, + null, null, Albums.DEFAULT_SORT_ORDER ) diff --git a/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueAdapter.kt b/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueAdapter.kt index a4e7a16e5..387a6fa09 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueAdapter.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueAdapter.kt @@ -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() { private var data = mutableListOf() 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
(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 } } diff --git a/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueFragment.kt b/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueFragment.kt index 981ca021f..14d46003a 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueFragment.kt @@ -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 diff --git a/app/src/main/java/org/oxycblt/auxio/recycler/viewholders/ModelHolders.kt b/app/src/main/java/org/oxycblt/auxio/recycler/viewholders/ModelHolders.kt index b849db605..8a8948151 100644 --- a/app/src/main/java/org/oxycblt/auxio/recycler/viewholders/ModelHolders.kt +++ b/app/src/main/java/org/oxycblt/auxio/recycler/viewholders/ModelHolders.kt @@ -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(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) + ) + } + } +} diff --git a/app/src/main/java/org/oxycblt/auxio/search/SearchAdapter.kt b/app/src/main/java/org/oxycblt/auxio/search/SearchAdapter.kt index a837193b2..be6566212 100644 --- a/app/src/main/java/org/oxycblt/auxio/search/SearchAdapter.kt +++ b/app/src/main/java/org/oxycblt/auxio/search/SearchAdapter.kt @@ -33,6 +33,8 @@ class SearchAdapter( is Album -> AlbumViewHolder.ITEM_TYPE is Song -> SongViewHolder.ITEM_TYPE is Header -> HeaderViewHolder.ITEM_TYPE + + else -> -1 } } diff --git a/app/src/main/res/layout/item_action_header.xml b/app/src/main/res/layout/item_action_header.xml index 8e225fe3e..52762988c 100644 --- a/app/src/main/res/layout/item_action_header.xml +++ b/app/src/main/res/layout/item_action_header.xml @@ -8,7 +8,7 @@ + type="org.oxycblt.auxio.music.ActionHeader" />