Signed-off-by: Matt Bruce <mbrucedogs@gmail.com>
This commit is contained in:
parent
6322f1cbd3
commit
bf489878dd
@ -11,6 +11,12 @@ import SwiftUI
|
|||||||
@main
|
@main
|
||||||
struct TheNoiseClockApp: App {
|
struct TheNoiseClockApp: App {
|
||||||
|
|
||||||
|
// MARK: - Initialization
|
||||||
|
init() {
|
||||||
|
// Initialize notification delegate to handle snooze actions
|
||||||
|
_ = NotificationDelegate.shared
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - Body
|
// MARK: - Body
|
||||||
var body: some Scene {
|
var body: some Scene {
|
||||||
WindowGroup {
|
WindowGroup {
|
||||||
|
|||||||
@ -70,7 +70,7 @@ class FocusModeService {
|
|||||||
UNNotificationAction(
|
UNNotificationAction(
|
||||||
identifier: "SNOOZE_ACTION",
|
identifier: "SNOOZE_ACTION",
|
||||||
title: "Snooze",
|
title: "Snooze",
|
||||||
options: [.foreground]
|
options: []
|
||||||
),
|
),
|
||||||
UNNotificationAction(
|
UNNotificationAction(
|
||||||
identifier: "STOP_ACTION",
|
identifier: "STOP_ACTION",
|
||||||
|
|||||||
179
TheNoiseClock/Services/NotificationDelegate.swift
Normal file
179
TheNoiseClock/Services/NotificationDelegate.swift
Normal file
@ -0,0 +1,179 @@
|
|||||||
|
//
|
||||||
|
// 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
|
||||||
|
print("🔔 Notification delegate configured")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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
|
||||||
|
|
||||||
|
print("🔔 Notification action received: \(actionIdentifier)")
|
||||||
|
|
||||||
|
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:
|
||||||
|
print("🔔 Unknown action: \(actionIdentifier)")
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
print("❌ Could not find alarm for snooze action")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
print("🔔 Snoozing alarm: \(alarm.label) for \(alarm.snoozeDuration) minutes")
|
||||||
|
|
||||||
|
// Calculate snooze time (current time + snooze duration)
|
||||||
|
let snoozeTime = Date().addingTimeInterval(TimeInterval(alarm.snoozeDuration * 60))
|
||||||
|
print("🔔 Snooze time: \(snoozeTime)")
|
||||||
|
print("🔔 Current time: \(Date())")
|
||||||
|
|
||||||
|
// 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 {
|
||||||
|
print("❌ Could not find alarm ID for stop action")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
print("🔔 Stopping alarm: \(alarmId)")
|
||||||
|
|
||||||
|
// 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 {
|
||||||
|
print("❌ Could not find alarm ID for notification tap")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
print("🔔 Notification tapped for alarm: \(alarmId)")
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
print("🔔 Snooze notification scheduled for \(snoozeAlarm.time)")
|
||||||
|
} catch {
|
||||||
|
print("❌ Error scheduling snooze notification: \(error)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -29,6 +29,9 @@ class AlarmViewModel {
|
|||||||
notificationService: NotificationService = NotificationService()) {
|
notificationService: NotificationService = NotificationService()) {
|
||||||
self.alarmService = alarmService
|
self.alarmService = alarmService
|
||||||
self.notificationService = notificationService
|
self.notificationService = notificationService
|
||||||
|
|
||||||
|
// Register alarm service with notification delegate for snooze handling
|
||||||
|
NotificationDelegate.shared.setAlarmService(alarmService)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Public Interface
|
// MARK: - Public Interface
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user