diff --git a/app/lint-baseline.xml b/app/lint-baseline.xml
index 79238f25c..79a4921cb 100644
--- a/app/lint-baseline.xml
+++ b/app/lint-baseline.xml
@@ -124,7 +124,7 @@
errorLine2=" ^">
@@ -168,7 +168,7 @@
errorLine2=" ^">
@@ -179,7 +179,7 @@
errorLine2=" ^">
@@ -190,7 +190,7 @@
errorLine2=" ^">
@@ -212,7 +212,7 @@
errorLine2=" ^">
@@ -223,7 +223,7 @@
errorLine2=" ^">
@@ -234,7 +234,7 @@
errorLine2=" ^">
@@ -245,7 +245,7 @@
errorLine2=" ^">
@@ -267,7 +267,7 @@
errorLine2=" ^">
@@ -278,7 +278,7 @@
errorLine2=" ^">
@@ -289,21 +289,21 @@
errorLine2=" ^">
+
+
+
+
-
-
-
-
@@ -322,7 +322,7 @@
errorLine2=" ^">
@@ -333,7 +333,7 @@
errorLine2=" ^">
@@ -344,7 +344,7 @@
errorLine2=" ^">
@@ -355,7 +355,7 @@
errorLine2=" ^">
@@ -366,7 +366,7 @@
errorLine2=" ^">
@@ -377,7 +377,7 @@
errorLine2=" ^">
@@ -388,7 +388,7 @@
errorLine2=" ^">
@@ -399,7 +399,7 @@
errorLine2=" ^">
@@ -410,7 +410,7 @@
errorLine2=" ^">
@@ -421,7 +421,7 @@
errorLine2=" ^">
@@ -432,7 +432,7 @@
errorLine2=" ^">
@@ -443,7 +443,7 @@
errorLine2=" ^">
@@ -454,7 +454,7 @@
errorLine2=" ^">
@@ -465,7 +465,7 @@
errorLine2=" ^">
@@ -476,7 +476,7 @@
errorLine2=" ^">
@@ -509,7 +509,7 @@
errorLine2=" ^">
@@ -520,7 +520,7 @@
errorLine2=" ^">
@@ -531,7 +531,7 @@
errorLine2=" ^">
@@ -542,7 +542,7 @@
errorLine2=" ^">
@@ -553,7 +553,7 @@
errorLine2=" ^">
@@ -564,7 +564,7 @@
errorLine2=" ^">
@@ -575,7 +575,7 @@
errorLine2=" ^">
@@ -586,7 +586,7 @@
errorLine2=" ^">
@@ -597,7 +597,7 @@
errorLine2=" ^">
@@ -608,7 +608,7 @@
errorLine2=" ^">
@@ -619,7 +619,7 @@
errorLine2=" ^">
@@ -630,7 +630,7 @@
errorLine2=" ^">
@@ -641,7 +641,7 @@
errorLine2=" ^">
@@ -652,7 +652,7 @@
errorLine2=" ^">
@@ -663,7 +663,7 @@
errorLine2=" ^">
@@ -674,7 +674,7 @@
errorLine2=" ^">
@@ -685,7 +685,7 @@
errorLine2=" ^">
@@ -696,7 +696,7 @@
errorLine2=" ^">
@@ -729,7 +729,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
@@ -740,7 +740,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
@@ -751,7 +751,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
@@ -762,7 +762,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
@@ -773,7 +773,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
@@ -1290,7 +1290,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
@@ -1301,7 +1301,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~">
@@ -1312,7 +1312,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~">
@@ -1323,7 +1323,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~">
@@ -1334,7 +1334,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
@@ -1345,7 +1345,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
@@ -1356,7 +1356,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
@@ -1367,7 +1367,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
@@ -1378,7 +1378,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
@@ -1389,7 +1389,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
@@ -1400,7 +1400,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~">
@@ -1411,7 +1411,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
@@ -1422,7 +1422,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~">
@@ -1433,7 +1433,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~">
@@ -1444,7 +1444,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~">
@@ -1455,7 +1455,7 @@
errorLine2=" ~~~~~~~~~~~~">
@@ -1466,7 +1466,7 @@
errorLine2=" ~~~~~~~~~~~~~~">
@@ -1477,7 +1477,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~">
@@ -1488,7 +1488,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~">
@@ -1499,7 +1499,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~">
@@ -1510,7 +1510,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~">
@@ -1521,7 +1521,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
@@ -1532,7 +1532,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~">
@@ -1543,7 +1543,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
@@ -1554,7 +1554,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
@@ -1565,7 +1565,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
@@ -1576,7 +1576,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~">
@@ -1587,7 +1587,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
@@ -1598,7 +1598,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~">
@@ -1609,7 +1609,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
@@ -1620,7 +1620,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~">
@@ -1631,7 +1631,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~">
diff --git a/app/src/main/java/app/pachli/TabPreferenceActivity.kt b/app/src/main/java/app/pachli/TabPreferenceActivity.kt
index 2ba80d618..61e73e90e 100644
--- a/app/src/main/java/app/pachli/TabPreferenceActivity.kt
+++ b/app/src/main/java/app/pachli/TabPreferenceActivity.kt
@@ -65,7 +65,6 @@ import java.util.regex.Pattern
import javax.inject.Inject
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
-import timber.log.Timber
@AndroidEntryPoint
class TabPreferenceActivity : BaseActivity(), ItemInteractionListener {
@@ -332,7 +331,6 @@ class TabPreferenceActivity : BaseActivity(), ItemInteractionListener {
selectListBinding.progressBar.hide()
dialog.dismiss()
Snackbar.make(binding.root, R.string.error_list_load, Snackbar.LENGTH_LONG).show()
- Timber.w(it.throwable, "failed to load lists")
}
}
}
diff --git a/app/src/main/java/app/pachli/fragment/SFragment.kt b/app/src/main/java/app/pachli/fragment/SFragment.kt
index 8e9812a4e..fe77e650b 100644
--- a/app/src/main/java/app/pachli/fragment/SFragment.kt
+++ b/app/src/main/java/app/pachli/fragment/SFragment.kt
@@ -129,7 +129,7 @@ abstract class SFragment : Fragment(), StatusActionListener
val msg = getString(
R.string.server_repository_error,
accountManager.activeAccount!!.domain,
- it.msg(requireContext()),
+ it.fmt(requireContext()),
)
Timber.e(msg)
try {
diff --git a/core/common/src/main/kotlin/app/pachli/core/common/PachliError.kt b/core/common/src/main/kotlin/app/pachli/core/common/PachliError.kt
index ae1ecb2f5..adfa08501 100644
--- a/core/common/src/main/kotlin/app/pachli/core/common/PachliError.kt
+++ b/core/common/src/main/kotlin/app/pachli/core/common/PachliError.kt
@@ -19,36 +19,41 @@ package app.pachli.core.common
import android.content.Context
import androidx.annotation.StringRes
+import app.pachli.core.common.string.unicodeWrap
/**
- * Base class for errors throughout the app.
+ * Interface for errors throughout the app.
*
* Derive new error class hierarchies for different components using a sealed
* class hierarchy like so:
*
* ```kotlin
* sealed class Error(
- * @StringRes resourceId: Int,
- * vararg formatArgs: String,
- * source: PachliError? = null,
- * ) : PachliError(resourceId, *formatArgs, source = source) {
+ * @StringRes override val resourceId: Int,
+ * override val formatArgs: Array,
+ * cause: PachliError? = null,
+ * ) : PachliError {
* data object SomeProblem : Error(R.string.error_some_problem)
* data class OutOfRange(val input: Int) : Error(
- * R.string.error_out_of_range
+ * R.string.error_out_of_range, // "Value %1$d is out of range"
* input,
* )
- * data class Fetch(val url: String, val e: PachliError) : Error(
- * R.string.error_fetch,
+ * data class Fetch(val url: String, val cause: PachliError) : Error(
+ * R.string.error_fetch, // "Could not fetch %1$s: %2$s"
* url,
- * source = e,
+ * cause = cause,
* )
* }
* ```
*
- * In this example `SomeProblem` represents an error with no additional context,
- * `OtherProblem` is an error relating to a URL and the URL will be included in
- * the error message, and `WrappedError` represents an error that wraps another
- * error that was the actual cause.
+ * In this example `SomeProblem` represents an error with no additional context.
+ *
+ * `OutOfRange` is an error relating to a single value with no underlying cause.
+ * The value (`input`) will be inserted in the string at `%1$s`.
+ *
+ * `Fetch` is an error relating to a URL with an underlying cause. The URL will be
+ * included in the error message at `%1$s`, and the string representation of the
+ * cause will be included at `%2$s`.
*
* Possible string resources for those errors would be:
*
@@ -57,23 +62,27 @@ import androidx.annotation.StringRes
* Value %1$d is out of range
* Could not fetch %1$s: %2$s
* ```
- *
- * In that last example the `url` parameter will be interpolated as the first
- * placeholder and the error message from the error passed as the `source`
- * parameter will be interpolated as the second placeholder.
- *
- * @property resourceId String resource ID for the error message
- * @property formatArgs 0 or more arguments to interpolate in to the string resource
- * @property source (optional) The underlying error that caused this error
*/
-open class PachliError(
- @StringRes private val resourceId: Int,
- private vararg val formatArgs: String,
- val source: PachliError? = null,
-) {
- fun msg(context: Context): String {
+interface PachliError {
+ /** String resource ID for the error message. */
+ @get:StringRes
+ val resourceId: Int
+
+ /** Arguments to be interpolated in to the string from [resourceId]. */
+ val formatArgs: Array
+
+ /**
+ * The cause of this error. If present the string representation of `cause`
+ * will be set as the last format argument when formatting [resourceId].
+ */
+ val cause: PachliError?
+
+ /**
+ * @return A localised, unicode-wrapped error message for this error.
+ */
+ fun fmt(context: Context): String {
val args = mutableListOf(*formatArgs)
- source?.let { args.add(it.msg(context)) }
- return context.getString(resourceId, *args.toTypedArray())
+ cause?.let { args.add(it.fmt(context)) }
+ return context.getString(resourceId, *args.toTypedArray()).unicodeWrap()
}
}
diff --git a/core/data/src/main/kotlin/app/pachli/core/data/repository/ListsRepository.kt b/core/data/src/main/kotlin/app/pachli/core/data/repository/ListsRepository.kt
index b10138557..e83f8c9d5 100644
--- a/core/data/src/main/kotlin/app/pachli/core/data/repository/ListsRepository.kt
+++ b/core/data/src/main/kotlin/app/pachli/core/data/repository/ListsRepository.kt
@@ -17,6 +17,7 @@
package app.pachli.core.data.repository
+import app.pachli.core.common.PachliError
import app.pachli.core.network.model.MastoList
import app.pachli.core.network.model.TimelineAccount
import app.pachli.core.network.model.UserListRepliesPolicy
@@ -36,26 +37,26 @@ interface HasListId {
}
/** Errors that can be returned from this repository */
-interface ListsError : ApiError {
+interface ListsError : PachliError {
@JvmInline
- value class Create(private val error: ApiError) : ListsError, ApiError by error
+ value class Create(private val error: ApiError) : ListsError, PachliError by error
@JvmInline
- value class Retrieve(private val error: ApiError) : ListsError, ApiError by error
+ value class Retrieve(private val error: ApiError) : ListsError, PachliError by error
@JvmInline
- value class Update(private val error: ApiError) : ListsError, ApiError by error
+ value class Update(private val error: ApiError) : ListsError, PachliError by error
@JvmInline
- value class Delete(private val error: ApiError) : ListsError, ApiError by error
+ value class Delete(private val error: ApiError) : ListsError, PachliError by error
- data class GetListsWithAccount(val accountId: String, private val error: ApiError) : ListsError, ApiError by error
+ data class GetListsWithAccount(val accountId: String, private val error: ApiError) : ListsError, PachliError by error
- data class GetAccounts(override val listId: String, private val error: ApiError) : ListsError, HasListId, ApiError by error
+ data class GetAccounts(override val listId: String, private val error: ApiError) : ListsError, HasListId, PachliError by error
- data class AddAccounts(override val listId: String, private val error: ApiError) : ListsError, HasListId, ApiError by error
+ data class AddAccounts(override val listId: String, private val error: ApiError) : ListsError, HasListId, PachliError by error
- data class DeleteAccounts(override val listId: String, private val error: ApiError) : ListsError, HasListId, ApiError by error
+ data class DeleteAccounts(override val listId: String, private val error: ApiError) : ListsError, HasListId, PachliError by error
}
interface ListsRepository {
diff --git a/core/data/src/main/kotlin/app/pachli/core/data/repository/ServerRepository.kt b/core/data/src/main/kotlin/app/pachli/core/data/repository/ServerRepository.kt
index 498596f76..b14a9464c 100644
--- a/core/data/src/main/kotlin/app/pachli/core/data/repository/ServerRepository.kt
+++ b/core/data/src/main/kotlin/app/pachli/core/data/repository/ServerRepository.kt
@@ -119,13 +119,14 @@ class ServerRepository @Inject constructor(
}
sealed class Error(
- @StringRes resourceId: Int,
- vararg formatArgs: String,
- source: PachliError? = null,
- ) : PachliError(resourceId, *formatArgs, source = source) {
+ @StringRes override val resourceId: Int,
+ override val formatArgs: Array = emptyArray(),
+ override val cause: PachliError? = null,
+ ) : PachliError {
+
data class GetWellKnownNodeInfo(val throwable: Throwable) : Error(
R.string.server_repository_error_get_well_known_node_info,
- throwable.localizedMessage,
+ throwable.localizedMessage?.let { arrayOf(it) }.orEmpty(),
)
data object UnsupportedSchema : Error(
@@ -134,24 +135,23 @@ class ServerRepository @Inject constructor(
data class GetNodeInfo(val url: String, val throwable: Throwable) : Error(
R.string.server_repository_error_get_node_info,
- url,
- throwable.localizedMessage,
+ arrayOf(url, throwable.localizedMessage ?: ""),
)
data class ValidateNodeInfo(val url: String, val error: NodeInfo.Error) : Error(
R.string.server_repository_error_validate_node_info,
- url,
- source = error,
+ arrayOf(url),
+ cause = error,
)
data class GetInstanceInfoV1(val throwable: Throwable) : Error(
R.string.server_repository_error_get_instance_info,
- throwable.localizedMessage,
+ throwable.localizedMessage?.let { arrayOf(it) }.orEmpty(),
)
data class Capabilities(val error: Server.Error) : Error(
R.string.server_repository_error_capabilities,
- source = error,
+ cause = error,
)
}
}
diff --git a/core/network/lint-baseline.xml b/core/network/lint-baseline.xml
index fb7b14703..a84701647 100644
--- a/core/network/lint-baseline.xml
+++ b/core/network/lint-baseline.xml
@@ -1,4 +1,4 @@
-
+
diff --git a/core/network/src/main/kotlin/app/pachli/core/network/Server.kt b/core/network/src/main/kotlin/app/pachli/core/network/Server.kt
index 35323d739..a2904452b 100644
--- a/core/network/src/main/kotlin/app/pachli/core/network/Server.kt
+++ b/core/network/src/main/kotlin/app/pachli/core/network/Server.kt
@@ -17,7 +17,6 @@
package app.pachli.core.network
-import androidx.annotation.StringRes
import androidx.annotation.VisibleForTesting
import androidx.annotation.VisibleForTesting.Companion.PRIVATE
import app.pachli.core.common.PachliError
@@ -296,16 +295,13 @@ data class Server(
}
/** Errors that can occur when processing server capabilities */
- sealed class Error(
- @StringRes resourceId: Int,
- vararg formatArgs: String,
- ) : PachliError(resourceId, *formatArgs) {
+ sealed interface Error : PachliError {
/** Could not parse the server's version string */
- data class UnparseableVersion(val version: String, val throwable: Throwable) : Error(
- R.string.server_error_unparseable_version,
- version,
- throwable.localizedMessage,
- )
+ data class UnparseableVersion(val version: String, val throwable: Throwable) : Error {
+ override val resourceId = R.string.server_error_unparseable_version
+ override val formatArgs: Array = arrayOf(version, throwable.localizedMessage ?: "")
+ override val cause: PachliError? = null
+ }
}
}
diff --git a/core/network/src/main/kotlin/app/pachli/core/network/model/nodeinfo/NodeInfo.kt b/core/network/src/main/kotlin/app/pachli/core/network/model/nodeinfo/NodeInfo.kt
index 831dc9c8d..4395114da 100644
--- a/core/network/src/main/kotlin/app/pachli/core/network/model/nodeinfo/NodeInfo.kt
+++ b/core/network/src/main/kotlin/app/pachli/core/network/model/nodeinfo/NodeInfo.kt
@@ -77,10 +77,11 @@ data class NodeInfo(val software: Software) {
}
sealed class Error(
- @StringRes resourceId: Int,
- vararg formatArgs: String,
- source: PachliError? = null,
- ) : PachliError(resourceId, *formatArgs, source = source) {
+ @StringRes override val resourceId: Int,
+ ) : PachliError {
+ override val formatArgs = emptyArray()
+ override val cause: PachliError? = null
+
data object NoSoftwareBlock : Error(R.string.node_info_error_no_software)
data object NoSoftwareName : Error(R.string.node_info_error_no_software_name)
data object NoSoftwareVersion : Error(R.string.node_info_error_no_software_version)
diff --git a/core/network/src/main/kotlin/app/pachli/core/network/retrofit/apiresult/ApiResult.kt b/core/network/src/main/kotlin/app/pachli/core/network/retrofit/apiresult/ApiResult.kt
index daf3e1f3d..50ff6b7b0 100644
--- a/core/network/src/main/kotlin/app/pachli/core/network/retrofit/apiresult/ApiResult.kt
+++ b/core/network/src/main/kotlin/app/pachli/core/network/retrofit/apiresult/ApiResult.kt
@@ -17,6 +17,10 @@
package app.pachli.core.network.retrofit.apiresult
+import androidx.annotation.StringRes
+import app.pachli.core.common.PachliError
+import app.pachli.core.network.R
+import app.pachli.core.network.extensions.getServerErrorMessage
import com.github.michaelbull.result.Err
import com.github.michaelbull.result.Ok
import com.github.michaelbull.result.Result
@@ -45,50 +49,57 @@ data class ApiResponse(
/**
* A failed response from an API call.
+ *
+ * @param resourceId String resource ID of the error message.
+ * @param throwable The [Throwable] that caused this error. The server
+ * message (if it exists), or [Throwable.getLocalizedMessage] will be
+ * interpolated in to this string at `%1$s`.
*/
-interface ApiError {
- // This has to be Throwable, not Exception, because Retrofit exposes
- // errors as Throwable
- val throwable: Throwable
+sealed class ApiError(
+ @StringRes override val resourceId: Int,
+ val throwable: Throwable,
+) : PachliError {
+ override val formatArgs = (
+ throwable.getServerErrorMessage() ?: throwable.localizedMessage
+ )?.let { arrayOf(it) }.orEmpty()
+ override val cause: PachliError? = null
companion object {
- fun from(exception: Throwable): ApiError {
- return when (exception) {
- is HttpException -> when (exception.code()) {
- in 400..499 -> ClientError.from(exception)
- in 500..599 -> ServerError.from(exception)
- else -> Unknown(exception)
+ fun from(throwable: Throwable): ApiError {
+ return when (throwable) {
+ is HttpException -> when (throwable.code()) {
+ in 400..499 -> ClientError.from(throwable)
+ in 500..599 -> ServerError.from(throwable)
+ else -> Unknown(throwable)
}
- is JsonDataException -> JsonParse(exception)
- is IOException -> IO(exception)
- else -> Unknown(exception)
+ is JsonDataException -> JsonParseError(throwable)
+ is IOException -> IoError(throwable)
+ else -> Unknown(throwable)
}
}
}
- data class Unknown(override val throwable: Throwable) : ApiError
+ data class Unknown(val exception: Throwable) : ApiError(
+ R.string.error_generic_fmt,
+ exception,
+ )
}
-sealed interface HttpError : ApiError {
- override val throwable: HttpException
-
- /**
- * The error message for this error, one of (in preference order):
- *
- * - The error body of the response that created this error
- * - The throwable.message
- * - Literal string "Unknown"
- */
- val message
- get() = throwable.response()?.errorBody()?.string() ?: throwable.message() ?: "Unknown"
-}
+sealed class HttpError(
+ @StringRes override val resourceId: Int,
+ open val exception: HttpException,
+) : ApiError(resourceId, exception)
/** 4xx errors */
-sealed interface ClientError : HttpError {
+sealed class ClientError(
+ @StringRes override val resourceId: Int,
+ exception: HttpException,
+) : HttpError(resourceId, exception) {
companion object {
fun from(exception: HttpException): ClientError {
return when (exception.code()) {
+ 400 -> BadRequest(exception)
401 -> Unauthorized(exception)
404 -> NotFound(exception)
410 -> Gone(exception)
@@ -97,14 +108,32 @@ sealed interface ClientError : HttpError {
}
}
- data class Unauthorized(override val throwable: HttpException) : ClientError
- data class NotFound(override val throwable: HttpException) : ClientError
- data class Gone(override val throwable: HttpException) : ClientError
- data class UnknownClientError(override val throwable: HttpException) : ClientError
+ /** 400 Bad request */
+ data class BadRequest(override val exception: HttpException) :
+ ClientError(R.string.error_generic_fmt, exception)
+
+ /** 401 Unauthorized */
+ data class Unauthorized(override val exception: HttpException) :
+ ClientError(R.string.error_generic_fmt, exception)
+
+ /** 404 Not found */
+ data class NotFound(override val exception: HttpException) :
+ ClientError(R.string.error_404_not_found_fmt, exception)
+
+ /** 410 Gone */
+ data class Gone(override val exception: HttpException) :
+ ClientError(R.string.error_generic_fmt, exception)
+
+ /** All other 4xx client errors */
+ data class UnknownClientError(override val exception: HttpException) :
+ ClientError(R.string.error_generic_fmt, exception)
}
/** 5xx errors */
-sealed interface ServerError : HttpError {
+sealed class ServerError(
+ @StringRes override val resourceId: Int,
+ exception: HttpException,
+) : HttpError(resourceId, exception) {
companion object {
fun from(exception: HttpException): ServerError {
return when (exception.code()) {
@@ -117,15 +146,32 @@ sealed interface ServerError : HttpError {
}
}
- data class Internal(override val throwable: HttpException) : ServerError
- data class NotImplemented(override val throwable: HttpException) : ServerError
- data class BadGateway(override val throwable: HttpException) : ServerError
- data class ServiceUnavailable(override val throwable: HttpException) : ServerError
- data class UnknownServerError(override val throwable: HttpException) : ServerError
+ /** 500 Internal error */
+ data class Internal(override val exception: HttpException) :
+ ServerError(R.string.error_generic_fmt, exception)
+
+ /** 501 Not implemented */
+ data class NotImplemented(override val exception: HttpException) :
+ ServerError(R.string.error_404_not_found_fmt, exception)
+
+ /** 502 Bad gateway */
+ data class BadGateway(override val exception: HttpException) :
+ ServerError(R.string.error_generic_fmt, exception)
+
+ /** 503 Service unavailable */
+ data class ServiceUnavailable(override val exception: HttpException) :
+ ServerError(R.string.error_generic_fmt, exception)
+
+ /** All other 5xx server errors */
+ data class UnknownServerError(override val exception: HttpException) :
+ ServerError(R.string.error_generic_fmt, exception)
}
-data class JsonParse(override val throwable: JsonDataException) : ApiError
-sealed interface NetworkError : ApiError
-data class IO(override val throwable: Exception) : NetworkError
+
+data class JsonParseError(val exception: JsonDataException) :
+ ApiError(R.string.error_json_data_fmt, exception)
+
+data class IoError(val exception: IOException) :
+ ApiError(R.string.error_network_fmt, exception)
/**
* Creates an [ApiResult] from a [Response].
diff --git a/core/network/src/main/res/values-ar/strings.xml b/core/network/src/main/res/values-ar/strings.xml
index a6b3daec9..6bfc567cf 100644
--- a/core/network/src/main/res/values-ar/strings.xml
+++ b/core/network/src/main/res/values-ar/strings.xml
@@ -1,2 +1,6 @@
-
\ No newline at end of file
+
+ وقع خطأ: %s
+ خادمك لا يدعم هذه الميزة: %1$s
+ وقع خطأ في الشبكة: %s
+
diff --git a/core/network/src/main/res/values-de/strings.xml b/core/network/src/main/res/values-de/strings.xml
index a6b3daec9..390147c11 100644
--- a/core/network/src/main/res/values-de/strings.xml
+++ b/core/network/src/main/res/values-de/strings.xml
@@ -1,2 +1,6 @@
-
\ No newline at end of file
+
+ Es ist ein Fehler aufgetreten: %s
+ Dein Server unterstützt diese Funktion nicht: %1$s
+ Ein Netzwerkfehler ist aufgetreten: %s
+
diff --git a/core/network/src/main/res/values-en-rGB/strings.xml b/core/network/src/main/res/values-en-rGB/strings.xml
index a6b3daec9..8fc09b1b3 100644
--- a/core/network/src/main/res/values-en-rGB/strings.xml
+++ b/core/network/src/main/res/values-en-rGB/strings.xml
@@ -1,2 +1,5 @@
-
\ No newline at end of file
+
+ An error occurred: %s
+ Your server does not support this feature: %1$s
+
diff --git a/core/network/src/main/res/values-es/strings.xml b/core/network/src/main/res/values-es/strings.xml
index 65371aed8..c531b01cd 100644
--- a/core/network/src/main/res/values-es/strings.xml
+++ b/core/network/src/main/res/values-es/strings.xml
@@ -4,4 +4,8 @@
versión del programa faltante, vacía o en blanco
no se pudo analizar \"%1$s\" como una versión: %2$s
no hay bloque sobre el programa
-
\ No newline at end of file
+ Ha ocurrido un error: %s
+ Su servidor no soporta esta función: %1$s
+ Tu servidor devolvió una respuesta inválida: %1$s
+ Ha ocurrido un error de red: %s
+
diff --git a/core/network/src/main/res/values-fi/strings.xml b/core/network/src/main/res/values-fi/strings.xml
index a6b3daec9..f6f08f58a 100644
--- a/core/network/src/main/res/values-fi/strings.xml
+++ b/core/network/src/main/res/values-fi/strings.xml
@@ -1,2 +1,6 @@
-
\ No newline at end of file
+
+ Tapahtui virhe: %s
+ Palvelimesi ei tue tätä ominaisuutta: %1$s
+ Tapahtui verkkovirhe: %s
+
diff --git a/core/network/src/main/res/values-fr/strings.xml b/core/network/src/main/res/values-fr/strings.xml
index a6b3daec9..dee920f55 100644
--- a/core/network/src/main/res/values-fr/strings.xml
+++ b/core/network/src/main/res/values-fr/strings.xml
@@ -1,2 +1,6 @@
-
\ No newline at end of file
+
+ Une erreur s\'est produite : %s
+ Votre serveur ne prend pas en charge cette fonctionnalité: %1$s
+ Une erreur réseau s\'est produite : %s
+
diff --git a/core/network/src/main/res/values-in/strings.xml b/core/network/src/main/res/values-in/strings.xml
index a6b3daec9..6f5510fd5 100644
--- a/core/network/src/main/res/values-in/strings.xml
+++ b/core/network/src/main/res/values-in/strings.xml
@@ -1,2 +1,6 @@
-
\ No newline at end of file
+
+ Terjadi error: %s
+ Server Anda tidak mendukung fitur ini: %1$s
+ Jaringan error: %s
+
diff --git a/core/network/src/main/res/values-it/strings.xml b/core/network/src/main/res/values-it/strings.xml
index a6b3daec9..79973eae4 100644
--- a/core/network/src/main/res/values-it/strings.xml
+++ b/core/network/src/main/res/values-it/strings.xml
@@ -1,2 +1,6 @@
-
\ No newline at end of file
+
+ Si è verificato un errore: %s
+ Il tuo server non supporta questa feature: %1$s
+ Si è verificato un errore di rete: %s
+
diff --git a/core/network/src/main/res/values-ja/strings.xml b/core/network/src/main/res/values-ja/strings.xml
index a6b3daec9..d04b24b23 100644
--- a/core/network/src/main/res/values-ja/strings.xml
+++ b/core/network/src/main/res/values-ja/strings.xml
@@ -1,2 +1,6 @@
-
\ No newline at end of file
+
+ エラーが発生しました: %s
+ あなたのサーバーはこの機能をサポートしていません: %1$s
+ ネットワーク エラーが発生しました: %s
+
diff --git a/core/network/src/main/res/values-kab/strings.xml b/core/network/src/main/res/values-kab/strings.xml
index a6b3daec9..7e755e1ba 100644
--- a/core/network/src/main/res/values-kab/strings.xml
+++ b/core/network/src/main/res/values-kab/strings.xml
@@ -1,2 +1,4 @@
-
\ No newline at end of file
+
+ Tella-d tuccḍa: %s
+
diff --git a/core/network/src/main/res/values-nl/strings.xml b/core/network/src/main/res/values-nl/strings.xml
index c75dcd51b..c6e0cf050 100644
--- a/core/network/src/main/res/values-nl/strings.xml
+++ b/core/network/src/main/res/values-nl/strings.xml
@@ -4,4 +4,7 @@
software naam mist, is leeg of blanco
software versie mist, is leeg of blanco
kon \"%1$s\" niet verwerken als een versie: %2$s
-
\ No newline at end of file
+ Er deed zich een fout voor: %s
+ Je server beschikt niet over ondersteuning voor deze feature: %1$s
+ Er deed zich een netwerkfout voor: %s
+
diff --git a/core/network/src/main/res/values-pt-rBR/strings.xml b/core/network/src/main/res/values-pt-rBR/strings.xml
index a6b3daec9..c1e3db390 100644
--- a/core/network/src/main/res/values-pt-rBR/strings.xml
+++ b/core/network/src/main/res/values-pt-rBR/strings.xml
@@ -1,2 +1,6 @@
-
\ No newline at end of file
+
+ Ocorreu um erro: %s
+ Tua instância não suporta este recurso: %1$s
+ Ocorreu um erro de rede: %s
+
diff --git a/core/network/src/main/res/values-sv/strings.xml b/core/network/src/main/res/values-sv/strings.xml
index f5b90cb94..314b0e353 100644
--- a/core/network/src/main/res/values-sv/strings.xml
+++ b/core/network/src/main/res/values-sv/strings.xml
@@ -4,4 +4,7 @@
innehöll inget mjukvarublock
mjukvarunamnet saknas, är tomt eller blankt
Kunde inte analysera \"%1$s\" som en version: %2$s
-
\ No newline at end of file
+ Ett fel har uppstått: %s
+ Din server stöder inte denna funktion: %1$s
+ Ett nätverksfel har uppstått: %s
+
diff --git a/core/network/src/main/res/values/strings.xml b/core/network/src/main/res/values/strings.xml
index 7329ad856..80cecd6bd 100644
--- a/core/network/src/main/res/values/strings.xml
+++ b/core/network/src/main/res/values/strings.xml
@@ -21,4 +21,8 @@
software version is missing, empty, or blank
could not parse \"%1$s\" as a version: %2$s
+ An error occurred: %s
+ Your server does not support this feature: %1$s
+ Your server returned an invalid response: %1$s
+ A network error occurred: %s
diff --git a/core/network/src/test/kotlin/app/pachli/core/network/retrofit/apiresult/ApiResultCallTest.kt b/core/network/src/test/kotlin/app/pachli/core/network/retrofit/apiresult/ApiResultCallTest.kt
index eb278f217..699853d20 100644
--- a/core/network/src/test/kotlin/app/pachli/core/network/retrofit/apiresult/ApiResultCallTest.kt
+++ b/core/network/src/test/kotlin/app/pachli/core/network/retrofit/apiresult/ApiResultCallTest.kt
@@ -89,11 +89,10 @@ class ApiResultCallTest {
override fun onResponse(call: Call>, response: Response>) {
val error = response.body()?.getError() as? ClientError.NotFound
assertThat(error).isInstanceOf(ClientError.NotFound::class.java)
- assertThat(error?.message).isEqualTo("not found")
- val throwable = error?.throwable
- assertThat(throwable).isInstanceOf(HttpException::class.java)
- assertThat(throwable?.code()).isEqualTo(404)
+ val exception = error?.exception
+ assertThat(exception).isInstanceOf(HttpException::class.java)
+ assertThat(exception?.code()).isEqualTo(404)
}
override fun onFailure(call: Call>, t: Throwable) {
@@ -107,7 +106,7 @@ class ApiResultCallTest {
@Test
fun `should parse call with IOException as ApiResult-failure`() {
- val error = Err(IO(IOException()))
+ val error = Err(IoError(IOException()))
networkApiResultCall.enqueue(
object : Callback> {
diff --git a/core/network/src/test/kotlin/app/pachli/core/network/retrofit/apiresult/ApiTest.kt b/core/network/src/test/kotlin/app/pachli/core/network/retrofit/apiresult/ApiTest.kt
index 3b46df0bd..e8348897c 100644
--- a/core/network/src/test/kotlin/app/pachli/core/network/retrofit/apiresult/ApiTest.kt
+++ b/core/network/src/test/kotlin/app/pachli/core/network/retrofit/apiresult/ApiTest.kt
@@ -140,8 +140,8 @@ class ApiTest {
val error = responseObject as? ServerError.Internal
assertThat(error).isInstanceOf(ServerError.Internal::class.java)
- assertThat(error?.throwable?.code()).isEqualTo(500)
- assertThat(error?.throwable?.message()).isEqualTo("Server Error")
+ assertThat(error?.exception?.code()).isEqualTo(500)
+ assertThat(error?.exception?.message()).isEqualTo("Server Error")
}
@Test
@@ -155,8 +155,8 @@ class ApiTest {
val error = responseObject as? ServerError.Internal
assertThat(error).isInstanceOf(ServerError.Internal::class.java)
- assertThat(error?.throwable?.code()).isEqualTo(500)
- assertThat(error?.throwable?.message()).isEqualTo("Server Error")
+ assertThat(error?.exception?.code()).isEqualTo(500)
+ assertThat(error?.exception?.message()).isEqualTo("Server Error")
}
@Test
@@ -167,9 +167,9 @@ class ApiTest {
api.getSiteAsync()
}
- val error = responseObject.getError() as? IO
+ val error = responseObject.getError() as? IoError
- assertThat(error).isInstanceOf(IO::class.java)
+ assertThat(error).isInstanceOf(IoError::class.java)
}
@Test
@@ -177,9 +177,9 @@ class ApiTest {
mockWebServer.enqueue(MockResponse().apply { socketPolicy = SocketPolicy.DISCONNECT_AFTER_REQUEST })
val responseObject = runBlocking { api.getSiteSync() }
- val error = responseObject.getError() as? IO
+ val error = responseObject.getError() as? IoError
- assertThat(error).isInstanceOf(IO::class.java)
+ assertThat(error).isInstanceOf(IoError::class.java)
}
@Test
@@ -189,9 +189,9 @@ class ApiTest {
mockWebServer.enqueue(response)
val responseObject = api.getSitesAsync().getError()
- val error = responseObject as? JsonParse
+ val error = responseObject as? JsonParseError
- assertThat(error).isInstanceOf(JsonParse::class.java)
+ assertThat(error).isInstanceOf(JsonParseError::class.java)
}
@Test
@@ -201,11 +201,11 @@ class ApiTest {
val responseObject = api.getSitesAsync().getError()
- val error = responseObject as? IO
+ val error = responseObject as? IoError
// Moshi reports invalid JSON as an IoException wrapping a JsonEncodingException
- assertThat(error).isInstanceOf(IO::class.java)
- assertThat(error?.throwable).isInstanceOf(JsonEncodingException::class.java)
+ assertThat(error).isInstanceOf(IoError::class.java)
+ assertThat(error?.exception).isInstanceOf(JsonEncodingException::class.java)
}
@Test
diff --git a/core/ui/src/main/kotlin/app/pachli/core/ui/BackgroundMessageView.kt b/core/ui/src/main/kotlin/app/pachli/core/ui/BackgroundMessageView.kt
index 0e6356ed6..c3db41cc7 100644
--- a/core/ui/src/main/kotlin/app/pachli/core/ui/BackgroundMessageView.kt
+++ b/core/ui/src/main/kotlin/app/pachli/core/ui/BackgroundMessageView.kt
@@ -32,6 +32,7 @@ import android.widget.LinearLayout
import android.widget.TextView
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
+import app.pachli.core.common.PachliError
import app.pachli.core.common.extensions.visible
import app.pachli.core.ui.databinding.ViewBackgroundMessageBinding
import app.pachli.core.ui.extensions.getDrawableRes
@@ -99,6 +100,10 @@ class BackgroundMessageView @JvmOverloads constructor(
setup(throwable.getDrawableRes(), throwable.getErrorString(context), listener)
}
+ fun setup(error: PachliError, listener: ((v: View) -> Unit)? = null) {
+ setup(error.getDrawableRes(), error.fmt(context), listener)
+ }
+
fun setup(message: BackgroundMessage, listener: ((v: View) -> Unit)? = null) {
setup(message.drawableRes, message.stringRes, listener)
}
diff --git a/core/ui/src/main/kotlin/app/pachli/core/ui/extensions/PachliErrorExtensions.kt b/core/ui/src/main/kotlin/app/pachli/core/ui/extensions/PachliErrorExtensions.kt
new file mode 100644
index 000000000..49c26d44b
--- /dev/null
+++ b/core/ui/src/main/kotlin/app/pachli/core/ui/extensions/PachliErrorExtensions.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2024 Pachli Association
+ *
+ * This file is a part of Pachli.
+ *
+ * This program is free software; you can redistribute it and/or modify it under the terms of the
+ * GNU General Public License as published by the Free Software Foundation; either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * Pachli is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
+ * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
+ * Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with Pachli; if not,
+ * see .
+ */
+
+package app.pachli.core.ui.extensions
+
+import androidx.annotation.DrawableRes
+import app.pachli.core.common.PachliError
+import app.pachli.core.network.retrofit.apiresult.ClientError
+import app.pachli.core.network.retrofit.apiresult.HttpError
+import app.pachli.core.network.retrofit.apiresult.IoError
+import app.pachli.core.ui.R
+
+/** @return A drawable resource to accompany the error message for this [PachliError]. */
+@DrawableRes
+fun PachliError.getDrawableRes(): Int = when (this) {
+ is IoError -> R.drawable.errorphant_offline
+ is HttpError -> when (this) {
+ is ClientError.NotFound -> R.drawable.elephant_friend_empty
+ else -> R.drawable.errorphant_offline
+ }
+ else -> R.drawable.errorphant_offline
+}
diff --git a/core/ui/src/main/kotlin/app/pachli/core/ui/extensions/ThrowableExtensions.kt b/core/ui/src/main/kotlin/app/pachli/core/ui/extensions/ThrowableExtensions.kt
index 0d631bb41..4f152ce4d 100644
--- a/core/ui/src/main/kotlin/app/pachli/core/ui/extensions/ThrowableExtensions.kt
+++ b/core/ui/src/main/kotlin/app/pachli/core/ui/extensions/ThrowableExtensions.kt
@@ -18,7 +18,9 @@
package app.pachli.core.ui.extensions
import android.content.Context
+import androidx.annotation.DrawableRes
import app.pachli.core.common.string.unicodeWrap
+import app.pachli.core.network.R as NR
import app.pachli.core.network.extensions.getServerErrorMessage
import app.pachli.core.ui.R
import com.squareup.moshi.JsonDataException
@@ -26,6 +28,7 @@ import java.io.IOException
import retrofit2.HttpException
/** @return A drawable resource to accompany the error message for this throwable */
+@DrawableRes
fun Throwable.getDrawableRes(): Int = when (this) {
is IOException -> R.drawable.errorphant_offline
is HttpException -> {
@@ -41,15 +44,15 @@ fun Throwable.getDrawableRes(): Int = when (this) {
/** @return A unicode-wrapped string error message for this throwable */
fun Throwable.getErrorString(context: Context): String = (
getServerErrorMessage() ?: when (this) {
- is IOException -> String.format(context.getString(R.string.error_network_fmt), localizedMessage)
+ is IOException -> String.format(context.getString(NR.string.error_network_fmt), localizedMessage)
is HttpException -> {
if (code() == 404) {
- String.format(context.getString(R.string.error_404_not_found_fmt), localizedMessage)
+ String.format(context.getString(NR.string.error_404_not_found_fmt), localizedMessage)
} else {
- String.format(context.getString(R.string.error_generic_fmt), localizedMessage)
+ String.format(context.getString(NR.string.error_generic_fmt), localizedMessage)
}
}
- is JsonDataException -> String.format(context.getString(R.string.error_json_data_fmt), localizedMessage)
- else -> String.format(context.getString(R.string.error_generic_fmt), localizedMessage)
+ is JsonDataException -> String.format(context.getString(NR.string.error_json_data_fmt), localizedMessage)
+ else -> String.format(context.getString(NR.string.error_generic_fmt), localizedMessage)
}
).unicodeWrap()
diff --git a/core/ui/src/main/res/values-ar/strings.xml b/core/ui/src/main/res/values-ar/strings.xml
index cf4faea65..9ad73019c 100644
--- a/core/ui/src/main/res/values-ar/strings.xml
+++ b/core/ui/src/main/res/values-ar/strings.xml
@@ -1,9 +1,6 @@
حدث خطأ في الشبكة! يرجى التحقق من اتصالك ثم أعد المحاولة!
- وقع خطأ في الشبكة: %s
- وقع خطأ: %s
- خادمك لا يدعم هذه الميزة: %1$s
وقع هناك خطأ.
لا شيء هنا.
أعد المحاولة
diff --git a/core/ui/src/main/res/values-de/strings.xml b/core/ui/src/main/res/values-de/strings.xml
index 0e2acd355..c6ce86808 100644
--- a/core/ui/src/main/res/values-de/strings.xml
+++ b/core/ui/src/main/res/values-de/strings.xml
@@ -1,9 +1,6 @@
Ein Netzwerkfehler ist aufgetreten. Bitte überprüfe deine Internetverbindung und versuche es erneut.
- Ein Netzwerkfehler ist aufgetreten: %s
- Es ist ein Fehler aufgetreten: %s
- Dein Server unterstützt diese Funktion nicht: %1$s
Ein Fehler ist aufgetreten.
Hier ist nichts.
Erneut versuchen
diff --git a/core/ui/src/main/res/values-en-rGB/strings.xml b/core/ui/src/main/res/values-en-rGB/strings.xml
index 181933188..421501210 100644
--- a/core/ui/src/main/res/values-en-rGB/strings.xml
+++ b/core/ui/src/main/res/values-en-rGB/strings.xml
@@ -1,7 +1,5 @@
A network error occurred! Please check your connection and try again!
- An error occurred: %s
- Your server does not support this feature: %1$s
An error occurred.
diff --git a/core/ui/src/main/res/values-es/strings.xml b/core/ui/src/main/res/values-es/strings.xml
index 18a948e71..cf91904b8 100644
--- a/core/ui/src/main/res/values-es/strings.xml
+++ b/core/ui/src/main/res/values-es/strings.xml
@@ -1,9 +1,6 @@
Ha ocurrido un error de red. Por favor, comprueba tu conexión e inténtalo de nuevo.
- Ha ocurrido un error de red: %s
- Ha ocurrido un error: %s
- Su servidor no soporta esta función: %1$s
Ha ocurrido un error.
Nada aquí.
Reintentar
@@ -11,6 +8,5 @@
Perfil
Más
Recargar
- Tu servidor devolvió una respuesta inválida: %1$s
\u0020(🔗 %s)
diff --git a/core/ui/src/main/res/values-fi/strings.xml b/core/ui/src/main/res/values-fi/strings.xml
index 146c0fe9f..0b9d02c7d 100644
--- a/core/ui/src/main/res/values-fi/strings.xml
+++ b/core/ui/src/main/res/values-fi/strings.xml
@@ -1,9 +1,6 @@
Verkkovirhe. Tarkista yhteytesi ja yritä uudelleen.
- Tapahtui verkkovirhe: %s
- Tapahtui virhe: %s
- Palvelimesi ei tue tätä ominaisuutta: %1$s
Tapahtui virhe.
Täällä ei ole mitään.
Yritä uudelleen
diff --git a/core/ui/src/main/res/values-fr/strings.xml b/core/ui/src/main/res/values-fr/strings.xml
index 1ff446828..5ea49a515 100644
--- a/core/ui/src/main/res/values-fr/strings.xml
+++ b/core/ui/src/main/res/values-fr/strings.xml
@@ -1,9 +1,6 @@
Une erreur réseau s’est produite ! Veuillez vérifier votre connexion puis réessayez !
- Une erreur réseau s\'est produite : %s
- Une erreur s\'est produite : %s
- Votre serveur ne prend pas en charge cette fonctionnalité: %1$s
Une erreur s’est produite.
Rien ici.
Réessayer
diff --git a/core/ui/src/main/res/values-in/strings.xml b/core/ui/src/main/res/values-in/strings.xml
index 88f1ddb96..9d840c90b 100644
--- a/core/ui/src/main/res/values-in/strings.xml
+++ b/core/ui/src/main/res/values-in/strings.xml
@@ -1,9 +1,6 @@
Terjadi kesalahan pada jaringan! Harap periksa koneksi Anda dan coba lagi!
- Jaringan error: %s
- Terjadi error: %s
- Server Anda tidak mendukung fitur ini: %1$s
Terjadi kesalahan.
Tidak ada apa pun disini.
Coba lagi
diff --git a/core/ui/src/main/res/values-it/strings.xml b/core/ui/src/main/res/values-it/strings.xml
index 4d8501af4..c70b7d5cc 100644
--- a/core/ui/src/main/res/values-it/strings.xml
+++ b/core/ui/src/main/res/values-it/strings.xml
@@ -1,9 +1,6 @@
Si è verificato un errore di rete. Per favore controlla la tua connessione e riprova.
- Si è verificato un errore di rete: %s
- Si è verificato un errore: %s
- Il tuo server non supporta questa feature: %1$s
Si è verificato un errore.
Qui non c\'è nulla.
Riprova
diff --git a/core/ui/src/main/res/values-ja/strings.xml b/core/ui/src/main/res/values-ja/strings.xml
index e254b7e8b..cf392b0ca 100644
--- a/core/ui/src/main/res/values-ja/strings.xml
+++ b/core/ui/src/main/res/values-ja/strings.xml
@@ -1,9 +1,6 @@
ネットワークエラーが発生しました。接続を確認してもう一度試してください。
- ネットワーク エラーが発生しました: %s
- エラーが発生しました: %s
- あなたのサーバーはこの機能をサポートしていません: %1$s
エラーが発生しました。
何もありません。
再試行
diff --git a/core/ui/src/main/res/values-kab/strings.xml b/core/ui/src/main/res/values-kab/strings.xml
index e34721f39..974294f13 100644
--- a/core/ui/src/main/res/values-kab/strings.xml
+++ b/core/ui/src/main/res/values-kab/strings.xml
@@ -5,7 +5,6 @@
Ɛreḍ tikkelt-nniḍen
Amaɣnu
Ugar
- Tella-d tuccḍa: %s
Smiren
Immed
-
\ No newline at end of file
+
diff --git a/core/ui/src/main/res/values-nl/strings.xml b/core/ui/src/main/res/values-nl/strings.xml
index 0bc42f5ca..d2f292bae 100644
--- a/core/ui/src/main/res/values-nl/strings.xml
+++ b/core/ui/src/main/res/values-nl/strings.xml
@@ -1,9 +1,6 @@
Er deed zich een netwerkfout voor. Controleer je verbinding en probeer opnieuw.
- Er deed zich een netwerkfout voor: %s
- Er deed zich een fout voor: %s
- Je server beschikt niet over ondersteuning voor deze feature: %1$s
Er deed zich een fout voor.
Hier is niets.
Opnieuw proberen
diff --git a/core/ui/src/main/res/values-pt-rBR/strings.xml b/core/ui/src/main/res/values-pt-rBR/strings.xml
index 283742416..fd3e68945 100644
--- a/core/ui/src/main/res/values-pt-rBR/strings.xml
+++ b/core/ui/src/main/res/values-pt-rBR/strings.xml
@@ -1,9 +1,6 @@
Ocorreu um erro de rede. Por favor, verifique tua Internet e tente novamente.
- Ocorreu um erro de rede: %s
- Ocorreu um erro: %s
- Tua instância não suporta este recurso: %1$s
Um erro ocorreu.
Nada aqui.
Tentar novamente
diff --git a/core/ui/src/main/res/values-sv/strings.xml b/core/ui/src/main/res/values-sv/strings.xml
index 944c30fb5..66e01b9b1 100644
--- a/core/ui/src/main/res/values-sv/strings.xml
+++ b/core/ui/src/main/res/values-sv/strings.xml
@@ -1,9 +1,6 @@
Ett nätverksfel uppstod. Kontrollera din anslutning och försök igen.
- Ett nätverksfel har uppstått: %s
- Ett fel har uppstått: %s
- Din server stöder inte denna funktion: %1$s
Ett fel har uppstått.
Ingenting här.
Försök igen
diff --git a/core/ui/src/main/res/values/strings.xml b/core/ui/src/main/res/values/strings.xml
index f1be1dcf8..765821396 100644
--- a/core/ui/src/main/res/values/strings.xml
+++ b/core/ui/src/main/res/values/strings.xml
@@ -1,10 +1,6 @@
A network error occurred. Please check your connection and try again.
- A network error occurred: %s
- An error occurred: %s
- Your server does not support this feature: %1$s
- Your server returned an invalid response: %1$s
An error occurred.
Nothing here.
Retry
diff --git a/feature/lists/src/main/kotlin/app/pachli/feature/lists/AccountsInListFragment.kt b/feature/lists/src/main/kotlin/app/pachli/feature/lists/AccountsInListFragment.kt
index 6584318f7..333dabf3d 100644
--- a/feature/lists/src/main/kotlin/app/pachli/feature/lists/AccountsInListFragment.kt
+++ b/feature/lists/src/main/kotlin/app/pachli/feature/lists/AccountsInListFragment.kt
@@ -34,6 +34,7 @@ import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.ListAdapter
import app.pachli.core.activity.emojify
import app.pachli.core.activity.loadAvatar
+import app.pachli.core.common.PachliError
import app.pachli.core.common.extensions.hide
import app.pachli.core.common.extensions.show
import app.pachli.core.common.extensions.viewBinding
@@ -141,7 +142,7 @@ class AccountsInListFragment : DialogFragment() {
launch {
viewModel.errors.collect {
- handleError(it.throwable)
+ handleError(it)
}
}
}
@@ -172,7 +173,7 @@ class AccountsInListFragment : DialogFragment() {
if (it is Accounts.Loaded) adapter.submitList(it.accounts)
}.onFailure {
binding.messageView.show()
- handleError(it.throwable)
+ handleError(it)
}
}
@@ -196,11 +197,11 @@ class AccountsInListFragment : DialogFragment() {
}
}.onFailure {
Timber.w(it.throwable, "Error searching for accounts in list")
- handleError(it.throwable)
+ handleError(it)
}
}
- private fun handleError(error: Throwable) {
+ private fun handleError(error: PachliError) {
binding.messageView.show()
binding.messageView.setup(error) {
binding.messageView.hide()
diff --git a/feature/lists/src/main/kotlin/app/pachli/feature/lists/ListsActivity.kt b/feature/lists/src/main/kotlin/app/pachli/feature/lists/ListsActivity.kt
index cc5797642..0f6ffaa09 100644
--- a/feature/lists/src/main/kotlin/app/pachli/feature/lists/ListsActivity.kt
+++ b/feature/lists/src/main/kotlin/app/pachli/feature/lists/ListsActivity.kt
@@ -43,14 +43,12 @@ import app.pachli.core.activity.extensions.startActivityWithDefaultTransition
import app.pachli.core.common.extensions.hide
import app.pachli.core.common.extensions.show
import app.pachli.core.common.extensions.viewBinding
-import app.pachli.core.common.string.unicodeWrap
import app.pachli.core.data.repository.Lists
+import app.pachli.core.data.repository.ListsError
import app.pachli.core.data.repository.ListsRepository.Companion.compareByListTitle
import app.pachli.core.navigation.TimelineActivityIntent
import app.pachli.core.network.model.MastoList
import app.pachli.core.network.model.UserListRepliesPolicy
-import app.pachli.core.network.retrofit.apiresult.ApiError
-import app.pachli.core.network.retrofit.apiresult.NetworkError
import app.pachli.core.ui.BackgroundMessage
import app.pachli.core.ui.extensions.await
import app.pachli.feature.lists.databinding.ActivityListsBinding
@@ -113,29 +111,7 @@ class ListsActivity : BaseActivity(), MenuProvider {
lifecycleScope.launch {
viewModel.errors.collect { error ->
- when (error) {
- is Error.Create -> showMessage(
- String.format(
- getString(R.string.error_create_list_fmt),
- error.title.unicodeWrap(),
- error.throwable.message.unicodeWrap(),
- ),
- )
- is Error.Delete -> showMessage(
- String.format(
- getString(R.string.error_delete_list_fmt),
- error.title.unicodeWrap(),
- error.throwable.message.unicodeWrap(),
- ),
- )
- is Error.Update -> showMessage(
- String.format(
- getString(R.string.error_rename_list_fmt),
- error.title.unicodeWrap(),
- error.throwable.message.unicodeWrap(),
- ),
- )
- }
+ showMessage(error.fmt(this@ListsActivity))
}
}
@@ -220,17 +196,13 @@ class ListsActivity : BaseActivity(), MenuProvider {
if (result == AlertDialog.BUTTON_POSITIVE) viewModel.deleteList(list.id, list.title)
}
- private fun bind(state: Result) {
+ private fun bind(state: Result) {
state.onFailure {
binding.listsRecycler.hide()
binding.messageView.show()
binding.swipeRefreshLayout.isRefreshing = false
- if (it is NetworkError) {
- binding.messageView.setup(BackgroundMessage.Network()) { viewModel.refresh() }
- } else {
- binding.messageView.setup(BackgroundMessage.GenericError()) { viewModel.refresh() }
- }
+ binding.messageView.setup(it) { viewModel.refresh() }
}
state.onSuccess { lists ->
diff --git a/feature/lists/src/main/kotlin/app/pachli/feature/lists/ListsForAccountFragment.kt b/feature/lists/src/main/kotlin/app/pachli/feature/lists/ListsForAccountFragment.kt
index 845f841e4..f34325314 100644
--- a/feature/lists/src/main/kotlin/app/pachli/feature/lists/ListsForAccountFragment.kt
+++ b/feature/lists/src/main/kotlin/app/pachli/feature/lists/ListsForAccountFragment.kt
@@ -155,7 +155,7 @@ class ListsForAccountFragment : DialogFragment() {
binding.listsView.hide()
binding.messageView.apply {
show()
- setup(it.throwable) {
+ setup(it) {
viewModel.refresh()
load()
}
diff --git a/feature/lists/src/main/kotlin/app/pachli/feature/lists/ListsForAccountViewModel.kt b/feature/lists/src/main/kotlin/app/pachli/feature/lists/ListsForAccountViewModel.kt
index b4a7990a6..ef6f1be16 100644
--- a/feature/lists/src/main/kotlin/app/pachli/feature/lists/ListsForAccountViewModel.kt
+++ b/feature/lists/src/main/kotlin/app/pachli/feature/lists/ListsForAccountViewModel.kt
@@ -24,7 +24,6 @@ import app.pachli.core.data.repository.Lists
import app.pachli.core.data.repository.ListsError
import app.pachli.core.data.repository.ListsRepository
import app.pachli.core.network.model.MastoList
-import app.pachli.core.network.retrofit.apiresult.ApiError
import com.github.michaelbull.result.Err
import com.github.michaelbull.result.Ok
import com.github.michaelbull.result.Result
@@ -67,7 +66,7 @@ class ListsForAccountViewModel @AssistedInject constructor(
private val _listsWithMembership = MutableStateFlow>(Ok(ListsWithMembership.Loading))
val listsWithMembership = _listsWithMembership.asStateFlow()
- private val _errors = Channel()
+ private val _errors = Channel()
val errors = _errors.receiveAsFlow()
private val listsWithMembershipMap = mutableMapOf()
@@ -164,21 +163,21 @@ class ListsForAccountViewModel @AssistedInject constructor(
* Marker for errors that can be part of the [Result] in the
* [ListsForAccountViewModel.listsWithMembership] flow
*/
- sealed interface FlowError : ApiError
+ sealed interface FlowError : Error
/** Asynchronous errors from network operations */
sealed interface Error : ListsError {
/** Failed to fetch lists, or lists containing a particular account */
@JvmInline
- value class GetListsWithAccount(val error: ListsError.GetListsWithAccount) : FlowError, ListsError by error
+ value class GetListsWithAccount(private val error: ListsError.GetListsWithAccount) : FlowError, Error, ListsError by error
@JvmInline
- value class Retrieve(val error: ListsError.Retrieve) : FlowError, ListsError by error
+ value class Retrieve(private val error: ListsError.Retrieve) : FlowError, Error, ListsError by error
@JvmInline
- value class AddAccounts(val error: ListsError.AddAccounts) : Error, HasListId by error, ListsError by error
+ value class AddAccounts(private val error: ListsError.AddAccounts) : Error, HasListId by error, ListsError by error
@JvmInline
- value class DeleteAccounts(val error: ListsError.DeleteAccounts) : Error, HasListId by error, ListsError by error
+ value class DeleteAccounts(private val error: ListsError.DeleteAccounts) : Error, HasListId by error, ListsError by error
}
}
diff --git a/feature/lists/src/main/kotlin/app/pachli/feature/lists/ListsViewModel.kt b/feature/lists/src/main/kotlin/app/pachli/feature/lists/ListsViewModel.kt
index c11790a22..d32c99c41 100644
--- a/feature/lists/src/main/kotlin/app/pachli/feature/lists/ListsViewModel.kt
+++ b/feature/lists/src/main/kotlin/app/pachli/feature/lists/ListsViewModel.kt
@@ -17,8 +17,10 @@
package app.pachli.feature.lists
+import androidx.annotation.StringRes
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
+import app.pachli.core.common.string.unicodeWrap
import app.pachli.core.data.repository.ListsError
import app.pachli.core.data.repository.ListsRepository
import app.pachli.core.network.model.UserListRepliesPolicy
@@ -32,14 +34,20 @@ import kotlinx.coroutines.flow.getAndUpdate
import kotlinx.coroutines.flow.receiveAsFlow
import kotlinx.coroutines.launch
-sealed interface Error : ListsError {
- val title: String
+sealed class Error(
+ @StringRes override val resourceId: Int,
+ override val formatArgs: Array,
+ override val cause: ListsError? = null,
+) : ListsError {
- data class Create(override val title: String, private val error: ListsError.Create) : Error, ListsError by error
+ data class Create(val title: String, override val cause: ListsError.Create) :
+ Error(R.string.error_create_list_fmt, arrayOf(title.unicodeWrap()), cause)
- data class Delete(override val title: String, private val error: ListsError.Delete) : Error, ListsError by error
+ data class Delete(val title: String, override val cause: ListsError.Delete) :
+ Error(R.string.error_delete_list_fmt, arrayOf(title.unicodeWrap()), cause)
- data class Update(override val title: String, private val error: ListsError.Update) : Error, ListsError by error
+ data class Update(val title: String, override val cause: ListsError.Update) :
+ Error(R.string.error_rename_list_fmt, arrayOf(title.unicodeWrap()), cause)
}
@HiltViewModel