180 lines
6.8 KiB
Swift
180 lines
6.8 KiB
Swift
//
|
|
// NotificationDelegate.swift
|
|
// TheNoiseClock
|
|
//
|
|
// Created by Matt Bruce on 9/8/25.
|
|
//
|
|
|
|
import UserNotifications
|
|
import Foundation
|
|
|
|
/// Delegate to handle notification actions (snooze, stop, etc.)
|
|
class NotificationDelegate: NSObject, UNUserNotificationCenterDelegate {
|
|
|
|
// MARK: - Singleton
|
|
static let shared = NotificationDelegate()
|
|
|
|
// MARK: - Properties
|
|
private var alarmService: AlarmService?
|
|
|
|
// MARK: - Initialization
|
|
private override init() {
|
|
super.init()
|
|
setupNotificationCenter()
|
|
}
|
|
|
|
// MARK: - Setup
|
|
private func setupNotificationCenter() {
|
|
UNUserNotificationCenter.current().delegate = self
|
|
DebugLogger.log("Notification delegate configured", category: .settings)
|
|
}
|
|
|
|
/// Set the alarm service instance (called from AlarmViewModel)
|
|
func setAlarmService(_ service: AlarmService) {
|
|
self.alarmService = service
|
|
}
|
|
|
|
// MARK: - UNUserNotificationCenterDelegate
|
|
|
|
/// Handle notification actions when app is in foreground
|
|
func userNotificationCenter(
|
|
_ center: UNUserNotificationCenter,
|
|
didReceive response: UNNotificationResponse,
|
|
withCompletionHandler completionHandler: @escaping () -> Void
|
|
) {
|
|
let actionIdentifier = response.actionIdentifier
|
|
let notification = response.notification
|
|
let userInfo = notification.request.content.userInfo
|
|
|
|
DebugLogger.log("Notification action received: \(actionIdentifier)", category: .settings)
|
|
|
|
switch actionIdentifier {
|
|
case "SNOOZE_ACTION":
|
|
handleSnoozeAction(userInfo: userInfo)
|
|
case "STOP_ACTION":
|
|
handleStopAction(userInfo: userInfo)
|
|
case UNNotificationDefaultActionIdentifier:
|
|
// User tapped the notification itself
|
|
handleNotificationTap(userInfo: userInfo)
|
|
default:
|
|
DebugLogger.log("Unknown action: \(actionIdentifier)", category: .settings)
|
|
}
|
|
|
|
completionHandler()
|
|
}
|
|
|
|
/// Handle notifications when app is in foreground
|
|
func userNotificationCenter(
|
|
_ center: UNUserNotificationCenter,
|
|
willPresent notification: UNNotification,
|
|
withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void
|
|
) {
|
|
// Show notification even when app is in foreground
|
|
completionHandler([.banner, .sound, .badge])
|
|
}
|
|
|
|
// MARK: - Action Handlers
|
|
|
|
private func handleSnoozeAction(userInfo: [AnyHashable: Any]) {
|
|
guard let alarmIdString = userInfo["alarmId"] as? String,
|
|
let alarmId = UUID(uuidString: alarmIdString),
|
|
let alarmService = self.alarmService,
|
|
let alarm = alarmService.getAlarm(id: alarmId) else {
|
|
DebugLogger.log("Could not find alarm for snooze action", category: .general)
|
|
return
|
|
}
|
|
|
|
DebugLogger.log("Snoozing alarm: \(alarm.label) for \(alarm.snoozeDuration) minutes", category: .settings)
|
|
|
|
// Calculate snooze time (current time + snooze duration)
|
|
let snoozeTime = Date().addingTimeInterval(TimeInterval(alarm.snoozeDuration * 60))
|
|
DebugLogger.log("Snooze time: \(snoozeTime)", category: .settings)
|
|
DebugLogger.log("Current time: \(Date())", category: .settings)
|
|
|
|
// Create a temporary alarm for the snooze
|
|
let snoozeAlarm = Alarm(
|
|
id: UUID(), // New ID for snooze alarm
|
|
time: snoozeTime,
|
|
isEnabled: true,
|
|
soundName: alarm.soundName,
|
|
label: "\(alarm.label) (Snoozed)",
|
|
notificationMessage: "Snoozed: \(alarm.notificationMessage)",
|
|
snoozeDuration: alarm.snoozeDuration,
|
|
isVibrationEnabled: alarm.isVibrationEnabled,
|
|
isLightFlashEnabled: alarm.isLightFlashEnabled,
|
|
volume: alarm.volume
|
|
)
|
|
|
|
// Schedule the snooze notification
|
|
Task {
|
|
await scheduleSnoozeNotification(snoozeAlarm, userInfo: userInfo)
|
|
}
|
|
}
|
|
|
|
private func handleStopAction(userInfo: [AnyHashable: Any]) {
|
|
guard let alarmIdString = userInfo["alarmId"] as? String,
|
|
let alarmId = UUID(uuidString: alarmIdString) else {
|
|
DebugLogger.log("Could not find alarm ID for stop action", category: .general)
|
|
return
|
|
}
|
|
|
|
DebugLogger.log("Stopping alarm: \(alarmId)", category: .settings)
|
|
|
|
// Cancel any pending notifications for this alarm
|
|
UNUserNotificationCenter.current().removePendingNotificationRequests(withIdentifiers: [alarmIdString])
|
|
|
|
// If this was a snooze alarm, we don't want to disable the original alarm
|
|
// Just cancel the current notification
|
|
}
|
|
|
|
private func handleNotificationTap(userInfo: [AnyHashable: Any]) {
|
|
guard let alarmIdString = userInfo["alarmId"] as? String,
|
|
let alarmId = UUID(uuidString: alarmIdString) else {
|
|
DebugLogger.log("Could not find alarm ID for notification tap", category: .general)
|
|
return
|
|
}
|
|
|
|
DebugLogger.log("Notification tapped for alarm: \(alarmId)", category: .settings)
|
|
|
|
// For now, just log the tap. In the future, this could open the alarm details
|
|
// or perform some other action when the user taps the notification
|
|
}
|
|
|
|
// MARK: - Private Methods
|
|
|
|
private func scheduleSnoozeNotification(_ snoozeAlarm: Alarm, userInfo: [AnyHashable: Any]) async {
|
|
let content = UNMutableNotificationContent()
|
|
content.title = snoozeAlarm.label
|
|
content.body = snoozeAlarm.notificationMessage
|
|
content.sound = UNNotificationSound(named: UNNotificationSoundName(rawValue: snoozeAlarm.soundName))
|
|
content.categoryIdentifier = "ALARM_CATEGORY"
|
|
content.userInfo = [
|
|
"alarmId": snoozeAlarm.id.uuidString,
|
|
"soundName": snoozeAlarm.soundName,
|
|
"isSnooze": true,
|
|
"originalAlarmId": userInfo["alarmId"] as? String ?? ""
|
|
]
|
|
|
|
// Create trigger for snooze time
|
|
let trigger = UNTimeIntervalNotificationTrigger(
|
|
timeInterval: snoozeAlarm.time.timeIntervalSinceNow,
|
|
repeats: false
|
|
)
|
|
|
|
// Create request
|
|
let request = UNNotificationRequest(
|
|
identifier: snoozeAlarm.id.uuidString,
|
|
content: content,
|
|
trigger: trigger
|
|
)
|
|
|
|
// Schedule notification
|
|
do {
|
|
try await UNUserNotificationCenter.current().add(request)
|
|
DebugLogger.log("Snooze notification scheduled for \(snoozeAlarm.time)", category: .settings)
|
|
} catch {
|
|
DebugLogger.log("Error scheduling snooze notification: \(error)", category: .general)
|
|
}
|
|
}
|
|
}
|