Prevent the addition of duplicate queue items
Band-aid over a bug with DiffCallback that causes weird behavior with duplicate queue items by disallowing the addition of queue items if theyre already allowed.
This commit is contained in:
parent
604145fb69
commit
2aa630948b
14 changed files with 59 additions and 43 deletions
|
@ -3,7 +3,6 @@ package org.oxycblt.auxio.music
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
|
|
||||||
// --- MUSIC MODELS ---
|
// --- MUSIC MODELS ---
|
||||||
// FIXME: Remove parent/child references so that they can be parcelable?
|
|
||||||
|
|
||||||
// The base model for all music
|
// The base model for all music
|
||||||
// This is used in a lot of general functions in order to have generic utilities
|
// This is used in a lot of general functions in order to have generic utilities
|
||||||
|
|
|
@ -92,6 +92,9 @@ class MusicStore private constructor() {
|
||||||
@Volatile
|
@Volatile
|
||||||
private var INSTANCE: MusicStore? = null
|
private var INSTANCE: MusicStore? = null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get/Instantiate the single instance of [MusicStore].
|
||||||
|
*/
|
||||||
fun getInstance(): MusicStore {
|
fun getInstance(): MusicStore {
|
||||||
val currentInstance = INSTANCE
|
val currentInstance = INSTANCE
|
||||||
|
|
||||||
|
|
|
@ -35,7 +35,7 @@ private val ID3_GENRES = arrayOf(
|
||||||
"Anime", "JPop", "Synthpop"
|
"Anime", "JPop", "Synthpop"
|
||||||
)
|
)
|
||||||
|
|
||||||
const val PAREN_FILTER = "()"
|
private const val PAREN_FILTER = "()"
|
||||||
|
|
||||||
// --- EXTENSION FUNCTIONS ---
|
// --- EXTENSION FUNCTIONS ---
|
||||||
|
|
||||||
|
|
|
@ -22,17 +22,16 @@ class QueueAdapter(
|
||||||
override fun getItemViewType(position: Int): Int {
|
override fun getItemViewType(position: Int): Int {
|
||||||
val item = getItem(position)
|
val item = getItem(position)
|
||||||
|
|
||||||
if (item is Header) {
|
return if (item is Header)
|
||||||
return HeaderViewHolder.ITEM_TYPE
|
HeaderViewHolder.ITEM_TYPE
|
||||||
} else {
|
else
|
||||||
return QUEUE_ITEM_VIEW_TYPE
|
QUEUE_ITEM_TYPE
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
||||||
return when (viewType) {
|
return when (viewType) {
|
||||||
HeaderViewHolder.ITEM_TYPE -> HeaderViewHolder.from(parent.context)
|
HeaderViewHolder.ITEM_TYPE -> HeaderViewHolder.from(parent.context)
|
||||||
QUEUE_ITEM_VIEW_TYPE -> ViewHolder(
|
QUEUE_ITEM_TYPE -> ViewHolder(
|
||||||
ItemQueueSongBinding.inflate(LayoutInflater.from(parent.context))
|
ItemQueueSongBinding.inflate(LayoutInflater.from(parent.context))
|
||||||
)
|
)
|
||||||
else -> error("Someone messed with the ViewHolder item types. Tell OxygenCobalt.")
|
else -> error("Someone messed with the ViewHolder item types. Tell OxygenCobalt.")
|
||||||
|
@ -76,6 +75,6 @@ class QueueAdapter(
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val QUEUE_ITEM_VIEW_TYPE = 0xA030
|
const val QUEUE_ITEM_TYPE = 0xA015
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,7 @@ class QueueDragCallback(private val playbackModel: PlaybackViewModel) : ItemTouc
|
||||||
recyclerView: RecyclerView,
|
recyclerView: RecyclerView,
|
||||||
viewHolder: RecyclerView.ViewHolder
|
viewHolder: RecyclerView.ViewHolder
|
||||||
): Int {
|
): Int {
|
||||||
// Only allow dragging/swiping with the queue item ViewHolder, not the header items.
|
// Only allow dragging/swiping with the queue item ViewHolder, not the headers.
|
||||||
return if (viewHolder is QueueAdapter.ViewHolder) {
|
return if (viewHolder is QueueAdapter.ViewHolder) {
|
||||||
makeFlag(
|
makeFlag(
|
||||||
ItemTouchHelper.ACTION_STATE_DRAG, ItemTouchHelper.UP or ItemTouchHelper.DOWN
|
ItemTouchHelper.ACTION_STATE_DRAG, ItemTouchHelper.UP or ItemTouchHelper.DOWN
|
||||||
|
|
|
@ -43,6 +43,8 @@ class QueueFragment : Fragment() {
|
||||||
helper.attachToRecyclerView(this)
|
helper.attachToRecyclerView(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- VIEWMODEL SETUP ---
|
||||||
|
|
||||||
playbackModel.userQueue.observe(viewLifecycleOwner) {
|
playbackModel.userQueue.observe(viewLifecycleOwner) {
|
||||||
if (it.isEmpty() && playbackModel.nextItemsInQueue.value!!.isEmpty()) {
|
if (it.isEmpty() && playbackModel.nextItemsInQueue.value!!.isEmpty()) {
|
||||||
findNavController().navigateUp()
|
findNavController().navigateUp()
|
||||||
|
|
|
@ -12,7 +12,7 @@ import kotlin.random.Random
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Master class for the playback state. This should ***not*** be used outside of the playback module.
|
* Master class for the playback state. This should ***not*** be used outside of the playback module.
|
||||||
* - If you want to show the playback state in the UI, use [org.oxycblt.auxio.playback.PlaybackViewModel].
|
* - If you want to use the playback state in the UI, use [org.oxycblt.auxio.playback.PlaybackViewModel].
|
||||||
* - If you want to add to the system aspects or the exoplayer instance, use [org.oxycblt.auxio.playback.PlaybackService].
|
* - If you want to add to the system aspects or the exoplayer instance, use [org.oxycblt.auxio.playback.PlaybackService].
|
||||||
*
|
*
|
||||||
* All instantiation should be done with [PlaybackStateManager.from()].
|
* All instantiation should be done with [PlaybackStateManager.from()].
|
||||||
|
@ -301,6 +301,8 @@ class PlaybackStateManager private constructor() {
|
||||||
|
|
||||||
mUserQueue.removeAt(index)
|
mUserQueue.removeAt(index)
|
||||||
|
|
||||||
|
Log.d(this::class.simpleName, mUserQueue.toString())
|
||||||
|
|
||||||
forceUserQueueUpdate()
|
forceUserQueueUpdate()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
package org.oxycblt.auxio.recycler
|
package org.oxycblt.auxio.recycler
|
||||||
|
|
||||||
// TODO: Swap these temp values for actual constants
|
|
||||||
enum class ShowMode {
|
enum class ShowMode {
|
||||||
SHOW_GENRES, SHOW_ARTISTS, SHOW_ALBUMS, SHOW_SONGS;
|
SHOW_GENRES, SHOW_ARTISTS, SHOW_ALBUMS, SHOW_SONGS;
|
||||||
|
|
||||||
|
|
|
@ -28,7 +28,7 @@ class GenreViewHolder private constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val ITEM_TYPE = 10
|
const val ITEM_TYPE = 0xA010
|
||||||
|
|
||||||
fun from(context: Context, doOnClick: (Genre) -> Unit): GenreViewHolder {
|
fun from(context: Context, doOnClick: (Genre) -> Unit): GenreViewHolder {
|
||||||
return GenreViewHolder(
|
return GenreViewHolder(
|
||||||
|
@ -50,7 +50,7 @@ class ArtistViewHolder private constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val ITEM_TYPE = 11
|
const val ITEM_TYPE = 0xA011
|
||||||
|
|
||||||
fun from(context: Context, doOnClick: (Artist) -> Unit): ArtistViewHolder {
|
fun from(context: Context, doOnClick: (Artist) -> Unit): ArtistViewHolder {
|
||||||
return ArtistViewHolder(
|
return ArtistViewHolder(
|
||||||
|
@ -72,7 +72,7 @@ class AlbumViewHolder private constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val ITEM_TYPE = 12
|
const val ITEM_TYPE = 0xA012
|
||||||
|
|
||||||
fun from(context: Context, doOnClick: (data: Album) -> Unit): AlbumViewHolder {
|
fun from(context: Context, doOnClick: (data: Album) -> Unit): AlbumViewHolder {
|
||||||
return AlbumViewHolder(
|
return AlbumViewHolder(
|
||||||
|
@ -99,7 +99,7 @@ class SongViewHolder private constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val ITEM_TYPE = 13
|
const val ITEM_TYPE = 0xA013
|
||||||
|
|
||||||
fun from(
|
fun from(
|
||||||
context: Context,
|
context: Context,
|
||||||
|
@ -123,7 +123,7 @@ class HeaderViewHolder(
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val ITEM_TYPE = 14
|
const val ITEM_TYPE = 0xA014
|
||||||
|
|
||||||
fun from(context: Context): HeaderViewHolder {
|
fun from(context: Context): HeaderViewHolder {
|
||||||
return HeaderViewHolder(
|
return HeaderViewHolder(
|
||||||
|
|
|
@ -60,13 +60,7 @@ fun PopupMenu.setupSongActions(song: Song, context: Context, playbackModel: Play
|
||||||
setOnMenuItemClickListener {
|
setOnMenuItemClickListener {
|
||||||
when (it.itemId) {
|
when (it.itemId) {
|
||||||
R.id.action_queue_add -> {
|
R.id.action_queue_add -> {
|
||||||
playbackModel.addToUserQueue(song)
|
doUserQueueAdd(context, song, playbackModel)
|
||||||
|
|
||||||
Toast.makeText(
|
|
||||||
context,
|
|
||||||
context.getString(R.string.label_queue_added),
|
|
||||||
Toast.LENGTH_SHORT
|
|
||||||
).show()
|
|
||||||
|
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
@ -97,13 +91,7 @@ fun PopupMenu.setupAlbumSongActions(
|
||||||
setOnMenuItemClickListener {
|
setOnMenuItemClickListener {
|
||||||
when (it.itemId) {
|
when (it.itemId) {
|
||||||
R.id.action_queue_add -> {
|
R.id.action_queue_add -> {
|
||||||
playbackModel.addToUserQueue(song)
|
doUserQueueAdd(context, song, playbackModel)
|
||||||
|
|
||||||
Toast.makeText(
|
|
||||||
context,
|
|
||||||
context.getString(R.string.label_queue_added),
|
|
||||||
Toast.LENGTH_SHORT
|
|
||||||
).show()
|
|
||||||
|
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
@ -113,8 +101,35 @@ fun PopupMenu.setupAlbumSongActions(
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
R.id.action_play_artist -> {
|
||||||
|
playbackModel.playSong(song, PlaybackMode.IN_ARTIST)
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
else -> false
|
else -> false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
show()
|
show()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun doUserQueueAdd(context: Context, song: Song, playbackModel: PlaybackViewModel) {
|
||||||
|
// If the song was already added to the user queue, then don't add it again.
|
||||||
|
// This is just to prevent a bug with DiffCallback that creates strange
|
||||||
|
// behavior when duplicate user queue items are added.
|
||||||
|
// FIXME: Fix the duplicate item DiffCallback issue
|
||||||
|
if (!playbackModel.userQueue.value!!.contains(song)) {
|
||||||
|
playbackModel.addToUserQueue(song)
|
||||||
|
|
||||||
|
Toast.makeText(
|
||||||
|
context,
|
||||||
|
context.getString(R.string.label_queue_added),
|
||||||
|
Toast.LENGTH_SHORT
|
||||||
|
).show()
|
||||||
|
} else {
|
||||||
|
Toast.makeText(
|
||||||
|
context,
|
||||||
|
context.getString(R.string.label_queue_already_added),
|
||||||
|
Toast.LENGTH_SHORT
|
||||||
|
).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -45,6 +45,8 @@
|
||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
android:layout_margin="@dimen/margin_mid_large"
|
android:layout_margin="@dimen/margin_mid_large"
|
||||||
android:contentDescription="@{@string/description_album_cover(song.name)}"
|
android:contentDescription="@{@string/description_album_cover(song.name)}"
|
||||||
|
android:outlineProvider="bounds"
|
||||||
|
android:elevation="4dp"
|
||||||
app:coverArt="@{song}"
|
app:coverArt="@{song}"
|
||||||
app:layout_constraintBottom_toTopOf="@+id/playback_song"
|
app:layout_constraintBottom_toTopOf="@+id/playback_song"
|
||||||
app:layout_constraintDimensionRatio="1:1"
|
app:layout_constraintDimensionRatio="1:1"
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
tools:context=".playback.queue.QueueFragment">
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
@ -34,15 +35,5 @@
|
||||||
tools:layout_editor_absoluteX="0dp"
|
tools:layout_editor_absoluteX="0dp"
|
||||||
tools:listitem="@layout/item_song" />
|
tools:listitem="@layout/item_song" />
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/queue_nothing_indicator"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:layout_marginTop="@dimen/margin_medium"
|
|
||||||
android:textSize="15sp"
|
|
||||||
android:text="@string/label_empty_queue"
|
|
||||||
android:textAlignment="center"
|
|
||||||
android:visibility="gone" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</layout>
|
</layout>
|
|
@ -8,4 +8,8 @@
|
||||||
android:id="@+id/action_go_artist"
|
android:id="@+id/action_go_artist"
|
||||||
android:title="@string/label_go_artist"
|
android:title="@string/label_go_artist"
|
||||||
android:icon="@drawable/ic_artist" />
|
android:icon="@drawable/ic_artist" />
|
||||||
|
<item
|
||||||
|
android:id="@+id/action_play_artist"
|
||||||
|
android:title="@string/label_play_artist"
|
||||||
|
android:icon="@drawable/ic_artist" />
|
||||||
</menu>
|
</menu>
|
|
@ -32,7 +32,7 @@
|
||||||
<string name="label_queue_add">Add to queue</string>
|
<string name="label_queue_add">Add to queue</string>
|
||||||
<string name="label_queue_added">Added to queue</string>
|
<string name="label_queue_added">Added to queue</string>
|
||||||
<string name="label_next_user_queue">Next in Queue</string>
|
<string name="label_next_user_queue">Next in Queue</string>
|
||||||
<string name="label_empty_queue">Nothing here</string>
|
<string name="label_queue_already_added">Already in queue!</string>
|
||||||
<string name="label_notification_playback">Music Playback</string>
|
<string name="label_notification_playback">Music Playback</string>
|
||||||
<string name="label_service_playback">The music playback service for Auxio.</string>
|
<string name="label_service_playback">The music playback service for Auxio.</string>
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue