diff --git a/app/src/main/java/chat/revolt/activities/MainActivity.kt b/app/src/main/java/chat/revolt/activities/MainActivity.kt index 1c35551d..c22be35d 100644 --- a/app/src/main/java/chat/revolt/activities/MainActivity.kt +++ b/app/src/main/java/chat/revolt/activities/MainActivity.kt @@ -23,6 +23,7 @@ import androidx.compose.animation.core.FiniteAnimationSpec import androidx.compose.animation.core.tween import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut +import androidx.compose.animation.scaleIn import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.material3.AlertDialog import androidx.compose.material3.MaterialTheme @@ -36,6 +37,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.TransformOrigin import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.Dp @@ -66,6 +68,7 @@ import chat.revolt.screens.DefaultDestinationScreen import chat.revolt.screens.about.AboutScreen import chat.revolt.screens.about.AttributionScreen import chat.revolt.screens.chat.ChatRouterScreen +import chat.revolt.screens.chat.views.channel.ChannelScreen import chat.revolt.screens.create.CreateGroupScreen import chat.revolt.screens.labs.LabsRootScreen import chat.revolt.screens.login.LoginGreetingScreen @@ -571,11 +574,56 @@ fun AppEntrypoint( easing = CubicBezierEasing(0.05f, 0.7f, 0.1f, 1f) ), initialOffset = { it / 3 } - ) + fadeIn(animationSpec = RevoltTweenFloat) + ) + fadeIn(animationSpec = RevoltTweenFloat) + scaleIn( + animationSpec = tween( + 400, + // cf. https://m3.material.io/styles/motion/easing-and-duration/tokens-specs#cbea5c6e-7b0d-47a0-98c3-767080a38d95 + easing = CubicBezierEasing(0.05f, 0.7f, 0.1f, 1f) + ), + initialScale = 0.8f, + transformOrigin = TransformOrigin.Center + ) } ) { MainScreen(navController) } + composable( + "main/conversation/{channelId}", + enterTransition = { + slideIntoContainer( + AnimatedContentTransitionScope.SlideDirection.Left, + animationSpec = tween( + 600, + // cf. https://m3.material.io/styles/motion/easing-and-duration/tokens-specs#cbea5c6e-7b0d-47a0-98c3-767080a38d95 + easing = CubicBezierEasing(0.05f, 0.7f, 0.1f, 1f) + ), + initialOffset = { it } + ) + fadeIn(animationSpec = RevoltTweenFloat) + }, + exitTransition = { + slideOutOfContainer( + AnimatedContentTransitionScope.SlideDirection.Right, + animationSpec = tween( + 600, + // cf. https://m3.material.io/styles/motion/easing-and-duration/tokens-specs#cbea5c6e-7b0d-47a0-98c3-767080a38d95 + easing = CubicBezierEasing(0.05f, 0.7f, 0.1f, 1f) + ), + targetOffset = { it } + ) + fadeOut(animationSpec = RevoltTweenFloat) + } + ) { backStackEntry -> + val channelId = backStackEntry.arguments?.getString("channelId") ?: "" + ChannelScreen( + channelId = channelId, + onToggleDrawer = {}, + useDrawer = false, + useBackButton = true, + backButtonAction = { + navController.popBackStack() + }, + useChatUI = true + ) + } composable("create/group") { CreateGroupScreen(navController) } 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 e9f3c9cd..5c5c2224 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 @@ -45,11 +45,13 @@ import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.requiredHeight import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.statusBars import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.text.InlineTextContent import androidx.compose.foundation.text.appendInlineContent import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material.icons.automirrored.filled.KeyboardArrowRight import androidx.compose.material.icons.filled.Close import androidx.compose.material.icons.filled.Edit @@ -186,6 +188,9 @@ fun ChannelScreen( channelId: String, onToggleDrawer: () -> Unit, useDrawer: Boolean, + useBackButton: Boolean = false, + backButtonAction: (() -> Unit)? = null, + useChatUI: Boolean = true, viewModel: ChannelScreenViewModel = hiltViewModel() ) { // @@ -495,12 +500,7 @@ fun ChannelScreen( // // Scaffold( - contentWindowInsets = WindowInsets( - left = 0, - right = 0, - top = 0, - bottom = 0 - ), + contentWindowInsets = WindowInsets.zero, topBar = { TopAppBar( modifier = Modifier.clickable { @@ -602,7 +602,7 @@ fun ChannelScreen( } } }, - windowInsets = WindowInsets.zero, + windowInsets = if (useChatUI) WindowInsets.statusBars else WindowInsets.zero, navigationIcon = { if (useDrawer) { IconButton(onClick = onToggleDrawer) { @@ -612,6 +612,14 @@ fun ChannelScreen( ) } } + if (useBackButton) { + IconButton(onClick = backButtonAction ?: {}) { + Icon( + imageVector = Icons.AutoMirrored.Default.ArrowBack, + contentDescription = stringResource(id = R.string.back) + ) + } + } } ) } diff --git a/app/src/main/java/chat/revolt/screens/main/ConversationsScreen.kt b/app/src/main/java/chat/revolt/screens/main/ConversationsScreen.kt new file mode 100644 index 00000000..9d778767 --- /dev/null +++ b/app/src/main/java/chat/revolt/screens/main/ConversationsScreen.kt @@ -0,0 +1,129 @@ +package chat.revolt.screens.main + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.exclude +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Badge +import androidx.compose.material3.CenterAlignedTopAppBar +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.Icon +import androidx.compose.material3.ListItem +import androidx.compose.material3.NavigationBarDefaults +import androidx.compose.material3.Scaffold +import androidx.compose.material3.ScaffoldDefaults +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import androidx.navigation.NavController +import chat.revolt.R +import chat.revolt.api.RevoltAPI +import chat.revolt.api.schemas.ChannelType +import chat.revolt.api.schemas.User +import chat.revolt.api.settings.LoadedSettings +import chat.revolt.components.generic.UserAvatar +import chat.revolt.internals.extensions.zero + +// Note - this is not a traditional screen per se, as it is a part of the main screen +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun ConversationsScreen(navController: NavController) { + val context = LocalContext.current + val dmAbleChannels = + RevoltAPI.channelCache.values + .filter { it.channelType == ChannelType.DirectMessage || it.channelType == ChannelType.Group } + .filter { if (it.channelType == ChannelType.DirectMessage) it.active == true else true } + .sortedBy { it.lastMessageID ?: it.id } + .reversed() + + Scaffold( + contentWindowInsets = ScaffoldDefaults.contentWindowInsets.exclude(NavigationBarDefaults.windowInsets), + topBar = { + CenterAlignedTopAppBar( + title = { Text(stringResource(R.string.main_tab_conversations)) }, + windowInsets = WindowInsets.zero + ) + }, + ) { pv -> + LazyColumn( + modifier = Modifier.padding(pv), + ) { + item(key = "saved_messages") { + val notesChannel = + RevoltAPI.channelCache.values.firstOrNull { it.channelType == ChannelType.SavedMessages } + val lastMessage = notesChannel?.lastMessageID?.let { RevoltAPI.messageCache[it] } + val hasAttachments = remember { + context.getString(R.string.reply_message_empty_has_attachments) + } + val preview = remember(lastMessage) { + (RevoltAPI.userCache[RevoltAPI.selfId]?.let { + User.resolveDefaultName( + it + ) + } + .orEmpty() + ": Remember to fdjhlfhdsfdsjfds").trim()// + (lastMessage?.content ?: hasAttachments)).trim() + } + + if (notesChannel != null) { + ListItem( + headlineContent = { + Text(stringResource(R.string.channel_notes)) + }, + supportingContent = { + if (preview.isNotBlank()) { + Text( + preview, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) + } + }, + leadingContent = { + Box(contentAlignment = Alignment.TopEnd) { + RevoltAPI.userCache[RevoltAPI.selfId]?.let { + UserAvatar( + username = it.username.toString(), + avatar = it.avatar, + userId = it.id.toString(), + shape = RoundedCornerShape(LoadedSettings.avatarRadius) + ) + } + Badge { + Icon( + painter = painterResource(R.drawable.ic_pin_24dp), + contentDescription = null, + modifier = Modifier.size(12.dp) + ) + } + } + }, + modifier = Modifier.clickable { + navController.navigate("main/conversation/${notesChannel.id}") + } + ) + HorizontalDivider() + } + } + items(1000) { + Text("Conversation $it", modifier = Modifier + .clickable { + navController.navigate("main/conversation/${it}") + } + .fillMaxWidth()) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/chat/revolt/screens/main/MainScreen.kt b/app/src/main/java/chat/revolt/screens/main/MainScreen.kt index bb4a654e..eb98221b 100644 --- a/app/src/main/java/chat/revolt/screens/main/MainScreen.kt +++ b/app/src/main/java/chat/revolt/screens/main/MainScreen.kt @@ -14,6 +14,7 @@ import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource import androidx.navigation.NavController import chat.revolt.R import chat.revolt.screens.chat.views.OverviewScreen @@ -47,7 +48,7 @@ fun MainScreen(navController: NavController) { ) }, label = { - Text("Communities") + Text(stringResource(R.string.main_tab_communities)) } ) NavigationBarItem( @@ -66,7 +67,7 @@ fun MainScreen(navController: NavController) { ) }, label = { - Text("Conversations") + Text(stringResource(R.string.main_tab_conversations)) } ) NavigationBarItem( @@ -85,7 +86,7 @@ fun MainScreen(navController: NavController) { ) }, label = { - Text("Overview") + Text(stringResource(R.string.main_tab_overview)) } ) } @@ -94,7 +95,12 @@ fun MainScreen(navController: NavController) { Box(Modifier.padding(pv)) { when (currentTab) { MainScreenTab.Communities -> {} - MainScreenTab.Conversations -> {} + MainScreenTab.Conversations -> { + ConversationsScreen( + navController + ) + } + MainScreenTab.Overview -> { OverviewScreen( navController, diff --git a/app/src/main/res/drawable/ic_pin_24dp.xml b/app/src/main/res/drawable/ic_pin_24dp.xml new file mode 100644 index 00000000..17f89a78 --- /dev/null +++ b/app/src/main/res/drawable/ic_pin_24dp.xml @@ -0,0 +1,9 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index bb02387f..53939ba1 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -169,6 +169,13 @@ Clear all incoming requests New Group + + Communities + + Conversations + + You + Create a new group Round up your crew in a group chat. You can add up to %1$d people. Group Name