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>
85 lines
2.1 KiB
Dart
85 lines
2.1 KiB
Dart
import 'package:flutter/material.dart';
|
|
|
|
class FlowSegment {
|
|
final Offset from;
|
|
final Offset to;
|
|
final Color color;
|
|
final double powerW;
|
|
final bool dashed;
|
|
|
|
const FlowSegment({
|
|
required this.from,
|
|
required this.to,
|
|
required this.color,
|
|
required this.powerW,
|
|
this.dashed = false,
|
|
});
|
|
}
|
|
|
|
class FlowPainter extends CustomPainter {
|
|
final List<FlowSegment> segments;
|
|
final double animValue;
|
|
final Color lineColor;
|
|
|
|
const FlowPainter({
|
|
required this.segments,
|
|
required this.animValue,
|
|
required this.lineColor,
|
|
});
|
|
|
|
@override
|
|
void paint(Canvas canvas, Size size) {
|
|
final linePaint = Paint()
|
|
..color = lineColor
|
|
..strokeWidth = 1.5
|
|
..style = PaintingStyle.stroke
|
|
..strokeCap = StrokeCap.round;
|
|
|
|
for (final seg in segments) {
|
|
final src = Offset(seg.from.dx * size.width, seg.from.dy * size.height);
|
|
final dst = Offset(seg.to.dx * size.width, seg.to.dy * size.height);
|
|
|
|
if (seg.dashed) {
|
|
_drawDashed(canvas, src, dst, linePaint);
|
|
} else {
|
|
canvas.drawLine(src, dst, linePaint);
|
|
}
|
|
|
|
if (seg.powerW > 30) {
|
|
final n = (seg.powerW / 600).clamp(1, 5).round();
|
|
final r = seg.powerW > 1500 ? 3.5 : 2.5;
|
|
final particlePaint = Paint()
|
|
..color = seg.color
|
|
..style = PaintingStyle.fill;
|
|
|
|
for (int i = 0; i < n; i++) {
|
|
final t = (animValue + i / n) % 1.0;
|
|
final pos = Offset.lerp(src, dst, t)!;
|
|
canvas.drawCircle(pos, r, particlePaint);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void _drawDashed(Canvas canvas, Offset src, Offset dst, Paint paint) {
|
|
final dir = dst - src;
|
|
final length = dir.distance;
|
|
if (length == 0) return;
|
|
final unit = dir / length;
|
|
const dashLen = 5.0;
|
|
const gapLen = 4.0;
|
|
double pos = 0;
|
|
while (pos < length) {
|
|
final end = (pos + dashLen).clamp(0.0, length);
|
|
canvas.drawLine(src + unit * pos, src + unit * end, paint);
|
|
pos += dashLen + gapLen;
|
|
}
|
|
}
|
|
|
|
@override
|
|
bool shouldRepaint(FlowPainter old) =>
|
|
old.animValue != animValue ||
|
|
old.lineColor != lineColor ||
|
|
old.segments.length != segments.length;
|
|
}
|