From 488f81ef740e29f1f52cd103fbf18649733dbed2 Mon Sep 17 00:00:00 2001 From: arkon Date: Sat, 28 Mar 2020 17:17:21 -0400 Subject: [PATCH] Use bottom sheet for library settings --- .../tachiyomi/ui/library/LibraryController.kt | 58 ++--- .../ui/library/LibraryNavigationView.kt | 216 ---------------- .../ui/library/LibrarySettingsSheet.kt | 243 ++++++++++++++++++ .../kanade/tachiyomi/ui/main/MainActivity.kt | 7 +- .../widget/ExtendedNavigationView.kt | 3 +- .../tachiyomi/widget/SimpleNavigationView.kt | 3 +- app/src/main/res/layout/library_drawer.xml | 7 - 7 files changed, 270 insertions(+), 267 deletions(-) delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryNavigationView.kt create mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/library/LibrarySettingsSheet.kt delete mode 100644 app/src/main/res/layout/library_drawer.xml diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryController.kt index ce3836831..d383c8289 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryController.kt @@ -15,8 +15,6 @@ import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.view.ActionMode import androidx.appcompat.widget.SearchView import androidx.core.graphics.drawable.DrawableCompat -import androidx.core.view.GravityCompat -import androidx.drawerlayout.widget.DrawerLayout import com.bluelinelabs.conductor.ControllerChangeHandler import com.bluelinelabs.conductor.ControllerChangeType import com.f2prateek.rx.preferences.Preference @@ -33,19 +31,16 @@ import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.getOrDefault import eu.kanade.tachiyomi.ui.base.controller.NucleusController import eu.kanade.tachiyomi.ui.base.controller.RootController -import eu.kanade.tachiyomi.ui.base.controller.SecondaryDrawerController import eu.kanade.tachiyomi.ui.base.controller.TabbedController import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction import eu.kanade.tachiyomi.ui.main.MainActivity import eu.kanade.tachiyomi.ui.manga.MangaController import eu.kanade.tachiyomi.util.system.getResourceColor import eu.kanade.tachiyomi.util.system.toast -import eu.kanade.tachiyomi.util.view.inflate import java.io.IOException import kotlinx.android.synthetic.main.library_controller.action_toolbar import kotlinx.android.synthetic.main.library_controller.empty_view import kotlinx.android.synthetic.main.library_controller.library_pager -import kotlinx.android.synthetic.main.main_activity.drawer import kotlinx.android.synthetic.main.main_activity.tabs import rx.Subscription import timber.log.Timber @@ -58,7 +53,6 @@ class LibraryController( ) : NucleusController(bundle), RootController, TabbedController, - SecondaryDrawerController, ActionMode.Callback, ChangeMangaCategoriesDialog.Listener, DeleteLibraryMangasDialog.Listener { @@ -118,14 +112,9 @@ class LibraryController( private var adapter: LibraryAdapter? = null /** - * Navigation view containing filter/sort/display items. + * Sheet containing filter/sort/display items. */ - private var navView: LibraryNavigationView? = null - - /** - * Drawer listener to allow swipe only for closing the drawer. - */ - private var drawerListener: DrawerLayout.DrawerListener? = null + private var settingsSheet: LibrarySettingsSheet? = null private var tabsVisibilityRelay: BehaviorRelay = BehaviorRelay.create(false) @@ -169,6 +158,15 @@ class LibraryController( if (selectedMangas.isNotEmpty()) { createActionModeIfNeeded() } + + settingsSheet = LibrarySettingsSheet(activity!!) { group -> + when (group) { + is LibrarySettingsSheet.Settings.FilterGroup -> onFilterChanged() + is LibrarySettingsSheet.Settings.SortGroup -> onSortChanged() + is LibrarySettingsSheet.Settings.DisplayGroup -> reattachAdapter() + is LibrarySettingsSheet.Settings.BadgeGroup -> onDownloadBadgeChanged() + } + } } override fun onChangeStarted(handler: ControllerChangeHandler, type: ControllerChangeType) { @@ -184,32 +182,12 @@ class LibraryController( action_toolbar.destroy() adapter?.onDestroy() adapter = null + settingsSheet = null tabsVisibilitySubscription?.unsubscribe() tabsVisibilitySubscription = null super.onDestroyView(view) } - override fun createSecondaryDrawer(drawer: DrawerLayout): ViewGroup { - val view = drawer.inflate(R.layout.library_drawer) as LibraryNavigationView - navView = view - drawer.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED, GravityCompat.END) - - navView?.onGroupClicked = { group -> - when (group) { - is LibraryNavigationView.FilterGroup -> onFilterChanged() - is LibraryNavigationView.SortGroup -> onSortChanged() - is LibraryNavigationView.DisplayGroup -> reattachAdapter() - is LibraryNavigationView.BadgeGroup -> onDownloadBadgeChanged() - } - } - - return view - } - - override fun cleanupSecondaryDrawer(drawer: DrawerLayout) { - navView = null - } - override fun configureTabs(tabs: TabLayout) { with(tabs) { tabGravity = TabLayout.GRAVITY_CENTER @@ -231,6 +209,10 @@ class LibraryController( tabsVisibilitySubscription = null } + fun showSettingsSheet() { + settingsSheet?.show() + } + fun onNextLibraryUpdate(categories: List, mangaMap: Map>) { val view = view ?: return val adapter = adapter ?: return @@ -364,12 +346,12 @@ class LibraryController( } override fun onPrepareOptionsMenu(menu: Menu) { - val navView = navView ?: return + val settingsSheet = settingsSheet ?: return val filterItem = menu.findItem(R.id.action_filter) // Tint icon if there's a filter active - if (navView.hasActiveFilters()) { + if (settingsSheet.hasActiveFilters()) { val filterColor = activity!!.getResourceColor(R.attr.colorFilterActive) DrawableCompat.setTint(filterItem.icon, filterColor) } @@ -378,9 +360,7 @@ class LibraryController( override fun onOptionsItemSelected(item: MenuItem): Boolean { when (item.itemId) { R.id.action_search -> expandActionViewFromInteraction = true - R.id.action_filter -> { - navView?.let { activity?.drawer?.openDrawer(GravityCompat.END) } - } + R.id.action_filter -> showSettingsSheet() R.id.action_update_library -> { activity?.let { LibraryUpdateService.start(it) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryNavigationView.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryNavigationView.kt deleted file mode 100644 index a7eb801dd..000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryNavigationView.kt +++ /dev/null @@ -1,216 +0,0 @@ -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(), BadgeGroup()) - - /** - * 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 - addView(recycler) - - 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) : 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) - - private val completed = Item.CheckboxGroup(R.string.completed, this) - - override val items = listOf(downloaded, unread, completed) - - 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() - completed.checked = preferences.filterCompleted().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) - completed -> preferences.filterCompleted().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 total = Item.MultiSort(R.string.action_sort_total, this) - - private val lastRead = Item.MultiSort(R.string.action_sort_last_read, this) - - private val lastChecked = Item.MultiSort(R.string.action_sort_last_checked, this) - - private val unread = Item.MultiSort(R.string.action_filter_unread, this) - - private val latestChapter = Item.MultiSort(R.string.action_sort_latest_chapter, this) - - override val items = listOf(alphabetically, lastRead, lastChecked, unread, total, latestChapter) - - 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 - lastChecked.state = if (sorting == LibrarySort.LAST_CHECKED) order else SORT_NONE - unread.state = if (sorting == LibrarySort.UNREAD) order else SORT_NONE - total.state = if (sorting == LibrarySort.TOTAL) order else SORT_NONE - latestChapter.state = if (sorting == LibrarySort.LATEST_CHAPTER) 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 - lastChecked -> LibrarySort.LAST_CHECKED - unread -> LibrarySort.UNREAD - total -> LibrarySort.TOTAL - latestChapter -> LibrarySort.LATEST_CHAPTER - else -> throw Exception("Unknown sorting") - }) - preferences.librarySortingAscending().set(item.state == SORT_ASC) - - item.group.items.forEach { adapter.notifyItemChanged(it) } - } - } - - inner class BadgeGroup : Group { - private val downloadBadge = Item.CheckboxGroup(R.string.action_display_download_badge, this) - override val header = null - override val footer = null - override val items = listOf(downloadBadge) - override fun initModels() { - downloadBadge.checked = preferences.downloadBadge().getOrDefault() - } - - override fun onItemClicked(item: Item) { - item as Item.CheckboxGroup - item.checked = !item.checked - preferences.downloadBadge().set((item.checked)) - adapter.notifyItemChanged(item) - } - } - - /** - * 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(item == list) - - item.group.items.forEach { adapter.notifyItemChanged(it) } - } - } -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibrarySettingsSheet.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibrarySettingsSheet.kt new file mode 100644 index 000000000..5b035b87a --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibrarySettingsSheet.kt @@ -0,0 +1,243 @@ +package eu.kanade.tachiyomi.ui.library + +import android.app.Activity +import android.content.Context +import android.util.AttributeSet +import com.google.android.material.bottomsheet.BottomSheetDialog +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 uy.kohesive.injekt.injectLazy + +class LibrarySettingsSheet( + activity: Activity, + onGroupClickListener: (ExtendedNavigationView.Group) -> Unit +) : BottomSheetDialog(activity) { + + private var navView: Settings + + init { + navView = Settings(activity) + navView.onGroupClicked = onGroupClickListener + + setContentView(navView) + } + + fun hasActiveFilters(): Boolean { + return navView.hasActiveFilters() + } + + /** + * The navigation view shown in the sheet with the different options to show the library. + */ + class Settings @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : + ExtendedNavigationView(context, attrs) { + + private val preferences: PreferencesHelper by injectLazy() + + /** + * List of groups shown in the view. + */ + private val groups = listOf(FilterGroup(), SortGroup(), DisplayGroup(), BadgeGroup()) + + /** + * 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 + addView(recycler) + + 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) : 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) + + private val completed = Item.CheckboxGroup(R.string.completed, this) + + override val items = listOf(downloaded, unread, completed) + + 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() + completed.checked = preferences.filterCompleted().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) + completed -> preferences.filterCompleted().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 total = Item.MultiSort(R.string.action_sort_total, this) + + private val lastRead = Item.MultiSort(R.string.action_sort_last_read, this) + + private val lastChecked = Item.MultiSort(R.string.action_sort_last_checked, this) + + private val unread = Item.MultiSort(R.string.action_filter_unread, this) + + private val latestChapter = Item.MultiSort(R.string.action_sort_latest_chapter, this) + + override val items = + listOf(alphabetically, lastRead, lastChecked, unread, total, latestChapter) + + 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()) + Item.MultiSort.SORT_ASC else Item.MultiSort.SORT_DESC + + alphabetically.state = + if (sorting == LibrarySort.ALPHA) order else Item.MultiSort.SORT_NONE + lastRead.state = + if (sorting == LibrarySort.LAST_READ) order else Item.MultiSort.SORT_NONE + lastChecked.state = + if (sorting == LibrarySort.LAST_CHECKED) order else Item.MultiSort.SORT_NONE + unread.state = + if (sorting == LibrarySort.UNREAD) order else Item.MultiSort.SORT_NONE + total.state = if (sorting == LibrarySort.TOTAL) order else Item.MultiSort.SORT_NONE + latestChapter.state = + if (sorting == LibrarySort.LATEST_CHAPTER) order else Item.MultiSort.SORT_NONE + } + + override fun onItemClicked(item: Item) { + item as Item.MultiStateGroup + val prevState = item.state + + item.group.items.forEach { + (it as Item.MultiStateGroup).state = + Item.MultiSort.SORT_NONE + } + item.state = when (prevState) { + Item.MultiSort.SORT_NONE -> Item.MultiSort.SORT_ASC + Item.MultiSort.SORT_ASC -> Item.MultiSort.SORT_DESC + Item.MultiSort.SORT_DESC -> Item.MultiSort.SORT_ASC + else -> throw Exception("Unknown state") + } + + preferences.librarySortingMode().set( + when (item) { + alphabetically -> LibrarySort.ALPHA + lastRead -> LibrarySort.LAST_READ + lastChecked -> LibrarySort.LAST_CHECKED + unread -> LibrarySort.UNREAD + total -> LibrarySort.TOTAL + latestChapter -> LibrarySort.LATEST_CHAPTER + else -> throw Exception("Unknown sorting") + } + ) + preferences.librarySortingAscending().set(item.state == Item.MultiSort.SORT_ASC) + + item.group.items.forEach { adapter.notifyItemChanged(it) } + } + } + + inner class BadgeGroup : Group { + private val downloadBadge = + Item.CheckboxGroup(R.string.action_display_download_badge, this) + override val header = null + override val footer = null + override val items = listOf(downloadBadge) + override fun initModels() { + downloadBadge.checked = preferences.downloadBadge().getOrDefault() + } + + override fun onItemClicked(item: Item) { + item as Item.CheckboxGroup + item.checked = !item.checked + preferences.downloadBadge().set((item.checked)) + adapter.notifyItemChanged(item) + } + } + + /** + * 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(item == list) + + item.group.items.forEach { adapter.notifyItemChanged(it) } + } + } + } +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt index 0345cc1d9..7a934c36e 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt @@ -86,7 +86,12 @@ class MainActivity : BaseActivity() { R.id.nav_more -> setRoot(MoreController(), id) } } else { - router.popToRoot() + when (id) { + R.id.nav_library -> { + val controller = router.getControllerWithTag(id.toString()) as? LibraryController + controller?.showSettingsSheet() + } + } } true } diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/ExtendedNavigationView.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/ExtendedNavigationView.kt index 3241b0172..286e14e6e 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/widget/ExtendedNavigationView.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/widget/ExtendedNavigationView.kt @@ -20,8 +20,7 @@ open class ExtendedNavigationView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 -) : - SimpleNavigationView(context, attrs, defStyleAttr) { +) : SimpleNavigationView(context, attrs, defStyleAttr) { /** * Every item of the nav view. Generic items must belong to this list, custom items could be diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/SimpleNavigationView.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/SimpleNavigationView.kt index 560e4bd4c..e55b89611 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/widget/SimpleNavigationView.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/widget/SimpleNavigationView.kt @@ -28,8 +28,7 @@ open class SimpleNavigationView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 -) : - ScrimInsetsFrameLayout(context, attrs, defStyleAttr) { +) : ScrimInsetsFrameLayout(context, attrs, defStyleAttr) { /** * Max width of the navigation view. diff --git a/app/src/main/res/layout/library_drawer.xml b/app/src/main/res/layout/library_drawer.xml deleted file mode 100644 index 0be0ac506..000000000 --- a/app/src/main/res/layout/library_drawer.xml +++ /dev/null @@ -1,7 +0,0 @@ - -