feat(ios): implement scheduleDailyNotification method
Implemented main scheduling method for iOS plugin, matching Android functionality: Core Features: - Permission checking and requesting (iOS notification authorization) - Time parsing (HH:mm format) with validation - Next run time calculation (handles same-day and next-day scheduling) - UNUserNotificationCenter scheduling with daily repeat - Priority/interruption level support (iOS 15.0+) - Prefetch scheduling 5 minutes before notification (BGTaskScheduler) - Schedule storage in UserDefaults Implementation Details: - Checks notification authorization status before scheduling - Requests permission if not granted (equivalent to Android exact alarm permission) - Parses time string and calculates next occurrence - Creates UNCalendarNotificationTrigger for daily repeat - Schedules BGAppRefreshTask for prefetch 5 minutes before - Stores schedule metadata in UserDefaults for persistence Matches Android API: - Same parameter structure (time, title, body, sound, priority, url) - Same behavior (daily repeat, prefetch scheduling) - iOS-specific adaptations (UNUserNotificationCenter vs AlarmManager) This is the first critical method implementation (10/52 methods now complete).
This commit is contained in:
@@ -121,6 +121,216 @@ public class DailyNotificationPlugin: CAPPlugin {
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Main Scheduling Method
|
||||
|
||||
/**
|
||||
* Schedule a daily notification
|
||||
*
|
||||
* This is the main scheduling method, equivalent to Android's scheduleDailyNotification.
|
||||
* Schedules both the notification and a prefetch 5 minutes before.
|
||||
*
|
||||
* @param call Plugin call with options:
|
||||
* - time: String (required) - Time in HH:mm format (e.g., "09:00")
|
||||
* - title: String (optional) - Notification title (default: "Daily Notification")
|
||||
* - body: String (optional) - Notification body (default: "")
|
||||
* - sound: Bool (optional) - Enable sound (default: true)
|
||||
* - priority: String (optional) - Priority: "high", "default", "low" (default: "default")
|
||||
* - url: String (optional) - URL for prefetch (optional, native fetcher used if registered)
|
||||
*/
|
||||
@objc func scheduleDailyNotification(_ call: CAPPluginCall) {
|
||||
// Check notification permissions first
|
||||
notificationCenter.getNotificationSettings { settings in
|
||||
if settings.authorizationStatus != .authorized {
|
||||
// Request permission if not granted
|
||||
self.notificationCenter.requestAuthorization(options: [.alert, .sound, .badge]) { granted, error in
|
||||
DispatchQueue.main.async {
|
||||
if let error = error {
|
||||
print("DNP-PLUGIN: Permission request failed: \(error)")
|
||||
call.reject("Notification permission request failed: \(error.localizedDescription)")
|
||||
return
|
||||
}
|
||||
|
||||
if !granted {
|
||||
print("DNP-PLUGIN: Notification permission denied")
|
||||
call.reject("Notification permission denied. Please enable notifications in Settings.", "PERMISSION_DENIED")
|
||||
return
|
||||
}
|
||||
|
||||
// Permission granted, proceed with scheduling
|
||||
self.performScheduleDailyNotification(call: call)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Permission already granted, proceed
|
||||
self.performScheduleDailyNotification(call: call)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform the actual scheduling after permission check
|
||||
*/
|
||||
private func performScheduleDailyNotification(call: CAPPluginCall) {
|
||||
guard let options = call.options else {
|
||||
call.reject("Options are required")
|
||||
return
|
||||
}
|
||||
|
||||
guard let timeString = options["time"] as? String else {
|
||||
call.reject("Time is required (format: HH:mm)")
|
||||
return
|
||||
}
|
||||
|
||||
let title = options["title"] as? String ?? "Daily Notification"
|
||||
let body = options["body"] as? String ?? ""
|
||||
let sound = options["sound"] as? Bool ?? true
|
||||
let priority = options["priority"] as? String ?? "default"
|
||||
let url = options["url"] as? String // Optional URL for prefetch
|
||||
|
||||
print("DNP-PLUGIN: Scheduling daily notification: time=\(timeString), title=\(title)")
|
||||
|
||||
// Parse time (HH:mm format)
|
||||
let timeComponents = timeString.components(separatedBy: ":")
|
||||
guard timeComponents.count == 2,
|
||||
let hour = Int(timeComponents[0]),
|
||||
let minute = Int(timeComponents[1]),
|
||||
hour >= 0 && hour <= 23,
|
||||
minute >= 0 && minute <= 59 else {
|
||||
call.reject("Invalid time format. Use HH:mm (e.g., 09:00)")
|
||||
return
|
||||
}
|
||||
|
||||
// Calculate next run time
|
||||
let calendar = Calendar.current
|
||||
let now = Date()
|
||||
var dateComponents = calendar.dateComponents([.year, .month, .day], from: now)
|
||||
dateComponents.hour = hour
|
||||
dateComponents.minute = minute
|
||||
dateComponents.second = 0
|
||||
|
||||
guard var nextRunDate = calendar.date(from: dateComponents) else {
|
||||
call.reject("Failed to calculate next run time")
|
||||
return
|
||||
}
|
||||
|
||||
// If the time has already passed today, schedule for tomorrow
|
||||
if nextRunDate <= now {
|
||||
nextRunDate = calendar.date(byAdding: .day, value: 1, to: nextRunDate) ?? nextRunDate
|
||||
}
|
||||
|
||||
let nextRunTime = nextRunDate.timeIntervalSince1970 * 1000 // Convert to milliseconds
|
||||
let nextRunTimeInterval = nextRunDate.timeIntervalSinceNow
|
||||
|
||||
// Create notification content
|
||||
let content = UNMutableNotificationContent()
|
||||
content.title = title
|
||||
content.body = body
|
||||
content.sound = sound ? .default : nil
|
||||
content.categoryIdentifier = "DAILY_NOTIFICATION"
|
||||
|
||||
// Set priority/interruption level
|
||||
if #available(iOS 15.0, *) {
|
||||
switch priority.lowercased() {
|
||||
case "high", "max":
|
||||
content.interruptionLevel = .critical
|
||||
case "low", "min":
|
||||
content.interruptionLevel = .passive
|
||||
default:
|
||||
content.interruptionLevel = .active
|
||||
}
|
||||
}
|
||||
|
||||
// Create date components for daily trigger
|
||||
var triggerComponents = DateComponents()
|
||||
triggerComponents.hour = hour
|
||||
triggerComponents.minute = minute
|
||||
|
||||
let trigger = UNCalendarNotificationTrigger(
|
||||
dateMatching: triggerComponents,
|
||||
repeats: true // Daily repeat
|
||||
)
|
||||
|
||||
// Create unique identifier
|
||||
let scheduleId = "daily_\(Int(Date().timeIntervalSince1970 * 1000))"
|
||||
|
||||
let request = UNNotificationRequest(
|
||||
identifier: scheduleId,
|
||||
content: content,
|
||||
trigger: trigger
|
||||
)
|
||||
|
||||
// Schedule the notification
|
||||
notificationCenter.add(request) { error in
|
||||
if let error = error {
|
||||
print("DNP-PLUGIN: Failed to schedule notification: \(error)")
|
||||
call.reject("Failed to schedule notification: \(error.localizedDescription)")
|
||||
return
|
||||
}
|
||||
|
||||
print("DNP-PLUGIN: Notification scheduled successfully: \(scheduleId)")
|
||||
|
||||
// Schedule prefetch 5 minutes before notification
|
||||
let fetchTime = nextRunTime - (5 * 60 * 1000) // 5 minutes before in milliseconds
|
||||
let fetchTimeInterval = (fetchTime / 1000) - Date().timeIntervalSince1970
|
||||
|
||||
if fetchTimeInterval > 0 {
|
||||
// Schedule background fetch task
|
||||
do {
|
||||
let fetchRequest = BGAppRefreshTaskRequest(identifier: self.fetchTaskIdentifier)
|
||||
fetchRequest.earliestBeginDate = Date(timeIntervalSinceNow: fetchTimeInterval)
|
||||
|
||||
try self.backgroundTaskScheduler.submit(fetchRequest)
|
||||
print("DNP-PLUGIN: Prefetch scheduled: fetchTime=\(fetchTime), notificationTime=\(nextRunTime)")
|
||||
} catch {
|
||||
print("DNP-PLUGIN: Failed to schedule prefetch: \(error)")
|
||||
// Don't fail the whole operation if prefetch scheduling fails
|
||||
}
|
||||
} else {
|
||||
// Fetch time is in the past, trigger immediate fetch if possible
|
||||
print("DNP-PLUGIN: Fetch time is in the past, skipping prefetch scheduling")
|
||||
}
|
||||
|
||||
// Store schedule in UserDefaults (similar to Android database storage)
|
||||
self.storeScheduleInUserDefaults(
|
||||
id: scheduleId,
|
||||
time: timeString,
|
||||
title: title,
|
||||
body: body,
|
||||
nextRunTime: nextRunTime
|
||||
)
|
||||
|
||||
call.resolve()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Store schedule in UserDefaults
|
||||
*/
|
||||
private func storeScheduleInUserDefaults(
|
||||
id: String,
|
||||
time: String,
|
||||
title: String,
|
||||
body: String,
|
||||
nextRunTime: TimeInterval
|
||||
) {
|
||||
let schedule: [String: Any] = [
|
||||
"id": id,
|
||||
"kind": "notify",
|
||||
"time": time,
|
||||
"title": title,
|
||||
"body": body,
|
||||
"nextRunTime": nextRunTime,
|
||||
"enabled": true,
|
||||
"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: - Private Implementation Methods
|
||||
|
||||
private func setupBackgroundTasks() {
|
||||
|
||||
Reference in New Issue
Block a user