feat(android): port cached_playlists + cached_playlist_tracks (M8 4.2 slice 3)
Two related entities mirroring flutter_client/lib/cache/db.dart:
- CachedPlaylists — one row per playlist; `systemVariant` is null for
user playlists and "for_you" / "songs_like_artist" / etc. for
system-generated mixes (used by the add-to-playlist sheet filter)
- CachedPlaylistTracks — composite-PK join table, `position` carries
ordering inside a playlist
DAO surfaces split user vs system playlists at the query layer so
ViewModels don't have to filter — observeUserPlaylists/observeSystemPlaylists.
PlaylistTrackDao gets a deleteByPlaylist for the replace-all pattern
after a sync delta lands.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -6,11 +6,15 @@ import androidx.room.TypeConverters
|
||||
import com.fabledsword.minstrel.cache.db.dao.CachedAlbumDao
|
||||
import com.fabledsword.minstrel.cache.db.dao.CachedArtistDao
|
||||
import com.fabledsword.minstrel.cache.db.dao.CachedLikeDao
|
||||
import com.fabledsword.minstrel.cache.db.dao.CachedPlaylistDao
|
||||
import com.fabledsword.minstrel.cache.db.dao.CachedPlaylistTrackDao
|
||||
import com.fabledsword.minstrel.cache.db.dao.CachedTrackDao
|
||||
import com.fabledsword.minstrel.cache.db.dao.SyncMetadataDao
|
||||
import com.fabledsword.minstrel.cache.db.entities.CachedAlbumEntity
|
||||
import com.fabledsword.minstrel.cache.db.entities.CachedArtistEntity
|
||||
import com.fabledsword.minstrel.cache.db.entities.CachedLikeEntity
|
||||
import com.fabledsword.minstrel.cache.db.entities.CachedPlaylistEntity
|
||||
import com.fabledsword.minstrel.cache.db.entities.CachedPlaylistTrackEntity
|
||||
import com.fabledsword.minstrel.cache.db.entities.CachedTrackEntity
|
||||
import com.fabledsword.minstrel.cache.db.entities.SyncMetadataEntity
|
||||
|
||||
@@ -34,6 +38,8 @@ import com.fabledsword.minstrel.cache.db.entities.SyncMetadataEntity
|
||||
CachedAlbumEntity::class,
|
||||
CachedTrackEntity::class,
|
||||
CachedLikeEntity::class,
|
||||
CachedPlaylistEntity::class,
|
||||
CachedPlaylistTrackEntity::class,
|
||||
],
|
||||
version = 1,
|
||||
exportSchema = true,
|
||||
@@ -45,4 +51,6 @@ abstract class AppDatabase : RoomDatabase() {
|
||||
abstract fun cachedAlbumDao(): CachedAlbumDao
|
||||
abstract fun cachedTrackDao(): CachedTrackDao
|
||||
abstract fun cachedLikeDao(): CachedLikeDao
|
||||
abstract fun cachedPlaylistDao(): CachedPlaylistDao
|
||||
abstract fun cachedPlaylistTrackDao(): CachedPlaylistTrackDao
|
||||
}
|
||||
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
package com.fabledsword.minstrel.cache.db.dao
|
||||
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Insert
|
||||
import androidx.room.OnConflictStrategy
|
||||
import androidx.room.Query
|
||||
import com.fabledsword.minstrel.cache.db.entities.CachedPlaylistEntity
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
@Dao
|
||||
interface CachedPlaylistDao {
|
||||
/** All playlists, user + system, sorted by name. */
|
||||
@Query("SELECT * FROM cached_playlists ORDER BY name COLLATE NOCASE ASC")
|
||||
fun observeAll(): Flow<List<CachedPlaylistEntity>>
|
||||
|
||||
/** User playlists only (systemVariant IS NULL) — for the add-to-playlist sheet. */
|
||||
@Query(
|
||||
"SELECT * FROM cached_playlists " +
|
||||
"WHERE systemVariant IS NULL ORDER BY name COLLATE NOCASE ASC",
|
||||
)
|
||||
fun observeUserPlaylists(): Flow<List<CachedPlaylistEntity>>
|
||||
|
||||
/** System playlists for the home screen. */
|
||||
@Query(
|
||||
"SELECT * FROM cached_playlists " +
|
||||
"WHERE systemVariant IS NOT NULL ORDER BY name COLLATE NOCASE ASC",
|
||||
)
|
||||
fun observeSystemPlaylists(): Flow<List<CachedPlaylistEntity>>
|
||||
|
||||
@Query("SELECT * FROM cached_playlists WHERE id = :id")
|
||||
suspend fun getById(id: String): CachedPlaylistEntity?
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
suspend fun upsertAll(rows: List<CachedPlaylistEntity>)
|
||||
|
||||
@Query("DELETE FROM cached_playlists WHERE id IN (:ids)")
|
||||
suspend fun deleteByIds(ids: List<String>)
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
package com.fabledsword.minstrel.cache.db.dao
|
||||
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Insert
|
||||
import androidx.room.OnConflictStrategy
|
||||
import androidx.room.Query
|
||||
import com.fabledsword.minstrel.cache.db.entities.CachedPlaylistTrackEntity
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
@Dao
|
||||
interface CachedPlaylistTrackDao {
|
||||
@Query(
|
||||
"SELECT * FROM cached_playlist_tracks " +
|
||||
"WHERE playlistId = :playlistId ORDER BY position ASC",
|
||||
)
|
||||
fun observeByPlaylist(playlistId: String): Flow<List<CachedPlaylistTrackEntity>>
|
||||
|
||||
@Query(
|
||||
"SELECT * FROM cached_playlist_tracks " +
|
||||
"WHERE playlistId = :playlistId ORDER BY position ASC",
|
||||
)
|
||||
suspend fun getByPlaylist(playlistId: String): List<CachedPlaylistTrackEntity>
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
suspend fun upsertAll(rows: List<CachedPlaylistTrackEntity>)
|
||||
|
||||
/** Replace-all pattern for a playlist; called after a sync delta lands. */
|
||||
@Query("DELETE FROM cached_playlist_tracks WHERE playlistId = :playlistId")
|
||||
suspend fun deleteByPlaylist(playlistId: String)
|
||||
|
||||
@Query(
|
||||
"DELETE FROM cached_playlist_tracks " +
|
||||
"WHERE playlistId = :playlistId AND trackId IN (:trackIds)",
|
||||
)
|
||||
suspend fun deleteTracksFromPlaylist(playlistId: String, trackIds: List<String>)
|
||||
}
|
||||
Vendored
+29
@@ -0,0 +1,29 @@
|
||||
package com.fabledsword.minstrel.cache.db.entities
|
||||
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
import kotlinx.datetime.Clock
|
||||
import kotlinx.datetime.Instant
|
||||
|
||||
/**
|
||||
* Cache row for one playlist (user or system). Mirrors
|
||||
* `flutter_client/lib/cache/db.dart`'s `CachedPlaylists` Drift table.
|
||||
*
|
||||
* `systemVariant` is null for user playlists and one of
|
||||
* "for_you" / "songs_like_artist" / "discover" / "todays_mix" / etc.
|
||||
* for system-generated mixes. Lets the add-to-playlist sheet filter
|
||||
* system playlists locally without a REST round-trip.
|
||||
*/
|
||||
@Entity(tableName = "cached_playlists")
|
||||
data class CachedPlaylistEntity(
|
||||
@PrimaryKey val id: String,
|
||||
val userId: String,
|
||||
val name: String,
|
||||
val description: String = "",
|
||||
val isPublic: Boolean = false,
|
||||
val coverPath: String? = null,
|
||||
val trackCount: Int = 0,
|
||||
val durationSec: Int = 0,
|
||||
val systemVariant: String? = null,
|
||||
val fetchedAt: Instant = Clock.System.now(),
|
||||
)
|
||||
Vendored
+19
@@ -0,0 +1,19 @@
|
||||
package com.fabledsword.minstrel.cache.db.entities
|
||||
|
||||
import androidx.room.Entity
|
||||
|
||||
/**
|
||||
* Ordered membership of tracks within a playlist. Mirrors
|
||||
* `flutter_client/lib/cache/db.dart`'s `CachedPlaylistTracks` Drift table.
|
||||
* Composite PK so the same track can only appear once per playlist;
|
||||
* `position` carries the ordering.
|
||||
*/
|
||||
@Entity(
|
||||
tableName = "cached_playlist_tracks",
|
||||
primaryKeys = ["playlistId", "trackId"],
|
||||
)
|
||||
data class CachedPlaylistTrackEntity(
|
||||
val playlistId: String,
|
||||
val trackId: String,
|
||||
val position: Int = 0,
|
||||
)
|
||||
Reference in New Issue
Block a user