178 lines
5.6 KiB
Dart
178 lines
5.6 KiB
Dart
import 'package:flutter_bloc/flutter_bloc.dart';
|
|
import 'package:firebase_auth/firebase_auth.dart';
|
|
import 'package:cloud_firestore/cloud_firestore.dart';
|
|
import 'package:travel_mate/services/notification_service.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<event.UserEvent, state.UserState> {
|
|
/// 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<event.UserInitialized>(_onUserInitialized);
|
|
on<event.LoadUser>(_onLoadUser);
|
|
on<event.UserUpdated>(_onUserUpdated);
|
|
on<event.UserLoggedOut>(_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<void> _onUserInitialized(
|
|
event.UserInitialized event,
|
|
Emitter<state.UserState> emit,
|
|
) async {
|
|
emit(state.UserLoading());
|
|
|
|
try {
|
|
final currentUser = _auth.currentUser;
|
|
|
|
if (currentUser == null) {
|
|
emit(state.UserError('No user currently authenticated'));
|
|
return;
|
|
}
|
|
|
|
// Initialize notifications and update token
|
|
final notificationService = NotificationService();
|
|
await notificationService.initialize();
|
|
final fcmToken = await notificationService.getFCMToken();
|
|
print('DEBUG: UserBloc - FCM Token retrieved: $fcmToken');
|
|
|
|
// 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',
|
|
fcmToken: fcmToken,
|
|
);
|
|
|
|
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()!,
|
|
});
|
|
|
|
// Update FCM token if it changed
|
|
if (fcmToken != null && user.fcmToken != fcmToken) {
|
|
print('DEBUG: UserBloc - Updating FCM token in Firestore');
|
|
await _firestore.collection('users').doc(currentUser.uid).update({
|
|
'fcmToken': fcmToken,
|
|
});
|
|
print('DEBUG: UserBloc - FCM token updated');
|
|
} else {
|
|
print(
|
|
'DEBUG: UserBloc - FCM token not updated. Local: $fcmToken, Firestore: ${user.fcmToken}',
|
|
);
|
|
}
|
|
|
|
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<void> _onLoadUser(
|
|
event.LoadUser event,
|
|
Emitter<state.UserState> 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<void> _onUserUpdated(
|
|
event.UserUpdated event,
|
|
Emitter<state.UserState> 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<void> _onUserLoggedOut(
|
|
event.UserLoggedOut event,
|
|
Emitter<state.UserState> emit,
|
|
) async {
|
|
emit(state.UserInitial());
|
|
}
|
|
}
|