1use std::{collections::HashMap, fmt::Display};
2
3use serde::{de::Error as DeError, Deserialize, Deserializer, Serialize, Serializer};
4use tauri::plugin::PermissionState;
5
6use url::Url;
7
8#[derive(Debug, Deserialize)]
9#[serde(rename_all = "camelCase")]
10pub struct PermissionResponse {
11 pub permission_state: PermissionState,
12}
13
14#[cfg(feature = "push-notifications")]
15#[derive(Debug, Deserialize)]
16#[serde(rename_all = "camelCase")]
17pub struct PushNotificationResponse {
18 pub device_token: String,
19}
20
21#[derive(Debug, Clone, Serialize, Deserialize)]
22#[serde(rename_all = "camelCase")]
23pub struct Attachment {
24 id: String,
25 url: Url,
26}
27
28impl Attachment {
29 pub fn new(id: impl Into<String>, url: Url) -> Self {
30 Self { id: id.into(), url }
31 }
32
33 #[must_use]
34 pub fn id(&self) -> &str {
35 &self.id
36 }
37
38 #[must_use]
39 pub const fn url(&self) -> &Url {
40 &self.url
41 }
42}
43
44#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
45#[serde(rename_all = "camelCase")]
46pub struct ScheduleInterval {
47 pub year: Option<u8>,
48 pub month: Option<u8>,
49 pub day: Option<u8>,
50 pub weekday: Option<u8>,
51 pub hour: Option<u8>,
52 pub minute: Option<u8>,
53 pub second: Option<u8>,
54}
55
56#[derive(Debug, Clone, Copy)]
57pub enum ScheduleEvery {
58 Year,
59 Month,
60 TwoWeeks,
61 Week,
62 Day,
63 Hour,
64 Minute,
65 Second,
66}
67
68impl Display for ScheduleEvery {
69 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
70 write!(
71 f,
72 "{}",
73 match self {
74 Self::Year => "year",
75 Self::Month => "month",
76 Self::TwoWeeks => "twoWeeks",
77 Self::Week => "week",
78 Self::Day => "day",
79 Self::Hour => "hour",
80 Self::Minute => "minute",
81 Self::Second => "second",
82 }
83 )
84 }
85}
86
87impl Serialize for ScheduleEvery {
88 fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
89 where
90 S: Serializer,
91 {
92 serializer.serialize_str(self.to_string().as_ref())
93 }
94}
95
96impl<'de> Deserialize<'de> for ScheduleEvery {
97 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
98 where
99 D: Deserializer<'de>,
100 {
101 let s = String::deserialize(deserializer)?;
102 match s.to_lowercase().as_str() {
103 "year" => Ok(Self::Year),
104 "month" => Ok(Self::Month),
105 "twoweeks" => Ok(Self::TwoWeeks),
106 "week" => Ok(Self::Week),
107 "day" => Ok(Self::Day),
108 "hour" => Ok(Self::Hour),
109 "minute" => Ok(Self::Minute),
110 "second" => Ok(Self::Second),
111 _ => Err(DeError::custom(format!("unknown every kind '{s}'"))),
112 }
113 }
114}
115
116#[derive(Debug, Clone, Serialize, Deserialize)]
117#[serde(rename_all = "camelCase")]
118pub enum Schedule {
119 #[serde(rename_all = "camelCase")]
120 At {
121 #[serde(
122 serialize_with = "iso8601::serialize",
123 deserialize_with = "time::serde::iso8601::deserialize"
124 )]
125 date: time::OffsetDateTime,
126 #[serde(default)]
127 repeating: bool,
128 #[serde(default)]
129 allow_while_idle: bool,
130 },
131 #[serde(rename_all = "camelCase")]
132 Interval {
133 interval: ScheduleInterval,
134 #[serde(default)]
135 allow_while_idle: bool,
136 },
137 #[serde(rename_all = "camelCase")]
138 Every {
139 interval: ScheduleEvery,
140 count: u8,
141 #[serde(default)]
142 allow_while_idle: bool,
143 },
144}
145
146mod iso8601 {
148 use serde::{ser::Error as _, Serialize, Serializer};
149 use time::{
150 format_description::well_known::iso8601::{Config, EncodedConfig},
151 format_description::well_known::Iso8601,
152 OffsetDateTime,
153 };
154
155 const SERDE_CONFIG: EncodedConfig = Config::DEFAULT.encode();
156
157 pub fn serialize<S: Serializer>(
158 datetime: &OffsetDateTime,
159 serializer: S,
160 ) -> Result<S::Ok, S::Error> {
161 datetime
162 .format(&Iso8601::<SERDE_CONFIG>)
163 .map_err(S::Error::custom)?
164 .serialize(serializer)
165 }
166}
167
168#[allow(clippy::struct_excessive_bools)]
170#[derive(Debug, Serialize, Deserialize)]
171#[serde(rename_all = "camelCase")]
172pub struct NotificationData {
173 #[serde(default = "default_id")]
174 pub(crate) id: i32,
175 pub(crate) channel_id: Option<String>,
176 pub(crate) title: Option<String>,
177 pub(crate) body: Option<String>,
178 pub(crate) schedule: Option<Schedule>,
179 pub(crate) large_body: Option<String>,
180 pub(crate) summary: Option<String>,
181 pub(crate) action_type_id: Option<String>,
182 pub(crate) group: Option<String>,
183 #[serde(default)]
184 pub(crate) group_summary: bool,
185 pub(crate) sound: Option<String>,
186 #[serde(default)]
187 pub(crate) inbox_lines: Vec<String>,
188 pub(crate) icon: Option<String>,
189 pub(crate) large_icon: Option<String>,
190 pub(crate) icon_color: Option<String>,
191 #[serde(default)]
192 pub(crate) attachments: Vec<Attachment>,
193 #[serde(default)]
194 pub(crate) extra: HashMap<String, serde_json::Value>,
195 #[serde(default)]
196 pub(crate) ongoing: bool,
197 #[serde(default)]
198 pub(crate) auto_cancel: bool,
199 #[serde(default)]
200 pub(crate) silent: bool,
201}
202
203fn default_id() -> i32 {
204 rand::random()
205}
206
207impl Default for NotificationData {
208 fn default() -> Self {
209 Self {
210 id: default_id(),
211 channel_id: None,
212 title: None,
213 body: None,
214 schedule: None,
215 large_body: None,
216 summary: None,
217 action_type_id: None,
218 group: None,
219 group_summary: false,
220 sound: None,
221 inbox_lines: Vec::new(),
222 icon: None,
223 large_icon: None,
224 icon_color: None,
225 attachments: Vec::new(),
226 extra: HashMap::default(),
227 ongoing: false,
228 auto_cancel: false,
229 silent: false,
230 }
231 }
232}
233
234#[derive(Debug, Deserialize, Serialize)]
235#[serde(rename_all = "camelCase")]
236pub struct PendingNotification {
237 pub(crate) id: i32,
238 pub(crate) title: Option<String>,
239 pub(crate) body: Option<String>,
240 pub(crate) schedule: Schedule,
241}
242
243impl PendingNotification {
244 #[must_use]
245 pub const fn id(&self) -> i32 {
246 self.id
247 }
248
249 #[must_use]
250 pub fn title(&self) -> Option<&str> {
251 self.title.as_deref()
252 }
253
254 #[must_use]
255 pub fn body(&self) -> Option<&str> {
256 self.body.as_deref()
257 }
258
259 #[must_use]
260 pub const fn schedule(&self) -> &Schedule {
261 &self.schedule
262 }
263}
264
265#[derive(Debug, Clone, Deserialize, Serialize)]
266#[serde(rename_all = "camelCase")]
267pub struct ActiveNotification {
268 pub(crate) id: i32,
269 pub(crate) tag: Option<String>,
270 pub(crate) title: Option<String>,
271 pub(crate) body: Option<String>,
272 pub(crate) group: Option<String>,
273 #[serde(default)]
274 pub(crate) group_summary: bool,
275 #[serde(default)]
276 pub(crate) data: HashMap<String, String>,
277 #[serde(default)]
278 pub(crate) extra: HashMap<String, serde_json::Value>,
279 #[serde(default)]
280 pub(crate) attachments: Vec<Attachment>,
281 pub(crate) action_type_id: Option<String>,
282 pub(crate) schedule: Option<Schedule>,
283 pub(crate) sound: Option<String>,
284}
285
286impl ActiveNotification {
287 #[must_use]
291 pub fn new(id: i32, title: Option<String>, body: Option<String>) -> Self {
292 Self {
293 id,
294 tag: None,
295 title,
296 body,
297 group: None,
298 group_summary: false,
299 data: HashMap::new(),
300 extra: HashMap::new(),
301 attachments: Vec::new(),
302 action_type_id: None,
303 schedule: None,
304 sound: None,
305 }
306 }
307
308 #[must_use]
309 pub const fn id(&self) -> i32 {
310 self.id
311 }
312
313 #[must_use]
314 pub fn tag(&self) -> Option<&str> {
315 self.tag.as_deref()
316 }
317
318 #[must_use]
319 pub fn title(&self) -> Option<&str> {
320 self.title.as_deref()
321 }
322
323 #[must_use]
324 pub fn body(&self) -> Option<&str> {
325 self.body.as_deref()
326 }
327
328 #[must_use]
329 pub fn group(&self) -> Option<&str> {
330 self.group.as_deref()
331 }
332
333 #[must_use]
334 pub const fn group_summary(&self) -> bool {
335 self.group_summary
336 }
337
338 #[must_use]
339 pub const fn data(&self) -> &HashMap<String, String> {
340 &self.data
341 }
342
343 #[must_use]
344 pub const fn extra(&self) -> &HashMap<String, serde_json::Value> {
345 &self.extra
346 }
347
348 #[must_use]
349 pub fn attachments(&self) -> &[Attachment] {
350 &self.attachments
351 }
352
353 #[must_use]
354 pub fn action_type_id(&self) -> Option<&str> {
355 self.action_type_id.as_deref()
356 }
357
358 #[must_use]
359 pub const fn schedule(&self) -> Option<&Schedule> {
360 self.schedule.as_ref()
361 }
362
363 #[must_use]
364 pub fn sound(&self) -> Option<&str> {
365 self.sound.as_deref()
366 }
367}
368
369#[allow(clippy::struct_excessive_bools)]
371#[derive(Debug, Clone, Serialize, Deserialize)]
372#[serde(rename_all = "camelCase")]
373pub struct ActionType {
374 id: String,
375 actions: Vec<Action>,
376 hidden_previews_body_placeholder: Option<String>,
377 #[serde(default)]
378 custom_dismiss_action: bool,
379 #[serde(default)]
380 allow_in_car_play: bool,
381 #[serde(default)]
382 hidden_previews_show_title: bool,
383 #[serde(default)]
384 hidden_previews_show_subtitle: bool,
385}
386
387impl ActionType {
388 pub fn new(id: impl Into<String>, actions: Vec<Action>) -> Self {
389 Self {
390 id: id.into(),
391 actions,
392 hidden_previews_body_placeholder: None,
393 custom_dismiss_action: false,
394 allow_in_car_play: false,
395 hidden_previews_show_title: false,
396 hidden_previews_show_subtitle: false,
397 }
398 }
399
400 #[must_use]
401 pub fn id(&self) -> &str {
402 &self.id
403 }
404
405 #[must_use]
406 pub fn actions(&self) -> &[Action] {
407 &self.actions
408 }
409}
410
411#[allow(clippy::struct_excessive_bools)]
413#[derive(Debug, Clone, Serialize, Deserialize)]
414#[serde(rename_all = "camelCase")]
415pub struct Action {
416 id: String,
417 title: String,
418 #[serde(default)]
419 requires_authentication: bool,
420 #[serde(default)]
421 foreground: bool,
422 #[serde(default)]
423 destructive: bool,
424 #[serde(default)]
425 input: bool,
426 input_button_title: Option<String>,
427 input_placeholder: Option<String>,
428}
429
430impl Action {
431 pub fn new(id: impl Into<String>, title: impl Into<String>, foreground: bool) -> Self {
432 Self {
433 id: id.into(),
434 title: title.into(),
435 requires_authentication: false,
436 foreground,
437 destructive: false,
438 input: false,
439 input_button_title: None,
440 input_placeholder: None,
441 }
442 }
443
444 #[must_use]
445 pub fn id(&self) -> &str {
446 &self.id
447 }
448
449 #[must_use]
450 pub fn title(&self) -> &str {
451 &self.title
452 }
453
454 #[must_use]
455 pub const fn foreground(&self) -> bool {
456 self.foreground
457 }
458}
459
460pub use android::*;
461
462mod android {
463 use serde::{Deserialize, Serialize};
464 use serde_repr::{Deserialize_repr, Serialize_repr};
465
466 #[derive(Debug, Default, Clone, Copy, Serialize_repr, Deserialize_repr)]
467 #[repr(u8)]
468 pub enum Importance {
469 None = 0,
470 Min = 1,
471 Low = 2,
472 #[default]
473 Default = 3,
474 High = 4,
475 }
476
477 #[derive(Debug, Clone, Copy, Serialize_repr, Deserialize_repr)]
478 #[repr(i8)]
479 pub enum Visibility {
480 Secret = -1,
481 Private = 0,
482 Public = 1,
483 }
484
485 #[derive(Debug, Serialize, Deserialize)]
486 #[serde(rename_all = "camelCase")]
487 pub struct Channel {
488 id: String,
489 name: String,
490 description: Option<String>,
491 sound: Option<String>,
492 lights: Option<bool>,
493 light_color: Option<String>,
494 vibration: Option<bool>,
495 importance: Option<Importance>,
496 visibility: Option<Visibility>,
497 }
498
499 #[derive(Debug)]
500 pub struct ChannelBuilder(Channel);
501
502 impl Channel {
503 pub fn builder(id: impl Into<String>, name: impl Into<String>) -> ChannelBuilder {
504 ChannelBuilder(Self {
505 id: id.into(),
506 name: name.into(),
507 description: None,
508 sound: None,
509 lights: Some(false),
510 light_color: None,
511 vibration: Some(false),
512 importance: None,
513 visibility: None,
514 })
515 }
516
517 #[must_use]
518 pub fn id(&self) -> &str {
519 &self.id
520 }
521
522 #[must_use]
523 pub fn name(&self) -> &str {
524 &self.name
525 }
526
527 #[must_use]
528 pub fn description(&self) -> Option<&str> {
529 self.description.as_deref()
530 }
531
532 #[must_use]
533 pub fn sound(&self) -> Option<&str> {
534 self.sound.as_deref()
535 }
536
537 #[must_use]
538 pub fn lights(&self) -> bool {
539 self.lights.unwrap_or(false)
540 }
541
542 #[must_use]
543 pub fn light_color(&self) -> Option<&str> {
544 self.light_color.as_deref()
545 }
546
547 #[must_use]
548 pub fn vibration(&self) -> bool {
549 self.vibration.unwrap_or(false)
550 }
551
552 #[must_use]
553 pub fn importance(&self) -> Importance {
554 self.importance.unwrap_or_default()
555 }
556
557 #[must_use]
558 pub const fn visibility(&self) -> Option<Visibility> {
559 self.visibility
560 }
561 }
562
563 impl ChannelBuilder {
564 #[must_use]
565 pub fn description(mut self, description: impl Into<String>) -> Self {
566 self.0.description.replace(description.into());
567 self
568 }
569
570 #[must_use]
571 pub fn sound(mut self, sound: impl Into<String>) -> Self {
572 self.0.sound.replace(sound.into());
573 self
574 }
575
576 #[must_use]
577 pub const fn lights(mut self, lights: bool) -> Self {
578 self.0.lights = Some(lights);
579 self
580 }
581
582 #[must_use]
583 pub fn light_color(mut self, color: impl Into<String>) -> Self {
584 self.0.light_color.replace(color.into());
585 self
586 }
587
588 #[must_use]
589 pub const fn vibration(mut self, vibration: bool) -> Self {
590 self.0.vibration = Some(vibration);
591 self
592 }
593
594 #[must_use]
595 pub const fn importance(mut self, importance: Importance) -> Self {
596 self.0.importance = Some(importance);
597 self
598 }
599
600 #[must_use]
601 pub fn visibility(mut self, visibility: Visibility) -> Self {
602 self.0.visibility.replace(visibility);
603 self
604 }
605
606 #[must_use]
607 pub fn build(self) -> Channel {
608 self.0
609 }
610 }
611}
612
613#[cfg(test)]
614mod tests {
615 use super::*;
616
617 #[test]
618 fn test_attachment_creation() {
619 let url = Url::parse("https://example.com/image.png").expect("Failed to parse URL");
620 let attachment = Attachment::new("test_id", url.clone());
621 assert_eq!(attachment.id, "test_id");
622 assert_eq!(attachment.url, url);
623 }
624
625 #[test]
626 fn test_attachment_serialization() {
627 let url = Url::parse("https://example.com/image.png").expect("Failed to parse URL");
628 let attachment = Attachment::new("test_id", url);
629 let json = serde_json::to_string(&attachment).expect("Failed to serialize attachment");
630 assert!(json.contains("test_id"));
631 assert!(json.contains("https://example.com/image.png"));
632 }
633
634 #[test]
635 fn test_attachment_deserialization() {
636 let json = r#"{"id":"test_id","url":"https://example.com/image.png"}"#;
637 let attachment: Attachment =
638 serde_json::from_str(json).expect("Failed to deserialize attachment");
639 assert_eq!(attachment.id, "test_id");
640 assert_eq!(attachment.url.as_str(), "https://example.com/image.png");
641 }
642
643 #[test]
644 fn test_schedule_every_display() {
645 assert_eq!(ScheduleEvery::Year.to_string(), "year");
646 assert_eq!(ScheduleEvery::Month.to_string(), "month");
647 assert_eq!(ScheduleEvery::TwoWeeks.to_string(), "twoWeeks");
648 assert_eq!(ScheduleEvery::Week.to_string(), "week");
649 assert_eq!(ScheduleEvery::Day.to_string(), "day");
650 assert_eq!(ScheduleEvery::Hour.to_string(), "hour");
651 assert_eq!(ScheduleEvery::Minute.to_string(), "minute");
652 assert_eq!(ScheduleEvery::Second.to_string(), "second");
653 }
654
655 #[test]
656 fn test_schedule_every_serialization() {
657 let json = serde_json::to_string(&ScheduleEvery::Day).expect("Failed to serialize Day");
658 assert_eq!(json, "\"day\"");
659
660 let json =
661 serde_json::to_string(&ScheduleEvery::TwoWeeks).expect("Failed to serialize TwoWeeks");
662 assert_eq!(json, "\"twoWeeks\"");
663 }
664
665 #[test]
666 fn test_schedule_every_deserialization() {
667 let every: ScheduleEvery =
668 serde_json::from_str("\"year\"").expect("Failed to deserialize year");
669 assert!(matches!(every, ScheduleEvery::Year));
670
671 let every: ScheduleEvery =
672 serde_json::from_str("\"month\"").expect("Failed to deserialize month");
673 assert!(matches!(every, ScheduleEvery::Month));
674
675 let every: ScheduleEvery =
676 serde_json::from_str("\"twoweeks\"").expect("Failed to deserialize twoweeks");
677 assert!(matches!(every, ScheduleEvery::TwoWeeks));
678
679 let every: ScheduleEvery =
680 serde_json::from_str("\"week\"").expect("Failed to deserialize week");
681 assert!(matches!(every, ScheduleEvery::Week));
682
683 let every: ScheduleEvery =
684 serde_json::from_str("\"day\"").expect("Failed to deserialize day");
685 assert!(matches!(every, ScheduleEvery::Day));
686
687 let every: ScheduleEvery =
688 serde_json::from_str("\"hour\"").expect("Failed to deserialize hour");
689 assert!(matches!(every, ScheduleEvery::Hour));
690
691 let every: ScheduleEvery =
692 serde_json::from_str("\"minute\"").expect("Failed to deserialize minute");
693 assert!(matches!(every, ScheduleEvery::Minute));
694
695 let every: ScheduleEvery =
696 serde_json::from_str("\"second\"").expect("Failed to deserialize second");
697 assert!(matches!(every, ScheduleEvery::Second));
698 }
699
700 #[test]
701 fn test_schedule_every_deserialization_invalid() {
702 let result: Result<ScheduleEvery, _> = serde_json::from_str("\"invalid\"");
703 assert!(result.is_err());
704 }
705
706 #[test]
707 fn test_schedule_interval_default() {
708 let interval = ScheduleInterval::default();
709 assert!(interval.year.is_none());
710 assert!(interval.month.is_none());
711 assert!(interval.day.is_none());
712 assert!(interval.weekday.is_none());
713 assert!(interval.hour.is_none());
714 assert!(interval.minute.is_none());
715 assert!(interval.second.is_none());
716 }
717
718 #[test]
719 fn test_schedule_interval_serialization() {
720 let interval = ScheduleInterval {
721 year: Some(24),
722 month: Some(12),
723 day: Some(25),
724 weekday: Some(1),
725 hour: Some(10),
726 minute: Some(30),
727 second: Some(0),
728 };
729 let json = serde_json::to_string(&interval).expect("Failed to serialize interval");
730 assert!(json.contains("\"year\":24"));
731 assert!(json.contains("\"month\":12"));
732 assert!(json.contains("\"day\":25"));
733 }
734
735 #[test]
736 fn test_notification_data_default() {
737 let data = NotificationData::default();
738 assert!(data.id != 0); assert!(data.channel_id.is_none());
740 assert!(data.title.is_none());
741 assert!(data.body.is_none());
742 assert!(data.schedule.is_none());
743 assert!(!data.group_summary);
744 assert!(!data.ongoing);
745 assert!(!data.auto_cancel);
746 assert!(!data.silent);
747 assert!(data.inbox_lines.is_empty());
748 assert!(data.attachments.is_empty());
749 assert!(data.extra.is_empty());
750 }
751
752 #[test]
753 fn test_notification_data_serialization() {
754 let data = NotificationData {
755 id: 123,
756 title: Some("Test Title".to_string()),
757 body: Some("Test Body".to_string()),
758 ongoing: true,
759 ..Default::default()
760 };
761
762 let json = serde_json::to_string(&data).expect("Failed to serialize notification data");
763 assert!(json.contains("\"id\":123"));
764 assert!(json.contains("\"title\":\"Test Title\""));
765 assert!(json.contains("\"body\":\"Test Body\""));
766 assert!(json.contains("\"ongoing\":true"));
767 }
768
769 #[test]
770 fn test_pending_notification_getters() {
771 let json = r#"{
772 "id": 456,
773 "title": "Pending Title",
774 "body": "Pending Body",
775 "schedule": {"every": {"interval": "day", "count": 1}}
776 }"#;
777 let pending: PendingNotification =
778 serde_json::from_str(json).expect("Failed to deserialize pending notification");
779
780 assert_eq!(pending.id(), 456);
781 assert_eq!(pending.title(), Some("Pending Title"));
782 assert_eq!(pending.body(), Some("Pending Body"));
783 assert!(matches!(pending.schedule(), Schedule::Every { .. }));
784 }
785
786 #[test]
787 fn test_active_notification_getters() {
788 let json = r#"{
789 "id": 789,
790 "title": "Active Title",
791 "body": "Active Body",
792 "group": "test_group",
793 "groupSummary": true
794 }"#;
795 let active: ActiveNotification =
796 serde_json::from_str(json).expect("Failed to deserialize active notification");
797
798 assert_eq!(active.id(), 789);
799 assert_eq!(active.title(), Some("Active Title"));
800 assert_eq!(active.body(), Some("Active Body"));
801 assert_eq!(active.group(), Some("test_group"));
802 assert!(active.group_summary());
803 assert!(active.data().is_empty());
804 assert!(active.extra().is_empty());
805 assert!(active.attachments().is_empty());
806 assert!(active.action_type_id().is_none());
807 assert!(active.schedule().is_none());
808 assert!(active.sound().is_none());
809 }
810
811 #[cfg(target_os = "android")]
812 #[test]
813 fn test_importance_default() {
814 let importance = Importance::default();
815 assert!(matches!(importance, Importance::Default));
816 }
817
818 #[cfg(target_os = "android")]
819 #[test]
820 fn test_importance_serialization() {
821 assert_eq!(
822 serde_json::to_string(&Importance::None).expect("Failed to serialize Importance::None"),
823 "0"
824 );
825 assert_eq!(
826 serde_json::to_string(&Importance::Min).expect("Failed to serialize Importance::Min"),
827 "1"
828 );
829 assert_eq!(
830 serde_json::to_string(&Importance::Low).expect("Failed to serialize Importance::Low"),
831 "2"
832 );
833 assert_eq!(
834 serde_json::to_string(&Importance::Default)
835 .expect("Failed to serialize Importance::Default"),
836 "3"
837 );
838 assert_eq!(
839 serde_json::to_string(&Importance::High).expect("Failed to serialize Importance::High"),
840 "4"
841 );
842 }
843
844 #[cfg(target_os = "android")]
845 #[test]
846 fn test_visibility_serialization() {
847 assert_eq!(
848 serde_json::to_string(&Visibility::Secret)
849 .expect("Failed to serialize Visibility::Secret"),
850 "-1"
851 );
852 assert_eq!(
853 serde_json::to_string(&Visibility::Private)
854 .expect("Failed to serialize Visibility::Private"),
855 "0"
856 );
857 assert_eq!(
858 serde_json::to_string(&Visibility::Public)
859 .expect("Failed to serialize Visibility::Public"),
860 "1"
861 );
862 }
863
864 #[cfg(target_os = "android")]
865 #[test]
866 fn test_channel_builder() {
867 let channel = Channel::builder("test_id", "Test Channel")
868 .description("Test Description")
869 .sound("test_sound")
870 .lights(true)
871 .light_color("#FF0000")
872 .vibration(true)
873 .importance(Importance::High)
874 .visibility(Visibility::Public)
875 .build();
876
877 assert_eq!(channel.id(), "test_id");
878 assert_eq!(channel.name(), "Test Channel");
879 assert_eq!(channel.description(), Some("Test Description"));
880 assert_eq!(channel.sound(), Some("test_sound"));
881 assert!(channel.lights());
882 assert_eq!(channel.light_color(), Some("#FF0000"));
883 assert!(channel.vibration());
884 assert!(matches!(channel.importance(), Importance::High));
885 assert_eq!(channel.visibility(), Some(Visibility::Public));
886 }
887
888 #[cfg(target_os = "android")]
889 #[test]
890 fn test_channel_builder_minimal() {
891 let channel = Channel::builder("minimal_id", "Minimal Channel").build();
892
893 assert_eq!(channel.id(), "minimal_id");
894 assert_eq!(channel.name(), "Minimal Channel");
895 assert_eq!(channel.description(), None);
896 assert_eq!(channel.sound(), None);
897 assert!(!channel.lights());
898 assert_eq!(channel.light_color(), None);
899 assert!(!channel.vibration());
900 assert!(matches!(channel.importance(), Importance::Default));
901 assert_eq!(channel.visibility(), None);
902 }
903
904 #[test]
905 fn test_schedule_at_serialization() {
906 use time::OffsetDateTime;
907
908 let date = OffsetDateTime::now_utc();
909 let schedule = Schedule::At {
910 date,
911 repeating: true,
912 allow_while_idle: false,
913 };
914
915 let json = serde_json::to_string(&schedule).expect("Failed to serialize Schedule::At");
916 assert!(json.contains("\"at\""));
917 assert!(json.contains("\"date\""));
918 assert!(json.contains("\"repeating\":true"));
919 assert!(json.contains("\"allowWhileIdle\":false"));
920 }
921
922 #[test]
923 fn test_schedule_interval_variant() {
924 let schedule = Schedule::Interval {
925 interval: ScheduleInterval {
926 hour: Some(10),
927 minute: Some(30),
928 ..Default::default()
929 },
930 allow_while_idle: true,
931 };
932
933 let json =
934 serde_json::to_string(&schedule).expect("Failed to serialize Schedule::Interval");
935 assert!(json.contains("\"interval\""));
936 assert!(json.contains("\"hour\":10"));
937 assert!(json.contains("\"minute\":30"));
938 assert!(json.contains("\"allowWhileIdle\":true"));
939 }
940
941 #[test]
942 fn test_schedule_every_variant() {
943 let schedule = Schedule::Every {
944 interval: ScheduleEvery::Day,
945 count: 5,
946 allow_while_idle: false,
947 };
948
949 let json = serde_json::to_string(&schedule).expect("Failed to serialize Schedule::Every");
950 assert!(json.contains("\"every\""));
951 assert!(json.contains("\"interval\":\"day\""));
952 assert!(json.contains("\"count\":5"));
953 }
954}