Skip to content

Commit dddff85

Browse files
committed
feat: implement and observe MSC 4278 config value
1 parent de66047 commit dddff85

File tree

7 files changed

+413
-33
lines changed

7 files changed

+413
-33
lines changed

Cargo.lock

Lines changed: 8 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ reqwest = { version = "0.12.12", default-features = false }
6161
rmp-serde = "1.3.0"
6262
# Be careful to use commits from the https://github.com/ruma/ruma/tree/ruma-0.12
6363
# branch until a proper release with breaking changes happens.
64-
ruma = { git = "https://github.com/ruma/ruma", rev = "689d9613a985edc089b5b729e6d9362f09b5df4f", features = [
64+
ruma = { git = "https://github.com/ruma/ruma", rev = "a8fd1b0322649bf59e2a5cfc73ab4fe46b21edd7", features = [
6565
"client-api-c",
6666
"compat-upload-signatures",
6767
"compat-user-id",
@@ -77,7 +77,7 @@ ruma = { git = "https://github.com/ruma/ruma", rev = "689d9613a985edc089b5b729e6
7777
"unstable-msc4171",
7878
"unstable-msc4278",
7979
] }
80-
ruma-common = { git = "https://github.com/ruma/ruma", rev = "689d9613a985edc089b5b729e6d9362f09b5df4f" }
80+
ruma-common = { git = "https://github.com/ruma/ruma", rev = "a8fd1b0322649bf59e2a5cfc73ab4fe46b21edd7" }
8181
serde = "1.0.217"
8282
serde_html_form = "0.2.7"
8383
serde_json = "1.0.138"

bindings/matrix-sdk-ffi/src/client.rs

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use std::{
66

77
use anyhow::{anyhow, Context as _};
88
use async_compat::get_runtime_handle;
9-
use futures_util::StreamExt;
9+
use futures_util::{pin_mut, StreamExt};
1010
use matrix_sdk::{
1111
authentication::oauth::{
1212
AccountManagementActionFull, ClientId, OAuthAuthorizationData, OAuthSession,
@@ -89,8 +89,8 @@ use crate::{
8989
room_directory_search::RoomDirectorySearch,
9090
room_preview::RoomPreview,
9191
ruma::{
92-
AccountDataEvent, AccountDataEventType, AuthData, MediaSource, RoomAccountDataEvent,
93-
RoomAccountDataEventType,
92+
AccountDataEvent, AccountDataEventType, AuthData, InviteAvatars, MediaPreviewConfig,
93+
MediaPreviews, MediaSource, RoomAccountDataEvent, RoomAccountDataEventType,
9494
},
9595
sync_service::{SyncService, SyncServiceBuilder},
9696
task_handle::TaskHandle,
@@ -1373,6 +1373,46 @@ impl Client {
13731373
pub async fn is_report_room_api_supported(&self) -> Result<bool, ClientError> {
13741374
Ok(self.inner.server_versions().await?.contains(&ruma::api::MatrixVersion::V1_13))
13751375
}
1376+
1377+
/// Subscribe to changes in the media preview configuration.
1378+
pub async fn subscribe_to_media_preview_config(
1379+
&self,
1380+
listener: Box<dyn MediaPreviewConfigListener>,
1381+
) -> Result<Arc<TaskHandle>, ClientError> {
1382+
let (initial_value, stream) = self.inner.account().observe_media_preview_config().await?;
1383+
Ok(Arc::new(TaskHandle::new(get_runtime_handle().spawn(async move {
1384+
// Send the initial value to the listener.
1385+
listener.on_change(initial_value.into());
1386+
// Listen for changes and notify the listener.
1387+
pin_mut!(stream);
1388+
while let Some(media_preview_config) = stream.next().await {
1389+
listener.on_change(media_preview_config.into());
1390+
}
1391+
}))))
1392+
}
1393+
1394+
/// Set the media previews timeline display policy
1395+
pub async fn set_media_preview_display_policy(
1396+
&self,
1397+
policy: MediaPreviews,
1398+
) -> Result<(), ClientError> {
1399+
self.inner.account().set_media_previews_display_policy(policy.into()).await?;
1400+
Ok(())
1401+
}
1402+
1403+
/// Get the media previews timeline display policy
1404+
pub async fn set_invite_avatars_display_policy(
1405+
&self,
1406+
policy: InviteAvatars,
1407+
) -> Result<(), ClientError> {
1408+
self.inner.account().set_invite_avatars_display_policy(policy.into()).await?;
1409+
Ok(())
1410+
}
1411+
}
1412+
1413+
#[matrix_sdk_ffi_macros::export(callback_interface)]
1414+
pub trait MediaPreviewConfigListener: Sync + Send {
1415+
fn on_change(&self, media_preview_config: MediaPreviewConfig);
13761416
}
13771417

13781418
#[matrix_sdk_ffi_macros::export(callback_interface)]

bindings/matrix-sdk-ffi/src/ruma.rs

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@ use ruma::{
3030
ignored_user_list::{IgnoredUser as RumaIgnoredUser, IgnoredUserListEventContent},
3131
location::AssetType as RumaAssetType,
3232
marked_unread::{MarkedUnreadEventContent, UnstableMarkedUnreadEventContent},
33+
media_preview_config::{
34+
InviteAvatars as RumaInviteAvatars, MediaPreviewConfigEventContent,
35+
MediaPreviews as RumaMediaPreviews,
36+
},
3337
poll::start::PollKind as RumaPollKind,
3438
push_rules::PushRulesEventContent,
3539
room::{
@@ -1104,6 +1108,61 @@ pub enum AccountDataEvent {
11041108
},
11051109
}
11061110

1111+
#[derive(Clone, uniffi::Enum, Default)]
1112+
pub enum MediaPreviews {
1113+
#[default]
1114+
On,
1115+
Private,
1116+
Off,
1117+
}
1118+
1119+
impl From<RumaMediaPreviews> for MediaPreviews {
1120+
fn from(value: RumaMediaPreviews) -> Self {
1121+
match value {
1122+
RumaMediaPreviews::On => Self::On,
1123+
RumaMediaPreviews::Private => Self::Private,
1124+
RumaMediaPreviews::Off => Self::Off,
1125+
_ => Default::default(),
1126+
}
1127+
}
1128+
}
1129+
1130+
impl From<MediaPreviews> for RumaMediaPreviews {
1131+
fn from(value: MediaPreviews) -> Self {
1132+
match value {
1133+
MediaPreviews::On => Self::On,
1134+
MediaPreviews::Private => Self::Private,
1135+
MediaPreviews::Off => Self::Off,
1136+
}
1137+
}
1138+
}
1139+
1140+
#[derive(Clone, uniffi::Enum, Default)]
1141+
pub enum InviteAvatars {
1142+
#[default]
1143+
On,
1144+
Off,
1145+
}
1146+
1147+
impl From<RumaInviteAvatars> for InviteAvatars {
1148+
fn from(value: RumaInviteAvatars) -> Self {
1149+
match value {
1150+
RumaInviteAvatars::On => Self::On,
1151+
RumaInviteAvatars::Off => Self::Off,
1152+
_ => Default::default(),
1153+
}
1154+
}
1155+
}
1156+
1157+
impl From<InviteAvatars> for RumaInviteAvatars {
1158+
fn from(value: InviteAvatars) -> Self {
1159+
match value {
1160+
InviteAvatars::On => Self::On,
1161+
InviteAvatars::Off => Self::Off,
1162+
}
1163+
}
1164+
}
1165+
11071166
/// Details about an ignored user.
11081167
///
11091168
/// This is currently empty.
@@ -1351,6 +1410,24 @@ impl From<RumaSecretStorageV1AesHmacSha2Properties> for SecretStorageV1AesHmacSh
13511410
}
13521411
}
13531412

1413+
#[derive(Clone, uniffi::Record, Default)]
1414+
pub struct MediaPreviewConfig {
1415+
/// The media previews setting for the user.
1416+
pub media_previews: MediaPreviews,
1417+
1418+
/// The invite avatars setting for the user.
1419+
pub invite_avatars: InviteAvatars,
1420+
}
1421+
1422+
impl From<MediaPreviewConfigEventContent> for MediaPreviewConfig {
1423+
fn from(value: MediaPreviewConfigEventContent) -> Self {
1424+
Self {
1425+
media_previews: value.media_previews.into(),
1426+
invite_avatars: value.invite_avatars.into(),
1427+
}
1428+
}
1429+
}
1430+
13541431
/// A passphrase from which a key is to be derived.
13551432
#[derive(Clone, uniffi::Record)]
13561433
pub struct PassPhrase {

crates/matrix-sdk/src/account.rs

Lines changed: 108 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
// See the License for the specific language governing permissions and
1515
// limitations under the License.
1616

17+
use futures_core::Stream;
18+
use futures_util::{stream, StreamExt};
1719
use matrix_sdk_base::{
1820
media::{MediaFormat, MediaRequestParameters},
1921
store::StateStoreExt,
@@ -36,9 +38,13 @@ use ruma::{
3638
assign,
3739
events::{
3840
ignored_user_list::{IgnoredUser, IgnoredUserListEventContent},
41+
media_preview_config::{
42+
InviteAvatars, MediaPreviewConfigEventContent, MediaPreviews,
43+
UnstableMediaPreviewConfigEventContent,
44+
},
3945
push_rules::PushRulesEventContent,
4046
room::MediaSource,
41-
AnyGlobalAccountDataEventContent, GlobalAccountDataEventContent,
47+
AnyGlobalAccountDataEventContent, GlobalAccountDataEvent, GlobalAccountDataEventContent,
4248
GlobalAccountDataEventType, StaticEventContent,
4349
},
4450
push::Ruleset,
@@ -980,6 +986,107 @@ impl Account {
980986
.await?;
981987
Ok(())
982988
}
989+
990+
/// Observes the media preview configuration.
991+
///
992+
/// This will return the initial value of the configuration and a stream
993+
/// that will yield new values as they are received.
994+
///
995+
/// The initial value is the one that was stored in the account data
996+
/// when the client was started. If no value was found in either the stable
997+
/// and unstable type, the default configuration will be returned.
998+
/// This is a temporary solution until we know which matrix version will
999+
/// support the stable type.
1000+
pub async fn observe_media_preview_config(
1001+
&self,
1002+
) -> Result<
1003+
(MediaPreviewConfigEventContent, impl Stream<Item = MediaPreviewConfigEventContent>),
1004+
Error,
1005+
> {
1006+
// First we check if there is avalue in the stable event
1007+
let initial_value =
1008+
self.fetch_account_data(GlobalAccountDataEventType::MediaPreviewConfig).await?;
1009+
1010+
let initial_value = if let Some(initial_value) = initial_value {
1011+
Some(initial_value)
1012+
} else {
1013+
// If there is no value in the stable event, we check the unstable
1014+
self.fetch_account_data(GlobalAccountDataEventType::UnstableMediaPreviewConfig).await?
1015+
};
1016+
1017+
// We deserialize the content of the event, if is not found we return the
1018+
// default
1019+
let initial_value = initial_value
1020+
.and_then(|value| value.deserialize_as::<MediaPreviewConfigEventContent>().ok())
1021+
.unwrap_or_default();
1022+
1023+
// We need to create two observers, one for the stable event and one for the
1024+
// unstable and combine them into a single stream.
1025+
let first_observer = self
1026+
.client
1027+
.observe_events::<GlobalAccountDataEvent<MediaPreviewConfigEventContent>, ()>();
1028+
1029+
let stream = first_observer.subscribe().map(|event| event.0.content);
1030+
1031+
let second_observer = self
1032+
.client
1033+
.observe_events::<GlobalAccountDataEvent<UnstableMediaPreviewConfigEventContent>, ()>();
1034+
1035+
let second_stream = second_observer.subscribe().map(|event| event.0.content.0);
1036+
1037+
let mut combined_stream = stream::select(stream, second_stream);
1038+
1039+
let result_stream = async_stream::stream! {
1040+
// The observers need to be alive for the individual streams to be alive, so let's now
1041+
// create a stream that takes ownership of them.
1042+
let _first_observer = first_observer;
1043+
let _second_observer = second_observer;
1044+
1045+
while let Some(item) = combined_stream.next().await {
1046+
yield item
1047+
}
1048+
};
1049+
1050+
Ok((initial_value, result_stream))
1051+
}
1052+
1053+
/// Set the media previews display policy in the timeline.
1054+
///
1055+
/// This will always use the unstable event until we know which matrix
1056+
/// version will support it.
1057+
pub async fn set_media_previews_display_policy(&self, policy: MediaPreviews) -> Result<()> {
1058+
let mut media_preview_config = self.get_media_preview_config_event_content().await?;
1059+
media_preview_config.0.media_previews = policy;
1060+
1061+
// Updating the account data
1062+
self.set_account_data(media_preview_config).await?;
1063+
Ok(())
1064+
}
1065+
1066+
/// Set the display policy for avatars in invite requests.
1067+
///
1068+
/// This will always use the unstable event until we know which matrix
1069+
/// version will support it.
1070+
pub async fn set_invite_avatars_display_policy(&self, policy: InviteAvatars) -> Result<()> {
1071+
let mut media_preview_config = self.get_media_preview_config_event_content().await?;
1072+
media_preview_config.0.invite_avatars = policy;
1073+
1074+
// Updating the account data
1075+
self.set_account_data(media_preview_config).await?;
1076+
Ok(())
1077+
}
1078+
1079+
async fn get_media_preview_config_event_content(
1080+
&self,
1081+
) -> Result<UnstableMediaPreviewConfigEventContent> {
1082+
let media_preview_config = self
1083+
.account_data::<UnstableMediaPreviewConfigEventContent>()
1084+
.await?
1085+
.map(|c| c.deserialize())
1086+
.transpose()?
1087+
.unwrap_or_default();
1088+
Ok(media_preview_config)
1089+
}
9831090
}
9841091

9851092
fn get_raw_content<Ev, C>(raw: Option<Raw<Ev>>) -> Result<Option<Raw<C>>> {

0 commit comments

Comments
 (0)