Extract and unit test MultipleTapToUnlock

This commit is contained in:
Benoit Marty
2025-06-26 17:44:08 +02:00
parent 78e82eac28
commit 11cbc2c293
6 changed files with 95 additions and 8 deletions

View File

@@ -15,5 +15,7 @@ android {
dependencies {
testImplementation(libs.test.junit)
testImplementation(libs.test.truth)
testImplementation(libs.coroutines.test)
testImplementation(libs.test.truth)
}
}

View File

@@ -0,0 +1,42 @@
/*
* 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.
*/
package io.element.android.libraries.ui.utils
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlin.time.Duration.Companion.seconds
/**
* Returns true if the user has tapped [numberOfTapToUnlock] times in a short amount of time.
* The counter is reset after 2 seconds of inactivity.
*
* @param numberOfTapToUnlock The number of taps required to unlock.
*/
class MultipleTapToUnlock(
private val numberOfTapToUnlock: Int = 7,
) {
private var counter = numberOfTapToUnlock
private var currentJob: Job? = null
fun unlock(scope: CoroutineScope): Boolean {
counter--
currentJob?.cancel()
return if (counter > 0) {
currentJob = scope.launch {
delay(2.seconds)
// Reset counter if user is not fast enough
counter = numberOfTapToUnlock
}
false
} else {
true
}
}
}

View File

@@ -0,0 +1,41 @@
/*
* 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.ui.utils
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
import kotlin.time.Duration.Companion.seconds
class MultipleTapToUnlockTest {
@Test
fun `test multiple tap should unlock`() = runTest {
val sut = MultipleTapToUnlock(3)
assertThat(sut.unlock(backgroundScope)).isFalse()
assertThat(sut.unlock(backgroundScope)).isFalse()
assertThat(sut.unlock(backgroundScope)).isTrue()
assertThat(sut.unlock(backgroundScope)).isTrue()
// All next call returns true
advanceTimeBy(3.seconds)
assertThat(sut.unlock(backgroundScope)).isTrue()
}
@Test
fun `test waiting should reset counter`() = runTest {
val sut = MultipleTapToUnlock(3)
assertThat(sut.unlock(backgroundScope)).isFalse()
assertThat(sut.unlock(backgroundScope)).isFalse()
advanceTimeBy(3.seconds)
assertThat(sut.unlock(backgroundScope)).isFalse()
assertThat(sut.unlock(backgroundScope)).isFalse()
assertThat(sut.unlock(backgroundScope)).isTrue()
}
}