Cette mise à jour refactorise en profondeur le chargement des événements sur la page Calendrier pour améliorer drastiquement les performances et la réactivité de l'application, en particulier pour les utilisateurs avec un grand nombre d'événements. Le système de chargement initial de tous les événements est remplacé par un mécanisme de lazy loading qui ne récupère que les données du mois affiché.
**Changements majeurs :**
- **Lazy Loading Côté Client (`EventProvider`) :**
- Une nouvelle méthode `loadMonthEvents` a été introduite pour charger uniquement les événements d'un mois spécifique (`year`, `month`).
- Un cache par mois (`_eventsByMonth`) a été mis en place pour éviter les rechargements inutiles lors de la navigation entre des mois déjà consultés.
- Ajout d'une fonction `preloadAdjacentMonths` qui charge en arrière-plan et silencieusement les mois précédent et suivant, assurant une navigation fluide dans le calendrier.
- **Nouveau Endpoint Backend (`getEventsByMonth`) :**
- Création d'un nouvel endpoint Cloud Function `getEventsByMonth` optimisé pour ne requêter que les événements dans une plage de dates (début et fin du mois).
- La fonction récupère les utilisateurs associés de manière optimisée en parallélisant les requêtes Firestore (Promise.all).
- La limite du nombre d'IDs par requête 'in' a été augmentée de 10 à 30 pour réduire le nombre d'appels à la base de données.
- **Intégration au Calendrier (`CalendarPage`) :**
- La page charge désormais les événements pour le mois courant au démarrage via `_loadCurrentMonthEvents`.
- Lorsqu'un utilisateur change de mois (`onPageChanged`), la page déclenche le chargement des données pour le nouveau mois, avec un préchargement des mois adjacents pour anticiper la navigation.
- Le chargement initial de tous les événements (`_loadEventsAsync`) a été déprécié.
- **Correction de la Séquence de Démarrage (`main.dart`) :**
- L'appel à `_autoLogin` est maintenant enveloppé dans `WidgetsBinding.instance.addPostFrameCallback`. Cela garantit que la navigation ne se produit qu'après le premier rendu de l'interface, évitant ainsi des erreurs potentielles de build/navigation concurrentes et fiabilisant le chargement initial des données utilisateur.
230 lines
7.4 KiB
Dart
230 lines
7.4 KiB
Dart
import 'package:firebase_auth/firebase_auth.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:image_picker/image_picker.dart';
|
|
import '../models/user_model.dart';
|
|
import '../models/role_model.dart';
|
|
import '../models/notification_preferences_model.dart';
|
|
import '../utils/firebase_storage_manager.dart';
|
|
import '../services/api_service.dart';
|
|
import '../services/data_service.dart';
|
|
import '../utils/performance_monitor.dart';
|
|
|
|
class LocalUserProvider with ChangeNotifier {
|
|
UserModel? _currentUser;
|
|
RoleModel? _currentRole;
|
|
final FirebaseAuth _auth = FirebaseAuth.instance;
|
|
final FirebaseStorageManager _storageManager = FirebaseStorageManager();
|
|
final DataService _dataService = DataService(apiService);
|
|
|
|
bool _isLoadingUserData = false;
|
|
DateTime? _lastUserDataLoad;
|
|
|
|
UserModel? get currentUser => _currentUser;
|
|
String? get uid => _currentUser?.uid;
|
|
String? get firstName => _currentUser?.firstName;
|
|
String? get lastName => _currentUser?.lastName;
|
|
String? get role => _currentUser?.role ?? 'USER';
|
|
String? get profilePhotoUrl => _currentUser?.profilePhotoUrl;
|
|
String? get email => _currentUser?.email;
|
|
String? get phoneNumber => _currentUser?.phoneNumber;
|
|
RoleModel? get currentRole => _currentRole;
|
|
List<String> get permissions => _currentRole?.permissions ?? [];
|
|
bool get isLoadingUserData => _isLoadingUserData;
|
|
|
|
/// Vérifie si les données utilisateur doivent être rechargées
|
|
bool _shouldReloadUserData() {
|
|
if (_currentUser == null) return true;
|
|
if (_lastUserDataLoad == null) return true;
|
|
|
|
final now = DateTime.now();
|
|
final difference = now.difference(_lastUserDataLoad!);
|
|
return difference.inMinutes > 5; // Cache de 5 minutes pour les données utilisateur
|
|
}
|
|
|
|
/// Charge les données de l'utilisateur actuel via Cloud Function
|
|
Future<void> loadUserData({bool forceReload = false}) async {
|
|
if (_auth.currentUser == null) {
|
|
print('No current user in Auth');
|
|
return;
|
|
}
|
|
|
|
// Éviter les rechargements inutiles
|
|
if (!forceReload && !_shouldReloadUserData()) {
|
|
print('Using cached user data');
|
|
return;
|
|
}
|
|
|
|
// Éviter les appels simultanés
|
|
if (_isLoadingUserData) {
|
|
print('User data already loading, skipping');
|
|
return;
|
|
}
|
|
|
|
_isLoadingUserData = true;
|
|
PerformanceMonitor.start('LocalUserProvider.loadUserData');
|
|
print('Loading user data for: ${_auth.currentUser!.uid}');
|
|
try {
|
|
// Utiliser la Cloud Function getCurrentUser
|
|
PerformanceMonitor.start('LocalUserProvider.getCurrentUser_API');
|
|
final result = await apiService.call('getCurrentUser', {});
|
|
PerformanceMonitor.end('LocalUserProvider.getCurrentUser_API');
|
|
|
|
final userData = result['user'] as Map<String, dynamic>;
|
|
|
|
print('User data loaded from API: ${userData['uid']}');
|
|
|
|
// Extraire le rôle
|
|
final roleData = userData['role'] as Map<String, dynamic>?;
|
|
if (roleData != null) {
|
|
_currentRole = RoleModel.fromMap(roleData, roleData['id'] as String);
|
|
}
|
|
|
|
// Créer le UserModel
|
|
_currentUser = UserModel(
|
|
uid: userData['uid'] as String,
|
|
email: userData['email'] as String? ?? '',
|
|
firstName: userData['firstName'] as String? ?? '',
|
|
lastName: userData['lastName'] as String? ?? '',
|
|
role: roleData?['id'] as String? ?? 'USER',
|
|
phoneNumber: userData['phoneNumber'] as String? ?? '',
|
|
profilePhotoUrl: userData['profilePhotoUrl'] as String? ?? '',
|
|
);
|
|
|
|
print('User data loaded successfully');
|
|
_lastUserDataLoad = DateTime.now();
|
|
_isLoadingUserData = false;
|
|
notifyListeners();
|
|
PerformanceMonitor.end('LocalUserProvider.loadUserData');
|
|
} catch (e) {
|
|
print('Error loading user data: $e');
|
|
_isLoadingUserData = false;
|
|
PerformanceMonitor.end('LocalUserProvider.loadUserData');
|
|
rethrow;
|
|
}
|
|
}
|
|
|
|
/// Met à jour l'utilisateur
|
|
void setUser(UserModel user) {
|
|
_currentUser = user;
|
|
notifyListeners();
|
|
}
|
|
|
|
/// Efface les données utilisateur
|
|
void clearUser() {
|
|
_currentUser = null;
|
|
_currentRole = null;
|
|
_lastUserDataLoad = null;
|
|
_isLoadingUserData = false;
|
|
notifyListeners();
|
|
}
|
|
|
|
/// Mise à jour des informations utilisateur via Cloud Function
|
|
Future<void> updateUserData({
|
|
String? firstName,
|
|
String? lastName,
|
|
String? phoneNumber,
|
|
}) async {
|
|
if (_currentUser == null) return;
|
|
try {
|
|
await _dataService.updateUser(
|
|
_currentUser!.uid,
|
|
{
|
|
'firstName': firstName ?? _currentUser!.firstName,
|
|
'lastName': lastName ?? _currentUser!.lastName,
|
|
'phoneNumber': phoneNumber ?? _currentUser!.phoneNumber,
|
|
},
|
|
);
|
|
|
|
_currentUser = _currentUser!.copyWith(
|
|
firstName: firstName,
|
|
lastName: lastName,
|
|
phoneNumber: phoneNumber,
|
|
);
|
|
notifyListeners();
|
|
} catch (e) {
|
|
debugPrint('Erreur mise à jour utilisateur : $e');
|
|
rethrow;
|
|
}
|
|
}
|
|
|
|
/// Mise à jour des préférences de notifications
|
|
Future<void> updateNotificationPreferences(NotificationPreferences preferences) async {
|
|
if (_currentUser == null) return;
|
|
try {
|
|
await _dataService.updateUser(
|
|
_currentUser!.uid,
|
|
{
|
|
'notificationPreferences': preferences.toMap(),
|
|
},
|
|
);
|
|
|
|
_currentUser = _currentUser!.copyWith(notificationPreferences: preferences);
|
|
notifyListeners();
|
|
} catch (e) {
|
|
debugPrint('Erreur mise à jour préférences notifications : $e');
|
|
rethrow;
|
|
}
|
|
}
|
|
|
|
/// Changement de photo de profil
|
|
Future<void> changeProfilePicture(XFile image) async {
|
|
if (_currentUser == null) return;
|
|
try {
|
|
String? newProfilePhotoUrl = await _storageManager.sendProfilePicture(
|
|
imageFile: image,
|
|
uid: _currentUser!.uid,
|
|
);
|
|
if (newProfilePhotoUrl != null) {
|
|
// Mettre à jour via Cloud Function
|
|
await _dataService.updateUser(
|
|
_currentUser!.uid,
|
|
{'profilePhotoUrl': newProfilePhotoUrl},
|
|
);
|
|
|
|
_currentUser = _currentUser!.copyWith(profilePhotoUrl: newProfilePhotoUrl);
|
|
notifyListeners();
|
|
}
|
|
} catch (e) {
|
|
debugPrint('Erreur mise à jour photo de profil : $e');
|
|
rethrow;
|
|
}
|
|
}
|
|
|
|
/// Connexion
|
|
Future<UserCredential> signInWithEmailAndPassword(
|
|
String email, String password) async {
|
|
try {
|
|
UserCredential userCredential = await _auth.signInWithEmailAndPassword(
|
|
email: email, password: password);
|
|
// Note: loadUserData() sera appelé en arrière-plan dans main.dart
|
|
// pour ne pas bloquer la navigation
|
|
return userCredential;
|
|
} catch (e) {
|
|
throw FirebaseAuthException(code: 'login-failed', message: e.toString());
|
|
}
|
|
}
|
|
|
|
/// Déconnexion
|
|
Future<void> signOut() async {
|
|
await _auth.signOut();
|
|
clearUser();
|
|
}
|
|
|
|
/// Vérifie si l'utilisateur a une permission spécifique
|
|
bool hasPermission(String permission) {
|
|
return _currentRole?.permissions.contains(permission) ?? false;
|
|
}
|
|
|
|
/// Vérifie si l'utilisateur a toutes les permissions données
|
|
bool hasAllPermissions(List<String> permissions) {
|
|
if (_currentRole == null) return false;
|
|
return permissions.every((p) => _currentRole!.permissions.contains(p));
|
|
}
|
|
|
|
/// Vérifie si l'utilisateur a au moins une des permissions données
|
|
bool hasAnyPermission(List<String> permissions) {
|
|
if (_currentRole == null) return false;
|
|
return permissions.any((p) => _currentRole!.permissions.contains(p));
|
|
}
|
|
}
|