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

64 lines
1.5 KiB
Dart

import 'package:flutter/material.dart';
/// Icône du cœur HEMS (« Héos ») : étincelle de marque au centre,
/// 4 nœuds pilotés alignés sur les pointes.
class HemsIcon extends StatelessWidget {
final double size;
final Color? color;
const HemsIcon({super.key, this.size = 24, this.color});
@override
Widget build(BuildContext context) {
final c = color ??
IconTheme.of(context).color ??
const Color(0xFF28A06A);
return SizedBox(
width: size,
height: size,
child: CustomPaint(painter: _HemsPainter(c)),
);
}
}
class _HemsPainter extends CustomPainter {
final Color color;
_HemsPainter(this.color);
static const _nodes = <Offset>[
Offset(12, 3.4),
Offset(20.6, 12),
Offset(12, 20.6),
Offset(3.4, 12),
];
static const _nodeR = 1.7;
@override
void paint(Canvas canvas, Size size) {
final s = size.width / 24.0;
final paint = Paint()
..color = color
..style = PaintingStyle.fill
..isAntiAlias = true;
final spark = Path()
..moveTo(12 * s, 7.4 * s)
..lineTo(13.25 * s, 10.75 * s)
..lineTo(16.6 * s, 12 * s)
..lineTo(13.25 * s, 13.25 * s)
..lineTo(12 * s, 16.6 * s)
..lineTo(10.75 * s, 13.25 * s)
..lineTo(7.4 * s, 12 * s)
..lineTo(10.75 * s, 10.75 * s)
..close();
canvas.drawPath(spark, paint);
for (final n in _nodes) {
canvas.drawCircle(Offset(n.dx * s, n.dy * s), _nodeR * s, paint);
}
}
@override
bool shouldRepaint(_HemsPainter old) => old.color != color;
}