use crate::models::PluginConfig;
use tauri::{AppHandle, Runtime};
use tauri_plugin_notification::NotificationExt;
#[derive(Clone)]
pub struct Notifier<R: Runtime> {
pub(crate) app: AppHandle<R>,
}
impl<R: Runtime> Notifier<R> {
pub fn show(&self, title: &str, body: &str) {
if let Err(e) = self
.app
.notification()
.builder()
.title(title)
.body(body)
.show()
{
log::warn!("background-service: notification failed: {e}");
}
}
pub fn show_with_id(&self, id: &str, title: &str, body: &str) {
if let Err(e) = self
.app
.notification()
.builder()
.id(stable_notification_id(id))
.title(title)
.body(body)
.show()
{
log::warn!("background-service: notification {id} failed: {e}");
}
}
}
pub(crate) fn stable_notification_id(id: &str) -> i32 {
let mut hash: u32 = 0x811c_9dc5;
for byte in id.as_bytes() {
hash ^= u32::from(*byte);
hash = hash.wrapping_mul(0x0100_0193);
}
hash as i32
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct NotifierPolicy {
pub on_timeout: bool,
pub on_recovery: bool,
}
impl NotifierPolicy {
pub fn derive(config: &PluginConfig, is_android: bool) -> Self {
let native_owns_timeout = is_android && config.android_on_timeout == "notifyUser";
Self {
on_timeout: config.notify_on_timeout && !native_owns_timeout,
on_recovery: config.notify_on_recovery && !is_android,
}
}
}
pub trait NotifySink: Send + Sync {
fn notify(&self, id: &str, title: &str, body: &str);
}
impl<R: Runtime> NotifySink for Notifier<R> {
fn notify(&self, id: &str, title: &str, body: &str) {
self.show_with_id(id, title, body);
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::models::PluginConfig;
#[allow(dead_code)]
fn notifier_clone_compiles<R: Runtime + Clone>(app: AppHandle<R>) {
let n = Notifier { app };
let _cloned = n.clone();
}
#[allow(dead_code)]
fn notifier_show_with_id_compiles<R: Runtime>(n: &Notifier<R>) {
n.show_with_id("bg-timeout", "title", "body");
}
#[allow(dead_code)]
fn notifier_is_notify_sink<R: Runtime>(n: Notifier<R>) -> std::sync::Arc<dyn NotifySink> {
std::sync::Arc::new(n)
}
#[test]
fn stable_notification_id_is_deterministic() {
assert_eq!(
stable_notification_id("bg-timeout"),
stable_notification_id("bg-timeout")
);
assert_ne!(
stable_notification_id("bg-timeout"),
stable_notification_id("bg-recovery")
);
}
fn config(
notify_on_timeout: bool,
notify_on_recovery: bool,
android_on_timeout: &str,
) -> PluginConfig {
PluginConfig {
notify_on_timeout,
notify_on_recovery,
android_on_timeout: android_on_timeout.into(),
..Default::default()
}
}
#[test]
fn derive_desktop_honors_configured_keys() {
let policy = NotifierPolicy::derive(&config(true, true, "notifyUser"), false);
assert_eq!(
policy,
NotifierPolicy {
on_timeout: true,
on_recovery: true
}
);
}
#[test]
fn derive_desktop_defaults_off() {
let policy = NotifierPolicy::derive(&config(false, false, "notifyUser"), false);
assert_eq!(
policy,
NotifierPolicy {
on_timeout: false,
on_recovery: false
}
);
}
#[test]
fn derive_android_notify_user_suppresses_timeout() {
let policy = NotifierPolicy::derive(&config(true, true, "notifyUser"), true);
assert_eq!(
policy,
NotifierPolicy {
on_timeout: false,
on_recovery: false
}
);
}
#[test]
fn derive_android_stop_keeps_timeout() {
let policy = NotifierPolicy::derive(&config(true, true, "stop"), true);
assert_eq!(
policy,
NotifierPolicy {
on_timeout: true,
on_recovery: false
}
);
}
#[test]
fn derive_android_schedule_recovery_keeps_timeout() {
let policy = NotifierPolicy::derive(&config(true, true, "scheduleRecovery"), true);
assert_eq!(
policy,
NotifierPolicy {
on_timeout: true,
on_recovery: false
}
);
}
#[test]
fn derive_android_always_suppresses_recovery() {
let policy = NotifierPolicy::derive(&config(false, true, "stop"), true);
assert_eq!(
policy,
NotifierPolicy {
on_timeout: false,
on_recovery: false
}
);
}
#[test]
fn derive_default_policy_is_all_off() {
assert_eq!(
NotifierPolicy::default(),
NotifierPolicy {
on_timeout: false,
on_recovery: false
}
);
}
}