mirror of
https://github.com/vleeuwenmenno/supplements.git
synced 2025-09-11 18:29:12 +02:00
adds retry functionality
This commit is contained in:
@@ -1,9 +1,9 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:flutter_local_notifications/flutter_local_notifications.dart'; // Import this
|
||||
import 'package:supplements/logging.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:shadcn_ui/shadcn_ui.dart';
|
||||
import 'package:supplements/logging.dart';
|
||||
|
||||
import 'providers/settings_provider.dart';
|
||||
import 'providers/simple_sync_provider.dart';
|
||||
@@ -46,7 +46,7 @@ class MyApp extends StatelessWidget {
|
||||
return MultiProvider(
|
||||
providers: [
|
||||
ChangeNotifierProvider(
|
||||
create: (context) => SupplementProvider()..initialize(),
|
||||
create: (context) => SupplementProvider()..initialize(context),
|
||||
),
|
||||
ChangeNotifierProvider.value(
|
||||
value: settingsProvider,
|
||||
|
@@ -23,6 +23,11 @@ class SettingsProvider extends ChangeNotifier {
|
||||
// Notifications
|
||||
int _snoozeMinutes = 10;
|
||||
|
||||
// Notification retry settings
|
||||
bool _notificationRetryEnabled = true;
|
||||
int _notificationRetryCount = 3;
|
||||
int _notificationRetryDelayMinutes = 5;
|
||||
|
||||
// Auto-sync settings
|
||||
bool _autoSyncEnabled = false;
|
||||
int _autoSyncDebounceSeconds = 5;
|
||||
@@ -42,6 +47,11 @@ class SettingsProvider extends ChangeNotifier {
|
||||
// Notifications
|
||||
int get snoozeMinutes => _snoozeMinutes;
|
||||
|
||||
// Notification retry getters
|
||||
bool get notificationRetryEnabled => _notificationRetryEnabled;
|
||||
int get notificationRetryCount => _notificationRetryCount;
|
||||
int get notificationRetryDelayMinutes => _notificationRetryDelayMinutes;
|
||||
|
||||
// Auto-sync getters
|
||||
bool get autoSyncEnabled => _autoSyncEnabled;
|
||||
int get autoSyncDebounceSeconds => _autoSyncDebounceSeconds;
|
||||
@@ -85,6 +95,11 @@ class SettingsProvider extends ChangeNotifier {
|
||||
// Load snooze setting
|
||||
_snoozeMinutes = prefs.getInt('snooze_minutes') ?? 10;
|
||||
|
||||
// Load notification retry settings
|
||||
_notificationRetryEnabled = prefs.getBool('notification_retry_enabled') ?? true;
|
||||
_notificationRetryCount = prefs.getInt('notification_retry_count') ?? 3;
|
||||
_notificationRetryDelayMinutes = prefs.getInt('notification_retry_delay_minutes') ?? 5;
|
||||
|
||||
// Load auto-sync settings
|
||||
_autoSyncEnabled = prefs.getBool('auto_sync_enabled') ?? false;
|
||||
_autoSyncDebounceSeconds = prefs.getInt('auto_sync_debounce_seconds') ?? 30;
|
||||
@@ -259,6 +274,37 @@ class SettingsProvider extends ChangeNotifier {
|
||||
await prefs.setInt('snooze_minutes', minutes);
|
||||
}
|
||||
|
||||
Future<void> setNotificationRetryEnabled(bool enabled) async {
|
||||
_notificationRetryEnabled = enabled;
|
||||
notifyListeners();
|
||||
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setBool('notification_retry_enabled', enabled);
|
||||
}
|
||||
|
||||
Future<void> setNotificationRetryCount(int count) async {
|
||||
if (count < 0 || count > 10) {
|
||||
throw ArgumentError('Retry count must be between 0 and 10');
|
||||
}
|
||||
_notificationRetryCount = count;
|
||||
notifyListeners();
|
||||
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setInt('notification_retry_count', count);
|
||||
}
|
||||
|
||||
Future<void> setNotificationRetryDelayMinutes(int minutes) async {
|
||||
const allowed = [1, 2, 3, 5, 10, 15, 20, 30];
|
||||
if (!allowed.contains(minutes)) {
|
||||
throw ArgumentError('Retry delay must be one of ${allowed.join(", ")} minutes');
|
||||
}
|
||||
_notificationRetryDelayMinutes = minutes;
|
||||
notifyListeners();
|
||||
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setInt('notification_retry_delay_minutes', minutes);
|
||||
}
|
||||
|
||||
// Auto-sync setters
|
||||
Future<void> setAutoSyncEnabled(bool enabled) async {
|
||||
_autoSyncEnabled = enabled;
|
||||
|
@@ -3,10 +3,12 @@ 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';
|
||||
@@ -27,6 +29,9 @@ class SupplementProvider with ChangeNotifier, WidgetsBindingObserver {
|
||||
// Callback for triggering sync when data changes
|
||||
VoidCallback? _onDataChanged;
|
||||
|
||||
// Context for accessing other providers
|
||||
BuildContext? _context;
|
||||
|
||||
List<Supplement> get supplements => _supplements;
|
||||
List<Map<String, dynamic>> get todayIntakes => _todayIntakes;
|
||||
List<Map<String, dynamic>> get monthlyIntakes => _monthlyIntakes;
|
||||
@@ -42,11 +47,12 @@ class SupplementProvider with ChangeNotifier, WidgetsBindingObserver {
|
||||
_onDataChanged?.call();
|
||||
}
|
||||
|
||||
Future<void> initialize() async {
|
||||
Future<void> 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);
|
||||
@@ -135,7 +141,21 @@ class SupplementProvider with ChangeNotifier, WidgetsBindingObserver {
|
||||
}
|
||||
|
||||
try {
|
||||
await _notificationService.scheduleDailyGroupedRemindersSafe(_supplements);
|
||||
SettingsProvider? settingsProvider;
|
||||
if (_context != null && _context!.mounted) {
|
||||
try {
|
||||
settingsProvider = Provider.of<SettingsProvider>(_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) {
|
||||
|
@@ -106,7 +106,18 @@ class SettingsScreen extends StatelessWidget {
|
||||
const SizedBox(height: 16),
|
||||
// Notifications
|
||||
Card(
|
||||
child: ListTile(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Notifications',
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
ListTile(
|
||||
contentPadding: EdgeInsets.zero,
|
||||
leading: const Icon(Icons.snooze),
|
||||
title: const Text('Snooze duration'),
|
||||
subtitle: const Text('Delay for Snooze action'),
|
||||
@@ -125,6 +136,68 @@ class SettingsScreen extends StatelessWidget {
|
||||
},
|
||||
),
|
||||
),
|
||||
const Divider(),
|
||||
SwitchListTile(
|
||||
contentPadding: EdgeInsets.zero,
|
||||
secondary: const Icon(Icons.repeat),
|
||||
title: const Text('Notification Retries'),
|
||||
subtitle: const Text('Automatically retry missed notifications'),
|
||||
value: settingsProvider.notificationRetryEnabled,
|
||||
onChanged: (value) {
|
||||
settingsProvider.setNotificationRetryEnabled(value);
|
||||
},
|
||||
),
|
||||
if (settingsProvider.notificationRetryEnabled) ...[
|
||||
const SizedBox(height: 8),
|
||||
ListTile(
|
||||
contentPadding: EdgeInsets.zero,
|
||||
leading: const Icon(Icons.format_list_numbered),
|
||||
title: const Text('Retry count'),
|
||||
subtitle: const Text('Number of retry attempts'),
|
||||
trailing: DropdownButton<int>(
|
||||
value: settingsProvider.notificationRetryCount,
|
||||
items: const [
|
||||
DropdownMenuItem(value: 1, child: Text('1')),
|
||||
DropdownMenuItem(value: 2, child: Text('2')),
|
||||
DropdownMenuItem(value: 3, child: Text('3')),
|
||||
DropdownMenuItem(value: 4, child: Text('4')),
|
||||
DropdownMenuItem(value: 5, child: Text('5')),
|
||||
],
|
||||
onChanged: (value) {
|
||||
if (value != null) {
|
||||
settingsProvider.setNotificationRetryCount(value);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
ListTile(
|
||||
contentPadding: EdgeInsets.zero,
|
||||
leading: const Icon(Icons.schedule),
|
||||
title: const Text('Retry delay'),
|
||||
subtitle: const Text('Time between retry attempts'),
|
||||
trailing: DropdownButton<int>(
|
||||
value: settingsProvider.notificationRetryDelayMinutes,
|
||||
items: const [
|
||||
DropdownMenuItem(value: 1, child: Text('1 min')),
|
||||
DropdownMenuItem(value: 2, child: Text('2 min')),
|
||||
DropdownMenuItem(value: 3, child: Text('3 min')),
|
||||
DropdownMenuItem(value: 5, child: Text('5 min')),
|
||||
DropdownMenuItem(value: 10, child: Text('10 min')),
|
||||
DropdownMenuItem(value: 15, child: Text('15 min')),
|
||||
DropdownMenuItem(value: 20, child: Text('20 min')),
|
||||
DropdownMenuItem(value: 30, child: Text('30 min')),
|
||||
],
|
||||
onChanged: (value) {
|
||||
if (value != null) {
|
||||
settingsProvider.setNotificationRetryDelayMinutes(value);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Card(
|
||||
|
@@ -4,14 +4,14 @@ import 'dart:convert';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:supplements/logging.dart';
|
||||
import 'package:supplements/services/simple_notification_service.dart';
|
||||
|
||||
import '../models/supplement.dart';
|
||||
import '../providers/supplement_provider.dart';
|
||||
import '../widgets/dialogs/bulk_take_dialog.dart';
|
||||
import '../widgets/dialogs/take_supplement_dialog.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:supplements/services/simple_notification_service.dart';
|
||||
|
||||
/// Centralizes routing from notification actions/taps to in-app UI.
|
||||
/// Handles both foreground/background taps and terminated-launch scenarios.
|
||||
@@ -32,6 +32,9 @@ class NotificationRouter {
|
||||
printLog('🔔 handleNotificationResponse: Received actionId: $actionId');
|
||||
printLog('🔔 handleNotificationResponse: Decoded payloadMap: $payloadMap');
|
||||
|
||||
// Cancel retry notifications for any interaction (take, snooze, or tap)
|
||||
await _cancelRetryNotificationsForResponse(payloadMap);
|
||||
|
||||
// Handle Snooze actions without surfacing UI
|
||||
if (actionId == 'snooze_single' || actionId == 'snooze_group') {
|
||||
try {
|
||||
@@ -162,14 +165,15 @@ class NotificationRouter {
|
||||
}
|
||||
|
||||
// Try to wait for providers to be ready to build rich content.
|
||||
BuildContext? ctx = _navigatorKey?.currentContext;
|
||||
final ready = await _waitUntilReady(timeout: const Duration(seconds: 5));
|
||||
if (ctx != null && !ctx.mounted) ctx = null;
|
||||
|
||||
SupplementProvider? provider;
|
||||
if (ready && ctx != null) {
|
||||
if (ready) {
|
||||
final ctx = _navigatorKey?.currentContext;
|
||||
if (ctx != null && ctx.mounted) {
|
||||
provider = Provider.of<SupplementProvider>(ctx, listen: false);
|
||||
}
|
||||
}
|
||||
|
||||
String title = 'Supplement reminder';
|
||||
String body = 'Tap to see details';
|
||||
@@ -295,4 +299,44 @@ class NotificationRouter {
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/// Cancel retry notifications when user responds to any notification.
|
||||
/// This prevents redundant notifications after user interaction.
|
||||
Future<void> _cancelRetryNotificationsForResponse(Map<String, dynamic>? payloadMap) async {
|
||||
if (payloadMap == null) return;
|
||||
|
||||
try {
|
||||
final type = payloadMap['type'];
|
||||
|
||||
if (type == 'single') {
|
||||
// For single notifications, we need to find the time slot to cancel retries
|
||||
// We can extract this from the meta if it's a retry, or find it from the supplement
|
||||
final meta = payloadMap['meta'] as Map<String, dynamic>?;
|
||||
if (meta != null && meta['originalTime'] != null) {
|
||||
// This is a retry notification, cancel remaining retries for the original time
|
||||
final originalTime = meta['originalTime'] as String;
|
||||
await SimpleNotificationService.instance.cancelRetryNotificationsForTimeSlot(originalTime);
|
||||
printLog('🚫 Cancelled retries for original time slot: $originalTime');
|
||||
} else {
|
||||
// This is an original notification, find the time slot from the supplement
|
||||
final supplementId = payloadMap['id'];
|
||||
if (supplementId is int) {
|
||||
// We need to find which time slots this supplement has and cancel retries for all of them
|
||||
// For now, we'll use the broader cancellation method
|
||||
await SimpleNotificationService.instance.cancelRetryNotificationsForSupplement(supplementId);
|
||||
printLog('🚫 Cancelled retries for supplement ID: $supplementId');
|
||||
}
|
||||
}
|
||||
} else if (type == 'group') {
|
||||
// For group notifications, we have the time key directly
|
||||
final timeKey = payloadMap['time'] as String?;
|
||||
if (timeKey != null) {
|
||||
await SimpleNotificationService.instance.cancelRetryNotificationsForTimeSlot(timeKey);
|
||||
printLog('🚫 Cancelled retries for time slot: $timeKey');
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
printLog('⚠️ Failed to cancel retry notifications: $e');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,11 +0,0 @@
|
||||
/*
|
||||
Deprecated/removed: notification_service.dart
|
||||
|
||||
This legacy notification service has been intentionally removed.
|
||||
The app now uses a minimal scheduler in:
|
||||
services/simple_notification_service.dart
|
||||
|
||||
All retry/snooze/database-tracking logic has been dropped to keep things simple.
|
||||
This file is left empty to ensure any lingering references fail at compile time,
|
||||
prompting migration to the new SimpleNotificationService.
|
||||
*/
|
@@ -1,12 +1,14 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
||||
import 'package:supplements/logging.dart';
|
||||
import 'package:supplements/services/notification_debug_store.dart';
|
||||
import 'package:supplements/services/notification_router.dart';
|
||||
import 'package:timezone/data/latest.dart' as tzdata;
|
||||
import 'package:timezone/timezone.dart' as tz;
|
||||
import 'dart:convert';
|
||||
import 'package:supplements/services/notification_router.dart';
|
||||
import 'package:supplements/services/notification_debug_store.dart';
|
||||
|
||||
import '../models/supplement.dart';
|
||||
import '../providers/settings_provider.dart';
|
||||
|
||||
/// A minimal notification scheduler focused purely on:
|
||||
/// - Initialization
|
||||
@@ -131,8 +133,12 @@ class SimpleNotificationService {
|
||||
///
|
||||
/// IDs:
|
||||
/// - Group ID per time slot: 40000 + hour*60 + minute.
|
||||
/// - Retry IDs: 50000 + (hour*60 + minute)*100 + retryIndex.
|
||||
/// - Stable and predictable for cancel/update operations.
|
||||
Future<void> scheduleDailyGroupedReminders(List<Supplement> supplements) async {
|
||||
Future<void> scheduleDailyGroupedReminders(
|
||||
List<Supplement> supplements, {
|
||||
SettingsProvider? settingsProvider,
|
||||
}) async {
|
||||
if (!_initialized) {
|
||||
await initialize();
|
||||
}
|
||||
@@ -281,6 +287,20 @@ class SimpleNotificationService {
|
||||
);
|
||||
|
||||
printLog('✅ Scheduled group $timeKey with ID $id');
|
||||
|
||||
// Schedule retry notifications if enabled
|
||||
if (settingsProvider != null && settingsProvider.notificationRetryEnabled) {
|
||||
await _scheduleRetryNotifications(
|
||||
timeKey: timeKey,
|
||||
supplements: items,
|
||||
isSingle: isSingle,
|
||||
title: title,
|
||||
body: body,
|
||||
payloadStr: payloadStr,
|
||||
retryCount: settingsProvider.notificationRetryCount,
|
||||
retryDelayMinutes: settingsProvider.notificationRetryDelayMinutes,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Log what the system reports as pending
|
||||
@@ -297,13 +317,16 @@ class SimpleNotificationService {
|
||||
|
||||
/// Convenience to schedule grouped reminders for today and tomorrow.
|
||||
///
|
||||
/// For iOS’s 64 limit, we stick to one day for recurring (matchDateTimeComponents)
|
||||
/// For iOS's 64 limit, we stick to one day for recurring (matchDateTimeComponents)
|
||||
/// which already repeats every day without needing to schedule future dates.
|
||||
/// If you want an extra safety net, you could schedule tomorrow’s one-offs,
|
||||
/// If you want an extra safety net, you could schedule tomorrow's one-offs,
|
||||
/// but with daily components this is unnecessary and risks hitting iOS limits.
|
||||
Future<void> scheduleDailyGroupedRemindersSafe(List<Supplement> supplements) async {
|
||||
// For now, just schedule today’s recurring groups.
|
||||
await scheduleDailyGroupedReminders(supplements);
|
||||
Future<void> scheduleDailyGroupedRemindersSafe(
|
||||
List<Supplement> supplements, {
|
||||
SettingsProvider? settingsProvider,
|
||||
}) async {
|
||||
// For now, just schedule today's recurring groups.
|
||||
await scheduleDailyGroupedReminders(supplements, settingsProvider: settingsProvider);
|
||||
}
|
||||
|
||||
/// Cancel all scheduled reminders for a given [supplementId].
|
||||
@@ -565,4 +588,176 @@ class SimpleNotificationService {
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
/// Schedule retry notifications for a specific time group.
|
||||
Future<void> _scheduleRetryNotifications({
|
||||
required String timeKey,
|
||||
required List<Supplement> supplements,
|
||||
required bool isSingle,
|
||||
required String title,
|
||||
required String body,
|
||||
required String payloadStr,
|
||||
required int retryCount,
|
||||
required int retryDelayMinutes,
|
||||
}) async {
|
||||
if (retryCount <= 0) return;
|
||||
|
||||
final parts = timeKey.split(':');
|
||||
final hour = int.parse(parts[0]);
|
||||
final minute = int.parse(parts[1]);
|
||||
final baseTime = _nextInstanceOfTime(hour, minute);
|
||||
|
||||
for (int retryIndex = 1; retryIndex <= retryCount; retryIndex++) {
|
||||
final retryDelay = Duration(minutes: retryDelayMinutes * retryIndex);
|
||||
final retryTime = baseTime.add(retryDelay);
|
||||
final retryId = 50000 + ((hour * 60) + minute) * 100 + retryIndex;
|
||||
|
||||
// Parse and modify payload to mark as retry
|
||||
Map<String, dynamic> retryPayload;
|
||||
try {
|
||||
retryPayload = Map<String, dynamic>.from(jsonDecode(payloadStr));
|
||||
retryPayload['meta'] = {
|
||||
'kind': 'retry',
|
||||
'originalTime': timeKey,
|
||||
'retryIndex': retryIndex,
|
||||
'retryOf': 40000 + (hour * 60) + minute,
|
||||
};
|
||||
} catch (e) {
|
||||
retryPayload = {
|
||||
'type': isSingle ? 'single' : 'group',
|
||||
if (isSingle) 'id': supplements.first.id else 'time': timeKey,
|
||||
'meta': {
|
||||
'kind': 'retry',
|
||||
'originalTime': timeKey,
|
||||
'retryIndex': retryIndex,
|
||||
'retryOf': 40000 + (hour * 60) + minute,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
final retryPayloadStr = jsonEncode(retryPayload);
|
||||
final retryTitle = 'Reminder: $title';
|
||||
|
||||
final androidDetails = AndroidNotificationDetails(
|
||||
_channelDailyId,
|
||||
_channelDailyName,
|
||||
channelDescription: _channelDailyDescription,
|
||||
importance: Importance.high,
|
||||
priority: Priority.high,
|
||||
styleInformation: BigTextStyleInformation(
|
||||
body,
|
||||
contentTitle: retryTitle,
|
||||
htmlFormatContentTitle: false,
|
||||
),
|
||||
actions: [
|
||||
if (isSingle)
|
||||
AndroidNotificationAction(
|
||||
'take_single',
|
||||
'Take',
|
||||
showsUserInterface: true,
|
||||
cancelNotification: true,
|
||||
)
|
||||
else
|
||||
AndroidNotificationAction(
|
||||
'take_group',
|
||||
'Take All',
|
||||
showsUserInterface: true,
|
||||
cancelNotification: true,
|
||||
),
|
||||
if (isSingle)
|
||||
AndroidNotificationAction(
|
||||
'snooze_single',
|
||||
'Snooze',
|
||||
showsUserInterface: false,
|
||||
)
|
||||
else
|
||||
AndroidNotificationAction(
|
||||
'snooze_group',
|
||||
'Snooze',
|
||||
showsUserInterface: false,
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
final iosDetails = DarwinNotificationDetails(
|
||||
categoryIdentifier: isSingle ? 'single' : 'group',
|
||||
);
|
||||
|
||||
await _plugin.zonedSchedule(
|
||||
retryId,
|
||||
retryTitle,
|
||||
isSingle ? body : 'Tap to see details',
|
||||
retryTime,
|
||||
NotificationDetails(
|
||||
android: androidDetails,
|
||||
iOS: iosDetails,
|
||||
),
|
||||
androidScheduleMode: AndroidScheduleMode.inexactAllowWhileIdle,
|
||||
payload: retryPayloadStr,
|
||||
);
|
||||
|
||||
// Log to debug store
|
||||
final createdAtMs = DateTime.now().millisecondsSinceEpoch;
|
||||
await NotificationDebugStore.instance.add(
|
||||
NotificationLogEntry(
|
||||
id: retryId,
|
||||
kind: 'retry',
|
||||
type: isSingle ? 'single' : 'group',
|
||||
whenEpochMs: retryTime.millisecondsSinceEpoch,
|
||||
createdAtEpochMs: createdAtMs,
|
||||
title: retryTitle,
|
||||
payload: retryPayloadStr,
|
||||
singleId: isSingle ? supplements.first.id : null,
|
||||
timeKey: isSingle ? null : timeKey,
|
||||
),
|
||||
);
|
||||
|
||||
printLog('🔄 Scheduled retry $retryIndex/$retryCount for $timeKey (ID: $retryId) at $retryTime');
|
||||
}
|
||||
}
|
||||
|
||||
/// Cancel retry notifications for a specific time slot.
|
||||
/// This should be called when a user responds to a notification.
|
||||
Future<void> cancelRetryNotificationsForTimeSlot(String timeKey) async {
|
||||
if (!_initialized) {
|
||||
await initialize();
|
||||
}
|
||||
|
||||
final parts = timeKey.split(':');
|
||||
if (parts.length != 2) return;
|
||||
|
||||
final hour = int.tryParse(parts[0]);
|
||||
final minute = int.tryParse(parts[1]);
|
||||
if (hour == null || minute == null) return;
|
||||
|
||||
// Calculate base retry ID range for this time slot
|
||||
final baseRetryId = 50000 + ((hour * 60) + minute) * 100;
|
||||
|
||||
// Cancel up to 10 possible retries (generous upper bound)
|
||||
for (int retryIndex = 1; retryIndex <= 10; retryIndex++) {
|
||||
final retryId = baseRetryId + retryIndex;
|
||||
await _plugin.cancel(retryId);
|
||||
printLog('🚫 Cancelled retry notification ID: $retryId for time slot $timeKey');
|
||||
}
|
||||
}
|
||||
|
||||
/// Cancel retry notifications for a specific supplement ID.
|
||||
/// This iterates through all possible time slots.
|
||||
Future<void> cancelRetryNotificationsForSupplement(int supplementId) async {
|
||||
if (!_initialized) {
|
||||
await initialize();
|
||||
}
|
||||
|
||||
// Cancel retries for all possible time slots (24 hours * 60 minutes)
|
||||
for (int hour = 0; hour < 24; hour++) {
|
||||
for (int minute = 0; minute < 60; minute += 5) { // Assume 5-minute intervals
|
||||
final baseRetryId = 50000 + ((hour * 60) + minute) * 100;
|
||||
for (int retryIndex = 1; retryIndex <= 10; retryIndex++) {
|
||||
final retryId = baseRetryId + retryIndex;
|
||||
await _plugin.cancel(retryId);
|
||||
}
|
||||
}
|
||||
}
|
||||
printLog('🚫 Cancelled all retry notifications for supplement ID: $supplementId');
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user