This commit is contained in:
Horis
2023-12-24 23:15:14 +08:00
parent 252d4ee22b
commit 46331a1283
3 changed files with 163 additions and 33 deletions
@@ -6,7 +6,6 @@ import android.content.Intent
import android.content.res.Configuration import android.content.res.Configuration
import android.net.Uri import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.os.SystemClock
import android.view.Gravity import android.view.Gravity
import android.view.InputDevice import android.view.InputDevice
import android.view.KeyEvent import android.view.KeyEvent
@@ -93,11 +92,11 @@ import io.legado.app.ui.widget.PopupAction
import io.legado.app.ui.widget.dialog.PhotoDialog import io.legado.app.ui.widget.dialog.PhotoDialog
import io.legado.app.ui.widget.dialog.TextDialog import io.legado.app.ui.widget.dialog.TextDialog
import io.legado.app.utils.ACache import io.legado.app.utils.ACache
import io.legado.app.utils.Debounce
import io.legado.app.utils.LogUtils import io.legado.app.utils.LogUtils
import io.legado.app.utils.StartActivityContract import io.legado.app.utils.StartActivityContract
import io.legado.app.utils.SyncedRenderer import io.legado.app.utils.SyncedRenderer
import io.legado.app.utils.applyOpenTint import io.legado.app.utils.applyOpenTint
import io.legado.app.utils.buildMainHandler
import io.legado.app.utils.getPrefBoolean import io.legado.app.utils.getPrefBoolean
import io.legado.app.utils.getPrefString import io.legado.app.utils.getPrefString
import io.legado.app.utils.hexString import io.legado.app.utils.hexString
@@ -118,7 +117,6 @@ import io.legado.app.utils.toastOnUi
import io.legado.app.utils.visible import io.legado.app.utils.visible
import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.Runnable
import kotlinx.coroutines.cancelChildren import kotlinx.coroutines.cancelChildren
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@@ -217,15 +215,13 @@ class ReadBookActivity : BaseReadBookActivity(),
override val pageFactory: TextPageFactory get() = binding.readView.pageFactory override val pageFactory: TextPageFactory get() = binding.readView.pageFactory
override val headerHeight: Int get() = binding.readView.curPage.headerHeight override val headerHeight: Int get() = binding.readView.curPage.headerHeight
private val menuLayoutIsVisible get() = bottomDialog > 0 || binding.readMenu.isVisible private val menuLayoutIsVisible get() = bottomDialog > 0 || binding.readMenu.isVisible
private val nextPageRunnable by lazy { Runnable { keyPage(PageDirection.NEXT) } } private val nextPageDebounce by lazy { Debounce { keyPage(PageDirection.NEXT) } }
private val prevPageRunnable by lazy { Runnable { keyPage(PageDirection.PREV) } } private val prevPageDebounce by lazy { Debounce { keyPage(PageDirection.PREV) } }
private var bookChanged = false private var bookChanged = false
private var pageChanged = false private var pageChanged = false
private var reloadContent = false private var reloadContent = false
private val autoPageRenderer by lazy { SyncedRenderer { doAutoPage(it) } } private val autoPageRenderer by lazy { SyncedRenderer { doAutoPage(it) } }
private var autoPageScrollOffset = 0.0 private var autoPageScrollOffset = 0.0
private val handler by lazy { buildMainHandler() }
private var lastKeyPageTime = 0L
//恢复跳转前进度对话框的交互结果 //恢复跳转前进度对话框的交互结果
private var confirmRestoreProcess: Boolean? = null private var confirmRestoreProcess: Boolean? = null
@@ -624,38 +620,39 @@ class ReadBookActivity : BaseReadBookActivity(),
if (menuLayoutIsVisible) { if (menuLayoutIsVisible) {
return super.onKeyDown(keyCode, event) return super.onKeyDown(keyCode, event)
} }
val longPress = event.repeatCount > 0
when { when {
isPrevKey(keyCode) -> { isPrevKey(keyCode) -> {
handleKeyPage(PageDirection.PREV) handleKeyPage(PageDirection.PREV, longPress)
return true return true
} }
isNextKey(keyCode) -> { isNextKey(keyCode) -> {
handleKeyPage(PageDirection.NEXT) handleKeyPage(PageDirection.NEXT, longPress)
return true return true
} }
} }
when (keyCode) { when (keyCode) {
KeyEvent.KEYCODE_VOLUME_UP -> if (volumeKeyPage(PageDirection.PREV)) { KeyEvent.KEYCODE_VOLUME_UP -> if (volumeKeyPage(PageDirection.PREV, longPress)) {
return true return true
} }
KeyEvent.KEYCODE_VOLUME_DOWN -> if (volumeKeyPage(PageDirection.NEXT)) { KeyEvent.KEYCODE_VOLUME_DOWN -> if (volumeKeyPage(PageDirection.NEXT, longPress)) {
return true return true
} }
KeyEvent.KEYCODE_PAGE_UP -> { KeyEvent.KEYCODE_PAGE_UP -> {
handleKeyPage(PageDirection.PREV) handleKeyPage(PageDirection.PREV, longPress)
return true return true
} }
KeyEvent.KEYCODE_PAGE_DOWN -> { KeyEvent.KEYCODE_PAGE_DOWN -> {
handleKeyPage(PageDirection.NEXT) handleKeyPage(PageDirection.NEXT, longPress)
return true return true
} }
KeyEvent.KEYCODE_SPACE -> { KeyEvent.KEYCODE_SPACE -> {
handleKeyPage(PageDirection.NEXT) handleKeyPage(PageDirection.NEXT, longPress)
return true return true
} }
} }
@@ -669,7 +666,7 @@ class ReadBookActivity : BaseReadBookActivity(),
override fun onKeyUp(keyCode: Int, event: KeyEvent): Boolean { override fun onKeyUp(keyCode: Int, event: KeyEvent): Boolean {
when (keyCode) { when (keyCode) {
KeyEvent.KEYCODE_VOLUME_UP, KeyEvent.KEYCODE_VOLUME_DOWN -> { KeyEvent.KEYCODE_VOLUME_UP, KeyEvent.KEYCODE_VOLUME_DOWN -> {
if (volumeKeyPage(PageDirection.NONE)) { if (volumeKeyPage(PageDirection.NONE, false)) {
return true return true
} }
} }
@@ -852,49 +849,52 @@ class ReadBookActivity : BaseReadBookActivity(),
if (menuLayoutIsVisible || !AppConfig.mouseWheelPage) { if (menuLayoutIsVisible || !AppConfig.mouseWheelPage) {
return return
} }
keyPageDebounce(direction, 200L, true) keyPageDebounce(direction, mouseWheel = true, longPress = false)
} }
/** /**
* 音量键翻页 * 音量键翻页
*/ */
private fun volumeKeyPage(direction: PageDirection): Boolean { private fun volumeKeyPage(direction: PageDirection, longPress: Boolean): Boolean {
if (!AppConfig.volumeKeyPage) { if (!AppConfig.volumeKeyPage) {
return false return false
} }
if (!AppConfig.volumeKeyPageOnPlay && BaseReadAloudService.isPlay()) { if (!AppConfig.volumeKeyPageOnPlay && BaseReadAloudService.isPlay()) {
return false return false
} }
handleKeyPage(direction) handleKeyPage(direction, longPress)
return true return true
} }
private fun handleKeyPage(direction: PageDirection) { private fun handleKeyPage(direction: PageDirection, longPress: Boolean) {
if (AppConfig.keyPageOnLongPress || direction == PageDirection.NONE) { if (AppConfig.keyPageOnLongPress || direction == PageDirection.NONE) {
keyPage(direction) keyPage(direction)
} else { } else {
keyPageDebounce(direction) keyPageDebounce(direction, longPress = longPress)
} }
} }
private fun keyPageDebounce( private fun keyPageDebounce(
direction: PageDirection, direction: PageDirection,
delay: Long = 100L, mouseWheel: Boolean = false,
mouseWheel: Boolean = false longPress: Boolean
) { ) {
handler.removeCallbacks(nextPageRunnable) if (longPress) {
handler.removeCallbacks(prevPageRunnable) return
if (!mouseWheel) { }
val keyPageTime = SystemClock.uptimeMillis() nextPageDebounce.apply {
val elapsed = keyPageTime - lastKeyPageTime wait = if (mouseWheel) 200L else 600L
lastKeyPageTime = keyPageTime leading = !mouseWheel
if (elapsed < 100) { trailing = mouseWheel
return }
} prevPageDebounce.apply {
wait = if (mouseWheel) 200L else 600L
leading = !mouseWheel
trailing = mouseWheel
} }
when (direction) { when (direction) {
PageDirection.NEXT -> handler.postDelayed(nextPageRunnable, delay) PageDirection.NEXT -> nextPageDebounce.invoke()
PageDirection.PREV -> handler.postDelayed(prevPageRunnable, delay) PageDirection.PREV -> prevPageDebounce.invoke()
else -> {} else -> {}
} }
} }
@@ -0,0 +1,121 @@
package io.legado.app.utils
import android.os.SystemClock
import kotlin.math.max
@Suppress("MemberVisibilityCanBePrivate")
open class Debounce<T>(
var wait: Long = 0L,
var maxWait: Long = -1L,
var leading: Boolean = false,
var trailing: Boolean = true,
private val func: () -> T
) {
companion object {
private val handler by lazy { buildMainHandler() }
}
private var lastCallTime = -1L
private var lastInvokeTime = 0L
private val maxing get() = maxWait != -1L
private var result: T? = null
private var hasTimer = false
private val timerExpiredRunnable = Runnable {
timerExpired()
}
init {
maxWait = if (maxing) max(maxWait, wait) else maxWait
}
private fun invokeFunc(time: Long): T {
lastInvokeTime = time
return func.invoke().also { result = it }
}
private fun startTimer(wait: Long) {
hasTimer = true
handler.postDelayed(timerExpiredRunnable, wait)
}
private fun cancelTimer() {
handler.removeCallbacks(timerExpiredRunnable)
}
private fun leadingEdge(time: Long): T? {
lastInvokeTime = time
startTimer(wait)
return if (leading) invokeFunc(time) else result
}
private fun trailingEdge(time: Long): T? {
hasTimer = false
return if (trailing) invokeFunc(time) else result
}
private fun remainingWait(time: Long): Long {
val timeSinceLastCall = time - lastCallTime
val timeSinceLastInvoke = time - lastInvokeTime
val timeWaiting = wait - timeSinceLastCall
return if (maxing) timeWaiting.coerceAtMost(maxWait - timeSinceLastInvoke) else timeWaiting
}
private fun shouldInvoke(time: Long): Boolean {
val timeSinceLastCall = time - lastCallTime
val timeSinceLastInvoke = time - lastInvokeTime
return lastCallTime == -1L
|| timeSinceLastCall >= wait
|| timeSinceLastCall < 0
|| maxing && timeSinceLastInvoke >= maxWait
}
private fun timerExpired() {
val time = SystemClock.uptimeMillis()
if (shouldInvoke(time)) {
trailingEdge(time)
} else {
startTimer(remainingWait(time))
}
}
fun cancel() {
if (hasTimer) {
cancelTimer()
}
lastInvokeTime = 0
lastCallTime = -1L
hasTimer = false
}
fun flush(): T? {
return if (hasTimer) trailingEdge(SystemClock.uptimeMillis()) else result
}
fun pending(): Boolean = hasTimer
operator fun invoke(): T? {
val time = SystemClock.uptimeMillis()
val isInvoking = shouldInvoke(time)
lastCallTime = time
if (isInvoking) {
if (!hasTimer) {
return leadingEdge(lastCallTime)
}
if (maxing) {
startTimer(wait)
return invokeFunc(lastCallTime)
}
}
if (!hasTimer) {
startTimer(wait)
}
return result
}
}
@@ -0,0 +1,9 @@
package io.legado.app.utils
@Suppress("unused")
class Throttle<T>(
wait: Long = 0L,
leading: Boolean = true,
trailing: Boolean = true,
func: () -> T
) : Debounce<T>(wait, wait, leading, trailing, func)