Signed-off-by: Matt Bruce <mbrucedogs@gmail.com>

This commit is contained in:
Matt Bruce 2025-09-08 16:08:19 -05:00
parent 6322f1cbd3
commit bf489878dd
4 changed files with 189 additions and 1 deletions

View File

@ -11,6 +11,12 @@ import SwiftUI
@main
struct TheNoiseClockApp: App {
// MARK: - Initialization
init() {
// Initialize notification delegate to handle snooze actions
_ = NotificationDelegate.shared
}
// MARK: - Body
var body: some Scene {
WindowGroup {

View File

@ -70,7 +70,7 @@ class FocusModeService {
UNNotificationAction(
identifier: "SNOOZE_ACTION",
title: "Snooze",
options: [.foreground]
options: []
),
UNNotificationAction(
identifier: "STOP_ACTION",

View 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)")
}
}
}

View File

@ -29,6 +29,9 @@ class AlarmViewModel {
notificationService: NotificationService = NotificationService()) {
self.alarmService = alarmService
self.notificationService = notificationService
// Register alarm service with notification delegate for snooze handling
NotificationDelegate.shared.setAlarmService(alarmService)
}
// MARK: - Public Interface