158 lines
4.8 KiB
Swift
158 lines
4.8 KiB
Swift
//
|
|
// AlarmViewModel.swift
|
|
// TheNoiseClock
|
|
//
|
|
// Created by Matt Bruce on 9/7/25.
|
|
//
|
|
|
|
import AlarmKit
|
|
import Bedrock
|
|
import Foundation
|
|
import Observation
|
|
|
|
/// ViewModel for alarm management using AlarmKit (iOS 26+).
|
|
/// AlarmKit provides alarms that cut through Focus modes and silent mode,
|
|
/// with built-in Live Activity countdown and system alarm UI.
|
|
@Observable
|
|
class AlarmViewModel {
|
|
|
|
// MARK: - Properties
|
|
private let alarmService: AlarmService
|
|
private let alarmKitService = AlarmKitService.shared
|
|
|
|
/// Whether AlarmKit is authorized
|
|
var isAlarmKitAuthorized: Bool {
|
|
alarmKitService.authorizationState == .authorized
|
|
}
|
|
|
|
var alarms: [Alarm] {
|
|
alarmService.alarms
|
|
}
|
|
|
|
var systemSounds: [String] {
|
|
AppConstants.SystemSounds.availableSounds
|
|
}
|
|
|
|
// MARK: - Initialization
|
|
init(alarmService: AlarmService = AlarmService()) {
|
|
self.alarmService = alarmService
|
|
}
|
|
|
|
// MARK: - Authorization
|
|
|
|
/// Request AlarmKit authorization. Should be called during onboarding.
|
|
func requestAlarmKitAuthorization() async -> Bool {
|
|
return await alarmKitService.requestAuthorization()
|
|
}
|
|
|
|
// MARK: - Alarm CRUD Operations
|
|
|
|
func addAlarm(_ alarm: Alarm) async {
|
|
alarmService.addAlarm(alarm)
|
|
|
|
// Schedule with AlarmKit if alarm is enabled
|
|
if alarm.isEnabled {
|
|
Design.debugLog("[alarms] Scheduling AlarmKit alarm for \(alarm.label)")
|
|
do {
|
|
try await alarmKitService.scheduleAlarm(alarm)
|
|
} catch {
|
|
Design.debugLog("[alarms] AlarmKit scheduling failed: \(error)")
|
|
}
|
|
}
|
|
}
|
|
|
|
func updateAlarm(_ alarm: Alarm) async {
|
|
alarmService.updateAlarm(alarm)
|
|
|
|
// Cancel existing and reschedule if enabled
|
|
alarmKitService.cancelAlarm(id: alarm.id)
|
|
|
|
if alarm.isEnabled {
|
|
Design.debugLog("[alarms] Rescheduling AlarmKit alarm for \(alarm.label)")
|
|
do {
|
|
try await alarmKitService.scheduleAlarm(alarm)
|
|
} catch {
|
|
Design.debugLog("[alarms] AlarmKit rescheduling failed: \(error)")
|
|
}
|
|
}
|
|
}
|
|
|
|
func deleteAlarm(id: UUID) async {
|
|
// Cancel AlarmKit alarm first
|
|
alarmKitService.cancelAlarm(id: id)
|
|
|
|
// Then delete from storage
|
|
alarmService.deleteAlarm(id: id)
|
|
}
|
|
|
|
func toggleAlarm(id: UUID) async {
|
|
guard var alarm = alarmService.getAlarm(id: id) else { return }
|
|
|
|
alarm.isEnabled.toggle()
|
|
alarmService.updateAlarm(alarm)
|
|
|
|
// Schedule or cancel based on new state
|
|
if alarm.isEnabled {
|
|
Design.debugLog("[alarms] Enabling AlarmKit alarm \(alarm.label)")
|
|
do {
|
|
try await alarmKitService.scheduleAlarm(alarm)
|
|
} catch {
|
|
Design.debugLog("[alarms] AlarmKit scheduling failed: \(error)")
|
|
}
|
|
} else {
|
|
alarmKitService.cancelAlarm(id: id)
|
|
}
|
|
}
|
|
|
|
func getAlarm(id: UUID) -> Alarm? {
|
|
return alarmService.getAlarm(id: id)
|
|
}
|
|
|
|
func createNewAlarm(
|
|
time: Date,
|
|
soundName: String = AppConstants.SystemSounds.defaultSound,
|
|
label: String = "Alarm",
|
|
notificationMessage: String = "Your alarm is ringing",
|
|
snoozeDuration: Int = 9,
|
|
isVibrationEnabled: Bool = true,
|
|
isLightFlashEnabled: Bool = false,
|
|
volume: Float = 1.0
|
|
) -> Alarm {
|
|
return Alarm(
|
|
id: UUID(),
|
|
time: time,
|
|
isEnabled: true,
|
|
soundName: soundName,
|
|
label: label,
|
|
notificationMessage: notificationMessage,
|
|
snoozeDuration: snoozeDuration,
|
|
isVibrationEnabled: isVibrationEnabled,
|
|
isLightFlashEnabled: isLightFlashEnabled,
|
|
volume: volume
|
|
)
|
|
}
|
|
|
|
// MARK: - App Lifecycle
|
|
|
|
/// Reschedule all enabled alarms with AlarmKit.
|
|
/// Call this on app launch to ensure alarms are registered.
|
|
func rescheduleAllAlarms() async {
|
|
Design.debugLog("[alarmkit] ========== RESCHEDULING ALL ALARMS ==========")
|
|
|
|
let enabledAlarms = alarmService.getEnabledAlarms()
|
|
Design.debugLog("[alarmkit] Found \(enabledAlarms.count) enabled alarm(s)")
|
|
|
|
for alarm in enabledAlarms {
|
|
Design.debugLog("[alarmkit] Scheduling: \(alarm.label) at \(alarm.time)")
|
|
do {
|
|
try await alarmKitService.scheduleAlarm(alarm)
|
|
} catch {
|
|
Design.debugLog("[alarmkit] ❌ Failed to reschedule \(alarm.label): \(error)")
|
|
}
|
|
}
|
|
|
|
Design.debugLog("[alarmkit] ========== RESCHEDULING COMPLETE ==========")
|
|
alarmKitService.logCurrentAlarms()
|
|
}
|
|
}
|