Add "Open in browser" action in lists

master
Koitharu 2 years ago
parent ea4a81c6ec
commit 8cf0203b42
Signed by: Koitharu
GPG Key ID: 676DEE768C17A9D7

@ -20,6 +20,7 @@ import coil.ImageLoader
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.koitharu.kotatsu.R 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.ExceptionResolver
import org.koitharu.kotatsu.core.exceptions.resolve.SnackbarErrorObserver import org.koitharu.kotatsu.core.exceptions.resolve.SnackbarErrorObserver
import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.core.prefs.AppSettings

@ -1,8 +1,11 @@
package org.koitharu.kotatsu.list.ui.adapter package org.koitharu.kotatsu.list.ui.adapter
import android.view.View
import androidx.core.view.isVisible import androidx.core.view.isVisible
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding 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.getDisplayMessage
import org.koitharu.kotatsu.core.util.ext.setTextAndVisible
import org.koitharu.kotatsu.databinding.ItemErrorStateBinding import org.koitharu.kotatsu.databinding.ItemErrorStateBinding
import org.koitharu.kotatsu.list.ui.model.ErrorState import org.koitharu.kotatsu.list.ui.model.ErrorState
import org.koitharu.kotatsu.list.ui.model.ListModel import org.koitharu.kotatsu.list.ui.model.ListModel
@ -13,9 +16,15 @@ fun errorStateListAD(
{ inflater, parent -> ItemErrorStateBinding.inflate(inflater, parent, false) }, { inflater, parent -> ItemErrorStateBinding.inflate(inflater, parent, false) },
) { ) {
binding.buttonRetry.setOnClickListener { val onClickListener = View.OnClickListener { v ->
listener.onRetryClick(item.exception) 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 { bind {
with(binding.textViewError) { with(binding.textViewError) {
@ -26,5 +35,6 @@ fun errorStateListAD(
isVisible = item.canRetry isVisible = item.canRetry
setText(item.buttonText) setText(item.buttonText)
} }
binding.buttonSecondary.setTextAndVisible(item.secondaryButtonText)
} }
} }

@ -4,5 +4,7 @@ interface ListStateHolderListener {
fun onRetryClick(error: Throwable) fun onRetryClick(error: Throwable)
fun onSecondaryErrorActionClick(error: Throwable) = Unit
fun onEmptyActionClick() fun onEmptyActionClick()
} }

@ -7,7 +7,8 @@ data class ErrorState(
val exception: Throwable, val exception: Throwable,
@DrawableRes val icon: Int, @DrawableRes val icon: Int,
val canRetry: Boolean, val canRetry: Boolean,
@StringRes val buttonText: Int @StringRes val buttonText: Int,
@StringRes val secondaryButtonText: Int,
) : ListModel { ) : ListModel {
override fun areItemsTheSame(other: ListModel) = other is ErrorState override fun areItemsTheSame(other: ListModel) = other is ErrorState

@ -1,5 +1,6 @@
package org.koitharu.kotatsu.list.ui.model package org.koitharu.kotatsu.list.ui.model
import androidx.annotation.StringRes
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.exceptions.resolve.ExceptionResolver import org.koitharu.kotatsu.core.exceptions.resolve.ExceptionResolver
import org.koitharu.kotatsu.core.prefs.ListMode import org.koitharu.kotatsu.core.prefs.ListMode
@ -74,11 +75,12 @@ suspend fun <C : MutableCollection<in MangaItemModel>> List<Manga>.toUi(
ListMode.GRID -> mapTo(destination) { it.toGridModel(extraProvider) } 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, exception = this,
icon = getDisplayIcon(), icon = getDisplayIcon(),
canRetry = canRetry, canRetry = canRetry,
buttonText = ExceptionResolver.getResolveStringId(this).ifZero { R.string.try_again }, buttonText = ExceptionResolver.getResolveStringId(this).ifZero { R.string.try_again },
secondaryButtonText = secondaryAction,
) )
fun Throwable.toErrorFooter() = ErrorFooter( fun Throwable.toErrorFooter() = ErrorFooter(

@ -10,10 +10,12 @@ import androidx.appcompat.widget.SearchView
import androidx.core.view.MenuProvider import androidx.core.view.MenuProvider
import androidx.core.view.inputmethod.EditorInfoCompat import androidx.core.view.inputmethod.EditorInfoCompat
import androidx.fragment.app.viewModels import androidx.fragment.app.viewModels
import com.google.android.material.snackbar.Snackbar
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.flow.distinctUntilChangedBy import kotlinx.coroutines.flow.distinctUntilChangedBy
import kotlinx.coroutines.flow.drop import kotlinx.coroutines.flow.drop
import org.koitharu.kotatsu.R 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.list.ListSelectionController
import org.koitharu.kotatsu.core.ui.util.MenuInvalidator import org.koitharu.kotatsu.core.ui.util.MenuInvalidator
import org.koitharu.kotatsu.core.util.ext.addMenuProvider import org.koitharu.kotatsu.core.util.ext.addMenuProvider
@ -70,6 +72,15 @@ class RemoteListFragment : MangaListFragment(), FilterOwner {
viewModel.resetFilter() 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 : private inner class RemoteListMenuProvider :
MenuProvider, MenuProvider,
SearchView.OnQueryTextListener, SearchView.OnQueryTextListener,

@ -20,6 +20,7 @@ import kotlinx.coroutines.plus
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.model.distinctById import org.koitharu.kotatsu.core.model.distinctById
import org.koitharu.kotatsu.core.parser.MangaRepository 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.prefs.AppSettings
import org.koitharu.kotatsu.core.util.ext.MutableEventFlow import org.koitharu.kotatsu.core.util.ext.MutableEventFlow
import org.koitharu.kotatsu.core.util.ext.call 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.MangaListFilter
import org.koitharu.kotatsu.parsers.model.MangaSource import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.model.MangaTag import org.koitharu.kotatsu.parsers.model.MangaTag
import org.koitharu.kotatsu.parsers.util.concatUrl
import javax.inject.Inject import javax.inject.Inject
private const val FILTER_MIN_INTERVAL = 250L private const val FILTER_MIN_INTERVAL = 250L
@ -72,6 +74,9 @@ open class RemoteListViewModel @Inject constructor(
val isSearchAvailable: Boolean val isSearchAvailable: Boolean
get() = repository.isSearchSupported get() = repository.isSearchSupported
val browserUrl: String?
get() = (repository as? RemoteMangaRepository)?.domain?.let { "https://$it" }
override val content = combine( override val content = combine(
mangaList.map { it?.skipNsfwIfNeeded() }, mangaList.map { it?.skipNsfwIfNeeded() },
listMode, listMode,
@ -80,7 +85,13 @@ open class RemoteListViewModel @Inject constructor(
) { list, mode, error, hasNext -> ) { list, mode, error, hasNext ->
buildList(list?.size?.plus(2) ?: 2) { buildList(list?.size?.plus(2) ?: 2) {
when { 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 == null -> add(LoadingState)
list.isEmpty() -> add(createEmptyState(canResetFilter = header.value.isFilterApplied)) list.isEmpty() -> add(createEmptyState(canResetFilter = header.value.isFilterApplied))
else -> { else -> {

@ -29,4 +29,13 @@
android:layout_marginTop="16dp" android:layout_marginTop="16dp"
android:text="@string/try_again" /> android:text="@string/try_again" />
<Button
android:id="@+id/button_secondary"
style="@style/Widget.Material3.Button.TextButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
tools:text="@string/open_in_browser"
tools:visibility="visible" />
</LinearLayout> </LinearLayout>

Loading…
Cancel
Save