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.architecture)
implementation(projects.libraries.matrix.api)
implementation(projects.libraries.matrixui)
implementation(projects.libraries.designsystem)
implementation(projects.libraries.preferences.api)
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.Presenter
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.RecoveryState
@Inject
class ChooseSelfVerificationModePresenter(
private val matrixClient: MatrixClient,
private val encryptionService: EncryptionService,
private val directLogoutPresenter: Presenter<DirectLogoutState>,
) : Presenter<ChooseSelfVerificationModeState> {
@Composable
override fun present(): ChooseSelfVerificationModeState {
val currentUser by matrixClient.userProfile.collectAsState()
val hasDevicesToVerifyAgainst by encryptionService.hasDevicesToVerifyAgainst.collectAsState()
val canUseRecoveryKey by produceState<AsyncData<Boolean>>(AsyncData.Uninitialized) {
encryptionService.recoveryStateStateFlow
@@ -72,6 +75,7 @@ class ChooseSelfVerificationModePresenter(
}
return ChooseSelfVerificationModeState(
currentUser = currentUser,
buttonsState = buttonsState,
directLogoutState = directLogoutState,
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.libraries.architecture.AsyncData
import io.element.android.libraries.matrix.api.user.MatrixUser
data class ChooseSelfVerificationModeState(
val currentUser: MatrixUser,
val buttonsState: AsyncData<ButtonsState>,
val directLogoutState: DirectLogoutState,
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 io.element.android.features.logout.api.direct.aDirectLogoutState
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 :
PreviewParameterProvider<ChooseSelfVerificationModeState> {
@@ -43,7 +45,9 @@ class ChooseSelfVerificationModeStatePreviewParam :
fun aChooseSelfVerificationModeState(
buttonsState: AsyncData<ChooseSelfVerificationModeState.ButtonsState> = AsyncData.Success(aButtonsState()),
currentUser: MatrixUser = aMatrixUser(),
) = ChooseSelfVerificationModeState(
currentUser = currentUser,
buttonsState = buttonsState,
directLogoutState = aDirectLogoutState(),
eventSink = {},
@@ -12,6 +12,7 @@ import androidx.activity.compose.BackHandler
import androidx.activity.compose.LocalActivity
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
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.TextButton
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
@Composable
@@ -75,6 +79,7 @@ fun ChooseSelfVerificationModeView(
subTitle = stringResource(id = R.string.screen_identity_confirmation_subtitle)
)
},
isScrollable = true,
footer = {
ChooseSelfVerificationModeButtons(
state = state,
@@ -84,20 +89,31 @@ fun ChooseSelfVerificationModeView(
)
}
) {
Row(
Column(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.Center,
verticalArrangement = Arrangement.spacedBy(24.dp),
) {
Text(
modifier = Modifier
.clickable(onClick = onLearnMore)
.padding(vertical = 4.dp, horizontal = 16.dp)
.semantics {
// Note: there is no Role.Link, so we use Role.Button for better accessibility support
role = Role.Button
},
text = stringResource(CommonStrings.action_learn_more),
style = ElementTheme.typography.fontBodyLgMedium
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.Center,
) {
Text(
modifier = Modifier
.clickable(onClick = onLearnMore)
.padding(vertical = 4.dp, horizontal = 16.dp)
.semantics {
// Note: there is no Role.Link, so we use Role.Button for better accessibility support
role = Role.Button
},
text = stringResource(CommonStrings.action_learn_more),
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.libraries.architecture.AsyncData
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.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.tests.testutils.lambda.lambdaRecorder
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
fun `present - state is relayed from EncryptionService, order 1`() = runTest {
val encryptionService = FakeEncryptionService()
@@ -126,9 +152,11 @@ class ChooseSessionVerificationModePresenterTest {
}
private fun createPresenter(
matrixClient: MatrixClient = FakeMatrixClient(),
encryptionService: FakeEncryptionService = FakeEncryptionService(),
directLogoutPresenter: Presenter<DirectLogoutState> = Presenter<DirectLogoutState> { aDirectLogoutState() }
) = ChooseSelfVerificationModePresenter(
matrixClient = matrixClient,
encryptionService = encryptionService,
directLogoutPresenter = directLogoutPresenter,
)