diff --git a/lib/features/dashboard/models/dashboard_models.dart b/lib/features/dashboard/models/dashboard_models.dart index 408dddc..cb28ee9 100644 --- a/lib/features/dashboard/models/dashboard_models.dart +++ b/lib/features/dashboard/models/dashboard_models.dart @@ -1,104 +1,16 @@ -import 'dart:math' as math; +/// Modèles du tableau de bord. +/// +/// 🔴 `HourlyFlow`, `WeatherPoint` et `DayPlan` ont été **supprimés** le 2026-09-08 avec la +/// carte « Plan de la journée » qu'ils alimentaient. `DayPlan.demo()` fabriquait vingt-quatre +/// heures de flux à partir d'une gaussienne et de coefficients écrits en dur, présentées sans +/// mention comme la journée en cours. +/// +/// Le code part **avec** la carte, délibérément : un générateur de données plausibles laissé +/// dans le dépôt se rebranche — c'est ce qui s'était passé pour les deux MPPT, les 38 % de +/// PAC, les 82 % de part solaire et le SOC à 62 %. Un plan reviendra quand il y aura une +/// prévision PV et une source tarifaire ; il se réécrira sur des données, pas sur celles-ci. +library; -enum WeatherKind { sun, partlyCloudy, cloudy, moon } - -class HourlyFlow { - final int hour; - final double solToHouse; - final double solToEcs; - final double solToVe; - final double solToBatt; - final double solToGrid; - final double battToHouse; - final double gridToHouse; - - const HourlyFlow({ - required this.hour, - this.solToHouse = 0, - this.solToEcs = 0, - this.solToVe = 0, - this.solToBatt = 0, - this.solToGrid = 0, - this.battToHouse = 0, - this.gridToHouse = 0, - }); - - double get totalPositive => - solToHouse + solToEcs + solToVe + solToBatt + solToGrid; - double get totalNegative => battToHouse + gridToHouse; -} - -class WeatherPoint { - final int hour; - final WeatherKind kind; - final int tempC; - - const WeatherPoint({ - required this.hour, - required this.kind, - required this.tempC, - }); -} - -class DayPlan { - final List hours; - final List weather; - final double nowHour; - - const DayPlan({ - required this.hours, - required this.weather, - required this.nowHour, - }); - - static DayPlan demo() { - final now = DateTime.now(); - final nowHour = now.hour + now.minute / 60.0; - - final hours = List.generate(24, (h) { - final solar = h >= 6 && h <= 20 - ? 3.8 * math.exp(-math.pow(h - 13, 2) / 18.0) - : 0.0; - final baseHouse = 1.2; - final peakHouse = (h >= 7 && h <= 9) || (h >= 18 && h <= 21) ? 0.7 : 0.0; - final house = baseHouse + peakHouse; - - final excess = math.max(0.0, solar - house); - final deficit = math.max(0.0, house - solar); - - final solToHouse = math.min(solar, house); - final solToEcs = excess > 0.5 ? math.min(excess * 0.35, 0.7) : 0.0; - final solToVe = excess > 1.2 ? math.min(excess * 0.3, 1.1) : 0.0; - final solToBatt = excess > 0.3 ? math.min(excess * 0.15, 0.4) : 0.0; - final solToGrid = math.max(0.0, excess - solToEcs - solToVe - solToBatt); - - final battToHouse = h >= 17 ? math.min(deficit * 0.55, 0.8) : 0.0; - final gridToHouse = math.max(0.0, deficit - battToHouse); - - return HourlyFlow( - hour: h, - solToHouse: solToHouse, - solToEcs: solToEcs, - solToVe: solToVe, - solToBatt: solToBatt, - solToGrid: solToGrid, - battToHouse: battToHouse, - gridToHouse: gridToHouse, - ); - }); - - final weather = [ - const WeatherPoint(hour: 0, kind: WeatherKind.moon, tempC: 17), - const WeatherPoint(hour: 4, kind: WeatherKind.moon, tempC: 15), - const WeatherPoint(hour: 8, kind: WeatherKind.partlyCloudy, tempC: 19), - const WeatherPoint(hour: 12, kind: WeatherKind.sun, tempC: 25), - const WeatherPoint(hour: 16, kind: WeatherKind.sun, tempC: 27), - const WeatherPoint(hour: 20, kind: WeatherKind.partlyCloudy, tempC: 22), - ]; - - return DayPlan(hours: hours, weather: weather, nowHour: nowHour); - } -} class HeosAction { final String iconKey; diff --git a/lib/features/dashboard/painters/day_plan_painter.dart b/lib/features/dashboard/painters/day_plan_painter.dart deleted file mode 100644 index 95ad712..0000000 --- a/lib/features/dashboard/painters/day_plan_painter.dart +++ /dev/null @@ -1,296 +0,0 @@ -import 'dart:math' as math; -import 'package:flutter/material.dart'; -import '../../../theme/etm_tokens.dart'; -import '../models/dashboard_models.dart'; - -class DayPlanPainter extends CustomPainter { - final DayPlan plan; - final Color surfaceColor; - final Color lineColor; - final Color inkColor; - final Color mutedColor; - final bool isDark; - - const DayPlanPainter({ - required this.plan, - required this.surfaceColor, - required this.lineColor, - required this.inkColor, - required this.mutedColor, - required this.isDark, - }); - - static const double _weatherH = 44.0; - static const double _xAxisH = 22.0; - static const double _leftPad = 28.0; - static const double _maxPosKwh = 4.0; - static const double _maxNegKwh = 1.8; - - @override - void paint(Canvas canvas, Size size) { - final chartH = size.height - _weatherH - _xAxisH; - final chartW = size.width - _leftPad; - final zeroY = _weatherH + chartH * (_maxPosKwh / (_maxPosKwh + _maxNegKwh)); - - final posScale = chartH * (_maxPosKwh / (_maxPosKwh + _maxNegKwh)) / _maxPosKwh; - final negScale = chartH * (_maxNegKwh / (_maxPosKwh + _maxNegKwh)) / _maxNegKwh; - - final nowX = _leftPad + plan.nowHour / 24 * chartW; - - // ── Future background - if (nowX < size.width) { - canvas.drawRect( - Rect.fromLTRB(nowX, _weatherH, size.width, _weatherH + chartH), - Paint() - ..color = (isDark - ? const Color(0xFF06141D) - : const Color(0xFFEEF2F5)) - .withValues(alpha: 0.55), - ); - _drawHatch(canvas, Rect.fromLTRB(nowX, _weatherH, size.width, _weatherH + chartH)); - } - - // ── Bars (24 hours) - final barW = chartW / 24; - for (final h in plan.hours) { - final x = _leftPad + h.hour * barW; - _drawStackedBars(canvas, x, barW, zeroY, posScale, negScale, h); - } - - // ── Y axis labels - _drawYLabels(canvas, zeroY, posScale, size); - - // ── Zero line - canvas.drawLine( - Offset(_leftPad, zeroY), - Offset(size.width, zeroY), - Paint() - ..color = lineColor - ..strokeWidth = 1.0, - ); - - // ── X axis labels (0h 6h 12h 18h 24h) - for (final h in [0, 6, 12, 18, 24]) { - final x = _leftPad + h / 24 * chartW; - _paintText( - canvas, - h == 24 ? '24h' : '${h}h', - Offset(x, _weatherH + chartH + 4), - mutedColor, - 9, - center: true, - ); - } - - // ── "Now" line + label - if (nowX > _leftPad && nowX < size.width) { - canvas.drawLine( - Offset(nowX, _weatherH), - Offset(nowX, _weatherH + chartH), - Paint() - ..color = inkColor.withValues(alpha: 0.6) - ..strokeWidth = 1.2 - ..style = PaintingStyle.stroke, - ); - final now = DateTime.now(); - final label = - '${now.hour.toString().padLeft(2, '0')}:${now.minute.toString().padLeft(2, '0')}'; - _drawNowBadge(canvas, nowX, _weatherH, label); - } - - // ── Weather band - _drawWeatherBand(canvas, chartW, plan.weather); - } - - void _drawStackedBars(Canvas canvas, double x, double barW, double zeroY, - double posScale, double negScale, HourlyFlow h) { - const gap = 1.5; - final bw = barW - gap; - - // Positive stack (upward from zeroY) - double topY = zeroY; - void stackPos(double kWh, Color color) { - if (kWh < 0.01) return; - final h2 = kWh * posScale; - canvas.drawRRect( - RRect.fromRectAndRadius( - Rect.fromLTWH(x + gap / 2, topY - h2, bw, h2), - const Radius.circular(1.5), - ), - Paint()..color = color, - ); - topY -= h2; - } - - stackPos(h.solToHouse, EtmTokens.solToHouse); - stackPos(h.solToEcs, EtmTokens.solToEcs); - stackPos(h.solToVe, EtmTokens.solToVe); - stackPos(h.solToBatt, EtmTokens.solToBatt); - stackPos(h.solToGrid, EtmTokens.solToGrid); - - // Negative stack (downward from zeroY) - double botY = zeroY; - void stackNeg(double kWh, Color color) { - if (kWh < 0.01) return; - final h2 = kWh * negScale; - canvas.drawRRect( - RRect.fromRectAndRadius( - Rect.fromLTWH(x + gap / 2, botY, bw, h2), - const Radius.circular(1.5), - ), - Paint()..color = color.withValues(alpha: 0.7), - ); - botY += h2; - } - - stackNeg(h.battToHouse, EtmTokens.battToHouse); - stackNeg(h.gridToHouse, EtmTokens.gridToHouse); - } - - void _drawHatch(Canvas canvas, Rect rect) { - canvas.save(); - canvas.clipRect(rect); - final hatchPaint = Paint() - ..color = surfaceColor.withValues(alpha: 0.5) - ..strokeWidth = 1.0 - ..style = PaintingStyle.stroke; - const step = 8.0; - for (double d = -rect.height; d < rect.width + rect.height; d += step) { - canvas.drawLine( - Offset(rect.left + d, rect.top), - Offset(rect.left + d + rect.height, rect.bottom), - hatchPaint, - ); - } - canvas.restore(); - } - - void _drawYLabels(Canvas canvas, double zeroY, double posScale, Size size) { - for (final kWh in [2.0, 4.0]) { - final y = zeroY - kWh * posScale; - _paintText(canvas, '${kWh.toInt()}', Offset(0, y - 5), mutedColor, 8); - canvas.drawLine( - Offset(_leftPad, y), - Offset(size.width, y), - Paint() - ..color = lineColor.withValues(alpha: 0.6) - ..strokeWidth = 0.5, - ); - } - } - - void _drawNowBadge(Canvas canvas, double x, double top, String label) { - final tp = TextPainter( - text: TextSpan( - text: label, - style: TextStyle( - fontFamily: 'IBMPlexMono', - fontSize: 9, - fontWeight: FontWeight.w600, - color: surfaceColor, - ), - ), - textDirection: TextDirection.ltr, - )..layout(); - const pad = 4.0; - final rect = Rect.fromCenter( - center: Offset(x, top - tp.height / 2 - pad - 2), - width: tp.width + pad * 2, - height: tp.height + pad, - ); - canvas.drawRRect( - RRect.fromRectAndRadius(rect, const Radius.circular(4)), - Paint()..color = inkColor.withValues(alpha: 0.75), - ); - tp.paint(canvas, Offset(rect.left + pad, rect.top + pad / 2)); - } - - void _drawWeatherBand( - Canvas canvas, double chartW, List points) { - for (final p in points) { - final x = _leftPad + p.hour / 24 * chartW; - final iconColor = p.kind == WeatherKind.sun || p.kind == WeatherKind.partlyCloudy - ? EtmTokens.solar - : mutedColor; - _drawWeatherIcon(canvas, Offset(x, 12), p.kind, iconColor); - _paintText( - canvas, - '${p.tempC}°', - Offset(x, 26), - mutedColor, - 9, - center: true, - ); - } - } - - void _drawWeatherIcon( - Canvas canvas, Offset center, WeatherKind kind, Color color) { - final paint = Paint()..color = color..style = PaintingStyle.fill; - switch (kind) { - case WeatherKind.sun: - _drawSun(canvas, center, color); - case WeatherKind.partlyCloudy: - _drawSun(canvas, center + const Offset(-3, -3), color, r: 5); - _drawCloud(canvas, center + const Offset(2, 2), color.withValues(alpha: 0.9)); - case WeatherKind.cloudy: - _drawCloud(canvas, center, mutedColor); - case WeatherKind.moon: - _drawMoon(canvas, center, color, paint); - } - } - - void _drawSun(Canvas canvas, Offset c, Color color, {double r = 6}) { - final p = Paint()..color = color..style = PaintingStyle.fill; - canvas.drawCircle(c, r, p); - final rp = Paint() - ..color = color - ..strokeWidth = 1.2 - ..style = PaintingStyle.stroke; - for (int i = 0; i < 8; i++) { - final a = i * math.pi / 4; - canvas.drawLine( - c + Offset(math.cos(a), math.sin(a)) * (r + 2), - c + Offset(math.cos(a), math.sin(a)) * (r + 5), - rp, - ); - } - } - - void _drawCloud(Canvas canvas, Offset c, Color color) { - final p = Paint()..color = color..style = PaintingStyle.fill; - canvas.drawCircle(c + const Offset(-4, 1), 4, p); - canvas.drawCircle(c + const Offset(1, -1), 5.5, p); - canvas.drawCircle(c + const Offset(6, 2), 3.5, p); - canvas.drawRect( - Rect.fromLTWH(c.dx - 7, c.dy + 1, 16, 4), - p, - ); - } - - void _drawMoon(Canvas canvas, Offset c, Color color, Paint paint) { - final path = Path()..addOval(Rect.fromCircle(center: c, radius: 7)); - final cut = Path()..addOval(Rect.fromCircle(center: c + const Offset(4, -3), radius: 6)); - final crescent = Path.combine(PathOperation.difference, path, cut); - canvas.drawPath(crescent, paint); - } - - void _paintText(Canvas canvas, String text, Offset pos, Color color, - double size, {bool center = false}) { - final tp = TextPainter( - text: TextSpan( - text: text, - style: TextStyle(fontSize: size, color: color, fontWeight: FontWeight.w500), - ), - textDirection: TextDirection.ltr, - )..layout(); - tp.paint( - canvas, - center ? Offset(pos.dx - tp.width / 2, pos.dy) : pos, - ); - } - - @override - bool shouldRepaint(DayPlanPainter old) => - old.plan != plan || old.isDark != isDark; -} diff --git a/lib/features/dashboard/widgets/day_plan_card.dart b/lib/features/dashboard/widgets/day_plan_card.dart deleted file mode 100644 index f02d04b..0000000 --- a/lib/features/dashboard/widgets/day_plan_card.dart +++ /dev/null @@ -1,97 +0,0 @@ -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(), - ); - } -} diff --git a/lib/screens/dashboard_screen.dart b/lib/screens/dashboard_screen.dart index 69da71f..53faa0c 100644 --- a/lib/screens/dashboard_screen.dart +++ b/lib/screens/dashboard_screen.dart @@ -13,7 +13,6 @@ import '../theme/etm_tokens.dart'; import '../features/dashboard/widgets/energy_flow_card.dart'; import '../features/dashboard/widgets/kpi_row.dart'; import '../features/dashboard/widgets/heos_decision_card.dart'; -import '../features/dashboard/widgets/day_plan_card.dart'; class DashboardScreen extends StatefulWidget { const DashboardScreen({super.key}); @@ -183,7 +182,26 @@ class _DashboardScreenState extends State { const SizedBox(height: 14), const HeosDecisionCard(), const SizedBox(height: 14), - const DayPlanCard(), + // ── « Plan de la journée » : RETIRÉE le 2026-09-08 ────────── + // + // Elle affichait `DayPlan.demo()` — vingt-quatre heures fabriquées + // par une gaussienne solaire et des coefficients écrits en dur + // (`solToEcs = excédent × 0,35`, `solToVe × 0,3`, `solToBatt × 0,15`, + // `battToHouse = déficit × 0,55`), sous le titre « Plan de la + // journée · aujourd'hui » et sans la moindre mention. Y compris une + // batterie alimentant la maison à partir de 17 h, sur une + // installation qui n'a pas de batterie. + // + // **Cinquième donnée inventée de ce tableau de bord** — après les + // deux MPPT, les 38 % de PAC, les 82 % de part solaire et le SOC à + // 62 %. Même réponse à chaque fois, et elle vaut encore : il n'y a + // rien derrière. + // + // Un plan suppose de savoir ce qui VA arriver : il demande une + // prévision PV et une source tarifaire, dont aucune n'existe. La + // carte reviendra avec elles, et elle dira alors quelque chose. Le + // code de fabrication part avec elle : laissé en place, il se + // rebranche. const SizedBox(height: 8), ]), ),