Add thread decoration with latest event details (#5355)
* Add thread decoration with latest event details * Update screenshots --------- Co-authored-by: ElementBot <android@element.io>
This commit is contained in:
parent
5cadd37fa6
commit
0a5c178fe8
106 changed files with 554 additions and 282 deletions
|
|
@ -34,6 +34,7 @@ enum class AvatarSize(val dp: Dp) {
|
|||
TimelineRoom(32.dp),
|
||||
TimelineSender(32.dp),
|
||||
TimelineReadReceipt(16.dp),
|
||||
TimelineThreadLatestEventSender(24.dp),
|
||||
|
||||
ComposerAlert(32.dp),
|
||||
|
||||
|
|
|
|||
|
|
@ -7,8 +7,19 @@
|
|||
|
||||
package io.element.android.libraries.eventformatter.api
|
||||
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.EventContent
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.EventTimelineItem
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.getDisambiguatedDisplayName
|
||||
|
||||
interface TimelineEventFormatter {
|
||||
fun format(event: EventTimelineItem): CharSequence?
|
||||
fun format(event: EventTimelineItem): CharSequence? {
|
||||
return format(
|
||||
content = event.content,
|
||||
isOutgoing = event.isOwn,
|
||||
sender = event.sender,
|
||||
senderDisambiguatedDisplayName = event.senderProfile.getDisambiguatedDisplayName(event.sender),
|
||||
)
|
||||
}
|
||||
fun format(content: EventContent, isOutgoing: Boolean, sender: UserId, senderDisambiguatedDisplayName: String): CharSequence?
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,9 @@ import io.element.android.libraries.core.meta.BuildMeta
|
|||
import io.element.android.libraries.di.SessionScope
|
||||
import io.element.android.libraries.eventformatter.api.TimelineEventFormatter
|
||||
import io.element.android.libraries.eventformatter.impl.mode.RenderingMode
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.CallNotifyContent
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.EventContent
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.EventTimelineItem
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.FailedToParseMessageLikeContent
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.FailedToParseStateContent
|
||||
|
|
@ -43,12 +45,16 @@ class DefaultTimelineEventFormatter(
|
|||
override fun format(event: EventTimelineItem): CharSequence? {
|
||||
val isOutgoing = event.isOwn
|
||||
val senderDisambiguatedDisplayName = event.senderProfile.getDisambiguatedDisplayName(event.sender)
|
||||
return when (val content = event.content) {
|
||||
return format(event.content, isOutgoing, event.sender, senderDisambiguatedDisplayName)
|
||||
}
|
||||
|
||||
override fun format(content: EventContent, isOutgoing: Boolean, sender: UserId, senderDisambiguatedDisplayName: String): CharSequence? {
|
||||
return when (content) {
|
||||
is RoomMembershipContent -> {
|
||||
roomMembershipContentFormatter.format(content, senderDisambiguatedDisplayName, isOutgoing)
|
||||
}
|
||||
is ProfileChangeContent -> {
|
||||
profileChangeContentFormatter.format(content, event.sender, senderDisambiguatedDisplayName, isOutgoing)
|
||||
profileChangeContentFormatter.format(content, sender, senderDisambiguatedDisplayName, isOutgoing)
|
||||
}
|
||||
is StateContent -> {
|
||||
stateContentFormatter.format(content, senderDisambiguatedDisplayName, isOutgoing, RenderingMode.Timeline)
|
||||
|
|
@ -66,7 +72,7 @@ class DefaultTimelineEventFormatter(
|
|||
is FailedToParseStateContent,
|
||||
is UnknownContent -> {
|
||||
if (buildMeta.isDebuggable) {
|
||||
error("You should not use this formatter for this event: $event")
|
||||
error("You should not use this formatter for this event content: $content")
|
||||
}
|
||||
sp.getString(CommonStrings.common_unsupported_event)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@ import com.google.common.truth.Truth.assertWithMessage
|
|||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
import io.element.android.libraries.matrix.api.media.ImageInfo
|
||||
import io.element.android.libraries.matrix.api.media.MediaSource
|
||||
import io.element.android.libraries.matrix.api.timeline.item.EventThreadInfo
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.AudioMessageType
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.EmoteMessageType
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.EventContent
|
||||
|
|
@ -175,7 +174,7 @@ class DefaultBaseRoomLastMessageFormatterTest {
|
|||
) {
|
||||
val body = "Shared body"
|
||||
fun createMessageContent(type: MessageType): MessageContent {
|
||||
return MessageContent(body, null, false, EventThreadInfo(null, null), type)
|
||||
return MessageContent(body, null, false, null, type)
|
||||
}
|
||||
|
||||
val sharedContentMessagesTypes = arrayOf(
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@ import com.google.common.truth.Truth.assertWithMessage
|
|||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
import io.element.android.libraries.matrix.api.media.ImageInfo
|
||||
import io.element.android.libraries.matrix.api.media.MediaSource
|
||||
import io.element.android.libraries.matrix.api.timeline.item.EventThreadInfo
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.AudioMessageType
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.EmoteMessageType
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.EventContent
|
||||
|
|
@ -130,7 +129,7 @@ class DefaultPinnedMessagesBannerFormatterTest {
|
|||
fun `Message contents`() {
|
||||
val body = "Shared body"
|
||||
fun createMessageContent(type: MessageType): MessageContent {
|
||||
return MessageContent(body, null, false, EventThreadInfo(null, null), type)
|
||||
return MessageContent(body, null, false, null, type)
|
||||
}
|
||||
|
||||
val sharedContentMessagesTypes = arrayOf(
|
||||
|
|
|
|||
|
|
@ -14,10 +14,10 @@ import io.element.android.libraries.matrix.api.timeline.item.event.EventContent
|
|||
import io.element.android.libraries.matrix.api.timeline.item.event.EventOrTransactionId
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.ProfileTimelineDetails
|
||||
|
||||
data class EventThreadInfo(
|
||||
val threadRootId: ThreadId?,
|
||||
val threadSummary: ThreadSummary?,
|
||||
)
|
||||
sealed interface EventThreadInfo {
|
||||
data class ThreadRoot(val summary: ThreadSummary) : EventThreadInfo
|
||||
data class ThreadResponse(val threadRootId: ThreadId) : EventThreadInfo
|
||||
}
|
||||
|
||||
data class ThreadSummary(
|
||||
val latestEvent: AsyncData<EmbeddedEventInfo>,
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ data class MessageContent(
|
|||
val body: String,
|
||||
val inReplyTo: InReplyTo?,
|
||||
val isEdited: Boolean,
|
||||
val threadInfo: EventThreadInfo,
|
||||
val threadInfo: EventThreadInfo?,
|
||||
val type: MessageType
|
||||
) : EventContent
|
||||
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ private const val MSG_TYPE_GALLERY_UNSTABLE = "dm.filament.gallery"
|
|||
class EventMessageMapper {
|
||||
private val inReplyToMapper by lazy { InReplyToMapper(TimelineEventContentMapper()) }
|
||||
|
||||
fun map(message: MsgLikeKind.Message, inReplyTo: InReplyToDetails?, threadInfo: EventThreadInfo): MessageContent = message.use {
|
||||
fun map(message: MsgLikeKind.Message, inReplyTo: InReplyToDetails?, threadInfo: EventThreadInfo?): MessageContent = message.use {
|
||||
val type = it.content.msgType.use(this::mapMessageType)
|
||||
val inReplyToEvent: InReplyTo? = inReplyTo?.use(inReplyToMapper::map)
|
||||
MessageContent(
|
||||
|
|
|
|||
|
|
@ -79,7 +79,7 @@ class TimelineEventContentMapper(
|
|||
content = map(latestEvent.content),
|
||||
senderId = UserId(latestEvent.sender),
|
||||
senderProfile = latestEvent.senderProfile.map(),
|
||||
timestamp = latestEvent.timestamp.toLong()
|
||||
timestamp = latestEvent.timestamp.toLong(),
|
||||
)
|
||||
)
|
||||
}
|
||||
|
|
@ -89,10 +89,12 @@ class TimelineEventContentMapper(
|
|||
numberOfReplies = numberOfReplies,
|
||||
)
|
||||
}
|
||||
val threadInfo = EventThreadInfo(
|
||||
threadRootId = it.content.threadRoot?.let(::ThreadId),
|
||||
threadSummary = threadSummary,
|
||||
)
|
||||
val threadRootId = it.content.threadRoot?.let(::ThreadId)
|
||||
val threadInfo = when {
|
||||
threadSummary != null -> EventThreadInfo.ThreadRoot(threadSummary)
|
||||
threadRootId != null -> EventThreadInfo.ThreadResponse(threadRootId)
|
||||
else -> null
|
||||
}
|
||||
eventMessageMapper.map(kind, inReplyTo, threadInfo)
|
||||
}
|
||||
is MsgLikeKind.Redacted -> {
|
||||
|
|
|
|||
|
|
@ -104,7 +104,7 @@ fun aMessageContent(
|
|||
body: String = "body",
|
||||
inReplyTo: InReplyTo? = null,
|
||||
isEdited: Boolean = false,
|
||||
threadInfo: EventThreadInfo = EventThreadInfo(threadRootId = null, threadSummary = null),
|
||||
threadInfo: EventThreadInfo? = null,
|
||||
messageType: MessageType = TextMessageType(
|
||||
body = body,
|
||||
formatted = null
|
||||
|
|
|
|||
|
|
@ -134,10 +134,7 @@ class InReplyToDetailsOtherProvider : InReplyToDetailsProvider() {
|
|||
private fun aMessageContent(
|
||||
body: String,
|
||||
type: MessageType,
|
||||
threadInfo: EventThreadInfo = EventThreadInfo(
|
||||
threadRootId = null,
|
||||
threadSummary = null,
|
||||
),
|
||||
threadInfo: EventThreadInfo? = null,
|
||||
) = MessageContent(
|
||||
body = body,
|
||||
inReplyTo = null,
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@
|
|||
package io.element.android.libraries.matrix.ui.messages.reply
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.libraries.matrix.api.timeline.item.EventThreadInfo
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.FormattedBody
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.InReplyTo
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.MembershipChange
|
||||
|
|
@ -70,7 +69,7 @@ class InReplyToDetailTest {
|
|||
body = "**Hello!**",
|
||||
inReplyTo = null,
|
||||
isEdited = false,
|
||||
threadInfo = EventThreadInfo(threadRootId = null, threadSummary = null),
|
||||
threadInfo = null,
|
||||
type = TextMessageType(
|
||||
body = "**Hello!**",
|
||||
formatted = FormattedBody(
|
||||
|
|
@ -95,7 +94,7 @@ class InReplyToDetailTest {
|
|||
body = "**Hello!**",
|
||||
inReplyTo = null,
|
||||
isEdited = false,
|
||||
threadInfo = EventThreadInfo(threadRootId = null, threadSummary = null),
|
||||
threadInfo = null,
|
||||
type = TextMessageType(
|
||||
body = "**Hello!**",
|
||||
formatted = null,
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ package io.element.android.libraries.textcomposer.model
|
|||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import io.element.android.libraries.matrix.api.core.EventId
|
||||
import io.element.android.libraries.matrix.api.timeline.item.EventThreadInfo
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.EventOrTransactionId
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.MessageContent
|
||||
import io.element.android.libraries.matrix.ui.messages.reply.InReplyToDetails
|
||||
|
|
@ -49,7 +50,7 @@ sealed interface MessageComposerMode {
|
|||
get() = this is Reply &&
|
||||
replyToDetails is InReplyToDetails.Ready &&
|
||||
replyToDetails.eventContent is MessageContent &&
|
||||
(replyToDetails.eventContent as MessageContent).threadInfo.threadRootId != null
|
||||
(replyToDetails.eventContent as MessageContent).threadInfo is EventThreadInfo.ThreadResponse
|
||||
}
|
||||
|
||||
fun MessageComposerMode.showCaptionCompatibilityWarning(): Boolean {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue