From 1e22e8de45df94b8e84c2f846fee6bc3928cafb5 Mon Sep 17 00:00:00 2001 From: Koitharu Date: Mon, 7 Oct 2024 20:02:34 +0300 Subject: [PATCH] Improve filter --- .../kotatsu/filter/ui/FilterCoordinator.kt | 9 +++ .../kotatsu/filter/ui/FilterFieldLayout.kt | 5 ++ .../kotatsu/filter/ui/FilterHeaderFragment.kt | 13 ++++ .../kotatsu/filter/ui/FilterHeaderProducer.kt | 75 +++++++++++++++---- .../filter/ui/sheet/FilterSheetFragment.kt | 9 +++ app/src/main/res/values/strings.xml | 1 + 6 files changed, 98 insertions(+), 14 deletions(-) diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/filter/ui/FilterCoordinator.kt b/app/src/main/kotlin/org/koitharu/kotatsu/filter/ui/FilterCoordinator.kt index 110bf6373..ec95fea68 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/filter/ui/FilterCoordinator.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/filter/ui/FilterCoordinator.kt @@ -332,6 +332,15 @@ class FilterCoordinator @Inject constructor( } } + fun toggleDemographic(value: Demographic, isSelected: Boolean) { + currentListFilter.update { oldValue -> + oldValue.copy( + demographics = if (isSelected) oldValue.demographics + value else oldValue.demographics - value, + query = oldValue.takeQueryIfSupported(), + ) + } + } + fun toggleContentType(value: ContentType, isSelected: Boolean) { currentListFilter.update { oldValue -> oldValue.copy( diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/filter/ui/FilterFieldLayout.kt b/app/src/main/kotlin/org/koitharu/kotatsu/filter/ui/FilterFieldLayout.kt index 90ca05cd8..b5b1f83c0 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/filter/ui/FilterFieldLayout.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/filter/ui/FilterFieldLayout.kt @@ -8,6 +8,7 @@ import android.view.View import android.widget.RelativeLayout import android.widget.TextView import androidx.annotation.AttrRes +import androidx.annotation.StringRes import androidx.core.content.ContextCompat import androidx.core.content.withStyledAttributes import androidx.core.view.isInvisible @@ -68,6 +69,10 @@ class FilterFieldLayout @JvmOverloads constructor( } } + fun setTitle(@StringRes titleResId: Int) { + binding.textViewTitle.setText(titleResId) + } + fun setError(errorMessage: String?) { if (errorMessage == null && errorView == null) { return diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/filter/ui/FilterHeaderFragment.kt b/app/src/main/kotlin/org/koitharu/kotatsu/filter/ui/FilterHeaderFragment.kt index 0dc0271e7..ff7d30917 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/filter/ui/FilterHeaderFragment.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/filter/ui/FilterHeaderFragment.kt @@ -16,7 +16,13 @@ import org.koitharu.kotatsu.core.util.ext.observe import org.koitharu.kotatsu.databinding.FragmentFilterHeaderBinding import org.koitharu.kotatsu.filter.ui.model.FilterHeaderModel import org.koitharu.kotatsu.filter.ui.tags.TagsCatalogSheet +import org.koitharu.kotatsu.parsers.model.ContentRating +import org.koitharu.kotatsu.parsers.model.ContentType +import org.koitharu.kotatsu.parsers.model.Demographic +import org.koitharu.kotatsu.parsers.model.MangaState import org.koitharu.kotatsu.parsers.model.MangaTag +import org.koitharu.kotatsu.parsers.model.YEAR_UNKNOWN +import java.util.Locale import javax.inject.Inject @AndroidEntryPoint @@ -55,6 +61,13 @@ class FilterHeaderFragment : BaseFragment(), ChipsV override fun onChipCloseClick(chip: Chip, data: Any?) { when (data) { is String -> filter.setQuery(null) + is ContentRating -> filter.toggleContentRating(data, false) + is Demographic -> filter.toggleDemographic(data, false) + is ContentType -> filter.toggleContentType(data, false) + is MangaState -> filter.toggleState(data, false) + is Locale -> filter.setLocale(null) + is Int -> filter.setYear(YEAR_UNKNOWN) + is IntRange -> filter.setYearRange(YEAR_UNKNOWN, YEAR_UNKNOWN) } } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/filter/ui/FilterHeaderProducer.kt b/app/src/main/kotlin/org/koitharu/kotatsu/filter/ui/FilterHeaderProducer.kt index c29c0eeec..e011beaa7 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/filter/ui/FilterHeaderProducer.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/filter/ui/FilterHeaderProducer.kt @@ -3,12 +3,15 @@ package org.koitharu.kotatsu.filter.ui import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine import org.koitharu.kotatsu.R +import org.koitharu.kotatsu.core.model.titleResId import org.koitharu.kotatsu.core.ui.widgets.ChipsView import org.koitharu.kotatsu.filter.ui.model.FilterHeaderModel import org.koitharu.kotatsu.filter.ui.model.FilterProperty +import org.koitharu.kotatsu.parsers.model.MangaListFilter import org.koitharu.kotatsu.parsers.model.MangaListFilterCapabilities import org.koitharu.kotatsu.parsers.model.MangaSource import org.koitharu.kotatsu.parsers.model.MangaTag +import org.koitharu.kotatsu.parsers.util.toTitleCase import org.koitharu.kotatsu.search.domain.MangaSearchRepository import javax.inject.Inject import com.google.android.material.R as materialR @@ -18,15 +21,14 @@ class FilterHeaderProducer @Inject constructor( ) { fun observeHeader(filterCoordinator: FilterCoordinator): Flow { - return combine(filterCoordinator.tags, filterCoordinator.query) { tags, query -> - createChipsList( + return combine(filterCoordinator.tags, filterCoordinator.observe()) { tags, snapshot -> + val chipList = createChipsList( source = filterCoordinator.mangaSource, capabilities = filterCoordinator.capabilities, - property = tags, - query = query, + tagsProperty = tags, + snapshot = snapshot.listFilter, limit = 8, ) - }.combine(filterCoordinator.observe()) { chipList, snapshot -> FilterHeaderModel( chips = chipList, sortOrder = snapshot.sortOrder, @@ -38,20 +40,20 @@ class FilterHeaderProducer @Inject constructor( private suspend fun createChipsList( source: MangaSource, capabilities: MangaListFilterCapabilities, - property: FilterProperty, - query: String?, + tagsProperty: FilterProperty, + snapshot: MangaListFilter, limit: Int, ): List { val result = ArrayDeque(limit + 3) - if (query.isNullOrEmpty() || capabilities.isSearchWithFiltersSupported) { - val selectedTags = property.selectedItems.toMutableSet() + if (snapshot.query.isNullOrEmpty() || capabilities.isSearchWithFiltersSupported) { + val selectedTags = tagsProperty.selectedItems.toMutableSet() var tags = if (selectedTags.isEmpty()) { searchRepository.getTagsSuggestion("", limit, source) } else { searchRepository.getTagsSuggestion(selectedTags).take(limit) } if (tags.size < limit) { - tags = tags + property.availableItems.take(limit - tags.size) + tags = tags + tagsProperty.availableItems.take(limit - tags.size) } if (tags.isEmpty() && selectedTags.isEmpty()) { return emptyList() @@ -77,13 +79,59 @@ class FilterHeaderProducer @Inject constructor( result.addFirst(model) } } - if (!query.isNullOrEmpty()) { + snapshot.locale?.let { result.addFirst( ChipsView.ChipModel( - title = query, + title = it.getDisplayName(it).toTitleCase(it), + icon = R.drawable.ic_language, + isCloseable = true, + data = it, + ), + ) + } + snapshot.types.forEach { + result.addFirst( + ChipsView.ChipModel( + titleResId = it.titleResId, + isCloseable = true, + data = it, + ), + ) + } + snapshot.demographics.forEach { + result.addFirst( + ChipsView.ChipModel( + titleResId = it.titleResId, + isCloseable = true, + data = it, + ), + ) + } + snapshot.contentRating.forEach { + result.addFirst( + ChipsView.ChipModel( + titleResId = it.titleResId, + isCloseable = true, + data = it, + ), + ) + } + snapshot.states.forEach { + result.addFirst( + ChipsView.ChipModel( + titleResId = it.titleResId, + isCloseable = true, + data = it, + ), + ) + } + if (!snapshot.query.isNullOrEmpty()) { + result.addFirst( + ChipsView.ChipModel( + title = snapshot.query, icon = materialR.drawable.abc_ic_search_api_material, isCloseable = true, - data = query, + data = snapshot.query, ), ) } @@ -97,6 +145,5 @@ class FilterHeaderProducer @Inject constructor( private fun moreTagsChip() = ChipsView.ChipModel( titleResId = R.string.more, isDropdown = true, - // icon = materialR.drawable.abc_ic_menu_overflow_material, ) } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/filter/ui/sheet/FilterSheetFragment.kt b/app/src/main/kotlin/org/koitharu/kotatsu/filter/ui/sheet/FilterSheetFragment.kt index dc9115332..53952a129 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/filter/ui/sheet/FilterSheetFragment.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/filter/ui/sheet/FilterSheetFragment.kt @@ -68,12 +68,20 @@ class FilterSheetFragment : BaseAdaptiveSheet(), filter.year.observe(viewLifecycleOwner, this::onYearChanged) filter.yearRange.observe(viewLifecycleOwner, this::onYearRangeChanged) + binding.layoutGenres.setTitle( + if (filter.capabilities.isMultipleTagsSupported) { + R.string.genres + } else { + R.string.genre + }, + ) binding.spinnerLocale.onItemSelectedListener = this binding.spinnerOriginalLocale.onItemSelectedListener = this binding.spinnerOrder.onItemSelectedListener = this binding.chipsState.onChipClickListener = this binding.chipsTypes.onChipClickListener = this binding.chipsContentRating.onChipClickListener = this + binding.chipsDemographics.onChipClickListener = this binding.chipsGenres.onChipClickListener = this binding.chipsGenresExclude.onChipClickListener = this binding.sliderYear.addOnChangeListener(this::onSliderValueChange) @@ -143,6 +151,7 @@ class FilterSheetFragment : BaseAdaptiveSheet(), is ContentType -> filter.toggleContentType(data, !chip.isChecked) is ContentRating -> filter.toggleContentRating(data, !chip.isChecked) + is Demographic -> filter.toggleDemographic(data, !chip.isChecked) null -> TagsCatalogSheet.show(getChildFragmentManager(), chip.parentView?.id == R.id.chips_genresExclude) } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index c1b0ecfaa..00cd1261b 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -741,4 +741,5 @@ Start download Save selected manga? This may consume traffic and disk space Save manga + Genre