feat: Add settings provider for theme and time range management

- Implemented SettingsProvider to manage user preferences for theme options and time ranges for reminders.
- Added persistent reminder settings with configurable retry intervals and maximum attempts.
- Created UI for settings screen to allow users to customize their preferences.
- Integrated shared_preferences for persistent storage of user settings.

feat: Introduce Ingredient model

- Created Ingredient model to represent nutritional components with properties for id, name, amount, and unit.
- Added methods for serialization and deserialization of Ingredient objects.

feat: Develop Archived Supplements Screen

- Implemented ArchivedSupplementsScreen to display archived supplements with options to unarchive or delete.
- Added UI components for listing archived supplements and handling user interactions.

chore: Update dependencies in pubspec.yaml and pubspec.lock

- Updated shared_preferences dependency to the latest version.
- Removed flutter_datetime_picker_plus dependency and added file dependency.
- Updated Flutter SDK constraint to >=3.27.0.
This commit is contained in:
2025-08-26 17:19:54 +02:00
parent e6181add08
commit 2aec59ec35
18 changed files with 3756 additions and 376 deletions

View File

@@ -1,4 +1,6 @@
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import '../models/supplement.dart';
import '../models/supplement_intake.dart';
import '../services/database_helper.dart';
@@ -12,6 +14,7 @@ class SupplementProvider with ChangeNotifier {
List<Map<String, dynamic>> _todayIntakes = [];
List<Map<String, dynamic>> _monthlyIntakes = [];
bool _isLoading = false;
Timer? _persistentReminderTimer;
List<Supplement> get supplements => _supplements;
List<Map<String, dynamic>> get todayIntakes => _todayIntakes;
@@ -20,9 +23,115 @@ class SupplementProvider with ChangeNotifier {
Future<void> initialize() async {
await _notificationService.initialize();
await _notificationService.requestPermissions();
// 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();
}
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();
}
Future<void> _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<void> checkPersistentRemindersWithSettings({
required bool persistentReminders,
required int reminderRetryInterval,
required int maxRetryAttempts,
}) async {
await _notificationService.checkPersistentReminders(
persistentReminders,
reminderRetryInterval,
maxRetryAttempts,
);
}
@override
void dispose() {
_persistentReminderTimer?.cancel();
super.dispose();
}
Future<void> _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<void> loadSupplements() async {
@@ -103,11 +212,11 @@ class SupplementProvider with ChangeNotifier {
}
}
Future<void> recordIntake(int supplementId, double dosage, {double? unitsTaken, String? notes}) async {
Future<void> recordIntake(int supplementId, double dosage, {double? unitsTaken, String? notes, DateTime? takenAt}) async {
try {
final intake = SupplementIntake(
supplementId: supplementId,
takenAt: DateTime.now(),
takenAt: takenAt ?? DateTime.now(),
dosageTaken: dosage,
unitsTaken: unitsTaken ?? 1.0,
notes: notes,
@@ -121,7 +230,7 @@ class SupplementProvider with ChangeNotifier {
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' : ''} ($dosage ${supplement.unit})',
'Recorded ${supplement.name}${unitsText.isNotEmpty ? ' - $unitsText' : ''} (${supplement.ingredientsDisplay})',
);
} catch (e) {
if (kDebugMode) {
@@ -167,10 +276,100 @@ class SupplementProvider with ChangeNotifier {
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;
}
// Archive functionality
List<Supplement> _archivedSupplements = [];
List<Supplement> get archivedSupplements => _archivedSupplements;
Future<void> loadArchivedSupplements() async {
try {
_archivedSupplements = await _databaseHelper.getArchivedSupplements();
notifyListeners();
} catch (e) {
if (kDebugMode) {
print('Error loading archived supplements: $e');
}
}
}
Future<void> 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<void> 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<void> 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<void> testNotifications() async {
await _notificationService.testNotification();
}
Future<void> testScheduledNotification() async {
await _notificationService.testScheduledNotification();
}
Future<void> testNotificationActions() async {
await _notificationService.testNotificationWithActions();
}
Future<List<PendingNotificationRequest>> getPendingNotifications() async {
return await _notificationService.getPendingNotifications();
}
// Debug method to test notification persistence
Future<void> rescheduleAllNotifications() async {
await _rescheduleAllNotifications();
}
// Debug method to cancel all notifications
Future<void> cancelAllNotifications() async {
await _notificationService.cancelAllReminders();
}
}