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::{Deserialize, Deserializer, Serialize, Serializer, de::Error as DeError};
8
9use url::Url;
10
11/// A media file attached to a notification.
12///
13/// Attachments are only used on mobile; desktop notifications ignore them.
14#[derive(Debug, Clone, Serialize, Deserialize)]
15#[serde(rename_all = "camelCase")]
16pub struct Attachment {
17    id: String,
18    url: Url,
19}
20
21impl Attachment {
22    /// Creates a new attachment with the given identifier and URL.
23    ///
24    /// The URL accepts the `asset` and `file` protocols.
25    pub fn new(id: impl Into<String>, url: Url) -> Self {
26        Self { id: id.into(), url }
27    }
28}
29
30/// The set of date fields a notification must match to be delivered.
31///
32/// Fields left as [`None`] match any value, so the notification fires on every date
33/// whose remaining components match. Used by [`Schedule::Interval`].
34#[derive(Debug, Clone, Default, Serialize, Deserialize)]
35#[serde(rename_all = "camelCase")]
36pub struct ScheduleInterval {
37    /// The year the notification fires on.
38    pub year: Option<u8>,
39    /// The month of the year the notification fires on.
40    pub month: Option<u8>,
41    /// The day of the month the notification fires on.
42    pub day: Option<u8>,
43    /// The day of the week the notification fires on.
44    ///
45    /// 1 - Sunday, 2 - Monday, 3 - Tuesday, 4 - Wednesday, 5 - Thursday, 6 - Friday, 7 - Saturday.
46    pub weekday: Option<u8>,
47    /// The hour of the day the notification fires on, in the 24-hour clock.
48    pub hour: Option<u8>,
49    /// The minute of the hour the notification fires on.
50    pub minute: Option<u8>,
51    /// The second of the minute the notification fires on.
52    pub second: Option<u8>,
53}
54
55/// The unit of the repeating interval used by [`Schedule::Every`].
56///
57/// It is serialized as its lowercase camelCase name, e.g. `twoWeeks`.
58#[derive(Debug, Clone)]
59pub enum ScheduleEvery {
60    /// Repeats every year.
61    ///
62    /// On Android a year is approximated as 52 weeks.
63    Year,
64    /// Repeats every month.
65    ///
66    /// On Android a month is approximated as 30 days.
67    Month,
68    /// Repeats every two weeks.
69    TwoWeeks,
70    /// Repeats every week.
71    Week,
72    /// Repeats every day.
73    Day,
74    /// Repeats every hour.
75    Hour,
76    /// Repeats every minute.
77    Minute,
78    /// Repeats every second.
79    ///
80    /// Not supported on iOS, where repeating triggers must be at least a minute apart.
81    Second,
82}
83
84impl Display for ScheduleEvery {
85    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
86        write!(
87            f,
88            "{}",
89            match self {
90                Self::Year => "year",
91                Self::Month => "month",
92                Self::TwoWeeks => "twoWeeks",
93                Self::Week => "week",
94                Self::Day => "day",
95                Self::Hour => "hour",
96                Self::Minute => "minute",
97                Self::Second => "second",
98            }
99        )
100    }
101}
102
103impl Serialize for ScheduleEvery {
104    fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
105    where
106        S: Serializer,
107    {
108        serializer.serialize_str(self.to_string().as_ref())
109    }
110}
111
112impl<'de> Deserialize<'de> for ScheduleEvery {
113    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
114    where
115        D: Deserializer<'de>,
116    {
117        let s = String::deserialize(deserializer)?;
118        match s.to_lowercase().as_str() {
119            "year" => Ok(Self::Year),
120            "month" => Ok(Self::Month),
121            "twoweeks" => Ok(Self::TwoWeeks),
122            "week" => Ok(Self::Week),
123            "day" => Ok(Self::Day),
124            "hour" => Ok(Self::Hour),
125            "minute" => Ok(Self::Minute),
126            "second" => Ok(Self::Second),
127            _ => Err(DeError::custom(format!("unknown every kind '{s}'"))),
128        }
129    }
130}
131
132/// Defines when a notification is delivered.
133///
134/// Scheduling is only implemented on mobile; the desktop implementation delivers the
135/// notification immediately and ignores the schedule.
136#[derive(Debug, Clone, Serialize, Deserialize)]
137#[serde(rename_all = "camelCase")]
138pub enum Schedule {
139    /// Fires at a specific date and time, which must be in the future.
140    #[serde(rename_all = "camelCase")]
141    At {
142        /// The date and time the notification fires at, serialized as an ISO-8601 string.
143        #[serde(
144            serialize_with = "iso8601::serialize",
145            deserialize_with = "time::serde::iso8601::deserialize"
146        )]
147        date: time::OffsetDateTime,
148        /// Whether the notification keeps repeating, using the duration between the moment it is
149        /// scheduled and `date` as the interval. Defaults to `false`.
150        ///
151        /// The interval must be at least one minute on iOS.
152        #[serde(default)]
153        repeating: bool,
154        /// Whether the notification is allowed to fire while the device is in low-power idle
155        /// (Doze) mode. Defaults to `false`.
156        ///
157        /// Only used on Android.
158        #[serde(default)]
159        allow_while_idle: bool,
160    },
161    /// Fires whenever the current date matches every field set on the given interval.
162    #[serde(rename_all = "camelCase")]
163    Interval {
164        /// The date fields the current date must match for the notification to fire.
165        interval: ScheduleInterval,
166        /// Whether the notification is allowed to fire while the device is in low-power idle
167        /// (Doze) mode. Defaults to `false`.
168        ///
169        /// Only used on Android.
170        #[serde(default)]
171        allow_while_idle: bool,
172    },
173    /// Fires repeatedly, once every `count` times the given interval unit.
174    #[serde(rename_all = "camelCase")]
175    Every {
176        /// The unit of the repeating interval.
177        interval: ScheduleEvery,
178        /// How many interval units elapse between each notification.
179        count: u8,
180        /// Whether the notification is allowed to fire while the device is in low-power idle
181        /// (Doze) mode. Defaults to `false`.
182        ///
183        /// Only used on Android.
184        #[serde(default)]
185        allow_while_idle: bool,
186    },
187}
188
189// custom ISO-8601 serialization that does not use 6 digits for years.
190mod iso8601 {
191    use serde::{Serialize, Serializer, ser::Error as _};
192    use time::{
193        OffsetDateTime,
194        format_description::well_known::Iso8601,
195        format_description::well_known::iso8601::{Config, EncodedConfig},
196    };
197
198    const SERDE_CONFIG: EncodedConfig = Config::DEFAULT.encode();
199
200    pub fn serialize<S: Serializer>(
201        datetime: &OffsetDateTime,
202        serializer: S,
203    ) -> Result<S::Ok, S::Error> {
204        datetime
205            .format(&Iso8601::<SERDE_CONFIG>)
206            .map_err(S::Error::custom)?
207            .serialize(serializer)
208    }
209}
210
211/// The payload of a notification, as sent to the platform implementation.
212///
213/// Build it with [`NotificationBuilder`](crate::NotificationBuilder) rather than constructing it directly.
214/// The identifier defaults to a random 32-bit integer when it is not provided.
215#[derive(Debug, Serialize, Deserialize)]
216#[serde(rename_all = "camelCase")]
217pub struct NotificationData {
218    #[serde(default = "default_id")]
219    pub(crate) id: i32,
220    pub(crate) channel_id: Option<String>,
221    pub(crate) title: Option<String>,
222    pub(crate) body: Option<String>,
223    pub(crate) schedule: Option<Schedule>,
224    pub(crate) large_body: Option<String>,
225    pub(crate) summary: Option<String>,
226    pub(crate) action_type_id: Option<String>,
227    pub(crate) group: Option<String>,
228    #[serde(default)]
229    pub(crate) group_summary: bool,
230    pub(crate) sound: Option<String>,
231    #[serde(default)]
232    pub(crate) inbox_lines: Vec<String>,
233    pub(crate) icon: Option<String>,
234    pub(crate) large_icon: Option<String>,
235    pub(crate) icon_color: Option<String>,
236    #[serde(default)]
237    pub(crate) attachments: Vec<Attachment>,
238    #[serde(default)]
239    pub(crate) extra: HashMap<String, serde_json::Value>,
240    #[serde(default)]
241    pub(crate) ongoing: bool,
242    #[serde(default)]
243    pub(crate) auto_cancel: bool,
244    #[serde(default)]
245    pub(crate) silent: bool,
246    /// Set the time that the event occurred (milliseconds since epoch).
247    /// Notifications in the panel are sorted by this time.
248    /// Android only.
249    pub(crate) when: Option<i64>,
250    /// Show the `when` field as a stopwatch (elapsed time).
251    /// Android only.
252    #[serde(default)]
253    pub(crate) uses_chronometer: bool,
254    /// Sets the Chronometer to count down instead of counting up.
255    /// Only relevant if `uses_chronometer` is true.
256    /// Android only.
257    #[serde(default)]
258    pub(crate) chronometer_count_down: bool,
259}
260
261fn default_id() -> i32 {
262    rand::random()
263}
264
265impl Default for NotificationData {
266    fn default() -> Self {
267        Self {
268            id: default_id(),
269            channel_id: None,
270            title: None,
271            body: None,
272            schedule: None,
273            large_body: None,
274            summary: None,
275            action_type_id: None,
276            group: None,
277            group_summary: false,
278            sound: None,
279            inbox_lines: Vec::new(),
280            icon: None,
281            large_icon: None,
282            icon_color: None,
283            attachments: Vec::new(),
284            extra: Default::default(),
285            ongoing: false,
286            auto_cancel: false,
287            silent: false,
288            when: None,
289            uses_chronometer: false,
290            chronometer_count_down: false,
291        }
292    }
293}
294
295/// A notification that was scheduled and has not been delivered yet.
296///
297/// Returned by `Notification::pending`, which is only available on mobile.
298#[derive(Debug, Deserialize)]
299#[serde(rename_all = "camelCase")]
300pub struct PendingNotification {
301    id: i32,
302    title: Option<String>,
303    body: Option<String>,
304    schedule: Schedule,
305}
306
307impl PendingNotification {
308    /// The notification identifier.
309    pub fn id(&self) -> i32 {
310        self.id
311    }
312
313    /// The notification title, if it was set.
314    pub fn title(&self) -> Option<&str> {
315        self.title.as_deref()
316    }
317
318    /// The notification body, if it was set.
319    pub fn body(&self) -> Option<&str> {
320        self.body.as_deref()
321    }
322
323    /// The schedule that determines when the notification is delivered.
324    pub fn schedule(&self) -> &Schedule {
325        &self.schedule
326    }
327}
328
329/// A notification that was delivered and is still visible in the notification center.
330///
331/// Returned by `Notification::active`, which is only available on mobile.
332/// Which fields are populated depends on the platform, since Android and iOS expose
333/// different information about delivered notifications.
334#[derive(Debug, Clone, Serialize, Deserialize)]
335#[serde(rename_all = "camelCase")]
336pub struct ActiveNotification {
337    id: i32,
338    tag: Option<String>,
339    title: Option<String>,
340    body: Option<String>,
341    group: Option<String>,
342    #[serde(default)]
343    group_summary: bool,
344    #[serde(default)]
345    data: HashMap<String, String>,
346    #[serde(default)]
347    extra: HashMap<String, serde_json::Value>,
348    #[serde(default)]
349    attachments: Vec<Attachment>,
350    action_type_id: Option<String>,
351    schedule: Option<Schedule>,
352    sound: Option<String>,
353}
354
355impl ActiveNotification {
356    /// The notification identifier.
357    pub fn id(&self) -> i32 {
358        self.id
359    }
360
361    /// The tag the notification was posted with.
362    ///
363    /// Only set on Android.
364    pub fn tag(&self) -> Option<&str> {
365        self.tag.as_deref()
366    }
367
368    /// The notification title, if it was set.
369    pub fn title(&self) -> Option<&str> {
370        self.title.as_deref()
371    }
372
373    /// The notification body, if it was set.
374    pub fn body(&self) -> Option<&str> {
375        self.body.as_deref()
376    }
377
378    /// The identifier of the group the notification belongs to.
379    ///
380    /// Only set on Android.
381    pub fn group(&self) -> Option<&str> {
382        self.group.as_deref()
383    }
384
385    /// Whether the notification is the summary of its group.
386    ///
387    /// Only set on Android. Defaults to `false`.
388    pub fn group_summary(&self) -> bool {
389        self.group_summary
390    }
391
392    /// The platform extras attached to the notification, as string values.
393    ///
394    /// Only set on Android, where it holds the `android.app.Notification` extras bundle.
395    pub fn data(&self) -> &HashMap<String, String> {
396        &self.data
397    }
398
399    /// The extra payload that was stored in the notification.
400    pub fn extra(&self) -> &HashMap<String, serde_json::Value> {
401        &self.extra
402    }
403
404    /// The attachments of the notification.
405    ///
406    /// Only set on iOS.
407    pub fn attachments(&self) -> &[Attachment] {
408        &self.attachments
409    }
410
411    /// The identifier of the action type the notification was registered with.
412    ///
413    /// Only set on iOS.
414    pub fn action_type_id(&self) -> Option<&str> {
415        self.action_type_id.as_deref()
416    }
417
418    /// The schedule the notification was delivered with, if it was scheduled.
419    pub fn schedule(&self) -> Option<&Schedule> {
420        self.schedule.as_ref()
421    }
422
423    /// The sound resource name of the notification.
424    ///
425    /// Only set on iOS.
426    pub fn sound(&self) -> Option<&str> {
427        self.sound.as_deref()
428    }
429}
430
431/// A group of [`Action`]s a notification can display, referenced by
432/// [`NotificationBuilder::action_type_id`](crate::NotificationBuilder::action_type_id).
433///
434/// Register it with `Notification::register_action_types` before sending a notification that uses it.
435/// It maps to a `UNNotificationCategory` on iOS, to an action group on Android and to the
436/// buttons of the notification on desktop.
437///
438/// Use [`ActionType::builder`] to construct one.
439#[derive(Debug, Clone, Serialize, Deserialize)]
440#[serde(rename_all = "camelCase")]
441pub struct ActionType {
442    id: String,
443    #[serde(default)]
444    actions: Vec<Action>,
445    hidden_previews_body_placeholder: Option<String>,
446    #[serde(default)]
447    custom_dismiss_action: bool,
448    #[serde(default)]
449    allow_in_car_play: bool,
450    #[serde(default)]
451    hidden_previews_show_title: bool,
452    #[serde(default)]
453    hidden_previews_show_subtitle: bool,
454}
455
456/// Builder for an [`ActionType`], created with [`ActionType::builder`].
457#[derive(Debug)]
458pub struct ActionTypeBuilder(ActionType);
459
460impl ActionType {
461    /// Creates a builder for an action type with the given identifier.
462    ///
463    /// All the optional settings default to `false` or [`None`];
464    /// call [`ActionTypeBuilder::build`] to get the [`ActionType`].
465    pub fn builder(id: impl Into<String>) -> ActionTypeBuilder {
466        ActionTypeBuilder(Self {
467            id: id.into(),
468            actions: Vec::new(),
469            hidden_previews_body_placeholder: None,
470            custom_dismiss_action: false,
471            allow_in_car_play: false,
472            hidden_previews_show_title: false,
473            hidden_previews_show_subtitle: false,
474        })
475    }
476
477    /// The identifier of this action type.
478    pub fn id(&self) -> &str {
479        &self.id
480    }
481
482    /// The actions associated with this action type.
483    pub fn actions(&self) -> &[Action] {
484        &self.actions
485    }
486
487    /// The placeholder shown instead of the notification body when previews are hidden.
488    ///
489    /// Only used on iOS.
490    pub fn hidden_previews_body_placeholder(&self) -> Option<&str> {
491        self.hidden_previews_body_placeholder.as_deref()
492    }
493
494    /// Whether the app is notified when the user dismisses the notification.
495    ///
496    /// Only used on iOS.
497    pub fn custom_dismiss_action(&self) -> bool {
498        self.custom_dismiss_action
499    }
500
501    /// Whether the notification can be displayed in a CarPlay environment.
502    ///
503    /// Only used on iOS.
504    pub fn allow_in_car_play(&self) -> bool {
505        self.allow_in_car_play
506    }
507
508    /// Whether the notification title is shown even when previews are hidden.
509    ///
510    /// Only used on iOS.
511    pub fn hidden_previews_show_title(&self) -> bool {
512        self.hidden_previews_show_title
513    }
514
515    /// Whether the notification subtitle is shown even when previews are hidden.
516    ///
517    /// Only used on iOS.
518    pub fn hidden_previews_show_subtitle(&self) -> bool {
519        self.hidden_previews_show_subtitle
520    }
521}
522
523impl ActionTypeBuilder {
524    /// Sets the actions associated with this action type.
525    pub fn actions(mut self, actions: Vec<Action>) -> Self {
526        self.0.actions = actions;
527        self
528    }
529
530    /// Sets the placeholder shown instead of the notification body when previews are hidden.
531    ///
532    /// Only used on iOS.
533    pub fn hidden_previews_body_placeholder(
534        mut self,
535        hidden_previews_body_placeholder: impl Into<String>,
536    ) -> Self {
537        self.0
538            .hidden_previews_body_placeholder
539            .replace(hidden_previews_body_placeholder.into());
540        self
541    }
542
543    /// Sets whether the app is notified when the user dismisses the notification.
544    ///
545    /// Only used on iOS.
546    pub fn custom_dismiss_action(mut self, custom_dismiss_action: bool) -> Self {
547        self.0.custom_dismiss_action = custom_dismiss_action;
548        self
549    }
550
551    /// Sets whether the notification can be displayed in a CarPlay environment.
552    ///
553    /// Only used on iOS.
554    pub fn allow_in_car_play(mut self, allow_in_car_play: bool) -> Self {
555        self.0.allow_in_car_play = allow_in_car_play;
556        self
557    }
558
559    /// Sets whether the notification title is shown even when previews are hidden.
560    ///
561    /// Only used on iOS.
562    pub fn hidden_previews_show_title(mut self, hidden_previews_show_title: bool) -> Self {
563        self.0.hidden_previews_show_title = hidden_previews_show_title;
564        self
565    }
566
567    /// Sets whether the notification subtitle is shown even when previews are hidden.
568    ///
569    /// Only used on iOS.
570    pub fn hidden_previews_show_subtitle(mut self, hidden_previews_show_subtitle: bool) -> Self {
571        self.0.hidden_previews_show_subtitle = hidden_previews_show_subtitle;
572        self
573    }
574
575    /// Builds the [`ActionType`].
576    pub fn build(self) -> ActionType {
577        self.0
578    }
579}
580
581/// A button the user can tap on a notification, belonging to an [`ActionType`].
582///
583/// It maps to a `UNNotificationAction` on iOS. On Android only the identifier, the title
584/// and the input flag are used, on desktop only the identifier and the title.
585///
586/// Use [`Action::builder`] to construct one.
587#[derive(Debug, Clone, Serialize, Deserialize)]
588#[serde(rename_all = "camelCase")]
589pub struct Action {
590    id: String,
591    title: String,
592    #[serde(default)]
593    requires_authentication: bool,
594    #[serde(default)]
595    foreground: bool,
596    #[serde(default)]
597    destructive: bool,
598    #[serde(default)]
599    input: bool,
600    input_button_title: Option<String>,
601    input_placeholder: Option<String>,
602}
603
604/// Builder for an [`Action`], created with [`Action::builder`].
605#[derive(Debug)]
606pub struct ActionBuilder(Action);
607
608impl Action {
609    /// Creates a builder for an action with the given identifier and button title.
610    ///
611    /// All the optional settings default to `false` or [`None`];
612    /// call [`ActionBuilder::build`] to get the [`Action`].
613    pub fn builder(id: impl Into<String>, title: impl Into<String>) -> ActionBuilder {
614        ActionBuilder(Self {
615            id: id.into(),
616            title: title.into(),
617            requires_authentication: false,
618            foreground: false,
619            destructive: false,
620            input: false,
621            input_button_title: None,
622            input_placeholder: None,
623        })
624    }
625
626    /// The identifier of this action, reported back when the user triggers it.
627    pub fn id(&self) -> &str {
628        &self.id
629    }
630
631    /// The text displayed on the action button.
632    pub fn title(&self) -> &str {
633        &self.title
634    }
635
636    /// Whether the device must be unlocked for the action to run.
637    ///
638    /// Only used on iOS.
639    pub fn requires_authentication(&self) -> bool {
640        self.requires_authentication
641    }
642
643    /// Whether the app is brought to the foreground when the action is triggered.
644    ///
645    /// Only used on iOS.
646    pub fn foreground(&self) -> bool {
647        self.foreground
648    }
649
650    /// Whether the action is displayed as destructive, usually in red.
651    ///
652    /// Only used on iOS.
653    pub fn destructive(&self) -> bool {
654        self.destructive
655    }
656
657    /// Whether triggering the action lets the user type a text response.
658    pub fn input(&self) -> bool {
659        self.input
660    }
661
662    /// The text displayed on the button that submits the text input.
663    ///
664    /// Only used on iOS.
665    pub fn input_button_title(&self) -> Option<&str> {
666        self.input_button_title.as_deref()
667    }
668
669    /// The placeholder displayed on the empty text input field.
670    ///
671    /// Only used on iOS.
672    pub fn input_placeholder(&self) -> Option<&str> {
673        self.input_placeholder.as_deref()
674    }
675}
676
677impl ActionBuilder {
678    /// Sets whether the device must be unlocked for the action to run.
679    ///
680    /// Only used on iOS.
681    pub fn requires_authentication(mut self, requires_authentication: bool) -> Self {
682        self.0.requires_authentication = requires_authentication;
683        self
684    }
685
686    /// Sets whether the app is brought to the foreground when the action is triggered.
687    ///
688    /// Only used on iOS.
689    pub fn foreground(mut self, foreground: bool) -> Self {
690        self.0.foreground = foreground;
691        self
692    }
693
694    /// Sets whether the action is displayed as destructive, usually in red.
695    ///
696    /// Only used on iOS.
697    pub fn destructive(mut self, destructive: bool) -> Self {
698        self.0.destructive = destructive;
699        self
700    }
701
702    /// Sets whether triggering the action lets the user type a text response.
703    pub fn input(mut self, input: bool) -> Self {
704        self.0.input = input;
705        self
706    }
707
708    /// Sets the text displayed on the button that submits the text input.
709    ///
710    /// Only used on iOS.
711    pub fn input_button_title(mut self, input_button_title: impl Into<String>) -> Self {
712        self.0.input_button_title.replace(input_button_title.into());
713        self
714    }
715
716    /// Sets the placeholder displayed on the empty text input field.
717    ///
718    /// Only used on iOS.
719    pub fn input_placeholder(mut self, input_placeholder: impl Into<String>) -> Self {
720        self.0.input_placeholder.replace(input_placeholder.into());
721        self
722    }
723
724    /// Builds the [`Action`].
725    pub fn build(self) -> Action {
726        self.0
727    }
728}
729
730/// An action the user performed on a notification, delivered to the handlers of
731/// `Notification::on_action` and to the `onAction` listeners of the JavaScript API.
732///
733/// On desktop it is emitted when the user clicks the notification body (`actionId` = `tap`) or
734/// one of the actions of its [`ActionType`], as far as the notification server reports it.
735#[derive(Debug, Clone, Serialize, Deserialize)]
736#[serde(rename_all = "camelCase")]
737pub struct ActionPerformed {
738    action_id: String,
739    input_value: Option<String>,
740    /// Read leniently: the platforms describe the notification differently, and an action is
741    /// still worth reporting when its notification cannot be read.
742    #[serde(default, deserialize_with = "lenient_notification")]
743    notification: Option<ActiveNotification>,
744}
745
746fn lenient_notification<'de, D: serde::Deserializer<'de>>(
747    deserializer: D,
748) -> Result<Option<ActiveNotification>, D::Error> {
749    let value = serde_json::Value::deserialize(deserializer)?;
750    Ok(serde_json::from_value(value).ok())
751}
752
753impl ActionPerformed {
754    /// The identifier of the performed action: `tap` for a click on the notification itself,
755    /// `dismiss` for a dismissal reported on iOS, otherwise the identifier of the [`Action`].
756    pub fn action_id(&self) -> &str {
757        &self.action_id
758    }
759
760    /// The text the user typed, for an [`Action`] with an input field (mobile only).
761    pub fn input_value(&self) -> Option<&str> {
762        self.input_value.as_deref()
763    }
764
765    /// The notification the action was performed on, as far as the platform reports it.
766    pub fn notification(&self) -> Option<&ActiveNotification> {
767        self.notification.as_ref()
768    }
769}
770
771#[cfg(target_os = "android")]
772pub use android::*;
773
774#[cfg(target_os = "android")]
775mod android {
776    use serde::{Deserialize, Serialize};
777    use serde_repr::{Deserialize_repr, Serialize_repr};
778
779    /// How much the notifications of a [`Channel`] interrupt the user.
780    ///
781    /// It maps to the `NotificationManager.IMPORTANCE_*` constants and is serialized as its
782    /// integer value. Only available on Android.
783    #[derive(Debug, Default, Clone, Copy, Serialize_repr, Deserialize_repr)]
784    #[repr(u8)]
785    pub enum Importance {
786        /// The notifications are not shown.
787        None = 0,
788        /// The notifications are only shown in the shade, below the fold, without a status bar icon.
789        Min = 1,
790        /// The notifications are shown without a sound.
791        Low = 2,
792        /// The notifications are shown and make a sound.
793        ///
794        /// This is the value used when the channel does not define an importance.
795        #[default]
796        Default = 3,
797        /// The notifications are shown, make a sound and pop up as a heads-up notification.
798        High = 4,
799    }
800
801    /// How much of a notification is shown on the lock screen.
802    ///
803    /// It maps to the `Notification.VISIBILITY_*` constants and is serialized as its
804    /// integer value. Only available on Android.
805    #[derive(Debug, Clone, Copy, Serialize_repr, Deserialize_repr)]
806    #[repr(i8)]
807    pub enum Visibility {
808        /// The notification is not shown on the lock screen at all.
809        Secret = -1,
810        /// The notification is shown on the lock screen with its sensitive content hidden.
811        ///
812        /// This is the value used when the channel does not define a visibility.
813        Private = 0,
814        /// The notification is shown in full on the lock screen.
815        Public = 1,
816    }
817
818    /// A notification channel, the category users configure notification behavior on.
819    ///
820    /// Notifications reference a channel through
821    /// [`NotificationBuilder::channel_id`](crate::NotificationBuilder::channel_id) and are not
822    /// delivered when the channel does not exist. Only available on Android.
823    /// Use [`Channel::builder`] to construct one.
824    #[derive(Debug, Serialize, Deserialize)]
825    #[serde(rename_all = "camelCase")]
826    pub struct Channel {
827        id: String,
828        name: String,
829        description: Option<String>,
830        sound: Option<String>,
831        lights: bool,
832        light_color: Option<String>,
833        vibration: bool,
834        importance: Importance,
835        visibility: Option<Visibility>,
836    }
837
838    /// Builder for a [`Channel`], created with [`Channel::builder`].
839    ///
840    /// Only available on Android.
841    #[derive(Debug)]
842    pub struct ChannelBuilder(Channel);
843
844    impl Channel {
845        /// Creates a builder for a channel with the given identifier and user visible name.
846        ///
847        /// Lights and vibration are disabled, the importance defaults to [`Importance::Default`]
848        /// and the remaining settings default to [`None`];
849        /// call [`ChannelBuilder::build`] to get the [`Channel`].
850        pub fn builder(id: impl Into<String>, name: impl Into<String>) -> ChannelBuilder {
851            ChannelBuilder(Self {
852                id: id.into(),
853                name: name.into(),
854                description: None,
855                sound: None,
856                lights: false,
857                light_color: None,
858                vibration: false,
859                importance: Default::default(),
860                visibility: None,
861            })
862        }
863
864        /// The identifier of this channel.
865        pub fn id(&self) -> &str {
866            &self.id
867        }
868
869        /// The user visible name of this channel.
870        pub fn name(&self) -> &str {
871            &self.name
872        }
873
874        /// The user visible description of this channel.
875        pub fn description(&self) -> Option<&str> {
876            self.description.as_deref()
877        }
878
879        /// The name of the sound resource played by the notifications of this channel.
880        ///
881        /// The resource must be placed in the app's `res/raw` folder.
882        pub fn sound(&self) -> Option<&str> {
883            self.sound.as_deref()
884        }
885
886        /// Whether the notifications of this channel blink the device light.
887        pub fn lights(&self) -> bool {
888            self.lights
889        }
890
891        /// The color of the device light, as a color string such as `#ff0000`.
892        pub fn light_color(&self) -> Option<&str> {
893            self.light_color.as_deref()
894        }
895
896        /// Whether the notifications of this channel vibrate the device.
897        pub fn vibration(&self) -> bool {
898            self.vibration
899        }
900
901        /// How much the notifications of this channel interrupt the user.
902        pub fn importance(&self) -> Importance {
903            self.importance
904        }
905
906        /// How much of the notifications of this channel is shown on the lock screen.
907        ///
908        /// [`Visibility::Private`] is used when this is [`None`].
909        pub fn visibility(&self) -> Option<Visibility> {
910            self.visibility
911        }
912    }
913
914    impl ChannelBuilder {
915        /// Sets the user visible description of the channel.
916        pub fn description(mut self, description: impl Into<String>) -> Self {
917            self.0.description.replace(description.into());
918            self
919        }
920
921        /// Sets the name of the sound resource played by the notifications of this channel.
922        ///
923        /// The resource must be placed in the app's `res/raw` folder.
924        pub fn sound(mut self, sound: impl Into<String>) -> Self {
925            self.0.sound.replace(sound.into());
926            self
927        }
928
929        /// Sets whether the notifications of this channel blink the device light.
930        pub fn lights(mut self, lights: bool) -> Self {
931            self.0.lights = lights;
932            self
933        }
934
935        /// Sets the color of the device light, as a color string such as `#ff0000`.
936        pub fn light_color(mut self, color: impl Into<String>) -> Self {
937            self.0.light_color.replace(color.into());
938            self
939        }
940
941        /// Sets whether the notifications of this channel vibrate the device.
942        pub fn vibration(mut self, vibration: bool) -> Self {
943            self.0.vibration = vibration;
944            self
945        }
946
947        /// Sets how much the notifications of this channel interrupt the user.
948        pub fn importance(mut self, importance: Importance) -> Self {
949            self.0.importance = importance;
950            self
951        }
952
953        /// Sets how much of the notifications of this channel is shown on the lock screen.
954        pub fn visibility(mut self, visibility: Visibility) -> Self {
955            self.0.visibility.replace(visibility);
956            self
957        }
958
959        /// Builds the [`Channel`].
960        pub fn build(self) -> Channel {
961            self.0
962        }
963    }
964}