Add a way to enter recovery key to verify the session.
This commit is contained in:
parent
6efb4869f3
commit
7fc9a4c0fa
14 changed files with 193 additions and 41 deletions
|
|
@ -216,7 +216,9 @@ class LoggedInFlowNode @AssistedInject constructor(
|
||||||
data object VerifySession : NavTarget
|
data object VerifySession : NavTarget
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
data object SecureBackup : NavTarget
|
data class SecureBackup(
|
||||||
|
val initialElement: SecureBackupEntryPoint.InitialTarget = SecureBackupEntryPoint.InitialTarget.Root
|
||||||
|
) : NavTarget
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
data object InviteList : NavTarget
|
data object InviteList : NavTarget
|
||||||
|
|
@ -298,7 +300,7 @@ class LoggedInFlowNode @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onSecureBackupClicked() {
|
override fun onSecureBackupClicked() {
|
||||||
backstack.push(NavTarget.SecureBackup)
|
backstack.push(NavTarget.SecureBackup())
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onOpenRoomNotificationSettings(roomId: RoomId) {
|
override fun onOpenRoomNotificationSettings(roomId: RoomId) {
|
||||||
|
|
@ -324,10 +326,24 @@ class LoggedInFlowNode @AssistedInject constructor(
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
NavTarget.VerifySession -> {
|
NavTarget.VerifySession -> {
|
||||||
verifySessionEntryPoint.createNode(this, buildContext)
|
val callback = object : VerifySessionEntryPoint.Callback {
|
||||||
|
override fun onEnterRecoveryKey() {
|
||||||
|
backstack.replace(
|
||||||
|
NavTarget.SecureBackup(
|
||||||
|
initialElement = SecureBackupEntryPoint.InitialTarget.EnterRecoveryKey
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
verifySessionEntryPoint
|
||||||
|
.nodeBuilder(this, buildContext)
|
||||||
|
.callback(callback)
|
||||||
|
.build()
|
||||||
}
|
}
|
||||||
NavTarget.SecureBackup -> {
|
is NavTarget.SecureBackup -> {
|
||||||
secureBackupEntryPoint.createNode(this, buildContext)
|
secureBackupEntryPoint.nodeBuilder(this, buildContext)
|
||||||
|
.params(SecureBackupEntryPoint.Params(initialElement = navTarget.initialElement))
|
||||||
|
.build()
|
||||||
}
|
}
|
||||||
NavTarget.InviteList -> {
|
NavTarget.InviteList -> {
|
||||||
val callback = object : InviteListEntryPoint.Callback {
|
val callback = object : InviteListEntryPoint.Callback {
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id("io.element.android-library")
|
id("io.element.android-library")
|
||||||
|
id("kotlin-parcelize")
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,28 @@
|
||||||
|
|
||||||
package io.element.android.features.securebackup.api
|
package io.element.android.features.securebackup.api
|
||||||
|
|
||||||
import io.element.android.libraries.architecture.SimpleFeatureEntryPoint
|
import android.os.Parcelable
|
||||||
|
import com.bumble.appyx.core.modality.BuildContext
|
||||||
|
import com.bumble.appyx.core.node.Node
|
||||||
|
import io.element.android.libraries.architecture.FeatureEntryPoint
|
||||||
|
import io.element.android.libraries.architecture.NodeInputs
|
||||||
|
import kotlinx.parcelize.Parcelize
|
||||||
|
|
||||||
interface SecureBackupEntryPoint : SimpleFeatureEntryPoint
|
interface SecureBackupEntryPoint : FeatureEntryPoint {
|
||||||
|
sealed interface InitialTarget : Parcelable {
|
||||||
|
@Parcelize
|
||||||
|
data object Root : InitialTarget
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
data object EnterRecoveryKey : InitialTarget
|
||||||
|
}
|
||||||
|
|
||||||
|
data class Params(val initialElement: InitialTarget) : NodeInputs
|
||||||
|
|
||||||
|
fun nodeBuilder(parentNode: Node, buildContext: BuildContext): NodeBuilder
|
||||||
|
|
||||||
|
interface NodeBuilder {
|
||||||
|
fun params(params: Params): NodeBuilder
|
||||||
|
fun build(): Node
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ package io.element.android.features.securebackup.impl
|
||||||
|
|
||||||
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.squareup.anvil.annotations.ContributesBinding
|
import com.squareup.anvil.annotations.ContributesBinding
|
||||||
import io.element.android.features.securebackup.api.SecureBackupEntryPoint
|
import io.element.android.features.securebackup.api.SecureBackupEntryPoint
|
||||||
import io.element.android.libraries.architecture.createNode
|
import io.element.android.libraries.architecture.createNode
|
||||||
|
|
@ -26,7 +27,18 @@ import javax.inject.Inject
|
||||||
|
|
||||||
@ContributesBinding(AppScope::class)
|
@ContributesBinding(AppScope::class)
|
||||||
class DefaultSecureBackupEntryPoint @Inject constructor() : SecureBackupEntryPoint {
|
class DefaultSecureBackupEntryPoint @Inject constructor() : SecureBackupEntryPoint {
|
||||||
override fun createNode(parentNode: Node, buildContext: BuildContext): Node {
|
override fun nodeBuilder(parentNode: Node, buildContext: BuildContext): SecureBackupEntryPoint.NodeBuilder {
|
||||||
return parentNode.createNode<SecureBackupFlowNode>(buildContext)
|
val plugins = ArrayList<Plugin>()
|
||||||
|
|
||||||
|
return object : SecureBackupEntryPoint.NodeBuilder {
|
||||||
|
override fun params(params: SecureBackupEntryPoint.Params): SecureBackupEntryPoint.NodeBuilder {
|
||||||
|
plugins += params
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun build(): Node {
|
||||||
|
return parentNode.createNode<SecureBackupFlowNode>(buildContext, plugins)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,7 @@ import com.bumble.appyx.navmodel.backstack.operation.push
|
||||||
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
|
||||||
|
import io.element.android.features.securebackup.api.SecureBackupEntryPoint
|
||||||
import io.element.android.features.securebackup.impl.disable.SecureBackupDisableNode
|
import io.element.android.features.securebackup.impl.disable.SecureBackupDisableNode
|
||||||
import io.element.android.features.securebackup.impl.enable.SecureBackupEnableNode
|
import io.element.android.features.securebackup.impl.enable.SecureBackupEnableNode
|
||||||
import io.element.android.features.securebackup.impl.enter.SecureBackupEnterRecoveryKeyNode
|
import io.element.android.features.securebackup.impl.enter.SecureBackupEnterRecoveryKeyNode
|
||||||
|
|
@ -44,7 +45,10 @@ class SecureBackupFlowNode @AssistedInject constructor(
|
||||||
@Assisted plugins: List<Plugin>,
|
@Assisted plugins: List<Plugin>,
|
||||||
) : BaseFlowNode<SecureBackupFlowNode.NavTarget>(
|
) : BaseFlowNode<SecureBackupFlowNode.NavTarget>(
|
||||||
backstack = BackStack(
|
backstack = BackStack(
|
||||||
initialElement = NavTarget.Root,
|
initialElement = when (plugins.filterIsInstance(SecureBackupEntryPoint.Params::class.java).first().initialElement) {
|
||||||
|
SecureBackupEntryPoint.InitialTarget.Root -> NavTarget.Root
|
||||||
|
SecureBackupEntryPoint.InitialTarget.EnterRecoveryKey -> NavTarget.EnterRecoveryKey
|
||||||
|
},
|
||||||
savedStateMap = buildContext.savedStateMap,
|
savedStateMap = buildContext.savedStateMap,
|
||||||
),
|
),
|
||||||
buildContext = buildContext,
|
buildContext = buildContext,
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,20 @@
|
||||||
|
|
||||||
package io.element.android.features.verifysession.api
|
package io.element.android.features.verifysession.api
|
||||||
|
|
||||||
import io.element.android.libraries.architecture.SimpleFeatureEntryPoint
|
import com.bumble.appyx.core.modality.BuildContext
|
||||||
|
import com.bumble.appyx.core.node.Node
|
||||||
|
import com.bumble.appyx.core.plugin.Plugin
|
||||||
|
import io.element.android.libraries.architecture.FeatureEntryPoint
|
||||||
|
|
||||||
interface VerifySessionEntryPoint : SimpleFeatureEntryPoint
|
interface VerifySessionEntryPoint : FeatureEntryPoint {
|
||||||
|
fun nodeBuilder(parentNode: Node, buildContext: BuildContext): NodeBuilder
|
||||||
|
|
||||||
|
interface NodeBuilder {
|
||||||
|
fun callback(callback: Callback): NodeBuilder
|
||||||
|
fun build(): Node
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Callback : Plugin {
|
||||||
|
fun onEnterRecoveryKey()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ package io.element.android.features.verifysession.impl
|
||||||
|
|
||||||
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.squareup.anvil.annotations.ContributesBinding
|
import com.squareup.anvil.annotations.ContributesBinding
|
||||||
import io.element.android.features.verifysession.api.VerifySessionEntryPoint
|
import io.element.android.features.verifysession.api.VerifySessionEntryPoint
|
||||||
import io.element.android.libraries.architecture.createNode
|
import io.element.android.libraries.architecture.createNode
|
||||||
|
|
@ -26,7 +27,18 @@ import javax.inject.Inject
|
||||||
|
|
||||||
@ContributesBinding(AppScope::class)
|
@ContributesBinding(AppScope::class)
|
||||||
class DefaultVerifySessionEntryPoint @Inject constructor() : VerifySessionEntryPoint {
|
class DefaultVerifySessionEntryPoint @Inject constructor() : VerifySessionEntryPoint {
|
||||||
override fun createNode(parentNode: Node, buildContext: BuildContext): Node {
|
override fun nodeBuilder(parentNode: Node, buildContext: BuildContext): VerifySessionEntryPoint.NodeBuilder {
|
||||||
return parentNode.createNode<VerifySelfSessionNode>(buildContext)
|
val plugins = ArrayList<Plugin>()
|
||||||
|
|
||||||
|
return object : VerifySessionEntryPoint.NodeBuilder {
|
||||||
|
override fun callback(callback: VerifySessionEntryPoint.Callback): VerifySessionEntryPoint.NodeBuilder {
|
||||||
|
plugins += callback
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun build(): Node {
|
||||||
|
return parentNode.createNode<VerifySelfSessionNode>(buildContext, plugins)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,9 +21,11 @@ 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
|
||||||
|
import io.element.android.features.verifysession.api.VerifySessionEntryPoint
|
||||||
import io.element.android.libraries.di.SessionScope
|
import io.element.android.libraries.di.SessionScope
|
||||||
|
|
||||||
@ContributesNode(SessionScope::class)
|
@ContributesNode(SessionScope::class)
|
||||||
|
|
@ -32,12 +34,20 @@ class VerifySelfSessionNode @AssistedInject constructor(
|
||||||
@Assisted plugins: List<Plugin>,
|
@Assisted plugins: List<Plugin>,
|
||||||
private val presenter: VerifySelfSessionPresenter,
|
private val presenter: VerifySelfSessionPresenter,
|
||||||
) : Node(buildContext, plugins = plugins) {
|
) : Node(buildContext, plugins = plugins) {
|
||||||
|
|
||||||
|
private fun onEnterRecoveryKey() {
|
||||||
|
plugins<VerifySessionEntryPoint.Callback>().forEach {
|
||||||
|
it.onEnterRecoveryKey()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun View(modifier: Modifier) {
|
override fun View(modifier: Modifier) {
|
||||||
val state = presenter.present()
|
val state = presenter.present()
|
||||||
VerifySelfSessionView(
|
VerifySelfSessionView(
|
||||||
state = state,
|
state = state,
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
|
onEnterRecoveryKey = { onEnterRecoveryKey() },
|
||||||
goBack = { navigateUp() }
|
goBack = { navigateUp() }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,12 +20,15 @@ package io.element.android.features.verifysession.impl
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.derivedStateOf
|
import androidx.compose.runtime.derivedStateOf
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import com.freeletics.flowredux.compose.rememberStateAndDispatch
|
import com.freeletics.flowredux.compose.rememberStateAndDispatch
|
||||||
import io.element.android.libraries.architecture.AsyncData
|
import io.element.android.libraries.architecture.AsyncData
|
||||||
import io.element.android.libraries.architecture.Presenter
|
import io.element.android.libraries.architecture.Presenter
|
||||||
|
import io.element.android.libraries.matrix.api.encryption.EncryptionService
|
||||||
|
import io.element.android.libraries.matrix.api.encryption.RecoveryState
|
||||||
import io.element.android.libraries.matrix.api.verification.SessionVerificationService
|
import io.element.android.libraries.matrix.api.verification.SessionVerificationService
|
||||||
import io.element.android.libraries.matrix.api.verification.VerificationFlowState
|
import io.element.android.libraries.matrix.api.verification.VerificationFlowState
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
|
@ -38,6 +41,7 @@ import io.element.android.features.verifysession.impl.VerifySelfSessionStateMach
|
||||||
|
|
||||||
class VerifySelfSessionPresenter @Inject constructor(
|
class VerifySelfSessionPresenter @Inject constructor(
|
||||||
private val sessionVerificationService: SessionVerificationService,
|
private val sessionVerificationService: SessionVerificationService,
|
||||||
|
private val encryptionService: EncryptionService,
|
||||||
private val stateMachine: VerifySelfSessionStateMachine,
|
private val stateMachine: VerifySelfSessionStateMachine,
|
||||||
) : Presenter<VerifySelfSessionState> {
|
) : Presenter<VerifySelfSessionState> {
|
||||||
@Composable
|
@Composable
|
||||||
|
|
@ -46,9 +50,14 @@ class VerifySelfSessionPresenter @Inject constructor(
|
||||||
// Force reset, just in case the service was left in a broken state
|
// Force reset, just in case the service was left in a broken state
|
||||||
sessionVerificationService.reset()
|
sessionVerificationService.reset()
|
||||||
}
|
}
|
||||||
|
val recoveryState by encryptionService.recoveryStateStateFlow.collectAsState()
|
||||||
val stateAndDispatch = stateMachine.rememberStateAndDispatch()
|
val stateAndDispatch = stateMachine.rememberStateAndDispatch()
|
||||||
val verificationFlowStep by remember {
|
val verificationFlowStep by remember {
|
||||||
derivedStateOf { stateAndDispatch.state.value.toVerificationStep() }
|
derivedStateOf {
|
||||||
|
stateAndDispatch.state.value.toVerificationStep(
|
||||||
|
canEnterRecoveryKey = recoveryState == RecoveryState.INCOMPLETE
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Start this after observing state machine
|
// Start this after observing state machine
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
|
|
@ -71,10 +80,12 @@ class VerifySelfSessionPresenter @Inject constructor(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun StateMachineState?.toVerificationStep(): VerifySelfSessionState.VerificationStep =
|
private fun StateMachineState?.toVerificationStep(
|
||||||
|
canEnterRecoveryKey: Boolean
|
||||||
|
): VerifySelfSessionState.VerificationStep =
|
||||||
when (val machineState = this) {
|
when (val machineState = this) {
|
||||||
StateMachineState.Initial, null -> {
|
StateMachineState.Initial, null -> {
|
||||||
VerifySelfSessionState.VerificationStep.Initial
|
VerifySelfSessionState.VerificationStep.Initial(canEnterRecoveryKey = canEnterRecoveryKey)
|
||||||
}
|
}
|
||||||
StateMachineState.RequestingVerification,
|
StateMachineState.RequestingVerification,
|
||||||
StateMachineState.StartingSasVerification,
|
StateMachineState.StartingSasVerification,
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ data class VerifySelfSessionState(
|
||||||
) {
|
) {
|
||||||
@Stable
|
@Stable
|
||||||
sealed interface VerificationStep {
|
sealed interface VerificationStep {
|
||||||
data object Initial : VerificationStep
|
data class Initial(val canEnterRecoveryKey: Boolean) : VerificationStep
|
||||||
data object Canceled : VerificationStep
|
data object Canceled : VerificationStep
|
||||||
data object AwaitingOtherDeviceResponse : VerificationStep
|
data object AwaitingOtherDeviceResponse : VerificationStep
|
||||||
data object Ready : VerificationStep
|
data object Ready : VerificationStep
|
||||||
|
|
|
||||||
|
|
@ -25,24 +25,27 @@ open class VerifySelfSessionStateProvider : PreviewParameterProvider<VerifySelfS
|
||||||
override val values: Sequence<VerifySelfSessionState>
|
override val values: Sequence<VerifySelfSessionState>
|
||||||
get() = sequenceOf(
|
get() = sequenceOf(
|
||||||
aVerifySelfSessionState(),
|
aVerifySelfSessionState(),
|
||||||
aVerifySelfSessionState().copy(
|
aVerifySelfSessionState(
|
||||||
verificationFlowStep = VerifySelfSessionState.VerificationStep.AwaitingOtherDeviceResponse
|
verificationFlowStep = VerifySelfSessionState.VerificationStep.AwaitingOtherDeviceResponse
|
||||||
),
|
),
|
||||||
aVerifySelfSessionState().copy(
|
aVerifySelfSessionState(
|
||||||
verificationFlowStep = VerifySelfSessionState.VerificationStep.Verifying(aEmojisSessionVerificationData(), AsyncData.Uninitialized)
|
verificationFlowStep = VerifySelfSessionState.VerificationStep.Verifying(aEmojisSessionVerificationData(), AsyncData.Uninitialized)
|
||||||
),
|
),
|
||||||
aVerifySelfSessionState().copy(
|
aVerifySelfSessionState(
|
||||||
verificationFlowStep = VerifySelfSessionState.VerificationStep.Verifying(aEmojisSessionVerificationData(), AsyncData.Loading())
|
verificationFlowStep = VerifySelfSessionState.VerificationStep.Verifying(aEmojisSessionVerificationData(), AsyncData.Loading())
|
||||||
),
|
),
|
||||||
aVerifySelfSessionState().copy(
|
aVerifySelfSessionState(
|
||||||
verificationFlowStep = VerifySelfSessionState.VerificationStep.Canceled
|
verificationFlowStep = VerifySelfSessionState.VerificationStep.Canceled
|
||||||
),
|
),
|
||||||
aVerifySelfSessionState().copy(
|
aVerifySelfSessionState(
|
||||||
verificationFlowStep = VerifySelfSessionState.VerificationStep.Ready
|
verificationFlowStep = VerifySelfSessionState.VerificationStep.Ready
|
||||||
),
|
),
|
||||||
aVerifySelfSessionState().copy(
|
aVerifySelfSessionState(
|
||||||
verificationFlowStep = VerifySelfSessionState.VerificationStep.Verifying(aDecimalsSessionVerificationData(), AsyncData.Uninitialized)
|
verificationFlowStep = VerifySelfSessionState.VerificationStep.Verifying(aDecimalsSessionVerificationData(), AsyncData.Uninitialized)
|
||||||
),
|
),
|
||||||
|
aVerifySelfSessionState(
|
||||||
|
verificationFlowStep = VerifySelfSessionState.VerificationStep.Initial(true)
|
||||||
|
),
|
||||||
// Add other state here
|
// Add other state here
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -59,8 +62,10 @@ private fun aDecimalsSessionVerificationData(
|
||||||
return SessionVerificationData.Decimals(decimals)
|
return SessionVerificationData.Decimals(decimals)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun aVerifySelfSessionState() = VerifySelfSessionState(
|
private fun aVerifySelfSessionState(
|
||||||
verificationFlowStep = VerifySelfSessionState.VerificationStep.Initial,
|
verificationFlowStep: VerifySelfSessionState.VerificationStep = VerifySelfSessionState.VerificationStep.Initial(false),
|
||||||
|
) = VerifySelfSessionState(
|
||||||
|
verificationFlowStep = verificationFlowStep,
|
||||||
eventSink = {},
|
eventSink = {},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -62,6 +62,7 @@ import io.element.android.features.verifysession.impl.VerifySelfSessionState.Ver
|
||||||
fun VerifySelfSessionView(
|
fun VerifySelfSessionView(
|
||||||
state: VerifySelfSessionState,
|
state: VerifySelfSessionState,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
|
onEnterRecoveryKey: () -> Unit,
|
||||||
goBack: () -> Unit,
|
goBack: () -> Unit,
|
||||||
) {
|
) {
|
||||||
fun goBackAndCancelIfNeeded() {
|
fun goBackAndCancelIfNeeded() {
|
||||||
|
|
@ -85,7 +86,11 @@ fun VerifySelfSessionView(
|
||||||
},
|
},
|
||||||
footer = {
|
footer = {
|
||||||
if (buttonsVisible) {
|
if (buttonsVisible) {
|
||||||
BottomMenu(screenState = state, goBack = ::goBackAndCancelIfNeeded)
|
BottomMenu(
|
||||||
|
screenState = state,
|
||||||
|
goBack = ::goBackAndCancelIfNeeded,
|
||||||
|
onEnterRecoveryKey = onEnterRecoveryKey
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
|
|
@ -96,13 +101,13 @@ fun VerifySelfSessionView(
|
||||||
@Composable
|
@Composable
|
||||||
private fun HeaderContent(verificationFlowStep: FlowStep) {
|
private fun HeaderContent(verificationFlowStep: FlowStep) {
|
||||||
val iconResourceId = when (verificationFlowStep) {
|
val iconResourceId = when (verificationFlowStep) {
|
||||||
FlowStep.Initial -> R.drawable.ic_verification_devices
|
is FlowStep.Initial -> R.drawable.ic_verification_devices
|
||||||
FlowStep.Canceled -> R.drawable.ic_verification_warning
|
FlowStep.Canceled -> R.drawable.ic_verification_warning
|
||||||
FlowStep.AwaitingOtherDeviceResponse -> R.drawable.ic_verification_waiting
|
FlowStep.AwaitingOtherDeviceResponse -> R.drawable.ic_verification_waiting
|
||||||
FlowStep.Ready, is FlowStep.Verifying, FlowStep.Completed -> R.drawable.ic_verification_emoji
|
FlowStep.Ready, is FlowStep.Verifying, FlowStep.Completed -> R.drawable.ic_verification_emoji
|
||||||
}
|
}
|
||||||
val titleTextId = when (verificationFlowStep) {
|
val titleTextId = when (verificationFlowStep) {
|
||||||
FlowStep.Initial -> R.string.screen_session_verification_open_existing_session_title
|
is FlowStep.Initial -> R.string.screen_session_verification_open_existing_session_title
|
||||||
FlowStep.Canceled -> CommonStrings.common_verification_cancelled
|
FlowStep.Canceled -> CommonStrings.common_verification_cancelled
|
||||||
FlowStep.AwaitingOtherDeviceResponse -> R.string.screen_session_verification_waiting_to_accept_title
|
FlowStep.AwaitingOtherDeviceResponse -> R.string.screen_session_verification_waiting_to_accept_title
|
||||||
FlowStep.Ready,
|
FlowStep.Ready,
|
||||||
|
|
@ -113,7 +118,7 @@ private fun HeaderContent(verificationFlowStep: FlowStep) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val subtitleTextId = when (verificationFlowStep) {
|
val subtitleTextId = when (verificationFlowStep) {
|
||||||
FlowStep.Initial -> R.string.screen_session_verification_open_existing_session_subtitle
|
is FlowStep.Initial -> R.string.screen_session_verification_open_existing_session_subtitle
|
||||||
FlowStep.Canceled -> R.string.screen_session_verification_cancelled_subtitle
|
FlowStep.Canceled -> R.string.screen_session_verification_cancelled_subtitle
|
||||||
FlowStep.AwaitingOtherDeviceResponse -> R.string.screen_session_verification_waiting_to_accept_subtitle
|
FlowStep.AwaitingOtherDeviceResponse -> R.string.screen_session_verification_waiting_to_accept_subtitle
|
||||||
FlowStep.Ready -> R.string.screen_session_verification_ready_subtitle
|
FlowStep.Ready -> R.string.screen_session_verification_ready_subtitle
|
||||||
|
|
@ -136,7 +141,7 @@ private fun HeaderContent(verificationFlowStep: FlowStep) {
|
||||||
private fun Content(flowState: FlowStep) {
|
private fun Content(flowState: FlowStep) {
|
||||||
Column(Modifier.fillMaxHeight(), verticalArrangement = Arrangement.Center) {
|
Column(Modifier.fillMaxHeight(), verticalArrangement = Arrangement.Center) {
|
||||||
when (flowState) {
|
when (flowState) {
|
||||||
FlowStep.Initial, FlowStep.Ready, FlowStep.Canceled, FlowStep.Completed -> Unit
|
is FlowStep.Initial, FlowStep.Ready, FlowStep.Canceled, FlowStep.Completed -> Unit
|
||||||
FlowStep.AwaitingOtherDeviceResponse -> ContentWaiting()
|
FlowStep.AwaitingOtherDeviceResponse -> ContentWaiting()
|
||||||
is FlowStep.Verifying -> ContentVerifying(flowState)
|
is FlowStep.Verifying -> ContentVerifying(flowState)
|
||||||
}
|
}
|
||||||
|
|
@ -203,13 +208,17 @@ private fun EmojiItemView(emoji: VerificationEmoji, modifier: Modifier = Modifie
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun BottomMenu(screenState: VerifySelfSessionState, goBack: () -> Unit) {
|
private fun BottomMenu(
|
||||||
|
screenState: VerifySelfSessionState,
|
||||||
|
onEnterRecoveryKey: () -> Unit,
|
||||||
|
goBack: () -> Unit,
|
||||||
|
) {
|
||||||
val verificationViewState = screenState.verificationFlowStep
|
val verificationViewState = screenState.verificationFlowStep
|
||||||
val eventSink = screenState.eventSink
|
val eventSink = screenState.eventSink
|
||||||
|
|
||||||
val isVerifying = (verificationViewState as? FlowStep.Verifying)?.state is AsyncData.Loading<Unit>
|
val isVerifying = (verificationViewState as? FlowStep.Verifying)?.state is AsyncData.Loading<Unit>
|
||||||
val positiveButtonTitle = when (verificationViewState) {
|
val positiveButtonTitle = when (verificationViewState) {
|
||||||
FlowStep.Initial -> R.string.screen_session_verification_positive_button_initial
|
is FlowStep.Initial -> R.string.screen_session_verification_positive_button_initial
|
||||||
FlowStep.Canceled -> R.string.screen_session_verification_positive_button_canceled
|
FlowStep.Canceled -> R.string.screen_session_verification_positive_button_canceled
|
||||||
is FlowStep.Verifying -> {
|
is FlowStep.Verifying -> {
|
||||||
if (isVerifying) {
|
if (isVerifying) {
|
||||||
|
|
@ -222,7 +231,7 @@ private fun BottomMenu(screenState: VerifySelfSessionState, goBack: () -> Unit)
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
val negativeButtonTitle = when (verificationViewState) {
|
val negativeButtonTitle = when (verificationViewState) {
|
||||||
FlowStep.Initial -> CommonStrings.action_cancel
|
is FlowStep.Initial -> CommonStrings.action_cancel
|
||||||
FlowStep.Canceled -> CommonStrings.action_cancel
|
FlowStep.Canceled -> CommonStrings.action_cancel
|
||||||
is FlowStep.Verifying -> R.string.screen_session_verification_they_dont_match
|
is FlowStep.Verifying -> R.string.screen_session_verification_they_dont_match
|
||||||
else -> null
|
else -> null
|
||||||
|
|
@ -230,7 +239,7 @@ private fun BottomMenu(screenState: VerifySelfSessionState, goBack: () -> Unit)
|
||||||
val negativeButtonEnabled = !isVerifying
|
val negativeButtonEnabled = !isVerifying
|
||||||
|
|
||||||
val positiveButtonEvent = when (verificationViewState) {
|
val positiveButtonEvent = when (verificationViewState) {
|
||||||
FlowStep.Initial -> VerifySelfSessionViewEvents.RequestVerification
|
is FlowStep.Initial -> VerifySelfSessionViewEvents.RequestVerification
|
||||||
FlowStep.Ready -> VerifySelfSessionViewEvents.StartSasVerification
|
FlowStep.Ready -> VerifySelfSessionViewEvents.StartSasVerification
|
||||||
is FlowStep.Verifying -> if (!isVerifying) VerifySelfSessionViewEvents.ConfirmVerification else null
|
is FlowStep.Verifying -> if (!isVerifying) VerifySelfSessionViewEvents.ConfirmVerification else null
|
||||||
FlowStep.Canceled -> VerifySelfSessionViewEvents.Restart
|
FlowStep.Canceled -> VerifySelfSessionViewEvents.Restart
|
||||||
|
|
@ -263,6 +272,17 @@ private fun BottomMenu(screenState: VerifySelfSessionState, goBack: () -> Unit)
|
||||||
enabled = negativeButtonEnabled,
|
enabled = negativeButtonEnabled,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
if (verificationViewState is FlowStep.Initial && verificationViewState.canEnterRecoveryKey) {
|
||||||
|
Text(
|
||||||
|
text = stringResource(id = CommonStrings.common_or),
|
||||||
|
color = ElementTheme.colors.textSecondary,
|
||||||
|
)
|
||||||
|
TextButton(
|
||||||
|
text = stringResource(R.string.screen_session_verification_enter_recovery_key),
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
onClick = onEnterRecoveryKey,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -271,6 +291,7 @@ private fun BottomMenu(screenState: VerifySelfSessionState, goBack: () -> Unit)
|
||||||
internal fun VerifySelfSessionViewPreview(@PreviewParameter(VerifySelfSessionStateProvider::class) state: VerifySelfSessionState) = ElementPreview {
|
internal fun VerifySelfSessionViewPreview(@PreviewParameter(VerifySelfSessionStateProvider::class) state: VerifySelfSessionState) = ElementPreview {
|
||||||
VerifySelfSessionView(
|
VerifySelfSessionView(
|
||||||
state = state,
|
state = state,
|
||||||
|
onEnterRecoveryKey = {},
|
||||||
goBack = {},
|
goBack = {},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,9 +23,13 @@ import app.cash.turbine.test
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
import io.element.android.features.verifysession.impl.VerifySelfSessionState.VerificationStep
|
import io.element.android.features.verifysession.impl.VerifySelfSessionState.VerificationStep
|
||||||
import io.element.android.libraries.architecture.AsyncData
|
import io.element.android.libraries.architecture.AsyncData
|
||||||
|
import io.element.android.libraries.matrix.api.encryption.EncryptionService
|
||||||
|
import io.element.android.libraries.matrix.api.encryption.RecoveryState
|
||||||
import io.element.android.libraries.matrix.api.verification.SessionVerificationData
|
import io.element.android.libraries.matrix.api.verification.SessionVerificationData
|
||||||
|
import io.element.android.libraries.matrix.api.verification.SessionVerificationService
|
||||||
import io.element.android.libraries.matrix.api.verification.VerificationEmoji
|
import io.element.android.libraries.matrix.api.verification.VerificationEmoji
|
||||||
import io.element.android.libraries.matrix.api.verification.VerificationFlowState
|
import io.element.android.libraries.matrix.api.verification.VerificationFlowState
|
||||||
|
import io.element.android.libraries.matrix.test.encryption.FakeEncryptionService
|
||||||
import io.element.android.libraries.matrix.test.verification.FakeSessionVerificationService
|
import io.element.android.libraries.matrix.test.verification.FakeSessionVerificationService
|
||||||
import io.element.android.tests.testutils.WarmUpRule
|
import io.element.android.tests.testutils.WarmUpRule
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
|
|
@ -44,7 +48,21 @@ class VerifySelfSessionPresenterTests {
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
presenter.present()
|
presenter.present()
|
||||||
}.test {
|
}.test {
|
||||||
assertThat(awaitItem().verificationFlowStep).isEqualTo(VerificationStep.Initial)
|
assertThat(awaitItem().verificationFlowStep).isEqualTo(VerificationStep.Initial(false))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `present - Initial state is received, can use recovery key`() = runTest {
|
||||||
|
val presenter = createVerifySelfSessionPresenter(
|
||||||
|
encryptionService = FakeEncryptionService().apply {
|
||||||
|
emitRecoveryState(RecoveryState.INCOMPLETE)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
|
presenter.present()
|
||||||
|
}.test {
|
||||||
|
assertThat(awaitItem().verificationFlowStep).isEqualTo(VerificationStep.Initial(true))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -67,7 +85,7 @@ class VerifySelfSessionPresenterTests {
|
||||||
presenter.present()
|
presenter.present()
|
||||||
}.test {
|
}.test {
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
assertThat(initialState.verificationFlowStep).isEqualTo(VerificationStep.Initial)
|
assertThat(initialState.verificationFlowStep).isEqualTo(VerificationStep.Initial(false))
|
||||||
val eventSink = initialState.eventSink
|
val eventSink = initialState.eventSink
|
||||||
eventSink(VerifySelfSessionViewEvents.StartSasVerification)
|
eventSink(VerifySelfSessionViewEvents.StartSasVerification)
|
||||||
// Await for other device response:
|
// Await for other device response:
|
||||||
|
|
@ -86,7 +104,7 @@ class VerifySelfSessionPresenterTests {
|
||||||
presenter.present()
|
presenter.present()
|
||||||
}.test {
|
}.test {
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
assertThat(initialState.verificationFlowStep).isEqualTo(VerificationStep.Initial)
|
assertThat(initialState.verificationFlowStep).isEqualTo(VerificationStep.Initial(false))
|
||||||
val eventSink = initialState.eventSink
|
val eventSink = initialState.eventSink
|
||||||
eventSink(VerifySelfSessionViewEvents.CancelAndClose)
|
eventSink(VerifySelfSessionViewEvents.CancelAndClose)
|
||||||
expectNoEvents()
|
expectNoEvents()
|
||||||
|
|
@ -203,7 +221,7 @@ class VerifySelfSessionPresenterTests {
|
||||||
sessionVerificationData: SessionVerificationData = SessionVerificationData.Emojis(emptyList()),
|
sessionVerificationData: SessionVerificationData = SessionVerificationData.Emojis(emptyList()),
|
||||||
): VerifySelfSessionState {
|
): VerifySelfSessionState {
|
||||||
var state = awaitItem()
|
var state = awaitItem()
|
||||||
assertThat(state.verificationFlowStep).isEqualTo(VerificationStep.Initial)
|
assertThat(state.verificationFlowStep).isEqualTo(VerificationStep.Initial(false))
|
||||||
state.eventSink(VerifySelfSessionViewEvents.RequestVerification)
|
state.eventSink(VerifySelfSessionViewEvents.RequestVerification)
|
||||||
// Await for other device response:
|
// Await for other device response:
|
||||||
state = awaitItem()
|
state = awaitItem()
|
||||||
|
|
@ -223,8 +241,13 @@ class VerifySelfSessionPresenterTests {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createVerifySelfSessionPresenter(
|
private fun createVerifySelfSessionPresenter(
|
||||||
service: FakeSessionVerificationService = FakeSessionVerificationService()
|
service: SessionVerificationService = FakeSessionVerificationService(),
|
||||||
|
encryptionService: EncryptionService = FakeEncryptionService(),
|
||||||
): VerifySelfSessionPresenter {
|
): VerifySelfSessionPresenter {
|
||||||
return VerifySelfSessionPresenter(service, VerifySelfSessionStateMachine(service))
|
return VerifySelfSessionPresenter(
|
||||||
|
sessionVerificationService = service,
|
||||||
|
encryptionService = encryptionService,
|
||||||
|
stateMachine = VerifySelfSessionStateMachine(service),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -141,6 +141,7 @@
|
||||||
<string name="common_mute">"Mute"</string>
|
<string name="common_mute">"Mute"</string>
|
||||||
<string name="common_no_results">"No results"</string>
|
<string name="common_no_results">"No results"</string>
|
||||||
<string name="common_offline">"Offline"</string>
|
<string name="common_offline">"Offline"</string>
|
||||||
|
<string name="common_or">"or"</string>
|
||||||
<string name="common_password">"Password"</string>
|
<string name="common_password">"Password"</string>
|
||||||
<string name="common_people">"People"</string>
|
<string name="common_people">"People"</string>
|
||||||
<string name="common_permalink">"Permalink"</string>
|
<string name="common_permalink">"Permalink"</string>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue