Twelve: Make RequestStatus even more generic

Change-Id: If20ff3cb0cb7731752ae491d734160c6c32b4893
diff --git a/app/src/main/java/org/lineageos/twelve/datasources/LocalDataSource.kt b/app/src/main/java/org/lineageos/twelve/datasources/LocalDataSource.kt
index c587804..b2a7835 100644
--- a/app/src/main/java/org/lineageos/twelve/datasources/LocalDataSource.kt
+++ b/app/src/main/java/org/lineageos/twelve/datasources/LocalDataSource.kt
@@ -198,26 +198,26 @@
         albumsUri,
         albumsProjection,
     ).mapEachRow(albumsProjection, mapAlbum).map {
-        RequestStatus.Success(it)
+        RequestStatus.Success<_, MediaError>(it)
     }
 
     override fun artists() = contentResolver.queryFlow(
         artistsUri,
         artistsProjection,
     ).mapEachRow(artistsProjection, mapArtist).map {
-        RequestStatus.Success(it)
+        RequestStatus.Success<_, MediaError>(it)
     }
 
     override fun genres() = contentResolver.queryFlow(
         genresUri,
         genresProjection,
     ).mapEachRow(genresProjection, mapGenre).map {
-        RequestStatus.Success(it)
+        RequestStatus.Success<_, MediaError>(it)
     }
 
     override fun playlists() = database.getPlaylistDao().getAll()
         .mapLatest { playlists ->
-            RequestStatus.Success(playlists.map { it.toModel() })
+            RequestStatus.Success<_, MediaError>(playlists.map { it.toModel() })
         }
 
     override fun search(query: String) = combine(
@@ -263,7 +263,7 @@
         ).mapEachRow(genresProjection, mapGenre),
     ) { albums, artists, audios, genres ->
         albums + artists + audios + genres
-    }.map { RequestStatus.Success(it) }
+    }.map { RequestStatus.Success<_, MediaError>(it) }
 
     override fun audio(audioUri: Uri) = contentResolver.queryFlow(
         audiosUri,
@@ -278,8 +278,8 @@
         )
     ).mapEachRow(audiosProjection, mapAudio).mapLatest { audios ->
         audios.firstOrNull()?.let {
-            RequestStatus.Success(it)
-        } ?: RequestStatus.Error(RequestStatus.Error.Type.NOT_FOUND)
+            RequestStatus.Success<_, MediaError>(it)
+        } ?: RequestStatus.Error(MediaError.NOT_FOUND)
     }
 
     override fun album(albumUri: Uri) = combine(
@@ -312,8 +312,8 @@
         ).mapEachRow(audiosProjection, mapAudio)
     ) { albums, audios ->
         albums.firstOrNull()?.let {
-            RequestStatus.Success(Pair(it, audios))
-        } ?: RequestStatus.Error(RequestStatus.Error.Type.NOT_FOUND)
+            RequestStatus.Success<_, MediaError>(Pair(it, audios))
+        } ?: RequestStatus.Error(MediaError.NOT_FOUND)
     }
 
     override fun artist(artistUri: Uri) = combine(
@@ -384,8 +384,8 @@
                 listOf(),
             )
 
-            RequestStatus.Success(Pair(it, artistWorks))
-        } ?: RequestStatus.Error(RequestStatus.Error.Type.NOT_FOUND)
+            RequestStatus.Success<_, MediaError>(Pair(it, artistWorks))
+        } ?: RequestStatus.Error(MediaError.NOT_FOUND)
     }
 
     override fun genre(genreUri: Uri) = combine(
@@ -415,8 +415,8 @@
         ).mapEachRow(audiosProjection, mapAudio)
     ) { genres, audios ->
         genres.firstOrNull()?.let {
-            RequestStatus.Success(Pair(it, audios))
-        } ?: RequestStatus.Error(RequestStatus.Error.Type.NOT_FOUND)
+            RequestStatus.Success<_, MediaError>(Pair(it, audios))
+        } ?: RequestStatus.Error(MediaError.NOT_FOUND)
     }
 
     override fun playlist(playlistUri: Uri) = database.getPlaylistDao().getPlaylistWithItems(
@@ -427,11 +427,11 @@
 
             audios(playlistWithItems.items.map(Item::audioUri))
                 .mapLatest {
-                    RequestStatus.Success(Pair(playlist, it))
+                    RequestStatus.Success<_, MediaError>(Pair(playlist, it))
                 }
         } ?: flowOf(
             RequestStatus.Error(
-                RequestStatus.Error.Type.NOT_FOUND
+                MediaError.NOT_FOUND
             )
         )
     }
@@ -440,7 +440,7 @@
         database.getPlaylistWithItemsDao().getPlaylistsWithItemStatus(
             audioUri
         ).mapLatest { data ->
-            RequestStatus.Success(
+            RequestStatus.Success<_, MediaError>(
                 data.map {
                     it.playlist.toModel() to it.value
                 }
@@ -450,20 +450,20 @@
     override suspend fun createPlaylist(name: String) = database.getPlaylistDao().create(
         name
     ).let {
-        RequestStatus.Success(ContentUris.withAppendedId(playlistsBaseUri, it))
+        RequestStatus.Success<_, MediaError>(ContentUris.withAppendedId(playlistsBaseUri, it))
     }
 
     override suspend fun renamePlaylist(playlistUri: Uri, name: String) =
         database.getPlaylistDao().rename(
             ContentUris.parseId(playlistUri), name
         ).let {
-            RequestStatus.Success(Unit)
+            RequestStatus.Success<_, MediaError>(Unit)
         }
 
     override suspend fun deletePlaylist(playlistUri: Uri) = database.getPlaylistDao().delete(
         ContentUris.parseId(playlistUri)
     ).let {
-        RequestStatus.Success(Unit)
+        RequestStatus.Success<_, MediaError>(Unit)
     }
 
     override suspend fun addAudioToPlaylist(
@@ -473,7 +473,7 @@
         ContentUris.parseId(playlistUri),
         audioUri
     ).let {
-        RequestStatus.Success(Unit)
+        RequestStatus.Success<_, MediaError>(Unit)
     }
 
     override suspend fun removeAudioFromPlaylist(
@@ -483,7 +483,7 @@
         ContentUris.parseId(playlistUri),
         audioUri
     ).let {
-        RequestStatus.Success(Unit)
+        RequestStatus.Success<_, MediaError>(Unit)
     }
 
     /**
diff --git a/app/src/main/java/org/lineageos/twelve/datasources/MediaDataSource.kt b/app/src/main/java/org/lineageos/twelve/datasources/MediaDataSource.kt
index 26de920..eba50ca 100644
--- a/app/src/main/java/org/lineageos/twelve/datasources/MediaDataSource.kt
+++ b/app/src/main/java/org/lineageos/twelve/datasources/MediaDataSource.kt
@@ -16,6 +16,8 @@
 import org.lineageos.twelve.models.Playlist
 import org.lineageos.twelve.models.RequestStatus
 
+typealias MediaRequestStatus<T> = RequestStatus<T, MediaError>
+
 /**
  * A data source for media.
  */
@@ -31,60 +33,60 @@
     /**
      * Get all the albums. All albums must have at least one audio associated with them.
      */
-    fun albums(): Flow<RequestStatus<List<Album>>>
+    fun albums(): Flow<MediaRequestStatus<List<Album>>>
 
     /**
      * Get all the artists. All artists must have at least one audio associated with them.
      */
-    fun artists(): Flow<RequestStatus<List<Artist>>>
+    fun artists(): Flow<MediaRequestStatus<List<Artist>>>
 
     /**
      * Get all the genres. All genres must have at least one audio associated with them.
      */
-    fun genres(): Flow<RequestStatus<List<Genre>>>
+    fun genres(): Flow<MediaRequestStatus<List<Genre>>>
 
     /**
      * Get all the playlists. A playlist can be empty.
      */
-    fun playlists(): Flow<RequestStatus<List<Playlist>>>
+    fun playlists(): Flow<MediaRequestStatus<List<Playlist>>>
 
     /**
      * Start a search for the given query.
      * Only the following items can be returned: [Album], [Artist], [Audio], [Genre], [Playlist].
      */
-    fun search(query: String): Flow<RequestStatus<List<MediaItem<*>>>>
+    fun search(query: String): Flow<MediaRequestStatus<List<MediaItem<*>>>>
 
     /**
      * Get the audio information of the given audio.
      */
-    fun audio(audioUri: Uri): Flow<RequestStatus<Audio>>
+    fun audio(audioUri: Uri): Flow<MediaRequestStatus<Audio>>
 
     /**
      * Get the album information and all the tracks of the given album.
      */
-    fun album(albumUri: Uri): Flow<RequestStatus<Pair<Album, List<Audio>>>>
+    fun album(albumUri: Uri): Flow<MediaRequestStatus<Pair<Album, List<Audio>>>>
 
     /**
      * Get the artist information and all the works associated with them.
      */
-    fun artist(artistUri: Uri): Flow<RequestStatus<Pair<Artist, ArtistWorks>>>
+    fun artist(artistUri: Uri): Flow<MediaRequestStatus<Pair<Artist, ArtistWorks>>>
 
     /**
      * Get the genre information and all the tracks of the given genre.
      */
-    fun genre(genreUri: Uri): Flow<RequestStatus<Pair<Genre, List<Audio>>>>
+    fun genre(genreUri: Uri): Flow<MediaRequestStatus<Pair<Genre, List<Audio>>>>
 
     /**
      * Get the playlist information and all the tracks of the given playlist.
      * If the playlist contains an audio that is unavailable, it will be mapped to null.
      */
-    fun playlist(playlistUri: Uri): Flow<RequestStatus<Pair<Playlist, List<Audio?>>>>
+    fun playlist(playlistUri: Uri): Flow<MediaRequestStatus<Pair<Playlist, List<Audio?>>>>
 
     /**
      * Get an audio status within all playlists.
      * @param audioUri The URI of the audio
      */
-    fun audioPlaylistsStatus(audioUri: Uri): Flow<RequestStatus<List<Pair<Playlist, Boolean>>>>
+    fun audioPlaylistsStatus(audioUri: Uri): Flow<MediaRequestStatus<List<Pair<Playlist, Boolean>>>>
 
     /**
      * Create a new playlist. Note that the name shouldn't be considered unique if possible, but
@@ -92,7 +94,7 @@
      * @param name The name of the playlist
      * @return A [RequestStatus] with the [Uri] of the new playlist if succeeded, an error otherwise
      */
-    suspend fun createPlaylist(name: String): RequestStatus<Uri>
+    suspend fun createPlaylist(name: String): MediaRequestStatus<Uri>
 
     /**
      * Rename a playlist.
@@ -100,14 +102,14 @@
      * @param name The new name of the playlist
      * @return [RequestStatus.Success] if success, [RequestStatus.Error] with an error otherwise
      */
-    suspend fun renamePlaylist(playlistUri: Uri, name: String): RequestStatus<Unit>
+    suspend fun renamePlaylist(playlistUri: Uri, name: String): MediaRequestStatus<Unit>
 
     /**
      * Delete a playlist.
      * @param playlistUri The URI of the playlist
      * @return [RequestStatus.Success] if success, [RequestStatus.Error] with an error otherwise
      */
-    suspend fun deletePlaylist(playlistUri: Uri): RequestStatus<Unit>
+    suspend fun deletePlaylist(playlistUri: Uri): MediaRequestStatus<Unit>
 
     /**
      * Add an audio to a playlist.
@@ -115,7 +117,7 @@
      * @param audioUri The URI of the audio
      * @return [RequestStatus.Success] if success, [RequestStatus.Error] with an error otherwise
      */
-    suspend fun addAudioToPlaylist(playlistUri: Uri, audioUri: Uri): RequestStatus<Unit>
+    suspend fun addAudioToPlaylist(playlistUri: Uri, audioUri: Uri): MediaRequestStatus<Unit>
 
     /**
      * Remove an audio from a playlist.
@@ -123,5 +125,5 @@
      * @param audioUri The URI of the audio
      * @return [RequestStatus.Success] if success, [RequestStatus.Error] with an error otherwise
      */
-    suspend fun removeAudioFromPlaylist(playlistUri: Uri, audioUri: Uri): RequestStatus<Unit>
+    suspend fun removeAudioFromPlaylist(playlistUri: Uri, audioUri: Uri): MediaRequestStatus<Unit>
 }
diff --git a/app/src/main/java/org/lineageos/twelve/datasources/MediaError.kt b/app/src/main/java/org/lineageos/twelve/datasources/MediaError.kt
new file mode 100644
index 0000000..5a1e558
--- /dev/null
+++ b/app/src/main/java/org/lineageos/twelve/datasources/MediaError.kt
@@ -0,0 +1,41 @@
+/*
+ * SPDX-FileCopyrightText: 2024 The LineageOS Project
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package org.lineageos.twelve.datasources
+
+/**
+ * [MediaDataSource] errors.
+ */
+enum class MediaError {
+    /**
+     * This feature isn't implemented.
+     */
+    NOT_IMPLEMENTED,
+
+    /**
+     * I/O error, can also be network.
+     */
+    IO,
+
+    /**
+     * Authentication error.
+     */
+    AUTHENTICATION_REQUIRED,
+
+    /**
+     * Invalid credentials.
+     */
+    INVALID_CREDENTIALS,
+
+    /**
+     * The item was not found.
+     */
+    NOT_FOUND,
+
+    /**
+     * Value returned on write requests: The value already exists.
+     */
+    ALREADY_EXISTS,
+}
diff --git a/app/src/main/java/org/lineageos/twelve/datasources/SubsonicDataSource.kt b/app/src/main/java/org/lineageos/twelve/datasources/SubsonicDataSource.kt
index 07e7bd4..84df2a8 100644
--- a/app/src/main/java/org/lineageos/twelve/datasources/SubsonicDataSource.kt
+++ b/app/src/main/java/org/lineageos/twelve/datasources/SubsonicDataSource.kt
@@ -268,26 +268,24 @@
 
     private suspend fun <T, O> SubsonicClient.MethodResult<T>.toRequestStatus(
         resultGetter: suspend T.() -> O
-    ) = when (this) {
+    ): RequestStatus<O, MediaError> = when (this) {
         is SubsonicClient.MethodResult.Success -> RequestStatus.Success(result.resultGetter())
-        is SubsonicClient.MethodResult.HttpError -> RequestStatus.Error(RequestStatus.Error.Type.IO)
+        is SubsonicClient.MethodResult.HttpError -> RequestStatus.Error(MediaError.IO)
         is SubsonicClient.MethodResult.SubsonicError -> RequestStatus.Error(
-            error?.code?.toRequestStatusType() ?: RequestStatus.Error.Type.IO
+            error?.code?.toRequestStatusType() ?: MediaError.IO
         )
     }
 
     private fun Error.Code.toRequestStatusType() = when (this) {
-        Error.Code.GENERIC_ERROR -> RequestStatus.Error.Type.IO
-        Error.Code.REQUIRED_PARAMETER_MISSING -> RequestStatus.Error.Type.IO
-        Error.Code.OUTDATED_CLIENT -> RequestStatus.Error.Type.IO
-        Error.Code.OUTDATED_SERVER -> RequestStatus.Error.Type.IO
-        Error.Code.WRONG_CREDENTIALS -> RequestStatus.Error.Type.INVALID_CREDENTIALS
-        Error.Code.TOKEN_AUTHENTICATION_NOT_SUPPORTED ->
-            RequestStatus.Error.Type.INVALID_CREDENTIALS
-
-        Error.Code.USER_NOT_AUTHORIZED -> RequestStatus.Error.Type.INVALID_CREDENTIALS
-        Error.Code.SUBSONIC_PREMIUM_TRIAL_ENDED -> RequestStatus.Error.Type.INVALID_CREDENTIALS
-        Error.Code.NOT_FOUND -> RequestStatus.Error.Type.NOT_FOUND
+        Error.Code.GENERIC_ERROR -> MediaError.IO
+        Error.Code.REQUIRED_PARAMETER_MISSING -> MediaError.IO
+        Error.Code.OUTDATED_CLIENT -> MediaError.IO
+        Error.Code.OUTDATED_SERVER -> MediaError.IO
+        Error.Code.WRONG_CREDENTIALS -> MediaError.INVALID_CREDENTIALS
+        Error.Code.TOKEN_AUTHENTICATION_NOT_SUPPORTED -> MediaError.INVALID_CREDENTIALS
+        Error.Code.USER_NOT_AUTHORIZED -> MediaError.INVALID_CREDENTIALS
+        Error.Code.SUBSONIC_PREMIUM_TRIAL_ENDED -> MediaError.INVALID_CREDENTIALS
+        Error.Code.NOT_FOUND -> MediaError.NOT_FOUND
     }
 
     private fun getAlbumUri(albumId: String) = albumsUri.buildUpon()
diff --git a/app/src/main/java/org/lineageos/twelve/ext/LinearProgressIndicator.kt b/app/src/main/java/org/lineageos/twelve/ext/LinearProgressIndicator.kt
index 3ec0320..6ce27e3 100644
--- a/app/src/main/java/org/lineageos/twelve/ext/LinearProgressIndicator.kt
+++ b/app/src/main/java/org/lineageos/twelve/ext/LinearProgressIndicator.kt
@@ -11,8 +11,8 @@
 /**
  * @see LinearProgressIndicator.setProgressCompat
  */
-fun <T> LinearProgressIndicator.setProgressCompat(
-    status: RequestStatus<T>, animated: Boolean
+fun <T, E> LinearProgressIndicator.setProgressCompat(
+    status: RequestStatus<T, E>, animated: Boolean
 ) {
     when (status) {
         is RequestStatus.Loading -> {
diff --git a/app/src/main/java/org/lineageos/twelve/fragments/AlbumFragment.kt b/app/src/main/java/org/lineageos/twelve/fragments/AlbumFragment.kt
index 8bf86a5..edaee44 100644
--- a/app/src/main/java/org/lineageos/twelve/fragments/AlbumFragment.kt
+++ b/app/src/main/java/org/lineageos/twelve/fragments/AlbumFragment.kt
@@ -33,6 +33,7 @@
 import kotlinx.coroutines.flow.collectLatest
 import kotlinx.coroutines.launch
 import org.lineageos.twelve.R
+import org.lineageos.twelve.datasources.MediaError
 import org.lineageos.twelve.ext.getParcelable
 import org.lineageos.twelve.ext.getViewProperty
 import org.lineageos.twelve.ext.setProgressCompat
@@ -318,7 +319,7 @@
                             toolbar.title = ""
                             albumTitleTextView.text = ""
 
-                            if (it.error == RequestStatus.Error.Type.NOT_FOUND) {
+                            if (it.error == MediaError.NOT_FOUND) {
                                 // Get out of here
                                 findNavController().navigateUp()
                             }
diff --git a/app/src/main/java/org/lineageos/twelve/fragments/ArtistFragment.kt b/app/src/main/java/org/lineageos/twelve/fragments/ArtistFragment.kt
index 14779e6..a9bb169 100644
--- a/app/src/main/java/org/lineageos/twelve/fragments/ArtistFragment.kt
+++ b/app/src/main/java/org/lineageos/twelve/fragments/ArtistFragment.kt
@@ -31,6 +31,7 @@
 import kotlinx.coroutines.flow.collectLatest
 import kotlinx.coroutines.launch
 import org.lineageos.twelve.R
+import org.lineageos.twelve.datasources.MediaError
 import org.lineageos.twelve.ext.getParcelable
 import org.lineageos.twelve.ext.getViewProperty
 import org.lineageos.twelve.ext.setProgressCompat
@@ -257,7 +258,7 @@
                     nestedScrollView.isVisible = false
                     noElementsNestedScrollView.isVisible = true
 
-                    if (it.error == RequestStatus.Error.Type.NOT_FOUND) {
+                    if (it.error == MediaError.NOT_FOUND) {
                         // Get out of here
                         findNavController().navigateUp()
                     }
diff --git a/app/src/main/java/org/lineageos/twelve/fragments/AudioBottomSheetDialogFragment.kt b/app/src/main/java/org/lineageos/twelve/fragments/AudioBottomSheetDialogFragment.kt
index 31fa7b2..be2863b 100644
--- a/app/src/main/java/org/lineageos/twelve/fragments/AudioBottomSheetDialogFragment.kt
+++ b/app/src/main/java/org/lineageos/twelve/fragments/AudioBottomSheetDialogFragment.kt
@@ -20,6 +20,7 @@
 import com.google.android.material.bottomsheet.BottomSheetDialogFragment
 import kotlinx.coroutines.launch
 import org.lineageos.twelve.R
+import org.lineageos.twelve.datasources.MediaError
 import org.lineageos.twelve.ext.getParcelable
 import org.lineageos.twelve.ext.getViewProperty
 import org.lineageos.twelve.models.RequestStatus
@@ -141,7 +142,7 @@
                 is RequestStatus.Error -> {
                     Log.e(LOG_TAG, "Failed to load audio, error: ${it.error}")
 
-                    if (it.error == RequestStatus.Error.Type.NOT_FOUND) {
+                    if (it.error == MediaError.NOT_FOUND) {
                         // Get out of here
                         findNavController().navigateUp()
                     }
diff --git a/app/src/main/java/org/lineageos/twelve/fragments/ManageProviderFragment.kt b/app/src/main/java/org/lineageos/twelve/fragments/ManageProviderFragment.kt
index daa3ca8..a0236a3 100644
--- a/app/src/main/java/org/lineageos/twelve/fragments/ManageProviderFragment.kt
+++ b/app/src/main/java/org/lineageos/twelve/fragments/ManageProviderFragment.kt
@@ -29,6 +29,7 @@
 import kotlinx.coroutines.flow.collectLatest
 import kotlinx.coroutines.launch
 import org.lineageos.twelve.R
+import org.lineageos.twelve.datasources.MediaError
 import org.lineageos.twelve.ext.getSerializable
 import org.lineageos.twelve.ext.getViewProperty
 import org.lineageos.twelve.ext.selectItem
@@ -247,7 +248,7 @@
                             is RequestStatus.Error -> {
                                 Log.e(LOG_TAG, "Failed to load provider")
 
-                                if (it.error == RequestStatus.Error.Type.NOT_FOUND) {
+                                if (it.error == MediaError.NOT_FOUND) {
                                     // Get out of here
                                     findNavController().navigateUp()
                                 }
diff --git a/app/src/main/java/org/lineageos/twelve/fragments/PlaylistFragment.kt b/app/src/main/java/org/lineageos/twelve/fragments/PlaylistFragment.kt
index 6cd20bc..0b894c2 100644
--- a/app/src/main/java/org/lineageos/twelve/fragments/PlaylistFragment.kt
+++ b/app/src/main/java/org/lineageos/twelve/fragments/PlaylistFragment.kt
@@ -30,6 +30,7 @@
 import kotlinx.coroutines.flow.collectLatest
 import kotlinx.coroutines.launch
 import org.lineageos.twelve.R
+import org.lineageos.twelve.datasources.MediaError
 import org.lineageos.twelve.ext.getParcelable
 import org.lineageos.twelve.ext.getViewProperty
 import org.lineageos.twelve.ext.setProgressCompat
@@ -277,7 +278,7 @@
                     noElementsNestedScrollView.isVisible = true
                     playAllFloatingActionButton.isVisible = false
 
-                    if (it.error == RequestStatus.Error.Type.NOT_FOUND) {
+                    if (it.error == MediaError.NOT_FOUND) {
                         // Get out of here
                         findNavController().navigateUp()
                     }
diff --git a/app/src/main/java/org/lineageos/twelve/models/RequestStatus.kt b/app/src/main/java/org/lineageos/twelve/models/RequestStatus.kt
index 560bfa1..50ee9b9 100644
--- a/app/src/main/java/org/lineageos/twelve/models/RequestStatus.kt
+++ b/app/src/main/java/org/lineageos/twelve/models/RequestStatus.kt
@@ -8,59 +8,27 @@
 /**
  * Request status for flows.
  */
-sealed class RequestStatus<T> {
+sealed class RequestStatus<T, E> {
     /**
      * Result is not ready yet.
      *
      * @param progress An optional percentage of the request progress
      */
-    class Loading<T>(
+    class Loading<T, E>(
         @androidx.annotation.IntRange(from = 0, to = 100) val progress: Int? = null
-    ) : RequestStatus<T>()
+    ) : RequestStatus<T, E>()
 
     /**
      * The result is ready.
      *
      * @param data The obtained data
      */
-    class Success<T>(val data: T) : RequestStatus<T>()
+    class Success<T, E>(val data: T) : RequestStatus<T, E>()
 
     /**
      * The request failed.
      *
      * @param error The error
      */
-    class Error<T>(val error: Type) : RequestStatus<T>() {
-        enum class Type {
-            /**
-             * This feature isn't implemented.
-             */
-            NOT_IMPLEMENTED,
-
-            /**
-             * I/O error, can also be network.
-             */
-            IO,
-
-            /**
-             * Authentication error.
-             */
-            AUTHENTICATION_REQUIRED,
-
-            /**
-             * Invalid credentials.
-             */
-            INVALID_CREDENTIALS,
-
-            /**
-             * The item was not found.
-             */
-            NOT_FOUND,
-
-            /**
-             * Value returned on write requests: The value already exists.
-             */
-            ALREADY_EXISTS,
-        }
-    }
+    class Error<T, E>(val error: E) : RequestStatus<T, E>()
 }
diff --git a/app/src/main/java/org/lineageos/twelve/repositories/MediaRepository.kt b/app/src/main/java/org/lineageos/twelve/repositories/MediaRepository.kt
index 9b1162e..9f08828 100644
--- a/app/src/main/java/org/lineageos/twelve/repositories/MediaRepository.kt
+++ b/app/src/main/java/org/lineageos/twelve/repositories/MediaRepository.kt
@@ -25,14 +25,8 @@
 import org.lineageos.twelve.database.TwelveDatabase
 import org.lineageos.twelve.datasources.LocalDataSource
 import org.lineageos.twelve.datasources.MediaDataSource
+import org.lineageos.twelve.datasources.MediaError
 import org.lineageos.twelve.datasources.SubsonicDataSource
-import org.lineageos.twelve.models.Album
-import org.lineageos.twelve.models.Artist
-import org.lineageos.twelve.models.ArtistWorks
-import org.lineageos.twelve.models.Audio
-import org.lineageos.twelve.models.Genre
-import org.lineageos.twelve.models.MediaItem
-import org.lineageos.twelve.models.Playlist
 import org.lineageos.twelve.models.Provider
 import org.lineageos.twelve.models.ProviderArgument.Companion.requireArgument
 import org.lineageos.twelve.models.ProviderType
@@ -309,87 +303,78 @@
     /**
      * @see MediaDataSource.albums
      */
-    fun albums(): Flow<RequestStatus<List<Album>>> =
-        navigationDataSource.flatMapLatest { it.albums() }
+    fun albums() = navigationDataSource.flatMapLatest { it.albums() }
 
     /**
      * @see MediaDataSource.artists
      */
-    fun artists(): Flow<RequestStatus<List<Artist>>> =
-        navigationDataSource.flatMapLatest { it.artists() }
+    fun artists() = navigationDataSource.flatMapLatest { it.artists() }
 
     /**
      * @see MediaDataSource.genres
      */
-    fun genres(): Flow<RequestStatus<List<Genre>>> =
-        navigationDataSource.flatMapLatest { it.genres() }
+    fun genres() = navigationDataSource.flatMapLatest { it.genres() }
 
     /**
      * @see MediaDataSource.playlists
      */
-    fun playlists(): Flow<RequestStatus<List<Playlist>>> =
-        navigationDataSource.flatMapLatest { it.playlists() }
+    fun playlists() = navigationDataSource.flatMapLatest { it.playlists() }
 
     /**
      * @see MediaDataSource.search
      */
-    fun search(query: String): Flow<RequestStatus<List<MediaItem<*>>>> =
-        navigationDataSource.flatMapLatest { it.search(query) }
+    fun search(query: String) = navigationDataSource.flatMapLatest { it.search(query) }
 
     /**
      * @see MediaDataSource.audio
      */
-    fun audio(audioUri: Uri): Flow<RequestStatus<Audio>> = withMediaItemsDataSourceFlow(audioUri) {
+    fun audio(audioUri: Uri) = withMediaItemsDataSourceFlow(audioUri) {
         audio(audioUri)
     }
 
     /**
      * @see MediaDataSource.album
      */
-    fun album(albumUri: Uri): Flow<RequestStatus<Pair<Album, List<Audio>>>> =
-        withMediaItemsDataSourceFlow(albumUri) {
-            album(albumUri)
-        }
+    fun album(albumUri: Uri) = withMediaItemsDataSourceFlow(albumUri) {
+        album(albumUri)
+    }
 
     /**
      * @see MediaDataSource.artist
      */
-    fun artist(artistUri: Uri): Flow<RequestStatus<Pair<Artist, ArtistWorks>>> =
-        withMediaItemsDataSourceFlow(artistUri) {
-            artist(artistUri)
-        }
+    fun artist(artistUri: Uri) = withMediaItemsDataSourceFlow(artistUri) {
+        artist(artistUri)
+    }
 
     /**
      * @see MediaDataSource.playlist
      */
-    fun playlist(playlistUri: Uri): Flow<RequestStatus<Pair<Playlist, List<Audio?>>>> =
-        withMediaItemsDataSourceFlow(playlistUri) {
-            playlist(playlistUri)
-        }
+    fun playlist(playlistUri: Uri) = withMediaItemsDataSourceFlow(playlistUri) {
+        playlist(playlistUri)
+    }
 
     /**
      * @see MediaDataSource.audioPlaylistsStatus
      */
-    fun audioPlaylistsStatus(audioUri: Uri): Flow<RequestStatus<List<Pair<Playlist, Boolean>>>> =
-        withMediaItemsDataSourceFlow(audioUri) {
-            audioPlaylistsStatus(audioUri)
-        }
+    fun audioPlaylistsStatus(audioUri: Uri) = withMediaItemsDataSourceFlow(audioUri) {
+        audioPlaylistsStatus(audioUri)
+    }
 
     /**
      * @see MediaDataSource.createPlaylist
      */
     suspend fun createPlaylist(
         provider: Provider, name: String
-    ): RequestStatus<Uri> = getDataSource(provider)?.createPlaylist(
+    ) = getDataSource(provider)?.createPlaylist(
         name
     ) ?: RequestStatus.Error(
-        RequestStatus.Error.Type.NOT_FOUND
+        MediaError.NOT_FOUND
     )
 
     /**
      * @see MediaDataSource.renamePlaylist
      */
-    suspend fun renamePlaylist(playlistUri: Uri, name: String): RequestStatus<Unit> =
+    suspend fun renamePlaylist(playlistUri: Uri, name: String) =
         withMediaItemsDataSource(playlistUri) {
             renamePlaylist(playlistUri, name)
         }
@@ -397,15 +382,14 @@
     /**
      * @see MediaDataSource.deletePlaylist
      */
-    suspend fun deletePlaylist(playlistUri: Uri): RequestStatus<Unit> =
-        withMediaItemsDataSource(playlistUri) {
-            deletePlaylist(playlistUri)
-        }
+    suspend fun deletePlaylist(playlistUri: Uri) = withMediaItemsDataSource(playlistUri) {
+        deletePlaylist(playlistUri)
+    }
 
     /**
      * @see MediaDataSource.addAudioToPlaylist
      */
-    suspend fun addAudioToPlaylist(playlistUri: Uri, audioUri: Uri): RequestStatus<Unit> =
+    suspend fun addAudioToPlaylist(playlistUri: Uri, audioUri: Uri) =
         withMediaItemsDataSource(playlistUri, audioUri) {
             addAudioToPlaylist(playlistUri, audioUri)
         }
@@ -413,7 +397,7 @@
     /**
      * @see MediaDataSource.removeAudioFromPlaylist
      */
-    suspend fun removeAudioFromPlaylist(playlistUri: Uri, audioUri: Uri): RequestStatus<Unit> =
+    suspend fun removeAudioFromPlaylist(playlistUri: Uri, audioUri: Uri) =
         withMediaItemsDataSource(playlistUri, audioUri) {
             removeAudioFromPlaylist(playlistUri, audioUri)
         }
@@ -463,11 +447,11 @@
      *   no [MediaDataSource] can handle the given URIs
      */
     private fun <T> withMediaItemsDataSourceFlow(
-        vararg uris: Uri, predicate: MediaDataSource.() -> Flow<RequestStatus<T>>
+        vararg uris: Uri, predicate: MediaDataSource.() -> Flow<RequestStatus<T, MediaError>>
     ) = allProvidersToDataSource.flatMapLatest {
         it.firstOrNull { (_, dataSource) ->
             uris.all { uri -> dataSource.isMediaItemCompatible(uri) }
-        }?.second?.predicate() ?: flowOf(RequestStatus.Error(RequestStatus.Error.Type.NOT_FOUND))
+        }?.second?.predicate() ?: flowOf(RequestStatus.Error(MediaError.NOT_FOUND))
     }
 
     /**
@@ -479,10 +463,10 @@
      *   error if no [MediaDataSource] can handle the given URIs
      */
     private suspend fun <T> withMediaItemsDataSource(
-        vararg uris: Uri, predicate: suspend MediaDataSource.() -> RequestStatus<T>
+        vararg uris: Uri, predicate: suspend MediaDataSource.() -> RequestStatus<T, MediaError>
     ) = allProvidersToDataSource.value.firstOrNull { (_, dataSource) ->
         uris.all { uri -> dataSource.isMediaItemCompatible(uri) }
-    }?.second?.predicate() ?: RequestStatus.Error(RequestStatus.Error.Type.NOT_FOUND)
+    }?.second?.predicate() ?: RequestStatus.Error(MediaError.NOT_FOUND)
 
     companion object {
         private const val LOCAL_PROVIDER_ID = 0L
diff --git a/app/src/main/java/org/lineageos/twelve/services/MediaRepositoryTree.kt b/app/src/main/java/org/lineageos/twelve/services/MediaRepositoryTree.kt
index 3af5a86..8ad3265 100644
--- a/app/src/main/java/org/lineageos/twelve/services/MediaRepositoryTree.kt
+++ b/app/src/main/java/org/lineageos/twelve/services/MediaRepositoryTree.kt
@@ -261,7 +261,7 @@
          * Converts a flow of [RequestStatus] to a one-shot result of [T].
          * Raises an exception on error.
          */
-        private suspend fun <T> Flow<RequestStatus<T>>.toOneShotResult() = mapNotNull {
+        private suspend fun <T, E> Flow<RequestStatus<T, E>>.toOneShotResult() = mapNotNull {
             when (it) {
                 is RequestStatus.Loading -> {
                     null
diff --git a/app/src/main/java/org/lineageos/twelve/viewmodels/ManageProviderViewModel.kt b/app/src/main/java/org/lineageos/twelve/viewmodels/ManageProviderViewModel.kt
index e9fcb16..bb0b907 100644
--- a/app/src/main/java/org/lineageos/twelve/viewmodels/ManageProviderViewModel.kt
+++ b/app/src/main/java/org/lineageos/twelve/viewmodels/ManageProviderViewModel.kt
@@ -20,6 +20,7 @@
 import kotlinx.coroutines.flow.mapLatest
 import kotlinx.coroutines.flow.stateIn
 import kotlinx.coroutines.launch
+import org.lineageos.twelve.datasources.MediaError
 import org.lineageos.twelve.models.ProviderType
 import org.lineageos.twelve.models.RequestStatus
 
@@ -57,8 +58,8 @@
                 providerIds.first, providerIds.second
             ).mapLatest { provider ->
                 provider?.let {
-                    RequestStatus.Success(it)
-                } ?: RequestStatus.Error(RequestStatus.Error.Type.NOT_FOUND)
+                    RequestStatus.Success<_, MediaError>(it)
+                } ?: RequestStatus.Error(MediaError.NOT_FOUND)
             }
         } ?: flowOf(RequestStatus.Success(null))
     }
diff --git a/app/src/main/java/org/lineageos/twelve/viewmodels/NowPlayingViewModel.kt b/app/src/main/java/org/lineageos/twelve/viewmodels/NowPlayingViewModel.kt
index d882028..dc42c54 100644
--- a/app/src/main/java/org/lineageos/twelve/viewmodels/NowPlayingViewModel.kt
+++ b/app/src/main/java/org/lineageos/twelve/viewmodels/NowPlayingViewModel.kt
@@ -157,7 +157,7 @@
         when (playbackState) {
             PlaybackState.BUFFERING -> RequestStatus.Loading()
 
-            else -> RequestStatus.Success(
+            else -> RequestStatus.Success<_, Nothing>(
                 mediaMetadata.artworkUri?.let {
                     Thumbnail(uri = it)
                 } ?: mediaMetadata.artworkData?.let {
diff --git a/app/src/main/java/org/lineageos/twelve/viewmodels/ProvidersViewModel.kt b/app/src/main/java/org/lineageos/twelve/viewmodels/ProvidersViewModel.kt
index 283cded..2fb5545 100644
--- a/app/src/main/java/org/lineageos/twelve/viewmodels/ProvidersViewModel.kt
+++ b/app/src/main/java/org/lineageos/twelve/viewmodels/ProvidersViewModel.kt
@@ -19,7 +19,7 @@
 open class ProvidersViewModel(application: Application) : TwelveViewModel(application) {
     @OptIn(ExperimentalCoroutinesApi::class)
     val providers = mediaRepository.allProviders
-        .mapLatest { RequestStatus.Success(it) }
+        .mapLatest { RequestStatus.Success<_, Nothing>(it) }
         .flowOn(Dispatchers.IO)
         .stateIn(
             viewModelScope,