From 4a94e36b3e0b68491c3db5532cff7fa70e1a80d0 Mon Sep 17 00:00:00 2001 From: ganfra Date: Mon, 2 Feb 2026 19:38:32 +0100 Subject: [PATCH] Complete SpaceFiltersView ui --- .../spacefilters/SpaceFiltersStateProvider.kt | 25 +++- .../impl/spacefilters/SpaceFiltersView.kt | 131 +++++++++++++++++- 2 files changed, 151 insertions(+), 5 deletions(-) diff --git a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/spacefilters/SpaceFiltersStateProvider.kt b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/spacefilters/SpaceFiltersStateProvider.kt index f6c5cccef4..3c64b93e40 100644 --- a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/spacefilters/SpaceFiltersStateProvider.kt +++ b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/spacefilters/SpaceFiltersStateProvider.kt @@ -8,6 +8,7 @@ package io.element.android.features.home.impl.spacefilters import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import io.element.android.libraries.matrix.api.core.RoomAlias import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.spaces.SpaceServiceFilter import io.element.android.libraries.previewutils.room.aSpaceRoom @@ -33,9 +34,24 @@ fun anUnselectedSpaceFiltersState( fun aSelectingSpaceFiltersState( availableFilters: List = listOf( - aSpaceServiceFilter(displayName = "Work"), - aSpaceServiceFilter(displayName = "Personal", roomId = RoomId("!personal:example.com")), - aSpaceServiceFilter(displayName = "Gaming", roomId = RoomId("!gaming:example.com")), + aSpaceServiceFilter( + displayName = "Work", + canonicalAlias = RoomAlias("#work:example.com"), + ), + aSpaceServiceFilter( + displayName = "Personal", + roomId = RoomId("!personal:example.com"), + ), + aSpaceServiceFilter( + displayName = "Projects", + roomId = RoomId("!projects:example.com"), + canonicalAlias = RoomAlias("#projects:example.com"), + level = 1, + ), + aSpaceServiceFilter( + displayName = "Gaming", + roomId = RoomId("!gaming:example.com"), + ), ), eventSink: (SpaceFiltersEvent.Selecting) -> Unit = {}, ) = SpaceFiltersState.Selecting( @@ -54,10 +70,11 @@ fun aSelectedSpaceFiltersState( fun aSpaceServiceFilter( displayName: String = "Space", roomId: RoomId = RoomId("!space:example.com"), + canonicalAlias: RoomAlias? = null, level: Int = 0, descendants: List = emptyList(), ) = SpaceServiceFilter( - spaceRoom = aSpaceRoom(displayName = displayName, roomId = roomId), + spaceRoom = aSpaceRoom(displayName = displayName, roomId = roomId, canonicalAlias = canonicalAlias), level = level, descendants = descendants, ) diff --git a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/spacefilters/SpaceFiltersView.kt b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/spacefilters/SpaceFiltersView.kt index a9250a74e2..a8a6e0a23f 100644 --- a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/spacefilters/SpaceFiltersView.kt +++ b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/spacefilters/SpaceFiltersView.kt @@ -7,18 +7,147 @@ package io.element.android.features.home.impl.spacefilters +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.navigationBarsPadding +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.systemBarsPadding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.rememberModalBottomSheetState 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.setValue +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.unit.dp +import io.element.android.compound.theme.ElementTheme +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.ModalBottomSheet +import io.element.android.libraries.designsystem.theme.components.Text +import io.element.android.libraries.matrix.api.spaces.SpaceServiceFilter +import io.element.android.libraries.matrix.ui.model.getAvatarData +@OptIn(ExperimentalMaterial3Api::class) @Composable fun SpaceFiltersView( state: SpaceFiltersState, modifier: Modifier = Modifier ) { - // TODO + val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true) + var showSheet by remember { mutableStateOf(false) } + + LaunchedEffect(state) { + when (state) { + is SpaceFiltersState.Selecting -> showSheet = true + else -> { + sheetState.hide() + showSheet = false + } + } + } + + Box(modifier = modifier) { + if (showSheet && state is SpaceFiltersState.Selecting) { + ModalBottomSheet( + modifier = Modifier + .systemBarsPadding() + .navigationBarsPadding(), + sheetState = sheetState, + onDismissRequest = { state.eventSink(SpaceFiltersEvent.Selecting.Cancel) }, + ) { + SpaceFiltersBottomSheetContent( + filters = state.availableFilters, + onFilterSelected = { filter -> + state.eventSink(SpaceFiltersEvent.Selecting.SelectFilter(filter)) + } + ) + } + } + } +} + +@Composable +private fun SpaceFiltersBottomSheetContent( + filters: List, + onFilterSelected: (SpaceServiceFilter) -> Unit, + modifier: Modifier = Modifier +) { + Column( + modifier = modifier + .fillMaxWidth() + .padding(bottom = 16.dp) + ) { + Text( + modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp), + text = "Your spaces", + style = ElementTheme.typography.fontBodyLgMedium, + ) + Spacer(modifier = Modifier.height(8.dp)) + LazyColumn { + items(filters) { filter -> + SpaceFilterItem( + filter = filter, + onClick = { onFilterSelected(filter) } + ) + } + } + } +} + +@Composable +private fun SpaceFilterItem( + filter: SpaceServiceFilter, + onClick: () -> Unit, + modifier: Modifier = Modifier +) { + val spaceRoom = filter.spaceRoom + val supportingText = spaceRoom.canonicalAlias?.value + + Row( + modifier = modifier + .fillMaxWidth() + .clickable(onClick = onClick) + .padding(horizontal = 16.dp, vertical = 12.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + // Level-based indentation + Spacer(modifier = Modifier.width((16 * filter.level).dp)) + Avatar( + avatarData = spaceRoom.getAvatarData(AvatarSize.RoomSelectRoomListItem), + avatarType = AvatarType.Space(), + ) + Spacer(modifier = Modifier.width(16.dp)) + Column { + Text( + text = spaceRoom.displayName, + style = ElementTheme.typography.fontBodyLgMedium, + color = ElementTheme.colors.textPrimary, + ) + if (supportingText != null) { + Text( + text = supportingText, + style = ElementTheme.typography.fontBodyMdRegular, + color = ElementTheme.colors.textSecondary, + ) + } + } + } } @PreviewsDayNight