feat: Upgrade Firebase Functions dependencies, enhance notification service with APNS support and FCM
This commit is contained in:
@@ -1,23 +1,24 @@
|
||||
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);
|
||||
@@ -25,9 +26,9 @@ class UserBloc extends Bloc<event.UserEvent, state.UserState> {
|
||||
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.
|
||||
@@ -36,49 +37,70 @@ class UserBloc extends Bloc<event.UserEvent, state.UserState> {
|
||||
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(
|
||||
@@ -86,13 +108,13 @@ class UserBloc extends Bloc<event.UserEvent, state.UserState> {
|
||||
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,
|
||||
@@ -106,9 +128,9 @@ class UserBloc extends Bloc<event.UserEvent, state.UserState> {
|
||||
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.
|
||||
@@ -118,32 +140,32 @@ class UserBloc extends Bloc<event.UserEvent, state.UserState> {
|
||||
) 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(
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
|
||||
/// Abstract base class for all user-related states.
|
||||
///
|
||||
///
|
||||
/// This class extends [Equatable] to enable value equality for state comparison.
|
||||
/// All user states in the application should inherit from this class.
|
||||
abstract class UserState extends Equatable {
|
||||
@@ -10,79 +10,82 @@ abstract class UserState extends Equatable {
|
||||
}
|
||||
|
||||
/// Initial state of the user bloc.
|
||||
///
|
||||
///
|
||||
/// This state represents the initial state before any user operations
|
||||
/// have been performed or when the user has logged out.
|
||||
class UserInitial extends UserState {}
|
||||
|
||||
/// State indicating that a user operation is in progress.
|
||||
///
|
||||
///
|
||||
/// This state is used to show loading indicators during user data
|
||||
/// operations like loading, updating, or initializing user information.
|
||||
class UserLoading extends UserState {}
|
||||
|
||||
/// State indicating that user data has been successfully loaded.
|
||||
///
|
||||
///
|
||||
/// This state contains the loaded user information and is used
|
||||
/// throughout the app to access current user data.
|
||||
class UserLoaded extends UserState {
|
||||
/// The loaded user data.
|
||||
final UserModel user;
|
||||
|
||||
|
||||
/// Creates a [UserLoaded] state with the given [user] data.
|
||||
UserLoaded(this.user);
|
||||
|
||||
|
||||
@override
|
||||
List<Object?> get props => [user];
|
||||
}
|
||||
|
||||
/// State indicating that a user operation has failed.
|
||||
///
|
||||
///
|
||||
/// This state contains an error message that can be displayed to the user
|
||||
/// when user operations fail.
|
||||
class UserError extends UserState {
|
||||
/// The error message describing what went wrong.
|
||||
final String message;
|
||||
|
||||
|
||||
/// Creates a [UserError] state with the given error [message].
|
||||
UserError(this.message);
|
||||
|
||||
|
||||
@override
|
||||
List<Object?> get props => [message];
|
||||
}
|
||||
|
||||
/// Simple user model for representing user data in the application.
|
||||
///
|
||||
///
|
||||
/// This model contains basic user information and provides methods for
|
||||
/// serialization/deserialization with Firestore operations.
|
||||
/// Simple user model for representing user data in the application.
|
||||
///
|
||||
///
|
||||
/// This model contains basic user information and provides methods for
|
||||
/// serialization/deserialization with Firestore operations.
|
||||
class UserModel {
|
||||
/// Unique identifier for the user (Firebase UID).
|
||||
final String id;
|
||||
|
||||
|
||||
/// User's email address.
|
||||
final String email;
|
||||
|
||||
|
||||
/// User's first name.
|
||||
final String prenom;
|
||||
|
||||
|
||||
/// User's last name (optional).
|
||||
final String? nom;
|
||||
|
||||
|
||||
/// Platform used for authentication (e.g., 'google', 'apple', 'email').
|
||||
final String? authMethod;
|
||||
|
||||
|
||||
/// User's phone number (optional).
|
||||
final String? phoneNumber;
|
||||
|
||||
|
||||
/// User's profile picture URL (optional).
|
||||
final String? profilePictureUrl;
|
||||
|
||||
|
||||
/// Firebase Cloud Messaging token for push notifications.
|
||||
final String? fcmToken;
|
||||
|
||||
/// Creates a new [UserModel] instance.
|
||||
///
|
||||
///
|
||||
/// [id], [email], and [prenom] are required fields.
|
||||
/// [nom], [authMethod], [phoneNumber], and [profilePictureUrl] are optional and can be null.
|
||||
UserModel({
|
||||
@@ -93,10 +96,11 @@ class UserModel {
|
||||
this.authMethod,
|
||||
this.phoneNumber,
|
||||
this.profilePictureUrl,
|
||||
this.fcmToken,
|
||||
});
|
||||
|
||||
|
||||
/// Creates a [UserModel] instance from a JSON map.
|
||||
///
|
||||
///
|
||||
/// Handles null values gracefully by providing default values.
|
||||
/// [prenom] defaults to 'Voyageur' (Traveler) if not provided.
|
||||
factory UserModel.fromJson(Map<String, dynamic> json) {
|
||||
@@ -108,11 +112,12 @@ class UserModel {
|
||||
authMethod: json['authMethod'] ?? json['platform'],
|
||||
phoneNumber: json['phoneNumber'],
|
||||
profilePictureUrl: json['profilePictureUrl'],
|
||||
fcmToken: json['fcmToken'],
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/// Converts the [UserModel] instance to a JSON map.
|
||||
///
|
||||
///
|
||||
/// Useful for storing user data in Firestore or other JSON-based operations.
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
@@ -123,6 +128,7 @@ class UserModel {
|
||||
'authMethod': authMethod,
|
||||
'phoneNumber': phoneNumber,
|
||||
'profilePictureUrl': profilePictureUrl,
|
||||
'fcmToken': fcmToken,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user