Patrick Schurig c638ec6c52 feat: connexions multi-HEMS + rôles & appareils (beta add/config)
Lot Connexions multi-HEMS :
- modèle Installation + InstallationStore (persistance par UUID ;
  métadonnées SharedPreferences, token via flutter_secure_storage)
- ConnectionManager au-dessus de nymea_service : mono-connexion, switchTo,
  connectNew, enterDemo (démo = active factice), contrôle d'identité DHCP
- nymea_service : connect() réveillé (ws://4444, token par UUID), capture
  Hello (uuid/name/initialSetupRequired), authenticate/createUser, resetState
- écran Installations (3 états) + déverrouillage installateur depuis la racine
- écran Connexion : auth 2 branches (JSONRPC.Authenticate / CreateUser),
  détection auto via initialSetupRequired, segmenteur de repli
- gate de routage (redirect + refreshListenable merge[cm,svc]) ;
  hasActiveConnection = isSimulation || (connected && authenticated)
- en-tête drawer cliquable → Installations
- invalidation des caches au switch (service.resetState + clearForSwitch
  rôles/scheduler/tariff), sans persist()

Lot Rôles & appareils :
- EnergySetupProvider refondu en tri-état (present/absent/non-configuré)
- écran roles_devices_screen (3 zones, drag-priorité, inférence solaire/batterie)
- LoadDescriptor (etmvariableload, rév.2 : PAC exclue → SG-Ready) ;
  Energy.SetRootMeter réel, Get/SetLoadConfig en stub loggé

Correctifs :
- bug signe énergie : production +, consommation NÉGATIVE sur nymea 1.15.2
  → consumptionW/home en .abs() (autoconso n'est plus clouée à 0)
- UUID Hello brace-wrapped normalisé

Tests : flutter analyze 0 erreur ; gate + zéro-fuite au switch (5/5 verts).
Validé en vrai : login auth .120, .75 ouvert, switch, déverrouillage installateur.
Note : embarque aussi le WIP dashboard/thème déjà présent dans l'arbre.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 08:39:08 +02:00

98 lines
2.9 KiB
Dart

import 'package:flutter/material.dart';
import '../../../theme/etm_tokens.dart';
import '../models/dashboard_models.dart';
import '../painters/day_plan_painter.dart';
class DayPlanCard extends StatelessWidget {
const DayPlanCard({super.key});
@override
Widget build(BuildContext context) {
final plan = DayPlan.demo();
final isDark = Theme.of(context).brightness == Brightness.dark;
return Container(
decoration: BoxDecoration(
color: EtmTokens.surfaceOf(context),
borderRadius: BorderRadius.circular(EtmTokens.radiusCard),
boxShadow: EtmTokens.cardShadowOf(context),
),
padding: const EdgeInsets.fromLTRB(16, 16, 16, 12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text('Plan de la journée',
style: EtmTokens.sans(
size: 15,
weight: FontWeight.w600,
color: EtmTokens.inkOf(context))),
Text("aujourd'hui",
style: EtmTokens.sans(
size: 12, color: EtmTokens.mutedOf(context))),
],
),
const SizedBox(height: 12),
SizedBox(
height: 180,
child: CustomPaint(
painter: DayPlanPainter(
plan: plan,
surfaceColor: EtmTokens.surfaceOf(context),
lineColor: EtmTokens.lineOf(context),
inkColor: EtmTokens.inkOf(context),
mutedColor: EtmTokens.mutedOf(context),
isDark: isDark,
),
size: const Size(double.infinity, 180),
),
),
const SizedBox(height: 12),
_Legend(context: context),
],
),
);
}
}
class _Legend extends StatelessWidget {
final BuildContext context;
const _Legend({required this.context});
@override
Widget build(BuildContext _) {
final items = [
('Maison', EtmTokens.solToHouse),
('ECS', EtmTokens.solToEcs),
('VE', EtmTokens.solToVe),
('Batterie', EtmTokens.solToBatt),
('Réseau', EtmTokens.solToGrid),
('Batt.→maison', EtmTokens.battToHouse),
];
return Wrap(
spacing: 12,
runSpacing: 6,
children: items.map((e) {
return Row(
mainAxisSize: MainAxisSize.min,
children: [
Container(
width: 10, height: 10,
decoration: BoxDecoration(
color: e.$2,
borderRadius: BorderRadius.circular(2),
),
),
const SizedBox(width: 4),
Text(e.$1,
style: EtmTokens.sans(
size: 10, color: EtmTokens.mutedOf(context))),
],
);
}).toList(),
);
}
}