Pin: add LockScreenConfig and address PR reviews

This commit is contained in:
ganfra
2023-10-23 15:30:17 +02:00
parent bf8cf00c51
commit d21623e523
15 changed files with 71 additions and 38 deletions

View File

@@ -0,0 +1,35 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.appconfig
object LockScreenConfig {
/**
* Whether the LockScreen is mandatory or not.
*/
const val IS_MANDATORY: Boolean = false
/**
* Some PINs are blacklisted.
*/
val PIN_BLACKLIST = listOf("0000", "1234")
/**
* The size of the PIN.
*/
const val PIN_SIZE = 4
}

View File

@@ -30,9 +30,11 @@ anvil {
}
dependencies {
ksp(libs.showkase.processor)
implementation(projects.anvilannotations)
anvil(projects.anvilcodegen)
api(projects.features.lockscreen.api)
implementation(projects.appconfig)
implementation(projects.libraries.core)
implementation(projects.libraries.architecture)
implementation(projects.libraries.matrix.api)
@@ -52,7 +54,4 @@ dependencies {
testImplementation(projects.libraries.cryptography.test)
testImplementation(projects.libraries.cryptography.impl)
testImplementation(projects.libraries.featureflag.test)
ksp(libs.showkase.processor)
}

View File

@@ -110,7 +110,7 @@ private fun PinDigitView(
internal fun PinEntryTextFieldPreview() {
ElementPreview {
PinEntryTextField(
pinEntry = PinEntry.empty(4).fillWith("12"),
pinEntry = PinEntry.createEmpty(4).fillWith("12"),
onValueChange = {},
)
}

View File

@@ -24,7 +24,7 @@ data class PinEntry(
) {
companion object {
fun empty(size: Int): PinEntry {
fun createEmpty(size: Int): PinEntry {
val digits = List(size) { PinDigit.Empty }
return PinEntry(
digits = digits.toPersistentList()
@@ -69,7 +69,7 @@ data class PinEntry(
}
fun clear(): PinEntry {
return fillWith("")
return createEmpty(size)
}
fun isComplete(): Boolean {

View File

@@ -21,15 +21,14 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import io.element.android.appconfig.LockScreenConfig
import io.element.android.features.lockscreen.impl.pin.model.PinEntry
import io.element.android.features.lockscreen.impl.setup.validation.SetupPinFailure
import io.element.android.features.lockscreen.impl.setup.validation.PinValidator
import io.element.android.features.lockscreen.impl.setup.validation.SetupPinFailure
import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.core.meta.BuildMeta
import javax.inject.Inject
private const val PIN_SIZE = 4
class SetupPinPresenter @Inject constructor(
private val pinValidator: PinValidator,
private val buildMeta: BuildMeta,
@@ -38,10 +37,10 @@ class SetupPinPresenter @Inject constructor(
@Composable
override fun present(): SetupPinState {
var choosePinEntry by remember {
mutableStateOf(PinEntry.empty(PIN_SIZE))
mutableStateOf(PinEntry.createEmpty(LockScreenConfig.PIN_SIZE))
}
var confirmPinEntry by remember {
mutableStateOf(PinEntry.empty(PIN_SIZE))
mutableStateOf(PinEntry.createEmpty(LockScreenConfig.PIN_SIZE))
}
var isConfirmationStep by remember {
mutableStateOf(false)
@@ -77,11 +76,11 @@ class SetupPinPresenter @Inject constructor(
SetupPinEvents.ClearFailure -> {
when (setupPinFailure) {
is SetupPinFailure.PinsDontMatch -> {
choosePinEntry = PinEntry.empty(PIN_SIZE)
confirmPinEntry = PinEntry.empty(PIN_SIZE)
choosePinEntry = choosePinEntry.clear()
confirmPinEntry = confirmPinEntry.clear()
}
is SetupPinFailure.PinBlacklisted -> {
choosePinEntry = PinEntry.empty(PIN_SIZE)
choosePinEntry = choosePinEntry.clear()
}
null -> Unit
}

View File

@@ -25,20 +25,20 @@ open class SetupPinStateProvider : PreviewParameterProvider<SetupPinState> {
get() = sequenceOf(
aSetupPinState(),
aSetupPinState(
choosePinEntry = PinEntry.empty(4).fillWith("12")
choosePinEntry = PinEntry.createEmpty(4).fillWith("12")
),
aSetupPinState(
choosePinEntry = PinEntry.empty(4).fillWith("1789"),
choosePinEntry = PinEntry.createEmpty(4).fillWith("1789"),
isConfirmationStep = true,
),
aSetupPinState(
choosePinEntry = PinEntry.empty(4).fillWith("1789"),
confirmPinEntry = PinEntry.empty(4).fillWith("1788"),
choosePinEntry = PinEntry.createEmpty(4).fillWith("1789"),
confirmPinEntry = PinEntry.createEmpty(4).fillWith("1788"),
isConfirmationStep = true,
creationFailure = SetupPinFailure.PinsDontMatch
),
aSetupPinState(
choosePinEntry = PinEntry.empty(4).fillWith("1111"),
choosePinEntry = PinEntry.createEmpty(4).fillWith("1111"),
creationFailure = SetupPinFailure.PinBlacklisted
),
@@ -46,8 +46,8 @@ open class SetupPinStateProvider : PreviewParameterProvider<SetupPinState> {
}
fun aSetupPinState(
choosePinEntry: PinEntry = PinEntry.empty(4),
confirmPinEntry: PinEntry = PinEntry.empty(4),
choosePinEntry: PinEntry = PinEntry.createEmpty(4),
confirmPinEntry: PinEntry = PinEntry.createEmpty(4),
isConfirmationStep: Boolean = false,
creationFailure: SetupPinFailure? = null,
) = SetupPinState(

View File

@@ -16,17 +16,12 @@
package io.element.android.features.lockscreen.impl.setup.validation
import androidx.annotation.VisibleForTesting
import io.element.android.appconfig.LockScreenConfig
import io.element.android.features.lockscreen.impl.pin.model.PinEntry
import javax.inject.Inject
class PinValidator @Inject constructor() {
companion object {
@VisibleForTesting
val BLACKLIST = listOf("0000", "1234")
}
sealed interface Result {
data object Valid : Result
data class Invalid(val failure: SetupPinFailure) : Result
@@ -34,7 +29,7 @@ class PinValidator @Inject constructor() {
fun isPinValid(pinEntry: PinEntry): Result {
val pinAsText = pinEntry.toText()
val isBlacklisted = BLACKLIST.any { it == pinAsText }
val isBlacklisted = LockScreenConfig.PIN_BLACKLIST.any { it == pinAsText }
return if (isBlacklisted) {
Result.Invalid(SetupPinFailure.PinBlacklisted)
} else {

View File

@@ -16,7 +16,7 @@
package io.element.android.features.lockscreen.impl.unlock
import io.element.android.features.lockscreen.impl.unlock.numpad.PinKeypadModel
import io.element.android.features.lockscreen.impl.unlock.keypad.PinKeypadModel
sealed interface PinUnlockEvents {
data class OnPinKeypadPressed(val pinKeypadModel: PinKeypadModel) : PinUnlockEvents

View File

@@ -23,9 +23,10 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import io.element.android.appconfig.LockScreenConfig
import io.element.android.features.lockscreen.api.LockScreenStateService
import io.element.android.features.lockscreen.impl.pin.model.PinEntry
import io.element.android.features.lockscreen.impl.unlock.numpad.PinKeypadModel
import io.element.android.features.lockscreen.impl.unlock.keypad.PinKeypadModel
import io.element.android.libraries.architecture.Presenter
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
@@ -39,9 +40,11 @@ class PinUnlockPresenter @Inject constructor(
@Composable
override fun present(): PinUnlockState {
var pinEntry by remember {
mutableStateOf(PinEntry.empty(4))
//TODO fetch size from db
mutableStateOf(PinEntry.createEmpty(LockScreenConfig.PIN_SIZE))
}
var remainingAttempts by rememberSaveable {
//TODO fetch from db
mutableIntStateOf(3)
}
var showWrongPinTitle by rememberSaveable {
@@ -56,6 +59,7 @@ class PinUnlockPresenter @Inject constructor(
is PinUnlockEvents.OnPinKeypadPressed -> {
pinEntry = pinEntry.process(event.pinKeypadModel)
if (pinEntry.isComplete()) {
//TODO check pin with PinCodeManager
coroutineScope.launch { pinStateService.unlock() }
}
}

View File

@@ -23,7 +23,7 @@ open class PinUnlockStateProvider : PreviewParameterProvider<PinUnlockState> {
override val values: Sequence<PinUnlockState>
get() = sequenceOf(
aPinUnlockState(),
aPinUnlockState(pinEntry = PinEntry.empty(4).fillWith("12")),
aPinUnlockState(pinEntry = PinEntry.createEmpty(4).fillWith("12")),
aPinUnlockState(showWrongPinTitle = true),
aPinUnlockState(showSignOutPrompt = true),
aPinUnlockState(showSignOutPrompt = true, remainingAttempts = 0),
@@ -31,7 +31,7 @@ open class PinUnlockStateProvider : PreviewParameterProvider<PinUnlockState> {
}
fun aPinUnlockState(
pinEntry: PinEntry = PinEntry.empty(4),
pinEntry: PinEntry = PinEntry.createEmpty(4),
remainingAttempts: Int = 3,
showWrongPinTitle: Boolean = false,
showSignOutPrompt: Boolean = false,

View File

@@ -47,7 +47,7 @@ import androidx.compose.ui.unit.dp
import io.element.android.features.lockscreen.impl.R
import io.element.android.features.lockscreen.impl.pin.model.PinDigit
import io.element.android.features.lockscreen.impl.pin.model.PinEntry
import io.element.android.features.lockscreen.impl.unlock.numpad.PinKeypad
import io.element.android.features.lockscreen.impl.unlock.keypad.PinKeypad
import io.element.android.libraries.designsystem.components.dialogs.ConfirmationDialog
import io.element.android.libraries.designsystem.components.dialogs.ErrorDialog
import io.element.android.libraries.designsystem.preview.ElementPreview

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package io.element.android.features.lockscreen.impl.unlock.numpad
package io.element.android.features.lockscreen.impl.unlock.keypad
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package io.element.android.features.lockscreen.impl.unlock.numpad
package io.element.android.features.lockscreen.impl.unlock.keypad
import androidx.compose.runtime.Immutable

View File

@@ -20,6 +20,7 @@ import app.cash.molecule.RecompositionMode
import app.cash.molecule.moleculeFlow
import app.cash.turbine.test
import com.google.common.truth.Truth.assertThat
import io.element.android.appconfig.LockScreenConfig
import io.element.android.features.lockscreen.impl.pin.model.assertEmpty
import io.element.android.features.lockscreen.impl.pin.model.assertText
import io.element.android.features.lockscreen.impl.setup.validation.PinValidator
@@ -31,7 +32,7 @@ import org.junit.Test
class SetupPinPresenterTest {
private val blacklistedPin = PinValidator.BLACKLIST.first()
private val blacklistedPin = LockScreenConfig.PIN_BLACKLIST
private val halfCompletePin = "12"
private val completePin = "1235"
private val mismatchedPin = "1236"

View File

@@ -23,7 +23,7 @@ import com.google.common.truth.Truth.assertThat
import io.element.android.features.lockscreen.impl.pin.model.assertEmpty
import io.element.android.features.lockscreen.impl.pin.model.assertText
import io.element.android.features.lockscreen.impl.state.DefaultLockScreenStateService
import io.element.android.features.lockscreen.impl.unlock.numpad.PinKeypadModel
import io.element.android.features.lockscreen.impl.unlock.keypad.PinKeypadModel
import io.element.android.libraries.featureflag.api.FeatureFlags
import io.element.android.libraries.featureflag.test.FakeFeatureFlagService
import io.element.android.tests.testutils.awaitLastSequentialItem