address review: localize connection errors, tighten i18n guard, drop dead resources

- AddSourceViewModel resolves SubsonicException.uiMessage too, so malformed/
  HTTP/truncated responses show localized text instead of the raw parser
  message; add a regression test.
- StringResourcesTest now requires an 'other' quantity in every plural and
  asserts exact placeholder (index and conversion) equality, both of which
  could previously pass while crashing getQuantityString at runtime.
- Map the pre-enum song_downloads.error sentences back to their DownloadError
  so existing failed rows keep their specific message.
- Localize the media-session star/unstar button label.
- Remove 12 unreferenced resources (queue name, filter year, sync_failed, and
  the unused codec_* strings; codec labels stay in AudioEncoding).
- Make buildTree's fallback arguments required; only tests used the defaults.
This commit is contained in:
austinried
2026-10-04 00:30:07 +00:00
parent 3162ac07d5
commit 699a6e86f5
33 changed files with 59 additions and 55 deletions
@@ -130,5 +130,25 @@ class DownloadErrorConverter {
fun fromDownloadError(error: DownloadError): String = error.name
@ColumnTypeConverter
fun toDownloadError(value: String): DownloadError = DownloadError.entries.firstOrNull { it.name == value } ?: DownloadError.Failed
fun toDownloadError(value: String): DownloadError =
DownloadError.entries.firstOrNull { it.name == value }
?: LEGACY_DOWNLOAD_ERRORS[value]
?: DownloadError.Failed
}
private val LEGACY_DOWNLOAD_ERRORS =
mapOf(
"Not enough space to download" to DownloadError.NoSpace,
"Storage is unavailable" to DownloadError.StorageUnavailable,
"Could not write the download" to DownloadError.WriteFailed,
"The server refused to send this track" to DownloadError.ServerRefused,
"The server redirected too many times" to DownloadError.TooManyRedirects,
"The download could not be resumed" to DownloadError.ResumeFailed,
"Download failed" to DownloadError.Failed,
"The downloaded file is missing" to DownloadError.FileMissing,
"The server sent an error instead of the track" to DownloadError.ServerError,
"Can't download: the server address is unavailable" to DownloadError.NoAddress,
"Nothing left to download from this list" to DownloadError.NothingLeft,
"The download stopped unexpectedly" to DownloadError.Stopped,
"Can't download from a source that isn't active" to DownloadError.InactiveSource,
)
@@ -2,7 +2,6 @@ package com.subtracks.data.sync
import android.util.Log
import com.subtracks.R
import com.subtracks.UiException
import com.subtracks.UiMessage
import com.subtracks.data.db.SubtracksDatabase
import com.subtracks.data.repo.QueueRepository
@@ -73,10 +72,6 @@ class SyncManager(
UiMessage(R.string.error_no_server)
}
is UiException -> {
failure.uiMessage
}
else -> {
(failure as? SubsonicException)?.uiMessage
?: UiMessage(R.string.sync_failed_detail, listOf(failure.message ?: ""))
@@ -28,6 +28,7 @@ import coil3.SingletonImageLoader
import com.google.common.util.concurrent.Futures
import com.google.common.util.concurrent.ListenableFuture
import com.subtracks.MainActivity
import com.subtracks.R
import com.subtracks.data.repo.LibraryRepository
import com.subtracks.data.repo.SourceRepository
import com.subtracks.data.source.StarType
@@ -176,7 +177,7 @@ class PlaybackService : MediaSessionService() {
CommandButton
.Builder(if (isStarred) CommandButton.ICON_STAR_FILLED else CommandButton.ICON_STAR_UNFILLED)
.setSessionCommand(STAR_COMMAND)
.setDisplayName(if (isStarred) "Unstar" else "Star")
.setDisplayName(getString(if (isStarred) R.string.actions_unstar else R.string.actions_star))
.build()
private fun streamingClient(): OkHttpClient =
@@ -93,8 +93,8 @@ class DownloadsViewModel(
internal fun buildTree(
songs: List<DownloadedSong>,
unknownArtist: String = "Unknown artist",
unknownAlbum: String = "Unknown album",
unknownArtist: String,
unknownAlbum: String,
): DownloadTree =
DownloadTree(
artists =
@@ -7,6 +7,7 @@ import com.subtracks.R
import com.subtracks.UiException
import com.subtracks.data.repo.DownloadRepository
import com.subtracks.data.repo.SourceRepository
import com.subtracks.data.source.subsonic.SubsonicException
import com.subtracks.data.sync.SyncManager
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
@@ -101,8 +102,8 @@ class AddSourceViewModel(
}
},
{ error ->
(error as? UiException)?.uiMessage?.resolve(resources)
?: resources.getString(R.string.connection_failed, error.message)
val ui = (error as? UiException)?.uiMessage ?: (error as? SubsonicException)?.uiMessage
ui?.resolve(resources) ?: resources.getString(R.string.connection_failed, error.message)
},
),
isError = result.isFailure,