import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; import 'package:shared_preferences/shared_preferences.dart'; import '../services/google_calendar_service.dart'; class CalendarScreen extends StatefulWidget { const CalendarScreen({super.key}); @override State createState() => _CalendarScreenState(); } class _CalendarScreenState extends State { final GoogleCalendarService _calendarService = GoogleCalendarService(); bool _isLoading = false; List _events = []; String? _errorMessage; String _bulletColor = 'Blue'; @override void initState() { super.initState(); _loadBulletColor(); _loadEvents(); } @override void dispose() { _calendarService.dispose(); super.dispose(); } Future _loadBulletColor() async { final prefs = await SharedPreferences.getInstance(); setState(() { _bulletColor = prefs.getString('bulletColor') ?? 'Blue'; }); } Color _getAccentColor() { switch (_bulletColor.toLowerCase()) { case 'blue': return Colors.blue; case 'green': return Colors.green; case 'yellow': return Colors.yellow; case 'purple': return Colors.purple; case 'cyan': return Colors.cyan; case 'teal': return Colors.teal; case 'indigo': return Colors.indigo; case 'pink': return Colors.pink; case 'lime': return Colors.lime; case 'mint': return Colors.greenAccent; case 'orange': return Colors.orange; case 'violet': return Colors.deepPurpleAccent; case 'amber': return Colors.amber; case 'brown': return Colors.brown; case 'grey': return Colors.grey; default: return Colors.blue; } } bool _isValidEvent(CalendarEvent event) { // Filter out events that are just single names or too short if (event.summary == null || event.summary!.trim().isEmpty) { return false; } final summary = event.summary!.trim(); // Exclude if it's just a single word (likely just a name) if (!summary.contains(' ') && summary.length < 15) { return false; } return true; } Future _loadEvents() async { setState(() { _isLoading = true; _errorMessage = null; }); try { final events = await _calendarService.getEvents( startDate: DateTime.now(), endDate: DateTime.now().add(const Duration(days: 365)), ); // Get today's date at midnight for comparison final now = DateTime.now(); final today = DateTime(now.year, now.month, now.day); // Filter: valid events AND events from today onwards final filteredEvents = events.where((event) { if (!_isValidEvent(event)) return false; // Only include events that are today or in the future if (event.startTime != null) { final eventDate = DateTime( event.startTime!.year, event.startTime!.month, event.startTime!.day, ); return eventDate.isAtSameMomentAs(today) || eventDate.isAfter(today); } return false; }).toList(); filteredEvents.sort((a, b) { if (a.startTime == null) return 1; if (b.startTime == null) return -1; return a.startTime!.compareTo(b.startTime!); }); setState(() { _events = filteredEvents; }); } catch (e) { setState(() { _errorMessage = 'Error loading events: $e'; }); } finally { setState(() => _isLoading = false); } } @override Widget build(BuildContext context) { final accentColor = _getAccentColor(); if (_isLoading && _events.isEmpty) { return Scaffold( body: Center( child: CircularProgressIndicator(color: accentColor), ), ); } if (_errorMessage != null && _events.isEmpty) { return Scaffold( body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(Icons.error_outline, size: 64, color: accentColor), const SizedBox(height: 24), Padding( padding: const EdgeInsets.symmetric(horizontal: 32), child: Text( _errorMessage!, textAlign: TextAlign.center, style: const TextStyle(fontSize: 16), ), ), const SizedBox(height: 16), ElevatedButton.icon( onPressed: _loadEvents, icon: const Icon(Icons.refresh), label: const Text('Retry'), style: ElevatedButton.styleFrom( backgroundColor: accentColor, foregroundColor: Colors.white, ), ), ], ), ), ); } return Scaffold( backgroundColor: Colors.white, body: RefreshIndicator( onRefresh: _loadEvents, color: accentColor, child: _events.isEmpty ? ListView( children: [ SizedBox(height: MediaQuery.of(context).size.height * 0.3), Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(Icons.event_available, size: 64, color: Colors.grey[400]), const SizedBox(height: 16), const Text( 'No upcoming events', style: TextStyle(fontSize: 16, color: Colors.grey), ), const SizedBox(height: 8), Text( 'Pull down to refresh', style: TextStyle(fontSize: 14, color: Colors.grey[400]), ), ], ), ), ], ) : ListView.builder( itemCount: _events.length, padding: const EdgeInsets.all(8), itemBuilder: (context, index) { final event = _events[index]; return _buildEventCard(event, accentColor); }, ), ), ); } Widget _buildEventCard(CalendarEvent event, Color accentColor) { final startTime = event.startTime; final isAllDay = event.isAllDay; String timeText; if (isAllDay) { timeText = 'All day'; } else if (startTime != null) { timeText = DateFormat('HH:mm').format(startTime.toLocal()); } else { timeText = ''; } // Format the date for display String dateText = ''; bool isToday = false; if (startTime != null) { final now = DateTime.now(); final today = DateTime(now.year, now.month, now.day); final eventDay = DateTime(startTime.year, startTime.month, startTime.day); final difference = eventDay.difference(today).inDays; if (difference == 0) { dateText = 'Today'; isToday = true; } else if (difference == 1) { dateText = 'Tomorrow'; } else { dateText = DateFormat('EEEE, MMMM d').format(startTime); } } return Container( margin: const EdgeInsets.symmetric(vertical: 6, horizontal: 16), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(12), boxShadow: [ BoxShadow( color: Colors.black.withValues(alpha: 0.05), blurRadius: 4, offset: const Offset(0, 2), ), ], ), child: Material( color: Colors.transparent, child: InkWell( onTap: () => _showEventDetails(event, accentColor), borderRadius: BorderRadius.circular(12), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Date header Container( width: double.infinity, padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), decoration: BoxDecoration( color: isToday ? accentColor.withValues(alpha: 0.15) : Colors.grey[50], borderRadius: const BorderRadius.only( topLeft: Radius.circular(12), topRight: Radius.circular(12), ), ), child: Row( children: [ Container( width: 4, height: 16, decoration: BoxDecoration( color: accentColor, borderRadius: BorderRadius.circular(2), ), ), const SizedBox(width: 8), Text( dateText, style: TextStyle( fontSize: 14, fontWeight: FontWeight.bold, color: isToday ? accentColor : Colors.black87, ), ), if (isToday) ...[ const SizedBox(width: 8), Container( padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2), decoration: BoxDecoration( color: accentColor, borderRadius: BorderRadius.circular(8), ), child: const Text( 'NOW', style: TextStyle( fontSize: 10, fontWeight: FontWeight.bold, color: Colors.white, ), ), ), ], ], ), ), // Time and title Padding( padding: const EdgeInsets.all(16), child: Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ // Time indicator with accent color Container( width: 60, height: 60, decoration: BoxDecoration( color: accentColor.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(12), ), child: Center( child: Text( timeText, textAlign: TextAlign.center, style: TextStyle( fontSize: isAllDay ? 12 : 14, fontWeight: FontWeight.bold, color: accentColor, ), ), ), ), const SizedBox(width: 16), // Title Expanded( child: Text( event.summary ?? 'Untitled Event', style: const TextStyle( fontSize: 15, fontWeight: FontWeight.w600, color: Colors.black87, ), maxLines: 2, overflow: TextOverflow.ellipsis, ), ), const SizedBox(width: 8), // Arrow icon Icon( Icons.arrow_forward_ios, size: 16, color: Colors.grey[400], ), ], ), ), ], ), ), ), ); } void _showEventDetails(CalendarEvent event, Color accentColor) { final startTime = event.startTime; final endTime = event.endTime; final isAllDay = event.isAllDay; showDialog( context: context, builder: (context) => Dialog( shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), child: Container( constraints: const BoxConstraints(maxWidth: 400), padding: const EdgeInsets.all(24), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Container( width: 4, height: 24, decoration: BoxDecoration( color: accentColor, borderRadius: BorderRadius.circular(2), ), ), const SizedBox(width: 12), Expanded( child: Text( event.summary ?? 'Event Details', style: const TextStyle( fontSize: 20, fontWeight: FontWeight.bold, color: Colors.black87, ), ), ), ], ), const SizedBox(height: 24), if (startTime != null) ...[ _buildDetailRow( icon: Icons.calendar_today, label: 'Date', value: DateFormat('EEEE, MMMM d, y').format(startTime), accentColor: accentColor, ), const SizedBox(height: 16), ], if (!isAllDay && startTime != null && endTime != null) ...[ _buildDetailRow( icon: Icons.access_time, label: 'Time', value: '${DateFormat('HH:mm').format(startTime.toLocal())} - ${DateFormat('HH:mm').format(endTime.toLocal())}', accentColor: accentColor, ), const SizedBox(height: 16), ], if (isAllDay) ...[ _buildDetailRow( icon: Icons.access_time, label: 'Duration', value: 'All day event', accentColor: accentColor, ), const SizedBox(height: 16), ], if (event.location != null && event.location!.isNotEmpty) ...[ _buildDetailRow( icon: Icons.location_on, label: 'Location', value: event.location!, accentColor: accentColor, ), const SizedBox(height: 16), ], if (event.description != null && event.description!.isNotEmpty) ...[ _buildDetailRow( icon: Icons.notes, label: 'Notes', value: event.description!, accentColor: accentColor, ), const SizedBox(height: 16), ], const SizedBox(height: 8), SizedBox( width: double.infinity, child: ElevatedButton( onPressed: () => Navigator.pop(context), style: ElevatedButton.styleFrom( backgroundColor: accentColor, foregroundColor: Colors.white, padding: const EdgeInsets.symmetric(vertical: 14), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), ), child: const Text( 'Close', style: TextStyle(fontWeight: FontWeight.w600), ), ), ), ], ), ), ), ); } Widget _buildDetailRow({ required IconData icon, required String label, required String value, required Color accentColor, }) { return Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( padding: const EdgeInsets.all(8), decoration: BoxDecoration( color: accentColor.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(8), ), child: Icon(icon, size: 20, color: accentColor), ), const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( label, style: TextStyle( fontSize: 12, fontWeight: FontWeight.w600, color: Colors.grey[600], ), ), const SizedBox(height: 4), Text( value, style: const TextStyle( fontSize: 14, color: Colors.black87, ), ), ], ), ), ], ); } }