Handle filter loading errors

This commit is contained in:
Koitharu
2022-03-04 08:16:36 +02:00
parent 5c05aaeacf
commit 3c64d6675e
7 changed files with 49 additions and 13 deletions

View File

@@ -10,4 +10,5 @@ class FilterAdapter(
filterTagDelegate(listener), filterTagDelegate(listener),
filterHeaderDelegate(), filterHeaderDelegate(),
filterLoadingDelegate(), filterLoadingDelegate(),
filterErrorDelegate(),
) )

View File

@@ -1,10 +1,12 @@
package org.koitharu.kotatsu.list.ui.filter package org.koitharu.kotatsu.list.ui.filter
import android.widget.TextView
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegate
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.databinding.ItemCheckableMultipleBinding import org.koitharu.kotatsu.databinding.ItemCheckableMultipleBinding
import org.koitharu.kotatsu.databinding.ItemCheckableSingleBinding import org.koitharu.kotatsu.databinding.ItemCheckableSingleBinding
import org.koitharu.kotatsu.databinding.ItemFilterHeaderBinding import org.koitharu.kotatsu.databinding.ItemFilterHeaderBinding
import org.koitharu.kotatsu.databinding.ItemLoadingFooterBinding
fun filterSortDelegate( fun filterSortDelegate(
listener: OnFilterChangedListener, listener: OnFilterChangedListener,
@@ -47,6 +49,11 @@ fun filterHeaderDelegate() = adapterDelegateViewBinding<FilterItem.Header, Filte
} }
} }
fun filterLoadingDelegate() = adapterDelegateViewBinding<FilterItem.Loading, FilterItem, ItemLoadingFooterBinding>( fun filterLoadingDelegate() = adapterDelegate<FilterItem.Loading, FilterItem>(R.layout.item_loading_footer) {}
{ layoutInflater, parent -> ItemLoadingFooterBinding.inflate(layoutInflater, parent, false) }
) { } fun filterErrorDelegate() = adapterDelegate<FilterItem.Error, FilterItem>(R.layout.item_sources_empty) {
bind {
(itemView as TextView).setText(item.textResId)
}
}

View File

@@ -17,14 +17,18 @@ class FilterDiffCallback : DiffUtil.ItemCallback<FilterItem>() {
oldItem is FilterItem.Sort && newItem is FilterItem.Sort -> { oldItem is FilterItem.Sort && newItem is FilterItem.Sort -> {
oldItem.order == newItem.order oldItem.order == newItem.order
} }
oldItem is FilterItem.Error && newItem is FilterItem.Error -> {
oldItem.textResId == newItem.textResId
}
else -> false else -> false
} }
} }
override fun areContentsTheSame(oldItem: FilterItem, newItem: FilterItem): Boolean { override fun areContentsTheSame(oldItem: FilterItem, newItem: FilterItem): Boolean {
return when { return when {
oldItem === newItem -> true oldItem == FilterItem.Loading && newItem == FilterItem.Loading -> true
oldItem is FilterItem.Header && newItem is FilterItem.Header -> true oldItem is FilterItem.Header && newItem is FilterItem.Header -> true
oldItem is FilterItem.Error && newItem is FilterItem.Error -> true
oldItem is FilterItem.Tag && newItem is FilterItem.Tag -> { oldItem is FilterItem.Tag && newItem is FilterItem.Tag -> {
oldItem.isChecked == newItem.isChecked oldItem.isChecked == newItem.isChecked
} }

View File

@@ -21,4 +21,8 @@ sealed interface FilterItem {
) : FilterItem ) : FilterItem
object Loading : FilterItem object Loading : FilterItem
class Error(
@StringRes val textResId: Int,
) : FilterItem
} }

View File

@@ -6,6 +6,7 @@ import kotlinx.coroutines.*
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.domain.MangaDataRepository import org.koitharu.kotatsu.base.domain.MangaDataRepository
import org.koitharu.kotatsu.base.ui.BaseViewModel import org.koitharu.kotatsu.base.ui.BaseViewModel
import org.koitharu.kotatsu.core.model.MangaTag
import org.koitharu.kotatsu.core.model.SortOrder import org.koitharu.kotatsu.core.model.SortOrder
import org.koitharu.kotatsu.core.parser.RemoteMangaRepository import org.koitharu.kotatsu.core.parser.RemoteMangaRepository
import java.util.* import java.util.*
@@ -21,12 +22,10 @@ class FilterViewModel(
private var job: Job? = null private var job: Job? = null
private var selectedSortOrder: SortOrder? = state.sortOrder private var selectedSortOrder: SortOrder? = state.sortOrder
private val selectedTags = HashSet(state.tags) private val selectedTags = HashSet(state.tags)
private val availableTagsDeferred = viewModelScope.async(Dispatchers.Default + createErrorHandler()) { private val localTagsDeferred = viewModelScope.async(Dispatchers.Default) {
repository.getTags()
}
private val localTagsDeferred = viewModelScope.async(Dispatchers.Default + createErrorHandler()) {
dataRepository.findTags(repository.source) dataRepository.findTags(repository.source)
} }
private var availableTagsDeferred = loadTagsAsync()
init { init {
showFilter() showFilter()
@@ -52,21 +51,24 @@ class FilterViewModel(
val previousJob = job val previousJob = job
job = launchJob(Dispatchers.Default) { job = launchJob(Dispatchers.Default) {
previousJob?.cancelAndJoin() previousJob?.cancelAndJoin()
val tags = availableTagsDeferred.await() val tags = tryLoadTags()
val localTags = localTagsDeferred.await() val localTags = localTagsDeferred.await()
val sortOrders = repository.sortOrders val sortOrders = repository.sortOrders
val list = ArrayList<FilterItem>(sortOrders.size + tags.size + 2) val list = ArrayList<FilterItem>(sortOrders.size + (tags?.size ?: 1) + 2)
list.add(FilterItem.Header(R.string.sort_order)) list.add(FilterItem.Header(R.string.sort_order))
sortOrders.sortedBy { it.ordinal }.mapTo(list) { sortOrders.sortedBy { it.ordinal }.mapTo(list) {
FilterItem.Sort(it, isSelected = it == selectedSortOrder) FilterItem.Sort(it, isSelected = it == selectedSortOrder)
} }
if (tags.isNotEmpty() || selectedTags.isNotEmpty()) { if (tags == null || tags.isNotEmpty() || selectedTags.isNotEmpty()) {
list.add(FilterItem.Header(R.string.genres)) list.add(FilterItem.Header(R.string.genres))
val mappedTags = TreeSet<FilterItem.Tag>(compareBy({ !it.isChecked }, { it.tag.title })) val mappedTags = TreeSet<FilterItem.Tag>(compareBy({ !it.isChecked }, { it.tag.title }))
localTags.mapTo(mappedTags) { FilterItem.Tag(it, isChecked = it in selectedTags) } localTags.mapTo(mappedTags) { FilterItem.Tag(it, isChecked = it in selectedTags) }
tags.mapTo(mappedTags) { FilterItem.Tag(it, isChecked = it in selectedTags) } tags?.mapTo(mappedTags) { FilterItem.Tag(it, isChecked = it in selectedTags) }
selectedTags.mapTo(mappedTags) { FilterItem.Tag(it, isChecked = true) } selectedTags.mapTo(mappedTags) { FilterItem.Tag(it, isChecked = true) }
list.addAll(mappedTags) list.addAll(mappedTags)
if (tags == null) {
list.add(FilterItem.Error(R.string.filter_load_error))
}
} }
ensureActive() ensureActive()
filter.postValue(list) filter.postValue(list)
@@ -93,4 +95,20 @@ class FilterViewModel(
updateFilters() updateFilters()
} }
} }
private suspend fun tryLoadTags(): Set<MangaTag>? {
val shouldRetryOnError = availableTagsDeferred.isCompleted
val result = availableTagsDeferred.await()
if (result == null && shouldRetryOnError) {
availableTagsDeferred = loadTagsAsync()
return availableTagsDeferred.await()
}
return result
}
private fun loadTagsAsync() = viewModelScope.async(Dispatchers.Default) {
kotlin.runCatching {
repository.getTags()
}.getOrNull()
}
} }

View File

@@ -253,4 +253,5 @@
<string name="screenshots_allow">Разрешить</string> <string name="screenshots_allow">Разрешить</string>
<string name="screenshots_block_nsfw">Запретить для NSFW</string> <string name="screenshots_block_nsfw">Запретить для NSFW</string>
<string name="screenshots_block_all">Запретить всегда</string> <string name="screenshots_block_all">Запретить всегда</string>
<string name="filter_load_error">Не удалось загрузить список жанров</string>
</resources> </resources>

View File

@@ -255,4 +255,5 @@
<string name="screenshots_allow">Allow</string> <string name="screenshots_allow">Allow</string>
<string name="screenshots_block_nsfw">Block on NSFW</string> <string name="screenshots_block_nsfw">Block on NSFW</string>
<string name="screenshots_block_all">Block always</string> <string name="screenshots_block_all">Block always</string>
<string name="filter_load_error">Unable to load genres list</string>
</resources> </resources>