Jauges d'hydratation et de calories dans le widget
Deux barres sous le titre : eau bue sur objectif, énergie consommée sur objectif calorique. La barre des calories vire à l'orange au-dessus de la cible. La jauge n'apparaît QUE si l'objectif correspondant existe. 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 — et il aurait l'air d'un réglage validé. Les trois champs du snapshot sont optionnels : un instantané écrit par une version antérieure reste lisible. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -53,6 +53,19 @@ struct CoachWidgetSnapshot: Codable {
|
|||||||
var waterMlToday: Double?
|
var waterMlToday: Double?
|
||||||
var coffeeCountToday: Int?
|
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).
|
/// Séance planifiée du jour (nil = jour OFF / pas de séance).
|
||||||
struct TodaySession: Codable {
|
struct TodaySession: Codable {
|
||||||
var sport: String // running, cycling, strength, mobility, hiking, rest…
|
var sport: String // running, cycling, strength, mobility, hiking, rest…
|
||||||
|
|||||||
@@ -60,11 +60,17 @@ struct CoachQuickEntryTimeline: TimelineEntry {
|
|||||||
let waterMl: Double
|
let waterMl: Double
|
||||||
let coffeeCount: Int
|
let coffeeCount: Int
|
||||||
let pending: Int
|
let pending: Int
|
||||||
|
let kcal: Double?
|
||||||
|
let kcalGoal: Double?
|
||||||
|
let waterGoal: Double?
|
||||||
}
|
}
|
||||||
|
|
||||||
struct CoachQuickProvider: TimelineProvider {
|
struct CoachQuickProvider: TimelineProvider {
|
||||||
func placeholder(in context: Context) -> CoachQuickEntryTimeline {
|
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) {
|
func getSnapshot(in context: Context, completion: @escaping (CoachQuickEntryTimeline) -> Void) {
|
||||||
@@ -91,7 +97,10 @@ struct CoachQuickProvider: TimelineProvider {
|
|||||||
date: Date(),
|
date: Date(),
|
||||||
waterMl: CoachQuickLog.todayTotal(kind: "water", syncedMl: syncedWater),
|
waterMl: CoachQuickLog.todayTotal(kind: "water", syncedMl: syncedWater),
|
||||||
coffeeCount: CoachQuickLog.todayCount(kind: "coffee", syncedCount: syncedCoffee),
|
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)
|
.lineLimit(1)
|
||||||
.minimumScaleFactor(0.8)
|
.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) {
|
HStack(spacing: 6) {
|
||||||
Text("\(litres) L")
|
Text("\(litres) L")
|
||||||
.font(.system(.title3, design: .rounded).weight(.semibold))
|
.font(.system(.subheadline, design: .rounded).weight(.semibold))
|
||||||
.monospacedDigit()
|
.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("· \(entry.coffeeCount) café\(entry.coffeeCount > 1 ? "s" : "")")
|
Text("· \(entry.coffeeCount) café\(entry.coffeeCount > 1 ? "s" : "")")
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
.foregroundStyle(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
|
|||||||
Reference in New Issue
Block a user