Files
impact/lib/features/history/widgets/history_chart.dart
2026-01-18 13:38:09 +01:00

245 lines
8.2 KiB
Dart

import 'package:flutter/material.dart';
import 'package:fl_chart/fl_chart.dart';
import 'package:intl/intl.dart';
import '../../../core/constants/app_constants.dart';
import '../../../core/theme/app_theme.dart';
import '../../../data/models/session.dart';
class HistoryChart extends StatelessWidget {
final List<Session> sessions;
const HistoryChart({
super.key,
required this.sessions,
});
@override
Widget build(BuildContext context) {
if (sessions.length < 2) {
return const SizedBox.shrink();
}
// Sort sessions by date and take last 10
final sortedSessions = List<Session>.from(sessions)
..sort((a, b) => a.createdAt.compareTo(b.createdAt));
final displaySessions = sortedSessions.length > 10
? sortedSessions.sublist(sortedSessions.length - 10)
: sortedSessions;
return Card(
child: Padding(
padding: const EdgeInsets.all(AppConstants.defaultPadding),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
const Icon(Icons.show_chart, color: AppTheme.primaryColor),
const SizedBox(width: 8),
Text(
'Evolution des scores',
style: Theme.of(context).textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.bold,
),
),
],
),
const SizedBox(height: 16),
SizedBox(
height: 200,
child: LineChart(
LineChartData(
gridData: FlGridData(
show: true,
drawVerticalLine: false,
horizontalInterval: 20,
getDrawingHorizontalLine: (value) {
return FlLine(
color: Colors.grey[300],
strokeWidth: 1,
);
},
),
titlesData: FlTitlesData(
show: true,
bottomTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
reservedSize: 30,
interval: 1,
getTitlesWidget: (value, meta) {
final index = value.toInt();
if (index < 0 || index >= displaySessions.length) {
return const SizedBox.shrink();
}
return Padding(
padding: const EdgeInsets.only(top: 8),
child: Text(
DateFormat('dd/MM').format(displaySessions[index].createdAt),
style: const TextStyle(fontSize: 10),
),
);
},
),
),
leftTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
reservedSize: 40,
interval: 20,
getTitlesWidget: (value, meta) {
return Text(
value.toInt().toString(),
style: const TextStyle(fontSize: 10),
);
},
),
),
topTitles: const AxisTitles(
sideTitles: SideTitles(showTitles: false),
),
rightTitles: const AxisTitles(
sideTitles: SideTitles(showTitles: false),
),
),
borderData: FlBorderData(
show: true,
border: Border(
bottom: BorderSide(color: Colors.grey[300]!),
left: BorderSide(color: Colors.grey[300]!),
),
),
minX: 0,
maxX: (displaySessions.length - 1).toDouble(),
minY: 0,
maxY: _getMaxY(displaySessions),
lineBarsData: [
// Score line
LineChartBarData(
spots: displaySessions.asMap().entries.map((entry) {
return FlSpot(
entry.key.toDouble(),
entry.value.totalScore.toDouble(),
);
}).toList(),
isCurved: true,
color: AppTheme.primaryColor,
barWidth: 3,
isStrokeCapRound: true,
dotData: FlDotData(
show: true,
getDotPainter: (spot, percent, barData, index) {
return FlDotCirclePainter(
radius: 4,
color: AppTheme.primaryColor,
strokeWidth: 2,
strokeColor: Colors.white,
);
},
),
belowBarData: BarAreaData(
show: true,
color: AppTheme.primaryColor.withValues(alpha: 0.1),
),
),
],
lineTouchData: LineTouchData(
touchTooltipData: LineTouchTooltipData(
getTooltipItems: (touchedSpots) {
return touchedSpots.map((spot) {
final session = displaySessions[spot.x.toInt()];
return LineTooltipItem(
'Score: ${session.totalScore}\n${session.shotCount} tirs',
const TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
),
);
}).toList();
},
),
),
),
),
),
const SizedBox(height: 8),
_buildLegend(context, displaySessions),
],
),
),
);
}
double _getMaxY(List<Session> sessions) {
double maxScore = 0;
for (final session in sessions) {
if (session.totalScore > maxScore) {
maxScore = session.totalScore.toDouble();
}
}
return (maxScore * 1.2).ceilToDouble();
}
Widget _buildLegend(BuildContext context, List<Session> displaySessions) {
final avgScore = displaySessions.fold<int>(0, (sum, s) => sum + s.totalScore) /
displaySessions.length;
final trend = displaySessions.length >= 2
? displaySessions.last.totalScore - displaySessions.first.totalScore
: 0;
return Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
_buildLegendItem(
context,
'Moyenne',
avgScore.toStringAsFixed(1),
Icons.analytics,
AppTheme.primaryColor,
),
_buildLegendItem(
context,
'Tendance',
trend >= 0 ? '+$trend' : '$trend',
trend >= 0 ? Icons.trending_up : Icons.trending_down,
trend >= 0 ? AppTheme.successColor : AppTheme.errorColor,
),
_buildLegendItem(
context,
'Sessions',
'${displaySessions.length}',
Icons.list,
Colors.grey,
),
],
);
}
Widget _buildLegendItem(
BuildContext context,
String label,
String value,
IconData icon,
Color color,
) {
return Column(
children: [
Icon(icon, size: 16, color: color),
const SizedBox(height: 4),
Text(
value,
style: TextStyle(
fontWeight: FontWeight.bold,
color: color,
),
),
Text(
label,
style: Theme.of(context).textTheme.bodySmall,
),
],
);
}
}