Le widget disait « JOUR OFF » les jours de sortie non planifiee
Pendant du commit web 25f53b5. Quand la seance prevue est remplacee par une autre activite, le plan du jour se vide : le widget iPhone affichait « JOUR OFF / Pas de seance prevue » et la complication Watch « Jour OFF » — le jour meme d'une sortie de 20 km. Le snapshot ne portait que le PLAN. CoachWidgetSnapshot gagne doneToday (sport, titre, sous-titre), optionnel : un snapshot ecrit par une version anterieure reste decodable. today decrit le plan, doneToday decrit la journee ; les deux peuvent coexister. CoachWidgetBridge le conserve quand l'appel l'omet, comme la nutrition — mais seulement si le snapshot precedent date d'aujourd'hui, sinon la sortie de la veille resterait affichee indefiniment. Les deux vues basculent sur l'activite realisee, avec la pastille verte « Realisee » deja utilisee pour une seance planifiee faite. Le sous-titre (« 20,3 km · 56 min ») passe devant le titre : le titre d'une seance importee est souvent vide. ⚠️ NON COMPILE — a builder au prochain passage sur le Mac. Aucun fichier nouveau, donc rien a cabler dans le pbxproj : les quatre fichiers touches sont deja membres de leurs cibles. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -58,7 +58,24 @@ public class CoachWidgetBridgePlugin: CAPPlugin, CAPBridgedPlugin {
|
||||
(call.getValue(key) as? NSNumber)?.doubleValue
|
||||
}
|
||||
|
||||
var done: CoachWidgetSnapshot.DoneActivity?
|
||||
if let d = call.getObject("doneToday") {
|
||||
done = CoachWidgetSnapshot.DoneActivity(
|
||||
sport: d["sport"] as? String ?? "other",
|
||||
title: d["title"] as? String,
|
||||
subtitle: d["subtitle"] as? String
|
||||
)
|
||||
}
|
||||
|
||||
var snapshot = CoachWidgetSnapshot(updatedAt: Date(), today: today, forme: forme)
|
||||
/* `doneToday` suit la même règle que la nutrition : absent de l'appel
|
||||
→ on garde le précédent. Un appel partiel effacerait sinon l'activité
|
||||
du jour, et le widget rebasculerait sur « JOUR OFF ».
|
||||
⚠️ Mais seulement dans la journée : au lendemain, un snapshot qui
|
||||
n'annonce plus rien doit repartir vide, sans quoi la sortie de la
|
||||
veille resterait affichée indéfiniment. */
|
||||
snapshot.doneToday = done ?? (Calendar.current.isDateInToday(previous?.updatedAt ?? .distantPast)
|
||||
? previous?.doneToday : nil)
|
||||
snapshot.waterMlToday = number("waterMlToday") ?? previous?.waterMlToday
|
||||
snapshot.coffeeCountToday =
|
||||
(call.getValue("coffeeCountToday") as? NSNumber)?.intValue ?? previous?.coffeeCountToday
|
||||
|
||||
@@ -60,6 +60,16 @@ struct CoachWidgetSnapshot: Codable {
|
||||
var kcalGoal: Double? = nil
|
||||
|
||||
|
||||
/// Activité RÉELLEMENT réalisée aujourd'hui, indépendante du plan.
|
||||
///
|
||||
/// Sans elle, un jour où la séance prévue est remplacée par une autre
|
||||
/// activité (bouton « j'ai fait du VTT », qui décale la séquence et vide
|
||||
/// le jour) s'affiche « JOUR OFF » — le jour même d'une sortie de 20 km.
|
||||
/// `today` décrit le plan, `doneToday` décrit la journée : les deux
|
||||
/// peuvent coexister, l'un être nil sans l'autre.
|
||||
/// Optionnel : un snapshot écrit par une version antérieure reste lisible.
|
||||
var doneToday: DoneActivity? = nil
|
||||
|
||||
/// 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…
|
||||
@@ -68,6 +78,13 @@ struct CoachWidgetSnapshot: Codable {
|
||||
var done: Bool // séance déjà réalisée aujourd'hui
|
||||
}
|
||||
|
||||
/// Ce qui a été fait dans la journée : sport, et de quoi le qualifier.
|
||||
struct DoneActivity: Codable {
|
||||
var sport: String // running, cycling, walking, strength…
|
||||
var title: String? // nom de la séance, souvent absent
|
||||
var subtitle: String? // "20,3 km · 56 min"
|
||||
}
|
||||
|
||||
/// Score de forme / récupération (nil = pas encore de score).
|
||||
struct FormeScore: Codable {
|
||||
var score: Int? // 0–100
|
||||
|
||||
@@ -167,7 +167,15 @@ struct CoachTodaySessionEntryView: View {
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .leading)
|
||||
}
|
||||
|
||||
/// Rien au plan. Deux situations très différentes, longtemps confondues :
|
||||
/// une vraie journée de repos, et une journée où le plan a été vidé parce
|
||||
/// que l'activité a été faite autrement. Annoncer « JOUR OFF » à qui vient
|
||||
/// de rouler 20 km efface son effort — d'où la bascule sur `doneToday`.
|
||||
@ViewBuilder
|
||||
private var emptyView: some View {
|
||||
if let d = entry.snapshot?.doneToday {
|
||||
doneView(d)
|
||||
} else {
|
||||
VStack(alignment: .leading, spacing: 6) {
|
||||
HStack(spacing: 6) {
|
||||
Image(systemName: "moon.zzz.fill").foregroundStyle(.secondary)
|
||||
@@ -179,6 +187,30 @@ struct CoachTodaySessionEntryView: View {
|
||||
}
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .leading)
|
||||
}
|
||||
}
|
||||
|
||||
private func doneView(_ d: CoachWidgetSnapshot.DoneActivity) -> some View {
|
||||
VStack(alignment: .leading, spacing: 6) {
|
||||
HStack(spacing: 6) {
|
||||
Image(systemName: sportSymbol(d.sport))
|
||||
.foregroundStyle(sportColor(d.sport))
|
||||
Text(sportLabel(d.sport).uppercased())
|
||||
.font(.caption2.weight(.bold)).foregroundStyle(.secondary)
|
||||
Spacer()
|
||||
Image(systemName: "checkmark.seal.fill")
|
||||
.foregroundStyle(.green).font(.caption)
|
||||
}
|
||||
Spacer(minLength: 0)
|
||||
// Le sous-titre porte la substance (« 20,3 km · 56 min ») ; le
|
||||
// titre d'une séance importée est souvent vide, d'où le repli.
|
||||
Text(d.subtitle ?? d.title ?? "Séance réalisée")
|
||||
.font(.headline).lineLimit(2).minimumScaleFactor(0.8)
|
||||
if d.subtitle != nil, let t = d.title, !t.isEmpty {
|
||||
Text(t).font(.caption).foregroundStyle(.secondary).lineLimit(1)
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .leading)
|
||||
}
|
||||
}
|
||||
|
||||
struct CoachTodaySessionWidget: Widget {
|
||||
|
||||
@@ -162,6 +162,25 @@ struct CoachWatchSessionView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if let d = entry.snapshot?.doneToday {
|
||||
/* Rien au plan, mais quelque chose a été fait : le plan se vide
|
||||
quand l'activité est remplacée (« j'ai fait du VTT »).
|
||||
Afficher « Jour OFF » ce jour-là est faux au poignet de
|
||||
quelqu'un qui vient de rentrer de sortie. */
|
||||
if family == .accessoryInline {
|
||||
Label(d.subtitle ?? watchSportLabel(d.sport),
|
||||
systemImage: watchSportSymbol(d.sport))
|
||||
} else {
|
||||
VStack(alignment: .leading, spacing: 2) {
|
||||
Label(watchSportLabel(d.sport).uppercased(),
|
||||
systemImage: watchSportSymbol(d.sport))
|
||||
.font(.caption2.weight(.bold))
|
||||
.foregroundStyle(.secondary)
|
||||
Text(d.subtitle ?? d.title ?? "Séance réalisée")
|
||||
.font(.headline).lineLimit(1)
|
||||
Text("Réalisée").font(.caption2).foregroundStyle(.green)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if family == .accessoryInline {
|
||||
Label("Jour OFF", systemImage: "moon.zzz.fill")
|
||||
|
||||
Reference in New Issue
Block a user