import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import '../providers/auth_provider.dart'; import '../providers/profile_provider.dart'; import '../providers/supabase_provider.dart'; import '../utils/lock_enforcer.dart'; import '../screens/auth/login_screen.dart'; import '../screens/auth/signup_screen.dart'; import '../screens/admin/offices_screen.dart'; import '../screens/admin/user_management_screen.dart'; import '../screens/admin/geofence_test_screen.dart'; import '../screens/dashboard/dashboard_screen.dart'; import '../screens/notifications/notifications_screen.dart'; import '../screens/profile/profile_screen.dart'; import '../screens/shared/under_development_screen.dart'; import '../screens/shared/permissions_screen.dart'; import '../screens/reports/reports_screen.dart'; import '../screens/tasks/task_detail_screen.dart'; import '../screens/tasks/tasks_list_screen.dart'; import '../screens/tickets/ticket_detail_screen.dart'; import '../screens/tickets/tickets_list_screen.dart'; import '../screens/workforce/workforce_screen.dart'; import '../screens/attendance/attendance_screen.dart'; import '../screens/whereabouts/whereabouts_screen.dart'; import '../widgets/app_shell.dart'; import '../screens/teams/teams_screen.dart'; import '../theme/m3_motion.dart'; final appRouterProvider = Provider((ref) { final notifier = RouterNotifier(ref); ref.onDispose(notifier.dispose); return GoRouter( initialLocation: '/dashboard', refreshListenable: notifier, redirect: (context, state) { final authState = ref.read(authStateChangesProvider); dynamic session; if (authState is AsyncData) { final state = authState.value; session = state?.session; } else { session = ref.read(sessionProvider); } final isAuthRoute = state.fullPath == '/login' || state.fullPath == '/signup'; final isSignedIn = session != null; final profileAsync = ref.read(currentProfileProvider); final isAdminRoute = state.matchedLocation.startsWith('/settings'); final role = profileAsync is AsyncData ? (profileAsync.value)?.role : null; final isAdmin = role == 'admin'; final isReportsRoute = state.matchedLocation == '/reports'; final hasReportsAccess = role == 'admin' || role == 'dispatcher' || role == 'it_staff'; if (!isSignedIn && !isAuthRoute) { return '/login'; } if (isSignedIn && isAuthRoute) { return '/dashboard'; } if (isAdminRoute && !isAdmin) { return '/tickets'; } if (isReportsRoute && !hasReportsAccess) { return '/tickets'; } // Attendance & Whereabouts: not accessible to standard users final isStandardOnly = role == 'standard'; final isAttendanceRoute = state.matchedLocation == '/attendance' || state.matchedLocation == '/whereabouts'; if (isAttendanceRoute && isStandardOnly) { return '/dashboard'; } return null; }, routes: [ GoRoute(path: '/login', builder: (context, state) => const LoginScreen()), GoRoute( path: '/signup', builder: (context, state) => const SignUpScreen(), ), ShellRoute( builder: (context, state, child) => AppScaffold(child: child), routes: [ GoRoute( path: '/settings/teams', pageBuilder: (context, state) => M3SharedAxisPage( key: state.pageKey, child: const TeamsScreen(), ), ), GoRoute( path: '/dashboard', pageBuilder: (context, state) => M3SharedAxisPage( key: state.pageKey, child: const DashboardScreen(), ), ), GoRoute( path: '/tickets', pageBuilder: (context, state) => M3SharedAxisPage( key: state.pageKey, child: const TicketsListScreen(), ), routes: [ GoRoute( path: ':id', pageBuilder: (context, state) => M3ContainerTransformPage( key: state.pageKey, child: TicketDetailScreen( ticketId: state.pathParameters['id'] ?? '', ), ), ), ], ), GoRoute( path: '/tasks', pageBuilder: (context, state) => M3SharedAxisPage( key: state.pageKey, child: const TasksListScreen(), ), routes: [ GoRoute( path: ':id', pageBuilder: (context, state) => M3ContainerTransformPage( key: state.pageKey, child: TaskDetailScreen( taskId: state.pathParameters['id'] ?? '', ), ), ), ], ), GoRoute( path: '/events', pageBuilder: (context, state) => M3SharedAxisPage( key: state.pageKey, child: const UnderDevelopmentScreen( title: 'Events', subtitle: 'Event monitoring is under development.', icon: Icons.event, ), ), ), GoRoute( path: '/announcements', pageBuilder: (context, state) => M3SharedAxisPage( key: state.pageKey, child: const UnderDevelopmentScreen( title: 'Announcement', subtitle: 'Operational broadcasts are coming soon.', icon: Icons.campaign, ), ), ), GoRoute( path: '/workforce', pageBuilder: (context, state) => M3SharedAxisPage( key: state.pageKey, child: const WorkforceScreen(), ), ), GoRoute( path: '/attendance', pageBuilder: (context, state) => M3SharedAxisPage( key: state.pageKey, child: const AttendanceScreen(), ), ), GoRoute( path: '/whereabouts', pageBuilder: (context, state) => M3SharedAxisPage( key: state.pageKey, child: const WhereaboutsScreen(), ), ), GoRoute( path: '/reports', pageBuilder: (context, state) => M3SharedAxisPage( key: state.pageKey, child: const ReportsScreen(), ), ), GoRoute( path: '/settings/users', pageBuilder: (context, state) => M3SharedAxisPage( key: state.pageKey, child: const UserManagementScreen(), ), ), GoRoute( path: '/settings/offices', pageBuilder: (context, state) => M3SharedAxisPage( key: state.pageKey, child: const OfficesScreen(), ), ), GoRoute( path: '/settings/geofence-test', pageBuilder: (context, state) => M3SharedAxisPage( key: state.pageKey, child: const GeofenceTestScreen(), ), ), GoRoute( path: '/settings/permissions', pageBuilder: (context, state) => M3SharedAxisPage( key: state.pageKey, child: const PermissionsScreen(), ), ), GoRoute( path: '/notifications', pageBuilder: (context, state) => M3SharedAxisPage( key: state.pageKey, child: const NotificationsScreen(), ), ), GoRoute( path: '/profile', pageBuilder: (context, state) => M3SharedAxisPage( key: state.pageKey, child: const ProfileScreen(), ), ), ], ), ], ); }); class RouterNotifier extends ChangeNotifier { RouterNotifier(this.ref) { _authSub = ref.listen(authStateChangesProvider, (previous, next) { // Only enforce lock on successful sign-in events, not on every auth state change if (next is AsyncData) { final authState = next.value; final session = authState?.session; // Only check for bans when we have a session and the previous state didn't final previousSession = previous is AsyncData ? previous.value?.session : null; if (session != null && previousSession == null) { // User just signed in; enforce lock check _enforceLockAsync(); } } notifyListeners(); }); _profileSub = ref.listen(currentProfileProvider, (previous, next) { notifyListeners(); }); } final Ref ref; late final ProviderSubscription _authSub; late final ProviderSubscription _profileSub; bool _lockEnforcementInProgress = false; /// Safely enforce lock in the background, preventing concurrent calls void _enforceLockAsync() { // Prevent concurrent enforcement calls if (_lockEnforcementInProgress) return; _lockEnforcementInProgress = true; // Use Future.microtask to defer execution and avoid blocking Future.microtask(() async { try { await enforceLockForCurrentUser(ref.read(supabaseClientProvider)); } catch (e) { debugPrint('RouterNotifier: lock enforcement error: $e'); } finally { _lockEnforcementInProgress = false; } }); } @override void dispose() { _authSub.close(); _profileSub.close(); super.dispose(); } }