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:
parent
ac7412c2d8
commit
a68564b5f0
99 changed files with 247 additions and 213 deletions
|
|
@ -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
|
||||
) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue