diff --git a/app/lint-baseline.xml b/app/lint-baseline.xml
index 83534adaa..7148a83bc 100644
--- a/app/lint-baseline.xml
+++ b/app/lint-baseline.xml
@@ -1481,6 +1481,28 @@
column="13"/>
+
+
+
+
+
+
+
+
+
+
+
+
{
- onSaveFailure(it.errorMessage)
+ onSaveFailure(it.cause?.getErrorString(this))
}
}
}
diff --git a/app/src/main/java/app/pachli/components/compose/ComposeActivity.kt b/app/src/main/java/app/pachli/components/compose/ComposeActivity.kt
index bc05abc56..f1083489a 100644
--- a/app/src/main/java/app/pachli/components/compose/ComposeActivity.kt
+++ b/app/src/main/java/app/pachli/components/compose/ComposeActivity.kt
@@ -80,7 +80,6 @@ import app.pachli.core.common.extensions.show
import app.pachli.core.common.extensions.viewBinding
import app.pachli.core.common.extensions.visible
import app.pachli.core.common.string.mastodonLength
-import app.pachli.core.common.string.unicodeWrap
import app.pachli.core.data.model.InstanceInfo.Companion.DEFAULT_CHARACTER_LIMIT
import app.pachli.core.data.model.InstanceInfo.Companion.DEFAULT_MAX_MEDIA_ATTACHMENTS
import app.pachli.core.database.model.AccountEntity
@@ -88,13 +87,13 @@ import app.pachli.core.designsystem.R as DR
import app.pachli.core.navigation.ComposeActivityIntent
import app.pachli.core.navigation.ComposeActivityIntent.ComposeOptions
import app.pachli.core.navigation.ComposeActivityIntent.ComposeOptions.InitialCursorPosition
-import app.pachli.core.network.extensions.getServerErrorMessage
import app.pachli.core.network.model.Attachment
import app.pachli.core.network.model.Emoji
import app.pachli.core.network.model.Status
import app.pachli.core.preferences.AppTheme
import app.pachli.core.preferences.PrefKeys
import app.pachli.core.preferences.SharedPreferencesRepository
+import app.pachli.core.ui.extensions.getErrorString
import app.pachli.databinding.ActivityComposeBinding
import app.pachli.util.PickMediaFiles
import app.pachli.util.getInitialLanguages
@@ -513,10 +512,7 @@ class ComposeActivity :
displayTransientMessage(throwable.errorMessage)
} else {
displayTransientMessage(
- getString(
- R.string.error_media_upload_sending_fmt,
- throwable.getServerErrorMessage().unicodeWrap(),
- ),
+ getString(R.string.error_media_upload_sending_fmt, throwable.getErrorString(this@ComposeActivity)),
)
}
}
diff --git a/app/src/main/java/app/pachli/components/compose/MediaUploader.kt b/app/src/main/java/app/pachli/components/compose/MediaUploader.kt
index 256d5d566..920987ea6 100644
--- a/app/src/main/java/app/pachli/components/compose/MediaUploader.kt
+++ b/app/src/main/java/app/pachli/components/compose/MediaUploader.kt
@@ -31,8 +31,8 @@ import app.pachli.R
import app.pachli.components.compose.ComposeActivity.QueuedMedia
import app.pachli.core.common.string.randomAlphanumericString
import app.pachli.core.data.model.InstanceInfo
-import app.pachli.core.network.extensions.getServerErrorMessage
import app.pachli.core.network.model.MediaUploadApi
+import app.pachli.core.ui.extensions.getErrorString
import app.pachli.network.ProgressRequestBody
import app.pachli.util.MEDIA_SIZE_UNKNOWN
import app.pachli.util.getImageSquarePixels
@@ -313,12 +313,7 @@ class MediaUploader @Inject constructor(
send(UploadEvent.FinishedEvent(responseBody.id, uploadResponse.code() == 200))
} else {
val error = HttpException(uploadResponse)
- val errorMessage = error.getServerErrorMessage()
- if (errorMessage == null) {
- throw error
- } else {
- throw UploadServerError(errorMessage)
- }
+ throw UploadServerError(error.getErrorString(context))
}
awaitClose()
diff --git a/app/src/main/java/app/pachli/components/notifications/NotificationsFragment.kt b/app/src/main/java/app/pachli/components/notifications/NotificationsFragment.kt
index 2eb10226c..c87f7e2e9 100644
--- a/app/src/main/java/app/pachli/components/notifications/NotificationsFragment.kt
+++ b/app/src/main/java/app/pachli/components/notifications/NotificationsFragment.kt
@@ -52,15 +52,14 @@ import app.pachli.core.activity.openLink
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.navigation.AttachmentViewData.Companion.list
-import app.pachli.core.network.extensions.getServerErrorMessage
import app.pachli.core.network.model.Filter
import app.pachli.core.network.model.Notification
import app.pachli.core.network.model.Poll
import app.pachli.core.network.model.Status
import app.pachli.core.ui.ActionButtonScrollListener
import app.pachli.core.ui.BackgroundMessage
+import app.pachli.core.ui.extensions.getErrorString
import app.pachli.databinding.FragmentTimelineNotificationsBinding
import app.pachli.fragment.SFragment
import app.pachli.interfaces.AccountActionListener
@@ -235,10 +234,7 @@ class NotificationsFragment :
viewModel.uiError.collect { error ->
val message = getString(
error.message,
- (
- error.throwable.getServerErrorMessage() ?: error.throwable.localizedMessage
- ?: getString(R.string.ui_error_unknown)
- ).unicodeWrap(),
+ error.throwable.getErrorString(requireContext()),
)
Timber.d(error.throwable, message)
val snackbar = Snackbar.make(
diff --git a/app/src/main/java/app/pachli/components/timeline/TimelineFragment.kt b/app/src/main/java/app/pachli/components/timeline/TimelineFragment.kt
index dcd00d4dc..27610c3b7 100644
--- a/app/src/main/java/app/pachli/components/timeline/TimelineFragment.kt
+++ b/app/src/main/java/app/pachli/components/timeline/TimelineFragment.kt
@@ -54,12 +54,10 @@ 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.database.model.TranslationState
import app.pachli.core.model.Timeline
import app.pachli.core.navigation.AccountListActivityIntent
import app.pachli.core.navigation.AttachmentViewData
-import app.pachli.core.network.extensions.getServerErrorMessage
import app.pachli.core.network.model.Poll
import app.pachli.core.network.model.Status
import app.pachli.core.ui.ActionButtonScrollListener
@@ -237,10 +235,7 @@ class TimelineFragment :
viewModel.uiError.collect { error ->
val message = getString(
error.message,
- (
- error.throwable.getServerErrorMessage() ?: error.throwable.localizedMessage
- ?: getString(R.string.ui_error_unknown)
- ).unicodeWrap(),
+ error.throwable.getErrorString(requireContext()),
)
Timber.d(error.throwable, message)
snackbar = Snackbar.make(
diff --git a/app/src/main/java/app/pachli/components/viewthread/ViewThreadFragment.kt b/app/src/main/java/app/pachli/components/viewthread/ViewThreadFragment.kt
index 0ae9b163b..be9b09653 100644
--- a/app/src/main/java/app/pachli/components/viewthread/ViewThreadFragment.kt
+++ b/app/src/main/java/app/pachli/components/viewthread/ViewThreadFragment.kt
@@ -38,13 +38,12 @@ import app.pachli.core.activity.openLink
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.designsystem.R as DR
import app.pachli.core.navigation.AccountListActivityIntent
import app.pachli.core.navigation.AttachmentViewData.Companion.list
-import app.pachli.core.network.extensions.getServerErrorMessage
import app.pachli.core.network.model.Poll
import app.pachli.core.network.model.Status
+import app.pachli.core.ui.extensions.getErrorString
import app.pachli.databinding.FragmentViewThreadBinding
import app.pachli.fragment.SFragment
import app.pachli.interfaces.StatusActionListener
@@ -211,10 +210,7 @@ class ViewThreadFragment :
lifecycleScope.launch {
viewModel.errors.collect { throwable ->
Timber.w(throwable, "failed to load status context")
- val msg = view.context.getString(
- app.pachli.core.ui.R.string.error_generic_fmt,
- throwable.getServerErrorMessage().unicodeWrap(),
- )
+ val msg = throwable.getErrorString(view.context)
Snackbar.make(binding.root, msg, Snackbar.LENGTH_INDEFINITE)
.setAction(app.pachli.core.ui.R.string.action_retry) {
viewModel.retry(thisThreadsStatusId)
diff --git a/app/src/main/java/app/pachli/fragment/SFragment.kt b/app/src/main/java/app/pachli/fragment/SFragment.kt
index 4b06b9a10..0af1634c8 100644
--- a/app/src/main/java/app/pachli/fragment/SFragment.kt
+++ b/app/src/main/java/app/pachli/fragment/SFragment.kt
@@ -60,6 +60,7 @@ import app.pachli.core.network.model.Attachment
import app.pachli.core.network.model.Status
import app.pachli.core.network.parseAsMastodonHtml
import app.pachli.core.network.retrofit.MastodonApi
+import app.pachli.core.ui.extensions.getErrorString
import app.pachli.interfaces.StatusActionListener
import app.pachli.network.ServerRepository
import app.pachli.usecase.TimelineCases
@@ -339,8 +340,7 @@ abstract class SFragment : Fragment(), StatusActionListener
R.id.pin -> {
lifecycleScope.launch {
timelineCases.pin(status.id, !status.isPinned()).onFailure { e: Throwable ->
- val message = e.message
- ?: getString(if (status.isPinned()) R.string.failed_to_unpin else R.string.failed_to_pin)
+ val message = e.getErrorString(requireContext())
Snackbar.make(requireView(), message, Snackbar.LENGTH_LONG).show()
}
}
diff --git a/app/src/main/java/app/pachli/usecase/TimelineCases.kt b/app/src/main/java/app/pachli/usecase/TimelineCases.kt
index 7fab5478c..c0ac6674a 100644
--- a/app/src/main/java/app/pachli/usecase/TimelineCases.kt
+++ b/app/src/main/java/app/pachli/usecase/TimelineCases.kt
@@ -27,7 +27,6 @@ import app.pachli.appstore.PollVoteEvent
import app.pachli.appstore.ReblogEvent
import app.pachli.appstore.StatusDeletedEvent
import app.pachli.components.timeline.CachedTimelineRepository
-import app.pachli.core.network.extensions.getServerErrorMessage
import app.pachli.core.network.model.DeletedStatus
import app.pachli.core.network.model.Poll
import app.pachli.core.network.model.Relationship
@@ -122,7 +121,7 @@ class TimelineCases @Inject constructor(
NetworkResult.success(status)
}, { e ->
Timber.w(e, "Failed to change pin state")
- NetworkResult.failure(TimelineError(e.getServerErrorMessage()))
+ NetworkResult.failure(e)
})
}
@@ -152,5 +151,3 @@ class TimelineCases @Inject constructor(
cachedTimelineRepository.translateUndo(statusViewData)
}
}
-
-class TimelineError(message: String?) : RuntimeException(message)
diff --git a/app/src/main/java/app/pachli/viewmodel/EditProfileViewModel.kt b/app/src/main/java/app/pachli/viewmodel/EditProfileViewModel.kt
index e3ba22795..9afe9f56d 100644
--- a/app/src/main/java/app/pachli/viewmodel/EditProfileViewModel.kt
+++ b/app/src/main/java/app/pachli/viewmodel/EditProfileViewModel.kt
@@ -26,7 +26,6 @@ import app.pachli.appstore.EventHub
import app.pachli.appstore.ProfileEditedEvent
import app.pachli.core.common.string.randomAlphanumericString
import app.pachli.core.data.repository.InstanceInfoRepository
-import app.pachli.core.network.extensions.getServerErrorMessage
import app.pachli.core.network.model.Account
import app.pachli.core.network.model.StringField
import app.pachli.core.network.retrofit.MastodonApi
@@ -151,7 +150,7 @@ class EditProfileViewModel @Inject constructor(
eventHub.dispatch(ProfileEditedEvent(newAccountData))
},
{ throwable ->
- saveData.postValue(Error(errorMessage = throwable.getServerErrorMessage()))
+ saveData.postValue(Error(cause = throwable))
},
)
}
diff --git a/app/src/test/java/app/pachli/usecase/TimelineCasesTest.kt b/app/src/test/java/app/pachli/usecase/TimelineCasesTest.kt
index 7c373b7d2..a9ecbec3e 100644
--- a/app/src/test/java/app/pachli/usecase/TimelineCasesTest.kt
+++ b/app/src/test/java/app/pachli/usecase/TimelineCasesTest.kt
@@ -5,6 +5,7 @@ import app.cash.turbine.test
import app.pachli.appstore.EventHub
import app.pachli.appstore.PinEvent
import app.pachli.components.timeline.CachedTimelineRepository
+import app.pachli.core.network.extensions.getServerErrorMessage
import app.pachli.core.network.model.Status
import app.pachli.core.network.retrofit.MastodonApi
import at.connyduck.calladapter.networkresult.NetworkResult
@@ -54,7 +55,7 @@ class TimelineCasesTest {
}
@Test
- fun `pin failure with server error throws TimelineError with server message`() {
+ fun `pin failure with server error returns failure with server message`() {
api.stub {
onBlocking { pinStatus(statusId) } doReturn NetworkResult.failure(
HttpException(
@@ -68,7 +69,7 @@ class TimelineCasesTest {
runBlocking {
assertEquals(
"Validation Failed: You have already pinned the maximum number of toots",
- timelineCases.pin(statusId, true).exceptionOrNull()?.message,
+ timelineCases.pin(statusId, true).exceptionOrNull()?.getServerErrorMessage(),
)
}
}
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 badfdd19c..0d631bb41 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
@@ -29,7 +29,7 @@ import retrofit2.HttpException
fun Throwable.getDrawableRes(): Int = when (this) {
is IOException -> R.drawable.errorphant_offline
is HttpException -> {
- if (this.code() == 404) {
+ if (code() == 404) {
R.drawable.elephant_friend_empty
} else {
R.drawable.errorphant_offline
@@ -38,10 +38,18 @@ fun Throwable.getDrawableRes(): Int = when (this) {
else -> R.drawable.errorphant_error
}
-/** @return A 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), this.localizedMessage.unicodeWrap())
- is HttpException -> if (this.code() == 404) String.format(context.getString(R.string.error_404_not_found_fmt), this.message.unicodeWrap()) else String.format(context.getString(R.string.error_generic_fmt), this.message.unicodeWrap())
- is JsonDataException -> String.format(context.getString(R.string.error_json_data_fmt), this.message.unicodeWrap())
- else -> String.format(context.getString(R.string.error_generic_fmt), this.message.unicodeWrap())
-}
+/** @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 HttpException -> {
+ if (code() == 404) {
+ String.format(context.getString(R.string.error_404_not_found_fmt), localizedMessage)
+ } else {
+ String.format(context.getString(R.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)
+ }
+ ).unicodeWrap()
diff --git a/feature/login/build.gradle.kts b/feature/login/build.gradle.kts
index 880f3505d..360a4fb6f 100644
--- a/feature/login/build.gradle.kts
+++ b/feature/login/build.gradle.kts
@@ -37,6 +37,7 @@ dependencies {
implementation(projects.core.navigation)
implementation(projects.core.network)
implementation(projects.core.preferences)
+ implementation(projects.core.ui)
implementation(libs.bundles.androidx)
implementation(libs.androidx.webkit)
diff --git a/feature/login/src/main/kotlin/app/pachli/feature/login/LoginActivity.kt b/feature/login/src/main/kotlin/app/pachli/feature/login/LoginActivity.kt
index 8f424417c..dcce37db5 100644
--- a/feature/login/src/main/kotlin/app/pachli/feature/login/LoginActivity.kt
+++ b/feature/login/src/main/kotlin/app/pachli/feature/login/LoginActivity.kt
@@ -34,13 +34,12 @@ import app.pachli.core.activity.extensions.setCloseTransition
import app.pachli.core.activity.extensions.startActivityWithTransition
import app.pachli.core.activity.openLinkInCustomTab
import app.pachli.core.common.extensions.viewBinding
-import app.pachli.core.common.string.unicodeWrap
import app.pachli.core.navigation.LoginActivityIntent
import app.pachli.core.navigation.MainActivityIntent
-import app.pachli.core.network.extensions.getServerErrorMessage
import app.pachli.core.network.model.AccessToken
import app.pachli.core.network.retrofit.MastodonApi
import app.pachli.core.preferences.getNonNullString
+import app.pachli.core.ui.extensions.getErrorString
import app.pachli.feature.login.databinding.ActivityLoginBinding
import at.connyduck.calladapter.networkresult.fold
import com.bumptech.glide.Glide
@@ -196,7 +195,7 @@ class LoginActivity : BaseActivity() {
binding.domainTextInputLayout.error =
String.format(
getString(R.string.error_failed_app_registration_fmt),
- (e.getServerErrorMessage() ?: e.localizedMessage).unicodeWrap(),
+ e.getErrorString(this@LoginActivity),
)
setLoading(false)
Timber.e(e, "Error when creating/registing app")