diff --git a/app/src/main/java/chat/revolt/activities/MainActivity.kt b/app/src/main/java/chat/revolt/activities/MainActivity.kt index fc2d76d9..b01af9b0 100644 --- a/app/src/main/java/chat/revolt/activities/MainActivity.kt +++ b/app/src/main/java/chat/revolt/activities/MainActivity.kt @@ -12,7 +12,6 @@ import androidx.compose.runtime.* import androidx.compose.ui.Modifier import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.IntOffset -import androidx.compose.ui.unit.IntSize import androidx.fragment.app.FragmentActivity import androidx.navigation.compose.dialog import chat.revolt.BuildConfig @@ -57,7 +56,6 @@ class MainActivity : FragmentActivity() { } val RevoltTweenInt: FiniteAnimationSpec = tween(400, easing = EaseInOutExpo) -val RevoltTweenIntSize: FiniteAnimationSpec = tween(400, easing = EaseInOutExpo) val RevoltTweenFloat: FiniteAnimationSpec = tween(400, easing = EaseInOutExpo) val RevoltTweenDp: FiniteAnimationSpec = tween(400, easing = EaseInOutExpo) @@ -78,25 +76,25 @@ fun AppEntrypoint() { startDestination = "splash", enterTransition = { slideIntoContainer( - AnimatedContentScope.SlideDirection.Left, + AnimatedContentTransitionScope.SlideDirection.Left, animationSpec = RevoltTweenInt ) }, exitTransition = { slideOutOfContainer( - AnimatedContentScope.SlideDirection.Left, + AnimatedContentTransitionScope.SlideDirection.Left, animationSpec = RevoltTweenInt ) + fadeOut(animationSpec = RevoltTweenFloat) }, popEnterTransition = { slideIntoContainer( - AnimatedContentScope.SlideDirection.Right, + AnimatedContentTransitionScope.SlideDirection.Right, animationSpec = RevoltTweenInt ) }, popExitTransition = { slideOutOfContainer( - AnimatedContentScope.SlideDirection.Right, + AnimatedContentTransitionScope.SlideDirection.Right, animationSpec = RevoltTweenInt ) } diff --git a/app/src/main/java/chat/revolt/api/realtime/RealtimeSocket.kt b/app/src/main/java/chat/revolt/api/realtime/RealtimeSocket.kt index 7db972e7..1fa8f7e4 100644 --- a/app/src/main/java/chat/revolt/api/realtime/RealtimeSocket.kt +++ b/app/src/main/java/chat/revolt/api/realtime/RealtimeSocket.kt @@ -127,24 +127,20 @@ object RealtimeSocket { ) Log.d("RealtimeSocket", "Adding users to cache.") - readyFrame.users.forEach { user -> - RevoltAPI.userCache[user.id!!] = user - } + val userMap = readyFrame.users.associateBy { it.id!! } + RevoltAPI.userCache.putAll(userMap) Log.d("RealtimeSocket", "Adding servers to cache.") - readyFrame.servers.forEach { server -> - RevoltAPI.serverCache[server.id!!] = server - } + val serverMap = readyFrame.servers.associateBy { it.id!! } + RevoltAPI.serverCache.putAll(serverMap) Log.d("RealtimeSocket", "Adding channels to cache.") - readyFrame.channels.forEach { channel -> - RevoltAPI.channelCache[channel.id!!] = channel - } + val channelMap = readyFrame.channels.associateBy { it.id!! } + RevoltAPI.channelCache.putAll(channelMap) Log.d("RealtimeSocket", "Adding emojis to cache.") - readyFrame.emojis.forEach { emoji -> - RevoltAPI.emojiCache[emoji.id!!] = emoji - } + val emojiMap = readyFrame.emojis.associateBy { it.id!! } + RevoltAPI.emojiCache.putAll(emojiMap) } "Message" -> { diff --git a/app/src/main/java/chat/revolt/components/chat/MessageField.kt b/app/src/main/java/chat/revolt/components/chat/MessageField.kt index 6861a0df..c656e503 100644 --- a/app/src/main/java/chat/revolt/components/chat/MessageField.kt +++ b/app/src/main/java/chat/revolt/components/chat/MessageField.kt @@ -73,7 +73,7 @@ fun MessageField( keyboardOptions = KeyboardOptions.Default, keyboardActions = KeyboardActions.Default, decorationBox = @Composable { innerTextField -> - TextFieldDefaults.TextFieldDecorationBox( + TextFieldDefaults.DecorationBox( value = messageContent, innerTextField = innerTextField, enabled = !disabled, @@ -90,13 +90,17 @@ fun MessageField( overflow = TextOverflow.Ellipsis, ) }, - colors = TextFieldDefaults.textFieldColors( + colors = TextFieldDefaults.colors( focusedIndicatorColor = Color.Transparent, unfocusedIndicatorColor = Color.Transparent, disabledIndicatorColor = Color.Transparent, errorIndicatorColor = Color.Transparent, - placeholderColor = Color.Gray, - containerColor = MaterialTheme.colorScheme.surfaceColorAtElevation(1.dp) + unfocusedPlaceholderColor = Color.Gray, + focusedPlaceholderColor = Color.Gray, + unfocusedContainerColor = MaterialTheme.colorScheme.surfaceColorAtElevation( + 1.dp + ), + focusedContainerColor = Color.Transparent, ), contentPadding = PaddingValues(16.dp), leadingIcon = { 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 bc2589e0..e3a89679 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 @@ -11,10 +11,16 @@ import androidx.compose.foundation.verticalScroll import androidx.compose.material3.DrawerState import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.ModalBottomSheet import androidx.compose.material3.Surface import androidx.compose.material3.Text +import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip @@ -26,6 +32,7 @@ import chat.revolt.R import chat.revolt.api.RevoltAPI import chat.revolt.api.schemas.ChannelType import chat.revolt.components.screens.chat.drawer.server.DrawerChannel +import chat.revolt.sheets.ChannelContextSheet import kotlinx.coroutines.launch @OptIn(ExperimentalMaterial3Api::class) @@ -35,10 +42,31 @@ fun RowScope.ChannelList( drawerState: DrawerState, currentChannel: String?, onChannelClick: (String) -> Unit, - onChannelLongClick: (String) -> Unit, ) { val coroutineScope = rememberCoroutineScope() + var channelContextSheetShown by remember { mutableStateOf(false) } + var channelContextSheetTarget by remember { mutableStateOf("") } + + if (channelContextSheetShown) { + val channelContextSheetState = rememberModalBottomSheetState() + + ModalBottomSheet( + sheetState = channelContextSheetState, + onDismissRequest = { + channelContextSheetShown = false + }, + ) { + ChannelContextSheet( + channelId = channelContextSheetTarget, + onHideSheet = { + channelContextSheetState.hide() + channelContextSheetShown = false + } + ) + } + } + Surface( tonalElevation = 1.dp, modifier = Modifier @@ -74,7 +102,8 @@ fun RowScope.ChannelList( coroutineScope.launch { drawerState.close() } }, onLongClick = { - onChannelLongClick(channel.id ?: return@DrawerChannel) + channelContextSheetTarget = channel.id ?: return@DrawerChannel + channelContextSheetShown = true } ) } @@ -132,7 +161,8 @@ fun RowScope.ChannelList( coroutineScope.launch { drawerState.close() } }, onLongClick = { - onChannelLongClick(ch.id ?: return@DrawerChannel) + channelContextSheetTarget = ch.id ?: return@DrawerChannel + channelContextSheetShown = true } ) } 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 d3e44584..9c77b251 100644 --- a/app/src/main/java/chat/revolt/screens/chat/ChatRouterScreen.kt +++ b/app/src/main/java/chat/revolt/screens/chat/ChatRouterScreen.kt @@ -12,7 +12,6 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Add @@ -22,10 +21,11 @@ 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.ModalBottomSheet import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.material3.rememberDrawerState +import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue @@ -62,23 +62,16 @@ import chat.revolt.components.screens.chat.drawer.server.DrawerServerlikeIcon import chat.revolt.components.screens.chat.drawer.server.ServerDrawerSeparator import chat.revolt.persistence.KVStorage import chat.revolt.screens.chat.dialogs.safety.ReportMessageDialog -import chat.revolt.screens.chat.sheets.AddServerSheet -import chat.revolt.screens.chat.sheets.ChannelContextSheet -import chat.revolt.screens.chat.sheets.ChannelInfoSheet -import chat.revolt.screens.chat.sheets.MessageContextSheet -import chat.revolt.screens.chat.sheets.StatusSheet import chat.revolt.screens.chat.views.HomeScreen import chat.revolt.screens.chat.views.NoCurrentChannelScreen import chat.revolt.screens.chat.views.channel.ChannelScreen +import chat.revolt.sheets.AddServerSheet +import chat.revolt.sheets.StatusSheet import com.airbnb.lottie.RenderMode import com.airbnb.lottie.compose.LottieAnimation import com.airbnb.lottie.compose.LottieCompositionSpec import com.airbnb.lottie.compose.animateLottieCompositionAsState import com.airbnb.lottie.compose.rememberLottieComposition -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 dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.launch @@ -163,7 +156,6 @@ class ChatRouterViewModel @Inject constructor( } @OptIn( - ExperimentalMaterialNavigationApi::class, ExperimentalComposeUiApi::class, ExperimentalMaterial3Api::class ) @@ -173,8 +165,7 @@ fun ChatRouterScreen(topNav: NavController, viewModel: ChatRouterViewModel = hil val scope = rememberCoroutineScope() val keyboardController = LocalSoftwareKeyboardController.current - val bottomSheetNavigator = rememberBottomSheetNavigator() - val navController = rememberNavController(bottomSheetNavigator) + val navController = rememberNavController() val showSidebarSpark = remember { mutableStateOf(false) } val sidebarSparkComposition by rememberLottieComposition(LottieCompositionSpec.RawRes(R.raw.open_settings_tutorial)) @@ -182,6 +173,9 @@ fun ChatRouterScreen(topNav: NavController, viewModel: ChatRouterViewModel = hil composition = sidebarSparkComposition, ) + var showStatusSheet by remember { mutableStateOf(false) } + var showAddServerSheet by remember { mutableStateOf(false) } + BackHandler(enabled = drawerState.isClosed) { scope.launch { drawerState.open() @@ -230,215 +224,210 @@ fun ChatRouterScreen(topNav: NavController, viewModel: ChatRouterViewModel = hil } } - ModalBottomSheetLayout( - sheetShape = RoundedCornerShape(topStart = 16.dp, topEnd = 16.dp), - sheetBackgroundColor = MaterialTheme.colorScheme.surface, - bottomSheetNavigator = bottomSheetNavigator, - ) { - if (showSidebarSpark.value) { - AlertDialog( - onDismissRequest = {}, - title = { - Text(stringResource(id = R.string.spark_sidebar_settings_tutorial)) - }, - text = { - Column { - LottieAnimation( - composition = sidebarSparkComposition, - progress = { sidebarSparkProgress }, - modifier = Modifier - .fillMaxWidth() - .aspectRatio(1f), - renderMode = RenderMode.HARDWARE - ) - Text(stringResource(id = R.string.spark_sidebar_settings_tutorial_description_1)) - Text(stringResource(id = R.string.spark_sidebar_settings_tutorial_description_2)) + if (showSidebarSpark.value) { + AlertDialog( + onDismissRequest = {}, + title = { + Text(stringResource(id = R.string.spark_sidebar_settings_tutorial)) + }, + text = { + Column { + LottieAnimation( + composition = sidebarSparkComposition, + progress = { sidebarSparkProgress }, + modifier = Modifier + .fillMaxWidth() + .aspectRatio(1f), + renderMode = RenderMode.HARDWARE + ) + Text(stringResource(id = R.string.spark_sidebar_settings_tutorial_description_1)) + Text(stringResource(id = R.string.spark_sidebar_settings_tutorial_description_2)) + } + }, + confirmButton = { + TextButton(onClick = { + scope.launch { + viewModel.setSettingsHintDisplayed() + } + showSidebarSpark.value = false + }) { + Text(stringResource(id = R.string.spark_sidebar_settings_tutorial_acknowledge)) + } + } + ) + } + + if (showStatusSheet) { + val statusSheetState = rememberModalBottomSheetState() + + ModalBottomSheet( + sheetState = statusSheetState, + onDismissRequest = { + showStatusSheet = false + }, + ) { + StatusSheet( + onBeforeNavigation = { + scope.launch { + statusSheetState.hide() + showStatusSheet = false } }, - confirmButton = { - TextButton(onClick = { - scope.launch { - viewModel.setSettingsHintDisplayed() - } - showSidebarSpark.value = false - }) { - Text(stringResource(id = R.string.spark_sidebar_settings_tutorial_acknowledge)) - } + onGoSettings = { + topNav.navigate("settings") } ) } + } - Column { - AnimatedVisibility(visible = RealtimeSocket.disconnectionState != DisconnectionState.Connected) { - DisconnectedNotice( - state = RealtimeSocket.disconnectionState, - onReconnect = { - RealtimeSocket.updateDisconnectionState(DisconnectionState.Reconnecting) - scope.launch { RevoltAPI.connectWS() } - }) - } + if (showAddServerSheet) { + val addServerSheetState = rememberModalBottomSheetState() - DismissibleNavigationDrawer( - drawerState = drawerState, - drawerContent = { - DismissibleDrawerSheet( - drawerContainerColor = Color.Transparent, - ) { - 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") - }, - 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") - }, - ) - } - } - } - } - }, - 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() - } - - 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("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() - } - - dialog("report/message/{messageId}") { backStackEntry -> - val messageId = backStackEntry.arguments?.getString("messageId") - if (messageId != null) { - ReportMessageDialog( - navController = navController, - messageId = messageId - ) - } - } - } - } - }) + ModalBottomSheet( + sheetState = addServerSheetState, + onDismissRequest = { + showAddServerSheet = false + }, + ) { + AddServerSheet() } } + + Column { + AnimatedVisibility(visible = RealtimeSocket.disconnectionState != DisconnectionState.Connected) { + DisconnectedNotice( + state = RealtimeSocket.disconnectionState, + onReconnect = { + RealtimeSocket.updateDisconnectionState(DisconnectionState.Reconnecting) + scope.launch { RevoltAPI.connectWS() } + }) + } + + DismissibleNavigationDrawer( + drawerState = drawerState, + drawerContent = { + DismissibleDrawerSheet( + drawerContainerColor = Color.Transparent, + ) { + 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 = { + showStatusSheet = true + }, + 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 = { + showAddServerSheet = true + } + ) { + 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) + } + ) + } + } + } + } + }, + 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() + } + + 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/StatusSheet.kt b/app/src/main/java/chat/revolt/screens/chat/sheets/StatusSheet.kt deleted file mode 100644 index ada4201d..00000000 --- a/app/src/main/java/chat/revolt/screens/chat/sheets/StatusSheet.kt +++ /dev/null @@ -1,64 +0,0 @@ -package chat.revolt.screens.chat.sheets - -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.Settings -import androidx.compose.material3.Icon -import androidx.compose.material3.Surface -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -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.components.generic.SheetClickable - -@Composable -fun StatusSheet( - navController: NavController, - topNav: NavController, -) { - if (RevoltAPI.selfId == null || RevoltAPI.userCache[RevoltAPI.selfId] == null) { - navController.popBackStack() - return - } - - val selfUser = RevoltAPI.userCache[RevoltAPI.selfId]!! - - Surface { - Column( - modifier = Modifier - .padding(horizontal = 16.dp, vertical = 8.dp) - .verticalScroll(rememberScrollState()) - ) { - Text(text = "Logged in as @${selfUser.username} (${selfUser.id})") - - Spacer(modifier = Modifier.height(8.dp)) - - SheetClickable( - icon = { modifier -> - Icon( - imageVector = Icons.Default.Settings, - contentDescription = null, - modifier = modifier - ) - }, - label = { style -> - Text( - text = stringResource(id = R.string.settings), - style = style - ) - } - ) { - topNav.navigate("settings") - } - } - } -} \ 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 fa45b9c5..1a8aa351 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 @@ -43,6 +43,8 @@ import chat.revolt.internals.markdown.MarkdownState import chat.revolt.internals.markdown.UserMentionRule import chat.revolt.internals.markdown.createCodeRule import chat.revolt.internals.markdown.createInlineCodeRule +import chat.revolt.sheets.ChannelInfoSheet +import chat.revolt.sheets.MessageContextSheet import com.discord.simpleast.core.simple.SimpleMarkdownRules import com.discord.simpleast.core.simple.SimpleRenderer import io.ktor.http.* @@ -50,6 +52,7 @@ import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.launch import java.io.File +@OptIn(ExperimentalMaterial3Api::class) @Composable fun ChannelScreen( navController: NavController, @@ -65,6 +68,11 @@ fun ChannelScreen( val codeBlockColor = MaterialTheme.colorScheme.surfaceColorAtElevation(2.dp) + var channelInfoSheetShown by remember { mutableStateOf(false) } + + var messageContextSheetShown by remember { mutableStateOf(false) } + var messageContextSheetTarget by remember { mutableStateOf("") } + val pickFileLauncher = rememberLauncherForActivityResult( contract = ActivityResultContracts.OpenMultipleDocuments() ) { uriList -> @@ -108,6 +116,43 @@ fun ChannelScreen( } } + if (channelInfoSheetShown) { + val channelInfoSheetState = rememberModalBottomSheetState() + + ModalBottomSheet( + sheetState = channelInfoSheetState, + onDismissRequest = { + channelInfoSheetShown = false + }, + ) { + ChannelInfoSheet( + channelId = channelId, + ) + } + } + + if (messageContextSheetShown) { + val messageContextSheetState = rememberModalBottomSheetState() + + ModalBottomSheet( + sheetState = messageContextSheetState, + onDismissRequest = { + messageContextSheetShown = false + }, + ) { + MessageContextSheet( + messageId = messageContextSheetTarget, + onHideSheet = { + messageContextSheetState.hide() + messageContextSheetShown = false + }, + onReportMessage = { + navController.navigate("report/message/$messageContextSheetTarget") + }, + ) + } + } + if (channel?.channelType == null) { CircularProgressIndicator() return @@ -117,11 +162,11 @@ fun ChannelScreen( ChannelHeader( channel = channel, onChannelClick = { - navController.navigate("channel/${channel.id}/info") + channelInfoSheetShown = true }, onToggleDrawer = onToggleDrawer ) - + val isScrolledToBottom = remember(lazyListState) { derivedStateOf { lazyListState.firstVisibleItemIndex <= 6 @@ -202,7 +247,8 @@ fun ChannelScreen( ) }, onMessageContextMenu = { - navController.navigate("message/${message.id}/menu") + messageContextSheetShown = true + messageContextSheetTarget = message.id ?: "" }, canReply = true, onReply = { diff --git a/app/src/main/java/chat/revolt/screens/chat/sheets/AddServerSheet.kt b/app/src/main/java/chat/revolt/sheets/AddServerSheet.kt similarity index 99% rename from app/src/main/java/chat/revolt/screens/chat/sheets/AddServerSheet.kt rename to app/src/main/java/chat/revolt/sheets/AddServerSheet.kt index c4ee29e8..6c7add2a 100644 --- a/app/src/main/java/chat/revolt/screens/chat/sheets/AddServerSheet.kt +++ b/app/src/main/java/chat/revolt/sheets/AddServerSheet.kt @@ -1,4 +1,4 @@ -package chat.revolt.screens.chat.sheets +package chat.revolt.sheets import android.content.Intent import android.util.Log diff --git a/app/src/main/java/chat/revolt/screens/chat/sheets/ChannelContextSheet.kt b/app/src/main/java/chat/revolt/sheets/ChannelContextSheet.kt similarity index 78% rename from app/src/main/java/chat/revolt/screens/chat/sheets/ChannelContextSheet.kt rename to app/src/main/java/chat/revolt/sheets/ChannelContextSheet.kt index 1633e566..fbbbd6d5 100644 --- a/app/src/main/java/chat/revolt/screens/chat/sheets/ChannelContextSheet.kt +++ b/app/src/main/java/chat/revolt/sheets/ChannelContextSheet.kt @@ -1,17 +1,23 @@ -package chat.revolt.screens.chat.sheets +package chat.revolt.sheets import android.widget.Toast +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.Icon import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalClipboardManager import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.AnnotatedString -import androidx.navigation.NavController +import androidx.compose.ui.unit.dp import chat.revolt.R import chat.revolt.api.RevoltAPI import chat.revolt.components.generic.SheetClickable @@ -19,12 +25,18 @@ import kotlinx.coroutines.launch @Composable fun ChannelContextSheet( - navController: NavController, channelId: String, + onHideSheet: suspend () -> Unit, ) { val channel = RevoltAPI.channelCache[channelId] if (channel == null) { - navController.popBackStack() + Box( + modifier = Modifier + .fillMaxWidth() + .height(200.dp) + ) { + CircularProgressIndicator(modifier = Modifier.align(Alignment.Center)) + } return } @@ -57,7 +69,10 @@ fun ChannelContextSheet( context.getString(R.string.channel_context_sheet_actions_copy_id_copied), Toast.LENGTH_SHORT ).show() - navController.popBackStack() + + coroutineScope.launch { + onHideSheet() + } } SheetClickable( @@ -79,8 +94,8 @@ fun ChannelContextSheet( channel.lastMessageID?.let { RevoltAPI.unreads.markAsRead(channelId, it, sync = true) } + onHideSheet() } - navController.popBackStack() } } } \ 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/sheets/ChannelInfoSheet.kt similarity index 88% rename from app/src/main/java/chat/revolt/screens/chat/sheets/ChannelInfoSheet.kt rename to app/src/main/java/chat/revolt/sheets/ChannelInfoSheet.kt index 62b4d5ec..46203116 100644 --- a/app/src/main/java/chat/revolt/screens/chat/sheets/ChannelInfoSheet.kt +++ b/app/src/main/java/chat/revolt/sheets/ChannelInfoSheet.kt @@ -1,7 +1,9 @@ -package chat.revolt.screens.chat.sheets +package chat.revolt.sheets +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column 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.rememberScrollState @@ -10,26 +12,32 @@ 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.CircularProgressIndicator 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.components.generic.SheetClickable @Composable fun ChannelInfoSheet( - navController: NavController, channelId: String, ) { val channel = RevoltAPI.channelCache[channelId] if (channel == null) { - navController.popBackStack() + Box( + modifier = Modifier + .fillMaxWidth() + .height(200.dp) + ) { + CircularProgressIndicator(modifier = Modifier.align(Alignment.Center)) + } return } diff --git a/app/src/main/java/chat/revolt/screens/chat/sheets/MessageContextSheet.kt b/app/src/main/java/chat/revolt/sheets/MessageContextSheet.kt similarity index 84% rename from app/src/main/java/chat/revolt/screens/chat/sheets/MessageContextSheet.kt rename to app/src/main/java/chat/revolt/sheets/MessageContextSheet.kt index 96a37283..1e529c01 100644 --- a/app/src/main/java/chat/revolt/screens/chat/sheets/MessageContextSheet.kt +++ b/app/src/main/java/chat/revolt/sheets/MessageContextSheet.kt @@ -1,10 +1,11 @@ -package chat.revolt.screens.chat.sheets +package chat.revolt.sheets import android.widget.Toast import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column 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.rememberScrollState @@ -12,12 +13,14 @@ import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Delete import androidx.compose.material.icons.filled.Edit +import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.material3.surfaceColorAtElevation import androidx.compose.runtime.Composable import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.platform.LocalClipboardManager @@ -26,7 +29,6 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.unit.dp -import androidx.navigation.NavController import chat.revolt.R import chat.revolt.api.REVOLT_APP import chat.revolt.api.RevoltAPI @@ -37,12 +39,19 @@ import kotlinx.coroutines.launch @Composable fun MessageContextSheet( - navController: NavController, messageId: String, + onHideSheet: suspend () -> Unit, + onReportMessage: () -> Unit, ) { val message = RevoltAPI.messageCache[messageId] if (message == null) { - navController.popBackStack() + Box( + modifier = Modifier + .fillMaxWidth() + .height(200.dp) + ) { + CircularProgressIndicator(modifier = Modifier.align(Alignment.Center)) + } return } @@ -89,8 +98,8 @@ fun MessageContextSheet( ) { coroutineScope.launch { UiCallbacks.replyToMessage(messageId) + onHideSheet() } - navController.popBackStack() } SheetClickable( @@ -113,7 +122,10 @@ fun MessageContextSheet( context.getString(R.string.comingsoon_toast), Toast.LENGTH_SHORT ).show() - navController.popBackStack() + + coroutineScope.launch { + onHideSheet() + } } SheetClickable( @@ -132,22 +144,27 @@ fun MessageContextSheet( }, ) { if (message.content.isNullOrEmpty()) { - Toast.makeText( - context, - context.getString(R.string.message_context_sheet_actions_copy_failed_empty), - Toast.LENGTH_SHORT - ).show() - navController.popBackStack() + coroutineScope.launch { + onHideSheet() + Toast.makeText( + context, + context.getString(R.string.message_context_sheet_actions_copy_failed_empty), + Toast.LENGTH_SHORT + ).show() + } return@SheetClickable } - clipboardManager.setText(AnnotatedString(message.content)) Toast.makeText( context, context.getString(R.string.copied), Toast.LENGTH_SHORT ).show() - navController.popBackStack() + + coroutineScope.launch { + clipboardManager.setText(AnnotatedString(message.content)) + onHideSheet() + } } SheetClickable( @@ -171,7 +188,11 @@ fun MessageContextSheet( context.getString(R.string.message_context_sheet_actions_copy_failed_empty), Toast.LENGTH_SHORT ).show() - navController.popBackStack() + + coroutineScope.launch { + onHideSheet() + } + return@SheetClickable } @@ -187,7 +208,10 @@ fun MessageContextSheet( context.getString(R.string.message_context_sheet_actions_copy_link_copied), Toast.LENGTH_SHORT ).show() - navController.popBackStack() + + coroutineScope.launch { + onHideSheet() + } } SheetClickable( @@ -213,7 +237,10 @@ fun MessageContextSheet( context.getString(R.string.message_context_sheet_actions_copy_id_copied), Toast.LENGTH_SHORT ).show() - navController.popBackStack() + + coroutineScope.launch { + onHideSheet() + } } @@ -237,7 +264,10 @@ fun MessageContextSheet( context.getString(R.string.comingsoon_toast), Toast.LENGTH_SHORT ).show() - navController.popBackStack() + + coroutineScope.launch { + onHideSheet() + } } SheetClickable( @@ -260,7 +290,10 @@ fun MessageContextSheet( context.getString(R.string.comingsoon_toast), Toast.LENGTH_SHORT ).show() - navController.popBackStack() + + coroutineScope.launch { + onHideSheet() + } } SheetClickable( @@ -283,7 +316,10 @@ fun MessageContextSheet( context.getString(R.string.comingsoon_toast), Toast.LENGTH_SHORT ).show() - navController.popBackStack() + + coroutineScope.launch { + onHideSheet() + } } SheetClickable( @@ -301,7 +337,9 @@ fun MessageContextSheet( ) }, ) { - navController.navigate("report/message/${message.id}") + coroutineScope.launch { + onReportMessage() + } } } } \ No newline at end of file diff --git a/app/src/main/java/chat/revolt/sheets/StatusSheet.kt b/app/src/main/java/chat/revolt/sheets/StatusSheet.kt new file mode 100644 index 00000000..e441294b --- /dev/null +++ b/app/src/main/java/chat/revolt/sheets/StatusSheet.kt @@ -0,0 +1,56 @@ +package chat.revolt.sheets + +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.Settings +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import chat.revolt.R +import chat.revolt.api.RevoltAPI +import chat.revolt.components.generic.SheetClickable + +@Composable +fun StatusSheet( + onBeforeNavigation: () -> Unit, + onGoSettings: () -> Unit +) { + val selfUser = RevoltAPI.userCache[RevoltAPI.selfId]!! + + Column( + modifier = Modifier + .padding(horizontal = 16.dp, vertical = 8.dp) + .verticalScroll(rememberScrollState()) + ) { + Text(text = "Logged in as @${selfUser.username} (${selfUser.id})") + + Spacer(modifier = Modifier.height(8.dp)) + + SheetClickable( + icon = { modifier -> + Icon( + imageVector = Icons.Default.Settings, + contentDescription = null, + modifier = modifier + ) + }, + label = { style -> + Text( + text = stringResource(id = R.string.settings), + style = style + ) + } + ) { + onBeforeNavigation() + onGoSettings() + } + } +} \ No newline at end of file diff --git a/build.gradle b/build.gradle index 5a6c5ee8..2e1f8c8a 100644 --- a/build.gradle +++ b/build.gradle @@ -1,8 +1,8 @@ buildscript { ext { compose_version = '1.4.0' - compose_bom_version = '2023.01.00' - accompanist_version = '0.28.0' + compose_bom_version = '2023.05.01' + accompanist_version = '0.31.2-alpha' okhttp_version = '4.10.0' nav_version = '2.5.3' hilt_version = '2.44'