From 49897c53b416ddcde88ca084621e2eecaa78abc4 Mon Sep 17 00:00:00 2001 From: OxygenCobalt Date: Sun, 22 Nov 2020 14:30:45 -0700 Subject: [PATCH] Rewrite Database to stop using room Refactor/Rewrite the database system to be based on SQLiteOpenHelper instead of Room, as Room will keep empty columns around even after trying to explicitly delete them. --- app/build.gradle | 6 - .../oxycblt/auxio/database/AuxioDatabase.kt | 38 --- .../oxycblt/auxio/database/PlaybackState.kt | 41 ++- .../auxio/database/PlaybackStateDAO.kt | 20 -- .../auxio/database/PlaybackStateDatabase.kt | 261 ++++++++++++++++++ .../org/oxycblt/auxio/database/QueueDAO.kt | 29 -- .../org/oxycblt/auxio/database/QueueItem.kt | 24 +- .../java/org/oxycblt/auxio/music/Models.kt | 1 + .../auxio/playback/PlaybackFragment.kt | 2 - .../auxio/playback/queue/QueueAdapter.kt | 6 +- .../auxio/playback/queue/QueueDragCallback.kt | 2 +- .../auxio/playback/queue/QueueFragment.kt | 10 +- .../playback/state/PlaybackStateManager.kt | 76 +++-- .../recycler/viewholders/ModelHolders.kt | 2 +- app/src/main/res/drawable/ic_clear.xml | 11 + .../res/drawable/ui_background_ripple.xml | 19 ++ .../main/res/drawable/ui_header_dividers.xml | 3 +- .../res/layout/fragment_compact_playback.xml | 4 +- app/src/main/res/layout/fragment_main.xml | 5 +- app/src/main/res/layout/fragment_playback.xml | 2 +- .../main/res/layout/item_action_header.xml | 52 ++++ app/src/main/res/layout/item_header.xml | 30 +- 22 files changed, 436 insertions(+), 208 deletions(-) delete mode 100644 app/src/main/java/org/oxycblt/auxio/database/AuxioDatabase.kt delete mode 100644 app/src/main/java/org/oxycblt/auxio/database/PlaybackStateDAO.kt create mode 100644 app/src/main/java/org/oxycblt/auxio/database/PlaybackStateDatabase.kt delete mode 100644 app/src/main/java/org/oxycblt/auxio/database/QueueDAO.kt create mode 100644 app/src/main/res/drawable/ic_clear.xml create mode 100644 app/src/main/res/drawable/ui_background_ripple.xml create mode 100644 app/src/main/res/layout/item_action_header.xml diff --git a/app/build.gradle b/app/build.gradle index b4941e512..ad870864a 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -72,12 +72,6 @@ dependencies { // Media implementation 'androidx.media:media:1.2.0' - // Database - def room_version = '2.3.0-alpha03' - implementation "androidx.room:room-runtime:$room_version" - kapt "androidx.room:room-compiler:$room_version" - implementation "androidx.room:room-ktx:$room_version" - // --- THIRD PARTY --- // Image loading diff --git a/app/src/main/java/org/oxycblt/auxio/database/AuxioDatabase.kt b/app/src/main/java/org/oxycblt/auxio/database/AuxioDatabase.kt deleted file mode 100644 index bcdd4957f..000000000 --- a/app/src/main/java/org/oxycblt/auxio/database/AuxioDatabase.kt +++ /dev/null @@ -1,38 +0,0 @@ -package org.oxycblt.auxio.database - -import android.content.Context -import androidx.room.Database -import androidx.room.Room -import androidx.room.RoomDatabase - -@Database(entities = [PlaybackState::class, QueueItem::class], version = 1, exportSchema = false) -abstract class AuxioDatabase : RoomDatabase() { - abstract val playbackStateDAO: PlaybackStateDAO - abstract val queueDAO: QueueDAO - - companion object { - @Volatile - private var INSTANCE: AuxioDatabase? = null - - /** - * Get/Instantiate the single instance of [AuxioDatabase]. - */ - fun getInstance(context: Context): AuxioDatabase { - val currentInstance = INSTANCE - - if (currentInstance != null) { - return currentInstance - } - - synchronized(this) { - val newInstance = Room.databaseBuilder( - context.applicationContext, - AuxioDatabase::class.java, - "playback_state_database" - ).fallbackToDestructiveMigration().build() - INSTANCE = newInstance - return newInstance - } - } - } -} diff --git a/app/src/main/java/org/oxycblt/auxio/database/PlaybackState.kt b/app/src/main/java/org/oxycblt/auxio/database/PlaybackState.kt index 64f909284..4b3ce9456 100644 --- a/app/src/main/java/org/oxycblt/auxio/database/PlaybackState.kt +++ b/app/src/main/java/org/oxycblt/auxio/database/PlaybackState.kt @@ -1,32 +1,27 @@ package org.oxycblt.auxio.database -import androidx.room.ColumnInfo -import androidx.room.Entity -import androidx.room.PrimaryKey - -@Entity(tableName = "playback_state_table") data class PlaybackState( - @PrimaryKey(autoGenerate = true) - var id: Long = Long.MIN_VALUE, - - @ColumnInfo(name = "song_id") - val songId: Long = Long.MIN_VALUE, - - @ColumnInfo(name = "position") + val id: Long = 0L, + val songId: Long = -1L, val position: Long, - - @ColumnInfo(name = "parent_id") val parentId: Long = -1L, - - @ColumnInfo(name = "mode") + val index: Int, val mode: Int, - - @ColumnInfo(name = "is_shuffling") val isShuffling: Boolean, - - @ColumnInfo(name = "loop_mode") + val shuffleSeed: Long, val loopMode: Int, - - @ColumnInfo(name = "in_user_queue") val inUserQueue: Boolean -) +) { + companion object { + const val COLUMN_ID = "state_id" + const val COLUMN_SONG_ID = "song_id" + const val COLUMN_POSITION = "position" + const val COLUMN_PARENT_ID = "parent_id" + const val COLUMN_INDEX = "state_index" + const val COLUMN_MODE = "mode" + const val COLUMN_IS_SHUFFLING = "is_shuffling" + const val COLUMN_SHUFFLE_SEED = "shuffle_seed" + const val COLUMN_LOOP_MODE = "loop_mode" + const val COLUMN_IN_USER_QUEUE = "is_user_queue" + } +} diff --git a/app/src/main/java/org/oxycblt/auxio/database/PlaybackStateDAO.kt b/app/src/main/java/org/oxycblt/auxio/database/PlaybackStateDAO.kt deleted file mode 100644 index 0f7a5a9ec..000000000 --- a/app/src/main/java/org/oxycblt/auxio/database/PlaybackStateDAO.kt +++ /dev/null @@ -1,20 +0,0 @@ -package org.oxycblt.auxio.database - -import androidx.room.Dao -import androidx.room.Insert -import androidx.room.Query - -@Dao -interface PlaybackStateDAO { - @Insert - fun insert(playbackState: PlaybackState) - - @Query("SELECT * FROM playback_state_table") - fun getAll(): List - - @Query("DELETE FROM playback_state_table") - fun clear() - - @Query("SELECT * FROM playback_state_table ORDER BY id DESC LIMIT 1") - fun getRecent(): PlaybackState? -} diff --git a/app/src/main/java/org/oxycblt/auxio/database/PlaybackStateDatabase.kt b/app/src/main/java/org/oxycblt/auxio/database/PlaybackStateDatabase.kt new file mode 100644 index 000000000..372a72eb2 --- /dev/null +++ b/app/src/main/java/org/oxycblt/auxio/database/PlaybackStateDatabase.kt @@ -0,0 +1,261 @@ +package org.oxycblt.auxio.database + +import android.content.ContentValues +import android.content.Context +import android.database.Cursor +import android.database.sqlite.SQLiteDatabase +import android.database.sqlite.SQLiteOpenHelper +import android.util.Log + +/** + * A bootstrapped SQLite database for managing the persistent playback state and queue. + */ +class PlaybackStateDatabase(context: Context) : SQLiteOpenHelper(context, DB_NAME, null, DB_VERSION) { + override fun onCreate(db: SQLiteDatabase) { + createTable(db, TABLE_NAME_STATE) + createTable(db, TABLE_NAME_QUEUE) + } + + override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) { + db.execSQL("DROP TABLE IF EXISTS $TABLE_NAME_STATE") + db.execSQL("DROP TABLE IF EXISTS $TABLE_NAME_QUEUE") + + onCreate(db) + } + + // --- DATABASE CONSTRUCTION FUNCTIONS --- + + private fun createTable(database: SQLiteDatabase, tableName: String) { + val command = StringBuilder() + command.append("CREATE TABLE IF NOT EXISTS $tableName(") + + if (tableName == TABLE_NAME_STATE) { + constructStateTable(command) + } else if (tableName == TABLE_NAME_QUEUE) { + constructQueueTable(command) + } + + database.execSQL(command.toString()) + } + + private fun constructStateTable(command: StringBuilder): StringBuilder { + command.append("${PlaybackState.COLUMN_ID} LONG PRIMARY KEY,") + command.append("${PlaybackState.COLUMN_SONG_ID} LONG NOT NULL,") + command.append("${PlaybackState.COLUMN_POSITION} LONG NOT NULL,") + command.append("${PlaybackState.COLUMN_PARENT_ID} LONG NOT NULL,") + command.append("${PlaybackState.COLUMN_INDEX} INTEGER NOT NULL,") + command.append("${PlaybackState.COLUMN_MODE} INTEGER NOT NULL,") + command.append("${PlaybackState.COLUMN_IS_SHUFFLING} BOOLEAN NOT NULL,") + command.append("${PlaybackState.COLUMN_SHUFFLE_SEED} LONG NOT NULL,") + command.append("${PlaybackState.COLUMN_LOOP_MODE} INTEGER NOT NULL,") + command.append("${PlaybackState.COLUMN_IN_USER_QUEUE} BOOLEAN NOT NULL)") + + return command + } + + private fun constructQueueTable(command: StringBuilder): StringBuilder { + command.append("${QueueItem.COLUMN_ID} LONG PRIMARY KEY,") + command.append("${QueueItem.COLUMN_SONG_ID} LONG NOT NULL,") + command.append("${QueueItem.COLUMN_ALBUM_ID} LONG NOT NULL,") + command.append("${QueueItem.COLUMN_IS_USER_QUEUE} BOOLEAN NOT NULL)") + + return command + } + + // --- INTERFACE FUNCTIONS --- + + fun writeState(state: PlaybackState) { + val database = writableDatabase + database.beginTransaction() + + try { + database.delete(TABLE_NAME_STATE, null, null) + database.setTransactionSuccessful() + } finally { + database.endTransaction() + + Log.d(this::class.simpleName, "Successfully wiped previous state.") + } + + try { + database.beginTransaction() + + val stateData = ContentValues(10) + + stateData.put(PlaybackState.COLUMN_ID, state.id) + stateData.put(PlaybackState.COLUMN_SONG_ID, state.songId) + stateData.put(PlaybackState.COLUMN_POSITION, state.position) + stateData.put(PlaybackState.COLUMN_PARENT_ID, state.parentId) + stateData.put(PlaybackState.COLUMN_INDEX, state.index) + stateData.put(PlaybackState.COLUMN_MODE, state.mode) + stateData.put(PlaybackState.COLUMN_IS_SHUFFLING, state.isShuffling) + stateData.put(PlaybackState.COLUMN_SHUFFLE_SEED, state.shuffleSeed) + stateData.put(PlaybackState.COLUMN_LOOP_MODE, state.loopMode) + stateData.put(PlaybackState.COLUMN_IN_USER_QUEUE, state.inUserQueue) + + database.insert(TABLE_NAME_STATE, null, stateData) + database.setTransactionSuccessful() + } finally { + database.endTransaction() + + Log.d(this::class.simpleName, "Wrote state to database.") + } + } + + fun readState(): PlaybackState? { + val database = writableDatabase + + var state: PlaybackState? = null + var stateCursor: Cursor? = null + + try { + stateCursor = database.query(TABLE_NAME_STATE, null, null, null, null, null, null) + + stateCursor?.use { cursor -> + if (cursor.count == 0) return@use + + val songIndex = cursor.getColumnIndexOrThrow(PlaybackState.COLUMN_SONG_ID) + val positionIndex = cursor.getColumnIndexOrThrow(PlaybackState.COLUMN_POSITION) + val parentIndex = cursor.getColumnIndexOrThrow(PlaybackState.COLUMN_PARENT_ID) + val indexIndex = cursor.getColumnIndexOrThrow(PlaybackState.COLUMN_INDEX) + val modeIndex = cursor.getColumnIndexOrThrow(PlaybackState.COLUMN_MODE) + val isShufflingIndex = + cursor.getColumnIndexOrThrow(PlaybackState.COLUMN_IS_SHUFFLING) + val shuffleSeedIndex = + cursor.getColumnIndexOrThrow(PlaybackState.COLUMN_SHUFFLE_SEED) + val loopModeIndex = cursor.getColumnIndexOrThrow(PlaybackState.COLUMN_LOOP_MODE) + val inUserQueueIndex = + cursor.getColumnIndexOrThrow(PlaybackState.COLUMN_IN_USER_QUEUE) + + cursor.moveToFirst() + + state = PlaybackState( + songId = cursor.getLong(songIndex), + position = cursor.getLong(positionIndex), + parentId = cursor.getLong(parentIndex), + index = cursor.getInt(indexIndex), + mode = cursor.getInt(modeIndex), + isShuffling = cursor.getInt(isShufflingIndex) == 1, + shuffleSeed = cursor.getLong(shuffleSeedIndex), + loopMode = cursor.getInt(loopModeIndex), + inUserQueue = cursor.getInt(inUserQueueIndex) == 1 + ) + } + } finally { + stateCursor?.close() + return state + } + } + + fun writeQueue(queue: List) { + val database = readableDatabase + database.beginTransaction() + + try { + database.delete(TABLE_NAME_QUEUE, null, null) + database.setTransactionSuccessful() + } finally { + database.endTransaction() + + Log.d(this::class.simpleName, "Successfully wiped queue.") + } + + Log.d(this::class.simpleName, "Writing to queue.") + + var position = 0 + + while (position < queue.size) { + database.beginTransaction() + var i = position + + try { + while (i < queue.size) { + val item = queue[i] + val itemData = ContentValues(4) + + i++ + + itemData.put(QueueItem.COLUMN_ID, item.id) + itemData.put(QueueItem.COLUMN_SONG_ID, item.songId) + itemData.put(QueueItem.COLUMN_ALBUM_ID, item.albumId) + itemData.put(QueueItem.COLUMN_IS_USER_QUEUE, item.isUserQueue) + + database.insert(TABLE_NAME_QUEUE, null, itemData) + } + + database.setTransactionSuccessful() + } finally { + database.endTransaction() + + // Update the position at the end, if an insert failed at any point, then + // the next iteration should skip it. + position = i + + Log.d(this::class.simpleName, "Wrote batch of $position songs.") + } + } + } + + fun readQueue(): List { + val database = readableDatabase + + val queueItems = mutableListOf() + var queueCursor: Cursor? = null + + try { + queueCursor = database.query(TABLE_NAME_QUEUE, null, null, null, null, null, null) + + queueCursor?.use { cursor -> + if (cursor.count == 0) return@use + + val idIndex = cursor.getColumnIndexOrThrow(QueueItem.COLUMN_ID) + val songIdIndex = cursor.getColumnIndexOrThrow(QueueItem.COLUMN_SONG_ID) + val albumIdIndex = cursor.getColumnIndexOrThrow(QueueItem.COLUMN_ALBUM_ID) + val isUserQueueIndex = cursor.getColumnIndexOrThrow(QueueItem.COLUMN_IS_USER_QUEUE) + + while (cursor.moveToNext()) { + val id = cursor.getLong(idIndex) + val songId = cursor.getLong(songIdIndex) + val albumId = cursor.getLong(albumIdIndex) + val isUserQueue = cursor.getInt(isUserQueueIndex) == 1 + + queueItems.add( + QueueItem(id, songId, albumId, isUserQueue) + ) + } + } + } finally { + queueCursor?.close() + + return queueItems + } + } + + companion object { + const val DB_VERSION = 1 + const val DB_NAME = "auxio_state_database" + + const val TABLE_NAME_STATE = "playback_state_table" + const val TABLE_NAME_QUEUE = "queue_table" + + @Volatile + private var INSTANCE: PlaybackStateDatabase? = null + + /** + * Get/Instantiate the single instance of [PlaybackStateDatabase]. + */ + fun getInstance(context: Context): PlaybackStateDatabase { + val currentInstance = INSTANCE + + if (currentInstance != null) { + return currentInstance + } + + synchronized(this) { + val newInstance = PlaybackStateDatabase(context.applicationContext) + INSTANCE = newInstance + return newInstance + } + } + } +} diff --git a/app/src/main/java/org/oxycblt/auxio/database/QueueDAO.kt b/app/src/main/java/org/oxycblt/auxio/database/QueueDAO.kt deleted file mode 100644 index 64167eb2d..000000000 --- a/app/src/main/java/org/oxycblt/auxio/database/QueueDAO.kt +++ /dev/null @@ -1,29 +0,0 @@ -package org.oxycblt.auxio.database - -import androidx.room.Dao -import androidx.room.Insert -import androidx.room.OnConflictStrategy -import androidx.room.Query -import androidx.room.Transaction - -@Dao -interface QueueDAO { - @Insert(onConflict = OnConflictStrategy.IGNORE) - fun insert(item: QueueItem) - - @Transaction - suspend fun insertAll(items: List) { - items.forEach { - insert(it) - } - } - - @Query("SELECT * FROM queue_table WHERE is_user_queue == 1") - fun getUserQueue(): List - - @Query("SELECT * FROM queue_table WHERE is_user_queue == 0") - fun getQueue(): List - - @Query("DELETE FROM queue_table") - fun clear() -} diff --git a/app/src/main/java/org/oxycblt/auxio/database/QueueItem.kt b/app/src/main/java/org/oxycblt/auxio/database/QueueItem.kt index 1be9063a6..790028c0e 100644 --- a/app/src/main/java/org/oxycblt/auxio/database/QueueItem.kt +++ b/app/src/main/java/org/oxycblt/auxio/database/QueueItem.kt @@ -1,23 +1,15 @@ package org.oxycblt.auxio.database -import androidx.room.ColumnInfo -import androidx.room.Entity -import androidx.room.PrimaryKey - -/** - * A simplified database entity that represents a given song in the queue. - */ -@Entity(tableName = "queue_table") data class QueueItem( - @PrimaryKey(autoGenerate = true) var id: Long = 0L, - - @ColumnInfo(name = "song_id") val songId: Long = Long.MIN_VALUE, - - @ColumnInfo(name = "album_id") val albumId: Long = Long.MIN_VALUE, - - @ColumnInfo(name = "is_user_queue") val isUserQueue: Boolean = false -) +) { + companion object { + const val COLUMN_ID = "id" + const val COLUMN_SONG_ID = "song_id" + const val COLUMN_ALBUM_ID = "album_id" + const val COLUMN_IS_USER_QUEUE = "is_user_queue" + } +} diff --git a/app/src/main/java/org/oxycblt/auxio/music/Models.kt b/app/src/main/java/org/oxycblt/auxio/music/Models.kt index c3292bf6c..0926bbb95 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/Models.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/Models.kt @@ -121,4 +121,5 @@ data class Genre( data class Header( override val id: Long = -1, override var name: String = "", + val isAction: Boolean = false ) : BaseModel() diff --git a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackFragment.kt b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackFragment.kt index fb0237fcf..77636f052 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackFragment.kt @@ -126,8 +126,6 @@ class PlaybackFragment : Fragment(), SeekBar.OnSeekBarChangeListener { } else { binding.playbackShuffle.imageTintList = controlColor } - - Log.d(this::class.simpleName, "Shuffle swap") } playbackModel.loopMode.observe(viewLifecycleOwner) { diff --git a/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueAdapter.kt b/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueAdapter.kt index 707ffca6f..005b29c32 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueAdapter.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueAdapter.kt @@ -44,7 +44,7 @@ class QueueAdapter( override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { return when (viewType) { HeaderViewHolder.ITEM_TYPE -> HeaderViewHolder.from(parent.context) - QUEUE_ITEM_TYPE -> ViewHolder( + QUEUE_ITEM_TYPE -> QueueSongViewHolder( ItemQueueSongBinding.inflate(LayoutInflater.from(parent.context)) ) else -> error("Someone messed with the ViewHolder item types.") @@ -53,7 +53,7 @@ class QueueAdapter( override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { when (val item = data[position]) { - is Song -> (holder as ViewHolder).bind(item) + is Song -> (holder as QueueSongViewHolder).bind(item) is Header -> (holder as HeaderViewHolder).bind(item) else -> { @@ -99,7 +99,7 @@ class QueueAdapter( } // Generic ViewHolder for a queue item - inner class ViewHolder( + inner class QueueSongViewHolder( private val binding: ItemQueueSongBinding, ) : BaseViewHolder(binding, null, null) { diff --git a/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueDragCallback.kt b/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueDragCallback.kt index cb1838ae5..5383e4cbb 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueDragCallback.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueDragCallback.kt @@ -17,7 +17,7 @@ class QueueDragCallback(private val playbackModel: PlaybackViewModel) : ItemTouc viewHolder: RecyclerView.ViewHolder ): Int { // Only allow dragging/swiping with the queue item ViewHolder, not the headers. - return if (viewHolder is QueueAdapter.ViewHolder) { + return if (viewHolder is QueueAdapter.QueueSongViewHolder) { makeFlag( ItemTouchHelper.ACTION_STATE_DRAG, ItemTouchHelper.UP or ItemTouchHelper.DOWN ) or makeFlag(ItemTouchHelper.ACTION_STATE_SWIPE, ItemTouchHelper.START) diff --git a/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueFragment.kt b/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueFragment.kt index 631d805ad..571b44622 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueFragment.kt @@ -75,7 +75,12 @@ class QueueFragment : Fragment() { val queue = mutableListOf() if (playbackModel.userQueue.value!!.isNotEmpty()) { - queue.add(Header(name = getString(R.string.label_next_user_queue))) + queue.add( + Header( + name = getString(R.string.label_next_user_queue), + isAction = true + ) + ) queue.addAll(playbackModel.userQueue.value!!) } @@ -88,7 +93,8 @@ class QueueFragment : Fragment() { getString(R.string.label_all_songs) else playbackModel.parent.value!!.name - ) + ), + isAction = false ) ) queue.addAll(playbackModel.nextItemsInQueue.value!!) diff --git a/app/src/main/java/org/oxycblt/auxio/playback/state/PlaybackStateManager.kt b/app/src/main/java/org/oxycblt/auxio/playback/state/PlaybackStateManager.kt index 0f9d33e14..d207291d6 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/state/PlaybackStateManager.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/state/PlaybackStateManager.kt @@ -4,8 +4,8 @@ import android.content.Context import android.util.Log import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext -import org.oxycblt.auxio.database.AuxioDatabase import org.oxycblt.auxio.database.PlaybackState +import org.oxycblt.auxio.database.PlaybackStateDatabase import org.oxycblt.auxio.database.QueueItem import org.oxycblt.auxio.music.Album import org.oxycblt.auxio.music.Artist @@ -436,12 +436,9 @@ class PlaybackStateManager private constructor() { val playbackState = packToPlaybackState() val queueItems = packQueue() - val database = AuxioDatabase.getInstance(context) - database.playbackStateDAO.clear() - database.queueDAO.clear() - - database.playbackStateDAO.insert(playbackState) - database.queueDAO.insertAll(queueItems) + val database = PlaybackStateDatabase.getInstance(context) + database.writeState(playbackState) + database.writeQueue(queueItems) } val time = System.currentTimeMillis() - start @@ -455,42 +452,24 @@ class PlaybackStateManager private constructor() { val start = System.currentTimeMillis() val state: PlaybackState? - val queueItems: List - val userQueueItems: List - withContext(Dispatchers.IO) { - val database = AuxioDatabase.getInstance(context) + val queueItems = withContext(Dispatchers.IO) { + val database = PlaybackStateDatabase.getInstance(context) - state = database.playbackStateDAO.getRecent() - queueItems = database.queueDAO.getQueue() - userQueueItems = database.queueDAO.getUserQueue() - - database.playbackStateDAO.clear() - database.queueDAO.clear() + state = database.readState() + database.readQueue() } val loadTime = System.currentTimeMillis() - start Log.d(this::class.simpleName, "Load finished in ${loadTime}ms") - if (state == null) { - Log.d(this::class.simpleName, "Nothing here. Not restoring.") + state?.let { + Log.d(this::class.simpleName, "Valid playback state $it") + Log.d(this::class.simpleName, "Valid queue size ${queueItems.size}") - mIsRestored = true - - return - } - - Log.d(this::class.simpleName, "Old state found, $state") - - unpackFromPlaybackState(state) - - Log.d(this::class.simpleName, "Found queue of size ${queueItems.size}") - - unpackQueues(queueItems, userQueueItems) - - mSong?.let { - mIndex = mQueue.indexOf(mSong) + unpackFromPlaybackState(it) + unpackQueue(queueItems) } val time = System.currentTimeMillis() - start @@ -510,8 +489,10 @@ class PlaybackStateManager private constructor() { songId = songId, position = mPosition, parentId = parentId, + index = mIndex, mode = intMode, isShuffling = mIsShuffling, + shuffleSeed = mShuffleSeed, loopMode = intLoopMode, inUserQueue = mIsInUserQueue ) @@ -520,12 +501,16 @@ class PlaybackStateManager private constructor() { private fun packQueue(): List { val unified = mutableListOf() + var queueItemId = 0L + mUserQueue.forEach { - unified.add(QueueItem(songId = it.id, albumId = it.albumId, isUserQueue = true)) + unified.add(QueueItem(queueItemId, it.id, it.albumId, true)) + queueItemId++ } mQueue.forEach { - unified.add(QueueItem(songId = it.id, albumId = it.albumId, isUserQueue = false)) + unified.add(QueueItem(queueItemId, it.id, it.albumId, false)) + queueItemId++ } return unified @@ -541,7 +526,9 @@ class PlaybackStateManager private constructor() { mMode = PlaybackMode.fromConstant(playbackState.mode) ?: PlaybackMode.ALL_SONGS mLoopMode = LoopMode.fromConstant(playbackState.loopMode) ?: LoopMode.NONE mIsShuffling = playbackState.isShuffling + mShuffleSeed = playbackState.shuffleSeed mIsInUserQueue = playbackState.inUserQueue + mIndex = playbackState.index callbacks.forEach { it.onSeekConfirm(mPosition) @@ -550,21 +537,26 @@ class PlaybackStateManager private constructor() { } } - private fun unpackQueues(queueItems: List, userQueueItems: List) { + private fun unpackQueue(queueItems: List) { val musicStore = MusicStore.getInstance() queueItems.forEach { item -> // Traverse albums and then album songs instead of just the songs, as its faster. musicStore.albums.find { it.id == item.albumId } ?.songs?.find { it.id == item.songId }?.let { - mQueue.add(it) + if (item.isUserQueue) { + mUserQueue.add(it) + } else { + mQueue.add(it) + } } } - userQueueItems.forEach { item -> - musicStore.albums.find { it.id == item.albumId } - ?.songs?.find { it.id == item.songId }?.let { - mUserQueue.add(it) + // Get a more accurate index [At least if were not in the user queue] + if (!mIsInUserQueue) { + mSong?.let { + val index = mQueue.indexOf(it) + mIndex = if (index != -1) index else mIndex } } diff --git a/app/src/main/java/org/oxycblt/auxio/recycler/viewholders/ModelHolders.kt b/app/src/main/java/org/oxycblt/auxio/recycler/viewholders/ModelHolders.kt index 4bf7c8905..8a316d45a 100644 --- a/app/src/main/java/org/oxycblt/auxio/recycler/viewholders/ModelHolders.kt +++ b/app/src/main/java/org/oxycblt/auxio/recycler/viewholders/ModelHolders.kt @@ -129,7 +129,7 @@ class SongViewHolder private constructor( } } -class HeaderViewHolder( +open class HeaderViewHolder( private val binding: ItemHeaderBinding ) : BaseViewHolder
(binding, null, null) { diff --git a/app/src/main/res/drawable/ic_clear.xml b/app/src/main/res/drawable/ic_clear.xml new file mode 100644 index 000000000..3b36be6a0 --- /dev/null +++ b/app/src/main/res/drawable/ic_clear.xml @@ -0,0 +1,11 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ui_background_ripple.xml b/app/src/main/res/drawable/ui_background_ripple.xml new file mode 100644 index 000000000..a7c0a21df --- /dev/null +++ b/app/src/main/res/drawable/ui_background_ripple.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ui_header_dividers.xml b/app/src/main/res/drawable/ui_header_dividers.xml index 55b7c783f..8a036f39b 100644 --- a/app/src/main/res/drawable/ui_header_dividers.xml +++ b/app/src/main/res/drawable/ui_header_dividers.xml @@ -5,7 +5,8 @@ https://stackoverflow.com/a/61157571/14143986 + android:right="-2dp" + android:top="-2dp"> - - + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_header.xml b/app/src/main/res/layout/item_header.xml index a40341f26..85698c013 100644 --- a/app/src/main/res/layout/item_header.xml +++ b/app/src/main/res/layout/item_header.xml @@ -10,23 +10,17 @@ type="org.oxycblt.auxio.music.Header" /> - - - - - + android:layout_height="wrap_content" + android:textColor="?android:attr/textColorPrimary" + 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:textSize="19sp" + android:text="@{header.name}" + tools:text="Songs" /> \ No newline at end of file