/// Authentication State Provider /// /// Manages authentication state for the Worker application. /// Handles login, logout, and user session management. /// /// Uses Riverpod 3.0 with code generation for type-safe state management. library; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:worker/features/auth/data/datasources/auth_local_datasource.dart'; import 'package:worker/features/auth/data/models/auth_session_model.dart'; import 'package:worker/features/auth/domain/entities/user.dart'; part 'auth_provider.g.dart'; /// Provide FlutterSecureStorage instance @riverpod FlutterSecureStorage secureStorage(Ref ref) { return const FlutterSecureStorage( aOptions: AndroidOptions(encryptedSharedPreferences: true), iOptions: IOSOptions(accessibility: KeychainAccessibility.first_unlock), ); } /// Provide AuthLocalDataSource instance @riverpod AuthLocalDataSource authLocalDataSource(Ref ref) { final secureStorage = ref.watch(secureStorageProvider); return AuthLocalDataSource(secureStorage); } /// Authentication state result /// /// Represents the result of authentication operations. /// Contains either the authenticated user or null if logged out. typedef AuthState = AsyncValue; /// Authentication Provider /// /// Main provider for authentication state management. /// Provides login and logout functionality with async state handling. /// /// Usage in widgets: /// ```dart /// final authState = ref.watch(authProvider); /// authState.when( /// data: (user) => user != null ? HomeScreen() : LoginScreen(), /// loading: () => LoadingIndicator(), /// error: (error, stack) => ErrorWidget(error), /// ); /// ``` @riverpod class Auth extends _$Auth { /// Get auth local data source AuthLocalDataSource get _localDataSource => ref.read(authLocalDataSourceProvider); /// Initialize with saved session if available @override Future build() async { // Check for saved session in secure storage final session = await _localDataSource.getSession(); if (session != null) { // User has saved session, create User entity final now = DateTime.now(); return User( userId: 'user_saved', // TODO: Get from API phoneNumber: '', // TODO: Get from saved user data fullName: session.fullName, email: '', // TODO: Get from saved user data role: UserRole.customer, status: UserStatus.active, loyaltyTier: LoyaltyTier.gold, totalPoints: 0, companyInfo: null, cccd: null, attachments: [], address: null, avatarUrl: null, referralCode: null, referredBy: null, erpnextCustomerId: null, createdAt: session.createdAt, updatedAt: now, lastLoginAt: now, ); } return null; } /// Login with phone number and password /// /// Simulates ERPNext API authentication with mock response. /// Stores session data (SID, CSRF token) in Hive. /// /// Parameters: /// - [phoneNumber]: User's phone number (Vietnamese format) /// - [password]: User's password /// /// Returns: Authenticated User object on success /// /// Throws: Exception on authentication failure Future login({ required String phoneNumber, required String password, }) async { // Set loading state state = const AsyncValue.loading(); // Simulate API call delay state = await AsyncValue.guard(() async { await Future.delayed(const Duration(seconds: 2)); // Mock validation if (phoneNumber.isEmpty || password.isEmpty) { throw Exception('Số điện thoại và mật khẩu không được để trống'); } if (password.length < 6) { throw Exception('Mật khẩu phải có ít nhất 6 ký tự'); } // Simulate API response matching ERPNext format final mockApiResponse = AuthSessionResponse( sessionExpired: 1, message: const LoginMessage( success: true, message: 'Login successful', sid: 'df7fd4e7ef1041aa3422b0ee861315ba8c28d4fe008a7d7e0e7e0e01', csrfToken: '6b6e37563854e951c36a7af4177956bb15ca469ca4f498b742648d70', apps: [ AppInfo( appTitle: 'App nhân viên kinh doanh', appEndpoint: '/ecommerce/app-sales', appLogo: 'https://assets.digitalbiz.com.vn/DBIZ_Internal/Logo/logo_app_sales.png', ), ], ), homePage: '/apps', fullName: 'Tân Duy Nguyễn', ); // Save session data to Hive final sessionData = SessionData.fromAuthResponse(mockApiResponse); await _localDataSource.saveSession(sessionData); // Create and return User entity final now = DateTime.now(); return User( userId: 'user_${phoneNumber.replaceAll('+84', '')}', phoneNumber: phoneNumber, fullName: mockApiResponse.fullName, email: 'user@eurotile.vn', role: UserRole.customer, status: UserStatus.active, loyaltyTier: LoyaltyTier.gold, totalPoints: 1500, companyInfo: const CompanyInfo( name: 'Công ty TNHH XYZ', taxId: '0123456789', businessType: 'Xây dựng', ), cccd: '001234567890', attachments: [], address: '123 Đường ABC, Quận 1, TP.HCM', avatarUrl: null, referralCode: 'REF${phoneNumber.replaceAll('+84', '').substring(0, 6)}', referredBy: null, erpnextCustomerId: null, createdAt: now.subtract(const Duration(days: 30)), updatedAt: now, lastLoginAt: now, ); }); } /// Logout current user /// /// Clears authentication state and removes saved session from Hive. Future logout() async { state = const AsyncValue.loading(); state = await AsyncValue.guard(() async { // Clear saved session from Hive await _localDataSource.clearSession(); // TODO: Call logout API to invalidate token on server await Future.delayed(const Duration(milliseconds: 500)); // Return null to indicate logged out return null; }); } /// Get current authenticated user /// /// Returns the current user if logged in, null otherwise. User? get currentUser => state.value; /// Check if user is authenticated /// /// Returns true if there is a logged-in user. bool get isAuthenticated => currentUser != null; /// Check if authentication is in progress /// /// Returns true during login/logout operations. bool get isLoading => state.isLoading; /// Get authentication error if any /// /// Returns error message or null if no error. Object? get error => state.error; } /// Convenience provider for checking if user is authenticated /// /// Usage: /// ```dart /// final isLoggedIn = ref.watch(isAuthenticatedProvider); /// if (isLoggedIn) { /// // Show home screen /// } /// ``` @riverpod bool isAuthenticated(Ref ref) { final authState = ref.watch(authProvider); return authState.value != null; } /// Convenience provider for getting current user /// /// Usage: /// ```dart /// final user = ref.watch(currentUserProvider); /// if (user != null) { /// Text('Welcome ${user.fullName}'); /// } /// ``` @riverpod User? currentUser(Ref ref) { final authState = ref.watch(authProvider); return authState.value; } /// Convenience provider for user's loyalty tier /// /// Returns the current user's loyalty tier or null if not logged in. /// /// Usage: /// ```dart /// final tier = ref.watch(userLoyaltyTierProvider); /// if (tier != null) { /// Text('Tier: ${tier.displayName}'); /// } /// ``` @riverpod LoyaltyTier? userLoyaltyTier(Ref ref) { final user = ref.watch(currentUserProvider); return user?.loyaltyTier; } /// Convenience provider for user's total points /// /// Returns the current user's total loyalty points or 0 if not logged in. /// /// Usage: /// ```dart /// final points = ref.watch(userTotalPointsProvider); /// Text('Points: $points'); /// ``` @riverpod int userTotalPoints(Ref ref) { final user = ref.watch(currentUserProvider); return user?.totalPoints ?? 0; }