You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
328 lines
12 KiB
328 lines
12 KiB
/**
|
|
* DailyNotificationPlugin.swift
|
|
* Daily Notification Plugin for Capacitor
|
|
*
|
|
* Handles daily notification scheduling and management on iOS
|
|
*/
|
|
|
|
import Foundation
|
|
import Capacitor
|
|
import UserNotifications
|
|
|
|
/// Represents the main plugin class for handling daily notifications
|
|
///
|
|
/// This plugin provides functionality for scheduling and managing daily notifications
|
|
/// on iOS devices using the UserNotifications framework.
|
|
@objc(DailyNotificationPlugin)
|
|
public class DailyNotificationPlugin: CAPPlugin {
|
|
private let notificationCenter = UNUserNotificationCenter.current()
|
|
|
|
private var settings: [String: Any] = [
|
|
"sound": true,
|
|
"priority": "default",
|
|
"retryCount": 3,
|
|
"retryInterval": 1000
|
|
]
|
|
|
|
private static let CHANNEL_ID = "daily_notification_channel"
|
|
private static let CHANNEL_NAME = "Daily Notifications"
|
|
private static let CHANNEL_DESCRIPTION = "Daily notification updates"
|
|
|
|
/// Schedules a new daily notification
|
|
/// - Parameter call: The plugin call containing notification parameters
|
|
/// - Returns: Void
|
|
/// - Throws: DailyNotificationError
|
|
@objc func scheduleDailyNotification(_ call: CAPPluginCall) {
|
|
guard let url = call.getString("url"),
|
|
let time = call.getString("time") else {
|
|
call.reject("Missing required parameters")
|
|
return
|
|
}
|
|
|
|
// Parse time string (HH:mm format)
|
|
let timeComponents = time.split(separator: ":")
|
|
guard timeComponents.count == 2,
|
|
let hour = Int(timeComponents[0]),
|
|
let minute = Int(timeComponents[1]),
|
|
hour >= 0 && hour < 24,
|
|
minute >= 0 && minute < 60 else {
|
|
call.reject("Invalid time format")
|
|
return
|
|
}
|
|
|
|
// Create notification content
|
|
let content = UNMutableNotificationContent()
|
|
content.title = call.getString("title") ?? "Daily Notification"
|
|
content.body = call.getString("body") ?? "Your daily update is ready"
|
|
content.sound = call.getBool("sound", true) ? .default : nil
|
|
|
|
// Set priority
|
|
if let priority = call.getString("priority") {
|
|
if #available(iOS 15.0, *) {
|
|
switch priority {
|
|
case "high":
|
|
content.interruptionLevel = .timeSensitive
|
|
case "low":
|
|
content.interruptionLevel = .passive
|
|
default:
|
|
content.interruptionLevel = .active
|
|
}
|
|
}
|
|
}
|
|
|
|
// Add to notification content setup
|
|
content.categoryIdentifier = "DAILY_NOTIFICATION"
|
|
let category = UNNotificationCategory(
|
|
identifier: "DAILY_NOTIFICATION",
|
|
actions: [],
|
|
intentIdentifiers: [],
|
|
options: .customDismissAction
|
|
)
|
|
notificationCenter.setNotificationCategories([category])
|
|
|
|
// Create trigger for daily notification
|
|
var dateComponents = DateComponents()
|
|
dateComponents.hour = hour
|
|
dateComponents.minute = minute
|
|
let trigger = UNCalendarNotificationTrigger(dateMatching: dateComponents, repeats: true)
|
|
|
|
// Add check for past time and adjust to next day
|
|
let calendar = Calendar.current
|
|
var components = DateComponents()
|
|
components.hour = hour
|
|
components.minute = minute
|
|
components.second = 0
|
|
|
|
if let date = calendar.date(from: components),
|
|
date.timeIntervalSinceNow <= 0 {
|
|
components.day = calendar.component(.day, from: Date()) + 1
|
|
}
|
|
|
|
// Create request
|
|
let identifier = String(format: "daily-notification-%d", (url as NSString).hash)
|
|
content.userInfo = ["url": url]
|
|
let request = UNNotificationRequest(
|
|
identifier: identifier,
|
|
content: content,
|
|
trigger: trigger
|
|
)
|
|
|
|
// Schedule notification
|
|
notificationCenter.add(request) { error in
|
|
if let error = error {
|
|
call.reject("Failed to schedule notification: \(error.localizedDescription)")
|
|
} else {
|
|
call.resolve()
|
|
}
|
|
}
|
|
}
|
|
|
|
@objc func getLastNotification(_ call: CAPPluginCall) {
|
|
notificationCenter.getDeliveredNotifications { notifications in
|
|
let lastNotification = notifications.first
|
|
let result: [String: Any] = [
|
|
"id": lastNotification?.request.identifier ?? "",
|
|
"title": lastNotification?.request.content.title ?? "",
|
|
"body": lastNotification?.request.content.body ?? "",
|
|
"timestamp": lastNotification?.date.timeIntervalSince1970 ?? 0
|
|
]
|
|
call.resolve(result)
|
|
}
|
|
}
|
|
|
|
@objc func cancelAllNotifications(_ call: CAPPluginCall) {
|
|
notificationCenter.removeAllPendingNotificationRequests()
|
|
notificationCenter.removeAllDeliveredNotifications()
|
|
call.resolve()
|
|
}
|
|
|
|
@objc func getNotificationStatus(_ call: CAPPluginCall) {
|
|
notificationCenter.getNotificationSettings { settings in
|
|
self.notificationCenter.getPendingNotificationRequests { requests in
|
|
var result: [String: Any] = [
|
|
"isEnabled": settings.authorizationStatus == .authorized,
|
|
"pending": requests.count
|
|
]
|
|
|
|
if let nextRequest = requests.first,
|
|
let trigger = nextRequest.trigger as? UNCalendarNotificationTrigger {
|
|
result["nextNotificationTime"] = trigger.nextTriggerDate()?.timeIntervalSince1970 ?? 0
|
|
}
|
|
|
|
// Add current settings
|
|
result["settings"] = self.settings
|
|
|
|
call.resolve(result)
|
|
}
|
|
}
|
|
}
|
|
|
|
@objc func updateSettings(_ call: CAPPluginCall) {
|
|
if let sound = call.getBool("sound") {
|
|
settings["sound"] = sound
|
|
}
|
|
|
|
if let priority = call.getString("priority") {
|
|
guard ["high", "default", "low"].contains(priority) else {
|
|
call.reject("Invalid priority value")
|
|
return
|
|
}
|
|
settings["priority"] = priority
|
|
}
|
|
|
|
if let timezone = call.getString("timezone") {
|
|
guard TimeZone(identifier: timezone) != nil else {
|
|
call.reject("Invalid timezone")
|
|
return
|
|
}
|
|
settings["timezone"] = timezone
|
|
}
|
|
|
|
// Update any existing notifications with new settings
|
|
notificationCenter.getPendingNotificationRequests { [weak self] requests in
|
|
guard let self = self else { return }
|
|
|
|
for request in requests {
|
|
let content = request.content.mutableCopy() as! UNMutableNotificationContent
|
|
|
|
// Update notification content based on new settings
|
|
content.sound = self.settings["sound"] as! Bool ? .default : nil
|
|
|
|
if let priority = self.settings["priority"] as? String {
|
|
if #available(iOS 15.0, *) {
|
|
switch priority {
|
|
case "high": content.interruptionLevel = .timeSensitive
|
|
case "low": content.interruptionLevel = .passive
|
|
default: content.interruptionLevel = .active
|
|
}
|
|
}
|
|
}
|
|
|
|
let newRequest = UNNotificationRequest(
|
|
identifier: request.identifier,
|
|
content: content,
|
|
trigger: request.trigger
|
|
)
|
|
|
|
self.notificationCenter.add(newRequest)
|
|
}
|
|
}
|
|
|
|
call.resolve(settings)
|
|
}
|
|
|
|
@objc public override func checkPermissions(_ call: CAPPluginCall) {
|
|
notificationCenter.getNotificationSettings { settings in
|
|
var result: [String: Any] = [:]
|
|
|
|
// Convert authorization status
|
|
switch settings.authorizationStatus {
|
|
case .authorized:
|
|
result["status"] = "granted"
|
|
case .denied:
|
|
result["status"] = "denied"
|
|
case .provisional:
|
|
result["status"] = "provisional"
|
|
case .ephemeral:
|
|
result["status"] = "ephemeral"
|
|
default:
|
|
result["status"] = "unknown"
|
|
}
|
|
|
|
// Add detailed settings
|
|
result["alert"] = settings.alertSetting == .enabled
|
|
result["badge"] = settings.badgeSetting == .enabled
|
|
result["sound"] = settings.soundSetting == .enabled
|
|
result["lockScreen"] = settings.lockScreenSetting == .enabled
|
|
result["carPlay"] = settings.carPlaySetting == .enabled
|
|
|
|
call.resolve(result)
|
|
}
|
|
}
|
|
|
|
@objc public override func requestPermissions(_ call: CAPPluginCall) {
|
|
let options: UNAuthorizationOptions = [.alert, .sound, .badge]
|
|
|
|
notificationCenter.requestAuthorization(options: options) { granted, error in
|
|
if let error = error {
|
|
call.reject("Failed to request permissions: \(error.localizedDescription)")
|
|
return
|
|
}
|
|
|
|
call.resolve([
|
|
"granted": granted
|
|
])
|
|
}
|
|
}
|
|
|
|
public override func load() {
|
|
notificationCenter.delegate = self
|
|
}
|
|
|
|
private func isValidTime(_ time: String) -> Bool {
|
|
let timeComponents = time.split(separator: ":")
|
|
guard timeComponents.count == 2,
|
|
let hour = Int(timeComponents[0]),
|
|
let minute = Int(timeComponents[1]) else {
|
|
return false
|
|
}
|
|
return hour >= 0 && hour < 24 && minute >= 0 && minute < 60
|
|
}
|
|
|
|
private func isValidTimezone(_ identifier: String) -> Bool {
|
|
return TimeZone(identifier: identifier) != nil
|
|
}
|
|
|
|
private func cleanupOldNotifications() {
|
|
let cutoffDate = Date().addingTimeInterval(-Double(DailyNotificationConfig.shared.retentionDays * 24 * 60 * 60))
|
|
notificationCenter.getDeliveredNotifications { notifications in
|
|
let oldNotifications = notifications.filter { $0.date < cutoffDate }
|
|
self.notificationCenter.removeDeliveredNotifications(withIdentifiers: oldNotifications.map { $0.request.identifier })
|
|
}
|
|
}
|
|
|
|
private func setupNotificationChannel() {
|
|
// iOS doesn't use notification channels like Android
|
|
// This method is kept for API compatibility
|
|
}
|
|
}
|
|
|
|
extension DailyNotificationPlugin: UNUserNotificationCenterDelegate {
|
|
public func userNotificationCenter(
|
|
_ center: UNUserNotificationCenter,
|
|
didReceive response: UNNotificationResponse,
|
|
withCompletionHandler completionHandler: @escaping () -> Void
|
|
) {
|
|
let notification = response.notification
|
|
let userInfo = notification.request.content.userInfo
|
|
|
|
// Create notification event data
|
|
let eventData: [String: Any] = [
|
|
"id": notification.request.identifier,
|
|
"title": notification.request.content.title,
|
|
"body": notification.request.content.body,
|
|
"action": response.actionIdentifier,
|
|
"data": userInfo
|
|
]
|
|
|
|
// Notify JavaScript
|
|
notifyListeners("notification", data: eventData)
|
|
|
|
completionHandler()
|
|
}
|
|
|
|
// Handle notifications when app is in foreground
|
|
public func userNotificationCenter(
|
|
_ center: UNUserNotificationCenter,
|
|
willPresent notification: UNNotification,
|
|
withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void
|
|
) {
|
|
var presentationOptions: UNNotificationPresentationOptions = []
|
|
if #available(iOS 14.0, *) {
|
|
presentationOptions = [.banner, .sound, .badge]
|
|
} else {
|
|
presentationOptions = [.alert, .sound, .badge]
|
|
}
|
|
completionHandler(presentationOptions)
|
|
}
|
|
}
|