Merge pull request #2013 from element-hq/feature/bma/logoutWording

Fix title of sign out screen regarding the different states
This commit is contained in:
Benoit Marty 2023-12-15 20:06:15 +01:00 committed by GitHub
commit 4c987c5b3f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 102 additions and 29 deletions

View file

@ -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<Async<Boolean>> = 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<Async<Boolean>>) = launch {
suspend {
encryptionService.doesBackupExistOnServer().getOrThrow()
}.runCatchingUpdatingState(action)
}
private fun CoroutineScope.logout(
logoutAction: MutableState<Async<String?>>,
ignoreSdkError: Boolean,

View file

@ -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<String?>,

View file

@ -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<LogoutState> {
@ -32,16 +34,26 @@ open class LogoutStateProvider : PreviewParameterProvider<LogoutState> {
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<String?> = Async.Uninitialized,
) = LogoutState(
isLastSession = isLastSession,
backupState = backupState,
doesBackupExistOnServer = doesBackupExistOnServer,
recoveryState = recoveryState,
backupUploadState = backupUploadState,
showConfirmationDialog = showConfirmationDialog,
logoutAction = logoutAction,

View file

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

View file

@ -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()
@ -155,7 +161,8 @@ class LogoutPresenterTest {
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
val initialState = awaitLastSequentialItem()
skipItems(1)
val initialState = awaitItem()
initialState.eventSink.invoke(LogoutEvents.Logout(ignoreSdkError = false))
val confirmationState = awaitItem()
assertThat(confirmationState.showConfirmationDialog).isTrue()
@ -164,6 +171,7 @@ class LogoutPresenterTest {
val loadingState = awaitItem()
assertThat(loadingState.showConfirmationDialog).isFalse()
assertThat(loadingState.logoutAction).isInstanceOf(Async.Loading::class.java)
skipItems(1)
val errorState = awaitItem()
assertThat(errorState.logoutAction).isEqualTo(Async.Failure<LogoutState>(A_THROWABLE))
errorState.eventSink.invoke(LogoutEvents.CloseDialogs)
@ -183,7 +191,8 @@ class LogoutPresenterTest {
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
val initialState = awaitLastSequentialItem()
skipItems(1)
val initialState = awaitItem()
initialState.eventSink.invoke(LogoutEvents.Logout(ignoreSdkError = false))
val confirmationState = awaitItem()
assertThat(confirmationState.showConfirmationDialog).isTrue()
@ -192,6 +201,7 @@ class LogoutPresenterTest {
val loadingState = awaitItem()
assertThat(loadingState.showConfirmationDialog).isFalse()
assertThat(loadingState.logoutAction).isInstanceOf(Async.Loading::class.java)
skipItems(1)
val errorState = awaitItem()
assertThat(errorState.logoutAction).isEqualTo(Async.Failure<LogoutState>(A_THROWABLE))
errorState.eventSink.invoke(LogoutEvents.Logout(ignoreSdkError = true))

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:0566f93f37532625a0c647992f25c2cbae9e944ab51629ad1c02322d28bad72e
size 29115
oid sha256:78edb2f0cbb3a7c975999aa31f90e5a82e7b73a21f2923e420f364da3c0118fd
size 13706

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:5845034f94d3e3452bdda9e04d8609385154b3462ac98abb267819a7d55e1672
size 33099
oid sha256:186d3f45a3e8b294e523ede9787563399d97b26d7181ad687afcd8b48f8be506
size 41247

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:2c2f057c86716407a4cd3491612f57b9d4b800cf4ef749c2e6dce3e12fbbf632
size 37972
oid sha256:186d3f45a3e8b294e523ede9787563399d97b26d7181ad687afcd8b48f8be506
size 41247

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:475436dfdc616c2c5111e9dfbbf058e57fa12b05c82064e9a778d56cdeb05923
size 34939
oid sha256:2d8dfacb3767fe7e9de7996cd0fb9bcb7f208e49498bfdc7b599546669b7b396
size 27632

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:a7001f904e0b190b8846cf0d440a972d1a6097fc168caee135469a2dbb39788c
size 31590
oid sha256:51528fe646b2ac7f679861af28443b22fe1a710586ac70aa730d98cf7c1e8856
size 21842

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:77fe1e9484c72dc9572a810941cc83ecd208aa080d59417e409c69edbea978d5
size 33484
oid sha256:47f4ac4fb71f8a4adc3890101244c4ee9fbbf87d0da0e0381a85792b79896a5f
size 26372

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:87010d4aae66e1030d4b9557e4f065df777324495459c08513040e6793d2d00d
size 36675

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:2c2f057c86716407a4cd3491612f57b9d4b800cf4ef749c2e6dce3e12fbbf632
size 37972

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:ccdef8b261183673b56d0405b0a2b233eddc0537af600f9071d1b0b0bb495e3b
size 27390
oid sha256:dd5acfa31ca9994634286aa59bdb67d86e438165296164db82e152cd56868585
size 13044

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:dbbd4c82b8f8a1c283074c51968b7e097595721cb25ba2612b9fff5e2c1a5c07
size 30999
oid sha256:49df4ac1a7ec108308b82d0fdb6f0e0d8ca2063960070fa9865562e59774ec21
size 38623

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:52bf6e6334dd36e2e39f6957a60c7d54839caffdf47a32a0bb463124fc2c27d4
size 35820
oid sha256:49df4ac1a7ec108308b82d0fdb6f0e0d8ca2063960070fa9865562e59774ec21
size 38623

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:426c68f5506c8f8a203a3d017130bebd556146b241d044d4ba9160b8029a4fd6
size 30490
oid sha256:51af5a971e3bb5958646ec1866ba41fd4d46e281713573b005bc71b4762ec7b4
size 24085

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:277730c5d3e711f28b8405f90b004268343845a1b528860cfbffcf47c7e39086
size 28511
oid sha256:6efe41b16360b4cfc891a5ffc99ff147ea5c5324341d5f8b8a2f574d360b5aad
size 19117

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:f3814c6d92191053e08d6c6b7c4ad7c52cdf868795bd50771d03c08885d2c298
size 29271
oid sha256:6a2a05a5d4f528302ca1a6f8bd799418f97d511bb808dadbfe806077ab2a9bab
size 22756

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:3961ac676e29ac3d6f8dede3aed1c0c22d400e6a95ca8c4333691102f1616c42
size 34922

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:52bf6e6334dd36e2e39f6957a60c7d54839caffdf47a32a0bb463124fc2c27d4
size 35820