diff --git a/.changes/fix-scheduled-notification.md b/.changes/fix-scheduled-notification.md
new file mode 100644
index 00000000..0fb5afde
--- /dev/null
+++ b/.changes/fix-scheduled-notification.md
@@ -0,0 +1,6 @@
+---
+"notification": patch
+"notification-js": patch
+---
+
+Fixes deserialization and implementation bugs with scheduled notifications on Android.
diff --git a/examples/api/vite.config.js b/examples/api/vite.config.js
index df8c64f2..e86960b4 100644
--- a/examples/api/vite.config.js
+++ b/examples/api/vite.config.js
@@ -11,8 +11,8 @@ import process from "process";
// https://vitejs.dev/config/
export default defineConfig(async () => {
const host =
- process.env.TAURI_PLATFORM === "android" ||
- process.env.TAURI_PLATFORM === "ios"
+ process.env.TAURI_ENV_PLATFORM === "android" ||
+ process.env.TAURI_ENV_PLATFORM === "ios"
? await internalIpV4()
: "localhost";
return {
diff --git a/plugins/notification/android/src/main/AndroidManifest.xml b/plugins/notification/android/src/main/AndroidManifest.xml
index 608de0c7..c49808ee 100644
--- a/plugins/notification/android/src/main/AndroidManifest.xml
+++ b/plugins/notification/android/src/main/AndroidManifest.xml
@@ -14,6 +14,7 @@
-
-
-
+
+
+
+
\ No newline at end of file
diff --git a/plugins/notification/android/src/main/java/Notification.kt b/plugins/notification/android/src/main/java/Notification.kt
index 60e40675..bf10f3dc 100644
--- a/plugins/notification/android/src/main/java/Notification.kt
+++ b/plugins/notification/android/src/main/java/Notification.kt
@@ -80,8 +80,6 @@ class Notification {
return null
}
- val isScheduled = schedule != null
-
companion object {
fun buildNotificationPendingList(notifications: List): List {
val pendingNotifications = mutableListOf()
diff --git a/plugins/notification/android/src/main/java/NotificationPlugin.kt b/plugins/notification/android/src/main/java/NotificationPlugin.kt
index 4b833dbc..3ead3152 100644
--- a/plugins/notification/android/src/main/java/NotificationPlugin.kt
+++ b/plugins/notification/android/src/main/java/NotificationPlugin.kt
@@ -268,7 +268,7 @@ class NotificationPlugin(private val activity: Activity): Plugin(activity) {
@PermissionCallback
private fun permissionsCallback(invoke: Invoke) {
val permissionsResultJSON = JSObject()
- permissionsResultJSON.put("display", getPermissionState())
+ permissionsResultJSON.put("permissionState", getPermissionState())
invoke.resolve(permissionsResultJSON)
}
diff --git a/plugins/notification/android/src/main/java/NotificationSchedule.kt b/plugins/notification/android/src/main/java/NotificationSchedule.kt
index 84bad6fa..459461b8 100644
--- a/plugins/notification/android/src/main/java/NotificationSchedule.kt
+++ b/plugins/notification/android/src/main/java/NotificationSchedule.kt
@@ -5,9 +5,9 @@
package app.tauri.notification
import android.annotation.SuppressLint
-import android.content.ClipData.Item
import android.text.format.DateUtils
import com.fasterxml.jackson.annotation.JsonFormat
+import com.fasterxml.jackson.annotation.JsonProperty
import com.fasterxml.jackson.core.JsonGenerator
import com.fasterxml.jackson.core.JsonParser
import com.fasterxml.jackson.core.JsonProcessingException
@@ -17,6 +17,7 @@ import com.fasterxml.jackson.databind.JsonNode
import com.fasterxml.jackson.databind.SerializerProvider
import com.fasterxml.jackson.databind.annotation.JsonDeserialize
import com.fasterxml.jackson.databind.annotation.JsonSerialize
+import com.fasterxml.jackson.databind.deser.std.StdDeserializer
import com.fasterxml.jackson.databind.ser.std.StdSerializer
import java.io.IOException
import java.text.SimpleDateFormat
@@ -24,11 +25,25 @@ import java.util.Calendar
import java.util.Date
import java.util.TimeZone
-
const val JS_DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"
enum class NotificationInterval {
- Year, Month, TwoWeeks, Week, Day, Hour, Minute, Second
+ @JsonProperty("year")
+ Year,
+ @JsonProperty("month")
+ Month,
+ @JsonProperty("twoWeeks")
+ TwoWeeks,
+ @JsonProperty("week")
+ Week,
+ @JsonProperty("day")
+ Day,
+ @JsonProperty("hour")
+ Hour,
+ @JsonProperty("minute")
+ Minute,
+ @JsonProperty("second")
+ Second
}
fun getIntervalTime(interval: NotificationInterval, count: Int): Long {
@@ -50,9 +65,24 @@ fun getIntervalTime(interval: NotificationInterval, count: Int): Long {
@JsonSerialize(using = NotificationScheduleSerializer::class)
sealed class NotificationSchedule {
// At specific moment of time (with repeating option)
- class At(@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = JS_DATE_FORMAT) var date: Date, val repeating: Boolean = false, val allowWhileIdle: Boolean = false): NotificationSchedule()
- class Interval(val interval: DateMatch, val allowWhileIdle: Boolean = false): NotificationSchedule()
- class Every(val interval: NotificationInterval, val count: Int = 0, val allowWhileIdle: Boolean = false): NotificationSchedule()
+ @JsonDeserialize
+ class At: NotificationSchedule() {
+ @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = JS_DATE_FORMAT)
+ lateinit var date: Date
+ var repeating: Boolean = false
+ var allowWhileIdle: Boolean = false
+ }
+ @JsonDeserialize
+ class Interval: NotificationSchedule() {
+ lateinit var interval: DateMatch
+ var allowWhileIdle: Boolean = false
+ }
+ @JsonDeserialize
+ class Every: NotificationSchedule() {
+ lateinit var interval: NotificationInterval
+ var count: Int = 0
+ var allowWhileIdle: Boolean = false
+ }
fun isRemovable(): Boolean {
return when (this) {
diff --git a/plugins/notification/android/src/main/java/NotificationStorage.kt b/plugins/notification/android/src/main/java/NotificationStorage.kt
index 273b37c8..bceb985d 100644
--- a/plugins/notification/android/src/main/java/NotificationStorage.kt
+++ b/plugins/notification/android/src/main/java/NotificationStorage.kt
@@ -20,7 +20,7 @@ class NotificationStorage(private val context: Context, private val jsonMapper:
val storage = getStorage(NOTIFICATION_STORE_ID)
val editor = storage.edit()
for (request in localNotifications) {
- if (request.isScheduled) {
+ if (request.schedule != null) {
val key: String = request.id.toString()
editor.putString(key, request.sourceJson.toString())
}
diff --git a/plugins/notification/android/src/main/java/TauriNotificationManager.kt b/plugins/notification/android/src/main/java/TauriNotificationManager.kt
index e653fab7..a8912739 100644
--- a/plugins/notification/android/src/main/java/TauriNotificationManager.kt
+++ b/plugins/notification/android/src/main/java/TauriNotificationManager.kt
@@ -212,7 +212,7 @@ class TauriNotificationManager(
createActionIntents(notification, mBuilder)
// notificationId is a unique int for each notification that you must define
val buildNotification = mBuilder.build()
- if (notification.isScheduled) {
+ if (notification.schedule != null) {
triggerScheduledNotification(buildNotification, notification)
} else {
notificationManager.notify(notification.id, buildNotification)
@@ -473,7 +473,7 @@ class TimedNotificationPublisher : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val notificationManager =
context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
- val notification = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ val notification = if (SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
intent.getParcelableExtra(
NOTIFICATION_KEY,
android.app.Notification::class.java
diff --git a/plugins/notification/guest-js/index.ts b/plugins/notification/guest-js/index.ts
index d121c5d1..b7bac7d7 100644
--- a/plugins/notification/guest-js/index.ts
+++ b/plugins/notification/guest-js/index.ts
@@ -163,49 +163,57 @@ enum ScheduleEvery {
Second = "second",
}
-type ScheduleData =
- | {
- at: {
+class Schedule {
+ at:
+ | {
date: Date;
repeating: boolean;
allowWhileIdle: boolean;
- };
- }
- | {
- interval: {
+ }
+ | undefined;
+ interval:
+ | {
interval: ScheduleInterval;
allowWhileIdle: boolean;
- };
- }
- | {
- every: {
+ }
+ | undefined;
+ every:
+ | {
interval: ScheduleEvery;
count: number;
allowWhileIdle: boolean;
- };
+ }
+ | undefined;
+
+ static at(date: Date, repeating = false, allowWhileIdle = false): Schedule {
+ return {
+ at: { date, repeating, allowWhileIdle },
+ interval: undefined,
+ every: undefined,
};
-
-class Schedule {
- schedule: ScheduleData;
-
- private constructor(schedule: ScheduleData) {
- this.schedule = schedule;
- }
-
- toJSON(): string {
- return JSON.stringify(this.schedule);
- }
-
- static at(date: Date, repeating = false, allowWhileIdle = false) {
- return new Schedule({ at: { date, repeating, allowWhileIdle } });
}
- static interval(interval: ScheduleInterval, allowWhileIdle = false) {
- return new Schedule({ interval: { interval, allowWhileIdle } });
+ static interval(
+ interval: ScheduleInterval,
+ allowWhileIdle = false,
+ ): Schedule {
+ return {
+ at: undefined,
+ interval: { interval, allowWhileIdle },
+ every: undefined,
+ };
}
- static every(kind: ScheduleEvery, count: number, allowWhileIdle = false) {
- return new Schedule({ every: { interval: kind, count, allowWhileIdle } });
+ static every(
+ kind: ScheduleEvery,
+ count: number,
+ allowWhileIdle = false,
+ ): Schedule {
+ return {
+ at: undefined,
+ interval: undefined,
+ every: { interval: kind, count, allowWhileIdle },
+ };
}
}
diff --git a/plugins/notification/src/api-iife.js b/plugins/notification/src/api-iife.js
index 6cc89550..3d8c020a 100644
--- a/plugins/notification/src/api-iife.js
+++ b/plugins/notification/src/api-iife.js
@@ -1 +1 @@
-if("__TAURI__"in window){var __TAURI_PLUGIN_NOTIFICATION__=function(n){"use strict";function e(n,e,i,t){if("a"===i&&!t)throw new TypeError("Private accessor was defined without a getter");if("function"==typeof e?n!==e||!t:!e.has(n))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===i?t:"a"===i?t.call(n):t?t.value:e.get(n)}var i,t,o,r;"function"==typeof SuppressedError&&SuppressedError;class a{constructor(){this.__TAURI_CHANNEL_MARKER__=!0,i.set(this,(()=>{})),this.id=function(n,e=!1){return window.__TAURI_INTERNALS__.transformCallback(n,e)}((n=>{e(this,i,"f").call(this,n)}))}set onmessage(n){!function(n,e,i,t,o){if("m"===t)throw new TypeError("Private method is not writable");if("a"===t&&!o)throw new TypeError("Private accessor was defined without a setter");if("function"==typeof e?n!==e||!o:!e.has(n))throw new TypeError("Cannot write private member to an object whose class did not declare it");"a"===t?o.call(n,i):o?o.value=i:e.set(n,i)}(this,i,n,"f")}get onmessage(){return e(this,i,"f")}toJSON(){return`__CHANNEL__:${this.id}`}}i=new WeakMap;class c{constructor(n,e,i){this.plugin=n,this.event=e,this.channelId=i}async unregister(){return u(`plugin:${this.plugin}|remove_listener`,{event:this.event,channelId:this.channelId})}}async function s(n,e,i){const t=new a;return t.onmessage=i,u(`plugin:${n}|register_listener`,{event:e,handler:t}).then((()=>new c(n,e,t.id)))}async function u(n,e={},i){return window.__TAURI_INTERNALS__.invoke(n,e,i)}n.ScheduleEvery=void 0,(t=n.ScheduleEvery||(n.ScheduleEvery={})).Year="year",t.Month="month",t.TwoWeeks="twoWeeks",t.Week="week",t.Day="day",t.Hour="hour",t.Minute="minute",t.Second="second";class l{constructor(n){this.schedule=n}toJSON(){return JSON.stringify(this.schedule)}static at(n,e=!1,i=!1){return new l({at:{date:n,repeating:e,allowWhileIdle:i}})}static interval(n,e=!1){return new l({interval:{interval:n,allowWhileIdle:e}})}static every(n,e,i=!1){return new l({every:{interval:n,count:e,allowWhileIdle:i}})}}return n.Importance=void 0,(o=n.Importance||(n.Importance={}))[o.None=0]="None",o[o.Min=1]="Min",o[o.Low=2]="Low",o[o.Default=3]="Default",o[o.High=4]="High",n.Visibility=void 0,(r=n.Visibility||(n.Visibility={}))[r.Secret=-1]="Secret",r[r.Private=0]="Private",r[r.Public=1]="Public",n.Schedule=l,n.active=async function(){return u("plugin:notification|get_active")},n.cancel=async function(n){return u("plugin:notification|cancel",{notifications:n})},n.cancelAll=async function(){return u("plugin:notification|cancel")},n.channels=async function(){return u("plugin:notification|listChannels")},n.createChannel=async function(n){return u("plugin:notification|create_channel",{...n})},n.isPermissionGranted=async function(){return"default"!==window.Notification.permission?Promise.resolve("granted"===window.Notification.permission):u("plugin:notification|is_permission_granted")},n.onAction=async function(n){return s("notification","actionPerformed",n)},n.onNotificationReceived=async function(n){return s("notification","notification",n)},n.pending=async function(){return u("plugin:notification|get_pending")},n.registerActionTypes=async function(n){return u("plugin:notification|register_action_types",{types:n})},n.removeActive=async function(n){return u("plugin:notification|remove_active",{notifications:n})},n.removeAllActive=async function(){return u("plugin:notification|remove_active")},n.removeChannel=async function(n){return u("plugin:notification|delete_channel",{id:n})},n.requestPermission=async function(){return window.Notification.requestPermission()},n.sendNotification=function(n){"string"==typeof n?new window.Notification(n):new window.Notification(n.title,n)},n}({});Object.defineProperty(window.__TAURI__,"notification",{value:__TAURI_PLUGIN_NOTIFICATION__})}
+if("__TAURI__"in window){var __TAURI_PLUGIN_NOTIFICATION__=function(n){"use strict";function e(n,e,i,t){if("a"===i&&!t)throw new TypeError("Private accessor was defined without a getter");if("function"==typeof e?n!==e||!t:!e.has(n))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===i?t:"a"===i?t.call(n):t?t.value:e.get(n)}var i,t,o,r;"function"==typeof SuppressedError&&SuppressedError;class a{constructor(){this.__TAURI_CHANNEL_MARKER__=!0,i.set(this,(()=>{})),this.id=function(n,e=!1){return window.__TAURI_INTERNALS__.transformCallback(n,e)}((n=>{e(this,i,"f").call(this,n)}))}set onmessage(n){!function(n,e,i,t,o){if("m"===t)throw new TypeError("Private method is not writable");if("a"===t&&!o)throw new TypeError("Private accessor was defined without a setter");if("function"==typeof e?n!==e||!o:!e.has(n))throw new TypeError("Cannot write private member to an object whose class did not declare it");"a"===t?o.call(n,i):o?o.value=i:e.set(n,i)}(this,i,n,"f")}get onmessage(){return e(this,i,"f")}toJSON(){return`__CHANNEL__:${this.id}`}}i=new WeakMap;class c{constructor(n,e,i){this.plugin=n,this.event=e,this.channelId=i}async unregister(){return u(`plugin:${this.plugin}|remove_listener`,{event:this.event,channelId:this.channelId})}}async function s(n,e,i){const t=new a;return t.onmessage=i,u(`plugin:${n}|register_listener`,{event:e,handler:t}).then((()=>new c(n,e,t.id)))}async function u(n,e={},i){return window.__TAURI_INTERNALS__.invoke(n,e,i)}n.ScheduleEvery=void 0,(t=n.ScheduleEvery||(n.ScheduleEvery={})).Year="year",t.Month="month",t.TwoWeeks="twoWeeks",t.Week="week",t.Day="day",t.Hour="hour",t.Minute="minute",t.Second="second";return n.Importance=void 0,(o=n.Importance||(n.Importance={}))[o.None=0]="None",o[o.Min=1]="Min",o[o.Low=2]="Low",o[o.Default=3]="Default",o[o.High=4]="High",n.Visibility=void 0,(r=n.Visibility||(n.Visibility={}))[r.Secret=-1]="Secret",r[r.Private=0]="Private",r[r.Public=1]="Public",n.Schedule=class{static at(n,e=!1,i=!1){return{at:{date:n,repeating:e,allowWhileIdle:i},interval:void 0,every:void 0}}static interval(n,e=!1){return{at:void 0,interval:{interval:n,allowWhileIdle:e},every:void 0}}static every(n,e,i=!1){return{at:void 0,interval:void 0,every:{interval:n,count:e,allowWhileIdle:i}}}},n.active=async function(){return u("plugin:notification|get_active")},n.cancel=async function(n){return u("plugin:notification|cancel",{notifications:n})},n.cancelAll=async function(){return u("plugin:notification|cancel")},n.channels=async function(){return u("plugin:notification|listChannels")},n.createChannel=async function(n){return u("plugin:notification|create_channel",{...n})},n.isPermissionGranted=async function(){return"default"!==window.Notification.permission?Promise.resolve("granted"===window.Notification.permission):u("plugin:notification|is_permission_granted")},n.onAction=async function(n){return s("notification","actionPerformed",n)},n.onNotificationReceived=async function(n){return s("notification","notification",n)},n.pending=async function(){return u("plugin:notification|get_pending")},n.registerActionTypes=async function(n){return u("plugin:notification|register_action_types",{types:n})},n.removeActive=async function(n){return u("plugin:notification|remove_active",{notifications:n})},n.removeAllActive=async function(){return u("plugin:notification|remove_active")},n.removeChannel=async function(n){return u("plugin:notification|delete_channel",{id:n})},n.requestPermission=async function(){return window.Notification.requestPermission()},n.sendNotification=function(n){"string"==typeof n?new window.Notification(n):new window.Notification(n.title,n)},n}({});Object.defineProperty(window.__TAURI__,"notification",{value:__TAURI_PLUGIN_NOTIFICATION__})}
diff --git a/plugins/notification/src/models.rs b/plugins/notification/src/models.rs
index 0db617ed..aa290dd3 100644
--- a/plugins/notification/src/models.rs
+++ b/plugins/notification/src/models.rs
@@ -51,14 +51,14 @@ impl Display for ScheduleEvery {
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",
+ Self::Year => "year",
+ Self::Month => "month",
+ Self::TwoWeeks => "twoWeeks",
+ Self::Week => "week",
+ Self::Day => "day",
+ Self::Hour => "hour",
+ Self::Minute => "minute",
+ Self::Second => "second",
}
)
}
@@ -96,6 +96,7 @@ impl<'de> Deserialize<'de> for ScheduleEvery {
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum Schedule {
+ #[serde(rename_all = "camelCase")]
At {
#[serde(
serialize_with = "iso8601::serialize",
@@ -107,11 +108,13 @@ pub enum Schedule {
#[serde(default)]
allow_while_idle: bool,
},
+ #[serde(rename_all = "camelCase")]
Interval {
interval: ScheduleInterval,
#[serde(default)]
allow_while_idle: bool,
},
+ #[serde(rename_all = "camelCase")]
Every {
interval: ScheduleEvery,
count: u8,