saving scroll position only while app is working & add divider with spacer on conversations screen
This commit is contained in:
+11
@@ -60,6 +60,9 @@ interface ConversationsViewModel {
|
||||
fun onOptionClicked(conversation: UiConversation, option: ConversationOption)
|
||||
|
||||
fun onErrorConsumed()
|
||||
|
||||
fun setScrollIndex(index: Int)
|
||||
fun setScrollOffset(offset: Int)
|
||||
}
|
||||
|
||||
class ConversationsViewModelImpl(
|
||||
@@ -206,6 +209,14 @@ class ConversationsViewModelImpl(
|
||||
baseError.setValue { null }
|
||||
}
|
||||
|
||||
override fun setScrollIndex(index: Int) {
|
||||
screenState.setValue { old -> old.copy(scrollIndex = index) }
|
||||
}
|
||||
|
||||
override fun setScrollOffset(offset: Int) {
|
||||
screenState.setValue { old -> old.copy(scrollOffset = offset) }
|
||||
}
|
||||
|
||||
private fun hideOptions(conversationId: Int) {
|
||||
screenState.setValue { old ->
|
||||
old.copy(
|
||||
|
||||
+6
-2
@@ -9,7 +9,9 @@ data class ConversationsScreenState(
|
||||
val isLoading: Boolean,
|
||||
val isPaginating: Boolean,
|
||||
val isPaginationExhausted: Boolean,
|
||||
val profileImageUrl: String?
|
||||
val profileImageUrl: String?,
|
||||
val scrollIndex: Int,
|
||||
val scrollOffset: Int
|
||||
) {
|
||||
|
||||
companion object {
|
||||
@@ -19,7 +21,9 @@ data class ConversationsScreenState(
|
||||
isLoading = true,
|
||||
isPaginating = false,
|
||||
isPaginationExhausted = false,
|
||||
profileImageUrl = null
|
||||
profileImageUrl = null,
|
||||
scrollIndex = 0,
|
||||
scrollOffset = 0,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+2
-3
@@ -45,8 +45,6 @@ fun ConversationsList(
|
||||
) {
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
|
||||
val conversations = screenState.conversations
|
||||
|
||||
val bottomPadding = LocalBottomPadding.current
|
||||
|
||||
LazyColumn(
|
||||
@@ -55,9 +53,10 @@ fun ConversationsList(
|
||||
) {
|
||||
item {
|
||||
Spacer(modifier = Modifier.height(padding.calculateTopPadding()))
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
}
|
||||
items(
|
||||
items = conversations,
|
||||
items = screenState.conversations,
|
||||
key = UiConversation::id,
|
||||
) { conversation ->
|
||||
val isUserAccount by remember(conversation) {
|
||||
|
||||
+12
-22
@@ -1,6 +1,5 @@
|
||||
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
|
||||
@@ -29,6 +28,7 @@ import androidx.compose.material3.DropdownMenu
|
||||
import androidx.compose.material3.DropdownMenuItem
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.FloatingActionButton
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.LinearProgressIndicator
|
||||
@@ -63,7 +63,6 @@ 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.imageLoader
|
||||
@@ -88,7 +87,6 @@ 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
|
||||
|
||||
@Composable
|
||||
@@ -100,8 +98,6 @@ fun ConversationsRoute(
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
|
||||
val prefs: SharedPreferences = koinInject()
|
||||
|
||||
val screenState by viewModel.screenState.collectAsStateWithLifecycle()
|
||||
val baseError by viewModel.baseError.collectAsStateWithLifecycle()
|
||||
val canPaginate by viewModel.canPaginate.collectAsStateWithLifecycle()
|
||||
@@ -121,8 +117,6 @@ fun ConversationsRoute(
|
||||
screenState = screenState,
|
||||
baseError = baseError,
|
||||
canPaginate = canPaginate,
|
||||
firstVisibleItemIndex = prefs.getInt("conversations_all_scroll_position", 0),
|
||||
firstVisibleItemScrollOffset = prefs.getInt("conversations_all_scroll_offset", 0),
|
||||
onSessionExpiredLogOutButtonClicked = { onError(BaseError.SessionExpired) },
|
||||
onConversationItemClicked = { id ->
|
||||
onConversationItemClicked(id)
|
||||
@@ -134,15 +128,10 @@ fun ConversationsRoute(
|
||||
onRefreshDropdownItemClicked = viewModel::onRefresh,
|
||||
onRefresh = viewModel::onRefresh,
|
||||
onConversationPhotoClicked = onConversationPhotoClicked,
|
||||
onSaveScrollPosition = { index ->
|
||||
prefs.edit { putInt("conversations_all_scroll_position", index) }
|
||||
},
|
||||
onSaveScrollOffsetPosition = { offset ->
|
||||
prefs.edit { putInt("conversations_all_scroll_offset", offset) }
|
||||
}
|
||||
setScrollIndex = viewModel::setScrollIndex,
|
||||
setScrollOffset = viewModel::setScrollOffset
|
||||
)
|
||||
|
||||
|
||||
HandleDialogs(
|
||||
screenState = screenState,
|
||||
viewModel = viewModel
|
||||
@@ -158,8 +147,6 @@ fun ConversationsScreen(
|
||||
screenState: ConversationsScreenState = ConversationsScreenState.EMPTY,
|
||||
baseError: BaseError? = null,
|
||||
canPaginate: Boolean = false,
|
||||
firstVisibleItemIndex: Int = 0,
|
||||
firstVisibleItemScrollOffset: Int = 0,
|
||||
onSessionExpiredLogOutButtonClicked: () -> Unit,
|
||||
onConversationItemClicked: (conversationId: Int) -> Unit = {},
|
||||
onConversationItemLongClicked: (conversation: UiConversation) -> Unit = {},
|
||||
@@ -168,8 +155,8 @@ fun ConversationsScreen(
|
||||
onRefreshDropdownItemClicked: () -> Unit = {},
|
||||
onRefresh: () -> Unit = {},
|
||||
onConversationPhotoClicked: (url: String) -> Unit = {},
|
||||
onSaveScrollPosition: (Int) -> Unit = {},
|
||||
onSaveScrollOffsetPosition: (Int) -> Unit = {}
|
||||
setScrollIndex: (Int) -> Unit = {},
|
||||
setScrollOffset: (Int) -> Unit = {}
|
||||
) {
|
||||
val view = LocalView.current
|
||||
val currentTheme = LocalThemeConfig.current
|
||||
@@ -179,20 +166,20 @@ fun ConversationsScreen(
|
||||
}
|
||||
|
||||
val listState = rememberLazyListState(
|
||||
initialFirstVisibleItemIndex = firstVisibleItemIndex,
|
||||
initialFirstVisibleItemScrollOffset = firstVisibleItemScrollOffset
|
||||
initialFirstVisibleItemIndex = screenState.scrollIndex,
|
||||
initialFirstVisibleItemScrollOffset = screenState.scrollOffset
|
||||
)
|
||||
|
||||
LaunchedEffect(listState) {
|
||||
snapshotFlow { listState.firstVisibleItemIndex }
|
||||
.debounce(500L)
|
||||
.collectLatest(onSaveScrollPosition)
|
||||
.collectLatest(setScrollIndex)
|
||||
}
|
||||
|
||||
LaunchedEffect(listState) {
|
||||
snapshotFlow { listState.firstVisibleItemScrollOffset }
|
||||
.debounce(500L)
|
||||
.collectLatest(onSaveScrollOffsetPosition)
|
||||
.collectLatest(setScrollOffset)
|
||||
}
|
||||
|
||||
val paginationConditionMet by remember(canPaginate, listState) {
|
||||
@@ -306,6 +293,9 @@ fun ConversationsScreen(
|
||||
AnimatedVisibility(showHorizontalProgressBar) {
|
||||
LinearProgressIndicator(modifier = Modifier.fillMaxWidth())
|
||||
}
|
||||
AnimatedVisibility(!showHorizontalProgressBar) {
|
||||
HorizontalDivider()
|
||||
}
|
||||
}
|
||||
},
|
||||
floatingActionButton = {
|
||||
|
||||
@@ -31,6 +31,11 @@ interface FriendsViewModel {
|
||||
fun onRefresh()
|
||||
|
||||
fun onErrorConsumed()
|
||||
|
||||
fun setScrollIndex(index: Int)
|
||||
fun setScrollOffset(offset: Int)
|
||||
fun setScrollIndexOnline(index: Int)
|
||||
fun setScrollOffsetOnline(offset: Int)
|
||||
}
|
||||
|
||||
class FriendsViewModelImpl(
|
||||
@@ -66,6 +71,22 @@ class FriendsViewModelImpl(
|
||||
baseError.setValue { null }
|
||||
}
|
||||
|
||||
override fun setScrollIndex(index: Int) {
|
||||
screenState.setValue { old -> old.copy(scrollIndex = index) }
|
||||
}
|
||||
|
||||
override fun setScrollOffset(offset: Int) {
|
||||
screenState.setValue { old -> old.copy(scrollOffset = offset) }
|
||||
}
|
||||
|
||||
override fun setScrollIndexOnline(index: Int) {
|
||||
screenState.setValue { old -> old.copy(scrollIndexOnline = index) }
|
||||
}
|
||||
|
||||
override fun setScrollOffsetOnline(offset: Int) {
|
||||
screenState.setValue { old -> old.copy(scrollOffsetOnline = offset) }
|
||||
}
|
||||
|
||||
private fun loadFriends(offset: Int = currentOffset.value) {
|
||||
friendsUseCase.getFriends(count = LOAD_COUNT, offset = offset)
|
||||
.listenValue(viewModelScope) { state ->
|
||||
|
||||
+10
-2
@@ -8,7 +8,11 @@ data class FriendsScreenState(
|
||||
val friends: List<UiFriend>,
|
||||
val onlineFriends: List<UiFriend>,
|
||||
val isPaginating: Boolean,
|
||||
val isPaginationExhausted: Boolean
|
||||
val isPaginationExhausted: Boolean,
|
||||
val scrollIndex: Int,
|
||||
val scrollOffset: Int,
|
||||
val scrollIndexOnline: Int,
|
||||
val scrollOffsetOnline: Int
|
||||
) {
|
||||
|
||||
companion object {
|
||||
@@ -17,7 +21,11 @@ data class FriendsScreenState(
|
||||
friends = emptyList(),
|
||||
onlineFriends = emptyList(),
|
||||
isPaginating = false,
|
||||
isPaginationExhausted = false
|
||||
isPaginationExhausted = false,
|
||||
scrollIndex = 0,
|
||||
scrollOffset = 0,
|
||||
scrollIndexOnline = 0,
|
||||
scrollOffsetOnline = 0,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,9 @@ import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.IconButtonDefaults
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.snapshotFlow
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
@@ -35,8 +37,14 @@ fun FriendsList(
|
||||
listState: LazyListState,
|
||||
maxLines: Int,
|
||||
padding: PaddingValues,
|
||||
onPhotoClicked: (url: String) -> Unit
|
||||
onPhotoClicked: (url: String) -> Unit,
|
||||
setCanScrollBackward: (Boolean) -> Unit
|
||||
) {
|
||||
LaunchedEffect(listState) {
|
||||
snapshotFlow { listState.canScrollBackward }
|
||||
.collect(setCanScrollBackward)
|
||||
}
|
||||
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
|
||||
val friends = uiFriends.toList()
|
||||
@@ -56,7 +64,6 @@ fun FriendsList(
|
||||
items = friends,
|
||||
key = UiFriend::userId,
|
||||
) { friend ->
|
||||
|
||||
FriendItem(
|
||||
friend = friend,
|
||||
maxLines = maxLines,
|
||||
|
||||
+40
-28
@@ -1,6 +1,5 @@
|
||||
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
|
||||
@@ -35,6 +34,7 @@ import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
@@ -47,7 +47,6 @@ 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
|
||||
@@ -69,7 +68,6 @@ 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
|
||||
|
||||
@Composable
|
||||
@@ -80,8 +78,6 @@ fun FriendsRoute(
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
|
||||
val prefs: SharedPreferences = koinInject()
|
||||
|
||||
val screenState by viewModel.screenState.collectAsStateWithLifecycle()
|
||||
val baseError by viewModel.baseError.collectAsStateWithLifecycle()
|
||||
val canPaginate by viewModel.canPaginate.collectAsStateWithLifecycle()
|
||||
@@ -101,23 +97,17 @@ fun FriendsRoute(
|
||||
screenState = screenState,
|
||||
baseError = baseError,
|
||||
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,
|
||||
onSaveScrollPosition = { index ->
|
||||
prefs.edit { putInt("friends_all_scroll_position", index) }
|
||||
},
|
||||
onSaveScrollOffsetPosition = { offset ->
|
||||
prefs.edit { putInt("friends_all_scroll_offset", offset) }
|
||||
}
|
||||
setScrollIndex = viewModel::setScrollIndex,
|
||||
setScrollOffset = viewModel::setScrollOffset,
|
||||
setScrollIndexOnline = viewModel::setScrollIndexOnline,
|
||||
setScrollOffsetOnline = viewModel::setScrollOffsetOnline,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
// TODO: 13/07/2024, Danil Nikolaev: support for online
|
||||
@OptIn(
|
||||
ExperimentalMaterial3Api::class,
|
||||
ExperimentalHazeMaterialsApi::class
|
||||
@@ -127,14 +117,14 @@ fun FriendsScreen(
|
||||
screenState: FriendsScreenState = FriendsScreenState.EMPTY,
|
||||
baseError: BaseError? = null,
|
||||
canPaginate: Boolean = false,
|
||||
firstVisibleItemIndex: Int = 0,
|
||||
firstVisibleItemScrollOffset: Int = 0,
|
||||
onSessionExpiredLogOutButtonClicked: () -> Unit = {},
|
||||
onPaginationConditionsMet: () -> Unit = {},
|
||||
onRefresh: () -> Unit = {},
|
||||
onPhotoClicked: (url: String) -> Unit = {},
|
||||
onSaveScrollPosition: (Int) -> Unit = {},
|
||||
onSaveScrollOffsetPosition: (Int) -> Unit = {}
|
||||
setScrollIndex: (Int) -> Unit,
|
||||
setScrollOffset: (Int) -> Unit,
|
||||
setScrollIndexOnline: (Int) -> Unit,
|
||||
setScrollOffsetOnline: (Int) -> Unit,
|
||||
) {
|
||||
val currentTheme = LocalThemeConfig.current
|
||||
|
||||
@@ -145,20 +135,36 @@ fun FriendsScreen(
|
||||
}
|
||||
|
||||
val listState = rememberLazyListState(
|
||||
initialFirstVisibleItemIndex = firstVisibleItemIndex,
|
||||
initialFirstVisibleItemScrollOffset = firstVisibleItemScrollOffset
|
||||
initialFirstVisibleItemIndex = screenState.scrollIndex,
|
||||
initialFirstVisibleItemScrollOffset = screenState.scrollOffset
|
||||
)
|
||||
val listStateOnline = rememberLazyListState(
|
||||
initialFirstVisibleItemIndex = screenState.scrollIndexOnline,
|
||||
initialFirstVisibleItemScrollOffset = screenState.scrollOffsetOnline
|
||||
)
|
||||
|
||||
LaunchedEffect(listState) {
|
||||
snapshotFlow { listState.firstVisibleItemIndex }
|
||||
.debounce(500L)
|
||||
.collectLatest(onSaveScrollPosition)
|
||||
.collectLatest(setScrollIndex)
|
||||
}
|
||||
|
||||
LaunchedEffect(listState) {
|
||||
snapshotFlow { listState.firstVisibleItemScrollOffset }
|
||||
.debounce(500L)
|
||||
.collectLatest(onSaveScrollOffsetPosition)
|
||||
.collectLatest(setScrollOffset)
|
||||
}
|
||||
|
||||
LaunchedEffect(listStateOnline) {
|
||||
snapshotFlow { listStateOnline.firstVisibleItemIndex }
|
||||
.debounce(500L)
|
||||
.collectLatest(setScrollIndexOnline)
|
||||
}
|
||||
|
||||
LaunchedEffect(listStateOnline) {
|
||||
snapshotFlow { listStateOnline.firstVisibleItemScrollOffset }
|
||||
.debounce(500L)
|
||||
.collectLatest(setScrollOffsetOnline)
|
||||
}
|
||||
|
||||
val paginationConditionMet by remember(canPaginate, listState) {
|
||||
@@ -177,8 +183,12 @@ fun FriendsScreen(
|
||||
|
||||
val hazeState = LocalHazeState.current
|
||||
|
||||
var canScrollBackward by remember {
|
||||
mutableStateOf(false)
|
||||
}
|
||||
|
||||
val topBarContainerColorAlpha by animateFloatAsState(
|
||||
targetValue = if (!currentTheme.enableBlur || !listState.canScrollBackward) 1f else 0f,
|
||||
targetValue = if (!currentTheme.enableBlur || !canScrollBackward) 1f else 0f,
|
||||
label = "toolbarColorAlpha",
|
||||
animationSpec = tween(
|
||||
durationMillis = 200,
|
||||
@@ -187,8 +197,7 @@ fun FriendsScreen(
|
||||
)
|
||||
|
||||
val topBarContainerColor by animateColorAsState(
|
||||
targetValue =
|
||||
if (currentTheme.enableBlur || !listState.canScrollBackward)
|
||||
targetValue = if (currentTheme.enableBlur || !canScrollBackward)
|
||||
MaterialTheme.colorScheme.surface
|
||||
else
|
||||
MaterialTheme.colorScheme.surfaceColorAtElevation(3.dp),
|
||||
@@ -341,10 +350,13 @@ fun FriendsScreen(
|
||||
}.fillMaxSize(),
|
||||
screenState = screenState,
|
||||
uiFriends = ImmutableList.copyOf(friendsToDisplay),
|
||||
listState = listState,
|
||||
listState = if (index == 0) listState else listStateOnline,
|
||||
maxLines = maxLines,
|
||||
padding = padding,
|
||||
onPhotoClicked = onPhotoClicked
|
||||
onPhotoClicked = onPhotoClicked,
|
||||
setCanScrollBackward = { can ->
|
||||
canScrollBackward = can
|
||||
}
|
||||
)
|
||||
|
||||
if (friendsToDisplay.isEmpty()) {
|
||||
|
||||
Reference in New Issue
Block a user