feature(security&privacy): working SpaceMember selection
This commit is contained in:
parent
1930877a81
commit
75ab791629
9 changed files with 37 additions and 54 deletions
|
|
@ -66,7 +66,7 @@ class SecurityAndPrivacyFlowNode(
|
||||||
data object EditRoomAddress : NavTarget
|
data object EditRoomAddress : NavTarget
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
data class ManageAuthorizedSpaces(val initialSelection: List<RoomId>) : NavTarget
|
data object ManageAuthorizedSpaces: NavTarget
|
||||||
}
|
}
|
||||||
|
|
||||||
private val callback: SecurityAndPrivacyEntryPoint.Callback = callback()
|
private val callback: SecurityAndPrivacyEntryPoint.Callback = callback()
|
||||||
|
|
@ -95,7 +95,7 @@ class SecurityAndPrivacyFlowNode(
|
||||||
val authorizedSpacesData = securityAndPrivacyNode.getAuthorizedSpacesData()
|
val authorizedSpacesData = securityAndPrivacyNode.getAuthorizedSpacesData()
|
||||||
val selectedSpaces = manageAuthorizedSpacesNode.waitForCompletion(authorizedSpacesData)
|
val selectedSpaces = manageAuthorizedSpacesNode.waitForCompletion(authorizedSpacesData)
|
||||||
withContext(NonCancellable) {
|
withContext(NonCancellable) {
|
||||||
backstack.pop()
|
navigator.closeManageAuthorizedSpaces()
|
||||||
securityAndPrivacyNode.onAuthorizedSpacesSelected(selectedSpaces)
|
securityAndPrivacyNode.onAuthorizedSpacesSelected(selectedSpaces)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -110,7 +110,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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,13 +13,12 @@ import com.bumble.appyx.navmodel.backstack.BackStack
|
||||||
import com.bumble.appyx.navmodel.backstack.operation.pop
|
import com.bumble.appyx.navmodel.backstack.operation.pop
|
||||||
import com.bumble.appyx.navmodel.backstack.operation.push
|
import com.bumble.appyx.navmodel.backstack.operation.push
|
||||||
import io.element.android.features.securityandprivacy.api.SecurityAndPrivacyEntryPoint
|
import io.element.android.features.securityandprivacy.api.SecurityAndPrivacyEntryPoint
|
||||||
import io.element.android.libraries.matrix.api.core.RoomId
|
|
||||||
|
|
||||||
interface SecurityAndPrivacyNavigator : Plugin {
|
interface SecurityAndPrivacyNavigator : Plugin {
|
||||||
fun onDone()
|
fun onDone()
|
||||||
fun openEditRoomAddress()
|
fun openEditRoomAddress()
|
||||||
fun closeEditRoomAddress()
|
fun closeEditRoomAddress()
|
||||||
fun openManageAuthorizedSpaces(initialSelection: List<RoomId>)
|
fun openManageAuthorizedSpaces()
|
||||||
fun closeManageAuthorizedSpaces()
|
fun closeManageAuthorizedSpaces()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -39,8 +38,8 @@ class BackstackSecurityAndPrivacyNavigator(
|
||||||
backStack.pop()
|
backStack.pop()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun openManageAuthorizedSpaces(initialSelection: List<RoomId>) {
|
override fun openManageAuthorizedSpaces() {
|
||||||
backStack.push(SecurityAndPrivacyFlowNode.NavTarget.ManageAuthorizedSpaces(initialSelection))
|
backStack.push(SecurityAndPrivacyFlowNode.NavTarget.ManageAuthorizedSpaces)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun closeManageAuthorizedSpaces() {
|
override fun closeManageAuthorizedSpaces() {
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,6 @@ 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.features.securityandprivacy.impl.SecurityAndPrivacyNavigator
|
||||||
import io.element.android.libraries.architecture.NodeInputs
|
|
||||||
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 io.element.android.libraries.matrix.api.core.RoomId
|
||||||
|
|
@ -32,16 +31,10 @@ import kotlinx.coroutines.flow.first
|
||||||
class ManageAuthorizedSpacesNode(
|
class ManageAuthorizedSpacesNode(
|
||||||
@Assisted buildContext: BuildContext,
|
@Assisted buildContext: BuildContext,
|
||||||
@Assisted plugins: List<Plugin>,
|
@Assisted plugins: List<Plugin>,
|
||||||
presenterFactory: ManageAuthorizedSpacesPresenter.Factory,
|
presenter: ManageAuthorizedSpacesPresenter,
|
||||||
) : Node(buildContext, plugins = plugins) {
|
) : Node(buildContext, plugins = plugins) {
|
||||||
|
|
||||||
data class Params(
|
|
||||||
val initialSelection: List<RoomId>
|
|
||||||
) : NodeInputs
|
|
||||||
|
|
||||||
private val navigator = plugins<SecurityAndPrivacyNavigator>().first()
|
private val navigator = plugins<SecurityAndPrivacyNavigator>().first()
|
||||||
private val presenter = presenterFactory.create(navigator)
|
|
||||||
|
|
||||||
private val stateFlow = launchMolecule { presenter.present() }
|
private val stateFlow = launchMolecule { presenter.present() }
|
||||||
|
|
||||||
suspend fun waitForCompletion(data: AuthorizedSpacesSelection): ImmutableList<RoomId> {
|
suspend fun waitForCompletion(data: AuthorizedSpacesSelection): ImmutableList<RoomId> {
|
||||||
|
|
@ -54,7 +47,7 @@ class ManageAuthorizedSpacesNode(
|
||||||
val state by stateFlow.collectAsState()
|
val state by stateFlow.collectAsState()
|
||||||
ManageAuthorizedSpacesView(
|
ManageAuthorizedSpacesView(
|
||||||
state = state,
|
state = state,
|
||||||
onBackClick = ::navigateUp,
|
onBackClick = { navigator.closeManageAuthorizedSpaces() },
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,57 +13,42 @@ import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import dev.zacsweers.metro.Assisted
|
import dev.zacsweers.metro.Inject
|
||||||
import dev.zacsweers.metro.AssistedFactory
|
|
||||||
import dev.zacsweers.metro.AssistedInject
|
|
||||||
import io.element.android.features.securityandprivacy.impl.SecurityAndPrivacyNavigator
|
|
||||||
import io.element.android.libraries.architecture.Presenter
|
import io.element.android.libraries.architecture.Presenter
|
||||||
import io.element.android.libraries.matrix.api.MatrixClient
|
|
||||||
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.room.JoinedRoom
|
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
import kotlinx.collections.immutable.toPersistentList
|
import kotlinx.collections.immutable.toPersistentList
|
||||||
|
|
||||||
@AssistedInject
|
@Inject
|
||||||
class ManageAuthorizedSpacesPresenter(
|
class ManageAuthorizedSpacesPresenter() : Presenter<ManageAuthorizedSpacesState> {
|
||||||
@Assisted private val navigator: SecurityAndPrivacyNavigator,
|
|
||||||
private val client: MatrixClient,
|
|
||||||
private val room: JoinedRoom,
|
|
||||||
) : Presenter<ManageAuthorizedSpacesState> {
|
|
||||||
@AssistedFactory
|
|
||||||
interface Factory {
|
|
||||||
fun create(navigator: SecurityAndPrivacyNavigator): ManageAuthorizedSpacesPresenter
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun present(): ManageAuthorizedSpacesState {
|
override fun present(): ManageAuthorizedSpacesState {
|
||||||
var currentSelection: ImmutableList<RoomId> by remember { mutableStateOf(persistentListOf()) }
|
var selectedIds: ImmutableList<RoomId> by remember { mutableStateOf(persistentListOf()) }
|
||||||
var spacesData by remember { mutableStateOf(AuthorizedSpacesSelection()) }
|
var spacesSelection by remember { mutableStateOf(AuthorizedSpacesSelection()) }
|
||||||
var isSelectionComplete by remember { mutableStateOf(false) }
|
var isSelectionComplete by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
fun handleEvent(event: ManageAuthorizedSpacesEvent) {
|
fun handleEvent(event: ManageAuthorizedSpacesEvent) {
|
||||||
when (event) {
|
when (event) {
|
||||||
ManageAuthorizedSpacesEvent.Done -> {
|
ManageAuthorizedSpacesEvent.Done ->isSelectionComplete = true
|
||||||
isSelectionComplete = true
|
|
||||||
}
|
|
||||||
is ManageAuthorizedSpacesEvent.ToggleSpace -> {
|
is ManageAuthorizedSpacesEvent.ToggleSpace -> {
|
||||||
currentSelection = if (currentSelection.contains(event.roomId)) {
|
selectedIds = if (selectedIds.contains(event.roomId)) {
|
||||||
currentSelection.minus(event.roomId).toPersistentList()
|
selectedIds.minus(event.roomId).toPersistentList()
|
||||||
} else {
|
} else {
|
||||||
currentSelection.plus(event.roomId).toPersistentList()
|
selectedIds.plus(event.roomId).toPersistentList()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is ManageAuthorizedSpacesEvent.SetData -> {
|
is ManageAuthorizedSpacesEvent.SetData -> {
|
||||||
spacesData = event.data
|
spacesSelection = event.data
|
||||||
currentSelection = event.data.initialSelectedIds
|
selectedIds = event.data.initialSelectedIds
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return ManageAuthorizedSpacesState(
|
return ManageAuthorizedSpacesState(
|
||||||
selection = spacesData,
|
selection = spacesSelection,
|
||||||
selectedIds = currentSelection,
|
selectedIds = selectedIds,
|
||||||
isSelectionComplete = isSelectionComplete,
|
isSelectionComplete = isSelectionComplete,
|
||||||
eventSink = ::handleEvent,
|
eventSink = ::handleEvent,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,9 @@ data class ManageAuthorizedSpacesState(
|
||||||
val selectedIds: ImmutableList<RoomId>,
|
val selectedIds: ImmutableList<RoomId>,
|
||||||
val isSelectionComplete: Boolean,
|
val isSelectionComplete: Boolean,
|
||||||
val eventSink: (ManageAuthorizedSpacesEvent) -> Unit
|
val eventSink: (ManageAuthorizedSpacesEvent) -> Unit
|
||||||
)
|
) {
|
||||||
|
val isDoneButtonEnabled = selectedIds.isNotEmpty()
|
||||||
|
}
|
||||||
|
|
||||||
data class AuthorizedSpacesSelection(
|
data class AuthorizedSpacesSelection(
|
||||||
val joinedSpaces: ImmutableList<SpaceRoom> = persistentListOf(),
|
val joinedSpaces: ImmutableList<SpaceRoom> = persistentListOf(),
|
||||||
|
|
|
||||||
|
|
@ -53,6 +53,7 @@ fun ManageAuthorizedSpacesView(
|
||||||
onDoneClick = {
|
onDoneClick = {
|
||||||
state.eventSink(ManageAuthorizedSpacesEvent.Done)
|
state.eventSink(ManageAuthorizedSpacesEvent.Done)
|
||||||
},
|
},
|
||||||
|
isDoneButtonEnabled = state.isDoneButtonEnabled
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
) { padding ->
|
) { padding ->
|
||||||
|
|
@ -160,6 +161,7 @@ private fun CheckableSpaceListItem(
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
private fun ManageAuthorizedSpacesTopBar(
|
private fun ManageAuthorizedSpacesTopBar(
|
||||||
|
isDoneButtonEnabled: Boolean,
|
||||||
onBackClick: () -> Unit,
|
onBackClick: () -> Unit,
|
||||||
onDoneClick: () -> Unit,
|
onDoneClick: () -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
|
|
@ -170,6 +172,7 @@ private fun ManageAuthorizedSpacesTopBar(
|
||||||
navigationIcon = { BackButton(onClick = onBackClick) },
|
navigationIcon = { BackButton(onClick = onBackClick) },
|
||||||
actions = {
|
actions = {
|
||||||
TextButton(
|
TextButton(
|
||||||
|
enabled = isDoneButtonEnabled,
|
||||||
text = stringResource(CommonStrings.action_done),
|
text = stringResource(CommonStrings.action_done),
|
||||||
onClick = onDoneClick,
|
onClick = onDoneClick,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,7 @@ class SecurityAndPrivacyNode(
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getAuthorizedSpacesData(): AuthorizedSpacesSelection{
|
fun getAuthorizedSpacesData(): AuthorizedSpacesSelection{
|
||||||
return stateFlow.value.getAuthorizedSpaceData()
|
return stateFlow.value.getAuthorizedSpacesSelection()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onAuthorizedSpacesSelected(selectedSpaces: ImmutableList<RoomId>) {
|
fun onAuthorizedSpacesSelected(selectedSpaces: ImmutableList<RoomId>) {
|
||||||
|
|
|
||||||
|
|
@ -118,7 +118,7 @@ class SecurityAndPrivacyPresenter(
|
||||||
address = savedSettings.address,
|
address = savedSettings.address,
|
||||||
)
|
)
|
||||||
|
|
||||||
val selectableJoinedSpaces by produceState(persistentSetOf()) {
|
val selectableJoinedSpaces by produceState(initialValue = persistentSetOf(), key1 = savedSettings.roomAccess.spaceIds()) {
|
||||||
val joinedParentSpaces = matrixClient
|
val joinedParentSpaces = matrixClient
|
||||||
.spaceService
|
.spaceService
|
||||||
.joinedParents(room.roomId)
|
.joinedParents(room.roomId)
|
||||||
|
|
@ -193,7 +193,7 @@ class SecurityAndPrivacyPresenter(
|
||||||
saveAction.value = AsyncAction.Uninitialized
|
saveAction.value = AsyncAction.Uninitialized
|
||||||
}
|
}
|
||||||
SecurityAndPrivacyEvent.ManageAuthorizedSpaces -> {
|
SecurityAndPrivacyEvent.ManageAuthorizedSpaces -> {
|
||||||
navigator.openManageAuthorizedSpaces(editedSettings.roomAccess.spaceIds())
|
navigator.openManageAuthorizedSpaces()
|
||||||
}
|
}
|
||||||
SecurityAndPrivacyEvent.SelectSpaceMemberAccess -> handleSpaceMemberAccessSelection(
|
SecurityAndPrivacyEvent.SelectSpaceMemberAccess -> handleSpaceMemberAccessSelection(
|
||||||
spaceSelectionMode = spaceSelectionMode,
|
spaceSelectionMode = spaceSelectionMode,
|
||||||
|
|
@ -254,9 +254,7 @@ class SecurityAndPrivacyPresenter(
|
||||||
}
|
}
|
||||||
when (spaceSelectionMode) {
|
when (spaceSelectionMode) {
|
||||||
is SpaceSelectionMode.None -> Unit
|
is SpaceSelectionMode.None -> Unit
|
||||||
is SpaceSelectionMode.Multiple -> navigator.openManageAuthorizedSpaces(
|
is SpaceSelectionMode.Multiple -> navigator.openManageAuthorizedSpaces()
|
||||||
initialSelection = spaceIds ,
|
|
||||||
)
|
|
||||||
is SpaceSelectionMode.Single -> {
|
is SpaceSelectionMode.Single -> {
|
||||||
val newRoomAccess = SecurityAndPrivacyRoomAccess.SpaceMember(
|
val newRoomAccess = SecurityAndPrivacyRoomAccess.SpaceMember(
|
||||||
spaceIds = persistentListOf(spaceSelectionMode.spaceId)
|
spaceIds = persistentListOf(spaceSelectionMode.spaceId)
|
||||||
|
|
|
||||||
|
|
@ -42,8 +42,8 @@ data class SecurityAndPrivacyState(
|
||||||
val isSpaceMemberSelectable = isSpaceSettingsEnabled && spaceSelectionMode != SpaceSelectionMode.None
|
val isSpaceMemberSelectable = isSpaceSettingsEnabled && spaceSelectionMode != SpaceSelectionMode.None
|
||||||
|
|
||||||
// Show SpaceMember option in two cases:
|
// Show SpaceMember option in two cases:
|
||||||
// - the SpaceSettings FF is enabled
|
|
||||||
// - SpaceMember is the current saved value
|
// - SpaceMember is the current saved value
|
||||||
|
// - SpaceMember option is selectable (ie. the FF is enabled and there is at least one space to select)
|
||||||
val showSpaceMemberOption = savedSettings.roomAccess is SecurityAndPrivacyRoomAccess.SpaceMember || isSpaceMemberSelectable
|
val showSpaceMemberOption = savedSettings.roomAccess is SecurityAndPrivacyRoomAccess.SpaceMember || isSpaceMemberSelectable
|
||||||
|
|
||||||
val showManageSpaceAction = spaceSelectionMode is SpaceSelectionMode.Multiple && editedSettings.roomAccess is SecurityAndPrivacyRoomAccess.SpaceMember
|
val showManageSpaceAction = spaceSelectionMode is SpaceSelectionMode.Multiple && editedSettings.roomAccess is SecurityAndPrivacyRoomAccess.SpaceMember
|
||||||
|
|
@ -94,13 +94,16 @@ data class SecurityAndPrivacyState(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getAuthorizedSpaceData(): AuthorizedSpacesSelection {
|
fun getAuthorizedSpacesSelection(): AuthorizedSpacesSelection {
|
||||||
return AuthorizedSpacesSelection(
|
return AuthorizedSpacesSelection(
|
||||||
joinedSpaces = selectableJoinedSpaces.toImmutableList(),
|
joinedSpaces = selectableJoinedSpaces.toImmutableList(),
|
||||||
unknownSpaceIds = savedSettings.roomAccess.spaceIds().filter { spaceId ->
|
unknownSpaceIds = savedSettings.roomAccess.spaceIds().filter { spaceId ->
|
||||||
selectableJoinedSpaces.none { it.roomId == spaceId }
|
selectableJoinedSpaces.none { it.roomId == spaceId }
|
||||||
}.toImmutableList(),
|
}.toImmutableList(),
|
||||||
initialSelectedIds = editedSettings.roomAccess.spaceIds().toImmutableList()
|
initialSelectedIds = when (editedSettings.roomAccess) {
|
||||||
|
is SecurityAndPrivacyRoomAccess.SpaceMember -> editedSettings.roomAccess.spaceIds
|
||||||
|
else -> savedSettings.roomAccess.spaceIds()
|
||||||
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue