import 'dart:async'; import 'package:flutter/foundation.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:provider/provider.dart'; import 'package:supplements/logging.dart'; import '../models/supplement.dart'; import '../models/supplement_intake.dart'; import '../providers/settings_provider.dart'; import '../services/database_helper.dart'; import '../services/database_sync_service.dart'; import '../services/simple_notification_service.dart'; class SupplementProvider with ChangeNotifier, WidgetsBindingObserver { final DatabaseHelper _databaseHelper = DatabaseHelper.instance; final SimpleNotificationService _notificationService = SimpleNotificationService.instance; bool _initialized = false; List _supplements = []; List> _todayIntakes = []; List> _monthlyIntakes = []; bool _isLoading = false; Timer? _dateChangeTimer; DateTime _lastDateCheck = DateTime.now(); // Callback for triggering sync when data changes VoidCallback? _onDataChanged; // Context for accessing other providers BuildContext? _context; List get supplements => _supplements; List> get todayIntakes => _todayIntakes; List> get monthlyIntakes => _monthlyIntakes; bool get isLoading => _isLoading; /// Set callback for triggering sync when data changes void setOnDataChangedCallback(VoidCallback? callback) { _onDataChanged = callback; } /// Trigger sync if callback is set void _triggerSyncIfEnabled() { _onDataChanged?.call(); } Future initialize([BuildContext? context]) async { if (_initialized) { return; } _initialized = true; _context = context; // Add this provider as an observer for app lifecycle changes WidgetsBinding.instance.addObserver(this); await _notificationService.initialize(); // Request permissions with error handling try { await _notificationService.requestPermissions(); } catch (e) { if (kDebugMode) { printLog('Error requesting notification permissions: $e'); } // Continue without notifications rather than crashing } await loadSupplements(); await loadTodayIntakes(); // Schedule notifications for all active supplements await _rescheduleAllNotifications(); // Start date change monitoring to reset daily intake status _startDateChangeMonitoring(); } 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) { printLog('Date changed detected: $lastCheckDate -> $currentDate'); printLog('Refreshing today\'s intakes for new day...'); } // Date has changed, refresh today's intakes _lastDateCheck = now; await loadTodayIntakes(); if (kDebugMode) { printLog('Today\'s intakes refreshed for new day'); } } }); } @override void dispose() { WidgetsBinding.instance.removeObserver(this); _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) { printLog('App resumed, checking for date change...'); } forceCheckDateChange(); } } Future _rescheduleAllNotifications() async { if (kDebugMode) { printLog('📱 Rescheduling notifications for all active supplements...'); } try { SettingsProvider? settingsProvider; if (_context != null && _context!.mounted) { try { settingsProvider = Provider.of(_context!, listen: false); } catch (e) { if (kDebugMode) { printLog('📱 Could not access SettingsProvider: $e'); } } } await _notificationService.scheduleDailyGroupedRemindersSafe( _supplements, settingsProvider: settingsProvider, ); await _notificationService.getPendingNotifications(); } catch (e) { if (kDebugMode) { printLog('📱 Error scheduling grouped notifications: $e'); } } if (kDebugMode) { printLog('📱 Finished rescheduling notifications'); } } Future loadSupplements() async { _isLoading = true; notifyListeners(); try { printLog('Loading supplements from database...'); _supplements = await _databaseHelper.getAllSupplements(); printLog('Loaded ${_supplements.length} supplements'); for (var supplement in _supplements) { printLog('Supplement: ${supplement.name}'); } } catch (e) { printLog('Error loading supplements: $e'); if (kDebugMode) { printLog('Error loading supplements: $e'); } } finally { _isLoading = false; notifyListeners(); } } Future addSupplement(Supplement supplement) async { try { printLog('Adding supplement: ${supplement.name}'); final id = await _databaseHelper.insertSupplement(supplement); printLog('Supplement inserted with ID: $id'); await loadSupplements(); printLog('Supplements reloaded, count: ${_supplements.length}'); await _rescheduleAllNotifications(); // Trigger sync after adding supplement _triggerSyncIfEnabled(); } catch (e) { printLog('Error adding supplement: $e'); if (kDebugMode) { printLog('Error adding supplement: $e'); } rethrow; } } Future updateSupplement(Supplement supplement) async { try { await _databaseHelper.updateSupplement(supplement); await loadSupplements(); await _rescheduleAllNotifications(); // Trigger sync after updating supplement _triggerSyncIfEnabled(); } catch (e) { if (kDebugMode) { printLog('Error updating supplement: $e'); } } } Future duplicateSupplement(int supplementId) async { try { final originalSupplement = await _databaseHelper.getSupplement(supplementId); if (originalSupplement != null) { final newSupplement = originalSupplement.copyWith( setNullId: true, // This will be a new entry newSyncId: true, // Generate a new syncId name: '${originalSupplement.name} (Copy)', createdAt: DateTime.now(), lastModified: DateTime.now(), syncStatus: RecordSyncStatus.pending, isDeleted: false, ); await addSupplement(newSupplement); } } catch (e) { if (kDebugMode) { printLog('Error duplicating supplement: $e'); } } } Future deleteSupplement(int id) async { try { await _databaseHelper.deleteSupplement(id); await loadSupplements(); await _rescheduleAllNotifications(); // Trigger sync after deleting supplement _triggerSyncIfEnabled(); } catch (e) { if (kDebugMode) { printLog('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(); // Trigger sync after recording intake _triggerSyncIfEnabled(); // 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.showInstant( title: 'Supplement Taken', body: 'Recorded ${supplement.name}${unitsText.isNotEmpty ? ' - $unitsText' : ''} (${supplement.ingredientsDisplay})', ); } catch (e) { if (kDebugMode) { printLog('Error recording intake: $e'); } } } Future loadTodayIntakes() async { try { final today = DateTime.now(); if (kDebugMode) { printLog('Loading intakes for date: ${today.year}-${today.month}-${today.day}'); } _todayIntakes = await _databaseHelper.getIntakesWithSupplementsForDate(today); if (kDebugMode) { printLog('Loaded ${_todayIntakes.length} intakes for today'); for (var intake in _todayIntakes) { printLog(' - Supplement ID: ${intake['supplement_id']}, taken at: ${intake['takenAt']}'); } } notifyListeners(); } catch (e) { if (kDebugMode) { printLog('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) { printLog('Error loading monthly intakes: $e'); } } } Future>> getIntakesForDate(DateTime date) async { try { return await _databaseHelper.getIntakesWithSupplementsForDate(date); } catch (e) { if (kDebugMode) { printLog('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(); // Trigger sync after deleting intake _triggerSyncIfEnabled(); } catch (e) { if (kDebugMode) { printLog('Error deleting intake: $e'); } } } Future permanentlyDeleteIntake(int intakeId) async { try { await _databaseHelper.permanentlyDeleteIntake(intakeId); await loadTodayIntakes(); // Also refresh monthly intakes if they're loaded if (_monthlyIntakes.isNotEmpty) { await loadMonthlyIntakes(DateTime.now().year, DateTime.now().month); } notifyListeners(); // Trigger sync after permanently deleting intake _triggerSyncIfEnabled(); } catch (e) { if (kDebugMode) { printLog('Error permanently 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; } /// Get the most recent intake for a supplement today Map? getMostRecentIntake(int supplementId) { final supplementIntakes = _todayIntakes .where((intake) => intake['supplement_id'] == supplementId) .toList(); if (supplementIntakes.isEmpty) return null; // Sort by takenAt time (most recent first) supplementIntakes.sort((a, b) { final aTime = DateTime.parse(a['takenAt']); final bTime = DateTime.parse(b['takenAt']); return bTime.compareTo(aTime); // Descending order }); return supplementIntakes.first; } Map get dailyIngredientIntake { final Map ingredientIntake = {}; for (final intake in _todayIntakes) { final supplement = _supplements.firstWhere((s) => s.id == intake['supplement_id']); final unitsTaken = intake['unitsTaken'] as double; for (final ingredient in supplement.ingredients) { final currentAmount = ingredientIntake[ingredient.name] ?? 0; ingredientIntake[ingredient.name] = currentAmount + (ingredient.amount * unitsTaken); } } return ingredientIntake; } // Method to manually refresh daily status (useful for testing or manual refresh) Future refreshDailyStatus() async { if (kDebugMode) { printLog('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) { printLog('Force checking date change...'); printLog('Current date: $currentDate'); printLog('Last check date: $lastCheckDate'); } if (currentDate != lastCheckDate) { if (kDebugMode) { printLog('Date change detected, refreshing intakes...'); } _lastDateCheck = now; await loadTodayIntakes(); } else { if (kDebugMode) { printLog('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) { printLog('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 // Trigger sync after archiving supplement _triggerSyncIfEnabled(); } catch (e) { if (kDebugMode) { printLog('Error archiving supplement: $e'); } } } Future unarchiveSupplement(int supplementId) async { try { await _databaseHelper.unarchiveSupplement(supplementId); await loadSupplements(); // Refresh active supplements await loadArchivedSupplements(); // Refresh archived supplements // Trigger sync after unarchiving supplement _triggerSyncIfEnabled(); } catch (e) { if (kDebugMode) { printLog('Error unarchiving supplement: $e'); } } } Future deleteArchivedSupplement(int supplementId) async { try { await _databaseHelper.permanentlyDeleteSupplement(supplementId); await loadArchivedSupplements(); // Refresh archived supplements // Trigger sync after permanently deleting archived supplement _triggerSyncIfEnabled(); } catch (e) { if (kDebugMode) { printLog('Error permanently deleting archived supplement: $e'); } } } // Debug methods for notification testing Future testNotifications() async { await _notificationService.showInstant( title: 'Test Notification', body: 'This is a test notification to verify the system is working.', ); } Future testScheduledNotification() async { await _notificationService.showInstant( title: 'Test Scheduled Notification', body: 'This is a simple test notification.', ); } Future testNotificationActions() async { await _notificationService.showInstant( title: 'Test Action Notification', body: 'Actions are not available in the simple notification service.', ); } Future> getPendingNotifications() async { // Not supported in simple service; return empty list for compatibility. return []; } // Debug method to test notification persistence Future rescheduleAllNotifications() async { await _rescheduleAllNotifications(); } // Debug method to cancel all notifications Future cancelAllNotifications() async { await _notificationService.cancelAll(); } }