Skip to main content

tauri_plugin_notification/
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)]
325pub struct ActionTypeBuilder(ActionType);
326
327#[cfg(mobile)]
328impl ActionType {
329    pub fn builder(id: impl Into<String>) -> ActionTypeBuilder {
330        ActionTypeBuilder(Self {
331            id: id.into(),
332            actions: Vec::new(),
333            hidden_previews_body_placeholder: None,
334            custom_dismiss_action: false,
335            allow_in_car_play: false,
336            hidden_previews_show_title: false,
337            hidden_previews_show_subtitle: false,
338        })
339    }
340
341    pub fn id(&self) -> &str {
342        &self.id
343    }
344
345    pub fn actions(&self) -> &[Action] {
346        &self.actions
347    }
348
349    pub fn hidden_previews_body_placeholder(&self) -> Option<&str> {
350        self.hidden_previews_body_placeholder.as_deref()
351    }
352
353    pub fn custom_dismiss_action(&self) -> bool {
354        self.custom_dismiss_action
355    }
356
357    pub fn allow_in_car_play(&self) -> bool {
358        self.allow_in_car_play
359    }
360
361    pub fn hidden_previews_show_title(&self) -> bool {
362        self.hidden_previews_show_title
363    }
364
365    pub fn hidden_previews_show_subtitle(&self) -> bool {
366        self.hidden_previews_show_subtitle
367    }
368}
369
370#[cfg(mobile)]
371impl ActionTypeBuilder {
372    pub fn actions(mut self, actions: Vec<Action>) -> Self {
373        self.0.actions = actions;
374        self
375    }
376
377    pub fn hidden_previews_body_placeholder(
378        mut self,
379        hidden_previews_body_placeholder: impl Into<String>,
380    ) -> Self {
381        self.0
382            .hidden_previews_body_placeholder
383            .replace(hidden_previews_body_placeholder.into());
384        self
385    }
386
387    pub fn custom_dismiss_action(mut self, custom_dismiss_action: bool) -> Self {
388        self.0.custom_dismiss_action = custom_dismiss_action;
389        self
390    }
391
392    pub fn allow_in_car_play(mut self, allow_in_car_play: bool) -> Self {
393        self.0.allow_in_car_play = allow_in_car_play;
394        self
395    }
396
397    pub fn hidden_previews_show_title(mut self, hidden_previews_show_title: bool) -> Self {
398        self.0.hidden_previews_show_title = hidden_previews_show_title;
399        self
400    }
401
402    pub fn hidden_previews_show_subtitle(mut self, hidden_previews_show_subtitle: bool) -> Self {
403        self.0.hidden_previews_show_subtitle = hidden_previews_show_subtitle;
404        self
405    }
406
407    pub fn build(self) -> ActionType {
408        self.0
409    }
410}
411
412#[cfg(mobile)]
413#[derive(Debug, Serialize)]
414#[serde(rename_all = "camelCase")]
415pub struct Action {
416    id: String,
417    title: String,
418    requires_authentication: bool,
419    foreground: bool,
420    destructive: bool,
421    input: bool,
422    input_button_title: Option<String>,
423    input_placeholder: Option<String>,
424}
425
426#[cfg(mobile)]
427#[derive(Debug)]
428pub struct ActionBuilder(Action);
429
430#[cfg(mobile)]
431impl Action {
432    pub fn builder(id: impl Into<String>, title: impl Into<String>) -> ActionBuilder {
433        ActionBuilder(Self {
434            id: id.into(),
435            title: title.into(),
436            requires_authentication: false,
437            foreground: false,
438            destructive: false,
439            input: false,
440            input_button_title: None,
441            input_placeholder: None,
442        })
443    }
444
445    pub fn id(&self) -> &str {
446        &self.id
447    }
448
449    pub fn title(&self) -> &str {
450        &self.title
451    }
452
453    pub fn requires_authentication(&self) -> bool {
454        self.requires_authentication
455    }
456
457    pub fn foreground(&self) -> bool {
458        self.foreground
459    }
460
461    pub fn destructive(&self) -> bool {
462        self.destructive
463    }
464
465    pub fn input(&self) -> bool {
466        self.input
467    }
468
469    pub fn input_button_title(&self) -> Option<&str> {
470        self.input_button_title.as_deref()
471    }
472
473    pub fn input_placeholder(&self) -> Option<&str> {
474        self.input_placeholder.as_deref()
475    }
476}
477
478#[cfg(mobile)]
479impl ActionBuilder {
480    pub fn requires_authentication(mut self, requires_authentication: bool) -> Self {
481        self.0.requires_authentication = requires_authentication;
482        self
483    }
484
485    pub fn foreground(mut self, foreground: bool) -> Self {
486        self.0.foreground = foreground;
487        self
488    }
489
490    pub fn destructive(mut self, destructive: bool) -> Self {
491        self.0.destructive = destructive;
492        self
493    }
494
495    pub fn input(mut self, input: bool) -> Self {
496        self.0.input = input;
497        self
498    }
499
500    pub fn input_button_title(mut self, input_button_title: impl Into<String>) -> Self {
501        self.0.input_button_title.replace(input_button_title.into());
502        self
503    }
504
505    pub fn input_placeholder(mut self, input_placeholder: impl Into<String>) -> Self {
506        self.0.input_placeholder.replace(input_placeholder.into());
507        self
508    }
509
510    pub fn build(self) -> Action {
511        self.0
512    }
513}
514
515#[cfg(target_os = "android")]
516pub use android::*;
517
518#[cfg(target_os = "android")]
519mod android {
520    use serde::{Deserialize, Serialize};
521    use serde_repr::{Deserialize_repr, Serialize_repr};
522
523    #[derive(Debug, Clone, Copy, Serialize_repr, Deserialize_repr)]
524    #[repr(u8)]
525    pub enum Importance {
526        None = 0,
527        Min = 1,
528        Low = 2,
529        Default = 3,
530        High = 4,
531    }
532
533    impl Default for Importance {
534        fn default() -> Self {
535            Self::Default
536        }
537    }
538
539    #[derive(Debug, Clone, Copy, Serialize_repr, Deserialize_repr)]
540    #[repr(i8)]
541    pub enum Visibility {
542        Secret = -1,
543        Private = 0,
544        Public = 1,
545    }
546
547    #[derive(Debug, Serialize, Deserialize)]
548    #[serde(rename_all = "camelCase")]
549    pub struct Channel {
550        id: String,
551        name: String,
552        description: Option<String>,
553        sound: Option<String>,
554        lights: bool,
555        light_color: Option<String>,
556        vibration: bool,
557        importance: Importance,
558        visibility: Option<Visibility>,
559    }
560
561    #[derive(Debug)]
562    pub struct ChannelBuilder(Channel);
563
564    impl Channel {
565        pub fn builder(id: impl Into<String>, name: impl Into<String>) -> ChannelBuilder {
566            ChannelBuilder(Self {
567                id: id.into(),
568                name: name.into(),
569                description: None,
570                sound: None,
571                lights: false,
572                light_color: None,
573                vibration: false,
574                importance: Default::default(),
575                visibility: None,
576            })
577        }
578
579        pub fn id(&self) -> &str {
580            &self.id
581        }
582
583        pub fn name(&self) -> &str {
584            &self.name
585        }
586
587        pub fn description(&self) -> Option<&str> {
588            self.description.as_deref()
589        }
590
591        pub fn sound(&self) -> Option<&str> {
592            self.sound.as_deref()
593        }
594
595        pub fn lights(&self) -> bool {
596            self.lights
597        }
598
599        pub fn light_color(&self) -> Option<&str> {
600            self.light_color.as_deref()
601        }
602
603        pub fn vibration(&self) -> bool {
604            self.vibration
605        }
606
607        pub fn importance(&self) -> Importance {
608            self.importance
609        }
610
611        pub fn visibility(&self) -> Option<Visibility> {
612            self.visibility
613        }
614    }
615
616    impl ChannelBuilder {
617        pub fn description(mut self, description: impl Into<String>) -> Self {
618            self.0.description.replace(description.into());
619            self
620        }
621
622        pub fn sound(mut self, sound: impl Into<String>) -> Self {
623            self.0.sound.replace(sound.into());
624            self
625        }
626
627        pub fn lights(mut self, lights: bool) -> Self {
628            self.0.lights = lights;
629            self
630        }
631
632        pub fn light_color(mut self, color: impl Into<String>) -> Self {
633            self.0.light_color.replace(color.into());
634            self
635        }
636
637        pub fn vibration(mut self, vibration: bool) -> Self {
638            self.0.vibration = vibration;
639            self
640        }
641
642        pub fn importance(mut self, importance: Importance) -> Self {
643            self.0.importance = importance;
644            self
645        }
646
647        pub fn visibility(mut self, visibility: Visibility) -> Self {
648            self.0.visibility.replace(visibility);
649            self
650        }
651
652        pub fn build(self) -> Channel {
653            self.0
654        }
655    }
656}