diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt index 427ab8d10a..b845953ec8 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt @@ -16,6 +16,9 @@ package io.element.android.features.messages.impl +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.expandVertically +import androidx.compose.animation.shrinkVertically import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement @@ -33,6 +36,7 @@ import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.statusBars import androidx.compose.foundation.layout.width +import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.MaterialTheme @@ -101,6 +105,7 @@ import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.designsystem.theme.components.TopAppBar import io.element.android.libraries.designsystem.utils.KeepScreenOn import io.element.android.libraries.designsystem.utils.OnLifecycleEvent +import io.element.android.libraries.designsystem.utils.isScrollingUp 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.UserId @@ -375,6 +380,7 @@ private fun MessagesViewContent( }, content = { paddingValues -> Box(modifier = Modifier.padding(paddingValues)) { + val timelineLazyListState = rememberLazyListState() TimelineView( state = state.timelineState, typingNotificationState = state.typingNotificationState, @@ -389,8 +395,17 @@ private fun MessagesViewContent( onReadReceiptClick = onReadReceiptClick, forceJumpToBottomVisibility = forceJumpToBottomVisibility, onJoinCallClick = onJoinCallClick, + lazyListState = timelineLazyListState, ) - PinnedMessagesBannerView(state = state.pinnedMessagesBannerState) + AnimatedVisibility( + visible = state.pinnedMessagesBannerState.displayBanner && timelineLazyListState.isScrollingUp(), + enter = expandVertically(), + exit = shrinkVertically(), + ) { + PinnedMessagesBannerView( + state = state.pinnedMessagesBannerState, + ) + } } }, sheetContent = { subcomposing: Boolean -> diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/banner/PinnedMessagesBannerView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/banner/PinnedMessagesBannerView.kt index 38e3562c5f..dbae6a6133 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/banner/PinnedMessagesBannerView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/banner/PinnedMessagesBannerView.kt @@ -32,12 +32,15 @@ import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width 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.runtime.LaunchedEffect import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.drawBehind @@ -63,8 +66,6 @@ fun PinnedMessagesBannerView( state: PinnedMessagesBannerState, modifier: Modifier = Modifier, ) { - if (!state.displayBanner) return - val borderColor = ElementTheme.colors.pinnedMessageBannerBorder Row( modifier = modifier @@ -199,6 +200,6 @@ private fun PinnedMessageItem( @Composable fun PinnedMessagesBannerViewPreview(@PreviewParameter(PinnedMessagesBannerStateProvider::class) state: PinnedMessagesBannerState) = ElementPreview { PinnedMessagesBannerView( - state = state + state = state, ) } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineView.kt index eff2019a11..b640d9d450 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineView.kt @@ -90,6 +90,7 @@ fun TimelineView( onReadReceiptClick: (TimelineItem.Event) -> Unit, onJoinCallClick: () -> Unit, modifier: Modifier = Modifier, + lazyListState: LazyListState = rememberLazyListState(), forceJumpToBottomVisibility: Boolean = false ) { fun clearFocusRequestState() { @@ -109,7 +110,6 @@ fun TimelineView( } val context = LocalContext.current - val lazyListState = rememberLazyListState() // Disable reverse layout when TalkBack is enabled to avoid incorrect ordering issues seen in the current Compose UI version val useReverseLayout = remember { val accessibilityManager = context.getSystemService(AccessibilityManager::class.java) diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/LazyListState.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/LazyListState.kt new file mode 100644 index 0000000000..7e1eb53461 --- /dev/null +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/LazyListState.kt @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.designsystem.utils + +import androidx.compose.foundation.lazy.LazyListState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue + +/** + * Returns whether the lazy list is currently scrolling up. + */ +@Composable +fun LazyListState.isScrollingUp(): Boolean { + var previousIndex by remember(this) { mutableIntStateOf(firstVisibleItemIndex) } + var previousScrollOffset by remember(this) { mutableIntStateOf(firstVisibleItemScrollOffset) } + return remember(this) { + derivedStateOf { + if (previousIndex != firstVisibleItemIndex) { + previousIndex > firstVisibleItemIndex + } else { + previousScrollOffset >= firstVisibleItemScrollOffset + }.also { + previousIndex = firstVisibleItemIndex + previousScrollOffset = firstVisibleItemScrollOffset + } + } + }.value +}