Merge pull request #7899 from element-hq/feature/bma/multiAccountDeviceVerification

Show account information on the identity confirmation screen
This commit is contained in:
Benoit Marty
2026-10-13 11:31:13 +02:00
committed by GitHub
16 changed files with 87 additions and 32 deletions
+1
View File
@@ -32,6 +32,7 @@ dependencies {
implementation(projects.libraries.core) implementation(projects.libraries.core)
implementation(projects.libraries.architecture) implementation(projects.libraries.architecture)
implementation(projects.libraries.matrix.api) implementation(projects.libraries.matrix.api)
implementation(projects.libraries.matrixui)
implementation(projects.libraries.designsystem) implementation(projects.libraries.designsystem)
implementation(projects.libraries.preferences.api) implementation(projects.libraries.preferences.api)
implementation(projects.libraries.uiCommon) implementation(projects.libraries.uiCommon)
@@ -20,16 +20,19 @@ import io.element.android.features.logout.api.direct.DirectLogoutState
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.core.coroutine.mapState import io.element.android.libraries.core.coroutine.mapState
import io.element.android.libraries.matrix.api.MatrixClient
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
@Inject @Inject
class ChooseSelfVerificationModePresenter( class ChooseSelfVerificationModePresenter(
private val matrixClient: MatrixClient,
private val encryptionService: EncryptionService, private val encryptionService: EncryptionService,
private val directLogoutPresenter: Presenter<DirectLogoutState>, private val directLogoutPresenter: Presenter<DirectLogoutState>,
) : Presenter<ChooseSelfVerificationModeState> { ) : Presenter<ChooseSelfVerificationModeState> {
@Composable @Composable
override fun present(): ChooseSelfVerificationModeState { override fun present(): ChooseSelfVerificationModeState {
val currentUser by matrixClient.userProfile.collectAsState()
val hasDevicesToVerifyAgainst by encryptionService.hasDevicesToVerifyAgainst.collectAsState() val hasDevicesToVerifyAgainst by encryptionService.hasDevicesToVerifyAgainst.collectAsState()
val canUseRecoveryKey by produceState<AsyncData<Boolean>>(AsyncData.Uninitialized) { val canUseRecoveryKey by produceState<AsyncData<Boolean>>(AsyncData.Uninitialized) {
encryptionService.recoveryStateStateFlow encryptionService.recoveryStateStateFlow
@@ -72,6 +75,7 @@ class ChooseSelfVerificationModePresenter(
} }
return ChooseSelfVerificationModeState( return ChooseSelfVerificationModeState(
currentUser = currentUser,
buttonsState = buttonsState, buttonsState = buttonsState,
directLogoutState = directLogoutState, directLogoutState = directLogoutState,
eventSink = ::handleEvent, eventSink = ::handleEvent,
@@ -10,8 +10,10 @@ package io.element.android.features.ftue.impl.sessionverification.choosemode
import io.element.android.features.logout.api.direct.DirectLogoutState import io.element.android.features.logout.api.direct.DirectLogoutState
import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.architecture.AsyncData
import io.element.android.libraries.matrix.api.user.MatrixUser
data class ChooseSelfVerificationModeState( data class ChooseSelfVerificationModeState(
val currentUser: MatrixUser,
val buttonsState: AsyncData<ButtonsState>, val buttonsState: AsyncData<ButtonsState>,
val directLogoutState: DirectLogoutState, val directLogoutState: DirectLogoutState,
val eventSink: (ChooseSelfVerificationModeEvent) -> Unit, val eventSink: (ChooseSelfVerificationModeEvent) -> Unit,
@@ -11,6 +11,8 @@ package io.element.android.features.ftue.impl.sessionverification.choosemode
import androidx.compose.ui.tooling.preview.PreviewParameterProvider import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import io.element.android.features.logout.api.direct.aDirectLogoutState import io.element.android.features.logout.api.direct.aDirectLogoutState
import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.architecture.AsyncData
import io.element.android.libraries.matrix.api.user.MatrixUser
import io.element.android.libraries.matrix.ui.components.aMatrixUser
class ChooseSelfVerificationModeStatePreviewParam : class ChooseSelfVerificationModeStatePreviewParam :
PreviewParameterProvider<ChooseSelfVerificationModeState> { PreviewParameterProvider<ChooseSelfVerificationModeState> {
@@ -43,7 +45,9 @@ class ChooseSelfVerificationModeStatePreviewParam :
fun aChooseSelfVerificationModeState( fun aChooseSelfVerificationModeState(
buttonsState: AsyncData<ChooseSelfVerificationModeState.ButtonsState> = AsyncData.Success(aButtonsState()), buttonsState: AsyncData<ChooseSelfVerificationModeState.ButtonsState> = AsyncData.Success(aButtonsState()),
currentUser: MatrixUser = aMatrixUser(),
) = ChooseSelfVerificationModeState( ) = ChooseSelfVerificationModeState(
currentUser = currentUser,
buttonsState = buttonsState, buttonsState = buttonsState,
directLogoutState = aDirectLogoutState(), directLogoutState = aDirectLogoutState(),
eventSink = {}, eventSink = {},
@@ -12,6 +12,7 @@ import androidx.activity.compose.BackHandler
import androidx.activity.compose.LocalActivity import androidx.activity.compose.LocalActivity
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
@@ -39,6 +40,9 @@ import io.element.android.libraries.designsystem.theme.components.OutlinedButton
import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.designsystem.theme.components.TextButton import io.element.android.libraries.designsystem.theme.components.TextButton
import io.element.android.libraries.designsystem.theme.components.TopAppBar import io.element.android.libraries.designsystem.theme.components.TopAppBar
import io.element.android.libraries.matrix.ui.components.accountinfo.AccountInfoCard
import io.element.android.libraries.matrix.ui.components.accountinfo.AccountInfoCardMode
import io.element.android.libraries.matrix.ui.components.accountinfo.AccountInfoCardState
import io.element.android.libraries.ui.strings.CommonStrings import io.element.android.libraries.ui.strings.CommonStrings
@Composable @Composable
@@ -75,6 +79,7 @@ fun ChooseSelfVerificationModeView(
subTitle = stringResource(id = R.string.screen_identity_confirmation_subtitle) subTitle = stringResource(id = R.string.screen_identity_confirmation_subtitle)
) )
}, },
isScrollable = true,
footer = { footer = {
ChooseSelfVerificationModeButtons( ChooseSelfVerificationModeButtons(
state = state, state = state,
@@ -83,6 +88,10 @@ fun ChooseSelfVerificationModeView(
onResetKey = onResetKey, onResetKey = onResetKey,
) )
} }
) {
Column(
modifier = Modifier.fillMaxWidth(),
verticalArrangement = Arrangement.spacedBy(24.dp),
) { ) {
Row( Row(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
@@ -100,6 +109,13 @@ fun ChooseSelfVerificationModeView(
style = ElementTheme.typography.fontBodyLgMedium style = ElementTheme.typography.fontBodyLgMedium
) )
} }
AccountInfoCard(
state = AccountInfoCardState(
matrixUser = state.currentUser,
mode = AccountInfoCardMode.Simple,
),
)
}
} }
} }
@@ -14,7 +14,13 @@ import io.element.android.features.logout.api.direct.DirectLogoutState
import io.element.android.features.logout.api.direct.aDirectLogoutState import io.element.android.features.logout.api.direct.aDirectLogoutState
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.MatrixClient
import io.element.android.libraries.matrix.api.encryption.RecoveryState import io.element.android.libraries.matrix.api.encryption.RecoveryState
import io.element.android.libraries.matrix.api.user.MatrixUser
import io.element.android.libraries.matrix.test.AN_AVATAR_URL
import io.element.android.libraries.matrix.test.A_SESSION_ID
import io.element.android.libraries.matrix.test.A_USER_NAME
import io.element.android.libraries.matrix.test.FakeMatrixClient
import io.element.android.libraries.matrix.test.encryption.FakeEncryptionService import io.element.android.libraries.matrix.test.encryption.FakeEncryptionService
import io.element.android.tests.testutils.lambda.lambdaRecorder import io.element.android.tests.testutils.lambda.lambdaRecorder
import io.element.android.tests.testutils.lambda.value import io.element.android.tests.testutils.lambda.value
@@ -34,6 +40,26 @@ class ChooseSessionVerificationModePresenterTest {
} }
} }
@Test
fun `present - current user is exposed`() = runTest {
val presenter = createPresenter(
matrixClient = FakeMatrixClient(
sessionId = A_SESSION_ID,
userDisplayName = A_USER_NAME,
userAvatarUrl = AN_AVATAR_URL,
),
)
presenter.test {
assertThat(awaitItem().currentUser).isEqualTo(
MatrixUser(
userId = A_SESSION_ID,
displayName = A_USER_NAME,
avatarUrl = AN_AVATAR_URL,
)
)
}
}
@Test @Test
fun `present - state is relayed from EncryptionService, order 1`() = runTest { fun `present - state is relayed from EncryptionService, order 1`() = runTest {
val encryptionService = FakeEncryptionService() val encryptionService = FakeEncryptionService()
@@ -126,9 +152,11 @@ class ChooseSessionVerificationModePresenterTest {
} }
private fun createPresenter( private fun createPresenter(
matrixClient: MatrixClient = FakeMatrixClient(),
encryptionService: FakeEncryptionService = FakeEncryptionService(), encryptionService: FakeEncryptionService = FakeEncryptionService(),
directLogoutPresenter: Presenter<DirectLogoutState> = Presenter<DirectLogoutState> { aDirectLogoutState() } directLogoutPresenter: Presenter<DirectLogoutState> = Presenter<DirectLogoutState> { aDirectLogoutState() }
) = ChooseSelfVerificationModePresenter( ) = ChooseSelfVerificationModePresenter(
matrixClient = matrixClient,
encryptionService = encryptionService, encryptionService = encryptionService,
directLogoutPresenter = directLogoutPresenter, directLogoutPresenter = directLogoutPresenter,
) )