Add a way to sign out when the user is asked to verify the session.
This commit is contained in:
parent
82fa369c8b
commit
20a8862b3f
11 changed files with 125 additions and 11 deletions
|
|
@ -16,8 +16,10 @@
|
|||
|
||||
package io.element.android.features.verifysession.impl
|
||||
|
||||
import android.app.Activity
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import com.bumble.appyx.core.modality.BuildContext
|
||||
import com.bumble.appyx.core.node.Node
|
||||
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.AssistedInject
|
||||
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.libraries.di.SessionScope
|
||||
|
||||
|
|
@ -39,12 +43,15 @@ class VerifySelfSessionNode @AssistedInject constructor(
|
|||
@Composable
|
||||
override fun View(modifier: Modifier) {
|
||||
val state = presenter.present()
|
||||
val activity = LocalContext.current as Activity
|
||||
val isDark = ElementTheme.isLightTheme.not()
|
||||
VerifySelfSessionView(
|
||||
state = state,
|
||||
modifier = modifier,
|
||||
onEnterRecoveryKey = callback::onEnterRecoveryKey,
|
||||
onResetKey = callback::onResetKey,
|
||||
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.LaunchedEffect
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
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.Presenter
|
||||
import io.element.android.libraries.architecture.runCatchingUpdatingState
|
||||
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.RecoveryState
|
||||
|
|
@ -49,6 +54,7 @@ class VerifySelfSessionPresenter @Inject constructor(
|
|||
private val stateMachine: VerifySelfSessionStateMachine,
|
||||
private val buildMeta: BuildMeta,
|
||||
private val sessionPreferencesStore: SessionPreferencesStore,
|
||||
private val logoutUseCase: LogoutUseCase,
|
||||
) : Presenter<VerifySelfSessionState> {
|
||||
@Composable
|
||||
override fun present(): VerifySelfSessionState {
|
||||
|
|
@ -61,6 +67,9 @@ class VerifySelfSessionPresenter @Inject constructor(
|
|||
val stateAndDispatch = stateMachine.rememberStateAndDispatch()
|
||||
val skipVerification by sessionPreferencesStore.isSessionVerificationSkipped().collectAsState(initial = false)
|
||||
val needsVerification by sessionVerificationService.needsSessionVerification.collectAsState(initial = true)
|
||||
val signOutAction = remember {
|
||||
mutableStateOf<AsyncAction<String?>>(AsyncAction.Uninitialized)
|
||||
}
|
||||
val verificationFlowStep by remember {
|
||||
derivedStateOf {
|
||||
when {
|
||||
|
|
@ -85,6 +94,7 @@ class VerifySelfSessionPresenter @Inject constructor(
|
|||
VerifySelfSessionViewEvents.DeclineVerification -> stateAndDispatch.dispatchAction(StateMachineEvent.DeclineChallenge)
|
||||
VerifySelfSessionViewEvents.Cancel -> stateAndDispatch.dispatchAction(StateMachineEvent.Cancel)
|
||||
VerifySelfSessionViewEvents.Reset -> stateAndDispatch.dispatchAction(StateMachineEvent.Reset)
|
||||
VerifySelfSessionViewEvents.SignOut -> coroutineScope.signOut(signOutAction)
|
||||
VerifySelfSessionViewEvents.SkipVerification -> coroutineScope.launch {
|
||||
sessionPreferencesStore.setSkipSessionVerification(true)
|
||||
}
|
||||
|
|
@ -92,6 +102,7 @@ class VerifySelfSessionPresenter @Inject constructor(
|
|||
}
|
||||
return VerifySelfSessionState(
|
||||
verificationFlowStep = verificationFlowStep,
|
||||
signOutAction = signOutAction.value,
|
||||
displaySkipButton = buildMeta.isDebuggable,
|
||||
eventSink = ::handleEvents,
|
||||
)
|
||||
|
|
@ -160,4 +171,10 @@ class VerifySelfSessionPresenter @Inject constructor(
|
|||
}
|
||||
}.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.Stable
|
||||
import io.element.android.libraries.architecture.AsyncAction
|
||||
import io.element.android.libraries.architecture.AsyncData
|
||||
import io.element.android.libraries.matrix.api.verification.SessionVerificationData
|
||||
|
||||
@Immutable
|
||||
data class VerifySelfSessionState(
|
||||
val verificationFlowStep: VerificationStep,
|
||||
val signOutAction: AsyncAction<String?>,
|
||||
val displaySkipButton: Boolean,
|
||||
val eventSink: (VerifySelfSessionViewEvents) -> Unit,
|
||||
) {
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ package io.element.android.features.verifysession.impl
|
|||
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
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.matrix.api.verification.SessionVerificationData
|
||||
import io.element.android.libraries.matrix.api.verification.VerificationEmoji
|
||||
|
|
@ -54,6 +55,10 @@ open class VerifySelfSessionStateProvider : PreviewParameterProvider<VerifySelfS
|
|||
verificationFlowStep = VerificationStep.Completed,
|
||||
displaySkipButton = true,
|
||||
),
|
||||
aVerifySelfSessionState(
|
||||
signOutAction = AsyncAction.Loading,
|
||||
displaySkipButton = true,
|
||||
),
|
||||
// Add other state here
|
||||
)
|
||||
}
|
||||
|
|
@ -72,12 +77,14 @@ private fun aDecimalsSessionVerificationData(
|
|||
|
||||
internal fun aVerifySelfSessionState(
|
||||
verificationFlowStep: VerificationStep = VerificationStep.Initial(canEnterRecoveryKey = false),
|
||||
signOutAction: AsyncAction<String?> = AsyncAction.Uninitialized,
|
||||
displaySkipButton: Boolean = false,
|
||||
eventSink: (VerifySelfSessionViewEvents) -> Unit = {},
|
||||
) = VerifySelfSessionState(
|
||||
verificationFlowStep = verificationFlowStep,
|
||||
displaySkipButton = displaySkipButton,
|
||||
eventSink = eventSink,
|
||||
signOutAction = signOutAction,
|
||||
)
|
||||
|
||||
private fun aVerificationEmojiList() = listOf(
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ import androidx.compose.runtime.getValue
|
|||
import androidx.compose.runtime.rememberUpdatedState
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalInspectionMode
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
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.tokens.generated.CompoundIcons
|
||||
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.designsystem.atomic.molecules.ButtonColumnMolecule
|
||||
import io.element.android.libraries.designsystem.atomic.pages.HeaderFooterPage
|
||||
import io.element.android.libraries.designsystem.components.BigIcon
|
||||
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.PreviewsDayNight
|
||||
import io.element.android.libraries.designsystem.theme.components.Button
|
||||
|
|
@ -70,11 +73,13 @@ fun VerifySelfSessionView(
|
|||
onEnterRecoveryKey: () -> Unit,
|
||||
onResetKey: () -> Unit,
|
||||
onFinish: () -> Unit,
|
||||
onSuccessLogout: (String?) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
fun resetFlow() {
|
||||
state.eventSink(VerifySelfSessionViewEvents.Reset)
|
||||
}
|
||||
|
||||
val latestOnFinish by rememberUpdatedState(newValue = onFinish)
|
||||
LaunchedEffect(state.verificationFlowStep, latestOnFinish) {
|
||||
if (state.verificationFlowStep is FlowStep.Skipped) {
|
||||
|
|
@ -97,17 +102,23 @@ fun VerifySelfSessionView(
|
|||
HeaderFooterPage(
|
||||
modifier = modifier,
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
title = {},
|
||||
actions = {
|
||||
if (state.displaySkipButton && state.verificationFlowStep != FlowStep.Completed) {
|
||||
TextButton(
|
||||
text = stringResource(CommonStrings.action_skip),
|
||||
onClick = { state.eventSink(VerifySelfSessionViewEvents.SkipVerification) }
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
TopAppBar(
|
||||
title = {},
|
||||
actions = {
|
||||
if (state.verificationFlowStep != FlowStep.Completed) {
|
||||
if (state.displaySkipButton && LocalInspectionMode.current.not()) {
|
||||
TextButton(
|
||||
text = stringResource(CommonStrings.action_skip),
|
||||
onClick = { state.eventSink(VerifySelfSessionViewEvents.SkipVerification) }
|
||||
)
|
||||
}
|
||||
TextButton(
|
||||
text = stringResource(CommonStrings.action_signout),
|
||||
onClick = { state.eventSink(VerifySelfSessionViewEvents.SignOut) }
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
},
|
||||
header = {
|
||||
HeaderContent(verificationFlowStep = verificationFlowStep)
|
||||
|
|
@ -124,6 +135,21 @@ fun VerifySelfSessionView(
|
|||
) {
|
||||
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
|
||||
|
|
@ -367,5 +393,6 @@ internal fun VerifySelfSessionViewPreview(@PreviewParameter(VerifySelfSessionSta
|
|||
onEnterRecoveryKey = {},
|
||||
onResetKey = {},
|
||||
onFinish = {},
|
||||
onSuccessLogout = {},
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,5 +23,6 @@ sealed interface VerifySelfSessionViewEvents {
|
|||
data object DeclineVerification : VerifySelfSessionViewEvents
|
||||
data object Cancel : VerifySelfSessionViewEvents
|
||||
data object Reset : VerifySelfSessionViewEvents
|
||||
data object SignOut : VerifySelfSessionViewEvents
|
||||
data object SkipVerification : VerifySelfSessionViewEvents
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,4 +28,5 @@
|
|||
<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_title">"Waiting to accept request"</string>
|
||||
<string name="screen_signout_in_progress_dialog_content">"Signing out…"</string>
|
||||
</resources>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue