// Copyright 2019-2023 Tauri Programme within The Commons Conservancy // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT //! Send message notifications (brief auto-expiring OS window element) to your user. Can also be used with the Notification Web API. //! //! ## Cargo features //! //! - **windows7-compat**: Deprecated and does nothing, since Tauri no longer supports Windows 7. #![doc( html_logo_url = "https://github.com/tauri-apps/tauri/raw/dev/app-icon.png", html_favicon_url = "https://github.com/tauri-apps/tauri/raw/dev/app-icon.png" )] use serde::Serialize; #[cfg(all(desktop, unix, not(target_os = "macos")))] use std::fmt; #[cfg(desktop)] use tauri::AppHandle; #[cfg(mobile)] use tauri::plugin::PluginHandle; use tauri::{ Manager, Runtime, plugin::{Builder, TauriPlugin}, }; pub use models::*; pub use tauri::plugin::PermissionState; #[cfg(desktop)] mod desktop; #[cfg(mobile)] mod mobile; mod commands; mod error; mod models; pub use error::{Error, Result}; #[cfg(desktop)] pub use desktop::Notification; #[cfg(mobile)] pub use mobile::Notification; /// The notification builder. #[derive(Debug)] pub struct NotificationBuilder { #[cfg(desktop)] app: AppHandle, #[cfg(mobile)] handle: PluginHandle, pub(crate) data: NotificationData, #[cfg(all(desktop, unix, not(target_os = "macos")))] on_close: Option, } #[cfg(all(desktop, unix, not(target_os = "macos")))] pub(crate) struct OnClose(Box); #[cfg(all(desktop, unix, not(target_os = "macos")))] impl fmt::Debug for OnClose { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str("OnClose") } } impl NotificationBuilder { #[cfg(desktop)] fn new(app: AppHandle) -> Self { Self { app, data: Default::default(), #[cfg(all(desktop, unix, not(target_os = "macos")))] on_close: None, } } #[cfg(mobile)] fn new(handle: PluginHandle) -> Self { Self { handle, data: Default::default(), } } /// Sets the notification identifier. pub fn id(mut self, id: i32) -> Self { self.data.id = id; self } /// Sets the identifier of the notification channel that delivers this notification. /// /// If the channel does not exist, the notification won't fire. /// Make sure the channel exists with `Notification::list_channels` and /// `Notification::create_channel`. /// /// Only used on Android. pub fn channel_id(mut self, id: impl Into) -> Self { self.data.channel_id.replace(id.into()); self } /// Sets the notification title. pub fn title(mut self, title: impl Into) -> Self { self.data.title.replace(title.into()); self } /// Sets the notification body. pub fn body(mut self, body: impl Into) -> Self { self.data.body.replace(body.into()); self } /// Schedule this notification to fire on a later time or a fixed interval. pub fn schedule(mut self, schedule: Schedule) -> Self { self.data.schedule.replace(schedule); self } /// Multiline text. /// Changes the notification style to big text. /// Cannot be used with `inboxLines`. pub fn large_body(mut self, large_body: impl Into) -> Self { self.data.large_body.replace(large_body.into()); self } /// Detail text for the notification with `largeBody`, `inboxLines` or `groupSummary`. pub fn summary(mut self, summary: impl Into) -> Self { self.data.summary.replace(summary.into()); self } /// Defines an action type for this notification. pub fn action_type_id(mut self, action_type_id: impl Into) -> Self { self.data.action_type_id.replace(action_type_id.into()); self } /// Identifier used to group multiple notifications. /// /// pub fn group(mut self, group: impl Into) -> Self { self.data.group.replace(group.into()); self } /// Instructs the system that this notification is the summary of a group on Android. pub fn group_summary(mut self) -> Self { self.data.group_summary = true; self } /// The sound resource name for the notification. pub fn sound(mut self, sound: impl Into) -> Self { self.data.sound.replace(sound.into()); self } /// Append an inbox line to the notification. /// Changes the notification style to inbox. /// Cannot be used with `largeBody`. /// /// Only supports up to 5 lines. pub fn inbox_line(mut self, line: impl Into) -> Self { self.data.inbox_lines.push(line.into()); self } /// Notification icon. /// /// On Android the icon must be placed in the app's `res/drawable` folder. pub fn icon(mut self, icon: impl Into) -> Self { self.data.icon.replace(icon.into()); self } /// Notification large icon (Android). /// /// The icon must be placed in the app's `res/drawable` folder. pub fn large_icon(mut self, large_icon: impl Into) -> Self { self.data.large_icon.replace(large_icon.into()); self } /// Icon color on Android. pub fn icon_color(mut self, icon_color: impl Into) -> Self { self.data.icon_color.replace(icon_color.into()); self } /// Append an attachment to the notification. pub fn attachment(mut self, attachment: Attachment) -> Self { self.data.attachments.push(attachment); self } /// Adds an extra payload to store in the notification. pub fn extra(mut self, key: impl Into, value: impl Serialize) -> Self { self.data .extra .insert(key.into(), serde_json::to_value(value).unwrap()); self } /// If true, the notification cannot be dismissed by the user on Android. /// /// An application service must manage the dismissal of the notification. /// It is typically used to indicate a background task that is pending (e.g. a file download) /// or the user is engaged with (e.g. playing music). pub fn ongoing(mut self) -> Self { self.data.ongoing = true; self } /// Automatically cancel the notification when the user clicks on it. pub fn auto_cancel(mut self) -> Self { self.data.auto_cancel = true; self } /// Changes the notification presentation to be silent on iOS (no badge, no sound, not listed). pub fn silent(mut self) -> Self { self.data.silent = true; self } /// Sets a callback to run when the notification closes, whether the user dismissed it, it /// expired or the app removed it. /// /// The callback runs on a background thread and should return quickly. It does not run when /// the session bus cannot be reached, or when the app exits first. /// /// ## Platform-specific /// /// - **Linux and the BSDs**: only available there. #[cfg(all(desktop, unix, not(target_os = "macos")))] pub fn on_close(mut self, handler: F) -> Self { self.on_close = Some(OnClose(Box::new(handler))); self } } /// Extensions to [`tauri::App`], [`tauri::AppHandle`], [`tauri::WebviewWindow`], [`tauri::Webview`] and [`tauri::Window`] to access the notification APIs. pub trait NotificationExt { /// Returns the notification APIs managed by the plugin. /// /// # Examples /// /// ```no_run /// use tauri_plugin_notification::NotificationExt; /// /// fn notify(app: &tauri::AppHandle) { /// app.notification() /// .builder() /// .title("Tauri") /// .body("Tauri is awesome!") /// .show() /// .unwrap(); /// } /// ``` fn notification(&self) -> &Notification; } impl> crate::NotificationExt for T { fn notification(&self) -> &Notification { self.state::>().inner() } } /// Initializes the plugin. pub fn init() -> TauriPlugin { let builder = Builder::new("notification"); // On mobile the other commands are implemented by the Kotlin and Swift plugins. #[cfg(mobile)] let builder = builder.invoke_handler(tauri::generate_handler![ commands::notify, commands::request_permission, commands::is_permission_granted ]); #[cfg(desktop)] let builder = builder .invoke_handler(tauri::generate_handler![ commands::notify, commands::request_permission, commands::is_permission_granted, commands::register_action_types, commands::remove_active, commands::register_listener, commands::remove_listener ]) // the listeners of a page are gone once it navigates away or its window is destroyed .on_page_load(|webview, payload| { if payload.event() == tauri::webview::PageLoadEvent::Started && let Some(notification) = webview.try_state::>() { notification.remove_webview_listeners(webview.label()); } }) .on_event(|app, event| { if let tauri::RunEvent::WindowEvent { label, event: tauri::WindowEvent::Destroyed, .. } = event && let Some(notification) = app.try_state::>() { notification.remove_window_listeners(label); } }); builder .js_init_script(include_str!("init-iife.js").replace( "__TEMPLATE_windows__", if cfg!(windows) { "true" } else { "false" }, )) .setup(|app, api| { #[cfg(mobile)] let notification = mobile::init(app, api)?; #[cfg(desktop)] let notification = desktop::init(app, api)?; app.manage(notification); Ok(()) }) .build() }