Updates to the Source Filter Sheet

With some renaming, but mostly taking changes from upstream + some from the new set categories sheet (such as reset/filter at the bottom

Co-Authored-By: arkon <4098258+arkon@users.noreply.github.com>
This commit is contained in:
Jays2Kings 2021-04-15 00:52:05 -04:00
parent 0afe075f46
commit 41f3d1ea2a
12 changed files with 257 additions and 230 deletions

View File

@ -289,7 +289,7 @@ open class BrowseSourceController(bundle: Bundle) :
}
private fun showFilters() {
val sheet = SourceSearchSheet(activity!!)
val sheet = SourceFilterSheet(activity!!)
sheet.setFilters(presenter.filterItems)
presenter.filtersChanged = false
val oldFilters = mutableListOf<Any?>()

View File

@ -0,0 +1,150 @@
package eu.kanade.tachiyomi.ui.source.browse
import android.app.Activity
import android.os.Bundle
import android.view.View
import android.view.ViewGroup
import android.view.ViewTreeObserver.OnGlobalLayoutListener
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialog
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.items.IFlexible
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.databinding.SourceFilterSheetBinding
import eu.kanade.tachiyomi.util.system.dpToPx
import eu.kanade.tachiyomi.util.view.collapse
import eu.kanade.tachiyomi.util.view.setEdgeToEdge
import eu.kanade.tachiyomi.util.view.updateLayoutParams
import eu.kanade.tachiyomi.util.view.updatePaddingRelative
class SourceFilterSheet(val activity: Activity) :
BottomSheetDialog(activity, R.style.BottomSheetDialogTheme) {
private val sheetBehavior: BottomSheetBehavior<*>
private var filterChanged = true
val adapter: FlexibleAdapter<IFlexible<*>> = FlexibleAdapter<IFlexible<*>>(null)
.setDisplayHeadersAtStartUp(true)
var onSearchClicked = {}
var onResetClicked = {}
private val binding = SourceFilterSheetBinding.inflate(activity.layoutInflater)
init {
setContentView(binding.root)
binding.searchBtn.setOnClickListener { dismiss() }
binding.resetBtn.setOnClickListener { onResetClicked() }
sheetBehavior = BottomSheetBehavior.from(binding.root.parent as ViewGroup)
sheetBehavior.peekHeight = 450.dpToPx
sheetBehavior.collapse()
setEdgeToEdge(activity, binding.root)
binding.titleLayout.viewTreeObserver.addOnGlobalLayoutListener(object : OnGlobalLayoutListener {
override fun onGlobalLayout() {
binding.cardView.updateLayoutParams<ConstraintLayout.LayoutParams> {
val fullHeight = activity.window.decorView.height
val insets = activity.window.decorView.rootWindowInsets
matchConstraintMaxHeight =
fullHeight - (insets?.systemWindowInsetTop ?: 0) -
binding.titleLayout.height - 75.dpToPx
}
if (binding.titleLayout.height > 0) {
binding.titleLayout.viewTreeObserver.removeOnGlobalLayoutListener(this)
}
}
})
(binding.root.parent.parent as? View)?.viewTreeObserver?.addOnGlobalLayoutListener(object : OnGlobalLayoutListener {
override fun onGlobalLayout() {
updateBottomButtons()
if (sheetBehavior.state != BottomSheetBehavior.STATE_COLLAPSED) {
(binding.root.parent.parent as? View)?.viewTreeObserver?.removeOnGlobalLayoutListener(this)
}
}
})
setOnShowListener {
updateBottomButtons()
}
binding.filtersRecycler.layoutManager = androidx.recyclerview.widget.LinearLayoutManager(context)
binding.filtersRecycler.clipToPadding = false
binding.filtersRecycler.adapter = adapter
binding.filtersRecycler.setHasFixedSize(true)
sheetBehavior.addBottomSheetCallback(
object : BottomSheetBehavior.BottomSheetCallback() {
override fun onSlide(bottomSheet: View, progress: Float) {
updateBottomButtons()
}
override fun onStateChanged(p0: View, state: Int) {
updateBottomButtons()
}
}
)
binding.filtersRecycler.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
super.onScrollStateChanged(recyclerView, newState)
if (newState == RecyclerView.SCROLL_STATE_IDLE) {
sheetBehavior.isDraggable = true
}
}
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
if (recyclerView.canScrollVertically(-1)) {
sheetBehavior.isDraggable = false
}
}
})
}
override fun onStart() {
super.onStart()
sheetBehavior.collapse()
updateBottomButtons()
binding.root.post {
updateBottomButtons()
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val attrsArray = intArrayOf(android.R.attr.actionBarSize)
val array = context.obtainStyledAttributes(attrsArray)
val headerHeight = array.getDimensionPixelSize(0, 0)
binding.titleLayout.updatePaddingRelative(
bottom = activity.window.decorView.rootWindowInsets.systemWindowInsetBottom
)
binding.titleLayout.updateLayoutParams<ConstraintLayout.LayoutParams> {
height = headerHeight + binding.titleLayout.paddingBottom
}
array.recycle()
}
private fun updateBottomButtons() {
val bottomSheet = binding.root.parent as View
val bottomSheetVisibleHeight = -bottomSheet.top + (activity.window.decorView.height - bottomSheet.height)
binding.titleLayout.translationY = bottomSheetVisibleHeight.toFloat()
}
override fun dismiss() {
super.dismiss()
if (filterChanged) {
onSearchClicked()
}
}
fun setFilters(items: List<IFlexible<*>>) {
adapter.updateDataSet(items)
}
}

View File

@ -1,130 +0,0 @@
package eu.kanade.tachiyomi.ui.source.browse
import android.animation.ObjectAnimator
import android.animation.ValueAnimator
import android.app.Activity
import android.view.ActionMode
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialog
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.items.IFlexible
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.databinding.SourceFilterSheetBinding
import eu.kanade.tachiyomi.util.system.dpToPx
import eu.kanade.tachiyomi.util.view.RecyclerWindowInsetsListener
import eu.kanade.tachiyomi.util.view.expand
import eu.kanade.tachiyomi.util.view.setEdgeToEdge
import uy.kohesive.injekt.injectLazy
class SourceSearchSheet(activity: Activity) :
BottomSheetDialog(activity, R.style.BottomSheetDialogTheme) {
/**
* Preferences helper.
*/
private val preferences by injectLazy<PreferencesHelper>()
private var sheetBehavior: BottomSheetBehavior<*>
private var elevationAnimator: ValueAnimator? = null
var filterChanged = true
var isNotElevated = false
val adapter: FlexibleAdapter<IFlexible<*>> = FlexibleAdapter<IFlexible<*>>(null)
.setDisplayHeadersAtStartUp(true)
var onSearchClicked = {}
var onResetClicked = {}
private val binding = SourceFilterSheetBinding.inflate(activity.layoutInflater)
init {
setContentView(binding.root)
binding.toolbarTitle.text = context.getString(R.string.search_filters)
binding.searchBtn.setOnClickListener { dismiss() }
binding.resetBtn.setOnClickListener { onResetClicked() }
sheetBehavior = BottomSheetBehavior.from(binding.root.parent as ViewGroup)
sheetBehavior.skipCollapsed = true
sheetBehavior.expand()
setEdgeToEdge(
activity,
binding.root,
50.dpToPx
)
binding.recycler.layoutManager = androidx.recyclerview.widget.LinearLayoutManager(context)
binding.recycler.clipToPadding = false
binding.recycler.adapter = adapter
binding.recycler.setHasFixedSize(true)
binding.recycler.setOnApplyWindowInsetsListener(RecyclerWindowInsetsListener)
// the spinner in the recycler can break the sheet's layout on change
// this is to reset it back
binding.sourceFilterSheet.post {
(binding.sourceFilterSheet.parent as? View)?.fitsSystemWindows = false
binding.sourceFilterSheet.viewTreeObserver.addOnDrawListener {
(binding.sourceFilterSheet.parent as? View)?.fitsSystemWindows = false
}
}
sheetBehavior.addBottomSheetCallback(
object : BottomSheetBehavior.BottomSheetCallback() {
override fun onSlide(bottomSheet: View, progress: Float) {}
override fun onStateChanged(p0: View, state: Int) {
if (state == BottomSheetBehavior.STATE_EXPANDED) {
sheetBehavior.skipCollapsed = true
}
}
}
)
binding.recycler.addOnScrollListener(
object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
val atTop = !binding.recycler.canScrollVertically(-1)
if (atTop != isNotElevated) {
elevationAnimator?.cancel()
isNotElevated = atTop
elevationAnimator?.cancel()
elevationAnimator = ObjectAnimator.ofFloat(
binding.titleLayout,
"elevation",
binding.titleLayout.elevation,
if (atTop) 0f else 10f.dpToPx
)
elevationAnimator?.duration = 100
elevationAnimator?.start()
}
}
}
)
}
override fun onWindowStartingActionMode(
callback: ActionMode.Callback?,
type: Int
): ActionMode? {
(binding.sourceFilterSheet.parent as View).fitsSystemWindows = false
return super.onWindowStartingActionMode(callback, type)
}
override fun dismiss() {
super.dismiss()
if (filterChanged) {
onSearchClicked()
}
}
fun setFilters(items: List<IFlexible<*>>) {
adapter.updateDataSet(items)
}
}

View File

@ -36,9 +36,9 @@ class GroupItem(val filter: Filter.Group<*>) : AbstractExpandableHeaderItem<Grou
holder.icon.setVectorCompat(
if (isExpanded) {
R.drawable.ic_expand_more_24dp
R.drawable.ic_expand_less_24dp
} else {
R.drawable.ic_chevron_right_24dp
R.drawable.ic_expand_more_24dp
}
)

View File

@ -1,9 +1,6 @@
package eu.kanade.tachiyomi.ui.source.filter
import android.view.View
import android.widget.ArrayAdapter
import android.widget.Spinner
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
@ -11,7 +8,7 @@ import eu.davidea.flexibleadapter.items.IFlexible
import eu.davidea.viewholders.FlexibleViewHolder
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.source.model.Filter
import eu.kanade.tachiyomi.widget.IgnoreFirstSpinnerListener
import eu.kanade.tachiyomi.widget.MaterialSpinnerView
open class SelectItem(val filter: Filter.Select<*>) : AbstractFlexibleItem<SelectItem.Holder>() {
@ -24,21 +21,13 @@ open class SelectItem(val filter: Filter.Select<*>) : AbstractFlexibleItem<Selec
}
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>, holder: Holder, position: Int, payloads: MutableList<Any?>?) {
holder.text.text = filter.name + ": "
holder.spinnerView.title = filter.name + ": "
val spinner = holder.spinner
spinner.prompt = filter.name
spinner.adapter = ArrayAdapter<Any>(
holder.itemView.context,
android.R.layout.simple_spinner_item,
filter.values
).apply {
setDropDownViewResource(R.layout.common_spinner_item)
}
spinner.onItemSelectedListener = IgnoreFirstSpinnerListener { pos ->
holder.spinnerView.setEntries(filter.values.map { it.toString() })
holder.spinnerView.setSelection(filter.state)
holder.spinnerView.onItemSelectedListener = { pos ->
filter.state = pos
}
spinner.setSelection(filter.state)
}
override fun equals(other: Any?): Boolean {
@ -52,8 +41,6 @@ open class SelectItem(val filter: Filter.Select<*>) : AbstractFlexibleItem<Selec
}
class Holder(view: View, adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>) : FlexibleViewHolder(view, adapter) {
val text: TextView = itemView.findViewById(R.id.nav_view_item_text)
val spinner: Spinner = itemView.findViewById(R.id.nav_view_item)
val spinnerView: MaterialSpinnerView = itemView.findViewById(R.id.nav_view_item)
}
}

View File

@ -33,9 +33,9 @@ class SortGroup(val filter: Filter.Sort) : AbstractExpandableHeaderItem<SortGrou
holder.icon.setVectorCompat(
if (isExpanded) {
R.drawable.ic_expand_more_24dp
R.drawable.ic_expand_less_24dp
} else {
R.drawable.ic_chevron_right_24dp
R.drawable.ic_expand_more_24dp
}
)

View File

@ -56,25 +56,36 @@ class MaterialSpinnerView @JvmOverloads constructor(context: Context, attrs: Att
init {
addView(binding.root)
val a = context.obtainStyledAttributes(attrs, R.styleable.ReaderSpinnerView, 0, 0)
val a = context.obtainStyledAttributes(attrs, R.styleable.MaterialSpinnerView, 0, 0)
val str = a.getString(R.styleable.ReaderSpinnerView_title) ?: ""
val str = a.getString(R.styleable.MaterialSpinnerView_title) ?: ""
title = str
val entries = (a.getTextArray(R.styleable.ReaderSpinnerView_android_entries) ?: emptyArray()).map { it.toString() }
val entries = (a.getTextArray(R.styleable.MaterialSpinnerView_android_entries) ?: emptyArray()).map { it.toString() }
this.entries = entries
binding.detailView.text = entries.firstOrNull().orEmpty()
if (entries.isNotEmpty()) {
popup = makeSettingsPopup()
setOnTouchListener(popup?.dragToOpenListener)
setOnClickListener {
popup?.show()
}
}
a.recycle()
}
fun setEntries(entries: List<String>) {
this.entries = entries
popup = makeSettingsPopup()
setOnTouchListener(popup?.dragToOpenListener)
setOnClickListener {
popup?.show()
}
}
fun setSelection(selection: Int) {
popup?.menu?.get(selectedPosition)?.let {
it.icon = ContextCompat.getDrawable(context, R.drawable.ic_blank_24dp)

View File

@ -9,7 +9,6 @@ import android.widget.CheckBox
import android.widget.CheckedTextView
import android.widget.EditText
import android.widget.RadioButton
import android.widget.Spinner
import android.widget.TextView
import androidx.appcompat.widget.TintTypedArray
import androidx.core.view.ViewCompat
@ -154,8 +153,7 @@ open class SimpleNavigationView @JvmOverloads constructor(
class SpinnerHolder(parent: ViewGroup, listener: OnClickListener? = null) :
ClickableHolder(parent.inflate(TR.layout.navigation_view_spinner), listener) {
val text: TextView = itemView.findViewById(TR.id.nav_view_item_text)
val spinner: Spinner = itemView.findViewById(TR.id.nav_view_item)
val spinnerView: MaterialSpinnerView = itemView.findViewById(TR.id.nav_view_item)
}
class EditTextHolder(parent: ViewGroup) :

View File

@ -1,30 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="?attr/listPreferredItemHeightSmall"
android:background="?colorPrimaryVariant"
android:orientation="horizontal"
android:gravity="center_vertical"
android:background="?android:attr/colorBackground"
android:orientation="horizontal"
android:paddingStart="?attr/listPreferredItemPaddingStart"
android:paddingEnd="?attr/listPreferredItemPaddingEnd"
android:elevation="2dp">
android:paddingEnd="?attr/listPreferredItemPaddingEnd">
<TextView
android:id="@+id/title"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"
android:layout_weight="1"
android:ellipsize="end"
android:maxLines="1"
android:textAppearance="@style/TextAppearance.AppCompat.Body2"
android:textColor="?attr/actionBarTintColor"
tools:text="Header"/>
android:textAllCaps="true"
android:textSize="15sp"
android:textAppearance="@style/TextAppearance.MaterialComponents.Headline6"
android:textColor="?android:attr/textColorPrimary"
tools:text="Header" />
<ImageView
android:id="@+id/expand_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
android:layout_height="wrap_content"
tools:src="@drawable/ic_expand_more_24dp"
app:tint="?android:attr/textColorPrimary" />
</LinearLayout>

View File

@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="?attr/listPreferredItemHeightSmall"
@ -9,24 +8,12 @@
android:background="?attr/selectableItemBackground"
android:focusable="true">
<TextView
android:id="@+id/nav_view_item_text"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:ellipsize="end"
android:maxLines="1"
android:layout_gravity="center"
android:paddingEnd="8dp"
tools:text="Filter:" />
<Spinner
<eu.kanade.tachiyomi.widget.MaterialSpinnerView
android:id="@+id/nav_view_item"
android:layout_width="0dp"
android:layout_marginEnd="8dp"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:gravity="center_vertical|start"
android:maxLines="1"
android:textAppearance="@style/TextAppearance.AppCompat.Body2" />
tools:entries="@array/viewers_selector"
tools:title="Filter: "/>
</LinearLayout>
</FrameLayout>

View File

@ -1,19 +1,40 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/source_filter_sheet"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/bottom_sheet_rounded_background"
app:layout_constraintVertical_chainStyle="packed"
android:backgroundTint="?android:attr/colorBackground">
<androidx.cardview.widget.CardView
android:id="@+id/card_view"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintBottom_toTopOf="@id/title_layout"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:cardCornerRadius="@dimen/rounded_radius"
app:cardElevation="0dp"
app:layout_constraintTop_toTopOf="parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler"
android:id="@+id/filters_recycler"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="?attr/actionBarSize"
android:clipToPadding="false" />
android:layout_height="wrap_content"
android:paddingTop="12dp"
android:paddingBottom="12dp"
android:clipToPadding="false"
android:background="@drawable/bottom_sheet_rounded_background"
android:backgroundTint="?attr/colorSecondary"
app:layout_constraintBottom_toTopOf="@id/title_layout"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.cardview.widget.CardView>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/title_layout"
@ -23,49 +44,49 @@
android:background="@drawable/bottom_sheet_rounded_background"
android:backgroundTint="?attr/colorSecondary"
android:clickable="true"
android:elevation="0dp"
android:focusable="true"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/card_view"
app:layout_constraintBottom_toBottomOf="parent"
android:orientation="horizontal">
<com.google.android.material.button.MaterialButton
android:id="@+id/reset_btn"
style="@style/Theme.Widget.Button.TextButton"
android:layout_width="wrap_content"
android:textColor="?attr/tabBarIconColor"
android:layout_height="match_parent"
android:textSize="16sp"
android:letterSpacing="0.0"
android:layout_height="wrap_content"
android:text="@string/reset"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/search_btn"
app:layout_constraintHorizontal_chainStyle="spread_inside"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.textview.MaterialTextView
android:id="@+id/toolbar_title"
style="@style/TextAppearance.AppCompat.Widget.ActionBar.Title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:ellipsize="end"
android:maxLines="1"
android:textColor="?actionBarTintColor"
android:textSize="20sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="Title Text" />
<com.google.android.material.button.MaterialButton
android:id="@+id/search_btn"
style="@style/Theme.Widget.Button.TextButton"
android:textSize="16sp"
style="@style/Theme.Widget.Button.Primary"
android:layout_width="wrap_content"
android:textColor="?attr/tabBarIconColor"
android:layout_height="match_parent"
android:text="@string/search"
android:layout_height="wrap_content"
android:layout_marginStart="48dp"
android:layout_marginEnd="16dp"
android:text="@string/filter"
app:flow_verticalBias="1.0"
app:iconTint="?colorAccent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
app:layout_constraintHorizontal_chainStyle="spread"
app:layout_constraintStart_toEndOf="@id/reset_btn"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintWidth_min="150dp" />
<View
android:id="@+id/bottom_divider"
android:layout_width="match_parent"
android:layout_height="1dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:background="@color/divider"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</FrameLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -19,7 +19,7 @@
<attr name="tabBarIconInactive" format="reference|integer"/>
<attr name="tabHighlightBackground" format="color"/>
<declare-styleable name="ReaderSpinnerView">
<declare-styleable name="MaterialSpinnerView">
<attr name="title" format="reference|string"/>
<attr name="android:entries"/>
<attr name="summary" format="reference|string" />