Added horizontal gesture to change categories while "show all categories" is disabled

Also shifting category hopper arrows to be left and right instead of up and down in single category mode
Closes #1280
This commit is contained in:
Jays2Kings 2022-07-11 23:57:21 -04:00
parent 6b1725fd3b
commit 0ef33f7ed2
6 changed files with 182 additions and 7 deletions

View file

@ -0,0 +1,99 @@
package eu.kanade.tachiyomi.ui.library
import android.graphics.Rect
import android.view.GestureDetector
import android.view.MotionEvent
import android.view.View
import eu.kanade.tachiyomi.util.system.isLTR
import eu.kanade.tachiyomi.util.view.activityBinding
import kotlin.math.abs
import kotlin.math.pow
import kotlin.math.sign
class LibraryCategoryGestureDetector(private val controller: LibraryController?) : GestureDetector
.SimpleOnGestureListener() {
var locked = false
var cancelled = false
private val poa = 1.7f
override fun onDown(e: MotionEvent): Boolean {
locked = false
controller ?: return false
val startingOnLibraryView = listOf<View?>(
controller.activityBinding?.bottomNav,
controller.binding.filterBottomSheet.root,
controller.binding.categoryHopperFrame,
controller.activityBinding?.appBar,
).none {
it ?: return false
val viewRect = Rect()
it.getGlobalVisibleRect(viewRect)
viewRect.contains(e.x.toInt(), e.y.toInt())
}
cancelled = !startingOnLibraryView
return startingOnLibraryView
}
override fun onScroll(
e1: MotionEvent,
e2: MotionEvent,
distanceX: Float,
distanceY: Float,
): Boolean {
val controller = controller ?: return false
val distance = e1.rawX - e2.rawX
val totalDistanceY = e1.rawY - e2.rawY
controller.binding.libraryGridRecycler.recycler.translationX =
if (!cancelled) abs(distance / 50).pow(poa) * -sign(distance / 50) else 0f
if (!locked && abs(distance) > 50 && !cancelled) {
val ev2 = MotionEvent.obtain(e1)
ev2.action = MotionEvent.ACTION_CANCEL
controller.binding.swipeRefresh.dispatchTouchEvent(ev2)
ev2.recycle()
locked = true
} else if (abs(totalDistanceY) > 50 && !locked) {
cancelled = true
return false
}
return super.onScroll(e1, e2, distanceX, distanceY)
}
override fun onFling(
e1: MotionEvent,
e2: MotionEvent,
velocityX: Float,
velocityY: Float,
): Boolean {
locked = false
if (cancelled) {
cancelled = false
return false
}
cancelled = false
val controller = controller ?: return false
var result = false
val diffY = e2.y - e1.y
val diffX = e2.x - e1.x
val recycler = controller.binding.libraryGridRecycler.recycler
var moved = false
if (abs(diffX) >= abs(diffY) &&
abs(diffX) > SWIPE_THRESHOLD * 5 &&
abs(velocityX) > SWIPE_VELOCITY_THRESHOLD
) {
moved = controller.jumpToNextCategory((diffX >= 0).xor(controller.binding.root.resources.isLTR))
result = true
}
if (!result || !moved) {
val animator = controller.binding.libraryGridRecycler.recycler.animate().setDuration(150L)
animator.translationX(0f)
animator.withEndAction { recycler.translationX = 0f }
animator.start()
}
return result
}
private companion object {
const val SWIPE_THRESHOLD = 50
const val SWIPE_VELOCITY_THRESHOLD = 100
}
}

View file

@ -87,6 +87,7 @@ import eu.kanade.tachiyomi.ui.reader.ReaderActivity
import eu.kanade.tachiyomi.ui.source.globalsearch.GlobalSearchController
import eu.kanade.tachiyomi.util.isLocal
import eu.kanade.tachiyomi.util.moveCategories
import eu.kanade.tachiyomi.util.system.contextCompatDrawable
import eu.kanade.tachiyomi.util.system.dpToPx
import eu.kanade.tachiyomi.util.system.getResourceColor
import eu.kanade.tachiyomi.util.system.ignoredSystemInsets
@ -162,6 +163,8 @@ class LibraryController(
var singleCategory: Boolean = false
private set
var hopperAnimation: ValueAnimator? = null
var catGestureDetector: GestureDetectorCompat? = null
/**
* Library search query.
@ -292,8 +295,7 @@ class LibraryController(
if (!binding.fastScroller.isFastScrolling) {
updateSmallerViewsTopMargins()
}
binding.roundedCategoryHopper.upCategory.alpha = if (isAtTop()) 0.25f else 1f
binding.roundedCategoryHopper.downCategory.alpha = if (isAtBottom()) 0.25f else 1f
updateHopperAlpha()
}
if (!binding.filterBottomSheet.filterBottomSheet.sheetBehavior.isHidden()) {
scrollDistance += abs(dy)
@ -332,6 +334,11 @@ class LibraryController(
}
}
fun updateHopperAlpha() {
binding.roundedCategoryHopper.upCategory.alpha = if (isAtTop()) 0.25f else 1f
binding.roundedCategoryHopper.downCategory.alpha = if (isAtBottom()) 0.25f else 1f
}
private fun removeStaggeredObserver() {
if (staggeredObserver != null) {
binding.libraryGridRecycler.recycler.viewTreeObserver.removeOnGlobalLayoutListener(
@ -384,6 +391,7 @@ class LibraryController(
closerToHopperBottom
}
val end = if (closerToEdge) maxHopperOffset else 0f
hopperAnimation?.cancel()
val alphaAnimation = ValueAnimator.ofFloat(hopperOffset, end)
alphaAnimation.addUpdateListener { valueAnimator ->
hopperOffset = valueAnimator.animatedValue as Float
@ -394,6 +402,7 @@ class LibraryController(
updateHopperY()
}
alphaAnimation.duration = shortAnimationDuration.toLong()
hopperAnimation = alphaAnimation
alphaAnimation.start()
}
}
@ -713,6 +722,7 @@ class LibraryController(
true
}.show()
}
catGestureDetector = GestureDetectorCompat(binding.root.context, LibraryCategoryGestureDetector(this))
binding.roundedCategoryHopper.categoryButton.setOnLongClickListener {
when (preferences.hopperLongPressAction().get()) {
@ -765,6 +775,20 @@ class LibraryController(
}
}
fun handleGeneralEvent(event: MotionEvent) {
if (presenter.showAllCategories) return
if (event.action == MotionEvent.ACTION_UP || event.action == MotionEvent.ACTION_CANCEL) {
val result = catGestureDetector?.onTouchEvent(event) ?: false
if (!result && binding.libraryGridRecycler.recycler.translationX != 0f) {
binding.libraryGridRecycler.recycler.animate().setDuration(150L)
.translationX(0f)
.start()
}
} else {
catGestureDetector?.onTouchEvent(event)
}
}
fun updateHopperY(windowInsets: WindowInsetsCompat? = null) {
val view = view ?: return
val insets = windowInsets ?: view.rootWindowInsetsCompat
@ -802,24 +826,26 @@ class LibraryController(
binding.jumperCategoryText.isVisible = !hide
}
private fun jumpToNextCategory(next: Boolean) {
val category = getVisibleHeader() ?: return
fun jumpToNextCategory(next: Boolean): Boolean {
val category = getVisibleHeader() ?: return false
if (presenter.showAllCategories) {
if (!next) {
val fPosition = binding.libraryGridRecycler.recycler.findFirstVisibleItemPosition()
if (fPosition > adapter.currentItems.indexOf(category)) {
scrollToHeader(category.category.order)
return
return true
}
}
val newOffset = adapter.headerItems.indexOf(category) + (if (next) 1 else -1)
if (if (!next) newOffset > -1 else newOffset < adapter.headerItems.size) {
return if (if (!next) newOffset > -1 else newOffset < adapter.headerItems.size) {
val newCategory = (adapter.headerItems[newOffset] as LibraryHeaderItem).category
val newOrder = newCategory.order
scrollToHeader(newOrder)
showCategoryText(newCategory.name)
true
} else {
binding.libraryGridRecycler.recycler.scrollToPosition(if (next) adapter.itemCount - 1 else 0)
true
}
} else {
val newOffset =
@ -835,8 +861,13 @@ class LibraryController(
val newOrder = newCategory.order
scrollToHeader(newOrder)
showCategoryText(newCategory.name)
hopperAnimation?.cancel()
hopperOffset = 0f
updateHopperY()
return true
}
}
return false
}
private fun getHeader(firstCompletelyVisible: Boolean = false): LibraryHeaderItem? {
@ -1036,6 +1067,15 @@ class LibraryController(
)
}
adapter.setItems(mangaMap)
if (binding.libraryGridRecycler.recycler.translationX != 0f) {
val time = binding.root.resources.getInteger(
android.R.integer.config_shortAnimTime,
).toLong()
viewScope.launchUI {
delay(time / 2)
binding.libraryGridRecycler.recycler.translationX = 0f
}
}
singleCategory = presenter.categories.size <= 1
binding.progress.isVisible = false
if (!freshStart) {
@ -1122,6 +1162,27 @@ class LibraryController(
setSubtitle()
showMiniBar()
}
updateHopperAlpha()
val isSingleCategory = !presenter.showAllCategories || presenter.forceShowAllCategories
val context = binding.roundedCategoryHopper.root.context
binding.roundedCategoryHopper.upCategory.setImageDrawable(
context.contextCompatDrawable(
if (isSingleCategory) {
R.drawable.ic_arrow_start_24dp
} else {
R.drawable.ic_expand_less_24dp
},
),
)
binding.roundedCategoryHopper.downCategory.setImageDrawable(
context.contextCompatDrawable(
if (isSingleCategory) {
R.drawable.ic_arrow_end_24dp
} else {
R.drawable.ic_expand_more_24dp
},
),
)
}
private fun showSlideAnimation() {
@ -1188,6 +1249,7 @@ class LibraryController(
activityBinding?.appBar?.updateAppBarAfterY(binding.libraryGridRecycler.recycler)
binding.swipeRefresh.isEnabled = !show
setSubtitle()
binding.categoryRecycler.isInvisible = !show
if (show) {
binding.categoryRecycler.post {
binding.categoryRecycler.scrollToCategory(activeCategory)

View file

@ -1112,7 +1112,10 @@ open class MainActivity : BaseActivity<MainActivityBinding>(), DownloadServiceLi
}
override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
ev?.let { gestureDetector?.onTouchEvent(it) }
ev?.let {
gestureDetector?.onTouchEvent(it)
(router.backstack.lastOrNull()?.controller as? LibraryController)?.handleGeneralEvent(it)
}
if (ev?.action == MotionEvent.ACTION_DOWN) {
if (snackBar != null && snackBar!!.isShown) {
val sRect = Rect()

View file

@ -0,0 +1,5 @@
<vector android:autoMirrored="true" android:height="24dp"
android:tint="#000000" android:viewportHeight="24"
android:viewportWidth="24" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M8.59,16.59L13.17,12 8.59,7.41 10,6l6,6 -6,6 -1.41,-1.41z"/>
</vector>

View file

@ -0,0 +1,5 @@
<vector android:autoMirrored="true" android:height="24dp"
android:tint="#000000" android:viewportHeight="24"
android:viewportWidth="24" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M15.41,16.59L10.83,12l4.58,-4.59L14,6l-6,6 6,6 1.41,-1.41z"/>
</vector>

View file

@ -28,6 +28,7 @@
android:layout_height="wrap_content"
android:layout_marginTop="?actionBarSize"
android:clipToPadding="false"
android:visibility="invisible"
android:paddingBottom="4dp"
android:scrollbars="vertical" />