diff --git a/changelog.d/1497.feature b/changelog.d/1497.feature new file mode 100644 index 0000000000..04c256dc18 --- /dev/null +++ b/changelog.d/1497.feature @@ -0,0 +1 @@ +Improve rendering of m.emote. diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentFactory.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentFactory.kt index 6a74fd11a5..55e889f496 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentFactory.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentFactory.kt @@ -24,6 +24,7 @@ import io.element.android.libraries.matrix.api.timeline.item.event.FailedToParse import io.element.android.libraries.matrix.api.timeline.item.event.MessageContent import io.element.android.libraries.matrix.api.timeline.item.event.PollContent import io.element.android.libraries.matrix.api.timeline.item.event.ProfileChangeContent +import io.element.android.libraries.matrix.api.timeline.item.event.ProfileTimelineDetails import io.element.android.libraries.matrix.api.timeline.item.event.RedactedContent import io.element.android.libraries.matrix.api.timeline.item.event.RoomMembershipContent import io.element.android.libraries.matrix.api.timeline.item.event.StateContent @@ -49,7 +50,10 @@ class TimelineItemContentFactory @Inject constructor( return when (val itemContent = eventTimelineItem.content) { is FailedToParseMessageLikeContent -> failedToParseMessageFactory.create(itemContent) is FailedToParseStateContent -> failedToParseStateFactory.create(itemContent) - is MessageContent -> messageFactory.create(itemContent) + is MessageContent -> { + val senderDisplayName = (eventTimelineItem.senderProfile as? ProfileTimelineDetails.Ready)?.displayName ?: eventTimelineItem.sender.value + messageFactory.create(itemContent, senderDisplayName) + } is ProfileChangeContent -> profileChangeFactory.create(eventTimelineItem) is RedactedContent -> redactedMessageFactory.create(itemContent) is RoomMembershipContent -> roomMembershipFactory.create(eventTimelineItem) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactory.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactory.kt index 3d0dc4d80c..8e6561543c 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactory.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactory.kt @@ -39,6 +39,7 @@ import io.element.android.libraries.matrix.api.timeline.item.event.LocationMessa import io.element.android.libraries.matrix.api.timeline.item.event.MessageContent import io.element.android.libraries.matrix.api.timeline.item.event.NoticeMessageType import io.element.android.libraries.matrix.api.timeline.item.event.TextMessageType +import io.element.android.libraries.matrix.api.timeline.item.event.UnknownMessageType import io.element.android.libraries.matrix.api.timeline.item.event.VideoMessageType import javax.inject.Inject @@ -47,11 +48,11 @@ class TimelineItemContentMessageFactory @Inject constructor( private val fileExtensionExtractor: FileExtensionExtractor, ) { - fun create(content: MessageContent): TimelineItemEventContent { - return when (val messageType = content.type) { + fun create(content: MessageContent, senderDisplayName: String): TimelineItemEventContent { + return when (val messageType = content.type ?: UnknownMessageType) { is EmoteMessageType -> TimelineItemEmoteContent( - body = messageType.body, - htmlDocument = messageType.formatted?.toHtmlDocument(), + body = "* $senderDisplayName ${messageType.body}", + htmlDocument = messageType.formatted?.toHtmlDocument(prefix = "* senderDisplayName"), isEdited = content.isEdited, ) is ImageMessageType -> { @@ -130,7 +131,7 @@ class TimelineItemContentMessageFactory @Inject constructor( htmlDocument = messageType.formatted?.toHtmlDocument(), isEdited = content.isEdited, ) - else -> TimelineItemUnknownContent + UnknownMessageType -> TimelineItemUnknownContent } } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/util/toHtmlDocument.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/util/toHtmlDocument.kt index 8031e77a4d..a38b631eb2 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/util/toHtmlDocument.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/util/toHtmlDocument.kt @@ -21,8 +21,12 @@ import io.element.android.libraries.matrix.api.timeline.item.event.MessageFormat import org.jsoup.Jsoup import org.jsoup.nodes.Document -fun FormattedBody.toHtmlDocument(): Document? { +fun FormattedBody.toHtmlDocument(prefix: String? = null): Document? { return takeIf { it.format == MessageFormat.HTML }?.body?.let { formattedBody -> - Jsoup.parse(formattedBody) + if (prefix != null) { + Jsoup.parse("$prefix $formattedBody") + } else { + Jsoup.parse(formattedBody) + } } } diff --git a/libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/DefaultRoomLastMessageFormatter.kt b/libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/DefaultRoomLastMessageFormatter.kt index 7e8a93ffe7..70ec625fe9 100644 --- a/libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/DefaultRoomLastMessageFormatter.kt +++ b/libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/DefaultRoomLastMessageFormatter.kt @@ -111,7 +111,7 @@ class DefaultRoomLastMessageFormatter @Inject constructor( val internalMessage = when (messageType) { // Doesn't need a prefix is EmoteMessageType -> { - return "- $senderDisplayName ${messageType.body}" + return "* $senderDisplayName ${messageType.body}" } is TextMessageType -> { messageType.body diff --git a/libraries/eventformatter/impl/src/test/kotlin/io/element/android/libraries/eventformatter/impl/DefaultRoomLastMessageFormatterTests.kt b/libraries/eventformatter/impl/src/test/kotlin/io/element/android/libraries/eventformatter/impl/DefaultRoomLastMessageFormatterTests.kt index 679f9ce891..b66b044a66 100644 --- a/libraries/eventformatter/impl/src/test/kotlin/io/element/android/libraries/eventformatter/impl/DefaultRoomLastMessageFormatterTests.kt +++ b/libraries/eventformatter/impl/src/test/kotlin/io/element/android/libraries/eventformatter/impl/DefaultRoomLastMessageFormatterTests.kt @@ -201,7 +201,7 @@ class DefaultRoomLastMessageFormatterTests { is ImageMessageType -> "Image" is FileMessageType -> "File" is LocationMessageType -> "Shared location" - is EmoteMessageType -> "- $senderName ${type.body}" + is EmoteMessageType -> "* $senderName ${type.body}" is TextMessageType, is NoticeMessageType -> body UnknownMessageType -> "Unsupported event" } @@ -217,7 +217,7 @@ class DefaultRoomLastMessageFormatterTests { is ImageMessageType -> "$senderName: Image" is FileMessageType -> "$senderName: File" is LocationMessageType -> "$senderName: Shared location" - is EmoteMessageType -> "- $senderName ${type.body}" + is EmoteMessageType -> "* $senderName ${type.body}" is TextMessageType, is NoticeMessageType -> "$senderName: $body" UnknownMessageType -> "$senderName: Unsupported event" } diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/EventContent.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/EventContent.kt index a3edf80bee..85b7d4b66a 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/EventContent.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/EventContent.kt @@ -63,7 +63,7 @@ sealed interface InReplyTo { data object Error : InReplyTo } -object RedactedContent : EventContent +data object RedactedContent : EventContent data class StickerContent( val body: String, @@ -124,11 +124,11 @@ data class FailedToParseStateContent( val error: String ) : EventContent -object UnknownContent : EventContent +data object UnknownContent : EventContent sealed interface MessageType -object UnknownMessageType : MessageType +data object UnknownMessageType : MessageType enum class MessageFormat { HTML, UNKNOWN diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotifiableEventResolver.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotifiableEventResolver.kt index a04707be2b..113a323611 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotifiableEventResolver.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotifiableEventResolver.kt @@ -87,7 +87,7 @@ class NotifiableEventResolver @Inject constructor( noisy = isNoisy, timestamp = this.timestamp, senderName = senderDisplayName, - body = descriptionFromMessageContent(content), + body = descriptionFromMessageContent(content, senderDisplayName ?: content.senderId.value), imageUriString = this.contentUrl, roomName = roomDisplayName, roomIsDirect = isDirect, @@ -133,7 +133,7 @@ class NotifiableEventResolver @Inject constructor( NotificationContent.MessageLike.KeyVerificationStart -> null.also { Timber.tag(loggerTag.value).d("Ignoring notification for verification ${content.javaClass.simpleName}") } - is NotificationContent.MessageLike.Poll -> { + is NotificationContent.MessageLike.Poll -> { buildNotifiableMessageEvent( sessionId = userId, senderId = content.senderId, @@ -205,10 +205,11 @@ class NotifiableEventResolver @Inject constructor( private fun descriptionFromMessageContent( content: NotificationContent.MessageLike.RoomMessage, + senderDisplayName: String, ): String { return when (val messageType = content.messageType) { is AudioMessageType -> messageType.body - is EmoteMessageType -> messageType.body + is EmoteMessageType -> "* $senderDisplayName ${messageType.body}" is FileMessageType -> messageType.body is ImageMessageType -> messageType.body is NoticeMessageType -> messageType.body