import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../../core/providers/mood_provider.dart'; const _moodColors = [ Color(0xFFE57373), Color(0xFFFFB74D), Color(0xFFFFD54F), Color(0xFF81C784), Color(0xFF4DB6AC), ]; class CalendarHeatmap extends ConsumerWidget { const CalendarHeatmap({ super.key, required this.month, required this.selectedDay, required this.onDayTap, }); final DateTime month; final DateTime selectedDay; final ValueChanged onDayTap; @override Widget build(BuildContext context, WidgetRef ref) { final start = DateTime(month.year, month.month, 1); final end = DateTime(month.year, month.month + 1, 0, 23, 59, 59); final averagesAsync = ref.watch(dailyAveragesProvider((start: start, end: end))); return averagesAsync.when( data: (averages) => _HeatmapGrid( month: month, averages: averages, selectedDay: selectedDay, onDayTap: onDayTap, ), loading: () => const SizedBox(height: 280, child: Center(child: CircularProgressIndicator())), error: (_, __) => const SizedBox(height: 280), ); } } class _HeatmapGrid extends StatelessWidget { const _HeatmapGrid({ required this.month, required this.averages, required this.selectedDay, required this.onDayTap, }); final DateTime month; final Map averages; final DateTime selectedDay; final ValueChanged onDayTap; @override Widget build(BuildContext context) { final cs = Theme.of(context).colorScheme; final daysInMonth = DateTime(month.year, month.month + 1, 0).day; final leadingBlanks = DateTime(month.year, month.month, 1).weekday - 1; final today = DateTime.now(); final todayNorm = DateTime(today.year, today.month, today.day); return Padding( padding: const EdgeInsets.fromLTRB(16, 4, 16, 16), child: Column( children: [ Row( children: ['Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa', 'Su'] .map((l) => Expanded( child: Center( child: Text( l, style: Theme.of(context) .textTheme .labelSmall ?.copyWith(color: cs.outline), ), ), )) .toList(), ), const SizedBox(height: 6), GridView.builder( shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 7, mainAxisSpacing: 4, crossAxisSpacing: 4, ), itemCount: leadingBlanks + daysInMonth, itemBuilder: (context, i) { if (i < leadingBlanks) return const SizedBox.shrink(); final day = i - leadingBlanks + 1; final date = DateTime(month.year, month.month, day); final avg = averages[date]; final isSelected = date == selectedDay; final isToday = date == todayNorm; Color? fill; if (avg != null) { final idx = (avg.clamp(1.0, 5.0) - 1).round().clamp(0, 4); fill = _moodColors[idx].withValues(alpha: 0.85); } return GestureDetector( onTap: () => onDayTap(date), child: Container( decoration: BoxDecoration( shape: BoxShape.circle, color: fill ?? cs.surfaceContainerHighest.withValues(alpha: 0.5), border: isSelected ? Border.all(color: cs.primary, width: 2.5) : isToday ? Border.all(color: cs.outline, width: 1) : null, ), child: Center( child: Text( '$day', style: Theme.of(context).textTheme.labelSmall?.copyWith( color: fill != null ? Colors.white : cs.onSurfaceVariant, fontWeight: isSelected || isToday ? FontWeight.bold : FontWeight.normal, ), ), ), ), ); }, ), ], ), ); } }