Refactor error handling

pull/311/head
Koitharu 3 years ago
parent da47dac3f7
commit c50fa8f10c
No known key found for this signature in database
GPG Key ID: 8E861F8CE6E7CE27

@ -1,14 +1,12 @@
package org.koitharu.kotatsu.base.ui.util package org.koitharu.kotatsu.base.ui.util
import android.app.Activity
import android.content.Context
import android.content.ContextWrapper
import android.view.View import android.view.View
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.domain.reverseAsync import org.koitharu.kotatsu.base.domain.reverseAsync
import org.koitharu.kotatsu.main.ui.owners.BottomNavOwner import org.koitharu.kotatsu.main.ui.owners.BottomNavOwner
import org.koitharu.kotatsu.utils.ext.findActivity
class ReversibleActionObserver( class ReversibleActionObserver(
private val snackbarHost: View, private val snackbarHost: View,
@ -29,10 +27,4 @@ class ReversibleActionObserver(
} }
snackbar.show() snackbar.show()
} }
private fun Context.findActivity(): Activity? = when (this) {
is Activity -> this
is ContextWrapper -> baseContext.findActivity()
else -> null
}
} }

@ -1,7 +1,11 @@
package org.koitharu.kotatsu.bookmarks.ui package org.koitharu.kotatsu.bookmarks.ui
import android.os.Bundle 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.appcompat.view.ActionMode
import androidx.core.graphics.Insets import androidx.core.graphics.Insets
import androidx.core.view.updateLayoutParams import androidx.core.view.updateLayoutParams
@ -10,7 +14,6 @@ import androidx.fragment.app.viewModels
import coil.ImageLoader import coil.ImageLoader
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.domain.reverseAsync import org.koitharu.kotatsu.base.domain.reverseAsync
import org.koitharu.kotatsu.base.ui.BaseFragment 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.domain.Bookmark
import org.koitharu.kotatsu.bookmarks.ui.adapter.BookmarksGroupAdapter import org.koitharu.kotatsu.bookmarks.ui.adapter.BookmarksGroupAdapter
import org.koitharu.kotatsu.bookmarks.ui.model.BookmarksGroup 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.databinding.FragmentListSimpleBinding
import org.koitharu.kotatsu.details.ui.DetailsActivity import org.koitharu.kotatsu.details.ui.DetailsActivity
import org.koitharu.kotatsu.list.ui.adapter.ListStateHolderListener 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.main.ui.owners.SnackbarOwner
import org.koitharu.kotatsu.parsers.model.Manga import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.reader.ui.ReaderActivity 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.invalidateNestedItemDecorations
import org.koitharu.kotatsu.utils.ext.scaleUpActivityOptionsOf import org.koitharu.kotatsu.utils.ext.scaleUpActivityOptionsOf
import javax.inject.Inject
@AndroidEntryPoint @AndroidEntryPoint
class BookmarksFragment : class BookmarksFragment :
@ -76,7 +80,7 @@ class BookmarksFragment :
binding.recyclerView.addItemDecoration(spacingDecoration) binding.recyclerView.addItemDecoration(spacingDecoration)
viewModel.content.observe(viewLifecycleOwner, ::onListChanged) viewModel.content.observe(viewLifecycleOwner, ::onListChanged)
viewModel.onError.observe(viewLifecycleOwner, ::onError) viewModel.onError.observe(viewLifecycleOwner, SnackbarErrorObserver(binding.recyclerView, this))
viewModel.onActionDone.observe(viewLifecycleOwner, ::onActionDone) viewModel.onActionDone.observe(viewLifecycleOwner, ::onActionDone)
} }
@ -132,6 +136,7 @@ class BookmarksFragment :
mode.finish() mode.finish()
true true
} }
else -> false else -> false
} }
} }
@ -154,14 +159,6 @@ class BookmarksFragment :
adapter?.items = list adapter?.items = list
} }
private fun onError(e: Throwable) {
Snackbar.make(
binding.recyclerView,
e.getDisplayMessage(resources),
Snackbar.LENGTH_SHORT,
).show()
}
private fun onActionDone(action: ReversibleAction) { private fun onActionDone(action: ReversibleAction) {
val handle = action.handle val handle = action.handle
val length = if (handle == null) Snackbar.LENGTH_SHORT else Snackbar.LENGTH_LONG val length = if (handle == null) Snackbar.LENGTH_SHORT else Snackbar.LENGTH_LONG

@ -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<Boolean>?,
) : 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)
}
}
}

@ -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<Boolean>?,
) : Observer<Throwable> {
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)
}
}
}
}

@ -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<Boolean>?,
) : 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()
}
}

@ -12,24 +12,20 @@ import androidx.fragment.app.FragmentManager
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.AlertDialogFragment 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.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.report
import org.koitharu.kotatsu.utils.ext.requireParcelable
import org.koitharu.kotatsu.utils.ext.requireSerializable import org.koitharu.kotatsu.utils.ext.requireSerializable
import org.koitharu.kotatsu.utils.ext.withArgs import org.koitharu.kotatsu.utils.ext.withArgs
class MangaErrorDialog : AlertDialogFragment<DialogMangaErrorBinding>() { class MangaErrorDialog : AlertDialogFragment<DialogMangaErrorBinding>() {
private lateinit var error: Throwable private lateinit var exception: ParseException
private lateinit var manga: Manga
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
val args = requireArguments() val args = requireArguments()
manga = args.requireParcelable<ParcelableManga>(ARG_MANGA).manga exception = args.requireSerializable(ARG_ERROR)
error = args.requireSerializable(ARG_ERROR)
} }
override fun onInflateView(inflater: LayoutInflater, container: ViewGroup?): DialogMangaErrorBinding { override fun onInflateView(inflater: LayoutInflater, container: ViewGroup?): DialogMangaErrorBinding {
@ -42,8 +38,8 @@ class MangaErrorDialog : AlertDialogFragment<DialogMangaErrorBinding>() {
movementMethod = LinkMovementMethod.getInstance() movementMethod = LinkMovementMethod.getInstance()
text = context.getString( text = context.getString(
R.string.manga_error_description_pattern, R.string.manga_error_description_pattern,
this@MangaErrorDialog.error.message?.htmlEncode().orEmpty(), exception.message?.htmlEncode().orEmpty(),
manga.publicUrl, exception.url,
).parseAsHtml(HtmlCompat.FROM_HTML_MODE_LEGACY) ).parseAsHtml(HtmlCompat.FROM_HTML_MODE_LEGACY)
} }
} }
@ -54,7 +50,7 @@ class MangaErrorDialog : AlertDialogFragment<DialogMangaErrorBinding>() {
.setNegativeButton(android.R.string.cancel, null) .setNegativeButton(android.R.string.cancel, null)
.setPositiveButton(R.string.report) { _, _ -> .setPositiveButton(R.string.report) { _, _ ->
dismiss() dismiss()
error.report() exception.report()
}.setTitle(R.string.error_occurred) }.setTitle(R.string.error_occurred)
} }
@ -62,10 +58,8 @@ class MangaErrorDialog : AlertDialogFragment<DialogMangaErrorBinding>() {
private const val TAG = "MangaErrorDialog" private const val TAG = "MangaErrorDialog"
private const val ARG_ERROR = "error" private const val ARG_ERROR = "error"
private const val ARG_MANGA = "manga"
fun show(fm: FragmentManager, manga: Manga, error: Throwable) = MangaErrorDialog().withArgs(2) { fun show(fm: FragmentManager, error: ParseException) = MangaErrorDialog().withArgs(1) {
putParcelable(ARG_MANGA, ParcelableManga(manga, false))
putSerializable(ARG_ERROR, error) putSerializable(ARG_ERROR, error)
}.show(fm, TAG) }.show(fm, TAG)
} }

@ -29,10 +29,9 @@ import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.domain.MangaIntent import org.koitharu.kotatsu.base.domain.MangaIntent
import org.koitharu.kotatsu.base.ui.BaseActivity import org.koitharu.kotatsu.base.ui.BaseActivity
import org.koitharu.kotatsu.base.ui.widgets.BottomSheetHeaderBar 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.model.parcelable.ParcelableManga
import org.koitharu.kotatsu.core.os.ShortcutsUpdater import org.koitharu.kotatsu.core.os.ShortcutsUpdater
import org.koitharu.kotatsu.core.ui.MangaErrorDialog
import org.koitharu.kotatsu.databinding.ActivityDetailsBinding import org.koitharu.kotatsu.databinding.ActivityDetailsBinding
import org.koitharu.kotatsu.details.service.MangaPrefetchService import org.koitharu.kotatsu.details.service.MangaPrefetchService
import org.koitharu.kotatsu.details.ui.model.ChapterListItem 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.ViewBadge
import org.koitharu.kotatsu.utils.ext.assistedViewModels import org.koitharu.kotatsu.utils.ext.assistedViewModels
import org.koitharu.kotatsu.utils.ext.getDisplayMessage 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.setNavigationBarTransparentCompat
import org.koitharu.kotatsu.utils.ext.textAndVisible import org.koitharu.kotatsu.utils.ext.textAndVisible
import javax.inject.Inject import javax.inject.Inject
@ -105,7 +103,19 @@ class DetailsActivity :
viewModel.manga.observe(this, ::onMangaUpdated) viewModel.manga.observe(this, ::onMangaUpdated)
viewModel.newChaptersCount.observe(this, ::onNewChaptersChanged) viewModel.newChaptersCount.observe(this, ::onNewChaptersChanged)
viewModel.onMangaRemoved.observe(this, ::onMangaRemoved) 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) { viewModel.onShowToast.observe(this) {
makeSnackbar(getString(it), Snackbar.LENGTH_SHORT).show() makeSnackbar(getString(it), Snackbar.LENGTH_SHORT).show()
} }
@ -191,37 +201,6 @@ class DetailsActivity :
finishAfterTransition() 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) { override fun onWindowInsetsChanged(insets: Insets) {
binding.root.updatePadding( binding.root.updatePadding(
left = insets.left, left = insets.left,

@ -13,7 +13,6 @@ import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import coil.ImageLoader import coil.ImageLoader
import com.google.android.material.snackbar.Snackbar
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.BaseFragment 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.ReversibleActionObserver
import org.koitharu.kotatsu.base.ui.util.SpanSizeResolver import org.koitharu.kotatsu.base.ui.util.SpanSizeResolver
import org.koitharu.kotatsu.bookmarks.ui.BookmarksActivity 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.databinding.FragmentExploreBinding
import org.koitharu.kotatsu.details.ui.DetailsActivity import org.koitharu.kotatsu.details.ui.DetailsActivity
import org.koitharu.kotatsu.explore.ui.adapter.ExploreAdapter 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.explore.ui.model.ExploreItem
import org.koitharu.kotatsu.favourites.ui.categories.FavouriteCategoriesActivity import org.koitharu.kotatsu.favourites.ui.categories.FavouriteCategoriesActivity
import org.koitharu.kotatsu.history.ui.HistoryActivity 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.Manga
import org.koitharu.kotatsu.parsers.model.MangaSource import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.search.ui.MangaListActivity import org.koitharu.kotatsu.search.ui.MangaListActivity
import org.koitharu.kotatsu.settings.SettingsActivity import org.koitharu.kotatsu.settings.SettingsActivity
import org.koitharu.kotatsu.suggestions.ui.SuggestionsActivity import org.koitharu.kotatsu.suggestions.ui.SuggestionsActivity
import org.koitharu.kotatsu.utils.ext.addMenuProvider import org.koitharu.kotatsu.utils.ext.addMenuProvider
import org.koitharu.kotatsu.utils.ext.getDisplayMessage
import javax.inject.Inject import javax.inject.Inject
@AndroidEntryPoint @AndroidEntryPoint
@ -74,7 +72,7 @@ class ExploreFragment :
viewModel.content.observe(viewLifecycleOwner) { viewModel.content.observe(viewLifecycleOwner) {
exploreAdapter?.items = it exploreAdapter?.items = it
} }
viewModel.onError.observe(viewLifecycleOwner, ::onError) viewModel.onError.observe(viewLifecycleOwner, SnackbarErrorObserver(binding.recyclerView, this))
viewModel.onOpenManga.observe(viewLifecycleOwner, ::onOpenManga) viewModel.onOpenManga.observe(viewLifecycleOwner, ::onOpenManga)
viewModel.onActionDone.observe(viewLifecycleOwner, ReversibleActionObserver(binding.recyclerView)) viewModel.onActionDone.observe(viewLifecycleOwner, ReversibleActionObserver(binding.recyclerView))
viewModel.isGrid.observe(viewLifecycleOwner, ::onGridModeChanged) viewModel.isGrid.observe(viewLifecycleOwner, ::onGridModeChanged)
@ -129,16 +127,6 @@ class ExploreFragment :
override fun onEmptyActionClick() = onManageClick(requireView()) 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) { private fun onOpenManga(manga: Manga) {
val intent = DetailsActivity.newIntent(context ?: return, manga) val intent = DetailsActivity.newIntent(context ?: return, manga)
startActivity(intent) startActivity(intent)

@ -18,11 +18,11 @@ import androidx.core.view.updatePadding
import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import coil.ImageLoader import coil.ImageLoader
import com.google.android.material.snackbar.Snackbar
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.BaseActivity import org.koitharu.kotatsu.base.ui.BaseActivity
import org.koitharu.kotatsu.base.ui.list.ListSelectionController 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.core.model.FavouriteCategory
import org.koitharu.kotatsu.databinding.ActivityCategoriesBinding import org.koitharu.kotatsu.databinding.ActivityCategoriesBinding
import org.koitharu.kotatsu.favourites.ui.FavouritesActivity 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.adapter.ListStateHolderListener
import org.koitharu.kotatsu.list.ui.model.ListModel import org.koitharu.kotatsu.list.ui.model.ListModel
import org.koitharu.kotatsu.parsers.model.SortOrder import org.koitharu.kotatsu.parsers.model.SortOrder
import org.koitharu.kotatsu.utils.ext.getDisplayMessage
import org.koitharu.kotatsu.utils.ext.scaleUpActivityOptionsOf import org.koitharu.kotatsu.utils.ext.scaleUpActivityOptionsOf
import javax.inject.Inject import javax.inject.Inject
@ -72,7 +71,7 @@ class FavouriteCategoriesActivity :
onBackPressedDispatcher.addCallback(exitReorderModeCallback) onBackPressedDispatcher.addCallback(exitReorderModeCallback)
viewModel.detalizedCategories.observe(this, ::onCategoriesChanged) viewModel.detalizedCategories.observe(this, ::onCategoriesChanged)
viewModel.onError.observe(this, ::onError) viewModel.onError.observe(this, SnackbarErrorObserver(binding.recyclerView, null))
viewModel.isInReorderMode.observe(this, ::onReorderModeChanged) viewModel.isInReorderMode.observe(this, ::onReorderModeChanged)
} }
@ -146,11 +145,6 @@ class FavouriteCategoriesActivity :
invalidateOptionsMenu() invalidateOptionsMenu()
} }
private fun onError(e: Throwable) {
Snackbar.make(binding.recyclerView, e.getDisplayMessage(resources), Snackbar.LENGTH_LONG)
.show()
}
private fun onReorderModeChanged(isReorderMode: Boolean) { private fun onReorderModeChanged(isReorderMode: Boolean) {
val transition = Fade().apply { val transition = Fade().apply {
duration = resources.getInteger(android.R.integer.config_shortAnimTime).toLong() duration = resources.getInteger(android.R.integer.config_shortAnimTime).toLong()

@ -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.decor.TypedSpacingItemDecoration
import org.koitharu.kotatsu.base.ui.list.fastscroll.FastScroller import org.koitharu.kotatsu.base.ui.list.fastscroll.FastScroller
import org.koitharu.kotatsu.base.ui.util.ReversibleAction 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.ExceptionResolver
import org.koitharu.kotatsu.core.exceptions.resolve.SnackbarErrorObserver
import org.koitharu.kotatsu.core.prefs.ListMode import org.koitharu.kotatsu.core.prefs.ListMode
import org.koitharu.kotatsu.databinding.FragmentListBinding import org.koitharu.kotatsu.databinding.FragmentListBinding
import org.koitharu.kotatsu.details.ui.DetailsActivity 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.ShareHelper
import org.koitharu.kotatsu.utils.ext.addMenuProvider import org.koitharu.kotatsu.utils.ext.addMenuProvider
import org.koitharu.kotatsu.utils.ext.clearItemDecorations 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.getThemeColor
import org.koitharu.kotatsu.utils.ext.measureHeight import org.koitharu.kotatsu.utils.ext.measureHeight
import org.koitharu.kotatsu.utils.ext.resolveDp import org.koitharu.kotatsu.utils.ext.resolveDp
@ -128,7 +126,7 @@ abstract class MangaListFragment :
viewModel.gridScale.observe(viewLifecycleOwner, ::onGridScaleChanged) viewModel.gridScale.observe(viewLifecycleOwner, ::onGridScaleChanged)
viewModel.isLoading.observe(viewLifecycleOwner, ::onLoadingStateChanged) viewModel.isLoading.observe(viewLifecycleOwner, ::onLoadingStateChanged)
viewModel.content.observe(viewLifecycleOwner, ::onListChanged) viewModel.content.observe(viewLifecycleOwner, ::onListChanged)
viewModel.onError.observe(viewLifecycleOwner, ::onError) viewModel.onError.observe(viewLifecycleOwner, SnackbarErrorObserver(binding.recyclerView, this))
viewModel.onActionDone.observe(viewLifecycleOwner, ::onActionDone) viewModel.onActionDone.observe(viewLifecycleOwner, ::onActionDone)
} }
@ -175,18 +173,6 @@ abstract class MangaListFragment :
listAdapter?.setItems(list, listCommitCallback) 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) { private fun onActionDone(action: ReversibleAction) {
val handle = action.handle val handle = action.handle
val length = if (handle == null) Snackbar.LENGTH_SHORT else Snackbar.LENGTH_LONG val length = if (handle == null) Snackbar.LENGTH_SHORT else Snackbar.LENGTH_LONG

@ -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_NO_SCROLL
import com.google.android.material.appbar.AppBarLayout.LayoutParams.SCROLL_FLAG_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.appbar.AppBarLayout.LayoutParams.SCROLL_FLAG_SNAP
import com.google.android.material.snackbar.Snackbar
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -41,6 +40,7 @@ import kotlinx.coroutines.withContext
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.BaseActivity import org.koitharu.kotatsu.base.ui.BaseActivity
import org.koitharu.kotatsu.base.ui.widgets.SlidingBottomNavigationView 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.databinding.ActivityMainBinding
import org.koitharu.kotatsu.details.service.MangaPrefetchService import org.koitharu.kotatsu.details.service.MangaPrefetchService
import org.koitharu.kotatsu.details.ui.DetailsActivity 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.tracker.work.TrackWorker
import org.koitharu.kotatsu.utils.VoiceInputContract import org.koitharu.kotatsu.utils.VoiceInputContract
import org.koitharu.kotatsu.utils.ext.drawableEnd 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.hideKeyboard
import org.koitharu.kotatsu.utils.ext.resolve import org.koitharu.kotatsu.utils.ext.resolve
import org.koitharu.kotatsu.utils.ext.scaleUpActivityOptionsOf import org.koitharu.kotatsu.utils.ext.scaleUpActivityOptionsOf
@ -132,7 +131,7 @@ class MainActivity :
} }
viewModel.onOpenReader.observe(this, this::onOpenReader) 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.isLoading.observe(this, this::onLoadingStateChanged)
viewModel.isResumeEnabled.observe(this, this::onResumeEnabledChanged) viewModel.isResumeEnabled.observe(this, this::onResumeEnabledChanged)
viewModel.counters.observe(this, ::onCountersChanged) viewModel.counters.observe(this, ::onCountersChanged)
@ -252,10 +251,6 @@ class MainActivity :
startActivity(ReaderActivity.newIntent(this, manga), options) 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) { private fun onCountersChanged(counters: SparseIntArray) {
repeat(counters.size) { i -> repeat(counters.size) { i ->
val id = counters.keyAt(i) val id = counters.keyAt(i)

@ -1,7 +1,6 @@
package org.koitharu.kotatsu.reader.ui package org.koitharu.kotatsu.reader.ui
import android.content.Context import android.content.Context
import android.content.DialogInterface
import android.content.Intent import android.content.Intent
import android.net.Uri import android.net.Uri
import android.os.Bundle import android.os.Bundle
@ -23,7 +22,6 @@ import androidx.core.view.isGone
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.core.view.updatePadding import androidx.core.view.updatePadding
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.Dispatchers 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.domain.MangaIntent
import org.koitharu.kotatsu.base.ui.BaseFullscreenActivity import org.koitharu.kotatsu.base.ui.BaseFullscreenActivity
import org.koitharu.kotatsu.bookmarks.domain.Bookmark 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.model.parcelable.ParcelableManga
import org.koitharu.kotatsu.core.prefs.ReaderMode import org.koitharu.kotatsu.core.prefs.ReaderMode
import org.koitharu.kotatsu.databinding.ActivityReaderBinding 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.IdlingDetector
import org.koitharu.kotatsu.utils.ShareHelper import org.koitharu.kotatsu.utils.ShareHelper
import org.koitharu.kotatsu.utils.ext.assistedViewModels 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.getParcelableExtraCompat
import org.koitharu.kotatsu.utils.ext.hasGlobalPoint 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.observeWithPrevious
import org.koitharu.kotatsu.utils.ext.postDelayed import org.koitharu.kotatsu.utils.ext.postDelayed
import org.koitharu.kotatsu.utils.ext.report
import org.koitharu.kotatsu.utils.ext.setValueRounded import org.koitharu.kotatsu.utils.ext.setValueRounded
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import javax.inject.Inject import javax.inject.Inject
@ -116,7 +111,21 @@ class ReaderActivity :
insetsDelegate.interceptingWindowInsetsListener = this insetsDelegate.interceptingWindowInsetsListener = this
idlingDetector.bindToLifecycle(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.readerMode.observe(this, this::onInitReader)
viewModel.onPageSaved.observe(this, this::onPageSaved) viewModel.onPageSaved.observe(this, this::onPageSaved)
viewModel.uiState.observeWithPrevious(this, this::onUiStateChanged) viewModel.uiState.observeWithPrevious(this, this::onUiStateChanged)
@ -218,22 +227,6 @@ class ReaderActivity :
menu.findItem(R.id.action_pages_thumbs).isVisible = hasPages 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) { override fun onGridTouch(area: Int) {
controlDelegate.onGridTouch(area, binding.container) 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 { companion object {
const val ACTION_MANGA_READ = "${BuildConfig.APPLICATION_ID}.action.READ_MANGA" const val ACTION_MANGA_READ = "${BuildConfig.APPLICATION_ID}.action.READ_MANGA"

@ -9,12 +9,12 @@ import androidx.core.view.isVisible
import androidx.core.view.updatePadding import androidx.core.view.updatePadding
import coil.ImageLoader import coil.ImageLoader
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.snackbar.Snackbar
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.BaseActivity import org.koitharu.kotatsu.base.ui.BaseActivity
import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener
import org.koitharu.kotatsu.base.ui.list.decor.TypedSpacingItemDecoration 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.databinding.ActivityScrobblerConfigBinding
import org.koitharu.kotatsu.details.ui.DetailsActivity import org.koitharu.kotatsu.details.ui.DetailsActivity
import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerService 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.assistedViewModels
import org.koitharu.kotatsu.utils.ext.disposeImageRequest import org.koitharu.kotatsu.utils.ext.disposeImageRequest
import org.koitharu.kotatsu.utils.ext.enqueueWith 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.hideCompat
import org.koitharu.kotatsu.utils.ext.newImageRequest import org.koitharu.kotatsu.utils.ext.newImageRequest
import org.koitharu.kotatsu.utils.ext.showCompat import org.koitharu.kotatsu.utils.ext.showCompat
@ -72,7 +71,7 @@ class ScrobblerConfigActivity : BaseActivity<ActivityScrobblerConfigBinding>(),
viewModel.content.observe(this, listAdapter::setItems) viewModel.content.observe(this, listAdapter::setItems)
viewModel.user.observe(this, this::onUserChanged) viewModel.user.observe(this, this::onUserChanged)
viewModel.isLoading.observe(this, this::onLoadingStateChanged) viewModel.isLoading.observe(this, this::onLoadingStateChanged)
viewModel.onError.observe(this, this::onError) viewModel.onError.observe(this, SnackbarErrorObserver(binding.recyclerView, null))
viewModel.onLoggedOut.observe(this) { viewModel.onLoggedOut.observe(this) {
finishAfterTransition() finishAfterTransition()
} }
@ -139,14 +138,6 @@ class ScrobblerConfigActivity : BaseActivity<ActivityScrobblerConfigBinding>(),
} }
} }
private fun onError(e: Throwable) {
Snackbar.make(
binding.recyclerView,
e.getDisplayMessage(resources),
Snackbar.LENGTH_LONG,
).show()
}
private fun showUserDialog() { private fun showUserDialog() {
MaterialAlertDialogBuilder(this) MaterialAlertDialogBuilder(this)
.setTitle(title) .setTitle(title)

@ -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.list.SectionedSelectionController
import org.koitharu.kotatsu.base.ui.util.RecyclerViewOwner import org.koitharu.kotatsu.base.ui.util.RecyclerViewOwner
import org.koitharu.kotatsu.base.ui.util.ReversibleAction 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.core.prefs.AppSettings
import org.koitharu.kotatsu.databinding.FragmentShelfBinding import org.koitharu.kotatsu.databinding.FragmentShelfBinding
import org.koitharu.kotatsu.details.ui.DetailsActivity 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.shelf.ui.model.ShelfSectionModel
import org.koitharu.kotatsu.tracker.ui.updates.UpdatesActivity import org.koitharu.kotatsu.tracker.ui.updates.UpdatesActivity
import org.koitharu.kotatsu.utils.ext.addMenuProvider import org.koitharu.kotatsu.utils.ext.addMenuProvider
import org.koitharu.kotatsu.utils.ext.getDisplayMessage
import javax.inject.Inject import javax.inject.Inject
@AndroidEntryPoint @AndroidEntryPoint
@ -82,7 +82,7 @@ class ShelfFragment :
addMenuProvider(ShelfMenuProvider(view.context, childFragmentManager, viewModel)) addMenuProvider(ShelfMenuProvider(view.context, childFragmentManager, viewModel))
viewModel.content.observe(viewLifecycleOwner, ::onListChanged) viewModel.content.observe(viewLifecycleOwner, ::onListChanged)
viewModel.onError.observe(viewLifecycleOwner, ::onError) viewModel.onError.observe(viewLifecycleOwner, SnackbarErrorObserver(binding.recyclerView, this))
viewModel.onActionDone.observe(viewLifecycleOwner, ::onActionDone) viewModel.onActionDone.observe(viewLifecycleOwner, ::onActionDone)
} }
@ -135,16 +135,6 @@ class ShelfFragment :
adapter?.items = list 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) { private fun onActionDone(action: ReversibleAction) {
val handle = action.handle val handle = action.handle
val length = if (handle == null) Snackbar.LENGTH_SHORT else Snackbar.LENGTH_LONG val length = if (handle == null) Snackbar.LENGTH_SHORT else Snackbar.LENGTH_LONG

@ -14,6 +14,7 @@ import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.BaseFragment import org.koitharu.kotatsu.base.ui.BaseFragment
import org.koitharu.kotatsu.base.ui.list.PaginationScrollListener import org.koitharu.kotatsu.base.ui.list.PaginationScrollListener
import org.koitharu.kotatsu.base.ui.list.decor.TypedSpacingItemDecoration 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.databinding.FragmentFeedBinding
import org.koitharu.kotatsu.details.ui.DetailsActivity import org.koitharu.kotatsu.details.ui.DetailsActivity
import org.koitharu.kotatsu.list.ui.adapter.MangaListListener 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.ui.feed.adapter.FeedAdapter
import org.koitharu.kotatsu.tracker.work.TrackWorker import org.koitharu.kotatsu.tracker.work.TrackWorker
import org.koitharu.kotatsu.utils.ext.addMenuProvider import org.koitharu.kotatsu.utils.ext.addMenuProvider
import org.koitharu.kotatsu.utils.ext.getDisplayMessage
import org.koitharu.kotatsu.utils.ext.getThemeColor import org.koitharu.kotatsu.utils.ext.getThemeColor
import javax.inject.Inject import javax.inject.Inject
@ -75,7 +75,7 @@ class FeedFragment :
) )
viewModel.content.observe(viewLifecycleOwner, this::onListChanged) viewModel.content.observe(viewLifecycleOwner, this::onListChanged)
viewModel.onError.observe(viewLifecycleOwner, this::onError) viewModel.onError.observe(viewLifecycleOwner, SnackbarErrorObserver(binding.recyclerView, this))
viewModel.onFeedCleared.observe(viewLifecycleOwner) { viewModel.onFeedCleared.observe(viewLifecycleOwner) {
onFeedCleared() onFeedCleared()
} }
@ -118,16 +118,6 @@ class FeedFragment :
snackbar.show() 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) { private fun onIsTrackerRunningChanged(isRunning: Boolean) {
binding.swipeRefreshLayout.isRefreshing = isRunning binding.swipeRefreshLayout.isRefreshing = isRunning
} }

@ -1,9 +1,11 @@
package org.koitharu.kotatsu.utils.ext package org.koitharu.kotatsu.utils.ext
import android.app.Activity
import android.app.ActivityManager import android.app.ActivityManager
import android.app.ActivityOptions import android.app.ActivityOptions
import android.content.Context import android.content.Context
import android.content.Context.ACTIVITY_SERVICE import android.content.Context.ACTIVITY_SERVICE
import android.content.ContextWrapper
import android.content.OperationApplicationException import android.content.OperationApplicationException
import android.content.SharedPreferences import android.content.SharedPreferences
import android.content.SyncResult import android.content.SyncResult
@ -172,3 +174,9 @@ fun Resources.getLocalesConfig(): LocaleListCompat {
} }
return LocaleListCompat.forLanguageTags(tagsList.complete()) return LocaleListCompat.forLanguageTags(tagsList.complete())
} }
fun Context.findActivity(): Activity? = when (this) {
is Activity -> this
is ContextWrapper -> baseContext.findActivity()
else -> null
}

@ -11,17 +11,16 @@ import androidx.core.graphics.Insets
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.core.view.updateLayoutParams import androidx.core.view.updateLayoutParams
import androidx.core.view.updatePadding 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 dagger.hilt.android.AndroidEntryPoint
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.BaseActivity import org.koitharu.kotatsu.base.ui.BaseActivity
import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener 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.core.prefs.AppWidgetConfig
import org.koitharu.kotatsu.databinding.ActivityCategoriesBinding 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.adapter.CategorySelectAdapter
import org.koitharu.kotatsu.widget.shelf.model.CategoryItem import org.koitharu.kotatsu.widget.shelf.model.CategoryItem
import com.google.android.material.R as materialR
@AndroidEntryPoint @AndroidEntryPoint
class ShelfConfigActivity : class ShelfConfigActivity :
@ -58,7 +57,7 @@ class ShelfConfigActivity :
viewModel.checkedId = config.categoryId viewModel.checkedId = config.categoryId
viewModel.content.observe(this, this::onContentChanged) 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) { override fun onClick(v: View) {
@ -105,11 +104,6 @@ class ShelfConfigActivity :
adapter.items = categories adapter.items = categories
} }
private fun onError(e: Throwable) {
Snackbar.make(binding.recyclerView, e.getDisplayMessage(resources), Snackbar.LENGTH_LONG)
.show()
}
private fun updateWidget() { private fun updateWidget() {
val intent = Intent(this, ShelfWidgetProvider::class.java) val intent = Intent(this, ShelfWidgetProvider::class.java)
intent.action = AppWidgetManager.ACTION_APPWIDGET_UPDATE intent.action = AppWidgetManager.ACTION_APPWIDGET_UPDATE

Loading…
Cancel
Save