feat(polar): begin convo screen

Signed-off-by: Infi <infi@infi.sh>
This commit is contained in:
Infi 2025-03-25 00:57:05 +01:00
parent 189df69580
commit 5cd247c2f3
6 changed files with 219 additions and 12 deletions

View File

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

View File

@ -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()
) {
// <editor-fold desc="State and effects">
@ -495,12 +500,7 @@ fun ChannelScreen(
// </editor-fold>
// <editor-fold desc="Begin UI composition">
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)
)
}
}
}
)
}

View File

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

View File

@ -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,

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="24dp"
android:width="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#ffffff"
android:pathData="M16,12V4H17V2H7V4H8V12L6,14V16H11.2V22H12.8V16H18V14L16,12Z" />
</vector>

View File

@ -169,6 +169,13 @@
<string name="friends_deny_all_incoming">Clear all incoming requests</string>
<string name="frends_new_group">New Group</string>
<!-- In an experimental iteration of the app's UI, there are three tabs. This is the tab which contains servers. -->
<string name="main_tab_communities">Communities</string>
<!-- In an experimental iteration of the app's UI, there are three tabs. This is the tab which contains direct messages and groups. -->
<string name="main_tab_conversations">Conversations</string>
<!-- In an experimental iteration of the app's UI, there are three tabs. This is the tab which contains the user's personal home page. -->
<string name="main_tab_overview">You</string>
<string name="create_group">Create a new group</string>
<string name="create_group_description">Round up your crew in a group chat. You can add up to %1$d people.</string>
<string name="create_group_name">Group Name</string>