diff --git a/appnav/src/main/kotlin/io/element/android/appnav/LoggedInEventProcessor.kt b/appnav/src/main/kotlin/io/element/android/appnav/LoggedInEventProcessor.kt index 5444b5c465e..c557d6e1c21 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/LoggedInEventProcessor.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/LoggedInEventProcessor.kt @@ -31,9 +31,17 @@ class LoggedInEventProcessor( observingJob = roomMembershipObserver.updates .filter { !it.isUserInRoom } .distinctUntilChanged() - .onEach { - when (it.change) { - MembershipChange.LEFT -> displayMessage(CommonStrings.common_current_user_left_room) + .onEach { roomMemberShipUpdate -> + when (roomMemberShipUpdate.change) { + MembershipChange.LEFT -> { + displayMessage( + if (roomMemberShipUpdate.isSpace) { + CommonStrings.common_current_user_left_space + } else { + CommonStrings.common_current_user_left_room + } + ) + } MembershipChange.INVITATION_REJECTED -> displayMessage(CommonStrings.common_current_user_rejected_invite) MembershipChange.KNOCK_RETRACTED -> displayMessage(CommonStrings.common_current_user_canceled_knock) else -> Unit diff --git a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/SpaceEvents.kt b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/SpaceEvents.kt index 848dac3ebce..ab927ee5c31 100644 --- a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/SpaceEvents.kt +++ b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/SpaceEvents.kt @@ -9,4 +9,6 @@ package io.element.android.features.space.impl sealed interface SpaceEvents { data object LoadMore : SpaceEvents + data object LeaveSpace : SpaceEvents + data object CancelLeaveSpace : SpaceEvents } diff --git a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/SpacePresenter.kt b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/SpacePresenter.kt index 95597371a6a..733bce87486 100644 --- a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/SpacePresenter.kt +++ b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/SpacePresenter.kt @@ -9,8 +9,10 @@ package io.element.android.features.space.impl import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.MutableState import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import dev.zacsweers.metro.Assisted @@ -18,15 +20,20 @@ import dev.zacsweers.metro.AssistedFactory import dev.zacsweers.metro.Inject import io.element.android.features.invite.api.SeenInvitesStore import io.element.android.features.space.api.SpaceEntryPoint +import io.element.android.features.space.impl.leave.ConfirmingLeavingSpace +import io.element.android.libraries.architecture.AsyncAction +import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.core.coroutine.mapState import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.spaces.SpaceRoomList import io.element.android.libraries.matrix.ui.safety.rememberHideInvitesAvatar +import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.persistentSetOf import kotlinx.collections.immutable.toPersistentList import kotlinx.collections.immutable.toPersistentSet import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.delay import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch @@ -65,10 +72,33 @@ class SpacePresenter( }.collectAsState() val currentSpace by remember { spaceRoomList.currentSpaceFlow() }.collectAsState(null) + val leaveSpaceBottomSheetState = remember { mutableStateOf>(AsyncAction.Uninitialized) } fun handleEvents(event: SpaceEvents) { when (event) { SpaceEvents.LoadMore -> coroutineScope.paginate() + SpaceEvents.CancelLeaveSpace -> { + leaveSpaceBottomSheetState.value = AsyncAction.Uninitialized + } + SpaceEvents.LeaveSpace -> if (leaveSpaceBottomSheetState.value is AsyncAction.Confirming) { + coroutineScope.launch { + leaveSpaceBottomSheetState.value = AsyncAction.Loading + client.getRoom(inputs.roomId)?.leave()?.fold( + onSuccess = { + // Successfully left the space, nothing more to do, the screen will be closed automatically + leaveSpaceBottomSheetState.value = AsyncAction.Success(Unit) + }, + onFailure = { + leaveSpaceBottomSheetState.value = AsyncAction.Failure(it) + } + ) + } + } else { + coroutineScope.startLeaveSpace( + spaceName = currentSpace?.name, + leaveSpaceBottomSheetState, + ) + } } } return SpaceState( @@ -77,6 +107,7 @@ class SpacePresenter( seenSpaceInvites = seenSpaceInvites, hideInvitesAvatar = hideInvitesAvatar, hasMoreToLoad = hasMoreToLoad, + leaveSpaceBottomSheetState = leaveSpaceBottomSheetState.value, eventSink = ::handleEvents, ) } @@ -84,4 +115,23 @@ class SpacePresenter( private fun CoroutineScope.paginate() = launch { spaceRoomList.paginate() } + + private fun CoroutineScope.startLeaveSpace( + spaceName: String?, + leaveSpaceBottomSheetState: MutableState>, + ) = launch { + leaveSpaceBottomSheetState.value = ConfirmingLeavingSpace( + spaceName = spaceName, + roomsWhereUserIsTheOnlyAdmin = AsyncData.Loading(), + ) + // TODO Fetch the actual list of rooms where the user is the only admin + delay(1000) + // Update state only if not cancelled by the user + if (leaveSpaceBottomSheetState.value is ConfirmingLeavingSpace) { + leaveSpaceBottomSheetState.value = ConfirmingLeavingSpace( + spaceName = spaceName, + roomsWhereUserIsTheOnlyAdmin = AsyncData.Success(persistentListOf()), + ) + } + } } diff --git a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/SpaceState.kt b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/SpaceState.kt index ad822283ca6..72f209921f0 100644 --- a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/SpaceState.kt +++ b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/SpaceState.kt @@ -7,6 +7,7 @@ package io.element.android.features.space.impl +import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.spaces.SpaceRoom import kotlinx.collections.immutable.ImmutableList @@ -18,5 +19,6 @@ data class SpaceState( val seenSpaceInvites: ImmutableSet, val hideInvitesAvatar: Boolean, val hasMoreToLoad: Boolean, + val leaveSpaceBottomSheetState: AsyncAction, val eventSink: (SpaceEvents) -> Unit ) diff --git a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/SpaceStateProvider.kt b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/SpaceStateProvider.kt index 36c2ab62b02..2b2a05a7148 100644 --- a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/SpaceStateProvider.kt +++ b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/SpaceStateProvider.kt @@ -8,6 +8,8 @@ package io.element.android.features.space.impl import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import io.element.android.features.space.impl.leave.aConfirmingLeavingSpace +import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.spaces.SpaceRoom import io.element.android.libraries.previewutils.room.aSpaceRoom @@ -26,7 +28,16 @@ open class SpaceStateProvider : PreviewParameterProvider { aSpaceState( hasMoreToLoad = false, children = aListOfSpaceRooms() - ) + ), + aSpaceState( + leaveSpaceBottomSheetState = aConfirmingLeavingSpace(), + ), + aSpaceState( + leaveSpaceBottomSheetState = AsyncAction.Loading, + ), + aSpaceState( + leaveSpaceBottomSheetState = AsyncAction.Failure(Exception("An error")), + ), // Add other states here ) } @@ -42,12 +53,14 @@ fun aSpaceState( seenSpaceInvites: Set = emptySet(), hideInvitesAvatar: Boolean = false, hasMoreToLoad: Boolean = false, + leaveSpaceBottomSheetState: AsyncAction = AsyncAction.Uninitialized, ) = SpaceState( currentSpace = parentSpace, children = children.toImmutableList(), seenSpaceInvites = seenSpaceInvites.toImmutableSet(), hideInvitesAvatar = hideInvitesAvatar, hasMoreToLoad = hasMoreToLoad, + leaveSpaceBottomSheetState = leaveSpaceBottomSheetState, eventSink = {} ) diff --git a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/SpaceView.kt b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/SpaceView.kt index f3bdf7379b2..a8faa517862 100644 --- a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/SpaceView.kt +++ b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/SpaceView.kt @@ -17,7 +17,10 @@ import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember import androidx.compose.runtime.rememberUpdatedState +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource @@ -28,20 +31,29 @@ import androidx.compose.ui.text.style.TextOverflow 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.features.space.impl.leave.ConfirmingLeavingSpace +import io.element.android.features.space.impl.leave.LeaveSpaceBottomSheet +import io.element.android.libraries.architecture.AsyncAction +import io.element.android.libraries.designsystem.components.ProgressDialog import io.element.android.libraries.designsystem.components.avatar.Avatar 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.ErrorDialog import io.element.android.libraries.designsystem.preview.ElementPreview 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.Icon +import io.element.android.libraries.designsystem.theme.components.IconButton 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 import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.room.CurrentUserMembership -import io.element.android.libraries.matrix.api.spaces.SpaceRoom import io.element.android.libraries.matrix.ui.components.SpaceHeaderView import io.element.android.libraries.matrix.ui.components.SpaceRoomItemView import io.element.android.libraries.matrix.ui.model.getAvatarData @@ -58,7 +70,10 @@ fun SpaceView( Scaffold( modifier = modifier, topBar = { - SpaceViewTopBar(currentSpace = state.currentSpace, onBackClick = onBackClick) + SpaceViewTopBar( + state = state, + onBackClick = onBackClick, + ) }, content = { padding -> Box( @@ -71,6 +86,25 @@ fun SpaceView( } }, ) + + when (state.leaveSpaceBottomSheetState) { + is AsyncAction.Confirming -> LeaveSpaceBottomSheet( + state = state.leaveSpaceBottomSheetState as ConfirmingLeavingSpace, + onLeaveSpace = { + state.eventSink(SpaceEvents.LeaveSpace) + }, + onDismiss = { + state.eventSink(SpaceEvents.CancelLeaveSpace) + } + ) + is AsyncAction.Failure -> ErrorDialog( + content = stringResource(CommonStrings.error_unknown), + onSubmit = { state.eventSink(SpaceEvents.CancelLeaveSpace) }, + ) + AsyncAction.Loading -> ProgressDialog() + is AsyncAction.Success, + AsyncAction.Uninitialized -> Unit + } } @Composable @@ -141,10 +175,11 @@ private fun LoadingMoreIndicator( @OptIn(ExperimentalMaterial3Api::class) @Composable private fun SpaceViewTopBar( - currentSpace: SpaceRoom?, + state: SpaceState, onBackClick: () -> Unit, modifier: Modifier = Modifier, ) { + val currentSpace = state.currentSpace TopAppBar( modifier = modifier, navigationIcon = { @@ -159,6 +194,34 @@ private fun SpaceViewTopBar( } }, actions = { + var showMenu by remember { mutableStateOf(false) } + IconButton( + onClick = { showMenu = !showMenu } + ) { + Icon( + imageVector = CompoundIcons.OverflowVertical(), + contentDescription = null, + ) + } + DropdownMenu( + expanded = showMenu, + onDismissRequest = { showMenu = false } + ) { + DropdownMenuItem( + onClick = { + showMenu = false + state.eventSink(SpaceEvents.LeaveSpace) + }, + text = { Text(stringResource(id = CommonStrings.action_leave)) }, + leadingIcon = { + Icon( + imageVector = CompoundIcons.Leave(), + tint = ElementTheme.colors.iconSecondary, + contentDescription = null, + ) + } + ) + } }, ) } diff --git a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/leave/ConfirmingLeavingSpace.kt b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/leave/ConfirmingLeavingSpace.kt new file mode 100644 index 00000000000..397360a497a --- /dev/null +++ b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/leave/ConfirmingLeavingSpace.kt @@ -0,0 +1,18 @@ +/* + * 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.space.impl.leave + +import io.element.android.libraries.architecture.AsyncAction +import io.element.android.libraries.architecture.AsyncData +import io.element.android.libraries.matrix.api.spaces.SpaceRoom +import kotlinx.collections.immutable.ImmutableList + +data class ConfirmingLeavingSpace( + val spaceName: String?, + val roomsWhereUserIsTheOnlyAdmin: AsyncData>, +) : AsyncAction.Confirming diff --git a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/leave/ConfirmingLeavingSpaceProvider.kt b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/leave/ConfirmingLeavingSpaceProvider.kt new file mode 100644 index 00000000000..9641d7b6801 --- /dev/null +++ b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/leave/ConfirmingLeavingSpaceProvider.kt @@ -0,0 +1,48 @@ +/* + * 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.space.impl.leave + +import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import io.element.android.libraries.architecture.AsyncData +import io.element.android.libraries.matrix.api.room.join.JoinRule +import io.element.android.libraries.matrix.api.spaces.SpaceRoom +import io.element.android.libraries.previewutils.room.aSpaceRoom +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.persistentListOf + +class ConfirmingLeavingSpaceProvider : PreviewParameterProvider { + override val values: Sequence + get() = sequenceOf( + aConfirmingLeavingSpace(), + aConfirmingLeavingSpace( + roomsWhereUserIsTheOnlyAdmin = AsyncData.Success(persistentListOf()), + ), + aConfirmingLeavingSpace( + spaceName = null, + roomsWhereUserIsTheOnlyAdmin = AsyncData.Success( + persistentListOf( + aSpaceRoom(), + aSpaceRoom( + worldReadable = true, + ), + aSpaceRoom( + joinRule = JoinRule.Private, + ), + ) + ) + ), + ) +} + +fun aConfirmingLeavingSpace( + spaceName: String? = "Element Space", + roomsWhereUserIsTheOnlyAdmin: AsyncData> = AsyncData.Uninitialized, +) = ConfirmingLeavingSpace( + spaceName = spaceName, + roomsWhereUserIsTheOnlyAdmin = roomsWhereUserIsTheOnlyAdmin, +) diff --git a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/leave/LeaveSpaceBottomSheet.kt b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/leave/LeaveSpaceBottomSheet.kt new file mode 100644 index 00000000000..e91dc0f7c41 --- /dev/null +++ b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/leave/LeaveSpaceBottomSheet.kt @@ -0,0 +1,224 @@ +/* + * 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.space.impl.leave + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.heightIn +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.pluralStringResource +import androidx.compose.ui.res.stringResource +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.features.space.impl.R +import io.element.android.libraries.architecture.AsyncData +import io.element.android.libraries.designsystem.atomic.molecules.ButtonColumnMolecule +import io.element.android.libraries.designsystem.atomic.molecules.IconTitleSubtitleMolecule +import io.element.android.libraries.designsystem.components.BigIcon +import io.element.android.libraries.designsystem.components.async.AsyncFailure +import io.element.android.libraries.designsystem.components.async.AsyncLoading +import io.element.android.libraries.designsystem.components.avatar.Avatar +import io.element.android.libraries.designsystem.components.avatar.AvatarSize +import io.element.android.libraries.designsystem.components.avatar.AvatarType +import io.element.android.libraries.designsystem.preview.ElementPreview +import io.element.android.libraries.designsystem.preview.PreviewsDayNight +import io.element.android.libraries.designsystem.theme.components.Button +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.IconSource +import io.element.android.libraries.designsystem.theme.components.ModalBottomSheet +import io.element.android.libraries.designsystem.theme.components.Text +import io.element.android.libraries.designsystem.theme.components.TextButton +import io.element.android.libraries.matrix.api.room.join.JoinRule +import io.element.android.libraries.matrix.api.spaces.SpaceRoom +import io.element.android.libraries.matrix.ui.model.getAvatarData +import io.element.android.libraries.ui.strings.CommonPlurals +import io.element.android.libraries.ui.strings.CommonStrings + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun LeaveSpaceBottomSheet( + state: ConfirmingLeavingSpace, + onLeaveSpace: () -> Unit, + onDismiss: () -> Unit, + modifier: Modifier = Modifier, +) { + ModalBottomSheet( + modifier = modifier, + onDismissRequest = onDismiss, + ) { + LazyColumn { + item { + LeaveSpaceBottomSheetHeader(state) + } + when (state.roomsWhereUserIsTheOnlyAdmin) { + is AsyncData.Success -> { + // List rooms where the user is the only admin + state.roomsWhereUserIsTheOnlyAdmin.data.forEach { spaceRoom -> + item { + SpaceItem(room = spaceRoom) + } + } + } + is AsyncData.Failure -> item { + AsyncFailure( + throwable = state.roomsWhereUserIsTheOnlyAdmin.error, + onRetry = null, + ) + } + is AsyncData.Loading, + AsyncData.Uninitialized -> + item { + AsyncLoading() + } + } + item { + LeaveSpaceBottomSheetButtons( + showLeaveButton = state.roomsWhereUserIsTheOnlyAdmin is AsyncData.Success, + onLeaveSpace = onLeaveSpace, + onCancel = onDismiss, + ) + } + } + } +} + +@Composable +private fun LeaveSpaceBottomSheetHeader(state: ConfirmingLeavingSpace) { + IconTitleSubtitleMolecule( + modifier = Modifier.padding(top = 24.dp, bottom = 8.dp, start = 24.dp, end = 24.dp), + iconStyle = BigIcon.Style.AlertSolid, + title = stringResource( + R.string.screen_bottom_sheet_leave_space_title, + state.spaceName ?: stringResource(CommonStrings.common_space) + ), + subTitle = + if (state.roomsWhereUserIsTheOnlyAdmin is AsyncData.Success) { + if (state.roomsWhereUserIsTheOnlyAdmin.data.isEmpty()) { + stringResource(R.string.screen_bottom_sheet_leave_space_subtitle) + } else { + stringResource(R.string.screen_bottom_sheet_leave_space_subtitle_admin) + } + } else { + null + }, + ) +} + +@Composable +private fun LeaveSpaceBottomSheetButtons( + showLeaveButton: Boolean, + onLeaveSpace: () -> Unit, + onCancel: () -> Unit, +) { + ButtonColumnMolecule( + modifier = Modifier.padding(top = 32.dp, bottom = 16.dp, start = 16.dp, end = 16.dp) + ) { + if (showLeaveButton) { + Button( + modifier = Modifier.fillMaxWidth(), + text = stringResource(CommonStrings.action_leave), + leadingIcon = IconSource.Vector(CompoundIcons.Leave()), + onClick = onLeaveSpace, + destructive = true, + ) + } + TextButton( + modifier = Modifier.fillMaxWidth(), + text = stringResource(CommonStrings.action_cancel), + onClick = onCancel, + ) + } +} + +@Composable +private fun SpaceItem(room: SpaceRoom) { + Column { + Row( + modifier = Modifier + .fillMaxWidth() + .heightIn(min = 66.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Avatar( + modifier = Modifier.padding(horizontal = 16.dp), + avatarData = room.getAvatarData(AvatarSize.LeaveSpaceRoom), + avatarType = if (room.isSpace) AvatarType.Space() else AvatarType.Room(), + ) + Column { + Text( + modifier = Modifier.padding(end = 16.dp), + text = room.name ?: stringResource(CommonStrings.common_no_room_name), + color = ElementTheme.colors.textPrimary, + style = ElementTheme.typography.fontBodyLgMedium, + maxLines = 1, + ) + Row( + verticalAlignment = Alignment.CenterVertically, + ) { + if (room.joinRule == JoinRule.Private) { + // Picto for private + Icon( + modifier = Modifier + .size(16.dp) + .padding(end = 4.dp), + imageVector = CompoundIcons.LockSolid(), + contentDescription = null, + tint = ElementTheme.colors.iconTertiary, + ) + } else if (room.worldReadable) { + // Picto for world readable + Icon( + modifier = Modifier + .size(16.dp) + .padding(end = 4.dp), + imageVector = CompoundIcons.Public(), + contentDescription = null, + tint = ElementTheme.colors.iconTertiary, + ) + } + // Number of members + Text( + modifier = Modifier.padding(end = 16.dp), + text = pluralStringResource( + CommonPlurals.common_member_count, + room.numJoinedMembers, + room.numJoinedMembers + ), + color = ElementTheme.colors.textSecondary, + style = ElementTheme.typography.fontBodyMdRegular, + ) + } + } + } + HorizontalDivider( + modifier = Modifier.padding(start = 32.dp + AvatarSize.LeaveSpaceRoom.dp) + ) + } +} + +@PreviewsDayNight +@Composable +internal fun LeaveSpaceBottomSheetPreview( + @PreviewParameter(ConfirmingLeavingSpaceProvider::class) state: ConfirmingLeavingSpace, +) = ElementPreview { + LeaveSpaceBottomSheet( + state = state, + onLeaveSpace = {}, + onDismiss = {}, + ) +} diff --git a/features/space/impl/src/main/res/values/localazy.xml b/features/space/impl/src/main/res/values/localazy.xml new file mode 100644 index 00000000000..34f5a0209b5 --- /dev/null +++ b/features/space/impl/src/main/res/values/localazy.xml @@ -0,0 +1,6 @@ + + + "This will also remove you from all rooms in this space." + "This will also remove you from all rooms in this space, including those you’re the only administrator for:" + "Leave %1$s?" + diff --git a/features/space/impl/src/test/kotlin/io/element/android/features/space/impl/SpacePresenterTest.kt b/features/space/impl/src/test/kotlin/io/element/android/features/space/impl/SpacePresenterTest.kt index 0bcd1303ae9..03769870185 100644 --- a/features/space/impl/src/test/kotlin/io/element/android/features/space/impl/SpacePresenterTest.kt +++ b/features/space/impl/src/test/kotlin/io/element/android/features/space/impl/SpacePresenterTest.kt @@ -13,10 +13,15 @@ import com.google.common.truth.Truth.assertThat import io.element.android.features.invite.api.SeenInvitesStore import io.element.android.features.invite.test.InMemorySeenInvitesStore import io.element.android.features.space.api.SpaceEntryPoint +import io.element.android.features.space.impl.leave.ConfirmingLeavingSpace +import io.element.android.libraries.architecture.AsyncAction +import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.spaces.SpaceRoomList +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.FakeMatrixClient +import io.element.android.libraries.matrix.test.room.FakeBaseRoom 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 @@ -51,6 +56,7 @@ class SpacePresenterTest { assertThat(state.seenSpaceInvites).isEmpty() assertThat(state.hideInvitesAvatar).isFalse() assertThat(state.hasMoreToLoad).isTrue() + assertThat(state.leaveSpaceBottomSheetState).isEqualTo(AsyncAction.Uninitialized) advanceUntilIdle() paginateResult.assertions().isCalledOnce() } @@ -153,6 +159,129 @@ class SpacePresenterTest { } } + @Test + fun `present - leave space and cancel`() = runTest { + val fakeSpaceRoomList = FakeSpaceRoomList( + paginateResult = { Result.success(Unit) }, + ) + val presenter = createSpacePresenter( + client = FakeMatrixClient( + spaceService = FakeSpaceService( + spaceRoomListResult = { fakeSpaceRoomList }, + ), + ), + ) + presenter.test { + val state = awaitItem() + advanceUntilIdle() + assertThat(state.leaveSpaceBottomSheetState).isEqualTo(AsyncAction.Uninitialized) + state.eventSink(SpaceEvents.LeaveSpace) + val stateAfterStarting = awaitItem() + assertThat(stateAfterStarting.leaveSpaceBottomSheetState).isInstanceOf(ConfirmingLeavingSpace::class.java) + val shown = stateAfterStarting.leaveSpaceBottomSheetState as ConfirmingLeavingSpace + assertThat(shown.spaceName).isNull() + assertThat(shown.roomsWhereUserIsTheOnlyAdmin).isInstanceOf(AsyncData.Loading::class.java) + val stateAfterLoading = awaitItem() + val shownLoaded = stateAfterLoading.leaveSpaceBottomSheetState as ConfirmingLeavingSpace + assertThat(shownLoaded.roomsWhereUserIsTheOnlyAdmin.dataOrNull()!!).isEmpty() + stateAfterLoading.eventSink(SpaceEvents.CancelLeaveSpace) + val stateAfterCancel = awaitItem() + assertThat(stateAfterCancel.leaveSpaceBottomSheetState).isEqualTo(AsyncAction.Uninitialized) + } + } + + @Test + fun `present - leave space and confirm`() = runTest { + val fakeSpaceRoomList = FakeSpaceRoomList( + paginateResult = { Result.success(Unit) }, + ) + val leaveRoomLambda = lambdaRecorder> { + Result.success(Unit) + } + val presenter = createSpacePresenter( + client = FakeMatrixClient( + spaceService = FakeSpaceService( + spaceRoomListResult = { fakeSpaceRoomList }, + ), + ).apply { + givenGetRoomResult( + roomId = A_ROOM_ID, + result = FakeBaseRoom( + leaveRoomLambda = leaveRoomLambda, + ) + ) + }, + ) + presenter.test { + val state = awaitItem() + advanceUntilIdle() + assertThat(state.leaveSpaceBottomSheetState).isEqualTo(AsyncAction.Uninitialized) + state.eventSink(SpaceEvents.LeaveSpace) + val stateAfterStarting = awaitItem() + assertThat(stateAfterStarting.leaveSpaceBottomSheetState).isInstanceOf(ConfirmingLeavingSpace::class.java) + val shown = stateAfterStarting.leaveSpaceBottomSheetState as ConfirmingLeavingSpace + assertThat(shown.spaceName).isNull() + assertThat(shown.roomsWhereUserIsTheOnlyAdmin).isInstanceOf(AsyncData.Loading::class.java) + val stateAfterLoading = awaitItem() + val shownLoaded = stateAfterLoading.leaveSpaceBottomSheetState as ConfirmingLeavingSpace + assertThat(shownLoaded.roomsWhereUserIsTheOnlyAdmin.dataOrNull()!!).isEmpty() + stateAfterLoading.eventSink(SpaceEvents.LeaveSpace) + val stateLoading = awaitItem() + assertThat(stateLoading.leaveSpaceBottomSheetState).isEqualTo(AsyncAction.Loading) + val stateFinal = awaitItem() + assertThat(stateFinal.leaveSpaceBottomSheetState).isEqualTo(AsyncAction.Success(Unit)) + leaveRoomLambda.assertions().isCalledOnce() + } + } + + @Test + fun `present - leave space, confirm then failure`() = runTest { + val fakeSpaceRoomList = FakeSpaceRoomList( + paginateResult = { Result.success(Unit) }, + ) + val leaveRoomLambda = lambdaRecorder> { + Result.failure(AN_EXCEPTION) + } + val presenter = createSpacePresenter( + client = FakeMatrixClient( + spaceService = FakeSpaceService( + spaceRoomListResult = { fakeSpaceRoomList }, + ), + ).apply { + givenGetRoomResult( + roomId = A_ROOM_ID, + result = FakeBaseRoom( + leaveRoomLambda = leaveRoomLambda, + ) + ) + }, + ) + presenter.test { + val state = awaitItem() + advanceUntilIdle() + assertThat(state.leaveSpaceBottomSheetState).isEqualTo(AsyncAction.Uninitialized) + state.eventSink(SpaceEvents.LeaveSpace) + val stateAfterStarting = awaitItem() + assertThat(stateAfterStarting.leaveSpaceBottomSheetState).isInstanceOf(ConfirmingLeavingSpace::class.java) + val shown = stateAfterStarting.leaveSpaceBottomSheetState as ConfirmingLeavingSpace + assertThat(shown.spaceName).isNull() + assertThat(shown.roomsWhereUserIsTheOnlyAdmin).isInstanceOf(AsyncData.Loading::class.java) + val stateAfterLoading = awaitItem() + val shownLoaded = stateAfterLoading.leaveSpaceBottomSheetState as ConfirmingLeavingSpace + assertThat(shownLoaded.roomsWhereUserIsTheOnlyAdmin.dataOrNull()!!).isEmpty() + stateAfterLoading.eventSink(SpaceEvents.LeaveSpace) + val stateLoading = awaitItem() + assertThat(stateLoading.leaveSpaceBottomSheetState).isEqualTo(AsyncAction.Loading) + val stateError = awaitItem() + assertThat(stateError.leaveSpaceBottomSheetState).isEqualTo(AsyncAction.Failure(AN_EXCEPTION)) + leaveRoomLambda.assertions().isCalledOnce() + // Close error + stateError.eventSink(SpaceEvents.CancelLeaveSpace) + val stateFinal = awaitItem() + assertThat(stateFinal.leaveSpaceBottomSheetState).isEqualTo(AsyncAction.Uninitialized) + } + } + private fun createSpacePresenter( inputs: SpaceEntryPoint.Inputs = SpaceEntryPoint.Inputs(A_ROOM_ID), client: MatrixClient = FakeMatrixClient(), diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/AvatarSize.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/AvatarSize.kt index 9e9ebb61811..c39d5282a70 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/AvatarSize.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/AvatarSize.kt @@ -69,4 +69,5 @@ enum class AvatarSize(val dp: Dp) { OrganizationHeader(64.dp), SpaceHeader(64.dp), SpaceMember(24.dp), + LeaveSpaceRoom(32.dp), } diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/RoomMembershipObserver.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/RoomMembershipObserver.kt index 19d7fdaaf25..a4a718b2195 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/RoomMembershipObserver.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/RoomMembershipObserver.kt @@ -15,6 +15,7 @@ import kotlinx.coroutines.flow.asSharedFlow class RoomMembershipObserver { data class RoomMembershipUpdate( val roomId: RoomId, + val isSpace: Boolean, val isUserInRoom: Boolean, val change: MembershipChange, ) @@ -22,12 +23,23 @@ class RoomMembershipObserver { private val _updates = MutableSharedFlow(extraBufferCapacity = 10) val updates = _updates.asSharedFlow() - suspend fun notifyUserLeftRoom(roomId: RoomId, membershipBeforeLeft: CurrentUserMembership) { + suspend fun notifyUserLeftRoom( + roomId: RoomId, + isSpace: Boolean, + membershipBeforeLeft: CurrentUserMembership, + ) { val membershipChange = when (membershipBeforeLeft) { CurrentUserMembership.INVITED -> MembershipChange.INVITATION_REJECTED CurrentUserMembership.KNOCKED -> MembershipChange.KNOCK_RETRACTED else -> MembershipChange.LEFT } - _updates.emit(RoomMembershipUpdate(roomId, false, membershipChange)) + _updates.emit( + RoomMembershipUpdate( + roomId = roomId, + isSpace = isSpace, + isUserInRoom = false, + change = membershipChange, + ) + ) } } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustBaseRoom.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustBaseRoom.kt index 975185242b7..d7dbe2180c4 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustBaseRoom.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustBaseRoom.kt @@ -155,7 +155,11 @@ class RustBaseRoom( runCatchingExceptions { innerRoom.leave() }.onSuccess { - roomMembershipObserver.notifyUserLeftRoom(roomId, membershipBeforeLeft) + roomMembershipObserver.notifyUserLeftRoom( + roomId = roomId, + isSpace = roomInfoFlow.value.isSpace, + membershipBeforeLeft = membershipBeforeLeft, + ) } } diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/room/RustBaseRoomTest.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/room/RustBaseRoomTest.kt index e24eca53cb3..50d6c348b56 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/room/RustBaseRoomTest.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/room/RustBaseRoomTest.kt @@ -57,6 +57,7 @@ class RustBaseRoomTest { leaveRoomAndObserveMembershipChange(roomMembershipObserver, rustBaseRoom) { val membershipUpdate = awaitItem() assertThat(membershipUpdate.roomId).isEqualTo(rustBaseRoom.roomId) + assertThat(membershipUpdate.isSpace).isFalse() assertThat(membershipUpdate.isUserInRoom).isFalse() assertThat(membershipUpdate.change).isEqualTo(MembershipChange.LEFT) } @@ -77,6 +78,7 @@ class RustBaseRoomTest { leaveRoomAndObserveMembershipChange(roomMembershipObserver, rustBaseRoom) { val membershipUpdate = awaitItem() assertThat(membershipUpdate.roomId).isEqualTo(rustBaseRoom.roomId) + assertThat(membershipUpdate.isSpace).isFalse() assertThat(membershipUpdate.isUserInRoom).isFalse() assertThat(membershipUpdate.change).isEqualTo(MembershipChange.KNOCK_RETRACTED) } @@ -97,6 +99,7 @@ class RustBaseRoomTest { leaveRoomAndObserveMembershipChange(roomMembershipObserver, rustBaseRoom) { val membershipUpdate = awaitItem() assertThat(membershipUpdate.roomId).isEqualTo(rustBaseRoom.roomId) + assertThat(membershipUpdate.isSpace).isFalse() assertThat(membershipUpdate.isUserInRoom).isFalse() assertThat(membershipUpdate.change).isEqualTo(MembershipChange.INVITATION_REJECTED) } diff --git a/tests/uitests/src/test/snapshots/images/features.space.impl.leave_LeaveSpaceBottomSheet_Day_0_en.png b/tests/uitests/src/test/snapshots/images/features.space.impl.leave_LeaveSpaceBottomSheet_Day_0_en.png new file mode 100644 index 00000000000..c10c0fbb868 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.space.impl.leave_LeaveSpaceBottomSheet_Day_0_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2eb6538b19530d6f16dc8da06a7fca8ccd3eb70f5d15cea244eb1415825debe2 +size 15412 diff --git a/tests/uitests/src/test/snapshots/images/features.space.impl.leave_LeaveSpaceBottomSheet_Day_1_en.png b/tests/uitests/src/test/snapshots/images/features.space.impl.leave_LeaveSpaceBottomSheet_Day_1_en.png new file mode 100644 index 00000000000..e33900f240f --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.space.impl.leave_LeaveSpaceBottomSheet_Day_1_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:41ddfc522defb64e9a6cc23de2d2985f96fd162cf804bcf9776b2d1538837156 +size 24036 diff --git a/tests/uitests/src/test/snapshots/images/features.space.impl.leave_LeaveSpaceBottomSheet_Day_2_en.png b/tests/uitests/src/test/snapshots/images/features.space.impl.leave_LeaveSpaceBottomSheet_Day_2_en.png new file mode 100644 index 00000000000..3803d7b4d0f --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.space.impl.leave_LeaveSpaceBottomSheet_Day_2_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4b34df3256a8f2a95343559e1c4459d187ad96635b0cbc2bbfd3598b8cc5c991 +size 45533 diff --git a/tests/uitests/src/test/snapshots/images/features.space.impl.leave_LeaveSpaceBottomSheet_Night_0_en.png b/tests/uitests/src/test/snapshots/images/features.space.impl.leave_LeaveSpaceBottomSheet_Night_0_en.png new file mode 100644 index 00000000000..17df18888e0 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.space.impl.leave_LeaveSpaceBottomSheet_Night_0_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9c964f138e8d5974dba0bfaa12bc67e5b4cbf98970b61030fef4e7e4ff447ae8 +size 14605 diff --git a/tests/uitests/src/test/snapshots/images/features.space.impl.leave_LeaveSpaceBottomSheet_Night_1_en.png b/tests/uitests/src/test/snapshots/images/features.space.impl.leave_LeaveSpaceBottomSheet_Night_1_en.png new file mode 100644 index 00000000000..bdb9788b573 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.space.impl.leave_LeaveSpaceBottomSheet_Night_1_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a0fa98927b349c685d45beec0c0fbab90f2ff4d9241f60cbe552c6bc96abbd4d +size 22890 diff --git a/tests/uitests/src/test/snapshots/images/features.space.impl.leave_LeaveSpaceBottomSheet_Night_2_en.png b/tests/uitests/src/test/snapshots/images/features.space.impl.leave_LeaveSpaceBottomSheet_Night_2_en.png new file mode 100644 index 00000000000..98428f23b0b --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.space.impl.leave_LeaveSpaceBottomSheet_Night_2_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7c3da92f1cc0a07a149b1ba51bb0f799c2388743693f3d1855879b0bb304a70f +size 44474 diff --git a/tests/uitests/src/test/snapshots/images/features.space.impl_SpaceView_Day_0_en.png b/tests/uitests/src/test/snapshots/images/features.space.impl_SpaceView_Day_0_en.png index 59cd1f1de1b..b3c73f00661 100644 --- a/tests/uitests/src/test/snapshots/images/features.space.impl_SpaceView_Day_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.space.impl_SpaceView_Day_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7e69f830b7f15de531fc46efc02964fb641a4e3754b5108fb8020e97bbfb4b56 -size 15698 +oid sha256:8a2644ca78865f478b5ae60179541892b917c871e8343d7b7fdafd749283ac92 +size 15965 diff --git a/tests/uitests/src/test/snapshots/images/features.space.impl_SpaceView_Day_1_en.png b/tests/uitests/src/test/snapshots/images/features.space.impl_SpaceView_Day_1_en.png index 393434d50b9..ecc52676613 100644 --- a/tests/uitests/src/test/snapshots/images/features.space.impl_SpaceView_Day_1_en.png +++ b/tests/uitests/src/test/snapshots/images/features.space.impl_SpaceView_Day_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b5fba00f44ab63c07978ec0143f2f1fefc8fd1d78aed22a367e831c105b806c7 -size 17306 +oid sha256:62db54a66db98db83ac23a3a8983a800727b29f246c51710d5ac8d598cf1d2e6 +size 17588 diff --git a/tests/uitests/src/test/snapshots/images/features.space.impl_SpaceView_Day_2_en.png b/tests/uitests/src/test/snapshots/images/features.space.impl_SpaceView_Day_2_en.png index 2fa59b0adaf..db5b9ba2807 100644 --- a/tests/uitests/src/test/snapshots/images/features.space.impl_SpaceView_Day_2_en.png +++ b/tests/uitests/src/test/snapshots/images/features.space.impl_SpaceView_Day_2_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6c7b387db1b0637745dfc93559b9afa3c637e5a492bf19f6f1d0dbe802ed73bb -size 47156 +oid sha256:9e03823d2aa571758f0e2ef6b97e02fa7676dbdf9e15367d025f7227c1ff0bd0 +size 47423 diff --git a/tests/uitests/src/test/snapshots/images/features.space.impl_SpaceView_Day_3_en.png b/tests/uitests/src/test/snapshots/images/features.space.impl_SpaceView_Day_3_en.png index 8cb2f6776ce..e956d9134a3 100644 --- a/tests/uitests/src/test/snapshots/images/features.space.impl_SpaceView_Day_3_en.png +++ b/tests/uitests/src/test/snapshots/images/features.space.impl_SpaceView_Day_3_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9892ec0c06bfa6e0b3a95fd35efa97e44f2e1f6264a324d6fdd994847f1830d1 -size 45897 +oid sha256:10b3e091e451b7e490b6d3e922c9a86fe9723205f63c829d317c994177e48aea +size 46114 diff --git a/tests/uitests/src/test/snapshots/images/features.space.impl_SpaceView_Day_4_en.png b/tests/uitests/src/test/snapshots/images/features.space.impl_SpaceView_Day_4_en.png new file mode 100644 index 00000000000..42a4be72235 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.space.impl_SpaceView_Day_4_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c52c8f64e34a5f60bfd62fc4b5b30b2d00e8a8e4c51dc2218eb4c1cf23a4f7aa +size 25737 diff --git a/tests/uitests/src/test/snapshots/images/features.space.impl_SpaceView_Night_0_en.png b/tests/uitests/src/test/snapshots/images/features.space.impl_SpaceView_Night_0_en.png index a2c2d2043d4..8ef6b19312d 100644 --- a/tests/uitests/src/test/snapshots/images/features.space.impl_SpaceView_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.space.impl_SpaceView_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:91ad7e392e21afe93606956e8e7eab7f0b075574e782ff15e873a71fc3ab16ab -size 15637 +oid sha256:a592a81e73aa88bcc4a929f4c389c124f52ea9367c3e9138144308bed991c392 +size 15862 diff --git a/tests/uitests/src/test/snapshots/images/features.space.impl_SpaceView_Night_1_en.png b/tests/uitests/src/test/snapshots/images/features.space.impl_SpaceView_Night_1_en.png index 67cdaf4a391..f0e46c310e3 100644 --- a/tests/uitests/src/test/snapshots/images/features.space.impl_SpaceView_Night_1_en.png +++ b/tests/uitests/src/test/snapshots/images/features.space.impl_SpaceView_Night_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9d50be9f20ed0e75e337d790edb329996abf9e626591882088817301338fadb6 -size 17213 +oid sha256:a1ef6902533edf5033e4918d1b726785826cd9f956c7c32448cb1522050f4116 +size 17438 diff --git a/tests/uitests/src/test/snapshots/images/features.space.impl_SpaceView_Night_2_en.png b/tests/uitests/src/test/snapshots/images/features.space.impl_SpaceView_Night_2_en.png index 6e55a5816ee..5e2a278f024 100644 --- a/tests/uitests/src/test/snapshots/images/features.space.impl_SpaceView_Night_2_en.png +++ b/tests/uitests/src/test/snapshots/images/features.space.impl_SpaceView_Night_2_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d76f7b10d8c88f39ab36581be35dd2a5ca5e7c3a92c69b96d4f3ec87f2e93468 -size 46564 +oid sha256:e4c1551b0495b74dd7ae37823d81c06b308acdab7263a77a570b0f4b30419119 +size 46784 diff --git a/tests/uitests/src/test/snapshots/images/features.space.impl_SpaceView_Night_3_en.png b/tests/uitests/src/test/snapshots/images/features.space.impl_SpaceView_Night_3_en.png index 298a35ce39b..4839949cccb 100644 --- a/tests/uitests/src/test/snapshots/images/features.space.impl_SpaceView_Night_3_en.png +++ b/tests/uitests/src/test/snapshots/images/features.space.impl_SpaceView_Night_3_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:057ceb522ff354d0d7e8b73d59eb53c559f9c22a2727d24744dc6a34b9daaa55 -size 45034 +oid sha256:414aa2f5bf18fd2b5e1943c199e0c480dbf953649d870e6da613ff421afa43ac +size 45251 diff --git a/tests/uitests/src/test/snapshots/images/features.space.impl_SpaceView_Night_4_en.png b/tests/uitests/src/test/snapshots/images/features.space.impl_SpaceView_Night_4_en.png new file mode 100644 index 00000000000..5aa06b14724 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.space.impl_SpaceView_Night_4_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:abc328bad965daa20caa8e88ac77ff4f07882eef75a484ebd10ff7c1fe2d6fa0 +size 24621 diff --git a/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.avatar_Avatar_Avatars_105_en.png b/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.avatar_Avatar_Avatars_105_en.png new file mode 100644 index 00000000000..d19cf477f7c --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.avatar_Avatar_Avatars_105_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:157bbbc4511b29b531d85f465c473b7ac186af5f697dfd6ffb6da5a02bb0ca02 +size 16960 diff --git a/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.avatar_Avatar_Avatars_106_en.png b/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.avatar_Avatar_Avatars_106_en.png new file mode 100644 index 00000000000..59c1e3c6ec0 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.avatar_Avatar_Avatars_106_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cf17a6c4cd39061bd34e616ec10440d47826a6af81033b99c3a1fb1060e95563 +size 16237 diff --git a/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.avatar_Avatar_Avatars_107_en.png b/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.avatar_Avatar_Avatars_107_en.png new file mode 100644 index 00000000000..4e70c3836da --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.avatar_Avatar_Avatars_107_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:992430d8297f30891aeaf61b054660fc4a4d1c125b06f7df4acc860beda79611 +size 18890 diff --git a/tools/localazy/config.json b/tools/localazy/config.json index 0aac7e51af9..505162992bc 100644 --- a/tools/localazy/config.json +++ b/tools/localazy/config.json @@ -199,6 +199,12 @@ "screen\\.security_and_privacy\\..*" ] }, + { + "name" : ":features:space:impl", + "includeRegex" : [ + "screen\\.bottom_sheet\\.leave_space\\..*" + ] + }, { "name" : ":features:userprofile:shared", "includeRegex" : [