Timeline: start handling multiple contents
This commit is contained in:
@@ -4,6 +4,7 @@ import io.element.android.x.designsystem.components.avatar.AvatarData
|
||||
import io.element.android.x.designsystem.components.avatar.AvatarSize
|
||||
import io.element.android.x.features.messages.model.MessagesItemGroupPosition
|
||||
import io.element.android.x.features.messages.model.MessagesTimelineItemState
|
||||
import io.element.android.x.features.messages.model.content.*
|
||||
import io.element.android.x.matrix.MatrixClient
|
||||
import io.element.android.x.matrix.media.MediaResolver
|
||||
import io.element.android.x.matrix.room.MatrixRoom
|
||||
@@ -61,15 +62,30 @@ class MessageTimelineItemStateMapper(
|
||||
)
|
||||
}
|
||||
|
||||
private fun MatrixTimelineItem.Event.computeContent(): String? {
|
||||
val messageType =
|
||||
event.content().asMessage()?.msgtype()
|
||||
return when (messageType) {
|
||||
is MessageType.Emote -> messageType.content.body
|
||||
is MessageType.Image -> messageType.content.body
|
||||
is MessageType.Notice -> messageType.content.body
|
||||
is MessageType.Text -> messageType.content.body
|
||||
null -> null
|
||||
private fun MatrixTimelineItem.Event.computeContent(): MessagesTimelineItemContent {
|
||||
val content = event.content()
|
||||
content.asUnableToDecrypt()?.let { encryptedMessage ->
|
||||
return MessagesTimelineItemEncryptedContent(encryptedMessage)
|
||||
}
|
||||
if (content.isRedactedMessage()) {
|
||||
return MessagesTimelineItemRedactedContent
|
||||
}
|
||||
val contentAsMessage = content.asMessage()
|
||||
return when (val messageType = contentAsMessage?.msgtype()) {
|
||||
is MessageType.Emote -> MessagesTimelineItemEmoteContent(
|
||||
body = messageType.content.body,
|
||||
formattedBody = messageType.content.formatted
|
||||
)
|
||||
is MessageType.Image -> MessagesTimelineItemUnknownContent
|
||||
is MessageType.Notice -> MessagesTimelineItemNoticeContent(
|
||||
body = messageType.content.body,
|
||||
formattedBody = messageType.content.formatted
|
||||
)
|
||||
is MessageType.Text -> MessagesTimelineItemTextContent(
|
||||
body = messageType.content.body,
|
||||
formattedBody = messageType.content.formatted
|
||||
)
|
||||
null -> MessagesTimelineItemUnknownContent
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -36,9 +36,17 @@ import com.airbnb.mvrx.compose.mavericksViewModel
|
||||
import io.element.android.x.core.data.LogCompositions
|
||||
import io.element.android.x.core.data.StableCharSequence
|
||||
import io.element.android.x.designsystem.components.avatar.AvatarData
|
||||
import io.element.android.x.features.messages.components.MessagesTimelineItemEncryptedView
|
||||
import io.element.android.x.features.messages.components.MessagesTimelineItemRedactedView
|
||||
import io.element.android.x.features.messages.components.MessagesTimelineItemTextView
|
||||
import io.element.android.x.features.messages.components.MessagesTimelineItemUnknownView
|
||||
import io.element.android.x.features.messages.model.MessagesItemGroupPosition
|
||||
import io.element.android.x.features.messages.model.MessagesTimelineItemState
|
||||
import io.element.android.x.features.messages.model.MessagesViewState
|
||||
import io.element.android.x.features.messages.model.content.MessagesTimelineItemEncryptedContent
|
||||
import io.element.android.x.features.messages.model.content.MessagesTimelineItemRedactedContent
|
||||
import io.element.android.x.features.messages.model.content.MessagesTimelineItemTextBasedContent
|
||||
import io.element.android.x.features.messages.model.content.MessagesTimelineItemUnknownContent
|
||||
import io.element.android.x.features.messages.textcomposer.MessageComposerViewModel
|
||||
import io.element.android.x.features.messages.textcomposer.MessageComposerViewState
|
||||
import io.element.android.x.textcomposer.TextComposer
|
||||
@@ -231,7 +239,32 @@ fun MessageEventRow(
|
||||
Modifier.zIndex(1f)
|
||||
)
|
||||
}
|
||||
MessageEventBubble(messageEvent, Modifier.zIndex(-1f))
|
||||
MessageEventBubble(
|
||||
groupPosition = messageEvent.groupPosition,
|
||||
isMine = messageEvent.isMine,
|
||||
modifier = Modifier
|
||||
.zIndex(-1f)
|
||||
) {
|
||||
val contentModifier = Modifier.padding(horizontal = 12.dp, vertical = 6.dp)
|
||||
when (messageEvent.content) {
|
||||
is MessagesTimelineItemEncryptedContent -> MessagesTimelineItemEncryptedView(
|
||||
content = messageEvent.content,
|
||||
modifier = contentModifier
|
||||
)
|
||||
is MessagesTimelineItemRedactedContent -> MessagesTimelineItemRedactedView(
|
||||
content = messageEvent.content,
|
||||
modifier = contentModifier
|
||||
)
|
||||
is MessagesTimelineItemTextBasedContent -> MessagesTimelineItemTextView(
|
||||
content = messageEvent.content,
|
||||
modifier = contentModifier
|
||||
)
|
||||
is MessagesTimelineItemUnknownContent -> MessagesTimelineItemUnknownView(
|
||||
content = messageEvent.content,
|
||||
modifier = contentModifier
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (messageEvent.isMine) {
|
||||
Spacer(modifier = Modifier.width(16.dp))
|
||||
@@ -267,10 +300,12 @@ private fun MessageSenderInformation(
|
||||
|
||||
@Composable
|
||||
fun MessageEventBubble(
|
||||
messageEvent: MessagesTimelineItemState.MessageEvent,
|
||||
groupPosition: MessagesItemGroupPosition,
|
||||
isMine: Boolean,
|
||||
modifier: Modifier = Modifier,
|
||||
content: @Composable () -> Unit,
|
||||
) {
|
||||
fun MessagesTimelineItemState.MessageEvent.bubbleShape(): Shape {
|
||||
fun bubbleShape(): Shape {
|
||||
return when (groupPosition) {
|
||||
MessagesItemGroupPosition.First -> if (isMine) {
|
||||
RoundedCornerShape(BUBBLE_RADIUS, BUBBLE_RADIUS, 0.dp, BUBBLE_RADIUS)
|
||||
@@ -297,15 +332,15 @@ fun MessageEventBubble(
|
||||
}
|
||||
}
|
||||
|
||||
fun Modifier.offsetForItem(messageEvent: MessagesTimelineItemState.MessageEvent): Modifier {
|
||||
return if (messageEvent.isMine) {
|
||||
fun Modifier.offsetForItem(): Modifier {
|
||||
return if (isMine) {
|
||||
offset(y = -(12.dp))
|
||||
} else {
|
||||
offset(x = 20.dp, y = -(12.dp))
|
||||
}
|
||||
}
|
||||
|
||||
val (backgroundBubbleColor, border) = if (messageEvent.isMine) {
|
||||
val (backgroundBubbleColor, border) = if (isMine) {
|
||||
Pair(MaterialTheme.colorScheme.surfaceVariant, null)
|
||||
} else {
|
||||
Pair(
|
||||
@@ -313,12 +348,11 @@ fun MessageEventBubble(
|
||||
BorderStroke(1.dp, MaterialTheme.colorScheme.surfaceVariant)
|
||||
)
|
||||
}
|
||||
|
||||
val bubbleShape = messageEvent.bubbleShape()
|
||||
val bubbleShape = bubbleShape()
|
||||
Surface(
|
||||
modifier = modifier
|
||||
.widthIn(min = 80.dp)
|
||||
.offsetForItem(messageEvent)
|
||||
.offsetForItem()
|
||||
.clip(bubbleShape)
|
||||
.clickable(
|
||||
onClick = { },
|
||||
@@ -327,13 +361,9 @@ fun MessageEventBubble(
|
||||
),
|
||||
color = backgroundBubbleColor,
|
||||
shape = bubbleShape,
|
||||
border = border
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.padding(horizontal = 12.dp, vertical = 6.dp),
|
||||
text = messageEvent.content ?: "",
|
||||
)
|
||||
}
|
||||
border = border,
|
||||
content = content
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
package io.element.android.x.features.messages.components
|
||||
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Warning
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import io.element.android.x.features.messages.model.content.MessagesTimelineItemEncryptedContent
|
||||
|
||||
@Composable
|
||||
fun MessagesTimelineItemEncryptedView(
|
||||
content: MessagesTimelineItemEncryptedContent,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
MessagesTimelineItemInformativeView(
|
||||
text = "Decryption error",
|
||||
iconDescription = "Warning",
|
||||
icon = Icons.Default.Warning,
|
||||
modifier = modifier
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package io.element.android.x.features.messages.components
|
||||
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.text.font.FontStyle
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
|
||||
@Composable
|
||||
fun MessagesTimelineItemInformativeView(
|
||||
text: String,
|
||||
iconDescription: String,
|
||||
icon: ImageVector,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
Row(
|
||||
modifier = modifier,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Icon(
|
||||
imageVector = icon,
|
||||
tint = MaterialTheme.colorScheme.secondary,
|
||||
contentDescription = iconDescription
|
||||
)
|
||||
Spacer(modifier = Modifier.width(4.dp))
|
||||
Text(
|
||||
fontStyle = FontStyle.Italic,
|
||||
color = MaterialTheme.colorScheme.secondary,
|
||||
fontSize = 12.sp,
|
||||
text = text
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package io.element.android.x.features.messages.components
|
||||
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Delete
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import io.element.android.x.features.messages.model.content.MessagesTimelineItemRedactedContent
|
||||
|
||||
@Composable
|
||||
fun MessagesTimelineItemRedactedView(
|
||||
content: MessagesTimelineItemRedactedContent,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
MessagesTimelineItemInformativeView(
|
||||
text = "This message has been deleted",
|
||||
iconDescription = "Delete",
|
||||
icon = Icons.Default.Delete,
|
||||
modifier = modifier
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package io.element.android.x.features.messages.components
|
||||
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import io.element.android.x.features.messages.model.content.MessagesTimelineItemTextBasedContent
|
||||
|
||||
@Composable
|
||||
fun MessagesTimelineItemTextView(
|
||||
content: MessagesTimelineItemTextBasedContent,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
Box(modifier) {
|
||||
Text(text = content.body)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package io.element.android.x.features.messages.components
|
||||
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Info
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import io.element.android.x.features.messages.model.content.MessagesTimelineItemUnknownContent
|
||||
|
||||
@Composable
|
||||
fun MessagesTimelineItemUnknownView(
|
||||
content: MessagesTimelineItemUnknownContent,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
MessagesTimelineItemInformativeView(
|
||||
text = "Event not handled by EAX",
|
||||
iconDescription = "Info",
|
||||
icon = Icons.Default.Info,
|
||||
modifier = modifier
|
||||
)
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package io.element.android.x.features.messages.model
|
||||
|
||||
import io.element.android.x.designsystem.components.avatar.AvatarData
|
||||
import io.element.android.x.features.messages.model.content.MessagesTimelineItemContent
|
||||
|
||||
sealed interface MessagesTimelineItemState {
|
||||
data class Virtual(
|
||||
@@ -12,10 +13,10 @@ sealed interface MessagesTimelineItemState {
|
||||
val senderId: String,
|
||||
val senderDisplayName: String?,
|
||||
val senderAvatar: AvatarData,
|
||||
val content: String? = null,
|
||||
val content: MessagesTimelineItemContent,
|
||||
val sentTime: String = "",
|
||||
val isMine: Boolean = false,
|
||||
val groupPosition: MessagesItemGroupPosition = MessagesItemGroupPosition.None
|
||||
val groupPosition: MessagesItemGroupPosition = MessagesItemGroupPosition.None,
|
||||
) : MessagesTimelineItemState {
|
||||
|
||||
val showSenderInformation = groupPosition.isNew() && !isMine
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
package io.element.android.x.features.messages.model.content
|
||||
|
||||
sealed interface MessagesTimelineItemContent
|
||||
@@ -0,0 +1,8 @@
|
||||
package io.element.android.x.features.messages.model.content
|
||||
|
||||
import org.matrix.rustcomponents.sdk.FormattedBody
|
||||
|
||||
data class MessagesTimelineItemEmoteContent(
|
||||
override val body: String,
|
||||
override val formattedBody: FormattedBody?,
|
||||
) : MessagesTimelineItemTextBasedContent
|
||||
@@ -0,0 +1,7 @@
|
||||
package io.element.android.x.features.messages.model.content
|
||||
|
||||
import org.matrix.rustcomponents.sdk.EncryptedMessage
|
||||
|
||||
data class MessagesTimelineItemEncryptedContent(
|
||||
val encryptedMessage: EncryptedMessage
|
||||
) : MessagesTimelineItemContent
|
||||
@@ -0,0 +1,8 @@
|
||||
package io.element.android.x.features.messages.model.content
|
||||
|
||||
import org.matrix.rustcomponents.sdk.FormattedBody
|
||||
|
||||
data class MessagesTimelineItemNoticeContent(
|
||||
override val body: String,
|
||||
override val formattedBody: FormattedBody?,
|
||||
) : MessagesTimelineItemTextBasedContent
|
||||
@@ -0,0 +1,3 @@
|
||||
package io.element.android.x.features.messages.model.content
|
||||
|
||||
object MessagesTimelineItemRedactedContent : MessagesTimelineItemContent
|
||||
@@ -0,0 +1,8 @@
|
||||
package io.element.android.x.features.messages.model.content
|
||||
|
||||
import org.matrix.rustcomponents.sdk.FormattedBody
|
||||
|
||||
sealed interface MessagesTimelineItemTextBasedContent : MessagesTimelineItemContent {
|
||||
val body: String
|
||||
val formattedBody: FormattedBody?
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package io.element.android.x.features.messages.model.content
|
||||
|
||||
import org.matrix.rustcomponents.sdk.FormattedBody
|
||||
|
||||
data class MessagesTimelineItemTextContent(
|
||||
override val body: String,
|
||||
override val formattedBody: FormattedBody?,
|
||||
) : MessagesTimelineItemTextBasedContent
|
||||
@@ -0,0 +1,3 @@
|
||||
package io.element.android.x.features.messages.model.content
|
||||
|
||||
object MessagesTimelineItemUnknownContent : MessagesTimelineItemContent
|
||||
Reference in New Issue
Block a user