diff --git a/android/app/src/main/java/com/fabledsword/minstrel/admin/ui/AdminInvitesViewModel.kt b/android/app/src/main/java/com/fabledsword/minstrel/admin/ui/AdminInvitesViewModel.kt index 7f43f3c4..5a70ae3a 100644 --- a/android/app/src/main/java/com/fabledsword/minstrel/admin/ui/AdminInvitesViewModel.kt +++ b/android/app/src/main/java/com/fabledsword/minstrel/admin/ui/AdminInvitesViewModel.kt @@ -3,6 +3,7 @@ package com.fabledsword.minstrel.admin.ui import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.fabledsword.minstrel.admin.data.AdminInvitesRepository +import com.fabledsword.minstrel.api.ErrorCopy import com.fabledsword.minstrel.models.Invite import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.channels.Channel @@ -49,7 +50,7 @@ class AdminInvitesViewModel @Inject constructor( internal.update { it.copy( isLoading = false, - message = "Couldn't load invites: ${e.message ?: "unknown error"}", + message = ErrorCopy.fromThrowable(e), ) } } @@ -67,7 +68,7 @@ class AdminInvitesViewModel @Inject constructor( @Suppress("TooGenericExceptionCaught") e: Throwable, ) { internal.update { - it.copy(message = "Couldn't generate invite: ${e.message ?: "unknown error"}") + it.copy(message = ErrorCopy.fromThrowable(e)) } } } diff --git a/android/app/src/main/java/com/fabledsword/minstrel/admin/ui/AdminLandingScreen.kt b/android/app/src/main/java/com/fabledsword/minstrel/admin/ui/AdminLandingScreen.kt index be303cd2..917e7232 100644 --- a/android/app/src/main/java/com/fabledsword/minstrel/admin/ui/AdminLandingScreen.kt +++ b/android/app/src/main/java/com/fabledsword/minstrel/admin/ui/AdminLandingScreen.kt @@ -37,6 +37,7 @@ import com.composables.icons.lucide.Users import com.fabledsword.minstrel.admin.data.AdminQuarantineRepository import com.fabledsword.minstrel.admin.data.AdminRequestsRepository import com.fabledsword.minstrel.admin.data.AdminUsersRepository +import com.fabledsword.minstrel.api.ErrorCopy import com.fabledsword.minstrel.nav.Admin import com.fabledsword.minstrel.nav.AdminQuarantine import com.fabledsword.minstrel.nav.AdminRequests @@ -95,7 +96,7 @@ class AdminLandingViewModel @Inject constructor( @Suppress("TooGenericExceptionCaught") e: Throwable, ) { internal.value = AdminLandingUiState.Error( - e.message ?: "Admin counts load failed", + ErrorCopy.fromThrowable(e), ) } } diff --git a/android/app/src/main/java/com/fabledsword/minstrel/admin/ui/AdminQuarantineViewModel.kt b/android/app/src/main/java/com/fabledsword/minstrel/admin/ui/AdminQuarantineViewModel.kt index 04a1d3cc..f894aed8 100644 --- a/android/app/src/main/java/com/fabledsword/minstrel/admin/ui/AdminQuarantineViewModel.kt +++ b/android/app/src/main/java/com/fabledsword/minstrel/admin/ui/AdminQuarantineViewModel.kt @@ -3,6 +3,7 @@ package com.fabledsword.minstrel.admin.ui import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.fabledsword.minstrel.admin.data.AdminQuarantineRepository +import com.fabledsword.minstrel.api.ErrorCopy import com.fabledsword.minstrel.events.EventsStream import com.fabledsword.minstrel.models.AdminQuarantineItemRef import dagger.hilt.android.lifecycle.HiltViewModel @@ -52,7 +53,7 @@ class AdminQuarantineViewModel @Inject constructor( @Suppress("TooGenericExceptionCaught") e: Throwable, ) { internal.value = AdminQuarantineUiState.Error( - e.message ?: "Admin quarantine load failed", + ErrorCopy.fromThrowable(e), ) } } diff --git a/android/app/src/main/java/com/fabledsword/minstrel/admin/ui/AdminRequestsViewModel.kt b/android/app/src/main/java/com/fabledsword/minstrel/admin/ui/AdminRequestsViewModel.kt index 3e3ba216..6ac37758 100644 --- a/android/app/src/main/java/com/fabledsword/minstrel/admin/ui/AdminRequestsViewModel.kt +++ b/android/app/src/main/java/com/fabledsword/minstrel/admin/ui/AdminRequestsViewModel.kt @@ -4,6 +4,7 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.fabledsword.minstrel.admin.data.AdminRequestsRepository import com.fabledsword.minstrel.admin.data.AdminUsersRepository +import com.fabledsword.minstrel.api.ErrorCopy import com.fabledsword.minstrel.events.EventsStream import com.fabledsword.minstrel.models.RequestRef import dagger.hilt.android.lifecycle.HiltViewModel @@ -69,7 +70,7 @@ class AdminRequestsViewModel @Inject constructor( @Suppress("TooGenericExceptionCaught") e: Throwable, ) { internal.value = AdminRequestsUiState.Error( - e.message ?: "Admin requests load failed", + ErrorCopy.fromThrowable(e), ) } } diff --git a/android/app/src/main/java/com/fabledsword/minstrel/admin/ui/AdminUsersViewModel.kt b/android/app/src/main/java/com/fabledsword/minstrel/admin/ui/AdminUsersViewModel.kt index db5ba657..bf17fab8 100644 --- a/android/app/src/main/java/com/fabledsword/minstrel/admin/ui/AdminUsersViewModel.kt +++ b/android/app/src/main/java/com/fabledsword/minstrel/admin/ui/AdminUsersViewModel.kt @@ -3,6 +3,7 @@ package com.fabledsword.minstrel.admin.ui import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.fabledsword.minstrel.admin.data.AdminUsersRepository +import com.fabledsword.minstrel.api.ErrorCopy import com.fabledsword.minstrel.models.AdminUserRef import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.Job @@ -43,7 +44,7 @@ class AdminUsersViewModel @Inject constructor( } catch ( @Suppress("TooGenericExceptionCaught") e: Throwable, ) { - internal.value = AdminUsersUiState.Error(e.message ?: "Users load failed") + internal.value = AdminUsersUiState.Error(ErrorCopy.fromThrowable(e)) } } diff --git a/android/app/src/main/java/com/fabledsword/minstrel/api/ErrorCopy.kt b/android/app/src/main/java/com/fabledsword/minstrel/api/ErrorCopy.kt new file mode 100644 index 00000000..caabc851 --- /dev/null +++ b/android/app/src/main/java/com/fabledsword/minstrel/api/ErrorCopy.kt @@ -0,0 +1,109 @@ +package com.fabledsword.minstrel.api + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import kotlinx.serialization.json.Json +import retrofit2.HttpException +import java.io.IOException + +/** + * Maps server error codes (and common transport failures) to + * friendly, sentence-case copy. Mirrors + * `flutter_client/assets/error-copy.json` + `error_copy.dart`. + * + * Server errors are `{"error":{"code":"...","message":"..."}}`. + * [fromThrowable] pulls the code out of a Retrofit [HttpException]'s + * error body and maps it; network failures (no connection / DNS / + * refused) map to `connection_refused`; anything unrecognised falls + * back to `unknown`. + * + * Replaces the `e.message ?: "unknown error"` pattern scattered + * across ViewModels with copy a user can actually act on. + */ +object ErrorCopy { + private val json = Json { ignoreUnknownKeys = true } + + @Serializable + private data class Envelope(val error: Body? = null) + + @Serializable + private data class Body(val code: String = "", val message: String = "") + + /** Friendly copy for a known error [code], or the `unknown` fallback. */ + fun messageFor(code: String): String = TABLE[code] ?: TABLE.getValue("unknown") + + /** + * Friendly copy for any caught throwable. Parses Retrofit + * HttpException bodies for the server code; treats IOExceptions + * as connection failures. + */ + fun fromThrowable(t: Throwable): String = when (t) { + is HttpException -> messageFor(codeFromHttp(t)) + is IOException -> messageFor("connection_refused") + else -> TABLE.getValue("unknown") + } + + private fun codeFromHttp(e: HttpException): String { + val raw = runCatching { e.response()?.errorBody()?.string() }.getOrNull() + ?: return "unknown" + val code = runCatching { json.decodeFromString(raw).error?.code } + .getOrNull() + .orEmpty() + return code.ifEmpty { "unknown" } + } + + private val TABLE: Map = mapOf( + "unknown" to "Something went wrong.", + "unauthenticated" to "Your session has ended. Please sign in again.", + "auth_required" to "You need to sign in to do that.", + "forbidden" to "You don't have permission to do that.", + "not_authorized" to "You don't have permission to do that.", + "invalid_credentials" to "Wrong username or password.", + "wrong_password" to "Current password is incorrect.", + "password_too_short" to "Password must be at least 8 characters.", + "username_invalid" to "That username isn't valid.", + "username_taken" to "That username is already taken.", + "email_invalid" to "Enter a valid email address.", + "email_taken" to "That email is already in use.", + "invalid_token" to "That link has expired or already been used. Request a new one.", + "invite_invalid" to "That invite has expired or already been used.", + "invite_required" to "An invite is required to register on this server.", + "last_admin" to "Can't demote or delete the last admin.", + "no_email_on_file" to "No email is associated with that account.", + "not_configured" to "This integration isn't set up yet.", + "validation" to "Some fields aren't valid. Check and try again.", + "missing_fields" to "Required fields are missing.", + "missing_query" to "Search needs at least one keyword.", + "invalid_id" to "Invalid identifier.", + "invalid_body" to "Couldn't read the request.", + "bad_request" to "Invalid request.", + "bad_body" to "Couldn't read the request.", + "bad_kind" to "Invalid request type.", + "bad_paging" to "Invalid page size or offset.", + "bad_reason" to "Invalid quarantine reason.", + "mbid_required" to "An MBID is required for this lookup.", + "system_playlist_readonly" to "System playlists can't be edited directly.", + "connection_refused" to "Couldn't reach the server. Check the URL and try again.", + "lidarr_unreachable" to + "Lidarr is unreachable right now. Try again, or check Admin → Integrations.", + "lidarr_disabled" to "Lidarr integration is not enabled.", + "lidarr_auth_failed" to "Lidarr authentication failed.", + "lidarr_defaults_incomplete" to + "Lidarr is missing a default quality profile or root folder. " + + "Set them in Admin → Integrations.", + "lidarr_server_error" to "Lidarr returned an error. Check Lidarr's logs for the cause.", + "lidarr_rejected" to + "Lidarr rejected the request. Check the server logs for the field-level reason.", + "lidarr_album_lookup_failed" to + "Lidarr doesn't recognize this album. Try Resolve or Delete file instead.", + "album_mbid_missing" to "This track has no Lidarr album to remove.", + "request_not_pending" to "This request is no longer pending.", + "request_not_found" to "That request no longer exists.", + "track_not_found" to "That track no longer exists.", + "album_not_found" to "That album no longer exists.", + "artist_not_found" to "That artist no longer exists.", + "playlist_not_found" to "That playlist no longer exists.", + "user_not_found" to "That user no longer exists.", + "not_found" to "That no longer exists.", + ) +} diff --git a/android/app/src/main/java/com/fabledsword/minstrel/auth/ui/LoginViewModel.kt b/android/app/src/main/java/com/fabledsword/minstrel/auth/ui/LoginViewModel.kt index 43b15e1a..d7a01786 100644 --- a/android/app/src/main/java/com/fabledsword/minstrel/auth/ui/LoginViewModel.kt +++ b/android/app/src/main/java/com/fabledsword/minstrel/auth/ui/LoginViewModel.kt @@ -2,6 +2,7 @@ package com.fabledsword.minstrel.auth.ui import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.fabledsword.minstrel.api.ErrorCopy import com.fabledsword.minstrel.auth.AuthController import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableStateFlow @@ -49,7 +50,7 @@ class LoginViewModel @Inject constructor( internal.update { it.copy( isSubmitting = false, - errorMessage = e.message ?: "Sign-in failed", + errorMessage = ErrorCopy.fromThrowable(e), ) } } diff --git a/android/app/src/main/java/com/fabledsword/minstrel/discover/ui/DiscoverViewModel.kt b/android/app/src/main/java/com/fabledsword/minstrel/discover/ui/DiscoverViewModel.kt index d84a6637..3ec241db 100644 --- a/android/app/src/main/java/com/fabledsword/minstrel/discover/ui/DiscoverViewModel.kt +++ b/android/app/src/main/java/com/fabledsword/minstrel/discover/ui/DiscoverViewModel.kt @@ -2,6 +2,7 @@ package com.fabledsword.minstrel.discover.ui import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.fabledsword.minstrel.api.ErrorCopy import com.fabledsword.minstrel.discover.data.DiscoverRepository import com.fabledsword.minstrel.discover.data.RequestOutcome import com.fabledsword.minstrel.models.ArtistSuggestionRef @@ -78,7 +79,7 @@ class DiscoverViewModel @Inject constructor( internal.update { it.copy( suggestions = SuggestionState.Error( - e.message ?: "Suggestions failed", + ErrorCopy.fromThrowable(e), ), ) } @@ -100,7 +101,7 @@ class DiscoverViewModel @Inject constructor( @Suppress("TooGenericExceptionCaught") e: Throwable, ) { internal.update { - it.copy(results = ResultsState.Error(e.message ?: "Search failed")) + it.copy(results = ResultsState.Error(ErrorCopy.fromThrowable(e))) } } } diff --git a/android/app/src/main/java/com/fabledsword/minstrel/history/ui/HistoryTab.kt b/android/app/src/main/java/com/fabledsword/minstrel/history/ui/HistoryTab.kt index 078e132a..69efca0b 100644 --- a/android/app/src/main/java/com/fabledsword/minstrel/history/ui/HistoryTab.kt +++ b/android/app/src/main/java/com/fabledsword/minstrel/history/ui/HistoryTab.kt @@ -22,6 +22,7 @@ import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.ViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.viewModelScope +import com.fabledsword.minstrel.api.ErrorCopy import com.fabledsword.minstrel.history.data.HistoryEntry import com.fabledsword.minstrel.history.data.HistoryRepository import com.fabledsword.minstrel.models.TrackRef @@ -81,7 +82,7 @@ class HistoryTabViewModel @Inject constructor( } catch ( @Suppress("TooGenericExceptionCaught") e: Throwable, ) { - internal.value = HistoryUiState.Error(e.message ?: "History load failed") + internal.value = HistoryUiState.Error(ErrorCopy.fromThrowable(e)) } } diff --git a/android/app/src/main/java/com/fabledsword/minstrel/home/ui/HomeScreen.kt b/android/app/src/main/java/com/fabledsword/minstrel/home/ui/HomeScreen.kt index 77164cde..fec89414 100644 --- a/android/app/src/main/java/com/fabledsword/minstrel/home/ui/HomeScreen.kt +++ b/android/app/src/main/java/com/fabledsword/minstrel/home/ui/HomeScreen.kt @@ -42,6 +42,7 @@ import androidx.navigation.NavHostController import coil3.compose.AsyncImage import com.composables.icons.lucide.Lucide import com.composables.icons.lucide.Music +import com.fabledsword.minstrel.api.ErrorCopy import com.fabledsword.minstrel.home.data.HomeRepository import com.fabledsword.minstrel.library.widgets.AlbumCard import com.fabledsword.minstrel.library.widgets.ArtistCard @@ -174,7 +175,7 @@ class HomeViewModel @Inject constructor( observeHomeSections().combine(playlistsRepository.observeAll()) { sections, playlists -> val merged = sections.copy(playlists = playlists) if (merged.isAllEmpty) HomeUiState.Empty else HomeUiState.Success(merged) - }.catch { e -> emit(HomeUiState.Error(e.message ?: "Home load failed")) } + }.catch { e -> emit(HomeUiState.Error(ErrorCopy.fromThrowable(e))) } } // ─── Screen ────────────────────────────────────────────────────────── diff --git a/android/app/src/main/java/com/fabledsword/minstrel/library/ui/AlbumDetailViewModel.kt b/android/app/src/main/java/com/fabledsword/minstrel/library/ui/AlbumDetailViewModel.kt index c7daaf43..52926a69 100644 --- a/android/app/src/main/java/com/fabledsword/minstrel/library/ui/AlbumDetailViewModel.kt +++ b/android/app/src/main/java/com/fabledsword/minstrel/library/ui/AlbumDetailViewModel.kt @@ -4,6 +4,7 @@ import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import androidx.navigation.toRoute +import com.fabledsword.minstrel.api.ErrorCopy import com.fabledsword.minstrel.library.data.LibraryRepository import com.fabledsword.minstrel.likes.data.LikesRepository import com.fabledsword.minstrel.models.AlbumDetailRef @@ -80,7 +81,7 @@ class AlbumDetailViewModel @Inject constructor( } catch ( @Suppress("TooGenericExceptionCaught") e: Throwable, ) { - internal.value = AlbumDetailUiState.Error(e.message ?: "Album load failed") + internal.value = AlbumDetailUiState.Error(ErrorCopy.fromThrowable(e)) } } diff --git a/android/app/src/main/java/com/fabledsword/minstrel/library/ui/ArtistDetailViewModel.kt b/android/app/src/main/java/com/fabledsword/minstrel/library/ui/ArtistDetailViewModel.kt index 22e73e4c..9355c670 100644 --- a/android/app/src/main/java/com/fabledsword/minstrel/library/ui/ArtistDetailViewModel.kt +++ b/android/app/src/main/java/com/fabledsword/minstrel/library/ui/ArtistDetailViewModel.kt @@ -4,6 +4,7 @@ import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import androidx.navigation.toRoute +import com.fabledsword.minstrel.api.ErrorCopy import com.fabledsword.minstrel.library.data.LibraryRepository import com.fabledsword.minstrel.likes.data.LikesRepository import com.fabledsword.minstrel.models.ArtistDetailRef @@ -84,7 +85,7 @@ class ArtistDetailViewModel @Inject constructor( } catch ( @Suppress("TooGenericExceptionCaught") e: Throwable, ) { - internal.value = ArtistDetailUiState.Error(e.message ?: "Artist load failed") + internal.value = ArtistDetailUiState.Error(ErrorCopy.fromThrowable(e)) } } @@ -112,7 +113,7 @@ class ArtistDetailViewModel @Inject constructor( @Suppress("TooGenericExceptionCaught") e: Throwable, ) { transientMessagesChannel.trySend( - "Couldn't start playback: ${e.message ?: "unknown error"}", + "Couldn't start playback: ${ErrorCopy.fromThrowable(e)}", ) } } diff --git a/android/app/src/main/java/com/fabledsword/minstrel/library/ui/LibraryViewModel.kt b/android/app/src/main/java/com/fabledsword/minstrel/library/ui/LibraryViewModel.kt index 9fde4d50..a10b0140 100644 --- a/android/app/src/main/java/com/fabledsword/minstrel/library/ui/LibraryViewModel.kt +++ b/android/app/src/main/java/com/fabledsword/minstrel/library/ui/LibraryViewModel.kt @@ -2,6 +2,7 @@ package com.fabledsword.minstrel.library.ui import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.fabledsword.minstrel.api.ErrorCopy import com.fabledsword.minstrel.cache.sync.SyncController import com.fabledsword.minstrel.library.data.LibraryRepository import dagger.hilt.android.lifecycle.HiltViewModel @@ -35,7 +36,7 @@ class LibraryViewModel @Inject constructor( } } .catch { e -> - emit(LibraryUiState.Error(e.message ?: "Library load failed")) + emit(LibraryUiState.Error(ErrorCopy.fromThrowable(e))) } .stateIn( scope = viewModelScope, diff --git a/android/app/src/main/java/com/fabledsword/minstrel/likes/ui/LikedTab.kt b/android/app/src/main/java/com/fabledsword/minstrel/likes/ui/LikedTab.kt index 1d56084b..f7175541 100644 --- a/android/app/src/main/java/com/fabledsword/minstrel/likes/ui/LikedTab.kt +++ b/android/app/src/main/java/com/fabledsword/minstrel/likes/ui/LikedTab.kt @@ -27,6 +27,7 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.viewModelScope import androidx.navigation.NavHostController +import com.fabledsword.minstrel.api.ErrorCopy import com.fabledsword.minstrel.likes.data.LikesRepository import com.fabledsword.minstrel.library.widgets.AlbumCard import com.fabledsword.minstrel.library.widgets.ArtistCard @@ -107,7 +108,7 @@ class LikedTabViewModel @Inject constructor( val sections = LikedSections(artists, albums, tracks) if (sections.isAllEmpty) LikedTabUiState.Empty else LikedTabUiState.Success(sections) } - .catch { e -> emit(LikedTabUiState.Error(e.message ?: "Liked load failed")) } + .catch { e -> emit(LikedTabUiState.Error(ErrorCopy.fromThrowable(e))) } .stateIn( scope = viewModelScope, started = SharingStarted.WhileSubscribed(SHARE_STOP_TIMEOUT_MS), diff --git a/android/app/src/main/java/com/fabledsword/minstrel/playlists/ui/PlaylistDetailScreen.kt b/android/app/src/main/java/com/fabledsword/minstrel/playlists/ui/PlaylistDetailScreen.kt index ce37f970..bb4c0d27 100644 --- a/android/app/src/main/java/com/fabledsword/minstrel/playlists/ui/PlaylistDetailScreen.kt +++ b/android/app/src/main/java/com/fabledsword/minstrel/playlists/ui/PlaylistDetailScreen.kt @@ -58,6 +58,7 @@ import com.composables.icons.lucide.Lucide import com.composables.icons.lucide.Play import com.composables.icons.lucide.RefreshCw import com.composables.icons.lucide.Shuffle +import com.fabledsword.minstrel.api.ErrorCopy import com.fabledsword.minstrel.events.EventsStream import com.fabledsword.minstrel.events.LiveEvent import com.fabledsword.minstrel.models.PlaylistRef @@ -196,7 +197,7 @@ class PlaylistDetailViewModel @Inject constructor( @Suppress("TooGenericExceptionCaught") e: Throwable, ) { internal.value = PlaylistDetailUiState.Error( - e.message ?: "Couldn't load playlist", + ErrorCopy.fromThrowable(e), ) } } diff --git a/android/app/src/main/java/com/fabledsword/minstrel/playlists/ui/PlaylistsListScreen.kt b/android/app/src/main/java/com/fabledsword/minstrel/playlists/ui/PlaylistsListScreen.kt index 007d3b65..b8c9ef0a 100644 --- a/android/app/src/main/java/com/fabledsword/minstrel/playlists/ui/PlaylistsListScreen.kt +++ b/android/app/src/main/java/com/fabledsword/minstrel/playlists/ui/PlaylistsListScreen.kt @@ -23,6 +23,7 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.viewModelScope import androidx.navigation.NavHostController +import com.fabledsword.minstrel.api.ErrorCopy import com.fabledsword.minstrel.events.EventsStream import com.fabledsword.minstrel.models.PlaylistRef import com.fabledsword.minstrel.nav.PlaylistDetail @@ -89,7 +90,7 @@ class PlaylistsListViewModel @Inject constructor( } } .catch { e -> - emit(PlaylistsListUiState.Error(e.message ?: "Playlists load failed")) + emit(PlaylistsListUiState.Error(ErrorCopy.fromThrowable(e))) } .stateIn( scope = viewModelScope, 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 a7dca162..ffa466e0 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 @@ -2,6 +2,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.models.QuarantineRef import com.fabledsword.minstrel.quarantine.data.QuarantineRepository import dagger.hilt.android.lifecycle.HiltViewModel @@ -43,7 +44,7 @@ class HiddenTabViewModel @Inject constructor( } catch ( @Suppress("TooGenericExceptionCaught") e: Throwable, ) { - internal.value = HiddenTabUiState.Error(e.message ?: "Hidden load failed") + internal.value = HiddenTabUiState.Error(ErrorCopy.fromThrowable(e)) } } diff --git a/android/app/src/main/java/com/fabledsword/minstrel/requests/ui/RequestsViewModel.kt b/android/app/src/main/java/com/fabledsword/minstrel/requests/ui/RequestsViewModel.kt index 2d027078..7faff607 100644 --- a/android/app/src/main/java/com/fabledsword/minstrel/requests/ui/RequestsViewModel.kt +++ b/android/app/src/main/java/com/fabledsword/minstrel/requests/ui/RequestsViewModel.kt @@ -2,6 +2,7 @@ package com.fabledsword.minstrel.requests.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.RequestRef import com.fabledsword.minstrel.requests.data.RequestsRepository @@ -54,7 +55,7 @@ class RequestsViewModel @Inject constructor( } catch ( @Suppress("TooGenericExceptionCaught") e: Throwable, ) { - internal.value = RequestsUiState.Error(e.message ?: "Requests load failed") + internal.value = RequestsUiState.Error(ErrorCopy.fromThrowable(e)) } } diff --git a/android/app/src/main/java/com/fabledsword/minstrel/search/ui/SearchViewModel.kt b/android/app/src/main/java/com/fabledsword/minstrel/search/ui/SearchViewModel.kt index 103765ac..86860f09 100644 --- a/android/app/src/main/java/com/fabledsword/minstrel/search/ui/SearchViewModel.kt +++ b/android/app/src/main/java/com/fabledsword/minstrel/search/ui/SearchViewModel.kt @@ -2,6 +2,7 @@ package com.fabledsword.minstrel.search.ui import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.fabledsword.minstrel.api.ErrorCopy import com.fabledsword.minstrel.models.SearchResponseRef import com.fabledsword.minstrel.models.TrackRef import com.fabledsword.minstrel.player.PlayerController @@ -103,7 +104,7 @@ class SearchViewModel @Inject constructor( @Suppress("TooGenericExceptionCaught") e: Throwable, ) { internal.update { - it.copy(results = SearchResultsState.Error(e.message ?: "Search failed")) + it.copy(results = SearchResultsState.Error(ErrorCopy.fromThrowable(e))) } } } diff --git a/android/app/src/main/java/com/fabledsword/minstrel/settings/ui/ListenBrainzViewModel.kt b/android/app/src/main/java/com/fabledsword/minstrel/settings/ui/ListenBrainzViewModel.kt index 52987842..6e7bcb41 100644 --- a/android/app/src/main/java/com/fabledsword/minstrel/settings/ui/ListenBrainzViewModel.kt +++ b/android/app/src/main/java/com/fabledsword/minstrel/settings/ui/ListenBrainzViewModel.kt @@ -2,6 +2,7 @@ package com.fabledsword.minstrel.settings.ui import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.fabledsword.minstrel.api.ErrorCopy import com.fabledsword.minstrel.models.ListenBrainzStatus import com.fabledsword.minstrel.settings.data.MeRepository import dagger.hilt.android.lifecycle.HiltViewModel @@ -56,7 +57,7 @@ class ListenBrainzViewModel @Inject constructor( internal.update { it.copy( isLoading = false, - message = "Couldn't load: ${e.message ?: "unknown error"}", + message = ErrorCopy.fromThrowable(e), ) } } @@ -84,7 +85,7 @@ class ListenBrainzViewModel @Inject constructor( internal.update { it.copy( isSaving = false, - message = "Couldn't save: ${e.message ?: "unknown error"}", + message = ErrorCopy.fromThrowable(e), ) } } @@ -104,7 +105,7 @@ class ListenBrainzViewModel @Inject constructor( internal.update { it.copy( isSaving = false, - message = "Couldn't update: ${e.message ?: "unknown error"}", + message = ErrorCopy.fromThrowable(e), ) } } diff --git a/android/app/src/main/java/com/fabledsword/minstrel/settings/ui/PasswordViewModel.kt b/android/app/src/main/java/com/fabledsword/minstrel/settings/ui/PasswordViewModel.kt index af087517..2745ea02 100644 --- a/android/app/src/main/java/com/fabledsword/minstrel/settings/ui/PasswordViewModel.kt +++ b/android/app/src/main/java/com/fabledsword/minstrel/settings/ui/PasswordViewModel.kt @@ -2,6 +2,7 @@ package com.fabledsword.minstrel.settings.ui import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.fabledsword.minstrel.api.ErrorCopy import com.fabledsword.minstrel.settings.data.MeRepository import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableStateFlow @@ -64,7 +65,7 @@ class PasswordViewModel @Inject constructor( internal.update { it.copy( isChanging = false, - message = "Couldn't change password: ${e.message ?: "unknown error"}", + message = ErrorCopy.fromThrowable(e), ) } } diff --git a/android/app/src/main/java/com/fabledsword/minstrel/settings/ui/ProfileViewModel.kt b/android/app/src/main/java/com/fabledsword/minstrel/settings/ui/ProfileViewModel.kt index c684ca7f..d375a7f1 100644 --- a/android/app/src/main/java/com/fabledsword/minstrel/settings/ui/ProfileViewModel.kt +++ b/android/app/src/main/java/com/fabledsword/minstrel/settings/ui/ProfileViewModel.kt @@ -2,6 +2,7 @@ package com.fabledsword.minstrel.settings.ui import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.fabledsword.minstrel.api.ErrorCopy import com.fabledsword.minstrel.models.MyProfile import com.fabledsword.minstrel.settings.data.MeRepository import dagger.hilt.android.lifecycle.HiltViewModel @@ -71,7 +72,7 @@ class ProfileViewModel @Inject constructor( internal.update { it.copy( isLoading = false, - message = "Couldn't load profile: ${e.message ?: "unknown error"}", + message = ErrorCopy.fromThrowable(e), ) } } @@ -102,7 +103,7 @@ class ProfileViewModel @Inject constructor( internal.update { it.copy( isSaving = false, - message = "Couldn't save: ${e.message ?: "unknown error"}", + message = ErrorCopy.fromThrowable(e), ) } } diff --git a/android/app/src/main/java/com/fabledsword/minstrel/shared/widgets/trackactions/AddToPlaylistViewModel.kt b/android/app/src/main/java/com/fabledsword/minstrel/shared/widgets/trackactions/AddToPlaylistViewModel.kt index fb5f6865..42c0e18f 100644 --- a/android/app/src/main/java/com/fabledsword/minstrel/shared/widgets/trackactions/AddToPlaylistViewModel.kt +++ b/android/app/src/main/java/com/fabledsword/minstrel/shared/widgets/trackactions/AddToPlaylistViewModel.kt @@ -2,6 +2,7 @@ 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.models.PlaylistRef import com.fabledsword.minstrel.playlists.data.PlaylistsRepository import dagger.hilt.android.lifecycle.HiltViewModel @@ -44,7 +45,7 @@ class AddToPlaylistViewModel @Inject constructor( } emit(AddToPlaylistUiState.Loading) } - .catch { e -> emit(AddToPlaylistUiState.Error(e.message ?: "Failed to load playlists")) } + .catch { e -> emit(AddToPlaylistUiState.Error(ErrorCopy.fromThrowable(e))) } .stateIn( scope = viewModelScope, started = SharingStarted.WhileSubscribed(SHARE_STOP_TIMEOUT_MS),