diff --git a/app/src/main/kotlin/dev/meloda/fast/presentation/MainScreen.kt b/app/src/main/kotlin/dev/meloda/fast/presentation/MainScreen.kt index 5571cc1d..3971abc9 100644 --- a/app/src/main/kotlin/dev/meloda/fast/presentation/MainScreen.kt +++ b/app/src/main/kotlin/dev/meloda/fast/presentation/MainScreen.kt @@ -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, diff --git a/core/ui/src/main/kotlin/dev/meloda/fast/ui/theme/AppTheme.kt b/core/ui/src/main/kotlin/dev/meloda/fast/ui/theme/AppTheme.kt index 88fe8c74..7cb44f43 100644 --- a/core/ui/src/main/kotlin/dev/meloda/fast/ui/theme/AppTheme.kt +++ b/core/ui/src/main/kotlin/dev/meloda/fast/ui/theme/AppTheme.kt @@ -129,6 +129,7 @@ val LocalSizeConfig = compositionLocalOf { val LocalHazeState = compositionLocalOf { HazeState() } val LocalBottomPadding = compositionLocalOf { 0.dp } val LocalUser = compositionLocalOf { null } +val LocalScrollToTop = compositionLocalOf { mapOf() } @Composable fun AppTheme( diff --git a/feature/conversations/src/main/kotlin/dev/meloda/fast/conversations/navigation/ConversationsNavigation.kt b/feature/conversations/src/main/kotlin/dev/meloda/fast/conversations/navigation/ConversationsNavigation.kt index 63876d0e..de39e945 100644 --- a/feature/conversations/src/main/kotlin/dev/meloda/fast/conversations/navigation/ConversationsNavigation.kt +++ b/feature/conversations/src/main/kotlin/dev/meloda/fast/conversations/navigation/ConversationsNavigation.kt @@ -17,6 +17,7 @@ fun NavGraphBuilder.conversationsScreen( onError: (BaseError) -> Unit, onConversationItemClicked: (id: Int) -> Unit, onCreateChatClicked: () -> Unit, + onScrolledToTop: () -> Unit, navController: NavController, ) { composable { @@ -27,6 +28,7 @@ fun NavGraphBuilder.conversationsScreen( onError = onError, onConversationItemClicked = onConversationItemClicked, onCreateChatButtonClicked = onCreateChatClicked, + onScrolledToTop = onScrolledToTop, viewModel = viewModel ) } diff --git a/feature/conversations/src/main/kotlin/dev/meloda/fast/conversations/presentation/ConversationsScreen.kt b/feature/conversations/src/main/kotlin/dev/meloda/fast/conversations/presentation/ConversationsScreen.kt index 2f05e6f6..92c31bcd 100644 --- a/feature/conversations/src/main/kotlin/dev/meloda/fast/conversations/presentation/ConversationsScreen.kt +++ b/feature/conversations/src/main/kotlin/dev/meloda/fast/conversations/presentation/ConversationsScreen.kt @@ -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) diff --git a/feature/friends/src/main/kotlin/dev/meloda/fast/friends/navigation/FriendsNavigation.kt b/feature/friends/src/main/kotlin/dev/meloda/fast/friends/navigation/FriendsNavigation.kt index 1612a7a7..6eddecc8 100644 --- a/feature/friends/src/main/kotlin/dev/meloda/fast/friends/navigation/FriendsNavigation.kt +++ b/feature/friends/src/main/kotlin/dev/meloda/fast/friends/navigation/FriendsNavigation.kt @@ -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 { FriendsRoute( onError = onError, onPhotoClicked = onPhotoClicked, - onMessageClicked = onMessageClicked + onMessageClicked = onMessageClicked, + onScrolledToTop = onScrolledToTop ) } } diff --git a/feature/friends/src/main/kotlin/dev/meloda/fast/friends/presentation/FriendsScreen.kt b/feature/friends/src/main/kotlin/dev/meloda/fast/friends/presentation/FriendsScreen.kt index 85ceed61..58e40e0b 100644 --- a/feature/friends/src/main/kotlin/dev/meloda/fast/friends/presentation/FriendsScreen.kt +++ b/feature/friends/src/main/kotlin/dev/meloda/fast/friends/presentation/FriendsScreen.kt @@ -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) diff --git a/feature/friends/src/main/kotlin/dev/meloda/fast/friends/presentation/RootFriendsScreen.kt b/feature/friends/src/main/kotlin/dev/meloda/fast/friends/presentation/RootFriendsScreen.kt index 3212389c..c7fe88b1 100644 --- a/feature/friends/src/main/kotlin/dev/meloda/fast/friends/presentation/RootFriendsScreen.kt +++ b/feature/friends/src/main/kotlin/dev/meloda/fast/friends/presentation/RootFriendsScreen.kt @@ -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 ) } }