mirror of
https://github.com/tauri-apps/plugins-workspace.git
synced 2026-04-29 12:06:01 +02:00
feat(notification): implement Android and iOS APIs (#340)
This commit is contained in:
committed by
GitHub
parent
1397172e95
commit
be1c775b8d
@@ -2,30 +2,21 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
use serde::Deserialize;
|
||||
use tauri::{command, AppHandle, Runtime, State};
|
||||
|
||||
use crate::{Notification, PermissionState, Result};
|
||||
|
||||
/// The options for the notification API.
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct NotificationOptions {
|
||||
/// The notification title.
|
||||
pub title: String,
|
||||
/// The notification body.
|
||||
pub body: Option<String>,
|
||||
/// The notification icon.
|
||||
pub icon: Option<String>,
|
||||
}
|
||||
use crate::{Notification, NotificationData, PermissionState, Result};
|
||||
|
||||
#[command]
|
||||
pub(crate) async fn is_permission_granted<R: Runtime>(
|
||||
_app: AppHandle<R>,
|
||||
notification: State<'_, Notification<R>>,
|
||||
) -> Result<bool> {
|
||||
notification
|
||||
.permission_state()
|
||||
.map(|s| s == PermissionState::Granted)
|
||||
) -> Result<Option<bool>> {
|
||||
let state = notification.permission_state()?;
|
||||
match state {
|
||||
PermissionState::Granted => Ok(Some(true)),
|
||||
PermissionState::Denied => Ok(Some(false)),
|
||||
PermissionState::Unknown => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
#[command]
|
||||
@@ -40,15 +31,9 @@ pub(crate) async fn request_permission<R: Runtime>(
|
||||
pub(crate) async fn notify<R: Runtime>(
|
||||
_app: AppHandle<R>,
|
||||
notification: State<'_, Notification<R>>,
|
||||
options: NotificationOptions,
|
||||
options: NotificationData,
|
||||
) -> Result<()> {
|
||||
let mut builder = notification.builder().title(options.title);
|
||||
if let Some(body) = options.body {
|
||||
builder = builder.body(body);
|
||||
}
|
||||
if let Some(icon) = options.icon {
|
||||
builder = builder.icon(icon);
|
||||
}
|
||||
|
||||
let mut builder = notification.builder();
|
||||
builder.data = options;
|
||||
builder.show()
|
||||
}
|
||||
|
||||
+125
-12
@@ -30,16 +30,6 @@ use desktop::Notification;
|
||||
#[cfg(mobile)]
|
||||
use mobile::Notification;
|
||||
|
||||
#[derive(Debug, Default, Serialize)]
|
||||
struct NotificationData {
|
||||
/// The notification title.
|
||||
title: Option<String>,
|
||||
/// The notification body.
|
||||
body: Option<String>,
|
||||
/// The notification icon.
|
||||
icon: Option<String>,
|
||||
}
|
||||
|
||||
/// The notification builder.
|
||||
#[derive(Debug)]
|
||||
pub struct NotificationBuilder<R: Runtime> {
|
||||
@@ -47,7 +37,7 @@ pub struct NotificationBuilder<R: Runtime> {
|
||||
app: AppHandle<R>,
|
||||
#[cfg(mobile)]
|
||||
handle: PluginHandle<R>,
|
||||
data: NotificationData,
|
||||
pub(crate) data: NotificationData,
|
||||
}
|
||||
|
||||
impl<R: Runtime> NotificationBuilder<R> {
|
||||
@@ -67,6 +57,21 @@ impl<R: Runtime> NotificationBuilder<R> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the notification identifier.
|
||||
pub fn id(mut self, id: i32) -> Self {
|
||||
self.data.id = id;
|
||||
self
|
||||
}
|
||||
|
||||
/// Identifier of the {@link Channel} that deliveres this notification.
|
||||
///
|
||||
/// If the channel does not exist, the notification won't fire.
|
||||
/// Make sure the channel exists with {@link listChannels} and {@link createChannel}.
|
||||
pub fn channel_id(mut self, id: impl Into<String>) -> Self {
|
||||
self.data.channel_id.replace(id.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the notification title.
|
||||
pub fn title(mut self, title: impl Into<String>) -> Self {
|
||||
self.data.title.replace(title.into());
|
||||
@@ -79,11 +84,119 @@ impl<R: Runtime> NotificationBuilder<R> {
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the notification icon.
|
||||
/// 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<String>) -> 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<String>) -> 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<String>) -> Self {
|
||||
self.data.action_type_id.replace(action_type_id.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Identifier used to group multiple notifications.
|
||||
///
|
||||
/// https://developer.apple.com/documentation/usernotifications/unmutablenotificationcontent/1649872-threadidentifier
|
||||
pub fn group(mut self, group: impl Into<String>) -> 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. Only available on mobile.
|
||||
pub fn sound(mut self, sound: impl Into<String>) -> 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<String>) -> 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<String>) -> 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<String>) -> Self {
|
||||
self.data.large_icon.replace(large_icon.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Icon color on Android.
|
||||
pub fn icon_color(mut self, icon_color: impl Into<String>) -> 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<String>, 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
|
||||
}
|
||||
}
|
||||
|
||||
/// Extensions to [`tauri::App`], [`tauri::AppHandle`] and [`tauri::Window`] to access the notification APIs.
|
||||
|
||||
@@ -10,6 +10,8 @@ use tauri::{
|
||||
|
||||
use crate::models::*;
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[cfg(target_os = "android")]
|
||||
const PLUGIN_IDENTIFIER: &str = "app.tauri.notification";
|
||||
|
||||
@@ -31,7 +33,8 @@ pub fn init<R: Runtime, C: DeserializeOwned>(
|
||||
impl<R: Runtime> crate::NotificationBuilder<R> {
|
||||
pub fn show(self) -> crate::Result<()> {
|
||||
self.handle
|
||||
.run_mobile_plugin("notify", self.data)
|
||||
.run_mobile_plugin::<ShowResponse>("show", self.data)
|
||||
.map(|_| ())
|
||||
.map_err(Into::into)
|
||||
}
|
||||
}
|
||||
@@ -46,17 +49,121 @@ impl<R: Runtime> Notification<R> {
|
||||
|
||||
pub fn request_permission(&self) -> crate::Result<PermissionState> {
|
||||
self.0
|
||||
.run_mobile_plugin::<PermissionResponse>("requestPermission", ())
|
||||
.run_mobile_plugin::<PermissionResponse>("requestPermissions", ())
|
||||
.map(|r| r.permission_state)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
pub fn permission_state(&self) -> crate::Result<PermissionState> {
|
||||
self.0
|
||||
.run_mobile_plugin::<PermissionResponse>("permissionState", ())
|
||||
.run_mobile_plugin::<PermissionResponse>("checkPermissions", ())
|
||||
.map(|r| r.permission_state)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
pub fn register_action_types(&self, types: Vec<ActionType>) -> crate::Result<()> {
|
||||
let mut args = HashMap::new();
|
||||
args.insert("types", types);
|
||||
self.0
|
||||
.run_mobile_plugin("registerActionTypes", args)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
pub fn remove_active(&self, notifications: Vec<i32>) -> crate::Result<()> {
|
||||
let mut args = HashMap::new();
|
||||
args.insert(
|
||||
"notifications",
|
||||
notifications
|
||||
.into_iter()
|
||||
.map(|id| {
|
||||
let mut notification = HashMap::new();
|
||||
notification.insert("id", id);
|
||||
notification
|
||||
})
|
||||
.collect::<Vec<HashMap<&str, i32>>>(),
|
||||
);
|
||||
self.0
|
||||
.run_mobile_plugin("removeActive", args)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
pub fn active(&self) -> crate::Result<Vec<ActiveNotification>> {
|
||||
self.0
|
||||
.run_mobile_plugin::<ActiveResponse>("getActive", ())
|
||||
.map(|r| r.notifications)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
pub fn remove_all_active(&self) -> crate::Result<()> {
|
||||
self.0
|
||||
.run_mobile_plugin("removeActive", ())
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
pub fn pending(&self) -> crate::Result<Vec<PendingNotification>> {
|
||||
self.0
|
||||
.run_mobile_plugin::<PendingResponse>("getPending", ())
|
||||
.map(|r| r.notifications)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Cancel pending notifications.
|
||||
pub fn cancel(&self, notifications: Vec<i32>) -> crate::Result<()> {
|
||||
let mut args = HashMap::new();
|
||||
args.insert("notifications", notifications);
|
||||
self.0.run_mobile_plugin("cancel", args).map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Cancel all pending notifications.
|
||||
pub fn cancel_all(&self) -> crate::Result<()> {
|
||||
self.0.run_mobile_plugin("cancel", ()).map_err(Into::into)
|
||||
}
|
||||
|
||||
#[cfg(target_os = "android")]
|
||||
pub fn create_channel(&self, channel: Channel) -> crate::Result<()> {
|
||||
self.0
|
||||
.run_mobile_plugin("createChannel", channel)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
#[cfg(target_os = "android")]
|
||||
pub fn delete_channel(&self, id: impl Into<String>) -> crate::Result<()> {
|
||||
let mut args = HashMap::new();
|
||||
args.insert("id", id.into());
|
||||
self.0
|
||||
.run_mobile_plugin("deleteChannel", args)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
#[cfg(target_os = "android")]
|
||||
pub fn list_channels(&self) -> crate::Result<Vec<Channel>> {
|
||||
self.0
|
||||
.run_mobile_plugin::<ListChannelsResult>("listChannels", ())
|
||||
.map(|r| r.channels)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "android")]
|
||||
#[derive(Deserialize)]
|
||||
struct ListChannelsResult {
|
||||
channels: Vec<Channel>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct PendingResponse {
|
||||
notifications: Vec<PendingNotification>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct ActiveResponse {
|
||||
notifications: Vec<ActiveNotification>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct ShowResponse {
|
||||
#[allow(dead_code)]
|
||||
id: i32,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
|
||||
@@ -2,10 +2,201 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
use std::fmt::Display;
|
||||
use std::{collections::HashMap, fmt::Display};
|
||||
|
||||
use serde::{de::Error as DeError, Deserialize, Deserializer, Serialize, Serializer};
|
||||
|
||||
use url::Url;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Attachment {
|
||||
id: String,
|
||||
url: Url,
|
||||
}
|
||||
|
||||
impl Attachment {
|
||||
pub fn new(id: impl Into<String>, url: Url) -> Self {
|
||||
Self { id: id.into(), url }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ScheduleInterval {
|
||||
pub year: Option<u8>,
|
||||
pub month: Option<u8>,
|
||||
pub day: Option<u8>,
|
||||
pub weekday: Option<u8>,
|
||||
pub hour: Option<u8>,
|
||||
pub minute: Option<u8>,
|
||||
pub second: Option<u8>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ScheduleEvery {
|
||||
Year,
|
||||
Month,
|
||||
TwoWeeks,
|
||||
Week,
|
||||
Day,
|
||||
Hour,
|
||||
Minute,
|
||||
Second,
|
||||
}
|
||||
|
||||
impl Display for ScheduleEvery {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{}",
|
||||
match self {
|
||||
Self::Year => "Year",
|
||||
Self::Month => "Month",
|
||||
Self::TwoWeeks => "TwoWeeks",
|
||||
Self::Week => "Week",
|
||||
Self::Day => "Day",
|
||||
Self::Hour => "Hour",
|
||||
Self::Minute => "Minute",
|
||||
Self::Second => "Second",
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for ScheduleEvery {
|
||||
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
serializer.serialize_str(self.to_string().as_ref())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for ScheduleEvery {
|
||||
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let s = String::deserialize(deserializer)?;
|
||||
match s.to_lowercase().as_str() {
|
||||
"year" => Ok(Self::Year),
|
||||
"month" => Ok(Self::Month),
|
||||
"twoweeks" => Ok(Self::TwoWeeks),
|
||||
"week" => Ok(Self::Week),
|
||||
"day" => Ok(Self::Day),
|
||||
"hour" => Ok(Self::Hour),
|
||||
"minute" => Ok(Self::Minute),
|
||||
"second" => Ok(Self::Second),
|
||||
_ => Err(DeError::custom(format!("unknown every kind '{s}'"))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(tag = "kind", content = "data")]
|
||||
pub enum Schedule {
|
||||
At {
|
||||
#[serde(
|
||||
serialize_with = "iso8601::serialize",
|
||||
deserialize_with = "time::serde::iso8601::deserialize"
|
||||
)]
|
||||
date: time::OffsetDateTime,
|
||||
#[serde(default)]
|
||||
repeating: bool,
|
||||
},
|
||||
Interval(ScheduleInterval),
|
||||
Every {
|
||||
interval: ScheduleEvery,
|
||||
},
|
||||
}
|
||||
|
||||
// custom ISO-8601 serialization that does not use 6 digits for years.
|
||||
mod iso8601 {
|
||||
use serde::{ser::Error as _, Serialize, Serializer};
|
||||
use time::{
|
||||
format_description::well_known::iso8601::{Config, EncodedConfig},
|
||||
format_description::well_known::Iso8601,
|
||||
OffsetDateTime,
|
||||
};
|
||||
|
||||
const SERDE_CONFIG: EncodedConfig = Config::DEFAULT.encode();
|
||||
|
||||
pub fn serialize<S: Serializer>(
|
||||
datetime: &OffsetDateTime,
|
||||
serializer: S,
|
||||
) -> Result<S::Ok, S::Error> {
|
||||
datetime
|
||||
.format(&Iso8601::<SERDE_CONFIG>)
|
||||
.map_err(S::Error::custom)?
|
||||
.serialize(serializer)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct NotificationData {
|
||||
#[serde(default = "default_id")]
|
||||
pub(crate) id: i32,
|
||||
pub(crate) channel_id: Option<String>,
|
||||
pub(crate) title: Option<String>,
|
||||
pub(crate) body: Option<String>,
|
||||
pub(crate) schedule: Option<Schedule>,
|
||||
pub(crate) large_body: Option<String>,
|
||||
pub(crate) summary: Option<String>,
|
||||
pub(crate) action_type_id: Option<String>,
|
||||
pub(crate) group: Option<String>,
|
||||
#[serde(default)]
|
||||
pub(crate) group_summary: bool,
|
||||
pub(crate) sound: Option<String>,
|
||||
#[serde(default)]
|
||||
pub(crate) inbox_lines: Vec<String>,
|
||||
pub(crate) icon: Option<String>,
|
||||
pub(crate) large_icon: Option<String>,
|
||||
pub(crate) icon_color: Option<String>,
|
||||
#[serde(default)]
|
||||
pub(crate) attachments: Vec<Attachment>,
|
||||
#[serde(default)]
|
||||
pub(crate) extra: HashMap<String, serde_json::Value>,
|
||||
#[serde(default)]
|
||||
pub(crate) ongoing: bool,
|
||||
#[serde(default)]
|
||||
pub(crate) auto_cancel: bool,
|
||||
#[serde(default)]
|
||||
pub(crate) silent: bool,
|
||||
}
|
||||
|
||||
fn default_id() -> i32 {
|
||||
rand::random()
|
||||
}
|
||||
|
||||
impl Default for NotificationData {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
id: default_id(),
|
||||
channel_id: None,
|
||||
title: None,
|
||||
body: None,
|
||||
schedule: None,
|
||||
large_body: None,
|
||||
summary: None,
|
||||
action_type_id: None,
|
||||
group: None,
|
||||
group_summary: false,
|
||||
sound: None,
|
||||
inbox_lines: Vec::new(),
|
||||
icon: None,
|
||||
large_icon: None,
|
||||
icon_color: None,
|
||||
attachments: Vec::new(),
|
||||
extra: Default::default(),
|
||||
ongoing: false,
|
||||
auto_cancel: false,
|
||||
silent: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Permission state.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum PermissionState {
|
||||
@@ -13,6 +204,8 @@ pub enum PermissionState {
|
||||
Granted,
|
||||
/// Permission access has been denied.
|
||||
Denied,
|
||||
/// Unknown state. Must request permission.
|
||||
Unknown,
|
||||
}
|
||||
|
||||
impl Display for PermissionState {
|
||||
@@ -20,6 +213,7 @@ impl Display for PermissionState {
|
||||
match self {
|
||||
Self::Granted => write!(f, "granted"),
|
||||
Self::Denied => write!(f, "denied"),
|
||||
Self::Unknown => write!(f, "Unknown"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -42,7 +236,274 @@ impl<'de> Deserialize<'de> for PermissionState {
|
||||
match s.to_lowercase().as_str() {
|
||||
"granted" => Ok(Self::Granted),
|
||||
"denied" => Ok(Self::Denied),
|
||||
"default" => Ok(Self::Unknown),
|
||||
_ => Err(DeError::custom(format!("unknown permission state '{s}'"))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct PendingNotification {
|
||||
id: i32,
|
||||
title: Option<String>,
|
||||
body: Option<String>,
|
||||
schedule: Schedule,
|
||||
}
|
||||
|
||||
impl PendingNotification {
|
||||
pub fn id(&self) -> i32 {
|
||||
self.id
|
||||
}
|
||||
|
||||
pub fn title(&self) -> Option<&str> {
|
||||
self.title.as_deref()
|
||||
}
|
||||
|
||||
pub fn body(&self) -> Option<&str> {
|
||||
self.body.as_deref()
|
||||
}
|
||||
|
||||
pub fn schedule(&self) -> &Schedule {
|
||||
&self.schedule
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ActiveNotification {
|
||||
id: i32,
|
||||
tag: Option<String>,
|
||||
title: Option<String>,
|
||||
body: Option<String>,
|
||||
group: Option<String>,
|
||||
#[serde(default)]
|
||||
group_summary: bool,
|
||||
#[serde(default)]
|
||||
data: HashMap<String, String>,
|
||||
#[serde(default)]
|
||||
extra: HashMap<String, serde_json::Value>,
|
||||
#[serde(default)]
|
||||
attachments: Vec<Attachment>,
|
||||
action_type_id: Option<String>,
|
||||
schedule: Option<Schedule>,
|
||||
sound: Option<String>,
|
||||
}
|
||||
|
||||
impl ActiveNotification {
|
||||
pub fn id(&self) -> i32 {
|
||||
self.id
|
||||
}
|
||||
|
||||
pub fn tag(&self) -> Option<&str> {
|
||||
self.tag.as_deref()
|
||||
}
|
||||
|
||||
pub fn title(&self) -> Option<&str> {
|
||||
self.title.as_deref()
|
||||
}
|
||||
|
||||
pub fn body(&self) -> Option<&str> {
|
||||
self.body.as_deref()
|
||||
}
|
||||
|
||||
pub fn group(&self) -> Option<&str> {
|
||||
self.group.as_deref()
|
||||
}
|
||||
|
||||
pub fn group_summary(&self) -> bool {
|
||||
self.group_summary
|
||||
}
|
||||
|
||||
pub fn data(&self) -> &HashMap<String, String> {
|
||||
&self.data
|
||||
}
|
||||
|
||||
pub fn extra(&self) -> &HashMap<String, serde_json::Value> {
|
||||
&self.extra
|
||||
}
|
||||
|
||||
pub fn attachments(&self) -> &[Attachment] {
|
||||
&self.attachments
|
||||
}
|
||||
|
||||
pub fn action_type_id(&self) -> Option<&str> {
|
||||
self.action_type_id.as_deref()
|
||||
}
|
||||
|
||||
pub fn schedule(&self) -> Option<&Schedule> {
|
||||
self.schedule.as_ref()
|
||||
}
|
||||
|
||||
pub fn sound(&self) -> Option<&str> {
|
||||
self.sound.as_deref()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ActionType {
|
||||
id: String,
|
||||
actions: Vec<Action>,
|
||||
hidden_previews_body_placeholder: Option<String>,
|
||||
custom_dismiss_action: bool,
|
||||
allow_in_car_play: bool,
|
||||
hidden_previews_show_title: bool,
|
||||
hidden_previews_show_subtitle: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Action {
|
||||
id: String,
|
||||
title: String,
|
||||
requires_authentication: bool,
|
||||
foreground: bool,
|
||||
destructive: bool,
|
||||
input: bool,
|
||||
input_button_title: Option<String>,
|
||||
input_placeholder: Option<String>,
|
||||
}
|
||||
|
||||
#[cfg(target_os = "android")]
|
||||
pub use android::*;
|
||||
|
||||
#[cfg(target_os = "android")]
|
||||
mod android {
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_repr::{Deserialize_repr, Serialize_repr};
|
||||
|
||||
#[derive(Debug, Clone, Copy, Serialize_repr, Deserialize_repr)]
|
||||
#[repr(u8)]
|
||||
pub enum Importance {
|
||||
None = 0,
|
||||
Min = 1,
|
||||
Low = 2,
|
||||
Default = 3,
|
||||
High = 4,
|
||||
}
|
||||
|
||||
impl Default for Importance {
|
||||
fn default() -> Self {
|
||||
Self::Default
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Serialize_repr, Deserialize_repr)]
|
||||
#[repr(i8)]
|
||||
pub enum Visibility {
|
||||
Secret = -1,
|
||||
Private = 0,
|
||||
Public = 1,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Channel {
|
||||
id: String,
|
||||
name: String,
|
||||
description: Option<String>,
|
||||
sound: Option<String>,
|
||||
lights: bool,
|
||||
light_color: Option<String>,
|
||||
vibration: bool,
|
||||
importance: Importance,
|
||||
visibility: Option<Visibility>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ChannelBuilder(Channel);
|
||||
|
||||
impl Channel {
|
||||
pub fn builder(id: impl Into<String>, name: impl Into<String>) -> ChannelBuilder {
|
||||
ChannelBuilder(Self {
|
||||
id: id.into(),
|
||||
name: name.into(),
|
||||
description: None,
|
||||
sound: None,
|
||||
lights: false,
|
||||
light_color: None,
|
||||
vibration: false,
|
||||
importance: Default::default(),
|
||||
visibility: None,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn id(&self) -> &str {
|
||||
&self.id
|
||||
}
|
||||
|
||||
pub fn name(&self) -> &str {
|
||||
&self.name
|
||||
}
|
||||
|
||||
pub fn description(&self) -> Option<&str> {
|
||||
self.description.as_deref()
|
||||
}
|
||||
|
||||
pub fn sound(&self) -> Option<&str> {
|
||||
self.sound.as_deref()
|
||||
}
|
||||
|
||||
pub fn lights(&self) -> bool {
|
||||
self.lights
|
||||
}
|
||||
|
||||
pub fn light_color(&self) -> Option<&str> {
|
||||
self.light_color.as_deref()
|
||||
}
|
||||
|
||||
pub fn vibration(&self) -> bool {
|
||||
self.vibration
|
||||
}
|
||||
|
||||
pub fn importance(&self) -> Importance {
|
||||
self.importance
|
||||
}
|
||||
|
||||
pub fn visibility(&self) -> Option<Visibility> {
|
||||
self.visibility
|
||||
}
|
||||
}
|
||||
|
||||
impl ChannelBuilder {
|
||||
pub fn description(mut self, description: impl Into<String>) -> Self {
|
||||
self.0.description.replace(description.into());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn sound(mut self, sound: impl Into<String>) -> Self {
|
||||
self.0.sound.replace(sound.into());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn lights(mut self, lights: bool) -> Self {
|
||||
self.0.lights = lights;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn light_color(mut self, color: impl Into<String>) -> Self {
|
||||
self.0.light_color.replace(color.into());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn vibration(mut self, vibration: bool) -> Self {
|
||||
self.0.vibration = vibration;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn importance(mut self, importance: Importance) -> Self {
|
||||
self.0.importance = importance;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn visibility(mut self, visibility: Visibility) -> Self {
|
||||
self.0.visibility.replace(visibility);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn build(self) -> Channel {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user