diff --git a/assets/images/Icon_mark_read.svg b/assets/images/Icon_mark_read.svg
new file mode 100644
index 00000000..fd7ed9bd
--- /dev/null
+++ b/assets/images/Icon_mark_read.svg
@@ -0,0 +1,5 @@
+
diff --git a/assets/images/notification_setting.svg b/assets/images/notification_setting.svg
new file mode 100644
index 00000000..d007a3db
--- /dev/null
+++ b/assets/images/notification_setting.svg
@@ -0,0 +1,4 @@
+
diff --git a/lib/controllers/api_routes/api_manager.dart b/lib/controllers/api_routes/api_manager.dart
index 6142dbc5..f0c7ab42 100644
--- a/lib/controllers/api_routes/api_manager.dart
+++ b/lib/controllers/api_routes/api_manager.dart
@@ -336,6 +336,46 @@ class ApiManager {
return response;
}
+ /// POST request with List/Array body (for endpoints that expect JSON arrays)
+ Future postList(String url, {Map? headers, required List body, bool showToast = true}) async {
+ headers ??= {};
+
+ headers.addAll(_headers);
+
+ Uri url0 = Uri.parse(url);
+ if (!kReleaseMode) {
+ print("URL:$url0");
+ print("Headers:$headers");
+ print("Body:$body");
+ }
+
+ // OPTIMIZATION: Use persistent HTTP client for connection pooling
+ var request = http.Request('POST', url0);
+ request.body = json.encode(body);
+ request.headers.addAll(headers);
+
+ http.StreamedResponse streamedResponse = await _httpClient.send(request);
+ http.Response response = await http.Response.fromStream(streamedResponse);
+
+ // OPTIMIZATION: Parse JSON once and reuse (was parsing twice before)
+ try {
+ if (response.statusCode == 401) {
+ showLoginDialog();
+ } else {
+ // Parse response body only once
+ final responseBody = jsonDecode(response.body);
+ if (responseBody is Map) {
+ final message = responseBody["message"]; // Reuse parsed body
+ if (message != null && message.toString().isNotEmpty && showToast) {
+ Fluttertoast.showToast(msg: message, toastLength: Toast.LENGTH_LONG);
+ }
+ }
+ }
+ } catch (ex) {}
+
+ return response;
+ }
+
Future multiPart(String url, {Map? headers, required Map body, required List> files}) async {
Map headers0 = _headers;
diff --git a/lib/controllers/api_routes/urls.dart b/lib/controllers/api_routes/urls.dart
index d2204b85..79563b15 100644
--- a/lib/controllers/api_routes/urls.dart
+++ b/lib/controllers/api_routes/urls.dart
@@ -1,15 +1,15 @@
class URLs {
URLs._();
- static const String appReleaseBuildNumber = "50";
+ static const String appReleaseBuildNumber = "52";
// static const host1 = "https://atomsm.hmg.com"; // production url
static const host1 = "https://atomsmdev.hmg.com"; // local DEV url
// static const host1 = "https://atomsmuat.hmg.com"; // local UAT url
- static final String _baseUrl = "$_host/mobile"; // host local UAT
+ // static final String _baseUrl = "$_host/mobile"; // host local UAT
// static final String _baseUrl = "$_host/v2/mobile"; // new V2 apis
- // static final String _baseUrl = "$_host/v3/mobile"; // v3 for production CM,PM,TM
+ static final String _baseUrl = "$_host/v3/mobile"; // v3 for production CM,PM,TM
// static final String _baseUrl = "$_host/v4/mobile"; // v4 for Demo module
// static final String _baseUrl = "$_host/v5/mobile"; // v5 for data segregation
// static final String _baseUrl = "$_host/v6/mobile"; // for asset delivery module
@@ -262,7 +262,20 @@ class URLs {
static get addSupplierEngineer => "$_baseUrl/Supplier/AddSupplierEngineer";
- static get getSystemNotifications => "$_baseUrl/SystemNotification/GetSystemNotifications"; // get
+ // static get getSystemNotifications => "$_baseUrl/SystemNotification/GetSystemNotifications"; // get (OLD - COMMENTED OUT)
+ static get getNotificationInbox => "$_baseUrl/NotificationNew/Inbox"; // get notifications with filters
+ static get markAllNotificationsAsRead => "$_baseUrl/NotificationNew/MarkAllRead"; // mark all as read
+ static get markOneNotificationAsRead => "$_baseUrl/NotificationNew/MarkAsReadOneNotification"; // mark one notification as read
+ static get getUnreadNotificationCount => "$_baseUrl/NotificationNew/GetUnread"; // mark one notification as read
+
+ // Notification Filter Lookups
+ static get getNotificationPriorityLookup => "$_baseUrl/Lookups/GetLookup?lookupEnum=5021"; // notification priority filter
+ static get getNotificationChannelLookup => "$_baseUrl/Lookups/GetLookup?lookupEnum=5024"; // notification channel filter
+ static get getNotificationRecipientLookup => "$_baseUrl/Lookups/GetLookup?lookupEnum=5022"; // notification recipient filter
+
+ // Modules and SubModules
+ static get getModulesWithSubModules => "$_baseUrl/Module/GetModulesWithSubModules"; // get modules with submodules for filters
+
static get getRecentNotifications => "$_baseUrl/return/user/recent/notification"; // get
static get createRequest => "$_baseUrl/CallRequest/AddCallRequest"; // get
static get CheckIfAssetHasAnotherServiceRequest => "$_baseUrl/ServiceRequest/CheckForPendingRequestForAsset"; // get
@@ -595,6 +608,10 @@ class URLs {
// Print/Report URLs
static get generateReport => "$_baseUrl/ReportPDF/GenerateReport";
+ // Notification Preference Center
+ static String get getNotificationPreferences => "$_baseUrl/NotificationPreferenceCenter/GetTemplateApplyOnCurrentUser";
+ static String get saveNotificationPreferences => "$_baseUrl/NotificationPreferenceCenter/SaveNotificationPreferenceCenter";
+
//4104 attachment type lookup
// Demo Request APIs
diff --git a/lib/controllers/notification/firebase_notification_manger.dart b/lib/controllers/notification/firebase_notification_manger.dart
index 9866be33..a39438cd 100644
--- a/lib/controllers/notification/firebase_notification_manger.dart
+++ b/lib/controllers/notification/firebase_notification_manger.dart
@@ -6,16 +6,29 @@ import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/material.dart';
import 'package:google_api_availability/google_api_availability.dart';
import 'package:huawei_push/huawei_push.dart' as h_push;
+import 'package:provider/provider.dart';
import 'package:test_sa/controllers/notification/notification_manger.dart';
+import 'package:test_sa/controllers/providers/api/notifications_provider.dart';
+import 'package:test_sa/models/all_requests_and_count_model.dart';
import 'package:test_sa/models/device/device_transfer.dart';
import 'package:test_sa/models/new_models/gas_refill_model.dart';
+import 'package:test_sa/modules/asset_delivery_module/pages/asset_delivery_page.dart';
+import 'package:test_sa/modules/asset_inventory_module/pages/asset_inventory_detail_view.dart';
+import 'package:test_sa/modules/asset_inventory_module/pages/asset_inventory_page.dart';
import 'package:test_sa/modules/cm_module/cm_detail_page.dart';
import 'package:test_sa/modules/cx_module/survey/survey_page.dart';
+import 'package:test_sa/modules/demo_module/demo_detail_view_page.dart';
+import 'package:test_sa/modules/incident_module/incident_detail_page.dart';
+import 'package:test_sa/modules/internal_audit_module/pages/equipment_internal_audit/equipment_internal_audit_detail_page.dart';
+import 'package:test_sa/modules/internal_audit_module/pages/system_internal_audit/system_internal_audit_detail_page.dart';
+import 'package:test_sa/modules/loan_module/pages/loan_equipment_detail_page.dart';
+import 'package:test_sa/modules/medical_gas_inspection/medical_gas_inspection_detail_page.dart';
import 'package:test_sa/modules/pm_module/ppm_wo/ppm_details_page.dart';
import 'package:test_sa/modules/pm_module/recurrent_wo/recurrent_work_order_view.dart';
import 'package:test_sa/modules/tm_module/device_transfer/device_transfer_details.dart';
import 'package:test_sa/modules/tm_module/tasks/task_request_detail_view.dart';
import 'package:test_sa/modules/tm_module/gas_refill/gas_refill_details.dart';
+import 'package:test_sa/modules/traf_module/traf_request_detail_page.dart';
import 'package:test_sa/new_views/common_widgets/default_app_bar.dart';
import 'package:test_sa/views/widgets/loaders/no_data_found.dart';
@@ -90,6 +103,7 @@ class FirebaseNotificationManger {
Widget? serviceClass;
String? transactionType = messageData["transactionType"]?.toString();
+ bool IsOldNotification = messageData["IsOldNotification"]?.toString() == null;
if (transactionType == null) {
return;
@@ -129,69 +143,108 @@ class FirebaseNotificationManger {
return;
}
- // PPM=1,
- // ServiceRequestEngineer = 3,
- // AssetTransfer=7,
- // SparePartTransaction= 8,
- // GasRefill=9,
- // TechnicalRetirmentWO = 11,
- // Recurrent = 12,
- switch (transactionType) {
- case "1":
- serviceClass = PpmDetailsPage(requestId: int.parse(messageData["requestNumber"].toString()));
- break;
- //these three request are same corrective maintenance....
- case "3":
- serviceClass = CMDetailPage(requestId: int.parse(messageData["requestNumber"].toString()), moduleId: 1);
- break;
- case "8":
- serviceClass = CMDetailPage(requestId: int.parse(messageData["requestNumber"].toString()), moduleId: 1);
- break;
- case "11":
- serviceClass = CMDetailPage(requestId: int.parse(messageData["requestNumber"].toString()), moduleId: 1);
- break;
- case "7":
- serviceClass = DeviceTransferDetails(model: DeviceTransfer(id: int.parse(messageData["requestNumber"].toString())), moduleId: 3);
- break;
- case "9":
- serviceClass = GasRefillDetailsPage(
- priority: messageData["priority"],
- date: messageData["createdOn"],
- moduleId: 2,
- model: GasRefillModel(id: int.parse(messageData["requestNumber"].toString())),
- );
- break;
- case "12":
- serviceClass = RecurrentWorkOrderView(taskId: int.parse(messageData["requestNumber"].toString()));
- case "17":
- serviceClass = SurveyPage(surveyId: int.parse(messageData["requestNumber"].toString()));
+ if (IsOldNotification) {
+ switch (transactionType) {
+ case "1":
+ serviceClass = PpmDetailsPage(requestId: int.parse(messageData["requestNumber"].toString()));
+ break;
+ case "3":
+ serviceClass = CMDetailPage(requestId: int.parse(messageData["requestNumber"].toString()), moduleId: 1);
+ break;
+ case "8":
+ serviceClass = CMDetailPage(requestId: int.parse(messageData["requestNumber"].toString()), moduleId: 1);
+ break;
+ case "11":
+ serviceClass = CMDetailPage(requestId: int.parse(messageData["requestNumber"].toString()), moduleId: 1);
+ break;
+ case "7":
+ serviceClass = DeviceTransferDetails(model: DeviceTransfer(id: int.parse(messageData["requestNumber"].toString())), moduleId: 3);
+ break;
+ case "9":
+ serviceClass = GasRefillDetailsPage(
+ priority: messageData["priority"],
+ date: messageData["createdOn"],
+ moduleId: 2,
+ model: GasRefillModel(id: int.parse(messageData["requestNumber"].toString())),
+ );
+ break;
+ case "12":
+ serviceClass = RecurrentWorkOrderView(taskId: int.parse(messageData["requestNumber"].toString()));
+ case "17":
+ serviceClass = SurveyPage(surveyId: int.parse(messageData["requestNumber"].toString()));
+
+ default:
+ serviceClass = const Scaffold(appBar: DefaultAppBar(), body: Center(child: NoDataFound()));
+ }
+ } else {
+ switch (transactionType) {
+ case "1":
+ serviceClass = CMDetailPage(requestId: int.parse(messageData["requestNumber"].toString()), moduleId: 1);
+ break;
+ case "2":
+ serviceClass = GasRefillDetailsPage(
+ priority: messageData["priority"],
+ date: messageData["createdOn"],
+ moduleId: 2,
+ model: GasRefillModel(id: int.parse(messageData["requestNumber"].toString())),
+ );
+ break;
+ case "3":
+ serviceClass = DeviceTransferDetails(model: DeviceTransfer(id: int.parse(messageData["requestNumber"].toString())), moduleId: 3);
+ break;
+ case "4":
+ serviceClass = PpmDetailsPage(requestId: int.parse(messageData["requestNumber"].toString()));
+ break;
+ case "5":
+ serviceClass = RecurrentWorkOrderView(taskId: int.parse(messageData["requestNumber"].toString()));
+ break;
+ case "6":
+ serviceClass = TaskRequestDetailsView(
+ taskId: int.parse(messageData["requestNumber"].toString()),
+ requestDetails: RequestsDetails(nameOfType: messageData["sourceName"], status: messageData["statusName"], priority: messageData["priorityName"], date: messageData["createdDate"]),
+ moduleId: 6);
+ break;
+ case "7":
+ serviceClass = TaskRequestDetailsView(
+ taskId: int.parse(messageData["requestNumber"].toString()),
+ requestDetails: RequestsDetails(nameOfType: messageData["sourceName"], status: messageData["statusName"], priority: messageData["priorityName"], date: messageData["createdDate"]),
+ moduleId: 7);
+ break;
+ case "8":
+ serviceClass = AssetInventoryPage(sessionId: int.parse(messageData["requestNumber"].toString()), moduleId: int.parse(transactionType));
+ break;
+ case "9":
+ serviceClass = TrafRequestDetailPage(trafId: int.parse(messageData["requestNumber"].toString()));
+ break;
+ case "10":
+ serviceClass = EquipmentInternalAuditDetailPage(auditId: int.parse(messageData["requestNumber"].toString()));
+ break;
+ case "11":
+ serviceClass = SystemInternalAuditDetailPage(auditId: int.parse(messageData["requestNumber"].toString()));
+ break;
+ case "12":
+ serviceClass = LoanEquipmentDetailPage(loanId: int.parse(messageData["requestNumber"].toString()));
+ break;
+ case "13":
+ serviceClass = AssetDeliveryPage(requestId: int.parse(messageData["requestNumber"].toString()));
+ break;
+ case "14":
+ serviceClass = MedicalGasInspectionDetailPage(inspectionId: int.parse(messageData["requestNumber"].toString()), prority: messageData["priorityName"]);
+ break;
+ case "15":
+ serviceClass = DemoDetailViewPage(demoId: int.parse(messageData["requestNumber"].toString()), moduleId: 15);
+ break;
+ case "16":
+ serviceClass = IncidentDetailPage(incidentId: int.parse(messageData["requestNumber"].toString()));
+ break;
+ case "17":
+ serviceClass = SurveyPage(surveyId: int.parse(messageData["requestNumber"].toString()));
- //Didn't handle task request yet...
- // case 6:
- // serviceClass = TaskRequestDetailsView(
- // taskId: int.parse(messageData["requestNumber"].toString()),
- // requestDetails: RequestsDetails(nameOfType: messageData["sourceName"], status: messageData["statusName"], priority: messageData["priorityName"], date: messageData["createdDate"]));
- // return;
- default:
- serviceClass = const Scaffold(appBar: DefaultAppBar(), body: Center(child: NoDataFound()));
+ default:
+ serviceClass = const Scaffold(appBar: DefaultAppBar(), body: Center(child: NoDataFound()));
+ }
}
- // if (messageData["requestType"] == "Service request to engineer") {
- // serviceClass = ServiceRequestDetailMain(requestId: messageData["requestNumber"] ?? '');
- // } else if (messageData["requestType"] == "Gas Refill") {
- // serviceClass = GasRefillDetailsPage(
- // priority: messageData["priority"],
- // date: messageData["createdOn"],
- // model: GasRefillModel(id: int.parse(messageData["requestNumber"].toString())),
- // );
- // } else if (messageData["requestType"] == "Asset Transfer") {
- // serviceClass = DeviceTransferDetails(model: DeviceTransfer(id: int.parse(messageData["requestNumber"].toString())));
- // } else if (messageData["requestType"] == "PPM") {
- // serviceClass = PpmDetailsPage(requestId: int.parse(messageData["requestNumber"].toString()));
- // }
- // else if (data["requestType"] == "WorkOrder") {
- //
- // }
Navigator.of(context).push(MaterialPageRoute(builder: (_) => serviceClass!));
}
}
@@ -241,7 +294,7 @@ class FirebaseNotificationManger {
}
await FirebaseMessaging.instance.setAutoInitEnabled(true);
- await FirebaseMessaging.instance.setForegroundNotificationPresentationOptions(alert: true, badge: true, sound: true);
+ await FirebaseMessaging.instance.setForegroundNotificationPresentationOptions(alert: true, badge: false, sound: true);
FirebaseMessaging.instance.getInitialMessage().then((initialMessage) {
if (initialMessage != null) {
@@ -252,8 +305,20 @@ class FirebaseNotificationManger {
FirebaseMessaging.onMessage.listen((RemoteMessage message) {
if (Platform.isAndroid) {
if (message.data["notificationType"] != 'NurseConfirmArrive') {
- NotificationManger.showNotification(
- title: message.notification?.title ?? "", subtext: message.notification?.body ?? "", hashcode: int.tryParse("1234" ?? "") ?? 1, payload: json.encode(message.data), context: context);
+ // Extract title and body from notification field first, fallback to data field
+ final String title = message.notification?.title ?? message.data['title'] ?? 'ATOMS';
+ final String body = message.notification?.body ?? message.data['body'] ?? 'New notification';
+
+ // Always show with our grouping logic
+ NotificationManger.showNotification(title: title, subtext: body, hashcode: DateTime.now().millisecondsSinceEpoch.hashCode, payload: json.encode(message.data), context: context);
+
+ // Update unread notification count badge when new notification arrives
+ try {
+ final notificationsProvider = Provider.of(context, listen: false);
+ notificationsProvider.getUnreadCount();
+ } catch (e) {
+ log('Error updating notification count: $e');
+ }
}
}
return;
diff --git a/lib/controllers/notification/notification_manger.dart b/lib/controllers/notification/notification_manger.dart
index ab1df81a..af91ccab 100644
--- a/lib/controllers/notification/notification_manger.dart
+++ b/lib/controllers/notification/notification_manger.dart
@@ -1,3 +1,4 @@
+import 'dart:convert';
import 'dart:io';
import 'package:flutter/foundation.dart';
@@ -43,46 +44,88 @@ class NotificationManger {
await localNotificationsPlugin.initialize(initializationSettings, onDidReceiveNotificationResponse: onNotificationPressed);
} // push new notification
- static const AndroidNotificationDetails androidNotificationDetails = AndroidNotificationDetails(
- 'com.hmg.atoms',
- 'ATOMS',
- channelDescription: 'Push notification service for ATOMS',
- importance: Importance.max,
- priority: Priority.max,
- icon: "@drawable/ic_stat_name",
- playSound: true,
- channelShowBadge: true,
- visibility: NotificationVisibility.public,
- enableVibration: true,
- groupKey: 'com.hmg.atoms',
- );
-
- static const DarwinNotificationDetails iosNotificationDetails = DarwinNotificationDetails(
- categoryIdentifier: "atoms",
- );
+ static AndroidNotificationDetails androidNotificationDetails(String? groupId, String? channelId) => AndroidNotificationDetails(
+ channelId ?? 'com.hmg.atoms',
+ 'ATOMS',
+ channelDescription: 'Push notification service for ATOMS',
+ importance: Importance.max,
+ priority: Priority.max,
+ icon: "@drawable/ic_stat_name",
+ playSound: true,
+ channelShowBadge: true,
+ visibility: NotificationVisibility.public,
+ enableVibration: true,
+ groupKey: groupId ?? 'com.hmg.atoms',
+ setAsGroupSummary: false,
+ );
+
+ static DarwinNotificationDetails iosNotificationDetails(String? threadId) => DarwinNotificationDetails(categoryIdentifier: "atoms", threadIdentifier: threadId);
static Future showNotification({required BuildContext context, required String title, required String subtext, required int hashcode, String? payload}) async {
- // const AndroidNotificationDetails androidChannel = AndroidNotificationDetails(
- // 'com.hmg.atoms',
- // 'ATOMS',
- // channelDescription: 'Push notification service for ATOMS',
- // importance: Importance.max,
- // priority: Priority.max,
- // icon: "@drawable/ic_stat_name",
- // playSound: true,
- // channelShowBadge: true,
- // visibility: NotificationVisibility.public,
- // enableVibration: true,
- // groupKey: 'com.hmg.atoms',
- // );
-
- // const DarwinNotificationDetails iosNotificationDetails = DarwinNotificationDetails(
- // categoryIdentifier: "atoms",
- // );
-
- const NotificationDetails platformChannel = NotificationDetails(android: androidNotificationDetails, iOS: iosNotificationDetails, macOS: iosNotificationDetails);
+ var dataPayload = json.decode(payload ?? "{}");
+ String? groupId = dataPayload["group_id"];
+ NotificationDetails platformChannel = NotificationDetails(android: androidNotificationDetails(groupId, groupId), iOS: iosNotificationDetails(groupId), macOS: iosNotificationDetails(groupId));
await localNotificationsPlugin.show(hashcode, title, subtext, platformChannel, payload: payload);
+
+ // If there's a group_id, also show/update the summary notification
+ if (groupId != null && Platform.isAndroid) {
+ await _showGroupSummary(groupId);
+ }
+ }
+
+ static final Map _groupCounts = {};
+
+ static Future _showGroupSummary(String groupId) async {
+ // Query actual active notifications from the system
+ final androidPlugin = localNotificationsPlugin.resolvePlatformSpecificImplementation();
+
+ final List? activeNotifications = await androidPlugin?.getActiveNotifications();
+
+ // Count notifications that actually exist in this group (exclude summary itself)
+ int count = 0;
+ if (activeNotifications != null) {
+ count = activeNotifications.where((notification) {
+ return notification.groupKey == groupId && notification.id != groupId.hashCode; // Exclude summary notification
+ }).length;
+ }
+
+ // Fallback to in-memory count if query fails (for older Android versions)
+ if (count == 0) {
+ _groupCounts[groupId] = (_groupCounts[groupId] ?? 0) + 1;
+ count = _groupCounts[groupId]!;
+ }
+
+ // Only show summary if we have 2+ notifications
+ if (count < 2) return;
+
+ final AndroidNotificationDetails summaryDetails = AndroidNotificationDetails(
+ 'com.hmg.atoms',
+ 'ATOMS',
+ channelDescription: 'Push notification service for ATOMS',
+ importance: Importance.max,
+ priority: Priority.max,
+ icon: "@drawable/ic_stat_name",
+ groupKey: groupId,
+ setAsGroupSummary: true,
+ onlyAlertOnce: true,
+ autoCancel: true,
+ styleInformation: InboxStyleInformation(
+ [],
+ contentTitle: 'ATOMS',
+ summaryText: 'You have $count new notifications',
+ ),
+ );
+
+ // Use group_id's hashCode as the summary notification ID to ensure uniqueness
+ final int summaryId = groupId.hashCode;
+
+ await localNotificationsPlugin.show(
+ summaryId,
+ 'ATOMS',
+ '$count new notifications',
+ NotificationDetails(android: summaryDetails),
+ );
}
static Future scheduleNotification({int? id, String? title, String? body, String? payLoad, required DateTime scheduledNotificationDateTime}) async {
@@ -95,7 +138,7 @@ class NotificationManger {
scheduledNotificationDateTime,
tz.local,
),
- const NotificationDetails(android: androidNotificationDetails, iOS: iosNotificationDetails, macOS: iosNotificationDetails),
+ NotificationDetails(android: androidNotificationDetails(null, null), iOS: iosNotificationDetails(null), macOS: iosNotificationDetails(null)),
androidScheduleMode: AndroidScheduleMode.exactAllowWhileIdle,
uiLocalNotificationDateInterpretation: UILocalNotificationDateInterpretation.absoluteTime);
}
@@ -103,4 +146,34 @@ class NotificationManger {
static Future cancelNotificationById(int notificationId) async {
await localNotificationsPlugin.cancel(notificationId);
}
+
+ /// Show group summary notification (for background isolate)
+ static Future _showGroupSummaryBackground(String groupId, FlutterLocalNotificationsPlugin plugin) async {
+ final AndroidNotificationDetails summaryDetails = AndroidNotificationDetails(
+ 'com.hmg.atoms',
+ 'ATOMS',
+ channelDescription: 'Push notification service for ATOMS',
+ importance: Importance.max,
+ priority: Priority.max,
+ icon: "@drawable/ic_stat_name",
+ groupKey: groupId,
+ setAsGroupSummary: true,
+ autoCancel: true,
+ onlyAlertOnce: true,
+ styleInformation: InboxStyleInformation(
+ [],
+ contentTitle: 'ATOMS',
+ summaryText: 'New notifications',
+ ),
+ );
+
+ final int summaryId = groupId.hashCode;
+
+ await plugin.show(
+ summaryId,
+ 'ATOMS',
+ 'New notifications',
+ NotificationDetails(android: summaryDetails),
+ );
+ }
}
diff --git a/lib/controllers/providers/api/all_requests_provider.dart b/lib/controllers/providers/api/all_requests_provider.dart
index 0a93116b..fe447042 100644
--- a/lib/controllers/providers/api/all_requests_provider.dart
+++ b/lib/controllers/providers/api/all_requests_provider.dart
@@ -137,7 +137,14 @@ class AllRequestsProvider extends ChangeNotifier {
List list = [1, 2, 3, 4];
if (context.userProvider.isAssessor) {
- list = [9, 15];
+ list = [9];
+ context.userProvider.modulesPermissionsList.forEach((module) {
+ if (module.module!.value! == 8 || module.module!.value! == 9 || module.module!.value! == 15) {
+ list.add(module.module!.value!);
+ }
+ });
+ list.add(16);
+ list = list.toSet().toList();
return list;
}
if (context.userProvider.isQualityUser) {
@@ -158,9 +165,8 @@ class AllRequestsProvider extends ChangeNotifier {
list.add(7); // task mod
}
- //TODO uncommit this to enable TRAF in production.
context.userProvider.modulesPermissionsList.forEach((module) {
- if (module.module!.value! == 8 || module.module!.value! == 9) {
+ if (module.module!.value! == 8 || module.module!.value! == 9 || module.module!.value! == 15) {
list.add(module.module!.value!);
}
});
@@ -184,11 +190,10 @@ class AllRequestsProvider extends ChangeNotifier {
// list = [16];
list.add(16);
}
- if ( context.userProvider.isHouseKeeper) {
+ if (context.userProvider.isHouseKeeper) {
list = [5];
}
-
return list;
}
@@ -220,6 +225,8 @@ class AllRequestsProvider extends ChangeNotifier {
if (search?.assetName?.value?.isNotEmpty ?? false) "assetName": search!.assetName!.value,
if (search?.assetNo?.value?.isNotEmpty ?? false) "assetNumber": search!.assetNo!.value,
if (search?.sn?.value?.isNotEmpty ?? false) "assetSerialNo": search!.sn!.value,
+ if (search?.externalPo?.value?.isNotEmpty ?? false) "externalPo": search!.externalPo!.value,
+ if (search?.internalPo?.value?.isNotEmpty ?? false) "internalPo": search!.internalPo!.value,
if (search?.model?.value?.isNotEmpty ?? false) "model": search!.model!.value,
if (search?.manufacture?.value?.isNotEmpty ?? false) "manufacturer": search!.manufacture!.value,
if (search?.startDate != null) "from": search!.startDate!.toIso8601String(),
diff --git a/lib/controllers/providers/api/notifications_provider.dart b/lib/controllers/providers/api/notifications_provider.dart
index 53d97eaa..2909c363 100644
--- a/lib/controllers/providers/api/notifications_provider.dart
+++ b/lib/controllers/providers/api/notifications_provider.dart
@@ -1,9 +1,11 @@
import 'dart:convert';
+import 'dart:developer';
import 'package:flutter/cupertino.dart';
import 'package:http/http.dart';
import 'package:test_sa/controllers/api_routes/api_manager.dart';
import 'package:test_sa/controllers/api_routes/urls.dart';
+import 'package:test_sa/models/notification_filter_model.dart';
import 'package:test_sa/models/system_notification_model.dart';
import 'package:test_sa/models/user.dart';
@@ -11,11 +13,24 @@ class NotificationsProvider extends ChangeNotifier {
// number of items call in each request
final pageItemNumber = 12;
+ // Store active filters
+ NotificationFilterModel? activeFilters;
+
+ // Store notification counts from API
+ int totalAllNotifications = 0;
+ int totalNotificationRead = 0;
+ int totalNotificationUnread = 0;
+ int totalRows = 0; // Total number of items available in the database
+
//reset provider data
void reset() {
notifications = [];
nextPage = true;
stateCode = null;
+ totalAllNotifications = 0;
+ totalNotificationRead = 0;
+ totalNotificationUnread = 0;
+ totalRows = 0;
}
// state code of current request to defied error message
@@ -38,28 +53,61 @@ class NotificationsProvider extends ChangeNotifier {
/// return state code if request complete may be 200, 404 or 403
/// for more details check http state manager
/// lib\controllers\http_status_manger\http_status_manger.dart
- Future getSystemNotifications({required User user, bool resetProvider = false}) async {
+ Future getSystemNotifications({
+ required User user,
+ bool resetProvider = false,
+ NotificationFilterModel? filters,
+ }) async {
if (isLoading == true) return -2;
if (resetProvider) {
reset();
}
+
+ // Store filters for pagination
+ if (filters != null) {
+ activeFilters = filters;
+ }
+
isLoading = true;
notifyListeners();
late Response response;
try {
- final Map body = {"pageSize": pageItemNumber, "pageNumber": notifications.length ~/ pageItemNumber + 1, "userId": user.userID};
- response = await ApiManager.instance.post(URLs.getSystemNotifications, body: body);
+ // Base request body with pagination
+ final Map body = {
+ "pageSize": pageItemNumber,
+ "pageNumber": notifications.length ~/ pageItemNumber + 1,
+ };
+
+ // Add filter parameters only if they have values
+ if (activeFilters != null) {
+ body.addAll(activeFilters!.toApiBody());
+ }
+ // Don't add null values if no filters are active - just send pagination params only
+
+ response = await ApiManager.instance.post(URLs.getNotificationInbox, body: body);
+
+ // OLD API - Commented out but kept for reference
+ // final Map body = {"pageSize": pageItemNumber, "pageNumber": notifications.length ~/ pageItemNumber + 1, "userId": user.userID};
+ // response = await ApiManager.instance.post(URLs.getSystemNotifications, body: body);
stateCode = response.statusCode;
if (response.statusCode >= 200 && response.statusCode < 300) {
// client's request was successfully received
- List requestsListJson = json.decode(response.body)["data"];
+ final responseData = json.decode(response.body);
+ List requestsListJson = responseData["data"];
List serviceRequestsPage = requestsListJson.map((request) => SystemNotificationModel.fromJson(request)).toList();
- notifications ??= [];
notifications.addAll(serviceRequestsPage);
- // Update nextPage based on response length
- nextPage = serviceRequestsPage.length == pageItemNumber;
+
+ // Update notification counts from API response
+ totalAllNotifications = responseData["totalAllNotifications"] ?? 0;
+ totalNotificationRead = responseData["totalNotificationRead"] ?? 0;
+ totalNotificationUnread = responseData["totalNotificationUnread"] ?? 0;
+ totalRows = responseData["totalRows"] ?? 0;
+
+ // Update nextPage based on totalRows comparison
+ // If we've loaded all available notifications, there's no next page
+ nextPage = notifications.length < totalRows;
}
isLoading = false;
notifyListeners();
@@ -73,11 +121,115 @@ class NotificationsProvider extends ChangeNotifier {
}
}
- /// return -2 if request in progress
- /// return -1 if error happen when sending request
- /// return state code if request complete may be 200, 404 or 403
- /// for more details check http state manager
- /// lib\controllers\http_status_manger\http_status_manger.dart
+ Future