fix(dashboard+thème): compteurs réseau honnêtes, et le sélecteur clair/sombre s'applique partout

1) « Vers réseau » — TROIS défauts, dont un seul était visible.

   La valeur aberrante (9,10184409403335e+33 kWh) ne vient PAS de l'app : Energy.GetPowerBalance
   la publie telle quelle sur .75. Trois des quatre compteurs cumulés sont corrompus
   (totalReturn 9,1e+33, totalAcquisition 7,5e+33, totalConsumption -1,6e+33) alors que
   totalProduction reste sain, et que les états du Thing compteur sont tous plausibles
   (1429 kWh importés, 88 kWh exportés). C'est donc l'accumulation de l'experience-plugin
   énergie qui déraille, côté box — signalé, pas corrigé ici.

   Les deux autres défauts étaient bien dans l'app :
   - « Depuis réseau » lisait l'OPPOSÉ du compteur d'export au lieu de totalAcquisition,
     qui a son propre compteur. Dès que l'export était positif, la tuile affichait
     0,0 kWh en permanence, quelle que soit l'électricité réellement achetée ;
   - les deux tuiles étaient libellées « aujourd'hui » alors que ces compteurs sont
     CUMULÉS depuis l'origine, jamais remis à zéro.

   Corrigé : EnergyData porte gridExportKwh / gridImportKwh, transportés tels quels — l'app
   ne maquille pas une donnée que la box publie fausse. Un plafond de plausibilité
   (10 GWh, ~3000 ans de consommation) distingue « mesure » de « compteur cassé » : au-delà
   la tuile affiche « — · compteur box invalide ». Imprimer le nombre le présenterait comme
   une mesure ; l'effacer laisserait croire à zéro.

2) Thème : 126 sites basculés des constantes claires vers les variantes contextuelles
   (EtmTokens.bg → bgOf(context), card → surfaceOf, ink → inkOf, muted → mutedOf,
   faint → faintOf, line → lineOf) dans ac_screen, things_screen, energy_screen,
   favorites_screen, main_drawer, ev_charging_card, energy_flow_widget. Ces écrans
   restaient en palette claire quel que soit le sélecteur.

   9 sites RESTENT en constante, et c'est délibéré : ce sont des tables statiques et des
   helpers de haut niveau (categoryInfoMap, _options des favoris, _modeColor, peintres du
   flux) où la couleur est une identité d'icône et où aucun BuildContext n'est en portée.
   Les faire suivre le thème demande de threader le contexte jusqu'à ces tables — un
   refactor à part entière, pas un correctif de fin de soirée.

Tests 80/80, analyze 0 erreur.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HajXLUczEyZd22JeewRfff
This commit is contained in:
Patrick Schurig 2026-08-25 23:13:20 +02:00
parent 15a52637a5
commit 38475e0007
11 changed files with 213 additions and 145 deletions

View File

@ -11,8 +11,19 @@ class KpiRow extends StatelessWidget {
Widget build(BuildContext context) {
final autoconso = data.selfConsumptionRate.clamp(0.0, 100.0);
final autonomy = data.autonomyRate.clamp(0.0, 100.0);
final toGrid = data.dayGridInjectionWh.clamp(0.0, double.infinity) / 1000;
final fromGrid = (-data.dayGridInjectionWh).clamp(0.0, double.infinity) / 1000;
// Deux compteurs DISTINCTS de la box, et non un seul et son opposé.
//
// Ces deux tuiles lisaient toutes les deux `dayGridInjectionWh` — l'export — la
// seconde en prenant son opposé. Or l'import a son propre compteur
// (`totalAcquisition`) : dès que l'export était positif, « Depuis réseau » affichait
// 0,0 kWh en permanence, quelle que soit l'électricité réellement achetée.
//
// `null` = la box publie une valeur inexploitable ; la tuile le dit au lieu
// d'imprimer un nombre absurde (constaté sur `.75` : 9,1e+33 kWh exportés, alors que
// `totalProduction` restait sain — les compteurs cumulés de l'experience-plugin
// énergie sont corrompus, pas ceux du Thing compteur).
final toGrid = data.gridExportKwhOrNull;
final fromGrid = data.gridImportKwhOrNull;
return Column(
children: [
@ -40,7 +51,9 @@ class KpiRow extends StatelessWidget {
children: [
Expanded(
child: _ValueTile(
label: 'Vers réseau · aujourd\'hui',
// « aujourd'hui » était faux : `totalReturn` est cumulé depuis
// l'origine et n'est pas remis à zéro chaque nuit.
label: 'Vers réseau · cumulé',
value: toGrid,
icon: Icons.north_west_rounded,
color: EtmTokens.ecoOf(context),
@ -49,7 +62,7 @@ class KpiRow extends StatelessWidget {
const SizedBox(width: 10),
Expanded(
child: _ValueTile(
label: 'Depuis réseau · aujourd\'hui',
label: 'Depuis réseau · cumulé',
value: fromGrid,
icon: Icons.south_east_rounded,
color: EtmTokens.importColor,
@ -161,7 +174,13 @@ class _RingTileState extends State<_RingTile>
class _ValueTile extends StatelessWidget {
final String label;
final double value;
/// Valeur en kWh, ou `null` si la box en publie une **inexploitable**.
///
/// Le cas n'est pas théorique : `.75` publie 9,1e+33 kWh exportés. Imprimer ce nombre
/// le présente comme une mesure ; l'effacer laisse croire à zéro. On affiche donc « — »
/// et on dit d'où vient le trou, ce qui est la seule lecture honnête des trois.
final double? value;
final IconData icon;
final Color color;
@ -198,16 +217,18 @@ class _ValueTile extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'${value.toStringAsFixed(1)} kWh',
value == null ? '—' : '${value!.toStringAsFixed(1)} kWh',
style: EtmTokens.mono(
size: 16,
weight: FontWeight.w600,
color: EtmTokens.inkOf(context),
color: value == null
? EtmTokens.mutedOf(context)
: EtmTokens.inkOf(context),
),
),
const SizedBox(height: 3),
Text(
label,
value == null ? '$label · compteur box invalide' : label,
style: EtmTokens.sans(
size: 10.5,
color: EtmTokens.mutedOf(context),

View File

@ -11,6 +11,19 @@ class EnergyData {
final double dayProductionWh;
final double daySelfConsumptionWh;
final double dayGridInjectionWh;
/// Compteurs CUMULÉS de la box (kWh), tels qu'elle les publie — pas des totaux du jour.
///
/// `Energy.GetPowerBalance` rend `totalReturn` (exporté) et `totalAcquisition`
/// (importé). Ils sont **cumulés depuis toujours**, pas remis à zéro chaque nuit : les
/// afficher sous un libellé « aujourd'hui » serait faux.
///
/// `NaN` signifie **valeur non exploitable**, et c'est un état à part entière : le banc
/// `.75` publie des compteurs manifestement corrompus (7,5e+33 kWh importés) alors que
/// `totalProduction` reste sain. L'app ne les corrige pas — elle refuse de les
/// présenter comme une mesure (cf. [gridExportKwhOrNull]).
final double gridExportKwh;
final double gridImportKwh;
final double selfConsumptionRate; // %
final double autonomyRate; // %
final double dayGains; // €
@ -32,6 +45,8 @@ class EnergyData {
this.dayProductionWh = 0,
this.daySelfConsumptionWh = 0,
this.dayGridInjectionWh = 0,
this.gridExportKwh = 0,
this.gridImportKwh = 0,
this.selfConsumptionRate = 0,
this.autonomyRate = 0,
this.dayGains = 0,
@ -52,6 +67,8 @@ class EnergyData {
double? dayProductionWh,
double? daySelfConsumptionWh,
double? dayGridInjectionWh,
double? gridExportKwh,
double? gridImportKwh,
double? selfConsumptionRate,
double? autonomyRate,
double? dayGains,
@ -71,6 +88,8 @@ class EnergyData {
dayProductionWh: dayProductionWh ?? this.dayProductionWh,
daySelfConsumptionWh: daySelfConsumptionWh ?? this.daySelfConsumptionWh,
dayGridInjectionWh: dayGridInjectionWh ?? this.dayGridInjectionWh,
gridExportKwh: gridExportKwh ?? this.gridExportKwh,
gridImportKwh: gridImportKwh ?? this.gridImportKwh,
selfConsumptionRate: selfConsumptionRate ?? this.selfConsumptionRate,
autonomyRate: autonomyRate ?? this.autonomyRate,
dayGains: dayGains ?? this.dayGains,
@ -80,6 +99,26 @@ class EnergyData {
gridLimitOk: gridLimitOk ?? this.gridLimitOk,
);
}
/// Plafond de plausibilité pour un compteur d'énergie domestique cumulé, en kWh.
///
/// 10 GWh, soit environ **trois mille ans** de consommation d'une maison. Aucune
/// installation résidentielle ne l'atteindra ; tout ce qui le dépasse est un compteur
/// cassé, pas un record. Le seuil est volontairement absurde-haut : il ne s'agit pas de
/// juger une valeur, seulement d'attraper les corrompues.
static const double kPlausibleMaxKwh = 1e7;
/// Vrai si [v] peut être présenté comme une mesure.
static bool isPlausibleKwh(double v) =>
v.isFinite && v >= 0 && v <= kPlausibleMaxKwh;
/// Export cumulé, ou `null` si la box publie une valeur inexploitable.
double? get gridExportKwhOrNull =>
isPlausibleKwh(gridExportKwh) ? gridExportKwh : null;
/// Import cumulé, ou `null` si la box publie une valeur inexploitable.
double? get gridImportKwhOrNull =>
isPlausibleKwh(gridImportKwh) ? gridImportKwh : null;
}
enum ChargingMode { pv, minPv, boost }
@ -98,6 +137,7 @@ class HistoryPoint {
this.gridWh = 0,
this.batteryWh = 0,
});
}
// ── Entrée Energy.GetPowerBalanceLogs ─────────────────────────────────────────
@ -123,4 +163,5 @@ class PowerBalanceEntry {
this.totalReturnWh = 0,
this.totalAcquisitionWh = 0,
});
}

View File

@ -102,7 +102,7 @@ const Map<String, ThingCategory> interfaceToCategoryMap = {
// Infos visuelles par catégorie
// ─────────────────────────────────────────────────────────────────────────────
const Map<ThingCategory, ThingCategoryInfo> categoryInfoMap = {
Map<ThingCategory, ThingCategoryInfo> categoryInfoMap = {
ThingCategory.energy: ThingCategoryInfo(
category: ThingCategory.energy,
label: 'Compteurs & Prises',

View File

@ -103,12 +103,12 @@ class _ACScreenState extends State<ACScreen> {
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: EtmTokens.bg,
backgroundColor: EtmTokens.bgOf(context),
body: CustomScrollView(
slivers: [
SliverAppBar(
floating: true,
backgroundColor: EtmTokens.bg,
backgroundColor: EtmTokens.bgOf(context),
elevation: 0,
leading: const DrawerMenuButton(),
leadingWidth: 64,
@ -118,8 +118,8 @@ class _ACScreenState extends State<ACScreen> {
Padding(
padding: const EdgeInsets.only(right: 8),
child: IconButton(
icon: const Icon(Icons.schedule_rounded,
color: EtmTokens.muted, size: 22),
icon: Icon(Icons.schedule_rounded,
color: EtmTokens.mutedOf(context), size: 22),
onPressed: () {},
tooltip: 'Planning hebdomadaire',
),
@ -273,9 +273,9 @@ class _ActiveZoneCard extends StatelessWidget {
],
),
const Padding(
Padding(
padding: EdgeInsets.symmetric(vertical: 14),
child: Divider(height: 1, color: EtmTokens.line),
child: Divider(height: 1, color: EtmTokens.lineOf(context)),
),
// Températures
@ -285,16 +285,16 @@ class _ActiveZoneCard extends StatelessWidget {
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Actuelle', style: EtmTokens.sans(size: 11, color: EtmTokens.muted)),
Text('Actuelle', style: EtmTokens.sans(size: 11, color: EtmTokens.mutedOf(context))),
Text('${zone.currentTemp.toStringAsFixed(1)}°C',
style: EtmTokens.mono(size: 28, weight: FontWeight.w700)),
],
),
Icon(Icons.arrow_forward_rounded, color: EtmTokens.faint, size: 20),
Icon(Icons.arrow_forward_rounded, color: EtmTokens.faintOf(context), size: 20),
Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text('Cible', style: EtmTokens.sans(size: 11, color: EtmTokens.muted)),
Text('Cible', style: EtmTokens.sans(size: 11, color: EtmTokens.mutedOf(context))),
Row(
children: [
_TempButton(
@ -338,10 +338,10 @@ class _ActiveZoneCard extends StatelessWidget {
duration: const Duration(milliseconds: 180),
padding: const EdgeInsets.symmetric(vertical: 8),
decoration: BoxDecoration(
color: sel ? mcol.withValues(alpha: 0.12) : EtmTokens.bg,
color: sel ? mcol.withValues(alpha: 0.12) : EtmTokens.bgOf(context),
borderRadius: BorderRadius.circular(10),
border: Border.all(
color: sel ? mcol : EtmTokens.line,
color: sel ? mcol : EtmTokens.lineOf(context),
width: sel ? 1.5 : 1,
),
),
@ -349,12 +349,12 @@ class _ActiveZoneCard extends StatelessWidget {
children: [
Icon(_modeIcon(m),
size: 18,
color: sel ? mcol : EtmTokens.faint),
color: sel ? mcol : EtmTokens.faintOf(context)),
const SizedBox(height: 3),
Text(_modeLabelShort(m),
style: EtmTokens.sans(
size: 10,
color: sel ? mcol : EtmTokens.muted,
color: sel ? mcol : EtmTokens.mutedOf(context),
weight: sel ? FontWeight.w600 : FontWeight.w400)),
],
),
@ -393,10 +393,10 @@ class _InactiveZoneCard extends StatelessWidget {
Container(
width: 40, height: 40,
decoration: BoxDecoration(
color: EtmTokens.bg,
color: EtmTokens.bgOf(context),
borderRadius: BorderRadius.circular(12),
),
child: Icon(_modeIcon(zone.mode), color: EtmTokens.faint, size: 22),
child: Icon(_modeIcon(zone.mode), color: EtmTokens.faintOf(context), size: 22),
),
const SizedBox(width: 14),
Expanded(
@ -405,7 +405,7 @@ class _InactiveZoneCard extends StatelessWidget {
children: [
Text(zone.name,
style: EtmTokens.sans(size: 15, weight: FontWeight.w600)),
Text('Éteint', style: EtmTokens.sans(size: 12, color: EtmTokens.faint)),
Text('Éteint', style: EtmTokens.sans(size: 12, color: EtmTokens.faintOf(context))),
],
),
),
@ -457,7 +457,7 @@ class _PACCard extends StatelessWidget {
Text('PAC Atlantic',
style: EtmTokens.sans(size: 15, weight: FontWeight.w600)),
Text('Source de chauffage · SG-Ready',
style: EtmTokens.sans(size: 11, color: EtmTokens.muted)),
style: EtmTokens.sans(size: 11, color: EtmTokens.mutedOf(context))),
],
),
),
@ -479,10 +479,10 @@ class _PACCard extends StatelessWidget {
duration: const Duration(milliseconds: 180),
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
decoration: BoxDecoration(
color: sgAuto ? EtmTokens.greenSoft : EtmTokens.bg,
color: sgAuto ? EtmTokens.greenSoft : EtmTokens.bgOf(context),
borderRadius: BorderRadius.circular(99),
border: Border.all(
color: sgAuto ? EtmTokens.green : EtmTokens.line,
color: sgAuto ? EtmTokens.green : EtmTokens.lineOf(context),
),
),
child: Row(
@ -490,13 +490,13 @@ class _PACCard extends StatelessWidget {
children: [
Icon(Icons.auto_awesome_rounded,
size: 12,
color: sgAuto ? EtmTokens.green : EtmTokens.muted),
color: sgAuto ? EtmTokens.green : EtmTokens.mutedOf(context)),
const SizedBox(width: 4),
Text('Auto',
style: EtmTokens.sans(
size: 11,
weight: FontWeight.w600,
color: sgAuto ? EtmTokens.green : EtmTokens.muted)),
color: sgAuto ? EtmTokens.green : EtmTokens.mutedOf(context))),
],
),
),
@ -512,7 +512,7 @@ class _PACCard extends StatelessWidget {
final sel = !sgAuto && sgState == s;
final (label, color) = switch (s) {
SGReadyState.blocked => ('Bloqué', EtmTokens.danger),
SGReadyState.normal => ('Normal', EtmTokens.muted),
SGReadyState.normal => ('Normal', EtmTokens.mutedOf(context)),
SGReadyState.recommended => ('Recommandé', EtmTokens.amber),
SGReadyState.forced => ('Forcé', EtmTokens.green),
};
@ -526,13 +526,13 @@ class _PACCard extends StatelessWidget {
padding: const EdgeInsets.symmetric(vertical: 9),
decoration: BoxDecoration(
color: isBlocked
? EtmTokens.bg
? EtmTokens.bgOf(context)
: sel
? color.withValues(alpha: 0.14)
: EtmTokens.bg,
: EtmTokens.bgOf(context),
borderRadius: BorderRadius.circular(10),
border: Border.all(
color: sel ? color : EtmTokens.line,
color: sel ? color : EtmTokens.lineOf(context),
width: sel ? 1.5 : 1,
),
),
@ -543,8 +543,8 @@ class _PACCard extends StatelessWidget {
size: 11,
weight: sel ? FontWeight.w600 : FontWeight.w400,
color: isBlocked
? EtmTokens.faint
: sel ? color : EtmTokens.muted,
? EtmTokens.faintOf(context)
: sel ? color : EtmTokens.mutedOf(context),
),
),
),
@ -618,7 +618,7 @@ class _DHWCard extends StatelessWidget {
Text('Chauffe-eau',
style: EtmTokens.sans(size: 15, weight: FontWeight.w600)),
Text('Ballon thermodynamique',
style: EtmTokens.sans(size: 11, color: EtmTokens.muted)),
style: EtmTokens.sans(size: 11, color: EtmTokens.mutedOf(context))),
],
),
),
@ -626,9 +626,9 @@ class _DHWCard extends StatelessWidget {
],
),
const Padding(
Padding(
padding: EdgeInsets.symmetric(vertical: 14),
child: Divider(height: 1, color: EtmTokens.line),
child: Divider(height: 1, color: EtmTokens.lineOf(context)),
),
// Températures
@ -639,16 +639,16 @@ class _DHWCard extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Température eau',
style: EtmTokens.sans(size: 11, color: EtmTokens.muted)),
style: EtmTokens.sans(size: 11, color: EtmTokens.mutedOf(context))),
Text('${currentTemp.toStringAsFixed(0)}°C',
style: EtmTokens.mono(size: 28, weight: FontWeight.w700)),
],
),
Icon(Icons.arrow_forward_rounded, color: EtmTokens.faint, size: 20),
Icon(Icons.arrow_forward_rounded, color: EtmTokens.faintOf(context), size: 20),
Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text('Cible', style: EtmTokens.sans(size: 11, color: EtmTokens.muted)),
Text('Cible', style: EtmTokens.sans(size: 11, color: EtmTokens.mutedOf(context))),
Row(
children: [
_TempButton(
@ -692,22 +692,22 @@ class _DHWCard extends StatelessWidget {
duration: const Duration(milliseconds: 180),
padding: const EdgeInsets.symmetric(vertical: 10),
decoration: BoxDecoration(
color: sel ? mc.withValues(alpha: 0.12) : EtmTokens.bg,
color: sel ? mc.withValues(alpha: 0.12) : EtmTokens.bgOf(context),
borderRadius: BorderRadius.circular(12),
border: Border.all(
color: sel ? mc : EtmTokens.line,
color: sel ? mc : EtmTokens.lineOf(context),
width: sel ? 1.5 : 1,
),
),
child: Column(
children: [
Icon(mi, size: 20, color: sel ? mc : EtmTokens.faint),
Icon(mi, size: 20, color: sel ? mc : EtmTokens.faintOf(context)),
const SizedBox(height: 4),
Text(ml,
style: EtmTokens.sans(
size: 12,
weight: sel ? FontWeight.w600 : FontWeight.w400,
color: sel ? mc : EtmTokens.muted)),
color: sel ? mc : EtmTokens.mutedOf(context))),
],
),
),
@ -771,16 +771,16 @@ class _ClimCard extends StatelessWidget {
Text('Climatiseur',
style: EtmTokens.sans(size: 15, weight: FontWeight.w600)),
Text('Rafraîchissement anticipé',
style: EtmTokens.sans(size: 11, color: EtmTokens.muted)),
style: EtmTokens.sans(size: 11, color: EtmTokens.mutedOf(context))),
],
),
),
],
),
const Padding(
Padding(
padding: EdgeInsets.symmetric(vertical: 14),
child: Divider(height: 1, color: EtmTokens.line),
child: Divider(height: 1, color: EtmTokens.lineOf(context)),
),
// Températures
@ -790,16 +790,16 @@ class _ClimCard extends StatelessWidget {
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Actuelle', style: EtmTokens.sans(size: 11, color: EtmTokens.muted)),
Text('Actuelle', style: EtmTokens.sans(size: 11, color: EtmTokens.mutedOf(context))),
Text('${currentTemp.toStringAsFixed(1)}°C',
style: EtmTokens.mono(size: 28, weight: FontWeight.w700)),
],
),
const Icon(Icons.arrow_forward_rounded, color: EtmTokens.faint, size: 20),
Icon(Icons.arrow_forward_rounded, color: EtmTokens.faintOf(context), size: 20),
Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text('Cible', style: EtmTokens.sans(size: 11, color: EtmTokens.muted)),
Text('Cible', style: EtmTokens.sans(size: 11, color: EtmTokens.mutedOf(context))),
Row(
children: [
_TempButton(
@ -849,14 +849,14 @@ class _ClimCard extends StatelessWidget {
Text(
'Sur surplus solaire, pour traverser le pic tarifaire '
'17h–21h sans clim payante.',
style: EtmTokens.sans(size: 11, color: EtmTokens.muted),
style: EtmTokens.sans(size: 11, color: EtmTokens.mutedOf(context)),
),
const SizedBox(height: 8),
Row(
children: [
Text('820 W', style: EtmTokens.mono(size: 12, color: EtmTokens.blue)),
Text(' · 100% solaire',
style: EtmTokens.sans(size: 12, color: EtmTokens.muted)),
style: EtmTokens.sans(size: 12, color: EtmTokens.mutedOf(context))),
],
),
],
@ -879,7 +879,7 @@ class _Card extends StatelessWidget {
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
color: EtmTokens.card,
color: EtmTokens.surfaceOf(context),
borderRadius: BorderRadius.circular(EtmTokens.radiusLg),
boxShadow: EtmTokens.cardShadow,
),
@ -991,7 +991,7 @@ class _SurplusInfo extends StatelessWidget {
Expanded(
child: RichText(
text: TextSpan(
style: EtmTokens.sans(size: 11, color: EtmTokens.muted),
style: EtmTokens.sans(size: 11, color: EtmTokens.mutedOf(context)),
children: [
TextSpan(
text: '$label ',
@ -1010,7 +1010,7 @@ class _SurplusInfo extends StatelessWidget {
Text('${powerKw.toStringAsFixed(1)} kW',
style: EtmTokens.mono(size: 11, color: EtmTokens.green)),
Text('$solarPct% solaire',
style: EtmTokens.sans(size: 10, color: EtmTokens.muted)),
style: EtmTokens.sans(size: 10, color: EtmTokens.mutedOf(context))),
],
),
],

View File

@ -384,8 +384,8 @@ class _LinkItem extends StatelessWidget {
return _DrawerTile(
icon: icon,
label: label,
trailing: const Icon(Icons.open_in_new_rounded,
size: 14, color: EtmTokens.faint),
trailing: Icon(Icons.open_in_new_rounded,
size: 14, color: EtmTokens.faintOf(context)),
onTap: () async {
context.read<NavigationProvider>().closeDrawer();
final uri = Uri.parse(url);
@ -494,8 +494,8 @@ class _StyledExpansion extends StatelessWidget {
child: ExpansionTile(
leading: Icon(icon, size: 20, color: Colors.white.withValues(alpha: 0.92)),
title: Text(label, style: EtmTokens.sans(size: 14, color: Colors.white.withValues(alpha: 0.92))),
iconColor: EtmTokens.faint,
collapsedIconColor: EtmTokens.faint,
iconColor: EtmTokens.faintOf(context),
collapsedIconColor: EtmTokens.faintOf(context),
tilePadding: const EdgeInsets.symmetric(horizontal: 20),
childrenPadding: EdgeInsets.zero,
children: children,
@ -518,7 +518,7 @@ class _SubItem extends StatelessWidget {
},
child: Padding(
padding: const EdgeInsets.fromLTRB(56, 10, 16, 10),
child: Text(label, style: EtmTokens.sans(size: 13, color: EtmTokens.faint)),
child: Text(label, style: EtmTokens.sans(size: 13, color: EtmTokens.faintOf(context))),
),
);
}

View File

@ -153,13 +153,13 @@ class _EnergyScreenState extends State<EnergyScreen> {
return Consumer<NymeaService>(
builder: (context, service, _) {
return Scaffold(
backgroundColor: EtmTokens.bg,
backgroundColor: EtmTokens.bgOf(context),
body: CustomScrollView(
slivers: [
// AppBar
SliverAppBar(
floating: true,
backgroundColor: EtmTokens.bg,
backgroundColor: EtmTokens.bgOf(context),
elevation: 0,
leading: const DrawerMenuButton(),
leadingWidth: 64,
@ -169,7 +169,7 @@ class _EnergyScreenState extends State<EnergyScreen> {
IconButton(
icon: Icon(
Icons.calendar_today_rounded,
color: _selectedDate != null ? EtmTokens.blue : EtmTokens.muted,
color: _selectedDate != null ? EtmTokens.blue : EtmTokens.mutedOf(context),
size: 20,
),
onPressed: _pickDate,
@ -177,8 +177,8 @@ class _EnergyScreenState extends State<EnergyScreen> {
Padding(
padding: const EdgeInsets.only(right: 8),
child: IconButton(
icon: const Icon(Icons.refresh_rounded,
color: EtmTokens.muted, size: 20),
icon: Icon(Icons.refresh_rounded,
color: EtmTokens.mutedOf(context), size: 20),
onPressed: _fetch,
),
),
@ -302,7 +302,7 @@ class _EnergyScreenState extends State<EnergyScreen> {
}
final xInterval = _xInterval();
final labelStyle = EtmTokens.sans(size: 9, color: EtmTokens.muted);
final labelStyle = EtmTokens.sans(size: 9, color: EtmTokens.mutedOf(context));
final yInterval = yMax / 4; // 5 graduations : 0, 25%, 50%, 75%, 100% de yMax
return LineChart(
@ -315,7 +315,7 @@ class _EnergyScreenState extends State<EnergyScreen> {
drawVerticalLine: false,
horizontalInterval: yInterval,
getDrawingHorizontalLine: (_) =>
FlLine(color: EtmTokens.line, strokeWidth: 1),
FlLine(color: EtmTokens.lineOf(context), strokeWidth: 1),
),
borderData: FlBorderData(show: false),
titlesData: FlTitlesData(
@ -450,7 +450,7 @@ class _EnergyScreenState extends State<EnergyScreen> {
));
}
final labelStyle = EtmTokens.sans(size: 9, color: EtmTokens.muted);
final labelStyle = EtmTokens.sans(size: 9, color: EtmTokens.mutedOf(context));
return BarChart(
BarChartData(
@ -468,7 +468,7 @@ class _EnergyScreenState extends State<EnergyScreen> {
gridData: FlGridData(
show: true,
drawVerticalLine: false,
getDrawingHorizontalLine: (_) => FlLine(color: EtmTokens.line, strokeWidth: 1),
getDrawingHorizontalLine: (_) => FlLine(color: EtmTokens.lineOf(context), strokeWidth: 1),
),
borderData: FlBorderData(show: false),
titlesData: FlTitlesData(
@ -606,7 +606,7 @@ class _KpiTile extends StatelessWidget {
child: Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: EtmTokens.card,
color: EtmTokens.surfaceOf(context),
borderRadius: BorderRadius.circular(EtmTokens.radius),
boxShadow: EtmTokens.cardShadow,
),
@ -622,7 +622,7 @@ class _KpiTile extends StatelessWidget {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(label, style: EtmTokens.sans(size: 11, color: EtmTokens.muted)),
Text(label, style: EtmTokens.sans(size: 11, color: EtmTokens.mutedOf(context))),
const SizedBox(height: 2),
Text(value,
style: EtmTokens.mono(size: 15, weight: FontWeight.w700, color: color),
@ -656,7 +656,7 @@ class _PeriodSelector extends StatelessWidget {
return Container(
padding: const EdgeInsets.all(4),
decoration: BoxDecoration(
color: EtmTokens.card,
color: EtmTokens.surfaceOf(context),
borderRadius: BorderRadius.circular(EtmTokens.radius),
boxShadow: EtmTokens.cardShadow,
),
@ -679,7 +679,7 @@ class _PeriodSelector extends StatelessWidget {
style: EtmTokens.sans(
size: 13,
weight: selected ? FontWeight.w600 : FontWeight.w400,
color: selected ? Colors.white : EtmTokens.muted,
color: selected ? Colors.white : EtmTokens.mutedOf(context),
),
),
),
@ -758,7 +758,7 @@ class _EtmChartCard extends StatelessWidget {
return Container(
padding: const EdgeInsets.all(18),
decoration: BoxDecoration(
color: EtmTokens.card,
color: EtmTokens.surfaceOf(context),
borderRadius: BorderRadius.circular(EtmTokens.radiusLg),
boxShadow: EtmTokens.cardShadow,
),
@ -774,7 +774,7 @@ class _EtmChartCard extends StatelessWidget {
Text(title,
style: EtmTokens.sans(size: 16, weight: FontWeight.w600)),
Text(subtitle,
style: EtmTokens.sans(size: 11, color: EtmTokens.muted)),
style: EtmTokens.sans(size: 11, color: EtmTokens.mutedOf(context))),
],
),
],
@ -793,10 +793,10 @@ class _EtmChartCard extends StatelessWidget {
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.bar_chart_rounded,
color: EtmTokens.faint, size: 32),
color: EtmTokens.faintOf(context), size: 32),
const SizedBox(height: 8),
Text('Aucune donnée',
style: EtmTokens.sans(size: 13, color: EtmTokens.muted)),
style: EtmTokens.sans(size: 13, color: EtmTokens.mutedOf(context))),
],
),
)
@ -833,7 +833,7 @@ class _LegendDot extends StatelessWidget {
decoration: BoxDecoration(
color: color, borderRadius: BorderRadius.circular(2))),
const SizedBox(width: 5),
Text(label, style: EtmTokens.sans(size: 11, color: EtmTokens.muted)),
Text(label, style: EtmTokens.sans(size: 11, color: EtmTokens.mutedOf(context))),
],
);
}
@ -849,7 +849,7 @@ class _ForecastPlaceholder extends StatelessWidget {
return Container(
padding: const EdgeInsets.all(18),
decoration: BoxDecoration(
color: EtmTokens.card,
color: EtmTokens.surfaceOf(context),
borderRadius: BorderRadius.circular(EtmTokens.radiusLg),
boxShadow: EtmTokens.cardShadow,
),
@ -862,19 +862,19 @@ class _ForecastPlaceholder extends StatelessWidget {
Text('Météo & prévision',
style: EtmTokens.sans(size: 16, weight: FontWeight.w600)),
Text('aujourd\'hui',
style: EtmTokens.sans(size: 12, color: EtmTokens.muted)),
style: EtmTokens.sans(size: 12, color: EtmTokens.mutedOf(context))),
],
),
const SizedBox(height: 14),
Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: EtmTokens.bg,
color: EtmTokens.bgOf(context),
borderRadius: BorderRadius.circular(12),
),
child: Row(
children: [
const Icon(Icons.cloud_outlined, color: EtmTokens.faint, size: 30),
Icon(Icons.cloud_outlined, color: EtmTokens.faintOf(context), size: 30),
const SizedBox(width: 14),
Expanded(
child: Column(
@ -882,12 +882,12 @@ class _ForecastPlaceholder extends StatelessWidget {
children: [
Text('Prévisions non disponibles',
style: EtmTokens.sans(size: 13, weight: FontWeight.w500,
color: EtmTokens.muted)),
color: EtmTokens.mutedOf(context))),
const SizedBox(height: 4),
Text(
'Configurez un provider dans Tarifs & Héos '
'pour activer les prévisions PV et tarifaires.',
style: EtmTokens.sans(size: 11, color: EtmTokens.faint)),
style: EtmTokens.sans(size: 11, color: EtmTokens.faintOf(context))),
],
),
),

View File

@ -33,12 +33,12 @@ class _FavoritesScreenState extends State<FavoritesScreen> {
final favs = service.favoriteWidgets;
return Scaffold(
backgroundColor: EtmTokens.bg,
backgroundColor: EtmTokens.bgOf(context),
body: CustomScrollView(
slivers: [
SliverAppBar(
floating: true,
backgroundColor: EtmTokens.bg,
backgroundColor: EtmTokens.bgOf(context),
elevation: 0,
leading: const DrawerMenuButton(),
leadingWidth: 64,
@ -143,7 +143,7 @@ class _FavoriteCard extends StatelessWidget {
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
color: EtmTokens.card,
color: EtmTokens.surfaceOf(context),
borderRadius: BorderRadius.circular(EtmTokens.radiusLg),
boxShadow: EtmTokens.cardShadow,
),
@ -154,20 +154,20 @@ class _FavoriteCard extends StatelessWidget {
// En-tête : poignée + titre + supprimer
Row(
children: [
const Icon(Icons.drag_handle_rounded,
color: EtmTokens.faint, size: 20),
Icon(Icons.drag_handle_rounded,
color: EtmTokens.faintOf(context), size: 20),
const SizedBox(width: 8),
Expanded(
child: Text(fav.title,
style: EtmTokens.sans(
size: 14,
weight: FontWeight.w600,
color: EtmTokens.muted)),
color: EtmTokens.mutedOf(context))),
),
GestureDetector(
onTap: onRemove,
child: const Icon(Icons.close_rounded,
color: EtmTokens.faint, size: 18),
child: Icon(Icons.close_rounded,
color: EtmTokens.faintOf(context), size: 18),
),
],
),
@ -263,11 +263,11 @@ class _BigMetric extends StatelessWidget {
Padding(
padding: const EdgeInsets.only(bottom: 5, left: 4),
child: Text(unit,
style: EtmTokens.sans(size: 15, color: EtmTokens.muted)),
style: EtmTokens.sans(size: 15, color: EtmTokens.mutedOf(context))),
),
],
),
Text(label, style: EtmTokens.sans(size: 12, color: EtmTokens.muted)),
Text(label, style: EtmTokens.sans(size: 12, color: EtmTokens.mutedOf(context))),
const SizedBox(height: 2),
Text(sub,
style: EtmTokens.sans(
@ -329,7 +329,7 @@ class _BatteryWidget extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('État de charge',
style: EtmTokens.sans(size: 13, color: EtmTokens.muted)),
style: EtmTokens.sans(size: 13, color: EtmTokens.mutedOf(context))),
const SizedBox(height: 6),
Text(
isCharging
@ -343,7 +343,7 @@ class _BatteryWidget extends StatelessWidget {
const SizedBox(height: 4),
Text(
'${data.batteryPower.abs().toStringAsFixed(0)} W',
style: EtmTokens.sans(size: 12, color: EtmTokens.muted),
style: EtmTokens.sans(size: 12, color: EtmTokens.mutedOf(context)),
),
],
),
@ -409,7 +409,7 @@ class _RateTile extends StatelessWidget {
),
const SizedBox(height: 6),
Text(label,
style: EtmTokens.sans(size: 11, color: EtmTokens.muted),
style: EtmTokens.sans(size: 11, color: EtmTokens.mutedOf(context)),
textAlign: TextAlign.center),
],
);
@ -451,7 +451,7 @@ class _EVWidget extends StatelessWidget {
style: EtmTokens.sans(size: 12, color: color)),
Text(
'${data.solarSourcePercent.toStringAsFixed(0)}% solaire',
style: EtmTokens.sans(size: 11, color: EtmTokens.muted)),
style: EtmTokens.sans(size: 11, color: EtmTokens.mutedOf(context))),
],
),
),
@ -543,7 +543,7 @@ class _MiniChart extends StatelessWidget {
height: 64,
child: Center(
child: Text('Pas de données',
style: EtmTokens.sans(size: 12, color: EtmTokens.muted)),
style: EtmTokens.sans(size: 12, color: EtmTokens.mutedOf(context))),
),
);
}
@ -603,7 +603,7 @@ class _EmptyState extends StatelessWidget {
size: 18, weight: FontWeight.w600)),
const SizedBox(height: 8),
Text('Ajoutez des widgets pour un accès rapide',
style: EtmTokens.sans(size: 14, color: EtmTokens.muted)),
style: EtmTokens.sans(size: 14, color: EtmTokens.mutedOf(context))),
const SizedBox(height: 24),
GestureDetector(
onTap: onAdd,
@ -652,8 +652,8 @@ class _AddSheet extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
decoration: const BoxDecoration(
color: EtmTokens.card,
decoration: BoxDecoration(
color: EtmTokens.surfaceOf(context),
borderRadius: BorderRadius.vertical(top: Radius.circular(24)),
),
child: DraggableScrollableSheet(
@ -668,7 +668,7 @@ class _AddSheet extends StatelessWidget {
margin: const EdgeInsets.only(top: 12, bottom: 18),
width: 40, height: 4,
decoration: BoxDecoration(
color: EtmTokens.line,
color: EtmTokens.lineOf(context),
borderRadius: BorderRadius.circular(2)),
),
Padding(
@ -681,8 +681,8 @@ class _AddSheet extends StatelessWidget {
size: 18, weight: FontWeight.w600)),
GestureDetector(
onTap: () => Navigator.pop(context),
child: const Icon(Icons.close_rounded,
color: EtmTokens.faint),
child: Icon(Icons.close_rounded,
color: EtmTokens.faintOf(context)),
),
],
),
@ -713,12 +713,12 @@ class _AddSheet extends StatelessWidget {
child: Container(
padding: const EdgeInsets.all(14),
decoration: BoxDecoration(
color: added ? EtmTokens.bg : EtmTokens.card,
color: added ? EtmTokens.bgOf(context) : EtmTokens.surfaceOf(context),
borderRadius:
BorderRadius.circular(EtmTokens.radius),
border: Border.all(
color: added
? EtmTokens.line
? EtmTokens.lineOf(context)
: color.withValues(alpha: 0.3),
),
boxShadow: added ? null : EtmTokens.cardShadow,
@ -732,7 +732,7 @@ class _AddSheet extends StatelessWidget {
borderRadius: BorderRadius.circular(12),
),
child: Icon(icon,
color: added ? EtmTokens.faint : color,
color: added ? EtmTokens.faintOf(context) : color,
size: 22),
),
const SizedBox(width: 14),
@ -745,12 +745,12 @@ class _AddSheet extends StatelessWidget {
size: 14,
weight: FontWeight.w600,
color: added
? EtmTokens.faint
? EtmTokens.faintOf(context)
: EtmTokens.navy)),
Text(subtitle,
style: EtmTokens.sans(
size: 11,
color: EtmTokens.muted)),
color: EtmTokens.mutedOf(context))),
],
),
),

View File

@ -62,13 +62,13 @@ class _ThingsScreenState extends State<ThingsScreen> {
final offlineCount = things.where((t) => !_isOnline(t, _classFor(t, classes))).length;
return Scaffold(
backgroundColor: EtmTokens.bg,
backgroundColor: EtmTokens.bgOf(context),
body: CustomScrollView(
slivers: [
// AppBar
SliverAppBar(
floating: true,
backgroundColor: EtmTokens.bg,
backgroundColor: EtmTokens.bgOf(context),
elevation: 0,
leading: const DrawerMenuButton(),
leadingWidth: 64,
@ -94,8 +94,8 @@ class _ThingsScreenState extends State<ThingsScreen> {
Padding(
padding: const EdgeInsets.only(right: 8),
child: IconButton(
icon: const Icon(Icons.refresh_rounded,
color: EtmTokens.muted, size: 20),
icon: Icon(Icons.refresh_rounded,
color: EtmTokens.mutedOf(context), size: 20),
onPressed: service.refresh,
),
),
@ -197,14 +197,14 @@ class _StatusBanner extends StatelessWidget {
dotColor = EtmTokens.orange;
text = '$offlineCount appareil(s) hors ligne';
} else {
dotColor = EtmTokens.faint;
dotColor = EtmTokens.faintOf(context);
text = 'Aucun appareil connecté';
}
return Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
decoration: BoxDecoration(
color: EtmTokens.card,
color: EtmTokens.surfaceOf(context),
borderRadius: BorderRadius.circular(EtmTokens.radius),
boxShadow: EtmTokens.cardShadow,
),
@ -217,7 +217,7 @@ class _StatusBanner extends StatelessWidget {
const SizedBox(width: 10),
Expanded(
child: Text(text,
style: EtmTokens.sans(size: 13, color: EtmTokens.muted)),
style: EtmTokens.sans(size: 13, color: EtmTokens.mutedOf(context))),
),
],
),
@ -334,7 +334,7 @@ class _CategoryCard extends StatelessWidget {
// Container en dehors de Material pour que l'ombre soit visible
return Container(
decoration: BoxDecoration(
color: EtmTokens.card,
color: EtmTokens.surfaceOf(context),
borderRadius: BorderRadius.circular(EtmTokens.radius),
boxShadow: EtmTokens.cardShadow,
),
@ -379,7 +379,7 @@ class _CategoryCard extends StatelessWidget {
),
Text(
'$count appareil${count > 1 ? 's' : ''}',
style: EtmTokens.sans(size: 11, color: EtmTokens.muted),
style: EtmTokens.sans(size: 11, color: EtmTokens.mutedOf(context)),
),
],
),
@ -431,7 +431,7 @@ class _ThingRow extends StatelessWidget {
decoration: BoxDecoration(
border: isLast
? null
: Border(bottom: BorderSide(color: EtmTokens.line)),
: Border(bottom: BorderSide(color: EtmTokens.lineOf(context))),
borderRadius: isLast
? const BorderRadius.vertical(bottom: Radius.circular(EtmTokens.radius))
: null,
@ -441,7 +441,7 @@ class _ThingRow extends StatelessWidget {
Container(
width: 7, height: 7,
decoration: BoxDecoration(
color: isOnline ? color : EtmTokens.faint,
color: isOnline ? color : EtmTokens.faintOf(context),
shape: BoxShape.circle,
),
),
@ -461,7 +461,7 @@ class _ThingRow extends StatelessWidget {
style: EtmTokens.mono(
size: 12,
weight: FontWeight.w700,
color: isOnline ? color : EtmTokens.muted,
color: isOnline ? color : EtmTokens.mutedOf(context),
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
@ -471,7 +471,7 @@ class _ThingRow extends StatelessWidget {
isOnline ? 'OK' : 'Veille',
style: EtmTokens.sans(
size: 12,
color: isOnline ? color : EtmTokens.faint,
color: isOnline ? color : EtmTokens.faintOf(context),
),
),
],
@ -495,9 +495,9 @@ class _Placeholder extends StatelessWidget {
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(icon, size: 56, color: EtmTokens.faint),
Icon(icon, size: 56, color: EtmTokens.faintOf(context)),
const SizedBox(height: 14),
Text(label, style: EtmTokens.sans(size: 15, color: EtmTokens.muted)),
Text(label, style: EtmTokens.sans(size: 15, color: EtmTokens.mutedOf(context))),
],
),
),

View File

@ -883,6 +883,12 @@ class NymeaService extends ChangeNotifier {
batteryPower: bat,
dayProductionWh: totalProdKwh * 1000,
dayGridInjectionWh: totalRetKwh * 1000,
// Compteurs CUMULÉS, transportés tels quels : ni corrigés, ni recalculés. Leur
// plausibilité se juge à l'affichage (EnergyData.isPlausibleKwh) — l'app ne
// maquille pas une donnée que la box publie fausse, elle refuse de la présenter
// comme une mesure.
gridExportKwh: totalRetKwh,
gridImportKwh: totalAcqKwh,
daySelfConsumptionWh: selfConsoKwh * 1000,
// n/a (seam → null) mappé à 0 pour le champ non-nullable (UX §2 item 4).
selfConsumptionRate: ratios.autoconsommation ?? 0.0,

View File

@ -39,7 +39,7 @@ class _EnergyFlowWidgetState extends State<EnergyFlowWidget>
final data = widget.data;
return Container(
decoration: BoxDecoration(
color: EtmTokens.card,
color: EtmTokens.surfaceOf(context),
borderRadius: BorderRadius.circular(EtmTokens.radiusLg),
boxShadow: EtmTokens.cardShadow,
),
@ -56,9 +56,9 @@ class _EnergyFlowWidgetState extends State<EnergyFlowWidget>
Row(
children: [
Text('${data.temperature.toStringAsFixed(0)}°C',
style: EtmTokens.sans(size: 14, color: EtmTokens.muted)),
style: EtmTokens.sans(size: 14, color: EtmTokens.mutedOf(context))),
const SizedBox(width: 4),
const Icon(Icons.wb_cloudy_outlined, color: EtmTokens.faint, size: 20),
Icon(Icons.wb_cloudy_outlined, color: EtmTokens.faintOf(context), size: 20),
],
),
],
@ -357,7 +357,7 @@ class _Node extends StatelessWidget {
color: Colors.white,
shape: BoxShape.circle,
border: Border.all(
color: borderColor ?? EtmTokens.line,
color: borderColor ?? EtmTokens.lineOf(context),
width: 1.5,
),
boxShadow: ringColor != null
@ -382,14 +382,14 @@ class _Node extends StatelessWidget {
const SizedBox(height: 2),
Text(value, style: EtmTokens.mono(size: r * 0.32, color: valueColor)),
if (sub != null)
Text(sub!, style: EtmTokens.mono(size: r * 0.26, color: subColor ?? EtmTokens.muted)),
Text(sub!, style: EtmTokens.mono(size: r * 0.26, color: subColor ?? EtmTokens.mutedOf(context))),
],
),
),
const SizedBox(height: 4),
Text(
label,
style: EtmTokens.sans(size: 11, color: EtmTokens.muted),
style: EtmTokens.sans(size: 11, color: EtmTokens.mutedOf(context)),
textAlign: TextAlign.center,
),
],
@ -426,11 +426,11 @@ class _OptimalBanner extends StatelessWidget {
Text('Mode optimal',
style: EtmTokens.sans(size: 14, weight: FontWeight.w600, color: EtmTokens.greenDark)),
Text('Héos optimise votre consommation',
style: EtmTokens.sans(size: 12, color: EtmTokens.muted)),
style: EtmTokens.sans(size: 12, color: EtmTokens.mutedOf(context))),
],
),
),
const Icon(Icons.chevron_right, color: EtmTokens.faint, size: 20),
Icon(Icons.chevron_right, color: EtmTokens.faintOf(context), size: 20),
],
),
);

View File

@ -128,7 +128,7 @@ class _EVChargingCardState extends State<EVChargingCard> {
return Container(
decoration: BoxDecoration(
color: EtmTokens.card,
color: EtmTokens.surfaceOf(context),
borderRadius: BorderRadius.circular(EtmTokens.radiusLg),
boxShadow: EtmTokens.cardShadow,
),
@ -155,13 +155,13 @@ class _EVChargingCardState extends State<EVChargingCard> {
Text('Borne de recharge (EVSE)',
style: EtmTokens.sans(size: 15, weight: FontWeight.w600)),
Text(_statusLabel(mode),
style: EtmTokens.sans(size: 12, color: EtmTokens.muted)),
style: EtmTokens.sans(size: 12, color: EtmTokens.mutedOf(context))),
],
),
),
GestureDetector(
onTap: () => _showSettings(context),
child: const Icon(Icons.tune, color: EtmTokens.faint, size: 20),
child: Icon(Icons.tune, color: EtmTokens.faintOf(context), size: 20),
),
],
),
@ -197,7 +197,7 @@ class _EVChargingCardState extends State<EVChargingCard> {
style: EtmTokens.mono(size: 38, weight: FontWeight.w700),
),
Text('W Puissance actuelle',
style: EtmTokens.sans(size: 12, color: EtmTokens.muted)),
style: EtmTokens.sans(size: 12, color: EtmTokens.mutedOf(context))),
],
),
const Spacer(),
@ -206,7 +206,7 @@ class _EVChargingCardState extends State<EVChargingCard> {
children: [
Text('${data.solarSourcePercent.toStringAsFixed(0)}%',
style: EtmTokens.mono(size: 22, color: EtmTokens.green)),
Text('solaire', style: EtmTokens.sans(size: 11, color: EtmTokens.muted)),
Text('solaire', style: EtmTokens.sans(size: 11, color: EtmTokens.mutedOf(context))),
],
),
],
@ -321,11 +321,11 @@ class _DeadlineToggleRow extends StatelessWidget {
Row(
children: [
Icon(Icons.flag_outlined, size: 16,
color: enabled ? EtmTokens.blue : EtmTokens.faint),
color: enabled ? EtmTokens.blue : EtmTokens.faintOf(context)),
const SizedBox(width: 6),
Text('Cible deadline',
style: EtmTokens.sans(size: 13,
color: enabled ? EtmTokens.blue : EtmTokens.muted,
color: enabled ? EtmTokens.blue : EtmTokens.mutedOf(context),
weight: enabled ? FontWeight.w600 : FontWeight.w400)),
],
),
@ -480,7 +480,7 @@ class _SocProgress extends StatelessWidget {
],
),
const SizedBox(height: 2),
Text('Aujourd\'hui à 07:30', style: EtmTokens.sans(size: 11, color: EtmTokens.muted)),
Text('Aujourd\'hui à 07:30', style: EtmTokens.sans(size: 11, color: EtmTokens.mutedOf(context))),
const SizedBox(height: 8),
ClipRRect(
borderRadius: BorderRadius.circular(99),
@ -621,12 +621,12 @@ class _TimePicker extends StatelessWidget {
),
child: Row(
children: [
const Icon(Icons.access_time, size: 16, color: EtmTokens.faint),
Icon(Icons.access_time, size: 16, color: EtmTokens.faintOf(context)),
const SizedBox(width: 8),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(label, style: EtmTokens.sans(size: 11, color: EtmTokens.muted)),
Text(label, style: EtmTokens.sans(size: 11, color: EtmTokens.mutedOf(context))),
Text(
'${time.hour.toString().padLeft(2, '0')}:${time.minute.toString().padLeft(2, '0')}',
style: EtmTokens.mono(size: 13),