From e6750dd30155148e94d08d6fc083569e1075a87a Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 28 Sep 2026 17:23:59 +0200 Subject: [PATCH] Show the account receiving the device verification request Replace SessionDetailsView by AccountInfoCard with the DeviceVerification mode on the incoming device verification screen, so that the account concerned by the request is clearly indicated, with its avatar and Matrix ID. The current user is built from the SessionStore, including the locally stored avatar thumbnail, used as a fallback when the avatar cannot be loaded, for instance for an account which is not the current one. Co-Authored-By: Claude Opus 5.5 (1M context) --- features/verifysession/impl/build.gradle.kts | 2 + .../incoming/IncomingVerificationPresenter.kt | 24 ++++ .../incoming/IncomingVerificationState.kt | 2 + .../IncomingVerificationStatePreviewParam.kt | 6 + .../impl/incoming/IncomingVerificationView.kt | 17 ++- .../impl/incoming/ui/SessionDetailsView.kt | 103 ------------------ .../IncomingVerificationPresenterTest.kt | 46 ++++++++ .../components/accountinfo/AccountInfoCard.kt | 3 +- .../accountinfo/AccountInfoCardState.kt | 1 - .../src/main/res/values/localazy.xml | 1 + 10 files changed, 94 insertions(+), 111 deletions(-) delete mode 100644 features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/incoming/ui/SessionDetailsView.kt diff --git a/features/verifysession/impl/build.gradle.kts b/features/verifysession/impl/build.gradle.kts index 89a87edf19..fc51d87acd 100644 --- a/features/verifysession/impl/build.gradle.kts +++ b/features/verifysession/impl/build.gradle.kts @@ -34,6 +34,7 @@ dependencies { implementation(projects.libraries.matrixui) implementation(projects.libraries.designsystem) implementation(projects.libraries.preferences.api) + implementation(projects.libraries.sessionStorage.api) implementation(projects.libraries.uiStrings) implementation(projects.features.logout.api) api(libs.statemachine) @@ -44,4 +45,5 @@ dependencies { testImplementation(projects.libraries.dateformatter.test) testImplementation(projects.libraries.matrix.test) testImplementation(projects.libraries.preferences.test) + testImplementation(projects.libraries.sessionStorage.test) } diff --git a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/incoming/IncomingVerificationPresenter.kt b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/incoming/IncomingVerificationPresenter.kt index 4cde3f927f..00daf9b5b8 100644 --- a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/incoming/IncomingVerificationPresenter.kt +++ b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/incoming/IncomingVerificationPresenter.kt @@ -13,6 +13,7 @@ package io.element.android.features.verifysession.impl.incoming import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.collectAsState import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.remember @@ -26,13 +27,17 @@ import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.dateformatter.api.DateFormatter import io.element.android.libraries.dateformatter.api.DateFormatterMode import io.element.android.libraries.di.annotations.SessionCoroutineScope +import io.element.android.libraries.matrix.api.core.SessionId +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.SessionVerificationService import io.element.android.libraries.matrix.api.verification.VerificationFlowState import io.element.android.libraries.matrix.api.verification.VerificationRequest +import io.element.android.libraries.sessionstorage.api.SessionStore import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.mapNotNull import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import timber.log.Timber @@ -45,6 +50,8 @@ class IncomingVerificationPresenter( @Assisted private val navigator: IncomingVerificationNavigator, @SessionCoroutineScope private val sessionCoroutineScope: CoroutineScope, private val sessionVerificationService: SessionVerificationService, + private val sessionId: SessionId, + private val sessionStore: SessionStore, private val stateMachine: IncomingVerificationStateMachine, private val dateFormatter: DateFormatter, ) : Presenter { @@ -92,9 +99,24 @@ class IncomingVerificationPresenter( mode = DateFormatterMode.TimeOrDate, ) } + val currentUser by remember { + sessionStore.sessionsFlow().mapNotNull { sessions -> + sessions.find { it.userId == sessionId.value }?.let { sessionData -> + MatrixUser( + userId = sessionId, + displayName = sessionData.userDisplayName, + avatarUrl = sessionData.userAvatarUrl, + // Locally stored avatar, used as a fallback when the avatar cannot be loaded, + // for instance for a session which is not the current one + avatarThumbnail = sessionData.userAvatarData, + ) + } + } + }.collectAsState(MatrixUser(sessionId)) val step by remember { derivedStateOf { stateAndDispatch.state.value.toVerificationStep( + currentUser = currentUser, sessionVerificationRequestDetails = verificationRequest.details, formattedSignInTime = formattedSignInTime, ) @@ -147,6 +169,7 @@ class IncomingVerificationPresenter( } private fun StateMachineState?.toVerificationStep( + currentUser: MatrixUser, sessionVerificationRequestDetails: SessionVerificationRequestDetails, formattedSignInTime: String, ): Step = @@ -156,6 +179,7 @@ class IncomingVerificationPresenter( StateMachineState.RejectingIncomingVerification, null -> { Step.Initial( + currentUser = currentUser, deviceDisplayName = sessionVerificationRequestDetails.deviceDisplayName, deviceId = sessionVerificationRequestDetails.deviceId, formattedSignInTime = formattedSignInTime, diff --git a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/incoming/IncomingVerificationState.kt b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/incoming/IncomingVerificationState.kt index e3663800b2..bd4e343c49 100644 --- a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/incoming/IncomingVerificationState.kt +++ b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/incoming/IncomingVerificationState.kt @@ -10,6 +10,7 @@ package io.element.android.features.verifysession.impl.incoming import androidx.compose.runtime.Stable import io.element.android.libraries.matrix.api.core.DeviceId +import io.element.android.libraries.matrix.api.user.MatrixUser import io.element.android.libraries.matrix.api.verification.SessionVerificationData import io.element.android.libraries.matrix.api.verification.VerificationRequest @@ -21,6 +22,7 @@ data class IncomingVerificationState( @Stable sealed interface Step { data class Initial( + val currentUser: MatrixUser, val deviceDisplayName: String?, val deviceId: DeviceId, val formattedSignInTime: String, diff --git a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/incoming/IncomingVerificationStatePreviewParam.kt b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/incoming/IncomingVerificationStatePreviewParam.kt index 68672be025..bdf3ad2196 100644 --- a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/incoming/IncomingVerificationStatePreviewParam.kt +++ b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/incoming/IncomingVerificationStatePreviewParam.kt @@ -13,6 +13,7 @@ import io.element.android.features.verifysession.impl.incoming.IncomingVerificat import io.element.android.features.verifysession.impl.ui.aDecimalsSessionVerificationData import io.element.android.features.verifysession.impl.ui.aEmojisSessionVerificationData import io.element.android.libraries.designsystem.preview.USER_NAME_ALICE +import io.element.android.libraries.designsystem.preview.USER_NAME_BOB import io.element.android.libraries.matrix.api.core.DeviceId import io.element.android.libraries.matrix.api.core.FlowId import io.element.android.libraries.matrix.api.core.UserId @@ -50,6 +51,11 @@ open class IncomingVerificationStatePreviewParam : PreviewParameterProvider { _ -> } @@ -59,6 +98,7 @@ class IncomingVerificationPresenterTest { val initialState = awaitItem() assertThat(initialState.step).isEqualTo( IncomingVerificationState.Step.Initial( + currentUser = MatrixUser(A_SESSION_ID), deviceDisplayName = "a device name", deviceId = A_DEVICE_ID, formattedSignInTime = "567 TimeOrDate false", @@ -128,6 +168,7 @@ class IncomingVerificationPresenterTest { val initialState = awaitItem() assertThat(initialState.step).isEqualTo( IncomingVerificationState.Step.Initial( + currentUser = MatrixUser(A_SESSION_ID), deviceDisplayName = "a device name", deviceId = A_DEVICE_ID, formattedSignInTime = "567 TimeOrDate false", @@ -193,6 +234,7 @@ class IncomingVerificationPresenterTest { val initialState = awaitItem() assertThat(initialState.step).isEqualTo( IncomingVerificationState.Step.Initial( + currentUser = MatrixUser(A_SESSION_ID), deviceDisplayName = "a device name", deviceId = A_DEVICE_ID, formattedSignInTime = "567 TimeOrDate false", @@ -228,6 +270,7 @@ class IncomingVerificationPresenterTest { val initialState = awaitItem() assertThat(initialState.step).isEqualTo( IncomingVerificationState.Step.Initial( + currentUser = MatrixUser(A_SESSION_ID), deviceDisplayName = "a device name", deviceId = A_DEVICE_ID, formattedSignInTime = "567 TimeOrDate false", @@ -315,10 +358,13 @@ internal fun TestScope.createPresenter( navigator: IncomingVerificationNavigator = IncomingVerificationNavigator { lambdaError() }, service: SessionVerificationService = FakeSessionVerificationService(), dateFormatter: DateFormatter = FakeDateFormatter(), + sessionStore: SessionStore = InMemorySessionStore(), ) = IncomingVerificationPresenter( verificationRequest = verificationRequest, navigator = navigator, sessionVerificationService = service, + sessionId = A_SESSION_ID, + sessionStore = sessionStore, stateMachine = IncomingVerificationStateMachine(service), dateFormatter = dateFormatter, sessionCoroutineScope = backgroundScope, diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/accountinfo/AccountInfoCard.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/accountinfo/AccountInfoCard.kt index ab0b172cc5..38de05999f 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/accountinfo/AccountInfoCard.kt +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/accountinfo/AccountInfoCard.kt @@ -127,8 +127,7 @@ private fun DeviceInfoRow( horizontalArrangement = Arrangement.spacedBy(8.dp), ) { TextWithLabelMolecule( - // TODO i18n - label = "Signed in", + label = stringResource(CommonStrings.common_signed_in), text = mode.signInFormattedTimestamp, modifier = Modifier.weight(1f), ) diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/accountinfo/AccountInfoCardState.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/accountinfo/AccountInfoCardState.kt index 68b66d72ee..7077fa7c0b 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/accountinfo/AccountInfoCardState.kt +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/accountinfo/AccountInfoCardState.kt @@ -24,7 +24,6 @@ sealed interface AccountInfoCardMode { val otherUser: MatrixUser, ) : AccountInfoCardMode - // TODO Replace SessionDetailsView with this component data class DeviceVerification( val deviceName: String?, val deviceId: DeviceId, diff --git a/libraries/ui-strings/src/main/res/values/localazy.xml b/libraries/ui-strings/src/main/res/values/localazy.xml index 7a0ce8fd4e..1e568160d5 100644 --- a/libraries/ui-strings/src/main/res/values/localazy.xml +++ b/libraries/ui-strings/src/main/res/values/localazy.xml @@ -412,6 +412,7 @@ Reason: %1$s." "Shared live location" "Shared location" "Shared space" + "Signed in" "Removing device" "Something went wrong" "We encountered an issue. Please try again."