mirror of
https://github.com/Novaloop-AG/element-x-android.git
synced 2026-11-03 06:55:41 +00:00
Merge pull request #7551 from hayaksi1/fix/5344-verification-unavailable
Say so when this session has no secrets to verify another one with
This commit is contained in:
+10
-2
@@ -13,6 +13,7 @@ package io.element.android.features.verifysession.impl.incoming
|
|||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.DisposableEffect
|
import androidx.compose.runtime.DisposableEffect
|
||||||
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
|
||||||
@@ -26,6 +27,8 @@ import io.element.android.libraries.architecture.Presenter
|
|||||||
import io.element.android.libraries.dateformatter.api.DateFormatter
|
import io.element.android.libraries.dateformatter.api.DateFormatter
|
||||||
import io.element.android.libraries.dateformatter.api.DateFormatterMode
|
import io.element.android.libraries.dateformatter.api.DateFormatterMode
|
||||||
import io.element.android.libraries.di.annotations.SessionCoroutineScope
|
import io.element.android.libraries.di.annotations.SessionCoroutineScope
|
||||||
|
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.SessionVerificationRequestDetails
|
import io.element.android.libraries.matrix.api.verification.SessionVerificationRequestDetails
|
||||||
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
|
||||||
@@ -45,6 +48,7 @@ class IncomingVerificationPresenter(
|
|||||||
@Assisted private val navigator: IncomingVerificationNavigator,
|
@Assisted private val navigator: IncomingVerificationNavigator,
|
||||||
@SessionCoroutineScope private val sessionCoroutineScope: CoroutineScope,
|
@SessionCoroutineScope private val sessionCoroutineScope: CoroutineScope,
|
||||||
private val sessionVerificationService: SessionVerificationService,
|
private val sessionVerificationService: SessionVerificationService,
|
||||||
|
private val encryptionService: EncryptionService,
|
||||||
private val stateMachine: IncomingVerificationStateMachine,
|
private val stateMachine: IncomingVerificationStateMachine,
|
||||||
private val dateFormatter: DateFormatter,
|
private val dateFormatter: DateFormatter,
|
||||||
) : Presenter<IncomingVerificationState> {
|
) : Presenter<IncomingVerificationState> {
|
||||||
@@ -92,12 +96,15 @@ class IncomingVerificationPresenter(
|
|||||||
mode = DateFormatterMode.TimeOrDate,
|
mode = DateFormatterMode.TimeOrDate,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
val step by remember {
|
val recoveryState by encryptionService.recoveryStateStateFlow.collectAsState()
|
||||||
|
val isMissingSecrets = recoveryState == RecoveryState.INCOMPLETE
|
||||||
|
val step by remember(isMissingSecrets) {
|
||||||
derivedStateOf {
|
derivedStateOf {
|
||||||
stateAndDispatch.state.value.toVerificationStep(
|
val currentStep = stateAndDispatch.state.value.toVerificationStep(
|
||||||
sessionVerificationRequestDetails = verificationRequest.details,
|
sessionVerificationRequestDetails = verificationRequest.details,
|
||||||
formattedSignInTime = formattedSignInTime,
|
formattedSignInTime = formattedSignInTime,
|
||||||
)
|
)
|
||||||
|
if (isMissingSecrets && currentStep is Step.Initial) Step.Unavailable else currentStep
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -131,6 +138,7 @@ class IncomingVerificationPresenter(
|
|||||||
} else {
|
} else {
|
||||||
stateAndDispatch.dispatchAction(StateMachineEvent.DeclineChallenge)
|
stateAndDispatch.dispatchAction(StateMachineEvent.DeclineChallenge)
|
||||||
}
|
}
|
||||||
|
Step.Unavailable,
|
||||||
Step.Canceled,
|
Step.Canceled,
|
||||||
Step.Completed,
|
Step.Completed,
|
||||||
Step.Failure -> navigator.onFinish()
|
Step.Failure -> navigator.onFinish()
|
||||||
|
|||||||
+2
@@ -32,6 +32,8 @@ data class IncomingVerificationState(
|
|||||||
val isWaiting: Boolean,
|
val isWaiting: Boolean,
|
||||||
) : Step
|
) : Step
|
||||||
|
|
||||||
|
data object Unavailable : Step
|
||||||
|
|
||||||
data object Canceled : Step
|
data object Canceled : Step
|
||||||
data object Completed : Step
|
data object Completed : Step
|
||||||
data object Failure : Step
|
data object Failure : Step
|
||||||
|
|||||||
+1
@@ -43,6 +43,7 @@ open class IncomingVerificationStatePreviewParam : PreviewParameterProvider<Inco
|
|||||||
anIncomingVerificationState(step = Step.Completed, verificationRequest = anIncomingUserVerificationRequest()),
|
anIncomingVerificationState(step = Step.Completed, verificationRequest = anIncomingUserVerificationRequest()),
|
||||||
anIncomingVerificationState(step = Step.Failure),
|
anIncomingVerificationState(step = Step.Failure),
|
||||||
anIncomingVerificationState(step = Step.Canceled),
|
anIncomingVerificationState(step = Step.Canceled),
|
||||||
|
anIncomingVerificationState(step = Step.Unavailable),
|
||||||
// Add other state here
|
// Add other state here
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
+13
-1
@@ -95,7 +95,8 @@ fun IncomingVerificationView(
|
|||||||
@Composable
|
@Composable
|
||||||
private fun IncomingVerificationHeader(step: Step, request: VerificationRequest.Incoming) {
|
private fun IncomingVerificationHeader(step: Step, request: VerificationRequest.Incoming) {
|
||||||
val iconStyle = when (step) {
|
val iconStyle = when (step) {
|
||||||
Step.Canceled -> BigIcon.Style.AlertSolid
|
Step.Canceled,
|
||||||
|
Step.Unavailable -> BigIcon.Style.AlertSolid
|
||||||
is Step.Initial -> when (request) {
|
is Step.Initial -> when (request) {
|
||||||
is VerificationRequest.Incoming.OtherSession -> BigIcon.Style.Default(CompoundIcons.Devices())
|
is VerificationRequest.Incoming.OtherSession -> BigIcon.Style.Default(CompoundIcons.Devices())
|
||||||
is VerificationRequest.Incoming.User -> BigIcon.Style.Default(CompoundIcons.UserProfileSolid())
|
is VerificationRequest.Incoming.User -> BigIcon.Style.Default(CompoundIcons.UserProfileSolid())
|
||||||
@@ -106,6 +107,7 @@ private fun IncomingVerificationHeader(step: Step, request: VerificationRequest.
|
|||||||
}
|
}
|
||||||
val titleTextId = when (step) {
|
val titleTextId = when (step) {
|
||||||
Step.Canceled -> CommonStrings.common_verification_failed
|
Step.Canceled -> CommonStrings.common_verification_failed
|
||||||
|
Step.Unavailable -> R.string.screen_session_verification_unavailable_title
|
||||||
is Step.Initial -> R.string.screen_session_verification_request_title
|
is Step.Initial -> R.string.screen_session_verification_request_title
|
||||||
is Step.Verifying -> when (step.data) {
|
is Step.Verifying -> when (step.data) {
|
||||||
is SessionVerificationData.Decimals -> R.string.screen_session_verification_compare_numbers_title
|
is SessionVerificationData.Decimals -> R.string.screen_session_verification_compare_numbers_title
|
||||||
@@ -116,6 +118,7 @@ private fun IncomingVerificationHeader(step: Step, request: VerificationRequest.
|
|||||||
}
|
}
|
||||||
val subtitleTextId = when (step) {
|
val subtitleTextId = when (step) {
|
||||||
Step.Canceled -> R.string.screen_session_verification_request_failure_subtitle
|
Step.Canceled -> R.string.screen_session_verification_request_failure_subtitle
|
||||||
|
Step.Unavailable -> R.string.screen_session_verification_unavailable_subtitle
|
||||||
is Step.Initial -> when (request) {
|
is Step.Initial -> when (request) {
|
||||||
is VerificationRequest.Incoming.OtherSession -> R.string.screen_session_verification_request_subtitle
|
is VerificationRequest.Incoming.OtherSession -> R.string.screen_session_verification_request_subtitle
|
||||||
is VerificationRequest.Incoming.User -> R.string.screen_session_verification_user_responder_subtitle
|
is VerificationRequest.Incoming.User -> R.string.screen_session_verification_user_responder_subtitle
|
||||||
@@ -246,6 +249,15 @@ private fun IncomingVerificationBottomMenu(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Step.Unavailable -> {
|
||||||
|
VerificationBottomMenu {
|
||||||
|
Button(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
text = stringResource(CommonStrings.action_ignore),
|
||||||
|
onClick = { eventSink(IncomingVerificationViewEvent.IgnoreVerification) },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
Step.Canceled,
|
Step.Canceled,
|
||||||
is Step.Completed,
|
is Step.Completed,
|
||||||
is Step.Failure -> {
|
is Step.Failure -> {
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?><!--
|
||||||
|
~ Copyright (c) 2026 Element Creations Ltd.
|
||||||
|
~
|
||||||
|
~ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||||
|
~ Please see LICENSE files in the repository root for full details.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<resources>
|
||||||
|
<string name="screen_session_verification_unavailable_title">Verification with this device is currently unavailable</string>
|
||||||
|
<string name="screen_session_verification_unavailable_subtitle">Please use another device or recovery key.</string>
|
||||||
|
</resources>
|
||||||
+46
@@ -13,6 +13,8 @@ import io.element.android.features.verifysession.impl.ui.aEmojisSessionVerificat
|
|||||||
import io.element.android.libraries.dateformatter.api.DateFormatter
|
import io.element.android.libraries.dateformatter.api.DateFormatter
|
||||||
import io.element.android.libraries.dateformatter.test.FakeDateFormatter
|
import io.element.android.libraries.dateformatter.test.FakeDateFormatter
|
||||||
import io.element.android.libraries.matrix.api.core.FlowId
|
import io.element.android.libraries.matrix.api.core.FlowId
|
||||||
|
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.user.MatrixUser
|
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||||
import io.element.android.libraries.matrix.api.verification.SessionVerificationRequestDetails
|
import io.element.android.libraries.matrix.api.verification.SessionVerificationRequestDetails
|
||||||
import io.element.android.libraries.matrix.api.verification.SessionVerificationService
|
import io.element.android.libraries.matrix.api.verification.SessionVerificationService
|
||||||
@@ -21,6 +23,7 @@ import io.element.android.libraries.matrix.api.verification.VerificationRequest
|
|||||||
import io.element.android.libraries.matrix.test.A_DEVICE_ID
|
import io.element.android.libraries.matrix.test.A_DEVICE_ID
|
||||||
import io.element.android.libraries.matrix.test.A_TIMESTAMP
|
import io.element.android.libraries.matrix.test.A_TIMESTAMP
|
||||||
import io.element.android.libraries.matrix.test.A_USER_ID
|
import io.element.android.libraries.matrix.test.A_USER_ID
|
||||||
|
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 io.element.android.tests.testutils.lambda.lambdaError
|
import io.element.android.tests.testutils.lambda.lambdaError
|
||||||
@@ -294,6 +297,47 @@ class IncomingVerificationPresenterTest {
|
|||||||
navigatorLambda.assertions().isCalledOnce()
|
navigatorLambda.assertions().isCalledOnce()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `present - a session missing its secrets cannot be used to verify and offers no verification action`() = runTest {
|
||||||
|
val acknowledgeVerificationRequestLambda = lambdaRecorder<VerificationRequest.Incoming, Unit> { _ -> }
|
||||||
|
val resetLambda = lambdaRecorder<Boolean, Unit> { }
|
||||||
|
val acceptVerificationRequestLambda = lambdaRecorder<Unit> { lambdaError() }
|
||||||
|
val encryptionService = FakeEncryptionService().apply {
|
||||||
|
recoveryStateStateFlow.value = RecoveryState.INCOMPLETE
|
||||||
|
}
|
||||||
|
createPresenter(
|
||||||
|
service = FakeSessionVerificationService(
|
||||||
|
acknowledgeVerificationRequestLambda = acknowledgeVerificationRequestLambda,
|
||||||
|
acceptVerificationRequestLambda = acceptVerificationRequestLambda,
|
||||||
|
resetLambda = resetLambda,
|
||||||
|
),
|
||||||
|
encryptionService = encryptionService,
|
||||||
|
).test {
|
||||||
|
advanceUntilIdle()
|
||||||
|
assertThat(awaitItem().step).isEqualTo(IncomingVerificationState.Step.Unavailable)
|
||||||
|
acceptVerificationRequestLambda.assertions().isNeverCalled()
|
||||||
|
cancelAndIgnoreRemainingEvents()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `present - a session that holds its secrets is offered the verification action`() = runTest {
|
||||||
|
val encryptionService = FakeEncryptionService().apply {
|
||||||
|
recoveryStateStateFlow.value = RecoveryState.ENABLED
|
||||||
|
}
|
||||||
|
createPresenter(
|
||||||
|
service = FakeSessionVerificationService(
|
||||||
|
acknowledgeVerificationRequestLambda = { },
|
||||||
|
resetLambda = { },
|
||||||
|
),
|
||||||
|
encryptionService = encryptionService,
|
||||||
|
).test {
|
||||||
|
advanceUntilIdle()
|
||||||
|
assertThat(awaitItem().step).isInstanceOf(IncomingVerificationState.Step.Initial::class.java)
|
||||||
|
cancelAndIgnoreRemainingEvents()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val anIncomingSessionVerificationRequest = VerificationRequest.Incoming.OtherSession(
|
private val anIncomingSessionVerificationRequest = VerificationRequest.Incoming.OtherSession(
|
||||||
@@ -314,11 +358,13 @@ internal fun TestScope.createPresenter(
|
|||||||
verificationRequest: VerificationRequest.Incoming = anIncomingSessionVerificationRequest,
|
verificationRequest: VerificationRequest.Incoming = anIncomingSessionVerificationRequest,
|
||||||
navigator: IncomingVerificationNavigator = IncomingVerificationNavigator { lambdaError() },
|
navigator: IncomingVerificationNavigator = IncomingVerificationNavigator { lambdaError() },
|
||||||
service: SessionVerificationService = FakeSessionVerificationService(),
|
service: SessionVerificationService = FakeSessionVerificationService(),
|
||||||
|
encryptionService: EncryptionService = FakeEncryptionService(),
|
||||||
dateFormatter: DateFormatter = FakeDateFormatter(),
|
dateFormatter: DateFormatter = FakeDateFormatter(),
|
||||||
) = IncomingVerificationPresenter(
|
) = IncomingVerificationPresenter(
|
||||||
verificationRequest = verificationRequest,
|
verificationRequest = verificationRequest,
|
||||||
navigator = navigator,
|
navigator = navigator,
|
||||||
sessionVerificationService = service,
|
sessionVerificationService = service,
|
||||||
|
encryptionService = encryptionService,
|
||||||
stateMachine = IncomingVerificationStateMachine(service),
|
stateMachine = IncomingVerificationStateMachine(service),
|
||||||
dateFormatter = dateFormatter,
|
dateFormatter = dateFormatter,
|
||||||
sessionCoroutineScope = backgroundScope,
|
sessionCoroutineScope = backgroundScope,
|
||||||
|
|||||||
BIN
Binary file not shown.
BIN
Binary file not shown.
Reference in New Issue
Block a user