improvements

ui_ux_rollout_merge_notification_settings
WaseemAbbasi22 7 hours ago
parent afc1e9c77a
commit 37a2be7b78

@ -163,7 +163,7 @@ class NotificationsProvider extends ChangeNotifier {
/// Returns true if successful, false otherwise
Future<bool> markOneAsRead({required String notificationId}) async {
try {
final response = await ApiManager.instance.get("${URLs.markOneNotificationAsRead}?NotificationId=$notificationId");
final response = await ApiManager.instance.get("${URLs.markOneNotificationAsRead}?NotificationId=$notificationId", enableToastMessage: false);
if (response.statusCode >= 200 && response.statusCode < 300) {
// Update the specific notification in the local list to read status

@ -12,6 +12,7 @@ class SystemNotificationModel {
String? createdOn;
String? modifiedOn;
String? priorityName;
int? priorityValue;
String? statusName;
int? transactionType;
String? moduleName;
@ -38,6 +39,7 @@ class SystemNotificationModel {
this.createdOn,
this.modifiedOn,
this.priorityName,
this.priorityValue,
this.transactionType,
this.statusName,
this.moduleName,
@ -65,6 +67,7 @@ class SystemNotificationModel {
createdOn = json['createdDate'] ?? json['createdOn'];
modifiedOn = json['modifiedOn'];
priorityName = json['priorityName'];
priorityValue = json['priorityValue'];
statusName = json['statusName'];
transactionType = json['transactionType'];
moduleName = json['moduleName'];
@ -94,6 +97,7 @@ class SystemNotificationModel {
data['createdOn'] = createdOn;
data['modifiedOn'] = modifiedOn;
data['priorityName'] = priorityName;
data['priorityValue'] = priorityValue;
data['priority'] = priorityName;
data['statusName'] = statusName;
data['transactionType'] = transactionType;
@ -120,6 +124,7 @@ class SystemNotificationModel {
data['createdOn'] = createdOn;
data['modifiedOn'] = modifiedOn;
data['priorityName'] = priorityName;
data['priorityValue'] = priorityValue;
data['statusName'] = statusName;
data['transactionType'] = transactionType;
data['moduleName'] = moduleName;

@ -30,14 +30,14 @@ class NotificationPreferenceCard extends StatelessWidget {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
spacing: 4.toScreenHeight,
children: [
if(isMandatory) ...[
4.height,
Text(
'You cannot update this notification as it is mandatory.'.addTranslation,
style: AppTextStyle.body2.copyWith(
style: AppTextStyle.tiny.copyWith(
color: AppColor.red30,
fontSize: 10.toScreenWidth,
),
),
],
@ -54,27 +54,23 @@ class NotificationPreferenceCard extends StatelessWidget {
_buildCompactToggle(context, isEnabled, isMandatory),
],
),
4.height,
InfoTextWidget(
label: context.translation.notificationModule,
value: template.moduleName ?? '',
),
4.height,
InfoTextWidget(
label: 'Sub Module'.addTranslation,
value: template.subModuleName ?? '',
),
4.height,
if (template.templateChannels != null && template.templateChannels!.isNotEmpty) ...[
InfoTextLabelWidget(
label: '${context.translation.channelPreference} :',
),
8.height,
_buildChannelChips(context, isMandatory),
],
],
).toShadowContainer(context,padding: 16);
).toShadowContainer(context);
}
Widget _buildCompactToggle(BuildContext context, bool isEnabled, bool isMandatory) {

@ -418,6 +418,42 @@ class AppColor {
}
}
//0 critical, 1 high, 2 low, 3 medium,
static Color getNotificationPriorityStatusTextColor(BuildContext context, int id) {
switch (id) {
//low priority
case 2:
return greenStatusColor;
case 3:
return greenStatusTextColor;
case 0:
return redStatusTextColor;
case 1:
return redStatusTextColor;
default:
return defaultStatusTextColor(context);
}
}
static Color getNotificationPriorityStatusColor(BuildContext context, int id) {
switch (id) {
//low priority
case 2:
return greenStatusColor;
case 0:
return redStatusColor;
//high priority
case 1:
return redStatusColor;
case 3:
return greenStatusColor;
default:
return defaultStatusColor(context);
}
}
static Color getPriorityStatusColor(BuildContext context, int id) {
switch (id) {
//low priority

@ -98,7 +98,7 @@ class _NotificationFilterPageState extends State<NotificationFilterPage> {
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: AppColor.scaffold2Background(context),
// backgroundColor: AppColor.scaffold2Background(context),
appBar: DefaultAppBar(
title: context.translation.filter,
actions: [
@ -126,17 +126,16 @@ class _NotificationFilterPageState extends State<NotificationFilterPage> {
body: Column(
children: [
SingleChildScrollView(
padding: const EdgeInsets.all(16),
padding: const EdgeInsets.all(12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// Filter by Priority
InfoHeader16Widget("Priority"),
8.height,
SingleItemDropDownMenu<Lookup, NotificationPriorityLookupProvider>(
context: context,
showAsBottomSheet: true,
showShadow: false,
showAsFullScreenDialog: true,
showShadow: true,
title: "Select Priority",
initialValue: selectedPriority,
onSelect: (value) {
@ -151,15 +150,15 @@ class _NotificationFilterPageState extends State<NotificationFilterPage> {
},
),
16.height,
12.height,
// Filter by Module
InfoHeader16Widget("Module"),
8.height,
SingleItemDropDownMenu<ModuleWithSubModulesModel, ModulesWithSubModulesProvider>(
context: context,
showAsBottomSheet: true,
showShadow: false,
showAsFullScreenDialog: true,
showShadow: true,
title: "Select Module",
initialValue: selectedModule,
onSelect: (value) {
@ -177,15 +176,15 @@ class _NotificationFilterPageState extends State<NotificationFilterPage> {
},
),
16.height,
12.height,
// Filter by SubModule
InfoHeader16Widget( "SubModule"),
InfoHeader16Widget("SubModule"),
8.height,
SingleItemDropDownMenu<SubModuleModel, NullableLoadingProvider>(
context: context,
showAsBottomSheet: true,
showShadow: false,
showAsFullScreenDialog: true,
showShadow: true,
title: "Select SubModule",
initialValue: selectedSubModule,
enabled: selectedModule?.subModules?.isNotEmpty ?? false,
@ -202,10 +201,10 @@ class _NotificationFilterPageState extends State<NotificationFilterPage> {
},
),
16.height,
12.height,
// Date Range Filter
context.translation.pickDate.heading5(context).custom(color: AppColor.textStyleColor(context)),
InfoHeader16Widget(context.translation.pickDate),
8.height,
Row(
children: [
@ -239,7 +238,6 @@ class _NotificationFilterPageState extends State<NotificationFilterPage> {
],
),
).expanded,
16.height,
FooterActionButton.footerContainer(
context: context,
child: AppFilledButton(

@ -5,6 +5,7 @@ import 'package:provider/provider.dart';
import 'package:test_sa/controllers/notification/firebase_notification_manger.dart';
import 'package:test_sa/controllers/providers/api/notifications_provider.dart';
import 'package:test_sa/extensions/context_extension.dart';
import 'package:test_sa/extensions/int_extensions.dart';
import 'package:test_sa/extensions/widget_extensions.dart';
import 'package:test_sa/models/system_notification_model.dart';
import 'package:test_sa/views/pages/user/notifications/widgets/notification_card_redesigned.dart';
@ -27,22 +28,17 @@ class NotificationsList extends StatelessWidget {
nextPage: nextPage,
onLazyLoad: onLazyLoad,
child: ListView.separated(
// padding: const EdgeInsets.all(16),
itemCount: notifications.length,
separatorBuilder: (context, itemIndex) => const SizedBox(height: 12),
separatorBuilder: (context, itemIndex) => 12.height,
itemBuilder: (context, itemIndex) {
final notification = notifications[itemIndex];
return NotificationCardRedesigned(
notification: notification,
onTap: () async {
// Mark notification as read when tapped
log('i am tapped with id ${notification.toJson()}');
if (notification.id != null && !(notification.readed ?? false)) {
final provider = Provider.of<NotificationsProvider>(context, listen: false);
await provider.markOneAsRead(notificationId: notification.id!);
}
// Navigate to notification details
FirebaseNotificationManger.handleMessage(context, notification.toNotificationJson());
},
);

@ -16,7 +16,6 @@ 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';
@ -80,7 +79,6 @@ class _NotificationsPageState extends State<NotificationsPage> with TickerProvid
);
}
/// Handle search with debounce
void _onSearchChanged(String value) {
setState(() {
_searchQuery = value;
@ -141,7 +139,6 @@ class _NotificationsPageState extends State<NotificationsPage> with TickerProvid
_userProvider = Provider.of<UserProvider>(context);
return Scaffold(
backgroundColor: AppColor.scaffold2Background(context),
appBar: DefaultAppBar(
title: context.translation.notifications,
actions: [
@ -181,37 +178,26 @@ class _NotificationsPageState extends State<NotificationsPage> with TickerProvid
),
body: SafeArea(
child: Column(
spacing: 12.toScreenHeight,
crossAxisAlignment: CrossAxisAlignment.end,
children: [
// Search Section
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
8.height,
AppTextFormField(
controller: _searchController,
labelText: context.translation.search,
showShadow: false,
showBorder: true,
prefixIconData: Icons.search,
hintText: 'Search by Title or content'.addTranslation,
hintStyle: AppTextStyles.tinyFont.copyWith(color: AppColor.labelTextStyleColor(context)),
contentPadding: const EdgeInsets.symmetric(vertical: 14, horizontal: 16),
onChange: (value) {
_onSearchChanged(value);
},
),
],
12.height,
AppTextFormField(
controller: _searchController,
labelText: context.translation.search,
showShadow: false,
showBorder: true,
prefixIconData: Icons.search,
onChange: (value) {
_onSearchChanged(value);
},
),
16.height,
// Notification Tabs
// 8.height,
Row(
children: [
Expanded(
child: SizedBox(
height: 35.toScreenHeight,
height: 30.toScreenHeight,
child: ListView.separated(
scrollDirection: Axis.horizontal,
itemCount: 3,
@ -238,9 +224,10 @@ class _NotificationsPageState extends State<NotificationsPage> with TickerProvid
8.width,
// Sort & Filter Container
Container(
height: 35.toScreenHeight,
padding: const EdgeInsets.symmetric(horizontal: 8),
height: 30.toScreenHeight,
padding: const EdgeInsets.symmetric(horizontal: 6),
decoration: BoxDecoration(
boxShadow: [BoxShadow(color: Colors.black.withOpacity(0.05), blurRadius: 14, offset: const Offset(0, 0), spreadRadius: 0)],
color: AppColor.background(context),
borderRadius: BorderRadius.circular(6),
border: Border.all(color: AppColor.borderColor(context)),
@ -287,7 +274,7 @@ class _NotificationsPageState extends State<NotificationsPage> with TickerProvid
_loadNotifications();
}
}),
// Active Filters Badge
// Active Filters Badge we are not using custom badge widget here as it need more customizations
if (_activeFilterCount > 0)
Positioned(
right: -8,
@ -321,15 +308,12 @@ class _NotificationsPageState extends State<NotificationsPage> with TickerProvid
),
],
),
16.height,
// 8.height,
// Notifications List
Expanded(
child: LoadingManager(
isLoading: (_notificationsProvider.isLoading) && _notificationsProvider.notifications.isEmpty,
isFailedLoading: false,
// Don't show error state, show no data found instead
stateCode: _notificationsProvider.stateCode,
onRefresh: () async {
_notificationsProvider.reset();
@ -352,7 +336,7 @@ class _NotificationsPageState extends State<NotificationsPage> with TickerProvid
),
),
],
).paddingOnly(start: 16, end: 16),
).paddingOnly(start: 12, end: 12),
),
);
}

@ -1,11 +1,14 @@
import 'package:flutter/material.dart';
import 'package:test_sa/extensions/int_extensions.dart';
import 'package:test_sa/extensions/string_extensions.dart';
import 'package:test_sa/extensions/text_extensions.dart';
import 'package:test_sa/extensions/widget_extensions.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/views/widgets/item_views/info_date_widget.dart';
import 'package:test_sa/views/widgets/item_views/info_header_widget.dart';
import 'package:test_sa/views/widgets/item_views/info_text_widget.dart';
import 'package:test_sa/views/widgets/requests/request_status.dart';
class NotificationCardRedesigned extends StatelessWidget {
final SystemNotificationModel notification;
@ -20,112 +23,55 @@ class NotificationCardRedesigned extends StatelessWidget {
@override
Widget build(BuildContext context) {
final isUnread = !(notification.readed ?? false);
return Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: AppColor.background(context),
borderRadius: BorderRadius.circular(8),
border: Border.all(color: AppColor.borderColor(context)),
),
child: InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(8),
child: Column(
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
spacing: 4.toScreenHeight,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
if (notification.priorityName != null && notification.priorityValue != null)
StatusLabel(
label: notification.priorityName!,
radius: 4,
textColor: AppColor.getNotificationPriorityStatusTextColor(context, notification.priorityValue!),
backgroundColor: AppColor.getNotificationPriorityStatusColor(context, notification.priorityValue!),
),
if (notification.createdOn?.isNotEmpty ?? false) InfoDateWidget(notification.createdOn ?? ""),
],
),
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Priority Badge & Date Row
Row(
children: [
// Priority Badge
if (notification.priorityName != null && notification.priorityName!.isNotEmpty)
Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: AppColor.getRequestStatusColorByName(
context,
notification.priorityName!,
),
borderRadius: BorderRadius.circular(4),
),
child: Text(
notification.priorityName!,
style: AppTextStyles.tinyFont2.copyWith(
color: AppColor.getRequestStatusTextColorByName(
context,
notification.priorityName!,
),
fontWeight: FontWeight.w600,
),
),
),
const Spacer(),
// Date & Time - Top Right
if (notification.createdOn?.isNotEmpty ?? false)
InfoDateWidget(notification.createdOn ?? ""),
],
),
8.height,
// Title with Unread Dot
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Unread indicator (blue dot) - aligned with title
if (isUnread)
Padding(
padding: const EdgeInsets.only(top: 6, right: 8),
child: Container(
width: 8,
height: 8,
decoration: const BoxDecoration(
color: AppColor.primary10,
shape: BoxShape.circle,
),
),
),
// Title
Expanded(
child: InfoHeader16Widget(
notification.title ?? '',
),
),
],
),
8.height,
if (notification.sourceName?.isNotEmpty ?? false)
Container(
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
decoration: BoxDecoration(
color: AppColor.primary10.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(4),
),
child: Text(
notification.sourceName!,
style: AppTextStyles.tinyFont2.copyWith(
if (isUnread)
Padding(
padding: const EdgeInsets.only(top: 6, right: 8),
child: Container(
width: 8,
height: 8,
decoration: const BoxDecoration(
color: AppColor.primary10,
fontWeight: FontWeight.w500,
shape: BoxShape.circle,
),
),
),
if (notification.sourceName?.isNotEmpty ?? false) 8.height,
// Message Preview
if (notification.text?.isNotEmpty ?? false)
InfoTextLabelWidget(
label: notification.text!,
// style: AppTextStyles.bodyText2.copyWith(
// color: AppColor.textColor(context),
// height: 1.4,
// ),
// maxLines: 3,
// overflow: TextOverflow.ellipsis,
Expanded(
child: InfoHeader16Widget(
notification.title ?? '',
),
),
],
),
),
);
if (notification.sourceName?.isNotEmpty ?? false)
InfoTextWidget(
label: 'Source'.addTranslation,
value: notification.sourceName ?? '',
),
if (notification.text?.isNotEmpty ?? false)
InfoTextLabelWidget(
label: notification.text!,
),
],
).toShadowContainer(context).onPress(onTap);
}
}

@ -22,7 +22,7 @@ class NotificationTabButton extends StatelessWidget {
onTap: onTap,
child: Container(
alignment: Alignment.center,
padding: const EdgeInsets.symmetric(horizontal: 12),
padding: const EdgeInsets.symmetric(horizontal: 6),
decoration: ShapeDecoration(
color: AppColor.background(context),
shape: RoundedRectangleBorder(

Loading…
Cancel
Save