import 'dart:async'; import 'package:flutter/foundation.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import '../models/supplement.dart'; import '../models/supplement_intake.dart'; import '../services/database_helper.dart'; import '../services/notification_service.dart'; class SupplementProvider with ChangeNotifier, WidgetsBindingObserver { final DatabaseHelper _databaseHelper = DatabaseHelper.instance; final NotificationService _notificationService = NotificationService(); List _supplements = []; List> _todayIntakes = []; List> _monthlyIntakes = []; bool _isLoading = false; Timer? _persistentReminderTimer; Timer? _dateChangeTimer; DateTime _lastDateCheck = DateTime.now(); List get supplements => _supplements; List> get todayIntakes => _todayIntakes; List> get monthlyIntakes => _monthlyIntakes; bool get isLoading => _isLoading; Future initialize() async { // Add this provider as an observer for app lifecycle changes WidgetsBinding.instance.addObserver(this); await _notificationService.initialize(); // Set up the callback for handling supplement intake from notifications print('📱 Setting up notification callback...'); _notificationService.setTakeSupplementCallback((supplementId, supplementName, units, unitType) { print('📱 === NOTIFICATION CALLBACK TRIGGERED ==='); print('📱 Supplement ID: $supplementId'); print('📱 Supplement Name: $supplementName'); print('📱 Units: $units'); print('📱 Unit Type: $unitType'); // Record the intake when user taps "Take" on notification recordIntake(supplementId, 0.0, unitsTaken: units); print('📱 Intake recorded successfully'); print('📱 === CALLBACK COMPLETE ==='); if (kDebugMode) { print('📱 Recorded intake from notification: $supplementName ($units $unitType)'); } }); print('📱 Notification callback setup complete'); // Request permissions with error handling try { await _notificationService.requestPermissions(); } catch (e) { if (kDebugMode) { print('Error requesting notification permissions: $e'); } // Continue without notifications rather than crashing } await loadSupplements(); await loadTodayIntakes(); // Reschedule notifications for all active supplements to ensure persistence await _rescheduleAllNotifications(); // Start periodic checking for persistent reminders (every 5 minutes) _startPersistentReminderCheck(); // Start date change monitoring to reset daily intake status _startDateChangeMonitoring(); } void _startPersistentReminderCheck() { // Cancel any existing timer _persistentReminderTimer?.cancel(); // Check every 5 minutes for persistent reminders _persistentReminderTimer = Timer.periodic(const Duration(minutes: 5), (timer) async { try { // This will be called from settings provider context, so we need to import it await _checkPersistentReminders(); } catch (e) { if (kDebugMode) { print('Error checking persistent reminders: $e'); } } }); // Also check immediately _checkPersistentReminders(); } void _startDateChangeMonitoring() { // Cancel any existing timer _dateChangeTimer?.cancel(); // Check every minute if the date has changed _dateChangeTimer = Timer.periodic(const Duration(minutes: 1), (timer) async { final now = DateTime.now(); final currentDate = DateTime(now.year, now.month, now.day); final lastCheckDate = DateTime(_lastDateCheck.year, _lastDateCheck.month, _lastDateCheck.day); if (currentDate != lastCheckDate) { if (kDebugMode) { print('Date changed detected: ${lastCheckDate} -> ${currentDate}'); print('Refreshing today\'s intakes for new day...'); } // Date has changed, refresh today's intakes _lastDateCheck = now; await loadTodayIntakes(); if (kDebugMode) { print('Today\'s intakes refreshed for new day'); } } }); } Future _checkPersistentReminders() async { // This method will be enhanced to accept settings from the UI layer // For now, we'll check with default settings // In practice, the UI should call checkPersistentRemindersWithSettings if (kDebugMode) { print('📱 Checking persistent reminders with default settings'); } } // Method to be called from UI with actual settings Future checkPersistentRemindersWithSettings({ required bool persistentReminders, required int reminderRetryInterval, required int maxRetryAttempts, }) async { print('📱 🔄 MANUAL CHECK: Persistent reminders called from UI'); await _notificationService.checkPersistentReminders( persistentReminders, reminderRetryInterval, maxRetryAttempts, ); } // Add a manual trigger method for testing Future triggerRetryCheck() async { print('📱 🚨 MANUAL TRIGGER: Forcing retry check...'); await checkPersistentRemindersWithSettings( persistentReminders: true, reminderRetryInterval: 5, // Force 5 minute interval for testing maxRetryAttempts: 3, ); } @override void dispose() { WidgetsBinding.instance.removeObserver(this); _persistentReminderTimer?.cancel(); _dateChangeTimer?.cancel(); super.dispose(); } @override void didChangeAppLifecycleState(AppLifecycleState state) { super.didChangeAppLifecycleState(state); if (state == AppLifecycleState.resumed) { // App came back to foreground, check if date changed if (kDebugMode) { print('App resumed, checking for date change...'); } forceCheckDateChange(); } } Future _rescheduleAllNotifications() async { if (kDebugMode) { print('📱 Rescheduling notifications for all active supplements...'); } for (final supplement in _supplements) { if (supplement.reminderTimes.isNotEmpty) { try { await _notificationService.scheduleSupplementReminders(supplement); } catch (e) { if (kDebugMode) { print('📱 Error rescheduling notifications for ${supplement.name}: $e'); } } } } if (kDebugMode) { print('📱 Finished rescheduling notifications'); } } Future loadSupplements() async { _isLoading = true; notifyListeners(); try { print('Loading supplements from database...'); _supplements = await _databaseHelper.getAllSupplements(); print('Loaded ${_supplements.length} supplements'); for (var supplement in _supplements) { print('Supplement: ${supplement.name}'); } } catch (e) { print('Error loading supplements: $e'); if (kDebugMode) { print('Error loading supplements: $e'); } } finally { _isLoading = false; notifyListeners(); } } Future addSupplement(Supplement supplement) async { try { print('Adding supplement: ${supplement.name}'); final id = await _databaseHelper.insertSupplement(supplement); print('Supplement inserted with ID: $id'); final newSupplement = supplement.copyWith(id: id); // Schedule notifications (skip if there's an error) try { await _notificationService.scheduleSupplementReminders(newSupplement); print('Notifications scheduled'); } catch (notificationError) { print('Warning: Could not schedule notifications: $notificationError'); } await loadSupplements(); print('Supplements reloaded, count: ${_supplements.length}'); } catch (e) { print('Error adding supplement: $e'); if (kDebugMode) { print('Error adding supplement: $e'); } rethrow; } } Future updateSupplement(Supplement supplement) async { try { await _databaseHelper.updateSupplement(supplement); // Reschedule notifications await _notificationService.scheduleSupplementReminders(supplement); await loadSupplements(); } catch (e) { if (kDebugMode) { print('Error updating supplement: $e'); } } } Future deleteSupplement(int id) async { try { await _databaseHelper.deleteSupplement(id); // Cancel notifications await _notificationService.cancelSupplementReminders(id); await loadSupplements(); } catch (e) { if (kDebugMode) { print('Error deleting supplement: $e'); } } } Future recordIntake(int supplementId, double dosage, {double? unitsTaken, String? notes, DateTime? takenAt}) async { try { final intake = SupplementIntake( supplementId: supplementId, takenAt: takenAt ?? DateTime.now(), dosageTaken: dosage, unitsTaken: unitsTaken ?? 1.0, notes: notes, ); await _databaseHelper.insertIntake(intake); await loadTodayIntakes(); // Show confirmation notification final supplement = _supplements.firstWhere((s) => s.id == supplementId); final unitsText = unitsTaken != null && unitsTaken != 1 ? '${unitsTaken.toStringAsFixed(unitsTaken % 1 == 0 ? 0 : 1)} ${supplement.unitType}' : ''; await _notificationService.showInstantNotification( 'Supplement Taken', 'Recorded ${supplement.name}${unitsText.isNotEmpty ? ' - $unitsText' : ''} (${supplement.ingredientsDisplay})', ); } catch (e) { if (kDebugMode) { print('Error recording intake: $e'); } } } Future loadTodayIntakes() async { try { final today = DateTime.now(); if (kDebugMode) { print('Loading intakes for date: ${today.year}-${today.month}-${today.day}'); } _todayIntakes = await _databaseHelper.getIntakesWithSupplementsForDate(today); if (kDebugMode) { print('Loaded ${_todayIntakes.length} intakes for today'); for (var intake in _todayIntakes) { print(' - Supplement ID: ${intake['supplement_id']}, taken at: ${intake['takenAt']}'); } } notifyListeners(); } catch (e) { if (kDebugMode) { print('Error loading today\'s intakes: $e'); } } } Future loadMonthlyIntakes(int year, int month) async { try { _monthlyIntakes = await _databaseHelper.getIntakesWithSupplementsForMonth(year, month); notifyListeners(); } catch (e) { if (kDebugMode) { print('Error loading monthly intakes: $e'); } } } Future>> getIntakesForDate(DateTime date) async { try { return await _databaseHelper.getIntakesWithSupplementsForDate(date); } catch (e) { if (kDebugMode) { print('Error loading intakes for date: $e'); } return []; } } Future deleteIntake(int intakeId) async { try { await _databaseHelper.deleteIntake(intakeId); await loadTodayIntakes(); // Also refresh monthly intakes if they're loaded if (_monthlyIntakes.isNotEmpty) { await loadMonthlyIntakes(DateTime.now().year, DateTime.now().month); } notifyListeners(); } catch (e) { if (kDebugMode) { print('Error deleting intake: $e'); } } } bool hasBeenTakenToday(int supplementId) { return _todayIntakes.any((intake) => intake['supplement_id'] == supplementId); } int getTodayIntakeCount(int supplementId) { return _todayIntakes.where((intake) => intake['supplement_id'] == supplementId).length; } // Method to manually refresh daily status (useful for testing or manual refresh) Future refreshDailyStatus() async { if (kDebugMode) { print('Manually refreshing daily status...'); } _lastDateCheck = DateTime.now(); await loadTodayIntakes(); } // Method to force check for date change (useful for testing) Future forceCheckDateChange() async { final now = DateTime.now(); final currentDate = DateTime(now.year, now.month, now.day); final lastCheckDate = DateTime(_lastDateCheck.year, _lastDateCheck.month, _lastDateCheck.day); if (kDebugMode) { print('Force checking date change...'); print('Current date: $currentDate'); print('Last check date: $lastCheckDate'); } if (currentDate != lastCheckDate) { if (kDebugMode) { print('Date change detected, refreshing intakes...'); } _lastDateCheck = now; await loadTodayIntakes(); } else { if (kDebugMode) { print('No date change detected'); } } } // Archive functionality List _archivedSupplements = []; List get archivedSupplements => _archivedSupplements; Future loadArchivedSupplements() async { try { _archivedSupplements = await _databaseHelper.getArchivedSupplements(); notifyListeners(); } catch (e) { if (kDebugMode) { print('Error loading archived supplements: $e'); } } } Future archiveSupplement(int supplementId) async { try { await _databaseHelper.archiveSupplement(supplementId); await loadSupplements(); // Refresh active supplements await loadArchivedSupplements(); // Refresh archived supplements } catch (e) { if (kDebugMode) { print('Error archiving supplement: $e'); } } } Future unarchiveSupplement(int supplementId) async { try { await _databaseHelper.unarchiveSupplement(supplementId); await loadSupplements(); // Refresh active supplements await loadArchivedSupplements(); // Refresh archived supplements } catch (e) { if (kDebugMode) { print('Error unarchiving supplement: $e'); } } } Future deleteArchivedSupplement(int supplementId) async { try { await _databaseHelper.deleteSupplement(supplementId); await loadArchivedSupplements(); // Refresh archived supplements } catch (e) { if (kDebugMode) { print('Error deleting archived supplement: $e'); } } } // Debug methods for notification testing Future testNotifications() async { await _notificationService.testNotification(); } Future testScheduledNotification() async { await _notificationService.testScheduledNotification(); } Future testNotificationActions() async { await _notificationService.testNotificationWithActions(); } Future> getPendingNotifications() async { return await _notificationService.getPendingNotifications(); } // Get pending notifications with retry information from database Future>> getTrackedNotifications() async { return await DatabaseHelper.instance.getPendingNotifications(); } // Debug method to test notification persistence Future rescheduleAllNotifications() async { await _rescheduleAllNotifications(); } // Debug method to cancel all notifications Future cancelAllNotifications() async { await _notificationService.cancelAllReminders(); } }