- specs -> docs/ (DASHBOARD_SPEC compagnon du contrat) - ref introspection nymea-jsonRPC -> docs/reference/ - mockups HTML -> docs/mockups/ ; briefs consommés -> docs/archive/ - track AGENTS.md + INTERFACE_etmvariableload.md (étaient untracked) - INTERFACE_*: marqué miroir, canonique = repo plugin
253 lines
13 KiB
Markdown
253 lines
13 KiB
Markdown
# Dashboard ETM-PowerSync — Brief d'implémentation Flutter
|
||
|
||
> Spec pour Claude Code. Objectif : recréer l'écran **Dashboard** de l'app `app.etm-powersync.fr` (Flutter), tel que validé en maquette. Les fichiers HTML de référence (`etm-powersync-dashboard-v4.html`, `etm-powersync-flux-drilldown.html`) sont la **source de vérité visuelle** — ce document en est la transcription technique.
|
||
|
||
---
|
||
|
||
## 1. Contexte
|
||
|
||
ETM-PowerSync est un HEMS (Home Energy Management System). Le cœur intelligent s'appelle **Héos** (optimiseur prédictif MILP, horizon 24–48 h). Le Dashboard doit faire deux choses : montrer l'état énergétique **en direct**, et rendre **lisible et explicable** ce que Héos décide — c'est le différenciateur produit. Public : clients résidentiels (langage clair, zéro jargon) + installateurs.
|
||
|
||
---
|
||
|
||
## 2. Principes directeurs (ne pas dévier)
|
||
|
||
1. **Chaque information à un seul endroit.** Pas de doublon entre les blocs. L'autoconsommation est en KPI (haut) → elle n'apparaît nulle part ailleurs sur le Dashboard.
|
||
2. **Lisibilité avant densité.** Lecture rapide en haut, détail optionnel en bas. Le client pressé s'arrête à la décision Héos ; le curieux scrolle.
|
||
3. **Expliquer = créer la confiance.** Toute action automatique de Héos est accompagnée d'un *pourquoi* en langage courant.
|
||
4. **Cohérence des flux = physique réelle.** Héos est au centre. Le solaire et le réseau alimentent (→ vers le centre) ; le centre répartit vers batterie et maison. Jamais « maison → batterie ».
|
||
5. **Passé plein, futur voilé.** Sur tout graphe temporel : réalisé en couleur pleine, prévu en hachuré + fond grisé, ligne « maintenant » qui traverse.
|
||
6. **Fluide = rien ne saute.** Chaque valeur live s'anime (interpolation), jamais de flash.
|
||
7. **Quality floor non négociable.** Responsive, dark mode, focus clavier, `prefers-reduced-motion` respecté, contraste AA.
|
||
|
||
---
|
||
|
||
## 3. Design tokens (`etm_tokens.dart`)
|
||
|
||
Compléter le fichier existant avec ces valeurs. Typo : **IBM Plex Sans** (UI) + **IBM Plex Mono** (toutes les valeurs chiffrées / données).
|
||
|
||
### Couleurs de marque (identiques light/dark sauf note)
|
||
| Token | Hex | Usage |
|
||
|---|---|---|
|
||
| `brand` | `#31A3DD` | bleu ETM, accents, navigation active |
|
||
| `solar` | `#FEC113` (dark `#FFCB33`) | production solaire |
|
||
| `eco` | `#28A06A` (dark `#33B87A`) | Héos, éco, autonomie, batterie |
|
||
| `heat` | `#EF8B2A` | chauffage / PAC |
|
||
| `water` | `#31A3DD` | eau chaude (ECS) |
|
||
| `grid` | `#8A98A3` | réseau |
|
||
| `import` | `#E8806F` | import réseau |
|
||
|
||
### Thème clair
|
||
| Token | Valeur |
|
||
|---|---|
|
||
| `bg` | `#EEF2F5` |
|
||
| `bg2` | `#E4EAEE` |
|
||
| `surface` | `#FFFFFF` |
|
||
| `ink` | `#0D2B3B` |
|
||
| `muted` | `#5D6F7B` |
|
||
| `faint` | `#8B9AA4` |
|
||
| `line` | `rgba(13,43,59,.10)` |
|
||
| `lineStrong` | `rgba(13,43,59,.16)` |
|
||
|
||
### Thème sombre
|
||
| Token | Valeur |
|
||
|---|---|
|
||
| `bg` | `#06141D` |
|
||
| `bg2` | `#0A1F2B` |
|
||
| `surface` | `#0D2B3B` (la couleur de marque devient la surface) |
|
||
| `ink` | `#EAF3F7` |
|
||
| `muted` | `#9BB0BC` |
|
||
| `faint` | `#6C8493` |
|
||
| `line` | `rgba(255,255,255,.09)` |
|
||
| `lineStrong` | `rgba(255,255,255,.16)` |
|
||
|
||
### Couleurs des flux (graphe « plan de la journée »)
|
||
`solToHouse #B8C34D` · `solToEcs #31A3DD` · `solToVe #28A06A` · `solToBatt #7FCF72` · `solToGrid #FEC113` · `battToHouse #2F6FB0` · `gridToHouse #E8806F`
|
||
|
||
### Formes & profondeur
|
||
- Rayons : cartes **20**, contrôles internes **13–14**, pills/badges **30** (stadium).
|
||
- **Parti pris assumé** : bordure hairline 1 px (`line`) + rayon large. Ombres quasi nulles en clair (`0 1px 2px rgba(13,43,59,.04)`), aucune en sombre. **Ne pas** revenir aux cartes blanches à ombre portée Material par défaut.
|
||
- Espacement entre cartes : 14. Padding interne carte : 14–16.
|
||
|
||
---
|
||
|
||
## 4. Architecture de l'écran
|
||
|
||
`DashboardScreen` = `Scaffold` + `SingleChildScrollView` (colonne), bottom nav fixe à 5 onglets. Ordre vertical des blocs :
|
||
|
||
```
|
||
[ TopBar : wordmark + toggle thème clair/sombre ]
|
||
[ 1. EnergyFlowCard ] ← flux en croix, temps réel, drill-down
|
||
[ 2. KpiRow ] ← 4 tuiles : autoconso, autonomie, vers/depuis réseau
|
||
[ 3. HeosDecisionCard ] ← décision en cours + pourquoi
|
||
[ 4. DayPlanCard ] ← météo + barres horaires (réalisé/prévu)
|
||
[ BottomNav : Dashboard·Énergie·Things·A/C·Favoris ]
|
||
```
|
||
|
||
Onglets : `Dashboard` actif (couleur `brand`), les autres en `faint`.
|
||
|
||
---
|
||
|
||
## 5. Composants
|
||
|
||
### 5.1 EnergyFlowCard — flux en croix + drill-down
|
||
|
||
**Layout** : carte carrée (aspect 1:1, max 320). `Stack` :
|
||
- Couche basse : `CustomPaint` (lignes hairline entre hub↔nœuds + **particules animées**).
|
||
- Couche haute : nœuds en `Positioned` (Stack), `GestureDetector` sur les nœuds tappables.
|
||
|
||
**Vue d'ensemble (`overview`)** — positions cardinales :
|
||
| Position | Nœud | Valeur (ex.) | Direction flux | Tap |
|
||
|---|---|---|---|---|
|
||
| centre (hub) | **Héos** (icône HEMS, anneau eco pulsant) | — | — | — |
|
||
| haut | Solaire | `3.8 kW` | entrant (nœud→hub) | → `prod` |
|
||
| droite | Batterie | `87 %` | sortant (hub→nœud) | — |
|
||
| gauche | Réseau | `0.2 kW` | entrant | — |
|
||
| bas | Maison | `2.4 kW` | sortant | → `conso` |
|
||
|
||
**Drill-down `conso`** (depuis Maison) — hub = Maison, autour :
|
||
PAC (haut, `heat`), Eau chaude (droite, `water`), Voiture (bas, `eco`), **Autres (estimé)** (gauche, bordure **dashed**, `grid`).
|
||
> Le nœud « Autres (estimé) » est **obligatoire** : il absorbe le non-mesuré pour que le bilan boucle (conso totale = somme des postes). Sans lui, l'addition ne tombe pas juste et le client se méfie.
|
||
|
||
**Drill-down `prod`** (depuis Solaire) — hub = Solaire, autour :
|
||
Onduleur 1 (haut), Onduleur 2 (bas), chacun avec **point d'état** (vert OK / orange défaut). Sens : onduleur → hub.
|
||
|
||
**Particules** : `CustomPainter` + `AnimationController` en boucle. N points par segment circulant de la source vers la destination. **Vitesse et densité ∝ puissance** (flux fort = plus de points, plus rapides ; flux faible = 1 point lent). Couleur = couleur de la source.
|
||
|
||
**Transitions drill-down** : sortie = fade + scale 0.8 (~170 ms) ; entrée = « éclatement radial », chaque nœud apparaît en `scale .25→1` avec overshoot (`Curves.easeOutBack`) et **stagger** (~60 ms entre nœuds). Détail intégral du sens/positions dans `etm-powersync-flux-drilldown.html`.
|
||
|
||
**Affordance tap** : badge `+` discret sur les nœuds tappables (Héos n'est pas tappable ; Solaire et Maison le sont). Profondeur **max 2 niveaux** — au-delà, écran dédié.
|
||
|
||
**Icône HEMS** : utiliser `HemsIcon` (déjà livré, `hems_icon.dart`), couleur `eco`.
|
||
|
||
### 5.2 KpiRow
|
||
|
||
Grille 2×2.
|
||
- **Tuile autoconsommation** : anneau de progression (`CustomPaint`, couleur `solar`) + `%` en mono. S'anime au chargement (anneau qui se remplit + count-up).
|
||
- **Tuile autonomie** : idem, couleur `eco`.
|
||
- **Tuile « Vers réseau »** : icône flèche ←, valeur `kWh` du jour, badge fond `eco` léger.
|
||
- **Tuile « Depuis réseau »** : icône flèche →, valeur `kWh`, badge fond `import` léger.
|
||
|
||
> Ces deux métriques (autoconso/autonomie) sont les seules occurrences sur le Dashboard. Ne pas les répéter ailleurs.
|
||
|
||
### 5.3 HeosDecisionCard
|
||
|
||
Carte bordée `eco` (liseré subtil). Contenu :
|
||
- **En-tête** : icône étincelle (mark Héos) + titre « Héos pilote en ce moment » + sous-titre mono « optimise sur 24 h · maj HH:MM » + point eco pulsant.
|
||
- **Résumé** : 1 phrase en langage client, segment clé surligné `eco` (ex. « Le surplus part dans l'eau chaude et la voiture plutôt que d'être revendu à bas prix »).
|
||
- **Liste d'actions** (1 par asset piloté) : icône + titre court + **le pourquoi** (1 ligne, `muted`) + puissance (`kW`, mono, `eco`), séparées par hairline.
|
||
- **Pied** : lien « Voir le plan de la journée » → ouvre l'écran plein **Plan de la journée** (séparé, voir `etm-powersync-plan-journee.html` ; c'est là que vivent les pistes appareils détaillées + la ligne gains €).
|
||
|
||
### 5.4 DayPlanCard — météo + barres horaires
|
||
|
||
`CustomPaint` unique (préférer à fl_chart : contrôle fin du voile futur + bande météo intégrée). **Une seule colonne temporelle, axe horaire partagé**, de haut en bas :
|
||
1. **Bande météo** : icône (soleil / soleil-nuage / lune) + température, à intervalles (~toutes les 4 h), posée sur l'axe.
|
||
2. **Barres empilées horaires** (kWh) : segments positifs vers le haut (`solToHouse`, `solToEcs`, `solToVe`, `solToBatt`, `solToGrid`), négatifs vers le bas (`battToHouse`, `gridToHouse`). Axe Y `0/2/4`.
|
||
3. **Axe X** : `0h 6h 12h 18h 24h`.
|
||
|
||
**Traitement passé/futur** : tout ce qui est à droite de la ligne « maintenant » = **fond grisé + voile hachuré** (lignes diagonales couleur surface, opacité ~.5) ; ligne « maintenant » verticale + tab `HH:MM`.
|
||
|
||
**Légende** sous le graphe : pastilles maison/eau/voiture/batterie/réseau/batt.→maison.
|
||
|
||
> **NE PAS mettre sur le Dashboard** (retirés volontairement, ils vivent dans l'écran plein « Plan de la journée ») :
|
||
> - les **pistes appareils** (timelines ECS / batterie charge-décharge / VE) ;
|
||
> - la **ligne gains** (autoconso prévue / réseau ce soir / +€).
|
||
|
||
### 5.5 Toggle thème
|
||
|
||
Switch clair/sombre dans la TopBar, knob animé (overshoot). Persister le choix (provider de thème).
|
||
|
||
---
|
||
|
||
## 6. Animations & widgets
|
||
|
||
| Effet | Approche Flutter |
|
||
|---|---|
|
||
| Valeurs live (kW, %) | `TweenAnimationBuilder` (count-up, easeOutCubic ~900 ms) |
|
||
| Particules de flux | `CustomPainter` + `AnimationController` répété ; position = `(t + offset) % 1` mappée sur le segment |
|
||
| Anneaux KPI | `CustomPaint`, `Tween` sur l'angle balayé |
|
||
| Toggles / switch | `AnimatedAlign` ou `AnimatedPositioned`, courbe overshoot (`Curves.easeOutBack`) |
|
||
| Segmented (pill qui glisse) | `Stack` + `AnimatedPositioned` du « glider » |
|
||
| Cartes expandables (A/C) | `AnimatedSize` |
|
||
| Drill-down croix | `AnimationController` + `Interval` staggered (scale + opacity) |
|
||
| Anneau Héos (hub) | pulsation lente en boucle (opacité 0.25↔0.6) |
|
||
|
||
**Reduced motion** : si `MediaQuery.of(context).disableAnimations`, court-circuiter les animations (valeurs finales directes, pas de particules en boucle).
|
||
|
||
---
|
||
|
||
## 7. Modèles de données (esquisse)
|
||
|
||
```dart
|
||
class LiveFlow { // alimente EnergyFlowCard (overview)
|
||
final double solarKw, gridKw, houseKw, batteryKw; // batteryKw signé
|
||
final int batteryPct;
|
||
}
|
||
|
||
class FlowNode { // générique, réutilisé à chaque niveau
|
||
final String label, valueText, iconKey;
|
||
final FlowDirection direction; // inbound | outbound
|
||
final bool tappable, estimated; // estimated → bordure dashed
|
||
final NodeState? state; // ok | warn (onduleurs)
|
||
final String? drillTo; // 'conso' | 'prod'
|
||
}
|
||
|
||
class HeosDecision {
|
||
final String summary; // langage client
|
||
final List<HeosAction> actions;
|
||
final DateTime updatedAt;
|
||
}
|
||
class HeosAction { final String assetIconKey, title, why; final double powerKw; }
|
||
|
||
class Kpi { final int autoconsumptionPct, autonomyPct; final double toGridKwh, fromGridKwh; }
|
||
|
||
class DayPlan {
|
||
final List<HourlyFlow> hours; // 0..23
|
||
final List<WeatherPoint> weather;
|
||
final double nowHour; // ex. 14.5
|
||
}
|
||
class HourlyFlow { // tous en kWh, signés selon sens
|
||
final int hour;
|
||
final double solToHouse, solToEcs, solToVe, solToBatt, solToGrid;
|
||
final double battToHouse, gridToHouse;
|
||
}
|
||
class WeatherPoint { final int hour; final WeatherKind kind; final int tempC; }
|
||
```
|
||
|
||
Toutes les valeurs affichées proviennent de ces modèles (les chiffres des maquettes sont des exemples). Prévoir des états **loading** (skeleton) et **vide** (« en attente de données du compteur… », jamais d'écran blanc).
|
||
|
||
---
|
||
|
||
## 8. Assets fournis
|
||
- `hems_icon.dart` — `HemsIcon(size, color)`, `CustomPainter` natif sans dépendance. À utiliser pour le hub central.
|
||
- `hems_icon.svg` — équivalent SVG (`currentColor`) si passage par `flutter_svg`.
|
||
|
||
---
|
||
|
||
## 9. Arborescence suggérée
|
||
|
||
```
|
||
lib/features/dashboard/
|
||
dashboard_screen.dart
|
||
widgets/energy_flow_card.dart + painters/flow_painter.dart
|
||
widgets/kpi_row.dart + painters/ring_painter.dart
|
||
widgets/heos_decision_card.dart
|
||
widgets/day_plan_card.dart + painters/day_plan_painter.dart
|
||
models/dashboard_models.dart
|
||
lib/shared/widgets/hems_icon.dart
|
||
lib/theme/etm_tokens.dart (compléter)
|
||
```
|
||
|
||
## 10. Ordre de construction recommandé
|
||
1. Compléter `etm_tokens.dart` (couleurs light/dark, typo, radius).
|
||
2. `KpiRow` + `RingPainter` (le plus simple, valide les tokens).
|
||
3. `HeosDecisionCard` (statique, valide la typo/hairlines).
|
||
4. `EnergyFlowCard` overview + `FlowPainter` (lignes + particules), **puis** drill-down.
|
||
5. `DayPlanCard` + `DayPlanPainter` (barres + voile futur + bande météo).
|
||
6. Assemblage `DashboardScreen` + bottom nav + toggle thème.
|
||
7. Passe quality floor : reduced motion, focus, contraste AA en clair **et** sombre.
|
||
|
||
---
|
||
|
||
*Réfs visuelles : `etm-powersync-dashboard-v4.html` (écran complet) et `etm-powersync-flux-drilldown.html` (interaction croix). En cas de doute sur un espacement ou une couleur, ces fichiers font foi.*
|