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:
Sylvain Bettinelli
2026-08-11 17:56:53 +00:00
parent 326e73aaae
commit 12b56a4d39
4 changed files with 89 additions and 4 deletions

View File

@@ -167,15 +167,47 @@ 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)
Text("JOUR OFF").font(.caption2.weight(.bold)).foregroundStyle(.secondary)
}
Spacer(minLength: 0)
Text("Pas de séance prévue")
.font(.subheadline).foregroundStyle(.secondary)
}
.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: "moon.zzz.fill").foregroundStyle(.secondary)
Text("JOUR OFF").font(.caption2.weight(.bold)).foregroundStyle(.secondary)
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)
Text("Pas de séance prévue")
.font(.subheadline).foregroundStyle(.secondary)
// 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)
}