import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:popover/popover.dart'; import '../models/supplement.dart'; import '../providers/settings_provider.dart'; import '../providers/simple_sync_provider.dart'; import '../providers/supplement_provider.dart'; import '../services/database_sync_service.dart'; import '../widgets/dialogs/take_supplement_dialog.dart'; class TodayScheduleScreen extends StatelessWidget { const TodayScheduleScreen({super.key}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text("My Supplements"), 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 == SyncStatus.completed && syncProvider.lastSyncTime != null && DateTime.now().difference(syncProvider.lastSyncTime!).inSeconds < 5 ? const Icon(Icons.check, color: Colors.green) : const Icon(Icons.sync), onPressed: syncProvider.isSyncing ? null : () { syncProvider.syncDatabase(); }, tooltip: syncProvider.isSyncing ? 'Syncing...' : 'Force Sync', ); }, ), ], ), body: Consumer2( builder: (context, provider, settingsProvider, child) { if (provider.isLoading) { return const Center(child: CircularProgressIndicator()); } if (provider.todayIntakes.isEmpty && provider.supplements.isEmpty) { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( Icons.schedule, size: 64, color: Theme.of(context).colorScheme.onSurfaceVariant, ), const SizedBox(height: 16), Text( 'No schedule for today', style: TextStyle( fontSize: 18, color: Theme.of(context).colorScheme.onSurfaceVariant, ), ), const SizedBox(height: 8), Text( 'Add supplements with reminder times to see your schedule', style: TextStyle( color: Theme.of(context).colorScheme.onSurfaceVariant, ), ), ], ), ); } return RefreshIndicator( onRefresh: () async { await provider.loadSupplements(); await provider.refreshDailyStatus(); }, child: _buildTimelineView(context, provider, settingsProvider), ); }, ), ); } Widget _buildTimelineView(BuildContext context, SupplementProvider provider, SettingsProvider settingsProvider) { final scheduledItems = _getScheduledItems(provider.supplements, provider.todayIntakes); final groupedItems = _groupScheduledItemsByTimeOfDay(scheduledItems); return ListView( padding: const EdgeInsets.all(16), children: [ if (groupedItems['morning']!.isNotEmpty) ...[ _buildTimeSectionHeader('Morning', Icons.wb_sunny, Colors.orange), ...groupedItems['morning']!.map((item) => _buildScheduledItem(context, item)), const SizedBox(height: 12), ], if (groupedItems['afternoon']!.isNotEmpty) ...[ _buildTimeSectionHeader('Afternoon', Icons.light_mode, Colors.blue), ...groupedItems['afternoon']!.map((item) => _buildScheduledItem(context, item)), const SizedBox(height: 12), ], if (groupedItems['evening']!.isNotEmpty) ...[ _buildTimeSectionHeader('Evening', Icons.nightlight_round, Colors.indigo), ...groupedItems['evening']!.map((item) => _buildScheduledItem(context, item)), const SizedBox(height: 12), ], if (groupedItems['night']!.isNotEmpty) ...[ _buildTimeSectionHeader('Night', Icons.bedtime, Colors.purple), ...groupedItems['night']!.map((item) => _buildScheduledItem(context, item)), ], ], ); } Widget _buildTimeSectionHeader(String title, IconData icon, Color color) { return Container( margin: const EdgeInsets.only(bottom: 8, top: 8), padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), decoration: BoxDecoration( color: color.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(8), ), child: Row( children: [ Icon(icon, size: 16, color: color), const SizedBox(width: 8), Text( title, style: TextStyle( fontSize: 14, fontWeight: FontWeight.w600, color: color, ), ), ], ), ); } Widget _buildScheduledItem(BuildContext context, Map item) { final time = item['time'] as String; final supplement = item['supplement'] as Supplement; final status = item['status'] as String; final actualTime = item['actualTime'] as String?; final isCompleted = status == 'on_time' || status == 'off_time'; final isOnTime = status == 'on_time'; return Builder( builder: (context) => InkWell( onTap: () { showPopover( context: context, bodyBuilder: (context) => Container( constraints: const BoxConstraints(maxWidth: 150), decoration: BoxDecoration( color: Theme.of(context).colorScheme.surface, borderRadius: BorderRadius.circular(12), border: Border.all( color: Theme.of(context).colorScheme.outline.withValues(alpha: 0.3), width: 1, ), boxShadow: [ BoxShadow( color: Theme.of(context).colorScheme.shadow.withValues(alpha: 0.1), blurRadius: 8, offset: const Offset(0, 2), ), ], ), child: Column( mainAxisSize: MainAxisSize.min, children: [ if (isCompleted) _buildPopoverItem( context: context, icon: Icons.undo, label: 'Undo Last Taken', onTap: () async { Navigator.of(context).pop(); final provider = context.read(); final mostRecentIntake = provider.getMostRecentIntake(supplement.id!); if (mostRecentIntake != null) { await provider.deleteIntake(mostRecentIntake['id']); // Refresh the schedule after undoing if (context.mounted) { provider.refreshDailyStatus(); } } }, color: Colors.orange, ), _buildPopoverItem( context: context, icon: isCompleted ? Icons.add_circle_outline : Icons.medication, label: isCompleted ? 'Take Again' : 'Take', onTap: () async { Navigator.of(context).pop(); await showTakeSupplementDialog(context, supplement); // Refresh the schedule after taking if (context.mounted) { context.read().refreshDailyStatus(); } }, ), ], ), ), direction: PopoverDirection.top, width: 180, height: null, arrowHeight: 0, arrowWidth: 0, backgroundColor: Colors.transparent, shadow: const [], ); }, borderRadius: BorderRadius.circular(8), child: AnimatedContainer( duration: const Duration(milliseconds: 150), margin: const EdgeInsets.only(bottom: 6), padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), decoration: BoxDecoration( color: isCompleted ? Theme.of(context).colorScheme.surface : Theme.of(context).colorScheme.primary.withValues(alpha: 0.05), borderRadius: BorderRadius.circular(8), border: Border.all( color: isCompleted ? Theme.of(context).colorScheme.outline.withValues(alpha: 0.3) : Theme.of(context).colorScheme.primary.withValues(alpha: 0.3), ), ), child: Row( children: [ Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( time, style: TextStyle( fontSize: 12, fontWeight: FontWeight.w500, color: isCompleted ? Theme.of(context).colorScheme.onSurfaceVariant : Theme.of(context).colorScheme.primary, ), ), if (actualTime != null && actualTime != time) Text( 'Taken: $actualTime', style: TextStyle( fontSize: 10, color: Theme.of(context).colorScheme.onSurfaceVariant.withValues(alpha: 0.6), ), ), ], ), const SizedBox(width: 12), Expanded( child: Text( supplement.name, style: TextStyle( fontSize: 14, fontWeight: FontWeight.w500, color: isCompleted ? Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.7) : Theme.of(context).colorScheme.onSurface, decoration: isCompleted ? TextDecoration.lineThrough : null, ), ), ), if (isCompleted) Row( children: [ if (!isOnTime) Icon( Icons.schedule, size: 14, color: Colors.orange, ), const SizedBox(width: 4), Icon( Icons.check_circle, size: 16, color: isOnTime ? Colors.green : Colors.orange, ), ], ) else Container( padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2), decoration: BoxDecoration( color: Theme.of(context).colorScheme.primary, borderRadius: BorderRadius.circular(12), ), child: Text( 'Pending', style: TextStyle( fontSize: 10, color: Theme.of(context).colorScheme.onPrimary, fontWeight: FontWeight.w500, ), ), ), ], ), ), ), ); } Widget _buildPopoverItem({ required BuildContext context, required IconData icon, required String label, required VoidCallback onTap, Color? color, }) { return InkWell( onTap: onTap, child: Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), child: Row( children: [ Icon( icon, size: 18, color: color ?? Theme.of(context).colorScheme.onSurface, ), const SizedBox(width: 12), Text( label, style: TextStyle( fontSize: 14, color: color ?? Theme.of(context).colorScheme.onSurface, fontWeight: FontWeight.w500, ), ), ], ), ), ); } List> _getScheduledItems(List supplements, List> todayIntakes) { final List> scheduledItems = []; for (final supplement in supplements) { for (final reminderTime in supplement.reminderTimes) { // Parse reminder time (format: "HH:MM") final parts = reminderTime.split(':'); final hour = int.parse(parts[0]); final minute = int.parse(parts[1]); // Check if this reminder has been taken today String status = 'pending'; String? actualTime; for (final intake in todayIntakes) { if (intake['supplement_id'] == supplement.id) { final takenAt = DateTime.parse(intake['takenAt']); final reminderDateTime = DateTime( takenAt.year, takenAt.month, takenAt.day, hour, minute, ); // Check if taken within 1 hour of reminder time final timeDiff = (takenAt.difference(reminderDateTime)).inMinutes.abs(); if (timeDiff <= 60) { status = 'on_time'; actualTime = '${takenAt.hour.toString().padLeft(2, '0')}:${takenAt.minute.toString().padLeft(2, '0')}'; } else { status = 'off_time'; actualTime = '${takenAt.hour.toString().padLeft(2, '0')}:${takenAt.minute.toString().padLeft(2, '0')}'; } break; } } scheduledItems.add({ 'time': reminderTime, 'supplement': supplement, 'status': status, 'actualTime': actualTime, }); } } // Sort by time scheduledItems.sort((a, b) { final timeA = a['time'] as String; final timeB = b['time'] as String; return timeA.compareTo(timeB); }); return scheduledItems; } Map>> _groupScheduledItemsByTimeOfDay(List> scheduledItems) { final Map>> grouped = { 'morning': >[], 'afternoon': >[], 'evening': >[], 'night': >[], }; for (final item in scheduledItems) { final time = item['time'] as String; final parts = time.split(':'); final hour = int.parse(parts[0]); String category; if (hour >= 6 && hour < 12) { category = 'morning'; } else if (hour >= 12 && hour < 18) { category = 'afternoon'; } else if (hour >= 18 && hour < 22) { category = 'evening'; } else { category = 'night'; } grouped[category]!.add(item); } // Sort items by time within each category for (final category in grouped.keys) { grouped[category]!.sort((a, b) { final timeA = a['time'] as String; final timeB = b['time'] as String; return timeA.compareTo(timeB); }); } return grouped; } }