180 lines
5.5 KiB
Swift
180 lines
5.5 KiB
Swift
//
|
|
// 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<NoiseClockAlarmMetadata>.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<NoiseClockAlarmMetadata>
|
|
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<NoiseClockAlarmMetadata>
|
|
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)
|
|
}
|
|
}
|
|
|