diff --git a/chat-engine/src/main/kotlin/app/dapk/st/engine/Models.kt b/chat-engine/src/main/kotlin/app/dapk/st/engine/Models.kt index b823b03..5a29bff 100644 --- a/chat-engine/src/main/kotlin/app/dapk/st/engine/Models.kt +++ b/chat-engine/src/main/kotlin/app/dapk/st/engine/Models.kt @@ -104,6 +104,12 @@ sealed class RoomEvent { abstract val utcTimestamp: Long abstract val author: RoomMember abstract val meta: MessageMeta + abstract val edited: Boolean + + val time: String by lazy(mode = LazyThreadSafetyMode.NONE) { + val instant = Instant.ofEpochMilli(utcTimestamp) + ZonedDateTime.ofInstant(instant, DEFAULT_ZONE).toLocalTime().format(MESSAGE_TIME_FORMAT) + } data class Encrypted( override val eventId: EventId, @@ -112,10 +118,8 @@ sealed class RoomEvent { override val meta: MessageMeta, ) : RoomEvent() { - val time: String by lazy(mode = LazyThreadSafetyMode.NONE) { - val instant = Instant.ofEpochMilli(utcTimestamp) - ZonedDateTime.ofInstant(instant, DEFAULT_ZONE).toLocalTime().format(MESSAGE_TIME_FORMAT) - } + override val edited: Boolean = false + } data class Message( @@ -124,15 +128,9 @@ sealed class RoomEvent { val content: String, override val author: RoomMember, override val meta: MessageMeta, - val edited: Boolean = false, + override val edited: Boolean = false, val redacted: Boolean = false, - ) : RoomEvent() { - - val time: String by lazy(mode = LazyThreadSafetyMode.NONE) { - val instant = Instant.ofEpochMilli(utcTimestamp) - ZonedDateTime.ofInstant(instant, DEFAULT_ZONE).toLocalTime().format(MESSAGE_TIME_FORMAT) - } - } + ) : RoomEvent() data class Reply( val message: RoomEvent, @@ -143,13 +141,9 @@ sealed class RoomEvent { override val utcTimestamp: Long = message.utcTimestamp override val author: RoomMember = message.author override val meta: MessageMeta = message.meta + override val edited: Boolean = message.edited val replyingToSelf = replyingTo.author == message.author - - val time: String by lazy(mode = LazyThreadSafetyMode.NONE) { - val instant = Instant.ofEpochMilli(utcTimestamp) - ZonedDateTime.ofInstant(instant, DEFAULT_ZONE).toLocalTime().format(MESSAGE_TIME_FORMAT) - } } data class Image( @@ -158,14 +152,9 @@ sealed class RoomEvent { val imageMeta: ImageMeta, override val author: RoomMember, override val meta: MessageMeta, - val edited: Boolean = false, + override val edited: Boolean = false, ) : RoomEvent() { - val time: String by lazy(mode = LazyThreadSafetyMode.NONE) { - val instant = Instant.ofEpochMilli(utcTimestamp) - ZonedDateTime.ofInstant(instant, DEFAULT_ZONE).toLocalTime().format(MESSAGE_TIME_FORMAT) - } - data class ImageMeta( val width: Int?, val height: Int?, diff --git a/design-library/src/main/kotlin/app/dapk/st/design/components/AlignedContainer.kt b/design-library/src/main/kotlin/app/dapk/st/design/components/AlignedContainer.kt new file mode 100644 index 0000000..5eea262 --- /dev/null +++ b/design-library/src/main/kotlin/app/dapk/st/design/components/AlignedContainer.kt @@ -0,0 +1,147 @@ +package app.dapk.st.design.components + +import androidx.compose.animation.core.Animatable +import androidx.compose.foundation.gestures.Orientation +import androidx.compose.foundation.gestures.draggable +import androidx.compose.foundation.gestures.rememberDraggableState +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.LazyItemScope +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalConfiguration +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.unit.IntOffset +import androidx.compose.ui.unit.dp +import kotlinx.coroutines.launch +import kotlin.math.roundToInt + +private val selfBackgroundShape = RoundedCornerShape(12.dp, 0.dp, 12.dp, 12.dp) +private val othersBackgroundShape = RoundedCornerShape(0.dp, 12.dp, 12.dp, 12.dp) + +data class BubbleMeta( + val shape: RoundedCornerShape, + val background: Color, + val isSelf: Boolean, +) + +fun BubbleMeta.isNotSelf() = !this.isSelf + +@Composable +fun LazyItemScope.AlignedContainer( + avatar: Avatar, + isSelf: Boolean, + wasPreviousMessageSameSender: Boolean, + onReply: () -> Unit, + content: @Composable BubbleMeta.() -> Unit +) { + val rowWithMeta = @Composable { + DraggableRow( + avatar = avatar, + isSelf = isSelf, + wasPreviousMessageSameSender = wasPreviousMessageSameSender, + onReply = { onReply() } + ) { + content( + when (isSelf) { + true -> BubbleMeta(selfBackgroundShape, SmallTalkTheme.extendedColors.selfBubble, isSelf = true) + false -> BubbleMeta(othersBackgroundShape, SmallTalkTheme.extendedColors.othersBubble, isSelf = false) + } + ) + } + } + + when (isSelf) { + true -> SelfContainer(rowWithMeta) + false -> OtherContainer(rowWithMeta) + } +} + +@Composable +private fun LazyItemScope.OtherContainer(content: @Composable () -> Unit) { + Box(modifier = Modifier.Companion.fillParentMaxWidth(0.95f), contentAlignment = Alignment.TopStart) { + content() + } +} + +@Composable +private fun LazyItemScope.SelfContainer(content: @Composable () -> Unit) { + Box(modifier = Modifier.Companion.fillParentMaxWidth(), contentAlignment = Alignment.TopEnd) { + Box(modifier = Modifier.Companion.fillParentMaxWidth(0.85f), contentAlignment = Alignment.TopEnd) { + content() + } + } +} + +@Composable +private fun DraggableRow( + isSelf: Boolean, + wasPreviousMessageSameSender: Boolean, + onReply: () -> Unit, + avatar: Avatar, + content: @Composable () -> Unit +) { + + val screenWidthDp = LocalConfiguration.current.screenWidthDp.dp + val localDensity = LocalDensity.current + + val coroutineScope = rememberCoroutineScope() + val offsetX = remember { Animatable(0f) } + + Row( + Modifier.padding(horizontal = 12.dp) + .offset { IntOffset(offsetX.value.roundToInt(), 0) } + .draggable( + orientation = Orientation.Horizontal, + state = rememberDraggableState { + if ((offsetX.value + it) > 0) { + coroutineScope.launch { offsetX.snapTo(offsetX.value + it) } + } + }, + onDragStopped = { + with(localDensity) { + if (offsetX.value > (screenWidthDp.toPx() * 0.15)) { + onReply() + } + } + + coroutineScope.launch { + offsetX.animateTo(targetValue = 0f) + } + } + ) + ) { + when (isSelf) { + true -> { + // do nothing + } + + false -> SenderAvatar(wasPreviousMessageSameSender, avatar) + } + content() + } +} + +@Composable +private fun SenderAvatar(wasPreviousMessageSameSender: Boolean, avatar: Avatar) { + val displayImageSize = 32.dp + when { + wasPreviousMessageSameSender -> { + Spacer(modifier = Modifier.width(displayImageSize)) + } + + avatar.url == null -> { + MissingAvatarIcon(avatar.name, displayImageSize) + } + + else -> { + MessengerUrlIcon(avatar.url, displayImageSize) + } + } +} + +data class Avatar(val url: String?, val name: String) \ No newline at end of file diff --git a/design-library/src/main/kotlin/app/dapk/st/design/components/Bubble.kt b/design-library/src/main/kotlin/app/dapk/st/design/components/Bubble.kt new file mode 100644 index 0000000..667cbbc --- /dev/null +++ b/design-library/src/main/kotlin/app/dapk/st/design/components/Bubble.kt @@ -0,0 +1,76 @@ +package app.dapk.st.design.components + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.* +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +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.TextAlign +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp + +data class Event(val authorName: String, val edited: Boolean, val time: String) + +@Composable +fun Bubble(bubble: BubbleMeta, content: @Composable () -> Unit) { + Box(modifier = Modifier.padding(start = 6.dp)) { + Box( + Modifier + .padding(4.dp) + .clip(bubble.shape) + .background(bubble.background) + .height(IntrinsicSize.Max), + ) { + content() + } + } +} + +@Composable +fun TextBubbleContent(bubble: BubbleMeta, event: Event, textContent: String, status: @Composable () -> Unit) { + Bubble(bubble) { + Column( + Modifier + .padding(8.dp) + .width(IntrinsicSize.Max) + .defaultMinSize(minWidth = 50.dp) + ) { + if (bubble.isNotSelf()) { + Text( + fontSize = 11.sp, + text = event.authorName, + maxLines = 1, + color = bubble.textColor() + ) + } + Text( + text = textContent, + color = bubble.textColor(), + fontSize = 15.sp, + modifier = Modifier.wrapContentSize(), + textAlign = TextAlign.Start, + ) + + Spacer(modifier = Modifier.height(2.dp)) + Row(horizontalArrangement = Arrangement.End, verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth()) { + val editedPrefix = if (event.edited) "(edited) " else null + Text( + fontSize = 9.sp, + text = "${editedPrefix ?: ""}${event.time}", + textAlign = TextAlign.End, + color = bubble.textColor(), + modifier = Modifier.wrapContentSize() + ) + status() + } + } + } +} + +@Composable +private fun BubbleMeta.textColor(): Color { + return if (this.isSelf) SmallTalkTheme.extendedColors.onSelfBubble else SmallTalkTheme.extendedColors.onOthersBubble +} \ No newline at end of file diff --git a/features/messenger/src/main/kotlin/app/dapk/st/messenger/MessengerScreen.kt b/features/messenger/src/main/kotlin/app/dapk/st/messenger/MessengerScreen.kt index 8662e70..d5476fe 100644 --- a/features/messenger/src/main/kotlin/app/dapk/st/messenger/MessengerScreen.kt +++ b/features/messenger/src/main/kotlin/app/dapk/st/messenger/MessengerScreen.kt @@ -3,15 +3,11 @@ package app.dapk.st.messenger import android.content.res.Configuration import androidx.activity.result.ActivityResultLauncher import androidx.compose.animation.* -import androidx.compose.animation.core.Animatable import androidx.compose.animation.core.tween import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.border import androidx.compose.foundation.clickable -import androidx.compose.foundation.gestures.Orientation -import androidx.compose.foundation.gestures.draggable -import androidx.compose.foundation.gestures.rememberDraggableState import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.* import androidx.compose.foundation.shape.CircleShape @@ -56,7 +52,6 @@ import app.dapk.st.navigator.Navigator import coil.compose.rememberAsyncImagePainter import coil.request.ImageRequest import kotlinx.coroutines.launch -import kotlin.math.roundToInt @Composable internal fun MessengerScreen( @@ -193,58 +188,20 @@ private fun ColumnScope.RoomContent(self: UserId, state: RoomState, replyActions ) { index, item -> val previousEvent = if (index != 0) state.events[index - 1] else null val wasPreviousMessageSameSender = previousEvent?.author?.id == item.author.id - AlignedBubble(item, self, wasPreviousMessageSameSender, replyActions) { + + AlignedContainer( + avatar = Avatar(item.author.avatarUrl?.value, item.author.displayName ?: item.author.id.value), + isSelf = self == item.author.id, + wasPreviousMessageSameSender = wasPreviousMessageSameSender, + onReply = { replyActions.onReply(item) }, + ) { + val event = Event(item.author.displayName ?: item.author.id.value, item.edited, item.time) + val status = @Composable { SendStatus(item) } when (item) { - is RoomEvent.Image -> MessageImage(it as BubbleContent) - is RoomEvent.Message -> TextBubbleContent(it as BubbleContent) - is RoomEvent.Reply -> ReplyBubbleContent(it as BubbleContent) - is RoomEvent.Encrypted -> EncryptedBubbleContent(it as BubbleContent) - } - } - } - } -} - -private data class BubbleContent( - val shape: RoundedCornerShape, - val background: Color, - val isNotSelf: Boolean, - val message: T -) - -@Composable -private fun LazyItemScope.AlignedBubble( - message: T, - self: UserId, - wasPreviousMessageSameSender: Boolean, - replyActions: ReplyActions, - content: @Composable (BubbleContent) -> Unit -) { - when (message.author.id == self) { - true -> { - Box(modifier = Modifier.fillParentMaxWidth(), contentAlignment = Alignment.TopEnd) { - Box(modifier = Modifier.fillParentMaxWidth(0.85f), contentAlignment = Alignment.TopEnd) { - Bubble( - message = message, - isNotSelf = false, - wasPreviousMessageSameSender = wasPreviousMessageSameSender, - replyActions = replyActions, - ) { - content(BubbleContent(selfBackgroundShape, SmallTalkTheme.extendedColors.selfBubble, false, message)) - } - } - } - } - - false -> { - Box(modifier = Modifier.fillParentMaxWidth(0.95f), contentAlignment = Alignment.TopStart) { - Bubble( - message = message, - isNotSelf = true, - wasPreviousMessageSameSender = wasPreviousMessageSameSender, - replyActions = replyActions, - ) { - content(BubbleContent(othersBackgroundShape, SmallTalkTheme.extendedColors.othersBubble, true, message)) + is RoomEvent.Image -> MessageImage(this, item) + is RoomEvent.Message -> TextBubbleContent(this, event, item.content, status = status) + is RoomEvent.Reply -> ReplyBubbleContent(this, item) + is RoomEvent.Encrypted -> EncryptedBubbleContent(this, item) } } } @@ -252,15 +209,15 @@ private fun LazyItemScope.AlignedBubble( } @Composable -private fun MessageImage(content: BubbleContent) { +private fun MessageImage(bubble: BubbleMeta, event: RoomEvent.Image) { val context = LocalContext.current Box(modifier = Modifier.padding(start = 6.dp)) { Box( Modifier .padding(4.dp) - .clip(content.shape) - .background(content.background) + .clip(bubble.shape) + .background(bubble.background) .height(IntrinsicSize.Max), ) { Column( @@ -269,23 +226,23 @@ private fun MessageImage(content: BubbleContent) { .width(IntrinsicSize.Max) .defaultMinSize(minWidth = 50.dp) ) { - if (content.isNotSelf) { + if (bubble.isNotSelf()) { Text( fontSize = 11.sp, - text = content.message.author.displayName ?: content.message.author.id.value, + text = event.author.displayName ?: event.author.id.value, maxLines = 1, - color = content.textColor() + color = bubble.textColor() ) } Spacer(modifier = Modifier.height(4.dp)) Image( - modifier = Modifier.size(content.message.imageMeta.scale(LocalDensity.current, LocalConfiguration.current)), + modifier = Modifier.size(event.imageMeta.scale(LocalDensity.current, LocalConfiguration.current)), painter = rememberAsyncImagePainter( model = ImageRequest.Builder(context) .fetcherFactory(LocalDecyptingFetcherFactory.current) - .memoryCacheKey(content.message.imageMeta.url) - .data(content.message) + .memoryCacheKey(event.imageMeta.url) + .data(event) .build() ), contentDescription = null, @@ -293,15 +250,15 @@ private fun MessageImage(content: BubbleContent) { Spacer(modifier = Modifier.height(4.dp)) Row(horizontalArrangement = Arrangement.End, verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth()) { - val editedPrefix = if (content.message.edited) "(edited) " else null + val editedPrefix = if (event.edited) "(edited) " else null Text( fontSize = 9.sp, - text = "${editedPrefix ?: ""}${content.message.time}", + text = "${editedPrefix ?: ""}${event.time}", textAlign = TextAlign.End, - color = content.textColor(), + color = bubble.textColor(), modifier = Modifier.wrapContentSize() ) - SendStatus(content.message) + SendStatus(event) } } } @@ -329,82 +286,19 @@ private fun Int.scalerFor(max: Float): Float { return max / this } -private val selfBackgroundShape = RoundedCornerShape(12.dp, 0.dp, 12.dp, 12.dp) -private val othersBackgroundShape = RoundedCornerShape(0.dp, 12.dp, 12.dp, 12.dp) - @Composable -private fun Bubble( - message: RoomEvent, - isNotSelf: Boolean, - wasPreviousMessageSameSender: Boolean, - replyActions: ReplyActions, - content: @Composable () -> Unit -) { - - val screenWidthDp = LocalConfiguration.current.screenWidthDp.dp - val localDensity = LocalDensity.current - - val coroutineScope = rememberCoroutineScope() - val offsetX = remember { Animatable(0f) } - - Row( - Modifier.padding(horizontal = 12.dp) - .offset { IntOffset(offsetX.value.roundToInt(), 0) } - .draggable( - orientation = Orientation.Horizontal, - state = rememberDraggableState { - if ((offsetX.value + it) > 0) { - coroutineScope.launch { offsetX.snapTo(offsetX.value + it) } - } - }, - onDragStopped = { - with(localDensity) { - if (offsetX.value > (screenWidthDp.toPx() * 0.15)) { - replyActions.onReply(message) - } - } - - coroutineScope.launch { - offsetX.animateTo(targetValue = 0f) - } - } - ) - ) { - when { - isNotSelf -> { - val displayImageSize = 32.dp - when { - wasPreviousMessageSameSender -> { - Spacer(modifier = Modifier.width(displayImageSize)) - } - - message.author.avatarUrl == null -> { - MissingAvatarIcon(message.author.displayName ?: message.author.id.value, displayImageSize) - } - - else -> { - MessengerUrlIcon(message.author.avatarUrl!!.value, displayImageSize) - } - } - } - } - content() - } +private fun BubbleMeta.textColor(): Color { + return if (this.isSelf) SmallTalkTheme.extendedColors.onSelfBubble else SmallTalkTheme.extendedColors.onOthersBubble } @Composable -private fun BubbleContent<*>.textColor(): Color { - return if (this.isNotSelf) SmallTalkTheme.extendedColors.onOthersBubble else SmallTalkTheme.extendedColors.onSelfBubble -} - -@Composable -private fun TextBubbleContent(content: BubbleContent) { +private fun EncryptedBubbleContent(bubble: BubbleMeta, event: RoomEvent.Encrypted) { Box(modifier = Modifier.padding(start = 6.dp)) { Box( Modifier .padding(4.dp) - .clip(content.shape) - .background(content.background) + .clip(bubble.shape) + .background(bubble.background) .height(IntrinsicSize.Max), ) { Column( @@ -413,66 +307,17 @@ private fun TextBubbleContent(content: BubbleContent) { .width(IntrinsicSize.Max) .defaultMinSize(minWidth = 50.dp) ) { - if (content.isNotSelf) { + if (bubble.isNotSelf()) { Text( fontSize = 11.sp, - text = content.message.author.displayName ?: content.message.author.id.value, + text = event.author.displayName ?: event.author.id.value, maxLines = 1, - color = content.textColor() - ) - } - Text( - text = content.message.content, - color = content.textColor(), - fontSize = 15.sp, - modifier = Modifier.wrapContentSize(), - textAlign = TextAlign.Start, - ) - - Spacer(modifier = Modifier.height(2.dp)) - Row(horizontalArrangement = Arrangement.End, verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth()) { - val editedPrefix = if (content.message.edited) "(edited) " else null - Text( - fontSize = 9.sp, - text = "${editedPrefix ?: ""}${content.message.time}", - textAlign = TextAlign.End, - color = content.textColor(), - modifier = Modifier.wrapContentSize() - ) - SendStatus(content.message) - } - } - } - } -} - -@Composable -private fun EncryptedBubbleContent(content: BubbleContent) { - Box(modifier = Modifier.padding(start = 6.dp)) { - Box( - Modifier - .padding(4.dp) - .clip(content.shape) - .background(content.background) - .height(IntrinsicSize.Max), - ) { - Column( - Modifier - .padding(8.dp) - .width(IntrinsicSize.Max) - .defaultMinSize(minWidth = 50.dp) - ) { - if (content.isNotSelf) { - Text( - fontSize = 11.sp, - text = content.message.author.displayName ?: content.message.author.id.value, - maxLines = 1, - color = content.textColor() + color = bubble.textColor() ) } Text( text = "Encrypted message", - color = content.textColor(), + color = bubble.textColor(), fontSize = 15.sp, modifier = Modifier.wrapContentSize(), textAlign = TextAlign.Start, @@ -482,12 +327,12 @@ private fun EncryptedBubbleContent(content: BubbleContent) Row(horizontalArrangement = Arrangement.End, verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth()) { Text( fontSize = 9.sp, - text = "${content.message.time}", + text = event.time, textAlign = TextAlign.End, - color = content.textColor(), + color = bubble.textColor(), modifier = Modifier.wrapContentSize() ) - SendStatus(content.message) + SendStatus(event) } } } @@ -495,13 +340,13 @@ private fun EncryptedBubbleContent(content: BubbleContent) } @Composable -private fun ReplyBubbleContent(content: BubbleContent) { +private fun ReplyBubbleContent(bubble: BubbleMeta, event: RoomEvent.Reply) { Box(modifier = Modifier.padding(start = 6.dp)) { Box( Modifier .padding(4.dp) - .clip(content.shape) - .background(content.background) + .clip(bubble.shape) + .background(bubble.background) .height(IntrinsicSize.Max), ) { Column( @@ -515,26 +360,26 @@ private fun ReplyBubbleContent(content: BubbleContent) { Modifier .fillMaxWidth() .background( - if (content.isNotSelf) SmallTalkTheme.extendedColors.onOthersBubble.copy(alpha = 0.1f) else SmallTalkTheme.extendedColors.onSelfBubble.copy( + if (bubble.isNotSelf()) SmallTalkTheme.extendedColors.onOthersBubble.copy(alpha = 0.1f) else SmallTalkTheme.extendedColors.onSelfBubble.copy( alpha = 0.2f ), RoundedCornerShape(12.dp) ) .padding(8.dp) ) { - val replyName = if (!content.isNotSelf && content.message.replyingToSelf) "You" else content.message.replyingTo.author.displayName - ?: content.message.replyingTo.author.id.value + val replyName = if (!bubble.isNotSelf() && event.replyingToSelf) "You" else event.replyingTo.author.displayName + ?: event.replyingTo.author.id.value Text( fontSize = 11.sp, text = replyName, maxLines = 1, - color = content.textColor() + color = bubble.textColor() ) Spacer(modifier = Modifier.height(2.dp)) - when (val replyingTo = content.message.replyingTo) { + when (val replyingTo = event.replyingTo) { is RoomEvent.Message -> { Text( text = replyingTo.content, - color = content.textColor().copy(alpha = 0.8f), + color = bubble.textColor().copy(alpha = 0.8f), fontSize = 14.sp, modifier = Modifier.wrapContentSize(), textAlign = TextAlign.Start, @@ -564,7 +409,7 @@ private fun ReplyBubbleContent(content: BubbleContent) { is RoomEvent.Encrypted -> { Text( text = "Encrypted message", - color = content.textColor().copy(alpha = 0.8f), + color = bubble.textColor().copy(alpha = 0.8f), fontSize = 14.sp, modifier = Modifier.wrapContentSize(), textAlign = TextAlign.Start, @@ -575,19 +420,19 @@ private fun ReplyBubbleContent(content: BubbleContent) { Spacer(modifier = Modifier.height(12.dp)) - if (content.isNotSelf) { + if (bubble.isNotSelf()) { Text( fontSize = 11.sp, - text = content.message.message.author.displayName ?: content.message.message.author.id.value, + text = event.message.author.displayName ?: event.message.author.id.value, maxLines = 1, - color = content.textColor() + color = bubble.textColor() ) } - when (val message = content.message.message) { + when (val message = event.message) { is RoomEvent.Message -> { Text( text = message.content, - color = content.textColor(), + color = bubble.textColor(), fontSize = 15.sp, modifier = Modifier.wrapContentSize(), textAlign = TextAlign.Start, @@ -617,7 +462,7 @@ private fun ReplyBubbleContent(content: BubbleContent) { is RoomEvent.Encrypted -> { Text( text = "Encrypted message", - color = content.textColor(), + color = bubble.textColor(), fontSize = 15.sp, modifier = Modifier.wrapContentSize(), textAlign = TextAlign.Start, @@ -629,12 +474,12 @@ private fun ReplyBubbleContent(content: BubbleContent) { Row(horizontalArrangement = Arrangement.End, verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth()) { Text( fontSize = 9.sp, - text = content.message.time, + text = event.time, textAlign = TextAlign.End, - color = content.textColor(), + color = bubble.textColor(), modifier = Modifier.wrapContentSize() ) - SendStatus(content.message.message) + SendStatus(event.message) } } } @@ -643,7 +488,7 @@ private fun ReplyBubbleContent(content: BubbleContent) { @Composable -private fun RowScope.SendStatus(message: RoomEvent) { +private fun SendStatus(message: RoomEvent) { when (val meta = message.meta) { MessageMeta.FromServer -> { // last message is self