PIN: branch SignOut
This commit is contained in:
parent
f1988e3093
commit
54cd62ab76
16 changed files with 120 additions and 36 deletions
|
|
@ -56,6 +56,10 @@ class DefaultLockScreenService @Inject constructor(
|
|||
override fun onPinCodeVerified() {
|
||||
_lockScreenState.value = LockScreenLockState.Unlocked
|
||||
}
|
||||
|
||||
override fun onPinCodeRemoved() {
|
||||
_lockScreenState.value = LockScreenLockState.Unlocked
|
||||
}
|
||||
})
|
||||
coroutineScope.lockIfNeeded()
|
||||
observeAppForegroundState()
|
||||
|
|
|
|||
|
|
@ -34,10 +34,10 @@ import io.element.android.libraries.architecture.BackstackNode
|
|||
import io.element.android.libraries.architecture.NodeInputs
|
||||
import io.element.android.libraries.architecture.animation.rememberDefaultTransitionHandler
|
||||
import io.element.android.libraries.architecture.createNode
|
||||
import io.element.android.libraries.di.AppScope
|
||||
import io.element.android.libraries.di.SessionScope
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@ContributesNode(AppScope::class)
|
||||
@ContributesNode(SessionScope::class)
|
||||
class LockScreenFlowNode @AssistedInject constructor(
|
||||
@Assisted buildContext: BuildContext,
|
||||
@Assisted plugins: List<Plugin>,
|
||||
|
|
|
|||
|
|
@ -38,11 +38,11 @@ import io.element.android.features.lockscreen.impl.unlock.PinUnlockNode
|
|||
import io.element.android.libraries.architecture.BackstackNode
|
||||
import io.element.android.libraries.architecture.animation.rememberDefaultTransitionHandler
|
||||
import io.element.android.libraries.architecture.createNode
|
||||
import io.element.android.libraries.di.AppScope
|
||||
import io.element.android.libraries.di.SessionScope
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@ContributesNode(AppScope::class)
|
||||
@ContributesNode(SessionScope::class)
|
||||
class LockScreenSettingsFlowNode @AssistedInject constructor(
|
||||
@Assisted buildContext: BuildContext,
|
||||
@Assisted plugins: List<Plugin>,
|
||||
|
|
|
|||
|
|
@ -25,9 +25,9 @@ import com.bumble.appyx.core.plugin.plugins
|
|||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedInject
|
||||
import io.element.android.anvilannotations.ContributesNode
|
||||
import io.element.android.libraries.di.AppScope
|
||||
import io.element.android.libraries.di.SessionScope
|
||||
|
||||
@ContributesNode(AppScope::class)
|
||||
@ContributesNode(SessionScope::class)
|
||||
class LockScreenSettingsNode @AssistedInject constructor(
|
||||
@Assisted buildContext: BuildContext,
|
||||
@Assisted plugins: List<Plugin>,
|
||||
|
|
|
|||
|
|
@ -24,9 +24,9 @@ import com.bumble.appyx.core.plugin.Plugin
|
|||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedInject
|
||||
import io.element.android.anvilannotations.ContributesNode
|
||||
import io.element.android.libraries.di.AppScope
|
||||
import io.element.android.libraries.di.SessionScope
|
||||
|
||||
@ContributesNode(AppScope::class)
|
||||
@ContributesNode(SessionScope::class)
|
||||
class SetupPinNode @AssistedInject constructor(
|
||||
@Assisted buildContext: BuildContext,
|
||||
@Assisted plugins: List<Plugin>,
|
||||
|
|
|
|||
|
|
@ -22,4 +22,6 @@ sealed interface PinUnlockEvents {
|
|||
data class OnPinKeypadPressed(val pinKeypadModel: PinKeypadModel) : PinUnlockEvents
|
||||
data object OnForgetPin : PinUnlockEvents
|
||||
data object ClearSignOutPrompt : PinUnlockEvents
|
||||
data object SignOut : PinUnlockEvents
|
||||
data object OnUseBiometric : PinUnlockEvents
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,9 +24,9 @@ import com.bumble.appyx.core.plugin.Plugin
|
|||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedInject
|
||||
import io.element.android.anvilannotations.ContributesNode
|
||||
import io.element.android.libraries.di.AppScope
|
||||
import io.element.android.libraries.di.SessionScope
|
||||
|
||||
@ContributesNode(AppScope::class)
|
||||
@ContributesNode(SessionScope::class)
|
||||
class PinUnlockNode @AssistedInject constructor(
|
||||
@Assisted buildContext: BuildContext,
|
||||
@Assisted plugins: List<Plugin>,
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ package io.element.android.features.lockscreen.impl.unlock
|
|||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
|
|
@ -29,10 +30,16 @@ import io.element.android.features.lockscreen.impl.pin.model.PinEntry
|
|||
import io.element.android.features.lockscreen.impl.unlock.keypad.PinKeypadModel
|
||||
import io.element.android.libraries.architecture.Async
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
import io.element.android.libraries.architecture.runCatchingUpdatingState
|
||||
import io.element.android.libraries.matrix.api.MatrixClient
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
class PinUnlockPresenter @Inject constructor(
|
||||
private val pinCodeManager: PinCodeManager,
|
||||
private val matrixClient: MatrixClient,
|
||||
private val coroutineScope: CoroutineScope,
|
||||
) : Presenter<PinUnlockState> {
|
||||
|
||||
@Composable
|
||||
|
|
@ -51,6 +58,10 @@ class PinUnlockPresenter @Inject constructor(
|
|||
mutableStateOf(false)
|
||||
}
|
||||
|
||||
val signOutAction = remember {
|
||||
mutableStateOf<Async<String?>>(Async.Uninitialized)
|
||||
}
|
||||
|
||||
LaunchedEffect(pinEntry) {
|
||||
if (pinEntry.isComplete()) {
|
||||
val isVerified = pinCodeManager.verifyPinCode(pinEntry.toText())
|
||||
|
|
@ -73,6 +84,13 @@ class PinUnlockPresenter @Inject constructor(
|
|||
}
|
||||
PinUnlockEvents.OnForgetPin -> showSignOutPrompt = true
|
||||
PinUnlockEvents.ClearSignOutPrompt -> showSignOutPrompt = false
|
||||
PinUnlockEvents.SignOut -> {
|
||||
showSignOutPrompt = false
|
||||
coroutineScope.signOut(signOutAction)
|
||||
}
|
||||
PinUnlockEvents.OnUseBiometric -> {
|
||||
//TODO
|
||||
}
|
||||
}
|
||||
}
|
||||
return PinUnlockState(
|
||||
|
|
@ -80,10 +98,17 @@ class PinUnlockPresenter @Inject constructor(
|
|||
showWrongPinTitle = showWrongPinTitle,
|
||||
remainingAttempts = remainingAttempts,
|
||||
showSignOutPrompt = showSignOutPrompt,
|
||||
signOutAction = signOutAction.value,
|
||||
eventSink = ::handleEvents
|
||||
)
|
||||
}
|
||||
|
||||
private fun CoroutineScope.signOut(signOutAction: MutableState<Async<String?>>) = launch {
|
||||
suspend {
|
||||
matrixClient.logout()
|
||||
}.runCatchingUpdatingState(signOutAction)
|
||||
}
|
||||
|
||||
private fun PinEntry.process(pinKeypadModel: PinKeypadModel): PinEntry {
|
||||
return when (pinKeypadModel) {
|
||||
PinKeypadModel.Back -> deleteLast()
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ data class PinUnlockState(
|
|||
val showWrongPinTitle: Boolean,
|
||||
val remainingAttempts: Async<Int>,
|
||||
val showSignOutPrompt: Boolean,
|
||||
val signOutAction: Async<String?>,
|
||||
val eventSink: (PinUnlockEvents) -> Unit
|
||||
) {
|
||||
val isSignOutPromptCancellable = (remainingAttempts.dataOrNull() ?: 0) > 0
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ open class PinUnlockStateProvider : PreviewParameterProvider<PinUnlockState> {
|
|||
aPinUnlockState(showWrongPinTitle = true),
|
||||
aPinUnlockState(showSignOutPrompt = true),
|
||||
aPinUnlockState(showSignOutPrompt = true, remainingAttempts = 0),
|
||||
aPinUnlockState(signOutAction = Async.Loading()),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -36,10 +37,12 @@ fun aPinUnlockState(
|
|||
remainingAttempts: Int = 3,
|
||||
showWrongPinTitle: Boolean = false,
|
||||
showSignOutPrompt: Boolean = false,
|
||||
signOutAction: Async<String?> = Async.Uninitialized,
|
||||
) = PinUnlockState(
|
||||
pinEntry = pinEntry,
|
||||
showWrongPinTitle = showWrongPinTitle,
|
||||
remainingAttempts = Async.Success(remainingAttempts),
|
||||
showSignOutPrompt = showSignOutPrompt,
|
||||
signOutAction = signOutAction,
|
||||
eventSink = {}
|
||||
)
|
||||
|
|
|
|||
|
|
@ -48,6 +48,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.keypad.PinKeypad
|
||||
import io.element.android.libraries.architecture.Async
|
||||
import io.element.android.libraries.designsystem.components.ProgressDialog
|
||||
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
|
||||
|
|
@ -79,7 +81,13 @@ fun PinUnlockView(
|
|||
}
|
||||
val footer = @Composable {
|
||||
PinUnlockFooter(
|
||||
modifier = Modifier.padding(top = 24.dp)
|
||||
modifier = Modifier.padding(top = 24.dp),
|
||||
onUseBiometric = {
|
||||
state.eventSink(PinUnlockEvents.OnUseBiometric)
|
||||
},
|
||||
onForgotPin = {
|
||||
state.eventSink(PinUnlockEvents.OnForgetPin)
|
||||
},
|
||||
)
|
||||
}
|
||||
val content = @Composable { constraints: BoxWithConstraintsScope ->
|
||||
|
|
@ -107,23 +115,42 @@ 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 = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (state.showSignOutPrompt) {
|
||||
SignOutPrompt(
|
||||
isCancellable = state.isSignOutPromptCancellable,
|
||||
onSignOut = { state.eventSink(PinUnlockEvents.SignOut) },
|
||||
onDismiss = { state.eventSink(PinUnlockEvents.ClearSignOutPrompt) },
|
||||
)
|
||||
}
|
||||
if (state.signOutAction is Async.Loading) {
|
||||
ProgressDialog(text = stringResource(id = R.string.screen_signout_in_progress_dialog_content))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SignOutPrompt(
|
||||
isCancellable: Boolean,
|
||||
onSignOut: () -> Unit,
|
||||
onDismiss: () -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
if (isCancellable) {
|
||||
ConfirmationDialog(
|
||||
title = stringResource(id = R.string.screen_app_lock_signout_alert_title),
|
||||
content = stringResource(id = R.string.screen_app_lock_signout_alert_message),
|
||||
onSubmitClicked = onSignOut,
|
||||
onDismiss = onDismiss,
|
||||
modifier = modifier,
|
||||
)
|
||||
} 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 = onSignOut,
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -255,11 +282,13 @@ private fun PinUnlockHeader(
|
|||
|
||||
@Composable
|
||||
private fun PinUnlockFooter(
|
||||
onUseBiometric: ()->Unit,
|
||||
onForgotPin: ()->Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Row(modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceAround) {
|
||||
TextButton(text = "Use biometric", onClick = { })
|
||||
TextButton(text = stringResource(id = R.string.screen_app_lock_forgot_pin), onClick = { })
|
||||
TextButton(text = "Use biometric", onClick = onUseBiometric)
|
||||
TextButton(text = stringResource(id = R.string.screen_app_lock_forgot_pin), onClick = onForgotPin)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,12 +4,17 @@
|
|||
<item quantity="one">"Wrong PIN. You have %1$d more chance"</item>
|
||||
<item quantity="other">"Wrong PIN. You have %1$d more chances"</item>
|
||||
</plurals>
|
||||
<string name="screen_app_lock_biometric_authentication">"biometric authentication"</string>
|
||||
<string name="screen_app_lock_biometric_unlock">"biometric unlock"</string>
|
||||
<string name="screen_app_lock_forgot_pin">"Forgot PIN?"</string>
|
||||
<string name="screen_app_lock_settings_change_pin">"Change PIN code"</string>
|
||||
<string name="screen_app_lock_settings_enable_biometric_unlock">"Allow biometric unlock"</string>
|
||||
<string name="screen_app_lock_settings_remove_pin">"Remove PIN"</string>
|
||||
<string name="screen_app_lock_settings_remove_pin_alert_message">"Are you sure you want to remove PIN?"</string>
|
||||
<string name="screen_app_lock_settings_remove_pin_alert_title">"Remove PIN?"</string>
|
||||
<string name="screen_app_lock_setup_biometric_unlock_allow_title">"Allow %1$s"</string>
|
||||
<string name="screen_app_lock_setup_biometric_unlock_skip">"I’d rather use PIN"</string>
|
||||
<string name="screen_app_lock_setup_biometric_unlock_subtitle">"Save yourself some time and use %1$s to unlock the app each time"</string>
|
||||
<string name="screen_app_lock_setup_choose_pin">"Choose PIN"</string>
|
||||
<string name="screen_app_lock_setup_confirm_pin">"Confirm PIN"</string>
|
||||
<string name="screen_app_lock_setup_pin_blacklisted_dialog_content">"You cannot choose this as your PIN code for security reasons"</string>
|
||||
|
|
@ -22,4 +27,5 @@ Choose something memorable. If you forget this PIN, you will be logged out of th
|
|||
<string name="screen_app_lock_signout_alert_message">"You’ll need to re-login and create a new PIN to proceed"</string>
|
||||
<string name="screen_app_lock_signout_alert_title">"You are being signed out"</string>
|
||||
<string name="screen_app_lock_subtitle">"You have 3 attempts to unlock"</string>
|
||||
<string name="screen_signout_in_progress_dialog_content">"Signing out…"</string>
|
||||
</resources>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue