From 81c6247cd4283c9128103bd9b9eabbad6c617cc4 Mon Sep 17 00:00:00 2001 From: Danil Nikolaev Date: Wed, 4 Dec 2024 19:29:36 +0300 Subject: [PATCH] saving scroll position on friends screen & conversations --- .../presentation/ConversationsScreen.kt | 45 +++++++++++++++---- .../friends/presentation/FriendsScreen.kt | 44 ++++++++++++++---- 2 files changed, 71 insertions(+), 18 deletions(-) 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 1854f3e6..a00b2034 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 @@ -1,5 +1,6 @@ package dev.meloda.fast.conversations.presentation +import android.content.SharedPreferences import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.animateColorAsState import androidx.compose.animation.core.Animatable @@ -50,6 +51,7 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue +import androidx.compose.runtime.snapshotFlow import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip @@ -63,6 +65,7 @@ import androidx.compose.ui.unit.DpOffset import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.dp +import androidx.core.content.edit import androidx.core.view.HapticFeedbackConstantsCompat import androidx.lifecycle.compose.collectAsStateWithLifecycle import coil.compose.AsyncImage @@ -77,7 +80,6 @@ import dev.meloda.fast.conversations.model.ConversationOption import dev.meloda.fast.conversations.model.ConversationsScreenState import dev.meloda.fast.conversations.model.UiConversation import dev.meloda.fast.datastore.AppSettings -import dev.meloda.fast.datastore.UserSettings import dev.meloda.fast.model.BaseError import dev.meloda.fast.ui.components.ErrorView import dev.meloda.fast.ui.components.FullScreenLoader @@ -86,6 +88,8 @@ import dev.meloda.fast.ui.theme.LocalBottomPadding import dev.meloda.fast.ui.theme.LocalHazeState import dev.meloda.fast.ui.theme.LocalThemeConfig import dev.meloda.fast.ui.util.isScrollingUp +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.launch import org.koin.compose.koinInject import dev.meloda.fast.ui.R as UiR @@ -99,9 +103,7 @@ fun ConversationsRoute( ) { val context = LocalContext.current - val userSettings: UserSettings = koinInject() - - val enablePullToRefresh by userSettings.enablePullToRefresh.collectAsStateWithLifecycle() + val prefs: SharedPreferences = koinInject() val screenState by viewModel.screenState.collectAsStateWithLifecycle() val baseError by viewModel.baseError.collectAsStateWithLifecycle() @@ -122,7 +124,8 @@ fun ConversationsRoute( screenState = screenState, baseError = baseError, canPaginate = canPaginate, - enablePullToRefresh = enablePullToRefresh, + firstVisibleItemIndex = prefs.getInt("conversations_all_scroll_position", 0), + firstVisibleItemScrollOffset = prefs.getInt("conversations_all_scroll_offset", 0), onSessionExpiredLogOutButtonClicked = { onError(BaseError.SessionExpired) }, onConversationItemClicked = { id -> onConversationItemClicked(id) @@ -133,7 +136,13 @@ fun ConversationsRoute( onPaginationConditionsMet = viewModel::onPaginationConditionsMet, onRefreshDropdownItemClicked = viewModel::onRefresh, onRefresh = viewModel::onRefresh, - onConversationPhotoClicked = onConversationPhotoClicked + onConversationPhotoClicked = onConversationPhotoClicked, + onSaveScrollPosition = { index -> + prefs.edit { putInt("conversations_all_scroll_position", index) } + }, + onSaveScrollOffsetPosition = { offset -> + prefs.edit { putInt("conversations_all_scroll_offset", offset) } + } ) @@ -152,7 +161,8 @@ fun ConversationsScreen( screenState: ConversationsScreenState = ConversationsScreenState.EMPTY, baseError: BaseError? = null, canPaginate: Boolean = false, - enablePullToRefresh: Boolean = false, + firstVisibleItemIndex: Int = 0, + firstVisibleItemScrollOffset: Int = 0, onSessionExpiredLogOutButtonClicked: () -> Unit, onConversationItemClicked: (conversationId: Int) -> Unit = {}, onConversationItemLongClicked: (conversation: UiConversation) -> Unit = {}, @@ -160,7 +170,9 @@ fun ConversationsScreen( onPaginationConditionsMet: () -> Unit = {}, onRefreshDropdownItemClicked: () -> Unit = {}, onRefresh: () -> Unit = {}, - onConversationPhotoClicked: (url: String) -> Unit = {} + onConversationPhotoClicked: (url: String) -> Unit = {}, + onSaveScrollPosition: (Int) -> Unit = {}, + onSaveScrollOffsetPosition: (Int) -> Unit = {} ) { val view = LocalView.current val currentTheme = LocalThemeConfig.current @@ -169,7 +181,22 @@ fun ConversationsScreen( mutableIntStateOf(if (currentTheme.enableMultiline) 2 else 1) } - val listState = rememberLazyListState() + val listState = rememberLazyListState( + initialFirstVisibleItemIndex = firstVisibleItemIndex, + initialFirstVisibleItemScrollOffset = firstVisibleItemScrollOffset + ) + + LaunchedEffect(listState) { + snapshotFlow { listState.firstVisibleItemIndex } + .debounce(500L) + .collectLatest(onSaveScrollPosition) + } + + LaunchedEffect(listState) { + snapshotFlow { listState.firstVisibleItemScrollOffset } + .debounce(500L) + .collectLatest(onSaveScrollOffsetPosition) + } val paginationConditionMet by remember(canPaginate, listState) { derivedStateOf { 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 d2a8b9ba..8b87ba8e 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 @@ -1,5 +1,6 @@ package dev.meloda.fast.friends.presentation +import android.content.SharedPreferences import androidx.compose.animation.animateColorAsState import androidx.compose.animation.core.FastOutLinearInEasing import androidx.compose.animation.core.animateFloatAsState @@ -46,6 +47,7 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.dp +import androidx.core.content.edit import androidx.lifecycle.compose.collectAsStateWithLifecycle import coil.imageLoader import coil.request.ImageRequest @@ -53,7 +55,6 @@ import dev.chrisbanes.haze.haze import dev.chrisbanes.haze.hazeChild import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi import dev.chrisbanes.haze.materials.HazeMaterials -import dev.meloda.fast.datastore.UserSettings import dev.meloda.fast.friends.FriendsViewModel import dev.meloda.fast.friends.FriendsViewModelImpl import dev.meloda.fast.friends.model.FriendsScreenState @@ -65,6 +66,8 @@ import dev.meloda.fast.ui.model.TabItem import dev.meloda.fast.ui.theme.LocalHazeState import dev.meloda.fast.ui.theme.LocalThemeConfig import dev.meloda.fast.ui.util.ImmutableList +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.debounce import org.koin.androidx.compose.koinViewModel import org.koin.compose.koinInject import dev.meloda.fast.ui.R as UiR @@ -77,9 +80,7 @@ fun FriendsRoute( ) { val context = LocalContext.current - val userSettings: UserSettings = koinInject() - - val enablePullToRefresh by userSettings.enablePullToRefresh.collectAsStateWithLifecycle() + val prefs: SharedPreferences = koinInject() val screenState by viewModel.screenState.collectAsStateWithLifecycle() val baseError by viewModel.baseError.collectAsStateWithLifecycle() @@ -99,12 +100,19 @@ fun FriendsRoute( FriendsScreen( screenState = screenState, baseError = baseError, - enablePullToRefresh = enablePullToRefresh, canPaginate = canPaginate, + firstVisibleItemIndex = prefs.getInt("friends_all_scroll_position", 0), + firstVisibleItemScrollOffset = prefs.getInt("friends_all_scroll_offset", 0), onSessionExpiredLogOutButtonClicked = { onError(BaseError.SessionExpired) }, onPaginationConditionsMet = viewModel::onPaginationConditionsMet, onRefresh = viewModel::onRefresh, - onPhotoClicked = onPhotoClicked + onPhotoClicked = onPhotoClicked, + onSaveScrollPosition = { index -> + prefs.edit { putInt("friends_all_scroll_position", index) } + }, + onSaveScrollOffsetPosition = { offset -> + prefs.edit { putInt("friends_all_scroll_offset", offset) } + } ) } @@ -118,12 +126,15 @@ fun FriendsRoute( fun FriendsScreen( screenState: FriendsScreenState = FriendsScreenState.EMPTY, baseError: BaseError? = null, - enablePullToRefresh: Boolean = false, canPaginate: Boolean = false, + firstVisibleItemIndex: Int = 0, + firstVisibleItemScrollOffset: Int = 0, onSessionExpiredLogOutButtonClicked: () -> Unit = {}, onPaginationConditionsMet: () -> Unit = {}, onRefresh: () -> Unit = {}, - onPhotoClicked: (url: String) -> Unit = {} + onPhotoClicked: (url: String) -> Unit = {}, + onSaveScrollPosition: (Int) -> Unit = {}, + onSaveScrollOffsetPosition: (Int) -> Unit = {} ) { val currentTheme = LocalThemeConfig.current @@ -133,7 +144,22 @@ fun FriendsScreen( } } - val listState = rememberLazyListState() + val listState = rememberLazyListState( + initialFirstVisibleItemIndex = firstVisibleItemIndex, + initialFirstVisibleItemScrollOffset = firstVisibleItemScrollOffset + ) + + LaunchedEffect(listState) { + snapshotFlow { listState.firstVisibleItemIndex } + .debounce(500L) + .collectLatest(onSaveScrollPosition) + } + + LaunchedEffect(listState) { + snapshotFlow { listState.firstVisibleItemScrollOffset } + .debounce(500L) + .collectLatest(onSaveScrollOffsetPosition) + } val paginationConditionMet by remember(canPaginate, listState) { derivedStateOf {