From 9822d8825efdab17e31a1fee0ca55c6bbb69caa2 Mon Sep 17 00:00:00 2001 From: ganfra Date: Fri, 20 Oct 2023 15:43:55 +0200 Subject: [PATCH] Pin unlock : start branching logic --- .../lockscreen/impl/pin/model/PinEntry.kt | 24 ++++++++++++- .../impl/setup/SetupPinPresenter.kt | 4 +-- .../state/DefaultLockScreenStateService.kt | 3 +- .../lockscreen/impl/unlock/PinUnlockEvents.kt | 3 ++ .../impl/unlock/PinUnlockPresenter.kt | 25 +++++++++++++ .../lockscreen/impl/unlock/PinUnlockState.kt | 3 ++ .../impl/unlock/PinUnlockStateProvider.kt | 6 +++- .../lockscreen/impl/unlock/PinUnlockView.kt | 36 +++++++++---------- .../impl/unlock/numpad/PinKeypad.kt | 10 +++--- .../impl/unlock/numpad/PinKeypadModel.kt | 2 +- 10 files changed, 85 insertions(+), 31 deletions(-) diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/pin/model/PinEntry.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/pin/model/PinEntry.kt index d14b71ffe2..92dda869a6 100644 --- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/pin/model/PinEntry.kt +++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/pin/model/PinEntry.kt @@ -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() diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/SetupPinPresenter.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/SetupPinPresenter.kt index af34c387d2..8561e5333c 100644 --- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/SetupPinPresenter.kt +++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/SetupPinPresenter.kt @@ -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 diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/state/DefaultLockScreenStateService.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/state/DefaultLockScreenStateService.kt index dbfeca2c6a..a071de06e9 100644 --- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/state/DefaultLockScreenStateService.kt +++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/state/DefaultLockScreenStateService.kt @@ -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 } } diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/PinUnlockEvents.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/PinUnlockEvents.kt index ba35f3045c..8dddd40e8a 100644 --- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/PinUnlockEvents.kt +++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/PinUnlockEvents.kt @@ -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 } diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/PinUnlockPresenter.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/PinUnlockPresenter.kt index 24cba574af..d673ad7966 100644 --- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/PinUnlockPresenter.kt +++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/PinUnlockPresenter.kt @@ -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 + } + } } diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/PinUnlockState.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/PinUnlockState.kt index dec731c040..69d3213c7e 100644 --- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/PinUnlockState.kt +++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/PinUnlockState.kt @@ -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 ) diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/PinUnlockStateProvider.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/PinUnlockStateProvider.kt index f5afb44fa9..5120818316 100644 --- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/PinUnlockStateProvider.kt +++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/PinUnlockStateProvider.kt @@ -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 { override val values: Sequence @@ -25,6 +26,9 @@ open class PinUnlockStateProvider : PreviewParameterProvider { ) } -fun aPinUnlockState() = PinUnlockState( +fun aPinUnlockState( + pinEntry: PinEntry = PinEntry.empty(4), +) = PinUnlockState( + pinEntry = pinEntry, eventSink = {} ) diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/PinUnlockView.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/PinUnlockView.kt index 47027853b4..5631f33e32 100644 --- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/PinUnlockView.kt +++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/PinUnlockView.kt @@ -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) } } diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/numpad/PinKeypad.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/numpad/PinKeypad.kt index 44a09ed08f..d22a34b732 100644 --- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/numpad/PinKeypad.kt +++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/numpad/PinKeypad.kt @@ -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) }, ) } diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/numpad/PinKeypadModel.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/numpad/PinKeypadModel.kt index 4ea42e7f42..f1430dcaa5 100644 --- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/numpad/PinKeypadModel.kt +++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/numpad/PinKeypadModel.kt @@ -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 }