diff --git a/frontend/AGENTS.md b/frontend/AGENTS.md
index b722931..23687e8 100644
--- a/frontend/AGENTS.md
+++ b/frontend/AGENTS.md
@@ -171,6 +171,9 @@ Files backup/restore - mirror mode or zip -->
+
+
---
# TIS_RMS Server Manager (dont do)
diff --git a/frontend/android/app/src/main/AndroidManifest.xml b/frontend/android/app/src/main/AndroidManifest.xml
index 33783fc..bf17c14 100644
--- a/frontend/android/app/src/main/AndroidManifest.xml
+++ b/frontend/android/app/src/main/AndroidManifest.xml
@@ -10,16 +10,23 @@
android:required="false"/>
-
+
+
+
+
+
+
>();
if (list.isNotEmpty) {
// 3. Find the last seen notification ID
final highestOldId = prefs.getInt('last_seen_notification_id') ?? 0;
- // Find new ones
- final newNotes = list
- .where((e) => (e['id'] as int) > highestOldId)
- .toList();
+ // Find new unread notifications
+ final newNotes = list.where((e) {
+ final id = e['id'] as int? ?? 0;
+ final isRead = (e['is_read'] == 1 || e['is_read'] == true);
+ if (highestOldId == 0) {
+ return !isRead;
+ }
+ return id > highestOldId && !isRead;
+ }).toList();
if (newNotes.isNotEmpty) {
final notificationService = NotificationService();
- await notificationService
- .initialize(); // Ensure it's ready in this isolate
+ await notificationService.initialize();
// 4. Show local notification for each new one
for (var note in newNotes) {
+ final id = note['id'] as int?;
+ final title = note['title']?.toString() ?? 'TIS RMS Notification';
+ final message = note['message']?.toString() ?? '';
await notificationService.showNotification(
- title: note['title'] ?? 'New Notification',
- body: note['message'] ?? '',
+ id: id,
+ title: title,
+ body: message,
);
}
+ }
- // 5. Update highest seen ID in prefs
- final newHighestId = list.map((e) => e['id'] as int).reduce(math.max);
+ // 5. Update highest seen ID in prefs
+ final ids = list.map((e) => e['id'] as int? ?? 0).toList();
+ if (ids.isNotEmpty) {
+ final newHighestId = ids.reduce(math.max);
await prefs.setInt(
'last_seen_notification_id',
math.max(highestOldId, newHighestId),
@@ -63,8 +81,7 @@ void callbackDispatcher() {
}
return Future.value(true);
- } catch (e) {
- print('Background fetch failed: $e');
+ } catch (_) {
return Future.value(false); // Retries on false
}
});
diff --git a/frontend/lib/core/services/foreground_sync_service.dart b/frontend/lib/core/services/foreground_sync_service.dart
new file mode 100644
index 0000000..e70bc9f
--- /dev/null
+++ b/frontend/lib/core/services/foreground_sync_service.dart
@@ -0,0 +1,157 @@
+import 'dart:async';
+import 'dart:io' show Platform;
+import 'dart:math' as math;
+import 'package:dio/dio.dart';
+import 'package:flutter/foundation.dart' show kIsWeb;
+import 'package:flutter_foreground_task/flutter_foreground_task.dart';
+import 'package:flutter_secure_storage/flutter_secure_storage.dart';
+import 'package:shared_preferences/shared_preferences.dart';
+import '../network/api_constants.dart';
+import 'notification_service.dart';
+
+@pragma('vm:entry-point')
+void startForegroundCallback() {
+ FlutterForegroundTask.setTaskHandler(ForegroundSyncHandler());
+}
+
+class ForegroundSyncHandler extends TaskHandler {
+ final FlutterSecureStorage _storage = const FlutterSecureStorage();
+ final NotificationService _notificationService = NotificationService();
+
+ @override
+ Future onStart(DateTime timestamp, TaskStarter starter) async {
+ await _notificationService.initialize();
+ }
+
+ @override
+ Future onRepeatEvent(DateTime timestamp) async {
+ try {
+ final prefs = await SharedPreferences.getInstance();
+ String rawUrl = prefs.getString('server_url') ?? ApiConstants.baseUrl;
+ final clean = rawUrl.replaceAll(RegExp(r'/+$'), '');
+ final baseUrl = clean.endsWith('/api') ? clean : '$clean/api';
+
+ final token = await _storage.read(key: 'jwt_token');
+ if (token == null || token.isEmpty) {
+ return;
+ }
+
+ final dio = Dio(
+ BaseOptions(
+ baseUrl: baseUrl,
+ connectTimeout: const Duration(seconds: 5),
+ receiveTimeout: const Duration(seconds: 5),
+ headers: {'Authorization': 'Bearer $token'},
+ ),
+ );
+
+ final response = await dio.get('/notifications');
+ final list = (response.data as List).cast