feat(plugin): add optional rolloverIntervalMinutes for dev/testing

Add optional rolloverIntervalMinutes to scheduleDailyNotification so the
next occurrence can be scheduled N minutes after the current trigger
(e.g. 10 minutes) instead of 24 hours. Value is persisted and used on
rollover and after reboot.

- TypeScript: NotificationOptions.rolloverIntervalMinutes?: number
- Android: Schedule.rolloverIntervalMinutes in Room (migration 2→3);
  Plugin and ScheduleHelper persist it; Worker uses it in rollover and
  updates nextRunAt; ReactivationManager uses it in boot recovery
- iOS: NotificationContent.rolloverIntervalMinutes (Codable); Plugin
  passes it into content; Scheduler uses it in calculateNextScheduledTime
  and copies to nextContent on rollover

When absent or ≤0, behavior unchanged (24h). App can clear by calling
scheduleDailyNotification without the parameter.
This commit is contained in:
Jose Olarte III
2026-03-03 17:45:45 +08:00
parent aa0eaa5389
commit e873a46bbd
8 changed files with 187 additions and 98 deletions

View File

@@ -27,6 +27,8 @@ class NotificationContent: Codable {
let url: String?
let payload: [String: Any]?
let etag: String?
/** When > 0, next occurrence is this many minutes after trigger (dev/testing). Nil/0 = 24h. Persisted for rollover and recovery. */
var rolloverIntervalMinutes: Int?
// Phase 2: Delivery tracking properties
var deliveryStatus: String? // e.g., "scheduled", "delivered", "missed", "error"
@@ -43,6 +45,7 @@ class NotificationContent: Codable {
case url
case payload
case etag
case rolloverIntervalMinutes
case deliveryStatus
case lastDeliveryAttempt
}
@@ -64,6 +67,7 @@ class NotificationContent: Codable {
payload = nil
}
etag = try container.decodeIfPresent(String.self, forKey: .etag)
rolloverIntervalMinutes = try container.decodeIfPresent(Int.self, forKey: .rolloverIntervalMinutes)
deliveryStatus = try container.decodeIfPresent(String.self, forKey: .deliveryStatus)
lastDeliveryAttempt = try container.decodeIfPresent(Int64.self, forKey: .lastDeliveryAttempt)
}
@@ -83,6 +87,7 @@ class NotificationContent: Codable {
try container.encode(payloadString, forKey: .payload)
}
try container.encodeIfPresent(etag, forKey: .etag)
try container.encodeIfPresent(rolloverIntervalMinutes, forKey: .rolloverIntervalMinutes)
try container.encodeIfPresent(deliveryStatus, forKey: .deliveryStatus)
try container.encodeIfPresent(lastDeliveryAttempt, forKey: .lastDeliveryAttempt)
}
@@ -100,20 +105,21 @@ class NotificationContent: Codable {
* @param url URL for content fetching
* @param payload Additional payload data
* @param etag ETag for HTTP caching
* @param rolloverIntervalMinutes When > 0, next occurrence is this many minutes after trigger (dev/testing). Nil/0 = 24h.
* @param deliveryStatus Delivery status (optional, Phase 2)
* @param lastDeliveryAttempt Last delivery attempt timestamp (optional, Phase 2)
*/
init(id: String,
title: String?,
body: String?,
scheduledTime: Int64,
fetchedAt: Int64,
url: String?,
payload: [String: Any]?,
init(id: String,
title: String?,
body: String?,
scheduledTime: Int64,
fetchedAt: Int64,
url: String?,
payload: [String: Any]?,
etag: String?,
rolloverIntervalMinutes: Int? = nil,
deliveryStatus: String? = nil,
lastDeliveryAttempt: Int64? = nil) {
self.id = id
self.title = title
self.body = body
@@ -122,6 +128,7 @@ class NotificationContent: Codable {
self.url = url
self.payload = payload
self.etag = etag
self.rolloverIntervalMinutes = rolloverIntervalMinutes
self.deliveryStatus = deliveryStatus
self.lastDeliveryAttempt = lastDeliveryAttempt
}
@@ -259,6 +266,7 @@ class NotificationContent: Codable {
lastDeliveryAttempt = nil
}
let rollover = (dict["rolloverIntervalMinutes"] as? NSNumber)?.intValue
return NotificationContent(
id: id,
title: dict["title"] as? String,
@@ -268,6 +276,7 @@ class NotificationContent: Codable {
url: dict["url"] as? String,
payload: dict["payload"] as? [String: Any],
etag: dict["etag"] as? String,
rolloverIntervalMinutes: rollover,
deliveryStatus: dict["deliveryStatus"] as? String,
lastDeliveryAttempt: lastDeliveryAttempt
)