diff --git a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/HomeState.kt b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/HomeState.kt index fdaf01e9aa..b81cf79db1 100644 --- a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/HomeState.kt +++ b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/HomeState.kt @@ -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() } diff --git a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/HomeView.kt b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/HomeView.kt index 2b407bb49b..32afbaf284 100644 --- a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/HomeView.kt +++ b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/HomeView.kt @@ -25,6 +25,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.shadow import androidx.compose.ui.graphics.Color import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.platform.LocalLayoutDirection @@ -143,7 +144,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 @@ -171,7 +172,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) { diff --git a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/components/HomeTopBar.kt b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/components/HomeTopBar.kt index e40c24cddc..b936d90da8 100644 --- a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/components/HomeTopBar.kt +++ b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/components/HomeTopBar.kt @@ -16,7 +16,6 @@ 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.TopAppBar import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.TopAppBarScrollBehavior 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.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.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.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 @@ -81,45 +82,9 @@ fun HomeTopBar( onAccountSwitch: (SessionId) -> Unit, scrollBehavior: TopAppBarScrollBehavior, displayMenuItems: Boolean, + canReportBug: Boolean, displayFilters: Boolean, 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, - 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, ) { Column(modifier) { @@ -138,6 +103,7 @@ private fun DefaultHomeTopBar( modifier = Modifier.semantics { heading() }, + style = ElementTheme.typography.aliasScreenTitle, text = title, ) }, @@ -152,7 +118,7 @@ private fun DefaultHomeTopBar( actions = { if (displayMenuItems) { IconButton( - onClick = onSearchClick, + onClick = onToggleSearch, ) { Icon( 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 - windowInsets = WindowInsets(4.dp), + windowInsets = WindowInsets(left = 4.dp), ) if (displayFilters) { - RoomListFiltersView( - state = filtersState, - modifier = Modifier.padding(bottom = 16.dp) - ) + TopAppBarScrollBehaviorLayout(scrollBehavior = scrollBehavior) { + RoomListFiltersView( + state = filtersState, + modifier = Modifier.padding(bottom = 16.dp) + ) + } } } } @@ -270,9 +238,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 { @@ -298,20 +268,20 @@ private fun AccountIcon( @OptIn(ExperimentalMaterial3Api::class) @PreviewsDayNight @Composable -internal fun DefaultHomeTopBarPreview() = ElementPreview { - DefaultHomeTopBar( +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 = {}, ) } @@ -319,20 +289,20 @@ internal fun DefaultHomeTopBarPreview() = ElementPreview { @OptIn(ExperimentalMaterial3Api::class) @PreviewsDayNight @Composable -internal fun DefaultHomeTopBarWithIndicatorPreview() = ElementPreview { - DefaultHomeTopBar( +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 = {}, ) } @@ -340,20 +310,20 @@ internal fun DefaultHomeTopBarWithIndicatorPreview() = ElementPreview { @OptIn(ExperimentalMaterial3Api::class) @PreviewsDayNight @Composable -internal fun DefaultHomeTopBarMultiAccountPreview() = ElementPreview { - DefaultHomeTopBar( +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 = {}, ) } diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/TopAppBarScrollBehaviorLayout.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/TopAppBarScrollBehaviorLayout.kt new file mode 100644 index 0000000000..f4c62ca974 --- /dev/null +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/TopAppBarScrollBehaviorLayout.kt @@ -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()) + } + } + ) + } +}