diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/MangaListFragment.kt b/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/MangaListFragment.kt index c5df6e3fc..61f3e49a3 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/MangaListFragment.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/MangaListFragment.kt @@ -20,6 +20,7 @@ import coil.ImageLoader import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.launch import org.koitharu.kotatsu.R +import org.koitharu.kotatsu.browser.cloudflare.CaptchaNotifier import org.koitharu.kotatsu.core.exceptions.resolve.ExceptionResolver import org.koitharu.kotatsu.core.exceptions.resolve.SnackbarErrorObserver import org.koitharu.kotatsu.core.prefs.AppSettings diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/adapter/ErrorStateListAD.kt b/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/adapter/ErrorStateListAD.kt index 03de52eb8..89f9741ab 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/adapter/ErrorStateListAD.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/adapter/ErrorStateListAD.kt @@ -1,8 +1,11 @@ package org.koitharu.kotatsu.list.ui.adapter +import android.view.View import androidx.core.view.isVisible import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding +import org.koitharu.kotatsu.R import org.koitharu.kotatsu.core.util.ext.getDisplayMessage +import org.koitharu.kotatsu.core.util.ext.setTextAndVisible import org.koitharu.kotatsu.databinding.ItemErrorStateBinding import org.koitharu.kotatsu.list.ui.model.ErrorState import org.koitharu.kotatsu.list.ui.model.ListModel @@ -13,10 +16,16 @@ fun errorStateListAD( { inflater, parent -> ItemErrorStateBinding.inflate(inflater, parent, false) }, ) { - binding.buttonRetry.setOnClickListener { - listener.onRetryClick(item.exception) + val onClickListener = View.OnClickListener { v -> + when (v.id) { + R.id.button_retry -> listener.onRetryClick(item.exception) + R.id.button_secondary -> listener.onSecondaryErrorActionClick(item.exception) + } } + binding.buttonRetry.setOnClickListener(onClickListener) + binding.buttonSecondary.setOnClickListener(onClickListener) + bind { with(binding.textViewError) { text = item.exception.getDisplayMessage(context.resources) @@ -26,5 +35,6 @@ fun errorStateListAD( isVisible = item.canRetry setText(item.buttonText) } + binding.buttonSecondary.setTextAndVisible(item.secondaryButtonText) } } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/adapter/ListStateHolderListener.kt b/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/adapter/ListStateHolderListener.kt index 5040db801..0e0d4ad84 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/adapter/ListStateHolderListener.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/adapter/ListStateHolderListener.kt @@ -3,6 +3,8 @@ package org.koitharu.kotatsu.list.ui.adapter interface ListStateHolderListener { fun onRetryClick(error: Throwable) - + + fun onSecondaryErrorActionClick(error: Throwable) = Unit + fun onEmptyActionClick() -} \ No newline at end of file +} diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/model/ErrorState.kt b/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/model/ErrorState.kt index 9f1bf50a5..e07afb4ef 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/model/ErrorState.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/model/ErrorState.kt @@ -7,7 +7,8 @@ data class ErrorState( val exception: Throwable, @DrawableRes val icon: Int, val canRetry: Boolean, - @StringRes val buttonText: Int + @StringRes val buttonText: Int, + @StringRes val secondaryButtonText: Int, ) : ListModel { override fun areItemsTheSame(other: ListModel) = other is ErrorState diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/model/ListModelConversionExt.kt b/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/model/ListModelConversionExt.kt index a4fbe8e18..833734e2b 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/model/ListModelConversionExt.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/model/ListModelConversionExt.kt @@ -1,5 +1,6 @@ package org.koitharu.kotatsu.list.ui.model +import androidx.annotation.StringRes import org.koitharu.kotatsu.R import org.koitharu.kotatsu.core.exceptions.resolve.ExceptionResolver import org.koitharu.kotatsu.core.prefs.ListMode @@ -74,11 +75,12 @@ suspend fun > List.toUi( ListMode.GRID -> mapTo(destination) { it.toGridModel(extraProvider) } } -fun Throwable.toErrorState(canRetry: Boolean = true) = ErrorState( +fun Throwable.toErrorState(canRetry: Boolean = true, @StringRes secondaryAction: Int = 0) = ErrorState( exception = this, icon = getDisplayIcon(), canRetry = canRetry, buttonText = ExceptionResolver.getResolveStringId(this).ifZero { R.string.try_again }, + secondaryButtonText = secondaryAction, ) fun Throwable.toErrorFooter() = ErrorFooter( diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/remotelist/ui/RemoteListFragment.kt b/app/src/main/kotlin/org/koitharu/kotatsu/remotelist/ui/RemoteListFragment.kt index 564a84887..ce7673c1e 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/remotelist/ui/RemoteListFragment.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/remotelist/ui/RemoteListFragment.kt @@ -10,10 +10,12 @@ import androidx.appcompat.widget.SearchView import androidx.core.view.MenuProvider import androidx.core.view.inputmethod.EditorInfoCompat import androidx.fragment.app.viewModels +import com.google.android.material.snackbar.Snackbar import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.flow.distinctUntilChangedBy import kotlinx.coroutines.flow.drop import org.koitharu.kotatsu.R +import org.koitharu.kotatsu.browser.BrowserActivity import org.koitharu.kotatsu.core.ui.list.ListSelectionController import org.koitharu.kotatsu.core.ui.util.MenuInvalidator import org.koitharu.kotatsu.core.util.ext.addMenuProvider @@ -70,6 +72,15 @@ class RemoteListFragment : MangaListFragment(), FilterOwner { viewModel.resetFilter() } + override fun onSecondaryErrorActionClick(error: Throwable) { + viewModel.browserUrl?.also { url -> + startActivity( + BrowserActivity.newIntent(requireContext(), url, viewModel.source, viewModel.source.title), + ) + } ?: Snackbar.make(requireViewBinding().recyclerView, R.string.operation_not_supported, Snackbar.LENGTH_SHORT) + .show() + } + private inner class RemoteListMenuProvider : MenuProvider, SearchView.OnQueryTextListener, diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/remotelist/ui/RemoteListViewModel.kt b/app/src/main/kotlin/org/koitharu/kotatsu/remotelist/ui/RemoteListViewModel.kt index b316146cc..f6294c8ae 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/remotelist/ui/RemoteListViewModel.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/remotelist/ui/RemoteListViewModel.kt @@ -20,6 +20,7 @@ import kotlinx.coroutines.plus import org.koitharu.kotatsu.R import org.koitharu.kotatsu.core.model.distinctById import org.koitharu.kotatsu.core.parser.MangaRepository +import org.koitharu.kotatsu.core.parser.RemoteMangaRepository import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.core.util.ext.MutableEventFlow import org.koitharu.kotatsu.core.util.ext.call @@ -43,6 +44,7 @@ import org.koitharu.kotatsu.parsers.model.Manga import org.koitharu.kotatsu.parsers.model.MangaListFilter import org.koitharu.kotatsu.parsers.model.MangaSource import org.koitharu.kotatsu.parsers.model.MangaTag +import org.koitharu.kotatsu.parsers.util.concatUrl import javax.inject.Inject private const val FILTER_MIN_INTERVAL = 250L @@ -72,6 +74,9 @@ open class RemoteListViewModel @Inject constructor( val isSearchAvailable: Boolean get() = repository.isSearchSupported + val browserUrl: String? + get() = (repository as? RemoteMangaRepository)?.domain?.let { "https://$it" } + override val content = combine( mangaList.map { it?.skipNsfwIfNeeded() }, listMode, @@ -80,7 +85,13 @@ open class RemoteListViewModel @Inject constructor( ) { list, mode, error, hasNext -> buildList(list?.size?.plus(2) ?: 2) { when { - list.isNullOrEmpty() && error != null -> add(error.toErrorState(canRetry = true)) + list.isNullOrEmpty() && error != null -> add( + error.toErrorState( + canRetry = true, + secondaryAction = if (browserUrl != null) R.string.open_in_browser else 0, + ), + ) + list == null -> add(LoadingState) list.isEmpty() -> add(createEmptyState(canResetFilter = header.value.isFilterApplied)) else -> { diff --git a/app/src/main/res/layout/item_error_state.xml b/app/src/main/res/layout/item_error_state.xml index a63563f79..c0ca23455 100644 --- a/app/src/main/res/layout/item_error_state.xml +++ b/app/src/main/res/layout/item_error_state.xml @@ -29,4 +29,13 @@ android:layout_marginTop="16dp" android:text="@string/try_again" /> +