fix(notification): scheduled notifications not working (#909)

* fix(notification): scheduled notifications not working

* do not use toJSON since it doesnt work on isolation pattern

* fmt
pull/923/head
Lucas Fernandes Nogueira 1 year ago committed by GitHub
parent 61edbbec0a
commit 8dea78ac7d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,6 @@
---
"notification": patch
"notification-js": patch
---
Fixes deserialization and implementation bugs with scheduled notifications on Android.

@ -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 {

@ -14,6 +14,7 @@
</intent-filter>
</receiver>
</application>
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
<uses-permission android:name="android.permission.WAKE_LOCK"/>
</manifest>
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
</manifest>

@ -80,8 +80,6 @@ class Notification {
return null
}
val isScheduled = schedule != null
companion object {
fun buildNotificationPendingList(notifications: List<Notification>): List<PendingNotification> {
val pendingNotifications = mutableListOf<PendingNotification>()

@ -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)
}

@ -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) {

@ -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())
}

@ -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

@ -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 },
};
}
}

@ -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__})}

@ -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,

Loading…
Cancel
Save