feat: autoscroll and scroll to bottom
This commit is contained in:
parent
efdc035406
commit
5d5e396a26
|
|
@ -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
|
|
||||||
}
|
|
||||||
|
|
@ -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()
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
Loading…
Reference in New Issue