// ── Next Timer Widget (Small) ───────────────────────────────── // Home screen small widget showing next timer + countdown import SwiftUI import WidgetKit // MARK: - Timeline Provider struct NextTimerProvider: AppIntentTimelineProvider { typealias Entry = NextTimerEntry typealias Intent = NextTimerIntent func placeholder(in context: Context) -> NextTimerEntry { NextTimerEntry( date: Date(), timer: TimerSnapshot( id: "placeholder", label: "Team Standup", type: .alarm, urgency: .important, state: .active, targetTime: Date().addingTimeInterval(3600), duration: 3600, startedAt: Date(), elapsedBeforePause: 0, snoozeCount: 0, category: nil, pomodoroCurrentRound: nil, pomodoroTotalRounds: nil, pomodoroIsBreak: nil, nextWarningTime: Date().addingTimeInterval(1800), totalWarnings: 3, firedWarnings: 1 ) ) } func snapshot(for configuration: NextTimerIntent, in context: Context) async -> NextTimerEntry { let timer = SharedTimerDataManager.shared.readNextFiringTimer() return NextTimerEntry(date: Date(), timer: timer) } func timeline(for configuration: NextTimerIntent, in context: Context) async -> Timeline { let timer = SharedTimerDataManager.shared.readNextFiringTimer() let now = Date() // Create entries every 5 minutes for the next 30 minutes var entries: [NextTimerEntry] = [] for minuteOffset in stride(from: 0, through: 30, by: 5) { let entryDate = Calendar.current.date(byAdding: .minute, value: minuteOffset, to: now)! entries.append(NextTimerEntry(date: entryDate, timer: timer)) } // Reload after 30 minutes or when timer fires let reloadDate: Date if let timer = timer { reloadDate = min(timer.targetTime, now.addingTimeInterval(30 * 60)) } else { reloadDate = now.addingTimeInterval(30 * 60) } return Timeline(entries: entries, policy: .after(reloadDate)) } } // MARK: - Intent (no config needed, just a placeholder for AppIntentTimelineProvider) import AppIntents struct NextTimerIntent: WidgetConfigurationIntent { static var title: LocalizedStringResource = "Next Timer" static var description: IntentDescription = "Shows your next upcoming timer" } // MARK: - Entry struct NextTimerEntry: TimelineEntry { let date: Date let timer: TimerSnapshot? } // MARK: - Widget View struct NextTimerWidgetView: View { @Environment(\.widgetFamily) var family let entry: NextTimerEntry var body: some View { if let timer = entry.timer { timerContent(timer) } else { emptyContent } } @ViewBuilder private func timerContent(_ timer: TimerSnapshot) -> some View { VStack(alignment: .leading, spacing: 6) { // Urgency indicator + label HStack(spacing: 4) { Circle() .fill(urgencyColor(timer.urgency)) .frame(width: 8, height: 8) Text(timer.label) .font(.system(size: 13, weight: .semibold)) .foregroundStyle(.primary) .lineLimit(1) .privacySensitive() } Spacer() // Countdown using SwiftUI Date-relative text if timer.state == .paused { Text("Paused") .font(.system(size: 28, weight: .bold, design: .monospaced)) .foregroundStyle(.secondary) } else { Text(timer.targetTime, style: .timer) .font(.system(size: 28, weight: .bold, design: .monospaced)) .foregroundStyle(.primary) .multilineTextAlignment(.leading) .privacySensitive() } // Target time Text(formatTime(timer.targetTime)) .font(.system(size: 11, weight: .medium)) .foregroundStyle(.secondary) } .padding() .containerBackground(for: .widget) { urgencyColor(timer.urgency).opacity(0.1) } .widgetURL(URL(string: "chronomind://timer/\(timer.id)")) } private var emptyContent: some View { VStack(spacing: 8) { Image(systemName: "clock") .font(.system(size: 28)) .foregroundStyle(.secondary) Text("No Timers") .font(.system(size: 13, weight: .medium)) .foregroundStyle(.secondary) Text("Tap to create one") .font(.system(size: 11)) .foregroundStyle(.tertiary) } .padding() .containerBackground(for: .widget) { Color(.systemBackground) } .widgetURL(URL(string: "chronomind://create")) } private func urgencyColor(_ urgency: UrgencyLevel) -> Color { switch urgency { case .critical: return Color(red: 1.0, green: 0.28, blue: 0.34) case .important: return Color(red: 1.0, green: 0.62, blue: 0.26) case .standard: return Color(red: 0.99, green: 0.79, blue: 0.34) case .gentle: return Color(red: 0.18, green: 0.84, blue: 0.45) case .passive: return Color(red: 0.36, green: 0.55, blue: 0.93) } } } // MARK: - Widget Definition struct NextTimerWidget: Widget { let kind: String = "NextTimerWidget" var body: some WidgetConfiguration { AppIntentConfiguration( kind: kind, intent: NextTimerIntent.self, provider: NextTimerProvider() ) { entry in NextTimerWidgetView(entry: entry) } .configurationDisplayName("Next Timer") .description("Shows your next upcoming timer with countdown") .supportedFamilies([.systemSmall]) } }