From 98cfddce3f47a68b46458c2a44255db1aeb2f8a3 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 15 May 2025 14:08:05 +0200 Subject: [PATCH] Check homeserver when login using qr code (#4708) * Login with Qr code: check homeserver validity * QrCode login, unauthorized homeserver: update copy. * Update screenshots * Add unit test on SdkQrCodeLoginData * Remove default param value. * Remember imageAnalysis --------- Co-authored-by: ElementBot --- .../AccountProviderProvider.kt | 6 +- .../changeserver/ChangeServerPresenter.kt | 5 +- .../changeserver/ChangeServerStateProvider.kt | 10 +++- .../impl/changeserver/ChangeServerView.kt | 2 +- .../UnauthorizedAccountProviderException.kt | 5 +- .../login/impl/error/ChangeServerError.kt | 9 ++- .../qrcode/scan/QrCodeScanPresenter.kt | 13 +++- .../qrcode/scan/QrCodeScanStateProvider.kt | 10 ++++ .../screens/qrcode/scan/QrCodeScanView.kt | 13 ++++ .../impl/src/main/res/values/localazy.xml | 2 + .../changeserver/ChangeServerPresenterTest.kt | 8 ++- .../qrcode/scan/QrCodeScanPresenterTest.kt | 59 +++++++++++++++++-- .../api/auth/qrlogin/MatrixQrCodeLoginData.kt | 4 +- .../impl/auth/qrlogin/SdkQrCodeLoginData.kt | 6 +- .../auth/qrlogin/SdkQrCodeLoginDataTest.kt | 35 +++++++++++ .../impl/fixtures/fakes/FakeQrCodeData.kt | 20 +++++++ .../FakeMatrixQrCodeLoginDataFactory.kt | 7 ++- .../libraries/qrcode/QrCodeCameraView.kt | 10 ++-- ...changeserver_ChangeServerView_Day_3_en.png | 4 +- ...angeserver_ChangeServerView_Night_3_en.png | 4 +- ...ns.qrcode.scan_QrCodeScanView_Day_4_en.png | 3 + ....qrcode.scan_QrCodeScanView_Night_4_en.png | 3 + 22 files changed, 208 insertions(+), 30 deletions(-) create mode 100644 libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/auth/qrlogin/SdkQrCodeLoginDataTest.kt create mode 100644 libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeQrCodeData.kt create mode 100644 tests/uitests/src/test/snapshots/images/features.login.impl.screens.qrcode.scan_QrCodeScanView_Day_4_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.login.impl.screens.qrcode.scan_QrCodeScanView_Night_4_en.png diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderProvider.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderProvider.kt index ddc635b5df..75a6127d9f 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderProvider.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderProvider.kt @@ -21,8 +21,10 @@ open class AccountProviderProvider : PreviewParameterProvider { ) } -fun anAccountProvider() = AccountProvider( - url = AuthenticationConfig.MATRIX_ORG_URL, +fun anAccountProvider( + url: String = AuthenticationConfig.MATRIX_ORG_URL, +) = AccountProvider( + url = url, subtitle = "Matrix.org is an open network for secure, decentralized communication.", isPublic = true, isMatrixOrg = true, diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerPresenter.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerPresenter.kt index 497bf96271..94510455bc 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerPresenter.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerPresenter.kt @@ -56,7 +56,10 @@ class ChangeServerPresenter @Inject constructor( ) = launch { suspend { if (enterpriseService.isAllowedToConnectToHomeserver(data.url).not()) { - throw UnauthorizedAccountProviderException(data) + throw UnauthorizedAccountProviderException( + unauthorisedAccountProviderTitle = data.title, + authorisedAccountProviderTitles = listOfNotNull(enterpriseService.defaultHomeserver()) + ) } authenticationService.setHomeserver(data.url).map { authenticationService.getHomeserverDetails().value!! diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerStateProvider.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerStateProvider.kt index ae9ef58252..2549109488 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerStateProvider.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerStateProvider.kt @@ -8,7 +8,6 @@ package io.element.android.features.login.impl.changeserver import androidx.compose.ui.tooling.preview.PreviewParameterProvider -import io.element.android.features.login.impl.accountprovider.anAccountProvider import io.element.android.features.login.impl.error.ChangeServerError import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.ui.strings.CommonStrings @@ -19,7 +18,14 @@ open class ChangeServerStateProvider : PreviewParameterProvider, ) : Exception() diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/error/ChangeServerError.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/error/ChangeServerError.kt index 69c0121c15..e3fdfc2d04 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/error/ChangeServerError.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/error/ChangeServerError.kt @@ -11,7 +11,6 @@ import androidx.annotation.StringRes import androidx.compose.runtime.Composable import androidx.compose.ui.res.stringResource import io.element.android.features.login.impl.R -import io.element.android.features.login.impl.accountprovider.AccountProvider import io.element.android.features.login.impl.changeserver.UnauthorizedAccountProviderException import io.element.android.libraries.matrix.api.auth.AuthenticationException import io.element.android.libraries.ui.strings.CommonStrings @@ -26,7 +25,8 @@ sealed class ChangeServerError : Throwable() { } data class UnauthorizedAccountProvider( - val accountProvider: AccountProvider, + val unauthorisedAccountProviderTitle: String, + val authorisedAccountProviderTitles: List, ) : ChangeServerError() data object SlidingSyncAlert : ChangeServerError() @@ -35,7 +35,10 @@ sealed class ChangeServerError : Throwable() { fun from(error: Throwable): ChangeServerError = when (error) { is AuthenticationException.SlidingSyncVersion -> SlidingSyncAlert is AuthenticationException.Oidc -> Error(messageStr = error.message) - is UnauthorizedAccountProviderException -> UnauthorizedAccountProvider(error.accountProvider) + is UnauthorizedAccountProviderException -> UnauthorizedAccountProvider( + unauthorisedAccountProviderTitle = error.unauthorisedAccountProviderTitle, + authorisedAccountProviderTitles = error.authorisedAccountProviderTitles, + ) else -> Error(messageId = R.string.screen_change_server_error_invalid_homeserver) } } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/scan/QrCodeScanPresenter.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/scan/QrCodeScanPresenter.kt index 692d920958..5d0f7c41b8 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/scan/QrCodeScanPresenter.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/scan/QrCodeScanPresenter.kt @@ -15,6 +15,8 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue +import io.element.android.features.enterprise.api.EnterpriseService +import io.element.android.features.login.impl.changeserver.UnauthorizedAccountProviderException import io.element.android.features.login.impl.qrcode.QrCodeLoginManager import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.Presenter @@ -36,6 +38,7 @@ class QrCodeScanPresenter @Inject constructor( private val qrCodeLoginDataFactory: MatrixQrCodeLoginDataFactory, private val qrCodeLoginManager: QrCodeLoginManager, private val coroutineDispatchers: CoroutineDispatchers, + private val enterpriseService: EnterpriseService, ) : Presenter { private var isScanning by mutableStateOf(true) @@ -90,9 +93,17 @@ class QrCodeScanPresenter @Inject constructor( launch(coroutineDispatchers.computation) { suspend { - qrCodeLoginDataFactory.parseQrCodeData(code).onFailure { + val data = qrCodeLoginDataFactory.parseQrCodeData(code).onFailure { Timber.e(it, "Error parsing QR code data") }.getOrThrow() + val serverName = data.serverName() + if (serverName != null && enterpriseService.isAllowedToConnectToHomeserver(serverName).not()) { + throw UnauthorizedAccountProviderException( + unauthorisedAccountProviderTitle = serverName, + authorisedAccountProviderTitles = listOfNotNull(enterpriseService.defaultHomeserver()) + ) + } + data }.runCatchingUpdatingState(codeScannedAction) }.invokeOnCompletion { isProcessingCode.set(false) diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/scan/QrCodeScanStateProvider.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/scan/QrCodeScanStateProvider.kt index e9d32c0699..cdea9f8b41 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/scan/QrCodeScanStateProvider.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/scan/QrCodeScanStateProvider.kt @@ -8,6 +8,7 @@ package io.element.android.features.login.impl.screens.qrcode.scan import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import io.element.android.features.login.impl.changeserver.UnauthorizedAccountProviderException import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.matrix.api.auth.qrlogin.MatrixQrCodeLoginData import io.element.android.libraries.matrix.api.auth.qrlogin.QrLoginException @@ -19,6 +20,15 @@ open class QrCodeScanStateProvider : PreviewParameterProvider { aQrCodeScanState(isScanning = false, authenticationAction = AsyncAction.Loading), aQrCodeScanState(isScanning = false, authenticationAction = AsyncAction.Failure(Exception("Error"))), aQrCodeScanState(isScanning = false, authenticationAction = AsyncAction.Failure(QrLoginException.OtherDeviceNotSignedIn)), + aQrCodeScanState( + isScanning = false, + authenticationAction = AsyncAction.Failure( + UnauthorizedAccountProviderException( + unauthorisedAccountProviderTitle = "example.com", + authorisedAccountProviderTitles = listOf("element.io", "element.org"), + ) + ) + ), // Add other state here ) } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/scan/QrCodeScanView.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/scan/QrCodeScanView.kt index 2727653ca5..0c5598c528 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/scan/QrCodeScanView.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/scan/QrCodeScanView.kt @@ -35,6 +35,7 @@ import androidx.compose.ui.unit.dp import io.element.android.compound.theme.ElementTheme import io.element.android.compound.tokens.generated.CompoundIcons import io.element.android.features.login.impl.R +import io.element.android.features.login.impl.changeserver.UnauthorizedAccountProviderException import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.designsystem.atomic.pages.FlowStepPage import io.element.android.libraries.designsystem.components.BigIcon @@ -144,6 +145,12 @@ private fun ColumnScope.Buttons( Spacer(modifier = Modifier.width(4.dp)) Text( text = when (error) { + is UnauthorizedAccountProviderException -> { + stringResource( + id = R.string.screen_change_server_error_unauthorized_homeserver_title, + error.unauthorisedAccountProviderTitle, + ) + } is QrLoginException.OtherDeviceNotSignedIn -> { stringResource(R.string.screen_qr_code_login_device_not_signed_in_scan_state_subtitle) } @@ -156,6 +163,12 @@ private fun ColumnScope.Buttons( } Text( text = when (error) { + is UnauthorizedAccountProviderException -> { + stringResource( + id = R.string.screen_change_server_error_unauthorized_homeserver_content, + error.authorisedAccountProviderTitles.joinToString(), + ) + } is QrLoginException.OtherDeviceNotSignedIn -> { stringResource(R.string.screen_qr_code_login_device_not_signed_in_scan_state_description) } diff --git a/features/login/impl/src/main/res/values/localazy.xml b/features/login/impl/src/main/res/values/localazy.xml index d257ac1b11..bebbc8e6c3 100644 --- a/features/login/impl/src/main/res/values/localazy.xml +++ b/features/login/impl/src/main/res/values/localazy.xml @@ -18,6 +18,8 @@ %1$s" "The selected account provider does not support sliding sync. An upgrade to the server is needed to use %1$s." "%1$s is not allowed to connect to %2$s." + "This app has been configured to allow: %1$s." + "Account provider %1$s not allowed." "Homeserver URL" "Enter a domain address." "What is the address of your server?" diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerPresenterTest.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerPresenterTest.kt index 20b70028d9..5d4e3bae5f 100644 --- a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerPresenterTest.kt +++ b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerPresenterTest.kt @@ -84,6 +84,7 @@ class ChangeServerPresenterTest { createPresenter( enterpriseService = FakeEnterpriseService( isAllowedToConnectToHomeserverResult = isAllowedToConnectToHomeserverResult, + defaultHomeserverResult = { "element.io" }, ), ).test { val initialState = awaitItem() @@ -94,8 +95,11 @@ class ChangeServerPresenterTest { assertThat(loadingState.changeServerAction).isInstanceOf(AsyncData.Loading::class.java) val failureState = awaitItem() assertThat( - (failureState.changeServerAction.errorOrNull() as ChangeServerError.UnauthorizedAccountProvider).accountProvider - ).isEqualTo(anAccountProvider) + (failureState.changeServerAction.errorOrNull() as ChangeServerError.UnauthorizedAccountProvider).unauthorisedAccountProviderTitle + ).isEqualTo(anAccountProvider.title) + assertThat( + (failureState.changeServerAction.errorOrNull() as ChangeServerError.UnauthorizedAccountProvider).authorisedAccountProviderTitles + ).containsExactly("element.io") isAllowedToConnectToHomeserverResult.assertions() .isCalledOnce() .with(value(A_HOMESERVER_URL)) diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/qrcode/scan/QrCodeScanPresenterTest.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/qrcode/scan/QrCodeScanPresenterTest.kt index 294f9c48c2..18a6678388 100644 --- a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/qrcode/scan/QrCodeScanPresenterTest.kt +++ b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/qrcode/scan/QrCodeScanPresenterTest.kt @@ -11,12 +11,17 @@ import app.cash.molecule.RecompositionMode import app.cash.molecule.moleculeFlow import app.cash.turbine.test import com.google.common.truth.Truth.assertThat +import io.element.android.features.enterprise.api.EnterpriseService +import io.element.android.features.enterprise.test.FakeEnterpriseService +import io.element.android.features.login.impl.changeserver.UnauthorizedAccountProviderException import io.element.android.features.login.impl.qrcode.FakeQrCodeLoginManager import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.matrix.api.auth.qrlogin.QrCodeLoginStep import io.element.android.libraries.matrix.api.auth.qrlogin.QrLoginException +import io.element.android.libraries.matrix.test.auth.qrlogin.FakeMatrixQrCodeLoginData import io.element.android.libraries.matrix.test.auth.qrlogin.FakeMatrixQrCodeLoginDataFactory import io.element.android.tests.testutils.lambda.lambdaRecorder +import io.element.android.tests.testutils.test import io.element.android.tests.testutils.testCoroutineDispatchers import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runTest @@ -38,10 +43,22 @@ class QrCodeScanPresenterTest { @Test fun `present - scanned QR code successfully`() = runTest { - val presenter = createQrCodeScanPresenter() - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + val qrCodeLoginDataFactory = FakeMatrixQrCodeLoginDataFactory( + parseQrCodeLoginDataResult = { + Result.success( + FakeMatrixQrCodeLoginData( + serverNameResult = { "example.com" } + ) + ) + } + ) + val presenter = createQrCodeScanPresenter( + qrCodeLoginDataFactory = qrCodeLoginDataFactory, + enterpriseService = FakeEnterpriseService( + isAllowedToConnectToHomeserverResult = { true }, + ) + ) + presenter.test { val initialState = awaitItem() initialState.eventSink(QrCodeScanEvents.QrCodeScanned(byteArrayOf())) assertThat(awaitItem().isScanning).isFalse() @@ -50,6 +67,38 @@ class QrCodeScanPresenterTest { } } + @Test + fun `present - scanned QR code successfully, but homeserver not allowed`() = runTest { + val qrCodeLoginDataFactory = FakeMatrixQrCodeLoginDataFactory( + parseQrCodeLoginDataResult = { + Result.success( + FakeMatrixQrCodeLoginData( + serverNameResult = { "example.com" } + ) + ) + } + ) + val presenter = createQrCodeScanPresenter( + qrCodeLoginDataFactory = qrCodeLoginDataFactory, + enterpriseService = FakeEnterpriseService( + isAllowedToConnectToHomeserverResult = { false }, + defaultHomeserverResult = { "element.io" } + ) + ) + presenter.test { + val initialState = awaitItem() + initialState.eventSink(QrCodeScanEvents.QrCodeScanned(byteArrayOf())) + assertThat(awaitItem().isScanning).isFalse() + assertThat(awaitItem().authenticationAction.isLoading()).isTrue() + awaitItem().also { state -> + assertThat((state.authenticationAction.errorOrNull() as UnauthorizedAccountProviderException).unauthorisedAccountProviderTitle) + .isEqualTo("example.com") + assertThat((state.authenticationAction.errorOrNull() as UnauthorizedAccountProviderException).authorisedAccountProviderTitles) + .containsExactly("element.io") + } + } + } + @Test fun `present - scanned QR code failed and can be retried`() = runTest { val qrCodeLoginDataFactory = FakeMatrixQrCodeLoginDataFactory( @@ -103,9 +152,11 @@ class QrCodeScanPresenterTest { qrCodeLoginDataFactory: FakeMatrixQrCodeLoginDataFactory = FakeMatrixQrCodeLoginDataFactory(), coroutineDispatchers: CoroutineDispatchers = testCoroutineDispatchers(), qrCodeLoginManager: FakeQrCodeLoginManager = FakeQrCodeLoginManager(), + enterpriseService: EnterpriseService = FakeEnterpriseService(), ) = QrCodeScanPresenter( qrCodeLoginDataFactory = qrCodeLoginDataFactory, qrCodeLoginManager = qrCodeLoginManager, coroutineDispatchers = coroutineDispatchers, + enterpriseService = enterpriseService, ) } diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/qrlogin/MatrixQrCodeLoginData.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/qrlogin/MatrixQrCodeLoginData.kt index 7a01b8f586..48532e44b9 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/qrlogin/MatrixQrCodeLoginData.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/qrlogin/MatrixQrCodeLoginData.kt @@ -7,4 +7,6 @@ package io.element.android.libraries.matrix.api.auth.qrlogin -interface MatrixQrCodeLoginData +interface MatrixQrCodeLoginData { + fun serverName(): String? +} diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/qrlogin/SdkQrCodeLoginData.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/qrlogin/SdkQrCodeLoginData.kt index 04a338f17e..485e9bfaec 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/qrlogin/SdkQrCodeLoginData.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/qrlogin/SdkQrCodeLoginData.kt @@ -12,4 +12,8 @@ import org.matrix.rustcomponents.sdk.QrCodeData as RustQrCodeData class SdkQrCodeLoginData( internal val rustQrCodeData: RustQrCodeData, -) : MatrixQrCodeLoginData +) : MatrixQrCodeLoginData { + override fun serverName(): String? { + return rustQrCodeData.serverName() + } +} diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/auth/qrlogin/SdkQrCodeLoginDataTest.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/auth/qrlogin/SdkQrCodeLoginDataTest.kt new file mode 100644 index 0000000000..c63a1bc2b1 --- /dev/null +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/auth/qrlogin/SdkQrCodeLoginDataTest.kt @@ -0,0 +1,35 @@ +/* + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.matrix.impl.auth.qrlogin + +import com.google.common.truth.Truth.assertThat +import io.element.android.libraries.matrix.impl.fixtures.fakes.FakeQrCodeData +import io.element.android.libraries.matrix.test.A_HOMESERVER_URL +import org.junit.Test + +class SdkQrCodeLoginDataTest { + @Test + fun `getServer reads the value from the Rust side, null case`() { + val sut = SdkQrCodeLoginData( + rustQrCodeData = FakeQrCodeData( + serverNameResult = { null }, + ), + ) + assertThat(sut.serverName()).isNull() + } + + @Test + fun `getServer reads the value from the Rust side`() { + val sut = SdkQrCodeLoginData( + rustQrCodeData = FakeQrCodeData( + serverNameResult = { A_HOMESERVER_URL }, + ), + ) + assertThat(sut.serverName()).isEqualTo(A_HOMESERVER_URL) + } +} diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeQrCodeData.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeQrCodeData.kt new file mode 100644 index 0000000000..7033d1de9a --- /dev/null +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeQrCodeData.kt @@ -0,0 +1,20 @@ +/* + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.matrix.impl.fixtures.fakes + +import io.element.android.tests.testutils.lambda.lambdaError +import org.matrix.rustcomponents.sdk.NoPointer +import org.matrix.rustcomponents.sdk.QrCodeData + +class FakeQrCodeData( + private val serverNameResult: () -> String? = { lambdaError() }, +) : QrCodeData(NoPointer) { + override fun serverName(): String? { + return serverNameResult() + } +} diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/auth/qrlogin/FakeMatrixQrCodeLoginDataFactory.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/auth/qrlogin/FakeMatrixQrCodeLoginDataFactory.kt index bb12c0120d..344b567589 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/auth/qrlogin/FakeMatrixQrCodeLoginDataFactory.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/auth/qrlogin/FakeMatrixQrCodeLoginDataFactory.kt @@ -9,6 +9,7 @@ package io.element.android.libraries.matrix.test.auth.qrlogin import io.element.android.libraries.matrix.api.auth.qrlogin.MatrixQrCodeLoginData import io.element.android.libraries.matrix.api.auth.qrlogin.MatrixQrCodeLoginDataFactory +import io.element.android.tests.testutils.lambda.lambdaError import io.element.android.tests.testutils.lambda.lambdaRecorder class FakeMatrixQrCodeLoginDataFactory( @@ -20,4 +21,8 @@ class FakeMatrixQrCodeLoginDataFactory( } } -class FakeMatrixQrCodeLoginData : MatrixQrCodeLoginData +class FakeMatrixQrCodeLoginData( + private val serverNameResult: () -> String? = { lambdaError() }, +) : MatrixQrCodeLoginData { + override fun serverName() = serverNameResult() +} diff --git a/libraries/qrcode/src/main/kotlin/io/element/android/libraries/qrcode/QrCodeCameraView.kt b/libraries/qrcode/src/main/kotlin/io/element/android/libraries/qrcode/QrCodeCameraView.kt index d59de4da9b..0087a8a9bb 100644 --- a/libraries/qrcode/src/main/kotlin/io/element/android/libraries/qrcode/QrCodeCameraView.kt +++ b/libraries/qrcode/src/main/kotlin/io/element/android/libraries/qrcode/QrCodeCameraView.kt @@ -44,8 +44,8 @@ import kotlin.coroutines.suspendCoroutine @Composable fun QrCodeCameraView( onScanQrCode: (ByteArray) -> Unit, + renderPreview: Boolean, modifier: Modifier = Modifier, - renderPreview: Boolean = true, ) { if (LocalInspectionMode.current) { Box( @@ -62,9 +62,11 @@ fun QrCodeCameraView( var cameraProvider by remember { mutableStateOf(null) } val previewUseCase = remember { Preview.Builder().build() } var lastFrame by remember { mutableStateOf(null) } - val imageAnalysis = ImageAnalysis.Builder() - .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST) - .build() + val imageAnalysis = remember { + ImageAnalysis.Builder() + .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST) + .build() + } LaunchedEffect(Unit) { cameraProvider = localContext.getCameraProvider() diff --git a/tests/uitests/src/test/snapshots/images/features.login.impl.changeserver_ChangeServerView_Day_3_en.png b/tests/uitests/src/test/snapshots/images/features.login.impl.changeserver_ChangeServerView_Day_3_en.png index 2e5917d7de..d65b4da938 100644 --- a/tests/uitests/src/test/snapshots/images/features.login.impl.changeserver_ChangeServerView_Day_3_en.png +++ b/tests/uitests/src/test/snapshots/images/features.login.impl.changeserver_ChangeServerView_Day_3_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6c1257fbdf388933be8fb2ae211c8abacc4cdb01c2a924a7ba3729337c9b6707 -size 15434 +oid sha256:a1e37b08acfdf40279b0990dd65c58d38d2248026f0d2fe2a1c396245ea931fd +size 15892 diff --git a/tests/uitests/src/test/snapshots/images/features.login.impl.changeserver_ChangeServerView_Night_3_en.png b/tests/uitests/src/test/snapshots/images/features.login.impl.changeserver_ChangeServerView_Night_3_en.png index 8a1571970c..aac7b5148e 100644 --- a/tests/uitests/src/test/snapshots/images/features.login.impl.changeserver_ChangeServerView_Night_3_en.png +++ b/tests/uitests/src/test/snapshots/images/features.login.impl.changeserver_ChangeServerView_Night_3_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cb9a2d2145791122cc2ab7d903485de361fb6098216bf578dca7cc350e9f94a0 -size 13650 +oid sha256:8db77f38d81113a5411c40c1ebf811090ce809fd0f0df3d29eab05d3ed0bb64a +size 14114 diff --git a/tests/uitests/src/test/snapshots/images/features.login.impl.screens.qrcode.scan_QrCodeScanView_Day_4_en.png b/tests/uitests/src/test/snapshots/images/features.login.impl.screens.qrcode.scan_QrCodeScanView_Day_4_en.png new file mode 100644 index 0000000000..60ec9cc17b --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.login.impl.screens.qrcode.scan_QrCodeScanView_Day_4_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5f90a8ecae26714e76d5536682716a15aefc3d4c8836617add9a0ea946e7d242 +size 31486 diff --git a/tests/uitests/src/test/snapshots/images/features.login.impl.screens.qrcode.scan_QrCodeScanView_Night_4_en.png b/tests/uitests/src/test/snapshots/images/features.login.impl.screens.qrcode.scan_QrCodeScanView_Night_4_en.png new file mode 100644 index 0000000000..0581885f19 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.login.impl.screens.qrcode.scan_QrCodeScanView_Night_4_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:34cd9f4558efc143b4ab3bec96342a42cfad6906da0d46e75d78db36b7e59bd6 +size 30242