Merge pull request #5599 from element-hq/feature/fga/home_topbar_2

Design : update Home TopBar and RoomList Filters
This commit is contained in:
ganfra 2025-10-23 18:29:40 +02:00 committed by GitHub
commit 0745f308c5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
50 changed files with 333 additions and 288 deletions

View file

@ -32,5 +32,6 @@ data class HomeState(
val eventSink: (HomeEvents) -> Unit,
) {
val displayActions = currentHomeNavigationBarItem == HomeNavigationBarItem.Chats
val displayRoomListFilters = currentHomeNavigationBarItem == HomeNavigationBarItem.Chats && roomListState.displayFilters
val showNavigationBar = isSpaceFeatureEnabled && homeSpacesState.spaceRooms.isNotEmpty()
}

View file

@ -18,6 +18,7 @@ import androidx.compose.foundation.layout.calculateStartPadding
import androidx.compose.foundation.layout.consumeWindowInsets
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.rememberTopAppBarState
@ -39,9 +40,9 @@ import dev.chrisbanes.haze.materials.HazeMaterials
import dev.chrisbanes.haze.rememberHazeState
import io.element.android.compound.theme.ElementTheme
import io.element.android.compound.tokens.generated.CompoundIcons
import io.element.android.features.home.impl.components.HomeTopBar
import io.element.android.features.home.impl.components.RoomListContentView
import io.element.android.features.home.impl.components.RoomListMenuAction
import io.element.android.features.home.impl.components.RoomListTopBar
import io.element.android.features.home.impl.model.RoomListRoomSummary
import io.element.android.features.home.impl.roomlist.RoomListContextMenu
import io.element.android.features.home.impl.roomlist.RoomListDeclineInviteMenu
@ -62,6 +63,7 @@ import io.element.android.libraries.designsystem.theme.components.Scaffold
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarHost
import io.element.android.libraries.designsystem.utils.snackbar.rememberSnackbarHostState
import io.element.android.libraries.matrix.api.core.RoomId
import kotlinx.coroutines.launch
@Composable
fun HomeView(
@ -143,7 +145,7 @@ private fun HomeScaffold(
}
val appBarState = rememberTopAppBarState()
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior(appBarState)
val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior(appBarState)
val snackbarHostState = rememberSnackbarHostState(snackbarMessage = state.snackbarMessage)
val roomListState: RoomListState = state.roomListState
@ -154,11 +156,13 @@ private fun HomeScaffold(
}
val hazeState = rememberHazeState()
val roomsLazyListState = rememberLazyListState()
val spacesLazyListState = rememberLazyListState()
Scaffold(
modifier = modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
topBar = {
RoomListTopBar(
HomeTopBar(
title = stringResource(state.currentHomeNavigationBarItem.labelRes),
currentUserAndNeighbors = state.currentUserAndNeighbors,
showAvatarIndicator = state.showAvatarIndicator,
@ -171,7 +175,7 @@ private fun HomeScaffold(
},
scrollBehavior = scrollBehavior,
displayMenuItems = state.displayActions,
displayFilters = roomListState.displayFilters && state.currentHomeNavigationBarItem == HomeNavigationBarItem.Chats,
displayFilters = state.displayRoomListFilters,
filtersState = roomListState.filtersState,
canReportBug = state.canReportBug,
modifier = if (state.isSpaceFeatureEnabled) {
@ -180,41 +184,39 @@ private fun HomeScaffold(
style = HazeMaterials.thick(),
)
} else {
Modifier
.background(ElementTheme.colors.bgCanvasDefault)
Modifier.background(ElementTheme.colors.bgCanvasDefault)
}
)
},
bottomBar = {
if (state.showNavigationBar) {
NavigationBar(
containerColor = Color.Transparent,
modifier = Modifier
.hazeEffect(
state = hazeState,
style = HazeMaterials.thick(),
)
) {
HomeNavigationBarItem.entries.forEach { item ->
val isSelected = state.currentHomeNavigationBarItem == item
NavigationBarItem(
selected = isSelected,
onClick = {
state.eventSink(HomeEvents.SelectHomeNavigationBarItem(item))
},
icon = {
NavigationBarIcon(
imageVector = item.icon(isSelected),
)
},
label = {
NavigationBarText(
text = stringResource(item.labelRes),
)
val coroutineScope = rememberCoroutineScope()
HomeBottomBar(
currentHomeNavigationBarItem = state.currentHomeNavigationBarItem,
onItemClick = { item ->
// scroll to top if selecting the same item
if (item == state.currentHomeNavigationBarItem) {
val lazyListStateTarget = when (item) {
HomeNavigationBarItem.Chats -> roomsLazyListState
HomeNavigationBarItem.Spaces -> spacesLazyListState
}
)
}
}
coroutineScope.launch {
if (lazyListStateTarget.firstVisibleItemIndex > 10) {
lazyListStateTarget.scrollToItem(10)
}
// Also reset the scrollBehavior height offset as it's not triggered by programmatic scrolls
scrollBehavior.state.heightOffset = 0f
lazyListStateTarget.animateScrollToItem(0)
}
} else {
state.eventSink(HomeEvents.SelectHomeNavigationBarItem(item))
}
},
modifier = Modifier.hazeEffect(
state = hazeState,
style = HazeMaterials.thick(),
)
)
}
},
content = { padding ->
@ -223,6 +225,7 @@ private fun HomeScaffold(
RoomListContentView(
contentState = roomListState.contentState,
filtersState = roomListState.filtersState,
lazyListState = roomsLazyListState,
hideInvitesAvatars = roomListState.hideInvitesAvatars,
eventSink = roomListState.eventSink,
onSetUpRecoveryClick = onSetUpRecoveryClick,
@ -260,6 +263,7 @@ private fun HomeScaffold(
.consumeWindowInsets(padding)
.hazeSource(state = hazeState),
state = state.homeSpacesState,
lazyListState = spacesLazyListState,
onSpaceClick = { spaceId ->
onRoomClick(spaceId)
}
@ -283,6 +287,38 @@ private fun HomeScaffold(
)
}
@Composable
private fun HomeBottomBar(
currentHomeNavigationBarItem: HomeNavigationBarItem,
onItemClick: (HomeNavigationBarItem) -> Unit,
modifier: Modifier = Modifier,
) {
NavigationBar(
containerColor = Color.Transparent,
modifier = modifier
) {
HomeNavigationBarItem.entries.forEach { item ->
val isSelected = currentHomeNavigationBarItem == item
NavigationBarItem(
selected = isSelected,
onClick = {
onItemClick(item)
},
icon = {
NavigationBarIcon(
imageVector = item.icon(isSelected),
)
},
label = {
NavigationBarText(
text = stringResource(item.labelRes),
)
}
)
}
}
}
internal fun RoomListRoomSummary.contentType() = displayType.ordinal
@PreviewsDayNight

View file

@ -10,14 +10,12 @@ package io.element.android.features.home.impl.components
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.statusBarsPadding
import androidx.compose.foundation.pager.VerticalPager
import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.TopAppBarScrollBehavior
import androidx.compose.material3.rememberTopAppBarState
@ -32,7 +30,6 @@ import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.heading
@ -46,22 +43,20 @@ import io.element.android.features.home.impl.filters.RoomListFiltersState
import io.element.android.features.home.impl.filters.RoomListFiltersView
import io.element.android.features.home.impl.filters.aRoomListFiltersState
import io.element.android.libraries.designsystem.atomic.atoms.RedIndicatorAtom
import io.element.android.libraries.designsystem.components.TopAppBarScrollBehaviorLayout
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.modifiers.backgroundVerticalGradient
import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.designsystem.text.applyScaleDown
import io.element.android.libraries.designsystem.text.toSp
import io.element.android.libraries.designsystem.theme.aliasScreenTitle
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.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.MediumTopAppBar
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.SessionId
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.user.MatrixUser
@ -76,7 +71,7 @@ import kotlinx.collections.immutable.toImmutableList
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun RoomListTopBar(
fun HomeTopBar(
title: String,
currentUserAndNeighbors: ImmutableList<MatrixUser>,
showAvatarIndicator: Boolean,
@ -87,169 +82,113 @@ fun RoomListTopBar(
onAccountSwitch: (SessionId) -> Unit,
scrollBehavior: TopAppBarScrollBehavior,
displayMenuItems: Boolean,
canReportBug: Boolean,
displayFilters: Boolean,
filtersState: RoomListFiltersState,
canReportBug: Boolean,
modifier: Modifier = Modifier,
) {
DefaultRoomListTopBar(
title = title,
currentUserAndNeighbors = currentUserAndNeighbors,
showAvatarIndicator = showAvatarIndicator,
areSearchResultsDisplayed = areSearchResultsDisplayed,
onOpenSettings = onOpenSettings,
onAccountSwitch = onAccountSwitch,
onSearchClick = onToggleSearch,
onMenuActionClick = onMenuActionClick,
scrollBehavior = scrollBehavior,
displayMenuItems = displayMenuItems,
displayFilters = displayFilters,
filtersState = filtersState,
canReportBug = canReportBug,
modifier = modifier,
)
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun DefaultRoomListTopBar(
title: String,
currentUserAndNeighbors: ImmutableList<MatrixUser>,
showAvatarIndicator: Boolean,
areSearchResultsDisplayed: Boolean,
scrollBehavior: TopAppBarScrollBehavior,
onOpenSettings: () -> Unit,
onAccountSwitch: (SessionId) -> Unit,
onSearchClick: () -> Unit,
onMenuActionClick: (RoomListMenuAction) -> Unit,
displayMenuItems: Boolean,
displayFilters: Boolean,
filtersState: RoomListFiltersState,
canReportBug: Boolean,
modifier: Modifier = Modifier,
) {
val collapsedFraction = scrollBehavior.state.collapsedFraction
Box(modifier = modifier) {
val collapsedTitleTextStyle = ElementTheme.typography.aliasScreenTitle
val expandedTitleTextStyle = ElementTheme.typography.fontHeadingLgBold.copy(
// Due to a limitation of MediumTopAppBar, and to avoid the text to be truncated,
// ensure that the font size will never be bigger than 28.dp.
fontSize = 28.dp.applyScaleDown().toSp()
)
MaterialTheme(
colorScheme = ElementTheme.materialColors,
shapes = MaterialTheme.shapes,
typography = ElementTheme.materialTypography.copy(
headlineSmall = expandedTitleTextStyle,
titleLarge = collapsedTitleTextStyle
Column(modifier) {
TopAppBar(
modifier = Modifier
.backgroundVerticalGradient(
isVisible = !areSearchResultsDisplayed,
)
.statusBarsPadding(),
colors = TopAppBarDefaults.topAppBarColors(
containerColor = Color.Transparent,
scrolledContainerColor = Color.Transparent,
),
) {
Column {
MediumTopAppBar(
modifier = Modifier
.backgroundVerticalGradient(
isVisible = !areSearchResultsDisplayed,
)
.statusBarsPadding(),
colors = TopAppBarDefaults.mediumTopAppBarColors(
containerColor = Color.Transparent,
scrolledContainerColor = Color.Transparent,
),
title = {
Text(
modifier = Modifier.semantics {
heading()
},
text = title,
)
title = {
Text(
modifier = Modifier.semantics {
heading()
},
navigationIcon = {
NavigationIcon(
currentUserAndNeighbors = currentUserAndNeighbors,
showAvatarIndicator = showAvatarIndicator,
onAccountSwitch = onAccountSwitch,
onClick = onOpenSettings,
style = ElementTheme.typography.aliasScreenTitle,
text = title,
)
},
navigationIcon = {
NavigationIcon(
currentUserAndNeighbors = currentUserAndNeighbors,
showAvatarIndicator = showAvatarIndicator,
onAccountSwitch = onAccountSwitch,
onClick = onOpenSettings,
)
},
actions = {
if (displayMenuItems) {
IconButton(
onClick = onToggleSearch,
) {
Icon(
imageVector = CompoundIcons.Search(),
contentDescription = stringResource(CommonStrings.action_search),
)
},
actions = {
if (displayMenuItems) {
IconButton(
onClick = onSearchClick,
) {
Icon(
imageVector = CompoundIcons.Search(),
contentDescription = stringResource(CommonStrings.action_search),
}
if (RoomListConfig.HAS_DROP_DOWN_MENU) {
var showMenu by remember { mutableStateOf(false) }
IconButton(
onClick = { showMenu = !showMenu }
) {
Icon(
imageVector = CompoundIcons.OverflowVertical(),
contentDescription = null,
)
}
DropdownMenu(
expanded = showMenu,
onDismissRequest = { showMenu = false }
) {
if (RoomListConfig.SHOW_INVITE_MENU_ITEM) {
DropdownMenuItem(
onClick = {
showMenu = false
onMenuActionClick(RoomListMenuAction.InviteFriends)
},
text = { Text(stringResource(id = CommonStrings.action_invite)) },
leadingIcon = {
Icon(
imageVector = CompoundIcons.ShareAndroid(),
tint = ElementTheme.colors.iconSecondary,
contentDescription = null,
)
}
)
}
if (RoomListConfig.HAS_DROP_DOWN_MENU) {
var showMenu by remember { mutableStateOf(false) }
IconButton(
onClick = { showMenu = !showMenu }
) {
Icon(
imageVector = CompoundIcons.OverflowVertical(),
contentDescription = null,
)
}
DropdownMenu(
expanded = showMenu,
onDismissRequest = { showMenu = false }
) {
if (RoomListConfig.SHOW_INVITE_MENU_ITEM) {
DropdownMenuItem(
onClick = {
showMenu = false
onMenuActionClick(RoomListMenuAction.InviteFriends)
},
text = { Text(stringResource(id = CommonStrings.action_invite)) },
leadingIcon = {
Icon(
imageVector = CompoundIcons.ShareAndroid(),
tint = ElementTheme.colors.iconSecondary,
contentDescription = null,
)
}
if (RoomListConfig.SHOW_REPORT_PROBLEM_MENU_ITEM && canReportBug) {
DropdownMenuItem(
onClick = {
showMenu = false
onMenuActionClick(RoomListMenuAction.ReportBug)
},
text = { Text(stringResource(id = CommonStrings.common_report_a_problem)) },
leadingIcon = {
Icon(
imageVector = CompoundIcons.ChatProblem(),
tint = ElementTheme.colors.iconSecondary,
contentDescription = null,
)
}
if (RoomListConfig.SHOW_REPORT_PROBLEM_MENU_ITEM && canReportBug) {
DropdownMenuItem(
onClick = {
showMenu = false
onMenuActionClick(RoomListMenuAction.ReportBug)
},
text = { Text(stringResource(id = CommonStrings.common_report_a_problem)) },
leadingIcon = {
Icon(
imageVector = CompoundIcons.ChatProblem(),
tint = ElementTheme.colors.iconSecondary,
contentDescription = null,
)
}
)
}
}
)
}
}
},
scrollBehavior = scrollBehavior,
windowInsets = WindowInsets(0.dp),
)
if (displayFilters) {
RoomListFiltersView(
state = filtersState,
modifier = Modifier.padding(bottom = 16.dp)
)
}
}
},
// We want a 16dp left padding for the navigationIcon :
// 4dp from default TopAppBarHorizontalPadding
// 8dp from AccountIcon default padding (because of IconButton)
// 4dp extra padding using left insets
windowInsets = WindowInsets(left = 4.dp),
)
if (displayFilters) {
TopAppBarScrollBehaviorLayout(scrollBehavior = scrollBehavior) {
RoomListFiltersView(
state = filtersState,
modifier = Modifier.padding(bottom = 16.dp)
)
}
}
HorizontalDivider(
modifier = Modifier
.fillMaxWidth()
.alpha(collapsedFraction)
.align(Alignment.BottomCenter),
color = ElementTheme.materialColors.outlineVariant,
)
}
}
@ -301,9 +240,11 @@ private fun AccountIcon(
isCurrentAccount: Boolean,
showAvatarIndicator: Boolean,
onClick: () -> Unit,
modifier: Modifier = Modifier,
) {
val testTag = if (isCurrentAccount) Modifier.testTag(TestTags.homeScreenSettings) else Modifier
IconButton(
modifier = if (isCurrentAccount) Modifier.testTag(TestTags.homeScreenSettings) else Modifier,
modifier = modifier.then(testTag),
onClick = onClick,
) {
Box {
@ -329,20 +270,20 @@ private fun AccountIcon(
@OptIn(ExperimentalMaterial3Api::class)
@PreviewsDayNight
@Composable
internal fun DefaultRoomListTopBarPreview() = ElementPreview {
DefaultRoomListTopBar(
internal fun HomeTopBarPreview() = ElementPreview {
HomeTopBar(
title = stringResource(R.string.screen_roomlist_main_space_title),
currentUserAndNeighbors = persistentListOf(MatrixUser(UserId("@id:domain"), "Alice")),
showAvatarIndicator = false,
areSearchResultsDisplayed = false,
scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior(rememberTopAppBarState()),
scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState()),
onOpenSettings = {},
onAccountSwitch = {},
onSearchClick = {},
onToggleSearch = {},
displayMenuItems = true,
canReportBug = true,
displayFilters = true,
filtersState = aRoomListFiltersState(),
canReportBug = true,
onMenuActionClick = {},
)
}
@ -350,20 +291,20 @@ internal fun DefaultRoomListTopBarPreview() = ElementPreview {
@OptIn(ExperimentalMaterial3Api::class)
@PreviewsDayNight
@Composable
internal fun DefaultRoomListTopBarWithIndicatorPreview() = ElementPreview {
DefaultRoomListTopBar(
internal fun HomeTopBarWithIndicatorPreview() = ElementPreview {
HomeTopBar(
title = stringResource(R.string.screen_roomlist_main_space_title),
currentUserAndNeighbors = persistentListOf(MatrixUser(UserId("@id:domain"), "Alice")),
showAvatarIndicator = true,
areSearchResultsDisplayed = false,
scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior(rememberTopAppBarState()),
scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState()),
onOpenSettings = {},
onAccountSwitch = {},
onSearchClick = {},
onToggleSearch = {},
displayMenuItems = true,
canReportBug = true,
displayFilters = true,
filtersState = aRoomListFiltersState(),
canReportBug = true,
onMenuActionClick = {},
)
}
@ -371,20 +312,20 @@ internal fun DefaultRoomListTopBarWithIndicatorPreview() = ElementPreview {
@OptIn(ExperimentalMaterial3Api::class)
@PreviewsDayNight
@Composable
internal fun DefaultRoomListTopBarMultiAccountPreview() = ElementPreview {
DefaultRoomListTopBar(
internal fun HomeTopBarMultiAccountPreview() = ElementPreview {
HomeTopBar(
title = stringResource(R.string.screen_roomlist_main_space_title),
currentUserAndNeighbors = aMatrixUserList().take(3).toImmutableList(),
showAvatarIndicator = false,
areSearchResultsDisplayed = false,
scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior(rememberTopAppBarState()),
scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState()),
onOpenSettings = {},
onAccountSwitch = {},
onSearchClick = {},
onToggleSearch = {},
displayMenuItems = true,
canReportBug = true,
displayFilters = true,
filtersState = aRoomListFiltersState(),
canReportBug = true,
onMenuActionClick = {},
)
}

View file

@ -18,6 +18,7 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.runtime.Composable
@ -60,6 +61,7 @@ import kotlinx.collections.immutable.ImmutableList
fun RoomListContentView(
contentState: RoomListContentState,
filtersState: RoomListFiltersState,
lazyListState: LazyListState,
hideInvitesAvatars: Boolean,
eventSink: (RoomListEvents) -> Unit,
onSetUpRecoveryClick: () -> Unit,
@ -97,6 +99,7 @@ fun RoomListContentView(
onSetUpRecoveryClick = onSetUpRecoveryClick,
onConfirmRecoveryKeyClick = onConfirmRecoveryKeyClick,
onRoomClick = onRoomClick,
lazyListState = lazyListState,
contentPadding = contentPadding,
)
}
@ -176,6 +179,7 @@ private fun RoomsView(
onConfirmRecoveryKeyClick: () -> Unit,
onRoomClick: (RoomListRoomSummary) -> Unit,
contentPadding: PaddingValues,
lazyListState: LazyListState,
modifier: Modifier = Modifier,
) {
if (state.summaries.isEmpty() && filtersState.hasAnyFilterSelected) {
@ -192,6 +196,7 @@ private fun RoomsView(
onConfirmRecoveryKeyClick = onConfirmRecoveryKeyClick,
onRoomClick = onRoomClick,
contentPadding = contentPadding,
lazyListState = lazyListState,
modifier = modifier.fillMaxSize(),
)
}
@ -206,9 +211,9 @@ private fun RoomsViewList(
onConfirmRecoveryKeyClick: () -> Unit,
onRoomClick: (RoomListRoomSummary) -> Unit,
contentPadding: PaddingValues,
lazyListState: LazyListState,
modifier: Modifier = Modifier,
) {
val lazyListState = rememberLazyListState()
val visibleRange by remember {
derivedStateOf {
val layoutInfo = lazyListState.layoutInfo
@ -343,6 +348,7 @@ internal fun RoomListContentViewPreview(@PreviewParameter(RoomListContentStatePr
onConfirmRecoveryKeyClick = {},
onRoomClick = {},
onCreateRoomClick = {},
lazyListState = rememberLazyListState(),
contentPadding = PaddingValues(0.dp),
)
}

View file

@ -8,6 +8,8 @@
package io.element.android.features.home.impl.spaces
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.PreviewParameter
@ -26,10 +28,14 @@ import kotlinx.collections.immutable.toImmutableList
@Composable
fun HomeSpacesView(
state: HomeSpacesState,
lazyListState: LazyListState,
onSpaceClick: (RoomId) -> Unit,
modifier: Modifier = Modifier,
) {
LazyColumn(modifier) {
LazyColumn(
modifier = modifier,
state = lazyListState
) {
val space = state.space
when (space) {
CurrentSpace.Root -> {
@ -77,6 +83,7 @@ internal fun HomeSpacesViewPreview(
) = ElementPreview {
HomeSpacesView(
state = state,
lazyListState = rememberLazyListState(),
onSpaceClick = {},
modifier = Modifier,
)

View file

@ -0,0 +1,54 @@
/*
* 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.libraries.designsystem.components
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Surface
import androidx.compose.material3.TopAppBarScrollBehavior
import androidx.compose.material3.contentColorFor
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.UiComposable
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.Layout
import io.element.android.compound.theme.ElementTheme
/**
* A layout that measures its content to set the height offset limit of a [TopAppBarScrollBehavior].
* It places the content according to the current height offset of the scroll behavior.
*
*/
@ExperimentalMaterial3Api
@Composable
fun TopAppBarScrollBehaviorLayout(
scrollBehavior: TopAppBarScrollBehavior,
modifier: Modifier = Modifier,
backgroundColor: Color = ElementTheme.colors.bgCanvasDefault,
contentColor: Color = contentColorFor(backgroundColor),
content: @Composable @UiComposable () -> Unit,
) {
Surface(
modifier = modifier,
color = backgroundColor,
contentColor = contentColor
) {
Layout(
content = content,
measurePolicy = { measurables, constraints ->
val placeable = measurables.first().measure(constraints)
val contentHeight = placeable.height.toFloat()
scrollBehavior.state.heightOffsetLimit = -contentHeight
val heightOffset = scrollBehavior.state.heightOffset
val layoutHeight = (contentHeight + heightOffset).toInt()
layout(placeable.width, layoutHeight) {
placeable.place(0, heightOffset.toInt())
}
}
)
}
}

View file

@ -81,11 +81,11 @@ class KonsistPreviewTest {
"BackgroundVerticalGradientDisabledPreview",
"BackgroundVerticalGradientPreview",
"ColorAliasesPreview",
"DefaultRoomListTopBarMultiAccountPreview",
"DefaultRoomListTopBarWithIndicatorPreview",
"FocusedEventPreview",
"GradientFloatingActionButtonCircleShapePreview",
"HeaderFooterPageScrollablePreview",
"HomeTopBarMultiAccountPreview",
"HomeTopBarWithIndicatorPreview",
"IconsOtherPreview",
"MarkdownTextComposerEditPreview",
"MatrixBadgeAtomInfoPreview",

View file

@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:b9595d23fae3fb23bf1a893744c3e28f99b163091d374dabd65d24dbde69af6c
size 25617

View file

@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:fd357c3f6b143b3dcc4f8a23cd0c8e34b801709630a2c31406b4563e928f64ed
size 22757

View file

@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:b9b7f028749ba909fbeb0c8c2a81ccc6337b3f9c7a8af20e5492c938b1ac8d7a
size 25962

View file

@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:13b6b3cd99a2561e5116c5dd9950fdb1db61cf3a5a83014c67900bc455f0b53c
size 23171

View file

@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:9c8add737c25478dc3be8fc638661fb03be922702136d4ae83bfcfca4287e5c1
size 25709

View file

@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:9b330c24f0a118665aa3f6eb68b2573deed4167cd3ddbbbe08eed8ddc27c639d
size 22814

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:6992a2fbaebed88e22c58e393d086bbcba50afd822ac869b9e27dc68ed3a493e
size 22007

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:67a87f2231268e26e06e62aada18c4c62e8ee53e1eb1ddc538f8c7e98881acbc
size 20256

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:0fdbe7e7ab22f44cc60a04582d5a5159055d4af3af0d3d2a7cd012f48abd9592
size 22443

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:96942af60064898a853268f8aa8103f04a836332fef09fbfa83ad01cccae54d3
size 20651

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:e38f16c942bcfb31103b5e80d168a5425f181632ea4440a054aa4d0985bd335a
size 22106

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:677c21f745e4b9c7984f6c8fe3f84a27fabe6aeaf5f8cac82bb5ac692bd6a797
size 20308

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:b9d9c029f8aa66d921be752b3bd5c70d00aa80b8dc6fed343a4ab7b9be5a187f
size 125045
oid sha256:a3530f2d4ba4b189c7e40c19548fe1d44fea844505ed411cd476eb41add5aac5
size 122437

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:6a249702f230f3dc0c9ee097fc11496df64c11436680e3f790d1fbf9051c4119
size 65098
oid sha256:60f321d5352e1966b44e13b4b9e56da3f2cfd14e5eb4a468b61b1939799d364a
size 61182

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:440881dfdccf466ac78d3486f05a16acf32c65cd9a926e51f9a03c8df153a996
size 33572
oid sha256:dd257b64d9e91bedebcf4552e8b93d357304954ad844b7f7d52bc76fc1543747
size 29901

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:4e7a170db66cbdae26a12cba6d221264097733e09fb868974ce2f29dbf4f942d
size 28313
oid sha256:4e3e455fbe6e3a10ec1578e01dc3f10c3b2b0688f99a1855319143c8fc7044a8
size 25355

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:a1e803f9818ffed85454a4d7c02d2b5fa63e9fc6280c521cddfd8253225ed790
size 89858
oid sha256:bf7d32047c608340c06edfb8ade3cc8b92858eaa763fedafa915c16439657fc2
size 88205

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:86eb0a74576e1023a0e79e2c42083efa0ab7e59f57688f431cdb825937106d03
size 83133
oid sha256:abf95575cbd55470c798accf988bc6a509daea71eded4108857595993873df4e
size 81038

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:d85ed502c37c8486c9b7e235ebb639bb7a6ddcdfec388b48ec1626ca45d7fd95
size 51335
oid sha256:c9e019924f41de0c16f1f403944b8fe390afa65d1ad604db3dd0baadbc30db5b
size 50953

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:6a249702f230f3dc0c9ee097fc11496df64c11436680e3f790d1fbf9051c4119
size 65098
oid sha256:60f321d5352e1966b44e13b4b9e56da3f2cfd14e5eb4a468b61b1939799d364a
size 61182

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:6a249702f230f3dc0c9ee097fc11496df64c11436680e3f790d1fbf9051c4119
size 65098
oid sha256:60f321d5352e1966b44e13b4b9e56da3f2cfd14e5eb4a468b61b1939799d364a
size 61182

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:3df1fc44ef9b4c71349aefaa06a09583b25b5baa2765c572fde2579d543c7dc8
size 62134
oid sha256:93b7d7b0d7df69921015ece40ae3ab859c81cd3e78828143f660a481a72b73e2
size 62305

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:08f1583ccded2046d10fd19890bcc70b9fedf0efe310318a6a6209bded37b423
size 52241
oid sha256:ef2b3724cdd28175824554f800019fa663be341f0854751b50321e9b73203b6a
size 54275

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:6a249702f230f3dc0c9ee097fc11496df64c11436680e3f790d1fbf9051c4119
size 65098
oid sha256:60f321d5352e1966b44e13b4b9e56da3f2cfd14e5eb4a468b61b1939799d364a
size 61182

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:e609fcb03b38d2265b91f6cccbd91092b050c34f979116a20e1d2f72c9fb7d9c
size 52933
oid sha256:6bb7a5a23947cd3a311c8d9526508c121beb4e0ee37f337bf8361845be10d172
size 54415

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:58536ac6068bce6399048fb5a082791ce4d2d74dc9e39d96e6483c818067a3b1
size 52741
oid sha256:11e9f28e6131eae66e268b72daab6d404d1f1f8b0eba78eb11d4376284eb11e3
size 54220

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:a5eb9e2d9245b31509ecd22351cc11b6d5fe27dcad76ab1eab36d692cf0fed2b
size 50942
oid sha256:26bfe68b4448aa83e19f196d8633eeb74883064db319aab355a5e755b7cbb56d
size 52439

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:a6c524b3c587eea43d5d753757fe42434ee751709559851ec9e0ca283c6f2236
size 82960
oid sha256:196cad6f5c3d01d52c88713526848d7905b59b49866f27f6e420df0b6b8f9e73
size 80857

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:ea48147772f8fa99aca0345a5a98f8d2ea6fd26793edab55f7d920a7e3f56414
size 60213
oid sha256:7e9b4b3247cf2f540866286de61760a45956d8179891aeb669b4763bdd29393d
size 57993

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:c065e34ccdcb98cf347177057029fb052986e680eb3a0ce0d3ba1110f893a6ed
size 29213
oid sha256:496855b1b3186adcad8b35895ee51776dce00a8d3229b8dccac4d6e4482bdaff
size 26577

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:aeecc6082c03bbb7fc3d5d2cff9c145fa6b1b2da253fc5d9d7df4b3b49705f67
size 23513
oid sha256:8b98678dc90bce52ab2293e5102794b4fc3b51fc667a6c061d771ea90212a0f2
size 22034

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:3911b9f33b862f1fa47ac80420f1ded92d8c7c304a0b0af8c7132033eca63da1
size 84486
oid sha256:a7ff4652d94dec6bc98b3f1dea0d182576c7bfc72e39f232d224046e6492b55e
size 83764

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:58917137b3500639edcd3308f665cb9111450bf0c34fe07880076f43725ae145
size 78202
oid sha256:cb1d9b5143703ec9a8035e11374c7e56ce4d3f836005632a109e5556b141c59b
size 77098

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:82748aa6dee71f50a9a1eaa1f34e2966126c4b13c4dc2a2a53c5588f8ba83f50
size 46455
oid sha256:8f0e5430bcd399d5a39c3d92e7551243f0e15bc315f9ec6ded43829d4672308c
size 47186

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:ea48147772f8fa99aca0345a5a98f8d2ea6fd26793edab55f7d920a7e3f56414
size 60213
oid sha256:7e9b4b3247cf2f540866286de61760a45956d8179891aeb669b4763bdd29393d
size 57993

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:ea48147772f8fa99aca0345a5a98f8d2ea6fd26793edab55f7d920a7e3f56414
size 60213
oid sha256:7e9b4b3247cf2f540866286de61760a45956d8179891aeb669b4763bdd29393d
size 57993

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:494864f07f4ca2ef426f611842f580fa6d2c5d04c61385acd649e50ee49e9405
size 58119
oid sha256:a8db3006e7ea4826022d47a4ae37dab2a7ddd064d4cc2c48b44deca0f158541c
size 59301

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:e3d89f2829ddb05c63da3f4afcadf55e70a8c70b7df46d0e04eba35932fe947d
size 49455
oid sha256:1b6fc1c78e05737b3231172b2b72b532b769ae9928ddd6674a864bee56566f6e
size 52623

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:ea48147772f8fa99aca0345a5a98f8d2ea6fd26793edab55f7d920a7e3f56414
size 60213
oid sha256:7e9b4b3247cf2f540866286de61760a45956d8179891aeb669b4763bdd29393d
size 57993

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:0493d1a09ba7d41e0ef0b66ef4068528af281cfaa9f7187d64d17316ca97d2fd
size 50038
oid sha256:957b8712456f13d83857616e2ffab05c5d60f50e6b39037bb47af0d272c53b02
size 51863

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:4215bf2ac9870ec1d6f3832adc8e8bfd2ce4d1ab2c1ef5584a0a934e880969be
size 49887
oid sha256:beac305c3cc0661633873276a99798b1fa693342bd4ab9424bc44759a1f745ec
size 51695

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:9977db4bcc333f7fa0f662e8cc73170fe96a90bcb545f5471cb6e107f159f943
size 48083
oid sha256:aba9a16cabbe67f28512721740e268bebd93b3084efd93e8de7eed251fd1ffe0
size 49849

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:21cbfa635df606c3fa17345a1ad1055e47f2265b059cb10580dfc464c694870e
size 78085
oid sha256:101d0425024c30f2dce8b6d91ed2416fc115c4bf3719fd6c46093705ba4ef772
size 76985