feat: channel info sheet (shows description)
This commit is contained in:
parent
493a542ae9
commit
9c26a7ab93
|
|
@ -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,
|
||||||
)
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -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)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue