Fix most review comments

This commit is contained in:
Jorge Martín 2024-08-14 16:53:50 +02:00
parent 08ed272991
commit 9c09f96c0b
14 changed files with 136 additions and 78 deletions

View file

@ -23,6 +23,7 @@ import io.element.android.libraries.matrix.api.encryption.IdentityResetHandle
import io.element.android.libraries.matrix.api.verification.SessionVerificationService import io.element.android.libraries.matrix.api.verification.SessionVerificationService
import io.element.android.libraries.matrix.api.verification.SessionVerifiedStatus import io.element.android.libraries.matrix.api.verification.SessionVerifiedStatus
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.filterIsInstance import kotlinx.coroutines.flow.filterIsInstance
@ -37,9 +38,10 @@ class ResetIdentityFlowManager @Inject constructor(
) { ) {
private val resetHandleFlow: MutableStateFlow<AsyncData<IdentityResetHandle>> = MutableStateFlow(AsyncData.Uninitialized) private val resetHandleFlow: MutableStateFlow<AsyncData<IdentityResetHandle>> = MutableStateFlow(AsyncData.Uninitialized)
val currentHandleFlow: StateFlow<AsyncData<IdentityResetHandle>> = resetHandleFlow val currentHandleFlow: StateFlow<AsyncData<IdentityResetHandle>> = resetHandleFlow
private var whenResetIsDoneWaitingJob: Job? = null
fun whenResetIsDone(block: () -> Unit) { fun whenResetIsDone(block: () -> Unit) {
sessionCoroutineScope.launch { whenResetIsDoneWaitingJob = sessionCoroutineScope.launch {
sessionVerificationService.sessionVerifiedStatus.filterIsInstance<SessionVerifiedStatus.Verified>().first() sessionVerificationService.sessionVerifiedStatus.filterIsInstance<SessionVerifiedStatus.Verified>().first()
block() block()
} }
@ -70,5 +72,8 @@ class ResetIdentityFlowManager @Inject constructor(
suspend fun cancel() { suspend fun cancel() {
currentHandleFlow.value.dataOrNull()?.cancel() currentHandleFlow.value.dataOrNull()?.cancel()
resetHandleFlow.value = AsyncData.Uninitialized resetHandleFlow.value = AsyncData.Uninitialized
whenResetIsDoneWaitingJob?.cancel()
whenResetIsDoneWaitingJob = null
} }
} }

View file

@ -23,6 +23,7 @@ import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.window.DialogProperties
import androidx.lifecycle.DefaultLifecycleObserver import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleOwner
import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.modality.BuildContext
@ -45,14 +46,13 @@ import io.element.android.libraries.designsystem.components.ProgressDialog
import io.element.android.libraries.di.SessionScope import io.element.android.libraries.di.SessionScope
import io.element.android.libraries.matrix.api.encryption.IdentityOidcResetHandle import io.element.android.libraries.matrix.api.encryption.IdentityOidcResetHandle
import io.element.android.libraries.matrix.api.encryption.IdentityPasswordResetHandle import io.element.android.libraries.matrix.api.encryption.IdentityPasswordResetHandle
import io.element.android.libraries.matrix.api.encryption.IdentityResetHandle
import io.element.android.libraries.oidc.api.OidcEntryPoint import io.element.android.libraries.oidc.api.OidcEntryPoint
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.filterIsInstance import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
import timber.log.Timber
@ContributesNode(SessionScope::class) @ContributesNode(SessionScope::class)
class ResetIdentityFlowNode @AssistedInject constructor( class ResetIdentityFlowNode @AssistedInject constructor(
@ -94,7 +94,7 @@ class ResetIdentityFlowNode @AssistedInject constructor(
lifecycle.addObserver(object : DefaultLifecycleObserver { lifecycle.addObserver(object : DefaultLifecycleObserver {
override fun onStart(owner: LifecycleOwner) { override fun onStart(owner: LifecycleOwner) {
// If the custom tab was opened, we need to cancel the reset job // If the custom tab was opened, we need to cancel the reset job
// when we come back to the node if it the reset wasn't successful // when we come back to the node if the reset wasn't successful
cancelResetJob() cancelResetJob()
} }
@ -129,22 +129,29 @@ class ResetIdentityFlowNode @AssistedInject constructor(
} }
private fun CoroutineScope.startReset() = launch { private fun CoroutineScope.startReset() = launch {
val handle = resetIdentityFlowManager.getResetHandle() resetIdentityFlowManager.getResetHandle()
.filterIsInstance<AsyncData.Success<IdentityResetHandle>>() .collectLatest { state ->
.first() when (state) {
.data is AsyncData.Failure -> {
cancelResetJob()
when (handle) { Timber.e(state.error, "Could not load the reset identity handle.")
is IdentityOidcResetHandle -> { }
if (oidcEntryPoint.canUseCustomTab()) { is AsyncData.Success -> {
activity.openUrlInChromeCustomTab(null, false, handle.url) when (val handle = state.data) {
} else { is IdentityOidcResetHandle -> {
backstack.push(NavTarget.ResetOidc(handle.url)) if (oidcEntryPoint.canUseCustomTab()) {
activity.openUrlInChromeCustomTab(null, false, handle.url)
} else {
backstack.push(NavTarget.ResetOidc(handle.url))
}
resetJob = launch { handle.resetOidc() }
}
is IdentityPasswordResetHandle -> backstack.push(NavTarget.ResetPassword)
}
}
else -> Unit
} }
resetJob = launch { handle.resetOidc() }
} }
is IdentityPasswordResetHandle -> backstack.push(NavTarget.ResetPassword)
}
} }
private fun cancelResetJob() { private fun cancelResetJob() {
@ -162,7 +169,10 @@ class ResetIdentityFlowNode @AssistedInject constructor(
val startResetState by resetIdentityFlowManager.currentHandleFlow.collectAsState() val startResetState by resetIdentityFlowManager.currentHandleFlow.collectAsState()
if (startResetState.isLoading()) { if (startResetState.isLoading()) {
ProgressDialog() ProgressDialog(
properties = DialogProperties(dismissOnBackPress = true, dismissOnClickOutside = true),
onDismissRequest = { cancelResetJob() }
)
} }
BackstackView(modifier) BackstackView(modifier)

View file

@ -34,14 +34,14 @@ import io.element.android.libraries.matrix.api.encryption.IdentityPasswordResetH
class ResetIdentityPasswordNode @AssistedInject constructor( class ResetIdentityPasswordNode @AssistedInject constructor(
@Assisted buildContext: BuildContext, @Assisted buildContext: BuildContext,
@Assisted plugins: List<Plugin>, @Assisted plugins: List<Plugin>,
private val coroutineDispatchers: CoroutineDispatchers, coroutineDispatchers: CoroutineDispatchers,
) : Node(buildContext, plugins = plugins) { ) : Node(buildContext, plugins = plugins) {
data class Inputs(val handle: IdentityPasswordResetHandle) : NodeInputs data class Inputs(val handle: IdentityPasswordResetHandle) : NodeInputs
private val presenter by lazy { private val presenter = ResetIdentityPasswordPresenter(
val inputs = inputs<Inputs>() identityPasswordResetHandle = inputs<Inputs>().handle,
ResetIdentityPasswordPresenter(inputs.handle, dispatchers = coroutineDispatchers) dispatchers = coroutineDispatchers
} )
@Composable @Composable
override fun View(modifier: Modifier) { override fun View(modifier: Modifier) {

View file

@ -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
*
* https://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.securebackup.impl.reset.password
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import io.element.android.libraries.architecture.AsyncAction
class ResetIdentityPasswordStateProvider : PreviewParameterProvider<ResetIdentityPasswordState> {
override val values: Sequence<ResetIdentityPasswordState>
get() = sequenceOf(
aResetIdentityPasswordState(),
aResetIdentityPasswordState(resetAction = AsyncAction.Loading),
aResetIdentityPasswordState(resetAction = AsyncAction.Success(Unit)),
aResetIdentityPasswordState(resetAction = AsyncAction.Failure(IllegalStateException("Failed"))),
)
}
private fun aResetIdentityPasswordState(
resetAction: AsyncAction<Unit> = AsyncAction.Uninitialized,
eventSink: (ResetIdentityPasswordEvent) -> Unit = {},
) = ResetIdentityPasswordState(
resetAction = resetAction,
eventSink = eventSink,
)

View file

@ -27,12 +27,12 @@ import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.PasswordVisualTransformation import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.text.input.VisualTransformation import androidx.compose.ui.text.input.VisualTransformation
import androidx.compose.ui.tooling.preview.PreviewParameter
import io.element.android.compound.tokens.generated.CompoundIcons import io.element.android.compound.tokens.generated.CompoundIcons
import io.element.android.libraries.architecture.AsyncAction import io.element.android.features.securebackup.impl.R
import io.element.android.libraries.designsystem.atomic.pages.FlowStepPage import io.element.android.libraries.designsystem.atomic.pages.FlowStepPage
import io.element.android.libraries.designsystem.components.BigIcon import io.element.android.libraries.designsystem.components.BigIcon
import io.element.android.libraries.designsystem.components.ProgressDialog import io.element.android.libraries.designsystem.components.ProgressDialog
import io.element.android.libraries.designsystem.components.dialogs.ErrorDialog
import io.element.android.libraries.designsystem.components.form.textFieldState import io.element.android.libraries.designsystem.components.form.textFieldState
import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.preview.PreviewsDayNight
@ -54,13 +54,19 @@ fun ResetIdentityPasswordView(
FlowStepPage( FlowStepPage(
modifier = modifier, modifier = modifier,
iconStyle = BigIcon.Style.Default(CompoundIcons.LockSolid()), iconStyle = BigIcon.Style.Default(CompoundIcons.LockSolid()),
title = stringResource(CommonStrings.screen_reset_encryption_password_title), title = stringResource(R.string.screen_reset_encryption_password_title),
subTitle = stringResource(CommonStrings.screen_reset_encryption_password_subtitle), subTitle = stringResource(R.string.screen_reset_encryption_password_subtitle),
onBackClick = onBack, onBackClick = onBack,
content = { content = {
Content( Content(
text = passwordState.value, text = passwordState.value,
onTextChange = { passwordState.value = it } onTextChange = { newText ->
if (state.resetAction.isFailure()) {
state.eventSink(ResetIdentityPasswordEvent.DismissError)
}
passwordState.value = newText
},
hasError = state.resetAction.isFailure(),
) )
}, },
buttons = { buttons = {
@ -69,22 +75,19 @@ fun ResetIdentityPasswordView(
text = stringResource(CommonStrings.action_reset_identity), text = stringResource(CommonStrings.action_reset_identity),
onClick = { state.eventSink(ResetIdentityPasswordEvent.Reset(passwordState.value)) }, onClick = { state.eventSink(ResetIdentityPasswordEvent.Reset(passwordState.value)) },
destructive = true, destructive = true,
enabled = passwordState.value.isNotEmpty(),
) )
} }
) )
// On success we need to wait until the screen is automatically dismissed, so we keep the progress dialog
if (state.resetAction.isLoading() || state.resetAction.isSuccess()) { if (state.resetAction.isLoading() || state.resetAction.isSuccess()) {
ProgressDialog() ProgressDialog()
} else if (state.resetAction.isFailure()) {
ErrorDialog(
content = stringResource(CommonStrings.error_unknown),
onDismiss = { state.eventSink(ResetIdentityPasswordEvent.DismissError) }
)
} }
} }
@Composable @Composable
private fun Content(text: String, onTextChange: (String) -> Unit) { private fun Content(text: String, onTextChange: (String) -> Unit, hasError: Boolean) {
var showPassword by remember { mutableStateOf(false) } var showPassword by remember { mutableStateOf(false) }
OutlinedTextField( OutlinedTextField(
modifier = Modifier modifier = Modifier
@ -93,7 +96,7 @@ private fun Content(text: String, onTextChange: (String) -> Unit) {
value = text, value = text,
onValueChange = onTextChange, onValueChange = onTextChange,
label = { Text(stringResource(CommonStrings.common_password)) }, label = { Text(stringResource(CommonStrings.common_password)) },
placeholder = { Text(stringResource(CommonStrings.screen_reset_encryption_password_placeholder)) }, placeholder = { Text(stringResource(R.string.screen_reset_encryption_password_placeholder)) },
singleLine = true, singleLine = true,
visualTransformation = if (showPassword) VisualTransformation.None else PasswordVisualTransformation(), visualTransformation = if (showPassword) VisualTransformation.None else PasswordVisualTransformation(),
trailingIcon = { trailingIcon = {
@ -105,19 +108,22 @@ private fun Content(text: String, onTextChange: (String) -> Unit) {
IconButton(onClick = { showPassword = !showPassword }) { IconButton(onClick = { showPassword = !showPassword }) {
Icon(imageVector = image, description) Icon(imageVector = image, description)
} }
},
isError = hasError,
supportingText = if (hasError) {
{ Text(stringResource(R.string.screen_reset_encryption_password_error)) }
} else {
null
} }
) )
} }
@PreviewsDayNight @PreviewsDayNight
@Composable @Composable
internal fun ResetIdentityPasswordViewPreview() { internal fun ResetIdentityPasswordViewPreview(@PreviewParameter(ResetIdentityPasswordStateProvider::class) state: ResetIdentityPasswordState) {
ElementPreview { ElementPreview {
ResetIdentityPasswordView( ResetIdentityPasswordView(
state = ResetIdentityPasswordState( state = state,
resetAction = AsyncAction.Uninitialized,
eventSink = {}
),
onBack = {} onBack = {}
) )
} }

View file

@ -29,6 +29,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import io.element.android.compound.theme.ElementTheme import io.element.android.compound.theme.ElementTheme
import io.element.android.compound.tokens.generated.CompoundIcons import io.element.android.compound.tokens.generated.CompoundIcons
import io.element.android.features.securebackup.impl.R
import io.element.android.libraries.designsystem.atomic.organisms.InfoListItem 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.organisms.InfoListOrganism
import io.element.android.libraries.designsystem.atomic.pages.FlowStepPage import io.element.android.libraries.designsystem.atomic.pages.FlowStepPage
@ -52,8 +53,8 @@ fun ResetIdentityRootView(
FlowStepPage( FlowStepPage(
modifier = modifier, modifier = modifier,
iconStyle = BigIcon.Style.AlertSolid, iconStyle = BigIcon.Style.AlertSolid,
title = stringResource(io.element.android.libraries.ui.strings.R.string.screen_encryption_reset_title), title = stringResource(R.string.screen_encryption_reset_title),
subTitle = stringResource(io.element.android.libraries.ui.strings.R.string.screen_encryption_reset_subtitle), subTitle = stringResource(R.string.screen_encryption_reset_subtitle),
isScrollable = true, isScrollable = true,
content = { Content() }, content = { Content() },
buttons = { buttons = {
@ -69,9 +70,9 @@ fun ResetIdentityRootView(
if (state.displayConfirmationDialog) { if (state.displayConfirmationDialog) {
ConfirmationDialog( ConfirmationDialog(
title = stringResource(CommonStrings.screen_reset_encryption_confirmation_alert_title), title = stringResource(R.string.screen_reset_encryption_confirmation_alert_title),
content = stringResource(CommonStrings.screen_reset_encryption_confirmation_alert_subtitle), content = stringResource(R.string.screen_reset_encryption_confirmation_alert_subtitle),
submitText = stringResource(CommonStrings.screen_reset_encryption_confirmation_alert_action), submitText = stringResource(R.string.screen_reset_encryption_confirmation_alert_action),
onSubmitClick = { onSubmitClick = {
state.eventSink(ResetIdentityRootEvent.DismissDialog) state.eventSink(ResetIdentityRootEvent.DismissDialog)
onContinue() onContinue()
@ -92,7 +93,7 @@ private fun Content() {
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
items = persistentListOf( items = persistentListOf(
InfoListItem( InfoListItem(
message = stringResource(CommonStrings.screen_encryption_reset_bullet_1), message = stringResource(R.string.screen_encryption_reset_bullet_1),
iconComposable = { iconComposable = {
Icon( Icon(
modifier = Modifier.size(20.dp), modifier = Modifier.size(20.dp),
@ -103,7 +104,7 @@ private fun Content() {
}, },
), ),
InfoListItem( InfoListItem(
message = stringResource(CommonStrings.screen_encryption_reset_bullet_2), message = stringResource(R.string.screen_encryption_reset_bullet_2),
iconComposable = { iconComposable = {
Icon( Icon(
modifier = Modifier.size(20.dp), modifier = Modifier.size(20.dp),
@ -114,7 +115,7 @@ private fun Content() {
}, },
), ),
InfoListItem( InfoListItem(
message = stringResource(CommonStrings.screen_encryption_reset_bullet_3), message = stringResource(R.string.screen_encryption_reset_bullet_3),
iconComposable = { iconComposable = {
Icon( Icon(
modifier = Modifier.size(20.dp), modifier = Modifier.size(20.dp),
@ -130,7 +131,7 @@ private fun Content() {
Text( Text(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
text = stringResource(CommonStrings.screen_encryption_reset_footer), text = stringResource(R.string.screen_encryption_reset_footer),
style = ElementTheme.typography.fontBodyMdMedium, style = ElementTheme.typography.fontBodyMdMedium,
color = ElementTheme.colors.textActionPrimary, color = ElementTheme.colors.textActionPrimary,
textAlign = TextAlign.Center, textAlign = TextAlign.Center,

View file

@ -16,6 +16,12 @@
<string name="screen_create_new_recovery_key_list_item_4">"Follow the instructions to create a new recovery key"</string> <string name="screen_create_new_recovery_key_list_item_4">"Follow the instructions to create a new recovery key"</string>
<string name="screen_create_new_recovery_key_list_item_5">"Save your new recovery key in a password manager or encrypted note"</string> <string name="screen_create_new_recovery_key_list_item_5">"Save your new recovery key in a password manager or encrypted note"</string>
<string name="screen_create_new_recovery_key_title">"Reset the encryption for your account using another device"</string> <string name="screen_create_new_recovery_key_title">"Reset the encryption for your account using another device"</string>
<string name="screen_encryption_reset_bullet_1">"Your account details, contacts, preferences, and chat list will be kept"</string>
<string name="screen_encryption_reset_bullet_2">"You will lose your existing message history"</string>
<string name="screen_encryption_reset_bullet_3">"You will need to verify all your existing devices and contacts again"</string>
<string name="screen_encryption_reset_footer">"Only reset your identity if you dont have access to another signed-in device and youve lost your recovery key."</string>
<string name="screen_encryption_reset_subtitle">"If youre not signed in to any other devices and youve lost your recovery key, then youll need to reset your identity to continue using the app. "</string>
<string name="screen_encryption_reset_title">"Reset your identity in case you cant confirm another way"</string>
<string name="screen_key_backup_disable_confirmation_action_turn_off">"Turn off"</string> <string name="screen_key_backup_disable_confirmation_action_turn_off">"Turn off"</string>
<string name="screen_key_backup_disable_confirmation_description">"You will lose your encrypted messages if you are signed out of all devices."</string> <string name="screen_key_backup_disable_confirmation_description">"You will lose your encrypted messages if you are signed out of all devices."</string>
<string name="screen_key_backup_disable_confirmation_title">"Are you sure you want to turn off backup?"</string> <string name="screen_key_backup_disable_confirmation_title">"Are you sure you want to turn off backup?"</string>
@ -51,4 +57,11 @@
<string name="screen_recovery_key_setup_generate_key_description">"Make sure you can store your recovery key somewhere safe"</string> <string name="screen_recovery_key_setup_generate_key_description">"Make sure you can store your recovery key somewhere safe"</string>
<string name="screen_recovery_key_setup_success">"Recovery setup successful"</string> <string name="screen_recovery_key_setup_success">"Recovery setup successful"</string>
<string name="screen_recovery_key_setup_title">"Set up recovery"</string> <string name="screen_recovery_key_setup_title">"Set up recovery"</string>
<string name="screen_reset_encryption_confirmation_alert_action">"Yes, reset now"</string>
<string name="screen_reset_encryption_confirmation_alert_subtitle">"This process is irreversible."</string>
<string name="screen_reset_encryption_confirmation_alert_title">"Are you sure you want to reset your encryption?"</string>
<string name="screen_reset_encryption_password_error">"An unknown error happened. Please check your account password is correct and try again."</string>
<string name="screen_reset_encryption_password_placeholder">"Enter…"</string>
<string name="screen_reset_encryption_password_subtitle">"Confirm that you want to reset your encryption."</string>
<string name="screen_reset_encryption_password_title">"Enter your account password to continue"</string>
</resources> </resources>

View file

@ -76,12 +76,12 @@ class ResetIdentityPasswordViewTest {
} }
@Test @Test
fun `clicking OK dismisses the error dialog`() { fun `modifying the password dismisses the error state`() {
val eventsRecorder = EventsRecorder<ResetIdentityPasswordEvent>() val eventsRecorder = EventsRecorder<ResetIdentityPasswordEvent>()
rule.setResetPasswordView( rule.setResetPasswordView(
ResetIdentityPasswordState(resetAction = AsyncAction.Failure(IllegalStateException("A failure")), eventSink = eventsRecorder), ResetIdentityPasswordState(resetAction = AsyncAction.Failure(IllegalStateException("A failure")), eventSink = eventsRecorder),
) )
rule.clickOn(CommonStrings.action_ok) rule.onNodeWithText("Password").performTextInput("A password")
eventsRecorder.assertSingle(ResetIdentityPasswordEvent.DismissError) eventsRecorder.assertSingle(ResetIdentityPasswordEvent.DismissError)
} }

View file

@ -20,6 +20,7 @@ import androidx.activity.ComponentActivity
import androidx.compose.ui.test.junit4.AndroidComposeTestRule import androidx.compose.ui.test.junit4.AndroidComposeTestRule
import androidx.compose.ui.test.junit4.createAndroidComposeRule import androidx.compose.ui.test.junit4.createAndroidComposeRule
import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.junit.runners.AndroidJUnit4
import io.element.android.features.securebackup.impl.R
import io.element.android.libraries.ui.strings.CommonStrings import io.element.android.libraries.ui.strings.CommonStrings
import io.element.android.tests.testutils.EnsureNeverCalled import io.element.android.tests.testutils.EnsureNeverCalled
import io.element.android.tests.testutils.EventsRecorder import io.element.android.tests.testutils.EventsRecorder
@ -80,7 +81,7 @@ class ResetIdentityRootViewTest {
ResetIdentityRootState(displayConfirmationDialog = true, eventSink = {}), ResetIdentityRootState(displayConfirmationDialog = true, eventSink = {}),
onContinue = it, onContinue = it,
) )
rule.clickOn(CommonStrings.screen_reset_encryption_confirmation_alert_action) rule.clickOn(R.string.screen_reset_encryption_confirmation_alert_action)
} }
} }

View file

@ -22,13 +22,14 @@ import io.element.android.libraries.matrix.api.encryption.EnableRecoveryProgress
import io.element.android.libraries.matrix.api.encryption.EncryptionService import io.element.android.libraries.matrix.api.encryption.EncryptionService
import io.element.android.libraries.matrix.api.encryption.IdentityResetHandle import io.element.android.libraries.matrix.api.encryption.IdentityResetHandle
import io.element.android.libraries.matrix.api.encryption.RecoveryState import io.element.android.libraries.matrix.api.encryption.RecoveryState
import io.element.android.tests.testutils.lambda.lambdaError
import io.element.android.tests.testutils.simulateLongTask import io.element.android.tests.testutils.simulateLongTask
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.flowOf
class FakeEncryptionService( class FakeEncryptionService(
var startIdentityResetLambda: () -> Result<IdentityResetHandle?> = { error("Not implemented") }, var startIdentityResetLambda: () -> Result<IdentityResetHandle?> = { lambdaError() },
) : EncryptionService { ) : EncryptionService {
private var disableRecoveryFailure: Exception? = null private var disableRecoveryFailure: Exception? = null
override val backupStateStateFlow: MutableStateFlow<BackupState> = MutableStateFlow(BackupState.UNKNOWN) override val backupStateStateFlow: MutableStateFlow<BackupState> = MutableStateFlow(BackupState.UNKNOWN)

View file

@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2022 New Vector Ltd * Copyright (c) 2024 New Vector Ltd
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -43,13 +43,8 @@ dependencies {
implementation(projects.libraries.androidutils) implementation(projects.libraries.androidutils)
implementation(projects.libraries.architecture) implementation(projects.libraries.architecture)
implementation(projects.libraries.matrix.api) implementation(projects.libraries.matrix.api)
implementation(projects.libraries.matrix.api)
implementation(projects.libraries.network)
implementation(projects.libraries.designsystem) implementation(projects.libraries.designsystem)
implementation(projects.libraries.testtags)
implementation(projects.libraries.uiStrings) implementation(projects.libraries.uiStrings)
implementation(projects.libraries.permissions.api)
implementation(projects.libraries.qrcode)
implementation(libs.androidx.browser) implementation(libs.androidx.browser)
implementation(platform(libs.network.retrofit.bom)) implementation(platform(libs.network.retrofit.bom))
implementation(libs.network.retrofit) implementation(libs.network.retrofit)
@ -57,15 +52,12 @@ dependencies {
api(projects.libraries.oidc.api) api(projects.libraries.oidc.api)
testImplementation(libs.test.junit) testImplementation(libs.test.junit)
testImplementation(libs.androidx.compose.ui.test.junit)
testImplementation(libs.androidx.test.ext.junit) testImplementation(libs.androidx.test.ext.junit)
testImplementation(libs.coroutines.test) testImplementation(libs.coroutines.test)
testImplementation(libs.molecule.runtime) testImplementation(libs.molecule.runtime)
testImplementation(libs.test.robolectric)
testImplementation(libs.test.truth) testImplementation(libs.test.truth)
testImplementation(libs.test.turbine) testImplementation(libs.test.turbine)
testImplementation(projects.libraries.matrix.test) testImplementation(projects.libraries.matrix.test)
testImplementation(projects.libraries.permissions.test) testImplementation(projects.libraries.permissions.test)
testImplementation(projects.tests.testutils) testImplementation(projects.tests.testutils)
testReleaseImplementation(libs.androidx.compose.ui.test.manifest)
} }

View file

@ -268,12 +268,6 @@ Reason: %1$s."</string>
<string name="invite_friends_text">"Hey, talk to me on %1$s: %2$s"</string> <string name="invite_friends_text">"Hey, talk to me on %1$s: %2$s"</string>
<string name="login_initial_device_name_android">"%1$s Android"</string> <string name="login_initial_device_name_android">"%1$s Android"</string>
<string name="preference_rageshake">"Rageshake to report bug"</string> <string name="preference_rageshake">"Rageshake to report bug"</string>
<string name="screen_encryption_reset_bullet_1">"Your account details, contacts, preferences, and chat list will be kept"</string>
<string name="screen_encryption_reset_bullet_2">"You will lose your existing message history"</string>
<string name="screen_encryption_reset_bullet_3">"You will need to verify all your existing devices and contacts again"</string>
<string name="screen_encryption_reset_footer">"Only reset your identity if you dont have access to another signed-in device and youve lost your recovery key."</string>
<string name="screen_encryption_reset_subtitle">"If youre not signed in to any other devices and youve lost your recovery key, then youll need to reset your identity to continue using the app. "</string>
<string name="screen_encryption_reset_title">"Reset your identity in case you cant confirm another way"</string>
<string name="screen_media_picker_error_failed_selection">"Failed selecting media, please try again."</string> <string name="screen_media_picker_error_failed_selection">"Failed selecting media, please try again."</string>
<string name="screen_media_upload_preview_error_failed_processing">"Failed processing media to upload, please try again."</string> <string name="screen_media_upload_preview_error_failed_processing">"Failed processing media to upload, please try again."</string>
<string name="screen_media_upload_preview_error_failed_sending">"Failed uploading media, please try again."</string> <string name="screen_media_upload_preview_error_failed_sending">"Failed uploading media, please try again."</string>
@ -284,12 +278,6 @@ Reason: %1$s."</string>
<item quantity="other">"%1$d Pinned messages"</item> <item quantity="other">"%1$d Pinned messages"</item>
</plurals> </plurals>
<string name="screen_pinned_timeline_screen_title_empty">"Pinned messages"</string> <string name="screen_pinned_timeline_screen_title_empty">"Pinned messages"</string>
<string name="screen_reset_encryption_confirmation_alert_action">"Yes, reset now"</string>
<string name="screen_reset_encryption_confirmation_alert_subtitle">"This process is irreversible."</string>
<string name="screen_reset_encryption_confirmation_alert_title">"Are you sure you want to reset your encryption?"</string>
<string name="screen_reset_encryption_password_placeholder">"Enter…"</string>
<string name="screen_reset_encryption_password_subtitle">"Confirm that you want to reset your encryption."</string>
<string name="screen_reset_encryption_password_title">"Enter your account password to continue"</string>
<string name="screen_room_details_pinned_events_row_title">"Pinned messages"</string> <string name="screen_room_details_pinned_events_row_title">"Pinned messages"</string>
<string name="screen_room_error_failed_processing_media">"Failed processing media to upload, please try again."</string> <string name="screen_room_error_failed_processing_media">"Failed processing media to upload, please try again."</string>
<string name="screen_room_error_failed_retrieving_user_details">"Could not retrieve user details"</string> <string name="screen_room_error_failed_retrieving_user_details">"Could not retrieve user details"</string>

View file

@ -210,7 +210,10 @@
"screen_chat_backup_.*", "screen_chat_backup_.*",
"screen_key_backup_disable_.*", "screen_key_backup_disable_.*",
"screen_recovery_key_.*", "screen_recovery_key_.*",
"screen_create_new_recovery_key_.*" "screen_create_new_recovery_key_.*",
"screen_encryption_reset.*",
"screen_reset_encryption.*",
"screen\\.reset_encryption.*"
] ]
}, },
{ {