diff --git a/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt b/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt index 5a8db185ef..b304f99c6b 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt @@ -260,7 +260,7 @@ class LoggedInFlowNode @AssistedInject constructor( } override fun onSetUpRecoveryClick() { - backstack.push(NavTarget.SecureBackup(initialElement = SecureBackupEntryPoint.InitialTarget.SetUpRecovery)) + backstack.push(NavTarget.SecureBackup(initialElement = SecureBackupEntryPoint.InitialTarget.Root)) } override fun onSessionConfirmRecoveryKeyClick() { 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 10e59bf334..0c758852db 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 @@ -138,8 +138,8 @@ private fun ColumnScope.ManageAppSection( } if (state.showSecureBackup) { ListItem( - headlineContent = { Text(stringResource(id = CommonStrings.common_chat_backup)) }, - leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.KeySolid())), + headlineContent = { Text(stringResource(id = CommonStrings.common_encryption)) }, + leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.Key())), trailingContent = ListItemContent.Badge.takeIf { state.showSecureBackupBadge }, onClick = onSecureBackupClick, ) diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/components/SetUpRecoveryKeyBanner.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/components/SetUpRecoveryKeyBanner.kt index 75fd6bda9b..971edaf540 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/components/SetUpRecoveryKeyBanner.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/components/SetUpRecoveryKeyBanner.kt @@ -25,6 +25,7 @@ internal fun SetUpRecoveryKeyBanner( modifier = modifier, title = stringResource(R.string.banner_set_up_recovery_title), content = stringResource(R.string.banner_set_up_recovery_content), + actionText = stringResource(R.string.banner_set_up_recovery_submit), onSubmitClick = onContinueClick, onDismissClick = onDismissClick, ) diff --git a/features/roomlist/impl/src/main/res/values/localazy.xml b/features/roomlist/impl/src/main/res/values/localazy.xml index 79f9b493e6..9502826dd9 100644 --- a/features/roomlist/impl/src/main/res/values/localazy.xml +++ b/features/roomlist/impl/src/main/res/values/localazy.xml @@ -4,8 +4,9 @@ "Your server now supports a new, faster protocol. Log out and log back in to upgrade now. Doing this now will help you avoid a forced logout when the old protocol is removed later." "Your homeserver no longer supports the old protocol. Please log out and log back in to continue using the app." "Upgrade available" - "Generate a new recovery key that can be used to restore your encrypted message history in case you lose access to your devices." - "Set up recovery" + "Recover your cryptographic identity and message history with a recovery key if you have lost all your existing devices." + "Set up recovery" + "Set up recovery to protect your account" "Your chat backup is currently out of sync. You need to enter your recovery key to maintain access to your chat backup." "Enter your recovery key" "To ensure you never miss an important call, please change your settings to allow full-screen notifications when your phone is locked." diff --git a/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListViewTest.kt b/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListViewTest.kt index 63327d0d49..66821ada35 100644 --- a/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListViewTest.kt +++ b/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListViewTest.kt @@ -121,12 +121,9 @@ class RoomListViewTest { ), onSetUpRecoveryClick = callback, ) - // Remove automatic initial events eventsRecorder.clear() - - rule.clickOn(CommonStrings.action_continue) - + rule.clickOn(R.string.banner_set_up_recovery_submit) eventsRecorder.assertEmpty() } } diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/SecureBackupFlowNode.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/SecureBackupFlowNode.kt index d3388cb37e..5d9aeec21e 100644 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/SecureBackupFlowNode.kt +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/SecureBackupFlowNode.kt @@ -22,7 +22,6 @@ import dagger.assisted.AssistedInject import io.element.android.anvilannotations.ContributesNode import io.element.android.features.securebackup.api.SecureBackupEntryPoint import io.element.android.features.securebackup.impl.disable.SecureBackupDisableNode -import io.element.android.features.securebackup.impl.enable.SecureBackupEnableNode import io.element.android.features.securebackup.impl.enter.SecureBackupEnterRecoveryKeyNode import io.element.android.features.securebackup.impl.reset.ResetIdentityFlowNode import io.element.android.features.securebackup.impl.root.SecureBackupRootNode @@ -63,9 +62,6 @@ class SecureBackupFlowNode @AssistedInject constructor( @Parcelize data object Disable : NavTarget - @Parcelize - data object Enable : NavTarget - @Parcelize data object EnterRecoveryKey : NavTarget @@ -91,10 +87,6 @@ class SecureBackupFlowNode @AssistedInject constructor( backstack.push(NavTarget.Disable) } - override fun onEnableClick() { - backstack.push(NavTarget.Enable) - } - override fun onConfirmRecoveryKeyClick() { backstack.push(NavTarget.EnterRecoveryKey) } @@ -116,9 +108,6 @@ class SecureBackupFlowNode @AssistedInject constructor( NavTarget.Disable -> { createNode(buildContext) } - NavTarget.Enable -> { - createNode(buildContext) - } NavTarget.EnterRecoveryKey -> { val callback = object : SecureBackupEnterRecoveryKeyNode.Callback { override fun onEnterRecoveryKeySuccess() { diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/disable/SecureBackupDisablePresenter.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/disable/SecureBackupDisablePresenter.kt index 4e661e0a13..fc721d23ca 100644 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/disable/SecureBackupDisablePresenter.kt +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/disable/SecureBackupDisablePresenter.kt @@ -37,11 +37,7 @@ class SecureBackupDisablePresenter @Inject constructor( val coroutineScope = rememberCoroutineScope() fun handleEvents(event: SecureBackupDisableEvents) { when (event) { - is SecureBackupDisableEvents.DisableBackup -> if (disableAction.value.isConfirming()) { - coroutineScope.disableBackup(disableAction) - } else { - disableAction.value = AsyncAction.ConfirmingNoParams - } + is SecureBackupDisableEvents.DisableBackup -> coroutineScope.disableBackup(disableAction) SecureBackupDisableEvents.DismissDialogs -> { disableAction.value = AsyncAction.Uninitialized } 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 1e603ae20d..8d32dba4c2 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 @@ -7,6 +7,7 @@ package io.element.android.features.securebackup.impl.disable +import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.ColumnScope @@ -25,7 +26,6 @@ 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 import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.theme.components.Button @@ -44,7 +44,7 @@ fun SecureBackupDisableView( onBackClick = onBackClick, title = stringResource(id = R.string.screen_key_backup_disable_title), subTitle = stringResource(id = R.string.screen_key_backup_disable_description), - iconStyle = BigIcon.Style.Default(CompoundIcons.KeyOffSolid()), + iconStyle = BigIcon.Style.AlertSolid, buttons = { Buttons(state = state) }, ) { Content(state = state) @@ -52,12 +52,6 @@ fun SecureBackupDisableView( AsyncActionView( async = state.disableAction, - confirmationDialog = { - SecureBackupDisableConfirmationDialog( - onConfirm = { state.eventSink.invoke(SecureBackupDisableEvents.DisableBackup) }, - onDismiss = { state.eventSink.invoke(SecureBackupDisableEvents.DismissDialogs) }, - ) - }, progressDialog = {}, errorMessage = { it.message ?: it.toString() }, onErrorDismiss = { state.eventSink.invoke(SecureBackupDisableEvents.DismissDialogs) }, @@ -65,18 +59,6 @@ fun SecureBackupDisableView( ) } -@Composable -private fun SecureBackupDisableConfirmationDialog(onConfirm: () -> Unit, onDismiss: () -> Unit) { - ConfirmationDialog( - title = stringResource(id = R.string.screen_key_backup_disable_confirmation_title), - content = stringResource(id = R.string.screen_key_backup_disable_confirmation_description), - submitText = stringResource(id = R.string.screen_key_backup_disable_confirmation_action_turn_off), - destructiveSubmit = true, - onSubmitClick = onConfirm, - onDismiss = onDismiss, - ) -} - @Composable private fun ColumnScope.Buttons( state: SecureBackupDisableState, @@ -105,15 +87,20 @@ private fun Content(state: SecureBackupDisableState) { @Composable private fun SecureBackupDisableItem(text: String) { - Row(modifier = Modifier.fillMaxWidth()) { + Row( + modifier = Modifier + .fillMaxWidth() + .background(color = ElementTheme.colors.bgActionSecondaryHovered) + .padding(horizontal = 16.dp, vertical = 12.dp), + horizontalArrangement = Arrangement.spacedBy(12.dp), + ) { Icon( imageVector = CompoundIcons.Close(), contentDescription = null, tint = ElementTheme.colors.iconCriticalPrimary, - modifier = Modifier.size(20.dp) + modifier = Modifier.size(24.dp) ) Text( - modifier = Modifier.padding(start = 8.dp, end = 4.dp), text = text, color = ElementTheme.colors.textSecondary, style = ElementTheme.typography.fontBodyMdRegular, diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/enable/SecureBackupEnableEvents.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/enable/SecureBackupEnableEvents.kt deleted file mode 100644 index 57e43268d1..0000000000 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/enable/SecureBackupEnableEvents.kt +++ /dev/null @@ -1,13 +0,0 @@ -/* - * Copyright 2023, 2024 New Vector Ltd. - * - * SPDX-License-Identifier: AGPL-3.0-only - * Please see LICENSE in the repository root for full details. - */ - -package io.element.android.features.securebackup.impl.enable - -sealed interface SecureBackupEnableEvents { - data object EnableBackup : SecureBackupEnableEvents - data object DismissDialog : SecureBackupEnableEvents -} diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/enable/SecureBackupEnableNode.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/enable/SecureBackupEnableNode.kt deleted file mode 100644 index 1ae7044edc..0000000000 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/enable/SecureBackupEnableNode.kt +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 2023, 2024 New Vector Ltd. - * - * SPDX-License-Identifier: AGPL-3.0-only - * Please see LICENSE in the repository root for full details. - */ - -package io.element.android.features.securebackup.impl.enable - -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 dagger.assisted.Assisted -import dagger.assisted.AssistedInject -import io.element.android.anvilannotations.ContributesNode -import io.element.android.libraries.di.SessionScope - -@ContributesNode(SessionScope::class) -class SecureBackupEnableNode @AssistedInject constructor( - @Assisted buildContext: BuildContext, - @Assisted plugins: List, - private val presenter: SecureBackupEnablePresenter, -) : Node(buildContext, plugins = plugins) { - @Composable - override fun View(modifier: Modifier) { - val state = presenter.present() - SecureBackupEnableView( - state = state, - modifier = modifier, - onSuccess = ::navigateUp, - onBackClick = ::navigateUp, - ) - } -} diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/enable/SecureBackupEnablePresenter.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/enable/SecureBackupEnablePresenter.kt deleted file mode 100644 index ae2ee57c9c..0000000000 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/enable/SecureBackupEnablePresenter.kt +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright 2023, 2024 New Vector Ltd. - * - * SPDX-License-Identifier: AGPL-3.0-only - * Please see LICENSE in the repository root for full details. - */ - -package io.element.android.features.securebackup.impl.enable - -import androidx.compose.runtime.Composable -import androidx.compose.runtime.MutableState -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope -import io.element.android.features.securebackup.impl.loggerTagDisable -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.encryption.EncryptionService -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.launch -import timber.log.Timber -import javax.inject.Inject - -class SecureBackupEnablePresenter @Inject constructor( - private val encryptionService: EncryptionService, -) : Presenter { - @Composable - override fun present(): SecureBackupEnableState { - val enableAction = remember { mutableStateOf>(AsyncAction.Uninitialized) } - val coroutineScope = rememberCoroutineScope() - fun handleEvents(event: SecureBackupEnableEvents) { - when (event) { - is SecureBackupEnableEvents.EnableBackup -> - coroutineScope.enableBackup(enableAction) - SecureBackupEnableEvents.DismissDialog -> { - enableAction.value = AsyncAction.Uninitialized - } - } - } - - return SecureBackupEnableState( - enableAction = enableAction.value, - eventSink = ::handleEvents - ) - } - - private fun CoroutineScope.enableBackup(action: MutableState>) = launch { - suspend { - Timber.tag(loggerTagDisable.value).d("Calling encryptionService.enableBackups()") - encryptionService.enableBackups().getOrThrow() - }.runCatchingUpdatingState(action) - } -} diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/enable/SecureBackupEnableState.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/enable/SecureBackupEnableState.kt deleted file mode 100644 index 058ba49cb6..0000000000 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/enable/SecureBackupEnableState.kt +++ /dev/null @@ -1,15 +0,0 @@ -/* - * Copyright 2023, 2024 New Vector Ltd. - * - * SPDX-License-Identifier: AGPL-3.0-only - * Please see LICENSE in the repository root for full details. - */ - -package io.element.android.features.securebackup.impl.enable - -import io.element.android.libraries.architecture.AsyncAction - -data class SecureBackupEnableState( - val enableAction: AsyncAction, - val eventSink: (SecureBackupEnableEvents) -> Unit -) diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/enable/SecureBackupEnableStateProvider.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/enable/SecureBackupEnableStateProvider.kt deleted file mode 100644 index 482f11f1fd..0000000000 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/enable/SecureBackupEnableStateProvider.kt +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright 2023, 2024 New Vector Ltd. - * - * SPDX-License-Identifier: AGPL-3.0-only - * Please see LICENSE in the repository root for full details. - */ - -package io.element.android.features.securebackup.impl.enable - -import androidx.compose.ui.tooling.preview.PreviewParameterProvider -import io.element.android.libraries.architecture.AsyncAction - -open class SecureBackupEnableStateProvider : PreviewParameterProvider { - override val values: Sequence - get() = sequenceOf( - aSecureBackupEnableState(), - aSecureBackupEnableState(enableAction = AsyncAction.Loading), - aSecureBackupEnableState(enableAction = AsyncAction.Failure(Exception("Failed to enable"))), - // Add other states here - ) -} - -fun aSecureBackupEnableState( - enableAction: AsyncAction = AsyncAction.Uninitialized, -) = SecureBackupEnableState( - enableAction = enableAction, - eventSink = {} -) 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 deleted file mode 100644 index f14e361c46..0000000000 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/enable/SecureBackupEnableView.kt +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright 2023, 2024 New Vector Ltd. - * - * SPDX-License-Identifier: AGPL-3.0-only - * Please see LICENSE in the repository root for full details. - */ - -package io.element.android.features.securebackup.impl.enable - -import androidx.compose.foundation.layout.ColumnScope -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.res.stringResource -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 -import io.element.android.libraries.designsystem.theme.components.Button - -@Composable -fun SecureBackupEnableView( - state: SecureBackupEnableState, - onSuccess: () -> Unit, - onBackClick: () -> Unit, - modifier: Modifier = Modifier, -) { - FlowStepPage( - modifier = modifier, - onBackClick = onBackClick, - title = stringResource(id = R.string.screen_chat_backup_key_backup_action_enable), - iconStyle = BigIcon.Style.Default(CompoundIcons.KeySolid()), - buttons = { Buttons(state = state) } - ) - AsyncActionView( - async = state.enableAction, - progressDialog = { }, - onSuccess = { onSuccess() }, - onErrorDismiss = { state.eventSink.invoke(SecureBackupEnableEvents.DismissDialog) } - ) -} - -@Composable -private fun ColumnScope.Buttons( - state: SecureBackupEnableState, -) { - Button( - text = stringResource(id = R.string.screen_chat_backup_key_backup_action_enable), - showProgress = state.enableAction.isLoading(), - modifier = Modifier.fillMaxWidth(), - onClick = { state.eventSink.invoke(SecureBackupEnableEvents.EnableBackup) } - ) -} - -@PreviewsDayNight -@Composable -internal fun SecureBackupEnableViewPreview( - @PreviewParameter(SecureBackupEnableStateProvider::class) state: SecureBackupEnableState -) = ElementPreview { - SecureBackupEnableView( - state = state, - onSuccess = {}, - onBackClick = {}, - ) -} diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/root/SecureBackupRootEvents.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/root/SecureBackupRootEvents.kt index 89de592805..f80e3c638b 100644 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/root/SecureBackupRootEvents.kt +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/root/SecureBackupRootEvents.kt @@ -9,4 +9,7 @@ package io.element.android.features.securebackup.impl.root sealed interface SecureBackupRootEvents { data object RetryKeyBackupState : SecureBackupRootEvents + data object EnableKeyStorage : SecureBackupRootEvents + data object DisplayKeyStorageDisabledError : SecureBackupRootEvents + data object DismissDialog : SecureBackupRootEvents } diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/root/SecureBackupRootNode.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/root/SecureBackupRootNode.kt index 113c569d39..a0eace77e6 100644 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/root/SecureBackupRootNode.kt +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/root/SecureBackupRootNode.kt @@ -34,7 +34,6 @@ class SecureBackupRootNode @AssistedInject constructor( fun onSetupClick() fun onChangeClick() fun onDisableClick() - fun onEnableClick() fun onConfirmRecoveryKeyClick() } @@ -50,10 +49,6 @@ class SecureBackupRootNode @AssistedInject constructor( plugins().forEach { it.onDisableClick() } } - private fun onEnableClick() { - plugins().forEach { it.onEnableClick() } - } - private fun onConfirmRecoveryKeyClick() { plugins().forEach { it.onConfirmRecoveryKeyClick() } } @@ -71,7 +66,6 @@ class SecureBackupRootNode @AssistedInject constructor( onBackClick = ::navigateUp, onSetupClick = ::onSetupClick, onChangeClick = ::onChangeClick, - onEnableClick = ::onEnableClick, onDisableClick = ::onDisableClick, onConfirmRecoveryKeyClick = ::onConfirmRecoveryKeyClick, onLearnMoreClick = { onLearnMoreClick(uriHandler) }, diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/root/SecureBackupRootPresenter.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/root/SecureBackupRootPresenter.kt index 075c9980a1..655be4f7a2 100644 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/root/SecureBackupRootPresenter.kt +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/root/SecureBackupRootPresenter.kt @@ -15,7 +15,10 @@ 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.securebackup.impl.loggerTagDisable import io.element.android.features.securebackup.impl.loggerTagRoot +import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.runCatchingUpdatingState @@ -41,7 +44,8 @@ class SecureBackupRootPresenter @Inject constructor( val backupState by encryptionService.backupStateStateFlow.collectAsState() val recoveryState by encryptionService.recoveryStateStateFlow.collectAsState() - + val enableAction: MutableState> = remember { mutableStateOf(AsyncAction.Uninitialized) } + var displayKeyStorageDisabledError by remember { mutableStateOf(false) } Timber.tag(loggerTagRoot.value).d("backupState: $backupState") Timber.tag(loggerTagRoot.value).d("recoveryState: $recoveryState") @@ -56,14 +60,22 @@ class SecureBackupRootPresenter @Inject constructor( fun handleEvents(event: SecureBackupRootEvents) { when (event) { SecureBackupRootEvents.RetryKeyBackupState -> localCoroutineScope.getKeyBackupStatus(doesBackupExistOnServerAction) + SecureBackupRootEvents.EnableKeyStorage -> localCoroutineScope.enableBackup(enableAction) + SecureBackupRootEvents.DismissDialog -> { + enableAction.value = AsyncAction.Uninitialized + displayKeyStorageDisabledError = false + } + SecureBackupRootEvents.DisplayKeyStorageDisabledError -> displayKeyStorageDisabledError = true } } return SecureBackupRootState( + enableAction = enableAction.value, backupState = backupState, doesBackupExistOnServer = doesBackupExistOnServerAction.value, recoveryState = recoveryState, appName = buildMeta.applicationName, + displayKeyStorageDisabledError = displayKeyStorageDisabledError, snackbarMessage = snackbarMessage, eventSink = ::handleEvents, ) @@ -74,4 +86,11 @@ class SecureBackupRootPresenter @Inject constructor( encryptionService.doesBackupExistOnServer().getOrThrow() }.runCatchingUpdatingState(action) } + + private fun CoroutineScope.enableBackup(action: MutableState>) = launch { + suspend { + Timber.tag(loggerTagDisable.value).d("Calling encryptionService.enableBackups()") + encryptionService.enableBackups().getOrThrow() + }.runCatchingUpdatingState(action) + } } diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/root/SecureBackupRootState.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/root/SecureBackupRootState.kt index 944706e6cc..5da4f0c5f3 100644 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/root/SecureBackupRootState.kt +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/root/SecureBackupRootState.kt @@ -7,16 +7,31 @@ package io.element.android.features.securebackup.impl.root +import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.designsystem.utils.snackbar.SnackbarMessage import io.element.android.libraries.matrix.api.encryption.BackupState import io.element.android.libraries.matrix.api.encryption.RecoveryState data class SecureBackupRootState( + val enableAction: AsyncAction, val backupState: BackupState, val doesBackupExistOnServer: AsyncData, val recoveryState: RecoveryState, val appName: String, + val displayKeyStorageDisabledError: Boolean, val snackbarMessage: SnackbarMessage?, val eventSink: (SecureBackupRootEvents) -> Unit, -) +) { + val isKeyStorageEnabled: Boolean + get() = when (backupState) { + BackupState.UNKNOWN -> doesBackupExistOnServer.dataOrNull() == true + BackupState.CREATING, + BackupState.ENABLING, + BackupState.RESUMING, + BackupState.DOWNLOADING, + BackupState.ENABLED -> true + BackupState.WAITING_FOR_SYNC, + BackupState.DISABLING -> false + } +} diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/root/SecureBackupRootStateProvider.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/root/SecureBackupRootStateProvider.kt index c07c166975..003fab5f3f 100644 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/root/SecureBackupRootStateProvider.kt +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/root/SecureBackupRootStateProvider.kt @@ -8,6 +8,7 @@ package io.element.android.features.securebackup.impl.root import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.designsystem.utils.snackbar.SnackbarMessage import io.element.android.libraries.matrix.api.encryption.BackupState @@ -22,28 +23,47 @@ open class SecureBackupRootStateProvider : PreviewParameterProvider = AsyncAction.Uninitialized, backupState: BackupState = BackupState.UNKNOWN, doesBackupExistOnServer: AsyncData = AsyncData.Uninitialized, recoveryState: RecoveryState = RecoveryState.UNKNOWN, + displayKeyStorageDisabledError: Boolean = false, snackbarMessage: SnackbarMessage? = null, ) = SecureBackupRootState( + enableAction = enableAction, backupState = backupState, doesBackupExistOnServer = doesBackupExistOnServer, recoveryState = recoveryState, appName = "Element", + displayKeyStorageDisabledError = displayKeyStorageDisabledError, snackbarMessage = snackbarMessage, eventSink = {}, ) diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/root/SecureBackupRootView.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/root/SecureBackupRootView.kt index 47e3068869..2a4cfbbe6e 100644 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/root/SecureBackupRootView.kt +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/root/SecureBackupRootView.kt @@ -7,28 +7,27 @@ package io.element.android.features.securebackup.impl.root -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.progressSemantics import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier 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.features.securebackup.impl.R import io.element.android.libraries.architecture.AsyncData -import io.element.android.libraries.designsystem.components.async.AsyncLoading +import io.element.android.libraries.designsystem.components.async.AsyncActionView +import io.element.android.libraries.designsystem.components.dialogs.ErrorDialog import io.element.android.libraries.designsystem.components.list.ListItemContent -import io.element.android.libraries.designsystem.components.preferences.PreferenceDivider import io.element.android.libraries.designsystem.components.preferences.PreferencePage -import io.element.android.libraries.designsystem.components.preferences.PreferenceText import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.text.buildAnnotatedStringWithStyledPart import io.element.android.libraries.designsystem.theme.components.CircularProgressIndicator +import io.element.android.libraries.designsystem.theme.components.HorizontalDivider import io.element.android.libraries.designsystem.theme.components.ListItem import io.element.android.libraries.designsystem.theme.components.Text -import io.element.android.libraries.designsystem.theme.components.TextButton import io.element.android.libraries.designsystem.utils.snackbar.SnackbarHost import io.element.android.libraries.designsystem.utils.snackbar.rememberSnackbarHostState import io.element.android.libraries.matrix.api.encryption.BackupState @@ -41,7 +40,6 @@ fun SecureBackupRootView( onBackClick: () -> Unit, onSetupClick: () -> Unit, onChangeClick: () -> Unit, - onEnableClick: () -> Unit, onDisableClick: () -> Unit, onConfirmRecoveryKeyClick: () -> Unit, onLearnMoreClick: () -> Unit, @@ -52,122 +50,186 @@ fun SecureBackupRootView( PreferencePage( modifier = modifier, onBackClick = onBackClick, - title = stringResource(id = CommonStrings.common_chat_backup), + title = stringResource(id = CommonStrings.common_encryption), snackbarHost = { SnackbarHost(snackbarHostState) }, ) { - val text = buildAnnotatedStringWithStyledPart( - fullTextRes = R.string.screen_chat_backup_key_backup_description, - coloredTextRes = CommonStrings.action_learn_more, - color = ElementTheme.colors.textPrimary, - underline = false, - bold = true, - ) - PreferenceText( - title = stringResource(id = R.string.screen_chat_backup_key_backup_title), - subtitleAnnotated = text, + ListItem( + headlineContent = { + Text( + text = stringResource(id = R.string.screen_chat_backup_key_backup_title), + ) + }, + supportingContent = { + Text( + text = buildAnnotatedStringWithStyledPart( + fullTextRes = R.string.screen_chat_backup_key_backup_description, + coloredTextRes = CommonStrings.action_learn_more, + color = ElementTheme.colors.textPrimary, + underline = false, + bold = true, + ), + ) + }, onClick = onLearnMoreClick, ) - // Disable / Enable backup - when (state.backupState) { - BackupState.WAITING_FOR_SYNC -> Unit - BackupState.UNKNOWN -> { - when (state.doesBackupExistOnServer) { - is AsyncData.Success -> when (state.doesBackupExistOnServer.data) { - true -> { - PreferenceText( - title = stringResource(id = R.string.screen_chat_backup_key_backup_action_disable), - tintColor = ElementTheme.colors.textCriticalPrimary, - onClick = onDisableClick, + // Disable / Enable key storage + ListItem( + headlineContent = { + Text( + text = stringResource(id = R.string.screen_chat_backup_key_storage_toggle_title), + ) + }, + trailingContent = when (state.backupState) { + BackupState.WAITING_FOR_SYNC, + BackupState.DISABLING -> ListItemContent.Custom { LoadingView() } + BackupState.UNKNOWN -> { + when (state.doesBackupExistOnServer) { + is AsyncData.Success -> { + ListItemContent.Switch(checked = state.doesBackupExistOnServer.data) + } + is AsyncData.Loading, + AsyncData.Uninitialized -> ListItemContent.Custom { LoadingView() } + is AsyncData.Failure -> ListItemContent.Custom { + Text( + text = stringResource(id = CommonStrings.action_retry) ) } - false -> { - PreferenceText( - title = stringResource(id = R.string.screen_chat_backup_key_backup_action_enable), - onClick = onEnableClick, - ) - } - } - is AsyncData.Loading, - AsyncData.Uninitialized -> { - ListItem(headlineContent = { - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.Center, - ) { - CircularProgressIndicator() - } - }) - } - is AsyncData.Failure -> { - ListItem( - headlineContent = { - Text( - text = stringResource(id = CommonStrings.error_unknown), - ) - }, - trailingContent = ListItemContent.Custom { - TextButton( - text = stringResource( - id = CommonStrings.action_retry - ), - onClick = { state.eventSink.invoke(SecureBackupRootEvents.RetryKeyBackupState) } - ) - } - ) - - PreferenceText( - title = stringResource(id = R.string.screen_chat_backup_key_backup_action_enable), - onClick = onEnableClick, - ) } } - } - BackupState.CREATING, - BackupState.ENABLING, - BackupState.RESUMING, - BackupState.ENABLED, - BackupState.DOWNLOADING -> { - PreferenceText( - title = stringResource(id = R.string.screen_chat_backup_key_backup_action_disable), - tintColor = ElementTheme.colors.textCriticalPrimary, - onClick = onDisableClick, - ) - } - BackupState.DISABLING -> { - AsyncLoading() - } - } - - PreferenceDivider() - + BackupState.CREATING, + BackupState.ENABLING, + BackupState.RESUMING, + BackupState.ENABLED, + BackupState.DOWNLOADING -> ListItemContent.Switch(checked = true) + }, + onClick = { + when (state.backupState) { + BackupState.WAITING_FOR_SYNC, + BackupState.DISABLING -> Unit + BackupState.UNKNOWN -> { + when (state.doesBackupExistOnServer) { + is AsyncData.Success -> { + if (state.doesBackupExistOnServer.data) { + onDisableClick() + } else { + state.eventSink.invoke(SecureBackupRootEvents.EnableKeyStorage) + } + } + is AsyncData.Loading, + AsyncData.Uninitialized -> Unit + is AsyncData.Failure -> state.eventSink.invoke(SecureBackupRootEvents.RetryKeyBackupState) + } + } + BackupState.CREATING, + BackupState.ENABLING, + BackupState.RESUMING, + BackupState.ENABLED, + BackupState.DOWNLOADING -> onDisableClick() + } + }, + ) + HorizontalDivider() // Setup recovery when (state.recoveryState) { RecoveryState.UNKNOWN, RecoveryState.WAITING_FOR_SYNC -> Unit RecoveryState.DISABLED -> { - PreferenceText( - title = stringResource(id = R.string.screen_chat_backup_recovery_action_setup), - subtitle = stringResource(id = R.string.screen_chat_backup_recovery_action_setup_description, state.appName), - onClick = onSetupClick, - showEndBadge = true, + ListItem( + headlineContent = { + Text( + text = stringResource(id = R.string.screen_chat_backup_recovery_action_setup), + ) + }, + supportingContent = { + Text( + text = stringResource(id = R.string.screen_chat_backup_recovery_action_setup_description, state.appName), + ) + }, + trailingContent = ListItemContent.Badge, + enabled = state.isKeyStorageEnabled, + alwaysClickable = true, + onClick = { + if (state.isKeyStorageEnabled) { + onSetupClick() + } else { + state.eventSink.invoke(SecureBackupRootEvents.DisplayKeyStorageDisabledError) + } + }, ) } RecoveryState.ENABLED -> { - PreferenceText( - title = stringResource(id = R.string.screen_chat_backup_recovery_action_change), - onClick = onChangeClick, + ListItem( + headlineContent = { + Text( + text = stringResource(id = R.string.screen_chat_backup_recovery_action_change), + ) + }, + supportingContent = { + Text( + text = stringResource(id = R.string.screen_chat_backup_recovery_action_change_description), + ) + }, + enabled = state.isKeyStorageEnabled, + alwaysClickable = true, + onClick = { + if (state.isKeyStorageEnabled) { + onChangeClick() + } else { + state.eventSink.invoke(SecureBackupRootEvents.DisplayKeyStorageDisabledError) + } + }, ) } RecoveryState.INCOMPLETE -> - PreferenceText( - title = stringResource(id = R.string.screen_chat_backup_recovery_action_confirm), - subtitle = stringResource(id = R.string.screen_chat_backup_recovery_action_confirm_description), - showEndBadge = true, - onClick = onConfirmRecoveryKeyClick, + ListItem( + headlineContent = { + Text( + text = stringResource(id = R.string.screen_chat_backup_recovery_action_confirm), + ) + }, + supportingContent = { + Text( + text = stringResource(id = R.string.screen_chat_backup_recovery_action_confirm_description), + ) + }, + trailingContent = ListItemContent.Badge, + enabled = state.isKeyStorageEnabled, + alwaysClickable = true, + onClick = { + if (state.isKeyStorageEnabled) { + onConfirmRecoveryKeyClick() + } else { + state.eventSink.invoke(SecureBackupRootEvents.DisplayKeyStorageDisabledError) + } + }, ) } } + + AsyncActionView( + async = state.enableAction, + progressDialog = { }, + onSuccess = { }, + onErrorDismiss = { state.eventSink.invoke(SecureBackupRootEvents.DismissDialog) } + ) + if (state.displayKeyStorageDisabledError) { + ErrorDialog( + title = null, + content = stringResource(id = R.string.screen_chat_backup_key_storage_disabled_error), + onSubmit = { state.eventSink.invoke(SecureBackupRootEvents.DismissDialog) }, + ) + } +} + +@Composable +private fun LoadingView() { + CircularProgressIndicator( + modifier = Modifier + .progressSemantics() + .size(24.dp), + strokeWidth = 2.dp + ) } @PreviewsDayNight @@ -180,7 +242,6 @@ internal fun SecureBackupRootViewPreview( onBackClick = {}, onSetupClick = {}, onChangeClick = {}, - onEnableClick = {}, onDisableClick = {}, onConfirmRecoveryKeyClick = {}, onLearnMoreClick = {}, diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/setup/views/RecoveryKeyView.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/setup/views/RecoveryKeyView.kt index 06fa7b2c46..7ebf2d0219 100644 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/setup/views/RecoveryKeyView.kt +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/setup/views/RecoveryKeyView.kt @@ -91,14 +91,14 @@ private fun RecoveryKeyStaticContent( ) { Row( modifier = Modifier - .fillMaxWidth() - .clip(RoundedCornerShape(14.dp)) - .background( - color = ElementTheme.colors.bgSubtleSecondary, - shape = RoundedCornerShape(14.dp) - ) - .clickableIfNotNull(onClick) - .padding(horizontal = 16.dp, vertical = 16.dp), + .fillMaxWidth() + .clip(RoundedCornerShape(14.dp)) + .background( + color = ElementTheme.colors.bgSubtleSecondary, + shape = RoundedCornerShape(14.dp) + ) + .clickableIfNotNull(onClick) + .padding(horizontal = 16.dp, vertical = 16.dp), horizontalArrangement = Arrangement.spacedBy(8.dp) ) { if (state.formattedRecoveryKey != null) { @@ -116,15 +116,15 @@ private fun RecoveryKeyStaticContent( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.Center, modifier = Modifier - .fillMaxWidth() - .padding(vertical = 11.dp) + .fillMaxWidth() + .padding(vertical = 11.dp) ) { if (state.inProgress) { CircularProgressIndicator( modifier = Modifier - .progressSemantics() - .padding(end = 8.dp) - .size(16.dp), + .progressSemantics() + .padding(end = 8.dp) + .size(16.dp), color = ElementTheme.colors.textPrimary, strokeWidth = 1.5.dp, ) @@ -161,12 +161,12 @@ private fun RecoveryKeyFormContent( } OutlinedTextField( modifier = Modifier - .fillMaxWidth() - .testTag(TestTags.recoveryKey) - .autofill( - autofillTypes = listOf(AutofillType.Password), - onFill = { onChange(it) }, - ), + .fillMaxWidth() + .testTag(TestTags.recoveryKey) + .autofill( + autofillTypes = listOf(AutofillType.Password), + onFill = { onChange(it) }, + ), minLines = 2, value = state.formattedRecoveryKey.orEmpty(), onValueChange = onChange, @@ -189,30 +189,18 @@ private fun RecoveryKeyFooter(state: RecoveryKeyViewState) { RecoveryKeyUserStory.Setup, RecoveryKeyUserStory.Change -> { if (state.formattedRecoveryKey == null) { - Row( - verticalAlignment = Alignment.CenterVertically, - ) { - Icon( - imageVector = CompoundIcons.InfoSolid(), - contentDescription = null, - tint = ElementTheme.colors.iconSecondary, - modifier = Modifier - .padding(start = 16.dp) - .size(20.dp), - ) - Text( - text = stringResource( - id = if (state.recoveryKeyUserStory == RecoveryKeyUserStory.Change) { - R.string.screen_recovery_key_change_generate_key_description - } else { - R.string.screen_recovery_key_setup_generate_key_description - } - ), - color = ElementTheme.colors.textSecondary, - modifier = Modifier.padding(start = 8.dp), - style = ElementTheme.typography.fontBodySmRegular, - ) - } + Text( + text = stringResource( + id = if (state.recoveryKeyUserStory == RecoveryKeyUserStory.Change) { + R.string.screen_recovery_key_change_generate_key_description + } else { + R.string.screen_recovery_key_setup_generate_key_description + } + ), + color = ElementTheme.colors.textSecondary, + modifier = Modifier.padding(start = 16.dp), + style = ElementTheme.typography.fontBodySmRegular, + ) } else { Text( text = stringResource(id = R.string.screen_recovery_key_save_key_description), diff --git a/features/securebackup/impl/src/main/res/values/localazy.xml b/features/securebackup/impl/src/main/res/values/localazy.xml index 3fb2c3b027..71c879b088 100644 --- a/features/securebackup/impl/src/main/res/values/localazy.xml +++ b/features/securebackup/impl/src/main/res/values/localazy.xml @@ -1,9 +1,10 @@ - "Turn off backup" + "Delete key storage" "Turn on backup" "Store your cryptographic identity and message keys securely on the server. This will allow you to view your message history on any new devices. %1$s." "Key storage" + "Key storage must be turned on to set up recovery." "Upload keys from this device" "Allow key storage" "Change recovery key" @@ -28,10 +29,10 @@ "Turn off" "You will lose your encrypted messages if you are signed out of all devices." "Are you sure you want to turn off backup?" - "Turning off backup will remove your current encryption key backup and turn off other security features. In this case, you will:" - "Not have encrypted message history on new devices" - "Lose access to your encrypted messages if you are signed out of %1$s everywhere" - "Are you sure you want to turn off backup?" + "Deleting key storage will remove your cryptographic identity and message keys from the server and turn off the following security features:" + "You will not have encrypted message history on new devices" + "You will lose access to your encrypted messages if you are signed out of %1$s everywhere" + "Are you sure you want to turn off key storage and delete it?" "Get a new recovery key if you\'ve lost your existing one. After changing your recovery key, your old one will no longer work." "Generate a new recovery key" "Do not share this with anyone!" @@ -54,7 +55,7 @@ "Save your recovery key somewhere safe" "You will not be able to access your new recovery key after this step." "Have you saved your recovery key?" - "Your chat backup is protected by a recovery key. If you need a new recovery key after setup you can recreate by selecting ‘Change recovery key’." + "Your key storage is protected by a recovery key. If you need a new recovery key after setup, you can recreate it by selecting ‘Change recovery key’." "Generate your recovery key" "Do not share this with anyone!" "Recovery setup successful" diff --git a/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/disable/SecureBackupDisablePresenterTest.kt b/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/disable/SecureBackupDisablePresenterTest.kt index b0d6399c04..073a2de11c 100644 --- a/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/disable/SecureBackupDisablePresenterTest.kt +++ b/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/disable/SecureBackupDisablePresenterTest.kt @@ -38,22 +38,6 @@ class SecureBackupDisablePresenterTest { } } - @Test - fun `present - user delete backup and cancel`() = runTest { - val presenter = createSecureBackupDisablePresenter() - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { - val initialState = awaitItem() - initialState.eventSink(SecureBackupDisableEvents.DisableBackup) - val state = awaitItem() - assertThat(state.disableAction).isEqualTo(AsyncAction.ConfirmingNoParams) - initialState.eventSink(SecureBackupDisableEvents.DismissDialogs) - val finalState = awaitItem() - assertThat(finalState.disableAction).isEqualTo(AsyncAction.Uninitialized) - } - } - @Test fun `present - user delete backup success`() = runTest { val presenter = createSecureBackupDisablePresenter() @@ -63,9 +47,6 @@ class SecureBackupDisablePresenterTest { val initialState = awaitItem() assertThat(initialState.disableAction).isEqualTo(AsyncAction.Uninitialized) initialState.eventSink(SecureBackupDisableEvents.DisableBackup) - val state = awaitItem() - assertThat(state.disableAction).isEqualTo(AsyncAction.ConfirmingNoParams) - initialState.eventSink(SecureBackupDisableEvents.DisableBackup) val loadingState = awaitItem() assertThat(loadingState.disableAction).isInstanceOf(AsyncAction.Loading::class.java) val finalState = awaitItem() @@ -87,9 +68,6 @@ class SecureBackupDisablePresenterTest { val initialState = awaitItem() assertThat(initialState.disableAction).isEqualTo(AsyncAction.Uninitialized) initialState.eventSink(SecureBackupDisableEvents.DisableBackup) - val state = awaitItem() - assertThat(state.disableAction).isEqualTo(AsyncAction.ConfirmingNoParams) - initialState.eventSink(SecureBackupDisableEvents.DisableBackup) val loadingState = awaitItem() assertThat(loadingState.disableAction).isInstanceOf(AsyncAction.Loading::class.java) val errorState = awaitItem() diff --git a/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/enable/SecureBackupEnablePresenterTest.kt b/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/enable/SecureBackupEnablePresenterTest.kt deleted file mode 100644 index 46d210ad93..0000000000 --- a/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/enable/SecureBackupEnablePresenterTest.kt +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright 2023, 2024 New Vector Ltd. - * - * SPDX-License-Identifier: AGPL-3.0-only - * Please see LICENSE in the repository root for full details. - */ - -package io.element.android.features.securebackup.impl.enable - -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.encryption.EncryptionService -import io.element.android.libraries.matrix.test.AN_EXCEPTION -import io.element.android.libraries.matrix.test.encryption.FakeEncryptionService -import io.element.android.tests.testutils.WarmUpRule -import kotlinx.coroutines.test.runTest -import org.junit.Rule -import org.junit.Test - -class SecureBackupEnablePresenterTest { - @get:Rule - val warmUpRule = WarmUpRule() - - @Test - fun `present - initial state`() = runTest { - val presenter = createPresenter() - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { - val initialState = awaitItem() - assertThat(initialState.enableAction).isEqualTo(AsyncAction.Uninitialized) - } - } - - @Test - fun `present - user enable backup`() = runTest { - val presenter = createPresenter() - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { - val initialState = awaitItem() - initialState.eventSink(SecureBackupEnableEvents.EnableBackup) - val loadingState = awaitItem() - assertThat(loadingState.enableAction).isInstanceOf(AsyncAction.Loading::class.java) - val finalState = awaitItem() - assertThat(finalState.enableAction).isEqualTo(AsyncAction.Success(Unit)) - } - } - - @Test - fun `present - user enable backup with error`() = runTest { - val encryptionService = FakeEncryptionService() - encryptionService.givenEnableBackupsFailure(AN_EXCEPTION) - val presenter = createPresenter(encryptionService = encryptionService) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { - val initialState = awaitItem() - initialState.eventSink(SecureBackupEnableEvents.EnableBackup) - val loadingState = awaitItem() - assertThat(loadingState.enableAction).isInstanceOf(AsyncAction.Loading::class.java) - val errorState = awaitItem() - assertThat(errorState.enableAction).isEqualTo(AsyncAction.Failure(AN_EXCEPTION)) - errorState.eventSink(SecureBackupEnableEvents.DismissDialog) - val finalState = awaitItem() - assertThat(finalState.enableAction).isEqualTo(AsyncAction.Uninitialized) - } - } - - private fun createPresenter( - encryptionService: EncryptionService = FakeEncryptionService(), - ) = SecureBackupEnablePresenter( - encryptionService = encryptionService, - ) -}