feat(android): port cached_likes (M8 4.2 slice 2)
Mirrors flutter_client/lib/cache/db.dart's CachedLikes Drift table —
composite PK (userId, entityType, entityId) so the same user can
like a track and its album and its artist independently. entityType
is a plain string ("track" | "album" | "artist") for parity with the
Drift schema and the server wire format.
DAO surface tailored to consumers we know are coming:
- observeLikedTrackIds(userId): Flow<List<String>> — audio-cache
eviction reads this set to identify "liked" bucket members
- observeLikedIdsOfType(userId, entityType): generalized variant
- observeIsLiked(...): scalar Flow for LikeButton composables
- upsertAll (sync writes)
- delete (mutation queue → toggle off)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -5,10 +5,12 @@ import androidx.room.RoomDatabase
|
||||
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.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.CachedTrackEntity
|
||||
import com.fabledsword.minstrel.cache.db.entities.SyncMetadataEntity
|
||||
|
||||
@@ -31,6 +33,7 @@ import com.fabledsword.minstrel.cache.db.entities.SyncMetadataEntity
|
||||
CachedArtistEntity::class,
|
||||
CachedAlbumEntity::class,
|
||||
CachedTrackEntity::class,
|
||||
CachedLikeEntity::class,
|
||||
],
|
||||
version = 1,
|
||||
exportSchema = true,
|
||||
@@ -41,4 +44,5 @@ abstract class AppDatabase : RoomDatabase() {
|
||||
abstract fun cachedArtistDao(): CachedArtistDao
|
||||
abstract fun cachedAlbumDao(): CachedAlbumDao
|
||||
abstract fun cachedTrackDao(): CachedTrackDao
|
||||
abstract fun cachedLikeDao(): CachedLikeDao
|
||||
}
|
||||
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
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.CachedLikeEntity
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
@Dao
|
||||
interface CachedLikeDao {
|
||||
/** All track IDs the user has liked. Audio-cache eviction uses this set. */
|
||||
@Query(
|
||||
"SELECT entityId FROM cached_likes " +
|
||||
"WHERE userId = :userId AND entityType = 'track'",
|
||||
)
|
||||
fun observeLikedTrackIds(userId: String): Flow<List<String>>
|
||||
|
||||
@Query(
|
||||
"SELECT entityId FROM cached_likes " +
|
||||
"WHERE userId = :userId AND entityType = :entityType",
|
||||
)
|
||||
fun observeLikedIdsOfType(userId: String, entityType: String): Flow<List<String>>
|
||||
|
||||
@Query(
|
||||
"SELECT EXISTS(SELECT 1 FROM cached_likes " +
|
||||
"WHERE userId = :userId AND entityType = :entityType AND entityId = :entityId)",
|
||||
)
|
||||
fun observeIsLiked(userId: String, entityType: String, entityId: String): Flow<Boolean>
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
suspend fun upsertAll(rows: List<CachedLikeEntity>)
|
||||
|
||||
@Query(
|
||||
"DELETE FROM cached_likes " +
|
||||
"WHERE userId = :userId AND entityType = :entityType AND entityId = :entityId",
|
||||
)
|
||||
suspend fun delete(userId: String, entityType: String, entityId: String)
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
package com.fabledsword.minstrel.cache.db.entities
|
||||
|
||||
import androidx.room.Entity
|
||||
import kotlinx.datetime.Clock
|
||||
import kotlinx.datetime.Instant
|
||||
|
||||
/**
|
||||
* Like membership row. Mirrors `flutter_client/lib/cache/db.dart`'s
|
||||
* `CachedLikes` Drift table. Composite primary key — one user may
|
||||
* independently like a track AND its album AND its artist; rows are
|
||||
* disambiguated by the (userId, entityType, entityId) triple.
|
||||
*
|
||||
* `entityType` is one of "track" | "album" | "artist". Stored as a
|
||||
* plain string for parity with the Drift schema and the server's wire
|
||||
* format; an enum doesn't earn its keep here.
|
||||
*/
|
||||
@Entity(
|
||||
tableName = "cached_likes",
|
||||
primaryKeys = ["userId", "entityType", "entityId"],
|
||||
)
|
||||
data class CachedLikeEntity(
|
||||
val userId: String,
|
||||
val entityType: String,
|
||||
val entityId: String,
|
||||
val likedAt: Instant = Clock.System.now(),
|
||||
)
|
||||
Reference in New Issue
Block a user