Add new filter fields
This commit is contained in:
@@ -9,6 +9,7 @@ import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.util.ext.iterator
|
||||
import org.koitharu.kotatsu.details.ui.model.ChapterListItem
|
||||
import org.koitharu.kotatsu.parsers.model.ContentRating
|
||||
import org.koitharu.kotatsu.parsers.model.Demographic
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import org.koitharu.kotatsu.parsers.model.MangaChapter
|
||||
import org.koitharu.kotatsu.parsers.model.MangaState
|
||||
@@ -68,6 +69,16 @@ val ContentRating.titleResId: Int
|
||||
ContentRating.ADULT -> R.string.rating_adult
|
||||
}
|
||||
|
||||
@get:StringRes
|
||||
val Demographic.titleResId: Int
|
||||
get() = when (this) {
|
||||
Demographic.SHOUNEN -> R.string.demographic_shounen
|
||||
Demographic.SHOUJO -> R.string.demographic_shoujo
|
||||
Demographic.SEINEN -> R.string.demographic_seinen
|
||||
Demographic.JOSEI -> R.string.demographic_josei
|
||||
Demographic.NONE -> R.string.none
|
||||
}
|
||||
|
||||
fun Manga.findChapter(id: Long): MangaChapter? {
|
||||
return chapters?.findById(id)
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ import androidx.viewpager2.widget.ViewPager2
|
||||
import com.google.android.material.button.MaterialButton
|
||||
import com.google.android.material.chip.Chip
|
||||
import com.google.android.material.progressindicator.BaseProgressIndicator
|
||||
import com.google.android.material.slider.RangeSlider
|
||||
import com.google.android.material.slider.Slider
|
||||
import com.google.android.material.tabs.TabLayout
|
||||
import kotlin.math.roundToInt
|
||||
@@ -88,6 +89,17 @@ fun Slider.setValueRounded(newValue: Float) {
|
||||
value = roundedValue.coerceIn(valueFrom, valueTo)
|
||||
}
|
||||
|
||||
fun RangeSlider.setValuesRounded(vararg newValues: Float) {
|
||||
val step = stepSize
|
||||
values = newValues.map { newValue ->
|
||||
if (step <= 0f) {
|
||||
newValue
|
||||
} else {
|
||||
(newValue / step).roundToInt() * step
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun RecyclerView.invalidateNestedItemDecorations() {
|
||||
descendants.filterIsInstance<RecyclerView>().forEach {
|
||||
it.invalidateItemDecorations()
|
||||
|
||||
@@ -67,6 +67,9 @@ class FilterCoordinator @Inject constructor(
|
||||
val isFilterApplied: Boolean
|
||||
get() = !currentListFilter.value.isEmpty()
|
||||
|
||||
val query: StateFlow<String?> = currentListFilter.map { it.query }
|
||||
.stateIn(coroutineScope, SharingStarted.Lazily, null)
|
||||
|
||||
val sortOrder: StateFlow<FilterProperty<SortOrder>> = currentSortOrder.map { selected ->
|
||||
FilterProperty(
|
||||
availableItems = availableSortOrders.sortedByOrdinal(),
|
||||
@@ -261,6 +264,12 @@ class FilterCoordinator @Inject constructor(
|
||||
currentListFilter.value = value
|
||||
}
|
||||
|
||||
fun setQuery(value: String?) {
|
||||
currentListFilter.update { oldValue ->
|
||||
oldValue.copy(query = value?.trim()?.takeUnless { it.isEmpty() })
|
||||
}
|
||||
}
|
||||
|
||||
fun setLocale(value: Locale?) {
|
||||
currentListFilter.update { oldValue ->
|
||||
oldValue.copy(locale = value)
|
||||
@@ -273,6 +282,12 @@ class FilterCoordinator @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
fun setYearRange(valueFrom: Int, valueTo: Int) {
|
||||
currentListFilter.update { oldValue ->
|
||||
oldValue.copy(yearFrom = valueFrom, yearTo = valueTo)
|
||||
}
|
||||
}
|
||||
|
||||
fun toggleState(value: MangaState, isSelected: Boolean) {
|
||||
currentListFilter.update { oldValue ->
|
||||
oldValue.copy(
|
||||
@@ -289,6 +304,14 @@ class FilterCoordinator @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
fun toggleContentType(value: ContentType, isSelected: Boolean) {
|
||||
currentListFilter.update { oldValue ->
|
||||
oldValue.copy(
|
||||
types = if (isSelected) oldValue.types + value else oldValue.types - value,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun toggleTag(value: MangaTag, isSelected: Boolean) {
|
||||
currentListFilter.update { oldValue ->
|
||||
val newTags = if (capabilities.isMultipleTagsSupported) {
|
||||
|
||||
@@ -13,6 +13,8 @@ import androidx.core.view.updatePadding
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import com.google.android.material.button.MaterialButtonToggleGroup
|
||||
import com.google.android.material.chip.Chip
|
||||
import com.google.android.material.slider.BaseOnChangeListener
|
||||
import com.google.android.material.slider.RangeSlider
|
||||
import com.google.android.material.slider.Slider
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.model.SortDirection
|
||||
@@ -25,6 +27,7 @@ import org.koitharu.kotatsu.core.util.ext.getDisplayName
|
||||
import org.koitharu.kotatsu.core.util.ext.observe
|
||||
import org.koitharu.kotatsu.core.util.ext.parentView
|
||||
import org.koitharu.kotatsu.core.util.ext.setValueRounded
|
||||
import org.koitharu.kotatsu.core.util.ext.setValuesRounded
|
||||
import org.koitharu.kotatsu.core.util.ext.showDistinct
|
||||
import org.koitharu.kotatsu.core.util.ext.textAndVisible
|
||||
import org.koitharu.kotatsu.databinding.SheetFilterBinding
|
||||
@@ -32,16 +35,19 @@ import org.koitharu.kotatsu.filter.ui.FilterCoordinator
|
||||
import org.koitharu.kotatsu.filter.ui.model.FilterProperty
|
||||
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.SortOrder
|
||||
import org.koitharu.kotatsu.parsers.model.YEAR_UNKNOWN
|
||||
import org.koitharu.kotatsu.parsers.util.toIntUp
|
||||
import java.util.Locale
|
||||
import com.google.android.material.R as materialR
|
||||
|
||||
class FilterSheetFragment : BaseAdaptiveSheet<SheetFilterBinding>(),
|
||||
AdapterView.OnItemSelectedListener,
|
||||
ChipsView.OnChipClickListener, MaterialButtonToggleGroup.OnButtonCheckedListener, Slider.OnChangeListener {
|
||||
ChipsView.OnChipClickListener, MaterialButtonToggleGroup.OnButtonCheckedListener {
|
||||
|
||||
override fun onCreateViewBinding(inflater: LayoutInflater, container: ViewGroup?): SheetFilterBinding {
|
||||
return SheetFilterBinding.inflate(inflater, container, false)
|
||||
@@ -62,16 +68,21 @@ class FilterSheetFragment : BaseAdaptiveSheet<SheetFilterBinding>(),
|
||||
filter.tags.observe(viewLifecycleOwner, this::onTagsChanged)
|
||||
filter.tagsExcluded.observe(viewLifecycleOwner, this::onTagsExcludedChanged)
|
||||
filter.states.observe(viewLifecycleOwner, this::onStateChanged)
|
||||
filter.contentTypes.observe(viewLifecycleOwner, this::onContentTypesChanged)
|
||||
filter.contentRating.observe(viewLifecycleOwner, this::onContentRatingChanged)
|
||||
filter.demographics.observe(viewLifecycleOwner, this::onDemographicsChanged)
|
||||
filter.year.observe(viewLifecycleOwner, this::onYearChanged)
|
||||
filter.yearRange.observe(viewLifecycleOwner, this::onYearRangeChanged)
|
||||
|
||||
binding.spinnerLocale.onItemSelectedListener = this
|
||||
binding.spinnerOrder.onItemSelectedListener = this
|
||||
binding.chipsState.onChipClickListener = this
|
||||
binding.chipsTypes.onChipClickListener = this
|
||||
binding.chipsContentRating.onChipClickListener = this
|
||||
binding.chipsGenres.onChipClickListener = this
|
||||
binding.chipsGenresExclude.onChipClickListener = this
|
||||
binding.sliderYear.addOnChangeListener(this)
|
||||
binding.sliderYear.addOnChangeListener(this::onSliderValueChange)
|
||||
binding.sliderYearsRange.addOnChangeListener(this::onRangeSliderValueChange)
|
||||
binding.layoutSortDirection.addOnButtonCheckedListener(this)
|
||||
}
|
||||
|
||||
@@ -95,14 +106,33 @@ class FilterSheetFragment : BaseAdaptiveSheet<SheetFilterBinding>(),
|
||||
|
||||
override fun onNothingSelected(parent: AdapterView<*>?) = Unit
|
||||
|
||||
override fun onValueChange(slider: Slider, value: Float, fromUser: Boolean) {
|
||||
private fun onSliderValueChange(slider: Slider, value: Float, fromUser: Boolean) {
|
||||
if (!fromUser) {
|
||||
return
|
||||
}
|
||||
val intValue = value.toInt()
|
||||
val filter = requireFilter()
|
||||
when (slider.id) {
|
||||
R.id.slider_year -> filter.setYear(intValue)
|
||||
R.id.slider_year -> filter.setYear(
|
||||
if (intValue <= slider.valueFrom.toIntUp()) {
|
||||
YEAR_UNKNOWN
|
||||
} else {
|
||||
intValue
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun onRangeSliderValueChange(slider: RangeSlider, value: Float, fromUser: Boolean) {
|
||||
if (!fromUser) {
|
||||
return
|
||||
}
|
||||
val filter = requireFilter()
|
||||
when (slider.id) {
|
||||
R.id.slider_yearsRange -> filter.setYearRange(
|
||||
valueFrom = slider.valueFrom.toInt(),
|
||||
valueTo = slider.valueTo.toInt(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,6 +146,7 @@ class FilterSheetFragment : BaseAdaptiveSheet<SheetFilterBinding>(),
|
||||
filter.toggleTag(data, !chip.isChecked)
|
||||
}
|
||||
|
||||
is ContentType -> filter.toggleContentType(data, !chip.isChecked)
|
||||
is ContentRating -> filter.toggleContentRating(data, !chip.isChecked)
|
||||
null -> TagsCatalogSheet.show(getChildFragmentManager(), chip.parentView?.id == R.id.chips_genresExclude)
|
||||
}
|
||||
@@ -270,6 +301,23 @@ class FilterSheetFragment : BaseAdaptiveSheet<SheetFilterBinding>(),
|
||||
b.chipsState.setChips(chips)
|
||||
}
|
||||
|
||||
private fun onContentTypesChanged(value: FilterProperty<ContentType>) {
|
||||
val b = viewBinding ?: return
|
||||
b.textViewTypesTitle.isGone = value.isEmpty()
|
||||
b.chipsTypes.isGone = value.isEmpty()
|
||||
if (value.isEmpty()) {
|
||||
return
|
||||
}
|
||||
val chips = value.availableItems.map { type ->
|
||||
ChipsView.ChipModel(
|
||||
title = getString(type.titleResId),
|
||||
isChecked = type in value.selectedItems,
|
||||
data = type,
|
||||
)
|
||||
}
|
||||
b.chipsTypes.setChips(chips)
|
||||
}
|
||||
|
||||
private fun onContentRatingChanged(value: FilterProperty<ContentRating>) {
|
||||
val b = viewBinding ?: return
|
||||
b.textViewContentRatingTitle.isGone = value.isEmpty()
|
||||
@@ -287,16 +335,58 @@ class FilterSheetFragment : BaseAdaptiveSheet<SheetFilterBinding>(),
|
||||
b.chipsContentRating.setChips(chips)
|
||||
}
|
||||
|
||||
private fun onDemographicsChanged(value: FilterProperty<Demographic>) {
|
||||
val b = viewBinding ?: return
|
||||
b.textViewDemographicsTitle.isGone = value.isEmpty()
|
||||
b.chipsDemographics.isGone = value.isEmpty()
|
||||
if (value.isEmpty()) {
|
||||
return
|
||||
}
|
||||
val chips = value.availableItems.map { demographic ->
|
||||
ChipsView.ChipModel(
|
||||
title = getString(demographic.titleResId),
|
||||
isChecked = demographic in value.selectedItems,
|
||||
data = demographic,
|
||||
)
|
||||
}
|
||||
b.chipsDemographics.setChips(chips)
|
||||
}
|
||||
|
||||
private fun onYearChanged(value: FilterProperty<Int>) {
|
||||
val b = viewBinding ?: return
|
||||
b.textViewYear.isGone = value.isEmpty()
|
||||
b.headerYear.isGone = value.isEmpty()
|
||||
b.sliderYear.isGone = value.isEmpty()
|
||||
if (value.isEmpty()) {
|
||||
return
|
||||
}
|
||||
val currentValue = value.selectedItems.singleOrNull() ?: YEAR_UNKNOWN
|
||||
b.textViewYearValue.text = if (currentValue == YEAR_UNKNOWN) {
|
||||
getString(R.string.none)
|
||||
} else {
|
||||
currentValue.toString()
|
||||
}
|
||||
b.sliderYear.valueFrom = value.availableItems.first().toFloat()
|
||||
b.sliderYear.valueTo = value.availableItems.last().toFloat()
|
||||
b.sliderYear.setValueRounded((value.selectedItems.singleOrNull() ?: YEAR_UNKNOWN).toFloat())
|
||||
b.sliderYear.setValueRounded(currentValue.toFloat())
|
||||
}
|
||||
|
||||
private fun onYearRangeChanged(value: FilterProperty<Int>) {
|
||||
val b = viewBinding ?: return
|
||||
b.headerYearsRange.isGone = value.isEmpty()
|
||||
b.sliderYearsRange.isGone = value.isEmpty()
|
||||
if (value.isEmpty()) {
|
||||
return
|
||||
}
|
||||
b.sliderYearsRange.valueFrom = value.availableItems.first().toFloat()
|
||||
b.sliderYearsRange.valueTo = value.availableItems.last().toFloat()
|
||||
val currentValueFrom = value.selectedItems.firstOrNull()?.toFloat() ?: b.sliderYearsRange.valueFrom
|
||||
val currentValueTo = value.selectedItems.lastOrNull()?.toFloat() ?: b.sliderYearsRange.valueTo
|
||||
b.textViewYearsRangeValue.text = getString(
|
||||
R.string.memory_usage_pattern,
|
||||
currentValueFrom.toString(),
|
||||
currentValueTo.toString(),
|
||||
)
|
||||
b.sliderYearsRange.setValuesRounded(currentValueFrom, currentValueTo)
|
||||
}
|
||||
|
||||
private fun requireFilter() = (requireActivity() as FilterCoordinator.Owner).filterCoordinator
|
||||
|
||||
@@ -204,6 +204,26 @@
|
||||
app:chipStyle="@style/Widget.Kotatsu.Chip.Filter"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textView_types_title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/margin_normal"
|
||||
android:singleLine="true"
|
||||
android:text="@string/type"
|
||||
android:textAppearance="?textAppearanceTitleSmall"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<org.koitharu.kotatsu.core.ui.widgets.ChipsView
|
||||
android:id="@+id/chips_types"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/margin_normal"
|
||||
android:visibility="gone"
|
||||
app:chipStyle="@style/Widget.Kotatsu.Chip.Filter"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textView_state_title"
|
||||
android:layout_width="match_parent"
|
||||
@@ -245,16 +265,55 @@
|
||||
tools:visibility="visible" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textView_year"
|
||||
android:id="@+id/textView_demographics_title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/margin_normal"
|
||||
android:singleLine="true"
|
||||
android:text="@string/year"
|
||||
android:text="@string/demographics"
|
||||
android:textAppearance="?textAppearanceTitleSmall"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<org.koitharu.kotatsu.core.ui.widgets.ChipsView
|
||||
android:id="@+id/chips_demographics"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/margin_normal"
|
||||
android:visibility="gone"
|
||||
app:chipStyle="@style/Widget.Kotatsu.Chip.Filter"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/header_year"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/margin_normal"
|
||||
android:baselineAligned="true"
|
||||
android:baselineAlignedChildIndex="0"
|
||||
android:orientation="horizontal"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textView_year"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:singleLine="true"
|
||||
android:text="@string/year"
|
||||
android:textAppearance="?textAppearanceTitleSmall" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textView_year_value"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:singleLine="true"
|
||||
android:textAppearance="?textAppearanceBodyMedium"
|
||||
tools:text="2024" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<com.google.android.material.slider.Slider
|
||||
android:id="@+id/slider_year"
|
||||
android:layout_width="match_parent"
|
||||
@@ -262,8 +321,55 @@
|
||||
android:layout_marginTop="@dimen/margin_normal"
|
||||
android:stepSize="1"
|
||||
android:visibility="gone"
|
||||
app:labelBehavior="visible"
|
||||
app:labelBehavior="gone"
|
||||
app:tickVisible="true"
|
||||
tools:value="2020"
|
||||
tools:valueFrom="1900"
|
||||
tools:valueTo="2090"
|
||||
tools:visibility="visible" />
|
||||
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/header_yearsRange"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/margin_normal"
|
||||
android:baselineAligned="true"
|
||||
android:baselineAlignedChildIndex="0"
|
||||
android:orientation="horizontal"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textView_yearsRange"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:singleLine="true"
|
||||
android:text="@string/year"
|
||||
android:textAppearance="?textAppearanceTitleSmall" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textView_yearsRange_value"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:singleLine="true"
|
||||
android:textAppearance="?textAppearanceBodyMedium"
|
||||
tools:text="2024" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<com.google.android.material.slider.RangeSlider
|
||||
android:id="@+id/slider_yearsRange"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/margin_normal"
|
||||
android:stepSize="1"
|
||||
android:visibility="gone"
|
||||
app:labelBehavior="gone"
|
||||
app:tickVisible="true"
|
||||
tools:valueFrom="1900"
|
||||
tools:valueTo="2090"
|
||||
tools:visibility="visible" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
@@ -719,4 +719,9 @@
|
||||
<string name="popular_in_year">Popular this year</string>
|
||||
<string name="original_language">Original language</string>
|
||||
<string name="year">Year</string>
|
||||
<string name="demographics">Demographics</string>
|
||||
<string name="demographic_shounen">Shounen</string>
|
||||
<string name="demographic_shoujo">Shoujo</string>
|
||||
<string name="demographic_seinen">Seinen</string>
|
||||
<string name="demographic_josei">Josei</string>
|
||||
</resources>
|
||||
|
||||
Reference in New Issue
Block a user