Merge pull request #3359 from element-hq/feature/bma/signOutOnIdentityConfirmation
Add a way to sign out when the user is asked to verify the session.
This commit is contained in:
commit
a74dde96e0
19 changed files with 145 additions and 23 deletions
|
|
@ -43,6 +43,7 @@ dependencies {
|
||||||
implementation(projects.libraries.designsystem)
|
implementation(projects.libraries.designsystem)
|
||||||
implementation(projects.libraries.preferences.api)
|
implementation(projects.libraries.preferences.api)
|
||||||
implementation(projects.libraries.uiStrings)
|
implementation(projects.libraries.uiStrings)
|
||||||
|
implementation(projects.features.logout.api)
|
||||||
api(libs.statemachine)
|
api(libs.statemachine)
|
||||||
api(projects.features.verifysession.api)
|
api(projects.features.verifysession.api)
|
||||||
|
|
||||||
|
|
@ -52,6 +53,7 @@ dependencies {
|
||||||
testImplementation(libs.test.robolectric)
|
testImplementation(libs.test.robolectric)
|
||||||
testImplementation(libs.test.truth)
|
testImplementation(libs.test.truth)
|
||||||
testImplementation(libs.test.turbine)
|
testImplementation(libs.test.turbine)
|
||||||
|
testImplementation(projects.features.logout.test)
|
||||||
testImplementation(projects.libraries.matrix.test)
|
testImplementation(projects.libraries.matrix.test)
|
||||||
testImplementation(projects.libraries.preferences.test)
|
testImplementation(projects.libraries.preferences.test)
|
||||||
testImplementation(projects.tests.testutils)
|
testImplementation(projects.tests.testutils)
|
||||||
|
|
|
||||||
|
|
@ -16,8 +16,10 @@
|
||||||
|
|
||||||
package io.element.android.features.verifysession.impl
|
package io.element.android.features.verifysession.impl
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
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
|
||||||
|
|
@ -25,6 +27,8 @@ 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.compound.theme.ElementTheme
|
||||||
|
import io.element.android.features.logout.api.util.onSuccessLogout
|
||||||
import io.element.android.features.verifysession.api.VerifySessionEntryPoint
|
import io.element.android.features.verifysession.api.VerifySessionEntryPoint
|
||||||
import io.element.android.libraries.di.SessionScope
|
import io.element.android.libraries.di.SessionScope
|
||||||
|
|
||||||
|
|
@ -39,12 +43,15 @@ class VerifySelfSessionNode @AssistedInject constructor(
|
||||||
@Composable
|
@Composable
|
||||||
override fun View(modifier: Modifier) {
|
override fun View(modifier: Modifier) {
|
||||||
val state = presenter.present()
|
val state = presenter.present()
|
||||||
|
val activity = LocalContext.current as Activity
|
||||||
|
val isDark = ElementTheme.isLightTheme.not()
|
||||||
VerifySelfSessionView(
|
VerifySelfSessionView(
|
||||||
state = state,
|
state = state,
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
onEnterRecoveryKey = callback::onEnterRecoveryKey,
|
onEnterRecoveryKey = callback::onEnterRecoveryKey,
|
||||||
onResetKey = callback::onResetKey,
|
onResetKey = callback::onResetKey,
|
||||||
onFinish = callback::onDone,
|
onFinish = callback::onDone,
|
||||||
|
onSuccessLogout = { onSuccessLogout(activity, isDark, it) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,14 +20,19 @@ 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.MutableState
|
||||||
import androidx.compose.runtime.collectAsState
|
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.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import com.freeletics.flowredux.compose.rememberStateAndDispatch
|
import com.freeletics.flowredux.compose.rememberStateAndDispatch
|
||||||
|
import io.element.android.features.logout.api.LogoutUseCase
|
||||||
|
import io.element.android.libraries.architecture.AsyncAction
|
||||||
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.architecture.runCatchingUpdatingState
|
||||||
import io.element.android.libraries.core.meta.BuildMeta
|
import io.element.android.libraries.core.meta.BuildMeta
|
||||||
import io.element.android.libraries.matrix.api.encryption.EncryptionService
|
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.encryption.RecoveryState
|
||||||
|
|
@ -49,6 +54,7 @@ class VerifySelfSessionPresenter @Inject constructor(
|
||||||
private val stateMachine: VerifySelfSessionStateMachine,
|
private val stateMachine: VerifySelfSessionStateMachine,
|
||||||
private val buildMeta: BuildMeta,
|
private val buildMeta: BuildMeta,
|
||||||
private val sessionPreferencesStore: SessionPreferencesStore,
|
private val sessionPreferencesStore: SessionPreferencesStore,
|
||||||
|
private val logoutUseCase: LogoutUseCase,
|
||||||
) : Presenter<VerifySelfSessionState> {
|
) : Presenter<VerifySelfSessionState> {
|
||||||
@Composable
|
@Composable
|
||||||
override fun present(): VerifySelfSessionState {
|
override fun present(): VerifySelfSessionState {
|
||||||
|
|
@ -61,6 +67,9 @@ class VerifySelfSessionPresenter @Inject constructor(
|
||||||
val stateAndDispatch = stateMachine.rememberStateAndDispatch()
|
val stateAndDispatch = stateMachine.rememberStateAndDispatch()
|
||||||
val skipVerification by sessionPreferencesStore.isSessionVerificationSkipped().collectAsState(initial = false)
|
val skipVerification by sessionPreferencesStore.isSessionVerificationSkipped().collectAsState(initial = false)
|
||||||
val needsVerification by sessionVerificationService.needsSessionVerification.collectAsState(initial = true)
|
val needsVerification by sessionVerificationService.needsSessionVerification.collectAsState(initial = true)
|
||||||
|
val signOutAction = remember {
|
||||||
|
mutableStateOf<AsyncAction<String?>>(AsyncAction.Uninitialized)
|
||||||
|
}
|
||||||
val verificationFlowStep by remember {
|
val verificationFlowStep by remember {
|
||||||
derivedStateOf {
|
derivedStateOf {
|
||||||
when {
|
when {
|
||||||
|
|
@ -85,6 +94,7 @@ class VerifySelfSessionPresenter @Inject constructor(
|
||||||
VerifySelfSessionViewEvents.DeclineVerification -> stateAndDispatch.dispatchAction(StateMachineEvent.DeclineChallenge)
|
VerifySelfSessionViewEvents.DeclineVerification -> stateAndDispatch.dispatchAction(StateMachineEvent.DeclineChallenge)
|
||||||
VerifySelfSessionViewEvents.Cancel -> stateAndDispatch.dispatchAction(StateMachineEvent.Cancel)
|
VerifySelfSessionViewEvents.Cancel -> stateAndDispatch.dispatchAction(StateMachineEvent.Cancel)
|
||||||
VerifySelfSessionViewEvents.Reset -> stateAndDispatch.dispatchAction(StateMachineEvent.Reset)
|
VerifySelfSessionViewEvents.Reset -> stateAndDispatch.dispatchAction(StateMachineEvent.Reset)
|
||||||
|
VerifySelfSessionViewEvents.SignOut -> coroutineScope.signOut(signOutAction)
|
||||||
VerifySelfSessionViewEvents.SkipVerification -> coroutineScope.launch {
|
VerifySelfSessionViewEvents.SkipVerification -> coroutineScope.launch {
|
||||||
sessionPreferencesStore.setSkipSessionVerification(true)
|
sessionPreferencesStore.setSkipSessionVerification(true)
|
||||||
}
|
}
|
||||||
|
|
@ -92,6 +102,7 @@ class VerifySelfSessionPresenter @Inject constructor(
|
||||||
}
|
}
|
||||||
return VerifySelfSessionState(
|
return VerifySelfSessionState(
|
||||||
verificationFlowStep = verificationFlowStep,
|
verificationFlowStep = verificationFlowStep,
|
||||||
|
signOutAction = signOutAction.value,
|
||||||
displaySkipButton = buildMeta.isDebuggable,
|
displaySkipButton = buildMeta.isDebuggable,
|
||||||
eventSink = ::handleEvents,
|
eventSink = ::handleEvents,
|
||||||
)
|
)
|
||||||
|
|
@ -160,4 +171,10 @@ class VerifySelfSessionPresenter @Inject constructor(
|
||||||
}
|
}
|
||||||
}.launchIn(this)
|
}.launchIn(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun CoroutineScope.signOut(signOutAction: MutableState<AsyncAction<String?>>) = launch {
|
||||||
|
suspend {
|
||||||
|
logoutUseCase.logout(ignoreSdkError = true)
|
||||||
|
}.runCatchingUpdatingState(signOutAction)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,12 +18,14 @@ package io.element.android.features.verifysession.impl
|
||||||
|
|
||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
import androidx.compose.runtime.Stable
|
import androidx.compose.runtime.Stable
|
||||||
|
import io.element.android.libraries.architecture.AsyncAction
|
||||||
import io.element.android.libraries.architecture.AsyncData
|
import io.element.android.libraries.architecture.AsyncData
|
||||||
import io.element.android.libraries.matrix.api.verification.SessionVerificationData
|
import io.element.android.libraries.matrix.api.verification.SessionVerificationData
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
data class VerifySelfSessionState(
|
data class VerifySelfSessionState(
|
||||||
val verificationFlowStep: VerificationStep,
|
val verificationFlowStep: VerificationStep,
|
||||||
|
val signOutAction: AsyncAction<String?>,
|
||||||
val displaySkipButton: Boolean,
|
val displaySkipButton: Boolean,
|
||||||
val eventSink: (VerifySelfSessionViewEvents) -> Unit,
|
val eventSink: (VerifySelfSessionViewEvents) -> Unit,
|
||||||
) {
|
) {
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ package io.element.android.features.verifysession.impl
|
||||||
|
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||||
import io.element.android.features.verifysession.impl.VerifySelfSessionState.VerificationStep
|
import io.element.android.features.verifysession.impl.VerifySelfSessionState.VerificationStep
|
||||||
|
import io.element.android.libraries.architecture.AsyncAction
|
||||||
import io.element.android.libraries.architecture.AsyncData
|
import io.element.android.libraries.architecture.AsyncData
|
||||||
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.VerificationEmoji
|
import io.element.android.libraries.matrix.api.verification.VerificationEmoji
|
||||||
|
|
@ -54,6 +55,10 @@ open class VerifySelfSessionStateProvider : PreviewParameterProvider<VerifySelfS
|
||||||
verificationFlowStep = VerificationStep.Completed,
|
verificationFlowStep = VerificationStep.Completed,
|
||||||
displaySkipButton = true,
|
displaySkipButton = true,
|
||||||
),
|
),
|
||||||
|
aVerifySelfSessionState(
|
||||||
|
signOutAction = AsyncAction.Loading,
|
||||||
|
displaySkipButton = true,
|
||||||
|
),
|
||||||
// Add other state here
|
// Add other state here
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -72,12 +77,14 @@ private fun aDecimalsSessionVerificationData(
|
||||||
|
|
||||||
internal fun aVerifySelfSessionState(
|
internal fun aVerifySelfSessionState(
|
||||||
verificationFlowStep: VerificationStep = VerificationStep.Initial(canEnterRecoveryKey = false),
|
verificationFlowStep: VerificationStep = VerificationStep.Initial(canEnterRecoveryKey = false),
|
||||||
|
signOutAction: AsyncAction<String?> = AsyncAction.Uninitialized,
|
||||||
displaySkipButton: Boolean = false,
|
displaySkipButton: Boolean = false,
|
||||||
eventSink: (VerifySelfSessionViewEvents) -> Unit = {},
|
eventSink: (VerifySelfSessionViewEvents) -> Unit = {},
|
||||||
) = VerifySelfSessionState(
|
) = VerifySelfSessionState(
|
||||||
verificationFlowStep = verificationFlowStep,
|
verificationFlowStep = verificationFlowStep,
|
||||||
displaySkipButton = displaySkipButton,
|
displaySkipButton = displaySkipButton,
|
||||||
eventSink = eventSink,
|
eventSink = eventSink,
|
||||||
|
signOutAction = signOutAction,
|
||||||
)
|
)
|
||||||
|
|
||||||
private fun aVerificationEmojiList() = listOf(
|
private fun aVerificationEmojiList() = listOf(
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,7 @@ import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.rememberUpdatedState
|
import androidx.compose.runtime.rememberUpdatedState
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.LocalInspectionMode
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
|
|
@ -46,11 +47,13 @@ import androidx.compose.ui.unit.dp
|
||||||
import io.element.android.compound.theme.ElementTheme
|
import io.element.android.compound.theme.ElementTheme
|
||||||
import io.element.android.compound.tokens.generated.CompoundIcons
|
import io.element.android.compound.tokens.generated.CompoundIcons
|
||||||
import io.element.android.features.verifysession.impl.emoji.toEmojiResource
|
import io.element.android.features.verifysession.impl.emoji.toEmojiResource
|
||||||
|
import io.element.android.libraries.architecture.AsyncAction
|
||||||
import io.element.android.libraries.architecture.AsyncData
|
import io.element.android.libraries.architecture.AsyncData
|
||||||
import io.element.android.libraries.designsystem.atomic.molecules.ButtonColumnMolecule
|
import io.element.android.libraries.designsystem.atomic.molecules.ButtonColumnMolecule
|
||||||
import io.element.android.libraries.designsystem.atomic.pages.HeaderFooterPage
|
import io.element.android.libraries.designsystem.atomic.pages.HeaderFooterPage
|
||||||
import io.element.android.libraries.designsystem.components.BigIcon
|
import io.element.android.libraries.designsystem.components.BigIcon
|
||||||
import io.element.android.libraries.designsystem.components.PageTitle
|
import io.element.android.libraries.designsystem.components.PageTitle
|
||||||
|
import io.element.android.libraries.designsystem.components.ProgressDialog
|
||||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||||
import io.element.android.libraries.designsystem.theme.components.Button
|
import io.element.android.libraries.designsystem.theme.components.Button
|
||||||
|
|
@ -70,11 +73,13 @@ fun VerifySelfSessionView(
|
||||||
onEnterRecoveryKey: () -> Unit,
|
onEnterRecoveryKey: () -> Unit,
|
||||||
onResetKey: () -> Unit,
|
onResetKey: () -> Unit,
|
||||||
onFinish: () -> Unit,
|
onFinish: () -> Unit,
|
||||||
|
onSuccessLogout: (String?) -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
fun resetFlow() {
|
fun resetFlow() {
|
||||||
state.eventSink(VerifySelfSessionViewEvents.Reset)
|
state.eventSink(VerifySelfSessionViewEvents.Reset)
|
||||||
}
|
}
|
||||||
|
|
||||||
val latestOnFinish by rememberUpdatedState(newValue = onFinish)
|
val latestOnFinish by rememberUpdatedState(newValue = onFinish)
|
||||||
LaunchedEffect(state.verificationFlowStep, latestOnFinish) {
|
LaunchedEffect(state.verificationFlowStep, latestOnFinish) {
|
||||||
if (state.verificationFlowStep is FlowStep.Skipped) {
|
if (state.verificationFlowStep is FlowStep.Skipped) {
|
||||||
|
|
@ -97,17 +102,25 @@ fun VerifySelfSessionView(
|
||||||
HeaderFooterPage(
|
HeaderFooterPage(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
topBar = {
|
topBar = {
|
||||||
TopAppBar(
|
TopAppBar(
|
||||||
title = {},
|
title = {},
|
||||||
actions = {
|
actions = {
|
||||||
if (state.displaySkipButton && state.verificationFlowStep != FlowStep.Completed) {
|
if (state.verificationFlowStep != FlowStep.Completed &&
|
||||||
TextButton(
|
state.displaySkipButton &&
|
||||||
text = stringResource(CommonStrings.action_skip),
|
LocalInspectionMode.current.not()) {
|
||||||
onClick = { state.eventSink(VerifySelfSessionViewEvents.SkipVerification) }
|
TextButton(
|
||||||
)
|
text = stringResource(CommonStrings.action_skip),
|
||||||
}
|
onClick = { state.eventSink(VerifySelfSessionViewEvents.SkipVerification) }
|
||||||
}
|
)
|
||||||
)
|
}
|
||||||
|
if (state.verificationFlowStep is FlowStep.Initial) {
|
||||||
|
TextButton(
|
||||||
|
text = stringResource(CommonStrings.action_signout),
|
||||||
|
onClick = { state.eventSink(VerifySelfSessionViewEvents.SignOut) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
},
|
},
|
||||||
header = {
|
header = {
|
||||||
HeaderContent(verificationFlowStep = verificationFlowStep)
|
HeaderContent(verificationFlowStep = verificationFlowStep)
|
||||||
|
|
@ -124,6 +137,21 @@ fun VerifySelfSessionView(
|
||||||
) {
|
) {
|
||||||
Content(flowState = verificationFlowStep)
|
Content(flowState = verificationFlowStep)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
when (state.signOutAction) {
|
||||||
|
AsyncAction.Loading -> {
|
||||||
|
ProgressDialog(text = stringResource(id = R.string.screen_signout_in_progress_dialog_content))
|
||||||
|
}
|
||||||
|
is AsyncAction.Success -> {
|
||||||
|
val latestOnSuccessLogout by rememberUpdatedState(onSuccessLogout)
|
||||||
|
LaunchedEffect(state) {
|
||||||
|
latestOnSuccessLogout(state.signOutAction.data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AsyncAction.Confirming,
|
||||||
|
is AsyncAction.Failure,
|
||||||
|
AsyncAction.Uninitialized -> Unit
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
|
@ -367,5 +395,6 @@ internal fun VerifySelfSessionViewPreview(@PreviewParameter(VerifySelfSessionSta
|
||||||
onEnterRecoveryKey = {},
|
onEnterRecoveryKey = {},
|
||||||
onResetKey = {},
|
onResetKey = {},
|
||||||
onFinish = {},
|
onFinish = {},
|
||||||
|
onSuccessLogout = {},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,5 +23,6 @@ sealed interface VerifySelfSessionViewEvents {
|
||||||
data object DeclineVerification : VerifySelfSessionViewEvents
|
data object DeclineVerification : VerifySelfSessionViewEvents
|
||||||
data object Cancel : VerifySelfSessionViewEvents
|
data object Cancel : VerifySelfSessionViewEvents
|
||||||
data object Reset : VerifySelfSessionViewEvents
|
data object Reset : VerifySelfSessionViewEvents
|
||||||
|
data object SignOut : VerifySelfSessionViewEvents
|
||||||
data object SkipVerification : VerifySelfSessionViewEvents
|
data object SkipVerification : VerifySelfSessionViewEvents
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -28,4 +28,5 @@
|
||||||
<string name="screen_session_verification_they_match">"They match"</string>
|
<string name="screen_session_verification_they_match">"They match"</string>
|
||||||
<string name="screen_session_verification_waiting_to_accept_subtitle">"Accept the request to start the verification process in your other session to continue."</string>
|
<string name="screen_session_verification_waiting_to_accept_subtitle">"Accept the request to start the verification process in your other session to continue."</string>
|
||||||
<string name="screen_session_verification_waiting_to_accept_title">"Waiting to accept request"</string>
|
<string name="screen_session_verification_waiting_to_accept_title">"Waiting to accept request"</string>
|
||||||
|
<string name="screen_signout_in_progress_dialog_content">"Signing out…"</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,8 @@ import app.cash.molecule.moleculeFlow
|
||||||
import app.cash.turbine.ReceiveTurbine
|
import app.cash.turbine.ReceiveTurbine
|
||||||
import app.cash.turbine.test
|
import app.cash.turbine.test
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
|
import io.element.android.features.logout.api.LogoutUseCase
|
||||||
|
import io.element.android.features.logout.test.FakeLogoutUseCase
|
||||||
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.core.meta.BuildMeta
|
import io.element.android.libraries.core.meta.BuildMeta
|
||||||
|
|
@ -36,6 +38,8 @@ 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.libraries.preferences.test.InMemorySessionPreferencesStore
|
import io.element.android.libraries.preferences.test.InMemorySessionPreferencesStore
|
||||||
import io.element.android.tests.testutils.WarmUpRule
|
import io.element.android.tests.testutils.WarmUpRule
|
||||||
|
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
||||||
|
import io.element.android.tests.testutils.lambda.value
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
|
|
@ -309,6 +313,31 @@ class VerifySelfSessionPresenterTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `present - When user request to sign out, the sign out use case is invoked`() = runTest {
|
||||||
|
val service = FakeSessionVerificationService().apply {
|
||||||
|
givenNeedsSessionVerification(false)
|
||||||
|
givenVerifiedStatus(SessionVerifiedStatus.Verified)
|
||||||
|
givenVerificationFlowState(VerificationFlowState.Finished)
|
||||||
|
}
|
||||||
|
val signOutLambda = lambdaRecorder<Boolean, String?> { "aUrl" }
|
||||||
|
val presenter = createVerifySelfSessionPresenter(
|
||||||
|
service,
|
||||||
|
logoutUseCase = FakeLogoutUseCase(signOutLambda)
|
||||||
|
)
|
||||||
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
|
presenter.present()
|
||||||
|
}.test {
|
||||||
|
skipItems(1)
|
||||||
|
val initialItem = awaitItem()
|
||||||
|
initialItem.eventSink(VerifySelfSessionViewEvents.SignOut)
|
||||||
|
val finalItem = awaitItem()
|
||||||
|
assertThat(finalItem.signOutAction.isSuccess()).isTrue()
|
||||||
|
assertThat(finalItem.signOutAction.dataOrNull()).isEqualTo("aUrl")
|
||||||
|
signOutLambda.assertions().isCalledOnce().with(value(true))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private suspend fun ReceiveTurbine<VerifySelfSessionState>.requestVerificationAndAwaitVerifyingState(
|
private suspend fun ReceiveTurbine<VerifySelfSessionState>.requestVerificationAndAwaitVerifyingState(
|
||||||
fakeService: FakeSessionVerificationService,
|
fakeService: FakeSessionVerificationService,
|
||||||
sessionVerificationData: SessionVerificationData = SessionVerificationData.Emojis(emptyList()),
|
sessionVerificationData: SessionVerificationData = SessionVerificationData.Emojis(emptyList()),
|
||||||
|
|
@ -344,6 +373,7 @@ class VerifySelfSessionPresenterTest {
|
||||||
encryptionService: EncryptionService = FakeEncryptionService(),
|
encryptionService: EncryptionService = FakeEncryptionService(),
|
||||||
buildMeta: BuildMeta = aBuildMeta(),
|
buildMeta: BuildMeta = aBuildMeta(),
|
||||||
sessionPreferencesStore: InMemorySessionPreferencesStore = InMemorySessionPreferencesStore(),
|
sessionPreferencesStore: InMemorySessionPreferencesStore = InMemorySessionPreferencesStore(),
|
||||||
|
logoutUseCase: LogoutUseCase = FakeLogoutUseCase(),
|
||||||
): VerifySelfSessionPresenter {
|
): VerifySelfSessionPresenter {
|
||||||
return VerifySelfSessionPresenter(
|
return VerifySelfSessionPresenter(
|
||||||
sessionVerificationService = service,
|
sessionVerificationService = service,
|
||||||
|
|
@ -351,6 +381,7 @@ class VerifySelfSessionPresenterTest {
|
||||||
stateMachine = VerifySelfSessionStateMachine(service, encryptionService),
|
stateMachine = VerifySelfSessionStateMachine(service, encryptionService),
|
||||||
buildMeta = buildMeta,
|
buildMeta = buildMeta,
|
||||||
sessionPreferencesStore = sessionPreferencesStore,
|
sessionPreferencesStore = sessionPreferencesStore,
|
||||||
|
logoutUseCase = logoutUseCase,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ import androidx.activity.ComponentActivity
|
||||||
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
|
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
|
||||||
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
|
import io.element.android.libraries.architecture.AsyncAction
|
||||||
import io.element.android.libraries.architecture.AsyncData
|
import io.element.android.libraries.architecture.AsyncData
|
||||||
import io.element.android.libraries.ui.strings.CommonStrings
|
import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
import io.element.android.tests.testutils.EnsureNeverCalled
|
import io.element.android.tests.testutils.EnsureNeverCalled
|
||||||
|
|
@ -27,6 +28,7 @@ import io.element.android.tests.testutils.EnsureNeverCalledWithParam
|
||||||
import io.element.android.tests.testutils.EventsRecorder
|
import io.element.android.tests.testutils.EventsRecorder
|
||||||
import io.element.android.tests.testutils.clickOn
|
import io.element.android.tests.testutils.clickOn
|
||||||
import io.element.android.tests.testutils.ensureCalledOnce
|
import io.element.android.tests.testutils.ensureCalledOnce
|
||||||
|
import io.element.android.tests.testutils.ensureCalledOnceWithParam
|
||||||
import io.element.android.tests.testutils.pressBackKey
|
import io.element.android.tests.testutils.pressBackKey
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
@ -213,11 +215,26 @@ class VerifySelfSessionViewTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `on success logout - onFinished callback is called immediately`() {
|
||||||
|
val aUrl = "aUrl"
|
||||||
|
ensureCalledOnceWithParam<String?>(aUrl) { callback ->
|
||||||
|
rule.setVerifySelfSessionView(
|
||||||
|
aVerifySelfSessionState(
|
||||||
|
signOutAction = AsyncAction.Success(aUrl),
|
||||||
|
eventSink = EnsureNeverCalledWithParam(),
|
||||||
|
),
|
||||||
|
onSuccessLogout = callback,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setVerifySelfSessionView(
|
private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setVerifySelfSessionView(
|
||||||
state: VerifySelfSessionState,
|
state: VerifySelfSessionState,
|
||||||
onEnterRecoveryKey: () -> Unit = EnsureNeverCalled(),
|
onEnterRecoveryKey: () -> Unit = EnsureNeverCalled(),
|
||||||
onFinished: () -> Unit = EnsureNeverCalled(),
|
onFinished: () -> Unit = EnsureNeverCalled(),
|
||||||
onResetKey: () -> Unit = EnsureNeverCalled(),
|
onResetKey: () -> Unit = EnsureNeverCalled(),
|
||||||
|
onSuccessLogout: (String?) -> Unit = EnsureNeverCalledWithParam(),
|
||||||
) {
|
) {
|
||||||
setContent {
|
setContent {
|
||||||
VerifySelfSessionView(
|
VerifySelfSessionView(
|
||||||
|
|
@ -225,6 +242,7 @@ class VerifySelfSessionViewTest {
|
||||||
onEnterRecoveryKey = onEnterRecoveryKey,
|
onEnterRecoveryKey = onEnterRecoveryKey,
|
||||||
onFinish = onFinished,
|
onFinish = onFinished,
|
||||||
onResetKey = onResetKey,
|
onResetKey = onResetKey,
|
||||||
|
onSuccessLogout = onSuccessLogout,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:711074ce017e2909ae1cb610925bf7be96e182095cb08de5ca1d57b4dd206c92
|
oid sha256:205fc4655fed7b11ef133e0df13b8175676ff38d398fde45a2e6a3b140a216e2
|
||||||
size 30834
|
size 31440
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:1f7797415de4a52095a47dc7beb5b51c7528a90e9c8daf5e4d3eff030b22f3f7
|
||||||
|
size 32596
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:c242503ced2d16da2fd8c738e198689b60d00d40d423b52820aa01a622acd39a
|
oid sha256:205fc4655fed7b11ef133e0df13b8175676ff38d398fde45a2e6a3b140a216e2
|
||||||
size 29695
|
size 31440
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:d8d6fedd34ea54f9492bdc74f784aac902717328e3eda83f97d06e1eef970424
|
oid sha256:2a8350fe244b42863ee1fb60c403aa2c695a5152755f73bf27598e71e033ef0b
|
||||||
size 24201
|
size 25962
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:df4be3e3577698e939dc02ebc0819768c04525a6b6cc67386e33501610dff5b2
|
oid sha256:4307ad367eb24d8f7590dfadf4e4b728ec4845ba4720b72824f5b2c60175f8fd
|
||||||
size 29959
|
size 30555
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:a66446c1dc2ff1e32fd06ee722aaaed578369e6de0a0024d435104bcadc47d49
|
||||||
|
size 31012
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:a229523ad1f17f957adaffb47e8a64679bc3636ab94641bc492f98b7f5d3fe3d
|
oid sha256:4307ad367eb24d8f7590dfadf4e4b728ec4845ba4720b72824f5b2c60175f8fd
|
||||||
size 28861
|
size 30555
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:b090b019f8d0a98b9e0ec4e4bd53628af15737eb441f36f40ba4abceb4c75608
|
oid sha256:b0da42a76838a54311a5ea6ec54725f3bb9c91c4ff7bc09ff571d6cd7b6796e1
|
||||||
size 23622
|
size 25369
|
||||||
|
|
|
||||||
|
|
@ -55,6 +55,7 @@
|
||||||
"name" : ":features:verifysession:impl",
|
"name" : ":features:verifysession:impl",
|
||||||
"includeRegex" : [
|
"includeRegex" : [
|
||||||
"screen_session_verification_.*",
|
"screen_session_verification_.*",
|
||||||
|
"screen_signout_in_progress_dialog_content",
|
||||||
"screen_identity_.*"
|
"screen_identity_.*"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue