etm-powersync-app/lib/models/thing_category.dart
Patrick Schurig 38475e0007 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
2026-08-25 23:13:20 +02:00

205 lines
8.1 KiB
Dart

import 'package:flutter/material.dart';
import '../theme/etm_tokens.dart';
import 'nymea_models.dart';
// ─────────────────────────────────────────────────────────────────────────────
// Catégories affichées dans le Things screen
// ─────────────────────────────────────────────────────────────────────────────
enum ThingCategory {
energy, // smartmeter, energymeter, powersocket
solar, // solarinverter, solarpanel
battery, // battery, energystorage
evCharger, // evcharger, wallbox
cars, // evvehicle, electricvehicle + class name "car"/"vehicle"
hvac, // thermostat, heatingzone, ac, pump
lighting, // light, dimmablelight, colorlight
sensors, // sensor, temperaturesensor, humiditysensor, motionsensor
network, // networkdevice, gateway
notifications, // pushnotification, email, sms
media, // mediadevice, mediaplayer
weather, // weather
other, // tout le reste
}
class ThingCategoryInfo {
final ThingCategory category;
final String label;
final IconData icon;
final Color color;
const ThingCategoryInfo({
required this.category,
required this.label,
required this.icon,
required this.color,
});
}
// ─────────────────────────────────────────────────────────────────────────────
// Mapping interface nymea → catégorie
// ─────────────────────────────────────────────────────────────────────────────
const Map<String, ThingCategory> interfaceToCategoryMap = {
'smartmeter': ThingCategory.energy,
'electricmeter': ThingCategory.energy,
'energymeter': ThingCategory.energy,
'smartenergymeter': ThingCategory.energy,
'extendedsmartmeter': ThingCategory.energy,
'powersocket': ThingCategory.energy,
'smartplug': ThingCategory.energy,
'solarinverter': ThingCategory.solar,
'solarpanel': ThingCategory.solar,
'inverter': ThingCategory.solar,
'battery': ThingCategory.battery,
'energystorage': ThingCategory.battery,
'batterymonitor': ThingCategory.battery,
'evcharger': ThingCategory.evCharger,
'wallbox': ThingCategory.evCharger,
'evvehicle': ThingCategory.cars,
'electricvehicle': ThingCategory.cars,
'electriccar': ThingCategory.cars,
'vehicle': ThingCategory.cars,
'thermostat': ThingCategory.hvac,
'heatingzone': ThingCategory.hvac,
'ac': ThingCategory.hvac,
'airconditioner': ThingCategory.hvac,
'pump': ThingCategory.hvac,
'heatpump': ThingCategory.hvac,
'simpleheatpump': ThingCategory.hvac, // interfaces réelles (hems)
'smartgridheatpump': ThingCategory.hvac,
'ventilation': ThingCategory.hvac,
'boiler': ThingCategory.hvac,
'light': ThingCategory.lighting,
'dimmablelight': ThingCategory.lighting,
'colorlight': ThingCategory.lighting,
'colortemperaturelight': ThingCategory.lighting,
'sensor': ThingCategory.sensors,
'temperaturesensor': ThingCategory.sensors,
'humiditysensor': ThingCategory.sensors,
'pressuresensor': ThingCategory.sensors,
'motionsensor': ThingCategory.sensors,
'doorsensor': ThingCategory.sensors,
'windowsensor': ThingCategory.sensors,
'smokesensor': ThingCategory.sensors,
'co2sensor': ThingCategory.sensors,
'lightsensor': ThingCategory.sensors,
'networkdevice': ThingCategory.network,
'gateway': ThingCategory.network,
'router': ThingCategory.network,
'pushnotification': ThingCategory.notifications,
'notificationservice': ThingCategory.notifications,
'notification': ThingCategory.notifications,
'email': ThingCategory.notifications,
'sms': ThingCategory.notifications,
'weather': ThingCategory.weather,
'weatherstation': ThingCategory.weather,
'mediadevice': ThingCategory.media,
'mediaplayer': ThingCategory.media,
};
// ─────────────────────────────────────────────────────────────────────────────
// Infos visuelles par catégorie
// ─────────────────────────────────────────────────────────────────────────────
Map<ThingCategory, ThingCategoryInfo> categoryInfoMap = {
ThingCategory.energy: ThingCategoryInfo(
category: ThingCategory.energy,
label: 'Compteurs & Prises',
icon: Icons.electrical_services_rounded,
color: EtmTokens.muted,
),
ThingCategory.solar: ThingCategoryInfo(
category: ThingCategory.solar,
label: 'Solaire',
icon: Icons.solar_power_rounded,
color: EtmTokens.amber,
),
ThingCategory.battery: ThingCategoryInfo(
category: ThingCategory.battery,
label: 'Stockage',
icon: Icons.battery_charging_full_rounded,
color: EtmTokens.green,
),
ThingCategory.evCharger: ThingCategoryInfo(
category: ThingCategory.evCharger,
label: 'Chargeurs EV',
icon: Icons.electric_car_rounded,
color: EtmTokens.blue,
),
ThingCategory.cars: ThingCategoryInfo(
category: ThingCategory.cars,
label: 'Véhicules',
icon: Icons.directions_car_rounded,
color: EtmTokens.blue,
),
ThingCategory.hvac: ThingCategoryInfo(
category: ThingCategory.hvac,
label: 'Chauffage & Clim',
icon: Icons.thermostat_rounded,
color: EtmTokens.orange,
),
ThingCategory.lighting: ThingCategoryInfo(
category: ThingCategory.lighting,
label: 'Éclairage',
icon: Icons.lightbulb_rounded,
color: EtmTokens.amber,
),
ThingCategory.sensors: ThingCategoryInfo(
category: ThingCategory.sensors,
label: 'Capteurs',
icon: Icons.sensors_rounded,
color: EtmTokens.blue,
),
ThingCategory.network: ThingCategoryInfo(
category: ThingCategory.network,
label: 'Réseau',
icon: Icons.router_rounded,
color: Color(0xFF7C4DFF),
),
ThingCategory.notifications: ThingCategoryInfo(
category: ThingCategory.notifications,
label: 'Notifications',
icon: Icons.notifications_rounded,
color: Color(0xFFEC407A),
),
ThingCategory.weather: ThingCategoryInfo(
category: ThingCategory.weather,
label: 'Météo',
icon: Icons.wb_cloudy_rounded,
color: EtmTokens.blue,
),
ThingCategory.media: ThingCategoryInfo(
category: ThingCategory.media,
label: 'Multimédia',
icon: Icons.speaker_rounded,
color: Color(0xFFAB47BC),
),
ThingCategory.other: ThingCategoryInfo(
category: ThingCategory.other,
label: 'Autres',
icon: Icons.device_hub_rounded,
color: EtmTokens.faint,
),
};
// ─────────────────────────────────────────────────────────────────────────────
// Extension utilitaire sur NymeaThingClass
// ─────────────────────────────────────────────────────────────────────────────
extension ThingClassCategoryExt on NymeaThingClass {
ThingCategory get category {
for (final iface in interfaces) {
final cat = interfaceToCategoryMap[iface.toLowerCase()];
if (cat != null) return cat;
}
// Fallback : nom de classe contient "car" ou "vehicle" → catégorie Cars
final lower = name.toLowerCase();
if (lower.contains('car') || lower.contains('vehicle')) {
return ThingCategory.cars;
}
return ThingCategory.other;
}
ThingCategoryInfo get categoryDetails => categoryInfoMap[category]!;
}