diff --git a/app/src/main/java/chat/revolt/activities/MainActivity.kt b/app/src/main/java/chat/revolt/activities/MainActivity.kt index 37766434..448c81e2 100644 --- a/app/src/main/java/chat/revolt/activities/MainActivity.kt +++ b/app/src/main/java/chat/revolt/activities/MainActivity.kt @@ -629,7 +629,6 @@ fun AppEntrypoint( ChatRouterScreen( navController, windowSizeClass, - disableBackHandler = showVoiceUI, onNullifiedUser = { onRetryConnection() navController.popBackStack( @@ -698,6 +697,7 @@ fun AppEntrypoint( onToggleDrawer = {}, useDrawer = false, useBackButton = true, + backToChannelsScreen = {}, backButtonAction = { navController.popBackStack() }, diff --git a/app/src/main/java/chat/revolt/composables/screens/chat/drawer/ChannelSideDrawer.kt b/app/src/main/java/chat/revolt/composables/screens/chat/drawer/ChannelSideDrawer.kt index c78fb7c4..11d91893 100644 --- a/app/src/main/java/chat/revolt/composables/screens/chat/drawer/ChannelSideDrawer.kt +++ b/app/src/main/java/chat/revolt/composables/screens/chat/drawer/ChannelSideDrawer.kt @@ -37,7 +37,6 @@ import androidx.compose.foundation.lazy.LazyListState import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.CornerSize -import androidx.compose.material3.DrawerState import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon @@ -99,7 +98,6 @@ import chat.revolt.composables.screens.chat.ChannelIcon import chat.revolt.screens.chat.ChatRouterDestination import chat.revolt.screens.chat.LocalIsConnected import chat.revolt.sheets.ChannelContextSheet -import kotlinx.coroutines.launch @OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class) @Composable @@ -108,7 +106,6 @@ fun ChannelSideDrawer( currentDestination: ChatRouterDestination, onDestinationChanged: (ChatRouterDestination) -> Unit, onLongPressAvatar: () -> Unit, - drawerState: DrawerState?, navigateToServer: (String) -> Unit, onShowServerContextSheet: (String) -> Unit, showSettingsIcon: Boolean, @@ -124,10 +121,11 @@ fun ChannelSideDrawer( val channelListState = rememberLazyListState() LaunchedEffect(currentDestination) { - if (currentDestination is ChatRouterDestination.Channel && currentServer != null) { + if ((currentDestination is ChatRouterDestination.ServersChannels) && currentServer != null) { val channelIndex = categorisedChannels?.indexOfFirst { + when (it) { - is CategorisedChannelList.Channel -> it.channel.id == currentDestination.channelId + is CategorisedChannelList.Channel -> it.channel.id == currentDestination.serverID else -> false } } ?: 0 @@ -357,9 +355,6 @@ fun ChannelSideDrawer( .clip(CircleShape) .clickable { serverInList.id?.let { srvId -> navigateToServer(srvId) } - scope.launch { - drawerState?.close() - } }) { val icon = serverInList.icon?.id?.let { iconId -> "$REVOLT_FILES/icons/$iconId" @@ -439,9 +434,6 @@ fun ChannelSideDrawer( .clip(CircleShape) .clickable { onOpenSettings() - scope.launch { - drawerState?.close() - } } .size(48.dp), contentAlignment = Alignment.Center @@ -577,7 +569,6 @@ fun ChannelSideDrawer( DirectMessagesChannelListRenderer( currentDestination, onDestinationChanged, - drawerState, channelListState, onOpenChannelContextSheet = { channelContextSheetTarget = it } ) @@ -586,7 +577,6 @@ fun ChannelSideDrawer( categorisedChannels, currentDestination, onDestinationChanged, - drawerState, channelListState, onOpenChannelContextSheet = { channelContextSheetTarget = it }, serverId = currentServer @@ -600,11 +590,9 @@ fun ChannelSideDrawer( fun ColumnScope.DirectMessagesChannelListRenderer( currentDestination: ChatRouterDestination, onDestinationChanged: (ChatRouterDestination) -> Unit, - drawerState: DrawerState?, channelListState: LazyListState, onOpenChannelContextSheet: (String) -> Unit, ) { - val scope = rememberCoroutineScope() val dmAbleChannels = RevoltAPI.channelCache.values .filter { it.channelType == ChannelType.DirectMessage || it.channelType == ChannelType.Group } @@ -626,12 +614,9 @@ fun ColumnScope.DirectMessagesChannelListRenderer( channelType = ChannelType.TextChannel ), iconType = ChannelItemIconType.Painter(painterResource(R.drawable.icn_star_shine_24dp)), - isCurrent = currentDestination is ChatRouterDestination.Overview, + isCurrent = currentDestination is ChatRouterDestination.Settings, onDestinationChanged = { - onDestinationChanged(ChatRouterDestination.Overview) - scope.launch { - drawerState?.close() - } + onDestinationChanged(ChatRouterDestination.Settings) }, hasUnread = false, onOpenChannelContextSheet = {} @@ -650,9 +635,6 @@ fun ColumnScope.DirectMessagesChannelListRenderer( isCurrent = currentDestination is ChatRouterDestination.Friends, onDestinationChanged = { onDestinationChanged(ChatRouterDestination.Friends) - scope.launch { - drawerState?.close() - } }, hasUnread = FriendRequests.getIncoming().isNotEmpty(), onOpenChannelContextSheet = {}, @@ -675,9 +657,6 @@ fun ColumnScope.DirectMessagesChannelListRenderer( currentDestination.channelId == notesChannel.id, onDestinationChanged = { onDestinationChanged(it) - scope.launch { - drawerState?.close() - } }, hasUnread = false, onOpenChannelContextSheet = {}, @@ -732,9 +711,6 @@ fun ColumnScope.DirectMessagesChannelListRenderer( isMuted = NotificationSettingsProvider.isChannelMuted(channel.id!!, null), onDestinationChanged = { dest -> onDestinationChanged(dest) - scope.launch { - drawerState?.close() - } }, onOpenChannelContextSheet = onOpenChannelContextSheet ) @@ -756,13 +732,10 @@ fun ColumnScope.ServerChannelListRenderer( categorisedChannels: List?, currentDestination: ChatRouterDestination, onDestinationChanged: (ChatRouterDestination) -> Unit, - drawerState: DrawerState?, channelListState: LazyListState, onOpenChannelContextSheet: (String) -> Unit, serverId: String ) { - val scope = rememberCoroutineScope() - LazyColumn( state = channelListState, verticalArrangement = Arrangement.spacedBy(4.dp), @@ -806,11 +779,8 @@ fun ColumnScope.ServerChannelListRenderer( else -> false }, - onDestinationChanged = { - onDestinationChanged(it) - scope.launch { - drawerState?.close() - } + onDestinationChanged = { dest -> + onDestinationChanged(dest) }, hasUnread = channelOrCat.channel.lastMessageID?.let { lastMessageID -> RevoltAPI.unreads.hasUnread( 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 0489ba8d..2fab97f9 100644 --- a/app/src/main/java/chat/revolt/screens/chat/ChatRouterScreen.kt +++ b/app/src/main/java/chat/revolt/screens/chat/ChatRouterScreen.kt @@ -1,21 +1,16 @@ package chat.revolt.screens.chat +import android.Manifest import android.annotation.SuppressLint import android.content.Context import android.os.Build import android.util.Log import android.view.accessibility.AccessibilityManager import android.view.inputmethod.InputMethodManager -import androidx.activity.compose.BackHandler import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.animation.AnimatedVisibility -import androidx.compose.foundation.background -import androidx.compose.foundation.clickable -import androidx.compose.foundation.interaction.MutableInteractionSource -import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.WindowInsetsSides import androidx.compose.foundation.layout.fillMaxSize @@ -23,15 +18,20 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.only import androidx.compose.foundation.layout.systemBars import androidx.compose.foundation.layout.windowInsetsPadding +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Face +import androidx.compose.material.icons.filled.Home +import androidx.compose.material.icons.filled.Person import androidx.compose.material3.AlertDialog -import androidx.compose.material3.DismissibleDrawerSheet -import androidx.compose.material3.DismissibleNavigationDrawer +import androidx.compose.material3.BottomAppBar import androidx.compose.material3.DrawerState 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.NavigationBarItem +import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.material3.rememberDrawerState @@ -44,7 +44,6 @@ import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.compositionLocalOf import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope @@ -52,8 +51,6 @@ import androidx.compose.runtime.setValue import androidx.compose.runtime.snapshotFlow import androidx.compose.runtime.structuralEqualityPolicy import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.layout.onSizeChanged import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalView import androidx.compose.ui.res.painterResource @@ -79,15 +76,14 @@ import chat.revolt.composables.chat.DisconnectedNotice import chat.revolt.composables.screens.chat.drawer.ChannelSideDrawer import chat.revolt.dialogs.NotificationRationaleDialog import chat.revolt.internals.Changelogs -import chat.revolt.internals.extensions.zero import chat.revolt.persistence.KVStorage import chat.revolt.screens.chat.dialogs.safety.ReportMessageDialog import chat.revolt.screens.chat.dialogs.safety.ReportServerDialog import chat.revolt.screens.chat.dialogs.safety.ReportUserDialog import chat.revolt.screens.chat.views.FriendsScreen import chat.revolt.screens.chat.views.NoCurrentChannelScreen -import chat.revolt.screens.chat.views.OverviewScreen import chat.revolt.screens.chat.views.channel.ChannelScreen +import chat.revolt.screens.settings.SettingsScreen import chat.revolt.sheets.AddServerSheet import chat.revolt.sheets.ChangelogSheet import chat.revolt.sheets.EarlyAccessSheet @@ -109,28 +105,32 @@ import kotlinx.coroutines.launch import javax.inject.Inject sealed class ChatRouterDestination { - data object Overview : ChatRouterDestination() + data object Settings : ChatRouterDestination() data object Friends : ChatRouterDestination() + data object Home : ChatRouterDestination() data class Channel(val channelId: String) : ChatRouterDestination() + data class ServersChannels(val serverID: String) : ChatRouterDestination() data class NoCurrentChannel(val serverId: String?) : ChatRouterDestination() fun asSerialisedString(): String { return when (this) { - is Overview -> "overview" + is Settings -> "overview" is Friends -> "friends" is Channel -> "channel/$channelId" + is ServersChannels -> "channel/$serverID/servers" is NoCurrentChannel -> "no_current_channel/$serverId" + ChatRouterDestination.Home -> "home" } } companion object { - val default = Overview - val defaultForDMList = Overview + val default = Settings + val defaultForDMList = Settings fun fromString(destination: String): ChatRouterDestination { return when { - destination == "home" -> Overview // previous name for overview - destination == "overview" -> Overview + destination == "home" -> Settings // previous name for overview + destination == "overview" -> Settings destination == "friends" -> Friends destination.startsWith("no_current_channel/") -> NoCurrentChannel( destination.removePrefix( @@ -258,11 +258,8 @@ class ChatRouterViewModel @Inject constructor( savedLastChannel ?: RevoltAPI.serverCache[serverId]?.channels?.firstOrNull() val channelExists = RevoltAPI.channelCache.containsKey(channelId) - if (channelId != null && channelExists) { - setSaveDestination(ChatRouterDestination.Channel(channelId)) - } else { - setSaveDestination(ChatRouterDestination.NoCurrentChannel(serverId)) - } + setSaveDestination(ChatRouterDestination.ServersChannels(serverId)) +// } } } @@ -274,7 +271,6 @@ val LocalIsConnected = compositionLocalOf(structuralEqualityPolicy()) { false } fun ChatRouterScreen( topNav: NavController, windowSizeClass: WindowSizeClass, - disableBackHandler: Boolean, onNullifiedUser: () -> Unit, onEnterVoiceUI: (String) -> Unit, viewModel: ChatRouterViewModel = hiltViewModel() @@ -284,8 +280,6 @@ fun ChatRouterScreen( val context = LocalContext.current val view = LocalView.current - var drawerWidth by remember { mutableFloatStateOf(0.0f) } - var showPlatformModDMHint by remember { mutableStateOf(false) } var showStatusSheet by remember { mutableStateOf(false) } @@ -736,7 +730,7 @@ fun ChatRouterScreen( onSelected = { accepted -> if (accepted) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - askNotificationsPermission.launch(android.Manifest.permission.POST_NOTIFICATIONS) + askNotificationsPermission.launch(Manifest.permission.POST_NOTIFICATIONS) } else { viewModel.setRegisterForNotifications() } @@ -817,128 +811,26 @@ fun ChatRouterScreen( CompositionLocalProvider( LocalIsConnected provides (RealtimeSocket.disconnectionState == DisconnectionState.Connected) ) { - if (useTabletAwareUI) { - Row { - DismissibleDrawerSheet( - drawerContainerColor = Color.Transparent, - windowInsets = WindowInsets.zero - ) { - Sidebar( - viewModel = viewModel, - topNav = topNav, - currentServer = currentServer, - onShowStatusSheet = { + ChannelNavigator( + dest = viewModel.currentDestination, + topNav = topNav, + toggleDrawer = { + toggleDrawerLambda() + }, + onShowStatusSheet = { showStatusSheet = true - }, - onShowServerContextSheet = { + }, + onShowServerContextSheet = { serverContextSheetTarget = it showServerContextSheet = true - }, - onShowAddServerSheet = { + }, + onShowAddServerSheet = { showAddServerSheet = true - }, - showSettingsButton = isTouchExplorationEnabled, - onOpenSettings = { - topNav.navigate("settings") - }, - ) - } - ChannelNavigator( - dest = viewModel.currentDestination, - topNav = topNav, - useDrawer = false, - disableBackHandler = disableBackHandler, - toggleDrawer = { - toggleDrawerLambda() - }, - onEnterVoiceUI = onEnterVoiceUI, - ) - } - } else { - var useSidebarGesture by remember { mutableStateOf(true) } - DismissibleNavigationDrawer( - drawerState = drawerState, - gesturesEnabled = useSidebarGesture, - drawerContent = { - DismissibleDrawerSheet( - drawerContainerColor = Color.Transparent, - windowInsets = WindowInsets.zero, - modifier = Modifier.onSizeChanged { - drawerWidth = it.width.toFloat() - } - ) { - Sidebar( - viewModel = viewModel, - topNav = topNav, - currentServer = currentServer, - onShowStatusSheet = { - showStatusSheet = true - }, - onShowServerContextSheet = { - serverContextSheetTarget = it - showServerContextSheet = true - }, - onShowAddServerSheet = { - showAddServerSheet = true - }, - showSettingsButton = isTouchExplorationEnabled, - onOpenSettings = { - topNav.navigate("settings") - }, - drawerState = drawerState - ) - } - }, - content = { - Box(Modifier.fillMaxSize()) { - ChannelNavigator( - dest = viewModel.currentDestination, - topNav = topNav, - useDrawer = true, - disableBackHandler = disableBackHandler, - toggleDrawer = { - toggleDrawerLambda() - }, - drawerState = drawerState, - drawerGestureEnabled = useSidebarGesture, - setDrawerGestureEnabled = { - useSidebarGesture = it - }, - onEnterVoiceUI = onEnterVoiceUI, - ) - - // This is the overlay on the main content when the drawer is open - val interactionSource = remember { MutableInteractionSource() } - Box( - Modifier - .then( - if (drawerState.isOpen) { - Modifier.clickable( - interactionSource = interactionSource, - indication = null, - enabled = drawerState.isOpen, - onClick = { - scope.launch { - drawerState.close() - } - } - ) - } else Modifier - ) - .fillMaxSize() - .background( - MaterialTheme - .colorScheme - .surfaceContainerLowest - .copy( - alpha = (1.0f + (drawerState.currentOffset / drawerWidth)) * 0.7f - ) - ) - ) - } - } - ) - } + }, + isTouchExplorationEnabled = isTouchExplorationEnabled, + viewModel = viewModel, + currentServer = currentServer, + ) } } } @@ -948,8 +840,8 @@ fun Sidebar( viewModel: ChatRouterViewModel, currentServer: String?, topNav: NavController, - drawerState: DrawerState? = null, onShowStatusSheet: () -> Unit, + navigateToServer: (String) -> Unit, onShowServerContextSheet: (String) -> Unit, onShowAddServerSheet: () -> Unit, showSettingsButton: Boolean, @@ -959,8 +851,7 @@ fun Sidebar( onDestinationChanged = viewModel::setSaveDestination, currentDestination = viewModel.currentDestination, currentServer = currentServer, - drawerState = drawerState, - navigateToServer = viewModel::navigateToServer, + navigateToServer = navigateToServer, onLongPressAvatar = onShowStatusSheet, onShowServerContextSheet = onShowServerContextSheet, showSettingsIcon = showSettingsButton, @@ -974,59 +865,146 @@ fun Sidebar( fun ChannelNavigator( dest: ChatRouterDestination, topNav: NavController, - useDrawer: Boolean, + viewModel: ChatRouterViewModel, + onShowStatusSheet: () -> Unit = {}, + onShowAddServerSheet: () -> Unit = {}, + onShowServerContextSheet: (String) -> Unit = {}, + currentServer: String?, toggleDrawer: () -> Unit, + isTouchExplorationEnabled: Boolean, drawerState: DrawerState? = null, - drawerGestureEnabled: Boolean = true, - disableBackHandler: Boolean = false, - onEnterVoiceUI: (String) -> Unit = {}, setDrawerGestureEnabled: (Boolean) -> Unit = {}, ) { val scope = rememberCoroutineScope() - BackHandler(useDrawer && !disableBackHandler) { - toggleDrawer() - } - - Column(Modifier.fillMaxSize()) { - when (dest) { - is ChatRouterDestination.Overview -> { - OverviewScreen( - navController = topNav, - useDrawer = useDrawer, - onDrawerClicked = toggleDrawer, - ) - } - - is ChatRouterDestination.Friends -> { - FriendsScreen( - topNav = topNav, - useDrawer = useDrawer, - onDrawerClicked = toggleDrawer, - ) - } - - is ChatRouterDestination.Channel -> { - ChannelScreen( - channelId = dest.channelId, - onToggleDrawer = { - scope.launch { - if (drawerState?.isOpen == true) { - drawerState.close() - } else { - drawerState?.open() - } - } + Scaffold( + modifier = Modifier.fillMaxSize(), + bottomBar = { + BottomAppBar { + NavigationBarItem( + icon = { + Icon( + imageVector = Icons.Default.Home, + contentDescription = "Home", + ) }, - useDrawer = useDrawer, - drawerGestureEnabled = drawerGestureEnabled, - setDrawerGestureEnabled = setDrawerGestureEnabled, - drawerIsOpen = drawerState?.isOpen == true, + label = { + Text(text = "you") + }, + selected = dest is ChatRouterDestination.Home, + enabled = true, + onClick = { + viewModel.setSaveDestination(ChatRouterDestination.Home) + } + ) + NavigationBarItem( + icon = { + Icon( + imageVector = Icons.Default.Person, + contentDescription = "Friends", + ) + }, + label = { + Text(text = "Friends") + }, + selected = dest is ChatRouterDestination.Friends, + enabled = true, + onClick = { + viewModel.setSaveDestination(ChatRouterDestination.Friends) + } + ) + NavigationBarItem( + icon = { + Icon( + imageVector = Icons.Default.Face, + contentDescription = "You", + ) + }, + label = { + Text(text = "you") + }, + selected = dest is ChatRouterDestination.Settings, + enabled = true, + onClick = { + viewModel.setSaveDestination(ChatRouterDestination.Settings) + } ) } + } + ) { innerPadding -> + Column { + when (dest) { + is ChatRouterDestination.Settings -> { + SettingsScreen( + navController = topNav, + ) + } - is ChatRouterDestination.NoCurrentChannel -> { - NoCurrentChannelScreen(useDrawer = useDrawer, onDrawerClicked = toggleDrawer) + is ChatRouterDestination.Friends -> { + FriendsScreen( + topNav = topNav, + onDrawerClicked = toggleDrawer, + ) + } + + is ChatRouterDestination.Home -> { + Sidebar( + viewModel = viewModel, + topNav = topNav, + currentServer = currentServer, + onShowStatusSheet = onShowStatusSheet, + navigateToServer = viewModel::navigateToServer, + onShowServerContextSheet = onShowServerContextSheet, + onShowAddServerSheet = onShowAddServerSheet, + showSettingsButton = isTouchExplorationEnabled, + onOpenSettings = { + topNav.navigate("settings") + }, + ) + } + is ChatRouterDestination.ServersChannels-> { + Sidebar( + viewModel = viewModel, + topNav = topNav, + currentServer = dest.serverID, + onShowStatusSheet = onShowStatusSheet, + navigateToServer = viewModel::navigateToServer, + onShowServerContextSheet = onShowServerContextSheet, + onShowAddServerSheet = onShowAddServerSheet, + showSettingsButton = isTouchExplorationEnabled, + onOpenSettings = { + topNav.navigate("settings") + }, + ) + } + + is ChatRouterDestination.Channel -> { + ChannelScreen( + channelId = dest.channelId, + backToChannelsScreen = { + currentServer?.let { + viewModel.setSaveDestination(ChatRouterDestination.ServersChannels( + serverID = currentServer + )) + } + }, + onToggleDrawer = { + scope.launch { + if (drawerState?.isOpen == true) { + drawerState.close() + } else { + drawerState?.open() + } + } + }, + setDrawerGestureEnabled = setDrawerGestureEnabled, + drawerIsOpen = drawerState?.isOpen == true, + ) + } + + is ChatRouterDestination.NoCurrentChannel -> { + NoCurrentChannelScreen(onDrawerClicked = toggleDrawer) + } } } } diff --git a/app/src/main/java/chat/revolt/screens/chat/views/FriendsScreen.kt b/app/src/main/java/chat/revolt/screens/chat/views/FriendsScreen.kt index 38e54d16..5906c657 100644 --- a/app/src/main/java/chat/revolt/screens/chat/views/FriendsScreen.kt +++ b/app/src/main/java/chat/revolt/screens/chat/views/FriendsScreen.kt @@ -120,7 +120,7 @@ private fun showInvalidClipboardToast(context: Context) { ExperimentalMaterial3ExpressiveApi::class ) @Composable -fun FriendsScreen(topNav: NavController, useDrawer: Boolean, onDrawerClicked: () -> Unit) { +fun FriendsScreen(topNav: NavController, useDrawer: Boolean = false, onDrawerClicked: () -> Unit) { val clipboard = LocalClipboard.current val context = LocalContext.current var overflowMenuShown by remember { mutableStateOf(false) } diff --git a/app/src/main/java/chat/revolt/screens/chat/views/NoCurrentChannelScreen.kt b/app/src/main/java/chat/revolt/screens/chat/views/NoCurrentChannelScreen.kt index 5354c8a8..91dcc03e 100644 --- a/app/src/main/java/chat/revolt/screens/chat/views/NoCurrentChannelScreen.kt +++ b/app/src/main/java/chat/revolt/screens/chat/views/NoCurrentChannelScreen.kt @@ -10,8 +10,6 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.statusBars -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Menu import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.IconButton @@ -33,7 +31,7 @@ import chat.revolt.screens.chat.LocalIsConnected @OptIn(ExperimentalMaterial3Api::class) @Composable -fun NoCurrentChannelScreen(useDrawer: Boolean, onDrawerClicked: () -> Unit) { +fun NoCurrentChannelScreen(useDrawer: Boolean = false, onDrawerClicked: () -> Unit) { Scaffold( topBar = { Column { diff --git a/app/src/main/java/chat/revolt/screens/chat/views/OverviewScreen.kt b/app/src/main/java/chat/revolt/screens/chat/views/OverviewScreen.kt index 31622127..fa9bc235 100644 --- a/app/src/main/java/chat/revolt/screens/chat/views/OverviewScreen.kt +++ b/app/src/main/java/chat/revolt/screens/chat/views/OverviewScreen.kt @@ -68,7 +68,7 @@ import io.sentry.Sentry @Composable fun OverviewScreen( navController: NavController, - useDrawer: Boolean, + useDrawer: Boolean = false, onDrawerClicked: () -> Unit, includePadding: Boolean = true ) { 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 5c8b80ab..728d4d58 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 @@ -190,9 +190,9 @@ private const val NOT_ENOUGH_SPACE_FOR_PANES_THRESHOLD = 500 fun ChannelScreen( channelId: String, onToggleDrawer: () -> Unit, - useDrawer: Boolean, + useDrawer: Boolean = false, + backToChannelsScreen: (() -> Unit)?, useBackButton: Boolean = false, - drawerGestureEnabled: Boolean = true, setDrawerGestureEnabled: (Boolean) -> Unit = {}, drawerIsOpen: Boolean = false, backButtonAction: (() -> Unit)? = null, @@ -204,6 +204,12 @@ fun ChannelScreen( val context = LocalContext.current val config = LocalConfiguration.current + backToChannelsScreen?.let { + BackHandler { + backToChannelsScreen() + } + } + LaunchedEffect(Unit) { viewModel.listenToWsEvents() } @@ -347,7 +353,7 @@ fun ChannelScreen( MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues ) - } catch (e: Exception) { + } catch (_: Exception) { Toast.makeText( context, context.getString( @@ -363,7 +369,7 @@ fun ChannelScreen( capturedPhotoUri.value?.let { uri -> pickCameraLauncher.launch(uri) } - } catch (e: Exception) { + } catch (_: Exception) { Toast.makeText( context, context.getString( @@ -650,627 +656,631 @@ fun ChannelScreen( targetState = viewModel.ageGateUnlocked, label = "ageGateUnlocked" ) { ageGateUnlocked -> - if (ageGateUnlocked == false) { - ChannelScreenAgeGate( - onAccept = { - scope.launch { - viewModel.unlockAgeGate() - } - }, - onDeny = { - onToggleDrawer() - } - ) - } else if (ageGateUnlocked == null) { - Box( - contentAlignment = Alignment.Center, - modifier = Modifier.fillMaxSize(), - ) { - CircularProgressIndicator(modifier = Modifier.size(48.dp)) - } - } else if (ageGateUnlocked == true) { - Column( - modifier = Modifier - .padding(pv) - ) { - Box( - modifier = Modifier.weight(1f), - contentAlignment = Alignment.BottomCenter - ) { - LazyColumn( - state = lazyListState, - userScrollEnabled = !disableScroll, - reverseLayout = true, - contentPadding = PaddingValues(top = 16.dp, bottom = 32.dp) - ) { - - // If we don't have a guaranteed first item, the message list will not scroll - // to the bottom when new messages are added. Evil hack to make our other evil - // hack (clear/addAll) work. Too bad! - item(key = "guaranteed_first") { - Box {} + when (ageGateUnlocked) { + false -> { + ChannelScreenAgeGate( + onAccept = { + scope.launch { + viewModel.unlockAgeGate() } + }, + onDeny = { + onToggleDrawer() + } + ) + } + null -> { + Box( + contentAlignment = Alignment.Center, + modifier = Modifier.fillMaxSize(), + ) { + CircularProgressIndicator(modifier = Modifier.size(48.dp)) + } + } + true -> { + Column( + modifier = Modifier + .padding(pv) + ) { + Box( + modifier = Modifier.weight(1f), + contentAlignment = Alignment.BottomCenter + ) { + LazyColumn( + state = lazyListState, + userScrollEnabled = !disableScroll, + reverseLayout = true, + contentPadding = PaddingValues(top = 16.dp, bottom = 32.dp) + ) { - items( - viewModel.items.size, - key = { index -> - when (val item = viewModel.items[index]) { - is ChannelScreenItem.RegularMessage -> item.message.id!! - is ChannelScreenItem.ProspectiveMessage -> item.message.id!! - is ChannelScreenItem.FailedMessage -> item.message.id!! - is ChannelScreenItem.SystemMessage -> item.message.id!! - is ChannelScreenItem.DateDivider -> item.instant.toEpochMilliseconds() - is ChannelScreenItem.LoadTrigger -> index - is ChannelScreenItem.Loading -> index - } - }, - contentType = { index -> - when (viewModel.items.getOrNull(index)) { - null -> null - is ChannelScreenItem.RegularMessage -> "RegularMessage" - is ChannelScreenItem.ProspectiveMessage -> "ProspectiveMessage" - is ChannelScreenItem.FailedMessage -> "FailedMessage" - is ChannelScreenItem.SystemMessage -> "SystemMessage" - is ChannelScreenItem.DateDivider -> "DateDivider" - is ChannelScreenItem.LoadTrigger -> "LoadTrigger" - is ChannelScreenItem.Loading -> "Loading" - } + // If we don't have a guaranteed first item, the message list will not scroll + // to the bottom when new messages are added. Evil hack to make our other evil + // hack (clear/addAll) work. Too bad! + item(key = "guaranteed_first") { + Box {} } - ) { index -> - when (val item = viewModel.items[index]) { - is ChannelScreenItem.RegularMessage -> { - RegularMessage( - item.message, - viewModel.channel, - drawerIsOpen = drawerIsOpen, - setDrawerGestureEnabled = { - setDrawerGestureEnabled(it) - }, - setDisableScroll = { - disableScroll = it - }, - showMessageBottomSheet = { - messageContextSheetTarget = it - messageContextSheetShown = true - }, - showReactBottomSheet = { - item.message.id?.let { - reactSheetTarget = it - reactSheetShown = true - } - }, - putTextAtCursorPosition = viewModel::putAtCursorPosition, - replyToMessage = viewModel::addReplyTo, - scope = scope - ) - } - is ChannelScreenItem.ProspectiveMessage -> { - Box(Modifier.alpha(0.5f)) { - Message( - message = item.message, - onMessageContextMenu = { - // TODO Context menu that allows you to cancel send - }, - onAvatarClick = {}, - onNameClick = {}, - canReply = false, - onReply = {}, - onAddReaction = {} - ) + items( + viewModel.items.size, + key = { index -> + when (val item = viewModel.items[index]) { + is ChannelScreenItem.RegularMessage -> item.message.id!! + is ChannelScreenItem.ProspectiveMessage -> item.message.id!! + is ChannelScreenItem.FailedMessage -> item.message.id!! + is ChannelScreenItem.SystemMessage -> item.message.id!! + is ChannelScreenItem.DateDivider -> item.instant.toEpochMilliseconds() + is ChannelScreenItem.LoadTrigger -> index + is ChannelScreenItem.Loading -> index + } + }, + contentType = { index -> + when (viewModel.items.getOrNull(index)) { + null -> null + is ChannelScreenItem.RegularMessage -> "RegularMessage" + is ChannelScreenItem.ProspectiveMessage -> "ProspectiveMessage" + is ChannelScreenItem.FailedMessage -> "FailedMessage" + is ChannelScreenItem.SystemMessage -> "SystemMessage" + is ChannelScreenItem.DateDivider -> "DateDivider" + is ChannelScreenItem.LoadTrigger -> "LoadTrigger" + is ChannelScreenItem.Loading -> "Loading" } } + ) { index -> + when (val item = viewModel.items[index]) { + is ChannelScreenItem.RegularMessage -> { + RegularMessage( + item.message, + viewModel.channel, + drawerIsOpen = drawerIsOpen, + setDrawerGestureEnabled = { + setDrawerGestureEnabled(it) + }, + setDisableScroll = { + disableScroll = it + }, + showMessageBottomSheet = { + messageContextSheetTarget = it + messageContextSheetShown = true + }, + showReactBottomSheet = { + item.message.id?.let { + reactSheetTarget = it + reactSheetShown = true + } + }, + putTextAtCursorPosition = viewModel::putAtCursorPosition, + replyToMessage = viewModel::addReplyTo, + scope = scope + ) + } - is ChannelScreenItem.FailedMessage -> { - CompositionLocalProvider(LocalContentColor provides MaterialTheme.colorScheme.error) { - Column { + is ChannelScreenItem.ProspectiveMessage -> { + Box(Modifier.alpha(0.5f)) { Message( message = item.message, - onMessageContextMenu = {}, + onMessageContextMenu = { + // TODO Context menu that allows you to cancel send + }, onAvatarClick = {}, onNameClick = {}, canReply = false, onReply = {}, onAddReaction = {} ) - Row { - UserAvatarWidthPlaceholder() - Text( - stringResource(R.string.message_failed_to_send), - style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.error.copy( - alpha = 0.8f - ), - modifier = Modifier.padding( - top = 4.dp, - bottom = 4.dp, - start = 20.dp - ) - ) - } } } - } - is ChannelScreenItem.SystemMessage -> { - SystemMessage(message = item.message) - } - - is ChannelScreenItem.DateDivider -> { - DateDivider(instant = item.instant) - } - - is ChannelScreenItem.LoadTrigger -> { - LaunchedEffect(Unit) { - Log.d( - "ChannelScreen", - "LoadTrigger: After ${item.after} Before ${item.before}" - ) - } - } - - is ChannelScreenItem.Loading -> { - Column( - modifier = Modifier - .fillMaxWidth() - .shimmer(rememberShimmer(ShimmerBounds.Window)), - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.spacedBy(8.dp) - ) { - MessageSkeleton(MessageSkeletonVariant.One) - MessageSkeleton(MessageSkeletonVariant.Two) - MessageSkeleton(MessageSkeletonVariant.Three) - } - } - } - } - } - - TypingIndicator( - users = viewModel.typingUsers, - serverId = viewModel.channel?.server - ) - - androidx.compose.animation.AnimatedVisibility( - !isScrolledToBottom.value, - enter = slideInVertically( - animationSpec = RevoltTweenInt, - initialOffsetY = { it } - ) + fadeIn(animationSpec = RevoltTweenFloat), - exit = slideOutVertically( - animationSpec = RevoltTweenInt, - targetOffsetY = { it } - ) + fadeOut(animationSpec = RevoltTweenFloat) - ) { - SmallFloatingActionButton( - modifier = Modifier - .padding(bottom = scrollDownFABPadding) - .align(Alignment.BottomCenter) - .padding(16.dp), - onClick = { - scope.launch { - lazyListState.animateScrollToItem(0) - } - }, - contentColor = MaterialTheme.colorScheme.onSurfaceVariant, - containerColor = MaterialTheme.colorScheme.surfaceVariant - ) { - Icon( - painter = painterResource(R.drawable.icn_south_24dp), - contentDescription = stringResource(R.string.scroll_to_bottom) - ) - } - } - - Column( - verticalArrangement = Arrangement.spacedBy(8.dp), - modifier = Modifier - .align(Alignment.TopCenter) - .padding(8.dp) - ) { - if (viewModel.showPhysicalKeyboardSpark) { - Card { - Column( - verticalArrangement = Arrangement.spacedBy(8.dp), - modifier = Modifier.padding(16.dp) - ) { - Text( - stringResource(R.string.spark_keyboard_shortcuts), - style = MaterialTheme.typography.bodyLarge, - fontWeight = FontWeight.SemiBold - ) - Text( - buildAnnotatedString { - val raw = - stringResource(R.string.spark_keyboard_shortcuts_description) - val before = raw.substringBefore("%1\$s") - val after = raw.substringAfter("%1\$s") - - append(before) - appendInlineContent("metaKey", "Meta") - append(" + /") - append(after) - }, - inlineContent = mapOf( - "metaKey" to InlineTextContent( - placeholder = Placeholder( - width = 1.em, - height = 1.em, - placeholderVerticalAlign = PlaceholderVerticalAlign.Center + is ChannelScreenItem.FailedMessage -> { + CompositionLocalProvider(LocalContentColor provides MaterialTheme.colorScheme.error) { + Column { + Message( + message = item.message, + onMessageContextMenu = {}, + onAvatarClick = {}, + onNameClick = {}, + canReply = false, + onReply = {}, + onAddReaction = {} ) - ) { - with(LocalDensity.current) { - Image( - painterResource(R.drawable.ic_meta_key_24dp), - contentDescription = null, - colorFilter = ColorFilter.tint( - LocalContentColor.current + Row { + UserAvatarWidthPlaceholder() + Text( + stringResource(R.string.message_failed_to_send), + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.error.copy( + alpha = 0.8f + ), + modifier = Modifier.padding( + top = 4.dp, + bottom = 4.dp, + start = 20.dp ) ) } } - ), - style = MaterialTheme.typography.bodyLarge - ) - Row( - horizontalArrangement = Arrangement.spacedBy(8.dp), - ) { - Button( - onClick = { - viewModel.dismissPhysicalKeyboardSpark() - }, - modifier = Modifier.weight(1f) - ) { - Text(stringResource(R.string.spark_keyboard_shortcuts_dismiss)) } - TextButton( - onClick = { - (context as Activity).requestShowKeyboardShortcuts() - }, - modifier = Modifier.weight(1f) + } + + is ChannelScreenItem.SystemMessage -> { + SystemMessage(message = item.message) + } + + is ChannelScreenItem.DateDivider -> { + DateDivider(instant = item.instant) + } + + is ChannelScreenItem.LoadTrigger -> { + LaunchedEffect(Unit) { + Log.d( + "ChannelScreen", + "LoadTrigger: After ${item.after} Before ${item.before}" + ) + } + } + + is ChannelScreenItem.Loading -> { + Column( + modifier = Modifier + .fillMaxWidth() + .shimmer(rememberShimmer(ShimmerBounds.Window)), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(8.dp) ) { - Text(stringResource(R.string.spark_keyboard_shortcuts_cta)) + MessageSkeleton(MessageSkeletonVariant.One) + MessageSkeleton(MessageSkeletonVariant.Two) + MessageSkeleton(MessageSkeletonVariant.Three) } } } } } - if (viewModel.channel?.channelType == ChannelType.VoiceChannel - && FeatureFlags.voiceChannels2_0Granted + TypingIndicator( + users = viewModel.typingUsers, + serverId = viewModel.channel?.server + ) + + androidx.compose.animation.AnimatedVisibility( + !isScrolledToBottom.value, + enter = slideInVertically( + animationSpec = RevoltTweenInt, + initialOffsetY = { it } + ) + fadeIn(animationSpec = RevoltTweenFloat), + exit = slideOutVertically( + animationSpec = RevoltTweenInt, + targetOffsetY = { it } + ) + fadeOut(animationSpec = RevoltTweenFloat) ) { - JoinVoiceChannelButton(channelId) + SmallFloatingActionButton( + modifier = Modifier + .padding(bottom = scrollDownFABPadding) + .align(Alignment.BottomCenter) + .padding(16.dp), + onClick = { + scope.launch { + lazyListState.animateScrollToItem(0) + } + }, + contentColor = MaterialTheme.colorScheme.onSurfaceVariant, + containerColor = MaterialTheme.colorScheme.surfaceVariant + ) { + Icon( + painter = painterResource(R.drawable.icn_south_24dp), + contentDescription = stringResource(R.string.scroll_to_bottom) + ) + } } - } - } - Column( - modifier = Modifier - .background(MaterialTheme.colorScheme.surfaceContainer) - .fillMaxWidth(), - verticalArrangement = Arrangement.Center, - horizontalAlignment = Alignment.CenterHorizontally - ) { - AnimatedContent( - targetState = viewModel.denyMessageField, - label = "denyMessageField" - ) { deny -> - if (!deny) { - Column { - AnimatedVisibility( - visible = viewModel.draftReplyTo.isNotEmpty() && !viewModel.denyMessageField - ) { - ReplyManager( - replies = viewModel.draftReplyTo, - onToggleMention = { - scope.launch { viewModel.toggleMentionOnReply(it.id) } - }, - onRemove = { - viewModel.draftReplyTo.remove(it) - } - ) - } + Column( + verticalArrangement = Arrangement.spacedBy(8.dp), + modifier = Modifier + .align(Alignment.TopCenter) + .padding(8.dp) + ) { + if (viewModel.showPhysicalKeyboardSpark) { + Card { + Column( + verticalArrangement = Arrangement.spacedBy(8.dp), + modifier = Modifier.padding(16.dp) + ) { + Text( + stringResource(R.string.spark_keyboard_shortcuts), + style = MaterialTheme.typography.bodyLarge, + fontWeight = FontWeight.SemiBold + ) + Text( + buildAnnotatedString { + val raw = + stringResource(R.string.spark_keyboard_shortcuts_description) + val before = raw.substringBefore("%1\$s") + val after = raw.substringAfter("%1\$s") - AnimatedVisibility( - visible = viewModel.draftAttachments.isNotEmpty() && !viewModel.denyMessageField - ) { - AttachmentManager( - attachments = viewModel.draftAttachments, - uploading = viewModel.attachmentUploadProgress > 0, - uploadProgress = viewModel.attachmentUploadProgress, - canRemove = true, - canPreview = true, - onRemove = { - viewModel.draftAttachments.remove(it) - }, - onToggleSpoiler = { - val index = viewModel.draftAttachments - .indexOfFirst { a -> a.pickerIdentifier == it.pickerIdentifier } - - if (index != -1) { - val attachment = - viewModel.draftAttachments[index] - viewModel.draftAttachments[index] = - attachment.copy( - spoiler = !attachment.spoiler + append(before) + appendInlineContent("metaKey", "Meta") + append(" + /") + append(after) + }, + inlineContent = mapOf( + "metaKey" to InlineTextContent( + placeholder = Placeholder( + width = 1.em, + height = 1.em, + placeholderVerticalAlign = PlaceholderVerticalAlign.Center ) + ) { + with(LocalDensity.current) { + Image( + painterResource(R.drawable.ic_meta_key_24dp), + contentDescription = null, + colorFilter = ColorFilter.tint( + LocalContentColor.current + ) + ) + } + } + ), + style = MaterialTheme.typography.bodyLarge + ) + Row( + horizontalArrangement = Arrangement.spacedBy(8.dp), + ) { + Button( + onClick = { + viewModel.dismissPhysicalKeyboardSpark() + }, + modifier = Modifier.weight(1f) + ) { + Text(stringResource(R.string.spark_keyboard_shortcuts_dismiss)) + } + TextButton( + onClick = { + (context as Activity).requestShowKeyboardShortcuts() + }, + modifier = Modifier.weight(1f) + ) { + Text(stringResource(R.string.spark_keyboard_shortcuts_cta)) } } - ) + } } + } - AnimatedVisibility(visible = viewModel.editingMessage != null) { - Row(Modifier.padding(start = 24.dp, top = 8.dp)) { - AssistChip( - onClick = { - viewModel.editingMessage = null - viewModel.putDraftContent("", true) + if (viewModel.channel?.channelType == ChannelType.VoiceChannel + && FeatureFlags.voiceChannels2_0Granted + ) { + JoinVoiceChannelButton(channelId) + } + } + } + + Column( + modifier = Modifier + .background(MaterialTheme.colorScheme.surfaceContainer) + .fillMaxWidth(), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally + ) { + AnimatedContent( + targetState = viewModel.denyMessageField, + label = "denyMessageField" + ) { deny -> + if (!deny) { + Column { + AnimatedVisibility( + visible = viewModel.draftReplyTo.isNotEmpty() && !viewModel.denyMessageField + ) { + ReplyManager( + replies = viewModel.draftReplyTo, + onToggleMention = { + scope.launch { viewModel.toggleMentionOnReply(it.id) } }, - label = { - Text(stringResource(R.string.message_field_editing_message)) + onRemove = { + viewModel.draftReplyTo.remove(it) + } + ) + } + + AnimatedVisibility( + visible = viewModel.draftAttachments.isNotEmpty() && !viewModel.denyMessageField + ) { + AttachmentManager( + attachments = viewModel.draftAttachments, + uploading = viewModel.attachmentUploadProgress > 0, + uploadProgress = viewModel.attachmentUploadProgress, + canRemove = true, + canPreview = true, + onRemove = { + viewModel.draftAttachments.remove(it) }, + onToggleSpoiler = { + val index = viewModel.draftAttachments + .indexOfFirst { a -> a.pickerIdentifier == it.pickerIdentifier } + + if (index != -1) { + val attachment = + viewModel.draftAttachments[index] + viewModel.draftAttachments[index] = + attachment.copy( + spoiler = !attachment.spoiler + ) + } + } + ) + } + + AnimatedVisibility(visible = viewModel.editingMessage != null) { + Row(Modifier.padding(start = 24.dp, top = 8.dp)) { + AssistChip( + onClick = { + viewModel.editingMessage = null + viewModel.putDraftContent("", true) + }, + label = { + Text(stringResource(R.string.message_field_editing_message)) + }, + leadingIcon = { + Icon( + painter = painterResource(R.drawable.icn_edit_24dp), + contentDescription = null + ) + }, + trailingIcon = { + Icon( + painter = painterResource(R.drawable.icn_close_24dp), + contentDescription = stringResource(R.string.message_field_editing_message_cancel_alt), + tint = MaterialTheme.colorScheme.onSurface, + modifier = Modifier.alpha(0.8f) + ) + } + ) + } + } + + MessageField( + initialValue = viewModel.initialTextFieldValue, + initialValueDirtyMarker = viewModel.initialTextFieldValueDirtyMarker, + onValueChange = viewModel::putDraftContent, + onAddAttachment = { + if (viewModel.activePane == ChannelScreenActivePane.AttachmentPicker) { + viewModel.activePane = + ChannelScreenActivePane.None + } else { + viewModel.activePane = + ChannelScreenActivePane.AttachmentPicker + } + }, + onCommitAttachment = { + processFileUri(it, null) + }, + onPickEmoji = { + if (viewModel.activePane == ChannelScreenActivePane.EmojiPicker) { + viewModel.activePane = + ChannelScreenActivePane.None + } else { + viewModel.activePane = + ChannelScreenActivePane.EmojiPicker + } + }, + onSendMessage = viewModel::sendPendingMessage, + channelType = viewModel.channel?.channelType + ?: ChannelType.TextChannel, + channelName = viewModel.channel?.let { channel -> + ChannelUtils.resolveName(channel) + } + ?: stringResource(R.string.unknown), + onFocusChange = { isFocused -> + if (isFocused && viewModel.activePane != ChannelScreenActivePane.None) { + viewModel.activePane = + ChannelScreenActivePane.None + imeInTransition = true + } + }, + forceSendButton = viewModel.draftAttachments.isNotEmpty(), + canAttach = (channelPermissions has PermissionBit.UploadFiles) && viewModel.editingMessage == null, + serverId = viewModel.channel?.server, + channelId = channelId, + failedValidation = viewModel.draftContent.length > 2000, + valueIsBlank = viewModel.draftContent.isBlank(), + cancelEdit = { + viewModel.editingMessage = null + viewModel.putDraftContent("", true) + } + ) + + DropdownMenu( + expanded = viewModel.activePane == ChannelScreenActivePane.AttachmentPicker && notEnoughSpaceForPanes, + onDismissRequest = { + viewModel.activePane = ChannelScreenActivePane.None + } + ) { + DropdownMenuItem( leadingIcon = { Icon( - painter = painterResource(R.drawable.icn_edit_24dp), - contentDescription = null + painter = painterResource(R.drawable.icn_attach_file_24dp), + contentDescription = null // Provided by text below ) }, - trailingIcon = { + text = { Text(stringResource(R.string.file_picker_chip_documents)) }, + onClick = { + openDocumentPickerCallback() + viewModel.activePane = + ChannelScreenActivePane.None + } + ) + DropdownMenuItem( + leadingIcon = { Icon( - painter = painterResource(R.drawable.icn_close_24dp), - contentDescription = stringResource(R.string.message_field_editing_message_cancel_alt), - tint = MaterialTheme.colorScheme.onSurface, - modifier = Modifier.alpha(0.8f) + painter = painterResource(R.drawable.icn_camera_24dp), + contentDescription = null // Provided by text below ) + }, + text = { Text(stringResource(R.string.file_picker_chip_camera)) }, + onClick = { + openCameraCallback() + viewModel.activePane = + ChannelScreenActivePane.None + } + ) + DropdownMenuItem( + leadingIcon = { + Icon( + painter = painterResource(R.drawable.icn_photo_library_24dp), + contentDescription = null // Provided by text below + ) + }, + text = { Text(stringResource(R.string.file_picker_chip_photo_picker)) }, + onClick = { + openPhotoPickerCallback() + viewModel.activePane = + ChannelScreenActivePane.None } ) } } - - MessageField( - initialValue = viewModel.initialTextFieldValue, - initialValueDirtyMarker = viewModel.initialTextFieldValueDirtyMarker, - onValueChange = viewModel::putDraftContent, - onAddAttachment = { - if (viewModel.activePane == ChannelScreenActivePane.AttachmentPicker) { - viewModel.activePane = - ChannelScreenActivePane.None - } else { - viewModel.activePane = - ChannelScreenActivePane.AttachmentPicker - } - }, - onCommitAttachment = { - processFileUri(it, null) - }, - onPickEmoji = { - if (viewModel.activePane == ChannelScreenActivePane.EmojiPicker) { - viewModel.activePane = - ChannelScreenActivePane.None - } else { - viewModel.activePane = - ChannelScreenActivePane.EmojiPicker - } - }, - onSendMessage = viewModel::sendPendingMessage, - channelType = viewModel.channel?.channelType - ?: ChannelType.TextChannel, - channelName = viewModel.channel?.let { channel -> - ChannelUtils.resolveName(channel) - } - ?: stringResource(R.string.unknown), - onFocusChange = { isFocused -> - if (isFocused && viewModel.activePane != ChannelScreenActivePane.None) { - viewModel.activePane = - ChannelScreenActivePane.None - imeInTransition = true - } - }, - forceSendButton = viewModel.draftAttachments.isNotEmpty(), - canAttach = (channelPermissions has PermissionBit.UploadFiles) && viewModel.editingMessage == null, - serverId = viewModel.channel?.server, - channelId = channelId, - failedValidation = viewModel.draftContent.length > 2000, - valueIsBlank = viewModel.draftContent.isBlank(), - cancelEdit = { - viewModel.editingMessage = null - viewModel.putDraftContent("", true) - } - ) - - DropdownMenu( - expanded = viewModel.activePane == ChannelScreenActivePane.AttachmentPicker && notEnoughSpaceForPanes, - onDismissRequest = { - viewModel.activePane = ChannelScreenActivePane.None - } + } else { + Box( + modifier = Modifier + .padding(horizontal = 32.dp, vertical = 16.dp) ) { - DropdownMenuItem( - leadingIcon = { - Icon( - painter = painterResource(R.drawable.icn_attach_file_24dp), - contentDescription = null // Provided by text below - ) - }, - text = { Text(stringResource(R.string.file_picker_chip_documents)) }, - onClick = { - openDocumentPickerCallback() - viewModel.activePane = - ChannelScreenActivePane.None - } - ) - DropdownMenuItem( - leadingIcon = { - Icon( - painter = painterResource(R.drawable.icn_camera_24dp), - contentDescription = null // Provided by text below - ) - }, - text = { Text(stringResource(R.string.file_picker_chip_camera)) }, - onClick = { - openCameraCallback() - viewModel.activePane = - ChannelScreenActivePane.None - } - ) - DropdownMenuItem( - leadingIcon = { - Icon( - painter = painterResource(R.drawable.icn_photo_library_24dp), - contentDescription = null // Provided by text below - ) - }, - text = { Text(stringResource(R.string.file_picker_chip_photo_picker)) }, - onClick = { - openPhotoPickerCallback() - viewModel.activePane = - ChannelScreenActivePane.None - } + Text( + stringResource(viewModel.denyMessageFieldReasonResource), + color = MaterialTheme.colorScheme.error, + style = MaterialTheme.typography.bodyMedium, + textAlign = TextAlign.Center ) } } - } else { - Box( - modifier = Modifier - .padding(horizontal = 32.dp, vertical = 16.dp) - ) { - Text( - stringResource(viewModel.denyMessageFieldReasonResource), - color = MaterialTheme.colorScheme.error, - style = MaterialTheme.typography.bodyMedium, - textAlign = TextAlign.Center - ) - } } - } - if (viewModel.activePane == ChannelScreenActivePane.None && !imeInTransition) { - Spacer( - Modifier - .imePadding() - .navigationBarsPadding() - .background(MaterialTheme.colorScheme.surfaceContainer) - ) - } else { - if (!notEnoughSpaceForPanes) { - Box( - Modifier - .heightIn(min = pxAsDp(fallbackKeyboardHeight)) - ) { - Box( - Modifier.then( - if (emojiSearchFocused) { - Modifier.requiredHeight( - pxAsDp( - max( - imeCurrentInset * 2, - fallbackKeyboardHeight - ) - ) - ) - } else { - Modifier.requiredHeight( - pxAsDp( - fallbackKeyboardHeight - ) - ) - } - ) - ) { - when (viewModel.activePane) { - ChannelScreenActivePane.EmojiPicker -> { - BackHandler(enabled = viewModel.activePane == ChannelScreenActivePane.EmojiPicker) { - viewModel.activePane = - ChannelScreenActivePane.None - } - - Column( - modifier = Modifier - .fillMaxWidth() - .background(MaterialTheme.colorScheme.surfaceContainer) - .padding(4.dp) - .navigationBarsPadding() - ) { - EmojiPicker( - onEmojiSelected = viewModel::putAtCursorPosition, - bottomInset = pxAsDp( - max( - imeCurrentInset - navigationBarsInset, - 0 - ) - ), - onSearchFocus = { - emojiSearchFocused = it - } - ) - } - } - - ChannelScreenActivePane.AttachmentPicker -> { - BackHandler(enabled = viewModel.activePane == ChannelScreenActivePane.AttachmentPicker) { - viewModel.activePane = - ChannelScreenActivePane.None - } - - MediaPickerGateway( - onOpenPhotoPicker = { - openPhotoPickerCallback() - viewModel.activePane = - ChannelScreenActivePane.None - }, - onOpenDocumentPicker = { - openDocumentPickerCallback() - viewModel.activePane = - ChannelScreenActivePane.None - }, - onOpenCamera = { - openCameraCallback() - viewModel.activePane = - ChannelScreenActivePane.None - }, - ) - } - - else -> { - // Do nothing - } - } - } - Box(Modifier.imePadding()) - } - } else { - if (viewModel.activePane == ChannelScreenActivePane.EmojiPicker) { - BackHandler(enabled = viewModel.activePane == ChannelScreenActivePane.EmojiPicker) { - viewModel.activePane = - ChannelScreenActivePane.None - } - - Column( - modifier = Modifier - .fillMaxWidth() - .height(600.dp) - .background(MaterialTheme.colorScheme.surfaceContainer) - .padding(4.dp) - .navigationBarsPadding() - ) { - EmojiPicker( - onEmojiSelected = viewModel::putAtCursorPosition, - bottomInset = pxAsDp( - max( - imeCurrentInset - navigationBarsInset, - 0 - ) - ), - onSearchFocus = { - emojiSearchFocused = it - } - ) - } - } - Box( + if (viewModel.activePane == ChannelScreenActivePane.None && !imeInTransition) { + Spacer( Modifier .imePadding() .navigationBarsPadding() + .background(MaterialTheme.colorScheme.surfaceContainer) ) + } else { + if (!notEnoughSpaceForPanes) { + Box( + Modifier + .heightIn(min = pxAsDp(fallbackKeyboardHeight)) + ) { + Box( + Modifier.then( + if (emojiSearchFocused) { + Modifier.requiredHeight( + pxAsDp( + max( + imeCurrentInset * 2, + fallbackKeyboardHeight + ) + ) + ) + } else { + Modifier.requiredHeight( + pxAsDp( + fallbackKeyboardHeight + ) + ) + } + ) + ) { + when (viewModel.activePane) { + ChannelScreenActivePane.EmojiPicker -> { + BackHandler(enabled = viewModel.activePane == ChannelScreenActivePane.EmojiPicker) { + viewModel.activePane = + ChannelScreenActivePane.None + } + + Column( + modifier = Modifier + .fillMaxWidth() + .background(MaterialTheme.colorScheme.surfaceContainer) + .padding(4.dp) + .navigationBarsPadding() + ) { + EmojiPicker( + onEmojiSelected = viewModel::putAtCursorPosition, + bottomInset = pxAsDp( + max( + imeCurrentInset - navigationBarsInset, + 0 + ) + ), + onSearchFocus = { + emojiSearchFocused = it + } + ) + } + } + + ChannelScreenActivePane.AttachmentPicker -> { + BackHandler(enabled = viewModel.activePane == ChannelScreenActivePane.AttachmentPicker) { + viewModel.activePane = + ChannelScreenActivePane.None + } + + MediaPickerGateway( + onOpenPhotoPicker = { + openPhotoPickerCallback() + viewModel.activePane = + ChannelScreenActivePane.None + }, + onOpenDocumentPicker = { + openDocumentPickerCallback() + viewModel.activePane = + ChannelScreenActivePane.None + }, + onOpenCamera = { + openCameraCallback() + viewModel.activePane = + ChannelScreenActivePane.None + }, + ) + } + + else -> { + // Do nothing + } + } + } + Box(Modifier.imePadding()) + } + } else { + if (viewModel.activePane == ChannelScreenActivePane.EmojiPicker) { + BackHandler(enabled = viewModel.activePane == ChannelScreenActivePane.EmojiPicker) { + viewModel.activePane = + ChannelScreenActivePane.None + } + + Column( + modifier = Modifier + .fillMaxWidth() + .height(600.dp) + .background(MaterialTheme.colorScheme.surfaceContainer) + .padding(4.dp) + .navigationBarsPadding() + ) { + EmojiPicker( + onEmojiSelected = viewModel::putAtCursorPosition, + bottomInset = pxAsDp( + max( + imeCurrentInset - navigationBarsInset, + 0 + ) + ), + onSearchFocus = { + emojiSearchFocused = it + } + ) + } + } + Box( + Modifier + .imePadding() + .navigationBarsPadding() + ) + } } } } diff --git a/app/src/main/java/chat/revolt/screens/settings/SettingsScreen.kt b/app/src/main/java/chat/revolt/screens/settings/SettingsScreen.kt index 01af3010..877eefa5 100644 --- a/app/src/main/java/chat/revolt/screens/settings/SettingsScreen.kt +++ b/app/src/main/java/chat/revolt/screens/settings/SettingsScreen.kt @@ -80,16 +80,6 @@ fun SettingsScreen( overflow = TextOverflow.Ellipsis, ) }, - navigationIcon = { - IconButton(onClick = { - navController.popBackStack() - }) { - Icon( - painter = painterResource(R.drawable.icn_arrow_back_24dp), - contentDescription = stringResource(id = R.string.back) - ) - } - }, ) }, ) { pv ->