Add "Open in browser" action in lists

This commit is contained in:
Koitharu
2024-04-29 11:32:42 +03:00
parent ea4a81c6ec
commit 8cf0203b42
8 changed files with 54 additions and 7 deletions

View File

@@ -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

View File

@@ -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)
}
}

View File

@@ -3,6 +3,8 @@ package org.koitharu.kotatsu.list.ui.adapter
interface ListStateHolderListener {
fun onRetryClick(error: Throwable)
fun onSecondaryErrorActionClick(error: Throwable) = Unit
fun onEmptyActionClick()
}
}

View File

@@ -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

View File

@@ -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 <C : MutableCollection<in MangaItemModel>> List<Manga>.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(

View File

@@ -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,

View File

@@ -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 -> {

View File

@@ -29,4 +29,13 @@
android:layout_marginTop="16dp"
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>