Skip to content

feat: implement and observe MSC4278 config value #5040

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
May 19, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 8 additions & 8 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ reqwest = { version = "0.12.12", default-features = false }
rmp-serde = "1.3.0"
# Be careful to use commits from the https://github.com/ruma/ruma/tree/ruma-0.12
# branch until a proper release with breaking changes happens.
ruma = { git = "https://github.com/ruma/ruma", rev = "689d9613a985edc089b5b729e6d9362f09b5df4f", features = [
ruma = { git = "https://github.com/ruma/ruma", rev = "a8fd1b0322649bf59e2a5cfc73ab4fe46b21edd7", features = [
"client-api-c",
"compat-upload-signatures",
"compat-user-id",
Expand All @@ -77,7 +77,7 @@ ruma = { git = "https://github.com/ruma/ruma", rev = "689d9613a985edc089b5b729e6
"unstable-msc4171",
"unstable-msc4278",
] }
ruma-common = { git = "https://github.com/ruma/ruma", rev = "689d9613a985edc089b5b729e6d9362f09b5df4f" }
ruma-common = { git = "https://github.com/ruma/ruma", rev = "a8fd1b0322649bf59e2a5cfc73ab4fe46b21edd7" }
serde = "1.0.217"
serde_html_form = "0.2.7"
serde_json = "1.0.138"
Expand Down
46 changes: 43 additions & 3 deletions bindings/matrix-sdk-ffi/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use std::{

use anyhow::{anyhow, Context as _};
use async_compat::get_runtime_handle;
use futures_util::StreamExt;
use futures_util::{pin_mut, StreamExt};
use matrix_sdk::{
authentication::oauth::{
AccountManagementActionFull, ClientId, OAuthAuthorizationData, OAuthSession,
Expand Down Expand Up @@ -89,8 +89,8 @@ use crate::{
room_directory_search::RoomDirectorySearch,
room_preview::RoomPreview,
ruma::{
AccountDataEvent, AccountDataEventType, AuthData, MediaSource, RoomAccountDataEvent,
RoomAccountDataEventType,
AccountDataEvent, AccountDataEventType, AuthData, InviteAvatars, MediaPreviewConfig,
MediaPreviews, MediaSource, RoomAccountDataEvent, RoomAccountDataEventType,
},
sync_service::{SyncService, SyncServiceBuilder},
task_handle::TaskHandle,
Expand Down Expand Up @@ -1373,6 +1373,46 @@ impl Client {
pub async fn is_report_room_api_supported(&self) -> Result<bool, ClientError> {
Ok(self.inner.server_versions().await?.contains(&ruma::api::MatrixVersion::V1_13))
}

/// Subscribe to changes in the media preview configuration.
pub async fn subscribe_to_media_preview_config(
&self,
listener: Box<dyn MediaPreviewConfigListener>,
) -> Result<Arc<TaskHandle>, ClientError> {
let (initial_value, stream) = self.inner.account().observe_media_preview_config().await?;
Ok(Arc::new(TaskHandle::new(get_runtime_handle().spawn(async move {
// Send the initial value to the listener.
listener.on_change(initial_value.into());
// Listen for changes and notify the listener.
pin_mut!(stream);
while let Some(media_preview_config) = stream.next().await {
listener.on_change(media_preview_config.into());
}
}))))
}

/// Set the media previews timeline display policy
pub async fn set_media_preview_display_policy(
&self,
policy: MediaPreviews,
) -> Result<(), ClientError> {
self.inner.account().set_media_previews_display_policy(policy.into()).await?;
Ok(())
}

/// Get the media previews timeline display policy
pub async fn set_invite_avatars_display_policy(
&self,
policy: InviteAvatars,
) -> Result<(), ClientError> {
self.inner.account().set_invite_avatars_display_policy(policy.into()).await?;
Ok(())
}
}

#[matrix_sdk_ffi_macros::export(callback_interface)]
pub trait MediaPreviewConfigListener: Sync + Send {
fn on_change(&self, media_preview_config: MediaPreviewConfig);
}

#[matrix_sdk_ffi_macros::export(callback_interface)]
Expand Down
88 changes: 88 additions & 0 deletions bindings/matrix-sdk-ffi/src/ruma.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ use ruma::{
ignored_user_list::{IgnoredUser as RumaIgnoredUser, IgnoredUserListEventContent},
location::AssetType as RumaAssetType,
marked_unread::{MarkedUnreadEventContent, UnstableMarkedUnreadEventContent},
media_preview_config::{
InviteAvatars as RumaInviteAvatars, MediaPreviewConfigEventContent,
MediaPreviews as RumaMediaPreviews,
},
poll::start::PollKind as RumaPollKind,
push_rules::PushRulesEventContent,
room::{
Expand Down Expand Up @@ -1104,6 +1108,68 @@ pub enum AccountDataEvent {
},
}

/// The policy that decides if media previews should be shown in the timeline.
#[derive(Clone, uniffi::Enum, Default)]
pub enum MediaPreviews {
/// Always show media previews in the timeline.
#[default]
On,
/// Show media previews in the timeline only if the room is private.
Private,
/// Never show media previews in the timeline.
Off,
}

impl From<RumaMediaPreviews> for MediaPreviews {
fn from(value: RumaMediaPreviews) -> Self {
match value {
RumaMediaPreviews::On => Self::On,
RumaMediaPreviews::Private => Self::Private,
RumaMediaPreviews::Off => Self::Off,
_ => Default::default(),
}
}
}

impl From<MediaPreviews> for RumaMediaPreviews {
fn from(value: MediaPreviews) -> Self {
match value {
MediaPreviews::On => Self::On,
MediaPreviews::Private => Self::Private,
MediaPreviews::Off => Self::Off,
}
}
}

/// The policy that decides if avatars should be shown in invite requests.
#[derive(Clone, uniffi::Enum, Default)]
pub enum InviteAvatars {
/// Always show avatars in invite requests.
#[default]
On,
/// Never show avatars in invite requests.
Off,
}

impl From<RumaInviteAvatars> for InviteAvatars {
fn from(value: RumaInviteAvatars) -> Self {
match value {
RumaInviteAvatars::On => Self::On,
RumaInviteAvatars::Off => Self::Off,
_ => Default::default(),
}
}
}

impl From<InviteAvatars> for RumaInviteAvatars {
fn from(value: InviteAvatars) -> Self {
match value {
InviteAvatars::On => Self::On,
InviteAvatars::Off => Self::Off,
}
}
}

/// Details about an ignored user.
///
/// This is currently empty.
Expand Down Expand Up @@ -1351,6 +1417,28 @@ impl From<RumaSecretStorageV1AesHmacSha2Properties> for SecretStorageV1AesHmacSh
}
}

/// The content of an `m.media_preview_config` event.
///
/// Is also the content of the unstable
/// `io.element.msc4278.media_preview_config`.
#[derive(Clone, uniffi::Record, Default)]
pub struct MediaPreviewConfig {
/// The media previews setting for the user.
pub media_previews: MediaPreviews,

/// The invite avatars setting for the user.
pub invite_avatars: InviteAvatars,
}

impl From<MediaPreviewConfigEventContent> for MediaPreviewConfig {
fn from(value: MediaPreviewConfigEventContent) -> Self {
Self {
media_previews: value.media_previews.into(),
invite_avatars: value.invite_avatars.into(),
}
}
}

/// A passphrase from which a key is to be derived.
#[derive(Clone, uniffi::Record)]
pub struct PassPhrase {
Expand Down
Loading
Loading