Iterate on removing space child rooms.

This commit is contained in:
ganfra 2026-01-14 15:29:18 +01:00
parent 7b8e11c8cb
commit bed35807d2
3 changed files with 54 additions and 10 deletions

View file

@ -112,6 +112,23 @@ class SpacePresenter(
var isManageMode by remember { mutableStateOf(false) } var isManageMode by remember { mutableStateOf(false) }
var selectedRoomIds by remember { mutableStateOf<Set<RoomId>>(emptySet()) } var selectedRoomIds by remember { mutableStateOf<Set<RoomId>>(emptySet()) }
var removeRoomsAction by remember { mutableStateOf<AsyncAction<Unit>>(AsyncAction.Uninitialized) } var removeRoomsAction by remember { mutableStateOf<AsyncAction<Unit>>(AsyncAction.Uninitialized) }
var removedRoomIds by remember { mutableStateOf<Set<RoomId>>(emptySet()) }
val filteredChildren by remember {
derivedStateOf {
children
.filterNot { it.roomId in removedRoomIds }
.let { list ->
if (isManageMode) {
// In manage mode, only show rooms (not spaces)
list.filter { !it.isSpace }
} else {
list
}
}
.toImmutableList()
}
}
LaunchedEffect(children) { LaunchedEffect(children) {
// Remove joined children from the join actions // Remove joined children from the join actions
@ -171,10 +188,19 @@ class SpacePresenter(
localCoroutineScope.launch { localCoroutineScope.launch {
removeRoomsAction = AsyncAction.Loading removeRoomsAction = AsyncAction.Loading
val spaceId = spaceRoomList.roomId val spaceId = spaceRoomList.roomId
val results = selectedRoomIds.map { roomId -> val roomsToRemove = selectedRoomIds.toSet()
async { spaceService.removeChildFromSpace(spaceId, roomId) } val successfullyRemoved = mutableSetOf<RoomId>()
val results = roomsToRemove.map { roomId ->
async {
spaceService.removeChildFromSpace(spaceId, roomId)
.onSuccess { successfullyRemoved.add(roomId) }
}
} }
val hasError = results.awaitAll().any { it.isFailure } results.awaitAll()
if (successfullyRemoved.isNotEmpty()) {
removedRoomIds = removedRoomIds + successfullyRemoved
}
val hasError = successfullyRemoved.size < roomsToRemove.size
if (hasError) { if (hasError) {
removeRoomsAction = AsyncAction.Failure(Exception("Failed to remove some rooms")) removeRoomsAction = AsyncAction.Failure(Exception("Failed to remove some rooms"))
} else { } else {
@ -191,7 +217,7 @@ class SpacePresenter(
} }
return SpaceState( return SpaceState(
currentSpace = currentSpace.getOrNull(), currentSpace = currentSpace.getOrNull(),
children = children, children = filteredChildren,
seenSpaceInvites = seenSpaceInvites, seenSpaceInvites = seenSpaceInvites,
hideInvitesAvatar = hideInvitesAvatar, hideInvitesAvatar = hideInvitesAvatar,
hasMoreToLoad = hasMoreToLoad, hasMoreToLoad = hasMoreToLoad,

View file

@ -34,11 +34,12 @@ data class SpaceState(
val eventSink: (SpaceEvents) -> Unit val eventSink: (SpaceEvents) -> Unit
) { ) {
fun isJoining(spaceId: RoomId): Boolean = joinActions[spaceId] == AsyncAction.Loading fun isJoining(spaceId: RoomId): Boolean = joinActions[spaceId] == AsyncAction.Loading
fun isSelected(spaceId: RoomId): Boolean = selectedRoomIds.contains(spaceId)
val hasAnyFailure: Boolean = joinActions.values.any { val hasAnyFailure: Boolean = joinActions.values.any {
it is AsyncAction.Failure it is AsyncAction.Failure
} }
val showManageRoomsAction: Boolean = canManageRooms && children.isNotEmpty() val showManageRoomsAction: Boolean = canManageRooms && children.any { spaceRoom -> !spaceRoom.isSpace }
val selectedCount: Int = selectedRoomIds.size val selectedCount: Int = selectedRoomIds.size
val isRemoveButtonEnabled: Boolean = selectedRoomIds.isNotEmpty() val isRemoveButtonEnabled: Boolean = selectedRoomIds.isNotEmpty()
} }

View file

@ -8,6 +8,7 @@
package io.element.android.features.space.impl.root package io.element.android.features.space.impl.root
import androidx.activity.compose.BackHandler
import androidx.annotation.StringRes import androidx.annotation.StringRes
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.interaction.MutableInteractionSource
@ -42,10 +43,9 @@ import io.element.android.compound.theme.ElementTheme
import io.element.android.compound.tokens.generated.CompoundIcons import io.element.android.compound.tokens.generated.CompoundIcons
import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.AsyncAction
import io.element.android.libraries.designsystem.atomic.molecules.InviteButtonsRowMolecule import io.element.android.libraries.designsystem.atomic.molecules.InviteButtonsRowMolecule
import io.element.android.libraries.designsystem.components.async.AsyncActionView
import io.element.android.libraries.designsystem.components.dialogs.ConfirmationDialog
import io.element.android.libraries.designsystem.components.ClickableLinkText import io.element.android.libraries.designsystem.components.ClickableLinkText
import io.element.android.libraries.designsystem.components.SimpleModalBottomSheet import io.element.android.libraries.designsystem.components.SimpleModalBottomSheet
import io.element.android.libraries.designsystem.components.async.AsyncActionView
import io.element.android.libraries.designsystem.components.async.AsyncIndicator import io.element.android.libraries.designsystem.components.async.AsyncIndicator
import io.element.android.libraries.designsystem.components.async.AsyncIndicatorHost import io.element.android.libraries.designsystem.components.async.AsyncIndicatorHost
import io.element.android.libraries.designsystem.components.async.rememberAsyncIndicatorState import io.element.android.libraries.designsystem.components.async.rememberAsyncIndicatorState
@ -54,18 +54,19 @@ import io.element.android.libraries.designsystem.components.avatar.AvatarData
import io.element.android.libraries.designsystem.components.avatar.AvatarSize import io.element.android.libraries.designsystem.components.avatar.AvatarSize
import io.element.android.libraries.designsystem.components.avatar.AvatarType import io.element.android.libraries.designsystem.components.avatar.AvatarType
import io.element.android.libraries.designsystem.components.button.BackButton import io.element.android.libraries.designsystem.components.button.BackButton
import io.element.android.libraries.designsystem.components.dialogs.ConfirmationDialog
import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.designsystem.theme.components.Checkbox
import io.element.android.libraries.designsystem.theme.components.CircularProgressIndicator import io.element.android.libraries.designsystem.theme.components.CircularProgressIndicator
import io.element.android.libraries.designsystem.theme.components.DropdownMenu import io.element.android.libraries.designsystem.theme.components.DropdownMenu
import io.element.android.libraries.designsystem.theme.components.DropdownMenuItem import io.element.android.libraries.designsystem.theme.components.DropdownMenuItem
import io.element.android.libraries.designsystem.theme.components.Checkbox
import io.element.android.libraries.designsystem.theme.components.HorizontalDivider import io.element.android.libraries.designsystem.theme.components.HorizontalDivider
import io.element.android.libraries.designsystem.theme.components.Icon import io.element.android.libraries.designsystem.theme.components.Icon
import io.element.android.libraries.designsystem.theme.components.IconButton import io.element.android.libraries.designsystem.theme.components.IconButton
import io.element.android.libraries.designsystem.theme.components.TextButton
import io.element.android.libraries.designsystem.theme.components.Scaffold import io.element.android.libraries.designsystem.theme.components.Scaffold
import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.designsystem.theme.components.TextButton
import io.element.android.libraries.designsystem.theme.components.TopAppBar import io.element.android.libraries.designsystem.theme.components.TopAppBar
import io.element.android.libraries.matrix.api.room.CurrentUserMembership import io.element.android.libraries.matrix.api.room.CurrentUserMembership
import io.element.android.libraries.matrix.api.spaces.SpaceRoom import io.element.android.libraries.matrix.api.spaces.SpaceRoom
@ -90,6 +91,15 @@ fun SpaceView(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
acceptDeclineInviteView: @Composable () -> Unit, acceptDeclineInviteView: @Composable () -> Unit,
) { ) {
BackHandler {
if (state.isManageMode) {
state.eventSink(SpaceEvents.ExitManageMode)
} else {
onBackClick()
}
}
Scaffold( Scaffold(
modifier = modifier, modifier = modifier,
topBar = { topBar = {
@ -230,7 +240,7 @@ private fun SpaceViewContent(
) { index, spaceRoom -> ) { index, spaceRoom ->
val isInvitation = spaceRoom.state == CurrentUserMembership.INVITED val isInvitation = spaceRoom.state == CurrentUserMembership.INVITED
val isCurrentlyJoining = state.isJoining(spaceRoom.roomId) val isCurrentlyJoining = state.isJoining(spaceRoom.roomId)
val isSelected = spaceRoom.roomId in state.selectedRoomIds val isSelected = state.isSelected(spaceRoom.roomId)
SpaceRoomItemView( SpaceRoomItemView(
spaceRoom = spaceRoom, spaceRoom = spaceRoom,
showUnreadIndicator = isInvitation && spaceRoom.roomId !in state.seenSpaceInvites, showUnreadIndicator = isInvitation && spaceRoom.roomId !in state.seenSpaceInvites,
@ -530,6 +540,13 @@ private fun RemoveRoomsConfirmationDialog(
onSubmitClick = onConfirm, onSubmitClick = onConfirm,
onDismiss = onDismiss, onDismiss = onDismiss,
destructiveSubmit = true, destructiveSubmit = true,
icon = {
Icon(
imageVector = CompoundIcons.Error(),
tint = ElementTheme.colors.textCriticalPrimary,
contentDescription = null
)
}
) )
} }
else -> { else -> {