Add ability to clear user queue
Add an option to clear the user queue in QueueFragment.
This commit is contained in:
parent
49897c53b4
commit
1d50d24c4f
11 changed files with 82 additions and 69 deletions
|
@ -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
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -37,10 +37,22 @@ 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 {
|
||||
findNavController().navigateUp()
|
||||
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 {
|
||||
|
@ -53,8 +65,14 @@ class QueueFragment : Fragment() {
|
|||
// --- VIEWMODEL SETUP ---
|
||||
|
||||
playbackModel.userQueue.observe(viewLifecycleOwner) {
|
||||
if (it.isEmpty() && playbackModel.nextItemsInQueue.value!!.isEmpty()) {
|
||||
findNavController().navigateUp()
|
||||
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!!)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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>
|
8
app/src/main/res/menu/menu_queue.xml
Normal file
8
app/src/main/res/menu/menu_queue.xml
Normal 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>
|
|
@ -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>
|
||||
|
||||
|
|
Loading…
Reference in a new issue