//! The Tauri half of delivery: putting an occurrence in front of a person. //! //! Behind the `tauri` feature, and here rather than in `quasi-tauri` because //! that crate takes `quasi-router` and `quasi-http` unconditionally -- the //! protocol handler is most of what it is -- so an app wanting delivery had to //! link the whole description stack to get eight lines over a plugin. This //! crate already owns the abstraction the eight lines implement //! ([`Deliver`](crate::Deliver), [`Outbox`](crate::Outbox)) and already makes //! its heavier halves optional for exactly this reason, so a third feature is //! the pattern rather than an exception. //! //! Tauri first, because it is the host with a consumer. goingson is already on //! `tauri-plugin-notification` and its watcher is the reference implementation //! for what an adapter owes -- except that almost none of what that file does //! is about Tauri. Consulting the kind's `enabled` key, suppressing a duplicate, //! not spamming after a restart and bounding the memory are all //! [`Outbox`](crate::Outbox)'s, and they are tested there against no host //! at all. //! //! What is left here is the part that genuinely is the host's, and it is one //! call. That is the split working rather than the adapter being thin by //! accident: the terminal and egui hosts, when a consumer appears for them, //! have the same amount left to write. //! //! ```no_run //! use quasi_notifs::{Kind, Notifier, Occurrence, Outbox, Registry, config::Unset}; //! //! static KINDS: &[Kind] = &[Kind::new( //! "snooze-expiry", //! "Snoozed items resurface", //! "When something you snoozed comes back.", //! "Reminders", //! ) //! .shipping_on()]; //! static NOTIFS: Registry = Registry::new(KINDS); //! //! # fn watcher(app: tauri::AppHandle) { //! let mut outbox = Outbox::new(NOTIFS, Notifier::new(app)); //! // Per tick, per kind: a pass. What was due while the app was shut is the //! // kind's own answer, declared once, honoured here. //! let mut pass = outbox.sweep("snooze-expiry"); //! pass.offer( //! &Occurrence::new("snooze-expiry", "task-7", "Task resurfaced", "Write the thing"), //! &Unset, //! ); //! # } //! ``` use crate::{Deliver, Occurrence}; use tauri::{AppHandle, Runtime}; use tauri_plugin_notification::NotificationExt; /// Delivers through `tauri-plugin-notification`. /// /// Holds an [`AppHandle`], which is what the plugin extension hangs off. One /// per app, owned by the [`Outbox`](crate::Outbox) that decides whether /// anything reaches it. /// /// Generic over the runtime, like everything else tauri hands out: this crate /// takes `tauri` with default features off, so there is no `Wry` to default to /// and an app names its own. #[derive(Debug, Clone)] pub struct Notifier { app: AppHandle, refused: usize, } impl Notifier { /// A notifier over this app. #[must_use] pub const fn new(app: AppHandle) -> Self { Self { app, refused: 0 } } /// How many occurrences the platform refused to show. /// /// [`Deliver`] cannot fail in a way a caller could act on -- an outbox that /// retried would be a queue nobody asked for, and the occurrence is already /// noted as sent -- but a host that has failed every time has a permission /// problem rather than a quiet user, and those look identical without this. /// Read it where the watcher logs. #[must_use] pub const fn refused(&self) -> usize { self.refused } /// The app this delivers through. #[must_use] pub const fn app(&self) -> &AppHandle { &self.app } } impl Deliver for Notifier { fn deliver(&mut self, note: &Occurrence) { if self .app .notification() .builder() .title(¬e.title) .body(¬e.body) .show() .is_err() { self.refused += 1; } } }