Reactions ux updates (#1020)
* Fix ordering of reaction count/key label on outgoing messages and fix reaction button height - Fix ordering of reaction count/key label on outgoing messages - Fix reaction button height * Fix emojis circles on action list * Fix shape of reaction summary button when pressed * Update screenshots --------- Co-authored-by: ElementBot <benoitm+elementbot@element.io>
This commit is contained in:
@@ -31,6 +31,7 @@ import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.ExperimentalMaterialApi
|
||||
import androidx.compose.material.ListItem
|
||||
@@ -342,7 +343,7 @@ internal fun EmojiReactionsRow(
|
||||
) {
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
modifier = modifier.padding(horizontal = 28.dp, vertical = 16.dp)
|
||||
modifier = modifier.padding(horizontal = 24.dp, vertical = 16.dp)
|
||||
) {
|
||||
// TODO use most recently used emojis here when available from the Rust SDK
|
||||
val defaultEmojis = sequenceOf(
|
||||
@@ -352,21 +353,25 @@ internal fun EmojiReactionsRow(
|
||||
val isHighlighted = highlightedEmojis.contains(emoji)
|
||||
EmojiButton(emoji, isHighlighted, onEmojiReactionClicked)
|
||||
}
|
||||
|
||||
Icon(
|
||||
imageVector = Icons.Outlined.AddReaction,
|
||||
contentDescription = "Emojis",
|
||||
tint = MaterialTheme.colorScheme.secondary,
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(24.dp)
|
||||
.align(Alignment.CenterVertically)
|
||||
.clickable(
|
||||
enabled = true,
|
||||
onClick = onCustomReactionClicked,
|
||||
indication = rememberRipple(bounded = false, radius = emojiRippleRadius),
|
||||
interactionSource = remember { MutableInteractionSource() }
|
||||
)
|
||||
)
|
||||
.size(48.dp),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Outlined.AddReaction,
|
||||
contentDescription = "Emojis",
|
||||
tint = MaterialTheme.colorScheme.secondary,
|
||||
modifier = Modifier
|
||||
.size(24.dp)
|
||||
.clickable(
|
||||
enabled = true,
|
||||
onClick = onCustomReactionClicked,
|
||||
indication = rememberRipple(bounded = false, radius = emojiRippleRadius),
|
||||
interactionSource = remember { MutableInteractionSource() }
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -385,12 +390,13 @@ private fun EmojiButton(
|
||||
Box(
|
||||
modifier = modifier
|
||||
.size(48.dp)
|
||||
.background(backgroundColor, RoundedCornerShape(24.dp)),
|
||||
.background(backgroundColor, CircleShape),
|
||||
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Text(
|
||||
emoji,
|
||||
fontSize = 28.dp.toSp(),
|
||||
fontSize = 24.dp.toSp(),
|
||||
color = Color.White,
|
||||
modifier = Modifier
|
||||
.clickable(
|
||||
|
||||
@@ -113,6 +113,7 @@ sealed class MessagesReactionsButtonContent {
|
||||
}
|
||||
|
||||
private val reactionEmojiLineHeight = 20.sp
|
||||
private val addEmojiSize = 16.dp
|
||||
|
||||
@Composable
|
||||
private fun TextContent(
|
||||
@@ -135,7 +136,8 @@ private fun IconContent(
|
||||
contentDescription = stringResource(id = R.string.screen_room_timeline_add_reaction),
|
||||
tint = ElementTheme.materialColors.secondary,
|
||||
modifier = modifier
|
||||
.size(reactionEmojiLineHeight.toDp())
|
||||
.size(addEmojiSize)
|
||||
|
||||
)
|
||||
|
||||
@Composable
|
||||
@@ -173,15 +175,20 @@ internal fun MessagesReactionButtonPreview(@PreviewParameter(AggregatedReactionP
|
||||
)
|
||||
}
|
||||
|
||||
@DayNightPreviews
|
||||
@Composable
|
||||
internal fun MessagesAddReactionButtonPreview() = ElementPreview {
|
||||
MessagesReactionButton(
|
||||
content = MessagesReactionsButtonContent.Icon(Icons.Outlined.AddReaction),
|
||||
onClick = {},
|
||||
onLongClick = {}
|
||||
)
|
||||
}
|
||||
|
||||
@DayNightPreviews
|
||||
@Composable
|
||||
internal fun MessagesReactionExtraButtonsPreview() = ElementPreview {
|
||||
Row {
|
||||
MessagesReactionButton(
|
||||
content = MessagesReactionsButtonContent.Icon(Icons.Outlined.AddReaction),
|
||||
onClick = {},
|
||||
onLongClick = {}
|
||||
)
|
||||
MessagesReactionButton(
|
||||
content = MessagesReactionsButtonContent.Text("12 more"),
|
||||
onClick = {},
|
||||
|
||||
@@ -58,12 +58,12 @@ fun TimelineItemReactionsLayout(
|
||||
SubcomposeLayout(modifier) { constraints ->
|
||||
// Given the placeables and returns a structure representing
|
||||
// how they should wrap on to multiple rows given the constraints max width.
|
||||
fun calculateRows(measurables: List<Placeable>): List<List<Placeable>> {
|
||||
fun calculateRows(placeables: List<Placeable>): List<List<Placeable>> {
|
||||
val rows = mutableListOf<List<Placeable>>()
|
||||
var currentRow = mutableListOf<Placeable>()
|
||||
var rowX = 0
|
||||
|
||||
measurables.forEach { placeable ->
|
||||
placeables.forEach { placeable ->
|
||||
val horizontalSpacing = if (currentRow.isEmpty()) 0 else itemSpacing.toPx().toInt()
|
||||
// If the current view does not fit on this row bump to the next
|
||||
if (rowX + placeable.width > constraints.maxWidth) {
|
||||
@@ -146,12 +146,18 @@ fun TimelineItemReactionsLayout(
|
||||
}
|
||||
}
|
||||
|
||||
val reactionsPlaceables = subcompose(0, reactions).map { it.measure(constraints) }
|
||||
var reactionsPlaceables = subcompose(0, reactions).map { it.measure(constraints) }
|
||||
if (reactionsPlaceables.isEmpty()) {
|
||||
return@SubcomposeLayout layoutRows(listOf())
|
||||
}
|
||||
val addMorePlaceable = subcompose(1, addMoreButton).first().measure(constraints)
|
||||
val expandPlaceable = subcompose(2, expandButton).first().measure(constraints)
|
||||
var expandPlaceable = subcompose(1, expandButton).first().measure(constraints)
|
||||
// Enforce all reaction buttons have the same height
|
||||
val maxHeight = (reactionsPlaceables + listOf(expandPlaceable)).maxOf { it.height }
|
||||
val newConstrains = constraints.copy(minHeight = maxHeight)
|
||||
reactionsPlaceables = subcompose(2, reactions).map { it.measure(newConstrains) }
|
||||
expandPlaceable = subcompose(3, expandButton).first().measure(newConstrains)
|
||||
val addMorePlaceable = subcompose(4, addMoreButton).first().measure(newConstrains)
|
||||
|
||||
|
||||
// Calculate the layout of the rows with the reactions button and add more button
|
||||
val reactionsAndAddMore = calculateRows(reactionsPlaceables + listOf(addMorePlaceable))
|
||||
|
||||
@@ -47,68 +47,74 @@ fun TimelineItemReactions(
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
var expanded: Boolean by rememberSaveable { mutableStateOf(false) }
|
||||
|
||||
// In LTR languages we want an incoming message's reactions to be LRT and outgoing to be RTL.
|
||||
// For RTL languages it should be the opposite.
|
||||
val reactionsLayoutDirection = if (!isOutgoing) LocalLayoutDirection.current
|
||||
else if (LocalLayoutDirection.current == LayoutDirection.Ltr)
|
||||
LayoutDirection.Rtl
|
||||
else
|
||||
LayoutDirection.Ltr
|
||||
|
||||
CompositionLocalProvider(LocalLayoutDirection provides reactionsLayoutDirection) {
|
||||
TimelineItemReactionsView(
|
||||
modifier = modifier,
|
||||
reactions = reactionsState.reactions,
|
||||
expanded = expanded,
|
||||
isOutgoing = isOutgoing,
|
||||
onReactionClick = onReactionClicked,
|
||||
onReactionLongClick = onReactionLongClicked,
|
||||
onMoreReactionsClick = onMoreReactionsClicked,
|
||||
onToggleExpandClick = { expanded = !expanded },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun TimelineItemReactionsView(
|
||||
reactions: ImmutableList<AggregatedReaction>,
|
||||
isOutgoing: Boolean,
|
||||
expanded: Boolean,
|
||||
onReactionClick: (emoji: String) -> Unit,
|
||||
onReactionLongClick: (emoji: String) -> Unit,
|
||||
onMoreReactionsClick: () -> Unit,
|
||||
onToggleExpandClick: () -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) = TimelineItemReactionsLayout(
|
||||
modifier = modifier,
|
||||
itemSpacing = 4.dp,
|
||||
rowSpacing = 4.dp,
|
||||
expanded = expanded,
|
||||
expandButton = {
|
||||
MessagesReactionButton(
|
||||
content = MessagesReactionsButtonContent.Text(
|
||||
text = stringResource(id = if (expanded) R.string.screen_room_reactions_show_less else R.string.screen_room_reactions_show_more)
|
||||
),
|
||||
onClick = onToggleExpandClick,
|
||||
onLongClick = {}
|
||||
) {
|
||||
// In LTR languages we want an incoming message's reactions to be LRT and outgoing to be RTL.
|
||||
// For RTL languages it should be the opposite.
|
||||
val currentLayout = LocalLayoutDirection.current
|
||||
val reactionsLayoutDirection = if (!isOutgoing) currentLayout
|
||||
else if (currentLayout == LayoutDirection.Ltr)
|
||||
LayoutDirection.Rtl
|
||||
else
|
||||
LayoutDirection.Ltr
|
||||
|
||||
return CompositionLocalProvider(LocalLayoutDirection provides reactionsLayoutDirection) {
|
||||
TimelineItemReactionsLayout(
|
||||
modifier = modifier,
|
||||
itemSpacing = 4.dp,
|
||||
rowSpacing = 4.dp,
|
||||
expanded = expanded,
|
||||
expandButton = {
|
||||
MessagesReactionButton(
|
||||
content = MessagesReactionsButtonContent.Text(
|
||||
text = stringResource(id = if (expanded) R.string.screen_room_reactions_show_less else R.string.screen_room_reactions_show_more)
|
||||
),
|
||||
onClick = onToggleExpandClick,
|
||||
onLongClick = {}
|
||||
)
|
||||
},
|
||||
addMoreButton = {
|
||||
MessagesReactionButton(
|
||||
content = MessagesReactionsButtonContent.Icon(Icons.Outlined.AddReaction),
|
||||
onClick = onMoreReactionsClick,
|
||||
onLongClick = {}
|
||||
)
|
||||
},
|
||||
reactions = {
|
||||
reactions.forEach { reaction ->
|
||||
CompositionLocalProvider(LocalLayoutDirection provides currentLayout) {
|
||||
MessagesReactionButton(
|
||||
content = MessagesReactionsButtonContent.Reaction(reaction = reaction),
|
||||
onClick = { onReactionClick(reaction.key) },
|
||||
onLongClick = { onReactionLongClick(reaction.key) }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
},
|
||||
addMoreButton = {
|
||||
MessagesReactionButton(
|
||||
content = MessagesReactionsButtonContent.Icon(Icons.Outlined.AddReaction),
|
||||
onClick = onMoreReactionsClick,
|
||||
onLongClick = {}
|
||||
)
|
||||
},
|
||||
reactions = {
|
||||
reactions.forEach { reaction ->
|
||||
MessagesReactionButton(
|
||||
content = MessagesReactionsButtonContent.Reaction(reaction = reaction),
|
||||
onClick = { onReactionClick(reaction.key) },
|
||||
onLongClick = { onReactionLongClick(reaction.key) }
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@DayNightPreviews
|
||||
@Composable
|
||||
|
||||
@@ -51,6 +51,7 @@ import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
@@ -177,10 +178,12 @@ fun AggregatedReactionButton(
|
||||
MaterialTheme.colorScheme.primary
|
||||
}
|
||||
|
||||
val roundedCornerShape = RoundedCornerShape(corner = CornerSize(percent = 50))
|
||||
Surface(
|
||||
modifier = modifier
|
||||
.background(buttonColor, roundedCornerShape)
|
||||
.clip(roundedCornerShape)
|
||||
.clickable(onClick = onClick)
|
||||
.background(buttonColor, RoundedCornerShape(corner = CornerSize(percent = 50)))
|
||||
.padding(vertical = 8.dp, horizontal = 12.dp),
|
||||
color = buttonColor
|
||||
) {
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user