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
import androidx.compose.animation.*
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.Crossfade
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
@ -31,8 +32,13 @@ import chat.revolt.api.realtime.RealtimeSocket
import chat.revolt.api.schemas.ChannelType
import chat.revolt.components.chat.DisconnectedNotice
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.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
class ChatRouterViewModel : ViewModel() {
@ -65,129 +71,138 @@ class ChatRouterViewModel : ViewModel() {
}
}
@OptIn(ExperimentalMaterial3Api::class)
@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterialNavigationApi::class)
@Composable
fun ChatRouterScreen(topNav: NavController, viewModel: ChatRouterViewModel = viewModel()) {
val channelDrawerState = rememberDrawerState(DrawerValue.Closed)
val scope = rememberCoroutineScope()
val navController = rememberNavController()
val bottomSheetNavigator = rememberBottomSheetNavigator()
val navController = rememberNavController(bottomSheetNavigator)
val navBackStackEntry by navController.currentBackStackEntryAsState()
Column {
AnimatedVisibility(visible = RealtimeSocket.disconnectionState != DisconnectionState.Connected) {
DisconnectedNotice(
state = RealtimeSocket.disconnectionState,
onReconnect = {
RealtimeSocket.updateDisconnectionState(DisconnectionState.Reconnecting)
scope.launch { RevoltAPI.connectWS() }
})
}
DismissibleNavigationDrawer(
drawerState = channelDrawerState,
drawerContent = {
ModalDrawerSheet(
drawerContainerColor = MaterialTheme.colorScheme.surfaceColorAtElevation(1.dp)
) {
Column(Modifier.fillMaxWidth()) {
Row {
Column(
modifier = Modifier
.fillMaxHeight()
.verticalScroll(rememberScrollState())
.background(MaterialTheme.colorScheme.surfaceColorAtElevation(2.dp))
) {
DrawerServerlikeIcon(
onClick = {
viewModel.navigateToServer("home", navController)
}
ModalBottomSheetLayout(bottomSheetNavigator = bottomSheetNavigator) {
Column {
AnimatedVisibility(visible = RealtimeSocket.disconnectionState != DisconnectionState.Connected) {
DisconnectedNotice(
state = RealtimeSocket.disconnectionState,
onReconnect = {
RealtimeSocket.updateDisconnectionState(DisconnectionState.Reconnecting)
scope.launch { RevoltAPI.connectWS() }
})
}
DismissibleNavigationDrawer(
drawerState = channelDrawerState,
drawerContent = {
ModalDrawerSheet(
drawerContainerColor = MaterialTheme.colorScheme.surfaceColorAtElevation(1.dp)
) {
Column(Modifier.fillMaxWidth()) {
Row {
Column(
modifier = Modifier
.fillMaxHeight()
.verticalScroll(rememberScrollState())
.background(
MaterialTheme.colorScheme.surfaceColorAtElevation(
2.dp
)
)
) {
Icon(
Icons.Default.Home,
contentDescription = stringResource(id = R.string.home),
modifier = Modifier.padding(4.dp)
)
DrawerServerlikeIcon(
onClick = {
viewModel.navigateToServer("home", navController)
}
) {
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()
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
)
}
}
}
Crossfade(targetState = viewModel.currentServer) {
Column(
Modifier
.weight(1f)
) {
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
Crossfade(targetState = viewModel.currentServer) {
Column(
Modifier
.weight(1f)
) {
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
}
}
})
}
}
}
}
@ -196,28 +211,40 @@ fun ChatRouterScreen(topNav: NavController, viewModel: ChatRouterViewModel = vie
}
}
}
}
},
modifier = Modifier.weight(1f),
) {
Column(Modifier.fillMaxSize()) {
NavHost(navController = navController, startDestination = "home") {
composable("home") {
HomeScreen(navController = topNav)
}
composable("channel/{channelId}") { backStackEntry ->
val channelId = backStackEntry.arguments?.getString("channelId")
if (channelId != null) {
ChannelScreen(navController, channelId = channelId)
},
modifier = Modifier.weight(1f),
) {
Column(Modifier.fillMaxSize()) {
NavHost(navController = navController, startDestination = "home") {
composable("home") {
HomeScreen(navController = topNav)
}
composable("channel/{channelId}") { backStackEntry ->
val channelId = backStackEntry.arguments?.getString("channelId")
if (channelId != null) {
ChannelScreen(
navController = navController,
channelId = channelId
)
}
}
bottomSheet("channel/{channelId}/info") { backStackEntry ->
val channelId = backStackEntry.arguments?.getString("channelId")
if (channelId != null) {
ChannelInfoSheet(
navController = navController,
channelId = channelId
)
}
}
}
}
}
}
BottomNavigation(
navController = topNav,
show = channelDrawerState.currentValue == DrawerValue.Open,
)
BottomNavigation(
navController = topNav,
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
private var _callback = mutableStateOf<RealtimeSocket.ChannelCallback?>(null)
val callback: RealtimeSocket.ChannelCallback?
private val callback: RealtimeSocket.ChannelCallback?
get() = _callback.value
private var _renderableMessages = mutableStateListOf<MessageSchema>()
val renderableMessages: List<MessageSchema>
get() = _renderableMessages
fun setRenderableMessages(messages: List<MessageSchema>) {
private fun setRenderableMessages(messages: List<MessageSchema>) {
_renderableMessages.clear()
_renderableMessages.addAll(messages)
}
@ -89,7 +89,7 @@ class ChannelScreenViewModel : ViewModel() {
val attachments: List<FileArgs>
get() = _attachments
fun setAttachments(attachments: List<FileArgs>) {
private fun setAttachments(attachments: List<FileArgs>) {
_attachments.clear()
_attachments.addAll(attachments)
}
@ -180,7 +180,7 @@ class ChannelScreenViewModel : ViewModel() {
viewModelScope.launch {
val messages = arrayListOf<MessageSchema>()
if (!renderableMessages.isEmpty()) {
if (renderableMessages.isNotEmpty()) {
fetchMessagesFromChannel(
channel!!.id!!,
limit = 50,
@ -383,7 +383,7 @@ fun ChannelScreen(
val totalItemsNumber = layoutInfo.totalItemsCount
val lastVisibleItemIndex =
(layoutInfo.visibleItemsInfo.lastOrNull()?.index ?: 0) + 1
val buffer = if (totalItemsNumber > 6) 6 else 0
val buffer = 6
lastVisibleItemIndex > (totalItemsNumber - buffer)
}

View File

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

View File

@ -113,6 +113,9 @@
<string name="copy">Copy</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_theme">Theme</string>
<string name="settings_appearance_theme_none">System</string>