// // DailyNotificationPlugin.swift // DailyNotificationPlugin // // Created by Matthew Raymer on 2025-09-22 // Copyright © 2025 TimeSafari. All rights reserved. // import Foundation import Capacitor import UserNotifications import BackgroundTasks import CoreData /** * iOS implementation of Daily Notification Plugin * Implements BGTaskScheduler + UNUserNotificationCenter for dual scheduling * * @author Matthew Raymer * @version 1.1.0 * @created 2025-09-22 09:22:32 UTC */ @objc(DailyNotificationPlugin) public class DailyNotificationPlugin: CAPPlugin { private let notificationCenter = UNUserNotificationCenter.current() private let backgroundTaskScheduler = BGTaskScheduler.shared private let persistenceController = PersistenceController.shared // Background task identifiers private let fetchTaskIdentifier = "com.timesafari.dailynotification.fetch" private let notifyTaskIdentifier = "com.timesafari.dailynotification.notify" override public func load() { super.load() setupBackgroundTasks() print("DNP-PLUGIN: Daily Notification Plugin loaded on iOS") } // MARK: - Configuration Methods @objc func configure(_ call: CAPPluginCall) { guard let options = call.getObject("options") else { call.reject("Configuration options required") return } print("DNP-PLUGIN: Configure called with options: \(options)") // Store configuration in UserDefaults UserDefaults.standard.set(options, forKey: "DailyNotificationConfig") call.resolve() } // MARK: - Dual Scheduling Methods @objc func scheduleContentFetch(_ call: CAPPluginCall) { guard let config = call.getObject("config") else { call.reject("Content fetch config required") return } print("DNP-PLUGIN: Scheduling content fetch") do { try scheduleBackgroundFetch(config: config) call.resolve() } catch { print("DNP-PLUGIN: Failed to schedule content fetch: \(error)") call.reject("Content fetch scheduling failed: \(error.localizedDescription)") } } @objc func scheduleUserNotification(_ call: CAPPluginCall) { guard let config = call.getObject("config") else { call.reject("User notification config required") return } print("DNP-PLUGIN: Scheduling user notification") do { try scheduleUserNotification(config: config) call.resolve() } catch { print("DNP-PLUGIN: Failed to schedule user notification: \(error)") call.reject("User notification scheduling failed: \(error.localizedDescription)") } } @objc func scheduleDualNotification(_ call: CAPPluginCall) { guard let config = call.getObject("config"), let contentFetchConfig = config["contentFetch"] as? [String: Any], let userNotificationConfig = config["userNotification"] as? [String: Any] else { call.reject("Dual notification config required") return } print("DNP-PLUGIN: Scheduling dual notification") do { try scheduleBackgroundFetch(config: contentFetchConfig) try scheduleUserNotification(config: userNotificationConfig) call.resolve() } catch { print("DNP-PLUGIN: Failed to schedule dual notification: \(error)") call.reject("Dual notification scheduling failed: \(error.localizedDescription)") } } @objc func getDualScheduleStatus(_ call: CAPPluginCall) { Task { do { let status = try await getHealthStatus() call.resolve(status) } catch { print("DNP-PLUGIN: Failed to get dual schedule status: \(error)") call.reject("Status retrieval failed: \(error.localizedDescription)") } } } // MARK: - Private Implementation Methods private func setupBackgroundTasks() { // Register background fetch task backgroundTaskScheduler.register(forTaskWithIdentifier: fetchTaskIdentifier, using: nil) { task in self.handleBackgroundFetch(task: task as! BGAppRefreshTask) } // Register background processing task backgroundTaskScheduler.register(forTaskWithIdentifier: notifyTaskIdentifier, using: nil) { task in self.handleBackgroundNotify(task: task as! BGProcessingTask) } } private func scheduleBackgroundFetch(config: [String: Any]) throws { let request = BGAppRefreshTaskRequest(identifier: fetchTaskIdentifier) // Calculate next run time (simplified - would use proper cron parsing in production) let nextRunTime = calculateNextRunTime(from: config["schedule"] as? String ?? "0 9 * * *") request.earliestBeginDate = Date(timeIntervalSinceNow: nextRunTime) try backgroundTaskScheduler.submit(request) print("DNP-FETCH-SCHEDULE: Background fetch scheduled for \(request.earliestBeginDate!)") } private func scheduleUserNotification(config: [String: Any]) throws { let content = UNMutableNotificationContent() content.title = config["title"] as? String ?? "Daily Notification" content.body = config["body"] as? String ?? "Your daily update is ready" content.sound = (config["sound"] as? Bool ?? true) ? .default : nil // Create trigger (simplified - would use proper cron parsing in production) let nextRunTime = calculateNextRunTime(from: config["schedule"] as? String ?? "0 9 * * *") let trigger = UNTimeIntervalNotificationTrigger(timeInterval: nextRunTime, repeats: false) let request = UNNotificationRequest( identifier: "daily-notification-\(Date().timeIntervalSince1970)", content: content, trigger: trigger ) notificationCenter.add(request) { error in if let error = error { print("DNP-NOTIFY-SCHEDULE: Failed to schedule notification: \(error)") } else { print("DNP-NOTIFY-SCHEDULE: Notification scheduled successfully") } } } private func calculateNextRunTime(from schedule: String) -> TimeInterval { // Simplified implementation - would use proper cron parsing in production // For now, return next day at 9 AM return 86400 // 24 hours } }