tauri_plugin_notifications/
models.rs

1// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
2// SPDX-License-Identifier: Apache-2.0
3// SPDX-License-Identifier: MIT
4
5use std::{collections::HashMap, fmt::Display};
6
7use serde::{de::Error as DeError, Deserialize, Deserializer, Serialize, Serializer};
8
9use url::Url;
10
11#[derive(Debug, Serialize, Deserialize)]
12#[serde(rename_all = "camelCase")]
13pub struct Attachment {
14    id: String,
15    url: Url,
16}
17
18impl Attachment {
19    pub fn new(id: impl Into<String>, url: Url) -> Self {
20        Self { id: id.into(), url }
21    }
22}
23
24#[derive(Debug, Default, Serialize, Deserialize)]
25#[serde(rename_all = "camelCase")]
26pub struct ScheduleInterval {
27    pub year: Option<u8>,
28    pub month: Option<u8>,
29    pub day: Option<u8>,
30    pub weekday: Option<u8>,
31    pub hour: Option<u8>,
32    pub minute: Option<u8>,
33    pub second: Option<u8>,
34}
35
36#[derive(Debug)]
37pub enum ScheduleEvery {
38    Year,
39    Month,
40    TwoWeeks,
41    Week,
42    Day,
43    Hour,
44    Minute,
45    Second,
46}
47
48impl Display for ScheduleEvery {
49    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
50        write!(
51            f,
52            "{}",
53            match self {
54                Self::Year => "year",
55                Self::Month => "month",
56                Self::TwoWeeks => "twoWeeks",
57                Self::Week => "week",
58                Self::Day => "day",
59                Self::Hour => "hour",
60                Self::Minute => "minute",
61                Self::Second => "second",
62            }
63        )
64    }
65}
66
67impl Serialize for ScheduleEvery {
68    fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
69    where
70        S: Serializer,
71    {
72        serializer.serialize_str(self.to_string().as_ref())
73    }
74}
75
76impl<'de> Deserialize<'de> for ScheduleEvery {
77    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
78    where
79        D: Deserializer<'de>,
80    {
81        let s = String::deserialize(deserializer)?;
82        match s.to_lowercase().as_str() {
83            "year" => Ok(Self::Year),
84            "month" => Ok(Self::Month),
85            "twoweeks" => Ok(Self::TwoWeeks),
86            "week" => Ok(Self::Week),
87            "day" => Ok(Self::Day),
88            "hour" => Ok(Self::Hour),
89            "minute" => Ok(Self::Minute),
90            "second" => Ok(Self::Second),
91            _ => Err(DeError::custom(format!("unknown every kind '{s}'"))),
92        }
93    }
94}
95
96#[derive(Debug, Serialize, Deserialize)]
97#[serde(rename_all = "camelCase")]
98pub enum Schedule {
99    #[serde(rename_all = "camelCase")]
100    At {
101        #[serde(
102            serialize_with = "iso8601::serialize",
103            deserialize_with = "time::serde::iso8601::deserialize"
104        )]
105        date: time::OffsetDateTime,
106        #[serde(default)]
107        repeating: bool,
108        #[serde(default)]
109        allow_while_idle: bool,
110    },
111    #[serde(rename_all = "camelCase")]
112    Interval {
113        interval: ScheduleInterval,
114        #[serde(default)]
115        allow_while_idle: bool,
116    },
117    #[serde(rename_all = "camelCase")]
118    Every {
119        interval: ScheduleEvery,
120        count: u8,
121        #[serde(default)]
122        allow_while_idle: bool,
123    },
124}
125
126// custom ISO-8601 serialization that does not use 6 digits for years.
127mod iso8601 {
128    use serde::{ser::Error as _, Serialize, Serializer};
129    use time::{
130        format_description::well_known::iso8601::{Config, EncodedConfig},
131        format_description::well_known::Iso8601,
132        OffsetDateTime,
133    };
134
135    const SERDE_CONFIG: EncodedConfig = Config::DEFAULT.encode();
136
137    pub fn serialize<S: Serializer>(
138        datetime: &OffsetDateTime,
139        serializer: S,
140    ) -> Result<S::Ok, S::Error> {
141        datetime
142            .format(&Iso8601::<SERDE_CONFIG>)
143            .map_err(S::Error::custom)?
144            .serialize(serializer)
145    }
146}
147
148#[derive(Debug, Serialize, Deserialize)]
149#[serde(rename_all = "camelCase")]
150pub struct NotificationData {
151    #[serde(default = "default_id")]
152    pub(crate) id: i32,
153    pub(crate) channel_id: Option<String>,
154    pub(crate) title: Option<String>,
155    pub(crate) body: Option<String>,
156    pub(crate) schedule: Option<Schedule>,
157    pub(crate) large_body: Option<String>,
158    pub(crate) summary: Option<String>,
159    pub(crate) action_type_id: Option<String>,
160    pub(crate) group: Option<String>,
161    #[serde(default)]
162    pub(crate) group_summary: bool,
163    pub(crate) sound: Option<String>,
164    #[serde(default)]
165    pub(crate) inbox_lines: Vec<String>,
166    pub(crate) icon: Option<String>,
167    pub(crate) large_icon: Option<String>,
168    pub(crate) icon_color: Option<String>,
169    #[serde(default)]
170    pub(crate) attachments: Vec<Attachment>,
171    #[serde(default)]
172    pub(crate) extra: HashMap<String, serde_json::Value>,
173    #[serde(default)]
174    pub(crate) ongoing: bool,
175    #[serde(default)]
176    pub(crate) auto_cancel: bool,
177    #[serde(default)]
178    pub(crate) silent: bool,
179}
180
181fn default_id() -> i32 {
182    rand::random()
183}
184
185impl Default for NotificationData {
186    fn default() -> Self {
187        Self {
188            id: default_id(),
189            channel_id: None,
190            title: None,
191            body: None,
192            schedule: None,
193            large_body: None,
194            summary: None,
195            action_type_id: None,
196            group: None,
197            group_summary: false,
198            sound: None,
199            inbox_lines: Vec::new(),
200            icon: None,
201            large_icon: None,
202            icon_color: None,
203            attachments: Vec::new(),
204            extra: Default::default(),
205            ongoing: false,
206            auto_cancel: false,
207            silent: false,
208        }
209    }
210}
211
212#[derive(Debug, Deserialize)]
213#[serde(rename_all = "camelCase")]
214pub struct PendingNotification {
215    id: i32,
216    title: Option<String>,
217    body: Option<String>,
218    schedule: Schedule,
219}
220
221impl PendingNotification {
222    pub fn id(&self) -> i32 {
223        self.id
224    }
225
226    pub fn title(&self) -> Option<&str> {
227        self.title.as_deref()
228    }
229
230    pub fn body(&self) -> Option<&str> {
231        self.body.as_deref()
232    }
233
234    pub fn schedule(&self) -> &Schedule {
235        &self.schedule
236    }
237}
238
239#[derive(Debug, Deserialize)]
240#[serde(rename_all = "camelCase")]
241pub struct ActiveNotification {
242    id: i32,
243    tag: Option<String>,
244    title: Option<String>,
245    body: Option<String>,
246    group: Option<String>,
247    #[serde(default)]
248    group_summary: bool,
249    #[serde(default)]
250    data: HashMap<String, String>,
251    #[serde(default)]
252    extra: HashMap<String, serde_json::Value>,
253    #[serde(default)]
254    attachments: Vec<Attachment>,
255    action_type_id: Option<String>,
256    schedule: Option<Schedule>,
257    sound: Option<String>,
258}
259
260impl ActiveNotification {
261    pub fn id(&self) -> i32 {
262        self.id
263    }
264
265    pub fn tag(&self) -> Option<&str> {
266        self.tag.as_deref()
267    }
268
269    pub fn title(&self) -> Option<&str> {
270        self.title.as_deref()
271    }
272
273    pub fn body(&self) -> Option<&str> {
274        self.body.as_deref()
275    }
276
277    pub fn group(&self) -> Option<&str> {
278        self.group.as_deref()
279    }
280
281    pub fn group_summary(&self) -> bool {
282        self.group_summary
283    }
284
285    pub fn data(&self) -> &HashMap<String, String> {
286        &self.data
287    }
288
289    pub fn extra(&self) -> &HashMap<String, serde_json::Value> {
290        &self.extra
291    }
292
293    pub fn attachments(&self) -> &[Attachment] {
294        &self.attachments
295    }
296
297    pub fn action_type_id(&self) -> Option<&str> {
298        self.action_type_id.as_deref()
299    }
300
301    pub fn schedule(&self) -> Option<&Schedule> {
302        self.schedule.as_ref()
303    }
304
305    pub fn sound(&self) -> Option<&str> {
306        self.sound.as_deref()
307    }
308}
309
310#[cfg(mobile)]
311#[derive(Debug, Serialize)]
312#[serde(rename_all = "camelCase")]
313pub struct ActionType {
314    id: String,
315    actions: Vec<Action>,
316    hidden_previews_body_placeholder: Option<String>,
317    custom_dismiss_action: bool,
318    allow_in_car_play: bool,
319    hidden_previews_show_title: bool,
320    hidden_previews_show_subtitle: bool,
321}
322
323#[cfg(mobile)]
324#[derive(Debug, Serialize)]
325#[serde(rename_all = "camelCase")]
326pub struct Action {
327    id: String,
328    title: String,
329    requires_authentication: bool,
330    foreground: bool,
331    destructive: bool,
332    input: bool,
333    input_button_title: Option<String>,
334    input_placeholder: Option<String>,
335}
336
337#[cfg(target_os = "android")]
338pub use android::*;
339
340#[cfg(target_os = "android")]
341mod android {
342    use serde::{Deserialize, Serialize};
343    use serde_repr::{Deserialize_repr, Serialize_repr};
344
345    #[derive(Debug, Clone, Copy, Serialize_repr, Deserialize_repr)]
346    #[repr(u8)]
347    pub enum Importance {
348        None = 0,
349        Min = 1,
350        Low = 2,
351        Default = 3,
352        High = 4,
353    }
354
355    impl Default for Importance {
356        fn default() -> Self {
357            Self::Default
358        }
359    }
360
361    #[derive(Debug, Clone, Copy, Serialize_repr, Deserialize_repr)]
362    #[repr(i8)]
363    pub enum Visibility {
364        Secret = -1,
365        Private = 0,
366        Public = 1,
367    }
368
369    #[derive(Debug, Serialize, Deserialize)]
370    #[serde(rename_all = "camelCase")]
371    pub struct Channel {
372        id: String,
373        name: String,
374        description: Option<String>,
375        sound: Option<String>,
376        lights: bool,
377        light_color: Option<String>,
378        vibration: bool,
379        importance: Importance,
380        visibility: Option<Visibility>,
381    }
382
383    #[derive(Debug)]
384    pub struct ChannelBuilder(Channel);
385
386    impl Channel {
387        pub fn builder(id: impl Into<String>, name: impl Into<String>) -> ChannelBuilder {
388            ChannelBuilder(Self {
389                id: id.into(),
390                name: name.into(),
391                description: None,
392                sound: None,
393                lights: false,
394                light_color: None,
395                vibration: false,
396                importance: Default::default(),
397                visibility: None,
398            })
399        }
400
401        pub fn id(&self) -> &str {
402            &self.id
403        }
404
405        pub fn name(&self) -> &str {
406            &self.name
407        }
408
409        pub fn description(&self) -> Option<&str> {
410            self.description.as_deref()
411        }
412
413        pub fn sound(&self) -> Option<&str> {
414            self.sound.as_deref()
415        }
416
417        pub fn lights(&self) -> bool {
418            self.lights
419        }
420
421        pub fn light_color(&self) -> Option<&str> {
422            self.light_color.as_deref()
423        }
424
425        pub fn vibration(&self) -> bool {
426            self.vibration
427        }
428
429        pub fn importance(&self) -> Importance {
430            self.importance
431        }
432
433        pub fn visibility(&self) -> Option<Visibility> {
434            self.visibility
435        }
436    }
437
438    impl ChannelBuilder {
439        pub fn description(mut self, description: impl Into<String>) -> Self {
440            self.0.description.replace(description.into());
441            self
442        }
443
444        pub fn sound(mut self, sound: impl Into<String>) -> Self {
445            self.0.sound.replace(sound.into());
446            self
447        }
448
449        pub fn lights(mut self, lights: bool) -> Self {
450            self.0.lights = lights;
451            self
452        }
453
454        pub fn light_color(mut self, color: impl Into<String>) -> Self {
455            self.0.light_color.replace(color.into());
456            self
457        }
458
459        pub fn vibration(mut self, vibration: bool) -> Self {
460            self.0.vibration = vibration;
461            self
462        }
463
464        pub fn importance(mut self, importance: Importance) -> Self {
465            self.0.importance = importance;
466            self
467        }
468
469        pub fn visibility(mut self, visibility: Visibility) -> Self {
470            self.0.visibility.replace(visibility);
471            self
472        }
473
474        pub fn build(self) -> Channel {
475            self.0
476        }
477    }
478}