Simplify repeated code that shows errors (#3762)

Instead of repeating the same if/else check on the error type when setting up the background message, move this in to BackgroundMessageView.

Provide different `setup()` variants, including one that just takes a throwable and a handler, and figures out the correct drawables and error message.

Update and simplify call sites.
This commit is contained in:
Nik Clayton 2023-06-19 23:49:20 +02:00 committed by GitHub
parent af7e883165
commit 153a9ad9c2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 46 additions and 124 deletions

View file

@ -46,7 +46,6 @@ import com.keylesspalace.tusky.util.viewBinding
import com.keylesspalace.tusky.viewmodel.AccountsInListViewModel import com.keylesspalace.tusky.viewmodel.AccountsInListViewModel
import com.keylesspalace.tusky.viewmodel.State import com.keylesspalace.tusky.viewmodel.State
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.io.IOException
import javax.inject.Inject import javax.inject.Inject
private typealias AccountInfo = Pair<TimelineAccount, Boolean> private typealias AccountInfo = Pair<TimelineAccount, Boolean>
@ -146,23 +145,10 @@ class AccountsInListFragment : DialogFragment(), Injectable {
private fun handleError(error: Throwable) { private fun handleError(error: Throwable) {
binding.messageView.show() binding.messageView.show()
val retryAction = { _: View -> binding.messageView.setup(error) { _: View ->
binding.messageView.hide() binding.messageView.hide()
viewModel.load(listId) viewModel.load(listId)
} }
if (error is IOException) {
binding.messageView.setup(
R.drawable.elephant_offline,
R.string.error_network,
retryAction
)
} else {
binding.messageView.setup(
R.drawable.elephant_error,
R.string.error_generic,
retryAction
)
}
} }
private fun onRemoveFromList(accountId: String) { private fun onRemoveFromList(accountId: String) {

View file

@ -40,7 +40,6 @@ import com.keylesspalace.tusky.util.viewBinding
import com.keylesspalace.tusky.util.visible import com.keylesspalace.tusky.util.visible
import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.io.IOException
import javax.inject.Inject import javax.inject.Inject
class ListsForAccountFragment : DialogFragment(), Injectable { class ListsForAccountFragment : DialogFragment(), Injectable {
@ -103,16 +102,7 @@ class ListsForAccountFragment : DialogFragment(), Injectable {
binding.listsView.hide() binding.listsView.hide()
binding.messageView.apply { binding.messageView.apply {
show() show()
setup(error) { load() }
if (error is IOException) {
setup(R.drawable.elephant_offline, R.string.error_network) {
load()
}
} else {
setup(R.drawable.elephant_error, R.string.error_generic) {
load()
}
}
} }
} }
} }

View file

@ -51,7 +51,6 @@ import com.mikepenz.iconics.utils.colorInt
import com.mikepenz.iconics.utils.sizeDp import com.mikepenz.iconics.utils.sizeDp
import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.io.IOException
import javax.inject.Inject import javax.inject.Inject
/** /**
@ -133,12 +132,7 @@ class AccountMediaFragment :
} }
is LoadState.Error -> { is LoadState.Error -> {
binding.statusView.show() binding.statusView.show()
binding.statusView.setup((loadState.refresh as LoadState.Error).error)
if ((loadState.refresh as LoadState.Error).error is IOException) {
binding.statusView.setup(R.drawable.elephant_offline, R.string.error_network, null)
} else {
binding.statusView.setup(R.drawable.elephant_error, R.string.error_generic, null)
}
} }
is LoadState.Loading -> { is LoadState.Loading -> {
binding.progressBar.show() binding.progressBar.show()

View file

@ -393,16 +393,9 @@ class AccountListFragment :
if (adapter.itemCount == 0) { if (adapter.itemCount == 0) {
binding.messageView.show() binding.messageView.show()
if (throwable is IOException) { binding.messageView.setup(throwable) {
binding.messageView.setup(R.drawable.elephant_offline, R.string.error_network) { binding.messageView.hide()
binding.messageView.hide() this.fetchAccounts(null)
this.fetchAccounts(null)
}
} else {
binding.messageView.setup(R.drawable.elephant_error, R.string.error_generic) {
binding.messageView.hide()
this.fetchAccounts(null)
}
} }
} }
} }

View file

@ -64,7 +64,6 @@ import com.mikepenz.iconics.utils.sizeDp
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.io.IOException
import javax.inject.Inject import javax.inject.Inject
import kotlin.time.DurationUnit import kotlin.time.DurationUnit
import kotlin.time.toDuration import kotlin.time.toDuration
@ -139,16 +138,7 @@ class ConversationsFragment :
} }
is LoadState.Error -> { is LoadState.Error -> {
binding.statusView.show() binding.statusView.show()
binding.statusView.setup((loadState.refresh as LoadState.Error).error) { refreshContent() }
if ((loadState.refresh as LoadState.Error).error is IOException) {
binding.statusView.setup(R.drawable.elephant_offline, R.string.error_network) {
refreshContent()
}
} else {
binding.statusView.setup(R.drawable.elephant_error, R.string.error_generic) {
refreshContent()
}
}
} }
is LoadState.Loading -> { is LoadState.Loading -> {
binding.progressBar.show() binding.progressBar.show()

View file

@ -31,7 +31,6 @@ import com.keylesspalace.tusky.util.viewBinding
import com.keylesspalace.tusky.util.visible import com.keylesspalace.tusky.util.visible
import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.io.IOException
import javax.inject.Inject import javax.inject.Inject
class FollowedTagsActivity : class FollowedTagsActivity :
@ -108,11 +107,7 @@ class FollowedTagsActivity :
binding.followedTagsView.hide() binding.followedTagsView.hide()
binding.followedTagsMessageView.show() binding.followedTagsMessageView.show()
val errorState = loadState.refresh as LoadState.Error val errorState = loadState.refresh as LoadState.Error
if (errorState.error is IOException) { binding.followedTagsMessageView.setup(errorState.error) { retry() }
binding.followedTagsMessageView.setup(R.drawable.elephant_offline, R.string.error_network) { retry() }
} else {
binding.followedTagsMessageView.setup(R.drawable.elephant_error, R.string.error_generic) { retry() }
}
Log.w(TAG, "error loading followed hashtags", errorState.error) Log.w(TAG, "error loading followed hashtags", errorState.error)
} else { } else {
binding.followedTagsView.show() binding.followedTagsView.show()

View file

@ -26,7 +26,6 @@ import com.keylesspalace.tusky.util.viewBinding
import com.keylesspalace.tusky.view.EndlessOnScrollListener import com.keylesspalace.tusky.view.EndlessOnScrollListener
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.io.IOException
import javax.inject.Inject import javax.inject.Inject
class InstanceListFragment : Fragment(R.layout.fragment_instance_list), Injectable, InstanceActionListener { class InstanceListFragment : Fragment(R.layout.fragment_instance_list), Injectable, InstanceActionListener {
@ -146,16 +145,9 @@ class InstanceListFragment : Fragment(R.layout.fragment_instance_list), Injectab
if (adapter.itemCount == 0) { if (adapter.itemCount == 0) {
binding.messageView.show() binding.messageView.show()
if (throwable is IOException) { binding.messageView.setup(throwable) {
binding.messageView.setup(R.drawable.elephant_offline, R.string.error_network) { binding.messageView.hide()
binding.messageView.hide() this.fetchInstances(null)
this.fetchInstances(null)
}
} else {
binding.messageView.setup(R.drawable.elephant_error, R.string.error_generic) {
binding.messageView.hide()
this.fetchInstances(null)
}
} }
} }
} }

View file

@ -47,7 +47,6 @@ import com.mikepenz.iconics.utils.colorInt
import com.mikepenz.iconics.utils.sizeDp import com.mikepenz.iconics.utils.sizeDp
import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.io.IOException
import javax.inject.Inject import javax.inject.Inject
class ScheduledStatusActivity : class ScheduledStatusActivity :
@ -102,15 +101,7 @@ class ScheduledStatusActivity :
binding.errorMessageView.show() binding.errorMessageView.show()
val errorState = loadState.refresh as LoadState.Error val errorState = loadState.refresh as LoadState.Error
if (errorState.error is IOException) { binding.errorMessageView.setup(errorState.error) { refreshStatuses() }
binding.errorMessageView.setup(R.drawable.elephant_offline, R.string.error_network) {
refreshStatuses()
}
} else {
binding.errorMessageView.setup(R.drawable.elephant_error, R.string.error_generic) {
refreshStatuses()
}
}
} }
if (loadState.refresh != LoadState.Loading) { if (loadState.refresh != LoadState.Loading) {
binding.swipeRefreshLayout.isRefreshing = false binding.swipeRefreshLayout.isRefreshing = false

View file

@ -79,7 +79,6 @@ import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.core.Observable import io.reactivex.rxjava3.core.Observable
import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.io.IOException
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import javax.inject.Inject import javax.inject.Inject
@ -242,16 +241,7 @@ class TimelineFragment :
} }
is LoadState.Error -> { is LoadState.Error -> {
binding.statusView.show() binding.statusView.show()
binding.statusView.setup((loadState.refresh as LoadState.Error).error) { onRefresh() }
if ((loadState.refresh as LoadState.Error).error is IOException) {
binding.statusView.setup(R.drawable.elephant_offline, R.string.error_network) {
onRefresh()
}
} else {
binding.statusView.setup(R.drawable.elephant_error, R.string.error_generic) {
onRefresh()
}
}
} }
is LoadState.Loading -> { is LoadState.Loading -> {
binding.progressBar.show() binding.progressBar.show()

View file

@ -60,7 +60,6 @@ import kotlinx.coroutines.CoroutineStart
import kotlinx.coroutines.awaitCancellation import kotlinx.coroutines.awaitCancellation
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.io.IOException
import javax.inject.Inject import javax.inject.Inject
class ViewThreadFragment : class ViewThreadFragment :
@ -201,21 +200,7 @@ class ViewThreadFragment :
binding.recyclerView.hide() binding.recyclerView.hide()
binding.statusView.show() binding.statusView.show()
if (uiState.throwable is IOException) { binding.statusView.setup(uiState.throwable) { viewModel.retry(thisThreadsStatusId) }
binding.statusView.setup(
R.drawable.elephant_offline,
R.string.error_network
) {
viewModel.retry(thisThreadsStatusId)
}
} else {
binding.statusView.setup(
R.drawable.elephant_error,
R.string.error_generic
) {
viewModel.retry(thisThreadsStatusId)
}
}
} }
is ThreadUiState.Success -> { is ThreadUiState.Success -> {
if (uiState.statusViewData.none { viewData -> viewData.isDetailed }) { if (uiState.statusViewData.none { viewData -> viewData.isDetailed }) {

View file

@ -53,7 +53,6 @@ import com.mikepenz.iconics.typeface.library.googlematerial.GoogleMaterial
import com.mikepenz.iconics.utils.colorInt import com.mikepenz.iconics.utils.colorInt
import com.mikepenz.iconics.utils.sizeDp import com.mikepenz.iconics.utils.sizeDp
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.io.IOException
import javax.inject.Inject import javax.inject.Inject
class ViewEditsFragment : class ViewEditsFragment :
@ -112,14 +111,6 @@ class ViewEditsFragment :
binding.initialProgressBar.hide() binding.initialProgressBar.hide()
when (uiState.throwable) { when (uiState.throwable) {
is IOException -> {
binding.statusView.setup(
R.drawable.elephant_offline,
R.string.error_network
) {
viewModel.loadEdits(statusId, force = true)
}
}
is ViewEditsViewModel.MissingEditsException -> { is ViewEditsViewModel.MissingEditsException -> {
binding.statusView.setup( binding.statusView.setup(
R.drawable.elephant_friend_empty, R.drawable.elephant_friend_empty,
@ -127,10 +118,7 @@ class ViewEditsFragment :
) )
} }
else -> { else -> {
binding.statusView.setup( binding.statusView.setup(uiState.throwable) {
R.drawable.elephant_error,
R.string.error_generic
) {
viewModel.loadEdits(statusId, force = true) viewModel.loadEdits(statusId, force = true)
} }
} }

View file

@ -1,8 +1,11 @@
package com.keylesspalace.tusky.util package com.keylesspalace.tusky.util
import android.content.Context
import com.keylesspalace.tusky.R
import org.json.JSONException import org.json.JSONException
import org.json.JSONObject import org.json.JSONObject
import retrofit2.HttpException import retrofit2.HttpException
import java.io.IOException
/** /**
* checks if this throwable indicates an error causes by a 4xx/5xx server response and * checks if this throwable indicates an error causes by a 4xx/5xx server response and
@ -24,3 +27,16 @@ fun Throwable.getServerErrorMessage(): String? {
} }
return null return null
} }
/** @return A drawable resource to accompany the error message for this throwable */
fun Throwable.getDrawableRes(): Int = when (this) {
is IOException -> R.drawable.elephant_offline
is HttpException -> R.drawable.elephant_offline
else -> R.drawable.elephant_error
}
/** @return A string error message for this throwable */
fun Throwable.getErrorString(context: Context): String = getServerErrorMessage() ?: when (this) {
is IOException -> context.getString(R.string.error_network)
else -> context.getString(R.string.error_generic)
}

View file

@ -13,6 +13,8 @@ import androidx.annotation.StringRes
import com.keylesspalace.tusky.R import com.keylesspalace.tusky.R
import com.keylesspalace.tusky.databinding.ViewBackgroundMessageBinding import com.keylesspalace.tusky.databinding.ViewBackgroundMessageBinding
import com.keylesspalace.tusky.util.addDrawables import com.keylesspalace.tusky.util.addDrawables
import com.keylesspalace.tusky.util.getDrawableRes
import com.keylesspalace.tusky.util.getErrorString
import com.keylesspalace.tusky.util.visible import com.keylesspalace.tusky.util.visible
/** /**
@ -35,16 +37,26 @@ class BackgroundMessageView @JvmOverloads constructor(
} }
} }
fun setup(throwable: Throwable, listener: ((v: View) -> Unit)? = null) {
setup(throwable.getDrawableRes(), throwable.getErrorString(context), listener)
}
fun setup(
@DrawableRes imageRes: Int,
@StringRes messageRes: Int,
clickListener: ((v: View) -> Unit)? = null
) = setup(imageRes, context.getString(messageRes), clickListener)
/** /**
* Setup image, message and button. * Setup image, message and button.
* If [clickListener] is `null` then the button will be hidden. * If [clickListener] is `null` then the button will be hidden.
*/ */
fun setup( fun setup(
@DrawableRes imageRes: Int, @DrawableRes imageRes: Int,
@StringRes messageRes: Int, message: String,
clickListener: ((v: View) -> Unit)? = null clickListener: ((v: View) -> Unit)? = null
) { ) {
binding.messageTextView.setText(messageRes) binding.messageTextView.text = message
binding.messageTextView.movementMethod = LinkMovementMethod.getInstance() binding.messageTextView.movementMethod = LinkMovementMethod.getInstance()
binding.imageView.setImageResource(imageRes) binding.imageView.setImageResource(imageRes)
binding.button.setOnClickListener(clickListener) binding.button.setOnClickListener(clickListener)

View file

@ -18,7 +18,7 @@
<resources> <resources>
<string name="error_generic">An error occurred.</string> <string name="error_generic">An error occurred.</string>
<string name="error_network">A network error occurred! Please check your connection and try again!</string> <string name="error_network">A network error occurred. Please check your connection and try again.</string>
<string name="error_empty">This cannot be empty.</string> <string name="error_empty">This cannot be empty.</string>
<string name="error_invalid_domain">Invalid domain entered</string> <string name="error_invalid_domain">Invalid domain entered</string>
<string name="error_failed_app_registration">Failed authenticating with that instance. If this persists, try "Login in Browser" from the menu.</string> <string name="error_failed_app_registration">Failed authenticating with that instance. If this persists, try "Login in Browser" from the menu.</string>