import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:firebase_auth/firebase_auth.dart'; import 'package:cloud_firestore/cloud_firestore.dart'; import 'user_event.dart' as event; import 'user_state.dart' as state; /// BLoC for managing user data and operations. /// /// This BLoC handles user-related operations including loading user data, /// updating user information, and managing user state throughout the application. /// It coordinates with Firebase Auth and Firestore to manage user data persistence. class UserBloc extends Bloc { /// Firebase Auth instance for authentication operations. final FirebaseAuth _auth = FirebaseAuth.instance; /// Firestore instance for user data operations. final FirebaseFirestore _firestore = FirebaseFirestore.instance; /// Creates a new [UserBloc] with initial state. /// /// Registers event handlers for all user-related events. UserBloc() : super(state.UserInitial()) { on(_onUserInitialized); on(_onLoadUser); on(_onUserUpdated); on(_onUserLoggedOut); } /// Handles [UserInitialized] events. /// /// Initializes the current authenticated user's data by fetching it from Firestore. /// If the user doesn't exist in Firestore, creates a default user document. /// This is typically called when the app starts or after successful authentication. Future _onUserInitialized( event.UserInitialized event, Emitter emit, ) async { emit(state.UserLoading()); try { final currentUser = _auth.currentUser; if (currentUser == null) { emit(state.UserError('No user currently authenticated')); return; } // Fetch user data from Firestore final userDoc = await _firestore .collection('users') .doc(currentUser.uid) .get(); if (!userDoc.exists) { // Create a default user if it doesn't exist final defaultUser = state.UserModel( id: currentUser.uid, email: currentUser.email ?? '', prenom: currentUser.displayName ?? 'Voyageur', ); await _firestore .collection('users') .doc(currentUser.uid) .set(defaultUser.toJson()); emit(state.UserLoaded(defaultUser)); } else { final user = state.UserModel.fromJson({ 'id': currentUser.uid, ...userDoc.data()!, }); emit(state.UserLoaded(user)); } } catch (e) { emit(state.UserError('Error loading user: $e')); } } /// Handles [LoadUser] events. /// /// Loads a specific user's data from Firestore by their user ID. /// This is useful when you need to display information about other users. Future _onLoadUser( event.LoadUser event, Emitter emit, ) async { emit(state.UserLoading()); try { final userDoc = await _firestore .collection('users') .doc(event.userId) .get(); if (userDoc.exists) { final user = state.UserModel.fromJson({ 'id': event.userId, ...userDoc.data()!, }); emit(state.UserLoaded(user)); } else { emit(state.UserError('User not found')); } } catch (e) { emit(state.UserError('Error loading user: $e')); } } /// Handles [UserUpdated] events. /// /// Updates the current user's data in Firestore with the provided information. /// Only updates the fields specified in the userData map, allowing for partial updates. /// After successful update, reloads the user data to reflect changes. Future _onUserUpdated( event.UserUpdated event, Emitter emit, ) async { if (this.state is state.UserLoaded) { final currentUser = (this.state as state.UserLoaded).user; try { await _firestore .collection('users') .doc(currentUser.id) .update(event.userData); final updatedDoc = await _firestore .collection('users') .doc(currentUser.id) .get(); final updatedUser = state.UserModel.fromJson({ 'id': currentUser.id, ...updatedDoc.data()!, }); emit(state.UserLoaded(updatedUser)); } catch (e) { emit(state.UserError('Error updating user: $e')); } } } /// Handles [UserLoggedOut] events. /// /// Resets the user bloc state to initial when the user logs out. /// This clears any cached user data from the application state. Future _onUserLoggedOut( event.UserLoggedOut event, Emitter emit, ) async { emit(state.UserInitial()); } }