feat(android): TrackActions hidden-state reacts to SSE (audit v3 Bug-9/§4.12)
TrackActionsViewModel fetched the hidden-track snapshot only at init and after local flag/unflag, so a hide/unhide from another device left the sheet's Hide/Unhide label stale until the VM was recreated. Now it subscribes to EventsStream and refreshes on any quarantine.* event (flagged / unflagged / resolved / file_deleted / deleted_via_lidarr). Also routed the radio / append-to-playlist / hide / unhide failure snackbars through ErrorCopy.fromThrowable instead of raw it.message, keeping the action-context prefix. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
+21
-7
@@ -2,6 +2,8 @@ package com.fabledsword.minstrel.shared.widgets.trackactions
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.fabledsword.minstrel.api.ErrorCopy
|
||||
import com.fabledsword.minstrel.events.EventsStream
|
||||
import com.fabledsword.minstrel.likes.data.LikesRepository
|
||||
import com.fabledsword.minstrel.models.TrackRef
|
||||
import com.fabledsword.minstrel.player.PlayerController
|
||||
@@ -15,6 +17,7 @@ import kotlinx.coroutines.flow.SharedFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asSharedFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.filter
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
@@ -31,9 +34,10 @@ import javax.inject.Inject
|
||||
* Hidden-state observation: [QuarantineRepository] doesn't yet expose
|
||||
* a Flow for the caller's quarantine list (no Room cache; see the
|
||||
* repository's class comment), so this VM holds its own snapshot of
|
||||
* the hidden track-id set. Refreshed on init and after every flag /
|
||||
* unflag action so the sheet reflects the current state next time
|
||||
* it opens for the same track.
|
||||
* the hidden track-id set. Refreshed on init, after every flag /
|
||||
* unflag action, AND on any `quarantine.*` SSE event so a hide/unhide
|
||||
* from another device updates the sheet's Hide/Unhide label without
|
||||
* a manual reload.
|
||||
*/
|
||||
@HiltViewModel
|
||||
class TrackActionsViewModel @Inject constructor(
|
||||
@@ -41,6 +45,7 @@ class TrackActionsViewModel @Inject constructor(
|
||||
private val quarantine: QuarantineRepository,
|
||||
private val player: PlayerController,
|
||||
private val playlists: PlaylistsRepository,
|
||||
private val eventsStream: EventsStream,
|
||||
) : ViewModel() {
|
||||
|
||||
private val messages = MutableSharedFlow<String>(extraBufferCapacity = 4)
|
||||
@@ -51,6 +56,11 @@ class TrackActionsViewModel @Inject constructor(
|
||||
|
||||
init {
|
||||
refreshHidden()
|
||||
viewModelScope.launch {
|
||||
eventsStream.events
|
||||
.filter { it.kind.startsWith("quarantine.") }
|
||||
.collect { refreshHidden() }
|
||||
}
|
||||
}
|
||||
|
||||
fun isLikedFlow(trackId: String): Flow<Boolean> =
|
||||
@@ -84,7 +94,9 @@ class TrackActionsViewModel @Inject constructor(
|
||||
fun startRadio(trackId: String) {
|
||||
viewModelScope.launch {
|
||||
runCatching { player.startRadio(trackId) }
|
||||
.onFailure { messages.tryEmit("Couldn't start radio: ${it.message ?: "unknown error"}") }
|
||||
.onFailure {
|
||||
messages.tryEmit("Couldn't start radio: ${ErrorCopy.fromThrowable(it)}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,7 +104,9 @@ class TrackActionsViewModel @Inject constructor(
|
||||
viewModelScope.launch {
|
||||
runCatching { playlists.appendTrack(playlistId, trackId) }
|
||||
.onSuccess { messages.tryEmit("Added to playlist") }
|
||||
.onFailure { messages.tryEmit("Couldn't add to playlist: ${it.message ?: "unknown error"}") }
|
||||
.onFailure {
|
||||
messages.tryEmit("Couldn't add to playlist: ${ErrorCopy.fromThrowable(it)}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,7 +114,7 @@ class TrackActionsViewModel @Inject constructor(
|
||||
viewModelScope.launch {
|
||||
runCatching { quarantine.flag(track.id, reason, notes) }
|
||||
.onSuccess { refreshHidden() }
|
||||
.onFailure { messages.tryEmit("Couldn't hide: ${it.message ?: "unknown error"}") }
|
||||
.onFailure { messages.tryEmit("Couldn't hide: ${ErrorCopy.fromThrowable(it)}") }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -108,7 +122,7 @@ class TrackActionsViewModel @Inject constructor(
|
||||
viewModelScope.launch {
|
||||
runCatching { quarantine.unflag(trackId) }
|
||||
.onSuccess { refreshHidden() }
|
||||
.onFailure { messages.tryEmit("Couldn't unhide: ${it.message ?: "unknown error"}") }
|
||||
.onFailure { messages.tryEmit("Couldn't unhide: ${ErrorCopy.fromThrowable(it)}") }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user