From c50fa8f10c0caca41e432b8a6672e40d2f4a7796 Mon Sep 17 00:00:00 2001 From: Koitharu Date: Fri, 3 Mar 2023 20:08:38 +0200 Subject: [PATCH] Refactor error handling --- .../base/ui/util/ReversibleActionObserver.kt | 10 +-- .../kotatsu/bookmarks/ui/BookmarksFragment.kt | 21 +++--- .../exceptions/resolve/DialogErrorObserver.kt | 66 +++++++++++++++++ .../core/exceptions/resolve/ErrorObserver.kt | 44 +++++++++++ .../resolve/SnackbarErrorObserver.kt | 47 ++++++++++++ .../kotatsu/core/ui/MangaErrorDialog.kt | 20 ++--- .../kotatsu/details/ui/DetailsActivity.kt | 49 ++++--------- .../kotatsu/explore/ui/ExploreFragment.kt | 16 +--- .../categories/FavouriteCategoriesActivity.kt | 10 +-- .../kotatsu/list/ui/MangaListFragment.kt | 18 +---- .../koitharu/kotatsu/main/ui/MainActivity.kt | 9 +-- .../kotatsu/reader/ui/ReaderActivity.kt | 73 ++++--------------- .../ui/config/ScrobblerConfigActivity.kt | 13 +--- .../kotatsu/shelf/ui/ShelfFragment.kt | 14 +--- .../kotatsu/tracker/ui/feed/FeedFragment.kt | 14 +--- .../koitharu/kotatsu/utils/ext/AndroidExt.kt | 8 ++ .../widget/shelf/ShelfConfigActivity.kt | 12 +-- 17 files changed, 229 insertions(+), 215 deletions(-) create mode 100644 app/src/main/java/org/koitharu/kotatsu/core/exceptions/resolve/DialogErrorObserver.kt create mode 100644 app/src/main/java/org/koitharu/kotatsu/core/exceptions/resolve/ErrorObserver.kt create mode 100644 app/src/main/java/org/koitharu/kotatsu/core/exceptions/resolve/SnackbarErrorObserver.kt diff --git a/app/src/main/java/org/koitharu/kotatsu/base/ui/util/ReversibleActionObserver.kt b/app/src/main/java/org/koitharu/kotatsu/base/ui/util/ReversibleActionObserver.kt index da9504989..f5191615a 100644 --- a/app/src/main/java/org/koitharu/kotatsu/base/ui/util/ReversibleActionObserver.kt +++ b/app/src/main/java/org/koitharu/kotatsu/base/ui/util/ReversibleActionObserver.kt @@ -1,14 +1,12 @@ package org.koitharu.kotatsu.base.ui.util -import android.app.Activity -import android.content.Context -import android.content.ContextWrapper import android.view.View import androidx.lifecycle.Observer import com.google.android.material.snackbar.Snackbar import org.koitharu.kotatsu.R import org.koitharu.kotatsu.base.domain.reverseAsync import org.koitharu.kotatsu.main.ui.owners.BottomNavOwner +import org.koitharu.kotatsu.utils.ext.findActivity class ReversibleActionObserver( private val snackbarHost: View, @@ -29,10 +27,4 @@ class ReversibleActionObserver( } snackbar.show() } - - private fun Context.findActivity(): Activity? = when (this) { - is Activity -> this - is ContextWrapper -> baseContext.findActivity() - else -> null - } } diff --git a/app/src/main/java/org/koitharu/kotatsu/bookmarks/ui/BookmarksFragment.kt b/app/src/main/java/org/koitharu/kotatsu/bookmarks/ui/BookmarksFragment.kt index 870996d83..a2bbe2503 100644 --- a/app/src/main/java/org/koitharu/kotatsu/bookmarks/ui/BookmarksFragment.kt +++ b/app/src/main/java/org/koitharu/kotatsu/bookmarks/ui/BookmarksFragment.kt @@ -1,7 +1,11 @@ package org.koitharu.kotatsu.bookmarks.ui import android.os.Bundle -import android.view.* +import android.view.LayoutInflater +import android.view.Menu +import android.view.MenuItem +import android.view.View +import android.view.ViewGroup import androidx.appcompat.view.ActionMode import androidx.core.graphics.Insets import androidx.core.view.updateLayoutParams @@ -10,7 +14,6 @@ import androidx.fragment.app.viewModels import coil.ImageLoader import com.google.android.material.snackbar.Snackbar import dagger.hilt.android.AndroidEntryPoint -import javax.inject.Inject import org.koitharu.kotatsu.R import org.koitharu.kotatsu.base.domain.reverseAsync import org.koitharu.kotatsu.base.ui.BaseFragment @@ -24,6 +27,7 @@ import org.koitharu.kotatsu.bookmarks.data.ids import org.koitharu.kotatsu.bookmarks.domain.Bookmark import org.koitharu.kotatsu.bookmarks.ui.adapter.BookmarksGroupAdapter import org.koitharu.kotatsu.bookmarks.ui.model.BookmarksGroup +import org.koitharu.kotatsu.core.exceptions.resolve.SnackbarErrorObserver import org.koitharu.kotatsu.databinding.FragmentListSimpleBinding import org.koitharu.kotatsu.details.ui.DetailsActivity import org.koitharu.kotatsu.list.ui.adapter.ListStateHolderListener @@ -32,9 +36,9 @@ import org.koitharu.kotatsu.main.ui.owners.AppBarOwner import org.koitharu.kotatsu.main.ui.owners.SnackbarOwner import org.koitharu.kotatsu.parsers.model.Manga import org.koitharu.kotatsu.reader.ui.ReaderActivity -import org.koitharu.kotatsu.utils.ext.getDisplayMessage import org.koitharu.kotatsu.utils.ext.invalidateNestedItemDecorations import org.koitharu.kotatsu.utils.ext.scaleUpActivityOptionsOf +import javax.inject.Inject @AndroidEntryPoint class BookmarksFragment : @@ -76,7 +80,7 @@ class BookmarksFragment : binding.recyclerView.addItemDecoration(spacingDecoration) viewModel.content.observe(viewLifecycleOwner, ::onListChanged) - viewModel.onError.observe(viewLifecycleOwner, ::onError) + viewModel.onError.observe(viewLifecycleOwner, SnackbarErrorObserver(binding.recyclerView, this)) viewModel.onActionDone.observe(viewLifecycleOwner, ::onActionDone) } @@ -132,6 +136,7 @@ class BookmarksFragment : mode.finish() true } + else -> false } } @@ -154,14 +159,6 @@ class BookmarksFragment : adapter?.items = list } - private fun onError(e: Throwable) { - Snackbar.make( - binding.recyclerView, - e.getDisplayMessage(resources), - Snackbar.LENGTH_SHORT, - ).show() - } - private fun onActionDone(action: ReversibleAction) { val handle = action.handle val length = if (handle == null) Snackbar.LENGTH_SHORT else Snackbar.LENGTH_LONG diff --git a/app/src/main/java/org/koitharu/kotatsu/core/exceptions/resolve/DialogErrorObserver.kt b/app/src/main/java/org/koitharu/kotatsu/core/exceptions/resolve/DialogErrorObserver.kt new file mode 100644 index 000000000..0dc07c7f2 --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/core/exceptions/resolve/DialogErrorObserver.kt @@ -0,0 +1,66 @@ +package org.koitharu.kotatsu.core.exceptions.resolve + +import android.content.DialogInterface +import android.view.View +import androidx.core.util.Consumer +import androidx.fragment.app.Fragment +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import org.koitharu.kotatsu.R +import org.koitharu.kotatsu.core.ui.MangaErrorDialog +import org.koitharu.kotatsu.parsers.exception.ParseException +import org.koitharu.kotatsu.utils.ext.getDisplayMessage + +class DialogErrorObserver( + host: View, + fragment: Fragment?, + resolver: ExceptionResolver?, + private val onResolved: Consumer?, +) : ErrorObserver(host, fragment, resolver, onResolved) { + + constructor( + host: View, + fragment: Fragment?, + ) : this(host, fragment, null, null) + + override fun onChanged(error: Throwable?) { + if (error == null) { + return + } + val listener = DialogListener(error) + val dialogBuilder = MaterialAlertDialogBuilder(activity ?: host.context) + .setMessage(error.getDisplayMessage(host.context.resources)) + .setNegativeButton(R.string.close, listener) + .setOnCancelListener(listener) + if (canResolve(error)) { + dialogBuilder.setPositiveButton(ExceptionResolver.getResolveStringId(error), listener) + } else if (error is ParseException) { + val fm = fragmentManager + if (fm != null) { + dialogBuilder.setPositiveButton(R.string.details) { _, _ -> + MangaErrorDialog.show(fm, error) + } + } + } + val dialog = dialogBuilder.create() + if (activity != null) { + dialog.setOwnerActivity(activity) + } + dialog.show() + } + + private inner class DialogListener( + private val error: Throwable, + ) : DialogInterface.OnClickListener, DialogInterface.OnCancelListener { + + override fun onClick(dialog: DialogInterface?, which: Int) { + when (which) { + DialogInterface.BUTTON_NEGATIVE -> onResolved?.accept(false) + DialogInterface.BUTTON_POSITIVE -> resolve(error) + } + } + + override fun onCancel(dialog: DialogInterface?) { + onResolved?.accept(false) + } + } +} diff --git a/app/src/main/java/org/koitharu/kotatsu/core/exceptions/resolve/ErrorObserver.kt b/app/src/main/java/org/koitharu/kotatsu/core/exceptions/resolve/ErrorObserver.kt new file mode 100644 index 000000000..214cacdc8 --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/core/exceptions/resolve/ErrorObserver.kt @@ -0,0 +1,44 @@ +package org.koitharu.kotatsu.core.exceptions.resolve + +import android.view.View +import androidx.appcompat.app.AppCompatActivity +import androidx.core.util.Consumer +import androidx.fragment.app.Fragment +import androidx.fragment.app.FragmentManager +import androidx.lifecycle.LifecycleCoroutineScope +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.Observer +import androidx.lifecycle.coroutineScope +import kotlinx.coroutines.isActive +import kotlinx.coroutines.launch +import org.koitharu.kotatsu.utils.ext.findActivity +import org.koitharu.kotatsu.utils.ext.viewLifecycleScope + +abstract class ErrorObserver( + protected val host: View, + protected val fragment: Fragment?, + private val resolver: ExceptionResolver?, + private val onResolved: Consumer?, +) : Observer { + + protected val activity = host.context.findActivity() + + private val lifecycleScope: LifecycleCoroutineScope + get() = checkNotNull(fragment?.viewLifecycleScope ?: (activity as? LifecycleOwner)?.lifecycle?.coroutineScope) + + protected val fragmentManager: FragmentManager? + get() = fragment?.childFragmentManager ?: (activity as? AppCompatActivity)?.supportFragmentManager + + protected fun canResolve(error: Throwable): Boolean { + return resolver != null && ExceptionResolver.canResolve(error) + } + + protected fun resolve(error: Throwable) { + lifecycleScope.launch { + val isResolved = resolver?.resolve(error) ?: false + if (isActive) { + onResolved?.accept(isResolved) + } + } + } +} diff --git a/app/src/main/java/org/koitharu/kotatsu/core/exceptions/resolve/SnackbarErrorObserver.kt b/app/src/main/java/org/koitharu/kotatsu/core/exceptions/resolve/SnackbarErrorObserver.kt new file mode 100644 index 000000000..1e1366d76 --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/core/exceptions/resolve/SnackbarErrorObserver.kt @@ -0,0 +1,47 @@ +package org.koitharu.kotatsu.core.exceptions.resolve + +import android.view.View +import androidx.core.util.Consumer +import androidx.fragment.app.Fragment +import com.google.android.material.snackbar.Snackbar +import org.koitharu.kotatsu.R +import org.koitharu.kotatsu.core.ui.MangaErrorDialog +import org.koitharu.kotatsu.main.ui.owners.BottomNavOwner +import org.koitharu.kotatsu.parsers.exception.ParseException +import org.koitharu.kotatsu.utils.ext.getDisplayMessage + +class SnackbarErrorObserver( + host: View, + fragment: Fragment?, + resolver: ExceptionResolver?, + onResolved: Consumer?, +) : ErrorObserver(host, fragment, resolver, onResolved) { + + constructor( + host: View, + fragment: Fragment?, + ) : this(host, fragment, null, null) + + override fun onChanged(error: Throwable?) { + if (error == null) { + return + } + val snackbar = Snackbar.make(host, error.getDisplayMessage(host.context.resources), Snackbar.LENGTH_SHORT) + if (activity is BottomNavOwner) { + snackbar.anchorView = activity.bottomNav + } + if (canResolve(error)) { + snackbar.setAction(ExceptionResolver.getResolveStringId(error)) { + resolve(error) + } + } else if (error is ParseException) { + val fm = fragmentManager + if (fm != null) { + snackbar.setAction(R.string.details) { + MangaErrorDialog.show(fm, error) + } + } + } + snackbar.show() + } +} diff --git a/app/src/main/java/org/koitharu/kotatsu/core/ui/MangaErrorDialog.kt b/app/src/main/java/org/koitharu/kotatsu/core/ui/MangaErrorDialog.kt index 4930ac0fe..e9b2e1697 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/ui/MangaErrorDialog.kt +++ b/app/src/main/java/org/koitharu/kotatsu/core/ui/MangaErrorDialog.kt @@ -12,24 +12,20 @@ import androidx.fragment.app.FragmentManager import com.google.android.material.dialog.MaterialAlertDialogBuilder import org.koitharu.kotatsu.R import org.koitharu.kotatsu.base.ui.AlertDialogFragment -import org.koitharu.kotatsu.core.model.parcelable.ParcelableManga import org.koitharu.kotatsu.databinding.DialogMangaErrorBinding -import org.koitharu.kotatsu.parsers.model.Manga +import org.koitharu.kotatsu.parsers.exception.ParseException import org.koitharu.kotatsu.utils.ext.report -import org.koitharu.kotatsu.utils.ext.requireParcelable import org.koitharu.kotatsu.utils.ext.requireSerializable import org.koitharu.kotatsu.utils.ext.withArgs class MangaErrorDialog : AlertDialogFragment() { - private lateinit var error: Throwable - private lateinit var manga: Manga + private lateinit var exception: ParseException override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val args = requireArguments() - manga = args.requireParcelable(ARG_MANGA).manga - error = args.requireSerializable(ARG_ERROR) + exception = args.requireSerializable(ARG_ERROR) } override fun onInflateView(inflater: LayoutInflater, container: ViewGroup?): DialogMangaErrorBinding { @@ -42,8 +38,8 @@ class MangaErrorDialog : AlertDialogFragment() { movementMethod = LinkMovementMethod.getInstance() text = context.getString( R.string.manga_error_description_pattern, - this@MangaErrorDialog.error.message?.htmlEncode().orEmpty(), - manga.publicUrl, + exception.message?.htmlEncode().orEmpty(), + exception.url, ).parseAsHtml(HtmlCompat.FROM_HTML_MODE_LEGACY) } } @@ -54,7 +50,7 @@ class MangaErrorDialog : AlertDialogFragment() { .setNegativeButton(android.R.string.cancel, null) .setPositiveButton(R.string.report) { _, _ -> dismiss() - error.report() + exception.report() }.setTitle(R.string.error_occurred) } @@ -62,10 +58,8 @@ class MangaErrorDialog : AlertDialogFragment() { private const val TAG = "MangaErrorDialog" private const val ARG_ERROR = "error" - private const val ARG_MANGA = "manga" - fun show(fm: FragmentManager, manga: Manga, error: Throwable) = MangaErrorDialog().withArgs(2) { - putParcelable(ARG_MANGA, ParcelableManga(manga, false)) + fun show(fm: FragmentManager, error: ParseException) = MangaErrorDialog().withArgs(1) { putSerializable(ARG_ERROR, error) }.show(fm, TAG) } diff --git a/app/src/main/java/org/koitharu/kotatsu/details/ui/DetailsActivity.kt b/app/src/main/java/org/koitharu/kotatsu/details/ui/DetailsActivity.kt index 9e4cf2886..7542552dc 100644 --- a/app/src/main/java/org/koitharu/kotatsu/details/ui/DetailsActivity.kt +++ b/app/src/main/java/org/koitharu/kotatsu/details/ui/DetailsActivity.kt @@ -29,10 +29,9 @@ import org.koitharu.kotatsu.R import org.koitharu.kotatsu.base.domain.MangaIntent import org.koitharu.kotatsu.base.ui.BaseActivity import org.koitharu.kotatsu.base.ui.widgets.BottomSheetHeaderBar -import org.koitharu.kotatsu.core.exceptions.resolve.ExceptionResolver +import org.koitharu.kotatsu.core.exceptions.resolve.SnackbarErrorObserver import org.koitharu.kotatsu.core.model.parcelable.ParcelableManga import org.koitharu.kotatsu.core.os.ShortcutsUpdater -import org.koitharu.kotatsu.core.ui.MangaErrorDialog import org.koitharu.kotatsu.databinding.ActivityDetailsBinding import org.koitharu.kotatsu.details.service.MangaPrefetchService import org.koitharu.kotatsu.details.ui.model.ChapterListItem @@ -45,7 +44,6 @@ import org.koitharu.kotatsu.reader.ui.ReaderState import org.koitharu.kotatsu.utils.ViewBadge import org.koitharu.kotatsu.utils.ext.assistedViewModels import org.koitharu.kotatsu.utils.ext.getDisplayMessage -import org.koitharu.kotatsu.utils.ext.isReportable import org.koitharu.kotatsu.utils.ext.setNavigationBarTransparentCompat import org.koitharu.kotatsu.utils.ext.textAndVisible import javax.inject.Inject @@ -105,7 +103,19 @@ class DetailsActivity : viewModel.manga.observe(this, ::onMangaUpdated) viewModel.newChaptersCount.observe(this, ::onNewChaptersChanged) viewModel.onMangaRemoved.observe(this, ::onMangaRemoved) - viewModel.onError.observe(this, ::onError) + viewModel.onError.observe( + this, + SnackbarErrorObserver( + host = binding.containerDetails, + fragment = null, + resolver = exceptionResolver, + onResolved = { isResolved -> + if (isResolved) { + viewModel.reload() + } + }, + ), + ) viewModel.onShowToast.observe(this) { makeSnackbar(getString(it), Snackbar.LENGTH_SHORT).show() } @@ -191,37 +201,6 @@ class DetailsActivity : finishAfterTransition() } - private fun onError(e: Throwable) { - val manga = viewModel.manga.value - when { - ExceptionResolver.canResolve(e) -> { - resolveError(e) - } - - manga == null -> { - Toast.makeText(this, e.getDisplayMessage(resources), Toast.LENGTH_LONG).show() - finishAfterTransition() - } - - else -> { - val snackbar = makeSnackbar( - e.getDisplayMessage(resources), - if (viewModel.manga.value?.chapters == null) { - Snackbar.LENGTH_INDEFINITE - } else { - Snackbar.LENGTH_LONG - }, - ) - if (e.isReportable()) { - snackbar.setAction(R.string.details) { - MangaErrorDialog.show(supportFragmentManager, manga, e) - } - } - snackbar.show() - } - } - } - override fun onWindowInsetsChanged(insets: Insets) { binding.root.updatePadding( left = insets.left, diff --git a/app/src/main/java/org/koitharu/kotatsu/explore/ui/ExploreFragment.kt b/app/src/main/java/org/koitharu/kotatsu/explore/ui/ExploreFragment.kt index ebcf96e26..7d8e326b8 100644 --- a/app/src/main/java/org/koitharu/kotatsu/explore/ui/ExploreFragment.kt +++ b/app/src/main/java/org/koitharu/kotatsu/explore/ui/ExploreFragment.kt @@ -13,7 +13,6 @@ import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import coil.ImageLoader -import com.google.android.material.snackbar.Snackbar import dagger.hilt.android.AndroidEntryPoint import org.koitharu.kotatsu.R import org.koitharu.kotatsu.base.ui.BaseFragment @@ -22,6 +21,7 @@ import org.koitharu.kotatsu.base.ui.util.RecyclerViewOwner import org.koitharu.kotatsu.base.ui.util.ReversibleActionObserver import org.koitharu.kotatsu.base.ui.util.SpanSizeResolver import org.koitharu.kotatsu.bookmarks.ui.BookmarksActivity +import org.koitharu.kotatsu.core.exceptions.resolve.SnackbarErrorObserver import org.koitharu.kotatsu.databinding.FragmentExploreBinding import org.koitharu.kotatsu.details.ui.DetailsActivity import org.koitharu.kotatsu.explore.ui.adapter.ExploreAdapter @@ -29,14 +29,12 @@ import org.koitharu.kotatsu.explore.ui.adapter.ExploreListEventListener import org.koitharu.kotatsu.explore.ui.model.ExploreItem import org.koitharu.kotatsu.favourites.ui.categories.FavouriteCategoriesActivity import org.koitharu.kotatsu.history.ui.HistoryActivity -import org.koitharu.kotatsu.main.ui.owners.BottomNavOwner import org.koitharu.kotatsu.parsers.model.Manga import org.koitharu.kotatsu.parsers.model.MangaSource import org.koitharu.kotatsu.search.ui.MangaListActivity import org.koitharu.kotatsu.settings.SettingsActivity import org.koitharu.kotatsu.suggestions.ui.SuggestionsActivity import org.koitharu.kotatsu.utils.ext.addMenuProvider -import org.koitharu.kotatsu.utils.ext.getDisplayMessage import javax.inject.Inject @AndroidEntryPoint @@ -74,7 +72,7 @@ class ExploreFragment : viewModel.content.observe(viewLifecycleOwner) { exploreAdapter?.items = it } - viewModel.onError.observe(viewLifecycleOwner, ::onError) + viewModel.onError.observe(viewLifecycleOwner, SnackbarErrorObserver(binding.recyclerView, this)) viewModel.onOpenManga.observe(viewLifecycleOwner, ::onOpenManga) viewModel.onActionDone.observe(viewLifecycleOwner, ReversibleActionObserver(binding.recyclerView)) viewModel.isGrid.observe(viewLifecycleOwner, ::onGridModeChanged) @@ -129,16 +127,6 @@ class ExploreFragment : override fun onEmptyActionClick() = onManageClick(requireView()) - private fun onError(e: Throwable) { - val snackbar = Snackbar.make( - binding.recyclerView, - e.getDisplayMessage(resources), - Snackbar.LENGTH_SHORT, - ) - snackbar.anchorView = (activity as? BottomNavOwner)?.bottomNav - snackbar.show() - } - private fun onOpenManga(manga: Manga) { val intent = DetailsActivity.newIntent(context ?: return, manga) startActivity(intent) diff --git a/app/src/main/java/org/koitharu/kotatsu/favourites/ui/categories/FavouriteCategoriesActivity.kt b/app/src/main/java/org/koitharu/kotatsu/favourites/ui/categories/FavouriteCategoriesActivity.kt index ea61ed226..3be5f542f 100644 --- a/app/src/main/java/org/koitharu/kotatsu/favourites/ui/categories/FavouriteCategoriesActivity.kt +++ b/app/src/main/java/org/koitharu/kotatsu/favourites/ui/categories/FavouriteCategoriesActivity.kt @@ -18,11 +18,11 @@ import androidx.core.view.updatePadding import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.RecyclerView import coil.ImageLoader -import com.google.android.material.snackbar.Snackbar import dagger.hilt.android.AndroidEntryPoint import org.koitharu.kotatsu.R import org.koitharu.kotatsu.base.ui.BaseActivity import org.koitharu.kotatsu.base.ui.list.ListSelectionController +import org.koitharu.kotatsu.core.exceptions.resolve.SnackbarErrorObserver import org.koitharu.kotatsu.core.model.FavouriteCategory import org.koitharu.kotatsu.databinding.ActivityCategoriesBinding import org.koitharu.kotatsu.favourites.ui.FavouritesActivity @@ -31,7 +31,6 @@ import org.koitharu.kotatsu.favourites.ui.categories.edit.FavouritesCategoryEdit import org.koitharu.kotatsu.list.ui.adapter.ListStateHolderListener import org.koitharu.kotatsu.list.ui.model.ListModel import org.koitharu.kotatsu.parsers.model.SortOrder -import org.koitharu.kotatsu.utils.ext.getDisplayMessage import org.koitharu.kotatsu.utils.ext.scaleUpActivityOptionsOf import javax.inject.Inject @@ -72,7 +71,7 @@ class FavouriteCategoriesActivity : onBackPressedDispatcher.addCallback(exitReorderModeCallback) viewModel.detalizedCategories.observe(this, ::onCategoriesChanged) - viewModel.onError.observe(this, ::onError) + viewModel.onError.observe(this, SnackbarErrorObserver(binding.recyclerView, null)) viewModel.isInReorderMode.observe(this, ::onReorderModeChanged) } @@ -146,11 +145,6 @@ class FavouriteCategoriesActivity : invalidateOptionsMenu() } - private fun onError(e: Throwable) { - Snackbar.make(binding.recyclerView, e.getDisplayMessage(resources), Snackbar.LENGTH_LONG) - .show() - } - private fun onReorderModeChanged(isReorderMode: Boolean) { val transition = Fade().apply { duration = resources.getInteger(android.R.integer.config_shortAnimTime).toLong() diff --git a/app/src/main/java/org/koitharu/kotatsu/list/ui/MangaListFragment.kt b/app/src/main/java/org/koitharu/kotatsu/list/ui/MangaListFragment.kt index 7527a2c44..cab87b11a 100644 --- a/app/src/main/java/org/koitharu/kotatsu/list/ui/MangaListFragment.kt +++ b/app/src/main/java/org/koitharu/kotatsu/list/ui/MangaListFragment.kt @@ -31,9 +31,8 @@ import org.koitharu.kotatsu.base.ui.list.decor.SpacingItemDecoration import org.koitharu.kotatsu.base.ui.list.decor.TypedSpacingItemDecoration import org.koitharu.kotatsu.base.ui.list.fastscroll.FastScroller import org.koitharu.kotatsu.base.ui.util.ReversibleAction -import org.koitharu.kotatsu.browser.cloudflare.CloudFlareDialog -import org.koitharu.kotatsu.core.exceptions.CloudFlareProtectedException import org.koitharu.kotatsu.core.exceptions.resolve.ExceptionResolver +import org.koitharu.kotatsu.core.exceptions.resolve.SnackbarErrorObserver import org.koitharu.kotatsu.core.prefs.ListMode import org.koitharu.kotatsu.databinding.FragmentListBinding import org.koitharu.kotatsu.details.ui.DetailsActivity @@ -55,7 +54,6 @@ import org.koitharu.kotatsu.search.ui.MangaListActivity import org.koitharu.kotatsu.utils.ShareHelper import org.koitharu.kotatsu.utils.ext.addMenuProvider import org.koitharu.kotatsu.utils.ext.clearItemDecorations -import org.koitharu.kotatsu.utils.ext.getDisplayMessage import org.koitharu.kotatsu.utils.ext.getThemeColor import org.koitharu.kotatsu.utils.ext.measureHeight import org.koitharu.kotatsu.utils.ext.resolveDp @@ -128,7 +126,7 @@ abstract class MangaListFragment : viewModel.gridScale.observe(viewLifecycleOwner, ::onGridScaleChanged) viewModel.isLoading.observe(viewLifecycleOwner, ::onLoadingStateChanged) viewModel.content.observe(viewLifecycleOwner, ::onListChanged) - viewModel.onError.observe(viewLifecycleOwner, ::onError) + viewModel.onError.observe(viewLifecycleOwner, SnackbarErrorObserver(binding.recyclerView, this)) viewModel.onActionDone.observe(viewLifecycleOwner, ::onActionDone) } @@ -175,18 +173,6 @@ abstract class MangaListFragment : listAdapter?.setItems(list, listCommitCallback) } - private fun onError(e: Throwable) { - if (e is CloudFlareProtectedException) { - CloudFlareDialog.newInstance(e.url, e.headers).show(childFragmentManager, CloudFlareDialog.TAG) - } else { - Snackbar.make( - binding.recyclerView, - e.getDisplayMessage(resources), - Snackbar.LENGTH_SHORT, - ).show() - } - } - private fun onActionDone(action: ReversibleAction) { val handle = action.handle val length = if (handle == null) Snackbar.LENGTH_SHORT else Snackbar.LENGTH_LONG diff --git a/app/src/main/java/org/koitharu/kotatsu/main/ui/MainActivity.kt b/app/src/main/java/org/koitharu/kotatsu/main/ui/MainActivity.kt index 87983c024..a132031fc 100644 --- a/app/src/main/java/org/koitharu/kotatsu/main/ui/MainActivity.kt +++ b/app/src/main/java/org/koitharu/kotatsu/main/ui/MainActivity.kt @@ -33,7 +33,6 @@ import com.google.android.material.appbar.AppBarLayout.LayoutParams.SCROLL_FLAG_ import com.google.android.material.appbar.AppBarLayout.LayoutParams.SCROLL_FLAG_NO_SCROLL import com.google.android.material.appbar.AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL import com.google.android.material.appbar.AppBarLayout.LayoutParams.SCROLL_FLAG_SNAP -import com.google.android.material.snackbar.Snackbar import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -41,6 +40,7 @@ import kotlinx.coroutines.withContext import org.koitharu.kotatsu.R import org.koitharu.kotatsu.base.ui.BaseActivity import org.koitharu.kotatsu.base.ui.widgets.SlidingBottomNavigationView +import org.koitharu.kotatsu.core.exceptions.resolve.SnackbarErrorObserver import org.koitharu.kotatsu.databinding.ActivityMainBinding import org.koitharu.kotatsu.details.service.MangaPrefetchService import org.koitharu.kotatsu.details.ui.DetailsActivity @@ -62,7 +62,6 @@ import org.koitharu.kotatsu.suggestions.ui.SuggestionsWorker import org.koitharu.kotatsu.tracker.work.TrackWorker import org.koitharu.kotatsu.utils.VoiceInputContract import org.koitharu.kotatsu.utils.ext.drawableEnd -import org.koitharu.kotatsu.utils.ext.getDisplayMessage import org.koitharu.kotatsu.utils.ext.hideKeyboard import org.koitharu.kotatsu.utils.ext.resolve import org.koitharu.kotatsu.utils.ext.scaleUpActivityOptionsOf @@ -132,7 +131,7 @@ class MainActivity : } viewModel.onOpenReader.observe(this, this::onOpenReader) - viewModel.onError.observe(this, this::onError) + viewModel.onError.observe(this, SnackbarErrorObserver(binding.container, null)) viewModel.isLoading.observe(this, this::onLoadingStateChanged) viewModel.isResumeEnabled.observe(this, this::onResumeEnabledChanged) viewModel.counters.observe(this, ::onCountersChanged) @@ -252,10 +251,6 @@ class MainActivity : startActivity(ReaderActivity.newIntent(this, manga), options) } - private fun onError(e: Throwable) { - Snackbar.make(binding.container, e.getDisplayMessage(resources), Snackbar.LENGTH_SHORT).show() - } - private fun onCountersChanged(counters: SparseIntArray) { repeat(counters.size) { i -> val id = counters.keyAt(i) diff --git a/app/src/main/java/org/koitharu/kotatsu/reader/ui/ReaderActivity.kt b/app/src/main/java/org/koitharu/kotatsu/reader/ui/ReaderActivity.kt index e4f97f1f5..b02b2896b 100644 --- a/app/src/main/java/org/koitharu/kotatsu/reader/ui/ReaderActivity.kt +++ b/app/src/main/java/org/koitharu/kotatsu/reader/ui/ReaderActivity.kt @@ -1,7 +1,6 @@ package org.koitharu.kotatsu.reader.ui import android.content.Context -import android.content.DialogInterface import android.content.Intent import android.net.Uri import android.os.Bundle @@ -23,7 +22,6 @@ import androidx.core.view.isGone import androidx.core.view.isVisible import androidx.core.view.updatePadding import androidx.lifecycle.lifecycleScope -import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.snackbar.Snackbar import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.Dispatchers @@ -34,7 +32,7 @@ import org.koitharu.kotatsu.R import org.koitharu.kotatsu.base.domain.MangaIntent import org.koitharu.kotatsu.base.ui.BaseFullscreenActivity import org.koitharu.kotatsu.bookmarks.domain.Bookmark -import org.koitharu.kotatsu.core.exceptions.resolve.ExceptionResolver +import org.koitharu.kotatsu.core.exceptions.resolve.DialogErrorObserver import org.koitharu.kotatsu.core.model.parcelable.ParcelableManga import org.koitharu.kotatsu.core.prefs.ReaderMode import org.koitharu.kotatsu.databinding.ActivityReaderBinding @@ -51,13 +49,10 @@ import org.koitharu.kotatsu.utils.GridTouchHelper import org.koitharu.kotatsu.utils.IdlingDetector import org.koitharu.kotatsu.utils.ShareHelper import org.koitharu.kotatsu.utils.ext.assistedViewModels -import org.koitharu.kotatsu.utils.ext.getDisplayMessage import org.koitharu.kotatsu.utils.ext.getParcelableExtraCompat import org.koitharu.kotatsu.utils.ext.hasGlobalPoint -import org.koitharu.kotatsu.utils.ext.isReportable import org.koitharu.kotatsu.utils.ext.observeWithPrevious import org.koitharu.kotatsu.utils.ext.postDelayed -import org.koitharu.kotatsu.utils.ext.report import org.koitharu.kotatsu.utils.ext.setValueRounded import java.util.concurrent.TimeUnit import javax.inject.Inject @@ -116,7 +111,21 @@ class ReaderActivity : insetsDelegate.interceptingWindowInsetsListener = this idlingDetector.bindToLifecycle(this) - viewModel.onError.observe(this, this::onError) + viewModel.onError.observe( + this, + DialogErrorObserver( + host = binding.container, + fragment = null, + resolver = exceptionResolver, + onResolved = { isResolved -> + if (isResolved) { + viewModel.reload() + } else if (viewModel.content.value?.pages.isNullOrEmpty()) { + finishAfterTransition() + } + }, + ), + ) viewModel.readerMode.observe(this, this::onInitReader) viewModel.onPageSaved.observe(this, this::onPageSaved) viewModel.uiState.observeWithPrevious(this, this::onUiStateChanged) @@ -218,22 +227,6 @@ class ReaderActivity : menu.findItem(R.id.action_pages_thumbs).isVisible = hasPages } - private fun onError(e: Throwable) { - val listener = ErrorDialogListener(e) - val dialog = MaterialAlertDialogBuilder(this) - .setTitle(R.string.error_occurred) - .setMessage(e.getDisplayMessage(resources)) - .setNegativeButton(R.string.close, listener) - .setOnCancelListener(listener) - val resolveTextId = ExceptionResolver.getResolveStringId(e) - if (resolveTextId != 0) { - dialog.setPositiveButton(resolveTextId, listener) - } else if (e.isReportable()) { - dialog.setPositiveButton(R.string.report, listener) - } - dialog.show() - } - override fun onGridTouch(area: Int) { controlDelegate.onGridTouch(area, binding.container) } @@ -396,40 +389,6 @@ class ReaderActivity : } } - private inner class ErrorDialogListener( - private val exception: Throwable, - ) : DialogInterface.OnClickListener, DialogInterface.OnCancelListener { - - override fun onClick(dialog: DialogInterface?, which: Int) { - if (which == DialogInterface.BUTTON_POSITIVE) { - dialog?.dismiss() - if (ExceptionResolver.canResolve(exception)) { - tryResolve(exception) - } else { - exception.report() - } - } else { - onCancel(dialog) - } - } - - override fun onCancel(dialog: DialogInterface?) { - if (viewModel.content.value?.pages.isNullOrEmpty()) { - finishAfterTransition() - } - } - - private fun tryResolve(e: Throwable) { - lifecycleScope.launch { - if (exceptionResolver.resolve(e)) { - viewModel.reload() - } else { - onCancel(null) - } - } - } - } - companion object { const val ACTION_MANGA_READ = "${BuildConfig.APPLICATION_ID}.action.READ_MANGA" diff --git a/app/src/main/java/org/koitharu/kotatsu/scrobbling/common/ui/config/ScrobblerConfigActivity.kt b/app/src/main/java/org/koitharu/kotatsu/scrobbling/common/ui/config/ScrobblerConfigActivity.kt index 34e3aeef2..55e64d871 100644 --- a/app/src/main/java/org/koitharu/kotatsu/scrobbling/common/ui/config/ScrobblerConfigActivity.kt +++ b/app/src/main/java/org/koitharu/kotatsu/scrobbling/common/ui/config/ScrobblerConfigActivity.kt @@ -9,12 +9,12 @@ import androidx.core.view.isVisible import androidx.core.view.updatePadding import coil.ImageLoader import com.google.android.material.dialog.MaterialAlertDialogBuilder -import com.google.android.material.snackbar.Snackbar import dagger.hilt.android.AndroidEntryPoint import org.koitharu.kotatsu.R import org.koitharu.kotatsu.base.ui.BaseActivity import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener import org.koitharu.kotatsu.base.ui.list.decor.TypedSpacingItemDecoration +import org.koitharu.kotatsu.core.exceptions.resolve.SnackbarErrorObserver import org.koitharu.kotatsu.databinding.ActivityScrobblerConfigBinding import org.koitharu.kotatsu.details.ui.DetailsActivity import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerService @@ -25,7 +25,6 @@ import org.koitharu.kotatsu.tracker.ui.feed.adapter.FeedAdapter import org.koitharu.kotatsu.utils.ext.assistedViewModels import org.koitharu.kotatsu.utils.ext.disposeImageRequest import org.koitharu.kotatsu.utils.ext.enqueueWith -import org.koitharu.kotatsu.utils.ext.getDisplayMessage import org.koitharu.kotatsu.utils.ext.hideCompat import org.koitharu.kotatsu.utils.ext.newImageRequest import org.koitharu.kotatsu.utils.ext.showCompat @@ -72,7 +71,7 @@ class ScrobblerConfigActivity : BaseActivity(), viewModel.content.observe(this, listAdapter::setItems) viewModel.user.observe(this, this::onUserChanged) viewModel.isLoading.observe(this, this::onLoadingStateChanged) - viewModel.onError.observe(this, this::onError) + viewModel.onError.observe(this, SnackbarErrorObserver(binding.recyclerView, null)) viewModel.onLoggedOut.observe(this) { finishAfterTransition() } @@ -139,14 +138,6 @@ class ScrobblerConfigActivity : BaseActivity(), } } - private fun onError(e: Throwable) { - Snackbar.make( - binding.recyclerView, - e.getDisplayMessage(resources), - Snackbar.LENGTH_LONG, - ).show() - } - private fun showUserDialog() { MaterialAlertDialogBuilder(this) .setTitle(title) diff --git a/app/src/main/java/org/koitharu/kotatsu/shelf/ui/ShelfFragment.kt b/app/src/main/java/org/koitharu/kotatsu/shelf/ui/ShelfFragment.kt index 19dd12912..d9821cff0 100644 --- a/app/src/main/java/org/koitharu/kotatsu/shelf/ui/ShelfFragment.kt +++ b/app/src/main/java/org/koitharu/kotatsu/shelf/ui/ShelfFragment.kt @@ -20,6 +20,7 @@ import org.koitharu.kotatsu.base.ui.BaseFragment import org.koitharu.kotatsu.base.ui.list.SectionedSelectionController import org.koitharu.kotatsu.base.ui.util.RecyclerViewOwner import org.koitharu.kotatsu.base.ui.util.ReversibleAction +import org.koitharu.kotatsu.core.exceptions.resolve.SnackbarErrorObserver import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.databinding.FragmentShelfBinding import org.koitharu.kotatsu.details.ui.DetailsActivity @@ -36,7 +37,6 @@ import org.koitharu.kotatsu.shelf.ui.adapter.ShelfListEventListener import org.koitharu.kotatsu.shelf.ui.model.ShelfSectionModel import org.koitharu.kotatsu.tracker.ui.updates.UpdatesActivity import org.koitharu.kotatsu.utils.ext.addMenuProvider -import org.koitharu.kotatsu.utils.ext.getDisplayMessage import javax.inject.Inject @AndroidEntryPoint @@ -82,7 +82,7 @@ class ShelfFragment : addMenuProvider(ShelfMenuProvider(view.context, childFragmentManager, viewModel)) viewModel.content.observe(viewLifecycleOwner, ::onListChanged) - viewModel.onError.observe(viewLifecycleOwner, ::onError) + viewModel.onError.observe(viewLifecycleOwner, SnackbarErrorObserver(binding.recyclerView, this)) viewModel.onActionDone.observe(viewLifecycleOwner, ::onActionDone) } @@ -135,16 +135,6 @@ class ShelfFragment : adapter?.items = list } - private fun onError(e: Throwable) { - val snackbar = Snackbar.make( - binding.recyclerView, - e.getDisplayMessage(resources), - Snackbar.LENGTH_SHORT, - ) - snackbar.anchorView = (activity as? BottomNavOwner)?.bottomNav - snackbar.show() - } - private fun onActionDone(action: ReversibleAction) { val handle = action.handle val length = if (handle == null) Snackbar.LENGTH_SHORT else Snackbar.LENGTH_LONG diff --git a/app/src/main/java/org/koitharu/kotatsu/tracker/ui/feed/FeedFragment.kt b/app/src/main/java/org/koitharu/kotatsu/tracker/ui/feed/FeedFragment.kt index 95b51d35a..0ca7b391e 100644 --- a/app/src/main/java/org/koitharu/kotatsu/tracker/ui/feed/FeedFragment.kt +++ b/app/src/main/java/org/koitharu/kotatsu/tracker/ui/feed/FeedFragment.kt @@ -14,6 +14,7 @@ import org.koitharu.kotatsu.R import org.koitharu.kotatsu.base.ui.BaseFragment import org.koitharu.kotatsu.base.ui.list.PaginationScrollListener import org.koitharu.kotatsu.base.ui.list.decor.TypedSpacingItemDecoration +import org.koitharu.kotatsu.core.exceptions.resolve.SnackbarErrorObserver import org.koitharu.kotatsu.databinding.FragmentFeedBinding import org.koitharu.kotatsu.details.ui.DetailsActivity import org.koitharu.kotatsu.list.ui.adapter.MangaListListener @@ -25,7 +26,6 @@ import org.koitharu.kotatsu.parsers.model.MangaTag import org.koitharu.kotatsu.tracker.ui.feed.adapter.FeedAdapter import org.koitharu.kotatsu.tracker.work.TrackWorker import org.koitharu.kotatsu.utils.ext.addMenuProvider -import org.koitharu.kotatsu.utils.ext.getDisplayMessage import org.koitharu.kotatsu.utils.ext.getThemeColor import javax.inject.Inject @@ -75,7 +75,7 @@ class FeedFragment : ) viewModel.content.observe(viewLifecycleOwner, this::onListChanged) - viewModel.onError.observe(viewLifecycleOwner, this::onError) + viewModel.onError.observe(viewLifecycleOwner, SnackbarErrorObserver(binding.recyclerView, this)) viewModel.onFeedCleared.observe(viewLifecycleOwner) { onFeedCleared() } @@ -118,16 +118,6 @@ class FeedFragment : snackbar.show() } - private fun onError(e: Throwable) { - val snackbar = Snackbar.make( - binding.recyclerView, - e.getDisplayMessage(resources), - Snackbar.LENGTH_SHORT, - ) - snackbar.anchorView = (activity as? BottomNavOwner)?.bottomNav - snackbar.show() - } - private fun onIsTrackerRunningChanged(isRunning: Boolean) { binding.swipeRefreshLayout.isRefreshing = isRunning } diff --git a/app/src/main/java/org/koitharu/kotatsu/utils/ext/AndroidExt.kt b/app/src/main/java/org/koitharu/kotatsu/utils/ext/AndroidExt.kt index 251604b12..b595f5b60 100644 --- a/app/src/main/java/org/koitharu/kotatsu/utils/ext/AndroidExt.kt +++ b/app/src/main/java/org/koitharu/kotatsu/utils/ext/AndroidExt.kt @@ -1,9 +1,11 @@ package org.koitharu.kotatsu.utils.ext +import android.app.Activity import android.app.ActivityManager import android.app.ActivityOptions import android.content.Context import android.content.Context.ACTIVITY_SERVICE +import android.content.ContextWrapper import android.content.OperationApplicationException import android.content.SharedPreferences import android.content.SyncResult @@ -172,3 +174,9 @@ fun Resources.getLocalesConfig(): LocaleListCompat { } return LocaleListCompat.forLanguageTags(tagsList.complete()) } + +fun Context.findActivity(): Activity? = when (this) { + is Activity -> this + is ContextWrapper -> baseContext.findActivity() + else -> null +} diff --git a/app/src/main/java/org/koitharu/kotatsu/widget/shelf/ShelfConfigActivity.kt b/app/src/main/java/org/koitharu/kotatsu/widget/shelf/ShelfConfigActivity.kt index a6a987cc7..b5d3f5eb0 100644 --- a/app/src/main/java/org/koitharu/kotatsu/widget/shelf/ShelfConfigActivity.kt +++ b/app/src/main/java/org/koitharu/kotatsu/widget/shelf/ShelfConfigActivity.kt @@ -11,17 +11,16 @@ import androidx.core.graphics.Insets import androidx.core.view.isVisible import androidx.core.view.updateLayoutParams import androidx.core.view.updatePadding -import com.google.android.material.R as materialR -import com.google.android.material.snackbar.Snackbar import dagger.hilt.android.AndroidEntryPoint import org.koitharu.kotatsu.R import org.koitharu.kotatsu.base.ui.BaseActivity import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener +import org.koitharu.kotatsu.core.exceptions.resolve.SnackbarErrorObserver import org.koitharu.kotatsu.core.prefs.AppWidgetConfig import org.koitharu.kotatsu.databinding.ActivityCategoriesBinding -import org.koitharu.kotatsu.utils.ext.getDisplayMessage import org.koitharu.kotatsu.widget.shelf.adapter.CategorySelectAdapter import org.koitharu.kotatsu.widget.shelf.model.CategoryItem +import com.google.android.material.R as materialR @AndroidEntryPoint class ShelfConfigActivity : @@ -58,7 +57,7 @@ class ShelfConfigActivity : viewModel.checkedId = config.categoryId viewModel.content.observe(this, this::onContentChanged) - viewModel.onError.observe(this, this::onError) + viewModel.onError.observe(this, SnackbarErrorObserver(binding.recyclerView, null)) } override fun onClick(v: View) { @@ -105,11 +104,6 @@ class ShelfConfigActivity : adapter.items = categories } - private fun onError(e: Throwable) { - Snackbar.make(binding.recyclerView, e.getDisplayMessage(resources), Snackbar.LENGTH_LONG) - .show() - } - private fun updateWidget() { val intent = Intent(this, ShelfWidgetProvider::class.java) intent.action = AppWidgetManager.ACTION_APPWIDGET_UPDATE