From d7754752ba4de8862b208bc4603f82f50a857dcc Mon Sep 17 00:00:00 2001 From: Matthew Raymer Date: Tue, 11 Nov 2025 01:54:52 -0800 Subject: [PATCH] feat(ios): implement getNotificationStatus and cancelAllNotifications methods Implemented status and cancellation methods matching Android functionality: getNotificationStatus(): - Gets pending notifications from UNUserNotificationCenter - Retrieves schedules from UserDefaults - Calculates next notification time from schedules - Returns status matching Android API structure - Includes isEnabled, isScheduled, lastNotificationTime, nextNotificationTime, pending count cancelAllNotifications(): - Removes all pending daily notifications from UNUserNotificationCenter - Cancels background fetch tasks by identifier - Clears notification schedules from UserDefaults - Idempotent (safe to call multiple times) - Matches Android behavior (cancels alarms, WorkManager jobs, database) Helper Methods: - getSchedulesFromUserDefaults() - Retrieves stored schedules - Improved storeScheduleInUserDefaults() - Prevents duplicates Progress: 12/52 methods implemented (23% complete) --- ios/Plugin/DailyNotificationPlugin.swift | 117 ++++++++++++++++++++++- 1 file changed, 116 insertions(+), 1 deletion(-) diff --git a/ios/Plugin/DailyNotificationPlugin.swift b/ios/Plugin/DailyNotificationPlugin.swift index 8455aa8..ded74f0 100644 --- a/ios/Plugin/DailyNotificationPlugin.swift +++ b/ios/Plugin/DailyNotificationPlugin.swift @@ -313,6 +313,11 @@ public class DailyNotificationPlugin: CAPPlugin { body: String, nextRunTime: TimeInterval ) { + var schedules = UserDefaults.standard.array(forKey: "DailyNotificationSchedules") as? [[String: Any]] ?? [] + + // Remove existing schedule with same ID if present + schedules.removeAll { ($0["id"] as? String) == id } + let schedule: [String: Any] = [ "id": id, "kind": "notify", @@ -324,13 +329,123 @@ public class DailyNotificationPlugin: CAPPlugin { "createdAt": Date().timeIntervalSince1970 * 1000 ] - var schedules = UserDefaults.standard.array(forKey: "DailyNotificationSchedules") as? [[String: Any]] ?? [] schedules.append(schedule) UserDefaults.standard.set(schedules, forKey: "DailyNotificationSchedules") print("DNP-PLUGIN: Schedule stored: \(id)") } + // MARK: - Status & Cancellation Methods + + /** + * Get notification status + * + * Returns the current status of scheduled notifications, including: + * - Whether notifications are enabled/scheduled + * - Last notification time + * - Next notification time + * - Pending notification count + * + * Equivalent to Android's getNotificationStatus method. + */ + @objc func getNotificationStatus(_ call: CAPPluginCall) { + // Get pending notifications from UNUserNotificationCenter + notificationCenter.getPendingNotificationRequests { requests in + // Filter for daily notifications (those starting with "daily_") + let dailyNotifications = requests.filter { $0.identifier.hasPrefix("daily_") } + + // Get schedules from UserDefaults + let schedules = self.getSchedulesFromUserDefaults() + let notifySchedules = schedules.filter { + ($0["kind"] as? String) == "notify" && + ($0["enabled"] as? Bool) == true + } + + // Calculate next notification time + var nextNotificationTime: TimeInterval = 0 + if let nextRunTimes = notifySchedules.compactMap({ $0["nextRunTime"] as? TimeInterval }) as? [TimeInterval], + !nextRunTimes.isEmpty { + nextNotificationTime = nextRunTimes.min() ?? 0 + } + + // Get last notification time from UserDefaults (stored when notification is delivered) + // For now, we'll use 0 if not available (history tracking can be added later) + let lastNotificationTime = UserDefaults.standard.double(forKey: "DailyNotificationLastDeliveryTime") + + // Build result matching Android API + let result: [String: Any] = [ + "isEnabled": !notifySchedules.isEmpty, + "isScheduled": !notifySchedules.isEmpty, + "lastNotificationTime": lastNotificationTime > 0 ? lastNotificationTime : 0, + "nextNotificationTime": nextNotificationTime, + "scheduledCount": notifySchedules.count, + "pending": dailyNotifications.count, + "settings": [ + "enabled": !notifySchedules.isEmpty, + "count": notifySchedules.count + ] as [String: Any] + ] + + DispatchQueue.main.async { + call.resolve(result) + } + } + } + + /** + * Cancel all notifications + * + * Cancels all scheduled daily notifications: + * 1. Removes all pending notifications from UNUserNotificationCenter + * 2. Cancels all background fetch tasks + * 3. Clears schedules from UserDefaults + * + * Equivalent to Android's cancelAllNotifications method. + * The method is idempotent - safe to call multiple times. + */ + @objc func cancelAllNotifications(_ call: CAPPluginCall) { + print("DNP-PLUGIN: Cancelling all notifications") + + // 1. Get all pending notifications + notificationCenter.getPendingNotificationRequests { requests in + let dailyNotificationIds = requests + .filter { $0.identifier.hasPrefix("daily_") } + .map { $0.identifier } + + // 2. Remove all daily notifications + if !dailyNotificationIds.isEmpty { + self.notificationCenter.removePendingNotificationRequests(withIdentifiers: dailyNotificationIds) + print("DNP-PLUGIN: Removed \(dailyNotificationIds.count) pending notification(s)") + } + + // 3. Cancel all background tasks + // Cancel by identifier (BGTaskScheduler requires identifier to cancel) + // Note: cancel() doesn't throw, it's safe to call even if task doesn't exist + self.backgroundTaskScheduler.cancel(taskRequestWithIdentifier: self.fetchTaskIdentifier) + self.backgroundTaskScheduler.cancel(taskRequestWithIdentifier: self.notifyTaskIdentifier) + print("DNP-PLUGIN: Cancelled background tasks") + + // 4. Clear schedules from UserDefaults + var schedules = UserDefaults.standard.array(forKey: "DailyNotificationSchedules") as? [[String: Any]] ?? [] + let notifySchedules = schedules.filter { ($0["kind"] as? String) == "notify" } + schedules.removeAll { ($0["kind"] as? String) == "notify" } + UserDefaults.standard.set(schedules, forKey: "DailyNotificationSchedules") + print("DNP-PLUGIN: Removed \(notifySchedules.count) schedule(s) from storage") + + DispatchQueue.main.async { + print("DNP-PLUGIN: All notifications cancelled successfully") + call.resolve() + } + } + } + + /** + * Get schedules from UserDefaults + */ + private func getSchedulesFromUserDefaults() -> [[String: Any]] { + return UserDefaults.standard.array(forKey: "DailyNotificationSchedules") as? [[String: Any]] ?? [] + } + // MARK: - Private Implementation Methods private func setupBackgroundTasks() {