// // AlarmLiveActivityWidget.swift // TheNoiseClockWidget // // Created by Matt Bruce on 2/2/26. // import AlarmKit import SwiftUI import WidgetKit /// Live Activity widget for alarm countdown and alerting states. /// Uses AlarmKit's AlarmAttributes for automatic countdown management. struct AlarmLiveActivityWidget: Widget { var body: some WidgetConfiguration { ActivityConfiguration(for: AlarmAttributes.self) { context in // Lock Screen presentation LockScreenAlarmView( attributes: context.attributes, state: context.state ) } dynamicIsland: { context in DynamicIsland { // Expanded regions DynamicIslandExpandedRegion(.leading) { if let metadata = context.attributes.metadata { AlarmTitleView(metadata: metadata) } else { Text("Alarm") .font(.caption) } } DynamicIslandExpandedRegion(.trailing) { AlarmProgressView(state: context.state) } DynamicIslandExpandedRegion(.bottom) { ExpandedAlarmView( attributes: context.attributes, state: context.state ) } } compactLeading: { // Compact leading - countdown text CountdownTextView(state: context.state) .foregroundStyle(context.attributes.tintColor) } compactTrailing: { // Compact trailing - progress ring AlarmProgressView(state: context.state) .frame(maxWidth: 32) } minimal: { // Minimal - just an alarm icon Image(systemName: "alarm.fill") .foregroundStyle(context.attributes.tintColor) } } } } // MARK: - Lock Screen View struct LockScreenAlarmView: View { let attributes: AlarmAttributes let state: AlarmPresentationState private var alarmLabel: String { attributes.metadata?.label ?? "Alarm" } var body: some View { VStack(spacing: 12) { // Alarm label Text(alarmLabel) .font(.headline) .foregroundStyle(.primary) // Countdown state if case .countdown(let countdown) = state.mode { VStack(spacing: 4) { Text("Alarm in") .font(.caption) .foregroundStyle(.secondary) Text(timerInterval: Date.now...countdown.fireDate, countsDown: true) .font(.system(size: 48, weight: .bold, design: .rounded)) .monospacedDigit() .foregroundStyle(attributes.tintColor) } } else if case .paused = state.mode { Text("Paused") .font(.title3) .foregroundStyle(.secondary) } else { // Other states (alerting, etc.) VStack(spacing: 4) { Image(systemName: "alarm.waves.left.and.right.fill") .font(.system(size: 32)) .foregroundStyle(attributes.tintColor) .symbolEffect(.pulse) Text("Alarm Ringing") .font(.title3.weight(.semibold)) } } } .padding() .frame(maxWidth: .infinity) } } // MARK: - Expanded Dynamic Island View struct ExpandedAlarmView: View { let attributes: AlarmAttributes let state: AlarmPresentationState var body: some View { HStack { CountdownTextView(state: state) .font(.headline) Spacer() AlarmProgressView(state: state) .frame(maxHeight: 30) } } } // MARK: - Countdown Text View struct CountdownTextView: View { let state: AlarmPresentationState var body: some View { if case .countdown(let countdown) = state.mode { Text(timerInterval: Date.now...countdown.fireDate, countsDown: true) .monospacedDigit() .lineLimit(1) } else if case .paused = state.mode { Text("Paused") } else { Text("Now!") .fontWeight(.bold) } } } // MARK: - Progress View struct AlarmProgressView: View { let state: AlarmPresentationState var body: some View { if case .countdown(let countdown) = state.mode { ProgressView( timerInterval: Date.now...countdown.fireDate, label: { EmptyView() }, currentValueLabel: { Text("") } ) .progressViewStyle(.circular) } else if case .paused = state.mode { Image(systemName: "pause.fill") } else { Image(systemName: "alarm.waves.left.and.right.fill") .symbolEffect(.pulse) } } } // MARK: - Title View struct AlarmTitleView: View { let metadata: NoiseClockAlarmMetadata var body: some View { Text(metadata.label) .font(.caption) .lineLimit(1) } }