Horis 2 lat temu
rodzic
commit
46331a1283

+ 33 - 33
app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt

@@ -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 prevPageRunnable by lazy { Runnable { keyPage(PageDirection.PREV) } }
+    private val nextPageDebounce by lazy { Debounce { keyPage(PageDirection.NEXT) } }
+    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)
-        handler.removeCallbacks(prevPageRunnable)
-        if (!mouseWheel) {
-            val keyPageTime = SystemClock.uptimeMillis()
-            val elapsed = keyPageTime - lastKeyPageTime
-            lastKeyPageTime = keyPageTime
-            if (elapsed < 100) {
-                return
-            }
+        if (longPress) {
+            return
+        }
+        nextPageDebounce.apply {
+            wait = if (mouseWheel) 200L else 600L
+            leading = !mouseWheel
+            trailing = mouseWheel
+        }
+        prevPageDebounce.apply {
+            wait = if (mouseWheel) 200L else 600L
+            leading = !mouseWheel
+            trailing = mouseWheel
         }
         }
         when (direction) {
         when (direction) {
-            PageDirection.NEXT -> handler.postDelayed(nextPageRunnable, delay)
-            PageDirection.PREV -> handler.postDelayed(prevPageRunnable, delay)
+            PageDirection.NEXT -> nextPageDebounce.invoke()
+            PageDirection.PREV -> prevPageDebounce.invoke()
             else -> {}
             else -> {}
         }
         }
     }
     }

+ 121 - 0
app/src/main/java/io/legado/app/utils/Debounce.kt

@@ -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
+    }
+
+}

+ 9 - 0
app/src/main/java/io/legado/app/utils/Throttle.kt

@@ -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)