Browse Source

Pin unlock : add signout prompt

pull/1624/head
ganfra 11 months ago
parent
commit
67d4271f4d
  1. 1
      features/lockscreen/impl/build.gradle.kts
  2. 2
      features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/pin/DefaultPinCodeManager.kt
  3. 2
      features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/pin/PinCodeManager.kt
  4. 3
      features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/pin/model/PinEntry.kt
  5. 1
      features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/PinUnlockEvents.kt
  6. 19
      features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/PinUnlockPresenter.kt
  7. 7
      features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/PinUnlockState.kt
  8. 10
      features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/PinUnlockStateProvider.kt
  9. 36
      features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/PinUnlockView.kt
  10. 5
      libraries/featureflag/impl/src/main/kotlin/io/element/android/libraries/featureflag/impl/PreferencesFeatureFlagProvider.kt

1
features/lockscreen/impl/build.gradle.kts

@ -40,6 +40,7 @@ dependencies { @@ -40,6 +40,7 @@ dependencies {
implementation(projects.libraries.designsystem)
implementation(projects.libraries.featureflag.api)
implementation(projects.libraries.cryptography.api)
implementation(projects.libraries.uiStrings)
testImplementation(libs.test.junit)
testImplementation(libs.coroutines.test)

2
features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/pin/DefaultPinCodeManager.kt

@ -37,7 +37,7 @@ class DefaultPinCodeManager @Inject constructor( @@ -37,7 +37,7 @@ class DefaultPinCodeManager @Inject constructor(
return pinCodeStore.hasPinCode()
}
override suspend fun SetupPinCode(pinCode: String) {
override suspend fun setupPinCode(pinCode: String) {
val secretKey = secretKeyProvider.getOrCreateKey(SECRET_KEY_ALIAS)
val encryptedPinCode = encryptionDecryptionService.encrypt(secretKey, pinCode.toByteArray()).toBase64()
pinCodeStore.saveEncryptedPinCode(encryptedPinCode)

2
features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/pin/PinCodeManager.kt

@ -30,7 +30,7 @@ interface PinCodeManager { @@ -30,7 +30,7 @@ interface PinCodeManager {
* Creates a new encrypted pin code.
* @param pinCode the clear pin code to create
*/
suspend fun SetupPinCode(pinCode: String)
suspend fun setupPinCode(pinCode: String)
/**
* @return true if the pin code is correct.

3
features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/pin/model/PinEntry.kt

@ -18,10 +18,11 @@ package io.element.android.features.lockscreen.impl.pin.model @@ -18,10 +18,11 @@ package io.element.android.features.lockscreen.impl.pin.model
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toPersistentList
import java.io.Serializable
data class PinEntry(
val digits: ImmutableList<PinDigit>,
) {
): Serializable {
companion object {
fun empty(size: Int): PinEntry {

1
features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/PinUnlockEvents.kt

@ -21,4 +21,5 @@ import io.element.android.features.lockscreen.impl.unlock.numpad.PinKeypadModel @@ -21,4 +21,5 @@ import io.element.android.features.lockscreen.impl.unlock.numpad.PinKeypadModel
sealed interface PinUnlockEvents {
data class OnPinKeypadPressed(val pinKeypadModel: PinKeypadModel) : PinUnlockEvents
data object Unlock : PinUnlockEvents
data object OnForgetPin : PinUnlockEvents
}

19
features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/PinUnlockPresenter.kt

@ -18,8 +18,9 @@ package io.element.android.features.lockscreen.impl.unlock @@ -18,8 +18,9 @@ package io.element.android.features.lockscreen.impl.unlock
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
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.features.lockscreen.api.LockScreenStateService
import io.element.android.features.lockscreen.impl.pin.model.PinEntry
@ -36,10 +37,18 @@ class PinUnlockPresenter @Inject constructor( @@ -36,10 +37,18 @@ class PinUnlockPresenter @Inject constructor(
@Composable
override fun present(): PinUnlockState {
var pinEntry by remember {
var pinEntry by rememberSaveable {
mutableStateOf(PinEntry.empty(4))
}
var remainingAttempts by rememberSaveable {
mutableIntStateOf(3)
}
var showWrongPinTitle by rememberSaveable {
mutableStateOf(false)
}
var showSignOutPrompt by rememberSaveable {
mutableStateOf(false)
}
fun handleEvents(event: PinUnlockEvents) {
when (event) {
@ -50,10 +59,14 @@ class PinUnlockPresenter @Inject constructor( @@ -50,10 +59,14 @@ class PinUnlockPresenter @Inject constructor(
coroutineScope.launch { pinStateService.unlock() }
}
}
PinUnlockEvents.OnForgetPin -> showSignOutPrompt = true
}
}
return PinUnlockState(
pinEntry = pinEntry,
showWrongPinTitle = showWrongPinTitle,
remainingAttempts = remainingAttempts,
showSignOutPrompt = showSignOutPrompt,
eventSink = ::handleEvents
)
}

7
features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/PinUnlockState.kt

@ -20,5 +20,10 @@ import io.element.android.features.lockscreen.impl.pin.model.PinEntry @@ -20,5 +20,10 @@ import io.element.android.features.lockscreen.impl.pin.model.PinEntry
data class PinUnlockState(
val pinEntry: PinEntry,
val showWrongPinTitle: Boolean,
val remainingAttempts: Int,
val showSignOutPrompt: Boolean,
val eventSink: (PinUnlockEvents) -> Unit
)
) {
val isSignOutPromptCancellable = remainingAttempts > 0
}

10
features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/PinUnlockStateProvider.kt

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

36
features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/PinUnlockView.kt

@ -38,6 +38,7 @@ import androidx.compose.material3.MaterialTheme @@ -38,6 +38,7 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.pluralStringResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.PreviewParameter
@ -46,6 +47,8 @@ import io.element.android.features.lockscreen.impl.R @@ -46,6 +47,8 @@ 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.libraries.designsystem.components.dialogs.ConfirmationDialog
import io.element.android.libraries.designsystem.components.dialogs.ErrorDialog
import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.designsystem.theme.components.Icon
@ -53,6 +56,7 @@ import io.element.android.libraries.designsystem.theme.components.Surface @@ -53,6 +56,7 @@ import io.element.android.libraries.designsystem.theme.components.Surface
import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.designsystem.theme.components.TextButton
import io.element.android.libraries.theme.ElementTheme
import io.element.android.libraries.ui.strings.CommonStrings
@Composable
fun PinUnlockView(
@ -101,6 +105,22 @@ fun PinUnlockView( @@ -101,6 +105,22 @@ fun PinUnlockView(
modifier = commonModifier,
)
}
if (state.showSignOutPrompt) {
if (state.isSignOutPromptCancellable) {
ConfirmationDialog(
title = stringResource(id = R.string.screen_app_lock_signout_alert_title),
content = stringResource(id = R.string.screen_app_lock_signout_alert_message),
onSubmitClicked = {},
onDismiss = {},
)
} else {
ErrorDialog(
title = stringResource(id = R.string.screen_app_lock_signout_alert_title),
content = stringResource(id = R.string.screen_app_lock_signout_alert_message),
onDismiss = {},
)
}
}
}
}
}
@ -196,7 +216,7 @@ private fun PinUnlockHeader( @@ -196,7 +216,7 @@ private fun PinUnlockHeader(
)
Spacer(modifier = Modifier.height(16.dp))
Text(
text = "Enter your PIN",
text = stringResource(id = CommonStrings.common_enter_your_pin),
modifier = Modifier
.fillMaxWidth(),
textAlign = TextAlign.Center,
@ -204,12 +224,22 @@ private fun PinUnlockHeader( @@ -204,12 +224,22 @@ private fun PinUnlockHeader(
color = MaterialTheme.colorScheme.primary,
)
Spacer(Modifier.height(8.dp))
val subtitle = if (state.showWrongPinTitle) {
pluralStringResource(id = R.plurals.screen_app_lock_subtitle_wrong_pin, count = state.remainingAttempts, state.remainingAttempts)
} else {
stringResource(id = R.string.screen_app_lock_subtitle)
}
val subtitleColor = if (state.showWrongPinTitle) {
MaterialTheme.colorScheme.error
} else {
MaterialTheme.colorScheme.secondary
}
Text(
text = "You have 3 attempts to unlock",
text = subtitle,
modifier = Modifier.fillMaxWidth(),
textAlign = TextAlign.Center,
style = ElementTheme.typography.fontBodyMdRegular,
color = MaterialTheme.colorScheme.secondary,
color = subtitleColor,
)
Spacer(Modifier.height(24.dp))
PinDotsRow(state.pinEntry)

5
libraries/featureflag/impl/src/main/kotlin/io/element/android/libraries/featureflag/impl/PreferencesFeatureFlagProvider.kt

@ -24,6 +24,7 @@ import androidx.datastore.preferences.core.edit @@ -24,6 +24,7 @@ import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.preferencesDataStore
import io.element.android.libraries.di.ApplicationContext
import io.element.android.libraries.featureflag.api.Feature
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map
import javax.inject.Inject
@ -44,10 +45,10 @@ class PreferencesFeatureFlagProvider @Inject constructor(@ApplicationContext con @@ -44,10 +45,10 @@ class PreferencesFeatureFlagProvider @Inject constructor(@ApplicationContext con
}
}
override suspend fun isFeatureEnabled(feature: Feature): Boolean {
override fun isFeatureEnabled(feature: Feature): Flow<Boolean> {
return store.data.map { prefs ->
prefs[booleanPreferencesKey(feature.key)] ?: feature.defaultValue
}.first()
}
}
override fun hasFeature(feature: Feature): Boolean {

Loading…
Cancel
Save