import 'package:christian_period_tracker/models/scripture.dart'; 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'; import '../log/log_screen.dart'; class CalendarScreen extends ConsumerStatefulWidget { final bool readOnly; const CalendarScreen({ super.key, this.readOnly = false, }); @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) { final entry = _getEntryForDate(date, entries); if (entry == null) { final phase = _getPhaseForDate(date, lastPeriodStart, cycleLength); if (phase != null) { return Positioned( bottom: 1, child: Container( width: 4, height: 4, decoration: BoxDecoration( color: _getPhaseColor(phase).withOpacity(0.3), shape: BoxShape.circle, ), ), ); } return null; } // If we have an entry, show icons/markers return Positioned( bottom: 1, child: Row( mainAxisSize: MainAxisSize.min, children: [ if (entry.isPeriodDay) Container( width: 6, height: 6, margin: const EdgeInsets.symmetric(horizontal: 1), decoration: const BoxDecoration( color: AppColors.menstrualPhase, shape: BoxShape.circle, ), ), if (entry.mood != null || entry.energyLevel != 3 || entry.hasSymptoms) Container( width: 6, height: 6, margin: const EdgeInsets.symmetric(horizontal: 1), decoration: const BoxDecoration( color: AppColors.softGold, shape: BoxShape.circle, ), ), ], ), ); }, ), ), ), 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); return Container( margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), padding: const EdgeInsets.all(20), decoration: BoxDecoration( color: Theme.of(context).cardTheme.color, borderRadius: BorderRadius.circular(16), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.05), blurRadius: 10, offset: const Offset(0, 4), ), ], ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( '${_getMonthName(date.month)} ${date.day}, ${date.year}', style: Theme.of(context).textTheme.titleLarge?.copyWith( fontWeight: FontWeight.w600, ), ), if (entry != null) const Icon(Icons.check_circle, color: AppColors.sageGreen, size: 20), ], ), const SizedBox(height: 16), if (phase != null) Padding( padding: const EdgeInsets.only(bottom: 16), child: 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), ), ), ], ), ), ), if (entry == null) Text( phase?.description ?? 'No data for this date', style: Theme.of(context) .textTheme .bodyMedium ?.copyWith(color: AppColors.warmGray), ) else ...[ // Period Detail if (entry.isPeriodDay) _buildDetailRow(Icons.water_drop, 'Period Day', AppColors.menstrualPhase, value: entry.flowIntensity?.label), // Mood Detail if (entry.mood != null) _buildDetailRow( Icons.emoji_emotions_outlined, 'Mood', AppColors.softGold, value: '${entry.mood!.emoji} ${entry.mood!.label}'), // Energy Detail _buildDetailRow( Icons.flash_on, 'Energy Level', AppColors.follicularPhase, value: _getEnergyLabel(entry.energyLevel)), // Symptoms if (entry.hasSymptoms) _buildDetailRow( Icons.healing_outlined, 'Symptoms', AppColors.lavender, value: _getSymptomsString(entry)), // Contextual Recommendation _buildRecommendation(entry), // Notes if (entry.notes?.isNotEmpty == true) Padding( padding: const EdgeInsets.only(top: 12), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('Notes', style: GoogleFonts.outfit( fontSize: 12, fontWeight: FontWeight.w600, color: AppColors.warmGray)), const SizedBox(height: 4), Text(entry.notes!, style: GoogleFonts.outfit(fontSize: 14)), ], ), ), ], const SizedBox(height: 24), // Action Buttons if (!widget.readOnly) Row( children: [ Expanded( child: ElevatedButton.icon( onPressed: () { Navigator.of(context).push( MaterialPageRoute( builder: (context) => Scaffold( appBar: AppBar( title: Text( 'Log for ${_getMonthName(date.month)} ${date.day}'), ), body: LogScreen(initialDate: date), ), ), ); }, icon: Icon(entry != null ? Icons.edit_note : Icons.add_circle_outline), label: Text(entry != null ? 'Edit Log' : 'Add Log'), style: ElevatedButton.styleFrom( backgroundColor: AppColors.sageGreen, foregroundColor: Colors.white, padding: const EdgeInsets.symmetric(vertical: 12), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12)), ), ), ), ], ), ], ), ); } Widget _buildRecommendation(CycleEntry entry) { final scripture = ScriptureDatabase().getRecommendedScripture(entry); if (scripture == null) return const SizedBox.shrink(); final user = ref.read(userProfileProvider); final translation = user?.bibleTranslation ?? BibleTranslation.esv; final isDark = Theme.of(context).brightness == Brightness.dark; return Container( margin: const EdgeInsets.only(top: 16), padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: AppColors.softGold.withOpacity(isDark ? 0.15 : 0.1), borderRadius: BorderRadius.circular(12), border: Border.all(color: AppColors.softGold.withOpacity(0.3)), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ const Icon(Icons.auto_awesome, color: AppColors.softGold, size: 18), const SizedBox(width: 8), Text( 'Daily Encouragement', style: GoogleFonts.outfit( fontSize: 14, fontWeight: FontWeight.w600, color: AppColors.softGold, ), ), ], ), const SizedBox(height: 12), Text( '"${scripture.getVerse(translation)}"', style: GoogleFonts.lora( fontSize: 14, fontStyle: FontStyle.italic, color: isDark ? Colors.white : AppColors.charcoal, height: 1.5, ), ), const SizedBox(height: 8), Text( '— ${scripture.reference}', style: GoogleFonts.outfit( fontSize: 12, fontWeight: FontWeight.w500, color: AppColors.warmGray, ), ), ], ), ); } Widget _buildDetailRow(IconData icon, String label, Color color, {String? value}) { return Padding( padding: const EdgeInsets.symmetric(vertical: 8), child: Row( children: [ Container( padding: const EdgeInsets.all(8), decoration: BoxDecoration( color: color.withOpacity(0.1), borderRadius: BorderRadius.circular(8), ), child: Icon(icon, color: color, size: 18), ), const SizedBox(width: 12), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( label, style: GoogleFonts.outfit( fontSize: 12, color: AppColors.warmGray, ), ), if (value != null) Text( value, style: GoogleFonts.outfit( fontSize: 14, fontWeight: FontWeight.w500, ), ), ], ), ], ), ); } String _getSymptomsString(CycleEntry entry) { List s = []; if (entry.crampIntensity != null && entry.crampIntensity! > 0) s.add('Cramps (${entry.crampIntensity}/5)'); if (entry.hasHeadache) s.add('Headache'); if (entry.hasBloating) s.add('Bloating'); if (entry.hasBreastTenderness) s.add('Breast Tenderness'); if (entry.hasFatigue) s.add('Fatigue'); if (entry.hasAcne) s.add('Acne'); return s.join(', '); } String _getEnergyLabel(int? energyLevel) { if (energyLevel == null) return 'Not logged'; if (energyLevel <= 1) return 'Very Low'; if (energyLevel == 2) return 'Low'; if (energyLevel == 3) return 'Neutral'; if (energyLevel == 4) return 'High'; return 'Very High'; } 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; } } }