add concord reactions and edits
This commit is contained in:
@@ -229,7 +229,8 @@ fun ChatMessage(
|
||||
|
||||
if (model.reactions.isNotEmpty()) {
|
||||
MessageReactions(
|
||||
reactions = model.reactions,
|
||||
emojis = model.reactions.map { it.emoji },
|
||||
total = model.reactions.sumOf { it.authors.size },
|
||||
modifier = Modifier.offset(y = 12.dp)
|
||||
)
|
||||
}
|
||||
@@ -247,13 +248,14 @@ fun ChatMessage(
|
||||
}
|
||||
}
|
||||
|
||||
/** The reaction chips under a bubble, shared by DMs and Channels. */
|
||||
@Composable
|
||||
private fun MessageReactions(
|
||||
reactions: List<ReactionGroup>,
|
||||
fun MessageReactions(
|
||||
emojis: List<String>,
|
||||
total: Int,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
val totalCount = reactions.sumOf { it.authors.size }
|
||||
val displayEmojis = reactions.take(3).map { it.emoji }
|
||||
val displayEmojis = emojis.take(3)
|
||||
|
||||
Row(
|
||||
modifier = modifier,
|
||||
@@ -274,7 +276,7 @@ private fun MessageReactions(
|
||||
}
|
||||
}
|
||||
}
|
||||
if (totalCount > 2) {
|
||||
if (total > 2) {
|
||||
Surface(
|
||||
modifier = Modifier.size(24.dp),
|
||||
color = MaterialTheme.colorScheme.surface,
|
||||
@@ -282,7 +284,7 @@ private fun MessageReactions(
|
||||
) {
|
||||
Box(contentAlignment = Alignment.Center) {
|
||||
Text(
|
||||
text = totalCount.toString(),
|
||||
text = total.toString(),
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
fontSize = 10.sp,
|
||||
)
|
||||
|
||||
@@ -650,7 +650,7 @@ private fun ReplyPreview(
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ReactionToolbar(
|
||||
fun ReactionToolbar(
|
||||
onReaction: (String) -> Unit
|
||||
) {
|
||||
val reactionEmojis = listOf("👍", "❤️", "😂", "😮", "😢", "😡", "🎉")
|
||||
|
||||
+87
-3
@@ -1,5 +1,7 @@
|
||||
package su.reya.coop.screens.communities
|
||||
|
||||
import androidx.compose.foundation.combinedClickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
@@ -28,6 +30,7 @@ import androidx.compose.material3.ScaffoldDefaults
|
||||
import androidx.compose.material3.SnackbarHost
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.material3.TopAppBar
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.runtime.Composable
|
||||
@@ -56,6 +59,8 @@ import su.reya.coop.dayLabel
|
||||
import su.reya.coop.sanitizeName
|
||||
import su.reya.coop.screens.chat.ChatInput
|
||||
import su.reya.coop.screens.chat.DateSeparator
|
||||
import su.reya.coop.screens.chat.MessageReactions
|
||||
import su.reya.coop.screens.chat.ReactionToolbar
|
||||
import su.reya.coop.shared.Avatar
|
||||
import su.reya.coop.short
|
||||
import su.reya.coop.timeLabel
|
||||
@@ -81,6 +86,12 @@ fun ChannelScreen(viewModel: ChannelScreenViewModel) {
|
||||
|
||||
var text by remember { mutableStateOf("") }
|
||||
|
||||
/** The message whose action row is open, if any. */
|
||||
var activeMessageId by remember { mutableStateOf<String?>(null) }
|
||||
|
||||
/** The message an Edit is being written for, if any. Its text lives in [text]. */
|
||||
var editing by remember { mutableStateOf<ConcordMessage?>(null) }
|
||||
|
||||
val grouped by remember {
|
||||
derivedStateOf {
|
||||
viewModel.messages
|
||||
@@ -193,6 +204,18 @@ fun ChannelScreen(viewModel: ChannelScreenViewModel) {
|
||||
ChannelMessageRow(
|
||||
message = message,
|
||||
isMine = message.author == currentUser?.publicKey?.toHex(),
|
||||
active = activeMessageId == message.idHex,
|
||||
onDismissActions = { activeMessageId = null },
|
||||
onLongPress = { activeMessageId = message.idHex },
|
||||
onReaction = { emoji ->
|
||||
viewModel.sendReaction(message, emoji)
|
||||
activeMessageId = null
|
||||
},
|
||||
onEdit = {
|
||||
editing = message
|
||||
text = message.content
|
||||
activeMessageId = null
|
||||
},
|
||||
)
|
||||
}
|
||||
item(key = "day:$day") { DateSeparator(day) }
|
||||
@@ -211,13 +234,33 @@ fun ChannelScreen(viewModel: ChannelScreenViewModel) {
|
||||
color = MaterialTheme.colorScheme.outline,
|
||||
)
|
||||
} else {
|
||||
if (editing != null) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp, vertical = 4.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Text(
|
||||
text = "Editing a message",
|
||||
modifier = Modifier.weight(1f),
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
)
|
||||
TextButton(onClick = { editing = null; text = "" }) { Text("Cancel") }
|
||||
}
|
||||
}
|
||||
|
||||
// No attachments and no dictation in v1, so the input shows neither: a button
|
||||
// that does nothing would be worse than its absence.
|
||||
ChatInput(
|
||||
value = text,
|
||||
onValueChange = { text = it },
|
||||
onSend = {
|
||||
viewModel.sendMessage(text)
|
||||
val target = editing
|
||||
if (target == null) viewModel.sendMessage(text)
|
||||
else viewModel.editMessage(target.idHex, text)
|
||||
editing = null
|
||||
text = ""
|
||||
},
|
||||
)
|
||||
@@ -229,7 +272,15 @@ fun ChannelScreen(viewModel: ChannelScreenViewModel) {
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ChannelMessageRow(message: ConcordMessage, isMine: Boolean) {
|
||||
private fun ChannelMessageRow(
|
||||
message: ConcordMessage,
|
||||
isMine: Boolean,
|
||||
active: Boolean,
|
||||
onDismissActions: () -> Unit,
|
||||
onLongPress: () -> Unit,
|
||||
onReaction: (String) -> Unit,
|
||||
onEdit: () -> Unit,
|
||||
) {
|
||||
val (pubkey, profile) = rememberAuthor(message.author)
|
||||
val name = profile?.name?.sanitizeName()?.takeIf { it.isNotBlank() }
|
||||
?: pubkey?.short()
|
||||
@@ -242,7 +293,16 @@ private fun ChannelMessageRow(message: ConcordMessage, isMine: Boolean) {
|
||||
) {
|
||||
Avatar(picture = profile?.picture, description = name, size = 36.dp)
|
||||
Spacer(modifier = Modifier.size(10.dp))
|
||||
Column(modifier = Modifier.weight(1f)) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.combinedClickable(
|
||||
interactionSource = remember { MutableInteractionSource() },
|
||||
indication = null,
|
||||
onClick = onDismissActions,
|
||||
onLongClick = onLongPress,
|
||||
)
|
||||
) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Text(
|
||||
text = if (isMine) "$name (you)" else name,
|
||||
@@ -258,12 +318,36 @@ private fun ChannelMessageRow(message: ConcordMessage, isMine: Boolean) {
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
color = MaterialTheme.colorScheme.outline,
|
||||
)
|
||||
if (message.edited) {
|
||||
Spacer(modifier = Modifier.size(4.dp))
|
||||
Text(
|
||||
text = "(edited)",
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
color = MaterialTheme.colorScheme.outline,
|
||||
)
|
||||
}
|
||||
}
|
||||
Text(
|
||||
text = message.content,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
)
|
||||
if (message.reactions.isNotEmpty()) {
|
||||
MessageReactions(
|
||||
emojis = message.reactions.map { it.emoji },
|
||||
total = message.reactions.sumOf { it.authors.size },
|
||||
modifier = Modifier.padding(top = 4.dp),
|
||||
)
|
||||
}
|
||||
if (active) {
|
||||
Column(modifier = Modifier.padding(top = 8.dp)) {
|
||||
ReactionToolbar(onReaction = onReaction)
|
||||
// Only the author may edit, so only they are offered it.
|
||||
if (isMine) {
|
||||
TextButton(onClick = onEdit) { Text("Edit") }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user