import 'package:flutter/material.dart'; import 'package:flutter/cupertino.dart'; import 'package:go_router/go_router.dart'; import 'package:lucide_icons/lucide_icons.dart'; import '../../../../core/di/injection.dart'; import '../../../../core/theme/design_tokens.dart'; import '../calendar_state_manager.dart'; import '../calendar_time_utils.dart'; import '../widgets/bottom_dock.dart'; import '../widgets/create_event_sheet.dart'; import '../../data/services/mock_calendar_service.dart'; class CalendarMonthScreen extends StatefulWidget { final bool resetToToday; const CalendarMonthScreen({super.key, this.resetToToday = false}); @override State createState() => _CalendarMonthScreenState(); } class _CalendarMonthScreenState extends State { late final CalendarStateManager _calendarManager; late DateTime _currentMonth; late DateTime _selectedDate; Key _eventsKey = UniqueKey(); @override void initState() { super.initState(); _calendarManager = sl(); if (widget.resetToToday) { _calendarManager.resetToToday(); } final savedDate = _calendarManager.selectedDate; _selectedDate = savedDate; _currentMonth = DateTime(savedDate.year, savedDate.month, 1); } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: AppColors.todoBg, body: SafeArea( child: Column( children: [ _buildHeader(), Expanded( child: SingleChildScrollView( child: Padding( padding: const EdgeInsets.only(bottom: 84), child: _buildMonthContent(), ), ), ), _buildBottomDock(), ], ), ), ); } Widget _buildHeader() { return SizedBox( height: 76, child: Padding( padding: const EdgeInsets.only(left: 16, right: 16, top: 14, bottom: 6), child: Column( children: [ SizedBox( height: 56, child: Row( children: [ GestureDetector( onTap: () => _showMonthPicker(), child: Row( children: [ Text( '${_currentMonth.month}月', style: const TextStyle( fontSize: 22, fontWeight: FontWeight.w700, color: AppColors.slate900, ), ), const SizedBox(width: 6), const Icon( LucideIcons.chevronDown, size: 16, color: AppColors.slate900, ), ], ), ), const Spacer(), GestureDetector( onTap: () => CreateEventSheet.show( context, onSaved: () { setState(() { _eventsKey = UniqueKey(); }); }, ), child: Container( width: 36, height: 36, decoration: BoxDecoration( color: AppColors.blue600, borderRadius: BorderRadius.circular(18), ), child: const Icon( LucideIcons.plus, size: 20, color: Colors.white, ), ), ), ], ), ), ], ), ), ); } Widget _buildMonthContent() { return Column( children: [ _buildWeekdayHeader(), Container(height: 1, color: AppColors.border), ..._buildWeeks(), ], ); } Widget _buildWeekdayHeader() { const weekdays = ['日', '一', '二', '三', '四', '五', '六']; return SizedBox( height: 40, child: Padding( padding: const EdgeInsets.symmetric(horizontal: 16), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: weekdays .map( (day) => SizedBox( width: 36, child: Center( child: Text( day, style: const TextStyle( fontSize: 11, fontWeight: FontWeight.w600, color: AppColors.slate400, ), ), ), ), ) .toList(), ), ), ); } List _buildWeeks() { final firstDayOfMonth = DateTime( _currentMonth.year, _currentMonth.month, 1, ); final lastDayOfMonth = DateTime( _currentMonth.year, _currentMonth.month + 1, 0, ); final startWeekday = firstDayOfMonth.weekday % 7; final daysInMonth = lastDayOfMonth.day; final totalCells = ((daysInMonth + startWeekday) / 7).ceil() * 7; final weeks = []; for (var weekStart = 0; weekStart < totalCells; weekStart += 7) { weeks.add(_buildWeekRow(weekStart, startWeekday, daysInMonth)); if (weekStart + 7 < totalCells) { weeks.add(Container(height: 1, color: AppColors.border)); } } return weeks; } Widget _buildWeekRow(int weekStart, int startWeekday, int daysInMonth) { return Padding( padding: const EdgeInsets.only(left: 16, right: 16, top: 14, bottom: 0), child: Column( children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: List.generate(7, (index) { final dayIndex = weekStart + index - startWeekday + 1; if (dayIndex < 1 || dayIndex > daysInMonth) { return SizedBox(width: 36, height: 36); } final date = DateTime( _currentMonth.year, _currentMonth.month, dayIndex, ); final isSelected = isSameDay(_selectedDate, date); return GestureDetector( onTap: () { setState(() { _selectedDate = date; }); _calendarManager.setSelectedDate(date); _calendarManager.setViewType(CalendarViewType.month); final ymd = formatYmd(date); context.push('/calendar/dayweek?date=$ymd'); }, child: Container( width: 36, height: 36, decoration: BoxDecoration( color: isSelected ? AppColors.blue100 : Colors.transparent, borderRadius: BorderRadius.circular(18), ), child: Center( child: Text( '$dayIndex', style: TextStyle( fontSize: 15, fontWeight: isSelected ? FontWeight.w600 : FontWeight.normal, color: isSelected ? AppColors.blue600 : AppColors.slate900, ), ), ), ), ); }), ), const SizedBox(height: 10), KeyedSubtree( key: _eventsKey, child: _buildWeekEvents(weekStart, startWeekday, daysInMonth), ), ], ), ); } Widget _buildWeekEvents(int weekStart, int startWeekday, int daysInMonth) { final firstDayOfMonth = DateTime( _currentMonth.year, _currentMonth.month, 1, ); final weekFirstDate = firstDayOfMonth.add( Duration(days: weekStart - startWeekday), ); return SizedBox( height: 70, child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: List.generate(7, (index) { final dayIndex = weekStart + index - startWeekday + 1; if (dayIndex < 1 || dayIndex > daysInMonth) { return const SizedBox(width: 38, height: 1); } final date = weekFirstDate.add(Duration(days: index)); final events = CalendarService().getEventsForDay(date); final displayEvents = events.take(2).toList(); final remainingCount = events.length - 2; return SizedBox( width: 38, height: 70, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ ...displayEvents.map((event) { final color = _parseColor(event.metadata?.color); return GestureDetector( onTap: () { _calendarManager.setSelectedDate(date); context.push('/calendar/events/${event.id}'); }, child: Container( margin: const EdgeInsets.only(bottom: 2), padding: const EdgeInsets.symmetric( horizontal: 4, vertical: 2, ), decoration: BoxDecoration( color: color.withValues(alpha: 0.2), borderRadius: BorderRadius.circular(4), ), child: Text( event.title, style: TextStyle( fontSize: 9, color: color, fontWeight: FontWeight.w500, ), maxLines: 1, overflow: TextOverflow.ellipsis, ), ), ); }), if (remainingCount > 0) GestureDetector( onTap: () { _calendarManager.setSelectedDate(date); _calendarManager.setViewType(CalendarViewType.day); context.push('/calendar/dayweek?date=${formatYmd(date)}'); }, child: Text( '+$remainingCount', style: const TextStyle( fontSize: 9, color: AppColors.slate500, fontWeight: FontWeight.w500, ), ), ), ], ), ); }), ), ); } Color _parseColor(String? hex) { if (hex == null || hex.isEmpty) return AppColors.blue600; try { return Color(int.parse(hex.replaceFirst('#', '0xFF'))); } catch (_) { return AppColors.blue600; } } void _showMonthPicker() { showModalBottomSheet( context: context, builder: (context) => Container( height: 300, color: Colors.white, child: Column( children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ TextButton( onPressed: () => Navigator.pop(context), child: const Text('取消'), ), TextButton( onPressed: () => Navigator.pop(context), child: const Text('确定'), ), ], ), Expanded( child: Row( children: [ Expanded( child: CupertinoPicker( itemExtent: 40, scrollController: FixedExtentScrollController( initialItem: _currentMonth.year - 2020, ), onSelectedItemChanged: (index) { setState(() { _currentMonth = DateTime( 2020 + index, _currentMonth.month, 1, ); }); }, children: List.generate(20, (index) { return Center(child: Text('${2020 + index}年')); }), ), ), Expanded( child: CupertinoPicker( itemExtent: 40, scrollController: FixedExtentScrollController( initialItem: _currentMonth.month - 1, ), onSelectedItemChanged: (index) { setState(() { _currentMonth = DateTime( _currentMonth.year, index + 1, 1, ); }); }, children: List.generate(12, (index) { return Center(child: Text('${index + 1}月')); }), ), ), ], ), ), ], ), ), ); } Widget _buildBottomDock() { return BottomDock( activeTab: DockTab.calendar, onTodoTap: () { _calendarManager.setViewType(CalendarViewType.month); context.push('/todo'); }, onCalendarTap: () {}, onHomeTap: () => context.go('/home'), ); } }