import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import '../../features/auth/presentation/pages/login_page.dart'; import '../../features/auth/di/auth_dependency_injection.dart'; import '../../features/warehouse/presentation/pages/warehouse_selection_page.dart'; import '../../features/operation/presentation/pages/operation_selection_page.dart'; import '../../features/products/presentation/pages/products_page.dart'; import '../../features/products/presentation/pages/product_detail_page.dart'; import '../../features/warehouse/domain/entities/warehouse_entity.dart'; import '../storage/secure_storage.dart'; /// Application router configuration using GoRouter /// /// Implements authentication-based redirect logic: /// - Unauthenticated users are redirected to /login /// - Authenticated users on /login are redirected to /warehouses /// - Proper parameter passing between routes /// /// App Flow: Login → Warehouses → Operations → Products class AppRouter { final Ref ref; final SecureStorage secureStorage; AppRouter({ required this.ref, required this.secureStorage, }); late final GoRouter router = GoRouter( debugLogDiagnostics: true, initialLocation: '/login', refreshListenable: GoRouterRefreshStream(ref), redirect: _handleRedirect, routes: [ // ==================== Auth Routes ==================== /// Login Route /// Path: /login /// Initial route for unauthenticated users GoRoute( path: '/login', name: 'login', builder: (context, state) => const LoginPage(), ), // ==================== Main App Routes ==================== /// Warehouse Selection Route /// Path: /warehouses /// Shows list of available warehouses after login GoRoute( path: '/warehouses', name: 'warehouses', builder: (context, state) => const WarehouseSelectionPage(), ), /// Operation Selection Route /// Path: /operations /// Takes warehouse data as extra parameter /// Shows Import/Export operation options for selected warehouse GoRoute( path: '/operations', name: 'operations', builder: (context, state) { final warehouse = state.extra as WarehouseEntity?; if (warehouse == null) { // If no warehouse data, redirect to warehouses WidgetsBinding.instance.addPostFrameCallback((_) { context.go('/warehouses'); }); return const _ErrorScreen( message: 'Warehouse data is required', ); } return OperationSelectionPage(warehouse: warehouse); }, ), /// Products List Route /// Path: /products /// Takes warehouse, warehouseName, and operationType as extra parameter /// Shows products for selected warehouse and operation GoRoute( path: '/products', name: 'products', builder: (context, state) { final params = state.extra as Map?; if (params == null) { // If no params, redirect to warehouses WidgetsBinding.instance.addPostFrameCallback((_) { context.go('/warehouses'); }); return const _ErrorScreen( message: 'Product parameters are required', ); } // Extract required parameters final warehouse = params['warehouse'] as WarehouseEntity?; final warehouseName = params['warehouseName'] as String?; final operationType = params['operationType'] as String?; // Validate parameters if (warehouse == null || warehouseName == null || operationType == null) { WidgetsBinding.instance.addPostFrameCallback((_) { context.go('/warehouses'); }); return const _ErrorScreen( message: 'Invalid product parameters', ); } return ProductsPage( warehouseId: warehouse.id, warehouseName: warehouseName, operationType: operationType, ); }, ), /// Product Detail Route /// Path: /product-detail /// Takes warehouseId, productId, warehouseName, and optional stageId as extra parameter /// Shows detailed information for a specific product /// If stageId is provided, only that stage is shown, otherwise all stages are shown GoRoute( path: '/product-detail', name: 'product-detail', builder: (context, state) { final params = state.extra as Map?; if (params == null) { // If no params, redirect to warehouses WidgetsBinding.instance.addPostFrameCallback((_) { context.go('/warehouses'); }); return const _ErrorScreen( message: 'Product detail parameters are required', ); } // Extract required parameters final warehouseId = params['warehouseId'] as int?; final productId = params['productId'] as int?; final warehouseName = params['warehouseName'] as String?; // Extract optional stageId final stageId = params['stageId'] as int?; // Validate parameters if (warehouseId == null || productId == null || warehouseName == null) { WidgetsBinding.instance.addPostFrameCallback((_) { context.go('/warehouses'); }); return const _ErrorScreen( message: 'Invalid product detail parameters', ); } return ProductDetailPage( warehouseId: warehouseId, productId: productId, warehouseName: warehouseName, stageId: stageId, ); }, ), ], // ==================== Error Handling ==================== errorBuilder: (context, state) { return Scaffold( appBar: AppBar( title: const Text('Page Not Found'), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( Icons.error_outline, size: 64, color: Theme.of(context).colorScheme.error, ), const SizedBox(height: 16), Text( 'Page Not Found', style: Theme.of(context).textTheme.headlineSmall, ), const SizedBox(height: 8), Text( 'The page "${state.uri.path}" does not exist.', style: Theme.of(context).textTheme.bodyMedium?.copyWith( color: Theme.of(context).colorScheme.onSurfaceVariant, ), textAlign: TextAlign.center, ), const SizedBox(height: 24), ElevatedButton( onPressed: () => context.go('/login'), child: const Text('Go to Login'), ), ], ), ), ); }, ); /// Handle global redirect logic based on authentication status /// /// Redirect rules: /// 1. Check authentication status using SecureStorage /// 2. If not authenticated and not on login page → redirect to /login /// 3. If authenticated and on login page → redirect to /warehouses /// 4. Otherwise, allow navigation Future _handleRedirect( BuildContext context, GoRouterState state, ) async { try { // Check if user has access token final isAuthenticated = await secureStorage.isAuthenticated(); final isOnLoginPage = state.matchedLocation == '/login'; // User is not authenticated if (!isAuthenticated) { // Allow access to login page if (isOnLoginPage) { return null; } // Redirect to login for all other pages return '/login'; } // User is authenticated if (isAuthenticated) { // Redirect away from login page to warehouses if (isOnLoginPage) { return '/warehouses'; } // Allow access to all other pages return null; } return null; } catch (e) { // On error, redirect to login for safety debugPrint('Error in redirect: $e'); return '/login'; } } } /// Provider for AppRouter /// /// Creates and provides the GoRouter instance with dependencies final appRouterProvider = Provider((ref) { final secureStorage = SecureStorage(); final appRouter = AppRouter( ref: ref, secureStorage: secureStorage, ); return appRouter.router; }); /// Helper class to refresh router when auth state changes /// /// This allows GoRouter to react to authentication state changes /// and re-evaluate redirect logic class GoRouterRefreshStream extends ChangeNotifier { final Ref ref; GoRouterRefreshStream(this.ref) { // Listen to auth state changes // When auth state changes, notify GoRouter to re-evaluate redirects ref.listen( authProvider, (_, __) => notifyListeners(), ); } } /// Error screen widget for route parameter validation errors class _ErrorScreen extends StatelessWidget { final String message; const _ErrorScreen({required this.message}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Error'), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( Icons.error_outline, size: 64, color: Theme.of(context).colorScheme.error, ), const SizedBox(height: 16), Text( 'Navigation Error', style: Theme.of(context).textTheme.headlineSmall, ), const SizedBox(height: 8), Padding( padding: const EdgeInsets.symmetric(horizontal: 24), child: Text( message, style: Theme.of(context).textTheme.bodyMedium?.copyWith( color: Theme.of(context).colorScheme.onSurfaceVariant, ), textAlign: TextAlign.center, ), ), const SizedBox(height: 24), ElevatedButton( onPressed: () => context.go('/warehouses'), child: const Text('Go to Warehouses'), ), ], ), ), ); } } /// Extension methods for easier type-safe navigation /// /// Usage: /// ```dart /// context.goToLogin(); /// context.goToWarehouses(); /// context.goToOperations(warehouse); /// context.goToProducts(warehouse, 'import'); /// ``` extension AppRouterExtension on BuildContext { /// Navigate to login page void goToLogin() => go('/login'); /// Navigate to warehouses list void goToWarehouses() => go('/warehouses'); /// Navigate to operation selection with warehouse data void goToOperations(WarehouseEntity warehouse) { go('/operations', extra: warehouse); } /// Navigate to products list with required parameters /// /// [warehouse] - Selected warehouse entity /// [operationType] - Either 'import' or 'export' void goToProducts({ required WarehouseEntity warehouse, required String operationType, }) { go( '/products', extra: { 'warehouse': warehouse, 'warehouseName': warehouse.name, 'operationType': operationType, }, ); } /// Navigate to product detail page /// /// [warehouseId] - ID of the warehouse /// [productId] - ID of the product to view /// [warehouseName] - Name of the warehouse (for display) /// [stageId] - Optional ID of specific stage to show (if null, show all stages) void goToProductDetail({ required int warehouseId, required int productId, required String warehouseName, int? stageId, }) { push( '/product-detail', extra: { 'warehouseId': warehouseId, 'productId': productId, 'warehouseName': warehouseName, if (stageId != null) 'stageId': stageId, }, ); } /// Pop current route void goBack() => pop(); } /// Extension for named route navigation extension AppRouterNamedExtension on BuildContext { /// Navigate to login page using named route void goToLoginNamed() => goNamed('login'); /// Navigate to warehouses using named route void goToWarehousesNamed() => goNamed('warehouses'); /// Navigate to operations using named route with warehouse void goToOperationsNamed(WarehouseEntity warehouse) { goNamed('operations', extra: warehouse); } /// Navigate to products using named route with parameters void goToProductsNamed({ required WarehouseEntity warehouse, required String operationType, }) { goNamed( 'products', extra: { 'warehouse': warehouse, 'warehouseName': warehouse.name, 'operationType': operationType, }, ); } }