1use std::sync::{Arc, RwLock};
2
3use matrix_sdk::{
4 event_handler::EventHandlerHandle,
5 notification_settings::{
6 NotificationSettings as SdkNotificationSettings,
7 RoomNotificationMode as SdkRoomNotificationMode,
8 },
9 ruma::events::push_rules::PushRulesEvent,
10 Client as MatrixClient,
11};
12use matrix_sdk_common::{SendOutsideWasm, SyncOutsideWasm};
13use ruma::{
14 events::push_rules::PushRulesEventContent,
15 push::{
16 Action as SdkAction, ComparisonOperator as SdkComparisonOperator, PredefinedOverrideRuleId,
17 PredefinedUnderrideRuleId, PushCondition as SdkPushCondition, RoomMemberCountIs,
18 RuleKind as SdkRuleKind, ScalarJsonValue as SdkJsonValue, Tweak as SdkTweak,
19 },
20 Int, RoomId, UInt,
21};
22use tokio::sync::RwLock as AsyncRwLock;
23
24use crate::error::{ClientError, NotificationSettingsError};
25
26#[derive(Clone, Default, uniffi::Enum)]
27pub enum ComparisonOperator {
28 #[default]
30 Eq,
31
32 Lt,
34
35 Gt,
37
38 Ge,
40
41 Le,
43}
44
45impl From<SdkComparisonOperator> for ComparisonOperator {
46 fn from(value: SdkComparisonOperator) -> Self {
47 match value {
48 SdkComparisonOperator::Eq => Self::Eq,
49 SdkComparisonOperator::Lt => Self::Lt,
50 SdkComparisonOperator::Gt => Self::Gt,
51 SdkComparisonOperator::Ge => Self::Ge,
52 SdkComparisonOperator::Le => Self::Le,
53 }
54 }
55}
56
57impl From<ComparisonOperator> for SdkComparisonOperator {
58 fn from(value: ComparisonOperator) -> Self {
59 match value {
60 ComparisonOperator::Eq => Self::Eq,
61 ComparisonOperator::Lt => Self::Lt,
62 ComparisonOperator::Gt => Self::Gt,
63 ComparisonOperator::Ge => Self::Ge,
64 ComparisonOperator::Le => Self::Le,
65 }
66 }
67}
68
69#[derive(Debug, Clone, Default, uniffi::Enum)]
70pub enum JsonValue {
71 #[default]
73 Null,
74
75 Bool { value: bool },
77
78 Integer { value: i64 },
80
81 String { value: String },
83}
84
85impl From<SdkJsonValue> for JsonValue {
86 fn from(value: SdkJsonValue) -> Self {
87 match value {
88 SdkJsonValue::Null => Self::Null,
89 SdkJsonValue::Bool(b) => Self::Bool { value: b },
90 SdkJsonValue::Integer(i) => Self::Integer { value: i.into() },
91 SdkJsonValue::String(s) => Self::String { value: s },
92 }
93 }
94}
95
96impl From<JsonValue> for SdkJsonValue {
97 fn from(value: JsonValue) -> Self {
98 match value {
99 JsonValue::Null => Self::Null,
100 JsonValue::Bool { value } => Self::Bool(value),
101 JsonValue::Integer { value } => Self::Integer(Int::new(value).unwrap_or_default()),
102 JsonValue::String { value } => Self::String(value),
103 }
104 }
105}
106
107#[derive(Clone, uniffi::Enum)]
108pub enum PushCondition {
109 EventMatch {
111 key: String,
115
116 pattern: String,
121 },
122
123 ContainsDisplayName,
126
127 RoomMemberCount { prefix: ComparisonOperator, count: u64 },
129
130 SenderNotificationPermission {
134 key: String,
140 },
141
142 EventPropertyIs {
144 key: String,
148
149 value: JsonValue,
151 },
152
153 EventPropertyContains {
155 key: String,
159
160 value: JsonValue,
162 },
163}
164
165impl TryFrom<SdkPushCondition> for PushCondition {
166 type Error = String;
167
168 fn try_from(value: SdkPushCondition) -> Result<Self, Self::Error> {
169 Ok(match value {
170 SdkPushCondition::EventMatch { key, pattern } => Self::EventMatch { key, pattern },
171 #[allow(deprecated)]
172 SdkPushCondition::ContainsDisplayName => Self::ContainsDisplayName,
173 SdkPushCondition::RoomMemberCount { is } => {
174 Self::RoomMemberCount { prefix: is.prefix.into(), count: is.count.into() }
175 }
176 SdkPushCondition::SenderNotificationPermission { key } => {
177 Self::SenderNotificationPermission { key: key.to_string() }
178 }
179 SdkPushCondition::EventPropertyIs { key, value } => {
180 Self::EventPropertyIs { key, value: value.into() }
181 }
182 SdkPushCondition::EventPropertyContains { key, value } => {
183 Self::EventPropertyContains { key, value: value.into() }
184 }
185 _ => return Err("Unsupported condition type".to_owned()),
186 })
187 }
188}
189
190impl From<PushCondition> for SdkPushCondition {
191 fn from(value: PushCondition) -> Self {
192 match value {
193 PushCondition::EventMatch { key, pattern } => Self::EventMatch { key, pattern },
194 #[allow(deprecated)]
195 PushCondition::ContainsDisplayName => Self::ContainsDisplayName,
196 PushCondition::RoomMemberCount { prefix, count } => Self::RoomMemberCount {
197 is: RoomMemberCountIs {
198 prefix: prefix.into(),
199 count: UInt::new(count).unwrap_or_default(),
200 },
201 },
202 PushCondition::SenderNotificationPermission { key } => {
203 Self::SenderNotificationPermission { key: key.into() }
204 }
205 PushCondition::EventPropertyIs { key, value } => {
206 Self::EventPropertyIs { key, value: value.into() }
207 }
208 PushCondition::EventPropertyContains { key, value } => {
209 Self::EventPropertyContains { key, value: value.into() }
210 }
211 }
212 }
213}
214
215#[derive(Clone, uniffi::Enum)]
216pub enum RuleKind {
217 Override,
219
220 Underride,
222
223 Sender,
225
226 Room,
228
229 Content,
231
232 Custom {
233 value: String,
234 },
235}
236
237impl From<SdkRuleKind> for RuleKind {
238 fn from(value: SdkRuleKind) -> Self {
239 match value {
240 SdkRuleKind::Override => Self::Override,
241 SdkRuleKind::Underride => Self::Underride,
242 SdkRuleKind::Sender => Self::Sender,
243 SdkRuleKind::Room => Self::Room,
244 SdkRuleKind::Content => Self::Content,
245 SdkRuleKind::_Custom(_) => Self::Custom { value: value.as_str().to_owned() },
246 _ => Self::Custom { value: value.to_string() },
247 }
248 }
249}
250
251impl From<RuleKind> for SdkRuleKind {
252 fn from(value: RuleKind) -> Self {
253 match value {
254 RuleKind::Override => Self::Override,
255 RuleKind::Underride => Self::Underride,
256 RuleKind::Sender => Self::Sender,
257 RuleKind::Room => Self::Room,
258 RuleKind::Content => Self::Content,
259 RuleKind::Custom { value } => SdkRuleKind::from(value),
260 }
261 }
262}
263
264#[derive(Clone, uniffi::Enum)]
265pub enum Tweak {
267 Sound { value: String },
273
274 Highlight { value: bool },
277
278 Custom {
280 name: String,
282
283 value: String,
285 },
286}
287
288impl TryFrom<SdkTweak> for Tweak {
289 type Error = String;
290
291 fn try_from(value: SdkTweak) -> Result<Self, Self::Error> {
292 Ok(match value {
293 SdkTweak::Sound(sound) => Self::Sound { value: sound },
294 SdkTweak::Highlight(highlight) => Self::Highlight { value: highlight },
295 SdkTweak::Custom { name, value } => {
296 let json_string = serde_json::to_string(&value)
297 .map_err(|e| format!("Failed to serialize custom tweak value: {e}"))?;
298
299 Self::Custom { name, value: json_string }
300 }
301 _ => return Err("Unsupported tweak type".to_owned()),
302 })
303 }
304}
305
306impl TryFrom<Tweak> for SdkTweak {
307 type Error = String;
308
309 fn try_from(value: Tweak) -> Result<Self, Self::Error> {
310 Ok(match value {
311 Tweak::Sound { value } => Self::Sound(value),
312 Tweak::Highlight { value } => Self::Highlight(value),
313 Tweak::Custom { name, value } => {
314 let json_value: serde_json::Value = serde_json::from_str(&value)
315 .map_err(|e| format!("Failed to deserialize custom tweak value: {e}"))?;
316 let value = serde_json::from_value(json_value)
317 .map_err(|e| format!("Failed to convert JSON value: {e}"))?;
318
319 Self::Custom { name, value }
320 }
321 })
322 }
323}
324
325#[derive(Clone, uniffi::Enum)]
326pub enum Action {
328 Notify,
330 SetTweak { value: Tweak },
332}
333
334impl TryFrom<SdkAction> for Action {
335 type Error = String;
336
337 fn try_from(value: SdkAction) -> Result<Self, Self::Error> {
338 Ok(match value {
339 SdkAction::Notify => Self::Notify,
340 SdkAction::SetTweak(tweak) => Self::SetTweak {
341 value: tweak.try_into().map_err(|e| format!("Failed to convert tweak: {e}"))?,
342 },
343 _ => return Err("Unsupported action type".to_owned()),
344 })
345 }
346}
347
348impl TryFrom<Action> for SdkAction {
349 type Error = String;
350
351 fn try_from(value: Action) -> Result<Self, Self::Error> {
352 Ok(match value {
353 Action::Notify => Self::Notify,
354 Action::SetTweak { value } => Self::SetTweak(
355 value.try_into().map_err(|e| format!("Failed to convert tweak: {e}"))?,
356 ),
357 })
358 }
359}
360
361#[derive(Clone, uniffi::Enum)]
363pub enum RoomNotificationMode {
364 AllMessages,
366 MentionsAndKeywordsOnly,
368 Mute,
370}
371
372impl From<SdkRoomNotificationMode> for RoomNotificationMode {
373 fn from(value: SdkRoomNotificationMode) -> Self {
374 match value {
375 SdkRoomNotificationMode::AllMessages => Self::AllMessages,
376 SdkRoomNotificationMode::MentionsAndKeywordsOnly => Self::MentionsAndKeywordsOnly,
377 SdkRoomNotificationMode::Mute => Self::Mute,
378 }
379 }
380}
381
382impl From<RoomNotificationMode> for SdkRoomNotificationMode {
383 fn from(value: RoomNotificationMode) -> Self {
384 match value {
385 RoomNotificationMode::AllMessages => Self::AllMessages,
386 RoomNotificationMode::MentionsAndKeywordsOnly => Self::MentionsAndKeywordsOnly,
387 RoomNotificationMode::Mute => Self::Mute,
388 }
389 }
390}
391
392#[matrix_sdk_ffi_macros::export(callback_interface)]
394pub trait NotificationSettingsDelegate: SyncOutsideWasm + SendOutsideWasm {
395 fn settings_did_change(&self);
396}
397
398#[derive(Clone, uniffi::Record)]
400pub struct RoomNotificationSettings {
401 mode: RoomNotificationMode,
403 is_default: bool,
405}
406
407impl RoomNotificationSettings {
408 fn new(mode: RoomNotificationMode, is_default: bool) -> Self {
409 RoomNotificationSettings { mode, is_default }
410 }
411}
412
413#[derive(Clone, uniffi::Object)]
414pub struct NotificationSettings {
415 sdk_client: MatrixClient,
416 sdk_notification_settings: Arc<AsyncRwLock<SdkNotificationSettings>>,
417 pushrules_event_handler: Arc<RwLock<Option<EventHandlerHandle>>>,
418}
419
420impl NotificationSettings {
421 pub(crate) fn new(
422 sdk_client: MatrixClient,
423 sdk_notification_settings: SdkNotificationSettings,
424 ) -> Self {
425 Self {
426 sdk_client,
427 sdk_notification_settings: Arc::new(AsyncRwLock::new(sdk_notification_settings)),
428 pushrules_event_handler: Arc::new(RwLock::new(None)),
429 }
430 }
431}
432
433impl Drop for NotificationSettings {
434 fn drop(&mut self) {
435 if let Some(event_handler) = self.pushrules_event_handler.read().unwrap().as_ref() {
437 self.sdk_client.remove_event_handler(event_handler.clone());
438 }
439 }
440}
441
442#[matrix_sdk_ffi_macros::export]
443impl NotificationSettings {
444 pub fn set_delegate(&self, delegate: Option<Box<dyn NotificationSettingsDelegate>>) {
445 if let Some(delegate) = delegate {
446 let delegate: Arc<dyn NotificationSettingsDelegate> = Arc::from(delegate);
447
448 let event_handler =
450 self.sdk_client.add_event_handler(move |_: PushRulesEvent| async move {
451 delegate.settings_did_change();
452 });
453
454 *self.pushrules_event_handler.write().unwrap() = Some(event_handler);
455 } else {
456 let event_handler = &mut *self.pushrules_event_handler.write().unwrap();
458 if let Some(event_handler) = event_handler {
459 self.sdk_client.remove_event_handler(event_handler.clone());
460 }
461 *event_handler = None;
462 }
463 }
464
465 pub async fn get_room_notification_settings(
474 &self,
475 room_id: String,
476 is_encrypted: bool,
477 is_one_to_one: bool,
478 ) -> Result<RoomNotificationSettings, NotificationSettingsError> {
479 let parsed_room_id = RoomId::parse(&room_id)
480 .map_err(|_e| NotificationSettingsError::InvalidRoomId { room_id })?;
481
482 let notification_settings = self.sdk_notification_settings.read().await;
483
484 if let Some(mode) =
486 notification_settings.get_user_defined_room_notification_mode(&parsed_room_id).await
487 {
488 return Ok(RoomNotificationSettings::new(mode.into(), false));
489 }
490
491 let mode = notification_settings
494 .get_default_room_notification_mode(is_encrypted.into(), is_one_to_one.into())
495 .await;
496
497 Ok(RoomNotificationSettings::new(mode.into(), true))
498 }
499
500 pub async fn set_room_notification_mode(
502 &self,
503 room_id: String,
504 mode: RoomNotificationMode,
505 ) -> Result<(), NotificationSettingsError> {
506 let parsed_room_id = RoomId::parse(&room_id)
507 .map_err(|_e| NotificationSettingsError::InvalidRoomId { room_id })?;
508
509 self.sdk_notification_settings
510 .read()
511 .await
512 .set_room_notification_mode(&parsed_room_id, mode.into())
513 .await?;
514
515 Ok(())
516 }
517
518 pub async fn get_user_defined_room_notification_mode(
520 &self,
521 room_id: String,
522 ) -> Result<Option<RoomNotificationMode>, NotificationSettingsError> {
523 let notification_settings = self.sdk_notification_settings.read().await;
524 let parsed_room_id = RoomId::parse(&room_id)
525 .map_err(|_e| NotificationSettingsError::InvalidRoomId { room_id })?;
526 if let Some(mode) =
528 notification_settings.get_user_defined_room_notification_mode(&parsed_room_id).await
529 {
530 Ok(Some(mode.into()))
531 } else {
532 Ok(None)
533 }
534 }
535
536 pub async fn get_default_room_notification_mode(
547 &self,
548 is_encrypted: bool,
549 is_one_to_one: bool,
550 ) -> RoomNotificationMode {
551 let notification_settings = self.sdk_notification_settings.read().await;
552 let mode = notification_settings
553 .get_default_room_notification_mode(is_encrypted.into(), is_one_to_one.into())
554 .await;
555 mode.into()
556 }
557
558 pub async fn set_default_room_notification_mode(
567 &self,
568 is_encrypted: bool,
569 is_one_to_one: bool,
570 mode: RoomNotificationMode,
571 ) -> Result<(), NotificationSettingsError> {
572 let notification_settings = self.sdk_notification_settings.read().await;
573 notification_settings
574 .set_default_room_notification_mode(
575 is_encrypted.into(),
576 is_one_to_one.into(),
577 mode.into(),
578 )
579 .await?;
580 Ok(())
581 }
582
583 pub async fn restore_default_room_notification_mode(
585 &self,
586 room_id: String,
587 ) -> Result<(), NotificationSettingsError> {
588 let notification_settings = self.sdk_notification_settings.read().await;
589 let parsed_room_id = RoomId::parse(&room_id)
590 .map_err(|_e| NotificationSettingsError::InvalidRoomId { room_id })?;
591 notification_settings.delete_user_defined_room_rules(&parsed_room_id).await?;
592 Ok(())
593 }
594
595 pub async fn get_rooms_with_user_defined_rules(&self, enabled: Option<bool>) -> Vec<String> {
597 let notification_settings = self.sdk_notification_settings.read().await;
598 notification_settings.get_rooms_with_user_defined_rules(enabled).await
599 }
600
601 pub async fn contains_keywords_rules(&self) -> bool {
603 let notification_settings = self.sdk_notification_settings.read().await;
604 notification_settings.contains_keyword_rules().await
605 }
606
607 pub async fn is_room_mention_enabled(&self) -> Result<bool, NotificationSettingsError> {
609 let notification_settings = self.sdk_notification_settings.read().await;
610 let enabled = notification_settings
611 .is_push_rule_enabled(SdkRuleKind::Override, PredefinedOverrideRuleId::IsRoomMention)
612 .await?;
613 Ok(enabled)
614 }
615
616 pub async fn set_room_mention_enabled(
618 &self,
619 enabled: bool,
620 ) -> Result<(), NotificationSettingsError> {
621 let notification_settings = self.sdk_notification_settings.read().await;
622 notification_settings
623 .set_push_rule_enabled(
624 SdkRuleKind::Override,
625 PredefinedOverrideRuleId::IsRoomMention,
626 enabled,
627 )
628 .await?;
629 Ok(())
630 }
631
632 pub async fn is_user_mention_enabled(&self) -> Result<bool, NotificationSettingsError> {
634 let notification_settings = self.sdk_notification_settings.read().await;
635 let enabled = notification_settings
636 .is_push_rule_enabled(SdkRuleKind::Override, PredefinedOverrideRuleId::IsUserMention)
637 .await?;
638 Ok(enabled)
639 }
640
641 pub async fn can_push_encrypted_event_to_device(&self) -> bool {
645 let notification_settings = self.sdk_notification_settings.read().await;
646 if let Ok(enabled) = notification_settings
648 .is_push_rule_enabled(SdkRuleKind::Override, ".m.rule.encrypted_event")
649 .await
650 {
651 enabled
652 } else {
653 notification_settings
655 .is_push_rule_enabled(SdkRuleKind::Override, ".org.matrix.msc4028.encrypted_event")
656 .await
657 .unwrap_or(false)
658 }
659 }
660
661 pub async fn can_homeserver_push_encrypted_event_to_device(&self) -> bool {
665 self.sdk_client.can_homeserver_push_encrypted_event_to_device().await.unwrap()
666 }
667
668 pub async fn set_user_mention_enabled(
670 &self,
671 enabled: bool,
672 ) -> Result<(), NotificationSettingsError> {
673 let notification_settings = self.sdk_notification_settings.read().await;
674 notification_settings
675 .set_push_rule_enabled(
676 SdkRuleKind::Override,
677 PredefinedOverrideRuleId::IsUserMention,
678 enabled,
679 )
680 .await?;
681 Ok(())
682 }
683
684 pub async fn is_call_enabled(&self) -> Result<bool, NotificationSettingsError> {
686 let notification_settings = self.sdk_notification_settings.read().await;
687 let enabled = notification_settings
688 .is_push_rule_enabled(SdkRuleKind::Underride, PredefinedUnderrideRuleId::Call)
689 .await?;
690 Ok(enabled)
691 }
692
693 pub async fn set_call_enabled(&self, enabled: bool) -> Result<(), NotificationSettingsError> {
695 let notification_settings = self.sdk_notification_settings.read().await;
696 notification_settings
697 .set_push_rule_enabled(SdkRuleKind::Underride, PredefinedUnderrideRuleId::Call, enabled)
698 .await?;
699 Ok(())
700 }
701
702 pub async fn is_invite_for_me_enabled(&self) -> Result<bool, NotificationSettingsError> {
704 let notification_settings = self.sdk_notification_settings.read().await;
705 let enabled = notification_settings
706 .is_push_rule_enabled(
707 SdkRuleKind::Override,
708 PredefinedOverrideRuleId::InviteForMe.as_str(),
709 )
710 .await?;
711 Ok(enabled)
712 }
713
714 pub async fn set_invite_for_me_enabled(
716 &self,
717 enabled: bool,
718 ) -> Result<(), NotificationSettingsError> {
719 let notification_settings = self.sdk_notification_settings.read().await;
720 notification_settings
721 .set_push_rule_enabled(
722 SdkRuleKind::Override,
723 PredefinedOverrideRuleId::InviteForMe.as_str(),
724 enabled,
725 )
726 .await?;
727 Ok(())
728 }
729
730 pub async fn set_custom_push_rule(
732 &self,
733 rule_id: String,
734 rule_kind: RuleKind,
735 actions: Vec<Action>,
736 conditions: Vec<PushCondition>,
737 ) -> Result<(), NotificationSettingsError> {
738 let notification_settings = self.sdk_notification_settings.read().await;
739 let actions: Result<Vec<_>, _> =
740 actions.into_iter().map(|action| action.try_into()).collect();
741 let actions = actions.map_err(|e| NotificationSettingsError::Generic { msg: e })?;
742
743 notification_settings
744 .create_custom_conditional_push_rule(
745 rule_id,
746 rule_kind.into(),
747 actions,
748 conditions.into_iter().map(|condition| condition.into()).collect(),
749 )
750 .await?;
751 Ok(())
752 }
753
754 pub async fn unmute_room(
763 &self,
764 room_id: String,
765 is_encrypted: bool,
766 is_one_to_one: bool,
767 ) -> Result<(), NotificationSettingsError> {
768 let notification_settings = self.sdk_notification_settings.read().await;
769 let parsed_room_id = RoomId::parse(&room_id)
770 .map_err(|_e| NotificationSettingsError::InvalidRoomId { room_id })?;
771 notification_settings
772 .unmute_room(&parsed_room_id, is_encrypted.into(), is_one_to_one.into())
773 .await?;
774 Ok(())
775 }
776
777 pub async fn get_raw_push_rules(&self) -> Result<Option<String>, ClientError> {
779 let raw_push_rules =
780 self.sdk_client.account().account_data::<PushRulesEventContent>().await?;
781 Ok(raw_push_rules.map(|raw| serde_json::to_string(&raw)).transpose()?)
782 }
783}