// ── Watch Complications ─────────────────────────────────────── // WidgetKit complications for Apple Watch faces import SwiftUI import WidgetKit // MARK: - Complication Timeline Provider struct WatchComplicationProvider: AppIntentTimelineProvider { typealias Entry = WatchComplicationEntry typealias Intent = WatchComplicationIntent func placeholder(in context: Context) -> WatchComplicationEntry { WatchComplicationEntry( date: Date(), label: "Standup", targetTime: Date().addingTimeInterval(3600), urgency: .important, hasTimer: true ) } func snapshot(for configuration: WatchComplicationIntent, in context: Context) async -> WatchComplicationEntry { entryFromSharedData() } func timeline(for configuration: WatchComplicationIntent, in context: Context) async -> Timeline { let entry = entryFromSharedData() let now = Date() var entries: [WatchComplicationEntry] = [] for minuteOffset in stride(from: 0, through: 30, by: 5) { let entryDate = Calendar.current.date(byAdding: .minute, value: minuteOffset, to: now)! entries.append(WatchComplicationEntry( date: entryDate, label: entry.label, targetTime: entry.targetTime, urgency: entry.urgency, hasTimer: entry.hasTimer )) } let reloadDate: Date if entry.hasTimer { reloadDate = min(entry.targetTime, now.addingTimeInterval(15 * 60)) } else { reloadDate = now.addingTimeInterval(15 * 60) } return Timeline(entries: entries, policy: .after(reloadDate)) } func recommendations() -> [AppIntentRecommendation] { [AppIntentRecommendation(intent: WatchComplicationIntent(), description: "Next Timer")] } private func entryFromSharedData() -> WatchComplicationEntry { if let timer = SharedTimerDataManager.shared.readNextFiringTimer() { return WatchComplicationEntry( date: Date(), label: timer.label, targetTime: timer.targetTime, urgency: timer.urgency, hasTimer: true ) } return WatchComplicationEntry( date: Date(), label: "", targetTime: Date(), urgency: .standard, hasTimer: false ) } } // MARK: - Intent import AppIntents struct WatchComplicationIntent: WidgetConfigurationIntent { static var title: LocalizedStringResource = "Timer Complication" static var description: IntentDescription = "Shows next timer on watch face" } // MARK: - Entry struct WatchComplicationEntry: TimelineEntry { let date: Date let label: String let targetTime: Date let urgency: UrgencyLevel let hasTimer: Bool } // MARK: - Circular Complication struct CircularComplicationView: View { let entry: WatchComplicationEntry var body: some View { if entry.hasTimer { ZStack { AccessoryWidgetBackground() VStack(spacing: 0) { Text(entry.targetTime, style: .timer) .font(.system(size: 14, weight: .bold, design: .monospaced)) .minimumScaleFactor(0.5) .widgetAccentable() } } } else { ZStack { AccessoryWidgetBackground() Image(systemName: "clock") .font(.system(size: 18)) } } } } // MARK: - Rectangular Complication struct RectangularComplicationView: View { let entry: WatchComplicationEntry var body: some View { if entry.hasTimer { HStack { VStack(alignment: .leading, spacing: 2) { Text(entry.label) .font(.system(size: 13, weight: .semibold)) .lineLimit(1) .widgetAccentable() Text(entry.targetTime, style: .timer) .font(.system(size: 12, weight: .medium, design: .monospaced)) Text(formatTime(entry.targetTime)) .font(.system(size: 10)) .foregroundStyle(.secondary) } Spacer() } } else { HStack { VStack(alignment: .leading, spacing: 2) { Text("ChronoMind") .font(.system(size: 13, weight: .semibold)) Text("No timers") .font(.system(size: 11)) .foregroundStyle(.secondary) } Spacer() } } } } // MARK: - Inline Complication struct InlineComplicationView: View { let entry: WatchComplicationEntry var body: some View { if entry.hasTimer { HStack(spacing: 4) { Image(systemName: "clock.fill") Text("\(entry.label) in") Text(entry.targetTime, style: .timer) } } else { HStack(spacing: 4) { Image(systemName: "clock") Text("No timers") } } } } // MARK: - Corner Complication struct CornerComplicationView: View { let entry: WatchComplicationEntry var body: some View { if entry.hasTimer { Text(entry.targetTime, style: .timer) .font(.system(size: 12, weight: .bold, design: .monospaced)) .widgetAccentable() .widgetLabel { Text(entry.label) } } else { Image(systemName: "clock") .widgetLabel { Text("ChronoMind") } } } } // MARK: - Family Dispatcher struct WatchComplicationEntryView: View { @Environment(\.widgetFamily) var family let entry: WatchComplicationEntry var body: some View { switch family { case .accessoryCircular: CircularComplicationView(entry: entry) case .accessoryRectangular: RectangularComplicationView(entry: entry) case .accessoryInline: InlineComplicationView(entry: entry) case .accessoryCorner: CornerComplicationView(entry: entry) default: CircularComplicationView(entry: entry) } } } // MARK: - Widget Definition struct WatchTimerComplication: Widget { let kind: String = "WatchTimerComplication" var body: some WidgetConfiguration { AppIntentConfiguration( kind: kind, intent: WatchComplicationIntent.self, provider: WatchComplicationProvider() ) { entry in WatchComplicationEntryView(entry: entry) .containerBackground(for: .widget) { } } .configurationDisplayName("Timer") .description("Shows next timer countdown on your watch face") .supportedFamilies([ .accessoryCircular, .accessoryRectangular, .accessoryInline, .accessoryCorner, ]) } }