From 855647f90f6cebde92ff9a0d623c77d52f801263 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 27 Feb 2024 15:41:57 +0100 Subject: [PATCH 01/60] QrCode login: enable and navigate to new Node --- .../android/appnav/NotLoggedInFlowNode.kt | 11 ++++++++--- .../features/login/api/LoginEntryPoint.kt | 1 + .../login/impl/DefaultLoginEntryPoint.kt | 5 ++++- .../features/login/impl/LoginFlowNode.kt | 18 +++++++++++++++++- .../onboarding/api/OnBoardingEntryPoint.kt | 1 + .../onboarding/impl/OnBoardingConfig.kt | 2 +- .../features/onboarding/impl/OnBoardingNode.kt | 6 +++++- 7 files changed, 37 insertions(+), 7 deletions(-) diff --git a/appnav/src/main/kotlin/io/element/android/appnav/NotLoggedInFlowNode.kt b/appnav/src/main/kotlin/io/element/android/appnav/NotLoggedInFlowNode.kt index fae6d586aa2..fbab9923762 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/NotLoggedInFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/NotLoggedInFlowNode.kt @@ -75,6 +75,7 @@ class NotLoggedInFlowNode @AssistedInject constructor( @Parcelize data class LoginFlow( val isAccountCreation: Boolean, + val isQrCode: Boolean, ) : NavTarget @Parcelize @@ -86,11 +87,15 @@ class NotLoggedInFlowNode @AssistedInject constructor( NavTarget.OnBoarding -> { val callback = object : OnBoardingEntryPoint.Callback { override fun onSignUp() { - backstack.push(NavTarget.LoginFlow(isAccountCreation = true)) + backstack.push(NavTarget.LoginFlow(isAccountCreation = true, isQrCode = false)) } override fun onSignIn() { - backstack.push(NavTarget.LoginFlow(isAccountCreation = false)) + backstack.push(NavTarget.LoginFlow(isAccountCreation = false, isQrCode = false)) + } + + override fun onSignInWithQrCode() { + backstack.push(NavTarget.LoginFlow(isAccountCreation = false, isQrCode = true)) } override fun onOpenDeveloperSettings() { @@ -108,7 +113,7 @@ class NotLoggedInFlowNode @AssistedInject constructor( } is NavTarget.LoginFlow -> { loginEntryPoint.nodeBuilder(this, buildContext) - .params(LoginEntryPoint.Params(isAccountCreation = navTarget.isAccountCreation)) + .params(LoginEntryPoint.Params(isAccountCreation = navTarget.isAccountCreation, isQrCode = navTarget.isQrCode)) .build() } NavTarget.ConfigureTracing -> { diff --git a/features/login/api/src/main/kotlin/io/element/android/features/login/api/LoginEntryPoint.kt b/features/login/api/src/main/kotlin/io/element/android/features/login/api/LoginEntryPoint.kt index 07a546192d3..9f8ed68d32e 100644 --- a/features/login/api/src/main/kotlin/io/element/android/features/login/api/LoginEntryPoint.kt +++ b/features/login/api/src/main/kotlin/io/element/android/features/login/api/LoginEntryPoint.kt @@ -23,6 +23,7 @@ import io.element.android.libraries.architecture.FeatureEntryPoint interface LoginEntryPoint : FeatureEntryPoint { data class Params( val isAccountCreation: Boolean, + val isQrCode: Boolean, ) fun nodeBuilder(parentNode: Node, buildContext: BuildContext): NodeBuilder diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/DefaultLoginEntryPoint.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/DefaultLoginEntryPoint.kt index 633fc43b5c1..56c974452fe 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/DefaultLoginEntryPoint.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/DefaultLoginEntryPoint.kt @@ -32,7 +32,10 @@ class DefaultLoginEntryPoint @Inject constructor() : LoginEntryPoint { return object : LoginEntryPoint.NodeBuilder { override fun params(params: LoginEntryPoint.Params): LoginEntryPoint.NodeBuilder { - plugins += LoginFlowNode.Inputs(isAccountCreation = params.isAccountCreation) + plugins += LoginFlowNode.Inputs( + isAccountCreation = params.isAccountCreation, + isQrCode = params.isQrCode, + ) return this } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/LoginFlowNode.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/LoginFlowNode.kt index cd17a7daf38..dd2d115329d 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/LoginFlowNode.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/LoginFlowNode.kt @@ -27,6 +27,7 @@ import com.bumble.appyx.core.lifecycle.subscribe import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.plugin.Plugin +import com.bumble.appyx.core.plugin.plugins import com.bumble.appyx.navmodel.backstack.BackStack import com.bumble.appyx.navmodel.backstack.operation.newRoot import com.bumble.appyx.navmodel.backstack.operation.push @@ -69,7 +70,7 @@ class LoginFlowNode @AssistedInject constructor( private val oidcActionFlow: OidcActionFlow, ) : BaseFlowNode( backstack = BackStack( - initialElement = NavTarget.ConfirmAccountProvider, + initialElement = NavTarget.Root, savedStateMap = buildContext.savedStateMap, ), buildContext = buildContext, @@ -80,6 +81,7 @@ class LoginFlowNode @AssistedInject constructor( data class Inputs( val isAccountCreation: Boolean, + val isQrCode: Boolean, ) : NodeInputs private val inputs: Inputs = inputs() @@ -107,6 +109,12 @@ class LoginFlowNode @AssistedInject constructor( } sealed interface NavTarget : Parcelable { + @Parcelize + data object Root : NavTarget + + @Parcelize + data object QrCode : NavTarget + @Parcelize data object ConfirmAccountProvider : NavTarget @@ -128,6 +136,13 @@ class LoginFlowNode @AssistedInject constructor( override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node { return when (navTarget) { + NavTarget.Root -> { + if (plugins().first().isQrCode) { + resolve(NavTarget.QrCode, buildContext) + } else { + resolve(NavTarget.ConfirmAccountProvider, buildContext) + } + } NavTarget.ConfirmAccountProvider -> { val inputs = ConfirmAccountProviderNode.Inputs( isAccountCreation = inputs.isAccountCreation @@ -203,6 +218,7 @@ class LoginFlowNode @AssistedInject constructor( } createNode(buildContext, plugins = listOf(callback, inputs)) } + NavTarget.QrCode -> TODO() } } diff --git a/features/onboarding/api/src/main/kotlin/io/element/android/features/onboarding/api/OnBoardingEntryPoint.kt b/features/onboarding/api/src/main/kotlin/io/element/android/features/onboarding/api/OnBoardingEntryPoint.kt index a70f14dc430..ddb3643dc1d 100644 --- a/features/onboarding/api/src/main/kotlin/io/element/android/features/onboarding/api/OnBoardingEntryPoint.kt +++ b/features/onboarding/api/src/main/kotlin/io/element/android/features/onboarding/api/OnBoardingEntryPoint.kt @@ -32,6 +32,7 @@ interface OnBoardingEntryPoint : FeatureEntryPoint { interface Callback : Plugin { fun onSignUp() fun onSignIn() + fun onSignInWithQrCode() fun onOpenDeveloperSettings() fun onReportProblem() } diff --git a/features/onboarding/impl/src/main/kotlin/io/element/android/features/onboarding/impl/OnBoardingConfig.kt b/features/onboarding/impl/src/main/kotlin/io/element/android/features/onboarding/impl/OnBoardingConfig.kt index 969ac382a4e..7858007bc9e 100644 --- a/features/onboarding/impl/src/main/kotlin/io/element/android/features/onboarding/impl/OnBoardingConfig.kt +++ b/features/onboarding/impl/src/main/kotlin/io/element/android/features/onboarding/impl/OnBoardingConfig.kt @@ -17,6 +17,6 @@ package io.element.android.features.onboarding.impl object OnBoardingConfig { - const val CAN_LOGIN_WITH_QR_CODE = false + const val CAN_LOGIN_WITH_QR_CODE = true const val CAN_CREATE_ACCOUNT = false } diff --git a/features/onboarding/impl/src/main/kotlin/io/element/android/features/onboarding/impl/OnBoardingNode.kt b/features/onboarding/impl/src/main/kotlin/io/element/android/features/onboarding/impl/OnBoardingNode.kt index f7d829d5feb..faf3e3be8df 100644 --- a/features/onboarding/impl/src/main/kotlin/io/element/android/features/onboarding/impl/OnBoardingNode.kt +++ b/features/onboarding/impl/src/main/kotlin/io/element/android/features/onboarding/impl/OnBoardingNode.kt @@ -45,6 +45,10 @@ class OnBoardingNode @AssistedInject constructor( plugins().forEach { it.onSignUp() } } + private fun onSignInWithQrCode() { + plugins().forEach { it.onSignInWithQrCode() } + } + private fun onOpenDeveloperSettings() { plugins().forEach { it.onOpenDeveloperSettings() } } @@ -61,7 +65,7 @@ class OnBoardingNode @AssistedInject constructor( modifier = modifier, onSignIn = ::onSignIn, onCreateAccount = ::onSignUp, - onSignInWithQrCode = { /* Not supported yet */ }, + onSignInWithQrCode = ::onSignInWithQrCode, onOpenDeveloperSettings = ::onOpenDeveloperSettings, onReportProblem = ::onReportProblem, ) From cb0287a0e3982c391ab43c27f7ba45a19d545e6a Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 27 Feb 2024 18:53:57 +0100 Subject: [PATCH 02/60] QrCode login: Intro screen with Camera permission --- features/login/impl/build.gradle.kts | 1 + .../features/login/impl/LoginFlowNode.kt | 24 +++- .../screens/qrcode/intro/QrCodeIntroEvents.kt | 21 +++ .../screens/qrcode/intro/QrCodeIntroNode.kt | 59 ++++++++ .../qrcode/intro/QrCodeIntroPresenter.kt | 68 +++++++++ .../screens/qrcode/intro/QrCodeIntroState.kt | 26 ++++ .../qrcode/intro/QrCodeIntroStateProvider.kt | 45 ++++++ .../screens/qrcode/intro/QrCodeIntroView.kt | 130 ++++++++++++++++++ 8 files changed, 371 insertions(+), 3 deletions(-) create mode 100644 features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/intro/QrCodeIntroEvents.kt create mode 100644 features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/intro/QrCodeIntroNode.kt create mode 100644 features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/intro/QrCodeIntroPresenter.kt create mode 100644 features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/intro/QrCodeIntroState.kt create mode 100644 features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/intro/QrCodeIntroStateProvider.kt create mode 100644 features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/intro/QrCodeIntroView.kt diff --git a/features/login/impl/build.gradle.kts b/features/login/impl/build.gradle.kts index 47ddd893fa3..e84bee0e15d 100644 --- a/features/login/impl/build.gradle.kts +++ b/features/login/impl/build.gradle.kts @@ -49,6 +49,7 @@ dependencies { implementation(projects.libraries.designsystem) implementation(projects.libraries.testtags) implementation(projects.libraries.uiStrings) + implementation(projects.libraries.permissions.api) implementation(libs.androidx.browser) implementation(platform(libs.network.retrofit.bom)) implementation(libs.network.retrofit) diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/LoginFlowNode.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/LoginFlowNode.kt index dd2d115329d..5b45b90c16e 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/LoginFlowNode.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/LoginFlowNode.kt @@ -46,6 +46,7 @@ import io.element.android.features.login.impl.screens.changeaccountprovider.Chan import io.element.android.features.login.impl.screens.confirmaccountprovider.ConfirmAccountProviderNode import io.element.android.features.login.impl.screens.loginpassword.LoginFormState import io.element.android.features.login.impl.screens.loginpassword.LoginPasswordNode +import io.element.android.features.login.impl.screens.qrcode.intro.QrCodeIntroNode import io.element.android.features.login.impl.screens.searchaccountprovider.SearchAccountProviderNode import io.element.android.features.login.impl.screens.waitlistscreen.WaitListNode import io.element.android.libraries.architecture.BackstackView @@ -113,7 +114,10 @@ class LoginFlowNode @AssistedInject constructor( data object Root : NavTarget @Parcelize - data object QrCode : NavTarget + data object QrCodeIntro : NavTarget + + @Parcelize + data object QrCodeScan : NavTarget @Parcelize data object ConfirmAccountProvider : NavTarget @@ -138,7 +142,7 @@ class LoginFlowNode @AssistedInject constructor( return when (navTarget) { NavTarget.Root -> { if (plugins().first().isQrCode) { - resolve(NavTarget.QrCode, buildContext) + resolve(NavTarget.QrCodeIntro, buildContext) } else { resolve(NavTarget.ConfirmAccountProvider, buildContext) } @@ -218,7 +222,21 @@ class LoginFlowNode @AssistedInject constructor( } createNode(buildContext, plugins = listOf(callback, inputs)) } - NavTarget.QrCode -> TODO() + NavTarget.QrCodeIntro -> { + val callback = object : QrCodeIntroNode.Callback { + override fun onCancelClicked() { + navigateUp() + } + + override fun onContinue() { + backstack.push(NavTarget.QrCodeScan) + } + } + createNode(buildContext, plugins = listOf(callback)) + } + NavTarget.QrCodeScan -> { + TODO() + } } } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/intro/QrCodeIntroEvents.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/intro/QrCodeIntroEvents.kt new file mode 100644 index 00000000000..982d7ac1af9 --- /dev/null +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/intro/QrCodeIntroEvents.kt @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.login.impl.screens.qrcode.intro + +sealed interface QrCodeIntroEvents { + data object Continue : QrCodeIntroEvents +} diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/intro/QrCodeIntroNode.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/intro/QrCodeIntroNode.kt new file mode 100644 index 00000000000..105c5b35255 --- /dev/null +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/intro/QrCodeIntroNode.kt @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.login.impl.screens.qrcode.intro + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import com.bumble.appyx.core.modality.BuildContext +import com.bumble.appyx.core.node.Node +import com.bumble.appyx.core.plugin.Plugin +import com.bumble.appyx.core.plugin.plugins +import dagger.assisted.Assisted +import dagger.assisted.AssistedInject +import io.element.android.anvilannotations.ContributesNode +import io.element.android.libraries.di.AppScope + +@ContributesNode(AppScope::class) +class QrCodeIntroNode @AssistedInject constructor( + @Assisted buildContext: BuildContext, + @Assisted plugins: List, + private val presenter: QrCodeIntroPresenter, +) : Node(buildContext, plugins = plugins) { + interface Callback : Plugin { + fun onCancelClicked() + fun onContinue() + } + + private fun onCancelClicked() { + plugins().forEach { it.onCancelClicked() } + } + + private fun onContinue() { + plugins().forEach { it.onContinue() } + } + + @Composable + override fun View(modifier: Modifier) { + val state = presenter.present() + QrCodeIntroView( + state = state, + onBackClicked = ::onCancelClicked, + onContinue = ::onContinue, + modifier = modifier + ) + } +} diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/intro/QrCodeIntroPresenter.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/intro/QrCodeIntroPresenter.kt new file mode 100644 index 00000000000..2f1b7379a00 --- /dev/null +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/intro/QrCodeIntroPresenter.kt @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.login.impl.screens.qrcode.intro + +import android.Manifest +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import io.element.android.libraries.architecture.Presenter +import io.element.android.libraries.core.meta.BuildMeta +import io.element.android.libraries.permissions.api.PermissionsEvents +import io.element.android.libraries.permissions.api.PermissionsPresenter +import javax.inject.Inject + +class QrCodeIntroPresenter @Inject constructor( + private val buildMeta: BuildMeta, + permissionsPresenterFactory: PermissionsPresenter.Factory, +) : Presenter { + private val cameraPermissionPresenter: PermissionsPresenter = permissionsPresenterFactory.create(Manifest.permission.CAMERA) + private var pendingPermissionRequest = false + + @Composable + override fun present(): QrCodeIntroState { + val cameraPermissionState = cameraPermissionPresenter.present() + var canContinue by remember { mutableStateOf(false) } + LaunchedEffect(cameraPermissionState.permissionGranted) { + if (cameraPermissionState.permissionGranted && pendingPermissionRequest) { + pendingPermissionRequest = false + canContinue = true + } + } + + fun handleEvents(event: QrCodeIntroEvents) { + when (event) { + QrCodeIntroEvents.Continue -> if (cameraPermissionState.permissionGranted) { + canContinue = true + } else { + pendingPermissionRequest = true + cameraPermissionState.eventSink(PermissionsEvents.RequestPermissions) + } + } + } + + return QrCodeIntroState( + appName = buildMeta.applicationName, + cameraPermissionState = cameraPermissionState, + canContinue = canContinue, + eventSink = ::handleEvents + ) + } +} diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/intro/QrCodeIntroState.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/intro/QrCodeIntroState.kt new file mode 100644 index 00000000000..602e18923be --- /dev/null +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/intro/QrCodeIntroState.kt @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.login.impl.screens.qrcode.intro + +import io.element.android.libraries.permissions.api.PermissionsState + +data class QrCodeIntroState( + val appName: String, + val cameraPermissionState: PermissionsState, + val canContinue: Boolean, + val eventSink: (QrCodeIntroEvents) -> Unit +) diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/intro/QrCodeIntroStateProvider.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/intro/QrCodeIntroStateProvider.kt new file mode 100644 index 00000000000..7c4aa717960 --- /dev/null +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/intro/QrCodeIntroStateProvider.kt @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.login.impl.screens.qrcode.intro + +import android.Manifest +import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import io.element.android.libraries.permissions.api.PermissionsState +import io.element.android.libraries.permissions.api.aPermissionsState + +open class QrCodeIntroStateProvider : PreviewParameterProvider { + override val values: Sequence + get() = sequenceOf( + aQrCodeIntroState(), + // Add other state here + ) +} + +fun aQrCodeIntroState( + appName: String = "Element X", + cameraPermissionState: PermissionsState = aPermissionsState( + showDialog = false, + permission = Manifest.permission.CAMERA, + ), + canContinue: Boolean = false, + eventSink: (QrCodeIntroEvents) -> Unit = {}, +) = QrCodeIntroState( + appName = appName, + cameraPermissionState = cameraPermissionState, + canContinue = canContinue, + eventSink = eventSink +) diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/intro/QrCodeIntroView.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/intro/QrCodeIntroView.kt new file mode 100644 index 00000000000..cbb40343174 --- /dev/null +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/intro/QrCodeIntroView.kt @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.login.impl.screens.qrcode.intro + +import androidx.compose.foundation.layout.ColumnScope +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.rememberUpdatedState +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.PreviewParameter +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.libraries.designsystem.atomic.organisms.InfoListItem +import io.element.android.libraries.designsystem.atomic.organisms.InfoListOrganism +import io.element.android.libraries.designsystem.atomic.pages.FlowStepPage +import io.element.android.libraries.designsystem.preview.ElementPreview +import io.element.android.libraries.designsystem.preview.PreviewsDayNight +import io.element.android.libraries.designsystem.theme.components.Button +import io.element.android.libraries.designsystem.theme.components.Text +import io.element.android.libraries.permissions.api.PermissionsView +import io.element.android.libraries.ui.strings.CommonStrings +import kotlinx.collections.immutable.persistentListOf + +@Composable +fun QrCodeIntroView( + state: QrCodeIntroState, + onBackClicked: () -> Unit, + onContinue: () -> Unit, + modifier: Modifier = Modifier, +) { + val latestOnContinue by rememberUpdatedState(onContinue) + LaunchedEffect(state.canContinue) { + if (state.canContinue) { + latestOnContinue() + } + } + FlowStepPage( + modifier = modifier, + onBackClicked = onBackClicked, + iconVector = CompoundIcons.Computer(), + title = "Open ${state.appName} to another device to get the QR code", // TODO Localazy + content = { Content(state = state) }, + buttons = { Buttons(state = state) } + ) + + PermissionsView( + state = state.cameraPermissionState, + ) +} + +@Composable +private fun Content( + state: QrCodeIntroState, +) { + // TODO integrate final design + InfoListOrganism( + modifier = Modifier.padding(top = 50.dp), + items = persistentListOf( + InfoListItem( + message = "Open ${state.appName} on a desktop device", + iconComposable = { NumberIcon(1) }, + ), + InfoListItem( + message = "Click on your avatar", + iconComposable = { NumberIcon(2) }, + ), + InfoListItem( + message = "Select ”Link new device”", + iconComposable = { NumberIcon(3) }, + ), + InfoListItem( + message = "Select ”Show QR code”", + iconComposable = { NumberIcon(4) }, + ), + ), + textStyle = ElementTheme.typography.fontBodyMdRegular, + iconTint = ElementTheme.colors.textPrimary, + backgroundColor = Color.Transparent + ) +} + +@Composable +private fun NumberIcon(i: Int) { + Text( + text = i.toString(), + ) +} + +@Composable +private fun ColumnScope.Buttons( + state: QrCodeIntroState, +) { + Button( + text = stringResource(id = CommonStrings.action_continue), + modifier = Modifier.fillMaxWidth(), + onClick = { + state.eventSink.invoke(QrCodeIntroEvents.Continue) + } + ) +} + +@PreviewsDayNight +@Composable +internal fun QrCodeIntroViewPreview(@PreviewParameter(QrCodeIntroStateProvider::class) state: QrCodeIntroState) = ElementPreview { + QrCodeIntroView( + state = state, + onBackClicked = {}, + onContinue = {}, + ) +} From 188e5e17de7e6ab679281dc647ebf8123b6ec0b2 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 27 Feb 2024 21:52:55 +0100 Subject: [PATCH 03/60] QrCode Scanner - WIP --- features/login/impl/build.gradle.kts | 1 + .../features/login/impl/LoginFlowNode.kt | 9 +- .../screens/qrcode/scan/QrCodeScanEvents.kt | 22 +++ .../screens/qrcode/scan/QrCodeScanNode.kt | 53 ++++++ .../qrcode/scan/QrCodeScanPresenter.kt | 72 +++++++ .../screens/qrcode/scan/QrCodeScanState.kt | 25 +++ .../qrcode/scan/QrCodeScanStateProvider.kt | 40 ++++ .../screens/qrcode/scan/QrCodeScanView.kt | 179 ++++++++++++++++++ gradle/libs.versions.toml | 6 + .../designsystem/modifiers/CornerBorder.kt | 73 +++++++ libraries/qrcode/build.gradle.kts | 30 +++ .../libraries/qrcode/QRCodeAnalyzer.kt | 80 ++++++++ .../libraries/qrcode/QrCodeCameraView.kt | 96 ++++++++++ 13 files changed, 685 insertions(+), 1 deletion(-) create mode 100644 features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/scan/QrCodeScanEvents.kt create mode 100644 features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/scan/QrCodeScanNode.kt create mode 100644 features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/scan/QrCodeScanPresenter.kt create mode 100644 features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/scan/QrCodeScanState.kt create mode 100644 features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/scan/QrCodeScanStateProvider.kt create mode 100644 features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/scan/QrCodeScanView.kt create mode 100644 libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/modifiers/CornerBorder.kt create mode 100644 libraries/qrcode/build.gradle.kts create mode 100644 libraries/qrcode/src/main/kotlin/io/element/android/libraries/qrcode/QRCodeAnalyzer.kt create mode 100644 libraries/qrcode/src/main/kotlin/io/element/android/libraries/qrcode/QrCodeCameraView.kt diff --git a/features/login/impl/build.gradle.kts b/features/login/impl/build.gradle.kts index e84bee0e15d..173bcb61ef6 100644 --- a/features/login/impl/build.gradle.kts +++ b/features/login/impl/build.gradle.kts @@ -50,6 +50,7 @@ dependencies { implementation(projects.libraries.testtags) implementation(projects.libraries.uiStrings) implementation(projects.libraries.permissions.api) + implementation(projects.libraries.qrcode) implementation(libs.androidx.browser) implementation(platform(libs.network.retrofit.bom)) implementation(libs.network.retrofit) diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/LoginFlowNode.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/LoginFlowNode.kt index 5b45b90c16e..5db7b4906c4 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/LoginFlowNode.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/LoginFlowNode.kt @@ -30,6 +30,7 @@ import com.bumble.appyx.core.plugin.Plugin import com.bumble.appyx.core.plugin.plugins import com.bumble.appyx.navmodel.backstack.BackStack import com.bumble.appyx.navmodel.backstack.operation.newRoot +import com.bumble.appyx.navmodel.backstack.operation.pop import com.bumble.appyx.navmodel.backstack.operation.push import com.bumble.appyx.navmodel.backstack.operation.singleTop import dagger.assisted.Assisted @@ -47,6 +48,7 @@ import io.element.android.features.login.impl.screens.confirmaccountprovider.Con import io.element.android.features.login.impl.screens.loginpassword.LoginFormState import io.element.android.features.login.impl.screens.loginpassword.LoginPasswordNode import io.element.android.features.login.impl.screens.qrcode.intro.QrCodeIntroNode +import io.element.android.features.login.impl.screens.qrcode.scan.QrCodeScanNode import io.element.android.features.login.impl.screens.searchaccountprovider.SearchAccountProviderNode import io.element.android.features.login.impl.screens.waitlistscreen.WaitListNode import io.element.android.libraries.architecture.BackstackView @@ -235,7 +237,12 @@ class LoginFlowNode @AssistedInject constructor( createNode(buildContext, plugins = listOf(callback)) } NavTarget.QrCodeScan -> { - TODO() + val callback = object : QrCodeScanNode.Callback { + override fun onCancelClicked() { + backstack.pop() + } + } + createNode(buildContext, plugins = listOf(callback)) } } } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/scan/QrCodeScanEvents.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/scan/QrCodeScanEvents.kt new file mode 100644 index 00000000000..c66114c3d49 --- /dev/null +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/scan/QrCodeScanEvents.kt @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.login.impl.screens.qrcode.scan + +sealed interface QrCodeScanEvents { + data class QrCodeScanned(val code: String) : QrCodeScanEvents + data object TryAgain : QrCodeScanEvents +} diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/scan/QrCodeScanNode.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/scan/QrCodeScanNode.kt new file mode 100644 index 00000000000..423eb5a403d --- /dev/null +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/scan/QrCodeScanNode.kt @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.login.impl.screens.qrcode.scan + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import com.bumble.appyx.core.modality.BuildContext +import com.bumble.appyx.core.node.Node +import com.bumble.appyx.core.plugin.Plugin +import com.bumble.appyx.core.plugin.plugins +import dagger.assisted.Assisted +import dagger.assisted.AssistedInject +import io.element.android.anvilannotations.ContributesNode +import io.element.android.libraries.di.AppScope + +@ContributesNode(AppScope::class) +class QrCodeScanNode @AssistedInject constructor( + @Assisted buildContext: BuildContext, + @Assisted plugins: List, + private val presenter: QrCodeScanPresenter, +) : Node(buildContext, plugins = plugins) { + interface Callback : Plugin { + fun onCancelClicked() + } + + private fun onCancelClicked() { + plugins().forEach { it.onCancelClicked() } + } + + @Composable + override fun View(modifier: Modifier) { + val state = presenter.present() + QrCodeScanView( + state = state, + onBackClicked = ::onCancelClicked, + modifier = modifier + ) + } +} 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 new file mode 100644 index 00000000000..30086dd4c74 --- /dev/null +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/scan/QrCodeScanPresenter.kt @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.login.impl.screens.qrcode.scan + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue +import io.element.android.libraries.architecture.AsyncAction +import io.element.android.libraries.architecture.Presenter +import io.element.android.libraries.architecture.runCatchingUpdatingState +import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import javax.inject.Inject + +class QrCodeScanPresenter @Inject constructor( + private val authenticationService: MatrixAuthenticationService, +) : Presenter { + + @Composable + override fun present(): QrCodeScanState { + val coroutineScope = rememberCoroutineScope() + var isScanning by remember { mutableStateOf(true) } + val authenticationAction: MutableState> = remember { mutableStateOf(AsyncAction.Uninitialized) } + + fun handleEvents(event: QrCodeScanEvents) { + when (event) { + QrCodeScanEvents.TryAgain -> { + isScanning = true + authenticationAction.value = AsyncAction.Uninitialized + } + is QrCodeScanEvents.QrCodeScanned -> { + isScanning = false + coroutineScope.authenticateWithCode(authenticationAction, event.code) + } + } + } + + return QrCodeScanState( + isScanning = isScanning, + authenticationAction = authenticationAction.value, + eventSink = ::handleEvents + ) + } + + private fun CoroutineScope.authenticateWithCode(authenticationAction: MutableState>, code: String) = launch { + suspend { + // TODO Call the SDK API, for now, just simulate an error + delay(1000) + throw Exception("Simulated error $code") + }.runCatchingUpdatingState(authenticationAction) + } +} diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/scan/QrCodeScanState.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/scan/QrCodeScanState.kt new file mode 100644 index 00000000000..3b2fa0cec37 --- /dev/null +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/scan/QrCodeScanState.kt @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.login.impl.screens.qrcode.scan + +import io.element.android.libraries.architecture.AsyncAction + +data class QrCodeScanState( + val isScanning: Boolean, + val authenticationAction: AsyncAction, + val eventSink: (QrCodeScanEvents) -> Unit +) 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 new file mode 100644 index 00000000000..37d21f8929d --- /dev/null +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/scan/QrCodeScanStateProvider.kt @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.login.impl.screens.qrcode.scan + +import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import io.element.android.libraries.architecture.AsyncAction + +open class QrCodeScanStateProvider : PreviewParameterProvider { + override val values: Sequence + get() = sequenceOf( + aQrCodeScanState(), + aQrCodeScanState(isScanning = false, authenticationAction = AsyncAction.Loading), + aQrCodeScanState(isScanning = false, authenticationAction = AsyncAction.Failure(Exception("Error"))), + // Add other state here + ) +} + +fun aQrCodeScanState( + isScanning: Boolean = true, + authenticationAction: AsyncAction = AsyncAction.Uninitialized, + eventSink: (QrCodeScanEvents) -> Unit = {}, +) = QrCodeScanState( + isScanning = isScanning, + authenticationAction = authenticationAction, + eventSink = eventSink +) 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 new file mode 100644 index 00000000000..0e5efb64c29 --- /dev/null +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/scan/QrCodeScanView.kt @@ -0,0 +1,179 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.login.impl.screens.qrcode.scan + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ColumnScope +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.aspectRatio +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.progressSemantics +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.PreviewParameter +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.libraries.architecture.AsyncAction +import io.element.android.libraries.designsystem.atomic.pages.FlowStepPage +import io.element.android.libraries.designsystem.modifiers.cornerBorder +import io.element.android.libraries.designsystem.preview.ElementPreview +import io.element.android.libraries.designsystem.preview.PreviewsDayNight +import io.element.android.libraries.designsystem.theme.components.Button +import io.element.android.libraries.designsystem.theme.components.CircularProgressIndicator +import io.element.android.libraries.designsystem.theme.components.Icon +import io.element.android.libraries.designsystem.theme.components.Text +import io.element.android.libraries.qrcode.QrCodeCameraView +import io.element.android.libraries.ui.strings.CommonStrings + +@Composable +fun QrCodeScanView( + state: QrCodeScanState, + onBackClicked: () -> Unit, + modifier: Modifier = Modifier, +) { + FlowStepPage( + modifier = modifier, + onBackClicked = onBackClicked, + iconVector = CompoundIcons.Computer(), + title = "Scan the QR code", // TODO Localazy + content = { Content(state = state) }, + buttons = { Buttons(state = state) } + ) +} + +@Composable +private fun Content( + state: QrCodeScanState, +) { + val modifier = Modifier + .fillMaxWidth() + .padding(start = 16.dp, end = 16.dp, top = 50.dp) + .aspectRatio(1f) + .cornerBorder( + strokeWidth = 4.dp, + color = ElementTheme.colors.textPrimary, + cornerSizeDp = 42.dp, + ) + Box( + modifier = modifier, + contentAlignment = Alignment.Center, + ) { + if (state.isScanning) { + QrCodeCameraView( + modifier = Modifier.fillMaxSize(), + onQrCodeScanned = { state.eventSink.invoke(QrCodeScanEvents.QrCodeScanned(it)) } + ) + } else { + Icon( + modifier = Modifier + .fillMaxSize() + .padding(32.dp), + imageVector = CompoundIcons.QrCode(), + contentDescription = null, + tint = ElementTheme.colors.textPrimary, + ) + } + } +} + +@Composable +private fun ColumnScope.Buttons( + state: QrCodeScanState, +) { + when (state.authenticationAction) { + is AsyncAction.Failure -> { + Button( + text = stringResource(id = CommonStrings.action_try_again), + modifier = Modifier.fillMaxWidth(), + onClick = { + state.eventSink.invoke(QrCodeScanEvents.TryAgain) + } + ) + Column( + modifier = Modifier.fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(2.dp), + ) { + Row( + verticalAlignment = Alignment.CenterVertically, + ) { + Icon( + imageVector = CompoundIcons.Error(), + tint = MaterialTheme.colorScheme.error, + contentDescription = null, + modifier = Modifier.size(24.dp) + ) + Spacer(modifier = Modifier.width(4.dp)) + Text( + text = "Wrong QR code", // TODO Localazy + textAlign = TextAlign.Center, + color = MaterialTheme.colorScheme.error, + style = ElementTheme.typography.fontBodySmMedium, + ) + } + Text( + text = "Use the QR code shown on the other device", // TODO Localazy + textAlign = TextAlign.Center, + style = ElementTheme.typography.fontBodySmRegular, + color = ElementTheme.colors.textSecondary, + ) + } + } + AsyncAction.Loading -> { + Column( + modifier = Modifier.fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(8.dp), + ) { + CircularProgressIndicator( + modifier = Modifier + .progressSemantics() + .size(20.dp), + strokeWidth = 2.dp + ) + Text( + text = "Establishing a secure connection", // TODO Localazy + textAlign = TextAlign.Center, + ) + } + } + AsyncAction.Uninitialized, + AsyncAction.Confirming, + is AsyncAction.Success -> Unit + } +} + +@PreviewsDayNight +@Composable +internal fun QrCodeScanViewPreview(@PreviewParameter(QrCodeScanStateProvider::class) state: QrCodeScanState) = ElementPreview { + QrCodeScanView( + state = state, + onBackClicked = {}, + ) +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 8474ae9c43e..3b229e01e87 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -16,6 +16,7 @@ constraintlayout_compose = "1.0.1" lifecycle = "2.7.0" activity = "1.8.2" media3 = "1.3.0" +camera = "1.3.2" # Compose compose_bom = "2024.04.00" @@ -76,6 +77,9 @@ androidx_datastore_datastore = { module = "androidx.datastore:datastore", versio androidx_exifinterface = "androidx.exifinterface:exifinterface:1.3.7" androidx_constraintlayout = { module = "androidx.constraintlayout:constraintlayout", version.ref = "constraintlayout" } androidx_constraintlayout_compose = { module = "androidx.constraintlayout:constraintlayout-compose", version.ref = "constraintlayout_compose" } +androidx_camera_lifecycle = { module = "androidx.camera:camera-lifecycle", version.ref = "camera" } +androidx_camera_view = { module = "androidx.camera:camera-view", version.ref = "camera" } +androidx_camera_camera2 = { module = "androidx.camera:camera-camera2", version.ref = "camera" } androidx_recyclerview = "androidx.recyclerview:recyclerview:1.3.2" androidx_browser = "androidx.browser:browser:1.8.0" @@ -173,6 +177,8 @@ maplibre_ktx = "org.maplibre.gl:android-sdk-ktx-v7:2.0.2" maplibre_annotation = "org.maplibre.gl:android-plugin-annotation-v9:2.0.2" opusencoder = "io.element.android:opusencoder:1.1.0" kotlinpoet = "com.squareup:kotlinpoet:1.16.0" +# Stick to 3.3.3 because of https://github.com/zxing/zxing/issues/1170 +zxing = "com.google.zxing:core:3.3.3" # Analytics posthog = "com.posthog:posthog-android:3.1.16" diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/modifiers/CornerBorder.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/modifiers/CornerBorder.kt new file mode 100644 index 00000000000..51bb4dd2a2f --- /dev/null +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/modifiers/CornerBorder.kt @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.designsystem.modifiers + +import androidx.compose.ui.Modifier +import androidx.compose.ui.composed +import androidx.compose.ui.draw.drawWithContent +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.Path +import androidx.compose.ui.graphics.PathEffect +import androidx.compose.ui.graphics.StrokeCap +import androidx.compose.ui.graphics.drawscope.Stroke +import androidx.compose.ui.unit.Dp +import io.element.android.libraries.designsystem.text.toPx + +/** + * Draw a border on corners around the content. + */ +fun Modifier.cornerBorder( + strokeWidth: Dp, + color: Color, + cornerSizeDp: Dp, +) = composed( + factory = { + val strokeWidthPx = strokeWidth.toPx() + val cornerSize = cornerSizeDp.toPx() + drawWithContent { + drawContent() + val width = size.width + val height = size.height + drawPath( + path = Path().apply { + // Top left corner + moveTo(0f, cornerSize) + lineTo(0f, 0f) + lineTo(cornerSize, 0f) + // Top right corner + moveTo(width - cornerSize, 0f) + lineTo(width, 0f) + lineTo(width, cornerSize) + // Bottom right corner + moveTo(width, height - cornerSize) + lineTo(width, height) + lineTo(width - cornerSize, height) + // Bottom left corner + moveTo(cornerSize, height) + lineTo(0f, height) + lineTo(0f, height - cornerSize) + }, + color = color, + style = Stroke( + width = strokeWidthPx, + pathEffect = PathEffect.cornerPathEffect(strokeWidthPx / 2), + cap = StrokeCap.Round, + ), + ) + } + } +) diff --git a/libraries/qrcode/build.gradle.kts b/libraries/qrcode/build.gradle.kts new file mode 100644 index 00000000000..44184f42317 --- /dev/null +++ b/libraries/qrcode/build.gradle.kts @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +plugins { + id("io.element.android-compose-library") +} + +android { + namespace = "io.element.android.libraries.qrcode" +} + +dependencies { + implementation(projects.libraries.designsystem) + implementation(libs.androidx.camera.lifecycle) + implementation(libs.androidx.camera.view) + implementation(libs.androidx.camera.camera2) + implementation(libs.zxing) +} diff --git a/libraries/qrcode/src/main/kotlin/io/element/android/libraries/qrcode/QRCodeAnalyzer.kt b/libraries/qrcode/src/main/kotlin/io/element/android/libraries/qrcode/QRCodeAnalyzer.kt new file mode 100644 index 00000000000..1d8574f99d1 --- /dev/null +++ b/libraries/qrcode/src/main/kotlin/io/element/android/libraries/qrcode/QRCodeAnalyzer.kt @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.qrcode + +import android.graphics.ImageFormat +import androidx.camera.core.ImageAnalysis +import androidx.camera.core.ImageProxy +import com.google.zxing.BarcodeFormat +import com.google.zxing.BinaryBitmap +import com.google.zxing.DecodeHintType +import com.google.zxing.MultiFormatReader +import com.google.zxing.NotFoundException +import com.google.zxing.PlanarYUVLuminanceSource +import com.google.zxing.common.HybridBinarizer +import timber.log.Timber +import java.nio.ByteBuffer + +internal class QRCodeAnalyzer( + private val onQrCodeScanned: (result: String?) -> Unit +) : ImageAnalysis.Analyzer { + override fun analyze(image: ImageProxy) { + if (image.format in SUPPORTED_IMAGE_FORMATS) { + val bytes = image.planes.first().buffer.toByteArray() + val source = PlanarYUVLuminanceSource( + bytes, + image.width, + image.height, + 0, + 0, + image.width, + image.height, + false + ) + val binaryBitmap = BinaryBitmap(HybridBinarizer(source)) + try { + val result = MultiFormatReader().apply { + setHints( + mapOf( + DecodeHintType.POSSIBLE_FORMATS to listOf(BarcodeFormat.QR_CODE) + ) + ) + }.decode(binaryBitmap) + onQrCodeScanned(result.text) + } catch (_: NotFoundException) { + // No QR code found in the image + } catch (e: Exception) { + Timber.w(e, "Error decoding QR code") + } finally { + image.close() + } + } + } + + private fun ByteBuffer.toByteArray(): ByteArray { + rewind() + return ByteArray(remaining()).also { get(it) } + } + + companion object { + private val SUPPORTED_IMAGE_FORMATS = listOf( + ImageFormat.YUV_420_888, + ImageFormat.YUV_422_888, + ImageFormat.YUV_444_888, + ) + } +} 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 new file mode 100644 index 00000000000..9cc0889eac6 --- /dev/null +++ b/libraries/qrcode/src/main/kotlin/io/element/android/libraries/qrcode/QrCodeCameraView.kt @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.qrcode + +import androidx.camera.core.CameraSelector +import androidx.camera.core.ImageAnalysis +import androidx.camera.core.Preview +import androidx.camera.lifecycle.ProcessCameraProvider +import androidx.camera.view.PreviewView +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clipToBounds +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalInspectionMode +import androidx.compose.ui.platform.LocalLifecycleOwner +import androidx.compose.ui.viewinterop.AndroidView +import androidx.core.content.ContextCompat +import io.element.android.compound.theme.ElementTheme +import io.element.android.libraries.designsystem.theme.components.Text +import timber.log.Timber + +@Composable +fun QrCodeCameraView( + onQrCodeScanned: (String) -> Unit, + modifier: Modifier = Modifier, +) { + if (LocalInspectionMode.current) { + Box( + modifier = modifier + .background(color = ElementTheme.colors.bgSubtlePrimary), + contentAlignment = Alignment.Center, + ) { + Text("CameraView") + } + } else { + val localContext = LocalContext.current + val lifecycleOwner = LocalLifecycleOwner.current + val cameraProviderFuture = remember { ProcessCameraProvider.getInstance(localContext) } + AndroidView( + modifier = modifier.clipToBounds(), + factory = { context -> + val previewView = PreviewView(context) + val preview = Preview.Builder() + .build() + val selector = CameraSelector.Builder() + .requireLensFacing(CameraSelector.LENS_FACING_BACK) + .build() + preview.setSurfaceProvider(previewView.surfaceProvider) + val imageAnalysis = ImageAnalysis.Builder() + .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST) + .build() + imageAnalysis.setAnalyzer( + ContextCompat.getMainExecutor(context), + QRCodeAnalyzer { result -> + result?.let { + Timber.d("QR code scanned!") + onQrCodeScanned(it) + } + } + ) + try { + cameraProviderFuture.get().bindToLifecycle( + lifecycleOwner, + selector, + preview, + imageAnalysis + ) + } catch (e: Exception) { + Timber.e(e, "Use case binding failed") + } + previewView + }, + onRelease = { + cameraProviderFuture.get().unbindAll() + }, + ) + } +} From 8af1d52d2d66c460f934263a6d3f447b8f2cf4f2 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 29 Feb 2024 09:27:18 +0100 Subject: [PATCH 04/60] to merge if it works. --- .../android/libraries/qrcode/QRCodeAnalyzer.kt | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/libraries/qrcode/src/main/kotlin/io/element/android/libraries/qrcode/QRCodeAnalyzer.kt b/libraries/qrcode/src/main/kotlin/io/element/android/libraries/qrcode/QRCodeAnalyzer.kt index 1d8574f99d1..d093e8f4ff3 100644 --- a/libraries/qrcode/src/main/kotlin/io/element/android/libraries/qrcode/QRCodeAnalyzer.kt +++ b/libraries/qrcode/src/main/kotlin/io/element/android/libraries/qrcode/QRCodeAnalyzer.kt @@ -19,19 +19,19 @@ package io.element.android.libraries.qrcode import android.graphics.ImageFormat import androidx.camera.core.ImageAnalysis import androidx.camera.core.ImageProxy -import com.google.zxing.BarcodeFormat import com.google.zxing.BinaryBitmap -import com.google.zxing.DecodeHintType -import com.google.zxing.MultiFormatReader import com.google.zxing.NotFoundException import com.google.zxing.PlanarYUVLuminanceSource import com.google.zxing.common.HybridBinarizer +import com.google.zxing.qrcode.QRCodeReader import timber.log.Timber import java.nio.ByteBuffer internal class QRCodeAnalyzer( private val onQrCodeScanned: (result: String?) -> Unit ) : ImageAnalysis.Analyzer { + private val reader = QRCodeReader() + override fun analyze(image: ImageProxy) { if (image.format in SUPPORTED_IMAGE_FORMATS) { val bytes = image.planes.first().buffer.toByteArray() @@ -47,13 +47,7 @@ internal class QRCodeAnalyzer( ) val binaryBitmap = BinaryBitmap(HybridBinarizer(source)) try { - val result = MultiFormatReader().apply { - setHints( - mapOf( - DecodeHintType.POSSIBLE_FORMATS to listOf(BarcodeFormat.QR_CODE) - ) - ) - }.decode(binaryBitmap) + val result = reader.decode(binaryBitmap) onQrCodeScanned(result.text) } catch (_: NotFoundException) { // No QR code found in the image From ad608bbbbdc7068ab81f8464a9b8e5462164696c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= Date: Wed, 10 Apr 2024 15:44:04 +0200 Subject: [PATCH 05/60] WIP changes: - Force portrait orientation on the login flow. - Create `NumberedList` UI components. - Improve camera permission dialog. - Improve how the QR code preview is rendered. - Freeze the last frame of the preview when a QR code is read. - Update `FlowStepPage` contents to fit the new designs. - Update strings in UI. --- .../io/element/android/x/di/AppModule.kt | 1 + .../android/appnav/NotLoggedInFlowNode.kt | 5 + .../impl/src/main/res/values/localazy.xml | 1 - .../impl/src/main/res/values/localazy.xml | 9 +- .../features/login/impl/LoginFlowNode.kt | 11 ++ .../confirmation/QrCodeConfirmationNode.kt | 32 ++++ .../qrcode/intro/QrCodeIntroPresenter.kt | 2 +- .../screens/qrcode/intro/QrCodeIntroState.kt | 2 +- .../qrcode/intro/QrCodeIntroStateProvider.kt | 5 +- .../screens/qrcode/intro/QrCodeIntroView.kt | 57 +++--- .../screens/qrcode/scan/QrCodeScanNode.kt | 6 + .../qrcode/scan/QrCodeScanPresenter.kt | 4 +- .../screens/qrcode/scan/QrCodeScanView.kt | 164 ++++++++++-------- .../impl/src/main/res/values/localazy.xml | 12 ++ .../impl/src/main/res/values/localazy.xml | 2 +- .../createkey/CreateNewRecoveryKeyView.kt | 72 ++------ .../impl/src/main/res/values/localazy.xml | 2 +- gradle/libs.versions.toml | 5 +- .../android/libraries/core/meta/BuildMeta.kt | 1 + libraries/designsystem/build.gradle.kts | 4 +- .../atomic/molecules/NumberedListMolecule.kt | 62 +++++++ .../atomic/organisms/NumberedListOrganism.kt | 42 +++++ .../designsystem/atomic/pages/FlowStepPage.kt | 10 +- .../components/dialogs/ConfirmationDialog.kt | 2 + .../theme/components/AlertDialogContent.kt | 2 + .../designsystem/utils/AnnotatedString.kt | 36 ++++ .../designsystem/utils/ForceOrientation.kt | 55 ++++++ .../permissions/api/PermissionsView.kt | 5 +- .../libraries/qrcode/QrCodeCameraView.kt | 97 +++++++---- .../src/main/res/values/localazy.xml | 6 + tools/localazy/config.json | 3 +- 31 files changed, 492 insertions(+), 225 deletions(-) create mode 100644 features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/confirmation/QrCodeConfirmationNode.kt create mode 100644 libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/molecules/NumberedListMolecule.kt create mode 100644 libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/organisms/NumberedListOrganism.kt create mode 100644 libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/AnnotatedString.kt create mode 100644 libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/ForceOrientation.kt diff --git a/app/src/main/kotlin/io/element/android/x/di/AppModule.kt b/app/src/main/kotlin/io/element/android/x/di/AppModule.kt index 47a2c373ad6..5e7d8aaae04 100644 --- a/app/src/main/kotlin/io/element/android/x/di/AppModule.kt +++ b/app/src/main/kotlin/io/element/android/x/di/AppModule.kt @@ -89,6 +89,7 @@ object AppModule { gitBranchName = BuildConfig.GIT_BRANCH_NAME, flavorDescription = BuildConfig.FLAVOR_DESCRIPTION, flavorShortDescription = BuildConfig.SHORT_FLAVOR_DESCRIPTION, + desktopApplicationName = "Element", ) @Provides diff --git a/appnav/src/main/kotlin/io/element/android/appnav/NotLoggedInFlowNode.kt b/appnav/src/main/kotlin/io/element/android/appnav/NotLoggedInFlowNode.kt index fbab9923762..6cd6ad1798a 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/NotLoggedInFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/NotLoggedInFlowNode.kt @@ -35,6 +35,8 @@ import io.element.android.features.onboarding.api.OnBoardingEntryPoint import io.element.android.features.preferences.api.ConfigureTracingEntryPoint import io.element.android.libraries.architecture.BackstackView import io.element.android.libraries.architecture.BaseFlowNode +import io.element.android.libraries.designsystem.utils.ForceOrientationInMobileDevices +import io.element.android.libraries.designsystem.utils.ScreenOrientation import io.element.android.libraries.di.AppScope import io.element.android.libraries.matrix.ui.media.NotLoggedInImageLoaderFactory import kotlinx.parcelize.Parcelize @@ -124,6 +126,9 @@ class NotLoggedInFlowNode @AssistedInject constructor( @Composable override fun View(modifier: Modifier) { + // The login flow doesn't support landscape mode on mobile devices yet + ForceOrientationInMobileDevices(orientation = ScreenOrientation.PORTRAIT) + BackstackView() } } diff --git a/features/createroom/impl/src/main/res/values/localazy.xml b/features/createroom/impl/src/main/res/values/localazy.xml index c9f5686c491..251541f610e 100644 --- a/features/createroom/impl/src/main/res/values/localazy.xml +++ b/features/createroom/impl/src/main/res/values/localazy.xml @@ -1,7 +1,6 @@ "New room" - "Invite people to Element" "Invite people" "An error occurred when creating the room" "Messages in this room are encrypted. Encryption can’t be disabled afterwards." diff --git a/features/ftue/impl/src/main/res/values/localazy.xml b/features/ftue/impl/src/main/res/values/localazy.xml index ba227878c17..8c43ef0a812 100644 --- a/features/ftue/impl/src/main/res/values/localazy.xml +++ b/features/ftue/impl/src/main/res/values/localazy.xml @@ -2,13 +2,18 @@ "You can change your settings later." "Allow notifications and never miss a message" - "Open Element on a desktop device" + "Establishing connection" + "Open %1$s on a desktop device" "Click on your avatar" "Select %1$s" "“Link new device”" "Select %1$s" "“Show QR code”" - "Open Element on another device to get the QR code" + "Open %1$s on another device to get the QR code" + "Use the QR code shown on the other device." + "Try Again" + "Wrong QR code" + "Scan the QR code" "Calls, polls, search and more will be added later this year." "Message history for encrypted rooms isn’t available yet." "We’d love to hear from you, let us know what you think via the settings page." diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/LoginFlowNode.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/LoginFlowNode.kt index 5db7b4906c4..88b27927ced 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/LoginFlowNode.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/LoginFlowNode.kt @@ -47,6 +47,7 @@ import io.element.android.features.login.impl.screens.changeaccountprovider.Chan import io.element.android.features.login.impl.screens.confirmaccountprovider.ConfirmAccountProviderNode import io.element.android.features.login.impl.screens.loginpassword.LoginFormState import io.element.android.features.login.impl.screens.loginpassword.LoginPasswordNode +import io.element.android.features.login.impl.screens.qrcode.confirmation.QrCodeConfirmationNode import io.element.android.features.login.impl.screens.qrcode.intro.QrCodeIntroNode import io.element.android.features.login.impl.screens.qrcode.scan.QrCodeScanNode import io.element.android.features.login.impl.screens.searchaccountprovider.SearchAccountProviderNode @@ -121,6 +122,9 @@ class LoginFlowNode @AssistedInject constructor( @Parcelize data object QrCodeScan : NavTarget + @Parcelize + data object QrCodeConfirmation : NavTarget + @Parcelize data object ConfirmAccountProvider : NavTarget @@ -238,12 +242,19 @@ class LoginFlowNode @AssistedInject constructor( } NavTarget.QrCodeScan -> { val callback = object : QrCodeScanNode.Callback { + override fun onScannedCode() { + backstack.push(NavTarget.QrCodeConfirmation) + } + override fun onCancelClicked() { backstack.pop() } } createNode(buildContext, plugins = listOf(callback)) } + NavTarget.QrCodeConfirmation -> { + createNode(buildContext) + } } } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/confirmation/QrCodeConfirmationNode.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/confirmation/QrCodeConfirmationNode.kt new file mode 100644 index 00000000000..7d9fcda8688 --- /dev/null +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/confirmation/QrCodeConfirmationNode.kt @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.login.impl.screens.qrcode.confirmation + +import com.bumble.appyx.core.modality.BuildContext +import com.bumble.appyx.core.node.Node +import com.bumble.appyx.core.plugin.Plugin +import dagger.assisted.Assisted +import dagger.assisted.AssistedInject +import io.element.android.anvilannotations.ContributesNode +import io.element.android.libraries.di.AppScope + +@ContributesNode(AppScope::class) +class QrCodeConfirmationNode @AssistedInject constructor( + @Assisted buildContext: BuildContext, + @Assisted plugins: List, +) : Node(buildContext = buildContext, plugins = plugins) { +} diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/intro/QrCodeIntroPresenter.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/intro/QrCodeIntroPresenter.kt index 2f1b7379a00..0e4dd527cdb 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/intro/QrCodeIntroPresenter.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/intro/QrCodeIntroPresenter.kt @@ -59,7 +59,7 @@ class QrCodeIntroPresenter @Inject constructor( } return QrCodeIntroState( - appName = buildMeta.applicationName, + desktopAppName = buildMeta.desktopApplicationName, cameraPermissionState = cameraPermissionState, canContinue = canContinue, eventSink = ::handleEvents diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/intro/QrCodeIntroState.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/intro/QrCodeIntroState.kt index 602e18923be..9d9609acdf8 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/intro/QrCodeIntroState.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/intro/QrCodeIntroState.kt @@ -19,7 +19,7 @@ package io.element.android.features.login.impl.screens.qrcode.intro import io.element.android.libraries.permissions.api.PermissionsState data class QrCodeIntroState( - val appName: String, + val desktopAppName: String, val cameraPermissionState: PermissionsState, val canContinue: Boolean, val eventSink: (QrCodeIntroEvents) -> Unit diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/intro/QrCodeIntroStateProvider.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/intro/QrCodeIntroStateProvider.kt index 7c4aa717960..7f7c62cd03a 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/intro/QrCodeIntroStateProvider.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/intro/QrCodeIntroStateProvider.kt @@ -25,12 +25,13 @@ open class QrCodeIntroStateProvider : PreviewParameterProvider override val values: Sequence get() = sequenceOf( aQrCodeIntroState(), + aQrCodeIntroState(cameraPermissionState = aPermissionsState(showDialog = true, permission = Manifest.permission.CAMERA)), // Add other state here ) } fun aQrCodeIntroState( - appName: String = "Element X", + desktopAppName: String = "Element", cameraPermissionState: PermissionsState = aPermissionsState( showDialog = false, permission = Manifest.permission.CAMERA, @@ -38,7 +39,7 @@ fun aQrCodeIntroState( canContinue: Boolean = false, eventSink: (QrCodeIntroEvents) -> Unit = {}, ) = QrCodeIntroState( - appName = appName, + desktopAppName = desktopAppName, cameraPermissionState = cameraPermissionState, canContinue = canContinue, eventSink = eventSink diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/intro/QrCodeIntroView.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/intro/QrCodeIntroView.kt index cbb40343174..b4ef2dd7bf6 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/intro/QrCodeIntroView.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/intro/QrCodeIntroView.kt @@ -24,19 +24,19 @@ import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.rememberUpdatedState import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.tooling.preview.PreviewParameter 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.libraries.designsystem.atomic.organisms.InfoListItem -import io.element.android.libraries.designsystem.atomic.organisms.InfoListOrganism +import io.element.android.features.login.impl.R +import io.element.android.libraries.designsystem.atomic.organisms.NumberedListOrganism import io.element.android.libraries.designsystem.atomic.pages.FlowStepPage import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.theme.components.Button -import io.element.android.libraries.designsystem.theme.components.Text +import io.element.android.libraries.designsystem.theme.components.Icon +import io.element.android.libraries.designsystem.utils.annotatedTextWithBold import io.element.android.libraries.permissions.api.PermissionsView import io.element.android.libraries.ui.strings.CommonStrings import kotlinx.collections.immutable.persistentListOf @@ -58,51 +58,34 @@ fun QrCodeIntroView( modifier = modifier, onBackClicked = onBackClicked, iconVector = CompoundIcons.Computer(), - title = "Open ${state.appName} to another device to get the QR code", // TODO Localazy + title = stringResource(id = R.string.screen_qr_code_login_initial_state_title, state.desktopAppName), content = { Content(state = state) }, buttons = { Buttons(state = state) } ) PermissionsView( + // TODO: localazy + title = "Allow camera access to scan the QR code", + icon = { Icon(imageVector = CompoundIcons.TakePhotoSolid(), contentDescription = null) }, state = state.cameraPermissionState, ) } @Composable -private fun Content( - state: QrCodeIntroState, -) { - // TODO integrate final design - InfoListOrganism( - modifier = Modifier.padding(top = 50.dp), +private fun Content(state: QrCodeIntroState) { + NumberedListOrganism( + modifier = Modifier.padding(top = 50.dp, start = 20.dp, end = 20.dp), items = persistentListOf( - InfoListItem( - message = "Open ${state.appName} on a desktop device", - iconComposable = { NumberIcon(1) }, - ), - InfoListItem( - message = "Click on your avatar", - iconComposable = { NumberIcon(2) }, - ), - InfoListItem( - message = "Select ”Link new device”", - iconComposable = { NumberIcon(3) }, - ), - InfoListItem( - message = "Select ”Show QR code”", - iconComposable = { NumberIcon(4) }, + AnnotatedString(stringResource(R.string.screen_qr_code_login_initial_state_item_1, state.desktopAppName)), + AnnotatedString(stringResource(R.string.screen_qr_code_login_initial_state_item_2)), + annotatedTextWithBold( + text = stringResource(R.string.screen_qr_code_login_initial_state_item_3, stringResource(R.string.screen_qr_code_login_initial_state_item_3_action)), + boldText = stringResource(R.string.screen_qr_code_login_initial_state_item_3_action)), + annotatedTextWithBold( + text = stringResource(R.string.screen_qr_code_login_initial_state_item_4, stringResource(R.string.screen_qr_code_login_initial_state_item_4_action)), + boldText = stringResource(R.string.screen_qr_code_login_initial_state_item_4_action) ), ), - textStyle = ElementTheme.typography.fontBodyMdRegular, - iconTint = ElementTheme.colors.textPrimary, - backgroundColor = Color.Transparent - ) -} - -@Composable -private fun NumberIcon(i: Int) { - Text( - text = i.toString(), ) } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/scan/QrCodeScanNode.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/scan/QrCodeScanNode.kt index 423eb5a403d..8c3c0b551ed 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/scan/QrCodeScanNode.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/scan/QrCodeScanNode.kt @@ -34,9 +34,14 @@ class QrCodeScanNode @AssistedInject constructor( private val presenter: QrCodeScanPresenter, ) : Node(buildContext, plugins = plugins) { interface Callback : Plugin { + fun onScannedCode() fun onCancelClicked() } + private fun onScannedCode() { + plugins().forEach { it.onScannedCode() } + } + private fun onCancelClicked() { plugins().forEach { it.onCancelClicked() } } @@ -46,6 +51,7 @@ class QrCodeScanNode @AssistedInject constructor( val state = presenter.present() QrCodeScanView( state = state, + onSecureConnectionReady = ::onScannedCode, onBackClicked = ::onCancelClicked, modifier = modifier ) 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 30086dd4c74..154304278ca 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 @@ -65,8 +65,8 @@ class QrCodeScanPresenter @Inject constructor( private fun CoroutineScope.authenticateWithCode(authenticationAction: MutableState>, code: String) = launch { suspend { // TODO Call the SDK API, for now, just simulate an error - delay(1000) - throw Exception("Simulated error $code") + delay(3000) +// throw Exception("Simulated error $code") }.runCatchingUpdatingState(authenticationAction) } } 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 0e5efb64c29..728055532ad 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 @@ -16,15 +16,18 @@ package io.element.android.features.login.impl.screens.qrcode.scan +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.BoxWithConstraints import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.ColumnScope import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.aspectRatio +import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width @@ -39,9 +42,11 @@ import androidx.compose.ui.tooling.preview.PreviewParameter 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.libraries.architecture.AsyncAction import io.element.android.libraries.designsystem.atomic.pages.FlowStepPage import io.element.android.libraries.designsystem.modifiers.cornerBorder +import io.element.android.libraries.designsystem.modifiers.squareSize import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.theme.components.Button @@ -55,13 +60,14 @@ import io.element.android.libraries.ui.strings.CommonStrings fun QrCodeScanView( state: QrCodeScanState, onBackClicked: () -> Unit, + onSecureConnectionReady: () -> Unit, modifier: Modifier = Modifier, ) { FlowStepPage( modifier = modifier, onBackClicked = onBackClicked, iconVector = CompoundIcons.Computer(), - title = "Scan the QR code", // TODO Localazy + title = stringResource(R.string.screen_qr_code_login_scanning_state_title), content = { Content(state = state) }, buttons = { Buttons(state = state) } ) @@ -71,32 +77,35 @@ fun QrCodeScanView( private fun Content( state: QrCodeScanState, ) { - val modifier = Modifier - .fillMaxWidth() - .padding(start = 16.dp, end = 16.dp, top = 50.dp) - .aspectRatio(1f) - .cornerBorder( - strokeWidth = 4.dp, - color = ElementTheme.colors.textPrimary, - cornerSizeDp = 42.dp, - ) - Box( - modifier = modifier, + BoxWithConstraints( + modifier = Modifier.fillMaxWidth(), contentAlignment = Alignment.Center, ) { - if (state.isScanning) { - QrCodeCameraView( - modifier = Modifier.fillMaxSize(), - onQrCodeScanned = { state.eventSink.invoke(QrCodeScanEvents.QrCodeScanned(it)) } - ) + val modifier = if (constraints.maxWidth > constraints.maxHeight) { + Modifier.fillMaxHeight() } else { - Icon( + Modifier.fillMaxWidth() + }.then( + Modifier + .padding(start = 20.dp, end = 20.dp, top = 50.dp) + .squareSize() + .cornerBorder( + strokeWidth = 4.dp, + color = ElementTheme.colors.textPrimary, + cornerSizeDp = 42.dp, + ) + ) + Box( + modifier = modifier, + contentAlignment = Alignment.Center, + ) { + QrCodeCameraView( modifier = Modifier .fillMaxSize() - .padding(32.dp), - imageVector = CompoundIcons.QrCode(), - contentDescription = null, - tint = ElementTheme.colors.textPrimary, + // TODO: FOR TESTING ONLY, REMOVE THIS + .clickable { state.eventSink.invoke(QrCodeScanEvents.QrCodeScanned("ASDA")) }, + onQrCodeScanned = { state.eventSink.invoke(QrCodeScanEvents.QrCodeScanned(it)) }, + renderPreview = state.isScanning, ) } } @@ -106,66 +115,72 @@ private fun Content( private fun ColumnScope.Buttons( state: QrCodeScanState, ) { - when (state.authenticationAction) { - is AsyncAction.Failure -> { - Button( - text = stringResource(id = CommonStrings.action_try_again), - modifier = Modifier.fillMaxWidth(), - onClick = { - state.eventSink.invoke(QrCodeScanEvents.TryAgain) + Column(Modifier.heightIn(min = 130.dp)) { + when (state.authenticationAction) { + is AsyncAction.Failure -> { + Button( + text = stringResource(id = R.string.screen_qr_code_login_invalid_scan_state_retry_button), + modifier = Modifier + .fillMaxWidth() + .padding(bottom = 16.dp), + onClick = { + state.eventSink.invoke(QrCodeScanEvents.TryAgain) + } + ) + Column( + modifier = Modifier.fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(2.dp), + ) { + Row( + verticalAlignment = Alignment.CenterVertically, + ) { + Icon( + imageVector = CompoundIcons.Error(), + tint = MaterialTheme.colorScheme.error, + contentDescription = null, + modifier = Modifier.size(24.dp) + ) + Spacer(modifier = Modifier.width(4.dp)) + Text( + text = stringResource(R.string.screen_qr_code_login_invalid_scan_state_subtitle), + textAlign = TextAlign.Center, + color = MaterialTheme.colorScheme.error, + style = ElementTheme.typography.fontBodySmMedium, + ) + } + Text( + text = stringResource(R.string.screen_qr_code_login_invalid_scan_state_description), + textAlign = TextAlign.Center, + style = ElementTheme.typography.fontBodySmRegular, + color = ElementTheme.colors.textSecondary, + ) } - ) - Column( - modifier = Modifier.fillMaxWidth(), - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.spacedBy(2.dp), - ) { - Row( - verticalAlignment = Alignment.CenterVertically, + } + AsyncAction.Loading -> { + Column( + modifier = Modifier.fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(8.dp), ) { - Icon( - imageVector = CompoundIcons.Error(), - tint = MaterialTheme.colorScheme.error, - contentDescription = null, - modifier = Modifier.size(24.dp) + CircularProgressIndicator( + modifier = Modifier + .progressSemantics() + .size(20.dp), + strokeWidth = 2.dp ) - Spacer(modifier = Modifier.width(4.dp)) Text( - text = "Wrong QR code", // TODO Localazy + text = "Establishing a secure connection", // TODO Localazy textAlign = TextAlign.Center, - color = MaterialTheme.colorScheme.error, - style = ElementTheme.typography.fontBodySmMedium, + style = ElementTheme.typography.fontBodySmRegular, + color = ElementTheme.colors.textSecondary, ) } - Text( - text = "Use the QR code shown on the other device", // TODO Localazy - textAlign = TextAlign.Center, - style = ElementTheme.typography.fontBodySmRegular, - color = ElementTheme.colors.textSecondary, - ) - } - } - AsyncAction.Loading -> { - Column( - modifier = Modifier.fillMaxWidth(), - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.spacedBy(8.dp), - ) { - CircularProgressIndicator( - modifier = Modifier - .progressSemantics() - .size(20.dp), - strokeWidth = 2.dp - ) - Text( - text = "Establishing a secure connection", // TODO Localazy - textAlign = TextAlign.Center, - ) } + AsyncAction.Uninitialized, + AsyncAction.Confirming, + is AsyncAction.Success -> Unit } - AsyncAction.Uninitialized, - AsyncAction.Confirming, - is AsyncAction.Success -> Unit } } @@ -174,6 +189,7 @@ private fun ColumnScope.Buttons( internal fun QrCodeScanViewPreview(@PreviewParameter(QrCodeScanStateProvider::class) state: QrCodeScanState) = ElementPreview { QrCodeScanView( state = state, + onSecureConnectionReady = {}, onBackClicked = {}, ) } diff --git a/features/login/impl/src/main/res/values/localazy.xml b/features/login/impl/src/main/res/values/localazy.xml index f268b464f12..e35f8b4011f 100644 --- a/features/login/impl/src/main/res/values/localazy.xml +++ b/features/login/impl/src/main/res/values/localazy.xml @@ -30,6 +30,18 @@ "Matrix is an open network for secure, decentralised communication." "Welcome back!" "Sign in to %1$s" + "Establishing connection" + "Open %1$s on a desktop device" + "Click on your avatar" + "Select %1$s" + "“Link new device”" + "Select %1$s" + "“Show QR code”" + "Open %1$s on another device to get the QR code" + "Use the QR code shown on the other device." + "Try Again" + "Wrong QR code" + "Scan the QR code" "Change account provider" "A private server for Element employees." "Matrix is an open network for secure, decentralised communication." diff --git a/features/onboarding/impl/src/main/res/values/localazy.xml b/features/onboarding/impl/src/main/res/values/localazy.xml index 2e8d8724d47..78acc7ffb47 100644 --- a/features/onboarding/impl/src/main/res/values/localazy.xml +++ b/features/onboarding/impl/src/main/res/values/localazy.xml @@ -3,7 +3,7 @@ "Sign in manually" "Sign in with QR code" "Create account" - "Welcome to the fastest Element ever. Supercharged for speed and simplicity." + "Welcome to the fastest %1$s ever. Supercharged for speed and simplicity." "Welcome to %1$s. Supercharged, for speed and simplicity." "Be in your element" diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/createkey/CreateNewRecoveryKeyView.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/createkey/CreateNewRecoveryKeyView.kt index ed3a5cd3390..bbedaabf0a6 100644 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/createkey/CreateNewRecoveryKeyView.kt +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/createkey/CreateNewRecoveryKeyView.kt @@ -16,35 +16,26 @@ package io.element.android.features.securebackup.impl.createkey -import androidx.compose.foundation.border -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.shape.CircleShape import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.AnnotatedString -import androidx.compose.ui.text.SpanStyle -import androidx.compose.ui.text.buildAnnotatedString -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.style.TextAlign 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.securebackup.impl.R +import io.element.android.libraries.designsystem.atomic.organisms.NumberedListOrganism import io.element.android.libraries.designsystem.components.BigIcon import io.element.android.libraries.designsystem.components.PageTitle import io.element.android.libraries.designsystem.components.button.BackButton -import io.element.android.libraries.designsystem.modifiers.squareSize import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.theme.components.Scaffold -import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.designsystem.theme.components.TopAppBar +import io.element.android.libraries.designsystem.utils.annotatedTextWithBold +import kotlinx.collections.immutable.toImmutableList @OptIn(ExperimentalMaterial3Api::class) @Composable @@ -73,55 +64,18 @@ fun CreateNewRecoveryKeyView( @Composable private fun Content() { - Column(modifier = Modifier.padding(horizontal = 16.dp), verticalArrangement = Arrangement.spacedBy(24.dp)) { - Item(index = 1, text = AnnotatedString(stringResource(R.string.screen_create_new_recovery_key_list_item_1))) - Item(index = 2, text = AnnotatedString(stringResource(R.string.screen_create_new_recovery_key_list_item_2))) - Item( - index = 3, - text = buildAnnotatedString { - val resetAllAction = stringResource(R.string.screen_create_new_recovery_key_list_item_3_reset_all) - val text = stringResource(R.string.screen_create_new_recovery_key_list_item_3, resetAllAction) - append(text) - val start = text.indexOf(resetAllAction) - val end = start + resetAllAction.length - if (start in text.indices && end in text.indices) { - addStyle(SpanStyle(fontWeight = FontWeight.Bold), start, end) - } - } - ) - Item(index = 4, text = AnnotatedString(stringResource(R.string.screen_create_new_recovery_key_list_item_4))) - Item(index = 5, text = AnnotatedString(stringResource(R.string.screen_create_new_recovery_key_list_item_5))) - } -} - -@Composable -private fun Item(index: Int, text: AnnotatedString) { - Row( - horizontalArrangement = Arrangement.spacedBy(8.dp) - ) { - ItemNumber(index = index) - Text(text = text, style = ElementTheme.typography.fontBodyMdRegular, color = ElementTheme.colors.textPrimary) - } -} - -@Composable -private fun ItemNumber( - index: Int, -) { - val color = ElementTheme.colors.textPlaceholder - Box( - modifier = Modifier - .border(1.dp, color, CircleShape) - .squareSize() - ) { - Text( - modifier = Modifier.padding(1.5.dp), - text = index.toString(), - style = ElementTheme.typography.fontBodySmRegular, - color = color, - textAlign = TextAlign.Center, + val listItems = buildList { + add(AnnotatedString(stringResource(R.string.screen_create_new_recovery_key_list_item_1))) + add(AnnotatedString(stringResource(R.string.screen_create_new_recovery_key_list_item_2))) + add( + annotatedTextWithBold( + text = stringResource(R.string.screen_create_new_recovery_key_list_item_3), + boldText = stringResource(R.string.screen_create_new_recovery_key_list_item_3_reset_all)) ) + add(AnnotatedString(stringResource(R.string.screen_create_new_recovery_key_list_item_4))) + add(AnnotatedString(stringResource(R.string.screen_create_new_recovery_key_list_item_5))) } + NumberedListOrganism(modifier = Modifier.padding(horizontal = 16.dp), items = listItems.toImmutableList()) } @PreviewsDayNight diff --git a/features/securebackup/impl/src/main/res/values/localazy.xml b/features/securebackup/impl/src/main/res/values/localazy.xml index 9e188d12513..d3fb41857ea 100644 --- a/features/securebackup/impl/src/main/res/values/localazy.xml +++ b/features/securebackup/impl/src/main/res/values/localazy.xml @@ -9,7 +9,7 @@ "Your chat backup is currently out of sync." "Set up recovery" "Get access to your encrypted messages if you lose all your devices or are signed out of %1$s everywhere." - "Open Element in a desktop device" + "Open %1$s in a desktop device" "Sign into your account again" "When asked to verify your device, select %1$s" "“Reset all”" diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 3b229e01e87..83bf6e32a3b 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -17,6 +17,7 @@ lifecycle = "2.7.0" activity = "1.8.2" media3 = "1.3.0" camera = "1.3.2" +material3 = "1.2.1" # Compose compose_bom = "2024.04.00" @@ -98,7 +99,9 @@ androidx_preference = "androidx.preference:preference:1.2.1" androidx_webkit = "androidx.webkit:webkit:1.10.0" androidx_compose_bom = { module = "androidx.compose:compose-bom", version.ref = "compose_bom" } -androidx_compose_material3 = "androidx.compose.material3:material3:1.2.1" +androidx_compose_material3 = { module = "androidx.compose.material3:material3", version.ref = "material3" } +androidx_compose_material3_windowsizeclass = { module = "androidx.compose.material3:material3-window-size-class", version.ref = "material3" } +androidx_compose_material3_adaptive = "androidx.compose.material3:material3-adaptive-android:1.0.0-alpha06" androidx_compose_ui = { module = "androidx.compose.ui:ui" } androidx_compose_ui_tooling = { module = "androidx.compose.ui:ui-tooling" } androidx_compose_ui_tooling_preview = { module = "androidx.compose.ui:ui-tooling-preview" } diff --git a/libraries/core/src/main/kotlin/io/element/android/libraries/core/meta/BuildMeta.kt b/libraries/core/src/main/kotlin/io/element/android/libraries/core/meta/BuildMeta.kt index cac30e02e43..71d26a5f9c7 100644 --- a/libraries/core/src/main/kotlin/io/element/android/libraries/core/meta/BuildMeta.kt +++ b/libraries/core/src/main/kotlin/io/element/android/libraries/core/meta/BuildMeta.kt @@ -28,4 +28,5 @@ data class BuildMeta( val gitBranchName: String, val flavorDescription: String, val flavorShortDescription: String, + val desktopApplicationName: String = applicationName, ) diff --git a/libraries/designsystem/build.gradle.kts b/libraries/designsystem/build.gradle.kts index 6cb1f4f7d25..13d5e18e1a2 100644 --- a/libraries/designsystem/build.gradle.kts +++ b/libraries/designsystem/build.gradle.kts @@ -36,7 +36,9 @@ android { dependencies { api(libs.compound) - // Should not be there, but this is a POC + + implementation(libs.androidx.compose.material3.windowsizeclass) + implementation(libs.androidx.compose.material3.adaptive) implementation(libs.coil.compose) implementation(libs.vanniktech.blurhash) implementation(projects.libraries.architecture) diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/molecules/NumberedListMolecule.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/molecules/NumberedListMolecule.kt new file mode 100644 index 00000000000..bfe57de369a --- /dev/null +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/molecules/NumberedListMolecule.kt @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.designsystem.atomic.molecules + +import androidx.compose.foundation.border +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import io.element.android.compound.theme.ElementTheme +import io.element.android.libraries.designsystem.modifiers.squareSize +import io.element.android.libraries.designsystem.theme.components.Text + +@Composable +fun NumberedListMolecule(index: Int, text: AnnotatedString) { + Row( + horizontalArrangement = Arrangement.spacedBy(8.dp) + ) { + ItemNumber(index = index) + Text(text = text, style = ElementTheme.typography.fontBodyMdRegular, color = ElementTheme.colors.textPrimary) + } +} + +@Composable +private fun ItemNumber( + index: Int, +) { + val color = ElementTheme.colors.textPlaceholder + Box( + modifier = Modifier + .border(1.dp, color, CircleShape) + .squareSize() + ) { + Text( + modifier = Modifier.padding(1.5.dp), + text = index.toString(), + style = ElementTheme.typography.fontBodySmRegular, + color = color, + textAlign = TextAlign.Center, + ) + } +} diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/organisms/NumberedListOrganism.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/organisms/NumberedListOrganism.kt new file mode 100644 index 00000000000..ccdd875939b --- /dev/null +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/organisms/NumberedListOrganism.kt @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.designsystem.atomic.organisms + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.unit.dp +import io.element.android.libraries.designsystem.atomic.molecules.NumberedListMolecule +import kotlinx.collections.immutable.ImmutableList + +@Composable +fun NumberedListOrganism( + items: ImmutableList, + modifier: Modifier = Modifier, +) { + LazyColumn( + modifier = modifier, + verticalArrangement = Arrangement.spacedBy(24.dp), + ) { + itemsIndexed(items) { index, item -> + NumberedListMolecule(index = index + 1, text = item) + } + } +} diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/pages/FlowStepPage.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/pages/FlowStepPage.kt index 7eb4b6d4136..9f8f8dad92e 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/pages/FlowStepPage.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/pages/FlowStepPage.kt @@ -31,6 +31,8 @@ import io.element.android.compound.theme.ElementTheme import io.element.android.compound.tokens.generated.CompoundIcons import io.element.android.libraries.designsystem.atomic.molecules.ButtonColumnMolecule import io.element.android.libraries.designsystem.atomic.molecules.IconTitleSubtitleMolecule +import io.element.android.libraries.designsystem.components.BigIcon +import io.element.android.libraries.designsystem.components.PageTitle import io.element.android.libraries.designsystem.components.button.BackButton import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight @@ -49,7 +51,7 @@ import io.element.android.libraries.designsystem.theme.components.TopAppBar @OptIn(ExperimentalMaterial3Api::class) @Composable fun FlowStepPage( - iconVector: ImageVector?, + iconVector: ImageVector, title: String, modifier: Modifier = Modifier, onBackClicked: (() -> Unit)? = null, @@ -73,10 +75,10 @@ fun FlowStepPage( ) }, header = { - IconTitleSubtitleMolecule( - iconImageVector = iconVector, + PageTitle( title = title, - subTitle = subTitle, + subtitle = subTitle, + iconStyle = BigIcon.Style.Default(iconVector) , ) }, content = content, diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/dialogs/ConfirmationDialog.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/dialogs/ConfirmationDialog.kt index 49d3c98b4f4..433817af4a3 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/dialogs/ConfirmationDialog.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/dialogs/ConfirmationDialog.kt @@ -44,6 +44,7 @@ fun ConfirmationDialog( thirdButtonText: String? = null, onCancelClicked: () -> Unit = onDismiss, onThirdButtonClicked: () -> Unit = {}, + icon: @Composable (() -> Unit)? = null, ) { BasicAlertDialog(modifier = modifier, onDismissRequest = onDismiss) { ConfirmationDialogContent( @@ -56,6 +57,7 @@ fun ConfirmationDialog( onSubmitClicked = onSubmitClicked, onCancelClicked = onCancelClicked, onThirdButtonClicked = onThirdButtonClicked, + icon = icon, ) } } diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/AlertDialogContent.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/AlertDialogContent.kt index 4927a195e0b..9a450465080 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/AlertDialogContent.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/AlertDialogContent.kt @@ -37,6 +37,7 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Shape import androidx.compose.ui.layout.Layout import androidx.compose.ui.layout.Placeable +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp @@ -150,6 +151,7 @@ internal fun SimpleAlertDialogContent( Text( text = titleText, style = ElementTheme.typography.fontHeadingSmMedium, + textAlign = TextAlign.Center, ) } }, diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/AnnotatedString.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/AnnotatedString.kt new file mode 100644 index 00000000000..562b0033b4b --- /dev/null +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/AnnotatedString.kt @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.designsystem.utils + +import androidx.compose.runtime.Composable +import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.text.SpanStyle +import androidx.compose.ui.text.buildAnnotatedString +import androidx.compose.ui.text.font.FontWeight + +@Composable +fun annotatedTextWithBold(text: String, boldText: String): AnnotatedString { + return buildAnnotatedString { + append(text) + val start = text.indexOf(boldText) + val end = start + boldText.length + val textRange = 0..text.length + if (start in textRange && end in textRange) { + addStyle(SpanStyle(fontWeight = FontWeight.Bold), start, end) + } + } +} diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/ForceOrientation.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/ForceOrientation.kt new file mode 100644 index 00000000000..040d1ec0670 --- /dev/null +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/ForceOrientation.kt @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.designsystem.utils + +import android.content.pm.ActivityInfo +import androidx.activity.ComponentActivity +import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi +import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo +import androidx.compose.material3.windowsizeclass.WindowHeightSizeClass +import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass +import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +import androidx.compose.ui.platform.LocalContext + +@Composable +fun ForceOrientation(orientation: ScreenOrientation) { + val activity = LocalContext.current as? ComponentActivity ?: return + val orientationFlags = when (orientation) { + ScreenOrientation.PORTRAIT -> ActivityInfo.SCREEN_ORIENTATION_PORTRAIT + ScreenOrientation.LANDSCAPE -> ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE + } + DisposableEffect(orientation) { + activity.requestedOrientation = orientationFlags + onDispose { activity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED } + } +} + +@OptIn(ExperimentalMaterial3AdaptiveApi::class) +@Composable +fun ForceOrientationInMobileDevices(orientation: ScreenOrientation) { + val windowAdaptiveInfo = currentWindowAdaptiveInfo() + if (windowAdaptiveInfo.windowSizeClass.widthSizeClass == WindowWidthSizeClass.Compact + || windowAdaptiveInfo.windowSizeClass.heightSizeClass == WindowHeightSizeClass.Compact) { + ForceOrientation(orientation = orientation) + } +} + +enum class ScreenOrientation { + PORTRAIT, + LANDSCAPE +} diff --git a/libraries/permissions/api/src/main/kotlin/io/element/android/libraries/permissions/api/PermissionsView.kt b/libraries/permissions/api/src/main/kotlin/io/element/android/libraries/permissions/api/PermissionsView.kt index 38b40e64030..aef419a56b5 100644 --- a/libraries/permissions/api/src/main/kotlin/io/element/android/libraries/permissions/api/PermissionsView.kt +++ b/libraries/permissions/api/src/main/kotlin/io/element/android/libraries/permissions/api/PermissionsView.kt @@ -30,18 +30,21 @@ import io.element.android.libraries.ui.strings.CommonStrings fun PermissionsView( state: PermissionsState, modifier: Modifier = Modifier, + title: String = stringResource(id = CommonStrings.common_permission), + icon: @Composable (() -> Unit)? = null, ) { if (state.showDialog.not()) return ConfirmationDialog( modifier = modifier, - title = stringResource(id = CommonStrings.common_permission), + title = title, content = state.permission.toDialogContent(), submitText = stringResource(id = CommonStrings.action_open_settings), onSubmitClicked = { state.eventSink.invoke(PermissionsEvents.OpenSystemSettingAndCloseDialog) }, onDismiss = { state.eventSink.invoke(PermissionsEvents.CloseDialog) }, + icon = icon, ) } 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 9cc0889eac6..c7ba8e41f26 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 @@ -16,18 +16,25 @@ package io.element.android.libraries.qrcode +import android.graphics.Bitmap import androidx.camera.core.CameraSelector import androidx.camera.core.ImageAnalysis import androidx.camera.core.Preview import androidx.camera.lifecycle.ProcessCameraProvider import androidx.camera.view.PreviewView +import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clipToBounds +import androidx.compose.ui.graphics.ImageBitmap +import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalInspectionMode import androidx.compose.ui.platform.LocalLifecycleOwner @@ -41,6 +48,7 @@ import timber.log.Timber fun QrCodeCameraView( onQrCodeScanned: (String) -> Unit, modifier: Modifier = Modifier, + renderPreview: Boolean = true, ) { if (LocalInspectionMode.current) { Box( @@ -54,43 +62,60 @@ fun QrCodeCameraView( val localContext = LocalContext.current val lifecycleOwner = LocalLifecycleOwner.current val cameraProviderFuture = remember { ProcessCameraProvider.getInstance(localContext) } - AndroidView( - modifier = modifier.clipToBounds(), - factory = { context -> - val previewView = PreviewView(context) - val preview = Preview.Builder() - .build() - val selector = CameraSelector.Builder() - .requireLensFacing(CameraSelector.LENS_FACING_BACK) - .build() - preview.setSurfaceProvider(previewView.surfaceProvider) - val imageAnalysis = ImageAnalysis.Builder() - .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST) - .build() - imageAnalysis.setAnalyzer( - ContextCompat.getMainExecutor(context), - QRCodeAnalyzer { result -> - result?.let { - Timber.d("QR code scanned!") - onQrCodeScanned(it) + val previewUseCase = remember { Preview.Builder().build() } + var lastFrame by remember { mutableStateOf(null) } + Box(modifier.clipToBounds()) { + AndroidView( + factory = { context -> + val previewView = PreviewView(context) + previewView + }, + update = { previewView -> + if (renderPreview) { + lastFrame = null + val selector = CameraSelector.Builder() + .requireLensFacing(CameraSelector.LENS_FACING_BACK) + .build() + previewUseCase.setSurfaceProvider(previewView.surfaceProvider) + val imageAnalysis = ImageAnalysis.Builder() + .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST) + .build() + imageAnalysis.setAnalyzer( + ContextCompat.getMainExecutor(previewView.context), + QRCodeAnalyzer { result -> + result?.let { + Timber.d("QR code scanned!") + onQrCodeScanned(it) + } + } + ) + try { + cameraProviderFuture.get().bindToLifecycle( + lifecycleOwner, + selector, + previewUseCase, + imageAnalysis + ) + lastFrame = null + } catch (e: Exception) { + Timber.e(e, "Use case binding failed") } + } else { + // Save last frame to display it as the 'frozen' preview + if (lastFrame == null) { + lastFrame = previewView.bitmap + Timber.d("Saving last frame. Is null? ${lastFrame == null}") + } + cameraProviderFuture.get().unbind(previewUseCase) } - ) - try { - cameraProviderFuture.get().bindToLifecycle( - lifecycleOwner, - selector, - preview, - imageAnalysis - ) - } catch (e: Exception) { - Timber.e(e, "Use case binding failed") - } - previewView - }, - onRelease = { - cameraProviderFuture.get().unbindAll() - }, - ) + }, + onRelease = { + cameraProviderFuture.get().unbindAll() + }, + ) + lastFrame?.let { + Image(bitmap = it.asImageBitmap(), contentDescription = null) + } + } } } diff --git a/libraries/ui-strings/src/main/res/values/localazy.xml b/libraries/ui-strings/src/main/res/values/localazy.xml index f19696065ab..c927eec7cd9 100644 --- a/libraries/ui-strings/src/main/res/values/localazy.xml +++ b/libraries/ui-strings/src/main/res/values/localazy.xml @@ -245,6 +245,12 @@ "Hey, talk to me on %1$s: %2$s" "%1$s Android" "Rageshake to report bug" + "Join room" + "Knock to join" + "Click the button below and a room administrator will be notified. You’ll be able to join the conversation once approved." + "You must be a member of this room to view the message history." + "Want to join this room?" + "Preview is not available" "Failed selecting media, please try again." "Failed processing media to upload, please try again." "Failed uploading media, please try again." diff --git a/tools/localazy/config.json b/tools/localazy/config.json index ac9c773e1a1..4c7c659712a 100644 --- a/tools/localazy/config.json +++ b/tools/localazy/config.json @@ -114,7 +114,8 @@ "screen_change_server_.*", "screen_change_account_provider_.*", "screen_account_provider_.*", - "screen_waitlist_.*" + "screen_waitlist_.*", + "screen_qr_code_login_.*" ] }, { From 6f36885a31fafc142ff58826d92ecfbdd66b2ebc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= Date: Thu, 11 Apr 2024 13:55:26 +0200 Subject: [PATCH 06/60] Add new code confirmation and connection not secure screens --- .../impl/src/main/res/values/localazy.xml | 13 +- .../login/impl/oidc/webview/OidcView.kt | 2 +- .../confirmation/QrCodeConfirmationNode.kt | 9 + .../QrCodeConfirmationPresenter.kt | 59 +++++++ .../confirmation/QrCodeConfirmationState.kt | 26 +++ .../confirmation/QrCodeConfirmationView.kt | 158 ++++++++++++++++++ .../QrConfirmationCodeStatePreviewProvider.kt | 34 ++++ .../QrCodeConnectionNotSecureNode.kt | 34 ++++ .../QrCodeConnectionNotSecureView.kt | 105 ++++++++++++ .../screens/qrcode/intro/QrCodeIntroView.kt | 7 +- .../screens/qrcode/scan/QrCodeScanView.kt | 3 +- .../impl/src/main/res/values/localazy.xml | 13 +- .../features/logout/impl/LogoutView.kt | 3 +- .../impl/root/PreferencesRootView.kt | 10 +- .../impl/disable/SecureBackupDisableView.kt | 3 +- .../impl/enable/SecureBackupEnableView.kt | 3 +- .../enter/SecureBackupEnterRecoveryKeyView.kt | 3 +- .../impl/setup/SecureBackupSetupView.kt | 3 +- .../designsystem/atomic/pages/FlowStepPage.kt | 6 +- .../permissions/api/PermissionsView.kt | 3 +- .../src/main/res/values/localazy.xml | 1 + 21 files changed, 480 insertions(+), 18 deletions(-) create mode 100644 features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/confirmation/QrCodeConfirmationPresenter.kt create mode 100644 features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/confirmation/QrCodeConfirmationState.kt create mode 100644 features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/confirmation/QrCodeConfirmationView.kt create mode 100644 features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/confirmation/QrConfirmationCodeStatePreviewProvider.kt create mode 100644 features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/connectionnotsecure/QrCodeConnectionNotSecureNode.kt create mode 100644 features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/connectionnotsecure/QrCodeConnectionNotSecureView.kt diff --git a/features/ftue/impl/src/main/res/values/localazy.xml b/features/ftue/impl/src/main/res/values/localazy.xml index 8c43ef0a812..eff55a1e2e1 100644 --- a/features/ftue/impl/src/main/res/values/localazy.xml +++ b/features/ftue/impl/src/main/res/values/localazy.xml @@ -3,6 +3,12 @@ "You can change your settings later." "Allow notifications and never miss a message" "Establishing connection" + "A secure connection could not be made to the new device. Your existing devices are still safe and you don\'t need to worry about them." + "What now?" + "Try signing in again with a QR code in case this was a network problem" + "If you encounter the same problem, try a different wifi network or use your mobile data instead of wifi" + "If that doesn’t work, sign in manually" + "Connection not secure" "Open %1$s on a desktop device" "Click on your avatar" "Select %1$s" @@ -11,9 +17,14 @@ "“Show QR code”" "Open %1$s on another device to get the QR code" "Use the QR code shown on the other device." - "Try Again" + "Try again" "Wrong QR code" + "Go to camera settings" + "You need to give permission for Element to use your device’s camera in order to continue." + "Allow camera access to scan the QR code" "Scan the QR code" + "Start over" + "An unexpected error occurred. Please try again." "Calls, polls, search and more will be added later this year." "Message history for encrypted rooms isn’t available yet." "We’d love to hear from you, let us know what you think via the settings page." diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/oidc/webview/OidcView.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/oidc/webview/OidcView.kt index fb86ae18a57..c07078cdff7 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/oidc/webview/OidcView.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/oidc/webview/OidcView.kt @@ -89,6 +89,6 @@ fun OidcView( internal fun OidcViewPreview(@PreviewParameter(OidcStateProvider::class) state: OidcState) = ElementPreview { OidcView( state = state, - onNavigateBack = { }, + onNavigateBack = {}, ) } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/confirmation/QrCodeConfirmationNode.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/confirmation/QrCodeConfirmationNode.kt index 7d9fcda8688..2f8d81bdf72 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/confirmation/QrCodeConfirmationNode.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/confirmation/QrCodeConfirmationNode.kt @@ -16,6 +16,8 @@ package io.element.android.features.login.impl.screens.qrcode.confirmation +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.plugin.Plugin @@ -28,5 +30,12 @@ import io.element.android.libraries.di.AppScope class QrCodeConfirmationNode @AssistedInject constructor( @Assisted buildContext: BuildContext, @Assisted plugins: List, + private val presenter: QrCodeConfirmationPresenter, ) : Node(buildContext = buildContext, plugins = plugins) { + + @Composable + override fun View(modifier: Modifier) { + val state = presenter.present() + QrCodeConfirmationView(state = state) + } } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/confirmation/QrCodeConfirmationPresenter.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/confirmation/QrCodeConfirmationPresenter.kt new file mode 100644 index 00000000000..a3db13ba940 --- /dev/null +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/confirmation/QrCodeConfirmationPresenter.kt @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.login.impl.screens.qrcode.confirmation + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.produceState +import io.element.android.features.login.api.oidc.OidcAction +import io.element.android.features.login.impl.oidc.customtab.DefaultOidcActionFlow +import io.element.android.libraries.architecture.Presenter +import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService +import javax.inject.Inject + +class QrCodeConfirmationPresenter @Inject constructor( + private val oidcActionFlow: DefaultOidcActionFlow, + private val authenticationService: MatrixAuthenticationService, +) : Presenter { + @Composable + override fun present(): QrCodeConfirmationState { + LaunchedEffect(Unit) { + observeOidcAction() + } + return QrCodeConfirmationState(QrCodeConfirmationStep.DisplayCheckCode("12")) + } + + private suspend fun observeOidcAction() { + oidcActionFlow.collect { + when (it) { + null -> Unit + is OidcAction.Success -> onOidcSuccess(it.url) + is OidcAction.GoBack -> { + + } + } + } + } + + private suspend fun onOidcSuccess(url: String) { + authenticationService.loginWithOidc(url) + .onSuccess { sessionId -> + + } + } +} diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/confirmation/QrCodeConfirmationState.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/confirmation/QrCodeConfirmationState.kt new file mode 100644 index 00000000000..70615f4fd0f --- /dev/null +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/confirmation/QrCodeConfirmationState.kt @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.login.impl.screens.qrcode.confirmation + +data class QrCodeConfirmationState( + val step: QrCodeConfirmationStep, +) + +sealed interface QrCodeConfirmationStep { + data class DisplayCheckCode(val code: String) : QrCodeConfirmationStep + data class DisplayVerificationCode(val code: String) : QrCodeConfirmationStep +} diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/confirmation/QrCodeConfirmationView.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/confirmation/QrCodeConfirmationView.kt new file mode 100644 index 00000000000..823ab615551 --- /dev/null +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/confirmation/QrCodeConfirmationView.kt @@ -0,0 +1,158 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.login.impl.screens.qrcode.confirmation + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ExperimentalLayoutApi +import androidx.compose.foundation.layout.FlowRow +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.PreviewParameter +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.libraries.designsystem.atomic.pages.FlowStepPage +import io.element.android.libraries.designsystem.components.BigIcon +import io.element.android.libraries.designsystem.preview.ElementPreview +import io.element.android.libraries.designsystem.preview.PreviewsDayNight +import io.element.android.libraries.designsystem.theme.components.CircularProgressIndicator +import io.element.android.libraries.designsystem.theme.components.OutlinedButton +import io.element.android.libraries.designsystem.theme.components.Text +import io.element.android.libraries.ui.strings.CommonStrings + +@Composable +fun QrCodeConfirmationView( + state: QrCodeConfirmationState, + modifier: Modifier = Modifier, +) { + val icon = when (state.step) { + is QrCodeConfirmationStep.DisplayCheckCode -> CompoundIcons.Computer() + is QrCodeConfirmationStep.DisplayVerificationCode -> CompoundIcons.LockSolid() + } + // TODO: localazy + val title = when (state.step) { + is QrCodeConfirmationStep.DisplayCheckCode -> "Enter the number below on your device" + is QrCodeConfirmationStep.DisplayVerificationCode -> "Your verification code" + } + // TODO: localazy + val subtitle = when (state.step) { + is QrCodeConfirmationStep.DisplayCheckCode -> "You’ll be asked to enter the two digits shown below." + is QrCodeConfirmationStep.DisplayVerificationCode -> "Your account provider may ask for the following code to verify the sign in." + } + FlowStepPage( + modifier = modifier, + iconStyle = BigIcon.Style.Default(icon), + title = title, + subTitle = subtitle, + content = { Content(state = state) }, + buttons = { Buttons(state = state) } + ) +} + +@Composable +private fun Content(state: QrCodeConfirmationState) { + Column( + modifier = Modifier.padding(top = 16.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + when (state.step) { + is QrCodeConfirmationStep.DisplayCheckCode -> { + Digits(code = state.step.code) + Spacer(modifier = Modifier.height(32.dp)) + WaitingForOtherDevice() + } + is QrCodeConfirmationStep.DisplayVerificationCode -> { + Digits(code = state.step.code) + Spacer(modifier = Modifier.height(32.dp)) + WaitingForOtherDevice() + } + } + } +} + +@OptIn(ExperimentalLayoutApi::class) +@Composable +private fun Digits(code: String) { + FlowRow( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.Center, + ) { + code.forEach { + Text( + modifier = Modifier + .padding(horizontal = 6.dp, vertical = 4.dp) + .clip(RoundedCornerShape(4.dp)) + .background(ElementTheme.colors.bgActionSecondaryPressed) + .padding(horizontal = 16.dp, vertical = 17.dp), + text = it.toString() + ) + } + } +} + +@Composable +private fun WaitingForOtherDevice() { + Column( + modifier = Modifier.fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(4.dp), + ) { + CircularProgressIndicator( + modifier = Modifier + .size(20.dp) + .padding(2.dp), + strokeWidth = 2.dp, + ) + // TODO: localazy + Text( + text = "Waiting for your other device", + style = ElementTheme.typography.fontBodySmRegular, + color = ElementTheme.colors.textSecondary, + textAlign = TextAlign.Center, + ) + } +} + +@Composable +private fun Buttons(state: QrCodeConfirmationState) { + Column(modifier = Modifier.fillMaxWidth()) { + OutlinedButton( + modifier = Modifier.fillMaxWidth(), + text = stringResource(CommonStrings.action_cancel), onClick = { /*TODO*/ } + ) + } +} + +@PreviewsDayNight +@Composable +internal fun QrCodeConfirmationViewPreview(@PreviewParameter(QrConfirmationCodeStatePreviewProvider::class) state: QrCodeConfirmationState) { + ElementPreview { + QrCodeConfirmationView(state = state) + } +} diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/confirmation/QrConfirmationCodeStatePreviewProvider.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/confirmation/QrConfirmationCodeStatePreviewProvider.kt new file mode 100644 index 00000000000..f7ae2e686e8 --- /dev/null +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/confirmation/QrConfirmationCodeStatePreviewProvider.kt @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.login.impl.screens.qrcode.confirmation + +import androidx.compose.ui.tooling.preview.PreviewParameterProvider + +class QrConfirmationCodeStatePreviewProvider : PreviewParameterProvider { + override val values: Sequence + get() = sequenceOf( + QrCodeConfirmationState( + step = QrCodeConfirmationStep.DisplayCheckCode("12"), + ), + QrCodeConfirmationState( + step = QrCodeConfirmationStep.DisplayVerificationCode("123456"), + ), + QrCodeConfirmationState( + step = QrCodeConfirmationStep.DisplayVerificationCode("123456789"), + ), + ) +} diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/connectionnotsecure/QrCodeConnectionNotSecureNode.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/connectionnotsecure/QrCodeConnectionNotSecureNode.kt new file mode 100644 index 00000000000..2c562ad1568 --- /dev/null +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/connectionnotsecure/QrCodeConnectionNotSecureNode.kt @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.login.impl.screens.qrcode.connectionnotsecure + +import com.bumble.appyx.core.modality.BuildContext +import com.bumble.appyx.core.node.Node +import com.bumble.appyx.core.plugin.Plugin +import dagger.assisted.Assisted +import dagger.assisted.AssistedInject +import io.element.android.anvilannotations.ContributesNode +import io.element.android.libraries.di.AppScope + +@ContributesNode(AppScope::class) +class QrCodeConnectionNotSecureNode @AssistedInject constructor( + @Assisted buildContext: BuildContext, + @Assisted plugins: List, +) : Node(buildContext = buildContext, plugins = plugins +) { + +} diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/connectionnotsecure/QrCodeConnectionNotSecureView.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/connectionnotsecure/QrCodeConnectionNotSecureView.kt new file mode 100644 index 00000000000..af62bbc1b6f --- /dev/null +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/connectionnotsecure/QrCodeConnectionNotSecureView.kt @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.login.impl.screens.qrcode.connectionnotsecure + +import androidx.activity.compose.BackHandler +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import io.element.android.compound.theme.ElementTheme +import io.element.android.features.login.impl.R +import io.element.android.libraries.designsystem.atomic.organisms.NumberedListOrganism +import io.element.android.libraries.designsystem.atomic.pages.FlowStepPage +import io.element.android.libraries.designsystem.components.BigIcon +import io.element.android.libraries.designsystem.preview.ElementPreview +import io.element.android.libraries.designsystem.preview.PreviewsDayNight +import io.element.android.libraries.designsystem.theme.components.Button +import io.element.android.libraries.designsystem.theme.components.Text +import kotlinx.collections.immutable.persistentListOf + +@Composable +fun QrCodeConnectionNotSecureView( + modifier: Modifier = Modifier, + onRetry: () -> Unit, +) { + BackHandler { + onRetry() + } + FlowStepPage( + modifier = modifier, + iconStyle = BigIcon.Style.AlertSolid, + title = stringResource(R.string.screen_qr_code_login_connection_note_secure_state_title), + subTitle = stringResource(R.string.screen_qr_code_login_connection_note_secure_state_description), + content = { Content() }, + buttons = { Buttons(onRetry) } + ) +} + +@Composable +private fun Content() { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(top = 20.dp) + .verticalScroll(rememberScrollState()), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(24.dp) + ) { + Text( + modifier = Modifier.fillMaxWidth(), + text = stringResource(R.string.screen_qr_code_login_connection_note_secure_state_list_header), + style = ElementTheme.typography.fontBodyLgMedium, + textAlign = TextAlign.Center, + ) + NumberedListOrganism( + items = persistentListOf( + AnnotatedString(stringResource(R.string.screen_qr_code_login_connection_note_secure_state_list_item_1)), + AnnotatedString(stringResource(R.string.screen_qr_code_login_connection_note_secure_state_list_item_2)), + AnnotatedString(stringResource(R.string.screen_qr_code_login_connection_note_secure_state_list_item_3)), + ) + ) + } +} + +@Composable +private fun Buttons(onRetry: () -> Unit) { + Button( + modifier = Modifier.fillMaxWidth(), + text = stringResource(R.string.screen_qr_code_login_start_over_button), + onClick = onRetry + ) +} + +@PreviewsDayNight +@Composable +internal fun QrCodeConnectionNotSecureViewPreview() { + ElementPreview { + QrCodeConnectionNotSecureView( + onRetry = {} + ) + } +} diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/intro/QrCodeIntroView.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/intro/QrCodeIntroView.kt index b4ef2dd7bf6..453a36bc8f8 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/intro/QrCodeIntroView.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/intro/QrCodeIntroView.kt @@ -32,6 +32,7 @@ import io.element.android.compound.tokens.generated.CompoundIcons import io.element.android.features.login.impl.R import io.element.android.libraries.designsystem.atomic.organisms.NumberedListOrganism import io.element.android.libraries.designsystem.atomic.pages.FlowStepPage +import io.element.android.libraries.designsystem.components.BigIcon import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.theme.components.Button @@ -57,15 +58,15 @@ fun QrCodeIntroView( FlowStepPage( modifier = modifier, onBackClicked = onBackClicked, - iconVector = CompoundIcons.Computer(), + iconStyle = BigIcon.Style.Default(CompoundIcons.Computer()), title = stringResource(id = R.string.screen_qr_code_login_initial_state_title, state.desktopAppName), content = { Content(state = state) }, buttons = { Buttons(state = state) } ) PermissionsView( - // TODO: localazy - title = "Allow camera access to scan the QR code", + title = stringResource(R.string.screen_qr_code_login_no_camera_permission_state_title), + content = stringResource(R.string.screen_qr_code_login_no_camera_permission_state_description), icon = { Icon(imageVector = CompoundIcons.TakePhotoSolid(), contentDescription = null) }, state = state.cameraPermissionState, ) 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 728055532ad..8c524e25e5a 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 @@ -45,6 +45,7 @@ import io.element.android.compound.tokens.generated.CompoundIcons import io.element.android.features.login.impl.R import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.designsystem.atomic.pages.FlowStepPage +import io.element.android.libraries.designsystem.components.BigIcon import io.element.android.libraries.designsystem.modifiers.cornerBorder import io.element.android.libraries.designsystem.modifiers.squareSize import io.element.android.libraries.designsystem.preview.ElementPreview @@ -66,7 +67,7 @@ fun QrCodeScanView( FlowStepPage( modifier = modifier, onBackClicked = onBackClicked, - iconVector = CompoundIcons.Computer(), + iconStyle = BigIcon.Style.Default(CompoundIcons.Computer()), title = stringResource(R.string.screen_qr_code_login_scanning_state_title), content = { Content(state = state) }, buttons = { Buttons(state = state) } diff --git a/features/login/impl/src/main/res/values/localazy.xml b/features/login/impl/src/main/res/values/localazy.xml index e35f8b4011f..0af66431730 100644 --- a/features/login/impl/src/main/res/values/localazy.xml +++ b/features/login/impl/src/main/res/values/localazy.xml @@ -31,6 +31,12 @@ "Welcome back!" "Sign in to %1$s" "Establishing connection" + "A secure connection could not be made to the new device. Your existing devices are still safe and you don\'t need to worry about them." + "What now?" + "Try signing in again with a QR code in case this was a network problem" + "If you encounter the same problem, try a different wifi network or use your mobile data instead of wifi" + "If that doesn’t work, sign in manually" + "Connection not secure" "Open %1$s on a desktop device" "Click on your avatar" "Select %1$s" @@ -39,9 +45,14 @@ "“Show QR code”" "Open %1$s on another device to get the QR code" "Use the QR code shown on the other device." - "Try Again" + "Try again" "Wrong QR code" + "Go to camera settings" + "You need to give permission for Element to use your device’s camera in order to continue." + "Allow camera access to scan the QR code" "Scan the QR code" + "Start over" + "An unexpected error occurred. Please try again." "Change account provider" "A private server for Element employees." "Matrix is an open network for secure, decentralised communication." diff --git a/features/logout/impl/src/main/kotlin/io/element/android/features/logout/impl/LogoutView.kt b/features/logout/impl/src/main/kotlin/io/element/android/features/logout/impl/LogoutView.kt index 19160236ac8..3f841332353 100644 --- a/features/logout/impl/src/main/kotlin/io/element/android/features/logout/impl/LogoutView.kt +++ b/features/logout/impl/src/main/kotlin/io/element/android/features/logout/impl/LogoutView.kt @@ -33,6 +33,7 @@ import io.element.android.features.logout.impl.tools.isBackingUp import io.element.android.features.logout.impl.ui.LogoutActionDialog import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.designsystem.atomic.pages.FlowStepPage +import io.element.android.libraries.designsystem.components.BigIcon import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.theme.components.Button @@ -62,7 +63,7 @@ fun LogoutView( onBackClicked = onBackClicked, title = title(state), subTitle = subtitle(state), - iconVector = CompoundIcons.KeySolid(), + iconStyle = BigIcon.Style.Default(CompoundIcons.KeySolid()), modifier = modifier, content = { Content(state) }, buttons = { diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootView.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootView.kt index 21132df5c9d..50a880ac78a 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootView.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootView.kt @@ -81,15 +81,21 @@ fun PreferencesRootView( }, user = state.myUser, ) + ListItem( + // TODO: localazy + headlineContent = { Text("Link new device") }, + leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.QrCode())), + onClick = { TODO() } + ) if (state.showSecureBackup) { ListItem( headlineContent = { Text(stringResource(id = CommonStrings.common_chat_backup)) }, - leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.KeySolid())), + leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.Key())), trailingContent = ListItemContent.Badge.takeIf { state.showSecureBackupBadge }, onClick = onSecureBackupClicked, ) - HorizontalDivider() } + HorizontalDivider() if (state.accountManagementUrl != null) { ListItem( headlineContent = { Text(stringResource(id = CommonStrings.action_manage_account)) }, diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/disable/SecureBackupDisableView.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/disable/SecureBackupDisableView.kt index 37fc3eb2bef..c0652af8f13 100644 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/disable/SecureBackupDisableView.kt +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/disable/SecureBackupDisableView.kt @@ -32,6 +32,7 @@ import io.element.android.compound.theme.ElementTheme import io.element.android.compound.tokens.generated.CompoundIcons import io.element.android.features.securebackup.impl.R import io.element.android.libraries.designsystem.atomic.pages.FlowStepPage +import io.element.android.libraries.designsystem.components.BigIcon import io.element.android.libraries.designsystem.components.async.AsyncActionView import io.element.android.libraries.designsystem.components.dialogs.ConfirmationDialog import io.element.android.libraries.designsystem.preview.ElementPreview @@ -52,7 +53,7 @@ fun SecureBackupDisableView( onBackClicked = onBackClicked, title = stringResource(id = R.string.screen_key_backup_disable_title), subTitle = stringResource(id = R.string.screen_key_backup_disable_description), - iconVector = CompoundIcons.KeyOffSolid(), + iconStyle = BigIcon.Style.Default(CompoundIcons.KeyOffSolid()), content = { Content(state = state) }, buttons = { Buttons(state = state) }, ) diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/enable/SecureBackupEnableView.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/enable/SecureBackupEnableView.kt index 53748d38790..8b8506e5f83 100644 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/enable/SecureBackupEnableView.kt +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/enable/SecureBackupEnableView.kt @@ -25,6 +25,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameter import io.element.android.compound.tokens.generated.CompoundIcons import io.element.android.features.securebackup.impl.R import io.element.android.libraries.designsystem.atomic.pages.FlowStepPage +import io.element.android.libraries.designsystem.components.BigIcon import io.element.android.libraries.designsystem.components.async.AsyncActionView import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight @@ -41,7 +42,7 @@ fun SecureBackupEnableView( modifier = modifier, onBackClicked = onBackClicked, title = stringResource(id = R.string.screen_chat_backup_key_backup_action_enable), - iconVector = CompoundIcons.KeySolid(), + iconStyle = BigIcon.Style.Default(CompoundIcons.KeySolid()), buttons = { Buttons(state = state) } ) AsyncActionView( diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/enter/SecureBackupEnterRecoveryKeyView.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/enter/SecureBackupEnterRecoveryKeyView.kt index b74078c7f29..f7ffd80d47f 100644 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/enter/SecureBackupEnterRecoveryKeyView.kt +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/enter/SecureBackupEnterRecoveryKeyView.kt @@ -28,6 +28,7 @@ import io.element.android.compound.tokens.generated.CompoundIcons import io.element.android.features.securebackup.impl.R import io.element.android.features.securebackup.impl.setup.views.RecoveryKeyView import io.element.android.libraries.designsystem.atomic.pages.FlowStepPage +import io.element.android.libraries.designsystem.components.BigIcon import io.element.android.libraries.designsystem.components.async.AsyncActionView import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight @@ -53,7 +54,7 @@ fun SecureBackupEnterRecoveryKeyView( FlowStepPage( modifier = modifier, onBackClicked = onBackClicked, - iconVector = CompoundIcons.KeySolid(), + iconStyle = BigIcon.Style.Default(CompoundIcons.KeySolid()), title = stringResource(id = R.string.screen_recovery_key_confirm_title), subTitle = stringResource(id = R.string.screen_recovery_key_confirm_description), content = { Content(state = state) }, diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/setup/SecureBackupSetupView.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/setup/SecureBackupSetupView.kt index 03d4f80b979..aee2b2ebb3a 100644 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/setup/SecureBackupSetupView.kt +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/setup/SecureBackupSetupView.kt @@ -31,6 +31,7 @@ import io.element.android.features.securebackup.impl.setup.views.RecoveryKeyView import io.element.android.libraries.androidutils.system.copyToClipboard import io.element.android.libraries.androidutils.system.startSharePlainTextIntent import io.element.android.libraries.designsystem.atomic.pages.FlowStepPage +import io.element.android.libraries.designsystem.components.BigIcon import io.element.android.libraries.designsystem.components.dialogs.ConfirmationDialog import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight @@ -51,7 +52,7 @@ fun SecureBackupSetupView( onBackClicked = onBackClicked.takeIf { state.canGoBack() }, title = title(state), subTitle = subtitle(state), - iconVector = CompoundIcons.KeySolid(), + iconStyle = BigIcon.Style.Default(CompoundIcons.KeySolid()), content = { Content(state) }, buttons = { Buttons(state, onDone = onDone) }, ) diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/pages/FlowStepPage.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/pages/FlowStepPage.kt index 9f8f8dad92e..0a020e22b43 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/pages/FlowStepPage.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/pages/FlowStepPage.kt @@ -51,7 +51,7 @@ import io.element.android.libraries.designsystem.theme.components.TopAppBar @OptIn(ExperimentalMaterial3Api::class) @Composable fun FlowStepPage( - iconVector: ImageVector, + iconStyle: BigIcon.Style, title: String, modifier: Modifier = Modifier, onBackClicked: (() -> Unit)? = null, @@ -78,7 +78,7 @@ fun FlowStepPage( PageTitle( title = title, subtitle = subTitle, - iconStyle = BigIcon.Style.Default(iconVector) , + iconStyle = iconStyle, ) }, content = content, @@ -99,7 +99,7 @@ internal fun FlowStepPagePreview() = ElementPreview { onBackClicked = {}, title = "Title", subTitle = "Subtitle", - iconVector = CompoundIcons.Computer(), + iconStyle = BigIcon.Style.Default(CompoundIcons.Computer()), content = { Box( Modifier diff --git a/libraries/permissions/api/src/main/kotlin/io/element/android/libraries/permissions/api/PermissionsView.kt b/libraries/permissions/api/src/main/kotlin/io/element/android/libraries/permissions/api/PermissionsView.kt index aef419a56b5..1f3412177ee 100644 --- a/libraries/permissions/api/src/main/kotlin/io/element/android/libraries/permissions/api/PermissionsView.kt +++ b/libraries/permissions/api/src/main/kotlin/io/element/android/libraries/permissions/api/PermissionsView.kt @@ -31,6 +31,7 @@ fun PermissionsView( state: PermissionsState, modifier: Modifier = Modifier, title: String = stringResource(id = CommonStrings.common_permission), + content: String? = null, icon: @Composable (() -> Unit)? = null, ) { if (state.showDialog.not()) return @@ -38,7 +39,7 @@ fun PermissionsView( ConfirmationDialog( modifier = modifier, title = title, - content = state.permission.toDialogContent(), + content = content ?: state.permission.toDialogContent(), submitText = stringResource(id = CommonStrings.action_open_settings), onSubmitClicked = { state.eventSink.invoke(PermissionsEvents.OpenSystemSettingAndCloseDialog) diff --git a/libraries/ui-strings/src/main/res/values/localazy.xml b/libraries/ui-strings/src/main/res/values/localazy.xml index c927eec7cd9..8e0e463a3a9 100644 --- a/libraries/ui-strings/src/main/res/values/localazy.xml +++ b/libraries/ui-strings/src/main/res/values/localazy.xml @@ -200,6 +200,7 @@ "Settings" "Shared location" "Signing out" + "Something went wrong" "Starting chat…" "Sticker" "Success" From 2a3084220c84ac3be68e8f9805782834a44c1322 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= Date: Fri, 12 Apr 2024 13:50:55 +0200 Subject: [PATCH 07/60] WIP: start integrating Rust SDK APIs. This needs `jme/qr-login-for-testing-with-clients` branch in the rust-sdk project --- .../screens/qrcode/scan/QrCodeScanEvents.kt | 2 +- .../qrcode/scan/QrCodeScanPresenter.kt | 11 +++++-- .../screens/qrcode/scan/QrCodeScanView.kt | 2 +- .../api/auth/MatrixAuthenticationService.kt | 2 ++ .../auth/RustMatrixAuthenticationService.kt | 32 +++++++++++++++++++ .../libraries/qrcode/QRCodeAnalyzer.kt | 8 +++-- .../libraries/qrcode/QrCodeCameraView.kt | 9 ++++-- 7 files changed, 57 insertions(+), 9 deletions(-) diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/scan/QrCodeScanEvents.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/scan/QrCodeScanEvents.kt index c66114c3d49..d4b24d68d6f 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/scan/QrCodeScanEvents.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/scan/QrCodeScanEvents.kt @@ -17,6 +17,6 @@ package io.element.android.features.login.impl.screens.qrcode.scan sealed interface QrCodeScanEvents { - data class QrCodeScanned(val code: String) : QrCodeScanEvents + data class QrCodeScanned(val code: ByteArray) : QrCodeScanEvents data object TryAgain : QrCodeScanEvents } 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 154304278ca..595214bc037 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 @@ -36,10 +36,11 @@ class QrCodeScanPresenter @Inject constructor( private val authenticationService: MatrixAuthenticationService, ) : Presenter { + private var isScanning by mutableStateOf(true) + @Composable override fun present(): QrCodeScanState { val coroutineScope = rememberCoroutineScope() - var isScanning by remember { mutableStateOf(true) } val authenticationAction: MutableState> = remember { mutableStateOf(AsyncAction.Uninitialized) } fun handleEvents(event: QrCodeScanEvents) { @@ -62,10 +63,14 @@ class QrCodeScanPresenter @Inject constructor( ) } - private fun CoroutineScope.authenticateWithCode(authenticationAction: MutableState>, code: String) = launch { + private fun CoroutineScope.authenticateWithCode(authenticationAction: MutableState>, code: ByteArray) = launch { suspend { // TODO Call the SDK API, for now, just simulate an error - delay(3000) + val result = authenticationService.loginWithQrCode(code) + .onFailure { + isScanning = true + } +// delay(3000) // throw Exception("Simulated error $code") }.runCatchingUpdatingState(authenticationAction) } 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 8c524e25e5a..a0efdc88af7 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 @@ -104,7 +104,7 @@ private fun Content( modifier = Modifier .fillMaxSize() // TODO: FOR TESTING ONLY, REMOVE THIS - .clickable { state.eventSink.invoke(QrCodeScanEvents.QrCodeScanned("ASDA")) }, + .clickable { state.eventSink.invoke(QrCodeScanEvents.QrCodeScanned(byteArrayOf(1))) }, onQrCodeScanned = { state.eventSink.invoke(QrCodeScanEvents.QrCodeScanned(it)) }, renderPreview = state.isScanning, ) diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/MatrixAuthenticationService.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/MatrixAuthenticationService.kt index 501b40508e6..a509df2c2c3 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/MatrixAuthenticationService.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/MatrixAuthenticationService.kt @@ -53,4 +53,6 @@ interface MatrixAuthenticationService { * Attempt to login using the [callbackUrl] provided by the Oidc page. */ suspend fun loginWithOidc(callbackUrl: String): Result + + suspend fun loginWithQrCode(qrCodeBytes: ByteArray): Result } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationService.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationService.kt index ad34e30d189..7e9be1a8db5 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationService.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationService.kt @@ -42,7 +42,12 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.withContext +import org.matrix.rustcomponents.sdk.ClientBuilder import org.matrix.rustcomponents.sdk.OidcAuthenticationData +import org.matrix.rustcomponents.sdk.OidcConfiguration +import org.matrix.rustcomponents.sdk.QrCodeData +import org.matrix.rustcomponents.sdk.QrLoginProgress +import org.matrix.rustcomponents.sdk.QrLoginProgressListener import org.matrix.rustcomponents.sdk.use import timber.log.Timber import java.io.File @@ -209,4 +214,31 @@ class RustMatrixAuthenticationService @Inject constructor( } } } + + override suspend fun loginWithQrCode(qrCodeBytes: ByteArray) = runCatching { + val decodedQrData = QrCodeData.fromBytes(qrCodeBytes) + val client = ClientBuilder().buildWithQrCode( + qrCodeData = decodedQrData, + oidcConfiguration = oidcConfiguration, + progressListener = object : QrLoginProgressListener { + override fun onUpdate(state: QrLoginProgress) { + println("QR Code login progress: $state") + } + } + ) + val sessionData = client.use { + it.session().toSessionData( + isTokenValid = true, + loginType = LoginType.OIDC, + passphrase = pendingPassphrase, + needsVerification = true, + ) + } + pendingOidcAuthenticationData?.close() + pendingOidcAuthenticationData = null + sessionStore.storeData(sessionData) + SessionId(sessionData.userId) + }.onFailure { + it.printStackTrace() + } } diff --git a/libraries/qrcode/src/main/kotlin/io/element/android/libraries/qrcode/QRCodeAnalyzer.kt b/libraries/qrcode/src/main/kotlin/io/element/android/libraries/qrcode/QRCodeAnalyzer.kt index d093e8f4ff3..965ec32e3df 100644 --- a/libraries/qrcode/src/main/kotlin/io/element/android/libraries/qrcode/QRCodeAnalyzer.kt +++ b/libraries/qrcode/src/main/kotlin/io/element/android/libraries/qrcode/QRCodeAnalyzer.kt @@ -20,15 +20,17 @@ import android.graphics.ImageFormat import androidx.camera.core.ImageAnalysis import androidx.camera.core.ImageProxy import com.google.zxing.BinaryBitmap +import com.google.zxing.DecodeHintType import com.google.zxing.NotFoundException import com.google.zxing.PlanarYUVLuminanceSource +import com.google.zxing.ResultMetadataType import com.google.zxing.common.HybridBinarizer import com.google.zxing.qrcode.QRCodeReader import timber.log.Timber import java.nio.ByteBuffer internal class QRCodeAnalyzer( - private val onQrCodeScanned: (result: String?) -> Unit + private val onQrCodeScanned: (result: ByteArray?) -> Unit ) : ImageAnalysis.Analyzer { private val reader = QRCodeReader() @@ -48,7 +50,9 @@ internal class QRCodeAnalyzer( val binaryBitmap = BinaryBitmap(HybridBinarizer(source)) try { val result = reader.decode(binaryBitmap) - onQrCodeScanned(result.text) + val byteSegments = result.resultMetadata[ResultMetadataType.BYTE_SEGMENTS] as? List<*> + val contents = byteSegments?.first() as? ByteArray ?: error("No byte segments found") + onQrCodeScanned(contents) } catch (_: NotFoundException) { // No QR code found in the image } catch (e: Exception) { 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 c7ba8e41f26..9ac69d36781 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 @@ -46,7 +46,7 @@ import timber.log.Timber @Composable fun QrCodeCameraView( - onQrCodeScanned: (String) -> Unit, + onQrCodeScanned: (ByteArray) -> Unit, modifier: Modifier = Modifier, renderPreview: Boolean = true, ) { @@ -83,6 +83,7 @@ fun QrCodeCameraView( imageAnalysis.setAnalyzer( ContextCompat.getMainExecutor(previewView.context), QRCodeAnalyzer { result -> + // TODO: This probably needs a Mutex or some AtomicBoolean to prevent several onQrCodeScanned calls result?.let { Timber.d("QR code scanned!") onQrCodeScanned(it) @@ -106,7 +107,11 @@ fun QrCodeCameraView( lastFrame = previewView.bitmap Timber.d("Saving last frame. Is null? ${lastFrame == null}") } - cameraProviderFuture.get().unbind(previewUseCase) + cameraProviderFuture.get().let { cameraProvider -> + if (cameraProvider.isBound(previewUseCase)) { + cameraProvider.unbind(previewUseCase) + } + } } }, onRelease = { From 253ce120ef8bf265a89fcc69191fbc9cd5a96a49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= Date: Tue, 23 Apr 2024 12:42:58 +0200 Subject: [PATCH 08/60] Set up QR code login flow up to failure at the end --- .../features/login/impl/LoginFlowNode.kt | 43 +---- .../login/impl/qrcode/QrCodeLoginFlowNode.kt | 151 ++++++++++++++++++ .../login/impl/qrcode/QrCodeLoginPresenter.kt | 43 +++++ .../confirmation/QrCodeConfirmationNode.kt | 19 ++- .../QrCodeConfirmationPresenter.kt | 59 ------- .../confirmation/QrCodeConfirmationState.kt | 9 +- .../confirmation/QrCodeConfirmationView.kt | 33 ++-- .../QrCodeErrorNode.kt} | 4 +- .../QrCodeErrorView.kt} | 8 +- .../screens/qrcode/scan/QrCodeScanNode.kt | 9 +- .../qrcode/scan/QrCodeScanPresenter.kt | 32 ++-- .../screens/qrcode/scan/QrCodeScanState.kt | 3 +- .../qrcode/scan/QrCodeScanStateProvider.kt | 3 +- .../screens/qrcode/scan/QrCodeScanView.kt | 13 +- .../api/auth/MatrixAuthenticationService.kt | 4 +- .../api/auth/qrlogin/MatrixQrCodeLoginData.kt | 19 +++ .../qrlogin/MatrixQrCodeLoginDataFactory.kt | 21 +++ .../api/auth/qrlogin/QrCodeLoginStep.kt | 25 +++ .../auth/RustMatrixAuthenticationService.kt | 12 +- .../auth/qrlogin/QrLoginProgressExtensions.kt | 29 ++++ .../qrlogin/RustQrCodeLoginDataFactory.kt | 31 ++++ .../impl/auth/qrlogin/SdkQrCodeLoginData.kt | 24 +++ 22 files changed, 443 insertions(+), 151 deletions(-) create mode 100644 features/login/impl/src/main/kotlin/io/element/android/features/login/impl/qrcode/QrCodeLoginFlowNode.kt create mode 100644 features/login/impl/src/main/kotlin/io/element/android/features/login/impl/qrcode/QrCodeLoginPresenter.kt delete mode 100644 features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/confirmation/QrCodeConfirmationPresenter.kt rename features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/{connectionnotsecure/QrCodeConnectionNotSecureNode.kt => error/QrCodeErrorNode.kt} (88%) rename features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/{connectionnotsecure/QrCodeConnectionNotSecureView.kt => error/QrCodeErrorView.kt} (94%) create mode 100644 libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/qrlogin/MatrixQrCodeLoginData.kt create mode 100644 libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/qrlogin/MatrixQrCodeLoginDataFactory.kt create mode 100644 libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/qrlogin/QrCodeLoginStep.kt create mode 100644 libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/qrlogin/QrLoginProgressExtensions.kt create mode 100644 libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/qrlogin/RustQrCodeLoginDataFactory.kt create mode 100644 libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/qrlogin/SdkQrCodeLoginData.kt diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/LoginFlowNode.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/LoginFlowNode.kt index 88b27927ced..5306067c77e 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/LoginFlowNode.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/LoginFlowNode.kt @@ -30,7 +30,6 @@ import com.bumble.appyx.core.plugin.Plugin import com.bumble.appyx.core.plugin.plugins import com.bumble.appyx.navmodel.backstack.BackStack import com.bumble.appyx.navmodel.backstack.operation.newRoot -import com.bumble.appyx.navmodel.backstack.operation.pop import com.bumble.appyx.navmodel.backstack.operation.push import com.bumble.appyx.navmodel.backstack.operation.singleTop import dagger.assisted.Assisted @@ -43,13 +42,11 @@ import io.element.android.features.login.impl.accountprovider.AccountProviderDat import io.element.android.features.login.impl.oidc.CustomTabAvailabilityChecker import io.element.android.features.login.impl.oidc.customtab.CustomTabHandler import io.element.android.features.login.impl.oidc.webview.OidcNode +import io.element.android.features.login.impl.qrcode.QrCodeLoginFlowNode import io.element.android.features.login.impl.screens.changeaccountprovider.ChangeAccountProviderNode import io.element.android.features.login.impl.screens.confirmaccountprovider.ConfirmAccountProviderNode import io.element.android.features.login.impl.screens.loginpassword.LoginFormState import io.element.android.features.login.impl.screens.loginpassword.LoginPasswordNode -import io.element.android.features.login.impl.screens.qrcode.confirmation.QrCodeConfirmationNode -import io.element.android.features.login.impl.screens.qrcode.intro.QrCodeIntroNode -import io.element.android.features.login.impl.screens.qrcode.scan.QrCodeScanNode import io.element.android.features.login.impl.screens.searchaccountprovider.SearchAccountProviderNode import io.element.android.features.login.impl.screens.waitlistscreen.WaitListNode import io.element.android.libraries.architecture.BackstackView @@ -116,15 +113,6 @@ class LoginFlowNode @AssistedInject constructor( @Parcelize data object Root : NavTarget - @Parcelize - data object QrCodeIntro : NavTarget - - @Parcelize - data object QrCodeScan : NavTarget - - @Parcelize - data object QrCodeConfirmation : NavTarget - @Parcelize data object ConfirmAccountProvider : NavTarget @@ -148,7 +136,7 @@ class LoginFlowNode @AssistedInject constructor( return when (navTarget) { NavTarget.Root -> { if (plugins().first().isQrCode) { - resolve(NavTarget.QrCodeIntro, buildContext) + createNode(buildContext) } else { resolve(NavTarget.ConfirmAccountProvider, buildContext) } @@ -228,33 +216,6 @@ class LoginFlowNode @AssistedInject constructor( } createNode(buildContext, plugins = listOf(callback, inputs)) } - NavTarget.QrCodeIntro -> { - val callback = object : QrCodeIntroNode.Callback { - override fun onCancelClicked() { - navigateUp() - } - - override fun onContinue() { - backstack.push(NavTarget.QrCodeScan) - } - } - createNode(buildContext, plugins = listOf(callback)) - } - NavTarget.QrCodeScan -> { - val callback = object : QrCodeScanNode.Callback { - override fun onScannedCode() { - backstack.push(NavTarget.QrCodeConfirmation) - } - - override fun onCancelClicked() { - backstack.pop() - } - } - createNode(buildContext, plugins = listOf(callback)) - } - NavTarget.QrCodeConfirmation -> { - createNode(buildContext) - } } } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/qrcode/QrCodeLoginFlowNode.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/qrcode/QrCodeLoginFlowNode.kt new file mode 100644 index 00000000000..80dcc018da7 --- /dev/null +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/qrcode/QrCodeLoginFlowNode.kt @@ -0,0 +1,151 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.login.impl.qrcode + +import android.os.Parcelable +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.lifecycle.lifecycleScope +import com.bumble.appyx.core.modality.BuildContext +import com.bumble.appyx.core.node.Node +import com.bumble.appyx.core.plugin.Plugin +import com.bumble.appyx.navmodel.backstack.BackStack +import com.bumble.appyx.navmodel.backstack.operation.pop +import com.bumble.appyx.navmodel.backstack.operation.push +import com.bumble.appyx.navmodel.backstack.operation.replace +import dagger.assisted.Assisted +import dagger.assisted.AssistedInject +import io.element.android.anvilannotations.ContributesNode +import io.element.android.features.login.impl.screens.qrcode.confirmation.QrCodeConfirmationNode +import io.element.android.features.login.impl.screens.qrcode.confirmation.QrCodeConfirmationStep +import io.element.android.features.login.impl.screens.qrcode.error.QrCodeErrorNode +import io.element.android.features.login.impl.screens.qrcode.intro.QrCodeIntroNode +import io.element.android.features.login.impl.screens.qrcode.scan.QrCodeScanNode +import io.element.android.libraries.architecture.BackstackView +import io.element.android.libraries.architecture.BaseFlowNode +import io.element.android.libraries.architecture.createNode +import io.element.android.libraries.di.AppScope +import io.element.android.libraries.matrix.api.auth.qrlogin.MatrixQrCodeLoginData +import io.element.android.libraries.matrix.api.auth.qrlogin.QrCodeLoginStep +import kotlinx.coroutines.launch +import kotlinx.parcelize.Parcelize + +@ContributesNode(AppScope::class) +class QrCodeLoginFlowNode @AssistedInject constructor( + @Assisted buildContext: BuildContext, + @Assisted plugins: List, + private val qrCodeLoginPresenter: QrCodeLoginPresenter, +) : BaseFlowNode( + backstack = BackStack( + initialElement = NavTarget.Initial, + savedStateMap = buildContext.savedStateMap, + ), + buildContext = buildContext, + plugins = plugins, +) { + override fun onBuilt() { + super.onBuilt() + + lifecycleScope.launch { + qrCodeLoginPresenter.currentLoginStep + .collect { step -> + when (step) { + is QrCodeLoginStep.EstablishingSecureChannel -> { + backstack.push(NavTarget.QrCodeConfirmation(QrCodeConfirmationStep.DisplayCheckCode(step.checkCode))) + } + is QrCodeLoginStep.WaitingForToken -> { + backstack.replace(NavTarget.QrCodeConfirmation(QrCodeConfirmationStep.DisplayVerificationCode(step.userCode))) + } + else -> Unit + } + } + } + } + + sealed interface NavTarget : Parcelable { + @Parcelize + data object Initial : NavTarget + + @Parcelize + data object QrCodeScan : NavTarget + + @Parcelize + data class QrCodeConfirmation(val step: QrCodeConfirmationStep) : NavTarget + + @Parcelize + // TODO specify the error type + data class Error(val message: String) : NavTarget + } + + override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node { + return when (navTarget) { + is NavTarget.Initial -> { + val callback = object : QrCodeIntroNode.Callback { + override fun onCancelClicked() { + navigateUp() + } + + override fun onContinue() { + backstack.push(NavTarget.QrCodeScan) + } + } + createNode(buildContext, plugins = listOf(callback)) + } + is NavTarget.QrCodeScan -> { + val callback = object : QrCodeScanNode.Callback { + override fun onScannedCode(qrCodeLoginData: MatrixQrCodeLoginData) { + lifecycleScope.launch { + startAuthentication(qrCodeLoginData) + } + } + + override fun onCancelClicked() { + backstack.pop() + } + } + createNode(buildContext, plugins = listOf(callback)) + } + is NavTarget.QrCodeConfirmation -> { + val callback = object : QrCodeConfirmationNode.Callback { + override fun onCancel() { + // TODO actually cancel the login attempt + navigateUp() + } + } + createNode(buildContext, plugins = listOf(navTarget.step, callback)) + } + is NavTarget.Error -> { + // TODO specify the error type + createNode(buildContext) + } + } + } + + private suspend fun startAuthentication(qrCodeLoginData: MatrixQrCodeLoginData) { + runCatching { + qrCodeLoginPresenter.authenticate(qrCodeLoginData) + }.onFailure { + // TODO specify the error type + backstack.push(NavTarget.Error(it.message ?: "Unknown error")) + } + } + + @Composable + override fun View(modifier: Modifier) { + BackstackView() + } +} diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/qrcode/QrCodeLoginPresenter.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/qrcode/QrCodeLoginPresenter.kt new file mode 100644 index 00000000000..a09f569b675 --- /dev/null +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/qrcode/QrCodeLoginPresenter.kt @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.login.impl.qrcode + +import io.element.android.libraries.di.AppScope +import io.element.android.libraries.di.SingleIn +import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService +import io.element.android.libraries.matrix.api.auth.qrlogin.MatrixQrCodeLoginData +import io.element.android.libraries.matrix.api.auth.qrlogin.QrCodeLoginStep +import io.element.android.libraries.matrix.api.core.SessionId +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import javax.inject.Inject + +@SingleIn(AppScope::class) +class QrCodeLoginPresenter @Inject constructor( + private val authenticationService: MatrixAuthenticationService, +) { + private val _currentLoginStep = MutableStateFlow(QrCodeLoginStep.Uninitialized) + val currentLoginStep: StateFlow = _currentLoginStep + + suspend fun authenticate(qrCodeLoginData: MatrixQrCodeLoginData): Result { + _currentLoginStep.value = QrCodeLoginStep.Uninitialized + + return authenticationService.loginWithQrCode(qrCodeLoginData) { step -> + _currentLoginStep.value = step + } + } +} diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/confirmation/QrCodeConfirmationNode.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/confirmation/QrCodeConfirmationNode.kt index 2f8d81bdf72..9c3f478ad79 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/confirmation/QrCodeConfirmationNode.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/confirmation/QrCodeConfirmationNode.kt @@ -21,21 +21,34 @@ import androidx.compose.ui.Modifier import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.plugin.Plugin +import com.bumble.appyx.core.plugin.plugins import dagger.assisted.Assisted import dagger.assisted.AssistedInject import io.element.android.anvilannotations.ContributesNode +import io.element.android.libraries.architecture.inputs import io.element.android.libraries.di.AppScope @ContributesNode(AppScope::class) class QrCodeConfirmationNode @AssistedInject constructor( @Assisted buildContext: BuildContext, @Assisted plugins: List, - private val presenter: QrCodeConfirmationPresenter, ) : Node(buildContext = buildContext, plugins = plugins) { + interface Callback : Plugin { + fun onCancel() + } + + private val step = inputs() + + private fun onCancel() { + plugins().forEach { it.onCancel() } + } + @Composable override fun View(modifier: Modifier) { - val state = presenter.present() - QrCodeConfirmationView(state = state) + QrCodeConfirmationView( + step = step, + onCancel = ::onCancel, + ) } } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/confirmation/QrCodeConfirmationPresenter.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/confirmation/QrCodeConfirmationPresenter.kt deleted file mode 100644 index a3db13ba940..00000000000 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/confirmation/QrCodeConfirmationPresenter.kt +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (c) 2024 New Vector Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.element.android.features.login.impl.screens.qrcode.confirmation - -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.getValue -import androidx.compose.runtime.produceState -import io.element.android.features.login.api.oidc.OidcAction -import io.element.android.features.login.impl.oidc.customtab.DefaultOidcActionFlow -import io.element.android.libraries.architecture.Presenter -import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService -import javax.inject.Inject - -class QrCodeConfirmationPresenter @Inject constructor( - private val oidcActionFlow: DefaultOidcActionFlow, - private val authenticationService: MatrixAuthenticationService, -) : Presenter { - @Composable - override fun present(): QrCodeConfirmationState { - LaunchedEffect(Unit) { - observeOidcAction() - } - return QrCodeConfirmationState(QrCodeConfirmationStep.DisplayCheckCode("12")) - } - - private suspend fun observeOidcAction() { - oidcActionFlow.collect { - when (it) { - null -> Unit - is OidcAction.Success -> onOidcSuccess(it.url) - is OidcAction.GoBack -> { - - } - } - } - } - - private suspend fun onOidcSuccess(url: String) { - authenticationService.loginWithOidc(url) - .onSuccess { sessionId -> - - } - } -} diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/confirmation/QrCodeConfirmationState.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/confirmation/QrCodeConfirmationState.kt index 70615f4fd0f..55d8df60c1c 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/confirmation/QrCodeConfirmationState.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/confirmation/QrCodeConfirmationState.kt @@ -16,11 +16,18 @@ package io.element.android.features.login.impl.screens.qrcode.confirmation +import android.os.Parcelable +import io.element.android.libraries.architecture.NodeInputs +import kotlinx.parcelize.Parcelize + data class QrCodeConfirmationState( val step: QrCodeConfirmationStep, ) -sealed interface QrCodeConfirmationStep { +sealed interface QrCodeConfirmationStep : NodeInputs, Parcelable { + @Parcelize data class DisplayCheckCode(val code: String) : QrCodeConfirmationStep + + @Parcelize data class DisplayVerificationCode(val code: String) : QrCodeConfirmationStep } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/confirmation/QrCodeConfirmationView.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/confirmation/QrCodeConfirmationView.kt index 823ab615551..8d3544904b1 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/confirmation/QrCodeConfirmationView.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/confirmation/QrCodeConfirmationView.kt @@ -48,20 +48,21 @@ import io.element.android.libraries.ui.strings.CommonStrings @Composable fun QrCodeConfirmationView( - state: QrCodeConfirmationState, + step: QrCodeConfirmationStep, + onCancel: () -> Unit, modifier: Modifier = Modifier, ) { - val icon = when (state.step) { + val icon = when (step) { is QrCodeConfirmationStep.DisplayCheckCode -> CompoundIcons.Computer() is QrCodeConfirmationStep.DisplayVerificationCode -> CompoundIcons.LockSolid() } // TODO: localazy - val title = when (state.step) { + val title = when (step) { is QrCodeConfirmationStep.DisplayCheckCode -> "Enter the number below on your device" is QrCodeConfirmationStep.DisplayVerificationCode -> "Your verification code" } // TODO: localazy - val subtitle = when (state.step) { + val subtitle = when (step) { is QrCodeConfirmationStep.DisplayCheckCode -> "You’ll be asked to enter the two digits shown below." is QrCodeConfirmationStep.DisplayVerificationCode -> "Your account provider may ask for the following code to verify the sign in." } @@ -70,25 +71,25 @@ fun QrCodeConfirmationView( iconStyle = BigIcon.Style.Default(icon), title = title, subTitle = subtitle, - content = { Content(state = state) }, - buttons = { Buttons(state = state) } + content = { Content(step = step) }, + buttons = { Buttons(onCancel = onCancel) } ) } @Composable -private fun Content(state: QrCodeConfirmationState) { +private fun Content(step: QrCodeConfirmationStep) { Column( modifier = Modifier.padding(top = 16.dp), horizontalAlignment = Alignment.CenterHorizontally ) { - when (state.step) { + when (step) { is QrCodeConfirmationStep.DisplayCheckCode -> { - Digits(code = state.step.code) + Digits(code = step.code) Spacer(modifier = Modifier.height(32.dp)) WaitingForOtherDevice() } is QrCodeConfirmationStep.DisplayVerificationCode -> { - Digits(code = state.step.code) + Digits(code = step.code) Spacer(modifier = Modifier.height(32.dp)) WaitingForOtherDevice() } @@ -140,11 +141,14 @@ private fun WaitingForOtherDevice() { } @Composable -private fun Buttons(state: QrCodeConfirmationState) { +private fun Buttons( + onCancel: () -> Unit, +) { Column(modifier = Modifier.fillMaxWidth()) { OutlinedButton( modifier = Modifier.fillMaxWidth(), - text = stringResource(CommonStrings.action_cancel), onClick = { /*TODO*/ } + text = stringResource(CommonStrings.action_cancel), + onClick = onCancel ) } } @@ -153,6 +157,9 @@ private fun Buttons(state: QrCodeConfirmationState) { @Composable internal fun QrCodeConfirmationViewPreview(@PreviewParameter(QrConfirmationCodeStatePreviewProvider::class) state: QrCodeConfirmationState) { ElementPreview { - QrCodeConfirmationView(state = state) + QrCodeConfirmationView( + step = state.step, + onCancel = {}, + ) } } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/connectionnotsecure/QrCodeConnectionNotSecureNode.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/error/QrCodeErrorNode.kt similarity index 88% rename from features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/connectionnotsecure/QrCodeConnectionNotSecureNode.kt rename to features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/error/QrCodeErrorNode.kt index 2c562ad1568..4ebc166ed8f 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/connectionnotsecure/QrCodeConnectionNotSecureNode.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/error/QrCodeErrorNode.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.element.android.features.login.impl.screens.qrcode.connectionnotsecure +package io.element.android.features.login.impl.screens.qrcode.error import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node @@ -25,7 +25,7 @@ import io.element.android.anvilannotations.ContributesNode import io.element.android.libraries.di.AppScope @ContributesNode(AppScope::class) -class QrCodeConnectionNotSecureNode @AssistedInject constructor( +class QrCodeErrorNode @AssistedInject constructor( @Assisted buildContext: BuildContext, @Assisted plugins: List, ) : Node(buildContext = buildContext, plugins = plugins diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/connectionnotsecure/QrCodeConnectionNotSecureView.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/error/QrCodeErrorView.kt similarity index 94% rename from features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/connectionnotsecure/QrCodeConnectionNotSecureView.kt rename to features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/error/QrCodeErrorView.kt index af62bbc1b6f..5e1ed2f47fb 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/connectionnotsecure/QrCodeConnectionNotSecureView.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/error/QrCodeErrorView.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.element.android.features.login.impl.screens.qrcode.connectionnotsecure +package io.element.android.features.login.impl.screens.qrcode.error import androidx.activity.compose.BackHandler import androidx.compose.foundation.layout.Arrangement @@ -42,7 +42,7 @@ import io.element.android.libraries.designsystem.theme.components.Text import kotlinx.collections.immutable.persistentListOf @Composable -fun QrCodeConnectionNotSecureView( +fun QrCodeErrorView( modifier: Modifier = Modifier, onRetry: () -> Unit, ) { @@ -96,9 +96,9 @@ private fun Buttons(onRetry: () -> Unit) { @PreviewsDayNight @Composable -internal fun QrCodeConnectionNotSecureViewPreview() { +internal fun QrCodeErrorViewPreview() { ElementPreview { - QrCodeConnectionNotSecureView( + QrCodeErrorView( onRetry = {} ) } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/scan/QrCodeScanNode.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/scan/QrCodeScanNode.kt index 8c3c0b551ed..035501275b5 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/scan/QrCodeScanNode.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/scan/QrCodeScanNode.kt @@ -26,6 +26,7 @@ import dagger.assisted.Assisted import dagger.assisted.AssistedInject import io.element.android.anvilannotations.ContributesNode import io.element.android.libraries.di.AppScope +import io.element.android.libraries.matrix.api.auth.qrlogin.MatrixQrCodeLoginData @ContributesNode(AppScope::class) class QrCodeScanNode @AssistedInject constructor( @@ -34,12 +35,12 @@ class QrCodeScanNode @AssistedInject constructor( private val presenter: QrCodeScanPresenter, ) : Node(buildContext, plugins = plugins) { interface Callback : Plugin { - fun onScannedCode() + fun onScannedCode(qrCodeLoginData: MatrixQrCodeLoginData) fun onCancelClicked() } - private fun onScannedCode() { - plugins().forEach { it.onScannedCode() } + private fun onQrCodeDataReady(qrCodeLoginData: MatrixQrCodeLoginData) { + plugins().forEach { it.onScannedCode(qrCodeLoginData) } } private fun onCancelClicked() { @@ -51,7 +52,7 @@ class QrCodeScanNode @AssistedInject constructor( val state = presenter.present() QrCodeScanView( state = state, - onSecureConnectionReady = ::onScannedCode, + onQrCodeDataReady = ::onQrCodeDataReady, onBackClicked = ::onCancelClicked, modifier = modifier ) 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 595214bc037..9ca8213f649 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 @@ -27,21 +27,27 @@ import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.runCatchingUpdatingState import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService +import io.element.android.libraries.matrix.api.auth.qrlogin.MatrixQrCodeLoginData +import io.element.android.libraries.matrix.api.auth.qrlogin.MatrixQrCodeLoginDataFactory import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.catch import kotlinx.coroutines.launch +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock import javax.inject.Inject class QrCodeScanPresenter @Inject constructor( - private val authenticationService: MatrixAuthenticationService, + private val qrCodeLoginDataFactory: MatrixQrCodeLoginDataFactory, ) : Presenter { private var isScanning by mutableStateOf(true) + private val codeScannedMutex = Mutex() + @Composable override fun present(): QrCodeScanState { val coroutineScope = rememberCoroutineScope() - val authenticationAction: MutableState> = remember { mutableStateOf(AsyncAction.Uninitialized) } + val authenticationAction: MutableState> = remember { mutableStateOf(AsyncAction.Uninitialized) } fun handleEvents(event: QrCodeScanEvents) { when (event) { @@ -49,9 +55,9 @@ class QrCodeScanPresenter @Inject constructor( isScanning = true authenticationAction.value = AsyncAction.Uninitialized } - is QrCodeScanEvents.QrCodeScanned -> { + is QrCodeScanEvents.QrCodeScanned -> coroutineScope.launch { isScanning = false - coroutineScope.authenticateWithCode(authenticationAction, event.code) + getQrCodeData(authenticationAction, event.code) } } } @@ -63,15 +69,13 @@ class QrCodeScanPresenter @Inject constructor( ) } - private fun CoroutineScope.authenticateWithCode(authenticationAction: MutableState>, code: ByteArray) = launch { - suspend { - // TODO Call the SDK API, for now, just simulate an error - val result = authenticationService.loginWithQrCode(code) - .onFailure { - isScanning = true + private fun CoroutineScope.getQrCodeData(codeScannedAction: MutableState>, code: ByteArray) = launch { + if (!codeScannedMutex.isLocked) { + suspend { + codeScannedMutex.withLock { + qrCodeLoginDataFactory.parseQrCodeData(code).getOrThrow() } -// delay(3000) -// throw Exception("Simulated error $code") - }.runCatchingUpdatingState(authenticationAction) + }.runCatchingUpdatingState(codeScannedAction) + } } } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/scan/QrCodeScanState.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/scan/QrCodeScanState.kt index 3b2fa0cec37..45657c02260 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/scan/QrCodeScanState.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/scan/QrCodeScanState.kt @@ -17,9 +17,10 @@ package io.element.android.features.login.impl.screens.qrcode.scan import io.element.android.libraries.architecture.AsyncAction +import io.element.android.libraries.matrix.api.auth.qrlogin.MatrixQrCodeLoginData data class QrCodeScanState( val isScanning: Boolean, - val authenticationAction: AsyncAction, + val authenticationAction: AsyncAction, val eventSink: (QrCodeScanEvents) -> Unit ) 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 37d21f8929d..699fd233501 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 @@ -18,6 +18,7 @@ package io.element.android.features.login.impl.screens.qrcode.scan import androidx.compose.ui.tooling.preview.PreviewParameterProvider import io.element.android.libraries.architecture.AsyncAction +import io.element.android.libraries.matrix.api.auth.qrlogin.MatrixQrCodeLoginData open class QrCodeScanStateProvider : PreviewParameterProvider { override val values: Sequence @@ -31,7 +32,7 @@ open class QrCodeScanStateProvider : PreviewParameterProvider { fun aQrCodeScanState( isScanning: Boolean = true, - authenticationAction: AsyncAction = AsyncAction.Uninitialized, + authenticationAction: AsyncAction = AsyncAction.Uninitialized, eventSink: (QrCodeScanEvents) -> Unit = {}, ) = QrCodeScanState( isScanning = isScanning, 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 a0efdc88af7..93b4362fc82 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 @@ -34,6 +34,7 @@ import androidx.compose.foundation.layout.width import androidx.compose.foundation.progressSemantics import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource @@ -54,6 +55,7 @@ import io.element.android.libraries.designsystem.theme.components.Button import io.element.android.libraries.designsystem.theme.components.CircularProgressIndicator import io.element.android.libraries.designsystem.theme.components.Icon import io.element.android.libraries.designsystem.theme.components.Text +import io.element.android.libraries.matrix.api.auth.qrlogin.MatrixQrCodeLoginData import io.element.android.libraries.qrcode.QrCodeCameraView import io.element.android.libraries.ui.strings.CommonStrings @@ -61,9 +63,16 @@ import io.element.android.libraries.ui.strings.CommonStrings fun QrCodeScanView( state: QrCodeScanState, onBackClicked: () -> Unit, - onSecureConnectionReady: () -> Unit, + onQrCodeDataReady: (MatrixQrCodeLoginData) -> Unit, modifier: Modifier = Modifier, ) { + // QR code data parsed successfully, notify the parent node + if (state.authenticationAction is AsyncAction.Success) { + LaunchedEffect(state.authenticationAction) { + onQrCodeDataReady(state.authenticationAction.data) + } + } + FlowStepPage( modifier = modifier, onBackClicked = onBackClicked, @@ -190,7 +199,7 @@ private fun ColumnScope.Buttons( internal fun QrCodeScanViewPreview(@PreviewParameter(QrCodeScanStateProvider::class) state: QrCodeScanState) = ElementPreview { QrCodeScanView( state = state, - onSecureConnectionReady = {}, + onQrCodeDataReady = {}, onBackClicked = {}, ) } diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/MatrixAuthenticationService.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/MatrixAuthenticationService.kt index a509df2c2c3..c7d3e79144a 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/MatrixAuthenticationService.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/MatrixAuthenticationService.kt @@ -17,6 +17,8 @@ package io.element.android.libraries.matrix.api.auth import io.element.android.libraries.matrix.api.MatrixClient +import io.element.android.libraries.matrix.api.auth.qrlogin.MatrixQrCodeLoginData +import io.element.android.libraries.matrix.api.auth.qrlogin.QrCodeLoginStep import io.element.android.libraries.matrix.api.core.SessionId import io.element.android.libraries.sessionstorage.api.LoggedInState import kotlinx.coroutines.flow.Flow @@ -54,5 +56,5 @@ interface MatrixAuthenticationService { */ suspend fun loginWithOidc(callbackUrl: String): Result - suspend fun loginWithQrCode(qrCodeBytes: ByteArray): Result + suspend fun loginWithQrCode(qrCodeData: MatrixQrCodeLoginData, progress: (QrCodeLoginStep) -> Unit): Result } 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 new file mode 100644 index 00000000000..541675b0a88 --- /dev/null +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/qrlogin/MatrixQrCodeLoginData.kt @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.matrix.api.auth.qrlogin + +interface MatrixQrCodeLoginData diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/qrlogin/MatrixQrCodeLoginDataFactory.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/qrlogin/MatrixQrCodeLoginDataFactory.kt new file mode 100644 index 00000000000..0258d551065 --- /dev/null +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/qrlogin/MatrixQrCodeLoginDataFactory.kt @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.matrix.api.auth.qrlogin + +interface MatrixQrCodeLoginDataFactory { + fun parseQrCodeData(data: ByteArray): Result +} diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/qrlogin/QrCodeLoginStep.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/qrlogin/QrCodeLoginStep.kt new file mode 100644 index 00000000000..ab31df2658c --- /dev/null +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/qrlogin/QrCodeLoginStep.kt @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.matrix.api.auth.qrlogin + +sealed interface QrCodeLoginStep { + data object Uninitialized : QrCodeLoginStep + data class EstablishingSecureChannel(val checkCode: String) : QrCodeLoginStep + data object Starting : QrCodeLoginStep + data class WaitingForToken(val userCode: String) : QrCodeLoginStep + data object Finished : QrCodeLoginStep +} diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationService.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationService.kt index 7e9be1a8db5..0cd1e47d4d8 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationService.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationService.kt @@ -27,8 +27,12 @@ import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService import io.element.android.libraries.matrix.api.auth.MatrixHomeServerDetails import io.element.android.libraries.matrix.api.auth.OidcDetails +import io.element.android.libraries.matrix.api.auth.qrlogin.MatrixQrCodeLoginData +import io.element.android.libraries.matrix.api.auth.qrlogin.QrCodeLoginStep import io.element.android.libraries.matrix.api.core.SessionId import io.element.android.libraries.matrix.impl.RustMatrixClientFactory +import io.element.android.libraries.matrix.impl.auth.qrlogin.SdkQrCodeLoginData +import io.element.android.libraries.matrix.impl.auth.qrlogin.toStep import io.element.android.libraries.matrix.impl.certificates.UserCertificatesProvider import io.element.android.libraries.matrix.impl.exception.mapClientException import io.element.android.libraries.matrix.impl.keys.PassphraseGenerator @@ -44,8 +48,6 @@ import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.withContext import org.matrix.rustcomponents.sdk.ClientBuilder import org.matrix.rustcomponents.sdk.OidcAuthenticationData -import org.matrix.rustcomponents.sdk.OidcConfiguration -import org.matrix.rustcomponents.sdk.QrCodeData import org.matrix.rustcomponents.sdk.QrLoginProgress import org.matrix.rustcomponents.sdk.QrLoginProgressListener import org.matrix.rustcomponents.sdk.use @@ -215,14 +217,14 @@ class RustMatrixAuthenticationService @Inject constructor( } } - override suspend fun loginWithQrCode(qrCodeBytes: ByteArray) = runCatching { - val decodedQrData = QrCodeData.fromBytes(qrCodeBytes) + override suspend fun loginWithQrCode(qrCodeData: MatrixQrCodeLoginData, progress: (QrCodeLoginStep) -> Unit) = runCatching { val client = ClientBuilder().buildWithQrCode( - qrCodeData = decodedQrData, + qrCodeData = (qrCodeData as SdkQrCodeLoginData).rustQrCodeData, oidcConfiguration = oidcConfiguration, progressListener = object : QrLoginProgressListener { override fun onUpdate(state: QrLoginProgress) { println("QR Code login progress: $state") + progress(state.toStep()) } } ) diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/qrlogin/QrLoginProgressExtensions.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/qrlogin/QrLoginProgressExtensions.kt new file mode 100644 index 00000000000..9d00e7c4787 --- /dev/null +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/qrlogin/QrLoginProgressExtensions.kt @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.matrix.impl.auth.qrlogin + +import io.element.android.libraries.matrix.api.auth.qrlogin.QrCodeLoginStep +import org.matrix.rustcomponents.sdk.QrLoginProgress + +fun QrLoginProgress.toStep(): QrCodeLoginStep { + return when (this) { + is QrLoginProgress.EstablishingSecureChannel -> QrCodeLoginStep.EstablishingSecureChannel(checkCode.toString()) + is QrLoginProgress.Starting -> QrCodeLoginStep.Starting + is QrLoginProgress.WaitingForToken -> QrCodeLoginStep.WaitingForToken(userCode) + is QrLoginProgress.Done -> QrCodeLoginStep.Finished + } +} diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/qrlogin/RustQrCodeLoginDataFactory.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/qrlogin/RustQrCodeLoginDataFactory.kt new file mode 100644 index 00000000000..9cf55e018a4 --- /dev/null +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/qrlogin/RustQrCodeLoginDataFactory.kt @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.matrix.impl.auth.qrlogin + +import com.squareup.anvil.annotations.ContributesBinding +import io.element.android.libraries.di.AppScope +import io.element.android.libraries.matrix.api.auth.qrlogin.MatrixQrCodeLoginData +import io.element.android.libraries.matrix.api.auth.qrlogin.MatrixQrCodeLoginDataFactory +import org.matrix.rustcomponents.sdk.QrCodeData +import javax.inject.Inject + +@ContributesBinding(AppScope::class) +class RustQrCodeLoginDataFactory @Inject constructor() : MatrixQrCodeLoginDataFactory { + override fun parseQrCodeData(data: ByteArray): Result { + return runCatching { SdkQrCodeLoginData(QrCodeData.fromBytes(data)) } + } +} 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 new file mode 100644 index 00000000000..da24fbf4bb5 --- /dev/null +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/qrlogin/SdkQrCodeLoginData.kt @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.matrix.impl.auth.qrlogin + +import io.element.android.libraries.matrix.api.auth.qrlogin.MatrixQrCodeLoginData +import org.matrix.rustcomponents.sdk.QrCodeData as RustQrCodeData + +class SdkQrCodeLoginData( + internal val rustQrCodeData: RustQrCodeData, +) : MatrixQrCodeLoginData From c9a2e2964d717a0411c1d53f2a33d41f8296fe18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= Date: Tue, 23 Apr 2024 12:57:31 +0200 Subject: [PATCH 09/60] Add localized strings --- .../ftue/impl/src/main/res/values/localazy.xml | 7 ++++++- .../qrcode/confirmation/QrCodeConfirmationView.kt | 14 ++++++-------- .../login/impl/src/main/res/values/localazy.xml | 7 ++++++- .../impl/src/main/res/values/localazy.xml | 2 +- .../ui-strings/src/main/res/values/localazy.xml | 1 + 5 files changed, 20 insertions(+), 11 deletions(-) diff --git a/features/ftue/impl/src/main/res/values/localazy.xml b/features/ftue/impl/src/main/res/values/localazy.xml index eff55a1e2e1..ae50fe9aa1c 100644 --- a/features/ftue/impl/src/main/res/values/localazy.xml +++ b/features/ftue/impl/src/main/res/values/localazy.xml @@ -9,6 +9,8 @@ "If you encounter the same problem, try a different wifi network or use your mobile data instead of wifi" "If that doesn’t work, sign in manually" "Connection not secure" + "You’ll be asked to enter the two digits shown below." + "Enter the number on your other device" "Open %1$s on a desktop device" "Click on your avatar" "Select %1$s" @@ -20,11 +22,14 @@ "Try again" "Wrong QR code" "Go to camera settings" - "You need to give permission for Element to use your device’s camera in order to continue." + "You need to give permission for %1$s to use your device’s camera in order to continue." "Allow camera access to scan the QR code" "Scan the QR code" "Start over" "An unexpected error occurred. Please try again." + "Waiting for your other device" + "Your account provider may ask for the following code to verify the sign in." + "Your verification code" "Calls, polls, search and more will be added later this year." "Message history for encrypted rooms isn’t available yet." "We’d love to hear from you, let us know what you think via the settings page." diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/confirmation/QrCodeConfirmationView.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/confirmation/QrCodeConfirmationView.kt index 8d3544904b1..a7245fff110 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/confirmation/QrCodeConfirmationView.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/confirmation/QrCodeConfirmationView.kt @@ -37,6 +37,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameter 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.libraries.designsystem.atomic.pages.FlowStepPage import io.element.android.libraries.designsystem.components.BigIcon import io.element.android.libraries.designsystem.preview.ElementPreview @@ -56,15 +57,13 @@ fun QrCodeConfirmationView( is QrCodeConfirmationStep.DisplayCheckCode -> CompoundIcons.Computer() is QrCodeConfirmationStep.DisplayVerificationCode -> CompoundIcons.LockSolid() } - // TODO: localazy val title = when (step) { - is QrCodeConfirmationStep.DisplayCheckCode -> "Enter the number below on your device" - is QrCodeConfirmationStep.DisplayVerificationCode -> "Your verification code" + is QrCodeConfirmationStep.DisplayCheckCode -> stringResource(R.string.screen_qr_code_login_device_code_title) + is QrCodeConfirmationStep.DisplayVerificationCode -> stringResource(R.string.screen_qr_code_login_verify_code_title) } - // TODO: localazy val subtitle = when (step) { - is QrCodeConfirmationStep.DisplayCheckCode -> "You’ll be asked to enter the two digits shown below." - is QrCodeConfirmationStep.DisplayVerificationCode -> "Your account provider may ask for the following code to verify the sign in." + is QrCodeConfirmationStep.DisplayCheckCode -> stringResource(R.string.screen_qr_code_login_device_code_subtitle) + is QrCodeConfirmationStep.DisplayVerificationCode -> stringResource(R.string.screen_qr_code_login_verify_code_subtitle) } FlowStepPage( modifier = modifier, @@ -130,9 +129,8 @@ private fun WaitingForOtherDevice() { .padding(2.dp), strokeWidth = 2.dp, ) - // TODO: localazy Text( - text = "Waiting for your other device", + text = stringResource(R.string.screen_qr_code_login_verify_code_loading), style = ElementTheme.typography.fontBodySmRegular, color = ElementTheme.colors.textSecondary, textAlign = TextAlign.Center, diff --git a/features/login/impl/src/main/res/values/localazy.xml b/features/login/impl/src/main/res/values/localazy.xml index 0af66431730..26fbd83be37 100644 --- a/features/login/impl/src/main/res/values/localazy.xml +++ b/features/login/impl/src/main/res/values/localazy.xml @@ -37,6 +37,8 @@ "If you encounter the same problem, try a different wifi network or use your mobile data instead of wifi" "If that doesn’t work, sign in manually" "Connection not secure" + "You’ll be asked to enter the two digits shown below." + "Enter the number on your other device" "Open %1$s on a desktop device" "Click on your avatar" "Select %1$s" @@ -48,11 +50,14 @@ "Try again" "Wrong QR code" "Go to camera settings" - "You need to give permission for Element to use your device’s camera in order to continue." + "You need to give permission for %1$s to use your device’s camera in order to continue." "Allow camera access to scan the QR code" "Scan the QR code" "Start over" "An unexpected error occurred. Please try again." + "Waiting for your other device" + "Your account provider may ask for the following code to verify the sign in." + "Your verification code" "Change account provider" "A private server for Element employees." "Matrix is an open network for secure, decentralised communication." diff --git a/libraries/eventformatter/impl/src/main/res/values/localazy.xml b/libraries/eventformatter/impl/src/main/res/values/localazy.xml index 53589974fef..5e06d74e92c 100644 --- a/libraries/eventformatter/impl/src/main/res/values/localazy.xml +++ b/libraries/eventformatter/impl/src/main/res/values/localazy.xml @@ -30,7 +30,7 @@ "You joined the room" "%1$s requested to join" "%1$s allowed %2$s to join" - "%1$s allowed you to join" + "You allowed %1$s to join" "You requested to join" "%1$s rejected %2$s\'s request to join" "You rejected %1$s\'s request to join" diff --git a/libraries/ui-strings/src/main/res/values/localazy.xml b/libraries/ui-strings/src/main/res/values/localazy.xml index 8e0e463a3a9..360761915ca 100644 --- a/libraries/ui-strings/src/main/res/values/localazy.xml +++ b/libraries/ui-strings/src/main/res/values/localazy.xml @@ -255,6 +255,7 @@ "Failed selecting media, please try again." "Failed processing media to upload, please try again." "Failed uploading media, please try again." + "Failed to resolve room alias." "Failed processing media to upload, please try again." "Could not retrieve user details" "Block" From c83e6c5d40d1e4608261102a85d6de360391dc8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= Date: Tue, 23 Apr 2024 14:31:14 +0200 Subject: [PATCH 10/60] Fix multiple `ClientBuilder` instances being created --- .../login/impl/qrcode/QrCodeLoginFlowNode.kt | 14 ++++++++------ .../qrcode/scan/QrCodeScanPresenter.kt | 19 +++++++++++-------- .../libraries/qrcode/QrCodeCameraView.kt | 10 ++++++---- 3 files changed, 25 insertions(+), 18 deletions(-) diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/qrcode/QrCodeLoginFlowNode.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/qrcode/QrCodeLoginFlowNode.kt index 80dcc018da7..a3dfb1f10d4 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/qrcode/QrCodeLoginFlowNode.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/qrcode/QrCodeLoginFlowNode.kt @@ -136,12 +136,14 @@ class QrCodeLoginFlowNode @AssistedInject constructor( } private suspend fun startAuthentication(qrCodeLoginData: MatrixQrCodeLoginData) { - runCatching { - qrCodeLoginPresenter.authenticate(qrCodeLoginData) - }.onFailure { - // TODO specify the error type - backstack.push(NavTarget.Error(it.message ?: "Unknown error")) - } + qrCodeLoginPresenter.authenticate(qrCodeLoginData) + .onSuccess { + println("Logged into session $it") + } + .onFailure { + // TODO specify the error type + backstack.push(NavTarget.Error(it.message ?: "Unknown error")) + } } @Composable 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 9ca8213f649..084f4d6bb7d 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 @@ -34,6 +34,7 @@ import kotlinx.coroutines.flow.catch import kotlinx.coroutines.launch import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock +import java.util.concurrent.atomic.AtomicBoolean import javax.inject.Inject class QrCodeScanPresenter @Inject constructor( @@ -42,7 +43,7 @@ class QrCodeScanPresenter @Inject constructor( private var isScanning by mutableStateOf(true) - private val codeScannedMutex = Mutex() + private val isProcessingCode = AtomicBoolean(false) @Composable override fun present(): QrCodeScanState { @@ -55,9 +56,9 @@ class QrCodeScanPresenter @Inject constructor( isScanning = true authenticationAction.value = AsyncAction.Uninitialized } - is QrCodeScanEvents.QrCodeScanned -> coroutineScope.launch { + is QrCodeScanEvents.QrCodeScanned -> { isScanning = false - getQrCodeData(authenticationAction, event.code) + coroutineScope.getQrCodeData(authenticationAction, event.code) } } } @@ -69,13 +70,15 @@ class QrCodeScanPresenter @Inject constructor( ) } - private fun CoroutineScope.getQrCodeData(codeScannedAction: MutableState>, code: ByteArray) = launch { - if (!codeScannedMutex.isLocked) { + private fun CoroutineScope.getQrCodeData(codeScannedAction: MutableState>, code: ByteArray) { + if (codeScannedAction.value.isSuccess() || isProcessingCode.compareAndSet(true, true)) return + + launch { suspend { - codeScannedMutex.withLock { - qrCodeLoginDataFactory.parseQrCodeData(code).getOrThrow() - } + qrCodeLoginDataFactory.parseQrCodeData(code).getOrThrow() }.runCatchingUpdatingState(codeScannedAction) + }.invokeOnCompletion { + isProcessingCode.set(false) } } } 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 9ac69d36781..750ebad8843 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 @@ -64,6 +64,9 @@ fun QrCodeCameraView( val cameraProviderFuture = remember { ProcessCameraProvider.getInstance(localContext) } val previewUseCase = remember { Preview.Builder().build() } var lastFrame by remember { mutableStateOf(null) } + val imageAnalysis = ImageAnalysis.Builder() + .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST) + .build() Box(modifier.clipToBounds()) { AndroidView( factory = { context -> @@ -77,13 +80,9 @@ fun QrCodeCameraView( .requireLensFacing(CameraSelector.LENS_FACING_BACK) .build() previewUseCase.setSurfaceProvider(previewView.surfaceProvider) - val imageAnalysis = ImageAnalysis.Builder() - .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST) - .build() imageAnalysis.setAnalyzer( ContextCompat.getMainExecutor(previewView.context), QRCodeAnalyzer { result -> - // TODO: This probably needs a Mutex or some AtomicBoolean to prevent several onQrCodeScanned calls result?.let { Timber.d("QR code scanned!") onQrCodeScanned(it) @@ -102,6 +101,9 @@ fun QrCodeCameraView( Timber.e(e, "Use case binding failed") } } else { + // Stop analyzer + imageAnalysis.clearAnalyzer() + // Save last frame to display it as the 'frozen' preview if (lastFrame == null) { lastFrame = previewView.bitmap From 92ea84ccec21bef9bc569ddcdec2176c699f7f87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= Date: Tue, 23 Apr 2024 15:56:12 +0200 Subject: [PATCH 11/60] Use `checkCodeString` for the check code value instead --- .../matrix/impl/auth/qrlogin/QrLoginProgressExtensions.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/qrlogin/QrLoginProgressExtensions.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/qrlogin/QrLoginProgressExtensions.kt index 9d00e7c4787..1dc60297004 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/qrlogin/QrLoginProgressExtensions.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/qrlogin/QrLoginProgressExtensions.kt @@ -21,7 +21,7 @@ import org.matrix.rustcomponents.sdk.QrLoginProgress fun QrLoginProgress.toStep(): QrCodeLoginStep { return when (this) { - is QrLoginProgress.EstablishingSecureChannel -> QrCodeLoginStep.EstablishingSecureChannel(checkCode.toString()) + is QrLoginProgress.EstablishingSecureChannel -> QrCodeLoginStep.EstablishingSecureChannel(checkCodeString) is QrLoginProgress.Starting -> QrCodeLoginStep.Starting is QrLoginProgress.WaitingForToken -> QrCodeLoginStep.WaitingForToken(userCode) is QrLoginProgress.Done -> QrCodeLoginStep.Finished From 0542be75cba8b9a1a1d72eec4a0fb10918db6ed6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= Date: Wed, 24 Apr 2024 16:24:22 +0200 Subject: [PATCH 12/60] Finish the login flow on a successful QR code login --- .../features/login/impl/qrcode/QrCodeLoginPresenter.kt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/qrcode/QrCodeLoginPresenter.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/qrcode/QrCodeLoginPresenter.kt index a09f569b675..c4ad0b44c5e 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/qrcode/QrCodeLoginPresenter.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/qrcode/QrCodeLoginPresenter.kt @@ -16,6 +16,7 @@ package io.element.android.features.login.impl.qrcode +import io.element.android.features.login.impl.DefaultLoginUserStory import io.element.android.libraries.di.AppScope import io.element.android.libraries.di.SingleIn import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService @@ -29,6 +30,7 @@ import javax.inject.Inject @SingleIn(AppScope::class) class QrCodeLoginPresenter @Inject constructor( private val authenticationService: MatrixAuthenticationService, + private val defaultLoginUserStory: DefaultLoginUserStory, ) { private val _currentLoginStep = MutableStateFlow(QrCodeLoginStep.Uninitialized) val currentLoginStep: StateFlow = _currentLoginStep @@ -38,6 +40,8 @@ class QrCodeLoginPresenter @Inject constructor( return authenticationService.loginWithQrCode(qrCodeLoginData) { step -> _currentLoginStep.value = step + }.onSuccess { + defaultLoginUserStory.setLoginFlowIsDone(true) } } } From a3ba41cb4b8d317b7d82087f4c88915976affa2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= Date: Wed, 24 Apr 2024 17:29:02 +0200 Subject: [PATCH 13/60] WIP changes to try to fix verification state --- .../login/impl/qrcode/QrCodeLoginFlowNode.kt | 2 +- .../auth/RustMatrixAuthenticationService.kt | 86 +++++++++++++++---- 2 files changed, 72 insertions(+), 16 deletions(-) diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/qrcode/QrCodeLoginFlowNode.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/qrcode/QrCodeLoginFlowNode.kt index a3dfb1f10d4..dd21eba1e45 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/qrcode/QrCodeLoginFlowNode.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/qrcode/QrCodeLoginFlowNode.kt @@ -65,7 +65,7 @@ class QrCodeLoginFlowNode @AssistedInject constructor( .collect { step -> when (step) { is QrCodeLoginStep.EstablishingSecureChannel -> { - backstack.push(NavTarget.QrCodeConfirmation(QrCodeConfirmationStep.DisplayCheckCode(step.checkCode))) + backstack.replace(NavTarget.QrCodeConfirmation(QrCodeConfirmationStep.DisplayCheckCode(step.checkCode))) } is QrCodeLoginStep.WaitingForToken -> { backstack.replace(NavTarget.QrCodeConfirmation(QrCodeConfirmationStep.DisplayVerificationCode(step.userCode))) diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationService.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationService.kt index 0cd1e47d4d8..b509aab55dd 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationService.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationService.kt @@ -38,6 +38,7 @@ import io.element.android.libraries.matrix.impl.exception.mapClientException import io.element.android.libraries.matrix.impl.keys.PassphraseGenerator import io.element.android.libraries.matrix.impl.mapper.toSessionData import io.element.android.libraries.matrix.impl.proxy.ProxyProvider +import io.element.android.libraries.matrix.impl.util.cancelAndDestroy import io.element.android.libraries.network.useragent.UserAgentProvider import io.element.android.libraries.sessionstorage.api.LoggedInState import io.element.android.libraries.sessionstorage.api.LoginType @@ -45,11 +46,17 @@ import io.element.android.libraries.sessionstorage.api.SessionStore import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.withContext import org.matrix.rustcomponents.sdk.ClientBuilder import org.matrix.rustcomponents.sdk.OidcAuthenticationData import org.matrix.rustcomponents.sdk.QrLoginProgress import org.matrix.rustcomponents.sdk.QrLoginProgressListener +import org.matrix.rustcomponents.sdk.RecoveryState +import org.matrix.rustcomponents.sdk.RecoveryStateListener +import org.matrix.rustcomponents.sdk.TaskHandle +import org.matrix.rustcomponents.sdk.VerificationState +import org.matrix.rustcomponents.sdk.VerificationStateListener import org.matrix.rustcomponents.sdk.use import timber.log.Timber import java.io.File @@ -59,14 +66,14 @@ import org.matrix.rustcomponents.sdk.AuthenticationService as RustAuthentication @ContributesBinding(AppScope::class) @SingleIn(AppScope::class) class RustMatrixAuthenticationService @Inject constructor( - baseDirectory: File, + private val baseDirectory: File, private val coroutineDispatchers: CoroutineDispatchers, private val sessionStore: SessionStore, - userAgentProvider: UserAgentProvider, + private val userAgentProvider: UserAgentProvider, private val rustMatrixClientFactory: RustMatrixClientFactory, private val passphraseGenerator: PassphraseGenerator, - userCertificatesProvider: UserCertificatesProvider, - proxyProvider: ProxyProvider, + private val userCertificatesProvider: UserCertificatesProvider, + private val proxyProvider: ProxyProvider, private val buildMeta: BuildMeta, ) : MatrixAuthenticationService { // Passphrase which will be used for new sessions. Existing sessions will use the passphrase @@ -218,27 +225,76 @@ class RustMatrixAuthenticationService @Inject constructor( } override suspend fun loginWithQrCode(qrCodeData: MatrixQrCodeLoginData, progress: (QrCodeLoginStep) -> Unit) = runCatching { - val client = ClientBuilder().buildWithQrCode( - qrCodeData = (qrCodeData as SdkQrCodeLoginData).rustQrCodeData, - oidcConfiguration = oidcConfiguration, - progressListener = object : QrLoginProgressListener { - override fun onUpdate(state: QrLoginProgress) { - println("QR Code login progress: $state") - progress(state.toStep()) + val client = ClientBuilder() + .basePath(baseDirectory.absolutePath) + .userAgent(userAgentProvider.provide()) + .serverVersions(listOf("v1.0", "v1.1", "v1.2", "v1.3", "v1.4", "v1.5")) + .addRootCertificates(userCertificatesProvider.provides()) + .let { + // Sadly ClientBuilder.proxy() does not accept null :/ + // Tracked by https://github.com/matrix-org/matrix-rust-sdk/issues/3159 + val proxy = proxyProvider.provides() + if (proxy != null) { + it.proxy(proxy) + } else { + it } } - ) - val sessionData = client.use { - it.session().toSessionData( + .buildWithQrCode( + qrCodeData = (qrCodeData as SdkQrCodeLoginData).rustQrCodeData, + oidcConfiguration = oidcConfiguration, + progressListener = object : QrLoginProgressListener { + override fun onUpdate(state: QrLoginProgress) { + println("QR Code login progress: $state") + progress(state.toStep()) + } + } + ) + val sessionData = client.session() + .toSessionData( isTokenValid = true, loginType = LoginType.OIDC, passphrase = pendingPassphrase, needsVerification = true, ) - } pendingOidcAuthenticationData?.close() pendingOidcAuthenticationData = null sessionStore.storeData(sessionData) + + val recoveryMutex = Mutex(locked = true) + val verificationMutex = Mutex(locked = true) + val encryptionService = client.encryption() + + val syncService = client.syncService().finish() + syncService.start() + + var recoveryStateHandle: TaskHandle? = null + var verificationStateHandle: TaskHandle? = null + recoveryStateHandle = encryptionService.recoveryStateListener(object : RecoveryStateListener { + override fun onUpdate(status: RecoveryState) { + if (status == RecoveryState.ENABLED) { + Timber.d("Recovery ENABLED") + recoveryMutex.unlock() + recoveryStateHandle?.cancelAndDestroy() + } + } + }) + verificationStateHandle = encryptionService.verificationStateListener(object : VerificationStateListener { + override fun onUpdate(status: VerificationState) { + if (status == VerificationState.VERIFIED) { + Timber.d("VERIFIED") + verificationMutex.unlock() + verificationStateHandle?.cancelAndDestroy() + } + } + }) + verificationMutex.lock() + recoveryMutex.lock() + + syncService.stop() + syncService.destroy() + client.destroy() + SessionId(sessionData.userId) }.onFailure { it.printStackTrace() From 480386450ebc07b5974d53e80fb84d18a49312d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= Date: Thu, 25 Apr 2024 09:22:11 +0200 Subject: [PATCH 14/60] Reuse existing code and simplify the flow --- .../screens/qrcode/scan/QrCodeScanView.kt | 7 ++-- .../matrix/impl/RustMatrixClientFactory.kt | 34 +++++++++++-------- .../auth/RustMatrixAuthenticationService.kt | 29 +++++----------- 3 files changed, 29 insertions(+), 41 deletions(-) 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 93b4362fc82..d1b6414fd35 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 @@ -110,10 +110,7 @@ private fun Content( contentAlignment = Alignment.Center, ) { QrCodeCameraView( - modifier = Modifier - .fillMaxSize() - // TODO: FOR TESTING ONLY, REMOVE THIS - .clickable { state.eventSink.invoke(QrCodeScanEvents.QrCodeScanned(byteArrayOf(1))) }, + modifier = Modifier.fillMaxSize(), onQrCodeScanned = { state.eventSink.invoke(QrCodeScanEvents.QrCodeScanned(it)) }, renderPreview = state.isScanning, ) @@ -180,7 +177,7 @@ private fun ColumnScope.Buttons( strokeWidth = 2.dp ) Text( - text = "Establishing a secure connection", // TODO Localazy + text = stringResource(R.string.screen_qr_code_login_connecting_subtitle), textAlign = TextAlign.Center, style = ElementTheme.typography.fontBodySmRegular, color = ElementTheme.colors.textSecondary, diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClientFactory.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClientFactory.kt index 80302933d53..cb0fbbefa43 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClientFactory.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClientFactory.kt @@ -46,25 +46,11 @@ class RustMatrixClientFactory @Inject constructor( private val utdTracker: UtdTracker, ) { suspend fun create(sessionData: SessionData): RustMatrixClient = withContext(coroutineDispatchers.io) { - val client = ClientBuilder() - .basePath(baseDirectory.absolutePath) + val client = getBaseClientBuilder() .homeserverUrl(sessionData.homeserverUrl) .username(sessionData.userId) .passphrase(sessionData.passphrase) - .userAgent(userAgentProvider.provide()) - .let { - // Sadly ClientBuilder.proxy() does not accept null :/ - // Tracked by https://github.com/matrix-org/matrix-rust-sdk/issues/3159 - val proxy = proxyProvider.provides() - if (proxy != null) { - it.proxy(proxy) - } else { - it - } - } - .addRootCertificates(userCertificatesProvider.provides()) // FIXME Quick and dirty fix for stopping version requests on startup https://github.com/matrix-org/matrix-rust-sdk/pull/1376 - .serverVersions(listOf("v1.0", "v1.1", "v1.2", "v1.3", "v1.4", "v1.5")) .use { it.build() } client.restoreSession(sessionData.toSession()) @@ -84,6 +70,24 @@ class RustMatrixClientFactory @Inject constructor( clock = clock, ) } + + internal fun getBaseClientBuilder(): ClientBuilder { + return ClientBuilder() + .basePath(baseDirectory.absolutePath) + .userAgent(userAgentProvider.provide()) + .addRootCertificates(userCertificatesProvider.provides()) + .serverVersions(listOf("v1.0", "v1.1", "v1.2", "v1.3", "v1.4", "v1.5")) + .let { + // Sadly ClientBuilder.proxy() does not accept null :/ + // Tracked by https://github.com/matrix-org/matrix-rust-sdk/issues/3159 + val proxy = proxyProvider.provides() + if (proxy != null) { + it.proxy(proxy) + } else { + it + } + } + } } private fun SessionData.toSession() = Session( diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationService.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationService.kt index b509aab55dd..05f6705414a 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationService.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationService.kt @@ -66,14 +66,14 @@ import org.matrix.rustcomponents.sdk.AuthenticationService as RustAuthentication @ContributesBinding(AppScope::class) @SingleIn(AppScope::class) class RustMatrixAuthenticationService @Inject constructor( - private val baseDirectory: File, + baseDirectory: File, private val coroutineDispatchers: CoroutineDispatchers, private val sessionStore: SessionStore, - private val userAgentProvider: UserAgentProvider, + userAgentProvider: UserAgentProvider, private val rustMatrixClientFactory: RustMatrixClientFactory, private val passphraseGenerator: PassphraseGenerator, - private val userCertificatesProvider: UserCertificatesProvider, - private val proxyProvider: ProxyProvider, + userCertificatesProvider: UserCertificatesProvider, + proxyProvider: ProxyProvider, private val buildMeta: BuildMeta, ) : MatrixAuthenticationService { // Passphrase which will be used for new sessions. Existing sessions will use the passphrase @@ -225,21 +225,8 @@ class RustMatrixAuthenticationService @Inject constructor( } override suspend fun loginWithQrCode(qrCodeData: MatrixQrCodeLoginData, progress: (QrCodeLoginStep) -> Unit) = runCatching { - val client = ClientBuilder() - .basePath(baseDirectory.absolutePath) - .userAgent(userAgentProvider.provide()) - .serverVersions(listOf("v1.0", "v1.1", "v1.2", "v1.3", "v1.4", "v1.5")) - .addRootCertificates(userCertificatesProvider.provides()) - .let { - // Sadly ClientBuilder.proxy() does not accept null :/ - // Tracked by https://github.com/matrix-org/matrix-rust-sdk/issues/3159 - val proxy = proxyProvider.provides() - if (proxy != null) { - it.proxy(proxy) - } else { - it - } - } + val client = rustMatrixClientFactory.getBaseClientBuilder() + .passphrase(pendingPassphrase) .buildWithQrCode( qrCodeData = (qrCodeData as SdkQrCodeLoginData).rustQrCodeData, oidcConfiguration = oidcConfiguration, @@ -272,8 +259,8 @@ class RustMatrixAuthenticationService @Inject constructor( var verificationStateHandle: TaskHandle? = null recoveryStateHandle = encryptionService.recoveryStateListener(object : RecoveryStateListener { override fun onUpdate(status: RecoveryState) { + Timber.d("QR Login recovery state: $status") if (status == RecoveryState.ENABLED) { - Timber.d("Recovery ENABLED") recoveryMutex.unlock() recoveryStateHandle?.cancelAndDestroy() } @@ -281,8 +268,8 @@ class RustMatrixAuthenticationService @Inject constructor( }) verificationStateHandle = encryptionService.verificationStateListener(object : VerificationStateListener { override fun onUpdate(status: VerificationState) { + Timber.d("QR Login Verification state: $status") if (status == VerificationState.VERIFIED) { - Timber.d("VERIFIED") verificationMutex.unlock() verificationStateHandle?.cancelAndDestroy() } From 8f35a7be222b2dd58f5ac19eb1b4d187b67a7ce8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= Date: Thu, 25 Apr 2024 14:38:49 +0200 Subject: [PATCH 15/60] Remove unnecessary 'link new device' option --- .../features/preferences/impl/root/PreferencesRootView.kt | 6 ------ 1 file changed, 6 deletions(-) diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootView.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootView.kt index 50a880ac78a..0ad236ff744 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootView.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootView.kt @@ -81,12 +81,6 @@ fun PreferencesRootView( }, user = state.myUser, ) - ListItem( - // TODO: localazy - headlineContent = { Text("Link new device") }, - leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.QrCode())), - onClick = { TODO() } - ) if (state.showSecureBackup) { ListItem( headlineContent = { Text(stringResource(id = CommonStrings.common_chat_backup)) }, From a580355fa59e53f7e753dca60e704630caa6bfb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= Date: Thu, 25 Apr 2024 14:39:54 +0200 Subject: [PATCH 16/60] Actually cancel the QR code login and do a cleanup of any lingering client and other SDK services --- .../login/impl/qrcode/QrCodeLoginFlowNode.kt | 63 ++++++---- .../auth/RustMatrixAuthenticationService.kt | 117 ++++++++++-------- 2 files changed, 100 insertions(+), 80 deletions(-) diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/qrcode/QrCodeLoginFlowNode.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/qrcode/QrCodeLoginFlowNode.kt index dd21eba1e45..2aab2d85078 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/qrcode/QrCodeLoginFlowNode.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/qrcode/QrCodeLoginFlowNode.kt @@ -24,6 +24,7 @@ import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.plugin.Plugin import com.bumble.appyx.navmodel.backstack.BackStack +import com.bumble.appyx.navmodel.backstack.operation.newRoot import com.bumble.appyx.navmodel.backstack.operation.pop import com.bumble.appyx.navmodel.backstack.operation.push import com.bumble.appyx.navmodel.backstack.operation.replace @@ -41,8 +42,12 @@ import io.element.android.libraries.architecture.createNode import io.element.android.libraries.di.AppScope import io.element.android.libraries.matrix.api.auth.qrlogin.MatrixQrCodeLoginData import io.element.android.libraries.matrix.api.auth.qrlogin.QrCodeLoginStep +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Job import kotlinx.coroutines.launch import kotlinx.parcelize.Parcelize +import timber.log.Timber +import kotlin.coroutines.coroutineContext @ContributesNode(AppScope::class) class QrCodeLoginFlowNode @AssistedInject constructor( @@ -57,6 +62,23 @@ class QrCodeLoginFlowNode @AssistedInject constructor( buildContext = buildContext, plugins = plugins, ) { + private var authenticationJob: Job? = null + + sealed interface NavTarget : Parcelable { + @Parcelize + data object Initial : NavTarget + + @Parcelize + data object QrCodeScan : NavTarget + + @Parcelize + data class QrCodeConfirmation(val step: QrCodeConfirmationStep) : NavTarget + + @Parcelize + // TODO specify the error type + data class Error(val message: String) : NavTarget + } + override fun onBuilt() { super.onBuilt() @@ -76,21 +98,6 @@ class QrCodeLoginFlowNode @AssistedInject constructor( } } - sealed interface NavTarget : Parcelable { - @Parcelize - data object Initial : NavTarget - - @Parcelize - data object QrCodeScan : NavTarget - - @Parcelize - data class QrCodeConfirmation(val step: QrCodeConfirmationStep) : NavTarget - - @Parcelize - // TODO specify the error type - data class Error(val message: String) : NavTarget - } - override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node { return when (navTarget) { is NavTarget.Initial -> { @@ -122,8 +129,9 @@ class QrCodeLoginFlowNode @AssistedInject constructor( is NavTarget.QrCodeConfirmation -> { val callback = object : QrCodeConfirmationNode.Callback { override fun onCancel() { - // TODO actually cancel the login attempt - navigateUp() + authenticationJob?.cancel() + authenticationJob = null + backstack.newRoot(NavTarget.Initial) } } createNode(buildContext, plugins = listOf(navTarget.step, callback)) @@ -136,14 +144,19 @@ class QrCodeLoginFlowNode @AssistedInject constructor( } private suspend fun startAuthentication(qrCodeLoginData: MatrixQrCodeLoginData) { - qrCodeLoginPresenter.authenticate(qrCodeLoginData) - .onSuccess { - println("Logged into session $it") - } - .onFailure { - // TODO specify the error type - backstack.push(NavTarget.Error(it.message ?: "Unknown error")) - } + authenticationJob = CoroutineScope(coroutineContext).launch { + qrCodeLoginPresenter.authenticate(qrCodeLoginData) + .onSuccess { + println("Logged into session $it") + authenticationJob = null + } + .onFailure { + // TODO specify the error type + Timber.e(it, "QR code authentication failed") + backstack.push(NavTarget.Error(it.message ?: "Unknown error")) + authenticationJob = null + } + } } @Composable diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationService.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationService.kt index 05f6705414a..212a828d88c 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationService.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationService.kt @@ -18,6 +18,7 @@ package io.element.android.libraries.matrix.impl.auth import com.squareup.anvil.annotations.ContributesBinding import io.element.android.libraries.core.coroutine.CoroutineDispatchers +import io.element.android.libraries.core.extensions.finally import io.element.android.libraries.core.extensions.mapFailure import io.element.android.libraries.core.meta.BuildMeta import io.element.android.libraries.core.meta.BuildType @@ -43,6 +44,7 @@ import io.element.android.libraries.network.useragent.UserAgentProvider import io.element.android.libraries.sessionstorage.api.LoggedInState import io.element.android.libraries.sessionstorage.api.LoginType import io.element.android.libraries.sessionstorage.api.SessionStore +import kotlinx.coroutines.CancellationException import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow @@ -54,6 +56,7 @@ import org.matrix.rustcomponents.sdk.QrLoginProgress import org.matrix.rustcomponents.sdk.QrLoginProgressListener import org.matrix.rustcomponents.sdk.RecoveryState import org.matrix.rustcomponents.sdk.RecoveryStateListener +import org.matrix.rustcomponents.sdk.SyncService import org.matrix.rustcomponents.sdk.TaskHandle import org.matrix.rustcomponents.sdk.VerificationState import org.matrix.rustcomponents.sdk.VerificationStateListener @@ -224,66 +227,70 @@ class RustMatrixAuthenticationService @Inject constructor( } } - override suspend fun loginWithQrCode(qrCodeData: MatrixQrCodeLoginData, progress: (QrCodeLoginStep) -> Unit) = runCatching { - val client = rustMatrixClientFactory.getBaseClientBuilder() - .passphrase(pendingPassphrase) - .buildWithQrCode( - qrCodeData = (qrCodeData as SdkQrCodeLoginData).rustQrCodeData, - oidcConfiguration = oidcConfiguration, - progressListener = object : QrLoginProgressListener { - override fun onUpdate(state: QrLoginProgress) { - println("QR Code login progress: $state") - progress(state.toStep()) - } - } - ) - val sessionData = client.session() - .toSessionData( - isTokenValid = true, - loginType = LoginType.OIDC, - passphrase = pendingPassphrase, - needsVerification = true, - ) - pendingOidcAuthenticationData?.close() - pendingOidcAuthenticationData = null - sessionStore.storeData(sessionData) + override suspend fun loginWithQrCode(qrCodeData: MatrixQrCodeLoginData, progress: (QrCodeLoginStep) -> Unit) = + withContext(coroutineDispatchers.io) { + runCatching { + val client = rustMatrixClientFactory.getBaseClientBuilder() + .passphrase(pendingPassphrase) + .buildWithQrCode( + qrCodeData = (qrCodeData as SdkQrCodeLoginData).rustQrCodeData, + oidcConfiguration = oidcConfiguration, + progressListener = object : QrLoginProgressListener { + override fun onUpdate(state: QrLoginProgress) { + Timber.d("QR Code login progress: $state") + progress(state.toStep()) + } + } + ) + client.use { rustClient -> + val sessionData = rustClient.session() + .toSessionData( + isTokenValid = true, + loginType = LoginType.OIDC, + passphrase = pendingPassphrase, + needsVerification = true, + ) + sessionStore.storeData(sessionData) - val recoveryMutex = Mutex(locked = true) - val verificationMutex = Mutex(locked = true) - val encryptionService = client.encryption() + val recoveryMutex = Mutex(locked = true) + val verificationMutex = Mutex(locked = true) + val encryptionService = rustClient.encryption() - val syncService = client.syncService().finish() - syncService.start() + val syncService = rustClient.syncService().finish() + syncService.start() - var recoveryStateHandle: TaskHandle? = null - var verificationStateHandle: TaskHandle? = null - recoveryStateHandle = encryptionService.recoveryStateListener(object : RecoveryStateListener { - override fun onUpdate(status: RecoveryState) { - Timber.d("QR Login recovery state: $status") - if (status == RecoveryState.ENABLED) { - recoveryMutex.unlock() - recoveryStateHandle?.cancelAndDestroy() + var recoveryStateHandle: TaskHandle? = null + var verificationStateHandle: TaskHandle? = null + recoveryStateHandle = encryptionService.recoveryStateListener(object : RecoveryStateListener { + override fun onUpdate(status: RecoveryState) { + Timber.d("QR Login recovery state: $status") + if (status == RecoveryState.ENABLED) { + recoveryMutex.unlock() + recoveryStateHandle?.cancelAndDestroy() + } + } + }) + verificationStateHandle = encryptionService.verificationStateListener(object : VerificationStateListener { + override fun onUpdate(status: VerificationState) { + Timber.d("QR Login Verification state: $status") + if (status == VerificationState.VERIFIED) { + verificationMutex.unlock() + verificationStateHandle?.cancelAndDestroy() + } + } + }) + verificationMutex.lock() + recoveryMutex.lock() + + syncService.stop() + syncService.destroy() + + SessionId(sessionData.userId) } - } - }) - verificationStateHandle = encryptionService.verificationStateListener(object : VerificationStateListener { - override fun onUpdate(status: VerificationState) { - Timber.d("QR Login Verification state: $status") - if (status == VerificationState.VERIFIED) { - verificationMutex.unlock() - verificationStateHandle?.cancelAndDestroy() + }.onFailure { throwable -> + if (throwable is CancellationException) { + throw throwable } } - }) - verificationMutex.lock() - recoveryMutex.lock() - - syncService.stop() - syncService.destroy() - client.destroy() - - SessionId(sessionData.userId) - }.onFailure { - it.printStackTrace() } } From 2fc6499f552ef02a621d62bc91c85f443e43af22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= Date: Thu, 25 Apr 2024 16:03:35 +0200 Subject: [PATCH 17/60] Add some tests to onboarding screen --- features/onboarding/impl/build.gradle.kts | 10 ++ .../onboarding/impl/OnboardingViewTest.kt | 148 ++++++++++++++++++ .../test/auth/FakeAuthenticationService.kt | 12 +- 3 files changed, 169 insertions(+), 1 deletion(-) create mode 100644 features/onboarding/impl/src/test/kotlin/io/element/android/features/onboarding/impl/OnboardingViewTest.kt diff --git a/features/onboarding/impl/build.gradle.kts b/features/onboarding/impl/build.gradle.kts index 9994eacf81a..b5c073eac0a 100644 --- a/features/onboarding/impl/build.gradle.kts +++ b/features/onboarding/impl/build.gradle.kts @@ -23,6 +23,12 @@ plugins { android { namespace = "io.element.android.features.onboarding.impl" + + testOptions { + unitTests { + isIncludeAndroidResources = true + } + } } anvil { @@ -43,10 +49,14 @@ dependencies { ksp(libs.showkase.processor) testImplementation(libs.test.junit) + testImplementation(libs.androidx.compose.ui.test.junit) + testImplementation(libs.androidx.test.ext.junit) testImplementation(libs.coroutines.test) testImplementation(libs.molecule.runtime) + testImplementation(libs.test.robolectric) testImplementation(libs.test.truth) testImplementation(libs.test.turbine) testImplementation(projects.libraries.matrix.test) testImplementation(projects.tests.testutils) + testReleaseImplementation(libs.androidx.compose.ui.test.manifest) } diff --git a/features/onboarding/impl/src/test/kotlin/io/element/android/features/onboarding/impl/OnboardingViewTest.kt b/features/onboarding/impl/src/test/kotlin/io/element/android/features/onboarding/impl/OnboardingViewTest.kt new file mode 100644 index 00000000000..cc618254074 --- /dev/null +++ b/features/onboarding/impl/src/test/kotlin/io/element/android/features/onboarding/impl/OnboardingViewTest.kt @@ -0,0 +1,148 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.onboarding.impl + +import androidx.activity.ComponentActivity +import androidx.compose.ui.test.hasContentDescription +import androidx.compose.ui.test.junit4.AndroidComposeTestRule +import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.compose.ui.test.performClick +import androidx.test.ext.junit.runners.AndroidJUnit4 +import io.element.android.libraries.ui.strings.CommonStrings +import io.element.android.tests.testutils.EnsureNeverCalled +import io.element.android.tests.testutils.clickOn +import io.element.android.tests.testutils.ensureCalledOnce +import org.junit.Rule +import org.junit.Test +import org.junit.rules.TestRule +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class OnboardingViewTest { + @get:Rule + val rule = createAndroidComposeRule() + + @Test + fun `when can create account - clicking on create account calls the expected callback`() { + ensureCalledOnce { callback -> + rule.setOnboardingView( + state = OnBoardingState( + isDebugBuild = false, + canLoginWithQrCode = false, + canCreateAccount = true, + ), + onCreateAccount = callback, + ) + rule.clickOn(R.string.screen_onboarding_sign_up) + } + } + + @Test + fun `when can login with QR code - clicking on sign in with QR code calls the expected callback`() { + ensureCalledOnce { callback -> + rule.setOnboardingView( + state = OnBoardingState( + isDebugBuild = false, + canLoginWithQrCode = true, + canCreateAccount = false, + ), + onSignInWithQrCode = callback, + ) + rule.clickOn(R.string.screen_onboarding_sign_in_with_qr_code) + } + } + + @Test + fun `when can login with QR code - clicking on sign in manually calls the expected callback`() { + ensureCalledOnce { callback -> + rule.setOnboardingView( + state = OnBoardingState( + isDebugBuild = false, + canLoginWithQrCode = true, + canCreateAccount = false, + ), + onSignIn = callback, + ) + rule.clickOn(R.string.screen_onboarding_sign_in_manually) + } + } + + @Test + fun `when cannot login with QR code or create account - clicking on continue calls the sign in callback`() { + ensureCalledOnce { callback -> + rule.setOnboardingView( + state = OnBoardingState( + isDebugBuild = false, + canLoginWithQrCode = false, + canCreateAccount = false, + ), + onSignIn = callback, + ) + rule.clickOn(CommonStrings.action_continue) + } + } + + @Test + fun `when on debug build - clicking on the settings icon opens the developer settings`() { + ensureCalledOnce { callback -> + rule.setOnboardingView( + state = OnBoardingState( + isDebugBuild = true, + canLoginWithQrCode = false, + canCreateAccount = false, + ), + onOpenDeveloperSettings = callback + ) + rule.onNode(hasContentDescription(rule.activity.getString(CommonStrings.common_settings))).performClick() + } + } + + @Test + fun `clicking on report a problem calls the sign in callback`() { + ensureCalledOnce { callback -> + rule.setOnboardingView( + state = OnBoardingState( + isDebugBuild = false, + canLoginWithQrCode = false, + canCreateAccount = false, + ), + onReportProblem = callback, + ) + rule.clickOn(CommonStrings.common_report_a_problem) + } + } + + private fun AndroidComposeTestRule.setOnboardingView( + state: OnBoardingState, + onSignInWithQrCode: () -> Unit = EnsureNeverCalled(), + onSignIn: () -> Unit = EnsureNeverCalled(), + onCreateAccount: () -> Unit = EnsureNeverCalled(), + onOpenDeveloperSettings: () -> Unit = EnsureNeverCalled(), + onReportProblem: () -> Unit = EnsureNeverCalled(), + ) { + setContent { + OnBoardingView( + state = state, + onSignInWithQrCode = onSignInWithQrCode, + onSignIn = onSignIn, + onCreateAccount = onCreateAccount, + onOpenDeveloperSettings = onOpenDeveloperSettings, + onReportProblem = onReportProblem, + ) + } + } +} diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/auth/FakeAuthenticationService.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/auth/FakeAuthenticationService.kt index 2cf6b77a788..96d53239840 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/auth/FakeAuthenticationService.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/auth/FakeAuthenticationService.kt @@ -20,9 +20,13 @@ import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService import io.element.android.libraries.matrix.api.auth.MatrixHomeServerDetails import io.element.android.libraries.matrix.api.auth.OidcDetails +import io.element.android.libraries.matrix.api.auth.qrlogin.MatrixQrCodeLoginData +import io.element.android.libraries.matrix.api.auth.qrlogin.QrCodeLoginStep import io.element.android.libraries.matrix.api.core.SessionId +import io.element.android.libraries.matrix.test.A_SESSION_ID import io.element.android.libraries.matrix.test.A_USER_ID import io.element.android.libraries.sessionstorage.api.LoggedInState +import io.element.android.tests.testutils.lambda.lambdaRecorder import io.element.android.tests.testutils.simulateLongTask import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow @@ -31,7 +35,9 @@ import kotlinx.coroutines.flow.flowOf val A_OIDC_DATA = OidcDetails(url = "a-url") -class FakeAuthenticationService : MatrixAuthenticationService { +class FakeAuthenticationService( + var loginWithQrCodeResult: () -> Result = lambdaRecorder> { Result.success(A_SESSION_ID) }, +) : MatrixAuthenticationService { private val homeserver = MutableStateFlow(null) private var oidcError: Throwable? = null private var oidcCancelError: Throwable? = null @@ -83,6 +89,10 @@ class FakeAuthenticationService : MatrixAuthenticationService { loginError?.let { Result.failure(it) } ?: Result.success(A_USER_ID) } + override suspend fun loginWithQrCode(qrCodeData: MatrixQrCodeLoginData, progress: (QrCodeLoginStep) -> Unit): Result = simulateLongTask { + loginWithQrCodeResult() + } + fun givenOidcError(throwable: Throwable?) { oidcError = throwable } From f4b51f9f322295df88b7410f25d6571d3bdab8f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= Date: Thu, 25 Apr 2024 16:27:14 +0200 Subject: [PATCH 18/60] Add some tests to QR code intro screen --- features/login/impl/build.gradle.kts | 5 + .../qrcode/intro/QrCodeIntroPresenter.kt | 2 +- .../qrcode/intro/QrCodeIntroPresenterTest.kt | 80 ++++++++++++++++ .../qrcode/intro/QrCodeIntroViewTest.kt | 95 +++++++++++++++++++ 4 files changed, 181 insertions(+), 1 deletion(-) create mode 100644 features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/qrcode/intro/QrCodeIntroPresenterTest.kt create mode 100644 features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/qrcode/intro/QrCodeIntroViewTest.kt diff --git a/features/login/impl/build.gradle.kts b/features/login/impl/build.gradle.kts index 173bcb61ef6..25bf1dc1b3e 100644 --- a/features/login/impl/build.gradle.kts +++ b/features/login/impl/build.gradle.kts @@ -59,10 +59,15 @@ dependencies { ksp(libs.showkase.processor) testImplementation(libs.test.junit) + testImplementation(libs.androidx.compose.ui.test.junit) + testImplementation(libs.androidx.test.ext.junit) testImplementation(libs.coroutines.test) testImplementation(libs.molecule.runtime) + testImplementation(libs.test.robolectric) testImplementation(libs.test.truth) testImplementation(libs.test.turbine) testImplementation(projects.libraries.matrix.test) + testImplementation(projects.libraries.permissions.test) testImplementation(projects.tests.testutils) + testReleaseImplementation(libs.androidx.compose.ui.test.manifest) } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/intro/QrCodeIntroPresenter.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/intro/QrCodeIntroPresenter.kt index 0e4dd527cdb..fbe951ebb12 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/intro/QrCodeIntroPresenter.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/intro/QrCodeIntroPresenter.kt @@ -34,7 +34,7 @@ class QrCodeIntroPresenter @Inject constructor( permissionsPresenterFactory: PermissionsPresenter.Factory, ) : Presenter { private val cameraPermissionPresenter: PermissionsPresenter = permissionsPresenterFactory.create(Manifest.permission.CAMERA) - private var pendingPermissionRequest = false + private var pendingPermissionRequest by mutableStateOf(false) @Composable override fun present(): QrCodeIntroState { diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/qrcode/intro/QrCodeIntroPresenterTest.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/qrcode/intro/QrCodeIntroPresenterTest.kt new file mode 100644 index 00000000000..336e8d2114c --- /dev/null +++ b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/qrcode/intro/QrCodeIntroPresenterTest.kt @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.login.impl.screens.qrcode.intro + +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.libraries.core.meta.BuildMeta +import io.element.android.libraries.matrix.test.core.aBuildMeta +import io.element.android.libraries.permissions.test.FakePermissionsPresenter +import io.element.android.libraries.permissions.test.FakePermissionsPresenterFactory +import kotlinx.coroutines.test.runTest +import org.junit.Test + +class QrCodeIntroPresenterTest { + @Test + fun `present - initial state`() = runTest { + val presenter = createQrCodeIntroPresenter() + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + awaitItem().run { + assertThat(desktopAppName).isEmpty() + assertThat(cameraPermissionState.permission).isEqualTo("android.permission.POST_NOTIFICATIONS") + assertThat(canContinue).isFalse() + } + } + } + + @Test + fun `present - Continue with camera permissions can continue`() = runTest { + val permissionsPresenter = FakePermissionsPresenter().apply { setPermissionGranted() } + val permissionsPresenterFactory = FakePermissionsPresenterFactory(permissionsPresenter) + val presenter = createQrCodeIntroPresenter(permissionsPresenterFactory = permissionsPresenterFactory) + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + awaitItem().eventSink(QrCodeIntroEvents.Continue) + assertThat(awaitItem().canContinue).isTrue() + } + } + + @Test + fun `present - Continue with unknown camera permissions opens permission dialog`() = runTest { + val permissionsPresenter = FakePermissionsPresenter() + val permissionsPresenterFactory = FakePermissionsPresenterFactory(permissionsPresenter) + val presenter = createQrCodeIntroPresenter(permissionsPresenterFactory = permissionsPresenterFactory) + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + awaitItem().eventSink(QrCodeIntroEvents.Continue) + assertThat(awaitItem().cameraPermissionState.showDialog).isTrue() + } + } + + private fun createQrCodeIntroPresenter( + buildMeta: BuildMeta = aBuildMeta(), + permissionsPresenterFactory: FakePermissionsPresenterFactory = FakePermissionsPresenterFactory(), + ): QrCodeIntroPresenter { + return QrCodeIntroPresenter( + buildMeta = buildMeta, + permissionsPresenterFactory = permissionsPresenterFactory, + ) + } +} diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/qrcode/intro/QrCodeIntroViewTest.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/qrcode/intro/QrCodeIntroViewTest.kt new file mode 100644 index 00000000000..21a030b5135 --- /dev/null +++ b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/qrcode/intro/QrCodeIntroViewTest.kt @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.login.impl.screens.qrcode.intro + +import androidx.activity.ComponentActivity +import androidx.compose.ui.test.junit4.AndroidComposeTestRule +import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.test.ext.junit.runners.AndroidJUnit4 +import io.element.android.libraries.ui.strings.CommonStrings +import io.element.android.tests.testutils.EnsureNeverCalled +import io.element.android.tests.testutils.EventsRecorder +import io.element.android.tests.testutils.clickOn +import io.element.android.tests.testutils.ensureCalledOnce +import io.element.android.tests.testutils.pressBack +import io.element.android.tests.testutils.pressBackKey +import org.junit.Rule +import org.junit.Test +import org.junit.rules.TestRule +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class QrCodeIntroViewTest { + @get:Rule + val rule = createAndroidComposeRule() + + @Test + fun `on back pressed - calls the expected callback`() { + ensureCalledOnce { callback -> + rule.setQrCodeIntroView( + state = aQrCodeIntroState(), + onBackClicked = callback + ) + rule.pressBackKey() + } + } + + @Test + fun `on back button clicked - calls the expected callback`() { + ensureCalledOnce { callback -> + rule.setQrCodeIntroView( + state = aQrCodeIntroState(), + onBackClicked = callback + ) + rule.pressBack() + } + } + + @Test + fun `when can continue - calls the expected callback`() { + ensureCalledOnce { callback -> + rule.setQrCodeIntroView( + state = aQrCodeIntroState(canContinue = true), + onContinue = callback + ) + } + } + + @Test + fun `on continue button clicked - emits the Continue event`() { + val eventRecorder = EventsRecorder() + rule.setQrCodeIntroView( + state = aQrCodeIntroState(eventSink = eventRecorder), + ) + rule.clickOn(CommonStrings.action_continue) + eventRecorder.assertSingle(QrCodeIntroEvents.Continue) + } + + private fun AndroidComposeTestRule.setQrCodeIntroView( + state: QrCodeIntroState, + onBackClicked: () -> Unit = EnsureNeverCalled(), + onContinue: () -> Unit = EnsureNeverCalled(), + ) { + setContent { + QrCodeIntroView( + state = state, + onBackClicked = onBackClicked, + onContinue = onContinue, + ) + } + } +} From d6ef9be562004d7f62a8542e52cae15663fbe7b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= Date: Fri, 26 Apr 2024 11:43:44 +0200 Subject: [PATCH 19/60] Fix generic error screen not being displayed --- .../screens/qrcode/error/QrCodeErrorNode.kt | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/error/QrCodeErrorNode.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/error/QrCodeErrorNode.kt index 4ebc166ed8f..b33601d0094 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/error/QrCodeErrorNode.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/error/QrCodeErrorNode.kt @@ -16,9 +16,12 @@ package io.element.android.features.login.impl.screens.qrcode.error +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.plugin.Plugin +import com.bumble.appyx.core.plugin.plugins import dagger.assisted.Assisted import dagger.assisted.AssistedInject import io.element.android.anvilannotations.ContributesNode @@ -28,7 +31,17 @@ import io.element.android.libraries.di.AppScope class QrCodeErrorNode @AssistedInject constructor( @Assisted buildContext: BuildContext, @Assisted plugins: List, -) : Node(buildContext = buildContext, plugins = plugins -) { +) : Node(buildContext = buildContext, plugins = plugins) { + interface Callback: Plugin { + fun onRetry() + } + private fun onRetry() { + plugins().forEach { it.onRetry() } + } + + @Composable + override fun View(modifier: Modifier) { + QrCodeErrorView(onRetry = ::onRetry) + } } From 5791d3223199b84fba800568391319512797b6f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= Date: Fri, 26 Apr 2024 11:43:52 +0200 Subject: [PATCH 20/60] More tests --- .../screens/qrcode/scan/QrCodeScanView.kt | 2 - .../qrcode/scan/QrCodeScanPresenterTest.kt | 100 ++++++++++++++++++ .../screens/qrcode/scan/QrCodeScanViewTest.kt | 75 +++++++++++++ 3 files changed, 175 insertions(+), 2 deletions(-) create mode 100644 features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/qrcode/scan/QrCodeScanPresenterTest.kt create mode 100644 features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/qrcode/scan/QrCodeScanViewTest.kt 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 d1b6414fd35..f43b08a9aef 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 @@ -16,7 +16,6 @@ package io.element.android.features.login.impl.screens.qrcode.scan -import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.BoxWithConstraints @@ -57,7 +56,6 @@ import io.element.android.libraries.designsystem.theme.components.Icon import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.matrix.api.auth.qrlogin.MatrixQrCodeLoginData import io.element.android.libraries.qrcode.QrCodeCameraView -import io.element.android.libraries.ui.strings.CommonStrings @Composable fun QrCodeScanView( 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 new file mode 100644 index 00000000000..7f25737f8d9 --- /dev/null +++ b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/qrcode/scan/QrCodeScanPresenterTest.kt @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.login.impl.screens.qrcode.scan + +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.libraries.architecture.AsyncAction +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.lambdaRecorder +import kotlinx.coroutines.test.runTest +import org.junit.Test + +class QrCodeScanPresenterTest { + @Test + fun `present - initial state`() = runTest { + val presenter = createQrCodeScanPresenter() + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + awaitItem().run { + assertThat(isScanning).isTrue() + assertThat(authenticationAction.isUninitialized()).isTrue() + } + } + } + + @Test + fun `present - scanned QR code successfully`() = runTest { + val presenter = createQrCodeScanPresenter() + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + val initialState = awaitItem() + initialState.eventSink(QrCodeScanEvents.QrCodeScanned(byteArrayOf())) + awaitItem().run { + assertThat(isScanning).isFalse() + assertThat(authenticationAction.isLoading()).isTrue() + } + assertThat(awaitItem().authenticationAction).isEqualTo(AsyncAction.Success(Result.success(FakeQrCodeLoginData()))) + } + } + + @Test + fun `present - scanned QR code failed and can be retried`() = runTest { + val qrCodeLoginDataFactory = FakeMatrixQrCodeLoginDataFactory( + parseQrCodeLoginDataResult = { Result.failure(Exception("Failed to parse QR code")) } + ) + val presenter = createQrCodeScanPresenter(qrCodeLoginDataFactory = qrCodeLoginDataFactory) + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + val initialState = awaitItem() + initialState.eventSink(QrCodeScanEvents.QrCodeScanned(byteArrayOf())) + awaitItem().run { + assertThat(isScanning).isFalse() + assertThat(authenticationAction.isLoading()).isTrue() + } + val errorState = awaitItem() + assertThat(errorState.authenticationAction.isFailure()).isTrue() + + errorState.eventSink(QrCodeScanEvents.TryAgain) + awaitItem().run { + assertThat(isScanning).isTrue() + assertThat(authenticationAction.isUninitialized()).isTrue() + } + } + } + + private fun createQrCodeScanPresenter( + qrCodeLoginDataFactory: FakeMatrixQrCodeLoginDataFactory = FakeMatrixQrCodeLoginDataFactory(), + ) = QrCodeScanPresenter(qrCodeLoginDataFactory) +} + +class FakeMatrixQrCodeLoginDataFactory( + var parseQrCodeLoginDataResult: () -> Result = + lambdaRecorder> { Result.success(FakeQrCodeLoginData()) }, +) : MatrixQrCodeLoginDataFactory { + override fun parseQrCodeData(data: ByteArray): Result { + return parseQrCodeLoginDataResult() + } +} + +class FakeQrCodeLoginData : MatrixQrCodeLoginData diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/qrcode/scan/QrCodeScanViewTest.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/qrcode/scan/QrCodeScanViewTest.kt new file mode 100644 index 00000000000..01763db962e --- /dev/null +++ b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/qrcode/scan/QrCodeScanViewTest.kt @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.login.impl.screens.qrcode.scan + +import androidx.activity.ComponentActivity +import androidx.compose.ui.test.junit4.AndroidComposeTestRule +import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.test.ext.junit.runners.AndroidJUnit4 +import io.element.android.libraries.architecture.AsyncAction +import io.element.android.libraries.matrix.api.auth.qrlogin.MatrixQrCodeLoginData +import io.element.android.tests.testutils.EnsureNeverCalled +import io.element.android.tests.testutils.EnsureNeverCalledWithParam +import io.element.android.tests.testutils.ensureCalledOnce +import io.element.android.tests.testutils.ensureCalledOnceWithParam +import io.element.android.tests.testutils.pressBackKey +import org.junit.Rule +import org.junit.Test +import org.junit.rules.TestRule +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class QrCodeScanViewTest { + @get:Rule + val rule = createAndroidComposeRule() + + @Test + fun `on back pressed - calls the expected callback`() { + ensureCalledOnce { callback -> + rule.setQrCodeScanView( + state = aQrCodeScanState(), + onBackClicked = callback + ) + rule.pressBackKey() + } + } + + @Test + fun `on QR code data ready - calls the expected callback`() { + val data = FakeQrCodeLoginData() + ensureCalledOnceWithParam(data) { callback -> + rule.setQrCodeScanView( + state = aQrCodeScanState(authenticationAction = AsyncAction.Success(data)), + onQrCodeDataReady = callback + ) + } + } + + private fun AndroidComposeTestRule.setQrCodeScanView( + state: QrCodeScanState, + onBackClicked: () -> Unit = EnsureNeverCalled(), + onQrCodeDataReady: (MatrixQrCodeLoginData) -> Unit = EnsureNeverCalledWithParam(), + ) { + setContent { + QrCodeScanView( + state = state, + onBackClicked = onBackClicked, + onQrCodeDataReady = onQrCodeDataReady + ) + } + } +} From 7b2c4ad48768eca162f9ef9aad75b63933b590cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= Date: Fri, 26 Apr 2024 12:21:20 +0200 Subject: [PATCH 21/60] Add minimal tests to `QrCodeLoginFlowNode`. Rename `QrCodeLoginPresenter` to `QrCodeLoginManager`, since it's not really a presenter. --- .../login/impl/qrcode/QrCodeLoginFlowNode.kt | 31 +++++--- ...oginPresenter.kt => QrCodeLoginManager.kt} | 15 +++- .../impl/qrcode/QrCodeLoginFlowNodeTest.kt | 79 +++++++++++++++++++ 3 files changed, 109 insertions(+), 16 deletions(-) rename features/login/impl/src/main/kotlin/io/element/android/features/login/impl/qrcode/{QrCodeLoginPresenter.kt => QrCodeLoginManager.kt} (80%) create mode 100644 features/login/impl/src/test/kotlin/io/element/android/features/login/impl/qrcode/QrCodeLoginFlowNodeTest.kt diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/qrcode/QrCodeLoginFlowNode.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/qrcode/QrCodeLoginFlowNode.kt index 2aab2d85078..08b753f221d 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/qrcode/QrCodeLoginFlowNode.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/qrcode/QrCodeLoginFlowNode.kt @@ -17,6 +17,7 @@ package io.element.android.features.login.impl.qrcode import android.os.Parcelable +import androidx.annotation.VisibleForTesting import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.lifecycle.lifecycleScope @@ -42,18 +43,18 @@ import io.element.android.libraries.architecture.createNode import io.element.android.libraries.di.AppScope import io.element.android.libraries.matrix.api.auth.qrlogin.MatrixQrCodeLoginData import io.element.android.libraries.matrix.api.auth.qrlogin.QrCodeLoginStep +import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job import kotlinx.coroutines.launch import kotlinx.parcelize.Parcelize import timber.log.Timber -import kotlin.coroutines.coroutineContext @ContributesNode(AppScope::class) class QrCodeLoginFlowNode @AssistedInject constructor( @Assisted buildContext: BuildContext, @Assisted plugins: List, - private val qrCodeLoginPresenter: QrCodeLoginPresenter, + private val qrCodeLoginManager: QrCodeLoginManager, ) : BaseFlowNode( backstack = BackStack( initialElement = NavTarget.Initial, @@ -82,8 +83,13 @@ class QrCodeLoginFlowNode @AssistedInject constructor( override fun onBuilt() { super.onBuilt() + observeLoginStep() + } + + @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) + internal fun observeLoginStep() { lifecycleScope.launch { - qrCodeLoginPresenter.currentLoginStep + qrCodeLoginManager.currentLoginStep .collect { step -> when (step) { is QrCodeLoginStep.EstablishingSecureChannel -> { @@ -115,9 +121,7 @@ class QrCodeLoginFlowNode @AssistedInject constructor( is NavTarget.QrCodeScan -> { val callback = object : QrCodeScanNode.Callback { override fun onScannedCode(qrCodeLoginData: MatrixQrCodeLoginData) { - lifecycleScope.launch { - startAuthentication(qrCodeLoginData) - } + lifecycleScope.startAuthentication(qrCodeLoginData) } override fun onCancelClicked() { @@ -143,18 +147,21 @@ class QrCodeLoginFlowNode @AssistedInject constructor( } } - private suspend fun startAuthentication(qrCodeLoginData: MatrixQrCodeLoginData) { - authenticationJob = CoroutineScope(coroutineContext).launch { - qrCodeLoginPresenter.authenticate(qrCodeLoginData) + private fun CoroutineScope.startAuthentication(qrCodeLoginData: MatrixQrCodeLoginData) { + authenticationJob = launch { + qrCodeLoginManager.authenticate(qrCodeLoginData) .onSuccess { println("Logged into session $it") authenticationJob = null } - .onFailure { + .onFailure { throwable -> // TODO specify the error type - Timber.e(it, "QR code authentication failed") - backstack.push(NavTarget.Error(it.message ?: "Unknown error")) + Timber.e(throwable, "QR code authentication failed") authenticationJob = null + if (throwable is CancellationException) { + throw throwable + } + backstack.push(NavTarget.Error(throwable.message ?: "Unknown error")) } } } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/qrcode/QrCodeLoginPresenter.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/qrcode/QrCodeLoginManager.kt similarity index 80% rename from features/login/impl/src/main/kotlin/io/element/android/features/login/impl/qrcode/QrCodeLoginPresenter.kt rename to features/login/impl/src/main/kotlin/io/element/android/features/login/impl/qrcode/QrCodeLoginManager.kt index c4ad0b44c5e..28a19c79dc0 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/qrcode/QrCodeLoginPresenter.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/qrcode/QrCodeLoginManager.kt @@ -16,6 +16,7 @@ package io.element.android.features.login.impl.qrcode +import com.squareup.anvil.annotations.ContributesBinding import io.element.android.features.login.impl.DefaultLoginUserStory import io.element.android.libraries.di.AppScope import io.element.android.libraries.di.SingleIn @@ -28,14 +29,15 @@ import kotlinx.coroutines.flow.StateFlow import javax.inject.Inject @SingleIn(AppScope::class) -class QrCodeLoginPresenter @Inject constructor( +@ContributesBinding(AppScope::class) +class QrCodeLoginManagerImpl @Inject constructor( private val authenticationService: MatrixAuthenticationService, private val defaultLoginUserStory: DefaultLoginUserStory, -) { +): QrCodeLoginManager { private val _currentLoginStep = MutableStateFlow(QrCodeLoginStep.Uninitialized) - val currentLoginStep: StateFlow = _currentLoginStep + override val currentLoginStep: StateFlow = _currentLoginStep - suspend fun authenticate(qrCodeLoginData: MatrixQrCodeLoginData): Result { + override suspend fun authenticate(qrCodeLoginData: MatrixQrCodeLoginData): Result { _currentLoginStep.value = QrCodeLoginStep.Uninitialized return authenticationService.loginWithQrCode(qrCodeLoginData) { step -> @@ -45,3 +47,8 @@ class QrCodeLoginPresenter @Inject constructor( } } } + +interface QrCodeLoginManager { + val currentLoginStep: StateFlow + suspend fun authenticate(qrCodeLoginData: MatrixQrCodeLoginData): Result +} diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/qrcode/QrCodeLoginFlowNodeTest.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/qrcode/QrCodeLoginFlowNodeTest.kt new file mode 100644 index 00000000000..d8fc8496831 --- /dev/null +++ b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/qrcode/QrCodeLoginFlowNodeTest.kt @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.login.impl.qrcode + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.bumble.appyx.core.modality.AncestryInfo +import com.bumble.appyx.core.modality.BuildContext +import com.bumble.appyx.utils.customisations.NodeCustomisationDirectoryImpl +import com.google.common.truth.Truth.assertThat +import io.element.android.features.login.impl.screens.qrcode.confirmation.QrCodeConfirmationStep +import io.element.android.libraries.matrix.api.auth.qrlogin.MatrixQrCodeLoginData +import io.element.android.libraries.matrix.api.auth.qrlogin.QrCodeLoginStep +import io.element.android.libraries.matrix.api.core.SessionId +import io.element.android.libraries.matrix.test.A_SESSION_ID +import io.element.android.tests.testutils.lambda.lambdaRecorder +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class QrCodeLoginFlowNodeTest { + @Test + fun `backstack changes when confirmation steps are received`() = runTest { + val qrCodeLoginManager = FakeQrCodeLoginManager() + val flowNode = createLoginFlowNode(qrCodeLoginManager = qrCodeLoginManager) + flowNode.observeLoginStep() + assertThat(flowNode.currentNavTarget()).isEqualTo(QrCodeLoginFlowNode.NavTarget.Initial) + + qrCodeLoginManager.currentLoginStep.value = QrCodeLoginStep.EstablishingSecureChannel("12") + assertThat(flowNode.currentNavTarget()).isEqualTo(QrCodeLoginFlowNode.NavTarget.QrCodeConfirmation(QrCodeConfirmationStep.DisplayCheckCode("12"))) + + qrCodeLoginManager.currentLoginStep.value = QrCodeLoginStep.WaitingForToken("123456") + assertThat(flowNode.currentNavTarget()) + .isEqualTo(QrCodeLoginFlowNode.NavTarget.QrCodeConfirmation(QrCodeConfirmationStep.DisplayVerificationCode("123456"))) + } + + private fun createLoginFlowNode( + qrCodeLoginManager: FakeQrCodeLoginManager = FakeQrCodeLoginManager(), + ): QrCodeLoginFlowNode { + val buildContext = BuildContext( + ancestryInfo = AncestryInfo.Root, + savedStateMap = null, + customisations = NodeCustomisationDirectoryImpl() + ) + return QrCodeLoginFlowNode( + buildContext = buildContext, + plugins = emptyList(), + qrCodeLoginManager = qrCodeLoginManager, + ) + } + + private fun QrCodeLoginFlowNode.currentNavTarget() = backstack.elements.value.last().key.navTarget +} + +class FakeQrCodeLoginManager( + var authenticateResult: (MatrixQrCodeLoginData) -> Result = + lambdaRecorder> { Result.success(A_SESSION_ID) }, +) : QrCodeLoginManager { + override val currentLoginStep: MutableStateFlow = MutableStateFlow(QrCodeLoginStep.Uninitialized) + + override suspend fun authenticate(qrCodeLoginData: MatrixQrCodeLoginData): Result { + return authenticateResult(qrCodeLoginData) + } +} From dc435f8f1ee66260790584190771c28563586650 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= Date: Fri, 26 Apr 2024 15:39:38 +0200 Subject: [PATCH 22/60] Workaround QrCodeErrorView crash --- .../login/impl/screens/qrcode/error/QrCodeErrorView.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/error/QrCodeErrorView.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/error/QrCodeErrorView.kt index 5e1ed2f47fb..b5d5e60c8e3 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/error/QrCodeErrorView.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/error/QrCodeErrorView.kt @@ -64,8 +64,7 @@ private fun Content() { Column( modifier = Modifier .fillMaxWidth() - .padding(top = 20.dp) - .verticalScroll(rememberScrollState()), + .padding(top = 20.dp), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy(24.dp) ) { @@ -75,6 +74,7 @@ private fun Content() { style = ElementTheme.typography.fontBodyLgMedium, textAlign = TextAlign.Center, ) + // TODO make `NumberedListOrganism` not rely on LazyColumn, restore vertical scroll to parent column NumberedListOrganism( items = persistentListOf( AnnotatedString(stringResource(R.string.screen_qr_code_login_connection_note_secure_state_list_item_1)), From 18a8fd899dd25d99b55c2a9690ed946f29e702cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= Date: Fri, 26 Apr 2024 15:39:52 +0200 Subject: [PATCH 23/60] Fix QR code login and verification --- .../login/impl/qrcode/QrCodeLoginFlowNode.kt | 14 +++- .../login/impl/qrcode/QrCodeLoginManager.kt | 3 - .../auth/RustMatrixAuthenticationService.kt | 75 +++++++++---------- 3 files changed, 49 insertions(+), 43 deletions(-) diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/qrcode/QrCodeLoginFlowNode.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/qrcode/QrCodeLoginFlowNode.kt index 08b753f221d..4b1e710f999 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/qrcode/QrCodeLoginFlowNode.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/qrcode/QrCodeLoginFlowNode.kt @@ -32,6 +32,7 @@ import com.bumble.appyx.navmodel.backstack.operation.replace import dagger.assisted.Assisted import dagger.assisted.AssistedInject import io.element.android.anvilannotations.ContributesNode +import io.element.android.features.login.impl.DefaultLoginUserStory import io.element.android.features.login.impl.screens.qrcode.confirmation.QrCodeConfirmationNode import io.element.android.features.login.impl.screens.qrcode.confirmation.QrCodeConfirmationStep import io.element.android.features.login.impl.screens.qrcode.error.QrCodeErrorNode @@ -55,6 +56,7 @@ class QrCodeLoginFlowNode @AssistedInject constructor( @Assisted buildContext: BuildContext, @Assisted plugins: List, private val qrCodeLoginManager: QrCodeLoginManager, + private val defaultLoginUserStory: DefaultLoginUserStory, ) : BaseFlowNode( backstack = BackStack( initialElement = NavTarget.Initial, @@ -141,8 +143,15 @@ class QrCodeLoginFlowNode @AssistedInject constructor( createNode(buildContext, plugins = listOf(navTarget.step, callback)) } is NavTarget.Error -> { + val callback = object : QrCodeErrorNode.Callback { + override fun onRetry() { + authenticationJob?.cancel() + authenticationJob = null + backstack.newRoot(NavTarget.Initial) + } + } // TODO specify the error type - createNode(buildContext) + createNode(buildContext, plugins = listOf(callback)) } } } @@ -151,7 +160,8 @@ class QrCodeLoginFlowNode @AssistedInject constructor( authenticationJob = launch { qrCodeLoginManager.authenticate(qrCodeLoginData) .onSuccess { - println("Logged into session $it") + defaultLoginUserStory.setLoginFlowIsDone(true) + backstack.newRoot(NavTarget.Initial) authenticationJob = null } .onFailure { throwable -> diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/qrcode/QrCodeLoginManager.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/qrcode/QrCodeLoginManager.kt index 28a19c79dc0..5c53c21274a 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/qrcode/QrCodeLoginManager.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/qrcode/QrCodeLoginManager.kt @@ -32,7 +32,6 @@ import javax.inject.Inject @ContributesBinding(AppScope::class) class QrCodeLoginManagerImpl @Inject constructor( private val authenticationService: MatrixAuthenticationService, - private val defaultLoginUserStory: DefaultLoginUserStory, ): QrCodeLoginManager { private val _currentLoginStep = MutableStateFlow(QrCodeLoginStep.Uninitialized) override val currentLoginStep: StateFlow = _currentLoginStep @@ -42,8 +41,6 @@ class QrCodeLoginManagerImpl @Inject constructor( return authenticationService.loginWithQrCode(qrCodeLoginData) { step -> _currentLoginStep.value = step - }.onSuccess { - defaultLoginUserStory.setLoginFlowIsDone(true) } } } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationService.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationService.kt index 212a828d88c..0b0dd108883 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationService.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationService.kt @@ -242,51 +242,50 @@ class RustMatrixAuthenticationService @Inject constructor( } } ) - client.use { rustClient -> - val sessionData = rustClient.session() - .toSessionData( - isTokenValid = true, - loginType = LoginType.OIDC, - passphrase = pendingPassphrase, - needsVerification = true, - ) - sessionStore.storeData(sessionData) + val sessionData = client.session() + .toSessionData( + isTokenValid = true, + loginType = LoginType.QR, + passphrase = pendingPassphrase, + needsVerification = false, + ) + sessionStore.storeData(sessionData) - val recoveryMutex = Mutex(locked = true) - val verificationMutex = Mutex(locked = true) - val encryptionService = rustClient.encryption() + val recoveryMutex = Mutex(locked = true) + val verificationMutex = Mutex(locked = true) + val encryptionService = client.encryption() - val syncService = rustClient.syncService().finish() - syncService.start() + val syncService = client.syncService().finish() + syncService.start() - var recoveryStateHandle: TaskHandle? = null - var verificationStateHandle: TaskHandle? = null - recoveryStateHandle = encryptionService.recoveryStateListener(object : RecoveryStateListener { - override fun onUpdate(status: RecoveryState) { - Timber.d("QR Login recovery state: $status") - if (status == RecoveryState.ENABLED) { - recoveryMutex.unlock() - recoveryStateHandle?.cancelAndDestroy() - } + var recoveryStateHandle: TaskHandle? = null + var verificationStateHandle: TaskHandle? = null + recoveryStateHandle = encryptionService.recoveryStateListener(object : RecoveryStateListener { + override fun onUpdate(status: RecoveryState) { + Timber.d("QR Login recovery state: $status") + if (status == RecoveryState.ENABLED) { + recoveryMutex.unlock() + recoveryStateHandle?.cancelAndDestroy() } - }) - verificationStateHandle = encryptionService.verificationStateListener(object : VerificationStateListener { - override fun onUpdate(status: VerificationState) { - Timber.d("QR Login Verification state: $status") - if (status == VerificationState.VERIFIED) { - verificationMutex.unlock() - verificationStateHandle?.cancelAndDestroy() - } + } + }) + verificationStateHandle = encryptionService.verificationStateListener(object : VerificationStateListener { + override fun onUpdate(status: VerificationState) { + Timber.d("QR Login Verification state: $status") + if (status == VerificationState.VERIFIED) { + verificationMutex.unlock() + verificationStateHandle?.cancelAndDestroy() } - }) - verificationMutex.lock() - recoveryMutex.lock() + } + }) + verificationMutex.lock() + recoveryMutex.lock() - syncService.stop() - syncService.destroy() + encryptionService.destroy() + syncService.stop() + syncService.destroy() - SessionId(sessionData.userId) - } + SessionId(sessionData.userId) }.onFailure { throwable -> if (throwable is CancellationException) { throw throwable From def8af2d9c855b6ca126de21a78f751a61e3bed1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= Date: Fri, 26 Apr 2024 16:41:25 +0200 Subject: [PATCH 24/60] Fix navigation issues in login flow by actually resetting the state once we're done --- .../login/impl/qrcode/QrCodeLoginFlowNode.kt | 1 - .../login/impl/qrcode/QrCodeLoginManager.kt | 5 +- .../auth/RustMatrixAuthenticationService.kt | 65 ++++--------------- 3 files changed, 15 insertions(+), 56 deletions(-) diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/qrcode/QrCodeLoginFlowNode.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/qrcode/QrCodeLoginFlowNode.kt index 4b1e710f999..cb56237443a 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/qrcode/QrCodeLoginFlowNode.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/qrcode/QrCodeLoginFlowNode.kt @@ -161,7 +161,6 @@ class QrCodeLoginFlowNode @AssistedInject constructor( qrCodeLoginManager.authenticate(qrCodeLoginData) .onSuccess { defaultLoginUserStory.setLoginFlowIsDone(true) - backstack.newRoot(NavTarget.Initial) authenticationJob = null } .onFailure { throwable -> diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/qrcode/QrCodeLoginManager.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/qrcode/QrCodeLoginManager.kt index 5c53c21274a..d66182a748a 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/qrcode/QrCodeLoginManager.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/qrcode/QrCodeLoginManager.kt @@ -17,7 +17,7 @@ package io.element.android.features.login.impl.qrcode import com.squareup.anvil.annotations.ContributesBinding -import io.element.android.features.login.impl.DefaultLoginUserStory +import io.element.android.libraries.core.extensions.finally import io.element.android.libraries.di.AppScope import io.element.android.libraries.di.SingleIn import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService @@ -41,6 +41,9 @@ class QrCodeLoginManagerImpl @Inject constructor( return authenticationService.loginWithQrCode(qrCodeLoginData) { step -> _currentLoginStep.value = step + }.finally { + // Whether the login was successful or not, we reset the current login step + _currentLoginStep.value = QrCodeLoginStep.Uninitialized } } } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationService.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationService.kt index 0b0dd108883..25807272c3c 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationService.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationService.kt @@ -18,7 +18,6 @@ package io.element.android.libraries.matrix.impl.auth import com.squareup.anvil.annotations.ContributesBinding import io.element.android.libraries.core.coroutine.CoroutineDispatchers -import io.element.android.libraries.core.extensions.finally import io.element.android.libraries.core.extensions.mapFailure import io.element.android.libraries.core.meta.BuildMeta import io.element.android.libraries.core.meta.BuildType @@ -39,7 +38,6 @@ import io.element.android.libraries.matrix.impl.exception.mapClientException import io.element.android.libraries.matrix.impl.keys.PassphraseGenerator import io.element.android.libraries.matrix.impl.mapper.toSessionData import io.element.android.libraries.matrix.impl.proxy.ProxyProvider -import io.element.android.libraries.matrix.impl.util.cancelAndDestroy import io.element.android.libraries.network.useragent.UserAgentProvider import io.element.android.libraries.sessionstorage.api.LoggedInState import io.element.android.libraries.sessionstorage.api.LoginType @@ -48,18 +46,10 @@ import kotlinx.coroutines.CancellationException import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.withContext -import org.matrix.rustcomponents.sdk.ClientBuilder import org.matrix.rustcomponents.sdk.OidcAuthenticationData import org.matrix.rustcomponents.sdk.QrLoginProgress import org.matrix.rustcomponents.sdk.QrLoginProgressListener -import org.matrix.rustcomponents.sdk.RecoveryState -import org.matrix.rustcomponents.sdk.RecoveryStateListener -import org.matrix.rustcomponents.sdk.SyncService -import org.matrix.rustcomponents.sdk.TaskHandle -import org.matrix.rustcomponents.sdk.VerificationState -import org.matrix.rustcomponents.sdk.VerificationStateListener import org.matrix.rustcomponents.sdk.use import timber.log.Timber import java.io.File @@ -242,50 +232,17 @@ class RustMatrixAuthenticationService @Inject constructor( } } ) - val sessionData = client.session() - .toSessionData( - isTokenValid = true, - loginType = LoginType.QR, - passphrase = pendingPassphrase, - needsVerification = false, - ) - sessionStore.storeData(sessionData) - - val recoveryMutex = Mutex(locked = true) - val verificationMutex = Mutex(locked = true) - val encryptionService = client.encryption() - - val syncService = client.syncService().finish() - syncService.start() - - var recoveryStateHandle: TaskHandle? = null - var verificationStateHandle: TaskHandle? = null - recoveryStateHandle = encryptionService.recoveryStateListener(object : RecoveryStateListener { - override fun onUpdate(status: RecoveryState) { - Timber.d("QR Login recovery state: $status") - if (status == RecoveryState.ENABLED) { - recoveryMutex.unlock() - recoveryStateHandle?.cancelAndDestroy() - } - } - }) - verificationStateHandle = encryptionService.verificationStateListener(object : VerificationStateListener { - override fun onUpdate(status: VerificationState) { - Timber.d("QR Login Verification state: $status") - if (status == VerificationState.VERIFIED) { - verificationMutex.unlock() - verificationStateHandle?.cancelAndDestroy() - } - } - }) - verificationMutex.lock() - recoveryMutex.lock() - - encryptionService.destroy() - syncService.stop() - syncService.destroy() - - SessionId(sessionData.userId) + client.use { rustClient -> + val sessionData = rustClient.session() + .toSessionData( + isTokenValid = true, + loginType = LoginType.QR, + passphrase = pendingPassphrase, + needsVerification = false, + ) + sessionStore.storeData(sessionData) + SessionId(sessionData.userId) + } }.onFailure { throwable -> if (throwable is CancellationException) { throw throwable From f3b2aa85c971e4e9698730122b38cbcb8baf6118 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= Date: Fri, 26 Apr 2024 16:50:39 +0200 Subject: [PATCH 25/60] Actually `QrCodeLoginManagerImpl` doesn't need to be a singleton --- .../features/login/impl/qrcode/QrCodeLoginManager.kt | 6 ------ 1 file changed, 6 deletions(-) diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/qrcode/QrCodeLoginManager.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/qrcode/QrCodeLoginManager.kt index d66182a748a..2dbc878cef7 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/qrcode/QrCodeLoginManager.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/qrcode/QrCodeLoginManager.kt @@ -17,9 +17,7 @@ package io.element.android.features.login.impl.qrcode import com.squareup.anvil.annotations.ContributesBinding -import io.element.android.libraries.core.extensions.finally import io.element.android.libraries.di.AppScope -import io.element.android.libraries.di.SingleIn import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService import io.element.android.libraries.matrix.api.auth.qrlogin.MatrixQrCodeLoginData import io.element.android.libraries.matrix.api.auth.qrlogin.QrCodeLoginStep @@ -28,7 +26,6 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import javax.inject.Inject -@SingleIn(AppScope::class) @ContributesBinding(AppScope::class) class QrCodeLoginManagerImpl @Inject constructor( private val authenticationService: MatrixAuthenticationService, @@ -41,9 +38,6 @@ class QrCodeLoginManagerImpl @Inject constructor( return authenticationService.loginWithQrCode(qrCodeLoginData) { step -> _currentLoginStep.value = step - }.finally { - // Whether the login was successful or not, we reset the current login step - _currentLoginStep.value = QrCodeLoginStep.Uninitialized } } } From 14775d58e6aa532599620dbbd9eb1e158973dfd4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= Date: Mon, 29 Apr 2024 15:48:48 +0200 Subject: [PATCH 26/60] Add and fix tests --- ...tionState.kt => QrCodeConfirmationStep.kt} | 4 -- ... QrCodeConfirmationStepPreviewProvider.kt} | 16 ++--- .../confirmation/QrCodeConfirmationView.kt | 8 ++- .../qrcode/scan/QrCodeScanPresenter.kt | 8 +-- .../impl/qrcode/QrCodeLoginFlowNodeTest.kt | 3 + .../QrCodeConfirmationViewTest.kt | 70 +++++++++++++++++++ .../qrcode/error/QrCodeErrorViewTest.kt | 64 +++++++++++++++++ .../qrcode/scan/QrCodeScanPresenterTest.kt | 48 +++++-------- .../screens/qrcode/scan/QrCodeScanViewTest.kt | 1 + .../impl/OnBoardingPresenterTest.kt | 2 +- .../designsystem/utils/ForceOrientation.kt | 14 ---- .../utils/ForceOrientationInMobileDevices.kt | 34 +++++++++ .../FakeMatrixQrCodeLoginDataFactory.kt | 32 +++++++++ 13 files changed, 237 insertions(+), 67 deletions(-) rename features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/confirmation/{QrCodeConfirmationState.kt => QrCodeConfirmationStep.kt} (93%) rename features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/confirmation/{QrConfirmationCodeStatePreviewProvider.kt => QrCodeConfirmationStepPreviewProvider.kt} (58%) create mode 100644 features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/qrcode/confirmation/QrCodeConfirmationViewTest.kt create mode 100644 features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/qrcode/error/QrCodeErrorViewTest.kt create mode 100644 libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/ForceOrientationInMobileDevices.kt create mode 100644 libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/auth/qrlogin/FakeMatrixQrCodeLoginDataFactory.kt diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/confirmation/QrCodeConfirmationState.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/confirmation/QrCodeConfirmationStep.kt similarity index 93% rename from features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/confirmation/QrCodeConfirmationState.kt rename to features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/confirmation/QrCodeConfirmationStep.kt index 55d8df60c1c..63848e83132 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/confirmation/QrCodeConfirmationState.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/confirmation/QrCodeConfirmationStep.kt @@ -20,10 +20,6 @@ import android.os.Parcelable import io.element.android.libraries.architecture.NodeInputs import kotlinx.parcelize.Parcelize -data class QrCodeConfirmationState( - val step: QrCodeConfirmationStep, -) - sealed interface QrCodeConfirmationStep : NodeInputs, Parcelable { @Parcelize data class DisplayCheckCode(val code: String) : QrCodeConfirmationStep diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/confirmation/QrConfirmationCodeStatePreviewProvider.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/confirmation/QrCodeConfirmationStepPreviewProvider.kt similarity index 58% rename from features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/confirmation/QrConfirmationCodeStatePreviewProvider.kt rename to features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/confirmation/QrCodeConfirmationStepPreviewProvider.kt index f7ae2e686e8..cd6125fee83 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/confirmation/QrConfirmationCodeStatePreviewProvider.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/confirmation/QrCodeConfirmationStepPreviewProvider.kt @@ -18,17 +18,11 @@ package io.element.android.features.login.impl.screens.qrcode.confirmation import androidx.compose.ui.tooling.preview.PreviewParameterProvider -class QrConfirmationCodeStatePreviewProvider : PreviewParameterProvider { - override val values: Sequence +class QrCodeConfirmationStepPreviewProvider : PreviewParameterProvider { + override val values: Sequence get() = sequenceOf( - QrCodeConfirmationState( - step = QrCodeConfirmationStep.DisplayCheckCode("12"), - ), - QrCodeConfirmationState( - step = QrCodeConfirmationStep.DisplayVerificationCode("123456"), - ), - QrCodeConfirmationState( - step = QrCodeConfirmationStep.DisplayVerificationCode("123456789"), - ), + QrCodeConfirmationStep.DisplayCheckCode("12"), + QrCodeConfirmationStep.DisplayVerificationCode("123456"), + QrCodeConfirmationStep.DisplayVerificationCode("123456789"), ) } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/confirmation/QrCodeConfirmationView.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/confirmation/QrCodeConfirmationView.kt index a7245fff110..825dbe3ad16 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/confirmation/QrCodeConfirmationView.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/confirmation/QrCodeConfirmationView.kt @@ -16,6 +16,7 @@ package io.element.android.features.login.impl.screens.qrcode.confirmation +import androidx.activity.compose.BackHandler import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column @@ -53,6 +54,9 @@ fun QrCodeConfirmationView( onCancel: () -> Unit, modifier: Modifier = Modifier, ) { + BackHandler { + onCancel() + } val icon = when (step) { is QrCodeConfirmationStep.DisplayCheckCode -> CompoundIcons.Computer() is QrCodeConfirmationStep.DisplayVerificationCode -> CompoundIcons.LockSolid() @@ -153,10 +157,10 @@ private fun Buttons( @PreviewsDayNight @Composable -internal fun QrCodeConfirmationViewPreview(@PreviewParameter(QrConfirmationCodeStatePreviewProvider::class) state: QrCodeConfirmationState) { +internal fun QrCodeConfirmationViewPreview(@PreviewParameter(QrCodeConfirmationStepPreviewProvider::class) step: QrCodeConfirmationStep) { ElementPreview { QrCodeConfirmationView( - step = state.step, + step = step, onCancel = {}, ) } 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 084f4d6bb7d..e32d62dbc65 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 @@ -26,19 +26,17 @@ import androidx.compose.runtime.setValue import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.runCatchingUpdatingState -import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService +import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.matrix.api.auth.qrlogin.MatrixQrCodeLoginData import io.element.android.libraries.matrix.api.auth.qrlogin.MatrixQrCodeLoginDataFactory import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.flow.catch import kotlinx.coroutines.launch -import kotlinx.coroutines.sync.Mutex -import kotlinx.coroutines.sync.withLock import java.util.concurrent.atomic.AtomicBoolean import javax.inject.Inject class QrCodeScanPresenter @Inject constructor( private val qrCodeLoginDataFactory: MatrixQrCodeLoginDataFactory, + private val coroutineDispatchers: CoroutineDispatchers, ) : Presenter { private var isScanning by mutableStateOf(true) @@ -73,7 +71,7 @@ class QrCodeScanPresenter @Inject constructor( private fun CoroutineScope.getQrCodeData(codeScannedAction: MutableState>, code: ByteArray) { if (codeScannedAction.value.isSuccess() || isProcessingCode.compareAndSet(true, true)) return - launch { + launch(coroutineDispatchers.computation) { suspend { qrCodeLoginDataFactory.parseQrCodeData(code).getOrThrow() }.runCatchingUpdatingState(codeScannedAction) diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/qrcode/QrCodeLoginFlowNodeTest.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/qrcode/QrCodeLoginFlowNodeTest.kt index d8fc8496831..066c59094a7 100644 --- a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/qrcode/QrCodeLoginFlowNodeTest.kt +++ b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/qrcode/QrCodeLoginFlowNodeTest.kt @@ -21,6 +21,7 @@ import com.bumble.appyx.core.modality.AncestryInfo import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.utils.customisations.NodeCustomisationDirectoryImpl import com.google.common.truth.Truth.assertThat +import io.element.android.features.login.impl.DefaultLoginUserStory import io.element.android.features.login.impl.screens.qrcode.confirmation.QrCodeConfirmationStep import io.element.android.libraries.matrix.api.auth.qrlogin.MatrixQrCodeLoginData import io.element.android.libraries.matrix.api.auth.qrlogin.QrCodeLoginStep @@ -51,6 +52,7 @@ class QrCodeLoginFlowNodeTest { private fun createLoginFlowNode( qrCodeLoginManager: FakeQrCodeLoginManager = FakeQrCodeLoginManager(), + defaultLoginUserStory: DefaultLoginUserStory = DefaultLoginUserStory(), ): QrCodeLoginFlowNode { val buildContext = BuildContext( ancestryInfo = AncestryInfo.Root, @@ -61,6 +63,7 @@ class QrCodeLoginFlowNodeTest { buildContext = buildContext, plugins = emptyList(), qrCodeLoginManager = qrCodeLoginManager, + defaultLoginUserStory = defaultLoginUserStory ) } diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/qrcode/confirmation/QrCodeConfirmationViewTest.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/qrcode/confirmation/QrCodeConfirmationViewTest.kt new file mode 100644 index 00000000000..36189c6b138 --- /dev/null +++ b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/qrcode/confirmation/QrCodeConfirmationViewTest.kt @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.login.impl.screens.qrcode.confirmation + +import androidx.activity.ComponentActivity +import androidx.compose.ui.test.junit4.AndroidComposeTestRule +import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.test.ext.junit.runners.AndroidJUnit4 +import io.element.android.libraries.ui.strings.CommonStrings +import io.element.android.tests.testutils.clickOn +import io.element.android.tests.testutils.ensureCalledOnce +import io.element.android.tests.testutils.pressBackKey +import org.junit.Rule +import org.junit.Test +import org.junit.rules.TestRule +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class QrCodeConfirmationViewTest { + @get:Rule + val rule = createAndroidComposeRule() + + @Test + fun `on back pressed - calls the expected callback`() { + ensureCalledOnce { callback -> + rule.setQrCodeConfirmationView( + step = QrCodeConfirmationStep.DisplayCheckCode("12"), + onCancel = callback + ) + rule.pressBackKey() + } + } + + @Test + fun `on Cancel button clicked - calls the expected callback`() { + ensureCalledOnce { callback -> + rule.setQrCodeConfirmationView( + step = QrCodeConfirmationStep.DisplayCheckCode("12"), + onCancel = callback + ) + rule.clickOn(CommonStrings.action_cancel) + } + } + + private fun AndroidComposeTestRule.setQrCodeConfirmationView( + step: QrCodeConfirmationStep, + onCancel: () -> Unit + ) { + setContent { + QrCodeConfirmationView( + step = step, + onCancel = onCancel + ) + } + } +} diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/qrcode/error/QrCodeErrorViewTest.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/qrcode/error/QrCodeErrorViewTest.kt new file mode 100644 index 00000000000..1b8cbcdb82b --- /dev/null +++ b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/qrcode/error/QrCodeErrorViewTest.kt @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.login.impl.screens.qrcode.error + +import androidx.activity.ComponentActivity +import androidx.compose.ui.test.junit4.AndroidComposeTestRule +import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.test.ext.junit.runners.AndroidJUnit4 +import io.element.android.features.login.impl.R +import io.element.android.tests.testutils.clickOn +import io.element.android.tests.testutils.ensureCalledOnce +import io.element.android.tests.testutils.pressBackKey +import org.junit.Rule +import org.junit.Test +import org.junit.rules.TestRule +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class QrCodeErrorViewTest { + @get:Rule + val rule = createAndroidComposeRule() + + @Test + fun `on back pressed - calls the onRetry callback`() { + ensureCalledOnce { callback -> + rule.setQrCodeErrorView( + onRetry = callback + ) + rule.pressBackKey() + } + } + + @Test + fun `on try again button clicked - calls the expected callback`() { + ensureCalledOnce { callback -> + rule.setQrCodeErrorView( + onRetry = callback + ) + rule.clickOn(R.string.screen_qr_code_login_start_over_button) + } + } + + private fun AndroidComposeTestRule.setQrCodeErrorView( + onRetry: () -> Unit + ) { + setContent { + QrCodeErrorView(onRetry = onRetry) + } + } +} 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 7f25737f8d9..43defcc2359 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 @@ -20,10 +20,10 @@ 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.libraries.architecture.AsyncAction -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.lambdaRecorder +import io.element.android.libraries.core.coroutine.CoroutineDispatchers +import io.element.android.libraries.matrix.test.auth.qrlogin.FakeMatrixQrCodeLoginDataFactory +import io.element.android.tests.testutils.testCoroutineDispatchers +import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runTest import org.junit.Test @@ -49,11 +49,9 @@ class QrCodeScanPresenterTest { }.test { val initialState = awaitItem() initialState.eventSink(QrCodeScanEvents.QrCodeScanned(byteArrayOf())) - awaitItem().run { - assertThat(isScanning).isFalse() - assertThat(authenticationAction.isLoading()).isTrue() - } - assertThat(awaitItem().authenticationAction).isEqualTo(AsyncAction.Success(Result.success(FakeQrCodeLoginData()))) + assertThat(awaitItem().isScanning).isFalse() + assertThat(awaitItem().authenticationAction.isLoading()).isTrue() + assertThat(awaitItem().authenticationAction.isSuccess()).isTrue() } } @@ -68,33 +66,23 @@ class QrCodeScanPresenterTest { }.test { val initialState = awaitItem() initialState.eventSink(QrCodeScanEvents.QrCodeScanned(byteArrayOf())) - awaitItem().run { - assertThat(isScanning).isFalse() - assertThat(authenticationAction.isLoading()).isTrue() - } + assertThat(awaitItem().isScanning).isFalse() + assertThat(awaitItem().authenticationAction.isLoading()).isTrue() + val errorState = awaitItem() assertThat(errorState.authenticationAction.isFailure()).isTrue() errorState.eventSink(QrCodeScanEvents.TryAgain) - awaitItem().run { - assertThat(isScanning).isTrue() - assertThat(authenticationAction.isUninitialized()).isTrue() - } + assertThat(awaitItem().isScanning).isTrue() + assertThat(awaitItem().authenticationAction.isUninitialized()).isTrue() } } - private fun createQrCodeScanPresenter( + private fun TestScope.createQrCodeScanPresenter( qrCodeLoginDataFactory: FakeMatrixQrCodeLoginDataFactory = FakeMatrixQrCodeLoginDataFactory(), - ) = QrCodeScanPresenter(qrCodeLoginDataFactory) -} - -class FakeMatrixQrCodeLoginDataFactory( - var parseQrCodeLoginDataResult: () -> Result = - lambdaRecorder> { Result.success(FakeQrCodeLoginData()) }, -) : MatrixQrCodeLoginDataFactory { - override fun parseQrCodeData(data: ByteArray): Result { - return parseQrCodeLoginDataResult() - } + coroutineDispatchers: CoroutineDispatchers = testCoroutineDispatchers(), + ) = QrCodeScanPresenter( + qrCodeLoginDataFactory = qrCodeLoginDataFactory, + coroutineDispatchers = coroutineDispatchers, + ) } - -class FakeQrCodeLoginData : MatrixQrCodeLoginData diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/qrcode/scan/QrCodeScanViewTest.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/qrcode/scan/QrCodeScanViewTest.kt index 01763db962e..7f0c97b425a 100644 --- a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/qrcode/scan/QrCodeScanViewTest.kt +++ b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/qrcode/scan/QrCodeScanViewTest.kt @@ -22,6 +22,7 @@ import androidx.compose.ui.test.junit4.createAndroidComposeRule import androidx.test.ext.junit.runners.AndroidJUnit4 import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.matrix.api.auth.qrlogin.MatrixQrCodeLoginData +import io.element.android.libraries.matrix.test.auth.qrlogin.FakeQrCodeLoginData import io.element.android.tests.testutils.EnsureNeverCalled import io.element.android.tests.testutils.EnsureNeverCalledWithParam import io.element.android.tests.testutils.ensureCalledOnce diff --git a/features/onboarding/impl/src/test/kotlin/io/element/android/features/onboarding/impl/OnBoardingPresenterTest.kt b/features/onboarding/impl/src/test/kotlin/io/element/android/features/onboarding/impl/OnBoardingPresenterTest.kt index 51d1ccc9324..28b6dc4dc78 100644 --- a/features/onboarding/impl/src/test/kotlin/io/element/android/features/onboarding/impl/OnBoardingPresenterTest.kt +++ b/features/onboarding/impl/src/test/kotlin/io/element/android/features/onboarding/impl/OnBoardingPresenterTest.kt @@ -39,7 +39,7 @@ class OnBoardingPresenterTest { }.test { val initialState = awaitItem() assertThat(initialState.isDebugBuild).isTrue() - assertThat(initialState.canLoginWithQrCode).isFalse() + assertThat(initialState.canLoginWithQrCode).isTrue() assertThat(initialState.canCreateAccount).isFalse() } } diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/ForceOrientation.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/ForceOrientation.kt index 040d1ec0670..e3a349d4793 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/ForceOrientation.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/ForceOrientation.kt @@ -18,10 +18,6 @@ package io.element.android.libraries.designsystem.utils import android.content.pm.ActivityInfo import androidx.activity.ComponentActivity -import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi -import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo -import androidx.compose.material3.windowsizeclass.WindowHeightSizeClass -import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.ui.platform.LocalContext @@ -39,16 +35,6 @@ fun ForceOrientation(orientation: ScreenOrientation) { } } -@OptIn(ExperimentalMaterial3AdaptiveApi::class) -@Composable -fun ForceOrientationInMobileDevices(orientation: ScreenOrientation) { - val windowAdaptiveInfo = currentWindowAdaptiveInfo() - if (windowAdaptiveInfo.windowSizeClass.widthSizeClass == WindowWidthSizeClass.Compact - || windowAdaptiveInfo.windowSizeClass.heightSizeClass == WindowHeightSizeClass.Compact) { - ForceOrientation(orientation = orientation) - } -} - enum class ScreenOrientation { PORTRAIT, LANDSCAPE diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/ForceOrientationInMobileDevices.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/ForceOrientationInMobileDevices.kt new file mode 100644 index 00000000000..70ffb8fe3ef --- /dev/null +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/ForceOrientationInMobileDevices.kt @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.designsystem.utils + +import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi +import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo +import androidx.compose.material3.windowsizeclass.WindowHeightSizeClass +import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass +import androidx.compose.runtime.Composable + +@OptIn(ExperimentalMaterial3AdaptiveApi::class) +@Composable +fun ForceOrientationInMobileDevices(orientation: ScreenOrientation) { + val windowAdaptiveInfo = currentWindowAdaptiveInfo() + if (windowAdaptiveInfo.windowSizeClass.widthSizeClass == WindowWidthSizeClass.Compact + || windowAdaptiveInfo.windowSizeClass.heightSizeClass == WindowHeightSizeClass.Compact + ) { + ForceOrientation(orientation = orientation) + } +} 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 new file mode 100644 index 00000000000..99ad1d73ad4 --- /dev/null +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/auth/qrlogin/FakeMatrixQrCodeLoginDataFactory.kt @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +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.lambdaRecorder + +class FakeMatrixQrCodeLoginDataFactory( + var parseQrCodeLoginDataResult: () -> Result = + lambdaRecorder> { Result.success(FakeQrCodeLoginData()) }, +) : MatrixQrCodeLoginDataFactory { + override fun parseQrCodeData(data: ByteArray): Result { + return parseQrCodeLoginDataResult() + } +} + +class FakeQrCodeLoginData : MatrixQrCodeLoginData From bf87cfeee07932344156907319fa4d7e6ee1055c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= Date: Mon, 29 Apr 2024 15:49:37 +0200 Subject: [PATCH 27/60] Display the 'establishing secure connection' after the QR code is successfully read --- .../login/impl/screens/qrcode/scan/QrCodeScanView.kt | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) 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 f43b08a9aef..2344330d3cc 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 @@ -162,7 +162,7 @@ private fun ColumnScope.Buttons( ) } } - AsyncAction.Loading -> { + AsyncAction.Loading, is AsyncAction.Success -> { Column( modifier = Modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally, @@ -183,8 +183,7 @@ private fun ColumnScope.Buttons( } } AsyncAction.Uninitialized, - AsyncAction.Confirming, - is AsyncAction.Success -> Unit + AsyncAction.Confirming -> Unit } } } From 1ec15c03397199736e02603b3ab2f15e681a7d55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= Date: Mon, 29 Apr 2024 16:14:19 +0200 Subject: [PATCH 28/60] Update strings --- features/ftue/impl/src/main/res/values/localazy.xml | 9 ++++----- .../login/impl/screens/qrcode/intro/QrCodeIntroView.kt | 5 +---- features/login/impl/src/main/res/values/localazy.xml | 9 ++++----- features/onboarding/impl/build.gradle.kts | 1 + .../securebackup/impl/src/main/res/values/localazy.xml | 3 ++- .../impl/DefaultRoomLastMessageFormatterTest.kt | 2 +- libraries/ui-strings/src/main/res/values/localazy.xml | 6 ++++++ 7 files changed, 19 insertions(+), 16 deletions(-) diff --git a/features/ftue/impl/src/main/res/values/localazy.xml b/features/ftue/impl/src/main/res/values/localazy.xml index ae50fe9aa1c..9264bfd4b19 100644 --- a/features/ftue/impl/src/main/res/values/localazy.xml +++ b/features/ftue/impl/src/main/res/values/localazy.xml @@ -2,21 +2,20 @@ "You can change your settings later." "Allow notifications and never miss a message" - "Establishing connection" + "Establishing a secure connection" "A secure connection could not be made to the new device. Your existing devices are still safe and you don\'t need to worry about them." "What now?" "Try signing in again with a QR code in case this was a network problem" "If you encounter the same problem, try a different wifi network or use your mobile data instead of wifi" "If that doesn’t work, sign in manually" "Connection not secure" - "You’ll be asked to enter the two digits shown below." - "Enter the number on your other device" + "You’ll be asked to enter the two digits shown on this device." + "Enter the number below on your other device" "Open %1$s on a desktop device" "Click on your avatar" "Select %1$s" "“Link new device”" - "Select %1$s" - "“Show QR code”" + "Follow the instructions shown" "Open %1$s on another device to get the QR code" "Use the QR code shown on the other device." "Try again" diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/intro/QrCodeIntroView.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/intro/QrCodeIntroView.kt index 453a36bc8f8..19f9b07a5e8 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/intro/QrCodeIntroView.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/intro/QrCodeIntroView.kt @@ -82,10 +82,7 @@ private fun Content(state: QrCodeIntroState) { annotatedTextWithBold( text = stringResource(R.string.screen_qr_code_login_initial_state_item_3, stringResource(R.string.screen_qr_code_login_initial_state_item_3_action)), boldText = stringResource(R.string.screen_qr_code_login_initial_state_item_3_action)), - annotatedTextWithBold( - text = stringResource(R.string.screen_qr_code_login_initial_state_item_4, stringResource(R.string.screen_qr_code_login_initial_state_item_4_action)), - boldText = stringResource(R.string.screen_qr_code_login_initial_state_item_4_action) - ), + AnnotatedString(stringResource(R.string.screen_qr_code_login_initial_state_item_4)), ), ) } diff --git a/features/login/impl/src/main/res/values/localazy.xml b/features/login/impl/src/main/res/values/localazy.xml index 26fbd83be37..af1234d4125 100644 --- a/features/login/impl/src/main/res/values/localazy.xml +++ b/features/login/impl/src/main/res/values/localazy.xml @@ -30,21 +30,20 @@ "Matrix is an open network for secure, decentralised communication." "Welcome back!" "Sign in to %1$s" - "Establishing connection" + "Establishing a secure connection" "A secure connection could not be made to the new device. Your existing devices are still safe and you don\'t need to worry about them." "What now?" "Try signing in again with a QR code in case this was a network problem" "If you encounter the same problem, try a different wifi network or use your mobile data instead of wifi" "If that doesn’t work, sign in manually" "Connection not secure" - "You’ll be asked to enter the two digits shown below." - "Enter the number on your other device" + "You’ll be asked to enter the two digits shown on this device." + "Enter the number below on your other device" "Open %1$s on a desktop device" "Click on your avatar" "Select %1$s" "“Link new device”" - "Select %1$s" - "“Show QR code”" + "Follow the instructions shown" "Open %1$s on another device to get the QR code" "Use the QR code shown on the other device." "Try again" diff --git a/features/onboarding/impl/build.gradle.kts b/features/onboarding/impl/build.gradle.kts index b5c073eac0a..0e08435cba4 100644 --- a/features/onboarding/impl/build.gradle.kts +++ b/features/onboarding/impl/build.gradle.kts @@ -38,6 +38,7 @@ anvil { dependencies { implementation(projects.anvilannotations) anvil(projects.anvilcodegen) + implementation(projects.appconfig) implementation(projects.libraries.core) implementation(projects.libraries.architecture) implementation(projects.libraries.matrix.api) diff --git a/features/securebackup/impl/src/main/res/values/localazy.xml b/features/securebackup/impl/src/main/res/values/localazy.xml index d3fb41857ea..e1159031a2d 100644 --- a/features/securebackup/impl/src/main/res/values/localazy.xml +++ b/features/securebackup/impl/src/main/res/values/localazy.xml @@ -35,8 +35,9 @@ "If you have a security key or security phrase, this will work too." "Recovery key or passcode" "Enter…" + "Lost your recovery key?" "Recovery key confirmed" - "Enter your recovery key or passcode" + "Enter your recovery key" "Copied recovery key" "Generating…" "Save recovery key" diff --git a/libraries/eventformatter/impl/src/test/kotlin/io/element/android/libraries/eventformatter/impl/DefaultRoomLastMessageFormatterTest.kt b/libraries/eventformatter/impl/src/test/kotlin/io/element/android/libraries/eventformatter/impl/DefaultRoomLastMessageFormatterTest.kt index 158eeec20df..a777b89ca7a 100644 --- a/libraries/eventformatter/impl/src/test/kotlin/io/element/android/libraries/eventformatter/impl/DefaultRoomLastMessageFormatterTest.kt +++ b/libraries/eventformatter/impl/src/test/kotlin/io/element/android/libraries/eventformatter/impl/DefaultRoomLastMessageFormatterTest.kt @@ -432,7 +432,7 @@ class DefaultRoomLastMessageFormatterTest { val youAcceptedKnockEvent = createRoomEvent(sentByYou = true, senderDisplayName = null, content = someoneContent) val youAcceptedKnock = formatter.format(youAcceptedKnockEvent, false) - assertThat(youAcceptedKnock).isEqualTo("${someoneContent.userId} allowed you to join") + assertThat(youAcceptedKnock).isEqualTo("You allowed ${someoneContent.userId} to join") val someoneAcceptedKnockEvent = createRoomEvent(sentByYou = false, senderDisplayName = otherName, content = someoneContent) val someoneAcceptedKnock = formatter.format(someoneAcceptedKnockEvent, false) diff --git a/libraries/ui-strings/src/main/res/values/localazy.xml b/libraries/ui-strings/src/main/res/values/localazy.xml index 360761915ca..32b1f60361b 100644 --- a/libraries/ui-strings/src/main/res/values/localazy.xml +++ b/libraries/ui-strings/src/main/res/values/localazy.xml @@ -158,12 +158,14 @@ "Modern" "Mute" "No results" + "No room name" "Offline" "or" "Password" "People" "Permalink" "Permission" + "Please wait…" "Are you sure you want to end this poll?" "Poll: %1$s" "Total votes: %1$s" @@ -213,6 +215,7 @@ "Topic" "What is this room about?" "Unable to decrypt" + "You don\'t have access to this message" "Invites couldn\'t be sent to one or more users." "Unable to send invite(s)" "Unlock" @@ -237,6 +240,7 @@ "Failed loading messages" "%1$s could not access your location. Please try again later." "Failed to upload your voice message." + "Message not found" "%1$s does not have permission to access your location. You can enable access in Settings." "%1$s does not have permission to access your location. Enable access below." "%1$s does not have permission to access your microphone. Enable access to record a voice message." @@ -248,6 +252,8 @@ "Rageshake to report bug" "Join room" "Knock to join" + "%1$s does not support spaces yet. You can access spaces on web." + "Spaces are not supported yet" "Click the button below and a room administrator will be notified. You’ll be able to join the conversation once approved." "You must be a member of this room to view the message history." "Want to join this room?" From 57e737912b7eed260119ad2d0172c3fc5158e05e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= Date: Mon, 29 Apr 2024 16:15:04 +0200 Subject: [PATCH 29/60] Move `OnboardingConfig` to `appconfig` module --- .../io/element/android/appconfig}/OnBoardingConfig.kt | 6 ++++-- .../android/features/onboarding/impl/OnBoardingPresenter.kt | 1 + 2 files changed, 5 insertions(+), 2 deletions(-) rename {features/onboarding/impl/src/main/kotlin/io/element/android/features/onboarding/impl => appconfig/src/main/kotlin/io/element/android/appconfig}/OnBoardingConfig.kt (78%) diff --git a/features/onboarding/impl/src/main/kotlin/io/element/android/features/onboarding/impl/OnBoardingConfig.kt b/appconfig/src/main/kotlin/io/element/android/appconfig/OnBoardingConfig.kt similarity index 78% rename from features/onboarding/impl/src/main/kotlin/io/element/android/features/onboarding/impl/OnBoardingConfig.kt rename to appconfig/src/main/kotlin/io/element/android/appconfig/OnBoardingConfig.kt index 7858007bc9e..a87aac61b78 100644 --- a/features/onboarding/impl/src/main/kotlin/io/element/android/features/onboarding/impl/OnBoardingConfig.kt +++ b/appconfig/src/main/kotlin/io/element/android/appconfig/OnBoardingConfig.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023 New Vector Ltd + * Copyright (c) 2024 New Vector Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,9 +14,11 @@ * limitations under the License. */ -package io.element.android.features.onboarding.impl +package io.element.android.appconfig object OnBoardingConfig { + /** Whether the user can use QR code login. */ const val CAN_LOGIN_WITH_QR_CODE = true + /** Whether the user can create an account using the app. */ const val CAN_CREATE_ACCOUNT = false } diff --git a/features/onboarding/impl/src/main/kotlin/io/element/android/features/onboarding/impl/OnBoardingPresenter.kt b/features/onboarding/impl/src/main/kotlin/io/element/android/features/onboarding/impl/OnBoardingPresenter.kt index a514637026e..c98102b118d 100644 --- a/features/onboarding/impl/src/main/kotlin/io/element/android/features/onboarding/impl/OnBoardingPresenter.kt +++ b/features/onboarding/impl/src/main/kotlin/io/element/android/features/onboarding/impl/OnBoardingPresenter.kt @@ -17,6 +17,7 @@ package io.element.android.features.onboarding.impl import androidx.compose.runtime.Composable +import io.element.android.appconfig.OnBoardingConfig import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.core.meta.BuildMeta import io.element.android.libraries.core.meta.BuildType From e12c19e97c9380144a8d55203bbef3887e92dde3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= Date: Mon, 29 Apr 2024 19:15:40 +0200 Subject: [PATCH 30/60] More tests, rename `QrCodeLoginManagerImpl` to `DefaultQrCodeLoginManager` --- .../impl/qrcode/DefaultQrCodeLoginManager.kt | 43 +++++++++++ .../login/impl/qrcode/QrCodeLoginManager.kt | 21 ------ .../qrcode/DefaultQrCodeLoginManagerTest.kt | 75 +++++++++++++++++++ .../QrCodeConfirmationViewTest.kt | 2 +- .../test/auth/FakeAuthenticationService.kt | 5 +- 5 files changed, 122 insertions(+), 24 deletions(-) create mode 100644 features/login/impl/src/main/kotlin/io/element/android/features/login/impl/qrcode/DefaultQrCodeLoginManager.kt create mode 100644 features/login/impl/src/test/kotlin/io/element/android/features/login/impl/qrcode/DefaultQrCodeLoginManagerTest.kt diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/qrcode/DefaultQrCodeLoginManager.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/qrcode/DefaultQrCodeLoginManager.kt new file mode 100644 index 00000000000..c25d3b84023 --- /dev/null +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/qrcode/DefaultQrCodeLoginManager.kt @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.login.impl.qrcode + +import com.squareup.anvil.annotations.ContributesBinding +import io.element.android.libraries.di.AppScope +import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService +import io.element.android.libraries.matrix.api.auth.qrlogin.MatrixQrCodeLoginData +import io.element.android.libraries.matrix.api.auth.qrlogin.QrCodeLoginStep +import io.element.android.libraries.matrix.api.core.SessionId +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import javax.inject.Inject + +@ContributesBinding(AppScope::class) +class DefaultQrCodeLoginManager @Inject constructor( + private val authenticationService: MatrixAuthenticationService, +): QrCodeLoginManager { + private val _currentLoginStep = MutableStateFlow(QrCodeLoginStep.Uninitialized) + override val currentLoginStep: StateFlow = _currentLoginStep + + override suspend fun authenticate(qrCodeLoginData: MatrixQrCodeLoginData): Result { + _currentLoginStep.value = QrCodeLoginStep.Uninitialized + + return authenticationService.loginWithQrCode(qrCodeLoginData) { step -> + _currentLoginStep.value = step + } + } +} diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/qrcode/QrCodeLoginManager.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/qrcode/QrCodeLoginManager.kt index 2dbc878cef7..e214f5e0c90 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/qrcode/QrCodeLoginManager.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/qrcode/QrCodeLoginManager.kt @@ -16,31 +16,10 @@ package io.element.android.features.login.impl.qrcode -import com.squareup.anvil.annotations.ContributesBinding -import io.element.android.libraries.di.AppScope -import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService import io.element.android.libraries.matrix.api.auth.qrlogin.MatrixQrCodeLoginData import io.element.android.libraries.matrix.api.auth.qrlogin.QrCodeLoginStep import io.element.android.libraries.matrix.api.core.SessionId -import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow -import javax.inject.Inject - -@ContributesBinding(AppScope::class) -class QrCodeLoginManagerImpl @Inject constructor( - private val authenticationService: MatrixAuthenticationService, -): QrCodeLoginManager { - private val _currentLoginStep = MutableStateFlow(QrCodeLoginStep.Uninitialized) - override val currentLoginStep: StateFlow = _currentLoginStep - - override suspend fun authenticate(qrCodeLoginData: MatrixQrCodeLoginData): Result { - _currentLoginStep.value = QrCodeLoginStep.Uninitialized - - return authenticationService.loginWithQrCode(qrCodeLoginData) { step -> - _currentLoginStep.value = step - } - } -} interface QrCodeLoginManager { val currentLoginStep: StateFlow diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/qrcode/DefaultQrCodeLoginManagerTest.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/qrcode/DefaultQrCodeLoginManagerTest.kt new file mode 100644 index 00000000000..21e985c4f3a --- /dev/null +++ b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/qrcode/DefaultQrCodeLoginManagerTest.kt @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.login.impl.qrcode + +import app.cash.turbine.test +import com.google.common.truth.Truth.assertThat +import io.element.android.libraries.matrix.api.auth.qrlogin.QrCodeLoginStep +import io.element.android.libraries.matrix.test.A_SESSION_ID +import io.element.android.libraries.matrix.test.auth.FakeAuthenticationService +import io.element.android.libraries.matrix.test.auth.qrlogin.FakeQrCodeLoginData +import kotlinx.coroutines.test.runTest +import org.junit.Test + +class DefaultQrCodeLoginManagerTest { + @Test + fun `authenticate - returns success if the login succeeded`() = runTest { + val authenticationService = FakeAuthenticationService( + loginWithQrCodeResult = { _, _ -> Result.success(A_SESSION_ID) } + ) + val manager = DefaultQrCodeLoginManager(authenticationService) + val result = manager.authenticate(FakeQrCodeLoginData()) + + assertThat(result.isSuccess).isTrue() + assertThat(result.getOrNull()).isEqualTo(A_SESSION_ID) + } + + @Test + fun `authenticate - returns failure if the login failed`() = runTest { + val authenticationService = FakeAuthenticationService( + loginWithQrCodeResult = { _, _ -> Result.failure(IllegalStateException("Auth failed")) } + ) + val manager = DefaultQrCodeLoginManager(authenticationService) + val result = manager.authenticate(FakeQrCodeLoginData()) + + assertThat(result.isFailure).isTrue() + assertThat(result.exceptionOrNull()).isNotNull() + } + + @Test + fun `authenticate - emits the auth steps`() = runTest { + val authenticationService = FakeAuthenticationService( + loginWithQrCodeResult = { _, progressListener -> + progressListener(QrCodeLoginStep.EstablishingSecureChannel("00")) + progressListener(QrCodeLoginStep.Starting) + progressListener(QrCodeLoginStep.WaitingForToken("000000")) + progressListener(QrCodeLoginStep.Finished) + Result.success(A_SESSION_ID) + } + ) + val manager = DefaultQrCodeLoginManager(authenticationService) + manager.currentLoginStep.test { + manager.authenticate(FakeQrCodeLoginData()) + + assertThat(awaitItem()).isEqualTo(QrCodeLoginStep.Uninitialized) + assertThat(awaitItem()).isEqualTo(QrCodeLoginStep.EstablishingSecureChannel("00")) + assertThat(awaitItem()).isEqualTo(QrCodeLoginStep.Starting) + assertThat(awaitItem()).isEqualTo(QrCodeLoginStep.WaitingForToken("000000")) + assertThat(awaitItem()).isEqualTo(QrCodeLoginStep.Finished) + } + } +} diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/qrcode/confirmation/QrCodeConfirmationViewTest.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/qrcode/confirmation/QrCodeConfirmationViewTest.kt index 36189c6b138..78eae62377d 100644 --- a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/qrcode/confirmation/QrCodeConfirmationViewTest.kt +++ b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/qrcode/confirmation/QrCodeConfirmationViewTest.kt @@ -49,7 +49,7 @@ class QrCodeConfirmationViewTest { fun `on Cancel button clicked - calls the expected callback`() { ensureCalledOnce { callback -> rule.setQrCodeConfirmationView( - step = QrCodeConfirmationStep.DisplayCheckCode("12"), + step = QrCodeConfirmationStep.DisplayVerificationCode("123456"), onCancel = callback ) rule.clickOn(CommonStrings.action_cancel) diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/auth/FakeAuthenticationService.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/auth/FakeAuthenticationService.kt index 96d53239840..b1d0255b7b7 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/auth/FakeAuthenticationService.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/auth/FakeAuthenticationService.kt @@ -36,7 +36,8 @@ import kotlinx.coroutines.flow.flowOf val A_OIDC_DATA = OidcDetails(url = "a-url") class FakeAuthenticationService( - var loginWithQrCodeResult: () -> Result = lambdaRecorder> { Result.success(A_SESSION_ID) }, + var loginWithQrCodeResult: (qrCodeData: MatrixQrCodeLoginData, progress: (QrCodeLoginStep) -> Unit) -> Result = + lambdaRecorder Unit, Result> { _, _ -> Result.success(A_SESSION_ID) }, ) : MatrixAuthenticationService { private val homeserver = MutableStateFlow(null) private var oidcError: Throwable? = null @@ -90,7 +91,7 @@ class FakeAuthenticationService( } override suspend fun loginWithQrCode(qrCodeData: MatrixQrCodeLoginData, progress: (QrCodeLoginStep) -> Unit): Result = simulateLongTask { - loginWithQrCodeResult() + loginWithQrCodeResult(qrCodeData, progress) } fun givenOidcError(throwable: Throwable?) { From f01de8580399381622dc65473998c17193337f64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= Date: Tue, 30 Apr 2024 12:17:14 +0200 Subject: [PATCH 31/60] Fix merge issues --- .../io/element/android/x/di/AppModule.kt | 1 - .../onboarding/impl/OnboardingViewTest.kt | 33 ++++--------------- gradle/libs.versions.toml | 1 + .../android/libraries/core/meta/BuildMeta.kt | 1 - .../auth/RustMatrixAuthenticationService.kt | 1 - 5 files changed, 7 insertions(+), 30 deletions(-) diff --git a/app/src/main/kotlin/io/element/android/x/di/AppModule.kt b/app/src/main/kotlin/io/element/android/x/di/AppModule.kt index df1484ecbed..a121e0a6fb9 100644 --- a/app/src/main/kotlin/io/element/android/x/di/AppModule.kt +++ b/app/src/main/kotlin/io/element/android/x/di/AppModule.kt @@ -92,7 +92,6 @@ object AppModule { gitBranchName = BuildConfig.GIT_BRANCH_NAME, flavorDescription = BuildConfig.FLAVOR_DESCRIPTION, flavorShortDescription = BuildConfig.SHORT_FLAVOR_DESCRIPTION, - desktopApplicationName = "Element", ) @Provides diff --git a/features/onboarding/impl/src/test/kotlin/io/element/android/features/onboarding/impl/OnboardingViewTest.kt b/features/onboarding/impl/src/test/kotlin/io/element/android/features/onboarding/impl/OnboardingViewTest.kt index cc618254074..9a83573807c 100644 --- a/features/onboarding/impl/src/test/kotlin/io/element/android/features/onboarding/impl/OnboardingViewTest.kt +++ b/features/onboarding/impl/src/test/kotlin/io/element/android/features/onboarding/impl/OnboardingViewTest.kt @@ -40,11 +40,7 @@ class OnboardingViewTest { fun `when can create account - clicking on create account calls the expected callback`() { ensureCalledOnce { callback -> rule.setOnboardingView( - state = OnBoardingState( - isDebugBuild = false, - canLoginWithQrCode = false, - canCreateAccount = true, - ), + state = anOnBoardingState(canCreateAccount = true), onCreateAccount = callback, ) rule.clickOn(R.string.screen_onboarding_sign_up) @@ -55,11 +51,7 @@ class OnboardingViewTest { fun `when can login with QR code - clicking on sign in with QR code calls the expected callback`() { ensureCalledOnce { callback -> rule.setOnboardingView( - state = OnBoardingState( - isDebugBuild = false, - canLoginWithQrCode = true, - canCreateAccount = false, - ), + state = anOnBoardingState(canLoginWithQrCode = true), onSignInWithQrCode = callback, ) rule.clickOn(R.string.screen_onboarding_sign_in_with_qr_code) @@ -70,11 +62,7 @@ class OnboardingViewTest { fun `when can login with QR code - clicking on sign in manually calls the expected callback`() { ensureCalledOnce { callback -> rule.setOnboardingView( - state = OnBoardingState( - isDebugBuild = false, - canLoginWithQrCode = true, - canCreateAccount = false, - ), + state = anOnBoardingState(canLoginWithQrCode = true), onSignIn = callback, ) rule.clickOn(R.string.screen_onboarding_sign_in_manually) @@ -85,8 +73,7 @@ class OnboardingViewTest { fun `when cannot login with QR code or create account - clicking on continue calls the sign in callback`() { ensureCalledOnce { callback -> rule.setOnboardingView( - state = OnBoardingState( - isDebugBuild = false, + state = anOnBoardingState( canLoginWithQrCode = false, canCreateAccount = false, ), @@ -100,11 +87,7 @@ class OnboardingViewTest { fun `when on debug build - clicking on the settings icon opens the developer settings`() { ensureCalledOnce { callback -> rule.setOnboardingView( - state = OnBoardingState( - isDebugBuild = true, - canLoginWithQrCode = false, - canCreateAccount = false, - ), + state = anOnBoardingState(isDebugBuild = true), onOpenDeveloperSettings = callback ) rule.onNode(hasContentDescription(rule.activity.getString(CommonStrings.common_settings))).performClick() @@ -115,11 +98,7 @@ class OnboardingViewTest { fun `clicking on report a problem calls the sign in callback`() { ensureCalledOnce { callback -> rule.setOnboardingView( - state = OnBoardingState( - isDebugBuild = false, - canLoginWithQrCode = false, - canCreateAccount = false, - ), + state = anOnBoardingState(), onReportProblem = callback, ) rule.clickOn(CommonStrings.common_report_a_problem) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index f4153d4c6e1..e407a316df3 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -20,6 +20,7 @@ constraintlayout = "2.1.4" constraintlayout_compose = "1.0.1" lifecycle = "2.7.0" activity = "1.8.2" +material3 = "1.2.1" media3 = "1.3.1" camera = "1.3.2" diff --git a/libraries/core/src/main/kotlin/io/element/android/libraries/core/meta/BuildMeta.kt b/libraries/core/src/main/kotlin/io/element/android/libraries/core/meta/BuildMeta.kt index b2424b7bd2a..ad5581f6313 100644 --- a/libraries/core/src/main/kotlin/io/element/android/libraries/core/meta/BuildMeta.kt +++ b/libraries/core/src/main/kotlin/io/element/android/libraries/core/meta/BuildMeta.kt @@ -30,5 +30,4 @@ data class BuildMeta( val gitBranchName: String, val flavorDescription: String, val flavorShortDescription: String, - val desktopApplicationName: String = applicationName, ) diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationService.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationService.kt index ebc335ac731..5e4f80d8210 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationService.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationService.kt @@ -226,7 +226,6 @@ class RustMatrixAuthenticationService @Inject constructor( isTokenValid = true, loginType = LoginType.QR, passphrase = pendingPassphrase, - needsVerification = false, ) sessionStore.storeData(sessionData) SessionId(sessionData.userId) From f4a24cc1d619af568babe3ec7dfe12840fabb2cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= Date: Fri, 3 May 2024 16:59:47 +0200 Subject: [PATCH 32/60] Fix lint issues, update strings --- .../android/appconfig/OnBoardingConfig.kt | 1 + .../src/main/res/values-de/translations.xml | 2 +- .../src/main/res/values-de/translations.xml | 2 +- .../src/main/res/values-de/translations.xml | 2 +- .../src/main/res/values-be/translations.xml | 3 +- .../src/main/res/values-cs/translations.xml | 3 +- .../src/main/res/values-de/translations.xml | 3 +- .../src/main/res/values-fr/translations.xml | 21 +++++++++++-- .../src/main/res/values-hu/translations.xml | 3 +- .../src/main/res/values-in/translations.xml | 2 -- .../src/main/res/values-ru/translations.xml | 2 -- .../src/main/res/values-sk/translations.xml | 3 +- .../src/main/res/values-de/translations.xml | 2 +- .../src/main/res/values-fr/translations.xml | 4 +-- .../src/main/res/values-sv/translations.xml | 8 +++++ .../main/res/values-zh-rTW/translations.xml | 3 ++ .../impl/qrcode/DefaultQrCodeLoginManager.kt | 2 +- .../confirmation/QrCodeConfirmationNode.kt | 1 - .../screens/qrcode/error/QrCodeErrorNode.kt | 2 +- .../screens/qrcode/error/QrCodeErrorView.kt | 2 -- .../screens/qrcode/intro/QrCodeIntroView.kt | 8 +++-- .../qrcode/scan/QrCodeScanPresenter.kt | 1 - .../screens/qrcode/scan/QrCodeScanView.kt | 7 +++-- .../src/main/res/values-be/translations.xml | 27 +++++++++++++++++ .../src/main/res/values-bg/translations.xml | 1 + .../src/main/res/values-cs/translations.xml | 27 +++++++++++++++++ .../src/main/res/values-de/translations.xml | 27 +++++++++++++++++ .../src/main/res/values-es/translations.xml | 1 + .../src/main/res/values-fr/translations.xml | 30 ++++++++++++++++++- .../src/main/res/values-hu/translations.xml | 27 +++++++++++++++++ .../src/main/res/values-in/translations.xml | 26 ++++++++++++++++ .../src/main/res/values-it/translations.xml | 1 + .../src/main/res/values-ro/translations.xml | 1 + .../src/main/res/values-ru/translations.xml | 26 ++++++++++++++++ .../src/main/res/values-sk/translations.xml | 27 +++++++++++++++++ .../src/main/res/values-sv/translations.xml | 1 + .../src/main/res/values-uk/translations.xml | 1 + .../main/res/values-zh-rTW/translations.xml | 1 + .../src/main/res/values-de/translations.xml | 2 +- .../src/main/res/values-sv/translations.xml | 7 +++++ .../src/main/res/values-sv/translations.xml | 2 ++ .../src/main/res/values-de/translations.xml | 10 +++---- .../src/main/res/values-sv/translations.xml | 2 ++ .../main/res/values-zh-rTW/translations.xml | 2 ++ .../createkey/CreateNewRecoveryKeyView.kt | 3 +- .../src/main/res/values-de/translations.xml | 15 +++++----- .../src/main/res/values-fr/translations.xml | 6 ++-- .../src/main/res/values-sv/translations.xml | 3 ++ .../src/main/res/values-fr/translations.xml | 2 +- .../src/main/res/values-sv/translations.xml | 1 + .../main/res/values-zh-rTW/translations.xml | 8 +++-- .../atomic/molecules/NumberedListMolecule.kt | 7 ++++- .../designsystem/atomic/pages/FlowStepPage.kt | 2 -- .../utils/ForceOrientationInMobileDevices.kt | 4 +-- .../src/main/res/values-fr/translations.xml | 2 +- .../libraries/qrcode/QRCodeAnalyzer.kt | 1 - .../libraries/qrcode/QrCodeCameraView.kt | 1 - .../src/main/res/values-cs/translations.xml | 1 + .../src/main/res/values-de/translations.xml | 9 +++--- .../src/main/res/values-fr/translations.xml | 4 ++- .../src/main/res/values-hu/translations.xml | 1 + .../src/main/res/values-sv/translations.xml | 7 +++++ .../src/main/res/values/localazy.xml | 1 + 63 files changed, 346 insertions(+), 68 deletions(-) diff --git a/appconfig/src/main/kotlin/io/element/android/appconfig/OnBoardingConfig.kt b/appconfig/src/main/kotlin/io/element/android/appconfig/OnBoardingConfig.kt index a87aac61b78..dd30e20b8dd 100644 --- a/appconfig/src/main/kotlin/io/element/android/appconfig/OnBoardingConfig.kt +++ b/appconfig/src/main/kotlin/io/element/android/appconfig/OnBoardingConfig.kt @@ -19,6 +19,7 @@ package io.element.android.appconfig object OnBoardingConfig { /** Whether the user can use QR code login. */ const val CAN_LOGIN_WITH_QR_CODE = true + /** Whether the user can create an account using the app. */ const val CAN_CREATE_ACCOUNT = false } diff --git a/features/analytics/api/src/main/res/values-de/translations.xml b/features/analytics/api/src/main/res/values-de/translations.xml index e4f5952ae0c..ec26ff08fce 100644 --- a/features/analytics/api/src/main/res/values-de/translations.xml +++ b/features/analytics/api/src/main/res/values-de/translations.xml @@ -1,7 +1,7 @@ "Teile anonyme Nutzungsdaten, um uns bei der Identifizierung von Problemen zu helfen." - "Du kannst alle unsere Bedingungen lesen %1$s." + "Weitere Informationen findest du %1$s ." "hier" "Analysedaten teilen" diff --git a/features/analytics/impl/src/main/res/values-de/translations.xml b/features/analytics/impl/src/main/res/values-de/translations.xml index 74eff06c697..e2180092d96 100644 --- a/features/analytics/impl/src/main/res/values-de/translations.xml +++ b/features/analytics/impl/src/main/res/values-de/translations.xml @@ -2,7 +2,7 @@ "Wir zeichnen keine persönlichen Daten auf und erstellen keine Profile." "Teile anonyme Nutzungsdaten, um uns bei der Identifizierung von Problemen zu helfen." - "Du kannst alle unsere Bedingungen lesen %1$s." + "Weitere Informationen findest du %1$s ." "hier" "Du kannst diese Funktion jederzeit deaktivieren" "Wir geben deine Daten nicht an Dritte weiter" diff --git a/features/createroom/impl/src/main/res/values-de/translations.xml b/features/createroom/impl/src/main/res/values-de/translations.xml index 6c77a2ad28f..441f747e7e2 100644 --- a/features/createroom/impl/src/main/res/values-de/translations.xml +++ b/features/createroom/impl/src/main/res/values-de/translations.xml @@ -7,7 +7,7 @@ "Privater Raum (nur auf Einladung)" "Die Nachrichten sind nicht verschlüsselt und können von jedem gelesen werden. Die Verschlüsselung kann zu einem späteren Zeitpunkt aktiviert werden." "Öffentlicher Raum (für alle)" - "Raum-Name" + "Raumname" "Raum erstellen" "Thema (optional)" "Beim Versuch, einen Chat zu starten, ist ein Fehler aufgetreten" diff --git a/features/ftue/impl/src/main/res/values-be/translations.xml b/features/ftue/impl/src/main/res/values-be/translations.xml index 768a5db68a6..e9666b57e31 100644 --- a/features/ftue/impl/src/main/res/values-be/translations.xml +++ b/features/ftue/impl/src/main/res/values-be/translations.xml @@ -15,8 +15,7 @@ "Націсніце на свой аватар" "Выберыце %1$s" "“Звязаць новую прыладу”" - "Выберыце %1$s" - "“Паказаць QR-код”" + "Выконвайце паказаныя інструкцыі" "Адкрыйце %1$s на іншай прыладзе, каб атрымаць QR-код" "Выкарыстоўвайце QR-код, паказаны на іншай прыладзе." "Паўтарыць спробу" diff --git a/features/ftue/impl/src/main/res/values-cs/translations.xml b/features/ftue/impl/src/main/res/values-cs/translations.xml index 17456a5eca1..b6c0efa729a 100644 --- a/features/ftue/impl/src/main/res/values-cs/translations.xml +++ b/features/ftue/impl/src/main/res/values-cs/translations.xml @@ -15,8 +15,7 @@ "Klikněte na svůj avatar" "Vybrat %1$s" "\"Připojit nové zařízení\"" - "Vybrat %1$s" - "\"Zobrazit QR kód\"" + "Postupujte podle uvedených pokynů" "Otevřete %1$s na jiném zařízení pro získání QR kódu" "Použijte QR kód zobrazený na druhém zařízení." "Zkusit znovu" diff --git a/features/ftue/impl/src/main/res/values-de/translations.xml b/features/ftue/impl/src/main/res/values-de/translations.xml index 93ebf87261d..4640cf119c0 100644 --- a/features/ftue/impl/src/main/res/values-de/translations.xml +++ b/features/ftue/impl/src/main/res/values-de/translations.xml @@ -15,8 +15,7 @@ "Klick auf deinen Avatar" "Wähle %1$s" "\"Neues Gerät verknüpfen\"" - "Wähle %1$s" - "\"QR-Code anzeigen\"" + "Befolge die angezeigten Anweisungen" "Öffne %1$s auf einem anderen Gerät, um den QR-Code zu erhalten" "Verwende den QR-Code, der auf dem anderen Gerät angezeigt wird." "Erneut versuchen" diff --git a/features/ftue/impl/src/main/res/values-fr/translations.xml b/features/ftue/impl/src/main/res/values-fr/translations.xml index 8caf4935843..c69b729fc7b 100644 --- a/features/ftue/impl/src/main/res/values-fr/translations.xml +++ b/features/ftue/impl/src/main/res/values-fr/translations.xml @@ -2,18 +2,33 @@ "Vous pourrez modifier vos paramètres ultérieurement." "Autorisez les notifications et ne manquez aucun message" - "Établissement de la connexion" + "Établissement d’une connexion sécurisée" + "Aucune connexion sécurisée n’a pu être établie avec la nouvelle session. Vos sessions existantes sont toujours en sécurité et vous n’avez pas à vous en soucier." + "Et maintenant ?" + "Essayez de vous connecter à nouveau à l’aide du code QR au cas où il s’agirait d’un problème réseau" + "Si vous rencontrez le même problème, essayez un autre réseau wifi ou utilisez vos données mobiles au lieu du wifi" + "Si cela ne fonctionne pas, connectez-vous manuellement" + "La connexion n’est pas sécurisée" + "Il vous sera demandé de saisir les deux chiffres affichés sur cet appareil." + "Saisissez le nombre ci-dessous sur votre autre appareil" "Ouvrez %1$s sur un ordinateur" "Cliquez sur votre image de profil" "Choisissez %1$s" "“Associer une nouvelle session”" - "Choisissez %1$s" - "“Afficher le QR code”" + "Suivez les instructions affichées" "Ouvrez %1$s sur un autre appareil pour obtenir le QR code" "Scannez le QR code affiché sur l’autre appareil." "Essayer à nouveau" "QR code erroné" + "Accéder aux paramètres de l’appareil photo" + "Vous devez autoriser %1$s à utiliser la camera de votre appareil pour continuer." + "Autoriser l’usage de la caméra pour scanner le code QR" "Scannez le QR code" + "Recommencer" + "Une erreur inattendue s’est produite. Veuillez réessayer." + "En attente de votre autre session" + "Votre fournisseur de compte peut vous demander le code suivant pour vérifier la connexion." + "Votre code de vérification" "Les appels, les sondages, les recherches et plus encore seront ajoutés plus tard cette année." "L’historique des messages pour les salons chiffrés ne sera pas disponible dans cette mise à jour." "N’hésitez pas à nous faire part de vos commentaires via l’écran des paramètres." diff --git a/features/ftue/impl/src/main/res/values-hu/translations.xml b/features/ftue/impl/src/main/res/values-hu/translations.xml index 2fce86ebbbb..e5c11f0c2b0 100644 --- a/features/ftue/impl/src/main/res/values-hu/translations.xml +++ b/features/ftue/impl/src/main/res/values-hu/translations.xml @@ -15,8 +15,7 @@ "Kattintson a profilképére" "Válassza ezt: %1$s" "„Új eszköz összekapcsolása”" - "Válassza ezt: %1$s" - "„QR-kód megjelenítése”" + "Kövesse a látható utasításokat" "Nyissa meg az %1$set egy másik eszközön a QR-kód lekéréséhez." "Használja a másik eszközön látható QR-kódot." "Próbálja újra" diff --git a/features/ftue/impl/src/main/res/values-in/translations.xml b/features/ftue/impl/src/main/res/values-in/translations.xml index 0150e54251a..c99839166b0 100644 --- a/features/ftue/impl/src/main/res/values-in/translations.xml +++ b/features/ftue/impl/src/main/res/values-in/translations.xml @@ -15,8 +15,6 @@ "Klik pada avatar Anda" "Pilih %1$s" "“Tautkan perangkat baru”" - "Pilih %1$s" - "“Tampilkan kode QR”" "Buka %1$s di perangkat lain untuk mendapatkan kode QR" "Gunakan kode QR yang ditampilkan di perangkat lain." "Coba lagi" diff --git a/features/ftue/impl/src/main/res/values-ru/translations.xml b/features/ftue/impl/src/main/res/values-ru/translations.xml index ca34ccf26d6..3fe5ceefc2f 100644 --- a/features/ftue/impl/src/main/res/values-ru/translations.xml +++ b/features/ftue/impl/src/main/res/values-ru/translations.xml @@ -15,8 +15,6 @@ "Нажмите на свое изображение" "Выбрать %1$s" "\"Привязать новое устройство\"" - "Выбрать %1$s" - "\"Показать QR-код\"" "Откройте %1$s на другом устройстве, чтобы получить QR-код" "Используйте QR-код, показанный на другом устройстве." "Повторить попытку" diff --git a/features/ftue/impl/src/main/res/values-sk/translations.xml b/features/ftue/impl/src/main/res/values-sk/translations.xml index cf184f6ddc6..593fe781827 100644 --- a/features/ftue/impl/src/main/res/values-sk/translations.xml +++ b/features/ftue/impl/src/main/res/values-sk/translations.xml @@ -15,8 +15,7 @@ "Kliknite na svoj obrázok" "Vyberte %1$s" "„Prepojiť nové zariadenie“" - "Vyberte %1$s" - "„Zobraziť QR kód“" + "Postupujte podľa zobrazených pokynov" "Ak chcete získať QR kód, otvorte %1$s na inom zariadení" "Použite QR kód zobrazený na druhom zariadení." "Skúste to znova" diff --git a/features/lockscreen/impl/src/main/res/values-de/translations.xml b/features/lockscreen/impl/src/main/res/values-de/translations.xml index c24e9744c41..53df8aeca06 100644 --- a/features/lockscreen/impl/src/main/res/values-de/translations.xml +++ b/features/lockscreen/impl/src/main/res/values-de/translations.xml @@ -16,7 +16,7 @@ "PIN bestätigen" "Aus Sicherheitsgründen kann dieser PIN-Code nicht verwendet werden." "Bitte eine andere PIN verwenden." - "Sperre %1$s mit einem PIN Code, um den Zugriff auf Deine Chats zu beschränken. + "Sperre %1$s mit einem PIN Code, um den Zugriff auf deine Chats zu beschränken. Wähle etwas Einprägsames. Bei falscher Eingabe wirst du aus der App ausgeloggt." "Bitte gib die gleiche PIN wie zuvor ein." diff --git a/features/lockscreen/impl/src/main/res/values-fr/translations.xml b/features/lockscreen/impl/src/main/res/values-fr/translations.xml index df95f43648d..94e65cc6f04 100644 --- a/features/lockscreen/impl/src/main/res/values-fr/translations.xml +++ b/features/lockscreen/impl/src/main/res/values-fr/translations.xml @@ -1,7 +1,7 @@ - "Authentification biométrique" - "Déverrouillage biométrique" + "authentification biométrique" + "déverrouillage biométrique" "Déverrouiller avec la biométrie" "Code PIN oublié?" "Modifier le code PIN" diff --git a/features/lockscreen/impl/src/main/res/values-sv/translations.xml b/features/lockscreen/impl/src/main/res/values-sv/translations.xml index 67c069affef..14a2faffd9b 100644 --- a/features/lockscreen/impl/src/main/res/values-sv/translations.xml +++ b/features/lockscreen/impl/src/main/res/values-sv/translations.xml @@ -1,11 +1,17 @@ + "biometrisk autentisering" + "biometrisk upplåsning" + "Lås upp med biometri" "Glömt PIN-kod?" "Byt PIN-kod" "Tillåt biometrisk upplåsning" "Ta bort PIN-kod" "Är du säker på att du vill ta bort PIN-koden?" "Ta bort PIN-koden?" + "Tillåt %1$s" + "Jag vill hellre använda PIN-kod" + "Bespara dig själv lite tid och använd %1$s för att låsa upp appen varje gång" "Välj PIN-kod" "Bekräfta PIN-kod" "Du kan inte välja detta som din PIN-kod av säkerhetsskäl" @@ -25,5 +31,7 @@ Välj något minnesvärt. Om du glömmer den här PIN-koden loggas du ut från a "Fel PIN-kod. Du har %1$d försök kvar" "Fel PIN-kod. Du har %1$d försök kvar" + "Använd biometri" + "Använd PIN-kod" "Loggar ut …" diff --git a/features/lockscreen/impl/src/main/res/values-zh-rTW/translations.xml b/features/lockscreen/impl/src/main/res/values-zh-rTW/translations.xml index 2f479b97d10..757604c4f4c 100644 --- a/features/lockscreen/impl/src/main/res/values-zh-rTW/translations.xml +++ b/features/lockscreen/impl/src/main/res/values-zh-rTW/translations.xml @@ -15,6 +15,9 @@ "確認 PIN 碼" "基於安全性的考量,您選的 PIN 碼無法使用" "選擇不一樣的 PIN 碼" + "將 %1$s 上鎖,為你的聊天室添加一層防護。 + +請選擇好記憶的數字。如果忘記 PIN 碼,您會被登出。" "請輸入相同的 PIN 碼兩次" "PIN 碼不一樣" "您需要重新登入並建立新的 PIN 碼才能繼續" diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/qrcode/DefaultQrCodeLoginManager.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/qrcode/DefaultQrCodeLoginManager.kt index c25d3b84023..1f9f4b74170 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/qrcode/DefaultQrCodeLoginManager.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/qrcode/DefaultQrCodeLoginManager.kt @@ -29,7 +29,7 @@ import javax.inject.Inject @ContributesBinding(AppScope::class) class DefaultQrCodeLoginManager @Inject constructor( private val authenticationService: MatrixAuthenticationService, -): QrCodeLoginManager { +) : QrCodeLoginManager { private val _currentLoginStep = MutableStateFlow(QrCodeLoginStep.Uninitialized) override val currentLoginStep: StateFlow = _currentLoginStep diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/confirmation/QrCodeConfirmationNode.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/confirmation/QrCodeConfirmationNode.kt index 9c3f478ad79..835e0670785 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/confirmation/QrCodeConfirmationNode.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/confirmation/QrCodeConfirmationNode.kt @@ -33,7 +33,6 @@ class QrCodeConfirmationNode @AssistedInject constructor( @Assisted buildContext: BuildContext, @Assisted plugins: List, ) : Node(buildContext = buildContext, plugins = plugins) { - interface Callback : Plugin { fun onCancel() } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/error/QrCodeErrorNode.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/error/QrCodeErrorNode.kt index b33601d0094..cb04a732c18 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/error/QrCodeErrorNode.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/error/QrCodeErrorNode.kt @@ -32,7 +32,7 @@ class QrCodeErrorNode @AssistedInject constructor( @Assisted buildContext: BuildContext, @Assisted plugins: List, ) : Node(buildContext = buildContext, plugins = plugins) { - interface Callback: Plugin { + interface Callback : Plugin { fun onRetry() } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/error/QrCodeErrorView.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/error/QrCodeErrorView.kt index b5d5e60c8e3..d6cf2bd22df 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/error/QrCodeErrorView.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/error/QrCodeErrorView.kt @@ -21,8 +21,6 @@ import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.verticalScroll import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/intro/QrCodeIntroView.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/intro/QrCodeIntroView.kt index 19f9b07a5e8..32dfebd7c97 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/intro/QrCodeIntroView.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/intro/QrCodeIntroView.kt @@ -80,8 +80,12 @@ private fun Content(state: QrCodeIntroState) { AnnotatedString(stringResource(R.string.screen_qr_code_login_initial_state_item_1, state.desktopAppName)), AnnotatedString(stringResource(R.string.screen_qr_code_login_initial_state_item_2)), annotatedTextWithBold( - text = stringResource(R.string.screen_qr_code_login_initial_state_item_3, stringResource(R.string.screen_qr_code_login_initial_state_item_3_action)), - boldText = stringResource(R.string.screen_qr_code_login_initial_state_item_3_action)), + text = stringResource( + id = R.string.screen_qr_code_login_initial_state_item_3, + stringResource(R.string.screen_qr_code_login_initial_state_item_3_action), + ), + boldText = stringResource(R.string.screen_qr_code_login_initial_state_item_3_action) + ), AnnotatedString(stringResource(R.string.screen_qr_code_login_initial_state_item_4)), ), ) 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 e32d62dbc65..bc8a6c52b67 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 @@ -38,7 +38,6 @@ class QrCodeScanPresenter @Inject constructor( private val qrCodeLoginDataFactory: MatrixQrCodeLoginDataFactory, private val coroutineDispatchers: CoroutineDispatchers, ) : Presenter { - private var isScanning by mutableStateOf(true) private val isProcessingCode = AtomicBoolean(false) 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 2344330d3cc..da3880651c4 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 @@ -34,6 +34,8 @@ import androidx.compose.foundation.progressSemantics import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.rememberUpdatedState import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource @@ -64,10 +66,11 @@ fun QrCodeScanView( onQrCodeDataReady: (MatrixQrCodeLoginData) -> Unit, modifier: Modifier = Modifier, ) { + val updatedOnQrCodeDataReady by rememberUpdatedState(onQrCodeDataReady) // QR code data parsed successfully, notify the parent node if (state.authenticationAction is AsyncAction.Success) { - LaunchedEffect(state.authenticationAction) { - onQrCodeDataReady(state.authenticationAction.data) + LaunchedEffect(state.authenticationAction, updatedOnQrCodeDataReady) { + updatedOnQrCodeDataReady(state.authenticationAction.data) } } diff --git a/features/login/impl/src/main/res/values-be/translations.xml b/features/login/impl/src/main/res/values-be/translations.xml index 228deb957c9..f393c527aeb 100644 --- a/features/login/impl/src/main/res/values-be/translations.xml +++ b/features/login/impl/src/main/res/values-be/translations.xml @@ -30,6 +30,33 @@ "Matrix - гэта адкрытая сетка для бяспечнай, дэцэнтралізаванай сувязі." "Сардэчна запрашаем!" "Увайдзіце ў %1$s" + "Ўсталяванне бяспечнага злучэння" + "Не атрымалася ўсталяваць бяспечнае злучэнне з новай прыладай. Існуючыя прылады па-ранейшаму ў бяспецы, і вам не трэба турбавацца пра іх." + "Што зараз?" + "Паспрабуйце зноў увайсці ў сістэму з дапамогай QR-кода, калі гэта была сеткавая праблема" + "Калі вы сутыкнуліся з той жа праблемай, паспрабуйце іншую сетку Wi-Fi або скарыстайцеся мабільнымі дадзенымі замест Wi-Fi." + "Калі гэта не дапамагло, увайдзіце ўручную" + "Злучэнне небяспечнае" + "Вам будзе прапанавана ўвесці дзве лічбы, паказаныя на гэтай прыладзе." + "Увядзіце наступны нумар на іншай прыладзе." + "Адкрыйце %1$s на настольнай прыладзе" + "Націсніце на свой аватар" + "Выберыце %1$s" + "“Звязаць новую прыладу”" + "Выконвайце паказаныя інструкцыі" + "Адкрыйце %1$s на іншай прыладзе, каб атрымаць QR-код" + "Выкарыстоўвайце QR-код, паказаны на іншай прыладзе." + "Паўтарыць спробу" + "Няправільны QR-код" + "Перайсці ў налады камеры" + "Каб працягнуць, вам неабходна дазволіць %1$s выкарыстоўваць камеру вашай прылады." + "Дазвольце доступ да камеры для сканавання QR-кода" + "Сканаваць QR-код" + "Пачаць спачатку" + "Адбылася нечаканая памылка. Калі ласка, паспрабуйце яшчэ раз." + "У чаканні іншай прылады" + "Ваш правайдэр уліковага запісу можа запытаць наступны код для праверкі ўваходу." + "Ваш код спраўджання" "Змяніць правайдара ўліковага запісу" "Прыватны сервер для супрацоўнікаў Element." "Matrix - гэта адкрытая сетка для бяспечнай, дэцэнтралізаванай сувязі." diff --git a/features/login/impl/src/main/res/values-bg/translations.xml b/features/login/impl/src/main/res/values-bg/translations.xml index f4e52f7ac6e..cfc51c3f8c4 100644 --- a/features/login/impl/src/main/res/values-bg/translations.xml +++ b/features/login/impl/src/main/res/values-bg/translations.xml @@ -18,6 +18,7 @@ "Matrix е отворена мрежа за сигурна, децентрализирана комуникация." "Добре дошли отново!" "Влизане в %1$s" + "Повторен опит" "Промяна на доставчика на акаунт" "Matrix е отворена мрежа за сигурна, децентрализирана комуникация." "Това е мястото, където ще живеят вашите разговори — точно както бихте използвали имейл доставчик, за да съхранявате вашите имейли." diff --git a/features/login/impl/src/main/res/values-cs/translations.xml b/features/login/impl/src/main/res/values-cs/translations.xml index 0616f621b1a..ae5981e3c07 100644 --- a/features/login/impl/src/main/res/values-cs/translations.xml +++ b/features/login/impl/src/main/res/values-cs/translations.xml @@ -30,6 +30,33 @@ "Matrix je otevřená síť pro bezpečnou a decentralizovanou komunikaci." "Vítejte zpět!" "Přihlaste se k %1$s" + "Navazování zabezpečeného spojení" + "K novému zařízení se nepodařilo navázat bezpečné připojení. Vaše stávající zařízení jsou stále v bezpečí a nemusíte se o ně obávat." + "Co teď?" + "Zkuste se znovu přihlásit pomocí QR kódu v případě, že se jednalo o problém se sítí" + "Pokud narazíte na stejný problém, zkuste jinou síť wifi nebo použijte mobilní data místo wifi" + "Pokud to nefunguje, přihlaste se ručně" + "Připojení není zabezpečené" + "Budete požádáni o zadání dvou níže uvedených číslic." + "Zadejte níže uvedené číslo na svém dalším zařízení" + "Otevřete %1$s na stolním počítači" + "Klikněte na svůj avatar" + "Vybrat %1$s" + "\"Připojit nové zařízení\"" + "Postupujte podle uvedených pokynů" + "Otevřete %1$s na jiném zařízení pro získání QR kódu" + "Použijte QR kód zobrazený na druhém zařízení." + "Zkusit znovu" + "Špatný QR kód" + "Přejděte na nastavení fotoaparátu" + "Abyste mohli pokračovat, musíte aplikaci %1$s udělit povolení k použití kamery vašeho zařízení." + "Povolte přístup k fotoaparátu a naskenujte QR kód" + "Naskenujte QR kód" + "Začít znovu" + "Vyskytla se neočekávaná chyba. Prosím zkuste to znovu." + "Čekání na vaše další zařízení" + "Váš poskytovatel účtu může požádat o následující kód pro ověření přihlášení." + "Váš ověřovací kód" "Změnit poskytovatele účtu" "Soukromý server pro zaměstnance Elementu." "Matrix je otevřená síť pro bezpečnou a decentralizovanou komunikaci." diff --git a/features/login/impl/src/main/res/values-de/translations.xml b/features/login/impl/src/main/res/values-de/translations.xml index 27fd2265179..6873dd8fde9 100644 --- a/features/login/impl/src/main/res/values-de/translations.xml +++ b/features/login/impl/src/main/res/values-de/translations.xml @@ -30,6 +30,33 @@ "Matrix ist ein offenes Netzwerk für eine sichere, dezentrale Kommunikation." "Willkommen zurück!" "Anmelden bei %1$s" + "Sichere Verbindung aufbauen" + "Es konnte keine sichere Verbindung zu dem neuen Gerät hergestellt werden." + "Und jetzt?" + "Versuche, dich erneut mit einem QR-Code anzumelden, falls dies ein Netzwerkproblem war." + "Wenn das Problem bestehen bleibt, versuche es mit einem anderen WLAN-Netzwerk oder verwende deine mobilen Daten statt WLAN." + "Wenn das nicht funktioniert, melde dich manuell an" + "Die Verbindung ist nicht sicher" + "Du wirst aufgefordert, die beiden unten abgebildeten Ziffern einzugeben." + "Trage die unten angezeigte Zahl auf einem anderen Device ein" + "%1$s auf einem Desktop-Gerät öffnen" + "Klick auf deinen Avatar" + "Wähle %1$s" + "\"Neues Gerät verknüpfen\"" + "Befolge die angezeigten Anweisungen" + "Öffne %1$s auf einem anderen Gerät, um den QR-Code zu erhalten" + "Verwende den QR-Code, der auf dem anderen Gerät angezeigt wird." + "Erneut versuchen" + "Falscher QR-Code" + "Gehe zu den Kameraeinstellungen" + "Du musst %1$s die Erlaubnis erteilen, die Kamera deines Geräts zu verwenden, um fortzufahren." + "Erlaube Zugriff auf die Kamera zum Scannen des QR-Codes" + "QR-Code scannen" + "Neu beginnen" + "Ein unerwarteter Fehler ist aufgetreten. Bitte versuche es erneut." + "Warten auf dein anderes Gerät" + "Dein Account-Provider kann nach dem folgenden Code fragen, um die Anmeldung zu bestätigen." + "Dein Verifizierungscode" "Kontoanbieter wechseln" "Ein privater Server für die Mitarbeiter von Element." "Matrix ist ein offenes Netzwerk für eine sichere, dezentrale Kommunikation." diff --git a/features/login/impl/src/main/res/values-es/translations.xml b/features/login/impl/src/main/res/values-es/translations.xml index 28e003edc67..9325ae8c623 100644 --- a/features/login/impl/src/main/res/values-es/translations.xml +++ b/features/login/impl/src/main/res/values-es/translations.xml @@ -27,6 +27,7 @@ "Matrix es una red abierta para una comunicación segura y descentralizada." "¡Hola de nuevo!" "Iniciar sesión en %1$s" + "Inténtalo de nuevo" "Cambiar el proveedor de la cuenta" "Un servidor privado para los empleados de Element." "Matrix es una red abierta para una comunicación segura y descentralizada." diff --git a/features/login/impl/src/main/res/values-fr/translations.xml b/features/login/impl/src/main/res/values-fr/translations.xml index 3a882ae9652..eb94c8b0f04 100644 --- a/features/login/impl/src/main/res/values-fr/translations.xml +++ b/features/login/impl/src/main/res/values-fr/translations.xml @@ -14,7 +14,8 @@ "Utilisez un autre fournisseur de compte, tel que votre propre serveur privé ou un serveur professionnel." "Changer de fournisseur de compte" "Nous n’avons pas pu atteindre ce serveur d’accueil. Vérifiez que vous avez correctement saisi l’URL du serveur d’accueil. Si l’URL est correcte, contactez l’administrateur de votre serveur d’accueil pour obtenir de l’aide." - "Sliding sync n’est pas disponible en raison d’un problème dans le well-known file : %1$s" + "Sliding sync n’est pas disponible en raison d’un problème dans le well-known file : +%1$s" "Ce serveur ne prend actuellement pas en charge la synchronisation glissante." "URL du serveur d’accueil" "Vous ne pouvez vous connecter qu’à un serveur existant qui prend en charge le sliding sync. L’administrateur de votre serveur d’accueil devra le configurer. %1$s" @@ -29,6 +30,33 @@ "Matrix est un réseau ouvert pour une communication sécurisée et décentralisée." "Content de vous revoir !" "Connectez-vous à %1$s" + "Établissement d’une connexion sécurisée" + "Aucune connexion sécurisée n’a pu être établie avec la nouvelle session. Vos sessions existantes sont toujours en sécurité et vous n’avez pas à vous en soucier." + "Et maintenant ?" + "Essayez de vous connecter à nouveau à l’aide du code QR au cas où il s’agirait d’un problème réseau" + "Si vous rencontrez le même problème, essayez un autre réseau wifi ou utilisez vos données mobiles au lieu du wifi" + "Si cela ne fonctionne pas, connectez-vous manuellement" + "La connexion n’est pas sécurisée" + "Il vous sera demandé de saisir les deux chiffres affichés sur cet appareil." + "Saisissez le nombre ci-dessous sur votre autre appareil" + "Ouvrez %1$s sur un ordinateur" + "Cliquez sur votre image de profil" + "Choisissez %1$s" + "“Associer une nouvelle session”" + "Suivez les instructions affichées" + "Ouvrez %1$s sur un autre appareil pour obtenir le QR code" + "Scannez le QR code affiché sur l’autre appareil." + "Essayer à nouveau" + "QR code erroné" + "Accéder aux paramètres de l’appareil photo" + "Vous devez autoriser %1$s à utiliser la camera de votre appareil pour continuer." + "Autoriser l’usage de la caméra pour scanner le code QR" + "Scannez le QR code" + "Recommencer" + "Une erreur inattendue s’est produite. Veuillez réessayer." + "En attente de votre autre session" + "Votre fournisseur de compte peut vous demander le code suivant pour vérifier la connexion." + "Votre code de vérification" "Changer de fournisseur de compte" "Un serveur privé pour les employés d’Element." "Matrix est un réseau ouvert pour une communication sécurisée et décentralisée." diff --git a/features/login/impl/src/main/res/values-hu/translations.xml b/features/login/impl/src/main/res/values-hu/translations.xml index f81e84a86f9..9d5d99d46ec 100644 --- a/features/login/impl/src/main/res/values-hu/translations.xml +++ b/features/login/impl/src/main/res/values-hu/translations.xml @@ -30,6 +30,33 @@ "A Matrix egy nyitott hálózat a biztonságos, decentralizált kommunikációhoz." "Örülünk, hogy visszatért!" "Bejelentkezés ide: %1$s" + "Biztonságos kapcsolat létesítése" + "Nem sikerült biztonságos kapcsolatot létesíteni az új eszközzel. A meglévő eszközei továbbra is biztonságban vannak, és nem kell aggódnia miattuk." + "Most mi lesz?" + "Próbáljon meg újra bejelentkezni egy QR-kóddal, ha ez hálózati probléma volt." + "Ha ugyanezzel a problémával találkozik, próbálkozzon másik Wi-Fi-hálózattal, vagy a Wi-Fi helyett használja a mobil-adatkapcsolatát" + "Ha ez nem működik, jelentkezzen be kézileg" + "A kapcsolat nem biztonságos" + "A rendszer kérni fogja, hogy adja meg az alábbi két számjegyet az eszközén." + "Adja meg az alábbi számot a másik eszközén" + "Nyissa meg az %1$set egy asztali eszközön" + "Kattintson a profilképére" + "Válassza ezt: %1$s" + "„Új eszköz összekapcsolása”" + "Kövesse a látható utasításokat" + "Nyissa meg az %1$set egy másik eszközön a QR-kód lekéréséhez." + "Használja a másik eszközön látható QR-kódot." + "Próbálja újra" + "Hibás QR-kód" + "Ugrás a kamerabeállításokhoz" + "A folytatáshoz engedélyeznie kell, hogy az %1$s használhassa az eszköz kameráját." + "Engedélyezze a kamera elérését a QR-kód beolvasásához" + "Olvassa be a QR-kódot" + "Újrakezdés" + "Váratlan hiba történt. Próbálja meg újra." + "Várakozás a másik eszközre" + "A fiókszolgáltatója kérheti a következő kódot a bejelentkezése ellenőrzéséhez." + "Az Ön ellenőrzőkódja" "Fiókszolgáltató módosítása" "Egy privát kiszolgáló az Element alkalmazottai számára." "A Matrix egy nyitott hálózat a biztonságos, decentralizált kommunikációhoz." diff --git a/features/login/impl/src/main/res/values-in/translations.xml b/features/login/impl/src/main/res/values-in/translations.xml index 17d61faae21..4f32383f208 100644 --- a/features/login/impl/src/main/res/values-in/translations.xml +++ b/features/login/impl/src/main/res/values-in/translations.xml @@ -30,6 +30,32 @@ "Matrix adalah jaringan terbuka untuk komunikasi yang aman dan terdesentralisasi." "Selamat datang kembali!" "Masuk ke %1$s" + "Membuat koneksi" + "Koneksi aman tidak dapat dibuat ke perangkat baru. Perangkat Anda yang ada masih aman dan Anda tidak perlu khawatir tentang mereka." + "Apa sekarang?" + "Coba masuk lagi dengan kode QR jika ini adalah masalah jaringan" + "Jika Anda mengalami masalah yang sama, coba jaringan Wi-Fi yang berbeda atau gunakan data seluler Anda daripada Wi-Fi" + "Jika tidak berhasil, masuk secara manual" + "Koneksi tidak aman" + "Anda akan diminta untuk memasukkan dua digit yang ditunjukkan di bawah ini." + "Masukkan nomor di perangkat Anda" + "Buka %1$s di perangkat desktop" + "Klik pada avatar Anda" + "Pilih %1$s" + "“Tautkan perangkat baru”" + "Buka %1$s di perangkat lain untuk mendapatkan kode QR" + "Gunakan kode QR yang ditampilkan di perangkat lain." + "Coba lagi" + "Kode QR salah" + "Pergi ke pengaturan kamera" + "Anda perlu memberikan izin ke %1$s untuk menggunakan kamera perangkat Anda untuk melanjutkan." + "Izinkan akses kamera untuk memindai kode QR" + "Pindai kode QR" + "Mulai dari awal" + "Terjadi kesalahan tak terduga. Silakan coba lagi." + "Menunggu perangkat Anda yang lain" + "Penyedia akun Anda mungkin meminta kode berikut untuk memverifikasi proses masuk." + "Kode verifikasi Anda" "Ubah penyedia akun" "Server pribadi untuk karyawan Element." "Matrix adalah jaringan terbuka untuk komunikasi yang aman dan terdesentralisasi." diff --git a/features/login/impl/src/main/res/values-it/translations.xml b/features/login/impl/src/main/res/values-it/translations.xml index dcfc5c7b089..59753ed9268 100644 --- a/features/login/impl/src/main/res/values-it/translations.xml +++ b/features/login/impl/src/main/res/values-it/translations.xml @@ -30,6 +30,7 @@ "Matrix è una rete aperta per comunicazioni sicure e decentralizzate." "Bentornato!" "Accedi a %1$s" + "Riprova" "Cambia fornitore dell\'account" "Un server privato per i dipendenti di Element." "Matrix è una rete aperta per comunicazioni sicure e decentralizzate." diff --git a/features/login/impl/src/main/res/values-ro/translations.xml b/features/login/impl/src/main/res/values-ro/translations.xml index 3f384a8764b..4ac61b4b6bf 100644 --- a/features/login/impl/src/main/res/values-ro/translations.xml +++ b/features/login/impl/src/main/res/values-ro/translations.xml @@ -27,6 +27,7 @@ "Matrix este o rețea deschisă pentru o comunicare sigură și descentralizată." "Bine ați revenit!" "Conectați-vă la %1$s" + "Încercați din nou" "Schimbați furnizorul contului" "Un server privat pentru angajații Element." "Matrix este o rețea deschisă pentru o comunicare sigură și descentralizată." diff --git a/features/login/impl/src/main/res/values-ru/translations.xml b/features/login/impl/src/main/res/values-ru/translations.xml index ef643cd8a10..f1d6b4f2144 100644 --- a/features/login/impl/src/main/res/values-ru/translations.xml +++ b/features/login/impl/src/main/res/values-ru/translations.xml @@ -30,6 +30,32 @@ "Matrix — это открытая сеть для безопасной децентрализованной связи." "Рады видеть вас снова!" "Войти в %1$s" + "Установление соединения" + "Не удалось установить безопасное соединение с новым устройством. Существующие устройства по-прежнему в безопасности, и вам не нужно беспокоиться о них." + "Что теперь?" + "Попробуйте снова войти в систему с помощью QR-кода, если это была сетевая проблема" + "Если вы столкнулись с той же проблемой, попробуйте сменить точку доступа Wi-Fi или используйте мобильные данные" + "Если это не помогло, войдите вручную" + "Соединение не защищено" + "Вам будет предложено ввести две цифры, показанные ниже." + "Введите номер на своем устройстве" + "Откройте %1$s на настольном устройстве" + "Нажмите на свое изображение" + "Выбрать %1$s" + "\"Привязать новое устройство\"" + "Откройте %1$s на другом устройстве, чтобы получить QR-код" + "Используйте QR-код, показанный на другом устройстве." + "Повторить попытку" + "Неверный QR-код" + "Перейдите в настройки камеры" + "Чтобы продолжить, вам необходимо разрешить %1$s использовать камеру вашего устройства." + "Разрешите доступ к камере для сканирования QR-кода" + "Сканировать QR-код" + "Начать заново" + "Произошла непредвиденная ошибка. Пожалуйста, попробуйте еще раз." + "В ожидании другого устройства" + "Поставщик учетной записи может запросить следующий код для подтверждения входа." + "Ваш код подтверждения" "Сменить учетную запись" "Частный сервер для сотрудников Element." "Matrix — это открытая сеть для безопасной децентрализованной связи." diff --git a/features/login/impl/src/main/res/values-sk/translations.xml b/features/login/impl/src/main/res/values-sk/translations.xml index fcc55b038e4..156891980e8 100644 --- a/features/login/impl/src/main/res/values-sk/translations.xml +++ b/features/login/impl/src/main/res/values-sk/translations.xml @@ -30,6 +30,33 @@ "Matrix je otvorená sieť pre bezpečnú a decentralizovanú komunikáciu." "Vitajte späť!" "Prihlásiť sa do %1$s" + "Nadväzovanie bezpečného spojenia" + "K novému zariadeniu sa nepodarilo vytvoriť bezpečné pripojenie. Vaše existujúce zariadenia sú stále v bezpečí a nemusíte sa o ne obávať." + "Čo teraz?" + "Skúste sa znova prihlásiť pomocou QR kódu v prípade, že ide o problém so sieťou" + "Ak narazíte na rovnaký problém, vyskúšajte inú sieť Wi-Fi alebo namiesto siete Wi-Fi použite mobilné dáta" + "Ak to nefunguje, prihláste sa manuálne" + "Pripojenie nie je bezpečené" + "Budete požiadaní o zadanie dvoch číslic zobrazených na tomto zariadení." + "Zadajte nižšie uvedené číslo na vašom druhom zariadení" + "Otvorte %1$s na stolnom zariadení" + "Kliknite na svoj obrázok" + "Vyberte %1$s" + "„Prepojiť nové zariadenie“" + "Postupujte podľa zobrazených pokynov" + "Ak chcete získať QR kód, otvorte %1$s na inom zariadení" + "Použite QR kód zobrazený na druhom zariadení." + "Skúste to znova" + "Nesprávny QR kód" + "Prejsť na nastavenia fotoaparátu" + "Ak chcete pokračovať, musíte udeliť povolenie aplikácii %1$s používať fotoaparát vášho zariadenia." + "Povoľte prístup k fotoaparátu na naskenovanie QR kódu" + "Naskenovať QR kód" + "Začať odznova" + "Vyskytla sa neočakávaná chyba. Prosím, skúste to znova." + "Čaká sa na vaše druhé zariadenie" + "Váš poskytovateľ účtu môže požiadať o nasledujúci kód na overenie prihlásenia." + "Váš overovací kód" "Zmeniť poskytovateľa účtu" "Súkromný server pre zamestnancov spoločnosti Element." "Matrix je otvorená sieť pre bezpečnú a decentralizovanú komunikáciu." diff --git a/features/login/impl/src/main/res/values-sv/translations.xml b/features/login/impl/src/main/res/values-sv/translations.xml index 011552465ad..ad27beb87ba 100644 --- a/features/login/impl/src/main/res/values-sv/translations.xml +++ b/features/login/impl/src/main/res/values-sv/translations.xml @@ -27,6 +27,7 @@ "Matrix är ett öppet nätverk för säker, decentraliserad kommunikation." "Välkommen tillbaka!" "Logga in på %1$s" + "Försök igen" "Byt kontoleverantör" "En privat server för Element-anställda." "Matrix är ett öppet nätverk för säker, decentraliserad kommunikation." diff --git a/features/login/impl/src/main/res/values-uk/translations.xml b/features/login/impl/src/main/res/values-uk/translations.xml index df952171fce..1e01a8eba03 100644 --- a/features/login/impl/src/main/res/values-uk/translations.xml +++ b/features/login/impl/src/main/res/values-uk/translations.xml @@ -30,6 +30,7 @@ "Matrix — це відкрита мережа для безпечної, децентралізованої комунікації." "З поверненням!" "Увійти в %1$s" + "Спробуйте ще раз" "Змінити провайдера облікового запису" "Приватний сервер для співробітників Element." "Matrix — це відкрита мережа для безпечної, децентралізованої комунікації." diff --git a/features/login/impl/src/main/res/values-zh-rTW/translations.xml b/features/login/impl/src/main/res/values-zh-rTW/translations.xml index 0127d21f8f9..d345095c4b0 100644 --- a/features/login/impl/src/main/res/values-zh-rTW/translations.xml +++ b/features/login/impl/src/main/res/values-zh-rTW/translations.xml @@ -23,6 +23,7 @@ "Matrix 是一個開放網路,為了安全且去中心化的通訊而生。" "歡迎回來!" "登入 %1$s" + "再試一次" "更改帳號提供者" "Matrix 是一個開放網路,為了安全且去中心化的通訊而生。" "您的所有對話將保存於此,就如同您的電子郵件供應商會保存您的電子郵件一樣。" diff --git a/features/logout/impl/src/main/res/values-de/translations.xml b/features/logout/impl/src/main/res/values-de/translations.xml index a93dc95b54b..eeec111177a 100644 --- a/features/logout/impl/src/main/res/values-de/translations.xml +++ b/features/logout/impl/src/main/res/values-de/translations.xml @@ -5,7 +5,7 @@ "Abmelden" "Abmelden…" "Du bist dabei, dich von deiner letzten Sitzung abzumelden. Wenn du dich jetzt abmeldest, verlierst du den Zugriff auf deine verschlüsselten Nachrichten." - "Du hast das Backup ausgeschaltet" + "Du hast das Backup deaktiviert." "Deine Schlüssel wurden noch gesichert, als du offline gegangen bist. Stelle die Verbindung wieder her, damit deine Schlüssel gesichert werden können, bevor du dich abmeldest." "Deine Schlüssel werden noch gesichert" "Bitte warte, bis der Vorgang abgeschlossen ist, bevor du dich abmeldest." diff --git a/features/logout/impl/src/main/res/values-sv/translations.xml b/features/logout/impl/src/main/res/values-sv/translations.xml index d1c1369e147..fdf0e5102e6 100644 --- a/features/logout/impl/src/main/res/values-sv/translations.xml +++ b/features/logout/impl/src/main/res/values-sv/translations.xml @@ -4,8 +4,15 @@ "Logga ut" "Logga ut" "Loggar ut …" + "Du är på väg att logga ut ur din senaste session. Om du loggar ut nu kommer du att förlora åtkomsten till dina krypterade meddelanden." "Du har stängt av säkerhetskopiering" + "Dina nycklar säkerhetskopierades fortfarande när du gick offline. Anslut igen så att dina nycklar kan säkerhetskopieras innan du loggar ut." + "Dina nycklar säkerhetskopieras fortfarande" + "Vänta tills detta är klart innan du loggar ut." + "Dina nycklar säkerhetskopieras fortfarande" "Logga ut" "Du är på väg att logga ut ur din sista session. Om du loggar ut nu förlorar du åtkomsten till dina krypterade meddelanden." "Återställning inte inställd" + "Du är på väg att logga ut från din senaste session. Om du loggar ut nu kan du förlora åtkomsten till dina krypterade meddelanden." + "Har du sparat din återställningsnyckel?" diff --git a/features/messages/impl/src/main/res/values-sv/translations.xml b/features/messages/impl/src/main/res/values-sv/translations.xml index fddf525181f..72b65967433 100644 --- a/features/messages/impl/src/main/res/values-sv/translations.xml +++ b/features/messages/impl/src/main/res/values-sv/translations.xml @@ -21,8 +21,10 @@ "Omröstning" "Textformatering" "Meddelandehistoriken är för närvarande otillgänglig." + "Meddelandehistorik är inte tillgänglig i det här rummet. Verifiera den här enheten för att se din meddelandehistorik." "Vill du bjuda tillbaka dem?" "Du är ensam i den här chatten" + "Meddela hela rummet" "Alla" "Skicka igen" "Ditt meddelande kunde inte skickas" diff --git a/features/roomdetails/impl/src/main/res/values-de/translations.xml b/features/roomdetails/impl/src/main/res/values-de/translations.xml index dfecc5eb757..575ac4f2268 100644 --- a/features/roomdetails/impl/src/main/res/values-de/translations.xml +++ b/features/roomdetails/impl/src/main/res/values-de/translations.xml @@ -20,15 +20,15 @@ "Personen entfernen" "Avatar ändern" "Raum-Details anpassen" - "Raum-Name ändern" - "Raum-Thema ändern" + "Raumname ändern" + "Raumthema ändern" "Nachrichten senden" "Admins bearbeiten" - "Du kannst diese Aktion nicht mehr rückgängig machen. Du vergibst dieselbe Rolle, wie auch Du sie hast." + "Du kannst diese Aktion nicht mehr rückgängig machen. Du vergibst dieselbe Rolle, die du auch hast." "Als Administrator hinzufügen?" "Zurückstufen" "Du stufst dich selbst herab. Diese Änderung kann nicht rückgängig gemacht werden. Wenn du der letzte Benutzer mit dieser Rolle bist, ist es nicht möglich, diese Rolle wiederzuerlangen." - "Möchtest Du Dich selbst herabstufen?" + "Möchtest du dich selbst herabstufen?" "%1$s (Ausstehend)" "(Ausstehend)" "Administratoren haben automatisch Moderatorenrechte" @@ -56,7 +56,7 @@ "Standard" "Benachrichtigungen" "Rollen und Berechtigungen" - "Raum-Name" + "Raumname" "Sicherheit" "Teilen" "Informationen" diff --git a/features/roomlist/impl/src/main/res/values-sv/translations.xml b/features/roomlist/impl/src/main/res/values-sv/translations.xml index 7bc59f17e85..7253f19276b 100644 --- a/features/roomlist/impl/src/main/res/values-sv/translations.xml +++ b/features/roomlist/impl/src/main/res/values-sv/translations.xml @@ -1,5 +1,7 @@ + "Din chattsäkerhetskopia är för närvarande inte synkroniserad. Du måste ange din återställningsnyckel för att behålla åtkomsten till din chattsäkerhetskopia." + "Ange din återställningsnyckel" "Är du säker på att du vill tacka nej till inbjudan att gå med%1$s?" "Avböj inbjudan" "Är du säker på att du vill avböja denna privata chatt med %1$s?" diff --git a/features/roomlist/impl/src/main/res/values-zh-rTW/translations.xml b/features/roomlist/impl/src/main/res/values-zh-rTW/translations.xml index 84de2a0d351..8d0ec972f73 100644 --- a/features/roomlist/impl/src/main/res/values-zh-rTW/translations.xml +++ b/features/roomlist/impl/src/main/res/values-zh-rTW/translations.xml @@ -8,6 +8,8 @@ "建立新的對話或聊天室" "我的最愛" "夥伴" + "聊天室" + "未讀" "所有聊天室" "您似乎正在使用新的裝置。請使用另一個裝置進行驗證,以存取您的加密訊息。" "驗證這是您本人" diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/createkey/CreateNewRecoveryKeyView.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/createkey/CreateNewRecoveryKeyView.kt index 4161566719a..53201d697b4 100644 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/createkey/CreateNewRecoveryKeyView.kt +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/createkey/CreateNewRecoveryKeyView.kt @@ -71,7 +71,8 @@ private fun Content(desktopApplicationName: String) { add( annotatedTextWithBold( text = stringResource(R.string.screen_create_new_recovery_key_list_item_3), - boldText = stringResource(R.string.screen_create_new_recovery_key_list_item_3_reset_all)) + boldText = stringResource(R.string.screen_create_new_recovery_key_list_item_3_reset_all) + ) ) add(AnnotatedString(stringResource(R.string.screen_create_new_recovery_key_list_item_4))) add(AnnotatedString(stringResource(R.string.screen_create_new_recovery_key_list_item_5))) diff --git a/features/securebackup/impl/src/main/res/values-de/translations.xml b/features/securebackup/impl/src/main/res/values-de/translations.xml index c7a4eca7d37..c95e51db7ea 100644 --- a/features/securebackup/impl/src/main/res/values-de/translations.xml +++ b/features/securebackup/impl/src/main/res/values-de/translations.xml @@ -31,13 +31,14 @@ "Ausschalten" "Du verlierst deine verschlüsselten Nachrichten, wenn du auf allen Geräten abgemeldet bist." - "Bist du sicher, dass du das Backup ausschalten willst?" - "Wenn du das Backup ausschaltest, wird dein aktuelles Backup des Verschlüsselungsschlüssels entfernt und andere Sicherheitsfunktionen werden deaktiviert. In diesem Fall wirst du:" - "Keine Historie für verschlüsselte Nachrichten auf neuen Geräten" + "Bist du sicher, dass du das Backup deaktivieren willst?" + "Wenn du das Backup deaktivierst, wird dein aktuelles Backup des Verschlüsselungsschlüssels entfernt und andere Sicherheitsfunktionen werden deaktiviert. +Das bedeutet:" + "Keine Historie für verschlüsselte Nachrichten auf neuen Geräten ." "Du verlierst den Zugriff auf deine verschlüsselten Nachrichten, wenn du dich überall von %1$s abmeldest" - "Bist du sicher, dass du das Backup ausschalten willst?" - "Besorge dir einen neuen Wiederherstellungsschlüssel, wenn du deinen alten verloren hast. Nachdem du deinen Wiederherstellungsschlüssel geändert hast, funktioniert dein alter Schlüssel nicht mehr." - "Erstelle einen neuen Wiederherstellungsschlüssel" + "Bist du sicher, dass du das Backup deaktivieren willst?" + "Hier kannst Du einen neuen Wiederherstellungsschlüssel erstellen. Nachdem Du einen neuen Wiederherstellungsschlüssel erstellt hast, funktioniert dein alter Schlüssel nicht mehr." + "Wiederherstellungsschlüssel erstellen" "Stelle sicher, dass du deinen Wiederherstellungsschlüssel an einem sicheren Ort aufbewahren kannst" "Wiederherstellungsschlüssel geändert" "Wiederherstellungsschlüssel ändern?" @@ -55,7 +56,7 @@ " oder Passcode" "Eingeben…" - "Hast Du Deinen Wiederherstellungschlüssel vergessen?" + "Hast du deinen Wiederherstellungschlüssel vergessen?" "Wiederherstellungsschlüssel bestätigt" "Bitte Wiederherstellungsschlüssel eingeben" "Wiederherstellungsschlüssel kopiert" diff --git a/features/securebackup/impl/src/main/res/values-fr/translations.xml b/features/securebackup/impl/src/main/res/values-fr/translations.xml index 67ce8fe986b..824e786cc4a 100644 --- a/features/securebackup/impl/src/main/res/values-fr/translations.xml +++ b/features/securebackup/impl/src/main/res/values-fr/translations.xml @@ -25,7 +25,7 @@ "Êtes-vous certain de vouloir désactiver la sauvegarde?" "Obtenez une nouvelle clé de récupération dans le cas où vous avez oublié l’ancienne. Après le changement, l’ancienne clé ne sera plus utilisable." "Générer une nouvelle clé" - "Assurez-vous de conserver la clé dans un endroit sûr." + "Assurez-vous de conserver la clé dans un endroit sûr" "Clé de récupération modifée" "Changer la clé de récupération?" "Créer une nouvelle clé de récupération" @@ -46,9 +46,9 @@ "Sauvegarder la clé" "La clé ne pourra plus être affichée après cette étape." "Avez-vous sauvegardé votre clé de récupération?" - "Votre sauvegarde est protégée par votre clé de récupération. Si vous avez besoin d’une nouvelle clé après la configuration, vous pourrez en créer une nouvelle en cliquant sur \"Changer la clé de récupération\"" + "Votre sauvegarde est protégée par votre clé de récupération. Si vous avez besoin d’une nouvelle clé après la configuration, vous pourrez en créer une nouvelle en cliquant sur \"Changer la clé de récupération\"." "Générer la clé de récupération" - "Assurez-vous de pouvoir enregistrer votre clé dans un endroit sécurisé." + "Assurez-vous de pouvoir enregistrer votre clé dans un endroit sécurisé" "Sauvegarde mise en place avec succès" "Configurer la sauvegarde" diff --git a/features/securebackup/impl/src/main/res/values-sv/translations.xml b/features/securebackup/impl/src/main/res/values-sv/translations.xml index 3b36ee9a436..cf73f06c7b7 100644 --- a/features/securebackup/impl/src/main/res/values-sv/translations.xml +++ b/features/securebackup/impl/src/main/res/values-sv/translations.xml @@ -22,10 +22,13 @@ "Återställningsnyckel ändrad" "Byt återställningsnyckel?" "Se till att ingen kan se den här skärmen" + "Felaktig återställningsnyckel" "Om du har en säkerhetsnyckel eller säkerhetsfras så funkar den också." "Ange …" "Återställningsnyckel bekräftad" "Ange din återställningsnyckel" + "Kopierade återställningsnyckel" + "Genererar …" "Spara återställningsnyckeln" "Skriv ner din återställningsnyckel någonstans säkert eller spara den i en lösenordshanterare." "Tryck för att kopiera återställningsnyckeln" diff --git a/features/signedout/impl/src/main/res/values-fr/translations.xml b/features/signedout/impl/src/main/res/values-fr/translations.xml index 8b4c9b74615..e093df8517a 100644 --- a/features/signedout/impl/src/main/res/values-fr/translations.xml +++ b/features/signedout/impl/src/main/res/values-fr/translations.xml @@ -3,6 +3,6 @@ "Le mot de passe de votre compte a été modifié sur un autre appareil" "Cette session a été supprimée depuis un autre appareil" "L’administrateur de votre serveur a révoqué votre accès." - "La déconnexion peut être due à une des raisons ci-dessous. Veuillez vous connecter à nouveau pour continuer à utiliser %1$s." + "La déconnexion peut être due à une des raisons ci-dessous. Veuillez vous connecter à nouveau pour continuer à utiliser %s." "Vous avez été déconnecté" diff --git a/features/verifysession/impl/src/main/res/values-sv/translations.xml b/features/verifysession/impl/src/main/res/values-sv/translations.xml index e1c4572085b..bac08c22a6f 100644 --- a/features/verifysession/impl/src/main/res/values-sv/translations.xml +++ b/features/verifysession/impl/src/main/res/values-sv/translations.xml @@ -9,6 +9,7 @@ "Försök att verifiera igen" "Jag är redo" "Väntar på att matcha" + "Jämför en unik uppsättning emojis." "Jämför de unika emojierna och se till att de visas i samma ordning." "De matchar inte" "De matchar" diff --git a/features/verifysession/impl/src/main/res/values-zh-rTW/translations.xml b/features/verifysession/impl/src/main/res/values-zh-rTW/translations.xml index d73ea8e2c4f..e98480316a6 100644 --- a/features/verifysession/impl/src/main/res/values-zh-rTW/translations.xml +++ b/features/verifysession/impl/src/main/res/values-zh-rTW/translations.xml @@ -1,7 +1,11 @@ - "裝置已認證" - "使用另一個裝置" + "建立新的復原金鑰" + "驗證這部裝置以設定安全通訊。" + "確認這是你本人" + "您可以安全地讀取和發送訊息了,與您聊天的人也可以信任這部裝置。" + "裝置已驗證" + "使用另一部裝置" "正在等待其他裝置……" "似乎出了一點問題。有可能是因為等候逾時,或是請求被拒絕。" "確認顯示在其他工作階段上的表情符號是否和下方的相同。" diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/molecules/NumberedListMolecule.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/molecules/NumberedListMolecule.kt index bfe57de369a..596995a2af9 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/molecules/NumberedListMolecule.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/molecules/NumberedListMolecule.kt @@ -32,8 +32,13 @@ import io.element.android.libraries.designsystem.modifiers.squareSize import io.element.android.libraries.designsystem.theme.components.Text @Composable -fun NumberedListMolecule(index: Int, text: AnnotatedString) { +fun NumberedListMolecule( + index: Int, + text: AnnotatedString, + modifier: Modifier = Modifier, +) { Row( + modifier = modifier, horizontalArrangement = Arrangement.spacedBy(8.dp) ) { ItemNumber(index = index) diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/pages/FlowStepPage.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/pages/FlowStepPage.kt index 0a020e22b43..01a46d429ad 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/pages/FlowStepPage.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/pages/FlowStepPage.kt @@ -25,12 +25,10 @@ import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.vector.ImageVector 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.libraries.designsystem.atomic.molecules.ButtonColumnMolecule -import io.element.android.libraries.designsystem.atomic.molecules.IconTitleSubtitleMolecule import io.element.android.libraries.designsystem.components.BigIcon import io.element.android.libraries.designsystem.components.PageTitle import io.element.android.libraries.designsystem.components.button.BackButton diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/ForceOrientationInMobileDevices.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/ForceOrientationInMobileDevices.kt index 70ffb8fe3ef..0cab1f406a8 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/ForceOrientationInMobileDevices.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/ForceOrientationInMobileDevices.kt @@ -26,8 +26,8 @@ import androidx.compose.runtime.Composable @Composable fun ForceOrientationInMobileDevices(orientation: ScreenOrientation) { val windowAdaptiveInfo = currentWindowAdaptiveInfo() - if (windowAdaptiveInfo.windowSizeClass.widthSizeClass == WindowWidthSizeClass.Compact - || windowAdaptiveInfo.windowSizeClass.heightSizeClass == WindowHeightSizeClass.Compact + if (windowAdaptiveInfo.windowSizeClass.widthSizeClass == WindowWidthSizeClass.Compact || + windowAdaptiveInfo.windowSizeClass.heightSizeClass == WindowHeightSizeClass.Compact ) { ForceOrientation(orientation = orientation) } diff --git a/libraries/eventformatter/impl/src/main/res/values-fr/translations.xml b/libraries/eventformatter/impl/src/main/res/values-fr/translations.xml index b6e0f3b32d5..664aeebeaf7 100644 --- a/libraries/eventformatter/impl/src/main/res/values-fr/translations.xml +++ b/libraries/eventformatter/impl/src/main/res/values-fr/translations.xml @@ -30,7 +30,7 @@ "Vous avez rejoint le salon" "%1$s a demandé à rejoindre" "%1$s a autorisé %2$s à rejoindre" - "Vous avez autoriser %1$s à joindre le salon" + "Vous avez autorisé %1$s à joindre le salon" "Vous avez demandé à rejoindre" "%1$s a rejeté la demande de %2$s pour rejoindre" "Vous avez rejeté la demande de %1$s pour rejoindre" diff --git a/libraries/qrcode/src/main/kotlin/io/element/android/libraries/qrcode/QRCodeAnalyzer.kt b/libraries/qrcode/src/main/kotlin/io/element/android/libraries/qrcode/QRCodeAnalyzer.kt index 965ec32e3df..62e4c80e27b 100644 --- a/libraries/qrcode/src/main/kotlin/io/element/android/libraries/qrcode/QRCodeAnalyzer.kt +++ b/libraries/qrcode/src/main/kotlin/io/element/android/libraries/qrcode/QRCodeAnalyzer.kt @@ -20,7 +20,6 @@ import android.graphics.ImageFormat import androidx.camera.core.ImageAnalysis import androidx.camera.core.ImageProxy import com.google.zxing.BinaryBitmap -import com.google.zxing.DecodeHintType import com.google.zxing.NotFoundException import com.google.zxing.PlanarYUVLuminanceSource import com.google.zxing.ResultMetadataType 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 750ebad8843..aa6727ad51f 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 @@ -33,7 +33,6 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clipToBounds -import androidx.compose.ui.graphics.ImageBitmap import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalInspectionMode diff --git a/libraries/ui-strings/src/main/res/values-cs/translations.xml b/libraries/ui-strings/src/main/res/values-cs/translations.xml index 1aa3dc6ec5d..66c33b70d99 100644 --- a/libraries/ui-strings/src/main/res/values-cs/translations.xml +++ b/libraries/ui-strings/src/main/res/values-cs/translations.xml @@ -244,6 +244,7 @@ "Načítání zpráv se nezdařilo" "%1$s nemá přístup k vaší poloze. Zkuste to prosím později." "Nepodařilo se nahrát hlasovou zprávu." + "Zpráva nebyla nalezena" "%1$s nemá oprávnění k přístupu k vaší poloze. Přístup můžete povolit v Nastavení." "%1$s nemá oprávnění k přístupu k vaší poloze. Povolit přístup níže." "%1$s nemá oprávnění k přístupu k mikrofonu. Povolte přístup k nahrávání hlasové zprávy." diff --git a/libraries/ui-strings/src/main/res/values-de/translations.xml b/libraries/ui-strings/src/main/res/values-de/translations.xml index eb8702cbc42..af217016a2a 100644 --- a/libraries/ui-strings/src/main/res/values-de/translations.xml +++ b/libraries/ui-strings/src/main/res/values-de/translations.xml @@ -158,7 +158,7 @@ "Modern" "Stummschalten" "Keine Ergebnisse" - "Kein Raum-Name" + "Kein Raumname" "Offline" "oder" "Passwort" @@ -177,9 +177,7 @@ "Datenschutz­erklärung" "Reaktion" "Reaktionen" - - "Wiederherstellungsschlüssel" - + "Wiederherstellungsschlüssel" "Wird erneuert…" "%1$s antworten" "Einen Fehler melden" @@ -187,7 +185,7 @@ "Bericht eingereicht" "Rich-Text-Editor" "Raum" - "Raum-Name" + "Raumname" "z.B. dein Projektname" "Gespeicherte Änderungen" "Speichern" @@ -242,6 +240,7 @@ "Fehler beim Laden der Nachrichten" "%1$s konnte nicht auf deinen Standort zugreifen. Bitte versuche es später erneut." "Fehler beim Hochladen der Sprachnachricht." + "Nachricht nicht gefunden" "%1$s hat keine Erlaubnis, auf deinen Standort zuzugreifen. Du kannst den Zugriff in den Einstellungen aktivieren." "%1$s hat keine Erlaubnis, auf deinen Standort zuzugreifen. Aktiviere unten den Zugriff." "%1$s hat nicht die Erlaubnis auf dein Mikrofon zuzugreifen. Aktiviere den Zugriff, um eine Sprachnachricht aufzunehmen." diff --git a/libraries/ui-strings/src/main/res/values-fr/translations.xml b/libraries/ui-strings/src/main/res/values-fr/translations.xml index e3c91a8804f..484d4835ae3 100644 --- a/libraries/ui-strings/src/main/res/values-fr/translations.xml +++ b/libraries/ui-strings/src/main/res/values-fr/translations.xml @@ -240,6 +240,7 @@ "Échec du chargement des messages" "%1$s n’a pas pu accéder à votre position. Veuillez réessayer ultérieurement." "Échec lors de l’envoi du message vocal." + "Message introuvable" "%1$s n’est pas autorisé à accéder à votre position. Vous pouvez activer l’accès dans les Paramètres." "%1$s n’est pas autorisé à accéder à votre position. Activez l’accès ci-dessous." "%1$s n’a pas l’autorisation d’accéder au microphone. Autorisez l’accès pour enregistrer un message." @@ -261,6 +262,7 @@ "Débloquer" "Vous pourrez à nouveau voir tous ses messages." "Débloquer l’utilisateur" + "Discussion" "Partage de position" "Partager ma position" "Ouvrir dans Apple Maps" @@ -269,5 +271,5 @@ "Partager cette position" "Position" "Version : %1$s ( %2$s )" - "Ang." + "fr" diff --git a/libraries/ui-strings/src/main/res/values-hu/translations.xml b/libraries/ui-strings/src/main/res/values-hu/translations.xml index 409a2645ec0..5e669f61f45 100644 --- a/libraries/ui-strings/src/main/res/values-hu/translations.xml +++ b/libraries/ui-strings/src/main/res/values-hu/translations.xml @@ -240,6 +240,7 @@ "Nem sikerült betölteni az üzeneteket" "Az %1$s nem tudta elérni a tartózkodási helyét. Próbálja újra később." "Nem sikerült feltölteni a hangüzenetét." + "Az üzenet nem található" "Az %1$snek nincs engedélye, hogy hozzáférjen a tartózkodási helyéhez. Ezt a beállításokban engedélyezheti." "Az %1$snek nincs engedélye, hogy hozzáférjen a tartózkodási helyéhez. Engedélyezze alább az elérését." "Az %1$snek nincs engedélye, hogy hozzáférjen a mikrofonhoz. Engedélyezze, hogy tudjon hangüzenetet felvenni." diff --git a/libraries/ui-strings/src/main/res/values-sv/translations.xml b/libraries/ui-strings/src/main/res/values-sv/translations.xml index 78b87f5cbef..11bdb97932c 100644 --- a/libraries/ui-strings/src/main/res/values-sv/translations.xml +++ b/libraries/ui-strings/src/main/res/values-sv/translations.xml @@ -15,6 +15,7 @@ "Avslutade omröstning" "Skicka filer" "Visa lösenord" + "Starta ett samtal" "Användarmeny" "Spela in röstmeddelande." "Godkänn" @@ -39,6 +40,7 @@ "Redigera omröstning" "Aktivera" "Avsluta omröstning" + "Ange PIN-kod" "Glömt lösenordet?" "Vidarebefordra" "Bjud in" @@ -146,8 +148,10 @@ "Uppdaterar …" "Svarar till %1$s" "Rapportera en bugg" + "Rapportera ett problem" "Rapport inskickad" "Riktextredigerare" + "Rum" "Rumsnamn" "t.ex. ditt projektnamn" "Skärmlås" @@ -159,6 +163,7 @@ "Server-URL" "Inställningar" "Delade plats" + "Loggar ut" "Startar chatt …" "Dekal" "Lyckades" @@ -178,9 +183,11 @@ "Användarnamn" "Verifiering avbruten" "Verifieringen slutförd" + "Verifiera enheten" "Video" "Röstmeddelande" "Väntar …" + "Väntar på detta meddelande" "Bekräftelse" "Fel" "Lyckades" diff --git a/libraries/ui-strings/src/main/res/values/localazy.xml b/libraries/ui-strings/src/main/res/values/localazy.xml index f25969332e3..cede56abc39 100644 --- a/libraries/ui-strings/src/main/res/values/localazy.xml +++ b/libraries/ui-strings/src/main/res/values/localazy.xml @@ -262,6 +262,7 @@ "Unblock" "You\'ll be able to see all messages from them again." "Unblock user" + "Chat" "Share location" "Share my location" "Open in Apple Maps" From 261aef69bf12b7e44b4b0b02057f291cf8117306 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= Date: Fri, 3 May 2024 16:59:57 +0200 Subject: [PATCH 33/60] Use custom Rust SDK version --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index e407a316df3..169dfaff8d5 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -166,7 +166,7 @@ jsoup = "org.jsoup:jsoup:1.17.2" appyx_core = { module = "com.bumble.appyx:core", version.ref = "appyx" } molecule-runtime = "app.cash.molecule:molecule-runtime:1.4.2" timber = "com.jakewharton.timber:timber:5.0.1" -matrix_sdk = "org.matrix.rustcomponents:sdk-android:0.2.16" +matrix_sdk = "org.matrix.rustcomponents:sdk-android:0.2.17-qrlogin" matrix_richtexteditor = { module = "io.element.android:wysiwyg", version.ref = "wysiwyg" } matrix_richtexteditor_compose = { module = "io.element.android:wysiwyg-compose", version.ref = "wysiwyg" } sqldelight-driver-android = { module = "app.cash.sqldelight:android-driver", version.ref = "sqldelight" } From 4141f7a3cd0814d412396385b8887a5fda907e19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= Date: Mon, 6 May 2024 14:47:35 +0200 Subject: [PATCH 34/60] Adapt to changes in SDK --- .../android/libraries/matrix/impl/RustMatrixClient.kt | 8 ++------ .../libraries/matrix/impl/room/MatrixRoomInfoMapper.kt | 2 +- .../android/libraries/matrix/impl/room/RustMatrixRoom.kt | 3 ++- .../libraries/matrix/impl/roomlist/RoomListExtensions.kt | 2 +- .../matrix/impl/roomlist/RoomSummaryDetailsFactory.kt | 2 +- .../libraries/matrix/impl/timeline/RustTimeline.kt | 2 +- 6 files changed, 8 insertions(+), 11 deletions(-) diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt index 91036df1cc4..10afcf94a0f 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt @@ -96,6 +96,7 @@ import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withContext import kotlinx.coroutines.withTimeout import org.matrix.rustcomponents.sdk.BackupState @@ -145,12 +146,7 @@ class RustMatrixClient( dispatchers = dispatchers, ) private val notificationProcessSetup = NotificationProcessSetup.SingleProcess(syncService) - private val notificationClient = client.notificationClient(notificationProcessSetup) - .use { builder -> - builder - .filterByPushRules() - .finish() - } + private val notificationClient = runBlocking { client.notificationClient(notificationProcessSetup) } private val notificationService = RustNotificationService(sessionId, notificationClient, dispatchers, clock) private val notificationSettingsService = RustNotificationSettingsService(client, dispatchers) .apply { start() } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/MatrixRoomInfoMapper.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/MatrixRoomInfoMapper.kt index d3ef3283bd6..3fe4dcf1b77 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/MatrixRoomInfoMapper.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/MatrixRoomInfoMapper.kt @@ -38,7 +38,7 @@ class MatrixRoomInfoMapper( fun map(rustRoomInfo: RustRoomInfo): MatrixRoomInfo = rustRoomInfo.use { return MatrixRoomInfo( id = RoomId(it.id), - name = it.name, + name = it.displayName, topic = it.topic, avatarUrl = it.avatarUrl, isDirect = it.isDirect, diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt index 1fe5d786277..8156d44f716 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt @@ -191,8 +191,9 @@ class RustMatrixRoom( roomListItem.destroy() } + // TODO: delete this one, replace it with displayName override val name: String? - get() = runCatching { roomListItem.name() }.getOrDefault(null) + get() = runCatching { roomListItem.displayName() }.getOrDefault(null) override val displayName: String get() = runCatching { innerRoom.displayName() }.getOrDefault("") diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomListExtensions.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomListExtensions.kt index 79ea6b17e0c..8094f0bee7e 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomListExtensions.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomListExtensions.kt @@ -126,7 +126,7 @@ internal fun RoomListServiceInterface.syncIndicator(): Flow { + private suspend fun fetchDetailsForEvent(eventId: EventId): Result { return runCatching { inner.fetchDetailsForEvent(eventId.value) } From b38ec554452db430df71b295dfebf7fe3ad765de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= Date: Thu, 9 May 2024 16:39:39 +0200 Subject: [PATCH 35/60] Fix reading several incorrect QR codes breaking previews --- .../libraries/qrcode/QrCodeCameraView.kt | 120 ++++++++++++------ 1 file changed, 81 insertions(+), 39 deletions(-) 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 aa6727ad51f..29e2926ca49 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 @@ -16,6 +16,7 @@ package io.element.android.libraries.qrcode +import android.content.Context import android.graphics.Bitmap import androidx.camera.core.CameraSelector import androidx.camera.core.ImageAnalysis @@ -26,9 +27,11 @@ import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -41,7 +44,11 @@ import androidx.compose.ui.viewinterop.AndroidView import androidx.core.content.ContextCompat import io.element.android.compound.theme.ElementTheme import io.element.android.libraries.designsystem.theme.components.Text +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch import timber.log.Timber +import kotlin.coroutines.resume +import kotlin.coroutines.suspendCoroutine @Composable fun QrCodeCameraView( @@ -58,65 +65,90 @@ fun QrCodeCameraView( Text("CameraView") } } else { + val coroutineScope = rememberCoroutineScope() val localContext = LocalContext.current val lifecycleOwner = LocalLifecycleOwner.current - val cameraProviderFuture = remember { ProcessCameraProvider.getInstance(localContext) } + 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() + + LaunchedEffect(Unit) { + cameraProvider = localContext.getCameraProvider() + } + + suspend fun startQRCodeAnalysis(cameraProvider: ProcessCameraProvider, previewView: PreviewView, attempt: Int = 1) { + lastFrame = null + val cameraSelector = CameraSelector.Builder() + .requireLensFacing(CameraSelector.LENS_FACING_BACK) + .build() + imageAnalysis.setAnalyzer( + ContextCompat.getMainExecutor(previewView.context), + QRCodeAnalyzer { result -> + result?.let { + Timber.d("QR code scanned!") + onQrCodeScanned(it) + } + } + ) + try { + // Make sure we unbind all use cases before binding them again + cameraProvider.unbindAll() + + cameraProvider.bindToLifecycle( + lifecycleOwner, + cameraSelector, + previewUseCase, + imageAnalysis + ) + lastFrame = null + } catch (e: Exception) { + val maxAttempts = 3 + if (attempt > maxAttempts) { + Timber.e(e, "Use case binding failed after $maxAttempts attempts. Giving up.") + } else { + Timber.e(e, "Use case binding failed (attempt #$attempt). Retrying after a delay...") + delay(100) + startQRCodeAnalysis(cameraProvider, previewView, attempt + 1) + } + } + } + + fun stopQRCodeAnalysis(previewView: PreviewView) { + // Stop analyzer + imageAnalysis.clearAnalyzer() + + // Save last frame to display it as the 'frozen' preview + if (lastFrame == null) { + lastFrame = previewView.bitmap + Timber.d("Saving last frame for frozen preview.") + } + + // Unbind preview use case + cameraProvider?.unbindAll() + } + Box(modifier.clipToBounds()) { AndroidView( factory = { context -> val previewView = PreviewView(context) + previewUseCase.setSurfaceProvider(previewView.surfaceProvider) previewView }, update = { previewView -> if (renderPreview) { - lastFrame = null - val selector = CameraSelector.Builder() - .requireLensFacing(CameraSelector.LENS_FACING_BACK) - .build() - previewUseCase.setSurfaceProvider(previewView.surfaceProvider) - imageAnalysis.setAnalyzer( - ContextCompat.getMainExecutor(previewView.context), - QRCodeAnalyzer { result -> - result?.let { - Timber.d("QR code scanned!") - onQrCodeScanned(it) - } - } - ) - try { - cameraProviderFuture.get().bindToLifecycle( - lifecycleOwner, - selector, - previewUseCase, - imageAnalysis - ) - lastFrame = null - } catch (e: Exception) { - Timber.e(e, "Use case binding failed") + cameraProvider?.let { provider -> + coroutineScope.launch { startQRCodeAnalysis(provider, previewView) } } } else { - // Stop analyzer - imageAnalysis.clearAnalyzer() - - // Save last frame to display it as the 'frozen' preview - if (lastFrame == null) { - lastFrame = previewView.bitmap - Timber.d("Saving last frame. Is null? ${lastFrame == null}") - } - cameraProviderFuture.get().let { cameraProvider -> - if (cameraProvider.isBound(previewUseCase)) { - cameraProvider.unbind(previewUseCase) - } - } + stopQRCodeAnalysis(previewView) } }, onRelease = { - cameraProviderFuture.get().unbindAll() + cameraProvider?.unbindAll() + cameraProvider = null }, ) lastFrame?.let { @@ -125,3 +157,13 @@ fun QrCodeCameraView( } } } + +@Suppress("BlockingMethodInNonBlockingContext") +private suspend fun Context.getCameraProvider(): ProcessCameraProvider = + suspendCoroutine { continuation -> + ProcessCameraProvider.getInstance(this).also { cameraProvider -> + cameraProvider.addListener({ + continuation.resume(cameraProvider.get()) + }, ContextCompat.getMainExecutor(this)) + } + } From 3b56690c90fd4a0e2bef7b87049e747a77fea89d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= Date: Fri, 10 May 2024 08:26:23 +0200 Subject: [PATCH 36/60] Use SNAPSHOT version for the Rust SDK --- gradle/libs.versions.toml | 3 ++- settings.gradle.kts | 19 ++++++++++++++----- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 64523822dd1..14459b3712c 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -166,7 +166,8 @@ jsoup = "org.jsoup:jsoup:1.17.2" appyx_core = { module = "com.bumble.appyx:core", version.ref = "appyx" } molecule-runtime = "app.cash.molecule:molecule-runtime:1.4.2" timber = "com.jakewharton.timber:timber:5.0.1" -matrix_sdk = "org.matrix.rustcomponents:sdk-android:0.2.17-qrlogin" +# TODO: revert this to a stable version containing the QR code login feature once it's released +matrix_sdk = "org.matrix.rustcomponents:sdk-android:0.2.17-qrlogin-SNAPSHOT" matrix_richtexteditor = { module = "io.element.android:wysiwyg", version.ref = "wysiwyg" } matrix_richtexteditor_compose = { module = "io.element.android:wysiwyg-compose", version.ref = "wysiwyg" } sqldelight-driver-android = { module = "app.cash.sqldelight:android-driver", version.ref = "sqldelight" } diff --git a/settings.gradle.kts b/settings.gradle.kts index cd3689abe37..f7b49810890 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -27,9 +27,22 @@ pluginManagement { dependencyResolutionManagement { repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) repositories { + // To have immediate access to Rust SDK versions + maven { + url = URI("https://s01.oss.sonatype.org/content/repositories/releases") + content { + includeModule("org.matrix.rustcomponents", "sdk-android") + } + } + // Sonatype S01 snapshots (Rust SDK, Compound) + maven { + url = URI("https://s01.oss.sonatype.org/content/repositories/snapshots/") + content { + includeModule("org.matrix.rustcomponents", "sdk-android") + } + } google() mavenCentral() - maven { url = URI("https://oss.sonatype.org/content/repositories/snapshots/") } maven { url = URI("https://www.jitpack.io") content { @@ -37,10 +50,6 @@ dependencyResolutionManagement { includeModule("com.github.matrix-org", "matrix-analytics-events") } } - // To have immediate access to Rust SDK versions - maven { - url = URI("https://s01.oss.sonatype.org/content/repositories/releases") - } flatDir { dirs("libraries/matrix/libs") } From 338f819b7e52b79bd42c714c4ada4ae00ab61bbb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= Date: Fri, 10 May 2024 08:39:53 +0200 Subject: [PATCH 37/60] Only display the preview view when it's receiving video input --- .../io/element/android/libraries/qrcode/QrCodeCameraView.kt | 4 ++++ 1 file changed, 4 insertions(+) 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 29e2926ca49..fe72561e02c 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 @@ -42,6 +42,7 @@ import androidx.compose.ui.platform.LocalInspectionMode import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.compose.ui.viewinterop.AndroidView import androidx.core.content.ContextCompat +import androidx.core.view.isVisible import io.element.android.compound.theme.ElementTheme import io.element.android.libraries.designsystem.theme.components.Text import kotlinx.coroutines.delay @@ -135,6 +136,9 @@ fun QrCodeCameraView( factory = { context -> val previewView = PreviewView(context) previewUseCase.setSurfaceProvider(previewView.surfaceProvider) + previewView.previewStreamState.observe(lifecycleOwner) { state -> + previewView.alpha = if (state == PreviewView.StreamState.STREAMING) 1f else 0f + } previewView }, update = { previewView -> From fe2e2f3546bc9081f16c98c855e159771830bbed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= Date: Fri, 17 May 2024 15:27:02 +0200 Subject: [PATCH 38/60] Add error mapping --- .../login/impl/qrcode/QrCodeLoginFlowNode.kt | 1 - .../api/auth/qrlogin/QrCodeDecodeException.kt | 31 +++++++++++ .../api/auth/qrlogin/QrLoginException.kt | 27 ++++++++++ .../auth/RustMatrixAuthenticationService.kt | 9 ++++ .../matrix/impl/auth/qrlogin/QrErrorMapper.kt | 54 +++++++++++++++++++ 5 files changed, 121 insertions(+), 1 deletion(-) create mode 100644 libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/qrlogin/QrCodeDecodeException.kt create mode 100644 libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/qrlogin/QrLoginException.kt create mode 100644 libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/qrlogin/QrErrorMapper.kt diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/qrcode/QrCodeLoginFlowNode.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/qrcode/QrCodeLoginFlowNode.kt index cb56237443a..95e74ce1215 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/qrcode/QrCodeLoginFlowNode.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/qrcode/QrCodeLoginFlowNode.kt @@ -164,7 +164,6 @@ class QrCodeLoginFlowNode @AssistedInject constructor( authenticationJob = null } .onFailure { throwable -> - // TODO specify the error type Timber.e(throwable, "QR code authentication failed") authenticationJob = null if (throwable is CancellationException) { diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/qrlogin/QrCodeDecodeException.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/qrlogin/QrCodeDecodeException.kt new file mode 100644 index 00000000000..408a63692e8 --- /dev/null +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/qrlogin/QrCodeDecodeException.kt @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.matrix.api.auth.qrlogin + +sealed class QrCodeDecodeException(message: String) : Exception(message) { + class Crypto(message: String, val reason: Reason) : QrCodeDecodeException(message) { + enum class Reason { + NOT_ENOUGH_DATA, + NOT_UTF8, + URL_PARSE, + INVALID_MODE, + INVALID_VERSION, + BASE64, + INVALID_PREFIX; + } + } +} diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/qrlogin/QrLoginException.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/qrlogin/QrLoginException.kt new file mode 100644 index 00000000000..2ee650fc4a3 --- /dev/null +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/qrlogin/QrLoginException.kt @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.matrix.api.auth.qrlogin + +sealed class QrLoginException : Exception() { + data object Cancelled : QrLoginException() + data object ConnectionInsecure : QrLoginException() + data object Declined : QrLoginException() + data object Expired : QrLoginException() + data object InvalidQrCode : QrLoginException() + data object LinkingNotSupported : QrLoginException() + data object Unknown : QrLoginException() +} diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationService.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationService.kt index 5e4f80d8210..91c34f67b5f 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationService.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationService.kt @@ -29,6 +29,7 @@ import io.element.android.libraries.matrix.api.auth.qrlogin.MatrixQrCodeLoginDat import io.element.android.libraries.matrix.api.auth.qrlogin.QrCodeLoginStep import io.element.android.libraries.matrix.api.core.SessionId import io.element.android.libraries.matrix.impl.RustMatrixClientFactory +import io.element.android.libraries.matrix.impl.auth.qrlogin.QrErrorMapper import io.element.android.libraries.matrix.impl.auth.qrlogin.SdkQrCodeLoginData import io.element.android.libraries.matrix.impl.auth.qrlogin.toStep import io.element.android.libraries.matrix.impl.certificates.UserCertificatesProvider @@ -46,6 +47,8 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.withContext import org.matrix.rustcomponents.sdk.OidcAuthenticationData +import org.matrix.rustcomponents.sdk.QrCodeDecodeException +import org.matrix.rustcomponents.sdk.QrLoginException import org.matrix.rustcomponents.sdk.QrLoginProgress import org.matrix.rustcomponents.sdk.QrLoginProgressListener import org.matrix.rustcomponents.sdk.use @@ -234,6 +237,12 @@ class RustMatrixAuthenticationService @Inject constructor( if (throwable is CancellationException) { throw throwable } + }.mapFailure { + when (it) { + is QrCodeDecodeException -> QrErrorMapper.map(it) + is QrLoginException -> QrErrorMapper.map(it) + else -> it + } } } } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/qrlogin/QrErrorMapper.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/qrlogin/QrErrorMapper.kt new file mode 100644 index 00000000000..92e136b1ee7 --- /dev/null +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/qrlogin/QrErrorMapper.kt @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.matrix.impl.auth.qrlogin + +import io.element.android.libraries.matrix.api.auth.qrlogin.QrCodeDecodeException +import io.element.android.libraries.matrix.api.auth.qrlogin.QrLoginException +import org.matrix.rustcomponents.sdk.HumanQrLoginError +import org.matrix.rustcomponents.sdk.qrLoginErrorToHumanError +import uniffi.matrix_sdk_crypto.LoginQrCodeDecodeError +import org.matrix.rustcomponents.sdk.QrCodeDecodeException as RustQrCodeDecodeException +import org.matrix.rustcomponents.sdk.QrLoginException as RustQrLoginException + +object QrErrorMapper { + fun map(loginException: RustQrLoginException) : QrLoginException = map(qrLoginErrorToHumanError(loginException)) + + fun map(qrCodeDecodeException: RustQrCodeDecodeException) : QrCodeDecodeException = when (qrCodeDecodeException) { + is RustQrCodeDecodeException.Crypto -> { + val reason = when (qrCodeDecodeException.error) { + LoginQrCodeDecodeError.NOT_ENOUGH_DATA -> QrCodeDecodeException.Crypto.Reason.NOT_ENOUGH_DATA + LoginQrCodeDecodeError.NOT_UTF8 -> QrCodeDecodeException.Crypto.Reason.NOT_UTF8 + LoginQrCodeDecodeError.URL_PARSE -> QrCodeDecodeException.Crypto.Reason.URL_PARSE + LoginQrCodeDecodeError.INVALID_MODE -> QrCodeDecodeException.Crypto.Reason.INVALID_MODE + LoginQrCodeDecodeError.INVALID_VERSION -> QrCodeDecodeException.Crypto.Reason.INVALID_VERSION + LoginQrCodeDecodeError.BASE64 -> QrCodeDecodeException.Crypto.Reason.BASE64 + LoginQrCodeDecodeError.INVALID_PREFIX -> QrCodeDecodeException.Crypto.Reason.INVALID_PREFIX + } + QrCodeDecodeException.Crypto(qrCodeDecodeException.message, reason) + } + } + + private fun map(humanQrLoginError: HumanQrLoginError): QrLoginException = when (humanQrLoginError) { + is HumanQrLoginError.Cancelled -> QrLoginException.Cancelled + is HumanQrLoginError.ConnectionInsecure -> QrLoginException.ConnectionInsecure + is HumanQrLoginError.Declined -> QrLoginException.Declined + is HumanQrLoginError.Expired -> QrLoginException.Expired + is HumanQrLoginError.InvalidQrCode -> QrLoginException.InvalidQrCode + is HumanQrLoginError.LinkingNotSupported -> QrLoginException.LinkingNotSupported + is HumanQrLoginError.Unknown -> QrLoginException.Unknown + } +} From c70a8909dffe765b104964302b978b1e7ddc0499 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= Date: Fri, 17 May 2024 17:15:41 +0200 Subject: [PATCH 39/60] WIP: mapping errors to error screens --- .../login/impl/qrcode/QrCodeLoginFlowNode.kt | 49 +++++++++++++++++-- .../qrcode/scan/QrCodeScanPresenter.kt | 4 +- 2 files changed, 49 insertions(+), 4 deletions(-) diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/qrcode/QrCodeLoginFlowNode.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/qrcode/QrCodeLoginFlowNode.kt index 95e74ce1215..8558fb9c9cb 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/qrcode/QrCodeLoginFlowNode.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/qrcode/QrCodeLoginFlowNode.kt @@ -44,6 +44,7 @@ import io.element.android.libraries.architecture.createNode import io.element.android.libraries.di.AppScope import io.element.android.libraries.matrix.api.auth.qrlogin.MatrixQrCodeLoginData import io.element.android.libraries.matrix.api.auth.qrlogin.QrCodeLoginStep +import io.element.android.libraries.matrix.api.auth.qrlogin.QrLoginException import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job @@ -78,8 +79,7 @@ class QrCodeLoginFlowNode @AssistedInject constructor( data class QrCodeConfirmation(val step: QrCodeConfirmationStep) : NavTarget @Parcelize - // TODO specify the error type - data class Error(val message: String) : NavTarget + data class Error(val errorType: QrCodeErrorScreenType) : NavTarget } override fun onBuilt() { @@ -169,7 +169,30 @@ class QrCodeLoginFlowNode @AssistedInject constructor( if (throwable is CancellationException) { throw throwable } - backstack.push(NavTarget.Error(throwable.message ?: "Unknown error")) + when (throwable) { + is QrLoginException.InvalidQrCode -> { + // Inline error code + } + is QrLoginException.Cancelled -> { + backstack.replace(NavTarget.Error(QrCodeErrorScreenType.Cancelled)) + } + is QrLoginException.Expired -> { + backstack.replace(NavTarget.Error(QrCodeErrorScreenType.Expired)) + } + is QrLoginException.Declined -> { + backstack.replace(NavTarget.Error(QrCodeErrorScreenType.Declined)) + } + is QrLoginException.ConnectionInsecure -> { + backstack.replace(NavTarget.Error(QrCodeErrorScreenType.InsecureChannelDetected)) + } + is QrLoginException.LinkingNotSupported -> { + backstack.replace(NavTarget.Error(QrCodeErrorScreenType.ProtocolNotSupported)) + } + else -> { + Timber.e(throwable, "Unknown error found") + backstack.replace(NavTarget.Error(QrCodeErrorScreenType.UnknownError)) + } + } } } } @@ -179,3 +202,23 @@ class QrCodeLoginFlowNode @AssistedInject constructor( BackstackView() } } + +sealed interface QrCodeErrorScreenType : Parcelable { + @Parcelize + data object Cancelled : QrCodeErrorScreenType + + @Parcelize + data object Expired : QrCodeErrorScreenType + + @Parcelize + data object InsecureChannelDetected : QrCodeErrorScreenType + + @Parcelize + data object Declined : QrCodeErrorScreenType + + @Parcelize + data object ProtocolNotSupported : QrCodeErrorScreenType + + @Parcelize + data object UnknownError : QrCodeErrorScreenType +} 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 bc8a6c52b67..8b142715458 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 @@ -72,7 +72,9 @@ class QrCodeScanPresenter @Inject constructor( launch(coroutineDispatchers.computation) { suspend { - qrCodeLoginDataFactory.parseQrCodeData(code).getOrThrow() + qrCodeLoginDataFactory.parseQrCodeData(code).onFailure { + println("Error parsing QR code data: $it") + }.getOrThrow() }.runCatchingUpdatingState(codeScannedAction) }.invokeOnCompletion { isProcessingCode.set(false) From fdbcd0406308faf38a2389abc41f3be58612521d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= Date: Mon, 20 May 2024 16:37:21 +0200 Subject: [PATCH 40/60] Add error handling (to be tested) --- .../impl/src/main/res/values/localazy.xml | 10 ++ .../login/impl/qrcode/QrCodeLoginFlowNode.kt | 9 +- .../login/impl/qrcode/QrCodeLoginManager.kt | 12 ++ .../screens/qrcode/error/QrCodeErrorNode.kt | 10 +- .../screens/qrcode/error/QrCodeErrorView.kt | 112 ++++++++++++++---- .../qrcode/scan/QrCodeScanPresenter.kt | 3 +- .../impl/src/main/res/values/localazy.xml | 13 +- .../impl/src/main/res/values/localazy.xml | 2 + .../impl/src/main/res/values/localazy.xml | 1 - .../src/main/res/values/localazy.xml | 1 + 10 files changed, 140 insertions(+), 33 deletions(-) diff --git a/features/ftue/impl/src/main/res/values/localazy.xml b/features/ftue/impl/src/main/res/values/localazy.xml index 256dcbae315..fc751c123e1 100644 --- a/features/ftue/impl/src/main/res/values/localazy.xml +++ b/features/ftue/impl/src/main/res/values/localazy.xml @@ -11,6 +11,16 @@ "Connection not secure" "You’ll be asked to enter the two digits shown on this device." "Enter the number below on your other device" + "The sign in was cancelled on the other device." + "Sign in request cancelled" + "The request on your other device was not accepted." + "Sign in declined" + "Sign in expired. Please try again." + "The sign in was not completed in time" + "Your other device does not support signing in to Element with a QR code. + +Try signing in manually, or scan the QR code with another device." + "QR code not supported" "Ready to scan" "Open %1$s on a desktop device" "Click on your avatar" diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/qrcode/QrCodeLoginFlowNode.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/qrcode/QrCodeLoginFlowNode.kt index 8558fb9c9cb..68a24cd16a7 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/qrcode/QrCodeLoginFlowNode.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/qrcode/QrCodeLoginFlowNode.kt @@ -40,6 +40,7 @@ import io.element.android.features.login.impl.screens.qrcode.intro.QrCodeIntroNo import io.element.android.features.login.impl.screens.qrcode.scan.QrCodeScanNode import io.element.android.libraries.architecture.BackstackView import io.element.android.libraries.architecture.BaseFlowNode +import io.element.android.libraries.architecture.NodeInputs import io.element.android.libraries.architecture.createNode import io.element.android.libraries.di.AppScope import io.element.android.libraries.matrix.api.auth.qrlogin.MatrixQrCodeLoginData @@ -150,8 +151,7 @@ class QrCodeLoginFlowNode @AssistedInject constructor( backstack.newRoot(NavTarget.Initial) } } - // TODO specify the error type - createNode(buildContext, plugins = listOf(callback)) + createNode(buildContext, plugins = listOf(navTarget.errorType, callback)) } } } @@ -171,7 +171,8 @@ class QrCodeLoginFlowNode @AssistedInject constructor( } when (throwable) { is QrLoginException.InvalidQrCode -> { - // Inline error code + // This is handled in the scan QR screen, we should never receive it here + Timber.e(throwable, "Unexpected invalid QR code error found after decoding") } is QrLoginException.Cancelled -> { backstack.replace(NavTarget.Error(QrCodeErrorScreenType.Cancelled)) @@ -203,7 +204,7 @@ class QrCodeLoginFlowNode @AssistedInject constructor( } } -sealed interface QrCodeErrorScreenType : Parcelable { +sealed interface QrCodeErrorScreenType : NodeInputs, Parcelable { @Parcelize data object Cancelled : QrCodeErrorScreenType diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/qrcode/QrCodeLoginManager.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/qrcode/QrCodeLoginManager.kt index e214f5e0c90..1c4249b9255 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/qrcode/QrCodeLoginManager.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/qrcode/QrCodeLoginManager.kt @@ -21,7 +21,19 @@ import io.element.android.libraries.matrix.api.auth.qrlogin.QrCodeLoginStep import io.element.android.libraries.matrix.api.core.SessionId import kotlinx.coroutines.flow.StateFlow +/** + * Helper to handle the QR code login flow after the QR code data has been provided. + */ interface QrCodeLoginManager { + /** + * The current QR code login step. + */ val currentLoginStep: StateFlow + + /** + * Authenticate using the provided [qrCodeLoginData]. + * @param qrCodeLoginData the QR code login data from the scanned QR code. + * @return the logged in [SessionId] if the authentication was successful or a failure result. + */ suspend fun authenticate(qrCodeLoginData: MatrixQrCodeLoginData): Result } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/error/QrCodeErrorNode.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/error/QrCodeErrorNode.kt index cb04a732c18..34e3a3935b0 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/error/QrCodeErrorNode.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/error/QrCodeErrorNode.kt @@ -25,6 +25,8 @@ import com.bumble.appyx.core.plugin.plugins import dagger.assisted.Assisted import dagger.assisted.AssistedInject import io.element.android.anvilannotations.ContributesNode +import io.element.android.features.login.impl.qrcode.QrCodeErrorScreenType +import io.element.android.libraries.architecture.inputs import io.element.android.libraries.di.AppScope @ContributesNode(AppScope::class) @@ -40,8 +42,14 @@ class QrCodeErrorNode @AssistedInject constructor( plugins().forEach { it.onRetry() } } + private val qrCodeErrorScreenType = inputs() + @Composable override fun View(modifier: Modifier) { - QrCodeErrorView(onRetry = ::onRetry) + QrCodeErrorView( + modifier = modifier, + errorScreenType = qrCodeErrorScreenType, + onRetry = ::onRetry + ) } } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/error/QrCodeErrorView.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/error/QrCodeErrorView.kt index d6cf2bd22df..66117154462 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/error/QrCodeErrorView.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/error/QrCodeErrorView.kt @@ -19,17 +19,24 @@ package io.element.android.features.login.impl.screens.qrcode.error import androidx.activity.compose.BackHandler import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ColumnScope +import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.tooling.preview.PreviewParameterProvider import androidx.compose.ui.unit.dp import io.element.android.compound.theme.ElementTheme import io.element.android.features.login.impl.R +import io.element.android.features.login.impl.qrcode.QrCodeErrorScreenType import io.element.android.libraries.designsystem.atomic.organisms.NumberedListOrganism import io.element.android.libraries.designsystem.atomic.pages.FlowStepPage import io.element.android.libraries.designsystem.components.BigIcon @@ -37,11 +44,13 @@ import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.theme.components.Button import io.element.android.libraries.designsystem.theme.components.Text +import io.element.android.libraries.ui.strings.CommonStrings import kotlinx.collections.immutable.persistentListOf @Composable fun QrCodeErrorView( modifier: Modifier = Modifier, + errorScreenType: QrCodeErrorScreenType, onRetry: () -> Unit, ) { BackHandler { @@ -50,36 +59,77 @@ fun QrCodeErrorView( FlowStepPage( modifier = modifier, iconStyle = BigIcon.Style.AlertSolid, - title = stringResource(R.string.screen_qr_code_login_connection_note_secure_state_title), - subTitle = stringResource(R.string.screen_qr_code_login_connection_note_secure_state_description), - content = { Content() }, + title = titleText(errorScreenType), + subTitle = subtitleText(errorScreenType), + content = { Content(errorScreenType) }, buttons = { Buttons(onRetry) } ) } @Composable -private fun Content() { - Column( - modifier = Modifier - .fillMaxWidth() - .padding(top = 20.dp), - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.spacedBy(24.dp) - ) { - Text( - modifier = Modifier.fillMaxWidth(), - text = stringResource(R.string.screen_qr_code_login_connection_note_secure_state_list_header), - style = ElementTheme.typography.fontBodyLgMedium, - textAlign = TextAlign.Center, - ) - // TODO make `NumberedListOrganism` not rely on LazyColumn, restore vertical scroll to parent column - NumberedListOrganism( - items = persistentListOf( - AnnotatedString(stringResource(R.string.screen_qr_code_login_connection_note_secure_state_list_item_1)), - AnnotatedString(stringResource(R.string.screen_qr_code_login_connection_note_secure_state_list_item_2)), - AnnotatedString(stringResource(R.string.screen_qr_code_login_connection_note_secure_state_list_item_3)), - ) +private fun titleText(errorScreenType: QrCodeErrorScreenType) = when (errorScreenType) { + QrCodeErrorScreenType.Cancelled -> stringResource(R.string.screen_qr_code_login_error_cancelled_title) + QrCodeErrorScreenType.Declined -> stringResource(R.string.screen_qr_code_login_error_declined_title) + QrCodeErrorScreenType.Expired -> stringResource(R.string.screen_qr_code_login_error_expired_title) + QrCodeErrorScreenType.ProtocolNotSupported -> stringResource(R.string.screen_qr_code_login_error_linking_not_suported_title) + QrCodeErrorScreenType.InsecureChannelDetected -> stringResource(id = R.string.screen_qr_code_login_connection_note_secure_state_title) + is QrCodeErrorScreenType.UnknownError -> stringResource(CommonStrings.common_something_went_wrong) +} + +@Composable +private fun subtitleText(errorScreenType: QrCodeErrorScreenType) = when (errorScreenType) { + QrCodeErrorScreenType.Cancelled -> stringResource(R.string.screen_qr_code_login_error_cancelled_subtitle) + QrCodeErrorScreenType.Declined -> stringResource(R.string.screen_qr_code_login_error_declined_subtitle) + QrCodeErrorScreenType.Expired -> stringResource(R.string.screen_qr_code_login_error_expired_subtitle) + QrCodeErrorScreenType.ProtocolNotSupported -> stringResource(R.string.screen_qr_code_login_error_linking_not_suported_subtitle) + QrCodeErrorScreenType.InsecureChannelDetected -> stringResource(id = R.string.screen_qr_code_login_connection_note_secure_state_description) + is QrCodeErrorScreenType.UnknownError -> stringResource(R.string.screen_qr_code_login_unknown_error_description) +} + +@Composable +private fun ColumnScope.InsecureChannelDetectedError() { + Text( + modifier = Modifier.fillMaxWidth().verticalScroll(rememberScrollState()), + text = stringResource(R.string.screen_qr_code_login_connection_note_secure_state_list_header), + style = ElementTheme.typography.fontBodyLgMedium, + textAlign = TextAlign.Center, + ) + NumberedListOrganism( + modifier = Modifier.fillMaxSize(), + items = persistentListOf( + AnnotatedString(stringResource(R.string.screen_qr_code_login_connection_note_secure_state_list_item_1)), + AnnotatedString(stringResource(R.string.screen_qr_code_login_connection_note_secure_state_list_item_2)), + AnnotatedString(stringResource(R.string.screen_qr_code_login_connection_note_secure_state_list_item_3)), ) + ) +} + +@Composable +private fun ErrorMessageParagraph(text: String) { + Text( + modifier = Modifier.fillMaxWidth(), + text = text, + style = ElementTheme.typography.fontBodyMdRegular, + color = ElementTheme.colors.textSecondary, + textAlign = TextAlign.Center, + ) +} + +@Composable +private fun Content(errorScreenType: QrCodeErrorScreenType) { + when (errorScreenType) { + QrCodeErrorScreenType.InsecureChannelDetected -> { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(top = 20.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(24.dp) + ) { + InsecureChannelDetectedError() + } + } + else -> Unit } } @@ -94,10 +144,22 @@ private fun Buttons(onRetry: () -> Unit) { @PreviewsDayNight @Composable -internal fun QrCodeErrorViewPreview() { +internal fun QrCodeErrorViewPreview(@PreviewParameter(QrCodeErrorTypeProvider::class) errorScreenType: QrCodeErrorScreenType) { ElementPreview { QrCodeErrorView( + errorScreenType = errorScreenType, onRetry = {} ) } } + +class QrCodeErrorTypeProvider : PreviewParameterProvider { + override val values: Sequence = sequenceOf( + QrCodeErrorScreenType.Cancelled, + QrCodeErrorScreenType.Declined, + QrCodeErrorScreenType.Expired, + QrCodeErrorScreenType.ProtocolNotSupported, + QrCodeErrorScreenType.InsecureChannelDetected, + QrCodeErrorScreenType.UnknownError + ) +} 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 8b142715458..715d96d0280 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 @@ -31,6 +31,7 @@ import io.element.android.libraries.matrix.api.auth.qrlogin.MatrixQrCodeLoginDat import io.element.android.libraries.matrix.api.auth.qrlogin.MatrixQrCodeLoginDataFactory import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch +import timber.log.Timber import java.util.concurrent.atomic.AtomicBoolean import javax.inject.Inject @@ -73,7 +74,7 @@ class QrCodeScanPresenter @Inject constructor( launch(coroutineDispatchers.computation) { suspend { qrCodeLoginDataFactory.parseQrCodeData(code).onFailure { - println("Error parsing QR code data: $it") + Timber.e(it, "Error parsing QR code data") }.getOrThrow() }.runCatchingUpdatingState(codeScannedAction) }.invokeOnCompletion { diff --git a/features/login/impl/src/main/res/values/localazy.xml b/features/login/impl/src/main/res/values/localazy.xml index af1234d4125..7d0e0eeda56 100644 --- a/features/login/impl/src/main/res/values/localazy.xml +++ b/features/login/impl/src/main/res/values/localazy.xml @@ -39,11 +39,22 @@ "Connection not secure" "You’ll be asked to enter the two digits shown on this device." "Enter the number below on your other device" + "The sign in was cancelled on the other device." + "Sign in request cancelled" + "The request on your other device was not accepted." + "Sign in declined" + "Sign in expired. Please try again." + "The sign in was not completed in time" + "Your other device does not support signing in to Element with a QR code. + +Try signing in manually, or scan the QR code with another device." + "QR code not supported" + "Ready to scan" "Open %1$s on a desktop device" "Click on your avatar" "Select %1$s" "“Link new device”" - "Follow the instructions shown" + "Scan the QR code with this device" "Open %1$s on another device to get the QR code" "Use the QR code shown on the other device." "Try again" diff --git a/features/preferences/impl/src/main/res/values/localazy.xml b/features/preferences/impl/src/main/res/values/localazy.xml index 56a5c0ba03c..d9b6b583edf 100644 --- a/features/preferences/impl/src/main/res/values/localazy.xml +++ b/features/preferences/impl/src/main/res/values/localazy.xml @@ -1,10 +1,12 @@ + "Choose how to receive notifications" "Developer mode" "Enable to have access to features and functionality for developers." "Custom Element Call base URL" "Set a custom base URL for Element Call." "Invalid URL, please make sure you include the protocol (http/https) and the correct address." + "Push notification provider" "Disable the rich text editor to type Markdown manually." "Read receipts" "If turned off, your read receipts won\'t be sent to anyone. You will still receive read receipts from other users." diff --git a/libraries/push/impl/src/main/res/values/localazy.xml b/libraries/push/impl/src/main/res/values/localazy.xml index c9d7627d07f..1064d5c31ea 100644 --- a/libraries/push/impl/src/main/res/values/localazy.xml +++ b/libraries/push/impl/src/main/res/values/localazy.xml @@ -46,7 +46,6 @@ "%d room" "%d rooms" - "Choose how to receive notifications" "Background synchronization" "Google Services" "No valid Google Play Services found. Notifications may not work properly." diff --git a/libraries/ui-strings/src/main/res/values/localazy.xml b/libraries/ui-strings/src/main/res/values/localazy.xml index e667873318a..8d46b6e5b16 100644 --- a/libraries/ui-strings/src/main/res/values/localazy.xml +++ b/libraries/ui-strings/src/main/res/values/localazy.xml @@ -119,6 +119,7 @@ "Blocked users" "Bubbles" "Call in progress (unsupported)" + "Call started" "Chat backup" "Copyright" "Creating room…" From ac1e464001af64fdce56a880504a45e1a6ae9516 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= Date: Tue, 21 May 2024 12:40:42 +0200 Subject: [PATCH 41/60] Use Zxing-CPP instead of outdated Zxing `v3.3.3` --- gradle/libs.versions.toml | 3 +- .../auth/RustMatrixAuthenticationService.kt | 9 +++-- libraries/qrcode/build.gradle.kts | 2 +- .../libraries/qrcode/QRCodeAnalyzer.kt | 38 ++++--------------- 4 files changed, 14 insertions(+), 38 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index ff0ce9ce930..7df96a3ca1e 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -186,8 +186,7 @@ maplibre_ktx = "org.maplibre.gl:android-sdk-ktx-v7:2.0.2" maplibre_annotation = "org.maplibre.gl:android-plugin-annotation-v9:2.0.2" opusencoder = "io.element.android:opusencoder:1.1.0" kotlinpoet = "com.squareup:kotlinpoet:1.16.0" -# Stick to 3.3.3 because of https://github.com/zxing/zxing/issues/1170 -zxing = "com.google.zxing:core:3.3.3" +zxing_cpp = "io.github.zxing-cpp:android:2.2.0" # Analytics posthog = "com.posthog:posthog-android:3.2.1" diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationService.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationService.kt index 91c34f67b5f..a9f3d8ec4e1 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationService.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationService.kt @@ -233,16 +233,17 @@ class RustMatrixAuthenticationService @Inject constructor( sessionStore.storeData(sessionData) SessionId(sessionData.userId) } - }.onFailure { throwable -> - if (throwable is CancellationException) { - throw throwable - } }.mapFailure { when (it) { is QrCodeDecodeException -> QrErrorMapper.map(it) is QrLoginException -> QrErrorMapper.map(it) else -> it } + }.onFailure { throwable -> + if (throwable is CancellationException) { + throw throwable + } + Timber.e(throwable, "Failed to login with QR code") } } } diff --git a/libraries/qrcode/build.gradle.kts b/libraries/qrcode/build.gradle.kts index 44184f42317..65aa597bf16 100644 --- a/libraries/qrcode/build.gradle.kts +++ b/libraries/qrcode/build.gradle.kts @@ -26,5 +26,5 @@ dependencies { implementation(libs.androidx.camera.lifecycle) implementation(libs.androidx.camera.view) implementation(libs.androidx.camera.camera2) - implementation(libs.zxing) + implementation(libs.zxing.cpp) } diff --git a/libraries/qrcode/src/main/kotlin/io/element/android/libraries/qrcode/QRCodeAnalyzer.kt b/libraries/qrcode/src/main/kotlin/io/element/android/libraries/qrcode/QRCodeAnalyzer.kt index 62e4c80e27b..dab8745b47a 100644 --- a/libraries/qrcode/src/main/kotlin/io/element/android/libraries/qrcode/QRCodeAnalyzer.kt +++ b/libraries/qrcode/src/main/kotlin/io/element/android/libraries/qrcode/QRCodeAnalyzer.kt @@ -19,41 +19,22 @@ package io.element.android.libraries.qrcode import android.graphics.ImageFormat import androidx.camera.core.ImageAnalysis import androidx.camera.core.ImageProxy -import com.google.zxing.BinaryBitmap -import com.google.zxing.NotFoundException -import com.google.zxing.PlanarYUVLuminanceSource -import com.google.zxing.ResultMetadataType -import com.google.zxing.common.HybridBinarizer -import com.google.zxing.qrcode.QRCodeReader import timber.log.Timber -import java.nio.ByteBuffer +import zxingcpp.BarcodeReader internal class QRCodeAnalyzer( private val onQrCodeScanned: (result: ByteArray?) -> Unit ) : ImageAnalysis.Analyzer { - private val reader = QRCodeReader() + private val reader = BarcodeReader() override fun analyze(image: ImageProxy) { if (image.format in SUPPORTED_IMAGE_FORMATS) { - val bytes = image.planes.first().buffer.toByteArray() - val source = PlanarYUVLuminanceSource( - bytes, - image.width, - image.height, - 0, - 0, - image.width, - image.height, - false - ) - val binaryBitmap = BinaryBitmap(HybridBinarizer(source)) + try { - val result = reader.decode(binaryBitmap) - val byteSegments = result.resultMetadata[ResultMetadataType.BYTE_SEGMENTS] as? List<*> - val contents = byteSegments?.first() as? ByteArray ?: error("No byte segments found") - onQrCodeScanned(contents) - } catch (_: NotFoundException) { - // No QR code found in the image + val bytes = reader.read(image).firstNotNullOfOrNull { it.bytes } + if (bytes != null ) { + onQrCodeScanned(bytes) + } } catch (e: Exception) { Timber.w(e, "Error decoding QR code") } finally { @@ -62,11 +43,6 @@ internal class QRCodeAnalyzer( } } - private fun ByteBuffer.toByteArray(): ByteArray { - rewind() - return ByteArray(remaining()).also { get(it) } - } - companion object { private val SUPPORTED_IMAGE_FORMATS = listOf( ImageFormat.YUV_420_888, From 5d474e2f13e8f4c27d01a048f185153a7497262f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= Date: Fri, 24 May 2024 09:19:02 +0200 Subject: [PATCH 42/60] Add new error codes and screens --- .../impl/src/main/res/values/localazy.xml | 4 ++- .../login/impl/qrcode/QrCodeLoginFlowNode.kt | 3 +++ .../screens/qrcode/error/QrCodeErrorNode.kt | 5 +++- .../screens/qrcode/error/QrCodeErrorView.kt | 19 +++++++++----- .../impl/src/main/res/values/localazy.xml | 4 ++- .../api/auth/qrlogin/QrLoginException.kt | 2 ++ .../auth/RustMatrixAuthenticationService.kt | 4 +-- .../matrix/impl/auth/qrlogin/QrErrorMapper.kt | 25 +++++++++---------- 8 files changed, 42 insertions(+), 24 deletions(-) diff --git a/features/ftue/impl/src/main/res/values/localazy.xml b/features/ftue/impl/src/main/res/values/localazy.xml index fc751c123e1..4038f21908d 100644 --- a/features/ftue/impl/src/main/res/values/localazy.xml +++ b/features/ftue/impl/src/main/res/values/localazy.xml @@ -17,10 +17,12 @@ "Sign in declined" "Sign in expired. Please try again." "The sign in was not completed in time" - "Your other device does not support signing in to Element with a QR code. + "Your other device does not support signing in to %s with a QR code. Try signing in manually, or scan the QR code with another device." "QR code not supported" + "Your account provider does not support %1$s." + "%1$s not supported" "Ready to scan" "Open %1$s on a desktop device" "Click on your avatar" diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/qrcode/QrCodeLoginFlowNode.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/qrcode/QrCodeLoginFlowNode.kt index 68a24cd16a7..dd5d0dd3a76 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/qrcode/QrCodeLoginFlowNode.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/qrcode/QrCodeLoginFlowNode.kt @@ -220,6 +220,9 @@ sealed interface QrCodeErrorScreenType : NodeInputs, Parcelable { @Parcelize data object ProtocolNotSupported : QrCodeErrorScreenType + @Parcelize + data object SlidingSyncNotAvailable : QrCodeErrorScreenType + @Parcelize data object UnknownError : QrCodeErrorScreenType } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/error/QrCodeErrorNode.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/error/QrCodeErrorNode.kt index 34e3a3935b0..c9f56155b6d 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/error/QrCodeErrorNode.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/error/QrCodeErrorNode.kt @@ -27,12 +27,14 @@ import dagger.assisted.AssistedInject import io.element.android.anvilannotations.ContributesNode import io.element.android.features.login.impl.qrcode.QrCodeErrorScreenType import io.element.android.libraries.architecture.inputs +import io.element.android.libraries.core.meta.BuildMeta import io.element.android.libraries.di.AppScope @ContributesNode(AppScope::class) class QrCodeErrorNode @AssistedInject constructor( @Assisted buildContext: BuildContext, @Assisted plugins: List, + private val buildMeta: BuildMeta, ) : Node(buildContext = buildContext, plugins = plugins) { interface Callback : Plugin { fun onRetry() @@ -49,7 +51,8 @@ class QrCodeErrorNode @AssistedInject constructor( QrCodeErrorView( modifier = modifier, errorScreenType = qrCodeErrorScreenType, - onRetry = ::onRetry + appName = buildMeta.productionApplicationName, + onRetry = ::onRetry, ) } } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/error/QrCodeErrorView.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/error/QrCodeErrorView.kt index 66117154462..cb82f755df9 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/error/QrCodeErrorView.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/error/QrCodeErrorView.kt @@ -51,6 +51,7 @@ import kotlinx.collections.immutable.persistentListOf fun QrCodeErrorView( modifier: Modifier = Modifier, errorScreenType: QrCodeErrorScreenType, + appName: String, onRetry: () -> Unit, ) { BackHandler { @@ -59,37 +60,41 @@ fun QrCodeErrorView( FlowStepPage( modifier = modifier, iconStyle = BigIcon.Style.AlertSolid, - title = titleText(errorScreenType), - subTitle = subtitleText(errorScreenType), + title = titleText(errorScreenType, appName), + subTitle = subtitleText(errorScreenType, appName), content = { Content(errorScreenType) }, buttons = { Buttons(onRetry) } ) } @Composable -private fun titleText(errorScreenType: QrCodeErrorScreenType) = when (errorScreenType) { +private fun titleText(errorScreenType: QrCodeErrorScreenType, appName: String) = when (errorScreenType) { QrCodeErrorScreenType.Cancelled -> stringResource(R.string.screen_qr_code_login_error_cancelled_title) QrCodeErrorScreenType.Declined -> stringResource(R.string.screen_qr_code_login_error_declined_title) QrCodeErrorScreenType.Expired -> stringResource(R.string.screen_qr_code_login_error_expired_title) QrCodeErrorScreenType.ProtocolNotSupported -> stringResource(R.string.screen_qr_code_login_error_linking_not_suported_title) QrCodeErrorScreenType.InsecureChannelDetected -> stringResource(id = R.string.screen_qr_code_login_connection_note_secure_state_title) + QrCodeErrorScreenType.SlidingSyncNotAvailable -> stringResource(id = R.string.screen_qr_code_login_error_sliding_sync_not_supported_title, appName) is QrCodeErrorScreenType.UnknownError -> stringResource(CommonStrings.common_something_went_wrong) } @Composable -private fun subtitleText(errorScreenType: QrCodeErrorScreenType) = when (errorScreenType) { +private fun subtitleText(errorScreenType: QrCodeErrorScreenType, appName: String) = when (errorScreenType) { QrCodeErrorScreenType.Cancelled -> stringResource(R.string.screen_qr_code_login_error_cancelled_subtitle) QrCodeErrorScreenType.Declined -> stringResource(R.string.screen_qr_code_login_error_declined_subtitle) QrCodeErrorScreenType.Expired -> stringResource(R.string.screen_qr_code_login_error_expired_subtitle) - QrCodeErrorScreenType.ProtocolNotSupported -> stringResource(R.string.screen_qr_code_login_error_linking_not_suported_subtitle) + QrCodeErrorScreenType.ProtocolNotSupported -> stringResource(R.string.screen_qr_code_login_error_linking_not_suported_subtitle, appName) QrCodeErrorScreenType.InsecureChannelDetected -> stringResource(id = R.string.screen_qr_code_login_connection_note_secure_state_description) + QrCodeErrorScreenType.SlidingSyncNotAvailable -> stringResource(id = R.string.screen_qr_code_login_error_sliding_sync_not_supported_subtitle, appName) is QrCodeErrorScreenType.UnknownError -> stringResource(R.string.screen_qr_code_login_unknown_error_description) } @Composable private fun ColumnScope.InsecureChannelDetectedError() { Text( - modifier = Modifier.fillMaxWidth().verticalScroll(rememberScrollState()), + modifier = Modifier + .fillMaxWidth() + .verticalScroll(rememberScrollState()), text = stringResource(R.string.screen_qr_code_login_connection_note_secure_state_list_header), style = ElementTheme.typography.fontBodyLgMedium, textAlign = TextAlign.Center, @@ -148,6 +153,7 @@ internal fun QrCodeErrorViewPreview(@PreviewParameter(QrCodeErrorTypeProvider::c ElementPreview { QrCodeErrorView( errorScreenType = errorScreenType, + appName = "Element X", onRetry = {} ) } @@ -160,6 +166,7 @@ class QrCodeErrorTypeProvider : PreviewParameterProvider QrCodeErrorScreenType.Expired, QrCodeErrorScreenType.ProtocolNotSupported, QrCodeErrorScreenType.InsecureChannelDetected, + QrCodeErrorScreenType.SlidingSyncNotAvailable, QrCodeErrorScreenType.UnknownError ) } diff --git a/features/login/impl/src/main/res/values/localazy.xml b/features/login/impl/src/main/res/values/localazy.xml index 7d0e0eeda56..bccc840afdd 100644 --- a/features/login/impl/src/main/res/values/localazy.xml +++ b/features/login/impl/src/main/res/values/localazy.xml @@ -45,10 +45,12 @@ "Sign in declined" "Sign in expired. Please try again." "The sign in was not completed in time" - "Your other device does not support signing in to Element with a QR code. + "Your other device does not support signing in to %s with a QR code. Try signing in manually, or scan the QR code with another device." "QR code not supported" + "Your account provider does not support %1$s." + "%1$s not supported" "Ready to scan" "Open %1$s on a desktop device" "Click on your avatar" diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/qrlogin/QrLoginException.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/qrlogin/QrLoginException.kt index 2ee650fc4a3..034c2c2b6f5 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/qrlogin/QrLoginException.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/qrlogin/QrLoginException.kt @@ -23,5 +23,7 @@ sealed class QrLoginException : Exception() { data object Expired : QrLoginException() data object InvalidQrCode : QrLoginException() data object LinkingNotSupported : QrLoginException() + data object OidcMetadataInvalid : QrLoginException() + data object SlidingSyncNotAvailable : QrLoginException() data object Unknown : QrLoginException() } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationService.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationService.kt index a9f3d8ec4e1..d992c7fc1c1 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationService.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationService.kt @@ -46,9 +46,9 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.withContext +import org.matrix.rustcomponents.sdk.HumanQrLoginException import org.matrix.rustcomponents.sdk.OidcAuthenticationData import org.matrix.rustcomponents.sdk.QrCodeDecodeException -import org.matrix.rustcomponents.sdk.QrLoginException import org.matrix.rustcomponents.sdk.QrLoginProgress import org.matrix.rustcomponents.sdk.QrLoginProgressListener import org.matrix.rustcomponents.sdk.use @@ -236,7 +236,7 @@ class RustMatrixAuthenticationService @Inject constructor( }.mapFailure { when (it) { is QrCodeDecodeException -> QrErrorMapper.map(it) - is QrLoginException -> QrErrorMapper.map(it) + is HumanQrLoginException -> QrErrorMapper.map(it) else -> it } }.onFailure { throwable -> diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/qrlogin/QrErrorMapper.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/qrlogin/QrErrorMapper.kt index 92e136b1ee7..10eb80a4f3d 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/qrlogin/QrErrorMapper.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/qrlogin/QrErrorMapper.kt @@ -18,15 +18,12 @@ package io.element.android.libraries.matrix.impl.auth.qrlogin import io.element.android.libraries.matrix.api.auth.qrlogin.QrCodeDecodeException import io.element.android.libraries.matrix.api.auth.qrlogin.QrLoginException -import org.matrix.rustcomponents.sdk.HumanQrLoginError -import org.matrix.rustcomponents.sdk.qrLoginErrorToHumanError +import org.matrix.rustcomponents.sdk.HumanQrLoginException +import org.matrix.rustcomponents.sdk.HumanQrLoginException as RustHumanQrLoginException import uniffi.matrix_sdk_crypto.LoginQrCodeDecodeError import org.matrix.rustcomponents.sdk.QrCodeDecodeException as RustQrCodeDecodeException -import org.matrix.rustcomponents.sdk.QrLoginException as RustQrLoginException object QrErrorMapper { - fun map(loginException: RustQrLoginException) : QrLoginException = map(qrLoginErrorToHumanError(loginException)) - fun map(qrCodeDecodeException: RustQrCodeDecodeException) : QrCodeDecodeException = when (qrCodeDecodeException) { is RustQrCodeDecodeException.Crypto -> { val reason = when (qrCodeDecodeException.error) { @@ -42,13 +39,15 @@ object QrErrorMapper { } } - private fun map(humanQrLoginError: HumanQrLoginError): QrLoginException = when (humanQrLoginError) { - is HumanQrLoginError.Cancelled -> QrLoginException.Cancelled - is HumanQrLoginError.ConnectionInsecure -> QrLoginException.ConnectionInsecure - is HumanQrLoginError.Declined -> QrLoginException.Declined - is HumanQrLoginError.Expired -> QrLoginException.Expired - is HumanQrLoginError.InvalidQrCode -> QrLoginException.InvalidQrCode - is HumanQrLoginError.LinkingNotSupported -> QrLoginException.LinkingNotSupported - is HumanQrLoginError.Unknown -> QrLoginException.Unknown + fun map(humanQrLoginError: RustHumanQrLoginException): QrLoginException = when (humanQrLoginError) { + is RustHumanQrLoginException.Cancelled -> QrLoginException.Cancelled + is RustHumanQrLoginException.ConnectionInsecure -> QrLoginException.ConnectionInsecure + is RustHumanQrLoginException.Declined -> QrLoginException.Declined + is RustHumanQrLoginException.Expired -> QrLoginException.Expired + is RustHumanQrLoginException.InvalidQrCode -> QrLoginException.InvalidQrCode + is RustHumanQrLoginException.LinkingNotSupported -> QrLoginException.LinkingNotSupported + is RustHumanQrLoginException.Unknown -> QrLoginException.Unknown + is HumanQrLoginException.OidcMetadataInvalid -> QrLoginException.OidcMetadataInvalid + is HumanQrLoginException.SlidingSyncNotAvailable -> QrLoginException.SlidingSyncNotAvailable } } From 13567780b1f3ab56db5ba7cd207a5dc37661ca0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= Date: Fri, 24 May 2024 09:24:05 +0200 Subject: [PATCH 43/60] Create `QrCodeLoginScope` to isolate the feature and make sure `QrCodeLoginManager` now singleton instances are discarded between login attempts --- .../login/impl/di/QrCodeLoginBindings.kt | 25 +++++++ .../login/impl/di/QrCodeLoginComponent.kt | 38 ++++++++++ .../login/impl/di/QrCodeLoginScope.kt | 19 +++++ .../impl/qrcode/DefaultQrCodeLoginManager.kt | 17 ++++- .../login/impl/qrcode/QrCodeLoginFlowNode.kt | 74 +++++++++++-------- .../login/impl/qrcode/QrCodeLoginManager.kt | 2 + .../screens/qrcode/scan/QrCodeScanNode.kt | 4 +- .../qrcode/scan/QrCodeScanPresenter.kt | 23 ++++++ .../api/auth/qrlogin/QrCodeLoginStep.kt | 1 + 9 files changed, 167 insertions(+), 36 deletions(-) create mode 100644 features/login/impl/src/main/kotlin/io/element/android/features/login/impl/di/QrCodeLoginBindings.kt create mode 100644 features/login/impl/src/main/kotlin/io/element/android/features/login/impl/di/QrCodeLoginComponent.kt create mode 100644 features/login/impl/src/main/kotlin/io/element/android/features/login/impl/di/QrCodeLoginScope.kt diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/di/QrCodeLoginBindings.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/di/QrCodeLoginBindings.kt new file mode 100644 index 00000000000..61279ec56ad --- /dev/null +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/di/QrCodeLoginBindings.kt @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.login.impl.di + +import com.squareup.anvil.annotations.ContributesTo +import io.element.android.features.login.impl.qrcode.DefaultQrCodeLoginManager + +@ContributesTo(QrCodeLoginScope::class) +interface QrCodeLoginBindings { + fun qrCodeLoginManager(): DefaultQrCodeLoginManager +} diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/di/QrCodeLoginComponent.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/di/QrCodeLoginComponent.kt new file mode 100644 index 00000000000..f92e192e400 --- /dev/null +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/di/QrCodeLoginComponent.kt @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.login.impl.di + +import com.squareup.anvil.annotations.ContributesTo +import com.squareup.anvil.annotations.MergeSubcomponent +import dagger.Subcomponent +import io.element.android.libraries.architecture.NodeFactoriesBindings +import io.element.android.libraries.di.AppScope +import io.element.android.libraries.di.SingleIn + +@SingleIn(QrCodeLoginScope::class) +@MergeSubcomponent(QrCodeLoginScope::class) +interface QrCodeLoginComponent : NodeFactoriesBindings { + @Subcomponent.Builder + interface Builder { + fun build(): QrCodeLoginComponent + } + + @ContributesTo(AppScope::class) + interface ParentBindings { + fun qrCodeLoginComponentBuilder(): Builder + } +} diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/di/QrCodeLoginScope.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/di/QrCodeLoginScope.kt new file mode 100644 index 00000000000..12d4973c2db --- /dev/null +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/di/QrCodeLoginScope.kt @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.login.impl.di + +abstract class QrCodeLoginScope private constructor() diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/qrcode/DefaultQrCodeLoginManager.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/qrcode/DefaultQrCodeLoginManager.kt index 1f9f4b74170..9ab6494d959 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/qrcode/DefaultQrCodeLoginManager.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/qrcode/DefaultQrCodeLoginManager.kt @@ -17,16 +17,19 @@ package io.element.android.features.login.impl.qrcode import com.squareup.anvil.annotations.ContributesBinding -import io.element.android.libraries.di.AppScope +import io.element.android.features.login.impl.di.QrCodeLoginScope +import io.element.android.libraries.di.SingleIn import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService import io.element.android.libraries.matrix.api.auth.qrlogin.MatrixQrCodeLoginData 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.core.SessionId import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import javax.inject.Inject -@ContributesBinding(AppScope::class) +@SingleIn(QrCodeLoginScope::class) +@ContributesBinding(QrCodeLoginScope::class) class DefaultQrCodeLoginManager @Inject constructor( private val authenticationService: MatrixAuthenticationService, ) : QrCodeLoginManager { @@ -34,10 +37,18 @@ class DefaultQrCodeLoginManager @Inject constructor( override val currentLoginStep: StateFlow = _currentLoginStep override suspend fun authenticate(qrCodeLoginData: MatrixQrCodeLoginData): Result { - _currentLoginStep.value = QrCodeLoginStep.Uninitialized + reset() return authenticationService.loginWithQrCode(qrCodeLoginData) { step -> _currentLoginStep.value = step + }.onFailure { throwable -> + if (throwable is QrLoginException) { + _currentLoginStep.value = QrCodeLoginStep.Failed(throwable) + } } } + + override fun reset() { + _currentLoginStep.value = QrCodeLoginStep.Uninitialized + } } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/qrcode/QrCodeLoginFlowNode.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/qrcode/QrCodeLoginFlowNode.kt index dd5d0dd3a76..f58da8d571b 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/qrcode/QrCodeLoginFlowNode.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/qrcode/QrCodeLoginFlowNode.kt @@ -33,6 +33,8 @@ import dagger.assisted.Assisted import dagger.assisted.AssistedInject import io.element.android.anvilannotations.ContributesNode import io.element.android.features.login.impl.DefaultLoginUserStory +import io.element.android.features.login.impl.di.QrCodeLoginBindings +import io.element.android.features.login.impl.di.QrCodeLoginComponent import io.element.android.features.login.impl.screens.qrcode.confirmation.QrCodeConfirmationNode import io.element.android.features.login.impl.screens.qrcode.confirmation.QrCodeConfirmationStep import io.element.android.features.login.impl.screens.qrcode.error.QrCodeErrorNode @@ -41,12 +43,13 @@ import io.element.android.features.login.impl.screens.qrcode.scan.QrCodeScanNode import io.element.android.libraries.architecture.BackstackView import io.element.android.libraries.architecture.BaseFlowNode import io.element.android.libraries.architecture.NodeInputs +import io.element.android.libraries.architecture.bindings import io.element.android.libraries.architecture.createNode import io.element.android.libraries.di.AppScope +import io.element.android.libraries.di.DaggerComponentOwner import io.element.android.libraries.matrix.api.auth.qrlogin.MatrixQrCodeLoginData import io.element.android.libraries.matrix.api.auth.qrlogin.QrCodeLoginStep import io.element.android.libraries.matrix.api.auth.qrlogin.QrLoginException -import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job import kotlinx.coroutines.launch @@ -57,7 +60,7 @@ import timber.log.Timber class QrCodeLoginFlowNode @AssistedInject constructor( @Assisted buildContext: BuildContext, @Assisted plugins: List, - private val qrCodeLoginManager: QrCodeLoginManager, + qrCodeLoginComponentBuilder: QrCodeLoginComponent.Builder, private val defaultLoginUserStory: DefaultLoginUserStory, ) : BaseFlowNode( backstack = BackStack( @@ -66,9 +69,12 @@ class QrCodeLoginFlowNode @AssistedInject constructor( ), buildContext = buildContext, plugins = plugins, -) { +), DaggerComponentOwner { private var authenticationJob: Job? = null + override val daggerComponent = qrCodeLoginComponentBuilder.build() + private val qrCodeLoginManager by lazy { bindings().qrCodeLoginManager() } + sealed interface NavTarget : Parcelable { @Parcelize data object Initial : NavTarget @@ -101,6 +107,39 @@ class QrCodeLoginFlowNode @AssistedInject constructor( is QrCodeLoginStep.WaitingForToken -> { backstack.replace(NavTarget.QrCodeConfirmation(QrCodeConfirmationStep.DisplayVerificationCode(step.userCode))) } + is QrCodeLoginStep.Failed -> { + when (val error = step.error) { + is QrLoginException.InvalidQrCode -> { + // Do nothing here, it'll be handled in the scan QR screen + } + is QrLoginException.Cancelled -> { + backstack.replace(NavTarget.Error(QrCodeErrorScreenType.Cancelled)) + } + is QrLoginException.Expired -> { + backstack.replace(NavTarget.Error(QrCodeErrorScreenType.Expired)) + } + is QrLoginException.Declined -> { + backstack.replace(NavTarget.Error(QrCodeErrorScreenType.Declined)) + } + is QrLoginException.ConnectionInsecure -> { + backstack.replace(NavTarget.Error(QrCodeErrorScreenType.InsecureChannelDetected)) + } + is QrLoginException.LinkingNotSupported -> { + backstack.replace(NavTarget.Error(QrCodeErrorScreenType.ProtocolNotSupported)) + } + is QrLoginException.SlidingSyncNotAvailable -> { + backstack.replace(NavTarget.Error(QrCodeErrorScreenType.SlidingSyncNotAvailable)) + } + is QrLoginException.OidcMetadataInvalid -> { + Timber.e(error, "OIDC metadata is invalid") + backstack.replace(NavTarget.Error(QrCodeErrorScreenType.UnknownError)) + } + else -> { + Timber.e(error, "Unknown error found") + backstack.replace(NavTarget.Error(QrCodeErrorScreenType.UnknownError)) + } + } + } else -> Unit } } @@ -148,6 +187,7 @@ class QrCodeLoginFlowNode @AssistedInject constructor( override fun onRetry() { authenticationJob?.cancel() authenticationJob = null + qrCodeLoginManager.reset() backstack.newRoot(NavTarget.Initial) } } @@ -166,34 +206,6 @@ class QrCodeLoginFlowNode @AssistedInject constructor( .onFailure { throwable -> Timber.e(throwable, "QR code authentication failed") authenticationJob = null - if (throwable is CancellationException) { - throw throwable - } - when (throwable) { - is QrLoginException.InvalidQrCode -> { - // This is handled in the scan QR screen, we should never receive it here - Timber.e(throwable, "Unexpected invalid QR code error found after decoding") - } - is QrLoginException.Cancelled -> { - backstack.replace(NavTarget.Error(QrCodeErrorScreenType.Cancelled)) - } - is QrLoginException.Expired -> { - backstack.replace(NavTarget.Error(QrCodeErrorScreenType.Expired)) - } - is QrLoginException.Declined -> { - backstack.replace(NavTarget.Error(QrCodeErrorScreenType.Declined)) - } - is QrLoginException.ConnectionInsecure -> { - backstack.replace(NavTarget.Error(QrCodeErrorScreenType.InsecureChannelDetected)) - } - is QrLoginException.LinkingNotSupported -> { - backstack.replace(NavTarget.Error(QrCodeErrorScreenType.ProtocolNotSupported)) - } - else -> { - Timber.e(throwable, "Unknown error found") - backstack.replace(NavTarget.Error(QrCodeErrorScreenType.UnknownError)) - } - } } } } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/qrcode/QrCodeLoginManager.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/qrcode/QrCodeLoginManager.kt index 1c4249b9255..cd0010dc01d 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/qrcode/QrCodeLoginManager.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/qrcode/QrCodeLoginManager.kt @@ -36,4 +36,6 @@ interface QrCodeLoginManager { * @return the logged in [SessionId] if the authentication was successful or a failure result. */ suspend fun authenticate(qrCodeLoginData: MatrixQrCodeLoginData): Result + + fun reset() } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/scan/QrCodeScanNode.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/scan/QrCodeScanNode.kt index 035501275b5..d8f2f04f3e3 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/scan/QrCodeScanNode.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/scan/QrCodeScanNode.kt @@ -25,10 +25,10 @@ import com.bumble.appyx.core.plugin.plugins import dagger.assisted.Assisted import dagger.assisted.AssistedInject import io.element.android.anvilannotations.ContributesNode -import io.element.android.libraries.di.AppScope +import io.element.android.features.login.impl.di.QrCodeLoginScope import io.element.android.libraries.matrix.api.auth.qrlogin.MatrixQrCodeLoginData -@ContributesNode(AppScope::class) +@ContributesNode(QrCodeLoginScope::class) class QrCodeScanNode @AssistedInject constructor( @Assisted buildContext: BuildContext, @Assisted plugins: List, 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 715d96d0280..b3571bf79f9 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 @@ -17,19 +17,26 @@ package io.element.android.features.login.impl.screens.qrcode.scan import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.MutableState +import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue 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.login.impl.qrcode.QrCodeLoginManager import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.runCatchingUpdatingState import io.element.android.libraries.core.coroutine.CoroutineDispatchers 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.QrCodeLoginStep +import io.element.android.libraries.matrix.api.auth.qrlogin.QrLoginException import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import timber.log.Timber import java.util.concurrent.atomic.AtomicBoolean @@ -37,6 +44,7 @@ import javax.inject.Inject class QrCodeScanPresenter @Inject constructor( private val qrCodeLoginDataFactory: MatrixQrCodeLoginDataFactory, + private val qrCodeLoginManager: QrCodeLoginManager, private val coroutineDispatchers: CoroutineDispatchers, ) : Presenter { private var isScanning by mutableStateOf(true) @@ -48,6 +56,8 @@ class QrCodeScanPresenter @Inject constructor( val coroutineScope = rememberCoroutineScope() val authenticationAction: MutableState> = remember { mutableStateOf(AsyncAction.Uninitialized) } + ObserveQRCodeLoginFailures(authenticationAction = authenticationAction) + fun handleEvents(event: QrCodeScanEvents) { when (event) { QrCodeScanEvents.TryAgain -> { @@ -68,6 +78,19 @@ class QrCodeScanPresenter @Inject constructor( ) } + @Composable + private fun ObserveQRCodeLoginFailures(authenticationAction: MutableState>) { + LaunchedEffect(Unit) { + qrCodeLoginManager.currentLoginStep + .onEach { state -> + if (state is QrCodeLoginStep.Failed && state.error is QrLoginException.InvalidQrCode) { + authenticationAction.value = AsyncAction.Failure(state.error) + } + } + .launchIn(this) + } + } + private fun CoroutineScope.getQrCodeData(codeScannedAction: MutableState>, code: ByteArray) { if (codeScannedAction.value.isSuccess() || isProcessingCode.compareAndSet(true, true)) return diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/qrlogin/QrCodeLoginStep.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/qrlogin/QrCodeLoginStep.kt index ab31df2658c..4ecc7a6cd69 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/qrlogin/QrCodeLoginStep.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/qrlogin/QrCodeLoginStep.kt @@ -21,5 +21,6 @@ sealed interface QrCodeLoginStep { data class EstablishingSecureChannel(val checkCode: String) : QrCodeLoginStep data object Starting : QrCodeLoginStep data class WaitingForToken(val userCode: String) : QrCodeLoginStep + data class Failed(val error: QrLoginException) : QrCodeLoginStep data object Finished : QrCodeLoginStep } From 14b21dddabfb46f113cfecb38df59bb45c561ae3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= Date: Fri, 24 May 2024 12:06:21 +0200 Subject: [PATCH 44/60] Fix and improve tests and related code --- .../login/impl/di/QrCodeLoginBindings.kt | 3 +- .../login/impl/qrcode/QrCodeLoginFlowNode.kt | 34 ++-- .../screens/qrcode/error/QrCodeErrorView.kt | 4 +- .../qrcode/scan/QrCodeScanPresenter.kt | 1 + .../login/impl/di/FakeQrCodeLoginComponent.kt | 49 +++++ .../impl/qrcode/FakeQrCodeLoginManager.kt | 41 ++++ .../impl/qrcode/QrCodeLoginFlowNodeTest.kt | 175 ++++++++++++++++-- .../qrcode/error/QrCodeErrorViewTest.kt | 11 +- .../qrcode/scan/QrCodeScanPresenterTest.kt | 32 ++++ gradle/libs.versions.toml | 4 +- .../libraries/qrcode/QRCodeAnalyzer.kt | 2 +- 11 files changed, 316 insertions(+), 40 deletions(-) create mode 100644 features/login/impl/src/test/kotlin/io/element/android/features/login/impl/di/FakeQrCodeLoginComponent.kt create mode 100644 features/login/impl/src/test/kotlin/io/element/android/features/login/impl/qrcode/FakeQrCodeLoginManager.kt diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/di/QrCodeLoginBindings.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/di/QrCodeLoginBindings.kt index 61279ec56ad..75d9aa66398 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/di/QrCodeLoginBindings.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/di/QrCodeLoginBindings.kt @@ -18,8 +18,9 @@ package io.element.android.features.login.impl.di import com.squareup.anvil.annotations.ContributesTo import io.element.android.features.login.impl.qrcode.DefaultQrCodeLoginManager +import io.element.android.features.login.impl.qrcode.QrCodeLoginManager @ContributesTo(QrCodeLoginScope::class) interface QrCodeLoginBindings { - fun qrCodeLoginManager(): DefaultQrCodeLoginManager + fun qrCodeLoginManager(): QrCodeLoginManager } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/qrcode/QrCodeLoginFlowNode.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/qrcode/QrCodeLoginFlowNode.kt index f58da8d571b..6f04bf57a9e 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/qrcode/QrCodeLoginFlowNode.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/qrcode/QrCodeLoginFlowNode.kt @@ -19,6 +19,7 @@ package io.element.android.features.login.impl.qrcode import android.os.Parcelable import androidx.annotation.VisibleForTesting import androidx.compose.runtime.Composable +import androidx.compose.runtime.Immutable import androidx.compose.ui.Modifier import androidx.lifecycle.lifecycleScope import com.bumble.appyx.core.modality.BuildContext @@ -45,6 +46,7 @@ import io.element.android.libraries.architecture.BaseFlowNode import io.element.android.libraries.architecture.NodeInputs import io.element.android.libraries.architecture.bindings import io.element.android.libraries.architecture.createNode +import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.di.AppScope import io.element.android.libraries.di.DaggerComponentOwner import io.element.android.libraries.matrix.api.auth.qrlogin.MatrixQrCodeLoginData @@ -62,6 +64,7 @@ class QrCodeLoginFlowNode @AssistedInject constructor( @Assisted plugins: List, qrCodeLoginComponentBuilder: QrCodeLoginComponent.Builder, private val defaultLoginUserStory: DefaultLoginUserStory, + private val coroutineDispatchers: CoroutineDispatchers, ) : BaseFlowNode( backstack = BackStack( initialElement = NavTarget.Initial, @@ -95,6 +98,10 @@ class QrCodeLoginFlowNode @AssistedInject constructor( observeLoginStep() } + fun isLoginInProgress(): Boolean { + return authenticationJob?.isActive == true + } + @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) internal fun observeLoginStep() { lifecycleScope.launch { @@ -174,30 +181,30 @@ class QrCodeLoginFlowNode @AssistedInject constructor( } is NavTarget.QrCodeConfirmation -> { val callback = object : QrCodeConfirmationNode.Callback { - override fun onCancel() { - authenticationJob?.cancel() - authenticationJob = null - backstack.newRoot(NavTarget.Initial) - } + override fun onCancel() = reset() } createNode(buildContext, plugins = listOf(navTarget.step, callback)) } is NavTarget.Error -> { val callback = object : QrCodeErrorNode.Callback { - override fun onRetry() { - authenticationJob?.cancel() - authenticationJob = null - qrCodeLoginManager.reset() - backstack.newRoot(NavTarget.Initial) - } + override fun onRetry() = reset() } createNode(buildContext, plugins = listOf(navTarget.errorType, callback)) } } } - private fun CoroutineScope.startAuthentication(qrCodeLoginData: MatrixQrCodeLoginData) { - authenticationJob = launch { + @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) + internal fun reset() { + authenticationJob?.cancel() + authenticationJob = null + qrCodeLoginManager.reset() + backstack.newRoot(NavTarget.Initial) + } + + @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) + internal fun CoroutineScope.startAuthentication(qrCodeLoginData: MatrixQrCodeLoginData) { + authenticationJob = launch(coroutineDispatchers.main) { qrCodeLoginManager.authenticate(qrCodeLoginData) .onSuccess { defaultLoginUserStory.setLoginFlowIsDone(true) @@ -216,6 +223,7 @@ class QrCodeLoginFlowNode @AssistedInject constructor( } } +@Immutable sealed interface QrCodeErrorScreenType : NodeInputs, Parcelable { @Parcelize data object Cancelled : QrCodeErrorScreenType diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/error/QrCodeErrorView.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/error/QrCodeErrorView.kt index cb82f755df9..8dfea8d6827 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/error/QrCodeErrorView.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/error/QrCodeErrorView.kt @@ -149,7 +149,7 @@ private fun Buttons(onRetry: () -> Unit) { @PreviewsDayNight @Composable -internal fun QrCodeErrorViewPreview(@PreviewParameter(QrCodeErrorTypeProvider::class) errorScreenType: QrCodeErrorScreenType) { +internal fun QrCodeErrorViewPreview(@PreviewParameter(QrCodeErrorScreenTypeProvider::class) errorScreenType: QrCodeErrorScreenType) { ElementPreview { QrCodeErrorView( errorScreenType = errorScreenType, @@ -159,7 +159,7 @@ internal fun QrCodeErrorViewPreview(@PreviewParameter(QrCodeErrorTypeProvider::c } } -class QrCodeErrorTypeProvider : PreviewParameterProvider { +class QrCodeErrorScreenTypeProvider : PreviewParameterProvider { override val values: Sequence = sequenceOf( QrCodeErrorScreenType.Cancelled, QrCodeErrorScreenType.Declined, 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 b3571bf79f9..2d0fa74097a 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 @@ -85,6 +85,7 @@ class QrCodeScanPresenter @Inject constructor( .onEach { state -> if (state is QrCodeLoginStep.Failed && state.error is QrLoginException.InvalidQrCode) { authenticationAction.value = AsyncAction.Failure(state.error) + qrCodeLoginManager.reset() } } .launchIn(this) diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/di/FakeQrCodeLoginComponent.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/di/FakeQrCodeLoginComponent.kt new file mode 100644 index 00000000000..39f96107ca9 --- /dev/null +++ b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/di/FakeQrCodeLoginComponent.kt @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.login.impl.di + +import com.bumble.appyx.core.modality.BuildContext +import com.bumble.appyx.core.node.Node +import com.bumble.appyx.core.plugin.Plugin +import io.element.android.features.login.impl.qrcode.FakeQrCodeLoginManager +import io.element.android.features.login.impl.qrcode.QrCodeLoginFlowNode +import io.element.android.features.login.impl.qrcode.QrCodeLoginManager +import io.element.android.libraries.architecture.AssistedNodeFactory +import io.element.android.libraries.architecture.createNode + +internal class FakeQrCodeLoginComponent(private val _qrCodeLoginManager: QrCodeLoginManager) : + QrCodeLoginComponent { + // Ignore this error, it does override a method once code generation is done + override fun qrCodeLoginManager(): QrCodeLoginManager = _qrCodeLoginManager + + class Builder(private val qrCodeLoginManager: QrCodeLoginManager = FakeQrCodeLoginManager()) : + QrCodeLoginComponent.Builder { + override fun build(): QrCodeLoginComponent { + return FakeQrCodeLoginComponent(qrCodeLoginManager) + } + } + + override fun nodeFactories(): Map, AssistedNodeFactory<*>> { + return mapOf( + QrCodeLoginFlowNode::class.java to object : AssistedNodeFactory { + override fun create(buildContext: BuildContext, plugins: List): QrCodeLoginFlowNode { + return createNode(buildContext, plugins) + } + } + ) + } +} diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/qrcode/FakeQrCodeLoginManager.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/qrcode/FakeQrCodeLoginManager.kt new file mode 100644 index 00000000000..cc15a80e46e --- /dev/null +++ b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/qrcode/FakeQrCodeLoginManager.kt @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.login.impl.qrcode + +import io.element.android.libraries.matrix.api.auth.qrlogin.MatrixQrCodeLoginData +import io.element.android.libraries.matrix.api.auth.qrlogin.QrCodeLoginStep +import io.element.android.libraries.matrix.api.core.SessionId +import io.element.android.libraries.matrix.test.A_SESSION_ID +import io.element.android.tests.testutils.lambda.lambdaRecorder +import kotlinx.coroutines.flow.MutableStateFlow + +class FakeQrCodeLoginManager( + var authenticateResult: (MatrixQrCodeLoginData) -> Result = + lambdaRecorder> { Result.success(A_SESSION_ID) }, + var resetAction: () -> Unit = lambdaRecorder { }, +) : QrCodeLoginManager { + override val currentLoginStep: MutableStateFlow = + MutableStateFlow(QrCodeLoginStep.Uninitialized) + + override suspend fun authenticate(qrCodeLoginData: MatrixQrCodeLoginData): Result { + return authenticateResult(qrCodeLoginData) + } + + override fun reset() { + resetAction() + } +} diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/qrcode/QrCodeLoginFlowNodeTest.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/qrcode/QrCodeLoginFlowNodeTest.kt index 066c59094a7..b542c99ced3 100644 --- a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/qrcode/QrCodeLoginFlowNodeTest.kt +++ b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/qrcode/QrCodeLoginFlowNodeTest.kt @@ -22,13 +22,18 @@ import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.utils.customisations.NodeCustomisationDirectoryImpl import com.google.common.truth.Truth.assertThat import io.element.android.features.login.impl.DefaultLoginUserStory +import io.element.android.features.login.impl.di.FakeQrCodeLoginComponent import io.element.android.features.login.impl.screens.qrcode.confirmation.QrCodeConfirmationStep -import io.element.android.libraries.matrix.api.auth.qrlogin.MatrixQrCodeLoginData +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.core.SessionId +import io.element.android.libraries.matrix.api.auth.qrlogin.QrLoginException import io.element.android.libraries.matrix.test.A_SESSION_ID -import io.element.android.tests.testutils.lambda.lambdaRecorder -import kotlinx.coroutines.flow.MutableStateFlow +import io.element.android.libraries.matrix.test.auth.FakeAuthenticationService +import io.element.android.libraries.matrix.test.auth.qrlogin.FakeQrCodeLoginData +import io.element.android.tests.testutils.testCoroutineDispatchers +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.advanceUntilIdle import kotlinx.coroutines.test.runTest import org.junit.Test import org.junit.runner.RunWith @@ -50,9 +55,151 @@ class QrCodeLoginFlowNodeTest { .isEqualTo(QrCodeLoginFlowNode.NavTarget.QrCodeConfirmation(QrCodeConfirmationStep.DisplayVerificationCode("123456"))) } - private fun createLoginFlowNode( - qrCodeLoginManager: FakeQrCodeLoginManager = FakeQrCodeLoginManager(), + @Test + fun `backstack changes when failure step is received`() = runTest { + val qrCodeLoginManager = FakeQrCodeLoginManager() + val flowNode = createLoginFlowNode(qrCodeLoginManager = qrCodeLoginManager) + flowNode.observeLoginStep() + assertThat(flowNode.currentNavTarget()).isEqualTo(QrCodeLoginFlowNode.NavTarget.Initial) + + // Only case when this doesn't happen, since it's handled by the already displayed UI + qrCodeLoginManager.currentLoginStep.value = QrCodeLoginStep.Failed(QrLoginException.InvalidQrCode) + assertThat(flowNode.currentNavTarget()).isEqualTo(QrCodeLoginFlowNode.NavTarget.Initial) + + qrCodeLoginManager.currentLoginStep.value = QrCodeLoginStep.Failed(QrLoginException.Expired) + assertThat(flowNode.currentNavTarget()).isEqualTo(QrCodeLoginFlowNode.NavTarget.Error(QrCodeErrorScreenType.Expired)) + + qrCodeLoginManager.currentLoginStep.value = QrCodeLoginStep.Failed(QrLoginException.Declined) + assertThat(flowNode.currentNavTarget()).isEqualTo(QrCodeLoginFlowNode.NavTarget.Error(QrCodeErrorScreenType.Declined)) + + qrCodeLoginManager.currentLoginStep.value = QrCodeLoginStep.Failed(QrLoginException.Cancelled) + assertThat(flowNode.currentNavTarget()).isEqualTo(QrCodeLoginFlowNode.NavTarget.Error(QrCodeErrorScreenType.Cancelled)) + + qrCodeLoginManager.currentLoginStep.value = QrCodeLoginStep.Failed(QrLoginException.SlidingSyncNotAvailable) + assertThat(flowNode.currentNavTarget()).isEqualTo(QrCodeLoginFlowNode.NavTarget.Error(QrCodeErrorScreenType.SlidingSyncNotAvailable)) + + qrCodeLoginManager.currentLoginStep.value = QrCodeLoginStep.Failed(QrLoginException.LinkingNotSupported) + assertThat(flowNode.currentNavTarget()).isEqualTo(QrCodeLoginFlowNode.NavTarget.Error(QrCodeErrorScreenType.ProtocolNotSupported)) + + qrCodeLoginManager.currentLoginStep.value = QrCodeLoginStep.Failed(QrLoginException.ConnectionInsecure) + assertThat(flowNode.currentNavTarget()).isEqualTo(QrCodeLoginFlowNode.NavTarget.Error(QrCodeErrorScreenType.InsecureChannelDetected)) + + qrCodeLoginManager.currentLoginStep.value = QrCodeLoginStep.Failed(QrLoginException.OidcMetadataInvalid) + assertThat(flowNode.currentNavTarget()).isEqualTo(QrCodeLoginFlowNode.NavTarget.Error(QrCodeErrorScreenType.UnknownError)) + + qrCodeLoginManager.currentLoginStep.value = QrCodeLoginStep.Failed(QrLoginException.Unknown) + assertThat(flowNode.currentNavTarget()).isEqualTo(QrCodeLoginFlowNode.NavTarget.Error(QrCodeErrorScreenType.UnknownError)) + } + + @Test + fun `backstack doesn't change when other steps are received`() = runTest { + val qrCodeLoginManager = FakeQrCodeLoginManager() + val flowNode = createLoginFlowNode(qrCodeLoginManager = qrCodeLoginManager) + flowNode.observeLoginStep() + assertThat(flowNode.currentNavTarget()).isEqualTo(QrCodeLoginFlowNode.NavTarget.Initial) + + qrCodeLoginManager.currentLoginStep.value = QrCodeLoginStep.Starting + assertThat(flowNode.currentNavTarget()).isEqualTo(QrCodeLoginFlowNode.NavTarget.Initial) + + qrCodeLoginManager.currentLoginStep.value = QrCodeLoginStep.Finished + assertThat(flowNode.currentNavTarget()).isEqualTo(QrCodeLoginFlowNode.NavTarget.Initial) + } + + @OptIn(ExperimentalCoroutinesApi::class) + @Test + fun `startAuthentication - success marks the login flow as done`() = runTest { + val fakeAuthenticationService = FakeAuthenticationService( + loginWithQrCodeResult = { _, progress -> + progress(QrCodeLoginStep.Finished) + Result.success(A_SESSION_ID) + } + ) + // Test with a real manager to ensure the flow is correctly done + val qrCodeLoginManager = DefaultQrCodeLoginManager(fakeAuthenticationService) + val defaultLoginUserStory = DefaultLoginUserStory().apply { + loginFlowIsDone.value = false + } + val flowNode = createLoginFlowNode( + qrCodeLoginManager = qrCodeLoginManager, + defaultLoginUserStory = defaultLoginUserStory, + coroutineDispatchers = testCoroutineDispatchers(useUnconfinedTestDispatcher = true) + ) + + flowNode.run { startAuthentication(FakeQrCodeLoginData()) } + assertThat(flowNode.isLoginInProgress()).isTrue() + + advanceUntilIdle() + + assertThat(qrCodeLoginManager.currentLoginStep.value).isEqualTo(QrCodeLoginStep.Finished) + assertThat(defaultLoginUserStory.loginFlowIsDone.value).isTrue() + assertThat(flowNode.isLoginInProgress()).isFalse() + } + + @OptIn(ExperimentalCoroutinesApi::class) + @Test + fun `startAuthentication - failure is correctly handled`() = runTest { + val fakeAuthenticationService = FakeAuthenticationService( + loginWithQrCodeResult = { _, progress -> + progress(QrCodeLoginStep.Failed(QrLoginException.Unknown)) + Result.failure(IllegalStateException("Failed")) + } + ) + // Test with a real manager to ensure the flow is correctly done + val qrCodeLoginManager = DefaultQrCodeLoginManager(fakeAuthenticationService) + val defaultLoginUserStory = DefaultLoginUserStory().apply { + loginFlowIsDone.value = false + } + val flowNode = createLoginFlowNode( + qrCodeLoginManager = qrCodeLoginManager, + defaultLoginUserStory = defaultLoginUserStory, + coroutineDispatchers = testCoroutineDispatchers(useUnconfinedTestDispatcher = true) + ) + + flowNode.run { startAuthentication(FakeQrCodeLoginData()) } + assertThat(flowNode.isLoginInProgress()).isTrue() + + advanceUntilIdle() + + assertThat(qrCodeLoginManager.currentLoginStep.value).isEqualTo(QrCodeLoginStep.Failed(QrLoginException.Unknown)) + assertThat(defaultLoginUserStory.loginFlowIsDone.value).isFalse() + assertThat(flowNode.isLoginInProgress()).isFalse() + } + + @OptIn(ExperimentalCoroutinesApi::class) + @Test + fun `startAuthentication - then reset`() = runTest { + val fakeAuthenticationService = FakeAuthenticationService( + loginWithQrCodeResult = { _, progress -> + progress(QrCodeLoginStep.Finished) + Result.success(A_SESSION_ID) + } + ) + // Test with a real manager to ensure the flow is correctly done + val qrCodeLoginManager = DefaultQrCodeLoginManager(fakeAuthenticationService) + val defaultLoginUserStory = DefaultLoginUserStory().apply { + loginFlowIsDone.value = false + } + val flowNode = createLoginFlowNode( + qrCodeLoginManager = qrCodeLoginManager, + defaultLoginUserStory = defaultLoginUserStory, + coroutineDispatchers = testCoroutineDispatchers(useUnconfinedTestDispatcher = true) + ) + + flowNode.run { startAuthentication(FakeQrCodeLoginData()) } + assertThat(flowNode.isLoginInProgress()).isTrue() + flowNode.reset() + + advanceUntilIdle() + + assertThat(qrCodeLoginManager.currentLoginStep.value).isEqualTo(QrCodeLoginStep.Uninitialized) + assertThat(defaultLoginUserStory.loginFlowIsDone.value).isFalse() + assertThat(flowNode.isLoginInProgress()).isFalse() + } + + private fun TestScope.createLoginFlowNode( + qrCodeLoginManager: QrCodeLoginManager = FakeQrCodeLoginManager(), defaultLoginUserStory: DefaultLoginUserStory = DefaultLoginUserStory(), + coroutineDispatchers: CoroutineDispatchers = testCoroutineDispatchers() ): QrCodeLoginFlowNode { val buildContext = BuildContext( ancestryInfo = AncestryInfo.Root, @@ -62,21 +209,11 @@ class QrCodeLoginFlowNodeTest { return QrCodeLoginFlowNode( buildContext = buildContext, plugins = emptyList(), - qrCodeLoginManager = qrCodeLoginManager, - defaultLoginUserStory = defaultLoginUserStory + qrCodeLoginComponentBuilder = FakeQrCodeLoginComponent.Builder(qrCodeLoginManager), + defaultLoginUserStory = defaultLoginUserStory, + coroutineDispatchers = coroutineDispatchers, ) } private fun QrCodeLoginFlowNode.currentNavTarget() = backstack.elements.value.last().key.navTarget } - -class FakeQrCodeLoginManager( - var authenticateResult: (MatrixQrCodeLoginData) -> Result = - lambdaRecorder> { Result.success(A_SESSION_ID) }, -) : QrCodeLoginManager { - override val currentLoginStep: MutableStateFlow = MutableStateFlow(QrCodeLoginStep.Uninitialized) - - override suspend fun authenticate(qrCodeLoginData: MatrixQrCodeLoginData): Result { - return authenticateResult(qrCodeLoginData) - } -} diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/qrcode/error/QrCodeErrorViewTest.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/qrcode/error/QrCodeErrorViewTest.kt index 1b8cbcdb82b..f68e686f1a7 100644 --- a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/qrcode/error/QrCodeErrorViewTest.kt +++ b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/qrcode/error/QrCodeErrorViewTest.kt @@ -21,6 +21,7 @@ import androidx.compose.ui.test.junit4.AndroidComposeTestRule import androidx.compose.ui.test.junit4.createAndroidComposeRule import androidx.test.ext.junit.runners.AndroidJUnit4 import io.element.android.features.login.impl.R +import io.element.android.features.login.impl.qrcode.QrCodeErrorScreenType import io.element.android.tests.testutils.clickOn import io.element.android.tests.testutils.ensureCalledOnce import io.element.android.tests.testutils.pressBackKey @@ -55,10 +56,16 @@ class QrCodeErrorViewTest { } private fun AndroidComposeTestRule.setQrCodeErrorView( - onRetry: () -> Unit + onRetry: () -> Unit, + errorScreenType: QrCodeErrorScreenType = QrCodeErrorScreenType.UnknownError, + appName: String = "Element X", ) { setContent { - QrCodeErrorView(onRetry = onRetry) + QrCodeErrorView( + errorScreenType = errorScreenType, + appName = appName, + onRetry = onRetry + ) } } } 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 43defcc2359..7bc351b2834 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 @@ -20,8 +20,12 @@ 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.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.FakeMatrixQrCodeLoginDataFactory +import io.element.android.tests.testutils.lambda.lambdaRecorder import io.element.android.tests.testutils.testCoroutineDispatchers import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runTest @@ -78,11 +82,39 @@ class QrCodeScanPresenterTest { } } + @Test + fun `present - login failed with invalid QR format and we display it and recover from it`() = runTest { + val qrCodeLoginDataFactory = FakeMatrixQrCodeLoginDataFactory() + val qrCodeLoginManager = FakeQrCodeLoginManager() + val resetAction = lambdaRecorder { + qrCodeLoginManager.currentLoginStep.value = QrCodeLoginStep.Uninitialized + } + qrCodeLoginManager.resetAction = resetAction + val presenter = createQrCodeScanPresenter(qrCodeLoginDataFactory = qrCodeLoginDataFactory, qrCodeLoginManager = qrCodeLoginManager) + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + // Skip initial item + skipItems(1) + + qrCodeLoginManager.currentLoginStep.value = QrCodeLoginStep.Failed(QrLoginException.InvalidQrCode) + + val errorState = awaitItem() + // The state for this screen is failure + assertThat(errorState.authenticationAction.isFailure()).isTrue() + // However, the QrCodeLoginManager is reset + resetAction.assertions().isCalledOnce() + assertThat(qrCodeLoginManager.currentLoginStep.value).isEqualTo(QrCodeLoginStep.Uninitialized) + } + } + private fun TestScope.createQrCodeScanPresenter( qrCodeLoginDataFactory: FakeMatrixQrCodeLoginDataFactory = FakeMatrixQrCodeLoginDataFactory(), coroutineDispatchers: CoroutineDispatchers = testCoroutineDispatchers(), + qrCodeLoginManager: FakeQrCodeLoginManager = FakeQrCodeLoginManager(), ) = QrCodeScanPresenter( qrCodeLoginDataFactory = qrCodeLoginDataFactory, + qrCodeLoginManager = qrCodeLoginManager, coroutineDispatchers = coroutineDispatchers, ) } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index cb9448f5682..1a52e1d287c 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -102,8 +102,8 @@ androidx_preference = "androidx.preference:preference:1.2.1" androidx_webkit = "androidx.webkit:webkit:1.11.0" androidx_compose_bom = { module = "androidx.compose:compose-bom", version.ref = "compose_bom" } -androidx_compose_material3 = { module = "androidx.compose.material3:material3", version.ref = "material3" } -androidx_compose_material3_windowsizeclass = { module = "androidx.compose.material3:material3-window-size-class", version.ref = "material3" } +androidx_compose_material3 = { module = "androidx.compose.material3:material3" } +androidx_compose_material3_windowsizeclass = { module = "androidx.compose.material3:material3-window-size-class" } androidx_compose_material3_adaptive = "androidx.compose.material3:material3-adaptive-android:1.0.0-alpha06" androidx_compose_ui = { module = "androidx.compose.ui:ui" } androidx_compose_ui_tooling = { module = "androidx.compose.ui:ui-tooling" } diff --git a/libraries/qrcode/src/main/kotlin/io/element/android/libraries/qrcode/QRCodeAnalyzer.kt b/libraries/qrcode/src/main/kotlin/io/element/android/libraries/qrcode/QRCodeAnalyzer.kt index dab8745b47a..451efd67fe1 100644 --- a/libraries/qrcode/src/main/kotlin/io/element/android/libraries/qrcode/QRCodeAnalyzer.kt +++ b/libraries/qrcode/src/main/kotlin/io/element/android/libraries/qrcode/QRCodeAnalyzer.kt @@ -25,7 +25,7 @@ import zxingcpp.BarcodeReader internal class QRCodeAnalyzer( private val onQrCodeScanned: (result: ByteArray?) -> Unit ) : ImageAnalysis.Analyzer { - private val reader = BarcodeReader() + private val reader by lazy { BarcodeReader() } override fun analyze(image: ImageProxy) { if (image.format in SUPPORTED_IMAGE_FORMATS) { From 43276e32481b46ae35537abc39a070289f6afe98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= Date: Fri, 24 May 2024 12:38:04 +0200 Subject: [PATCH 45/60] Fix lint issues --- .../features/login/impl/di/QrCodeLoginBindings.kt | 1 - .../impl/screens/qrcode/error/QrCodeErrorView.kt | 13 +------------ .../impl/screens/qrcode/scan/QrCodeScanPresenter.kt | 12 +++++++----- .../login/impl/di/FakeQrCodeLoginComponent.kt | 4 ++-- .../api/auth/qrlogin/QrCodeDecodeException.kt | 2 +- .../matrix/impl/auth/qrlogin/QrErrorMapper.kt | 6 +++--- .../android/libraries/qrcode/QRCodeAnalyzer.kt | 5 +---- .../android/libraries/qrcode/QrCodeCameraView.kt | 1 - 8 files changed, 15 insertions(+), 29 deletions(-) diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/di/QrCodeLoginBindings.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/di/QrCodeLoginBindings.kt index 75d9aa66398..d16a69f75f6 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/di/QrCodeLoginBindings.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/di/QrCodeLoginBindings.kt @@ -17,7 +17,6 @@ package io.element.android.features.login.impl.di import com.squareup.anvil.annotations.ContributesTo -import io.element.android.features.login.impl.qrcode.DefaultQrCodeLoginManager import io.element.android.features.login.impl.qrcode.QrCodeLoginManager @ContributesTo(QrCodeLoginScope::class) diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/error/QrCodeErrorView.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/error/QrCodeErrorView.kt index 8dfea8d6827..04aac518e1e 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/error/QrCodeErrorView.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/error/QrCodeErrorView.kt @@ -49,10 +49,10 @@ import kotlinx.collections.immutable.persistentListOf @Composable fun QrCodeErrorView( - modifier: Modifier = Modifier, errorScreenType: QrCodeErrorScreenType, appName: String, onRetry: () -> Unit, + modifier: Modifier = Modifier, ) { BackHandler { onRetry() @@ -109,17 +109,6 @@ private fun ColumnScope.InsecureChannelDetectedError() { ) } -@Composable -private fun ErrorMessageParagraph(text: String) { - Text( - modifier = Modifier.fillMaxWidth(), - text = text, - style = ElementTheme.typography.fontBodyMdRegular, - color = ElementTheme.colors.textSecondary, - textAlign = TextAlign.Center, - ) -} - @Composable private fun Content(errorScreenType: QrCodeErrorScreenType) { when (errorScreenType) { 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 2d0fa74097a..444c84dd38e 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 @@ -19,7 +19,6 @@ package io.element.android.features.login.impl.screens.qrcode.scan import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.MutableState -import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -56,7 +55,9 @@ class QrCodeScanPresenter @Inject constructor( val coroutineScope = rememberCoroutineScope() val authenticationAction: MutableState> = remember { mutableStateOf(AsyncAction.Uninitialized) } - ObserveQRCodeLoginFailures(authenticationAction = authenticationAction) + ObserveQRCodeLoginFailures { + authenticationAction.value = AsyncAction.Failure(it) + } fun handleEvents(event: QrCodeScanEvents) { when (event) { @@ -79,12 +80,13 @@ class QrCodeScanPresenter @Inject constructor( } @Composable - private fun ObserveQRCodeLoginFailures(authenticationAction: MutableState>) { - LaunchedEffect(Unit) { + private fun ObserveQRCodeLoginFailures(onInvalidQrCodeError: (QrLoginException) -> Unit) { + LaunchedEffect(onInvalidQrCodeError) { qrCodeLoginManager.currentLoginStep .onEach { state -> if (state is QrCodeLoginStep.Failed && state.error is QrLoginException.InvalidQrCode) { - authenticationAction.value = AsyncAction.Failure(state.error) + onInvalidQrCodeError(state.error) + // The error was handled here, reset the login state qrCodeLoginManager.reset() } } diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/di/FakeQrCodeLoginComponent.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/di/FakeQrCodeLoginComponent.kt index 39f96107ca9..f3a8dbd6940 100644 --- a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/di/FakeQrCodeLoginComponent.kt +++ b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/di/FakeQrCodeLoginComponent.kt @@ -25,10 +25,10 @@ import io.element.android.features.login.impl.qrcode.QrCodeLoginManager import io.element.android.libraries.architecture.AssistedNodeFactory import io.element.android.libraries.architecture.createNode -internal class FakeQrCodeLoginComponent(private val _qrCodeLoginManager: QrCodeLoginManager) : +internal class FakeQrCodeLoginComponent(private val qrCodeLoginManager: QrCodeLoginManager) : QrCodeLoginComponent { // Ignore this error, it does override a method once code generation is done - override fun qrCodeLoginManager(): QrCodeLoginManager = _qrCodeLoginManager + override fun qrCodeLoginManager(): QrCodeLoginManager = qrCodeLoginManager class Builder(private val qrCodeLoginManager: QrCodeLoginManager = FakeQrCodeLoginManager()) : QrCodeLoginComponent.Builder { diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/qrlogin/QrCodeDecodeException.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/qrlogin/QrCodeDecodeException.kt index 408a63692e8..26f7da59c3e 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/qrlogin/QrCodeDecodeException.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/qrlogin/QrCodeDecodeException.kt @@ -25,7 +25,7 @@ sealed class QrCodeDecodeException(message: String) : Exception(message) { INVALID_MODE, INVALID_VERSION, BASE64, - INVALID_PREFIX; + INVALID_PREFIX } } } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/qrlogin/QrErrorMapper.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/qrlogin/QrErrorMapper.kt index 10eb80a4f3d..137bcfff210 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/qrlogin/QrErrorMapper.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/qrlogin/QrErrorMapper.kt @@ -19,12 +19,12 @@ package io.element.android.libraries.matrix.impl.auth.qrlogin import io.element.android.libraries.matrix.api.auth.qrlogin.QrCodeDecodeException import io.element.android.libraries.matrix.api.auth.qrlogin.QrLoginException import org.matrix.rustcomponents.sdk.HumanQrLoginException -import org.matrix.rustcomponents.sdk.HumanQrLoginException as RustHumanQrLoginException import uniffi.matrix_sdk_crypto.LoginQrCodeDecodeError +import org.matrix.rustcomponents.sdk.HumanQrLoginException as RustHumanQrLoginException import org.matrix.rustcomponents.sdk.QrCodeDecodeException as RustQrCodeDecodeException object QrErrorMapper { - fun map(qrCodeDecodeException: RustQrCodeDecodeException) : QrCodeDecodeException = when (qrCodeDecodeException) { + fun map(qrCodeDecodeException: RustQrCodeDecodeException): QrCodeDecodeException = when (qrCodeDecodeException) { is RustQrCodeDecodeException.Crypto -> { val reason = when (qrCodeDecodeException.error) { LoginQrCodeDecodeError.NOT_ENOUGH_DATA -> QrCodeDecodeException.Crypto.Reason.NOT_ENOUGH_DATA @@ -38,7 +38,7 @@ object QrErrorMapper { QrCodeDecodeException.Crypto(qrCodeDecodeException.message, reason) } } - + fun map(humanQrLoginError: RustHumanQrLoginException): QrLoginException = when (humanQrLoginError) { is RustHumanQrLoginException.Cancelled -> QrLoginException.Cancelled is RustHumanQrLoginException.ConnectionInsecure -> QrLoginException.ConnectionInsecure diff --git a/libraries/qrcode/src/main/kotlin/io/element/android/libraries/qrcode/QRCodeAnalyzer.kt b/libraries/qrcode/src/main/kotlin/io/element/android/libraries/qrcode/QRCodeAnalyzer.kt index 451efd67fe1..81ea273851b 100644 --- a/libraries/qrcode/src/main/kotlin/io/element/android/libraries/qrcode/QRCodeAnalyzer.kt +++ b/libraries/qrcode/src/main/kotlin/io/element/android/libraries/qrcode/QRCodeAnalyzer.kt @@ -29,12 +29,9 @@ internal class QRCodeAnalyzer( override fun analyze(image: ImageProxy) { if (image.format in SUPPORTED_IMAGE_FORMATS) { - try { val bytes = reader.read(image).firstNotNullOfOrNull { it.bytes } - if (bytes != null ) { - onQrCodeScanned(bytes) - } + bytes?.let { onQrCodeScanned(it) } } catch (e: Exception) { Timber.w(e, "Error decoding QR code") } finally { 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 fe72561e02c..9c0bf8edc3b 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 @@ -42,7 +42,6 @@ import androidx.compose.ui.platform.LocalInspectionMode import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.compose.ui.viewinterop.AndroidView import androidx.core.content.ContextCompat -import androidx.core.view.isVisible import io.element.android.compound.theme.ElementTheme import io.element.android.libraries.designsystem.theme.components.Text import kotlinx.coroutines.delay From c50ec27538b53536767947379252dde23d08c518 Mon Sep 17 00:00:00 2001 From: ElementBot Date: Fri, 24 May 2024 10:59:09 +0000 Subject: [PATCH 46/60] Update screenshots --- ..._RootView_null_RootView-Day-3_3_null_2,NEXUS_5,1.0,en].png | 4 ++-- ...ootView_null_RootView-Night-3_4_null_2,NEXUS_5,1.0,en].png | 4 ++-- ...View_null_LeaveRoomView-Day-0_1_null_6,NEXUS_5,1.0,en].png | 4 ++-- ...ew_null_LeaveRoomView-Night-0_2_null_6,NEXUS_5,1.0,en].png | 4 ++-- ..._LockScreenSettingsView-Day-1_2_null_2,NEXUS_5,1.0,en].png | 4 ++-- ...ockScreenSettingsView-Night-1_3_null_2,NEXUS_5,1.0,en].png | 4 ++-- ...nView_null_SetupPinView-Day-3_4_null_3,NEXUS_5,1.0,en].png | 4 ++-- ...nView_null_SetupPinView-Day-3_4_null_4,NEXUS_5,1.0,en].png | 4 ++-- ...iew_null_SetupPinView-Night-3_5_null_3,NEXUS_5,1.0,en].png | 4 ++-- ...iew_null_SetupPinView-Night-3_5_null_4,NEXUS_5,1.0,en].png | 4 ++-- ..._QrCodeConfirmationView-Day-7_8_null_0,NEXUS_5,1.0,en].png | 3 +++ ..._QrCodeConfirmationView-Day-7_8_null_1,NEXUS_5,1.0,en].png | 3 +++ ..._QrCodeConfirmationView-Day-7_8_null_2,NEXUS_5,1.0,en].png | 3 +++ ...rCodeConfirmationView-Night-7_9_null_0,NEXUS_5,1.0,en].png | 3 +++ ...rCodeConfirmationView-Night-7_9_null_1,NEXUS_5,1.0,en].png | 3 +++ ...rCodeConfirmationView-Night-7_9_null_2,NEXUS_5,1.0,en].png | 3 +++ ...ew_null_QrCodeErrorView-Day-8_9_null_0,NEXUS_5,1.0,en].png | 3 +++ ...ew_null_QrCodeErrorView-Day-8_9_null_1,NEXUS_5,1.0,en].png | 3 +++ ...ew_null_QrCodeErrorView-Day-8_9_null_2,NEXUS_5,1.0,en].png | 3 +++ ...ew_null_QrCodeErrorView-Day-8_9_null_3,NEXUS_5,1.0,en].png | 3 +++ ...ew_null_QrCodeErrorView-Day-8_9_null_4,NEXUS_5,1.0,en].png | 3 +++ ...ew_null_QrCodeErrorView-Day-8_9_null_5,NEXUS_5,1.0,en].png | 3 +++ ...ew_null_QrCodeErrorView-Day-8_9_null_6,NEXUS_5,1.0,en].png | 3 +++ ...null_QrCodeErrorView-Night-8_10_null_0,NEXUS_5,1.0,en].png | 3 +++ ...null_QrCodeErrorView-Night-8_10_null_1,NEXUS_5,1.0,en].png | 3 +++ ...null_QrCodeErrorView-Night-8_10_null_2,NEXUS_5,1.0,en].png | 3 +++ ...null_QrCodeErrorView-Night-8_10_null_3,NEXUS_5,1.0,en].png | 3 +++ ...null_QrCodeErrorView-Night-8_10_null_4,NEXUS_5,1.0,en].png | 3 +++ ...null_QrCodeErrorView-Night-8_10_null_5,NEXUS_5,1.0,en].png | 3 +++ ...null_QrCodeErrorView-Night-8_10_null_6,NEXUS_5,1.0,en].png | 3 +++ ...w_null_QrCodeIntroView-Day-9_10_null_0,NEXUS_5,1.0,en].png | 3 +++ ...w_null_QrCodeIntroView-Day-9_10_null_1,NEXUS_5,1.0,en].png | 3 +++ ...null_QrCodeIntroView-Night-9_11_null_0,NEXUS_5,1.0,en].png | 3 +++ ...null_QrCodeIntroView-Night-9_11_null_1,NEXUS_5,1.0,en].png | 3 +++ ...w_null_QrCodeScanView-Day-10_11_null_0,NEXUS_5,1.0,en].png | 3 +++ ...w_null_QrCodeScanView-Day-10_11_null_1,NEXUS_5,1.0,en].png | 3 +++ ...w_null_QrCodeScanView-Day-10_11_null_2,NEXUS_5,1.0,en].png | 3 +++ ...null_QrCodeScanView-Night-10_12_null_0,NEXUS_5,1.0,en].png | 3 +++ ...null_QrCodeScanView-Night-10_12_null_1,NEXUS_5,1.0,en].png | 3 +++ ...null_QrCodeScanView-Night-10_12_null_2,NEXUS_5,1.0,en].png | 3 +++ ...hAccountProviderView-Day-11_12_null_0,NEXUS_5,1.0,en].png} | 0 ...hAccountProviderView-Day-11_12_null_1,NEXUS_5,1.0,en].png} | 0 ...ccountProviderView-Night-11_13_null_0,NEXUS_5,1.0,en].png} | 0 ...ccountProviderView-Night-11_13_null_1,NEXUS_5,1.0,en].png} | 0 ...ew_null_WaitListView-Day-12_13_null_0,NEXUS_5,1.0,en].png} | 0 ...ew_null_WaitListView-Day-12_13_null_1,NEXUS_5,1.0,en].png} | 0 ...ew_null_WaitListView-Day-12_13_null_2,NEXUS_5,1.0,en].png} | 0 ...ew_null_WaitListView-Day-12_13_null_3,NEXUS_5,1.0,en].png} | 0 ...ew_null_WaitListView-Day-12_13_null_4,NEXUS_5,1.0,en].png} | 0 ..._null_WaitListView-Night-12_14_null_0,NEXUS_5,1.0,en].png} | 0 ..._null_WaitListView-Night-12_14_null_1,NEXUS_5,1.0,en].png} | 0 ..._null_WaitListView-Night-12_14_null_2,NEXUS_5,1.0,en].png} | 0 ..._null_WaitListView-Night-12_14_null_3,NEXUS_5,1.0,en].png} | 0 ..._null_WaitListView-Night-12_14_null_4,NEXUS_5,1.0,en].png} | 0 ...outView_null_LogoutView-Day-0_1_null_0,NEXUS_5,1.0,en].png | 4 ++-- ...outView_null_LogoutView-Day-0_1_null_1,NEXUS_5,1.0,en].png | 4 ++-- ...outView_null_LogoutView-Day-0_1_null_2,NEXUS_5,1.0,en].png | 4 ++-- ...outView_null_LogoutView-Day-0_1_null_3,NEXUS_5,1.0,en].png | 4 ++-- ...outView_null_LogoutView-Day-0_1_null_4,NEXUS_5,1.0,en].png | 4 ++-- ...outView_null_LogoutView-Day-0_1_null_5,NEXUS_5,1.0,en].png | 4 ++-- ...outView_null_LogoutView-Day-0_1_null_6,NEXUS_5,1.0,en].png | 4 ++-- ...outView_null_LogoutView-Day-0_1_null_7,NEXUS_5,1.0,en].png | 4 ++-- ...outView_null_LogoutView-Day-0_1_null_8,NEXUS_5,1.0,en].png | 4 ++-- ...outView_null_LogoutView-Day-0_1_null_9,NEXUS_5,1.0,en].png | 4 ++-- ...tView_null_LogoutView-Night-0_2_null_0,NEXUS_5,1.0,en].png | 4 ++-- ...tView_null_LogoutView-Night-0_2_null_1,NEXUS_5,1.0,en].png | 4 ++-- ...tView_null_LogoutView-Night-0_2_null_2,NEXUS_5,1.0,en].png | 4 ++-- ...tView_null_LogoutView-Night-0_2_null_3,NEXUS_5,1.0,en].png | 4 ++-- ...tView_null_LogoutView-Night-0_2_null_4,NEXUS_5,1.0,en].png | 4 ++-- ...tView_null_LogoutView-Night-0_2_null_5,NEXUS_5,1.0,en].png | 4 ++-- ...tView_null_LogoutView-Night-0_2_null_6,NEXUS_5,1.0,en].png | 4 ++-- ...tView_null_LogoutView-Night-0_2_null_7,NEXUS_5,1.0,en].png | 4 ++-- ...tView_null_LogoutView-Night-0_2_null_8,NEXUS_5,1.0,en].png | 4 ++-- ...tView_null_LogoutView-Night-0_2_null_9,NEXUS_5,1.0,en].png | 4 ++-- ...ll_AdvancedSettingsView-Day-1_2_null_4,NEXUS_5,1.0,en].png | 4 ++-- ..._AdvancedSettingsView-Night-1_3_null_4,NEXUS_5,1.0,en].png | 4 ++-- ...omMembersModerationView-Day-7_7_null_7,NEXUS_5,1.0,en].png | 4 ++-- ...MembersModerationView-Night-7_8_null_7,NEXUS_5,1.0,en].png | 4 ++-- ..._null_ChangeRolesView-Day-12_12_null_6,NEXUS_5,1.0,en].png | 4 ++-- ...ull_ChangeRolesView-Night-12_13_null_6,NEXUS_5,1.0,en].png | 4 ++-- ...geRoomPermissionsView-Day-14_14_null_6,NEXUS_5,1.0,en].png | 4 ++-- ...RoomPermissionsView-Night-14_15_null_6,NEXUS_5,1.0,en].png | 4 ++-- ..._CreateNewRecoveryKeyView-Day-0_1_null,NEXUS_5,1.0,en].png | 4 ++-- ...reateNewRecoveryKeyView-Night-0_2_null,NEXUS_5,1.0,en].png | 4 ++-- ...SecureBackupDisableView-Day-1_2_null_0,NEXUS_5,1.0,en].png | 4 ++-- ...SecureBackupDisableView-Day-1_2_null_1,NEXUS_5,1.0,en].png | 4 ++-- ...SecureBackupDisableView-Day-1_2_null_2,NEXUS_5,1.0,en].png | 4 ++-- ...SecureBackupDisableView-Day-1_2_null_3,NEXUS_5,1.0,en].png | 4 ++-- ...cureBackupDisableView-Night-1_3_null_0,NEXUS_5,1.0,en].png | 4 ++-- ...cureBackupDisableView-Night-1_3_null_1,NEXUS_5,1.0,en].png | 4 ++-- ...cureBackupDisableView-Night-1_3_null_2,NEXUS_5,1.0,en].png | 4 ++-- ...cureBackupDisableView-Night-1_3_null_3,NEXUS_5,1.0,en].png | 4 ++-- ..._SecureBackupEnableView-Day-2_3_null_0,NEXUS_5,1.0,en].png | 4 ++-- ..._SecureBackupEnableView-Day-2_3_null_1,NEXUS_5,1.0,en].png | 4 ++-- ..._SecureBackupEnableView-Day-2_3_null_2,NEXUS_5,1.0,en].png | 4 ++-- ...ecureBackupEnableView-Night-2_4_null_0,NEXUS_5,1.0,en].png | 4 ++-- ...ecureBackupEnableView-Night-2_4_null_1,NEXUS_5,1.0,en].png | 4 ++-- ...ecureBackupEnableView-Night-2_4_null_2,NEXUS_5,1.0,en].png | 4 ++-- ...kupEnterRecoveryKeyView-Day-3_4_null_0,NEXUS_5,1.0,en].png | 4 ++-- ...kupEnterRecoveryKeyView-Day-3_4_null_1,NEXUS_5,1.0,en].png | 4 ++-- ...kupEnterRecoveryKeyView-Day-3_4_null_2,NEXUS_5,1.0,en].png | 4 ++-- ...kupEnterRecoveryKeyView-Day-3_4_null_3,NEXUS_5,1.0,en].png | 4 ++-- ...pEnterRecoveryKeyView-Night-3_5_null_0,NEXUS_5,1.0,en].png | 4 ++-- ...pEnterRecoveryKeyView-Night-3_5_null_1,NEXUS_5,1.0,en].png | 4 ++-- ...pEnterRecoveryKeyView-Night-3_5_null_2,NEXUS_5,1.0,en].png | 4 ++-- ...pEnterRecoveryKeyView-Night-3_5_null_3,NEXUS_5,1.0,en].png | 4 ++-- ...reBackupSetupViewChange-Day-6_7_null_0,NEXUS_5,1.0,en].png | 4 ++-- ...reBackupSetupViewChange-Day-6_7_null_1,NEXUS_5,1.0,en].png | 4 ++-- ...reBackupSetupViewChange-Day-6_7_null_2,NEXUS_5,1.0,en].png | 4 ++-- ...reBackupSetupViewChange-Day-6_7_null_3,NEXUS_5,1.0,en].png | 4 ++-- ...reBackupSetupViewChange-Day-6_7_null_4,NEXUS_5,1.0,en].png | 4 ++-- ...BackupSetupViewChange-Night-6_8_null_0,NEXUS_5,1.0,en].png | 4 ++-- ...BackupSetupViewChange-Night-6_8_null_1,NEXUS_5,1.0,en].png | 4 ++-- ...BackupSetupViewChange-Night-6_8_null_2,NEXUS_5,1.0,en].png | 4 ++-- ...BackupSetupViewChange-Night-6_8_null_3,NEXUS_5,1.0,en].png | 4 ++-- ...BackupSetupViewChange-Night-6_8_null_4,NEXUS_5,1.0,en].png | 4 ++-- ...l_SecureBackupSetupView-Day-5_6_null_0,NEXUS_5,1.0,en].png | 4 ++-- ...l_SecureBackupSetupView-Day-5_6_null_1,NEXUS_5,1.0,en].png | 4 ++-- ...l_SecureBackupSetupView-Day-5_6_null_2,NEXUS_5,1.0,en].png | 4 ++-- ...l_SecureBackupSetupView-Day-5_6_null_3,NEXUS_5,1.0,en].png | 4 ++-- ...l_SecureBackupSetupView-Day-5_6_null_4,NEXUS_5,1.0,en].png | 4 ++-- ...SecureBackupSetupView-Night-5_7_null_0,NEXUS_5,1.0,en].png | 4 ++-- ...SecureBackupSetupView-Night-5_7_null_1,NEXUS_5,1.0,en].png | 4 ++-- ...SecureBackupSetupView-Night-5_7_null_2,NEXUS_5,1.0,en].png | 4 ++-- ...SecureBackupSetupView-Night-5_7_null_3,NEXUS_5,1.0,en].png | 4 ++-- ...SecureBackupSetupView-Night-5_7_null_4,NEXUS_5,1.0,en].png | 4 ++-- ...wStepPage_null_FlowStepPage-Day_0_null,NEXUS_5,1.0,en].png | 4 ++-- ...tepPage_null_FlowStepPage-Night_1_null,NEXUS_5,1.0,en].png | 4 ++-- ...ew_null_PermissionsView-Day-0_1_null_0,NEXUS_5,1.0,en].png | 4 ++-- ...ew_null_PermissionsView-Day-0_1_null_1,NEXUS_5,1.0,en].png | 4 ++-- ...ew_null_PermissionsView-Day-0_1_null_2,NEXUS_5,1.0,en].png | 4 ++-- ...ew_null_PermissionsView-Day-0_1_null_3,NEXUS_5,1.0,en].png | 4 ++-- ..._null_PermissionsView-Night-0_2_null_0,NEXUS_5,1.0,en].png | 4 ++-- ..._null_PermissionsView-Night-0_2_null_1,NEXUS_5,1.0,en].png | 4 ++-- ..._null_PermissionsView-Night-0_2_null_2,NEXUS_5,1.0,en].png | 4 ++-- ..._null_PermissionsView-Night-0_2_null_3,NEXUS_5,1.0,en].png | 4 ++-- ...alogCreateLinkWithoutText-Day-7_8_null,NEXUS_5,1.0,en].png | 4 ++-- ...ogCreateLinkWithoutText-Night-7_9_null,NEXUS_5,1.0,en].png | 4 ++-- ...poserLinkDialogCreateLink-Day-6_7_null,NEXUS_5,1.0,en].png | 4 ++-- ...serLinkDialogCreateLink-Night-6_8_null,NEXUS_5,1.0,en].png | 4 ++-- ...omposerLinkDialogEditLink-Day-8_9_null,NEXUS_5,1.0,en].png | 4 ++-- ...oserLinkDialogEditLink-Night-8_10_null,NEXUS_5,1.0,en].png | 4 ++-- ...rorView_null_AppErrorView-Day-0_1_null,NEXUS_5,1.0,en].png | 4 ++-- ...rView_null_AppErrorView-Night-0_2_null,NEXUS_5,1.0,en].png | 2 +- 144 files changed, 289 insertions(+), 199 deletions(-) create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.confirmation_QrCodeConfirmationView_null_QrCodeConfirmationView-Day-7_8_null_0,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.confirmation_QrCodeConfirmationView_null_QrCodeConfirmationView-Day-7_8_null_1,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.confirmation_QrCodeConfirmationView_null_QrCodeConfirmationView-Day-7_8_null_2,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.confirmation_QrCodeConfirmationView_null_QrCodeConfirmationView-Night-7_9_null_0,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.confirmation_QrCodeConfirmationView_null_QrCodeConfirmationView-Night-7_9_null_1,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.confirmation_QrCodeConfirmationView_null_QrCodeConfirmationView-Night-7_9_null_2,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.error_QrCodeErrorView_null_QrCodeErrorView-Day-8_9_null_0,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.error_QrCodeErrorView_null_QrCodeErrorView-Day-8_9_null_1,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.error_QrCodeErrorView_null_QrCodeErrorView-Day-8_9_null_2,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.error_QrCodeErrorView_null_QrCodeErrorView-Day-8_9_null_3,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.error_QrCodeErrorView_null_QrCodeErrorView-Day-8_9_null_4,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.error_QrCodeErrorView_null_QrCodeErrorView-Day-8_9_null_5,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.error_QrCodeErrorView_null_QrCodeErrorView-Day-8_9_null_6,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.error_QrCodeErrorView_null_QrCodeErrorView-Night-8_10_null_0,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.error_QrCodeErrorView_null_QrCodeErrorView-Night-8_10_null_1,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.error_QrCodeErrorView_null_QrCodeErrorView-Night-8_10_null_2,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.error_QrCodeErrorView_null_QrCodeErrorView-Night-8_10_null_3,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.error_QrCodeErrorView_null_QrCodeErrorView-Night-8_10_null_4,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.error_QrCodeErrorView_null_QrCodeErrorView-Night-8_10_null_5,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.error_QrCodeErrorView_null_QrCodeErrorView-Night-8_10_null_6,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.intro_QrCodeIntroView_null_QrCodeIntroView-Day-9_10_null_0,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.intro_QrCodeIntroView_null_QrCodeIntroView-Day-9_10_null_1,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.intro_QrCodeIntroView_null_QrCodeIntroView-Night-9_11_null_0,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.intro_QrCodeIntroView_null_QrCodeIntroView-Night-9_11_null_1,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.scan_QrCodeScanView_null_QrCodeScanView-Day-10_11_null_0,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.scan_QrCodeScanView_null_QrCodeScanView-Day-10_11_null_1,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.scan_QrCodeScanView_null_QrCodeScanView-Day-10_11_null_2,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.scan_QrCodeScanView_null_QrCodeScanView-Night-10_12_null_0,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.scan_QrCodeScanView_null_QrCodeScanView-Night-10_12_null_1,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.scan_QrCodeScanView_null_QrCodeScanView-Night-10_12_null_2,NEXUS_5,1.0,en].png rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.login.impl.screens.searchaccountprovider_SearchAccountProviderView_null_SearchAccountProviderView-Day-7_8_null_0,NEXUS_5,1.0,en].png => ui_S_t[f.login.impl.screens.searchaccountprovider_SearchAccountProviderView_null_SearchAccountProviderView-Day-11_12_null_0,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.login.impl.screens.searchaccountprovider_SearchAccountProviderView_null_SearchAccountProviderView-Day-7_8_null_1,NEXUS_5,1.0,en].png => ui_S_t[f.login.impl.screens.searchaccountprovider_SearchAccountProviderView_null_SearchAccountProviderView-Day-11_12_null_1,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.login.impl.screens.searchaccountprovider_SearchAccountProviderView_null_SearchAccountProviderView-Night-7_9_null_0,NEXUS_5,1.0,en].png => ui_S_t[f.login.impl.screens.searchaccountprovider_SearchAccountProviderView_null_SearchAccountProviderView-Night-11_13_null_0,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.login.impl.screens.searchaccountprovider_SearchAccountProviderView_null_SearchAccountProviderView-Night-7_9_null_1,NEXUS_5,1.0,en].png => ui_S_t[f.login.impl.screens.searchaccountprovider_SearchAccountProviderView_null_SearchAccountProviderView-Night-11_13_null_1,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.login.impl.screens.waitlistscreen_WaitListView_null_WaitListView-Day-8_9_null_0,NEXUS_5,1.0,en].png => ui_S_t[f.login.impl.screens.waitlistscreen_WaitListView_null_WaitListView-Day-12_13_null_0,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.login.impl.screens.waitlistscreen_WaitListView_null_WaitListView-Day-8_9_null_1,NEXUS_5,1.0,en].png => ui_S_t[f.login.impl.screens.waitlistscreen_WaitListView_null_WaitListView-Day-12_13_null_1,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.login.impl.screens.waitlistscreen_WaitListView_null_WaitListView-Day-8_9_null_2,NEXUS_5,1.0,en].png => ui_S_t[f.login.impl.screens.waitlistscreen_WaitListView_null_WaitListView-Day-12_13_null_2,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.login.impl.screens.waitlistscreen_WaitListView_null_WaitListView-Day-8_9_null_3,NEXUS_5,1.0,en].png => ui_S_t[f.login.impl.screens.waitlistscreen_WaitListView_null_WaitListView-Day-12_13_null_3,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.login.impl.screens.waitlistscreen_WaitListView_null_WaitListView-Day-8_9_null_4,NEXUS_5,1.0,en].png => ui_S_t[f.login.impl.screens.waitlistscreen_WaitListView_null_WaitListView-Day-12_13_null_4,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.login.impl.screens.waitlistscreen_WaitListView_null_WaitListView-Night-8_10_null_0,NEXUS_5,1.0,en].png => ui_S_t[f.login.impl.screens.waitlistscreen_WaitListView_null_WaitListView-Night-12_14_null_0,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.login.impl.screens.waitlistscreen_WaitListView_null_WaitListView-Night-8_10_null_1,NEXUS_5,1.0,en].png => ui_S_t[f.login.impl.screens.waitlistscreen_WaitListView_null_WaitListView-Night-12_14_null_1,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.login.impl.screens.waitlistscreen_WaitListView_null_WaitListView-Night-8_10_null_2,NEXUS_5,1.0,en].png => ui_S_t[f.login.impl.screens.waitlistscreen_WaitListView_null_WaitListView-Night-12_14_null_2,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.login.impl.screens.waitlistscreen_WaitListView_null_WaitListView-Night-8_10_null_3,NEXUS_5,1.0,en].png => ui_S_t[f.login.impl.screens.waitlistscreen_WaitListView_null_WaitListView-Night-12_14_null_3,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.login.impl.screens.waitlistscreen_WaitListView_null_WaitListView-Night-8_10_null_4,NEXUS_5,1.0,en].png => ui_S_t[f.login.impl.screens.waitlistscreen_WaitListView_null_WaitListView-Night-12_14_null_4,NEXUS_5,1.0,en].png} (100%) diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[appnav.root_RootView_null_RootView-Day-3_3_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[appnav.root_RootView_null_RootView-Day-3_3_null_2,NEXUS_5,1.0,en].png index e1103596c2a..17904a637bd 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[appnav.root_RootView_null_RootView-Day-3_3_null_2,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[appnav.root_RootView_null_RootView-Day-3_3_null_2,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f606c73de0ccec703eb7a6eb374d014438ed6fda829fafe30a6757402786b59f -size 25590 +oid sha256:316dabc0ae87bc30ae039f60a7e51975137b590184954cf805f5faa919dcf833 +size 25586 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[appnav.root_RootView_null_RootView-Night-3_4_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[appnav.root_RootView_null_RootView-Night-3_4_null_2,NEXUS_5,1.0,en].png index c12b0c44e88..18f05bf6cef 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[appnav.root_RootView_null_RootView-Night-3_4_null_2,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[appnav.root_RootView_null_RootView-Night-3_4_null_2,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:70e4e94aad224d9c3d71e40bb080030455d07d87c495ac4bdc3f54aff28a4eb4 -size 21916 +oid sha256:0ae32b3f2dee34fb5da208d3052dc92adb21df04c61667fb7aa397bb5056f551 +size 21909 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.leaveroom.api_LeaveRoomView_null_LeaveRoomView-Day-0_1_null_6,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.leaveroom.api_LeaveRoomView_null_LeaveRoomView-Day-0_1_null_6,NEXUS_5,1.0,en].png index 00ac5ac1586..831f415574e 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.leaveroom.api_LeaveRoomView_null_LeaveRoomView-Day-0_1_null_6,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.leaveroom.api_LeaveRoomView_null_LeaveRoomView-Day-0_1_null_6,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:925d6129884511860505ed7ba3cec9f2ddb50105aa1f74d9ff2dc8bbed22f8d4 -size 37706 +oid sha256:f933a8b29c082f1db2cd3ea7b41b4c4d61c450a5e55b4ff9e13b0b50334764cc +size 37729 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.leaveroom.api_LeaveRoomView_null_LeaveRoomView-Night-0_2_null_6,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.leaveroom.api_LeaveRoomView_null_LeaveRoomView-Night-0_2_null_6,NEXUS_5,1.0,en].png index c6ddfe89fc3..687b00511e6 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.leaveroom.api_LeaveRoomView_null_LeaveRoomView-Night-0_2_null_6,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.leaveroom.api_LeaveRoomView_null_LeaveRoomView-Night-0_2_null_6,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a46f95f72addd2775c6dfd4dcbfe2bfb2f68eb0eaa4fe2676c3d17b285390e27 -size 32718 +oid sha256:6874f456b41979ad743f99423a4fce35d188c55e3f8f23d9da02d7335207e2b8 +size 32698 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.lockscreen.impl.settings_LockScreenSettingsView_null_LockScreenSettingsView-Day-1_2_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.lockscreen.impl.settings_LockScreenSettingsView_null_LockScreenSettingsView-Day-1_2_null_2,NEXUS_5,1.0,en].png index 422da386ea2..269600505ac 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.lockscreen.impl.settings_LockScreenSettingsView_null_LockScreenSettingsView-Day-1_2_null_2,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.lockscreen.impl.settings_LockScreenSettingsView_null_LockScreenSettingsView-Day-1_2_null_2,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:51eb7ef770f1f9023f45e2a4f009d06ea8819a32f357344326340038c66c4604 -size 32043 +oid sha256:2c6e1c1a21054c27b3345545537e5ad69c8afb042008b54f2c1a3e4d8d989bf6 +size 32035 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.lockscreen.impl.settings_LockScreenSettingsView_null_LockScreenSettingsView-Night-1_3_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.lockscreen.impl.settings_LockScreenSettingsView_null_LockScreenSettingsView-Night-1_3_null_2,NEXUS_5,1.0,en].png index 0a449cdbaeb..c0ee347e556 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.lockscreen.impl.settings_LockScreenSettingsView_null_LockScreenSettingsView-Night-1_3_null_2,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.lockscreen.impl.settings_LockScreenSettingsView_null_LockScreenSettingsView-Night-1_3_null_2,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d484bfc2c60df38eee0e76a7f5de958b7b1b227003d3e7a9bcfcc4be575c6334 -size 28295 +oid sha256:d8fee4544eb56fb30322db29f2df22453e27c442f033b213b886a2adefd3bf21 +size 28245 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.lockscreen.impl.setup.pin_SetupPinView_null_SetupPinView-Day-3_4_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.lockscreen.impl.setup.pin_SetupPinView_null_SetupPinView-Day-3_4_null_3,NEXUS_5,1.0,en].png index b7f6aac7171..a0ce47097a6 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.lockscreen.impl.setup.pin_SetupPinView_null_SetupPinView-Day-3_4_null_3,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.lockscreen.impl.setup.pin_SetupPinView_null_SetupPinView-Day-3_4_null_3,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:93263a32ec800656943f59f3d733c1b816c8f5c386b677584e58c525b4e79d6d -size 28626 +oid sha256:e724044b995b58ea7f5be8e28f081344d5a9c440f90c82f1a45dbd7e039564aa +size 28539 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.lockscreen.impl.setup.pin_SetupPinView_null_SetupPinView-Day-3_4_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.lockscreen.impl.setup.pin_SetupPinView_null_SetupPinView-Day-3_4_null_4,NEXUS_5,1.0,en].png index 66635f04827..27f6e91dc25 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.lockscreen.impl.setup.pin_SetupPinView_null_SetupPinView-Day-3_4_null_4,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.lockscreen.impl.setup.pin_SetupPinView_null_SetupPinView-Day-3_4_null_4,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6d7564e74b4dac299a5a864179df71aa957ed7fd21f9e5a6c4f3ca0741538c5e -size 35605 +oid sha256:4a2e72394e1a959423839b9975abe459fe9a3e682fa3da9d940a60caaffb0e71 +size 35600 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.lockscreen.impl.setup.pin_SetupPinView_null_SetupPinView-Night-3_5_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.lockscreen.impl.setup.pin_SetupPinView_null_SetupPinView-Night-3_5_null_3,NEXUS_5,1.0,en].png index 69e4040c24a..55a038e5714 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.lockscreen.impl.setup.pin_SetupPinView_null_SetupPinView-Night-3_5_null_3,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.lockscreen.impl.setup.pin_SetupPinView_null_SetupPinView-Night-3_5_null_3,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9b23435a077d09cc2e2b514b071510078b885045e0b9404cc2b27577d4323367 -size 25185 +oid sha256:e3ac390e401966f7004a59378d998d7f073bba1c5f0675f6fdcf476652a77fa3 +size 25173 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.lockscreen.impl.setup.pin_SetupPinView_null_SetupPinView-Night-3_5_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.lockscreen.impl.setup.pin_SetupPinView_null_SetupPinView-Night-3_5_null_4,NEXUS_5,1.0,en].png index 804737dd4f2..a176f8bbb53 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.lockscreen.impl.setup.pin_SetupPinView_null_SetupPinView-Night-3_5_null_4,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.lockscreen.impl.setup.pin_SetupPinView_null_SetupPinView-Night-3_5_null_4,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8d3458428cc9c96f4c24a8c071984eef3914e5714c2be5667eb718411f256c31 -size 30917 +oid sha256:8ee9a3f3e8b1de862fe2dce49f9d0616cb694fab31e214e304b96550be283005 +size 30820 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.confirmation_QrCodeConfirmationView_null_QrCodeConfirmationView-Day-7_8_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.confirmation_QrCodeConfirmationView_null_QrCodeConfirmationView-Day-7_8_null_0,NEXUS_5,1.0,en].png new file mode 100644 index 00000000000..9fb63483f2d --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.confirmation_QrCodeConfirmationView_null_QrCodeConfirmationView-Day-7_8_null_0,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2bff5d329fe526b6f015c3bd6159b989c40ffa09b61dab8a715b4e98a58cbcfa +size 35052 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.confirmation_QrCodeConfirmationView_null_QrCodeConfirmationView-Day-7_8_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.confirmation_QrCodeConfirmationView_null_QrCodeConfirmationView-Day-7_8_null_1,NEXUS_5,1.0,en].png new file mode 100644 index 00000000000..03544f53dca --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.confirmation_QrCodeConfirmationView_null_QrCodeConfirmationView-Day-7_8_null_1,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:41f9ad3c73698d296962f66c6333a2db9c7f60f5ae3d46a26a4684350f63a6ec +size 35668 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.confirmation_QrCodeConfirmationView_null_QrCodeConfirmationView-Day-7_8_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.confirmation_QrCodeConfirmationView_null_QrCodeConfirmationView-Day-7_8_null_2,NEXUS_5,1.0,en].png new file mode 100644 index 00000000000..29947461991 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.confirmation_QrCodeConfirmationView_null_QrCodeConfirmationView-Day-7_8_null_2,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:870600305d6dcc3aa83d340fc4d13eeba9291e63dc1d0cf2b9e54d4cc99fefb7 +size 37968 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.confirmation_QrCodeConfirmationView_null_QrCodeConfirmationView-Night-7_9_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.confirmation_QrCodeConfirmationView_null_QrCodeConfirmationView-Night-7_9_null_0,NEXUS_5,1.0,en].png new file mode 100644 index 00000000000..217e36b2464 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.confirmation_QrCodeConfirmationView_null_QrCodeConfirmationView-Night-7_9_null_0,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d72ee11bb2f772760bf0b3f2f1bb54ffc6796533b78445a95409364aa4e6a6d9 +size 33145 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.confirmation_QrCodeConfirmationView_null_QrCodeConfirmationView-Night-7_9_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.confirmation_QrCodeConfirmationView_null_QrCodeConfirmationView-Night-7_9_null_1,NEXUS_5,1.0,en].png new file mode 100644 index 00000000000..4bf8f86621a --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.confirmation_QrCodeConfirmationView_null_QrCodeConfirmationView-Night-7_9_null_1,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f7041a310e2be3c6c5374c5c337d444881f85649f29869e9539a94c89c826a21 +size 34634 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.confirmation_QrCodeConfirmationView_null_QrCodeConfirmationView-Night-7_9_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.confirmation_QrCodeConfirmationView_null_QrCodeConfirmationView-Night-7_9_null_2,NEXUS_5,1.0,en].png new file mode 100644 index 00000000000..14b7d0575c2 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.confirmation_QrCodeConfirmationView_null_QrCodeConfirmationView-Night-7_9_null_2,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:49df438d7a9d3bce6b73558740270fa68a223018c373575f958590b8ad3168fb +size 37051 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.error_QrCodeErrorView_null_QrCodeErrorView-Day-8_9_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.error_QrCodeErrorView_null_QrCodeErrorView-Day-8_9_null_0,NEXUS_5,1.0,en].png new file mode 100644 index 00000000000..44563689e1f --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.error_QrCodeErrorView_null_QrCodeErrorView-Day-8_9_null_0,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a6be99dde0529bf92ec6f0a4a2d61e22b8e3aac94dae9c343f2670692c433569 +size 23296 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.error_QrCodeErrorView_null_QrCodeErrorView-Day-8_9_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.error_QrCodeErrorView_null_QrCodeErrorView-Day-8_9_null_1,NEXUS_5,1.0,en].png new file mode 100644 index 00000000000..92ad74dc2a5 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.error_QrCodeErrorView_null_QrCodeErrorView-Day-8_9_null_1,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:257a0571dbaccf06f145dee2ce79addaa5fd80f902b2c64f68858e5c4312f454 +size 22636 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.error_QrCodeErrorView_null_QrCodeErrorView-Day-8_9_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.error_QrCodeErrorView_null_QrCodeErrorView-Day-8_9_null_2,NEXUS_5,1.0,en].png new file mode 100644 index 00000000000..a3db4551e83 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.error_QrCodeErrorView_null_QrCodeErrorView-Day-8_9_null_2,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ef4f42d13fa6cb50ba2179509ae0118125c7a95152d1d4bc5127868c65386121 +size 24221 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.error_QrCodeErrorView_null_QrCodeErrorView-Day-8_9_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.error_QrCodeErrorView_null_QrCodeErrorView-Day-8_9_null_3,NEXUS_5,1.0,en].png new file mode 100644 index 00000000000..521857e7066 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.error_QrCodeErrorView_null_QrCodeErrorView-Day-8_9_null_3,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4c74c21762dbd1c6dee55efeb7b9d6f90828da9b86a0d237a0466a3b16a8bfd5 +size 35997 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.error_QrCodeErrorView_null_QrCodeErrorView-Day-8_9_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.error_QrCodeErrorView_null_QrCodeErrorView-Day-8_9_null_4,NEXUS_5,1.0,en].png new file mode 100644 index 00000000000..29f66103efc --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.error_QrCodeErrorView_null_QrCodeErrorView-Day-8_9_null_4,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5085fc11390e591242da68d7b5f79caa1c1f9f5b3af9516c68dc42c006b02318 +size 70434 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.error_QrCodeErrorView_null_QrCodeErrorView-Day-8_9_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.error_QrCodeErrorView_null_QrCodeErrorView-Day-8_9_null_5,NEXUS_5,1.0,en].png new file mode 100644 index 00000000000..93556f727c9 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.error_QrCodeErrorView_null_QrCodeErrorView-Day-8_9_null_5,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fb34b8a91864a13af81f871df3d50494b28e2daf3a48123c1a21c076991310b1 +size 23704 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.error_QrCodeErrorView_null_QrCodeErrorView-Day-8_9_null_6,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.error_QrCodeErrorView_null_QrCodeErrorView-Day-8_9_null_6,NEXUS_5,1.0,en].png new file mode 100644 index 00000000000..353f84555c5 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.error_QrCodeErrorView_null_QrCodeErrorView-Day-8_9_null_6,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:de65b622e9286884ab9ffe3102f4af5507932776f3c844089268aaf6f91b15a0 +size 23401 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.error_QrCodeErrorView_null_QrCodeErrorView-Night-8_10_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.error_QrCodeErrorView_null_QrCodeErrorView-Night-8_10_null_0,NEXUS_5,1.0,en].png new file mode 100644 index 00000000000..5fc511d67b1 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.error_QrCodeErrorView_null_QrCodeErrorView-Night-8_10_null_0,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c9c08983edf7bbdc1d520769e9f22050e0b9c690de094f86db2748255582002d +size 22218 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.error_QrCodeErrorView_null_QrCodeErrorView-Night-8_10_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.error_QrCodeErrorView_null_QrCodeErrorView-Night-8_10_null_1,NEXUS_5,1.0,en].png new file mode 100644 index 00000000000..3e2b0451819 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.error_QrCodeErrorView_null_QrCodeErrorView-Night-8_10_null_1,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:179cb5e77e7ef0ed9311e60b2bd531ff1b0fe153bad4ada15c4c3810d4c09cf7 +size 21932 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.error_QrCodeErrorView_null_QrCodeErrorView-Night-8_10_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.error_QrCodeErrorView_null_QrCodeErrorView-Night-8_10_null_2,NEXUS_5,1.0,en].png new file mode 100644 index 00000000000..2218673d895 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.error_QrCodeErrorView_null_QrCodeErrorView-Night-8_10_null_2,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c8910d2d6b46f6eb414be78b71d32019e8c624b0bda139897d1daf81b1817778 +size 23096 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.error_QrCodeErrorView_null_QrCodeErrorView-Night-8_10_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.error_QrCodeErrorView_null_QrCodeErrorView-Night-8_10_null_3,NEXUS_5,1.0,en].png new file mode 100644 index 00000000000..67bbda3f549 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.error_QrCodeErrorView_null_QrCodeErrorView-Night-8_10_null_3,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f039fa26a67e9ad8c8ac6631408a8cfc63bc1189cfb35dae4bbf6a47b714d0a3 +size 34445 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.error_QrCodeErrorView_null_QrCodeErrorView-Night-8_10_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.error_QrCodeErrorView_null_QrCodeErrorView-Night-8_10_null_4,NEXUS_5,1.0,en].png new file mode 100644 index 00000000000..e7117a9b2ff --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.error_QrCodeErrorView_null_QrCodeErrorView-Night-8_10_null_4,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8db66d0f64278d3175ab64cd60649ceb7a8f46341d67459b1918846036d318c9 +size 64676 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.error_QrCodeErrorView_null_QrCodeErrorView-Night-8_10_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.error_QrCodeErrorView_null_QrCodeErrorView-Night-8_10_null_5,NEXUS_5,1.0,en].png new file mode 100644 index 00000000000..b6871bed022 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.error_QrCodeErrorView_null_QrCodeErrorView-Night-8_10_null_5,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8b4bffea0e3a76f5bb6a781acc37c017675f2ff35b0844961f8a8ffb3b92ab34 +size 22717 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.error_QrCodeErrorView_null_QrCodeErrorView-Night-8_10_null_6,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.error_QrCodeErrorView_null_QrCodeErrorView-Night-8_10_null_6,NEXUS_5,1.0,en].png new file mode 100644 index 00000000000..eaeed57bcb5 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.error_QrCodeErrorView_null_QrCodeErrorView-Night-8_10_null_6,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fe6fca5fe35bb1df5453215df74c0cc8c2580c6b1d1e71db11c7a34da4bb609e +size 22166 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.intro_QrCodeIntroView_null_QrCodeIntroView-Day-9_10_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.intro_QrCodeIntroView_null_QrCodeIntroView-Day-9_10_null_0,NEXUS_5,1.0,en].png new file mode 100644 index 00000000000..e509e677a6e --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.intro_QrCodeIntroView_null_QrCodeIntroView-Day-9_10_null_0,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ded6f11e88e85a42700d1d9b88c4571853d440dcbb7d64b2d24a93a5fe0784d9 +size 45757 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.intro_QrCodeIntroView_null_QrCodeIntroView-Day-9_10_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.intro_QrCodeIntroView_null_QrCodeIntroView-Day-9_10_null_1,NEXUS_5,1.0,en].png new file mode 100644 index 00000000000..a366c222037 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.intro_QrCodeIntroView_null_QrCodeIntroView-Day-9_10_null_1,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:08f5c3928218cbb35e0edcc5ec710633bb20e120fe32c680e7e1d00503dd6895 +size 48635 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.intro_QrCodeIntroView_null_QrCodeIntroView-Night-9_11_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.intro_QrCodeIntroView_null_QrCodeIntroView-Night-9_11_null_0,NEXUS_5,1.0,en].png new file mode 100644 index 00000000000..2c643f5c7bb --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.intro_QrCodeIntroView_null_QrCodeIntroView-Night-9_11_null_0,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cf4ef82ef2bc40198befb73d1531b63fdcff8ce3e71a7669b97571caa94eae1a +size 41611 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.intro_QrCodeIntroView_null_QrCodeIntroView-Night-9_11_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.intro_QrCodeIntroView_null_QrCodeIntroView-Night-9_11_null_1,NEXUS_5,1.0,en].png new file mode 100644 index 00000000000..3daf90cabba --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.intro_QrCodeIntroView_null_QrCodeIntroView-Night-9_11_null_1,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e7d145e6ba56418918934621d4f77d13f48c5bd868938d9fdd225e2048434edf +size 42503 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.scan_QrCodeScanView_null_QrCodeScanView-Day-10_11_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.scan_QrCodeScanView_null_QrCodeScanView-Day-10_11_null_0,NEXUS_5,1.0,en].png new file mode 100644 index 00000000000..0aa4a191125 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.scan_QrCodeScanView_null_QrCodeScanView-Day-10_11_null_0,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a9f3d92455b16ab319b4830c91516e2af3a43ac8ac3f88d6a04d643328c99d4f +size 18512 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.scan_QrCodeScanView_null_QrCodeScanView-Day-10_11_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.scan_QrCodeScanView_null_QrCodeScanView-Day-10_11_null_1,NEXUS_5,1.0,en].png new file mode 100644 index 00000000000..7fcdc04a3d2 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.scan_QrCodeScanView_null_QrCodeScanView-Day-10_11_null_1,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:15f289148c18ee687ab55156375c89ebebfe130d5602c1906e15e3ad6d0918da +size 23545 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.scan_QrCodeScanView_null_QrCodeScanView-Day-10_11_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.scan_QrCodeScanView_null_QrCodeScanView-Day-10_11_null_2,NEXUS_5,1.0,en].png new file mode 100644 index 00000000000..77f53529500 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.scan_QrCodeScanView_null_QrCodeScanView-Day-10_11_null_2,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ec401a67e09c646f42ac8b8c381e2844b39ca8ed37fe067982e178d10ab8d6d3 +size 30340 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.scan_QrCodeScanView_null_QrCodeScanView-Night-10_12_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.scan_QrCodeScanView_null_QrCodeScanView-Night-10_12_null_0,NEXUS_5,1.0,en].png new file mode 100644 index 00000000000..b946d360b07 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.scan_QrCodeScanView_null_QrCodeScanView-Night-10_12_null_0,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:852a1fc63b7b350d420a08348e424fe90aca9e3198c59ed5787e27f803f4cc80 +size 17143 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.scan_QrCodeScanView_null_QrCodeScanView-Night-10_12_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.scan_QrCodeScanView_null_QrCodeScanView-Night-10_12_null_1,NEXUS_5,1.0,en].png new file mode 100644 index 00000000000..d7fc59ed6e1 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.scan_QrCodeScanView_null_QrCodeScanView-Night-10_12_null_1,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6e0d8c54299bdb78edebb2bd1fae16ede18c0bbb57ed1c85083f6d3a97face90 +size 22139 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.scan_QrCodeScanView_null_QrCodeScanView-Night-10_12_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.scan_QrCodeScanView_null_QrCodeScanView-Night-10_12_null_2,NEXUS_5,1.0,en].png new file mode 100644 index 00000000000..5e17456723a --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.scan_QrCodeScanView_null_QrCodeScanView-Night-10_12_null_2,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bee05f1c2f0d9b83cd62cce543bdcfc1cd5fcee3af42047397a2edb42068180a +size 28463 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.searchaccountprovider_SearchAccountProviderView_null_SearchAccountProviderView-Day-7_8_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.searchaccountprovider_SearchAccountProviderView_null_SearchAccountProviderView-Day-11_12_null_0,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.searchaccountprovider_SearchAccountProviderView_null_SearchAccountProviderView-Day-7_8_null_0,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.searchaccountprovider_SearchAccountProviderView_null_SearchAccountProviderView-Day-11_12_null_0,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.searchaccountprovider_SearchAccountProviderView_null_SearchAccountProviderView-Day-7_8_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.searchaccountprovider_SearchAccountProviderView_null_SearchAccountProviderView-Day-11_12_null_1,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.searchaccountprovider_SearchAccountProviderView_null_SearchAccountProviderView-Day-7_8_null_1,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.searchaccountprovider_SearchAccountProviderView_null_SearchAccountProviderView-Day-11_12_null_1,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.searchaccountprovider_SearchAccountProviderView_null_SearchAccountProviderView-Night-7_9_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.searchaccountprovider_SearchAccountProviderView_null_SearchAccountProviderView-Night-11_13_null_0,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.searchaccountprovider_SearchAccountProviderView_null_SearchAccountProviderView-Night-7_9_null_0,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.searchaccountprovider_SearchAccountProviderView_null_SearchAccountProviderView-Night-11_13_null_0,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.searchaccountprovider_SearchAccountProviderView_null_SearchAccountProviderView-Night-7_9_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.searchaccountprovider_SearchAccountProviderView_null_SearchAccountProviderView-Night-11_13_null_1,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.searchaccountprovider_SearchAccountProviderView_null_SearchAccountProviderView-Night-7_9_null_1,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.searchaccountprovider_SearchAccountProviderView_null_SearchAccountProviderView-Night-11_13_null_1,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.waitlistscreen_WaitListView_null_WaitListView-Day-8_9_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.waitlistscreen_WaitListView_null_WaitListView-Day-12_13_null_0,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.waitlistscreen_WaitListView_null_WaitListView-Day-8_9_null_0,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.waitlistscreen_WaitListView_null_WaitListView-Day-12_13_null_0,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.waitlistscreen_WaitListView_null_WaitListView-Day-8_9_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.waitlistscreen_WaitListView_null_WaitListView-Day-12_13_null_1,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.waitlistscreen_WaitListView_null_WaitListView-Day-8_9_null_1,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.waitlistscreen_WaitListView_null_WaitListView-Day-12_13_null_1,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.waitlistscreen_WaitListView_null_WaitListView-Day-8_9_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.waitlistscreen_WaitListView_null_WaitListView-Day-12_13_null_2,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.waitlistscreen_WaitListView_null_WaitListView-Day-8_9_null_2,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.waitlistscreen_WaitListView_null_WaitListView-Day-12_13_null_2,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.waitlistscreen_WaitListView_null_WaitListView-Day-8_9_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.waitlistscreen_WaitListView_null_WaitListView-Day-12_13_null_3,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.waitlistscreen_WaitListView_null_WaitListView-Day-8_9_null_3,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.waitlistscreen_WaitListView_null_WaitListView-Day-12_13_null_3,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.waitlistscreen_WaitListView_null_WaitListView-Day-8_9_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.waitlistscreen_WaitListView_null_WaitListView-Day-12_13_null_4,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.waitlistscreen_WaitListView_null_WaitListView-Day-8_9_null_4,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.waitlistscreen_WaitListView_null_WaitListView-Day-12_13_null_4,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.waitlistscreen_WaitListView_null_WaitListView-Night-8_10_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.waitlistscreen_WaitListView_null_WaitListView-Night-12_14_null_0,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.waitlistscreen_WaitListView_null_WaitListView-Night-8_10_null_0,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.waitlistscreen_WaitListView_null_WaitListView-Night-12_14_null_0,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.waitlistscreen_WaitListView_null_WaitListView-Night-8_10_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.waitlistscreen_WaitListView_null_WaitListView-Night-12_14_null_1,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.waitlistscreen_WaitListView_null_WaitListView-Night-8_10_null_1,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.waitlistscreen_WaitListView_null_WaitListView-Night-12_14_null_1,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.waitlistscreen_WaitListView_null_WaitListView-Night-8_10_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.waitlistscreen_WaitListView_null_WaitListView-Night-12_14_null_2,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.waitlistscreen_WaitListView_null_WaitListView-Night-8_10_null_2,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.waitlistscreen_WaitListView_null_WaitListView-Night-12_14_null_2,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.waitlistscreen_WaitListView_null_WaitListView-Night-8_10_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.waitlistscreen_WaitListView_null_WaitListView-Night-12_14_null_3,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.waitlistscreen_WaitListView_null_WaitListView-Night-8_10_null_3,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.waitlistscreen_WaitListView_null_WaitListView-Night-12_14_null_3,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.waitlistscreen_WaitListView_null_WaitListView-Night-8_10_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.waitlistscreen_WaitListView_null_WaitListView-Night-12_14_null_4,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.waitlistscreen_WaitListView_null_WaitListView-Night-8_10_null_4,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.waitlistscreen_WaitListView_null_WaitListView-Night-12_14_null_4,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.logout.impl_LogoutView_null_LogoutView-Day-0_1_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.logout.impl_LogoutView_null_LogoutView-Day-0_1_null_0,NEXUS_5,1.0,en].png index f92b8d86734..3e8df831cc5 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.logout.impl_LogoutView_null_LogoutView-Day-0_1_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.logout.impl_LogoutView_null_LogoutView-Day-0_1_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f670200a658c07e4f1a0502f4915652bb116974674fa8cb3a2abdfd15b9caac6 -size 13499 +oid sha256:06859aba2434bd7048b13551d97407ebddf77e0285e6c164f97adb296653e181 +size 13024 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.logout.impl_LogoutView_null_LogoutView-Day-0_1_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.logout.impl_LogoutView_null_LogoutView-Day-0_1_null_1,NEXUS_5,1.0,en].png index ffdb8376504..4c0a94eb93a 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.logout.impl_LogoutView_null_LogoutView-Day-0_1_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.logout.impl_LogoutView_null_LogoutView-Day-0_1_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:633c3b27df8fb915f3d47deaab3a58b1c83df510d5b01f592b889c84fedea242 -size 40951 +oid sha256:6572c6d03ba8e06d72ac5cf16268e826a996340b8dba6019116e5133884ea5fa +size 40074 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.logout.impl_LogoutView_null_LogoutView-Day-0_1_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.logout.impl_LogoutView_null_LogoutView-Day-0_1_null_2,NEXUS_5,1.0,en].png index 6ecda3527cc..27382bbcf5b 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.logout.impl_LogoutView_null_LogoutView-Day-0_1_null_2,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.logout.impl_LogoutView_null_LogoutView-Day-0_1_null_2,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8ff9a8a91455b5beddd59cc1523f3182d28e2652230653f12f088e322a651ddf -size 30433 +oid sha256:4f7ba30ddac1f1c0ecc9cf977a802a7943aae86f1360b25700f1db3dedcc19eb +size 29865 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.logout.impl_LogoutView_null_LogoutView-Day-0_1_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.logout.impl_LogoutView_null_LogoutView-Day-0_1_null_3,NEXUS_5,1.0,en].png index ffdb8376504..4c0a94eb93a 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.logout.impl_LogoutView_null_LogoutView-Day-0_1_null_3,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.logout.impl_LogoutView_null_LogoutView-Day-0_1_null_3,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:633c3b27df8fb915f3d47deaab3a58b1c83df510d5b01f592b889c84fedea242 -size 40951 +oid sha256:6572c6d03ba8e06d72ac5cf16268e826a996340b8dba6019116e5133884ea5fa +size 40074 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.logout.impl_LogoutView_null_LogoutView-Day-0_1_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.logout.impl_LogoutView_null_LogoutView-Day-0_1_null_4,NEXUS_5,1.0,en].png index 405a4695e2c..d7b6cd01180 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.logout.impl_LogoutView_null_LogoutView-Day-0_1_null_4,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.logout.impl_LogoutView_null_LogoutView-Day-0_1_null_4,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7b835b9719f3ead626b1338875bc48e7f3e93969334221663d51917135e45d5f -size 27524 +oid sha256:58e8a238d093ddd2869ac03cf644fecd93e509814bc930af8037d19f66250a2b +size 27170 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.logout.impl_LogoutView_null_LogoutView-Day-0_1_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.logout.impl_LogoutView_null_LogoutView-Day-0_1_null_5,NEXUS_5,1.0,en].png index f0bb97209d1..a139e97e94d 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.logout.impl_LogoutView_null_LogoutView-Day-0_1_null_5,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.logout.impl_LogoutView_null_LogoutView-Day-0_1_null_5,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0febe80eece401e8c293ae626b1641db9eeafbb65d90724d71c1fe1f4b3e7e5a -size 21712 +oid sha256:6181c50e8937f6b3ed8209d2b693472d05c7dadc238bc8021adf46d3bf065964 +size 21350 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.logout.impl_LogoutView_null_LogoutView-Day-0_1_null_6,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.logout.impl_LogoutView_null_LogoutView-Day-0_1_null_6,NEXUS_5,1.0,en].png index cfaebd5f228..94890ff13f3 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.logout.impl_LogoutView_null_LogoutView-Day-0_1_null_6,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.logout.impl_LogoutView_null_LogoutView-Day-0_1_null_6,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:63516117fe1073f55d10d1db57c034b2a4d8849718e1f284cdfa7b17be13e444 -size 26255 +oid sha256:52b5818f5c807cf13c0cb98033a1f6631e7e71b2b5f0f5cc41eb2725a95a5de6 +size 25869 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.logout.impl_LogoutView_null_LogoutView-Day-0_1_null_7,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.logout.impl_LogoutView_null_LogoutView-Day-0_1_null_7,NEXUS_5,1.0,en].png index edef3eb4053..480ab7657dd 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.logout.impl_LogoutView_null_LogoutView-Day-0_1_null_7,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.logout.impl_LogoutView_null_LogoutView-Day-0_1_null_7,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:db5b7f5ea356ab4285198bb0a150ede07d69d37006b5b347bd36574f55d5ce25 -size 38833 +oid sha256:9dc2b5a9cb4bdef8d22b3019f5458dafa14765c32524ac10785039f81439a0fd +size 38286 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.logout.impl_LogoutView_null_LogoutView-Day-0_1_null_8,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.logout.impl_LogoutView_null_LogoutView-Day-0_1_null_8,NEXUS_5,1.0,en].png index aa3f9abd52a..78c9bfbdb0d 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.logout.impl_LogoutView_null_LogoutView-Day-0_1_null_8,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.logout.impl_LogoutView_null_LogoutView-Day-0_1_null_8,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8d6aaa2a3500e1c484f84da6217a129439773deef187a5a6a6e02762b8cf7d4d -size 36370 +oid sha256:98991b5c29d54c2f092a7250dc649099ed30328a7a0e58fb3278c125eb1fcec5 +size 35918 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.logout.impl_LogoutView_null_LogoutView-Day-0_1_null_9,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.logout.impl_LogoutView_null_LogoutView-Day-0_1_null_9,NEXUS_5,1.0,en].png index 0c8b719319c..b8c4f1add48 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.logout.impl_LogoutView_null_LogoutView-Day-0_1_null_9,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.logout.impl_LogoutView_null_LogoutView-Day-0_1_null_9,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f42ca076978903806d9e760eff37d207d7b59598093625db2803cb7390df271f -size 37673 +oid sha256:f282e13a384916514c965ce6fded078f279918556b21e705f6fd570f8a8c826a +size 36969 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.logout.impl_LogoutView_null_LogoutView-Night-0_2_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.logout.impl_LogoutView_null_LogoutView-Night-0_2_null_0,NEXUS_5,1.0,en].png index f407d449302..1faa021c781 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.logout.impl_LogoutView_null_LogoutView-Night-0_2_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.logout.impl_LogoutView_null_LogoutView-Night-0_2_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6281c219f7f637d24e09f6f74c2443fe5d9e4b432404cd1434e02ee87fd94f26 -size 12892 +oid sha256:689253e0c68a31a7e9904588c69763ec10ab0de7da2e71dcd13f1a8873b4e9c3 +size 12414 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.logout.impl_LogoutView_null_LogoutView-Night-0_2_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.logout.impl_LogoutView_null_LogoutView-Night-0_2_null_1,NEXUS_5,1.0,en].png index b7242acf110..6985a68adea 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.logout.impl_LogoutView_null_LogoutView-Night-0_2_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.logout.impl_LogoutView_null_LogoutView-Night-0_2_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4e50c6a70b8122e48fe7786a6f7b108cf78a6085f1abf77271980f28768ed9bd -size 38404 +oid sha256:ecf872759a271301a687f2443218a8ce5491ea570966eeacec908d95225271cc +size 37630 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.logout.impl_LogoutView_null_LogoutView-Night-0_2_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.logout.impl_LogoutView_null_LogoutView-Night-0_2_null_2,NEXUS_5,1.0,en].png index 5353902d592..39583240cdd 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.logout.impl_LogoutView_null_LogoutView-Night-0_2_null_2,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.logout.impl_LogoutView_null_LogoutView-Night-0_2_null_2,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3f81464b6bad7c990313dcbd460312c42ddc0fbbbb49711aded2ee5b7f7785e5 -size 28786 +oid sha256:a74e605de002f9f4cc540cbc6595cda476c8e6c517b13facc2013e2da3fedfa2 +size 28064 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.logout.impl_LogoutView_null_LogoutView-Night-0_2_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.logout.impl_LogoutView_null_LogoutView-Night-0_2_null_3,NEXUS_5,1.0,en].png index b7242acf110..6985a68adea 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.logout.impl_LogoutView_null_LogoutView-Night-0_2_null_3,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.logout.impl_LogoutView_null_LogoutView-Night-0_2_null_3,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4e50c6a70b8122e48fe7786a6f7b108cf78a6085f1abf77271980f28768ed9bd -size 38404 +oid sha256:ecf872759a271301a687f2443218a8ce5491ea570966eeacec908d95225271cc +size 37630 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.logout.impl_LogoutView_null_LogoutView-Night-0_2_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.logout.impl_LogoutView_null_LogoutView-Night-0_2_null_4,NEXUS_5,1.0,en].png index 0acd91562be..99c4509f30b 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.logout.impl_LogoutView_null_LogoutView-Night-0_2_null_4,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.logout.impl_LogoutView_null_LogoutView-Night-0_2_null_4,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3b8415e487f9c7b33ef33d212d481b807aa11cd635a316c833a7e11f68081995 -size 23926 +oid sha256:4301bebd628ca4e9110455a87e40ae59986e0e2c388212cd7d092e218042d18b +size 23359 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.logout.impl_LogoutView_null_LogoutView-Night-0_2_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.logout.impl_LogoutView_null_LogoutView-Night-0_2_null_5,NEXUS_5,1.0,en].png index 62b78ef2686..e286d8ef972 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.logout.impl_LogoutView_null_LogoutView-Night-0_2_null_5,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.logout.impl_LogoutView_null_LogoutView-Night-0_2_null_5,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:624f48f1f45ce3908a2fa4f9c5ff7d77e01b4dcd8992a2c4d69d5be6e76a5913 -size 18964 +oid sha256:8c8820183a52df7021d16139f1a465814d253acd8db08394680361ec209c293e +size 18390 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.logout.impl_LogoutView_null_LogoutView-Night-0_2_null_6,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.logout.impl_LogoutView_null_LogoutView-Night-0_2_null_6,NEXUS_5,1.0,en].png index 8629678a19e..1c7d58d1c3b 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.logout.impl_LogoutView_null_LogoutView-Night-0_2_null_6,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.logout.impl_LogoutView_null_LogoutView-Night-0_2_null_6,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2b92ab3a5309dc3feec01f521b7037d95a70b8e53a01cafb77b09e71d1a942ef -size 22622 +oid sha256:da211b8fb87fbaad51fef0a6b46c33e80a2596b9273712b4e62b1c23cd607956 +size 22041 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.logout.impl_LogoutView_null_LogoutView-Night-0_2_null_7,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.logout.impl_LogoutView_null_LogoutView-Night-0_2_null_7,NEXUS_5,1.0,en].png index 6f9a01acc64..8770331a3eb 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.logout.impl_LogoutView_null_LogoutView-Night-0_2_null_7,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.logout.impl_LogoutView_null_LogoutView-Night-0_2_null_7,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7e46c2e311c3df4e8ce44eb899b8d52b21b7ce22e5e0027abf02d78270caa683 -size 36926 +oid sha256:e448f8ca835f75dcaf24d470d3c1b323f618eef1816d5463f0f6a7c0390d128a +size 36103 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.logout.impl_LogoutView_null_LogoutView-Night-0_2_null_8,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.logout.impl_LogoutView_null_LogoutView-Night-0_2_null_8,NEXUS_5,1.0,en].png index 8274ac92d72..f51db01f62d 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.logout.impl_LogoutView_null_LogoutView-Night-0_2_null_8,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.logout.impl_LogoutView_null_LogoutView-Night-0_2_null_8,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:83f28c6bc246143085757af44d0be250c13f1799dfcab115680479dea46a953a -size 34720 +oid sha256:90ce627d784aa1057ac8f665c384c450f296efa17596666fd1ae12556f016d1d +size 34130 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.logout.impl_LogoutView_null_LogoutView-Night-0_2_null_9,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.logout.impl_LogoutView_null_LogoutView-Night-0_2_null_9,NEXUS_5,1.0,en].png index e6ba55728ec..f984aa86213 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.logout.impl_LogoutView_null_LogoutView-Night-0_2_null_9,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.logout.impl_LogoutView_null_LogoutView-Night-0_2_null_9,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:95de2a832bf54e7586564e29c675bd97c0d3be62d65a5bdbe6ed489be2b49c28 -size 35628 +oid sha256:f0a12298a5c27be387f653c107512c8fa4009bed2f9c7efacbbc5eca025633e9 +size 35037 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.advanced_AdvancedSettingsView_null_AdvancedSettingsView-Day-1_2_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.advanced_AdvancedSettingsView_null_AdvancedSettingsView-Day-1_2_null_4,NEXUS_5,1.0,en].png index 35c515822e7..f9b6c3edd9d 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.advanced_AdvancedSettingsView_null_AdvancedSettingsView-Day-1_2_null_4,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.advanced_AdvancedSettingsView_null_AdvancedSettingsView-Day-1_2_null_4,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:22b20d44483d246fb0b10a96d2e243195b2c2c45df4721337c77be0f4261d596 -size 42891 +oid sha256:9c7e614cef04ef8a97d7a0ccea9cf66dfaf34cebad4d299cdf63c8f0e7bc6f5e +size 42928 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.advanced_AdvancedSettingsView_null_AdvancedSettingsView-Night-1_3_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.advanced_AdvancedSettingsView_null_AdvancedSettingsView-Night-1_3_null_4,NEXUS_5,1.0,en].png index 106f6044400..0cb7f346d4b 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.advanced_AdvancedSettingsView_null_AdvancedSettingsView-Night-1_3_null_4,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.advanced_AdvancedSettingsView_null_AdvancedSettingsView-Night-1_3_null_4,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5ef2574271233ac991ea92ef9b5b00fec488ea14c3d75cc8a308bbe4b11e5a63 -size 37557 +oid sha256:d45372adc3949012741c5dad4f1f48e1ffdd6971cbdbafcb1556aa0eef0890d6 +size 37588 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.moderation_RoomMembersModerationView_null_RoomMembersModerationView-Day-7_7_null_7,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.moderation_RoomMembersModerationView_null_RoomMembersModerationView-Day-7_7_null_7,NEXUS_5,1.0,en].png index f2df014dda3..24eca80a51a 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.moderation_RoomMembersModerationView_null_RoomMembersModerationView-Day-7_7_null_7,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.moderation_RoomMembersModerationView_null_RoomMembersModerationView-Day-7_7_null_7,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:80fd575cdce74e271d92fb49258b79af0cfba2826320225830061a2be9b738d4 -size 30470 +oid sha256:5b3fd9f221df0df05655fb72624978d251cbd6336d1795bad9a731cae727d270 +size 30414 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.moderation_RoomMembersModerationView_null_RoomMembersModerationView-Night-7_8_null_7,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.moderation_RoomMembersModerationView_null_RoomMembersModerationView-Night-7_8_null_7,NEXUS_5,1.0,en].png index b0f81d6edf4..a04c52e653a 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.moderation_RoomMembersModerationView_null_RoomMembersModerationView-Night-7_8_null_7,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.moderation_RoomMembersModerationView_null_RoomMembersModerationView-Night-7_8_null_7,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e10ed25980d51632b8e6dc0c5b0f81c9b137d1882d28f3d74caee0fc852beb8a -size 25419 +oid sha256:12b50b494ade128c82cdc0c8854f3575312800b9c78eef5bdd7d68b59549884e +size 25309 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_null_ChangeRolesView-Day-12_12_null_6,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_null_ChangeRolesView-Day-12_12_null_6,NEXUS_5,1.0,en].png index 0618e2e36e3..d945c5a6e08 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_null_ChangeRolesView-Day-12_12_null_6,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_null_ChangeRolesView-Day-12_12_null_6,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8bb41968ae644485c0e3dd51f23ffee1cfabf62d2c72df9efc577545913c87fb -size 64304 +oid sha256:307d7ed2cfd0574fecc3f75d74562d21f956f45ed0441b12f4ee0acede8d7833 +size 64290 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_null_ChangeRolesView-Night-12_13_null_6,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_null_ChangeRolesView-Night-12_13_null_6,NEXUS_5,1.0,en].png index 6aa63a44334..f1544ef1d9c 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_null_ChangeRolesView-Night-12_13_null_6,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_null_ChangeRolesView-Night-12_13_null_6,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:29027fe574ca4681e532b127d609e0e71a9f6095a56ee81db33dd258cbb4701b -size 60085 +oid sha256:91dfa35e176ffd7663f6a8999dd89c4f11edf051683d8327fce0ca95a695598f +size 60067 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Day-14_14_null_6,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Day-14_14_null_6,NEXUS_5,1.0,en].png index 1e8a0d28134..f1efceae0e0 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Day-14_14_null_6,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Day-14_14_null_6,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:eee33a3534c66fb6a6ecebfdfe3518a7446880905da5bb0aae11ff260f7470eb -size 47999 +oid sha256:9f7d1c724bff62e1dbbf6dba747efcbb60d4c9e679d67795c95fe4f97c615e47 +size 47965 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Night-14_15_null_6,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Night-14_15_null_6,NEXUS_5,1.0,en].png index 41b61c015cf..56b4f0f0326 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Night-14_15_null_6,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Night-14_15_null_6,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2d332b41b0a566848e81376e335706e822a25cbd3c40233cedef3f21eba7dbf0 -size 42811 +oid sha256:7ba021673b32199838c80f1b862361c6469b9487970c4603dd428ed4a0ec3424 +size 42760 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.createkey_CreateNewRecoveryKeyView_null_CreateNewRecoveryKeyView-Day-0_1_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.createkey_CreateNewRecoveryKeyView_null_CreateNewRecoveryKeyView-Day-0_1_null,NEXUS_5,1.0,en].png index ecac9d32ec1..fc133096052 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.createkey_CreateNewRecoveryKeyView_null_CreateNewRecoveryKeyView-Day-0_1_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.createkey_CreateNewRecoveryKeyView_null_CreateNewRecoveryKeyView-Day-0_1_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2e7892eb99a1307f2c34968f76e994e6b8e3916ff5c218eb9cbc81424001fd54 -size 64251 +oid sha256:4f8d909d70e89b2a07b349cfafb6e8d1766d7d3fd992bb1e113a73f4cdba1600 +size 63194 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.createkey_CreateNewRecoveryKeyView_null_CreateNewRecoveryKeyView-Night-0_2_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.createkey_CreateNewRecoveryKeyView_null_CreateNewRecoveryKeyView-Night-0_2_null,NEXUS_5,1.0,en].png index 6be4090def7..ebd91c50fa8 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.createkey_CreateNewRecoveryKeyView_null_CreateNewRecoveryKeyView-Night-0_2_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.createkey_CreateNewRecoveryKeyView_null_CreateNewRecoveryKeyView-Night-0_2_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:dbcf18bb210a68b0e02512abd949845a8c5a705a177ce0875599734ce46da7ff -size 57525 +oid sha256:13e59a91adb0795ddd033e3a4f666ff046317337ab06eba112a0d429c70179de +size 56153 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.disable_SecureBackupDisableView_null_SecureBackupDisableView-Day-1_2_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.disable_SecureBackupDisableView_null_SecureBackupDisableView-Day-1_2_null_0,NEXUS_5,1.0,en].png index f81f879d4c3..abfaefd6a75 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.disable_SecureBackupDisableView_null_SecureBackupDisableView-Day-1_2_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.disable_SecureBackupDisableView_null_SecureBackupDisableView-Day-1_2_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4a44e4e825613b9074dd3540b855e04d60be9b2c92800bf8b59318bacf7e9e13 -size 58837 +oid sha256:50b7fada6e3c2d30e2cefa4d61a1b2fe48fc91e4d191e26037213a8f91188beb +size 58244 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.disable_SecureBackupDisableView_null_SecureBackupDisableView-Day-1_2_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.disable_SecureBackupDisableView_null_SecureBackupDisableView-Day-1_2_null_1,NEXUS_5,1.0,en].png index 9b0bab38021..ef4185de49c 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.disable_SecureBackupDisableView_null_SecureBackupDisableView-Day-1_2_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.disable_SecureBackupDisableView_null_SecureBackupDisableView-Day-1_2_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a9836c41a609a4c2b6a8f4399e6eaf3a728fd8525f2e2799578e0c2a2920ee7d -size 47750 +oid sha256:c43db1b16c8a9b4d9904fad5cd045b50ffb418b584e876019a9820f0dd4e0506 +size 48135 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.disable_SecureBackupDisableView_null_SecureBackupDisableView-Day-1_2_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.disable_SecureBackupDisableView_null_SecureBackupDisableView-Day-1_2_null_2,NEXUS_5,1.0,en].png index 13bdef5f01d..b79b386b262 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.disable_SecureBackupDisableView_null_SecureBackupDisableView-Day-1_2_null_2,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.disable_SecureBackupDisableView_null_SecureBackupDisableView-Day-1_2_null_2,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c4dd71e75099f447127611d17ba3370fad1142880b8c2cee869ae4de072a2c3a -size 59496 +oid sha256:8c4adb79e797c3f2ff2f3363d0b39995f7f8be7429ab30b8d75c359fa3b70b07 +size 58902 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.disable_SecureBackupDisableView_null_SecureBackupDisableView-Day-1_2_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.disable_SecureBackupDisableView_null_SecureBackupDisableView-Day-1_2_null_3,NEXUS_5,1.0,en].png index d4374e318a7..98e8b74c6e9 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.disable_SecureBackupDisableView_null_SecureBackupDisableView-Day-1_2_null_3,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.disable_SecureBackupDisableView_null_SecureBackupDisableView-Day-1_2_null_3,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c06a6bfa6b4633d1e621b153c33f4797ce810447b6333b16c7903f93a44b7cbc -size 30441 +oid sha256:86b015ad42c3591afa5514e3d172115acd0c8ab97dbb2f6c40e0ce63293f6024 +size 31138 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.disable_SecureBackupDisableView_null_SecureBackupDisableView-Night-1_3_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.disable_SecureBackupDisableView_null_SecureBackupDisableView-Night-1_3_null_0,NEXUS_5,1.0,en].png index c5610061ab5..ef54fc8ce93 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.disable_SecureBackupDisableView_null_SecureBackupDisableView-Night-1_3_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.disable_SecureBackupDisableView_null_SecureBackupDisableView-Night-1_3_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:064d3ba3a36fbb3cda2f3e52a93769611f9ceb525cee57e13c93f863f02d2375 -size 57116 +oid sha256:80ada0400cb5d6324880c3d87172671142d225269b02612a7ab90d6217848339 +size 56315 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.disable_SecureBackupDisableView_null_SecureBackupDisableView-Night-1_3_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.disable_SecureBackupDisableView_null_SecureBackupDisableView-Night-1_3_null_1,NEXUS_5,1.0,en].png index fc92b3b603f..f3a78d6ddb0 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.disable_SecureBackupDisableView_null_SecureBackupDisableView-Night-1_3_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.disable_SecureBackupDisableView_null_SecureBackupDisableView-Night-1_3_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:052915f0797ab55a15fa919b90c0afa0d59cb3d660be9e216d1e159def4e3289 -size 41876 +oid sha256:7a254134c8371a3619fbcbb20817d82a2bdac99b5c3478cc6b87d58f5c47fbda +size 41985 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.disable_SecureBackupDisableView_null_SecureBackupDisableView-Night-1_3_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.disable_SecureBackupDisableView_null_SecureBackupDisableView-Night-1_3_null_2,NEXUS_5,1.0,en].png index 95652f882dd..69acbaae9f1 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.disable_SecureBackupDisableView_null_SecureBackupDisableView-Night-1_3_null_2,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.disable_SecureBackupDisableView_null_SecureBackupDisableView-Night-1_3_null_2,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8725ea0ff263ffa4c679cae4353f58edaf59b9cd442c3090b3e6274e5bd5caf4 -size 57599 +oid sha256:e205d30cff1eb4da41c0969e036b79fbb5a7eb1684a3c8ff0fc3cfe8dcb3d3f0 +size 56790 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.disable_SecureBackupDisableView_null_SecureBackupDisableView-Night-1_3_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.disable_SecureBackupDisableView_null_SecureBackupDisableView-Night-1_3_null_3,NEXUS_5,1.0,en].png index 1f9bc16a8c3..ae4fd5f24ab 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.disable_SecureBackupDisableView_null_SecureBackupDisableView-Night-1_3_null_3,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.disable_SecureBackupDisableView_null_SecureBackupDisableView-Night-1_3_null_3,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:afd0b803fdd2a04a73c16f7aa2e9c1b0f2b8a41ad050ba3cc753e5eef56b1047 -size 26807 +oid sha256:85f14968e422f1b82f1b6ac9cffe20fee1cdc39875da393e1a50f9f96490be34 +size 27041 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enable_SecureBackupEnableView_null_SecureBackupEnableView-Day-2_3_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enable_SecureBackupEnableView_null_SecureBackupEnableView-Day-2_3_null_0,NEXUS_5,1.0,en].png index edc379c1d86..2801b314170 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enable_SecureBackupEnableView_null_SecureBackupEnableView-Day-2_3_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enable_SecureBackupEnableView_null_SecureBackupEnableView-Day-2_3_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d06540794a9891e734ed28540e5e090a57f83d3158827144f1e0d96ef1e169fc -size 15499 +oid sha256:ce7c875e682e35c8d4f3ab445d4606ff3a7748012c4de45578d53891facee1c6 +size 15006 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enable_SecureBackupEnableView_null_SecureBackupEnableView-Day-2_3_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enable_SecureBackupEnableView_null_SecureBackupEnableView-Day-2_3_null_1,NEXUS_5,1.0,en].png index e870073ed04..9067a84cfe5 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enable_SecureBackupEnableView_null_SecureBackupEnableView-Day-2_3_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enable_SecureBackupEnableView_null_SecureBackupEnableView-Day-2_3_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7fbfb1d5f9040a97ae47b211b81103f77df799be1eaab12c5aa9ffd4409cb439 -size 16052 +oid sha256:04cacf7b4074b9466b56dc94bf2a3f6246c1a323fc7f823687e2c0cbd3a11084 +size 15564 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enable_SecureBackupEnableView_null_SecureBackupEnableView-Day-2_3_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enable_SecureBackupEnableView_null_SecureBackupEnableView-Day-2_3_null_2,NEXUS_5,1.0,en].png index 417e7f1f470..1b030e472a7 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enable_SecureBackupEnableView_null_SecureBackupEnableView-Day-2_3_null_2,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enable_SecureBackupEnableView_null_SecureBackupEnableView-Day-2_3_null_2,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3836c247c21c7db517fe6f5f5e07f81a22dbaeb0ba8a9b8600f0aa06b99948bf -size 22110 +oid sha256:ef55f84387ca8c61bc5e365a00c3ec654d31feb72177a76cd6836467c03b8890 +size 21627 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enable_SecureBackupEnableView_null_SecureBackupEnableView-Night-2_4_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enable_SecureBackupEnableView_null_SecureBackupEnableView-Night-2_4_null_0,NEXUS_5,1.0,en].png index d6112d12e09..d178a1240a3 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enable_SecureBackupEnableView_null_SecureBackupEnableView-Night-2_4_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enable_SecureBackupEnableView_null_SecureBackupEnableView-Night-2_4_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fdc5662cebd3b521128523e4afa25bfa685fd60791a3d7105cdbf453821901f5 -size 14625 +oid sha256:62ffd4bf9f63a8c50feec1fa7f2f4eb8e774b671b8def15d16beb7dc45391f4f +size 14042 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enable_SecureBackupEnableView_null_SecureBackupEnableView-Night-2_4_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enable_SecureBackupEnableView_null_SecureBackupEnableView-Night-2_4_null_1,NEXUS_5,1.0,en].png index eddca870941..8996c25dbc6 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enable_SecureBackupEnableView_null_SecureBackupEnableView-Night-2_4_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enable_SecureBackupEnableView_null_SecureBackupEnableView-Night-2_4_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:19a5d55cc8d6b234f71aaca7a052ccc167abf2e47ac7240b31cc9bcd81de0312 -size 15160 +oid sha256:a4c60dcd25ee44b5c342a70a611639303d1b4a421c8c9cfb7d215d620c7569c6 +size 14568 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enable_SecureBackupEnableView_null_SecureBackupEnableView-Night-2_4_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enable_SecureBackupEnableView_null_SecureBackupEnableView-Night-2_4_null_2,NEXUS_5,1.0,en].png index 8a0ff142f9d..0d7cc6779ff 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enable_SecureBackupEnableView_null_SecureBackupEnableView-Night-2_4_null_2,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enable_SecureBackupEnableView_null_SecureBackupEnableView-Night-2_4_null_2,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6648d1d6eec0a5b18ce264435be16379aaa7422d0c5f7ca7fcfe76a9fce33e56 -size 18845 +oid sha256:691a9cff6c88d7a25a08cc31a330cdddd5b98214b7a1774278732fa1e12f6e26 +size 18252 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Day-3_4_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Day-3_4_null_0,NEXUS_5,1.0,en].png index d8682a6a155..d8b8316e1b2 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Day-3_4_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Day-3_4_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5584b334e0ca80d3619d00d1168b2a15ced299e529adb8bd53bfc9bc9e495ecf -size 41339 +oid sha256:eeba7841b7f7bc296e20430cea930f93b99624f3f966a0246b6327ecb830050f +size 40694 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Day-3_4_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Day-3_4_null_1,NEXUS_5,1.0,en].png index aa07ae2e60d..a0f7e7d6f2e 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Day-3_4_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Day-3_4_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3ffbd8973d7370f0a992dc148b73bcc243b77b4e80d9a7ce8a49aec1312ec451 -size 52942 +oid sha256:7db21c3a4b92b7212538cb34be12ea6b59188bff5729d309c3186bc638e26bfc +size 52335 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Day-3_4_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Day-3_4_null_2,NEXUS_5,1.0,en].png index 5eba9096cd1..74802f62b8a 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Day-3_4_null_2,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Day-3_4_null_2,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8154d4dfb395001e30dfb0807e796996abb812aa40523a3ec2c7a996345a1cb0 -size 50651 +oid sha256:d276e2d2eea5624107266aed4da45bebb02f08501ea939422ceb5af98d9e5f82 +size 50167 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Day-3_4_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Day-3_4_null_3,NEXUS_5,1.0,en].png index d9f174c0e9f..e314ac25cb7 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Day-3_4_null_3,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Day-3_4_null_3,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ed4451219724c9b5dfb151a3d138dd54dfba6c78b961881bf56b602813dae13b -size 44523 +oid sha256:99ba716acde7b4ccc038b0cb7fd80acaf1ee27f016a50c783d8bede2ac35d6fe +size 46882 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Night-3_5_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Night-3_5_null_0,NEXUS_5,1.0,en].png index 9dd8bd3bfb8..b7ee516a912 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Night-3_5_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Night-3_5_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:73b5a7598ca2255e88ef2098168e326aa1d23952ca8a43360380a6bfc2b36cce -size 38770 +oid sha256:5b3c0ea92ed7ea695d2627c285c96b6c3aa3f2cd165ddf4b78619599a41ad7f3 +size 38016 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Night-3_5_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Night-3_5_null_1,NEXUS_5,1.0,en].png index a5ed227845f..831429ef291 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Night-3_5_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Night-3_5_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:92ec6f85b19f217c9b5242e183e868d7288c0aca3578e6e563907910197113fe -size 49105 +oid sha256:2700e9c46eea5787fced440065d14d99eb5f6165372db114ead4b2bafbadd163 +size 48327 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Night-3_5_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Night-3_5_null_2,NEXUS_5,1.0,en].png index ef11d24386a..26f366861d0 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Night-3_5_null_2,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Night-3_5_null_2,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:857fe8d13c9801edffb6ae9e36317a279addee2ea1729fc4b0be84d45e06d023 -size 47449 +oid sha256:d4d0f01db3f1bb77cdadad0c45c4589b160306d8f37729bdfb26b25294ce18f9 +size 46726 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Night-3_5_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Night-3_5_null_3,NEXUS_5,1.0,en].png index f9e00a548ec..7bc915f532a 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Night-3_5_null_3,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Night-3_5_null_3,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2731b003df8b067a1d8f04ccd8209ce8f6a3de0c6469c29ea3cc1025390e378a -size 39419 +oid sha256:157a00be84cc8636ea21ada02719b57bffdf89a7789751628c618ae15f856685 +size 41389 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupViewChange_null_SecureBackupSetupViewChange-Day-6_7_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupViewChange_null_SecureBackupSetupViewChange-Day-6_7_null_0,NEXUS_5,1.0,en].png index 6980beec6bc..96cffb14969 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupViewChange_null_SecureBackupSetupViewChange-Day-6_7_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupViewChange_null_SecureBackupSetupViewChange-Day-6_7_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:dee9a6df9bffe9b49a445bdfb2c2cd75c930459300edabab15b186b62df49f72 -size 50395 +oid sha256:cb4369e39322b3a0242556041ee83d32944cdb0479afa45a0c4fb2fec09b3627 +size 49884 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupViewChange_null_SecureBackupSetupViewChange-Day-6_7_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupViewChange_null_SecureBackupSetupViewChange-Day-6_7_null_1,NEXUS_5,1.0,en].png index e1cf5691dac..2842bdafeb9 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupViewChange_null_SecureBackupSetupViewChange-Day-6_7_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupViewChange_null_SecureBackupSetupViewChange-Day-6_7_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fe12143605284f0a31edd382c3a123c63f54c12c86c71557c2774d8250ed97c9 -size 48242 +oid sha256:54d7d1261502a98be76f8052c86246bd5ab145cbe15ab5361b2d09dfd81491af +size 47617 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupViewChange_null_SecureBackupSetupViewChange-Day-6_7_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupViewChange_null_SecureBackupSetupViewChange-Day-6_7_null_2,NEXUS_5,1.0,en].png index 57c3222d341..c08d423ecae 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupViewChange_null_SecureBackupSetupViewChange-Day-6_7_null_2,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupViewChange_null_SecureBackupSetupViewChange-Day-6_7_null_2,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:77890f379265cc8feb4f3226c36da0ad29472ebcef34fee0a54bc8017855050f -size 53139 +oid sha256:f9939e254965ce6741bb4ab3245f55553f0661d7d65fdc23f6b374f18c671a50 +size 52584 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupViewChange_null_SecureBackupSetupViewChange-Day-6_7_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupViewChange_null_SecureBackupSetupViewChange-Day-6_7_null_3,NEXUS_5,1.0,en].png index 57c3222d341..c08d423ecae 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupViewChange_null_SecureBackupSetupViewChange-Day-6_7_null_3,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupViewChange_null_SecureBackupSetupViewChange-Day-6_7_null_3,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:77890f379265cc8feb4f3226c36da0ad29472ebcef34fee0a54bc8017855050f -size 53139 +oid sha256:f9939e254965ce6741bb4ab3245f55553f0661d7d65fdc23f6b374f18c671a50 +size 52584 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupViewChange_null_SecureBackupSetupViewChange-Day-6_7_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupViewChange_null_SecureBackupSetupViewChange-Day-6_7_null_4,NEXUS_5,1.0,en].png index 7ffddf4401c..ee16bf0bfd7 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupViewChange_null_SecureBackupSetupViewChange-Day-6_7_null_4,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupViewChange_null_SecureBackupSetupViewChange-Day-6_7_null_4,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f238fc53089dcf207da554545eafec5d19a4698f63be40ea1082075d52448e4a -size 48398 +oid sha256:e55709e69a2e7f0969e10490cc47863e988c32680880ce0d0ee91535f62811ca +size 52951 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupViewChange_null_SecureBackupSetupViewChange-Night-6_8_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupViewChange_null_SecureBackupSetupViewChange-Night-6_8_null_0,NEXUS_5,1.0,en].png index 1eb1581dd41..b510cf5f41f 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupViewChange_null_SecureBackupSetupViewChange-Night-6_8_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupViewChange_null_SecureBackupSetupViewChange-Night-6_8_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2054149330036c4a98af731238d0d5f8d8ef9026f99a228a63ef881345489bba -size 48472 +oid sha256:2b280312e91bd453220f05e636ec91a389c7df7a1bb6786109e38b1af99ead26 +size 47785 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupViewChange_null_SecureBackupSetupViewChange-Night-6_8_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupViewChange_null_SecureBackupSetupViewChange-Night-6_8_null_1,NEXUS_5,1.0,en].png index 4abfdd267d5..d1b6e47b929 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupViewChange_null_SecureBackupSetupViewChange-Night-6_8_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupViewChange_null_SecureBackupSetupViewChange-Night-6_8_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f098868403417e9813c2e1bf0ac318ab9d06f16c8982c6d0dffcdf70b3e77d78 -size 46227 +oid sha256:ec46aad434e4fc7c1618ac2ea658c009888afd5bedad0bc0a1259c2008f61b12 +size 45593 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupViewChange_null_SecureBackupSetupViewChange-Night-6_8_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupViewChange_null_SecureBackupSetupViewChange-Night-6_8_null_2,NEXUS_5,1.0,en].png index cc4d7d7dbff..8c1c1af9fca 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupViewChange_null_SecureBackupSetupViewChange-Night-6_8_null_2,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupViewChange_null_SecureBackupSetupViewChange-Night-6_8_null_2,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:10b9f6d872eef32250436b7ef6461aca53350fb43eca917e884e8ed1d76b7953 -size 50507 +oid sha256:33082691eb1c24d3eb0a8c6114e01333f9e8ddd1a9e92e7806d58a53fc66ed96 +size 49810 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupViewChange_null_SecureBackupSetupViewChange-Night-6_8_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupViewChange_null_SecureBackupSetupViewChange-Night-6_8_null_3,NEXUS_5,1.0,en].png index cc4d7d7dbff..8c1c1af9fca 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupViewChange_null_SecureBackupSetupViewChange-Night-6_8_null_3,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupViewChange_null_SecureBackupSetupViewChange-Night-6_8_null_3,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:10b9f6d872eef32250436b7ef6461aca53350fb43eca917e884e8ed1d76b7953 -size 50507 +oid sha256:33082691eb1c24d3eb0a8c6114e01333f9e8ddd1a9e92e7806d58a53fc66ed96 +size 49810 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupViewChange_null_SecureBackupSetupViewChange-Night-6_8_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupViewChange_null_SecureBackupSetupViewChange-Night-6_8_null_4,NEXUS_5,1.0,en].png index 9dd2af19fae..c2fd1627982 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupViewChange_null_SecureBackupSetupViewChange-Night-6_8_null_4,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupViewChange_null_SecureBackupSetupViewChange-Night-6_8_null_4,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:140332a81d31d039587587ee4e7f3b3ed64d12710a5ff91694faa8c267e213f8 -size 42444 +oid sha256:2780a50b42f70bbd97e20674477e4fbc61a490fc1a82910ecdd2e411fbdd7cfd +size 46930 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupView_null_SecureBackupSetupView-Day-5_6_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupView_null_SecureBackupSetupView-Day-5_6_null_0,NEXUS_5,1.0,en].png index dc74ff7aa70..1a88bcbb74f 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupView_null_SecureBackupSetupView-Day-5_6_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupView_null_SecureBackupSetupView-Day-5_6_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1995b9571c96bc7594e3985ef4f94e76a27eecba1354be512f16e16f09c3321b -size 51662 +oid sha256:b93fea4fce889f57554495f501dbbff862f3a2c3e2521268a33e34b491208675 +size 51127 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupView_null_SecureBackupSetupView-Day-5_6_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupView_null_SecureBackupSetupView-Day-5_6_null_1,NEXUS_5,1.0,en].png index 3601a11e5a8..c85f292761e 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupView_null_SecureBackupSetupView-Day-5_6_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupView_null_SecureBackupSetupView-Day-5_6_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fad5cb07054c859fa5aae98c3c4cea992aabd8122f02a081700bba2b93a23d1f -size 49797 +oid sha256:100cc145f871223543145127070a8a4c4e214649feb21eb0381c568872dee2a4 +size 49264 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupView_null_SecureBackupSetupView-Day-5_6_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupView_null_SecureBackupSetupView-Day-5_6_null_2,NEXUS_5,1.0,en].png index 57c3222d341..c08d423ecae 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupView_null_SecureBackupSetupView-Day-5_6_null_2,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupView_null_SecureBackupSetupView-Day-5_6_null_2,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:77890f379265cc8feb4f3226c36da0ad29472ebcef34fee0a54bc8017855050f -size 53139 +oid sha256:f9939e254965ce6741bb4ab3245f55553f0661d7d65fdc23f6b374f18c671a50 +size 52584 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupView_null_SecureBackupSetupView-Day-5_6_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupView_null_SecureBackupSetupView-Day-5_6_null_3,NEXUS_5,1.0,en].png index 57c3222d341..c08d423ecae 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupView_null_SecureBackupSetupView-Day-5_6_null_3,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupView_null_SecureBackupSetupView-Day-5_6_null_3,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:77890f379265cc8feb4f3226c36da0ad29472ebcef34fee0a54bc8017855050f -size 53139 +oid sha256:f9939e254965ce6741bb4ab3245f55553f0661d7d65fdc23f6b374f18c671a50 +size 52584 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupView_null_SecureBackupSetupView-Day-5_6_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupView_null_SecureBackupSetupView-Day-5_6_null_4,NEXUS_5,1.0,en].png index 7ffddf4401c..ee16bf0bfd7 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupView_null_SecureBackupSetupView-Day-5_6_null_4,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupView_null_SecureBackupSetupView-Day-5_6_null_4,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f238fc53089dcf207da554545eafec5d19a4698f63be40ea1082075d52448e4a -size 48398 +oid sha256:e55709e69a2e7f0969e10490cc47863e988c32680880ce0d0ee91535f62811ca +size 52951 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupView_null_SecureBackupSetupView-Night-5_7_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupView_null_SecureBackupSetupView-Night-5_7_null_0,NEXUS_5,1.0,en].png index 7cbb1cc72ba..978f67cd0a0 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupView_null_SecureBackupSetupView-Night-5_7_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupView_null_SecureBackupSetupView-Night-5_7_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:19c68e5ce4e45e27aa4c9fa4ef78798067131a8450a16e0193397a7bb106bd25 -size 49879 +oid sha256:c4aa4792a1719d1e0775500e58d042c140b89ffe814d6ce28604b20533c73450 +size 49183 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupView_null_SecureBackupSetupView-Night-5_7_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupView_null_SecureBackupSetupView-Night-5_7_null_1,NEXUS_5,1.0,en].png index 4a41ae3ae36..cf21ef2df67 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupView_null_SecureBackupSetupView-Night-5_7_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupView_null_SecureBackupSetupView-Night-5_7_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:083211e979412ec942655551fdb08d94a5567775c05ea6ec7529a1e3710af5a6 -size 47882 +oid sha256:65fdd84aaee3d828f3e29f5abf79b992642b8e206534febd2c9cf9967add702b +size 47219 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupView_null_SecureBackupSetupView-Night-5_7_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupView_null_SecureBackupSetupView-Night-5_7_null_2,NEXUS_5,1.0,en].png index cc4d7d7dbff..8c1c1af9fca 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupView_null_SecureBackupSetupView-Night-5_7_null_2,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupView_null_SecureBackupSetupView-Night-5_7_null_2,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:10b9f6d872eef32250436b7ef6461aca53350fb43eca917e884e8ed1d76b7953 -size 50507 +oid sha256:33082691eb1c24d3eb0a8c6114e01333f9e8ddd1a9e92e7806d58a53fc66ed96 +size 49810 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupView_null_SecureBackupSetupView-Night-5_7_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupView_null_SecureBackupSetupView-Night-5_7_null_3,NEXUS_5,1.0,en].png index cc4d7d7dbff..8c1c1af9fca 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupView_null_SecureBackupSetupView-Night-5_7_null_3,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupView_null_SecureBackupSetupView-Night-5_7_null_3,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:10b9f6d872eef32250436b7ef6461aca53350fb43eca917e884e8ed1d76b7953 -size 50507 +oid sha256:33082691eb1c24d3eb0a8c6114e01333f9e8ddd1a9e92e7806d58a53fc66ed96 +size 49810 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupView_null_SecureBackupSetupView-Night-5_7_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupView_null_SecureBackupSetupView-Night-5_7_null_4,NEXUS_5,1.0,en].png index 9dd2af19fae..c2fd1627982 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupView_null_SecureBackupSetupView-Night-5_7_null_4,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupView_null_SecureBackupSetupView-Night-5_7_null_4,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:140332a81d31d039587587ee4e7f3b3ed64d12710a5ff91694faa8c267e213f8 -size 42444 +oid sha256:2780a50b42f70bbd97e20674477e4fbc61a490fc1a82910ecdd2e411fbdd7cfd +size 46930 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.atomic.pages_FlowStepPage_null_FlowStepPage-Day_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.atomic.pages_FlowStepPage_null_FlowStepPage-Day_0_null,NEXUS_5,1.0,en].png index 794b0b8d258..68456e62f88 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.atomic.pages_FlowStepPage_null_FlowStepPage-Day_0_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.atomic.pages_FlowStepPage_null_FlowStepPage-Day_0_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d63d314c0e00239ab889f840abedd4ad24d49d483bd677652baf82b0b40b1c80 -size 18516 +oid sha256:b0fc699def2a690015b618b6bb3dd8363cac9ee388d580bed4ba6317c4047685 +size 18341 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.atomic.pages_FlowStepPage_null_FlowStepPage-Night_1_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.atomic.pages_FlowStepPage_null_FlowStepPage-Night_1_null,NEXUS_5,1.0,en].png index c62fade2e11..94c7a8d0f27 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.atomic.pages_FlowStepPage_null_FlowStepPage-Night_1_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.atomic.pages_FlowStepPage_null_FlowStepPage-Night_1_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:915a7bf622b194b00bd419894b08dd025b60bdf69db6c4b253349e8eb0145698 -size 17673 +oid sha256:0108031310bbaae157a3ae39f958edd6458aa7733b772c200c68f635b6b530be +size 17476 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.permissions.api_PermissionsView_null_PermissionsView-Day-0_1_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.permissions.api_PermissionsView_null_PermissionsView-Day-0_1_null_0,NEXUS_5,1.0,en].png index a578d69128d..3f5d84ad6fd 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.permissions.api_PermissionsView_null_PermissionsView-Day-0_1_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.permissions.api_PermissionsView_null_PermissionsView-Day-0_1_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ac49f8798ffa60757fcda059c52dffe9f16271bf507f917dffadd20e461acd2a -size 31866 +oid sha256:205fb4655ad2b32d8e882814569d4d184facbf88e4753e641c94d1f22fe36991 +size 31861 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.permissions.api_PermissionsView_null_PermissionsView-Day-0_1_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.permissions.api_PermissionsView_null_PermissionsView-Day-0_1_null_1,NEXUS_5,1.0,en].png index cffb3cf5d86..86af8532967 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.permissions.api_PermissionsView_null_PermissionsView-Day-0_1_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.permissions.api_PermissionsView_null_PermissionsView-Day-0_1_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:aa94619e6f4022ee84aca4e31393de336545b7cf6d73c0a025a1cca68ce713e0 -size 31099 +oid sha256:a4103bd575ff198ccb3e180b8f729c2c063a67f1fa1fff787a32e4fc761fca02 +size 31089 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.permissions.api_PermissionsView_null_PermissionsView-Day-0_1_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.permissions.api_PermissionsView_null_PermissionsView-Day-0_1_null_2,NEXUS_5,1.0,en].png index 19e14046b15..2912f887933 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.permissions.api_PermissionsView_null_PermissionsView-Day-0_1_null_2,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.permissions.api_PermissionsView_null_PermissionsView-Day-0_1_null_2,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d0c1dff252324c71845437bd7fd261e7faacf98030365f3cca29763a1bf8ce24 -size 31645 +oid sha256:005c8a574e3df607d9ab177b73823e17bf1ec6fd1b3485d200dbbae6d9c22514 +size 31641 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.permissions.api_PermissionsView_null_PermissionsView-Day-0_1_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.permissions.api_PermissionsView_null_PermissionsView-Day-0_1_null_3,NEXUS_5,1.0,en].png index 956d853953b..1795cf1d23c 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.permissions.api_PermissionsView_null_PermissionsView-Day-0_1_null_3,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.permissions.api_PermissionsView_null_PermissionsView-Day-0_1_null_3,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fcacc94648c996c477eb4b0179df4eeffab6d1f7106cc656c548048ae33d4476 -size 24572 +oid sha256:e03f6dd20b2004eeddc290c0a0055b612db0f9ceb7051ebedcdbc7b2c5ee0608 +size 24573 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.permissions.api_PermissionsView_null_PermissionsView-Night-0_2_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.permissions.api_PermissionsView_null_PermissionsView-Night-0_2_null_0,NEXUS_5,1.0,en].png index 3a963029b6e..b8f53d17056 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.permissions.api_PermissionsView_null_PermissionsView-Night-0_2_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.permissions.api_PermissionsView_null_PermissionsView-Night-0_2_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5ad31f0c4f91caae057ee5b5256a1fa108c998c829c00fe8a1274ea757bf4793 -size 27348 +oid sha256:7eb681677028793b13c38ebf10e63fe6d2d1c2698845bdf3a69c662fa8b9ed13 +size 27343 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.permissions.api_PermissionsView_null_PermissionsView-Night-0_2_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.permissions.api_PermissionsView_null_PermissionsView-Night-0_2_null_1,NEXUS_5,1.0,en].png index 7ba9368ad24..915ba6d08cc 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.permissions.api_PermissionsView_null_PermissionsView-Night-0_2_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.permissions.api_PermissionsView_null_PermissionsView-Night-0_2_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:18f63eb6d1891ebf4efc5679adf52ca1e3d7cdbcbc30f36be5db8e55c36589b4 -size 26530 +oid sha256:2d88a7b56daf54b0484d9481df9facddcf7ae3ae09dbf0d4533ada5ca35a9450 +size 26525 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.permissions.api_PermissionsView_null_PermissionsView-Night-0_2_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.permissions.api_PermissionsView_null_PermissionsView-Night-0_2_null_2,NEXUS_5,1.0,en].png index 700e8ddcae6..7e845670268 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.permissions.api_PermissionsView_null_PermissionsView-Night-0_2_null_2,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.permissions.api_PermissionsView_null_PermissionsView-Night-0_2_null_2,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c694d1867634284d946cc0ebcbe91b9997a523ab7ebfb9923f113d8c07753f95 -size 27044 +oid sha256:cf677e3f0b0253e254f1cae2f272c1cd7395a6976dcd4678af7085c5c8b79c49 +size 27052 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.permissions.api_PermissionsView_null_PermissionsView-Night-0_2_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.permissions.api_PermissionsView_null_PermissionsView-Night-0_2_null_3,NEXUS_5,1.0,en].png index a810217e457..b4a27083a29 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.permissions.api_PermissionsView_null_PermissionsView-Night-0_2_null_3,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.permissions.api_PermissionsView_null_PermissionsView-Night-0_2_null_3,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9239138fd8813cb7b28baa93a8ab7ccc60c5b9bb1f330b7e4e2ec92d7012648f -size 20730 +oid sha256:93ab66ef80557e6e2f2ebcea91ea941e1ab9aceddc6abee6148d15bb5de6318e +size 20735 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer_TextComposerLinkDialogCreateLinkWithoutText_null_TextComposerLinkDialogCreateLinkWithoutText-Day-7_8_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer_TextComposerLinkDialogCreateLinkWithoutText_null_TextComposerLinkDialogCreateLinkWithoutText-Day-7_8_null,NEXUS_5,1.0,en].png index c89936dda52..e9152092f3c 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer_TextComposerLinkDialogCreateLinkWithoutText_null_TextComposerLinkDialogCreateLinkWithoutText-Day-7_8_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer_TextComposerLinkDialogCreateLinkWithoutText_null_TextComposerLinkDialogCreateLinkWithoutText-Day-7_8_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:734a60dc37f2c3f5841ab477d38db034703654c531722d7560a431d5f060f113 -size 17281 +oid sha256:fd54bcdc9581bf22754248ecb29913d3f6290b57c6a6e2536a3430d037687342 +size 17238 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer_TextComposerLinkDialogCreateLinkWithoutText_null_TextComposerLinkDialogCreateLinkWithoutText-Night-7_9_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer_TextComposerLinkDialogCreateLinkWithoutText_null_TextComposerLinkDialogCreateLinkWithoutText-Night-7_9_null,NEXUS_5,1.0,en].png index 5ff6a58533a..4c6538a9755 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer_TextComposerLinkDialogCreateLinkWithoutText_null_TextComposerLinkDialogCreateLinkWithoutText-Night-7_9_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer_TextComposerLinkDialogCreateLinkWithoutText_null_TextComposerLinkDialogCreateLinkWithoutText-Night-7_9_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b723aef3136b03ade441ebbc26b3c6705185a3d2f4918841d57f3db5ccac5c91 -size 13174 +oid sha256:95319bccc331014a3aa788fafac550e28ed2d774c0b11d4ecf98b7092c51391b +size 13142 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer_TextComposerLinkDialogCreateLink_null_TextComposerLinkDialogCreateLink-Day-6_7_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer_TextComposerLinkDialogCreateLink_null_TextComposerLinkDialogCreateLink-Day-6_7_null,NEXUS_5,1.0,en].png index df00761695a..1a247f193af 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer_TextComposerLinkDialogCreateLink_null_TextComposerLinkDialogCreateLink-Day-6_7_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer_TextComposerLinkDialogCreateLink_null_TextComposerLinkDialogCreateLink-Day-6_7_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0bbbbce7dc7176979fad367430efc344da3669918a9bc4762519a9a419dff192 -size 18805 +oid sha256:1807ee3822bf8e57edd2c1fe515340529249192d268c65c4392ceecc7a5a4afe +size 18797 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer_TextComposerLinkDialogCreateLink_null_TextComposerLinkDialogCreateLink-Night-6_8_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer_TextComposerLinkDialogCreateLink_null_TextComposerLinkDialogCreateLink-Night-6_8_null,NEXUS_5,1.0,en].png index 1eee8a30f6d..196d5ac08a8 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer_TextComposerLinkDialogCreateLink_null_TextComposerLinkDialogCreateLink-Night-6_8_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer_TextComposerLinkDialogCreateLink_null_TextComposerLinkDialogCreateLink-Night-6_8_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:deaef5e0582fa46d67f96ce5b50fb669e59ac5f76ffd079a93c9476cd1d4fc90 -size 14417 +oid sha256:c0178e566a44b061416b71db9688816d2726df87d227d517fa203dfa3e37945d +size 14461 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer_TextComposerLinkDialogEditLink_null_TextComposerLinkDialogEditLink-Day-8_9_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer_TextComposerLinkDialogEditLink_null_TextComposerLinkDialogEditLink-Day-8_9_null,NEXUS_5,1.0,en].png index 32afaaec233..5deb60eb780 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer_TextComposerLinkDialogEditLink_null_TextComposerLinkDialogEditLink-Day-8_9_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer_TextComposerLinkDialogEditLink_null_TextComposerLinkDialogEditLink-Day-8_9_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d82d7f94ddedd035fba88b75360aefad2789030a60d6f291863a9ee238fc0f94 -size 21190 +oid sha256:09f36b3dcaabdc17f7fb3f95b68d8077f6bdceff539c13d67e611b6d92e3c0e3 +size 21180 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer_TextComposerLinkDialogEditLink_null_TextComposerLinkDialogEditLink-Night-8_10_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer_TextComposerLinkDialogEditLink_null_TextComposerLinkDialogEditLink-Night-8_10_null,NEXUS_5,1.0,en].png index c2a890e33cb..1613f1718b1 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer_TextComposerLinkDialogEditLink_null_TextComposerLinkDialogEditLink-Night-8_10_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer_TextComposerLinkDialogEditLink_null_TextComposerLinkDialogEditLink-Night-8_10_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:787cc5ee6d40cb33b2e462a9c5a679e3bf6bd178f68f8bdaf83fc9d790dc57b1 -size 17121 +oid sha256:8ba53a3064eded7911a05a4a9ab5534dcd1398839b998c308745f3cac8805da5 +size 17123 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[services.apperror.impl_AppErrorView_null_AppErrorView-Day-0_1_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[services.apperror.impl_AppErrorView_null_AppErrorView-Day-0_1_null,NEXUS_5,1.0,en].png index 5acca03c1c5..730902ab3fb 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[services.apperror.impl_AppErrorView_null_AppErrorView-Day-0_1_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[services.apperror.impl_AppErrorView_null_AppErrorView-Day-0_1_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e77894690c1423b67e975d551f019ba43e9d73899c2c75def814cfe1531e9253 -size 24040 +oid sha256:30683f023338227297991536d339326894f19835ed99ba13a21d0bba40cd2609 +size 24035 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[services.apperror.impl_AppErrorView_null_AppErrorView-Night-0_2_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[services.apperror.impl_AppErrorView_null_AppErrorView-Night-0_2_null,NEXUS_5,1.0,en].png index 28c7fb7aac2..cf3b168ecdc 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[services.apperror.impl_AppErrorView_null_AppErrorView-Night-0_2_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[services.apperror.impl_AppErrorView_null_AppErrorView-Night-0_2_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e2fc386d065ab90b1f28b2fe4974546db4faf3c015c20668be5db137385209bd +oid sha256:33e77f92a239c4ca4e5ee6b1aff07ebf27d0c5173349df21b4996b7eb7f8f7ea size 20111 From f4e8f3b54a78be862f024ebbe9910a252848a444 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= Date: Wed, 29 May 2024 12:29:32 +0200 Subject: [PATCH 47/60] Bump Matrix Rust SDK to `v0.2.21` and fix conflicts --- gradle/libs.versions.toml | 2 +- .../matrix/api/notification/NotificationData.kt | 9 +++++++++ .../TimelineEventToNotificationContentMapper.kt | 8 ++++++++ .../libraries/matrix/impl/room/RoomSyncSubscriber.kt | 3 ++- .../impl/notifications/DefaultNotifiableEventResolver.kt | 4 +++- 5 files changed, 23 insertions(+), 3 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 79ab0ea0db4..3963140a092 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -156,7 +156,7 @@ jsoup = "org.jsoup:jsoup:1.17.2" appyx_core = { module = "com.bumble.appyx:core", version.ref = "appyx" } molecule-runtime = "app.cash.molecule:molecule-runtime:1.4.3" timber = "com.jakewharton.timber:timber:5.0.1" -matrix_sdk = "org.matrix.rustcomponents:sdk-android:0.2.20" +matrix_sdk = "org.matrix.rustcomponents:sdk-android:0.2.21" matrix_richtexteditor = { module = "io.element.android:wysiwyg", version.ref = "wysiwyg" } matrix_richtexteditor_compose = { module = "io.element.android:wysiwyg-compose", version.ref = "wysiwyg" } sqldelight-driver-android = { module = "app.cash.sqldelight:android-driver", version.ref = "sqldelight" } diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/notification/NotificationData.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/notification/NotificationData.kt index 9df9698eec8..55bce7c5f84 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/notification/NotificationData.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/notification/NotificationData.kt @@ -52,6 +52,10 @@ sealed interface NotificationContent { data class CallInvite( val senderId: UserId, ) : MessageLike + data class CallNotify( + val senderId: UserId, + val type: CallNotifyType, + ) : MessageLike data object CallHangup : MessageLike data object CallCandidates : MessageLike @@ -108,3 +112,8 @@ sealed interface NotificationContent { data object SpaceParent : StateEvent } } + +enum class CallNotifyType { + RING, + NOTIFY +} diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/notification/TimelineEventToNotificationContentMapper.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/notification/TimelineEventToNotificationContentMapper.kt index f599b090794..4591a2ef52f 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/notification/TimelineEventToNotificationContentMapper.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/notification/TimelineEventToNotificationContentMapper.kt @@ -17,10 +17,12 @@ package io.element.android.libraries.matrix.impl.notification import io.element.android.libraries.matrix.api.core.UserId +import io.element.android.libraries.matrix.api.notification.CallNotifyType import io.element.android.libraries.matrix.api.notification.NotificationContent import io.element.android.libraries.matrix.impl.room.member.RoomMemberMapper import io.element.android.libraries.matrix.impl.timeline.item.event.EventMessageMapper import org.matrix.rustcomponents.sdk.MessageLikeEventContent +import org.matrix.rustcomponents.sdk.NotifyType import org.matrix.rustcomponents.sdk.StateEventContent import org.matrix.rustcomponents.sdk.TimelineEvent import org.matrix.rustcomponents.sdk.TimelineEventType @@ -79,6 +81,7 @@ private fun MessageLikeEventContent.toContent(senderId: UserId): NotificationCon MessageLikeEventContent.CallCandidates -> NotificationContent.MessageLike.CallCandidates MessageLikeEventContent.CallHangup -> NotificationContent.MessageLike.CallHangup MessageLikeEventContent.CallInvite -> NotificationContent.MessageLike.CallInvite(senderId) + is MessageLikeEventContent.CallNotify -> NotificationContent.MessageLike.CallNotify(senderId, notifyType.map()) MessageLikeEventContent.KeyVerificationAccept -> NotificationContent.MessageLike.KeyVerificationAccept MessageLikeEventContent.KeyVerificationCancel -> NotificationContent.MessageLike.KeyVerificationCancel MessageLikeEventContent.KeyVerificationDone -> NotificationContent.MessageLike.KeyVerificationDone @@ -97,3 +100,8 @@ private fun MessageLikeEventContent.toContent(senderId: UserId): NotificationCon } } } + +private fun NotifyType.map(): CallNotifyType = when (this) { + NotifyType.NOTIFY -> CallNotifyType.NOTIFY + NotifyType.RING -> CallNotifyType.RING +} diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RoomSyncSubscriber.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RoomSyncSubscriber.kt index 1b6b3648441..771bf596803 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RoomSyncSubscriber.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RoomSyncSubscriber.kt @@ -41,7 +41,8 @@ class RoomSyncSubscriber( RequiredState(key = EventType.STATE_ROOM_JOIN_RULES, value = ""), RequiredState(key = EventType.STATE_ROOM_POWER_LEVELS, value = ""), ), - timelineLimit = null + timelineLimit = null, + includeHeroes = true, ) suspend fun subscribe(roomId: RoomId) = mutex.withLock { diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/DefaultNotifiableEventResolver.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/DefaultNotifiableEventResolver.kt index 0ab01937f4b..78414fddd1a 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/DefaultNotifiableEventResolver.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/DefaultNotifiableEventResolver.kt @@ -150,8 +150,10 @@ class DefaultNotifiableEventResolver @Inject constructor( } NotificationContent.MessageLike.CallAnswer, NotificationContent.MessageLike.CallCandidates, - NotificationContent.MessageLike.CallHangup -> null.also { + NotificationContent.MessageLike.CallHangup, + is NotificationContent.MessageLike.CallNotify -> { // TODO CallNotify will be handled separately in the future Timber.tag(loggerTag.value).d("Ignoring notification for call ${content.javaClass.simpleName}") + null } is NotificationContent.MessageLike.CallInvite -> { buildNotifiableMessageEvent( From 32e160997a4a787c10ad401e1d27e20075c6f679 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= Date: Wed, 29 May 2024 16:13:39 +0200 Subject: [PATCH 48/60] Add QR code login FF enabled only for debug and nightly builds --- .../io/element/android/appconfig/OnBoardingConfig.kt | 2 +- features/onboarding/impl/build.gradle.kts | 5 +++-- .../features/onboarding/impl/OnBoardingPresenter.kt | 12 ++++++++++-- .../libraries/featureflag/api/FeatureFlags.kt | 7 +++++++ libraries/featureflag/impl/build.gradle.kts | 1 + .../featureflag/impl/DefaultFeatureFlagService.kt | 2 +- .../featureflag/impl/StaticFeatureFlagProvider.kt | 2 ++ 7 files changed, 25 insertions(+), 6 deletions(-) diff --git a/appconfig/src/main/kotlin/io/element/android/appconfig/OnBoardingConfig.kt b/appconfig/src/main/kotlin/io/element/android/appconfig/OnBoardingConfig.kt index dd30e20b8dd..e2a78fc5227 100644 --- a/appconfig/src/main/kotlin/io/element/android/appconfig/OnBoardingConfig.kt +++ b/appconfig/src/main/kotlin/io/element/android/appconfig/OnBoardingConfig.kt @@ -18,7 +18,7 @@ package io.element.android.appconfig object OnBoardingConfig { /** Whether the user can use QR code login. */ - const val CAN_LOGIN_WITH_QR_CODE = true + const val CAN_LOGIN_WITH_QR_CODE = false /** Whether the user can create an account using the app. */ const val CAN_CREATE_ACCOUNT = false diff --git a/features/onboarding/impl/build.gradle.kts b/features/onboarding/impl/build.gradle.kts index 0e08435cba4..6f02b9a7327 100644 --- a/features/onboarding/impl/build.gradle.kts +++ b/features/onboarding/impl/build.gradle.kts @@ -40,12 +40,13 @@ dependencies { anvil(projects.anvilcodegen) implementation(projects.appconfig) implementation(projects.libraries.core) + implementation(projects.libraries.androidutils) implementation(projects.libraries.architecture) - implementation(projects.libraries.matrix.api) implementation(projects.libraries.designsystem) + implementation(projects.libraries.featureflag.api) + implementation(projects.libraries.matrix.api) implementation(projects.libraries.testtags) implementation(projects.libraries.uiStrings) - implementation(projects.libraries.androidutils) api(projects.features.onboarding.api) ksp(libs.showkase.processor) diff --git a/features/onboarding/impl/src/main/kotlin/io/element/android/features/onboarding/impl/OnBoardingPresenter.kt b/features/onboarding/impl/src/main/kotlin/io/element/android/features/onboarding/impl/OnBoardingPresenter.kt index de3dc96e420..ed7200310f6 100644 --- a/features/onboarding/impl/src/main/kotlin/io/element/android/features/onboarding/impl/OnBoardingPresenter.kt +++ b/features/onboarding/impl/src/main/kotlin/io/element/android/features/onboarding/impl/OnBoardingPresenter.kt @@ -17,10 +17,14 @@ package io.element.android.features.onboarding.impl import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.produceState import io.element.android.appconfig.OnBoardingConfig import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.core.meta.BuildMeta import io.element.android.libraries.core.meta.BuildType +import io.element.android.libraries.featureflag.api.FeatureFlagService +import io.element.android.libraries.featureflag.api.FeatureFlags import javax.inject.Inject /** @@ -29,13 +33,17 @@ import javax.inject.Inject */ class OnBoardingPresenter @Inject constructor( private val buildMeta: BuildMeta, + private val featureFlagService: FeatureFlagService, ) : Presenter { @Composable override fun present(): OnBoardingState { - return OnBoardingState( + val canLoginWithQrCode by produceState(initialValue = false) { + value = featureFlagService.isFeatureEnabled(FeatureFlags.QrCodeLogin) + } + return OnBoardingState( isDebugBuild = buildMeta.buildType != BuildType.RELEASE, productionApplicationName = buildMeta.productionApplicationName, - canLoginWithQrCode = OnBoardingConfig.CAN_LOGIN_WITH_QR_CODE, + canLoginWithQrCode = canLoginWithQrCode, canCreateAccount = OnBoardingConfig.CAN_CREATE_ACCOUNT, ) } diff --git a/libraries/featureflag/api/src/main/kotlin/io/element/android/libraries/featureflag/api/FeatureFlags.kt b/libraries/featureflag/api/src/main/kotlin/io/element/android/libraries/featureflag/api/FeatureFlags.kt index 1a0602fd154..5c3bd8efd4d 100644 --- a/libraries/featureflag/api/src/main/kotlin/io/element/android/libraries/featureflag/api/FeatureFlags.kt +++ b/libraries/featureflag/api/src/main/kotlin/io/element/android/libraries/featureflag/api/FeatureFlags.kt @@ -89,4 +89,11 @@ enum class FeatureFlags( defaultValue = false, isFinished = false, ), + QrCodeLogin( + key = "feature.qrCodeLogin", + title = "Enable login using QR code", + description = "Allow the user to login using the QR code flow", + defaultValue = true, + isFinished = false, + ), } diff --git a/libraries/featureflag/impl/build.gradle.kts b/libraries/featureflag/impl/build.gradle.kts index 64fcd1eece9..76b6836eb3c 100644 --- a/libraries/featureflag/impl/build.gradle.kts +++ b/libraries/featureflag/impl/build.gradle.kts @@ -33,6 +33,7 @@ dependencies { api(projects.libraries.featureflag.api) implementation(libs.dagger) implementation(libs.androidx.datastore.preferences) + implementation(projects.appconfig) implementation(projects.libraries.di) implementation(projects.libraries.core) implementation(libs.coroutines.core) diff --git a/libraries/featureflag/impl/src/main/kotlin/io/element/android/libraries/featureflag/impl/DefaultFeatureFlagService.kt b/libraries/featureflag/impl/src/main/kotlin/io/element/android/libraries/featureflag/impl/DefaultFeatureFlagService.kt index ac5e0d56cfe..0a6b97e13d1 100644 --- a/libraries/featureflag/impl/src/main/kotlin/io/element/android/libraries/featureflag/impl/DefaultFeatureFlagService.kt +++ b/libraries/featureflag/impl/src/main/kotlin/io/element/android/libraries/featureflag/impl/DefaultFeatureFlagService.kt @@ -39,7 +39,7 @@ class DefaultFeatureFlagService @Inject constructor( } override suspend fun setFeatureEnabled(feature: Feature, enabled: Boolean): Boolean { - return providers.filterIsInstance(MutableFeatureFlagProvider::class.java) + return providers.filterIsInstance() .sortedBy(FeatureFlagProvider::priority) .firstOrNull() ?.setFeatureEnabled(feature, enabled) diff --git a/libraries/featureflag/impl/src/main/kotlin/io/element/android/libraries/featureflag/impl/StaticFeatureFlagProvider.kt b/libraries/featureflag/impl/src/main/kotlin/io/element/android/libraries/featureflag/impl/StaticFeatureFlagProvider.kt index d8b485871a1..139877500bb 100644 --- a/libraries/featureflag/impl/src/main/kotlin/io/element/android/libraries/featureflag/impl/StaticFeatureFlagProvider.kt +++ b/libraries/featureflag/impl/src/main/kotlin/io/element/android/libraries/featureflag/impl/StaticFeatureFlagProvider.kt @@ -16,6 +16,7 @@ package io.element.android.libraries.featureflag.impl +import io.element.android.appconfig.OnBoardingConfig import io.element.android.libraries.featureflag.api.Feature import io.element.android.libraries.featureflag.api.FeatureFlags import kotlinx.coroutines.flow.Flow @@ -42,6 +43,7 @@ class StaticFeatureFlagProvider @Inject constructor() : FeatureFlags.MarkAsUnread -> true FeatureFlags.RoomDirectorySearch -> false FeatureFlags.ShowBlockedUsersDetails -> false + FeatureFlags.QrCodeLogin -> OnBoardingConfig.CAN_LOGIN_WITH_QR_CODE } } else { false From 53f5f09060df105a87b70d5599cf3c299b35597b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= Date: Wed, 29 May 2024 16:14:24 +0200 Subject: [PATCH 49/60] Add new `QrLoginException.OtherDeviceNotSignedIn` and remove `QrLoginException.InvalidQrCode` --- .../impl/src/main/res/values/localazy.xml | 2 ++ .../login/impl/qrcode/QrCodeLoginFlowNode.kt | 2 +- .../qrcode/scan/QrCodeScanPresenter.kt | 8 ++--- .../qrcode/scan/QrCodeScanStateProvider.kt | 2 ++ .../screens/qrcode/scan/QrCodeScanView.kt | 14 +++++++-- .../impl/src/main/res/values/localazy.xml | 2 ++ .../impl/qrcode/QrCodeLoginFlowNodeTest.kt | 9 +++--- .../qrcode/scan/QrCodeScanPresenterTest.kt | 4 +-- .../api/auth/qrlogin/QrCodeDecodeException.kt | 24 ++++++++------- .../api/auth/qrlogin/QrLoginException.kt | 2 +- .../matrix/impl/auth/qrlogin/QrErrorMapper.kt | 30 +++++++++++-------- 11 files changed, 62 insertions(+), 37 deletions(-) diff --git a/features/ftue/impl/src/main/res/values/localazy.xml b/features/ftue/impl/src/main/res/values/localazy.xml index 4038f21908d..4a48b517da4 100644 --- a/features/ftue/impl/src/main/res/values/localazy.xml +++ b/features/ftue/impl/src/main/res/values/localazy.xml @@ -11,6 +11,8 @@ "Connection not secure" "You’ll be asked to enter the two digits shown on this device." "Enter the number below on your other device" + "Sign in to your other device and then try again, or use another device that’s already signed in." + "Other device not signed in" "The sign in was cancelled on the other device." "Sign in request cancelled" "The request on your other device was not accepted." diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/qrcode/QrCodeLoginFlowNode.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/qrcode/QrCodeLoginFlowNode.kt index 6f04bf57a9e..4d5f0e6a95a 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/qrcode/QrCodeLoginFlowNode.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/qrcode/QrCodeLoginFlowNode.kt @@ -116,7 +116,7 @@ class QrCodeLoginFlowNode @AssistedInject constructor( } is QrCodeLoginStep.Failed -> { when (val error = step.error) { - is QrLoginException.InvalidQrCode -> { + is QrLoginException.OtherDeviceNotSignedIn -> { // Do nothing here, it'll be handled in the scan QR screen } is QrLoginException.Cancelled -> { 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 444c84dd38e..aeedc32542b 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 @@ -80,12 +80,12 @@ class QrCodeScanPresenter @Inject constructor( } @Composable - private fun ObserveQRCodeLoginFailures(onInvalidQrCodeError: (QrLoginException) -> Unit) { - LaunchedEffect(onInvalidQrCodeError) { + private fun ObserveQRCodeLoginFailures(onQrCodeLoginError: (QrLoginException) -> Unit) { + LaunchedEffect(onQrCodeLoginError) { qrCodeLoginManager.currentLoginStep .onEach { state -> - if (state is QrCodeLoginStep.Failed && state.error is QrLoginException.InvalidQrCode) { - onInvalidQrCodeError(state.error) + if (state is QrCodeLoginStep.Failed) { + onQrCodeLoginError(state.error) // The error was handled here, reset the login state qrCodeLoginManager.reset() } 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 699fd233501..764c46643a4 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 @@ -19,6 +19,7 @@ package io.element.android.features.login.impl.screens.qrcode.scan import androidx.compose.ui.tooling.preview.PreviewParameterProvider 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 open class QrCodeScanStateProvider : PreviewParameterProvider { override val values: Sequence @@ -26,6 +27,7 @@ open class QrCodeScanStateProvider : PreviewParameterProvider { aQrCodeScanState(), aQrCodeScanState(isScanning = false, authenticationAction = AsyncAction.Loading), aQrCodeScanState(isScanning = false, authenticationAction = AsyncAction.Failure(Exception("Error"))), + aQrCodeScanState(isScanning = false, authenticationAction = AsyncAction.Failure(QrLoginException.OtherDeviceNotSignedIn)), // 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 da3880651c4..008d470db52 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 @@ -57,6 +57,7 @@ import io.element.android.libraries.designsystem.theme.components.CircularProgre import io.element.android.libraries.designsystem.theme.components.Icon import io.element.android.libraries.designsystem.theme.components.Text 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.qrcode.QrCodeCameraView @Composable @@ -140,6 +141,7 @@ private fun ColumnScope.Buttons( horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy(2.dp), ) { + val error = state.authenticationAction.error Row( verticalAlignment = Alignment.CenterVertically, ) { @@ -151,14 +153,22 @@ private fun ColumnScope.Buttons( ) Spacer(modifier = Modifier.width(4.dp)) Text( - text = stringResource(R.string.screen_qr_code_login_invalid_scan_state_subtitle), + text = when (error) { + is QrLoginException.OtherDeviceNotSignedIn -> { + stringResource(R.string.screen_qr_code_login_device_not_signed_in_scan_state_title) + } + else -> stringResource(R.string.screen_qr_code_login_invalid_scan_state_subtitle) + }, textAlign = TextAlign.Center, color = MaterialTheme.colorScheme.error, style = ElementTheme.typography.fontBodySmMedium, ) } Text( - text = stringResource(R.string.screen_qr_code_login_invalid_scan_state_description), + text = when (error) { + is QrLoginException.OtherDeviceNotSignedIn -> stringResource(R.string.screen_qr_code_login_device_not_signed_in_scan_state_subtitle) + else -> stringResource(R.string.screen_qr_code_login_invalid_scan_state_description) + }, textAlign = TextAlign.Center, style = ElementTheme.typography.fontBodySmRegular, color = ElementTheme.colors.textSecondary, diff --git a/features/login/impl/src/main/res/values/localazy.xml b/features/login/impl/src/main/res/values/localazy.xml index bccc840afdd..1192bbeb4c3 100644 --- a/features/login/impl/src/main/res/values/localazy.xml +++ b/features/login/impl/src/main/res/values/localazy.xml @@ -39,6 +39,8 @@ "Connection not secure" "You’ll be asked to enter the two digits shown on this device." "Enter the number below on your other device" + "Sign in to your other device and then try again, or use another device that’s already signed in." + "Other device not signed in" "The sign in was cancelled on the other device." "Sign in request cancelled" "The request on your other device was not accepted." diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/qrcode/QrCodeLoginFlowNodeTest.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/qrcode/QrCodeLoginFlowNodeTest.kt index b542c99ced3..33fac53c833 100644 --- a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/qrcode/QrCodeLoginFlowNodeTest.kt +++ b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/qrcode/QrCodeLoginFlowNodeTest.kt @@ -29,6 +29,7 @@ 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.A_SESSION_ID import io.element.android.libraries.matrix.test.auth.FakeAuthenticationService +import io.element.android.libraries.matrix.test.auth.FakeMatrixAuthenticationService import io.element.android.libraries.matrix.test.auth.qrlogin.FakeQrCodeLoginData import io.element.android.tests.testutils.testCoroutineDispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -63,7 +64,7 @@ class QrCodeLoginFlowNodeTest { assertThat(flowNode.currentNavTarget()).isEqualTo(QrCodeLoginFlowNode.NavTarget.Initial) // Only case when this doesn't happen, since it's handled by the already displayed UI - qrCodeLoginManager.currentLoginStep.value = QrCodeLoginStep.Failed(QrLoginException.InvalidQrCode) + qrCodeLoginManager.currentLoginStep.value = QrCodeLoginStep.Failed(QrLoginException.OtherDeviceNotSignedIn) assertThat(flowNode.currentNavTarget()).isEqualTo(QrCodeLoginFlowNode.NavTarget.Initial) qrCodeLoginManager.currentLoginStep.value = QrCodeLoginStep.Failed(QrLoginException.Expired) @@ -108,7 +109,7 @@ class QrCodeLoginFlowNodeTest { @OptIn(ExperimentalCoroutinesApi::class) @Test fun `startAuthentication - success marks the login flow as done`() = runTest { - val fakeAuthenticationService = FakeAuthenticationService( + val fakeAuthenticationService = FakeMatrixAuthenticationService( loginWithQrCodeResult = { _, progress -> progress(QrCodeLoginStep.Finished) Result.success(A_SESSION_ID) @@ -138,7 +139,7 @@ class QrCodeLoginFlowNodeTest { @OptIn(ExperimentalCoroutinesApi::class) @Test fun `startAuthentication - failure is correctly handled`() = runTest { - val fakeAuthenticationService = FakeAuthenticationService( + val fakeAuthenticationService = FakeMatrixAuthenticationService( loginWithQrCodeResult = { _, progress -> progress(QrCodeLoginStep.Failed(QrLoginException.Unknown)) Result.failure(IllegalStateException("Failed")) @@ -168,7 +169,7 @@ class QrCodeLoginFlowNodeTest { @OptIn(ExperimentalCoroutinesApi::class) @Test fun `startAuthentication - then reset`() = runTest { - val fakeAuthenticationService = FakeAuthenticationService( + val fakeAuthenticationService = FakeMatrixAuthenticationService( loginWithQrCodeResult = { _, progress -> progress(QrCodeLoginStep.Finished) Result.success(A_SESSION_ID) 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 7bc351b2834..df79a51a41d 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 @@ -83,7 +83,7 @@ class QrCodeScanPresenterTest { } @Test - fun `present - login failed with invalid QR format and we display it and recover from it`() = runTest { + fun `present - login failed with so we display the error and recover from it`() = runTest { val qrCodeLoginDataFactory = FakeMatrixQrCodeLoginDataFactory() val qrCodeLoginManager = FakeQrCodeLoginManager() val resetAction = lambdaRecorder { @@ -97,7 +97,7 @@ class QrCodeScanPresenterTest { // Skip initial item skipItems(1) - qrCodeLoginManager.currentLoginStep.value = QrCodeLoginStep.Failed(QrLoginException.InvalidQrCode) + qrCodeLoginManager.currentLoginStep.value = QrCodeLoginStep.Failed(QrLoginException.OtherDeviceNotSignedIn) val errorState = awaitItem() // The state for this screen is failure diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/qrlogin/QrCodeDecodeException.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/qrlogin/QrCodeDecodeException.kt index 26f7da59c3e..a0719fa8f0b 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/qrlogin/QrCodeDecodeException.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/qrlogin/QrCodeDecodeException.kt @@ -17,15 +17,19 @@ package io.element.android.libraries.matrix.api.auth.qrlogin sealed class QrCodeDecodeException(message: String) : Exception(message) { - class Crypto(message: String, val reason: Reason) : QrCodeDecodeException(message) { - enum class Reason { - NOT_ENOUGH_DATA, - NOT_UTF8, - URL_PARSE, - INVALID_MODE, - INVALID_VERSION, - BASE64, - INVALID_PREFIX - } + class Crypto( + message: String, +// val reason: Reason + ) : QrCodeDecodeException(message) { + // We plan to restore it in the future when UniFFi can process them +// enum class Reason { +// NOT_ENOUGH_DATA, +// NOT_UTF8, +// URL_PARSE, +// INVALID_MODE, +// INVALID_VERSION, +// BASE64, +// INVALID_PREFIX +// } } } diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/qrlogin/QrLoginException.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/qrlogin/QrLoginException.kt index 034c2c2b6f5..0a31a46236f 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/qrlogin/QrLoginException.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/qrlogin/QrLoginException.kt @@ -21,9 +21,9 @@ sealed class QrLoginException : Exception() { data object ConnectionInsecure : QrLoginException() data object Declined : QrLoginException() data object Expired : QrLoginException() - data object InvalidQrCode : QrLoginException() data object LinkingNotSupported : QrLoginException() data object OidcMetadataInvalid : QrLoginException() data object SlidingSyncNotAvailable : QrLoginException() + data object OtherDeviceNotSignedIn : QrLoginException() data object Unknown : QrLoginException() } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/qrlogin/QrErrorMapper.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/qrlogin/QrErrorMapper.kt index 137bcfff210..739c984557f 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/qrlogin/QrErrorMapper.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/qrlogin/QrErrorMapper.kt @@ -26,16 +26,20 @@ import org.matrix.rustcomponents.sdk.QrCodeDecodeException as RustQrCodeDecodeEx object QrErrorMapper { fun map(qrCodeDecodeException: RustQrCodeDecodeException): QrCodeDecodeException = when (qrCodeDecodeException) { is RustQrCodeDecodeException.Crypto -> { - val reason = when (qrCodeDecodeException.error) { - LoginQrCodeDecodeError.NOT_ENOUGH_DATA -> QrCodeDecodeException.Crypto.Reason.NOT_ENOUGH_DATA - LoginQrCodeDecodeError.NOT_UTF8 -> QrCodeDecodeException.Crypto.Reason.NOT_UTF8 - LoginQrCodeDecodeError.URL_PARSE -> QrCodeDecodeException.Crypto.Reason.URL_PARSE - LoginQrCodeDecodeError.INVALID_MODE -> QrCodeDecodeException.Crypto.Reason.INVALID_MODE - LoginQrCodeDecodeError.INVALID_VERSION -> QrCodeDecodeException.Crypto.Reason.INVALID_VERSION - LoginQrCodeDecodeError.BASE64 -> QrCodeDecodeException.Crypto.Reason.BASE64 - LoginQrCodeDecodeError.INVALID_PREFIX -> QrCodeDecodeException.Crypto.Reason.INVALID_PREFIX - } - QrCodeDecodeException.Crypto(qrCodeDecodeException.message, reason) + // We plan to restore it in the future when UniFFi can process them +// val reason = when (qrCodeDecodeException.error) { +// LoginQrCodeDecodeError.NOT_ENOUGH_DATA -> QrCodeDecodeException.Crypto.Reason.NOT_ENOUGH_DATA +// LoginQrCodeDecodeError.NOT_UTF8 -> QrCodeDecodeException.Crypto.Reason.NOT_UTF8 +// LoginQrCodeDecodeError.URL_PARSE -> QrCodeDecodeException.Crypto.Reason.URL_PARSE +// LoginQrCodeDecodeError.INVALID_MODE -> QrCodeDecodeException.Crypto.Reason.INVALID_MODE +// LoginQrCodeDecodeError.INVALID_VERSION -> QrCodeDecodeException.Crypto.Reason.INVALID_VERSION +// LoginQrCodeDecodeError.BASE64 -> QrCodeDecodeException.Crypto.Reason.BASE64 +// LoginQrCodeDecodeError.INVALID_PREFIX -> QrCodeDecodeException.Crypto.Reason.INVALID_PREFIX +// } + QrCodeDecodeException.Crypto( + qrCodeDecodeException.message.orEmpty(), +// reason + ) } } @@ -44,10 +48,10 @@ object QrErrorMapper { is RustHumanQrLoginException.ConnectionInsecure -> QrLoginException.ConnectionInsecure is RustHumanQrLoginException.Declined -> QrLoginException.Declined is RustHumanQrLoginException.Expired -> QrLoginException.Expired - is RustHumanQrLoginException.InvalidQrCode -> QrLoginException.InvalidQrCode + is RustHumanQrLoginException.OtherDeviceNotSignedIn -> QrLoginException.OtherDeviceNotSignedIn is RustHumanQrLoginException.LinkingNotSupported -> QrLoginException.LinkingNotSupported is RustHumanQrLoginException.Unknown -> QrLoginException.Unknown - is HumanQrLoginException.OidcMetadataInvalid -> QrLoginException.OidcMetadataInvalid - is HumanQrLoginException.SlidingSyncNotAvailable -> QrLoginException.SlidingSyncNotAvailable + is RustHumanQrLoginException.OidcMetadataInvalid -> QrLoginException.OidcMetadataInvalid + is RustHumanQrLoginException.SlidingSyncNotAvailable -> QrLoginException.SlidingSyncNotAvailable } } From bfd27ed8167d8fa150250253b0fc40ac080452ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= Date: Wed, 29 May 2024 18:10:33 +0200 Subject: [PATCH 50/60] Bump SDK, fix tests and lint issues --- .../qrcode/DefaultQrCodeLoginManagerTest.kt | 16 ++++++++-------- .../impl/qrcode/QrCodeLoginFlowNodeTest.kt | 9 ++++----- .../screens/qrcode/scan/QrCodeScanViewTest.kt | 4 ++-- features/onboarding/impl/build.gradle.kts | 1 + .../onboarding/impl/OnBoardingPresenterTest.kt | 17 +++++++++++++---- gradle/libs.versions.toml | 2 +- .../designsystem/modifiers/CornerBorder.kt | 1 + .../matrix/impl/auth/qrlogin/QrErrorMapper.kt | 2 -- .../qrlogin/FakeMatrixQrCodeLoginDataFactory.kt | 4 ++-- 9 files changed, 32 insertions(+), 24 deletions(-) diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/qrcode/DefaultQrCodeLoginManagerTest.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/qrcode/DefaultQrCodeLoginManagerTest.kt index 21e985c4f3a..a3ad568cf57 100644 --- a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/qrcode/DefaultQrCodeLoginManagerTest.kt +++ b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/qrcode/DefaultQrCodeLoginManagerTest.kt @@ -20,19 +20,19 @@ import app.cash.turbine.test import com.google.common.truth.Truth.assertThat import io.element.android.libraries.matrix.api.auth.qrlogin.QrCodeLoginStep import io.element.android.libraries.matrix.test.A_SESSION_ID -import io.element.android.libraries.matrix.test.auth.FakeAuthenticationService -import io.element.android.libraries.matrix.test.auth.qrlogin.FakeQrCodeLoginData +import io.element.android.libraries.matrix.test.auth.FakeMatrixAuthenticationService +import io.element.android.libraries.matrix.test.auth.qrlogin.FakeMatrixQrCodeLoginData import kotlinx.coroutines.test.runTest import org.junit.Test class DefaultQrCodeLoginManagerTest { @Test fun `authenticate - returns success if the login succeeded`() = runTest { - val authenticationService = FakeAuthenticationService( + val authenticationService = FakeMatrixAuthenticationService( loginWithQrCodeResult = { _, _ -> Result.success(A_SESSION_ID) } ) val manager = DefaultQrCodeLoginManager(authenticationService) - val result = manager.authenticate(FakeQrCodeLoginData()) + val result = manager.authenticate(FakeMatrixQrCodeLoginData()) assertThat(result.isSuccess).isTrue() assertThat(result.getOrNull()).isEqualTo(A_SESSION_ID) @@ -40,11 +40,11 @@ class DefaultQrCodeLoginManagerTest { @Test fun `authenticate - returns failure if the login failed`() = runTest { - val authenticationService = FakeAuthenticationService( + val authenticationService = FakeMatrixAuthenticationService( loginWithQrCodeResult = { _, _ -> Result.failure(IllegalStateException("Auth failed")) } ) val manager = DefaultQrCodeLoginManager(authenticationService) - val result = manager.authenticate(FakeQrCodeLoginData()) + val result = manager.authenticate(FakeMatrixQrCodeLoginData()) assertThat(result.isFailure).isTrue() assertThat(result.exceptionOrNull()).isNotNull() @@ -52,7 +52,7 @@ class DefaultQrCodeLoginManagerTest { @Test fun `authenticate - emits the auth steps`() = runTest { - val authenticationService = FakeAuthenticationService( + val authenticationService = FakeMatrixAuthenticationService( loginWithQrCodeResult = { _, progressListener -> progressListener(QrCodeLoginStep.EstablishingSecureChannel("00")) progressListener(QrCodeLoginStep.Starting) @@ -63,7 +63,7 @@ class DefaultQrCodeLoginManagerTest { ) val manager = DefaultQrCodeLoginManager(authenticationService) manager.currentLoginStep.test { - manager.authenticate(FakeQrCodeLoginData()) + manager.authenticate(FakeMatrixQrCodeLoginData()) assertThat(awaitItem()).isEqualTo(QrCodeLoginStep.Uninitialized) assertThat(awaitItem()).isEqualTo(QrCodeLoginStep.EstablishingSecureChannel("00")) diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/qrcode/QrCodeLoginFlowNodeTest.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/qrcode/QrCodeLoginFlowNodeTest.kt index 33fac53c833..f53e9c74f74 100644 --- a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/qrcode/QrCodeLoginFlowNodeTest.kt +++ b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/qrcode/QrCodeLoginFlowNodeTest.kt @@ -28,9 +28,8 @@ 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.A_SESSION_ID -import io.element.android.libraries.matrix.test.auth.FakeAuthenticationService import io.element.android.libraries.matrix.test.auth.FakeMatrixAuthenticationService -import io.element.android.libraries.matrix.test.auth.qrlogin.FakeQrCodeLoginData +import io.element.android.libraries.matrix.test.auth.qrlogin.FakeMatrixQrCodeLoginData import io.element.android.tests.testutils.testCoroutineDispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.TestScope @@ -126,7 +125,7 @@ class QrCodeLoginFlowNodeTest { coroutineDispatchers = testCoroutineDispatchers(useUnconfinedTestDispatcher = true) ) - flowNode.run { startAuthentication(FakeQrCodeLoginData()) } + flowNode.run { startAuthentication(FakeMatrixQrCodeLoginData()) } assertThat(flowNode.isLoginInProgress()).isTrue() advanceUntilIdle() @@ -156,7 +155,7 @@ class QrCodeLoginFlowNodeTest { coroutineDispatchers = testCoroutineDispatchers(useUnconfinedTestDispatcher = true) ) - flowNode.run { startAuthentication(FakeQrCodeLoginData()) } + flowNode.run { startAuthentication(FakeMatrixQrCodeLoginData()) } assertThat(flowNode.isLoginInProgress()).isTrue() advanceUntilIdle() @@ -186,7 +185,7 @@ class QrCodeLoginFlowNodeTest { coroutineDispatchers = testCoroutineDispatchers(useUnconfinedTestDispatcher = true) ) - flowNode.run { startAuthentication(FakeQrCodeLoginData()) } + flowNode.run { startAuthentication(FakeMatrixQrCodeLoginData()) } assertThat(flowNode.isLoginInProgress()).isTrue() flowNode.reset() diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/qrcode/scan/QrCodeScanViewTest.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/qrcode/scan/QrCodeScanViewTest.kt index 7f0c97b425a..cd5a3fdfa67 100644 --- a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/qrcode/scan/QrCodeScanViewTest.kt +++ b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/qrcode/scan/QrCodeScanViewTest.kt @@ -22,7 +22,7 @@ import androidx.compose.ui.test.junit4.createAndroidComposeRule import androidx.test.ext.junit.runners.AndroidJUnit4 import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.matrix.api.auth.qrlogin.MatrixQrCodeLoginData -import io.element.android.libraries.matrix.test.auth.qrlogin.FakeQrCodeLoginData +import io.element.android.libraries.matrix.test.auth.qrlogin.FakeMatrixQrCodeLoginData import io.element.android.tests.testutils.EnsureNeverCalled import io.element.android.tests.testutils.EnsureNeverCalledWithParam import io.element.android.tests.testutils.ensureCalledOnce @@ -51,7 +51,7 @@ class QrCodeScanViewTest { @Test fun `on QR code data ready - calls the expected callback`() { - val data = FakeQrCodeLoginData() + val data = FakeMatrixQrCodeLoginData() ensureCalledOnceWithParam(data) { callback -> rule.setQrCodeScanView( state = aQrCodeScanState(authenticationAction = AsyncAction.Success(data)), diff --git a/features/onboarding/impl/build.gradle.kts b/features/onboarding/impl/build.gradle.kts index 6f02b9a7327..a8c7b0db00f 100644 --- a/features/onboarding/impl/build.gradle.kts +++ b/features/onboarding/impl/build.gradle.kts @@ -59,6 +59,7 @@ dependencies { testImplementation(libs.test.truth) testImplementation(libs.test.turbine) testImplementation(projects.libraries.matrix.test) + testImplementation(projects.libraries.featureflag.test) testImplementation(projects.tests.testutils) testReleaseImplementation(libs.androidx.compose.ui.test.manifest) } diff --git a/features/onboarding/impl/src/test/kotlin/io/element/android/features/onboarding/impl/OnBoardingPresenterTest.kt b/features/onboarding/impl/src/test/kotlin/io/element/android/features/onboarding/impl/OnBoardingPresenterTest.kt index c54f6f463f2..651d8c5cf44 100644 --- a/features/onboarding/impl/src/test/kotlin/io/element/android/features/onboarding/impl/OnBoardingPresenterTest.kt +++ b/features/onboarding/impl/src/test/kotlin/io/element/android/features/onboarding/impl/OnBoardingPresenterTest.kt @@ -21,6 +21,8 @@ import app.cash.molecule.moleculeFlow import app.cash.turbine.test import com.google.common.truth.Truth.assertThat import io.element.android.libraries.core.meta.BuildType +import io.element.android.libraries.featureflag.api.FeatureFlags +import io.element.android.libraries.featureflag.test.FakeFeatureFlagService import io.element.android.libraries.matrix.test.core.aBuildMeta import io.element.android.tests.testutils.WarmUpRule import kotlinx.coroutines.test.runTest @@ -34,31 +36,38 @@ class OnBoardingPresenterTest { @Test fun `present - initial state`() = runTest { val presenter = OnBoardingPresenter( - aBuildMeta( + buildMeta = aBuildMeta( applicationName = "A", productionApplicationName = "B", desktopApplicationName = "C", - ) + ), + featureFlagService = FakeFeatureFlagService(initialState = mapOf(FeatureFlags.QrCodeLogin.name to true)), ) moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() assertThat(initialState.isDebugBuild).isTrue() - assertThat(initialState.canLoginWithQrCode).isTrue() + assertThat(initialState.canLoginWithQrCode).isFalse() assertThat(initialState.productionApplicationName).isEqualTo("B") assertThat(initialState.canCreateAccount).isFalse() + + assertThat(awaitItem().canLoginWithQrCode).isTrue() } } @Test fun `present - initial state release`() = runTest { - val presenter = OnBoardingPresenter(aBuildMeta(buildType = BuildType.RELEASE)) + val presenter = OnBoardingPresenter( + buildMeta = aBuildMeta(buildType = BuildType.RELEASE), + featureFlagService = FakeFeatureFlagService(), + ) moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() assertThat(initialState.isDebugBuild).isFalse() + cancelAndIgnoreRemainingEvents() } } } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 39f84b5c7f6..1ad12532648 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -162,7 +162,7 @@ jsoup = "org.jsoup:jsoup:1.17.2" appyx_core = { module = "com.bumble.appyx:core", version.ref = "appyx" } molecule-runtime = "app.cash.molecule:molecule-runtime:1.4.3" timber = "com.jakewharton.timber:timber:5.0.1" -matrix_sdk = "org.matrix.rustcomponents:sdk-android:0.2.21" +matrix_sdk = "org.matrix.rustcomponents:sdk-android:0.2.22" matrix_richtexteditor = { module = "io.element.android:wysiwyg", version.ref = "wysiwyg" } matrix_richtexteditor_compose = { module = "io.element.android:wysiwyg-compose", version.ref = "wysiwyg" } sqldelight-driver-android = { module = "app.cash.sqldelight:android-driver", version.ref = "sqldelight" } diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/modifiers/CornerBorder.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/modifiers/CornerBorder.kt index 51bb4dd2a2f..b8d60f9acfd 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/modifiers/CornerBorder.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/modifiers/CornerBorder.kt @@ -30,6 +30,7 @@ import io.element.android.libraries.designsystem.text.toPx /** * Draw a border on corners around the content. */ +@Suppress("ModifierComposed") fun Modifier.cornerBorder( strokeWidth: Dp, color: Color, diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/qrlogin/QrErrorMapper.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/qrlogin/QrErrorMapper.kt index 739c984557f..e55b662b724 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/qrlogin/QrErrorMapper.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/qrlogin/QrErrorMapper.kt @@ -18,8 +18,6 @@ package io.element.android.libraries.matrix.impl.auth.qrlogin import io.element.android.libraries.matrix.api.auth.qrlogin.QrCodeDecodeException import io.element.android.libraries.matrix.api.auth.qrlogin.QrLoginException -import org.matrix.rustcomponents.sdk.HumanQrLoginException -import uniffi.matrix_sdk_crypto.LoginQrCodeDecodeError import org.matrix.rustcomponents.sdk.HumanQrLoginException as RustHumanQrLoginException import org.matrix.rustcomponents.sdk.QrCodeDecodeException as RustQrCodeDecodeException 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 99ad1d73ad4..6e542dd7399 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 @@ -22,11 +22,11 @@ import io.element.android.tests.testutils.lambda.lambdaRecorder class FakeMatrixQrCodeLoginDataFactory( var parseQrCodeLoginDataResult: () -> Result = - lambdaRecorder> { Result.success(FakeQrCodeLoginData()) }, + lambdaRecorder> { Result.success(FakeMatrixQrCodeLoginData()) }, ) : MatrixQrCodeLoginDataFactory { override fun parseQrCodeData(data: ByteArray): Result { return parseQrCodeLoginDataResult() } } -class FakeQrCodeLoginData : MatrixQrCodeLoginData +class FakeMatrixQrCodeLoginData : MatrixQrCodeLoginData From b81556f81a02d450ef9f39242421aa26ba45703d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= Date: Wed, 29 May 2024 18:23:26 +0200 Subject: [PATCH 51/60] Fix more lint issues --- .../login/impl/screens/qrcode/intro/QrCodeIntroNode.kt | 2 +- .../login/impl/screens/qrcode/intro/QrCodeIntroView.kt | 6 +++--- .../login/impl/screens/qrcode/scan/QrCodeScanNode.kt | 2 +- .../login/impl/screens/qrcode/scan/QrCodeScanView.kt | 8 ++++---- .../impl/screens/qrcode/intro/QrCodeIntroViewTest.kt | 2 +- .../login/impl/screens/qrcode/scan/QrCodeScanViewTest.kt | 6 +++--- .../io/element/android/libraries/qrcode/QRCodeAnalyzer.kt | 4 ++-- .../element/android/libraries/qrcode/QrCodeCameraView.kt | 4 ++-- 8 files changed, 17 insertions(+), 17 deletions(-) diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/intro/QrCodeIntroNode.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/intro/QrCodeIntroNode.kt index 105c5b35255..f9fc3085bb9 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/intro/QrCodeIntroNode.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/intro/QrCodeIntroNode.kt @@ -51,7 +51,7 @@ class QrCodeIntroNode @AssistedInject constructor( val state = presenter.present() QrCodeIntroView( state = state, - onBackClicked = ::onCancelClicked, + onBackClick = ::onCancelClicked, onContinue = ::onContinue, modifier = modifier ) diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/intro/QrCodeIntroView.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/intro/QrCodeIntroView.kt index 32dfebd7c97..12e8de01f06 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/intro/QrCodeIntroView.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/intro/QrCodeIntroView.kt @@ -45,7 +45,7 @@ import kotlinx.collections.immutable.persistentListOf @Composable fun QrCodeIntroView( state: QrCodeIntroState, - onBackClicked: () -> Unit, + onBackClick: () -> Unit, onContinue: () -> Unit, modifier: Modifier = Modifier, ) { @@ -57,7 +57,7 @@ fun QrCodeIntroView( } FlowStepPage( modifier = modifier, - onBackClicked = onBackClicked, + onBackClick = onBackClick, iconStyle = BigIcon.Style.Default(CompoundIcons.Computer()), title = stringResource(id = R.string.screen_qr_code_login_initial_state_title, state.desktopAppName), content = { Content(state = state) }, @@ -109,7 +109,7 @@ private fun ColumnScope.Buttons( internal fun QrCodeIntroViewPreview(@PreviewParameter(QrCodeIntroStateProvider::class) state: QrCodeIntroState) = ElementPreview { QrCodeIntroView( state = state, - onBackClicked = {}, + onBackClick = {}, onContinue = {}, ) } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/scan/QrCodeScanNode.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/scan/QrCodeScanNode.kt index d8f2f04f3e3..d2c7a418b0f 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/scan/QrCodeScanNode.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/scan/QrCodeScanNode.kt @@ -53,7 +53,7 @@ class QrCodeScanNode @AssistedInject constructor( QrCodeScanView( state = state, onQrCodeDataReady = ::onQrCodeDataReady, - onBackClicked = ::onCancelClicked, + onBackClick = ::onCancelClicked, modifier = modifier ) } 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 008d470db52..b1eea8fe7c5 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 @@ -63,7 +63,7 @@ import io.element.android.libraries.qrcode.QrCodeCameraView @Composable fun QrCodeScanView( state: QrCodeScanState, - onBackClicked: () -> Unit, + onBackClick: () -> Unit, onQrCodeDataReady: (MatrixQrCodeLoginData) -> Unit, modifier: Modifier = Modifier, ) { @@ -77,7 +77,7 @@ fun QrCodeScanView( FlowStepPage( modifier = modifier, - onBackClicked = onBackClicked, + onBackClick = onBackClick, iconStyle = BigIcon.Style.Default(CompoundIcons.Computer()), title = stringResource(R.string.screen_qr_code_login_scanning_state_title), content = { Content(state = state) }, @@ -113,7 +113,7 @@ private fun Content( ) { QrCodeCameraView( modifier = Modifier.fillMaxSize(), - onQrCodeScanned = { state.eventSink.invoke(QrCodeScanEvents.QrCodeScanned(it)) }, + onScanQrCode = { state.eventSink.invoke(QrCodeScanEvents.QrCodeScanned(it)) }, renderPreview = state.isScanning, ) } @@ -207,6 +207,6 @@ internal fun QrCodeScanViewPreview(@PreviewParameter(QrCodeScanStateProvider::cl QrCodeScanView( state = state, onQrCodeDataReady = {}, - onBackClicked = {}, + onBackClick = {}, ) } diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/qrcode/intro/QrCodeIntroViewTest.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/qrcode/intro/QrCodeIntroViewTest.kt index 21a030b5135..6e258a46023 100644 --- a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/qrcode/intro/QrCodeIntroViewTest.kt +++ b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/qrcode/intro/QrCodeIntroViewTest.kt @@ -87,7 +87,7 @@ class QrCodeIntroViewTest { setContent { QrCodeIntroView( state = state, - onBackClicked = onBackClicked, + onBackClick = onBackClicked, onContinue = onContinue, ) } diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/qrcode/scan/QrCodeScanViewTest.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/qrcode/scan/QrCodeScanViewTest.kt index cd5a3fdfa67..aece85f2de1 100644 --- a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/qrcode/scan/QrCodeScanViewTest.kt +++ b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/qrcode/scan/QrCodeScanViewTest.kt @@ -43,7 +43,7 @@ class QrCodeScanViewTest { ensureCalledOnce { callback -> rule.setQrCodeScanView( state = aQrCodeScanState(), - onBackClicked = callback + onBackClick = callback ) rule.pressBackKey() } @@ -62,13 +62,13 @@ class QrCodeScanViewTest { private fun AndroidComposeTestRule.setQrCodeScanView( state: QrCodeScanState, - onBackClicked: () -> Unit = EnsureNeverCalled(), + onBackClick: () -> Unit = EnsureNeverCalled(), onQrCodeDataReady: (MatrixQrCodeLoginData) -> Unit = EnsureNeverCalledWithParam(), ) { setContent { QrCodeScanView( state = state, - onBackClicked = onBackClicked, + onBackClick = onBackClick, onQrCodeDataReady = onQrCodeDataReady ) } diff --git a/libraries/qrcode/src/main/kotlin/io/element/android/libraries/qrcode/QRCodeAnalyzer.kt b/libraries/qrcode/src/main/kotlin/io/element/android/libraries/qrcode/QRCodeAnalyzer.kt index 81ea273851b..be83581642c 100644 --- a/libraries/qrcode/src/main/kotlin/io/element/android/libraries/qrcode/QRCodeAnalyzer.kt +++ b/libraries/qrcode/src/main/kotlin/io/element/android/libraries/qrcode/QRCodeAnalyzer.kt @@ -23,7 +23,7 @@ import timber.log.Timber import zxingcpp.BarcodeReader internal class QRCodeAnalyzer( - private val onQrCodeScanned: (result: ByteArray?) -> Unit + private val onScanQrCode: (result: ByteArray?) -> Unit ) : ImageAnalysis.Analyzer { private val reader by lazy { BarcodeReader() } @@ -31,7 +31,7 @@ internal class QRCodeAnalyzer( if (image.format in SUPPORTED_IMAGE_FORMATS) { try { val bytes = reader.read(image).firstNotNullOfOrNull { it.bytes } - bytes?.let { onQrCodeScanned(it) } + bytes?.let { onScanQrCode(it) } } catch (e: Exception) { Timber.w(e, "Error decoding QR code") } finally { 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 9c0bf8edc3b..428ff0e3d4f 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 @@ -52,7 +52,7 @@ import kotlin.coroutines.suspendCoroutine @Composable fun QrCodeCameraView( - onQrCodeScanned: (ByteArray) -> Unit, + onScanQrCode: (ByteArray) -> Unit, modifier: Modifier = Modifier, renderPreview: Boolean = true, ) { @@ -89,7 +89,7 @@ fun QrCodeCameraView( QRCodeAnalyzer { result -> result?.let { Timber.d("QR code scanned!") - onQrCodeScanned(it) + onScanQrCode(it) } } ) From fdc1f8f487afac66e8ab7e7d03123ddaf7247ee2 Mon Sep 17 00:00:00 2001 From: ElementBot Date: Wed, 29 May 2024 19:23:16 +0000 Subject: [PATCH 52/60] Update screenshots --- ..._LockScreenSettingsView-Day-1_2_null_2,NEXUS_5,1.0,en].png | 4 ++-- ...ockScreenSettingsView-Night-1_3_null_2,NEXUS_5,1.0,en].png | 4 ++-- ...w_null_QrCodeScanView-Day-10_11_null_3,NEXUS_5,1.0,en].png | 3 +++ ...null_QrCodeScanView-Night-10_12_null_3,NEXUS_5,1.0,en].png | 3 +++ ...ll_AdvancedSettingsView-Day-1_2_null_4,NEXUS_5,1.0,en].png | 3 --- ..._AdvancedSettingsView-Night-1_3_null_4,NEXUS_5,1.0,en].png | 3 --- ...otificationSettingsView-Day-6_7_null_6,NEXUS_5,1.0,en].png | 4 ++-- ...ificationSettingsView-Night-6_8_null_6,NEXUS_5,1.0,en].png | 4 ++-- 8 files changed, 14 insertions(+), 14 deletions(-) create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.scan_QrCodeScanView_null_QrCodeScanView-Day-10_11_null_3,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.scan_QrCodeScanView_null_QrCodeScanView-Night-10_12_null_3,NEXUS_5,1.0,en].png delete mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.advanced_AdvancedSettingsView_null_AdvancedSettingsView-Day-1_2_null_4,NEXUS_5,1.0,en].png delete mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.advanced_AdvancedSettingsView_null_AdvancedSettingsView-Night-1_3_null_4,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.lockscreen.impl.settings_LockScreenSettingsView_null_LockScreenSettingsView-Day-1_2_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.lockscreen.impl.settings_LockScreenSettingsView_null_LockScreenSettingsView-Day-1_2_null_2,NEXUS_5,1.0,en].png index 269600505ac..6fd25ee44cf 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.lockscreen.impl.settings_LockScreenSettingsView_null_LockScreenSettingsView-Day-1_2_null_2,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.lockscreen.impl.settings_LockScreenSettingsView_null_LockScreenSettingsView-Day-1_2_null_2,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2c6e1c1a21054c27b3345545537e5ad69c8afb042008b54f2c1a3e4d8d989bf6 -size 32035 +oid sha256:93b933f74b0d6c41d907cf86f79e8df3d1c18b6b45772b2f6c8fcb3714c796b1 +size 32038 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.lockscreen.impl.settings_LockScreenSettingsView_null_LockScreenSettingsView-Night-1_3_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.lockscreen.impl.settings_LockScreenSettingsView_null_LockScreenSettingsView-Night-1_3_null_2,NEXUS_5,1.0,en].png index c0ee347e556..6c2d4625efc 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.lockscreen.impl.settings_LockScreenSettingsView_null_LockScreenSettingsView-Night-1_3_null_2,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.lockscreen.impl.settings_LockScreenSettingsView_null_LockScreenSettingsView-Night-1_3_null_2,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d8fee4544eb56fb30322db29f2df22453e27c442f033b213b886a2adefd3bf21 -size 28245 +oid sha256:491522503cf99e052f5740abe6e73511e5eab7fd464011501aeecccb8f033def +size 28325 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.scan_QrCodeScanView_null_QrCodeScanView-Day-10_11_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.scan_QrCodeScanView_null_QrCodeScanView-Day-10_11_null_3,NEXUS_5,1.0,en].png new file mode 100644 index 00000000000..8aa9fdacc9c --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.scan_QrCodeScanView_null_QrCodeScanView-Day-10_11_null_3,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d7722fd8ff06e09e1b4d9d940221b999ce8d2a208d8f32be92d100d5433c28a2 +size 36447 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.scan_QrCodeScanView_null_QrCodeScanView-Night-10_12_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.scan_QrCodeScanView_null_QrCodeScanView-Night-10_12_null_3,NEXUS_5,1.0,en].png new file mode 100644 index 00000000000..abfd2d536a2 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.scan_QrCodeScanView_null_QrCodeScanView-Night-10_12_null_3,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7a6d1922a96a18e83ea76d09e208db92752cdef74e0bc05dc5a8e876cbf28783 +size 34410 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.advanced_AdvancedSettingsView_null_AdvancedSettingsView-Day-1_2_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.advanced_AdvancedSettingsView_null_AdvancedSettingsView-Day-1_2_null_4,NEXUS_5,1.0,en].png deleted file mode 100644 index f9b6c3edd9d..00000000000 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.advanced_AdvancedSettingsView_null_AdvancedSettingsView-Day-1_2_null_4,NEXUS_5,1.0,en].png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9c7e614cef04ef8a97d7a0ccea9cf66dfaf34cebad4d299cdf63c8f0e7bc6f5e -size 42928 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.advanced_AdvancedSettingsView_null_AdvancedSettingsView-Night-1_3_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.advanced_AdvancedSettingsView_null_AdvancedSettingsView-Night-1_3_null_4,NEXUS_5,1.0,en].png deleted file mode 100644 index 0cb7f346d4b..00000000000 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.advanced_AdvancedSettingsView_null_AdvancedSettingsView-Night-1_3_null_4,NEXUS_5,1.0,en].png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d45372adc3949012741c5dad4f1f48e1ffdd6971cbdbafcb1556aa0eef0890d6 -size 37588 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications_NotificationSettingsView_null_NotificationSettingsView-Day-6_7_null_6,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications_NotificationSettingsView_null_NotificationSettingsView-Day-6_7_null_6,NEXUS_5,1.0,en].png index 5ae2b8073f8..75b5e7fe468 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications_NotificationSettingsView_null_NotificationSettingsView-Day-6_7_null_6,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications_NotificationSettingsView_null_NotificationSettingsView-Day-6_7_null_6,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cb04bfe4a65161812715d39c5c1804a376280811d6acb4cefef70453cbec6b74 -size 44791 +oid sha256:7178bf29291631942fab67ba3258ded0cbe0569909d03ef30cb07e1a2d7b573f +size 44835 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications_NotificationSettingsView_null_NotificationSettingsView-Night-6_8_null_6,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications_NotificationSettingsView_null_NotificationSettingsView-Night-6_8_null_6,NEXUS_5,1.0,en].png index fe2926ec489..58e5c0861ef 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications_NotificationSettingsView_null_NotificationSettingsView-Night-6_8_null_6,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications_NotificationSettingsView_null_NotificationSettingsView-Night-6_8_null_6,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c729ce75714b35fe84346e72f551b0a4e63e19b1d35285b398e69258d07da99f -size 39134 +oid sha256:86d95a745c62021f4314c3d9d20767d8b84d7e5a55cbba37534326ca812624be +size 39181 From 9f615c466f8f562a38f70935aa92090c44a93170 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= Date: Thu, 30 May 2024 11:41:57 +0200 Subject: [PATCH 53/60] Use enum for login flow type --- .../element/android/appnav/NotLoggedInFlowNode.kt | 14 ++++++-------- features/login/api/build.gradle.kts | 1 + .../android/features/login/api/LoginEntryPoint.kt | 12 ++++++++++-- .../features/login/impl/DefaultLoginEntryPoint.kt | 5 +---- .../android/features/login/impl/LoginFlowNode.kt | 9 ++++----- 5 files changed, 22 insertions(+), 19 deletions(-) diff --git a/appnav/src/main/kotlin/io/element/android/appnav/NotLoggedInFlowNode.kt b/appnav/src/main/kotlin/io/element/android/appnav/NotLoggedInFlowNode.kt index 6cd6ad1798a..72d2402eef7 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/NotLoggedInFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/NotLoggedInFlowNode.kt @@ -31,6 +31,7 @@ import dagger.assisted.Assisted import dagger.assisted.AssistedInject import io.element.android.anvilannotations.ContributesNode import io.element.android.features.login.api.LoginEntryPoint +import io.element.android.features.login.api.LoginFlowType import io.element.android.features.onboarding.api.OnBoardingEntryPoint import io.element.android.features.preferences.api.ConfigureTracingEntryPoint import io.element.android.libraries.architecture.BackstackView @@ -75,10 +76,7 @@ class NotLoggedInFlowNode @AssistedInject constructor( data object OnBoarding : NavTarget @Parcelize - data class LoginFlow( - val isAccountCreation: Boolean, - val isQrCode: Boolean, - ) : NavTarget + data class LoginFlow(val type: LoginFlowType) : NavTarget @Parcelize data object ConfigureTracing : NavTarget @@ -89,15 +87,15 @@ class NotLoggedInFlowNode @AssistedInject constructor( NavTarget.OnBoarding -> { val callback = object : OnBoardingEntryPoint.Callback { override fun onSignUp() { - backstack.push(NavTarget.LoginFlow(isAccountCreation = true, isQrCode = false)) + backstack.push(NavTarget.LoginFlow(type = LoginFlowType.SIGN_UP)) } override fun onSignIn() { - backstack.push(NavTarget.LoginFlow(isAccountCreation = false, isQrCode = false)) + backstack.push(NavTarget.LoginFlow(type = LoginFlowType.SIGN_IN_MANUAL)) } override fun onSignInWithQrCode() { - backstack.push(NavTarget.LoginFlow(isAccountCreation = false, isQrCode = true)) + backstack.push(NavTarget.LoginFlow(type = LoginFlowType.SIGN_IN_QR_CODE)) } override fun onOpenDeveloperSettings() { @@ -115,7 +113,7 @@ class NotLoggedInFlowNode @AssistedInject constructor( } is NavTarget.LoginFlow -> { loginEntryPoint.nodeBuilder(this, buildContext) - .params(LoginEntryPoint.Params(isAccountCreation = navTarget.isAccountCreation, isQrCode = navTarget.isQrCode)) + .params(LoginEntryPoint.Params(flowType = navTarget.type)) .build() } NavTarget.ConfigureTracing -> { diff --git a/features/login/api/build.gradle.kts b/features/login/api/build.gradle.kts index 5b7bddb15f9..97fb3bec04d 100644 --- a/features/login/api/build.gradle.kts +++ b/features/login/api/build.gradle.kts @@ -15,6 +15,7 @@ */ plugins { id("io.element.android-library") + id("kotlin-parcelize") } android { diff --git a/features/login/api/src/main/kotlin/io/element/android/features/login/api/LoginEntryPoint.kt b/features/login/api/src/main/kotlin/io/element/android/features/login/api/LoginEntryPoint.kt index 9f8ed68d32e..545dbb9f45e 100644 --- a/features/login/api/src/main/kotlin/io/element/android/features/login/api/LoginEntryPoint.kt +++ b/features/login/api/src/main/kotlin/io/element/android/features/login/api/LoginEntryPoint.kt @@ -16,14 +16,15 @@ package io.element.android.features.login.api +import android.os.Parcelable import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node import io.element.android.libraries.architecture.FeatureEntryPoint +import kotlinx.parcelize.Parcelize interface LoginEntryPoint : FeatureEntryPoint { data class Params( - val isAccountCreation: Boolean, - val isQrCode: Boolean, + val flowType: LoginFlowType ) fun nodeBuilder(parentNode: Node, buildContext: BuildContext): NodeBuilder @@ -33,3 +34,10 @@ interface LoginEntryPoint : FeatureEntryPoint { fun build(): Node } } + +@Parcelize +enum class LoginFlowType : Parcelable { + SIGN_IN_MANUAL, + SIGN_IN_QR_CODE, + SIGN_UP +} diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/DefaultLoginEntryPoint.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/DefaultLoginEntryPoint.kt index 56c974452fe..914b60756e7 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/DefaultLoginEntryPoint.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/DefaultLoginEntryPoint.kt @@ -32,10 +32,7 @@ class DefaultLoginEntryPoint @Inject constructor() : LoginEntryPoint { return object : LoginEntryPoint.NodeBuilder { override fun params(params: LoginEntryPoint.Params): LoginEntryPoint.NodeBuilder { - plugins += LoginFlowNode.Inputs( - isAccountCreation = params.isAccountCreation, - isQrCode = params.isQrCode, - ) + plugins += LoginFlowNode.Inputs(flowType = params.flowType) return this } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/LoginFlowNode.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/LoginFlowNode.kt index f9ae920cfd6..0af102a4038 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/LoginFlowNode.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/LoginFlowNode.kt @@ -27,7 +27,6 @@ import com.bumble.appyx.core.lifecycle.subscribe import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.plugin.Plugin -import com.bumble.appyx.core.plugin.plugins import com.bumble.appyx.navmodel.backstack.BackStack import com.bumble.appyx.navmodel.backstack.operation.newRoot import com.bumble.appyx.navmodel.backstack.operation.push @@ -36,6 +35,7 @@ import dagger.assisted.Assisted import dagger.assisted.AssistedInject import io.element.android.anvilannotations.ContributesNode import io.element.android.compound.theme.ElementTheme +import io.element.android.features.login.api.LoginFlowType import io.element.android.features.login.api.oidc.OidcAction import io.element.android.features.login.api.oidc.OidcActionFlow import io.element.android.features.login.impl.accountprovider.AccountProviderDataSource @@ -81,8 +81,7 @@ class LoginFlowNode @AssistedInject constructor( private var darkTheme: Boolean = false data class Inputs( - val isAccountCreation: Boolean, - val isQrCode: Boolean, + val flowType: LoginFlowType, ) : NodeInputs private val inputs: Inputs = inputs() @@ -135,7 +134,7 @@ class LoginFlowNode @AssistedInject constructor( override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node { return when (navTarget) { NavTarget.Root -> { - if (plugins().first().isQrCode) { + if (inputs.flowType == LoginFlowType.SIGN_IN_QR_CODE) { createNode(buildContext) } else { resolve(NavTarget.ConfirmAccountProvider, buildContext) @@ -143,7 +142,7 @@ class LoginFlowNode @AssistedInject constructor( } NavTarget.ConfirmAccountProvider -> { val inputs = ConfirmAccountProviderNode.Inputs( - isAccountCreation = inputs.isAccountCreation + isAccountCreation = inputs.flowType == LoginFlowType.SIGN_UP, ) val callback = object : ConfirmAccountProviderNode.Callback { override fun onOidcDetails(oidcDetails: OidcDetails) { From 06880645db5027aaeb69671a9fb731e55a0dca74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= Date: Thu, 30 May 2024 11:42:19 +0200 Subject: [PATCH 54/60] Make nodes in qrcode feature use `QrCodeLoginScope` instead of `AppScope` --- .../screens/qrcode/confirmation/QrCodeConfirmationNode.kt | 4 ++-- .../login/impl/screens/qrcode/error/QrCodeErrorNode.kt | 4 ++-- .../login/impl/screens/qrcode/intro/QrCodeIntroNode.kt | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/confirmation/QrCodeConfirmationNode.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/confirmation/QrCodeConfirmationNode.kt index 835e0670785..038d9b19989 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/confirmation/QrCodeConfirmationNode.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/confirmation/QrCodeConfirmationNode.kt @@ -25,10 +25,10 @@ import com.bumble.appyx.core.plugin.plugins import dagger.assisted.Assisted import dagger.assisted.AssistedInject import io.element.android.anvilannotations.ContributesNode +import io.element.android.features.login.impl.di.QrCodeLoginScope import io.element.android.libraries.architecture.inputs -import io.element.android.libraries.di.AppScope -@ContributesNode(AppScope::class) +@ContributesNode(QrCodeLoginScope::class) class QrCodeConfirmationNode @AssistedInject constructor( @Assisted buildContext: BuildContext, @Assisted plugins: List, diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/error/QrCodeErrorNode.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/error/QrCodeErrorNode.kt index c9f56155b6d..66ddd8bfbed 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/error/QrCodeErrorNode.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/error/QrCodeErrorNode.kt @@ -25,12 +25,12 @@ import com.bumble.appyx.core.plugin.plugins import dagger.assisted.Assisted import dagger.assisted.AssistedInject import io.element.android.anvilannotations.ContributesNode +import io.element.android.features.login.impl.di.QrCodeLoginScope import io.element.android.features.login.impl.qrcode.QrCodeErrorScreenType import io.element.android.libraries.architecture.inputs import io.element.android.libraries.core.meta.BuildMeta -import io.element.android.libraries.di.AppScope -@ContributesNode(AppScope::class) +@ContributesNode(QrCodeLoginScope::class) class QrCodeErrorNode @AssistedInject constructor( @Assisted buildContext: BuildContext, @Assisted plugins: List, diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/intro/QrCodeIntroNode.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/intro/QrCodeIntroNode.kt index f9fc3085bb9..b207462bd16 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/intro/QrCodeIntroNode.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/intro/QrCodeIntroNode.kt @@ -25,9 +25,9 @@ import com.bumble.appyx.core.plugin.plugins import dagger.assisted.Assisted import dagger.assisted.AssistedInject import io.element.android.anvilannotations.ContributesNode -import io.element.android.libraries.di.AppScope +import io.element.android.features.login.impl.di.QrCodeLoginScope -@ContributesNode(AppScope::class) +@ContributesNode(QrCodeLoginScope::class) class QrCodeIntroNode @AssistedInject constructor( @Assisted buildContext: BuildContext, @Assisted plugins: List, From 6ff42bca79925898f83d6d3801d25930364c5e57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= Date: Thu, 30 May 2024 12:26:28 +0200 Subject: [PATCH 55/60] Add bottom padding to the camera preview in `QrCodeScanView` --- .../features/login/impl/screens/qrcode/scan/QrCodeScanView.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 b1eea8fe7c5..c7a9ba50a1a 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 @@ -99,7 +99,7 @@ private fun Content( Modifier.fillMaxWidth() }.then( Modifier - .padding(start = 20.dp, end = 20.dp, top = 50.dp) + .padding(start = 20.dp, end = 20.dp, top = 50.dp, bottom = 32.dp) .squareSize() .cornerBorder( strokeWidth = 4.dp, From 13c97ca9dc5a7293b414ebd80a1312ac96cde6b6 Mon Sep 17 00:00:00 2001 From: ElementBot Date: Thu, 30 May 2024 10:39:10 +0000 Subject: [PATCH 56/60] Update screenshots --- ...w_null_QrCodeScanView-Day-10_11_null_0,NEXUS_5,1.0,en].png | 4 ++-- ...w_null_QrCodeScanView-Day-10_11_null_1,NEXUS_5,1.0,en].png | 4 ++-- ...w_null_QrCodeScanView-Day-10_11_null_2,NEXUS_5,1.0,en].png | 4 ++-- ...w_null_QrCodeScanView-Day-10_11_null_3,NEXUS_5,1.0,en].png | 4 ++-- ...null_QrCodeScanView-Night-10_12_null_0,NEXUS_5,1.0,en].png | 4 ++-- ...null_QrCodeScanView-Night-10_12_null_1,NEXUS_5,1.0,en].png | 4 ++-- ...null_QrCodeScanView-Night-10_12_null_2,NEXUS_5,1.0,en].png | 4 ++-- ...null_QrCodeScanView-Night-10_12_null_3,NEXUS_5,1.0,en].png | 4 ++-- 8 files changed, 16 insertions(+), 16 deletions(-) diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.scan_QrCodeScanView_null_QrCodeScanView-Day-10_11_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.scan_QrCodeScanView_null_QrCodeScanView-Day-10_11_null_0,NEXUS_5,1.0,en].png index 0aa4a191125..99b1262c483 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.scan_QrCodeScanView_null_QrCodeScanView-Day-10_11_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.scan_QrCodeScanView_null_QrCodeScanView-Day-10_11_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a9f3d92455b16ab319b4830c91516e2af3a43ac8ac3f88d6a04d643328c99d4f -size 18512 +oid sha256:2ba081e025288682e8044658fdc2fc3ae0e4b56e90b506850cfa6fa885fe6932 +size 18171 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.scan_QrCodeScanView_null_QrCodeScanView-Day-10_11_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.scan_QrCodeScanView_null_QrCodeScanView-Day-10_11_null_1,NEXUS_5,1.0,en].png index 7fcdc04a3d2..e5308fe0d7a 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.scan_QrCodeScanView_null_QrCodeScanView-Day-10_11_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.scan_QrCodeScanView_null_QrCodeScanView-Day-10_11_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:15f289148c18ee687ab55156375c89ebebfe130d5602c1906e15e3ad6d0918da -size 23545 +oid sha256:b747fe68c4773b6a11e744caa850a57512234b07955cef9bd9c37184345ad093 +size 23224 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.scan_QrCodeScanView_null_QrCodeScanView-Day-10_11_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.scan_QrCodeScanView_null_QrCodeScanView-Day-10_11_null_2,NEXUS_5,1.0,en].png index 77f53529500..8e6de754d7e 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.scan_QrCodeScanView_null_QrCodeScanView-Day-10_11_null_2,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.scan_QrCodeScanView_null_QrCodeScanView-Day-10_11_null_2,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ec401a67e09c646f42ac8b8c381e2844b39ca8ed37fe067982e178d10ab8d6d3 -size 30340 +oid sha256:4f920d0b049006623290955fdf34103a80255bbc9bfbbf6a86fcd2d550cffdd6 +size 30170 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.scan_QrCodeScanView_null_QrCodeScanView-Day-10_11_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.scan_QrCodeScanView_null_QrCodeScanView-Day-10_11_null_3,NEXUS_5,1.0,en].png index 8aa9fdacc9c..56383562e5c 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.scan_QrCodeScanView_null_QrCodeScanView-Day-10_11_null_3,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.scan_QrCodeScanView_null_QrCodeScanView-Day-10_11_null_3,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d7722fd8ff06e09e1b4d9d940221b999ce8d2a208d8f32be92d100d5433c28a2 -size 36447 +oid sha256:38b0f09fed7ae29ea83bcec1aa00a8a0310bf493165a81d7ef0675d13df8b558 +size 36296 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.scan_QrCodeScanView_null_QrCodeScanView-Night-10_12_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.scan_QrCodeScanView_null_QrCodeScanView-Night-10_12_null_0,NEXUS_5,1.0,en].png index b946d360b07..54de03b1666 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.scan_QrCodeScanView_null_QrCodeScanView-Night-10_12_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.scan_QrCodeScanView_null_QrCodeScanView-Night-10_12_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:852a1fc63b7b350d420a08348e424fe90aca9e3198c59ed5787e27f803f4cc80 -size 17143 +oid sha256:02dfb8229d4f9b28ed990d685da67dce4607a2ca1c068f2e62364bdd33403eee +size 16948 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.scan_QrCodeScanView_null_QrCodeScanView-Night-10_12_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.scan_QrCodeScanView_null_QrCodeScanView-Night-10_12_null_1,NEXUS_5,1.0,en].png index d7fc59ed6e1..d3d7c038569 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.scan_QrCodeScanView_null_QrCodeScanView-Night-10_12_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.scan_QrCodeScanView_null_QrCodeScanView-Night-10_12_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6e0d8c54299bdb78edebb2bd1fae16ede18c0bbb57ed1c85083f6d3a97face90 -size 22139 +oid sha256:410ae4231767c9d0a1ce2347537217411ea5b24ef9483658df7f5afa489d45c1 +size 21905 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.scan_QrCodeScanView_null_QrCodeScanView-Night-10_12_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.scan_QrCodeScanView_null_QrCodeScanView-Night-10_12_null_2,NEXUS_5,1.0,en].png index 5e17456723a..49e60cbc161 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.scan_QrCodeScanView_null_QrCodeScanView-Night-10_12_null_2,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.scan_QrCodeScanView_null_QrCodeScanView-Night-10_12_null_2,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bee05f1c2f0d9b83cd62cce543bdcfc1cd5fcee3af42047397a2edb42068180a -size 28463 +oid sha256:e426c97ea160a5adf752399e705aa4712af8308ac34205fc165ab9db741f3910 +size 28433 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.scan_QrCodeScanView_null_QrCodeScanView-Night-10_12_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.scan_QrCodeScanView_null_QrCodeScanView-Night-10_12_null_3,NEXUS_5,1.0,en].png index abfd2d536a2..666b8bdd1ab 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.scan_QrCodeScanView_null_QrCodeScanView-Night-10_12_null_3,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.scan_QrCodeScanView_null_QrCodeScanView-Night-10_12_null_3,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7a6d1922a96a18e83ea76d09e208db92752cdef74e0bc05dc5a8e876cbf28783 -size 34410 +oid sha256:5c6f46334975e38a13e63d5b1f2cc9ae9f1d8f8e04c264068be8d239fd376108 +size 34328 From a2ac07ad809e0ded78a92b35592932ba66fd9618 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= Date: Fri, 31 May 2024 11:54:35 +0200 Subject: [PATCH 57/60] Update strings --- features/ftue/impl/src/main/res/values/localazy.xml | 6 +++--- .../login/impl/screens/qrcode/scan/QrCodeScanView.kt | 4 ++-- features/login/impl/src/main/res/values/localazy.xml | 6 +++--- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/features/ftue/impl/src/main/res/values/localazy.xml b/features/ftue/impl/src/main/res/values/localazy.xml index 4a48b517da4..7a2e02514fe 100644 --- a/features/ftue/impl/src/main/res/values/localazy.xml +++ b/features/ftue/impl/src/main/res/values/localazy.xml @@ -11,11 +11,11 @@ "Connection not secure" "You’ll be asked to enter the two digits shown on this device." "Enter the number below on your other device" - "Sign in to your other device and then try again, or use another device that’s already signed in." - "Other device not signed in" + "Sign in to your other device and then try again, or use another device that’s already signed in." + "Other device not signed in" "The sign in was cancelled on the other device." "Sign in request cancelled" - "The request on your other device was not accepted." + "The sign in was declined on the other device." "Sign in declined" "Sign in expired. Please try again." "The sign in was not completed in time" 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 c7a9ba50a1a..fbc9eb82d0f 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 @@ -155,7 +155,7 @@ private fun ColumnScope.Buttons( Text( text = when (error) { is QrLoginException.OtherDeviceNotSignedIn -> { - stringResource(R.string.screen_qr_code_login_device_not_signed_in_scan_state_title) + stringResource(R.string.screen_qr_code_login_device_not_signed_in_scan_state_subtitle) } else -> stringResource(R.string.screen_qr_code_login_invalid_scan_state_subtitle) }, @@ -166,7 +166,7 @@ private fun ColumnScope.Buttons( } Text( text = when (error) { - is QrLoginException.OtherDeviceNotSignedIn -> stringResource(R.string.screen_qr_code_login_device_not_signed_in_scan_state_subtitle) + is QrLoginException.OtherDeviceNotSignedIn -> stringResource(R.string.screen_qr_code_login_device_not_signed_in_scan_state_description) else -> stringResource(R.string.screen_qr_code_login_invalid_scan_state_description) }, textAlign = TextAlign.Center, diff --git a/features/login/impl/src/main/res/values/localazy.xml b/features/login/impl/src/main/res/values/localazy.xml index 1192bbeb4c3..cade81c4f20 100644 --- a/features/login/impl/src/main/res/values/localazy.xml +++ b/features/login/impl/src/main/res/values/localazy.xml @@ -39,11 +39,11 @@ "Connection not secure" "You’ll be asked to enter the two digits shown on this device." "Enter the number below on your other device" - "Sign in to your other device and then try again, or use another device that’s already signed in." - "Other device not signed in" + "Sign in to your other device and then try again, or use another device that’s already signed in." + "Other device not signed in" "The sign in was cancelled on the other device." "Sign in request cancelled" - "The request on your other device was not accepted." + "The sign in was declined on the other device." "Sign in declined" "Sign in expired. Please try again." "The sign in was not completed in time" From 6375be41c4d21227e71fa5ade795b072527295ab Mon Sep 17 00:00:00 2001 From: ElementBot Date: Fri, 31 May 2024 10:43:04 +0000 Subject: [PATCH 58/60] Update screenshots --- ...ew_null_QrCodeErrorView-Day-8_9_null_1,NEXUS_5,1.0,en].png | 4 ++-- ...null_QrCodeErrorView-Night-8_10_null_1,NEXUS_5,1.0,en].png | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.error_QrCodeErrorView_null_QrCodeErrorView-Day-8_9_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.error_QrCodeErrorView_null_QrCodeErrorView-Day-8_9_null_1,NEXUS_5,1.0,en].png index 92ad74dc2a5..22d425c7591 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.error_QrCodeErrorView_null_QrCodeErrorView-Day-8_9_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.error_QrCodeErrorView_null_QrCodeErrorView-Day-8_9_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:257a0571dbaccf06f145dee2ce79addaa5fd80f902b2c64f68858e5c4312f454 -size 22636 +oid sha256:7985ff4afe7ed4497dffd2e6e0143520584dca3db99858a7ace76ef77ecfaead +size 20796 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.error_QrCodeErrorView_null_QrCodeErrorView-Night-8_10_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.error_QrCodeErrorView_null_QrCodeErrorView-Night-8_10_null_1,NEXUS_5,1.0,en].png index 3e2b0451819..f227e6a15e7 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.error_QrCodeErrorView_null_QrCodeErrorView-Night-8_10_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.qrcode.error_QrCodeErrorView_null_QrCodeErrorView-Night-8_10_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:179cb5e77e7ef0ed9311e60b2bd531ff1b0fe153bad4ada15c4c3810d4c09cf7 -size 21932 +oid sha256:ba3ea3558bf942fdeab54b93852042eac7359a53cfe1a1244e6f406caca2d082 +size 20105 From eab9676c3a317d402ae2cccd44c5e8ff271e7db8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= Date: Fri, 31 May 2024 12:52:04 +0200 Subject: [PATCH 59/60] Fix lint issues --- .../screens/qrcode/confirmation/QrCodeConfirmationStep.kt | 2 ++ .../features/login/impl/screens/qrcode/scan/QrCodeScanView.kt | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/confirmation/QrCodeConfirmationStep.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/confirmation/QrCodeConfirmationStep.kt index 63848e83132..b41433d5c8f 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/confirmation/QrCodeConfirmationStep.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/confirmation/QrCodeConfirmationStep.kt @@ -17,9 +17,11 @@ package io.element.android.features.login.impl.screens.qrcode.confirmation import android.os.Parcelable +import androidx.compose.runtime.Immutable import io.element.android.libraries.architecture.NodeInputs import kotlinx.parcelize.Parcelize +@Immutable sealed interface QrCodeConfirmationStep : NodeInputs, Parcelable { @Parcelize data class DisplayCheckCode(val code: String) : QrCodeConfirmationStep 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 fbc9eb82d0f..97925c11ad9 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 @@ -166,7 +166,9 @@ private fun ColumnScope.Buttons( } Text( text = when (error) { - is QrLoginException.OtherDeviceNotSignedIn -> stringResource(R.string.screen_qr_code_login_device_not_signed_in_scan_state_description) + is QrLoginException.OtherDeviceNotSignedIn -> { + stringResource(R.string.screen_qr_code_login_device_not_signed_in_scan_state_description) + } else -> stringResource(R.string.screen_qr_code_login_invalid_scan_state_description) }, textAlign = TextAlign.Center, From 7fad1a64f5399a7b9a4c101881b283a8b7f39ce0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= Date: Fri, 31 May 2024 13:21:21 +0200 Subject: [PATCH 60/60] Fix maestro tests --- .maestro/tests/account/login.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.maestro/tests/account/login.yaml b/.maestro/tests/account/login.yaml index 69833a985ed..18054297d62 100644 --- a/.maestro/tests/account/login.yaml +++ b/.maestro/tests/account/login.yaml @@ -1,6 +1,6 @@ appId: ${MAESTRO_APP_ID} --- -- tapOn: "Continue" +- tapOn: "Sign in manually" - runFlow: ../assertions/assertLoginDisplayed.yaml - takeScreenshot: build/maestro/100-SignIn - runFlow: changeServer.yaml