Pin unlock : start branching logic
This commit is contained in:
@@ -50,14 +50,36 @@ data class PinEntry(
|
||||
return copy(digits = newDigits.toPersistentList())
|
||||
}
|
||||
|
||||
fun deleteLast(): PinEntry {
|
||||
if (isEmpty()) return this
|
||||
val newDigits = digits.toMutableList()
|
||||
newDigits.indexOfLast { it is PinDigit.Filled }.also { lastFilled ->
|
||||
newDigits[lastFilled] = PinDigit.Empty
|
||||
}
|
||||
return copy(digits = newDigits.toPersistentList())
|
||||
}
|
||||
|
||||
fun addDigit(digit: Char): PinEntry {
|
||||
if (isComplete()) return this
|
||||
val newDigits = digits.toMutableList()
|
||||
newDigits.indexOfFirst { it is PinDigit.Empty }.also { firstEmpty ->
|
||||
newDigits[firstEmpty] = PinDigit.Filled(digit)
|
||||
}
|
||||
return copy(digits = newDigits.toPersistentList())
|
||||
}
|
||||
|
||||
fun clear(): PinEntry {
|
||||
return fillWith("")
|
||||
}
|
||||
|
||||
fun isPinComplete(): Boolean {
|
||||
fun isComplete(): Boolean {
|
||||
return digits.all { it is PinDigit.Filled }
|
||||
}
|
||||
|
||||
fun isEmpty(): Boolean {
|
||||
return digits.all { it is PinDigit.Empty }
|
||||
}
|
||||
|
||||
fun toText(): String {
|
||||
return digits.joinToString("") {
|
||||
it.toText()
|
||||
|
||||
@@ -55,7 +55,7 @@ class SetupPinPresenter @Inject constructor(
|
||||
is SetupPinEvents.OnPinEntryChanged -> {
|
||||
if (isConfirmationStep) {
|
||||
confirmPinEntry = confirmPinEntry.fillWith(event.entryAsText)
|
||||
if (confirmPinEntry.isPinComplete()) {
|
||||
if (confirmPinEntry.isComplete()) {
|
||||
if (confirmPinEntry == choosePinEntry) {
|
||||
//TODO save in db and navigate to next screen
|
||||
} else {
|
||||
@@ -64,7 +64,7 @@ class SetupPinPresenter @Inject constructor(
|
||||
}
|
||||
} else {
|
||||
choosePinEntry = choosePinEntry.fillWith(event.entryAsText)
|
||||
if (choosePinEntry.isPinComplete()) {
|
||||
if (choosePinEntry.isComplete()) {
|
||||
when (val pinValidationResult = pinValidator.isPinValid(choosePinEntry)) {
|
||||
is PinValidator.Result.Invalid -> {
|
||||
setupPinFailure = pinValidationResult.failure
|
||||
|
||||
@@ -25,7 +25,6 @@ import io.element.android.libraries.featureflag.api.FeatureFlagService
|
||||
import io.element.android.libraries.featureflag.api.FeatureFlags
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
@@ -57,7 +56,7 @@ class DefaultLockScreenStateService @Inject constructor(
|
||||
override suspend fun entersBackground() = coroutineScope {
|
||||
lockJob = launch {
|
||||
if (featureFlagService.isFeatureEnabled(FeatureFlags.PinUnlock)) {
|
||||
delay(GRACE_PERIOD_IN_MILLIS)
|
||||
//delay(GRACE_PERIOD_IN_MILLIS)
|
||||
_lockScreenState.value = LockScreenState.Locked
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,9 @@
|
||||
|
||||
package io.element.android.features.lockscreen.impl.unlock
|
||||
|
||||
import io.element.android.features.lockscreen.impl.unlock.numpad.PinKeypadModel
|
||||
|
||||
sealed interface PinUnlockEvents {
|
||||
data class OnPinKeypadPressed(val pinKeypadModel: PinKeypadModel) : PinUnlockEvents
|
||||
data object Unlock : PinUnlockEvents
|
||||
}
|
||||
|
||||
@@ -17,7 +17,13 @@
|
||||
package io.element.android.features.lockscreen.impl.unlock
|
||||
|
||||
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.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.libraries.architecture.Presenter
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
@@ -31,13 +37,32 @@ class PinUnlockPresenter @Inject constructor(
|
||||
@Composable
|
||||
override fun present(): PinUnlockState {
|
||||
|
||||
var pinEntry by remember {
|
||||
mutableStateOf(PinEntry.empty(4))
|
||||
}
|
||||
|
||||
fun handleEvents(event: PinUnlockEvents) {
|
||||
when (event) {
|
||||
PinUnlockEvents.Unlock -> coroutineScope.launch { pinStateService.unlock() }
|
||||
is PinUnlockEvents.OnPinKeypadPressed -> {
|
||||
pinEntry = pinEntry.process(event.pinKeypadModel)
|
||||
if (pinEntry.isComplete()) {
|
||||
coroutineScope.launch { pinStateService.unlock() }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return PinUnlockState(
|
||||
pinEntry = pinEntry,
|
||||
eventSink = ::handleEvents
|
||||
)
|
||||
}
|
||||
|
||||
private fun PinEntry.process(pinKeypadModel: PinKeypadModel): PinEntry {
|
||||
return when (pinKeypadModel) {
|
||||
PinKeypadModel.Back -> deleteLast()
|
||||
is PinKeypadModel.Number -> addDigit(pinKeypadModel.number)
|
||||
PinKeypadModel.Empty -> this
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,9 @@
|
||||
|
||||
package io.element.android.features.lockscreen.impl.unlock
|
||||
|
||||
import io.element.android.features.lockscreen.impl.pin.model.PinEntry
|
||||
|
||||
data class PinUnlockState(
|
||||
val pinEntry: PinEntry,
|
||||
val eventSink: (PinUnlockEvents) -> Unit
|
||||
)
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package io.element.android.features.lockscreen.impl.unlock
|
||||
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import io.element.android.features.lockscreen.impl.pin.model.PinEntry
|
||||
|
||||
open class PinUnlockStateProvider : PreviewParameterProvider<PinUnlockState> {
|
||||
override val values: Sequence<PinUnlockState>
|
||||
@@ -25,6 +26,9 @@ open class PinUnlockStateProvider : PreviewParameterProvider<PinUnlockState> {
|
||||
)
|
||||
}
|
||||
|
||||
fun aPinUnlockState() = PinUnlockState(
|
||||
fun aPinUnlockState(
|
||||
pinEntry: PinEntry = PinEntry.empty(4),
|
||||
) = PinUnlockState(
|
||||
pinEntry = pinEntry,
|
||||
eventSink = {}
|
||||
)
|
||||
|
||||
@@ -36,11 +36,12 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.unit.dp
|
||||
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.libraries.designsystem.atomic.pages.HeaderFooterPage
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
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.Icon
|
||||
import io.element.android.libraries.designsystem.theme.components.Surface
|
||||
import io.element.android.libraries.designsystem.theme.components.Text
|
||||
@@ -53,7 +54,12 @@ fun PinUnlockView(
|
||||
) {
|
||||
Surface(modifier) {
|
||||
HeaderFooterPage(
|
||||
header = { PinUnlockHeader(modifier = Modifier.padding(top = 60.dp, bottom = 12.dp)) },
|
||||
header = {
|
||||
PinUnlockHeader(
|
||||
state = state,
|
||||
modifier = Modifier.padding(top = 60.dp, bottom = 12.dp)
|
||||
)
|
||||
},
|
||||
content = {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
@@ -62,7 +68,9 @@ fun PinUnlockView(
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
PinKeypad(
|
||||
onClick = {}
|
||||
onClick = {
|
||||
state.eventSink(PinUnlockEvents.OnPinKeypadPressed(it))
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -70,26 +78,15 @@ fun PinUnlockView(
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun PinUnlockFooter(state: PinUnlockState) {
|
||||
Button(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
text = "Unlock",
|
||||
onClick = {
|
||||
state.eventSink(PinUnlockEvents.Unlock)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun PinDotsRow(
|
||||
pinEntry: PinEntry,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Row(modifier, horizontalArrangement = spacedBy(8.dp), verticalAlignment = Alignment.CenterVertically) {
|
||||
PinDot(isFilled = true)
|
||||
PinDot(isFilled = true)
|
||||
PinDot(isFilled = false)
|
||||
PinDot(isFilled = false)
|
||||
for (digit in pinEntry.digits) {
|
||||
PinDot(isFilled = digit is PinDigit.Filled)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,6 +109,7 @@ private fun PinDot(
|
||||
|
||||
@Composable
|
||||
private fun PinUnlockHeader(
|
||||
state: PinUnlockState,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Column(modifier, horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
@@ -140,7 +138,7 @@ private fun PinUnlockHeader(
|
||||
color = MaterialTheme.colorScheme.secondary,
|
||||
)
|
||||
Spacer(Modifier.height(24.dp))
|
||||
PinDotsRow()
|
||||
PinDotsRow(state.pinEntry)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -61,25 +61,25 @@ fun PinKeypad(
|
||||
PinKeypadRow(
|
||||
verticalAlignment = verticalAlignment,
|
||||
horizontalArrangement = horizontalArrangement,
|
||||
models = listOf(PinKeypadModel.Number("1"), PinKeypadModel.Number("2"), PinKeypadModel.Number("3")),
|
||||
models = listOf(PinKeypadModel.Number('1'), PinKeypadModel.Number('2'), PinKeypadModel.Number('3')),
|
||||
onClick = onClick,
|
||||
)
|
||||
PinKeypadRow(
|
||||
verticalAlignment = verticalAlignment,
|
||||
horizontalArrangement = horizontalArrangement,
|
||||
models = listOf(PinKeypadModel.Number("4"), PinKeypadModel.Number("5"), PinKeypadModel.Number("6")),
|
||||
models = listOf(PinKeypadModel.Number('4'), PinKeypadModel.Number('5'), PinKeypadModel.Number('6')),
|
||||
onClick = onClick,
|
||||
)
|
||||
PinKeypadRow(
|
||||
verticalAlignment = verticalAlignment,
|
||||
horizontalArrangement = horizontalArrangement,
|
||||
models = listOf(PinKeypadModel.Number("7"), PinKeypadModel.Number("8"), PinKeypadModel.Number("9")),
|
||||
models = listOf(PinKeypadModel.Number('7'), PinKeypadModel.Number('8'), PinKeypadModel.Number('9')),
|
||||
onClick = onClick,
|
||||
)
|
||||
PinKeypadRow(
|
||||
verticalAlignment = verticalAlignment,
|
||||
horizontalArrangement = horizontalArrangement,
|
||||
models = listOf(PinKeypadModel.Empty, PinKeypadModel.Number("0"), PinKeypadModel.Back),
|
||||
models = listOf(PinKeypadModel.Empty, PinKeypadModel.Number('0'), PinKeypadModel.Back),
|
||||
onClick = onClick,
|
||||
)
|
||||
}
|
||||
@@ -114,7 +114,7 @@ private fun PinKeypadRow(
|
||||
PinKeyBadDigitButton(
|
||||
size = 80.dp,
|
||||
modifier = commonModifier,
|
||||
digit = model.number,
|
||||
digit = model.number.toString(),
|
||||
onClick = { onClick(model) },
|
||||
)
|
||||
}
|
||||
|
||||
@@ -22,5 +22,5 @@ import androidx.compose.runtime.Immutable
|
||||
sealed interface PinKeypadModel {
|
||||
data object Empty : PinKeypadModel
|
||||
data object Back : PinKeypadModel
|
||||
data class Number(val number: String) : PinKeypadModel
|
||||
data class Number(val number: Char) : PinKeypadModel
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user