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: Migrate to material animation system
|
||||||
*
|
*
|
||||||
* TODO: Re-document project
|
|
||||||
*
|
|
||||||
* TODO: Unit testing
|
* TODO: Unit testing
|
||||||
*
|
*
|
||||||
|
* TODO: Standardize from/new usage
|
||||||
|
*
|
||||||
|
* TODO: Standardize companion object usage
|
||||||
|
*
|
||||||
* @author Alexander Capehart (OxygenCobalt)
|
* @author Alexander Capehart (OxygenCobalt)
|
||||||
*/
|
*/
|
||||||
class MainActivity : AppCompatActivity() {
|
class MainActivity : AppCompatActivity() {
|
||||||
|
|
|
@ -27,7 +27,7 @@ import org.oxycblt.auxio.databinding.ItemAlbumSongBinding
|
||||||
import org.oxycblt.auxio.databinding.ItemDetailBinding
|
import org.oxycblt.auxio.databinding.ItemDetailBinding
|
||||||
import org.oxycblt.auxio.databinding.ItemDiscHeaderBinding
|
import org.oxycblt.auxio.databinding.ItemDiscHeaderBinding
|
||||||
import org.oxycblt.auxio.detail.DiscHeader
|
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.Item
|
||||||
import org.oxycblt.auxio.list.recycler.SelectionIndicatorAdapter
|
import org.oxycblt.auxio.list.recycler.SelectionIndicatorAdapter
|
||||||
import org.oxycblt.auxio.list.recycler.SimpleItemCallback
|
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.
|
* Bind new data to this instance.
|
||||||
* @param song The new [Song] to bind.
|
* @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) {
|
fun bind(song: Song, listener: SelectableListListener) {
|
||||||
listener.bind(song, binding.root, binding.songMenu)
|
listener.bind(this, song, binding.songMenu)
|
||||||
|
|
||||||
binding.songTrack.apply {
|
binding.songTrack.apply {
|
||||||
if (song.track != null) {
|
if (song.track != null) {
|
||||||
|
|
|
@ -26,7 +26,7 @@ import org.oxycblt.auxio.R
|
||||||
import org.oxycblt.auxio.databinding.ItemDetailBinding
|
import org.oxycblt.auxio.databinding.ItemDetailBinding
|
||||||
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.ExtendedListListener
|
import org.oxycblt.auxio.list.SelectableListListener
|
||||||
import org.oxycblt.auxio.list.Item
|
import org.oxycblt.auxio.list.Item
|
||||||
import org.oxycblt.auxio.list.recycler.SelectionIndicatorAdapter
|
import org.oxycblt.auxio.list.recycler.SelectionIndicatorAdapter
|
||||||
import org.oxycblt.auxio.list.recycler.SimpleItemCallback
|
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.
|
* Bind new data to this instance.
|
||||||
* @param album The new [Album] to bind.
|
* @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) {
|
fun bind(album: Album, listener: SelectableListListener) {
|
||||||
listener.bind(album, binding.root, binding.parentMenu)
|
listener.bind(this, album, binding.parentMenu)
|
||||||
binding.parentImage.bind(album)
|
binding.parentImage.bind(album)
|
||||||
binding.parentName.text = album.resolveName(binding.context)
|
binding.parentName.text = album.resolveName(binding.context)
|
||||||
binding.parentInfo.text =
|
binding.parentInfo.text =
|
||||||
|
@ -232,10 +232,10 @@ private class ArtistSongViewHolder private constructor(private val binding: Item
|
||||||
/**
|
/**
|
||||||
* Bind new data to this instance.
|
* Bind new data to this instance.
|
||||||
* @param song The new [Song] to bind.
|
* @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) {
|
fun bind(song: Song, listener: SelectableListListener) {
|
||||||
listener.bind(song, binding.root, binding.songMenu)
|
listener.bind(this, song, binding.songMenu)
|
||||||
binding.songAlbumCover.bind(song)
|
binding.songAlbumCover.bind(song)
|
||||||
binding.songName.text = song.resolveName(binding.context)
|
binding.songName.text = song.resolveName(binding.context)
|
||||||
binding.songInfo.text = song.album.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.IntegerTable
|
||||||
import org.oxycblt.auxio.databinding.ItemSortHeaderBinding
|
import org.oxycblt.auxio.databinding.ItemSortHeaderBinding
|
||||||
import org.oxycblt.auxio.detail.SortHeader
|
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.Header
|
||||||
import org.oxycblt.auxio.list.Item
|
import org.oxycblt.auxio.list.Item
|
||||||
import org.oxycblt.auxio.list.recycler.*
|
import org.oxycblt.auxio.list.recycler.*
|
||||||
|
@ -87,8 +87,8 @@ abstract class DetailAdapter(
|
||||||
differ.submitList(newList)
|
differ.submitList(newList)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** An extended [ExtendedListListener] for [DetailAdapter] implementations. */
|
/** An extended [SelectableListListener] for [DetailAdapter] implementations. */
|
||||||
interface Listener : ExtendedListListener {
|
interface Listener : SelectableListListener {
|
||||||
// TODO: Split off into sub-listeners if a collapsing toolbar is implemented.
|
// 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
|
* 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].
|
* 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>() {
|
SelectionIndicatorAdapter<AlbumViewHolder>() {
|
||||||
private val differ = SyncListDiffer(this, AlbumViewHolder.DIFF_CALLBACK)
|
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].
|
* 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>() {
|
SelectionIndicatorAdapter<ArtistViewHolder>() {
|
||||||
private val differ = SyncListDiffer(this, ArtistViewHolder.DIFF_CALLBACK)
|
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].
|
* 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>() {
|
SelectionIndicatorAdapter<GenreViewHolder>() {
|
||||||
private val differ = SyncListDiffer(this, GenreViewHolder.DIFF_CALLBACK)
|
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].
|
* 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>() {
|
SelectionIndicatorAdapter<SongViewHolder>() {
|
||||||
private val differ = SyncListDiffer(this, SongViewHolder.DIFF_CALLBACK)
|
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)
|
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.
|
* @param viewHolder The [RecyclerView.ViewHolder] to start dragging.
|
||||||
*/
|
*/
|
||||||
fun onPickUpTab(viewHolder: RecyclerView.ViewHolder)
|
fun onPickUp(viewHolder: RecyclerView.ViewHolder)
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
@ -105,7 +106,7 @@ class TabViewHolder private constructor(private val binding: ItemTabBinding) :
|
||||||
/**
|
/**
|
||||||
* Bind new data to this instance.
|
* Bind new data to this instance.
|
||||||
* @param tab The new [Tab] to bind.
|
* @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")
|
@SuppressLint("ClickableViewAccessibility")
|
||||||
fun bind(tab: Tab, listener: TabAdapter.Listener) {
|
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.setOnTouchListener { _, motionEvent ->
|
||||||
binding.tabDragHandle.performClick()
|
binding.tabDragHandle.performClick()
|
||||||
if (motionEvent.actionMasked == MotionEvent.ACTION_DOWN) {
|
if (motionEvent.actionMasked == MotionEvent.ACTION_DOWN) {
|
||||||
listener.onPickUpTab(this)
|
listener.onPickUp(this)
|
||||||
true
|
true
|
||||||
} else false
|
} else false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new instance.
|
* Create a new instance.
|
||||||
* @param parent The parent to inflate this instance from.
|
* @param parent The parent to inflate this instance from.
|
||||||
|
|
|
@ -104,7 +104,7 @@ class TabCustomizeDialog : ViewBindingDialogFragment<DialogTabsBinding>(), TabAd
|
||||||
tabAdapter.tabs.filterIsInstance<Tab.Visible>().isNotEmpty()
|
tabAdapter.tabs.filterIsInstance<Tab.Visible>().isNotEmpty()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onPickUpTab(viewHolder: RecyclerView.ViewHolder) {
|
override fun onPickUp(viewHolder: RecyclerView.ViewHolder) {
|
||||||
touchHelper.startDrag(viewHolder)
|
touchHelper.startDrag(viewHolder)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,6 @@ package org.oxycblt.auxio.list
|
||||||
|
|
||||||
import androidx.annotation.StringRes
|
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.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -36,7 +36,7 @@ import org.oxycblt.auxio.util.showToast
|
||||||
* A Fragment containing a selectable list.
|
* A Fragment containing a selectable list.
|
||||||
* @author Alexander Capehart (OxygenCobalt)
|
* @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()
|
protected val navModel: NavigationViewModel by activityViewModels()
|
||||||
private var currentMenu: PopupMenu? = null
|
private var currentMenu: PopupMenu? = null
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,17 @@
|
||||||
package org.oxycblt.auxio.list
|
package org.oxycblt.auxio.list
|
||||||
|
|
||||||
|
import android.view.MotionEvent
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.Button
|
import android.widget.Button
|
||||||
|
import android.widget.ImageView
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A basic listener for list interactions.
|
* 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.
|
* Called when an [Item] in the list is clicked.
|
||||||
* @param item The [Item] that was 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.
|
* 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.
|
* @param item The [Item] to show a menu for.
|
||||||
|
@ -33,12 +39,12 @@ interface ExtendedListListener : BasicListListener {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Binds this instance to a list item.
|
* 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 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.
|
* @param menuButton A [Button] that opens a menu.
|
||||||
*/
|
*/
|
||||||
fun bind(item: Item, root: View, menuButton: Button) {
|
fun bind(viewHolder: RecyclerView.ViewHolder, item: Item, menuButton: Button) {
|
||||||
root.apply {
|
viewHolder.itemView.apply {
|
||||||
// Map clicks to the click callback.
|
// Map clicks to the click callback.
|
||||||
setOnClickListener { onClick(item) }
|
setOnClickListener { onClick(item) }
|
||||||
// Map long clicks to the selection callback.
|
// Map long clicks to the selection callback.
|
||||||
|
@ -47,7 +53,6 @@ interface ExtendedListListener : BasicListListener {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Map the menu button to the menu opening callback.
|
// Map the menu button to the menu opening callback.
|
||||||
menuButton.setOnClickListener { onOpenMenu(item, it) }
|
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.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.ExtendedListListener
|
import org.oxycblt.auxio.list.SelectableListListener
|
||||||
import org.oxycblt.auxio.list.Header
|
import org.oxycblt.auxio.list.Header
|
||||||
import org.oxycblt.auxio.music.Album
|
import org.oxycblt.auxio.music.Album
|
||||||
import org.oxycblt.auxio.music.Artist
|
import org.oxycblt.auxio.music.Artist
|
||||||
|
@ -43,10 +43,10 @@ class SongViewHolder private constructor(private val binding: ItemSongBinding) :
|
||||||
/**
|
/**
|
||||||
* Bind new data to this instance.
|
* Bind new data to this instance.
|
||||||
* @param song The new [Song] to bind.
|
* @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) {
|
fun bind(song: Song, listener: SelectableListListener) {
|
||||||
listener.bind(song, binding.root, binding.songMenu)
|
listener.bind(this, song, binding.songMenu)
|
||||||
binding.songAlbumCover.bind(song)
|
binding.songAlbumCover.bind(song)
|
||||||
binding.songName.text = song.resolveName(binding.context)
|
binding.songName.text = song.resolveName(binding.context)
|
||||||
binding.songInfo.text = song.resolveArtistContents(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.
|
* Bind new data to this instance.
|
||||||
* @param album The new [Album] to bind.
|
* @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) {
|
fun bind(album: Album, listener: SelectableListListener) {
|
||||||
listener.bind(album, binding.root, binding.parentMenu)
|
listener.bind(this, album, binding.parentMenu)
|
||||||
binding.parentImage.bind(album)
|
binding.parentImage.bind(album)
|
||||||
binding.parentName.text = album.resolveName(binding.context)
|
binding.parentName.text = album.resolveName(binding.context)
|
||||||
binding.parentInfo.text = album.resolveArtistContents(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.
|
* Bind new data to this instance.
|
||||||
* @param artist The new [Artist] to bind.
|
* @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) {
|
fun bind(artist: Artist, listener: SelectableListListener) {
|
||||||
listener.bind(artist, binding.root, binding.parentMenu)
|
listener.bind(this, artist, binding.parentMenu)
|
||||||
binding.parentImage.bind(artist)
|
binding.parentImage.bind(artist)
|
||||||
binding.parentName.text = artist.resolveName(binding.context)
|
binding.parentName.text = artist.resolveName(binding.context)
|
||||||
binding.parentInfo.text =
|
binding.parentInfo.text =
|
||||||
|
@ -197,10 +197,10 @@ class GenreViewHolder private constructor(private val binding: ItemParentBinding
|
||||||
/**
|
/**
|
||||||
* Bind new data to this instance.
|
* Bind new data to this instance.
|
||||||
* @param genre The new [Genre] to bind.
|
* @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) {
|
fun bind(genre: Genre, listener: SelectableListListener) {
|
||||||
listener.bind(genre, binding.root, binding.parentMenu)
|
listener.bind(this, genre, binding.parentMenu)
|
||||||
binding.parentImage.bind(genre)
|
binding.parentImage.bind(genre)
|
||||||
binding.parentName.text = genre.resolveName(binding.context)
|
binding.parentName.text = genre.resolveName(binding.context)
|
||||||
binding.parentInfo.text =
|
binding.parentInfo.text =
|
||||||
|
|
|
@ -21,7 +21,7 @@ import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import org.oxycblt.auxio.databinding.ItemPickerChoiceBinding
|
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.list.recycler.DialogRecyclerView
|
||||||
import org.oxycblt.auxio.music.Artist
|
import org.oxycblt.auxio.music.Artist
|
||||||
import org.oxycblt.auxio.util.context
|
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].
|
* 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.
|
* @author OxygenCobalt.
|
||||||
*/
|
*/
|
||||||
class ArtistChoiceAdapter(private val listener: BasicListListener) :
|
class ArtistChoiceAdapter(private val listener: ClickableListListener) :
|
||||||
RecyclerView.Adapter<ArtistChoiceViewHolder>() {
|
RecyclerView.Adapter<ArtistChoiceViewHolder>() {
|
||||||
private var artists = listOf<Artist>()
|
private var artists = listOf<Artist>()
|
||||||
|
|
||||||
|
@ -65,9 +65,9 @@ class ArtistChoiceViewHolder(private val binding: ItemPickerChoiceBinding) :
|
||||||
/**
|
/**
|
||||||
* Bind new data to this instance.
|
* Bind new data to this instance.
|
||||||
* @param artist The new [Artist] to bind.
|
* @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.root.setOnClickListener { listener.onClick(artist) }
|
||||||
binding.pickerImage.bind(artist)
|
binding.pickerImage.bind(artist)
|
||||||
binding.pickerName.text = artist.resolveName(binding.context)
|
binding.pickerName.text = artist.resolveName(binding.context)
|
||||||
|
|
|
@ -24,7 +24,7 @@ import androidx.fragment.app.viewModels
|
||||||
import androidx.navigation.fragment.findNavController
|
import androidx.navigation.fragment.findNavController
|
||||||
import org.oxycblt.auxio.R
|
import org.oxycblt.auxio.R
|
||||||
import org.oxycblt.auxio.databinding.DialogMusicPickerBinding
|
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.list.Item
|
||||||
import org.oxycblt.auxio.music.Artist
|
import org.oxycblt.auxio.music.Artist
|
||||||
import org.oxycblt.auxio.shared.ViewBindingDialogFragment
|
import org.oxycblt.auxio.shared.ViewBindingDialogFragment
|
||||||
|
@ -36,7 +36,7 @@ import org.oxycblt.auxio.util.collectImmediately
|
||||||
* multiple [Artist]'s to choose from.
|
* multiple [Artist]'s to choose from.
|
||||||
* @author Alexander Capehart (OxygenCobalt)
|
* @author Alexander Capehart (OxygenCobalt)
|
||||||
*/
|
*/
|
||||||
abstract class ArtistPickerDialog : ViewBindingDialogFragment<DialogMusicPickerBinding>(), BasicListListener {
|
abstract class ArtistPickerDialog : ViewBindingDialogFragment<DialogMusicPickerBinding>(), ClickableListListener {
|
||||||
protected val pickerModel: PickerViewModel by viewModels()
|
protected val pickerModel: PickerViewModel by viewModels()
|
||||||
// Okay to leak this since the Listener will not be called until after initialization.
|
// Okay to leak this since the Listener will not be called until after initialization.
|
||||||
private val artistAdapter = ArtistChoiceAdapter(@Suppress("LeakingThis") this)
|
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.getDimen
|
||||||
import org.oxycblt.auxio.util.inflater
|
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>() {
|
RecyclerView.Adapter<QueueSongViewHolder>() {
|
||||||
private var differ = SyncListDiffer(this, QueueSongViewHolder.DIFF_CALLBACK)
|
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 currentIndex = 0
|
||||||
private var isPlaying = false
|
private var isPlaying = false
|
||||||
|
|
||||||
|
@ -59,63 +67,106 @@ class QueueAdapter(private val listener: QueueItemListener) :
|
||||||
viewHolder.bind(differ.currentList[position], listener)
|
viewHolder.bind(differ.currentList[position], listener)
|
||||||
}
|
}
|
||||||
|
|
||||||
viewHolder.isEnabled = position > currentIndex
|
viewHolder.isFuture = position > currentIndex
|
||||||
viewHolder.updatePlayingIndicator(position == currentIndex, isPlaying)
|
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>) {
|
fun submitList(newList: List<Song>) {
|
||||||
differ.submitList(newList)
|
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>) {
|
fun replaceList(newList: List<Song>) {
|
||||||
differ.replaceList(newList)
|
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
|
var updatedIndex = false
|
||||||
|
|
||||||
if (index != currentIndex) {
|
if (index != currentIndex) {
|
||||||
when {
|
val lastIndex = currentIndex
|
||||||
index < currentIndex -> {
|
currentIndex = index
|
||||||
val lastIndex = currentIndex
|
|
||||||
currentIndex = index
|
|
||||||
notifyItemRangeChanged(0, lastIndex + 1, PAYLOAD_UPDATE_INDEX)
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
currentIndex = index
|
|
||||||
notifyItemRangeChanged(0, currentIndex + 1, PAYLOAD_UPDATE_INDEX)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
updatedIndex = true
|
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) {
|
if (this.isPlaying != isPlaying) {
|
||||||
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) {
|
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 {
|
companion object {
|
||||||
val PAYLOAD_UPDATE_INDEX = Any()
|
private val PAYLOAD_UPDATE_POSITION = Any()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
interface QueueItemListener {
|
/**
|
||||||
fun onClick(viewHolder: RecyclerView.ViewHolder)
|
* A [PlayingIndicatorAdapter.ViewHolder] that displays a queue [Song]. Use [new] to create an
|
||||||
fun onPickUp(viewHolder: RecyclerView.ViewHolder)
|
* instance.
|
||||||
}
|
* @author Alexander Capehart (OxygenCobalt)
|
||||||
|
*/
|
||||||
class QueueSongViewHolder private constructor(private val binding: ItemQueueSongBinding) :
|
class QueueSongViewHolder private constructor(private val binding: ItemQueueSongBinding) :
|
||||||
PlayingIndicatorAdapter.ViewHolder(binding.root) {
|
PlayingIndicatorAdapter.ViewHolder(binding.root) {
|
||||||
|
/**
|
||||||
|
* The "body" view of this [QueueSongViewHolder] that shows the [Song] information.
|
||||||
|
*/
|
||||||
val bodyView: View
|
val bodyView: View
|
||||||
get() = binding.body
|
get() = binding.body
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The background view of this [QueueSongViewHolder] that shows the delete icon.
|
||||||
|
*/
|
||||||
val backgroundView: View
|
val backgroundView: View
|
||||||
get() = binding.background
|
get() = binding.background
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The actual background drawable of this [QueueSongViewHolder] that can be manipulated.
|
||||||
|
*/
|
||||||
val backgroundDrawable =
|
val backgroundDrawable =
|
||||||
MaterialShapeDrawable.createWithElevationOverlay(binding.root.context).apply {
|
MaterialShapeDrawable.createWithElevationOverlay(binding.root.context).apply {
|
||||||
fillColor = binding.context.getAttrColorCompat(R.attr.colorSurface)
|
fillColor = binding.context.getAttrColorCompat(R.attr.colorSurface)
|
||||||
|
@ -123,6 +174,19 @@ class QueueSongViewHolder private constructor(private val binding: ItemQueueSong
|
||||||
alpha = 0
|
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 {
|
init {
|
||||||
binding.body.background =
|
binding.body.background =
|
||||||
LayerDrawable(
|
LayerDrawable(
|
||||||
|
@ -134,17 +198,24 @@ class QueueSongViewHolder private constructor(private val binding: ItemQueueSong
|
||||||
backgroundDrawable))
|
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")
|
@SuppressLint("ClickableViewAccessibility")
|
||||||
fun bind(item: Song, listener: QueueItemListener) {
|
fun bind(song: Song, listener: QueueAdapter.Listener) {
|
||||||
binding.songAlbumCover.bind(item)
|
binding.body.setOnClickListener {
|
||||||
binding.songName.text = item.resolveName(binding.context)
|
listener.onClick(this)
|
||||||
binding.songInfo.text = item.resolveArtistContents(binding.context)
|
}
|
||||||
|
|
||||||
|
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.background.isInvisible = true
|
||||||
|
|
||||||
binding.body.setOnClickListener { listener.onClick(this) }
|
// Set up the drag handle to start a drag whenever it is touched.
|
||||||
|
|
||||||
// Roll our own drag handlers as the default ones suck
|
|
||||||
binding.songDragHandle.setOnTouchListener { _, motionEvent ->
|
binding.songDragHandle.setOnTouchListener { _, motionEvent ->
|
||||||
binding.songDragHandle.performClick()
|
binding.songDragHandle.performClick()
|
||||||
if (motionEvent.actionMasked == MotionEvent.ACTION_DOWN) {
|
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) {
|
override fun updatePlayingIndicator(isActive: Boolean, isPlaying: Boolean) {
|
||||||
binding.interactBody.isSelected = isActive
|
binding.interactBody.isSelected = isActive
|
||||||
binding.songAlbumCover.isPlaying = isPlaying
|
binding.songAlbumCover.isPlaying = isPlaying
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
/**
|
||||||
|
* Create a new instance.
|
||||||
|
* @param parent The parent to inflate this instance from.
|
||||||
|
* @return A new instance.
|
||||||
|
*/
|
||||||
fun new(parent: View) =
|
fun new(parent: View) =
|
||||||
QueueSongViewHolder(ItemQueueSongBinding.inflate(parent.context.inflater))
|
QueueSongViewHolder(ItemQueueSongBinding.inflate(parent.context.inflater))
|
||||||
|
|
||||||
|
/** A comparator that can be used with DiffUtil. */
|
||||||
val DIFF_CALLBACK = SongViewHolder.DIFF_CALLBACK
|
val DIFF_CALLBACK = SongViewHolder.DIFF_CALLBACK
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,7 +32,8 @@ import org.oxycblt.auxio.util.replaceSystemBarInsetsCompat
|
||||||
import org.oxycblt.auxio.util.systemBarInsetsCompat
|
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)
|
* @author Alexander Capehart (OxygenCobalt)
|
||||||
*/
|
*/
|
||||||
class QueueBottomSheetBehavior<V : View>(context: Context, attributeSet: AttributeSet?) :
|
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)
|
private var barSpacing = context.getDimenPixels(R.dimen.spacing_small)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
|
// Not hide-able (and not programmatically hide-able)
|
||||||
isHideable = false
|
isHideable = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,18 +55,19 @@ class QueueBottomSheetBehavior<V : View>(context: Context, attributeSet: Attribu
|
||||||
dependency: View
|
dependency: View
|
||||||
): Boolean {
|
): Boolean {
|
||||||
barHeight = dependency.height
|
barHeight = dependency.height
|
||||||
return false // No change, just grabbed the height
|
// No change, just grabbed the height
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun createBackground(context: Context) =
|
override fun createBackground(context: Context) =
|
||||||
MaterialShapeDrawable.createWithElevationOverlay(context).apply {
|
MaterialShapeDrawable.createWithElevationOverlay(context).apply {
|
||||||
|
// The queue sheet's background is a static elevated background.
|
||||||
fillColor = context.getAttrColorCompat(R.attr.colorSurface)
|
fillColor = context.getAttrColorCompat(R.attr.colorSurface)
|
||||||
elevation = context.getDimen(R.dimen.elevation_normal)
|
elevation = context.getDimen(R.dimen.elevation_normal)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun applyWindowInsets(child: View, insets: WindowInsets): WindowInsets {
|
override fun applyWindowInsets(child: View, insets: WindowInsets): WindowInsets {
|
||||||
super.applyWindowInsets(child, insets)
|
super.applyWindowInsets(child, insets)
|
||||||
|
|
||||||
// Offset our expanded panel by the size of the playback bar, as that is shown when
|
// Offset our expanded panel by the size of the playback bar, as that is shown when
|
||||||
// we slide up the panel.
|
// we slide up the panel.
|
||||||
val bars = insets.systemBarInsetsCompat
|
val bars = insets.systemBarInsetsCompat
|
||||||
|
|
|
@ -24,12 +24,12 @@ import androidx.recyclerview.widget.ItemTouchHelper
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import org.oxycblt.auxio.R
|
import org.oxycblt.auxio.R
|
||||||
import org.oxycblt.auxio.util.getDimen
|
import org.oxycblt.auxio.util.getDimen
|
||||||
|
import org.oxycblt.auxio.util.getInteger
|
||||||
import org.oxycblt.auxio.util.logD
|
import org.oxycblt.auxio.util.logD
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A highly customized [ItemTouchHelper.Callback] that handles the queue system while basically
|
* A highly customized [ItemTouchHelper.Callback] that enables some extra eye candy in the queue
|
||||||
* rebuilding most the "Material-y" aspects of an editable list because Google's implementations are
|
* UI, such as an animation when lifting items.
|
||||||
* hot garbage. This shouldn't have *too many* UI bugs. I hope.
|
|
||||||
* @author Alexander Capehart (OxygenCobalt)
|
* @author Alexander Capehart (OxygenCobalt)
|
||||||
*/
|
*/
|
||||||
class QueueDragCallback(private val playbackModel: QueueViewModel) : ItemTouchHelper.Callback() {
|
class QueueDragCallback(private val playbackModel: QueueViewModel) : ItemTouchHelper.Callback() {
|
||||||
|
@ -40,11 +40,13 @@ class QueueDragCallback(private val playbackModel: QueueViewModel) : ItemTouchHe
|
||||||
viewHolder: RecyclerView.ViewHolder
|
viewHolder: RecyclerView.ViewHolder
|
||||||
): Int {
|
): Int {
|
||||||
val queueHolder = viewHolder as QueueSongViewHolder
|
val queueHolder = viewHolder as QueueSongViewHolder
|
||||||
return if (queueHolder.isEnabled) {
|
return if (queueHolder.isFuture) {
|
||||||
makeFlag(
|
makeFlag(
|
||||||
ItemTouchHelper.ACTION_STATE_DRAG, ItemTouchHelper.UP or ItemTouchHelper.DOWN) or
|
ItemTouchHelper.ACTION_STATE_DRAG, ItemTouchHelper.UP or ItemTouchHelper.DOWN) or
|
||||||
makeFlag(ItemTouchHelper.ACTION_STATE_SWIPE, ItemTouchHelper.START)
|
makeFlag(ItemTouchHelper.ACTION_STATE_SWIPE, ItemTouchHelper.START)
|
||||||
} else {
|
} else {
|
||||||
|
// Avoid allowing any touch actions for already-played queue items, as the playback
|
||||||
|
// system does not currently allow for this.
|
||||||
0
|
0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -58,12 +60,11 @@ class QueueDragCallback(private val playbackModel: QueueViewModel) : ItemTouchHe
|
||||||
actionState: Int,
|
actionState: Int,
|
||||||
isCurrentlyActive: Boolean
|
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
|
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) {
|
if (shouldLift && isCurrentlyActive && actionState == ItemTouchHelper.ACTION_STATE_DRAG) {
|
||||||
logD("Lifting queue item")
|
logD("Lifting queue item")
|
||||||
|
|
||||||
|
@ -72,7 +73,7 @@ class QueueDragCallback(private val playbackModel: QueueViewModel) : ItemTouchHe
|
||||||
holder.itemView
|
holder.itemView
|
||||||
.animate()
|
.animate()
|
||||||
.translationZ(elevation)
|
.translationZ(elevation)
|
||||||
.setDuration(100)
|
.setDuration(recyclerView.context.getInteger(R.integer.anim_fade_exit_duration).toLong())
|
||||||
.setUpdateListener {
|
.setUpdateListener {
|
||||||
bg.alpha = ((holder.itemView.translationZ / elevation) * 255).toInt()
|
bg.alpha = ((holder.itemView.translationZ / elevation) * 255).toInt()
|
||||||
}
|
}
|
||||||
|
@ -82,20 +83,19 @@ class QueueDragCallback(private val playbackModel: QueueViewModel) : ItemTouchHe
|
||||||
shouldLift = false
|
shouldLift = false
|
||||||
}
|
}
|
||||||
|
|
||||||
// We show a background with a clear icon behind the queue song each time one is swiped
|
// We show a background with a delete 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
|
// away. To avoid working with canvas, this is simply placed behind the queue body.
|
||||||
// main "body" layout of the queue item and then translate that.
|
|
||||||
//
|
|
||||||
// That comes with a couple of problems, however. For one, the background view will always
|
// 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
|
// 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
|
// 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
|
// not being swiped. This issue is also the reason why the background is not merged with
|
||||||
// another weird pixel desynchronization issue that is less visible but still incredibly
|
// the FrameLayout within the queue item.
|
||||||
// annoying.
|
|
||||||
if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE) {
|
if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE) {
|
||||||
holder.backgroundView.isInvisible = dX == 0f
|
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.bodyView.translationX = dX
|
||||||
holder.itemView.translationY = dY
|
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.
|
// When an elevated item is cleared, we reset the elevation using another animation.
|
||||||
val holder = viewHolder as QueueSongViewHolder
|
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) {
|
if (holder.itemView.translationZ != 0f) {
|
||||||
logD("Dropping queue item")
|
logD("Dropping queue item")
|
||||||
|
|
||||||
|
@ -112,7 +114,7 @@ class QueueDragCallback(private val playbackModel: QueueViewModel) : ItemTouchHe
|
||||||
holder.itemView
|
holder.itemView
|
||||||
.animate()
|
.animate()
|
||||||
.translationZ(0f)
|
.translationZ(0f)
|
||||||
.setDuration(100)
|
.setDuration(recyclerView.context.getInteger(R.integer.anim_fade_exit_duration).toLong())
|
||||||
.setUpdateListener {
|
.setUpdateListener {
|
||||||
bg.alpha = ((holder.itemView.translationZ / elevation) * 255).toInt()
|
bg.alpha = ((holder.itemView.translationZ / elevation) * 255).toInt()
|
||||||
}
|
}
|
||||||
|
@ -122,6 +124,8 @@ class QueueDragCallback(private val playbackModel: QueueViewModel) : ItemTouchHe
|
||||||
|
|
||||||
shouldLift = true
|
shouldLift = true
|
||||||
|
|
||||||
|
// Reset translations. We do not call the default implementation, so we must do
|
||||||
|
// this ourselves.
|
||||||
holder.bodyView.translationX = 0f
|
holder.bodyView.translationX = 0f
|
||||||
holder.itemView.translationY = 0f
|
holder.itemView.translationY = 0f
|
||||||
}
|
}
|
||||||
|
@ -138,5 +142,6 @@ class QueueDragCallback(private val playbackModel: QueueViewModel) : ItemTouchHe
|
||||||
playbackModel.removeQueueDataItem(viewHolder.bindingAdapterPosition)
|
playbackModel.removeQueueDataItem(viewHolder.bindingAdapterPosition)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Long-press events are too buggy, only allow dragging with the handle.
|
||||||
override fun isLongPressDragEnabled() = false
|
override fun isLongPressDragEnabled() = false
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,6 @@ package org.oxycblt.auxio.playback.queue
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import androidx.fragment.app.Fragment
|
|
||||||
import androidx.fragment.app.activityViewModels
|
import androidx.fragment.app.activityViewModels
|
||||||
import androidx.recyclerview.widget.ItemTouchHelper
|
import androidx.recyclerview.widget.ItemTouchHelper
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
|
@ -33,11 +32,10 @@ import org.oxycblt.auxio.util.collectImmediately
|
||||||
import org.oxycblt.auxio.util.logD
|
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)
|
* @author Alexander Capehart (OxygenCobalt)
|
||||||
*/
|
*/
|
||||||
class QueueFragment : ViewBindingFragment<FragmentQueueBinding>(), QueueItemListener {
|
class QueueFragment : ViewBindingFragment<FragmentQueueBinding>(), QueueAdapter.Listener {
|
||||||
private val queueModel: QueueViewModel by activityViewModels()
|
private val queueModel: QueueViewModel by activityViewModels()
|
||||||
private val playbackModel: PlaybackViewModel by androidActivityViewModels()
|
private val playbackModel: PlaybackViewModel by androidActivityViewModels()
|
||||||
private val queueAdapter = QueueAdapter(this)
|
private val queueAdapter = QueueAdapter(this)
|
||||||
|
@ -48,13 +46,15 @@ class QueueFragment : ViewBindingFragment<FragmentQueueBinding>(), QueueItemList
|
||||||
override fun onCreateBinding(inflater: LayoutInflater) = FragmentQueueBinding.inflate(inflater)
|
override fun onCreateBinding(inflater: LayoutInflater) = FragmentQueueBinding.inflate(inflater)
|
||||||
|
|
||||||
override fun onBindingCreated(binding: FragmentQueueBinding, savedInstanceState: Bundle?) {
|
override fun onBindingCreated(binding: FragmentQueueBinding, savedInstanceState: Bundle?) {
|
||||||
|
super.onBindingCreated(binding, savedInstanceState)
|
||||||
|
|
||||||
|
// --- UI SETUP ---
|
||||||
binding.queueRecycler.apply {
|
binding.queueRecycler.apply {
|
||||||
adapter = queueAdapter
|
adapter = queueAdapter
|
||||||
touchHelper.attachToRecyclerView(this)
|
touchHelper.attachToRecyclerView(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- VIEWMODEL SETUP ----
|
// --- VIEWMODEL SETUP ----
|
||||||
|
|
||||||
collectImmediately(
|
collectImmediately(
|
||||||
queueModel.queue, queueModel.index, playbackModel.isPlaying, ::updateQueue)
|
queueModel.queue, queueModel.index, playbackModel.isPlaying, ::updateQueue)
|
||||||
}
|
}
|
||||||
|
@ -65,6 +65,7 @@ class QueueFragment : ViewBindingFragment<FragmentQueueBinding>(), QueueItemList
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onClick(viewHolder: RecyclerView.ViewHolder) {
|
override fun onClick(viewHolder: RecyclerView.ViewHolder) {
|
||||||
|
// Clicking on a queue item should start playing it.
|
||||||
queueModel.goto(viewHolder.bindingAdapterPosition)
|
queueModel.goto(viewHolder.bindingAdapterPosition)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -75,31 +76,34 @@ class QueueFragment : ViewBindingFragment<FragmentQueueBinding>(), QueueItemList
|
||||||
private fun updateQueue(queue: List<Song>, index: Int, isPlaying: Boolean) {
|
private fun updateQueue(queue: List<Song>, index: Int, isPlaying: Boolean) {
|
||||||
val binding = requireBinding()
|
val binding = requireBinding()
|
||||||
|
|
||||||
val replaceQueue = queueModel.replaceQueue
|
// Replace or diff the queue depending on the type of change it is.
|
||||||
if (replaceQueue == true) {
|
// TODO: Extend this to the whole app.
|
||||||
|
if (queueModel.replaceQueue == true) {
|
||||||
logD("Replacing queue")
|
logD("Replacing queue")
|
||||||
queueAdapter.replaceList(queue)
|
queueAdapter.replaceList(queue)
|
||||||
} else {
|
} else {
|
||||||
logD("Diffing queue")
|
logD("Diffing queue")
|
||||||
queueAdapter.submitList(queue)
|
queueAdapter.submitList(queue)
|
||||||
}
|
}
|
||||||
|
|
||||||
queueModel.finishReplace()
|
queueModel.finishReplace()
|
||||||
|
|
||||||
|
// If requested, scroll to a new item (occurs when the index moves)
|
||||||
val scrollTo = queueModel.scrollTo
|
val scrollTo = queueModel.scrollTo
|
||||||
if (scrollTo != null) {
|
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 lmm = binding.queueRecycler.layoutManager as LinearLayoutManager
|
||||||
val start = lmm.findFirstCompletelyVisibleItemPosition()
|
val start = lmm.findFirstCompletelyVisibleItemPosition()
|
||||||
val end = lmm.findLastCompletelyVisibleItemPosition()
|
val end = lmm.findLastCompletelyVisibleItemPosition()
|
||||||
|
|
||||||
if (scrollTo !in start..end) {
|
if (scrollTo !in start..end) {
|
||||||
logD("Scrolling to new position")
|
logD("Scrolling to new position")
|
||||||
binding.queueRecycler.scrollToPosition(scrollTo)
|
binding.queueRecycler.scrollToPosition(scrollTo)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
queueModel.finishScrollTo()
|
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
|
import org.oxycblt.auxio.playback.state.PlaybackStateManager
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class enabling more advanced queue list functionality and queue editing. TODO: Allow editing
|
* A [ViewModel] that manages the current queue state and allows navigation through the queue.
|
||||||
* previous parts of the queue
|
*
|
||||||
* @author Alexander Capehart (OxygenCobalt)
|
* @author Alexander Capehart (OxygenCobalt)
|
||||||
*/
|
*/
|
||||||
class QueueViewModel : ViewModel(), PlaybackStateManager.Callback {
|
class QueueViewModel : ViewModel(), PlaybackStateManager.Callback {
|
||||||
private val playbackManager = PlaybackStateManager.getInstance()
|
private val playbackManager = PlaybackStateManager.getInstance()
|
||||||
|
|
||||||
private val _queue = MutableStateFlow(listOf<Song>())
|
private val _queue = MutableStateFlow(listOf<Song>())
|
||||||
|
/** The current queue. */
|
||||||
val queue: StateFlow<List<Song>> = _queue
|
val queue: StateFlow<List<Song>> = _queue
|
||||||
|
|
||||||
private val _index = MutableStateFlow(playbackManager.index)
|
private val _index = MutableStateFlow(playbackManager.index)
|
||||||
|
/** The index of the currently playing song in the queue. */
|
||||||
val index: StateFlow<Int>
|
val index: StateFlow<Int>
|
||||||
get() = _index
|
get() = _index
|
||||||
|
|
||||||
|
/** Whether to replace or diff the queue list when updating it. Is null if not specified. */
|
||||||
var replaceQueue: Boolean? = null
|
var replaceQueue: Boolean? = null
|
||||||
|
/** Flag to scroll to a particular queue item. Is null if no command has been specified. */
|
||||||
var scrollTo: Int? = null
|
var scrollTo: Int? = null
|
||||||
|
|
||||||
init {
|
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) {
|
fun goto(adapterIndex: Int) {
|
||||||
if (adapterIndex !in playbackManager.queue.indices) {
|
if (adapterIndex !in playbackManager.queue.indices) {
|
||||||
|
// Invalid input. Nothing to do.
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
playbackManager.goto(adapterIndex)
|
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) {
|
fun removeQueueDataItem(adapterIndex: Int) {
|
||||||
if (adapterIndex <= playbackManager.index ||
|
if (adapterIndex <= playbackManager.index ||
|
||||||
adapterIndex !in playbackManager.queue.indices) {
|
adapterIndex !in playbackManager.queue.indices) {
|
||||||
|
// Invalid input. Nothing to do.
|
||||||
|
// TODO: Allow editing played queue items.
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
playbackManager.removeQueueItem(adapterIndex)
|
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 {
|
fun moveQueueDataItems(adapterFrom: Int, adapterTo: Int): Boolean {
|
||||||
if (adapterFrom <= playbackManager.index || adapterTo <= playbackManager.index) {
|
if (adapterFrom <= playbackManager.index || adapterTo <= playbackManager.index) {
|
||||||
|
// Invalid input. Nothing to do.
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
playbackManager.moveQueueItem(adapterFrom, adapterTo)
|
playbackManager.moveQueueItem(adapterFrom, adapterTo)
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finish a replace flag specified by [replaceQueue].
|
||||||
|
*/
|
||||||
fun finishReplace() {
|
fun finishReplace() {
|
||||||
replaceQueue = null
|
replaceQueue = null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finish a scroll operation started by [scrollTo].
|
||||||
|
*/
|
||||||
fun finishScrollTo() {
|
fun finishScrollTo() {
|
||||||
scrollTo = null
|
scrollTo = null
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onIndexMoved(index: Int) {
|
override fun onIndexMoved(index: Int) {
|
||||||
|
// Index moved -> Scroll to new index
|
||||||
replaceQueue = null
|
replaceQueue = null
|
||||||
scrollTo = index
|
scrollTo = index
|
||||||
_index.value = index
|
_index.value = index
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onQueueChanged(queue: List<Song>) {
|
override fun onQueueChanged(queue: List<Song>) {
|
||||||
|
// Queue changed trivially due to item move -> Diff queue, stay at current index.
|
||||||
replaceQueue = false
|
replaceQueue = false
|
||||||
scrollTo = null
|
scrollTo = null
|
||||||
_queue.value = playbackManager.queue.toMutableList()
|
_queue.value = playbackManager.queue.toMutableList()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onQueueReworked(index: Int, queue: List<Song>) {
|
override fun onQueueReworked(index: Int, queue: List<Song>) {
|
||||||
|
// Queue changed completely -> Replace queue, update index
|
||||||
replaceQueue = true
|
replaceQueue = true
|
||||||
scrollTo = index
|
scrollTo = index
|
||||||
_queue.value = playbackManager.queue.toMutableList()
|
_queue.value = playbackManager.queue.toMutableList()
|
||||||
|
@ -106,6 +130,7 @@ class QueueViewModel : ViewModel(), PlaybackStateManager.Callback {
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onNewPlayback(index: Int, queue: List<Song>, parent: MusicParent?) {
|
override fun onNewPlayback(index: Int, queue: List<Song>, parent: MusicParent?) {
|
||||||
|
// Entirely new queue -> Replace queue, update index
|
||||||
replaceQueue = true
|
replaceQueue = true
|
||||||
scrollTo = index
|
scrollTo = index
|
||||||
_queue.value = playbackManager.queue.toMutableList()
|
_queue.value = playbackManager.queue.toMutableList()
|
||||||
|
|
|
@ -29,10 +29,10 @@ import org.oxycblt.auxio.music.Song
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An adapter that displays search results.
|
* 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)
|
* @author Alexander Capehart (OxygenCobalt)
|
||||||
*/
|
*/
|
||||||
class SearchAdapter(private val listener: ExtendedListListener) :
|
class SearchAdapter(private val listener: SelectableListListener) :
|
||||||
SelectionIndicatorAdapter<RecyclerView.ViewHolder>(), AuxioRecyclerView.SpanSizeLookup {
|
SelectionIndicatorAdapter<RecyclerView.ViewHolder>(), AuxioRecyclerView.SpanSizeLookup {
|
||||||
private val differ = AsyncListDiffer(this, DIFF_CALLBACK)
|
private val differ = AsyncListDiffer(this, DIFF_CALLBACK)
|
||||||
|
|
||||||
|
|
|
@ -23,17 +23,17 @@ import androidx.appcompat.widget.TooltipCompat
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import org.oxycblt.auxio.R
|
import org.oxycblt.auxio.R
|
||||||
import org.oxycblt.auxio.databinding.ItemAccentBinding
|
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.getAttrColorCompat
|
||||||
import org.oxycblt.auxio.util.getColorCompat
|
import org.oxycblt.auxio.util.getColorCompat
|
||||||
import org.oxycblt.auxio.util.inflater
|
import org.oxycblt.auxio.util.inflater
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A [RecyclerView.Adapter] that displays [Accent] choices.
|
* 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)
|
* @author Alexander Capehart (OxygenCobalt)
|
||||||
*/
|
*/
|
||||||
class AccentAdapter(private val listener: BasicListListener) :
|
class AccentAdapter(private val listener: ClickableListListener) :
|
||||||
RecyclerView.Adapter<AccentViewHolder>() {
|
RecyclerView.Adapter<AccentViewHolder>() {
|
||||||
/** The currently selected [Accent]. */
|
/** The currently selected [Accent]. */
|
||||||
var selectedAccent: Accent? = null
|
var selectedAccent: Accent? = null
|
||||||
|
@ -90,9 +90,9 @@ class AccentViewHolder private constructor(private val binding: ItemAccentBindin
|
||||||
/**
|
/**
|
||||||
* Bind new data to this instance.
|
* Bind new data to this instance.
|
||||||
* @param accent The new [Accent] to bind.
|
* @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 {
|
binding.accent.apply {
|
||||||
setOnClickListener { listener.onClick(accent) }
|
setOnClickListener { listener.onClick(accent) }
|
||||||
backgroundTintList = context.getColorCompat(accent.primary)
|
backgroundTintList = context.getColorCompat(accent.primary)
|
||||||
|
|
|
@ -23,7 +23,7 @@ import androidx.appcompat.app.AlertDialog
|
||||||
import org.oxycblt.auxio.BuildConfig
|
import org.oxycblt.auxio.BuildConfig
|
||||||
import org.oxycblt.auxio.R
|
import org.oxycblt.auxio.R
|
||||||
import org.oxycblt.auxio.databinding.DialogAccentBinding
|
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.list.Item
|
||||||
import org.oxycblt.auxio.settings.Settings
|
import org.oxycblt.auxio.settings.Settings
|
||||||
import org.oxycblt.auxio.shared.ViewBindingDialogFragment
|
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].
|
* A [ViewBindingDialogFragment] that allows the user to configure the current [Accent].
|
||||||
* @author Alexander Capehart (OxygenCobalt)
|
* @author Alexander Capehart (OxygenCobalt)
|
||||||
*/
|
*/
|
||||||
class AccentCustomizeDialog : ViewBindingDialogFragment<DialogAccentBinding>(), BasicListListener {
|
class AccentCustomizeDialog : ViewBindingDialogFragment<DialogAccentBinding>(), ClickableListListener {
|
||||||
private var accentAdapter = AccentAdapter(this)
|
private var accentAdapter = AccentAdapter(this)
|
||||||
private val settings: Settings by lifecycleObject { binding -> Settings(binding.context) }
|
private val settings: Settings by lifecycleObject { binding -> Settings(binding.context) }
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue