Refactor space selection to use SpaceSelectionStateHolder

Move authorized space selection state to a shared StateHolder scoped
to RoomScope. This simplifies communication between SecurityAndPrivacy
and ManageAuthorizedSpaces nodes by replacing the complex coroutine-based
parent-child coordination with a reactive state flow pattern.
This commit is contained in:
ganfra 2026-01-09 12:00:54 +01:00
parent c66f8c8c34
commit 993cf838a0
17 changed files with 283 additions and 278 deletions

View file

@ -13,14 +13,12 @@ import androidx.annotation.VisibleForTesting
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.coroutineScope
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import com.bumble.appyx.core.modality.BuildContext
import com.bumble.appyx.core.node.Node
import com.bumble.appyx.core.plugin.Plugin
import com.bumble.appyx.navmodel.backstack.BackStack
import com.bumble.appyx.navmodel.backstack.activeElement
import dev.zacsweers.metro.Assisted
import dev.zacsweers.metro.AssistedInject
import io.element.android.annotations.ContributesNode
@ -36,12 +34,10 @@ import io.element.android.libraries.architecture.createNode
import io.element.android.libraries.di.RoomScope
import io.element.android.libraries.matrix.api.room.JoinedRoom
import io.element.android.libraries.matrix.api.room.powerlevels.use
import kotlinx.coroutines.NonCancellable
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlinx.parcelize.Parcelize
@ContributesNode(RoomScope::class)
@ -66,7 +62,7 @@ class SecurityAndPrivacyFlowNode(
data object EditRoomAddress : NavTarget
@Parcelize
data class ManageAuthorizedSpaces(val forKnockRestricted: Boolean = false) : NavTarget
data object ManageAuthorizedSpaces : NavTarget
}
private val callback: SecurityAndPrivacyEntryPoint.Callback = callback()
@ -90,21 +86,6 @@ class SecurityAndPrivacyFlowNode(
callback.onDone()
}
}
whenChildrenAttached {
commonLifecycle: Lifecycle,
securityAndPrivacyNode: SecurityAndPrivacyNode,
manageAuthorizedSpacesNode: ManageAuthorizedSpacesNode
->
commonLifecycle.coroutineScope.launch {
val authorizedSpacesData = securityAndPrivacyNode.getAuthorizedSpacesData()
val selectedSpaces = manageAuthorizedSpacesNode.waitForCompletion(authorizedSpacesData)
val forKnock = (backstack.activeElement as? NavTarget.ManageAuthorizedSpaces)?.forKnockRestricted ?: false
withContext(NonCancellable) {
navigator.closeManageAuthorizedSpaces()
securityAndPrivacyNode.onAuthorizedSpacesSelected(selectedSpaces, forKnock = forKnock)
}
}
}
}
override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node {
@ -115,7 +96,7 @@ class SecurityAndPrivacyFlowNode(
NavTarget.EditRoomAddress -> {
createNode<EditRoomAddressNode>(buildContext, plugins = listOf(navigator))
}
is NavTarget.ManageAuthorizedSpaces -> {
NavTarget.ManageAuthorizedSpaces -> {
createNode<ManageAuthorizedSpacesNode>(buildContext, plugins = listOf(navigator))
}
}

View file

@ -18,7 +18,7 @@ interface SecurityAndPrivacyNavigator : Plugin {
fun onDone()
fun openEditRoomAddress()
fun closeEditRoomAddress()
fun openManageAuthorizedSpaces(forKnockRestricted: Boolean)
fun openManageAuthorizedSpaces()
fun closeManageAuthorizedSpaces()
}
@ -38,8 +38,8 @@ class BackstackSecurityAndPrivacyNavigator(
backStack.pop()
}
override fun openManageAuthorizedSpaces(forKnockRestricted: Boolean) {
backStack.push(SecurityAndPrivacyFlowNode.NavTarget.ManageAuthorizedSpaces(forKnockRestricted))
override fun openManageAuthorizedSpaces() {
backStack.push(SecurityAndPrivacyFlowNode.NavTarget.ManageAuthorizedSpaces)
}
override fun closeManageAuthorizedSpaces() {

View file

@ -11,7 +11,7 @@ package io.element.android.features.securityandprivacy.impl.manageauthorizedspac
import io.element.android.libraries.matrix.api.core.RoomId
sealed interface ManageAuthorizedSpacesEvent {
data class SetData(val data: AuthorizedSpacesSelection) : ManageAuthorizedSpacesEvent
data object Cancel : ManageAuthorizedSpacesEvent
data object Done : ManageAuthorizedSpacesEvent
data class ToggleSpace(val roomId: RoomId) : ManageAuthorizedSpacesEvent
}

View file

@ -19,12 +19,8 @@ import com.bumble.appyx.core.plugin.plugins
import dev.zacsweers.metro.Assisted
import dev.zacsweers.metro.AssistedInject
import io.element.android.annotations.ContributesNode
import io.element.android.features.securityandprivacy.impl.SecurityAndPrivacyNavigator
import io.element.android.libraries.architecture.appyx.launchMolecule
import io.element.android.libraries.di.RoomScope
import io.element.android.libraries.matrix.api.core.RoomId
import kotlinx.collections.immutable.ImmutableList
import kotlinx.coroutines.flow.first
@ContributesNode(RoomScope::class)
@AssistedInject
@ -33,20 +29,13 @@ class ManageAuthorizedSpacesNode(
@Assisted plugins: List<Plugin>,
presenter: ManageAuthorizedSpacesPresenter,
) : Node(buildContext, plugins = plugins) {
private val navigator = plugins<SecurityAndPrivacyNavigator>().first()
private val stateFlow = launchMolecule { presenter.present() }
suspend fun waitForCompletion(data: AuthorizedSpacesSelection): ImmutableList<RoomId> {
stateFlow.value.eventSink(ManageAuthorizedSpacesEvent.SetData(data))
return stateFlow.first { it.isSelectionComplete }.selectedIds
}
@Composable
override fun View(modifier: Modifier) {
val state by stateFlow.collectAsState()
ManageAuthorizedSpacesView(
state = state,
onBackClick = { navigator.closeManageAuthorizedSpaces() },
modifier = modifier
)
}

View file

@ -9,46 +9,43 @@
package io.element.android.features.securityandprivacy.impl.manageauthorizedspaces
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import dev.zacsweers.metro.Inject
import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.matrix.api.core.RoomId
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList
@Inject
class ManageAuthorizedSpacesPresenter : Presenter<ManageAuthorizedSpacesState> {
class ManageAuthorizedSpacesPresenter(
private val spaceSelectionStateHolder: SpaceSelectionStateHolder,
) : Presenter<ManageAuthorizedSpacesState> {
@Composable
override fun present(): ManageAuthorizedSpacesState {
var selectedIds: ImmutableList<RoomId> by remember { mutableStateOf(persistentListOf()) }
var spacesSelection by remember { mutableStateOf(AuthorizedSpacesSelection()) }
var isSelectionComplete by remember { mutableStateOf(false) }
val spaceSelectionState by spaceSelectionStateHolder.state.collectAsState()
fun handleEvent(event: ManageAuthorizedSpacesEvent) {
when (event) {
ManageAuthorizedSpacesEvent.Done -> isSelectionComplete = true
is ManageAuthorizedSpacesEvent.ToggleSpace -> {
selectedIds = if (selectedIds.contains(event.roomId)) {
selectedIds.minus(event.roomId).toImmutableList()
val currentSelectedIds = spaceSelectionState.selectedSpaceIds
val newSelectedIds = if (currentSelectedIds.contains(event.roomId)) {
currentSelectedIds.minus(event.roomId).toImmutableList()
} else {
selectedIds.plus(event.roomId).toImmutableList()
currentSelectedIds.plus(event.roomId).toImmutableList()
}
spaceSelectionStateHolder.updateSelectedSpaceIds(newSelectedIds)
}
is ManageAuthorizedSpacesEvent.SetData -> {
spacesSelection = event.data
selectedIds = event.data.initialSelectedIds
ManageAuthorizedSpacesEvent.Done -> {
spaceSelectionStateHolder.setCompletion(SpaceSelectionState.Completion.Completed)
}
ManageAuthorizedSpacesEvent.Cancel -> {
spaceSelectionStateHolder.setCompletion(SpaceSelectionState.Completion.Cancelled)
}
}
}
return ManageAuthorizedSpacesState(
selection = spacesSelection,
selectedIds = selectedIds,
isSelectionComplete = isSelectionComplete,
selectableSpaces = spaceSelectionState.selectableSpaces,
unknownSpaceIds = spaceSelectionState.unknownSpaceIds,
selectedIds = spaceSelectionState.selectedSpaceIds,
eventSink = ::handleEvent,
)
}

View file

@ -11,19 +11,13 @@ package io.element.android.features.securityandprivacy.impl.manageauthorizedspac
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.persistentListOf
import kotlinx.collections.immutable.ImmutableSet
data class ManageAuthorizedSpacesState(
val selection: AuthorizedSpacesSelection,
val selectableSpaces: ImmutableSet<SpaceRoom>,
val unknownSpaceIds: ImmutableList<RoomId>,
val selectedIds: ImmutableList<RoomId>,
val isSelectionComplete: Boolean,
val eventSink: (ManageAuthorizedSpacesEvent) -> Unit
) {
val isDoneButtonEnabled = selectedIds.isNotEmpty()
}
data class AuthorizedSpacesSelection(
val joinedSpaces: ImmutableList<SpaceRoom> = persistentListOf(),
val unknownSpaceIds: ImmutableList<RoomId> = persistentListOf(),
val initialSelectedIds: ImmutableList<RoomId> = persistentListOf()
)

View file

@ -14,21 +14,17 @@ import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.spaces.SpaceRoom
import io.element.android.libraries.previewutils.room.aSpaceRoom
import kotlinx.collections.immutable.toImmutableList
import kotlinx.collections.immutable.toImmutableSet
open class ManageAuthorizedSpacesStateProvider : PreviewParameterProvider<ManageAuthorizedSpacesState> {
override val values: Sequence<ManageAuthorizedSpacesState>
get() = sequenceOf(
aManageAuthorizedSpacesState(),
aManageAuthorizedSpacesState(
authorizedSpacesSelection = anAuthorizedSpaceSelection(
unknownSpaceIds = listOf(aRoomId(99))
)
unknownSpaceIds = listOf(aRoomId(99))
),
aManageAuthorizedSpacesState(
selectedIds = listOf(aRoomId(1), aRoomId(3)),
authorizedSpacesSelection = anAuthorizedSpaceSelection(
initialSelectedIds = listOf(aRoomId(1)),
),
),
)
}
@ -49,23 +45,14 @@ private fun aSpaceRoomList(count: Int): List<SpaceRoom> {
}
}
fun anAuthorizedSpaceSelection(
joinedSpaces: List<SpaceRoom> = aSpaceRoomList(5),
fun aManageAuthorizedSpacesState(
selectableSpaces: List<SpaceRoom> = aSpaceRoomList(5),
unknownSpaceIds: List<RoomId> = emptyList(),
initialSelectedIds: List<RoomId> = emptyList(),
) = AuthorizedSpacesSelection(
joinedSpaces = joinedSpaces.toImmutableList(),
unknownSpaceIds = unknownSpaceIds.toImmutableList(),
initialSelectedIds = initialSelectedIds.toImmutableList(),
)
private fun aManageAuthorizedSpacesState(
authorizedSpacesSelection: AuthorizedSpacesSelection = anAuthorizedSpaceSelection(),
selectedIds: List<RoomId> = emptyList(),
eventSink: (ManageAuthorizedSpacesEvent) -> Unit = {},
) = ManageAuthorizedSpacesState(
selection = authorizedSpacesSelection,
selectableSpaces = selectableSpaces.toImmutableSet(),
unknownSpaceIds = unknownSpaceIds.toImmutableList(),
selectedIds = selectedIds.toImmutableList(),
isSelectionComplete = false,
eventSink = eventSink,
)

View file

@ -8,6 +8,7 @@
package io.element.android.features.securityandprivacy.impl.manageauthorizedspaces
import androidx.activity.compose.BackHandler
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyListScope
@ -42,17 +43,24 @@ import io.element.android.libraries.ui.strings.CommonStrings
@Composable
fun ManageAuthorizedSpacesView(
state: ManageAuthorizedSpacesState,
onBackClick: () -> Unit,
modifier: Modifier = Modifier,
) {
fun onCancel() {
state.eventSink(ManageAuthorizedSpacesEvent.Cancel)
}
fun onDone() {
state.eventSink(ManageAuthorizedSpacesEvent.Done)
}
BackHandler(onBack = ::onCancel)
Scaffold(
modifier = modifier,
topBar = {
ManageAuthorizedSpacesTopBar(
onBackClick = onBackClick,
onDoneClick = {
state.eventSink(ManageAuthorizedSpacesEvent.Done)
},
onBackClick = ::onCancel,
onDoneClick = ::onDone,
isDoneButtonEnabled = state.isDoneButtonEnabled
)
}
@ -67,7 +75,7 @@ fun ManageAuthorizedSpacesView(
hasDivider = false,
)
}
items(items = state.selection.joinedSpaces) { space ->
items(items = state.selectableSpaces.toList()) { space ->
CheckableSpaceListItem(
headlineText = space.displayName,
supportingText = space.canonicalAlias?.value,
@ -80,14 +88,14 @@ fun ManageAuthorizedSpacesView(
}
)
}
if (state.selection.unknownSpaceIds.isNotEmpty()) {
if (state.unknownSpaceIds.isNotEmpty()) {
item {
ListSectionHeader(
title = stringResource(R.string.screen_manage_authorized_spaces_unknown_spaces_section_title),
hasDivider = true,
)
}
items(items = state.selection.unknownSpaceIds) {
items(items = state.unknownSpaceIds) {
CheckableSpaceListItem(
headlineText = stringResource(R.string.screen_manage_authorized_spaces_unknown_space),
supportingText = it.value,
@ -185,8 +193,5 @@ private fun ManageAuthorizedSpacesTopBar(
internal fun ManageAuthorizedSpacesViewPreview(
@PreviewParameter(ManageAuthorizedSpacesStateProvider::class) state: ManageAuthorizedSpacesState
) = ElementPreview {
ManageAuthorizedSpacesView(
state = state,
onBackClick = {},
)
ManageAuthorizedSpacesView(state = state)
}

View file

@ -0,0 +1,64 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
* Copyright 2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.features.securityandprivacy.impl.manageauthorizedspaces
import dev.zacsweers.metro.Inject
import dev.zacsweers.metro.SingleIn
import io.element.android.libraries.di.RoomScope
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.persistentSetOf
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
data class SpaceSelectionState(
val selectableSpaces: ImmutableSet<SpaceRoom>,
val unknownSpaceIds: ImmutableList<RoomId>,
val selectedSpaceIds: ImmutableList<RoomId>,
val completion: Completion,
) {
enum class Completion {
Initial,
Completed,
Cancelled,
}
companion object {
val INITIAL = SpaceSelectionState(
selectableSpaces = persistentSetOf(),
unknownSpaceIds = persistentListOf(),
selectedSpaceIds = persistentListOf(),
completion = Completion.Initial,
)
}
}
@Inject
@SingleIn(RoomScope::class)
class SpaceSelectionStateHolder {
private val _state = MutableStateFlow(SpaceSelectionState.INITIAL)
val state: StateFlow<SpaceSelectionState> = _state.asStateFlow()
fun update(transform: (SpaceSelectionState) -> SpaceSelectionState) {
_state.update(transform)
}
fun updateSelectedSpaceIds(selectedSpaceIds: ImmutableList<RoomId>) {
update { it.copy(selectedSpaceIds = selectedSpaceIds) }
}
fun setCompletion(completion: SpaceSelectionState.Completion) {
update { it.copy(completion = completion) }
}
}

View file

@ -23,12 +23,9 @@ import dev.zacsweers.metro.AssistedInject
import io.element.android.annotations.ContributesNode
import io.element.android.compound.theme.ElementTheme
import io.element.android.features.securityandprivacy.impl.SecurityAndPrivacyNavigator
import io.element.android.features.securityandprivacy.impl.manageauthorizedspaces.AuthorizedSpacesSelection
import io.element.android.libraries.androidutils.browser.openUrlInChromeCustomTab
import io.element.android.libraries.architecture.appyx.launchMolecule
import io.element.android.libraries.di.RoomScope
import io.element.android.libraries.matrix.api.core.RoomId
import kotlinx.collections.immutable.ImmutableList
@ContributesNode(RoomScope::class)
@AssistedInject
@ -46,19 +43,6 @@ class SecurityAndPrivacyNode(
activity.openUrlInChromeCustomTab(null, darkTheme, url)
}
fun getAuthorizedSpacesData(): AuthorizedSpacesSelection {
return stateFlow.value.getAuthorizedSpacesSelection()
}
fun onAuthorizedSpacesSelected(selectedSpaces: ImmutableList<RoomId>, forKnock: Boolean) {
val roomAccess = if (forKnock) {
SecurityAndPrivacyRoomAccess.AskToJoinWithSpaceMember(selectedSpaces)
} else {
SecurityAndPrivacyRoomAccess.SpaceMember(selectedSpaces)
}
stateFlow.value.eventSink(SecurityAndPrivacyEvent.ChangeRoomAccess(roomAccess))
}
@Composable
override fun View(modifier: Modifier) {
val activity = requireNotNull(LocalActivity.current)

View file

@ -26,6 +26,8 @@ import io.element.android.features.securityandprivacy.api.SecurityAndPrivacyPerm
import io.element.android.features.securityandprivacy.api.securityAndPrivacyPermissions
import io.element.android.features.securityandprivacy.impl.SecurityAndPrivacyNavigator
import io.element.android.features.securityandprivacy.impl.editroomaddress.matchesServer
import io.element.android.features.securityandprivacy.impl.manageauthorizedspaces.SpaceSelectionState
import io.element.android.features.securityandprivacy.impl.manageauthorizedspaces.SpaceSelectionStateHolder
import io.element.android.libraries.architecture.AsyncAction
import io.element.android.libraries.architecture.AsyncData
import io.element.android.libraries.architecture.Presenter
@ -51,18 +53,22 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
@AssistedInject
class SecurityAndPrivacyPresenter(
@Assisted private val navigator: SecurityAndPrivacyNavigator,
private val spaceSelectionStateHolder: SpaceSelectionStateHolder,
private val matrixClient: MatrixClient,
private val room: JoinedRoom,
private val featureFlagService: FeatureFlagService,
) : Presenter<SecurityAndPrivacyState> {
@AssistedFactory
interface Factory {
fun create(navigator: SecurityAndPrivacyNavigator): SecurityAndPrivacyPresenter
fun create(
navigator: SecurityAndPrivacyNavigator,
): SecurityAndPrivacyPresenter
}
@Composable
@ -136,6 +142,18 @@ class SecurityAndPrivacyPresenter(
}
}
LaunchedEffect(selectableJoinedSpaces, savedSettings.roomAccess) {
val unknownSpaceIds = savedSettings.roomAccess.spaceIds().filter { spaceId ->
selectableJoinedSpaces.none { it.roomId == spaceId }
}.toImmutableList()
spaceSelectionStateHolder.update { state ->
state.copy(
selectableSpaces = selectableJoinedSpaces,
unknownSpaceIds = unknownSpaceIds,
)
}
}
var showEnableEncryptionConfirmation by remember(savedSettings.isEncrypted) { mutableStateOf(false) }
val permissions by room.permissionsAsState(SecurityAndPrivacyPermissions.DEFAULT) { perms ->
perms.securityAndPrivacyPermissions()
@ -191,19 +209,27 @@ class SecurityAndPrivacyPresenter(
SecurityAndPrivacyEvent.DismissExitConfirmation -> {
saveAction.value = AsyncAction.Uninitialized
}
SecurityAndPrivacyEvent.ManageAuthorizedSpaces -> {
navigator.openManageAuthorizedSpaces(
SecurityAndPrivacyEvent.ManageAuthorizedSpaces -> coroutineScope.launch {
handleMultipleSelection(
savedAccess = savedSettings.roomAccess,
editedRoomAccess = editedRoomAccess,
forKnockRestricted = editedRoomAccess.value is SecurityAndPrivacyRoomAccess.AskToJoinWithSpaceMember
)
}
SecurityAndPrivacyEvent.SelectSpaceMemberAccess -> handleSpaceMemberAccessSelection(
spaceSelectionMode = spaceSelectionMode,
editedAccess = editedRoomAccess,
)
SecurityAndPrivacyEvent.SelectAskToJoinWithSpaceMembersAccess -> handleAskToJoinWithSpaceMembersAccessSelection(
spaceSelectionMode = spaceSelectionMode,
editedAccess = editedRoomAccess,
)
SecurityAndPrivacyEvent.SelectSpaceMemberAccess -> coroutineScope.launch {
handleSpaceMemberAccessSelection(
spaceSelectionMode = spaceSelectionMode,
savedAccess = savedSettings.roomAccess,
editedAccess = editedRoomAccess,
)
}
SecurityAndPrivacyEvent.SelectAskToJoinWithSpaceMembersAccess -> coroutineScope.launch {
handleAskToJoinWithSpaceMembersAccessSelection(
spaceSelectionMode = spaceSelectionMode,
savedAccess = savedSettings.roomAccess,
editedAccess = editedRoomAccess,
)
}
}
}
@ -248,8 +274,9 @@ class SecurityAndPrivacyPresenter(
return state
}
private fun handleSpaceMemberAccessSelection(
private suspend fun handleSpaceMemberAccessSelection(
spaceSelectionMode: SpaceSelectionMode,
savedAccess: SecurityAndPrivacyRoomAccess,
editedAccess: MutableState<SecurityAndPrivacyRoomAccess>,
) {
if (editedAccess.value is SecurityAndPrivacyRoomAccess.SpaceMember) {
@ -257,7 +284,11 @@ class SecurityAndPrivacyPresenter(
}
when (spaceSelectionMode) {
is SpaceSelectionMode.None -> Unit
is SpaceSelectionMode.Multiple -> navigator.openManageAuthorizedSpaces(forKnockRestricted = false)
is SpaceSelectionMode.Multiple -> handleMultipleSelection(
savedAccess = savedAccess,
editedRoomAccess = editedAccess,
forKnockRestricted = false,
)
is SpaceSelectionMode.Single -> {
val newRoomAccess = SecurityAndPrivacyRoomAccess.SpaceMember(
spaceIds = persistentListOf(spaceSelectionMode.spaceId)
@ -267,8 +298,9 @@ class SecurityAndPrivacyPresenter(
}
}
private fun handleAskToJoinWithSpaceMembersAccessSelection(
private suspend fun handleAskToJoinWithSpaceMembersAccessSelection(
spaceSelectionMode: SpaceSelectionMode,
savedAccess: SecurityAndPrivacyRoomAccess,
editedAccess: MutableState<SecurityAndPrivacyRoomAccess>,
) {
if (editedAccess.value is SecurityAndPrivacyRoomAccess.AskToJoinWithSpaceMember) {
@ -276,7 +308,11 @@ class SecurityAndPrivacyPresenter(
}
when (spaceSelectionMode) {
is SpaceSelectionMode.None -> Unit
is SpaceSelectionMode.Multiple -> navigator.openManageAuthorizedSpaces(forKnockRestricted = true)
is SpaceSelectionMode.Multiple -> handleMultipleSelection(
savedAccess = savedAccess,
editedRoomAccess = editedAccess,
forKnockRestricted = true,
)
is SpaceSelectionMode.Single -> {
val newRoomAccess = SecurityAndPrivacyRoomAccess.AskToJoinWithSpaceMember(
spaceIds = persistentListOf(spaceSelectionMode.spaceId)
@ -286,6 +322,38 @@ class SecurityAndPrivacyPresenter(
}
}
private suspend fun handleMultipleSelection(
savedAccess: SecurityAndPrivacyRoomAccess,
editedRoomAccess: MutableState<SecurityAndPrivacyRoomAccess>,
forKnockRestricted: Boolean
) {
val initialSelection = when (val currentRoomAccess = editedRoomAccess.value) {
is SecurityAndPrivacyRoomAccess.SpaceMember -> currentRoomAccess.spaceIds
is SecurityAndPrivacyRoomAccess.AskToJoinWithSpaceMember -> currentRoomAccess.spaceIds
else -> savedAccess.spaceIds()
}
spaceSelectionStateHolder.update { state ->
state.copy(selectedSpaceIds = initialSelection, completion = SpaceSelectionState.Completion.Initial)
}
navigator.openManageAuthorizedSpaces()
val newState = spaceSelectionStateHolder.state.first { it.completion != SpaceSelectionState.Completion.Initial }
when (newState.completion) {
SpaceSelectionState.Completion.Initial -> Unit
SpaceSelectionState.Completion.Cancelled -> {
navigator.closeManageAuthorizedSpaces()
}
SpaceSelectionState.Completion.Completed -> {
val selectedIds = newState.selectedSpaceIds
editedRoomAccess.value = if (forKnockRestricted) {
SecurityAndPrivacyRoomAccess.AskToJoinWithSpaceMember(spaceIds = selectedIds)
} else {
SecurityAndPrivacyRoomAccess.SpaceMember(spaceIds = selectedIds)
}
navigator.closeManageAuthorizedSpaces()
}
}
}
private fun getSpaceSelectionMode(
selectableJoinedSpaces: Set<SpaceRoom>,
savedAccess: SecurityAndPrivacyRoomAccess,

View file

@ -12,7 +12,6 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource
import io.element.android.features.securityandprivacy.api.SecurityAndPrivacyPermissions
import io.element.android.features.securityandprivacy.impl.R
import io.element.android.features.securityandprivacy.impl.manageauthorizedspaces.AuthorizedSpacesSelection
import io.element.android.libraries.architecture.AsyncAction
import io.element.android.libraries.architecture.AsyncData
import io.element.android.libraries.matrix.api.core.RoomId
@ -121,20 +120,6 @@ data class SecurityAndPrivacyState(
stringResource(R.string.screen_security_and_privacy_ask_to_join_option_description)
}
}
fun getAuthorizedSpacesSelection(): AuthorizedSpacesSelection {
return AuthorizedSpacesSelection(
joinedSpaces = selectableJoinedSpaces.toImmutableList(),
unknownSpaceIds = savedSettings.roomAccess.spaceIds().filter { spaceId ->
selectableJoinedSpaces.none { it.roomId == spaceId }
}.toImmutableList(),
initialSelectedIds = when (editedSettings.roomAccess) {
is SecurityAndPrivacyRoomAccess.SpaceMember -> editedSettings.roomAccess.spaceIds
is SecurityAndPrivacyRoomAccess.AskToJoinWithSpaceMember -> editedSettings.roomAccess.spaceIds
else -> savedSettings.roomAccess.spaceIds()
}
)
}
}
data class SecurityAndPrivacySettings(