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}
247
248fn default_id() -> i32 {
249 rand::random()
250}
251
252impl Default for NotificationData {
253 fn default() -> Self {
254 Self {
255 id: default_id(),
256 channel_id: None,
257 title: None,
258 body: None,
259 schedule: None,
260 large_body: None,
261 summary: None,
262 action_type_id: None,
263 group: None,
264 group_summary: false,
265 sound: None,
266 inbox_lines: Vec::new(),
267 icon: None,
268 large_icon: None,
269 icon_color: None,
270 attachments: Vec::new(),
271 extra: Default::default(),
272 ongoing: false,
273 auto_cancel: false,
274 silent: false,
275 }
276 }
277}
278
279/// A notification that was scheduled and has not been delivered yet.
280///
281/// Returned by `Notification::pending`, which is only available on mobile.
282#[derive(Debug, Deserialize)]
283#[serde(rename_all = "camelCase")]
284pub struct PendingNotification {
285 id: i32,
286 title: Option<String>,
287 body: Option<String>,
288 schedule: Schedule,
289}
290
291impl PendingNotification {
292 /// The notification identifier.
293 pub fn id(&self) -> i32 {
294 self.id
295 }
296
297 /// The notification title, if it was set.
298 pub fn title(&self) -> Option<&str> {
299 self.title.as_deref()
300 }
301
302 /// The notification body, if it was set.
303 pub fn body(&self) -> Option<&str> {
304 self.body.as_deref()
305 }
306
307 /// The schedule that determines when the notification is delivered.
308 pub fn schedule(&self) -> &Schedule {
309 &self.schedule
310 }
311}
312
313/// A notification that was delivered and is still visible in the notification center.
314///
315/// Returned by `Notification::active`, which is only available on mobile.
316/// Which fields are populated depends on the platform, since Android and iOS expose
317/// different information about delivered notifications.
318#[derive(Debug, Clone, Serialize, Deserialize)]
319#[serde(rename_all = "camelCase")]
320pub struct ActiveNotification {
321 id: i32,
322 tag: Option<String>,
323 title: Option<String>,
324 body: Option<String>,
325 group: Option<String>,
326 #[serde(default)]
327 group_summary: bool,
328 #[serde(default)]
329 data: HashMap<String, String>,
330 #[serde(default)]
331 extra: HashMap<String, serde_json::Value>,
332 #[serde(default)]
333 attachments: Vec<Attachment>,
334 action_type_id: Option<String>,
335 schedule: Option<Schedule>,
336 sound: Option<String>,
337}
338
339impl ActiveNotification {
340 /// The notification identifier.
341 pub fn id(&self) -> i32 {
342 self.id
343 }
344
345 /// The tag the notification was posted with.
346 ///
347 /// Only set on Android.
348 pub fn tag(&self) -> Option<&str> {
349 self.tag.as_deref()
350 }
351
352 /// The notification title, if it was set.
353 pub fn title(&self) -> Option<&str> {
354 self.title.as_deref()
355 }
356
357 /// The notification body, if it was set.
358 pub fn body(&self) -> Option<&str> {
359 self.body.as_deref()
360 }
361
362 /// The identifier of the group the notification belongs to.
363 ///
364 /// Only set on Android.
365 pub fn group(&self) -> Option<&str> {
366 self.group.as_deref()
367 }
368
369 /// Whether the notification is the summary of its group.
370 ///
371 /// Only set on Android. Defaults to `false`.
372 pub fn group_summary(&self) -> bool {
373 self.group_summary
374 }
375
376 /// The platform extras attached to the notification, as string values.
377 ///
378 /// Only set on Android, where it holds the `android.app.Notification` extras bundle.
379 pub fn data(&self) -> &HashMap<String, String> {
380 &self.data
381 }
382
383 /// The extra payload that was stored in the notification.
384 pub fn extra(&self) -> &HashMap<String, serde_json::Value> {
385 &self.extra
386 }
387
388 /// The attachments of the notification.
389 ///
390 /// Only set on iOS.
391 pub fn attachments(&self) -> &[Attachment] {
392 &self.attachments
393 }
394
395 /// The identifier of the action type the notification was registered with.
396 ///
397 /// Only set on iOS.
398 pub fn action_type_id(&self) -> Option<&str> {
399 self.action_type_id.as_deref()
400 }
401
402 /// The schedule the notification was delivered with, if it was scheduled.
403 pub fn schedule(&self) -> Option<&Schedule> {
404 self.schedule.as_ref()
405 }
406
407 /// The sound resource name of the notification.
408 ///
409 /// Only set on iOS.
410 pub fn sound(&self) -> Option<&str> {
411 self.sound.as_deref()
412 }
413}
414
415/// A group of [`Action`]s a notification can display, referenced by
416/// [`NotificationBuilder::action_type_id`](crate::NotificationBuilder::action_type_id).
417///
418/// Register it with `Notification::register_action_types` before sending a notification that uses it.
419/// It maps to a `UNNotificationCategory` on iOS, to an action group on Android and to the
420/// buttons of the notification on desktop.
421///
422/// Use [`ActionType::builder`] to construct one.
423#[derive(Debug, Clone, Serialize, Deserialize)]
424#[serde(rename_all = "camelCase")]
425pub struct ActionType {
426 id: String,
427 #[serde(default)]
428 actions: Vec<Action>,
429 hidden_previews_body_placeholder: Option<String>,
430 #[serde(default)]
431 custom_dismiss_action: bool,
432 #[serde(default)]
433 allow_in_car_play: bool,
434 #[serde(default)]
435 hidden_previews_show_title: bool,
436 #[serde(default)]
437 hidden_previews_show_subtitle: bool,
438}
439
440/// Builder for an [`ActionType`], created with [`ActionType::builder`].
441#[derive(Debug)]
442pub struct ActionTypeBuilder(ActionType);
443
444impl ActionType {
445 /// Creates a builder for an action type with the given identifier.
446 ///
447 /// All the optional settings default to `false` or [`None`];
448 /// call [`ActionTypeBuilder::build`] to get the [`ActionType`].
449 pub fn builder(id: impl Into<String>) -> ActionTypeBuilder {
450 ActionTypeBuilder(Self {
451 id: id.into(),
452 actions: Vec::new(),
453 hidden_previews_body_placeholder: None,
454 custom_dismiss_action: false,
455 allow_in_car_play: false,
456 hidden_previews_show_title: false,
457 hidden_previews_show_subtitle: false,
458 })
459 }
460
461 /// The identifier of this action type.
462 pub fn id(&self) -> &str {
463 &self.id
464 }
465
466 /// The actions associated with this action type.
467 pub fn actions(&self) -> &[Action] {
468 &self.actions
469 }
470
471 /// The placeholder shown instead of the notification body when previews are hidden.
472 ///
473 /// Only used on iOS.
474 pub fn hidden_previews_body_placeholder(&self) -> Option<&str> {
475 self.hidden_previews_body_placeholder.as_deref()
476 }
477
478 /// Whether the app is notified when the user dismisses the notification.
479 ///
480 /// Only used on iOS.
481 pub fn custom_dismiss_action(&self) -> bool {
482 self.custom_dismiss_action
483 }
484
485 /// Whether the notification can be displayed in a CarPlay environment.
486 ///
487 /// Only used on iOS.
488 pub fn allow_in_car_play(&self) -> bool {
489 self.allow_in_car_play
490 }
491
492 /// Whether the notification title is shown even when previews are hidden.
493 ///
494 /// Only used on iOS.
495 pub fn hidden_previews_show_title(&self) -> bool {
496 self.hidden_previews_show_title
497 }
498
499 /// Whether the notification subtitle is shown even when previews are hidden.
500 ///
501 /// Only used on iOS.
502 pub fn hidden_previews_show_subtitle(&self) -> bool {
503 self.hidden_previews_show_subtitle
504 }
505}
506
507impl ActionTypeBuilder {
508 /// Sets the actions associated with this action type.
509 pub fn actions(mut self, actions: Vec<Action>) -> Self {
510 self.0.actions = actions;
511 self
512 }
513
514 /// Sets the placeholder shown instead of the notification body when previews are hidden.
515 ///
516 /// Only used on iOS.
517 pub fn hidden_previews_body_placeholder(
518 mut self,
519 hidden_previews_body_placeholder: impl Into<String>,
520 ) -> Self {
521 self.0
522 .hidden_previews_body_placeholder
523 .replace(hidden_previews_body_placeholder.into());
524 self
525 }
526
527 /// Sets whether the app is notified when the user dismisses the notification.
528 ///
529 /// Only used on iOS.
530 pub fn custom_dismiss_action(mut self, custom_dismiss_action: bool) -> Self {
531 self.0.custom_dismiss_action = custom_dismiss_action;
532 self
533 }
534
535 /// Sets whether the notification can be displayed in a CarPlay environment.
536 ///
537 /// Only used on iOS.
538 pub fn allow_in_car_play(mut self, allow_in_car_play: bool) -> Self {
539 self.0.allow_in_car_play = allow_in_car_play;
540 self
541 }
542
543 /// Sets whether the notification title is shown even when previews are hidden.
544 ///
545 /// Only used on iOS.
546 pub fn hidden_previews_show_title(mut self, hidden_previews_show_title: bool) -> Self {
547 self.0.hidden_previews_show_title = hidden_previews_show_title;
548 self
549 }
550
551 /// Sets whether the notification subtitle is shown even when previews are hidden.
552 ///
553 /// Only used on iOS.
554 pub fn hidden_previews_show_subtitle(mut self, hidden_previews_show_subtitle: bool) -> Self {
555 self.0.hidden_previews_show_subtitle = hidden_previews_show_subtitle;
556 self
557 }
558
559 /// Builds the [`ActionType`].
560 pub fn build(self) -> ActionType {
561 self.0
562 }
563}
564
565/// A button the user can tap on a notification, belonging to an [`ActionType`].
566///
567/// It maps to a `UNNotificationAction` on iOS. On Android only the identifier, the title
568/// and the input flag are used, on desktop only the identifier and the title.
569///
570/// Use [`Action::builder`] to construct one.
571#[derive(Debug, Clone, Serialize, Deserialize)]
572#[serde(rename_all = "camelCase")]
573pub struct Action {
574 id: String,
575 title: String,
576 #[serde(default)]
577 requires_authentication: bool,
578 #[serde(default)]
579 foreground: bool,
580 #[serde(default)]
581 destructive: bool,
582 #[serde(default)]
583 input: bool,
584 input_button_title: Option<String>,
585 input_placeholder: Option<String>,
586}
587
588/// Builder for an [`Action`], created with [`Action::builder`].
589#[derive(Debug)]
590pub struct ActionBuilder(Action);
591
592impl Action {
593 /// Creates a builder for an action with the given identifier and button title.
594 ///
595 /// All the optional settings default to `false` or [`None`];
596 /// call [`ActionBuilder::build`] to get the [`Action`].
597 pub fn builder(id: impl Into<String>, title: impl Into<String>) -> ActionBuilder {
598 ActionBuilder(Self {
599 id: id.into(),
600 title: title.into(),
601 requires_authentication: false,
602 foreground: false,
603 destructive: false,
604 input: false,
605 input_button_title: None,
606 input_placeholder: None,
607 })
608 }
609
610 /// The identifier of this action, reported back when the user triggers it.
611 pub fn id(&self) -> &str {
612 &self.id
613 }
614
615 /// The text displayed on the action button.
616 pub fn title(&self) -> &str {
617 &self.title
618 }
619
620 /// Whether the device must be unlocked for the action to run.
621 ///
622 /// Only used on iOS.
623 pub fn requires_authentication(&self) -> bool {
624 self.requires_authentication
625 }
626
627 /// Whether the app is brought to the foreground when the action is triggered.
628 ///
629 /// Only used on iOS.
630 pub fn foreground(&self) -> bool {
631 self.foreground
632 }
633
634 /// Whether the action is displayed as destructive, usually in red.
635 ///
636 /// Only used on iOS.
637 pub fn destructive(&self) -> bool {
638 self.destructive
639 }
640
641 /// Whether triggering the action lets the user type a text response.
642 pub fn input(&self) -> bool {
643 self.input
644 }
645
646 /// The text displayed on the button that submits the text input.
647 ///
648 /// Only used on iOS.
649 pub fn input_button_title(&self) -> Option<&str> {
650 self.input_button_title.as_deref()
651 }
652
653 /// The placeholder displayed on the empty text input field.
654 ///
655 /// Only used on iOS.
656 pub fn input_placeholder(&self) -> Option<&str> {
657 self.input_placeholder.as_deref()
658 }
659}
660
661impl ActionBuilder {
662 /// Sets whether the device must be unlocked for the action to run.
663 ///
664 /// Only used on iOS.
665 pub fn requires_authentication(mut self, requires_authentication: bool) -> Self {
666 self.0.requires_authentication = requires_authentication;
667 self
668 }
669
670 /// Sets whether the app is brought to the foreground when the action is triggered.
671 ///
672 /// Only used on iOS.
673 pub fn foreground(mut self, foreground: bool) -> Self {
674 self.0.foreground = foreground;
675 self
676 }
677
678 /// Sets whether the action is displayed as destructive, usually in red.
679 ///
680 /// Only used on iOS.
681 pub fn destructive(mut self, destructive: bool) -> Self {
682 self.0.destructive = destructive;
683 self
684 }
685
686 /// Sets whether triggering the action lets the user type a text response.
687 pub fn input(mut self, input: bool) -> Self {
688 self.0.input = input;
689 self
690 }
691
692 /// Sets the text displayed on the button that submits the text input.
693 ///
694 /// Only used on iOS.
695 pub fn input_button_title(mut self, input_button_title: impl Into<String>) -> Self {
696 self.0.input_button_title.replace(input_button_title.into());
697 self
698 }
699
700 /// Sets the placeholder displayed on the empty text input field.
701 ///
702 /// Only used on iOS.
703 pub fn input_placeholder(mut self, input_placeholder: impl Into<String>) -> Self {
704 self.0.input_placeholder.replace(input_placeholder.into());
705 self
706 }
707
708 /// Builds the [`Action`].
709 pub fn build(self) -> Action {
710 self.0
711 }
712}
713
714/// An action the user performed on a notification, delivered to the handlers of
715/// `Notification::on_action` and to the `onAction` listeners of the JavaScript API.
716///
717/// On desktop it is emitted when the user clicks the notification body (`actionId` = `tap`) or
718/// one of the actions of its [`ActionType`], as far as the notification server reports it.
719#[derive(Debug, Clone, Serialize, Deserialize)]
720#[serde(rename_all = "camelCase")]
721pub struct ActionPerformed {
722 action_id: String,
723 input_value: Option<String>,
724 /// Read leniently: the platforms describe the notification differently, and an action is
725 /// still worth reporting when its notification cannot be read.
726 #[serde(default, deserialize_with = "lenient_notification")]
727 notification: Option<ActiveNotification>,
728}
729
730fn lenient_notification<'de, D: serde::Deserializer<'de>>(
731 deserializer: D,
732) -> Result<Option<ActiveNotification>, D::Error> {
733 let value = serde_json::Value::deserialize(deserializer)?;
734 Ok(serde_json::from_value(value).ok())
735}
736
737impl ActionPerformed {
738 /// The identifier of the performed action: `tap` for a click on the notification itself,
739 /// `dismiss` for a dismissal reported on iOS, otherwise the identifier of the [`Action`].
740 pub fn action_id(&self) -> &str {
741 &self.action_id
742 }
743
744 /// The text the user typed, for an [`Action`] with an input field (mobile only).
745 pub fn input_value(&self) -> Option<&str> {
746 self.input_value.as_deref()
747 }
748
749 /// The notification the action was performed on, as far as the platform reports it.
750 pub fn notification(&self) -> Option<&ActiveNotification> {
751 self.notification.as_ref()
752 }
753}
754
755#[cfg(target_os = "android")]
756pub use android::*;
757
758#[cfg(target_os = "android")]
759mod android {
760 use serde::{Deserialize, Serialize};
761 use serde_repr::{Deserialize_repr, Serialize_repr};
762
763 /// How much the notifications of a [`Channel`] interrupt the user.
764 ///
765 /// It maps to the `NotificationManager.IMPORTANCE_*` constants and is serialized as its
766 /// integer value. Only available on Android.
767 #[derive(Debug, Default, Clone, Copy, Serialize_repr, Deserialize_repr)]
768 #[repr(u8)]
769 pub enum Importance {
770 /// The notifications are not shown.
771 None = 0,
772 /// The notifications are only shown in the shade, below the fold, without a status bar icon.
773 Min = 1,
774 /// The notifications are shown without a sound.
775 Low = 2,
776 /// The notifications are shown and make a sound.
777 ///
778 /// This is the value used when the channel does not define an importance.
779 #[default]
780 Default = 3,
781 /// The notifications are shown, make a sound and pop up as a heads-up notification.
782 High = 4,
783 }
784
785 /// How much of a notification is shown on the lock screen.
786 ///
787 /// It maps to the `Notification.VISIBILITY_*` constants and is serialized as its
788 /// integer value. Only available on Android.
789 #[derive(Debug, Clone, Copy, Serialize_repr, Deserialize_repr)]
790 #[repr(i8)]
791 pub enum Visibility {
792 /// The notification is not shown on the lock screen at all.
793 Secret = -1,
794 /// The notification is shown on the lock screen with its sensitive content hidden.
795 ///
796 /// This is the value used when the channel does not define a visibility.
797 Private = 0,
798 /// The notification is shown in full on the lock screen.
799 Public = 1,
800 }
801
802 /// A notification channel, the category users configure notification behavior on.
803 ///
804 /// Notifications reference a channel through
805 /// [`NotificationBuilder::channel_id`](crate::NotificationBuilder::channel_id) and are not
806 /// delivered when the channel does not exist. Only available on Android.
807 /// Use [`Channel::builder`] to construct one.
808 #[derive(Debug, Serialize, Deserialize)]
809 #[serde(rename_all = "camelCase")]
810 pub struct Channel {
811 id: String,
812 name: String,
813 description: Option<String>,
814 sound: Option<String>,
815 lights: bool,
816 light_color: Option<String>,
817 vibration: bool,
818 importance: Importance,
819 visibility: Option<Visibility>,
820 }
821
822 /// Builder for a [`Channel`], created with [`Channel::builder`].
823 ///
824 /// Only available on Android.
825 #[derive(Debug)]
826 pub struct ChannelBuilder(Channel);
827
828 impl Channel {
829 /// Creates a builder for a channel with the given identifier and user visible name.
830 ///
831 /// Lights and vibration are disabled, the importance defaults to [`Importance::Default`]
832 /// and the remaining settings default to [`None`];
833 /// call [`ChannelBuilder::build`] to get the [`Channel`].
834 pub fn builder(id: impl Into<String>, name: impl Into<String>) -> ChannelBuilder {
835 ChannelBuilder(Self {
836 id: id.into(),
837 name: name.into(),
838 description: None,
839 sound: None,
840 lights: false,
841 light_color: None,
842 vibration: false,
843 importance: Default::default(),
844 visibility: None,
845 })
846 }
847
848 /// The identifier of this channel.
849 pub fn id(&self) -> &str {
850 &self.id
851 }
852
853 /// The user visible name of this channel.
854 pub fn name(&self) -> &str {
855 &self.name
856 }
857
858 /// The user visible description of this channel.
859 pub fn description(&self) -> Option<&str> {
860 self.description.as_deref()
861 }
862
863 /// The name of the sound resource played by the notifications of this channel.
864 ///
865 /// The resource must be placed in the app's `res/raw` folder.
866 pub fn sound(&self) -> Option<&str> {
867 self.sound.as_deref()
868 }
869
870 /// Whether the notifications of this channel blink the device light.
871 pub fn lights(&self) -> bool {
872 self.lights
873 }
874
875 /// The color of the device light, as a color string such as `#ff0000`.
876 pub fn light_color(&self) -> Option<&str> {
877 self.light_color.as_deref()
878 }
879
880 /// Whether the notifications of this channel vibrate the device.
881 pub fn vibration(&self) -> bool {
882 self.vibration
883 }
884
885 /// How much the notifications of this channel interrupt the user.
886 pub fn importance(&self) -> Importance {
887 self.importance
888 }
889
890 /// How much of the notifications of this channel is shown on the lock screen.
891 ///
892 /// [`Visibility::Private`] is used when this is [`None`].
893 pub fn visibility(&self) -> Option<Visibility> {
894 self.visibility
895 }
896 }
897
898 impl ChannelBuilder {
899 /// Sets the user visible description of the channel.
900 pub fn description(mut self, description: impl Into<String>) -> Self {
901 self.0.description.replace(description.into());
902 self
903 }
904
905 /// Sets the name of the sound resource played by the notifications of this channel.
906 ///
907 /// The resource must be placed in the app's `res/raw` folder.
908 pub fn sound(mut self, sound: impl Into<String>) -> Self {
909 self.0.sound.replace(sound.into());
910 self
911 }
912
913 /// Sets whether the notifications of this channel blink the device light.
914 pub fn lights(mut self, lights: bool) -> Self {
915 self.0.lights = lights;
916 self
917 }
918
919 /// Sets the color of the device light, as a color string such as `#ff0000`.
920 pub fn light_color(mut self, color: impl Into<String>) -> Self {
921 self.0.light_color.replace(color.into());
922 self
923 }
924
925 /// Sets whether the notifications of this channel vibrate the device.
926 pub fn vibration(mut self, vibration: bool) -> Self {
927 self.0.vibration = vibration;
928 self
929 }
930
931 /// Sets how much the notifications of this channel interrupt the user.
932 pub fn importance(mut self, importance: Importance) -> Self {
933 self.0.importance = importance;
934 self
935 }
936
937 /// Sets how much of the notifications of this channel is shown on the lock screen.
938 pub fn visibility(mut self, visibility: Visibility) -> Self {
939 self.0.visibility.replace(visibility);
940 self
941 }
942
943 /// Builds the [`Channel`].
944 pub fn build(self) -> Channel {
945 self.0
946 }
947 }
948}