import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:fl_chart/fl_chart.dart'; import '../../../core/widgets/app_scaffold.dart'; import '../../../core/widgets/loading_indicator.dart'; import '../../../core/widgets/empty_state.dart'; import '../application/insights_controller.dart'; class InsightsScreen extends ConsumerStatefulWidget { const InsightsScreen({super.key}); @override ConsumerState createState() => _InsightsScreenState(); } class _InsightsScreenState extends ConsumerState { @override void initState() { super.initState(); WidgetsBinding.instance.addPostFrameCallback((_) { ref.read(insightsControllerProvider.notifier).refresh(); }); } @override Widget build(BuildContext context) { final state = ref.watch(insightsControllerProvider); return AppScaffold( title: 'Insights', body: state.isLoading ? const LoadingIndicator() : state.error != null ? _buildError(state.error!) : _buildContent(state), ); } Widget _buildError(String error) { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text( 'Error loading insights', style: Theme.of(context).textTheme.titleLarge, ), const SizedBox(height: 8), Text(error), const SizedBox(height: 16), ElevatedButton( onPressed: () { ref.read(insightsControllerProvider.notifier).refresh(); }, child: const Text('Retry'), ), ], ), ); } Widget _buildContent(InsightsState state) { if (state.totalGoals == 0) { return const EmptyState( icon: Icons.insights_outlined, title: 'No data yet', subtitle: 'Start creating goals to see your insights', ); } return RefreshIndicator( onRefresh: () async { await ref.read(insightsControllerProvider.notifier).refresh(); }, child: SingleChildScrollView( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ _buildOverviewCards(state), const SizedBox(height: 24), _buildProgressChart(state), const SizedBox(height: 24), _buildGoalCompletionTrends(state), const SizedBox(height: 24), _buildStreakCard(state), ], ), ), ); } Widget _buildOverviewCards(InsightsState state) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Overview', style: Theme.of(context).textTheme.titleLarge, ), const SizedBox(height: 16), Row( children: [ Expanded( child: _buildStatCard( 'Total Goals', state.totalGoals.toString(), Icons.flag_outlined, Theme.of(context).colorScheme.primary, ), ), const SizedBox(width: 12), Expanded( child: _buildStatCard( 'Completed', state.completedGoals.toString(), Icons.check_circle_outline, Colors.green, ), ), ], ), const SizedBox(height: 12), Row( children: [ Expanded( child: _buildStatCard( 'Active', state.activeGoals.toString(), Icons.pending_outlined, Colors.orange, ), ), const SizedBox(width: 12), Expanded( child: _buildStatCard( 'Progress', '${state.overallProgress.toStringAsFixed(0)}%', Icons.trending_up, Colors.blue, ), ), ], ), ], ); } Widget _buildStatCard(String title, String value, IconData icon, Color color) { return Card( child: Padding( padding: const EdgeInsets.all(16), child: Column( children: [ Icon(icon, color: color, size: 32), const SizedBox(height: 8), Text( value, style: Theme.of(context).textTheme.headlineSmall?.copyWith( fontWeight: FontWeight.bold, color: color, ), ), const SizedBox(height: 4), Text( title, style: Theme.of(context).textTheme.bodySmall?.copyWith( color: Theme.of(context).colorScheme.onSurfaceVariant, ), ), ], ), ), ); } Widget _buildProgressChart(InsightsState state) { final trends = ref.read(insightsControllerProvider.notifier).getProgressVsTimeData(); return Card( child: Padding( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Progress vs Time', style: Theme.of(context).textTheme.titleLarge, ), const SizedBox(height: 16), SizedBox( height: 200, child: trends.isEmpty ? const Center(child: Text('No countdown data available')) : LineChart( LineChartData( gridData: const FlGridData(show: true), titlesData: FlTitlesData( leftTitles: AxisTitles( sideTitles: SideTitles( showTitles: true, reservedSize: 40, getTitlesWidget: (value, meta) { if (value % 20 == 0) { return Text( '${value.toInt()}%', style: const TextStyle(fontSize: 10), ); } return const Text(''); }, ), ), bottomTitles: AxisTitles( sideTitles: SideTitles( showTitles: true, reservedSize: 30, getTitlesWidget: (value, meta) { if (value.toInt() % 2 == 0) { return Text( 'Day ${value.toInt()}', style: const TextStyle(fontSize: 10), ); } return const Text(''); }, ), ), topTitles: const AxisTitles( sideTitles: SideTitles(showTitles: false), ), rightTitles: const AxisTitles( sideTitles: SideTitles(showTitles: false), ), ), borderData: FlBorderData(show: true), lineBarsData: [ LineChartBarData( spots: trends .map((t) => FlSpot(t['day'].toDouble(), t['expected'].toDouble())) .toList(), isCurved: true, color: Colors.grey[400], barWidth: 2, dotData: const FlDotData(show: false), ), LineChartBarData( spots: trends .map((t) => FlSpot(t['day'].toDouble(), t['actual'].toDouble())) .toList(), isCurved: true, color: Theme.of(context).colorScheme.primary, barWidth: 3, dotData: const FlDotData(show: true), ), ], ), ), ), const SizedBox(height: 12), Row( mainAxisAlignment: MainAxisAlignment.center, children: [ _buildLegendItem('Expected', Colors.grey[400]!), const SizedBox(width: 16), _buildLegendItem('Actual', Theme.of(context).colorScheme.primary), ], ), ], ), ), ); } Widget _buildLegendItem(String label, Color color) { return Row( children: [ Container( width: 12, height: 12, decoration: BoxDecoration( color: color, shape: BoxShape.circle, ), ), const SizedBox(width: 4), Text( label, style: Theme.of(context).textTheme.bodySmall, ), ], ); } Widget _buildGoalCompletionTrends(InsightsState state) { final trends = ref.read(insightsControllerProvider.notifier).getGoalCompletionTrends(); return Card( child: Padding( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Weekly Completion Trends', style: Theme.of(context).textTheme.titleLarge, ), const SizedBox(height: 16), SizedBox( height: 200, child: trends.isEmpty ? const Center(child: Text('No data available')) : BarChart( BarChartData( alignment: BarChartAlignment.spaceAround, maxY: trends.map((t) => t['completed'] as int).reduce((a, b) => a > b ? a : b).toDouble() + 1, barGroups: trends.asMap().entries.map((entry) { return BarChartGroupData( x: entry.key, barRods: [ BarChartRodData( toY: entry.value['completed'].toDouble(), color: Theme.of(context).colorScheme.primary, width: 20, borderRadius: BorderRadius.circular(4), ), ], ); }).toList(), titlesData: FlTitlesData( leftTitles: AxisTitles( sideTitles: SideTitles( showTitles: true, reservedSize: 40, getTitlesWidget: (value, meta) { if (value == value.toInt()) { return Text( value.toInt().toString(), style: const TextStyle(fontSize: 10), ); } return const Text(''); }, ), ), bottomTitles: AxisTitles( sideTitles: SideTitles( showTitles: true, reservedSize: 30, getTitlesWidget: (value, meta) { if (value >= 0 && value < trends.length) { return Text( trends[value.toInt()]['week'] as String, style: const TextStyle(fontSize: 10), ); } return const Text(''); }, ), ), topTitles: const AxisTitles( sideTitles: SideTitles(showTitles: false), ), rightTitles: const AxisTitles( sideTitles: SideTitles(showTitles: false), ), ), borderData: FlBorderData(show: true), ), ), ), ], ), ), ); } Widget _buildStreakCard(InsightsState state) { return Card( child: Padding( padding: const EdgeInsets.all(16), child: Row( children: [ const Icon( Icons.local_fire_department, size: 48, color: Colors.orange, ), const SizedBox(width: 16), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Current Streak', style: Theme.of(context).textTheme.titleMedium, ), const SizedBox(height: 4), Text( '${state.currentStreak} days', style: Theme.of(context).textTheme.headlineSmall?.copyWith( fontWeight: FontWeight.bold, color: Colors.orange, ), ), const SizedBox(height: 8), Text( 'Longest: ${state.longestStreak} days', style: Theme.of(context).textTheme.bodySmall?.copyWith( color: Theme.of(context).colorScheme.onSurfaceVariant, ), ), ], ), ), ], ), ), ); } }