design(home) : use enterAlwaysScrollBehavior for the RoomListFiltersView

This commit is contained in:
ganfra 2025-10-23 16:41:43 +02:00
parent 0498c3e4ed
commit eb2b527236
4 changed files with 90 additions and 64 deletions

View file

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

View file

@ -25,6 +25,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.shadow
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.platform.LocalLayoutDirection
@ -143,7 +144,7 @@ private fun HomeScaffold(
} }
val appBarState = rememberTopAppBarState() val appBarState = rememberTopAppBarState()
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior(appBarState) val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior(appBarState)
val snackbarHostState = rememberSnackbarHostState(snackbarMessage = state.snackbarMessage) val snackbarHostState = rememberSnackbarHostState(snackbarMessage = state.snackbarMessage)
val roomListState: RoomListState = state.roomListState val roomListState: RoomListState = state.roomListState
@ -171,7 +172,7 @@ private fun HomeScaffold(
}, },
scrollBehavior = scrollBehavior, scrollBehavior = scrollBehavior,
displayMenuItems = state.displayActions, displayMenuItems = state.displayActions,
displayFilters = roomListState.displayFilters && state.currentHomeNavigationBarItem == HomeNavigationBarItem.Chats, displayFilters = state.displayRoomListFilters,
filtersState = roomListState.filtersState, filtersState = roomListState.filtersState,
canReportBug = state.canReportBug, canReportBug = state.canReportBug,
modifier = if (state.isSpaceFeatureEnabled) { modifier = if (state.isSpaceFeatureEnabled) {

View file

@ -16,7 +16,6 @@ import androidx.compose.foundation.layout.statusBarsPadding
import androidx.compose.foundation.pager.VerticalPager import androidx.compose.foundation.pager.VerticalPager
import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.TopAppBarScrollBehavior import androidx.compose.material3.TopAppBarScrollBehavior
import androidx.compose.material3.rememberTopAppBarState import androidx.compose.material3.rememberTopAppBarState
@ -44,18 +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.RoomListFiltersView
import io.element.android.features.home.impl.filters.aRoomListFiltersState import io.element.android.features.home.impl.filters.aRoomListFiltersState
import io.element.android.libraries.designsystem.atomic.atoms.RedIndicatorAtom 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.Avatar
import io.element.android.libraries.designsystem.components.avatar.AvatarSize 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.avatar.AvatarType
import io.element.android.libraries.designsystem.modifiers.backgroundVerticalGradient import io.element.android.libraries.designsystem.modifiers.backgroundVerticalGradient
import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.preview.PreviewsDayNight
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.DropdownMenu
import io.element.android.libraries.designsystem.theme.components.DropdownMenuItem 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.Icon
import io.element.android.libraries.designsystem.theme.components.IconButton 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.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.SessionId
import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.user.MatrixUser import io.element.android.libraries.matrix.api.user.MatrixUser
@ -81,45 +82,9 @@ fun HomeTopBar(
onAccountSwitch: (SessionId) -> Unit, onAccountSwitch: (SessionId) -> Unit,
scrollBehavior: TopAppBarScrollBehavior, scrollBehavior: TopAppBarScrollBehavior,
displayMenuItems: Boolean, displayMenuItems: Boolean,
canReportBug: Boolean,
displayFilters: Boolean, displayFilters: Boolean,
filtersState: RoomListFiltersState, filtersState: RoomListFiltersState,
canReportBug: Boolean,
modifier: Modifier = Modifier,
) {
DefaultHomeTopBar(
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 DefaultHomeTopBar(
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, modifier: Modifier = Modifier,
) { ) {
Column(modifier) { Column(modifier) {
@ -138,6 +103,7 @@ private fun DefaultHomeTopBar(
modifier = Modifier.semantics { modifier = Modifier.semantics {
heading() heading()
}, },
style = ElementTheme.typography.aliasScreenTitle,
text = title, text = title,
) )
}, },
@ -152,7 +118,7 @@ private fun DefaultHomeTopBar(
actions = { actions = {
if (displayMenuItems) { if (displayMenuItems) {
IconButton( IconButton(
onClick = onSearchClick, onClick = onToggleSearch,
) { ) {
Icon( Icon(
imageVector = CompoundIcons.Search(), imageVector = CompoundIcons.Search(),
@ -209,15 +175,17 @@ private fun DefaultHomeTopBar(
} }
} }
}, },
scrollBehavior = scrollBehavior, //scrollBehavior = scrollBehavior,
// We need a 16dp left padding : 4dp default padding + 8dp IconButton padding + 4dp extra padding // We need a 16dp left padding : 4dp default padding + 8dp IconButton padding + 4dp extra padding
windowInsets = WindowInsets(4.dp), windowInsets = WindowInsets(left = 4.dp),
) )
if (displayFilters) { if (displayFilters) {
RoomListFiltersView( TopAppBarScrollBehaviorLayout(scrollBehavior = scrollBehavior) {
state = filtersState, RoomListFiltersView(
modifier = Modifier.padding(bottom = 16.dp) state = filtersState,
) modifier = Modifier.padding(bottom = 16.dp)
)
}
} }
} }
} }
@ -270,9 +238,11 @@ private fun AccountIcon(
isCurrentAccount: Boolean, isCurrentAccount: Boolean,
showAvatarIndicator: Boolean, showAvatarIndicator: Boolean,
onClick: () -> Unit, onClick: () -> Unit,
modifier: Modifier = Modifier,
) { ) {
val testTag = if (isCurrentAccount) Modifier.testTag(TestTags.homeScreenSettings) else Modifier
IconButton( IconButton(
modifier = if (isCurrentAccount) Modifier.testTag(TestTags.homeScreenSettings) else Modifier, modifier = modifier.then(testTag),
onClick = onClick, onClick = onClick,
) { ) {
Box { Box {
@ -298,20 +268,20 @@ private fun AccountIcon(
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@PreviewsDayNight @PreviewsDayNight
@Composable @Composable
internal fun DefaultHomeTopBarPreview() = ElementPreview { internal fun HomeTopBarPreview() = ElementPreview {
DefaultHomeTopBar( HomeTopBar(
title = stringResource(R.string.screen_roomlist_main_space_title), title = stringResource(R.string.screen_roomlist_main_space_title),
currentUserAndNeighbors = persistentListOf(MatrixUser(UserId("@id:domain"), "Alice")), currentUserAndNeighbors = persistentListOf(MatrixUser(UserId("@id:domain"), "Alice")),
showAvatarIndicator = false, showAvatarIndicator = false,
areSearchResultsDisplayed = false, areSearchResultsDisplayed = false,
scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior(rememberTopAppBarState()), scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState()),
onOpenSettings = {}, onOpenSettings = {},
onAccountSwitch = {}, onAccountSwitch = {},
onSearchClick = {}, onToggleSearch = {},
displayMenuItems = true, displayMenuItems = true,
canReportBug = true,
displayFilters = true, displayFilters = true,
filtersState = aRoomListFiltersState(), filtersState = aRoomListFiltersState(),
canReportBug = true,
onMenuActionClick = {}, onMenuActionClick = {},
) )
} }
@ -319,20 +289,20 @@ internal fun DefaultHomeTopBarPreview() = ElementPreview {
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@PreviewsDayNight @PreviewsDayNight
@Composable @Composable
internal fun DefaultHomeTopBarWithIndicatorPreview() = ElementPreview { internal fun HomeTopBarWithIndicatorPreview() = ElementPreview {
DefaultHomeTopBar( HomeTopBar(
title = stringResource(R.string.screen_roomlist_main_space_title), title = stringResource(R.string.screen_roomlist_main_space_title),
currentUserAndNeighbors = persistentListOf(MatrixUser(UserId("@id:domain"), "Alice")), currentUserAndNeighbors = persistentListOf(MatrixUser(UserId("@id:domain"), "Alice")),
showAvatarIndicator = true, showAvatarIndicator = true,
areSearchResultsDisplayed = false, areSearchResultsDisplayed = false,
scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior(rememberTopAppBarState()), scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState()),
onOpenSettings = {}, onOpenSettings = {},
onAccountSwitch = {}, onAccountSwitch = {},
onSearchClick = {}, onToggleSearch = {},
displayMenuItems = true, displayMenuItems = true,
canReportBug = true,
displayFilters = true, displayFilters = true,
filtersState = aRoomListFiltersState(), filtersState = aRoomListFiltersState(),
canReportBug = true,
onMenuActionClick = {}, onMenuActionClick = {},
) )
} }
@ -340,20 +310,20 @@ internal fun DefaultHomeTopBarWithIndicatorPreview() = ElementPreview {
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@PreviewsDayNight @PreviewsDayNight
@Composable @Composable
internal fun DefaultHomeTopBarMultiAccountPreview() = ElementPreview { internal fun HomeTopBarMultiAccountPreview() = ElementPreview {
DefaultHomeTopBar( HomeTopBar(
title = stringResource(R.string.screen_roomlist_main_space_title), title = stringResource(R.string.screen_roomlist_main_space_title),
currentUserAndNeighbors = aMatrixUserList().take(3).toImmutableList(), currentUserAndNeighbors = aMatrixUserList().take(3).toImmutableList(),
showAvatarIndicator = false, showAvatarIndicator = false,
areSearchResultsDisplayed = false, areSearchResultsDisplayed = false,
scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior(rememberTopAppBarState()), scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState()),
onOpenSettings = {}, onOpenSettings = {},
onAccountSwitch = {}, onAccountSwitch = {},
onSearchClick = {}, onToggleSearch = {},
displayMenuItems = true, displayMenuItems = true,
canReportBug = true,
displayFilters = true, displayFilters = true,
filtersState = aRoomListFiltersState(), filtersState = aRoomListFiltersState(),
canReportBug = true,
onMenuActionClick = {}, onMenuActionClick = {},
) )
} }

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())
}
}
)
}
}