From e42f2bf5255381c0b307f0acd23b2a7739c2d629 Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Fri, 22 May 2026 16:40:37 -0400 Subject: [PATCH] feat(android): port cached_home_index (M8 4.2 slice 9) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Mirrors flutter_client/lib/cache/db.dart's CachedHomeIndex — per-item rows that drive the Home screen sections (Recently Added Albums, Rediscover Albums/Artists, Most Played Tracks, Last Played Artists). Composite PK (section, position) — exactly one row per slot per section; sync replaces in-place via upsert. entityType ("album" / "artist" / "track") dispatches per-tile hydration to the right per-entity endpoint when the Home screen renders. DAO surface fits the sync flow: - observeBySection (Flow) for the Home composables - getBySection (suspend) for one-shot sync reads - upsertAll for sync writes - deleteBySection for replace-all on a section sync Co-Authored-By: Claude Opus 4.7 (1M context) --- .../minstrel/cache/db/AppDatabase.kt | 4 +++ .../cache/db/dao/CachedHomeIndexDao.kt | 33 ++++++++++++++++++ .../db/entities/CachedHomeIndexEntity.kt | 34 +++++++++++++++++++ 3 files changed, 71 insertions(+) create mode 100644 android/app/src/main/java/com/fabledsword/minstrel/cache/db/dao/CachedHomeIndexDao.kt create mode 100644 android/app/src/main/java/com/fabledsword/minstrel/cache/db/entities/CachedHomeIndexEntity.kt diff --git a/android/app/src/main/java/com/fabledsword/minstrel/cache/db/AppDatabase.kt b/android/app/src/main/java/com/fabledsword/minstrel/cache/db/AppDatabase.kt index f78325a9..a55f590f 100644 --- a/android/app/src/main/java/com/fabledsword/minstrel/cache/db/AppDatabase.kt +++ b/android/app/src/main/java/com/fabledsword/minstrel/cache/db/AppDatabase.kt @@ -6,6 +6,7 @@ import androidx.room.TypeConverters import com.fabledsword.minstrel.cache.db.dao.AudioCacheIndexDao import com.fabledsword.minstrel.cache.db.dao.CachedAlbumDao import com.fabledsword.minstrel.cache.db.dao.CachedArtistDao +import com.fabledsword.minstrel.cache.db.dao.CachedHomeIndexDao import com.fabledsword.minstrel.cache.db.dao.CachedLikeDao import com.fabledsword.minstrel.cache.db.dao.CachedMutationDao import com.fabledsword.minstrel.cache.db.dao.CachedPlaylistDao @@ -17,6 +18,7 @@ import com.fabledsword.minstrel.cache.db.dao.SyncMetadataDao import com.fabledsword.minstrel.cache.db.entities.AudioCacheIndexEntity import com.fabledsword.minstrel.cache.db.entities.CachedAlbumEntity import com.fabledsword.minstrel.cache.db.entities.CachedArtistEntity +import com.fabledsword.minstrel.cache.db.entities.CachedHomeIndexEntity import com.fabledsword.minstrel.cache.db.entities.CachedLikeEntity import com.fabledsword.minstrel.cache.db.entities.CachedMutationEntity import com.fabledsword.minstrel.cache.db.entities.CachedPlaylistEntity @@ -52,6 +54,7 @@ import com.fabledsword.minstrel.cache.db.entities.SyncMetadataEntity AudioCacheIndexEntity::class, CachedMutationEntity::class, CachedResumeStateEntity::class, + CachedHomeIndexEntity::class, ], version = 1, exportSchema = true, @@ -69,4 +72,5 @@ abstract class AppDatabase : RoomDatabase() { abstract fun audioCacheIndexDao(): AudioCacheIndexDao abstract fun cachedMutationDao(): CachedMutationDao abstract fun cachedResumeStateDao(): CachedResumeStateDao + abstract fun cachedHomeIndexDao(): CachedHomeIndexDao } diff --git a/android/app/src/main/java/com/fabledsword/minstrel/cache/db/dao/CachedHomeIndexDao.kt b/android/app/src/main/java/com/fabledsword/minstrel/cache/db/dao/CachedHomeIndexDao.kt new file mode 100644 index 00000000..aea08ff3 --- /dev/null +++ b/android/app/src/main/java/com/fabledsword/minstrel/cache/db/dao/CachedHomeIndexDao.kt @@ -0,0 +1,33 @@ +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.CachedHomeIndexEntity +import kotlinx.coroutines.flow.Flow + +@Dao +interface CachedHomeIndexDao { + @Query( + "SELECT * FROM cached_home_index " + + "WHERE section = :section ORDER BY position ASC", + ) + fun observeBySection(section: String): Flow> + + @Query( + "SELECT * FROM cached_home_index " + + "WHERE section = :section ORDER BY position ASC", + ) + suspend fun getBySection(section: String): List + + @Insert(onConflict = OnConflictStrategy.REPLACE) + suspend fun upsertAll(rows: List) + + /** Replace-all pattern; sync wipes a section then re-inserts. */ + @Query("DELETE FROM cached_home_index WHERE section = :section") + suspend fun deleteBySection(section: String) + + @Query("DELETE FROM cached_home_index") + suspend fun clear() +} diff --git a/android/app/src/main/java/com/fabledsword/minstrel/cache/db/entities/CachedHomeIndexEntity.kt b/android/app/src/main/java/com/fabledsword/minstrel/cache/db/entities/CachedHomeIndexEntity.kt new file mode 100644 index 00000000..70d5e11c --- /dev/null +++ b/android/app/src/main/java/com/fabledsword/minstrel/cache/db/entities/CachedHomeIndexEntity.kt @@ -0,0 +1,34 @@ +package com.fabledsword.minstrel.cache.db.entities + +import androidx.room.Entity +import kotlinx.datetime.Clock +import kotlinx.datetime.Instant + +/** + * Per-item row driving the Home screen sections. Mirrors + * `flutter_client/lib/cache/db.dart`'s `CachedHomeIndex` Drift table. + * + * `section` is one of (matching /api/home keys): + * - "recently_added_albums" + * - "rediscover_albums" + * - "rediscover_artists" + * - "most_played_tracks" + * - "last_played_artists" + * + * `entityType` is "album" | "artist" | "track" — dispatches hydration + * to the right per-entity endpoint when a tile is rendered. + * + * The composite PK (section, position) means there's exactly one row + * per slot per section; sync replaces in-place by upsert. + */ +@Entity( + tableName = "cached_home_index", + primaryKeys = ["section", "position"], +) +data class CachedHomeIndexEntity( + val section: String, + val position: Int, + val entityType: String, + val entityId: String, + val fetchedAt: Instant = Clock.System.now(), +)