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 createState() => _PendingNotificationsScreenState(); } class _PendingNotificationsScreenState extends State { List _pendingNotifications = []; List> _trackedNotifications = []; bool _isLoading = true; @override void initState() { super.initState(); _loadNotifications(); } Future _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 = >[]; // 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 = >[]; // 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 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'; } } }