Show account info on the identity confirmation screen

Render an AccountInfoCard with the current user on the "Confirm your digital identity" screen,
so the user knows which account needs to be verified.

Closes #7898

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Benoit Marty
2026-10-12 09:56:57 +02:00
parent f12b9c77d5
commit 87e1deaccf
6 changed files with 67 additions and 12 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,
) )