Browse Source

Pin create : add some more states to manage validation and confirmation

pull/1608/head
ganfra 11 months ago
parent
commit
c15a193d4a
  1. 1
      features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/create/CreatePinEvents.kt
  2. 55
      features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/create/CreatePinPresenter.kt
  3. 14
      features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/create/CreatePinState.kt
  4. 31
      features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/create/CreatePinStateProvider.kt
  5. 10
      features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/create/CreatePinView.kt
  6. 4
      features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/create/model/PinEntry.kt
  7. 22
      features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/create/validation/PinCreationFailure.kt
  8. 40
      features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/create/validation/PinValidator.kt

1
features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/create/CreatePinEvents.kt

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

55
features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/create/CreatePinPresenter.kt

@ -20,28 +20,73 @@ import androidx.compose.runtime.Composable @@ -20,28 +20,73 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
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.PinValidator
import io.element.android.features.lockscreen.impl.pin.PinCodeManager
import io.element.android.libraries.architecture.Presenter
import javax.inject.Inject
class CreatePinPresenter @Inject constructor() : Presenter<CreatePinState> {
private const val PIN_SIZE = 4
class CreatePinPresenter @Inject constructor(
private val pinValidator: PinValidator,
private val pinCodeManager: PinCodeManager,
) : Presenter<CreatePinState> {
@Composable
override fun present(): CreatePinState {
val pinEntry by remember {
mutableStateOf(PinEntry.empty(4))
var choosePinEntry by remember {
mutableStateOf(PinEntry.empty(PIN_SIZE))
}
var confirmPinEntry by remember {
mutableStateOf(PinEntry.empty(PIN_SIZE))
}
var isConfirmationStep by remember {
mutableStateOf(false)
}
var creationFailure by remember {
mutableStateOf<PinCreationFailure?>(null)
}
fun handleEvents(event: CreatePinEvents) {
when (event) {
is CreatePinEvents.OnPinEntryChanged -> {
pinEntry.fillWith(event.entryAsText)
if (isConfirmationStep) {
confirmPinEntry = confirmPinEntry.fillWith(event.entryAsText)
if (confirmPinEntry.isPinComplete()) {
if (confirmPinEntry == choosePinEntry) {
//pinCodeManager.savePin(confirmPinEntry.toText())
} else {
confirmPinEntry = PinEntry.empty(PIN_SIZE)
creationFailure = PinCreationFailure.ConfirmationPinNotMatching
}
}
} else {
choosePinEntry = choosePinEntry.fillWith(event.entryAsText)
if (choosePinEntry.isPinComplete()) {
when (val pinValidationResult = pinValidator.isPinValid(choosePinEntry)) {
is PinValidator.Result.Invalid -> {
choosePinEntry = PinEntry.empty(PIN_SIZE)
creationFailure = pinValidationResult.failure
}
PinValidator.Result.Valid -> isConfirmationStep = true
}
}
}
}
CreatePinEvents.OnClearValidationFailure -> {
creationFailure = null
}
}
}
return CreatePinState(
pinEntry = pinEntry,
choosePinEntry = choosePinEntry,
confirmPinEntry = confirmPinEntry,
isConfirmationStep = isConfirmationStep,
creationFailure = creationFailure,
eventSink = ::handleEvents
)
}

14
features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/create/CreatePinState.kt

@ -17,8 +17,18 @@ @@ -17,8 +17,18 @@
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.validation.PinCreationFailure
data class CreatePinState(
val pinEntry: PinEntry,
val choosePinEntry: PinEntry,
val confirmPinEntry: PinEntry,
val isConfirmationStep: Boolean,
val creationFailure: PinCreationFailure?,
val eventSink: (CreatePinEvents) -> Unit
)
) {
val activePinEntry = if (isConfirmationStep) {
confirmPinEntry
} else {
choosePinEntry
}
}

31
features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/create/CreatePinStateProvider.kt

@ -17,26 +17,33 @@ @@ -17,26 +17,33 @@
package io.element.android.features.lockscreen.impl.create
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import io.element.android.features.lockscreen.impl.create.model.PinDigit
import io.element.android.features.lockscreen.impl.create.model.PinEntry
import kotlinx.collections.immutable.persistentListOf
import io.element.android.features.lockscreen.impl.create.validation.PinCreationFailure
open class CreatePinStateProvider : PreviewParameterProvider<CreatePinState> {
override val values: Sequence<CreatePinState>
get() = sequenceOf(
aCreatePinState(),
// Add other states here
aCreatePinState(
choosePinEntry = PinEntry.empty(4).fillWith("12")
),
aCreatePinState(
choosePinEntry = PinEntry.empty(4).fillWith("1789"),
isConfirmationStep = true,
),
)
}
fun aCreatePinState() = CreatePinState(
pinEntry = PinEntry(
digits = persistentListOf(
PinDigit.Filled('1'),
PinDigit.Filled('2'),
PinDigit.Empty,
PinDigit.Empty,
)
),
fun aCreatePinState(
choosePinEntry: PinEntry = PinEntry.empty(4),
confirmPinEntry: PinEntry = PinEntry.empty(4),
isConfirmationStep: Boolean = false,
creationFailure: PinCreationFailure? = null,
) = CreatePinState(
choosePinEntry = choosePinEntry,
confirmPinEntry = confirmPinEntry,
isConfirmationStep = isConfirmationStep,
creationFailure = creationFailure,
eventSink = {}
)

10
features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/create/CreatePinView.kt

@ -75,7 +75,7 @@ fun CreatePinView( @@ -75,7 +75,7 @@ fun CreatePinView(
modifier = Modifier
.padding(padding)
.consumeWindowInsets(padding),
header = { CreatePinHeader() },
header = { CreatePinHeader(state.isConfirmationStep) },
footer = { CreatePinFooter() },
content = { CreatePinContent(state) }
)
@ -85,11 +85,12 @@ fun CreatePinView( @@ -85,11 +85,12 @@ fun CreatePinView(
@Composable
private fun CreatePinHeader(
isValidationStep: Boolean,
modifier: Modifier = Modifier,
) {
IconTitleSubtitleMolecule(
modifier = modifier,
title = "Choose 4 digit PIN",
title = if (isValidationStep) "Confirm PIN" else "Choose 4 digit PIN",
subTitle = "Lock Element to add extra security to your chats.\n\nChoose something memorable. If you forget this PIN, you will be logged out of the app",
iconImageVector = Icons.Default.Lock,
)
@ -111,9 +112,8 @@ private fun CreatePinContent( @@ -111,9 +112,8 @@ private fun CreatePinContent(
state: CreatePinState,
modifier: Modifier = Modifier,
) {
PinEntryTextField(
state.pinEntry,
state.activePinEntry,
onValueChange = {
state.eventSink(CreatePinEvents.OnPinEntryChanged(it))
},
@ -135,7 +135,7 @@ fun PinEntryTextField( @@ -135,7 +135,7 @@ fun PinEntryTextField(
onValueChange = {
onValueChange(it.text)
},
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.NumberPassword),
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
decorationBox = {
PinEntryRow(pinEntry = pinEntry)
}

4
features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/create/model/PinEntry.kt

@ -50,6 +50,10 @@ data class PinEntry( @@ -50,6 +50,10 @@ data class PinEntry(
return copy(digits = newDigits.toPersistentList())
}
fun clear(): PinEntry {
return fillWith("")
}
fun isPinComplete(): Boolean {
return digits.all { it is PinDigit.Filled }
}

22
features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/create/validation/PinCreationFailure.kt

@ -0,0 +1,22 @@ @@ -0,0 +1,22 @@
/*
* 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.features.lockscreen.impl.create.validation
sealed interface PinCreationFailure {
data object ChosenPinBlacklisted : PinCreationFailure
data object ConfirmationPinNotMatching : PinCreationFailure
}

40
features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/create/validation/PinValidator.kt

@ -0,0 +1,40 @@ @@ -0,0 +1,40 @@
/*
* 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.features.lockscreen.impl.create.validation
import io.element.android.features.lockscreen.impl.create.model.PinEntry
import javax.inject.Inject
private val BLACKLIST = listOf("0000", "1234")
class PinValidator @Inject constructor() {
sealed interface Result {
data object Valid : Result
data class Invalid(val failure: PinCreationFailure) : Result
}
fun isPinValid(pinEntry: PinEntry): Result {
val pinAsText = pinEntry.toText()
val isBlacklisted = BLACKLIST.any { it == pinAsText }
return if (isBlacklisted) {
Result.Invalid(PinCreationFailure.ChosenPinBlacklisted)
} else {
Result.Valid
}
}
}
Loading…
Cancel
Save