mirror of
https://github.com/tachiyomiorg/tachiyomi.git
synced 2024-11-11 21:10:49 +01:00
Add drawer to filter and sort the library (#570)
* Add additional drawer to filter and sort the library * Tint icon when there's a filter active * Comments and minor changes
This commit is contained in:
parent
2dd58e5f7d
commit
b067096fc7
17 changed files with 743 additions and 132 deletions
|
@ -7,7 +7,4 @@ object Constants {
|
||||||
const val NOTIFICATION_DOWNLOAD_CHAPTER_ERROR_ID = 4
|
const val NOTIFICATION_DOWNLOAD_CHAPTER_ERROR_ID = 4
|
||||||
const val NOTIFICATION_DOWNLOAD_IMAGE_ID = 5
|
const val NOTIFICATION_DOWNLOAD_IMAGE_ID = 5
|
||||||
|
|
||||||
const val SORT_LIBRARY_ALPHA = 0
|
|
||||||
const val SORT_LIBRARY_LAST_READ = 1
|
|
||||||
const val SORT_LIBRARY_LAST_UPDATED = 2
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -130,6 +130,8 @@ class PreferencesHelper(context: Context) {
|
||||||
|
|
||||||
fun librarySortingMode() = rxPrefs.getInteger(keys.librarySortingMode, 0)
|
fun librarySortingMode() = rxPrefs.getInteger(keys.librarySortingMode, 0)
|
||||||
|
|
||||||
|
fun librarySortingAscending() = rxPrefs.getBoolean("library_sorting_ascending", true)
|
||||||
|
|
||||||
fun automaticUpdates() = prefs.getBoolean(keys.automaticUpdates, false)
|
fun automaticUpdates() = prefs.getBoolean(keys.automaticUpdates, false)
|
||||||
|
|
||||||
fun hiddenCatalogues() = rxPrefs.getStringSet("hidden_catalogues", emptySet())
|
fun hiddenCatalogues() = rxPrefs.getStringSet("hidden_catalogues", emptySet())
|
||||||
|
|
|
@ -221,6 +221,7 @@ open class CatalogueFragment : BaseRxFragment<CataloguePresenter>(), FlexibleVie
|
||||||
|
|
||||||
// Setup filters button
|
// Setup filters button
|
||||||
menu.findItem(R.id.action_set_filter).apply {
|
menu.findItem(R.id.action_set_filter).apply {
|
||||||
|
icon.mutate()
|
||||||
if (presenter.source.filters.isEmpty()) {
|
if (presenter.source.filters.isEmpty()) {
|
||||||
isEnabled = false
|
isEnabled = false
|
||||||
icon.alpha = 128
|
icon.alpha = 128
|
||||||
|
|
|
@ -3,25 +3,27 @@ package eu.kanade.tachiyomi.ui.library
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
|
import android.graphics.Color
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.support.design.widget.TabLayout
|
import android.support.design.widget.TabLayout
|
||||||
|
import android.support.v4.graphics.drawable.DrawableCompat
|
||||||
import android.support.v4.view.ViewPager
|
import android.support.v4.view.ViewPager
|
||||||
|
import android.support.v4.widget.DrawerLayout
|
||||||
import android.support.v7.view.ActionMode
|
import android.support.v7.view.ActionMode
|
||||||
import android.support.v7.widget.SearchView
|
import android.support.v7.widget.SearchView
|
||||||
import android.view.*
|
import android.view.*
|
||||||
import com.afollestad.materialdialogs.MaterialDialog
|
import com.afollestad.materialdialogs.MaterialDialog
|
||||||
import com.f2prateek.rx.preferences.Preference
|
import com.f2prateek.rx.preferences.Preference
|
||||||
import eu.kanade.tachiyomi.Constants
|
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.database.models.Category
|
import eu.kanade.tachiyomi.data.database.models.Category
|
||||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
import eu.kanade.tachiyomi.data.library.LibraryUpdateService
|
import eu.kanade.tachiyomi.data.library.LibraryUpdateService
|
||||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||||
import eu.kanade.tachiyomi.data.preference.getOrDefault
|
import eu.kanade.tachiyomi.data.preference.getOrDefault
|
||||||
import eu.kanade.tachiyomi.data.preference.invert
|
|
||||||
import eu.kanade.tachiyomi.ui.base.fragment.BaseRxFragment
|
import eu.kanade.tachiyomi.ui.base.fragment.BaseRxFragment
|
||||||
import eu.kanade.tachiyomi.ui.category.CategoryActivity
|
import eu.kanade.tachiyomi.ui.category.CategoryActivity
|
||||||
import eu.kanade.tachiyomi.ui.main.MainActivity
|
import eu.kanade.tachiyomi.ui.main.MainActivity
|
||||||
|
import eu.kanade.tachiyomi.util.inflate
|
||||||
import eu.kanade.tachiyomi.util.toast
|
import eu.kanade.tachiyomi.util.toast
|
||||||
import kotlinx.android.synthetic.main.activity_main.*
|
import kotlinx.android.synthetic.main.activity_main.*
|
||||||
import kotlinx.android.synthetic.main.fragment_library.*
|
import kotlinx.android.synthetic.main.fragment_library.*
|
||||||
|
@ -81,6 +83,30 @@ class LibraryFragment : BaseRxFragment<LibraryPresenter>(), ActionMode.Callback
|
||||||
var mangaPerRow = 0
|
var mangaPerRow = 0
|
||||||
private set
|
private set
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Navigation view containing filter/sort/display items.
|
||||||
|
*/
|
||||||
|
private lateinit var navView: LibraryNavigationView
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Subscription for the number of manga per row.
|
* Subscription for the number of manga per row.
|
||||||
*/
|
*/
|
||||||
|
@ -149,6 +175,25 @@ class LibraryFragment : BaseRxFragment<LibraryPresenter>(), ActionMode.Callback
|
||||||
.skip(1)
|
.skip(1)
|
||||||
// Set again the adapter to recalculate the covers height
|
// Set again the adapter to recalculate the covers height
|
||||||
.subscribe { reattachAdapter() }
|
.subscribe { reattachAdapter() }
|
||||||
|
|
||||||
|
|
||||||
|
// Inflate and prepare drawer
|
||||||
|
navView = activity.drawer.inflate(R.layout.library_drawer) as LibraryNavigationView
|
||||||
|
activity.drawer.addView(navView)
|
||||||
|
activity.drawer.addDrawerListener(drawerListener)
|
||||||
|
|
||||||
|
navView.post {
|
||||||
|
if (isAdded && !activity.drawer.isDrawerOpen(navView))
|
||||||
|
activity.drawer.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED, navView)
|
||||||
|
}
|
||||||
|
|
||||||
|
navView.onGroupClicked = { group ->
|
||||||
|
when (group) {
|
||||||
|
is LibraryNavigationView.FilterGroup -> onFilterChanged()
|
||||||
|
is LibraryNavigationView.SortGroup -> onSortChanged()
|
||||||
|
is LibraryNavigationView.DisplayGroup -> reattachAdapter()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
|
@ -157,6 +202,8 @@ class LibraryFragment : BaseRxFragment<LibraryPresenter>(), ActionMode.Callback
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroyView() {
|
override fun onDestroyView() {
|
||||||
|
activity.drawer.removeDrawerListener(drawerListener)
|
||||||
|
activity.drawer.removeView(navView)
|
||||||
numColumnsSubscription?.unsubscribe()
|
numColumnsSubscription?.unsubscribe()
|
||||||
tabs.setupWithViewPager(null)
|
tabs.setupWithViewPager(null)
|
||||||
tabs.visibility = View.GONE
|
tabs.visibility = View.GONE
|
||||||
|
@ -169,34 +216,6 @@ class LibraryFragment : BaseRxFragment<LibraryPresenter>(), ActionMode.Callback
|
||||||
super.onSaveInstanceState(outState)
|
super.onSaveInstanceState(outState)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Prepare the Fragment host's standard options menu to be displayed. This is
|
|
||||||
* called right before the menu is shown, every time it is shown. You can
|
|
||||||
* use this method to efficiently enable/disable items or otherwise
|
|
||||||
* dynamically modify the contents.
|
|
||||||
*
|
|
||||||
* @param menu The options menu as last shown or first initialized by
|
|
||||||
*/
|
|
||||||
override fun onPrepareOptionsMenu(menu: Menu) {
|
|
||||||
// Initialize search menu
|
|
||||||
val filterDownloadedItem = menu.findItem(R.id.action_filter_downloaded)
|
|
||||||
val filterUnreadItem = menu.findItem(R.id.action_filter_unread)
|
|
||||||
val sortModeAlpha = menu.findItem(R.id.action_sort_alpha)
|
|
||||||
val sortModeLastRead = menu.findItem(R.id.action_sort_last_read)
|
|
||||||
val sortModeLastUpdated = menu.findItem(R.id.action_sort_last_updated)
|
|
||||||
|
|
||||||
// Set correct checkbox filter
|
|
||||||
filterDownloadedItem.isChecked = preferences.filterDownloaded().getOrDefault()
|
|
||||||
filterUnreadItem.isChecked = preferences.filterUnread().getOrDefault()
|
|
||||||
|
|
||||||
// Set correct radio button sort
|
|
||||||
when (preferences.librarySortingMode().getOrDefault()) {
|
|
||||||
Constants.SORT_LIBRARY_ALPHA -> sortModeAlpha.isChecked = true
|
|
||||||
Constants.SORT_LIBRARY_LAST_READ -> sortModeLastRead.isChecked = true
|
|
||||||
Constants.SORT_LIBRARY_LAST_UPDATED -> sortModeLastUpdated.isChecked = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
||||||
inflater.inflate(R.menu.library, menu)
|
inflater.inflate(R.menu.library, menu)
|
||||||
|
|
||||||
|
@ -209,6 +228,9 @@ class LibraryFragment : BaseRxFragment<LibraryPresenter>(), ActionMode.Callback
|
||||||
searchView.clearFocus()
|
searchView.clearFocus()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Mutate the filter icon because it needs to be tinted and the resource is shared.
|
||||||
|
menu.findItem(R.id.action_filter).icon.mutate()
|
||||||
|
|
||||||
searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
|
searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
|
||||||
override fun onQueryTextSubmit(query: String): Boolean {
|
override fun onQueryTextSubmit(query: String): Boolean {
|
||||||
onSearchTextChange(query)
|
onSearchTextChange(query)
|
||||||
|
@ -223,40 +245,19 @@ class LibraryFragment : BaseRxFragment<LibraryPresenter>(), ActionMode.Callback
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onPrepareOptionsMenu(menu: Menu) {
|
||||||
|
val filterItem = menu.findItem(R.id.action_filter)
|
||||||
|
|
||||||
|
// Tint icon if there's a filter active
|
||||||
|
val filterColor = if (navView.hasActiveFilters()) Color.rgb(255, 238, 7) else Color.WHITE
|
||||||
|
DrawableCompat.setTint(filterItem.icon, filterColor)
|
||||||
|
}
|
||||||
|
|
||||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||||
when (item.itemId) {
|
when (item.itemId) {
|
||||||
R.id.action_filter_unread -> {
|
R.id.action_filter -> {
|
||||||
// Update settings.
|
activity.drawer.openDrawer(Gravity.END)
|
||||||
preferences.filterUnread().invert()
|
|
||||||
// Apply filter.
|
|
||||||
onFilterOrSortChanged()
|
|
||||||
}
|
}
|
||||||
R.id.action_filter_downloaded -> {
|
|
||||||
// Update settings.
|
|
||||||
preferences.filterDownloaded().invert()
|
|
||||||
// Apply filter.
|
|
||||||
onFilterOrSortChanged()
|
|
||||||
}
|
|
||||||
R.id.action_filter_empty -> {
|
|
||||||
// Update settings.
|
|
||||||
preferences.filterUnread().set(false)
|
|
||||||
preferences.filterDownloaded().set(false)
|
|
||||||
// Apply filter
|
|
||||||
onFilterOrSortChanged()
|
|
||||||
}
|
|
||||||
R.id.action_sort_alpha -> {
|
|
||||||
preferences.librarySortingMode().set(Constants.SORT_LIBRARY_ALPHA)
|
|
||||||
onFilterOrSortChanged()
|
|
||||||
}
|
|
||||||
R.id.action_sort_last_read -> {
|
|
||||||
preferences.librarySortingMode().set(Constants.SORT_LIBRARY_LAST_READ)
|
|
||||||
onFilterOrSortChanged()
|
|
||||||
}
|
|
||||||
R.id.action_sort_last_updated -> {
|
|
||||||
preferences.librarySortingMode().set(Constants.SORT_LIBRARY_LAST_UPDATED)
|
|
||||||
onFilterOrSortChanged()
|
|
||||||
}
|
|
||||||
R.id.action_library_display_mode -> swapDisplayMode()
|
|
||||||
R.id.action_update_library -> {
|
R.id.action_update_library -> {
|
||||||
LibraryUpdateService.start(activity)
|
LibraryUpdateService.start(activity)
|
||||||
}
|
}
|
||||||
|
@ -271,19 +272,18 @@ class LibraryFragment : BaseRxFragment<LibraryPresenter>(), ActionMode.Callback
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Applies filter change
|
* Called when a filter is changed.
|
||||||
*/
|
*/
|
||||||
private fun onFilterOrSortChanged() {
|
private fun onFilterChanged() {
|
||||||
presenter.requestLibraryUpdate()
|
presenter.requestLibraryUpdate()
|
||||||
activity.supportInvalidateOptionsMenu()
|
activity.supportInvalidateOptionsMenu()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Swap display mode
|
* Called when the sorting mode is changed.
|
||||||
*/
|
*/
|
||||||
private fun swapDisplayMode() {
|
private fun onSortChanged() {
|
||||||
presenter.swapDisplayMode()
|
presenter.requestLibraryUpdate()
|
||||||
reattachAdapter()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -0,0 +1,187 @@
|
||||||
|
package eu.kanade.tachiyomi.ui.library
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.util.AttributeSet
|
||||||
|
import eu.kanade.tachiyomi.R
|
||||||
|
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||||
|
import eu.kanade.tachiyomi.data.preference.getOrDefault
|
||||||
|
import eu.kanade.tachiyomi.widget.ExtendedNavigationView
|
||||||
|
import eu.kanade.tachiyomi.widget.ExtendedNavigationView.Item.MultiSort.Companion.SORT_ASC
|
||||||
|
import eu.kanade.tachiyomi.widget.ExtendedNavigationView.Item.MultiSort.Companion.SORT_DESC
|
||||||
|
import eu.kanade.tachiyomi.widget.ExtendedNavigationView.Item.MultiSort.Companion.SORT_NONE
|
||||||
|
import uy.kohesive.injekt.injectLazy
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The navigation view shown in a drawer with the different options to show the library.
|
||||||
|
*/
|
||||||
|
class LibraryNavigationView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null)
|
||||||
|
: ExtendedNavigationView(context, attrs) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Preferences helper.
|
||||||
|
*/
|
||||||
|
private val preferences: PreferencesHelper by injectLazy()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of groups shown in the view.
|
||||||
|
*/
|
||||||
|
private val groups = listOf(FilterGroup(), SortGroup(), DisplayGroup())
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adapter instance.
|
||||||
|
*/
|
||||||
|
private val adapter = Adapter(groups.map { it.createItems() }.flatten())
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Click listener to notify the parent fragment when an item from a group is clicked.
|
||||||
|
*/
|
||||||
|
var onGroupClicked: (Group) -> Unit = {}
|
||||||
|
|
||||||
|
init {
|
||||||
|
recycler.adapter = adapter
|
||||||
|
|
||||||
|
groups.forEach { it.initModels() }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if there's at least one filter from [FilterGroup] active.
|
||||||
|
*/
|
||||||
|
fun hasActiveFilters(): Boolean {
|
||||||
|
return (groups[0] as FilterGroup).items.any { it.checked }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adapter of the recycler view.
|
||||||
|
*/
|
||||||
|
inner class Adapter(items: List<Item>) : ExtendedNavigationView.Adapter(items) {
|
||||||
|
|
||||||
|
override fun onItemClicked(item: Item) {
|
||||||
|
if (item is GroupedItem) {
|
||||||
|
item.group.onItemClicked(item)
|
||||||
|
onGroupClicked(item.group)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filters group (unread, downloaded, ...).
|
||||||
|
*/
|
||||||
|
inner class FilterGroup : Group {
|
||||||
|
|
||||||
|
private val downloaded = Item.CheckboxGroup(R.string.action_filter_downloaded, this)
|
||||||
|
|
||||||
|
private val unread = Item.CheckboxGroup(R.string.action_filter_unread, this)
|
||||||
|
|
||||||
|
override val items = listOf(downloaded, unread)
|
||||||
|
|
||||||
|
override val header = Item.Header(R.string.action_filter)
|
||||||
|
|
||||||
|
override val footer = Item.Separator()
|
||||||
|
|
||||||
|
override fun initModels() {
|
||||||
|
downloaded.checked = preferences.filterDownloaded().getOrDefault()
|
||||||
|
unread.checked = preferences.filterUnread().getOrDefault()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onItemClicked(item: Item) {
|
||||||
|
item as Item.CheckboxGroup
|
||||||
|
item.checked = !item.checked
|
||||||
|
when (item) {
|
||||||
|
downloaded -> preferences.filterDownloaded().set(item.checked)
|
||||||
|
unread -> preferences.filterUnread().set(item.checked)
|
||||||
|
}
|
||||||
|
|
||||||
|
adapter.notifyItemChanged(item)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sorting group (alphabetically, by last read, ...) and ascending or descending.
|
||||||
|
*/
|
||||||
|
inner class SortGroup : Group {
|
||||||
|
|
||||||
|
private val alphabetically = Item.MultiSort(R.string.action_sort_alpha, this)
|
||||||
|
|
||||||
|
private val lastRead = Item.MultiSort(R.string.action_sort_last_read, this)
|
||||||
|
|
||||||
|
private val lastUpdated = Item.MultiSort(R.string.action_sort_last_updated, this)
|
||||||
|
|
||||||
|
override val items = listOf(alphabetically, lastRead, lastUpdated)
|
||||||
|
|
||||||
|
override val header = Item.Header(R.string.action_sort)
|
||||||
|
|
||||||
|
override val footer = Item.Separator()
|
||||||
|
|
||||||
|
override fun initModels() {
|
||||||
|
val sorting = preferences.librarySortingMode().getOrDefault()
|
||||||
|
val order = if (preferences.librarySortingAscending().getOrDefault())
|
||||||
|
SORT_ASC else SORT_DESC
|
||||||
|
|
||||||
|
alphabetically.state = if (sorting == LibrarySort.ALPHA) order else SORT_NONE
|
||||||
|
lastRead.state = if (sorting == LibrarySort.LAST_READ) order else SORT_NONE
|
||||||
|
lastUpdated.state = if (sorting == LibrarySort.LAST_UPDATED) order else SORT_NONE
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onItemClicked(item: Item) {
|
||||||
|
item as Item.MultiStateGroup
|
||||||
|
val prevState = item.state
|
||||||
|
|
||||||
|
item.group.items.forEach { (it as Item.MultiStateGroup).state = SORT_NONE }
|
||||||
|
item.state = when (prevState) {
|
||||||
|
SORT_NONE -> SORT_ASC
|
||||||
|
SORT_ASC -> SORT_DESC
|
||||||
|
SORT_DESC -> SORT_ASC
|
||||||
|
else -> throw Exception("Unknown state")
|
||||||
|
}
|
||||||
|
|
||||||
|
preferences.librarySortingMode().set(when (item) {
|
||||||
|
alphabetically -> LibrarySort.ALPHA
|
||||||
|
lastRead -> LibrarySort.LAST_READ
|
||||||
|
lastUpdated -> LibrarySort.LAST_UPDATED
|
||||||
|
else -> throw Exception("Unknown sorting")
|
||||||
|
})
|
||||||
|
preferences.librarySortingAscending().set(if (item.state == SORT_ASC) true else false)
|
||||||
|
|
||||||
|
item.group.items.forEach { adapter.notifyItemChanged(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display group, to show the library as a list or a grid.
|
||||||
|
*/
|
||||||
|
inner class DisplayGroup : Group {
|
||||||
|
|
||||||
|
private val grid = Item.Radio(R.string.action_display_grid, this)
|
||||||
|
|
||||||
|
private val list = Item.Radio(R.string.action_display_list, this)
|
||||||
|
|
||||||
|
override val items = listOf(grid, list)
|
||||||
|
|
||||||
|
override val header = Item.Header(R.string.action_display)
|
||||||
|
|
||||||
|
override val footer = null
|
||||||
|
|
||||||
|
override fun initModels() {
|
||||||
|
val asList = preferences.libraryAsList().getOrDefault()
|
||||||
|
grid.checked = !asList
|
||||||
|
list.checked = asList
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onItemClicked(item: Item) {
|
||||||
|
item as Item.Radio
|
||||||
|
if (item.checked) return
|
||||||
|
|
||||||
|
item.group.items.forEach { (it as Item.Radio).checked = false }
|
||||||
|
item.checked = true
|
||||||
|
|
||||||
|
preferences.libraryAsList().set(if (item == list) true else false)
|
||||||
|
|
||||||
|
item.group.items.forEach { adapter.notifyItemChanged(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -4,7 +4,6 @@ import android.os.Bundle
|
||||||
import android.util.Pair
|
import android.util.Pair
|
||||||
import com.jakewharton.rxrelay.BehaviorRelay
|
import com.jakewharton.rxrelay.BehaviorRelay
|
||||||
import com.jakewharton.rxrelay.PublishRelay
|
import com.jakewharton.rxrelay.PublishRelay
|
||||||
import eu.kanade.tachiyomi.Constants
|
|
||||||
import eu.kanade.tachiyomi.data.cache.CoverCache
|
import eu.kanade.tachiyomi.data.cache.CoverCache
|
||||||
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
||||||
import eu.kanade.tachiyomi.data.database.models.Category
|
import eu.kanade.tachiyomi.data.database.models.Category
|
||||||
|
@ -111,9 +110,12 @@ class LibraryPresenter : BasePresenter<LibraryFragment>() {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun applyFilters(map: Map<Int, List<Manga>>): Map<Int, List<Manga>> {
|
private fun applyFilters(map: Map<Int, List<Manga>>): Map<Int, List<Manga>> {
|
||||||
|
val isAscending = preferences.librarySortingAscending().getOrDefault()
|
||||||
|
val comparator = Comparator<Manga> { m1, m2 -> sortManga(m1, m2) }
|
||||||
|
|
||||||
return map.mapValues { entry -> entry.value
|
return map.mapValues { entry -> entry.value
|
||||||
.filter { filterManga(it) }
|
.filter { filterManga(it) }
|
||||||
.sortedWith(Comparator<Manga> { m1, m2 -> sortManga(m1, m2) })
|
.sortedWith(if (isAscending) comparator else Collections.reverseOrder(comparator))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -172,19 +174,15 @@ class LibraryPresenter : BasePresenter<LibraryFragment>() {
|
||||||
*/
|
*/
|
||||||
fun sortManga(manga1: Manga, manga2: Manga): Int {
|
fun sortManga(manga1: Manga, manga2: Manga): Int {
|
||||||
when (preferences.librarySortingMode().getOrDefault()) {
|
when (preferences.librarySortingMode().getOrDefault()) {
|
||||||
Constants.SORT_LIBRARY_ALPHA -> return manga1.title.compareTo(manga2.title)
|
LibrarySort.ALPHA -> return manga1.title.compareTo(manga2.title)
|
||||||
Constants.SORT_LIBRARY_LAST_READ -> {
|
LibrarySort.LAST_READ -> {
|
||||||
var a = 0L
|
var a = 0L
|
||||||
var b = 0L
|
var b = 0L
|
||||||
manga1.id?.let { manga1Id ->
|
db.getLastHistoryByMangaId(manga1.id!!).executeAsBlocking()?.let { a = it.last_read }
|
||||||
manga2.id?.let { manga2Id ->
|
db.getLastHistoryByMangaId(manga2.id!!).executeAsBlocking()?.let { b = it.last_read }
|
||||||
db.getLastHistoryByMangaId(manga1Id).executeAsBlocking()?.let { a = it.last_read }
|
|
||||||
db.getLastHistoryByMangaId(manga2Id).executeAsBlocking()?.let { b = it.last_read }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return b.compareTo(a)
|
return b.compareTo(a)
|
||||||
}
|
}
|
||||||
Constants.SORT_LIBRARY_LAST_UPDATED -> return manga2.last_update.compareTo(manga1.last_update)
|
LibrarySort.LAST_UPDATED -> return manga2.last_update.compareTo(manga1.last_update)
|
||||||
else -> return manga1.title.compareTo(manga2.title)
|
else -> return manga1.title.compareTo(manga2.title)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -326,12 +324,4 @@ class LibraryPresenter : BasePresenter<LibraryFragment>() {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Changes the active display mode.
|
|
||||||
*/
|
|
||||||
fun swapDisplayMode() {
|
|
||||||
val displayAsList = preferences.libraryAsList().getOrDefault()
|
|
||||||
preferences.libraryAsList().set(!displayAsList)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
package eu.kanade.tachiyomi.ui.library
|
||||||
|
|
||||||
|
object LibrarySort {
|
||||||
|
|
||||||
|
const val ALPHA = 0
|
||||||
|
const val LAST_READ = 1
|
||||||
|
const val LAST_UPDATED = 2
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,363 @@
|
||||||
|
package eu.kanade.tachiyomi.widget
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.content.Context
|
||||||
|
import android.graphics.drawable.Drawable
|
||||||
|
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.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.TintTypedArray
|
||||||
|
import android.util.AttributeSet
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.CheckBox
|
||||||
|
import android.widget.CheckedTextView
|
||||||
|
import android.widget.RadioButton
|
||||||
|
import android.widget.TextView
|
||||||
|
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
|
||||||
|
* inflation and allowing customizable items (multiple selections, custom views, etc).
|
||||||
|
*/
|
||||||
|
@Suppress("LeakingThis")
|
||||||
|
@SuppressLint("PrivateResource")
|
||||||
|
open class ExtendedNavigationView @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)
|
||||||
|
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
|
||||||
|
* implemented by an abstract class. If more customization is needed in the future, this can be
|
||||||
|
* changed to an interface instead of sealed class.
|
||||||
|
*/
|
||||||
|
sealed class Item {
|
||||||
|
/**
|
||||||
|
* A view separator.
|
||||||
|
*/
|
||||||
|
class Separator(val paddingTop: Int = 0, val paddingBottom: Int = 0) : Item()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A header with a title.
|
||||||
|
*/
|
||||||
|
class Header(val resTitle: Int) : Item()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A checkbox.
|
||||||
|
*/
|
||||||
|
open class Checkbox(val resTitle: Int, var checked: Boolean = false) : Item()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A checkbox belonging to a group. The group must handle selections and restrictions.
|
||||||
|
*/
|
||||||
|
class CheckboxGroup(resTitle: Int, override val group: Group, checked: Boolean = false)
|
||||||
|
: Checkbox(resTitle, checked), GroupedItem
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A radio belonging to a group (a sole radio makes no sense). The group must handle
|
||||||
|
* selections and restrictions.
|
||||||
|
*/
|
||||||
|
class Radio(val resTitle: Int, override val group: Group, var checked: Boolean = false)
|
||||||
|
: Item(), GroupedItem
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An item with which needs more than two states (selected/deselected).
|
||||||
|
*/
|
||||||
|
abstract class MultiState(val resTitle: Int, var state: Int = 0) : Item() {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the drawable associated to every possible each state.
|
||||||
|
*/
|
||||||
|
abstract fun getStateDrawable(context: Context): Drawable?
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a vector tinted with the accent color.
|
||||||
|
*
|
||||||
|
* @param context any context.
|
||||||
|
* @param resId the vector resource to load and tint
|
||||||
|
*/
|
||||||
|
fun tintVector(context: Context, resId: Int): Drawable {
|
||||||
|
return VectorDrawableCompat.create(context.resources, resId, context.theme)!!.apply {
|
||||||
|
setTint(context.theme.getResourceColor(TR.attr.colorAccent))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An item with which needs more than two states (selected/deselected) belonging to a group.
|
||||||
|
* The group must handle selections and restrictions.
|
||||||
|
*/
|
||||||
|
abstract class MultiStateGroup(resTitle: Int, override val group: Group, state: Int = 0)
|
||||||
|
: MultiState(resTitle, state), GroupedItem
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A multistate item for sorting lists (unselected, ascending, descending).
|
||||||
|
*/
|
||||||
|
class MultiSort(resId: Int, group: Group) : MultiStateGroup(resId, group) {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val SORT_NONE = 0
|
||||||
|
const val SORT_ASC = 1
|
||||||
|
const val SORT_DESC = 2
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getStateDrawable(context: Context): Drawable? {
|
||||||
|
return when (state) {
|
||||||
|
SORT_ASC -> tintVector(context, TR.drawable.ic_keyboard_arrow_up_black_32dp)
|
||||||
|
SORT_DESC -> tintVector(context, TR.drawable.ic_keyboard_arrow_down_black_32dp)
|
||||||
|
SORT_NONE -> ContextCompat.getDrawable(context, TR.drawable.empty_drawable_32dp)
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface for an item belonging to a group.
|
||||||
|
*/
|
||||||
|
interface GroupedItem {
|
||||||
|
val group: Group
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A group containing a list of items.
|
||||||
|
*/
|
||||||
|
interface Group {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An optional header for the group, typically a [Item.Header].
|
||||||
|
*/
|
||||||
|
val header: Item?
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An optional footer for the group, typically a [Item.Separator].
|
||||||
|
*/
|
||||||
|
val footer: Item?
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The items of the group, excluding header and footer.
|
||||||
|
*/
|
||||||
|
val items: List<Item>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates all the elements of this group. Implementations can override this method for more
|
||||||
|
* customization.
|
||||||
|
*/
|
||||||
|
fun createItems() = (mutableListOf<Item>() + header + items + footer).filterNotNull()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called after creating the list of items. Implementations should load the current values
|
||||||
|
* into the models.
|
||||||
|
*/
|
||||||
|
fun initModels()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when an item of this group is clicked. The group is responsible for all the
|
||||||
|
* selections of its items.
|
||||||
|
*/
|
||||||
|
fun onItemClicked(item: Item)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
* [Item].
|
||||||
|
*/
|
||||||
|
abstract inner class Adapter(private val items: List<Item>) : RecyclerView.Adapter<Holder>() {
|
||||||
|
|
||||||
|
private val onClick = View.OnClickListener {
|
||||||
|
val pos = recycler.getChildAdapterPosition(it)
|
||||||
|
val item = items[pos]
|
||||||
|
onItemClicked(item)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun notifyItemChanged(item: Item) {
|
||||||
|
val pos = items.indexOf(item)
|
||||||
|
if (pos != -1) notifyItemChanged(pos)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getItemCount(): Int {
|
||||||
|
return items.size
|
||||||
|
}
|
||||||
|
|
||||||
|
@CallSuper
|
||||||
|
override fun getItemViewType(position: Int): Int {
|
||||||
|
val item = items[position]
|
||||||
|
return when (item) {
|
||||||
|
is Item.Header -> VIEW_TYPE_HEADER
|
||||||
|
is Item.Separator -> VIEW_TYPE_SEPARATOR
|
||||||
|
is Item.Radio -> VIEW_TYPE_RADIO
|
||||||
|
is Item.Checkbox -> VIEW_TYPE_CHECKBOX
|
||||||
|
is Item.MultiState -> VIEW_TYPE_MULTISTATE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@CallSuper
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): Holder {
|
||||||
|
return when (viewType) {
|
||||||
|
VIEW_TYPE_HEADER -> HeaderHolder(parent)
|
||||||
|
VIEW_TYPE_SEPARATOR -> SeparatorHolder(parent)
|
||||||
|
VIEW_TYPE_RADIO -> RadioHolder(parent, onClick)
|
||||||
|
VIEW_TYPE_CHECKBOX -> CheckboxHolder(parent, onClick)
|
||||||
|
VIEW_TYPE_MULTISTATE -> MultiStateHolder(parent, onClick)
|
||||||
|
else -> throw Exception("Unknown view type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@CallSuper
|
||||||
|
override fun onBindViewHolder(holder: Holder, position: Int) {
|
||||||
|
when (holder) {
|
||||||
|
is HeaderHolder -> {
|
||||||
|
val view = holder.itemView as TextView
|
||||||
|
val item = items[position] as Item.Header
|
||||||
|
view.setText(item.resTitle)
|
||||||
|
}
|
||||||
|
is SeparatorHolder -> {
|
||||||
|
val view = holder.itemView
|
||||||
|
val item = items[position] as Item.Separator
|
||||||
|
view.setPadding(0, item.paddingTop, 0, item.paddingBottom)
|
||||||
|
}
|
||||||
|
is RadioHolder -> {
|
||||||
|
val item = items[position] as Item.Radio
|
||||||
|
holder.radio.setText(item.resTitle)
|
||||||
|
holder.radio.isChecked = item.checked
|
||||||
|
}
|
||||||
|
is CheckboxHolder -> {
|
||||||
|
val item = items[position] as Item.CheckboxGroup
|
||||||
|
holder.check.setText(item.resTitle)
|
||||||
|
holder.check.isChecked = item.checked
|
||||||
|
}
|
||||||
|
is MultiStateHolder -> {
|
||||||
|
val item = items[position] as Item.MultiStateGroup
|
||||||
|
val drawable = item.getStateDrawable(context)
|
||||||
|
holder.text.setText(item.resTitle)
|
||||||
|
holder.text.setCompoundDrawablesWithIntrinsicBounds(drawable, null, null, null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract fun onItemClicked(item: Item)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
8
app/src/main/res/drawable/empty_drawable_32dp.xml
Normal file
8
app/src/main/res/drawable/empty_drawable_32dp.xml
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
<shape
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:shape="rectangle">
|
||||||
|
<solid android:color="@android:color/transparent"/>
|
||||||
|
<size
|
||||||
|
android:width="32dp"
|
||||||
|
android:height="32dp" />
|
||||||
|
</shape>
|
|
@ -0,0 +1,9 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="32dp"
|
||||||
|
android:height="32dp"
|
||||||
|
android:viewportWidth="24.0"
|
||||||
|
android:viewportHeight="24.0">
|
||||||
|
<path
|
||||||
|
android:fillColor="#FF000000"
|
||||||
|
android:pathData="M7.41,7.84L12,12.42l4.59,-4.58L18,9.25l-6,6 -6,-6z"/>
|
||||||
|
</vector>
|
|
@ -0,0 +1,9 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="32dp"
|
||||||
|
android:height="32dp"
|
||||||
|
android:viewportWidth="24.0"
|
||||||
|
android:viewportHeight="24.0">
|
||||||
|
<path
|
||||||
|
android:fillColor="#FF000000"
|
||||||
|
android:pathData="M7.41,15.41L12,10.83l4.59,4.58L18,14l-6,-6 -6,6z"/>
|
||||||
|
</vector>
|
8
app/src/main/res/layout/library_drawer.xml
Normal file
8
app/src/main/res/layout/library_drawer.xml
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<eu.kanade.tachiyomi.ui.library.LibraryNavigationView
|
||||||
|
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" />
|
23
app/src/main/res/layout/navigation_view_checkbox.xml
Normal file
23
app/src/main/res/layout/navigation_view_checkbox.xml
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
<?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:foreground="?attr/selectableItemBackground"
|
||||||
|
android:focusable="true">
|
||||||
|
|
||||||
|
<CheckBox
|
||||||
|
android:id="@+id/nav_view_item"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:paddingLeft="@dimen/material_component_lists_icon_left_padding"
|
||||||
|
android:background="@android:color/transparent"
|
||||||
|
android:gravity="center_vertical|start"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:clickable="false"
|
||||||
|
android:textAppearance="@style/TextAppearance.AppCompat.Body2" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
21
app/src/main/res/layout/navigation_view_checkedtext.xml
Normal file
21
app/src/main/res/layout/navigation_view_checkedtext.xml
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
<?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:foreground="?attr/selectableItemBackground"
|
||||||
|
android:focusable="true">
|
||||||
|
|
||||||
|
<CheckedTextView
|
||||||
|
android:id="@+id/nav_view_item"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:drawablePadding="@dimen/material_component_lists_icon_left_padding"
|
||||||
|
android:gravity="center_vertical|start"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:textAppearance="@style/TextAppearance.AppCompat.Body2" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
23
app/src/main/res/layout/navigation_view_radio.xml
Normal file
23
app/src/main/res/layout/navigation_view_radio.xml
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
<?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:foreground="?attr/selectableItemBackground"
|
||||||
|
android:focusable="true">
|
||||||
|
|
||||||
|
<RadioButton
|
||||||
|
android:id="@+id/nav_view_item"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:paddingLeft="@dimen/material_component_lists_icon_left_padding"
|
||||||
|
android:background="@android:color/transparent"
|
||||||
|
android:gravity="center_vertical|start"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:clickable="false"
|
||||||
|
android:textAppearance="@style/TextAppearance.AppCompat.Body2" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
|
@ -14,44 +14,7 @@
|
||||||
android:id="@+id/action_filter"
|
android:id="@+id/action_filter"
|
||||||
android:icon="@drawable/ic_filter_list_white_24dp"
|
android:icon="@drawable/ic_filter_list_white_24dp"
|
||||||
android:title="@string/action_filter"
|
android:title="@string/action_filter"
|
||||||
app:showAsAction="ifRoom">
|
app:showAsAction="ifRoom"/>
|
||||||
<menu>
|
|
||||||
<item
|
|
||||||
android:id="@+id/action_filter_downloaded"
|
|
||||||
android:checkable="true"
|
|
||||||
android:title="@string/action_filter_downloaded"/>
|
|
||||||
<item
|
|
||||||
android:id="@+id/action_filter_unread"
|
|
||||||
android:checkable="true"
|
|
||||||
android:title="@string/action_filter_unread"/>
|
|
||||||
<item
|
|
||||||
android:id="@+id/action_filter_empty"
|
|
||||||
android:title="@string/action_filter_empty"/>
|
|
||||||
</menu>
|
|
||||||
</item>
|
|
||||||
|
|
||||||
<item
|
|
||||||
android:id="@+id/action_sort"
|
|
||||||
android:icon="@drawable/ic_sort_white_24dp"
|
|
||||||
android:title="@string/action_sort"
|
|
||||||
app:showAsAction="never"
|
|
||||||
>
|
|
||||||
<menu>
|
|
||||||
<group
|
|
||||||
android:id="@+id/sort_group"
|
|
||||||
android:checkableBehavior="single">
|
|
||||||
<item
|
|
||||||
android:id="@+id/action_sort_alpha"
|
|
||||||
android:title="@string/action_sort_alpha"/>
|
|
||||||
<item
|
|
||||||
android:id="@+id/action_sort_last_read"
|
|
||||||
android:title="@string/action_sort_last_read"/>
|
|
||||||
<item
|
|
||||||
android:id="@+id/action_sort_last_updated"
|
|
||||||
android:title="@string/action_sort_last_updated"/>
|
|
||||||
</group>
|
|
||||||
</menu>
|
|
||||||
</item>
|
|
||||||
|
|
||||||
<item
|
<item
|
||||||
android:id="@+id/action_update_library"
|
android:id="@+id/action_update_library"
|
||||||
|
@ -59,11 +22,6 @@
|
||||||
android:title="@string/action_update_library"
|
android:title="@string/action_update_library"
|
||||||
app:showAsAction="ifRoom"/>
|
app:showAsAction="ifRoom"/>
|
||||||
|
|
||||||
<item
|
|
||||||
android:id="@+id/action_library_display_mode"
|
|
||||||
android:title="@string/action_display_mode"
|
|
||||||
app:showAsAction="never"/>
|
|
||||||
|
|
||||||
<item
|
<item
|
||||||
android:id="@+id/action_edit_categories"
|
android:id="@+id/action_edit_categories"
|
||||||
android:title="@string/action_edit_categories"
|
android:title="@string/action_edit_categories"
|
||||||
|
|
|
@ -60,6 +60,9 @@
|
||||||
<string name="action_open_in_browser">Open in browser</string>
|
<string name="action_open_in_browser">Open in browser</string>
|
||||||
<string name="action_add_to_home_screen">Add to home screen</string>
|
<string name="action_add_to_home_screen">Add to home screen</string>
|
||||||
<string name="action_display_mode">Change display mode</string>
|
<string name="action_display_mode">Change display mode</string>
|
||||||
|
<string name="action_display">Display</string>
|
||||||
|
<string name="action_display_grid">Grid</string>
|
||||||
|
<string name="action_display_list">List</string>
|
||||||
<string name="action_set_filter">Set filter</string>
|
<string name="action_set_filter">Set filter</string>
|
||||||
<string name="action_cancel">Cancel</string>
|
<string name="action_cancel">Cancel</string>
|
||||||
<string name="action_sort">Sort</string>
|
<string name="action_sort">Sort</string>
|
||||||
|
|
Loading…
Reference in a new issue