From c2ee548f0a091466dabce0ed065546c5dfb60399 Mon Sep 17 00:00:00 2001 From: Koitharu Date: Mon, 5 Jun 2023 09:34:56 +0300 Subject: [PATCH] Show errors in global search results --- .../search/ui/multi/MultiSearchListModel.kt | 7 +-- .../search/ui/multi/MultiSearchViewModel.kt | 54 +++++++------------ .../ui/multi/adapter/SearchResultsAD.kt | 5 ++ app/src/main/res/drawable/ic_error_small.xml | 11 ++++ app/src/main/res/layout/item_list_group.xml | 19 ++++++- 5 files changed, 58 insertions(+), 38 deletions(-) create mode 100644 app/src/main/res/drawable/ic_error_small.xml diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/multi/MultiSearchListModel.kt b/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/multi/MultiSearchListModel.kt index f8801ca11..8f6ed856e 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/multi/MultiSearchListModel.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/multi/MultiSearchListModel.kt @@ -8,6 +8,7 @@ class MultiSearchListModel( val source: MangaSource, val hasMore: Boolean, val list: List, + val error: Throwable?, ) : ListModel { override fun equals(other: Any?): Boolean { @@ -19,14 +20,14 @@ class MultiSearchListModel( if (source != other.source) return false if (hasMore != other.hasMore) return false if (list != other.list) return false - - return true + return error == other.error } override fun hashCode(): Int { var result = source.hashCode() result = 31 * result + hasMore.hashCode() result = 31 * result + list.hashCode() + result = 31 * result + (error?.hashCode() ?: 0) return result } -} \ No newline at end of file +} diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/multi/MultiSearchViewModel.kt b/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/multi/MultiSearchViewModel.kt index f117f6d09..2f4615796 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/multi/MultiSearchViewModel.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/multi/MultiSearchViewModel.kt @@ -16,8 +16,8 @@ import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.update import kotlinx.coroutines.plus +import kotlinx.coroutines.withTimeout import org.koitharu.kotatsu.R -import org.koitharu.kotatsu.core.exceptions.CompositeException import org.koitharu.kotatsu.core.parser.MangaRepository import org.koitharu.kotatsu.core.prefs.AppSettings 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.LoadingFooter 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.parsers.model.Manga import org.koitharu.kotatsu.parsers.util.runCatchingCancellable @@ -52,20 +51,17 @@ class MultiSearchViewModel @Inject constructor( private var searchJob: Job? = null private val listData = MutableStateFlow>(emptyList()) private val loadingData = MutableStateFlow(false) - private var listError = MutableStateFlow(null) val onDownloadStarted = MutableEventFlow() val query = MutableStateFlow(savedStateHandle.get(MultiSearchActivity.EXTRA_QUERY).orEmpty()) val list: StateFlow> = combine( listData, loadingData, - listError, - ) { list, loading, error -> + ) { list, loading -> when { list.isEmpty() -> listOf( when { loading -> LoadingState - error != null -> error.toErrorState(canRetry = true) else -> EmptyState( icon = R.drawable.ic_empty_common, textPrimary = R.string.nothing_found, @@ -101,15 +97,12 @@ class MultiSearchViewModel @Inject constructor( searchJob = launchJob(Dispatchers.Default) { prevJob?.cancelAndJoin() try { - listError.value = null listData.value = emptyList() loadingData.value = true query.value = q searchImpl(q) } catch (e: CancellationException) { throw e - } catch (e: Throwable) { - listError.value = e } finally { loadingData.value = false } @@ -129,35 +122,28 @@ class MultiSearchViewModel @Inject constructor( val deferredList = sources.map { source -> async(dispatcher) { runCatchingCancellable { - val list = mangaRepositoryFactory.create(source).getList(offset = 0, query = q) - .toUi(ListMode.GRID, extraProvider) - if (list.isNotEmpty()) { - MultiSearchListModel(source, list.size > MIN_HAS_MORE_ITEMS, list) - } else { - null + withTimeout(8_000) { + mangaRepositoryFactory.create(source).getList(offset = 0, query = q) + .toUi(ListMode.GRID, extraProvider) } - }.onFailure { - it.printStackTraceDebug() - } + }.fold( + 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() for (deferred in deferredList) { - deferred.await() - .onSuccess { item -> - if (item != null) { - 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) + deferred.await()?.let { item -> + listData.update { x -> x + item } } } } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/multi/adapter/SearchResultsAD.kt b/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/multi/adapter/SearchResultsAD.kt index 2fdcf1b90..9bd049052 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/multi/adapter/SearchResultsAD.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/multi/adapter/SearchResultsAD.kt @@ -1,5 +1,6 @@ package org.koitharu.kotatsu.search.ui.multi.adapter +import androidx.core.view.isGone import androidx.core.view.isVisible import androidx.lifecycle.LifecycleOwner 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.OnListItemClickListener 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.list.ui.ItemSizeResolver import org.koitharu.kotatsu.list.ui.MangaSelectionDecoration @@ -46,5 +49,7 @@ fun searchResultsAD( binding.buttonMore.isVisible = item.hasMore adapter.notifyDataSetChanged() adapter.items = item.list + binding.recyclerView.isGone = item.list.isEmpty() + binding.textViewError.textAndVisible = item.error?.getDisplayMessage(context.resources) } } diff --git a/app/src/main/res/drawable/ic_error_small.xml b/app/src/main/res/drawable/ic_error_small.xml new file mode 100644 index 000000000..964da43cc --- /dev/null +++ b/app/src/main/res/drawable/ic_error_small.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/layout/item_list_group.xml b/app/src/main/res/layout/item_list_group.xml index 998d7d74e..3a8becad9 100644 --- a/app/src/main/res/layout/item_list_group.xml +++ b/app/src/main/res/layout/item_list_group.xml @@ -47,4 +47,21 @@ android:paddingHorizontal="@dimen/grid_spacing" app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" /> - \ No newline at end of file + + +