Using new (old?) fast scroller in library

New category jumper now shows what catergory you're on while scrolling
This commit is contained in:
Jay 2020-05-06 20:11:06 -04:00
parent a9ec02caac
commit de8cb8c1b0
19 changed files with 242 additions and 345 deletions

View File

@ -177,8 +177,6 @@ class PreferencesHelper(val context: Context) {
fun gridSize() = rxPrefs.getInteger(Keys.gridSize, 2) fun gridSize() = rxPrefs.getInteger(Keys.gridSize, 2)
fun alwaysShowSeeker() = rxPrefs.getBoolean("always_show_seeker", false)
fun uniformGrid() = rxPrefs.getBoolean(Keys.uniformGrid, true) fun uniformGrid() = rxPrefs.getBoolean(Keys.uniformGrid, true)
fun chaptersDescAsDefault() = rxPrefs.getBoolean("chapters_desc_as_default", true) fun chaptersDescAsDefault() = rxPrefs.getBoolean("chapters_desc_as_default", true)

View File

@ -85,9 +85,6 @@ class DisplayBottomSheet(private val controller: LibraryController) : BottomShee
uniform_grid.bindToPreference(preferences.uniformGrid()) { uniform_grid.bindToPreference(preferences.uniformGrid()) {
controller.reattachAdapter() controller.reattachAdapter()
} }
autohide_seeker.bindToPreference(preferences.alwaysShowSeeker()) {
controller.updateShowScrollbar(autohide_seeker.isChecked)
}
grid_size_toggle_group.bindToPreference(preferences.gridSize()) { grid_size_toggle_group.bindToPreference(preferences.gridSize()) {
controller.reattachAdapter() controller.reattachAdapter()
} }

View File

@ -9,12 +9,12 @@ import eu.kanade.tachiyomi.data.database.models.Manga
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.util.lang.removeArticles import eu.kanade.tachiyomi.util.lang.removeArticles
import eu.kanade.tachiyomi.util.system.timeSpanFromNow
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Calendar import java.util.Calendar
import java.util.Date import java.util.Date
import java.util.Locale import java.util.Locale
import kotlin.math.max
/** /**
* Adapter storing a list of manga in a certain category. * Adapter storing a list of manga in a certain category.
@ -98,57 +98,63 @@ class LibraryCategoryAdapter(val controller: LibraryController) :
isLongPressDragEnabled = libraryListener.canDrag() && s.isNullOrBlank() isLongPressDragEnabled = libraryListener.canDrag() && s.isNullOrBlank()
} }
fun getSectionText(position: Int): String? { private fun getFirstLetter(name: String): String {
val letter = name.firstOrNull() ?: '#'
return if (letter.isLetter()) getFirstChar(name) else "#"
}
override fun onCreateBubbleText(position: Int): String {
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 recyclerView.context.getString(R.string.bottom)
val sorting = if (!preferences.showAllCategories().get()) {
controller.presenter.getCurrentCategory()?.sortingMode() ?: LibrarySort.DRAG_AND_DROP
} else if (preferences.hideCategories().getOrDefault()) {
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 || if (!preferences.hideCategories().getOrDefault()) item.category.name
!preferences.showAllCategories().get()) null else recyclerView.context.getString(R.string.top)
else getFirstChar(item.category.name) +
"\u200B".repeat(max(0, item.category.order))
is LibraryItem -> { is LibraryItem -> {
when (sorting) { if (!isSingleCategory) {
item.header?.category?.name.orEmpty()
} else if (item.manga.isBlank()) ""
else when (getSort()) {
LibrarySort.DRAG_AND_DROP -> { LibrarySort.DRAG_AND_DROP -> {
val category = db.getCategoriesForManga(item.manga).executeAsBlocking() if (!preferences.hideCategories().getOrDefault()) {
.firstOrNull() val title = item.manga.title
if (category == null) null if (preferences.removeArticles().getOrDefault())
else getFirstLetter(category.name) + "\u200B".repeat(max(0, category.order)) getFirstChar(title.removeArticles())
else getFirstChar(title)
} else {
val category = db.getCategoriesForManga(item.manga)
.executeAsBlocking().firstOrNull()?.name
category ?: recyclerView.context.getString(R.string.default_value)
}
} }
LibrarySort.LAST_READ -> { LibrarySort.LAST_READ -> {
val id = item.manga.id ?: return "" val id = item.manga.id ?: return ""
val history = db.getHistoryByMangaId(id).executeAsBlocking() val history = db.getHistoryByMangaId(id).executeAsBlocking()
val last = history.maxBy { it.last_read } val last = history.maxBy { it.last_read }
if (last != null && last.last_read > 100) getShorterDate(Date(last.last_read)) if (last != null && last.last_read > 100) last.last_read.timeSpanFromNow
else "*" else "N/A"
}
LibrarySort.TOTAL -> {
val unread = item.chapterCount
getShortRange(unread)
} }
LibrarySort.UNREAD -> { LibrarySort.UNREAD -> {
val unread = item.manga.unread val unread = item.manga.unread
if (unread > 0) getShortRange(unread) if (unread > 0) unread.toString()
else "R" else recyclerView.context.getString(R.string.read)
}
LibrarySort.TOTAL -> {
val total = item.chapterCount
if (total > 0) total.toString()
else "N/A"
} }
LibrarySort.LATEST_CHAPTER -> { LibrarySort.LATEST_CHAPTER -> {
val lastUpdate = item.manga.last_update val lastUpdate = item.manga.last_update
if (lastUpdate > 0) getShorterDate(Date(lastUpdate)) if (lastUpdate > 0) lastUpdate.timeSpanFromNow
else "*" // getShortDate(Date(lastUpdate))
else "N/A"
} }
LibrarySort.DATE_ADDED -> { LibrarySort.DATE_ADDED -> {
val lastUpdate = item.manga.date_added val lastUpdate = item.manga.date_added
if (lastUpdate > 0) getShorterDate(Date(lastUpdate)) if (lastUpdate > 0) lastUpdate.timeSpanFromNow
else "*" else "N/A"
} }
else -> { else -> {
val title = if (preferences.removeArticles() val title = if (preferences.removeArticles()
@ -163,67 +169,12 @@ class LibraryCategoryAdapter(val controller: LibraryController) :
} }
} }
private fun getFirstLetter(name: String): String { private fun getSort(): Int {
val letter = name.firstOrNull() ?: '#'
return if (letter.isLetter()) getFirstChar(name) else "#"
}
override fun onCreateBubbleText(position: Int): String {
val preferences: PreferencesHelper by injectLazy() val preferences: PreferencesHelper by injectLazy()
val db: DatabaseHelper by injectLazy() return if (!preferences.showAllCategories().get() && !preferences.hideCategories().getOrDefault()) {
if (position == itemCount - 1) return recyclerView.context.getString(R.string.bottom)
return when (val iFlexible: IFlexible<*>? = getItem(position)) {
is LibraryHeaderItem ->
if (!preferences.hideCategories().getOrDefault()) iFlexible.category.name
else recyclerView.context.getString(R.string.top)
is LibraryItem -> {
if (iFlexible.manga.isBlank()) ""
else when (if (!preferences.showAllCategories().get()) {
controller.presenter.getCurrentCategory()?.sortingMode() ?: LibrarySort.DRAG_AND_DROP controller.presenter.getCurrentCategory()?.sortingMode() ?: LibrarySort.DRAG_AND_DROP
} else preferences.librarySortingMode().getOrDefault()) {
LibrarySort.DRAG_AND_DROP -> {
if (!preferences.hideCategories().getOrDefault()) {
val title = iFlexible.manga.title
if (preferences.removeArticles().getOrDefault())
getFirstChar(title.removeArticles())
else getFirstChar(title)
} else { } else {
val category = db.getCategoriesForManga(iFlexible.manga) preferences.librarySortingMode().getOrDefault()
.executeAsBlocking().firstOrNull()?.name
category ?: recyclerView.context.getString(R.string.default_value)
}
}
LibrarySort.LAST_READ -> {
val id = iFlexible.manga.id ?: return ""
val history = db.getHistoryByMangaId(id).executeAsBlocking()
val last = history.maxBy { it.last_read }
if (last != null && last.last_read > 100) getShortDate(Date(last.last_read))
else "N/A"
}
LibrarySort.UNREAD -> {
val unread = iFlexible.manga.unread
if (unread > 0) getRange(unread)
else recyclerView.context.getString(R.string.read)
}
LibrarySort.TOTAL -> {
val total = iFlexible.chapterCount
if (total > 0) getRange(total)
else "N/A"
}
LibrarySort.LATEST_CHAPTER -> {
val lastUpdate = iFlexible.manga.last_update
if (lastUpdate > 0) getShortDate(Date(lastUpdate))
else "N/A"
}
LibrarySort.DATE_ADDED -> {
val lastUpdate = iFlexible.manga.date_added
if (lastUpdate > 0) getShortDate(Date(lastUpdate))
else "N/A"
}
else -> getSectionText(position) ?: ""
}
}
else -> ""
} }
} }
@ -254,23 +205,6 @@ class LibraryCategoryAdapter(val controller: LibraryController) :
} }
} }
private fun getRange(value: Int): String {
return when (value) {
1 -> "1"
2 -> "2"
3 -> "3"
4 -> "4"
5 -> "5"
in 6..10 -> "6-10"
in 11..50 -> "11-50"
in 51..100 -> "51-100"
in 101..500 -> "100-500"
in 499..899 -> "499-900"
in 901..Int.MAX_VALUE -> "900+"
else -> "None"
}
}
private fun getShorterDate(date: Date): String { private fun getShorterDate(date: Date): String {
val cal = Calendar.getInstance() val cal = Calendar.getInstance()
cal.time = Date() cal.time = Date()
@ -286,25 +220,7 @@ class LibraryCategoryAdapter(val controller: LibraryController) :
SimpleDateFormat("''yy", Locale.getDefault()).format(date) SimpleDateFormat("''yy", Locale.getDefault()).format(date)
} }
private fun getShortDate(date: Date): String {
val cal = Calendar.getInstance()
cal.time = Date()
val yearNow = cal.get(Calendar.YEAR)
val cal2 = Calendar.getInstance()
cal2.time = date
val yearThen = cal2.get(Calendar.YEAR)
return if (yearNow == yearThen)
SimpleDateFormat("MMMM", Locale.getDefault()).format(date)
else
SimpleDateFormat("yyyy", Locale.getDefault()).format(date)
}
interface LibraryListener { interface LibraryListener {
/**
* Called when an item of the list is released.
*/
fun startReading(position: Int) fun startReading(position: Int)
fun onItemReleased(position: Int) fun onItemReleased(position: Int)
fun canDrag(): Boolean fun canDrag(): Boolean

View File

@ -33,8 +33,6 @@ import com.bluelinelabs.conductor.ControllerChangeType
import com.github.florent37.viewtooltip.ViewTooltip import com.github.florent37.viewtooltip.ViewTooltip
import com.google.android.material.snackbar.BaseTransientBottomBar import com.google.android.material.snackbar.BaseTransientBottomBar
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import com.reddit.indicatorfastscroll.FastScrollItemIndicator
import com.reddit.indicatorfastscroll.FastScrollerView
import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.SelectableAdapter import eu.davidea.flexibleadapter.SelectableAdapter
import eu.davidea.flexibleadapter.items.IFlexible import eu.davidea.flexibleadapter.items.IFlexible
@ -59,7 +57,6 @@ import eu.kanade.tachiyomi.ui.manga.MangaDetailsController
import eu.kanade.tachiyomi.ui.migration.manga.design.PreMigrationController import eu.kanade.tachiyomi.ui.migration.manga.design.PreMigrationController
import eu.kanade.tachiyomi.ui.reader.ReaderActivity import eu.kanade.tachiyomi.ui.reader.ReaderActivity
import eu.kanade.tachiyomi.util.system.dpToPx import eu.kanade.tachiyomi.util.system.dpToPx
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
@ -71,11 +68,8 @@ import eu.kanade.tachiyomi.util.view.hide
import eu.kanade.tachiyomi.util.view.isExpanded import eu.kanade.tachiyomi.util.view.isExpanded
import eu.kanade.tachiyomi.util.view.isHidden import eu.kanade.tachiyomi.util.view.isHidden
import eu.kanade.tachiyomi.util.view.scrollViewWith import eu.kanade.tachiyomi.util.view.scrollViewWith
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.setStyle import eu.kanade.tachiyomi.util.view.setStyle
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
@ -156,11 +150,24 @@ class LibraryController(
private val scrollDistanceTilHidden = 1000.dpToPx private val scrollDistanceTilHidden = 1000.dpToPx
private var textAnim: ViewPropertyAnimator? = null private var textAnim: ViewPropertyAnimator? = null
private var scrollAnim: ViewPropertyAnimator? = null var hopperGravity: Int = preferences.hopperGravity().get()
private var alwaysShowScroller: Boolean = preferences.alwaysShowSeeker().getOrDefault() set(value) {
field = value
if (category_hopper_frame == null) return
jumper_category_text.updateLayoutParams<CoordinatorLayout.LayoutParams> {
anchorGravity = when (value) {
0 -> Gravity.RIGHT or Gravity.CENTER_VERTICAL
2 -> Gravity.LEFT or Gravity.CENTER_VERTICAL
else -> Gravity.TOP or Gravity.CENTER_HORIZONTAL
}
gravity = anchorGravity
}
}
private var filterTooltip: ViewTooltip? = null private var filterTooltip: ViewTooltip? = null
private var elevationAnim: ValueAnimator? = null private var elevationAnim: ValueAnimator? = null
private var elevate = false 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)
} }
@ -191,42 +198,18 @@ class LibraryController(
setActiveCategory() setActiveCategory()
if (presenter.categories.size > 1 && dy != 0 && recyclerView.translationY == 0f) { if (presenter.categories.size > 1 && dy != 0 && recyclerView.translationY == 0f) {
val headerItem = getHeader() ?: return val headerItem = getHeader() ?: return
val view = fast_scroller ?: return showCategoryText(headerItem.category.name)
val height = if (view.childCount > 0) {
view.height - (view.getChildAt(0)?.paddingTop
?: 0) - (view.getChildAt(view.childCount - 1)?.paddingBottom ?: 0)
} else {
view.height
}
val index = adapter.headerItems.indexOf(headerItem)
textAnim?.cancel()
textAnim = text_view_m.animate().alpha(0f).setDuration(250L).setStartDelay(2000)
textAnim?.start()
// fastScroll height * indicator position - center text - fastScroll padding
text_view_m.translationY =
height * (index.toFloat() / (adapter.headerItems.size + 1))
-text_view_m.height / 2 + 16.dpToPx
text_view_m.translationX = 45f.dpToPxEnd
text_view_m.alpha = 1f
text_view_m.text = headerItem.category.name
} }
} }
} }
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) { override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
super.onScrollStateChanged(recyclerView, newState) super.onScrollStateChanged(recyclerView, newState)
if (alwaysShowScroller) return
when (newState) { when (newState) {
RecyclerView.SCROLL_STATE_DRAGGING -> { RecyclerView.SCROLL_STATE_DRAGGING -> {
scrollAnim?.cancel() fast_scroller.showScrollbar()
if (fast_scroller?.translationX != 0f) {
fast_scroller?.show()
}
} }
RecyclerView.SCROLL_STATE_IDLE -> { RecyclerView.SCROLL_STATE_IDLE -> {
scrollAnim = fast_scroller?.hide()
val shortAnimationDuration = resources?.getInteger( val shortAnimationDuration = resources?.getInteger(
android.R.integer.config_shortAnimTime android.R.integer.config_shortAnimTime
) ?: 0 ) ?: 0
@ -241,9 +224,17 @@ class LibraryController(
} }
} }
fun showCategoryText(name: String) {
textAnim?.cancel()
textAnim = jumper_category_text.animate().alpha(0f).setDuration(250L).setStartDelay(2000)
textAnim?.start()
jumper_category_text.alpha = 1f
jumper_category_text.text = name
}
fun isAtTop(): Boolean { fun isAtTop(): Boolean {
return if (presenter.showAllCategories) { return if (presenter.showAllCategories) {
getVisibleHeader() == adapter.headerItems.firstOrNull() !recycler.canScrollVertically(-1)
} else { } else {
getVisibleHeader()?.category?.id == presenter.categories.firstOrNull()?.id getVisibleHeader()?.category?.id == presenter.categories.firstOrNull()?.id
} }
@ -251,7 +242,7 @@ class LibraryController(
fun isAtBottom(): Boolean { fun isAtBottom(): Boolean {
return if (presenter.showAllCategories) { return if (presenter.showAllCategories) {
getVisibleHeader() == adapter.headerItems.lastOrNull() !recycler.canScrollVertically(1)
} else { } else {
getVisibleHeader()?.category?.id == presenter.categories.lastOrNull()?.id getVisibleHeader()?.category?.id == presenter.categories.lastOrNull()?.id
} }
@ -283,13 +274,10 @@ class LibraryController(
super.onViewCreated(view) super.onViewCreated(view)
view.applyWindowInsetsForRootController(activity!!.bottom_nav) view.applyWindowInsetsForRootController(activity!!.bottom_nav)
if (!::presenter.isInitialized) presenter = LibraryPresenter(this) if (!::presenter.isInitialized) presenter = LibraryPresenter(this)
fast_scroller.setStartTranslationX(!alwaysShowScroller)
fast_scroller.setBackground(!alwaysShowScroller)
adapter = LibraryCategoryAdapter(this) adapter = LibraryCategoryAdapter(this)
adapter.expandItemsAtStartUp()
adapter.isRecursiveCollapse = true
setRecyclerLayout() setRecyclerLayout()
recycler.manager.spanSizeLookup = (object : GridLayoutManager.SpanSizeLookup() { recycler.manager.spanSizeLookup = (object : GridLayoutManager.SpanSizeLookup() {
override fun getSpanSize(position: Int): Int { override fun getSpanSize(position: Int): Int {
if (libraryLayout == 0) return 1 if (libraryLayout == 0) return 1
@ -301,73 +289,12 @@ class LibraryController(
}) })
recycler.setHasFixedSize(true) recycler.setHasFixedSize(true)
recycler.adapter = adapter recycler.adapter = adapter
fast_scroller.setupWithRecyclerView(recycler, { position ->
val letter = adapter.getSectionText(position) fast_scroller.addOnScrollStateChangeListener {
if (!singleCategory && presenter.showAllCategories && !adapter.isHeader( swipe_refresh.isEnabled = !it
adapter.getItem(
position
)
) && position != adapter.itemCount - 1
) null
else if (letter != null) FastScrollItemIndicator.Text(letter)
else FastScrollItemIndicator.Icon(R.drawable.ic_star_24dp)
})
fast_scroller.useDefaultScroller = false
fast_scroller.itemIndicatorSelectedCallbacks += object :
FastScrollerView.ItemIndicatorSelectedCallback {
override fun onItemIndicatorSelected(
indicator: FastScrollItemIndicator,
indicatorCenterY: Int,
itemPosition: Int
) {
fast_scroller.translationX = 0f
if (!alwaysShowScroller) {
scrollAnim?.cancel()
scrollAnim = fast_scroller.hide(2000)
} }
textAnim?.cancel() adapter.fastScroller = fast_scroller
textAnim = text_view_m.animate().alpha(0f).setDuration(250L).setStartDelay(2000)
textAnim?.start()
text_view_m.translationY = indicatorCenterY.toFloat() - text_view_m.height / 2
text_view_m.translationX = 0f
text_view_m.alpha = 1f
text_view_m.text = adapter.onCreateBubbleText(itemPosition)
val appbar = activity?.appbar
if (singleCategory) {
val order = when (val item = adapter.getItem(itemPosition)) {
is LibraryHeaderItem -> item
is LibraryItem -> item.header
else -> null
}?.category?.order
if (order != null) {
activeCategory = order
preferences.lastUsedCategory().set(order)
}
}
appbar?.y = 0f
val item = adapter.getItem(itemPosition)
if (item is LibraryHeaderItem) {
scrollToHeader(item.category.order)
} else {
recycler.suppressLayout(true)
(recycler.layoutManager as LinearLayoutManager).scrollToPositionWithOffset(
itemPosition, if (adapter.isSingleCategory) {
0
} else {
if (itemPosition == 0) {
0
} else {
(-40).dpToPx
}
}
)
recycler.suppressLayout(false)
}
}
}
recycler.addOnScrollListener(scrollListener) recycler.addOnScrollListener(scrollListener)
val tv = TypedValue() val tv = TypedValue()
@ -418,6 +345,7 @@ class LibraryController(
else -> Gravity.CENTER else -> Gravity.CENTER
} }
} }
hopperGravity = preferences.hopperGravity().get()
val gestureDetector = GestureDetectorCompat(activity, LibraryGestureDetector(this)) val gestureDetector = GestureDetectorCompat(activity, LibraryGestureDetector(this))
listOf(category_hopper_layout, up_category, down_category, category_button).forEach { listOf(category_hopper_layout, up_category, down_category, category_button).forEach {
@ -430,9 +358,9 @@ class LibraryController(
category_layout?.updateLayoutParams<ViewGroup.MarginLayoutParams> { category_layout?.updateLayoutParams<ViewGroup.MarginLayoutParams> {
topMargin = recycler?.paddingTop ?: 0 topMargin = recycler?.paddingTop ?: 0
} }
fast_scroller?.updateLayoutParams<ViewGroup.MarginLayoutParams> { // fast_scroller?.updateLayoutParams<ViewGroup.MarginLayoutParams> {
topMargin = insets.systemWindowInsetTop // topMargin = insets.systemWindowInsetTop
} // }
}) })
swipe_refresh.setOnRefreshListener { swipe_refresh.setOnRefreshListener {
@ -509,9 +437,10 @@ class LibraryController(
} else { } else {
newOffset < adapter.headerItems.size newOffset < adapter.headerItems.size
}) { }) {
val newOrder = val newCategory = (adapter.headerItems[newOffset] as LibraryHeaderItem).category
(adapter.headerItems[newOffset] as LibraryHeaderItem).category.order val newOrder = newCategory.order
scrollToHeader(newOrder) scrollToHeader(newOrder)
showCategoryText(newCategory.name)
} else { } else {
recycler.scrollToPosition(if (next) adapter.itemCount - 1 else 0) recycler.scrollToPosition(if (next) adapter.itemCount - 1 else 0)
} }
@ -525,8 +454,10 @@ class LibraryController(
newOffset < presenter.categories.size newOffset < presenter.categories.size
} }
) { ) {
val newOrder = presenter.categories[newOffset].order val newCategory = presenter.categories[newOffset]
val newOrder = newCategory.order
scrollToHeader(newOrder) scrollToHeader(newOrder)
showCategoryText(newCategory.name)
} }
} }
} }
@ -586,16 +517,6 @@ class LibraryController(
return order return order
} }
fun updateShowScrollbar(show: Boolean) {
alwaysShowScroller = show
fast_scroller?.setBackground(!show)
if (libraryLayout == 0) reattachAdapter()
scrollAnim?.cancel()
if (show) fast_scroller?.translationX = 0f
else scrollAnim = fast_scroller?.hide()
setRecyclerLayout()
}
override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View { override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View {
return inflater.inflate(R.layout.library_list_controller, container, false) return inflater.inflate(R.layout.library_list_controller, container, false)
} }
@ -627,8 +548,8 @@ class LibraryController(
else -> .75f else -> .75f
} }
recycler.updatePaddingRelative( recycler.updatePaddingRelative(
start = (if (alwaysShowScroller) 2 else 5).dpToPx, start = 5.dpToPx,
end = (if (alwaysShowScroller) 12 else 5).dpToPx end = 5.dpToPx
) )
} }
} }
@ -684,7 +605,7 @@ class LibraryController(
} }
fun onNextLibraryUpdate(mangaMap: List<LibraryItem>, freshStart: Boolean = false) { fun onNextLibraryUpdate(mangaMap: List<LibraryItem>, freshStart: Boolean = false) {
val view = view ?: return view ?: return
destroyActionModeIfNeeded() destroyActionModeIfNeeded()
if (mangaMap.isNotEmpty()) { if (mangaMap.isNotEmpty()) {
empty_view?.hide() empty_view?.hide()
@ -712,12 +633,6 @@ class LibraryController(
} else recycler_layout.alpha = 1f } else recycler_layout.alpha = 1f
if (justStarted && freshStart) { if (justStarted && freshStart) {
scrollToHeader(activeCategory) scrollToHeader(activeCategory)
if (!alwaysShowScroller) {
fast_scroller?.show(false)
view.post {
scrollAnim = fast_scroller?.hide(2000)
}
}
} }
category_hopper_frame.visibleIf(!singleCategory) category_hopper_frame.visibleIf(!singleCategory)
adapter.isLongPressDragEnabled = canDrag() adapter.isLongPressDragEnabled = canDrag()

View File

@ -67,6 +67,7 @@ class LibraryGestureDetector(private val controller: LibraryController) : Gestur
}) })
} }
} }
controller.hopperGravity = controller.preferences.hopperGravity().get()
result = true result = true
} }
return result return result

View File

@ -8,7 +8,6 @@ import android.text.SpannableString
import android.text.style.ForegroundColorSpan import android.text.style.ForegroundColorSpan
import android.util.TypedValue import android.util.TypedValue
import android.view.View import android.view.View
import android.view.ViewGroup
import android.widget.ImageView import android.widget.ImageView
import android.widget.ProgressBar import android.widget.ProgressBar
import android.widget.TextView import android.widget.TextView
@ -17,7 +16,6 @@ import androidx.appcompat.widget.PopupMenu
import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.f2prateek.rx.preferences.Preference
import com.github.florent37.viewtooltip.ViewTooltip import com.github.florent37.viewtooltip.ViewTooltip
import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.SelectableAdapter import eu.davidea.flexibleadapter.SelectableAdapter
@ -27,7 +25,6 @@ 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.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.ui.base.holder.BaseFlexibleViewHolder import eu.kanade.tachiyomi.ui.base.holder.BaseFlexibleViewHolder
import eu.kanade.tachiyomi.util.system.dpToPx import eu.kanade.tachiyomi.util.system.dpToPx
import eu.kanade.tachiyomi.util.system.getResourceColor import eu.kanade.tachiyomi.util.system.getResourceColor
@ -42,8 +39,7 @@ import uy.kohesive.injekt.api.get
class LibraryHeaderItem( class LibraryHeaderItem(
private val categoryF: (Int) -> Category, private val categoryF: (Int) -> Category,
private val catId: Int, private val catId: Int
private val showFastScroll: Preference<Boolean>
) : ) :
AbstractHeaderItem<LibraryHeaderItem.Holder>() { AbstractHeaderItem<LibraryHeaderItem.Holder>() {
@ -55,7 +51,7 @@ class LibraryHeaderItem(
view: View, view: View,
adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>> adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>
): Holder { ): Holder {
return Holder(view, adapter as LibraryCategoryAdapter, showFastScroll.getOrDefault()) return Holder(view, adapter as LibraryCategoryAdapter)
} }
override fun bindViewHolder( override fun bindViewHolder(
@ -90,7 +86,7 @@ class LibraryHeaderItem(
return -(category.id!!) return -(category.id!!)
} }
class Holder(val view: View, private val adapter: LibraryCategoryAdapter, padEnd: Boolean) : class Holder(val view: View, private val adapter: LibraryCategoryAdapter) :
BaseFlexibleViewHolder(view, adapter, true) { BaseFlexibleViewHolder(view, adapter, true) {
private val sectionText: TextView = view.findViewById(R.id.category_title) private val sectionText: TextView = view.findViewById(R.id.category_title)
@ -101,9 +97,6 @@ class LibraryHeaderItem(
private val catProgress: ProgressBar = view.findViewById(R.id.cat_progress) private val catProgress: ProgressBar = view.findViewById(R.id.cat_progress)
init { init {
sortText.updateLayoutParams<ViewGroup.MarginLayoutParams> {
marginEnd = (if (padEnd && adapter.recyclerView.paddingEnd == 0) 12 else 2).dpToPx
}
category_header_layout.setOnClickListener { toggleCategory() } category_header_layout.setOnClickListener { toggleCategory() }
updateButton.setOnClickListener { addCategoryToUpdate() } updateButton.setOnClickListener { addCategoryToUpdate() }
sectionText.setOnLongClickListener { sectionText.setOnLongClickListener {

View File

@ -37,9 +37,6 @@ class LibraryItem(
var unreadType = 2 var unreadType = 2
var chapterCount = -1 var chapterCount = -1
private val showFastScroll: Boolean
get() = preferences.alwaysShowSeeker().getOrDefault()
private val uniformSize: Boolean private val uniformSize: Boolean
get() = preferences.uniformGrid().getOrDefault() get() = preferences.uniformGrid().getOrDefault()
@ -62,7 +59,7 @@ class LibraryItem(
val libraryLayout = libraryLayout val libraryLayout = libraryLayout
val isFixedSize = uniformSize val isFixedSize = uniformSize
if (libraryLayout == 0 || manga.isBlank()) { if (libraryLayout == 0 || manga.isBlank()) {
LibraryListHolder(view, adapter as LibraryCategoryAdapter, showFastScroll) LibraryListHolder(view, adapter as LibraryCategoryAdapter)
} else { } else {
view.apply { view.apply {
val coverHeight = (parent.itemWidth / 3f * 4f).toInt() val coverHeight = (parent.itemWidth / 3f * 4f).toInt()
@ -107,7 +104,7 @@ class LibraryItem(
) )
} }
} else { } else {
LibraryListHolder(view, adapter as LibraryCategoryAdapter, showFastScroll) LibraryListHolder(view, adapter as LibraryCategoryAdapter)
} }
} }

View File

@ -29,16 +29,9 @@ import kotlinx.android.synthetic.main.unread_download_badge.*
class LibraryListHolder( class LibraryListHolder(
private val view: View, private val view: View,
adapter: LibraryCategoryAdapter, adapter: LibraryCategoryAdapter
padEnd: Boolean
) : LibraryHolder(view, adapter) { ) : LibraryHolder(view, adapter) {
init {
badge_view?.updateLayoutParams<ViewGroup.MarginLayoutParams> {
marginEnd = (if (padEnd) 22 else 12).dpToPx
}
}
/** /**
* Method called from [LibraryCategoryAdapter.onBindViewHolder]. It updates the data for this * Method called from [LibraryCategoryAdapter.onBindViewHolder]. It updates the data for this
* holder with the given manga. * holder with the given manga.

View File

@ -118,11 +118,7 @@ class LibraryPresenter(
private fun blankItem(id: Int = currentCategory): List<LibraryItem> { private fun blankItem(id: Int = currentCategory): List<LibraryItem> {
return listOf( return listOf(
LibraryItem( LibraryItem(
LibraryManga.createBlank(id), LibraryHeaderItem( LibraryManga.createBlank(id), LibraryHeaderItem({ getCategory(id) }, id)
{ getCategory(id) },
id,
preferences.alwaysShowSeeker()
)
) )
) )
} }
@ -499,7 +495,6 @@ class LibraryPresenter(
val categories = db.getCategories().executeAsBlocking().toMutableList() val categories = db.getCategories().executeAsBlocking().toMutableList()
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 showAll = showAllCategories 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(
@ -507,13 +502,13 @@ class LibraryPresenter(
preferences.librarySortingMode().getOrDefault(), preferences.librarySortingMode().getOrDefault(),
preferences.librarySortingAscending().getOrDefault() preferences.librarySortingAscending().getOrDefault()
) )
val catItemAll = LibraryHeaderItem({ categoryAll }, -1, seekPref) val catItemAll = LibraryHeaderItem({ categoryAll }, -1)
val categorySet = mutableSetOf<Int>() val categorySet = mutableSetOf<Int>()
val headerItems = (categories.mapNotNull { category -> val headerItems = (categories.mapNotNull { category ->
val id = category.id val id = category.id
if (id == null) null if (id == null) null
else id to LibraryHeaderItem({ getCategory(id) }, id, seekPref) else id to LibraryHeaderItem({ getCategory(id) }, id)
} + (-1 to catItemAll) + (0 to LibraryHeaderItem({ getCategory(0) }, 0, seekPref))).toMap() } + (-1 to catItemAll) + (0 to LibraryHeaderItem({ getCategory(0) }, 0))).toMap()
val items = libraryManga.mapNotNull { val items = libraryManga.mapNotNull {
val headerItem = (if (!showCategories) catItemAll val headerItem = (if (!showCategories) catItemAll
else headerItems[it.category]) ?: return@mapNotNull null else headerItems[it.category]) ?: return@mapNotNull null

View File

@ -0,0 +1,29 @@
package eu.kanade.tachiyomi.ui.library
import android.content.Context
import android.util.AttributeSet
import android.view.MotionEvent
import eu.davidea.fastscroller.FastScroller
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.util.system.dpToPxEnd
class MaterialFastScroll @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
FastScroller(context, attrs) {
init {
setViewsToUse(
R.layout.material_fastscroll, R.id.fast_scroller_bubble, R.id.fast_scroller_handle
)
}
override fun onTouchEvent(event: MotionEvent): Boolean {
if (isHidden) return false
return super.onTouchEvent(event)
}
override fun setBubbleAndHandlePosition(y: Float) {
super.setBubbleAndHandlePosition(y)
bubble.y = handle.y - bubble.height / 2f + handle.height / 2f
bubble.translationX = (-45f).dpToPxEnd
}
}

View File

@ -55,7 +55,6 @@ class AutofitRecyclerView @JvmOverloads constructor(context: Context, attrs: Att
val dpWidth = (measuredWidth.pxToDp / 100f).roundToInt() val dpWidth = (measuredWidth.pxToDp / 100f).roundToInt()
val count = max(1, (dpWidth / columnWidth).roundToInt()) val count = max(1, (dpWidth / columnWidth).roundToInt())
spanCount = count spanCount = count
// Timber.d("Dp width: $dpWidth - RSpan: $spanCount")
} }
} }
} }

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners android:radius="38dp"/>
<solid android:color="?colorPrimaryVariant" />
<size
android:width="30dp"
android:height="30dp"/>
</shape>

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_selected="true">
<shape android:shape="rectangle">
<corners android:radius="8dp"/>
<solid android:color="@color/colorAccent"/>
<size android:width="6dp" android:height="54dp"/>
</shape>
</item>
<item>
<shape android:shape="rectangle">
<corners android:radius="8dp"/>
<solid android:color="@color/fast_scroller_handle_idle"/>
<size android:width="6dp" android:height="54dp"/>
</shape>
</item>
</selector>

View File

@ -173,14 +173,6 @@
android:layout_marginEnd="12dp" android:layout_marginEnd="12dp"
android:text="@string/download_badge" /> android:text="@string/download_badge" />
<com.google.android.material.checkbox.MaterialCheckBox
android:id="@+id/autohide_seeker"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:layout_marginEnd="12dp"
android:text="@string/always_show_library_fast_scroll"/>
<com.google.android.material.checkbox.MaterialCheckBox <com.google.android.material.checkbox.MaterialCheckBox
android:id="@+id/hide_filters" android:id="@+id/hide_filters"
android:layout_width="match_parent" android:layout_width="match_parent"

View File

@ -1,11 +1,11 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<eu.kanade.tachiyomi.widget.AutofitRecyclerView xmlns:android="http://schemas.android.com/apk/res/android" <eu.kanade.tachiyomi.widget.AutofitRecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
style="@style/Theme.Widget.GridView.Catalogue"
android:id="@+id/recycler" android:id="@+id/recycler"
style="@style/Theme.Widget.GridView.Catalogue"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:columnWidth="140dp"
android:clipToPadding="false"
android:background="@drawable/bottom_sheet_rounded_background" android:background="@drawable/bottom_sheet_rounded_background"
tools:listitem="@layout/manga_grid_item"/> android:clipToPadding="false"
android:columnWidth="140dp"
tools:listitem="@layout/manga_grid_item" />

View File

@ -51,6 +51,15 @@
<include layout="@layout/library_grid_recycler" /> <include layout="@layout/library_grid_recycler" />
<eu.kanade.tachiyomi.ui.library.MaterialFastScroll
android:id="@+id/fast_scroller"
android:layout_width="match_parent"
app:fastScrollerAutoHideEnabled="true"
app:fastScrollerAutoHideDelayInMillis="1000"
app:fastScrollerBubbleEnabled="true"
app:fastScrollerIgnoreTouchesOutsideHandle="true"
android:layout_height="match_parent"/>
<View <View
android:id="@+id/recycler_cover" android:id="@+id/recycler_cover"
android:layout_width="match_parent" android:layout_width="match_parent"
@ -61,42 +70,6 @@
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout> </androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.reddit.indicatorfastscroll.FastScrollerView
android:id="@+id/fast_scroller"
android:layout_width="25dp"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:background="@drawable/fast_scroll_background"
android:elevation="10dp"
android:paddingStart="3dp"
android:paddingTop="8dp"
android:paddingEnd="0dp"
android:paddingBottom="8dp"
android:textColor="?android:attr/textColorPrimaryInverse"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:visibility="visible" />
<com.google.android.material.textview.MaterialTextView
android:id="@+id/text_view_m"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="50dp"
android:alpha="0"
android:background="@drawable/round_textview_background"
android:padding="10dp"
android:textColor="@android:color/white"
app:layout_constraintEnd_toStartOf="@id/fast_scroller"
app:layout_constraintTop_toTopOf="@id/fast_scroller"
tools:alpha="1"
tools:text="Category" />
</androidx.constraintlayout.widget.ConstraintLayout>
<eu.kanade.tachiyomi.widget.EmptyView <eu.kanade.tachiyomi.widget.EmptyView
android:id="@+id/empty_view" android:id="@+id/empty_view"
android:layout_width="wrap_content" android:layout_width="wrap_content"
@ -114,11 +87,33 @@
app:layout_anchor="@id/filter_bottom_sheet" app:layout_anchor="@id/filter_bottom_sheet"
app:layout_anchorGravity="top" /> app:layout_anchorGravity="top" />
<com.google.android.material.textview.MaterialTextView
android:elevation="10dp"
android:id="@+id/jumper_category_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_anchor="@id/category_hopper_frame"
android:background="@drawable/bubble_drawable"
android:gravity="center"
android:paddingStart="12dp"
android:paddingEnd="12dp"
android:layout_marginBottom="12dp"
android:layout_gravity="start|center"
app:layout_anchorGravity="start|center"
android:paddingTop="8dp"
android:paddingBottom="8dp"
android:alpha="0.0"
android:textColor="?actionBarTintColor"
android:textSize="15sp"
tools:alpha="1"
tools:text="Category and a long one"/>
<FrameLayout <FrameLayout
android:id="@+id/category_hopper_frame" android:id="@+id/category_hopper_frame"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="top|center" android:layout_gravity="top|center"
android:elevation="11dp"
app:layout_anchor="@id/filter_bottom_sheet" app:layout_anchor="@id/filter_bottom_sheet"
app:layout_anchorGravity="top|center"> app:layout_anchorGravity="top|center">

View File

@ -0,0 +1,52 @@
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="wrap_content"
android:layout_height="match_parent">
<View
android:id="@+id/fast_scroller_bar"
android:layout_width="7dp"
android:layout_height="match_parent"
android:layout_gravity="end"
android:background="@android:color/transparent"/>
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end">
<!-- No margin, use padding at the handle -->
<com.google.android.material.textview.MaterialTextView
android:elevation="10dp"
android:id="@+id/fast_scroller_bubble"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end|center_vertical"
android:layout_toStartOf="@+id/fast_scroller_handle"
android:background="@drawable/bubble_drawable"
android:gravity="center"
android:paddingStart="12dp"
android:paddingEnd="12dp"
android:paddingTop="8dp"
android:paddingBottom="8dp"
android:textColor="?actionBarTintColor"
android:textSize="15sp"
android:visibility="gone"
tools:text="A"
tools:visibility="visible"/>
<!-- Padding is here to have better grab -->
<ImageView
android:id="@+id/fast_scroller_handle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:contentDescription="@null"
android:paddingStart="6dp"
android:paddingEnd="4dp"
android:src="@drawable/thumb_drawable"/>
</RelativeLayout>
</merge>

View File

@ -4,7 +4,7 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="20dp" android:layout_marginStart="20dp"
android:layout_marginTop="20dp" android:layout_marginTop="10dp"
android:layout_marginEnd="20dp" android:layout_marginEnd="20dp"
android:layout_marginBottom="10dp" android:layout_marginBottom="10dp"
app:cardCornerRadius="25dp" app:cardCornerRadius="25dp"

View File

@ -133,7 +133,6 @@
<string name="start_with_filters_hidden">Start with filters hidden</string> <string name="start_with_filters_hidden">Start with filters hidden</string>
<string name="download_badge">Download badges</string> <string name="download_badge">Download badges</string>
<string name="hide_start_reading_button">Hide start reading button</string> <string name="hide_start_reading_button">Hide start reading button</string>
<string name="always_show_library_fast_scroll">Always show library fast scroll</string>
<string name="unread_badges">Unread badges</string> <string name="unread_badges">Unread badges</string>
<string name="uniform_covers">Uniform covers</string> <string name="uniform_covers">Uniform covers</string>
<string name="x_small">XS</string> <string name="x_small">XS</string>