feat: autoscroll and scroll to bottom

This commit is contained in:
Infi 2023-01-07 02:44:58 +01:00
parent efdc035406
commit 5d5e396a26
5 changed files with 87 additions and 38 deletions

View File

@ -9,7 +9,6 @@ import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import chat.revolt.BuildConfig
import com.bumptech.glide.integration.compose.ExperimentalGlideComposeApi import com.bumptech.glide.integration.compose.ExperimentalGlideComposeApi
import com.bumptech.glide.integration.compose.GlideImage import com.bumptech.glide.integration.compose.GlideImage
@ -39,7 +38,3 @@ fun RemoteImage(
.height(pxAsDp(height)), .height(pxAsDp(height)),
) )
} }
fun drawableResource(id: Int): String {
return "android.resource://" + BuildConfig.APPLICATION_ID + "/" + id
}

View File

@ -4,6 +4,7 @@ import android.annotation.SuppressLint
import android.content.Context import android.content.Context
import android.net.ConnectivityManager import android.net.ConnectivityManager
import android.net.NetworkCapabilities import android.net.NetworkCapabilities
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
@ -12,6 +13,7 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
@ -19,8 +21,6 @@ import androidx.lifecycle.viewModelScope
import androidx.navigation.NavController import androidx.navigation.NavController
import chat.revolt.R import chat.revolt.R
import chat.revolt.api.RevoltAPI import chat.revolt.api.RevoltAPI
import chat.revolt.components.generic.RemoteImage
import chat.revolt.components.generic.drawableResource
import chat.revolt.components.screens.splash.DisconnectedScreen import chat.revolt.components.screens.splash.DisconnectedScreen
import chat.revolt.persistence.KVStorage import chat.revolt.persistence.KVStorage
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
@ -110,9 +110,9 @@ fun SplashScreen(navController: NavController, viewModel: SplashScreenViewModel
verticalArrangement = Arrangement.Center, verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally horizontalAlignment = Alignment.CenterHorizontally
) { ) {
RemoteImage( Image(
url = drawableResource(R.drawable.revolt_logo_wide), painter = painterResource(id = R.drawable.revolt_logo_wide),
description = "Revolt Logo", contentDescription = "Revolt Logo",
contentScale = ContentScale.Fit, contentScale = ContentScale.Fit,
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()

View File

@ -10,7 +10,9 @@ import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.KeyboardArrowDown
import androidx.compose.material3.* import androidx.compose.material3.*
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
@ -71,6 +73,11 @@ class ChannelScreenViewModel : ViewModel() {
val renderableMessages: List<MessageSchema> val renderableMessages: List<MessageSchema>
get() = _renderableMessages get() = _renderableMessages
fun setRenderableMessages(messages: List<MessageSchema>) {
_renderableMessages.clear()
_renderableMessages.addAll(messages)
}
private var _typingUsers = mutableStateListOf<String>() private var _typingUsers = mutableStateListOf<String>()
val typingUsers: List<String> val typingUsers: List<String>
get() = _typingUsers get() = _typingUsers
@ -165,15 +172,17 @@ class ChannelScreenViewModel : ViewModel() {
_renderableMessages.clear() _renderableMessages.clear()
viewModelScope.launch { viewModelScope.launch {
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!!.reversed().forEach { message ->
addUserIfUnknown(message.author!!) 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
} }
_renderableMessages.add(message) messages.add(message)
} }
} }
setRenderableMessages(renderableMessages + messages)
} }
} }
@ -183,6 +192,7 @@ class ChannelScreenViewModel : ViewModel() {
} }
viewModelScope.launch { viewModelScope.launch {
val messages = arrayListOf<MessageSchema>()
fetchMessagesFromChannel( fetchMessagesFromChannel(
channel!!.id!!, channel!!.id!!,
limit = 20, limit = 20,
@ -190,13 +200,14 @@ class ChannelScreenViewModel : ViewModel() {
before = renderableMessages.first().id before = renderableMessages.first().id
).let { ).let {
it.messages!!.forEach { message -> it.messages!!.forEach { message ->
addUserIfUnknown(message.author!!) 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
} }
_renderableMessages.add(0, message) messages.add(message)
} }
} }
setRenderableMessages(messages + renderableMessages)
} }
} }
@ -348,7 +359,7 @@ fun ChannelScreen(
val channel = viewModel.channel val channel = viewModel.channel
val context = LocalContext.current val context = LocalContext.current
val scrollState = rememberScrollState() val lazyListState = rememberLazyListState()
val coroutineScope = rememberCoroutineScope() val coroutineScope = rememberCoroutineScope()
val channelInfoOpen = remember { mutableStateOf(false) } val channelInfoOpen = remember { mutableStateOf(false) }
@ -375,7 +386,6 @@ fun ChannelScreen(
) )
} }
} }
} }
} }
@ -436,23 +446,65 @@ fun ChannelScreen(
) )
} }
LazyColumn(Modifier.weight(1f)) {
item { val isScrolledToBottom = remember(lazyListState) {
Button( derivedStateOf {
onClick = { (lazyListState.layoutInfo.visibleItemsInfo.lastOrNull()?.index
coroutineScope.launch { ?: 0) >= viewModel.renderableMessages.size - 5
viewModel.fetchOlderMessages() }
} }
},
modifier = Modifier LaunchedEffect(viewModel.renderableMessages.size) {
.fillMaxWidth() if (isScrolledToBottom.value) {
.padding(vertical = 16.dp, horizontal = 8.dp) coroutineScope.launch {
) { lazyListState.scrollToItem(viewModel.renderableMessages.size)
Text("Load older")
} }
} }
items(viewModel.renderableMessages) { message -> }
Message(message)
Box(modifier = Modifier.weight(1f)) {
LazyColumn(state = lazyListState) {
item {
Button(
onClick = {
coroutineScope.launch {
viewModel.fetchOlderMessages()
}
},
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 16.dp, horizontal = 8.dp)
) {
Text("Load older")
}
}
items(viewModel.renderableMessages) { message ->
Message(message)
}
}
androidx.compose.animation.AnimatedVisibility(!isScrolledToBottom.value) {
ExtendedFloatingActionButton(
modifier = Modifier
.align(Alignment.BottomEnd)
.padding(16.dp),
text = {
Text(stringResource(R.string.scroll_to_bottom))
},
icon = {
Icon(
imageVector = Icons.Default.KeyboardArrowDown,
contentDescription = stringResource(R.string.scroll_to_bottom)
)
},
onClick = {
coroutineScope.launch {
lazyListState.animateScrollToItem(viewModel.renderableMessages.size)
}
},
contentColor = MaterialTheme.colorScheme.onSecondary,
containerColor = MaterialTheme.colorScheme.secondary
)
} }
} }
@ -504,7 +556,7 @@ fun ChannelScreen(
channelType = channel.channelType!!, channelType = channel.channelType!!,
channelName = channel.name ?: channel.id!!, channelName = channel.name ?: channel.id!!,
forceSendButton = viewModel.attachments.isNotEmpty(), forceSendButton = viewModel.attachments.isNotEmpty(),
disabled = viewModel.sendingMessage disabled = viewModel.attachments.isNotEmpty() && viewModel.sendingMessage
) )
} }
} }

View File

@ -1,5 +1,6 @@
package chat.revolt.screens.login package chat.revolt.screens.login
import androidx.compose.foundation.Image
import chat.revolt.R import chat.revolt.R
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.material3.Button import androidx.compose.material3.Button
@ -11,14 +12,13 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.navigation.NavController import androidx.navigation.NavController
import chat.revolt.components.generic.RemoteImage
import chat.revolt.components.generic.drawableResource
@Composable @Composable
fun GreeterScreen(navController: NavController) { fun GreeterScreen(navController: NavController) {
@ -37,9 +37,9 @@ fun GreeterScreen(navController: NavController) {
verticalArrangement = Arrangement.Center, verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally horizontalAlignment = Alignment.CenterHorizontally
) { ) {
RemoteImage( Image(
url = drawableResource(R.drawable.revolt_logo_wide), painter = painterResource(id = R.drawable.revolt_logo_wide),
description = "Revolt Logo", contentDescription = "Revolt Logo",
contentScale = ContentScale.Fit, contentScale = ContentScale.Fit,
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()

View File

@ -104,4 +104,6 @@
<string name="no_connection">No connection</string> <string name="no_connection">No connection</string>
<string name="no_connection_message">You are not connected to the internet. Please check your connection and try again.</string> <string name="no_connection_message">You are not connected to the internet. Please check your connection and try again.</string>
<string name="tap_to_retry">Tap to retry</string> <string name="tap_to_retry">Tap to retry</string>
<string name="scroll_to_bottom">Scroll to bottom</string>
</resources> </resources>