diff --git a/features/logout/impl/src/main/kotlin/io/element/android/features/logout/impl/LogoutPresenter.kt b/features/logout/impl/src/main/kotlin/io/element/android/features/logout/impl/LogoutPresenter.kt index ff76bde776..b8d8772104 100644 --- a/features/logout/impl/src/main/kotlin/io/element/android/features/logout/impl/LogoutPresenter.kt +++ b/features/logout/impl/src/main/kotlin/io/element/android/features/logout/impl/LogoutPresenter.kt @@ -28,9 +28,11 @@ import androidx.compose.runtime.setValue import io.element.android.libraries.architecture.Async import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.runCatchingUpdatingState +import io.element.android.libraries.core.bool.orTrue import io.element.android.libraries.featureflag.api.FeatureFlagService import io.element.android.libraries.featureflag.api.FeatureFlags import io.element.android.libraries.matrix.api.MatrixClient +import io.element.android.libraries.matrix.api.encryption.BackupState import io.element.android.libraries.matrix.api.encryption.BackupUploadState import io.element.android.libraries.matrix.api.encryption.EncryptionService import kotlinx.coroutines.CoroutineScope @@ -70,6 +72,19 @@ class LogoutPresenter @Inject constructor( isLastSession = encryptionService.isLastDevice().getOrNull() ?: false } + val backupState by encryptionService.backupStateStateFlow.collectAsState() + val recoveryState by encryptionService.recoveryStateStateFlow.collectAsState() + + val doesBackupExistOnServerAction: MutableState> = remember { + mutableStateOf(Async.Uninitialized) + } + + LaunchedEffect(backupState) { + if (backupState == BackupState.UNKNOWN) { + getKeyBackupStatus(doesBackupExistOnServerAction) + } + } + fun handleEvents(event: LogoutEvents) { when (event) { is LogoutEvents.Logout -> { @@ -89,6 +104,9 @@ class LogoutPresenter @Inject constructor( return LogoutState( isLastSession = isLastSession, + backupState = backupState, + doesBackupExistOnServer = doesBackupExistOnServerAction.value.dataOrNull().orTrue(), + recoveryState = recoveryState, backupUploadState = backupUploadState, showConfirmationDialog = showLogoutDialog, logoutAction = logoutAction.value, @@ -96,6 +114,12 @@ class LogoutPresenter @Inject constructor( ) } + private fun CoroutineScope.getKeyBackupStatus(action: MutableState>) = launch { + suspend { + encryptionService.doesBackupExistOnServer().getOrThrow() + }.runCatchingUpdatingState(action) + } + private fun CoroutineScope.logout( logoutAction: MutableState>, ignoreSdkError: Boolean, diff --git a/features/logout/impl/src/main/kotlin/io/element/android/features/logout/impl/LogoutState.kt b/features/logout/impl/src/main/kotlin/io/element/android/features/logout/impl/LogoutState.kt index 1672640ab7..abade32b96 100644 --- a/features/logout/impl/src/main/kotlin/io/element/android/features/logout/impl/LogoutState.kt +++ b/features/logout/impl/src/main/kotlin/io/element/android/features/logout/impl/LogoutState.kt @@ -17,10 +17,15 @@ package io.element.android.features.logout.impl import io.element.android.libraries.architecture.Async +import io.element.android.libraries.matrix.api.encryption.BackupState import io.element.android.libraries.matrix.api.encryption.BackupUploadState +import io.element.android.libraries.matrix.api.encryption.RecoveryState data class LogoutState( val isLastSession: Boolean, + val backupState: BackupState, + val doesBackupExistOnServer: Boolean, + val recoveryState: RecoveryState, val backupUploadState: BackupUploadState, val showConfirmationDialog: Boolean, val logoutAction: Async, diff --git a/features/logout/impl/src/main/kotlin/io/element/android/features/logout/impl/LogoutStateProvider.kt b/features/logout/impl/src/main/kotlin/io/element/android/features/logout/impl/LogoutStateProvider.kt index ddf0f30340..8c9482105d 100644 --- a/features/logout/impl/src/main/kotlin/io/element/android/features/logout/impl/LogoutStateProvider.kt +++ b/features/logout/impl/src/main/kotlin/io/element/android/features/logout/impl/LogoutStateProvider.kt @@ -18,7 +18,9 @@ package io.element.android.features.logout.impl import androidx.compose.ui.tooling.preview.PreviewParameterProvider import io.element.android.libraries.architecture.Async +import io.element.android.libraries.matrix.api.encryption.BackupState import io.element.android.libraries.matrix.api.encryption.BackupUploadState +import io.element.android.libraries.matrix.api.encryption.RecoveryState import io.element.android.libraries.matrix.api.encryption.SteadyStateException open class LogoutStateProvider : PreviewParameterProvider { @@ -32,16 +34,26 @@ open class LogoutStateProvider : PreviewParameterProvider { aLogoutState(logoutAction = Async.Loading()), aLogoutState(logoutAction = Async.Failure(Exception("Failed to logout"))), aLogoutState(backupUploadState = BackupUploadState.SteadyException(SteadyStateException.Connection("No network"))), + // Last session no recovery + aLogoutState(isLastSession = true, recoveryState = RecoveryState.DISABLED), + // Last session no backup + aLogoutState(isLastSession = true, backupState = BackupState.UNKNOWN, doesBackupExistOnServer = false), ) } fun aLogoutState( isLastSession: Boolean = false, + backupState: BackupState = BackupState.ENABLED, + doesBackupExistOnServer: Boolean = true, + recoveryState: RecoveryState = RecoveryState.ENABLED, backupUploadState: BackupUploadState = BackupUploadState.Unknown, showConfirmationDialog: Boolean = false, logoutAction: Async = Async.Uninitialized, ) = LogoutState( isLastSession = isLastSession, + backupState = backupState, + doesBackupExistOnServer = doesBackupExistOnServer, + recoveryState = recoveryState, backupUploadState = backupUploadState, showConfirmationDialog = showConfirmationDialog, logoutAction = logoutAction, 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 fd559642a5..d3d4805f82 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 @@ -43,7 +43,9 @@ import io.element.android.libraries.designsystem.theme.components.OutlinedButton import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.designsystem.theme.progressIndicatorTrackColor import io.element.android.libraries.designsystem.utils.CommonDrawables +import io.element.android.libraries.matrix.api.encryption.BackupState import io.element.android.libraries.matrix.api.encryption.BackupUploadState +import io.element.android.libraries.matrix.api.encryption.RecoveryState import io.element.android.libraries.matrix.api.encryption.SteadyStateException import io.element.android.libraries.testtags.TestTags import io.element.android.libraries.testtags.testTag @@ -120,7 +122,15 @@ fun LogoutView( private fun title(state: LogoutState): String { return when { state.backupUploadState.isBackingUp() -> stringResource(id = R.string.screen_signout_key_backup_ongoing_title) - state.isLastSession -> stringResource(id = R.string.screen_signout_key_backup_disabled_title) + state.isLastSession -> { + if (state.recoveryState != RecoveryState.ENABLED) { + stringResource(id = R.string.screen_signout_recovery_disabled_title) + } else if (state.backupState == BackupState.UNKNOWN && state.doesBackupExistOnServer.not()) { + stringResource(id = R.string.screen_signout_key_backup_disabled_title) + } else { + stringResource(id = R.string.screen_signout_save_recovery_key_title) + } + } else -> stringResource(CommonStrings.action_signout) } } @@ -138,10 +148,10 @@ private fun subtitle(state: LogoutState): String? { private fun BackupUploadState.isBackingUp(): Boolean { return when (this) { - BackupUploadState.Unknown, BackupUploadState.Waiting, is BackupUploadState.Uploading -> true is BackupUploadState.SteadyException -> exception is SteadyStateException.Connection + BackupUploadState.Unknown, BackupUploadState.Done, BackupUploadState.Error -> false } diff --git a/features/logout/impl/src/test/kotlin/io/element/android/features/logout/impl/LogoutPresenterTest.kt b/features/logout/impl/src/test/kotlin/io/element/android/features/logout/impl/LogoutPresenterTest.kt index 9b06e71ba3..66b635fa15 100644 --- a/features/logout/impl/src/test/kotlin/io/element/android/features/logout/impl/LogoutPresenterTest.kt +++ b/features/logout/impl/src/test/kotlin/io/element/android/features/logout/impl/LogoutPresenterTest.kt @@ -24,8 +24,10 @@ import io.element.android.libraries.architecture.Async import io.element.android.libraries.featureflag.api.FeatureFlags import io.element.android.libraries.featureflag.test.FakeFeatureFlagService import io.element.android.libraries.matrix.api.MatrixClient +import io.element.android.libraries.matrix.api.encryption.BackupState import io.element.android.libraries.matrix.api.encryption.BackupUploadState import io.element.android.libraries.matrix.api.encryption.EncryptionService +import io.element.android.libraries.matrix.api.encryption.RecoveryState import io.element.android.libraries.matrix.test.A_THROWABLE import io.element.android.libraries.matrix.test.FakeMatrixClient import io.element.android.libraries.matrix.test.encryption.FakeEncryptionService @@ -50,6 +52,9 @@ class LogoutPresenterTest { }.test { val initialState = awaitLastSequentialItem() assertThat(initialState.isLastSession).isFalse() + assertThat(initialState.backupState).isEqualTo(BackupState.UNKNOWN) + assertThat(initialState.doesBackupExistOnServer).isTrue() + assertThat(initialState.recoveryState).isEqualTo(RecoveryState.UNKNOWN) assertThat(initialState.backupUploadState).isEqualTo(BackupUploadState.Unknown) assertThat(initialState.showConfirmationDialog).isFalse() assertThat(initialState.logoutAction).isEqualTo(Async.Uninitialized) @@ -93,14 +98,15 @@ class LogoutPresenterTest { moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { - skipItems(1) val initialState = awaitItem() assertThat(initialState.isLastSession).isFalse() assertThat(initialState.backupUploadState).isEqualTo(BackupUploadState.Unknown) assertThat(initialState.showConfirmationDialog).isFalse() assertThat(initialState.logoutAction).isEqualTo(Async.Uninitialized) + skipItems(1) val waitingState = awaitItem() assertThat(waitingState.backupUploadState).isEqualTo(BackupUploadState.Waiting) + skipItems(1) val uploadingState = awaitItem() assertThat(uploadingState.backupUploadState).isEqualTo(BackupUploadState.Uploading(backedUpCount = 1, totalCount = 2)) val doneState = awaitItem()