feature(security&privacy): start branching logic of ManageAuthorizedSpaces
This commit is contained in:
@@ -24,6 +24,7 @@ import io.element.android.annotations.ContributesNode
|
|||||||
import io.element.android.features.securityandprivacy.api.SecurityAndPrivacyEntryPoint
|
import io.element.android.features.securityandprivacy.api.SecurityAndPrivacyEntryPoint
|
||||||
import io.element.android.features.securityandprivacy.api.securityAndPrivacyPermissions
|
import io.element.android.features.securityandprivacy.api.securityAndPrivacyPermissions
|
||||||
import io.element.android.features.securityandprivacy.impl.editroomaddress.EditRoomAddressNode
|
import io.element.android.features.securityandprivacy.impl.editroomaddress.EditRoomAddressNode
|
||||||
|
import io.element.android.features.securityandprivacy.impl.manageauthorizedspaces.ManageAuthorizedSpacesNode
|
||||||
import io.element.android.features.securityandprivacy.impl.root.SecurityAndPrivacyNode
|
import io.element.android.features.securityandprivacy.impl.root.SecurityAndPrivacyNode
|
||||||
import io.element.android.libraries.architecture.BackstackView
|
import io.element.android.libraries.architecture.BackstackView
|
||||||
import io.element.android.libraries.architecture.BaseFlowNode
|
import io.element.android.libraries.architecture.BaseFlowNode
|
||||||
@@ -58,6 +59,9 @@ class SecurityAndPrivacyFlowNode(
|
|||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
data object EditRoomAddress : NavTarget
|
data object EditRoomAddress : NavTarget
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
data object ManageAuthorizedSpaces : NavTarget
|
||||||
}
|
}
|
||||||
|
|
||||||
private val callback: SecurityAndPrivacyEntryPoint.Callback = callback()
|
private val callback: SecurityAndPrivacyEntryPoint.Callback = callback()
|
||||||
@@ -89,6 +93,9 @@ class SecurityAndPrivacyFlowNode(
|
|||||||
NavTarget.EditRoomAddress -> {
|
NavTarget.EditRoomAddress -> {
|
||||||
createNode<EditRoomAddressNode>(buildContext, plugins = listOf(navigator))
|
createNode<EditRoomAddressNode>(buildContext, plugins = listOf(navigator))
|
||||||
}
|
}
|
||||||
|
NavTarget.ManageAuthorizedSpaces -> {
|
||||||
|
createNode<ManageAuthorizedSpacesNode>(buildContext, plugins = listOf(navigator))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,8 @@ interface SecurityAndPrivacyNavigator : Plugin {
|
|||||||
fun onDone()
|
fun onDone()
|
||||||
fun openEditRoomAddress()
|
fun openEditRoomAddress()
|
||||||
fun closeEditRoomAddress()
|
fun closeEditRoomAddress()
|
||||||
|
fun openManageAuthorizedSpaces()
|
||||||
|
fun closeManageAuthorizedSpaces()
|
||||||
}
|
}
|
||||||
|
|
||||||
class BackstackSecurityAndPrivacyNavigator(
|
class BackstackSecurityAndPrivacyNavigator(
|
||||||
@@ -35,4 +37,12 @@ class BackstackSecurityAndPrivacyNavigator(
|
|||||||
override fun closeEditRoomAddress() {
|
override fun closeEditRoomAddress() {
|
||||||
backStack.pop()
|
backStack.pop()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun openManageAuthorizedSpaces() {
|
||||||
|
backStack.push(SecurityAndPrivacyFlowNode.NavTarget.ManageAuthorizedSpaces)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun closeManageAuthorizedSpaces() {
|
||||||
|
backStack.pop()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,7 +36,6 @@ import io.element.android.libraries.designsystem.theme.components.Scaffold
|
|||||||
import io.element.android.libraries.designsystem.theme.components.Text
|
import io.element.android.libraries.designsystem.theme.components.Text
|
||||||
import io.element.android.libraries.designsystem.theme.components.TextButton
|
import io.element.android.libraries.designsystem.theme.components.TextButton
|
||||||
import io.element.android.libraries.designsystem.theme.components.TopAppBar
|
import io.element.android.libraries.designsystem.theme.components.TopAppBar
|
||||||
import io.element.android.libraries.matrix.api.spaces.SpaceRoom
|
|
||||||
import io.element.android.libraries.matrix.ui.model.getAvatarData
|
import io.element.android.libraries.matrix.ui.model.getAvatarData
|
||||||
import io.element.android.libraries.ui.strings.CommonStrings
|
import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
|
|
||||||
@@ -80,11 +79,11 @@ fun ManageAuthorizedSpacesView(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if(state.unknownSpaceIds.isNotEmpty()){
|
if (state.unknownSpaceIds.isNotEmpty()) {
|
||||||
item {
|
item {
|
||||||
ListSectionHeader(
|
ListSectionHeader(
|
||||||
title = stringResource(R.string.screen_manage_authorized_spaces_unknown_spaces_section_title),
|
title = stringResource(R.string.screen_manage_authorized_spaces_unknown_spaces_section_title),
|
||||||
hasDivider = false,
|
hasDivider = true,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
items(items = state.unknownSpaceIds) {
|
items(items = state.unknownSpaceIds) {
|
||||||
@@ -140,7 +139,7 @@ private fun CheckableSpaceListItem(
|
|||||||
Text(text = supportingText)
|
Text(text = supportingText)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
leadingContent = avatarData?.let{
|
leadingContent = avatarData?.let {
|
||||||
ListItemContent.Custom {
|
ListItemContent.Custom {
|
||||||
Avatar(
|
Avatar(
|
||||||
avatarData = avatarData,
|
avatarData = avatarData,
|
||||||
@@ -158,7 +157,6 @@ private fun CheckableSpaceListItem(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
private fun ManageAuthorizedSpacesTopBar(
|
private fun ManageAuthorizedSpacesTopBar(
|
||||||
|
|||||||
@@ -10,10 +10,13 @@ package io.element.android.features.securityandprivacy.impl.root
|
|||||||
|
|
||||||
sealed interface SecurityAndPrivacyEvent {
|
sealed interface SecurityAndPrivacyEvent {
|
||||||
data object EditRoomAddress : SecurityAndPrivacyEvent
|
data object EditRoomAddress : SecurityAndPrivacyEvent
|
||||||
|
data object ManageAuthorizedSpaces : SecurityAndPrivacyEvent
|
||||||
data object Save : SecurityAndPrivacyEvent
|
data object Save : SecurityAndPrivacyEvent
|
||||||
data object Exit : SecurityAndPrivacyEvent
|
data object Exit : SecurityAndPrivacyEvent
|
||||||
data object DismissExitConfirmation : SecurityAndPrivacyEvent
|
data object DismissExitConfirmation : SecurityAndPrivacyEvent
|
||||||
data class ChangeRoomAccess(val roomAccess: SecurityAndPrivacyRoomAccess) : SecurityAndPrivacyEvent
|
data class ChangeRoomAccess(val roomAccess: SecurityAndPrivacyRoomAccess) : SecurityAndPrivacyEvent
|
||||||
|
// Special case for "Space Members"
|
||||||
|
data object SelectSpaceMemberAccess : SecurityAndPrivacyEvent
|
||||||
data object ToggleEncryptionState : SecurityAndPrivacyEvent
|
data object ToggleEncryptionState : SecurityAndPrivacyEvent
|
||||||
data object CancelEnableEncryption : SecurityAndPrivacyEvent
|
data object CancelEnableEncryption : SecurityAndPrivacyEvent
|
||||||
data object ConfirmEnableEncryption : SecurityAndPrivacyEvent
|
data object ConfirmEnableEncryption : SecurityAndPrivacyEvent
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import androidx.compose.runtime.collectAsState
|
|||||||
import androidx.compose.runtime.derivedStateOf
|
import androidx.compose.runtime.derivedStateOf
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.produceState
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
@@ -37,9 +38,15 @@ import io.element.android.libraries.matrix.api.core.RoomAlias
|
|||||||
import io.element.android.libraries.matrix.api.room.JoinedRoom
|
import io.element.android.libraries.matrix.api.room.JoinedRoom
|
||||||
import io.element.android.libraries.matrix.api.room.RoomInfo
|
import io.element.android.libraries.matrix.api.room.RoomInfo
|
||||||
import io.element.android.libraries.matrix.api.room.history.RoomHistoryVisibility
|
import io.element.android.libraries.matrix.api.room.history.RoomHistoryVisibility
|
||||||
|
import io.element.android.libraries.matrix.api.room.join.AllowRule
|
||||||
import io.element.android.libraries.matrix.api.room.join.JoinRule
|
import io.element.android.libraries.matrix.api.room.join.JoinRule
|
||||||
import io.element.android.libraries.matrix.api.room.powerlevels.permissionsAsState
|
import io.element.android.libraries.matrix.api.room.powerlevels.permissionsAsState
|
||||||
import io.element.android.libraries.matrix.api.roomdirectory.RoomVisibility
|
import io.element.android.libraries.matrix.api.roomdirectory.RoomVisibility
|
||||||
|
import io.element.android.libraries.matrix.api.spaces.SpaceRoom
|
||||||
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
|
import kotlinx.collections.immutable.persistentSetOf
|
||||||
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
|
import kotlinx.collections.immutable.toImmutableSet
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.async
|
import kotlinx.coroutines.async
|
||||||
import kotlinx.coroutines.awaitAll
|
import kotlinx.coroutines.awaitAll
|
||||||
@@ -86,7 +93,7 @@ class SecurityAndPrivacyPresenter(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var editedRoomAccess by remember(savedSettings.roomAccess) {
|
val editedRoomAccess = remember(savedSettings.roomAccess) {
|
||||||
mutableStateOf(savedSettings.roomAccess)
|
mutableStateOf(savedSettings.roomAccess)
|
||||||
}
|
}
|
||||||
var editedHistoryVisibility by remember(savedSettings.historyVisibility) {
|
var editedHistoryVisibility by remember(savedSettings.historyVisibility) {
|
||||||
@@ -99,13 +106,26 @@ class SecurityAndPrivacyPresenter(
|
|||||||
mutableStateOf(savedIsVisibleInRoomDirectory.value)
|
mutableStateOf(savedIsVisibleInRoomDirectory.value)
|
||||||
}
|
}
|
||||||
val editedSettings = SecurityAndPrivacySettings(
|
val editedSettings = SecurityAndPrivacySettings(
|
||||||
roomAccess = editedRoomAccess,
|
roomAccess = editedRoomAccess.value,
|
||||||
isEncrypted = editedIsEncrypted,
|
isEncrypted = editedIsEncrypted,
|
||||||
isVisibleInRoomDirectory = editedVisibleInRoomDirectory,
|
isVisibleInRoomDirectory = editedVisibleInRoomDirectory,
|
||||||
historyVisibility = editedHistoryVisibility,
|
historyVisibility = editedHistoryVisibility,
|
||||||
address = savedSettings.address,
|
address = savedSettings.address,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
val selectableJoinedSpaces by produceState(persistentSetOf()) {
|
||||||
|
val joinedParentSpaces = matrixClient
|
||||||
|
.spaceService
|
||||||
|
.joinedParents(room.roomId)
|
||||||
|
.getOrDefault(emptyList())
|
||||||
|
|
||||||
|
val nonParentJoinedSpaces = savedSettings.roomAccess
|
||||||
|
.spaceIds()
|
||||||
|
.mapNotNull { spaceId -> matrixClient.spaceService.getSpaceRoom(spaceId) }
|
||||||
|
|
||||||
|
value = (joinedParentSpaces + nonParentJoinedSpaces).toImmutableSet()
|
||||||
|
}
|
||||||
|
|
||||||
var showEnableEncryptionConfirmation by remember(savedSettings.isEncrypted) { mutableStateOf(false) }
|
var showEnableEncryptionConfirmation by remember(savedSettings.isEncrypted) { mutableStateOf(false) }
|
||||||
val permissions by room.permissionsAsState(SecurityAndPrivacyPermissions.DEFAULT) { perms ->
|
val permissions by room.permissionsAsState(SecurityAndPrivacyPermissions.DEFAULT) { perms ->
|
||||||
perms.securityAndPrivacyPermissions()
|
perms.securityAndPrivacyPermissions()
|
||||||
@@ -122,7 +142,7 @@ class SecurityAndPrivacyPresenter(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
is SecurityAndPrivacyEvent.ChangeRoomAccess -> {
|
is SecurityAndPrivacyEvent.ChangeRoomAccess -> {
|
||||||
editedRoomAccess = event.roomAccess
|
editedRoomAccess.value = event.roomAccess
|
||||||
}
|
}
|
||||||
is SecurityAndPrivacyEvent.ToggleEncryptionState -> {
|
is SecurityAndPrivacyEvent.ToggleEncryptionState -> {
|
||||||
if (editedIsEncrypted) {
|
if (editedIsEncrypted) {
|
||||||
@@ -161,6 +181,12 @@ class SecurityAndPrivacyPresenter(
|
|||||||
SecurityAndPrivacyEvent.DismissExitConfirmation -> {
|
SecurityAndPrivacyEvent.DismissExitConfirmation -> {
|
||||||
saveAction.value = AsyncAction.Uninitialized
|
saveAction.value = AsyncAction.Uninitialized
|
||||||
}
|
}
|
||||||
|
SecurityAndPrivacyEvent.ManageAuthorizedSpaces -> navigator.openManageAuthorizedSpaces()
|
||||||
|
SecurityAndPrivacyEvent.SelectSpaceMemberAccess -> handleSpaceMemberAccessSelection(
|
||||||
|
selectableJoinedSpaces = selectableJoinedSpaces,
|
||||||
|
savedAccess = savedSettings.roomAccess,
|
||||||
|
editedAccess = editedRoomAccess,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -179,13 +205,14 @@ class SecurityAndPrivacyPresenter(
|
|||||||
saveAction = saveAction.value,
|
saveAction = saveAction.value,
|
||||||
permissions = permissions,
|
permissions = permissions,
|
||||||
isSpace = roomInfo.isSpace,
|
isSpace = roomInfo.isSpace,
|
||||||
|
selectableJoinedSpaces = selectableJoinedSpaces,
|
||||||
eventSink = ::handleEvent,
|
eventSink = ::handleEvent,
|
||||||
)
|
)
|
||||||
|
|
||||||
// Revert changes that the user is not allowed to make anymore
|
// Revert changes that the user is not allowed to make anymore
|
||||||
LaunchedEffect(permissions, state.editedSettings.roomAccess) {
|
LaunchedEffect(permissions, state.editedSettings.roomAccess) {
|
||||||
if (!state.showRoomAccessSection) {
|
if (!state.showRoomAccessSection) {
|
||||||
editedRoomAccess = savedSettings.roomAccess
|
editedRoomAccess.value = savedSettings.roomAccess
|
||||||
}
|
}
|
||||||
if (!state.showEncryptionSection) {
|
if (!state.showEncryptionSection) {
|
||||||
editedIsEncrypted = savedSettings.isEncrypted
|
editedIsEncrypted = savedSettings.isEncrypted
|
||||||
@@ -202,6 +229,51 @@ class SecurityAndPrivacyPresenter(
|
|||||||
return state
|
return state
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun handleSpaceMemberAccessSelection(
|
||||||
|
selectableJoinedSpaces: Set<SpaceRoom>,
|
||||||
|
savedAccess: SecurityAndPrivacyRoomAccess,
|
||||||
|
editedAccess: MutableState<SecurityAndPrivacyRoomAccess>,
|
||||||
|
) {
|
||||||
|
if(editedAccess.value is SecurityAndPrivacyRoomAccess.SpaceMember) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val spaceSelection = getSpaceSelection(selectableJoinedSpaces, savedAccess)
|
||||||
|
when(spaceSelection){
|
||||||
|
is SpaceSelection.None -> Unit
|
||||||
|
is SpaceSelection.Multiple -> navigator.openManageAuthorizedSpaces()
|
||||||
|
is SpaceSelection.Single -> {
|
||||||
|
val newRoomAccess = SecurityAndPrivacyRoomAccess.SpaceMember(
|
||||||
|
spaceIds = persistentListOf(spaceSelection.spaceId)
|
||||||
|
)
|
||||||
|
editedAccess.value = newRoomAccess
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getSpaceSelection(
|
||||||
|
selectableJoinedSpaces: Set<SpaceRoom>,
|
||||||
|
savedAccess: SecurityAndPrivacyRoomAccess,
|
||||||
|
): SpaceSelection {
|
||||||
|
val selectableSpacesCount = (selectableJoinedSpaces.map { it.roomId } + savedAccess.spaceIds()).toSet().size
|
||||||
|
return when {
|
||||||
|
selectableSpacesCount == 0 -> SpaceSelection.None
|
||||||
|
selectableSpacesCount > 1 -> SpaceSelection.Multiple
|
||||||
|
else -> {
|
||||||
|
val joinedSpace = selectableJoinedSpaces.firstOrNull()
|
||||||
|
if (joinedSpace != null) {
|
||||||
|
SpaceSelection.Single(joinedSpace.roomId, joinedSpace)
|
||||||
|
} else {
|
||||||
|
val spaceId = savedAccess.spaceIds().firstOrNull()
|
||||||
|
if (spaceId == null) {
|
||||||
|
SpaceSelection.None
|
||||||
|
} else {
|
||||||
|
SpaceSelection.Single(spaceId, null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun CoroutineScope.isRoomVisibleInRoomDirectory(isRoomVisible: MutableState<AsyncData<Boolean>>) = launch {
|
private fun CoroutineScope.isRoomVisibleInRoomDirectory(isRoomVisible: MutableState<AsyncData<Boolean>>) = launch {
|
||||||
isRoomVisible.runUpdatingState {
|
isRoomVisible.runUpdatingState {
|
||||||
room.getRoomVisibility().map { it == RoomVisibility.Public }
|
room.getRoomVisibility().map { it == RoomVisibility.Public }
|
||||||
@@ -280,7 +352,12 @@ private fun JoinRule?.map(): SecurityAndPrivacyRoomAccess {
|
|||||||
return when (this) {
|
return when (this) {
|
||||||
JoinRule.Public -> SecurityAndPrivacyRoomAccess.Anyone
|
JoinRule.Public -> SecurityAndPrivacyRoomAccess.Anyone
|
||||||
JoinRule.Knock, is JoinRule.KnockRestricted -> SecurityAndPrivacyRoomAccess.AskToJoin
|
JoinRule.Knock, is JoinRule.KnockRestricted -> SecurityAndPrivacyRoomAccess.AskToJoin
|
||||||
is JoinRule.Restricted -> SecurityAndPrivacyRoomAccess.SpaceMember
|
is JoinRule.Restricted -> SecurityAndPrivacyRoomAccess.SpaceMember(
|
||||||
|
spaceIds = this.rules
|
||||||
|
.filterIsInstance<AllowRule.RoomMembership>()
|
||||||
|
.map { it.roomId }
|
||||||
|
.toImmutableList()
|
||||||
|
)
|
||||||
JoinRule.Invite -> SecurityAndPrivacyRoomAccess.InviteOnly
|
JoinRule.Invite -> SecurityAndPrivacyRoomAccess.InviteOnly
|
||||||
// All other cases are not supported so we default to InviteOnly
|
// All other cases are not supported so we default to InviteOnly
|
||||||
is JoinRule.Custom,
|
is JoinRule.Custom,
|
||||||
@@ -294,8 +371,9 @@ private fun SecurityAndPrivacyRoomAccess.map(): JoinRule? {
|
|||||||
SecurityAndPrivacyRoomAccess.Anyone -> JoinRule.Public
|
SecurityAndPrivacyRoomAccess.Anyone -> JoinRule.Public
|
||||||
SecurityAndPrivacyRoomAccess.AskToJoin -> JoinRule.Knock
|
SecurityAndPrivacyRoomAccess.AskToJoin -> JoinRule.Knock
|
||||||
SecurityAndPrivacyRoomAccess.InviteOnly -> JoinRule.Private
|
SecurityAndPrivacyRoomAccess.InviteOnly -> JoinRule.Private
|
||||||
// SpaceMember can't be selected in the ui
|
is SecurityAndPrivacyRoomAccess.SpaceMember -> JoinRule.Restricted(
|
||||||
SecurityAndPrivacyRoomAccess.SpaceMember -> null
|
rules = this.spaceIds.map { AllowRule.RoomMembership(it) }.toImmutableList()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,11 @@ package io.element.android.features.securityandprivacy.impl.root
|
|||||||
import io.element.android.features.securityandprivacy.api.SecurityAndPrivacyPermissions
|
import io.element.android.features.securityandprivacy.api.SecurityAndPrivacyPermissions
|
||||||
import io.element.android.libraries.architecture.AsyncAction
|
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.matrix.api.core.RoomId
|
||||||
|
import io.element.android.libraries.matrix.api.spaces.SpaceRoom
|
||||||
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
|
import kotlinx.collections.immutable.ImmutableSet
|
||||||
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
import kotlinx.collections.immutable.toImmutableList
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
|
|
||||||
data class SecurityAndPrivacyState(
|
data class SecurityAndPrivacyState(
|
||||||
@@ -24,8 +29,10 @@ data class SecurityAndPrivacyState(
|
|||||||
val saveAction: AsyncAction<Unit>,
|
val saveAction: AsyncAction<Unit>,
|
||||||
val isSpace: Boolean,
|
val isSpace: Boolean,
|
||||||
private val permissions: SecurityAndPrivacyPermissions,
|
private val permissions: SecurityAndPrivacyPermissions,
|
||||||
|
private val selectableJoinedSpaces: ImmutableSet<SpaceRoom>,
|
||||||
val eventSink: (SecurityAndPrivacyEvent) -> Unit
|
val eventSink: (SecurityAndPrivacyEvent) -> Unit
|
||||||
) {
|
) {
|
||||||
|
|
||||||
val canBeSaved = savedSettings != editedSettings
|
val canBeSaved = savedSettings != editedSettings
|
||||||
|
|
||||||
// Logic is in https://github.com/element-hq/element-meta/issues/3029
|
// Logic is in https://github.com/element-hq/element-meta/issues/3029
|
||||||
@@ -76,18 +83,31 @@ enum class SecurityAndPrivacyHistoryVisibility {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class SecurityAndPrivacyRoomAccess {
|
sealed interface SpaceSelection {
|
||||||
InviteOnly,
|
data object None : SpaceSelection
|
||||||
AskToJoin,
|
data class Single(val spaceId: RoomId, val spaceRoom: SpaceRoom?) : SpaceSelection
|
||||||
Anyone,
|
data object Multiple : SpaceSelection
|
||||||
SpaceMember;
|
}
|
||||||
|
|
||||||
|
sealed interface SecurityAndPrivacyRoomAccess {
|
||||||
|
data object InviteOnly : SecurityAndPrivacyRoomAccess
|
||||||
|
data object AskToJoin : SecurityAndPrivacyRoomAccess
|
||||||
|
data object Anyone : SecurityAndPrivacyRoomAccess
|
||||||
|
data class SpaceMember(val spaceIds: ImmutableList<RoomId>) : SecurityAndPrivacyRoomAccess
|
||||||
|
|
||||||
fun canConfigureRoomVisibility(): Boolean {
|
fun canConfigureRoomVisibility(): Boolean {
|
||||||
return when (this) {
|
return when (this) {
|
||||||
InviteOnly, SpaceMember -> false
|
InviteOnly, is SpaceMember -> false
|
||||||
AskToJoin, Anyone -> true
|
AskToJoin, Anyone -> true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun spaceIds(): ImmutableList<RoomId> {
|
||||||
|
return when (this) {
|
||||||
|
is SpaceMember -> spaceIds
|
||||||
|
else -> persistentListOf()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sealed class SecurityAndPrivacyFailures : Exception() {
|
sealed class SecurityAndPrivacyFailures : Exception() {
|
||||||
|
|||||||
@@ -12,6 +12,10 @@ import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
|||||||
import io.element.android.features.securityandprivacy.api.SecurityAndPrivacyPermissions
|
import io.element.android.features.securityandprivacy.api.SecurityAndPrivacyPermissions
|
||||||
import io.element.android.libraries.architecture.AsyncAction
|
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.matrix.api.spaces.SpaceRoom
|
||||||
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
|
import kotlinx.collections.immutable.toImmutableSet
|
||||||
|
|
||||||
open class SecurityAndPrivacyStateProvider : PreviewParameterProvider<SecurityAndPrivacyState> {
|
open class SecurityAndPrivacyStateProvider : PreviewParameterProvider<SecurityAndPrivacyState> {
|
||||||
override val values: Sequence<SecurityAndPrivacyState>
|
override val values: Sequence<SecurityAndPrivacyState>
|
||||||
@@ -61,7 +65,7 @@ private fun commonSecurityAndPrivacyStates(isSpace: Boolean): Sequence<SecurityA
|
|||||||
),
|
),
|
||||||
aSecurityAndPrivacyState(
|
aSecurityAndPrivacyState(
|
||||||
savedSettings = aSecurityAndPrivacySettings(
|
savedSettings = aSecurityAndPrivacySettings(
|
||||||
roomAccess = SecurityAndPrivacyRoomAccess.SpaceMember
|
roomAccess = SecurityAndPrivacyRoomAccess.SpaceMember(persistentListOf())
|
||||||
),
|
),
|
||||||
isSpace = isSpace,
|
isSpace = isSpace,
|
||||||
isKnockEnabled = false,
|
isKnockEnabled = false,
|
||||||
@@ -117,6 +121,7 @@ fun aSecurityAndPrivacyState(
|
|||||||
),
|
),
|
||||||
isKnockEnabled: Boolean = true,
|
isKnockEnabled: Boolean = true,
|
||||||
isSpace: Boolean = false,
|
isSpace: Boolean = false,
|
||||||
|
selectableJoinedSpaces: Set<SpaceRoom> = emptySet(),
|
||||||
eventSink: (SecurityAndPrivacyEvent) -> Unit = {}
|
eventSink: (SecurityAndPrivacyEvent) -> Unit = {}
|
||||||
) = SecurityAndPrivacyState(
|
) = SecurityAndPrivacyState(
|
||||||
editedSettings = editedSettings,
|
editedSettings = editedSettings,
|
||||||
@@ -127,5 +132,6 @@ fun aSecurityAndPrivacyState(
|
|||||||
isKnockEnabled = isKnockEnabled,
|
isKnockEnabled = isKnockEnabled,
|
||||||
permissions = permissions,
|
permissions = permissions,
|
||||||
isSpace = isSpace,
|
isSpace = isSpace,
|
||||||
|
selectableJoinedSpaces = selectableJoinedSpaces.toImmutableSet(),
|
||||||
eventSink = eventSink,
|
eventSink = eventSink,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ import io.element.android.libraries.designsystem.text.stringWithLink
|
|||||||
import io.element.android.libraries.designsystem.theme.components.CircularProgressIndicator
|
import io.element.android.libraries.designsystem.theme.components.CircularProgressIndicator
|
||||||
import io.element.android.libraries.designsystem.theme.components.IconSource
|
import io.element.android.libraries.designsystem.theme.components.IconSource
|
||||||
import io.element.android.libraries.designsystem.theme.components.ListItem
|
import io.element.android.libraries.designsystem.theme.components.ListItem
|
||||||
|
import io.element.android.libraries.designsystem.theme.components.ListSectionHeader
|
||||||
import io.element.android.libraries.designsystem.theme.components.Scaffold
|
import io.element.android.libraries.designsystem.theme.components.Scaffold
|
||||||
import io.element.android.libraries.designsystem.theme.components.Text
|
import io.element.android.libraries.designsystem.theme.components.Text
|
||||||
import io.element.android.libraries.designsystem.theme.components.TextButton
|
import io.element.android.libraries.designsystem.theme.components.TextButton
|
||||||
@@ -95,6 +96,8 @@ fun SecurityAndPrivacyView(
|
|||||||
saved = state.savedSettings.roomAccess,
|
saved = state.savedSettings.roomAccess,
|
||||||
isKnockEnabled = state.isKnockEnabled,
|
isKnockEnabled = state.isKnockEnabled,
|
||||||
onSelectOption = { state.eventSink(SecurityAndPrivacyEvent.ChangeRoomAccess(it)) },
|
onSelectOption = { state.eventSink(SecurityAndPrivacyEvent.ChangeRoomAccess(it)) },
|
||||||
|
onManageSpacesClick = { state.eventSink(SecurityAndPrivacyEvent.ManageAuthorizedSpaces) },
|
||||||
|
onSpaceMemberAccessClick = { state.eventSink(SecurityAndPrivacyEvent.SelectSpaceMemberAccess) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (state.showRoomVisibilitySections) {
|
if (state.showRoomVisibilitySections) {
|
||||||
@@ -212,6 +215,8 @@ private fun RoomAccessSection(
|
|||||||
saved: SecurityAndPrivacyRoomAccess,
|
saved: SecurityAndPrivacyRoomAccess,
|
||||||
isKnockEnabled: Boolean,
|
isKnockEnabled: Boolean,
|
||||||
onSelectOption: (SecurityAndPrivacyRoomAccess) -> Unit,
|
onSelectOption: (SecurityAndPrivacyRoomAccess) -> Unit,
|
||||||
|
onSpaceMemberAccessClick: () -> Unit,
|
||||||
|
onManageSpacesClick: () -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
SecurityAndPrivacySection(
|
SecurityAndPrivacySection(
|
||||||
@@ -226,17 +231,15 @@ private fun RoomAccessSection(
|
|||||||
onClick = { onSelectOption(SecurityAndPrivacyRoomAccess.Anyone) },
|
onClick = { onSelectOption(SecurityAndPrivacyRoomAccess.Anyone) },
|
||||||
)
|
)
|
||||||
// Show space member option, but disabled as we don't support this option for now.
|
// Show space member option, but disabled as we don't support this option for now.
|
||||||
if (saved == SecurityAndPrivacyRoomAccess.SpaceMember) {
|
|
||||||
ListItem(
|
ListItem(
|
||||||
headlineContent = { Text(text = stringResource(R.string.screen_security_and_privacy_room_access_space_members_option_title)) },
|
headlineContent = { Text(text = stringResource(R.string.screen_security_and_privacy_room_access_space_members_option_title)) },
|
||||||
supportingContent = {
|
supportingContent = {
|
||||||
Text(text = stringResource(R.string.screen_security_and_privacy_room_access_space_members_option_unavailable_description))
|
Text(text = stringResource(R.string.screen_security_and_privacy_room_access_space_members_option_unavailable_description))
|
||||||
},
|
},
|
||||||
trailingContent = ListItemContent.RadioButton(selected = edited == SecurityAndPrivacyRoomAccess.SpaceMember, enabled = false),
|
trailingContent = ListItemContent.RadioButton(selected = edited is SecurityAndPrivacyRoomAccess.SpaceMember),
|
||||||
leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.Space())),
|
leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.Space())),
|
||||||
enabled = false,
|
onClick = onSpaceMemberAccessClick,
|
||||||
)
|
)
|
||||||
}
|
|
||||||
// Show Ask to join option in two cases:
|
// Show Ask to join option in two cases:
|
||||||
// - the Knock FF is enabled
|
// - the Knock FF is enabled
|
||||||
// - AskToJoin is the current saved value
|
// - AskToJoin is the current saved value
|
||||||
@@ -257,6 +260,19 @@ private fun RoomAccessSection(
|
|||||||
leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.Lock())),
|
leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.Lock())),
|
||||||
onClick = { onSelectOption(SecurityAndPrivacyRoomAccess.InviteOnly) },
|
onClick = { onSelectOption(SecurityAndPrivacyRoomAccess.InviteOnly) },
|
||||||
)
|
)
|
||||||
|
if (edited is SecurityAndPrivacyRoomAccess.SpaceMember) {
|
||||||
|
val footerText = stringWithLink(
|
||||||
|
textRes = R.string.screen_security_and_privacy_room_access_footer,
|
||||||
|
url = stringResource(R.string.screen_security_and_privacy_room_access_footer_manage_spaces_action),
|
||||||
|
onLinkClick = { onManageSpacesClick()},
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = footerText,
|
||||||
|
style = ElementTheme.typography.fontBodySmRegular,
|
||||||
|
color = ElementTheme.colors.textSecondary,
|
||||||
|
modifier = Modifier.padding(bottom = 12.dp, start = 56.dp, end = 24.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user