LockScreen : fix one more navigation issue

This commit is contained in:
ganfra 2023-11-07 18:22:40 +01:00
parent a5e098dd1f
commit 41517614c8
9 changed files with 73 additions and 42 deletions

View file

@ -146,7 +146,7 @@ class FtueFlowNode @AssistedInject constructor(
} }
NavTarget.LockScreenSetup -> { NavTarget.LockScreenSetup -> {
val callback = object : LockScreenEntryPoint.Callback { val callback = object : LockScreenEntryPoint.Callback {
override fun onSetupCompleted() { override fun onSetupDone() {
lifecycleScope.launch { moveToNextStep() } lifecycleScope.launch { moveToNextStep() }
} }
} }

View file

@ -31,8 +31,8 @@ interface LockScreenEntryPoint : FeatureEntryPoint {
fun build(): Node fun build(): Node
} }
interface Callback: Plugin { interface Callback : Plugin {
fun onSetupCompleted() fun onSetupDone()
} }
enum class Target { enum class Target {

View file

@ -20,7 +20,6 @@ import android.os.Parcelable
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import com.bumble.appyx.core.composable.Children import com.bumble.appyx.core.composable.Children
import com.bumble.appyx.core.lifecycle.subscribe
import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.modality.BuildContext
import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.node.Node
import com.bumble.appyx.core.plugin.Plugin import com.bumble.appyx.core.plugin.Plugin
@ -30,8 +29,6 @@ import dagger.assisted.Assisted
import dagger.assisted.AssistedInject import dagger.assisted.AssistedInject
import io.element.android.anvilannotations.ContributesNode import io.element.android.anvilannotations.ContributesNode
import io.element.android.features.lockscreen.api.LockScreenEntryPoint import io.element.android.features.lockscreen.api.LockScreenEntryPoint
import io.element.android.features.lockscreen.impl.pin.DefaultPinCodeManagerCallback
import io.element.android.features.lockscreen.impl.pin.PinCodeManager
import io.element.android.features.lockscreen.impl.settings.LockScreenSettingsFlowNode import io.element.android.features.lockscreen.impl.settings.LockScreenSettingsFlowNode
import io.element.android.features.lockscreen.impl.setup.LockScreenSetupFlowNode import io.element.android.features.lockscreen.impl.setup.LockScreenSetupFlowNode
import io.element.android.features.lockscreen.impl.unlock.PinUnlockNode import io.element.android.features.lockscreen.impl.unlock.PinUnlockNode
@ -46,7 +43,6 @@ import kotlinx.parcelize.Parcelize
class LockScreenFlowNode @AssistedInject constructor( class LockScreenFlowNode @AssistedInject constructor(
@Assisted buildContext: BuildContext, @Assisted buildContext: BuildContext,
@Assisted plugins: List<Plugin>, @Assisted plugins: List<Plugin>,
private val pinCodeManager: PinCodeManager,
) : BackstackNode<LockScreenFlowNode.NavTarget>( ) : BackstackNode<LockScreenFlowNode.NavTarget>(
backstack = BackStack( backstack = BackStack(
initialElement = plugins.filterIsInstance(Inputs::class.java).first().initialNavTarget, initialElement = plugins.filterIsInstance(Inputs::class.java).first().initialNavTarget,
@ -71,26 +67,14 @@ class LockScreenFlowNode @AssistedInject constructor(
data object Settings : NavTarget data object Settings : NavTarget
} }
private val pinCodeManagerCallback = object : DefaultPinCodeManagerCallback() { private class OnSetupDoneCallback(private val plugins: List<LockScreenEntryPoint.Callback>) : LockScreenSetupFlowNode.Callback {
override fun onPinCodeCreated() { override fun onSetupDone() {
plugins<LockScreenEntryPoint.Callback>().forEach { plugins.forEach {
it.onSetupCompleted() it.onSetupDone()
} }
} }
} }
override fun onBuilt() {
super.onBuilt()
lifecycle.subscribe(
onCreate = {
pinCodeManager.addCallback(pinCodeManagerCallback)
},
onDestroy = {
pinCodeManager.removeCallback(pinCodeManagerCallback)
}
)
}
override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node { override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node {
return when (navTarget) { return when (navTarget) {
NavTarget.Unlock -> { NavTarget.Unlock -> {
@ -98,7 +82,8 @@ class LockScreenFlowNode @AssistedInject constructor(
createNode<PinUnlockNode>(buildContext, plugins = listOf(inputs)) createNode<PinUnlockNode>(buildContext, plugins = listOf(inputs))
} }
NavTarget.Setup -> { NavTarget.Setup -> {
createNode<LockScreenSetupFlowNode>(buildContext) val callback = OnSetupDoneCallback(plugins())
createNode<LockScreenSetupFlowNode>(buildContext, plugins = listOf(callback))
} }
NavTarget.Settings -> { NavTarget.Settings -> {
createNode<LockScreenSettingsFlowNode>(buildContext) createNode<LockScreenSettingsFlowNode>(buildContext)

View file

@ -24,6 +24,7 @@ import androidx.core.content.ContextCompat
import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentActivity
import io.element.android.libraries.cryptography.api.EncryptionDecryptionService import io.element.android.libraries.cryptography.api.EncryptionDecryptionService
import io.element.android.libraries.cryptography.api.SecretKeyRepository import io.element.android.libraries.cryptography.api.SecretKeyRepository
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.CompletableDeferred
import timber.log.Timber import timber.log.Timber
import java.security.InvalidKeyException import java.security.InvalidKeyException
@ -86,7 +87,12 @@ class DefaultBiometricUnlock(
val callback = AuthenticationCallback(callbacks, deferredAuthenticationResult) val callback = AuthenticationCallback(callbacks, deferredAuthenticationResult)
val prompt = BiometricPrompt(activity, executor, callback) val prompt = BiometricPrompt(activity, executor, callback)
prompt.authenticate(promptInfo, cryptoObject) prompt.authenticate(promptInfo, cryptoObject)
return deferredAuthenticationResult.await() return try {
deferredAuthenticationResult.await()
} catch (cancellation: CancellationException) {
prompt.cancelAuthentication()
BiometricUnlock.AuthenticationResult.Failure(cancellation)
}
} }
@Throws(KeyPermanentlyInvalidatedException::class) @Throws(KeyPermanentlyInvalidatedException::class)
@ -110,7 +116,6 @@ private class AuthenticationCallback(
override fun onAuthenticationFailed() { override fun onAuthenticationFailed() {
super.onAuthenticationFailed() super.onAuthenticationFailed()
callbacks.forEach { it.onBiometricUnlockFailed(null) } callbacks.forEach { it.onBiometricUnlockFailed(null) }
deferredAuthenticationResult.complete(BiometricUnlock.AuthenticationResult.Failure(null))
} }
override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) { override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {

View file

@ -33,7 +33,6 @@ import dagger.assisted.Assisted
import dagger.assisted.AssistedInject import dagger.assisted.AssistedInject
import io.element.android.anvilannotations.ContributesNode import io.element.android.anvilannotations.ContributesNode
import io.element.android.features.lockscreen.impl.biometric.BiometricUnlockManager import io.element.android.features.lockscreen.impl.biometric.BiometricUnlockManager
import io.element.android.features.lockscreen.impl.biometric.DefaultBiometricUnlockCallback
import io.element.android.features.lockscreen.impl.pin.DefaultPinCodeManagerCallback import io.element.android.features.lockscreen.impl.pin.DefaultPinCodeManagerCallback
import io.element.android.features.lockscreen.impl.pin.PinCodeManager import io.element.android.features.lockscreen.impl.pin.PinCodeManager
import io.element.android.features.lockscreen.impl.setup.pin.SetupPinNode import io.element.android.features.lockscreen.impl.setup.pin.SetupPinNode
@ -76,9 +75,6 @@ class LockScreenSettingsFlowNode @AssistedInject constructor(
} }
private val pinCodeManagerCallback = object : DefaultPinCodeManagerCallback() { private val pinCodeManagerCallback = object : DefaultPinCodeManagerCallback() {
override fun onPinCodeVerified() {
backstack.newRoot(NavTarget.Settings)
}
override fun onPinCodeRemoved() { override fun onPinCodeRemoved() {
navigateUp() navigateUp()
@ -89,12 +85,6 @@ class LockScreenSettingsFlowNode @AssistedInject constructor(
} }
} }
private val biometricUnlockCallback = object : DefaultBiometricUnlockCallback() {
override fun onBiometricUnlockSuccess() {
backstack.newRoot(NavTarget.Settings)
}
}
override fun onBuilt() { override fun onBuilt() {
super.onBuilt() super.onBuilt()
lifecycleScope.launch { lifecycleScope.launch {
@ -108,11 +98,9 @@ class LockScreenSettingsFlowNode @AssistedInject constructor(
lifecycle.subscribe( lifecycle.subscribe(
onCreate = { onCreate = {
pinCodeManager.addCallback(pinCodeManagerCallback) pinCodeManager.addCallback(pinCodeManagerCallback)
biometricUnlockManager.addCallback(biometricUnlockCallback)
}, },
onDestroy = { onDestroy = {
pinCodeManager.removeCallback(pinCodeManagerCallback) pinCodeManager.removeCallback(pinCodeManagerCallback)
biometricUnlockManager.removeCallback(biometricUnlockCallback)
} }
) )
} }
@ -121,7 +109,12 @@ class LockScreenSettingsFlowNode @AssistedInject constructor(
return when (navTarget) { return when (navTarget) {
NavTarget.Unlock -> { NavTarget.Unlock -> {
val inputs = PinUnlockNode.Inputs(isInAppUnlock = true) val inputs = PinUnlockNode.Inputs(isInAppUnlock = true)
createNode<PinUnlockNode>(buildContext, plugins = listOf(inputs)) val callback = object : PinUnlockNode.Callback {
override fun onUnlock() {
backstack.newRoot(NavTarget.Settings)
}
}
createNode<PinUnlockNode>(buildContext, plugins = listOf(inputs, callback))
} }
NavTarget.SetupPin -> { NavTarget.SetupPin -> {
createNode<SetupPinNode>(buildContext) createNode<SetupPinNode>(buildContext)

View file

@ -17,10 +17,12 @@
package io.element.android.features.lockscreen.impl.unlock package io.element.android.features.lockscreen.impl.unlock
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.modality.BuildContext
import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.node.Node
import com.bumble.appyx.core.plugin.Plugin import com.bumble.appyx.core.plugin.Plugin
import com.bumble.appyx.core.plugin.plugins
import dagger.assisted.Assisted import dagger.assisted.Assisted
import dagger.assisted.AssistedInject import dagger.assisted.AssistedInject
import io.element.android.anvilannotations.ContributesNode import io.element.android.anvilannotations.ContributesNode
@ -35,15 +37,30 @@ class PinUnlockNode @AssistedInject constructor(
private val presenter: PinUnlockPresenter, private val presenter: PinUnlockPresenter,
) : Node(buildContext, plugins = plugins) { ) : Node(buildContext, plugins = plugins) {
interface Callback : Plugin {
fun onUnlock()
}
data class Inputs( data class Inputs(
val isInAppUnlock: Boolean val isInAppUnlock: Boolean
) : NodeInputs ) : NodeInputs
private val inputs: Inputs = inputs() private val inputs: Inputs = inputs()
private fun onUnlock() {
plugins<Callback>().forEach {
it.onUnlock()
}
}
@Composable @Composable
override fun View(modifier: Modifier) { override fun View(modifier: Modifier) {
val state = presenter.present() val state = presenter.present()
LaunchedEffect(state.isUnlocked) {
if (state.isUnlocked) {
onUnlock()
}
}
PinUnlockView( PinUnlockView(
state = state, state = state,
isInAppUnlock = inputs.isInAppUnlock, isInAppUnlock = inputs.isInAppUnlock,

View file

@ -17,6 +17,7 @@
package io.element.android.features.lockscreen.impl.unlock package io.element.android.features.lockscreen.impl.unlock
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.MutableState import androidx.compose.runtime.MutableState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
@ -26,6 +27,8 @@ import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import io.element.android.features.lockscreen.impl.biometric.BiometricUnlock import io.element.android.features.lockscreen.impl.biometric.BiometricUnlock
import io.element.android.features.lockscreen.impl.biometric.BiometricUnlockManager import io.element.android.features.lockscreen.impl.biometric.BiometricUnlockManager
import io.element.android.features.lockscreen.impl.biometric.DefaultBiometricUnlockCallback
import io.element.android.features.lockscreen.impl.pin.DefaultPinCodeManagerCallback
import io.element.android.features.lockscreen.impl.pin.PinCodeManager import io.element.android.features.lockscreen.impl.pin.PinCodeManager
import io.element.android.features.lockscreen.impl.pin.model.PinEntry import io.element.android.features.lockscreen.impl.pin.model.PinEntry
import io.element.android.features.lockscreen.impl.unlock.keypad.PinKeypadModel import io.element.android.features.lockscreen.impl.unlock.keypad.PinKeypadModel
@ -36,6 +39,7 @@ import io.element.android.libraries.core.bool.orFalse
import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.MatrixClient
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
class PinUnlockPresenter @Inject constructor( class PinUnlockPresenter @Inject constructor(
@ -66,9 +70,10 @@ class PinUnlockPresenter @Inject constructor(
var biometricUnlockResult by remember { var biometricUnlockResult by remember {
mutableStateOf<BiometricUnlock.AuthenticationResult?>(null) mutableStateOf<BiometricUnlock.AuthenticationResult?>(null)
} }
val isUnlocked = remember {
mutableStateOf(false)
}
val biometricUnlock = biometricUnlockManager.rememberBiometricUnlock() val biometricUnlock = biometricUnlockManager.rememberBiometricUnlock()
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
suspend { suspend {
val pinCodeSize = pinCodeManager.getPinCodeSize() val pinCodeSize = pinCodeManager.getPinCodeSize()
@ -94,7 +99,7 @@ class PinUnlockPresenter @Inject constructor(
showSignOutPrompt = true showSignOutPrompt = true
} }
} }
IsUnlockedEffect(isUnlocked)
fun handleEvents(event: PinUnlockEvents) { fun handleEvents(event: PinUnlockEvents) {
when (event) { when (event) {
is PinUnlockEvents.OnPinKeypadPressed -> { is PinUnlockEvents.OnPinKeypadPressed -> {
@ -129,10 +134,33 @@ class PinUnlockPresenter @Inject constructor(
signOutAction = signOutAction.value, signOutAction = signOutAction.value,
showBiometricUnlock = biometricUnlock.isActive, showBiometricUnlock = biometricUnlock.isActive,
biometricUnlockResult = biometricUnlockResult, biometricUnlockResult = biometricUnlockResult,
isUnlocked = isUnlocked.value,
eventSink = ::handleEvents eventSink = ::handleEvents
) )
} }
@Composable
private fun IsUnlockedEffect(isUnlocked: MutableState<Boolean>) {
DisposableEffect(Unit) {
val biometricUnlockCallback = object : DefaultBiometricUnlockCallback() {
override fun onBiometricUnlockSuccess() {
isUnlocked.value = true
}
}
val pinCodeVerifiedCallback = object : DefaultPinCodeManagerCallback() {
override fun onPinCodeVerified() {
isUnlocked.value = true
}
}
biometricUnlockManager.addCallback(biometricUnlockCallback)
pinCodeManager.addCallback(pinCodeVerifiedCallback)
onDispose {
biometricUnlockManager.removeCallback(biometricUnlockCallback)
pinCodeManager.removeCallback(pinCodeVerifiedCallback)
}
}
}
private fun Async<PinEntry>.isComplete(): Boolean { private fun Async<PinEntry>.isComplete(): Boolean {
return dataOrNull()?.isComplete().orFalse() return dataOrNull()?.isComplete().orFalse()
} }

View file

@ -28,6 +28,7 @@ data class PinUnlockState(
val showSignOutPrompt: Boolean, val showSignOutPrompt: Boolean,
val signOutAction: Async<String?>, val signOutAction: Async<String?>,
val showBiometricUnlock: Boolean, val showBiometricUnlock: Boolean,
val isUnlocked: Boolean,
val biometricUnlockResult: BiometricUnlock.AuthenticationResult?, val biometricUnlockResult: BiometricUnlock.AuthenticationResult?,
val eventSink: (PinUnlockEvents) -> Unit val eventSink: (PinUnlockEvents) -> Unit
) { ) {

View file

@ -41,6 +41,7 @@ fun aPinUnlockState(
showSignOutPrompt: Boolean = false, showSignOutPrompt: Boolean = false,
showBiometricUnlock: Boolean = true, showBiometricUnlock: Boolean = true,
biometricUnlockResult: BiometricUnlock.AuthenticationResult? = null, biometricUnlockResult: BiometricUnlock.AuthenticationResult? = null,
isUnlocked: Boolean = false,
signOutAction: Async<String?> = Async.Uninitialized, signOutAction: Async<String?> = Async.Uninitialized,
) = PinUnlockState( ) = PinUnlockState(
pinEntry = Async.Success(pinEntry), pinEntry = Async.Success(pinEntry),
@ -50,5 +51,6 @@ fun aPinUnlockState(
showBiometricUnlock = showBiometricUnlock, showBiometricUnlock = showBiometricUnlock,
signOutAction = signOutAction, signOutAction = signOutAction,
biometricUnlockResult = biometricUnlockResult, biometricUnlockResult = biometricUnlockResult,
isUnlocked = isUnlocked,
eventSink = {} eventSink = {}
) )