import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../features/authentication/domain/repositories/auth_repository.dart'; import '../features/authentication/data/models/auth_user.dart'; import '../core/errors/auth_exceptions.dart'; /// Authentication state class /// /// Represents the current authentication state with loading and error information class AuthState { final AuthUser? user; final bool isLoading; final String? error; final bool isAuthenticated; const AuthState({ this.user, this.isLoading = false, this.error, }) : isAuthenticated = user != null; /// Creates a copy of AuthState with updated values AuthState copyWith({ AuthUser? user, bool? isLoading, String? error, bool clearError = false, }) { return AuthState( user: user ?? this.user, isLoading: isLoading ?? this.isLoading, error: clearError ? null : (error ?? this.error), ); } @override bool operator ==(Object other) { if (identical(this, other)) return true; return other is AuthState && other.user == user && other.isLoading == isLoading && other.error == error; } @override int get hashCode { return user.hashCode ^ isLoading.hashCode ^ error.hashCode; } @override String toString() { return 'AuthState(user: $user, isLoading: $isLoading, error: $error)'; } } /// Authentication provider that manages global auth state /// /// This provider uses Riverpod to manage authentication state throughout the app. /// It depends on AuthRepository and provides a clean interface for UI components /// to interact with authentication functionality. class AuthProvider extends StateNotifier { final AuthRepository _authRepository; StreamSubscription? _authSubscription; /// Creates a new AuthProvider /// /// [authRepository] - The authentication repository to use AuthProvider(this._authRepository) : super(const AuthState()) { _initializeAuthState(); } /// Initialize authentication state by listening to auth changes void _initializeAuthState() { _authSubscription = _authRepository.authStateChanges().listen( (user) { state = state.copyWith( user: user, clearError: true, ); }, onError: (error) { state = state.copyWith( error: error.toString(), isLoading: false, ); }, ); } /// Signs up a new user with email and password /// /// [email] - User's email address /// [password] - User's password /// /// Throws AuthException if registration fails Future signUp(String email, String password) async { if (state.isLoading) return; state = state.copyWith(isLoading: true, clearError: true); try { final user = await _authRepository.signUp(email, password); state = state.copyWith( user: user, isLoading: false, ); } catch (e) { state = state.copyWith( isLoading: false, error: e.toString(), ); rethrow; } } /// Signs in an existing user with email and password /// /// [email] - User's email address /// [password] - User's password /// /// Throws AuthException if sign in fails Future signIn(String email, String password) async { if (state.isLoading) return; state = state.copyWith(isLoading: true, clearError: true); try { final user = await _authRepository.signIn(email, password); state = state.copyWith( user: user, isLoading: false, ); } catch (e) { state = state.copyWith( isLoading: false, error: e.toString(), ); rethrow; } } /// Signs out the current user /// /// Clears authentication state and signs out from repository Future signOut() async { if (state.isLoading) return; state = state.copyWith(isLoading: true, clearError: true); try { await _authRepository.signOut(); state = state.copyWith( user: null, isLoading: false, ); } catch (e) { state = state.copyWith( isLoading: false, error: e.toString(), ); rethrow; } } /// Sends a password reset email /// /// [email] - User's email address /// /// Throws AuthException if reset fails Future resetPassword(String email) async { if (state.isLoading) return; state = state.copyWith(isLoading: true, clearError: true); try { await _authRepository.resetPassword(email); state = state.copyWith(isLoading: false); } catch (e) { state = state.copyWith( isLoading: false, error: e.toString(), ); rethrow; } } /// Gets the current authenticated user /// /// Updates state with current user information Future getCurrentUser() async { if (state.isLoading) return; state = state.copyWith(isLoading: true, clearError: true); try { final user = await _authRepository.getCurrentUser(); state = state.copyWith( user: user, isLoading: false, ); } catch (e) { state = state.copyWith( isLoading: false, error: e.toString(), ); rethrow; } } /// Refreshes the current authentication session /// /// Updates user data and extends session if possible Future refreshSession() async { if (state.isLoading || state.user == null) return; state = state.copyWith(isLoading: true, clearError: true); try { final user = await _authRepository.refreshSession(); state = state.copyWith( user: user, isLoading: false, ); } catch (e) { state = state.copyWith( isLoading: false, error: e.toString(), ); rethrow; } } /// Updates user profile information /// /// [displayName] - Optional new display name /// [avatarUrl] - Optional new avatar URL /// /// Throws AuthException if update fails Future updateProfile({ String? displayName, String? avatarUrl, }) async { if (state.isLoading || state.user == null) return; state = state.copyWith(isLoading: true, clearError: true); try { final user = await _authRepository.updateProfile( displayName: displayName, avatarUrl: avatarUrl, ); state = state.copyWith( user: user, isLoading: false, ); } catch (e) { state = state.copyWith( isLoading: false, error: e.toString(), ); rethrow; } } /// Sends email verification to current user /// /// Throws AuthException if sending fails Future sendEmailVerification() async { if (state.isLoading || state.user == null) return; state = state.copyWith(isLoading: true, clearError: true); try { await _authRepository.sendEmailVerification(); state = state.copyWith(isLoading: false); } catch (e) { state = state.copyWith( isLoading: false, error: e.toString(), ); rethrow; } } /// Changes user's password /// /// [currentPassword] - User's current password /// [newPassword] - User's new password /// /// Throws AuthException if change fails Future changePassword(String currentPassword, String newPassword) async { if (state.isLoading || state.user == null) return; state = state.copyWith(isLoading: true, clearError: true); try { await _authRepository.changePassword(currentPassword, newPassword); state = state.copyWith(isLoading: false); } catch (e) { state = state.copyWith( isLoading: false, error: e.toString(), ); rethrow; } } /// Deletes user's account /// /// This is a destructive operation and cannot be undone /// /// Throws AuthException if deletion fails Future deleteAccount() async { if (state.isLoading || state.user == null) return; state = state.copyWith(isLoading: true, clearError: true); try { await _authRepository.deleteAccount(); state = state.copyWith( user: null, isLoading: false, ); } catch (e) { state = state.copyWith( isLoading: false, error: e.toString(), ); rethrow; } } /// Checks if user's email is verified /// /// Returns true if email is verified, false if not, null if user not authenticated Future isEmailVerified() async { if (state.user == null) return null; try { return await _authRepository.isEmailVerified(); } catch (e) { state = state.copyWith(error: e.toString()); return null; } } /// Signs in with OAuth provider /// /// [provider] - The OAuth provider (e.g., 'google', 'github', 'apple') /// /// Throws AuthException if sign in fails Future signInWithOAuth(String provider) async { if (state.isLoading) return; state = state.copyWith(isLoading: true, clearError: true); try { final user = await _authRepository.signInWithOAuth(provider); state = state.copyWith( user: user, isLoading: false, ); } catch (e) { state = state.copyWith( isLoading: false, error: e.toString(), ); rethrow; } } /// Signs in anonymously /// /// Creates an anonymous user session that can be upgraded later /// /// Throws AuthException if sign in fails Future signInAnonymously() async { if (state.isLoading) return; state = state.copyWith(isLoading: true, clearError: true); try { final user = await _authRepository.signInAnonymously(); state = state.copyWith( user: user, isLoading: false, ); } catch (e) { state = state.copyWith( isLoading: false, error: e.toString(), ); rethrow; } } /// Updates password from reset token (password reset flow) /// /// [newPassword] - The new password to set /// /// Throws AuthException if update fails Future updatePasswordFromReset(String newPassword) async { if (state.isLoading) return; state = state.copyWith(isLoading: true, clearError: true); try { await _authRepository.updatePasswordFromReset(newPassword); state = state.copyWith(isLoading: false); } catch (e) { state = state.copyWith( isLoading: false, error: e.toString(), ); rethrow; } } /// Clears any authentication error void clearError() { state = state.copyWith(clearError: true); } /// Dispose of the provider and cancel subscriptions @override void dispose() { _authSubscription?.cancel(); super.dispose(); } } /// Provider for AuthRepository /// /// This provider creates and manages the AuthRepository instance final authRepositoryProvider = Provider((ref) { return AuthRepositoryImpl(); }); /// Provider for AuthProvider /// /// This is the main provider that UI components should use to access /// authentication state and methods final authProvider = StateNotifierProvider((ref) { final authRepository = ref.watch(authRepositoryProvider); return AuthProvider(authRepository); }); /// Provider for current authentication state /// /// Convenience provider for accessing just the state without methods final authStateProvider = Provider((ref) { return ref.watch(authProvider); }); /// Provider for current authenticated user /// /// Convenience provider for accessing just the user final currentUserProvider = Provider((ref) { return ref.watch(authStateProvider).user; }); /// Provider for authentication status /// /// Convenience provider for checking if user is authenticated final isAuthenticatedProvider = Provider((ref) { return ref.watch(authStateProvider).isAuthenticated; }); /// Provider for loading state /// /// Convenience provider for checking if auth operations are loading final authLoadingProvider = Provider((ref) { return ref.watch(authStateProvider).isLoading; }); /// Provider for authentication error /// /// Convenience provider for accessing any auth error final authErrorProvider = Provider((ref) { return ref.watch(authStateProvider).error; });