import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import '../../core/constants/app_colors.dart'; import '../screens/dashboard/dashboard_screen.dart'; import '../screens/settings/settings_screen.dart'; import '../screens/students/students_screen.dart'; import '../screens/documents/documents_screen.dart'; import '../screens/archives/archives_screen.dart'; import '../screens/reports/reports_screen.dart'; import '../screens/users/users_screen.dart'; import '../screens/audit_trail/audit_trail_screen.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; // Import Riverpod import '../providers/dashboard_provider.dart'; import '../providers/student_provider.dart' hide academicYearsProvider; import '../providers/document_provider.dart'; import '../providers/archives_provider.dart'; import '../providers/reports_provider.dart'; import '../providers/users_provider.dart'; import '../providers/activity_provider.dart'; import '../providers/auth_provider.dart'; import '../providers/setup_provider.dart'; import '../providers/system_settings_provider.dart'; import '../shared/dialogs/logout_dialog.dart'; import '../shared/widgets/abstract_background.dart'; import '../providers/navigation_provider.dart'; import '../providers/connected_users_provider.dart'; import '../screens/capstone_members/capstone_members_screen.dart'; import '../shared/dialogs/disconnected_dialog.dart'; import '../shared/inputs/app_search_bar.dart'; import '../shared/menus/profile_dropdown_menu.dart'; import '../shared/widgets/notification_icon_button.dart'; import '../screens/students/widgets/student_bulk_actions.dart'; import '../screens/documents/widgets/print_queue_modal.dart'; import '../screens/documents/widgets/upload_ocr_modal.dart'; import '../screens/students/widgets/add_student_modal.dart'; // Dummy screen for placeholders class PlaceholderScreen extends StatelessWidget { final String title; const PlaceholderScreen(this.title, {super.key}); @override Widget build(BuildContext context) => Center(child: Text(title, style: const TextStyle(fontSize: 24))); } class AndroidBottomNavLayout extends ConsumerStatefulWidget { final String userRole; const AndroidBottomNavLayout({super.key, required this.userRole}); @override ConsumerState createState() => _AndroidBottomNavLayoutState(); } class _AndroidBottomNavLayoutState extends ConsumerState with TickerProviderStateMixin { final GlobalKey _scaffoldKey = GlobalKey(); final Set _visitedIndices = {}; Timer? _holdTimer; Timer? _tabLoadingTimer; bool _isTabLoading = false; TabController? _tabController; List> _tabs = []; List> _allowedPrimary = []; List> _allowedSecondary = []; @override void initState() { super.initState(); _initTabs(); WidgetsBinding.instance.addPostFrameCallback((_) { ref.read(heartbeatServiceProvider).onConnectionLost = () { if (mounted) { DisconnectedDialog.show( context, onReconnected: () { final currentIdx = _tabController?.index ?? 0; if (currentIdx < _allowedPrimary.length) { final label = _allowedPrimary[currentIdx]['label'] as String? ?? ''; _reloadTabContent(label); } }, ); } }; }); } void _showStudentSearchDialog( BuildContext context, WidgetRef ref, { bool navigateToStudents = false, }) { final currentSearch = ref.read(studentQueryProvider).search; final searchController = TextEditingController(text: currentSearch); final searchFocusNode = FocusNode(); WidgetsBinding.instance.addPostFrameCallback((_) { searchFocusNode.requestFocus(); }); showDialog( context: context, barrierColor: Colors.black54, builder: (ctx) { final isDark = Theme.of(ctx).brightness == Brightness.dark; return Align( alignment: Alignment.topCenter, child: Padding( padding: const EdgeInsets.fromLTRB(16, kToolbarHeight + 24, 16, 0), child: Material( color: isDark ? AppColors.darkSurfaceCard : Colors.white, elevation: 4, borderRadius: BorderRadius.circular(12), child: AppSearchBar( controller: searchController, focusNode: searchFocusNode, collapsible: false, hint: 'Search by LRN or name...', maxWidth: 600, onSubmitted: (value) { Navigator.of(ctx).pop(); if (value.trim().isNotEmpty) { ref.read(studentQueryProvider.notifier).setFilters( status: '', schoolYear: '', gradeLevel: '', section: '', ); } ref.read(studentQueryProvider.notifier).setSearch(value); if (navigateToStudents) { ref.invalidate(studentPageProvider); ref.read(activeTabProvider.notifier).setTab('Students'); } }, ), ), ), ); }, ); } void _showDocumentSearchDialog( BuildContext context, WidgetRef ref, ) { final currentSearch = ref.read(documentQueryProvider).search; final searchController = TextEditingController(text: currentSearch); final searchFocusNode = FocusNode(); WidgetsBinding.instance.addPostFrameCallback((_) { searchFocusNode.requestFocus(); }); showDialog( context: context, barrierColor: Colors.black54, builder: (ctx) { final isDark = Theme.of(ctx).brightness == Brightness.dark; return Align( alignment: Alignment.topCenter, child: Padding( padding: const EdgeInsets.fromLTRB(16, kToolbarHeight + 24, 16, 0), child: Material( color: isDark ? AppColors.darkSurfaceCard : Colors.white, elevation: 4, borderRadius: BorderRadius.circular(12), child: AppSearchBar( controller: searchController, focusNode: searchFocusNode, collapsible: false, hint: 'Search by name, LRN, file…', maxWidth: 600, onSubmitted: (value) { Navigator.of(ctx).pop(); ref.read(documentQueryProvider.notifier).setSearch(value); ref.invalidate(foldersProvider); ref.invalidate(studentFoldersProvider); ref.invalidate(documentPageProvider); }, ), ), ), ); }, ); } void _showArchiveSearchDialog( BuildContext context, WidgetRef ref, ) { final currentSearch = ref.read(archiveDocumentQueryProvider).search; final searchController = TextEditingController(text: currentSearch); final searchFocusNode = FocusNode(); WidgetsBinding.instance.addPostFrameCallback((_) { searchFocusNode.requestFocus(); }); showDialog( context: context, barrierColor: Colors.black54, builder: (ctx) { final isDark = Theme.of(ctx).brightness == Brightness.dark; return Align( alignment: Alignment.topCenter, child: Padding( padding: const EdgeInsets.fromLTRB(16, kToolbarHeight + 24, 16, 0), child: Material( color: isDark ? AppColors.darkSurfaceCard : Colors.white, elevation: 4, borderRadius: BorderRadius.circular(12), child: AppSearchBar( controller: searchController, focusNode: searchFocusNode, collapsible: false, hint: 'Search archives by name, LRN, file…', maxWidth: 600, onSubmitted: (value) { Navigator.of(ctx).pop(); ref .read(archiveDocumentQueryProvider.notifier) .setSearch(value); ref.invalidate(archiveStudentFoldersProvider); ref.invalidate(archiveDocumentPageProvider); }, ), ), ), ); }, ); } void _showUserSearchDialog( BuildContext context, WidgetRef ref, ) { final currentSearch = ref.read(userSearchQueryProvider); final searchController = TextEditingController(text: currentSearch); final searchFocusNode = FocusNode(); WidgetsBinding.instance.addPostFrameCallback((_) { searchFocusNode.requestFocus(); }); showDialog( context: context, barrierColor: Colors.black54, builder: (ctx) { final isDark = Theme.of(ctx).brightness == Brightness.dark; return Align( alignment: Alignment.topCenter, child: Padding( padding: const EdgeInsets.fromLTRB(16, kToolbarHeight + 24, 16, 0), child: Material( color: isDark ? AppColors.darkSurfaceCard : Colors.white, elevation: 4, borderRadius: BorderRadius.circular(12), child: AppSearchBar( controller: searchController, focusNode: searchFocusNode, collapsible: false, hint: 'Search by username, name, email or role...', maxWidth: 600, onChanged: (value) { ref.read(userSearchQueryProvider.notifier).state = value; }, onSubmitted: (value) { Navigator.of(ctx).pop(); ref.read(userSearchQueryProvider.notifier).state = value; }, ), ), ), ); }, ); } void _initTabs() { final primaryTabsConfig = [ { 'label': 'Dashboard', 'icon': Icons.dashboard_outlined, 'activeIcon': Icons.dashboard, 'screen': const DashboardScreen(), 'roles': ['admin', 'teacher'], }, { 'label': 'Students', 'icon': Icons.people_outline, 'activeIcon': Icons.people, 'screen': StudentsScreen(userRole: widget.userRole), 'roles': ['admin', 'teacher'], }, { 'label': 'Documents', 'icon': Icons.folder_outlined, 'activeIcon': Icons.folder, 'screen': DocumentsScreen(userRole: widget.userRole), 'roles': ['admin', 'teacher'], }, { 'label': 'Archives', 'icon': Icons.archive_outlined, 'activeIcon': Icons.archive, 'screen': ArchivesScreen(userRole: widget.userRole), 'roles': ['admin'], }, ]; final secondaryTabsConfig = [ { 'label': 'Reports', 'icon': Icons.bar_chart, 'activeIcon': Icons.bar_chart, 'screen': ReportsScreen(userRole: widget.userRole), 'roles': ['admin'], }, { 'label': 'Users', 'icon': Icons.manage_accounts_outlined, 'activeIcon': Icons.manage_accounts, 'screen': const UsersScreen(), 'roles': ['admin'], }, { 'label': 'History', 'icon': Icons.manage_history_outlined, 'activeIcon': Icons.manage_history_rounded, 'screen': AuditTrailScreen(userRole: widget.userRole), 'roles': ['admin'], }, { 'label': 'Settings', 'icon': Icons.settings_outlined, 'activeIcon': Icons.settings, 'screen': SettingsScreen(userRole: widget.userRole), 'roles': ['admin', 'teacher'], }, ]; _allowedPrimary = primaryTabsConfig .where((t) => (t['roles'] as List).contains(widget.userRole)) .toList(); _allowedSecondary = secondaryTabsConfig .where((t) => (t['roles'] as List).contains(widget.userRole)) .toList(); _tabs = [..._allowedPrimary, ..._allowedSecondary]; _tabController = TabController(length: _tabs.length, vsync: this); _tabController!.addListener(_handleTabSelection); } void _handleTabSelection() { if (_tabController!.indexIsChanging) return; final newLabel = _tabs[_tabController!.index]['label'] as String; if (ref.read(activeTabProvider) != newLabel) { ref.read(activeTabProvider.notifier).setTab(newLabel); _reloadTabContent(newLabel); } } @override void dispose() { _tabController?.dispose(); _holdTimer?.cancel(); _tabLoadingTimer?.cancel(); super.dispose(); } void _reloadTabContent(String label) { switch (label) { case 'Dashboard': ref.invalidate(dashboardDataProvider); break; case 'Students': ref.read(studentQueryProvider.notifier).reset(); ref.invalidate(studentPageProvider); break; case 'Documents': ref.invalidate(foldersProvider); ref.invalidate(documentPageProvider); break; case 'Archives': ref.invalidate(archivePageProvider); break; case 'Reports': ref.invalidate(reportStatsProvider); ref.invalidate(academicYearsProvider); ref.invalidate(yearlyComparisonProvider); break; case 'Users': ref.read(userSearchQueryProvider.notifier).state = ''; ref.invalidate(usersProvider); break; case 'History': ref.invalidate(activityQueryProvider); ref.invalidate(recentActivitiesPageProvider); ref.invalidate(userHistoryQueryProvider); ref.invalidate(userHistoryPageProvider); break; case 'Settings': ref.invalidate(academicYearsListProvider); ref.invalidate(sectionsListProvider); ref.invalidate(gradeLevelsListProvider); ref.invalidate(systemSettingsProvider); ref.invalidate(profileProvider); ref.invalidate(requirementsSettingsProvider); break; } } void _onNavTapped(String label) { if (ref.read(studentMultiSelectProvider)) { ref.read(studentMultiSelectProvider.notifier).state = false; ref.read(studentSelectedIdsProvider.notifier).state = []; } setState(() { _isTabLoading = true; }); _tabLoadingTimer?.cancel(); _tabLoadingTimer = Timer(const Duration(milliseconds: 100), () { if (mounted) { setState(() { _isTabLoading = false; }); } }); ref.read(activeTabProvider.notifier).setTab(label); _reloadTabContent(label); } void _showCapstoneMembers(BuildContext context) { Navigator.push( context, MaterialPageRoute(builder: (context) => const CapstoneMembersScreen()), ); } @override Widget build(BuildContext context) { final activeTab = ref.watch(activeTabProvider); int currentIndex = _tabs.indexWhere((t) => t['label'] == activeTab); if (currentIndex == -1) currentIndex = 0; // Fallback to Dashboard // if (_tabController != null && _tabController!.index != currentIndex) { // _tabController!.animateTo(currentIndex); // } _visitedIndices.add(currentIndex); return PopScope( canPop: false, onPopInvokedWithResult: (didPop, result) async { if (didPop) return; if (activeTab != 'Dashboard') { if (activeTab == 'Documents' || activeTab == 'Students' || activeTab == 'Archives') { return; } _onNavTapped('Dashboard'); return; } final shouldExit = await showDialog( context: context, builder: (context) => AlertDialog( shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), title: const Row( children: [ Icon(Icons.warning_amber_rounded, color: Colors.redAccent), SizedBox(width: 10), Text('Confirm Exit'), ], ), content: const Text('Are you sure you want to exit the app?'), actions: [ TextButton( onPressed: () => Navigator.of(context).pop(false), child: const Text( 'CANCEL', style: TextStyle(color: Colors.grey), ), ), TextButton( onPressed: () => Navigator.of(context).pop(true), child: const Text( 'EXIT', style: TextStyle( color: Colors.redAccent, fontWeight: FontWeight.bold, ), ), ), ], ), ); if (shouldExit == true) { SystemNavigator.pop(); } }, child: Builder( builder: (context) { final mediaQuery = MediaQuery.of(context); final lockedMediaQuery = mediaQuery.copyWith( viewInsets: EdgeInsets.zero, padding: mediaQuery.viewPadding, ); final isDark = Theme.of(context).brightness == Brightness.dark; final overlayStyle = SystemUiOverlayStyle( statusBarColor: Colors.transparent, statusBarIconBrightness: isDark ? Brightness.light : Brightness.dark, systemNavigationBarColor: Colors.transparent, systemNavigationBarIconBrightness: isDark ? Brightness.light : Brightness.dark, systemNavigationBarDividerColor: Colors.transparent, ); final isStudentMultiSelect = (activeTab == 'Students') && ref.watch(studentMultiSelectProvider); return AnnotatedRegion( value: isStudentMultiSelect ? SystemUiOverlayStyle.light : overlayStyle, child: MediaQuery( data: lockedMediaQuery, child: Scaffold( key: _scaffoldKey, drawerEnableOpenDragGesture: !isStudentMultiSelect, drawerEdgeDragWidth: isStudentMultiSelect ? 0.0 : 64.0, resizeToAvoidBottomInset: false, backgroundColor: Theme.of(context).scaffoldBackgroundColor, appBar: isStudentMultiSelect ? AppBar( systemOverlayStyle: SystemUiOverlayStyle.light, backgroundColor: isDark ? AppColors.darkSurfaceCard : AppColors.primaryGreen, foregroundColor: Colors.white, elevation: 1, leading: IconButton( icon: const Icon(Icons.close, color: Colors.white), tooltip: 'Exit Selection', onPressed: () { ref.read(studentMultiSelectProvider.notifier).state = false; ref.read(studentSelectedIdsProvider.notifier).state = []; }, ), title: Consumer( builder: (context, ref, _) { final selectedIds = ref.watch(studentSelectedIdsProvider); return Text( '${selectedIds.length} selected', style: const TextStyle( fontSize: 17, fontWeight: FontWeight.bold, color: Colors.white, ), ); }, ), actions: [ Consumer( builder: (context, ref, _) { final selectedIds = ref.watch(studentSelectedIdsProvider); final pageAsync = ref.watch(studentPageProvider); final currentStudents = pageAsync.value?.students ?? []; final allSelected = currentStudents.isNotEmpty && currentStudents.every((s) => selectedIds.contains(s.id)); return IconButton( icon: Icon( allSelected ? Icons.deselect : Icons.select_all, color: Colors.white, ), tooltip: allSelected ? 'Unselect All' : 'Select All', onPressed: () { if (allSelected) { ref.read(studentSelectedIdsProvider.notifier).state = []; } else { ref.read(studentSelectedIdsProvider.notifier).state = currentStudents.map((s) => s.id).toList(); } }, ); }, ), if (widget.userRole != 'teacher') Consumer( builder: (context, ref, _) { final selectedIds = ref.watch(studentSelectedIdsProvider); if (selectedIds.isEmpty) return const SizedBox(width: 4); return Row( mainAxisSize: MainAxisSize.min, children: [ IconButton( icon: const Icon(Icons.group_add_rounded, color: Colors.white), tooltip: 'Enroll', onPressed: () => StudentBulkActions.bulkEnroll(context, ref), ), IconButton( icon: const Icon(Icons.school_rounded, color: Colors.white), tooltip: 'Graduate', onPressed: () => StudentBulkActions.bulkGraduate(context, ref), ), PopupMenuButton( icon: const Icon(Icons.more_vert_rounded, color: Colors.white), tooltip: 'More Actions', onSelected: (action) { if (action == 'Transferred' || action == 'Dropped' || action == 'Inactive') { StudentBulkActions.bulkChangeStatus(context, ref, action); } }, itemBuilder: (context) => [ const PopupMenuItem( value: 'Transferred', child: Row( children: [ Icon(Icons.transfer_within_a_station_rounded, size: 20, color: AppColors.primaryGreen), SizedBox(width: 10), Text('Transfer'), ], ), ), const PopupMenuItem( value: 'Dropped', child: Row( children: [ Icon(Icons.person_off_rounded, size: 20, color: Colors.redAccent), SizedBox(width: 10), Text('Drop'), ], ), ), const PopupMenuItem( value: 'Inactive', child: Row( children: [ Icon(Icons.do_not_disturb_on_total_silence_rounded, size: 20, color: Colors.orangeAccent), SizedBox(width: 10), Text('Set Inactive'), ], ), ), ], ), const SizedBox(width: 4), ], ); }, ), ], ) : AppBar( systemOverlayStyle: overlayStyle, backgroundColor: Theme.of(context).appBarTheme.backgroundColor, foregroundColor: Theme.of(context).colorScheme.onSurface, iconTheme: IconThemeData(color: Theme.of(context).colorScheme.onSurface), leading: Builder( builder: (context) => GestureDetector( behavior: HitTestBehavior.opaque, onHorizontalDragUpdate: (details) { if (details.primaryDelta != null && details.primaryDelta! > 4) { _scaffoldKey.currentState?.openDrawer(); } }, child: IconButton( icon: const Icon(Icons.menu_rounded), tooltip: 'Open navigation menu', onPressed: () { _scaffoldKey.currentState?.openDrawer(); }, ), ), ), title: Text( activeTab, style: TextStyle( fontWeight: FontWeight.bold, color: Theme.of(context).colorScheme.onSurface, ), ), elevation: 0, surfaceTintColor: Colors.transparent, actions: [ if (activeTab == 'Dashboard') ...[ // 1. Search Icon Consumer( builder: (context, ref, _) { return Tooltip( message: 'Search Students', child: IconButton( icon: const Icon(Icons.search, size: 22), onPressed: () => _showStudentSearchDialog( context, ref, navigateToStudents: true, ), ), ); }, ), // 2. Notification Icon with Unread Badge const NotificationIconButton(iconSize: 22), // 3. Profile Avatar Dropdown Consumer( builder: (context, ref, _) { final user = ref.watch(authProvider).value; return Padding( padding: const EdgeInsets.only(left: 4, right: 12), child: ProfileDropdownMenu( user: user, onRefresh: () => ref.read(authProvider.notifier).refreshUser(), ), ); }, ), ], if (activeTab == 'Students') ...[ // 1. Search Icon (consistent with Dashboard) Consumer( builder: (context, ref, _) { return Tooltip( message: 'Search Students', child: IconButton( icon: const Icon(Icons.search, size: 22), onPressed: () => _showStudentSearchDialog(context, ref), ), ); }, ), // 3. Add Student Icon (High-Attraction Elevated Primary CTA) if (widget.userRole != 'teacher') Tooltip( message: 'Add Student', child: Container( width: 36, height: 36, margin: const EdgeInsets.only(left: 4, right: 8), decoration: BoxDecoration( gradient: const LinearGradient( colors: [ Color(0xFF00B074), AppColors.primaryGreen, ], begin: Alignment.topLeft, end: Alignment.bottomRight, ), borderRadius: BorderRadius.circular(10), boxShadow: [ BoxShadow( color: AppColors.primaryGreen.withValues(alpha: 0.4), blurRadius: 6, offset: const Offset(0, 2), ), ], ), child: Material( color: Colors.transparent, child: InkWell( borderRadius: BorderRadius.circular(10), onTap: () => AddStudentModal.show(context), child: const Center( child: Icon( Icons.person_add_rounded, size: 20, color: Colors.white, ), ), ), ), ), ), const SizedBox(width: 4), ], if (activeTab == 'Documents') ...[ // 1. Search Icon (left side of Print List icon) Consumer( builder: (context, ref, _) { return Tooltip( message: 'Search Documents', child: IconButton( icon: const Icon(Icons.search, size: 22), onPressed: () => _showDocumentSearchDialog(context, ref), ), ); }, ), // 2. Print List Icon (for non-teachers) if (widget.userRole != 'teacher') Consumer( builder: (context, ref, _) { final queueAsync = ref.watch(printQueueProvider); final count = queueAsync.maybeWhen( data: (items) => items.length, orElse: () => 0, ); return Tooltip( message: 'Print List', child: IconButton( icon: Badge( isLabelVisible: count > 0, label: Text(count.toString()), child: const Icon(Icons.print_rounded, size: 22), ), onPressed: () => PrintQueueModal.show(context), ), ); }, ), // 3. Upload Document Icon (High-Attraction Elevated Primary CTA) Tooltip( message: 'Upload Document', child: Container( width: 36, height: 36, margin: const EdgeInsets.only(left: 4, right: 8), decoration: BoxDecoration( gradient: const LinearGradient( colors: [ Color(0xFF00B074), AppColors.primaryGreen, ], begin: Alignment.topLeft, end: Alignment.bottomRight, ), borderRadius: BorderRadius.circular(10), boxShadow: [ BoxShadow( color: AppColors.primaryGreen.withValues(alpha: 0.4), blurRadius: 6, offset: const Offset(0, 2), ), ], ), child: Material( color: Colors.transparent, child: InkWell( borderRadius: BorderRadius.circular(10), onTap: () { final openedFolder = ref.read(openedFolderProvider); final studentId = openedFolder?.id ?? ref.read(documentQueryProvider).studentId; UploadOcrModal.show( context, prefilledStudentId: studentId, prefilledLrn: openedFolder?.lrn, ); }, child: const Center( child: Icon( Icons.cloud_upload_rounded, size: 20, color: Colors.white, ), ), ), ), ), ), const SizedBox(width: 4), ], if (activeTab == 'Archives') ...[ // 1. Search Icon Consumer( builder: (context, ref, _) { return Tooltip( message: 'Search Archives', child: IconButton( icon: const Icon(Icons.search, size: 22), onPressed: () => _showArchiveSearchDialog(context, ref), ), ); }, ), // 2. Print List Icon (for non-teachers) if (widget.userRole != 'teacher') Consumer( builder: (context, ref, _) { final queueAsync = ref.watch(printQueueProvider); final count = queueAsync.maybeWhen( data: (items) => items.length, orElse: () => 0, ); return Tooltip( message: 'Print List', child: IconButton( icon: Badge( isLabelVisible: count > 0, label: Text(count.toString()), child: const Icon(Icons.print_rounded, size: 22), ), onPressed: () => PrintQueueModal.show(context), ), ); }, ), const SizedBox(width: 4), ], if (activeTab == 'Users') ...[ // 1. Search Icon Consumer( builder: (context, ref, _) { final query = ref.watch(userSearchQueryProvider); return Tooltip( message: query.isNotEmpty ? 'Clear Search' : 'Search Users', child: IconButton( icon: Icon( query.isNotEmpty ? Icons.close : Icons.search, size: 22, ), onPressed: () { if (query.isNotEmpty) { ref.read(userSearchQueryProvider.notifier).state = ''; } else { _showUserSearchDialog(context, ref); } }, ), ); }, ), // 2. Add User Button (High-Attraction Elevated Primary CTA) Tooltip( message: 'Add User', child: Container( width: 36, height: 36, margin: const EdgeInsets.only(left: 4, right: 8), decoration: BoxDecoration( gradient: const LinearGradient( colors: [ Color(0xFF00B074), AppColors.primaryGreen, ], begin: Alignment.topLeft, end: Alignment.bottomRight, ), borderRadius: BorderRadius.circular(10), boxShadow: [ BoxShadow( color: AppColors.primaryGreen.withValues(alpha: 0.4), blurRadius: 6, offset: const Offset(0, 2), ), ], ), child: Material( color: Colors.transparent, child: InkWell( borderRadius: BorderRadius.circular(10), onTap: () => AddEditUserModal.show(context), child: const Center( child: Icon( Icons.person_add_rounded, size: 20, color: Colors.white, ), ), ), ), ), ), const SizedBox(width: 4), ], ], ), drawer: Drawer( backgroundColor: Theme.of(context).colorScheme.surface, surfaceTintColor: Colors.transparent, width: 280, child: SafeArea( child: Column( children: [ Container( width: double.infinity, padding: const EdgeInsets.only( top: 16, bottom: 16, left: 24, right: 16, ), child: Row( children: [ GestureDetector( onLongPressStart: (_) { _holdTimer = Timer(const Duration(seconds: 2), () { _showCapstoneMembers(context); }); }, onLongPressEnd: (_) => _holdTimer?.cancel(), onLongPressCancel: () => _holdTimer?.cancel(), child: Container( decoration: BoxDecoration( borderRadius: BorderRadius.circular(8), boxShadow: [ BoxShadow( color: Colors.black.withValues(alpha: 0.05), blurRadius: 4, offset: const Offset(0, 2), ), ], ), child: ClipRRect( borderRadius: BorderRadius.circular(8), child: Image.asset( 'assets/images/logo.png', width: 36, height: 36, fit: BoxFit.contain, ), ), ), ), const SizedBox(width: 12), Text( 'TIS RMS', style: TextStyle( fontWeight: FontWeight.bold, fontSize: 18, letterSpacing: 1.2, color: Theme.of(context).colorScheme.onSurface, ), ), const Spacer(), const _DrawerCloseButton(), ], ), ), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Expanded( child: ListView( physics: const NeverScrollableScrollPhysics(), padding: EdgeInsets.zero, children: [ if (_allowedPrimary.isNotEmpty) ...[ Container( padding: const EdgeInsets.symmetric( horizontal: 24, vertical: 8, ), alignment: Alignment.centerLeft, child: Text( 'MENU', style: TextStyle( fontSize: 12, fontWeight: FontWeight.bold, color: Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.38), ), ), ), Padding( padding: const EdgeInsets.symmetric( horizontal: 12, ), child: Column( children: [ ..._allowedPrimary.map( (tab) => _buildDrawerItem( tab, currentIndex, _tabs, ), ), ], ), ), ], ], ), ), if (_allowedSecondary.isNotEmpty) Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Container( padding: const EdgeInsets.symmetric( horizontal: 24, vertical: 8, ), alignment: Alignment.centerLeft, child: Text( 'SYSTEM', style: TextStyle( fontSize: 12, fontWeight: FontWeight.bold, color: Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.38), ), ), ), Padding( padding: const EdgeInsets.symmetric( horizontal: 12, ), child: Column( children: [ ..._allowedSecondary.map( (tab) => _buildDrawerItem( tab, currentIndex, _tabs, ), ), ], ), ), ], ), ], ), ), const Divider(), Padding( padding: const EdgeInsets.fromLTRB(24, 16, 16, 24), child: Row( children: [ Builder( builder: (context) { final user = ref.watch(authProvider).value; final initials = user != null && user.firstName.isNotEmpty && user.lastName.isNotEmpty ? '${user.firstName[0]}${user.lastName[0]}' .toUpperCase() : 'SA'; return CircleAvatar( radius: 20, backgroundColor: AppColors.primaryGreen, child: Text( initials, style: const TextStyle( color: Colors.white, fontWeight: FontWeight.bold, fontSize: 16, ), ), ); }, ), const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( ref.watch(authProvider).value?.fullName ?? 'Unknown User', style: TextStyle( fontWeight: FontWeight.bold, fontSize: 14, color: Theme.of(context).colorScheme.onSurface, ), maxLines: 1, overflow: TextOverflow.ellipsis, ), Text( widget.userRole.toUpperCase(), style: TextStyle( fontSize: 12, color: Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.6), ), ), ], ), ), IconButton( icon: Icon( Icons.logout, color: Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.6), ), onPressed: () { Navigator.pop(context); // Close drawer first showLogoutConfirmationDialog(context); }, ), ], ), ), ], ), ), ), body: AbstractBackground( child: Stack( children: [ IndexedStack( index: currentIndex, children: _tabs.asMap().entries.map((entry) { return _visitedIndices.contains(entry.key) ? entry.value['screen'] as Widget : const SizedBox.shrink(); }).toList(), ), if (_isTabLoading) Container( color: Theme.of(context).scaffoldBackgroundColor, width: double.infinity, height: double.infinity, child: const _PageSkeletonLoader(), ), ], ), ), ), ), ); }, ), ); } Widget _buildDrawerItem( Map tab, int currentIndex, List> allTabs, ) { final label = tab['label'] as String; final icon = tab['icon'] as IconData; final activeIcon = tab['activeIcon'] as IconData? ?? icon; final index = allTabs.indexWhere((t) => t['label'] == label); final isSelected = index == currentIndex; final isDark = Theme.of(context).brightness == Brightness.dark; return Padding( padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 4.0), child: Material( color: isSelected ? AppColors.primaryGreen.withValues(alpha: 0.12) : Colors.transparent, borderRadius: BorderRadius.circular(12), clipBehavior: Clip.antiAlias, child: InkWell( onTap: () { Navigator.pop(context); // Close drawer _onNavTapped(label); }, child: Padding( padding: const EdgeInsets.symmetric( horizontal: 16.0, vertical: 12.0, ), child: Row( children: [ Icon( isSelected ? activeIcon : icon, color: isSelected ? AppColors.primaryGreen : (isDark ? Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.6) : Colors.grey.shade600), size: 22, ), const SizedBox(width: 16), Text( label, style: TextStyle( fontSize: 14, fontWeight: isSelected ? FontWeight.w600 : FontWeight.w500, color: isSelected ? AppColors.primaryGreen : (isDark ? Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.7) : Colors.grey.shade700), ), ), ], ), ), ), ), ); } } class _PageSkeletonLoader extends StatefulWidget { const _PageSkeletonLoader(); @override State<_PageSkeletonLoader> createState() => _PageSkeletonLoaderState(); } class _PageSkeletonLoaderState extends State<_PageSkeletonLoader> with SingleTickerProviderStateMixin { late AnimationController _controller; @override void initState() { super.initState(); _controller = AnimationController( vsync: this, duration: const Duration(seconds: 1), )..repeat(reverse: true); } @override void dispose() { _controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { final isDark = Theme.of(context).brightness == Brightness.dark; final baseColor = isDark ? Colors.grey.shade800 : Colors.grey.shade300; final secondaryColor = isDark ? Colors.grey.shade900 : Colors.grey.shade200; return FadeTransition( opacity: Tween(begin: 0.3, end: 1.0).animate(_controller), child: Padding( padding: const EdgeInsets.all(24.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( height: 40, width: 180, decoration: BoxDecoration( color: baseColor, borderRadius: BorderRadius.circular(8), ), ), const SizedBox(height: 24), Row( children: [ Expanded( child: Container( height: 80, decoration: BoxDecoration( color: secondaryColor, borderRadius: BorderRadius.circular(12), ), ), ), const SizedBox(width: 16), Expanded( child: Container( height: 80, decoration: BoxDecoration( color: secondaryColor, borderRadius: BorderRadius.circular(12), ), ), ), ], ), const SizedBox(height: 24), Expanded( child: ListView.separated( itemCount: 5, physics: const NeverScrollableScrollPhysics(), separatorBuilder: (_, _) => const SizedBox(height: 16), itemBuilder: (_, _) => Container( height: 60, decoration: BoxDecoration( color: secondaryColor, borderRadius: BorderRadius.circular(8), ), ), ), ), ], ), ), ); } } class _DrawerCloseButton extends StatefulWidget { const _DrawerCloseButton(); @override State<_DrawerCloseButton> createState() => _DrawerCloseButtonState(); } class _DrawerCloseButtonState extends State<_DrawerCloseButton> { bool _useArrow = false; @override void initState() { super.initState(); Future.delayed(const Duration(milliseconds: 150), () { if (mounted) { setState(() { _useArrow = true; }); } }); } @override Widget build(BuildContext context) { return IconButton( icon: AnimatedSwitcher( duration: const Duration(milliseconds: 250), transitionBuilder: (child, animation) { return FadeTransition( opacity: animation, child: ScaleTransition(scale: animation, child: child), ); }, child: Icon( _useArrow ? Icons.arrow_back : Icons.menu, key: ValueKey(_useArrow), color: Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.54), ), ), onPressed: () => Navigator.pop(context), ); } }