import 'package:flutter/material.dart'; import 'package:fl_chart/fl_chart.dart'; import 'dart:async'; class StatisticScreen extends StatefulWidget { final List> realtimeLogs; const StatisticScreen({super.key, required this.realtimeLogs}); @override State createState() => _StatisticScreenState(); } class _StatisticScreenState extends State { Timer? _refreshTimer; @override void initState() { super.initState(); // Refresh otomatis setiap detik _refreshTimer = Timer.periodic(const Duration(seconds: 1), (timer) { if (mounted) setState(() {}); }); } @override void dispose() { _refreshTimer?.cancel(); super.dispose(); } List _getRealtimeSpots() { if (widget.realtimeLogs.isEmpty) return []; return List.generate(widget.realtimeLogs.length, (index) { final log = widget.realtimeLogs[index]; final valueRaw = log['value']; final value = (valueRaw is num) ? valueRaw.toDouble() : 0.0; return FlSpot(index.toDouble(), value); }); } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: const Color(0xFFF5F7FA), appBar: AppBar( title: const Text( "Statistik", style: TextStyle( color: Colors.black, fontWeight: FontWeight.bold, fontSize: 22), ), backgroundColor: Colors.white, elevation: 0.5, iconTheme: const IconThemeData(color: Colors.black), ), body: Column( children: [ const SizedBox(height: 10), _buildInfoCard(), Expanded(child: _buildChartContainer()), const SizedBox(height: 20), ], ), ); } Widget _buildInfoCard() { return Container( margin: const EdgeInsets.symmetric(horizontal: 20, vertical: 10), padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: Colors.blue.withOpacity(0.08), borderRadius: BorderRadius.circular(12), border: Border.all(color: Colors.blue.withOpacity(0.1)), ), child: const Row( children: [ Icon(Icons.sync_rounded, color: Colors.blue, size: 22), SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "Statistik Realtime Selada Hidroponik", style: TextStyle( color: Colors.blue, fontWeight: FontWeight.bold, fontSize: 13), ), Text( "Data diperbarui otomatis setiap kali sensor mengirim nilai.", style: TextStyle(fontSize: 11, color: Colors.blueGrey), ), ], ), ), ], ), ); } Widget _buildChartContainer() { return Container( margin: const EdgeInsets.all(15), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(25), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.05), blurRadius: 15, offset: const Offset(0, 5)) ], ), child: ClipRRect( borderRadius: BorderRadius.circular(25), child: InteractiveViewer( constrained: false, alignment: Alignment.centerLeft, child: SizedBox( width: widget.realtimeLogs.length < 10 ? MediaQuery.of(context).size.width - 30 : widget.realtimeLogs.length * 60.0, height: MediaQuery.of(context).size.height * 0.55, child: Padding( padding: const EdgeInsets.fromLTRB(15, 40, 40, 15), child: LineChart(_mainData()), ), ), ), ), ); } LineChartData _mainData() { return LineChartData( gridData: FlGridData( show: true, drawVerticalLine: true, getDrawingHorizontalLine: (value) => FlLine(color: Colors.grey.withOpacity(0.1), strokeWidth: 1), getDrawingVerticalLine: (value) => FlLine(color: Colors.grey.withOpacity(0.1), strokeWidth: 1), ), titlesData: FlTitlesData( rightTitles: const AxisTitles(sideTitles: SideTitles(showTitles: false)), topTitles: const AxisTitles(sideTitles: SideTitles(showTitles: false)), leftTitles: AxisTitles( sideTitles: SideTitles( showTitles: true, reservedSize: 45, getTitlesWidget: (value, meta) { return Text( '${value.toInt()}%', style: const TextStyle(fontSize: 10, color: Colors.grey), ); }, ), ), bottomTitles: AxisTitles( sideTitles: SideTitles( showTitles: true, reservedSize: 32, interval: 1, getTitlesWidget: (value, meta) { final int index = value.toInt(); if (index >= 0 && index < widget.realtimeLogs.length) { final label = widget.realtimeLogs[index]['timeLabel']?.toString() ?? ""; // Menggunakan Padding & Text biasa untuk menghindari error SideTitleWidget return Padding( padding: const EdgeInsets.only(top: 8.0), child: Text( label, textAlign: TextAlign.center, style: TextStyle( fontSize: 9, fontWeight: FontWeight.w500, color: Colors.grey.shade600, ), ), ); } return const SizedBox(); }, ), ), ), borderData: FlBorderData(show: false), lineBarsData: [ LineChartBarData( spots: _getRealtimeSpots(), isCurved: true, curveSmoothness: 0.3, color: Colors.blueAccent, barWidth: 4, isStrokeCapRound: true, dotData: FlDotData( show: true, getDotPainter: (spot, percent, barData, index) => FlDotCirclePainter( radius: 4, color: Colors.white, strokeWidth: 2, strokeColor: Colors.blueAccent, ), ), belowBarData: BarAreaData( show: true, gradient: LinearGradient( colors: [ Colors.blueAccent.withOpacity(0.3), Colors.blueAccent.withOpacity(0.0) ], begin: Alignment.topCenter, end: Alignment.bottomCenter, ), ), ), ], lineTouchData: LineTouchData( handleBuiltInTouches: true, touchTooltipData: LineTouchTooltipData( getTooltipColor: (LineBarSpot touchedSpot) => Colors.blueAccent, getTooltipItems: (List touchedSpots) { return touchedSpots.map((spot) { return LineTooltipItem( '${spot.y.toStringAsFixed(1)}%', const TextStyle( color: Colors.white, fontWeight: FontWeight.bold, fontSize: 12, ), ); }).toList(); }, ), ), ); } }