Migrate database (2)

This commit is contained in:
vfsfitvnm 2022-06-29 15:03:20 +02:00
parent a7bdda074b
commit 6f5bc02216
13 changed files with 545 additions and 145 deletions

View file

@ -0,0 +1,419 @@
{
"formatVersion": 1,
"database": {
"version": 9,
"identityHash": "22e88f327e3340760100610939e9a158",
"entities": [
{
"tableName": "Song",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `title` TEXT NOT NULL, `albumId` TEXT, `artistsText` TEXT, `durationText` TEXT NOT NULL, `thumbnailUrl` TEXT, `lyrics` TEXT, `likedAt` INTEGER, `totalPlayTimeMs` INTEGER NOT NULL, `loudnessDb` REAL, `contentLength` INTEGER, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "title",
"columnName": "title",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "albumId",
"columnName": "albumId",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "artistsText",
"columnName": "artistsText",
"affinity": "TEXT",
"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
},
{
"fieldPath": "loudnessDb",
"columnName": "loudnessDb",
"affinity": "REAL",
"notNull": false
},
{
"fieldPath": "contentLength",
"columnName": "contentLength",
"affinity": "INTEGER",
"notNull": false
}
],
"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": "Artist",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `name` TEXT NOT NULL, `thumbnailUrl` TEXT, `info` TEXT, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "name",
"columnName": "name",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "thumbnailUrl",
"columnName": "thumbnailUrl",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "info",
"columnName": "info",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"id"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "SongArtistMap",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`songId` TEXT NOT NULL, `artistId` TEXT NOT NULL, PRIMARY KEY(`songId`, `artistId`), FOREIGN KEY(`songId`) REFERENCES `Song`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`artistId`) REFERENCES `Artist`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
"fields": [
{
"fieldPath": "songId",
"columnName": "songId",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "artistId",
"columnName": "artistId",
"affinity": "TEXT",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"songId",
"artistId"
]
},
"indices": [
{
"name": "index_SongArtistMap_songId",
"unique": false,
"columnNames": [
"songId"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_SongArtistMap_songId` ON `${TABLE_NAME}` (`songId`)"
},
{
"name": "index_SongArtistMap_artistId",
"unique": false,
"columnNames": [
"artistId"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_SongArtistMap_artistId` ON `${TABLE_NAME}` (`artistId`)"
}
],
"foreignKeys": [
{
"table": "Song",
"onDelete": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"songId"
],
"referencedColumns": [
"id"
]
},
{
"table": "Artist",
"onDelete": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"artistId"
],
"referencedColumns": [
"id"
]
}
]
},
{
"tableName": "Album",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `title` TEXT NOT NULL, `thumbnailUrl` TEXT, `year` TEXT, `authorsText` TEXT, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "title",
"columnName": "title",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "thumbnailUrl",
"columnName": "thumbnailUrl",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "year",
"columnName": "year",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "authorsText",
"columnName": "authorsText",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"id"
]
},
"indices": [],
"foreignKeys": []
},
{
"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": []
},
{
"tableName": "QueuedMediaItem",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `mediaItem` BLOB NOT NULL, `position` INTEGER)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "mediaItem",
"columnName": "mediaItem",
"affinity": "BLOB",
"notNull": true
},
{
"fieldPath": "position",
"columnName": "position",
"affinity": "INTEGER",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
},
"indices": [],
"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, '22e88f327e3340760100610939e9a158')"
]
}
}

View file

@ -1,11 +1,14 @@
package it.vfsfitvnm.vimusic package it.vfsfitvnm.vimusic
import android.content.ContentValues
import android.content.Context import android.content.Context
import android.database.Cursor import android.database.Cursor
import android.database.sqlite.SQLiteDatabase.CONFLICT_IGNORE
import android.os.Parcel import android.os.Parcel
import androidx.media3.common.MediaItem import androidx.media3.common.MediaItem
import androidx.room.* import androidx.room.*
import androidx.room.migration.AutoMigrationSpec import androidx.room.migration.AutoMigrationSpec
import androidx.sqlite.db.SimpleSQLiteQuery
import androidx.room.migration.Migration import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase import androidx.sqlite.db.SupportSQLiteDatabase
import it.vfsfitvnm.vimusic.models.* import it.vfsfitvnm.vimusic.models.*
@ -22,8 +25,11 @@ interface Database {
@Insert(onConflict = OnConflictStrategy.REPLACE) @Insert(onConflict = OnConflictStrategy.REPLACE)
fun insert(searchQuery: SearchQuery) fun insert(searchQuery: SearchQuery)
@Insert(onConflict = OnConflictStrategy.ABORT) @Insert(onConflict = OnConflictStrategy.IGNORE)
fun insert(info: Info): Long fun insert(info: Artist)
@Insert(onConflict = OnConflictStrategy.IGNORE)
fun insert(info: Album)
@Insert(onConflict = OnConflictStrategy.ABORT) @Insert(onConflict = OnConflictStrategy.ABORT)
fun insert(playlist: Playlist): Long fun insert(playlist: Playlist): Long
@ -32,7 +38,7 @@ interface Database {
fun insert(info: SongInPlaylist): Long fun insert(info: SongInPlaylist): Long
@Insert(onConflict = OnConflictStrategy.ABORT) @Insert(onConflict = OnConflictStrategy.ABORT)
fun insert(info: List<Info>): List<Long> fun insert(info: List<Artist>): List<Long>
@Query("SELECT * FROM Song WHERE id = :id") @Query("SELECT * FROM Song WHERE id = :id")
fun songFlow(id: String): Flow<Song?> fun songFlow(id: String): Flow<Song?>
@ -48,19 +54,19 @@ interface Database {
@Transaction @Transaction
@Query("SELECT * FROM Song WHERE id = :id") @Query("SELECT * FROM Song WHERE id = :id")
fun songWithInfo(id: String): SongWithInfo? fun songWithInfo(id: String): DetailedSong?
@Transaction @Transaction
@Query("SELECT * FROM Song WHERE totalPlayTimeMs > 0 ORDER BY ROWID DESC") @Query("SELECT * FROM Song WHERE totalPlayTimeMs > 0 ORDER BY ROWID DESC")
fun history(): Flow<List<SongWithInfo>> fun history(): Flow<List<DetailedSong>>
@Transaction @Transaction
@Query("SELECT * FROM Song WHERE likedAt IS NOT NULL ORDER BY likedAt DESC") @Query("SELECT * FROM Song WHERE likedAt IS NOT NULL ORDER BY likedAt DESC")
fun favorites(): Flow<List<SongWithInfo>> fun favorites(): Flow<List<DetailedSong>>
@Transaction @Transaction
@Query("SELECT * FROM Song WHERE totalPlayTimeMs >= 60000 ORDER BY totalPlayTimeMs DESC LIMIT 20") @Query("SELECT * FROM Song WHERE totalPlayTimeMs >= 60000 ORDER BY totalPlayTimeMs DESC LIMIT 20")
fun mostPlayed(): Flow<List<SongWithInfo>> fun mostPlayed(): Flow<List<DetailedSong>>
@Query("UPDATE Song SET totalPlayTimeMs = totalPlayTimeMs + :addition WHERE id = :id") @Query("UPDATE Song SET totalPlayTimeMs = totalPlayTimeMs + :addition WHERE id = :id")
fun incrementTotalPlayTimeMs(id: String, addition: Long) fun incrementTotalPlayTimeMs(id: String, addition: Long)
@ -82,7 +88,7 @@ interface Database {
fun incrementSongPositions(playlistId: Long, fromPosition: Int, toPosition: Int) fun incrementSongPositions(playlistId: Long, fromPosition: Int, toPosition: Int)
@Insert(onConflict = OnConflictStrategy.ABORT) @Insert(onConflict = OnConflictStrategy.ABORT)
fun insert(songWithAuthors: SongWithAuthors): Long fun insert(songWithAuthors: SongArtistMap): Long
@Insert(onConflict = OnConflictStrategy.ABORT) @Insert(onConflict = OnConflictStrategy.ABORT)
fun insert(song: Song): Long fun insert(song: Song): Long
@ -115,10 +121,10 @@ interface Database {
@Query("SELECT thumbnailUrl FROM Song JOIN SongInPlaylist ON id = songId WHERE playlistId = :id ORDER BY position LIMIT 4") @Query("SELECT thumbnailUrl FROM Song JOIN SongInPlaylist ON id = songId WHERE playlistId = :id ORDER BY position LIMIT 4")
fun playlistThumbnailUrls(id: Long): Flow<List<String?>> fun playlistThumbnailUrls(id: Long): Flow<List<String?>>
@Transaction // @Transaction
@RewriteQueriesToDropUnusedColumns // @RewriteQueriesToDropUnusedColumns
@Query("SELECT * FROM Info JOIN SongWithAuthors ON Info.id = SongWithAuthors.authorInfoId JOIN Song ON SongWithAuthors.songId = Song.id WHERE browseId = :artistId ORDER BY Song.ROWID DESC") // @Query("SELECT * FROM Info JOIN SongWithAuthors ON Info.id = SongWithAuthors.authorInfoId JOIN Song ON SongWithAuthors.songId = Song.id WHERE browseId = :artistId ORDER BY Song.ROWID DESC")
fun artistSongs(artistId: String): Flow<List<SongWithInfo>> // fun artistSongs(artistId: String): Flow<List<DetailedSong>>
@Insert(onConflict = OnConflictStrategy.ABORT) @Insert(onConflict = OnConflictStrategy.ABORT)
fun insertQueue(queuedMediaItems: List<QueuedMediaItem>) fun insertQueue(queuedMediaItems: List<QueuedMediaItem>)
@ -135,11 +141,9 @@ interface Database {
Song::class, Song::class,
SongInPlaylist::class, SongInPlaylist::class,
Playlist::class, Playlist::class,
Info::class,
SongWithAuthors::class,
Album::class,
Artist::class, Artist::class,
SongArtistMap::class, SongArtistMap::class,
Album::class,
SearchQuery::class, SearchQuery::class,
QueuedMediaItem::class, QueuedMediaItem::class,
], ],
@ -183,8 +187,42 @@ abstract class DatabaseInitializer protected constructor() : RoomDatabase() {
class From7To8Migration : AutoMigrationSpec class From7To8Migration : AutoMigrationSpec
class From8To9Migration : Migration(8, 9) { class From8To9Migration : Migration(8, 9) {
override fun migrate(database: SupportSQLiteDatabase) { override fun migrate(it: SupportSQLiteDatabase) {
it.query(SimpleSQLiteQuery("SELECT DISTINCT browseId, text, Info.id FROM Info JOIN Song ON Info.id = Song.albumId;")).use { cursor ->
val albumValues = ContentValues(2)
while (cursor.moveToNext()) {
albumValues.put("id", cursor.getString(0))
albumValues.put("title", cursor.getString(1))
it.insert("Album", CONFLICT_IGNORE, albumValues)
it.execSQL("UPDATE Song SET albumId = '${cursor.getString(0)}' WHERE albumId = ${cursor.getLong(2)}")
}
}
it.query(SimpleSQLiteQuery("SELECT GROUP_CONCAT(text, ''), SongWithAuthors.songId FROM Info JOIN SongWithAuthors ON Info.id = SongWithAuthors.authorInfoId GROUP BY songId;")).use { cursor ->
val songValues = ContentValues(1)
while (cursor.moveToNext()) {
println("artistsText: ${cursor.getString(0)} (cursor.getString(1))")
songValues.put("artistsText", cursor.getString(0))
it.update("Song", CONFLICT_IGNORE, songValues, "id = ?", arrayOf(cursor.getString(1)))
}
}
it.query(SimpleSQLiteQuery("SELECT browseId, text, Info.id FROM Info JOIN SongWithAuthors ON Info.id = SongWithAuthors.authorInfoId WHERE browseId NOT NULL;")).use { cursor ->
val artistValues = ContentValues(2)
while (cursor.moveToNext()) {
artistValues.put("id", cursor.getString(0))
artistValues.put("name", cursor.getString(1))
it.insert("Artist", CONFLICT_IGNORE, artistValues)
it.execSQL("UPDATE SongWithAuthors SET authorInfoId = '${cursor.getString(0)}' WHERE authorInfoId = ${cursor.getLong(2)}")
}
}
it.execSQL("INSERT INTO SongArtistMap(songId, artistId) SELECT songId, authorInfoId FROM SongWithAuthors")
it.execSQL("DROP TABLE Info;")
it.execSQL("DROP TABLE SongWithAuthors;")
} }
} }
} }

View file

@ -1,12 +0,0 @@
package it.vfsfitvnm.vimusic.models
import androidx.room.Entity
import androidx.room.PrimaryKey
// I know...
@Entity
data class Info(
@PrimaryKey(autoGenerate = true) val id: Long = 0,
val browseId: String?,
val text: String
)

View file

@ -15,7 +15,7 @@ data class PlaylistWithSongs(
entityColumn = "songId" entityColumn = "songId"
) )
) )
val songs: List<SongWithInfo> val songs: List<DetailedSong>
) { ) {
companion object { companion object {
val Empty = PlaylistWithSongs(Playlist(-1, ""), emptyList()) val Empty = PlaylistWithSongs(Playlist(-1, ""), emptyList())

View file

@ -3,7 +3,16 @@ package it.vfsfitvnm.vimusic.models
import androidx.room.* import androidx.room.*
@Entity @Entity(
// foreignKeys = [
// ForeignKey(
// entity = Album::class,
// parentColumns = ["id"],
// childColumns = ["albumId"],
// onDelete = ForeignKey.CASCADE
// ),
// ]
)
data class Song( data class Song(
@PrimaryKey val id: String, @PrimaryKey val id: String,
val title: String, val title: String,

View file

@ -1,28 +0,0 @@
package it.vfsfitvnm.vimusic.models
import androidx.compose.runtime.Immutable
import androidx.room.*
@Immutable
@Entity(
primaryKeys = ["songId", "authorInfoId"],
foreignKeys = [
ForeignKey(
entity = Song::class,
parentColumns = ["id"],
childColumns = ["songId"],
onDelete = ForeignKey.CASCADE
),
ForeignKey(
entity = Info::class,
parentColumns = ["id"],
childColumns = ["authorInfoId"],
onDelete = ForeignKey.CASCADE
)
]
)
data class SongWithAuthors(
val songId: String,
@ColumnInfo(index = true) val authorInfoId: Long
)

View file

@ -1,25 +0,0 @@
package it.vfsfitvnm.vimusic.models
import androidx.room.Embedded
import androidx.room.Junction
import androidx.room.Relation
open class SongWithInfo(
@Embedded val song: Song,
@Relation(
entity = Info::class,
parentColumn = "albumId",
entityColumn = "id"
) val album: Info?,
@Relation(
entity = Info::class,
parentColumn = "id",
entityColumn = "id",
associateBy = Junction(
value = SongWithAuthors::class,
parentColumn = "songId",
entityColumn = "authorInfoId"
)
)
val authors: List<Info>?
)

View file

@ -20,7 +20,7 @@ import it.vfsfitvnm.vimusic.*
import it.vfsfitvnm.vimusic.R import it.vfsfitvnm.vimusic.R
import it.vfsfitvnm.vimusic.models.Playlist import it.vfsfitvnm.vimusic.models.Playlist
import it.vfsfitvnm.vimusic.models.SongInPlaylist import it.vfsfitvnm.vimusic.models.SongInPlaylist
import it.vfsfitvnm.vimusic.models.SongWithInfo import it.vfsfitvnm.vimusic.models.DetailedSong
import it.vfsfitvnm.vimusic.ui.components.LocalMenuState import it.vfsfitvnm.vimusic.ui.components.LocalMenuState
import it.vfsfitvnm.vimusic.ui.screens.rememberArtistRoute import it.vfsfitvnm.vimusic.ui.screens.rememberArtistRoute
import it.vfsfitvnm.vimusic.ui.screens.rememberCreatePlaylistRoute import it.vfsfitvnm.vimusic.ui.screens.rememberCreatePlaylistRoute
@ -32,7 +32,7 @@ import kotlinx.coroutines.Dispatchers
@ExperimentalAnimationApi @ExperimentalAnimationApi
@Composable @Composable
fun InFavoritesMediaItemMenu( fun InFavoritesMediaItemMenu(
song: SongWithInfo, song: DetailedSong,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
onDismiss: (() -> Unit)? = null onDismiss: (() -> Unit)? = null
) { ) {
@ -51,7 +51,7 @@ fun InFavoritesMediaItemMenu(
@ExperimentalAnimationApi @ExperimentalAnimationApi
@Composable @Composable
fun InHistoryMediaItemMenu( fun InHistoryMediaItemMenu(
song: SongWithInfo, song: DetailedSong,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
onDismiss: (() -> Unit)? = null onDismiss: (() -> Unit)? = null
) { ) {
@ -94,7 +94,7 @@ fun InHistoryMediaItemMenu(
fun InPlaylistMediaItemMenu( fun InPlaylistMediaItemMenu(
playlistId: Long, playlistId: Long,
positionInPlaylist: Int, positionInPlaylist: Int,
song: SongWithInfo, song: DetailedSong,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
onDismiss: (() -> Unit)? = null onDismiss: (() -> Unit)? = null
) { ) {

View file

@ -28,7 +28,7 @@ import it.vfsfitvnm.route.RouteHandler
import it.vfsfitvnm.vimusic.Database import it.vfsfitvnm.vimusic.Database
import it.vfsfitvnm.vimusic.LocalPlayerServiceBinder import it.vfsfitvnm.vimusic.LocalPlayerServiceBinder
import it.vfsfitvnm.vimusic.R import it.vfsfitvnm.vimusic.R
import it.vfsfitvnm.vimusic.models.SongWithInfo import it.vfsfitvnm.vimusic.models.DetailedSong
import it.vfsfitvnm.vimusic.ui.components.OutcomeItem import it.vfsfitvnm.vimusic.ui.components.OutcomeItem
import it.vfsfitvnm.vimusic.ui.components.TopAppBar import it.vfsfitvnm.vimusic.ui.components.TopAppBar
import it.vfsfitvnm.vimusic.ui.components.themed.InHistoryMediaItemMenu import it.vfsfitvnm.vimusic.ui.components.themed.InHistoryMediaItemMenu
@ -40,6 +40,7 @@ import it.vfsfitvnm.vimusic.utils.*
import it.vfsfitvnm.youtubemusic.Outcome import it.vfsfitvnm.youtubemusic.Outcome
import it.vfsfitvnm.youtubemusic.YouTube import it.vfsfitvnm.youtubemusic.YouTube
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@ -96,8 +97,9 @@ fun ArtistScreen(
} }
val songs by remember(browseId) { val songs by remember(browseId) {
Database.artistSongs(browseId) flowOf(emptyList<DetailedSong>())
}.collectAsState(initial = emptyList(), context = Dispatchers.IO) // Database.artistSongs(browseId)
}.collectAsState(initial = emptyList<DetailedSong>(), context = Dispatchers.IO)
LazyColumn( LazyColumn(
state = lazyListState, state = lazyListState,
@ -218,7 +220,7 @@ fun ArtistScreen(
modifier = Modifier modifier = Modifier
.clickable(enabled = songs.isNotEmpty()) { .clickable(enabled = songs.isNotEmpty()) {
binder?.stopRadio() binder?.stopRadio()
binder?.player?.forcePlayFromBeginning(songs.shuffled().map(SongWithInfo::asMediaItem)) binder?.player?.forcePlayFromBeginning(songs.shuffled().map(DetailedSong::asMediaItem))
} }
.padding(horizontal = 8.dp, vertical = 8.dp) .padding(horizontal = 8.dp, vertical = 8.dp)
.size(20.dp) .size(20.dp)
@ -236,7 +238,7 @@ fun ArtistScreen(
thumbnailSize = songThumbnailSizePx, thumbnailSize = songThumbnailSizePx,
onClick = { onClick = {
binder?.stopRadio() binder?.stopRadio()
binder?.player?.forcePlayAtIndex(songs.map(SongWithInfo::asMediaItem), index) binder?.player?.forcePlayAtIndex(songs.map(DetailedSong::asMediaItem), index)
}, },
menuContent = { menuContent = {
InHistoryMediaItemMenu(song = song) InHistoryMediaItemMenu(song = song)

View file

@ -37,7 +37,7 @@ import it.vfsfitvnm.vimusic.enums.SongCollection
import it.vfsfitvnm.vimusic.enums.ThumbnailRoundness import it.vfsfitvnm.vimusic.enums.ThumbnailRoundness
import it.vfsfitvnm.vimusic.models.Playlist import it.vfsfitvnm.vimusic.models.Playlist
import it.vfsfitvnm.vimusic.models.SearchQuery import it.vfsfitvnm.vimusic.models.SearchQuery
import it.vfsfitvnm.vimusic.models.SongWithInfo import it.vfsfitvnm.vimusic.models.DetailedSong
import it.vfsfitvnm.vimusic.query import it.vfsfitvnm.vimusic.query
import it.vfsfitvnm.vimusic.ui.components.TopAppBar import it.vfsfitvnm.vimusic.ui.components.TopAppBar
import it.vfsfitvnm.vimusic.ui.components.themed.InFavoritesMediaItemMenu import it.vfsfitvnm.vimusic.ui.components.themed.InFavoritesMediaItemMenu
@ -358,7 +358,7 @@ fun HomeScreen() {
modifier = Modifier modifier = Modifier
.clickable(enabled = songCollection.isNotEmpty()) { .clickable(enabled = songCollection.isNotEmpty()) {
binder?.stopRadio() binder?.stopRadio()
binder?.player?.forcePlayFromBeginning(songCollection.shuffled().map(SongWithInfo::asMediaItem)) binder?.player?.forcePlayFromBeginning(songCollection.shuffled().map(DetailedSong::asMediaItem))
} }
.padding(horizontal = 8.dp, vertical = 8.dp) .padding(horizontal = 8.dp, vertical = 8.dp)
.size(20.dp) .size(20.dp)
@ -378,7 +378,7 @@ fun HomeScreen() {
thumbnailSize = thumbnailSize, thumbnailSize = thumbnailSize,
onClick = { onClick = {
binder?.stopRadio() binder?.stopRadio()
binder?.player?.forcePlayAtIndex(songCollection.map(SongWithInfo::asMediaItem), index) binder?.player?.forcePlayAtIndex(songCollection.map(DetailedSong::asMediaItem), index)
}, },
menuContent = { menuContent = {
when (preferences.homePageSongCollection) { when (preferences.homePageSongCollection) {

View file

@ -28,7 +28,7 @@ import it.vfsfitvnm.vimusic.*
import it.vfsfitvnm.vimusic.R import it.vfsfitvnm.vimusic.R
import it.vfsfitvnm.vimusic.models.PlaylistWithSongs import it.vfsfitvnm.vimusic.models.PlaylistWithSongs
import it.vfsfitvnm.vimusic.models.SongInPlaylist import it.vfsfitvnm.vimusic.models.SongInPlaylist
import it.vfsfitvnm.vimusic.models.SongWithInfo import it.vfsfitvnm.vimusic.models.DetailedSong
import it.vfsfitvnm.vimusic.ui.components.LocalMenuState import it.vfsfitvnm.vimusic.ui.components.LocalMenuState
import it.vfsfitvnm.vimusic.ui.components.TopAppBar import it.vfsfitvnm.vimusic.ui.components.TopAppBar
import it.vfsfitvnm.vimusic.ui.components.themed.* import it.vfsfitvnm.vimusic.ui.components.themed.*
@ -160,7 +160,7 @@ fun LocalPlaylistScreen(
enabled = playlistWithSongs.songs.isNotEmpty(), enabled = playlistWithSongs.songs.isNotEmpty(),
onClick = { onClick = {
menuState.hide() menuState.hide()
binder?.player?.enqueue(playlistWithSongs.songs.map(SongWithInfo::asMediaItem)) binder?.player?.enqueue(playlistWithSongs.songs.map(DetailedSong::asMediaItem))
} }
) )
@ -225,7 +225,7 @@ fun LocalPlaylistScreen(
modifier = Modifier modifier = Modifier
.clickable { .clickable {
binder?.stopRadio() binder?.stopRadio()
binder?.player?.forcePlayFromBeginning(playlistWithSongs.songs.map(SongWithInfo::asMediaItem).shuffled()) binder?.player?.forcePlayFromBeginning(playlistWithSongs.songs.map(DetailedSong::asMediaItem).shuffled())
} }
.shadow(elevation = 2.dp, shape = CircleShape) .shadow(elevation = 2.dp, shape = CircleShape)
.background( .background(
@ -243,7 +243,7 @@ fun LocalPlaylistScreen(
modifier = Modifier modifier = Modifier
.clickable { .clickable {
binder?.stopRadio() binder?.stopRadio()
binder?.player?.forcePlayFromBeginning(playlistWithSongs.songs.map(SongWithInfo::asMediaItem)) binder?.player?.forcePlayFromBeginning(playlistWithSongs.songs.map(DetailedSong::asMediaItem))
} }
.shadow(elevation = 2.dp, shape = CircleShape) .shadow(elevation = 2.dp, shape = CircleShape)
.background( .background(
@ -267,7 +267,7 @@ fun LocalPlaylistScreen(
thumbnailSize = thumbnailSize, thumbnailSize = thumbnailSize,
onClick = { onClick = {
binder?.stopRadio() binder?.stopRadio()
binder?.player?.forcePlayAtIndex(playlistWithSongs.songs.map(SongWithInfo::asMediaItem), index) binder?.player?.forcePlayAtIndex(playlistWithSongs.songs.map(DetailedSong::asMediaItem), index)
}, },
menuContent = { menuContent = {
InPlaylistMediaItemMenu( InPlaylistMediaItemMenu(

View file

@ -26,7 +26,7 @@ import coil.compose.AsyncImage
import coil.request.ImageRequest import coil.request.ImageRequest
import it.vfsfitvnm.vimusic.R import it.vfsfitvnm.vimusic.R
import it.vfsfitvnm.vimusic.enums.ThumbnailRoundness import it.vfsfitvnm.vimusic.enums.ThumbnailRoundness
import it.vfsfitvnm.vimusic.models.SongWithInfo import it.vfsfitvnm.vimusic.models.DetailedSong
import it.vfsfitvnm.vimusic.ui.components.LocalMenuState import it.vfsfitvnm.vimusic.ui.components.LocalMenuState
import it.vfsfitvnm.vimusic.ui.styling.LocalColorPalette import it.vfsfitvnm.vimusic.ui.styling.LocalColorPalette
import it.vfsfitvnm.vimusic.ui.styling.LocalTypography import it.vfsfitvnm.vimusic.ui.styling.LocalTypography
@ -67,7 +67,7 @@ fun SongItem(
@Composable @Composable
@NonRestartableComposable @NonRestartableComposable
fun SongItem( fun SongItem(
song: SongWithInfo, song: DetailedSong,
thumbnailSize: Int, thumbnailSize: Int,
onClick: () -> Unit, onClick: () -> Unit,
menuContent: @Composable () -> Unit, menuContent: @Composable () -> Unit,
@ -78,7 +78,7 @@ fun SongItem(
SongItem( SongItem(
thumbnailModel = song.song.thumbnailUrl?.thumbnail(thumbnailSize), thumbnailModel = song.song.thumbnailUrl?.thumbnail(thumbnailSize),
title = song.song.title, title = song.song.title,
authors = song.authors?.joinToString("") { it.text } ?: "", authors = song.song.artistsText ?: "",
durationText = song.song.durationText, durationText = song.song.durationText,
menuContent = menuContent, menuContent = menuContent,
onClick = onClick, onClick = onClick,

View file

@ -28,46 +28,43 @@ fun Database.insert(mediaItem: MediaItem): Song {
return@runInTransaction it return@runInTransaction it
} }
val albumInfo = mediaItem.mediaMetadata.extras?.getString("albumId")?.let { albumId -> val album = mediaItem.mediaMetadata.extras?.getString("albumId")?.let { albumId ->
Info( Album(
text = mediaItem.mediaMetadata.albumTitle!!.toString(), id = albumId,
browseId = albumId title = mediaItem.mediaMetadata.albumTitle!!.toString(),
) year = null,
} authorsText = null,
thumbnailUrl = null
val albumInfoId = albumInfo?.let { insert(it) } ).also(::insert)
val authorsInfo =
mediaItem.mediaMetadata.extras?.getStringArrayList("artistNames")?.let { artistNames ->
mediaItem.mediaMetadata.extras!!.getStringArrayList("artistIds")?.let { artistIds ->
artistNames.mapIndexed { index, artistName ->
Info(
text = artistName,
browseId = artistIds.getOrNull(index)
)
}
}
} }
val song = Song( val song = Song(
id = mediaItem.mediaId, id = mediaItem.mediaId,
title = mediaItem.mediaMetadata.title!!.toString(), title = mediaItem.mediaMetadata.title!!.toString(),
albumId = albumInfoId.toString(), artistsText = mediaItem.mediaMetadata.artist!!.toString(),
albumId = album?.id,
durationText = mediaItem.mediaMetadata.extras?.getString("durationText")!!, durationText = mediaItem.mediaMetadata.extras?.getString("durationText")!!,
thumbnailUrl = mediaItem.mediaMetadata.artworkUri!!.toString(), thumbnailUrl = mediaItem.mediaMetadata.artworkUri!!.toString(),
loudnessDb = mediaItem.mediaMetadata.extras?.getFloat("loudnessDb"), loudnessDb = mediaItem.mediaMetadata.extras?.getFloat("loudnessDb"),
contentLength = mediaItem.mediaMetadata.extras?.getLong("contentLength"), contentLength = mediaItem.mediaMetadata.extras?.getLong("contentLength"),
) ).also(::insert)
insert(song) mediaItem.mediaMetadata.extras?.getStringArrayList("artistNames")?.let { artistNames ->
mediaItem.mediaMetadata.extras!!.getStringArrayList("artistIds")?.let { artistIds ->
val authorsInfoId = authorsInfo?.let { insert(authorsInfo) } artistNames.mapIndexed { index, artistName ->
Artist(
authorsInfoId?.forEach { authorInfoId -> id = artistIds[index],
name = artistName,
thumbnailUrl = null,
info = null
).also(::insert)
}
}
}?.forEach { artist ->
insert( insert(
SongWithAuthors( SongArtistMap(
songId = mediaItem.mediaId, songId = song.id,
authorInfoId = authorInfoId artistId = artist.id
) )
) )
} }
@ -92,8 +89,8 @@ val YouTube.Item.Song.asMediaItem: MediaItem
"videoId" to info.endpoint!!.videoId, "videoId" to info.endpoint!!.videoId,
"albumId" to album?.endpoint?.browseId, "albumId" to album?.endpoint?.browseId,
"durationText" to durationText, "durationText" to durationText,
"artistNames" to authors.map { it.name }, "artistNames" to authors.filter { it.endpoint != null }.map { it.name },
"artistIds" to authors.map { it.endpoint?.browseId }, "artistIds" to authors.mapNotNull { it.endpoint?.browseId },
) )
) )
.build() .build()
@ -114,28 +111,28 @@ val YouTube.Item.Video.asMediaItem: MediaItem
bundleOf( bundleOf(
"videoId" to info.endpoint!!.videoId, "videoId" to info.endpoint!!.videoId,
"durationText" to durationText, "durationText" to durationText,
"artistNames" to if (isOfficialMusicVideo) authors.map { it.name } else null, "artistNames" to if (isOfficialMusicVideo) authors.filter { it.endpoint != null }.map { it.name } else null,
"artistIds" to if (isOfficialMusicVideo) authors.map { it.endpoint?.browseId } else null, "artistIds" to if (isOfficialMusicVideo) authors.mapNotNull { it.endpoint?.browseId } else null,
) )
) )
.build() .build()
) )
.build() .build()
val SongWithInfo.asMediaItem: MediaItem val DetailedSong.asMediaItem: MediaItem
get() = MediaItem.Builder() get() = MediaItem.Builder()
.setMediaMetadata( .setMediaMetadata(
MediaMetadata.Builder() MediaMetadata.Builder()
.setTitle(song.title) .setTitle(song.title)
.setArtist(authors?.joinToString("") { it.text }) .setArtist(song.artistsText)
.setAlbumTitle(album?.text) .setAlbumTitle(album?.title)
.setArtworkUri(song.thumbnailUrl?.toUri()) .setArtworkUri(song.thumbnailUrl?.toUri())
.setExtras( .setExtras(
bundleOf( bundleOf(
"videoId" to song.id, "videoId" to song.id,
"albumId" to album?.browseId, "albumId" to album?.id,
"artistNames" to authors?.map { it.text }, "artistNames" to artists?.map { it.name },
"artistIds" to authors?.map { it.browseId }, "artistIds" to artists?.map { it.id },
"durationText" to song.durationText, "durationText" to song.durationText,
"loudnessDb" to song.loudnessDb "loudnessDb" to song.loudnessDb
) )
@ -166,8 +163,8 @@ fun YouTube.PlaylistOrAlbum.Item.toMediaItem(
"playlistId" to info.endpoint?.playlistId, "playlistId" to info.endpoint?.playlistId,
"albumId" to (if (isFromAlbum) albumId else album?.endpoint?.browseId), "albumId" to (if (isFromAlbum) albumId else album?.endpoint?.browseId),
"durationText" to durationText, "durationText" to durationText,
"artistNames" to (authors ?: playlistOrAlbum.authors)?.map { it.name }, "artistNames" to (authors ?: playlistOrAlbum.authors)?.filter { it.endpoint != null }?.map { it.name },
"artistIds" to (authors ?: playlistOrAlbum.authors)?.map { it.endpoint?.browseId } "artistIds" to (authors ?: playlistOrAlbum.authors)?.mapNotNull { it.endpoint?.browseId }
) )
) )
.build() .build()