fix(ios): correct next notification time and improve rollover UI refresh
- Fix getNextNotificationTime() to find earliest scheduled notification instead of using first request (pendingNotificationRequests doesn't guarantee order) - Add comprehensive logging for rollover tracking with DNP-ROLLOVER prefix for Xcode console filtering - Reset all notifications and rollover state when scheduling new notification via scheduleDailyNotification() to ensure clean test state - Fix userInfo scope error in handleNotificationDelivery error handler - Update test app UI to refresh status every 5-10 seconds and immediately after notification delivery to reflect rollover changes - Add console logging in UI to debug getNotificationStatus() results This ensures the UI correctly displays the next notification time after rollover completes, and test notifications start with a clean slate.
This commit is contained in:
@@ -72,6 +72,14 @@ public class DailyNotificationPlugin: CAPPlugin {
|
||||
// Perform recovery on app launch (async, non-blocking)
|
||||
reactivationManager?.performRecovery()
|
||||
|
||||
// Register for notification delivery events (Notification Center pattern)
|
||||
NotificationCenter.default.addObserver(
|
||||
self,
|
||||
selector: #selector(handleNotificationDelivery(_:)),
|
||||
name: NSNotification.Name("DailyNotificationDelivered"),
|
||||
object: nil
|
||||
)
|
||||
|
||||
NSLog("DNP-DEBUG: DailyNotificationPlugin.load() completed - initialization done")
|
||||
print("DNP-PLUGIN: Daily Notification Plugin loaded on iOS")
|
||||
}
|
||||
@@ -1068,6 +1076,38 @@ public class DailyNotificationPlugin: CAPPlugin {
|
||||
|
||||
// Store notification content via state actor (thread-safe)
|
||||
Task {
|
||||
// Reset: Cancel all existing notifications and clear rollover state
|
||||
// This ensures clicking "Test Notification" starts fresh
|
||||
// Cancel all pending notifications (including rollovers)
|
||||
await scheduler.cancelAllNotifications()
|
||||
NSLog("DNP-PLUGIN: Cleared all pending notifications for fresh schedule")
|
||||
print("DNP-PLUGIN: Cleared all pending notifications for fresh schedule")
|
||||
|
||||
// Clear all stored notification content
|
||||
if let storage = self.storage {
|
||||
storage.clearAllNotifications()
|
||||
NSLog("DNP-PLUGIN: Cleared all stored notification content")
|
||||
print("DNP-PLUGIN: Cleared all stored notification content")
|
||||
}
|
||||
|
||||
// Clear rollover state from UserDefaults
|
||||
// Clear global rollover time
|
||||
if let storage = self.storage {
|
||||
storage.saveLastRolloverTime(0)
|
||||
}
|
||||
|
||||
// Clear per-notification rollover times
|
||||
// We need to clear all rollover_* keys from UserDefaults
|
||||
let userDefaults = UserDefaults.standard
|
||||
let allKeys = userDefaults.dictionaryRepresentation().keys
|
||||
for key in allKeys {
|
||||
if key.hasPrefix("rollover_") {
|
||||
userDefaults.removeObject(forKey: key)
|
||||
}
|
||||
}
|
||||
userDefaults.synchronize()
|
||||
NSLog("DNP-PLUGIN: Cleared all rollover state")
|
||||
print("DNP-PLUGIN: Cleared all rollover state")
|
||||
if #available(iOS 13.0, *) {
|
||||
if let stateActor = await self.stateActor {
|
||||
await stateActor.saveNotificationContent(content)
|
||||
@@ -1226,12 +1266,17 @@ public class DailyNotificationPlugin: CAPPlugin {
|
||||
// Calculate next notification time
|
||||
let nextNotificationTime = await scheduler.getNextNotificationTime() ?? 0
|
||||
|
||||
// Get rollover status
|
||||
let lastRolloverTime = storage?.getLastRolloverTime() ?? 0
|
||||
|
||||
var result: [String: Any] = [
|
||||
"isEnabled": isEnabled,
|
||||
"isScheduled": pendingCount > 0,
|
||||
"lastNotificationTime": lastNotification?.scheduledTime ?? 0,
|
||||
"nextNotificationTime": nextNotificationTime,
|
||||
"pending": pendingCount,
|
||||
"rolloverEnabled": true, // Indicate rollover is active
|
||||
"lastRolloverTime": lastRolloverTime, // When last rollover occurred
|
||||
"settings": settings
|
||||
]
|
||||
|
||||
@@ -1241,6 +1286,93 @@ public class DailyNotificationPlugin: CAPPlugin {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle notification delivery event (from Notification Center)
|
||||
*
|
||||
* This is called when AppDelegate posts notification delivery event
|
||||
* Matches Android's scheduleNextNotification() behavior
|
||||
*
|
||||
* @param notification NSNotification with userInfo containing notification_id and scheduled_time
|
||||
*/
|
||||
@objc private func handleNotificationDelivery(_ notification: Notification) {
|
||||
guard let userInfo = notification.userInfo,
|
||||
let notificationId = userInfo["notification_id"] as? String,
|
||||
let scheduledTime = userInfo["scheduled_time"] as? Int64 else {
|
||||
NSLog("DNP-ROLLOVER: INVALID_DATA userInfo=\(String(describing: notification.userInfo))")
|
||||
print("DNP-ROLLOVER: INVALID_DATA userInfo=\(String(describing: notification.userInfo))")
|
||||
return
|
||||
}
|
||||
|
||||
let scheduledTimeStr = formatTime(scheduledTime)
|
||||
NSLog("DNP-ROLLOVER: DELIVERY_DETECTED id=\(notificationId) scheduled_time=\(scheduledTimeStr)")
|
||||
print("DNP-ROLLOVER: DELIVERY_DETECTED id=\(notificationId) scheduled_time=\(scheduledTimeStr)")
|
||||
|
||||
Task {
|
||||
await processRollover(notificationId: notificationId, scheduledTime: scheduledTime)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process rollover for delivered notification
|
||||
*
|
||||
* @param notificationId ID of notification that was delivered
|
||||
* @param scheduledTime Scheduled time of delivered notification
|
||||
*/
|
||||
private func processRollover(notificationId: String, scheduledTime: Int64) async {
|
||||
let scheduledTimeStr = formatTime(scheduledTime)
|
||||
NSLog("DNP-ROLLOVER: PROCESS_START id=\(notificationId) scheduled_time=\(scheduledTimeStr)")
|
||||
print("DNP-ROLLOVER: PROCESS_START id=\(notificationId) scheduled_time=\(scheduledTimeStr)")
|
||||
|
||||
guard let scheduler = scheduler, let storage = storage else {
|
||||
NSLog("DNP-ROLLOVER: ERROR id=\(notificationId) plugin_not_initialized")
|
||||
print("DNP-ROLLOVER: ERROR id=\(notificationId) plugin_not_initialized")
|
||||
return
|
||||
}
|
||||
|
||||
// Get the notification content that was delivered
|
||||
guard let content = storage.getNotificationContent(id: notificationId) else {
|
||||
NSLog("DNP-ROLLOVER: ERROR id=\(notificationId) content_not_found")
|
||||
print("DNP-ROLLOVER: ERROR id=\(notificationId) content_not_found")
|
||||
return
|
||||
}
|
||||
|
||||
let contentTimeStr = formatTime(content.scheduledTime)
|
||||
NSLog("DNP-ROLLOVER: CONTENT_FOUND id=\(notificationId) content_scheduled_time=\(contentTimeStr)")
|
||||
print("DNP-ROLLOVER: CONTENT_FOUND id=\(notificationId) content_scheduled_time=\(contentTimeStr)")
|
||||
|
||||
// Schedule next notification
|
||||
// Note: DailyNotificationFetcher integration deferred to Phase 2
|
||||
let scheduled = await scheduler.scheduleNextNotification(
|
||||
content,
|
||||
storage: storage,
|
||||
fetcher: nil // TODO: Phase 2 - Add fetcher instance
|
||||
)
|
||||
|
||||
if scheduled {
|
||||
NSLog("DNP-ROLLOVER: PROCESS_SUCCESS id=\(notificationId) next_notification_scheduled")
|
||||
print("DNP-ROLLOVER: PROCESS_SUCCESS id=\(notificationId) next_notification_scheduled")
|
||||
// Log success (non-fatal, background operation)
|
||||
} else {
|
||||
NSLog("DNP-ROLLOVER: PROCESS_FAILED id=\(notificationId) next_notification_not_scheduled")
|
||||
print("DNP-ROLLOVER: PROCESS_FAILED id=\(notificationId) next_notification_not_scheduled")
|
||||
// Log failure but continue (recovery will handle on next launch)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Format time for logging
|
||||
*
|
||||
* @param timestamp Timestamp in milliseconds
|
||||
* @return Formatted time string
|
||||
*/
|
||||
private func formatTime(_ timestamp: Int64) -> String {
|
||||
let date = Date(timeIntervalSince1970: Double(timestamp) / 1000.0)
|
||||
let formatter = DateFormatter()
|
||||
formatter.dateStyle = .medium
|
||||
formatter.timeStyle = .short
|
||||
return formatter.string(from: date)
|
||||
}
|
||||
|
||||
/**
|
||||
* Check permission status
|
||||
* Returns boolean flags for each permission type
|
||||
|
||||
Reference in New Issue
Block a user