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_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 '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 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 _loadNotifications() async { final notificationsProvider = Provider.of(context, listen: false); final userProvider = Provider.of(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 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.notifications.where((n) => !(n.readed ?? false)).length; } int get _allCount { return _notificationsProvider.notifications.length; } int get _readCount { return _notificationsProvider.notifications.where((n) => n.readed ?? false).length; } /// Get count of active filters int get _activeFilterCount { if (_activeFilters == null) return 0; 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++; if (_activeFilters!.endDate != null) count++; return count; } @override Widget build(BuildContext context) { _notificationsProvider = Provider.of(context); _userProvider = Provider.of(context); return Scaffold( backgroundColor: AppColor.scaffold2Background(context), 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), 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: AppColor.primary10, ), ), ], ), 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, // 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: [ 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( context, MaterialPageRoute( builder: (context) => NotificationFilterPage(data: _activeFilters), ), ); if (result != null) { setState(() { _activeFilters = result; }); _loadNotifications(); } }), // Active Filters Badge if (_activeFilterCount > 0) Positioned( right: -8, top: -8, 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), ), ); } }