import WidgetKit import SwiftUI struct HabitsEntry: TimelineEntry { let date: Date let completed: Int let total: Int let tasksCount: Int var progress: Double { total > 0 ? Double(completed) / Double(total) : 0 } } struct HabitsProvider: TimelineProvider { func placeholder(in context: Context) -> HabitsEntry { HabitsEntry(date: Date(), completed: 3, total: 5, tasksCount: 2) } func getSnapshot(in context: Context, completion: @escaping (HabitsEntry) -> Void) { completion(currentEntry()) } func getTimeline(in context: Context, completion: @escaping (Timeline) -> Void) { let entry = currentEntry() let next = Calendar.current.date(byAdding: .minute, value: 15, to: Date()) ?? Date() completion(Timeline(entries: [entry], policy: .after(next))) } private func currentEntry() -> HabitsEntry { HabitsEntry( date: Date(), completed: WidgetData.habitsCompleted, total: WidgetData.habitsTotal, tasksCount: WidgetData.tasksCount ) } } struct HabitsProgressWidget: Widget { let kind = "HabitsProgress" var body: some WidgetConfiguration { StaticConfiguration(kind: kind, provider: HabitsProvider()) { entry in HabitsWidgetView(entry: entry) .containerBackground(.fill.tertiary, for: .widget) } .configurationDisplayName("Прогресс дня") .description("Привычки и задачи на сегодня") .supportedFamilies([.systemSmall, .systemMedium]) } } struct HabitsWidgetView: View { let entry: HabitsEntry @Environment(\.widgetFamily) var family var body: some View { Group { if family == .systemMedium { mediumView } else { smallView } } .frame(maxWidth: .infinity, maxHeight: .infinity) .background(Color(hex: "06060f")) } var smallView: some View { VStack(spacing: 10) { ZStack { Circle().stroke(Color.white.opacity(0.1), lineWidth: 8).frame(width: 64, height: 64) Circle().trim(from: 0, to: entry.progress) .stroke(Color(hex: "0D9488"), style: StrokeStyle(lineWidth: 8, lineCap: .round)) .frame(width: 64, height: 64).rotationEffect(.degrees(-90)) VStack(spacing: 0) { Text("\(entry.completed)").font(.system(size: 20, weight: .bold, design: .rounded)).foregroundColor(.white) Text("/\(entry.total)").font(.system(size: 11)).foregroundColor(.gray) } } Text("Привычки").font(.caption2).foregroundColor(.gray) } } var mediumView: some View { HStack(spacing: 16) { ZStack { Circle().stroke(Color.white.opacity(0.1), lineWidth: 8).frame(width: 72, height: 72) Circle().trim(from: 0, to: entry.progress) .stroke(Color(hex: "0D9488"), style: StrokeStyle(lineWidth: 8, lineCap: .round)) .frame(width: 72, height: 72).rotationEffect(.degrees(-90)) VStack(spacing: 0) { Text("\(entry.completed)").font(.system(size: 22, weight: .bold, design: .rounded)).foregroundColor(.white) Text("/\(entry.total)").font(.system(size: 12)).foregroundColor(.gray) } } VStack(alignment: .leading, spacing: 8) { Text("Прогресс дня").font(.subheadline.bold()).foregroundColor(.white) HStack(spacing: 12) { Label("\(entry.completed) готово", systemImage: "checkmark.circle.fill").font(.caption2).foregroundColor(Color(hex: "0D9488")) Label("\(entry.tasksCount) задач", systemImage: "calendar").font(.caption2).foregroundColor(Color(hex: "6366f1")) } GeometryReader { geo in ZStack(alignment: .leading) { RoundedRectangle(cornerRadius: 3).fill(Color.white.opacity(0.1)) RoundedRectangle(cornerRadius: 3).fill(Color(hex: "0D9488")).frame(width: geo.size.width * entry.progress) } }.frame(height: 6) } }.padding(16) } } extension Color { init(hex: String) { let hex = hex.trimmingCharacters(in: CharacterSet.alphanumerics.inverted) var int: UInt64 = 0; Scanner(string: hex).scanHexInt64(&int) let r, g, b: UInt64 if hex.count == 6 { (r, g, b) = (int >> 16, int >> 8 & 0xFF, int & 0xFF) } else { (r, g, b) = (0, 0, 0) } self.init(.sRGB, red: Double(r)/255, green: Double(g)/255, blue: Double(b)/255) } }