diff --git a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/root/SpaceEvents.kt b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/root/SpaceEvents.kt index 16a6ad1c3f..0f17a2f6f7 100644 --- a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/root/SpaceEvents.kt +++ b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/root/SpaceEvents.kt @@ -8,6 +8,7 @@ package io.element.android.features.space.impl.root +import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.spaces.SpaceRoom sealed interface SpaceEvents { @@ -19,4 +20,12 @@ sealed interface SpaceEvents { data class ShowTopicViewer(val topic: String) : SpaceEvents data object HideTopicViewer : SpaceEvents + + // Manage mode events + data object EnterManageMode : SpaceEvents + data object ExitManageMode : SpaceEvents + data class ToggleRoomSelection(val roomId: RoomId) : SpaceEvents + data object ConfirmRoomRemoval : SpaceEvents + data object RemoveSelectedRooms : SpaceEvents + data object ClearRemoveAction : SpaceEvents } 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 309747d2c9..abcb4b444e 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 @@ -40,6 +40,7 @@ import io.element.android.libraries.matrix.api.room.join.JoinRoom import io.element.android.libraries.matrix.api.room.powerlevels.permissionsAsState import io.element.android.libraries.matrix.api.spaces.SpaceRoom import io.element.android.libraries.matrix.api.spaces.SpaceRoomList +import io.element.android.libraries.matrix.api.spaces.SpaceService import io.element.android.libraries.matrix.ui.safety.rememberHideInvitesAvatar import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf @@ -48,6 +49,8 @@ import kotlinx.collections.immutable.toImmutableList import kotlinx.collections.immutable.toImmutableMap import kotlinx.collections.immutable.toImmutableSet import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.async +import kotlinx.coroutines.awaitAll import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch import kotlin.jvm.optionals.getOrNull @@ -62,6 +65,7 @@ class SpacePresenter( private val acceptDeclineInvitePresenter: Presenter, @SessionCoroutineScope private val sessionCoroutineScope: CoroutineScope, private val featureFlagService: FeatureFlagService, + private val spaceService: SpaceService, ) : Presenter { private var children by mutableStateOf>(persistentListOf()) @@ -104,6 +108,11 @@ class SpacePresenter( var topicViewerState: TopicViewerState by remember { mutableStateOf(TopicViewerState.Hidden) } + // Manage mode state + var isManageMode by remember { mutableStateOf(false) } + var selectedRoomIds by remember { mutableStateOf>(emptySet()) } + var removeRoomsAction by remember { mutableStateOf>(AsyncAction.Uninitialized) } + LaunchedEffect(children) { // Remove joined children from the join actions val joinedChildren = children @@ -138,6 +147,46 @@ class SpacePresenter( } SpaceEvents.HideTopicViewer -> topicViewerState = TopicViewerState.Hidden is SpaceEvents.ShowTopicViewer -> topicViewerState = TopicViewerState.Shown(event.topic) + + // Manage mode events + SpaceEvents.EnterManageMode -> { + isManageMode = true + selectedRoomIds = emptySet() + } + SpaceEvents.ExitManageMode -> { + isManageMode = false + selectedRoomIds = emptySet() + } + is SpaceEvents.ToggleRoomSelection -> { + selectedRoomIds = if (event.roomId in selectedRoomIds) { + selectedRoomIds - event.roomId + } else { + selectedRoomIds + event.roomId + } + } + SpaceEvents.RemoveSelectedRooms -> { + removeRoomsAction = AsyncAction.ConfirmingNoParams + } + SpaceEvents.ConfirmRoomRemoval -> { + localCoroutineScope.launch { + removeRoomsAction = AsyncAction.Loading + val spaceId = spaceRoomList.roomId + val results = selectedRoomIds.map { roomId -> + async { spaceService.removeChildFromSpace(spaceId, roomId) } + } + val hasError = results.awaitAll().any { it.isFailure } + if (hasError) { + removeRoomsAction = AsyncAction.Failure(Exception("Failed to remove some rooms")) + } else { + removeRoomsAction = AsyncAction.Success(Unit) + isManageMode = false + selectedRoomIds = emptySet() + } + } + } + SpaceEvents.ClearRemoveAction -> { + removeRoomsAction = AsyncAction.Uninitialized + } } } return SpaceState( @@ -150,6 +199,10 @@ class SpacePresenter( acceptDeclineInviteState = acceptDeclineInviteState, topicViewerState = topicViewerState, canAccessSpaceSettings = canAccessSpaceSettings, + isManageMode = isManageMode, + selectedRoomIds = selectedRoomIds.toImmutableSet(), + canManageRooms = permissions.canManageRooms, + removeRoomsAction = removeRoomsAction, eventSink = ::handleEvent, ) } 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 cceda62806..e33d4730d1 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 @@ -27,12 +27,20 @@ data class SpaceState( val acceptDeclineInviteState: AcceptDeclineInviteState, val topicViewerState: TopicViewerState, val canAccessSpaceSettings: Boolean, + val isManageMode: Boolean, + val selectedRoomIds: ImmutableSet, + val canManageRooms: Boolean, + val removeRoomsAction: AsyncAction, val eventSink: (SpaceEvents) -> Unit ) { fun isJoining(spaceId: RoomId): Boolean = joinActions[spaceId] == AsyncAction.Loading val hasAnyFailure: Boolean = joinActions.values.any { it is AsyncAction.Failure } + + val showManageRoomsAction: Boolean = canManageRooms && children.isNotEmpty() + val selectedCount: Int = selectedRoomIds.size + val isRemoveButtonEnabled: Boolean = selectedRoomIds.isNotEmpty() } @Immutable diff --git a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/root/SpaceStateProvider.kt b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/root/SpaceStateProvider.kt index 52894ad599..ea59bf6e2e 100644 --- a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/root/SpaceStateProvider.kt +++ b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/root/SpaceStateProvider.kt @@ -39,7 +39,26 @@ open class SpaceStateProvider : PreviewParameterProvider { aSpaceState( topicViewerState = TopicViewerState.Shown(topic = "Space description goes here." + LoremIpsum(20).values.first()), ), - // Add other states here + // Manage mode states + aSpaceState( + parentSpace = aParentSpace(), + children = aListOfSpaceRooms(), + isManageMode = true, + selectedRoomIds = emptySet(), + ), + aSpaceState( + parentSpace = aParentSpace(), + children = aListOfSpaceRooms(), + isManageMode = true, + selectedRoomIds = setOf(RoomId("!spaceId0:example.com"), RoomId("!spaceId1:example.com")), + ), + aSpaceState( + parentSpace = aParentSpace(), + children = aListOfSpaceRooms(), + isManageMode = true, + selectedRoomIds = setOf(RoomId("!spaceId0:example.com")), + removeRoomsAction = AsyncAction.ConfirmingNoParams, + ), ) } @@ -54,6 +73,10 @@ fun aSpaceState( acceptDeclineInviteState: AcceptDeclineInviteState = anAcceptDeclineInviteState(), topicViewerState: TopicViewerState = TopicViewerState.Hidden, canAccessSpaceSettings: Boolean = true, + isManageMode: Boolean = false, + selectedRoomIds: Set = emptySet(), + canManageRooms: Boolean = true, + removeRoomsAction: AsyncAction = AsyncAction.Uninitialized, eventSink: (SpaceEvents) -> Unit = { }, ) = SpaceState( currentSpace = parentSpace, @@ -65,6 +88,10 @@ fun aSpaceState( acceptDeclineInviteState = acceptDeclineInviteState, topicViewerState = topicViewerState, canAccessSpaceSettings = canAccessSpaceSettings, + isManageMode = isManageMode, + selectedRoomIds = selectedRoomIds.toImmutableSet(), + canManageRooms = canManageRooms, + removeRoomsAction = removeRoomsAction, eventSink = eventSink, ) 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 769b608e8e..56c932f968 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 @@ -40,7 +40,10 @@ import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp 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.AsyncIndicator @@ -56,9 +59,11 @@ import io.element.android.libraries.designsystem.preview.PreviewsDayNight 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.TopAppBar @@ -88,15 +93,26 @@ fun SpaceView( Scaffold( modifier = modifier, topBar = { - SpaceViewTopBar( - currentSpace = state.currentSpace, - canAccessSpaceSettings = state.canAccessSpaceSettings, - onBackClick = onBackClick, - onLeaveSpaceClick = onLeaveSpaceClick, - onShareSpace = onShareSpace, - onSettingsClick = onSettingsClick, - onViewMembersClick = onViewMembersClick, - ) + if (state.isManageMode) { + ManageModeTopBar( + selectedCount = state.selectedCount, + isRemoveButtonEnabled = state.isRemoveButtonEnabled, + onCancelClick = { state.eventSink(SpaceEvents.ExitManageMode) }, + onRemoveClick = { state.eventSink(SpaceEvents.RemoveSelectedRooms) }, + ) + } else { + SpaceViewTopBar( + currentSpace = state.currentSpace, + canAccessSpaceSettings = state.canAccessSpaceSettings, + showManageRoomsAction = state.showManageRoomsAction, + onBackClick = onBackClick, + onLeaveSpaceClick = onLeaveSpaceClick, + onShareSpace = onShareSpace, + onSettingsClick = onSettingsClick, + onViewMembersClick = onViewMembersClick, + onManageRoomsClick = { state.eventSink(SpaceEvents.EnterManageMode) }, + ) + } }, content = { padding -> Box( @@ -104,7 +120,13 @@ fun SpaceView( ) { SpaceViewContent( state = state, - onRoomClick = onRoomClick, + onRoomClick = { spaceRoom -> + if (state.isManageMode) { + state.eventSink(SpaceEvents.ToggleRoomSelection(spaceRoom.roomId)) + } else { + onRoomClick(spaceRoom) + } + }, onTopicClick = { topic -> state.eventSink(SpaceEvents.ShowTopicViewer(topic)) } @@ -125,6 +147,14 @@ fun SpaceView( } ) } + + // Confirmation dialog for removing rooms + RemoveRoomsConfirmationDialog( + removeRoomsAction = state.removeRoomsAction, + selectedCount = state.selectedCount, + onConfirm = { state.eventSink(SpaceEvents.ConfirmRoomRemoval) }, + onDismiss = { state.eventSink(SpaceEvents.ClearRemoveAction) }, + ) } @Composable @@ -200,6 +230,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 SpaceRoomItemView( spaceRoom = spaceRoom, showUnreadIndicator = isInvitation && spaceRoom.roomId !in state.seenSpaceInvites, @@ -210,17 +241,30 @@ private fun SpaceViewContent( onLongClick = { // TODO }, - trailingAction = spaceRoom.trailingAction(isCurrentlyJoining = isCurrentlyJoining) { - state.eventSink(SpaceEvents.Join(spaceRoom)) - }, - bottomAction = spaceRoom.inviteButtons( - onAcceptClick = { - state.eventSink(SpaceEvents.AcceptInvite(spaceRoom)) - }, - onDeclineClick = { - state.eventSink(SpaceEvents.DeclineInvite(spaceRoom)) + trailingAction = if (state.isManageMode) { + { + Checkbox( + checked = isSelected, + onCheckedChange = null, + ) } - ) + } else { + spaceRoom.trailingAction(isCurrentlyJoining = isCurrentlyJoining) { + state.eventSink(SpaceEvents.Join(spaceRoom)) + } + }, + bottomAction = if (state.isManageMode) { + null + } else { + spaceRoom.inviteButtons( + onAcceptClick = { + state.eventSink(SpaceEvents.AcceptInvite(spaceRoom)) + }, + onDeclineClick = { + state.eventSink(SpaceEvents.DeclineInvite(spaceRoom)) + } + ) + } ) if (index != state.children.lastIndex) { HorizontalDivider() @@ -259,11 +303,13 @@ private fun LoadingMoreIndicator( private fun SpaceViewTopBar( currentSpace: SpaceRoom?, canAccessSpaceSettings: Boolean, + showManageRoomsAction: Boolean, onBackClick: () -> Unit, onLeaveSpaceClick: () -> Unit, onSettingsClick: () -> Unit, onShareSpace: () -> Unit, onViewMembersClick: () -> Unit, + onManageRoomsClick: () -> Unit, modifier: Modifier = Modifier, ) { TopAppBar( @@ -313,6 +359,16 @@ private fun SpaceViewTopBar( onShareSpace() } ) + if (showManageRoomsAction) { + SpaceMenuItem( + titleRes = CommonStrings.action_manage_rooms, + icon = CompoundIcons.Edit(), + onClick = { + showMenu = false + onManageRoomsClick() + } + ) + } if (canAccessSpaceSettings) { SpaceMenuItem( titleRes = CommonStrings.common_settings, @@ -337,6 +393,39 @@ private fun SpaceViewTopBar( ) } +@OptIn(ExperimentalMaterial3Api::class) +@Composable +private fun ManageModeTopBar( + selectedCount: Int, + isRemoveButtonEnabled: Boolean, + onCancelClick: () -> Unit, + onRemoveClick: () -> Unit, + modifier: Modifier = Modifier, +) { + TopAppBar( + modifier = modifier, + navigationIcon = { + BackButton( + onClick = onCancelClick, + imageVector = CompoundIcons.Close() + ) + }, + title = { + Text( + text = "$selectedCount selected", + style = ElementTheme.typography.fontBodyLgMedium, + ) + }, + actions = { + TextButton( + text = stringResource(CommonStrings.action_remove), + onClick = onRemoveClick, + enabled = isRemoveButtonEnabled, + ) + }, + ) +} + @Composable private fun SpaceMenuItem( @StringRes titleRes: Int, @@ -425,6 +514,34 @@ private fun SpaceRoom.inviteButtons( } } +@Composable +private fun RemoveRoomsConfirmationDialog( + removeRoomsAction: AsyncAction, + selectedCount: Int, + onConfirm: () -> Unit, + onDismiss: () -> Unit, +) { + when (removeRoomsAction) { + AsyncAction.ConfirmingNoParams -> { + ConfirmationDialog( + title = "Remove $selectedCount rooms from space?", + content = "Removing a room will not affect the room access. To change the access go to Room info > Privacy & security.", + submitText = stringResource(CommonStrings.action_remove), + onSubmitClick = onConfirm, + onDismiss = onDismiss, + destructiveSubmit = true, + ) + } + else -> { + AsyncActionView( + async = removeRoomsAction, + onSuccess = { onDismiss() }, + onErrorDismiss = onDismiss, + ) + } + } +} + @PreviewsDayNight @Composable internal fun SpaceViewPreview( diff --git a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/settings/SpaceSettingsPermissions.kt b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/settings/SpaceSettingsPermissions.kt index e3ec70a51d..8297992b49 100644 --- a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/settings/SpaceSettingsPermissions.kt +++ b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/settings/SpaceSettingsPermissions.kt @@ -11,6 +11,7 @@ import io.element.android.features.roomdetailsedit.api.RoomDetailsEditPermission import io.element.android.features.roomdetailsedit.api.roomDetailsEditPermissions import io.element.android.features.securityandprivacy.api.SecurityAndPrivacyPermissions import io.element.android.features.securityandprivacy.api.securityAndPrivacyPermissions +import io.element.android.libraries.matrix.api.room.StateEventType import io.element.android.libraries.matrix.api.room.join.JoinRule import io.element.android.libraries.matrix.api.room.powerlevels.RoomPermissions import io.element.android.libraries.matrix.api.room.powerlevels.canEditRolesAndPermissions @@ -19,6 +20,7 @@ data class SpaceSettingsPermissions( val editDetailsPermissions: RoomDetailsEditPermissions, val canEditRolesAndPermissions: Boolean, val securityAndPrivacyPermissions: SecurityAndPrivacyPermissions, + val canManageRooms: Boolean, ) { fun hasAny(joinRule: JoinRule?): Boolean { return editDetailsPermissions.hasAny || @@ -31,6 +33,7 @@ data class SpaceSettingsPermissions( editDetailsPermissions = RoomDetailsEditPermissions.DEFAULT, canEditRolesAndPermissions = false, securityAndPrivacyPermissions = SecurityAndPrivacyPermissions.DEFAULT, + canManageRooms = false, ) } } @@ -40,5 +43,6 @@ fun RoomPermissions.spaceSettingsPermissions(): SpaceSettingsPermissions { editDetailsPermissions = roomDetailsEditPermissions(), canEditRolesAndPermissions = canEditRolesAndPermissions(), securityAndPrivacyPermissions = securityAndPrivacyPermissions(), + canManageRooms = canOwnUserSendState(StateEventType.SpaceChild), ) } diff --git a/features/space/impl/src/test/kotlin/io/element/android/features/space/impl/root/SpacePresenterTest.kt b/features/space/impl/src/test/kotlin/io/element/android/features/space/impl/root/SpacePresenterTest.kt index 917aceb262..65a4c76783 100644 --- a/features/space/impl/src/test/kotlin/io/element/android/features/space/impl/root/SpacePresenterTest.kt +++ b/features/space/impl/src/test/kotlin/io/element/android/features/space/impl/root/SpacePresenterTest.kt @@ -28,6 +28,7 @@ import io.element.android.libraries.matrix.api.room.BaseRoom import io.element.android.libraries.matrix.api.room.CurrentUserMembership import io.element.android.libraries.matrix.api.room.join.JoinRoom import io.element.android.libraries.matrix.api.spaces.SpaceRoomList +import io.element.android.libraries.matrix.api.spaces.SpaceService import io.element.android.libraries.matrix.test.AN_EXCEPTION import io.element.android.libraries.matrix.test.A_ROOM_ID import io.element.android.libraries.matrix.test.A_ROOM_ID_2 @@ -36,6 +37,7 @@ import io.element.android.libraries.matrix.test.room.FakeBaseRoom import io.element.android.libraries.matrix.test.room.join.FakeJoinRoom import io.element.android.libraries.matrix.test.room.powerlevels.FakeRoomPermissions import io.element.android.libraries.matrix.test.spaces.FakeSpaceRoomList +import io.element.android.libraries.matrix.test.spaces.FakeSpaceService import io.element.android.libraries.previewutils.room.aSpaceRoom import io.element.android.tests.testutils.EventsRecorder import io.element.android.tests.testutils.lambda.lambdaRecorder @@ -365,6 +367,7 @@ class SpacePresenterTest { ), acceptDeclineInvitePresenter: Presenter = Presenter { anAcceptDeclineInviteState() }, spaceSettingsEnabled: Boolean = false, + spaceService: FakeSpaceService = FakeSpaceService(), ): SpacePresenter { return SpacePresenter( client = client, @@ -379,6 +382,7 @@ class SpacePresenterTest { FeatureFlags.SpaceSettings.key to spaceSettingsEnabled, ) ), + spaceService = spaceService, ) } } diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/spaces/SpaceService.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/spaces/SpaceService.kt index 6f5ba674ec..a89bb2da86 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/spaces/SpaceService.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/spaces/SpaceService.kt @@ -18,4 +18,12 @@ interface SpaceService { fun spaceRoomList(id: RoomId): SpaceRoomList fun getLeaveSpaceHandle(spaceId: RoomId): LeaveSpaceHandle + + /** + * Remove a child room from a space. + * @param spaceId The space ID from which to remove the child. + * @param childId The room ID of the child to remove. + * @return A result indicating success or failure. + */ + suspend fun removeChildFromSpace(spaceId: RoomId, childId: RoomId): Result } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/spaces/RustSpaceService.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/spaces/RustSpaceService.kt index ba816c11c7..41aae086fa 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/spaces/RustSpaceService.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/spaces/RustSpaceService.kt @@ -79,6 +79,12 @@ class RustSpaceService( } } + override suspend fun removeChildFromSpace(spaceId: RoomId, childId: RoomId): Result = withContext(sessionDispatcher) { + runCatchingExceptions { + innerSpaceService.removeChildFromSpace(spaceId.value, childId.value) + } + } + init { innerSpaceService .spaceListUpdate() diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/spaces/FakeSpaceService.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/spaces/FakeSpaceService.kt index eaa36ee750..539bb0ce6f 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/spaces/FakeSpaceService.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/spaces/FakeSpaceService.kt @@ -23,6 +23,7 @@ class FakeSpaceService( private val joinedSpacesResult: () -> Result> = { lambdaError() }, private val spaceRoomListResult: (RoomId) -> SpaceRoomList = { lambdaError() }, private val leaveSpaceHandleResult: (RoomId) -> LeaveSpaceHandle = { lambdaError() }, + private val removeChildFromSpaceResult: (RoomId, RoomId) -> Result = { _, _ -> lambdaError() }, ) : SpaceService { private val _spaceRoomsFlow = MutableSharedFlow>() override val spaceRoomsFlow: SharedFlow> @@ -43,4 +44,8 @@ class FakeSpaceService( override fun getLeaveSpaceHandle(spaceId: RoomId): LeaveSpaceHandle { return leaveSpaceHandleResult(spaceId) } + + override suspend fun removeChildFromSpace(spaceId: RoomId, childId: RoomId): Result = simulateLongTask { + removeChildFromSpaceResult(spaceId, childId) + } } diff --git a/libraries/ui-strings/src/main/res/values/localazy.xml b/libraries/ui-strings/src/main/res/values/localazy.xml index df47b71577..f5c3c3f12f 100644 --- a/libraries/ui-strings/src/main/res/values/localazy.xml +++ b/libraries/ui-strings/src/main/res/values/localazy.xml @@ -112,6 +112,7 @@ "Load more" "Manage account" "Manage devices" + "Manage rooms" "Message" "Minimise" "Next"