saving scroll position on friends screen & conversations

This commit is contained in:
2024-12-04 19:29:36 +03:00
parent d7e8c0dd35
commit 81c6247cd4
2 changed files with 71 additions and 18 deletions
@@ -1,5 +1,6 @@
package dev.meloda.fast.conversations.presentation package dev.meloda.fast.conversations.presentation
import android.content.SharedPreferences
import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.animateColorAsState import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.core.Animatable import androidx.compose.animation.core.Animatable
@@ -50,6 +51,7 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip 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.IntOffset
import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.core.content.edit
import androidx.core.view.HapticFeedbackConstantsCompat import androidx.core.view.HapticFeedbackConstantsCompat
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import coil.compose.AsyncImage 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.ConversationsScreenState
import dev.meloda.fast.conversations.model.UiConversation import dev.meloda.fast.conversations.model.UiConversation
import dev.meloda.fast.datastore.AppSettings import dev.meloda.fast.datastore.AppSettings
import dev.meloda.fast.datastore.UserSettings
import dev.meloda.fast.model.BaseError import dev.meloda.fast.model.BaseError
import dev.meloda.fast.ui.components.ErrorView import dev.meloda.fast.ui.components.ErrorView
import dev.meloda.fast.ui.components.FullScreenLoader 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.LocalHazeState
import dev.meloda.fast.ui.theme.LocalThemeConfig import dev.meloda.fast.ui.theme.LocalThemeConfig
import dev.meloda.fast.ui.util.isScrollingUp import dev.meloda.fast.ui.util.isScrollingUp
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.koin.compose.koinInject import org.koin.compose.koinInject
import dev.meloda.fast.ui.R as UiR import dev.meloda.fast.ui.R as UiR
@@ -99,9 +103,7 @@ fun ConversationsRoute(
) { ) {
val context = LocalContext.current val context = LocalContext.current
val userSettings: UserSettings = koinInject() val prefs: SharedPreferences = koinInject()
val enablePullToRefresh by userSettings.enablePullToRefresh.collectAsStateWithLifecycle()
val screenState by viewModel.screenState.collectAsStateWithLifecycle() val screenState by viewModel.screenState.collectAsStateWithLifecycle()
val baseError by viewModel.baseError.collectAsStateWithLifecycle() val baseError by viewModel.baseError.collectAsStateWithLifecycle()
@@ -122,7 +124,8 @@ fun ConversationsRoute(
screenState = screenState, screenState = screenState,
baseError = baseError, baseError = baseError,
canPaginate = canPaginate, canPaginate = canPaginate,
enablePullToRefresh = enablePullToRefresh, firstVisibleItemIndex = prefs.getInt("conversations_all_scroll_position", 0),
firstVisibleItemScrollOffset = prefs.getInt("conversations_all_scroll_offset", 0),
onSessionExpiredLogOutButtonClicked = { onError(BaseError.SessionExpired) }, onSessionExpiredLogOutButtonClicked = { onError(BaseError.SessionExpired) },
onConversationItemClicked = { id -> onConversationItemClicked = { id ->
onConversationItemClicked(id) onConversationItemClicked(id)
@@ -133,7 +136,13 @@ fun ConversationsRoute(
onPaginationConditionsMet = viewModel::onPaginationConditionsMet, onPaginationConditionsMet = viewModel::onPaginationConditionsMet,
onRefreshDropdownItemClicked = viewModel::onRefresh, onRefreshDropdownItemClicked = viewModel::onRefresh,
onRefresh = 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, screenState: ConversationsScreenState = ConversationsScreenState.EMPTY,
baseError: BaseError? = null, baseError: BaseError? = null,
canPaginate: Boolean = false, canPaginate: Boolean = false,
enablePullToRefresh: Boolean = false, firstVisibleItemIndex: Int = 0,
firstVisibleItemScrollOffset: Int = 0,
onSessionExpiredLogOutButtonClicked: () -> Unit, onSessionExpiredLogOutButtonClicked: () -> Unit,
onConversationItemClicked: (conversationId: Int) -> Unit = {}, onConversationItemClicked: (conversationId: Int) -> Unit = {},
onConversationItemLongClicked: (conversation: UiConversation) -> Unit = {}, onConversationItemLongClicked: (conversation: UiConversation) -> Unit = {},
@@ -160,7 +170,9 @@ fun ConversationsScreen(
onPaginationConditionsMet: () -> Unit = {}, onPaginationConditionsMet: () -> Unit = {},
onRefreshDropdownItemClicked: () -> Unit = {}, onRefreshDropdownItemClicked: () -> Unit = {},
onRefresh: () -> Unit = {}, onRefresh: () -> Unit = {},
onConversationPhotoClicked: (url: String) -> Unit = {} onConversationPhotoClicked: (url: String) -> Unit = {},
onSaveScrollPosition: (Int) -> Unit = {},
onSaveScrollOffsetPosition: (Int) -> Unit = {}
) { ) {
val view = LocalView.current val view = LocalView.current
val currentTheme = LocalThemeConfig.current val currentTheme = LocalThemeConfig.current
@@ -169,7 +181,22 @@ fun ConversationsScreen(
mutableIntStateOf(if (currentTheme.enableMultiline) 2 else 1) 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) { val paginationConditionMet by remember(canPaginate, listState) {
derivedStateOf { derivedStateOf {
@@ -1,5 +1,6 @@
package dev.meloda.fast.friends.presentation package dev.meloda.fast.friends.presentation
import android.content.SharedPreferences
import androidx.compose.animation.animateColorAsState import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.core.FastOutLinearInEasing import androidx.compose.animation.core.FastOutLinearInEasing
import androidx.compose.animation.core.animateFloatAsState 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.text.style.TextOverflow
import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.core.content.edit
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import coil.imageLoader import coil.imageLoader
import coil.request.ImageRequest import coil.request.ImageRequest
@@ -53,7 +55,6 @@ import dev.chrisbanes.haze.haze
import dev.chrisbanes.haze.hazeChild import dev.chrisbanes.haze.hazeChild
import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi
import dev.chrisbanes.haze.materials.HazeMaterials import dev.chrisbanes.haze.materials.HazeMaterials
import dev.meloda.fast.datastore.UserSettings
import dev.meloda.fast.friends.FriendsViewModel import dev.meloda.fast.friends.FriendsViewModel
import dev.meloda.fast.friends.FriendsViewModelImpl import dev.meloda.fast.friends.FriendsViewModelImpl
import dev.meloda.fast.friends.model.FriendsScreenState 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.LocalHazeState
import dev.meloda.fast.ui.theme.LocalThemeConfig import dev.meloda.fast.ui.theme.LocalThemeConfig
import dev.meloda.fast.ui.util.ImmutableList 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.androidx.compose.koinViewModel
import org.koin.compose.koinInject import org.koin.compose.koinInject
import dev.meloda.fast.ui.R as UiR import dev.meloda.fast.ui.R as UiR
@@ -77,9 +80,7 @@ fun FriendsRoute(
) { ) {
val context = LocalContext.current val context = LocalContext.current
val userSettings: UserSettings = koinInject() val prefs: SharedPreferences = koinInject()
val enablePullToRefresh by userSettings.enablePullToRefresh.collectAsStateWithLifecycle()
val screenState by viewModel.screenState.collectAsStateWithLifecycle() val screenState by viewModel.screenState.collectAsStateWithLifecycle()
val baseError by viewModel.baseError.collectAsStateWithLifecycle() val baseError by viewModel.baseError.collectAsStateWithLifecycle()
@@ -99,12 +100,19 @@ fun FriendsRoute(
FriendsScreen( FriendsScreen(
screenState = screenState, screenState = screenState,
baseError = baseError, baseError = baseError,
enablePullToRefresh = enablePullToRefresh,
canPaginate = canPaginate, canPaginate = canPaginate,
firstVisibleItemIndex = prefs.getInt("friends_all_scroll_position", 0),
firstVisibleItemScrollOffset = prefs.getInt("friends_all_scroll_offset", 0),
onSessionExpiredLogOutButtonClicked = { onError(BaseError.SessionExpired) }, onSessionExpiredLogOutButtonClicked = { onError(BaseError.SessionExpired) },
onPaginationConditionsMet = viewModel::onPaginationConditionsMet, onPaginationConditionsMet = viewModel::onPaginationConditionsMet,
onRefresh = viewModel::onRefresh, 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( fun FriendsScreen(
screenState: FriendsScreenState = FriendsScreenState.EMPTY, screenState: FriendsScreenState = FriendsScreenState.EMPTY,
baseError: BaseError? = null, baseError: BaseError? = null,
enablePullToRefresh: Boolean = false,
canPaginate: Boolean = false, canPaginate: Boolean = false,
firstVisibleItemIndex: Int = 0,
firstVisibleItemScrollOffset: Int = 0,
onSessionExpiredLogOutButtonClicked: () -> Unit = {}, onSessionExpiredLogOutButtonClicked: () -> Unit = {},
onPaginationConditionsMet: () -> Unit = {}, onPaginationConditionsMet: () -> Unit = {},
onRefresh: () -> Unit = {}, onRefresh: () -> Unit = {},
onPhotoClicked: (url: String) -> Unit = {} onPhotoClicked: (url: String) -> Unit = {},
onSaveScrollPosition: (Int) -> Unit = {},
onSaveScrollOffsetPosition: (Int) -> Unit = {}
) { ) {
val currentTheme = LocalThemeConfig.current 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) { val paginationConditionMet by remember(canPaginate, listState) {
derivedStateOf { derivedStateOf {