Implement scroll to top in friends and conversations screens

This commit is contained in:
2025-03-29 22:31:48 +03:00
parent f1892670da
commit 5b5e8f8446
7 changed files with 71 additions and 8 deletions
@@ -36,7 +36,9 @@ import dev.chrisbanes.haze.HazeState
import dev.chrisbanes.haze.hazeEffect
import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi
import dev.chrisbanes.haze.materials.HazeMaterials
import dev.meloda.fast.conversations.navigation.Conversations
import dev.meloda.fast.conversations.navigation.conversationsScreen
import dev.meloda.fast.friends.navigation.Friends
import dev.meloda.fast.friends.navigation.friendsScreen
import dev.meloda.fast.model.BaseError
import dev.meloda.fast.model.BottomNavigationItem
@@ -44,6 +46,7 @@ import dev.meloda.fast.navigation.MainGraph
import dev.meloda.fast.profile.navigation.profileScreen
import dev.meloda.fast.ui.theme.LocalBottomPadding
import dev.meloda.fast.ui.theme.LocalHazeState
import dev.meloda.fast.ui.theme.LocalScrollToTop
import dev.meloda.fast.ui.theme.LocalThemeConfig
import dev.meloda.fast.ui.theme.LocalUser
import dev.meloda.fast.ui.util.ImmutableList
@@ -72,6 +75,14 @@ fun MainScreen(
derivedStateOf { user?.photo100 }
}
var scrollToTop by remember {
mutableStateOf(
navigationItems.associate {
it.route to false
}
)
}
Scaffold(
bottomBar = {
NavigationBar(
@@ -101,6 +112,10 @@ fun MainScreen(
inclusive = true
}
}
} else {
scrollToTop = scrollToTop.toMutableMap().also {
it[navigationItems[index].route] = true
}
}
},
icon = {
@@ -148,7 +163,8 @@ fun MainScreen(
) {
CompositionLocalProvider(
LocalHazeState provides hazeState,
LocalBottomPadding provides padding.calculateBottomPadding()
LocalBottomPadding provides padding.calculateBottomPadding(),
LocalScrollToTop provides scrollToTop
) {
NavHost(
navController = navController,
@@ -164,13 +180,23 @@ fun MainScreen(
friendsScreen(
onError = onError,
onPhotoClicked = onPhotoClicked,
onMessageClicked = onMessageClicked
onMessageClicked = onMessageClicked,
onScrolledToTop = {
scrollToTop = scrollToTop.toMutableMap().also {
it[Friends] = false
}
},
)
conversationsScreen(
onError = onError,
onConversationItemClicked = onConversationItemClicked,
onCreateChatClicked = onCreateChatClicked,
navController = navController,
onScrolledToTop = {
scrollToTop = scrollToTop.toMutableMap().also {
it[Conversations] = false
}
}
)
profileScreen(
onError = onError,
@@ -129,6 +129,7 @@ val LocalSizeConfig = compositionLocalOf {
val LocalHazeState = compositionLocalOf { HazeState() }
val LocalBottomPadding = compositionLocalOf { 0.dp }
val LocalUser = compositionLocalOf<VkUser?> { null }
val LocalScrollToTop = compositionLocalOf { mapOf<Any, Boolean>() }
@Composable
fun AppTheme(
@@ -17,6 +17,7 @@ fun NavGraphBuilder.conversationsScreen(
onError: (BaseError) -> Unit,
onConversationItemClicked: (id: Int) -> Unit,
onCreateChatClicked: () -> Unit,
onScrolledToTop: () -> Unit,
navController: NavController,
) {
composable<Conversations> {
@@ -27,6 +28,7 @@ fun NavGraphBuilder.conversationsScreen(
onError = onError,
onConversationItemClicked = onConversationItemClicked,
onCreateChatButtonClicked = onCreateChatClicked,
onScrolledToTop = onScrolledToTop,
viewModel = viewModel
)
}
@@ -63,6 +63,7 @@ import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi
import dev.chrisbanes.haze.materials.HazeMaterials
import dev.meloda.fast.conversations.ConversationsViewModel
import dev.meloda.fast.conversations.model.ConversationsScreenState
import dev.meloda.fast.conversations.navigation.Conversations
import dev.meloda.fast.model.BaseError
import dev.meloda.fast.ui.components.ErrorView
import dev.meloda.fast.ui.components.FullScreenLoader
@@ -72,6 +73,7 @@ import dev.meloda.fast.ui.model.api.ConversationOption
import dev.meloda.fast.ui.model.api.UiConversation
import dev.meloda.fast.ui.theme.LocalBottomPadding
import dev.meloda.fast.ui.theme.LocalHazeState
import dev.meloda.fast.ui.theme.LocalScrollToTop
import dev.meloda.fast.ui.theme.LocalThemeConfig
import dev.meloda.fast.ui.util.isScrollingUp
import kotlinx.coroutines.flow.collectLatest
@@ -83,6 +85,7 @@ fun ConversationsRoute(
onError: (BaseError) -> Unit,
onConversationItemClicked: (conversationId: Int) -> Unit,
onCreateChatButtonClicked: () -> Unit,
onScrolledToTop: () -> Unit,
viewModel: ConversationsViewModel
) {
val screenState by viewModel.screenState.collectAsStateWithLifecycle()
@@ -105,7 +108,8 @@ fun ConversationsRoute(
onRefresh = viewModel::onRefresh,
onCreateChatButtonClicked = onCreateChatButtonClicked,
setScrollIndex = viewModel::setScrollIndex,
setScrollOffset = viewModel::setScrollOffset
setScrollOffset = viewModel::setScrollOffset,
onScrolledToTop = onScrolledToTop
)
HandleDialogs(
@@ -132,7 +136,8 @@ fun ConversationsScreen(
onRefresh: () -> Unit = {},
onCreateChatButtonClicked: () -> Unit = {},
setScrollIndex: (Int) -> Unit = {},
setScrollOffset: (Int) -> Unit = {}
setScrollOffset: (Int) -> Unit = {},
onScrolledToTop: () -> Unit = {}
) {
val currentTheme = LocalThemeConfig.current
@@ -145,6 +150,17 @@ fun ConversationsScreen(
initialFirstVisibleItemScrollOffset = screenState.scrollOffset
)
val scrollToTop = LocalScrollToTop.current[Conversations] ?: false
LaunchedEffect(scrollToTop) {
if (scrollToTop) {
if (listState.firstVisibleItemIndex > 14) {
listState.scrollToItem(14)
}
listState.animateScrollToItem(0)
onScrolledToTop()
}
}
LaunchedEffect(listState) {
snapshotFlow { listState.firstVisibleItemIndex }
.debounce(500L)
@@ -12,13 +12,15 @@ object Friends
fun NavGraphBuilder.friendsScreen(
onError: (BaseError) -> Unit,
onPhotoClicked: (url: String) -> Unit,
onMessageClicked: (userId: Int) -> Unit
onMessageClicked: (userId: Int) -> Unit,
onScrolledToTop: () -> Unit
) {
composable<Friends> {
FriendsRoute(
onError = onError,
onPhotoClicked = onPhotoClicked,
onMessageClicked = onMessageClicked
onMessageClicked = onMessageClicked,
onScrolledToTop = onScrolledToTop
)
}
}
@@ -29,12 +29,14 @@ import dev.chrisbanes.haze.hazeSource
import dev.meloda.fast.friends.FriendsViewModel
import dev.meloda.fast.friends.FriendsViewModelImpl
import dev.meloda.fast.friends.OnlineFriendsViewModelImpl
import dev.meloda.fast.friends.navigation.Friends
import dev.meloda.fast.model.BaseError
import dev.meloda.fast.ui.R
import dev.meloda.fast.ui.components.ErrorView
import dev.meloda.fast.ui.components.FullScreenLoader
import dev.meloda.fast.ui.components.NoItemsView
import dev.meloda.fast.ui.theme.LocalHazeState
import dev.meloda.fast.ui.theme.LocalScrollToTop
import dev.meloda.fast.ui.theme.LocalThemeConfig
import dev.meloda.fast.ui.util.ImmutableList
import kotlinx.coroutines.flow.collectLatest
@@ -51,7 +53,8 @@ fun FriendsScreen(
onSessionExpiredLogOutButtonClicked: () -> Unit = {},
onPhotoClicked: (url: String) -> Unit = {},
onMessageClicked: (userId: Int) -> Unit = {},
setCanScrollBackward: (Boolean) -> Unit = {}
setCanScrollBackward: (Boolean) -> Unit = {},
onScrolledToTop: () -> Unit = {}
) {
val context: Context = LocalContext.current
val viewModel: FriendsViewModel =
@@ -93,6 +96,17 @@ fun FriendsScreen(
initialFirstVisibleItemScrollOffset = screenState.scrollOffset
)
val scrollToTop = LocalScrollToTop.current[Friends] ?: false
LaunchedEffect(scrollToTop) {
if (scrollToTop) {
if (listState.firstVisibleItemIndex > 14) {
listState.scrollToItem(14)
}
listState.animateScrollToItem(0)
onScrolledToTop()
}
}
LaunchedEffect(listState) {
snapshotFlow { listState.firstVisibleItemIndex }
.debounce(250L)
@@ -58,6 +58,7 @@ fun FriendsRoute(
onError: (BaseError) -> Unit,
onPhotoClicked: (url: String) -> Unit,
onMessageClicked: (userId: Int) -> Unit,
onScrolledToTop: () -> Unit
) {
val scope = rememberCoroutineScope()
val currentTheme = LocalThemeConfig.current
@@ -236,7 +237,8 @@ fun FriendsRoute(
onSessionExpiredLogOutButtonClicked = { onError(BaseError.SessionExpired) },
onPhotoClicked = onPhotoClicked,
onMessageClicked = onMessageClicked,
setCanScrollBackward = { canScrollBackward = it }
setCanScrollBackward = { canScrollBackward = it },
onScrolledToTop = onScrolledToTop
)
}
}