import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../features/auth/data/datasources/auth_remote_datasource.dart'; import '../../features/auth/data/repositories/auth_repository_impl.dart'; import '../../features/auth/domain/repositories/auth_repository.dart'; import '../../features/auth/domain/usecases/login_usecase.dart'; import '../../features/auth/presentation/providers/auth_provider.dart'; import '../../features/products/data/datasources/products_remote_datasource.dart'; import '../../features/products/data/repositories/products_repository_impl.dart'; import '../../features/products/domain/entities/product_stage_entity.dart'; import '../../features/products/domain/repositories/products_repository.dart'; import '../../features/products/domain/usecases/get_product_detail_usecase.dart'; import '../../features/products/domain/usecases/get_products_usecase.dart'; import '../../features/products/presentation/providers/product_detail_provider.dart'; import '../../features/products/presentation/providers/products_provider.dart'; import '../../features/warehouse/data/datasources/warehouse_remote_datasource.dart'; import '../../features/warehouse/data/repositories/warehouse_repository_impl.dart'; import '../../features/warehouse/domain/repositories/warehouse_repository.dart'; import '../../features/warehouse/domain/usecases/get_warehouses_usecase.dart'; import '../../features/warehouse/presentation/providers/warehouse_provider.dart'; import '../../features/users/data/datasources/users_local_datasource.dart'; import '../../features/users/data/datasources/users_remote_datasource.dart'; import '../../features/users/data/repositories/users_repository_impl.dart'; import '../../features/users/domain/repositories/users_repository.dart'; import '../../features/users/domain/usecases/get_users_usecase.dart'; import '../../features/users/domain/usecases/sync_users_usecase.dart'; import '../../features/users/presentation/providers/users_provider.dart'; import '../../features/users/presentation/providers/users_state.dart'; import '../network/api_client.dart'; import '../storage/secure_storage.dart'; /// ======================================================================== /// CORE PROVIDERS /// ======================================================================== /// These are singleton providers for core infrastructure services /// Secure storage provider (Singleton) /// Provides secure storage for sensitive data like tokens final secureStorageProvider = Provider((ref) { return SecureStorage(); }); /// API client provider (Singleton) /// Provides HTTP client with authentication and error handling /// Depends on SecureStorage for token management final apiClientProvider = Provider((ref) { final secureStorage = ref.watch(secureStorageProvider); return ApiClient(secureStorage); }); /// ======================================================================== /// AUTH FEATURE PROVIDERS /// ======================================================================== /// Providers for authentication feature following clean architecture // Data Layer /// Auth remote data source provider /// Handles API calls for authentication final authRemoteDataSourceProvider = Provider((ref) { final apiClient = ref.watch(apiClientProvider); return AuthRemoteDataSourceImpl(apiClient); }); /// Auth repository provider /// Implements domain repository interface /// Coordinates between data sources and handles error conversion final authRepositoryProvider = Provider((ref) { final remoteDataSource = ref.watch(authRemoteDataSourceProvider); final secureStorage = ref.watch(secureStorageProvider); return AuthRepositoryImpl( remoteDataSource: remoteDataSource, secureStorage: secureStorage, ); }); // Domain Layer /// Login use case provider /// Encapsulates login business logic final loginUseCaseProvider = Provider((ref) { final repository = ref.watch(authRepositoryProvider); return LoginUseCase(repository); }); /// Logout use case provider /// Encapsulates logout business logic final logoutUseCaseProvider = Provider((ref) { final repository = ref.watch(authRepositoryProvider); return LogoutUseCase(repository); }); /// Check auth status use case provider /// Checks if user is authenticated final checkAuthStatusUseCaseProvider = Provider((ref) { final repository = ref.watch(authRepositoryProvider); return CheckAuthStatusUseCase(repository); }); /// Get current user use case provider /// Retrieves current user data from storage final getCurrentUserUseCaseProvider = Provider((ref) { final repository = ref.watch(authRepositoryProvider); return GetCurrentUserUseCase(repository); }); /// Refresh token use case provider /// Refreshes access token using refresh token final refreshTokenUseCaseProvider = Provider((ref) { final repository = ref.watch(authRepositoryProvider); return RefreshTokenUseCase(repository); }); // Presentation Layer /// Auth state notifier provider /// Manages authentication state across the app /// This is the main provider to use in UI for auth state final authProvider = StateNotifierProvider((ref) { final loginUseCase = ref.watch(loginUseCaseProvider); final logoutUseCase = ref.watch(logoutUseCaseProvider); final checkAuthStatusUseCase = ref.watch(checkAuthStatusUseCaseProvider); final getCurrentUserUseCase = ref.watch(getCurrentUserUseCaseProvider); return AuthNotifier( loginUseCase: loginUseCase, logoutUseCase: logoutUseCase, checkAuthStatusUseCase: checkAuthStatusUseCase, getCurrentUserUseCase: getCurrentUserUseCase, ); }); /// Convenient providers for auth state /// Provider to check if user is authenticated /// Usage: ref.watch(isAuthenticatedProvider) final isAuthenticatedProvider = Provider((ref) { final authState = ref.watch(authProvider); return authState.isAuthenticated; }); /// Provider to get current user /// Returns null if user is not authenticated /// Usage: ref.watch(currentUserProvider) final currentUserProvider = Provider((ref) { final authState = ref.watch(authProvider); return authState.user; }); /// Provider to check if auth is loading /// Usage: ref.watch(isAuthLoadingProvider) final isAuthLoadingProvider = Provider((ref) { final authState = ref.watch(authProvider); return authState.isLoading; }); /// Provider to get auth error /// Returns null if no error /// Usage: ref.watch(authErrorProvider) final authErrorProvider = Provider((ref) { final authState = ref.watch(authProvider); return authState.error; }); /// ======================================================================== /// WAREHOUSE FEATURE PROVIDERS /// ======================================================================== /// Providers for warehouse feature following clean architecture // Data Layer /// Warehouse remote data source provider /// Handles API calls for warehouses final warehouseRemoteDataSourceProvider = Provider((ref) { final apiClient = ref.watch(apiClientProvider); return WarehouseRemoteDataSourceImpl(apiClient); }); /// Warehouse repository provider /// Implements domain repository interface final warehouseRepositoryProvider = Provider((ref) { final remoteDataSource = ref.watch(warehouseRemoteDataSourceProvider); return WarehouseRepositoryImpl(remoteDataSource); }); // Domain Layer /// Get warehouses use case provider /// Encapsulates warehouse fetching business logic final getWarehousesUseCaseProvider = Provider((ref) { final repository = ref.watch(warehouseRepositoryProvider); return GetWarehousesUseCase(repository); }); // Presentation Layer /// Warehouse state notifier provider /// Manages warehouse state including list and selection final warehouseProvider = StateNotifierProvider((ref) { final getWarehousesUseCase = ref.watch(getWarehousesUseCaseProvider); return WarehouseNotifier(getWarehousesUseCase); }); /// Convenient providers for warehouse state /// Provider to get list of warehouses /// Usage: ref.watch(warehousesListProvider) final warehousesListProvider = Provider((ref) { final warehouseState = ref.watch(warehouseProvider); return warehouseState.warehouses; }); /// Provider to get selected warehouse /// Returns null if no warehouse is selected /// Usage: ref.watch(selectedWarehouseProvider) final selectedWarehouseProvider = Provider((ref) { final warehouseState = ref.watch(warehouseProvider); return warehouseState.selectedWarehouse; }); /// Provider to check if warehouses are loading /// Usage: ref.watch(isWarehouseLoadingProvider) final isWarehouseLoadingProvider = Provider((ref) { final warehouseState = ref.watch(warehouseProvider); return warehouseState.isLoading; }); /// Provider to check if warehouses have been loaded /// Usage: ref.watch(hasWarehousesProvider) final hasWarehousesProvider = Provider((ref) { final warehouseState = ref.watch(warehouseProvider); return warehouseState.hasWarehouses; }); /// Provider to check if a warehouse is selected /// Usage: ref.watch(hasWarehouseSelectionProvider) final hasWarehouseSelectionProvider = Provider((ref) { final warehouseState = ref.watch(warehouseProvider); return warehouseState.hasSelection; }); /// Provider to get warehouse error /// Returns null if no error /// Usage: ref.watch(warehouseErrorProvider) final warehouseErrorProvider = Provider((ref) { final warehouseState = ref.watch(warehouseProvider); return warehouseState.error; }); /// ======================================================================== /// PRODUCTS FEATURE PROVIDERS /// ======================================================================== /// Providers for products feature following clean architecture // Data Layer /// Products remote data source provider /// Handles API calls for products final productsRemoteDataSourceProvider = Provider((ref) { final apiClient = ref.watch(apiClientProvider); return ProductsRemoteDataSourceImpl(apiClient); }); /// Products repository provider /// Implements domain repository interface final productsRepositoryProvider = Provider((ref) { final remoteDataSource = ref.watch(productsRemoteDataSourceProvider); return ProductsRepositoryImpl(remoteDataSource); }); // Domain Layer /// Get products use case provider /// Encapsulates product fetching business logic final getProductsUseCaseProvider = Provider((ref) { final repository = ref.watch(productsRepositoryProvider); return GetProductsUseCase(repository); }); /// Get product detail use case provider /// Encapsulates product detail fetching business logic final getProductDetailUseCaseProvider = Provider((ref) { final repository = ref.watch(productsRepositoryProvider); return GetProductDetailUseCase(repository); }); // Presentation Layer /// Products state notifier provider /// Manages products state including list, loading, and errors final productsProvider = StateNotifierProvider((ref) { final getProductsUseCase = ref.watch(getProductsUseCaseProvider); return ProductsNotifier(getProductsUseCase); }); /// Convenient providers for products state /// Provider to get list of products /// Usage: ref.watch(productsListProvider) final productsListProvider = Provider((ref) { final productsState = ref.watch(productsProvider); return productsState.products; }); /// Provider to get operation type (import/export) /// Usage: ref.watch(operationTypeProvider) final operationTypeProvider = Provider((ref) { final productsState = ref.watch(productsProvider); return productsState.operationType; }); /// Provider to get warehouse ID for products /// Returns null if no warehouse is set /// Usage: ref.watch(productsWarehouseIdProvider) final productsWarehouseIdProvider = Provider((ref) { final productsState = ref.watch(productsProvider); return productsState.warehouseId; }); /// Provider to get warehouse name for products /// Returns null if no warehouse is set /// Usage: ref.watch(productsWarehouseNameProvider) final productsWarehouseNameProvider = Provider((ref) { final productsState = ref.watch(productsProvider); return productsState.warehouseName; }); /// Provider to check if products are loading /// Usage: ref.watch(isProductsLoadingProvider) final isProductsLoadingProvider = Provider((ref) { final productsState = ref.watch(productsProvider); return productsState.isLoading; }); /// Provider to check if products list has items /// Usage: ref.watch(hasProductsProvider) final hasProductsProvider = Provider((ref) { final productsState = ref.watch(productsProvider); return productsState.products.isNotEmpty; }); /// Provider to get products count /// Usage: ref.watch(productsCountProvider) final productsCountProvider = Provider((ref) { final productsState = ref.watch(productsProvider); return productsState.products.length; }); /// Provider to get products error /// Returns null if no error /// Usage: ref.watch(productsErrorProvider) final productsErrorProvider = Provider((ref) { final productsState = ref.watch(productsProvider); return productsState.error; }); /// Product detail state notifier provider /// Manages product detail state for a specific product in a warehouse /// This needs to be a family provider to support multiple product details final productDetailProvider = StateNotifierProvider.family( (ref, _) { final getProductDetailUseCase = ref.watch(getProductDetailUseCaseProvider); return ProductDetailNotifier(getProductDetailUseCase); }, ); /// Convenient providers for product detail state /// Provider to get product stages list /// Usage: ref.watch(productStagesProvider(key)) final productStagesProvider = Provider.family, String>((ref, key) { final state = ref.watch(productDetailProvider(key)); return state.stages; }); /// Provider to get selected product stage /// Usage: ref.watch(selectedProductStageProvider(key)) final selectedProductStageProvider = Provider.family((ref, key) { final state = ref.watch(productDetailProvider(key)); return state.selectedStage; }); /// Provider to check if product detail is loading /// Usage: ref.watch(isProductDetailLoadingProvider(key)) final isProductDetailLoadingProvider = Provider.family((ref, key) { final state = ref.watch(productDetailProvider(key)); return state.isLoading; }); /// Provider to get product detail error /// Returns null if no error /// Usage: ref.watch(productDetailErrorProvider(key)) final productDetailErrorProvider = Provider.family((ref, key) { final state = ref.watch(productDetailProvider(key)); return state.error; }); /// ======================================================================== /// USERS FEATURE PROVIDERS /// ======================================================================== /// Providers for users feature following clean architecture // Data Layer /// Users local data source provider /// Handles local storage operations for users using Hive final usersLocalDataSourceProvider = Provider((ref) { return UsersLocalDataSourceImpl(); }); /// Users remote data source provider /// Handles API calls for users final usersRemoteDataSourceProvider = Provider((ref) { final apiClient = ref.watch(apiClientProvider); return UsersRemoteDataSourceImpl(apiClient); }); /// Users repository provider /// Implements domain repository interface /// Coordinates between local and remote data sources final usersRepositoryProvider = Provider((ref) { final remoteDataSource = ref.watch(usersRemoteDataSourceProvider); final localDataSource = ref.watch(usersLocalDataSourceProvider); return UsersRepositoryImpl( remoteDataSource: remoteDataSource, localDataSource: localDataSource, ); }); // Domain Layer /// Get users use case provider /// Encapsulates user fetching business logic (local-first strategy) final getUsersUseCaseProvider = Provider((ref) { final repository = ref.watch(usersRepositoryProvider); return GetUsersUseCase(repository); }); /// Sync users use case provider /// Encapsulates user syncing business logic (force refresh from API) final syncUsersUseCaseProvider = Provider((ref) { final repository = ref.watch(usersRepositoryProvider); return SyncUsersUseCase(repository); }); // Presentation Layer /// Users state notifier provider /// Manages users state including list, loading, and errors final usersProvider = StateNotifierProvider((ref) { final getUsersUseCase = ref.watch(getUsersUseCaseProvider); final syncUsersUseCase = ref.watch(syncUsersUseCaseProvider); return UsersNotifier( getUsersUseCase: getUsersUseCase, syncUsersUseCase: syncUsersUseCase, ); }); /// Convenient providers for users state /// Provider to get list of users /// Usage: ref.watch(usersListProvider) final usersListProvider = Provider((ref) { final usersState = ref.watch(usersProvider); return usersState.users; }); /// Provider to check if users are loading /// Usage: ref.watch(isUsersLoadingProvider) final isUsersLoadingProvider = Provider((ref) { final usersState = ref.watch(usersProvider); return usersState.isLoading; }); /// Provider to check if users list has items /// Usage: ref.watch(hasUsersProvider) final hasUsersProvider = Provider((ref) { final usersState = ref.watch(usersProvider); return usersState.users.isNotEmpty; }); /// Provider to get users count /// Usage: ref.watch(usersCountProvider) final usersCountProvider = Provider((ref) { final usersState = ref.watch(usersProvider); return usersState.users.length; }); /// Provider to get users error /// Returns null if no error /// Usage: ref.watch(usersErrorProvider) final usersErrorProvider = Provider((ref) { final usersState = ref.watch(usersProvider); return usersState.error; }); /// ======================================================================== /// USAGE EXAMPLES /// ======================================================================== /// /// 1. Authentication Example: /// ```dart /// // In your LoginPage /// class LoginPage extends ConsumerWidget { /// @override /// Widget build(BuildContext context, WidgetRef ref) { /// final isAuthenticated = ref.watch(isAuthenticatedProvider); /// final isLoading = ref.watch(isAuthLoadingProvider); /// final error = ref.watch(authErrorProvider); /// /// return Scaffold( /// body: Column( /// children: [ /// if (error != null) Text(error, style: errorStyle), /// ElevatedButton( /// onPressed: isLoading /// ? null /// : () => ref.read(authProvider.notifier).login( /// username, /// password, /// ), /// child: isLoading ? CircularProgressIndicator() : Text('Login'), /// ), /// ], /// ), /// ); /// } /// } /// ``` /// /// 2. Warehouse Selection Example: /// ```dart /// // In your WarehouseSelectionPage /// class WarehouseSelectionPage extends ConsumerWidget { /// @override /// Widget build(BuildContext context, WidgetRef ref) { /// final warehouses = ref.watch(warehousesListProvider); /// final isLoading = ref.watch(isWarehouseLoadingProvider); /// final selectedWarehouse = ref.watch(selectedWarehouseProvider); /// /// // Load warehouses on first build /// ref.listen(warehouseProvider, (previous, next) { /// if (previous?.warehouses.isEmpty ?? true && !next.isLoading) { /// ref.read(warehouseProvider.notifier).loadWarehouses(); /// } /// }); /// /// return Scaffold( /// body: isLoading /// ? CircularProgressIndicator() /// : ListView.builder( /// itemCount: warehouses.length, /// itemBuilder: (context, index) { /// final warehouse = warehouses[index]; /// return ListTile( /// title: Text(warehouse.name), /// selected: selectedWarehouse?.id == warehouse.id, /// onTap: () { /// ref.read(warehouseProvider.notifier) /// .selectWarehouse(warehouse); /// }, /// ); /// }, /// ), /// ); /// } /// } /// ``` /// /// 3. Products List Example: /// ```dart /// // In your ProductsPage /// class ProductsPage extends ConsumerWidget { /// final int warehouseId; /// final String warehouseName; /// final String operationType; /// /// const ProductsPage({ /// required this.warehouseId, /// required this.warehouseName, /// required this.operationType, /// }); /// /// @override /// Widget build(BuildContext context, WidgetRef ref) { /// final products = ref.watch(productsListProvider); /// final isLoading = ref.watch(isProductsLoadingProvider); /// final error = ref.watch(productsErrorProvider); /// /// // Load products on first build /// useEffect(() { /// ref.read(productsProvider.notifier).loadProducts( /// warehouseId, /// warehouseName, /// operationType, /// ); /// return null; /// }, []); /// /// return Scaffold( /// appBar: AppBar( /// title: Text('$warehouseName - $operationType'), /// ), /// body: isLoading /// ? CircularProgressIndicator() /// : error != null /// ? Text(error) /// : ListView.builder( /// itemCount: products.length, /// itemBuilder: (context, index) { /// final product = products[index]; /// return ListTile( /// title: Text(product.name), /// subtitle: Text(product.code), /// ); /// }, /// ), /// ); /// } /// } /// ``` /// /// 4. Checking Auth Status on App Start: /// ```dart /// // In your main.dart or root widget /// class App extends ConsumerWidget { /// @override /// Widget build(BuildContext context, WidgetRef ref) { /// // Check auth status when app starts /// useEffect(() { /// ref.read(authProvider.notifier).checkAuthStatus(); /// return null; /// }, []); /// /// final isAuthenticated = ref.watch(isAuthenticatedProvider); /// /// return MaterialApp( /// home: isAuthenticated ? WarehouseSelectionPage() : LoginPage(), /// ); /// } /// } /// ``` /// /// 5. Logout Example: /// ```dart /// // In any widget /// ElevatedButton( /// onPressed: () { /// ref.read(authProvider.notifier).logout(); /// }, /// child: Text('Logout'), /// ) /// ``` /// /// ======================================================================== /// ARCHITECTURE NOTES /// ======================================================================== /// /// This DI setup follows Clean Architecture principles: /// /// 1. **Separation of Concerns**: /// - Data Layer: Handles API calls and data storage /// - Domain Layer: Contains business logic and use cases /// - Presentation Layer: Manages UI state /// /// 2. **Dependency Direction**: /// - Presentation depends on Domain /// - Data depends on Domain /// - Domain depends on nothing (pure business logic) /// /// 3. **Provider Hierarchy**: /// - Core providers (Storage, API) are singletons /// - Data sources depend on API client /// - Repositories depend on data sources /// - Use cases depend on repositories /// - State notifiers depend on use cases /// /// 4. **State Management**: /// - StateNotifierProvider for mutable state /// - Provider for immutable dependencies /// - Convenient providers for derived state /// /// 5. **Testability**: /// - All dependencies are injected /// - Easy to mock for testing /// - Each layer can be tested independently /// /// 6. **Scalability**: /// - Add new features by following the same pattern /// - Clear structure for team collaboration /// - Easy to understand and maintain ///