feat: M3 weighted shuffle v1 — real /api/radio with scoring #21

Merged
bvandeusen merged 262 commits from dev into main 2026-04-27 11:00:29 -04:00
3 changed files with 71 additions and 0 deletions
Showing only changes of commit e42f2bf525 - Show all commits
@@ -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
}
@@ -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<List<CachedHomeIndexEntity>>
@Query(
"SELECT * FROM cached_home_index " +
"WHERE section = :section ORDER BY position ASC",
)
suspend fun getBySection(section: String): List<CachedHomeIndexEntity>
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun upsertAll(rows: List<CachedHomeIndexEntity>)
/** 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()
}
@@ -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(),
)