diff --git a/app/build.gradle b/app/build.gradle index b23267ead..e8093e261 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -16,8 +16,8 @@ android { applicationId 'org.koitharu.kotatsu' minSdk = 21 targetSdk = 35 - versionCode = 669 - versionName = '7.6-a1' + versionCode = 670 + versionName = '7.6-a2' generatedDensities = [] testInstrumentationRunner 'org.koitharu.kotatsu.HiltTestRunner' ksp { @@ -83,7 +83,7 @@ afterEvaluate { } dependencies { //noinspection GradleDependency - implementation('com.github.KotatsuApp:kotatsu-parsers:336c4a4d49') { + implementation('com.github.KotatsuApp:kotatsu-parsers:f2354957e6') { exclude group: 'org.json', module: 'json' } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/View.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/View.kt index f995e098e..82091bee9 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/View.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/View.kt @@ -96,7 +96,7 @@ fun RangeSlider.setValuesRounded(vararg newValues: Float) { newValue } else { (newValue / step).roundToInt() * step - } + }.coerceIn(valueFrom, valueTo) } } 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 9aa8de63a..059546880 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 @@ -36,6 +36,7 @@ import org.koitharu.kotatsu.parsers.model.MangaTag import org.koitharu.kotatsu.parsers.model.SortOrder import org.koitharu.kotatsu.parsers.model.YEAR_MIN import org.koitharu.kotatsu.parsers.util.SuspendLazy +import org.koitharu.kotatsu.parsers.util.ifZero import org.koitharu.kotatsu.remotelist.ui.RemoteListFragment import org.koitharu.kotatsu.search.domain.MangaSearchRepository import java.util.Calendar @@ -238,7 +239,7 @@ class FilterCoordinator @Inject constructor( }.map { selected -> FilterProperty( availableItems = listOf(YEAR_MIN, MAX_YEAR), - selectedItems = setOf(selected.yearFrom, selected.yearTo), + selectedItems = setOf(selected.yearFrom.ifZero { YEAR_MIN }, selected.yearTo.ifZero { MAX_YEAR }), ) }.stateIn(coroutineScope, SharingStarted.Lazily, FilterProperty.LOADING) } else { @@ -258,6 +259,7 @@ class FilterCoordinator @Inject constructor( fun setSortOrder(newSortOrder: SortOrder) { currentSortOrder.value = newSortOrder + repository.defaultSortOrder = newSortOrder } fun set(value: MangaListFilter) { @@ -276,6 +278,12 @@ class FilterCoordinator @Inject constructor( } } + fun setOriginalLocale(value: Locale?) { + currentListFilter.update { oldValue -> + oldValue.copy(originalLocale = value) + } + } + fun setYear(value: Int) { currentListFilter.update { oldValue -> oldValue.copy(year = value) 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 new file mode 100644 index 000000000..107f66072 --- /dev/null +++ b/app/src/main/kotlin/org/koitharu/kotatsu/filter/ui/FilterFieldLayout.kt @@ -0,0 +1,104 @@ +package org.koitharu.kotatsu.filter.ui + +import android.content.Context +import android.util.AttributeSet +import android.view.Gravity +import android.view.LayoutInflater +import android.view.View +import android.widget.RelativeLayout +import android.widget.TextView +import androidx.annotation.AttrRes +import androidx.core.content.ContextCompat +import androidx.core.content.withStyledAttributes +import androidx.core.view.isInvisible +import androidx.core.view.isVisible +import androidx.core.view.setPadding +import androidx.core.widget.TextViewCompat +import org.koitharu.kotatsu.R +import org.koitharu.kotatsu.core.util.ext.drawableStart +import org.koitharu.kotatsu.core.util.ext.getThemeColorStateList +import org.koitharu.kotatsu.core.util.ext.setThemeTextAppearance +import org.koitharu.kotatsu.core.util.ext.textAndVisible +import org.koitharu.kotatsu.databinding.ViewFilterFieldBinding +import java.util.LinkedList +import com.google.android.material.R as materialR + +class FilterFieldLayout @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + @AttrRes defStyleAttr: Int = 0, +) : RelativeLayout(context, attrs) { + + private val contentViews = LinkedList() + private val binding = ViewFilterFieldBinding.inflate(LayoutInflater.from(context), this) + private var errorView: TextView? = null + private var isInitialized = true + + init { + context.withStyledAttributes(attrs, R.styleable.FilterFieldLayout, defStyleAttr) { + binding.textViewTitle.text = getString(R.styleable.FilterFieldLayout_title) + binding.buttonMore.isInvisible = !getBoolean(R.styleable.FilterFieldLayout_showMoreButton, false) + } + } + + override fun onViewAdded(child: View) { + super.onViewAdded(child) + if (!isInitialized) { + return + } + assert(child.id != View.NO_ID) + val lp = (child.layoutParams as? LayoutParams) ?: (generateDefaultLayoutParams() as LayoutParams) + lp.alignWithParent = true + lp.width = 0 + lp.addRule(ALIGN_PARENT_START) + lp.addRule(ALIGN_PARENT_END) + lp.addRule(BELOW, contentViews.lastOrNull()?.id ?: binding.textViewTitle.id) + child.layoutParams = lp + contentViews.add(child) + } + + override fun onViewRemoved(child: View?) { + super.onViewRemoved(child) + contentViews.remove(child) + } + + fun setValueText(valueText: String?) { + if (!binding.buttonMore.isVisible) { + binding.textViewValue.textAndVisible = valueText + } + } + + fun setError(errorMessage: String?) { + if (errorMessage == null && errorView == null) { + return + } + getErrorLabel().textAndVisible = errorMessage + } + + fun setOnMoreButtonClickListener(clickListener: OnClickListener?) { + binding.buttonMore.setOnClickListener(clickListener) + } + + private fun getErrorLabel(): TextView { + errorView?.let { + return it + } + val label = TextView(context) + label.id = R.id.textView_error + label.compoundDrawablePadding = resources.getDimensionPixelOffset(R.dimen.screen_padding) + label.gravity = Gravity.CENTER_VERTICAL or Gravity.START + label.setPadding(resources.getDimensionPixelOffset(R.dimen.margin_small)) + label.setThemeTextAppearance( + materialR.attr.textAppearanceBodySmall, + materialR.style.TextAppearance_Material3_BodySmall, + ) + label.drawableStart = ContextCompat.getDrawable(context, R.drawable.ic_error_small) + TextViewCompat.setCompoundDrawableTintList( + label, + context.getThemeColorStateList(materialR.attr.colorControlNormal), + ) + addView(errorView) + errorView = label + return label + } +} diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/filter/ui/model/FilterProperty.kt b/app/src/main/kotlin/org/koitharu/kotatsu/filter/ui/model/FilterProperty.kt index 54777769a..228911838 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/filter/ui/model/FilterProperty.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/filter/ui/model/FilterProperty.kt @@ -29,6 +29,8 @@ data class FilterProperty( fun isEmpty(): Boolean = availableItems.isEmpty() + fun isEmptyAndSuccess(): Boolean = availableItems.isEmpty() && error == null + companion object { val LOADING = FilterProperty( 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 282361ddb..dc9115332 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 @@ -7,17 +7,13 @@ import android.view.View import android.view.ViewGroup import android.widget.AdapterView import android.widget.ArrayAdapter -import androidx.annotation.IdRes import androidx.core.view.isGone 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 import org.koitharu.kotatsu.core.model.titleResId import org.koitharu.kotatsu.core.ui.model.titleRes import org.koitharu.kotatsu.core.ui.sheet.BaseAdaptiveSheet @@ -29,7 +25,6 @@ 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 import org.koitharu.kotatsu.filter.ui.FilterCoordinator import org.koitharu.kotatsu.filter.ui.model.FilterProperty @@ -43,11 +38,10 @@ 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(), AdapterView.OnItemSelectedListener, - ChipsView.OnChipClickListener, MaterialButtonToggleGroup.OnButtonCheckedListener { + ChipsView.OnChipClickListener { override fun onCreateViewBinding(inflater: LayoutInflater, container: ViewGroup?): SheetFilterBinding { return SheetFilterBinding.inflate(inflater, container, false) @@ -63,8 +57,8 @@ class FilterSheetFragment : BaseAdaptiveSheet(), } val filter = requireFilter() filter.sortOrder.observe(viewLifecycleOwner, this::onSortOrderChanged) - // filter.filterSortDirection.observe(viewLifecycleOwner, this::onSortDirectionChanged) filter.locale.observe(viewLifecycleOwner, this::onLocaleChanged) + filter.originalLocale.observe(viewLifecycleOwner, this::onOriginalLocaleChanged) filter.tags.observe(viewLifecycleOwner, this::onTagsChanged) filter.tagsExcluded.observe(viewLifecycleOwner, this::onTagsExcludedChanged) filter.states.observe(viewLifecycleOwner, this::onStateChanged) @@ -75,6 +69,7 @@ class FilterSheetFragment : BaseAdaptiveSheet(), filter.yearRange.observe(viewLifecycleOwner, this::onYearRangeChanged) binding.spinnerLocale.onItemSelectedListener = this + binding.spinnerOriginalLocale.onItemSelectedListener = this binding.spinnerOrder.onItemSelectedListener = this binding.chipsState.onChipClickListener = this binding.chipsTypes.onChipClickListener = this @@ -83,24 +78,20 @@ class FilterSheetFragment : BaseAdaptiveSheet(), binding.chipsGenresExclude.onChipClickListener = this binding.sliderYear.addOnChangeListener(this::onSliderValueChange) binding.sliderYearsRange.addOnChangeListener(this::onRangeSliderValueChange) - binding.layoutSortDirection.addOnButtonCheckedListener(this) - } - - override fun onButtonChecked(group: MaterialButtonToggleGroup?, checkedId: Int, isChecked: Boolean) { - if (isChecked) { - // setSortDirection(getSortDirection(checkedId) ?: return) + binding.layoutGenres.setOnMoreButtonClickListener { + TagsCatalogSheet.show(getChildFragmentManager(), isExcludeTag = false) + } + binding.layoutGenresExclude.setOnMoreButtonClickListener { + TagsCatalogSheet.show(getChildFragmentManager(), isExcludeTag = true) } } override fun onItemSelected(parent: AdapterView<*>, view: View?, position: Int, id: Long) { val filter = requireFilter() when (parent.id) { - R.id.spinner_order -> { - val value = filter.sortOrder.value.availableItems[position] - filter.setSortOrder(value) - } - + R.id.spinner_order -> filter.setSortOrder(filter.sortOrder.value.availableItems[position]) R.id.spinner_locale -> filter.setLocale(filter.locale.value.availableItems[position]) + R.id.spinner_original_locale -> filter.setOriginalLocale(filter.originalLocale.value.availableItems[position]) } } @@ -130,8 +121,12 @@ class FilterSheetFragment : BaseAdaptiveSheet(), val filter = requireFilter() when (slider.id) { R.id.slider_yearsRange -> filter.setYearRange( - valueFrom = slider.valueFrom.toInt(), - valueTo = slider.valueTo.toInt(), + valueFrom = slider.values.firstOrNull()?.let { + if (it <= slider.valueFrom) YEAR_UNKNOWN else it.toInt() + } ?: YEAR_UNKNOWN, + valueTo = slider.values.lastOrNull()?.let { + if (it >= slider.valueTo) YEAR_UNKNOWN else it.toInt() + } ?: YEAR_UNKNOWN, ) } } @@ -154,8 +149,7 @@ class FilterSheetFragment : BaseAdaptiveSheet(), private fun onSortOrderChanged(value: FilterProperty) { val b = viewBinding ?: return - b.textViewOrderTitle.isGone = value.isEmpty() - b.cardOrder.isGone = value.isEmpty() + b.layoutOrder.isGone = value.isEmpty() if (value.isEmpty()) { return } @@ -172,29 +166,9 @@ class FilterSheetFragment : BaseAdaptiveSheet(), } } - private fun onSortDirectionChanged(value: FilterProperty) { - val b = viewBinding ?: return - b.layoutSortDirection.isGone = value.isEmpty() - if (value.isEmpty()) { - return - } - val selected = value.selectedItems.single() - b.buttonOrderAsc.isEnabled = SortDirection.ASC in value.availableItems - b.buttonOrderDesc.isEnabled = SortDirection.DESC in value.availableItems - b.layoutSortDirection.removeOnButtonCheckedListener(this) - b.layoutSortDirection.check( - when (selected) { - SortDirection.ASC -> R.id.button_order_asc - SortDirection.DESC -> R.id.button_order_desc - }, - ) - b.layoutSortDirection.addOnButtonCheckedListener(this) - } - private fun onLocaleChanged(value: FilterProperty) { val b = viewBinding ?: return - b.textViewLocaleTitle.isGone = value.isEmpty() - b.cardLocale.isGone = value.isEmpty() + b.layoutLocale.isGone = value.isEmpty() if (value.isEmpty()) { return } @@ -211,83 +185,61 @@ class FilterSheetFragment : BaseAdaptiveSheet(), } } - private fun onTagsChanged(value: FilterProperty) { + private fun onOriginalLocaleChanged(value: FilterProperty) { val b = viewBinding ?: return - b.textViewGenresTitle.isGone = value.isEmpty() - b.chipsGenres.isGone = value.isEmpty() - b.textViewGenresHint.textAndVisible = value.error?.getDisplayMessage(resources) + b.layoutOriginalLocale.isGone = value.isEmpty() if (value.isEmpty()) { return } - val chips = ArrayList(value.selectedItems.size + value.availableItems.size + 1) - value.selectedItems.mapTo(chips) { tag -> + val selected = value.selectedItems.singleOrNull() + b.spinnerOriginalLocale.adapter = ArrayAdapter( + b.spinnerOriginalLocale.context, + android.R.layout.simple_spinner_dropdown_item, + android.R.id.text1, + value.availableItems.map { it.getDisplayName(b.spinnerOriginalLocale.context) }, + ) + val selectedIndex = value.availableItems.indexOf(selected) + if (selectedIndex >= 0) { + b.spinnerOriginalLocale.setSelection(selectedIndex, false) + } + } + + private fun onTagsChanged(value: FilterProperty) { + val b = viewBinding ?: return + b.layoutGenres.isGone = value.isEmptyAndSuccess() + b.layoutGenres.setError(value.error?.getDisplayMessage(resources)) + if (value.isEmpty()) { + return + } + val chips = value.availableItems.map { tag -> ChipsView.ChipModel( title = tag.title, - isChecked = true, + isChecked = tag in value.selectedItems, data = tag, ) } - value.availableItems.mapNotNullTo(chips) { tag -> - if (tag !in value.selectedItems) { - ChipsView.ChipModel( - title = tag.title, - isChecked = false, - data = tag, - ) - } else { - null - } - } - chips.add( - ChipsView.ChipModel( - title = getString(R.string.more), - icon = materialR.drawable.abc_ic_menu_overflow_material, - ), - ) b.chipsGenres.setChips(chips) } private fun onTagsExcludedChanged(value: FilterProperty) { val b = viewBinding ?: return - b.textViewGenresExcludeTitle.isGone = value.isEmpty() - b.chipsGenresExclude.isGone = value.isEmpty() + b.layoutGenresExclude.isGone = value.isEmpty() if (value.isEmpty()) { return } - val chips = ArrayList(value.selectedItems.size + value.availableItems.size + 1) - value.selectedItems.mapTo(chips) { tag -> + val chips = value.availableItems.map { tag -> ChipsView.ChipModel( - tint = 0, title = tag.title, - icon = 0, - isChecked = true, + isChecked = tag in value.selectedItems, data = tag, ) } - value.availableItems.mapNotNullTo(chips) { tag -> - if (tag !in value.selectedItems) { - ChipsView.ChipModel( - title = tag.title, - isChecked = false, - data = tag, - ) - } else { - null - } - } - chips.add( - ChipsView.ChipModel( - title = getString(R.string.more), - icon = materialR.drawable.abc_ic_menu_overflow_material, - ), - ) b.chipsGenresExclude.setChips(chips) } private fun onStateChanged(value: FilterProperty) { val b = viewBinding ?: return - b.textViewStateTitle.isGone = value.isEmpty() - b.chipsState.isGone = value.isEmpty() + b.layoutState.isGone = value.isEmpty() if (value.isEmpty()) { return } @@ -303,8 +255,7 @@ class FilterSheetFragment : BaseAdaptiveSheet(), private fun onContentTypesChanged(value: FilterProperty) { val b = viewBinding ?: return - b.textViewTypesTitle.isGone = value.isEmpty() - b.chipsTypes.isGone = value.isEmpty() + b.layoutTypes.isGone = value.isEmpty() if (value.isEmpty()) { return } @@ -320,8 +271,7 @@ class FilterSheetFragment : BaseAdaptiveSheet(), private fun onContentRatingChanged(value: FilterProperty) { val b = viewBinding ?: return - b.textViewContentRatingTitle.isGone = value.isEmpty() - b.chipsContentRating.isGone = value.isEmpty() + b.layoutContentRating.isGone = value.isEmpty() if (value.isEmpty()) { return } @@ -337,8 +287,7 @@ class FilterSheetFragment : BaseAdaptiveSheet(), private fun onDemographicsChanged(value: FilterProperty) { val b = viewBinding ?: return - b.textViewDemographicsTitle.isGone = value.isEmpty() - b.chipsDemographics.isGone = value.isEmpty() + b.layoutDemographics.isGone = value.isEmpty() if (value.isEmpty()) { return } @@ -354,17 +303,18 @@ class FilterSheetFragment : BaseAdaptiveSheet(), private fun onYearChanged(value: FilterProperty) { val b = viewBinding ?: return - b.headerYear.isGone = value.isEmpty() - b.sliderYear.isGone = value.isEmpty() + b.layoutYear.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.layoutYear.setValueText( + if (currentValue == YEAR_UNKNOWN) { + getString(R.string.any) + } else { + currentValue.toString() + }, + ) b.sliderYear.valueFrom = value.availableItems.first().toFloat() b.sliderYear.valueTo = value.availableItems.last().toFloat() b.sliderYear.setValueRounded(currentValue.toFloat()) @@ -372,8 +322,7 @@ class FilterSheetFragment : BaseAdaptiveSheet(), private fun onYearRangeChanged(value: FilterProperty) { val b = viewBinding ?: return - b.headerYearsRange.isGone = value.isEmpty() - b.sliderYearsRange.isGone = value.isEmpty() + b.layoutYearsRange.isGone = value.isEmpty() if (value.isEmpty()) { return } @@ -381,22 +330,18 @@ class FilterSheetFragment : BaseAdaptiveSheet(), 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.layoutYearsRange.setValueText( + getString( + R.string.memory_usage_pattern, + currentValueFrom.toInt().toString(), + currentValueTo.toInt().toString(), + ), ) b.sliderYearsRange.setValuesRounded(currentValueFrom, currentValueTo) } private fun requireFilter() = (requireActivity() as FilterCoordinator.Owner).filterCoordinator - private fun getSortDirection(@IdRes buttonId: Int): SortDirection? = when (buttonId) { - R.id.button_order_asc -> SortDirection.ASC - R.id.button_order_desc -> SortDirection.DESC - else -> null - } - companion object { private const val TAG = "FilterSheet" diff --git a/app/src/main/res/layout/sheet_filter.xml b/app/src/main/res/layout/sheet_filter.xml index 2ad6ab344..eea7caa4d 100644 --- a/app/src/main/res/layout/sheet_filter.xml +++ b/app/src/main/res/layout/sheet_filter.xml @@ -17,360 +17,239 @@ android:id="@+id/scrollView" android:layout_width="match_parent" android:layout_height="match_parent" - android:scrollIndicators="top"> + android:scrollIndicators="top" + tools:ignore="UnusedAttribute"> - + app:title="@string/sort_order"> - - - - - - - - - + android:layout_marginHorizontal="@dimen/margin_small" + android:layout_marginTop="@dimen/margin_small"> - + - + - + + + android:layout_marginTop="@dimen/margin_small" + app:title="@string/language"> - - - + android:layout_height="wrap_content" + android:layout_marginHorizontal="@dimen/margin_small" + android:layout_marginTop="@dimen/margin_small"> - + + - + + + android:layout_marginTop="@dimen/margin_small" + app:title="@string/original_language"> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + android:layout_marginHorizontal="@dimen/margin_small" + android:layout_marginTop="@dimen/margin_small"> - + - + - + + + android:layout_marginTop="@dimen/margin_small" + app:showMoreButton="true" + app:title="@string/genres"> + - + + + android:layout_marginTop="@dimen/margin_small" + app:showMoreButton="true" + app:title="@string/genres_exclude"> - + android:layout_marginHorizontal="@dimen/margin_small" + android:layout_marginTop="@dimen/margin_small" + app:chipStyle="@style/Widget.Kotatsu.Chip.Filter" /> - + - - - + android:layout_marginTop="@dimen/margin_small" + app:title="@string/type"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/view_filter_field.xml b/app/src/main/res/layout/view_filter_field.xml new file mode 100644 index 000000000..5478bd1ed --- /dev/null +++ b/app/src/main/res/layout/view_filter_field.xml @@ -0,0 +1,44 @@ + + + + + + + + + + diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml index 690f1081d..113f67fc6 100644 --- a/app/src/main/res/values/attrs.xml +++ b/app/src/main/res/values/attrs.xml @@ -172,4 +172,9 @@ + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index b219423ef..9fef1ac61 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -724,4 +724,6 @@ Shoujo Seinen Josei + Years + Any