diff --git a/lib/services/notification_service.dart b/lib/services/notification_service.dart index 3105b00..e49c9a4 100644 --- a/lib/services/notification_service.dart +++ b/lib/services/notification_service.dart @@ -12,7 +12,7 @@ void notificationTapBackground(NotificationResponse notificationResponse) { print('SupplementsLog: 📱 Payload: ${notificationResponse.payload}'); print('SupplementsLog: 📱 Notification ID: ${notificationResponse.id}'); print('SupplementsLog: 📱 =========================================='); - + // For now, just log the action. The main app handler will process it. if (notificationResponse.actionId == 'take_supplement') { print('SupplementsLog: 📱 BACKGROUND: Take action detected'); @@ -30,7 +30,7 @@ class NotificationService { bool _isInitialized = false; static bool _engineInitialized = false; bool _permissionsRequested = false; - + // Callback for handling supplement intake from notifications Function(int supplementId, String supplementName, double units, String unitType)? _onTakeSupplementCallback; @@ -45,11 +45,11 @@ class NotificationService { print('SupplementsLog: 📱 Already initialized'); return; } - + try { print('SupplementsLog: 📱 Initializing timezones...'); print('SupplementsLog: 📱 Engine initialized flag: $_engineInitialized'); - + if (!_engineInitialized) { tz.initializeTimeZones(); _engineInitialized = true; @@ -61,15 +61,15 @@ class NotificationService { print('SupplementsLog: 📱 Warning: Timezone initialization issue (may already be initialized): $e'); _engineInitialized = true; // Mark as initialized to prevent retry } - + // Try to detect and set the local timezone more reliably try { // First try using the system timezone name final String timeZoneName = DateTime.now().timeZoneName; print('SupplementsLog: 📱 System timezone name: $timeZoneName'); - + tz.Location? location; - + // Try common timezone mappings for your region if (timeZoneName.contains('CET') || timeZoneName.contains('CEST')) { location = tz.getLocation('Europe/Amsterdam'); // Netherlands @@ -84,19 +84,19 @@ class NotificationService { location = tz.getLocation('Europe/Amsterdam'); } } - + tz.setLocalLocation(location); print('SupplementsLog: 📱 Timezone set to: ${location.name}'); - + } catch (e) { print('SupplementsLog: 📱 Error setting timezone: $e, using default'); // Fallback to a reasonable default for Netherlands tz.setLocalLocation(tz.getLocation('Europe/Amsterdam')); } - + print('SupplementsLog: 📱 Current local time: ${tz.TZDateTime.now(tz.local)}'); print('SupplementsLog: 📱 Current system time: ${DateTime.now()}'); - + const AndroidInitializationSettings androidSettings = AndroidInitializationSettings('@mipmap/ic_launcher'); const DarwinInitializationSettings iosSettings = DarwinInitializationSettings( requestAlertPermission: false, // We'll request these separately @@ -119,10 +119,10 @@ class NotificationService { onDidReceiveNotificationResponse: _onNotificationResponse, onDidReceiveBackgroundNotificationResponse: notificationTapBackground, ); - + // Test if notification response callback is working print('SupplementsLog: 📱 Callback function is set and ready'); - + _isInitialized = true; print('SupplementsLog: 📱 NotificationService initialization complete'); } @@ -135,7 +135,7 @@ class NotificationService { print('SupplementsLog: 📱 Notification ID: ${response.id}'); print('SupplementsLog: 📱 Input: ${response.input}'); print('SupplementsLog: 📱 ==============================='); - + if (response.actionId == 'take_supplement') { print('SupplementsLog: 📱 Processing TAKE action...'); _handleTakeAction(response.payload, response.id); @@ -151,43 +151,58 @@ class NotificationService { Future _handleTakeAction(String? payload, int? notificationId) async { print('SupplementsLog: 📱 === HANDLING TAKE ACTION ==='); print('SupplementsLog: 📱 Payload received: $payload'); - + if (payload != null) { try { // Parse the payload to get supplement info final parts = payload.split('|'); print('SupplementsLog: 📱 Payload parts: $parts (length: ${parts.length})'); - + if (parts.length >= 4) { final supplementId = int.parse(parts[0]); final supplementName = parts[1]; final units = double.parse(parts[2]); final unitType = parts[3]; - + print('SupplementsLog: 📱 Parsed data:'); print('SupplementsLog: 📱 - ID: $supplementId'); print('SupplementsLog: 📱 - Name: $supplementName'); print('SupplementsLog: 📱 - Units: $units'); print('SupplementsLog: 📱 - Type: $unitType'); - + // Call the callback to record the intake if (_onTakeSupplementCallback != null) { print('SupplementsLog: 📱 Calling supplement callback...'); - _onTakeSupplementCallback!(supplementId, supplementName, units, unitType); + _onTakeSupplementCallback!( + supplementId, supplementName, units, unitType); print('SupplementsLog: 📱 Callback completed'); } else { print('SupplementsLog: 📱 ERROR: No callback registered!'); } - - // Mark notification as taken in database (this will cancel any pending retries) - if (notificationId != null) { - print('SupplementsLog: 📱 Marking notification $notificationId as taken'); - await DatabaseHelper.instance.markNotificationTaken(notificationId); - - // Cancel any pending retry notifications for this notification - _cancelRetryNotifications(notificationId); + + // For retry notifications, the original notification ID is in the payload + int originalNotificationId; + if (parts.length > 4 && int.tryParse(parts[4]) != null) { + originalNotificationId = int.parse(parts[4]); + print( + 'SupplementsLog: 📱 Retry notification detected. Original ID: $originalNotificationId'); + } else if (notificationId != null) { + originalNotificationId = notificationId; + } else { + print( + 'SupplementsLog: 📱 ERROR: Could not determine notification ID to cancel.'); + return; } - + + // Mark notification as taken in database (this will cancel any pending retries) + print( + 'SupplementsLog: 📱 Marking notification $originalNotificationId as taken'); + await DatabaseHelper.instance + .markNotificationTaken(originalNotificationId); + + // Cancel any pending retry notifications for this notification + _cancelRetryNotifications(originalNotificationId); + // Show a confirmation notification print('SupplementsLog: 📱 Showing confirmation notification...'); showInstantNotification( @@ -195,7 +210,8 @@ class NotificationService { '$supplementName has been recorded at ${DateTime.now().hour.toString().padLeft(2, '0')}:${DateTime.now().minute.toString().padLeft(2, '0')}', ); } else { - print('SupplementsLog: 📱 ERROR: Invalid payload format - not enough parts'); + print( + 'SupplementsLog: 📱 ERROR: Invalid payload format - not enough parts'); } } catch (e) { print('SupplementsLog: 📱 ERROR in _handleTakeAction: $e'); @@ -218,26 +234,26 @@ class NotificationService { void _handleSnoozeAction(String? payload, int minutes, int? notificationId) { print('SupplementsLog: 📱 === HANDLING SNOOZE ACTION ==='); print('SupplementsLog: 📱 Payload: $payload, Minutes: $minutes'); - + if (payload != null) { try { final parts = payload.split('|'); if (parts.length >= 2) { final supplementId = int.parse(parts[0]); final supplementName = parts[1]; - + print('SupplementsLog: 📱 Snoozing supplement for $minutes minutes: $supplementName'); - + // Mark notification as snoozed in database (increment retry count) if (notificationId != null) { print('SupplementsLog: 📱 Incrementing retry count for notification $notificationId'); DatabaseHelper.instance.incrementRetryCount(notificationId); } - + // Schedule a new notification for the snooze time final snoozeTime = tz.TZDateTime.now(tz.local).add(Duration(minutes: minutes)); print('SupplementsLog: 📱 Snooze time: $snoozeTime'); - + _notifications.zonedSchedule( supplementId * 1000 + minutes, // Unique ID for snooze notifications 'Reminder: $supplementName', @@ -266,7 +282,7 @@ class NotificationService { androidScheduleMode: AndroidScheduleMode.exactAllowWhileIdle, payload: payload, ); - + showInstantNotification( 'Reminder Snoozed', '$supplementName reminder snoozed for $minutes minutes', @@ -300,32 +316,32 @@ class NotificationService { required int maxRetryAttempts, }) async { print('SupplementsLog: 📱 Checking for pending notifications to retry...'); - + try { if (!persistentReminders) { print('SupplementsLog: 📱 Persistent reminders disabled'); return; } - + print('SupplementsLog: 📱 Retry settings: interval=$reminderRetryInterval min, max=$maxRetryAttempts attempts'); - + // Get all pending notifications from database final pendingNotifications = await DatabaseHelper.instance.getPendingNotifications(); print('SupplementsLog: 📱 Found ${pendingNotifications.length} pending notifications'); - + final now = DateTime.now(); - + for (final notification in pendingNotifications) { final scheduledTime = DateTime.parse(notification['scheduledTime']).toLocal(); final retryCount = notification['retryCount'] as int; - final lastRetryTime = notification['lastRetryTime'] != null + final lastRetryTime = notification['lastRetryTime'] != null ? DateTime.parse(notification['lastRetryTime']).toLocal() : null; - + // Check if notification is overdue final timeSinceScheduled = now.difference(scheduledTime).inMinutes; final shouldRetry = timeSinceScheduled >= reminderRetryInterval; - + print('SupplementsLog: 📱 Checking notification ${notification['notificationId']}:'); print('SupplementsLog: 📱 Scheduled: $scheduledTime (local)'); print('SupplementsLog: 📱 Now: $now'); @@ -333,13 +349,13 @@ class NotificationService { print('SupplementsLog: 📱 Retry interval: $reminderRetryInterval minutes'); print('SupplementsLog: 📱 Should retry: $shouldRetry'); print('SupplementsLog: 📱 Retry count: $retryCount / $maxRetryAttempts'); - + // Check if we haven't exceeded max retry attempts if (retryCount >= maxRetryAttempts) { print('SupplementsLog: 📱 Notification ${notification['notificationId']} exceeded max attempts ($maxRetryAttempts)'); continue; } - + // Check if enough time has passed since last retry if (lastRetryTime != null) { final timeSinceLastRetry = now.difference(lastRetryTime).inMinutes; @@ -348,7 +364,7 @@ class NotificationService { continue; } } - + if (shouldRetry) { print('SupplementsLog: 📱 ⚡ SCHEDULING RETRY for notification ${notification['notificationId']}'); await _scheduleRetryNotification(notification, retryCount + 1); @@ -365,16 +381,16 @@ class NotificationService { try { final notificationId = notification['notificationId'] as int; final supplementId = notification['supplementId'] as int; - + // Generate a unique ID for this retry (200000 + original_id * 10 + retry_attempt) final retryNotificationId = 200000 + (notificationId * 10) + retryAttempt; - + print('SupplementsLog: 📱 Scheduling retry notification $retryNotificationId for supplement $supplementId (attempt $retryAttempt)'); - + // Get supplement details from database final supplements = await DatabaseHelper.instance.getAllSupplements(); final supplement = supplements.firstWhere((s) => s.id == supplementId && s.isActive, orElse: () => throw Exception('Supplement not found')); - + // Schedule the retry notification immediately await _notifications.show( retryNotificationId, @@ -404,10 +420,10 @@ class NotificationService { ), payload: '${supplement.id}|${supplement.name}|${supplement.numberOfUnits}|${supplement.unitType}|$notificationId', ); - + // Update the retry count in database await DatabaseHelper.instance.incrementRetryCount(notificationId); - + print('SupplementsLog: 📱 Retry notification scheduled successfully'); } catch (e) { print('SupplementsLog: 📱 Error scheduling retry notification: $e'); @@ -420,10 +436,10 @@ class NotificationService { print('SupplementsLog: 📱 Permissions already requested'); return true; } - + try { _permissionsRequested = true; - + final androidPlugin = _notifications.resolvePlatformSpecificImplementation(); if (androidPlugin != null) { print('SupplementsLog: 📱 Requesting Android permissions...'); @@ -434,7 +450,7 @@ class NotificationService { return false; } } - + final iosPlugin = _notifications.resolvePlatformSpecificImplementation(); if (iosPlugin != null) { print('SupplementsLog: 📱 Requesting iOS permissions...'); @@ -449,7 +465,7 @@ class NotificationService { return false; } } - + print('SupplementsLog: 📱 All permissions granted successfully'); return true; } catch (e) { @@ -462,7 +478,7 @@ class NotificationService { Future scheduleSupplementReminders(Supplement supplement) async { print('SupplementsLog: 📱 Scheduling reminders for ${supplement.name}'); print('SupplementsLog: 📱 Reminder times: ${supplement.reminderTimes}'); - + // Cancel existing notifications for this supplement await cancelSupplementReminders(supplement.id!); @@ -474,7 +490,7 @@ class NotificationService { final notificationId = supplement.id! * 100 + i; // Unique ID for each reminder final scheduledTime = _nextInstanceOfTime(hour, minute); - + print('SupplementsLog: 📱 Scheduling notification ID $notificationId for ${timeStr} -> ${scheduledTime}'); // Track this notification in the database @@ -505,7 +521,7 @@ class NotificationService { ), AndroidNotificationAction( 'snooze_10', - 'Snooze 10min', + 'Snooze 10min', icon: DrawableResourceAndroidBitmap('@android:drawable/ic_menu_recent_history'), showsUserInterface: true, // Changed to true to open app ), @@ -517,10 +533,10 @@ class NotificationService { matchDateTimeComponents: DateTimeComponents.time, payload: '${supplement.id}|${supplement.name}|${supplement.numberOfUnits}|${supplement.unitType}', ); - + print('SupplementsLog: 📱 Successfully scheduled notification ID $notificationId'); } - + // Get all pending notifications to verify final pendingNotifications = await _notifications.pendingNotificationRequests(); print('SupplementsLog: 📱 Total pending notifications: ${pendingNotifications.length}'); @@ -535,7 +551,7 @@ class NotificationService { final notificationId = supplementId * 100 + i; await _notifications.cancel(notificationId); } - + // Also clean up database tracking records for this supplement await DatabaseHelper.instance.clearNotificationTracking(supplementId); } @@ -547,18 +563,18 @@ class NotificationService { tz.TZDateTime _nextInstanceOfTime(int hour, int minute) { final tz.TZDateTime now = tz.TZDateTime.now(tz.local); tz.TZDateTime scheduledDate = tz.TZDateTime(tz.local, now.year, now.month, now.day, hour, minute); - + print('SupplementsLog: 📱 Current time: $now (${now.timeZoneName})'); print('SupplementsLog: 📱 Target time: ${hour.toString().padLeft(2, '0')}:${minute.toString().padLeft(2, '0')}'); print('SupplementsLog: 📱 Initial scheduled date: $scheduledDate (${scheduledDate.timeZoneName})'); - + if (scheduledDate.isBefore(now)) { scheduledDate = scheduledDate.add(const Duration(days: 1)); print('SupplementsLog: 📱 Time has passed, scheduling for tomorrow: $scheduledDate (${scheduledDate.timeZoneName})'); } else { print('SupplementsLog: 📱 Time is in the future, scheduling for today: $scheduledDate (${scheduledDate.timeZoneName})'); } - + return scheduledDate; } @@ -595,9 +611,9 @@ class NotificationService { print('SupplementsLog: 📱 Testing scheduled notification...'); final now = tz.TZDateTime.now(tz.local); final testTime = now.add(const Duration(minutes: 1)); - + print('SupplementsLog: 📱 Scheduling test notification for: $testTime'); - + await _notifications.zonedSchedule( 99999, // Special ID for test notifications 'Test Scheduled Notification', @@ -615,7 +631,7 @@ class NotificationService { ), androidScheduleMode: AndroidScheduleMode.exactAllowWhileIdle, ); - + print('SupplementsLog: 📱 Test notification scheduled successfully'); } @@ -627,7 +643,7 @@ class NotificationService { // Debug function to test notification actions Future testNotificationWithActions() async { print('SupplementsLog: 📱 Creating test notification with actions...'); - + await _notifications.show( 88888, // Special test ID 'Test Action Notification', @@ -658,14 +674,14 @@ class NotificationService { ), payload: '999|Test Supplement|1.0|capsule', ); - + print('SupplementsLog: 📱 Test notification with actions created'); } // Debug function to test basic notification tap response Future testBasicNotification() async { print('SupplementsLog: 📱 Creating basic test notification...'); - + await _notifications.show( 77777, // Special test ID for basic notification 'Basic Test Notification', @@ -682,7 +698,7 @@ class NotificationService { ), payload: 'basic_test', ); - + print('SupplementsLog: 📱 Basic test notification created'); } }