// // WaterWidget.swift // WaterWidget // // Created by richard on 2025/9/9. // import WidgetKit import SwiftUI // Data model for water intake struct WaterData { let currentIntake: Int let targetIntake: Int let quickAddAmount: Int let progressPercentage: Double init(currentIntake: Int = 0, targetIntake: Int = 2000, quickAddAmount: Int = 150) { self.currentIntake = currentIntake self.targetIntake = targetIntake self.quickAddAmount = quickAddAmount self.progressPercentage = min(Double(currentIntake) / Double(targetIntake), 1.0) } } struct Provider: AppIntentTimelineProvider { func placeholder(in context: Context) -> SimpleEntry { SimpleEntry(date: Date(), configuration: ConfigurationAppIntent(), waterData: WaterData()) } func snapshot(for configuration: ConfigurationAppIntent, in context: Context) async -> SimpleEntry { // In a real app, you would fetch the actual water data here let waterData = await fetchWaterData() return SimpleEntry(date: Date(), configuration: configuration, waterData: waterData) } func timeline(for configuration: ConfigurationAppIntent, in context: Context) async -> Timeline { var entries: [SimpleEntry] = [] let currentDate = Date() // Fetch current water data let waterData = await fetchWaterData() // Generate timeline entries for every hour for the next 5 hours for hourOffset in 0 ..< 5 { let entryDate = Calendar.current.date(byAdding: .hour, value: hourOffset, to: currentDate)! let entry = SimpleEntry(date: entryDate, configuration: configuration, waterData: waterData) entries.append(entry) } // Refresh every 15 minutes to keep data current return Timeline(entries: entries, policy: .after(Calendar.current.date(byAdding: .minute, value: 15, to: currentDate)!)) } // Fetch water data from shared App Group storage private func fetchWaterData() async -> WaterData { guard let sharedDefaults = UserDefaults(suiteName: "group.com.anonymous.digitalpilates") else { print("Failed to access App Group UserDefaults") return WaterData() // Return default data } // Read data using the same keys as defined in widgetDataSync.ts let currentIntake = sharedDefaults.object(forKey: "widget_current_water_intake") as? Int ?? 0 let targetIntake = sharedDefaults.object(forKey: "widget_daily_water_goal") as? Int ?? 2000 let quickAddAmount = sharedDefaults.object(forKey: "widget_quick_add_amount") as? Int ?? 150 print("Widget data loaded - Current: \(currentIntake)ml, Target: \(targetIntake)ml, Quick: \(quickAddAmount)ml") return WaterData( currentIntake: currentIntake, targetIntake: targetIntake, quickAddAmount: quickAddAmount ) } } struct SimpleEntry: TimelineEntry { let date: Date let configuration: ConfigurationAppIntent let waterData: WaterData } struct WaterWidgetEntryView : View { var entry: Provider.Entry var body: some View { VStack(spacing: 0) { // Header with title and add button HStack { Text("喝水") .font(.system(size: 14, weight: .medium)) .foregroundColor(Color(red: 0.098, green: 0.129, blue: 0.149)) Spacer() // Quick add water button Button(intent: AddWaterIntent(amount: entry.waterData.quickAddAmount)) { HStack(spacing: 2) { Text("+") Text("\(entry.waterData.quickAddAmount)ml") } .font(.system(size: 10, weight: .bold)) .foregroundColor(Color(red: 0.388, green: 0.4, blue: 0.945)) .padding(.horizontal, 6) .padding(.vertical, 5) .background(Color(red: 0.882, green: 0.906, blue: 1.0)) .cornerRadius(16) } .buttonStyle(PlainButtonStyle()) } .padding(.bottom, 8) Spacer() // Progress visualization - simplified bar chart representation HStack(spacing: 2) { ForEach(0..<12, id: \.self) { index in RoundedRectangle(cornerRadius: 1) .fill(index < Int(entry.waterData.progressPercentage * 12) ? Color(red: 0.49, green: 0.827, blue: 0.988) : Color(red: 0.941, green: 0.976, blue: 1.0)) .frame(width: 3, height: 12) } } .padding(.bottom, 8) // Water intake stats HStack(alignment: .firstTextBaseline, spacing: 2) { Text("\(entry.waterData.currentIntake)") .font(.system(size: 14, weight: .semibold)) .foregroundColor(Color(red: 0.098, green: 0.129, blue: 0.149)) Text("ml") .font(.system(size: 14, weight: .semibold)) .foregroundColor(Color(red: 0.098, green: 0.129, blue: 0.149)) Text("/ \(entry.waterData.targetIntake)ml") .font(.system(size: 12)) .foregroundColor(Color(red: 0.42, green: 0.447, blue: 0.502)) } } .padding(16) .containerBackground(.fill.tertiary, for: .widget) } } struct WaterWidget: Widget { let kind: String = "WaterWidget" var body: some WidgetConfiguration { AppIntentConfiguration(kind: kind, intent: ConfigurationAppIntent.self, provider: Provider()) { entry in WaterWidgetEntryView(entry: entry) } .configurationDisplayName("饮水记录") .description("追踪你的每日饮水量,快速添加饮水记录") .supportedFamilies([.systemSmall]) } } extension ConfigurationAppIntent { fileprivate static var defaultConfig: ConfigurationAppIntent { return ConfigurationAppIntent() } } #Preview(as: .systemSmall) { WaterWidget() } timeline: { SimpleEntry(date: .now, configuration: .defaultConfig, waterData: WaterData(currentIntake: 500, targetIntake: 2000, quickAddAmount: 150)) SimpleEntry(date: .now, configuration: .defaultConfig, waterData: WaterData(currentIntake: 1200, targetIntake: 2000, quickAddAmount: 200)) SimpleEntry(date: .now, configuration: .defaultConfig, waterData: WaterData(currentIntake: 1800, targetIntake: 2000, quickAddAmount: 150)) }