diff --git a/ios/App/App/CoachWidgetSnapshot.swift b/ios/App/App/CoachWidgetSnapshot.swift index 6bc8525..2783a14 100644 --- a/ios/App/App/CoachWidgetSnapshot.swift +++ b/ios/App/App/CoachWidgetSnapshot.swift @@ -53,6 +53,19 @@ struct CoachWidgetSnapshot: Codable { var waterMlToday: Double? var coffeeCountToday: Int? + /// Énergie du jour et objectif, poussés par l'app depuis le journal. + /// `kcalGoal` vient des objectifs nutritionnels réels (1950 au 06/08/2026) : + /// il n'est pas écrit en dur ici, pour qu'un changement d'objectif suive. + var kcalToday: Double? + var kcalGoal: Double? + + /// Objectif d'hydratation, en millilitres. **Optionnel et sans valeur par + /// défaut** : aucun objectif d'hydratation n'est défini dans les objectifs + /// nutritionnels. Tant qu'il vaut nil, le widget affiche le volume bu sans + /// barre — une jauge sur un objectif inventé donnerait un pourcentage qui ne + /// veut rien dire. + var waterGoalMl: Double? + /// Séance planifiée du jour (nil = jour OFF / pas de séance). struct TodaySession: Codable { var sport: String // running, cycling, strength, mobility, hiking, rest… diff --git a/ios/App/CoachLiveActivity/CoachQuickWidget.swift b/ios/App/CoachLiveActivity/CoachQuickWidget.swift index 9e78fea..1b260d5 100644 --- a/ios/App/CoachLiveActivity/CoachQuickWidget.swift +++ b/ios/App/CoachLiveActivity/CoachQuickWidget.swift @@ -60,11 +60,17 @@ struct CoachQuickEntryTimeline: TimelineEntry { let waterMl: Double let coffeeCount: Int let pending: Int + let kcal: Double? + let kcalGoal: Double? + let waterGoal: Double? } struct CoachQuickProvider: TimelineProvider { func placeholder(in context: Context) -> CoachQuickEntryTimeline { - CoachQuickEntryTimeline(date: Date(), waterMl: 1250, coffeeCount: 2, pending: 0) + CoachQuickEntryTimeline( + date: Date(), waterMl: 1250, coffeeCount: 2, pending: 0, + kcal: 1420, kcalGoal: 1950, waterGoal: 2000 + ) } func getSnapshot(in context: Context, completion: @escaping (CoachQuickEntryTimeline) -> Void) { @@ -91,7 +97,10 @@ struct CoachQuickProvider: TimelineProvider { date: Date(), waterMl: CoachQuickLog.todayTotal(kind: "water", syncedMl: syncedWater), coffeeCount: CoachQuickLog.todayCount(kind: "coffee", syncedCount: syncedCoffee), - pending: CoachQuickLog.pending().count + pending: CoachQuickLog.pending().count, + kcal: snapshot?.kcalToday, + kcalGoal: snapshot?.kcalGoal, + waterGoal: snapshot?.waterGoalMl ) } } @@ -117,10 +126,44 @@ struct CoachQuickWidgetView: View { .lineLimit(1) .minimumScaleFactor(0.8) + // Deux jauges : hydratation et énergie. La barre n'apparaît que si + // un objectif existe — une jauge sur un objectif inventé donnerait + // un pourcentage qui ne veut rien dire. + VStack(alignment: .leading, spacing: 3) { + HStack(spacing: 6) { + Text("\(litres) L") + .font(.system(.subheadline, design: .rounded).weight(.semibold)) + .monospacedDigit() + if let goal = entry.waterGoal, goal > 0 { + ProgressView(value: min(entry.waterMl / goal, 1)) + .tint(.blue) + Text("\(Int(goal / 1000)) L") + .font(.caption2).foregroundStyle(.tertiary).monospacedDigit() + } else { + Text("bue aujourd'hui") + .font(.caption2).foregroundStyle(.secondary) + Spacer() + } + } + + if let kcal = entry.kcal { + HStack(spacing: 6) { + Text("\(Int(kcal)) kcal") + .font(.system(.subheadline, design: .rounded).weight(.semibold)) + .monospacedDigit() + if let goal = entry.kcalGoal, goal > 0 { + ProgressView(value: min(kcal / goal, 1)) + .tint(kcal > goal ? .orange : .green) + Text("\(Int(goal))") + .font(.caption2).foregroundStyle(.tertiary).monospacedDigit() + } else { + Spacer() + } + } + } + } + HStack(spacing: 6) { - Text("\(litres) L") - .font(.system(.title3, design: .rounded).weight(.semibold)) - .monospacedDigit() Text("· \(entry.coffeeCount) café\(entry.coffeeCount > 1 ? "s" : "")") .font(.caption) .foregroundStyle(.secondary)