From 837ff62d8f15ee59f1a13ac26024b212f88dd3e0 Mon Sep 17 00:00:00 2001 From: Nguyen Duc Tuan Minh Date: Sat, 30 Nov 2024 18:32:55 +0700 Subject: [PATCH] Improved Local Playlist Screen --- .../12.json | 971 ++++++++++++++++++ .../12.json | 971 ++++++++++++++++++ .../simpmusic/data/db/AutoMigration7_8.kt | 11 +- .../maxrave/simpmusic/data/db/DatabaseDao.kt | 8 +- .../simpmusic/data/db/LocalDataSource.kt | 5 - .../simpmusic/data/db/MusicDatabase.kt | 3 +- .../data/db/entities/LocalPlaylistEntity.kt | 2 - .../data/manager/LocalPlaylistManager.kt | 53 +- .../data/repository/MainRepository.kt | 10 +- .../maxrave/simpmusic/di/DatabaseModule.kt | 2 +- .../simpmusic/ui/fragment/SearchFragment.kt | 2 +- .../ui/fragment/home/HomeFragment.kt | 2 +- .../ui/fragment/library/DownloadedFragment.kt | 6 +- .../ui/fragment/library/FavoriteFragment.kt | 2 +- .../ui/fragment/library/LibraryFragment.kt | 2 +- .../ui/fragment/library/MostPlayedFragment.kt | 2 +- .../ui/fragment/other/AlbumFragment.kt | 2 +- .../ui/fragment/other/ArtistFragment.kt | 2 +- .../fragment/other/LocalPlaylistFragment.kt | 22 +- .../ui/fragment/other/PlaylistFragment.kt | 9 +- .../ui/fragment/player/FullscreenFragment.kt | 2 +- .../ui/fragment/player/NowPlayingFragment.kt | 2 +- .../com/maxrave/simpmusic/utils/Resource.kt | 38 + .../viewModel/LocalPlaylistViewModel.kt | 41 +- .../maxrave/kotlinytmusicscraper/YouTube.kt | 4 +- .../extension/StringExt.kt | 4 + .../models/MusicPlaylistShelfRenderer.kt | 2 +- 27 files changed, 2093 insertions(+), 87 deletions(-) create mode 100644 app/schemas/com.maxrave.simpmusic.data.db.AutoMigration11to12Spec/12.json create mode 100644 app/schemas/com.maxrave.simpmusic.data.db.MusicDatabase/12.json diff --git a/app/schemas/com.maxrave.simpmusic.data.db.AutoMigration11to12Spec/12.json b/app/schemas/com.maxrave.simpmusic.data.db.AutoMigration11to12Spec/12.json new file mode 100644 index 00000000..ab8154e8 --- /dev/null +++ b/app/schemas/com.maxrave.simpmusic.data.db.AutoMigration11to12Spec/12.json @@ -0,0 +1,971 @@ +{ + "formatVersion": 1, + "database": { + "version": 12, + "identityHash": "832b1debb85059f9384050f738680b35", + "entities": [ + { + "tableName": "new_format", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`videoId` TEXT NOT NULL, `itag` INTEGER NOT NULL, `mimeType` TEXT, `codecs` TEXT, `bitrate` INTEGER, `sampleRate` INTEGER, `contentLength` INTEGER, `loudnessDb` REAL, `lengthSeconds` INTEGER, `playbackTrackingVideostatsPlaybackUrl` TEXT, `playbackTrackingAtrUrl` TEXT, `playbackTrackingVideostatsWatchtimeUrl` TEXT, `expired_time` TEXT NOT NULL DEFAULT 0, `cpn` TEXT, PRIMARY KEY(`videoId`))", + "fields": [ + { + "fieldPath": "videoId", + "columnName": "videoId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "itag", + "columnName": "itag", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "mimeType", + "columnName": "mimeType", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "codecs", + "columnName": "codecs", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "bitrate", + "columnName": "bitrate", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "sampleRate", + "columnName": "sampleRate", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "contentLength", + "columnName": "contentLength", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "loudnessDb", + "columnName": "loudnessDb", + "affinity": "REAL", + "notNull": false + }, + { + "fieldPath": "lengthSeconds", + "columnName": "lengthSeconds", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "playbackTrackingVideostatsPlaybackUrl", + "columnName": "playbackTrackingVideostatsPlaybackUrl", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "playbackTrackingAtrUrl", + "columnName": "playbackTrackingAtrUrl", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "playbackTrackingVideostatsWatchtimeUrl", + "columnName": "playbackTrackingVideostatsWatchtimeUrl", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "expiredTime", + "columnName": "expired_time", + "affinity": "TEXT", + "notNull": true, + "defaultValue": "0" + }, + { + "fieldPath": "cpn", + "columnName": "cpn", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "videoId" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "song_info", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`videoId` TEXT NOT NULL, `author` TEXT, `authorId` TEXT, `authorThumbnail` TEXT, `description` TEXT, `subscribers` TEXT, `viewCount` INTEGER, `uploadDate` TEXT, `like` INTEGER, `dislike` INTEGER, PRIMARY KEY(`videoId`))", + "fields": [ + { + "fieldPath": "videoId", + "columnName": "videoId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "author", + "columnName": "author", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "authorId", + "columnName": "authorId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "authorThumbnail", + "columnName": "authorThumbnail", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "subscribers", + "columnName": "subscribers", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "viewCount", + "columnName": "viewCount", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "uploadDate", + "columnName": "uploadDate", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "like", + "columnName": "like", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "dislike", + "columnName": "dislike", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "videoId" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "search_history", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`query` TEXT NOT NULL, PRIMARY KEY(`query`))", + "fields": [ + { + "fieldPath": "query", + "columnName": "query", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "query" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "song", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`videoId` TEXT NOT NULL, `albumId` TEXT, `albumName` TEXT, `artistId` TEXT, `artistName` TEXT, `duration` TEXT NOT NULL, `durationSeconds` INTEGER NOT NULL, `isAvailable` INTEGER NOT NULL, `isExplicit` INTEGER NOT NULL, `likeStatus` TEXT NOT NULL, `thumbnails` TEXT, `title` TEXT NOT NULL, `videoType` TEXT NOT NULL, `category` TEXT, `resultType` TEXT, `liked` INTEGER NOT NULL, `totalPlayTime` INTEGER NOT NULL, `downloadState` INTEGER NOT NULL, `inLibrary` TEXT NOT NULL, PRIMARY KEY(`videoId`))", + "fields": [ + { + "fieldPath": "videoId", + "columnName": "videoId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "albumId", + "columnName": "albumId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "albumName", + "columnName": "albumName", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "artistId", + "columnName": "artistId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "artistName", + "columnName": "artistName", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "duration", + "columnName": "duration", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "durationSeconds", + "columnName": "durationSeconds", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isAvailable", + "columnName": "isAvailable", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isExplicit", + "columnName": "isExplicit", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "likeStatus", + "columnName": "likeStatus", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "thumbnails", + "columnName": "thumbnails", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "videoType", + "columnName": "videoType", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "category", + "columnName": "category", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "resultType", + "columnName": "resultType", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "liked", + "columnName": "liked", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "totalPlayTime", + "columnName": "totalPlayTime", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "downloadState", + "columnName": "downloadState", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "inLibrary", + "columnName": "inLibrary", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "videoId" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "artist", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`channelId` TEXT NOT NULL, `name` TEXT NOT NULL, `thumbnails` TEXT, `followed` INTEGER NOT NULL, `inLibrary` TEXT NOT NULL, PRIMARY KEY(`channelId`))", + "fields": [ + { + "fieldPath": "channelId", + "columnName": "channelId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "thumbnails", + "columnName": "thumbnails", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "followed", + "columnName": "followed", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "inLibrary", + "columnName": "inLibrary", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "channelId" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "album", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`browseId` TEXT NOT NULL, `artistId` TEXT, `artistName` TEXT, `audioPlaylistId` TEXT NOT NULL, `description` TEXT NOT NULL, `duration` TEXT, `durationSeconds` INTEGER NOT NULL, `thumbnails` TEXT, `title` TEXT NOT NULL, `trackCount` INTEGER NOT NULL, `tracks` TEXT, `type` TEXT NOT NULL, `year` TEXT, `liked` INTEGER NOT NULL, `inLibrary` TEXT NOT NULL, `downloadState` INTEGER NOT NULL, PRIMARY KEY(`browseId`))", + "fields": [ + { + "fieldPath": "browseId", + "columnName": "browseId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "artistId", + "columnName": "artistId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "artistName", + "columnName": "artistName", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "audioPlaylistId", + "columnName": "audioPlaylistId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "duration", + "columnName": "duration", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "durationSeconds", + "columnName": "durationSeconds", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "thumbnails", + "columnName": "thumbnails", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "trackCount", + "columnName": "trackCount", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "tracks", + "columnName": "tracks", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "year", + "columnName": "year", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "liked", + "columnName": "liked", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "inLibrary", + "columnName": "inLibrary", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "downloadState", + "columnName": "downloadState", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "browseId" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "playlist", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `author` TEXT, `description` TEXT NOT NULL, `duration` TEXT NOT NULL, `durationSeconds` INTEGER NOT NULL, `privacy` TEXT NOT NULL, `thumbnails` TEXT NOT NULL, `title` TEXT NOT NULL, `trackCount` INTEGER NOT NULL, `tracks` TEXT, `year` TEXT, `liked` INTEGER NOT NULL, `inLibrary` TEXT NOT NULL, `downloadState` INTEGER NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "author", + "columnName": "author", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "duration", + "columnName": "duration", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "durationSeconds", + "columnName": "durationSeconds", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "privacy", + "columnName": "privacy", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "thumbnails", + "columnName": "thumbnails", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "trackCount", + "columnName": "trackCount", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "tracks", + "columnName": "tracks", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "year", + "columnName": "year", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "liked", + "columnName": "liked", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "inLibrary", + "columnName": "inLibrary", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "downloadState", + "columnName": "downloadState", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "local_playlist", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `title` TEXT NOT NULL, `thumbnail` TEXT, `inLibrary` TEXT NOT NULL, `downloadState` INTEGER NOT NULL, `youtubePlaylistId` TEXT, `youtube_sync_state` INTEGER NOT NULL DEFAULT 0, `tracks` TEXT)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "thumbnail", + "columnName": "thumbnail", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "inLibrary", + "columnName": "inLibrary", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "downloadState", + "columnName": "downloadState", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "youtubePlaylistId", + "columnName": "youtubePlaylistId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "syncState", + "columnName": "youtube_sync_state", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + }, + { + "fieldPath": "tracks", + "columnName": "tracks", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "lyrics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`videoId` TEXT NOT NULL, `error` INTEGER NOT NULL, `lines` TEXT, `syncType` TEXT, PRIMARY KEY(`videoId`))", + "fields": [ + { + "fieldPath": "videoId", + "columnName": "videoId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "error", + "columnName": "error", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lines", + "columnName": "lines", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "syncType", + "columnName": "syncType", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "videoId" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "queue", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`queueId` INTEGER NOT NULL, `listTrack` TEXT NOT NULL, PRIMARY KEY(`queueId`))", + "fields": [ + { + "fieldPath": "queueId", + "columnName": "queueId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "listTrack", + "columnName": "listTrack", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "queueId" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "set_video_id", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`videoId` TEXT NOT NULL, `setVideoId` TEXT, `youtubePlaylistId` TEXT NOT NULL, PRIMARY KEY(`videoId`, `youtubePlaylistId`))", + "fields": [ + { + "fieldPath": "videoId", + "columnName": "videoId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "setVideoId", + "columnName": "setVideoId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "youtubePlaylistId", + "columnName": "youtubePlaylistId", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "videoId", + "youtubePlaylistId" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "pair_song_local_playlist", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `playlistId` INTEGER NOT NULL, `songId` TEXT NOT NULL, `position` INTEGER NOT NULL, `inPlaylist` TEXT NOT NULL, FOREIGN KEY(`playlistId`) REFERENCES `local_playlist`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`songId`) REFERENCES `song`(`videoId`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "playlistId", + "columnName": "playlistId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "songId", + "columnName": "songId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "position", + "columnName": "position", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "inPlaylist", + "columnName": "inPlaylist", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_pair_song_local_playlist_playlistId", + "unique": false, + "columnNames": [ + "playlistId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_pair_song_local_playlist_playlistId` ON `${TABLE_NAME}` (`playlistId`)" + }, + { + "name": "index_pair_song_local_playlist_songId", + "unique": false, + "columnNames": [ + "songId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_pair_song_local_playlist_songId` ON `${TABLE_NAME}` (`songId`)" + } + ], + "foreignKeys": [ + { + "table": "local_playlist", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "playlistId" + ], + "referencedColumns": [ + "id" + ] + }, + { + "table": "song", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "songId" + ], + "referencedColumns": [ + "videoId" + ] + } + ] + }, + { + "tableName": "GoogleAccountEntity", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`email` TEXT NOT NULL, `name` TEXT NOT NULL, `thumbnailUrl` TEXT NOT NULL, `cache` TEXT, `isUsed` INTEGER NOT NULL, PRIMARY KEY(`email`))", + "fields": [ + { + "fieldPath": "email", + "columnName": "email", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "thumbnailUrl", + "columnName": "thumbnailUrl", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "cache", + "columnName": "cache", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "isUsed", + "columnName": "isUsed", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "email" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "followed_artist_single_and_album", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`channelId` TEXT NOT NULL, `name` TEXT NOT NULL, `single` TEXT NOT NULL, `album` TEXT NOT NULL, PRIMARY KEY(`channelId`))", + "fields": [ + { + "fieldPath": "channelId", + "columnName": "channelId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "single", + "columnName": "single", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "album", + "columnName": "album", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "channelId" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "notification", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `channelId` TEXT NOT NULL, `thumbnail` TEXT, `name` TEXT NOT NULL, `single` TEXT NOT NULL, `album` TEXT NOT NULL, `time` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "channelId", + "columnName": "channelId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "thumbnail", + "columnName": "thumbnail", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "single", + "columnName": "single", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "album", + "columnName": "album", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "time", + "columnName": "time", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + } + ], + "views": [], + "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, '832b1debb85059f9384050f738680b35')" + ] + } +} \ No newline at end of file diff --git a/app/schemas/com.maxrave.simpmusic.data.db.MusicDatabase/12.json b/app/schemas/com.maxrave.simpmusic.data.db.MusicDatabase/12.json new file mode 100644 index 00000000..a3d4f963 --- /dev/null +++ b/app/schemas/com.maxrave.simpmusic.data.db.MusicDatabase/12.json @@ -0,0 +1,971 @@ +{ + "formatVersion": 1, + "database": { + "version": 12, + "identityHash": "ae5b77cec9101c89870f784eabab06d1", + "entities": [ + { + "tableName": "new_format", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`videoId` TEXT NOT NULL, `itag` INTEGER NOT NULL, `mimeType` TEXT, `codecs` TEXT, `bitrate` INTEGER, `sampleRate` INTEGER, `contentLength` INTEGER, `loudnessDb` REAL, `lengthSeconds` INTEGER, `playbackTrackingVideostatsPlaybackUrl` TEXT, `playbackTrackingAtrUrl` TEXT, `playbackTrackingVideostatsWatchtimeUrl` TEXT, `expired_time` INTEGER NOT NULL DEFAULT 0, `cpn` TEXT, PRIMARY KEY(`videoId`))", + "fields": [ + { + "fieldPath": "videoId", + "columnName": "videoId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "itag", + "columnName": "itag", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "mimeType", + "columnName": "mimeType", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "codecs", + "columnName": "codecs", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "bitrate", + "columnName": "bitrate", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "sampleRate", + "columnName": "sampleRate", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "contentLength", + "columnName": "contentLength", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "loudnessDb", + "columnName": "loudnessDb", + "affinity": "REAL", + "notNull": false + }, + { + "fieldPath": "lengthSeconds", + "columnName": "lengthSeconds", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "playbackTrackingVideostatsPlaybackUrl", + "columnName": "playbackTrackingVideostatsPlaybackUrl", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "playbackTrackingAtrUrl", + "columnName": "playbackTrackingAtrUrl", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "playbackTrackingVideostatsWatchtimeUrl", + "columnName": "playbackTrackingVideostatsWatchtimeUrl", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "expiredTime", + "columnName": "expired_time", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + }, + { + "fieldPath": "cpn", + "columnName": "cpn", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "videoId" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "song_info", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`videoId` TEXT NOT NULL, `author` TEXT, `authorId` TEXT, `authorThumbnail` TEXT, `description` TEXT, `subscribers` TEXT, `viewCount` INTEGER, `uploadDate` TEXT, `like` INTEGER, `dislike` INTEGER, PRIMARY KEY(`videoId`))", + "fields": [ + { + "fieldPath": "videoId", + "columnName": "videoId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "author", + "columnName": "author", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "authorId", + "columnName": "authorId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "authorThumbnail", + "columnName": "authorThumbnail", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "subscribers", + "columnName": "subscribers", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "viewCount", + "columnName": "viewCount", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "uploadDate", + "columnName": "uploadDate", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "like", + "columnName": "like", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "dislike", + "columnName": "dislike", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "videoId" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "search_history", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`query` TEXT NOT NULL, PRIMARY KEY(`query`))", + "fields": [ + { + "fieldPath": "query", + "columnName": "query", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "query" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "song", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`videoId` TEXT NOT NULL, `albumId` TEXT, `albumName` TEXT, `artistId` TEXT, `artistName` TEXT, `duration` TEXT NOT NULL, `durationSeconds` INTEGER NOT NULL, `isAvailable` INTEGER NOT NULL, `isExplicit` INTEGER NOT NULL, `likeStatus` TEXT NOT NULL, `thumbnails` TEXT, `title` TEXT NOT NULL, `videoType` TEXT NOT NULL, `category` TEXT, `resultType` TEXT, `liked` INTEGER NOT NULL, `totalPlayTime` INTEGER NOT NULL, `downloadState` INTEGER NOT NULL, `inLibrary` INTEGER NOT NULL, PRIMARY KEY(`videoId`))", + "fields": [ + { + "fieldPath": "videoId", + "columnName": "videoId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "albumId", + "columnName": "albumId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "albumName", + "columnName": "albumName", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "artistId", + "columnName": "artistId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "artistName", + "columnName": "artistName", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "duration", + "columnName": "duration", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "durationSeconds", + "columnName": "durationSeconds", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isAvailable", + "columnName": "isAvailable", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isExplicit", + "columnName": "isExplicit", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "likeStatus", + "columnName": "likeStatus", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "thumbnails", + "columnName": "thumbnails", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "videoType", + "columnName": "videoType", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "category", + "columnName": "category", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "resultType", + "columnName": "resultType", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "liked", + "columnName": "liked", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "totalPlayTime", + "columnName": "totalPlayTime", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "downloadState", + "columnName": "downloadState", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "inLibrary", + "columnName": "inLibrary", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "videoId" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "artist", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`channelId` TEXT NOT NULL, `name` TEXT NOT NULL, `thumbnails` TEXT, `followed` INTEGER NOT NULL, `inLibrary` INTEGER NOT NULL, PRIMARY KEY(`channelId`))", + "fields": [ + { + "fieldPath": "channelId", + "columnName": "channelId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "thumbnails", + "columnName": "thumbnails", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "followed", + "columnName": "followed", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "inLibrary", + "columnName": "inLibrary", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "channelId" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "album", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`browseId` TEXT NOT NULL, `artistId` TEXT, `artistName` TEXT, `audioPlaylistId` TEXT NOT NULL, `description` TEXT NOT NULL, `duration` TEXT, `durationSeconds` INTEGER NOT NULL, `thumbnails` TEXT, `title` TEXT NOT NULL, `trackCount` INTEGER NOT NULL, `tracks` TEXT, `type` TEXT NOT NULL, `year` TEXT, `liked` INTEGER NOT NULL, `inLibrary` INTEGER NOT NULL, `downloadState` INTEGER NOT NULL, PRIMARY KEY(`browseId`))", + "fields": [ + { + "fieldPath": "browseId", + "columnName": "browseId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "artistId", + "columnName": "artistId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "artistName", + "columnName": "artistName", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "audioPlaylistId", + "columnName": "audioPlaylistId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "duration", + "columnName": "duration", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "durationSeconds", + "columnName": "durationSeconds", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "thumbnails", + "columnName": "thumbnails", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "trackCount", + "columnName": "trackCount", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "tracks", + "columnName": "tracks", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "year", + "columnName": "year", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "liked", + "columnName": "liked", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "inLibrary", + "columnName": "inLibrary", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "downloadState", + "columnName": "downloadState", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "browseId" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "playlist", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `author` TEXT, `description` TEXT NOT NULL, `duration` TEXT NOT NULL, `durationSeconds` INTEGER NOT NULL, `privacy` TEXT NOT NULL, `thumbnails` TEXT NOT NULL, `title` TEXT NOT NULL, `trackCount` INTEGER NOT NULL, `tracks` TEXT, `year` TEXT, `liked` INTEGER NOT NULL, `inLibrary` INTEGER NOT NULL, `downloadState` INTEGER NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "author", + "columnName": "author", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "duration", + "columnName": "duration", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "durationSeconds", + "columnName": "durationSeconds", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "privacy", + "columnName": "privacy", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "thumbnails", + "columnName": "thumbnails", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "trackCount", + "columnName": "trackCount", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "tracks", + "columnName": "tracks", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "year", + "columnName": "year", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "liked", + "columnName": "liked", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "inLibrary", + "columnName": "inLibrary", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "downloadState", + "columnName": "downloadState", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "local_playlist", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `title` TEXT NOT NULL, `thumbnail` TEXT, `inLibrary` INTEGER NOT NULL, `downloadState` INTEGER NOT NULL, `youtubePlaylistId` TEXT, `youtube_sync_state` INTEGER NOT NULL DEFAULT 0, `tracks` TEXT)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "thumbnail", + "columnName": "thumbnail", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "inLibrary", + "columnName": "inLibrary", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "downloadState", + "columnName": "downloadState", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "youtubePlaylistId", + "columnName": "youtubePlaylistId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "syncState", + "columnName": "youtube_sync_state", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + }, + { + "fieldPath": "tracks", + "columnName": "tracks", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "lyrics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`videoId` TEXT NOT NULL, `error` INTEGER NOT NULL, `lines` TEXT, `syncType` TEXT, PRIMARY KEY(`videoId`))", + "fields": [ + { + "fieldPath": "videoId", + "columnName": "videoId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "error", + "columnName": "error", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lines", + "columnName": "lines", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "syncType", + "columnName": "syncType", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "videoId" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "queue", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`queueId` INTEGER NOT NULL, `listTrack` TEXT NOT NULL, PRIMARY KEY(`queueId`))", + "fields": [ + { + "fieldPath": "queueId", + "columnName": "queueId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "listTrack", + "columnName": "listTrack", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "queueId" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "set_video_id", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`videoId` TEXT NOT NULL, `setVideoId` TEXT, `youtubePlaylistId` TEXT NOT NULL, PRIMARY KEY(`videoId`, `youtubePlaylistId`))", + "fields": [ + { + "fieldPath": "videoId", + "columnName": "videoId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "setVideoId", + "columnName": "setVideoId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "youtubePlaylistId", + "columnName": "youtubePlaylistId", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "videoId", + "youtubePlaylistId" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "pair_song_local_playlist", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `playlistId` INTEGER NOT NULL, `songId` TEXT NOT NULL, `position` INTEGER NOT NULL, `inPlaylist` INTEGER NOT NULL, FOREIGN KEY(`playlistId`) REFERENCES `local_playlist`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`songId`) REFERENCES `song`(`videoId`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "playlistId", + "columnName": "playlistId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "songId", + "columnName": "songId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "position", + "columnName": "position", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "inPlaylist", + "columnName": "inPlaylist", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_pair_song_local_playlist_playlistId", + "unique": false, + "columnNames": [ + "playlistId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_pair_song_local_playlist_playlistId` ON `${TABLE_NAME}` (`playlistId`)" + }, + { + "name": "index_pair_song_local_playlist_songId", + "unique": false, + "columnNames": [ + "songId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_pair_song_local_playlist_songId` ON `${TABLE_NAME}` (`songId`)" + } + ], + "foreignKeys": [ + { + "table": "local_playlist", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "playlistId" + ], + "referencedColumns": [ + "id" + ] + }, + { + "table": "song", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "songId" + ], + "referencedColumns": [ + "videoId" + ] + } + ] + }, + { + "tableName": "GoogleAccountEntity", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`email` TEXT NOT NULL, `name` TEXT NOT NULL, `thumbnailUrl` TEXT NOT NULL, `cache` TEXT, `isUsed` INTEGER NOT NULL, PRIMARY KEY(`email`))", + "fields": [ + { + "fieldPath": "email", + "columnName": "email", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "thumbnailUrl", + "columnName": "thumbnailUrl", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "cache", + "columnName": "cache", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "isUsed", + "columnName": "isUsed", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "email" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "followed_artist_single_and_album", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`channelId` TEXT NOT NULL, `name` TEXT NOT NULL, `single` TEXT NOT NULL, `album` TEXT NOT NULL, PRIMARY KEY(`channelId`))", + "fields": [ + { + "fieldPath": "channelId", + "columnName": "channelId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "single", + "columnName": "single", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "album", + "columnName": "album", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "channelId" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "notification", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `channelId` TEXT NOT NULL, `thumbnail` TEXT, `name` TEXT NOT NULL, `single` TEXT NOT NULL, `album` TEXT NOT NULL, `time` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "channelId", + "columnName": "channelId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "thumbnail", + "columnName": "thumbnail", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "single", + "columnName": "single", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "album", + "columnName": "album", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "time", + "columnName": "time", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + } + ], + "views": [], + "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, 'ae5b77cec9101c89870f784eabab06d1')" + ] + } +} \ No newline at end of file diff --git a/app/src/main/java/com/maxrave/simpmusic/data/db/AutoMigration7_8.kt b/app/src/main/java/com/maxrave/simpmusic/data/db/AutoMigration7_8.kt index 68892fd7..3e478620 100644 --- a/app/src/main/java/com/maxrave/simpmusic/data/db/AutoMigration7_8.kt +++ b/app/src/main/java/com/maxrave/simpmusic/data/db/AutoMigration7_8.kt @@ -1,5 +1,6 @@ package com.maxrave.simpmusic.data.db +import androidx.room.DeleteColumn import androidx.room.DeleteTable import androidx.room.migration.AutoMigrationSpec import androidx.sqlite.db.SupportSQLiteDatabase @@ -14,4 +15,12 @@ class AutoMigration7_8 : AutoMigrationSpec { super.onPostMigrate(db) db.execSQL("DROP TABLE IF EXISTS `format`") } -} \ No newline at end of file +} + +@DeleteColumn.Entries( + DeleteColumn( + tableName = "local_playlist", + columnName = "synced_with_youtube_playlist" + ) +) +class AutoMigration11_12: AutoMigrationSpec \ No newline at end of file diff --git a/app/src/main/java/com/maxrave/simpmusic/data/db/DatabaseDao.kt b/app/src/main/java/com/maxrave/simpmusic/data/db/DatabaseDao.kt index a2b31b32..1c52dcf8 100644 --- a/app/src/main/java/com/maxrave/simpmusic/data/db/DatabaseDao.kt +++ b/app/src/main/java/com/maxrave/simpmusic/data/db/DatabaseDao.kt @@ -348,19 +348,13 @@ interface DatabaseDao { youtubePlaylistId: String?, ) - @Query("UPDATE local_playlist SET synced_with_youtube_playlist = :synced WHERE id = :id") - suspend fun updateLocalPlaylistYouTubePlaylistSynced( - id: Long, - synced: Int, - ) - @Query("UPDATE local_playlist SET youtube_sync_state = :state WHERE id = :id") suspend fun updateLocalPlaylistYouTubePlaylistSyncState( id: Long, state: Int, ) - @Query("UPDATE local_playlist SET synced_with_youtube_playlist = 0, youtube_sync_state = 0, youtubePlaylistId = NULL WHERE id = :id") + @Query("UPDATE local_playlist SET youtube_sync_state = 0, youtubePlaylistId = NULL WHERE id = :id") suspend fun unsyncLocalPlaylist(id: Long) @Query("SELECT downloadState FROM local_playlist WHERE id = :id") diff --git a/app/src/main/java/com/maxrave/simpmusic/data/db/LocalDataSource.kt b/app/src/main/java/com/maxrave/simpmusic/data/db/LocalDataSource.kt index c52afeea..46ecf9e7 100644 --- a/app/src/main/java/com/maxrave/simpmusic/data/db/LocalDataSource.kt +++ b/app/src/main/java/com/maxrave/simpmusic/data/db/LocalDataSource.kt @@ -199,11 +199,6 @@ class LocalDataSource( ytId: String?, ) = databaseDao.updateLocalPlaylistYouTubePlaylistId(id, ytId) - suspend fun updateLocalPlaylistYouTubePlaylistSynced( - id: Long, - synced: Int, - ) = databaseDao.updateLocalPlaylistYouTubePlaylistSynced(id, synced) - suspend fun updateLocalPlaylistYouTubePlaylistSyncState( id: Long, syncState: Int, diff --git a/app/src/main/java/com/maxrave/simpmusic/data/db/MusicDatabase.kt b/app/src/main/java/com/maxrave/simpmusic/data/db/MusicDatabase.kt index 091ea85c..34be8ddb 100644 --- a/app/src/main/java/com/maxrave/simpmusic/data/db/MusicDatabase.kt +++ b/app/src/main/java/com/maxrave/simpmusic/data/db/MusicDatabase.kt @@ -27,7 +27,7 @@ import com.maxrave.simpmusic.data.db.entities.SongInfoEntity SetVideoIdEntity::class, PairSongLocalPlaylist::class, GoogleAccountEntity::class, FollowedArtistSingleAndAlbum::class, NotificationEntity::class, ], - version = 11, + version = 12, exportSchema = true, autoMigrations = [ AutoMigration(from = 2, to = 3), AutoMigration( @@ -42,6 +42,7 @@ import com.maxrave.simpmusic.data.db.entities.SongInfoEntity spec = AutoMigration7_8::class, ), AutoMigration(8, 9), AutoMigration(9, 10), + AutoMigration(from = 11, to = 12, spec = AutoMigration11_12::class) ], ) @TypeConverters(Converters::class) diff --git a/app/src/main/java/com/maxrave/simpmusic/data/db/entities/LocalPlaylistEntity.kt b/app/src/main/java/com/maxrave/simpmusic/data/db/entities/LocalPlaylistEntity.kt index 4166e67e..089bf2bb 100644 --- a/app/src/main/java/com/maxrave/simpmusic/data/db/entities/LocalPlaylistEntity.kt +++ b/app/src/main/java/com/maxrave/simpmusic/data/db/entities/LocalPlaylistEntity.kt @@ -17,8 +17,6 @@ data class LocalPlaylistEntity( val thumbnail: String? = null, val inLibrary: LocalDateTime = LocalDateTime.now(), val downloadState: Int = DownloadState.STATE_NOT_DOWNLOADED, - @ColumnInfo(name = "synced_with_youtube_playlist", defaultValue = "0") - val syncedWithYouTubePlaylist: Int = 0, val youtubePlaylistId: String? = null, @ColumnInfo(name = "youtube_sync_state", defaultValue = "0") val syncState: Int = YouTubeSyncState.NotSynced, diff --git a/app/src/main/java/com/maxrave/simpmusic/data/manager/LocalPlaylistManager.kt b/app/src/main/java/com/maxrave/simpmusic/data/manager/LocalPlaylistManager.kt index c02704c3..3e9955dc 100644 --- a/app/src/main/java/com/maxrave/simpmusic/data/manager/LocalPlaylistManager.kt +++ b/app/src/main/java/com/maxrave/simpmusic/data/manager/LocalPlaylistManager.kt @@ -6,6 +6,7 @@ import androidx.paging.Pager import androidx.paging.PagingConfig import androidx.paging.PagingData import com.maxrave.kotlinytmusicscraper.YouTube +import com.maxrave.kotlinytmusicscraper.extension.verifyYouTubePlaylistId import com.maxrave.kotlinytmusicscraper.models.MusicShelfRenderer import com.maxrave.simpmusic.R import com.maxrave.simpmusic.common.DownloadState @@ -170,7 +171,6 @@ class LocalPlaylistManager( title = playlist.title, thumbnail = playlist.thumbnails.lastOrNull()?.url, youtubePlaylistId = playlist.id, - syncedWithYouTubePlaylist = 1, tracks = playlist.tracks.toListVideoId(), downloadState = DownloadState.STATE_NOT_DOWNLOADED, syncState = Syncing, @@ -195,7 +195,7 @@ class LocalPlaylistManager( } } val ytPlaylistId = playlist.id - val id = if (ytPlaylistId.startsWith("VL")) ytPlaylistId else "VL$ytPlaylistId" + val id = ytPlaylistId.verifyYouTubePlaylistId() YouTube .customQuery(browseId = id, setLogin = true) .onSuccess { res -> @@ -255,15 +255,14 @@ class LocalPlaylistManager( parsed.forEach { setVideoId -> localDataSource.insertSetVideoId(setVideoId) } + localDataSource.updateLocalPlaylistYouTubePlaylistSyncState( + localPlaylistId, + Synced, + ) emit(LocalResource.Success(getString(R.string.synced))) }.onFailure { emit(LocalResource.Error("Can't get setVideoId")) } - localDataSource.updateLocalPlaylistYouTubePlaylistSyncState( - localPlaylistId, - Synced, - ) - emit(LocalResource.Success(getString(R.string.synced))) }.flowOn( Dispatchers.IO, ) @@ -274,15 +273,43 @@ class LocalPlaylistManager( * @param playlistId * @return Flow> */ - suspend fun syncLocalPlaylistToYouTubePlaylist(playlistId: Long) = - wrapResultResource { + fun syncLocalPlaylistToYouTubePlaylist(playlistId: Long) = + flow> { + emit(LocalResource.Loading()) val playlist = localDataSource.getLocalPlaylist(playlistId) - YouTube - .createPlaylist( + val res = YouTube.createPlaylist( playlist.title, playlist.tracks, - ).map { - it.playlistId + ) + val value = res.getOrNull() + if (res.isSuccess && value != null) { + val ytId = value.playlistId + Log.d(tag, "syncLocalPlaylistToYouTubePlaylist: $ytId") + YouTube.getYouTubePlaylistFullTracksWithSetVideoId(ytId).onSuccess { list -> + Log.d(tag, "syncLocalPlaylistToYouTubePlaylist: onSuccess song ${list.map { it.first.title }}") + Log.d(tag, "syncLocalPlaylistToYouTubePlaylist: onSuccess setVideoId ${list.map { it.second }}") + list.forEach { new -> + localDataSource.insertSong(new.first.toTrack().toSongEntity()) + localDataSource.insertSetVideoId( + SetVideoIdEntity( + videoId = new.first.id, + setVideoId = new.second, + youtubePlaylistId = ytId + ) + ) + } + if (list.isEmpty()) Log.w(tag, "syncLocalPlaylistToYouTubePlaylist: SetVideoIds Empty list") + localDataSource.updateLocalPlaylistYouTubePlaylistId(playlistId, ytId) + localDataSource.updateLocalPlaylistYouTubePlaylistSyncState(playlistId, Synced) + Log.d(tag, "syncLocalPlaylistToYouTubePlaylist: $ytId") + emit(LocalResource.Success(ytId)) + }.onFailure { + emit(LocalResource.Error(it.message ?: getString(R.string.error))) + } + } else { + val e = res.exceptionOrNull() + e?.printStackTrace() + emit(LocalResource.Error(e?.message ?: getString(R.string.error))) } } diff --git a/app/src/main/java/com/maxrave/simpmusic/data/repository/MainRepository.kt b/app/src/main/java/com/maxrave/simpmusic/data/repository/MainRepository.kt index 81362176..3e9d3f06 100644 --- a/app/src/main/java/com/maxrave/simpmusic/data/repository/MainRepository.kt +++ b/app/src/main/java/com/maxrave/simpmusic/data/repository/MainRepository.kt @@ -4,6 +4,7 @@ import android.content.Context import android.util.Log import androidx.media3.common.util.UnstableApi import com.maxrave.kotlinytmusicscraper.YouTube +import com.maxrave.kotlinytmusicscraper.extension.verifyYouTubePlaylistId import com.maxrave.kotlinytmusicscraper.models.AccountInfo import com.maxrave.kotlinytmusicscraper.models.MediaType import com.maxrave.kotlinytmusicscraper.models.MusicShelfRenderer @@ -494,13 +495,6 @@ class MainRepository( localDataSource.updateLocalPlaylistYouTubePlaylistId(id, ytId) } - suspend fun updateLocalPlaylistYouTubePlaylistSynced( - id: Long, - synced: Int, - ) = withContext(Dispatchers.IO) { - localDataSource.updateLocalPlaylistYouTubePlaylistSynced(id, synced) - } - suspend fun updateLocalPlaylistYouTubePlaylistSyncState( id: Long, syncState: Int, @@ -3058,7 +3052,7 @@ class MainRepository( ) = flow { runCatching { YouTube - .addPlaylistItem(youtubePlaylistId, videoId) + .addPlaylistItem(youtubePlaylistId.verifyYouTubePlaylistId(), videoId) .onSuccess { if (it.playlistEditResults.isNotEmpty()) { for (playlistEditResult in it.playlistEditResults) { diff --git a/app/src/main/java/com/maxrave/simpmusic/di/DatabaseModule.kt b/app/src/main/java/com/maxrave/simpmusic/di/DatabaseModule.kt index 4906ecee..13d022b8 100644 --- a/app/src/main/java/com/maxrave/simpmusic/di/DatabaseModule.kt +++ b/app/src/main/java/com/maxrave/simpmusic/di/DatabaseModule.kt @@ -9,8 +9,8 @@ import androidx.room.Room import androidx.room.RoomDatabase import androidx.room.migration.Migration import androidx.sqlite.db.SupportSQLiteDatabase -import com.google.gson.reflect.TypeToken import com.google.gson.Gson +import com.google.gson.reflect.TypeToken import com.maxrave.simpmusic.common.DB_NAME import com.maxrave.simpmusic.data.dataStore.DataStoreManager import com.maxrave.simpmusic.data.db.Converters diff --git a/app/src/main/java/com/maxrave/simpmusic/ui/fragment/SearchFragment.kt b/app/src/main/java/com/maxrave/simpmusic/ui/fragment/SearchFragment.kt index b19f32d0..b1eb911f 100644 --- a/app/src/main/java/com/maxrave/simpmusic/ui/fragment/SearchFragment.kt +++ b/app/src/main/java/com/maxrave/simpmusic/ui/fragment/SearchFragment.kt @@ -606,7 +606,7 @@ class SearchFragment : Fragment() { if (playlist.tracks != null) { tempTrack.addAll(playlist.tracks) } - if (!tempTrack.contains(track.videoId) && playlist.syncedWithYouTubePlaylist == 1 && playlist.youtubePlaylistId != null) { + if (!tempTrack.contains(track.videoId) && playlist.syncState == LocalPlaylistEntity.YouTubeSyncState.Synced && playlist.youtubePlaylistId != null) { viewModel.addToYouTubePlaylist(playlist.id, playlist.youtubePlaylistId, track.videoId) } if (!tempTrack.contains(track.videoId)) { diff --git a/app/src/main/java/com/maxrave/simpmusic/ui/fragment/home/HomeFragment.kt b/app/src/main/java/com/maxrave/simpmusic/ui/fragment/home/HomeFragment.kt index 2d8e24ad..d66d8941 100644 --- a/app/src/main/java/com/maxrave/simpmusic/ui/fragment/home/HomeFragment.kt +++ b/app/src/main/java/com/maxrave/simpmusic/ui/fragment/home/HomeFragment.kt @@ -207,7 +207,7 @@ class HomeFragment : Fragment() { // if (playlist.tracks != null) { // tempTrack.addAll(playlist.tracks) // } -// if (!tempTrack.contains(track.videoId) && playlist.syncedWithYouTubePlaylist == 1 && playlist.youtubePlaylistId != null) { +// if (!tempTrack.contains(track.videoId) && playlist.syncState == LocalPlaylistEntity.YouTubeSyncState.Synced && playlist.youtubePlaylistId != null) { // viewModel.addToYouTubePlaylist( // playlist.id, // playlist.youtubePlaylistId, diff --git a/app/src/main/java/com/maxrave/simpmusic/ui/fragment/library/DownloadedFragment.kt b/app/src/main/java/com/maxrave/simpmusic/ui/fragment/library/DownloadedFragment.kt index a89a4935..f8d0dd5f 100644 --- a/app/src/main/java/com/maxrave/simpmusic/ui/fragment/library/DownloadedFragment.kt +++ b/app/src/main/java/com/maxrave/simpmusic/ui/fragment/library/DownloadedFragment.kt @@ -18,8 +18,6 @@ import androidx.media3.exoplayer.offline.DownloadService import androidx.navigation.fragment.findNavController import androidx.recyclerview.widget.LinearLayoutManager import coil3.load -import coil3.request.crossfade -import coil3.request.placeholder import com.google.android.material.bottomsheet.BottomSheetDialog import com.maxrave.simpmusic.R import com.maxrave.simpmusic.adapter.artist.SeeArtistOfNowPlayingAdapter @@ -263,7 +261,9 @@ class DownloadedFragment : Fragment() { if (playlist.tracks != null) { tempTrack.addAll(playlist.tracks) } - if (!tempTrack.contains(song.videoId) && playlist.syncedWithYouTubePlaylist == 1 && playlist.youtubePlaylistId != null) { + if (!tempTrack.contains(song.videoId) && + playlist.syncState == LocalPlaylistEntity.YouTubeSyncState.Synced && + playlist.youtubePlaylistId != null) { viewModel.addToYouTubePlaylist(playlist.id, playlist.youtubePlaylistId, song.videoId) } if (!tempTrack.contains(song.videoId)) { diff --git a/app/src/main/java/com/maxrave/simpmusic/ui/fragment/library/FavoriteFragment.kt b/app/src/main/java/com/maxrave/simpmusic/ui/fragment/library/FavoriteFragment.kt index bb2dbb4f..7fbd6fde 100644 --- a/app/src/main/java/com/maxrave/simpmusic/ui/fragment/library/FavoriteFragment.kt +++ b/app/src/main/java/com/maxrave/simpmusic/ui/fragment/library/FavoriteFragment.kt @@ -288,7 +288,7 @@ class FavoriteFragment : Fragment() { tempTrack.addAll(playlist.tracks) } if (!tempTrack.contains(song.videoId) && - playlist.syncedWithYouTubePlaylist == 1 && + playlist.syncState == LocalPlaylistEntity.YouTubeSyncState.Synced && playlist.youtubePlaylistId != null ) { viewModel.addToYouTubePlaylist(playlist.id, playlist.youtubePlaylistId, song.videoId) diff --git a/app/src/main/java/com/maxrave/simpmusic/ui/fragment/library/LibraryFragment.kt b/app/src/main/java/com/maxrave/simpmusic/ui/fragment/library/LibraryFragment.kt index 0150560f..edae850c 100644 --- a/app/src/main/java/com/maxrave/simpmusic/ui/fragment/library/LibraryFragment.kt +++ b/app/src/main/java/com/maxrave/simpmusic/ui/fragment/library/LibraryFragment.kt @@ -396,7 +396,7 @@ class LibraryFragment : Fragment() { // } // if (!tempTrack.contains( // song.videoId, -// ) && playlist.syncedWithYouTubePlaylist == 1 && playlist.youtubePlaylistId != null +// ) && playlist.syncState == LocalPlaylistEntity.YouTubeSyncState.Synced && playlist.youtubePlaylistId != null // ) { // viewModel.addToYouTubePlaylist(playlist.id, playlist.youtubePlaylistId, song.videoId) // } diff --git a/app/src/main/java/com/maxrave/simpmusic/ui/fragment/library/MostPlayedFragment.kt b/app/src/main/java/com/maxrave/simpmusic/ui/fragment/library/MostPlayedFragment.kt index fdfb233f..a547ed67 100644 --- a/app/src/main/java/com/maxrave/simpmusic/ui/fragment/library/MostPlayedFragment.kt +++ b/app/src/main/java/com/maxrave/simpmusic/ui/fragment/library/MostPlayedFragment.kt @@ -305,7 +305,7 @@ class MostPlayedFragment: Fragment() { if (playlist.tracks != null) { tempTrack.addAll(playlist.tracks) } - if (!tempTrack.contains(song.videoId) && playlist.syncedWithYouTubePlaylist == 1 && playlist.youtubePlaylistId != null) { + if (!tempTrack.contains(song.videoId) && playlist.syncState == LocalPlaylistEntity.YouTubeSyncState.Synced && playlist.youtubePlaylistId != null) { viewModel.addToYouTubePlaylist(playlist.id, playlist.youtubePlaylistId, song.videoId) } if (!tempTrack.contains(song.videoId)) { diff --git a/app/src/main/java/com/maxrave/simpmusic/ui/fragment/other/AlbumFragment.kt b/app/src/main/java/com/maxrave/simpmusic/ui/fragment/other/AlbumFragment.kt index 2718677d..ba05c5b7 100644 --- a/app/src/main/java/com/maxrave/simpmusic/ui/fragment/other/AlbumFragment.kt +++ b/app/src/main/java/com/maxrave/simpmusic/ui/fragment/other/AlbumFragment.kt @@ -518,7 +518,7 @@ class AlbumFragment : Fragment() { if (!tempTrack.contains( song.videoId, ) && - playlist.syncedWithYouTubePlaylist == 1 && + playlist.syncState == LocalPlaylistEntity.YouTubeSyncState.Synced && playlist.youtubePlaylistId != null ) { viewModel.addToYouTubePlaylist( diff --git a/app/src/main/java/com/maxrave/simpmusic/ui/fragment/other/ArtistFragment.kt b/app/src/main/java/com/maxrave/simpmusic/ui/fragment/other/ArtistFragment.kt index e316a76f..228379b5 100644 --- a/app/src/main/java/com/maxrave/simpmusic/ui/fragment/other/ArtistFragment.kt +++ b/app/src/main/java/com/maxrave/simpmusic/ui/fragment/other/ArtistFragment.kt @@ -441,7 +441,7 @@ class ArtistFragment : Fragment() { tempTrack.addAll(playlist.tracks) } if (!tempTrack.contains(song.videoId) && - playlist.syncedWithYouTubePlaylist == 1 && + playlist.syncState == LocalPlaylistEntity.YouTubeSyncState.Synced && playlist.youtubePlaylistId != null ) { viewModel.addToYouTubePlaylist(playlist.id, playlist.youtubePlaylistId, song.videoId) diff --git a/app/src/main/java/com/maxrave/simpmusic/ui/fragment/other/LocalPlaylistFragment.kt b/app/src/main/java/com/maxrave/simpmusic/ui/fragment/other/LocalPlaylistFragment.kt index 921b8acd..31018a8b 100644 --- a/app/src/main/java/com/maxrave/simpmusic/ui/fragment/other/LocalPlaylistFragment.kt +++ b/app/src/main/java/com/maxrave/simpmusic/ui/fragment/other/LocalPlaylistFragment.kt @@ -300,7 +300,7 @@ class LocalPlaylistFragment : Fragment() { // } // if (!tempTrack.contains( // song.videoId, -// ) && playlist.syncedWithYouTubePlaylist == 1 && playlist.youtubePlaylistId != null +// ) && playlist.syncState == LocalPlaylistEntity.YouTubeSyncState.Synced && playlist.youtubePlaylistId != null // ) { // viewModel.addToYouTubePlaylist( // playlist.id, @@ -346,7 +346,7 @@ class LocalPlaylistFragment : Fragment() { // val temp = // playlistAdapter.getListTrack().getOrNull(position) as SongEntity // viewModel.deleteItem(temp, id!!) -// if (viewModel.localPlaylist.value?.syncedWithYouTubePlaylist == 1 && viewModel.localPlaylist.value?.youtubePlaylistId != null) { +// if (viewModel.localPlaylist.value?.syncState == LocalPlaylistEntity.YouTubeSyncState.Synced && viewModel.localPlaylist.value?.youtubePlaylistId != null) { // val videoId = viewModel.listTrack.value?.get(position)?.videoId // viewModel.removeYouTubePlaylistItem( // viewModel.localPlaylist.value?.youtubePlaylistId!!, @@ -427,7 +427,7 @@ class LocalPlaylistFragment : Fragment() { // } // if (!tempTrack.contains( // song.videoId, -// ) && viewModel.localPlaylist.value?.syncedWithYouTubePlaylist == 1 && viewModel.localPlaylist.value?.youtubePlaylistId != null +// ) && viewModel.localPlaylist.value?.syncState == LocalPlaylistEntity.YouTubeSyncState.Synced && viewModel.localPlaylist.value?.youtubePlaylistId != null // ) { // viewModel.addToYouTubePlaylist( // viewModel.localPlaylist.value?.id!!, @@ -496,7 +496,7 @@ class LocalPlaylistFragment : Fragment() { // if (viewModel.localPlaylist.value?.downloadState == DownloadState.STATE_DOWNLOADED) { // args.putInt("downloaded", 1) // } -// if (viewModel.localPlaylist.value?.syncedWithYouTubePlaylist == 1) { +// if (viewModel.localPlaylist.value?.syncState == LocalPlaylistEntity.YouTubeSyncState.Synced) { // args.putString( // "playlistId", // viewModel.localPlaylist.value?.youtubePlaylistId?.replaceFirst("VL", ""), @@ -535,7 +535,7 @@ class LocalPlaylistFragment : Fragment() { // if (viewModel.localPlaylist.value?.downloadState == DownloadState.STATE_DOWNLOADED) { // args.putInt("downloaded", 1) // } -// if (viewModel.localPlaylist.value?.syncedWithYouTubePlaylist == 1) { +// if (viewModel.localPlaylist.value?.syncState == LocalPlaylistEntity.YouTubeSyncState.Synced) { // args.putString( // "playlistId", // viewModel.localPlaylist.value?.youtubePlaylistId?.replaceFirst("VL", ""), @@ -609,7 +609,7 @@ class LocalPlaylistFragment : Fragment() { // } // binding.btSuggest.setOnClickListener { // if (binding.suggestLayout.visibility == View.GONE) { -// if (viewModel.localPlaylist.value?.syncedWithYouTubePlaylist == 1) { +// if (viewModel.localPlaylist.value?.syncState == LocalPlaylistEntity.YouTubeSyncState.Synced) { // if (viewModel.localPlaylist.value?.youtubePlaylistId != null) { // binding.suggestLayout.visibility = View.VISIBLE // viewModel.getSuggestions(viewModel.localPlaylist.value?.youtubePlaylistId!!) @@ -643,7 +643,7 @@ class LocalPlaylistFragment : Fragment() { // editDialogView.etPlaylistName.editText?.text.toString(), // id!!, // ) -// if (viewModel.localPlaylist.value?.syncedWithYouTubePlaylist == 1) { +// if (viewModel.localPlaylist.value?.syncState == LocalPlaylistEntity.YouTubeSyncState.Synced) { // viewModel.updateYouTubePlaylistTitle( // editDialogView.etPlaylistName.editText?.text.toString(), // viewModel.localPlaylist.value?.youtubePlaylistId!!, @@ -663,7 +663,7 @@ class LocalPlaylistFragment : Fragment() { // moreDialogView.tvSync.text = getString(R.string.sync) // moreDialogView.ivSync.setImageResource(R.drawable.baseline_sync_24) // moreDialogView.btUpdate.visibility = View.GONE -// } else if (viewModel.localPlaylist.value?.syncedWithYouTubePlaylist == 1) { +// } else if (viewModel.localPlaylist.value?.syncState == LocalPlaylistEntity.YouTubeSyncState.Synced) { // moreDialogView.tvSync.text = getString(R.string.synced) // moreDialogView.ivSync.setImageResource(R.drawable.baseline_sync_disabled_24) // moreDialogView.btUpdate.visibility = View.VISIBLE @@ -686,7 +686,7 @@ class LocalPlaylistFragment : Fragment() { // // binding.collapsingToolbarLayout.title = localPlaylist.title // // binding.tvTitle.text = localPlaylist.title // // binding.tvTitle.isSelected = true -// // if (localPlaylist.syncedWithYouTubePlaylist == 1 && localPlaylist.youtubePlaylistId != null) { +// // if (localPlaylist.syncState == LocalPlaylistEntity.YouTubeSyncState.Synced && localPlaylist.youtubePlaylistId != null) { // // if (!localPlaylist.tracks.isNullOrEmpty()) { // // viewModel.getSetVideoId(localPlaylist.youtubePlaylistId) // // } @@ -755,14 +755,14 @@ class LocalPlaylistFragment : Fragment() { // alertDialog.setCancelable(true) // alertDialog.show() // // viewModel.localPlaylist.observe(viewLifecycleOwner) { localPlaylist -> -// // if (localPlaylist?.syncedWithYouTubePlaylist == 1) { +// // if (localPlaylist?.syncState == LocalPlaylistEntity.YouTubeSyncState.Synced) { // // moreDialogView.tvSync.text = getString(R.string.synced) // // moreDialogView.ivSync.setImageResource( // // R.drawable.baseline_sync_disabled_24, // // ) // // } // // } -// } else if (viewModel.localPlaylist.value?.syncedWithYouTubePlaylist == 1) { +// } else if (viewModel.localPlaylist.value?.syncState == LocalPlaylistEntity.YouTubeSyncState.Synced) { // val alertDialog = // MaterialAlertDialogBuilder(requireContext()) // .setTitle(getString(R.string.warning)) diff --git a/app/src/main/java/com/maxrave/simpmusic/ui/fragment/other/PlaylistFragment.kt b/app/src/main/java/com/maxrave/simpmusic/ui/fragment/other/PlaylistFragment.kt index ae085ae2..2b095b04 100644 --- a/app/src/main/java/com/maxrave/simpmusic/ui/fragment/other/PlaylistFragment.kt +++ b/app/src/main/java/com/maxrave/simpmusic/ui/fragment/other/PlaylistFragment.kt @@ -13,7 +13,6 @@ import android.view.ViewGroup import android.widget.Toast import androidx.core.content.ContextCompat import androidx.core.graphics.ColorUtils -import androidx.core.graphics.drawable.toBitmap import androidx.core.net.toUri import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels @@ -26,12 +25,12 @@ import androidx.media3.exoplayer.offline.DownloadService import androidx.navigation.fragment.findNavController import androidx.palette.graphics.Palette import androidx.recyclerview.widget.LinearLayoutManager -import coil3.load -import coil3.request.crossfade -import coil3.request.placeholder import coil3.asDrawable +import coil3.load import coil3.request.CachePolicy import coil3.request.allowHardware +import coil3.request.crossfade +import coil3.request.placeholder import coil3.toBitmap import com.google.android.material.bottomsheet.BottomSheetDialog import com.google.android.material.snackbar.Snackbar @@ -772,7 +771,7 @@ class PlaylistFragment : Fragment() { if (!tempTrack.contains( song.videoId, ) && - playlist.syncedWithYouTubePlaylist == 1 && + playlist.syncState == LocalPlaylistEntity.YouTubeSyncState.Synced && playlist.youtubePlaylistId != null ) { viewModel.addToYouTubePlaylist( diff --git a/app/src/main/java/com/maxrave/simpmusic/ui/fragment/player/FullscreenFragment.kt b/app/src/main/java/com/maxrave/simpmusic/ui/fragment/player/FullscreenFragment.kt index d7bc5ea1..cf34a34a 100644 --- a/app/src/main/java/com/maxrave/simpmusic/ui/fragment/player/FullscreenFragment.kt +++ b/app/src/main/java/com/maxrave/simpmusic/ui/fragment/player/FullscreenFragment.kt @@ -480,7 +480,7 @@ class FullscreenFragment : Fragment() { } if (!tempTrack.contains( song.videoId, - ) && playlist.syncedWithYouTubePlaylist == 1 && playlist.youtubePlaylistId != null + ) && playlist.syncState == LocalPlaylistEntity.YouTubeSyncState.Synced && playlist.youtubePlaylistId != null ) { viewModel.addToYouTubePlaylist( playlist.id, diff --git a/app/src/main/java/com/maxrave/simpmusic/ui/fragment/player/NowPlayingFragment.kt b/app/src/main/java/com/maxrave/simpmusic/ui/fragment/player/NowPlayingFragment.kt index 54935072..ea10eda8 100644 --- a/app/src/main/java/com/maxrave/simpmusic/ui/fragment/player/NowPlayingFragment.kt +++ b/app/src/main/java/com/maxrave/simpmusic/ui/fragment/player/NowPlayingFragment.kt @@ -1644,7 +1644,7 @@ class NowPlayingFragment : Fragment() { // if (!tempTrack.contains( // song.videoId, // ) && -// playlist.syncedWithYouTubePlaylist == 1 && +// playlist.syncState == LocalPlaylistEntity.YouTubeSyncState.Synced && // playlist.youtubePlaylistId != null // ) { // viewModel.addToYouTubePlaylist( diff --git a/app/src/main/java/com/maxrave/simpmusic/utils/Resource.kt b/app/src/main/java/com/maxrave/simpmusic/utils/Resource.kt index 6b2390ce..7eb622b5 100644 --- a/app/src/main/java/com/maxrave/simpmusic/utils/Resource.kt +++ b/app/src/main/java/com/maxrave/simpmusic/utils/Resource.kt @@ -60,6 +60,18 @@ sealed class LocalResource( class Loading : LocalResource() } +sealed class NoResponseResource( + val message: String? = null, +) { + class Success() : NoResponseResource() + + class Error( + message: String, + ) : NoResponseResource(message) + + class Loading : NoResponseResource() +} + suspend fun Flow>.collectResource( distinct: Boolean = true, onSuccess: (T?) -> Unit, @@ -73,6 +85,19 @@ suspend fun Flow>.collectResource( } } +suspend fun Flow.collectNoResponseResource( + distinct: Boolean = true, + onSuccess: () -> Unit, + onError: (String) -> Unit = {}, + onLoading: () -> Unit = {}, +) = this.apply { if (distinct) distinctUntilChanged() }.collect { resource -> + when (resource) { + is NoResponseResource.Success -> onSuccess() + is NoResponseResource.Error -> onError(resource.message ?: "Error in collectNoResponseResource") + is NoResponseResource.Loading -> onLoading() + } +} + suspend fun Flow>.collectLatestResource( distinct: Boolean = true, onSuccess: (T?) -> Unit, @@ -84,4 +109,17 @@ suspend fun Flow>.collectLatestResource( is LocalResource.Error -> onError(resource.message ?: "Error in collectLatestResource") is LocalResource.Loading -> onLoading() } +} + +suspend fun Flow.collectLatestNoResponseResource( + distinct: Boolean = true, + onSuccess: () -> Unit, + onError: (String) -> Unit = {}, + onLoading: () -> Unit = {}, +) = this.apply { if (distinct) distinctUntilChanged() }.collectLatest { resource -> + when (resource) { + is NoResponseResource.Success -> onSuccess() + is NoResponseResource.Error -> onError(resource.message ?: "Error in collectLatestNoResponseResource") + is NoResponseResource.Loading -> onLoading() + } } \ No newline at end of file diff --git a/app/src/main/java/com/maxrave/simpmusic/viewModel/LocalPlaylistViewModel.kt b/app/src/main/java/com/maxrave/simpmusic/viewModel/LocalPlaylistViewModel.kt index 3baeabb2..a184905a 100644 --- a/app/src/main/java/com/maxrave/simpmusic/viewModel/LocalPlaylistViewModel.kt +++ b/app/src/main/java/com/maxrave/simpmusic/viewModel/LocalPlaylistViewModel.kt @@ -126,25 +126,25 @@ class LocalPlaylistViewModel( if (newList > currentList) { updatePlaylistState(uiState.value.id, refresh = true) } + delay(500) + val fullTracks = localPlaylistManager.getFullPlaylistTracks(id = id) + val notDownloadedList = fullTracks.filter { it.downloadState != STATE_DOWNLOADED }.map { it.videoId } + if (fullTracks.isEmpty()) { + updatePlaylistDownloadState(uiState.value.id, STATE_NOT_DOWNLOADED) + } else if (fullTracks.all { it.downloadState == STATE_DOWNLOADED } && uiState.value.downloadState != STATE_DOWNLOADED) { + updatePlaylistDownloadState(uiState.value.id, STATE_DOWNLOADED) + } else if ( + downloadUtils.downloads.value + .filter { it.value.state != Download.STATE_COMPLETED } + .map { it.key }.containsAll(notDownloadedList) && notDownloadedList.isNotEmpty() + && uiState.value.downloadState != STATE_DOWNLOADING + ) { + updatePlaylistDownloadState(uiState.value.id, STATE_DOWNLOADING) + } else if (uiState.value.downloadState != STATE_NOT_DOWNLOADED) { + updatePlaylistDownloadState(uiState.value.id, STATE_NOT_DOWNLOADED) + } } } - delay(500) - val fullTracks = localPlaylistManager.getFullPlaylistTracks(id = id) - val notDownloadedList = fullTracks.filter { it.downloadState != STATE_DOWNLOADED }.map { it.videoId } - if (fullTracks.isEmpty()) { - updatePlaylistDownloadState(uiState.value.id, STATE_NOT_DOWNLOADED) - } else if (fullTracks.all { it.downloadState == STATE_DOWNLOADED } && uiState.value.downloadState != STATE_DOWNLOADED) { - updatePlaylistDownloadState(uiState.value.id, STATE_DOWNLOADED) - } else if ( - downloadUtils.downloads.value - .filter { it.value.state != Download.STATE_COMPLETED } - .map { it.key }.containsAll(notDownloadedList) && notDownloadedList.isNotEmpty() - && uiState.value.downloadState != STATE_DOWNLOADING - ) { - updatePlaylistDownloadState(uiState.value.id, STATE_DOWNLOADING) - } else if (uiState.value.downloadState != STATE_NOT_DOWNLOADED) { - updatePlaylistDownloadState(uiState.value.id, STATE_NOT_DOWNLOADED) - } } } listTrackStringJob.join() @@ -523,7 +523,12 @@ class LocalPlaylistViewModel( .syncLocalPlaylistToYouTubePlaylist(id) .collectLatestResource( onSuccess = { ytId -> - updateLocalPlaylistSyncState(id, LocalPlaylistEntity.YouTubeSyncState.Synced, ytId) + _uiState.update { + it.copy( + syncState = LocalPlaylistEntity.YouTubeSyncState.Synced, + ytPlaylistId = ytId + ) + } makeToast(getString(R.string.synced)) hideLoadingDialog() }, diff --git a/kotlinYtmusicScraper/src/main/java/com/maxrave/kotlinytmusicscraper/YouTube.kt b/kotlinYtmusicScraper/src/main/java/com/maxrave/kotlinytmusicscraper/YouTube.kt index eaaef7c5..d45e4fc0 100644 --- a/kotlinYtmusicScraper/src/main/java/com/maxrave/kotlinytmusicscraper/YouTube.kt +++ b/kotlinYtmusicScraper/src/main/java/com/maxrave/kotlinytmusicscraper/YouTube.kt @@ -49,7 +49,6 @@ import com.maxrave.kotlinytmusicscraper.models.response.SearchResponse import com.maxrave.kotlinytmusicscraper.models.response.spotify.CanvasResponse import com.maxrave.kotlinytmusicscraper.models.response.spotify.PersonalTokenResponse import com.maxrave.kotlinytmusicscraper.models.response.spotify.SpotifyLyricsResponse -import com.maxrave.kotlinytmusicscraper.models.response.spotify.TokenResponse import com.maxrave.kotlinytmusicscraper.models.response.spotify.search.SpotifySearchResponse import com.maxrave.kotlinytmusicscraper.models.response.toLikeStatus import com.maxrave.kotlinytmusicscraper.models.simpmusic.GithubResponse @@ -586,9 +585,10 @@ object YouTube { suspend fun getYouTubePlaylistFullTracksWithSetVideoId(playlistId: String): Result>> = runCatching { + val plId = if (playlistId.startsWith("VL")) playlistId else "VL$playlistId" // SongItem / SetVideoId val listPair = mutableListOf>() - val response = ytMusic.playlist(playlistId).body() + val response = ytMusic.playlist(plId).body() listPair.addAll( response.fromPlaylistToTrackWithSetVideoId() ) diff --git a/kotlinYtmusicScraper/src/main/java/com/maxrave/kotlinytmusicscraper/extension/StringExt.kt b/kotlinYtmusicScraper/src/main/java/com/maxrave/kotlinytmusicscraper/extension/StringExt.kt index d59ddc55..422dcee8 100644 --- a/kotlinYtmusicScraper/src/main/java/com/maxrave/kotlinytmusicscraper/extension/StringExt.kt +++ b/kotlinYtmusicScraper/src/main/java/com/maxrave/kotlinytmusicscraper/extension/StringExt.kt @@ -14,4 +14,8 @@ fun randomString(length: Int): String { return (1..length) .map { chars[Random.nextInt(chars.length)] } .joinToString("") +} + +fun String.verifyYouTubePlaylistId(): String { + return if (startsWith("VL")) this else "VL$this" } \ No newline at end of file diff --git a/kotlinYtmusicScraper/src/main/java/com/maxrave/kotlinytmusicscraper/models/MusicPlaylistShelfRenderer.kt b/kotlinYtmusicScraper/src/main/java/com/maxrave/kotlinytmusicscraper/models/MusicPlaylistShelfRenderer.kt index 92dd0c8a..275f78ab 100644 --- a/kotlinYtmusicScraper/src/main/java/com/maxrave/kotlinytmusicscraper/models/MusicPlaylistShelfRenderer.kt +++ b/kotlinYtmusicScraper/src/main/java/com/maxrave/kotlinytmusicscraper/models/MusicPlaylistShelfRenderer.kt @@ -5,7 +5,7 @@ import kotlinx.serialization.Serializable @Serializable data class MusicPlaylistShelfRenderer( val playlistId: String?, - val contents: List, + val contents: List? = null, val collapsedItemCount: Int, val continuations: List?, )