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

@ -343,7 +343,6 @@ class Batoto(override val id: Int) : ParsedOnlineSource(), LoginSource {
ListField("Type", "type", arrayOf(ListValue("Any", ""), ListValue("Manga (Jp)", "jp"), ListValue("Manhwa (Kr)", "kr"), ListValue("Manhua (Cn)", "cn"), ListValue("Artbook", "ar"), ListValue("Other", "ot"))), ListField("Type", "type", arrayOf(ListValue("Any", ""), ListValue("Manga (Jp)", "jp"), ListValue("Manhwa (Kr)", "kr"), ListValue("Manhua (Cn)", "cn"), ListValue("Artbook", "ar"), ListValue("Other", "ot"))),
Status(), Status(),
Flag("Exclude mature", "mature", "m", ""), Flag("Exclude mature", "mature", "m", ""),
Filter.Header(""),
ListField("Order by", "order_cond", arrayOf(ListValue("Title", "title"), ListValue("Author", "author"), ListValue("Artist", "artist"), ListValue("Rating", "rating"), ListValue("Views", "views"), ListValue("Last Update", "update")), 4), ListField("Order by", "order_cond", arrayOf(ListValue("Title", "title"), ListValue("Author", "author"), ListValue("Artist", "artist"), ListValue("Rating", "rating"), ListValue("Views", "views"), ListValue("Last Update", "update")), 4),
Flag("Ascending order", "order", "asc", "desc"), Flag("Ascending order", "order", "asc", "desc"),
Filter.Header("Genres"), Filter.Header("Genres"),

View File

@ -155,7 +155,6 @@ class Mangafox(override val id: Int) : ParsedOnlineSource() {
TextField("Artist", "artist"), TextField("Artist", "artist"),
ListField("Type", "type", arrayOf(ListValue("Any", ""), ListValue("Japanese Manga", "1"), ListValue("Korean Manhwa", "2"), ListValue("Chinese Manhua", "3"))), ListField("Type", "type", arrayOf(ListValue("Any", ""), ListValue("Japanese Manga", "1"), ListValue("Korean Manhwa", "2"), ListValue("Chinese Manhua", "3"))),
Genre("Completed", "is_completed"), Genre("Completed", "is_completed"),
Filter.Header(""),
ListField("Order by", "sort", arrayOf(ListValue("Series name", "name"), ListValue("Rating", "rating"), ListValue("Views", "views"), ListValue("Total chapters", "total_chapters"), ListValue("Last chapter", "last_chapter_time")), 2), ListField("Order by", "sort", arrayOf(ListValue("Series name", "name"), ListValue("Rating", "rating"), ListValue("Views", "views"), ListValue("Total chapters", "total_chapters"), ListValue("Last chapter", "last_chapter_time")), 2),
Order(), Order(),
Filter.Header("Genres"), Filter.Header("Genres"),

View File

@ -162,7 +162,6 @@ class Mangahere(override val id: Int) : ParsedOnlineSource() {
TextField("Artist", "artist"), TextField("Artist", "artist"),
ListField("Type", "direction", arrayOf(ListValue("Any", ""), ListValue("Japanese Manga (read from right to left)", "rl"), ListValue("Korean Manhwa (read from left to right)", "lr"))), ListField("Type", "direction", arrayOf(ListValue("Any", ""), ListValue("Japanese Manga (read from right to left)", "rl"), ListValue("Korean Manhwa (read from left to right)", "lr"))),
Status(), Status(),
Filter.Header(""),
ListField("Order by", "sort", arrayOf(ListValue("Series name", "name"), ListValue("Rating", "rating"), ListValue("Views", "views"), ListValue("Total chapters", "total_chapters"), ListValue("Last chapter", "last_chapter_time")), 2), ListField("Order by", "sort", arrayOf(ListValue("Series name", "name"), ListValue("Rating", "rating"), ListValue("Views", "views"), ListValue("Total chapters", "total_chapters"), ListValue("Last chapter", "last_chapter_time")), 2),
Order(), Order(),
Filter.Header("Genres"), Filter.Header("Genres"),

View File

@ -3,6 +3,7 @@ package eu.kanade.tachiyomi.ui.catalogue
import android.content.res.Configuration import android.content.res.Configuration
import android.os.Bundle import android.os.Bundle
import android.support.design.widget.Snackbar import android.support.design.widget.Snackbar
import android.support.v4.widget.DrawerLayout
import android.support.v7.widget.* import android.support.v7.widget.*
import android.view.* import android.view.*
import android.view.animation.AnimationUtils 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.base.fragment.BaseRxFragment
import eu.kanade.tachiyomi.ui.main.MainActivity import eu.kanade.tachiyomi.ui.main.MainActivity
import eu.kanade.tachiyomi.ui.manga.MangaActivity import eu.kanade.tachiyomi.ui.manga.MangaActivity
import eu.kanade.tachiyomi.util.inflate
import eu.kanade.tachiyomi.util.snack import eu.kanade.tachiyomi.util.snack
import eu.kanade.tachiyomi.util.toast import eu.kanade.tachiyomi.util.toast
import eu.kanade.tachiyomi.widget.EndlessScrollListener import eu.kanade.tachiyomi.widget.EndlessScrollListener
import eu.kanade.tachiyomi.widget.IgnoreFirstSpinnerListener 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.fragment_catalogue.*
import kotlinx.android.synthetic.main.toolbar.* import kotlinx.android.synthetic.main.toolbar.*
import nucleus.factory.RequiresPresenter import nucleus.factory.RequiresPresenter
@ -100,6 +103,30 @@ open class CatalogueFragment : BaseRxFragment<CataloguePresenter>(), FlexibleVie
private val toolbar: Toolbar private val toolbar: Toolbar
get() = (activity as MainActivity).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 { companion object {
/** /**
* Creates a new instance of this fragment. * Creates a new instance of this fragment.
@ -176,6 +203,7 @@ open class CatalogueFragment : BaseRxFragment<CataloguePresenter>(), FlexibleVie
glm.scrollToPositionWithOffset(0, 0) glm.scrollToPositionWithOffset(0, 0)
llm.scrollToPositionWithOffset(0, 0) llm.scrollToPositionWithOffset(0, 0)
presenter.setActiveSource(source) presenter.setActiveSource(source)
navView?.setFilters(presenter.sourceFilters)
activity.invalidateOptionsMenu() activity.invalidateOptionsMenu()
} }
} }
@ -191,6 +219,32 @@ open class CatalogueFragment : BaseRxFragment<CataloguePresenter>(), FlexibleVie
setToolbarTitle("") setToolbarTitle("")
toolbar.addView(spinner) 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() showProgressBar()
} }
@ -244,7 +298,7 @@ open class CatalogueFragment : BaseRxFragment<CataloguePresenter>(), FlexibleVie
override fun onOptionsItemSelected(item: MenuItem): Boolean { override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) { when (item.itemId) {
R.id.action_display_mode -> swapDisplayMode() 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) else -> return super.onOptionsItemSelected(item)
} }
return true return true
@ -263,6 +317,10 @@ open class CatalogueFragment : BaseRxFragment<CataloguePresenter>(), FlexibleVie
} }
override fun onDestroyView() { override fun onDestroyView() {
navView?.let {
activity.drawer.removeDrawerListener(drawerListener)
activity.drawer.removeView(it)
}
numColumnsSubscription?.unsubscribe() numColumnsSubscription?.unsubscribe()
searchItem?.let { searchItem?.let {
if (it.isActionViewExpanded) it.collapseActionView() if (it.isActionViewExpanded) it.collapseActionView()
@ -448,29 +506,4 @@ open class CatalogueFragment : BaseRxFragment<CataloguePresenter>(), FlexibleVie
}.show() }.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 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. * Pager containing a list of manga results.
@ -105,6 +110,7 @@ open class CataloguePresenter : BasePresenter<CatalogueFragment>() {
try { try {
source = getLastUsedSource() source = getLastUsedSource()
sourceFilters = source.getFilterList()
} catch (error: NoSuchElementException) { } catch (error: NoSuchElementException) {
return return
} }
@ -130,9 +136,9 @@ open class CataloguePresenter : BasePresenter<CatalogueFragment>() {
* @param query the query. * @param query the query.
* @param filters the current state of the filters (for search mode). * @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.query = query
this.filters = filters this.appliedFilters = filters
if (!isListMode) { if (!isListMode) {
subscribeToMangaInitializer() subscribeToMangaInitializer()
@ -182,6 +188,7 @@ open class CataloguePresenter : BasePresenter<CatalogueFragment>() {
fun setActiveSource(source: OnlineSource) { fun setActiveSource(source: OnlineSource) {
prefs.lastUsedCatalogueSource().set(source.id) prefs.lastUsedCatalogueSource().set(source.id)
this.source = source this.source = source
sourceFilters = source.getFilterList()
restartPager(query = "", filters = emptyList()) 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 { init {
recycler.adapter = adapter recycler.adapter = adapter
addView(recycler)
groups.forEach { it.initModels() } 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.base.BaseReader
import eu.kanade.tachiyomi.ui.reader.viewer.pager.horizontal.LeftToRightReader import eu.kanade.tachiyomi.ui.reader.viewer.pager.horizontal.LeftToRightReader
import eu.kanade.tachiyomi.ui.reader.viewer.pager.horizontal.RightToLeftReader import eu.kanade.tachiyomi.ui.reader.viewer.pager.horizontal.RightToLeftReader
import eu.kanade.tachiyomi.util.toast
import rx.subscriptions.CompositeSubscription import rx.subscriptions.CompositeSubscription
/** /**

View File

@ -0,0 +1,9 @@
package eu.kanade.tachiyomi.util
import android.content.res.Resources
val Int.pxToDp: Int
get() = (this / Resources.getSystem().displayMetrics.density).toInt()
val Int.dpToPx: Int
get() = (this * Resources.getSystem().displayMetrics.density).toInt()

View File

@ -1,88 +1,27 @@
package eu.kanade.tachiyomi.widget package eu.kanade.tachiyomi.widget
import android.annotation.SuppressLint
import android.content.Context import android.content.Context
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.support.annotation.CallSuper import android.support.annotation.CallSuper
import android.support.design.R
import android.support.design.internal.ScrimInsetsFrameLayout
import android.support.graphics.drawable.VectorDrawableCompat import android.support.graphics.drawable.VectorDrawableCompat
import android.support.v4.content.ContextCompat import android.support.v4.content.ContextCompat
import android.support.v4.view.ViewCompat
import android.support.v7.widget.LinearLayoutManager
import android.support.v7.widget.RecyclerView import android.support.v7.widget.RecyclerView
import android.support.v7.widget.TintTypedArray
import android.util.AttributeSet import android.util.AttributeSet
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.CheckBox
import android.widget.CheckedTextView
import android.widget.RadioButton
import android.widget.TextView import android.widget.TextView
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.util.getResourceColor import eu.kanade.tachiyomi.util.getResourceColor
import eu.kanade.tachiyomi.util.inflate
import eu.kanade.tachiyomi.R as TR
/** /**
* An alternative implementation of [android.support.design.widget.NavigationView], without menu * An alternative implementation of [android.support.design.widget.NavigationView], without menu
* inflation and allowing customizable items (multiple selections, custom views, etc). * inflation and allowing customizable items (multiple selections, custom views, etc).
*/ */
@Suppress("LeakingThis")
@SuppressLint("PrivateResource")
open class ExtendedNavigationView @JvmOverloads constructor( open class ExtendedNavigationView @JvmOverloads constructor(
context: Context, context: Context,
attrs: AttributeSet? = null, attrs: AttributeSet? = null,
defStyleAttr: Int = 0) defStyleAttr: Int = 0)
: ScrimInsetsFrameLayout(context, attrs, defStyleAttr) { : SimpleNavigationView(context, attrs, defStyleAttr) {
/**
* Max width of the navigation view.
*/
private var maxWidth: Int
/**
* Recycler view containing all the items.
*/
protected val recycler = RecyclerView(context)
init {
// Custom attributes
val a = TintTypedArray.obtainStyledAttributes(context, attrs,
R.styleable.NavigationView, defStyleAttr,
R.style.Widget_Design_NavigationView)
ViewCompat.setBackground(
this, a.getDrawable(R.styleable.NavigationView_android_background))
if (a.hasValue(R.styleable.NavigationView_elevation)) {
ViewCompat.setElevation(this, a.getDimensionPixelSize(
R.styleable.NavigationView_elevation, 0).toFloat())
}
ViewCompat.setFitsSystemWindows(this,
a.getBoolean(R.styleable.NavigationView_android_fitsSystemWindows, false))
maxWidth = a.getDimensionPixelSize(R.styleable.NavigationView_android_maxWidth, 0)
a.recycle()
recycler.layoutManager = LinearLayoutManager(context)
addView(recycler)
}
/**
* Overriden to measure the width of the navigation view.
*/
override fun onMeasure(widthSpec: Int, heightSpec: Int) {
val width = when (MeasureSpec.getMode(widthSpec)) {
MeasureSpec.AT_MOST -> MeasureSpec.makeMeasureSpec(
Math.min(MeasureSpec.getSize(widthSpec), maxWidth), MeasureSpec.EXACTLY)
MeasureSpec.UNSPECIFIED -> MeasureSpec.makeMeasureSpec(maxWidth, MeasureSpec.EXACTLY)
else -> widthSpec
}
// Let super sort out the height
super.onMeasure(width, heightSpec)
}
/** /**
* Every item of the nav view. Generic items must belong to this list, custom items could be * Every item of the nav view. Generic items must belong to this list, custom items could be
@ -136,7 +75,7 @@ open class ExtendedNavigationView @JvmOverloads constructor(
*/ */
fun tintVector(context: Context, resId: Int): Drawable { fun tintVector(context: Context, resId: Int): Drawable {
return VectorDrawableCompat.create(context.resources, resId, context.theme)!!.apply { return VectorDrawableCompat.create(context.resources, resId, context.theme)!!.apply {
setTint(context.theme.getResourceColor(TR.attr.colorAccent)) setTint(context.theme.getResourceColor(R.attr.colorAccent))
} }
} }
} }
@ -161,9 +100,9 @@ open class ExtendedNavigationView @JvmOverloads constructor(
override fun getStateDrawable(context: Context): Drawable? { override fun getStateDrawable(context: Context): Drawable? {
return when (state) { return when (state) {
SORT_ASC -> tintVector(context, TR.drawable.ic_keyboard_arrow_up_black_32dp) SORT_ASC -> tintVector(context, R.drawable.ic_keyboard_arrow_up_black_32dp)
SORT_DESC -> tintVector(context, TR.drawable.ic_keyboard_arrow_down_black_32dp) SORT_DESC -> tintVector(context, R.drawable.ic_keyboard_arrow_down_black_32dp)
SORT_NONE -> ContextCompat.getDrawable(context, TR.drawable.empty_drawable_32dp) SORT_NONE -> ContextCompat.getDrawable(context, R.drawable.empty_drawable_32dp)
else -> null else -> null
} }
} }
@ -218,59 +157,6 @@ open class ExtendedNavigationView @JvmOverloads constructor(
} }
/**
* Base view holder.
*/
abstract class Holder(view: View) : RecyclerView.ViewHolder(view)
/**
* Separator view holder.
*/
class SeparatorHolder(parent: ViewGroup)
: Holder(parent.inflate(R.layout.design_navigation_item_separator))
/**
* Header view holder.
*/
class HeaderHolder(parent: ViewGroup)
: Holder(parent.inflate(R.layout.design_navigation_item_subheader))
/**
* Clickable view holder.
*/
abstract class ClickableHolder(view: View, listener: View.OnClickListener?) : Holder(view) {
init {
itemView.setOnClickListener(listener)
}
}
/**
* Radio view holder.
*/
class RadioHolder(parent: ViewGroup, listener: View.OnClickListener?)
: ClickableHolder(parent.inflate(TR.layout.navigation_view_radio), listener) {
val radio = itemView.findViewById(TR.id.nav_view_item) as RadioButton
}
/**
* Checkbox view holder.
*/
class CheckboxHolder(parent: ViewGroup, listener: View.OnClickListener?)
: ClickableHolder(parent.inflate(TR.layout.navigation_view_checkbox), listener) {
val check = itemView.findViewById(TR.id.nav_view_item) as CheckBox
}
/**
* Multi state view holder.
*/
class MultiStateHolder(parent: ViewGroup, listener: View.OnClickListener?)
: ClickableHolder(parent.inflate(TR.layout.navigation_view_checkedtext), listener) {
val text = itemView.findViewById(TR.id.nav_view_item) as CheckedTextView
}
/** /**
* Base adapter for the navigation view. It knows how to create and render every subclass of * Base adapter for the navigation view. It knows how to create and render every subclass of
* [Item]. * [Item].
@ -352,12 +238,4 @@ open class ExtendedNavigationView @JvmOverloads constructor(
} }
companion object {
private const val VIEW_TYPE_HEADER = 100
private const val VIEW_TYPE_SEPARATOR = 101
private const val VIEW_TYPE_RADIO = 102
private const val VIEW_TYPE_CHECKBOX = 103
private const val VIEW_TYPE_MULTISTATE = 104
}
} }

View File

@ -0,0 +1,152 @@
package eu.kanade.tachiyomi.widget
import android.annotation.SuppressLint
import android.content.Context
import android.support.design.R
import android.support.design.internal.ScrimInsetsFrameLayout
import android.support.design.widget.TextInputLayout
import android.support.v4.view.ViewCompat
import android.support.v7.widget.LinearLayoutManager
import android.support.v7.widget.RecyclerView
import android.support.v7.widget.TintTypedArray
import android.util.AttributeSet
import android.view.View
import android.view.ViewGroup
import android.widget.*
import eu.kanade.tachiyomi.util.inflate
import eu.kanade.tachiyomi.R as TR
@Suppress("LeakingThis")
@SuppressLint("PrivateResource")
open class SimpleNavigationView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0)
: ScrimInsetsFrameLayout(context, attrs, defStyleAttr) {
/**
* Max width of the navigation view.
*/
private var maxWidth: Int
/**
* Recycler view containing all the items.
*/
protected val recycler = RecyclerView(context)
init {
// Custom attributes
val a = TintTypedArray.obtainStyledAttributes(context, attrs,
R.styleable.NavigationView, defStyleAttr,
R.style.Widget_Design_NavigationView)
ViewCompat.setBackground(
this, a.getDrawable(R.styleable.NavigationView_android_background))
if (a.hasValue(R.styleable.NavigationView_elevation)) {
ViewCompat.setElevation(this, a.getDimensionPixelSize(
R.styleable.NavigationView_elevation, 0).toFloat())
}
ViewCompat.setFitsSystemWindows(this,
a.getBoolean(R.styleable.NavigationView_android_fitsSystemWindows, false))
maxWidth = a.getDimensionPixelSize(R.styleable.NavigationView_android_maxWidth, 0)
a.recycle()
recycler.layoutManager = LinearLayoutManager(context)
}
/**
* Overriden to measure the width of the navigation view.
*/
override fun onMeasure(widthSpec: Int, heightSpec: Int) {
val width = when (MeasureSpec.getMode(widthSpec)) {
MeasureSpec.AT_MOST -> MeasureSpec.makeMeasureSpec(
Math.min(MeasureSpec.getSize(widthSpec), maxWidth), MeasureSpec.EXACTLY)
MeasureSpec.UNSPECIFIED -> MeasureSpec.makeMeasureSpec(maxWidth, MeasureSpec.EXACTLY)
else -> widthSpec
}
// Let super sort out the height
super.onMeasure(width, heightSpec)
}
/**
* Base view holder.
*/
abstract class Holder(view: View) : RecyclerView.ViewHolder(view)
/**
* Separator view holder.
*/
class SeparatorHolder(parent: ViewGroup)
: Holder(parent.inflate(R.layout.design_navigation_item_separator))
/**
* Header view holder.
*/
class HeaderHolder(parent: ViewGroup)
: Holder(parent.inflate(R.layout.design_navigation_item_subheader))
/**
* Clickable view holder.
*/
abstract class ClickableHolder(view: View, listener: View.OnClickListener?) : Holder(view) {
init {
itemView.setOnClickListener(listener)
}
}
/**
* Radio view holder.
*/
class RadioHolder(parent: ViewGroup, listener: View.OnClickListener?)
: ClickableHolder(parent.inflate(TR.layout.navigation_view_radio), listener) {
val radio = itemView.findViewById(TR.id.nav_view_item) as RadioButton
}
/**
* Checkbox view holder.
*/
class CheckboxHolder(parent: ViewGroup, listener: View.OnClickListener?)
: ClickableHolder(parent.inflate(TR.layout.navigation_view_checkbox), listener) {
val check = itemView.findViewById(TR.id.nav_view_item) as CheckBox
}
/**
* Multi state view holder.
*/
class MultiStateHolder(parent: ViewGroup, listener: View.OnClickListener?)
: ClickableHolder(parent.inflate(TR.layout.navigation_view_checkedtext), listener) {
val text = itemView.findViewById(TR.id.nav_view_item) as CheckedTextView
}
class SpinnerHolder(parent: ViewGroup, listener: OnClickListener? = null)
: ClickableHolder(parent.inflate(TR.layout.navigation_view_spinner), listener) {
val text = itemView.findViewById(TR.id.nav_view_item_text) as TextView
val spinner = itemView.findViewById(TR.id.nav_view_item) as Spinner
}
class EditTextHolder(parent: ViewGroup)
: Holder(parent.inflate(TR.layout.navigation_view_text)) {
val wrapper = itemView.findViewById(TR.id.nav_view_item_wrapper) as TextInputLayout
val edit = itemView.findViewById(TR.id.nav_view_item) as EditText
}
protected companion object {
const val VIEW_TYPE_HEADER = 100
const val VIEW_TYPE_SEPARATOR = 101
const val VIEW_TYPE_RADIO = 102
const val VIEW_TYPE_CHECKBOX = 103
const val VIEW_TYPE_MULTISTATE = 104
const val VIEW_TYPE_TEXT = 105
const val VIEW_TYPE_LIST = 106
}
}

View File

@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_checked="true" android:drawable="@drawable/ic_check_box_24dp" />
<item android:drawable="@drawable/ic_check_box_outline_blank_24dp" />
</selector>

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<eu.kanade.tachiyomi.ui.catalogue.CatalogueNavigationView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/nav_view2"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="end"
android:fitsSystemWindows="false"/>

View File

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="8dp">
<Button
android:id="@+id/search_btn"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/action_search"/>
<Button
android:id="@+id/reset_btn"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/action_reset"/>
</LinearLayout>
</LinearLayout>

View File

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="?attr/listPreferredItemHeightSmall"
android:paddingLeft="?attr/listPreferredItemPaddingLeft"
android:paddingRight="?attr/listPreferredItemPaddingRight"
android:background="?attr/selectableItemBackground"
android:focusable="true">
<TextView
android:id="@+id/nav_view_item_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<Spinner
android:id="@+id/nav_view_item"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:gravity="center_vertical|start"
android:maxLines="1"
android:textAppearance="@style/TextAppearance.AppCompat.Body2" />
</LinearLayout>

View File

@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="?attr/listPreferredItemHeightSmall"
android:paddingLeft="?attr/listPreferredItemPaddingLeft"
android:paddingRight="?attr/listPreferredItemPaddingRight"
android:background="?attr/selectableItemBackground"
android:focusable="true">
<android.support.design.widget.TextInputLayout
android:id="@+id/nav_view_item_wrapper"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:gravity="center_vertical|start">
<EditText
android:id="@+id/nav_view_item"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:maxLines="1"
android:textAppearance="@style/TextAppearance.AppCompat.Body2" />
</android.support.design.widget.TextInputLayout>
</LinearLayout>

View File

@ -69,6 +69,7 @@
<string name="action_install">Install</string> <string name="action_install">Install</string>
<string name="action_share">Share</string> <string name="action_share">Share</string>
<string name="action_save">Save</string> <string name="action_save">Save</string>
<string name="action_reset">Reset</string>
<!-- Operations --> <!-- Operations -->
<string name="deleting">Deleting…</string> <string name="deleting">Deleting…</string>