feat: smooth af scrolling in channel

- typing indicator doesnt go in front of messages
- scrolling has been made so much smoother
- loading older messages is literally seamless
- FAB for scrolling back down is now in proper FAB position
This commit is contained in:
Infi 2023-01-26 01:05:15 +01:00
parent 95b6b17353
commit 0e001033e4
1 changed files with 21 additions and 16 deletions

View File

@ -138,7 +138,7 @@ class ChannelScreenViewModel : ViewModel() {
addUserIfUnknown(message.author!!) addUserIfUnknown(message.author!!)
} }
_renderableMessages.add(message) _renderableMessages.add(0, message)
} }
override fun onStartTyping(typing: ChannelStartTypingFrame) { override fun onStartTyping(typing: ChannelStartTypingFrame) {
@ -178,7 +178,7 @@ class ChannelScreenViewModel : ViewModel() {
viewModelScope.launch { viewModelScope.launch {
val messages = arrayListOf<MessageSchema>() val messages = arrayListOf<MessageSchema>()
fetchMessagesFromChannel(channel!!.id!!, limit = 50, false).let { fetchMessagesFromChannel(channel!!.id!!, limit = 50, false).let {
it.messages!!.reversed().forEach { message -> it.messages!!.forEach { message ->
addUserIfUnknown(message.author ?: return@forEach) addUserIfUnknown(message.author ?: return@forEach)
if (!RevoltAPI.messageCache.containsKey(message.id)) { if (!RevoltAPI.messageCache.containsKey(message.id)) {
RevoltAPI.messageCache[message.id!!] = message RevoltAPI.messageCache[message.id!!] = message
@ -201,7 +201,7 @@ class ChannelScreenViewModel : ViewModel() {
channel!!.id!!, channel!!.id!!,
limit = 20, limit = 20,
true, true,
before = renderableMessages.first().id before = renderableMessages.last().id
).let { ).let {
it.messages!!.forEach { message -> it.messages!!.forEach { message ->
addUserIfUnknown(message.author ?: return@forEach) addUserIfUnknown(message.author ?: return@forEach)
@ -211,7 +211,7 @@ class ChannelScreenViewModel : ViewModel() {
messages.add(message) messages.add(message)
} }
} }
setRenderableMessages(messages + renderableMessages) setRenderableMessages(renderableMessages + messages)
} }
} }
@ -453,21 +453,27 @@ fun ChannelScreen(
val isScrolledToBottom = remember(lazyListState) { val isScrolledToBottom = remember(lazyListState) {
derivedStateOf { derivedStateOf {
(lazyListState.layoutInfo.visibleItemsInfo.lastOrNull()?.index lazyListState.firstVisibleItemIndex <= 5
?: 0) >= viewModel.renderableMessages.size - 5
} }
} }
LaunchedEffect(viewModel.renderableMessages.size) { LaunchedEffect(viewModel.renderableMessages.size) {
if (isScrolledToBottom.value) { if (isScrolledToBottom.value) {
coroutineScope.launch { coroutineScope.launch {
lazyListState.scrollToItem(viewModel.renderableMessages.size) lazyListState.animateScrollToItem(0)
} }
} }
} }
Box(modifier = Modifier.weight(1f)) { Box(
LazyColumn(state = lazyListState) { modifier = Modifier.weight(1f),
contentAlignment = Alignment.BottomEnd
) {
LazyColumn(state = lazyListState, reverseLayout = true) {
items(viewModel.renderableMessages) { message ->
Message(message)
}
item { item {
Button( Button(
onClick = { onClick = {
@ -482,21 +488,20 @@ fun ChannelScreen(
Text("Load older") Text("Load older")
} }
} }
items(viewModel.renderableMessages) { message ->
Message(message)
}
} }
androidx.compose.animation.AnimatedVisibility( androidx.compose.animation.AnimatedVisibility(
!isScrolledToBottom.value, !isScrolledToBottom.value,
enter = slideInHorizontally( enter = slideInHorizontally(
animationSpec = RevoltTweenInt, animationSpec = RevoltTweenInt,
initialOffsetX = { -it }, initialOffsetX = { it },
) + fadeIn(animationSpec = RevoltTweenFloat), ) + fadeIn(animationSpec = RevoltTweenFloat),
exit = slideOutHorizontally( exit = slideOutHorizontally(
animationSpec = RevoltTweenInt, animationSpec = RevoltTweenInt,
targetOffsetX = { -it }, targetOffsetX = { it },
) + fadeOut(animationSpec = RevoltTweenFloat) ) + fadeOut(animationSpec = RevoltTweenFloat),
modifier = Modifier
.align(Alignment.BottomEnd)
) { ) {
ExtendedFloatingActionButton( ExtendedFloatingActionButton(
modifier = Modifier modifier = Modifier
@ -513,7 +518,7 @@ fun ChannelScreen(
}, },
onClick = { onClick = {
coroutineScope.launch { coroutineScope.launch {
lazyListState.animateScrollToItem(viewModel.renderableMessages.size) lazyListState.animateScrollToItem(0)
} }
}, },
contentColor = MaterialTheme.colorScheme.onPrimary, contentColor = MaterialTheme.colorScheme.onPrimary,