fix(notifications): throttle dictionary progress reposts
A fast download reposted the ongoing notification several times per second. Publish stage changes immediately and rate-limit intermediate updates to the latest snapshot.
This commit is contained in:
1 parent
ae4bffe516
commit
2fdcae6ae3
1 file changed
+53
-3
+53
-3
@@ -1,37 +1,86 @@
|
|||||||
package com.example.research.common.progress
|
package com.example.research.common.progress
|
||||||
|
|
||||||
|
import android.os.SystemClock
|
||||||
import kotlinx.coroutines.CoroutineDispatcher
|
import kotlinx.coroutines.CoroutineDispatcher
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlin.time.Duration.Companion.milliseconds
|
||||||
|
|
||||||
class DictionaryProgressPresenter(
|
class DictionaryProgressPresenter(
|
||||||
scope: CoroutineScope,
|
private val scope: CoroutineScope,
|
||||||
private val progressSnapshot: StateFlow<DictionaryProgressModel.Snapshot?>,
|
private val progressSnapshot: StateFlow<DictionaryProgressModel.Snapshot?>,
|
||||||
private val notifications: NotificationPort,
|
private val notifications: NotificationPort,
|
||||||
mainDispatcher: CoroutineDispatcher = Dispatchers.Main,
|
private val mainDispatcher: CoroutineDispatcher = Dispatchers.Main,
|
||||||
) : PipelineTerminalUi {
|
) : PipelineTerminalUi {
|
||||||
|
|
||||||
|
private companion object {
|
||||||
|
val MIN_NOTIFICATION_INTERVAL = 500.milliseconds
|
||||||
|
}
|
||||||
|
|
||||||
@Volatile
|
@Volatile
|
||||||
private var terminalActive = false
|
private var terminalActive = false
|
||||||
|
|
||||||
|
private var lastShownTitleRes: Int = 0
|
||||||
|
private var lastEmitMs: Long = 0L
|
||||||
|
private var pendingSnapshot: DictionaryProgressModel.Snapshot? = null
|
||||||
|
private var flushJob: Job? = null
|
||||||
|
|
||||||
init {
|
init {
|
||||||
scope.launch(mainDispatcher) {
|
scope.launch(mainDispatcher) {
|
||||||
progressSnapshot.collect { snapshot ->
|
progressSnapshot.collect { snapshot ->
|
||||||
when {
|
when {
|
||||||
snapshot == null -> {
|
snapshot == null -> {
|
||||||
|
resetThrottle()
|
||||||
if (!terminalActive && notifications.hasActiveProgressNotification()) {
|
if (!terminalActive && notifications.hasActiveProgressNotification()) {
|
||||||
notifications.cancelProgressNotification()
|
notifications.cancelProgressNotification()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
!terminalActive -> notifications.showProgress(snapshot.percent, snapshot.titleRes)
|
!terminalActive -> emitThrottled(snapshot)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun emitThrottled(snapshot: DictionaryProgressModel.Snapshot) {
|
||||||
|
val now = SystemClock.elapsedRealtime()
|
||||||
|
val stageChanged = snapshot.titleRes != lastShownTitleRes
|
||||||
|
if (stageChanged || (now - lastEmitMs).milliseconds >= MIN_NOTIFICATION_INTERVAL) {
|
||||||
|
flushJob?.cancel()
|
||||||
|
pendingSnapshot = null
|
||||||
|
publish(snapshot, now)
|
||||||
|
} else {
|
||||||
|
pendingSnapshot = snapshot
|
||||||
|
if (flushJob?.isActive != true) {
|
||||||
|
flushJob = scope.launch(mainDispatcher) {
|
||||||
|
delay(MIN_NOTIFICATION_INTERVAL - (now - lastEmitMs).milliseconds)
|
||||||
|
pendingSnapshot?.let { publish(it, SystemClock.elapsedRealtime()) }
|
||||||
|
pendingSnapshot = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun publish(snapshot: DictionaryProgressModel.Snapshot, now: Long) {
|
||||||
|
if (terminalActive) return
|
||||||
|
lastShownTitleRes = snapshot.titleRes
|
||||||
|
lastEmitMs = now
|
||||||
|
notifications.showProgress(snapshot.percent, snapshot.titleRes)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun resetThrottle() {
|
||||||
|
flushJob?.cancel()
|
||||||
|
pendingSnapshot = null
|
||||||
|
lastShownTitleRes = 0
|
||||||
|
lastEmitMs = 0L
|
||||||
|
}
|
||||||
|
|
||||||
fun beginFlow() {
|
fun beginFlow() {
|
||||||
terminalActive = false
|
terminalActive = false
|
||||||
|
resetThrottle()
|
||||||
notifications.cancelTerminalNotification()
|
notifications.cancelTerminalNotification()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,6 +101,7 @@ class DictionaryProgressPresenter(
|
|||||||
|
|
||||||
override fun cancelProgress() {
|
override fun cancelProgress() {
|
||||||
terminalActive = false
|
terminalActive = false
|
||||||
|
resetThrottle()
|
||||||
notifications.cancelProgressNotification()
|
notifications.cancelProgressNotification()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user