Repository: element-hq/element-x-android (language: kotlin)

Issue:
URLs are not clickable in media captions
### Steps to reproduce

1. Send a message with a media Captain
2. Make sure the caption contains a URL (https://...) somewhere
3. Observe issue

### Outcome

#### What did you expect?

URLs should be highlighted and clickable, as they are in normal text bodies

#### What happened instead?

The text is entirely plain, unclickable, without an underline nor is it blue 


### Your phone model

(Xiaomi) Poco F3

### Operating system version

Android 14

### Application version and app store

Element X 0.7.4 Nightly, b097e963

### Homeserver

bark.lgbt

### Will you send logs?

Yes

### Are you willing to provide a PR?

No


Discussion hints:
Duplicate of #3529 (although this issue is better worded)
> Duplicate of #3529 (although this issue is better worded)

oh I didn't catch that as I searched for "URL" not something like "link" :woozy_face: 

Pull request: Use formatted captions for images and video
## Content

This PR has the following changes:

- Parse the `caption` and `formattedCaption` in the same way as we do for text messages.
- Allow opening links from the caption UI text component.
- Split the `onClick` action for timeline items between those whose onClick action should be triggered by the whole message bubble and those who should trigger it only from certain UI elements (as the image/video preview in the timeline).

Note there's no way to send captions as either markdown or HTML at the moment from the app, so either you send these from some other client or you'd only be able to test this by sending a URL as part of the plain text caption.

This PR can be reviewed commit-by-commit.

## Motivation and context

Fixes #3850.

## Screenshots / GIFs

No changes in screenshots, I hope.

## Tests

<!-- Explain how you tested your development -->

- Send an image/video with an URL in the caption and some other text.
- Make sure the link in the caption is later clickable in the timeline item.
- Clicking on the image/video preview should open the viewer.
- Clicking on the text part that has no URL shouldn't do anything.
- Clicking on the URL should open it.
- Clicking on any other timeline items should have the same effect as before this patch.

## Tested devices

- [x] Physical
- [ ] Emulator
- OS version(s): 14

## Checklist

<!-- Depending on the Pull Request content, it can be acceptable if some of the following checkboxes stay unchecked. -->

- [ ] Changes have been tested on an Android device or Android emulator with API 23
- [ ] UI change has been tested on both light and dark themes
- [ ] Accessibility has been taken into account. See https://github.com/element-hq/element-x-android/blob/develop/CONTRIBUTING.md#accessibility
- [x] Pull request is based on the develop branch
- [x] Pull request title will be used in the release note, it clearly define what will change for the user
- [ ] Pull request includes screenshots or videos if containing UI changes
- [ ] Pull request includes a [sign off](https://matrix-org.github.io/synapse/latest/development/contributing_guide.html#sign-off)
- [x] You've made a self review of your PR


Test command: ./gradlew :features:messages:impl:testDebugUnitTest --tests "io.element.android.features.messages.impl.timeline.factories.event.TimelineItemContentMessageFactoryTest" --tests "io.element.android.features.messages.impl.timeline.TimelineViewTest" --tests "io.element.android.features.messages.impl.MessagesViewTest"

Fix patch:
diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesNode.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesNode.kt
index bf76f208e38..9fd823445f8 100644
--- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesNode.kt
+++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesNode.kt
@@ -212,7 +212,7 @@ class MessagesNode @AssistedInject constructor(
                 state = state,
                 onBackClick = this::navigateUp,
                 onRoomDetailsClick = this::onRoomDetailsClick,
-                onEventClick = this::onEventClick,
+                onEventContentClick = this::onEventClick,
                 onPreviewAttachments = this::onPreviewAttachments,
                 onUserDataClick = this::onUserDataClick,
                 onLinkClick = { url -> onLinkClick(activity, isDark, url, state.timelineState.eventSink) },
diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt
index e5d73fbed60..3e2918b3ceb 100644
--- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt
+++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt
@@ -114,7 +114,7 @@ fun MessagesView(
     state: MessagesState,
     onBackClick: () -> Unit,
     onRoomDetailsClick: () -> Unit,
-    onEventClick: (event: TimelineItem.Event) -> Boolean,
+    onEventContentClick: (event: TimelineItem.Event) -> Boolean,
     onUserDataClick: (UserId) -> Unit,
     onLinkClick: (String) -> Unit,
     onPreviewAttachments: (ImmutableList<Attachment>) -> Unit,
@@ -142,9 +142,9 @@ fun MessagesView(
     // This is needed because the composer is inside an AndroidView that can't be affected by the FocusManager in Compose
     val localView = LocalView.current
 
-    fun onMessageClick(event: TimelineItem.Event) {
+    fun onContentClick(event: TimelineItem.Event) {
         Timber.v("onMessageClick= ${event.id}")
-        val hideKeyboard = onEventClick(event)
+        val hideKeyboard = onEventContentClick(event)
         if (hideKeyboard) {
             localView.hideKeyboard()
         }
@@ -206,7 +206,7 @@ fun MessagesView(
                 modifier = Modifier
                     .padding(padding)
                     .consumeWindowInsets(padding),
-                onMessageClick = ::onMessageClick,
+                onContentClick = ::onContentClick,
                 onMessageLongClick = ::onMessageLongClick,
                 onUserDataClick = onUserDataClick,
                 onLinkClick = onLinkClick,
@@ -306,7 +306,7 @@ private fun AttachmentStateView(
 @Composable
 private fun MessagesViewContent(
     state: MessagesState,
-    onMessageClick: (TimelineItem.Event) -> Unit,
+    onContentClick: (TimelineItem.Event) -> Unit,
     onUserDataClick: (UserId) -> Unit,
     onLinkClick: (String) -> Unit,
     onReactionClick: (key: String, TimelineItem.Event) -> Unit,
@@ -382,7 +382,7 @@ private fun MessagesViewContent(
                         timelineProtectionState = state.timelineProtectionState,
                         onUserDataClick = onUserDataClick,
                         onLinkClick = onLinkClick,
-                        onMessageClick = onMessageClick,
+                        onContentClick = onContentClick,
                         onMessageLongClick = onMessageLongClick,
                         onSwipeToReply = onSwipeToReply,
                         onReactionClick = onReactionClick,
@@ -568,7 +568,7 @@ internal fun MessagesViewPreview(@PreviewParameter(MessagesStateProvider::class)
         state = state,
         onBackClick = {},
         onRoomDetailsClick = {},
-        onEventClick = { false },
+        onEventContentClick = { false },
         onUserDataClick = {},
         onLinkClick = {},
         onPreviewAttachments = {},
diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/identity/MessagesViewWithIdentityChangePreview.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/identity/MessagesViewWithIdentityChangePreview.kt
index c34f072c6d0..04750b6ad86 100644
--- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/identity/MessagesViewWithIdentityChangePreview.kt
+++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/identity/MessagesViewWithIdentityChangePreview.kt
@@ -33,7 +33,7 @@ internal fun MessagesViewWithIdentityChangePreview(
         ),
         onBackClick = {},
         onRoomDetailsClick = {},
-        onEventClick = { false },
+        onEventContentClick = { false },
         onUserDataClick = {},
         onLinkClick = {},
         onPreviewAttachments = {},
diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListView.kt
index f4a3247cbab..7ae88b9647e 100644
--- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListView.kt
+++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListView.kt
@@ -216,7 +216,7 @@ private fun PinnedMessagesListLoaded(
                 focusedEventId = null,
                 onUserDataClick = onUserDataClick,
                 onLinkClick = onLinkClick,
-                onClick = onEventClick,
+                onContentClick = onEventClick,
                 onLongClick = ::onMessageLongClick,
                 inReplyToClick = {},
                 onReactionClick = { _, _ -> },
@@ -230,6 +230,7 @@ private fun PinnedMessagesListLoaded(
                     TimelineItemEventContentViewWrapper(
                         event = event,
                         timelineProtectionState = state.timelineProtectionState,
+                        onContentClick = { onEventClick(event) },
                         onLinkClick = onLinkClick,
                         modifier = contentModifier,
                         onContentLayoutChange = onContentLayoutChange
@@ -244,6 +245,7 @@ private fun PinnedMessagesListLoaded(
 private fun TimelineItemEventContentViewWrapper(
     event: TimelineItem.Event,
     timelineProtectionState: TimelineProtectionState,
+    onContentClick: () -> Unit,
     onLinkClick: (String) -> Unit,
     onContentLayoutChange: (ContentAvoidingLayoutData) -> Unit,
     modifier: Modifier = Modifier,
@@ -258,10 +260,11 @@ private fun TimelineItemEventContentViewWrapper(
         TimelineItemEventContentView(
             content = event.content,
             hideMediaContent = timelineProtectionState.hideMediaContent(event.eventId),
-            onShowClick = { timelineProtectionState.eventSink(TimelineProtectionEvent.ShowContent(event.eventId)) },
+            onShowContentClick = { timelineProtectionState.eventSink(TimelineProtectionEvent.ShowContent(event.eventId)) },
             onLinkClick = onLinkClick,
             eventSink = { },
             modifier = modifier,
+            onContentClick = onContentClick,
             onContentLayoutChange = onContentLayoutChange
         )
     }
diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineView.kt
index f0e976e3685..6e34bba9e1a 100644
--- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineView.kt
+++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineView.kt
@@ -76,7 +76,7 @@ fun TimelineView(
     timelineProtectionState: TimelineProtectionState,
     onUserDataClick: (UserId) -> Unit,
     onLinkClick: (String) -> Unit,
-    onMessageClick: (TimelineItem.Event) -> Unit,
+    onContentClick: (TimelineItem.Event) -> Unit,
     onMessageLongClick: (TimelineItem.Event) -> Unit,
     onSwipeToReply: (TimelineItem.Event) -> Unit,
     onReactionClick: (emoji: String, TimelineItem.Event) -> Unit,
@@ -141,7 +141,7 @@ fun TimelineView(
                         focusedEventId = state.focusedEventId,
                         onUserDataClick = onUserDataClick,
                         onLinkClick = onLinkClick,
-                        onClick = onMessageClick,
+                        onContentClick = onContentClick,
                         onLongClick = onMessageLongClick,
                         inReplyToClick = ::inReplyToClick,
                         onReactionClick = onReactionClick,
@@ -322,7 +322,7 @@ internal fun TimelineViewPreview(
             timelineProtectionState = aTimelineProtectionState(),
             onUserDataClick = {},
             onLinkClick = {},
-            onMessageClick = {},
+            onContentClick = {},
             onMessageLongClick = {},
             onSwipeToReply = {},
             onReactionClick = { _, _ -> },
diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineViewMessageShieldPreview.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineViewMessageShieldPreview.kt
index 99f69675fa1..462e74137af 100644
--- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineViewMessageShieldPreview.kt
+++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineViewMessageShieldPreview.kt
@@ -41,7 +41,7 @@ internal fun TimelineViewMessageShieldPreview() = ElementPreview {
             timelineProtectionState = aTimelineProtectionState(),
             onUserDataClick = {},
             onLinkClick = {},
-            onMessageClick = {},
+            onContentClick = {},
             onMessageLongClick = {},
             onSwipeToReply = {},
             onReactionClick = { _, _ -> },
diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/ATimelineItemEventRow.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/ATimelineItemEventRow.kt
index 1fa5e7f9a1b..85e98549194 100644
--- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/ATimelineItemEventRow.kt
+++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/ATimelineItemEventRow.kt
@@ -30,7 +30,7 @@ internal fun ATimelineItemEventRow(
     timelineProtectionState = timelineProtectionState,
     isLastOutgoingMessage = isLastOutgoingMessage,
     isHighlighted = isHighlighted,
-    onClick = {},
+    onContentClick = {},
     onLongClick = {},
     onLinkClick = {},
     onUserDataClick = {},
diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRow.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRow.kt
index 2f0165cd7b2..0061ebe6dff 100644
--- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRow.kt
+++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRow.kt
@@ -114,7 +114,7 @@ fun TimelineItemEventRow(
     renderReadReceipts: Boolean,
     isLastOutgoingMessage: Boolean,
     isHighlighted: Boolean,
-    onClick: () -> Unit,
+    onContentClick: () -> Unit,
     onLongClick: () -> Unit,
     onLinkClick: (String) -> Unit,
     onUserDataClick: (UserId) -> Unit,
@@ -130,7 +130,8 @@ fun TimelineItemEventRow(
         TimelineItemEventContentView(
             content = event.content,
             hideMediaContent = timelineProtectionState.hideMediaContent(event.eventId),
-            onShowClick = { timelineProtectionState.eventSink(TimelineProtectionEvent.ShowContent(event.eventId)) },
+            onContentClick = onContentClick,
+            onShowContentClick = { timelineProtectionState.eventSink(TimelineProtectionEvent.ShowContent(event.eventId)) },
             onLinkClick = onLinkClick,
             eventSink = eventSink,
             modifier = contentModifier,
@@ -150,6 +151,12 @@ fun TimelineItemEventRow(
         inReplyToClick(inReplyToEventId)
     }
 
+    val onWholeItemClick = if (event.isWholeContentClickable) {
+        onContentClick
+    } else {
+        {}
+    }
+
     Column(modifier = modifier.fillMaxWidth()) {
         if (event.groupPosition.isNew()) {
             Spacer(modifier = Modifier.height(16.dp))
@@ -173,7 +180,7 @@ fun TimelineItemEventRow(
                         isHighlighted = isHighlighted,
                         timelineRoomInfo = timelineRoomInfo,
                         interactionSource = interactionSource,
-                        onClick = onClick,
+                        onContentClick = onWholeItemClick,
                         onLongClick = onLongClick,
                         inReplyToClick = ::inReplyToClick,
                         onUserDataClick = ::onUserDataClick,
@@ -207,7 +214,7 @@ fun TimelineItemEventRow(
                 isHighlighted = isHighlighted,
                 timelineRoomInfo = timelineRoomInfo,
                 interactionSource = interactionSource,
-                onClick = onClick,
+                onContentClick = onWholeItemClick,
                 onLongClick = onLongClick,
                 inReplyToClick = ::inReplyToClick,
                 onUserDataClick = ::onUserDataClick,
@@ -263,7 +270,7 @@ private fun TimelineItemEventRowContent(
     isHighlighted: Boolean,
     timelineRoomInfo: TimelineRoomInfo,
     interactionSource: MutableInteractionSource,
-    onClick: () -> Unit,
+    onContentClick: () -> Unit,
     onLongClick: () -> Unit,
     inReplyToClick: () -> Unit,
     onUserDataClick: () -> Unit,
@@ -340,7 +347,7 @@ private fun TimelineItemEventRowContent(
                 },
             state = bubbleState,
             interactionSource = interactionSource,
-            onClick = onClick,
+            onClick = onContentClick,
             onLongClick = onLongClick,
         ) {
             MessageEventBubbleContent(
diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemGroupedEventsRow.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemGroupedEventsRow.kt
index d280efa62c8..fe89d6de022 100644
--- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemGroupedEventsRow.kt
+++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemGroupedEventsRow.kt
@@ -57,10 +57,11 @@ fun TimelineItemGroupedEventsRow(
             TimelineItemEventContentView(
                 content = event.content,
                 hideMediaContent = timelineProtectionState.hideMediaContent(event.eventId),
-                onShowClick = { timelineProtectionState.eventSink(TimelineProtectionEvent.ShowContent(event.eventId)) },
+                onShowContentClick = { timelineProtectionState.eventSink(TimelineProtectionEvent.ShowContent(event.eventId)) },
                 onLinkClick = onLinkClick,
                 eventSink = eventSink,
                 modifier = contentModifier,
+                onContentClick = {},
                 onContentLayoutChange = onContentLayoutChange
             )
         },
@@ -121,10 +122,11 @@ private fun TimelineItemGroupedEventsRowContent(
             TimelineItemEventContentView(
                 content = event.content,
                 hideMediaContent = timelineProtectionState.hideMediaContent(event.eventId),
-                onShowClick = { timelineProtectionState.eventSink(TimelineProtectionEvent.ShowContent(event.eventId)) },
+                onShowContentClick = { timelineProtectionState.eventSink(TimelineProtectionEvent.ShowContent(event.eventId)) },
                 onLinkClick = onLinkClick,
                 eventSink = eventSink,
                 modifier = contentModifier,
+                onContentClick = {},
                 onContentLayoutChange = onContentLayoutChange
             )
         },
@@ -152,7 +154,7 @@ private fun TimelineItemGroupedEventsRowContent(
                         focusedEventId = focusedEventId,
                         onUserDataClick = onUserDataClick,
                         onLinkClick = onLinkClick,
-                        onClick = onClick,
+                        onContentClick = onClick,
                         onLongClick = onLongClick,
                         inReplyToClick = inReplyToClick,
                         onReactionClick = onReactionClick,
diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemRow.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemRow.kt
index 13c247645bc..047f9b3d04a 100644
--- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemRow.kt
+++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemRow.kt
@@ -28,7 +28,6 @@ import io.element.android.features.messages.impl.timeline.model.event.TimelineIt
 import io.element.android.features.messages.impl.timeline.model.event.TimelineItemStateContent
 import io.element.android.features.messages.impl.timeline.protection.TimelineProtectionEvent
 import io.element.android.features.messages.impl.timeline.protection.TimelineProtectionState
-import io.element.android.features.messages.impl.timeline.protection.mustBeProtected
 import io.element.android.libraries.designsystem.text.toPx
 import io.element.android.libraries.designsystem.theme.highlightedMessageBackgroundColor
 import io.element.android.libraries.matrix.api.core.EventId
@@ -44,7 +43,7 @@ internal fun TimelineItemRow(
     focusedEventId: EventId?,
     onUserDataClick: (UserId) -> Unit,
     onLinkClick: (String) -> Unit,
-    onClick: (TimelineItem.Event) -> Unit,
+    onContentClick: (TimelineItem.Event) -> Unit,
     onLongClick: (TimelineItem.Event) -> Unit,
     inReplyToClick: (EventId) -> Unit,
     onReactionClick: (key: String, TimelineItem.Event) -> Unit,
@@ -60,7 +59,8 @@ internal fun TimelineItemRow(
             TimelineItemEventContentView(
                 content = event.content,
                 hideMediaContent = timelineProtectionState.hideMediaContent(event.eventId),
-                onShowClick = { timelineProtectionState.eventSink(TimelineProtectionEvent.ShowContent(event.eventId)) },
+                onShowContentClick = { timelineProtectionState.eventSink(TimelineProtectionEvent.ShowContent(event.eventId)) },
+                onContentClick = { onContentClick(event) },
                 onLinkClick = onLinkClick,
                 eventSink = eventSink,
                 modifier = contentModifier,
@@ -95,7 +95,7 @@ internal fun TimelineItemRow(
                             renderReadReceipts = renderReadReceipts,
                             isLastOutgoingMessage = isLastOutgoingMessage,
                             isHighlighted = timelineItem.isEvent(focusedEventId),
-                            onClick = { onClick(timelineItem) },
+                            onClick = { onContentClick(timelineItem) },
                             onReadReceiptsClick = onReadReceiptClick,
                             onLongClick = { onLongClick(timelineItem) },
                             eventSink = eventSink,
@@ -118,11 +118,7 @@ internal fun TimelineItemRow(
                             timelineProtectionState = timelineProtectionState,
                             isLastOutgoingMessage = isLastOutgoingMessage,
                             isHighlighted = timelineItem.isEvent(focusedEventId),
-                            onClick = if (timelineProtectionState.hideMediaContent(timelineItem.eventId) && timelineItem.mustBeProtected()) {
-                                {}
-                            } else {
-                                { onClick(timelineItem) }
-                            },
+                            onContentClick = { onContentClick(timelineItem) },
                             onLongClick = { onLongClick(timelineItem) },
                             onLinkClick = onLinkClick,
                             onUserDataClick = onUserDataClick,
@@ -148,7 +144,7 @@ internal fun TimelineItemRow(
                     renderReadReceipts = renderReadReceipts,
                     isLastOutgoingMessage = isLastOutgoingMessage,
                     focusedEventId = focusedEventId,
-                    onClick = onClick,
+                    onClick = onContentClick,
                     onLongClick = onLongClick,
                     inReplyToClick = inReplyToClick,
                     onUserDataClick = onUserDataClick,
diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemStateEventRow.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemStateEventRow.kt
index f023a63c359..61726860160 100644
--- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemStateEventRow.kt
+++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemStateEventRow.kt
@@ -72,8 +72,9 @@ fun TimelineItemStateEventRow(
                     content = event.content,
                     onLinkClick = {},
                     hideMediaContent = false,
-                    onShowClick = {},
+                    onShowContentClick = {},
                     eventSink = eventSink,
+                    onContentClick = {},
                     modifier = Modifier.defaultTimelineContentPadding()
                 )
             }
diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemEventContentView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemEventContentView.kt
index b24a5ca0be5..3a3a43a6fc2 100644
--- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemEventContentView.kt
+++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemEventContentView.kt
@@ -36,7 +36,8 @@ import io.element.android.libraries.architecture.Presenter
 fun TimelineItemEventContentView(
     content: TimelineItemEventContent,
     hideMediaContent: Boolean,
-    onShowClick: () -> Unit,
+    onContentClick: () -> Unit,
+    onShowContentClick: () -> Unit,
     onLinkClick: (url: String) -> Unit,
     eventSink: (TimelineEvents.EventFromTimelineItem) -> Unit,
     modifier: Modifier = Modifier,
@@ -67,25 +68,31 @@ fun TimelineItemEventContentView(
         )
         is TimelineItemLocationContent -> TimelineItemLocationView(
             content = content,
+            onContentClick = onContentClick,
             modifier = modifier
         )
         is TimelineItemImageContent -> TimelineItemImageView(
             content = content,
             hideMediaContent = hideMediaContent,
-            onShowClick = onShowClick,
+            onContentClick = onContentClick,
+            onShowContentClick = onShowContentClick,
+            onLinkClick = onLinkClick,
             onContentLayoutChange = onContentLayoutChange,
             modifier = modifier,
         )
         is TimelineItemStickerContent -> TimelineItemStickerView(
             content = content,
             hideMediaContent = hideMediaContent,
-            onShowClick = onShowClick,
+            onContentClick = onContentClick,
+            onShowClick = onShowContentClick,
             modifier = modifier,
         )
         is TimelineItemVideoContent -> TimelineItemVideoView(
             content = content,
             hideMediaContent = hideMediaContent,
-            onShowClick = onShowClick,
+            onContentClick = onContentClick,
+            onShowContentClick = onShowContentClick,
+            onLinkClick = onLinkClick,
             onContentLayoutChange = onContentLayoutChange,
             modifier = modifier
         )
diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemImageView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemImageView.kt
index 63bbac7f6f8..123ba195f1e 100644
--- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemImageView.kt
+++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemImageView.kt
@@ -9,6 +9,7 @@ package io.element.android.features.messages.impl.timeline.components.event
 
 import android.text.SpannedString
 import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.Spacer
 import androidx.compose.foundation.layout.fillMaxWidth
@@ -50,7 +51,6 @@ import io.element.android.features.messages.impl.timeline.protection.ProtectedVi
 import io.element.android.libraries.designsystem.components.blurhash.blurHashBackground
 import io.element.android.libraries.designsystem.preview.ElementPreview
 import io.element.android.libraries.designsystem.preview.PreviewsDayNight
-import io.element.android.libraries.matrix.api.timeline.item.event.MessageFormat
 import io.element.android.libraries.textcomposer.ElementRichTextEditorStyle
 import io.element.android.libraries.ui.strings.CommonStrings
 import io.element.android.wysiwyg.compose.EditorStyledText
@@ -59,7 +59,9 @@ import io.element.android.wysiwyg.compose.EditorStyledText
 fun TimelineItemImageView(
     content: TimelineItemImageContent,
     hideMediaContent: Boolean,
-    onShowClick: () -> Unit,
+    onContentClick: () -> Unit,
+    onLinkClick: (String) -> Unit,
+    onShowContentClick: () -> Unit,
     onContentLayoutChange: (ContentAvoidingLayoutData) -> Unit,
     modifier: Modifier = Modifier,
 ) {
@@ -78,13 +80,14 @@ fun TimelineItemImageView(
         ) {
             ProtectedView(
                 hideContent = hideMediaContent,
-                onShowClick = onShowClick,
+                onShowClick = onShowContentClick,
             ) {
                 var isLoaded by remember { mutableStateOf(false) }
                 AsyncImage(
                     modifier = Modifier
                         .fillMaxWidth()
-                        .then(if (isLoaded) Modifier.background(Color.White) else Modifier),
+                        .then(if (isLoaded) Modifier.background(Color.White) else Modifier)
+                        .clickable(onClick = onContentClick),
                     model = content.thumbnailMediaRequestData,
                     contentScale = ContentScale.Fit,
                     alignment = Alignment.Center,
@@ -99,9 +102,7 @@ fun TimelineItemImageView(
             val caption = if (LocalInspectionMode.current) {
                 SpannedString(content.caption)
             } else {
-                content.formattedCaption?.body
-                    ?.takeIf { content.formattedCaption.format == MessageFormat.HTML }
-                    ?: SpannedString(content.caption)
+                content.formattedCaption ?: SpannedString(content.caption)
             }
             CompositionLocalProvider(
                 LocalContentColor provides ElementTheme.colors.textPrimary,
@@ -114,6 +115,7 @@ fun TimelineItemImageView(
                         .widthIn(min = MIN_HEIGHT_IN_DP.dp * aspectRatio, max = MAX_HEIGHT_IN_DP.dp * aspectRatio),
                     text = caption,
                     style = ElementRichTextEditorStyle.textStyle(),
+                    onLinkClickedListener = onLinkClick,
                     releaseOnDetach = false,
                     onTextLayout = ContentAvoidingLayout.measureLegacyLastTextLine(onContentLayoutChange = onContentLayoutChange),
                 )
@@ -128,7 +130,9 @@ internal fun TimelineItemImageViewPreview(@PreviewParameter(TimelineItemImageCon
     TimelineItemImageView(
         content = content,
         hideMediaContent = false,
-        onShowClick = {},
+        onShowContentClick = {},
+        onContentClick = {},
+        onLinkClick = {},
         onContentLayoutChange = {},
     )
 }
@@ -139,7 +143,9 @@ internal fun TimelineItemImageViewHideMediaContentPreview() = ElementPreview {
     TimelineItemImageView(
         content = aTimelineItemImageContent(),
         hideMediaContent = true,
-        onShowClick = {},
+        onShowContentClick = {},
+        onContentClick = {},
+        onLinkClick = {},
         onContentLayoutChange = {},
     )
 }
diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemLocationView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemLocationView.kt
index 91cfcff1d8f..5d402bc8f16 100644
--- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemLocationView.kt
+++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemLocationView.kt
@@ -7,6 +7,7 @@
 
 package io.element.android.features.messages.impl.timeline.components.event
 
+import androidx.compose.foundation.clickable
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.heightIn
@@ -25,9 +26,10 @@ import io.element.android.libraries.designsystem.theme.components.Text
 @Composable
 fun TimelineItemLocationView(
     content: TimelineItemLocationContent,
+    onContentClick: () -> Unit,
     modifier: Modifier = Modifier,
 ) {
-    Column(modifier = modifier.fillMaxWidth()) {
+    Column(modifier = modifier.clickable(onClick = onContentClick).fillMaxWidth()) {
         content.description?.let {
             Text(
                 text = it,
@@ -51,5 +53,8 @@ fun TimelineItemLocationView(
 @Composable
 internal fun TimelineItemLocationViewPreview(@PreviewParameter(TimelineItemLocationContentProvider::class) content: TimelineItemLocationContent) =
     ElementPreview {
-        TimelineItemLocationView(content)
+        TimelineItemLocationView(
+            content = content,
+            onContentClick = {},
+        )
     }
diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemStickerView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemStickerView.kt
index 0574843dbaf..df087769ed8 100644
--- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemStickerView.kt
+++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemStickerView.kt
@@ -8,6 +8,7 @@
 package io.element.android.features.messages.impl.timeline.components.event
 
 import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.runtime.Composable
@@ -40,6 +41,7 @@ private const val STICKER_SIZE_IN_DP = 128
 fun TimelineItemStickerView(
     content: TimelineItemStickerContent,
     hideMediaContent: Boolean,
+    onContentClick: () -> Unit,
     onShowClick: () -> Unit,
     modifier: Modifier = Modifier,
 ) {
@@ -61,7 +63,8 @@ fun TimelineItemStickerView(
                 AsyncImage(
                     modifier = Modifier
                         .fillMaxSize()
-                        .then(if (isLoaded) Modifier.background(Color.White) else Modifier),
+                        .then(if (isLoaded) Modifier.background(Color.White) else Modifier)
+                        .clickable(onClick = onContentClick),
                     model = MediaRequestData(
                         source = content.preferredMediaSource,
                         kind = MediaRequestData.Kind.File(
@@ -85,6 +88,7 @@ internal fun TimelineItemStickerViewPreview(@PreviewParameter(TimelineItemSticke
     TimelineItemStickerView(
         content = content,
         hideMediaContent = false,
+        onContentClick = {},
         onShowClick = {},
     )
 }
diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemVideoView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemVideoView.kt
index 64e6d00d718..65f59cd631f 100644
--- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemVideoView.kt
+++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemVideoView.kt
@@ -10,6 +10,7 @@ package io.element.android.features.messages.impl.timeline.components.event
 import android.text.SpannedString
 import androidx.compose.foundation.Image
 import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.Spacer
@@ -56,7 +57,6 @@ import io.element.android.libraries.designsystem.components.blurhash.blurHashBac
 import io.element.android.libraries.designsystem.modifiers.roundedBackground
 import io.element.android.libraries.designsystem.preview.ElementPreview
 import io.element.android.libraries.designsystem.preview.PreviewsDayNight
-import io.element.android.libraries.matrix.api.timeline.item.event.MessageFormat
 import io.element.android.libraries.matrix.ui.media.MAX_THUMBNAIL_HEIGHT
 import io.element.android.libraries.matrix.ui.media.MAX_THUMBNAIL_WIDTH
 import io.element.android.libraries.matrix.ui.media.MediaRequestData
@@ -68,7 +68,9 @@ import io.element.android.wysiwyg.compose.EditorStyledText
 fun TimelineItemVideoView(
     content: TimelineItemVideoContent,
     hideMediaContent: Boolean,
-    onShowClick: () -> Unit,
+    onContentClick: () -> Unit,
+    onShowContentClick: () -> Unit,
+    onLinkClick: (String) -> Unit,
     onContentLayoutChange: (ContentAvoidingLayoutData) -> Unit,
     modifier: Modifier = Modifier,
 ) {
@@ -90,13 +92,14 @@ fun TimelineItemVideoView(
         ) {
             ProtectedView(
                 hideContent = hideMediaContent,
-                onShowClick = onShowClick,
+                onShowClick = onShowContentClick,
             ) {
                 var isLoaded by remember { mutableStateOf(false) }
                 AsyncImage(
                     modifier = Modifier
                         .fillMaxWidth()
-                        .then(if (isLoaded) Modifier.background(Color.White) else Modifier),
+                        .then(if (isLoaded) Modifier.background(Color.White) else Modifier)
+                        .clickable(onClick = onContentClick),
                     model = MediaRequestData(
                         source = content.thumbnailSource,
                         kind = MediaRequestData.Kind.Thumbnail(
@@ -128,9 +131,7 @@ fun TimelineItemVideoView(
             val caption = if (LocalInspectionMode.current) {
                 SpannedString(content.caption)
             } else {
-                content.formattedCaption?.body
-                    ?.takeIf { content.formattedCaption.format == MessageFormat.HTML }
-                    ?: SpannedString(content.caption)
+                content.formattedCaption ?: SpannedString(content.caption)
             }
             CompositionLocalProvider(
                 LocalContentColor provides ElementTheme.colors.textPrimary,
@@ -142,6 +143,7 @@ fun TimelineItemVideoView(
                         .padding(horizontal = 4.dp) // This is (12.dp - 8.dp) contentPadding from CommonLayout
                         .widthIn(min = MIN_HEIGHT_IN_DP.dp * aspectRatio, max = MAX_HEIGHT_IN_DP.dp * aspectRatio),
                     text = caption,
+                    onLinkClickedListener = onLinkClick,
                     style = ElementRichTextEditorStyle.textStyle(),
                     releaseOnDetach = false,
                     onTextLayout = ContentAvoidingLayout.measureLegacyLastTextLine(onContentLayoutChange = onContentLayoutChange),
@@ -157,7 +159,9 @@ internal fun TimelineItemVideoViewPreview(@PreviewParameter(TimelineItemVideoCon
     TimelineItemVideoView(
         content = content,
         hideMediaContent = false,
-        onShowClick = {},
+        onShowContentClick = {},
+        onContentClick = {},
+        onLinkClick = {},
         onContentLayoutChange = {},
     )
 }
@@ -168,7 +172,9 @@ internal fun TimelineItemVideoViewHideMediaContentPreview() = ElementPreview {
     TimelineItemVideoView(
         content = aTimelineItemVideoContent(),
         hideMediaContent = true,
-        onShowClick = {},
+        onShowContentClick = {},
+        onContentClick = {},
+        onLinkClick = {},
         onContentLayoutChange = {},
     )
 }
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 001d40b254f..ff5ca57c113 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
@@ -86,7 +86,7 @@ class TimelineItemContentMessageFactory @Inject constructor(
                 TimelineItemImageContent(
                     filename = messageType.filename,
                     caption = messageType.caption?.trimEnd(),
-                    formattedCaption = messageType.formattedCaption,
+                    formattedCaption = parseHtml(messageType.formattedCaption) ?: messageType.caption?.withLinks(),
                     mediaSource = messageType.source,
                     thumbnailSource = messageType.info?.thumbnailSource,
                     mimeType = messageType.info?.mimetype ?: MimeTypes.OctetStream,
@@ -105,7 +105,7 @@ class TimelineItemContentMessageFactory @Inject constructor(
                 TimelineItemStickerContent(
                     filename = messageType.filename,
                     caption = messageType.caption?.trimEnd(),
-                    formattedCaption = messageType.formattedCaption,
+                    formattedCaption = parseHtml(messageType.formattedCaption) ?: messageType.caption?.withLinks(),
                     mediaSource = messageType.source,
                     thumbnailSource = messageType.info?.thumbnailSource,
                     mimeType = messageType.info?.mimetype ?: MimeTypes.OctetStream,
@@ -142,7 +142,7 @@ class TimelineItemContentMessageFactory @Inject constructor(
                 TimelineItemVideoContent(
                     filename = messageType.filename,
                     caption = messageType.caption?.trimEnd(),
-                    formattedCaption = messageType.formattedCaption,
+                    formattedCaption = parseHtml(messageType.formattedCaption) ?: messageType.caption?.withLinks(),
                     thumbnailSource = messageType.info?.thumbnailSource,
                     videoSource = messageType.source,
                     mimeType = messageType.info?.mimetype ?: MimeTypes.OctetStream,
@@ -161,7 +161,7 @@ class TimelineItemContentMessageFactory @Inject constructor(
                 TimelineItemAudioContent(
                     filename = messageType.filename,
                     caption = messageType.caption?.trimEnd(),
-                    formattedCaption = messageType.formattedCaption,
+                    formattedCaption = parseHtml(messageType.formattedCaption) ?: messageType.caption?.withLinks(),
                     mediaSource = messageType.source,
                     duration = messageType.info?.duration ?: Duration.ZERO,
                     mimeType = messageType.info?.mimetype ?: MimeTypes.OctetStream,
@@ -176,7 +176,7 @@ class TimelineItemContentMessageFactory @Inject constructor(
                             eventId = eventId,
                             filename = messageType.filename,
                             caption = messageType.caption?.trimEnd(),
-                            formattedCaption = messageType.formattedCaption,
+                            formattedCaption = parseHtml(messageType.formattedCaption) ?: messageType.caption?.withLinks(),
                             mediaSource = messageType.source,
                             duration = messageType.info?.duration ?: Duration.ZERO,
                             mimeType = messageType.info?.mimetype ?: MimeTypes.OctetStream,
@@ -187,7 +187,7 @@ class TimelineItemContentMessageFactory @Inject constructor(
                         TimelineItemAudioContent(
                             filename = messageType.filename,
                             caption = messageType.caption?.trimEnd(),
-                            formattedCaption = messageType.formattedCaption,
+                            formattedCaption = parseHtml(messageType.formattedCaption) ?: messageType.caption?.withLinks(),
                             mediaSource = messageType.source,
                             duration = messageType.info?.duration ?: Duration.ZERO,
                             mimeType = messageType.info?.mimetype ?: MimeTypes.OctetStream,
@@ -202,7 +202,7 @@ class TimelineItemContentMessageFactory @Inject constructor(
                 TimelineItemFileContent(
                     filename = messageType.filename,
                     caption = messageType.caption?.trimEnd(),
-                    formattedCaption = messageType.formattedCaption,
+                    formattedCaption = parseHtml(messageType.formattedCaption) ?: messageType.caption?.withLinks(),
                     thumbnailSource = messageType.info?.thumbnailSource,
                     fileSource = messageType.source,
                     mimeType = messageType.info?.mimetype ?: MimeTypes.fromFileExtension(fileExtension),
diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/TimelineItem.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/TimelineItem.kt
index dcf0e16aa83..e2c81a3794e 100644
--- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/TimelineItem.kt
+++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/TimelineItem.kt
@@ -9,8 +9,10 @@ package io.element.android.features.messages.impl.timeline.model
 
 import androidx.compose.runtime.Immutable
 import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEventContent
+import io.element.android.features.messages.impl.timeline.model.event.TimelineItemImageContent
 import io.element.android.features.messages.impl.timeline.model.event.TimelineItemStickerContent
 import io.element.android.features.messages.impl.timeline.model.event.TimelineItemTextBasedContent
+import io.element.android.features.messages.impl.timeline.model.event.TimelineItemVideoContent
 import io.element.android.features.messages.impl.timeline.model.virtual.TimelineItemVirtualModel
 import io.element.android.libraries.designsystem.components.avatar.AvatarData
 import io.element.android.libraries.matrix.api.core.EventId
@@ -93,6 +95,17 @@ sealed interface TimelineItem {
 
         val isRemote = eventId != null
 
+        /** Whether a click on any part of the event bubble should trigger the 'onContentClick' callback.
+         *
+         *  This is `true` for all events except for visual media events with a caption or formatted caption.
+         */
+        val isWholeContentClickable = when (content) {
+            is TimelineItemStickerContent -> content.formattedCaption == null && content.caption == null
+            is TimelineItemImageContent -> content.formattedCaption == null && content.caption == null
+            is TimelineItemVideoContent -> content.formattedCaption == null && content.caption == null
+            else -> true
+        }
+
         val eventOrTransactionId: EventOrTransactionId
             get() = EventOrTransactionId.from(eventId = eventId, transactionId = transactionId)
 
diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemAudioContent.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemAudioContent.kt
index fa2957b8604..0ecf45ec55b 100644
--- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemAudioContent.kt
+++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemAudioContent.kt
@@ -8,14 +8,13 @@
 package io.element.android.features.messages.impl.timeline.model.event
 
 import io.element.android.libraries.matrix.api.media.MediaSource
-import io.element.android.libraries.matrix.api.timeline.item.event.FormattedBody
 import io.element.android.libraries.mediaviewer.api.helper.formatFileExtensionAndSize
 import kotlin.time.Duration
 
 data class TimelineItemAudioContent(
     override val filename: String,
     override val caption: String?,
-    override val formattedCaption: FormattedBody?,
+    override val formattedCaption: CharSequence?,
     val duration: Duration,
     val mediaSource: MediaSource,
     val mimeType: String,
diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemEventContent.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemEventContent.kt
index 10204a75d8b..fafadcbf79d 100644
--- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemEventContent.kt
+++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemEventContent.kt
@@ -8,7 +8,6 @@
 package io.element.android.features.messages.impl.timeline.model.event
 
 import androidx.compose.runtime.Immutable
-import io.element.android.libraries.matrix.api.timeline.item.event.FormattedBody
 
 @Immutable
 sealed interface TimelineItemEventContent {
@@ -19,7 +18,7 @@ sealed interface TimelineItemEventContent {
 sealed interface TimelineItemEventContentWithAttachment : TimelineItemEventContent {
     val filename: String
     val caption: String?
-    val formattedCaption: FormattedBody?
+    val formattedCaption: CharSequence?
 
     val bestDescription: String
         get() = caption ?: filename
diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemFileContent.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemFileContent.kt
index f11325727cc..b7007c8bbca 100644
--- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemFileContent.kt
+++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemFileContent.kt
@@ -8,13 +8,12 @@
 package io.element.android.features.messages.impl.timeline.model.event
 
 import io.element.android.libraries.matrix.api.media.MediaSource
-import io.element.android.libraries.matrix.api.timeline.item.event.FormattedBody
 import io.element.android.libraries.mediaviewer.api.helper.formatFileExtensionAndSize
 
 data class TimelineItemFileContent(
     override val filename: String,
     override val caption: String?,
-    override val formattedCaption: FormattedBody?,
+    override val formattedCaption: CharSequence?,
     val fileSource: MediaSource,
     val thumbnailSource: MediaSource?,
     val formattedFileSize: String,
diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemImageContent.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemImageContent.kt
index e6e4bffb9b2..b13c7a4e377 100644
--- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemImageContent.kt
+++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemImageContent.kt
@@ -9,7 +9,6 @@ package io.element.android.features.messages.impl.timeline.model.event
 
 import io.element.android.libraries.core.mimetype.MimeTypes.isMimeTypeAnimatedImage
 import io.element.android.libraries.matrix.api.media.MediaSource
-import io.element.android.libraries.matrix.api.timeline.item.event.FormattedBody
 import io.element.android.libraries.matrix.ui.media.MAX_THUMBNAIL_HEIGHT
 import io.element.android.libraries.matrix.ui.media.MAX_THUMBNAIL_WIDTH
 import io.element.android.libraries.matrix.ui.media.MediaRequestData
@@ -17,7 +16,7 @@ import io.element.android.libraries.matrix.ui.media.MediaRequestData
 data class TimelineItemImageContent(
     override val filename: String,
     override val caption: String?,
-    override val formattedCaption: FormattedBody?,
+    override val formattedCaption: CharSequence?,
     val mediaSource: MediaSource,
     val thumbnailSource: MediaSource?,
     val formattedFileSize: String,
diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemStickerContent.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemStickerContent.kt
index 3476d97e2fc..06886307ae6 100644
--- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemStickerContent.kt
+++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemStickerContent.kt
@@ -8,12 +8,11 @@
 package io.element.android.features.messages.impl.timeline.model.event
 
 import io.element.android.libraries.matrix.api.media.MediaSource
-import io.element.android.libraries.matrix.api.timeline.item.event.FormattedBody
 
 data class TimelineItemStickerContent(
     override val filename: String,
     override val caption: String?,
-    override val formattedCaption: FormattedBody?,
+    override val formattedCaption: CharSequence?,
     val mediaSource: MediaSource,
     val thumbnailSource: MediaSource?,
     val formattedFileSize: String,
diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemVideoContent.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemVideoContent.kt
index 5c0e601708d..486a71b5d43 100644
--- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemVideoContent.kt
+++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemVideoContent.kt
@@ -8,13 +8,12 @@
 package io.element.android.features.messages.impl.timeline.model.event
 
 import io.element.android.libraries.matrix.api.media.MediaSource
-import io.element.android.libraries.matrix.api.timeline.item.event.FormattedBody
 import kotlin.time.Duration
 
 data class TimelineItemVideoContent(
     override val filename: String,
     override val caption: String?,
-    override val formattedCaption: FormattedBody?,
+    override val formattedCaption: CharSequence?,
     val duration: Duration,
     val videoSource: MediaSource,
     val thumbnailSource: MediaSource?,
diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemVoiceContent.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemVoiceContent.kt
index 853cc01045f..ebeabef7150 100644
--- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemVoiceContent.kt
+++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemVoiceContent.kt
@@ -9,7 +9,6 @@ package io.element.android.features.messages.impl.timeline.model.event
 
 import io.element.android.libraries.matrix.api.core.EventId
 import io.element.android.libraries.matrix.api.media.MediaSource
-import io.element.android.libraries.matrix.api.timeline.item.event.FormattedBody
 import kotlinx.collections.immutable.ImmutableList
 import kotlin.time.Duration
 
@@ -17,7 +16,7 @@ data class TimelineItemVoiceContent(
     val eventId: EventId?,
     override val filename: String,
     override val caption: String?,
-    override val formattedCaption: FormattedBody?,
+    override val formattedCaption: CharSequence?,
     val duration: Duration,
     val mediaSource: MediaSource,
     val mimeType: String,


Test patch:
diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesViewTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesViewTest.kt
index a20acaa5df0..c040ce99264 100644
--- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesViewTest.kt
+++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesViewTest.kt
@@ -529,7 +529,7 @@ private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setMessa
                 state = state,
                 onBackClick = onBackClick,
                 onRoomDetailsClick = onRoomDetailsClick,
-                onEventClick = onEventClick,
+                onEventContentClick = onEventClick,
                 onUserDataClick = onUserDataClick,
                 onLinkClick = onLinkClick,
                 onPreviewAttachments = onPreviewAttachments,
diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelineViewTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelineViewTest.kt
index 315abd83020..6a7b49c34c6 100644
--- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelineViewTest.kt
+++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelineViewTest.kt
@@ -158,7 +158,7 @@ private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setTimel
             timelineProtectionState = timelineProtectionState,
             onUserDataClick = onUserDataClick,
             onLinkClick = onLinkClick,
-            onMessageClick = onMessageClick,
+            onContentClick = onMessageClick,
             onMessageLongClick = onMessageLongClick,
             onSwipeToReply = onSwipeToReply,
             onReactionClick = onReactionClick,
diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactoryTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactoryTest.kt
index e9343bf21d0..a34e601e5da 100644
--- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactoryTest.kt
+++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactoryTest.kt
@@ -286,7 +286,7 @@ class TimelineItemContentMessageFactoryTest {
         val expected = TimelineItemVideoContent(
             filename = "body.mp4",
             caption = "body.mp4 caption",
-            formattedCaption = FormattedBody(MessageFormat.HTML, "formatted"),
+            formattedCaption = SpannedString("formatted"),
             duration = 1.minutes,
             videoSource = MediaSource(url = "url", json = null),
             thumbnailSource = MediaSource("url_thumbnail"),
@@ -527,7 +527,7 @@ class TimelineItemContentMessageFactoryTest {
         )
         val expected = TimelineItemImageContent(
             filename = "body.jpg",
-            formattedCaption = FormattedBody(MessageFormat.HTML, "formatted"),
+            formattedCaption = SpannedString("formatted"),
             caption = "body.jpg caption",
             mediaSource = MediaSource(url = "url", json = null),
             thumbnailSource = MediaSource("url_thumbnail"),