Show errors in global search results

pull/385/head
Koitharu 3 years ago
parent 1847759ec3
commit c2ee548f0a
No known key found for this signature in database
GPG Key ID: 8E861F8CE6E7CE27

@ -8,6 +8,7 @@ class MultiSearchListModel(
val source: MangaSource, val source: MangaSource,
val hasMore: Boolean, val hasMore: Boolean,
val list: List<MangaItemModel>, val list: List<MangaItemModel>,
val error: Throwable?,
) : ListModel { ) : ListModel {
override fun equals(other: Any?): Boolean { override fun equals(other: Any?): Boolean {
@ -19,14 +20,14 @@ class MultiSearchListModel(
if (source != other.source) return false if (source != other.source) return false
if (hasMore != other.hasMore) return false if (hasMore != other.hasMore) return false
if (list != other.list) return false if (list != other.list) return false
return error == other.error
return true
} }
override fun hashCode(): Int { override fun hashCode(): Int {
var result = source.hashCode() var result = source.hashCode()
result = 31 * result + hasMore.hashCode() result = 31 * result + hasMore.hashCode()
result = 31 * result + list.hashCode() result = 31 * result + list.hashCode()
result = 31 * result + (error?.hashCode() ?: 0)
return result return result
} }
} }

@ -16,8 +16,8 @@ import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.update import kotlinx.coroutines.flow.update
import kotlinx.coroutines.plus import kotlinx.coroutines.plus
import kotlinx.coroutines.withTimeout
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.exceptions.CompositeException
import org.koitharu.kotatsu.core.parser.MangaRepository import org.koitharu.kotatsu.core.parser.MangaRepository
import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.prefs.ListMode import org.koitharu.kotatsu.core.prefs.ListMode
@ -30,7 +30,6 @@ import org.koitharu.kotatsu.list.ui.model.EmptyState
import org.koitharu.kotatsu.list.ui.model.ListModel import org.koitharu.kotatsu.list.ui.model.ListModel
import org.koitharu.kotatsu.list.ui.model.LoadingFooter import org.koitharu.kotatsu.list.ui.model.LoadingFooter
import org.koitharu.kotatsu.list.ui.model.LoadingState import org.koitharu.kotatsu.list.ui.model.LoadingState
import org.koitharu.kotatsu.list.ui.model.toErrorState
import org.koitharu.kotatsu.list.ui.model.toUi import org.koitharu.kotatsu.list.ui.model.toUi
import org.koitharu.kotatsu.parsers.model.Manga import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
@ -52,20 +51,17 @@ class MultiSearchViewModel @Inject constructor(
private var searchJob: Job? = null private var searchJob: Job? = null
private val listData = MutableStateFlow<List<MultiSearchListModel>>(emptyList()) private val listData = MutableStateFlow<List<MultiSearchListModel>>(emptyList())
private val loadingData = MutableStateFlow(false) private val loadingData = MutableStateFlow(false)
private var listError = MutableStateFlow<Throwable?>(null)
val onDownloadStarted = MutableEventFlow<Unit>() val onDownloadStarted = MutableEventFlow<Unit>()
val query = MutableStateFlow(savedStateHandle.get<String>(MultiSearchActivity.EXTRA_QUERY).orEmpty()) val query = MutableStateFlow(savedStateHandle.get<String>(MultiSearchActivity.EXTRA_QUERY).orEmpty())
val list: StateFlow<List<ListModel>> = combine( val list: StateFlow<List<ListModel>> = combine(
listData, listData,
loadingData, loadingData,
listError, ) { list, loading ->
) { list, loading, error ->
when { when {
list.isEmpty() -> listOf( list.isEmpty() -> listOf(
when { when {
loading -> LoadingState loading -> LoadingState
error != null -> error.toErrorState(canRetry = true)
else -> EmptyState( else -> EmptyState(
icon = R.drawable.ic_empty_common, icon = R.drawable.ic_empty_common,
textPrimary = R.string.nothing_found, textPrimary = R.string.nothing_found,
@ -101,15 +97,12 @@ class MultiSearchViewModel @Inject constructor(
searchJob = launchJob(Dispatchers.Default) { searchJob = launchJob(Dispatchers.Default) {
prevJob?.cancelAndJoin() prevJob?.cancelAndJoin()
try { try {
listError.value = null
listData.value = emptyList() listData.value = emptyList()
loadingData.value = true loadingData.value = true
query.value = q query.value = q
searchImpl(q) searchImpl(q)
} catch (e: CancellationException) { } catch (e: CancellationException) {
throw e throw e
} catch (e: Throwable) {
listError.value = e
} finally { } finally {
loadingData.value = false loadingData.value = false
} }
@ -129,36 +122,29 @@ class MultiSearchViewModel @Inject constructor(
val deferredList = sources.map { source -> val deferredList = sources.map { source ->
async(dispatcher) { async(dispatcher) {
runCatchingCancellable { runCatchingCancellable {
val list = mangaRepositoryFactory.create(source).getList(offset = 0, query = q) withTimeout(8_000) {
mangaRepositoryFactory.create(source).getList(offset = 0, query = q)
.toUi(ListMode.GRID, extraProvider) .toUi(ListMode.GRID, extraProvider)
if (list.isNotEmpty()) {
MultiSearchListModel(source, list.size > MIN_HAS_MORE_ITEMS, list)
} else {
null
} }
}.onFailure { }.fold(
it.printStackTraceDebug() onSuccess = { list ->
if (list.isEmpty()) {
null
} else {
MultiSearchListModel(source, list.size > MIN_HAS_MORE_ITEMS, list, null)
} }
},
onFailure = { error ->
error.printStackTraceDebug()
MultiSearchListModel(source, true, emptyList(), error)
},
)
} }
} }
val errors = ArrayList<Throwable>()
for (deferred in deferredList) { for (deferred in deferredList) {
deferred.await() deferred.await()?.let { item ->
.onSuccess { item ->
if (item != null) {
listData.update { x -> x + item } listData.update { x -> x + item }
} }
}.onFailure {
errors.add(it)
}
}
if (listData.value.isEmpty()) {
when (errors.size) {
0 -> Unit
1 -> throw errors[0]
else -> throw CompositeException(errors)
}
} }
} }
} }

@ -1,5 +1,6 @@
package org.koitharu.kotatsu.search.ui.multi.adapter package org.koitharu.kotatsu.search.ui.multi.adapter
import androidx.core.view.isGone
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleOwner
import androidx.recyclerview.widget.RecyclerView.RecycledViewPool import androidx.recyclerview.widget.RecyclerView.RecycledViewPool
@ -10,6 +11,8 @@ import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.ui.list.AdapterDelegateClickListenerAdapter import org.koitharu.kotatsu.core.ui.list.AdapterDelegateClickListenerAdapter
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
import org.koitharu.kotatsu.core.ui.list.decor.SpacingItemDecoration import org.koitharu.kotatsu.core.ui.list.decor.SpacingItemDecoration
import org.koitharu.kotatsu.core.util.ext.getDisplayMessage
import org.koitharu.kotatsu.core.util.ext.textAndVisible
import org.koitharu.kotatsu.databinding.ItemListGroupBinding import org.koitharu.kotatsu.databinding.ItemListGroupBinding
import org.koitharu.kotatsu.list.ui.ItemSizeResolver import org.koitharu.kotatsu.list.ui.ItemSizeResolver
import org.koitharu.kotatsu.list.ui.MangaSelectionDecoration import org.koitharu.kotatsu.list.ui.MangaSelectionDecoration
@ -46,5 +49,7 @@ fun searchResultsAD(
binding.buttonMore.isVisible = item.hasMore binding.buttonMore.isVisible = item.hasMore
adapter.notifyDataSetChanged() adapter.notifyDataSetChanged()
adapter.items = item.list adapter.items = item.list
binding.recyclerView.isGone = item.list.isEmpty()
binding.textViewError.textAndVisible = item.error?.getDisplayMessage(context.resources)
} }
} }

@ -0,0 +1,11 @@
<vector
xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="@color/error"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="@android:color/white"
android:pathData="M11 15h2v2h-2zm0-8h2v6h-2zm0.99-5C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zM12 20c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8z" />
</vector>

@ -47,4 +47,21 @@
android:paddingHorizontal="@dimen/grid_spacing" android:paddingHorizontal="@dimen/grid_spacing"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" /> app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />
<TextView
android:id="@+id/textView_error"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_below="@id/recyclerView"
android:layout_alignParentStart="true"
android:layout_alignParentEnd="true"
android:layout_marginHorizontal="@dimen/grid_spacing"
android:drawablePadding="12dp"
android:gravity="center_vertical"
android:padding="@dimen/grid_spacing"
android:textAppearance="?attr/textAppearanceBodySmall"
android:visibility="gone"
app:drawableStartCompat="@drawable/ic_error_small"
tools:text="@tools:sample/lorem[6]"
tools:visibility="visible" />
</RelativeLayout> </RelativeLayout>
Loading…
Cancel
Save