Create pin : render failures

This commit is contained in:
ganfra 2023-10-19 12:34:29 +02:00
parent e5bcfb3936
commit 7ba9a0af77
7 changed files with 61 additions and 35 deletions

View file

@ -18,5 +18,5 @@ package io.element.android.features.lockscreen.impl.create
sealed interface CreatePinEvents { sealed interface CreatePinEvents {
data class OnPinEntryChanged(val entryAsText: String) : CreatePinEvents data class OnPinEntryChanged(val entryAsText: String) : CreatePinEvents
data object OnClearValidationFailure : CreatePinEvents data object ClearFailure : CreatePinEvents
} }

View file

@ -22,7 +22,7 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import io.element.android.features.lockscreen.impl.create.model.PinEntry import io.element.android.features.lockscreen.impl.create.model.PinEntry
import io.element.android.features.lockscreen.impl.create.validation.PinCreationFailure import io.element.android.features.lockscreen.impl.create.validation.CreatePinFailure
import io.element.android.features.lockscreen.impl.create.validation.PinValidator import io.element.android.features.lockscreen.impl.create.validation.PinValidator
import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.Presenter
import javax.inject.Inject import javax.inject.Inject
@ -44,8 +44,8 @@ class CreatePinPresenter @Inject constructor(
var isConfirmationStep by remember { var isConfirmationStep by remember {
mutableStateOf(false) mutableStateOf(false)
} }
var creationFailure by remember { var createPinFailure by remember {
mutableStateOf<PinCreationFailure?>(null) mutableStateOf<CreatePinFailure?>(null)
} }
fun handleEvents(event: CreatePinEvents) { fun handleEvents(event: CreatePinEvents) {
@ -57,7 +57,7 @@ class CreatePinPresenter @Inject constructor(
if (confirmPinEntry == choosePinEntry) { if (confirmPinEntry == choosePinEntry) {
//TODO save in db and navigate to next screen //TODO save in db and navigate to next screen
} else { } else {
creationFailure = PinCreationFailure.ConfirmationPinNotMatching createPinFailure = CreatePinFailure.ConfirmationPinNotMatching
} }
} }
} else { } else {
@ -65,26 +65,26 @@ class CreatePinPresenter @Inject constructor(
if (choosePinEntry.isPinComplete()) { if (choosePinEntry.isPinComplete()) {
when (val pinValidationResult = pinValidator.isPinValid(choosePinEntry)) { when (val pinValidationResult = pinValidator.isPinValid(choosePinEntry)) {
is PinValidator.Result.Invalid -> { is PinValidator.Result.Invalid -> {
creationFailure = pinValidationResult.failure createPinFailure = pinValidationResult.failure
} }
PinValidator.Result.Valid -> isConfirmationStep = true PinValidator.Result.Valid -> isConfirmationStep = true
} }
} }
} }
} }
CreatePinEvents.OnClearValidationFailure -> { CreatePinEvents.ClearFailure -> {
when (creationFailure) { when (createPinFailure) {
is PinCreationFailure.ConfirmationPinNotMatching -> { is CreatePinFailure.ConfirmationPinNotMatching -> {
choosePinEntry = PinEntry.empty(PIN_SIZE) choosePinEntry = PinEntry.empty(PIN_SIZE)
confirmPinEntry = PinEntry.empty(PIN_SIZE) confirmPinEntry = PinEntry.empty(PIN_SIZE)
} }
is PinCreationFailure.ChosenPinBlacklisted -> { is CreatePinFailure.ChosenPinBlacklisted -> {
choosePinEntry = PinEntry.empty(PIN_SIZE) choosePinEntry = PinEntry.empty(PIN_SIZE)
} }
null -> Unit null -> Unit
} }
isConfirmationStep = false isConfirmationStep = false
creationFailure = null createPinFailure = null
} }
} }
} }
@ -93,7 +93,7 @@ class CreatePinPresenter @Inject constructor(
choosePinEntry = choosePinEntry, choosePinEntry = choosePinEntry,
confirmPinEntry = confirmPinEntry, confirmPinEntry = confirmPinEntry,
isConfirmationStep = isConfirmationStep, isConfirmationStep = isConfirmationStep,
creationFailure = creationFailure, createPinFailure = createPinFailure,
eventSink = ::handleEvents eventSink = ::handleEvents
) )
} }

View file

@ -17,13 +17,13 @@
package io.element.android.features.lockscreen.impl.create package io.element.android.features.lockscreen.impl.create
import io.element.android.features.lockscreen.impl.create.model.PinEntry import io.element.android.features.lockscreen.impl.create.model.PinEntry
import io.element.android.features.lockscreen.impl.create.validation.PinCreationFailure import io.element.android.features.lockscreen.impl.create.validation.CreatePinFailure
data class CreatePinState( data class CreatePinState(
val choosePinEntry: PinEntry, val choosePinEntry: PinEntry,
val confirmPinEntry: PinEntry, val confirmPinEntry: PinEntry,
val isConfirmationStep: Boolean, val isConfirmationStep: Boolean,
val creationFailure: PinCreationFailure?, val createPinFailure: CreatePinFailure?,
val eventSink: (CreatePinEvents) -> Unit val eventSink: (CreatePinEvents) -> Unit
) { ) {
val activePinEntry = if (isConfirmationStep) { val activePinEntry = if (isConfirmationStep) {

View file

@ -18,7 +18,7 @@ package io.element.android.features.lockscreen.impl.create
import androidx.compose.ui.tooling.preview.PreviewParameterProvider import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import io.element.android.features.lockscreen.impl.create.model.PinEntry import io.element.android.features.lockscreen.impl.create.model.PinEntry
import io.element.android.features.lockscreen.impl.create.validation.PinCreationFailure import io.element.android.features.lockscreen.impl.create.validation.CreatePinFailure
open class CreatePinStateProvider : PreviewParameterProvider<CreatePinState> { open class CreatePinStateProvider : PreviewParameterProvider<CreatePinState> {
override val values: Sequence<CreatePinState> override val values: Sequence<CreatePinState>
@ -31,6 +31,17 @@ open class CreatePinStateProvider : PreviewParameterProvider<CreatePinState> {
choosePinEntry = PinEntry.empty(4).fillWith("1789"), choosePinEntry = PinEntry.empty(4).fillWith("1789"),
isConfirmationStep = true, isConfirmationStep = true,
), ),
aCreatePinState(
choosePinEntry = PinEntry.empty(4).fillWith("1789"),
confirmPinEntry = PinEntry.empty(4).fillWith("1788"),
isConfirmationStep = true,
creationFailure = CreatePinFailure.ConfirmationPinNotMatching
),
aCreatePinState(
choosePinEntry = PinEntry.empty(4).fillWith("1111"),
creationFailure = CreatePinFailure.ChosenPinBlacklisted
),
) )
} }
@ -38,12 +49,12 @@ fun aCreatePinState(
choosePinEntry: PinEntry = PinEntry.empty(4), choosePinEntry: PinEntry = PinEntry.empty(4),
confirmPinEntry: PinEntry = PinEntry.empty(4), confirmPinEntry: PinEntry = PinEntry.empty(4),
isConfirmationStep: Boolean = false, isConfirmationStep: Boolean = false,
creationFailure: PinCreationFailure? = null, creationFailure: CreatePinFailure? = null,
) = CreatePinState( ) = CreatePinState(
choosePinEntry = choosePinEntry, choosePinEntry = choosePinEntry,
confirmPinEntry = confirmPinEntry, confirmPinEntry = confirmPinEntry,
isConfirmationStep = isConfirmationStep, isConfirmationStep = isConfirmationStep,
creationFailure = creationFailure, createPinFailure = creationFailure,
eventSink = {} eventSink = {}
) )

View file

@ -42,12 +42,13 @@ import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import io.element.android.features.lockscreen.impl.create.model.PinDigit import io.element.android.features.lockscreen.impl.create.model.PinDigit
import io.element.android.features.lockscreen.impl.create.model.PinEntry import io.element.android.features.lockscreen.impl.create.model.PinEntry
import io.element.android.features.lockscreen.impl.create.validation.CreatePinFailure
import io.element.android.libraries.designsystem.atomic.molecules.IconTitleSubtitleMolecule import io.element.android.libraries.designsystem.atomic.molecules.IconTitleSubtitleMolecule
import io.element.android.libraries.designsystem.atomic.pages.HeaderFooterPage import io.element.android.libraries.designsystem.atomic.pages.HeaderFooterPage
import io.element.android.libraries.designsystem.components.button.BackButton import io.element.android.libraries.designsystem.components.button.BackButton
import io.element.android.libraries.designsystem.components.dialogs.ErrorDialog
import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.designsystem.theme.components.Button
import io.element.android.libraries.designsystem.theme.components.Scaffold import io.element.android.libraries.designsystem.theme.components.Scaffold
import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.designsystem.theme.components.TopAppBar import io.element.android.libraries.designsystem.theme.components.TopAppBar
@ -76,7 +77,6 @@ fun CreatePinView(
.padding(padding) .padding(padding)
.consumeWindowInsets(padding), .consumeWindowInsets(padding),
header = { CreatePinHeader(state.isConfirmationStep) }, header = { CreatePinHeader(state.isConfirmationStep) },
footer = { CreatePinFooter() },
content = { CreatePinContent(state) } content = { CreatePinContent(state) }
) )
} }
@ -96,17 +96,6 @@ private fun CreatePinHeader(
) )
} }
@Composable
private fun CreatePinFooter() {
Button(
modifier = Modifier.fillMaxWidth(),
text = "Continue",
onClick = {
}
)
}
@Composable @Composable
private fun CreatePinContent( private fun CreatePinContent(
state: CreatePinState, state: CreatePinState,
@ -121,6 +110,32 @@ private fun CreatePinContent(
.padding(top = 36.dp) .padding(top = 36.dp)
.fillMaxWidth() .fillMaxWidth()
) )
if (state.createPinFailure != null) {
ErrorDialog(
modifier = modifier,
title = state.createPinFailure.title(),
content = state.createPinFailure.content(),
onDismiss = {
state.eventSink(CreatePinEvents.ClearFailure)
}
)
}
}
@Composable
private fun CreatePinFailure.content(): String {
return when (this) {
CreatePinFailure.ChosenPinBlacklisted -> "You cannot choose this as your PIN code for security reasons"
CreatePinFailure.ConfirmationPinNotMatching -> "Please enter the same PIN twice"
}
}
@Composable
private fun CreatePinFailure.title(): String {
return when (this) {
CreatePinFailure.ChosenPinBlacklisted -> "Choose a different PIN"
CreatePinFailure.ConfirmationPinNotMatching -> "PINs don't match"
}
} }
@Composable @Composable

View file

@ -16,7 +16,7 @@
package io.element.android.features.lockscreen.impl.create.validation package io.element.android.features.lockscreen.impl.create.validation
sealed interface PinCreationFailure { sealed interface CreatePinFailure {
data object ChosenPinBlacklisted : PinCreationFailure data object ChosenPinBlacklisted : CreatePinFailure
data object ConfirmationPinNotMatching : PinCreationFailure data object ConfirmationPinNotMatching : CreatePinFailure
} }

View file

@ -25,14 +25,14 @@ class PinValidator @Inject constructor() {
sealed interface Result { sealed interface Result {
data object Valid : Result data object Valid : Result
data class Invalid(val failure: PinCreationFailure) : Result data class Invalid(val failure: CreatePinFailure) : Result
} }
fun isPinValid(pinEntry: PinEntry): Result { fun isPinValid(pinEntry: PinEntry): Result {
val pinAsText = pinEntry.toText() val pinAsText = pinEntry.toText()
val isBlacklisted = BLACKLIST.any { it == pinAsText } val isBlacklisted = BLACKLIST.any { it == pinAsText }
return if (isBlacklisted) { return if (isBlacklisted) {
Result.Invalid(PinCreationFailure.ChosenPinBlacklisted) Result.Invalid(CreatePinFailure.ChosenPinBlacklisted)
} else { } else {
Result.Valid Result.Valid
} }