Rework Message bubbles, to avoid using zIndex and offset.

This commit is contained in:
Benoit Marty 2023-06-22 17:04:57 +02:00 committed by Benoit Marty
parent 89e3302f12
commit 965e622032
2 changed files with 56 additions and 52 deletions

View file

@ -28,7 +28,6 @@ import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.width
@ -49,7 +48,6 @@ import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.compose.ui.zIndex
import io.element.android.features.messages.impl.timeline.aTimelineItemEvent import io.element.android.features.messages.impl.timeline.aTimelineItemEvent
import io.element.android.features.messages.impl.timeline.components.event.TimelineItemEventContentView import io.element.android.features.messages.impl.timeline.components.event.TimelineItemEventContentView
import io.element.android.features.messages.impl.timeline.model.TimelineItem import io.element.android.features.messages.impl.timeline.model.TimelineItem
@ -98,64 +96,71 @@ fun TimelineItemEventRow(
inReplyToClick(inReplyToEventId) inReplyToClick(inReplyToEventId)
} }
val (parentAlignment, contentAlignment) = if (event.isMine) { // To avoid using negative offset, we display in this Box a column with:
Pair(Alignment.CenterEnd, Alignment.End) // - Spacer to give room to the Sender information if they must be displayed;
} else { // - The message bubble;
Pair(Alignment.CenterStart, Alignment.Start) // - Spacer for the reactions if there are some.
} // Then the Sender information and the reactions are displayed on top of it.
// This fixes some clickable issue and some unexpected margin on top and bottom of each message row
Box( Box(
modifier = modifier modifier = modifier
.fillMaxWidth() .fillMaxWidth()
.wrapContentHeight(), .wrapContentHeight(),
contentAlignment = parentAlignment contentAlignment = if (event.isMine) Alignment.CenterEnd else Alignment.CenterStart
) { ) {
Row { Column {
Column(horizontalAlignment = contentAlignment) { if (event.showSenderInformation) {
if (event.showSenderInformation) { Spacer(modifier = Modifier.height(event.senderAvatar.size.dp - 8.dp))
MessageSenderInformation( }
event.safeSenderName, val bubbleState = BubbleState(
event.senderAvatar, groupPosition = event.groupPosition,
Modifier isMine = event.isMine,
.zIndex(1f) isHighlighted = isHighlighted,
.offset(y = 8.dp) )
.clickable(onClick = ::onUserDataClicked) MessageEventBubble(
) state = bubbleState,
} interactionSource = interactionSource,
val bubbleState = BubbleState( onClick = onClick,
groupPosition = event.groupPosition, onLongClick = onLongClick,
isMine = event.isMine, modifier = Modifier
isHighlighted = isHighlighted, .widthIn(max = 320.dp)
) ) {
MessageEventBubble( MessageEventBubbleContent(
state = bubbleState, event = event,
interactionSource = interactionSource, interactionSource = interactionSource,
onClick = onClick, onMessageClick = onClick,
onLongClick = onLongClick, onMessageLongClick = onLongClick,
modifier = Modifier inReplyToClick = ::inReplyToClicked,
.zIndex(-1f) onTimestampClicked = {
.widthIn(max = 320.dp) onTimestampClicked(event)
) { }
MessageEventBubbleContent(
event = event,
interactionSource = interactionSource,
onMessageClick = onClick,
onMessageLongClick = onLongClick,
inReplyToClick = ::inReplyToClicked,
onTimestampClicked = {
onTimestampClicked(event)
}
)
}
TimelineItemReactionsView(
reactionsState = event.reactionsState,
modifier = Modifier
.zIndex(1f)
.offset(x = if (event.isMine) 0.dp else 20.dp, y = -(4.dp))
) )
} }
if (event.reactionsState.reactions.isNotEmpty()) {
Spacer(modifier = Modifier.height(28.dp))
}
}
// Align to the top of the box
if (event.showSenderInformation) {
MessageSenderInformation(
event.safeSenderName,
event.senderAvatar,
Modifier
.align(Alignment.TopStart)
.clickable(onClick = ::onUserDataClicked)
)
}
// Align to the bottom of the box
if (event.reactionsState.reactions.isNotEmpty()) {
TimelineItemReactionsView(
reactionsState = event.reactionsState,
modifier = Modifier
.align(if (event.isMine) Alignment.BottomEnd else Alignment.BottomStart)
.padding(start = if (event.isMine) 0.dp else 20.dp)
)
} }
} }
// This is assuming that we are in a ColumnScope, but this is OK, for both Preview and real usage.
if (event.groupPosition.isNew()) { if (event.groupPosition.isNew()) {
Spacer(modifier = modifier.height(8.dp)) Spacer(modifier = modifier.height(8.dp))
} else { } else {
@ -173,7 +178,7 @@ private fun MessageSenderInformation(
val avatarStrokeColor = MaterialTheme.colorScheme.background val avatarStrokeColor = MaterialTheme.colorScheme.background
val avatarSize = senderAvatar.size.dp val avatarSize = senderAvatar.size.dp
Box( Box(
modifier = modifier.offset(y = avatarStrokeSize) modifier = modifier
) { ) {
// Background of Avatar, to erase the corner of the message content // Background of Avatar, to erase the corner of the message content
Canvas( Canvas(

View file

@ -31,7 +31,6 @@ fun TimelineItemReactionsView(
reactionsState: TimelineItemReactions, reactionsState: TimelineItemReactions,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
if (reactionsState.reactions.isEmpty()) return
FlowRow( FlowRow(
modifier = modifier, modifier = modifier,
mainAxisSpacing = 2.dp, mainAxisSpacing = 2.dp,