Files
QieKan-3.0/app/src/main/java/io/legato/kazusa/utils/Debounce.kt
T
2025-06-04 13:33:51 +08:00

130 lines
3.2 KiB
Kotlin

package io.legato.kazusa.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
}
}
fun <T> debounce(
wait: Long = 0L,
maxWait: Long = -1L,
leading: Boolean = false,
trailing: Boolean = true,
func: () -> T
) = Debounce(wait, maxWait, leading, trailing, func)