diff --git a/app/src/main/java/chat/revolt/screens/chat/ChatRouterScreen.kt b/app/src/main/java/chat/revolt/screens/chat/ChatRouterScreen.kt index 9f628e96..5961d861 100644 --- a/app/src/main/java/chat/revolt/screens/chat/ChatRouterScreen.kt +++ b/app/src/main/java/chat/revolt/screens/chat/ChatRouterScreen.kt @@ -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, + ) + } } } \ No newline at end of file diff --git a/app/src/main/java/chat/revolt/screens/chat/sheets/ChannelInfoSheet.kt b/app/src/main/java/chat/revolt/screens/chat/sheets/ChannelInfoSheet.kt new file mode 100644 index 00000000..823dde7b --- /dev/null +++ b/app/src/main/java/chat/revolt/screens/chat/sheets/ChannelInfoSheet.kt @@ -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) + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/chat/revolt/screens/chat/views/ChannelScreen.kt b/app/src/main/java/chat/revolt/screens/chat/views/ChannelScreen.kt index e1dd5dd3..d87bc619 100644 --- a/app/src/main/java/chat/revolt/screens/chat/views/ChannelScreen.kt +++ b/app/src/main/java/chat/revolt/screens/chat/views/ChannelScreen.kt @@ -61,14 +61,14 @@ class ChannelScreenViewModel : ViewModel() { get() = _channel private var _callback = mutableStateOf(null) - val callback: RealtimeSocket.ChannelCallback? + private val callback: RealtimeSocket.ChannelCallback? get() = _callback.value private var _renderableMessages = mutableStateListOf() val renderableMessages: List get() = _renderableMessages - fun setRenderableMessages(messages: List) { + private fun setRenderableMessages(messages: List) { _renderableMessages.clear() _renderableMessages.addAll(messages) } @@ -89,7 +89,7 @@ class ChannelScreenViewModel : ViewModel() { val attachments: List get() = _attachments - fun setAttachments(attachments: List) { + private fun setAttachments(attachments: List) { _attachments.clear() _attachments.addAll(attachments) } @@ -180,7 +180,7 @@ class ChannelScreenViewModel : ViewModel() { viewModelScope.launch { val messages = arrayListOf() - 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) } diff --git a/app/src/main/java/chat/revolt/ui/theme/Typography.kt b/app/src/main/java/chat/revolt/ui/theme/Typography.kt index fe30ede7..b0ecc8cf 100644 --- a/app/src/main/java/chat/revolt/ui/theme/Typography.kt +++ b/app/src/main/java/chat/revolt/ui/theme/Typography.kt @@ -97,7 +97,7 @@ val RevoltTypography = Typography( ), bodySmall = TextStyle( fontFamily = Inter, - fontWeight = FontWeight.Normal, + fontWeight = FontWeight.Bold, fontSize = 12.sp ) ) \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 9f4ae91c..457c2d48 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -113,6 +113,9 @@ Copy Copied to clipboard + Channel description + There hasn\'t been a description set for this channel yet. + Appearance Theme System