Handle filter loading errors
This commit is contained in:
@@ -10,4 +10,5 @@ class FilterAdapter(
|
|||||||
filterTagDelegate(listener),
|
filterTagDelegate(listener),
|
||||||
filterHeaderDelegate(),
|
filterHeaderDelegate(),
|
||||||
filterLoadingDelegate(),
|
filterLoadingDelegate(),
|
||||||
|
filterErrorDelegate(),
|
||||||
)
|
)
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,4 +21,8 @@ sealed interface FilterItem {
|
|||||||
) : FilterItem
|
) : FilterItem
|
||||||
|
|
||||||
object Loading : FilterItem
|
object Loading : FilterItem
|
||||||
|
|
||||||
|
class Error(
|
||||||
|
@StringRes val textResId: Int,
|
||||||
|
) : FilterItem
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -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>
|
||||||
@@ -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>
|
||||||
Reference in New Issue
Block a user