import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; import 'package:provider/provider.dart'; import '../providers/supplement_provider.dart'; import '../providers/sync_provider.dart'; class HistoryScreen extends StatefulWidget { const HistoryScreen({super.key}); @override State createState() => _HistoryScreenState(); } class _HistoryScreenState extends State { int _selectedMonth = DateTime.now().month; int _selectedYear = DateTime.now().year; DateTime? _selectedDay; @override void initState() { super.initState(); _selectedDay = DateTime.now(); // Set today as the default selected day WidgetsBinding.instance.addPostFrameCallback((_) { context.read().loadMonthlyIntakes(_selectedYear, _selectedMonth); }); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Intake History'), backgroundColor: Theme.of(context).colorScheme.inversePrimary, actions: [ Consumer( builder: (context, syncProvider, child) { if (!syncProvider.isConfigured) { return const SizedBox.shrink(); } return IconButton( icon: syncProvider.isSyncing ? const SizedBox( width: 20, height: 20, child: CircularProgressIndicator(strokeWidth: 2), ) : syncProvider.status.name == 'success' && DateTime.now().difference(syncProvider.lastSyncTime ?? DateTime.now()).inSeconds < 5 ? const Icon(Icons.check, color: Colors.green) : const Icon(Icons.sync), onPressed: syncProvider.isSyncing ? null : () { syncProvider.performManualSync(); }, tooltip: syncProvider.isSyncing ? 'Syncing...' : 'Force Sync', ); }, ), ], ), body: _buildCalendarView(), ); } Widget _buildCalendarView() { return Column( children: [ Container( padding: const EdgeInsets.all(16), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ IconButton( onPressed: () { setState(() { if (_selectedMonth == 1) { _selectedMonth = 12; _selectedYear--; } else { _selectedMonth--; } }); context.read().loadMonthlyIntakes(_selectedYear, _selectedMonth); }, icon: const Icon(Icons.chevron_left), ), InkWell( onTap: _showMonthPicker, borderRadius: BorderRadius.circular(8), child: Padding( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), child: Text( DateFormat('MMMM yyyy').format(DateTime(_selectedYear, _selectedMonth)), style: const TextStyle( fontSize: 18, fontWeight: FontWeight.bold, ), ), ), ), IconButton( onPressed: () { final now = DateTime.now(); if (_selectedYear < now.year || (_selectedYear == now.year && _selectedMonth < now.month)) { setState(() { if (_selectedMonth == 12) { _selectedMonth = 1; _selectedYear++; } else { _selectedMonth++; } }); context.read().loadMonthlyIntakes(_selectedYear, _selectedMonth); } }, icon: const Icon(Icons.chevron_right), ), ], ), ), Expanded( child: Consumer( builder: (context, provider, child) { // Group intakes by date final groupedIntakes = >>{}; for (final intake in provider.monthlyIntakes) { final date = DateTime.parse(intake['takenAt']); final dateKey = DateFormat('yyyy-MM-dd').format(date); groupedIntakes.putIfAbsent(dateKey, () => []); groupedIntakes[dateKey]!.add(intake); } return LayoutBuilder( builder: (context, constraints) { final isWideScreen = constraints.maxWidth > 800; if (isWideScreen) { // Desktop/tablet layout: side-by-side return Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Calendar on the left Expanded( flex: 2, child: Container( margin: const EdgeInsets.all(16), child: _buildCalendar(groupedIntakes), ), ), // Selected day details on the right Expanded( flex: 3, child: Container( margin: const EdgeInsets.fromLTRB(0, 16, 16, 16), child: _buildSelectedDayDetails(groupedIntakes), ), ), ], ); } else { // Mobile layout: vertical stack return Column( children: [ // Calendar Container( margin: const EdgeInsets.symmetric(horizontal: 16), child: _buildCalendar(groupedIntakes), ), const SizedBox(height: 16), // Selected day details Expanded( child: _buildSelectedDayDetails(groupedIntakes), ), ], ); } }, ); }, ), ), ], ); } void _showMonthPicker() async { final now = DateTime.now(); final picked = await showDatePicker( context: context, initialDate: DateTime(_selectedYear, _selectedMonth), firstDate: DateTime(2020), lastDate: now, initialDatePickerMode: DatePickerMode.year, builder: (context, child) { return Theme( data: Theme.of(context).copyWith( dialogTheme: DialogThemeData( shape: const RoundedRectangleBorder( borderRadius: BorderRadius.all(Radius.circular(16)), ), ), ), child: child!, ); }, ); if (picked != null) { setState(() { _selectedMonth = picked.month; _selectedYear = picked.year; _selectedDay = picked; // Set the selected day to the picked date }); context.read().loadMonthlyIntakes(_selectedYear, _selectedMonth); } } void _deleteIntake(BuildContext context, int intakeId, String supplementName) { showDialog( context: context, builder: (context) => AlertDialog( title: const Text('Delete Intake'), content: Text('Are you sure you want to delete this $supplementName intake?'), actions: [ TextButton( onPressed: () => Navigator.of(context).pop(), child: const Text('Cancel'), ), ElevatedButton( onPressed: () async { await context.read().deleteIntake(intakeId); Navigator.of(context).pop(); // Force refresh of the UI setState(() {}); // Force refresh of the current view data context.read().loadMonthlyIntakes(_selectedYear, _selectedMonth); ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('$supplementName intake deleted'), backgroundColor: Colors.red, ), ); }, style: ElevatedButton.styleFrom(backgroundColor: Colors.red), child: const Text('Delete'), ), ], ), ); } Widget _buildCalendar(Map>> groupedIntakes) { final firstDayOfMonth = DateTime(_selectedYear, _selectedMonth, 1); final lastDayOfMonth = DateTime(_selectedYear, _selectedMonth + 1, 0); final firstWeekday = firstDayOfMonth.weekday; final daysInMonth = lastDayOfMonth.day; // Calculate how many cells we need (including empty ones for alignment) final totalCells = ((daysInMonth + firstWeekday - 1) / 7).ceil() * 7; final weeks = (totalCells / 7).ceil(); return LayoutBuilder( builder: (context, constraints) { final isWideScreen = constraints.maxWidth > 800; // Calculate calendar height based on number of weeks needed final cellHeight = isWideScreen ? 56.0 : 48.0; final calendarContentHeight = (weeks * cellHeight) + 60; // +60 for headers and padding final calendarHeight = isWideScreen ? 400.0 : calendarContentHeight; return Card( child: Container( height: calendarHeight, child: Padding( padding: const EdgeInsets.all(16), child: Column( mainAxisSize: MainAxisSize.min, children: [ // Calendar header (weekdays) Row( children: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'] .map((day) => Expanded( child: Center( child: Text( day, style: TextStyle( fontWeight: FontWeight.bold, color: Theme.of(context).colorScheme.onSurfaceVariant, fontSize: isWideScreen ? 14 : 12, ), ), ), )) .toList(), ), const SizedBox(height: 8), // Calendar grid Expanded( child: GridView.builder( shrinkWrap: true, physics: const ClampingScrollPhysics(), gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 7, childAspectRatio: 1.0, mainAxisSpacing: 4, crossAxisSpacing: 4, ), itemCount: totalCells, itemBuilder: (context, index) { final dayNumber = index - firstWeekday + 2; if (dayNumber < 1 || dayNumber > daysInMonth) { return const SizedBox(); // Empty cell } final date = DateTime(_selectedYear, _selectedMonth, dayNumber); final dateKey = DateFormat('yyyy-MM-dd').format(date); final hasIntakes = groupedIntakes.containsKey(dateKey); final intakeCount = hasIntakes ? groupedIntakes[dateKey]!.length : 0; final isSelected = _selectedDay != null && DateFormat('yyyy-MM-dd').format(_selectedDay!) == dateKey; final isToday = DateFormat('yyyy-MM-dd').format(DateTime.now()) == dateKey; return GestureDetector( onTap: () { setState(() { _selectedDay = date; }); }, child: Container( margin: const EdgeInsets.all(1), decoration: BoxDecoration( color: isSelected ? Theme.of(context).colorScheme.primary : hasIntakes ? Theme.of(context).colorScheme.primaryContainer : null, border: isToday ? Border.all(color: Theme.of(context).colorScheme.secondary, width: 2) : null, borderRadius: BorderRadius.circular(8), ), child: Stack( children: [ Center( child: Text( '$dayNumber', style: TextStyle( color: isSelected ? Theme.of(context).colorScheme.onPrimary : hasIntakes ? Theme.of(context).colorScheme.onPrimaryContainer : Theme.of(context).colorScheme.onSurface, fontWeight: isToday ? FontWeight.bold : FontWeight.normal, fontSize: isWideScreen ? 16 : 14, ), ), ), if (hasIntakes) Positioned( top: 2, right: 2, child: Container( padding: EdgeInsets.all(isWideScreen ? 3 : 2), decoration: BoxDecoration( color: isSelected ? Theme.of(context).colorScheme.onPrimary : Theme.of(context).colorScheme.primary, borderRadius: BorderRadius.circular(8), ), constraints: BoxConstraints( minWidth: isWideScreen ? 18 : 16, minHeight: isWideScreen ? 18 : 16, ), child: Text( '$intakeCount', style: TextStyle( color: isSelected ? Theme.of(context).colorScheme.primary : Theme.of(context).colorScheme.onPrimary, fontSize: isWideScreen ? 11 : 10, fontWeight: FontWeight.bold, ), textAlign: TextAlign.center, ), ), ), ], ), ), ); }, ), ), ], ), ), ), ); }, ); } Widget _buildSelectedDayDetails(Map>> groupedIntakes) { return LayoutBuilder( builder: (context, constraints) { final isWideScreen = constraints.maxWidth > 600; if (_selectedDay == null) { return Card( child: Center( child: Padding( padding: const EdgeInsets.all(32), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( Icons.touch_app, size: isWideScreen ? 64 : 48, color: Theme.of(context).colorScheme.onSurfaceVariant, ), const SizedBox(height: 16), Text( 'Tap a date on the calendar to see details', style: TextStyle( fontSize: isWideScreen ? 18 : 16, color: Theme.of(context).colorScheme.onSurfaceVariant, ), textAlign: TextAlign.center, ), ], ), ), ), ); } final dateKey = DateFormat('yyyy-MM-dd').format(_selectedDay!); final dayIntakes = groupedIntakes[dateKey] ?? []; if (dayIntakes.isEmpty) { return Card( child: Center( child: Padding( padding: const EdgeInsets.all(32), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( Icons.event_available, size: isWideScreen ? 64 : 48, color: Theme.of(context).colorScheme.onSurfaceVariant, ), const SizedBox(height: 16), Text( 'No supplements taken on ${DateFormat('MMM d, yyyy').format(_selectedDay!)}', style: TextStyle( fontSize: isWideScreen ? 18 : 16, color: Theme.of(context).colorScheme.onSurfaceVariant, ), textAlign: TextAlign.center, ), ], ), ), ), ); } return Card( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( width: double.infinity, padding: EdgeInsets.all(isWideScreen ? 20 : 16), decoration: BoxDecoration( color: Theme.of(context).colorScheme.primaryContainer, borderRadius: const BorderRadius.only( topLeft: Radius.circular(12), topRight: Radius.circular(12), ), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( DateFormat('EEEE, MMM d, yyyy').format(_selectedDay!), style: TextStyle( fontSize: isWideScreen ? 20 : 18, fontWeight: FontWeight.bold, color: Theme.of(context).colorScheme.onPrimaryContainer, ), ), const SizedBox(height: 4), Text( '${dayIntakes.length} supplement${dayIntakes.length != 1 ? 's' : ''} taken', style: TextStyle( fontSize: isWideScreen ? 16 : 14, color: Theme.of(context).colorScheme.onPrimaryContainer.withOpacity(0.8), ), ), ], ), ), Expanded( child: ListView.builder( padding: EdgeInsets.all(isWideScreen ? 20 : 16), itemCount: dayIntakes.length, itemBuilder: (context, index) { final intake = dayIntakes[index]; final takenAt = DateTime.parse(intake['takenAt']); final units = (intake['unitsTaken'] as num?)?.toDouble() ?? 1.0; return Card( margin: const EdgeInsets.only(bottom: 12), elevation: 2, child: Padding( padding: EdgeInsets.all(isWideScreen ? 16 : 12), child: Row( children: [ CircleAvatar( backgroundColor: Theme.of(context).colorScheme.primary, radius: isWideScreen ? 24 : 20, child: Icon( Icons.medication, color: Theme.of(context).colorScheme.onPrimary, size: isWideScreen ? 24 : 20, ), ), SizedBox(width: isWideScreen ? 16 : 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( intake['supplementName'], style: TextStyle( fontWeight: FontWeight.w600, fontSize: isWideScreen ? 16 : 14, ), ), const SizedBox(height: 4), Text( '${units.toStringAsFixed(units % 1 == 0 ? 0 : 1)} ${intake['supplementUnitType'] ?? 'units'} at ${DateFormat('HH:mm').format(takenAt)}', style: TextStyle( color: Theme.of(context).colorScheme.primary, fontWeight: FontWeight.w500, fontSize: isWideScreen ? 14 : 12, ), ), if (intake['notes'] != null && intake['notes'].toString().isNotEmpty) ...[ const SizedBox(height: 4), Text( intake['notes'], style: TextStyle( fontSize: isWideScreen ? 13 : 12, fontStyle: FontStyle.italic, color: Theme.of(context).colorScheme.onSurfaceVariant, ), ), ], ], ), ), IconButton( icon: Icon( Icons.delete_outline, color: Colors.red.shade400, size: isWideScreen ? 24 : 20, ), onPressed: () => _deleteIntake(context, intake['id'], intake['supplementName']), tooltip: 'Delete intake', ), ], ), ), ); }, ), ), ], ), ); }, ); } @override void dispose() { super.dispose(); } }