import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:table_calendar/table_calendar.dart'; import '../../models/user_profile.dart'; import '../../models/cycle_entry.dart'; import '../../providers/user_provider.dart'; import '../../services/cycle_service.dart'; import '../../theme/app_theme.dart'; class CalendarScreen extends ConsumerStatefulWidget { const CalendarScreen({super.key}); @override ConsumerState createState() => _CalendarScreenState(); } class _CalendarScreenState extends ConsumerState { DateTime _focusedDay = DateTime.now(); DateTime? _selectedDay; CalendarFormat _calendarFormat = CalendarFormat.month; @override Widget build(BuildContext context) { final entries = ref.watch(cycleEntriesProvider); final user = ref.watch(userProfileProvider); final cycleLength = user?.averageCycleLength ?? 28; final lastPeriodStart = user?.lastPeriodStartDate; return SafeArea( child: Column( children: [ // Header Padding( padding: const EdgeInsets.all(20), child: Row( children: [ Expanded( child: Text( 'Calendar', style: GoogleFonts.outfit( fontSize: 28, fontWeight: FontWeight.w600, color: AppColors.charcoal, ), ), ), _buildLegendButton(), ], ), ), // Calendar Container( margin: const EdgeInsets.symmetric(horizontal: 16), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(20), boxShadow: [ BoxShadow( color: AppColors.charcoal.withOpacity(0.05), blurRadius: 15, offset: const Offset(0, 5), ), ], ), child: TableCalendar( firstDay: DateTime.now().subtract(const Duration(days: 365)), lastDay: DateTime.now().add(const Duration(days: 365)), focusedDay: _focusedDay, calendarFormat: _calendarFormat, selectedDayPredicate: (day) => isSameDay(_selectedDay, day), onDaySelected: (selectedDay, focusedDay) { setState(() { _selectedDay = selectedDay; _focusedDay = focusedDay; }); }, onFormatChanged: (format) { setState(() => _calendarFormat = format); }, onPageChanged: (focusedDay) { _focusedDay = focusedDay; }, calendarStyle: CalendarStyle( outsideDaysVisible: false, defaultTextStyle: GoogleFonts.outfit( fontSize: 14, color: AppColors.charcoal, ), weekendTextStyle: GoogleFonts.outfit( fontSize: 14, color: AppColors.charcoal, ), todayDecoration: BoxDecoration( color: AppColors.sageGreen.withOpacity(0.3), shape: BoxShape.circle, ), todayTextStyle: GoogleFonts.outfit( fontSize: 14, fontWeight: FontWeight.w600, color: AppColors.sageGreen, ), selectedDecoration: const BoxDecoration( color: AppColors.sageGreen, shape: BoxShape.circle, ), selectedTextStyle: GoogleFonts.outfit( fontSize: 14, fontWeight: FontWeight.w600, color: Colors.white, ), ), headerStyle: HeaderStyle( formatButtonVisible: false, titleCentered: true, titleTextStyle: GoogleFonts.outfit( fontSize: 18, fontWeight: FontWeight.w600, color: AppColors.charcoal, ), leftChevronIcon: Icon( Icons.chevron_left, color: AppColors.warmGray, ), rightChevronIcon: Icon( Icons.chevron_right, color: AppColors.warmGray, ), ), daysOfWeekStyle: DaysOfWeekStyle( weekdayStyle: GoogleFonts.outfit( fontSize: 12, fontWeight: FontWeight.w500, color: AppColors.warmGray, ), weekendStyle: GoogleFonts.outfit( fontSize: 12, fontWeight: FontWeight.w500, color: AppColors.warmGray, ), ), calendarBuilders: CalendarBuilders( markerBuilder: (context, date, events) { // Check if it's a logged period day final isLoggedPeriod = _isLoggedPeriodDay(date, entries); if (isLoggedPeriod) { return Positioned( bottom: 1, child: Container( width: 6, height: 6, decoration: BoxDecoration( color: AppColors.menstrualPhase, shape: BoxShape.circle, ), ), ); } final phase = _getPhaseForDate(date, lastPeriodStart, cycleLength); if (phase != null) { return Positioned( bottom: 1, child: Container( width: 6, height: 6, decoration: BoxDecoration( color: _getPhaseColor(phase).withOpacity(0.5), shape: BoxShape.circle, ), ), ); } return null; }, ), ), ), const SizedBox(height: 20), // Selected Day Info if (_selectedDay != null) Expanded( child: _buildDayInfo(_selectedDay!, lastPeriodStart, cycleLength, entries), ), ], ), ); } Widget _buildLegendButton() { return GestureDetector( onTap: () => _showLegend(), child: Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), decoration: BoxDecoration( color: AppColors.blushPink.withOpacity(0.5), borderRadius: BorderRadius.circular(20), ), child: Row( children: [ Icon(Icons.info_outline, size: 16, color: AppColors.rose), const SizedBox(width: 4), Text( 'Legend', style: GoogleFonts.outfit( fontSize: 12, fontWeight: FontWeight.w500, color: AppColors.rose, ), ), ], ), ), ); } void _showLegend() { showModalBottomSheet( context: context, shape: const RoundedRectangleBorder( borderRadius: BorderRadius.vertical(top: Radius.circular(20)), ), builder: (context) => Container( padding: const EdgeInsets.all(24), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Legend', style: GoogleFonts.outfit( fontSize: 20, fontWeight: FontWeight.w600, color: AppColors.charcoal, ), ), const SizedBox(height: 20), _buildLegendItem(AppColors.menstrualPhase, 'Period'), _buildLegendItem(AppColors.follicularPhase, 'Follicular Phase'), _buildLegendItem(AppColors.ovulationPhase, 'Ovulation Window'), _buildLegendItem(AppColors.lutealPhase, 'Luteal Phase'), const SizedBox(height: 20), ], ), ), ); } Widget _buildLegendItem(Color color, String label) { return Padding( padding: const EdgeInsets.symmetric(vertical: 8), child: Row( children: [ Container( width: 16, height: 16, decoration: BoxDecoration( color: color, shape: BoxShape.circle, ), ), const SizedBox(width: 12), Text( label, style: GoogleFonts.outfit( fontSize: 14, color: AppColors.charcoal, ), ), ], ), ); } Widget _buildDayInfo(DateTime date, DateTime? lastPeriodStart, int cycleLength, List entries) { final phase = _getPhaseForDate(date, lastPeriodStart, cycleLength); final entry = _getEntryForDate(date, entries); final isLoggedPeriod = entry?.isPeriodDay ?? false; return Container( margin: const EdgeInsets.symmetric(horizontal: 16), padding: const EdgeInsets.all(20), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(16), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( '${_getMonthName(date.month)} ${date.day}, ${date.year}', style: GoogleFonts.outfit( fontSize: 18, fontWeight: FontWeight.w600, color: AppColors.charcoal, ), ), const SizedBox(height: 12), if (phase != null) Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), decoration: BoxDecoration( color: _getPhaseColor(phase).withOpacity(0.15), borderRadius: BorderRadius.circular(20), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Text(phase.emoji), const SizedBox(width: 6), Text( phase.label, style: GoogleFonts.outfit( fontSize: 14, fontWeight: FontWeight.w500, color: _getPhaseColor(phase), ), ), ], ), ), const SizedBox(height: 12), if (isLoggedPeriod) Container( margin: const EdgeInsets.only(bottom: 12), padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: AppColors.menstrualPhase.withOpacity(0.1), borderRadius: BorderRadius.circular(12), border: Border.all(color: AppColors.menstrualPhase.withOpacity(0.3)), ), child: Row( children: [ Icon(Icons.water_drop, color: AppColors.menstrualPhase, size: 20), const SizedBox(width: 8), Text( 'Period Recorded', style: GoogleFonts.outfit( fontSize: 14, fontWeight: FontWeight.w600, color: AppColors.menstrualPhase, ), ), ], ), ), Text( phase?.description ?? 'No cycle data for this date', style: GoogleFonts.outfit( fontSize: 14, color: AppColors.warmGray, ), ), ], ), ); } CyclePhase? _getPhaseForDate(DateTime date, DateTime? lastPeriodStart, int cycleLength) { if (lastPeriodStart == null) return null; final daysSinceLastPeriod = date.difference(lastPeriodStart).inDays; if (daysSinceLastPeriod < 0) return null; final dayOfCycle = (daysSinceLastPeriod % cycleLength) + 1; if (dayOfCycle <= 5) return CyclePhase.menstrual; if (dayOfCycle <= 13) return CyclePhase.follicular; if (dayOfCycle <= 16) return CyclePhase.ovulation; return CyclePhase.luteal; } Color _getPhaseColor(CyclePhase phase) { switch (phase) { case CyclePhase.menstrual: return AppColors.menstrualPhase; case CyclePhase.follicular: return AppColors.follicularPhase; case CyclePhase.ovulation: return AppColors.ovulationPhase; case CyclePhase.luteal: return AppColors.lutealPhase; } } String _getMonthName(int month) { const months = [ 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December' ]; return months[month - 1]; } bool _isLoggedPeriodDay(DateTime date, List entries) { final entry = _getEntryForDate(date, entries); return entry?.isPeriodDay ?? false; } CycleEntry? _getEntryForDate(DateTime date, List entries) { try { return entries.firstWhere( (entry) => isSameDay(entry.date, date), ); } catch (_) { return null; } } }