import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import 'core/auth/auth_notifier.dart'; import 'core/auth/auth_state.dart'; import 'core/theme/app_theme.dart'; import 'core/widgets/error_view.dart'; import 'shared/providers/permissions_provider.dart'; import 'features/auth/splash_screen.dart'; import 'features/auth/login_screen.dart'; import 'features/dashboard/dashboard_screen.dart'; import 'features/tasks/task_list_screen.dart'; import 'features/tasks/task_detail_screen.dart'; import 'features/tasks/task_form_screen.dart'; import 'features/tasks/subtask_detail_screen.dart'; import 'features/surat/surat_list_screen.dart'; import 'features/surat/surat_detail_screen.dart'; import 'features/surat/surat_form_screen.dart'; import 'features/contracts/contract_list_screen.dart'; import 'features/contracts/contract_detail_screen.dart'; import 'features/contracts/contract_upload_screen.dart'; import 'features/contracts/contract_form_screen.dart'; import 'features/notifications/notification_screen.dart'; import 'features/profile/profile_screen.dart'; import 'features/profile/change_password_screen.dart'; import 'features/leave/leave_list_screen.dart'; final _rootNavKey = GlobalKey(debugLabel: 'root'); final _shellNavKey = GlobalKey(debugLabel: 'shell'); // Saves the deep link URL that arrived before the user was logged in final _pendingDeepLinkProvider = StateProvider((ref) => null); // ChangeNotifier that triggers GoRouter refresh when auth/permissions change class _RouterNotifier extends ChangeNotifier { _RouterNotifier(Ref ref) { ref.listen(authProvider, (_, __) => notifyListeners()); ref.listen(permissionsProvider, (_, __) => notifyListeners()); } } final routerProvider = Provider((ref) { final notifier = _RouterNotifier(ref); ref.onDispose(notifier.dispose); return GoRouter( navigatorKey: _rootNavKey, initialLocation: '/splash', refreshListenable: notifier, redirect: (context, state) { final authState = ref.read(authProvider); final permissions = ref.read(permissionsProvider).valueOrNull; final loc = state.matchedLocation; if (authState is AuthLoading) return '/splash'; if (authState is AuthUnauthenticated && loc != '/login') { // Save the intended destination so we can redirect after login final fullPath = state.uri.toString(); if (fullPath != '/splash' && fullPath != '/login') { ref.read(_pendingDeepLinkProvider.notifier).state = fullPath; } return '/login'; } if (authState is AuthAuthenticated && (loc == '/login' || loc == '/splash')) { // If a deep link was pending, navigate there instead of /home final pending = ref.read(_pendingDeepLinkProvider); if (pending != null && pending != '/login' && pending != '/splash') { ref.read(_pendingDeepLinkProvider.notifier).state = null; return pending; } return '/home'; } if (permissions != null && authState is AuthAuthenticated) { if (loc.startsWith('/surat') && !permissions.contains('letters')) return '/home'; if (loc.startsWith('/contracts') && !permissions.contains('contracts')) return '/home'; } return null; }, routes: [ GoRoute(path: '/splash', builder: (_, __) => const SplashScreen()), GoRoute(path: '/login', builder: (_, __) => const LoginScreen()), ShellRoute( navigatorKey: _shellNavKey, builder: (context, state, child) => MainShell(child: child, location: state.matchedLocation), routes: [ GoRoute(path: '/home', builder: (_, __) => const DashboardScreen()), // Tasks GoRoute(path: '/tasks', builder: (_, __) => const TaskListScreen()), GoRoute(path: '/tasks/create', builder: (_, __) => const TaskFormScreen()), GoRoute(path: '/tasks/:id', builder: (_, s) => TaskDetailScreen(id: int.parse(s.pathParameters['id']!))), GoRoute(path: '/tasks/:id/edit', builder: (_, s) => TaskFormScreen(editId: int.parse(s.pathParameters['id']!))), GoRoute(path: '/tasks/:id/subtasks/:sid', builder: (_, s) => SubtaskDetailScreen( taskId: int.parse(s.pathParameters['id']!), subtaskId: int.parse(s.pathParameters['sid']!), )), // Letters (web uses /letters, mobile uses /surat — both supported) GoRoute(path: '/surat', builder: (_, __) => const SuratListScreen()), GoRoute(path: '/surat/create', builder: (_, s) => SuratFormScreen(prefillTaskId: int.tryParse(s.uri.queryParameters['task_id'] ?? ''))), GoRoute(path: '/surat/:id', builder: (_, s) => SuratDetailScreen(id: int.parse(s.pathParameters['id']!))), GoRoute(path: '/surat/:id/edit', builder: (_, s) => SuratFormScreen(editId: int.parse(s.pathParameters['id']!))), // Web URL aliases GoRoute(path: '/letters', builder: (_, __) => const SuratListScreen()), GoRoute(path: '/letters/:id', builder: (_, s) => SuratDetailScreen(id: int.parse(s.pathParameters['id']!))), // Contracts GoRoute(path: '/contracts', builder: (_, __) => const ContractListScreen()), GoRoute(path: '/contracts/upload', builder: (_, __) => const ContractUploadScreen()), GoRoute(path: '/contracts/:id', builder: (_, s) => ContractDetailScreen(id: int.parse(s.pathParameters['id']!))), GoRoute(path: '/contracts/:id/edit', builder: (_, s) => ContractFormScreen(editId: int.parse(s.pathParameters['id']!))), // Leave Requests GoRoute(path: '/leave', builder: (_, __) => const LeaveListScreen()), // Utils GoRoute(path: '/notifications', builder: (_, __) => const NotificationScreen()), GoRoute(path: '/profile', builder: (_, __) => const ProfileScreen()), GoRoute(path: '/profile/password', builder: (_, __) => const ChangePasswordScreen()), ], ), ], errorBuilder: (_, state) => Scaffold( body: ErrorView(message: 'Page not found: ${state.error}'), ), ); }); // ── Bottom nav shell ───────────────────────────────────────────────────────── class MainShell extends ConsumerWidget { final Widget child; final String location; const MainShell({super.key, required this.child, required this.location}); @override Widget build(BuildContext context, WidgetRef ref) { final permissionsAsync = ref.watch(permissionsProvider); final permissions = permissionsAsync.valueOrNull; final canLetters = permissions?.contains('letters') ?? true; final canContracts = permissions?.contains('contracts') ?? true; // Build dynamic tab list final tabs = <_NavTab>[ const _NavTab(path: '/home', label: 'Home', icon: Icons.dashboard_outlined, activeIcon: Icons.dashboard), const _NavTab(path: '/tasks', label: 'Tasks', icon: Icons.check_box_outlined, activeIcon: Icons.check_box), if (canLetters) const _NavTab(path: '/surat', label: 'Letters', icon: Icons.mail_outline, activeIcon: Icons.mail), if (canContracts) const _NavTab(path: '/contracts', label: 'Contracts', icon: Icons.description_outlined, activeIcon: Icons.description), const _NavTab(path: '/leave', label: 'Leave Request', icon: Icons.event_busy_outlined, activeIcon: Icons.event_busy), ]; final currentIndex = tabs.indexWhere((t) => location.startsWith(t.path)); final tabIndex = currentIndex < 0 ? 0 : currentIndex; // Hide bottom nav on form/detail screens final hideNav = location.contains('/create') || location.contains('/edit') || location.contains('/upload') || (location.startsWith('/tasks/') && !location.endsWith('/tasks/')) || (location.startsWith('/surat/') && !location.endsWith('/surat/')) || (location.startsWith('/contracts/') && !location.endsWith('/contracts/')) || (location.startsWith('/leave/') && !location.endsWith('/leave/')) || location == '/notifications' || location == '/profile' || location == '/profile/password'; return Scaffold( body: child, bottomNavigationBar: hideNav ? null : BottomNavigationBar( currentIndex: tabIndex, onTap: (i) => context.go(tabs[i].path), type: BottomNavigationBarType.fixed, selectedItemColor: AppColors.primary, unselectedItemColor: AppColors.textSub, backgroundColor: AppColors.surface, elevation: 8, selectedFontSize: 11, unselectedFontSize: 11, items: tabs.map((t) => BottomNavigationBarItem( icon: Icon(t.icon), activeIcon: Icon(t.activeIcon), label: t.label, )).toList(), ), ); } } class _NavTab { final String path; final String label; final IconData icon; final IconData activeIcon; const _NavTab({required this.path, required this.label, required this.icon, required this.activeIcon}); }