I have a composable component that represent a message. Each message can either be incoming or outgoing, depending on that I would like to reverse all items in my message component.
The only way I found is to force a RTL layout, however it leads to text being reversed too.
Is there any other way around this?
MessageView.kt
@Composable
fun MessageView(
message: Message
) = Row(
modifier = Modifier
.fillMaxWidth()
.wrapContentHeight(),
verticalAlignment = Alignment.Bottom
) {
val (isIncoming) = message
val direction = if (isIncoming) {
LayoutDirection.Ltr
} else {
LayoutDirection.Rtl
}
CompositionLocalProvider(
LocalLayoutDirection provides direction
) {
MessageViewContent(message)
}
}
@Composable
private fun MessageViewContent(
message: Message
) = Row(
modifier = Modifier
.fillMaxWidth()
.wrapContentHeight(),
verticalAlignment = Alignment.Bottom
) {
val (isIncoming, text, at) = message
val background: Color
val textColor: Color
val timeColor: Color
val alignment: Alignment.Horizontal
val textAlignment: TextAlign
if (isIncoming) {
background = Color(0xFFEFEFEF)
textColor = Color(0xFF000000)
timeColor = Color(0xFF929292)
alignment = Alignment.End
textAlignment = TextAlign.Start
} else {
background = Color(0xFFE0727F)
textColor = Color(0xFFFEFEFE)
timeColor = Color(0xB3FEFEFE)
alignment = Alignment.Start
textAlignment = TextAlign.End
}
Image(
modifier = Modifier
.size(40.dp)
.clip(CircleShape),
painter = painterResource(R.drawable.ic_launcher_background),
contentDescription = null
)
Spacer(modifier = Modifier.width(12.dp))
Column(
modifier = Modifier
.weight(1F, fill = false)
.wrapContentWidth()
.background(
color = background,
shape = RoundedCornerShape(8.dp)
)
.padding(4.dp),
horizontalAlignment = alignment
) {
Text(
modifier = Modifier.padding(6.dp),
style = TextStyle(
fontSize = 16.sp,
color = textColor
),
text = text
)
Text(
style = TextStyle(
fontSize = 10.sp,
color = timeColor,
textAlign = textAlignment
),
text = "${at.hour}:${at.minute}",
)
}
Spacer(modifier = Modifier.width(60.dp))
}
So, I figured out a solution One can create a custom Horizontal Arrangement which uses code from already existing arrangements.
To use it, the containing Row
should span the width of the surface it's displayed on, and the items will simply be stacked the inverse side if the message is from the sender side.
ConversationSide.kt
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.LayoutDirection
private sealed interface ConversationSide : Arrangement.Horizontal {
data object Sender : ConversationSide {
override fun Density.arrange(
totalSize: Int,
sizes: IntArray,
layoutDirection: LayoutDirection,
outPositions: IntArray
) {
with(Arrangement.Start) {
arrange(
totalSize = totalSize,
sizes = sizes,
layoutDirection = when (layoutDirection) {
LayoutDirection.Ltr -> LayoutDirection.Rtl
LayoutDirection.Rtl -> LayoutDirection.Ltr
},
outPositions = outPositions
)
}
}
}
data object Receiver :
ConversationSide,
Arrangement.Horizontal by Arrangement.Start
}
Usage:
@Composable
fun MessageView(
message: Message
) = Row(
modifier = Modifier
.fillMaxWidth()
.wrapContentHeight(),
verticalAlignment = Alignment.Bottom,
horizontalArrangement = when {
message.isIncoming -> Side.Receiver
else -> Side.Sender
}
) {
...
}