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:
David Langley
2023-08-02 20:18:16 +01:00
committed by GitHub
parent ed6af5e49d
commit 30c513b1b5
99 changed files with 247 additions and 213 deletions

View File

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

View File

@@ -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 = {},

View File

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

View File

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

View File

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