Skip to main content

max / quasi

3.9 KB · 109 lines History Blame Raw
1 //! The Tauri half of delivery: putting an occurrence in front of a person.
2 //!
3 //! Behind the `tauri` feature, and here rather than in `quasi-tauri` because
4 //! that crate takes `quasi-router` and `quasi-http` unconditionally -- the
5 //! protocol handler is most of what it is -- so an app wanting delivery had to
6 //! link the whole description stack to get eight lines over a plugin. This
7 //! crate already owns the abstraction the eight lines implement
8 //! ([`Deliver`](crate::Deliver), [`Outbox`](crate::Outbox)) and already makes
9 //! its heavier halves optional for exactly this reason, so a third feature is
10 //! the pattern rather than an exception.
11 //!
12 //! Tauri first, because it is the host with a consumer. goingson is already on
13 //! `tauri-plugin-notification` and its watcher is the reference implementation
14 //! for what an adapter owes -- except that almost none of what that file does
15 //! is about Tauri. Consulting the kind's `enabled` key, suppressing a duplicate,
16 //! not spamming after a restart and bounding the memory are all
17 //! [`Outbox`](crate::Outbox)'s, and they are tested there against no host
18 //! at all.
19 //!
20 //! What is left here is the part that genuinely is the host's, and it is one
21 //! call. That is the split working rather than the adapter being thin by
22 //! accident: the terminal and egui hosts, when a consumer appears for them,
23 //! have the same amount left to write.
24 //!
25 //! ```no_run
26 //! use quasi_notifs::{Kind, Notifier, Occurrence, Outbox, Registry, config::Unset};
27 //!
28 //! static KINDS: &[Kind] = &[Kind::new(
29 //! "snooze-expiry",
30 //! "Snoozed items resurface",
31 //! "When something you snoozed comes back.",
32 //! "Reminders",
33 //! )
34 //! .shipping_on()];
35 //! static NOTIFS: Registry = Registry::new(KINDS);
36 //!
37 //! # fn watcher(app: tauri::AppHandle) {
38 //! let mut outbox = Outbox::new(NOTIFS, Notifier::new(app));
39 //! // Per tick, per kind: a pass. What was due while the app was shut is the
40 //! // kind's own answer, declared once, honoured here.
41 //! let mut pass = outbox.sweep("snooze-expiry");
42 //! pass.offer(
43 //! &Occurrence::new("snooze-expiry", "task-7", "Task resurfaced", "Write the thing"),
44 //! &Unset,
45 //! );
46 //! # }
47 //! ```
48
49 use crate::{Deliver, Occurrence};
50 use tauri::{AppHandle, Runtime};
51 use tauri_plugin_notification::NotificationExt;
52
53 /// Delivers through `tauri-plugin-notification`.
54 ///
55 /// Holds an [`AppHandle`], which is what the plugin extension hangs off. One
56 /// per app, owned by the [`Outbox`](crate::Outbox) that decides whether
57 /// anything reaches it.
58 ///
59 /// Generic over the runtime, like everything else tauri hands out: this crate
60 /// takes `tauri` with default features off, so there is no `Wry` to default to
61 /// and an app names its own.
62 #[derive(Debug, Clone)]
63 pub struct Notifier<R: Runtime> {
64 app: AppHandle<R>,
65 refused: usize,
66 }
67
68 impl<R: Runtime> Notifier<R> {
69 /// A notifier over this app.
70 #[must_use]
71 pub const fn new(app: AppHandle<R>) -> Self {
72 Self { app, refused: 0 }
73 }
74
75 /// How many occurrences the platform refused to show.
76 ///
77 /// [`Deliver`] cannot fail in a way a caller could act on -- an outbox that
78 /// retried would be a queue nobody asked for, and the occurrence is already
79 /// noted as sent -- but a host that has failed every time has a permission
80 /// problem rather than a quiet user, and those look identical without this.
81 /// Read it where the watcher logs.
82 #[must_use]
83 pub const fn refused(&self) -> usize {
84 self.refused
85 }
86
87 /// The app this delivers through.
88 #[must_use]
89 pub const fn app(&self) -> &AppHandle<R> {
90 &self.app
91 }
92 }
93
94 impl<R: Runtime> Deliver for Notifier<R> {
95 fn deliver(&mut self, note: &Occurrence) {
96 if self
97 .app
98 .notification()
99 .builder()
100 .title(&note.title)
101 .body(&note.body)
102 .show()
103 .is_err()
104 {
105 self.refused += 1;
106 }
107 }
108 }
109