From 851d5f92bbdfb16f930734ff8c26f4d528734aa9 Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Thu, 28 May 2026 08:19:31 -0400 Subject: [PATCH] feat(android): HiddenTab live-refreshes on quarantine.* SSE (parity map) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Flutter's _LiveEventsDispatcher invalidates myQuarantineProvider on any quarantine.* event, and the Hidden tab watches that provider — so a hide/unhide/delete from another device updates the open Hidden tab live, not just on re-open. Android's HiddenTab is fetch-on-visit with no shared reactive cache, so per the Android LiveEventsDispatcher's own documented pattern (screen-scoped state subscribes to EventsStream directly), HiddenTabViewModel now collects quarantine.* and refreshes. Same user-visible behavior as Flutter; same shape as the TrackActionsViewModel SSE wiring. (Paint-from-disk offline cache for the Hidden tab — Flutter's drift cached_quarantine_mine — remains the separate deferred Phase-13 work.) Co-Authored-By: Claude Opus 4.7 (1M context) --- .../minstrel/quarantine/ui/HiddenTabViewModel.kt | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/android/app/src/main/java/com/fabledsword/minstrel/quarantine/ui/HiddenTabViewModel.kt b/android/app/src/main/java/com/fabledsword/minstrel/quarantine/ui/HiddenTabViewModel.kt index ffa466e0..1573966a 100644 --- a/android/app/src/main/java/com/fabledsword/minstrel/quarantine/ui/HiddenTabViewModel.kt +++ b/android/app/src/main/java/com/fabledsword/minstrel/quarantine/ui/HiddenTabViewModel.kt @@ -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.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 {