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}