TheNoiseClock/TheNoiseClock/Services/AlarmService.swift

128 lines
3.8 KiB
Swift

//
// AlarmService.swift
// TheNoiseClock
//
// Created by Matt Bruce on 9/7/25.
//
import Foundation
import UserNotifications
import Observation
/// Service for managing alarms and notifications
@Observable
class AlarmService {
// MARK: - Properties
private(set) var alarms: [Alarm] = []
private var alarmLookup: [UUID: Int] = [:]
private var persistenceWorkItem: DispatchWorkItem?
private let focusModeService = FocusModeService.shared
// MARK: - Initialization
init() {
loadAlarms()
Task {
// Request permissions through FocusModeService for better compatibility
let granted = await focusModeService.requestNotificationPermissions()
if !granted {
// Fallback to original method
_ = await NotificationUtils.requestPermissions()
}
}
}
// MARK: - Public Interface
func addAlarm(_ alarm: Alarm) {
alarms.append(alarm)
updateAlarmLookup()
scheduleNotification(for: alarm)
saveAlarms()
}
func updateAlarm(_ alarm: Alarm) {
guard let index = alarmLookup[alarm.id] else { return }
alarms[index] = alarm
updateAlarmLookup()
scheduleNotification(for: alarm)
saveAlarms()
}
func deleteAlarm(id: UUID) {
alarms.removeAll { $0.id == id }
updateAlarmLookup()
NotificationUtils.removeNotification(identifier: id.uuidString)
saveAlarms()
}
func toggleAlarm(id: UUID) {
guard let index = alarmLookup[id] else { return }
alarms[index].isEnabled.toggle()
scheduleNotification(for: alarms[index])
saveAlarms()
}
func getAlarm(id: UUID) -> Alarm? {
return alarms.first { $0.id == id }
}
// MARK: - Private Methods
private func updateAlarmLookup() {
alarmLookup.removeAll()
for (index, alarm) in alarms.enumerated() {
alarmLookup[alarm.id] = index
}
}
private func scheduleNotification(for alarm: Alarm) {
// Remove existing notification
NotificationUtils.removeNotification(identifier: alarm.id.uuidString)
// Schedule new notification if enabled
if alarm.isEnabled {
Task {
// Use FocusModeService for better Focus mode compatibility
focusModeService.scheduleAlarmNotification(
identifier: alarm.id.uuidString,
title: "Wake Up!",
body: "Your alarm is ringing.",
date: alarm.time,
soundName: alarm.soundName,
repeats: false // For now, set to false since Alarm model doesn't have repeatDays
)
}
}
}
private func saveAlarms() {
persistenceWorkItem?.cancel()
let alarmsSnapshot = self.alarms
let work = DispatchWorkItem {
if let encoded = try? JSONEncoder().encode(alarmsSnapshot) {
UserDefaults.standard.set(encoded, forKey: AppConstants.StorageKeys.savedAlarms)
}
}
persistenceWorkItem = work
DispatchQueue.main.asyncAfter(
deadline: .now() + AppConstants.PersistenceDelays.alarms,
execute: work
)
}
private func loadAlarms() {
if let savedAlarms = UserDefaults.standard.data(forKey: AppConstants.StorageKeys.savedAlarms),
let decodedAlarms = try? JSONDecoder().decode([Alarm].self, from: savedAlarms) {
alarms = decodedAlarms
updateAlarmLookup()
// Reschedule all enabled alarms
for alarm in alarms where alarm.isEnabled {
scheduleNotification(for: alarm)
}
}
}
}