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 32afbaf284..69e9e2d0bd 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 @@ -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 @@ -25,7 +26,6 @@ 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 @@ -40,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.HomeTopBar 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 @@ -63,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( @@ -155,6 +156,8 @@ private fun HomeScaffold( } val hazeState = rememberHazeState() + val roomsLazyListState = rememberLazyListState() + val spacesLazyListState = rememberLazyListState() Scaffold( modifier = modifier.nestedScroll(scrollBehavior.nestedScrollConnection), @@ -181,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 -> @@ -224,6 +225,7 @@ private fun HomeScaffold( RoomListContentView( contentState = roomListState.contentState, filtersState = roomListState.filtersState, + lazyListState = roomsLazyListState, hideInvitesAvatars = roomListState.hideInvitesAvatars, eventSink = roomListState.eventSink, onSetUpRecoveryClick = onSetUpRecoveryClick, @@ -261,6 +263,7 @@ private fun HomeScaffold( .consumeWindowInsets(padding) .hazeSource(state = hazeState), state = state.homeSpacesState, + lazyListState = spacesLazyListState, onSpaceClick = { spaceId -> onRoomClick(spaceId) } @@ -284,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 diff --git a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/components/RoomListContentView.kt b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/components/RoomListContentView.kt index 34d036b204..4546849112 100644 --- a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/components/RoomListContentView.kt +++ b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/components/RoomListContentView.kt @@ -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), ) } diff --git a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/spaces/HomeSpacesView.kt b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/spaces/HomeSpacesView.kt index 66e5403b35..bc1f474956 100644 --- a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/spaces/HomeSpacesView.kt +++ b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/spaces/HomeSpacesView.kt @@ -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, )