LockScreen : fix one more navigation issue

This commit is contained in:
ganfra 2023-11-07 18:22:40 +01:00
parent 67bdb78f98
commit 04afa8c200
9 changed files with 73 additions and 42 deletions

View file

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

View file

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

View file

@ -33,7 +33,6 @@ import dagger.assisted.Assisted
import dagger.assisted.AssistedInject
import io.element.android.anvilannotations.ContributesNode
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.setup.pin.SetupPinNode
@ -76,9 +75,6 @@ class LockScreenSettingsFlowNode @AssistedInject constructor(
}
private val pinCodeManagerCallback = object : DefaultPinCodeManagerCallback() {
override fun onPinCodeVerified() {
backstack.newRoot(NavTarget.Settings)
}
override fun onPinCodeRemoved() {
navigateUp()
@ -89,12 +85,6 @@ class LockScreenSettingsFlowNode @AssistedInject constructor(
}
}
private val biometricUnlockCallback = object : DefaultBiometricUnlockCallback() {
override fun onBiometricUnlockSuccess() {
backstack.newRoot(NavTarget.Settings)
}
}
override fun onBuilt() {
super.onBuilt()
lifecycleScope.launch {
@ -108,11 +98,9 @@ class LockScreenSettingsFlowNode @AssistedInject constructor(
lifecycle.subscribe(
onCreate = {
pinCodeManager.addCallback(pinCodeManagerCallback)
biometricUnlockManager.addCallback(biometricUnlockCallback)
},
onDestroy = {
pinCodeManager.removeCallback(pinCodeManagerCallback)
biometricUnlockManager.removeCallback(biometricUnlockCallback)
}
)
}
@ -121,7 +109,12 @@ class LockScreenSettingsFlowNode @AssistedInject constructor(
return when (navTarget) {
NavTarget.Unlock -> {
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 -> {
createNode<SetupPinNode>(buildContext)

View file

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

View file

@ -17,6 +17,7 @@
package io.element.android.features.lockscreen.impl.unlock
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.getValue
@ -26,6 +27,8 @@ import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
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.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.model.PinEntry
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 kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import timber.log.Timber
import javax.inject.Inject
class PinUnlockPresenter @Inject constructor(
@ -66,9 +70,10 @@ class PinUnlockPresenter @Inject constructor(
var biometricUnlockResult by remember {
mutableStateOf<BiometricUnlock.AuthenticationResult?>(null)
}
val isUnlocked = remember {
mutableStateOf(false)
}
val biometricUnlock = biometricUnlockManager.rememberBiometricUnlock()
LaunchedEffect(Unit) {
suspend {
val pinCodeSize = pinCodeManager.getPinCodeSize()
@ -94,7 +99,7 @@ class PinUnlockPresenter @Inject constructor(
showSignOutPrompt = true
}
}
IsUnlockedEffect(isUnlocked)
fun handleEvents(event: PinUnlockEvents) {
when (event) {
is PinUnlockEvents.OnPinKeypadPressed -> {
@ -129,10 +134,33 @@ class PinUnlockPresenter @Inject constructor(
signOutAction = signOutAction.value,
showBiometricUnlock = biometricUnlock.isActive,
biometricUnlockResult = biometricUnlockResult,
isUnlocked = isUnlocked.value,
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 {
return dataOrNull()?.isComplete().orFalse()
}

View file

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

View file

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