Disable mutliple click (parallel or serial) on a room (#4683)

* Disable mutliple click (parallel or serial) on a room (Fixes #4619)

* Rename method from FirstThrottler

* Move check to the Compose and add unit test on it.
This commit is contained in:
Benoit Marty 2025-05-13 14:12:19 +02:00 committed by GitHub
parent c9ec26f87c
commit f6cbca4a82
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 84 additions and 27 deletions

View file

@ -6,36 +6,29 @@
*/
package io.element.android.libraries.androidutils.throttler
import android.os.SystemClock
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import java.util.concurrent.atomic.AtomicBoolean
/**
* Simple ThrottleFirst
* See https://raw.githubusercontent.com/wiki/ReactiveX/RxJava/images/rx-operators/throttleFirst.png
*/
class FirstThrottler(private val minimumInterval: Long = 800) {
private var lastDate = 0L
class FirstThrottler(
private val minimumInterval: Long = 800,
private val coroutineScope: CoroutineScope,
) {
private val canHandle = AtomicBoolean(true)
sealed interface CanHandleResult {
data object Yes : CanHandleResult
data class No(val shouldWaitMillis: Long) : CanHandleResult
fun waitMillis(): Long {
return when (this) {
Yes -> 0
is No -> shouldWaitMillis
fun canHandle(): Boolean {
return canHandle.getAndSet(false).also { result ->
if (result) {
coroutineScope.launch {
delay(minimumInterval)
canHandle.set(true)
}
}
}
}
fun canHandle(): CanHandleResult {
val now = SystemClock.elapsedRealtime()
val delaySinceLast = now - lastDate
if (delaySinceLast > minimumInterval) {
lastDate = now
return CanHandleResult.Yes
}
// Too early
return CanHandleResult.No(minimumInterval - delaySinceLast)
}
}

View file

@ -0,0 +1,32 @@
/*
* Copyright 2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
* Please see LICENSE files in the repository root for full details.
*/
@file:OptIn(ExperimentalCoroutinesApi::class)
package io.element.android.libraries.androidutils.throttler
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.advanceTimeBy
import kotlinx.coroutines.test.runTest
import org.junit.Test
class FirstThrottlerTest {
@Test
fun `throttle canHandle returns the expected result`() = runTest {
val throttler = FirstThrottler(
minimumInterval = 300,
coroutineScope = backgroundScope,
)
assertThat(throttler.canHandle()).isTrue()
assertThat(throttler.canHandle()).isFalse()
advanceTimeBy(200)
assertThat(throttler.canHandle()).isFalse()
advanceTimeBy(110)
assertThat(throttler.canHandle()).isTrue()
}
}