vfsfitvnm 3 rokov pred
rodič
commit
c78b3105b1

+ 310 - 0
app/schemas/it.vfsfitvnm.vimusic.DatabaseInitializer/4.json

@@ -0,0 +1,310 @@
+{
+  "formatVersion": 1,
+  "database": {
+    "version": 4,
+    "identityHash": "d5720e465abdf99b583c183298f18340",
+    "entities": [
+      {
+        "tableName": "Song",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `title` TEXT NOT NULL, `albumInfoId` INTEGER, `durationText` TEXT NOT NULL, `thumbnailUrl` TEXT, `lyrics` TEXT, `likedAt` INTEGER, `totalPlayTimeMs` INTEGER NOT NULL, PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "title",
+            "columnName": "title",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "albumInfoId",
+            "columnName": "albumInfoId",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "durationText",
+            "columnName": "durationText",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "thumbnailUrl",
+            "columnName": "thumbnailUrl",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "lyrics",
+            "columnName": "lyrics",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "likedAt",
+            "columnName": "likedAt",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "totalPlayTimeMs",
+            "columnName": "totalPlayTimeMs",
+            "affinity": "INTEGER",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "autoGenerate": false,
+          "columnNames": [
+            "id"
+          ]
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "SongInPlaylist",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`songId` TEXT NOT NULL, `playlistId` INTEGER NOT NULL, `position` INTEGER NOT NULL, PRIMARY KEY(`songId`, `playlistId`), FOREIGN KEY(`songId`) REFERENCES `Song`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`playlistId`) REFERENCES `Playlist`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
+        "fields": [
+          {
+            "fieldPath": "songId",
+            "columnName": "songId",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "playlistId",
+            "columnName": "playlistId",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "position",
+            "columnName": "position",
+            "affinity": "INTEGER",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "autoGenerate": false,
+          "columnNames": [
+            "songId",
+            "playlistId"
+          ]
+        },
+        "indices": [
+          {
+            "name": "index_SongInPlaylist_songId",
+            "unique": false,
+            "columnNames": [
+              "songId"
+            ],
+            "orders": [],
+            "createSql": "CREATE INDEX IF NOT EXISTS `index_SongInPlaylist_songId` ON `${TABLE_NAME}` (`songId`)"
+          },
+          {
+            "name": "index_SongInPlaylist_playlistId",
+            "unique": false,
+            "columnNames": [
+              "playlistId"
+            ],
+            "orders": [],
+            "createSql": "CREATE INDEX IF NOT EXISTS `index_SongInPlaylist_playlistId` ON `${TABLE_NAME}` (`playlistId`)"
+          }
+        ],
+        "foreignKeys": [
+          {
+            "table": "Song",
+            "onDelete": "CASCADE",
+            "onUpdate": "NO ACTION",
+            "columns": [
+              "songId"
+            ],
+            "referencedColumns": [
+              "id"
+            ]
+          },
+          {
+            "table": "Playlist",
+            "onDelete": "CASCADE",
+            "onUpdate": "NO ACTION",
+            "columns": [
+              "playlistId"
+            ],
+            "referencedColumns": [
+              "id"
+            ]
+          }
+        ]
+      },
+      {
+        "tableName": "Playlist",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL)",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "autoGenerate": true,
+          "columnNames": [
+            "id"
+          ]
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Info",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `browseId` TEXT, `text` TEXT NOT NULL)",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "browseId",
+            "columnName": "browseId",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "text",
+            "columnName": "text",
+            "affinity": "TEXT",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "autoGenerate": true,
+          "columnNames": [
+            "id"
+          ]
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "SongWithAuthors",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`songId` TEXT NOT NULL, `authorInfoId` INTEGER NOT NULL, PRIMARY KEY(`songId`, `authorInfoId`), FOREIGN KEY(`songId`) REFERENCES `Song`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`authorInfoId`) REFERENCES `Info`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
+        "fields": [
+          {
+            "fieldPath": "songId",
+            "columnName": "songId",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "authorInfoId",
+            "columnName": "authorInfoId",
+            "affinity": "INTEGER",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "autoGenerate": false,
+          "columnNames": [
+            "songId",
+            "authorInfoId"
+          ]
+        },
+        "indices": [
+          {
+            "name": "index_SongWithAuthors_authorInfoId",
+            "unique": false,
+            "columnNames": [
+              "authorInfoId"
+            ],
+            "orders": [],
+            "createSql": "CREATE INDEX IF NOT EXISTS `index_SongWithAuthors_authorInfoId` ON `${TABLE_NAME}` (`authorInfoId`)"
+          }
+        ],
+        "foreignKeys": [
+          {
+            "table": "Song",
+            "onDelete": "CASCADE",
+            "onUpdate": "NO ACTION",
+            "columns": [
+              "songId"
+            ],
+            "referencedColumns": [
+              "id"
+            ]
+          },
+          {
+            "table": "Info",
+            "onDelete": "CASCADE",
+            "onUpdate": "NO ACTION",
+            "columns": [
+              "authorInfoId"
+            ],
+            "referencedColumns": [
+              "id"
+            ]
+          }
+        ]
+      },
+      {
+        "tableName": "SearchQuery",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `query` TEXT NOT NULL)",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "query",
+            "columnName": "query",
+            "affinity": "TEXT",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "autoGenerate": true,
+          "columnNames": [
+            "id"
+          ]
+        },
+        "indices": [
+          {
+            "name": "index_SearchQuery_query",
+            "unique": true,
+            "columnNames": [
+              "query"
+            ],
+            "orders": [],
+            "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_SearchQuery_query` ON `${TABLE_NAME}` (`query`)"
+          }
+        ],
+        "foreignKeys": []
+      }
+    ],
+    "views": [
+      {
+        "viewName": "SortedSongInPlaylist",
+        "createSql": "CREATE VIEW `${VIEW_NAME}` AS SELECT * FROM SongInPlaylist ORDER BY position"
+      }
+    ],
+    "setupQueries": [
+      "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
+      "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'd5720e465abdf99b583c183298f18340')"
+    ]
+  }
+}

+ 6 - 45
app/src/main/kotlin/it/vfsfitvnm/vimusic/Database.kt

@@ -2,9 +2,8 @@ package it.vfsfitvnm.vimusic
 
 
 import android.content.Context
 import android.content.Context
 import android.database.Cursor
 import android.database.Cursor
-import android.os.Parcel
-import androidx.media3.common.MediaItem
 import androidx.room.*
 import androidx.room.*
+import androidx.room.migration.AutoMigrationSpec
 import it.vfsfitvnm.vimusic.models.*
 import it.vfsfitvnm.vimusic.models.*
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.Flow
 
 
@@ -84,15 +83,6 @@ interface Database {
     @Insert(onConflict = OnConflictStrategy.ABORT)
     @Insert(onConflict = OnConflictStrategy.ABORT)
     fun insert(song: Song): Long
     fun insert(song: Song): Long
 
 
-    @Insert(onConflict = OnConflictStrategy.ABORT)
-    fun insertQueuedMediaItems(queuedMediaItems: List<QueuedMediaItem>)
-
-    @Query("SELECT * FROM QueuedMediaItem")
-    fun queuedMediaItems(): List<QueuedMediaItem>
-
-    @Query("DELETE FROM QueuedMediaItem")
-    fun clearQueuedMediaItems()
-
     @Update
     @Update
     fun update(song: Song)
     fun update(song: Song)
 
 
@@ -130,19 +120,18 @@ interface Database {
         Info::class,
         Info::class,
         SongWithAuthors::class,
         SongWithAuthors::class,
         SearchQuery::class,
         SearchQuery::class,
-        QueuedMediaItem::class
     ],
     ],
     views = [
     views = [
         SortedSongInPlaylist::class
         SortedSongInPlaylist::class
     ],
     ],
-    version = 3,
+    version = 4,
     exportSchema = true,
     exportSchema = true,
     autoMigrations = [
     autoMigrations = [
         AutoMigration(from = 1, to = 2),
         AutoMigration(from = 1, to = 2),
         AutoMigration(from = 2, to = 3),
         AutoMigration(from = 2, to = 3),
-    ]
+        AutoMigration(from = 3, to = 4, spec = DatabaseInitializer.From3To4Migration::class),
+    ],
 )
 )
-@TypeConverters(Converters::class)
 abstract class DatabaseInitializer protected constructor() : RoomDatabase() {
 abstract class DatabaseInitializer protected constructor() : RoomDatabase() {
     abstract val database: Database
     abstract val database: Database
 
 
@@ -158,37 +147,9 @@ abstract class DatabaseInitializer protected constructor() : RoomDatabase() {
             }
             }
         }
         }
     }
     }
-}
 
 
-@TypeConverters
-object Converters {
-    // TODO: temporary
-    @TypeConverter
-    fun mediaItemFromByteArray(value: ByteArray?): MediaItem? {
-        return value?.let { byteArray ->
-            val parcel = Parcel.obtain()
-            parcel.unmarshall(byteArray, 0, byteArray.size)
-            parcel.setDataPosition(0)
-
-            val pb = parcel.readBundle(MediaItem::class.java.classLoader)
-            parcel.recycle()
-            pb?.let {
-                MediaItem.CREATOR.fromBundle(pb)
-            }
-        }
-    }
-
-    // TODO: temporary
-    @TypeConverter
-    fun mediaItemToByteArray(mediaItem: MediaItem?): ByteArray? {
-        return mediaItem?.toBundle()?.let { persistableBundle ->
-            val parcel = Parcel.obtain()
-            parcel.writeBundle(persistableBundle)
-            parcel.marshall().also {
-                parcel.recycle()
-            }
-        }
-    }
+    @DeleteTable.Entries(DeleteTable(tableName = "QueuedMediaItem"))
+    class From3To4Migration : AutoMigrationSpec
 }
 }
 
 
 val Database.internal: RoomDatabase
 val Database.internal: RoomDatabase

+ 0 - 13
app/src/main/kotlin/it/vfsfitvnm/vimusic/models/QueuedMediaItem.kt

@@ -1,13 +0,0 @@
-package it.vfsfitvnm.vimusic.models
-
-import androidx.media3.common.MediaItem
-import androidx.room.ColumnInfo
-import androidx.room.Entity
-import androidx.room.PrimaryKey
-
-@Entity
-class QueuedMediaItem(
-    @PrimaryKey(autoGenerate = true) val id: Long = 0,
-    @ColumnInfo(typeAffinity = ColumnInfo.BLOB) val mediaItem: MediaItem,
-    var position: Long?
-)

+ 0 - 47
app/src/main/kotlin/it/vfsfitvnm/vimusic/services/PlayerService.kt

@@ -41,7 +41,6 @@ import com.google.common.util.concurrent.Futures
 import com.google.common.util.concurrent.ListenableFuture
 import com.google.common.util.concurrent.ListenableFuture
 import it.vfsfitvnm.vimusic.*
 import it.vfsfitvnm.vimusic.*
 import it.vfsfitvnm.vimusic.R
 import it.vfsfitvnm.vimusic.R
-import it.vfsfitvnm.vimusic.models.QueuedMediaItem
 import it.vfsfitvnm.vimusic.utils.*
 import it.vfsfitvnm.vimusic.utils.*
 import it.vfsfitvnm.youtubemusic.Outcome
 import it.vfsfitvnm.youtubemusic.Outcome
 import kotlinx.coroutines.*
 import kotlinx.coroutines.*
@@ -123,55 +122,9 @@ class PlayerService : MediaSessionService(), MediaSession.MediaItemFiller,
             .build()
             .build()
 
 
         player.addListener(this)
         player.addListener(this)
-
-        if (preferences.persistentQueue) {
-            coroutineScope.launch(Dispatchers.IO) {
-                val queuedMediaItems = Database.queuedMediaItems()
-                Database.clearQueuedMediaItems()
-
-                if (queuedMediaItems.isEmpty()) return@launch
-
-                val index = queuedMediaItems.indexOfFirst { it.position != null }.coerceAtLeast(0)
-
-                withContext(Dispatchers.Main) {
-                    player.setMediaItems(
-                        queuedMediaItems
-                            .map(QueuedMediaItem::mediaItem)
-                            .map { mediaItem ->
-                                mediaItem.buildUpon()
-                                    .setUri(mediaItem.mediaId)
-                                    .setCustomCacheKey(mediaItem.mediaId)
-                                    .build()
-                            },
-                        true
-                    )
-                    player.seekTo(index, queuedMediaItems[index].position ?: 0)
-                    player.playWhenReady = false
-                    player.prepare()
-                }
-            }
-        }
     }
     }
 
 
     override fun onDestroy() {
     override fun onDestroy() {
-        if (preferences.persistentQueue) {
-            val mediaItems = player.currentTimeline.mediaItems
-            val mediaItemIndex = player.currentMediaItemIndex
-            val mediaItemPosition = player.currentPosition
-
-            Database.internal.queryExecutor.execute {
-                Database.clearQueuedMediaItems()
-                Database.insertQueuedMediaItems(
-                    mediaItems.mapIndexed { index, mediaItem ->
-                        QueuedMediaItem(
-                            mediaItem = mediaItem,
-                            position = if (index == mediaItemIndex) mediaItemPosition else null
-                        )
-                    }
-                )
-            }
-        }
-
         player.release()
         player.release()
         mediaSession.release()
         mediaSession.release()
         cache.release()
         cache.release()

+ 2 - 2
app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/settings/NotificationSettingsScreen.kt

@@ -78,8 +78,8 @@ fun NotificationSettingsScreen() {
                 }
                 }
 
 
                 SwitchSettingEntry(
                 SwitchSettingEntry(
-                    title = "[SOON] Show Like button",
-                    text = "Mark a song as favorite",
+                    title = "Show Like button",
+                    text = "Coming soon!",
                     isChecked = preferences.displayLikeButtonInNotification,
                     isChecked = preferences.displayLikeButtonInNotification,
                     onCheckedChange = {
                     onCheckedChange = {
                         preferences.displayLikeButtonInNotification = it
                         preferences.displayLikeButtonInNotification = it