notification implementation completed

ui_ux_rollout_merge_notification_settings
WaseemAbbasi22 6 days ago
parent 9ab4ed6964
commit afc1e9c77a

@ -242,9 +242,9 @@ class URLs {
static get markOneNotificationAsRead => "$_baseUrl/NotificationNew/MarkAsReadOneNotification"; // mark one notification as read static get markOneNotificationAsRead => "$_baseUrl/NotificationNew/MarkAsReadOneNotification"; // mark one notification as read
// Notification Filter Lookups // 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 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 // Modules and SubModules
static get getModulesWithSubModules => "$_baseUrl/Module/GetModulesWithSubModules"; // get modules with submodules for filters static get getModulesWithSubModules => "$_baseUrl/Module/GetModulesWithSubModules"; // get modules with submodules for filters

@ -15,11 +15,21 @@ class NotificationsProvider extends ChangeNotifier {
// Store active filters // Store active filters
NotificationFilterModel? activeFilters; 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 //reset provider data
void reset() { void reset() {
notifications = []; notifications = [];
nextPage = true; nextPage = true;
stateCode = null; stateCode = null;
totalAllNotifications = 0;
totalNotificationRead = 0;
totalNotificationUnread = 0;
totalRows = 0;
} }
// state code of current request to defied error message // state code of current request to defied error message
@ -83,11 +93,20 @@ class NotificationsProvider extends ChangeNotifier {
stateCode = response.statusCode; stateCode = response.statusCode;
if (response.statusCode >= 200 && response.statusCode < 300) { if (response.statusCode >= 200 && response.statusCode < 300) {
// client's request was successfully received // client's request was successfully received
List requestsListJson = json.decode(response.body)["data"]; final responseData = json.decode(response.body);
List requestsListJson = responseData["data"];
List<SystemNotificationModel> serviceRequestsPage = requestsListJson.map((request) => SystemNotificationModel.fromJson(request)).toList(); List<SystemNotificationModel> serviceRequestsPage = requestsListJson.map((request) => SystemNotificationModel.fromJson(request)).toList();
notifications.addAll(serviceRequestsPage); 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; isLoading = false;
notifyListeners(); notifyListeners();
@ -113,6 +132,11 @@ class NotificationsProvider extends ChangeNotifier {
for (var notification in notifications) { for (var notification in notifications) {
notification.readed = true; notification.readed = true;
} }
// Update counts: Move all unread to read
totalNotificationRead = totalNotificationRead + totalNotificationUnread;
totalNotificationUnread = 0;
notifyListeners(); notifyListeners();
// Parse response to get message // Parse response to get message
@ -144,8 +168,15 @@ class NotificationsProvider extends ChangeNotifier {
if (response.statusCode >= 200 && response.statusCode < 300) { if (response.statusCode >= 200 && response.statusCode < 300) {
// Update the specific notification in the local list to read status // Update the specific notification in the local list to read status
final index = notifications.indexWhere((n) => n.id == notificationId); final index = notifications.indexWhere((n) => n.id == notificationId);
if (index != -1) { if (index != -1 && notifications[index].readed == false) {
notifications[index].readed = true; notifications[index].readed = true;
// Update counts: Move one from unread to read
if (totalNotificationUnread > 0) {
totalNotificationUnread--;
totalNotificationRead++;
}
notifyListeners(); notifyListeners();
} }
return true; return true;

@ -13,6 +13,7 @@ class ModuleWithSubModulesModel extends Base {
ModuleWithSubModulesModel.fromJson(Map<String, dynamic> json) { ModuleWithSubModulesModel.fromJson(Map<String, dynamic> json) {
id = json['id']; id = json['id'];
identifier = id?.toString();
name = json['name']; name = json['name'];
if (json['subModules'] != null) { if (json['subModules'] != null) {
subModules = <SubModuleModel>[]; subModules = <SubModuleModel>[];

@ -22,6 +22,7 @@ class SystemNotificationModel {
bool? sent; bool? sent;
String? sentDate; String? sentDate;
int? noOfFailure; int? noOfFailure;
int? totalUnreadCount; // Added from API payload
SystemNotificationModel( SystemNotificationModel(
{this.userId, {this.userId,
@ -44,7 +45,8 @@ class SystemNotificationModel {
this.channelName, this.channelName,
this.sent, this.sent,
this.sentDate, this.sentDate,
this.noOfFailure}); this.noOfFailure,
this.totalUnreadCount});
SystemNotificationModel.fromJson(Map<String, dynamic>? json) { SystemNotificationModel.fromJson(Map<String, dynamic>? json) {
// Allow json to be null // Allow json to be null
@ -73,7 +75,7 @@ class SystemNotificationModel {
sent = json['sent']; sent = json['sent'];
sentDate = json['sentDate']; sentDate = json['sentDate'];
noOfFailure = json['noOfFailure']; noOfFailure = json['noOfFailure'];
totalUnreadCount = json['totalUnreadCount'];
} }
} }
@ -99,6 +101,7 @@ class SystemNotificationModel {
data['sent'] = sent; data['sent'] = sent;
data['sentDate'] = sentDate; data['sentDate'] = sentDate;
data['noOfFailure'] = noOfFailure; data['noOfFailure'] = noOfFailure;
data['totalUnreadCount'] = totalUnreadCount;
return data; return data;
} }
@ -125,6 +128,7 @@ class SystemNotificationModel {
data['sent'] = sent; data['sent'] = sent;
data['sentDate'] = sentDate; data['sentDate'] = sentDate;
data['noOfFailure'] = noOfFailure; data['noOfFailure'] = noOfFailure;
data['totalUnreadCount'] = totalUnreadCount;
return data; return data;
} }
} }

@ -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/loading_list_notifier.dart';
import 'package:test_sa/providers/lookups/modules_with_submodules_provider.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_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/date_and_time/date_picker.dart';
import 'package:test_sa/views/widgets/item_views/info_header_widget.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<NotificationFilterPage> { class _NotificationFilterPageState extends State<NotificationFilterPage> {
NotificationFilterModel? filter; NotificationFilterModel? filter;
Lookup? selectedPriority; Lookup? selectedPriority;
Lookup? selectedChannel;
Lookup? selectedRecipient;
ModuleWithSubModulesModel? selectedModule; ModuleWithSubModulesModel? selectedModule;
SubModuleModel? selectedSubModule; SubModuleModel? selectedSubModule;
@ -54,15 +50,11 @@ class _NotificationFilterPageState extends State<NotificationFilterPage> {
Future<void> _loadProvidersAndRestoreValues() async { Future<void> _loadProvidersAndRestoreValues() async {
// Get providers // Get providers
final priorityProvider = context.read<NotificationPriorityLookupProvider>(); final priorityProvider = context.read<NotificationPriorityLookupProvider>();
final channelProvider = context.read<NotificationChannelLookupProvider>();
final recipientProvider = context.read<NotificationRecipientLookupProvider>();
final moduleProvider = context.read<ModulesWithSubModulesProvider>(); final moduleProvider = context.read<ModulesWithSubModulesProvider>();
// Load data from providers if not already loaded // Load data from providers if not already loaded
final futures = <Future>[]; final futures = <Future>[];
if (priorityProvider.items.isEmpty) futures.add(priorityProvider.getData()); 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()); if (moduleProvider.items.isEmpty) futures.add(moduleProvider.getData());
// Wait for all providers to load // Wait for all providers to load
@ -81,26 +73,6 @@ class _NotificationFilterPageState extends State<NotificationFilterPage> {
if (selectedPriority?.id == null) selectedPriority = null; 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 // Restore module
if (filter?.moduleIds?.isNotEmpty ?? false) { if (filter?.moduleIds?.isNotEmpty ?? false) {
final moduleId = filter!.moduleIds!.first; final moduleId = filter!.moduleIds!.first;
@ -136,8 +108,6 @@ class _NotificationFilterPageState extends State<NotificationFilterPage> {
setState(() { setState(() {
filter?.clear(); filter?.clear();
selectedPriority = null; selectedPriority = null;
selectedChannel = null;
selectedRecipient = null;
selectedModule = null; selectedModule = null;
selectedSubModule = null; selectedSubModule = null;
}); });
@ -183,51 +153,6 @@ class _NotificationFilterPageState extends State<NotificationFilterPage> {
16.height, 16.height,
// Filter by Channel
InfoHeader16Widget("Channel"),
8.height,
SingleItemDropDownMenu<Lookup, NotificationChannelLookupProvider>(
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<Lookup, NotificationRecipientLookupProvider>(
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 // Filter by Module
InfoHeader16Widget("Module"), InfoHeader16Widget("Module"),
8.height, 8.height,

@ -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/notification_filter_model.dart';
import 'package:test_sa/models/system_notification_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/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/app_text_form_field.dart';
import 'package:test_sa/new_views/common_widgets/default_app_bar.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/notification_filter_page.dart';
import 'package:test_sa/views/pages/user/notifications/notifications_list.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/pages/user/notifications/widgets/notification_tab_button.dart';
import 'package:test_sa/views/widgets/loaders/app_loading.dart';
import 'dart:async'; import 'dart:async';
import '../../../widgets/loaders/loading_manager.dart'; import '../../../widgets/loaders/loading_manager.dart';
@ -108,15 +110,15 @@ class _NotificationsPageState extends State<NotificationsPage> with TickerProvid
} }
int get _unreadCount { int get _unreadCount {
return _notificationsProvider.notifications.where((n) => !(n.readed ?? false)).length; return _notificationsProvider.totalNotificationUnread;
} }
int get _allCount { int get _allCount {
return _notificationsProvider.notifications.length; return _notificationsProvider.totalAllNotifications;
} }
int get _readCount { int get _readCount {
return _notificationsProvider.notifications.where((n) => n.readed ?? false).length; return _notificationsProvider.totalNotificationRead;
} }
/// Get count of active filters /// Get count of active filters
@ -125,8 +127,6 @@ class _NotificationsPageState extends State<NotificationsPage> with TickerProvid
int count = 0; int count = 0;
if (_activeFilters!.priorityIds?.isNotEmpty ?? false) count++; 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!.moduleIds?.isNotEmpty ?? false) count++;
if (_activeFilters!.subModuleIds?.isNotEmpty ?? false) count++; if (_activeFilters!.subModuleIds?.isNotEmpty ?? false) count++;
if (_activeFilters!.startDate != null) count++; if (_activeFilters!.startDate != null) count++;
@ -145,34 +145,26 @@ class _NotificationsPageState extends State<NotificationsPage> with TickerProvid
appBar: DefaultAppBar( appBar: DefaultAppBar(
title: context.translation.notifications, title: context.translation.notifications,
actions: [ actions: [
// Mark All As Read Button
TextButton.icon( TextButton.icon(
onPressed: () async { onPressed: _unreadCount > 0
// Show loading dialog ? () async {
showDialog( showDialog(context: context, barrierDismissible: false, builder: (context) => const AppLazyLoading());
context: context, final result = await _notificationsProvider.markAllAsRead(
barrierDismissible: false, userId: _userProvider.user!.userID!,
builder: (context) => const Center( );
child: CircularProgressIndicator(), if (context.mounted) {
), Navigator.of(context).pop();
); }
if (context.mounted && result['message'] != null) {
// Call mark all as read API '${result['message']}'.showToast;
final result = await _notificationsProvider.markAllAsRead( }
userId: _userProvider.user!.userID!, }
); : null,
icon: 'Icon_mark_read'.toSvgAsset(
// Close loading dialog width: 12,
if (context.mounted) { height: 12,
Navigator.of(context).pop(); color: AppColor.primary10,
} ),
// 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),
label: Text( label: Text(
'Mark All as Read', 'Mark All as Read',
style: AppTextStyles.bodyText2.copyWith( style: AppTextStyles.bodyText2.copyWith(
@ -182,7 +174,7 @@ class _NotificationsPageState extends State<NotificationsPage> with TickerProvid
), ),
style: TextButton.styleFrom( style: TextButton.styleFrom(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), 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<NotificationsPage> with TickerProvid
16.height, 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 // Notification Tabs
Row( Row(
children: [ children: [
@ -310,7 +291,7 @@ class _NotificationsPageState extends State<NotificationsPage> with TickerProvid
if (_activeFilterCount > 0) if (_activeFilterCount > 0)
Positioned( Positioned(
right: -8, right: -8,
top: -8, top: -12,
child: Container( child: Container(
padding: const EdgeInsets.symmetric(horizontal: 5, vertical: 2), padding: const EdgeInsets.symmetric(horizontal: 5, vertical: 2),
decoration: BoxDecoration( decoration: BoxDecoration(
@ -347,7 +328,8 @@ class _NotificationsPageState extends State<NotificationsPage> with TickerProvid
Expanded( Expanded(
child: LoadingManager( child: LoadingManager(
isLoading: (_notificationsProvider.isLoading) && _notificationsProvider.notifications.isEmpty, 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, stateCode: _notificationsProvider.stateCode,
onRefresh: () async { onRefresh: () async {
_notificationsProvider.reset(); _notificationsProvider.reset();

Loading…
Cancel
Save