import 'package:flutter/material.dart'; import 'package:fl_chart/fl_chart.dart'; import 'package:didvan/models/statistic_data/data.dart'; import 'package:didvan/services/network/request.dart'; import 'package:didvan/services/network/request_helper.dart'; class MiniChart extends StatefulWidget { final String label; final double width; final double height; final Color? lineColor; final double? changePercent; final String? trend; const MiniChart({ super.key, required this.label, this.width = 100, this.height = 40, this.lineColor, this.changePercent, this.trend, }); @override State createState() => _MiniChartState(); } class _MiniChartState extends State { List chartData = []; bool isLoading = true; bool hasError = false; @override void initState() { super.initState(); _loadChartData(); } Future _loadChartData() async { try { final service = RequestService( RequestHelper.statisticDetails(widget.label, 'weekly'), ); await service.httpGet(); if (service.isSuccess) { final result = service.result['data']; final List data = []; for (var i = 0; i < result.length; i++) { data.add(Data.fromList(result[i])); } if (mounted) { setState(() { chartData = data.reversed.take(10).toList(); isLoading = false; hasError = false; }); } } else { if (mounted) { setState(() { isLoading = false; hasError = true; }); } } } catch (e) { if (mounted) { setState(() { isLoading = false; hasError = true; }); } } } double _stringToDouble(String value) { try { return double.parse(value.replaceAll(',', '')); } catch (e) { return 0.0; } } @override Widget build(BuildContext context) { if (isLoading) { return SizedBox( width: widget.width, height: widget.height, child: const Center( child: SizedBox( width: 16, height: 16, child: CircularProgressIndicator(strokeWidth: 2), ), ), ); } if (hasError || chartData.isEmpty) { return SizedBox( width: widget.width, height: widget.height, child: Container( decoration: BoxDecoration( color: Colors.grey.withOpacity(0.1), borderRadius: BorderRadius.circular(4), ), child: const Center( child: Icon( Icons.show_chart, size: 16, color: Colors.grey, ), ), ), ); } final spots = []; double minY = double.infinity; double maxY = double.negativeInfinity; for (int i = 0; i < chartData.length; i++) { final value = _stringToDouble(chartData[i].p); spots.add(FlSpot(i.toDouble(), value)); if (value < minY) minY = value; if (value > maxY) maxY = value; } final padding = (maxY - minY) * 0.1; minY -= padding; maxY += padding; final bool shouldShowShadow = widget.changePercent != null && widget.changePercent != 0; final bool isPositive = shouldShowShadow && widget.trend == 'high'; final bool isNegative = shouldShowShadow && widget.trend != 'high'; return SizedBox( width: widget.width, height: widget.height, child: LineChart( LineChartData( lineTouchData: const LineTouchData(enabled: false), gridData: const FlGridData(show: false), titlesData: const FlTitlesData(show: false), borderData: FlBorderData(show: false), minX: 0, maxX: (chartData.length - 1).toDouble(), minY: minY, maxY: maxY, lineBarsData: [ LineChartBarData( spots: spots, isCurved: false, color: widget.lineColor ?? Theme.of(context).colorScheme.primary, barWidth: 1.5, isStrokeCapRound: false, dotData: const FlDotData(show: false), belowBarData: isPositive ? BarAreaData( show: true, gradient: LinearGradient( colors: [ Colors.green.withOpacity(0.3), const Color.fromARGB(43, 255, 255, 255), ], stops: const [0.4, 1.0], begin: Alignment.topCenter, end: Alignment.bottomCenter, ), ) : BarAreaData(show: false), aboveBarData: isNegative ? BarAreaData( show: true, gradient: LinearGradient( colors: [ Colors.red.withOpacity(0.3), const Color.fromARGB(43, 255, 255, 255), ], stops: const [0.4, 1.0], begin: Alignment.bottomCenter, end: Alignment.topCenter, ), ) : BarAreaData(show: false), ), ], ), ), ); } }