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:
parent
15a52637a5
commit
38475e0007
@ -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),
|
||||
|
||||
@ -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,
|
||||
});
|
||||
|
||||
}
|
||||
@ -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',
|
||||
|
||||
@ -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))),
|
||||
],
|
||||
),
|
||||
],
|
||||
|
||||
@ -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))),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@ -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))),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@ -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))),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@ -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))),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
@ -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),
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user