import 'package:flutter/material.dart'; import 'package:fl_chart/fl_chart.dart'; import 'package:go_router/go_router.dart'; import '../../../../core/theme/app_colors.dart'; import '../../../../core/widgets/time_visualizer.dart'; /// Time perception tools dashboard. /// /// - Accuracy trend chart (fl_chart) showing estimate vs actual over time. /// - "You tend to underestimate by X%" insight. /// - Recent time entries list. /// - Tips for improving time awareness. class TimeDashboardScreen extends StatelessWidget { const TimeDashboardScreen({super.key}); // ── Dummy data for the chart ───────────────────────────────────── static const _estimatedData = [15.0, 20.0, 10.0, 30.0, 25.0, 15.0, 20.0]; static const _actualData = [22.0, 18.0, 14.0, 40.0, 28.0, 20.0, 19.0]; static const _labels = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']; double get _averageBias { double total = 0; for (int i = 0; i < _estimatedData.length; i++) { total += (_actualData[i] - _estimatedData[i]) / _estimatedData[i]; } return (total / _estimatedData.length) * 100; } @override Widget build(BuildContext context) { final theme = Theme.of(context); final bias = _averageBias; final biasText = bias > 0 ? 'You tend to underestimate by ${bias.abs().toStringAsFixed(0)}%' : 'You tend to overestimate by ${bias.abs().toStringAsFixed(0)}%'; return Scaffold( appBar: AppBar( leading: IconButton( icon: const Icon(Icons.arrow_back_rounded), onPressed: () => context.go('/'), ), title: const Text('Time Perception'), ), body: ListView( padding: const EdgeInsets.all(16), children: [ // ── Insight card ───────────────────────────────────────── Card( color: AppColors.primaryLight.withAlpha(30), elevation: 0, child: Padding( padding: const EdgeInsets.all(16), child: Row( children: [ const Icon(Icons.insights_rounded, size: 32, color: AppColors.primary), const SizedBox(width: 12), Expanded( child: Text(biasText, style: theme.textTheme.bodyLarge?.copyWith( fontWeight: FontWeight.w600, )), ), ], ), ), ), const SizedBox(height: 20), // ── Trend chart ────────────────────────────────────────── Text('Estimated vs Actual (this week)', style: theme.textTheme.titleMedium), const SizedBox(height: 12), SizedBox( height: 220, child: LineChart( LineChartData( gridData: const FlGridData(show: false), titlesData: FlTitlesData( leftTitles: AxisTitles( sideTitles: SideTitles( showTitles: true, reservedSize: 32, getTitlesWidget: (value, meta) => Text( '${value.toInt()}m', style: theme.textTheme.bodySmall, ), ), ), bottomTitles: AxisTitles( sideTitles: SideTitles( showTitles: true, getTitlesWidget: (value, meta) { final idx = value.toInt(); if (idx < 0 || idx >= _labels.length) return const SizedBox.shrink(); return Text(_labels[idx], style: theme.textTheme.bodySmall); }, ), ), topTitles: const AxisTitles(sideTitles: SideTitles(showTitles: false)), rightTitles: const AxisTitles(sideTitles: SideTitles(showTitles: false)), ), borderData: FlBorderData(show: false), lineBarsData: [ // Estimated LineChartBarData( spots: List.generate( _estimatedData.length, (i) => FlSpot(i.toDouble(), _estimatedData[i]), ), isCurved: true, color: AppColors.primary, barWidth: 3, dotData: const FlDotData(show: true), belowBarData: BarAreaData( show: true, color: AppColors.primary.withAlpha(20), ), ), // Actual LineChartBarData( spots: List.generate( _actualData.length, (i) => FlSpot(i.toDouble(), _actualData[i]), ), isCurved: true, color: AppColors.secondary, barWidth: 3, dotData: const FlDotData(show: true), dashArray: [6, 4], ), ], minY: 0, ), ), ), const SizedBox(height: 8), // ── Legend ──────────────────────────────────────────────── Row( mainAxisAlignment: MainAxisAlignment.center, children: [ _LegendDot(color: AppColors.primary, label: 'Estimated'), const SizedBox(width: 20), _LegendDot(color: AppColors.secondary, label: 'Actual'), ], ), const SizedBox(height: 24), // ── Recent entries ─────────────────────────────────────── Text('Recent', style: theme.textTheme.titleMedium), const SizedBox(height: 8), ...List.generate( _estimatedData.length, (i) => Padding( padding: const EdgeInsets.only(bottom: 12), child: TimeVisualizer( estimatedMinutes: _estimatedData[i].toInt(), actualMinutes: _actualData[i].toInt(), ), ), ), const SizedBox(height: 24), // ── Tips ───────────────────────────────────────────────── Text('Tips for improving time awareness', style: theme.textTheme.titleMedium), const SizedBox(height: 8), const _TipCard( icon: Icons.timer_outlined, text: 'Before starting, say your estimate out loud — it makes you more committed.', ), const _TipCard( icon: Icons.visibility_rounded, text: 'Use a visible clock or timer while working to build a sense of passing time.', ), const _TipCard( icon: Icons.edit_note_rounded, text: 'After each task, note how long it really took. Patterns will emerge!', ), ], ), ); } } class _LegendDot extends StatelessWidget { final Color color; final String label; const _LegendDot({required this.color, required this.label}); @override Widget build(BuildContext context) { return Row( mainAxisSize: MainAxisSize.min, children: [ Container(width: 10, height: 10, decoration: BoxDecoration(color: color, shape: BoxShape.circle)), const SizedBox(width: 6), Text(label, style: Theme.of(context).textTheme.bodySmall), ], ); } } class _TipCard extends StatelessWidget { final IconData icon; final String text; const _TipCard({required this.icon, required this.text}); @override Widget build(BuildContext context) { final theme = Theme.of(context); return Card( elevation: 0, color: AppColors.surfaceVariantLight, margin: const EdgeInsets.only(bottom: 8), child: Padding( padding: const EdgeInsets.all(14), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Icon(icon, size: 22, color: AppColors.primary), const SizedBox(width: 10), Expanded(child: Text(text, style: theme.textTheme.bodyMedium)), ], ), ), ); } }