change(security and privacy) : display SaveChangesDialog on exit

This commit is contained in:
ganfra
2025-11-19 21:44:39 +01:00
parent 1b2d66989f
commit 40bd16b1eb
5 changed files with 72 additions and 21 deletions

View File

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

View File

@@ -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<Unit>>(AsyncAction.Uninitialized) }
var confirmExitAction by remember { mutableStateOf<AsyncAction<Unit>>(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,
)

View File

@@ -22,6 +22,7 @@ data class SecurityAndPrivacyState(
val showEnableEncryptionConfirmation: Boolean,
val isKnockEnabled: Boolean,
val saveAction: AsyncAction<Unit>,
val confirmExitAction: AsyncAction<Unit>,
val isSpace: Boolean,
private val permissions: SecurityAndPrivacyPermissions,
val eventSink: (SecurityAndPrivacyEvents) -> Unit

View File

@@ -15,10 +15,29 @@ import io.element.android.libraries.architecture.AsyncData
open class SecurityAndPrivacyStateProvider : PreviewParameterProvider<SecurityAndPrivacyState> {
override val values: Sequence<SecurityAndPrivacyState>
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<SecurityAndPrivacyState> = sequenceOf(
private fun commonSecurityAndPrivacyStates(isSpace: Boolean): Sequence<SecurityAndPrivacyState> = sequenceOf(
aSecurityAndPrivacyState(isSpace = isSpace),
aSecurityAndPrivacyState(
editedSettings = aSecurityAndPrivacySettings(
@@ -26,6 +45,13 @@ private fun securityAndPrivacyStates(isSpace: Boolean): Sequence<SecurityAndPriv
),
isSpace = isSpace,
),
aSecurityAndPrivacyState(
savedSettings = aSecurityAndPrivacySettings(
roomAccess = SecurityAndPrivacyRoomAccess.AskToJoin
),
isSpace = isSpace,
isKnockEnabled = false,
),
aSecurityAndPrivacyState(
editedSettings = aSecurityAndPrivacySettings(
roomAccess = SecurityAndPrivacyRoomAccess.Anyone,
@@ -49,31 +75,18 @@ private fun securityAndPrivacyStates(isSpace: Boolean): Sequence<SecurityAndPriv
),
aSecurityAndPrivacyState(
editedSettings = aSecurityAndPrivacySettings(
roomAccess = SecurityAndPrivacyRoomAccess.Anyone,
isVisibleInRoomDirectory = AsyncData.Loading()
),
isSpace = isSpace,
),
aSecurityAndPrivacyState(
editedSettings = aSecurityAndPrivacySettings(
roomAccess = SecurityAndPrivacyRoomAccess.Anyone,
isVisibleInRoomDirectory = AsyncData.Success(true)
),
isSpace = isSpace,
),
aSecurityAndPrivacyState(
showEncryptionConfirmation = true,
isSpace = isSpace,
),
aSecurityAndPrivacyState(
saveAction = AsyncAction.Loading,
isSpace = isSpace,
),
aSecurityAndPrivacyState(
savedSettings = aSecurityAndPrivacySettings(
roomAccess = SecurityAndPrivacyRoomAccess.AskToJoin
),
isSpace = isSpace,
isKnockEnabled = false,
),
)
fun aSecurityAndPrivacySettings(
@@ -96,6 +109,7 @@ fun aSecurityAndPrivacyState(
homeserverName: String = "myserver.xyz",
showEncryptionConfirmation: Boolean = false,
saveAction: AsyncAction<Unit> = AsyncAction.Uninitialized,
confirmExitAction: AsyncAction<Unit> = 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,
)

View File

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