chore: update m3 and migrate bottom sheets to m3

Signed-off-by: Infi <wingit@geist.ga>
This commit is contained in:
Infi 2023-05-29 19:53:57 +02:00
parent 7fcc93219e
commit f845c72b23
13 changed files with 458 additions and 342 deletions

View File

@ -12,7 +12,6 @@ import androidx.compose.runtime.*
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.IntSize
import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentActivity
import androidx.navigation.compose.dialog import androidx.navigation.compose.dialog
import chat.revolt.BuildConfig import chat.revolt.BuildConfig
@ -57,7 +56,6 @@ class MainActivity : FragmentActivity() {
} }
val RevoltTweenInt: FiniteAnimationSpec<IntOffset> = tween(400, easing = EaseInOutExpo) val RevoltTweenInt: FiniteAnimationSpec<IntOffset> = tween(400, easing = EaseInOutExpo)
val RevoltTweenIntSize: FiniteAnimationSpec<IntSize> = tween(400, easing = EaseInOutExpo)
val RevoltTweenFloat: FiniteAnimationSpec<Float> = tween(400, easing = EaseInOutExpo) val RevoltTweenFloat: FiniteAnimationSpec<Float> = tween(400, easing = EaseInOutExpo)
val RevoltTweenDp: FiniteAnimationSpec<Dp> = tween(400, easing = EaseInOutExpo) val RevoltTweenDp: FiniteAnimationSpec<Dp> = tween(400, easing = EaseInOutExpo)
@ -78,25 +76,25 @@ fun AppEntrypoint() {
startDestination = "splash", startDestination = "splash",
enterTransition = { enterTransition = {
slideIntoContainer( slideIntoContainer(
AnimatedContentScope.SlideDirection.Left, AnimatedContentTransitionScope.SlideDirection.Left,
animationSpec = RevoltTweenInt animationSpec = RevoltTweenInt
) )
}, },
exitTransition = { exitTransition = {
slideOutOfContainer( slideOutOfContainer(
AnimatedContentScope.SlideDirection.Left, AnimatedContentTransitionScope.SlideDirection.Left,
animationSpec = RevoltTweenInt animationSpec = RevoltTweenInt
) + fadeOut(animationSpec = RevoltTweenFloat) ) + fadeOut(animationSpec = RevoltTweenFloat)
}, },
popEnterTransition = { popEnterTransition = {
slideIntoContainer( slideIntoContainer(
AnimatedContentScope.SlideDirection.Right, AnimatedContentTransitionScope.SlideDirection.Right,
animationSpec = RevoltTweenInt animationSpec = RevoltTweenInt
) )
}, },
popExitTransition = { popExitTransition = {
slideOutOfContainer( slideOutOfContainer(
AnimatedContentScope.SlideDirection.Right, AnimatedContentTransitionScope.SlideDirection.Right,
animationSpec = RevoltTweenInt animationSpec = RevoltTweenInt
) )
} }

View File

@ -127,24 +127,20 @@ object RealtimeSocket {
) )
Log.d("RealtimeSocket", "Adding users to cache.") Log.d("RealtimeSocket", "Adding users to cache.")
readyFrame.users.forEach { user -> val userMap = readyFrame.users.associateBy { it.id!! }
RevoltAPI.userCache[user.id!!] = user RevoltAPI.userCache.putAll(userMap)
}
Log.d("RealtimeSocket", "Adding servers to cache.") Log.d("RealtimeSocket", "Adding servers to cache.")
readyFrame.servers.forEach { server -> val serverMap = readyFrame.servers.associateBy { it.id!! }
RevoltAPI.serverCache[server.id!!] = server RevoltAPI.serverCache.putAll(serverMap)
}
Log.d("RealtimeSocket", "Adding channels to cache.") Log.d("RealtimeSocket", "Adding channels to cache.")
readyFrame.channels.forEach { channel -> val channelMap = readyFrame.channels.associateBy { it.id!! }
RevoltAPI.channelCache[channel.id!!] = channel RevoltAPI.channelCache.putAll(channelMap)
}
Log.d("RealtimeSocket", "Adding emojis to cache.") Log.d("RealtimeSocket", "Adding emojis to cache.")
readyFrame.emojis.forEach { emoji -> val emojiMap = readyFrame.emojis.associateBy { it.id!! }
RevoltAPI.emojiCache[emoji.id!!] = emoji RevoltAPI.emojiCache.putAll(emojiMap)
}
} }
"Message" -> { "Message" -> {

View File

@ -73,7 +73,7 @@ fun MessageField(
keyboardOptions = KeyboardOptions.Default, keyboardOptions = KeyboardOptions.Default,
keyboardActions = KeyboardActions.Default, keyboardActions = KeyboardActions.Default,
decorationBox = @Composable { innerTextField -> decorationBox = @Composable { innerTextField ->
TextFieldDefaults.TextFieldDecorationBox( TextFieldDefaults.DecorationBox(
value = messageContent, value = messageContent,
innerTextField = innerTextField, innerTextField = innerTextField,
enabled = !disabled, enabled = !disabled,
@ -90,13 +90,17 @@ fun MessageField(
overflow = TextOverflow.Ellipsis, overflow = TextOverflow.Ellipsis,
) )
}, },
colors = TextFieldDefaults.textFieldColors( colors = TextFieldDefaults.colors(
focusedIndicatorColor = Color.Transparent, focusedIndicatorColor = Color.Transparent,
unfocusedIndicatorColor = Color.Transparent, unfocusedIndicatorColor = Color.Transparent,
disabledIndicatorColor = Color.Transparent, disabledIndicatorColor = Color.Transparent,
errorIndicatorColor = Color.Transparent, errorIndicatorColor = Color.Transparent,
placeholderColor = Color.Gray, unfocusedPlaceholderColor = Color.Gray,
containerColor = MaterialTheme.colorScheme.surfaceColorAtElevation(1.dp) focusedPlaceholderColor = Color.Gray,
unfocusedContainerColor = MaterialTheme.colorScheme.surfaceColorAtElevation(
1.dp
),
focusedContainerColor = Color.Transparent,
), ),
contentPadding = PaddingValues(16.dp), contentPadding = PaddingValues(16.dp),
leadingIcon = { leadingIcon = {

View File

@ -11,10 +11,16 @@ import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.DrawerState import androidx.compose.material3.DrawerState
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.Surface import androidx.compose.material3.Surface
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.rememberModalBottomSheetState
import androidx.compose.runtime.Composable 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.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
@ -26,6 +32,7 @@ import chat.revolt.R
import chat.revolt.api.RevoltAPI import chat.revolt.api.RevoltAPI
import chat.revolt.api.schemas.ChannelType import chat.revolt.api.schemas.ChannelType
import chat.revolt.components.screens.chat.drawer.server.DrawerChannel import chat.revolt.components.screens.chat.drawer.server.DrawerChannel
import chat.revolt.sheets.ChannelContextSheet
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@ -35,10 +42,31 @@ fun RowScope.ChannelList(
drawerState: DrawerState, drawerState: DrawerState,
currentChannel: String?, currentChannel: String?,
onChannelClick: (String) -> Unit, onChannelClick: (String) -> Unit,
onChannelLongClick: (String) -> Unit,
) { ) {
val coroutineScope = rememberCoroutineScope() 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( Surface(
tonalElevation = 1.dp, tonalElevation = 1.dp,
modifier = Modifier modifier = Modifier
@ -74,7 +102,8 @@ fun RowScope.ChannelList(
coroutineScope.launch { drawerState.close() } coroutineScope.launch { drawerState.close() }
}, },
onLongClick = { onLongClick = {
onChannelLongClick(channel.id ?: return@DrawerChannel) channelContextSheetTarget = channel.id ?: return@DrawerChannel
channelContextSheetShown = true
} }
) )
} }
@ -132,7 +161,8 @@ fun RowScope.ChannelList(
coroutineScope.launch { drawerState.close() } coroutineScope.launch { drawerState.close() }
}, },
onLongClick = { onLongClick = {
onChannelLongClick(ch.id ?: return@DrawerChannel) channelContextSheetTarget = ch.id ?: return@DrawerChannel
channelContextSheetShown = true
} }
) )
} }

View File

@ -12,7 +12,6 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add 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.DrawerValue
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TextButton import androidx.compose.material3.TextButton
import androidx.compose.material3.rememberDrawerState import androidx.compose.material3.rememberDrawerState
import androidx.compose.material3.rememberModalBottomSheetState
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue 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.components.screens.chat.drawer.server.ServerDrawerSeparator
import chat.revolt.persistence.KVStorage import chat.revolt.persistence.KVStorage
import chat.revolt.screens.chat.dialogs.safety.ReportMessageDialog 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.HomeScreen
import chat.revolt.screens.chat.views.NoCurrentChannelScreen import chat.revolt.screens.chat.views.NoCurrentChannelScreen
import chat.revolt.screens.chat.views.channel.ChannelScreen 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.RenderMode
import com.airbnb.lottie.compose.LottieAnimation import com.airbnb.lottie.compose.LottieAnimation
import com.airbnb.lottie.compose.LottieCompositionSpec import com.airbnb.lottie.compose.LottieCompositionSpec
import com.airbnb.lottie.compose.animateLottieCompositionAsState import com.airbnb.lottie.compose.animateLottieCompositionAsState
import com.airbnb.lottie.compose.rememberLottieComposition 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 dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -163,7 +156,6 @@ class ChatRouterViewModel @Inject constructor(
} }
@OptIn( @OptIn(
ExperimentalMaterialNavigationApi::class,
ExperimentalComposeUiApi::class, ExperimentalComposeUiApi::class,
ExperimentalMaterial3Api::class ExperimentalMaterial3Api::class
) )
@ -173,8 +165,7 @@ fun ChatRouterScreen(topNav: NavController, viewModel: ChatRouterViewModel = hil
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
val keyboardController = LocalSoftwareKeyboardController.current val keyboardController = LocalSoftwareKeyboardController.current
val bottomSheetNavigator = rememberBottomSheetNavigator() val navController = rememberNavController()
val navController = rememberNavController(bottomSheetNavigator)
val showSidebarSpark = remember { mutableStateOf(false) } val showSidebarSpark = remember { mutableStateOf(false) }
val sidebarSparkComposition by rememberLottieComposition(LottieCompositionSpec.RawRes(R.raw.open_settings_tutorial)) val sidebarSparkComposition by rememberLottieComposition(LottieCompositionSpec.RawRes(R.raw.open_settings_tutorial))
@ -182,6 +173,9 @@ fun ChatRouterScreen(topNav: NavController, viewModel: ChatRouterViewModel = hil
composition = sidebarSparkComposition, composition = sidebarSparkComposition,
) )
var showStatusSheet by remember { mutableStateOf(false) }
var showAddServerSheet by remember { mutableStateOf(false) }
BackHandler(enabled = drawerState.isClosed) { BackHandler(enabled = drawerState.isClosed) {
scope.launch { scope.launch {
drawerState.open() drawerState.open()
@ -230,215 +224,210 @@ fun ChatRouterScreen(topNav: NavController, viewModel: ChatRouterViewModel = hil
} }
} }
ModalBottomSheetLayout( if (showSidebarSpark.value) {
sheetShape = RoundedCornerShape(topStart = 16.dp, topEnd = 16.dp), AlertDialog(
sheetBackgroundColor = MaterialTheme.colorScheme.surface, onDismissRequest = {},
bottomSheetNavigator = bottomSheetNavigator, title = {
) { Text(stringResource(id = R.string.spark_sidebar_settings_tutorial))
if (showSidebarSpark.value) { },
AlertDialog( text = {
onDismissRequest = {}, Column {
title = { LottieAnimation(
Text(stringResource(id = R.string.spark_sidebar_settings_tutorial)) composition = sidebarSparkComposition,
}, progress = { sidebarSparkProgress },
text = { modifier = Modifier
Column { .fillMaxWidth()
LottieAnimation( .aspectRatio(1f),
composition = sidebarSparkComposition, renderMode = RenderMode.HARDWARE
progress = { sidebarSparkProgress }, )
modifier = Modifier Text(stringResource(id = R.string.spark_sidebar_settings_tutorial_description_1))
.fillMaxWidth() Text(stringResource(id = R.string.spark_sidebar_settings_tutorial_description_2))
.aspectRatio(1f), }
renderMode = RenderMode.HARDWARE },
) confirmButton = {
Text(stringResource(id = R.string.spark_sidebar_settings_tutorial_description_1)) TextButton(onClick = {
Text(stringResource(id = R.string.spark_sidebar_settings_tutorial_description_2)) 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 = { onGoSettings = {
TextButton(onClick = { topNav.navigate("settings")
scope.launch {
viewModel.setSettingsHintDisplayed()
}
showSidebarSpark.value = false
}) {
Text(stringResource(id = R.string.spark_sidebar_settings_tutorial_acknowledge))
}
} }
) )
} }
}
Column { if (showAddServerSheet) {
AnimatedVisibility(visible = RealtimeSocket.disconnectionState != DisconnectionState.Connected) { val addServerSheetState = rememberModalBottomSheetState()
DisconnectedNotice(
state = RealtimeSocket.disconnectionState,
onReconnect = {
RealtimeSocket.updateDisconnectionState(DisconnectionState.Reconnecting)
scope.launch { RevoltAPI.connectWS() }
})
}
DismissibleNavigationDrawer( ModalBottomSheet(
drawerState = drawerState, sheetState = addServerSheetState,
drawerContent = { onDismissRequest = {
DismissibleDrawerSheet( showAddServerSheet = false
drawerContainerColor = Color.Transparent, },
) { ) {
Column(Modifier.fillMaxWidth()) { AddServerSheet()
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
)
}
}
}
}
})
} }
} }
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
)
}
}
}
}
})
}
} }

View File

@ -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")
}
}
}
}

View File

@ -43,6 +43,8 @@ import chat.revolt.internals.markdown.MarkdownState
import chat.revolt.internals.markdown.UserMentionRule import chat.revolt.internals.markdown.UserMentionRule
import chat.revolt.internals.markdown.createCodeRule import chat.revolt.internals.markdown.createCodeRule
import chat.revolt.internals.markdown.createInlineCodeRule 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.SimpleMarkdownRules
import com.discord.simpleast.core.simple.SimpleRenderer import com.discord.simpleast.core.simple.SimpleRenderer
import io.ktor.http.* import io.ktor.http.*
@ -50,6 +52,7 @@ import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.io.File import java.io.File
@OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun ChannelScreen( fun ChannelScreen(
navController: NavController, navController: NavController,
@ -65,6 +68,11 @@ fun ChannelScreen(
val codeBlockColor = MaterialTheme.colorScheme.surfaceColorAtElevation(2.dp) 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( val pickFileLauncher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.OpenMultipleDocuments() contract = ActivityResultContracts.OpenMultipleDocuments()
) { uriList -> ) { 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) { if (channel?.channelType == null) {
CircularProgressIndicator() CircularProgressIndicator()
return return
@ -117,7 +162,7 @@ fun ChannelScreen(
ChannelHeader( ChannelHeader(
channel = channel, channel = channel,
onChannelClick = { onChannelClick = {
navController.navigate("channel/${channel.id}/info") channelInfoSheetShown = true
}, },
onToggleDrawer = onToggleDrawer onToggleDrawer = onToggleDrawer
) )
@ -202,7 +247,8 @@ fun ChannelScreen(
) )
}, },
onMessageContextMenu = { onMessageContextMenu = {
navController.navigate("message/${message.id}/menu") messageContextSheetShown = true
messageContextSheetTarget = message.id ?: ""
}, },
canReply = true, canReply = true,
onReply = { onReply = {

View File

@ -1,4 +1,4 @@
package chat.revolt.screens.chat.sheets package chat.revolt.sheets
import android.content.Intent import android.content.Intent
import android.util.Log import android.util.Log

View File

@ -1,17 +1,23 @@
package chat.revolt.screens.chat.sheets package chat.revolt.sheets
import android.widget.Toast import android.widget.Toast
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column 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.Icon
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.rememberCoroutineScope 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.LocalClipboardManager
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.AnnotatedString
import androidx.navigation.NavController import androidx.compose.ui.unit.dp
import chat.revolt.R import chat.revolt.R
import chat.revolt.api.RevoltAPI import chat.revolt.api.RevoltAPI
import chat.revolt.components.generic.SheetClickable import chat.revolt.components.generic.SheetClickable
@ -19,12 +25,18 @@ import kotlinx.coroutines.launch
@Composable @Composable
fun ChannelContextSheet( fun ChannelContextSheet(
navController: NavController,
channelId: String, channelId: String,
onHideSheet: suspend () -> Unit,
) { ) {
val channel = RevoltAPI.channelCache[channelId] val channel = RevoltAPI.channelCache[channelId]
if (channel == null) { if (channel == null) {
navController.popBackStack() Box(
modifier = Modifier
.fillMaxWidth()
.height(200.dp)
) {
CircularProgressIndicator(modifier = Modifier.align(Alignment.Center))
}
return return
} }
@ -57,7 +69,10 @@ fun ChannelContextSheet(
context.getString(R.string.channel_context_sheet_actions_copy_id_copied), context.getString(R.string.channel_context_sheet_actions_copy_id_copied),
Toast.LENGTH_SHORT Toast.LENGTH_SHORT
).show() ).show()
navController.popBackStack()
coroutineScope.launch {
onHideSheet()
}
} }
SheetClickable( SheetClickable(
@ -79,8 +94,8 @@ fun ChannelContextSheet(
channel.lastMessageID?.let { channel.lastMessageID?.let {
RevoltAPI.unreads.markAsRead(channelId, it, sync = true) RevoltAPI.unreads.markAsRead(channelId, it, sync = true)
} }
onHideSheet()
} }
navController.popBackStack()
} }
} }
} }

View File

@ -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.Column
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState 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.Add
import androidx.compose.material.icons.filled.List import androidx.compose.material.icons.filled.List
import androidx.compose.material.icons.filled.Notifications import androidx.compose.material.icons.filled.Notifications
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.navigation.NavController
import chat.revolt.R import chat.revolt.R
import chat.revolt.api.RevoltAPI import chat.revolt.api.RevoltAPI
import chat.revolt.components.generic.SheetClickable import chat.revolt.components.generic.SheetClickable
@Composable @Composable
fun ChannelInfoSheet( fun ChannelInfoSheet(
navController: NavController,
channelId: String, channelId: String,
) { ) {
val channel = RevoltAPI.channelCache[channelId] val channel = RevoltAPI.channelCache[channelId]
if (channel == null) { if (channel == null) {
navController.popBackStack() Box(
modifier = Modifier
.fillMaxWidth()
.height(200.dp)
) {
CircularProgressIndicator(modifier = Modifier.align(Alignment.Center))
}
return return
} }

View File

@ -1,10 +1,11 @@
package chat.revolt.screens.chat.sheets package chat.revolt.sheets
import android.widget.Toast import android.widget.Toast
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState 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.Icons
import androidx.compose.material.icons.filled.Delete import androidx.compose.material.icons.filled.Delete
import androidx.compose.material.icons.filled.Edit import androidx.compose.material.icons.filled.Edit
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.surfaceColorAtElevation import androidx.compose.material3.surfaceColorAtElevation
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.platform.LocalClipboardManager 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.res.stringResource
import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.navigation.NavController
import chat.revolt.R import chat.revolt.R
import chat.revolt.api.REVOLT_APP import chat.revolt.api.REVOLT_APP
import chat.revolt.api.RevoltAPI import chat.revolt.api.RevoltAPI
@ -37,12 +39,19 @@ import kotlinx.coroutines.launch
@Composable @Composable
fun MessageContextSheet( fun MessageContextSheet(
navController: NavController,
messageId: String, messageId: String,
onHideSheet: suspend () -> Unit,
onReportMessage: () -> Unit,
) { ) {
val message = RevoltAPI.messageCache[messageId] val message = RevoltAPI.messageCache[messageId]
if (message == null) { if (message == null) {
navController.popBackStack() Box(
modifier = Modifier
.fillMaxWidth()
.height(200.dp)
) {
CircularProgressIndicator(modifier = Modifier.align(Alignment.Center))
}
return return
} }
@ -89,8 +98,8 @@ fun MessageContextSheet(
) { ) {
coroutineScope.launch { coroutineScope.launch {
UiCallbacks.replyToMessage(messageId) UiCallbacks.replyToMessage(messageId)
onHideSheet()
} }
navController.popBackStack()
} }
SheetClickable( SheetClickable(
@ -113,7 +122,10 @@ fun MessageContextSheet(
context.getString(R.string.comingsoon_toast), context.getString(R.string.comingsoon_toast),
Toast.LENGTH_SHORT Toast.LENGTH_SHORT
).show() ).show()
navController.popBackStack()
coroutineScope.launch {
onHideSheet()
}
} }
SheetClickable( SheetClickable(
@ -132,22 +144,27 @@ fun MessageContextSheet(
}, },
) { ) {
if (message.content.isNullOrEmpty()) { if (message.content.isNullOrEmpty()) {
Toast.makeText( coroutineScope.launch {
context, onHideSheet()
context.getString(R.string.message_context_sheet_actions_copy_failed_empty), Toast.makeText(
Toast.LENGTH_SHORT context,
).show() context.getString(R.string.message_context_sheet_actions_copy_failed_empty),
navController.popBackStack() Toast.LENGTH_SHORT
).show()
}
return@SheetClickable return@SheetClickable
} }
clipboardManager.setText(AnnotatedString(message.content))
Toast.makeText( Toast.makeText(
context, context,
context.getString(R.string.copied), context.getString(R.string.copied),
Toast.LENGTH_SHORT Toast.LENGTH_SHORT
).show() ).show()
navController.popBackStack()
coroutineScope.launch {
clipboardManager.setText(AnnotatedString(message.content))
onHideSheet()
}
} }
SheetClickable( SheetClickable(
@ -171,7 +188,11 @@ fun MessageContextSheet(
context.getString(R.string.message_context_sheet_actions_copy_failed_empty), context.getString(R.string.message_context_sheet_actions_copy_failed_empty),
Toast.LENGTH_SHORT Toast.LENGTH_SHORT
).show() ).show()
navController.popBackStack()
coroutineScope.launch {
onHideSheet()
}
return@SheetClickable return@SheetClickable
} }
@ -187,7 +208,10 @@ fun MessageContextSheet(
context.getString(R.string.message_context_sheet_actions_copy_link_copied), context.getString(R.string.message_context_sheet_actions_copy_link_copied),
Toast.LENGTH_SHORT Toast.LENGTH_SHORT
).show() ).show()
navController.popBackStack()
coroutineScope.launch {
onHideSheet()
}
} }
SheetClickable( SheetClickable(
@ -213,7 +237,10 @@ fun MessageContextSheet(
context.getString(R.string.message_context_sheet_actions_copy_id_copied), context.getString(R.string.message_context_sheet_actions_copy_id_copied),
Toast.LENGTH_SHORT Toast.LENGTH_SHORT
).show() ).show()
navController.popBackStack()
coroutineScope.launch {
onHideSheet()
}
} }
@ -237,7 +264,10 @@ fun MessageContextSheet(
context.getString(R.string.comingsoon_toast), context.getString(R.string.comingsoon_toast),
Toast.LENGTH_SHORT Toast.LENGTH_SHORT
).show() ).show()
navController.popBackStack()
coroutineScope.launch {
onHideSheet()
}
} }
SheetClickable( SheetClickable(
@ -260,7 +290,10 @@ fun MessageContextSheet(
context.getString(R.string.comingsoon_toast), context.getString(R.string.comingsoon_toast),
Toast.LENGTH_SHORT Toast.LENGTH_SHORT
).show() ).show()
navController.popBackStack()
coroutineScope.launch {
onHideSheet()
}
} }
SheetClickable( SheetClickable(
@ -283,7 +316,10 @@ fun MessageContextSheet(
context.getString(R.string.comingsoon_toast), context.getString(R.string.comingsoon_toast),
Toast.LENGTH_SHORT Toast.LENGTH_SHORT
).show() ).show()
navController.popBackStack()
coroutineScope.launch {
onHideSheet()
}
} }
SheetClickable( SheetClickable(
@ -301,7 +337,9 @@ fun MessageContextSheet(
) )
}, },
) { ) {
navController.navigate("report/message/${message.id}") coroutineScope.launch {
onReportMessage()
}
} }
} }
} }

View File

@ -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()
}
}
}

View File

@ -1,8 +1,8 @@
buildscript { buildscript {
ext { ext {
compose_version = '1.4.0' compose_version = '1.4.0'
compose_bom_version = '2023.01.00' compose_bom_version = '2023.05.01'
accompanist_version = '0.28.0' accompanist_version = '0.31.2-alpha'
okhttp_version = '4.10.0' okhttp_version = '4.10.0'
nav_version = '2.5.3' nav_version = '2.5.3'
hilt_version = '2.44' hilt_version = '2.44'