Add ability to clear user queue

Add an option to clear the user queue in QueueFragment.
This commit is contained in:
OxygenCobalt 2020-11-22 15:25:23 -07:00
parent 49897c53b4
commit 1d50d24c4f
11 changed files with 82 additions and 69 deletions

View file

@ -8,7 +8,10 @@ import android.database.sqlite.SQLiteOpenHelper
import android.util.Log
/**
* A bootstrapped SQLite database for managing the persistent playback state and queue.
* A SQLite database for managing the persistent playback state and queue.
* Yes, I know androidx has Room which supposedly makes database creation easier, but it also
* has a crippling bug where it will endlessly allocate rows even if you clear the entire db, so...
* @author OxygenCobalt
*/
class PlaybackStateDatabase(context: Context) : SQLiteOpenHelper(context, DB_NAME, null, DB_VERSION) {
override fun onCreate(db: SQLiteDatabase) {
@ -64,6 +67,9 @@ class PlaybackStateDatabase(context: Context) : SQLiteOpenHelper(context, DB_NAM
// --- INTERFACE FUNCTIONS ---
/**
* Clear the previously written [PlaybackState] and write a new one.
*/
fun writeState(state: PlaybackState) {
val database = writableDatabase
database.beginTransaction()
@ -102,6 +108,11 @@ class PlaybackStateDatabase(context: Context) : SQLiteOpenHelper(context, DB_NAM
}
}
/**
* Read the stored [PlaybackState] from the database, if there is one.
* @return The stored [PlaybackState], null if there isnt one,.
* @author OxygenCobalt
*/
fun readState(): PlaybackState? {
val database = writableDatabase
@ -112,6 +123,7 @@ class PlaybackStateDatabase(context: Context) : SQLiteOpenHelper(context, DB_NAM
stateCursor = database.query(TABLE_NAME_STATE, null, null, null, null, null, null)
stateCursor?.use { cursor ->
// Don't bother if the cursor [and therefore database] has nothing in it.
if (cursor.count == 0) return@use
val songIndex = cursor.getColumnIndexOrThrow(PlaybackState.COLUMN_SONG_ID)
@ -127,6 +139,7 @@ class PlaybackStateDatabase(context: Context) : SQLiteOpenHelper(context, DB_NAM
val inUserQueueIndex =
cursor.getColumnIndexOrThrow(PlaybackState.COLUMN_IN_USER_QUEUE)
// If there is something in it, get the first item from it, ignoring anything else.
cursor.moveToFirst()
state = PlaybackState(
@ -147,6 +160,11 @@ class PlaybackStateDatabase(context: Context) : SQLiteOpenHelper(context, DB_NAM
}
}
/**
* Write a list of [QueueItem]s to the database, clearing the previous queue present.
* @param queue The list of [QueueItem]s to be written.
* @author OxygenCobalt
*/
fun writeQueue(queue: List<QueueItem>) {
val database = readableDatabase
database.beginTransaction()
@ -196,6 +214,11 @@ class PlaybackStateDatabase(context: Context) : SQLiteOpenHelper(context, DB_NAM
}
}
/**
* Read the database for any [QueueItem]s.
* @return A list of any stored [QueueItem]s.
* @author OxygenCobalt
*/
fun readQueue(): List<QueueItem> {
val database = readableDatabase

View file

@ -120,6 +120,5 @@ data class Genre(
*/
data class Header(
override val id: Long = -1,
override var name: String = "",
val isAction: Boolean = false
override var name: String = ""
) : BaseModel()

View file

@ -159,7 +159,7 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback {
}
// Remove a queue OR user queue item, given a QueueAdapter index.
fun removeQueueItem(adapterIndex: Int, queueAdapter: QueueAdapter) {
fun removeQueueAdapterItem(adapterIndex: Int, queueAdapter: QueueAdapter) {
var index = adapterIndex.dec()
// If the item is in the user queue, then remove it from there after accounting for the header.
@ -182,7 +182,7 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback {
}
// Move queue OR user queue items, given QueueAdapter indices.
fun moveQueueItems(adapterFrom: Int, adapterTo: Int, queueAdapter: QueueAdapter): Boolean {
fun moveQueueAdapterItems(adapterFrom: Int, adapterTo: Int, queueAdapter: QueueAdapter): Boolean {
var from = adapterFrom.dec()
var to = adapterTo.dec()
@ -230,6 +230,10 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback {
playbackManager.addToUserQueue(songs)
}
fun clearUserQueue() {
playbackManager.clearUserQueue()
}
// --- STATUS FUNCTIONS ---
// Flip the playing status.

View file

@ -98,6 +98,14 @@ class QueueAdapter(
}
}
fun clearUserQueue() {
val nextQueueHeaderIndex = data.indexOfLast { it is Header }
val slice = data.slice(0 until nextQueueHeaderIndex)
data.removeAll(slice)
notifyItemRangeRemoved(0, slice.size)
}
// Generic ViewHolder for a queue item
inner class QueueSongViewHolder(
private val binding: ItemQueueSongBinding,

View file

@ -53,7 +53,7 @@ class QueueDragCallback(private val playbackModel: PlaybackViewModel) : ItemTouc
viewHolder: RecyclerView.ViewHolder,
target: RecyclerView.ViewHolder
): Boolean {
return playbackModel.moveQueueItems(
return playbackModel.moveQueueAdapterItems(
viewHolder.adapterPosition,
target.adapterPosition,
queueAdapter
@ -61,7 +61,7 @@ class QueueDragCallback(private val playbackModel: PlaybackViewModel) : ItemTouc
}
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
playbackModel.removeQueueItem(viewHolder.adapterPosition, queueAdapter)
playbackModel.removeQueueAdapterItem(viewHolder.adapterPosition, queueAdapter)
}
fun addQueueAdapter(adapter: QueueAdapter) {

View file

@ -37,12 +37,24 @@ class QueueFragment : Fragment() {
val queueAdapter = QueueAdapter(helper)
callback.addQueueAdapter(queueAdapter)
val queueClearItem = binding.queueToolbar.menu.findItem(R.id.action_clear_user_queue)
// --- UI SETUP ---
binding.queueToolbar.setNavigationOnClickListener {
binding.queueToolbar.apply {
setNavigationOnClickListener {
findNavController().navigateUp()
}
setOnMenuItemClickListener {
if (it.itemId == R.id.action_clear_user_queue) {
queueAdapter.clearUserQueue()
playbackModel.clearUserQueue()
true
} else false
}
}
binding.queueRecycler.apply {
setHasFixedSize(true)
applyDivider()
@ -53,8 +65,14 @@ class QueueFragment : Fragment() {
// --- VIEWMODEL SETUP ---
playbackModel.userQueue.observe(viewLifecycleOwner) {
if (it.isEmpty() && playbackModel.nextItemsInQueue.value!!.isEmpty()) {
if (it.isEmpty()) {
queueClearItem.isEnabled = false
if (playbackModel.nextItemsInQueue.value!!.isEmpty()) {
findNavController().navigateUp()
return@observe
}
}
queueAdapter.submitList(createQueueData())
@ -76,10 +94,7 @@ class QueueFragment : Fragment() {
if (playbackModel.userQueue.value!!.isNotEmpty()) {
queue.add(
Header(
name = getString(R.string.label_next_user_queue),
isAction = true
)
Header(name = getString(R.string.label_next_user_queue))
)
queue.addAll(playbackModel.userQueue.value!!)
}
@ -93,8 +108,7 @@ class QueueFragment : Fragment() {
getString(R.string.label_all_songs)
else
playbackModel.parent.value!!.name
),
isAction = false
)
)
)
queue.addAll(playbackModel.nextItemsInQueue.value!!)

View file

@ -337,6 +337,12 @@ class PlaybackStateManager private constructor() {
forceUserQueueUpdate()
}
fun clearUserQueue() {
mUserQueue.clear()
forceUserQueueUpdate()
}
// Force any callbacks to update when the queue is changed.
private fun forceQueueUpdate() {
mQueue = mQueue

View file

@ -20,6 +20,8 @@
android:clickable="true"
android:focusable="true"
android:elevation="@dimen/elevation_normal"
app:popupTheme="@style/Widget.CustomPopup"
app:menu="@menu/menu_queue"
app:navigationIcon="@drawable/ic_down"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"

View file

@ -1,52 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
tools:context=".recycler.viewholders.HeaderViewHolder">
<data>
<variable
name="header"
type="org.oxycblt.auxio.music.Header" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:id="@+id/header_text"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:fontFamily="@font/inter_semibold"
android:paddingStart="@dimen/padding_medium"
android:paddingTop="@dimen/padding_small"
android:paddingEnd="@dimen/padding_small"
android:paddingBottom="@dimen/padding_small"
android:text="@{header.name}"
android:textColor="?android:attr/textColorPrimary"
android:textSize="19sp"
app:layout_constraintEnd_toStartOf="@+id/header_action_button"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="Songs" />
<ImageButton
android:id="@+id/header_action_button"
style="@style/Widget.AppCompat.Button.Borderless"
android:layout_width="0dp"
android:layout_height="0dp"
android:visibility="gone"
android:src="@drawable/ic_clear"
tools:visibility="visible"
android:background="@drawable/ui_header_dividers"
app:layout_constraintBottom_toBottomOf="@+id/header_text"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintDimensionRatio="1:1"
app:layout_constraintTop_toTopOf="@+id/header_text"
tools:ignore="contentDescription" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

View file

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/action_clear_user_queue"
android:title="@string/label_clear_user_queue"
app:showAsAction="never" />
</menu>

View file

@ -25,6 +25,7 @@
<string name="label_queue_add">Add 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_clear_user_queue">Clear queue</string>
<string name="label_channel">Music Playback</string>
<string name="label_service_playback">The music playback service for Auxio.</string>