matrix_sdk_ffi/
notification_settings.rs

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::{PredefinedOverrideRuleId, PredefinedUnderrideRuleId, RuleKind},
14    RoomId,
15};
16use tokio::sync::RwLock as AsyncRwLock;
17
18use crate::error::NotificationSettingsError;
19
20/// Enum representing the push notification modes for a room.
21#[derive(Clone, uniffi::Enum)]
22pub enum RoomNotificationMode {
23    /// Receive notifications for all messages.
24    AllMessages,
25    /// Receive notifications for mentions and keywords only.
26    MentionsAndKeywordsOnly,
27    /// Do not receive any notifications.
28    Mute,
29}
30
31impl From<SdkRoomNotificationMode> for RoomNotificationMode {
32    fn from(value: SdkRoomNotificationMode) -> Self {
33        match value {
34            SdkRoomNotificationMode::AllMessages => Self::AllMessages,
35            SdkRoomNotificationMode::MentionsAndKeywordsOnly => Self::MentionsAndKeywordsOnly,
36            SdkRoomNotificationMode::Mute => Self::Mute,
37        }
38    }
39}
40
41impl From<RoomNotificationMode> for SdkRoomNotificationMode {
42    fn from(value: RoomNotificationMode) -> Self {
43        match value {
44            RoomNotificationMode::AllMessages => Self::AllMessages,
45            RoomNotificationMode::MentionsAndKeywordsOnly => Self::MentionsAndKeywordsOnly,
46            RoomNotificationMode::Mute => Self::Mute,
47        }
48    }
49}
50
51/// Delegate to notify of changes in push rules
52#[matrix_sdk_ffi_macros::export(callback_interface)]
53pub trait NotificationSettingsDelegate: Sync + Send {
54    fn settings_did_change(&self);
55}
56
57/// `RoomNotificationSettings` represents the current settings for a `Room`
58#[derive(Clone, uniffi::Record)]
59pub struct RoomNotificationSettings {
60    /// The room notification mode
61    mode: RoomNotificationMode,
62    /// Whether the mode is the default one
63    is_default: bool,
64}
65
66impl RoomNotificationSettings {
67    fn new(mode: RoomNotificationMode, is_default: bool) -> Self {
68        RoomNotificationSettings { mode, is_default }
69    }
70}
71
72#[derive(Clone, uniffi::Object)]
73pub struct NotificationSettings {
74    sdk_client: MatrixClient,
75    sdk_notification_settings: Arc<AsyncRwLock<SdkNotificationSettings>>,
76    pushrules_event_handler: Arc<RwLock<Option<EventHandlerHandle>>>,
77}
78
79impl NotificationSettings {
80    pub(crate) fn new(
81        sdk_client: MatrixClient,
82        sdk_notification_settings: SdkNotificationSettings,
83    ) -> Self {
84        Self {
85            sdk_client,
86            sdk_notification_settings: Arc::new(AsyncRwLock::new(sdk_notification_settings)),
87            pushrules_event_handler: Arc::new(RwLock::new(None)),
88        }
89    }
90}
91
92impl Drop for NotificationSettings {
93    fn drop(&mut self) {
94        // Remove the event handler on the sdk_client.
95        if let Some(event_handler) = self.pushrules_event_handler.read().unwrap().as_ref() {
96            self.sdk_client.remove_event_handler(event_handler.clone());
97        }
98    }
99}
100
101#[matrix_sdk_ffi_macros::export]
102impl NotificationSettings {
103    pub fn set_delegate(&self, delegate: Option<Box<dyn NotificationSettingsDelegate>>) {
104        if let Some(delegate) = delegate {
105            let delegate: Arc<dyn NotificationSettingsDelegate> = Arc::from(delegate);
106
107            // Add an event handler to listen to `PushRulesEvent`
108            let event_handler =
109                self.sdk_client.add_event_handler(move |_: PushRulesEvent| async move {
110                    delegate.settings_did_change();
111                });
112
113            *self.pushrules_event_handler.write().unwrap() = Some(event_handler);
114        } else {
115            // Remove the event handler if there is no delegate
116            let event_handler = &mut *self.pushrules_event_handler.write().unwrap();
117            if let Some(event_handler) = event_handler {
118                self.sdk_client.remove_event_handler(event_handler.clone());
119            }
120            *event_handler = None;
121        }
122    }
123
124    /// Get the notification settings for a room.
125    ///
126    /// # Arguments
127    ///
128    /// * `room_id` - the room ID
129    /// * `is_encrypted` - whether the room is encrypted
130    /// * `is_one_to_one` - whether the room is a direct chat involving two
131    ///   people
132    pub async fn get_room_notification_settings(
133        &self,
134        room_id: String,
135        is_encrypted: bool,
136        is_one_to_one: bool,
137    ) -> Result<RoomNotificationSettings, NotificationSettingsError> {
138        let parsed_room_id = RoomId::parse(&room_id)
139            .map_err(|_e| NotificationSettingsError::InvalidRoomId { room_id })?;
140
141        let notification_settings = self.sdk_notification_settings.read().await;
142
143        // Get the current user defined mode for this room
144        if let Some(mode) =
145            notification_settings.get_user_defined_room_notification_mode(&parsed_room_id).await
146        {
147            return Ok(RoomNotificationSettings::new(mode.into(), false));
148        }
149
150        // If the user has not defined a notification mode, return the default one for
151        // this room
152        let mode = notification_settings
153            .get_default_room_notification_mode(is_encrypted.into(), is_one_to_one.into())
154            .await;
155
156        Ok(RoomNotificationSettings::new(mode.into(), true))
157    }
158
159    /// Set the notification mode for a room.
160    pub async fn set_room_notification_mode(
161        &self,
162        room_id: String,
163        mode: RoomNotificationMode,
164    ) -> Result<(), NotificationSettingsError> {
165        let parsed_room_id = RoomId::parse(&room_id)
166            .map_err(|_e| NotificationSettingsError::InvalidRoomId { room_id })?;
167
168        self.sdk_notification_settings
169            .read()
170            .await
171            .set_room_notification_mode(&parsed_room_id, mode.into())
172            .await?;
173
174        Ok(())
175    }
176
177    /// Get the user defined room notification mode
178    pub async fn get_user_defined_room_notification_mode(
179        &self,
180        room_id: String,
181    ) -> Result<Option<RoomNotificationMode>, NotificationSettingsError> {
182        let notification_settings = self.sdk_notification_settings.read().await;
183        let parsed_room_id = RoomId::parse(&room_id)
184            .map_err(|_e| NotificationSettingsError::InvalidRoomId { room_id })?;
185        // Get the current user defined mode for this room
186        if let Some(mode) =
187            notification_settings.get_user_defined_room_notification_mode(&parsed_room_id).await
188        {
189            Ok(Some(mode.into()))
190        } else {
191            Ok(None)
192        }
193    }
194
195    /// Get the default room notification mode
196    ///
197    /// The mode will depend on the associated `PushRule` based on whether the
198    /// room is encrypted or not, and on the number of members.
199    ///
200    /// # Arguments
201    ///
202    /// * `is_encrypted` - whether the room is encrypted
203    /// * `is_one_to_one` - whether the room is a direct chats involving two
204    ///   people
205    pub async fn get_default_room_notification_mode(
206        &self,
207        is_encrypted: bool,
208        is_one_to_one: bool,
209    ) -> RoomNotificationMode {
210        let notification_settings = self.sdk_notification_settings.read().await;
211        let mode = notification_settings
212            .get_default_room_notification_mode(is_encrypted.into(), is_one_to_one.into())
213            .await;
214        mode.into()
215    }
216
217    /// Set the default room notification mode
218    ///
219    /// # Arguments
220    ///
221    /// * `is_encrypted` - whether the mode is for encrypted rooms
222    /// * `is_one_to_one` - whether the mode is for direct chats involving two
223    ///   people
224    /// * `mode` - the new default mode
225    pub async fn set_default_room_notification_mode(
226        &self,
227        is_encrypted: bool,
228        is_one_to_one: bool,
229        mode: RoomNotificationMode,
230    ) -> Result<(), NotificationSettingsError> {
231        let notification_settings = self.sdk_notification_settings.read().await;
232        notification_settings
233            .set_default_room_notification_mode(
234                is_encrypted.into(),
235                is_one_to_one.into(),
236                mode.into(),
237            )
238            .await?;
239        Ok(())
240    }
241
242    /// Restore the default notification mode for a room
243    pub async fn restore_default_room_notification_mode(
244        &self,
245        room_id: String,
246    ) -> Result<(), NotificationSettingsError> {
247        let notification_settings = self.sdk_notification_settings.read().await;
248        let parsed_room_id = RoomId::parse(&room_id)
249            .map_err(|_e| NotificationSettingsError::InvalidRoomId { room_id })?;
250        notification_settings.delete_user_defined_room_rules(&parsed_room_id).await?;
251        Ok(())
252    }
253
254    /// Get all room IDs for which a user-defined rule exists.
255    pub async fn get_rooms_with_user_defined_rules(&self, enabled: Option<bool>) -> Vec<String> {
256        let notification_settings = self.sdk_notification_settings.read().await;
257        notification_settings.get_rooms_with_user_defined_rules(enabled).await
258    }
259
260    /// Get whether some enabled keyword rules exist.
261    pub async fn contains_keywords_rules(&self) -> bool {
262        let notification_settings = self.sdk_notification_settings.read().await;
263        notification_settings.contains_keyword_rules().await
264    }
265
266    /// Get whether room mentions are enabled.
267    pub async fn is_room_mention_enabled(&self) -> Result<bool, NotificationSettingsError> {
268        let notification_settings = self.sdk_notification_settings.read().await;
269        let enabled = notification_settings
270            .is_push_rule_enabled(RuleKind::Override, PredefinedOverrideRuleId::IsRoomMention)
271            .await?;
272        Ok(enabled)
273    }
274
275    /// Set whether room mentions are enabled.
276    pub async fn set_room_mention_enabled(
277        &self,
278        enabled: bool,
279    ) -> Result<(), NotificationSettingsError> {
280        let notification_settings = self.sdk_notification_settings.read().await;
281        notification_settings
282            .set_push_rule_enabled(
283                RuleKind::Override,
284                PredefinedOverrideRuleId::IsRoomMention,
285                enabled,
286            )
287            .await?;
288        Ok(())
289    }
290
291    /// Get whether user mentions are enabled.
292    pub async fn is_user_mention_enabled(&self) -> Result<bool, NotificationSettingsError> {
293        let notification_settings = self.sdk_notification_settings.read().await;
294        let enabled = notification_settings
295            .is_push_rule_enabled(RuleKind::Override, PredefinedOverrideRuleId::IsUserMention)
296            .await?;
297        Ok(enabled)
298    }
299
300    /// Returns true if [MSC 4028 push rule][rule] is supported and enabled.
301    ///
302    /// [rule]: https://github.com/matrix-org/matrix-spec-proposals/blob/giomfo/push_encrypted_events/proposals/4028-push-all-encrypted-events-except-for-muted-rooms.md
303    pub async fn can_push_encrypted_event_to_device(&self) -> bool {
304        let notification_settings = self.sdk_notification_settings.read().await;
305        // Check stable identifier
306        if let Ok(enabled) = notification_settings
307            .is_push_rule_enabled(RuleKind::Override, ".m.rule.encrypted_event")
308            .await
309        {
310            enabled
311        } else {
312            // Check unstable identifier
313            notification_settings
314                .is_push_rule_enabled(RuleKind::Override, ".org.matrix.msc4028.encrypted_event")
315                .await
316                .unwrap_or(false)
317        }
318    }
319
320    /// Check whether [MSC 4028 push rule][rule] is enabled on the homeserver.
321    ///
322    /// [rule]: https://github.com/matrix-org/matrix-spec-proposals/blob/giomfo/push_encrypted_events/proposals/4028-push-all-encrypted-events-except-for-muted-rooms.md
323    pub async fn can_homeserver_push_encrypted_event_to_device(&self) -> bool {
324        self.sdk_client.can_homeserver_push_encrypted_event_to_device().await.unwrap()
325    }
326
327    /// Set whether user mentions are enabled.
328    pub async fn set_user_mention_enabled(
329        &self,
330        enabled: bool,
331    ) -> Result<(), NotificationSettingsError> {
332        let notification_settings = self.sdk_notification_settings.read().await;
333        notification_settings
334            .set_push_rule_enabled(
335                RuleKind::Override,
336                PredefinedOverrideRuleId::IsUserMention,
337                enabled,
338            )
339            .await?;
340        Ok(())
341    }
342
343    /// Get whether the `.m.rule.call` push rule is enabled
344    pub async fn is_call_enabled(&self) -> Result<bool, NotificationSettingsError> {
345        let notification_settings = self.sdk_notification_settings.read().await;
346        let enabled = notification_settings
347            .is_push_rule_enabled(RuleKind::Underride, PredefinedUnderrideRuleId::Call)
348            .await?;
349        Ok(enabled)
350    }
351
352    /// Set whether the `.m.rule.call` push rule is enabled
353    pub async fn set_call_enabled(&self, enabled: bool) -> Result<(), NotificationSettingsError> {
354        let notification_settings = self.sdk_notification_settings.read().await;
355        notification_settings
356            .set_push_rule_enabled(RuleKind::Underride, PredefinedUnderrideRuleId::Call, enabled)
357            .await?;
358        Ok(())
359    }
360
361    /// Get whether the `.m.rule.invite_for_me` push rule is enabled
362    pub async fn is_invite_for_me_enabled(&self) -> Result<bool, NotificationSettingsError> {
363        let notification_settings = self.sdk_notification_settings.read().await;
364        let enabled = notification_settings
365            .is_push_rule_enabled(
366                RuleKind::Override,
367                PredefinedOverrideRuleId::InviteForMe.as_str(),
368            )
369            .await?;
370        Ok(enabled)
371    }
372
373    /// Set whether the `.m.rule.invite_for_me` push rule is enabled
374    pub async fn set_invite_for_me_enabled(
375        &self,
376        enabled: bool,
377    ) -> Result<(), NotificationSettingsError> {
378        let notification_settings = self.sdk_notification_settings.read().await;
379        notification_settings
380            .set_push_rule_enabled(
381                RuleKind::Override,
382                PredefinedOverrideRuleId::InviteForMe.as_str(),
383                enabled,
384            )
385            .await?;
386        Ok(())
387    }
388
389    /// Unmute a room.
390    ///
391    /// # Arguments
392    ///
393    /// * `room_id` - the room to unmute
394    /// * `is_encrypted` - whether the room is encrypted
395    /// * `is_one_to_one` - whether the room is a direct chat involving two
396    ///   people
397    pub async fn unmute_room(
398        &self,
399        room_id: String,
400        is_encrypted: bool,
401        is_one_to_one: bool,
402    ) -> Result<(), NotificationSettingsError> {
403        let notification_settings = self.sdk_notification_settings.read().await;
404        let parsed_room_id = RoomId::parse(&room_id)
405            .map_err(|_e| NotificationSettingsError::InvalidRoomId { room_id })?;
406        notification_settings
407            .unmute_room(&parsed_room_id, is_encrypted.into(), is_one_to_one.into())
408            .await?;
409        Ok(())
410    }
411}