Change filters dialog with a drawer

This commit is contained in:
len
2017-01-02 12:09:23 +01:00
parent d3e9200a7f
commit c25af3d5ad
18 changed files with 481 additions and 320 deletions

View File

@@ -3,6 +3,7 @@ package eu.kanade.tachiyomi.ui.catalogue
import android.content.res.Configuration
import android.os.Bundle
import android.support.design.widget.Snackbar
import android.support.v4.widget.DrawerLayout
import android.support.v7.widget.*
import android.view.*
import android.view.animation.AnimationUtils
@@ -18,10 +19,12 @@ import eu.kanade.tachiyomi.ui.base.adapter.FlexibleViewHolder
import eu.kanade.tachiyomi.ui.base.fragment.BaseRxFragment
import eu.kanade.tachiyomi.ui.main.MainActivity
import eu.kanade.tachiyomi.ui.manga.MangaActivity
import eu.kanade.tachiyomi.util.inflate
import eu.kanade.tachiyomi.util.snack
import eu.kanade.tachiyomi.util.toast
import eu.kanade.tachiyomi.widget.EndlessScrollListener
import eu.kanade.tachiyomi.widget.IgnoreFirstSpinnerListener
import kotlinx.android.synthetic.main.activity_main.*
import kotlinx.android.synthetic.main.fragment_catalogue.*
import kotlinx.android.synthetic.main.toolbar.*
import nucleus.factory.RequiresPresenter
@@ -100,6 +103,30 @@ open class CatalogueFragment : BaseRxFragment<CataloguePresenter>(), FlexibleVie
private val toolbar: Toolbar
get() = (activity as MainActivity).toolbar
/**
* Navigation view containing filter items.
*/
private var navView: CatalogueNavigationView? = null
/**
* Drawer listener to allow swipe only for closing the drawer.
*/
private val drawerListener by lazy {
object : DrawerLayout.SimpleDrawerListener() {
override fun onDrawerClosed(drawerView: View) {
if (drawerView == navView) {
activity.drawer.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED, navView)
}
}
override fun onDrawerOpened(drawerView: View) {
if (drawerView == navView) {
activity.drawer.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED, navView)
}
}
}
}
companion object {
/**
* Creates a new instance of this fragment.
@@ -176,6 +203,7 @@ open class CatalogueFragment : BaseRxFragment<CataloguePresenter>(), FlexibleVie
glm.scrollToPositionWithOffset(0, 0)
llm.scrollToPositionWithOffset(0, 0)
presenter.setActiveSource(source)
navView?.setFilters(presenter.sourceFilters)
activity.invalidateOptionsMenu()
}
}
@@ -191,6 +219,32 @@ open class CatalogueFragment : BaseRxFragment<CataloguePresenter>(), FlexibleVie
setToolbarTitle("")
toolbar.addView(spinner)
// Inflate and prepare drawer
val navView = activity.drawer.inflate(R.layout.catalogue_drawer) as CatalogueNavigationView
this.navView = navView
activity.drawer.addView(navView)
activity.drawer.addDrawerListener(drawerListener)
navView.setFilters(presenter.sourceFilters)
navView.post {
if (isAdded && !activity.drawer.isDrawerOpen(navView))
activity.drawer.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED, navView)
}
navView.onSearchClicked = {
val allDefault = (0..navView.adapter.items.lastIndex)
.none { navView.adapter.items[it].state != presenter.source.filters[it].state }
presenter.setSourceFilter(if (allDefault) emptyList() else navView.adapter.items)
}
navView.onResetClicked = {
presenter.appliedFilters = emptyList()
val newFilters = presenter.source.getFilterList()
presenter.sourceFilters = newFilters
navView.setFilters(newFilters)
}
showProgressBar()
}
@@ -244,7 +298,7 @@ open class CatalogueFragment : BaseRxFragment<CataloguePresenter>(), FlexibleVie
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.action_display_mode -> swapDisplayMode()
R.id.action_set_filter -> showFiltersDialog()
R.id.action_set_filter -> navView?.let { activity.drawer.openDrawer(Gravity.END) }
else -> return super.onOptionsItemSelected(item)
}
return true
@@ -263,6 +317,10 @@ open class CatalogueFragment : BaseRxFragment<CataloguePresenter>(), FlexibleVie
}
override fun onDestroyView() {
navView?.let {
activity.drawer.removeDrawerListener(drawerListener)
activity.drawer.removeView(it)
}
numColumnsSubscription?.unsubscribe()
searchItem?.let {
if (it.isActionViewExpanded) it.collapseActionView()
@@ -448,29 +506,4 @@ open class CatalogueFragment : BaseRxFragment<CataloguePresenter>(), FlexibleVie
}.show()
}
/**
* Show the filter dialog for the source.
*/
private fun showFiltersDialog() {
val adapter = FilterAdapter(if (presenter.filters.isEmpty()) presenter.source.getFilterList() // make a copy
else presenter.filters)
MaterialDialog.Builder(context)
.title(R.string.action_set_filter)
.adapter(adapter, null)
.onPositive() { dialog, which ->
showProgressBar()
var allDefault = true
for (i in 0..adapter.filters.lastIndex) {
if (adapter.filters[i].state != presenter.source.filters[i].state) {
allDefault = false
break
}
}
presenter.setSourceFilter(if (allDefault) emptyList() else adapter.filters)
}
.positiveText(android.R.string.ok)
.negativeText(android.R.string.cancel)
.show()
}
}

View File

@@ -0,0 +1,151 @@
package eu.kanade.tachiyomi.ui.catalogue
import android.content.Context
import android.support.graphics.drawable.VectorDrawableCompat
import android.support.v7.widget.RecyclerView
import android.util.AttributeSet
import android.view.View
import android.view.ViewGroup
import android.widget.ArrayAdapter
import android.widget.TextView
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.source.online.OnlineSource.Filter
import eu.kanade.tachiyomi.util.dpToPx
import eu.kanade.tachiyomi.util.getResourceColor
import eu.kanade.tachiyomi.util.inflate
import eu.kanade.tachiyomi.widget.IgnoreFirstSpinnerListener
import eu.kanade.tachiyomi.widget.SimpleNavigationView
import eu.kanade.tachiyomi.widget.SimpleTextWatcher
import kotlinx.android.synthetic.main.catalogue_drawer_content.view.*
class CatalogueNavigationView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null)
: SimpleNavigationView(context, attrs) {
val adapter = Adapter()
var onSearchClicked = {}
var onResetClicked = {}
init {
recycler.adapter = adapter
val view = inflate(R.layout.catalogue_drawer_content)
(view as ViewGroup).addView(recycler)
addView(view)
search_btn.setOnClickListener { onSearchClicked() }
reset_btn.setOnClickListener { onResetClicked() }
}
fun setFilters(items: List<Filter<*>>) {
adapter.items = items
adapter.notifyDataSetChanged()
}
inner class Adapter : RecyclerView.Adapter<Holder>() {
var items: List<Filter<*>> = emptyList()
override fun getItemCount(): Int {
return items.size
}
override fun getItemViewType(position: Int): Int {
return when (items[position]) {
is Filter.Header -> VIEW_TYPE_HEADER
is Filter.CheckBox -> VIEW_TYPE_CHECKBOX
is Filter.TriState -> VIEW_TYPE_MULTISTATE
is Filter.List<*> -> VIEW_TYPE_LIST
is Filter.Text -> VIEW_TYPE_TEXT
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): Holder {
return when (viewType) {
VIEW_TYPE_HEADER -> HeaderHolder(parent)
VIEW_TYPE_CHECKBOX -> CheckboxHolder(parent, null)
VIEW_TYPE_MULTISTATE -> MultiStateHolder(parent, null).apply {
// Adjust view with checkbox
text.setPadding(4.dpToPx, 0, 0, 0)
text.compoundDrawablePadding = 20.dpToPx
}
VIEW_TYPE_LIST -> SpinnerHolder(parent)
VIEW_TYPE_TEXT -> EditTextHolder(parent)
else -> throw Exception("Unknown view type")
}
}
override fun onBindViewHolder(holder: Holder, position: Int) {
val filter = items[position]
when (filter) {
is Filter.Header -> {
val view = holder.itemView as TextView
view.visibility = if (filter.name.isEmpty()) View.GONE else View.VISIBLE
view.text = filter.name
}
is Filter.CheckBox -> {
val view = (holder as CheckboxHolder).check
view.text = filter.name
view.isChecked = filter.state
holder.itemView.setOnClickListener {
view.toggle()
filter.state = view.isChecked
}
}
is Filter.TriState -> {
val view = (holder as MultiStateHolder).text
view.text = filter.name
fun getIcon() = VectorDrawableCompat.create(view.resources, when (filter.state) {
Filter.TriState.STATE_IGNORE -> R.drawable.ic_check_box_outline_blank_24dp
Filter.TriState.STATE_INCLUDE -> R.drawable.ic_check_box_24dp
Filter.TriState.STATE_EXCLUDE -> R.drawable.ic_check_box_x_24dp
else -> throw Exception("Unknown state")
}, null)?.apply {
val color = if (filter.state == Filter.TriState.STATE_INCLUDE)
R.attr.colorAccent
else
android.R.attr.textColorSecondary
setTint(view.context.theme.getResourceColor(color))
}
view.setCompoundDrawablesWithIntrinsicBounds(getIcon(), null, null, null)
holder.itemView.setOnClickListener {
filter.state = (filter.state + 1) % 3
view.setCompoundDrawablesWithIntrinsicBounds(getIcon(), null, null, null)
}
}
is Filter.List<*> -> {
holder as SpinnerHolder
holder.text.text = 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.spinner_item)
}
spinner.onItemSelectedListener = IgnoreFirstSpinnerListener { position ->
filter.state = position
}
spinner.setSelection(filter.state)
}
is Filter.Text -> {
holder as EditTextHolder
holder.wrapper.visibility = if (filter.name.isEmpty()) View.GONE else View.VISIBLE
holder.wrapper.hint = filter.name
holder.edit.setText(filter.state)
holder.edit.addTextChangedListener(object : SimpleTextWatcher() {
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
filter.state = s.toString()
}
})
}
}
}
}
}

View File

@@ -65,9 +65,14 @@ open class CataloguePresenter : BasePresenter<CatalogueFragment>() {
private set
/**
* Filters states.
* Modifiable list of filters.
*/
var filters: List<Filter<*>> = emptyList()
var sourceFilters: List<Filter<*>> = emptyList()
/**
* List of filters used by the [Pager]. If empty alongside [query], the popular query is used.
*/
var appliedFilters: List<Filter<*>> = emptyList()
/**
* Pager containing a list of manga results.
@@ -105,6 +110,7 @@ open class CataloguePresenter : BasePresenter<CatalogueFragment>() {
try {
source = getLastUsedSource()
sourceFilters = source.getFilterList()
} catch (error: NoSuchElementException) {
return
}
@@ -130,9 +136,9 @@ open class CataloguePresenter : BasePresenter<CatalogueFragment>() {
* @param query the query.
* @param filters the current state of the filters (for search mode).
*/
fun restartPager(query: String = this.query, filters: List<Filter<*>> = this.filters) {
fun restartPager(query: String = this.query, filters: List<Filter<*>> = this.appliedFilters) {
this.query = query
this.filters = filters
this.appliedFilters = filters
if (!isListMode) {
subscribeToMangaInitializer()
@@ -182,6 +188,7 @@ open class CataloguePresenter : BasePresenter<CatalogueFragment>() {
fun setActiveSource(source: OnlineSource) {
prefs.lastUsedCatalogueSource().set(source.id)
this.source = source
sourceFilters = source.getFilterList()
restartPager(query = "", filters = emptyList())
}

View File

@@ -1,153 +0,0 @@
package eu.kanade.tachiyomi.ui.catalogue
import android.content.Context
import android.graphics.Typeface
import android.support.graphics.drawable.VectorDrawableCompat
import android.support.v7.widget.RecyclerView
import android.view.View
import android.view.ViewGroup
import android.widget.*
import android.widget.AdapterView.OnItemSelectedListener
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.source.online.OnlineSource.Filter
import android.text.TextWatcher
import android.text.Editable
import android.view.inputmethod.EditorInfo
import android.widget.TextView
import eu.kanade.tachiyomi.util.inflate
class FilterAdapter(val filters: List<Filter<*>>) : RecyclerView.Adapter<FilterAdapter.ViewHolder>() {
private companion object {
const val HEADER = 0
const val CHECKBOX = 1
const val TRISTATE = 2
const val LIST = 3
const val TEXT = 4
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FilterAdapter.ViewHolder {
return when (viewType) {
HEADER -> ViewHolder(SepText(parent))
LIST -> ViewHolder(TextSpinner(parent.context))
TEXT -> ViewHolder(TextEditText(parent.context))
else -> ViewHolder(CheckBox(parent.context))
}
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val filter = filters[position]
when (filter) {
is Filter.Header -> {
if (filter.name.isEmpty()) (holder.view as SepText).textView.visibility = View.GONE
else (holder.view as SepText).textView.text = filter.name
}
is Filter.CheckBox -> {
var checkBox = holder.view as CheckBox
checkBox.text = filter.name
checkBox.isChecked = filter.state
checkBox.setButtonDrawable(VectorDrawableCompat.create(checkBox.getResources(), R.drawable.ic_check_box_set, null))
checkBox.setOnCheckedChangeListener { buttonView, isChecked ->
filter.state = isChecked
}
}
is Filter.TriState -> {
var triCheckBox = holder.view as CheckBox
triCheckBox.text = filter.name
val icons = arrayOf(VectorDrawableCompat.create(triCheckBox.getResources(), R.drawable.ic_check_box_outline_blank_24dp, null),
VectorDrawableCompat.create(triCheckBox.getResources(), R.drawable.ic_check_box_24dp, null),
VectorDrawableCompat.create(triCheckBox.getResources(), R.drawable.ic_check_box_x_24dp, null))
triCheckBox.setButtonDrawable(icons[filter.state])
triCheckBox.invalidate()
triCheckBox.setOnCheckedChangeListener { buttonView, isChecked ->
filter.state = (filter.state + 1) % 3
triCheckBox.setButtonDrawable(icons[filter.state])
triCheckBox.invalidate()
}
}
is Filter.List<*> -> {
var txtSpin = holder.view as TextSpinner
if (filter.name.isEmpty()) txtSpin.textView.visibility = View.GONE
else txtSpin.textView.text = filter.name + ":"
txtSpin.spinner.adapter = ArrayAdapter<Any>(holder.view.context,
android.R.layout.simple_spinner_item, filter.values)
txtSpin.spinner.setSelection(filter.state)
txtSpin.spinner.onItemSelectedListener = object : OnItemSelectedListener {
override fun onItemSelected(parentView: AdapterView<*>, selectedItemView: View, pos: Int, id: Long) {
filter.state = pos
}
override fun onNothingSelected(parentView: AdapterView<*>) {
}
}
}
is Filter.Text -> {
var txtEdTx = holder.view as TextEditText
if (filter.name.isEmpty()) txtEdTx.textView.visibility = View.GONE
else txtEdTx.textView.text = filter.name + ":"
txtEdTx.editText.setText(filter.state)
txtEdTx.editText.addTextChangedListener(object : TextWatcher {
override fun afterTextChanged(s: Editable) {
}
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {
}
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
filter.state = s.toString()
}
})
}
}
}
override fun getItemCount(): Int {
return filters.size
}
override fun getItemViewType(position: Int): Int {
return when (filters[position]) {
is Filter.Header -> HEADER
is Filter.CheckBox -> CHECKBOX
is Filter.TriState -> TRISTATE
is Filter.List<*> -> LIST
is Filter.Text -> TEXT
}
}
class ViewHolder(val view: View) : RecyclerView.ViewHolder(view)
private class SepText(parent: ViewGroup) : LinearLayout(parent.context) {
val separator: View = parent.inflate(R.layout.design_navigation_item_separator)
val textView: TextView = TextView(context)
init {
orientation = LinearLayout.VERTICAL
textView.setTypeface(null, Typeface.BOLD);
addView(separator)
addView(textView)
}
}
private class TextSpinner(context: Context?) : LinearLayout(context) {
val textView: TextView = TextView(context)
val spinner: Spinner = Spinner(context)
init {
addView(textView)
addView(spinner)
}
}
private class TextEditText(context: Context?) : LinearLayout(context) {
val textView: TextView = TextView(context)
val editText: EditText = EditText(context)
init {
addView(textView)
editText.setSingleLine()
editText.setImeOptions(EditorInfo.IME_ACTION_DONE);
addView(editText)
}
}
}

View File

@@ -39,6 +39,7 @@ class LibraryNavigationView @JvmOverloads constructor(context: Context, attrs: A
init {
recycler.adapter = adapter
addView(recycler)
groups.forEach { it.initModels() }
}

View File

@@ -11,7 +11,6 @@ import eu.kanade.tachiyomi.ui.reader.ReaderChapter
import eu.kanade.tachiyomi.ui.reader.viewer.base.BaseReader
import eu.kanade.tachiyomi.ui.reader.viewer.pager.horizontal.LeftToRightReader
import eu.kanade.tachiyomi.ui.reader.viewer.pager.horizontal.RightToLeftReader
import eu.kanade.tachiyomi.util.toast
import rx.subscriptions.CompositeSubscription
/**