diff --git a/app/src/main/java/chat/revolt/components/chat/Message.kt b/app/src/main/java/chat/revolt/components/chat/Message.kt index 126943ea..0202d201 100644 --- a/app/src/main/java/chat/revolt/components/chat/Message.kt +++ b/app/src/main/java/chat/revolt/components/chat/Message.kt @@ -11,11 +11,24 @@ import android.widget.Toast import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.contract.ActivityResultContracts import androidx.browser.customtabs.CustomTabsIntent -import androidx.compose.foundation.* -import androidx.compose.foundation.layout.* +import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.combinedClickable +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Edit -import androidx.compose.material3.* +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.Icon +import androidx.compose.material3.LocalContentColor +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -115,6 +128,8 @@ fun Message( truncate: Boolean = false, parse: (MessageSchema) -> SpannableStringBuilder = { SpannableStringBuilder(it.content) }, onMessageContextMenu: () -> Unit = {}, + canReply: Boolean = false, + onReply: () -> Unit = {}, ) { val author = RevoltAPI.userCache[message.author] ?: return CircularProgressIndicator() val context = LocalContext.current diff --git a/app/src/main/java/chat/revolt/components/screens/chat/ChannelHeader.kt b/app/src/main/java/chat/revolt/components/screens/chat/ChannelHeader.kt new file mode 100644 index 00000000..7859d079 --- /dev/null +++ b/app/src/main/java/chat/revolt/components/screens/chat/ChannelHeader.kt @@ -0,0 +1,84 @@ +package chat.revolt.components.screens.chat + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.KeyboardArrowRight +import androidx.compose.material.icons.filled.Menu +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.alpha +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import chat.revolt.R +import chat.revolt.api.schemas.Channel + +@Composable +fun ChannelHeader( + channel: Channel, + onChannelClick: (String) -> Unit, + onToggleDrawer: () -> Unit, +) { + Row( + modifier = Modifier + .fillMaxWidth() + .clickable { + channel.id?.let { onChannelClick(it) } + } + .padding(vertical = 4.dp, horizontal = 4.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + IconButton(onClick = { + onToggleDrawer() + }) { + Icon( + imageVector = Icons.Default.Menu, + contentDescription = stringResource(R.string.menu), + ) + } + + Spacer(modifier = Modifier.width(4.dp)) + + channel.channelType?.let { + ChannelIcon( + channelType = it, + modifier = Modifier.alpha(0.6f) + ) + } + + Spacer(modifier = Modifier.width(8.dp)) + + Row( + modifier = Modifier.weight(1f), + verticalAlignment = Alignment.CenterVertically, + ) { + Text( + text = channel.name ?: "Ch #${channel.id}", + fontWeight = FontWeight.Medium, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) + + Spacer(modifier = Modifier.width(4.dp)) + + Icon( + imageVector = Icons.Default.KeyboardArrowRight, + contentDescription = stringResource(R.string.menu), + modifier = Modifier + .size(18.dp) + .alpha(0.4f), + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/chat/revolt/components/screens/chat/DoubleDrawer.kt b/app/src/main/java/chat/revolt/components/screens/chat/DoubleDrawer.kt deleted file mode 100644 index 78289719..00000000 --- a/app/src/main/java/chat/revolt/components/screens/chat/DoubleDrawer.kt +++ /dev/null @@ -1,194 +0,0 @@ -package chat.revolt.components.screens.chat - -import android.content.res.Configuration -import androidx.compose.animation.core.animateDpAsState -import androidx.compose.animation.core.spring -import androidx.compose.foundation.gestures.Orientation -import androidx.compose.foundation.layout.* -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.* -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.saveable.Saver -import androidx.compose.runtime.saveable.rememberSaveable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.platform.LocalDensity -import androidx.compose.ui.platform.LocalLayoutDirection -import androidx.compose.ui.unit.IntOffset -import androidx.compose.ui.unit.LayoutDirection -import androidx.compose.ui.unit.dp -import kotlin.math.abs -import kotlin.math.roundToInt - -enum class DoubleDrawerOpenState { - Start, - Center, - End -} - -@OptIn(ExperimentalMaterialApi::class) -class DoubleDrawerState( - var initialValue: DoubleDrawerOpenState = DoubleDrawerOpenState.Center, - val confirmStateChange: (DoubleDrawerOpenState) -> Boolean = { true } -) { - val swipeableState = SwipeableState( - initialValue = initialValue, - animationSpec = spring(), - confirmStateChange = confirmStateChange - ) - - suspend fun focusStart() { - swipeableState.animateTo(DoubleDrawerOpenState.Start) - } - - suspend fun focusCenter() { - swipeableState.animateTo(DoubleDrawerOpenState.Center) - } - - suspend fun focusEnd() { - swipeableState.animateTo(DoubleDrawerOpenState.End) - } - - val isStart: Boolean - get() = swipeableState.currentValue == DoubleDrawerOpenState.Start - val isCenter: Boolean - get() = swipeableState.currentValue == DoubleDrawerOpenState.Center - val isEnd: Boolean - get() = swipeableState.currentValue == DoubleDrawerOpenState.End - - val currentValue: DoubleDrawerOpenState - get() = swipeableState.currentValue - - companion object { - fun Saver( - confirmStateChange: (DoubleDrawerOpenState) -> Boolean - ): Saver = Saver( - save = { it.currentValue }, - restore = { DoubleDrawerState(it, confirmStateChange) } - ) - } -} - -@Composable -fun rememberDoubleDrawerState( - initialValue: DoubleDrawerOpenState = DoubleDrawerOpenState.Center, - confirmStateChange: (DoubleDrawerOpenState) -> Boolean = { true } -): DoubleDrawerState = rememberSaveable( - saver = DoubleDrawerState.Saver(confirmStateChange) -) { - DoubleDrawerState(initialValue, confirmStateChange) -} - -@OptIn(ExperimentalMaterialApi::class) -@Composable -fun DoubleDrawer( - state: DoubleDrawerState = rememberDoubleDrawerState(), - startPanel: @Composable () -> Unit, - endPanel: @Composable () -> Unit, - content: @Composable () -> Unit, -) { - val layoutDirection = LocalLayoutDirection.current - - BoxWithConstraints(Modifier.fillMaxSize()) { - val isPortrait = - LocalContext.current.resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT - val drawerWeight = - if (isPortrait) 0.9f else 0.8f - - val offsetValue = - (constraints.maxWidth * drawerWeight) + (LocalDensity.current.run { 16.dp.toPx() }) - val isAtOffset = abs(state.swipeableState.offset.value) == abs(offsetValue) - - val contentCornerRadius by animateDpAsState( - targetValue = if (isAtOffset) 16.dp else 0.dp, - ) - - Box( - modifier = Modifier - .fillMaxSize() - .swipeable( - state = state.swipeableState, - orientation = Orientation.Horizontal, - velocityThreshold = 500.dp, - anchors = mapOf( - offsetValue to DoubleDrawerOpenState.Start, - 0f to DoubleDrawerOpenState.Center, - -offsetValue to DoubleDrawerOpenState.End - ), - reverseDirection = layoutDirection == LayoutDirection.Rtl, - resistance = ResistanceConfig(0.5f, 0.5f) - ) - ) { - Box( - modifier = Modifier - .fillMaxHeight() - .fillMaxWidth(drawerWeight) - .align(Alignment.CenterStart) - .offset { - IntOffset( - x = state.swipeableState.offset.value.roundToInt() - offsetValue.roundToInt(), - y = 0 - ) - }, - ) { - Box( - modifier = Modifier - .fillMaxSize() - .clip(RoundedCornerShape(topEnd = 16.dp, bottomEnd = 16.dp)) - ) { - startPanel() - } - } - - Box( - modifier = Modifier - .fillMaxSize() - .align(Alignment.Center) - .offset { - IntOffset( - x = state.swipeableState.offset.value.roundToInt(), - y = 0 - ) - }, - ) { - Box( - modifier = Modifier - .fillMaxSize() - .clip(RoundedCornerShape(contentCornerRadius)) - ) { - content() - } - } - - Box( - modifier = Modifier - .fillMaxHeight() - .fillMaxWidth(drawerWeight) - .clip( - RoundedCornerShape( - topStart = 16.dp, - bottomStart = 16.dp - ) - ) - .align(Alignment.CenterEnd) - .offset { - IntOffset( - x = state.swipeableState.offset.value.roundToInt() + offsetValue.roundToInt(), - y = 0 - ) - }, - ) { - Box( - modifier = Modifier - .fillMaxSize() - .clip(RoundedCornerShape(topStart = 16.dp, bottomStart = 16.dp)) - ) { - endPanel() - } - } - } - } -} \ No newline at end of file diff --git a/app/src/main/java/chat/revolt/components/screens/chat/drawer/channel/ChannelList.kt b/app/src/main/java/chat/revolt/components/screens/chat/drawer/channel/ChannelList.kt index c0107367..bc2589e0 100644 --- a/app/src/main/java/chat/revolt/components/screens/chat/drawer/channel/ChannelList.kt +++ b/app/src/main/java/chat/revolt/components/screens/chat/drawer/channel/ChannelList.kt @@ -8,6 +8,8 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.DrawerState +import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.material3.Text @@ -23,14 +25,14 @@ import androidx.compose.ui.unit.sp import chat.revolt.R import chat.revolt.api.RevoltAPI import chat.revolt.api.schemas.ChannelType -import chat.revolt.components.screens.chat.DoubleDrawerState import chat.revolt.components.screens.chat.drawer.server.DrawerChannel import kotlinx.coroutines.launch +@OptIn(ExperimentalMaterial3Api::class) @Composable fun RowScope.ChannelList( serverId: String, - drawerState: DoubleDrawerState, + drawerState: DrawerState, currentChannel: String?, onChannelClick: (String) -> Unit, onChannelLongClick: (String) -> Unit, @@ -69,7 +71,7 @@ fun RowScope.ChannelList( } ?: false, onClick = { onChannelClick(channel.id ?: return@DrawerChannel) - coroutineScope.launch { drawerState.focusCenter() } + coroutineScope.launch { drawerState.close() } }, onLongClick = { onChannelLongClick(channel.id ?: return@DrawerChannel) @@ -127,7 +129,7 @@ fun RowScope.ChannelList( } ?: true, onClick = { onChannelClick(ch.id ?: return@DrawerChannel) - coroutineScope.launch { drawerState.focusCenter() } + coroutineScope.launch { drawerState.close() } }, onLongClick = { onChannelLongClick(ch.id ?: return@DrawerChannel) 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 2c126746..6230b575 100644 --- a/app/src/main/java/chat/revolt/screens/chat/ChatRouterScreen.kt +++ b/app/src/main/java/chat/revolt/screens/chat/ChatRouterScreen.kt @@ -1,9 +1,8 @@ package chat.revolt.screens.chat +import androidx.activity.compose.BackHandler import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.Crossfade -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.aspectRatio @@ -18,11 +17,15 @@ import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Add import androidx.compose.material3.AlertDialog +import androidx.compose.material3.DismissibleDrawerSheet +import androidx.compose.material3.DismissibleNavigationDrawer +import androidx.compose.material3.DrawerValue +import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.material3.TextButton -import androidx.compose.material3.surfaceColorAtElevation +import androidx.compose.material3.rememberDrawerState import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue @@ -33,10 +36,10 @@ import androidx.compose.runtime.snapshotFlow import androidx.compose.ui.Alignment import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalSoftwareKeyboardController import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope @@ -52,13 +55,10 @@ import chat.revolt.api.realtime.RealtimeSocket import chat.revolt.components.chat.DisconnectedNotice import chat.revolt.components.generic.UserAvatar import chat.revolt.components.generic.presenceFromStatus -import chat.revolt.components.screens.chat.DoubleDrawer -import chat.revolt.components.screens.chat.DoubleDrawerOpenState import chat.revolt.components.screens.chat.drawer.channel.ChannelList import chat.revolt.components.screens.chat.drawer.server.DrawerServer import chat.revolt.components.screens.chat.drawer.server.DrawerServerlikeIcon import chat.revolt.components.screens.chat.drawer.server.ServerDrawerSeparator -import chat.revolt.components.screens.chat.rememberDoubleDrawerState import chat.revolt.persistence.KVStorage import chat.revolt.screens.chat.dialogs.safety.ReportMessageDialog import chat.revolt.screens.chat.sheets.AddServerSheet @@ -168,10 +168,14 @@ class ChatRouterViewModel @Inject constructor( } } -@OptIn(ExperimentalMaterialNavigationApi::class, ExperimentalComposeUiApi::class) +@OptIn( + ExperimentalMaterialNavigationApi::class, + ExperimentalComposeUiApi::class, + ExperimentalMaterial3Api::class +) @Composable fun ChatRouterScreen(topNav: NavController, viewModel: ChatRouterViewModel = hiltViewModel()) { - val drawerState = rememberDoubleDrawerState() + val drawerState = rememberDrawerState(DrawerValue.Closed) val scope = rememberCoroutineScope() val keyboardController = LocalSoftwareKeyboardController.current @@ -184,11 +188,17 @@ fun ChatRouterScreen(topNav: NavController, viewModel: ChatRouterViewModel = hil composition = sidebarSparkComposition, ) + BackHandler(enabled = drawerState.isClosed) { + scope.launch { + drawerState.open() + } + } + LaunchedEffect(drawerState) { snapshotFlow { drawerState.currentValue } .distinctUntilChanged() .collect { state -> - if (state != DoubleDrawerOpenState.Center) { + if (state == DrawerValue.Closed) { keyboardController?.hide() } } @@ -274,165 +284,167 @@ fun ChatRouterScreen(topNav: NavController, viewModel: ChatRouterViewModel = hil }) } - DoubleDrawer( - state = drawerState, - startPanel = { - Column(Modifier.fillMaxWidth()) { - Row { - Column( - modifier = Modifier - .fillMaxHeight() - .verticalScroll(rememberScrollState()), - horizontalAlignment = Alignment.CenterHorizontally - ) { - UserAvatar( - username = RevoltAPI.userCache[RevoltAPI.selfId]?.username - ?: "", - presence = presenceFromStatus( - RevoltAPI.userCache[RevoltAPI.selfId]?.status?.presence - ?: "" - ), - userId = RevoltAPI.selfId ?: "", - avatar = RevoltAPI.userCache[RevoltAPI.selfId]?.avatar, - size = 48.dp, - presenceSize = 16.dp, - onClick = { - viewModel.navigateToServer("home", navController) - }, - onLongClick = { - navController.navigate("status") - }, + DismissibleNavigationDrawer( + drawerState = drawerState, + drawerContent = { + DismissibleDrawerSheet( + drawerContainerColor = Color.Transparent, + ) { + Column(Modifier.fillMaxWidth()) { + Row { + Column( modifier = Modifier - .padding(8.dp) - .size(48.dp) - ) - - ServerDrawerSeparator() - - RevoltAPI.serverCache.values - .sortedBy { it.id } - .forEach { server -> - if (server.id == null || server.name == null) return@forEach - - DrawerServer( - iconId = server.icon?.id, - serverName = server.name, - hasUnreads = RevoltAPI.unreads.serverHasUnread(server.id), - ) { - viewModel.navigateToServer( - server.id, - navController - ) - } - } - - DrawerServerlikeIcon( - onClick = { - navController.navigate("add_server") - } + .fillMaxHeight() + .verticalScroll(rememberScrollState()), + horizontalAlignment = Alignment.CenterHorizontally ) { - Icon( - Icons.Default.Add, - contentDescription = stringResource(id = R.string.server_plus_alt), - modifier = Modifier.padding(4.dp) + UserAvatar( + username = RevoltAPI.userCache[RevoltAPI.selfId]?.username + ?: "", + presence = presenceFromStatus( + RevoltAPI.userCache[RevoltAPI.selfId]?.status?.presence + ?: "" + ), + userId = RevoltAPI.selfId ?: "", + avatar = RevoltAPI.userCache[RevoltAPI.selfId]?.avatar, + size = 48.dp, + presenceSize = 16.dp, + onClick = { + viewModel.navigateToServer("home", navController) + }, + onLongClick = { + navController.navigate("status") + }, + modifier = Modifier + .padding(8.dp) + .size(48.dp) + ) + + ServerDrawerSeparator() + + RevoltAPI.serverCache.values + .sortedBy { it.id } + .forEach { server -> + if (server.id == null || server.name == null) return@forEach + + DrawerServer( + iconId = server.icon?.id, + serverName = server.name, + hasUnreads = RevoltAPI.unreads.serverHasUnread( + server.id + ), + ) { + viewModel.navigateToServer( + server.id, + navController + ) + } + } + + DrawerServerlikeIcon( + onClick = { + navController.navigate("add_server") + } + ) { + Icon( + Icons.Default.Add, + contentDescription = stringResource(id = R.string.server_plus_alt), + modifier = Modifier.padding(4.dp) + ) + } + } + + Crossfade( + targetState = viewModel.currentServer, + label = "Channel List" + ) { + ChannelList( + serverId = it, + drawerState = drawerState, + currentChannel = viewModel.currentChannel, + onChannelClick = { channelId -> + viewModel.navigateToChannel(channelId, navController) + }, + onChannelLongClick = { channelId -> + navController.navigate("channel/$channelId/info") + }, ) } } - - Crossfade( - targetState = viewModel.currentServer, - label = "Channel List" - ) { - ChannelList( - serverId = it, - drawerState = drawerState, - currentChannel = viewModel.currentChannel, - onChannelClick = { channelId -> - viewModel.navigateToChannel(channelId, navController) - }, - onChannelLongClick = { channelId -> - navController.navigate("channel/$channelId/info") - }, - ) - } } } }, - endPanel = { - Box( - modifier = Modifier - .background(MaterialTheme.colorScheme.surfaceColorAtElevation(2.dp)) - .fillMaxSize(), - contentAlignment = Alignment.Center - ) { - Text(text = "👋", fontSize = 64.sp) - } - }, - ) { - 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 - ) + content = { + 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, + onToggleDrawer = { + scope.launch { + if (drawerState.isOpen) drawerState.close() + else drawerState.open() + } + } + ) + } + } + composable("no_current_channel") { + NoCurrentChannelScreen() } - } - composable("no_current_channel") { - NoCurrentChannelScreen() - } - bottomSheet("channel/{channelId}/info") { backStackEntry -> - val channelId = backStackEntry.arguments?.getString("channelId") - if (channelId != null) { - ChannelInfoSheet( - navController = navController, - channelId = channelId - ) + bottomSheet("channel/{channelId}/info") { backStackEntry -> + val channelId = backStackEntry.arguments?.getString("channelId") + if (channelId != null) { + ChannelInfoSheet( + navController = navController, + channelId = channelId + ) + } } - } - bottomSheet("channel/{channelId}/menu") { backStackEntry -> - val channelId = backStackEntry.arguments?.getString("channelId") - if (channelId != null) { - ChannelContextSheet( - navController = navController, - channelId = channelId - ) + bottomSheet("channel/{channelId}/menu") { backStackEntry -> + val channelId = backStackEntry.arguments?.getString("channelId") + if (channelId != null) { + ChannelContextSheet( + navController = navController, + channelId = channelId + ) + } } - } - bottomSheet("message/{messageId}/menu") { backStackEntry -> - val messageId = backStackEntry.arguments?.getString("messageId") - if (messageId != null) { - MessageContextSheet( - navController = navController, - messageId = messageId - ) + bottomSheet("message/{messageId}/menu") { backStackEntry -> + val messageId = backStackEntry.arguments?.getString("messageId") + if (messageId != null) { + MessageContextSheet( + navController = navController, + messageId = messageId + ) + } + } + bottomSheet("status") { + StatusSheet(navController = navController, topNav = topNav) + } + bottomSheet("add_server") { + AddServerSheet() } - } - bottomSheet("status") { - StatusSheet(navController = navController, topNav = topNav) - } - bottomSheet("add_server") { - AddServerSheet() - } - dialog("report/message/{messageId}") { backStackEntry -> - val messageId = backStackEntry.arguments?.getString("messageId") - if (messageId != null) { - ReportMessageDialog( - navController = navController, - messageId = messageId - ) + dialog("report/message/{messageId}") { backStackEntry -> + val messageId = backStackEntry.arguments?.getString("messageId") + if (messageId != null) { + ReportMessageDialog( + navController = navController, + messageId = messageId + ) + } } } } - } - } + }) } } } \ 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 index 4b34b54a..62b4d5ec 100644 --- a/app/src/main/java/chat/revolt/screens/chat/sheets/ChannelInfoSheet.kt +++ b/app/src/main/java/chat/revolt/screens/chat/sheets/ChannelInfoSheet.kt @@ -1,21 +1,26 @@ package chat.revolt.screens.chat.sheets -import androidx.compose.foundation.layout.* +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Add +import androidx.compose.material.icons.filled.List +import androidx.compose.material.icons.filled.Notifications +import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme 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 +import chat.revolt.components.generic.SheetClickable @Composable fun ChannelInfoSheet( @@ -33,20 +38,8 @@ fun ChannelInfoSheet( .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, @@ -57,5 +50,69 @@ fun ChannelInfoSheet( ?: stringResource(id = R.string.channel_info_sheet_description_empty), modifier = Modifier.padding(bottom = 10.dp) ) + + Spacer(modifier = Modifier.height(8.dp)) + + Text( + text = stringResource(id = R.string.channel_info_sheet_options), + style = MaterialTheme.typography.bodySmall, + modifier = Modifier.padding(bottom = 4.dp) + ) + + SheetClickable( + icon = { modifier -> + Icon( + imageVector = Icons.Default.List, + contentDescription = null, + modifier = modifier + ) + }, + label = { style -> + Text( + text = stringResource(id = R.string.channel_info_sheet_options_members), + style = style + ) + } + ) { + + } + + SheetClickable( + icon = { modifier -> + Icon( + imageVector = Icons.Default.Add, + contentDescription = null, + modifier = modifier + ) + }, + label = { style -> + Text( + text = stringResource(id = R.string.channel_info_sheet_options_invite), + style = style + ) + } + ) { + + } + + SheetClickable( + icon = { modifier -> + Icon( + imageVector = Icons.Default.Notifications, + contentDescription = null, + modifier = modifier + ) + }, + label = { style -> + Text( + text = stringResource(id = R.string.channel_info_sheet_options_notifications_manage), + style = style + ) + } + ) { + + } + + Spacer(modifier = Modifier.height(8.dp)) } } \ No newline at end of file diff --git a/app/src/main/java/chat/revolt/screens/chat/views/channel/ChannelScreen.kt b/app/src/main/java/chat/revolt/screens/chat/views/channel/ChannelScreen.kt index 5c6edd1f..fa45b9c5 100644 --- a/app/src/main/java/chat/revolt/screens/chat/views/channel/ChannelScreen.kt +++ b/app/src/main/java/chat/revolt/screens/chat/views/channel/ChannelScreen.kt @@ -4,13 +4,10 @@ import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.animation.* import androidx.compose.animation.core.animateDpAsState -import androidx.compose.foundation.background -import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.rememberLazyListState -import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.KeyboardArrowDown import androidx.compose.material3.* @@ -21,7 +18,6 @@ import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.documentfile.provider.DocumentFile @@ -36,7 +32,7 @@ import chat.revolt.api.routes.microservices.autumn.FileArgs import chat.revolt.components.chat.Message import chat.revolt.components.chat.MessageField import chat.revolt.components.screens.chat.AttachmentManager -import chat.revolt.components.screens.chat.ChannelIcon +import chat.revolt.components.screens.chat.ChannelHeader import chat.revolt.components.screens.chat.ReplyManager import chat.revolt.components.screens.chat.TypingIndicator import chat.revolt.internals.markdown.ChannelMentionRule @@ -58,6 +54,7 @@ import java.io.File fun ChannelScreen( navController: NavController, channelId: String, + onToggleDrawer: () -> Unit, viewModel: ChannelScreenViewModel = viewModel() ) { val channel = viewModel.channel @@ -117,34 +114,14 @@ fun ChannelScreen( } Column { - Row( - modifier = Modifier - .clickable { - navController.navigate("channel/${channel.id}/info") - } - .fillMaxWidth() - .clip( - RoundedCornerShape( - bottomStart = 16.dp, - bottomEnd = 16.dp - ) - ) - .background(MaterialTheme.colorScheme.surfaceColorAtElevation(1.dp)) - .padding(16.dp), - verticalAlignment = Alignment.CenterVertically - ) { - ChannelIcon( - channelType = channel.channelType, - modifier = Modifier.padding(end = 8.dp) - ) - Text( - text = channel.name ?: channel.id!!, - style = MaterialTheme.typography.labelLarge, - fontWeight = FontWeight.Bold, - modifier = Modifier.weight(1f), - ) - } - + ChannelHeader( + channel = channel, + onChannelClick = { + navController.navigate("channel/${channel.id}/info") + }, + onToggleDrawer = onToggleDrawer + ) + val isScrolledToBottom = remember(lazyListState) { derivedStateOf { lazyListState.firstVisibleItemIndex <= 6 @@ -189,41 +166,49 @@ fun ChannelScreen( items = viewModel.renderableMessages, key = { it.id!! } ) { message -> - Message(message, parse = { - val parser = MarkdownParser() - .addRules( - SimpleMarkdownRules.createEscapeRule(), - UserMentionRule(), - ChannelMentionRule(), - CustomEmoteRule(), - ) - .addRules( - createCodeRule(context, codeBlockColor.toArgb()), - createInlineCodeRule(context, codeBlockColor.toArgb()), - ) - .addRules( - SimpleMarkdownRules.createSimpleMarkdownRules( - includeEscapeRule = false + Message( + message, + parse = { + val parser = MarkdownParser() + .addRules( + SimpleMarkdownRules.createEscapeRule(), + UserMentionRule(), + ChannelMentionRule(), + CustomEmoteRule(), + ) + .addRules( + createCodeRule(context, codeBlockColor.toArgb()), + createInlineCodeRule(context, codeBlockColor.toArgb()), + ) + .addRules( + SimpleMarkdownRules.createSimpleMarkdownRules( + includeEscapeRule = false + ) + ) + + SimpleRenderer.render( + source = it.content ?: "", + parser = parser, + initialState = MarkdownState(0), + renderContext = MarkdownContext( + memberMap = mapOf(), + userMap = RevoltAPI.userCache.toMap(), + channelMap = RevoltAPI.channelCache.mapValues { ch -> + ch.value.name ?: ch.value.id ?: "#DeletedChannel" + }, + emojiMap = RevoltAPI.emojiCache, + serverId = channel.server ?: "", ) ) - - SimpleRenderer.render( - source = it.content ?: "", - parser = parser, - initialState = MarkdownState(0), - renderContext = MarkdownContext( - memberMap = mapOf(), - userMap = RevoltAPI.userCache.toMap(), - channelMap = RevoltAPI.channelCache.mapValues { ch -> - ch.value.name ?: ch.value.id ?: "#DeletedChannel" - }, - emojiMap = RevoltAPI.emojiCache, - serverId = channel.server ?: "", - ) - ) - }) { - navController.navigate("message/${message.id}/menu") - } + }, + onMessageContextMenu = { + navController.navigate("message/${message.id}/menu") + }, + canReply = true, + onReply = { + viewModel.replyToMessage(message) + }, + ) } item { diff --git a/app/src/main/java/chat/revolt/screens/chat/views/channel/ChannelScreenViewModel.kt b/app/src/main/java/chat/revolt/screens/chat/views/channel/ChannelScreenViewModel.kt index adc47b45..db771ae1 100644 --- a/app/src/main/java/chat/revolt/screens/chat/views/channel/ChannelScreenViewModel.kt +++ b/app/src/main/java/chat/revolt/screens/chat/views/channel/ChannelScreenViewModel.kt @@ -358,4 +358,13 @@ class ChannelScreenViewModel : ViewModel() { Log.d("ChannelScreen", "Acking channel") } } + + fun replyToMessage(message: Message) { + addInReplyTo( + SendMessageReply( + id = message.id!!, + mention = false + ) + ) + } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 17414546..9f889ed2 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -149,6 +149,10 @@ Channel description There hasn\'t been a description set for this channel yet. + Options + Members + Invite + Manage notifications Copy Message is empty, nothing to copy