diff --git a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/root/SpacePresenter.kt b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/root/SpacePresenter.kt index abcb4b444e..e18f086834 100644 --- a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/root/SpacePresenter.kt +++ b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/root/SpacePresenter.kt @@ -112,6 +112,23 @@ class SpacePresenter( var isManageMode by remember { mutableStateOf(false) } var selectedRoomIds by remember { mutableStateOf>(emptySet()) } var removeRoomsAction by remember { mutableStateOf>(AsyncAction.Uninitialized) } + var removedRoomIds by remember { mutableStateOf>(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) { // Remove joined children from the join actions @@ -171,10 +188,19 @@ class SpacePresenter( localCoroutineScope.launch { removeRoomsAction = AsyncAction.Loading val spaceId = spaceRoomList.roomId - val results = selectedRoomIds.map { roomId -> - async { spaceService.removeChildFromSpace(spaceId, roomId) } + val roomsToRemove = selectedRoomIds.toSet() + val successfullyRemoved = mutableSetOf() + 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) { removeRoomsAction = AsyncAction.Failure(Exception("Failed to remove some rooms")) } else { @@ -191,7 +217,7 @@ class SpacePresenter( } return SpaceState( currentSpace = currentSpace.getOrNull(), - children = children, + children = filteredChildren, seenSpaceInvites = seenSpaceInvites, hideInvitesAvatar = hideInvitesAvatar, hasMoreToLoad = hasMoreToLoad, diff --git a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/root/SpaceState.kt b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/root/SpaceState.kt index e33d4730d1..05004ddace 100644 --- a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/root/SpaceState.kt +++ b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/root/SpaceState.kt @@ -34,11 +34,12 @@ data class SpaceState( val eventSink: (SpaceEvents) -> Unit ) { fun isJoining(spaceId: RoomId): Boolean = joinActions[spaceId] == AsyncAction.Loading + fun isSelected(spaceId: RoomId): Boolean = selectedRoomIds.contains(spaceId) val hasAnyFailure: Boolean = joinActions.values.any { 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 isRemoveButtonEnabled: Boolean = selectedRoomIds.isNotEmpty() } diff --git a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/root/SpaceView.kt b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/root/SpaceView.kt index 56c932f968..541c225b99 100644 --- a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/root/SpaceView.kt +++ b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/root/SpaceView.kt @@ -8,6 +8,7 @@ package io.element.android.features.space.impl.root +import androidx.activity.compose.BackHandler import androidx.annotation.StringRes import androidx.compose.foundation.clickable 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.libraries.architecture.AsyncAction 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.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.AsyncIndicatorHost 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.AvatarType 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.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.DropdownMenu 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.Icon 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.Text +import io.element.android.libraries.designsystem.theme.components.TextButton 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.spaces.SpaceRoom @@ -90,6 +91,15 @@ fun SpaceView( modifier: Modifier = Modifier, acceptDeclineInviteView: @Composable () -> Unit, ) { + + BackHandler { + if (state.isManageMode) { + state.eventSink(SpaceEvents.ExitManageMode) + } else { + onBackClick() + } + } + Scaffold( modifier = modifier, topBar = { @@ -230,7 +240,7 @@ private fun SpaceViewContent( ) { index, spaceRoom -> val isInvitation = spaceRoom.state == CurrentUserMembership.INVITED val isCurrentlyJoining = state.isJoining(spaceRoom.roomId) - val isSelected = spaceRoom.roomId in state.selectedRoomIds + val isSelected = state.isSelected(spaceRoom.roomId) SpaceRoomItemView( spaceRoom = spaceRoom, showUnreadIndicator = isInvitation && spaceRoom.roomId !in state.seenSpaceInvites, @@ -530,6 +540,13 @@ private fun RemoveRoomsConfirmationDialog( onSubmitClick = onConfirm, onDismiss = onDismiss, destructiveSubmit = true, + icon = { + Icon( + imageVector = CompoundIcons.Error(), + tint = ElementTheme.colors.textCriticalPrimary, + contentDescription = null + ) + } ) } else -> {