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:
parent
c66f8c8c34
commit
993cf838a0
17 changed files with 283 additions and 278 deletions
|
|
@ -13,14 +13,12 @@ import androidx.annotation.VisibleForTesting
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.lifecycle.Lifecycle
|
import androidx.lifecycle.Lifecycle
|
||||||
import androidx.lifecycle.coroutineScope
|
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.lifecycle.repeatOnLifecycle
|
import androidx.lifecycle.repeatOnLifecycle
|
||||||
import com.bumble.appyx.core.modality.BuildContext
|
import com.bumble.appyx.core.modality.BuildContext
|
||||||
import com.bumble.appyx.core.node.Node
|
import com.bumble.appyx.core.node.Node
|
||||||
import com.bumble.appyx.core.plugin.Plugin
|
import com.bumble.appyx.core.plugin.Plugin
|
||||||
import com.bumble.appyx.navmodel.backstack.BackStack
|
import com.bumble.appyx.navmodel.backstack.BackStack
|
||||||
import com.bumble.appyx.navmodel.backstack.activeElement
|
|
||||||
import dev.zacsweers.metro.Assisted
|
import dev.zacsweers.metro.Assisted
|
||||||
import dev.zacsweers.metro.AssistedInject
|
import dev.zacsweers.metro.AssistedInject
|
||||||
import io.element.android.annotations.ContributesNode
|
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.di.RoomScope
|
||||||
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.powerlevels.use
|
import io.element.android.libraries.matrix.api.room.powerlevels.use
|
||||||
import kotlinx.coroutines.NonCancellable
|
|
||||||
import kotlinx.coroutines.flow.filter
|
import kotlinx.coroutines.flow.filter
|
||||||
import kotlinx.coroutines.flow.first
|
import kotlinx.coroutines.flow.first
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
|
||||||
import kotlinx.parcelize.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
|
|
||||||
@ContributesNode(RoomScope::class)
|
@ContributesNode(RoomScope::class)
|
||||||
|
|
@ -66,7 +62,7 @@ class SecurityAndPrivacyFlowNode(
|
||||||
data object EditRoomAddress : NavTarget
|
data object EditRoomAddress : NavTarget
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
data class ManageAuthorizedSpaces(val forKnockRestricted: Boolean = false) : NavTarget
|
data object ManageAuthorizedSpaces : NavTarget
|
||||||
}
|
}
|
||||||
|
|
||||||
private val callback: SecurityAndPrivacyEntryPoint.Callback = callback()
|
private val callback: SecurityAndPrivacyEntryPoint.Callback = callback()
|
||||||
|
|
@ -90,21 +86,6 @@ class SecurityAndPrivacyFlowNode(
|
||||||
callback.onDone()
|
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 {
|
override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node {
|
||||||
|
|
@ -115,7 +96,7 @@ class SecurityAndPrivacyFlowNode(
|
||||||
NavTarget.EditRoomAddress -> {
|
NavTarget.EditRoomAddress -> {
|
||||||
createNode<EditRoomAddressNode>(buildContext, plugins = listOf(navigator))
|
createNode<EditRoomAddressNode>(buildContext, plugins = listOf(navigator))
|
||||||
}
|
}
|
||||||
is NavTarget.ManageAuthorizedSpaces -> {
|
NavTarget.ManageAuthorizedSpaces -> {
|
||||||
createNode<ManageAuthorizedSpacesNode>(buildContext, plugins = listOf(navigator))
|
createNode<ManageAuthorizedSpacesNode>(buildContext, plugins = listOf(navigator))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ interface SecurityAndPrivacyNavigator : Plugin {
|
||||||
fun onDone()
|
fun onDone()
|
||||||
fun openEditRoomAddress()
|
fun openEditRoomAddress()
|
||||||
fun closeEditRoomAddress()
|
fun closeEditRoomAddress()
|
||||||
fun openManageAuthorizedSpaces(forKnockRestricted: Boolean)
|
fun openManageAuthorizedSpaces()
|
||||||
fun closeManageAuthorizedSpaces()
|
fun closeManageAuthorizedSpaces()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -38,8 +38,8 @@ class BackstackSecurityAndPrivacyNavigator(
|
||||||
backStack.pop()
|
backStack.pop()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun openManageAuthorizedSpaces(forKnockRestricted: Boolean) {
|
override fun openManageAuthorizedSpaces() {
|
||||||
backStack.push(SecurityAndPrivacyFlowNode.NavTarget.ManageAuthorizedSpaces(forKnockRestricted))
|
backStack.push(SecurityAndPrivacyFlowNode.NavTarget.ManageAuthorizedSpaces)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun closeManageAuthorizedSpaces() {
|
override fun closeManageAuthorizedSpaces() {
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ package io.element.android.features.securityandprivacy.impl.manageauthorizedspac
|
||||||
import io.element.android.libraries.matrix.api.core.RoomId
|
import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
|
|
||||||
sealed interface ManageAuthorizedSpacesEvent {
|
sealed interface ManageAuthorizedSpacesEvent {
|
||||||
data class SetData(val data: AuthorizedSpacesSelection) : ManageAuthorizedSpacesEvent
|
data object Cancel : ManageAuthorizedSpacesEvent
|
||||||
data object Done : ManageAuthorizedSpacesEvent
|
data object Done : ManageAuthorizedSpacesEvent
|
||||||
data class ToggleSpace(val roomId: RoomId) : ManageAuthorizedSpacesEvent
|
data class ToggleSpace(val roomId: RoomId) : ManageAuthorizedSpacesEvent
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,12 +19,8 @@ import com.bumble.appyx.core.plugin.plugins
|
||||||
import dev.zacsweers.metro.Assisted
|
import dev.zacsweers.metro.Assisted
|
||||||
import dev.zacsweers.metro.AssistedInject
|
import dev.zacsweers.metro.AssistedInject
|
||||||
import io.element.android.annotations.ContributesNode
|
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.architecture.appyx.launchMolecule
|
||||||
import io.element.android.libraries.di.RoomScope
|
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)
|
@ContributesNode(RoomScope::class)
|
||||||
@AssistedInject
|
@AssistedInject
|
||||||
|
|
@ -33,20 +29,13 @@ class ManageAuthorizedSpacesNode(
|
||||||
@Assisted plugins: List<Plugin>,
|
@Assisted plugins: List<Plugin>,
|
||||||
presenter: ManageAuthorizedSpacesPresenter,
|
presenter: ManageAuthorizedSpacesPresenter,
|
||||||
) : Node(buildContext, plugins = plugins) {
|
) : Node(buildContext, plugins = plugins) {
|
||||||
private val navigator = plugins<SecurityAndPrivacyNavigator>().first()
|
|
||||||
private val stateFlow = launchMolecule { presenter.present() }
|
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
|
@Composable
|
||||||
override fun View(modifier: Modifier) {
|
override fun View(modifier: Modifier) {
|
||||||
val state by stateFlow.collectAsState()
|
val state by stateFlow.collectAsState()
|
||||||
ManageAuthorizedSpacesView(
|
ManageAuthorizedSpacesView(
|
||||||
state = state,
|
state = state,
|
||||||
onBackClick = { navigator.closeManageAuthorizedSpaces() },
|
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,46 +9,43 @@
|
||||||
package io.element.android.features.securityandprivacy.impl.manageauthorizedspaces
|
package io.element.android.features.securityandprivacy.impl.manageauthorizedspaces
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
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 dev.zacsweers.metro.Inject
|
||||||
import io.element.android.libraries.architecture.Presenter
|
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
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
class ManageAuthorizedSpacesPresenter : Presenter<ManageAuthorizedSpacesState> {
|
class ManageAuthorizedSpacesPresenter(
|
||||||
|
private val spaceSelectionStateHolder: SpaceSelectionStateHolder,
|
||||||
|
) : Presenter<ManageAuthorizedSpacesState> {
|
||||||
@Composable
|
@Composable
|
||||||
override fun present(): ManageAuthorizedSpacesState {
|
override fun present(): ManageAuthorizedSpacesState {
|
||||||
var selectedIds: ImmutableList<RoomId> by remember { mutableStateOf(persistentListOf()) }
|
val spaceSelectionState by spaceSelectionStateHolder.state.collectAsState()
|
||||||
var spacesSelection by remember { mutableStateOf(AuthorizedSpacesSelection()) }
|
|
||||||
var isSelectionComplete by remember { mutableStateOf(false) }
|
|
||||||
|
|
||||||
fun handleEvent(event: ManageAuthorizedSpacesEvent) {
|
fun handleEvent(event: ManageAuthorizedSpacesEvent) {
|
||||||
when (event) {
|
when (event) {
|
||||||
ManageAuthorizedSpacesEvent.Done -> isSelectionComplete = true
|
|
||||||
is ManageAuthorizedSpacesEvent.ToggleSpace -> {
|
is ManageAuthorizedSpacesEvent.ToggleSpace -> {
|
||||||
selectedIds = if (selectedIds.contains(event.roomId)) {
|
val currentSelectedIds = spaceSelectionState.selectedSpaceIds
|
||||||
selectedIds.minus(event.roomId).toImmutableList()
|
val newSelectedIds = if (currentSelectedIds.contains(event.roomId)) {
|
||||||
|
currentSelectedIds.minus(event.roomId).toImmutableList()
|
||||||
} else {
|
} else {
|
||||||
selectedIds.plus(event.roomId).toImmutableList()
|
currentSelectedIds.plus(event.roomId).toImmutableList()
|
||||||
}
|
}
|
||||||
|
spaceSelectionStateHolder.updateSelectedSpaceIds(newSelectedIds)
|
||||||
}
|
}
|
||||||
is ManageAuthorizedSpacesEvent.SetData -> {
|
ManageAuthorizedSpacesEvent.Done -> {
|
||||||
spacesSelection = event.data
|
spaceSelectionStateHolder.setCompletion(SpaceSelectionState.Completion.Completed)
|
||||||
selectedIds = event.data.initialSelectedIds
|
}
|
||||||
|
ManageAuthorizedSpacesEvent.Cancel -> {
|
||||||
|
spaceSelectionStateHolder.setCompletion(SpaceSelectionState.Completion.Cancelled)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return ManageAuthorizedSpacesState(
|
return ManageAuthorizedSpacesState(
|
||||||
selection = spacesSelection,
|
selectableSpaces = spaceSelectionState.selectableSpaces,
|
||||||
selectedIds = selectedIds,
|
unknownSpaceIds = spaceSelectionState.unknownSpaceIds,
|
||||||
isSelectionComplete = isSelectionComplete,
|
selectedIds = spaceSelectionState.selectedSpaceIds,
|
||||||
eventSink = ::handleEvent,
|
eventSink = ::handleEvent,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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.core.RoomId
|
||||||
import io.element.android.libraries.matrix.api.spaces.SpaceRoom
|
import io.element.android.libraries.matrix.api.spaces.SpaceRoom
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
import kotlinx.collections.immutable.ImmutableSet
|
||||||
|
|
||||||
data class ManageAuthorizedSpacesState(
|
data class ManageAuthorizedSpacesState(
|
||||||
val selection: AuthorizedSpacesSelection,
|
val selectableSpaces: ImmutableSet<SpaceRoom>,
|
||||||
|
val unknownSpaceIds: ImmutableList<RoomId>,
|
||||||
val selectedIds: ImmutableList<RoomId>,
|
val selectedIds: ImmutableList<RoomId>,
|
||||||
val isSelectionComplete: Boolean,
|
|
||||||
val eventSink: (ManageAuthorizedSpacesEvent) -> Unit
|
val eventSink: (ManageAuthorizedSpacesEvent) -> Unit
|
||||||
) {
|
) {
|
||||||
val isDoneButtonEnabled = selectedIds.isNotEmpty()
|
val isDoneButtonEnabled = selectedIds.isNotEmpty()
|
||||||
}
|
}
|
||||||
|
|
||||||
data class AuthorizedSpacesSelection(
|
|
||||||
val joinedSpaces: ImmutableList<SpaceRoom> = persistentListOf(),
|
|
||||||
val unknownSpaceIds: ImmutableList<RoomId> = persistentListOf(),
|
|
||||||
val initialSelectedIds: ImmutableList<RoomId> = persistentListOf()
|
|
||||||
)
|
|
||||||
|
|
|
||||||
|
|
@ -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.matrix.api.spaces.SpaceRoom
|
||||||
import io.element.android.libraries.previewutils.room.aSpaceRoom
|
import io.element.android.libraries.previewutils.room.aSpaceRoom
|
||||||
import kotlinx.collections.immutable.toImmutableList
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
|
import kotlinx.collections.immutable.toImmutableSet
|
||||||
|
|
||||||
open class ManageAuthorizedSpacesStateProvider : PreviewParameterProvider<ManageAuthorizedSpacesState> {
|
open class ManageAuthorizedSpacesStateProvider : PreviewParameterProvider<ManageAuthorizedSpacesState> {
|
||||||
override val values: Sequence<ManageAuthorizedSpacesState>
|
override val values: Sequence<ManageAuthorizedSpacesState>
|
||||||
get() = sequenceOf(
|
get() = sequenceOf(
|
||||||
aManageAuthorizedSpacesState(),
|
aManageAuthorizedSpacesState(),
|
||||||
aManageAuthorizedSpacesState(
|
aManageAuthorizedSpacesState(
|
||||||
authorizedSpacesSelection = anAuthorizedSpaceSelection(
|
unknownSpaceIds = listOf(aRoomId(99))
|
||||||
unknownSpaceIds = listOf(aRoomId(99))
|
|
||||||
)
|
|
||||||
),
|
),
|
||||||
aManageAuthorizedSpacesState(
|
aManageAuthorizedSpacesState(
|
||||||
selectedIds = listOf(aRoomId(1), aRoomId(3)),
|
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(
|
fun aManageAuthorizedSpacesState(
|
||||||
joinedSpaces: List<SpaceRoom> = aSpaceRoomList(5),
|
selectableSpaces: List<SpaceRoom> = aSpaceRoomList(5),
|
||||||
unknownSpaceIds: List<RoomId> = emptyList(),
|
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(),
|
selectedIds: List<RoomId> = emptyList(),
|
||||||
eventSink: (ManageAuthorizedSpacesEvent) -> Unit = {},
|
eventSink: (ManageAuthorizedSpacesEvent) -> Unit = {},
|
||||||
) = ManageAuthorizedSpacesState(
|
) = ManageAuthorizedSpacesState(
|
||||||
selection = authorizedSpacesSelection,
|
selectableSpaces = selectableSpaces.toImmutableSet(),
|
||||||
|
unknownSpaceIds = unknownSpaceIds.toImmutableList(),
|
||||||
selectedIds = selectedIds.toImmutableList(),
|
selectedIds = selectedIds.toImmutableList(),
|
||||||
isSelectionComplete = false,
|
|
||||||
eventSink = eventSink,
|
eventSink = eventSink,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@
|
||||||
|
|
||||||
package io.element.android.features.securityandprivacy.impl.manageauthorizedspaces
|
package io.element.android.features.securityandprivacy.impl.manageauthorizedspaces
|
||||||
|
|
||||||
|
import androidx.activity.compose.BackHandler
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.LazyListScope
|
import androidx.compose.foundation.lazy.LazyListScope
|
||||||
|
|
@ -42,17 +43,24 @@ import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
@Composable
|
@Composable
|
||||||
fun ManageAuthorizedSpacesView(
|
fun ManageAuthorizedSpacesView(
|
||||||
state: ManageAuthorizedSpacesState,
|
state: ManageAuthorizedSpacesState,
|
||||||
onBackClick: () -> Unit,
|
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
|
fun onCancel() {
|
||||||
|
state.eventSink(ManageAuthorizedSpacesEvent.Cancel)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onDone() {
|
||||||
|
state.eventSink(ManageAuthorizedSpacesEvent.Done)
|
||||||
|
}
|
||||||
|
|
||||||
|
BackHandler(onBack = ::onCancel)
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
topBar = {
|
topBar = {
|
||||||
ManageAuthorizedSpacesTopBar(
|
ManageAuthorizedSpacesTopBar(
|
||||||
onBackClick = onBackClick,
|
onBackClick = ::onCancel,
|
||||||
onDoneClick = {
|
onDoneClick = ::onDone,
|
||||||
state.eventSink(ManageAuthorizedSpacesEvent.Done)
|
|
||||||
},
|
|
||||||
isDoneButtonEnabled = state.isDoneButtonEnabled
|
isDoneButtonEnabled = state.isDoneButtonEnabled
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -67,7 +75,7 @@ fun ManageAuthorizedSpacesView(
|
||||||
hasDivider = false,
|
hasDivider = false,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
items(items = state.selection.joinedSpaces) { space ->
|
items(items = state.selectableSpaces.toList()) { space ->
|
||||||
CheckableSpaceListItem(
|
CheckableSpaceListItem(
|
||||||
headlineText = space.displayName,
|
headlineText = space.displayName,
|
||||||
supportingText = space.canonicalAlias?.value,
|
supportingText = space.canonicalAlias?.value,
|
||||||
|
|
@ -80,14 +88,14 @@ fun ManageAuthorizedSpacesView(
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (state.selection.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 = true,
|
hasDivider = true,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
items(items = state.selection.unknownSpaceIds) {
|
items(items = state.unknownSpaceIds) {
|
||||||
CheckableSpaceListItem(
|
CheckableSpaceListItem(
|
||||||
headlineText = stringResource(R.string.screen_manage_authorized_spaces_unknown_space),
|
headlineText = stringResource(R.string.screen_manage_authorized_spaces_unknown_space),
|
||||||
supportingText = it.value,
|
supportingText = it.value,
|
||||||
|
|
@ -185,8 +193,5 @@ private fun ManageAuthorizedSpacesTopBar(
|
||||||
internal fun ManageAuthorizedSpacesViewPreview(
|
internal fun ManageAuthorizedSpacesViewPreview(
|
||||||
@PreviewParameter(ManageAuthorizedSpacesStateProvider::class) state: ManageAuthorizedSpacesState
|
@PreviewParameter(ManageAuthorizedSpacesStateProvider::class) state: ManageAuthorizedSpacesState
|
||||||
) = ElementPreview {
|
) = ElementPreview {
|
||||||
ManageAuthorizedSpacesView(
|
ManageAuthorizedSpacesView(state = state)
|
||||||
state = state,
|
|
||||||
onBackClick = {},
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -23,12 +23,9 @@ import dev.zacsweers.metro.AssistedInject
|
||||||
import io.element.android.annotations.ContributesNode
|
import io.element.android.annotations.ContributesNode
|
||||||
import io.element.android.compound.theme.ElementTheme
|
import io.element.android.compound.theme.ElementTheme
|
||||||
import io.element.android.features.securityandprivacy.impl.SecurityAndPrivacyNavigator
|
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.androidutils.browser.openUrlInChromeCustomTab
|
||||||
import io.element.android.libraries.architecture.appyx.launchMolecule
|
import io.element.android.libraries.architecture.appyx.launchMolecule
|
||||||
import io.element.android.libraries.di.RoomScope
|
import io.element.android.libraries.di.RoomScope
|
||||||
import io.element.android.libraries.matrix.api.core.RoomId
|
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
|
||||||
|
|
||||||
@ContributesNode(RoomScope::class)
|
@ContributesNode(RoomScope::class)
|
||||||
@AssistedInject
|
@AssistedInject
|
||||||
|
|
@ -46,19 +43,6 @@ class SecurityAndPrivacyNode(
|
||||||
activity.openUrlInChromeCustomTab(null, darkTheme, url)
|
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
|
@Composable
|
||||||
override fun View(modifier: Modifier) {
|
override fun View(modifier: Modifier) {
|
||||||
val activity = requireNotNull(LocalActivity.current)
|
val activity = requireNotNull(LocalActivity.current)
|
||||||
|
|
|
||||||
|
|
@ -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.api.securityAndPrivacyPermissions
|
||||||
import io.element.android.features.securityandprivacy.impl.SecurityAndPrivacyNavigator
|
import io.element.android.features.securityandprivacy.impl.SecurityAndPrivacyNavigator
|
||||||
import io.element.android.features.securityandprivacy.impl.editroomaddress.matchesServer
|
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.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.Presenter
|
||||||
|
|
@ -51,18 +53,22 @@ import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.async
|
import kotlinx.coroutines.async
|
||||||
import kotlinx.coroutines.awaitAll
|
import kotlinx.coroutines.awaitAll
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.flow.first
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
@AssistedInject
|
@AssistedInject
|
||||||
class SecurityAndPrivacyPresenter(
|
class SecurityAndPrivacyPresenter(
|
||||||
@Assisted private val navigator: SecurityAndPrivacyNavigator,
|
@Assisted private val navigator: SecurityAndPrivacyNavigator,
|
||||||
|
private val spaceSelectionStateHolder: SpaceSelectionStateHolder,
|
||||||
private val matrixClient: MatrixClient,
|
private val matrixClient: MatrixClient,
|
||||||
private val room: JoinedRoom,
|
private val room: JoinedRoom,
|
||||||
private val featureFlagService: FeatureFlagService,
|
private val featureFlagService: FeatureFlagService,
|
||||||
) : Presenter<SecurityAndPrivacyState> {
|
) : Presenter<SecurityAndPrivacyState> {
|
||||||
@AssistedFactory
|
@AssistedFactory
|
||||||
interface Factory {
|
interface Factory {
|
||||||
fun create(navigator: SecurityAndPrivacyNavigator): SecurityAndPrivacyPresenter
|
fun create(
|
||||||
|
navigator: SecurityAndPrivacyNavigator,
|
||||||
|
): SecurityAndPrivacyPresenter
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@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) }
|
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()
|
||||||
|
|
@ -191,19 +209,27 @@ class SecurityAndPrivacyPresenter(
|
||||||
SecurityAndPrivacyEvent.DismissExitConfirmation -> {
|
SecurityAndPrivacyEvent.DismissExitConfirmation -> {
|
||||||
saveAction.value = AsyncAction.Uninitialized
|
saveAction.value = AsyncAction.Uninitialized
|
||||||
}
|
}
|
||||||
SecurityAndPrivacyEvent.ManageAuthorizedSpaces -> {
|
SecurityAndPrivacyEvent.ManageAuthorizedSpaces -> coroutineScope.launch {
|
||||||
navigator.openManageAuthorizedSpaces(
|
handleMultipleSelection(
|
||||||
|
savedAccess = savedSettings.roomAccess,
|
||||||
|
editedRoomAccess = editedRoomAccess,
|
||||||
forKnockRestricted = editedRoomAccess.value is SecurityAndPrivacyRoomAccess.AskToJoinWithSpaceMember
|
forKnockRestricted = editedRoomAccess.value is SecurityAndPrivacyRoomAccess.AskToJoinWithSpaceMember
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
SecurityAndPrivacyEvent.SelectSpaceMemberAccess -> handleSpaceMemberAccessSelection(
|
SecurityAndPrivacyEvent.SelectSpaceMemberAccess -> coroutineScope.launch {
|
||||||
spaceSelectionMode = spaceSelectionMode,
|
handleSpaceMemberAccessSelection(
|
||||||
editedAccess = editedRoomAccess,
|
spaceSelectionMode = spaceSelectionMode,
|
||||||
)
|
savedAccess = savedSettings.roomAccess,
|
||||||
SecurityAndPrivacyEvent.SelectAskToJoinWithSpaceMembersAccess -> handleAskToJoinWithSpaceMembersAccessSelection(
|
editedAccess = editedRoomAccess,
|
||||||
spaceSelectionMode = spaceSelectionMode,
|
)
|
||||||
editedAccess = editedRoomAccess,
|
}
|
||||||
)
|
SecurityAndPrivacyEvent.SelectAskToJoinWithSpaceMembersAccess -> coroutineScope.launch {
|
||||||
|
handleAskToJoinWithSpaceMembersAccessSelection(
|
||||||
|
spaceSelectionMode = spaceSelectionMode,
|
||||||
|
savedAccess = savedSettings.roomAccess,
|
||||||
|
editedAccess = editedRoomAccess,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -248,8 +274,9 @@ class SecurityAndPrivacyPresenter(
|
||||||
return state
|
return state
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleSpaceMemberAccessSelection(
|
private suspend fun handleSpaceMemberAccessSelection(
|
||||||
spaceSelectionMode: SpaceSelectionMode,
|
spaceSelectionMode: SpaceSelectionMode,
|
||||||
|
savedAccess: SecurityAndPrivacyRoomAccess,
|
||||||
editedAccess: MutableState<SecurityAndPrivacyRoomAccess>,
|
editedAccess: MutableState<SecurityAndPrivacyRoomAccess>,
|
||||||
) {
|
) {
|
||||||
if (editedAccess.value is SecurityAndPrivacyRoomAccess.SpaceMember) {
|
if (editedAccess.value is SecurityAndPrivacyRoomAccess.SpaceMember) {
|
||||||
|
|
@ -257,7 +284,11 @@ class SecurityAndPrivacyPresenter(
|
||||||
}
|
}
|
||||||
when (spaceSelectionMode) {
|
when (spaceSelectionMode) {
|
||||||
is SpaceSelectionMode.None -> Unit
|
is SpaceSelectionMode.None -> Unit
|
||||||
is SpaceSelectionMode.Multiple -> navigator.openManageAuthorizedSpaces(forKnockRestricted = false)
|
is SpaceSelectionMode.Multiple -> handleMultipleSelection(
|
||||||
|
savedAccess = savedAccess,
|
||||||
|
editedRoomAccess = editedAccess,
|
||||||
|
forKnockRestricted = false,
|
||||||
|
)
|
||||||
is SpaceSelectionMode.Single -> {
|
is SpaceSelectionMode.Single -> {
|
||||||
val newRoomAccess = SecurityAndPrivacyRoomAccess.SpaceMember(
|
val newRoomAccess = SecurityAndPrivacyRoomAccess.SpaceMember(
|
||||||
spaceIds = persistentListOf(spaceSelectionMode.spaceId)
|
spaceIds = persistentListOf(spaceSelectionMode.spaceId)
|
||||||
|
|
@ -267,8 +298,9 @@ class SecurityAndPrivacyPresenter(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleAskToJoinWithSpaceMembersAccessSelection(
|
private suspend fun handleAskToJoinWithSpaceMembersAccessSelection(
|
||||||
spaceSelectionMode: SpaceSelectionMode,
|
spaceSelectionMode: SpaceSelectionMode,
|
||||||
|
savedAccess: SecurityAndPrivacyRoomAccess,
|
||||||
editedAccess: MutableState<SecurityAndPrivacyRoomAccess>,
|
editedAccess: MutableState<SecurityAndPrivacyRoomAccess>,
|
||||||
) {
|
) {
|
||||||
if (editedAccess.value is SecurityAndPrivacyRoomAccess.AskToJoinWithSpaceMember) {
|
if (editedAccess.value is SecurityAndPrivacyRoomAccess.AskToJoinWithSpaceMember) {
|
||||||
|
|
@ -276,7 +308,11 @@ class SecurityAndPrivacyPresenter(
|
||||||
}
|
}
|
||||||
when (spaceSelectionMode) {
|
when (spaceSelectionMode) {
|
||||||
is SpaceSelectionMode.None -> Unit
|
is SpaceSelectionMode.None -> Unit
|
||||||
is SpaceSelectionMode.Multiple -> navigator.openManageAuthorizedSpaces(forKnockRestricted = true)
|
is SpaceSelectionMode.Multiple -> handleMultipleSelection(
|
||||||
|
savedAccess = savedAccess,
|
||||||
|
editedRoomAccess = editedAccess,
|
||||||
|
forKnockRestricted = true,
|
||||||
|
)
|
||||||
is SpaceSelectionMode.Single -> {
|
is SpaceSelectionMode.Single -> {
|
||||||
val newRoomAccess = SecurityAndPrivacyRoomAccess.AskToJoinWithSpaceMember(
|
val newRoomAccess = SecurityAndPrivacyRoomAccess.AskToJoinWithSpaceMember(
|
||||||
spaceIds = persistentListOf(spaceSelectionMode.spaceId)
|
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(
|
private fun getSpaceSelectionMode(
|
||||||
selectableJoinedSpaces: Set<SpaceRoom>,
|
selectableJoinedSpaces: Set<SpaceRoom>,
|
||||||
savedAccess: SecurityAndPrivacyRoomAccess,
|
savedAccess: SecurityAndPrivacyRoomAccess,
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,6 @@ import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import io.element.android.features.securityandprivacy.api.SecurityAndPrivacyPermissions
|
import io.element.android.features.securityandprivacy.api.SecurityAndPrivacyPermissions
|
||||||
import io.element.android.features.securityandprivacy.impl.R
|
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.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.core.RoomId
|
||||||
|
|
@ -121,20 +120,6 @@ data class SecurityAndPrivacyState(
|
||||||
stringResource(R.string.screen_security_and_privacy_ask_to_join_option_description)
|
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(
|
data class SecurityAndPrivacySettings(
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ class FakeSecurityAndPrivacyNavigator(
|
||||||
private val onDoneLambda: () -> Unit = { lambdaError() },
|
private val onDoneLambda: () -> Unit = { lambdaError() },
|
||||||
private val openEditRoomAddressLambda: () -> Unit = { lambdaError() },
|
private val openEditRoomAddressLambda: () -> Unit = { lambdaError() },
|
||||||
private val closeEditRoomAddressLambda: () -> Unit = { lambdaError() },
|
private val closeEditRoomAddressLambda: () -> Unit = { lambdaError() },
|
||||||
private val openManageAuthorizedSpacesLambda: (Boolean) -> Unit = { lambdaError() },
|
private val openManageAuthorizedSpacesLambda: () -> Unit = { lambdaError() },
|
||||||
private val closeManageAuthorizedSpacesLambda: () -> Unit = { lambdaError() },
|
private val closeManageAuthorizedSpacesLambda: () -> Unit = { lambdaError() },
|
||||||
) : SecurityAndPrivacyNavigator {
|
) : SecurityAndPrivacyNavigator {
|
||||||
override fun onDone() {
|
override fun onDone() {
|
||||||
|
|
@ -29,8 +29,8 @@ class FakeSecurityAndPrivacyNavigator(
|
||||||
closeEditRoomAddressLambda()
|
closeEditRoomAddressLambda()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun openManageAuthorizedSpaces(forKnockRestricted: Boolean) {
|
override fun openManageAuthorizedSpaces() {
|
||||||
openManageAuthorizedSpacesLambda(forKnockRestricted)
|
openManageAuthorizedSpacesLambda()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun closeManageAuthorizedSpaces() {
|
override fun closeManageAuthorizedSpaces() {
|
||||||
|
|
|
||||||
|
|
@ -49,27 +49,16 @@ class SecurityAndPrivacyFlowNodeTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `openManageAuthorizedSpaces navigates with forKnockRestricted false`() = runTest {
|
fun `openManageAuthorizedSpaces navigates to ManageAuthorizedSpaces`() = runTest {
|
||||||
val flowNode = createFlowNode()
|
val flowNode = createFlowNode()
|
||||||
flowNode.navigator.openManageAuthorizedSpaces(forKnockRestricted = false)
|
flowNode.navigator.openManageAuthorizedSpaces()
|
||||||
assertThat(flowNode.currentNavTarget()).isEqualTo(
|
assertThat(flowNode.currentNavTarget()).isEqualTo(SecurityAndPrivacyFlowNode.NavTarget.ManageAuthorizedSpaces)
|
||||||
SecurityAndPrivacyFlowNode.NavTarget.ManageAuthorizedSpaces(forKnockRestricted = false)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `openManageAuthorizedSpaces navigates with forKnockRestricted true`() = runTest {
|
|
||||||
val flowNode = createFlowNode()
|
|
||||||
flowNode.navigator.openManageAuthorizedSpaces(forKnockRestricted = true)
|
|
||||||
assertThat(flowNode.currentNavTarget()).isEqualTo(
|
|
||||||
SecurityAndPrivacyFlowNode.NavTarget.ManageAuthorizedSpaces(forKnockRestricted = true)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `closeManageAuthorizedSpaces pops backstack`() = runTest {
|
fun `closeManageAuthorizedSpaces pops backstack`() = runTest {
|
||||||
val flowNode = createFlowNode()
|
val flowNode = createFlowNode()
|
||||||
flowNode.navigator.openManageAuthorizedSpaces(forKnockRestricted = false)
|
flowNode.navigator.openManageAuthorizedSpaces()
|
||||||
assertThat(flowNode.currentNavTarget())
|
assertThat(flowNode.currentNavTarget())
|
||||||
.isInstanceOf(SecurityAndPrivacyFlowNode.NavTarget.ManageAuthorizedSpaces::class.java)
|
.isInstanceOf(SecurityAndPrivacyFlowNode.NavTarget.ManageAuthorizedSpaces::class.java)
|
||||||
flowNode.navigator.closeManageAuthorizedSpaces()
|
flowNode.navigator.closeManageAuthorizedSpaces()
|
||||||
|
|
|
||||||
|
|
@ -12,38 +12,33 @@ import com.google.common.truth.Truth.assertThat
|
||||||
import io.element.android.libraries.matrix.test.A_ROOM_ID
|
import io.element.android.libraries.matrix.test.A_ROOM_ID
|
||||||
import io.element.android.tests.testutils.test
|
import io.element.android.tests.testutils.test
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
|
import kotlinx.collections.immutable.persistentSetOf
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
||||||
class ManageAuthorizedSpacesPresenterTest {
|
class ManageAuthorizedSpacesPresenterTest {
|
||||||
@Test
|
@Test
|
||||||
fun `present - initial state has empty selection`() = runTest {
|
fun `present - initial state reflects shared state`() = runTest {
|
||||||
val presenter = ManageAuthorizedSpacesPresenter()
|
val sharedStateHolder = SpaceSelectionStateHolder()
|
||||||
|
val presenter = ManageAuthorizedSpacesPresenter(sharedStateHolder)
|
||||||
presenter.test {
|
presenter.test {
|
||||||
with(awaitItem()) {
|
with(awaitItem()) {
|
||||||
assertThat(selectedIds).isEmpty()
|
assertThat(selectedIds).isEmpty()
|
||||||
assertThat(isSelectionComplete).isFalse()
|
|
||||||
assertThat(isDoneButtonEnabled).isFalse()
|
assertThat(isDoneButtonEnabled).isFalse()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - SetData event updates selection and initial selectedIds`() = runTest {
|
fun `present - state reflects shared state with pre-selected spaces`() = runTest {
|
||||||
val presenter = ManageAuthorizedSpacesPresenter()
|
val sharedStateHolder = SpaceSelectionStateHolder()
|
||||||
|
val roomId = A_ROOM_ID
|
||||||
|
sharedStateHolder.update {
|
||||||
|
it.copy(selectedSpaceIds = persistentListOf(roomId))
|
||||||
|
}
|
||||||
|
val presenter = ManageAuthorizedSpacesPresenter(sharedStateHolder)
|
||||||
presenter.test {
|
presenter.test {
|
||||||
val initialState = awaitItem()
|
|
||||||
val roomId = A_ROOM_ID
|
|
||||||
val data = AuthorizedSpacesSelection(
|
|
||||||
joinedSpaces = persistentListOf(),
|
|
||||||
unknownSpaceIds = persistentListOf(),
|
|
||||||
initialSelectedIds = persistentListOf(roomId)
|
|
||||||
)
|
|
||||||
initialState.eventSink(ManageAuthorizedSpacesEvent.SetData(data))
|
|
||||||
// SetData updates two state variables, which may emit intermediate states
|
|
||||||
skipItems(1)
|
|
||||||
with(awaitItem()) {
|
with(awaitItem()) {
|
||||||
assertThat(selection).isEqualTo(data)
|
|
||||||
assertThat(selectedIds).containsExactly(roomId)
|
assertThat(selectedIds).containsExactly(roomId)
|
||||||
assertThat(isDoneButtonEnabled).isTrue()
|
assertThat(isDoneButtonEnabled).isTrue()
|
||||||
}
|
}
|
||||||
|
|
@ -51,8 +46,9 @@ class ManageAuthorizedSpacesPresenterTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - ToggleSpace event adds space to selectedIds`() = runTest {
|
fun `present - ToggleSpace event adds space to selectedIds in shared state`() = runTest {
|
||||||
val presenter = ManageAuthorizedSpacesPresenter()
|
val sharedStateHolder = SpaceSelectionStateHolder()
|
||||||
|
val presenter = ManageAuthorizedSpacesPresenter(sharedStateHolder)
|
||||||
presenter.test {
|
presenter.test {
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
val roomId = A_ROOM_ID
|
val roomId = A_ROOM_ID
|
||||||
|
|
@ -61,34 +57,69 @@ class ManageAuthorizedSpacesPresenterTest {
|
||||||
assertThat(selectedIds).containsExactly(roomId)
|
assertThat(selectedIds).containsExactly(roomId)
|
||||||
assertThat(isDoneButtonEnabled).isTrue()
|
assertThat(isDoneButtonEnabled).isTrue()
|
||||||
}
|
}
|
||||||
|
// Verify the shared state is also updated
|
||||||
|
assertThat(sharedStateHolder.state.value.selectedSpaceIds).containsExactly(roomId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - ToggleSpace event removes space when already selected`() = runTest {
|
fun `present - ToggleSpace event removes space when already selected`() = runTest {
|
||||||
val presenter = ManageAuthorizedSpacesPresenter()
|
val sharedStateHolder = SpaceSelectionStateHolder()
|
||||||
|
sharedStateHolder.updateSelectedSpaceIds(persistentListOf(A_ROOM_ID))
|
||||||
|
val presenter = ManageAuthorizedSpacesPresenter(sharedStateHolder)
|
||||||
presenter.test {
|
presenter.test {
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
val roomId = A_ROOM_ID
|
assertThat(initialState.selectedIds).containsExactly(A_ROOM_ID)
|
||||||
initialState.eventSink(ManageAuthorizedSpacesEvent.ToggleSpace(roomId))
|
initialState.eventSink(ManageAuthorizedSpacesEvent.ToggleSpace(A_ROOM_ID))
|
||||||
val stateWithSelection = awaitItem()
|
|
||||||
assertThat(stateWithSelection.selectedIds).containsExactly(roomId)
|
|
||||||
stateWithSelection.eventSink(ManageAuthorizedSpacesEvent.ToggleSpace(roomId))
|
|
||||||
with(awaitItem()) {
|
with(awaitItem()) {
|
||||||
assertThat(selectedIds).isEmpty()
|
assertThat(selectedIds).isEmpty()
|
||||||
assertThat(isDoneButtonEnabled).isFalse()
|
assertThat(isDoneButtonEnabled).isFalse()
|
||||||
}
|
}
|
||||||
|
// Verify the shared state is also updated
|
||||||
|
assertThat(sharedStateHolder.state.value.selectedSpaceIds).isEmpty()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - Done event sets isSelectionComplete to true`() = runTest {
|
fun `present - Done event sets completion to Completed`() = runTest {
|
||||||
val presenter = ManageAuthorizedSpacesPresenter()
|
val sharedStateHolder = SpaceSelectionStateHolder()
|
||||||
|
val presenter = ManageAuthorizedSpacesPresenter(sharedStateHolder)
|
||||||
presenter.test {
|
presenter.test {
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
initialState.eventSink(ManageAuthorizedSpacesEvent.Done)
|
initialState.eventSink(ManageAuthorizedSpacesEvent.Done)
|
||||||
|
cancelAndIgnoreRemainingEvents()
|
||||||
|
assertThat(sharedStateHolder.state.value.completion)
|
||||||
|
.isEqualTo(SpaceSelectionState.Completion.Completed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `present - Cancel event sets completion to Cancelled`() = runTest {
|
||||||
|
val sharedStateHolder = SpaceSelectionStateHolder()
|
||||||
|
val presenter = ManageAuthorizedSpacesPresenter(sharedStateHolder)
|
||||||
|
presenter.test {
|
||||||
|
val initialState = awaitItem()
|
||||||
|
initialState.eventSink(ManageAuthorizedSpacesEvent.Cancel)
|
||||||
|
cancelAndIgnoreRemainingEvents()
|
||||||
|
assertThat(sharedStateHolder.state.value.completion)
|
||||||
|
.isEqualTo(SpaceSelectionState.Completion.Cancelled)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `present - displays spaces from shared state`() = runTest {
|
||||||
|
val sharedStateHolder = SpaceSelectionStateHolder()
|
||||||
|
sharedStateHolder.update {
|
||||||
|
it.copy(
|
||||||
|
selectableSpaces = persistentSetOf(),
|
||||||
|
unknownSpaceIds = persistentListOf(A_ROOM_ID),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
val presenter = ManageAuthorizedSpacesPresenter(sharedStateHolder)
|
||||||
|
presenter.test {
|
||||||
with(awaitItem()) {
|
with(awaitItem()) {
|
||||||
assertThat(isSelectionComplete).isTrue()
|
assertThat(selectableSpaces).isEmpty()
|
||||||
|
assertThat(unknownSpaceIds).containsExactly(A_ROOM_ID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,15 +15,15 @@ import androidx.compose.ui.test.onNodeWithText
|
||||||
import androidx.compose.ui.test.performClick
|
import androidx.compose.ui.test.performClick
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
import io.element.android.libraries.matrix.api.core.RoomId
|
import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
|
import io.element.android.libraries.matrix.api.spaces.SpaceRoom
|
||||||
import io.element.android.libraries.matrix.test.A_ROOM_ID
|
import io.element.android.libraries.matrix.test.A_ROOM_ID
|
||||||
import io.element.android.libraries.previewutils.room.aSpaceRoom
|
import io.element.android.libraries.previewutils.room.aSpaceRoom
|
||||||
import io.element.android.libraries.ui.strings.CommonStrings
|
import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
import io.element.android.tests.testutils.EnsureNeverCalled
|
|
||||||
import io.element.android.tests.testutils.EventsRecorder
|
import io.element.android.tests.testutils.EventsRecorder
|
||||||
import io.element.android.tests.testutils.clickOn
|
import io.element.android.tests.testutils.clickOn
|
||||||
import io.element.android.tests.testutils.ensureCalledOnce
|
|
||||||
import io.element.android.tests.testutils.pressBack
|
import io.element.android.tests.testutils.pressBack
|
||||||
import kotlinx.collections.immutable.toImmutableList
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
|
import kotlinx.collections.immutable.toImmutableSet
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.rules.TestRule
|
import org.junit.rules.TestRule
|
||||||
|
|
@ -34,11 +34,12 @@ class ManageAuthorizedSpacesViewTest {
|
||||||
@get:Rule val rule = createAndroidComposeRule<ComponentActivity>()
|
@get:Rule val rule = createAndroidComposeRule<ComponentActivity>()
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `clicking back invokes callback`() {
|
fun `clicking back emits Cancel event`() {
|
||||||
ensureCalledOnce { callback ->
|
val recorder = EventsRecorder<ManageAuthorizedSpacesEvent>()
|
||||||
rule.setManageAuthorizedSpacesView(onBackClick = callback)
|
val state = aManageAuthorizedSpacesState(eventSink = recorder)
|
||||||
rule.pressBack()
|
rule.setManageAuthorizedSpacesView(state)
|
||||||
}
|
rule.pressBack()
|
||||||
|
recorder.assertSingle(ManageAuthorizedSpacesEvent.Cancel)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
@ -47,9 +48,7 @@ class ManageAuthorizedSpacesViewTest {
|
||||||
val space = aSpaceRoom(roomId = roomId, displayName = "Test Space")
|
val space = aSpaceRoom(roomId = roomId, displayName = "Test Space")
|
||||||
val recorder = EventsRecorder<ManageAuthorizedSpacesEvent>()
|
val recorder = EventsRecorder<ManageAuthorizedSpacesEvent>()
|
||||||
val state = aManageAuthorizedSpacesState(
|
val state = aManageAuthorizedSpacesState(
|
||||||
selection = anAuthorizedSpaceSelection(
|
selectableSpaces = listOf(space),
|
||||||
joinedSpaces = listOf(space)
|
|
||||||
),
|
|
||||||
eventSink = recorder
|
eventSink = recorder
|
||||||
)
|
)
|
||||||
rule.setManageAuthorizedSpacesView(state)
|
rule.setManageAuthorizedSpacesView(state)
|
||||||
|
|
@ -86,24 +85,20 @@ private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setManag
|
||||||
state: ManageAuthorizedSpacesState = aManageAuthorizedSpacesState(
|
state: ManageAuthorizedSpacesState = aManageAuthorizedSpacesState(
|
||||||
eventSink = EventsRecorder(expectEvents = false)
|
eventSink = EventsRecorder(expectEvents = false)
|
||||||
),
|
),
|
||||||
onBackClick: () -> Unit = EnsureNeverCalled(),
|
|
||||||
) {
|
) {
|
||||||
setContent {
|
setContent {
|
||||||
ManageAuthorizedSpacesView(
|
ManageAuthorizedSpacesView(state = state)
|
||||||
state = state,
|
|
||||||
onBackClick = onBackClick,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun aManageAuthorizedSpacesState(
|
private fun aManageAuthorizedSpacesState(
|
||||||
selection: AuthorizedSpacesSelection = AuthorizedSpacesSelection(),
|
selectableSpaces: List<SpaceRoom> = emptyList(),
|
||||||
|
unknownSpaceIds: List<RoomId> = emptyList(),
|
||||||
selectedIds: List<RoomId> = emptyList(),
|
selectedIds: List<RoomId> = emptyList(),
|
||||||
isSelectionComplete: Boolean = false,
|
|
||||||
eventSink: (ManageAuthorizedSpacesEvent) -> Unit = {},
|
eventSink: (ManageAuthorizedSpacesEvent) -> Unit = {},
|
||||||
) = ManageAuthorizedSpacesState(
|
) = ManageAuthorizedSpacesState(
|
||||||
selection = selection,
|
selectableSpaces = selectableSpaces.toImmutableSet(),
|
||||||
|
unknownSpaceIds = unknownSpaceIds.toImmutableList(),
|
||||||
selectedIds = selectedIds.toImmutableList(),
|
selectedIds = selectedIds.toImmutableList(),
|
||||||
isSelectionComplete = isSelectionComplete,
|
|
||||||
eventSink = eventSink,
|
eventSink = eventSink,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ package io.element.android.features.securityandprivacy.impl.root
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
import io.element.android.features.securityandprivacy.impl.FakeSecurityAndPrivacyNavigator
|
import io.element.android.features.securityandprivacy.impl.FakeSecurityAndPrivacyNavigator
|
||||||
import io.element.android.features.securityandprivacy.impl.SecurityAndPrivacyNavigator
|
import io.element.android.features.securityandprivacy.impl.SecurityAndPrivacyNavigator
|
||||||
|
import io.element.android.features.securityandprivacy.impl.manageauthorizedspaces.SpaceSelectionStateHolder
|
||||||
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.featureflag.api.FeatureFlagService
|
import io.element.android.libraries.featureflag.api.FeatureFlagService
|
||||||
|
|
@ -436,7 +437,7 @@ class SecurityAndPrivacyPresenterTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - SelectSpaceMemberAccess with multiple spaces opens ManageAuthorizedSpaces`() = runTest {
|
fun `present - SelectSpaceMemberAccess with multiple spaces opens ManageAuthorizedSpaces`() = runTest {
|
||||||
val openManageAuthorizedSpacesLambda = lambdaRecorder<Boolean, Unit> { }
|
val openManageAuthorizedSpacesLambda = lambdaRecorder<Unit> { }
|
||||||
val navigator =
|
val navigator =
|
||||||
FakeSecurityAndPrivacyNavigator(openManageAuthorizedSpacesLambda = openManageAuthorizedSpacesLambda)
|
FakeSecurityAndPrivacyNavigator(openManageAuthorizedSpacesLambda = openManageAuthorizedSpacesLambda)
|
||||||
val room = FakeJoinedRoom(
|
val room = FakeJoinedRoom(
|
||||||
|
|
@ -471,7 +472,7 @@ class SecurityAndPrivacyPresenterTest {
|
||||||
val state = awaitItem()
|
val state = awaitItem()
|
||||||
assertThat(state.isSpaceMemberSelectable).isTrue()
|
assertThat(state.isSpaceMemberSelectable).isTrue()
|
||||||
state.eventSink(SecurityAndPrivacyEvent.SelectSpaceMemberAccess)
|
state.eventSink(SecurityAndPrivacyEvent.SelectSpaceMemberAccess)
|
||||||
assert(openManageAuthorizedSpacesLambda).isCalledOnce().with(value(false))
|
assert(openManageAuthorizedSpacesLambda).isCalledOnce()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -605,7 +606,7 @@ class SecurityAndPrivacyPresenterTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - SelectAskToJoinWithSpaceMembersAccess with multiple spaces opens ManageAuthorizedSpaces`() = runTest {
|
fun `present - SelectAskToJoinWithSpaceMembersAccess with multiple spaces opens ManageAuthorizedSpaces`() = runTest {
|
||||||
val openManageAuthorizedSpacesLambda = lambdaRecorder<Boolean, Unit> { }
|
val openManageAuthorizedSpacesLambda = lambdaRecorder<Unit> { }
|
||||||
val navigator =
|
val navigator =
|
||||||
FakeSecurityAndPrivacyNavigator(openManageAuthorizedSpacesLambda = openManageAuthorizedSpacesLambda)
|
FakeSecurityAndPrivacyNavigator(openManageAuthorizedSpacesLambda = openManageAuthorizedSpacesLambda)
|
||||||
val room = FakeJoinedRoom(
|
val room = FakeJoinedRoom(
|
||||||
|
|
@ -642,7 +643,7 @@ class SecurityAndPrivacyPresenterTest {
|
||||||
val state = awaitItem()
|
val state = awaitItem()
|
||||||
assertThat(state.isAskToJoinWithSpaceMembersSelectable).isTrue()
|
assertThat(state.isAskToJoinWithSpaceMembersSelectable).isTrue()
|
||||||
state.eventSink(SecurityAndPrivacyEvent.SelectAskToJoinWithSpaceMembersAccess)
|
state.eventSink(SecurityAndPrivacyEvent.SelectAskToJoinWithSpaceMembersAccess)
|
||||||
assert(openManageAuthorizedSpacesLambda).isCalledOnce().with(value(true))
|
assert(openManageAuthorizedSpacesLambda).isCalledOnce()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -950,73 +951,6 @@ class SecurityAndPrivacyPresenterTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `present - getAuthorizedSpacesSelection returns correct data for SpaceMember`() = runTest {
|
|
||||||
val room = FakeJoinedRoom(
|
|
||||||
baseRoom = FakeBaseRoom(
|
|
||||||
roomPermissions = roomPermissions(),
|
|
||||||
initialRoomInfo = aRoomInfo(
|
|
||||||
historyVisibility = RoomHistoryVisibility.Shared,
|
|
||||||
joinRule = JoinRule.Restricted(
|
|
||||||
rules = persistentListOf(AllowRule.RoomMembership(A_ROOM_ID))
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
val spaceRoom = aSpaceRoom(roomId = A_ROOM_ID)
|
|
||||||
val client = FakeMatrixClient(
|
|
||||||
userIdServerNameLambda = { "matrix.org" },
|
|
||||||
spaceService = FakeSpaceService(
|
|
||||||
joinedParentsResult = { _ -> Result.success(listOf(spaceRoom)) },
|
|
||||||
getSpaceRoomResult = { null }
|
|
||||||
)
|
|
||||||
)
|
|
||||||
val presenter = createSecurityAndPrivacyPresenter(
|
|
||||||
room = room,
|
|
||||||
matrixClient = client,
|
|
||||||
featureFlagService = FakeFeatureFlagService(
|
|
||||||
initialState = mapOf(FeatureFlags.SpaceSettings.key to true)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
presenter.test {
|
|
||||||
skipItems(1)
|
|
||||||
with(awaitItem()) {
|
|
||||||
val selection = getAuthorizedSpacesSelection()
|
|
||||||
assertThat(selection.joinedSpaces).containsExactly(spaceRoom)
|
|
||||||
assertThat(selection.initialSelectedIds).containsExactly(A_ROOM_ID)
|
|
||||||
assertThat(selection.unknownSpaceIds).isEmpty()
|
|
||||||
}
|
|
||||||
cancelAndIgnoreRemainingEvents()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `present - getAuthorizedSpacesSelection identifies unknown space IDs`() = runTest {
|
|
||||||
val unknownSpaceId = RoomId("!unknown:matrix.org")
|
|
||||||
val room = FakeJoinedRoom(
|
|
||||||
baseRoom = FakeBaseRoom(
|
|
||||||
roomPermissions = roomPermissions(),
|
|
||||||
initialRoomInfo = aRoomInfo(
|
|
||||||
historyVisibility = RoomHistoryVisibility.Shared,
|
|
||||||
joinRule = JoinRule.Restricted(
|
|
||||||
rules = persistentListOf(AllowRule.RoomMembership(unknownSpaceId))
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
// No spaces available (the space in the join rule is unknown)
|
|
||||||
val presenter = createSecurityAndPrivacyPresenter(room = room)
|
|
||||||
presenter.test {
|
|
||||||
skipItems(1)
|
|
||||||
with(awaitItem()) {
|
|
||||||
val selection = getAuthorizedSpacesSelection()
|
|
||||||
assertThat(selection.joinedSpaces).isEmpty()
|
|
||||||
assertThat(selection.unknownSpaceIds).containsExactly(unknownSpaceId)
|
|
||||||
}
|
|
||||||
cancelAndIgnoreRemainingEvents()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - SelectAskToJoinWithSpaceMembersAccess with single space auto-selects`() = runTest {
|
fun `present - SelectAskToJoinWithSpaceMembersAccess with single space auto-selects`() = runTest {
|
||||||
val room = FakeJoinedRoom(
|
val room = FakeJoinedRoom(
|
||||||
|
|
@ -1169,12 +1103,14 @@ class SecurityAndPrivacyPresenterTest {
|
||||||
getSpaceRoomResult = { null }
|
getSpaceRoomResult = { null }
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
spaceSelectionStateHolder: SpaceSelectionStateHolder = SpaceSelectionStateHolder(),
|
||||||
): SecurityAndPrivacyPresenter {
|
): SecurityAndPrivacyPresenter {
|
||||||
return SecurityAndPrivacyPresenter(
|
return SecurityAndPrivacyPresenter(
|
||||||
room = room,
|
room = room,
|
||||||
matrixClient = matrixClient,
|
matrixClient = matrixClient,
|
||||||
navigator = navigator,
|
navigator = navigator,
|
||||||
featureFlagService = featureFlagService,
|
featureFlagService = featureFlagService,
|
||||||
|
spaceSelectionStateHolder = spaceSelectionStateHolder,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue