import 'package:flutter/material.dart'; import '../../../models/energy_data.dart'; import '../../../theme/etm_tokens.dart'; import '../painters/ring_painter.dart'; class KpiRow extends StatelessWidget { final EnergyData data; const KpiRow({super.key, required this.data}); @override Widget build(BuildContext context) { final autoconso = data.selfConsumptionRate.clamp(0.0, 100.0); final autonomy = data.autonomyRate.clamp(0.0, 100.0); final toGrid = data.dayGridInjectionWh.clamp(0.0, double.infinity) / 1000; final fromGrid = (-data.dayGridInjectionWh).clamp(0.0, double.infinity) / 1000; return Column( children: [ Row( children: [ Expanded( child: _RingTile( label: 'Autoconsommation', value: autoconso, color: EtmTokens.solarOf(context), ), ), const SizedBox(width: 10), Expanded( child: _RingTile( label: 'Autonomie', value: autonomy, color: EtmTokens.ecoOf(context), ), ), ], ), const SizedBox(height: 10), Row( children: [ Expanded( child: _ValueTile( label: 'Vers réseau · aujourd\'hui', value: toGrid, icon: Icons.north_west_rounded, color: EtmTokens.ecoOf(context), ), ), const SizedBox(width: 10), Expanded( child: _ValueTile( label: 'Depuis réseau · aujourd\'hui', value: fromGrid, icon: Icons.south_east_rounded, color: EtmTokens.importColor, ), ), ], ), ], ); } } // ── Ring tile — horizontal (ring left, text right) ──────────────────────────── class _RingTile extends StatefulWidget { final String label; final double value; final Color color; const _RingTile({required this.label, required this.value, required this.color}); @override State<_RingTile> createState() => _RingTileState(); } class _RingTileState extends State<_RingTile> with SingleTickerProviderStateMixin { late final AnimationController _ctrl; late Animation _ring; late Animation _count; @override void initState() { super.initState(); _ctrl = AnimationController(vsync: this, duration: const Duration(milliseconds: 1100)); _ring = Tween(begin: 0, end: widget.value / 100) .animate(CurvedAnimation(parent: _ctrl, curve: Curves.easeOutCubic)); _count = IntTween(begin: 0, end: widget.value.round()) .animate(CurvedAnimation(parent: _ctrl, curve: Curves.easeOutCubic)); _ctrl.forward(); } @override void dispose() { _ctrl.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Container( decoration: BoxDecoration( color: EtmTokens.surfaceOf(context), borderRadius: BorderRadius.circular(EtmTokens.radiusCard), boxShadow: EtmTokens.cardShadowOf(context), border: Border.all(color: EtmTokens.lineOf(context), width: 1), ), padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 13), child: Row( children: [ // Ring — 50×50, strokeWidth 5 (matches HTML .ring SVG) SizedBox( width: 50, height: 50, child: AnimatedBuilder( animation: _ring, builder: (context, _) => CustomPaint( size: const Size(50, 50), painter: RingPainter( value: _ring.value, color: widget.color, strokeWidth: 5, ), ), ), ), const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ AnimatedBuilder( animation: _count, builder: (context, _) => Text( '${_count.value} %', style: EtmTokens.mono( size: 18, weight: FontWeight.w600, color: EtmTokens.inkOf(context), ), ), ), const SizedBox(height: 3), Text( widget.label, style: EtmTokens.sans( size: 11, color: EtmTokens.mutedOf(context), ), ), ], ), ), ], ), ); } } // ── Value tile — horizontal (icon left, text right) ─────────────────────────── class _ValueTile extends StatelessWidget { final String label; final double value; final IconData icon; final Color color; const _ValueTile({ required this.label, required this.value, required this.icon, required this.color, }); @override Widget build(BuildContext context) { return Container( decoration: BoxDecoration( color: EtmTokens.surfaceOf(context), borderRadius: BorderRadius.circular(EtmTokens.radiusCard), boxShadow: EtmTokens.cardShadowOf(context), border: Border.all(color: EtmTokens.lineOf(context), width: 1), ), padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 13), child: Row( children: [ Container( width: 30, height: 30, decoration: BoxDecoration( color: color.withValues(alpha: 0.12), borderRadius: BorderRadius.circular(9), ), child: Icon(icon, color: color, size: 16), ), const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( '${value.toStringAsFixed(1)} kWh', style: EtmTokens.mono( size: 16, weight: FontWeight.w600, color: EtmTokens.inkOf(context), ), ), const SizedBox(height: 3), Text( label, style: EtmTokens.sans( size: 10.5, color: EtmTokens.mutedOf(context), ), overflow: TextOverflow.ellipsis, ), ], ), ), ], ), ); } }