mirror of
https://github.com/vleeuwenmenno/supplements.git
synced 2025-09-11 18:29:12 +02:00
feat: Implement pending notifications screen and enhance notification handling
This commit is contained in:
@@ -99,6 +99,7 @@ class SupplementProvider with ChangeNotifier {
|
|||||||
required int reminderRetryInterval,
|
required int reminderRetryInterval,
|
||||||
required int maxRetryAttempts,
|
required int maxRetryAttempts,
|
||||||
}) async {
|
}) async {
|
||||||
|
print('📱 🔄 MANUAL CHECK: Persistent reminders called from UI');
|
||||||
await _notificationService.checkPersistentReminders(
|
await _notificationService.checkPersistentReminders(
|
||||||
persistentReminders,
|
persistentReminders,
|
||||||
reminderRetryInterval,
|
reminderRetryInterval,
|
||||||
@@ -106,6 +107,16 @@ class SupplementProvider with ChangeNotifier {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add a manual trigger method for testing
|
||||||
|
Future<void> triggerRetryCheck() async {
|
||||||
|
print('📱 🚨 MANUAL TRIGGER: Forcing retry check...');
|
||||||
|
await checkPersistentRemindersWithSettings(
|
||||||
|
persistentReminders: true,
|
||||||
|
reminderRetryInterval: 5, // Force 5 minute interval for testing
|
||||||
|
maxRetryAttempts: 3,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_persistentReminderTimer?.cancel();
|
_persistentReminderTimer?.cancel();
|
||||||
@@ -363,6 +374,11 @@ class SupplementProvider with ChangeNotifier {
|
|||||||
return await _notificationService.getPendingNotifications();
|
return await _notificationService.getPendingNotifications();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get pending notifications with retry information from database
|
||||||
|
Future<List<Map<String, dynamic>>> getTrackedNotifications() async {
|
||||||
|
return await DatabaseHelper.instance.getPendingNotifications();
|
||||||
|
}
|
||||||
|
|
||||||
// Debug method to test notification persistence
|
// Debug method to test notification persistence
|
||||||
Future<void> rescheduleAllNotifications() async {
|
Future<void> rescheduleAllNotifications() async {
|
||||||
await _rescheduleAllNotifications();
|
await _rescheduleAllNotifications();
|
||||||
|
@@ -33,12 +33,12 @@ class _HomeScreenState extends State<HomeScreen> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _startPersistentReminderCheck() {
|
void _startPersistentReminderCheck() {
|
||||||
// Check immediately and then every 10 minutes
|
// Check immediately and then every 3 minutes (faster than any retry interval)
|
||||||
_checkPersistentReminders();
|
_checkPersistentReminders();
|
||||||
|
|
||||||
// Set up periodic checking
|
// Set up periodic checking every 3 minutes to ensure we catch all retry intervals
|
||||||
Future.doWhile(() async {
|
Future.doWhile(() async {
|
||||||
await Future.delayed(const Duration(minutes: 10));
|
await Future.delayed(const Duration(minutes: 3));
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
await _checkPersistentReminders();
|
await _checkPersistentReminders();
|
||||||
return true;
|
return true;
|
||||||
@@ -51,14 +51,18 @@ class _HomeScreenState extends State<HomeScreen> {
|
|||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
print('📱 === HOME SCREEN: Checking persistent reminders ===');
|
||||||
final supplementProvider = context.read<SupplementProvider>();
|
final supplementProvider = context.read<SupplementProvider>();
|
||||||
final settingsProvider = context.read<SettingsProvider>();
|
final settingsProvider = context.read<SettingsProvider>();
|
||||||
|
|
||||||
|
print('📱 Settings: persistent=${settingsProvider.persistentReminders}, interval=${settingsProvider.reminderRetryInterval}, max=${settingsProvider.maxRetryAttempts}');
|
||||||
|
|
||||||
await supplementProvider.checkPersistentRemindersWithSettings(
|
await supplementProvider.checkPersistentRemindersWithSettings(
|
||||||
persistentReminders: settingsProvider.persistentReminders,
|
persistentReminders: settingsProvider.persistentReminders,
|
||||||
reminderRetryInterval: settingsProvider.reminderRetryInterval,
|
reminderRetryInterval: settingsProvider.reminderRetryInterval,
|
||||||
maxRetryAttempts: settingsProvider.maxRetryAttempts,
|
maxRetryAttempts: settingsProvider.maxRetryAttempts,
|
||||||
);
|
);
|
||||||
|
print('📱 === HOME SCREEN: Persistent reminder check complete ===');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('Error checking persistent reminders: $e');
|
print('Error checking persistent reminders: $e');
|
||||||
}
|
}
|
||||||
|
368
lib/screens/pending_notifications_screen.dart
Normal file
368
lib/screens/pending_notifications_screen.dart
Normal file
@@ -0,0 +1,368 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
||||||
|
import '../services/notification_service.dart';
|
||||||
|
import '../services/database_helper.dart';
|
||||||
|
|
||||||
|
class PendingNotificationsScreen extends StatefulWidget {
|
||||||
|
const PendingNotificationsScreen({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<PendingNotificationsScreen> createState() => _PendingNotificationsScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PendingNotificationsScreenState extends State<PendingNotificationsScreen> {
|
||||||
|
List<PendingNotificationRequest> _pendingNotifications = [];
|
||||||
|
List<Map<String, dynamic>> _trackedNotifications = [];
|
||||||
|
bool _isLoading = true;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_loadNotifications();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _loadNotifications() async {
|
||||||
|
setState(() {
|
||||||
|
_isLoading = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Get system pending notifications
|
||||||
|
final notificationService = NotificationService();
|
||||||
|
final systemPending = await notificationService.getPendingNotifications();
|
||||||
|
|
||||||
|
// Get tracked notifications from database (including retries)
|
||||||
|
final trackedNotifications = await DatabaseHelper.instance.getPendingNotifications();
|
||||||
|
|
||||||
|
// Combine and sort by scheduled time (soonest first)
|
||||||
|
final allNotifications = <Map<String, dynamic>>[];
|
||||||
|
|
||||||
|
// Add system notifications
|
||||||
|
for (final notification in systemPending) {
|
||||||
|
allNotifications.add({
|
||||||
|
'id': notification.id,
|
||||||
|
'title': notification.title ?? 'Notification',
|
||||||
|
'body': notification.body ?? '',
|
||||||
|
'scheduledTime': DateTime.now(), // PendingNotificationRequest doesn't have scheduledTime
|
||||||
|
'type': 'system',
|
||||||
|
'isRetry': false,
|
||||||
|
'retryCount': 0,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add tracked notifications from database
|
||||||
|
for (final notification in trackedNotifications) {
|
||||||
|
final scheduledTime = DateTime.parse(notification['scheduledTime']).toLocal();
|
||||||
|
allNotifications.add({
|
||||||
|
'id': notification['notificationId'],
|
||||||
|
'title': 'Time for ${notification['supplementName'] ?? 'Supplement'}',
|
||||||
|
'body': 'Take your supplement',
|
||||||
|
'scheduledTime': scheduledTime,
|
||||||
|
'type': 'tracked',
|
||||||
|
'status': notification['status'],
|
||||||
|
'isRetry': notification['status'] == 'retrying',
|
||||||
|
'retryCount': notification['retryCount'] ?? 0,
|
||||||
|
'supplementName': notification['supplementName'],
|
||||||
|
'supplementId': notification['supplementId'],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort by scheduled time (soonest first)
|
||||||
|
allNotifications.sort((a, b) {
|
||||||
|
final timeA = a['scheduledTime'] as DateTime;
|
||||||
|
final timeB = b['scheduledTime'] as DateTime;
|
||||||
|
return timeA.compareTo(timeB);
|
||||||
|
});
|
||||||
|
|
||||||
|
setState(() {
|
||||||
|
_pendingNotifications = systemPending;
|
||||||
|
_trackedNotifications = trackedNotifications;
|
||||||
|
_isLoading = false;
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
print('Error loading notifications: $e');
|
||||||
|
setState(() {
|
||||||
|
_isLoading = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: const Text('Pending Notifications'),
|
||||||
|
actions: [
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.refresh),
|
||||||
|
onPressed: _loadNotifications,
|
||||||
|
tooltip: 'Refresh',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
body: _isLoading
|
||||||
|
? const Center(child: CircularProgressIndicator())
|
||||||
|
: RefreshIndicator(
|
||||||
|
onRefresh: _loadNotifications,
|
||||||
|
child: _buildNotificationsList(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildNotificationsList() {
|
||||||
|
if (_pendingNotifications.isEmpty && _trackedNotifications.isEmpty) {
|
||||||
|
return const Center(
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
Icons.notifications_off,
|
||||||
|
size: 64,
|
||||||
|
color: Colors.grey,
|
||||||
|
),
|
||||||
|
SizedBox(height: 16),
|
||||||
|
Text(
|
||||||
|
'No pending notifications',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 18,
|
||||||
|
color: Colors.grey,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(height: 8),
|
||||||
|
Text(
|
||||||
|
'All caught up!',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.grey,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Combine and sort all notifications
|
||||||
|
final allNotifications = <Map<String, dynamic>>[];
|
||||||
|
|
||||||
|
// Add system notifications
|
||||||
|
for (final notification in _pendingNotifications) {
|
||||||
|
allNotifications.add({
|
||||||
|
'id': notification.id,
|
||||||
|
'title': notification.title ?? 'Notification',
|
||||||
|
'body': notification.body ?? '',
|
||||||
|
'scheduledTime': DateTime.now(), // PendingNotificationRequest doesn't have scheduledTime
|
||||||
|
'type': 'system',
|
||||||
|
'isRetry': false,
|
||||||
|
'retryCount': 0,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add tracked notifications from database
|
||||||
|
for (final notification in _trackedNotifications) {
|
||||||
|
final scheduledTime = DateTime.parse(notification['scheduledTime']).toLocal();
|
||||||
|
allNotifications.add({
|
||||||
|
'id': notification['notificationId'],
|
||||||
|
'title': 'Time for ${notification['supplementName'] ?? 'Supplement'}',
|
||||||
|
'body': 'Take your supplement',
|
||||||
|
'scheduledTime': scheduledTime,
|
||||||
|
'type': 'tracked',
|
||||||
|
'status': notification['status'],
|
||||||
|
'isRetry': notification['status'] == 'retrying',
|
||||||
|
'retryCount': notification['retryCount'] ?? 0,
|
||||||
|
'supplementName': notification['supplementName'],
|
||||||
|
'supplementId': notification['supplementId'],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort by scheduled time (soonest first)
|
||||||
|
allNotifications.sort((a, b) {
|
||||||
|
final timeA = a['scheduledTime'] as DateTime;
|
||||||
|
final timeB = b['scheduledTime'] as DateTime;
|
||||||
|
return timeA.compareTo(timeB);
|
||||||
|
});
|
||||||
|
|
||||||
|
return ListView.builder(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
itemCount: allNotifications.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final notification = allNotifications[index];
|
||||||
|
return _buildNotificationCard(notification);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildNotificationCard(Map<String, dynamic> notification) {
|
||||||
|
final scheduledTime = notification['scheduledTime'] as DateTime;
|
||||||
|
final now = DateTime.now();
|
||||||
|
final isOverdue = scheduledTime.isBefore(now);
|
||||||
|
final isRetry = notification['isRetry'] as bool;
|
||||||
|
final retryCount = notification['retryCount'] as int;
|
||||||
|
final type = notification['type'] as String;
|
||||||
|
|
||||||
|
final timeUntil = scheduledTime.difference(now);
|
||||||
|
String timeText;
|
||||||
|
|
||||||
|
if (isOverdue) {
|
||||||
|
final overdue = now.difference(scheduledTime);
|
||||||
|
if (overdue.inDays > 0) {
|
||||||
|
timeText = 'Overdue by ${overdue.inDays}d ${overdue.inHours % 24}h';
|
||||||
|
} else if (overdue.inHours > 0) {
|
||||||
|
timeText = 'Overdue by ${overdue.inHours}h ${overdue.inMinutes % 60}m';
|
||||||
|
} else {
|
||||||
|
timeText = 'Overdue by ${overdue.inMinutes}m';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (timeUntil.inDays > 0) {
|
||||||
|
timeText = 'In ${timeUntil.inDays}d ${timeUntil.inHours % 24}h';
|
||||||
|
} else if (timeUntil.inHours > 0) {
|
||||||
|
timeText = 'In ${timeUntil.inHours}h ${timeUntil.inMinutes % 60}m';
|
||||||
|
} else {
|
||||||
|
timeText = 'In ${timeUntil.inMinutes}m';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Card(
|
||||||
|
margin: const EdgeInsets.only(bottom: 12),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
notification['title'] ?? 'Notification',
|
||||||
|
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (notification['body'] != null) ...[
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
Text(
|
||||||
|
notification['body'],
|
||||||
|
style: Theme.of(context).textTheme.bodyMedium,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
if (isRetry) ...[
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 8,
|
||||||
|
vertical: 4,
|
||||||
|
),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.orange.withOpacity(0.2),
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
border: Border.all(color: Colors.orange.withOpacity(0.5)),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
'Retry #$retryCount',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
color: Colors.orange.shade700,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
],
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 8,
|
||||||
|
vertical: 4,
|
||||||
|
),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: type == 'system'
|
||||||
|
? Colors.blue.withOpacity(0.2)
|
||||||
|
: Colors.green.withOpacity(0.2),
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
border: Border.all(
|
||||||
|
color: type == 'system'
|
||||||
|
? Colors.blue.withOpacity(0.5)
|
||||||
|
: Colors.green.withOpacity(0.5),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
type == 'system' ? 'System' : 'Tracked',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
color: type == 'system'
|
||||||
|
? Colors.blue.shade700
|
||||||
|
: Colors.green.shade700,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
isOverdue ? Icons.schedule_outlined : Icons.access_time,
|
||||||
|
size: 16,
|
||||||
|
color: isOverdue ? Colors.red : Colors.grey,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
Text(
|
||||||
|
'${_formatTime(scheduledTime)} • $timeText',
|
||||||
|
style: TextStyle(
|
||||||
|
color: isOverdue ? Colors.red : Colors.grey.shade700,
|
||||||
|
fontWeight: isOverdue ? FontWeight.bold : FontWeight.normal,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
if (type == 'tracked' && notification['supplementName'] != null) ...[
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
const Icon(
|
||||||
|
Icons.medication,
|
||||||
|
size: 16,
|
||||||
|
color: Colors.grey,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
Text(
|
||||||
|
notification['supplementName'],
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.grey.shade700,
|
||||||
|
fontStyle: FontStyle.italic,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
String _formatTime(DateTime dateTime) {
|
||||||
|
final now = DateTime.now();
|
||||||
|
final today = DateTime(now.year, now.month, now.day);
|
||||||
|
final tomorrow = today.add(const Duration(days: 1));
|
||||||
|
final notificationDate = DateTime(dateTime.year, dateTime.month, dateTime.day);
|
||||||
|
|
||||||
|
String timeStr = '${dateTime.hour.toString().padLeft(2, '0')}:${dateTime.minute.toString().padLeft(2, '0')}';
|
||||||
|
|
||||||
|
if (notificationDate == today) {
|
||||||
|
return 'Today $timeStr';
|
||||||
|
} else if (notificationDate == tomorrow) {
|
||||||
|
return 'Tomorrow $timeStr';
|
||||||
|
} else {
|
||||||
|
return '${dateTime.day}/${dateTime.month} $timeStr';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -3,6 +3,7 @@ import 'package:provider/provider.dart';
|
|||||||
import '../providers/settings_provider.dart';
|
import '../providers/settings_provider.dart';
|
||||||
import '../providers/supplement_provider.dart';
|
import '../providers/supplement_provider.dart';
|
||||||
import '../services/notification_service.dart';
|
import '../services/notification_service.dart';
|
||||||
|
import 'pending_notifications_screen.dart';
|
||||||
|
|
||||||
class SettingsScreen extends StatelessWidget {
|
class SettingsScreen extends StatelessWidget {
|
||||||
const SettingsScreen({super.key});
|
const SettingsScreen({super.key});
|
||||||
@@ -212,6 +213,49 @@ class SettingsScreen extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
|
Card(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
const Icon(Icons.notifications_outlined),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
Text(
|
||||||
|
'Notifications',
|
||||||
|
style: Theme.of(context).textTheme.titleMedium,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Text(
|
||||||
|
'View and manage pending notifications',
|
||||||
|
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
||||||
|
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
SizedBox(
|
||||||
|
width: double.infinity,
|
||||||
|
child: ElevatedButton.icon(
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).push(
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) => const PendingNotificationsScreen(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
icon: const Icon(Icons.list),
|
||||||
|
label: const Text('View Pending Notifications'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
if (Theme.of(context).brightness == Brightness.dark) // Only show in debug mode for now
|
if (Theme.of(context).brightness == Brightness.dark) // Only show in debug mode for now
|
||||||
Card(
|
Card(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
|
@@ -431,11 +431,13 @@ class DatabaseHelper {
|
|||||||
|
|
||||||
Future<List<Map<String, dynamic>>> getPendingNotifications() async {
|
Future<List<Map<String, dynamic>>> getPendingNotifications() async {
|
||||||
Database db = await database;
|
Database db = await database;
|
||||||
return await db.query(
|
return await db.rawQuery('''
|
||||||
notificationTrackingTable,
|
SELECT nt.*, s.name as supplementName
|
||||||
where: 'status IN (?, ?)',
|
FROM $notificationTrackingTable nt
|
||||||
whereArgs: ['pending', 'retrying'],
|
LEFT JOIN $supplementsTable s ON nt.supplementId = s.id
|
||||||
);
|
WHERE nt.status IN (?, ?)
|
||||||
|
ORDER BY nt.scheduledTime ASC
|
||||||
|
''', ['pending', 'retrying']);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> markNotificationExpired(int notificationId) async {
|
Future<void> markNotificationExpired(int notificationId) async {
|
||||||
|
@@ -148,7 +148,7 @@ class NotificationService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _handleTakeAction(String? payload, int? notificationId) {
|
Future<void> _handleTakeAction(String? payload, int? notificationId) async {
|
||||||
print('📱 === HANDLING TAKE ACTION ===');
|
print('📱 === HANDLING TAKE ACTION ===');
|
||||||
print('📱 Payload received: $payload');
|
print('📱 Payload received: $payload');
|
||||||
|
|
||||||
@@ -182,7 +182,7 @@ class NotificationService {
|
|||||||
// Mark notification as taken in database (this will cancel any pending retries)
|
// Mark notification as taken in database (this will cancel any pending retries)
|
||||||
if (notificationId != null) {
|
if (notificationId != null) {
|
||||||
print('📱 Marking notification $notificationId as taken');
|
print('📱 Marking notification $notificationId as taken');
|
||||||
DatabaseHelper.instance.markNotificationTaken(notificationId);
|
await DatabaseHelper.instance.markNotificationTaken(notificationId);
|
||||||
|
|
||||||
// Cancel any pending retry notifications for this notification
|
// Cancel any pending retry notifications for this notification
|
||||||
_cancelRetryNotifications(notificationId);
|
_cancelRetryNotifications(notificationId);
|
||||||
@@ -316,16 +316,24 @@ class NotificationService {
|
|||||||
final now = DateTime.now();
|
final now = DateTime.now();
|
||||||
|
|
||||||
for (final notification in pendingNotifications) {
|
for (final notification in pendingNotifications) {
|
||||||
final scheduledTime = DateTime.parse(notification['scheduledTime']);
|
final scheduledTime = DateTime.parse(notification['scheduledTime']).toLocal();
|
||||||
final retryCount = notification['retryCount'] as int;
|
final retryCount = notification['retryCount'] as int;
|
||||||
final lastRetryTime = notification['lastRetryTime'] != null
|
final lastRetryTime = notification['lastRetryTime'] != null
|
||||||
? DateTime.parse(notification['lastRetryTime'])
|
? DateTime.parse(notification['lastRetryTime']).toLocal()
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
// Check if notification is overdue
|
// Check if notification is overdue
|
||||||
final timeSinceScheduled = now.difference(scheduledTime).inMinutes;
|
final timeSinceScheduled = now.difference(scheduledTime).inMinutes;
|
||||||
final shouldRetry = timeSinceScheduled >= reminderRetryInterval;
|
final shouldRetry = timeSinceScheduled >= reminderRetryInterval;
|
||||||
|
|
||||||
|
print('📱 Checking notification ${notification['notificationId']}:');
|
||||||
|
print('📱 Scheduled: $scheduledTime (local)');
|
||||||
|
print('📱 Now: $now');
|
||||||
|
print('📱 Time since scheduled: $timeSinceScheduled minutes');
|
||||||
|
print('📱 Retry interval: $reminderRetryInterval minutes');
|
||||||
|
print('📱 Should retry: $shouldRetry');
|
||||||
|
print('📱 Retry count: $retryCount / $maxRetryAttempts');
|
||||||
|
|
||||||
// Check if we haven't exceeded max retry attempts
|
// Check if we haven't exceeded max retry attempts
|
||||||
if (retryCount >= maxRetryAttempts) {
|
if (retryCount >= maxRetryAttempts) {
|
||||||
print('📱 Notification ${notification['notificationId']} exceeded max attempts ($maxRetryAttempts)');
|
print('📱 Notification ${notification['notificationId']} exceeded max attempts ($maxRetryAttempts)');
|
||||||
@@ -342,7 +350,10 @@ class NotificationService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (shouldRetry) {
|
if (shouldRetry) {
|
||||||
|
print('📱 ⚡ SCHEDULING RETRY for notification ${notification['notificationId']}');
|
||||||
await _scheduleRetryNotification(notification, retryCount + 1);
|
await _scheduleRetryNotification(notification, retryCount + 1);
|
||||||
|
} else {
|
||||||
|
print('📱 ⏸️ NOT READY FOR RETRY: ${notification['notificationId']}');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -381,13 +392,11 @@ class NotificationService {
|
|||||||
'take_supplement',
|
'take_supplement',
|
||||||
'Take',
|
'Take',
|
||||||
showsUserInterface: true,
|
showsUserInterface: true,
|
||||||
icon: DrawableResourceAndroidBitmap('@drawable/ic_check'),
|
|
||||||
),
|
),
|
||||||
AndroidNotificationAction(
|
AndroidNotificationAction(
|
||||||
'snooze_10',
|
'snooze_10',
|
||||||
'Snooze 10min',
|
'Snooze 10min',
|
||||||
showsUserInterface: true,
|
showsUserInterface: true,
|
||||||
icon: DrawableResourceAndroidBitmap('@drawable/ic_snooze'),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
Reference in New Issue
Block a user