queue: redocument
Redocument the queue module.
This commit is contained in:
parent
b086c44b59
commit
7394e87471
24 changed files with 258 additions and 148 deletions
|
@ -48,10 +48,12 @@ import org.oxycblt.auxio.util.systemBarInsetsCompat
|
|||
*
|
||||
* TODO: Migrate to material animation system
|
||||
*
|
||||
* TODO: Re-document project
|
||||
*
|
||||
* TODO: Unit testing
|
||||
*
|
||||
* TODO: Standardize from/new usage
|
||||
*
|
||||
* TODO: Standardize companion object usage
|
||||
*
|
||||
* @author Alexander Capehart (OxygenCobalt)
|
||||
*/
|
||||
class MainActivity : AppCompatActivity() {
|
||||
|
|
|
@ -27,7 +27,7 @@ import org.oxycblt.auxio.databinding.ItemAlbumSongBinding
|
|||
import org.oxycblt.auxio.databinding.ItemDetailBinding
|
||||
import org.oxycblt.auxio.databinding.ItemDiscHeaderBinding
|
||||
import org.oxycblt.auxio.detail.DiscHeader
|
||||
import org.oxycblt.auxio.list.ExtendedListListener
|
||||
import org.oxycblt.auxio.list.SelectableListListener
|
||||
import org.oxycblt.auxio.list.Item
|
||||
import org.oxycblt.auxio.list.recycler.SelectionIndicatorAdapter
|
||||
import org.oxycblt.auxio.list.recycler.SimpleItemCallback
|
||||
|
@ -224,10 +224,10 @@ private class AlbumSongViewHolder private constructor(private val binding: ItemA
|
|||
/**
|
||||
* Bind new data to this instance.
|
||||
* @param song The new [Song] to bind.
|
||||
* @param listener A [ExtendedListListener] to bind interactions to.
|
||||
* @param listener A [SelectableListListener] to bind interactions to.
|
||||
*/
|
||||
fun bind(song: Song, listener: ExtendedListListener) {
|
||||
listener.bind(song, binding.root, binding.songMenu)
|
||||
fun bind(song: Song, listener: SelectableListListener) {
|
||||
listener.bind(this, song, binding.songMenu)
|
||||
|
||||
binding.songTrack.apply {
|
||||
if (song.track != null) {
|
||||
|
|
|
@ -26,7 +26,7 @@ import org.oxycblt.auxio.R
|
|||
import org.oxycblt.auxio.databinding.ItemDetailBinding
|
||||
import org.oxycblt.auxio.databinding.ItemParentBinding
|
||||
import org.oxycblt.auxio.databinding.ItemSongBinding
|
||||
import org.oxycblt.auxio.list.ExtendedListListener
|
||||
import org.oxycblt.auxio.list.SelectableListListener
|
||||
import org.oxycblt.auxio.list.Item
|
||||
import org.oxycblt.auxio.list.recycler.SelectionIndicatorAdapter
|
||||
import org.oxycblt.auxio.list.recycler.SimpleItemCallback
|
||||
|
@ -181,10 +181,10 @@ private class ArtistAlbumViewHolder private constructor(private val binding: Ite
|
|||
/**
|
||||
* Bind new data to this instance.
|
||||
* @param album The new [Album] to bind.
|
||||
* @param listener An [ExtendedListListener] to bind interactions to.
|
||||
* @param listener An [SelectableListListener] to bind interactions to.
|
||||
*/
|
||||
fun bind(album: Album, listener: ExtendedListListener) {
|
||||
listener.bind(album, binding.root, binding.parentMenu)
|
||||
fun bind(album: Album, listener: SelectableListListener) {
|
||||
listener.bind(this, album, binding.parentMenu)
|
||||
binding.parentImage.bind(album)
|
||||
binding.parentName.text = album.resolveName(binding.context)
|
||||
binding.parentInfo.text =
|
||||
|
@ -232,10 +232,10 @@ private class ArtistSongViewHolder private constructor(private val binding: Item
|
|||
/**
|
||||
* Bind new data to this instance.
|
||||
* @param song The new [Song] to bind.
|
||||
* @param listener An [ExtendedListListener] to bind interactions to.
|
||||
* @param listener An [SelectableListListener] to bind interactions to.
|
||||
*/
|
||||
fun bind(song: Song, listener: ExtendedListListener) {
|
||||
listener.bind(song, binding.root, binding.songMenu)
|
||||
fun bind(song: Song, listener: SelectableListListener) {
|
||||
listener.bind(this, song, binding.songMenu)
|
||||
binding.songAlbumCover.bind(song)
|
||||
binding.songName.text = song.resolveName(binding.context)
|
||||
binding.songInfo.text = song.album.resolveName(binding.context)
|
||||
|
|
|
@ -26,7 +26,7 @@ import androidx.recyclerview.widget.RecyclerView
|
|||
import org.oxycblt.auxio.IntegerTable
|
||||
import org.oxycblt.auxio.databinding.ItemSortHeaderBinding
|
||||
import org.oxycblt.auxio.detail.SortHeader
|
||||
import org.oxycblt.auxio.list.ExtendedListListener
|
||||
import org.oxycblt.auxio.list.SelectableListListener
|
||||
import org.oxycblt.auxio.list.Header
|
||||
import org.oxycblt.auxio.list.Item
|
||||
import org.oxycblt.auxio.list.recycler.*
|
||||
|
@ -87,8 +87,8 @@ abstract class DetailAdapter(
|
|||
differ.submitList(newList)
|
||||
}
|
||||
|
||||
/** An extended [ExtendedListListener] for [DetailAdapter] implementations. */
|
||||
interface Listener : ExtendedListListener {
|
||||
/** An extended [SelectableListListener] for [DetailAdapter] implementations. */
|
||||
interface Listener : SelectableListListener {
|
||||
// TODO: Split off into sub-listeners if a collapsing toolbar is implemented.
|
||||
/**
|
||||
* Called when the play button in a detail header is pressed, requesting that the current
|
||||
|
|
|
@ -139,9 +139,9 @@ class AlbumListFragment : ListFragment<FragmentHomeListBinding>(), FastScrollRec
|
|||
|
||||
/**
|
||||
* A [SelectionIndicatorAdapter] that shows a list of [Album]s using [AlbumViewHolder].
|
||||
* @param listener An [ExtendedListListener] to bind interactions to.
|
||||
* @param listener An [SelectableListListener] to bind interactions to.
|
||||
*/
|
||||
private class AlbumAdapter(private val listener: ExtendedListListener) :
|
||||
private class AlbumAdapter(private val listener: SelectableListListener) :
|
||||
SelectionIndicatorAdapter<AlbumViewHolder>() {
|
||||
private val differ = SyncListDiffer(this, AlbumViewHolder.DIFF_CALLBACK)
|
||||
|
||||
|
|
|
@ -114,9 +114,9 @@ class ArtistListFragment : ListFragment<FragmentHomeListBinding>(), FastScrollRe
|
|||
|
||||
/**
|
||||
* A [SelectionIndicatorAdapter] that shows a list of [Artist]s using [ArtistViewHolder].
|
||||
* @param listener An [ExtendedListListener] to bind interactions to.
|
||||
* @param listener An [SelectableListListener] to bind interactions to.
|
||||
*/
|
||||
private class ArtistAdapter(private val listener: ExtendedListListener) :
|
||||
private class ArtistAdapter(private val listener: SelectableListListener) :
|
||||
SelectionIndicatorAdapter<ArtistViewHolder>() {
|
||||
private val differ = SyncListDiffer(this, ArtistViewHolder.DIFF_CALLBACK)
|
||||
|
||||
|
|
|
@ -113,9 +113,9 @@ class GenreListFragment : ListFragment<FragmentHomeListBinding>(), FastScrollRec
|
|||
|
||||
/**
|
||||
* A [SelectionIndicatorAdapter] that shows a list of [Genre]s using [GenreViewHolder].
|
||||
* @param listener An [ExtendedListListener] to bind interactions to.
|
||||
* @param listener An [SelectableListListener] to bind interactions to.
|
||||
*/
|
||||
private class GenreAdapter(private val listener: ExtendedListListener) :
|
||||
private class GenreAdapter(private val listener: SelectableListListener) :
|
||||
SelectionIndicatorAdapter<GenreViewHolder>() {
|
||||
private val differ = SyncListDiffer(this, GenreViewHolder.DIFF_CALLBACK)
|
||||
|
||||
|
|
|
@ -153,9 +153,9 @@ class SongListFragment : ListFragment<FragmentHomeListBinding>(), FastScrollRecy
|
|||
|
||||
/**
|
||||
* A [SelectionIndicatorAdapter] that shows a list of [Song]s using [SongViewHolder].
|
||||
* @param listener An [ExtendedListListener] to bind interactions to.
|
||||
* @param listener An [SelectableListListener] to bind interactions to.
|
||||
*/
|
||||
private class SongAdapter(private val listener: ExtendedListListener) :
|
||||
private class SongAdapter(private val listener: SelectableListListener) :
|
||||
SelectionIndicatorAdapter<SongViewHolder>() {
|
||||
private val differ = SyncListDiffer(this, SongViewHolder.DIFF_CALLBACK)
|
||||
|
||||
|
|
|
@ -85,10 +85,11 @@ class TabAdapter(private val listener: Listener) : RecyclerView.Adapter<TabViewH
|
|||
fun onToggleVisibility(tabMode: MusicMode)
|
||||
|
||||
/**
|
||||
* Called when the drag handle is pressed, requesting that a drag should be started.
|
||||
* Called when the drag handle on a [RecyclerView.ViewHolder] is clicked, requesting that a
|
||||
* drag should be started.
|
||||
* @param viewHolder The [RecyclerView.ViewHolder] to start dragging.
|
||||
*/
|
||||
fun onPickUpTab(viewHolder: RecyclerView.ViewHolder)
|
||||
fun onPickUp(viewHolder: RecyclerView.ViewHolder)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
@ -105,7 +106,7 @@ class TabViewHolder private constructor(private val binding: ItemTabBinding) :
|
|||
/**
|
||||
* Bind new data to this instance.
|
||||
* @param tab The new [Tab] to bind.
|
||||
* @param listener An [TabAdapter.Listener] to bind interactions to.
|
||||
* @param listener A [TabAdapter.Listener] to bind interactions to.
|
||||
*/
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
fun bind(tab: Tab, listener: TabAdapter.Listener) {
|
||||
|
@ -130,14 +131,13 @@ class TabViewHolder private constructor(private val binding: ItemTabBinding) :
|
|||
binding.tabDragHandle.setOnTouchListener { _, motionEvent ->
|
||||
binding.tabDragHandle.performClick()
|
||||
if (motionEvent.actionMasked == MotionEvent.ACTION_DOWN) {
|
||||
listener.onPickUpTab(this)
|
||||
listener.onPickUp(this)
|
||||
true
|
||||
} else false
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
/**
|
||||
* Create a new instance.
|
||||
* @param parent The parent to inflate this instance from.
|
||||
|
|
|
@ -104,7 +104,7 @@ class TabCustomizeDialog : ViewBindingDialogFragment<DialogTabsBinding>(), TabAd
|
|||
tabAdapter.tabs.filterIsInstance<Tab.Visible>().isNotEmpty()
|
||||
}
|
||||
|
||||
override fun onPickUpTab(viewHolder: RecyclerView.ViewHolder) {
|
||||
override fun onPickUp(viewHolder: RecyclerView.ViewHolder) {
|
||||
touchHelper.startDrag(viewHolder)
|
||||
}
|
||||
|
||||
|
|
|
@ -2,7 +2,6 @@ package org.oxycblt.auxio.list
|
|||
|
||||
import androidx.annotation.StringRes
|
||||
|
||||
|
||||
/**
|
||||
* A marker for something that is a RecyclerView item. Has no functionality on it's own.
|
||||
*/
|
||||
|
|
|
@ -36,7 +36,7 @@ import org.oxycblt.auxio.util.showToast
|
|||
* A Fragment containing a selectable list.
|
||||
* @author Alexander Capehart (OxygenCobalt)
|
||||
*/
|
||||
abstract class ListFragment<VB : ViewBinding> : SelectionFragment<VB>(), ExtendedListListener {
|
||||
abstract class ListFragment<VB : ViewBinding> : SelectionFragment<VB>(), SelectableListListener {
|
||||
protected val navModel: NavigationViewModel by activityViewModels()
|
||||
private var currentMenu: PopupMenu? = null
|
||||
|
||||
|
|
|
@ -1,12 +1,17 @@
|
|||
package org.oxycblt.auxio.list
|
||||
|
||||
import android.view.MotionEvent
|
||||
import android.view.View
|
||||
import android.widget.Button
|
||||
import android.widget.ImageView
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
|
||||
/**
|
||||
* A basic listener for list interactions.
|
||||
* TODO: Supply a ViewHolder on clicks (allows editable lists to be standardized into a listener.)
|
||||
* @author Alexander Capehart (OxygenCobalt)
|
||||
*/
|
||||
interface BasicListListener {
|
||||
interface ClickableListListener {
|
||||
/**
|
||||
* Called when an [Item] in the list is clicked.
|
||||
* @param item The [Item] that was clicked.
|
||||
|
@ -15,9 +20,10 @@ interface BasicListListener {
|
|||
}
|
||||
|
||||
/**
|
||||
* An extension of [BasicListListener] that enables menu and selection functionality.
|
||||
* An extension of [ClickableListListener] that enables menu and selection functionality.
|
||||
* @author Alexander Capehart (OxygenCobalt)
|
||||
*/
|
||||
interface ExtendedListListener : BasicListListener {
|
||||
interface SelectableListListener : ClickableListListener {
|
||||
/**
|
||||
* Called when an [Item] in the list requests that a menu related to it should be opened.
|
||||
* @param item The [Item] to show a menu for.
|
||||
|
@ -33,12 +39,12 @@ interface ExtendedListListener : BasicListListener {
|
|||
|
||||
/**
|
||||
* Binds this instance to a list item.
|
||||
* @param viewHolder The [RecyclerView.ViewHolder] to bind.
|
||||
* @param item The [Item] that this list entry is bound to.
|
||||
* @param root The root of the list [View].
|
||||
* @param menuButton A [Button] that opens a menu.
|
||||
*/
|
||||
fun bind(item: Item, root: View, menuButton: Button) {
|
||||
root.apply {
|
||||
fun bind(viewHolder: RecyclerView.ViewHolder, item: Item, menuButton: Button) {
|
||||
viewHolder.itemView.apply {
|
||||
// Map clicks to the click callback.
|
||||
setOnClickListener { onClick(item) }
|
||||
// Map long clicks to the selection callback.
|
||||
|
@ -47,7 +53,6 @@ interface ExtendedListListener : BasicListListener {
|
|||
true
|
||||
}
|
||||
}
|
||||
|
||||
// Map the menu button to the menu opening callback.
|
||||
menuButton.setOnClickListener { onOpenMenu(item, it) }
|
||||
}
|
||||
|
|
|
@ -24,7 +24,7 @@ import org.oxycblt.auxio.R
|
|||
import org.oxycblt.auxio.databinding.ItemHeaderBinding
|
||||
import org.oxycblt.auxio.databinding.ItemParentBinding
|
||||
import org.oxycblt.auxio.databinding.ItemSongBinding
|
||||
import org.oxycblt.auxio.list.ExtendedListListener
|
||||
import org.oxycblt.auxio.list.SelectableListListener
|
||||
import org.oxycblt.auxio.list.Header
|
||||
import org.oxycblt.auxio.music.Album
|
||||
import org.oxycblt.auxio.music.Artist
|
||||
|
@ -43,10 +43,10 @@ class SongViewHolder private constructor(private val binding: ItemSongBinding) :
|
|||
/**
|
||||
* Bind new data to this instance.
|
||||
* @param song The new [Song] to bind.
|
||||
* @param listener An [ExtendedListListener] to bind interactions to.
|
||||
* @param listener An [SelectableListListener] to bind interactions to.
|
||||
*/
|
||||
fun bind(song: Song, listener: ExtendedListListener) {
|
||||
listener.bind(song, binding.root, binding.songMenu)
|
||||
fun bind(song: Song, listener: SelectableListListener) {
|
||||
listener.bind(this, song, binding.songMenu)
|
||||
binding.songAlbumCover.bind(song)
|
||||
binding.songName.text = song.resolveName(binding.context)
|
||||
binding.songInfo.text = song.resolveArtistContents(binding.context)
|
||||
|
@ -90,10 +90,10 @@ class AlbumViewHolder private constructor(private val binding: ItemParentBinding
|
|||
/**
|
||||
* Bind new data to this instance.
|
||||
* @param album The new [Album] to bind.
|
||||
* @param listener An [ExtendedListListener] to bind interactions to.
|
||||
* @param listener An [SelectableListListener] to bind interactions to.
|
||||
*/
|
||||
fun bind(album: Album, listener: ExtendedListListener) {
|
||||
listener.bind(album, binding.root, binding.parentMenu)
|
||||
fun bind(album: Album, listener: SelectableListListener) {
|
||||
listener.bind(this, album, binding.parentMenu)
|
||||
binding.parentImage.bind(album)
|
||||
binding.parentName.text = album.resolveName(binding.context)
|
||||
binding.parentInfo.text = album.resolveArtistContents(binding.context)
|
||||
|
@ -139,10 +139,10 @@ class ArtistViewHolder private constructor(private val binding: ItemParentBindin
|
|||
/**
|
||||
* Bind new data to this instance.
|
||||
* @param artist The new [Artist] to bind.
|
||||
* @param listener An [ExtendedListListener] to bind interactions to.
|
||||
* @param listener An [SelectableListListener] to bind interactions to.
|
||||
*/
|
||||
fun bind(artist: Artist, listener: ExtendedListListener) {
|
||||
listener.bind(artist, binding.root, binding.parentMenu)
|
||||
fun bind(artist: Artist, listener: SelectableListListener) {
|
||||
listener.bind(this, artist, binding.parentMenu)
|
||||
binding.parentImage.bind(artist)
|
||||
binding.parentName.text = artist.resolveName(binding.context)
|
||||
binding.parentInfo.text =
|
||||
|
@ -197,10 +197,10 @@ class GenreViewHolder private constructor(private val binding: ItemParentBinding
|
|||
/**
|
||||
* Bind new data to this instance.
|
||||
* @param genre The new [Genre] to bind.
|
||||
* @param listener An [ExtendedListListener] to bind interactions to.
|
||||
* @param listener An [SelectableListListener] to bind interactions to.
|
||||
*/
|
||||
fun bind(genre: Genre, listener: ExtendedListListener) {
|
||||
listener.bind(genre, binding.root, binding.parentMenu)
|
||||
fun bind(genre: Genre, listener: SelectableListListener) {
|
||||
listener.bind(this, genre, binding.parentMenu)
|
||||
binding.parentImage.bind(genre)
|
||||
binding.parentName.text = genre.resolveName(binding.context)
|
||||
binding.parentInfo.text =
|
||||
|
|
|
@ -21,7 +21,7 @@ import android.view.View
|
|||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import org.oxycblt.auxio.databinding.ItemPickerChoiceBinding
|
||||
import org.oxycblt.auxio.list.BasicListListener
|
||||
import org.oxycblt.auxio.list.ClickableListListener
|
||||
import org.oxycblt.auxio.list.recycler.DialogRecyclerView
|
||||
import org.oxycblt.auxio.music.Artist
|
||||
import org.oxycblt.auxio.util.context
|
||||
|
@ -29,10 +29,10 @@ import org.oxycblt.auxio.util.inflater
|
|||
|
||||
/**
|
||||
* An adapter responsible for showing a list of [Artist] choices in [ArtistPickerDialog].
|
||||
* @param listener A [BasicListListener] to bind interactions to.
|
||||
* @param listener A [ClickableListListener] to bind interactions to.
|
||||
* @author OxygenCobalt.
|
||||
*/
|
||||
class ArtistChoiceAdapter(private val listener: BasicListListener) :
|
||||
class ArtistChoiceAdapter(private val listener: ClickableListListener) :
|
||||
RecyclerView.Adapter<ArtistChoiceViewHolder>() {
|
||||
private var artists = listOf<Artist>()
|
||||
|
||||
|
@ -65,9 +65,9 @@ class ArtistChoiceViewHolder(private val binding: ItemPickerChoiceBinding) :
|
|||
/**
|
||||
* Bind new data to this instance.
|
||||
* @param artist The new [Artist] to bind.
|
||||
* @param listener A [BasicListListener] to bind interactions to.
|
||||
* @param listener A [ClickableListListener] to bind interactions to.
|
||||
*/
|
||||
fun bind(artist: Artist, listener: BasicListListener) {
|
||||
fun bind(artist: Artist, listener: ClickableListListener) {
|
||||
binding.root.setOnClickListener { listener.onClick(artist) }
|
||||
binding.pickerImage.bind(artist)
|
||||
binding.pickerName.text = artist.resolveName(binding.context)
|
||||
|
|
|
@ -24,7 +24,7 @@ import androidx.fragment.app.viewModels
|
|||
import androidx.navigation.fragment.findNavController
|
||||
import org.oxycblt.auxio.R
|
||||
import org.oxycblt.auxio.databinding.DialogMusicPickerBinding
|
||||
import org.oxycblt.auxio.list.BasicListListener
|
||||
import org.oxycblt.auxio.list.ClickableListListener
|
||||
import org.oxycblt.auxio.list.Item
|
||||
import org.oxycblt.auxio.music.Artist
|
||||
import org.oxycblt.auxio.shared.ViewBindingDialogFragment
|
||||
|
@ -36,7 +36,7 @@ import org.oxycblt.auxio.util.collectImmediately
|
|||
* multiple [Artist]'s to choose from.
|
||||
* @author Alexander Capehart (OxygenCobalt)
|
||||
*/
|
||||
abstract class ArtistPickerDialog : ViewBindingDialogFragment<DialogMusicPickerBinding>(), BasicListListener {
|
||||
abstract class ArtistPickerDialog : ViewBindingDialogFragment<DialogMusicPickerBinding>(), ClickableListListener {
|
||||
protected val pickerModel: PickerViewModel by viewModels()
|
||||
// Okay to leak this since the Listener will not be called until after initialization.
|
||||
private val artistAdapter = ArtistChoiceAdapter(@Suppress("LeakingThis") this)
|
||||
|
|
|
@ -36,9 +36,17 @@ import org.oxycblt.auxio.util.getAttrColorCompat
|
|||
import org.oxycblt.auxio.util.getDimen
|
||||
import org.oxycblt.auxio.util.inflater
|
||||
|
||||
class QueueAdapter(private val listener: QueueItemListener) :
|
||||
/**
|
||||
* A [RecyclerView.Adapter] that shows an editable list of queue items.
|
||||
* @param listener A [Listener] to bind interactions to.
|
||||
* @author Alexander Capehart (OxygenCobalt)
|
||||
*/
|
||||
class QueueAdapter(private val listener: Listener) :
|
||||
RecyclerView.Adapter<QueueSongViewHolder>() {
|
||||
private var differ = SyncListDiffer(this, QueueSongViewHolder.DIFF_CALLBACK)
|
||||
// Since PlayingIndicator adapter relies on an item value, we cannot use it for this
|
||||
// adapter, as one item can appear at several points in the UI. Use a similar implementation
|
||||
// with an index value instead.
|
||||
private var currentIndex = 0
|
||||
private var isPlaying = false
|
||||
|
||||
|
@ -59,63 +67,106 @@ class QueueAdapter(private val listener: QueueItemListener) :
|
|||
viewHolder.bind(differ.currentList[position], listener)
|
||||
}
|
||||
|
||||
viewHolder.isEnabled = position > currentIndex
|
||||
viewHolder.isFuture = position > currentIndex
|
||||
viewHolder.updatePlayingIndicator(position == currentIndex, isPlaying)
|
||||
}
|
||||
|
||||
/**
|
||||
* Synchronously update the list with new items. This is exceedingly slow for large diffs,
|
||||
* so only use it for trivial updates.
|
||||
* @param newList The new [Song]s for the adapter to display.
|
||||
*/
|
||||
fun submitList(newList: List<Song>) {
|
||||
differ.submitList(newList)
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace the list with a new list. This is exceedingly slow for large diffs,
|
||||
* so only use it for trivial updates.
|
||||
* @param newList The new [Song]s for the adapter to display.
|
||||
*/
|
||||
fun replaceList(newList: List<Song>) {
|
||||
differ.replaceList(newList)
|
||||
}
|
||||
|
||||
fun updateIndicator(index: Int, isPlaying: Boolean) {
|
||||
/**
|
||||
* Set the position of the currently playing item in the queue. This will mark the item
|
||||
* as playing and any previous items as played.
|
||||
* @param index The position of the currently playing item in the queue.
|
||||
* @param isPlaying Whether playback is ongoing or paused.
|
||||
*/
|
||||
fun setPosition(index: Int, isPlaying: Boolean) {
|
||||
var updatedIndex = false
|
||||
|
||||
if (index != currentIndex) {
|
||||
when {
|
||||
index < currentIndex -> {
|
||||
val lastIndex = currentIndex
|
||||
currentIndex = index
|
||||
notifyItemRangeChanged(0, lastIndex + 1, PAYLOAD_UPDATE_INDEX)
|
||||
}
|
||||
else -> {
|
||||
currentIndex = index
|
||||
notifyItemRangeChanged(0, currentIndex + 1, PAYLOAD_UPDATE_INDEX)
|
||||
}
|
||||
}
|
||||
|
||||
val lastIndex = currentIndex
|
||||
currentIndex = index
|
||||
updatedIndex = true
|
||||
|
||||
// Have to update not only the currently playing item, but also all items marked
|
||||
// as playing.
|
||||
if (currentIndex < lastIndex) {
|
||||
notifyItemRangeChanged(0, lastIndex + 1, PAYLOAD_UPDATE_POSITION)
|
||||
} else {
|
||||
notifyItemRangeChanged(0, currentIndex + 1, PAYLOAD_UPDATE_POSITION)
|
||||
}
|
||||
}
|
||||
|
||||
if (this.isPlaying != isPlaying) {
|
||||
this.isPlaying = isPlaying
|
||||
|
||||
// Don't need to do anything if we've already sent an update from changing the
|
||||
// index.
|
||||
if (!updatedIndex) {
|
||||
notifyItemChanged(index, PAYLOAD_UPDATE_INDEX)
|
||||
notifyItemChanged(index, PAYLOAD_UPDATE_POSITION)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A listener for queue list events.
|
||||
*/
|
||||
interface Listener {
|
||||
/**
|
||||
* Called when a [RecyclerView.ViewHolder] in the list as clicked.
|
||||
* @param viewHolder The [RecyclerView.ViewHolder] that was clicked.
|
||||
*/
|
||||
fun onClick(viewHolder: RecyclerView.ViewHolder)
|
||||
|
||||
/**
|
||||
* Called when the drag handle on a [RecyclerView.ViewHolder] is clicked, requesting that a
|
||||
* drag should be started.
|
||||
* @param viewHolder The [RecyclerView.ViewHolder] to start dragging.
|
||||
*/
|
||||
fun onPickUp(viewHolder: RecyclerView.ViewHolder)
|
||||
}
|
||||
|
||||
companion object {
|
||||
val PAYLOAD_UPDATE_INDEX = Any()
|
||||
private val PAYLOAD_UPDATE_POSITION = Any()
|
||||
}
|
||||
}
|
||||
|
||||
interface QueueItemListener {
|
||||
fun onClick(viewHolder: RecyclerView.ViewHolder)
|
||||
fun onPickUp(viewHolder: RecyclerView.ViewHolder)
|
||||
}
|
||||
|
||||
/**
|
||||
* A [PlayingIndicatorAdapter.ViewHolder] that displays a queue [Song]. Use [new] to create an
|
||||
* instance.
|
||||
* @author Alexander Capehart (OxygenCobalt)
|
||||
*/
|
||||
class QueueSongViewHolder private constructor(private val binding: ItemQueueSongBinding) :
|
||||
PlayingIndicatorAdapter.ViewHolder(binding.root) {
|
||||
/**
|
||||
* The "body" view of this [QueueSongViewHolder] that shows the [Song] information.
|
||||
*/
|
||||
val bodyView: View
|
||||
get() = binding.body
|
||||
|
||||
/**
|
||||
* The background view of this [QueueSongViewHolder] that shows the delete icon.
|
||||
*/
|
||||
val backgroundView: View
|
||||
get() = binding.background
|
||||
|
||||
/**
|
||||
* The actual background drawable of this [QueueSongViewHolder] that can be manipulated.
|
||||
*/
|
||||
val backgroundDrawable =
|
||||
MaterialShapeDrawable.createWithElevationOverlay(binding.root.context).apply {
|
||||
fillColor = binding.context.getAttrColorCompat(R.attr.colorSurface)
|
||||
|
@ -123,6 +174,19 @@ class QueueSongViewHolder private constructor(private val binding: ItemQueueSong
|
|||
alpha = 0
|
||||
}
|
||||
|
||||
/**
|
||||
* If this queue item is considered "in the future" (i.e has not played yet).
|
||||
*/
|
||||
var isFuture: Boolean
|
||||
get() = binding.songAlbumCover.isEnabled
|
||||
set(value) {
|
||||
// Don't want to disable clicking, just indicate the body and handle is disabled
|
||||
binding.songAlbumCover.isEnabled = value
|
||||
binding.songName.isEnabled = value
|
||||
binding.songInfo.isEnabled = value
|
||||
binding.songDragHandle.isEnabled = value
|
||||
}
|
||||
|
||||
init {
|
||||
binding.body.background =
|
||||
LayerDrawable(
|
||||
|
@ -134,17 +198,24 @@ class QueueSongViewHolder private constructor(private val binding: ItemQueueSong
|
|||
backgroundDrawable))
|
||||
}
|
||||
|
||||
/**
|
||||
* Bind new data to this instance.
|
||||
* @param song The new [Song] to bind.
|
||||
* @param listener A [QueueAdapter.Listener] to bind interactions to.
|
||||
*/
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
fun bind(item: Song, listener: QueueItemListener) {
|
||||
binding.songAlbumCover.bind(item)
|
||||
binding.songName.text = item.resolveName(binding.context)
|
||||
binding.songInfo.text = item.resolveArtistContents(binding.context)
|
||||
fun bind(song: Song, listener: QueueAdapter.Listener) {
|
||||
binding.body.setOnClickListener {
|
||||
listener.onClick(this)
|
||||
}
|
||||
|
||||
binding.songAlbumCover.bind(song)
|
||||
binding.songName.text = song.resolveName(binding.context)
|
||||
binding.songInfo.text = song.resolveArtistContents(binding.context)
|
||||
// TODO: Why is this here?
|
||||
binding.background.isInvisible = true
|
||||
|
||||
binding.body.setOnClickListener { listener.onClick(this) }
|
||||
|
||||
// Roll our own drag handlers as the default ones suck
|
||||
// Set up the drag handle to start a drag whenever it is touched.
|
||||
binding.songDragHandle.setOnTouchListener { _, motionEvent ->
|
||||
binding.songDragHandle.performClick()
|
||||
if (motionEvent.actionMasked == MotionEvent.ACTION_DOWN) {
|
||||
|
@ -154,25 +225,21 @@ class QueueSongViewHolder private constructor(private val binding: ItemQueueSong
|
|||
}
|
||||
}
|
||||
|
||||
var isEnabled: Boolean
|
||||
get() = binding.songAlbumCover.isEnabled
|
||||
set(value) {
|
||||
// Don't want to disable clicking, just indicate the body and handle is disabled
|
||||
binding.songAlbumCover.isEnabled = value
|
||||
binding.songName.isEnabled = value
|
||||
binding.songInfo.isEnabled = value
|
||||
binding.songDragHandle.isEnabled = value
|
||||
}
|
||||
|
||||
override fun updatePlayingIndicator(isActive: Boolean, isPlaying: Boolean) {
|
||||
binding.interactBody.isSelected = isActive
|
||||
binding.songAlbumCover.isPlaying = isPlaying
|
||||
}
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* Create a new instance.
|
||||
* @param parent The parent to inflate this instance from.
|
||||
* @return A new instance.
|
||||
*/
|
||||
fun new(parent: View) =
|
||||
QueueSongViewHolder(ItemQueueSongBinding.inflate(parent.context.inflater))
|
||||
|
||||
/** A comparator that can be used with DiffUtil. */
|
||||
val DIFF_CALLBACK = SongViewHolder.DIFF_CALLBACK
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,7 +32,8 @@ import org.oxycblt.auxio.util.replaceSystemBarInsetsCompat
|
|||
import org.oxycblt.auxio.util.systemBarInsetsCompat
|
||||
|
||||
/**
|
||||
* The bottom sheet behavior designed for the queue in particular.
|
||||
* The [BaseBottomSheetBehavior] for the queue bottom sheet. This is placed within the playback
|
||||
* sheet and automatically arranges itself to show the playback bar at the top.
|
||||
* @author Alexander Capehart (OxygenCobalt)
|
||||
*/
|
||||
class QueueBottomSheetBehavior<V : View>(context: Context, attributeSet: AttributeSet?) :
|
||||
|
@ -41,6 +42,7 @@ class QueueBottomSheetBehavior<V : View>(context: Context, attributeSet: Attribu
|
|||
private var barSpacing = context.getDimenPixels(R.dimen.spacing_small)
|
||||
|
||||
init {
|
||||
// Not hide-able (and not programmatically hide-able)
|
||||
isHideable = false
|
||||
}
|
||||
|
||||
|
@ -53,18 +55,19 @@ class QueueBottomSheetBehavior<V : View>(context: Context, attributeSet: Attribu
|
|||
dependency: View
|
||||
): Boolean {
|
||||
barHeight = dependency.height
|
||||
return false // No change, just grabbed the height
|
||||
// No change, just grabbed the height
|
||||
return false
|
||||
}
|
||||
|
||||
override fun createBackground(context: Context) =
|
||||
MaterialShapeDrawable.createWithElevationOverlay(context).apply {
|
||||
// The queue sheet's background is a static elevated background.
|
||||
fillColor = context.getAttrColorCompat(R.attr.colorSurface)
|
||||
elevation = context.getDimen(R.dimen.elevation_normal)
|
||||
}
|
||||
|
||||
override fun applyWindowInsets(child: View, insets: WindowInsets): WindowInsets {
|
||||
super.applyWindowInsets(child, insets)
|
||||
|
||||
// Offset our expanded panel by the size of the playback bar, as that is shown when
|
||||
// we slide up the panel.
|
||||
val bars = insets.systemBarInsetsCompat
|
||||
|
|
|
@ -24,12 +24,12 @@ import androidx.recyclerview.widget.ItemTouchHelper
|
|||
import androidx.recyclerview.widget.RecyclerView
|
||||
import org.oxycblt.auxio.R
|
||||
import org.oxycblt.auxio.util.getDimen
|
||||
import org.oxycblt.auxio.util.getInteger
|
||||
import org.oxycblt.auxio.util.logD
|
||||
|
||||
/**
|
||||
* A highly customized [ItemTouchHelper.Callback] that handles the queue system while basically
|
||||
* rebuilding most the "Material-y" aspects of an editable list because Google's implementations are
|
||||
* hot garbage. This shouldn't have *too many* UI bugs. I hope.
|
||||
* A highly customized [ItemTouchHelper.Callback] that enables some extra eye candy in the queue
|
||||
* UI, such as an animation when lifting items.
|
||||
* @author Alexander Capehart (OxygenCobalt)
|
||||
*/
|
||||
class QueueDragCallback(private val playbackModel: QueueViewModel) : ItemTouchHelper.Callback() {
|
||||
|
@ -40,11 +40,13 @@ class QueueDragCallback(private val playbackModel: QueueViewModel) : ItemTouchHe
|
|||
viewHolder: RecyclerView.ViewHolder
|
||||
): Int {
|
||||
val queueHolder = viewHolder as QueueSongViewHolder
|
||||
return if (queueHolder.isEnabled) {
|
||||
return if (queueHolder.isFuture) {
|
||||
makeFlag(
|
||||
ItemTouchHelper.ACTION_STATE_DRAG, ItemTouchHelper.UP or ItemTouchHelper.DOWN) or
|
||||
makeFlag(ItemTouchHelper.ACTION_STATE_SWIPE, ItemTouchHelper.START)
|
||||
} else {
|
||||
// Avoid allowing any touch actions for already-played queue items, as the playback
|
||||
// system does not currently allow for this.
|
||||
0
|
||||
}
|
||||
}
|
||||
|
@ -58,12 +60,11 @@ class QueueDragCallback(private val playbackModel: QueueViewModel) : ItemTouchHe
|
|||
actionState: Int,
|
||||
isCurrentlyActive: Boolean
|
||||
) {
|
||||
// The material design page on elevation has a cool example of draggable items elevating
|
||||
// themselves when being dragged. Too bad google's implementation of this doesn't even
|
||||
// work! To emulate it on my own, I check if this child is in a drag state and then animate
|
||||
// an elevation change.
|
||||
val holder = viewHolder as QueueSongViewHolder
|
||||
|
||||
// Hook drag events to "lifting" the queue item (i.e raising it's elevation). Make sure
|
||||
// this is only done once when the item is initially picked up.
|
||||
// TODO: I think this is possible to improve with a raw ValueAnimator.
|
||||
if (shouldLift && isCurrentlyActive && actionState == ItemTouchHelper.ACTION_STATE_DRAG) {
|
||||
logD("Lifting queue item")
|
||||
|
||||
|
@ -72,7 +73,7 @@ class QueueDragCallback(private val playbackModel: QueueViewModel) : ItemTouchHe
|
|||
holder.itemView
|
||||
.animate()
|
||||
.translationZ(elevation)
|
||||
.setDuration(100)
|
||||
.setDuration(recyclerView.context.getInteger(R.integer.anim_fade_exit_duration).toLong())
|
||||
.setUpdateListener {
|
||||
bg.alpha = ((holder.itemView.translationZ / elevation) * 255).toInt()
|
||||
}
|
||||
|
@ -82,20 +83,19 @@ class QueueDragCallback(private val playbackModel: QueueViewModel) : ItemTouchHe
|
|||
shouldLift = false
|
||||
}
|
||||
|
||||
// We show a background with a clear icon behind the queue song each time one is swiped
|
||||
// away. To avoid any canvas shenanigans, we just place a custom background view behind the
|
||||
// main "body" layout of the queue item and then translate that.
|
||||
//
|
||||
// We show a background with a delete icon behind the queue song each time one is swiped
|
||||
// away. To avoid working with canvas, this is simply placed behind the queue body.
|
||||
// That comes with a couple of problems, however. For one, the background view will always
|
||||
// lag behind the body view, resulting in a noticeable pixel offset when dragging. To fix
|
||||
// this, we make this a separate view and make this view invisible whenever the item is
|
||||
// not being swiped. We cannot merge this view with the FrameLayout, as that will cause
|
||||
// another weird pixel desynchronization issue that is less visible but still incredibly
|
||||
// annoying.
|
||||
// not being swiped. This issue is also the reason why the background is not merged with
|
||||
// the FrameLayout within the queue item.
|
||||
if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE) {
|
||||
holder.backgroundView.isInvisible = dX == 0f
|
||||
}
|
||||
|
||||
// Update other translations. We do not call the default implementation, so we must do
|
||||
// this ourselves.
|
||||
holder.bodyView.translationX = dX
|
||||
holder.itemView.translationY = dY
|
||||
}
|
||||
|
@ -104,6 +104,8 @@ class QueueDragCallback(private val playbackModel: QueueViewModel) : ItemTouchHe
|
|||
// When an elevated item is cleared, we reset the elevation using another animation.
|
||||
val holder = viewHolder as QueueSongViewHolder
|
||||
|
||||
// This function can be called multiple times, so only start the animation when the view's
|
||||
// translationZ is already non-zero.
|
||||
if (holder.itemView.translationZ != 0f) {
|
||||
logD("Dropping queue item")
|
||||
|
||||
|
@ -112,7 +114,7 @@ class QueueDragCallback(private val playbackModel: QueueViewModel) : ItemTouchHe
|
|||
holder.itemView
|
||||
.animate()
|
||||
.translationZ(0f)
|
||||
.setDuration(100)
|
||||
.setDuration(recyclerView.context.getInteger(R.integer.anim_fade_exit_duration).toLong())
|
||||
.setUpdateListener {
|
||||
bg.alpha = ((holder.itemView.translationZ / elevation) * 255).toInt()
|
||||
}
|
||||
|
@ -122,6 +124,8 @@ class QueueDragCallback(private val playbackModel: QueueViewModel) : ItemTouchHe
|
|||
|
||||
shouldLift = true
|
||||
|
||||
// Reset translations. We do not call the default implementation, so we must do
|
||||
// this ourselves.
|
||||
holder.bodyView.translationX = 0f
|
||||
holder.itemView.translationY = 0f
|
||||
}
|
||||
|
@ -138,5 +142,6 @@ class QueueDragCallback(private val playbackModel: QueueViewModel) : ItemTouchHe
|
|||
playbackModel.removeQueueDataItem(viewHolder.bindingAdapterPosition)
|
||||
}
|
||||
|
||||
// Long-press events are too buggy, only allow dragging with the handle.
|
||||
override fun isLongPressDragEnabled() = false
|
||||
}
|
||||
|
|
|
@ -19,7 +19,6 @@ package org.oxycblt.auxio.playback.queue
|
|||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.recyclerview.widget.ItemTouchHelper
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
|
@ -33,11 +32,10 @@ import org.oxycblt.auxio.util.collectImmediately
|
|||
import org.oxycblt.auxio.util.logD
|
||||
|
||||
/**
|
||||
* A [Fragment] that shows the queue and enables editing as well.
|
||||
*
|
||||
* A [ViewBindingFragment] that displays an editable queue.
|
||||
* @author Alexander Capehart (OxygenCobalt)
|
||||
*/
|
||||
class QueueFragment : ViewBindingFragment<FragmentQueueBinding>(), QueueItemListener {
|
||||
class QueueFragment : ViewBindingFragment<FragmentQueueBinding>(), QueueAdapter.Listener {
|
||||
private val queueModel: QueueViewModel by activityViewModels()
|
||||
private val playbackModel: PlaybackViewModel by androidActivityViewModels()
|
||||
private val queueAdapter = QueueAdapter(this)
|
||||
|
@ -48,13 +46,15 @@ class QueueFragment : ViewBindingFragment<FragmentQueueBinding>(), QueueItemList
|
|||
override fun onCreateBinding(inflater: LayoutInflater) = FragmentQueueBinding.inflate(inflater)
|
||||
|
||||
override fun onBindingCreated(binding: FragmentQueueBinding, savedInstanceState: Bundle?) {
|
||||
super.onBindingCreated(binding, savedInstanceState)
|
||||
|
||||
// --- UI SETUP ---
|
||||
binding.queueRecycler.apply {
|
||||
adapter = queueAdapter
|
||||
touchHelper.attachToRecyclerView(this)
|
||||
}
|
||||
|
||||
// --- VIEWMODEL SETUP ----
|
||||
|
||||
collectImmediately(
|
||||
queueModel.queue, queueModel.index, playbackModel.isPlaying, ::updateQueue)
|
||||
}
|
||||
|
@ -65,6 +65,7 @@ class QueueFragment : ViewBindingFragment<FragmentQueueBinding>(), QueueItemList
|
|||
}
|
||||
|
||||
override fun onClick(viewHolder: RecyclerView.ViewHolder) {
|
||||
// Clicking on a queue item should start playing it.
|
||||
queueModel.goto(viewHolder.bindingAdapterPosition)
|
||||
}
|
||||
|
||||
|
@ -75,31 +76,34 @@ class QueueFragment : ViewBindingFragment<FragmentQueueBinding>(), QueueItemList
|
|||
private fun updateQueue(queue: List<Song>, index: Int, isPlaying: Boolean) {
|
||||
val binding = requireBinding()
|
||||
|
||||
val replaceQueue = queueModel.replaceQueue
|
||||
if (replaceQueue == true) {
|
||||
// Replace or diff the queue depending on the type of change it is.
|
||||
// TODO: Extend this to the whole app.
|
||||
if (queueModel.replaceQueue == true) {
|
||||
logD("Replacing queue")
|
||||
queueAdapter.replaceList(queue)
|
||||
} else {
|
||||
logD("Diffing queue")
|
||||
queueAdapter.submitList(queue)
|
||||
}
|
||||
|
||||
queueModel.finishReplace()
|
||||
|
||||
// If requested, scroll to a new item (occurs when the index moves)
|
||||
val scrollTo = queueModel.scrollTo
|
||||
if (scrollTo != null) {
|
||||
// Do not scroll to indices that are not in the currently visible range.
|
||||
// This prevents the queue from jumping around when the user is trying to
|
||||
// navigate the queue.
|
||||
val lmm = binding.queueRecycler.layoutManager as LinearLayoutManager
|
||||
val start = lmm.findFirstCompletelyVisibleItemPosition()
|
||||
val end = lmm.findLastCompletelyVisibleItemPosition()
|
||||
|
||||
if (scrollTo !in start..end) {
|
||||
logD("Scrolling to new position")
|
||||
binding.queueRecycler.scrollToPosition(scrollTo)
|
||||
}
|
||||
}
|
||||
|
||||
queueModel.finishScrollTo()
|
||||
|
||||
queueAdapter.updateIndicator(index, isPlaying)
|
||||
// Update currently playing item
|
||||
queueAdapter.setPosition(index, isPlaying)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,21 +25,25 @@ import org.oxycblt.auxio.music.Song
|
|||
import org.oxycblt.auxio.playback.state.PlaybackStateManager
|
||||
|
||||
/**
|
||||
* Class enabling more advanced queue list functionality and queue editing. TODO: Allow editing
|
||||
* previous parts of the queue
|
||||
* A [ViewModel] that manages the current queue state and allows navigation through the queue.
|
||||
*
|
||||
* @author Alexander Capehart (OxygenCobalt)
|
||||
*/
|
||||
class QueueViewModel : ViewModel(), PlaybackStateManager.Callback {
|
||||
private val playbackManager = PlaybackStateManager.getInstance()
|
||||
|
||||
private val _queue = MutableStateFlow(listOf<Song>())
|
||||
/** The current queue. */
|
||||
val queue: StateFlow<List<Song>> = _queue
|
||||
|
||||
private val _index = MutableStateFlow(playbackManager.index)
|
||||
/** The index of the currently playing song in the queue. */
|
||||
val index: StateFlow<Int>
|
||||
get() = _index
|
||||
|
||||
/** Whether to replace or diff the queue list when updating it. Is null if not specified. */
|
||||
var replaceQueue: Boolean? = null
|
||||
/** Flag to scroll to a particular queue item. Is null if no command has been specified. */
|
||||
var scrollTo: Int? = null
|
||||
|
||||
init {
|
||||
|
@ -47,58 +51,78 @@ class QueueViewModel : ViewModel(), PlaybackStateManager.Callback {
|
|||
}
|
||||
|
||||
/**
|
||||
* Go to an item in the queue using it's recyclerview adapter index. No-ops if out of bounds.
|
||||
* Start playing the the queue item at the given index.
|
||||
* @param adapterIndex The index of the queue item to play. Does nothing if the index is out
|
||||
* of range.
|
||||
*/
|
||||
fun goto(adapterIndex: Int) {
|
||||
if (adapterIndex !in playbackManager.queue.indices) {
|
||||
// Invalid input. Nothing to do.
|
||||
return
|
||||
}
|
||||
|
||||
playbackManager.goto(adapterIndex)
|
||||
}
|
||||
|
||||
/** Remove a queue item using it's recyclerview adapter index. */
|
||||
/**
|
||||
* Remove a queue item at the given index.
|
||||
* @param adapterIndex The index of the queue item to play. Does nothing if the index is
|
||||
* out of range.
|
||||
*/
|
||||
fun removeQueueDataItem(adapterIndex: Int) {
|
||||
if (adapterIndex <= playbackManager.index ||
|
||||
adapterIndex !in playbackManager.queue.indices) {
|
||||
// Invalid input. Nothing to do.
|
||||
// TODO: Allow editing played queue items.
|
||||
return
|
||||
}
|
||||
|
||||
playbackManager.removeQueueItem(adapterIndex)
|
||||
}
|
||||
|
||||
/** Move queue items using their recyclerview adapter indices. */
|
||||
/**
|
||||
* Move a queue item from one index to another index.
|
||||
* @param adapterFrom The index of the queue item to move.
|
||||
* @param adapterTo The destination index for the queue item.
|
||||
* @return true if the items were moved, false otherwise.
|
||||
*/
|
||||
fun moveQueueDataItems(adapterFrom: Int, adapterTo: Int): Boolean {
|
||||
if (adapterFrom <= playbackManager.index || adapterTo <= playbackManager.index) {
|
||||
// Invalid input. Nothing to do.
|
||||
return false
|
||||
}
|
||||
|
||||
playbackManager.moveQueueItem(adapterFrom, adapterTo)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Finish a replace flag specified by [replaceQueue].
|
||||
*/
|
||||
fun finishReplace() {
|
||||
replaceQueue = null
|
||||
}
|
||||
|
||||
/**
|
||||
* Finish a scroll operation started by [scrollTo].
|
||||
*/
|
||||
fun finishScrollTo() {
|
||||
scrollTo = null
|
||||
}
|
||||
|
||||
override fun onIndexMoved(index: Int) {
|
||||
// Index moved -> Scroll to new index
|
||||
replaceQueue = null
|
||||
scrollTo = index
|
||||
_index.value = index
|
||||
}
|
||||
|
||||
override fun onQueueChanged(queue: List<Song>) {
|
||||
// Queue changed trivially due to item move -> Diff queue, stay at current index.
|
||||
replaceQueue = false
|
||||
scrollTo = null
|
||||
_queue.value = playbackManager.queue.toMutableList()
|
||||
}
|
||||
|
||||
override fun onQueueReworked(index: Int, queue: List<Song>) {
|
||||
// Queue changed completely -> Replace queue, update index
|
||||
replaceQueue = true
|
||||
scrollTo = index
|
||||
_queue.value = playbackManager.queue.toMutableList()
|
||||
|
@ -106,6 +130,7 @@ class QueueViewModel : ViewModel(), PlaybackStateManager.Callback {
|
|||
}
|
||||
|
||||
override fun onNewPlayback(index: Int, queue: List<Song>, parent: MusicParent?) {
|
||||
// Entirely new queue -> Replace queue, update index
|
||||
replaceQueue = true
|
||||
scrollTo = index
|
||||
_queue.value = playbackManager.queue.toMutableList()
|
||||
|
|
|
@ -29,10 +29,10 @@ import org.oxycblt.auxio.music.Song
|
|||
|
||||
/**
|
||||
* An adapter that displays search results.
|
||||
* @param listener An [ExtendedListListener] to bind interactions to.
|
||||
* @param listener An [SelectableListListener] to bind interactions to.
|
||||
* @author Alexander Capehart (OxygenCobalt)
|
||||
*/
|
||||
class SearchAdapter(private val listener: ExtendedListListener) :
|
||||
class SearchAdapter(private val listener: SelectableListListener) :
|
||||
SelectionIndicatorAdapter<RecyclerView.ViewHolder>(), AuxioRecyclerView.SpanSizeLookup {
|
||||
private val differ = AsyncListDiffer(this, DIFF_CALLBACK)
|
||||
|
||||
|
|
|
@ -23,17 +23,17 @@ import androidx.appcompat.widget.TooltipCompat
|
|||
import androidx.recyclerview.widget.RecyclerView
|
||||
import org.oxycblt.auxio.R
|
||||
import org.oxycblt.auxio.databinding.ItemAccentBinding
|
||||
import org.oxycblt.auxio.list.BasicListListener
|
||||
import org.oxycblt.auxio.list.ClickableListListener
|
||||
import org.oxycblt.auxio.util.getAttrColorCompat
|
||||
import org.oxycblt.auxio.util.getColorCompat
|
||||
import org.oxycblt.auxio.util.inflater
|
||||
|
||||
/**
|
||||
* A [RecyclerView.Adapter] that displays [Accent] choices.
|
||||
* @param listener A [BasicListListener] to bind interactions to.
|
||||
* @param listener A [ClickableListListener] to bind interactions to.
|
||||
* @author Alexander Capehart (OxygenCobalt)
|
||||
*/
|
||||
class AccentAdapter(private val listener: BasicListListener) :
|
||||
class AccentAdapter(private val listener: ClickableListListener) :
|
||||
RecyclerView.Adapter<AccentViewHolder>() {
|
||||
/** The currently selected [Accent]. */
|
||||
var selectedAccent: Accent? = null
|
||||
|
@ -90,9 +90,9 @@ class AccentViewHolder private constructor(private val binding: ItemAccentBindin
|
|||
/**
|
||||
* Bind new data to this instance.
|
||||
* @param accent The new [Accent] to bind.
|
||||
* @param listener A [BasicListListener] to bind interactions to.
|
||||
* @param listener A [ClickableListListener] to bind interactions to.
|
||||
*/
|
||||
fun bind(accent: Accent, listener: BasicListListener) {
|
||||
fun bind(accent: Accent, listener: ClickableListListener) {
|
||||
binding.accent.apply {
|
||||
setOnClickListener { listener.onClick(accent) }
|
||||
backgroundTintList = context.getColorCompat(accent.primary)
|
||||
|
|
|
@ -23,7 +23,7 @@ import androidx.appcompat.app.AlertDialog
|
|||
import org.oxycblt.auxio.BuildConfig
|
||||
import org.oxycblt.auxio.R
|
||||
import org.oxycblt.auxio.databinding.DialogAccentBinding
|
||||
import org.oxycblt.auxio.list.BasicListListener
|
||||
import org.oxycblt.auxio.list.ClickableListListener
|
||||
import org.oxycblt.auxio.list.Item
|
||||
import org.oxycblt.auxio.settings.Settings
|
||||
import org.oxycblt.auxio.shared.ViewBindingDialogFragment
|
||||
|
@ -35,7 +35,7 @@ import org.oxycblt.auxio.util.unlikelyToBeNull
|
|||
* A [ViewBindingDialogFragment] that allows the user to configure the current [Accent].
|
||||
* @author Alexander Capehart (OxygenCobalt)
|
||||
*/
|
||||
class AccentCustomizeDialog : ViewBindingDialogFragment<DialogAccentBinding>(), BasicListListener {
|
||||
class AccentCustomizeDialog : ViewBindingDialogFragment<DialogAccentBinding>(), ClickableListListener {
|
||||
private var accentAdapter = AccentAdapter(this)
|
||||
private val settings: Settings by lifecycleObject { binding -> Settings(binding.context) }
|
||||
|
||||
|
|
Loading…
Reference in a new issue