You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
cloudsolutions-atoms/lib/views/pages/user/notifications/notifications_page.dart

360 lines
13 KiB
Dart

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:test_sa/controllers/providers/api/notifications_provider.dart';
import 'package:test_sa/controllers/providers/api/user_provider.dart';
import 'package:test_sa/extensions/context_extension.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/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';
class NotificationsPage extends StatefulWidget {
static const String id = "/notifications";
const NotificationsPage({Key? key}) : super(key: key);
@override
_NotificationsPageState createState() => _NotificationsPageState();
}
class _NotificationsPageState extends State<NotificationsPage> with TickerProviderStateMixin {
late NotificationsProvider _notificationsProvider;
late UserProvider _userProvider;
final TextEditingController _searchController = TextEditingController();
int _selectedTabIndex = 0; // 0: All, 1: Unread, 2: Read
String _searchQuery = '';
bool isAscending = false;
NotificationFilterModel? _activeFilters;
Timer? _debounceTimer;
@override
void initState() {
super.initState();
// Initialize filter model with default isRead = 0 (All)
_activeFilters = NotificationFilterModel(isRead: 0);
// Load notifications when page loads
WidgetsBinding.instance.addPostFrameCallback((_) {
_loadNotifications();
});
}
@override
void dispose() {
_searchController.dispose();
_debounceTimer?.cancel();
super.dispose();
}
/// Load notifications from API
Future<void> _loadNotifications() async {
final notificationsProvider = Provider.of<NotificationsProvider>(context, listen: false);
final userProvider = Provider.of<UserProvider>(context, listen: false);
// Update filter model with search, sort, and tab selection
_activeFilters ??= NotificationFilterModel();
_activeFilters!.searchText = _searchQuery.isEmpty ? null : _searchQuery;
_activeFilters!.isAscending = isAscending;
_activeFilters!.isRead = _selectedTabIndex; // 0 = All, 1 = Unread, 2 = Read
// Reset and fetch fresh data with filters
notificationsProvider.reset();
await notificationsProvider.getSystemNotifications(
user: userProvider.user!,
resetProvider: true,
filters: _activeFilters,
);
}
/// Handle search with debounce
void _onSearchChanged(String value) {
setState(() {
_searchQuery = value;
});
// Cancel previous timer
_debounceTimer?.cancel();
// Start new timer (500ms debounce)
_debounceTimer = Timer(const Duration(milliseconds: 500), () {
_loadNotifications();
});
}
/// Toggle sort order and reload data
void _toggleSort() {
setState(() {
isAscending = !isAscending;
});
_loadNotifications();
}
List<SystemNotificationModel> get _filteredNotifications {
// Tab filtering is now handled by API via isRead parameter
// No need for local filtering
return _notificationsProvider.notifications;
}
int get _unreadCount {
return _notificationsProvider.totalNotificationUnread;
}
int get _allCount {
return _notificationsProvider.totalAllNotifications;
}
int get _readCount {
return _notificationsProvider.totalNotificationRead;
}
/// Get count of active filters
int get _activeFilterCount {
if (_activeFilters == null) return 0;
int count = 0;
if (_activeFilters!.priorityIds?.isNotEmpty ?? false) count++;
if (_activeFilters!.moduleIds?.isNotEmpty ?? false) count++;
if (_activeFilters!.subModuleIds?.isNotEmpty ?? false) count++;
if (_activeFilters!.startDate != null) count++;
if (_activeFilters!.endDate != null) count++;
return count;
}
@override
Widget build(BuildContext context) {
_notificationsProvider = Provider.of<NotificationsProvider>(context);
_userProvider = Provider.of<UserProvider>(context);
return Scaffold(
backgroundColor: AppColor.scaffold2Background(context),
appBar: DefaultAppBar(
title: context.translation.notifications,
actions: [
TextButton.icon(
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(
color: AppColor.primary10,
fontWeight: FontWeight.w500,
),
),
style: TextButton.styleFrom(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
foregroundColor: _unreadCount > 0 ? AppColor.primary10 : AppColor.neutral30,
),
),
],
),
body: SafeArea(
child: Column(
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);
},
),
],
),
16.height,
// Notification Tabs
Row(
children: [
Expanded(
child: SizedBox(
height: 35.toScreenHeight,
child: ListView.separated(
scrollDirection: Axis.horizontal,
itemCount: 3,
separatorBuilder: (context, index) => 8.width,
itemBuilder: (context, index) {
final labels = ['All Notifications', 'Read', 'Unread'];
final counts = [_allCount, _readCount, _unreadCount];
return NotificationTabButton(
label: labels[index],
count: counts[index],
isSelected: _selectedTabIndex == index,
onTap: () {
setState(() {
_selectedTabIndex = index;
});
// Reload notifications with new tab filter
_loadNotifications();
},
);
},
),
),
),
8.width,
// Sort & Filter Container
Container(
height: 35.toScreenHeight,
padding: const EdgeInsets.symmetric(horizontal: 8),
decoration: BoxDecoration(
color: AppColor.background(context),
borderRadius: BorderRadius.circular(6),
border: Border.all(color: AppColor.borderColor(context)),
),
child: Row(
children: [
// Sort Icon
Transform.flip(
flipY: isAscending,
child: 'dashboard/sort_descending'
.toSvgAsset(
width: 20,
height: 20,
color: AppColor.iconColor(context),
)
.onPress(() {
_toggleSort();
}),
),
14.width,
// Filter Icon
Stack(
clipBehavior: Clip.none,
children: [
'filter'
.toSvgAsset(
width: 20,
height: 20,
color: AppColor.iconColor(context),
)
.onPress(() async {
// Open filter page
final result = await Navigator.push<NotificationFilterModel>(
context,
MaterialPageRoute(
builder: (context) => NotificationFilterPage(data: _activeFilters),
),
);
if (result != null) {
setState(() {
_activeFilters = result;
});
_loadNotifications();
}
}),
// Active Filters Badge
if (_activeFilterCount > 0)
Positioned(
right: -8,
top: -12,
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 5, vertical: 2),
decoration: BoxDecoration(
color: AppColor.red30,
borderRadius: BorderRadius.circular(10),
border: Border.all(color: AppColor.white10, width: 1.5),
),
constraints: const BoxConstraints(
minWidth: 16,
minHeight: 16,
),
child: Text(
'$_activeFilterCount',
textAlign: TextAlign.center,
style: AppTextStyles.tinyFont2.copyWith(
color: AppColor.white10,
fontWeight: FontWeight.w600,
fontSize: 9,
),
),
),
),
],
),
],
),
),
],
),
16.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();
await _notificationsProvider.getSystemNotifications(
user: _userProvider.user!,
resetProvider: true,
filters: _activeFilters,
);
},
child: NotificationsList(
nextPage: _notificationsProvider.nextPage,
onLazyLoad: () async {
await _notificationsProvider.getSystemNotifications(
user: _userProvider.user!,
filters: _activeFilters,
);
},
notifications: _filteredNotifications,
),
),
),
],
).paddingOnly(start: 16, end: 16),
),
);
}
}