import WidgetKit import SwiftUI import SharedModels struct DailyProgressEntry: TimelineEntry { let date: Date let data: WidgetData } struct DailyProgressProvider: TimelineProvider { func placeholder(in context: Context) -> DailyProgressEntry { DailyProgressEntry(date: Date(), data: .placeholder) } func getSnapshot(in context: Context, completion: @escaping (DailyProgressEntry) -> Void) { if context.isPreview { completion(DailyProgressEntry(date: Date(), data: .placeholder)) return } completion(DailyProgressEntry(date: Date(), data: WidgetDataReader.read())) } func getTimeline(in context: Context, completion: @escaping (Timeline) -> Void) { let data = WidgetDataReader.read() var entries: [DailyProgressEntry] = [] let now = Date() for offset in 0..<8 { let date = Calendar.current.date(byAdding: .minute, value: offset * 15, to: now)! entries.append(DailyProgressEntry(date: date, data: data)) } completion(Timeline(entries: entries, policy: .atEnd)) } } struct DailyProgressWidget: Widget { let kind = "DailyProgressWidget" var body: some WidgetConfiguration { StaticConfiguration(kind: kind, provider: DailyProgressProvider()) { entry in DailyProgressWidgetView(entry: entry) .containerBackground(.fill.tertiary, for: .widget) } .configurationDisplayName("Daily Progress") .description("Track your daily practice goal and streak.") .supportedFamilies([.systemSmall]) } } struct DailyProgressWidgetView: View { let entry: DailyProgressEntry var body: some View { VStack(spacing: 8) { // Progress ring ZStack { Circle() .stroke(.quaternary, lineWidth: 6) Circle() .trim(from: 0, to: entry.data.progressPercent) .stroke(.orange, style: StrokeStyle(lineWidth: 6, lineCap: .round)) .rotationEffect(.degrees(-90)) VStack(spacing: 0) { Text("\(entry.data.todayCount)") .font(.title2.bold().monospacedDigit()) Text("/\(entry.data.dailyGoal)") .font(.caption2) .foregroundStyle(.secondary) } } .frame(width: 70, height: 70) // Streak if entry.data.currentStreak > 0 { HStack(spacing: 3) { Image(systemName: "flame.fill") .foregroundStyle(.orange) .font(.caption2) Text("\(entry.data.currentStreak)d") .font(.caption2.bold()) } } // Due cards if entry.data.dueCardCount > 0 { Text("\(entry.data.dueCardCount) due") .font(.caption2) .foregroundStyle(.secondary) } } } } #Preview(as: .systemSmall) { DailyProgressWidget() } timeline: { DailyProgressEntry(date: Date(), data: .placeholder) }