128 lines
3.8 KiB
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)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|