Maquette validée par Patrick (couleurs comprises). Deux graphes, et trois défauts d'affichage trouvés en chemin — dont deux à l'écran seulement. GRAPHE 1 — les trois « on ne voit pas » avaient trois causes. production : publiée NÉGATIVE (−103 W). La magnitude est prise AU RENDU, pas au transport : le champ reste brut (§7.1), et `.abs()` est licite sur une production, dont le signe ne porte aucune information — contrairement au réseau, où le signe dit le sens (d'où gridImportW/gridExportW là-bas). autoconsommation : `production − export` clampé à 0, donc TOUJOURS zéro avec une production négative. Même racine, corrigée par la même magnitude. SOC : correctement absent — aucune batterie sur l'installation. Ce qui manquait, c'était de le DIRE ; c'est maintenant une note sous le graphe. ⚠️ Et un deuxième empêchement, vu seulement à l'écran : le signe réparé, la production restait invisible SOUS l'autoconsommation. Tant que rien n'est exporté les deux sont égales au pixel près, et la verte était tracée par dessus. L'autoconso passe en pointillé — des tirets sur la bande ambre disent « tout est consommé sur place », leur décrochage dit l'export. Deux courbes qui coïncident doivent pouvoir se VOIR coïncider. ⚠️ Ce correctif en créait un autre : le SOC était aussi vert pointillé. Sur une installation AVEC batterie les deux auraient été indiscernables — collision qu'aucun banc sans batterie ne pouvait montrer. Le SOC passe en indigo. GRAPHE 2 — qui consomme, empilé. `Energy.GetThingPowerLogs`, et la liste des appareils vient de GetLoadConfig : chaque charge déclarée porte son `meterThingId`, son label et son domaine — vérifié renseigné sur les quatre charges de .75. Rien n'est deviné d'un nom d'appareil. Un appareil demandé sans réponse sort dans `sansHistorique` et est NOMMÉ sous le graphe, jamais tracé à zéro : la V2C ne déclare pas `smartmeter`, donc elle n'a pas de passé — ce qui n'est pas « elle n'a rien tiré ». Le RESTE du foyer est licite ici, contrairement au Sankey qui l'avait fait interdire : chaque part est un compteur dédié et le total est le compteur du foyer, donc la soustraction ne porte que du mesuré. Et un reste NÉGATIF n'est pas raboté — c'est la preuve qu'un sous-compteur n'est pas dans le périmètre du total, et l'écraser effacerait la seule chose qui le dise. 🔴 Le signe des sous-compteurs n'est PAS vérifié : les trois lisent 0 W à tous les pas, donc aucun échantillon non nul n'existe. Le compteur PV publie en négatif — rien n'autorise à supposer. La valeur est transportée brute et `signeIndetermine` le dit à l'écran. Couleurs par consommateur ajoutées au thème (pac ambre, ecs sarcelle, ev indigo) : distinctes des couleurs de FLUX, qui disent d'où vient l'énergie, là où celles-ci disent qui la prend. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GKM8FDNHZWogAsjjfB8pSw
172 lines
8.1 KiB
Dart
172 lines
8.1 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:google_fonts/google_fonts.dart';
|
|
|
|
/// Tokens de marque ETM PowerSync — source de vérité unique.
|
|
class EtmTokens {
|
|
EtmTokens._();
|
|
|
|
// ── Couleurs de marque (spec Dashboard v4) ────────────────────────────────
|
|
static const Color brand = Color(0xFF31A3DD);
|
|
static const Color solar = Color(0xFFFEC113);
|
|
static const Color solarDark = Color(0xFFFFCB33);
|
|
static const Color eco = Color(0xFF28A06A);
|
|
static const Color ecoDark = Color(0xFF33B87A);
|
|
static const Color heat = Color(0xFFEF8B2A);
|
|
static const Color water = Color(0xFF31A3DD);
|
|
static const Color gridColor = Color(0xFF8A98A3);
|
|
static const Color importColor = Color(0xFFE8806F);
|
|
|
|
// ── Legacy (compatibilité existante) ─────────────────────────────────────
|
|
static const Color navy = Color(0xFF0D2B3B);
|
|
static const Color navy2 = Color(0xFF10384C);
|
|
static const Color blue = Color(0xFF31A3DD);
|
|
static const Color amber = Color(0xFFFEC113);
|
|
static const Color green = Color(0xFF1DB86A);
|
|
static const Color greenDark= Color(0xFF159C58);
|
|
static const Color orange = Color(0xFFE8923A);
|
|
static const Color danger = Color(0xFFE8423F);
|
|
|
|
// ── Couleurs de flux ──────────────────────────────────────────────────────
|
|
static const Color solToHouse = Color(0xFFB8C34D);
|
|
static const Color solToEcs = Color(0xFF31A3DD);
|
|
static const Color solToVe = Color(0xFF28A06A);
|
|
static const Color solToBatt = Color(0xFF7FCF72);
|
|
static const Color solToGrid = Color(0xFFFEC113);
|
|
static const Color battToHouse = Color(0xFF2F6FB0);
|
|
static const Color gridToHouse = Color(0xFFE8806F);
|
|
|
|
// ── Neutres light ─────────────────────────────────────────────────────────
|
|
static const Color ink = navy;
|
|
static const Color muted= Color(0xFF5D6F7B);
|
|
static const Color faint= Color(0xFF8B9AA4);
|
|
static const Color line = Color(0xFFE4EAEE);
|
|
static const Color bg = Color(0xFFEEF2F5);
|
|
static const Color bg2 = Color(0xFFE4EAEE);
|
|
static const Color card = Color(0xFFFFFFFF);
|
|
|
|
// ── Neutres dark ──────────────────────────────────────────────────────────
|
|
static const Color bgDark = Color(0xFF06141D);
|
|
static const Color bg2Dark = Color(0xFF0A1F2B);
|
|
static const Color surfaceDark = Color(0xFF0D2B3B);
|
|
static const Color inkDark = Color(0xFFEAF3F7);
|
|
static const Color mutedDark = Color(0xFF9BB0BC);
|
|
static const Color faintDark = Color(0xFF6C8493);
|
|
|
|
// ── Soft fills (legacy light) ─────────────────────────────────────────────
|
|
static const Color greenSoft = Color(0xFFE7F8EF);
|
|
static const Color blueSoft = Color(0xFFE9F5FB);
|
|
static const Color amberSoft = Color(0xFFFFF6DD);
|
|
|
|
// ── Rayons ────────────────────────────────────────────────────────────────
|
|
static const double radius = 16;
|
|
static const double radiusLg = 22;
|
|
static const double radiusCard = 20;
|
|
static const double radiusCtrl = 13;
|
|
static const double radiusPill = 30;
|
|
|
|
// ── Context-aware ─────────────────────────────────────────────────────────
|
|
static bool _isDark(BuildContext c) =>
|
|
Theme.of(c).brightness == Brightness.dark;
|
|
|
|
static Color bgOf(BuildContext c) => _isDark(c) ? bgDark : bg;
|
|
static Color bg2Of(BuildContext c) => _isDark(c) ? bg2Dark : bg2;
|
|
static Color surfaceOf(BuildContext c) => _isDark(c) ? surfaceDark : card;
|
|
static Color inkOf(BuildContext c) => _isDark(c) ? inkDark : ink;
|
|
static Color mutedOf(BuildContext c) => _isDark(c) ? mutedDark : muted;
|
|
static Color faintOf(BuildContext c) => _isDark(c) ? faintDark : faint;
|
|
static Color ecoOf(BuildContext c) => _isDark(c) ? ecoDark : eco;
|
|
static Color solarOf(BuildContext c) => _isDark(c) ? solarDark : solar;
|
|
|
|
// ── Couleurs par CONSOMMATEUR ─────────────────────────────────────────────
|
|
//
|
|
// Distinctes des couleurs de FLUX (solaire, maison, réseau, batterie), qui disent d'où
|
|
// vient l'énergie. Celles-ci disent **qui la prend**, et il en fallait de nouvelles :
|
|
// le thème n'en portait aucune, et réutiliser une couleur de flux ferait lire une
|
|
// origine là où il n'y a qu'une destination.
|
|
//
|
|
// Trois teintes choisies pour ne heurter aucune couleur de flux existante, et validées
|
|
// par Patrick le 2026-09-08 sur la maquette `docs/mockups/ecran_energie_refonte.html`.
|
|
static const Color pac = Color(0xFFE88C30); // pompe à chaleur — ambre chaud
|
|
static const Color pacDark = Color(0xFFF09A45);
|
|
static const Color ecs = Color(0xFF2AA7A0); // eau chaude — sarcelle
|
|
static const Color ecsDark = Color(0xFF37BDB5);
|
|
static const Color ev = Color(0xFF7C6CE4); // bornes — indigo
|
|
static const Color evDark = Color(0xFF8E80EA);
|
|
|
|
static Color pacOf(BuildContext c) => _isDark(c) ? pacDark : pac;
|
|
static Color ecsOf(BuildContext c) => _isDark(c) ? ecsDark : ecs;
|
|
static Color evOf(BuildContext c) => _isDark(c) ? evDark : ev;
|
|
|
|
/// La couleur d'un domaine de charge, ou `null` s'il n'en a pas.
|
|
///
|
|
/// `null` n'est pas un défaut à combler : une charge sans domaine déclaré n'a pas de
|
|
/// couleur, et lui en attribuer une par défaut la ferait passer pour classée.
|
|
static Color? domaineOf(BuildContext c, String? domaine) => switch (domaine) {
|
|
'heating' => pacOf(c),
|
|
'ecs' => ecsOf(c),
|
|
'ev' => evOf(c),
|
|
_ => null,
|
|
};
|
|
|
|
static Color lineOf(BuildContext c) => _isDark(c)
|
|
? const Color(0xFFFFFFFF).withValues(alpha: 0.09)
|
|
: navy.withValues(alpha: 0.10);
|
|
|
|
static Color lineStrongOf(BuildContext c) => _isDark(c)
|
|
? const Color(0xFFFFFFFF).withValues(alpha: 0.16)
|
|
: navy.withValues(alpha: 0.16);
|
|
|
|
// ── Typographie ───────────────────────────────────────────────────────────
|
|
static TextStyle sans({
|
|
double size = 14,
|
|
FontWeight weight = FontWeight.w400,
|
|
Color color = ink,
|
|
double? height,
|
|
double? letterSpacing,
|
|
}) =>
|
|
GoogleFonts.ibmPlexSans(
|
|
fontSize: size,
|
|
fontWeight: weight,
|
|
color: color,
|
|
height: height,
|
|
letterSpacing: letterSpacing,
|
|
);
|
|
|
|
static TextStyle mono({
|
|
double size = 14,
|
|
FontWeight weight = FontWeight.w600,
|
|
Color color = ink,
|
|
}) =>
|
|
GoogleFonts.ibmPlexMono(
|
|
fontSize: size,
|
|
fontWeight: weight,
|
|
color: color,
|
|
fontFeatures: const [FontFeature.tabularFigures()],
|
|
);
|
|
|
|
static TextStyle sectionLabel() => sans(
|
|
size: 12,
|
|
weight: FontWeight.w600,
|
|
color: faint,
|
|
letterSpacing: 1.2,
|
|
);
|
|
|
|
// ── Ombres ────────────────────────────────────────────────────────────────
|
|
static List<BoxShadow> get cardShadow => [
|
|
BoxShadow(
|
|
color: navy.withValues(alpha: 0.04),
|
|
blurRadius: 2,
|
|
offset: const Offset(0, 1),
|
|
),
|
|
BoxShadow(
|
|
color: navy.withValues(alpha: 0.06),
|
|
blurRadius: 30,
|
|
offset: const Offset(0, 10),
|
|
),
|
|
];
|
|
|
|
static List<BoxShadow> cardShadowOf(BuildContext c) => _isDark(c)
|
|
? []
|
|
: cardShadow;
|
|
}
|