// ── Lock Screen Widget ──────────────────────────────────────── // Lock screen inline/circular/rectangular widgets for next timer import SwiftUI import WidgetKit import AppIntents // MARK: - Timeline Provider struct LockScreenTimerProvider: AppIntentTimelineProvider { typealias Entry = LockScreenTimerEntry typealias Intent = LockScreenTimerIntent func placeholder(in context: Context) -> LockScreenTimerEntry { LockScreenTimerEntry( date: Date(), label: "Standup", targetTime: Date().addingTimeInterval(3600), urgency: .important, hasTimer: true ) } func snapshot(for configuration: LockScreenTimerIntent, in context: Context) async -> LockScreenTimerEntry { entryFromSharedData() } func timeline(for configuration: LockScreenTimerIntent, in context: Context) async -> Timeline { let entry = entryFromSharedData() let now = Date() var entries: [LockScreenTimerEntry] = [] for minuteOffset in stride(from: 0, through: 30, by: 5) { let entryDate = Calendar.current.date(byAdding: .minute, value: minuteOffset, to: now)! entries.append(LockScreenTimerEntry( 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(30 * 60)) } else { reloadDate = now.addingTimeInterval(30 * 60) } return Timeline(entries: entries, policy: .after(reloadDate)) } private func entryFromSharedData() -> LockScreenTimerEntry { if let timer = SharedTimerDataManager.shared.readNextFiringTimer() { return LockScreenTimerEntry( date: Date(), label: timer.label, targetTime: timer.targetTime, urgency: timer.urgency, hasTimer: true ) } return LockScreenTimerEntry( date: Date(), label: "", targetTime: Date(), urgency: .standard, hasTimer: false ) } } // MARK: - Intent struct LockScreenTimerIntent: WidgetConfigurationIntent { static var title: LocalizedStringResource = "Lock Screen Timer" static var description: IntentDescription = "Shows next timer countdown on lock screen" } // MARK: - Entry struct LockScreenTimerEntry: TimelineEntry { let date: Date let label: String let targetTime: Date let urgency: UrgencyLevel let hasTimer: Bool } // MARK: - Widget Views struct LockScreenInlineView: View { let entry: LockScreenTimerEntry var body: some View { if entry.hasTimer { HStack(spacing: 4) { Image(systemName: "clock.fill") Text(entry.label) Text(entry.targetTime, style: .timer) } } else { HStack(spacing: 4) { Image(systemName: "clock") Text("No timers") } } } } struct LockScreenCircularView: View { let entry: LockScreenTimerEntry var body: some View { if entry.hasTimer { ZStack { // Progress ring based on remaining time 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: 20)) } } } } struct LockScreenRectangularView: View { let entry: LockScreenTimerEntry var body: some View { if entry.hasTimer { HStack(spacing: 8) { VStack(alignment: .leading, spacing: 2) { Text(entry.label) .font(.system(size: 14, 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: 14, weight: .semibold)) Text("No active timers") .font(.system(size: 12)) .foregroundStyle(.secondary) } Spacer() } } } } // MARK: - Widget Definition // MARK: - Family Dispatcher struct LockScreenWidgetEntryView: View { @Environment(\.widgetFamily) var family let entry: LockScreenTimerEntry var body: some View { switch family { case .accessoryInline: LockScreenInlineView(entry: entry) case .accessoryCircular: LockScreenCircularView(entry: entry) case .accessoryRectangular: LockScreenRectangularView(entry: entry) default: LockScreenRectangularView(entry: entry) } } } // MARK: - Widget Definition struct NextTimerLockScreenWidget: Widget { let kind: String = "NextTimerLockScreenWidget" var body: some WidgetConfiguration { AppIntentConfiguration( kind: kind, intent: LockScreenTimerIntent.self, provider: LockScreenTimerProvider() ) { entry in LockScreenWidgetEntryView(entry: entry) .containerBackground(for: .widget) { } } .configurationDisplayName("Timer Countdown") .description("Shows next timer countdown on your lock screen") .supportedFamilies([ .accessoryInline, .accessoryCircular, .accessoryRectangular, ]) } }