From afc1e9c77a21ece158af7b04b6a42e822fa0b18d Mon Sep 17 00:00:00 2001 From: WaseemAbbasi22 <50428976+WaseemAbbasi22@users.noreply.github.com> Date: Thu, 25 Jun 2026 16:54:49 +0300 Subject: [PATCH] notification implementation completed --- lib/controllers/api_routes/urls.dart | 4 +- .../providers/api/notifications_provider.dart | 39 +++++++++- lib/models/module_with_submodules_model.dart | 1 + lib/models/system_notification_model.dart | 8 +- .../notification_filter_page.dart | 75 ------------------- .../notifications/notifications_page.dart | 74 +++++++----------- 6 files changed, 72 insertions(+), 129 deletions(-) diff --git a/lib/controllers/api_routes/urls.dart b/lib/controllers/api_routes/urls.dart index 77f8ad95..b7e7e519 100644 --- a/lib/controllers/api_routes/urls.dart +++ b/lib/controllers/api_routes/urls.dart @@ -242,9 +242,9 @@ class URLs { static get markOneNotificationAsRead => "$_baseUrl/NotificationNew/MarkAsReadOneNotification"; // mark one notification as read // Notification Filter Lookups - static get getNotificationPriorityLookup => "$_baseUrl/Lookups/GetLookup?lookupEnum=5022"; // notification priority filter + 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=5021"; // notification recipient 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 diff --git a/lib/controllers/providers/api/notifications_provider.dart b/lib/controllers/providers/api/notifications_provider.dart index 49422655..6da2120d 100644 --- a/lib/controllers/providers/api/notifications_provider.dart +++ b/lib/controllers/providers/api/notifications_provider.dart @@ -15,11 +15,21 @@ class NotificationsProvider extends ChangeNotifier { // 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 @@ -83,11 +93,20 @@ class NotificationsProvider extends ChangeNotifier { 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.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(); @@ -113,6 +132,11 @@ class NotificationsProvider extends ChangeNotifier { for (var notification in notifications) { notification.readed = true; } + + // Update counts: Move all unread to read + totalNotificationRead = totalNotificationRead + totalNotificationUnread; + totalNotificationUnread = 0; + notifyListeners(); // Parse response to get message @@ -144,8 +168,15 @@ class NotificationsProvider extends ChangeNotifier { if (response.statusCode >= 200 && response.statusCode < 300) { // Update the specific notification in the local list to read status final index = notifications.indexWhere((n) => n.id == notificationId); - if (index != -1) { + if (index != -1 && notifications[index].readed == false) { notifications[index].readed = true; + + // Update counts: Move one from unread to read + if (totalNotificationUnread > 0) { + totalNotificationUnread--; + totalNotificationRead++; + } + notifyListeners(); } return true; diff --git a/lib/models/module_with_submodules_model.dart b/lib/models/module_with_submodules_model.dart index 1b215c8b..24dc3808 100644 --- a/lib/models/module_with_submodules_model.dart +++ b/lib/models/module_with_submodules_model.dart @@ -13,6 +13,7 @@ class ModuleWithSubModulesModel extends Base { ModuleWithSubModulesModel.fromJson(Map json) { id = json['id']; + identifier = id?.toString(); name = json['name']; if (json['subModules'] != null) { subModules = []; diff --git a/lib/models/system_notification_model.dart b/lib/models/system_notification_model.dart index 041a8780..bb64637d 100644 --- a/lib/models/system_notification_model.dart +++ b/lib/models/system_notification_model.dart @@ -22,6 +22,7 @@ class SystemNotificationModel { bool? sent; String? sentDate; int? noOfFailure; + int? totalUnreadCount; // Added from API payload SystemNotificationModel( {this.userId, @@ -44,7 +45,8 @@ class SystemNotificationModel { this.channelName, this.sent, this.sentDate, - this.noOfFailure}); + this.noOfFailure, + this.totalUnreadCount}); SystemNotificationModel.fromJson(Map? json) { // Allow json to be null @@ -73,7 +75,7 @@ class SystemNotificationModel { sent = json['sent']; sentDate = json['sentDate']; noOfFailure = json['noOfFailure']; - + totalUnreadCount = json['totalUnreadCount']; } } @@ -99,6 +101,7 @@ class SystemNotificationModel { data['sent'] = sent; data['sentDate'] = sentDate; data['noOfFailure'] = noOfFailure; + data['totalUnreadCount'] = totalUnreadCount; return data; } @@ -125,6 +128,7 @@ class SystemNotificationModel { data['sent'] = sent; data['sentDate'] = sentDate; data['noOfFailure'] = noOfFailure; + data['totalUnreadCount'] = totalUnreadCount; return data; } } \ No newline at end of file diff --git a/lib/views/pages/user/notifications/notification_filter_page.dart b/lib/views/pages/user/notifications/notification_filter_page.dart index fe5e1cd3..334f94a6 100644 --- a/lib/views/pages/user/notifications/notification_filter_page.dart +++ b/lib/views/pages/user/notifications/notification_filter_page.dart @@ -16,8 +16,6 @@ import 'package:test_sa/new_views/common_widgets/single_item_drop_down_menu.dart import 'package:test_sa/providers/loading_list_notifier.dart'; import 'package:test_sa/providers/lookups/modules_with_submodules_provider.dart'; import 'package:test_sa/providers/lookups/notification_priority_lookup_provider.dart'; -import 'package:test_sa/providers/lookups/notification_channel_lookup_provider.dart'; -import 'package:test_sa/providers/lookups/notification_recipient_lookup_provider.dart'; import 'package:test_sa/views/widgets/date_and_time/date_picker.dart'; import 'package:test_sa/views/widgets/item_views/info_header_widget.dart'; @@ -34,8 +32,6 @@ class NotificationFilterPage extends StatefulWidget { class _NotificationFilterPageState extends State { NotificationFilterModel? filter; Lookup? selectedPriority; - Lookup? selectedChannel; - Lookup? selectedRecipient; ModuleWithSubModulesModel? selectedModule; SubModuleModel? selectedSubModule; @@ -54,15 +50,11 @@ class _NotificationFilterPageState extends State { Future _loadProvidersAndRestoreValues() async { // Get providers final priorityProvider = context.read(); - final channelProvider = context.read(); - final recipientProvider = context.read(); final moduleProvider = context.read(); // Load data from providers if not already loaded final futures = []; if (priorityProvider.items.isEmpty) futures.add(priorityProvider.getData()); - if (channelProvider.items.isEmpty) futures.add(channelProvider.getData()); - if (recipientProvider.items.isEmpty) futures.add(recipientProvider.getData()); if (moduleProvider.items.isEmpty) futures.add(moduleProvider.getData()); // Wait for all providers to load @@ -81,26 +73,6 @@ class _NotificationFilterPageState extends State { if (selectedPriority?.id == null) selectedPriority = null; } - // Restore channel - if (filter?.channelIds?.isNotEmpty ?? false) { - final channelId = filter!.channelIds!.first; - selectedChannel = channelProvider.items.firstWhere( - (item) => item.id == channelId, - orElse: () => Lookup(), - ); - if (selectedChannel?.id == null) selectedChannel = null; - } - - // Restore recipient - if (filter?.recipientIds?.isNotEmpty ?? false) { - final recipientId = filter!.recipientIds!.first; - selectedRecipient = recipientProvider.items.firstWhere( - (item) => item.id == recipientId, - orElse: () => Lookup(), - ); - if (selectedRecipient?.id == null) selectedRecipient = null; - } - // Restore module if (filter?.moduleIds?.isNotEmpty ?? false) { final moduleId = filter!.moduleIds!.first; @@ -136,8 +108,6 @@ class _NotificationFilterPageState extends State { setState(() { filter?.clear(); selectedPriority = null; - selectedChannel = null; - selectedRecipient = null; selectedModule = null; selectedSubModule = null; }); @@ -183,51 +153,6 @@ class _NotificationFilterPageState extends State { 16.height, - // Filter by Channel - InfoHeader16Widget("Channel"), - 8.height, - SingleItemDropDownMenu( - context: context, - showAsBottomSheet: true, - showShadow: false, - title: "Select Channel", - initialValue: selectedChannel, - onSelect: (value) { - setState(() { - selectedChannel = value; - if (value != null) { - filter?.channelIds = [value.id!]; - } else { - filter?.channelIds?.clear(); - } - }); - }, - ), - - 16.height, - InfoHeader16Widget("Recipient"), - 8.height, - SingleItemDropDownMenu( - context: context, - showAsBottomSheet: true, - backgroundColor: AppColor.neutral100, - showShadow: false, - title: "Select Recipient", - initialValue: selectedRecipient, - onSelect: (value) { - setState(() { - selectedRecipient = value; - if (value != null) { - filter?.recipientIds = [value.id!]; - } else { - filter?.recipientIds?.clear(); - } - }); - }, - ), - - 16.height, - // Filter by Module InfoHeader16Widget("Module"), 8.height, diff --git a/lib/views/pages/user/notifications/notifications_page.dart b/lib/views/pages/user/notifications/notifications_page.dart index 4ffc2c11..c1207f19 100644 --- a/lib/views/pages/user/notifications/notifications_page.dart +++ b/lib/views/pages/user/notifications/notifications_page.dart @@ -10,11 +10,13 @@ import 'package:test_sa/extensions/widget_extensions.dart'; import 'package:test_sa/models/notification_filter_model.dart'; import 'package:test_sa/models/system_notification_model.dart'; import 'package:test_sa/new_views/app_style/app_color.dart'; +import 'package:test_sa/new_views/common_widgets/app_lazy_loading.dart'; import 'package:test_sa/new_views/common_widgets/app_text_form_field.dart'; import 'package:test_sa/new_views/common_widgets/default_app_bar.dart'; import 'package:test_sa/views/pages/user/notifications/notification_filter_page.dart'; import 'package:test_sa/views/pages/user/notifications/notifications_list.dart'; import 'package:test_sa/views/pages/user/notifications/widgets/notification_tab_button.dart'; +import 'package:test_sa/views/widgets/loaders/app_loading.dart'; import 'dart:async'; import '../../../widgets/loaders/loading_manager.dart'; @@ -108,15 +110,15 @@ class _NotificationsPageState extends State with TickerProvid } int get _unreadCount { - return _notificationsProvider.notifications.where((n) => !(n.readed ?? false)).length; + return _notificationsProvider.totalNotificationUnread; } int get _allCount { - return _notificationsProvider.notifications.length; + return _notificationsProvider.totalAllNotifications; } int get _readCount { - return _notificationsProvider.notifications.where((n) => n.readed ?? false).length; + return _notificationsProvider.totalNotificationRead; } /// Get count of active filters @@ -125,8 +127,6 @@ class _NotificationsPageState extends State with TickerProvid int count = 0; if (_activeFilters!.priorityIds?.isNotEmpty ?? false) count++; - if (_activeFilters!.channelIds?.isNotEmpty ?? false) count++; - if (_activeFilters!.recipientIds?.isNotEmpty ?? false) count++; if (_activeFilters!.moduleIds?.isNotEmpty ?? false) count++; if (_activeFilters!.subModuleIds?.isNotEmpty ?? false) count++; if (_activeFilters!.startDate != null) count++; @@ -145,34 +145,26 @@ class _NotificationsPageState extends State with TickerProvid appBar: DefaultAppBar( title: context.translation.notifications, actions: [ - // Mark All As Read Button TextButton.icon( - onPressed: () async { - // Show loading dialog - showDialog( - context: context, - barrierDismissible: false, - builder: (context) => const Center( - child: CircularProgressIndicator(), - ), - ); - - // Call mark all as read API - final result = await _notificationsProvider.markAllAsRead( - userId: _userProvider.user!.userID!, - ); - - // Close loading dialog - if (context.mounted) { - Navigator.of(context).pop(); - } - - // Show API response message only if provided - if (context.mounted && result['message'] != null) { - result['message'].addTranslation.showToast; - } - }, - icon: 'Icon_mark_read'.toSvgAsset(width: 12, height: 12), + onPressed: _unreadCount > 0 + ? () async { + showDialog(context: context, barrierDismissible: false, builder: (context) => const AppLazyLoading()); + final result = await _notificationsProvider.markAllAsRead( + userId: _userProvider.user!.userID!, + ); + if (context.mounted) { + Navigator.of(context).pop(); + } + if (context.mounted && result['message'] != null) { + '${result['message']}'.showToast; + } + } + : null, + icon: 'Icon_mark_read'.toSvgAsset( + width: 12, + height: 12, + color: AppColor.primary10, + ), label: Text( 'Mark All as Read', style: AppTextStyles.bodyText2.copyWith( @@ -182,7 +174,7 @@ class _NotificationsPageState extends State with TickerProvid ), style: TextButton.styleFrom( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), - foregroundColor: AppColor.primary10, + foregroundColor: _unreadCount > 0 ? AppColor.primary10 : AppColor.neutral30, ), ), ], @@ -214,17 +206,6 @@ class _NotificationsPageState extends State with TickerProvid 16.height, - // Filter Section (Placeholder for future filter/sort buttons) - Container( - padding: const EdgeInsets.symmetric(horizontal: 16), - child: Row( - children: [ - // TODO: Add Filter and Sort buttons here when needed - const Spacer(), - ], - ), - ), - // Notification Tabs Row( children: [ @@ -310,7 +291,7 @@ class _NotificationsPageState extends State with TickerProvid if (_activeFilterCount > 0) Positioned( right: -8, - top: -8, + top: -12, child: Container( padding: const EdgeInsets.symmetric(horizontal: 5, vertical: 2), decoration: BoxDecoration( @@ -347,7 +328,8 @@ class _NotificationsPageState extends State with TickerProvid Expanded( child: LoadingManager( isLoading: (_notificationsProvider.isLoading) && _notificationsProvider.notifications.isEmpty, - isFailedLoading: false, // Don't show error state, show no data found instead + isFailedLoading: false, + // Don't show error state, show no data found instead stateCode: _notificationsProvider.stateCode, onRefresh: () async { _notificationsProvider.reset();