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
Showing only changes of commit 851d5f92bb - Show all commits
@@ -3,6 +3,7 @@ package com.fabledsword.minstrel.quarantine.ui
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.fabledsword.minstrel.api.ErrorCopy
import com.fabledsword.minstrel.events.EventsStream
import com.fabledsword.minstrel.models.QuarantineRef
import com.fabledsword.minstrel.quarantine.data.QuarantineRepository
import dagger.hilt.android.lifecycle.HiltViewModel
@@ -10,6 +11,7 @@ import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.launch
import javax.inject.Inject
@@ -23,6 +25,7 @@ sealed interface HiddenTabUiState {
@HiltViewModel
class HiddenTabViewModel @Inject constructor(
private val repository: QuarantineRepository,
private val eventsStream: EventsStream,
) : ViewModel() {
private val internal = MutableStateFlow<HiddenTabUiState>(HiddenTabUiState.Loading)
@@ -30,6 +33,17 @@ class HiddenTabViewModel @Inject constructor(
init {
refresh()
// Live cross-device updates: any quarantine.* event (hide /
// unhide / resolve / delete from another device) re-fetches the
// list while the tab is open. Mirrors Flutter's LiveEventsDispatcher
// invalidating myQuarantineProvider; here the screen-scoped VM
// subscribes directly, per the Android dispatcher's documented
// pattern for state with no shared repository-refresh.
viewModelScope.launch {
eventsStream.events
.filter { it.kind.startsWith("quarantine.") }
.collect { refresh() }
}
}
fun refresh(): Job = viewModelScope.launch {