AlbumInfoViewModel.kt
62 lines · 2.3 KB
Changed in: 0.5.0
| 1 | package dev.stave.ui.albuminfo |
| 2 | |
| 3 | import androidx.lifecycle.ViewModel |
| 4 | import dev.stave.domain.model.DidlItem |
| 5 | import dev.stave.ui.safeLaunch |
| 6 | import dev.stave.upnp.AlbumInfoLoader |
| 7 | import kotlinx.coroutines.CancellationException |
| 8 | import kotlinx.coroutines.Job |
| 9 | import kotlinx.coroutines.flow.MutableStateFlow |
| 10 | import kotlinx.coroutines.flow.StateFlow |
| 11 | import kotlinx.coroutines.flow.asStateFlow |
| 12 | |
| 13 | /** Album-notes state. [Idle] before the user asks, [Loading] while we look, |
| 14 | * [Loaded] with the prose, [Empty] when no source had anything (no comment |
| 15 | * tag, no sibling .txt). Same shape as Android's DetailViewModel.AlbumInfoState. */ |
| 16 | sealed interface AlbumInfoState { |
| 17 | data object Idle : AlbumInfoState |
| 18 | data object Loading : AlbumInfoState |
| 19 | data class Loaded(val text: String) : AlbumInfoState |
| 20 | data object Empty : AlbumInfoState |
| 21 | } |
| 22 | |
| 23 | /** |
| 24 | * Drives the "Album notes" dialog (port of Android's DetailViewModel |
| 25 | * loadAlbumInfo / resetAlbumInfo). Every path that sets [AlbumInfoState.Loading] |
| 26 | * ends in Loaded or Empty, including on an unexpected throw, so the dialog can't |
| 27 | * spin forever. |
| 28 | */ |
| 29 | class AlbumInfoViewModel( |
| 30 | private val loader: AlbumInfoLoader, |
| 31 | ) : ViewModel() { |
| 32 | |
| 33 | private val _state = MutableStateFlow<AlbumInfoState>(AlbumInfoState.Idle) |
| 34 | val state: StateFlow<AlbumInfoState> = _state.asStateFlow() |
| 35 | |
| 36 | private var job: Job? = null |
| 37 | |
| 38 | /** Look up notes for the album [track] belongs to. Replaces any lookup in |
| 39 | * flight (e.g. the user reopened the dialog after the track changed). */ |
| 40 | fun load(track: DidlItem.Track) { |
| 41 | job?.cancel() |
| 42 | _state.value = AlbumInfoState.Loading |
| 43 | job = safeLaunch { |
| 44 | val text = try { |
| 45 | loader.load(track.resourceUrl) |
| 46 | } catch (c: CancellationException) { |
| 47 | throw c |
| 48 | } catch (e: Exception) { |
| 49 | System.err.println("[Earhorn] album notes lookup failed: ${e.javaClass.simpleName}: ${e.message}") |
| 50 | null |
| 51 | } |
| 52 | _state.value = if (text.isNullOrBlank()) AlbumInfoState.Empty else AlbumInfoState.Loaded(text) |
| 53 | } |
| 54 | } |
| 55 | |
| 56 | /** Back to Idle when the dialog closes; cancels a lookup in flight. */ |
| 57 | fun reset() { |
| 58 | job?.cancel() |
| 59 | job = null |
| 60 | _state.value = AlbumInfoState.Idle |
| 61 | } |
| 62 | } |


