feat: channel info sheet (shows description)

This commit is contained in:
Infi 2023-02-06 01:00:41 +01:00
parent 493a542ae9
commit 9c26a7ab93
5 changed files with 233 additions and 137 deletions

View File

@ -1,6 +1,7 @@
package chat.revolt.screens.chat package chat.revolt.screens.chat
import androidx.compose.animation.* import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.Crossfade
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollState
@ -31,8 +32,13 @@ import chat.revolt.api.realtime.RealtimeSocket
import chat.revolt.api.schemas.ChannelType import chat.revolt.api.schemas.ChannelType
import chat.revolt.components.chat.DisconnectedNotice import chat.revolt.components.chat.DisconnectedNotice
import chat.revolt.components.screens.chat.* import chat.revolt.components.screens.chat.*
import chat.revolt.screens.chat.sheets.ChannelInfoSheet
import chat.revolt.screens.chat.views.ChannelScreen import chat.revolt.screens.chat.views.ChannelScreen
import chat.revolt.screens.chat.views.HomeScreen import chat.revolt.screens.chat.views.HomeScreen
import com.google.accompanist.navigation.material.ExperimentalMaterialNavigationApi
import com.google.accompanist.navigation.material.ModalBottomSheetLayout
import com.google.accompanist.navigation.material.bottomSheet
import com.google.accompanist.navigation.material.rememberBottomSheetNavigator
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
class ChatRouterViewModel : ViewModel() { class ChatRouterViewModel : ViewModel() {
@ -65,129 +71,138 @@ class ChatRouterViewModel : ViewModel() {
} }
} }
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterialNavigationApi::class)
@Composable @Composable
fun ChatRouterScreen(topNav: NavController, viewModel: ChatRouterViewModel = viewModel()) { fun ChatRouterScreen(topNav: NavController, viewModel: ChatRouterViewModel = viewModel()) {
val channelDrawerState = rememberDrawerState(DrawerValue.Closed) val channelDrawerState = rememberDrawerState(DrawerValue.Closed)
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
val navController = rememberNavController()
val bottomSheetNavigator = rememberBottomSheetNavigator()
val navController = rememberNavController(bottomSheetNavigator)
val navBackStackEntry by navController.currentBackStackEntryAsState() val navBackStackEntry by navController.currentBackStackEntryAsState()
Column { ModalBottomSheetLayout(bottomSheetNavigator = bottomSheetNavigator) {
AnimatedVisibility(visible = RealtimeSocket.disconnectionState != DisconnectionState.Connected) { Column {
DisconnectedNotice( AnimatedVisibility(visible = RealtimeSocket.disconnectionState != DisconnectionState.Connected) {
state = RealtimeSocket.disconnectionState, DisconnectedNotice(
onReconnect = { state = RealtimeSocket.disconnectionState,
RealtimeSocket.updateDisconnectionState(DisconnectionState.Reconnecting) onReconnect = {
scope.launch { RevoltAPI.connectWS() } RealtimeSocket.updateDisconnectionState(DisconnectionState.Reconnecting)
}) scope.launch { RevoltAPI.connectWS() }
} })
DismissibleNavigationDrawer( }
drawerState = channelDrawerState,
drawerContent = { DismissibleNavigationDrawer(
ModalDrawerSheet( drawerState = channelDrawerState,
drawerContainerColor = MaterialTheme.colorScheme.surfaceColorAtElevation(1.dp) drawerContent = {
) { ModalDrawerSheet(
Column(Modifier.fillMaxWidth()) { drawerContainerColor = MaterialTheme.colorScheme.surfaceColorAtElevation(1.dp)
Row { ) {
Column( Column(Modifier.fillMaxWidth()) {
modifier = Modifier Row {
.fillMaxHeight() Column(
.verticalScroll(rememberScrollState()) modifier = Modifier
.background(MaterialTheme.colorScheme.surfaceColorAtElevation(2.dp)) .fillMaxHeight()
) { .verticalScroll(rememberScrollState())
DrawerServerlikeIcon( .background(
onClick = { MaterialTheme.colorScheme.surfaceColorAtElevation(
viewModel.navigateToServer("home", navController) 2.dp
} )
)
) { ) {
Icon( DrawerServerlikeIcon(
Icons.Default.Home, onClick = {
contentDescription = stringResource(id = R.string.home), viewModel.navigateToServer("home", navController)
modifier = Modifier.padding(4.dp) }
) ) {
Icon(
Icons.Default.Home,
contentDescription = stringResource(id = R.string.home),
modifier = Modifier.padding(4.dp)
)
}
ServerDrawerSeparator()
RevoltAPI.serverCache.values
.sortedBy { it.id }
.forEach { server ->
if (server.name == null) return@forEach
DrawerServer(
iconId = server.icon?.id,
serverName = server.name
) {
viewModel.navigateToServer(
server.id!!,
navController
)
}
}
} }
ServerDrawerSeparator() Crossfade(targetState = viewModel.currentServer) {
Column(
RevoltAPI.serverCache.values Modifier
.sortedBy { it.id } .weight(1f)
.forEach { server -> ) {
if (server.name == null) return@forEach if (it == "home") {
Column(
DrawerServer( Modifier
iconId = server.icon?.id, .weight(1f)
serverName = server.name .verticalScroll(rememberScrollState())
) { ) {
viewModel.navigateToServer( RevoltAPI.channelCache.values.filter { it.channelType == ChannelType.Group }
server.id!!, .forEach { channel ->
navController DrawerChannel(
) name = channel.name
} ?: "GDM #${channel.id}",
} channelType = ChannelType.Group,
} selected = channel.id == (navBackStackEntry?.arguments?.getString(
"channelId"
Crossfade(targetState = viewModel.currentServer) { ) ?: false),
Column( onClick = {
Modifier navController.navigate("channel/${channel.id}")
.weight(1f) scope.launch {
) { channelDrawerState.close()
if (it == "home") {
Column(
Modifier
.weight(1f)
.verticalScroll(rememberScrollState())
) {
RevoltAPI.channelCache.values.filter { it.channelType == ChannelType.Group }
.forEach { channel ->
DrawerChannel(
name = channel.name
?: "GDM #${channel.id}",
channelType = ChannelType.Group,
selected = channel.id == (navBackStackEntry?.arguments?.getString(
"channelId"
) ?: false),
onClick = {
navController.navigate("channel/${channel.id}")
scope.launch {
channelDrawerState.close()
}
}
)
}
}
} else {
val server = RevoltAPI.serverCache[it]
Text(
text = server?.name
?: stringResource(R.string.unknown),
fontWeight = FontWeight.Black,
fontSize = 24.sp,
modifier = Modifier.padding(16.dp)
)
Column(
Modifier
.weight(1f)
.verticalScroll(rememberScrollState())
) {
server?.channels?.forEach { channelId ->
RevoltAPI.channelCache[channelId]?.let { ch ->
DrawerChannel(
name = ch.name!!,
channelType = ch.channelType!!,
selected = navBackStackEntry?.arguments?.getString(
"channelId"
) == ch.id,
onClick = {
scope.launch { channelDrawerState.close() }
navController.navigate("channel/${ch.id}") {
popUpTo("home") {
inclusive = true
} }
} }
}) )
}
}
} else {
val server = RevoltAPI.serverCache[it]
Text(
text = server?.name
?: stringResource(R.string.unknown),
fontWeight = FontWeight.Black,
fontSize = 24.sp,
modifier = Modifier.padding(16.dp)
)
Column(
Modifier
.weight(1f)
.verticalScroll(rememberScrollState())
) {
server?.channels?.forEach { channelId ->
RevoltAPI.channelCache[channelId]?.let { ch ->
DrawerChannel(
name = ch.name!!,
channelType = ch.channelType!!,
selected = navBackStackEntry?.arguments?.getString(
"channelId"
) == ch.id,
onClick = {
scope.launch { channelDrawerState.close() }
navController.navigate("channel/${ch.id}") {
popUpTo("home") {
inclusive = true
}
}
})
}
} }
} }
} }
@ -196,28 +211,40 @@ fun ChatRouterScreen(topNav: NavController, viewModel: ChatRouterViewModel = vie
} }
} }
} }
} },
}, modifier = Modifier.weight(1f),
modifier = Modifier.weight(1f), ) {
) { Column(Modifier.fillMaxSize()) {
Column(Modifier.fillMaxSize()) { NavHost(navController = navController, startDestination = "home") {
NavHost(navController = navController, startDestination = "home") { composable("home") {
composable("home") { HomeScreen(navController = topNav)
HomeScreen(navController = topNav) }
} composable("channel/{channelId}") { backStackEntry ->
composable("channel/{channelId}") { backStackEntry -> val channelId = backStackEntry.arguments?.getString("channelId")
val channelId = backStackEntry.arguments?.getString("channelId") if (channelId != null) {
if (channelId != null) { ChannelScreen(
ChannelScreen(navController, channelId = channelId) navController = navController,
channelId = channelId
)
}
}
bottomSheet("channel/{channelId}/info") { backStackEntry ->
val channelId = backStackEntry.arguments?.getString("channelId")
if (channelId != null) {
ChannelInfoSheet(
navController = navController,
channelId = channelId
)
}
} }
} }
} }
} }
}
BottomNavigation( BottomNavigation(
navController = topNav, navController = topNav,
show = channelDrawerState.currentValue == DrawerValue.Open, show = channelDrawerState.currentValue == DrawerValue.Open,
) )
}
} }
} }

View File

@ -0,0 +1,66 @@
package chat.revolt.screens.chat.sheets
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.navigation.NavController
import chat.revolt.R
import chat.revolt.api.RevoltAPI
import chat.revolt.api.schemas.ChannelType
import chat.revolt.components.generic.PageHeader
import chat.revolt.components.screens.chat.ChannelIcon
@Composable
fun ChannelInfoSheet(
navController: NavController,
channelId: String,
) {
val channel = RevoltAPI.channelCache[channelId]
if (channel == null) {
navController.popBackStack()
return
}
Surface(
modifier = Modifier.fillMaxSize(),
) {
Column(
modifier = Modifier
.padding(horizontal = 16.dp)
.verticalScroll(rememberScrollState()),
) {
Row(
verticalAlignment = Alignment.CenterVertically
) {
ChannelIcon(
channelType = channel.channelType ?: ChannelType.TextChannel,
modifier = Modifier.size(32.dp)
)
PageHeader(
text = channel.name ?: channel.id ?: "",
modifier = Modifier.offset((-4).dp, 0.dp)
)
}
Spacer(modifier = Modifier.height(16.dp))
Text(
text = stringResource(id = R.string.channel_info_sheet_description),
style = MaterialTheme.typography.bodySmall,
modifier = Modifier.padding(bottom = 10.dp)
)
Text(
text = channel.description
?: stringResource(id = R.string.channel_info_sheet_description_empty),
modifier = Modifier.padding(bottom = 10.dp)
)
}
}
}

View File

@ -61,14 +61,14 @@ class ChannelScreenViewModel : ViewModel() {
get() = _channel get() = _channel
private var _callback = mutableStateOf<RealtimeSocket.ChannelCallback?>(null) private var _callback = mutableStateOf<RealtimeSocket.ChannelCallback?>(null)
val callback: RealtimeSocket.ChannelCallback? private val callback: RealtimeSocket.ChannelCallback?
get() = _callback.value get() = _callback.value
private var _renderableMessages = mutableStateListOf<MessageSchema>() private var _renderableMessages = mutableStateListOf<MessageSchema>()
val renderableMessages: List<MessageSchema> val renderableMessages: List<MessageSchema>
get() = _renderableMessages get() = _renderableMessages
fun setRenderableMessages(messages: List<MessageSchema>) { private fun setRenderableMessages(messages: List<MessageSchema>) {
_renderableMessages.clear() _renderableMessages.clear()
_renderableMessages.addAll(messages) _renderableMessages.addAll(messages)
} }
@ -89,7 +89,7 @@ class ChannelScreenViewModel : ViewModel() {
val attachments: List<FileArgs> val attachments: List<FileArgs>
get() = _attachments get() = _attachments
fun setAttachments(attachments: List<FileArgs>) { private fun setAttachments(attachments: List<FileArgs>) {
_attachments.clear() _attachments.clear()
_attachments.addAll(attachments) _attachments.addAll(attachments)
} }
@ -180,7 +180,7 @@ class ChannelScreenViewModel : ViewModel() {
viewModelScope.launch { viewModelScope.launch {
val messages = arrayListOf<MessageSchema>() val messages = arrayListOf<MessageSchema>()
if (!renderableMessages.isEmpty()) { if (renderableMessages.isNotEmpty()) {
fetchMessagesFromChannel( fetchMessagesFromChannel(
channel!!.id!!, channel!!.id!!,
limit = 50, limit = 50,
@ -383,7 +383,7 @@ fun ChannelScreen(
val totalItemsNumber = layoutInfo.totalItemsCount val totalItemsNumber = layoutInfo.totalItemsCount
val lastVisibleItemIndex = val lastVisibleItemIndex =
(layoutInfo.visibleItemsInfo.lastOrNull()?.index ?: 0) + 1 (layoutInfo.visibleItemsInfo.lastOrNull()?.index ?: 0) + 1
val buffer = if (totalItemsNumber > 6) 6 else 0 val buffer = 6
lastVisibleItemIndex > (totalItemsNumber - buffer) lastVisibleItemIndex > (totalItemsNumber - buffer)
} }

View File

@ -97,7 +97,7 @@ val RevoltTypography = Typography(
), ),
bodySmall = TextStyle( bodySmall = TextStyle(
fontFamily = Inter, fontFamily = Inter,
fontWeight = FontWeight.Normal, fontWeight = FontWeight.Bold,
fontSize = 12.sp fontSize = 12.sp
) )
) )

View File

@ -113,6 +113,9 @@
<string name="copy">Copy</string> <string name="copy">Copy</string>
<string name="copied">Copied to clipboard</string> <string name="copied">Copied to clipboard</string>
<string name="channel_info_sheet_description">Channel description</string>
<string name="channel_info_sheet_description_empty">There hasn\'t been a description set for this channel yet.</string>
<string name="settings_appearance">Appearance</string> <string name="settings_appearance">Appearance</string>
<string name="settings_appearance_theme">Theme</string> <string name="settings_appearance_theme">Theme</string>
<string name="settings_appearance_theme_none">System</string> <string name="settings_appearance_theme_none">System</string>