From a5594c1047197b15258bab3d002cb70f059840b7 Mon Sep 17 00:00:00 2001 From: Johannes Marbach Date: Wed, 28 May 2025 18:50:06 +0200 Subject: [PATCH 01/14] feat(timeline): Expose method to send galleries in matrix-sdk-ui Signed-off-by: Johannes Marbach --- crates/matrix-sdk-ui/Cargo.toml | 3 + .../src/timeline/controller/mod.rs | 1 + .../src/timeline/event_item/mod.rs | 1 + crates/matrix-sdk-ui/src/timeline/futures.rs | 36 +++++ crates/matrix-sdk-ui/src/timeline/mod.rs | 26 ++++ .../tests/integration/timeline/media.rs | 126 ++++++++++++++++++ 6 files changed, 193 insertions(+) diff --git a/crates/matrix-sdk-ui/Cargo.toml b/crates/matrix-sdk-ui/Cargo.toml index 938a384db96..892c4b2bb56 100644 --- a/crates/matrix-sdk-ui/Cargo.toml +++ b/crates/matrix-sdk-ui/Cargo.toml @@ -22,6 +22,9 @@ uniffi = ["dep:uniffi", "matrix-sdk/uniffi", "matrix-sdk-base/uniffi"] # Add support for encrypted extensible events. unstable-msc3956 = ["ruma/unstable-msc3956"] +# Add support for inline media galleries via msgtypes +unstable-msc4274 = ["matrix-sdk/unstable-msc4274"] + [dependencies] as_variant.workspace = true async-rx.workspace = true diff --git a/crates/matrix-sdk-ui/src/timeline/controller/mod.rs b/crates/matrix-sdk-ui/src/timeline/controller/mod.rs index 492ac613fa6..e15eede619c 100644 --- a/crates/matrix-sdk-ui/src/timeline/controller/mod.rs +++ b/crates/matrix-sdk-ui/src/timeline/controller/mod.rs @@ -237,6 +237,7 @@ pub fn default_event_filter(event: &AnySyncTimelineEvent, room_version: &RoomVer MessageType::Audio(_) | MessageType::Emote(_) | MessageType::File(_) + | MessageType::Gallery(_) | MessageType::Image(_) | MessageType::Location(_) | MessageType::Notice(_) diff --git a/crates/matrix-sdk-ui/src/timeline/event_item/mod.rs b/crates/matrix-sdk-ui/src/timeline/event_item/mod.rs index 4659947128b..ccbe6092348 100644 --- a/crates/matrix-sdk-ui/src/timeline/event_item/mod.rs +++ b/crates/matrix-sdk-ui/src/timeline/event_item/mod.rs @@ -369,6 +369,7 @@ impl EventTimelineItem { | MessageType::Emote(_) | MessageType::Audio(_) | MessageType::File(_) + | MessageType::Gallery(_) | MessageType::Image(_) | MessageType::Video(_) ) diff --git a/crates/matrix-sdk-ui/src/timeline/futures.rs b/crates/matrix-sdk-ui/src/timeline/futures.rs index bba9799a60a..e5d3f8133cb 100644 --- a/crates/matrix-sdk-ui/src/timeline/futures.rs +++ b/crates/matrix-sdk-ui/src/timeline/futures.rs @@ -1,6 +1,8 @@ use std::future::IntoFuture; use eyeball::SharedObservable; +#[cfg(feature = "unstable-msc4274")] +use matrix_sdk::attachment::GalleryConfig; use matrix_sdk::{attachment::AttachmentConfig, TransmissionProgress}; use matrix_sdk_base::boxed_into_future; use mime::Mime; @@ -92,3 +94,37 @@ impl<'a> IntoFuture for SendAttachment<'a> { Box::pin(fut.instrument(tracing_span)) } } + +#[cfg(feature = "unstable-msc4274")] +pub struct SendGallery<'a> { + timeline: &'a Timeline, + gallery: GalleryConfig, + tracing_span: Span, +} + +#[cfg(feature = "unstable-msc4274")] +impl<'a> SendGallery<'a> { + pub(crate) fn new(timeline: &'a Timeline, gallery: GalleryConfig) -> Self { + Self { timeline, gallery, tracing_span: Span::current() } + } +} + +#[cfg(feature = "unstable-msc4274")] +impl<'a> IntoFuture for SendGallery<'a> { + type Output = Result<(), Error>; + boxed_into_future!(extra_bounds: 'a); + + fn into_future(self) -> Self::IntoFuture { + let Self { timeline, gallery, tracing_span } = self; + + let fut = async move { + let send_queue = timeline.room().send_queue(); + let fut = send_queue.send_gallery(gallery); + fut.await.map_err(|_| Error::FailedSendingAttachment)?; + + Ok(()) + }; + + Box::pin(fut.instrument(tracing_span)) + } +} diff --git a/crates/matrix-sdk-ui/src/timeline/mod.rs b/crates/matrix-sdk-ui/src/timeline/mod.rs index 5109e7a1245..55f31845d78 100644 --- a/crates/matrix-sdk-ui/src/timeline/mod.rs +++ b/crates/matrix-sdk-ui/src/timeline/mod.rs @@ -21,8 +21,12 @@ use std::{fs, path::PathBuf, sync::Arc}; use algorithms::rfind_event_by_item_id; use event_item::TimelineItemHandle; use eyeball_im::VectorDiff; +#[cfg(feature = "unstable-msc4274")] +use futures::SendGallery; use futures_core::Stream; use imbl::Vector; +#[cfg(feature = "unstable-msc4274")] +use matrix_sdk::attachment::GalleryConfig; use matrix_sdk::{ attachment::AttachmentConfig, deserialized_responses::TimelineEvent, @@ -416,6 +420,28 @@ impl Timeline { SendAttachment::new(self, source.into(), mime_type, config) } + /// Sends a media gallery to the room. + /// + /// If the encryption feature is enabled, this method will transparently + /// encrypt the room message if the room is encrypted. + /// + /// The attachments and their optional thumbnails are stored in the media + /// cache and can be retrieved at any time, by calling + /// [`Media::get_media_content()`] with the `MediaSource` that can be found + /// in the corresponding `TimelineEventItem`, and using a + /// `MediaFormat::File`. + /// + /// # Arguments + /// * `gallery` - A configuration object containing details about the + /// gallery like files, thumbnails, etc. + /// + /// [`Media::get_media_content()`]: matrix_sdk::Media::get_media_content + #[cfg(feature = "unstable-msc4274")] + #[instrument(skip_all)] + pub fn send_gallery(&self, gallery: GalleryConfig) -> SendGallery<'_> { + SendGallery::new(self, gallery) + } + /// Redact an event given its [`TimelineEventItemId`] and an optional /// reason. pub async fn redact( diff --git a/crates/matrix-sdk-ui/tests/integration/timeline/media.rs b/crates/matrix-sdk-ui/tests/integration/timeline/media.rs index 9e4489ba325..3d21d3dc750 100644 --- a/crates/matrix-sdk-ui/tests/integration/timeline/media.rs +++ b/crates/matrix-sdk-ui/tests/integration/timeline/media.rs @@ -18,6 +18,8 @@ use assert_matches::assert_matches; use assert_matches2::assert_let; use eyeball_im::VectorDiff; use futures_util::{FutureExt, StreamExt}; +#[cfg(feature = "unstable-msc4274")] +use matrix_sdk::attachment::{AttachmentInfo, BaseFileInfo, GalleryConfig, GalleryItemInfo}; use matrix_sdk::{ assert_let_timeout, attachment::AttachmentConfig, @@ -26,6 +28,8 @@ use matrix_sdk::{ }; use matrix_sdk_test::{async_test, event_factory::EventFactory, JoinedRoomBuilder, ALICE}; use matrix_sdk_ui::timeline::{AttachmentSource, EventSendState, RoomExt}; +#[cfg(feature = "unstable-msc4274")] +use ruma::events::room::message::GalleryItemType; use ruma::{ event_id, events::room::{ @@ -262,6 +266,128 @@ async fn test_send_attachment_from_bytes() { assert!(timeline_stream.next().now_or_never().is_none()); } +#[cfg(feature = "unstable-msc4274")] +#[async_test] +async fn test_send_gallery_from_bytes() { + let mock = MatrixMockServer::new().await; + let client = mock.client_builder().build().await; + + mock.mock_room_state_encryption().plain().mount().await; + + let room_id = room_id!("!a98sd12bjh:example.org"); + let room = mock.sync_joined_room(&client, room_id).await; + let timeline = room.timeline().await.unwrap(); + + let (items, mut timeline_stream) = + timeline.subscribe_filter_map(|item| item.as_event().cloned()).await; + + assert!(items.is_empty()); + + let f = EventFactory::new(); + mock.sync_room( + &client, + JoinedRoomBuilder::new(room_id).add_timeline_event(f.text_msg("hello").sender(&ALICE)), + ) + .await; + + // Sanity check. + assert_let_timeout!(Some(VectorDiff::PushBack { value: item }) = timeline_stream.next()); + assert_let!(Some(msg) = item.content().as_message()); + assert_eq!(msg.body(), "hello"); + + // No other updates. + assert!(timeline_stream.next().now_or_never().is_none()); + + // The data of the file. + let filename = "test.bin"; + let data = b"hello world".to_vec(); + + // Set up mocks for the file upload. + mock.mock_upload() + .respond_with(ResponseTemplate::new(200).set_delay(Duration::from_secs(2)).set_body_json( + json!({ + "content_uri": "mxc://sdk.rs/media" + }), + )) + .mock_once() + .mount() + .await; + + mock.mock_room_send().ok(event_id!("$media")).mock_once().mount().await; + + // Queue sending of a gallery. + let gallery = + GalleryConfig::new().caption(Some("caption".to_owned())).add_item(GalleryItemInfo { + filename: filename.to_owned(), + content_type: mime::TEXT_PLAIN, + data, + attachment_info: AttachmentInfo::File(BaseFileInfo { size: None }), + caption: Some("item caption".to_owned()), + formatted_caption: None, + thumbnail: None, + }); + timeline.send_gallery(gallery).await.unwrap(); + + { + assert_let_timeout!(Some(VectorDiff::PushBack { value: item }) = timeline_stream.next()); + assert_matches!(item.send_state(), Some(EventSendState::NotSentYet)); + assert_let!(Some(msg) = item.content().as_message()); + + // Body matches gallery caption. + assert_eq!(msg.body(), "caption"); + + // Message is gallery of expected length + assert_let!(MessageType::Gallery(content) = msg.msgtype()); + assert_eq!(1, content.itemtypes.len()); + assert_let!(GalleryItemType::File(file) = content.itemtypes.first().unwrap()); + + // Item has filename and caption + assert_eq!(filename, file.filename()); + assert_eq!(Some("item caption"), file.caption()); + + // The URI refers to the local cache. + assert_let!(MediaSource::Plain(uri) = &file.source); + assert!(uri.to_string().contains("localhost")); + } + + // Eventually, the media is updated with the final MXC IDs… + sleep(Duration::from_secs(2)).await; + + { + assert_let_timeout!( + Some(VectorDiff::Set { index: 1, value: item }) = timeline_stream.next() + ); + assert_let!(Some(msg) = item.content().as_message()); + assert_matches!(item.send_state(), Some(EventSendState::NotSentYet)); + + // Message is gallery of expected length + assert_let!(MessageType::Gallery(content) = msg.msgtype()); + assert_eq!(1, content.itemtypes.len()); + assert_let!(GalleryItemType::File(file) = content.itemtypes.first().unwrap()); + + // Item has filename and caption + assert_eq!(filename, file.filename()); + assert_eq!(Some("item caption"), file.caption()); + + // The URI now refers to the final MXC URI. + assert_let!(MediaSource::Plain(uri) = &file.source); + assert_eq!(uri.to_string(), "mxc://sdk.rs/media"); + } + + // And eventually the event itself is sent. + { + assert_let_timeout!( + Some(VectorDiff::Set { index: 1, value: item }) = timeline_stream.next() + ); + assert_matches!(item.send_state(), Some(EventSendState::Sent{ event_id }) => { + assert_eq!(event_id, event_id!("$media")); + }); + } + + // That's all, folks! + assert!(timeline_stream.next().now_or_never().is_none()); +} + #[async_test] async fn test_react_to_local_media() { let mock = MatrixMockServer::new().await; From 5b0b56f16ee681ac5d763e2790ae8df9b15ddf4a Mon Sep 17 00:00:00 2001 From: Johannes Marbach Date: Wed, 28 May 2025 18:54:41 +0200 Subject: [PATCH 02/14] Add changelog --- crates/matrix-sdk-ui/CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/crates/matrix-sdk-ui/CHANGELOG.md b/crates/matrix-sdk-ui/CHANGELOG.md index 2e13829578c..fc0bf4cc9af 100644 --- a/crates/matrix-sdk-ui/CHANGELOG.md +++ b/crates/matrix-sdk-ui/CHANGELOG.md @@ -27,6 +27,9 @@ All notable changes to this project will be documented in this file. ([#5055](https://github.com/matrix-org/matrix-rust-sdk/pull/5055)) - `Timeline::mark_as_read()` unsets the unread flag of the room if it was set. ([#5055](https://github.com/matrix-org/matrix-rust-sdk/pull/5055)) +- Add new method `Timeline::send_gallery` to allow sending MSC4274-style + galleries. + ([#5125](https://github.com/matrix-org/matrix-rust-sdk/pull/5125)) ## [0.11.0] - 2025-04-11 From 7b6c0f04f3a633f39a322bbd84e7387042e291a2 Mon Sep 17 00:00:00 2001 From: Johannes Marbach Date: Wed, 28 May 2025 19:02:11 +0200 Subject: [PATCH 03/14] Add missing cfg attributes --- .../src/timeline/controller/mod.rs | 27 ++++++++++--------- .../src/timeline/event_item/mod.rs | 23 ++++++++-------- 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/crates/matrix-sdk-ui/src/timeline/controller/mod.rs b/crates/matrix-sdk-ui/src/timeline/controller/mod.rs index e15eede619c..c0fd013152c 100644 --- a/crates/matrix-sdk-ui/src/timeline/controller/mod.rs +++ b/crates/matrix-sdk-ui/src/timeline/controller/mod.rs @@ -232,20 +232,21 @@ pub fn default_event_filter(event: &AnySyncTimelineEvent, room_version: &RoomVer return false; } - matches!( - content.msgtype, + match content.msgtype { MessageType::Audio(_) - | MessageType::Emote(_) - | MessageType::File(_) - | MessageType::Gallery(_) - | MessageType::Image(_) - | MessageType::Location(_) - | MessageType::Notice(_) - | MessageType::ServerNotice(_) - | MessageType::Text(_) - | MessageType::Video(_) - | MessageType::VerificationRequest(_) - ) + | MessageType::Emote(_) + | MessageType::File(_) + | MessageType::Image(_) + | MessageType::Location(_) + | MessageType::Notice(_) + | MessageType::ServerNotice(_) + | MessageType::Text(_) + | MessageType::Video(_) + | MessageType::VerificationRequest(_) => true, + #[cfg(feature = "unstable-msc4274")] + MessageType::Gallery(_) => false, + _ => false, + } } AnyMessageLikeEventContent::Sticker(_) diff --git a/crates/matrix-sdk-ui/src/timeline/event_item/mod.rs b/crates/matrix-sdk-ui/src/timeline/event_item/mod.rs index ccbe6092348..101545fe7c0 100644 --- a/crates/matrix-sdk-ui/src/timeline/event_item/mod.rs +++ b/crates/matrix-sdk-ui/src/timeline/event_item/mod.rs @@ -362,18 +362,17 @@ impl EventTimelineItem { match self.content() { TimelineItemContent::MsgLike(msglike) => match &msglike.kind { - MsgLikeKind::Message(message) => { - matches!( - message.msgtype(), - MessageType::Text(_) - | MessageType::Emote(_) - | MessageType::Audio(_) - | MessageType::File(_) - | MessageType::Gallery(_) - | MessageType::Image(_) - | MessageType::Video(_) - ) - } + MsgLikeKind::Message(message) => match message.msgtype() { + MessageType::Text(_) + | MessageType::Emote(_) + | MessageType::Audio(_) + | MessageType::File(_) + | MessageType::Image(_) + | MessageType::Video(_) => true, + #[cfg(feature = "unstable-msc4274")] + MessageType::Gallery(_) => true, + _ => false, + }, MsgLikeKind::Poll(poll) => { poll.response_data.is_empty() && poll.end_event_timestamp.is_none() } From 2ff434514786734d5a300055ce8b6b95d1f9006b Mon Sep 17 00:00:00 2001 From: Johannes Marbach Date: Mon, 2 Jun 2025 21:10:48 +0200 Subject: [PATCH 04/14] Introduce galleries module to prevent excessive use of cfg --- crates/matrix-sdk-ui/src/timeline/futures.rs | 57 +++++++++++--------- 1 file changed, 33 insertions(+), 24 deletions(-) diff --git a/crates/matrix-sdk-ui/src/timeline/futures.rs b/crates/matrix-sdk-ui/src/timeline/futures.rs index 3a7e745db86..bb9af758747 100644 --- a/crates/matrix-sdk-ui/src/timeline/futures.rs +++ b/crates/matrix-sdk-ui/src/timeline/futures.rs @@ -1,8 +1,6 @@ use std::future::IntoFuture; use eyeball::SharedObservable; -#[cfg(feature = "unstable-msc4274")] -use matrix_sdk::attachment::GalleryConfig; use matrix_sdk::{attachment::AttachmentConfig, TransmissionProgress}; use matrix_sdk_base::boxed_into_future; use mime::Mime; @@ -96,35 +94,46 @@ impl<'a> IntoFuture for SendAttachment<'a> { } #[cfg(feature = "unstable-msc4274")] -pub struct SendGallery<'a> { - timeline: &'a Timeline, - gallery: GalleryConfig, - tracing_span: Span, -} +pub use galleries::*; #[cfg(feature = "unstable-msc4274")] -impl<'a> SendGallery<'a> { - pub(crate) fn new(timeline: &'a Timeline, gallery: GalleryConfig) -> Self { - Self { timeline, gallery, tracing_span: Span::current() } +mod galleries { + use std::future::IntoFuture; + + use matrix_sdk::attachment::GalleryConfig; + use matrix_sdk_base::boxed_into_future; + use tracing::{Instrument as _, Span}; + + use super::{Error, Timeline}; + + pub struct SendGallery<'a> { + timeline: &'a Timeline, + gallery: GalleryConfig, + tracing_span: Span, } -} -#[cfg(feature = "unstable-msc4274")] -impl<'a> IntoFuture for SendGallery<'a> { - type Output = Result<(), Error>; - boxed_into_future!(extra_bounds: 'a); + impl<'a> SendGallery<'a> { + pub(crate) fn new(timeline: &'a Timeline, gallery: GalleryConfig) -> Self { + Self { timeline, gallery, tracing_span: Span::current() } + } + } - fn into_future(self) -> Self::IntoFuture { - let Self { timeline, gallery, tracing_span } = self; + impl<'a> IntoFuture for SendGallery<'a> { + type Output = Result<(), Error>; + boxed_into_future!(extra_bounds: 'a); - let fut = async move { - let send_queue = timeline.room().send_queue(); - let fut = send_queue.send_gallery(gallery); - fut.await.map_err(|_| Error::FailedSendingAttachment)?; + fn into_future(self) -> Self::IntoFuture { + let Self { timeline, gallery, tracing_span } = self; - Ok(()) - }; + let fut = async move { + let send_queue = timeline.room().send_queue(); + let fut = send_queue.send_gallery(gallery); + fut.await.map_err(|_| Error::FailedSendingAttachment)?; - Box::pin(fut.instrument(tracing_span)) + Ok(()) + }; + + Box::pin(fut.instrument(tracing_span)) + } } } From f9f3f4973c46c3e3d4fd5e6f3413f15dbffaa6e5 Mon Sep 17 00:00:00 2001 From: Johannes Marbach Date: Mon, 2 Jun 2025 21:16:28 +0200 Subject: [PATCH 05/14] Switch to assert_pending --- .../tests/integration/timeline/media.rs | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/crates/matrix-sdk-ui/tests/integration/timeline/media.rs b/crates/matrix-sdk-ui/tests/integration/timeline/media.rs index 3d21d3dc750..5163788c59b 100644 --- a/crates/matrix-sdk-ui/tests/integration/timeline/media.rs +++ b/crates/matrix-sdk-ui/tests/integration/timeline/media.rs @@ -17,7 +17,7 @@ use std::{fs::File, io::Write as _, path::PathBuf, time::Duration}; use assert_matches::assert_matches; use assert_matches2::assert_let; use eyeball_im::VectorDiff; -use futures_util::{FutureExt, StreamExt}; +use futures_util::StreamExt; #[cfg(feature = "unstable-msc4274")] use matrix_sdk::attachment::{AttachmentInfo, BaseFileInfo, GalleryConfig, GalleryItemInfo}; use matrix_sdk::{ @@ -39,6 +39,7 @@ use ruma::{ room_id, }; use serde_json::json; +use stream_assert::assert_pending; use tempfile::TempDir; use tokio::time::sleep; use wiremock::ResponseTemplate; @@ -92,7 +93,7 @@ async fn test_send_attachment_from_file() { assert_eq!(msg.body(), "hello"); // No other updates. - assert!(timeline_stream.next().now_or_never().is_none()); + assert_pending!(timeline_stream); // Store a file in a temporary directory. let (_tmp_dir, file_path) = create_temporary_file("test.bin"); @@ -164,7 +165,7 @@ async fn test_send_attachment_from_file() { } // That's all, folks! - assert!(timeline_stream.next().now_or_never().is_none()); + assert_pending!(timeline_stream); } #[async_test] @@ -196,7 +197,7 @@ async fn test_send_attachment_from_bytes() { assert_eq!(msg.body(), "hello"); // No other updates. - assert!(timeline_stream.next().now_or_never().is_none()); + assert_pending!(timeline_stream); // The data of the file. let filename = "test.bin"; @@ -263,7 +264,7 @@ async fn test_send_attachment_from_bytes() { } // That's all, folks! - assert!(timeline_stream.next().now_or_never().is_none()); + assert_pending!(timeline_stream); } #[cfg(feature = "unstable-msc4274")] @@ -296,7 +297,7 @@ async fn test_send_gallery_from_bytes() { assert_eq!(msg.body(), "hello"); // No other updates. - assert!(timeline_stream.next().now_or_never().is_none()); + assert_pending!(timeline_stream); // The data of the file. let filename = "test.bin"; @@ -385,7 +386,7 @@ async fn test_send_gallery_from_bytes() { } // That's all, folks! - assert!(timeline_stream.next().now_or_never().is_none()); + assert_pending!(timeline_stream); } #[async_test] @@ -406,7 +407,7 @@ async fn test_react_to_local_media() { timeline.subscribe_filter_map(|item| item.as_event().cloned()).await; assert!(items.is_empty()); - assert!(timeline_stream.next().now_or_never().is_none()); + assert_pending!(timeline_stream); // Store a file in a temporary directory. let (_tmp_dir, file_path) = create_temporary_file("test.bin"); @@ -439,5 +440,5 @@ async fn test_react_to_local_media() { reactions.get("🤪").unwrap().get(own_user_id).unwrap(); // That's all, folks! - assert!(timeline_stream.next().now_or_never().is_none()); + assert_pending!(timeline_stream); } From 84e36c40960988af60ffc8eaafa763439a37bef2 Mon Sep 17 00:00:00 2001 From: Johannes Marbach Date: Mon, 2 Jun 2025 21:27:33 +0200 Subject: [PATCH 06/14] Pass duration into assert_let_timeout instead of using sleep --- .../matrix-sdk-ui/tests/integration/timeline/media.rs | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/crates/matrix-sdk-ui/tests/integration/timeline/media.rs b/crates/matrix-sdk-ui/tests/integration/timeline/media.rs index 5163788c59b..be42d9e60be 100644 --- a/crates/matrix-sdk-ui/tests/integration/timeline/media.rs +++ b/crates/matrix-sdk-ui/tests/integration/timeline/media.rs @@ -41,7 +41,6 @@ use ruma::{ use serde_json::json; use stream_assert::assert_pending; use tempfile::TempDir; -use tokio::time::sleep; use wiremock::ResponseTemplate; fn create_temporary_file(filename: &str) -> (TempDir, PathBuf) { @@ -138,10 +137,9 @@ async fn test_send_attachment_from_file() { } // Eventually, the media is updated with the final MXC IDs… - sleep(Duration::from_secs(2)).await; - { assert_let_timeout!( + Duration::from_secs(3), Some(VectorDiff::Set { index: 1, value: item }) = timeline_stream.next() ); assert_let!(Some(msg) = item.content().as_message()); @@ -237,10 +235,9 @@ async fn test_send_attachment_from_bytes() { } // Eventually, the media is updated with the final MXC IDs… - sleep(Duration::from_secs(2)).await; - { assert_let_timeout!( + Duration::from_secs(3), Some(VectorDiff::Set { index: 1, value: item }) = timeline_stream.next() ); assert_let!(Some(msg) = item.content().as_message()); @@ -352,10 +349,9 @@ async fn test_send_gallery_from_bytes() { } // Eventually, the media is updated with the final MXC IDs… - sleep(Duration::from_secs(2)).await; - { assert_let_timeout!( + Duration::from_secs(3), Some(VectorDiff::Set { index: 1, value: item }) = timeline_stream.next() ); assert_let!(Some(msg) = item.content().as_message()); From c9602002766f0e14a4651c86862bdd0af584d45c Mon Sep 17 00:00:00 2001 From: Johannes Marbach Date: Mon, 2 Jun 2025 21:30:09 +0200 Subject: [PATCH 07/14] Don't hide galleries in default event filter --- crates/matrix-sdk-ui/src/timeline/controller/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/matrix-sdk-ui/src/timeline/controller/mod.rs b/crates/matrix-sdk-ui/src/timeline/controller/mod.rs index c0fd013152c..cd9a8a733fb 100644 --- a/crates/matrix-sdk-ui/src/timeline/controller/mod.rs +++ b/crates/matrix-sdk-ui/src/timeline/controller/mod.rs @@ -244,7 +244,7 @@ pub fn default_event_filter(event: &AnySyncTimelineEvent, room_version: &RoomVer | MessageType::Video(_) | MessageType::VerificationRequest(_) => true, #[cfg(feature = "unstable-msc4274")] - MessageType::Gallery(_) => false, + MessageType::Gallery(_) => true, _ => false, } } From 59b92dfa2747fc52b4adae9a997d682156045ad8 Mon Sep 17 00:00:00 2001 From: Johannes Marbach Date: Tue, 3 Jun 2025 12:54:00 +0200 Subject: [PATCH 08/14] Send a gallery event in integration test to exercise filtering --- .../tests/integration/timeline/media.rs | 13 +++++++++-- testing/matrix-sdk-test/Cargo.toml | 2 +- testing/matrix-sdk-test/src/event_factory.rs | 22 ++++++++++++++++--- 3 files changed, 31 insertions(+), 6 deletions(-) diff --git a/crates/matrix-sdk-ui/tests/integration/timeline/media.rs b/crates/matrix-sdk-ui/tests/integration/timeline/media.rs index be42d9e60be..a2694ee7b6d 100644 --- a/crates/matrix-sdk-ui/tests/integration/timeline/media.rs +++ b/crates/matrix-sdk-ui/tests/integration/timeline/media.rs @@ -30,6 +30,8 @@ use matrix_sdk_test::{async_test, event_factory::EventFactory, JoinedRoomBuilder use matrix_sdk_ui::timeline::{AttachmentSource, EventSendState, RoomExt}; #[cfg(feature = "unstable-msc4274")] use ruma::events::room::message::GalleryItemType; +#[cfg(feature = "unstable-msc4274")] +use ruma::owned_mxc_uri; use ruma::{ event_id, events::room::{ @@ -284,14 +286,21 @@ async fn test_send_gallery_from_bytes() { let f = EventFactory::new(); mock.sync_room( &client, - JoinedRoomBuilder::new(room_id).add_timeline_event(f.text_msg("hello").sender(&ALICE)), + JoinedRoomBuilder::new(room_id).add_timeline_event( + f.gallery( + "check out my favourite gifs".to_owned(), + "rickroll.gif".to_owned(), + owned_mxc_uri!("mxc://sdk.rs/rickroll"), + ) + .sender(&ALICE), + ), ) .await; // Sanity check. assert_let_timeout!(Some(VectorDiff::PushBack { value: item }) = timeline_stream.next()); assert_let!(Some(msg) = item.content().as_message()); - assert_eq!(msg.body(), "hello"); + assert_eq!(msg.body(), "check out my favourite gifs"); // No other updates. assert_pending!(timeline_stream); diff --git a/testing/matrix-sdk-test/Cargo.toml b/testing/matrix-sdk-test/Cargo.toml index db04b904d3c..9c7df27cda4 100644 --- a/testing/matrix-sdk-test/Cargo.toml +++ b/testing/matrix-sdk-test/Cargo.toml @@ -27,7 +27,7 @@ matrix-sdk-test-macros = { version = "0.11.0", path = "../matrix-sdk-test-macros once_cell.workspace = true # Enable the unstable feature for polls support. # "client-api-s" enables need the "server" feature of ruma-client-api, which is needed to serialize Response objects to JSON. -ruma = { workspace = true, features = ["canonical-json", "client-api-s", "rand", "unstable-msc3381"] } +ruma = { workspace = true, features = ["canonical-json", "client-api-s", "rand", "unstable-msc3381", "unstable-msc4274"] } serde.workspace = true serde_json.workspace = true vodozemac.workspace = true diff --git a/testing/matrix-sdk-test/src/event_factory.rs b/testing/matrix-sdk-test/src/event_factory.rs index 55bd108068d..182c65d1148 100644 --- a/testing/matrix-sdk-test/src/event_factory.rs +++ b/testing/matrix-sdk-test/src/event_factory.rs @@ -45,9 +45,9 @@ use ruma::{ encrypted::{EncryptedEventScheme, RoomEncryptedEventContent}, member::{MembershipState, RoomMemberEventContent}, message::{ - FormattedBody, ImageMessageEventContent, MessageType, Relation, - RelationWithoutReplacement, RoomMessageEventContent, - RoomMessageEventContentWithoutRelation, + FormattedBody, GalleryItemType, GalleryMessageEventContent, + ImageMessageEventContent, MessageType, Relation, RelationWithoutReplacement, + RoomMessageEventContent, RoomMessageEventContentWithoutRelation, }, name::RoomNameEventContent, power_levels::RoomPowerLevelsEventContent, @@ -800,6 +800,22 @@ impl EventFactory { self.event(RoomMessageEventContent::new(MessageType::Image(image_event_content))) } + /// Create a gallery event containing a single plain (unencrypted) image + /// referencing the given MXC ID. + pub fn gallery( + &self, + body: String, + filename: String, + url: OwnedMxcUri, + ) -> EventBuilder { + let gallery_event_content = GalleryMessageEventContent::new( + body, + None, + vec![GalleryItemType::Image(ImageMessageEventContent::plain(filename, url))], + ); + self.event(RoomMessageEventContent::new(MessageType::Gallery(gallery_event_content))) + } + /// Create a typing notification event. pub fn typing(&self, user_ids: Vec<&UserId>) -> EventBuilder { let mut builder = self From 434b8960c898f531ac605fabf3345fa1f17177a9 Mon Sep 17 00:00:00 2001 From: Johannes Marbach Date: Tue, 3 Jun 2025 13:00:41 +0200 Subject: [PATCH 09/14] Enable matrix-sdk-ui/unstable-msc4274 from matrix-sdk-ffi --- bindings/matrix-sdk-ffi/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bindings/matrix-sdk-ffi/Cargo.toml b/bindings/matrix-sdk-ffi/Cargo.toml index bc6f452f1ec..f5f441e9cf4 100644 --- a/bindings/matrix-sdk-ffi/Cargo.toml +++ b/bindings/matrix-sdk-ffi/Cargo.toml @@ -17,7 +17,7 @@ release = true crate-type = ["cdylib", "staticlib"] [features] -default = ["bundled-sqlite"] +default = ["bundled-sqlite", "matrix-sdk-ui/unstable-msc4274"] bundled-sqlite = ["matrix-sdk/bundled-sqlite"] [dependencies] From f873500f32e8f15b68b82e46c46f8298f2829579 Mon Sep 17 00:00:00 2001 From: Johannes Marbach Date: Tue, 3 Jun 2025 13:01:04 +0200 Subject: [PATCH 10/14] Revert "Don't hide galleries in default event filter" This reverts commit c9602002766f0e14a4651c86862bdd0af584d45c. --- crates/matrix-sdk-ui/src/timeline/controller/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/matrix-sdk-ui/src/timeline/controller/mod.rs b/crates/matrix-sdk-ui/src/timeline/controller/mod.rs index cd9a8a733fb..c0fd013152c 100644 --- a/crates/matrix-sdk-ui/src/timeline/controller/mod.rs +++ b/crates/matrix-sdk-ui/src/timeline/controller/mod.rs @@ -244,7 +244,7 @@ pub fn default_event_filter(event: &AnySyncTimelineEvent, room_version: &RoomVer | MessageType::Video(_) | MessageType::VerificationRequest(_) => true, #[cfg(feature = "unstable-msc4274")] - MessageType::Gallery(_) => true, + MessageType::Gallery(_) => false, _ => false, } } From 80e2014b9ce38c65f531f7a76d6825407d2cf90f Mon Sep 17 00:00:00 2001 From: Johannes Marbach Date: Tue, 3 Jun 2025 13:22:58 +0200 Subject: [PATCH 11/14] Revert "Revert "Don't hide galleries in default event filter"" This reverts commit f873500f32e8f15b68b82e46c46f8298f2829579. --- crates/matrix-sdk-ui/src/timeline/controller/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/matrix-sdk-ui/src/timeline/controller/mod.rs b/crates/matrix-sdk-ui/src/timeline/controller/mod.rs index c0fd013152c..cd9a8a733fb 100644 --- a/crates/matrix-sdk-ui/src/timeline/controller/mod.rs +++ b/crates/matrix-sdk-ui/src/timeline/controller/mod.rs @@ -244,7 +244,7 @@ pub fn default_event_filter(event: &AnySyncTimelineEvent, room_version: &RoomVer | MessageType::Video(_) | MessageType::VerificationRequest(_) => true, #[cfg(feature = "unstable-msc4274")] - MessageType::Gallery(_) => false, + MessageType::Gallery(_) => true, _ => false, } } From 8328039f4a0aa5a561f4c7ca1b691935f0df2317 Mon Sep 17 00:00:00 2001 From: Johannes Marbach Date: Tue, 3 Jun 2025 14:01:19 +0200 Subject: [PATCH 12/14] Mock media config requests --- crates/matrix-sdk-ui/tests/integration/timeline/media.rs | 1 + crates/matrix-sdk/tests/integration/send_queue.rs | 1 + 2 files changed, 2 insertions(+) diff --git a/crates/matrix-sdk-ui/tests/integration/timeline/media.rs b/crates/matrix-sdk-ui/tests/integration/timeline/media.rs index 67ea1da2f0a..0be1d9c91e4 100644 --- a/crates/matrix-sdk-ui/tests/integration/timeline/media.rs +++ b/crates/matrix-sdk-ui/tests/integration/timeline/media.rs @@ -274,6 +274,7 @@ async fn test_send_gallery_from_bytes() { let mock = MatrixMockServer::new().await; let client = mock.client_builder().build().await; + mock.mock_authenticated_media_config().ok_default().mount().await; mock.mock_room_state_encryption().plain().mount().await; let room_id = room_id!("!a98sd12bjh:example.org"); diff --git a/crates/matrix-sdk/tests/integration/send_queue.rs b/crates/matrix-sdk/tests/integration/send_queue.rs index ba1096b29a8..b900c74e686 100644 --- a/crates/matrix-sdk/tests/integration/send_queue.rs +++ b/crates/matrix-sdk/tests/integration/send_queue.rs @@ -2134,6 +2134,7 @@ async fn test_gallery_uploads() { // ---------------------- // Prepare endpoints. + mock.mock_authenticated_media_config().ok_default().mount().await; mock.mock_room_state_encryption().plain().mount().await; mock.mock_room_send().ok(event_id!("$1")).mock_once().mount().await; From e9dfb4585381c47bacab3d5cee1a8d427303fc96 Mon Sep 17 00:00:00 2001 From: Johannes Marbach Date: Tue, 3 Jun 2025 14:01:37 +0200 Subject: [PATCH 13/14] Temporarily switch back to filtering out gallery events --- crates/matrix-sdk-ui/src/timeline/controller/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/matrix-sdk-ui/src/timeline/controller/mod.rs b/crates/matrix-sdk-ui/src/timeline/controller/mod.rs index cd9a8a733fb..c0fd013152c 100644 --- a/crates/matrix-sdk-ui/src/timeline/controller/mod.rs +++ b/crates/matrix-sdk-ui/src/timeline/controller/mod.rs @@ -244,7 +244,7 @@ pub fn default_event_filter(event: &AnySyncTimelineEvent, room_version: &RoomVer | MessageType::Video(_) | MessageType::VerificationRequest(_) => true, #[cfg(feature = "unstable-msc4274")] - MessageType::Gallery(_) => true, + MessageType::Gallery(_) => false, _ => false, } } From 22f19917e91a7ca20f091a0e8ef002d0d8d71799 Mon Sep 17 00:00:00 2001 From: Johannes Marbach Date: Tue, 3 Jun 2025 14:15:54 +0200 Subject: [PATCH 14/14] Revert "Temporarily switch back to filtering out gallery events" This reverts commit e9dfb4585381c47bacab3d5cee1a8d427303fc96. --- crates/matrix-sdk-ui/src/timeline/controller/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/matrix-sdk-ui/src/timeline/controller/mod.rs b/crates/matrix-sdk-ui/src/timeline/controller/mod.rs index c0fd013152c..cd9a8a733fb 100644 --- a/crates/matrix-sdk-ui/src/timeline/controller/mod.rs +++ b/crates/matrix-sdk-ui/src/timeline/controller/mod.rs @@ -244,7 +244,7 @@ pub fn default_event_filter(event: &AnySyncTimelineEvent, room_version: &RoomVer | MessageType::Video(_) | MessageType::VerificationRequest(_) => true, #[cfg(feature = "unstable-msc4274")] - MessageType::Gallery(_) => false, + MessageType::Gallery(_) => true, _ => false, } }