AlbumInfoViewModel.kt
Each line, with the release that last changed it.
| 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 | } |


