1use std::{collections::HashMap, fmt::Display};
6
7use serde::{de::Error as DeError, Deserialize, Deserializer, Serialize, Serializer};
8
9use url::Url;
10
11#[derive(Debug, Serialize, Deserialize)]
12#[serde(rename_all = "camelCase")]
13pub struct Attachment {
14 id: String,
15 url: Url,
16}
17
18impl Attachment {
19 pub fn new(id: impl Into<String>, url: Url) -> Self {
20 Self { id: id.into(), url }
21 }
22}
23
24#[derive(Debug, Default, Serialize, Deserialize)]
25#[serde(rename_all = "camelCase")]
26pub struct ScheduleInterval {
27 pub year: Option<u8>,
28 pub month: Option<u8>,
29 pub day: Option<u8>,
30 pub weekday: Option<u8>,
31 pub hour: Option<u8>,
32 pub minute: Option<u8>,
33 pub second: Option<u8>,
34}
35
36#[derive(Debug)]
37pub enum ScheduleEvery {
38 Year,
39 Month,
40 TwoWeeks,
41 Week,
42 Day,
43 Hour,
44 Minute,
45 Second,
46}
47
48impl Display for ScheduleEvery {
49 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
50 write!(
51 f,
52 "{}",
53 match self {
54 Self::Year => "year",
55 Self::Month => "month",
56 Self::TwoWeeks => "twoWeeks",
57 Self::Week => "week",
58 Self::Day => "day",
59 Self::Hour => "hour",
60 Self::Minute => "minute",
61 Self::Second => "second",
62 }
63 )
64 }
65}
66
67impl Serialize for ScheduleEvery {
68 fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
69 where
70 S: Serializer,
71 {
72 serializer.serialize_str(self.to_string().as_ref())
73 }
74}
75
76impl<'de> Deserialize<'de> for ScheduleEvery {
77 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
78 where
79 D: Deserializer<'de>,
80 {
81 let s = String::deserialize(deserializer)?;
82 match s.to_lowercase().as_str() {
83 "year" => Ok(Self::Year),
84 "month" => Ok(Self::Month),
85 "twoweeks" => Ok(Self::TwoWeeks),
86 "week" => Ok(Self::Week),
87 "day" => Ok(Self::Day),
88 "hour" => Ok(Self::Hour),
89 "minute" => Ok(Self::Minute),
90 "second" => Ok(Self::Second),
91 _ => Err(DeError::custom(format!("unknown every kind '{s}'"))),
92 }
93 }
94}
95
96#[derive(Debug, Serialize, Deserialize)]
97#[serde(rename_all = "camelCase")]
98pub enum Schedule {
99 #[serde(rename_all = "camelCase")]
100 At {
101 #[serde(
102 serialize_with = "iso8601::serialize",
103 deserialize_with = "time::serde::iso8601::deserialize"
104 )]
105 date: time::OffsetDateTime,
106 #[serde(default)]
107 repeating: bool,
108 #[serde(default)]
109 allow_while_idle: bool,
110 },
111 #[serde(rename_all = "camelCase")]
112 Interval {
113 interval: ScheduleInterval,
114 #[serde(default)]
115 allow_while_idle: bool,
116 },
117 #[serde(rename_all = "camelCase")]
118 Every {
119 interval: ScheduleEvery,
120 count: u8,
121 #[serde(default)]
122 allow_while_idle: bool,
123 },
124}
125
126mod iso8601 {
128 use serde::{ser::Error as _, Serialize, Serializer};
129 use time::{
130 format_description::well_known::iso8601::{Config, EncodedConfig},
131 format_description::well_known::Iso8601,
132 OffsetDateTime,
133 };
134
135 const SERDE_CONFIG: EncodedConfig = Config::DEFAULT.encode();
136
137 pub fn serialize<S: Serializer>(
138 datetime: &OffsetDateTime,
139 serializer: S,
140 ) -> Result<S::Ok, S::Error> {
141 datetime
142 .format(&Iso8601::<SERDE_CONFIG>)
143 .map_err(S::Error::custom)?
144 .serialize(serializer)
145 }
146}
147
148#[derive(Debug, Serialize, Deserialize)]
149#[serde(rename_all = "camelCase")]
150pub struct NotificationData {
151 #[serde(default = "default_id")]
152 pub(crate) id: i32,
153 pub(crate) channel_id: Option<String>,
154 pub(crate) title: Option<String>,
155 pub(crate) body: Option<String>,
156 pub(crate) schedule: Option<Schedule>,
157 pub(crate) large_body: Option<String>,
158 pub(crate) summary: Option<String>,
159 pub(crate) action_type_id: Option<String>,
160 pub(crate) group: Option<String>,
161 #[serde(default)]
162 pub(crate) group_summary: bool,
163 pub(crate) sound: Option<String>,
164 #[serde(default)]
165 pub(crate) inbox_lines: Vec<String>,
166 pub(crate) icon: Option<String>,
167 pub(crate) large_icon: Option<String>,
168 pub(crate) icon_color: Option<String>,
169 #[serde(default)]
170 pub(crate) attachments: Vec<Attachment>,
171 #[serde(default)]
172 pub(crate) extra: HashMap<String, serde_json::Value>,
173 #[serde(default)]
174 pub(crate) ongoing: bool,
175 #[serde(default)]
176 pub(crate) auto_cancel: bool,
177 #[serde(default)]
178 pub(crate) silent: bool,
179}
180
181fn default_id() -> i32 {
182 rand::random()
183}
184
185impl Default for NotificationData {
186 fn default() -> Self {
187 Self {
188 id: default_id(),
189 channel_id: None,
190 title: None,
191 body: None,
192 schedule: None,
193 large_body: None,
194 summary: None,
195 action_type_id: None,
196 group: None,
197 group_summary: false,
198 sound: None,
199 inbox_lines: Vec::new(),
200 icon: None,
201 large_icon: None,
202 icon_color: None,
203 attachments: Vec::new(),
204 extra: Default::default(),
205 ongoing: false,
206 auto_cancel: false,
207 silent: false,
208 }
209 }
210}
211
212#[derive(Debug, Deserialize)]
213#[serde(rename_all = "camelCase")]
214pub struct PendingNotification {
215 id: i32,
216 title: Option<String>,
217 body: Option<String>,
218 schedule: Schedule,
219}
220
221impl PendingNotification {
222 pub fn id(&self) -> i32 {
223 self.id
224 }
225
226 pub fn title(&self) -> Option<&str> {
227 self.title.as_deref()
228 }
229
230 pub fn body(&self) -> Option<&str> {
231 self.body.as_deref()
232 }
233
234 pub fn schedule(&self) -> &Schedule {
235 &self.schedule
236 }
237}
238
239#[derive(Debug, Deserialize)]
240#[serde(rename_all = "camelCase")]
241pub struct ActiveNotification {
242 id: i32,
243 tag: Option<String>,
244 title: Option<String>,
245 body: Option<String>,
246 group: Option<String>,
247 #[serde(default)]
248 group_summary: bool,
249 #[serde(default)]
250 data: HashMap<String, String>,
251 #[serde(default)]
252 extra: HashMap<String, serde_json::Value>,
253 #[serde(default)]
254 attachments: Vec<Attachment>,
255 action_type_id: Option<String>,
256 schedule: Option<Schedule>,
257 sound: Option<String>,
258}
259
260impl ActiveNotification {
261 pub fn id(&self) -> i32 {
262 self.id
263 }
264
265 pub fn tag(&self) -> Option<&str> {
266 self.tag.as_deref()
267 }
268
269 pub fn title(&self) -> Option<&str> {
270 self.title.as_deref()
271 }
272
273 pub fn body(&self) -> Option<&str> {
274 self.body.as_deref()
275 }
276
277 pub fn group(&self) -> Option<&str> {
278 self.group.as_deref()
279 }
280
281 pub fn group_summary(&self) -> bool {
282 self.group_summary
283 }
284
285 pub fn data(&self) -> &HashMap<String, String> {
286 &self.data
287 }
288
289 pub fn extra(&self) -> &HashMap<String, serde_json::Value> {
290 &self.extra
291 }
292
293 pub fn attachments(&self) -> &[Attachment] {
294 &self.attachments
295 }
296
297 pub fn action_type_id(&self) -> Option<&str> {
298 self.action_type_id.as_deref()
299 }
300
301 pub fn schedule(&self) -> Option<&Schedule> {
302 self.schedule.as_ref()
303 }
304
305 pub fn sound(&self) -> Option<&str> {
306 self.sound.as_deref()
307 }
308}
309
310#[cfg(mobile)]
311#[derive(Debug, Serialize)]
312#[serde(rename_all = "camelCase")]
313pub struct ActionType {
314 id: String,
315 actions: Vec<Action>,
316 hidden_previews_body_placeholder: Option<String>,
317 custom_dismiss_action: bool,
318 allow_in_car_play: bool,
319 hidden_previews_show_title: bool,
320 hidden_previews_show_subtitle: bool,
321}
322
323#[cfg(mobile)]
324#[derive(Debug, Serialize)]
325#[serde(rename_all = "camelCase")]
326pub struct Action {
327 id: String,
328 title: String,
329 requires_authentication: bool,
330 foreground: bool,
331 destructive: bool,
332 input: bool,
333 input_button_title: Option<String>,
334 input_placeholder: Option<String>,
335}
336
337#[cfg(target_os = "android")]
338pub use android::*;
339
340#[cfg(target_os = "android")]
341mod android {
342 use serde::{Deserialize, Serialize};
343 use serde_repr::{Deserialize_repr, Serialize_repr};
344
345 #[derive(Debug, Clone, Copy, Serialize_repr, Deserialize_repr)]
346 #[repr(u8)]
347 pub enum Importance {
348 None = 0,
349 Min = 1,
350 Low = 2,
351 Default = 3,
352 High = 4,
353 }
354
355 impl Default for Importance {
356 fn default() -> Self {
357 Self::Default
358 }
359 }
360
361 #[derive(Debug, Clone, Copy, Serialize_repr, Deserialize_repr)]
362 #[repr(i8)]
363 pub enum Visibility {
364 Secret = -1,
365 Private = 0,
366 Public = 1,
367 }
368
369 #[derive(Debug, Serialize, Deserialize)]
370 #[serde(rename_all = "camelCase")]
371 pub struct Channel {
372 id: String,
373 name: String,
374 description: Option<String>,
375 sound: Option<String>,
376 lights: bool,
377 light_color: Option<String>,
378 vibration: bool,
379 importance: Importance,
380 visibility: Option<Visibility>,
381 }
382
383 #[derive(Debug)]
384 pub struct ChannelBuilder(Channel);
385
386 impl Channel {
387 pub fn builder(id: impl Into<String>, name: impl Into<String>) -> ChannelBuilder {
388 ChannelBuilder(Self {
389 id: id.into(),
390 name: name.into(),
391 description: None,
392 sound: None,
393 lights: false,
394 light_color: None,
395 vibration: false,
396 importance: Default::default(),
397 visibility: None,
398 })
399 }
400
401 pub fn id(&self) -> &str {
402 &self.id
403 }
404
405 pub fn name(&self) -> &str {
406 &self.name
407 }
408
409 pub fn description(&self) -> Option<&str> {
410 self.description.as_deref()
411 }
412
413 pub fn sound(&self) -> Option<&str> {
414 self.sound.as_deref()
415 }
416
417 pub fn lights(&self) -> bool {
418 self.lights
419 }
420
421 pub fn light_color(&self) -> Option<&str> {
422 self.light_color.as_deref()
423 }
424
425 pub fn vibration(&self) -> bool {
426 self.vibration
427 }
428
429 pub fn importance(&self) -> Importance {
430 self.importance
431 }
432
433 pub fn visibility(&self) -> Option<Visibility> {
434 self.visibility
435 }
436 }
437
438 impl ChannelBuilder {
439 pub fn description(mut self, description: impl Into<String>) -> Self {
440 self.0.description.replace(description.into());
441 self
442 }
443
444 pub fn sound(mut self, sound: impl Into<String>) -> Self {
445 self.0.sound.replace(sound.into());
446 self
447 }
448
449 pub fn lights(mut self, lights: bool) -> Self {
450 self.0.lights = lights;
451 self
452 }
453
454 pub fn light_color(mut self, color: impl Into<String>) -> Self {
455 self.0.light_color.replace(color.into());
456 self
457 }
458
459 pub fn vibration(mut self, vibration: bool) -> Self {
460 self.0.vibration = vibration;
461 self
462 }
463
464 pub fn importance(mut self, importance: Importance) -> Self {
465 self.0.importance = importance;
466 self
467 }
468
469 pub fn visibility(mut self, visibility: Visibility) -> Self {
470 self.0.visibility.replace(visibility);
471 self
472 }
473
474 pub fn build(self) -> Channel {
475 self.0
476 }
477 }
478}