From 40bd16b1ebbe73012ef9b6e50c528c99ae8c5d3a Mon Sep 17 00:00:00 2001 From: ganfra Date: Wed, 19 Nov 2025 21:44:39 +0100 Subject: [PATCH] change(security and privacy) : display SaveChangesDialog on exit --- .../impl/root/SecurityAndPrivacyEvents.kt | 2 + .../impl/root/SecurityAndPrivacyPresenter.kt | 15 +++++- .../impl/root/SecurityAndPrivacyState.kt | 1 + .../root/SecurityAndPrivacyStateProvider.kt | 53 ++++++++++++------- .../impl/root/SecurityAndPrivacyView.kt | 22 +++++++- 5 files changed, 72 insertions(+), 21 deletions(-) diff --git a/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/root/SecurityAndPrivacyEvents.kt b/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/root/SecurityAndPrivacyEvents.kt index 8119ddb0c4..2553090541 100644 --- a/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/root/SecurityAndPrivacyEvents.kt +++ b/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/root/SecurityAndPrivacyEvents.kt @@ -11,6 +11,8 @@ package io.element.android.features.securityandprivacy.impl.root sealed interface SecurityAndPrivacyEvents { data object EditRoomAddress : SecurityAndPrivacyEvents data object Save : SecurityAndPrivacyEvents + data object Exit: SecurityAndPrivacyEvents + data object DismissExitConfirmation : SecurityAndPrivacyEvents data class ChangeRoomAccess(val roomAccess: SecurityAndPrivacyRoomAccess) : SecurityAndPrivacyEvents data object ToggleEncryptionState : SecurityAndPrivacyEvents data object CancelEnableEncryption : SecurityAndPrivacyEvents diff --git a/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/root/SecurityAndPrivacyPresenter.kt b/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/root/SecurityAndPrivacyPresenter.kt index e1766f3220..5bcbe15f7c 100644 --- a/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/root/SecurityAndPrivacyPresenter.kt +++ b/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/root/SecurityAndPrivacyPresenter.kt @@ -26,6 +26,7 @@ import io.element.android.features.securityandprivacy.impl.editroomaddress.match import io.element.android.features.securityandprivacy.api.securityAndPrivacyPermissionsAsState import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.AsyncData +import io.element.android.libraries.architecture.AsyncData.* import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.runCatchingUpdatingState import io.element.android.libraries.architecture.runUpdatingState @@ -64,6 +65,7 @@ class SecurityAndPrivacyPresenter( featureFlagService.isFeatureEnabledFlow(FeatureFlags.Knock) }.collectAsState(false) val saveAction = remember { mutableStateOf>(AsyncAction.Uninitialized) } + var confirmExitAction by remember { mutableStateOf>(AsyncAction.Uninitialized)} val homeserverName = remember { matrixClient.userIdServerName() } val syncUpdateFlow = room.syncUpdateFlow.collectAsState() val roomInfo by room.roomInfoFlow.collectAsState() @@ -133,7 +135,7 @@ class SecurityAndPrivacyPresenter( } SecurityAndPrivacyEvents.ToggleRoomVisibility -> { editedVisibleInRoomDirectory = when (val edited = editedVisibleInRoomDirectory) { - is AsyncData.Success -> AsyncData.Success(!edited.data) + is Success -> Success(!edited.data) else -> edited } } @@ -148,6 +150,16 @@ class SecurityAndPrivacyPresenter( SecurityAndPrivacyEvents.DismissSaveError -> { saveAction.value = AsyncAction.Uninitialized } + SecurityAndPrivacyEvents.Exit -> { + confirmExitAction = if (savedSettings == editedSettings || confirmExitAction.isConfirming()) { + AsyncAction.Success(Unit) + } else { + AsyncAction.ConfirmingNoParams + } + } + SecurityAndPrivacyEvents.DismissExitConfirmation -> { + confirmExitAction = AsyncAction.Uninitialized + } } } @@ -160,6 +172,7 @@ class SecurityAndPrivacyPresenter( saveAction = saveAction.value, permissions = permissions, isSpace = roomInfo.isSpace, + confirmExitAction = confirmExitAction, eventSink = ::handleEvent, ) diff --git a/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/root/SecurityAndPrivacyState.kt b/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/root/SecurityAndPrivacyState.kt index 61e8ffd17c..0671bbf163 100644 --- a/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/root/SecurityAndPrivacyState.kt +++ b/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/root/SecurityAndPrivacyState.kt @@ -22,6 +22,7 @@ data class SecurityAndPrivacyState( val showEnableEncryptionConfirmation: Boolean, val isKnockEnabled: Boolean, val saveAction: AsyncAction, + val confirmExitAction: AsyncAction, val isSpace: Boolean, private val permissions: SecurityAndPrivacyPermissions, val eventSink: (SecurityAndPrivacyEvents) -> Unit diff --git a/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/root/SecurityAndPrivacyStateProvider.kt b/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/root/SecurityAndPrivacyStateProvider.kt index 3f55449b51..00dc175b4c 100644 --- a/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/root/SecurityAndPrivacyStateProvider.kt +++ b/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/root/SecurityAndPrivacyStateProvider.kt @@ -15,10 +15,29 @@ import io.element.android.libraries.architecture.AsyncData open class SecurityAndPrivacyStateProvider : PreviewParameterProvider { override val values: Sequence - get() = securityAndPrivacyStates(isSpace = false) + securityAndPrivacyStates(isSpace = true) + get() = commonSecurityAndPrivacyStates(isSpace = false) + + commonSecurityAndPrivacyStates(isSpace = true) + + sequenceOf( + aSecurityAndPrivacyState( + saveAction = AsyncAction.Loading, + isSpace = false, + ), + aSecurityAndPrivacyState( + saveAction = AsyncAction.Failure(SecurityAndPrivacyFailures.SaveFailed), + isSpace = false, + ), + aSecurityAndPrivacyState( + confirmExitAction = AsyncAction.ConfirmingCancellation, + isSpace = false, + ), + aSecurityAndPrivacyState( + showEncryptionConfirmation = true, + isSpace = false, + ), + ) } -private fun securityAndPrivacyStates(isSpace: Boolean): Sequence = sequenceOf( +private fun commonSecurityAndPrivacyStates(isSpace: Boolean): Sequence = sequenceOf( aSecurityAndPrivacyState(isSpace = isSpace), aSecurityAndPrivacyState( editedSettings = aSecurityAndPrivacySettings( @@ -26,6 +45,13 @@ private fun securityAndPrivacyStates(isSpace: Boolean): Sequence = AsyncAction.Uninitialized, + confirmExitAction: AsyncAction = AsyncAction.Uninitialized, permissions: SecurityAndPrivacyPermissions = SecurityAndPrivacyPermissions( canChangeRoomAccess = true, canChangeHistoryVisibility = true, @@ -103,7 +117,7 @@ fun aSecurityAndPrivacyState( canChangeRoomVisibility = true ), isKnockEnabled: Boolean = true, - isSpace: Boolean = false, + isSpace: Boolean, eventSink: (SecurityAndPrivacyEvents) -> Unit = {} ) = SecurityAndPrivacyState( editedSettings = editedSettings, @@ -111,8 +125,9 @@ fun aSecurityAndPrivacyState( homeserverName = homeserverName, showEnableEncryptionConfirmation = showEncryptionConfirmation, saveAction = saveAction, + confirmExitAction = confirmExitAction, isKnockEnabled = isKnockEnabled, permissions = permissions, isSpace = isSpace, - eventSink = eventSink + eventSink = eventSink, ) diff --git a/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/root/SecurityAndPrivacyView.kt b/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/root/SecurityAndPrivacyView.kt index 4523be8d0c..a4e3396f44 100644 --- a/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/root/SecurityAndPrivacyView.kt +++ b/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/root/SecurityAndPrivacyView.kt @@ -8,6 +8,7 @@ package io.element.android.features.securityandprivacy.impl.root +import androidx.activity.compose.BackHandler import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.ColumnScope @@ -31,12 +32,14 @@ import androidx.compose.ui.unit.dp import io.element.android.compound.theme.ElementTheme import io.element.android.compound.tokens.generated.CompoundIcons import io.element.android.features.securityandprivacy.impl.R +import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.architecture.coverage.ExcludeFromCoverage import io.element.android.libraries.designsystem.components.async.AsyncActionView import io.element.android.libraries.designsystem.components.async.AsyncActionViewDefaults import io.element.android.libraries.designsystem.components.button.BackButton import io.element.android.libraries.designsystem.components.dialogs.ConfirmationDialog +import io.element.android.libraries.designsystem.components.dialogs.SaveChangesDialog import io.element.android.libraries.designsystem.components.list.ListItemContent import io.element.android.libraries.designsystem.preview.ElementPreviewDark import io.element.android.libraries.designsystem.preview.ElementPreviewLight @@ -50,6 +53,7 @@ import io.element.android.libraries.designsystem.theme.components.TextButton import io.element.android.libraries.designsystem.theme.components.TopAppBar import io.element.android.libraries.ui.strings.CommonStrings import kotlinx.collections.immutable.ImmutableSet +import java.security.Security @Composable fun SecurityAndPrivacyView( @@ -57,12 +61,17 @@ fun SecurityAndPrivacyView( onBackClick: () -> Unit, modifier: Modifier = Modifier, ) { + BackHandler { + state.eventSink(SecurityAndPrivacyEvents.Exit) + } Scaffold( modifier = modifier, topBar = { SecurityAndPrivacyToolbar( isSaveActionEnabled = state.canBeSaved, - onBackClick = onBackClick, + onBackClick = { + state.eventSink(SecurityAndPrivacyEvents.Exit) + }, onSaveClick = { state.eventSink(SecurityAndPrivacyEvents.Save) }, @@ -131,6 +140,17 @@ fun SecurityAndPrivacyView( }, onRetry = { state.eventSink(SecurityAndPrivacyEvents.Save) }, ) + AsyncActionView( + async = state.confirmExitAction, + onSuccess = { onBackClick() }, + onErrorDismiss = { }, + confirmationDialog = { + SaveChangesDialog( + onSubmitClick = { state.eventSink(SecurityAndPrivacyEvents.Exit) }, + onDismiss = { state.eventSink(SecurityAndPrivacyEvents.DismissExitConfirmation) } + ) + }, + ) } @OptIn(ExperimentalMaterial3Api::class)