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 <android@element.io>
This commit is contained in:
Benoit Marty 2025-05-15 14:08:05 +02:00 committed by GitHub
parent cf02ac7eeb
commit 98cfddce3f
22 changed files with 208 additions and 30 deletions

View file

@ -21,8 +21,10 @@ open class AccountProviderProvider : PreviewParameterProvider<AccountProvider> {
) )
} }
fun anAccountProvider() = AccountProvider( fun anAccountProvider(
url = AuthenticationConfig.MATRIX_ORG_URL, url: String = AuthenticationConfig.MATRIX_ORG_URL,
) = AccountProvider(
url = url,
subtitle = "Matrix.org is an open network for secure, decentralized communication.", subtitle = "Matrix.org is an open network for secure, decentralized communication.",
isPublic = true, isPublic = true,
isMatrixOrg = true, isMatrixOrg = true,

View file

@ -56,7 +56,10 @@ class ChangeServerPresenter @Inject constructor(
) = launch { ) = launch {
suspend { suspend {
if (enterpriseService.isAllowedToConnectToHomeserver(data.url).not()) { if (enterpriseService.isAllowedToConnectToHomeserver(data.url).not()) {
throw UnauthorizedAccountProviderException(data) throw UnauthorizedAccountProviderException(
unauthorisedAccountProviderTitle = data.title,
authorisedAccountProviderTitles = listOfNotNull(enterpriseService.defaultHomeserver())
)
} }
authenticationService.setHomeserver(data.url).map { authenticationService.setHomeserver(data.url).map {
authenticationService.getHomeserverDetails().value!! authenticationService.getHomeserverDetails().value!!

View file

@ -8,7 +8,6 @@
package io.element.android.features.login.impl.changeserver package io.element.android.features.login.impl.changeserver
import androidx.compose.ui.tooling.preview.PreviewParameterProvider 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.features.login.impl.error.ChangeServerError
import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.architecture.AsyncData
import io.element.android.libraries.ui.strings.CommonStrings import io.element.android.libraries.ui.strings.CommonStrings
@ -19,7 +18,14 @@ open class ChangeServerStateProvider : PreviewParameterProvider<ChangeServerStat
aChangeServerState(), aChangeServerState(),
aChangeServerState(changeServerAction = AsyncData.Failure(ChangeServerError.Error(CommonStrings.error_unknown))), aChangeServerState(changeServerAction = AsyncData.Failure(ChangeServerError.Error(CommonStrings.error_unknown))),
aChangeServerState(changeServerAction = AsyncData.Failure(ChangeServerError.SlidingSyncAlert)), aChangeServerState(changeServerAction = AsyncData.Failure(ChangeServerError.SlidingSyncAlert)),
aChangeServerState(changeServerAction = AsyncData.Failure(ChangeServerError.UnauthorizedAccountProvider(anAccountProvider()))), aChangeServerState(
changeServerAction = AsyncData.Failure(
ChangeServerError.UnauthorizedAccountProvider(
unauthorisedAccountProviderTitle = "example.com",
authorisedAccountProviderTitles = listOf("element.io", "element.org"),
)
)
),
) )
} }

View file

@ -62,7 +62,7 @@ fun ChangeServerView(
content = stringResource( content = stringResource(
id = R.string.screen_change_server_error_unauthorized_homeserver, id = R.string.screen_change_server_error_unauthorized_homeserver,
LocalBuildMeta.current.applicationName, LocalBuildMeta.current.applicationName,
error.accountProvider.title, error.unauthorisedAccountProviderTitle,
), ),
onSubmit = { onSubmit = {
eventSink.invoke(ChangeServerEvents.ClearError) eventSink.invoke(ChangeServerEvents.ClearError)

View file

@ -7,8 +7,7 @@
package io.element.android.features.login.impl.changeserver package io.element.android.features.login.impl.changeserver
import io.element.android.features.login.impl.accountprovider.AccountProvider
class UnauthorizedAccountProviderException( class UnauthorizedAccountProviderException(
val accountProvider: AccountProvider, val unauthorisedAccountProviderTitle: String,
val authorisedAccountProviderTitles: List<String>,
) : Exception() ) : Exception()

View file

@ -11,7 +11,6 @@ import androidx.annotation.StringRes
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import io.element.android.features.login.impl.R 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.features.login.impl.changeserver.UnauthorizedAccountProviderException
import io.element.android.libraries.matrix.api.auth.AuthenticationException import io.element.android.libraries.matrix.api.auth.AuthenticationException
import io.element.android.libraries.ui.strings.CommonStrings import io.element.android.libraries.ui.strings.CommonStrings
@ -26,7 +25,8 @@ sealed class ChangeServerError : Throwable() {
} }
data class UnauthorizedAccountProvider( data class UnauthorizedAccountProvider(
val accountProvider: AccountProvider, val unauthorisedAccountProviderTitle: String,
val authorisedAccountProviderTitles: List<String>,
) : ChangeServerError() ) : ChangeServerError()
data object SlidingSyncAlert : ChangeServerError() data object SlidingSyncAlert : ChangeServerError()
@ -35,7 +35,10 @@ sealed class ChangeServerError : Throwable() {
fun from(error: Throwable): ChangeServerError = when (error) { fun from(error: Throwable): ChangeServerError = when (error) {
is AuthenticationException.SlidingSyncVersion -> SlidingSyncAlert is AuthenticationException.SlidingSyncVersion -> SlidingSyncAlert
is AuthenticationException.Oidc -> Error(messageStr = error.message) 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) else -> Error(messageId = R.string.screen_change_server_error_invalid_homeserver)
} }
} }

View file

@ -15,6 +15,8 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue 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.features.login.impl.qrcode.QrCodeLoginManager
import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.AsyncAction
import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.Presenter
@ -36,6 +38,7 @@ class QrCodeScanPresenter @Inject constructor(
private val qrCodeLoginDataFactory: MatrixQrCodeLoginDataFactory, private val qrCodeLoginDataFactory: MatrixQrCodeLoginDataFactory,
private val qrCodeLoginManager: QrCodeLoginManager, private val qrCodeLoginManager: QrCodeLoginManager,
private val coroutineDispatchers: CoroutineDispatchers, private val coroutineDispatchers: CoroutineDispatchers,
private val enterpriseService: EnterpriseService,
) : Presenter<QrCodeScanState> { ) : Presenter<QrCodeScanState> {
private var isScanning by mutableStateOf(true) private var isScanning by mutableStateOf(true)
@ -90,9 +93,17 @@ class QrCodeScanPresenter @Inject constructor(
launch(coroutineDispatchers.computation) { launch(coroutineDispatchers.computation) {
suspend { suspend {
qrCodeLoginDataFactory.parseQrCodeData(code).onFailure { val data = qrCodeLoginDataFactory.parseQrCodeData(code).onFailure {
Timber.e(it, "Error parsing QR code data") Timber.e(it, "Error parsing QR code data")
}.getOrThrow() }.getOrThrow()
val serverName = data.serverName()
if (serverName != null && enterpriseService.isAllowedToConnectToHomeserver(serverName).not()) {
throw UnauthorizedAccountProviderException(
unauthorisedAccountProviderTitle = serverName,
authorisedAccountProviderTitles = listOfNotNull(enterpriseService.defaultHomeserver())
)
}
data
}.runCatchingUpdatingState(codeScannedAction) }.runCatchingUpdatingState(codeScannedAction)
}.invokeOnCompletion { }.invokeOnCompletion {
isProcessingCode.set(false) isProcessingCode.set(false)

View file

@ -8,6 +8,7 @@
package io.element.android.features.login.impl.screens.qrcode.scan package io.element.android.features.login.impl.screens.qrcode.scan
import androidx.compose.ui.tooling.preview.PreviewParameterProvider 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.architecture.AsyncAction
import io.element.android.libraries.matrix.api.auth.qrlogin.MatrixQrCodeLoginData import io.element.android.libraries.matrix.api.auth.qrlogin.MatrixQrCodeLoginData
import io.element.android.libraries.matrix.api.auth.qrlogin.QrLoginException import io.element.android.libraries.matrix.api.auth.qrlogin.QrLoginException
@ -19,6 +20,15 @@ open class QrCodeScanStateProvider : PreviewParameterProvider<QrCodeScanState> {
aQrCodeScanState(isScanning = false, authenticationAction = AsyncAction.Loading), aQrCodeScanState(isScanning = false, authenticationAction = AsyncAction.Loading),
aQrCodeScanState(isScanning = false, authenticationAction = AsyncAction.Failure(Exception("Error"))), aQrCodeScanState(isScanning = false, authenticationAction = AsyncAction.Failure(Exception("Error"))),
aQrCodeScanState(isScanning = false, authenticationAction = AsyncAction.Failure(QrLoginException.OtherDeviceNotSignedIn)), 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 // Add other state here
) )
} }

View file

@ -35,6 +35,7 @@ import androidx.compose.ui.unit.dp
import io.element.android.compound.theme.ElementTheme import io.element.android.compound.theme.ElementTheme
import io.element.android.compound.tokens.generated.CompoundIcons import io.element.android.compound.tokens.generated.CompoundIcons
import io.element.android.features.login.impl.R 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.architecture.AsyncAction
import io.element.android.libraries.designsystem.atomic.pages.FlowStepPage import io.element.android.libraries.designsystem.atomic.pages.FlowStepPage
import io.element.android.libraries.designsystem.components.BigIcon import io.element.android.libraries.designsystem.components.BigIcon
@ -144,6 +145,12 @@ private fun ColumnScope.Buttons(
Spacer(modifier = Modifier.width(4.dp)) Spacer(modifier = Modifier.width(4.dp))
Text( Text(
text = when (error) { text = when (error) {
is UnauthorizedAccountProviderException -> {
stringResource(
id = R.string.screen_change_server_error_unauthorized_homeserver_title,
error.unauthorisedAccountProviderTitle,
)
}
is QrLoginException.OtherDeviceNotSignedIn -> { is QrLoginException.OtherDeviceNotSignedIn -> {
stringResource(R.string.screen_qr_code_login_device_not_signed_in_scan_state_subtitle) stringResource(R.string.screen_qr_code_login_device_not_signed_in_scan_state_subtitle)
} }
@ -156,6 +163,12 @@ private fun ColumnScope.Buttons(
} }
Text( Text(
text = when (error) { text = when (error) {
is UnauthorizedAccountProviderException -> {
stringResource(
id = R.string.screen_change_server_error_unauthorized_homeserver_content,
error.authorisedAccountProviderTitles.joinToString(),
)
}
is QrLoginException.OtherDeviceNotSignedIn -> { is QrLoginException.OtherDeviceNotSignedIn -> {
stringResource(R.string.screen_qr_code_login_device_not_signed_in_scan_state_description) stringResource(R.string.screen_qr_code_login_device_not_signed_in_scan_state_description)
} }

View file

@ -18,6 +18,8 @@
%1$s"</string> %1$s"</string>
<string name="screen_change_server_error_no_sliding_sync_message">"The selected account provider does not support sliding sync. An upgrade to the server is needed to use %1$s."</string> <string name="screen_change_server_error_no_sliding_sync_message">"The selected account provider does not support sliding sync. An upgrade to the server is needed to use %1$s."</string>
<string name="screen_change_server_error_unauthorized_homeserver">"%1$s is not allowed to connect to %2$s."</string> <string name="screen_change_server_error_unauthorized_homeserver">"%1$s is not allowed to connect to %2$s."</string>
<string name="screen_change_server_error_unauthorized_homeserver_content">"This app has been configured to allow: %1$s."</string>
<string name="screen_change_server_error_unauthorized_homeserver_title">"Account provider %1$s not allowed."</string>
<string name="screen_change_server_form_header">"Homeserver URL"</string> <string name="screen_change_server_form_header">"Homeserver URL"</string>
<string name="screen_change_server_form_notice">"Enter a domain address."</string> <string name="screen_change_server_form_notice">"Enter a domain address."</string>
<string name="screen_change_server_subtitle">"What is the address of your server?"</string> <string name="screen_change_server_subtitle">"What is the address of your server?"</string>

View file

@ -84,6 +84,7 @@ class ChangeServerPresenterTest {
createPresenter( createPresenter(
enterpriseService = FakeEnterpriseService( enterpriseService = FakeEnterpriseService(
isAllowedToConnectToHomeserverResult = isAllowedToConnectToHomeserverResult, isAllowedToConnectToHomeserverResult = isAllowedToConnectToHomeserverResult,
defaultHomeserverResult = { "element.io" },
), ),
).test { ).test {
val initialState = awaitItem() val initialState = awaitItem()
@ -94,8 +95,11 @@ class ChangeServerPresenterTest {
assertThat(loadingState.changeServerAction).isInstanceOf(AsyncData.Loading::class.java) assertThat(loadingState.changeServerAction).isInstanceOf(AsyncData.Loading::class.java)
val failureState = awaitItem() val failureState = awaitItem()
assertThat( assertThat(
(failureState.changeServerAction.errorOrNull() as ChangeServerError.UnauthorizedAccountProvider).accountProvider (failureState.changeServerAction.errorOrNull() as ChangeServerError.UnauthorizedAccountProvider).unauthorisedAccountProviderTitle
).isEqualTo(anAccountProvider) ).isEqualTo(anAccountProvider.title)
assertThat(
(failureState.changeServerAction.errorOrNull() as ChangeServerError.UnauthorizedAccountProvider).authorisedAccountProviderTitles
).containsExactly("element.io")
isAllowedToConnectToHomeserverResult.assertions() isAllowedToConnectToHomeserverResult.assertions()
.isCalledOnce() .isCalledOnce()
.with(value(A_HOMESERVER_URL)) .with(value(A_HOMESERVER_URL))

View file

@ -11,12 +11,17 @@ import app.cash.molecule.RecompositionMode
import app.cash.molecule.moleculeFlow import app.cash.molecule.moleculeFlow
import app.cash.turbine.test import app.cash.turbine.test
import com.google.common.truth.Truth.assertThat 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.features.login.impl.qrcode.FakeQrCodeLoginManager
import io.element.android.libraries.core.coroutine.CoroutineDispatchers 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.QrCodeLoginStep
import io.element.android.libraries.matrix.api.auth.qrlogin.QrLoginException 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.libraries.matrix.test.auth.qrlogin.FakeMatrixQrCodeLoginDataFactory
import io.element.android.tests.testutils.lambda.lambdaRecorder import io.element.android.tests.testutils.lambda.lambdaRecorder
import io.element.android.tests.testutils.test
import io.element.android.tests.testutils.testCoroutineDispatchers import io.element.android.tests.testutils.testCoroutineDispatchers
import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.runTest
@ -38,10 +43,22 @@ class QrCodeScanPresenterTest {
@Test @Test
fun `present - scanned QR code successfully`() = runTest { fun `present - scanned QR code successfully`() = runTest {
val presenter = createQrCodeScanPresenter() val qrCodeLoginDataFactory = FakeMatrixQrCodeLoginDataFactory(
moleculeFlow(RecompositionMode.Immediate) { parseQrCodeLoginDataResult = {
presenter.present() Result.success(
}.test { FakeMatrixQrCodeLoginData(
serverNameResult = { "example.com" }
)
)
}
)
val presenter = createQrCodeScanPresenter(
qrCodeLoginDataFactory = qrCodeLoginDataFactory,
enterpriseService = FakeEnterpriseService(
isAllowedToConnectToHomeserverResult = { true },
)
)
presenter.test {
val initialState = awaitItem() val initialState = awaitItem()
initialState.eventSink(QrCodeScanEvents.QrCodeScanned(byteArrayOf())) initialState.eventSink(QrCodeScanEvents.QrCodeScanned(byteArrayOf()))
assertThat(awaitItem().isScanning).isFalse() 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 @Test
fun `present - scanned QR code failed and can be retried`() = runTest { fun `present - scanned QR code failed and can be retried`() = runTest {
val qrCodeLoginDataFactory = FakeMatrixQrCodeLoginDataFactory( val qrCodeLoginDataFactory = FakeMatrixQrCodeLoginDataFactory(
@ -103,9 +152,11 @@ class QrCodeScanPresenterTest {
qrCodeLoginDataFactory: FakeMatrixQrCodeLoginDataFactory = FakeMatrixQrCodeLoginDataFactory(), qrCodeLoginDataFactory: FakeMatrixQrCodeLoginDataFactory = FakeMatrixQrCodeLoginDataFactory(),
coroutineDispatchers: CoroutineDispatchers = testCoroutineDispatchers(), coroutineDispatchers: CoroutineDispatchers = testCoroutineDispatchers(),
qrCodeLoginManager: FakeQrCodeLoginManager = FakeQrCodeLoginManager(), qrCodeLoginManager: FakeQrCodeLoginManager = FakeQrCodeLoginManager(),
enterpriseService: EnterpriseService = FakeEnterpriseService(),
) = QrCodeScanPresenter( ) = QrCodeScanPresenter(
qrCodeLoginDataFactory = qrCodeLoginDataFactory, qrCodeLoginDataFactory = qrCodeLoginDataFactory,
qrCodeLoginManager = qrCodeLoginManager, qrCodeLoginManager = qrCodeLoginManager,
coroutineDispatchers = coroutineDispatchers, coroutineDispatchers = coroutineDispatchers,
enterpriseService = enterpriseService,
) )
} }

View file

@ -7,4 +7,6 @@
package io.element.android.libraries.matrix.api.auth.qrlogin package io.element.android.libraries.matrix.api.auth.qrlogin
interface MatrixQrCodeLoginData interface MatrixQrCodeLoginData {
fun serverName(): String?
}

View file

@ -12,4 +12,8 @@ import org.matrix.rustcomponents.sdk.QrCodeData as RustQrCodeData
class SdkQrCodeLoginData( class SdkQrCodeLoginData(
internal val rustQrCodeData: RustQrCodeData, internal val rustQrCodeData: RustQrCodeData,
) : MatrixQrCodeLoginData ) : MatrixQrCodeLoginData {
override fun serverName(): String? {
return rustQrCodeData.serverName()
}
}

View file

@ -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)
}
}

View file

@ -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()
}
}

View file

@ -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.MatrixQrCodeLoginData
import io.element.android.libraries.matrix.api.auth.qrlogin.MatrixQrCodeLoginDataFactory 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 import io.element.android.tests.testutils.lambda.lambdaRecorder
class FakeMatrixQrCodeLoginDataFactory( class FakeMatrixQrCodeLoginDataFactory(
@ -20,4 +21,8 @@ class FakeMatrixQrCodeLoginDataFactory(
} }
} }
class FakeMatrixQrCodeLoginData : MatrixQrCodeLoginData class FakeMatrixQrCodeLoginData(
private val serverNameResult: () -> String? = { lambdaError() },
) : MatrixQrCodeLoginData {
override fun serverName() = serverNameResult()
}

View file

@ -44,8 +44,8 @@ import kotlin.coroutines.suspendCoroutine
@Composable @Composable
fun QrCodeCameraView( fun QrCodeCameraView(
onScanQrCode: (ByteArray) -> Unit, onScanQrCode: (ByteArray) -> Unit,
renderPreview: Boolean,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
renderPreview: Boolean = true,
) { ) {
if (LocalInspectionMode.current) { if (LocalInspectionMode.current) {
Box( Box(
@ -62,9 +62,11 @@ fun QrCodeCameraView(
var cameraProvider by remember { mutableStateOf<ProcessCameraProvider?>(null) } var cameraProvider by remember { mutableStateOf<ProcessCameraProvider?>(null) }
val previewUseCase = remember { Preview.Builder().build() } val previewUseCase = remember { Preview.Builder().build() }
var lastFrame by remember { mutableStateOf<Bitmap?>(null) } var lastFrame by remember { mutableStateOf<Bitmap?>(null) }
val imageAnalysis = ImageAnalysis.Builder() val imageAnalysis = remember {
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST) ImageAnalysis.Builder()
.build() .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
.build()
}
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
cameraProvider = localContext.getCameraProvider() cameraProvider = localContext.getCameraProvider()

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:6c1257fbdf388933be8fb2ae211c8abacc4cdb01c2a924a7ba3729337c9b6707 oid sha256:a1e37b08acfdf40279b0990dd65c58d38d2248026f0d2fe2a1c396245ea931fd
size 15434 size 15892

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:cb9a2d2145791122cc2ab7d903485de361fb6098216bf578dca7cc350e9f94a0 oid sha256:8db77f38d81113a5411c40c1ebf811090ce809fd0f0df3d29eab05d3ed0bb64a
size 13650 size 14114

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:5f90a8ecae26714e76d5536682716a15aefc3d4c8836617add9a0ea946e7d242
size 31486

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:34cd9f4558efc143b4ab3bec96342a42cfad6906da0d46e75d78db36b7e59bd6
size 30242