Jump to category using the backdrop on the top of the library

Along with only showing a single category at a time
pull/3117/head
Jay 4 years ago
parent 49b18181e7
commit 20bab59df3

@ -263,6 +263,8 @@ class PreferencesHelper(val context: Context) {
fun deleteRemovedChapters() = flowPrefs.getInt(Keys.deleteRemovedChapters, 0) fun deleteRemovedChapters() = flowPrefs.getInt(Keys.deleteRemovedChapters, 0)
fun showAllCategories() = flowPrefs.getBoolean("show_all_categories", true)
// Tutorial preferences // Tutorial preferences
fun shownFilterTutorial() = flowPrefs.getBoolean("shown_filter_tutorial", false) fun shownFilterTutorial() = flowPrefs.getBoolean("shown_filter_tutorial", false)

@ -21,8 +21,8 @@ import kotlin.math.max
* *
* @param view the fragment containing this adapter. * @param view the fragment containing this adapter.
*/ */
class LibraryCategoryAdapter(val libraryListener: LibraryListener) : class LibraryCategoryAdapter(val controller: LibraryController) :
FlexibleAdapter<IFlexible<*>>(null, libraryListener, true) { FlexibleAdapter<IFlexible<*>>(null, controller, true) {
init { init {
setDisplayHeadersAtStartUp(true) setDisplayHeadersAtStartUp(true)
@ -32,6 +32,8 @@ class LibraryCategoryAdapter(val libraryListener: LibraryListener) :
*/ */
private var mangas: List<LibraryItem> = emptyList() private var mangas: List<LibraryItem> = emptyList()
val libraryListener: LibraryListener = controller
/** /**
* Sets a list of manga in the adapter. * Sets a list of manga in the adapter.
* *
@ -97,13 +99,18 @@ class LibraryCategoryAdapter(val libraryListener: LibraryListener) :
val preferences: PreferencesHelper by injectLazy() val preferences: PreferencesHelper by injectLazy()
val db: DatabaseHelper by injectLazy() val db: DatabaseHelper by injectLazy()
if (position == itemCount - 1) return "-" if (position == itemCount - 1) return "-"
val sorting = if (preferences.hideCategories().getOrDefault()) val sorting = if (!preferences.showAllCategories().get()) {
preferences.hideCategories().getOrDefault() controller.presenter.getCurrentCategory()?.sortingMode() ?: LibrarySort.DRAG_AND_DROP
else (headerItems.firstOrNull() as? LibraryHeaderItem)?.category?.sortingMode() } else if (preferences.hideCategories().getOrDefault()) {
?: LibrarySort.DRAG_AND_DROP preferences.librarySortingMode().getOrDefault()
} else {
(headerItems.firstOrNull() as? LibraryHeaderItem)?.category?.sortingMode()
?: LibrarySort.DRAG_AND_DROP
}
return when (val item: IFlexible<*>? = getItem(position)) { return when (val item: IFlexible<*>? = getItem(position)) {
is LibraryHeaderItem -> is LibraryHeaderItem ->
if (preferences.hideCategories().getOrDefault() || item.category.id == 0) null if (preferences.hideCategories().getOrDefault() || item.category.id == 0 ||
!preferences.showAllCategories().get()) null
else getFirstChar(item.category.name) + else getFirstChar(item.category.name) +
"\u200B".repeat(max(0, item.category.order)) "\u200B".repeat(max(0, item.category.order))
is LibraryItem -> { is LibraryItem -> {
@ -123,11 +130,11 @@ class LibraryCategoryAdapter(val libraryListener: LibraryListener) :
} }
LibrarySort.TOTAL -> { LibrarySort.TOTAL -> {
val unread = item.chapterCount val unread = item.chapterCount
(unread / 100).toString() getShortRange(unread)
} }
LibrarySort.UNREAD -> { LibrarySort.UNREAD -> {
val unread = item.manga.unread val unread = item.manga.unread
if (unread > 0) (unread / 100).toString() if (unread > 0) getShortRange(unread)
else "R" else "R"
} }
LibrarySort.LATEST_CHAPTER -> { LibrarySort.LATEST_CHAPTER -> {
@ -168,7 +175,9 @@ class LibraryCategoryAdapter(val libraryListener: LibraryListener) :
else recyclerView.context.getString(R.string.top) else recyclerView.context.getString(R.string.top)
is LibraryItem -> { is LibraryItem -> {
if (iFlexible.manga.isBlank()) "" if (iFlexible.manga.isBlank()) ""
else when (preferences.librarySortingMode().getOrDefault()) { else when (if (!preferences.showAllCategories().get()) {
controller.presenter.getCurrentCategory()?.sortingMode() ?: LibrarySort.DRAG_AND_DROP
} else preferences.librarySortingMode().getOrDefault()) {
LibrarySort.DRAG_AND_DROP -> { LibrarySort.DRAG_AND_DROP -> {
if (!preferences.hideCategories().getOrDefault()) { if (!preferences.hideCategories().getOrDefault()) {
val title = iFlexible.manga.title val title = iFlexible.manga.title
@ -225,18 +234,36 @@ class LibraryCategoryAdapter(val libraryListener: LibraryListener) :
} }
} }
private fun getShortRange(value: Int): String {
return when (value) {
1 -> "1"
2 -> "2"
3 -> "3"
4 -> "4"
5 -> "5"
in 6..10 -> "6"
in 11..50 -> "10"
in 51..100 -> "50"
in 101..500 -> "1+"
in 499..899 -> "4+"
in 901..Int.MAX_VALUE -> "9+"
else -> "0"
}
}
private fun getRange(value: Int): String { private fun getRange(value: Int): String {
return when (value) { return when (value) {
in 1..99 -> "< 100" 1 -> "1"
in 100..199 -> "100-199" 2 -> "2"
in 200..299 -> "200-299" 3 -> "3"
in 300..399 -> "300-399" 4 -> "4"
in 400..499 -> "400-499" 5 -> "5"
in 500..599 -> "500-599" in 6..10 -> "6-10"
in 600..699 -> "600-699" in 11..50 -> "11-50"
in 700..799 -> "700-799" in 51..100 -> "51-100"
in 800..899 -> "800-899" in 101..500 -> "100-500"
in 900..Int.MAX_VALUE -> "900+" in 499..899 -> "499-900"
in 901..Int.MAX_VALUE -> "900+"
else -> "None" else -> "None"
} }
} }

@ -1,5 +1,6 @@
package eu.kanade.tachiyomi.ui.library package eu.kanade.tachiyomi.ui.library
import android.animation.ValueAnimator
import android.app.Activity import android.app.Activity
import android.content.Context import android.content.Context
import android.graphics.Color import android.graphics.Color
@ -16,6 +17,7 @@ import android.view.inputmethod.InputMethodManager
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.view.ActionMode import androidx.appcompat.view.ActionMode
import androidx.appcompat.widget.SearchView import androidx.appcompat.widget.SearchView
import androidx.recyclerview.widget.DefaultItemAnimator
import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
@ -56,10 +58,11 @@ import eu.kanade.tachiyomi.util.system.dpToPxEnd
import eu.kanade.tachiyomi.util.system.getResourceColor import eu.kanade.tachiyomi.util.system.getResourceColor
import eu.kanade.tachiyomi.util.system.launchUI import eu.kanade.tachiyomi.util.system.launchUI
import eu.kanade.tachiyomi.util.view.applyWindowInsetsForRootController import eu.kanade.tachiyomi.util.view.applyWindowInsetsForRootController
import eu.kanade.tachiyomi.util.view.doOnApplyWindowInsets
import eu.kanade.tachiyomi.util.view.getItemView import eu.kanade.tachiyomi.util.view.getItemView
import eu.kanade.tachiyomi.util.view.gone import eu.kanade.tachiyomi.util.view.gone
import eu.kanade.tachiyomi.util.view.hide import eu.kanade.tachiyomi.util.view.hide
import eu.kanade.tachiyomi.util.view.scrollViewWith import eu.kanade.tachiyomi.util.view.marginTop
import eu.kanade.tachiyomi.util.view.setBackground import eu.kanade.tachiyomi.util.view.setBackground
import eu.kanade.tachiyomi.util.view.setOnQueryTextChangeListener import eu.kanade.tachiyomi.util.view.setOnQueryTextChangeListener
import eu.kanade.tachiyomi.util.view.setStartTranslationX import eu.kanade.tachiyomi.util.view.setStartTranslationX
@ -68,6 +71,7 @@ import eu.kanade.tachiyomi.util.view.show
import eu.kanade.tachiyomi.util.view.snack import eu.kanade.tachiyomi.util.view.snack
import eu.kanade.tachiyomi.util.view.updateLayoutParams import eu.kanade.tachiyomi.util.view.updateLayoutParams
import eu.kanade.tachiyomi.util.view.updatePaddingRelative import eu.kanade.tachiyomi.util.view.updatePaddingRelative
import eu.kanade.tachiyomi.util.view.visibleIf
import eu.kanade.tachiyomi.util.view.withFadeTransaction import eu.kanade.tachiyomi.util.view.withFadeTransaction
import kotlinx.android.synthetic.main.filter_bottom_sheet.* import kotlinx.android.synthetic.main.filter_bottom_sheet.*
import kotlinx.android.synthetic.main.library_grid_recycler.* import kotlinx.android.synthetic.main.library_grid_recycler.*
@ -142,7 +146,8 @@ class LibraryController(
private var scrollAnim: ViewPropertyAnimator? = null private var scrollAnim: ViewPropertyAnimator? = null
private var alwaysShowScroller: Boolean = preferences.alwaysShowSeeker().getOrDefault() private var alwaysShowScroller: Boolean = preferences.alwaysShowSeeker().getOrDefault()
private var filterTooltip: ViewTooltip? = null private var filterTooltip: ViewTooltip? = null
private var elevationAnim: ValueAnimator? = null
private var elevate = false
override fun getTitle(): String? { override fun getTitle(): String? {
return view?.context?.getString(R.string.library) return view?.context?.getString(R.string.library)
} }
@ -150,6 +155,8 @@ class LibraryController(
private var scrollListener = object : RecyclerView.OnScrollListener() { private var scrollListener = object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy) super.onScrolled(recyclerView, dx, dy)
val notAtTop = recycler.canScrollVertically(-1)
if (notAtTop != elevate) elevateFunc(notAtTop)
val order = getCategoryOrder() val order = getCategoryOrder()
if (filter_bottom_sheet.sheetBehavior?.state != BottomSheetBehavior.STATE_HIDDEN) { if (filter_bottom_sheet.sheetBehavior?.state != BottomSheetBehavior.STATE_HIDDEN) {
scrollDistance += abs(dy) scrollDistance += abs(dy)
@ -161,6 +168,7 @@ class LibraryController(
if (order != null && order != activeCategory && lastItem == null) { if (order != null && order != activeCategory && lastItem == null) {
preferences.lastUsedCategory().set(order) preferences.lastUsedCategory().set(order)
activeCategory = order activeCategory = order
setActiveCategory()
if (presenter.categories.size > 1 && dy != 0) { if (presenter.categories.size > 1 && dy != 0) {
val headerItem = getHeader() ?: return val headerItem = getHeader() ?: return
val view = fast_scroller?.getChildAt(0) ?: return val view = fast_scroller?.getChildAt(0) ?: return
@ -226,6 +234,12 @@ class LibraryController(
fast_scroller.setStartTranslationX(!alwaysShowScroller) fast_scroller.setStartTranslationX(!alwaysShowScroller)
fast_scroller.setBackground(!alwaysShowScroller) fast_scroller.setBackground(!alwaysShowScroller)
show_all.isChecked = preferences.showAllCategories().get()
show_all.setOnCheckedChangeListener { _, isChecked ->
preferences.showAllCategories().set(isChecked)
presenter.getLibrary()
}
adapter = LibraryCategoryAdapter(this) adapter = LibraryCategoryAdapter(this)
adapter.expandItemsAtStartUp() adapter.expandItemsAtStartUp()
adapter.isRecursiveCollapse = true adapter.isRecursiveCollapse = true
@ -243,7 +257,7 @@ class LibraryController(
recycler.adapter = adapter recycler.adapter = adapter
fast_scroller.setupWithRecyclerView(recycler, { position -> fast_scroller.setupWithRecyclerView(recycler, { position ->
val letter = adapter.getSectionText(position) val letter = adapter.getSectionText(position)
if (!singleCategory && if (!singleCategory && presenter.showAllCategories &&
!adapter.isHeader(adapter.getItem(position)) && !adapter.isHeader(adapter.getItem(position)) &&
position != adapter.itemCount - 1) null position != adapter.itemCount - 1) null
else if (letter != null) FastScrollItemIndicator.Text(letter) else if (letter != null) FastScrollItemIndicator.Text(letter)
@ -287,8 +301,7 @@ class LibraryController(
appbar?.y = 0f appbar?.y = 0f
recycler.suppressLayout(true) recycler.suppressLayout(true)
(recycler.layoutManager as LinearLayoutManager).scrollToPositionWithOffset( (recycler.layoutManager as LinearLayoutManager).scrollToPositionWithOffset(
itemPosition, itemPosition, 0
if (singleCategory) 0 else (if (itemPosition == 0) 0 else (-40).dpToPx)
) )
recycler.suppressLayout(false) recycler.suppressLayout(false)
} }
@ -298,16 +311,43 @@ class LibraryController(
val tv = TypedValue() val tv = TypedValue()
activity!!.theme.resolveAttribute(R.attr.actionBarTintColor, tv, true) activity!!.theme.resolveAttribute(R.attr.actionBarTintColor, tv, true)
swipe_refresh.setStyle() swipe_refresh.setStyle()
scrollViewWith(recycler, swipeRefreshLayout = swipe_refresh, afterInsets = { insets ->
recycler_cover.setOnClickListener {
showCategories(false)
}
category_recycler.onCategoryClicked = {
scrollToHeader(it)
showCategories(false)
}
swipe_refresh.setDistanceToTriggerSync(150.dpToPx)
val marginTop = category_layout.marginTop
recycler.doOnApplyWindowInsets { recyclerView, insets, _ ->
recyclerView.updateLayoutParams<ViewGroup.MarginLayoutParams> {
topMargin = marginTop + insets.systemWindowInsetTop
}
category_layout?.updateLayoutParams<ViewGroup.MarginLayoutParams> {
topMargin = marginTop + insets.systemWindowInsetTop
}
recycler_cover?.updateLayoutParams<ViewGroup.MarginLayoutParams> {
topMargin = marginTop + insets.systemWindowInsetTop
}
fast_scroller?.updateLayoutParams<ViewGroup.MarginLayoutParams> { fast_scroller?.updateLayoutParams<ViewGroup.MarginLayoutParams> {
topMargin = insets.systemWindowInsetTop topMargin = insets.systemWindowInsetTop
} }
}) swipe_refresh?.setProgressViewOffset(
true, (marginTop + insets.systemWindowInsetTop) + (-60).dpToPx, marginTop + insets.systemWindowInsetTop + 10.dpToPx
)
}
swipe_refresh.setOnRefreshListener { swipe_refresh.setOnRefreshListener {
swipe_refresh.isRefreshing = false swipe_refresh.isRefreshing = false
if (!LibraryUpdateService.isRunning()) { if (!LibraryUpdateService.isRunning()) {
when { when {
!presenter.showAllCategories -> {
presenter.categories.find { it.id == presenter.currentCategory }?.let {
updateLibrary(it)
}
}
presenter.allCategories.size <= 1 -> updateLibrary() presenter.allCategories.size <= 1 -> updateLibrary()
preferences.updateOnRefresh().getOrDefault() == -1 -> { preferences.updateOnRefresh().getOrDefault() == -1 -> {
MaterialDialog(activity!!).title(R.string.what_should_update) MaterialDialog(activity!!).title(R.string.what_should_update)
@ -357,7 +397,7 @@ class LibraryController(
presenter.onRestore() presenter.onRestore()
if (presenter.libraryItems.isNotEmpty()) { if (presenter.libraryItems.isNotEmpty()) {
onNextLibraryUpdate(presenter.libraryItems, true) presenter.restoreLibrary()
} else { } else {
recycler_layout.alpha = 0f recycler_layout.alpha = 0f
presenter.getLibrary() presenter.getLibrary()
@ -453,7 +493,13 @@ class LibraryController(
presenter.getLibrary() presenter.getLibrary()
DownloadService.callListeners() DownloadService.callListeners()
LibraryUpdateService.setListener(this) LibraryUpdateService.setListener(this)
} else closeTip() recycler_cover.isClickable = false
recycler_cover.isFocusable = false
activity?.dropdown?.visibleIf(!singleCategory)
} else {
closeTip()
activity?.dropdown?.gone()
}
} }
override fun onActivityResumed(activity: Activity) { override fun onActivityResumed(activity: Activity) {
@ -482,7 +528,7 @@ class LibraryController(
} }
fun onNextLibraryUpdate(mangaMap: List<LibraryItem>, freshStart: Boolean = false) { fun onNextLibraryUpdate(mangaMap: List<LibraryItem>, freshStart: Boolean = false) {
if (view == null) return val view = view ?: return
destroyActionModeIfNeeded() destroyActionModeIfNeeded()
if (mangaMap.isNotEmpty()) { if (mangaMap.isNotEmpty()) {
empty_view?.hide() empty_view?.hide()
@ -494,8 +540,12 @@ class LibraryController(
) )
} }
adapter.setItems(mangaMap) adapter.setItems(mangaMap)
if (recycler.itemAnimator == null)
recycler.post {
recycler.itemAnimator = DefaultItemAnimator()
}
singleCategory = presenter.categories.size <= 1 singleCategory = presenter.categories.size <= 1
activity?.dropdown?.visibleIf(!singleCategory)
progress.gone() progress.gone()
if (!freshStart) { if (!freshStart) {
justStarted = false justStarted = false
@ -506,15 +556,69 @@ class LibraryController(
scrollToHeader(activeCategory) scrollToHeader(activeCategory)
if (!alwaysShowScroller) { if (!alwaysShowScroller) {
fast_scroller?.show(false) fast_scroller?.show(false)
view?.post { view.post {
scrollAnim = fast_scroller?.hide(2000) scrollAnim = fast_scroller?.hide(2000)
} }
} }
} }
adapter.isLongPressDragEnabled = canDrag() adapter.isLongPressDragEnabled = canDrag()
category_recycler.setCategories(presenter.categories)
setActiveCategory()
activity?.toolbar?.setOnClickListener {
val recycler = recycler ?: return@setOnClickListener
if (singleCategory) {
recycler.scrollToPosition(0)
} else {
showCategories(recycler.translationY == 0f)
}
}
}
private fun showCategories(show: Boolean) {
recycler_cover.isClickable = show
recycler_cover.isFocusable = show
val translateY = if (show) category_layout.height.toFloat() + 12f.dpToPx else 0f
recycler.animate().translationY(translateY).start()
recycler_cover.animate().translationY(translateY).start()
recycler_cover.animate().alpha(if (show) 0.75f else 0f).start()
if (show) {
elevateFunc(false)
activity?.dropdown?.setImageResource(R.drawable.ic_arrow_drop_up_24dp)
} else {
val notAtTop = recycler.canScrollVertically(-1)
if (notAtTop != elevate) elevateFunc(notAtTop)
activity?.dropdown?.setImageResource(R.drawable.ic_arrow_drop_down_24dp)
}
}
private fun elevateFunc(el: Boolean) {
elevate = el
elevationAnim?.cancel()
elevationAnim = ValueAnimator.ofFloat(
activity?.appbar?.elevation ?: 0f, if (el) 15f else 0f
)
elevationAnim?.addUpdateListener { valueAnimator ->
activity?.appbar?.elevation = valueAnimator.animatedValue as Float
}
elevationAnim?.start()
}
fun setActiveCategory() {
val currentCategory = presenter.categories.indexOfFirst {
if (presenter.showAllCategories) it.order == activeCategory else presenter.currentCategory == it.id
}
category_recycler.setCategories(currentCategory)
} }
private fun scrollToHeader(pos: Int) { private fun scrollToHeader(pos: Int) {
if (!presenter.showAllCategories) {
recycler.itemAnimator = null
presenter.switchSection(pos)
activeCategory = pos
setActiveCategory()
recycler.scrollToPosition(0)
return
}
val headerPosition = adapter.indexOf(pos) val headerPosition = adapter.indexOf(pos)
if (headerPosition > -1) { if (headerPosition > -1) {
val appbar = activity?.appbar val appbar = activity?.appbar
@ -523,7 +627,7 @@ class LibraryController(
view?.rootWindowInsets?.systemWindowInsetTop ?: 0 view?.rootWindowInsets?.systemWindowInsetTop ?: 0
) ?: 0f).roundToInt() + 30.dpToPx ) ?: 0f).roundToInt() + 30.dpToPx
(recycler.layoutManager as LinearLayoutManager).scrollToPositionWithOffset( (recycler.layoutManager as LinearLayoutManager).scrollToPositionWithOffset(
headerPosition, (if (headerPosition == 0) 0 else (-40).dpToPx) + appbarOffset headerPosition, (if (headerPosition == 0) 0 else (-32).dpToPx) + appbarOffset
) )
recycler.suppressLayout(false) recycler.suppressLayout(false)
} }
@ -815,6 +919,10 @@ class LibraryController(
} }
override fun toggleCategoryVisibility(position: Int) { override fun toggleCategoryVisibility(position: Int) {
if (!presenter.showAllCategories) {
showCategories(true)
return
}
val catId = (adapter.getItem(position) as? LibraryHeaderItem)?.category?.id ?: return val catId = (adapter.getItem(position) as? LibraryHeaderItem)?.category?.id ?: return
presenter.toggleCategoryVisibility(catId) presenter.toggleCategoryVisibility(catId)
} }
@ -842,6 +950,7 @@ class LibraryController(
return items.all { adapter.isSelected(it) } return items.all { adapter.isSelected(it) }
} }
//region sheet methods
override fun showSheet() { override fun showSheet() {
closeTip() closeTip()
when { when {
@ -875,6 +984,7 @@ class LibraryController(
} }
return false return false
} }
//endregion
//region Toolbar options methods //region Toolbar options methods
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {

@ -36,6 +36,7 @@ import eu.kanade.tachiyomi.util.view.invisible
import eu.kanade.tachiyomi.util.view.updateLayoutParams import eu.kanade.tachiyomi.util.view.updateLayoutParams
import eu.kanade.tachiyomi.util.view.visInvisIf import eu.kanade.tachiyomi.util.view.visInvisIf
import eu.kanade.tachiyomi.util.view.visible import eu.kanade.tachiyomi.util.view.visible
import eu.kanade.tachiyomi.util.view.visibleIf
import kotlinx.android.synthetic.main.library_category_header_item.* import kotlinx.android.synthetic.main.library_category_header_item.*
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
@ -184,16 +185,16 @@ class LibraryHeaderItem(
updateButton.gone() updateButton.gone()
} }
LibraryUpdateService.categoryInQueue(category.id) -> { LibraryUpdateService.categoryInQueue(category.id) -> {
expandImage.visible() expandImage.visibleIf(adapter.headerItems.size > 1)
checkboxImage.gone() checkboxImage.gone()
catProgress.visible() catProgress.visible()
updateButton.invisible() updateButton.invisible()
} }
else -> { else -> {
expandImage.visible() expandImage.visibleIf(adapter.headerItems.size > 1)
catProgress.gone() catProgress.gone()
checkboxImage.gone() checkboxImage.gone()
updateButton.visInvisIf(category.id ?: 0 > -1) updateButton.visInvisIf(category.id ?: 0 > -1 && adapter.headerItems.size > 1)
} }
} }
} }

@ -60,7 +60,12 @@ class LibraryPresenter(
/** List of all manga to update the */ /** List of all manga to update the */
var libraryItems: List<LibraryItem> = emptyList() var libraryItems: List<LibraryItem> = emptyList()
private var sectionedLibraryItems: Map<Int, List<LibraryItem>> = emptyMap()
var currentCategory = -1
private set
private var allLibraryItems: List<LibraryItem> = emptyList() private var allLibraryItems: List<LibraryItem> = emptyList()
val showAllCategories
get() = preferences.showAllCategories().get()
private var totalChapters: Map<Long, Int>? = null private var totalChapters: Map<Long, Int>? = null
@ -92,16 +97,56 @@ class LibraryPresenter(
mangaMap = applyFilters(mangaMap) mangaMap = applyFilters(mangaMap)
mangaMap = applySort(mangaMap) mangaMap = applySort(mangaMap)
val freshStart = libraryItems.isEmpty() val freshStart = libraryItems.isEmpty()
libraryItems = mangaMap sectionLibrary(mangaMap, freshStart)
withContext(Dispatchers.Main) {
view.onNextLibraryUpdate(libraryItems, freshStart)
}
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
setTotalChapters() setTotalChapters()
} }
} }
} }
fun getCurrentCategory() = categories.find { it.id == currentCategory }
fun switchSection(order: Int) {
preferences.lastUsedCategory().set(order)
val category = categories.find { it.order == order }?.id ?: return
currentCategory = category
view.onNextLibraryUpdate(sectionedLibraryItems[currentCategory] ?: emptyList())
}
fun restoreLibrary() {
val items = libraryItems
val show = showAllCategories || preferences.hideCategories().getOrDefault()
if (!show) {
sectionedLibraryItems = items.groupBy { it.manga.category }
if (currentCategory == -1) currentCategory = categories.find {
it.order == preferences.lastUsedCategory().getOrDefault()
}?.id ?: 0
}
view.onNextLibraryUpdate(
if (!show) sectionedLibraryItems[currentCategory]
?: sectionedLibraryItems[categories.first().id] ?: emptyList()
else libraryItems, true
)
}
private suspend fun sectionLibrary(items: List<LibraryItem>, freshStart: Boolean = false) {
libraryItems = items
val show = showAllCategories || preferences.hideCategories().getOrDefault()
if (!show) {
sectionedLibraryItems = items.groupBy { it.manga.category }
if (currentCategory == -1) currentCategory = categories.find {
it.order == preferences.lastUsedCategory().getOrDefault()
}?.id ?: 0
}
withContext(Dispatchers.Main) {
view.onNextLibraryUpdate(
if (!show) sectionedLibraryItems[currentCategory]
?: sectionedLibraryItems[categories.first().id] ?: emptyList()
else libraryItems, freshStart
)
}
}
/** /**
* Applies library filters to the given list of manga. * Applies library filters to the given list of manga.
* *
@ -402,6 +447,7 @@ class LibraryPresenter(
val showCategories = !preferences.hideCategories().getOrDefault() val showCategories = !preferences.hideCategories().getOrDefault()
var libraryManga = db.getLibraryMangas().executeAsBlocking() var libraryManga = db.getLibraryMangas().executeAsBlocking()
val seekPref = preferences.alwaysShowSeeker() val seekPref = preferences.alwaysShowSeeker()
val showAll = showAllCategories
if (!showCategories) libraryManga = libraryManga.distinctBy { it.id } if (!showCategories) libraryManga = libraryManga.distinctBy { it.id }
val categoryAll = Category.createAll( val categoryAll = Category.createAll(
context, context,
@ -430,12 +476,13 @@ class LibraryPresenter(
if (showCategories) { if (showCategories) {
categories.forEach { category -> categories.forEach { category ->
val catId = category.id ?: return@forEach val catId = category.id ?: return@forEach
if (catId > 0 && !categorySet.contains(catId) && catId !in categoriesHidden) { if (catId > 0 && !categorySet.contains(catId) &&
(catId !in categoriesHidden || !showAll)) {
val headerItem = headerItems[catId] val headerItem = headerItems[catId]
if (headerItem != null) items.add( if (headerItem != null) items.add(
LibraryItem(LibraryManga.createBlank(catId), headerItem) LibraryItem(LibraryManga.createBlank(catId), headerItem)
) )
} else if (catId in categoriesHidden) { } else if (catId in categoriesHidden && showAll) {
val mangaToRemove = items.filter { it.manga.category == catId } val mangaToRemove = items.filter { it.manga.category == catId }
val mergedTitle = mangaToRemove.joinToString("-") { val mergedTitle = mangaToRemove.joinToString("-") {
it.manga.title + "-" + it.manga.author it.manga.title + "-" + it.manga.author
@ -450,7 +497,7 @@ class LibraryPresenter(
} }
categories.forEach { categories.forEach {
it.isHidden = it.id in categoriesHidden it.isHidden = it.id in categoriesHidden && showAll
} }
this.allCategories = categories this.allCategories = categories
@ -476,10 +523,7 @@ class LibraryPresenter(
var mangaMap = allLibraryItems var mangaMap = allLibraryItems
mangaMap = applyFilters(mangaMap) mangaMap = applyFilters(mangaMap)
mangaMap = applySort(mangaMap) mangaMap = applySort(mangaMap)
libraryItems = mangaMap sectionLibrary(mangaMap)
withContext(Dispatchers.Main) {
view.onNextLibraryUpdate(libraryItems)
}
} }
} }
@ -491,10 +535,7 @@ class LibraryPresenter(
allLibraryItems = mangaMap allLibraryItems = mangaMap
val current = libraryItems val current = libraryItems
setDownloadCount(current) setDownloadCount(current)
libraryItems = current sectionLibrary(current)
withContext(Dispatchers.Main) {
view.onNextLibraryUpdate(libraryItems)
}
} }
} }
@ -506,10 +547,7 @@ class LibraryPresenter(
allLibraryItems = mangaMap allLibraryItems = mangaMap
val current = libraryItems val current = libraryItems
setUnreadBadge(current) setUnreadBadge(current)
libraryItems = current sectionLibrary(current)
withContext(Dispatchers.Main) {
view.onNextLibraryUpdate(libraryItems)
}
} }
} }
@ -518,10 +556,7 @@ class LibraryPresenter(
scope.launch { scope.launch {
var mangaMap = libraryItems var mangaMap = libraryItems
mangaMap = applySort(mangaMap) mangaMap = applySort(mangaMap)
libraryItems = mangaMap sectionLibrary(mangaMap)
withContext(Dispatchers.Main) {
view.onNextLibraryUpdate(libraryItems)
}
} }
} }

@ -0,0 +1,36 @@
package eu.kanade.tachiyomi.ui.library.category
import android.view.View
import android.widget.TextView
import com.mikepenz.fastadapter.FastAdapter
import com.mikepenz.fastadapter.items.AbstractItem
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Category
class CategoryItem(val category: Category) :
AbstractItem<CategoryItem.ViewHolder>() {
/** defines the type defining this item. must be unique. preferably an id */
override val type: Int = R.id.category_text
/** defines the layout which will be used for this item in the list */
override val layoutRes: Int = R.layout.catergory_text_view
override var identifier = category.id?.toLong() ?: -1L
override fun getViewHolder(v: View): ViewHolder {
return ViewHolder(v)
}
class ViewHolder(view: View) : FastAdapter.ViewHolder<CategoryItem>(view) {
val categoryTitle: TextView = view.findViewById(R.id.category_text)
override fun bindView(item: CategoryItem, payloads: List<Any>) {
categoryTitle.text = item.category.name
}
override fun unbindView(item: CategoryItem) {
categoryTitle.text = null
}
}
}

@ -0,0 +1,70 @@
package eu.kanade.tachiyomi.ui.library.category
import android.content.Context
import android.util.AttributeSet
import android.view.ViewGroup
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.mikepenz.fastadapter.FastAdapter
import com.mikepenz.fastadapter.adapters.ItemAdapter
import com.mikepenz.fastadapter.listeners.OnBindViewHolderListenerImpl
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Category
import eu.kanade.tachiyomi.util.view.marginBottom
import eu.kanade.tachiyomi.util.view.marginTop
class CategoryRecyclerView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null
) : RecyclerView(context, attrs) {
val manager = LinearLayoutManager(context)
private val fastAdapter: FastAdapter<CategoryItem>
var onCategoryClicked: (Int) -> Unit = { _ -> }
private val itemAdapter = ItemAdapter<CategoryItem>()
var selectedCategory: Int = 0
init {
fastAdapter = FastAdapter.with(itemAdapter)
layoutManager = manager
adapter = fastAdapter
}
fun setCategories(items: List<Category>) {
itemAdapter.set(items.map(::CategoryItem))
fastAdapter.onBindViewHolderListener = (object : OnBindViewHolderListenerImpl<CategoryItem>() {
override fun onBindViewHolder(
viewHolder: ViewHolder,
position: Int,
payloads: List<Any>
) {
super.onBindViewHolder(viewHolder, position, payloads)
(viewHolder as? CategoryItem.ViewHolder)?.categoryTitle?.isSelected =
selectedCategory == position
}
})
fastAdapter.onClickListener = { _, _, item, _ ->
onCategoryClicked(item.category.order)
true
}
}
fun setCategories(selected: Int) {
selectedCategory = selected
for (i in 0..manager.itemCount) {
(findViewHolderForAdapterPosition(i) as? CategoryItem.ViewHolder)?.categoryTitle?.isSelected =
selectedCategory == i
}
}
override fun onMeasure(widthSpec: Int, heightSpec: Int) {
val recyclerView = (parent.parent as ViewGroup).findViewById<RecyclerView>(R.id.recycler)
val top = recyclerView.marginTop
val bottom = recyclerView.marginBottom
val parent = recyclerView.measuredHeight - top - bottom
val heightS = if (parent > 0)
MeasureSpec.makeMeasureSpec(parent, MeasureSpec.AT_MOST)
else heightSpec
super.onMeasure(widthSpec, heightS)
}
}

@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#FFFFFF"
android:viewportHeight="24.0" android:viewportWidth="24.0"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF000000" android:pathData="M7,10l5,5 5,-5z"/>
</vector>

@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#FFFFFF"
android:viewportHeight="24.0" android:viewportWidth="24.0"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF000000" android:pathData="M7,14l5,-5 5,5z"/>
</vector>

@ -15,7 +15,7 @@
</item> </item>
<item android:id="@android:id/mask"> <item android:id="@android:id/mask">
<color android:color="@android:color/transparent" /> <color android:color="?android:colorBackground" />
</item> </item>
</selector> </selector>
</item> </item>

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:tools="http://schemas.android.com/tools"
android:background="@drawable/list_item_selector">
<com.google.android.material.textview.MaterialTextView
android:id="@+id/category_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/list_item_selector"
android:paddingStart="12dp"
android:paddingTop="8dp"
android:paddingEnd="0dp"
android:paddingBottom="8dp"
tools:text="@string/categories"
android:textAppearance="@style/TextAppearance.MaterialComponents.Headline6"
android:textSize="16sp" />
</FrameLayout>

@ -8,6 +8,14 @@
android:background="@drawable/list_item_selector" android:background="@drawable/list_item_selector"
android:gravity="center_vertical"> android:gravity="center_vertical">
<Space
android:id="@+id/start_space"
android:layout_width="6dp"
app:layout_constraintBottom_toBottomOf="@+id/category_title"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@+id/category_title"
android:layout_height="1dp"/>
<ImageView <ImageView
android:id="@+id/checkbox" android:id="@+id/checkbox"
android:layout_width="wrap_content" android:layout_width="wrap_content"
@ -42,7 +50,7 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:barrierDirection="right" app:barrierDirection="right"
app:constraint_referenced_ids="collapse_arrow,checkbox" app:constraint_referenced_ids="start_space,collapse_arrow,checkbox"
/> />
<TextView <TextView

@ -7,4 +7,5 @@
android:layout_height="match_parent" android:layout_height="match_parent"
android:columnWidth="140dp" android:columnWidth="140dp"
android:clipToPadding="false" android:clipToPadding="false"
tools:listitem="@layout/manga_grid_item" /> android:background="@drawable/bottom_sheet_rounded_background"
tools:listitem="@layout/manga_grid_item"/>

@ -22,8 +22,37 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
<LinearLayout
android:id="@+id/category_layout"
android:layout_width="match_parent"
android:layout_marginTop="?actionBarSize"
android:orientation="vertical"
android:gravity="top"
android:layout_height="wrap_content" >
<com.google.android.material.checkbox.MaterialCheckBox
android:id="@+id/show_all"
android:layout_marginStart="6dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/show_all"/>
<eu.kanade.tachiyomi.ui.library.category.CategoryRecyclerView
android:id="@+id/category_recycler"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
<include layout="@layout/library_grid_recycler" /> <include layout="@layout/library_grid_recycler" />
<View
android:id="@+id/recycler_cover"
android:layout_marginTop="?actionBarSize"
android:alpha="0"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?android:colorBackground"/>
</FrameLayout> </FrameLayout>
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout> </androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout

@ -55,6 +55,16 @@
android:textColor="?actionBarTintColor" android:textColor="?actionBarTintColor"
android:textSize="20sp" android:textSize="20sp"
tools:text="Title Text" /> tools:text="Title Text" />
<ImageView
android:id="@+id/dropdown"
android:visibility="gone"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginStart="4dp"
android:src="@drawable/ic_arrow_drop_down_24dp"
android:tint="?actionBarTintColor" />
</LinearLayout> </LinearLayout>
</eu.kanade.tachiyomi.ui.base.CenteredToolbar> </eu.kanade.tachiyomi.ui.base.CenteredToolbar>
</com.google.android.material.appbar.AppBarLayout> </com.google.android.material.appbar.AppBarLayout>

@ -4,7 +4,6 @@
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/manga_layout" android:id="@+id/manga_layout"
android:layout_width="match_parent" android:layout_width="match_parent"
tools:background="?android:attr/colorBackground"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="bottom"> android:layout_gravity="bottom">

@ -9,5 +9,4 @@
android:columnWidth="120dp" android:columnWidth="120dp"
android:clipToPadding="false" android:clipToPadding="false"
tools:listitem="@layout/manga_grid_item" tools:listitem="@layout/manga_grid_item"
android:background="?android:colorBackground"
xmlns:app="http://schemas.android.com/apk/res-auto" /> xmlns:app="http://schemas.android.com/apk/res-auto" />
Loading…
Cancel
Save