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
import android.content.ContentValues
import android.content.Context
import android.database.Cursor
import android.database.sqlite.SQLiteDatabase.CONFLICT_IGNORE
import android.os.Parcel
import androidx.media3.common.MediaItem
import androidx.room.*
import androidx.room.migration.AutoMigrationSpec
import androidx.sqlite.db.SimpleSQLiteQuery
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
import it.vfsfitvnm.vimusic.models.*
@ -22,8 +25,11 @@ interface Database {
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insert(searchQuery: SearchQuery)
@Insert(onConflict = OnConflictStrategy.ABORT)
fun insert(info: Info): Long
@Insert(onConflict = OnConflictStrategy.IGNORE)
fun insert(info: Artist)
@Insert(onConflict = OnConflictStrategy.IGNORE)
fun insert(info: Album)
@Insert(onConflict = OnConflictStrategy.ABORT)
fun insert(playlist: Playlist): Long
@ -32,7 +38,7 @@ interface Database {
fun insert(info: SongInPlaylist): Long
@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")
fun songFlow(id: String): Flow<Song?>
@ -48,19 +54,19 @@ interface Database {
@Transaction
@Query("SELECT * FROM Song WHERE id = :id")
fun songWithInfo(id: String): SongWithInfo?
fun songWithInfo(id: String): DetailedSong?
@Transaction
@Query("SELECT * FROM Song WHERE totalPlayTimeMs > 0 ORDER BY ROWID DESC")
fun history(): Flow<List<SongWithInfo>>
fun history(): Flow<List<DetailedSong>>
@Transaction
@Query("SELECT * FROM Song WHERE likedAt IS NOT NULL ORDER BY likedAt DESC")
fun favorites(): Flow<List<SongWithInfo>>
fun favorites(): Flow<List<DetailedSong>>
@Transaction
@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")
fun incrementTotalPlayTimeMs(id: String, addition: Long)
@ -82,7 +88,7 @@ interface Database {
fun incrementSongPositions(playlistId: Long, fromPosition: Int, toPosition: Int)
@Insert(onConflict = OnConflictStrategy.ABORT)
fun insert(songWithAuthors: SongWithAuthors): Long
fun insert(songWithAuthors: SongArtistMap): Long
@Insert(onConflict = OnConflictStrategy.ABORT)
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")
fun playlistThumbnailUrls(id: Long): Flow<List<String?>>
@Transaction
@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")
fun artistSongs(artistId: String): Flow<List<SongWithInfo>>
// @Transaction
// @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")
// fun artistSongs(artistId: String): Flow<List<DetailedSong>>
@Insert(onConflict = OnConflictStrategy.ABORT)
fun insertQueue(queuedMediaItems: List<QueuedMediaItem>)
@ -135,11 +141,9 @@ interface Database {
Song::class,
SongInPlaylist::class,
Playlist::class,
Info::class,
SongWithAuthors::class,
Album::class,
Artist::class,
SongArtistMap::class,
Album::class,
SearchQuery::class,
QueuedMediaItem::class,
],
@ -166,7 +170,7 @@ abstract class DatabaseInitializer protected constructor() : RoomDatabase() {
lateinit var Instance: DatabaseInitializer
context(Context)
operator fun invoke() {
operator fun invoke() {
if (!::Instance.isInitialized) {
Instance = Room
.databaseBuilder(this@Context, DatabaseInitializer::class.java, "data.db")
@ -183,8 +187,42 @@ abstract class DatabaseInitializer protected constructor() : RoomDatabase() {
class From7To8Migration : AutoMigrationSpec
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"
)
)
val songs: List<SongWithInfo>
val songs: List<DetailedSong>
) {
companion object {
val Empty = PlaylistWithSongs(Playlist(-1, ""), emptyList())

View file

@ -3,7 +3,16 @@ package it.vfsfitvnm.vimusic.models
import androidx.room.*
@Entity
@Entity(
// foreignKeys = [
// ForeignKey(
// entity = Album::class,
// parentColumns = ["id"],
// childColumns = ["albumId"],
// onDelete = ForeignKey.CASCADE
// ),
// ]
)
data class Song(
@PrimaryKey val id: 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.models.Playlist
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.screens.rememberArtistRoute
import it.vfsfitvnm.vimusic.ui.screens.rememberCreatePlaylistRoute
@ -32,7 +32,7 @@ import kotlinx.coroutines.Dispatchers
@ExperimentalAnimationApi
@Composable
fun InFavoritesMediaItemMenu(
song: SongWithInfo,
song: DetailedSong,
modifier: Modifier = Modifier,
onDismiss: (() -> Unit)? = null
) {
@ -51,7 +51,7 @@ fun InFavoritesMediaItemMenu(
@ExperimentalAnimationApi
@Composable
fun InHistoryMediaItemMenu(
song: SongWithInfo,
song: DetailedSong,
modifier: Modifier = Modifier,
onDismiss: (() -> Unit)? = null
) {
@ -94,7 +94,7 @@ fun InHistoryMediaItemMenu(
fun InPlaylistMediaItemMenu(
playlistId: Long,
positionInPlaylist: Int,
song: SongWithInfo,
song: DetailedSong,
modifier: Modifier = Modifier,
onDismiss: (() -> Unit)? = null
) {

View file

@ -28,7 +28,7 @@ import it.vfsfitvnm.route.RouteHandler
import it.vfsfitvnm.vimusic.Database
import it.vfsfitvnm.vimusic.LocalPlayerServiceBinder
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.TopAppBar
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.YouTube
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.withContext
@ -96,8 +97,9 @@ fun ArtistScreen(
}
val songs by remember(browseId) {
Database.artistSongs(browseId)
}.collectAsState(initial = emptyList(), context = Dispatchers.IO)
flowOf(emptyList<DetailedSong>())
// Database.artistSongs(browseId)
}.collectAsState(initial = emptyList<DetailedSong>(), context = Dispatchers.IO)
LazyColumn(
state = lazyListState,
@ -218,7 +220,7 @@ fun ArtistScreen(
modifier = Modifier
.clickable(enabled = songs.isNotEmpty()) {
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)
.size(20.dp)
@ -236,7 +238,7 @@ fun ArtistScreen(
thumbnailSize = songThumbnailSizePx,
onClick = {
binder?.stopRadio()
binder?.player?.forcePlayAtIndex(songs.map(SongWithInfo::asMediaItem), index)
binder?.player?.forcePlayAtIndex(songs.map(DetailedSong::asMediaItem), index)
},
menuContent = {
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.models.Playlist
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.ui.components.TopAppBar
import it.vfsfitvnm.vimusic.ui.components.themed.InFavoritesMediaItemMenu
@ -358,7 +358,7 @@ fun HomeScreen() {
modifier = Modifier
.clickable(enabled = songCollection.isNotEmpty()) {
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)
.size(20.dp)
@ -378,7 +378,7 @@ fun HomeScreen() {
thumbnailSize = thumbnailSize,
onClick = {
binder?.stopRadio()
binder?.player?.forcePlayAtIndex(songCollection.map(SongWithInfo::asMediaItem), index)
binder?.player?.forcePlayAtIndex(songCollection.map(DetailedSong::asMediaItem), index)
},
menuContent = {
when (preferences.homePageSongCollection) {

View file

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

View file

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

View file

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