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 ruma::{
13 push::{
14 Action as SdkAction, ComparisonOperator as SdkComparisonOperator, PredefinedOverrideRuleId,
15 PredefinedUnderrideRuleId, PushCondition as SdkPushCondition, RoomMemberCountIs,
16 RuleKind as SdkRuleKind, ScalarJsonValue as SdkJsonValue, Tweak as SdkTweak,
17 },
18 Int, RoomId, UInt,
19};
20use tokio::sync::RwLock as AsyncRwLock;
21
22use crate::error::NotificationSettingsError;
23
24#[derive(Clone, Default, uniffi::Enum)]
25pub enum ComparisonOperator {
26 #[default]
28 Eq,
29
30 Lt,
32
33 Gt,
35
36 Ge,
38
39 Le,
41}
42
43impl From<SdkComparisonOperator> for ComparisonOperator {
44 fn from(value: SdkComparisonOperator) -> Self {
45 match value {
46 SdkComparisonOperator::Eq => Self::Eq,
47 SdkComparisonOperator::Lt => Self::Lt,
48 SdkComparisonOperator::Gt => Self::Gt,
49 SdkComparisonOperator::Ge => Self::Ge,
50 SdkComparisonOperator::Le => Self::Le,
51 }
52 }
53}
54
55impl From<ComparisonOperator> for SdkComparisonOperator {
56 fn from(value: ComparisonOperator) -> Self {
57 match value {
58 ComparisonOperator::Eq => Self::Eq,
59 ComparisonOperator::Lt => Self::Lt,
60 ComparisonOperator::Gt => Self::Gt,
61 ComparisonOperator::Ge => Self::Ge,
62 ComparisonOperator::Le => Self::Le,
63 }
64 }
65}
66
67#[derive(Debug, Clone, Default, uniffi::Enum)]
68pub enum JsonValue {
69 #[default]
71 Null,
72
73 Bool { value: bool },
75
76 Integer { value: i64 },
78
79 String { value: String },
81}
82
83impl From<SdkJsonValue> for JsonValue {
84 fn from(value: SdkJsonValue) -> Self {
85 match value {
86 SdkJsonValue::Null => Self::Null,
87 SdkJsonValue::Bool(b) => Self::Bool { value: b },
88 SdkJsonValue::Integer(i) => Self::Integer { value: i.into() },
89 SdkJsonValue::String(s) => Self::String { value: s },
90 }
91 }
92}
93
94impl From<JsonValue> for SdkJsonValue {
95 fn from(value: JsonValue) -> Self {
96 match value {
97 JsonValue::Null => Self::Null,
98 JsonValue::Bool { value } => Self::Bool(value),
99 JsonValue::Integer { value } => Self::Integer(Int::new(value).unwrap_or_default()),
100 JsonValue::String { value } => Self::String(value),
101 }
102 }
103}
104
105#[derive(Clone, uniffi::Enum)]
106pub enum PushCondition {
107 EventMatch {
109 key: String,
113
114 pattern: String,
119 },
120
121 ContainsDisplayName,
124
125 RoomMemberCount { prefix: ComparisonOperator, count: u64 },
127
128 SenderNotificationPermission {
132 key: String,
138 },
139
140 EventPropertyIs {
142 key: String,
146
147 value: JsonValue,
149 },
150
151 EventPropertyContains {
153 key: String,
157
158 value: JsonValue,
160 },
161}
162
163impl TryFrom<SdkPushCondition> for PushCondition {
164 type Error = ();
165
166 fn try_from(value: SdkPushCondition) -> Result<Self, Self::Error> {
167 Ok(match value {
168 SdkPushCondition::EventMatch { key, pattern } => Self::EventMatch { key, pattern },
169 SdkPushCondition::ContainsDisplayName => Self::ContainsDisplayName,
170 SdkPushCondition::RoomMemberCount { is } => {
171 Self::RoomMemberCount { prefix: is.prefix.into(), count: is.count.into() }
172 }
173 SdkPushCondition::SenderNotificationPermission { key } => {
174 Self::SenderNotificationPermission { key }
175 }
176 SdkPushCondition::EventPropertyIs { key, value } => {
177 Self::EventPropertyIs { key, value: value.into() }
178 }
179 SdkPushCondition::EventPropertyContains { key, value } => {
180 Self::EventPropertyContains { key, value: value.into() }
181 }
182 _ => return Err(()),
183 })
184 }
185}
186
187impl From<PushCondition> for SdkPushCondition {
188 fn from(value: PushCondition) -> Self {
189 match value {
190 PushCondition::EventMatch { key, pattern } => Self::EventMatch { key, pattern },
191 PushCondition::ContainsDisplayName => Self::ContainsDisplayName,
192 PushCondition::RoomMemberCount { prefix, count } => Self::RoomMemberCount {
193 is: RoomMemberCountIs {
194 prefix: prefix.into(),
195 count: UInt::new(count).unwrap_or_default(),
196 },
197 },
198 PushCondition::SenderNotificationPermission { key } => {
199 Self::SenderNotificationPermission { key }
200 }
201 PushCondition::EventPropertyIs { key, value } => {
202 Self::EventPropertyIs { key, value: value.into() }
203 }
204 PushCondition::EventPropertyContains { key, value } => {
205 Self::EventPropertyContains { key, value: value.into() }
206 }
207 }
208 }
209}
210
211#[derive(Clone, uniffi::Enum)]
212pub enum RuleKind {
213 Override,
215
216 Underride,
218
219 Sender,
221
222 Room,
224
225 Content,
227
228 Custom {
229 value: String,
230 },
231}
232
233impl From<SdkRuleKind> for RuleKind {
234 fn from(value: SdkRuleKind) -> Self {
235 match value {
236 SdkRuleKind::Override => Self::Override,
237 SdkRuleKind::Underride => Self::Underride,
238 SdkRuleKind::Sender => Self::Sender,
239 SdkRuleKind::Room => Self::Room,
240 SdkRuleKind::Content => Self::Content,
241 SdkRuleKind::_Custom(_) => Self::Custom { value: value.as_str().to_owned() },
242 _ => Self::Custom { value: value.to_string() },
243 }
244 }
245}
246
247impl From<RuleKind> for SdkRuleKind {
248 fn from(value: RuleKind) -> Self {
249 match value {
250 RuleKind::Override => Self::Override,
251 RuleKind::Underride => Self::Underride,
252 RuleKind::Sender => Self::Sender,
253 RuleKind::Room => Self::Room,
254 RuleKind::Content => Self::Content,
255 RuleKind::Custom { value } => SdkRuleKind::from(value),
256 }
257 }
258}
259
260#[derive(Clone, uniffi::Enum)]
261pub enum Tweak {
263 Sound { value: String },
269
270 Highlight { value: bool },
273
274 Custom {
276 name: String,
278
279 value: String,
281 },
282}
283
284impl TryFrom<SdkTweak> for Tweak {
285 type Error = String;
286
287 fn try_from(value: SdkTweak) -> Result<Self, Self::Error> {
288 Ok(match value {
289 SdkTweak::Sound(sound) => Self::Sound { value: sound },
290 SdkTweak::Highlight(highlight) => Self::Highlight { value: highlight },
291 SdkTweak::Custom { name, value } => {
292 let json_string = serde_json::to_string(&value)
293 .map_err(|e| format!("Failed to serialize custom tweak value: {}", e))?;
294
295 Self::Custom { name, value: json_string }
296 }
297 _ => return Err("Unsupported tweak type".to_owned()),
298 })
299 }
300}
301
302impl TryFrom<Tweak> for SdkTweak {
303 type Error = String;
304
305 fn try_from(value: Tweak) -> Result<Self, Self::Error> {
306 Ok(match value {
307 Tweak::Sound { value } => Self::Sound(value),
308 Tweak::Highlight { value } => Self::Highlight(value),
309 Tweak::Custom { name, value } => {
310 let json_value: serde_json::Value = serde_json::from_str(&value)
311 .map_err(|e| format!("Failed to deserialize custom tweak value: {}", e))?;
312 let value = serde_json::from_value(json_value)
313 .map_err(|e| format!("Failed to convert JSON value: {}", e))?;
314
315 Self::Custom { name, value }
316 }
317 })
318 }
319}
320
321#[derive(Clone, uniffi::Enum)]
322pub enum Action {
324 Notify,
326 SetTweak { value: Tweak },
328}
329
330impl TryFrom<SdkAction> for Action {
331 type Error = String;
332
333 fn try_from(value: SdkAction) -> Result<Self, Self::Error> {
334 Ok(match value {
335 SdkAction::Notify => Self::Notify,
336 SdkAction::SetTweak(tweak) => Self::SetTweak {
337 value: tweak.try_into().map_err(|e| format!("Failed to convert tweak: {}", e))?,
338 },
339 _ => return Err("Unsupported action type".to_owned()),
340 })
341 }
342}
343
344impl TryFrom<Action> for SdkAction {
345 type Error = String;
346
347 fn try_from(value: Action) -> Result<Self, Self::Error> {
348 Ok(match value {
349 Action::Notify => Self::Notify,
350 Action::SetTweak { value } => Self::SetTweak(
351 value.try_into().map_err(|e| format!("Failed to convert tweak: {}", e))?,
352 ),
353 })
354 }
355}
356
357#[derive(Clone, uniffi::Enum)]
359pub enum RoomNotificationMode {
360 AllMessages,
362 MentionsAndKeywordsOnly,
364 Mute,
366}
367
368impl From<SdkRoomNotificationMode> for RoomNotificationMode {
369 fn from(value: SdkRoomNotificationMode) -> Self {
370 match value {
371 SdkRoomNotificationMode::AllMessages => Self::AllMessages,
372 SdkRoomNotificationMode::MentionsAndKeywordsOnly => Self::MentionsAndKeywordsOnly,
373 SdkRoomNotificationMode::Mute => Self::Mute,
374 }
375 }
376}
377
378impl From<RoomNotificationMode> for SdkRoomNotificationMode {
379 fn from(value: RoomNotificationMode) -> Self {
380 match value {
381 RoomNotificationMode::AllMessages => Self::AllMessages,
382 RoomNotificationMode::MentionsAndKeywordsOnly => Self::MentionsAndKeywordsOnly,
383 RoomNotificationMode::Mute => Self::Mute,
384 }
385 }
386}
387
388#[matrix_sdk_ffi_macros::export(callback_interface)]
390pub trait NotificationSettingsDelegate: Sync + Send {
391 fn settings_did_change(&self);
392}
393
394#[derive(Clone, uniffi::Record)]
396pub struct RoomNotificationSettings {
397 mode: RoomNotificationMode,
399 is_default: bool,
401}
402
403impl RoomNotificationSettings {
404 fn new(mode: RoomNotificationMode, is_default: bool) -> Self {
405 RoomNotificationSettings { mode, is_default }
406 }
407}
408
409#[derive(Clone, uniffi::Object)]
410pub struct NotificationSettings {
411 sdk_client: MatrixClient,
412 sdk_notification_settings: Arc<AsyncRwLock<SdkNotificationSettings>>,
413 pushrules_event_handler: Arc<RwLock<Option<EventHandlerHandle>>>,
414}
415
416impl NotificationSettings {
417 pub(crate) fn new(
418 sdk_client: MatrixClient,
419 sdk_notification_settings: SdkNotificationSettings,
420 ) -> Self {
421 Self {
422 sdk_client,
423 sdk_notification_settings: Arc::new(AsyncRwLock::new(sdk_notification_settings)),
424 pushrules_event_handler: Arc::new(RwLock::new(None)),
425 }
426 }
427}
428
429impl Drop for NotificationSettings {
430 fn drop(&mut self) {
431 if let Some(event_handler) = self.pushrules_event_handler.read().unwrap().as_ref() {
433 self.sdk_client.remove_event_handler(event_handler.clone());
434 }
435 }
436}
437
438#[matrix_sdk_ffi_macros::export]
439impl NotificationSettings {
440 pub fn set_delegate(&self, delegate: Option<Box<dyn NotificationSettingsDelegate>>) {
441 if let Some(delegate) = delegate {
442 let delegate: Arc<dyn NotificationSettingsDelegate> = Arc::from(delegate);
443
444 let event_handler =
446 self.sdk_client.add_event_handler(move |_: PushRulesEvent| async move {
447 delegate.settings_did_change();
448 });
449
450 *self.pushrules_event_handler.write().unwrap() = Some(event_handler);
451 } else {
452 let event_handler = &mut *self.pushrules_event_handler.write().unwrap();
454 if let Some(event_handler) = event_handler {
455 self.sdk_client.remove_event_handler(event_handler.clone());
456 }
457 *event_handler = None;
458 }
459 }
460
461 pub async fn get_room_notification_settings(
470 &self,
471 room_id: String,
472 is_encrypted: bool,
473 is_one_to_one: bool,
474 ) -> Result<RoomNotificationSettings, NotificationSettingsError> {
475 let parsed_room_id = RoomId::parse(&room_id)
476 .map_err(|_e| NotificationSettingsError::InvalidRoomId { room_id })?;
477
478 let notification_settings = self.sdk_notification_settings.read().await;
479
480 if let Some(mode) =
482 notification_settings.get_user_defined_room_notification_mode(&parsed_room_id).await
483 {
484 return Ok(RoomNotificationSettings::new(mode.into(), false));
485 }
486
487 let mode = notification_settings
490 .get_default_room_notification_mode(is_encrypted.into(), is_one_to_one.into())
491 .await;
492
493 Ok(RoomNotificationSettings::new(mode.into(), true))
494 }
495
496 pub async fn set_room_notification_mode(
498 &self,
499 room_id: String,
500 mode: RoomNotificationMode,
501 ) -> Result<(), NotificationSettingsError> {
502 let parsed_room_id = RoomId::parse(&room_id)
503 .map_err(|_e| NotificationSettingsError::InvalidRoomId { room_id })?;
504
505 self.sdk_notification_settings
506 .read()
507 .await
508 .set_room_notification_mode(&parsed_room_id, mode.into())
509 .await?;
510
511 Ok(())
512 }
513
514 pub async fn get_user_defined_room_notification_mode(
516 &self,
517 room_id: String,
518 ) -> Result<Option<RoomNotificationMode>, NotificationSettingsError> {
519 let notification_settings = self.sdk_notification_settings.read().await;
520 let parsed_room_id = RoomId::parse(&room_id)
521 .map_err(|_e| NotificationSettingsError::InvalidRoomId { room_id })?;
522 if let Some(mode) =
524 notification_settings.get_user_defined_room_notification_mode(&parsed_room_id).await
525 {
526 Ok(Some(mode.into()))
527 } else {
528 Ok(None)
529 }
530 }
531
532 pub async fn get_default_room_notification_mode(
543 &self,
544 is_encrypted: bool,
545 is_one_to_one: bool,
546 ) -> RoomNotificationMode {
547 let notification_settings = self.sdk_notification_settings.read().await;
548 let mode = notification_settings
549 .get_default_room_notification_mode(is_encrypted.into(), is_one_to_one.into())
550 .await;
551 mode.into()
552 }
553
554 pub async fn set_default_room_notification_mode(
563 &self,
564 is_encrypted: bool,
565 is_one_to_one: bool,
566 mode: RoomNotificationMode,
567 ) -> Result<(), NotificationSettingsError> {
568 let notification_settings = self.sdk_notification_settings.read().await;
569 notification_settings
570 .set_default_room_notification_mode(
571 is_encrypted.into(),
572 is_one_to_one.into(),
573 mode.into(),
574 )
575 .await?;
576 Ok(())
577 }
578
579 pub async fn restore_default_room_notification_mode(
581 &self,
582 room_id: String,
583 ) -> Result<(), NotificationSettingsError> {
584 let notification_settings = self.sdk_notification_settings.read().await;
585 let parsed_room_id = RoomId::parse(&room_id)
586 .map_err(|_e| NotificationSettingsError::InvalidRoomId { room_id })?;
587 notification_settings.delete_user_defined_room_rules(&parsed_room_id).await?;
588 Ok(())
589 }
590
591 pub async fn get_rooms_with_user_defined_rules(&self, enabled: Option<bool>) -> Vec<String> {
593 let notification_settings = self.sdk_notification_settings.read().await;
594 notification_settings.get_rooms_with_user_defined_rules(enabled).await
595 }
596
597 pub async fn contains_keywords_rules(&self) -> bool {
599 let notification_settings = self.sdk_notification_settings.read().await;
600 notification_settings.contains_keyword_rules().await
601 }
602
603 pub async fn is_room_mention_enabled(&self) -> Result<bool, NotificationSettingsError> {
605 let notification_settings = self.sdk_notification_settings.read().await;
606 let enabled = notification_settings
607 .is_push_rule_enabled(SdkRuleKind::Override, PredefinedOverrideRuleId::IsRoomMention)
608 .await?;
609 Ok(enabled)
610 }
611
612 pub async fn set_room_mention_enabled(
614 &self,
615 enabled: bool,
616 ) -> Result<(), NotificationSettingsError> {
617 let notification_settings = self.sdk_notification_settings.read().await;
618 notification_settings
619 .set_push_rule_enabled(
620 SdkRuleKind::Override,
621 PredefinedOverrideRuleId::IsRoomMention,
622 enabled,
623 )
624 .await?;
625 Ok(())
626 }
627
628 pub async fn is_user_mention_enabled(&self) -> Result<bool, NotificationSettingsError> {
630 let notification_settings = self.sdk_notification_settings.read().await;
631 let enabled = notification_settings
632 .is_push_rule_enabled(SdkRuleKind::Override, PredefinedOverrideRuleId::IsUserMention)
633 .await?;
634 Ok(enabled)
635 }
636
637 pub async fn can_push_encrypted_event_to_device(&self) -> bool {
641 let notification_settings = self.sdk_notification_settings.read().await;
642 if let Ok(enabled) = notification_settings
644 .is_push_rule_enabled(SdkRuleKind::Override, ".m.rule.encrypted_event")
645 .await
646 {
647 enabled
648 } else {
649 notification_settings
651 .is_push_rule_enabled(SdkRuleKind::Override, ".org.matrix.msc4028.encrypted_event")
652 .await
653 .unwrap_or(false)
654 }
655 }
656
657 pub async fn can_homeserver_push_encrypted_event_to_device(&self) -> bool {
661 self.sdk_client.can_homeserver_push_encrypted_event_to_device().await.unwrap()
662 }
663
664 pub async fn set_user_mention_enabled(
666 &self,
667 enabled: bool,
668 ) -> Result<(), NotificationSettingsError> {
669 let notification_settings = self.sdk_notification_settings.read().await;
670 notification_settings
671 .set_push_rule_enabled(
672 SdkRuleKind::Override,
673 PredefinedOverrideRuleId::IsUserMention,
674 enabled,
675 )
676 .await?;
677 Ok(())
678 }
679
680 pub async fn is_call_enabled(&self) -> Result<bool, NotificationSettingsError> {
682 let notification_settings = self.sdk_notification_settings.read().await;
683 let enabled = notification_settings
684 .is_push_rule_enabled(SdkRuleKind::Underride, PredefinedUnderrideRuleId::Call)
685 .await?;
686 Ok(enabled)
687 }
688
689 pub async fn set_call_enabled(&self, enabled: bool) -> Result<(), NotificationSettingsError> {
691 let notification_settings = self.sdk_notification_settings.read().await;
692 notification_settings
693 .set_push_rule_enabled(SdkRuleKind::Underride, PredefinedUnderrideRuleId::Call, enabled)
694 .await?;
695 Ok(())
696 }
697
698 pub async fn is_invite_for_me_enabled(&self) -> Result<bool, NotificationSettingsError> {
700 let notification_settings = self.sdk_notification_settings.read().await;
701 let enabled = notification_settings
702 .is_push_rule_enabled(
703 SdkRuleKind::Override,
704 PredefinedOverrideRuleId::InviteForMe.as_str(),
705 )
706 .await?;
707 Ok(enabled)
708 }
709
710 pub async fn set_invite_for_me_enabled(
712 &self,
713 enabled: bool,
714 ) -> Result<(), NotificationSettingsError> {
715 let notification_settings = self.sdk_notification_settings.read().await;
716 notification_settings
717 .set_push_rule_enabled(
718 SdkRuleKind::Override,
719 PredefinedOverrideRuleId::InviteForMe.as_str(),
720 enabled,
721 )
722 .await?;
723 Ok(())
724 }
725
726 pub async fn set_custom_push_rule(
728 &self,
729 rule_id: String,
730 rule_kind: RuleKind,
731 actions: Vec<Action>,
732 conditions: Vec<PushCondition>,
733 ) -> Result<(), NotificationSettingsError> {
734 let notification_settings = self.sdk_notification_settings.read().await;
735 let actions: Result<Vec<_>, _> =
736 actions.into_iter().map(|action| action.try_into()).collect();
737 let actions = actions.map_err(|e| NotificationSettingsError::Generic { msg: e })?;
738
739 notification_settings
740 .create_custom_conditional_push_rule(
741 rule_id,
742 rule_kind.into(),
743 actions,
744 conditions.into_iter().map(|condition| condition.into()).collect(),
745 )
746 .await?;
747 Ok(())
748 }
749
750 pub async fn unmute_room(
759 &self,
760 room_id: String,
761 is_encrypted: bool,
762 is_one_to_one: bool,
763 ) -> Result<(), NotificationSettingsError> {
764 let notification_settings = self.sdk_notification_settings.read().await;
765 let parsed_room_id = RoomId::parse(&room_id)
766 .map_err(|_e| NotificationSettingsError::InvalidRoomId { room_id })?;
767 notification_settings
768 .unmute_room(&parsed_room_id, is_encrypted.into(), is_one_to_one.into())
769 .await?;
770 Ok(())
771 }
772}