diff --git a/app/.dart_tool/extension_discovery/README.md b/app/.dart_tool/extension_discovery/README.md new file mode 100644 index 00000000..9dc6757b --- /dev/null +++ b/app/.dart_tool/extension_discovery/README.md @@ -0,0 +1,31 @@ +Extension Discovery Cache +========================= + +This folder is used by `package:extension_discovery` to cache lists of +packages that contains extensions for other packages. + +DO NOT USE THIS FOLDER +---------------------- + + * Do not read (or rely) the contents of this folder. + * Do write to this folder. + +If you're interested in the lists of extensions stored in this folder use the +API offered by package `extension_discovery` to get this information. + +If this package doesn't work for your use-case, then don't try to read the +contents of this folder. It may change, and will not remain stable. + +Use package `extension_discovery` +--------------------------------- + +If you want to access information from this folder. + +Feel free to delete this folder +------------------------------- + +Files in this folder act as a cache, and the cache is discarded if the files +are older than the modification time of `.dart_tool/package_config.json`. + +Hence, it should never be necessary to clear this cache manually, if you find a +need to do please file a bug. diff --git a/app/.dart_tool/extension_discovery/vs_code.json b/app/.dart_tool/extension_discovery/vs_code.json new file mode 100644 index 00000000..ae23f23f --- /dev/null +++ b/app/.dart_tool/extension_discovery/vs_code.json @@ -0,0 +1 @@ +{"version":2,"entries":[{"package":"geosector_app","rootUri":"../","packageUri":"lib/"}]} \ No newline at end of file diff --git a/app/lib/app.dart b/app/lib/app.dart index b9b4597d..e3c3c99b 100644 --- a/app/lib/app.dart +++ b/app/lib/app.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:geosector_app/core/theme/app_theme.dart'; import 'package:go_router/go_router.dart'; -import 'package:geosector_app/core/services/api_service.dart'; +import 'package:geosector_app/core/services/current_user_service.dart'; import 'package:geosector_app/core/repositories/user_repository.dart'; import 'package:geosector_app/core/repositories/operation_repository.dart'; import 'package:geosector_app/core/repositories/passage_repository.dart'; @@ -16,14 +16,13 @@ import 'package:geosector_app/presentation/auth/login_page.dart'; import 'package:geosector_app/presentation/admin/admin_dashboard_page.dart'; import 'package:geosector_app/presentation/user/user_dashboard_page.dart'; -// Instances globales des services et repositories -final apiService = ApiService(); -final operationRepository = OperationRepository(apiService); -final passageRepository = PassageRepository(apiService); -final userRepository = UserRepository(apiService); -final sectorRepository = SectorRepository(apiService); -final membreRepository = MembreRepository(apiService); -final amicaleRepository = AmicaleRepository(apiService); +// Instances globales des repositories (plus besoin d'injecter ApiService) +final operationRepository = OperationRepository(); +final passageRepository = PassageRepository(); +final userRepository = UserRepository(); +final sectorRepository = SectorRepository(); +final membreRepository = MembreRepository(); +final amicaleRepository = AmicaleRepository(); final syncService = SyncService(userRepository: userRepository); final connectivityService = ConnectivityService(); @@ -60,8 +59,7 @@ class GeosectorApp extends StatelessWidget { name: 'login', builder: (context, state) { // Récupérer le type depuis les query parameters ou extra data - final type = state.uri.queryParameters['type'] ?? - (state.extra as Map?)?['type'] as String?; + final type = state.uri.queryParameters['type'] ?? (state.extra as Map?)?['type'] as String?; debugPrint('GoRoute: Affichage de LoginPage avec type: $type'); return LoginPage(loginType: type); @@ -125,44 +123,37 @@ class GeosectorApp extends StatelessWidget { } // Pages publiques qui ne nécessitent pas d'authentification - final publicPaths = [ - '/login', - '/login/user', - '/login/admin', - '/register' - ]; + final publicPaths = ['/login', '/login/user', '/login/admin', '/register']; if (publicPaths.any((path) => currentPath.startsWith(path))) { - debugPrint( - 'GoRouter.redirect: Page publique autorisée: $currentPath'); + debugPrint('GoRouter.redirect: Page publique autorisée: $currentPath'); return null; } // Vérifier l'authentification pour les pages protégées try { - final isAuthenticated = userRepository.isLoggedIn; - final currentUser = userRepository.currentUser; + // Utiliser le nouveau CurrentUserService + final userService = CurrentUserService.instance; + final isAuthenticated = userService.isLoggedIn; + final currentUser = userService.currentUser; debugPrint('GoRouter.redirect: isAuthenticated = $isAuthenticated'); debugPrint('GoRouter.redirect: currentUser = ${currentUser?.email}'); // Si pas authentifié, rediriger vers la splash page if (!isAuthenticated) { - debugPrint( - 'GoRouter.redirect: Non authentifié, redirection vers /'); + debugPrint('GoRouter.redirect: Non authentifié, redirection vers /'); return '/'; } // Vérifier les permissions pour les pages admin if (currentPath.startsWith('/admin')) { - final userRole = userRepository.getUserRole(); - final isAdmin = userRole > 1; // Admin = rôle 2 ou plus + final userRole = userService.userRole; + final isAdmin = userService.canAccessAdmin; - debugPrint( - 'GoRouter.redirect: userRole = $userRole, isAdmin = $isAdmin'); + debugPrint('GoRouter.redirect: userRole = $userRole, canAccessAdmin = $isAdmin'); if (!isAdmin) { - debugPrint( - 'GoRouter.redirect: Pas admin, redirection vers /user'); + debugPrint('GoRouter.redirect: Pas admin, redirection vers /user'); return '/user'; } } @@ -171,15 +162,13 @@ class GeosectorApp extends StatelessWidget { debugPrint('GoRouter.redirect: Accès autorisé à $currentPath'); return null; } catch (e) { - debugPrint( - 'GoRouter.redirect: Erreur lors de la vérification auth: $e'); + debugPrint('GoRouter.redirect: Erreur lors de la vérification auth: $e'); // En cas d'erreur, rediriger vers la splash page pour sécurité return '/'; } }, // Listener pour déboguer les changements de route - refreshListenable: - userRepository, // Écouter les changements dans userRepository + refreshListenable: CurrentUserService.instance, // Écouter les changements dans CurrentUserService debugLogDiagnostics: true, // Activer les logs de débogage errorBuilder: (context, state) { debugPrint('GoRouter.errorBuilder: Erreur pour ${state.uri.path}'); diff --git a/app/lib/core/repositories/amicale_repository.dart b/app/lib/core/repositories/amicale_repository.dart index 9363b85d..701ad06b 100644 --- a/app/lib/core/repositories/amicale_repository.dart +++ b/app/lib/core/repositories/amicale_repository.dart @@ -1,7 +1,209 @@ import 'dart:async'; import 'package:flutter/foundation.dart'; import 'package:hive_flutter/hive_flutter.dart'; +import 'dart:async'; +import 'package:flutter/foundation.dart'; +import 'package:hive/hive.dart'; +import 'package:geosector_app/core/data/models/amicale_model.dart'; +import 'package:geosector_app/core/services/api_service.dart'; import 'package:geosector_app/core/constants/app_keys.dart'; + +class AmicaleRepository extends ChangeNotifier { + // Constructeur sans paramètres - utilise ApiService.instance + AmicaleRepository(); + + // Utiliser un getter lazy pour n'accéder à la boîte que lorsque nécessaire + // et vérifier qu'elle est ouverte avant accès + Box get _amicaleBox { + _ensureBoxIsOpen(); + return Hive.box(AppKeys.amicaleBoxName); + } + + bool _isLoading = false; + + // Getters + bool get isLoading => _isLoading; + List get amicales => getAllAmicales(); + + // Méthode pour vérifier si la boîte est ouverte et l'ouvrir si nécessaire + Future _ensureBoxIsOpen() async { + const boxName = AppKeys.amicaleBoxName; + if (!Hive.isBoxOpen(boxName)) { + debugPrint('Ouverture de la boîte $boxName dans AmicaleRepository...'); + await Hive.openBox(boxName); + } + } + + // Récupérer toutes les amicales + List getAllAmicales() { + return _amicaleBox.values.toList(); + } + + // Récupérer une amicale par son ID + AmicaleModel? getAmicaleById(int id) { + return _amicaleBox.get(id); + } + + // Récupérer les amicales par type + List getAmicalesByType(int type) { + return _amicaleBox.values.where((amicale) => amicale.fkType == type).toList(); + } + + // Récupérer uniquement les clients (type 1) + List getClients() { + return getAmicalesByType(1); + } + + // Sauvegarder une amicale + Future saveAmicale(AmicaleModel amicale) async { + await _amicaleBox.put(amicale.id, amicale); + notifyListeners(); + } + + // Supprimer une amicale + Future deleteAmicale(int id) async { + await _amicaleBox.delete(id); + notifyListeners(); + } + + // Créer une amicale via l'API + Future createAmicale(AmicaleModel amicale) async { + _isLoading = true; + notifyListeners(); + + try { + // Préparer les données pour l'API + final data = amicale.toJson(); + + // Appeler l'API pour créer l'amicale + final response = await ApiService.instance.post('/amicales', data: data); + + if (response.statusCode == 201 || response.statusCode == 200) { + // Récupérer l'ID de la nouvelle amicale + final amicaleId = response.data['id'] is String ? int.parse(response.data['id']) : response.data['id'] as int; + + // Créer l'amicale localement avec l'ID retourné par l'API + final newAmicale = amicale.copyWith( + id: amicaleId, + lastSyncedAt: DateTime.now(), + isSynced: true, + ); + + await saveAmicale(newAmicale); + return true; + } + + return false; + } catch (e) { + debugPrint('Erreur lors de la création de l\'amicale: $e'); + return false; + } finally { + _isLoading = false; + notifyListeners(); + } + } + + // Mettre à jour une amicale via l'API + Future updateAmicale(AmicaleModel amicale) async { + _isLoading = true; + notifyListeners(); + + try { + // Préparer les données pour l'API + final data = amicale.toJson(); + + // Appeler l'API pour mettre à jour l'amicale + final response = await ApiService.instance.put('/amicales/${amicale.id}', data: data); + + if (response.statusCode == 200) { + // Mettre à jour l'amicale localement + final updatedAmicale = amicale.copyWith( + lastSyncedAt: DateTime.now(), + isSynced: true, + ); + + await saveAmicale(updatedAmicale); + return true; + } + + return false; + } catch (e) { + debugPrint('Erreur lors de la mise à jour de l\'amicale: $e'); + return false; + } finally { + _isLoading = false; + notifyListeners(); + } + } + + // Supprimer une amicale via l'API + Future deleteAmicaleViaApi(int id) async { + _isLoading = true; + notifyListeners(); + + try { + // Appeler l'API pour supprimer l'amicale + final response = await ApiService.instance.delete('/amicales/$id'); + + if (response.statusCode == 200 || response.statusCode == 204) { + // Supprimer l'amicale localement + await deleteAmicale(id); + return true; + } + + return false; + } catch (e) { + debugPrint('Erreur lors de la suppression de l\'amicale: $e'); + return false; + } finally { + _isLoading = false; + notifyListeners(); + } + } + + // Traitement des données d'amicales depuis l'API + Future processAmicalesData(dynamic amicalesData) async { + try { + debugPrint('Traitement des données des amicales...'); + + // Vérifier que les données sont au bon format + if (amicalesData == null) { + debugPrint('Aucune donnée d\'amicale à traiter'); + return; + } + + List amicalesList; + if (amicalesData is List) { + amicalesList = amicalesData; + } else if (amicalesData is Map && amicalesData.containsKey('data')) { + amicalesList = amicalesData['data'] as List; + } else { + debugPrint('Format de données d\'amicales non reconnu'); + return; + } + + // Vider la boîte avant d'ajouter les nouvelles données + await _amicaleBox.clear(); + + // Traiter chaque amicale + int count = 0; + for (final amicaleData in amicalesList) { + try { + final amicale = AmicaleModel.fromJson(amicaleData); + await _amicaleBox.put(amicale.id, amicale); + count++; + } catch (e) { + debugPrint('Erreur lors du traitement d\'une amicale: $e'); + } + } + + debugPrint('$count amicales traitées et stockées'); + notifyListeners(); + } catch (e) { + debugPrint('Erreur lors du traitement des amicales: $e'); + } + } +} import 'package:geosector_app/core/services/api_service.dart'; import 'package:geosector_app/core/data/models/amicale_model.dart'; diff --git a/app/lib/core/repositories/client_repository.dart b/app/lib/core/repositories/client_repository.dart index 9cb858b1..633c2f7a 100644 --- a/app/lib/core/repositories/client_repository.dart +++ b/app/lib/core/repositories/client_repository.dart @@ -1,81 +1,151 @@ import 'dart:async'; import 'package:flutter/foundation.dart'; -import 'package:hive_flutter/hive_flutter.dart'; -import 'package:geosector_app/core/constants/app_keys.dart'; -import 'package:geosector_app/core/services/api_service.dart'; +import 'package:hive/hive.dart'; import 'package:geosector_app/core/data/models/client_model.dart'; +import 'package:geosector_app/core/services/api_service.dart'; +import 'package:geosector_app/core/constants/app_keys.dart'; class ClientRepository extends ChangeNotifier { - // Utilisation de getters lazy pour n'accéder à la boîte que lorsque nécessaire - Box get _clientBox => - Hive.box(AppKeys.clientsBoxName); + // Constructeur sans paramètres - utilise ApiService.instance + ClientRepository(); + // Utiliser un getter lazy pour n'accéder à la boîte que lorsque nécessaire + // et vérifier qu'elle est ouverte avant accès + Box get _clientBox { + _ensureBoxIsOpen(); + return Hive.box(AppKeys.clientsBoxName); + } - final ApiService _apiService; bool _isLoading = false; - ClientRepository(this._apiService); - // Getters bool get isLoading => _isLoading; + List get clients => getAllClients(); - // Méthode pour vérifier si une boîte est ouverte et l'ouvrir si nécessaire + // Méthode pour vérifier si la boîte est ouverte et l'ouvrir si nécessaire Future _ensureBoxIsOpen() async { - try { - if (!Hive.isBoxOpen(AppKeys.clientsBoxName)) { - debugPrint('Ouverture de la boîte clients...'); - await Hive.openBox(AppKeys.clientsBoxName); - } - } catch (e) { - debugPrint('Erreur lors de l\'ouverture de la boîte clients: $e'); - throw Exception('Impossible d\'ouvrir la boîte clients: $e'); + const boxName = AppKeys.clientsBoxName; + if (!Hive.isBoxOpen(boxName)) { + debugPrint('Ouverture de la boîte $boxName dans ClientRepository...'); + await Hive.openBox(boxName); } } // Récupérer tous les clients List getAllClients() { - try { - _ensureBoxIsOpen(); - return _clientBox.values.toList(); - } catch (e) { - debugPrint('Erreur lors de la récupération des clients: $e'); - return []; - } + return _clientBox.values.toList(); } // Récupérer un client par son ID ClientModel? getClientById(int id) { - try { - _ensureBoxIsOpen(); - return _clientBox.get(id); - } catch (e) { - debugPrint('Erreur lors de la récupération du client: $e'); - return null; - } + return _clientBox.get(id); } - // Créer ou mettre à jour un client localement - Future saveClient(ClientModel client) async { - await _ensureBoxIsOpen(); + // Sauvegarder un client + Future saveClient(ClientModel client) async { await _clientBox.put(client.id, client); - notifyListeners(); // Notifier les changements pour mettre à jour l'UI - return client; + notifyListeners(); } - // Supprimer un client localement + // Supprimer un client Future deleteClient(int id) async { - await _ensureBoxIsOpen(); await _clientBox.delete(id); notifyListeners(); } - // Vider la boîte des clients - Future clearClients() async { - await _ensureBoxIsOpen(); - await _clientBox.clear(); + // Créer un client via l'API + Future createClient(ClientModel client) async { + _isLoading = true; notifyListeners(); + + try { + // Préparer les données pour l'API + final data = client.toJson(); + + // Appeler l'API pour créer le client + final response = await ApiService.instance.post('/clients', data: data); + + if (response.statusCode == 201 || response.statusCode == 200) { + // Récupérer l'ID du nouveau client + final clientId = response.data['id'] is String ? int.parse(response.data['id']) : response.data['id'] as int; + + // Créer le client localement avec l'ID retourné par l'API + final newClient = client.copyWith( + id: clientId, + lastSyncedAt: DateTime.now(), + isSynced: true, + ); + await saveClient(newClient); + return true; + } + return false; + } catch (e) { + debugPrint('Erreur lors de la création du client: $e'); + return false; + } finally { + _isLoading = false; + notifyListeners(); + } } - // Traiter les données des clients reçues de l'API + // Mettre à jour un client via l'API + Future updateClient(ClientModel client) async { + _isLoading = true; + notifyListeners(); + + try { + // Préparer les données pour l'API + final data = client.toJson(); + + // Appeler l'API pour mettre à jour le client + final response = await ApiService.instance.put('/clients/${client.id}', data: data); + + if (response.statusCode == 200) { + // Mettre à jour le client localement + final updatedClient = client.copyWith( + lastSyncedAt: DateTime.now(), + isSynced: true, + ); + + await saveClient(updatedClient); + return true; + } + + return false; + } catch (e) { + debugPrint('Erreur lors de la mise à jour du client: $e'); + return false; + } finally { + _isLoading = false; + notifyListeners(); + } + } + + // Supprimer un client via l'API + Future deleteClientViaApi(int id) async { + _isLoading = true; + notifyListeners(); + + try { + // Appeler l'API pour supprimer le client + final response = await ApiService.instance.delete('/clients/$id'); + + if (response.statusCode == 200 || response.statusCode == 204) { + // Supprimer le client localement + await deleteClient(id); + return true; + } + + return false; + } catch (e) { + debugPrint('Erreur lors de la suppression du client: $e'); + return false; + } finally { + _isLoading = false; + notifyListeners(); + } + } + + // Traitement des données de clients depuis l'API Future processClientsData(dynamic clientsData) async { try { debugPrint('Traitement des données des clients...'); @@ -97,7 +167,6 @@ class ClientRepository extends ChangeNotifier { } // Vider la boîte avant d'ajouter les nouvelles données - await _ensureBoxIsOpen(); await _clientBox.clear(); // Traiter chaque client @@ -107,7 +176,6 @@ class ClientRepository extends ChangeNotifier { final client = ClientModel.fromJson(clientData); await _clientBox.put(client.id, client); count++; - debugPrint('Client traité: ${client.name} (ID: ${client.id})'); } catch (e) { debugPrint('Erreur lors du traitement d\'un client: $e'); } @@ -120,21 +188,27 @@ class ClientRepository extends ChangeNotifier { } } + // Vider la boîte des clients + Future clearClients() async { + await _ensureBoxIsOpen(); + await _clientBox.clear(); + notifyListeners(); + } + // Récupérer les clients depuis l'API Future> fetchClientsFromApi() async { _isLoading = true; notifyListeners(); try { - final response = await _apiService.get('/clients'); + final response = await ApiService.instance.get('/clients'); if (response.statusCode == 200) { final clientsData = response.data; await processClientsData(clientsData); return getAllClients(); } else { - debugPrint( - 'Erreur lors de la récupération des clients: ${response.statusCode}'); + debugPrint('Erreur lors de la récupération des clients: ${response.statusCode}'); return []; } } catch (e) { @@ -153,9 +227,7 @@ class ClientRepository extends ChangeNotifier { } final lowercaseQuery = query.toLowerCase(); - return _clientBox.values - .where((client) => client.name.toLowerCase().contains(lowercaseQuery)) - .toList(); + return _clientBox.values.where((client) => client.name.toLowerCase().contains(lowercaseQuery)).toList(); } // Filtrer les clients par type @@ -165,15 +237,11 @@ class ClientRepository extends ChangeNotifier { // Filtrer les clients par région List getClientsByRegion(int regionId) { - return _clientBox.values - .where((client) => client.fkRegion == regionId) - .toList(); + return _clientBox.values.where((client) => client.fkRegion == regionId).toList(); } // Filtrer les clients actifs List getActiveClients() { - return _clientBox.values - .where((client) => client.chkActive == true) - .toList(); + return _clientBox.values.where((client) => client.chkActive == true).toList(); } } diff --git a/app/lib/core/repositories/membre_repository.dart b/app/lib/core/repositories/membre_repository.dart index 1e640fc4..b15423ed 100644 --- a/app/lib/core/repositories/membre_repository.dart +++ b/app/lib/core/repositories/membre_repository.dart @@ -1,20 +1,22 @@ import 'dart:async'; import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; -import 'package:hive_flutter/hive_flutter.dart'; -import 'package:geosector_app/core/constants/app_keys.dart'; -import 'package:geosector_app/core/services/api_service.dart'; +import 'package:hive/hive.dart'; import 'package:geosector_app/core/data/models/membre_model.dart'; +import 'package:geosector_app/core/services/api_service.dart'; +import 'package:geosector_app/core/constants/app_keys.dart'; class MembreRepository extends ChangeNotifier { - // Utilisation de getters lazy pour n'accéder à la boîte que lorsque nécessaire - Box get _membreBox => Hive.box(AppKeys.membresBoxName); + // Constructeur sans paramètres - utilise ApiService.instance + MembreRepository(); + // Utiliser un getter lazy pour n'accéder à la boîte que lorsque nécessaire + // et vérifier qu'elle est ouverte avant accès + Box get _membreBox { + _ensureBoxIsOpen(); + return Hive.box(AppKeys.membresBoxName); + } - final ApiService _apiService; bool _isLoading = false; - MembreRepository(this._apiService); - // Getters bool get isLoading => _isLoading; List get membres => getAllMembres(); @@ -32,17 +34,12 @@ class MembreRepository extends ChangeNotifier { } } - // Méthode pour vérifier si une boîte est ouverte et l'ouvrir si nécessaire + // Méthode pour vérifier si la boîte est ouverte et l'ouvrir si nécessaire Future _ensureBoxIsOpen() async { - try { - if (!Hive.isBoxOpen(AppKeys.membresBoxName)) { - debugPrint('Ouverture de la boîte ${AppKeys.membresBoxName}...'); - await Hive.openBox(AppKeys.membresBoxName); - debugPrint('Boîte ${AppKeys.membresBoxName} ouverte avec succès'); - } - } catch (e) { - debugPrint('Erreur lors de l\'ouverture de la boîte ${AppKeys.membresBoxName}: $e'); - throw Exception('Impossible d\'ouvrir la boîte ${AppKeys.membresBoxName}: $e'); + const boxName = AppKeys.membresBoxName; + if (!Hive.isBoxOpen(boxName)) { + debugPrint('Ouverture de la boîte $boxName dans MembreRepository...'); + await Hive.openBox(boxName); } } @@ -95,12 +92,11 @@ class MembreRepository extends ChangeNotifier { } } - // Créer ou mettre à jour un membre - Future saveMembre(MembreModel membre) async { + // Sauvegarder un membre + Future saveMembre(MembreModel membre) async { await _ensureBoxIsOpen(); await _membreBox.put(membre.id, membre); notifyListeners(); - return membre; } // Supprimer un membre @@ -110,72 +106,35 @@ class MembreRepository extends ChangeNotifier { notifyListeners(); } - // Récupérer les membres depuis l'API (uniquement pour l'interface admin) - Future> fetchMembresFromApi() async { - _isLoading = true; - notifyListeners(); - - try { - final hasConnection = await _apiService.hasInternetConnection(); - if (!hasConnection) { - debugPrint('Pas de connexion Internet, utilisation des données locales'); - return getAllMembres(); - } - - // Endpoint à adapter selon votre API - final response = await _apiService.get('/membres'); - final List membresData = response.data['membres']; - - // Vider la boîte avant d'ajouter les nouveaux membres - await _ensureBoxIsOpen(); - await _membreBox.clear(); - - final List membres = []; - for (var membreData in membresData) { - try { - final membre = MembreModel.fromJson(membreData); - await _membreBox.put(membre.id, membre); - membres.add(membre); - } catch (e) { - debugPrint('Erreur lors du traitement d\'un membre: $e'); - continue; - } - } - - notifyListeners(); - return membres; - } catch (e) { - debugPrint('Erreur lors de la récupération des membres depuis l\'API: $e'); - return getAllMembres(); - } finally { - _isLoading = false; - notifyListeners(); - } - } - // Créer un membre via l'API - Future createMembreViaApi(MembreModel membre) async { + Future createMembre(MembreModel membre) async { _isLoading = true; notifyListeners(); try { - final hasConnection = await _apiService.hasInternetConnection(); - if (!hasConnection) { - debugPrint('Pas de connexion Internet, impossible de créer le membre'); - return null; + // Préparer les données pour l'API + final data = membre.toJson(); + + // Appeler l'API pour créer le membre + final response = await ApiService.instance.post('/membres', data: data); + + if (response.statusCode == 201 || response.statusCode == 200) { + // Récupérer l'ID du nouveau membre + final membreId = response.data['id'] is String ? int.parse(response.data['id']) : response.data['id'] as int; + + // Créer le membre localement avec l'ID retourné par l'API + final newMembre = membre.copyWith( + id: membreId, + lastSyncedAt: DateTime.now(), + isSynced: true, + ); + await saveMembre(newMembre); + return true; } - - // Endpoint à adapter selon votre API - final response = await _apiService.post('/membres', data: membre.toJson()); - final membreData = response.data['membre']; - - final newMembre = MembreModel.fromJson(membreData); - await saveMembre(newMembre); - - return newMembre; + return false; } catch (e) { - debugPrint('Erreur lors de la création du membre via l\'API: $e'); - return null; + debugPrint('Erreur lors de la création du membre: $e'); + return false; } finally { _isLoading = false; notifyListeners(); @@ -183,28 +142,32 @@ class MembreRepository extends ChangeNotifier { } // Mettre à jour un membre via l'API - Future updateMembreViaApi(MembreModel membre) async { + Future updateMembre(MembreModel membre) async { _isLoading = true; notifyListeners(); try { - final hasConnection = await _apiService.hasInternetConnection(); - if (!hasConnection) { - debugPrint('Pas de connexion Internet, impossible de mettre à jour le membre'); - return null; + // Préparer les données pour l'API + final data = membre.toJson(); + + // Appeler l'API pour mettre à jour le membre + final response = await ApiService.instance.put('/membres/${membre.id}', data: data); + + if (response.statusCode == 200) { + // Mettre à jour le membre localement + final updatedMembre = membre.copyWith( + lastSyncedAt: DateTime.now(), + isSynced: true, + ); + + await saveMembre(updatedMembre); + return true; } - // Endpoint à adapter selon votre API - final response = await _apiService.put('/membres/${membre.id}', data: membre.toJson()); - final membreData = response.data['membre']; - - final updatedMembre = MembreModel.fromJson(membreData); - await saveMembre(updatedMembre); - - return updatedMembre; + return false; } catch (e) { - debugPrint('Erreur lors de la mise à jour du membre via l\'API: $e'); - return null; + debugPrint('Erreur lors de la mise à jour du membre: $e'); + return false; } finally { _isLoading = false; notifyListeners(); @@ -217,25 +180,66 @@ class MembreRepository extends ChangeNotifier { notifyListeners(); try { - final hasConnection = await _apiService.hasInternetConnection(); - if (!hasConnection) { - debugPrint('Pas de connexion Internet, impossible de supprimer le membre'); - return false; + // Appeler l'API pour supprimer le membre + final response = await ApiService.instance.delete('/membres/$id'); + + if (response.statusCode == 200 || response.statusCode == 204) { + // Supprimer le membre localement + await deleteMembre(id); + return true; } - // Endpoint à adapter selon votre API - await _apiService.delete('/membres/$id'); - - // Supprimer localement - await deleteMembre(id); - - return true; + return false; } catch (e) { - debugPrint('Erreur lors de la suppression du membre via l\'API: $e'); + debugPrint('Erreur lors de la suppression du membre: $e'); return false; } finally { _isLoading = false; notifyListeners(); } } + + // Traitement des données de membres depuis l'API + Future processMembresData(dynamic membresData) async { + try { + debugPrint('Traitement des données des membres...'); + + // Vérifier que les données sont au bon format + if (membresData == null) { + debugPrint('Aucune donnée de membre à traiter'); + return; + } + + List membresList; + if (membresData is List) { + membresList = membresData; + } else if (membresData is Map && membresData.containsKey('data')) { + membresList = membresData['data'] as List; + } else { + debugPrint('Format de données de membres non reconnu'); + return; + } + + // Vider la boîte avant d'ajouter les nouvelles données + await _ensureBoxIsOpen(); + await _membreBox.clear(); + + // Traiter chaque membre + int count = 0; + for (final membreData in membresList) { + try { + final membre = MembreModel.fromJson(membreData); + await _membreBox.put(membre.id, membre); + count++; + } catch (e) { + debugPrint('Erreur lors du traitement d\'un membre: $e'); + } + } + + debugPrint('$count membres traités et stockés'); + notifyListeners(); + } catch (e) { + debugPrint('Erreur lors du traitement des membres: $e'); + } + } } diff --git a/app/lib/core/repositories/operation_repository.dart b/app/lib/core/repositories/operation_repository.dart index 35e1dae1..4d5eeca0 100644 --- a/app/lib/core/repositories/operation_repository.dart +++ b/app/lib/core/repositories/operation_repository.dart @@ -12,20 +12,20 @@ class OperationRepository extends ChangeNotifier { _ensureBoxIsOpen(); return Hive.box(AppKeys.operationsBoxName); } - + // Méthode pour vérifier si la boîte est ouverte et l'ouvrir si nécessaire Future _ensureBoxIsOpen() async { - final boxName = AppKeys.operationsBoxName; + const boxName = AppKeys.operationsBoxName; if (!Hive.isBoxOpen(boxName)) { debugPrint('Ouverture de la boîte $boxName dans OperationRepository...'); await Hive.openBox(boxName); } } - final ApiService _apiService; bool _isLoading = false; - OperationRepository(this._apiService); + // Constructeur sans paramètres - utilise ApiService.instance + OperationRepository(); // Getters bool get isLoading => _isLoading; @@ -61,13 +61,11 @@ class OperationRepository extends ChangeNotifier { try { for (var operationData in operationsData) { final operationJson = operationData as Map; - final operationId = operationJson['id'] is String - ? int.parse(operationJson['id']) - : operationJson['id'] as int; - + final operationId = operationJson['id'] is String ? int.parse(operationJson['id']) : operationJson['id'] as int; + // Vérifier si l'opération existe déjà OperationModel? existingOperation = getOperationById(operationId); - + if (existingOperation == null) { // Créer une nouvelle opération final newOperation = OperationModel.fromJson(operationJson); @@ -106,14 +104,12 @@ class OperationRepository extends ChangeNotifier { }; // Appeler l'API pour créer l'opération - final response = await _apiService.post('/operations', data: data); - + final response = await ApiService.instance.post('/operations', data: data); + if (response.statusCode == 201 || response.statusCode == 200) { // Récupérer l'ID de la nouvelle opération - final operationId = response.data['id'] is String - ? int.parse(response.data['id']) - : response.data['id'] as int; - + final operationId = response.data['id'] is String ? int.parse(response.data['id']) : response.data['id'] as int; + // Créer l'opération localement final newOperation = OperationModel( id: operationId, @@ -124,11 +120,11 @@ class OperationRepository extends ChangeNotifier { isActive: true, isSynced: true, ); - + await saveOperation(newOperation); return true; } - + return false; } catch (e) { debugPrint('Erreur lors de la création de l\'opération: $e'); @@ -161,8 +157,8 @@ class OperationRepository extends ChangeNotifier { }; // Appeler l'API pour mettre à jour l'opération - final response = await _apiService.put('/operations/$id', data: data); - + final response = await ApiService.instance.put('/operations/$id', data: data); + if (response.statusCode == 200) { // Mettre à jour l'opération localement final updatedOperation = existingOperation.copyWith( @@ -173,11 +169,11 @@ class OperationRepository extends ChangeNotifier { lastSyncedAt: DateTime.now(), isSynced: true, ); - + await saveOperation(updatedOperation); return true; } - + return false; } catch (e) { debugPrint('Erreur lors de la mise à jour de l\'opération: $e'); @@ -195,14 +191,14 @@ class OperationRepository extends ChangeNotifier { try { // Appeler l'API pour supprimer l'opération - final response = await _apiService.delete('/operations/$id'); - + final response = await ApiService.instance.delete('/operations/$id'); + if (response.statusCode == 200 || response.statusCode == 204) { // Supprimer l'opération localement await deleteOperation(id); return true; } - + return false; } catch (e) { debugPrint('Erreur lors de la suppression de l\'opération: $e'); diff --git a/app/lib/core/repositories/passage_repository.dart b/app/lib/core/repositories/passage_repository.dart index 31e90bab..4faafb68 100644 --- a/app/lib/core/repositories/passage_repository.dart +++ b/app/lib/core/repositories/passage_repository.dart @@ -1,344 +1,141 @@ import 'dart:async'; -import 'package:flutter/material.dart'; +import 'package:flutter/foundation.dart'; import 'package:hive/hive.dart'; import 'package:geosector_app/core/data/models/passage_model.dart'; import 'package:geosector_app/core/services/api_service.dart'; import 'package:geosector_app/core/constants/app_keys.dart'; class PassageRepository extends ChangeNotifier { + // Constructeur sans paramètres - utilise ApiService.instance + PassageRepository(); + // Utiliser un getter lazy pour n'accéder à la boîte que lorsque nécessaire // et vérifier qu'elle est ouverte avant accès - Box? _box; - Box get _passageBox { - if (_box != null && _box!.isOpen) { - return _box!; - } - - if (!Hive.isBoxOpen(AppKeys.passagesBoxName)) { - throw StateError( - 'La boîte ${AppKeys.passagesBoxName} n\'est pas ouverte. Appelez _ensureBoxIsOpen() avant d\'accéder à la boîte.'); - } - - _box = Hive.box(AppKeys.passagesBoxName); - return _box!; + _ensureBoxIsOpen(); + return Hive.box(AppKeys.passagesBoxName); } - // Méthode pour vérifier si la boîte est ouverte et l'ouvrir si nécessaire - Future _ensureBoxIsOpen() async { - final boxName = AppKeys.passagesBoxName; + // Stream pour notifier des changements de passages + StreamController>? _passageStreamController; - // Si nous avons déjà une référence à la boîte et qu'elle est ouverte, retourner - if (_box != null && _box!.isOpen) { - return; - } - - // Si la boîte est déjà ouverte, récupérer la référence - if (Hive.isBoxOpen(boxName)) { - _box = Hive.box(boxName); - debugPrint( - 'PassageRepository: Boîte $boxName déjà ouverte, référence récupérée'); - return; - } - - // Sinon, ouvrir la boîte - try { - debugPrint('PassageRepository: Ouverture de la boîte $boxName...'); - _box = await Hive.openBox(boxName); - debugPrint('PassageRepository: Boîte $boxName ouverte avec succès'); - } catch (e) { - debugPrint( - 'PassageRepository: ERREUR lors de l\'ouverture de la boîte $boxName: $e'); - rethrow; // Propager l'erreur pour permettre une gestion appropriée - } + // Getter pour le stream des passages + Stream> get passageStream { + _passageStreamController ??= StreamController>.broadcast(); + return _passageStreamController!.stream; } - final ApiService _apiService; + @override + void dispose() { + _passageStreamController?.close(); + super.dispose(); + } + // ID du secteur par défaut + int defaultSectorId = 1; bool _isLoading = false; - PassageRepository(this._apiService); - // Getters bool get isLoading => _isLoading; List get passages => getAllPassages(); + // Méthode pour vérifier si la boîte est ouverte et l'ouvrir si nécessaire + Future _ensureBoxIsOpen() async { + const boxName = AppKeys.passagesBoxName; + if (!Hive.isBoxOpen(boxName)) { + debugPrint('Ouverture de la boîte $boxName dans PassageRepository...'); + await Hive.openBox(boxName); + } + } + // Récupérer tous les passages List getAllPassages() { - try { - // S'assurer que la boîte est ouverte avant d'y accéder - _ensureBoxIsOpen().then((_) { - debugPrint( - 'PassageRepository: Boîte ouverte avec succès pour getAllPassages'); - }).catchError((e) { - debugPrint( - 'PassageRepository: Erreur lors de l\'ouverture de la boîte pour getAllPassages: $e'); - }); - - return _passageBox.values.toList(); - } catch (e) { - debugPrint('PassageRepository: Erreur dans getAllPassages: $e'); - return []; // Retourner une liste vide en cas d'erreur - } + return _passageBox.values.toList(); } // Récupérer un passage par son ID PassageModel? getPassageById(int id) { - try { - // S'assurer que la boîte est ouverte avant d'y accéder - _ensureBoxIsOpen().then((_) { - debugPrint( - 'PassageRepository: Boîte ouverte avec succès pour getPassageById'); - }).catchError((e) { - debugPrint( - 'PassageRepository: Erreur lors de l\'ouverture de la boîte pour getPassageById: $e'); - }); - - return _passageBox.get(id); - } catch (e) { - debugPrint('PassageRepository: Erreur dans getPassageById: $e'); - return null; - } + return _passageBox.get(id); } // Récupérer les passages par secteur - List getPassagesBySector(int sectorId) { - try { - // S'assurer que la boîte est ouverte avant d'y accéder - _ensureBoxIsOpen().then((_) { - debugPrint( - 'PassageRepository: Boîte ouverte avec succès pour getPassagesBySector'); - }).catchError((e) { - debugPrint( - 'PassageRepository: Erreur lors de l\'ouverture de la boîte pour getPassagesBySector: $e'); - }); - - return _passageBox.values - .where((passage) => passage.fkSector == sectorId) - .toList(); - } catch (e) { - debugPrint('PassageRepository: Erreur dans getPassagesBySector: $e'); - return []; - } - } - - // Récupérer les passages par opération - List getPassagesByOperation(int operationId) { - try { - // S'assurer que la boîte est ouverte avant d'y accéder - _ensureBoxIsOpen().then((_) { - debugPrint( - 'PassageRepository: Boîte ouverte avec succès pour getPassagesByOperation'); - }).catchError((e) { - debugPrint( - 'PassageRepository: Erreur lors de l\'ouverture de la boîte pour getPassagesByOperation: $e'); - }); - - return _passageBox.values - .where((passage) => passage.fkOperation == operationId) - .toList(); - } catch (e) { - debugPrint('PassageRepository: Erreur dans getPassagesByOperation: $e'); - return []; - } + List getPassagesBySectorId(int sectorId) { + return _passageBox.values.where((passage) => passage.fkSector == sectorId).toList(); } // Récupérer les passages par type - List getPassagesByType(int typeId) { - try { - // S'assurer que la boîte est ouverte avant d'y accéder - _ensureBoxIsOpen().then((_) { - debugPrint( - 'PassageRepository: Boîte ouverte avec succès pour getPassagesByType'); - }).catchError((e) { - debugPrint( - 'PassageRepository: Erreur lors de l\'ouverture de la boîte pour getPassagesByType: $e'); - }); - - return _passageBox.values - .where((passage) => passage.fkType == typeId) - .toList(); - } catch (e) { - debugPrint('PassageRepository: Erreur dans getPassagesByType: $e'); - return []; - } + List getPassagesByType(int type) { + return _passageBox.values.where((passage) => passage.fkType == type).toList(); } - // Récupérer les passages par type de règlement - List getPassagesByPaymentType(int paymentTypeId) { - try { - // S'assurer que la boîte est ouverte avant d'y accéder - _ensureBoxIsOpen().then((_) { - debugPrint( - 'PassageRepository: Boîte ouverte avec succès pour getPassagesByPaymentType'); - }).catchError((e) { - debugPrint( - 'PassageRepository: Erreur lors de l\'ouverture de la boîte pour getPassagesByPaymentType: $e'); - }); - - return _passageBox.values - .where((passage) => passage.fkTypeReglement == paymentTypeId) - .toList(); - } catch (e) { - debugPrint('PassageRepository: Erreur dans getPassagesByPaymentType: $e'); - return []; - } + // Récupérer les passages par date + List getPassagesByDate(DateTime date) { + return _passageBox.values.where((passage) { + final passageDate = DateTime( + passage.passedAt.year, + passage.passedAt.month, + passage.passedAt.day, + ); + final searchDate = DateTime(date.year, date.month, date.day); + return passageDate.isAtSameMomentAs(searchDate); + }).toList(); } // Sauvegarder un passage Future savePassage(PassageModel passage) async { await _passageBox.put(passage.id, passage); notifyListeners(); + _notifyPassageStream(); + } + + // Sauvegarder plusieurs passages + Future savePassages(List passages) async { + for (final passage in passages) { + await _passageBox.put(passage.id, passage); + } + notifyListeners(); + _notifyPassageStream(); } // Supprimer un passage Future deletePassage(int id) async { await _passageBox.delete(id); notifyListeners(); + _notifyPassageStream(); } - // Traiter les passages reçus de l'API - Future processPassagesFromApi(List passagesData) async { - _isLoading = true; - notifyListeners(); - - try { - for (var passageData in passagesData) { - final passageJson = passageData as Map; - final passageId = passageJson['id'] is String - ? int.parse(passageJson['id']) - : passageJson['id'] as int; - - // Vérifier si le passage existe déjà - PassageModel? existingPassage = getPassageById(passageId); - - if (existingPassage == null) { - // Créer un nouveau passage - final newPassage = PassageModel.fromJson(passageJson); - await savePassage(newPassage); - } else { - // Mettre à jour le passage existant avec les nouvelles données - final updatedPassage = PassageModel.fromJson(passageJson).copyWith( - lastSyncedAt: DateTime.now(), - isActive: existingPassage.isActive, - isSynced: true, - ); - await savePassage(updatedPassage); - } - } - } catch (e) { - debugPrint('Erreur lors du traitement des passages: $e'); - } finally { - _isLoading = false; - notifyListeners(); - } + // Notifier le stream des changements + void _notifyPassageStream() { + _passageStreamController?.add(getAllPassages()); } - // Créer un nouveau passage - Future createPassage({ - required int fkOperation, - required int fkSector, - required int fkUser, - required int fkType, - required String fkAdresse, - required DateTime passedAt, - required String numero, - required String rue, - String rueBis = '', - required String ville, - String residence = '', - required int fkHabitat, - String appt = '', - String niveau = '', - required String gpsLat, - required String gpsLng, - String nomRecu = '', - String remarque = '', - required String montant, - required int fkTypeReglement, - String name = '', - String email = '', - String phone = '', - }) async { + // Créer un passage via l'API + Future createPassage(PassageModel passage) async { _isLoading = true; notifyListeners(); try { // Préparer les données pour l'API - final Map data = { - 'fk_operation': fkOperation, - 'fk_sector': fkSector, - 'fk_user': fkUser, - 'fk_type': fkType, - 'fk_adresse': fkAdresse, - 'passed_at': passedAt.toIso8601String(), - 'numero': numero, - 'rue': rue, - 'rue_bis': rueBis, - 'ville': ville, - 'residence': residence, - 'fk_habitat': fkHabitat, - 'appt': appt, - 'niveau': niveau, - 'gps_lat': gpsLat, - 'gps_lng': gpsLng, - 'nom_recu': nomRecu, - 'remarque': remarque, - 'montant': montant, - 'fk_type_reglement': fkTypeReglement, - 'name': name, - 'email': email, - 'phone': phone, - }; + final data = passage.toJson(); // Appeler l'API pour créer le passage - final response = await _apiService.post('/passages', data: data); + final response = await ApiService.instance.post('/passages', data: data); if (response.statusCode == 201 || response.statusCode == 200) { // Récupérer l'ID du nouveau passage - final passageId = response.data['id'] is String - ? int.parse(response.data['id']) - : response.data['id'] as int; + final passageId = response.data['id'] is String ? int.parse(response.data['id']) : response.data['id'] as int; - // Créer le modèle local - final newPassage = PassageModel( + // Créer le passage localement avec l'ID retourné par l'API + final newPassage = passage.copyWith( id: passageId, - fkOperation: fkOperation, - fkSector: fkSector, - fkUser: fkUser, - fkType: fkType, - fkAdresse: fkAdresse, - passedAt: passedAt, - numero: numero, - rue: rue, - rueBis: rueBis, - ville: ville, - residence: residence, - fkHabitat: fkHabitat, - appt: appt, - niveau: niveau, - gpsLat: gpsLat, - gpsLng: gpsLng, - nomRecu: nomRecu, - remarque: remarque, - montant: montant, - fkTypeReglement: fkTypeReglement, - nbPassages: 1, // Par défaut pour un nouveau passage - name: name, - email: email, - phone: phone, lastSyncedAt: DateTime.now(), - isActive: true, isSynced: true, ); await savePassage(newPassage); return true; - } else { - debugPrint( - 'Erreur lors de la création du passage: ${response.statusMessage}'); - return false; } + return false; } catch (e) { debugPrint('Erreur lors de la création du passage: $e'); return false; @@ -348,51 +145,30 @@ class PassageRepository extends ChangeNotifier { } } - // Mettre à jour un passage existant + // Mettre à jour un passage via l'API Future updatePassage(PassageModel passage) async { _isLoading = true; notifyListeners(); try { // Préparer les données pour l'API - final Map data = passage.toJson(); + final data = passage.toJson(); // Appeler l'API pour mettre à jour le passage - final response = - await _apiService.put('/passages/${passage.id}', data: data); - + final response = await ApiService.instance.put('/passages/${passage.id}', data: data); if (response.statusCode == 200) { - // Mettre à jour le modèle local + // Mettre à jour le passage localement final updatedPassage = passage.copyWith( lastSyncedAt: DateTime.now(), isSynced: true, ); - await savePassage(updatedPassage); return true; - } else { - debugPrint( - 'Erreur lors de la mise à jour du passage: ${response.statusMessage}'); - - // Marquer comme non synchronisé mais sauvegarder localement - final updatedPassage = passage.copyWith( - lastSyncedAt: DateTime.now(), - isSynced: false, - ); - - await savePassage(updatedPassage); - return false; } + + return false; } catch (e) { debugPrint('Erreur lors de la mise à jour du passage: $e'); - - // Marquer comme non synchronisé mais sauvegarder localement - final updatedPassage = passage.copyWith( - lastSyncedAt: DateTime.now(), - isSynced: false, - ); - - await savePassage(updatedPassage); return false; } finally { _isLoading = false; @@ -400,103 +176,107 @@ class PassageRepository extends ChangeNotifier { } } - // Synchroniser tous les passages non synchronisés - Future syncUnsyncedPassages() async { + // Supprimer un passage via l'API + Future deletePassageViaApi(int id) async { + _isLoading = true; + notifyListeners(); + try { - final hasConnection = await _apiService.hasInternetConnection(); + // Appeler l'API pour supprimer le passage + final response = await ApiService.instance.delete('/passages/$id'); - if (!hasConnection) { - return; + if (response.statusCode == 200 || response.statusCode == 204) { + // Supprimer le passage localement + await deletePassage(id); + return true; } - final unsyncedPassages = - _passageBox.values.where((passage) => !passage.isSynced).toList(); - - if (unsyncedPassages.isEmpty) { - return; - } - - _isLoading = true; - notifyListeners(); - - for (final passage in unsyncedPassages) { - try { - if (passage.id < 0) { - // Nouveau passage créé localement, à envoyer à l'API - await createPassage( - fkOperation: passage.fkOperation, - fkSector: passage.fkSector, - fkUser: passage.fkUser, - fkType: passage.fkType, - fkAdresse: passage.fkAdresse, - passedAt: passage.passedAt, - numero: passage.numero, - rue: passage.rue, - rueBis: passage.rueBis, - ville: passage.ville, - residence: passage.residence, - fkHabitat: passage.fkHabitat, - appt: passage.appt, - niveau: passage.niveau, - gpsLat: passage.gpsLat, - gpsLng: passage.gpsLng, - nomRecu: passage.nomRecu, - remarque: passage.remarque, - montant: passage.montant, - fkTypeReglement: passage.fkTypeReglement, - name: passage.name, - email: passage.email, - phone: passage.phone, - ); - - // Supprimer l'ancien passage avec ID temporaire - await deletePassage(passage.id); - } else { - // Passage existant à mettre à jour - await updatePassage(passage); - } - } catch (e) { - debugPrint( - 'Erreur lors de la synchronisation du passage ${passage.id}: $e'); - } - } + return false; } catch (e) { - debugPrint('Erreur lors de la synchronisation des passages: $e'); + debugPrint('Erreur lors de la suppression du passage: $e'); + return false; } finally { _isLoading = false; notifyListeners(); } } - // Récupérer les passages depuis l'API - Future fetchPassages() async { + // Traitement des données de passages depuis l'API + Future processPassagesData(dynamic passagesData) async { try { - final hasConnection = await _apiService.hasInternetConnection(); + debugPrint('Traitement des données des passages...'); - if (!hasConnection) { + // Vérifier que les données sont au bon format + if (passagesData == null) { + debugPrint('Aucune donnée de passage à traiter'); return; } - _isLoading = true; - notifyListeners(); - - final response = await _apiService.get('/passages'); - - if (response.statusCode == 200) { - final List passagesData = response.data; - await processPassagesFromApi(passagesData); + List passagesList; + if (passagesData is List) { + passagesList = passagesData; + } else if (passagesData is Map && passagesData.containsKey('data')) { + passagesList = passagesData['data'] as List; + } else { + debugPrint('Format de données de passages non reconnu'); + return; } - } catch (e) { - debugPrint('Erreur lors de la récupération des passages: $e'); - } finally { - _isLoading = false; + + // Vider la boîte avant d'ajouter les nouvelles données + await _passageBox.clear(); + + // Traiter chaque passage + int count = 0; + for (final passageData in passagesList) { + try { + final passage = PassageModel.fromJson(passageData); + await _passageBox.put(passage.id, passage); + count++; + } catch (e) { + debugPrint('Erreur lors du traitement d\'un passage: $e'); + } + } + + debugPrint('$count passages traités et stockés'); notifyListeners(); + _notifyPassageStream(); + } catch (e) { + debugPrint('Erreur lors du traitement des passages: $e'); } } + // Synchroniser tous les passages non synchronisés + Future syncUnsyncedPassages() async { + final unsyncedPassages = _passageBox.values.where((passage) => !passage.isSynced).toList(); + + for (final passage in unsyncedPassages) { + try { + await updatePassage(passage); + } catch (e) { + debugPrint('Erreur lors de la synchronisation du passage ${passage.id}: $e'); + } + } + } + + // Statistiques + Map getPassageStatistics() { + final allPassages = getAllPassages(); + + return { + 'total': allPassages.length, + 'effectues': allPassages.where((p) => p.fkType == 1).length, + 'a_finaliser': allPassages.where((p) => p.fkType == 2).length, + 'refuses': allPassages.where((p) => p.fkType == 3).length, + 'dons': allPassages.where((p) => p.fkType == 4).length, + 'lots': allPassages.where((p) => p.fkType == 5).length, + 'maisons_vides': allPassages.where((p) => p.fkType == 6).length, + }; + } + // Vider tous les passages Future clearAllPassages() async { await _passageBox.clear(); notifyListeners(); + _notifyPassageStream(); } } diff --git a/app/lib/core/repositories/sector_repository.dart b/app/lib/core/repositories/sector_repository.dart index 1e0ede90..01bed118 100644 --- a/app/lib/core/repositories/sector_repository.dart +++ b/app/lib/core/repositories/sector_repository.dart @@ -1,146 +1,186 @@ +import 'package:flutter/foundation.dart'; import 'package:hive/hive.dart'; import 'package:geosector_app/core/data/models/sector_model.dart'; import 'package:geosector_app/core/services/api_service.dart'; import 'package:geosector_app/core/constants/app_keys.dart'; -class SectorRepository { - final ApiService _apiService; - - SectorRepository(this._apiService); - +class SectorRepository extends ChangeNotifier { + // Constructeur sans paramètres - utilise ApiService.instance + SectorRepository(); // Utiliser un getter lazy pour n'accéder à la boîte que lorsque nécessaire // et vérifier qu'elle est ouverte avant accès - Box get _sectorsBox { + Box get _sectorBox { _ensureBoxIsOpen(); return Hive.box(AppKeys.sectorsBoxName); } - + + // Constante pour l'ID par défaut + static const int defaultSectorId = 1; + // Méthode pour vérifier si la boîte est ouverte et l'ouvrir si nécessaire Future _ensureBoxIsOpen() async { - final boxName = AppKeys.sectorsBoxName; + debugPrint('SectorRepository: Vérification de l\'ouverture de la boîte ${AppKeys.sectorsBoxName}...'); + const boxName = AppKeys.sectorsBoxName; if (!Hive.isBoxOpen(boxName)) { - print('Ouverture de la boîte $boxName dans SectorRepository...'); + debugPrint('Ouverture de la boîte $boxName dans SectorRepository...'); await Hive.openBox(boxName); } } - - // Récupérer tous les secteurs depuis la base de données locale + + // Récupérer tous les secteurs List getAllSectors() { - return _sectorsBox.values.toList(); + return _sectorBox.values.toList(); } - + // Récupérer un secteur par son ID SectorModel? getSectorById(int id) { - try { - return _sectorsBox.values.firstWhere( - (sector) => sector.id == id, - ); - } catch (e) { - return null; - } + return _sectorBox.get(id); } - + + // Sauvegarder un secteur + Future saveSector(SectorModel sector) async { + await _sectorBox.put(sector.id, sector); + notifyListeners(); + } + + // Supprimer un secteur + Future deleteSector(int id) async { + await _sectorBox.delete(id); + notifyListeners(); + } + // Sauvegarder les secteurs dans la base de données locale Future saveSectors(List sectors) async { // Vider la box avant d'ajouter les nouveaux secteurs - await _sectorsBox.clear(); - + await _sectorBox.clear(); + // Ajouter les nouveaux secteurs for (final sector in sectors) { - await _sectorsBox.put(sector.id, sector); + await _sectorBox.put(sector.id, sector); + } + notifyListeners(); + } + + // Traitement des données de secteurs depuis l'API + Future processSectorsData(dynamic sectorsData) async { + try { + debugPrint('Traitement des données des secteurs...'); + + // Vérifier que les données sont au bon format + if (sectorsData == null) { + debugPrint('Aucune donnée de secteur à traiter'); + return; + } + + List sectorsList; + if (sectorsData is List) { + sectorsList = sectorsData; + } else if (sectorsData is Map && sectorsData.containsKey('data')) { + sectorsList = sectorsData['data'] as List; + } else { + debugPrint('Format de données de secteurs non reconnu'); + return; + } + + // Vider la boîte avant d'ajouter les nouvelles données + await _sectorBox.clear(); + + // Traiter chaque secteur + int count = 0; + for (final sectorData in sectorsList) { + try { + final sector = SectorModel.fromJson(sectorData); + await _sectorBox.put(sector.id, sector); + count++; + } catch (e) { + debugPrint('Erreur lors du traitement d\'un secteur: $e'); + } + } + + debugPrint('$count secteurs traités et stockés'); + notifyListeners(); + } catch (e) { + debugPrint('Erreur lors du traitement des secteurs: $e'); } } - - // Ajouter ou mettre à jour un secteur - Future saveSector(SectorModel sector) async { - await _sectorsBox.put(sector.id, sector); - } - - // Supprimer un secteur - Future deleteSector(int id) async { - await _sectorsBox.delete(id); - } - + // Récupérer les secteurs depuis l'API Future> fetchSectorsFromApi() async { try { - final response = await _apiService.get(AppKeys.sectorsEndpoint); + final response = await ApiService.instance.get(AppKeys.sectorsEndpoint); final Map responseData = response as Map; - + if (responseData['status'] == 'success' && responseData['sectors'] != null) { final List sectorsJson = responseData['sectors']; - final List sectors = sectorsJson - .map((json) => SectorModel.fromJson(json)) - .toList(); - + final List sectors = sectorsJson.map((json) => SectorModel.fromJson(json)).toList(); + // Sauvegarder les secteurs localement await saveSectors(sectors); - return sectors; } - + return []; } catch (e) { // En cas d'erreur, retourner les secteurs locaux return getAllSectors(); } } - + // Créer un nouveau secteur via l'API Future createSector(SectorModel sector) async { try { - final response = await _apiService.post( + final response = await ApiService.instance.post( AppKeys.sectorsEndpoint, data: sector.toJson(), ); final Map responseData = response as Map; - + if (responseData['status'] == 'success' && responseData['sector'] != null) { final SectorModel newSector = SectorModel.fromJson(responseData['sector']); await saveSector(newSector); return newSector; } - + return null; } catch (e) { return null; } } - + // Mettre à jour un secteur via l'API Future updateSector(SectorModel sector) async { try { - final response = await _apiService.put( + final response = await ApiService.instance.put( '${AppKeys.sectorsEndpoint}/${sector.id}', data: sector.toJson(), ); final Map responseData = response as Map; - + if (responseData['status'] == 'success' && responseData['sector'] != null) { final SectorModel updatedSector = SectorModel.fromJson(responseData['sector']); await saveSector(updatedSector); return updatedSector; } - + return null; } catch (e) { return null; } } - + // Supprimer un secteur via l'API Future deleteSectorFromApi(int id) async { try { - final response = await _apiService.delete( + final response = await ApiService.instance.delete( '${AppKeys.sectorsEndpoint}/$id', ); final Map responseData = response as Map; - + if (responseData['status'] == 'success') { await deleteSector(id); return true; } - + return false; } catch (e) { return false; diff --git a/app/lib/core/repositories/user_repository.dart b/app/lib/core/repositories/user_repository.dart index 5740cd55..9a0f1d5c 100644 --- a/app/lib/core/repositories/user_repository.dart +++ b/app/lib/core/repositories/user_repository.dart @@ -1,15 +1,13 @@ import 'dart:async'; -import 'dart:io'; -import 'dart:js' as js; -import 'package:geosector_app/core/services/hive_web_fix.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import 'package:hive_flutter/hive_flutter.dart'; -import 'package:path_provider/path_provider.dart'; import 'package:geosector_app/core/constants/app_keys.dart'; import 'package:geosector_app/core/services/api_service.dart'; -import 'package:geosector_app/core/services/sync_service.dart'; +import 'package:geosector_app/core/services/current_user_service.dart'; +import 'package:geosector_app/core/services/current_amicale_service.dart'; +import 'package:geosector_app/core/services/data_loading_service.dart'; import 'package:geosector_app/core/services/hive_reset_state_service.dart'; import 'package:geosector_app/core/data/models/user_model.dart'; import 'package:geosector_app/core/data/models/amicale_model.dart'; @@ -17,111 +15,16 @@ import 'package:geosector_app/core/data/models/operation_model.dart'; import 'package:geosector_app/core/data/models/sector_model.dart'; import 'package:geosector_app/core/data/models/passage_model.dart'; import 'package:geosector_app/core/data/models/membre_model.dart'; -import 'package:geosector_app/core/data/models/user_sector_model.dart'; -import 'package:geosector_app/core/repositories/operation_repository.dart'; -import 'package:geosector_app/core/repositories/sector_repository.dart'; -import 'package:geosector_app/core/repositories/passage_repository.dart'; -import 'package:geosector_app/core/repositories/client_repository.dart'; -import 'package:geosector_app/core/repositories/amicale_repository.dart'; -import 'package:geosector_app/chat/models/conversation_model.dart'; -import 'package:geosector_app/chat/models/message_model.dart'; -import 'package:geosector_app/presentation/widgets/loading_overlay.dart'; import 'package:geosector_app/presentation/widgets/loading_progress_overlay.dart'; import 'package:geosector_app/core/models/loading_state.dart'; class UserRepository extends ChangeNotifier { - // Contrôle de l'état du chargement des données - LoadingState _loadingState = LoadingState.initial; - LoadingState get loadingState => _loadingState; - // Overlay pour afficher la progression du chargement OverlayEntry? _progressOverlay; - - // Méthode pour mettre à jour l'état du chargement - void _updateLoadingState(LoadingState newState) { - _loadingState = newState; - notifyListeners(); - - // Mettre à jour l'overlay si présent - if (_progressOverlay != null) { - _progressOverlay!.markNeedsBuild(); - } - } - - // Utilisation de getters lazy pour n'accéder aux boîtes que lorsque nécessaire - Box get _userBox => Hive.box(AppKeys.userBoxName); - Box get _amicaleBox => Hive.box(AppKeys.amicaleBoxName); - - // Getters pour les autres boîtes qui vérifient si elles sont ouvertes avant accès - Box get _operationBox { - _ensureBoxIsOpen(AppKeys.operationsBoxName); - return Hive.box(AppKeys.operationsBoxName); - } - - Box get _sectorBox { - _ensureBoxIsOpen(AppKeys.sectorsBoxName); - return Hive.box(AppKeys.sectorsBoxName); - } - - Box get _passageBox { - _ensureBoxIsOpen(AppKeys.passagesBoxName); - return Hive.box(AppKeys.passagesBoxName); - } - - Box get _membreBox { - _ensureBoxIsOpen(AppKeys.membresBoxName); - return Hive.box(AppKeys.membresBoxName); - } - - Box get _userSectorBox { - _ensureBoxIsOpen(AppKeys.userSectorBoxName); - return Hive.box(AppKeys.userSectorBoxName); - } - - Box get _settingsBox { - _ensureBoxIsOpen(AppKeys.settingsBoxName); - return Hive.box(AppKeys.settingsBoxName); - } - - Box get _chatConversationBox { - _ensureBoxIsOpen(AppKeys.chatConversationsBoxName); - return Hive.box(AppKeys.chatConversationsBoxName); - } - - Box get _chatMessageBox { - _ensureBoxIsOpen(AppKeys.chatMessagesBoxName); - return Hive.box(AppKeys.chatMessagesBoxName); - } - - // Méthode pour initialiser les boîtes après connexion - Future _initializeBoxes() async { - debugPrint('Initialisation des boîtes Hive nécessaires...'); - await _ensureBoxIsOpen(AppKeys.amicaleBoxName); - await _ensureBoxIsOpen(AppKeys.operationsBoxName); - await _ensureBoxIsOpen(AppKeys.sectorsBoxName); - await _ensureBoxIsOpen(AppKeys.passagesBoxName); - await _ensureBoxIsOpen(AppKeys.membresBoxName); - await _ensureBoxIsOpen(AppKeys.userSectorBoxName); - // Les boîtes de chat sont déjà initialisées au démarrage - await _ensureBoxIsOpen(AppKeys.chatConversationsBoxName); - await _ensureBoxIsOpen(AppKeys.chatMessagesBoxName); - debugPrint('Toutes les boîtes Hive sont maintenant ouvertes'); - } - - final ApiService _apiService; - final SyncService? _syncService; - final OperationRepository? _operationRepository; - final SectorRepository? _sectorRepository; - final PassageRepository? _passageRepository; - bool _isLoading = false; - UserRepository(this._apiService, - {SyncService? syncService, OperationRepository? operationRepository, SectorRepository? sectorRepository, PassageRepository? passageRepository}) - : _syncService = syncService, - _operationRepository = operationRepository, - _sectorRepository = sectorRepository, - _passageRepository = passageRepository { + // Constructeur simplifié - plus d'injection d'ApiService + UserRepository() { // Initialiser la session si un utilisateur est déjà connecté final currentUser = getCurrentUser(); if (currentUser != null && currentUser.sessionId != null) { @@ -129,136 +32,155 @@ class UserRepository extends ChangeNotifier { } } - // Cache pour l'utilisateur actuel - UserModel? _cachedCurrentUser; + // === DÉLÉGATION AUX SERVICES === + UserModel? get currentUser => CurrentUserService.instance.currentUser; + bool get isLoggedIn => CurrentUserService.instance.isLoggedIn; + int get userRole => CurrentUserService.instance.userRole; + + // Getters délégués + bool get isUser => CurrentUserService.instance.isUser; + bool get isAdminAmicale => CurrentUserService.instance.isAdminAmicale; + bool get isSuperAdmin => CurrentUserService.instance.isSuperAdmin; + bool get canAccessAdmin => CurrentUserService.instance.canAccessAdmin; + int? get userId => CurrentUserService.instance.userId; + String? get userEmail => CurrentUserService.instance.userEmail; + String? get userName => CurrentUserService.instance.userName; + String? get userFirstName => CurrentUserService.instance.userFirstName; // Getters bool get isLoading => _isLoading; - bool get isLoggedIn => currentUser != null; - int? get userId => currentUser?.id; - UserModel? get currentUser { - // Utiliser le cache s'il existe déjà - if (_cachedCurrentUser != null) { - return _cachedCurrentUser; - } + LoadingState get loadingState => DataLoadingService.instance.loadingState; - // Sinon, récupérer l'utilisateur et le mettre en cache - _cachedCurrentUser = _getCurrentUserFromStorage(); - return _cachedCurrentUser; - } + // === GETTERS POUR LES BOXES DÉLÉGUÉES === + Box get _userBox => Hive.box(AppKeys.userBoxName); - // Retourne le rôle de l'utilisateur sous forme d'entier - int getUserRole() { - final user = getCurrentUser(); - if (user == null) return 0; // Aucun utilisateur = aucun rôle - - // Convertir le rôle en int si c'est une String - if (user.role is String) { - return int.tryParse(user.role as String) ?? 1; - } else { - return user.role; - } - } - - // Méthode privée pour récupérer l'utilisateur depuis le stockage - UserModel? _getCurrentUserFromStorage() { + // Getters sécurisés pour les autres données + List get operations { try { - // Vérifier d'abord si la boîte est ouverte - if (!Hive.isBoxOpen(AppKeys.userBoxName)) { - debugPrint('Boîte users non ouverte, tentative d\'ouverture...'); - return null; // Retourner null plutôt que d'essayer d'ouvrir ici + if (Hive.isBoxOpen(AppKeys.operationsBoxName)) { + return Hive.box(AppKeys.operationsBoxName).values.toList(); } - - // Chercher un utilisateur avec une session active - final activeUsers = _userBox.values.where((user) => user.sessionId != null && user.sessionId!.isNotEmpty).toList(); - - // S'il y a des utilisateurs actifs, retourner le premier - if (activeUsers.isNotEmpty) { - return activeUsers.first; - } - - return null; + return []; } catch (e) { - debugPrint('Erreur lors de la récupération de l\'utilisateur depuis le stockage: $e'); - return null; + debugPrint('⚠️ Erreur accès operations: $e'); + return []; } } - // Récupérer l'utilisateur actuellement connecté + List get sectors { + try { + if (Hive.isBoxOpen(AppKeys.sectorsBoxName)) { + return Hive.box(AppKeys.sectorsBoxName).values.toList(); + } + return []; + } catch (e) { + debugPrint('⚠️ Erreur accès sectors: $e'); + return []; + } + } + + List get passages { + try { + if (Hive.isBoxOpen(AppKeys.passagesBoxName)) { + return Hive.box(AppKeys.passagesBoxName).values.toList(); + } + return []; + } catch (e) { + debugPrint('⚠️ Erreur accès passages: $e'); + return []; + } + } + + List get membres { + try { + if (Hive.isBoxOpen(AppKeys.membresBoxName)) { + return Hive.box(AppKeys.membresBoxName).values.toList(); + } + return []; + } catch (e) { + debugPrint('⚠️ Erreur accès membres: $e'); + return []; + } + } + + // === MÉTHODES PRINCIPALES === + + /// Récupérer l'utilisateur actuellement connecté UserModel? getCurrentUser() { - // Utiliser le getter currentUser qui gère le cache - return currentUser; + return CurrentUserService.instance.currentUser; } - // Mettre à jour le chemin de la page actuelle pour l'utilisateur connecté + /// Retourne le rôle de l'utilisateur sous forme d'entier + int getUserRole() { + return CurrentUserService.instance.userRole; + } + + /// Mettre à jour le chemin de la page actuelle pour l'utilisateur connecté Future updateLastPath(String path) async { - final currentUser = getCurrentUser(); - if (currentUser != null) { - final updatedUser = currentUser.copyWith(lastPath: path); - await saveUser(updatedUser); - } + await CurrentUserService.instance.updateLastPath(path); } - // Récupérer le dernier chemin visité par l'utilisateur + /// Récupérer le dernier chemin visité par l'utilisateur String? getLastPath() { - final currentUser = getCurrentUser(); - return currentUser?.lastPath; + return CurrentUserService.instance.getLastPath(); } - // Configurer la session dans l'API + /// Configurer la session dans l'API void setSessionId(String? sessionId) { - _apiService.setSessionId(sessionId); - } - - // Login API PHP - Future> loginAPI(String username, String password, {required String type}) async { - try { - return await _apiService.login(username, password, type: type); - } catch (e) { - debugPrint('Erreur login API: $e'); - rethrow; - } - } - - // Register API PHP - Uniquement pour les administrateurs - Future> registerAPI(String email, String name, String amicaleName, String postalCode, String cityName) async { - try { - final Map data = {'email': email, 'name': name, 'amicale_name': amicaleName, 'postal_code': postalCode, 'city_name': cityName}; - - final response = await _apiService.post(AppKeys.registerEndpoint, data: data); - return response.data; - } catch (e) { - debugPrint('Erreur register API: $e'); - rethrow; - } - } - - // Logout API PHP - Future logoutAPI() async { - try { - await _apiService.logout(); - } catch (e) { - debugPrint('Erreur logout API: $e'); - rethrow; - } + ApiService.instance.setSessionId(sessionId); } /// Navigation après connexion réussie void navigateAfterLogin(BuildContext context) { - final user = currentUser; - - if (user != null && context.mounted) { - final isAdmin = user.role == 1 || user.role == 2; - context.go(isAdmin ? '/admin' : '/user'); + if (context.mounted) { + final route = CurrentUserService.instance.getDefaultRoute(); + context.go(route); } } - // Méthode d'inscription (uniquement pour les administrateurs) + // === AUTHENTIFICATION === + + /// Login API PHP + Future> loginAPI(String username, String password, {required String type}) async { + try { + return await ApiService.instance.login(username, password, type: type); + } catch (e) { + debugPrint('❌ Erreur login API: $e'); + rethrow; + } + } + + /// Register API PHP - Uniquement pour les administrateurs + Future> registerAPI(String email, String name, String amicaleName, String postalCode, String cityName) async { + try { + final Map data = {'email': email, 'name': name, 'amicale_name': amicaleName, 'postal_code': postalCode, 'city_name': cityName}; + + final response = await ApiService.instance.post(AppKeys.registerEndpoint, data: data); + return response.data; + } catch (e) { + debugPrint('❌ Erreur register API: $e'); + rethrow; + } + } + + /// Logout API PHP + Future logoutAPI() async { + try { + await ApiService.instance.logout(); + } catch (e) { + debugPrint('⚠️ Erreur logout API: $e'); + rethrow; + } + } + + /// Méthode d'inscription (uniquement pour les administrateurs) Future register(String email, String password, String name, String amicaleName, String postalCode, String cityName) async { _isLoading = true; notifyListeners(); try { + debugPrint('📝 Tentative d\'inscription: $email'); + // Enregistrer l'administrateur via l'API final apiResult = await registerAPI(email, name, amicaleName, postalCode, cityName); @@ -278,16 +200,17 @@ class UserRepository extends ChangeNotifier { sessionExpiry: DateTime.parse(apiResult['session_expiry']), ); - // Sauvegarder dans le repository local - await saveUser(newAdmin); + // Sauvegarder dans le service + await CurrentUserService.instance.setUser(newAdmin); // Configurer la session dans l'API setSessionId(newAdmin.sessionId); notifyListeners(); + debugPrint('✅ Inscription réussie'); return true; } catch (e) { - debugPrint('Erreur d\'inscription: $e'); + debugPrint('❌ Erreur d\'inscription: $e'); return false; } finally { _isLoading = false; @@ -295,309 +218,58 @@ class UserRepository extends ChangeNotifier { } } - // Login complet avec suivi de progression + /// Connexion simplifiée avec DataLoadingService Future login(String username, String password, {required String type}) async { _isLoading = true; - _updateLoadingState(LoadingState.initial.copyWith( - message: 'Connexion en cours...', - stepDescription: 'Préparation des données', - )); notifyListeners(); try { - debugPrint('Début du processus de connexion pour: $username'); + debugPrint('🔐 Tentative de connexion: $username'); - // Étape 1: Nettoyage des boîtes non référencées (5%) - _updateLoadingState(_loadingState.copyWith( - progress: 0.05, - stepDescription: 'Nettoyage des données obsolètes', - )); + // Étape 1: Nettoyage des données via DataLoadingService + await DataLoadingService.instance.cleanDataBeforeLogin(); - final nonDefinedBoxes = ['auth', 'locations', 'messages']; - for (final boxName in nonDefinedBoxes) { - try { - if (Hive.isBoxOpen(boxName)) { - debugPrint('Fermeture de la boîte non référencée: $boxName'); - await Hive.box(boxName).close(); - } - - // Supprimer la boîte du disque - await Hive.deleteBoxFromDisk(boxName); - debugPrint('Nettoyage: Box $boxName supprimée'); - } catch (e) { - debugPrint('Erreur lors de la suppression de la boîte non référencée $boxName: $e'); - } - } - - // Étape 2: Nettoyage des données existantes (10%) - _updateLoadingState(_loadingState.copyWith( - progress: 0.10, - stepDescription: 'Préparation du stockage local', - )); - - debugPrint('Nettoyage des données existantes avant connexion...'); - - // Sur le web, utiliser notre méthode sécurisée pour nettoyer les boîtes Hive - if (kIsWeb) { - await HiveWebFix.safeCleanHiveBoxes(excludeBoxes: [AppKeys.userBoxName]); - } - // Sur iOS, nettoyer les fichiers Hive directement - else if (!kIsWeb && Platform.isIOS) { - await _cleanHiveFilesOnIOS(); - } - // Sur Android, nettoyer les fichiers Hive directement - else if (!kIsWeb && Platform.isAndroid) { - await _cleanHiveFilesOnAndroid(); - } - - // Étape 3: Recréation des boîtes (15%) - _updateLoadingState(_loadingState.copyWith( - progress: 0.15, - stepDescription: 'Initialisation des bases de données', - )); - - // Nettoyer les boîtes sans les fermer - await _clearAndRecreateBoxes(); - - // Initialiser les boîtes nécessaires avant d'appeler l'API - // Cela garantit que toutes les boîtes sont ouvertes avant le traitement des données - await _initializeBoxes(); - - // Étape 4: Connexion à l'API (25%) - _updateLoadingState(_loadingState.copyWith( - progress: 0.25, - stepDescription: 'Connexion au serveur', - )); - - // Appeler l'API - debugPrint('Appel de l\'API de connexion (type: $type)...'); + // Étape 2: Connexion à l'API (25%) final apiResult = await loginAPI(username, password, type: type); // Vérifier le statut de la réponse final status = apiResult['status'] as String?; final message = apiResult['message'] as String?; - // Si le statut n'est pas 'success', retourner false if (status != 'success') { - debugPrint('Échec de connexion: $message'); - _updateLoadingState(LoadingState.error(message ?? 'Échec de connexion')); + debugPrint('❌ Connexion échouée: $message'); return false; } - // Étape 5: Traitement des données utilisateur (35%) - _updateLoadingState(_loadingState.copyWith( - progress: 0.35, - stepDescription: 'Traitement des données utilisateur', - )); + // Étape 3: Traitement des données utilisateur (35%) + debugPrint('👤 Traitement des données utilisateur...'); - // Logging détaillé de la réponse API pour diagnostiquer le problème d'interface/rôle - debugPrint('Connexion réussie, contenu de la réponse API:'); - apiResult.forEach((key, value) { - debugPrint(' $key: $value'); - }); - - // Si la clé 'user' existe, examiner son contenu if (apiResult['user'] != null && apiResult['user'] is Map) { - debugPrint('Détails utilisateur:'); - final userDetails = apiResult['user'] as Map; - userDetails.forEach((key, value) { - debugPrint(' $key: $value (${value.runtimeType})'); - }); + final user = _processUserData(apiResult['user'] as Map, apiResult['session_id'], apiResult['session_expiry']); - // Construire un UserModel à partir des données utilisateur - final user = _processUserData(userDetails, apiResult['session_id'], apiResult['session_expiry']); - - // Supprimer les anciennes références à interface et utiliser directement le rôle - await saveUser(user); - - // Configurer la session + // Sauvegarder via le service + await CurrentUserService.instance.setUser(user); setSessionId(user.sessionId); - debugPrint('Utilisateur créé et sauvegardé: ${user.toJson()}'); - debugPrint('Vérification des rôles: role=${user.role}'); - debugPrint('Rôle utilisateur: ${getUserRole()}'); + debugPrint('✅ Utilisateur connecté: ${user.email}'); } - // Si la clé 'amicale' existe, examiner son contenu + // Étape 4: Traitement de l'amicale if (apiResult['amicale'] != null) { - debugPrint('Détails amicale:'); - - // Passer directement les données d'amicale au repository - // Le repository a été modifié pour gérer tous les formats possibles - final amicaleRepository = AmicaleRepository(_apiService); - await amicaleRepository.processAmicalesData(apiResult['amicale']); - - debugPrint('Amicales traitées et stockées via AmicaleRepository'); - } - - // Si la clé 'clients' existe, examiner son contenu - if (apiResult['clients'] != null) { - debugPrint('Détails clients:'); - List clientsList; - - if (apiResult['clients'] is List) { - clientsList = apiResult['clients'] as List; - } else if (apiResult['clients'] is Map && apiResult['clients'].containsKey('data')) { - clientsList = apiResult['clients']['data'] as List; - } else { - debugPrint('Format de données de clients non reconnu'); - clientsList = []; - } - - // Si l'utilisateur a un rôle > 2, traiter les clients - if (clientsList.isNotEmpty) { - debugPrint('Traitement de ${clientsList.length} clients de type 1'); - - // Utiliser le ClientRepository pour traiter les données des clients - final clientRepository = ClientRepository(_apiService); - await clientRepository.processClientsData(clientsList); - - debugPrint('Clients traités et stockés via ClientRepository'); - } else { - debugPrint('Aucun client à traiter (rôle utilisateur <= 2)'); + if (apiResult['amicale'] is Map) { + final amicale = AmicaleModel.fromJson(apiResult['amicale']); + await CurrentAmicaleService.instance.setAmicale(amicale); + debugPrint('✅ Amicale définie: ${amicale.name}'); } } - // Étape 6: Traitement des opérations (50%) - _updateLoadingState(_loadingState.copyWith( - progress: 0.50, - stepDescription: 'Chargement des opérations', - )); - - debugPrint('Traitement des données...'); - - // Traitement des données des opérations, secteurs, passages et membres - // Ces données devraient être présentes dans la réponse API - if (apiResult.containsKey('operations')) { - await _processOperations(apiResult['operations']); - debugPrint('Nombre d\'opérations chargées: ${_operationBox.length}'); - } - - // Étape 7: Traitement des secteurs (65%) - _updateLoadingState(_loadingState.copyWith( - progress: 0.65, - stepDescription: 'Chargement des secteurs', - )); - - if (apiResult.containsKey('sectors')) { - await _processSectors(apiResult['sectors']); - debugPrint('Nombre de secteurs chargés: ${_sectorBox.length}'); - } - - // Étape 8: Traitement des passages (75%) - _updateLoadingState(_loadingState.copyWith( - progress: 0.75, - stepDescription: 'Chargement des passages', - )); - - if (apiResult.containsKey('passages')) { - await _processPassages(apiResult['passages']); - debugPrint('Nombre de passages chargés: ${_passageBox.length}'); - } - - // Étape 9: Traitement des membres (85%) - _updateLoadingState(_loadingState.copyWith( - progress: 0.85, - stepDescription: 'Chargement des membres', - )); - - // Traitement des membres - if (apiResult.containsKey('membres') || apiResult.containsKey('members')) { - final membresData = apiResult['membres'] ?? apiResult['members']; - if (membresData != null) { - await _processMembres(membresData); - debugPrint('Nombre de membres chargés: ${_membreBox.length}'); - } - } - - // Étape 10: Traitement des associations utilisateurs-secteurs (95%) - _updateLoadingState(_loadingState.copyWith( - progress: 0.95, - stepDescription: 'Finalisation du chargement', - )); - - // Traitement des associations utilisateurs-secteurs - if (apiResult.containsKey('users_sectors')) { - await _processUserSectors(apiResult['users_sectors']); - debugPrint('Nombre d\'associations utilisateurs-secteurs chargées: ${_userSectorBox.length}'); - } - - // Vérification finale du remplissage des boîtes - debugPrint('=== VÉRIFICATION DU REMPLISSAGE DES BOÎTES ==='); - debugPrint('Utilisateurs: ${_userBox.length}'); - debugPrint('Opérations: ${_operationBox.length}'); - debugPrint('Secteurs: ${_sectorBox.length}'); - debugPrint('Passages: ${_passageBox.length}'); - debugPrint('Membres: ${_membreBox.length}'); - - // Afficher le nombre d'associations utilisateurs-secteurs avec plus de détails - final userSectorCount = _userSectorBox.length; - debugPrint('Associations utilisateurs-secteurs: $userSectorCount'); - - // Si des associations existent, afficher quelques détails - if (userSectorCount > 0) { - debugPrint('--- Détails des associations utilisateurs-secteurs ---'); - int displayCount = 0; - for (final userSector in _userSectorBox.values) { - if (displayCount < 5) { - // Limiter à 5 pour éviter de surcharger la console - debugPrint(' User ${userSector.id} (${userSector.firstName}) -> Secteur ${userSector.fkSector} (${userSector.name})'); - displayCount++; - } else { - debugPrint(' ... et ${userSectorCount - 5} autres associations'); - break; - } - } - } - - // Étape 11: Vérification finale des données (95%) - _updateLoadingState(_loadingState.copyWith( - progress: 0.95, - stepDescription: 'Vérification finale des données', - )); - - // Vérifier que le nombre de passages dans la réponse API correspond au nombre dans la Hive Box - int passagesCountInResponse = 0; - if (apiResult.containsKey('passages')) { - if (apiResult['passages'] is List) { - passagesCountInResponse = (apiResult['passages'] as List).length; - } else if (apiResult['passages'] is Map && apiResult['passages'].containsKey('data')) { - passagesCountInResponse = (apiResult['passages']['data'] as List).length; - } - } - - int passagesCountInBox = _passageBox.length; - debugPrint('Nombre de passages dans la réponse API: $passagesCountInResponse'); - debugPrint('Nombre de passages dans la Hive Box: $passagesCountInBox'); - - // Si les nombres ne correspondent pas, attendre un peu et revérifier - if (passagesCountInResponse > 0 && passagesCountInBox < passagesCountInResponse) { - debugPrint('Attente supplémentaire pour finaliser le chargement des passages...'); - await Future.delayed(const Duration(seconds: 1)); - passagesCountInBox = _passageBox.length; - debugPrint('Après attente: Nombre de passages dans la Hive Box: $passagesCountInBox'); - } - - // Étape 12: Chargement terminé (100%) - _updateLoadingState(LoadingState.completed.copyWith( - progress: 1.0, - message: 'Chargement terminé avec succès', - stepDescription: 'Connexion réussie', - )); - - debugPrint('=========================================='); - debugPrint('VÉRIFICATION FINALE DES DONNÉES:'); - debugPrint('Utilisateurs: ${_userBox.length}'); - debugPrint('Opérations: ${_operationBox.length}'); - debugPrint('Secteurs: ${_sectorBox.length}'); - debugPrint('Passages: ${_passageBox.length}'); - debugPrint('Membres: ${_membreBox.length}'); - debugPrint('=========================================='); + // Étape 5: Traitement de toutes les autres données via DataLoadingService + await DataLoadingService.instance.processLoginData(apiResult); + debugPrint('✅ Connexion réussie'); return true; } catch (e) { - debugPrint('Erreur de connexion: $e'); - _updateLoadingState(LoadingState.error(e.toString())); + debugPrint('❌ Erreur de connexion: $e'); return false; } finally { _isLoading = false; @@ -605,360 +277,54 @@ class UserRepository extends ChangeNotifier { } } - // Méthode pour vérifier si une boîte est ouverte et l'ouvrir si nécessaire - Future _ensureBoxIsOpen(String boxName) async { + /// Déconnexion simplifiée avec DataLoadingService + Future logout(BuildContext context) async { + _isLoading = true; + notifyListeners(); + try { - if (!Hive.isBoxOpen(boxName)) { - debugPrint('Ouverture de la boîte $boxName...'); - if (boxName == AppKeys.passagesBoxName) { - await Hive.openBox(boxName); - } else if (boxName == AppKeys.operationsBoxName) { - await Hive.openBox(boxName); - } else if (boxName == AppKeys.sectorsBoxName) { - await Hive.openBox(boxName); - } else if (boxName == AppKeys.userBoxName) { - await Hive.openBox(boxName); - } else if (boxName == AppKeys.membresBoxName) { - await Hive.openBox(boxName); - } else if (boxName == AppKeys.settingsBoxName) { - await Hive.openBox(boxName); - } else if (boxName == AppKeys.chatConversationsBoxName) { - await Hive.openBox(boxName); - } else if (boxName == AppKeys.chatMessagesBoxName) { - await Hive.openBox(boxName); - } else if (boxName == AppKeys.userSectorBoxName) { - await Hive.openBox(boxName); - } else { - await Hive.openBox(boxName); - } - // Boîte ouverte avec succès - } else { - // La boîte est déjà ouverte + debugPrint('🚪 Déconnexion en cours...'); + + try { + await logoutAPI(); + } catch (e) { + debugPrint('⚠️ Erreur API logout, mais on continue: $e'); } + + // Supprimer la session API + setSessionId(null); + + // Effacer les données via les services singleton + await CurrentUserService.instance.clearUser(); + await CurrentAmicaleService.instance.clearAmicale(); + + // Nettoyage complet via DataLoadingService + await DataLoadingService.instance.cleanDataAfterLogout(); + + // Réinitialiser l'état de HiveResetStateService + hiveResetStateService.reset(); + + if (context.mounted) { + context.go('/'); + } + + debugPrint('✅ Déconnexion réussie'); + return true; } catch (e) { - debugPrint('Erreur lors de l\'ouverture de la boîte $boxName: $e'); - throw Exception('Impossible d\'ouvrir la boîte $boxName: $e'); + debugPrint('❌ Erreur déconnexion: $e'); + if (context.mounted) { + context.go('/'); // Forcer la redirection même en cas d'erreur + } + return false; + } finally { + _isLoading = false; + notifyListeners(); } } - // Méthode pour vider et recréer toutes les boîtes Hive sauf la boîte des utilisateurs - Future _clearAndRecreateBoxes() async { - try { - debugPrint('Début de la suppression complète des données Hive...'); - - // Supprimer les références aux boîtes non définies dans AppKeys - // pour éviter les erreurs de suppression de boîtes non référencées - final nonDefinedBoxes = ['auth', 'locations', 'messages']; - for (final boxName in nonDefinedBoxes) { - try { - if (Hive.isBoxOpen(boxName)) { - debugPrint('Fermeture de la boîte non référencée: $boxName'); - await Hive.box(boxName).close(); - } - - // Supprimer la boîte du disque - await Hive.deleteBoxFromDisk(boxName); - debugPrint('Nettoyage: Box $boxName supprimée'); - } catch (e) { - debugPrint('Erreur lors de la suppression de la boîte non référencée $boxName: $e'); - } - } - - // Sur le web, utiliser notre méthode sécurisée pour nettoyer les boîtes Hive - if (kIsWeb) { - await HiveWebFix.safeCleanHiveBoxes(excludeBoxes: [AppKeys.userBoxName]); - } - // Sur iOS, nettoyer les fichiers Hive directement - else if (Platform.isIOS) { - await _cleanHiveFilesOnIOS(); - } - // Sur Android, nettoyer les fichiers Hive directement - else if (Platform.isAndroid) { - await _cleanHiveFilesOnAndroid(); - } - - // Liste des noms de boîtes à supprimer - final boxesToDelete = [ - AppKeys.passagesBoxName, - AppKeys.operationsBoxName, - AppKeys.sectorsBoxName, - AppKeys.userSectorBoxName, - AppKeys.chatConversationsBoxName, - AppKeys.chatMessagesBoxName, - ]; - - // Vider chaque boîte sans la fermer - for (final boxName in boxesToDelete) { - try { - debugPrint('Nettoyage de la boîte: $boxName'); - - // Vérifier si la boîte est déjà ouverte - if (Hive.isBoxOpen(boxName)) { - // Vider la boîte sans la fermer - debugPrint('Boîte $boxName déjà ouverte, vidage sans fermeture'); - if (boxName == AppKeys.passagesBoxName) { - await _passageBox.clear(); - } else if (boxName == AppKeys.operationsBoxName) { - await _operationBox.clear(); - } else if (boxName == AppKeys.sectorsBoxName) { - await _sectorBox.clear(); - } else if (boxName == AppKeys.userSectorBoxName) { - await _userSectorBox.clear(); - } else if (boxName == AppKeys.chatConversationsBoxName) { - await _chatConversationBox.clear(); - } else if (boxName == AppKeys.chatMessagesBoxName) { - await _chatMessageBox.clear(); - } - } else { - // Supprimer la boîte du disque si elle n'est pas ouverte - debugPrint('Boîte $boxName non ouverte, suppression du disque'); - await Hive.deleteBoxFromDisk(boxName); - } - } catch (e) { - debugPrint('Erreur lors du nettoyage de la boîte $boxName: $e'); - // Tenter de supprimer la boîte du disque en cas d'erreur - try { - await Hive.deleteBoxFromDisk(boxName); - } catch (deleteError) { - debugPrint('Impossible de supprimer la boîte $boxName: $deleteError'); - } - } - } - - // Attendre un court instant pour s'assurer que les opérations de suppression sont terminées - await Future.delayed(const Duration(milliseconds: 500)); - - // Recréer les boîtes avec la méthode sécurisée - debugPrint('Recréation des boîtes Hive...'); - - // Utiliser notre méthode pour s'assurer que les boîtes sont ouvertes - try { - // Passages - await _ensureBoxIsOpen(AppKeys.passagesBoxName); - - // Opérations - await _ensureBoxIsOpen(AppKeys.operationsBoxName); - - // Secteurs - await _ensureBoxIsOpen(AppKeys.sectorsBoxName); - - // Chat - await _ensureBoxIsOpen(AppKeys.chatConversationsBoxName); - await _ensureBoxIsOpen(AppKeys.chatMessagesBoxName); - - // Vérifier l'intégrité des boîtes après recréation - await _verifyHiveBoxesIntegrity(); - } catch (e) { - debugPrint('Erreur lors de la recréation des boîtes Hive: $e'); - // Tentative de récupération sur erreur - if (kIsWeb) { - debugPrint('Tentative de récupération sur le web...'); - await HiveWebFix.resetHiveCompletely(); - - // Réessayer d'ouvrir les boîtes - await _ensureBoxIsOpen(AppKeys.passagesBoxName); - await _ensureBoxIsOpen(AppKeys.operationsBoxName); - await _ensureBoxIsOpen(AppKeys.sectorsBoxName); - await _ensureBoxIsOpen(AppKeys.chatConversationsBoxName); - await _ensureBoxIsOpen(AppKeys.chatMessagesBoxName); - } - } - } catch (e) { - debugPrint('Erreur lors de la réinitialisation des boîtes Hive: $e'); - } - } - - // Méthode pour vérifier l'intégrité des boîtes Hive après recréation - Future _verifyHiveBoxesIntegrity() async { - try { - debugPrint('Vérification de l\'intégrité des boîtes Hive...'); - - // Liste des boîtes à vérifier avec leur type - final boxesToCheck = [ - {'name': AppKeys.passagesBoxName, 'type': 'passage'}, - {'name': AppKeys.operationsBoxName, 'type': 'operation'}, - {'name': AppKeys.sectorsBoxName, 'type': 'sector'}, - {'name': AppKeys.userSectorBoxName, 'type': 'user_sector'}, - {'name': AppKeys.amicaleBoxName, 'type': 'amicale'}, - {'name': AppKeys.chatConversationsBoxName, 'type': 'conversation'}, - {'name': AppKeys.chatMessagesBoxName, 'type': 'message'}, - ]; - - // Vérifier chaque boîte - for (final boxInfo in boxesToCheck) { - final boxName = boxInfo['name'] as String; - final boxType = boxInfo['type'] as String; - - try { - if (Hive.isBoxOpen(boxName)) { - // Utiliser une approche spécifique au type pour éviter les erreurs de typage - Box box; - try { - if (boxType == 'passage') { - box = _passageBox; - } else if (boxType == 'operation') { - box = _operationBox; - } else if (boxType == 'sector') { - box = _sectorBox; - } else if (boxType == 'user_sector') { - box = _userSectorBox; - } else if (boxType == 'conversation') { - box = _chatConversationBox; - } else if (boxType == 'message') { - box = _chatMessageBox; - } else { - box = Hive.box(boxName); - } - - final count = box.length; - debugPrint('Boîte $boxName: $count éléments'); - - // Si la boîte contient des éléments, c'est anormal après recréation - if (count > 0) { - debugPrint('ATTENTION: La boîte $boxName contient encore des données après recréation'); - // Essayer de vider la boîte une dernière fois - await box.clear(); - debugPrint('Vidage forcé de la boîte $boxName effectué'); - } - } catch (typeError) { - debugPrint('Erreur de typage lors de la vérification de $boxName: $typeError'); - - // Tentative alternative sans typage spécifique - try { - box = Hive.box(boxName); - final count = box.length; - debugPrint('Boîte $boxName (sans typage): $count éléments'); - - if (count > 0) { - await box.clear(); - debugPrint('Vidage forcé de la boîte $boxName (sans typage) effectué'); - } - } catch (e2) { - debugPrint('Impossible de vérifier la boîte $boxName même sans typage: $e2'); - } - } - } else { - debugPrint('Boîte $boxName non ouverte, impossible de vérifier l\'intégrité'); - } - } catch (e) { - debugPrint('Erreur lors de la vérification de la boîte $boxName: $e'); - } - } - - debugPrint('Vérification d\'intégrité terminée'); - } catch (e) { - debugPrint('Erreur lors de la vérification d\'intégrité des boîtes Hive: $e'); - } - } - - // Méthode spéciale pour nettoyer IndexedDB sur le web - Future _clearIndexedDB() async { - if (kIsWeb) { - try { - debugPrint('Nettoyage complet d\'IndexedDB sur le web...'); - // Utiliser JavaScript pour nettoyer IndexedDB - js.context.callMethod('eval', [ - ''' - var request = indexedDB.deleteDatabase("geosector_app"); - request.onsuccess = function() { console.log("IndexedDB nettoyé avec succès"); }; - request.onerror = function() { console.log("Erreur lors du nettoyage d'IndexedDB"); }; - ''' - ]); - await Future.delayed(const Duration(milliseconds: 500)); - debugPrint('Nettoyage d\'IndexedDB terminé'); - } catch (e) { - debugPrint('Erreur lors du nettoyage d\'IndexedDB: $e'); - } - } - } - - // Méthode spéciale pour nettoyer les fichiers Hive sur iOS - Future _cleanHiveFilesOnIOS() async { - if (!kIsWeb && Platform.isIOS) { - try { - debugPrint('Nettoyage des fichiers Hive sur iOS...'); - final appDir = await getApplicationDocumentsDirectory(); - final hiveDir = Directory('${appDir.path}/hive'); - - if (await hiveDir.exists()) { - debugPrint('Suppression du répertoire Hive: ${hiveDir.path}'); - // Exclure le dossier des utilisateurs pour conserver les informations de session - final entries = await hiveDir.list().toList(); - for (var entry in entries) { - final name = entry.path.split('/').last; - // Ne pas supprimer la boîte des utilisateurs - if (!name.contains(AppKeys.userBoxName)) { - debugPrint('Suppression de: ${entry.path}'); - if (entry is Directory) { - await entry.delete(recursive: true); - } else if (entry is File) { - await entry.delete(); - } - } - } - debugPrint('Nettoyage des fichiers Hive sur iOS terminé'); - } else { - debugPrint('Répertoire Hive non trouvé'); - } - } catch (e) { - debugPrint('Erreur lors du nettoyage des fichiers Hive sur iOS: $e'); - } - } - } - - // Méthode spéciale pour nettoyer les fichiers Hive sur Android - Future _cleanHiveFilesOnAndroid() async { - if (!kIsWeb && Platform.isAndroid) { - try { - debugPrint('Nettoyage des fichiers Hive sur Android...'); - final appDir = await getApplicationDocumentsDirectory(); - final hiveDir = Directory(appDir.path); - - if (await hiveDir.exists()) { - debugPrint('Recherche des fichiers Hive dans: ${hiveDir.path}'); - // Sur Android, les fichiers Hive sont directement dans le répertoire de l'application - final entries = await hiveDir.list().toList(); - int filesDeleted = 0; - - for (var entry in entries) { - final name = entry.path.split('/').last; - // Ne supprimer que les fichiers Hive, mais pas la boîte des utilisateurs - if (name.endsWith('.hive') && !name.contains(AppKeys.userBoxName)) { - debugPrint('Suppression du fichier Hive: ${entry.path}'); - if (entry is File) { - await entry.delete(); - filesDeleted++; - - // Supprimer également les fichiers lock associés - final lockFile = File('${entry.path}.lock'); - if (await lockFile.exists()) { - await lockFile.delete(); - debugPrint('Suppression du fichier lock: ${lockFile.path}'); - } - } - } - } - - debugPrint('Nettoyage des fichiers Hive sur Android terminé. $filesDeleted fichiers supprimés.'); - } else { - debugPrint('Répertoire d\'application non trouvé'); - } - } catch (e) { - debugPrint('Erreur lors du nettoyage des fichiers Hive sur Android: $e'); - } - } - } - - /// Méthode de connexion avec affichage d'un overlay de chargement avec progression - /// Cette méthode remplace AuthService.login et utilise le nouvel overlay avec barre de progression + /// Connexion avec interface utilisateur et progression Future loginWithUI(BuildContext context, String username, String password, {required String type}) async { try { - // Réinitialiser l'état de chargement - _updateLoadingState(LoadingState.initial.copyWith( - message: 'Connexion en cours...', - stepDescription: 'Préparation', - )); - // Créer et afficher l'overlay de progression _progressOverlay = LoadingProgressOverlayUtils.show( context: context, @@ -968,26 +334,26 @@ class UserRepository extends ChangeNotifier { blurAmount: 5.0, ); - // Écouter les changements d'état pour mettre à jour l'overlay - listener() { + // Écouter les changements d'état du DataLoadingService + void listener() { if (_progressOverlay != null) { - // Mettre à jour l'overlay avec les nouvelles valeurs + final loadingState = DataLoadingService.instance.loadingState; LoadingProgressOverlayUtils.update( overlayEntry: _progressOverlay!, - message: _loadingState.message, - progress: _loadingState.progress, - stepDescription: _loadingState.stepDescription, + message: loadingState.message, + progress: loadingState.progress, + stepDescription: loadingState.stepDescription, ); } } - // Ajouter l'écouteur - addListener(listener); + // Configurer le callback de progression + DataLoadingService.instance.setProgressCallback((_) => listener()); // Exécuter la connexion final result = await login(username, password, type: type); - // Attendre un court instant pour que l'utilisateur voie que le chargement est terminé + // Attendre un court instant pour que l'utilisateur voie le résultat if (result) { await Future.delayed(const Duration(milliseconds: 500)); } else { @@ -1000,394 +366,148 @@ class UserRepository extends ChangeNotifier { _progressOverlay = null; } - // Supprimer l'écouteur - removeListener(listener); + // Nettoyer le callback + DataLoadingService.instance.setProgressCallback(null); return result; } catch (e) { - // En cas d'erreur, supprimer l'overlay et relancer l'erreur + // En cas d'erreur, supprimer l'overlay if (_progressOverlay != null) { _progressOverlay!.remove(); _progressOverlay = null; } - _updateLoadingState(LoadingState.error(e.toString())); + DataLoadingService.instance.setProgressCallback(null); return false; } } - // Méthode de déconnexion unique avec navigation vers / splash_page - Future logout(BuildContext context) async { - _isLoading = true; - notifyListeners(); + // === ACCESSEURS DÉLÉGUÉS AUX SERVICES === - try { - debugPrint('Début du processus de déconnexion...'); - debugPrint('État isLoggedIn avant déconnexion: $isLoggedIn'); + /// Simplifier les getters d'amicale + AmicaleModel? getCurrentUserAmicale() => CurrentAmicaleService.instance.currentAmicale; - // Récupérer l'utilisateur actuel avant de nettoyer les données - final currentUser = getCurrentUser(); - if (currentUser == null) { - debugPrint('Aucun utilisateur connecté, déconnexion terminée'); - await _deepCleanHiveBoxes(); - debugPrint('État isLoggedIn après nettoyage: $isLoggedIn'); - - // Toujours rediriger avec pushAndRemoveUntil pour forcer la navigation - if (context.mounted) { - debugPrint('Redirection forcée vers / après nettoyage'); - context.go('/'); - } - - return true; - } - - debugPrint('Déconnexion de l\'utilisateur: ${currentUser.email}'); - - // Appeler l'API pour déconnecter la session - if (currentUser.sessionId != null) { - debugPrint('Déconnexion de la session API...'); - try { - await logoutAPI(); - } catch (e) { - debugPrint('Erreur lors de la déconnexion API, mais on continue: $e'); - } - } - - // Supprimer la session API - setSessionId(null); - - // Réinitialiser le cache de l'utilisateur actuel - _cachedCurrentUser = null; - debugPrint('Cache utilisateur réinitialisé (_cachedCurrentUser = null)'); - - // Nettoyage complet de toutes les boîtes Hive - debugPrint('Nettoyage profond des données Hive après déconnexion...'); - await _deepCleanHiveBoxes(); - - // Réinitialiser l'état de HiveResetStateService - hiveResetStateService.reset(); - debugPrint('État de HiveResetStateService réinitialisé'); - - debugPrint('Déconnexion terminée avec succès'); - - // Forcer la navigation avec pushAndRemoveUntil et attendre - if (context.mounted) { - debugPrint('Navigation forcée vers / après déconnexion'); - - // Attendre que toutes les opérations asynchrones soient terminées - await Future.delayed(const Duration(milliseconds: 200)); - - // Navigation forcée qui supprime toute la pile de navigation - context.go('/'); - - // Alternative si pushAndRemoveUntil ne fonctionne pas - // context.pushReplacementNamed('/'); - } - - notifyListeners(); - return true; - } catch (e) { - debugPrint('Erreur de déconnexion: $e'); - - // Même en cas d'erreur, essayer de naviguer vers la page d'accueil - if (context.mounted) { - debugPrint('Navigation d\'urgence vers / après erreur'); - context.go( - '/', - ); - } - - return false; - } finally { - _isLoading = false; - notifyListeners(); - debugPrint('État final isLoggedIn: $isLoggedIn'); - } - } - - // Méthode pour nettoyer en profondeur toutes les boîtes Hive et réinitialiser l'application - Future _deepCleanHiveBoxes() async { - try { - debugPrint('Début du nettoyage profond des boîtes Hive...'); - - // Sauvegarder le username et le rôle du dernier utilisateur connecté pour le pré-remplissage - String? lastUsername; - int? lastRole; - UserModel? lastUser; - if (Hive.isBoxOpen(AppKeys.userBoxName) && _userBox.isNotEmpty) { - try { - // Récupérer l'utilisateur actuel ou le dernier utilisateur connecté - lastUser = getCurrentUser() ?? _userBox.values.first; - lastUsername = lastUser.username; - - // Convertir le rôle en int si nécessaire - if (lastUser.role is String) { - lastRole = int.tryParse(lastUser.role as String) ?? 0; - } else { - lastRole = lastUser.role; - } - - debugPrint('Username sauvegardé pour pré-remplissage: $lastUsername'); - debugPrint('Rôle sauvegardé pour pré-remplissage: $lastRole'); - } catch (e) { - debugPrint('Erreur lors de la sauvegarde du username et du rôle: $e'); - } - } - - // 1. Vider toutes les boîtes sans les fermer - debugPrint('Vidage des boîtes Hive...'); - if (Hive.isBoxOpen(AppKeys.userBoxName)) { - try { - await _userBox.clear(); - debugPrint('Boîte users vidée'); - - // Si nous avons un username sauvegardé, créer un utilisateur minimal pour le pré-remplissage - if (lastUsername != null && lastUsername.isNotEmpty) { - final minimalUser = UserModel( - id: lastUser?.id ?? DateTime.now().millisecondsSinceEpoch, - email: lastUser?.email ?? '', - username: lastUsername, - role: lastRole ?? 0, // Conserver le rôle pour la vérification dans la page de login - createdAt: DateTime.now(), - lastSyncedAt: DateTime.now(), - isActive: false, - isSynced: false, - ); - await _userBox.put(minimalUser.id, minimalUser); - debugPrint('Utilisateur minimal créé pour pré-remplissage du username avec rôle: $lastRole'); - } - } catch (e) { - debugPrint('Erreur lors du vidage de la boîte users: $e'); - } - } - - if (Hive.isBoxOpen(AppKeys.operationsBoxName)) { - try { - await _operationBox.clear(); - debugPrint('Boîte operations vidée'); - } catch (e) { - debugPrint('Erreur lors du vidage de la boîte operations: $e'); - } - } - - if (Hive.isBoxOpen(AppKeys.sectorsBoxName)) { - try { - await _sectorBox.clear(); - debugPrint('Boîte sectors vidée'); - } catch (e) { - debugPrint('Erreur lors du vidage de la boîte sectors: $e'); - } - } - - if (Hive.isBoxOpen(AppKeys.passagesBoxName)) { - try { - await _passageBox.clear(); - debugPrint('Boîte passages vidée'); - } catch (e) { - debugPrint('Erreur lors du vidage de la boîte passages: $e'); - } - } - - if (Hive.isBoxOpen(AppKeys.settingsBoxName)) { - try { - await _settingsBox.clear(); - debugPrint('Boîte settings vidée'); - } catch (e) { - debugPrint('Erreur lors du vidage de la boîte settings: $e'); - } - } - - if (Hive.isBoxOpen(AppKeys.membresBoxName)) { - try { - await _membreBox.clear(); - debugPrint('Boîte membres vidée'); - } catch (e) { - debugPrint('Erreur lors du vidage de la boîte membres: $e'); - } - } - - if (Hive.isBoxOpen(AppKeys.chatConversationsBoxName)) { - try { - await _chatConversationBox.clear(); - debugPrint('Boîte chat_conversations vidée'); - } catch (e) { - debugPrint('Erreur lors du vidage de la boîte chat_conversations: $e'); - } - } - - if (Hive.isBoxOpen(AppKeys.chatMessagesBoxName)) { - try { - await _chatMessageBox.clear(); - debugPrint('Boîte chat_messages vidée'); - } catch (e) { - debugPrint('Erreur lors du vidage de la boîte chat_messages: $e'); - } - } - - if (Hive.isBoxOpen(AppKeys.userSectorBoxName)) { - try { - await _userSectorBox.clear(); - debugPrint('Boîte user_sector vidée'); - } catch (e) { - debugPrint('Erreur lors du vidage de la boîte user_sector: $e'); - } - } - - if (Hive.isBoxOpen(AppKeys.amicaleBoxName)) { - try { - await _amicaleBox.clear(); - debugPrint('Boîte amicale vidée'); - } catch (e) { - debugPrint('Erreur lors du vidage de la boîte amicales: $e'); - } - } - - // 2. Nettoyage spécifique à la plateforme - if (kIsWeb) { - await _clearIndexedDB(); - } else if (!kIsWeb && Platform.isIOS) { - await _cleanHiveFilesOnIOS(); - } else if (!kIsWeb && Platform.isAndroid) { - await _cleanHiveFilesOnAndroid(); - } - - // 3. Attendre que toutes les opérations de nettoyage soient terminées - debugPrint('Attente après nettoyage...'); - await Future.delayed(const Duration(milliseconds: 800)); - - // 4. Réinitialiser l'API Service - _apiService.setSessionId(null); - - // 5. Approche plus sûre : fermer, supprimer et rouvrir la boîte des utilisateurs - debugPrint('Approche sécurisée pour la boîte users...'); - - try { - // Vérifier si la boîte est ouverte avant de tenter de la fermer - if (Hive.isBoxOpen(AppKeys.userBoxName)) { - debugPrint('Fermeture de la boîte users...'); - try { - await Hive.box(AppKeys.userBoxName).close(); - debugPrint('Boîte users fermée avec succès'); - } catch (e) { - debugPrint('Erreur lors de la fermeture de la boîte users: $e'); - // Ne pas continuer avec la suppression si la fermeture a échoué - rethrow; - } - - // Attendre un peu pour s'assurer que la fermeture est terminée - await Future.delayed(const Duration(milliseconds: 300)); - - // Supprimer la boîte du disque seulement si la fermeture a réussi - debugPrint('Suppression de la boîte users du disque...'); - try { - await Hive.deleteBoxFromDisk(AppKeys.userBoxName); - debugPrint('Boîte users supprimée du disque avec succès'); - } catch (e) { - debugPrint('Erreur lors de la suppression de la boîte users: $e'); - // Ne pas continuer avec la réouverture si la suppression a échoué - rethrow; - } - } else { - debugPrint('La boîte users est déjà fermée, tentative de suppression directe...'); - try { - await Hive.deleteBoxFromDisk(AppKeys.userBoxName); - debugPrint('Boîte users supprimée du disque avec succès'); - } catch (e) { - debugPrint('Erreur lors de la suppression directe de la boîte users: $e'); - } - } - } catch (e) { - debugPrint('Erreur lors du processus de nettoyage de la boîte users: $e'); - // Continuer malgré l'erreur, mais ne pas tenter de réouvrir la boîte - return; - } - - // Attendre un peu pour s'assurer que la suppression est terminée - await Future.delayed(const Duration(milliseconds: 500)); - - // Rouvrir la boîte (elle sera vide) - debugPrint('Réouverture de la boîte users (vide)...'); - await Hive.openBox(AppKeys.userBoxName); - - // Vérifier que la boîte est bien vide - final checkUsers = _userBox.values.toList(); - debugPrint('Après approche radicale: ${checkUsers.length} utilisateurs restants'); - - // Forcer la réinitialisation du cache - _cachedCurrentUser = null; - debugPrint('Cache utilisateur forcé à null'); - - debugPrint('Nettoyage profond des boîtes Hive terminé'); - } catch (e) { - debugPrint('Erreur lors du nettoyage profond des boîtes Hive: $e'); - } - } - - // Obtenir tous les utilisateurs locaux + /// Obtenir tous les utilisateurs locaux List getAllUsers() { return _userBox.values.toList(); } - // Obtenir un utilisateur par son ID + /// Obtenir un utilisateur par son ID UserModel? getUserById(int id) { return _userBox.get(id); } - // Obtenir un utilisateur par son email + /// Obtenir un utilisateur par son email UserModel? getUserByEmail(String email) { try { return _userBox.values.firstWhere( (user) => user.email == email, ); } catch (e) { - return null; // Utilisateur non trouvé + return null; } } - // Créer ou mettre à jour un utilisateur localement + /// Créer ou mettre à jour un utilisateur localement Future saveUser(UserModel user) async { await _userBox.put(user.id, user); - notifyListeners(); // Notifier les changements pour mettre à jour l'UI + notifyListeners(); return user; } - // Supprimer un utilisateur localement + /// Supprimer un utilisateur localement Future deleteUser(String id) async { await _userBox.delete(id); } - // Créer un nouvel utilisateur localement et tenter de le synchroniser - Future createUser({ - required String email, - required String name, - required int role, - }) async { - // Générer un ID numérique temporaire (timestamp) - final int tempId = DateTime.now().millisecondsSinceEpoch; - final now = DateTime.now(); + // === MÉTHODES UTILITAIRES POUR LES DONNÉES === - final user = UserModel( - id: tempId, - email: email, - name: name, - role: role, - createdAt: now, - lastSyncedAt: now, - isSynced: false, - ); + /// Récupérer la dernière opération active + OperationModel? getCurrentOperation() { + try { + final ops = operations; + final activeOps = ops.where((op) => op.isActive).toList(); - await _userBox.put(user.id, user); + if (activeOps.isEmpty) { + return ops.isNotEmpty ? ops.last : null; + } - // Tenter de synchroniser si possible - await syncUser(user); - - return user; + return activeOps.last; + } catch (e) { + debugPrint('⚠️ Erreur récupération opération courante: $e'); + return null; + } } - // Synchroniser un utilisateur spécifique avec le serveur + /// Récupérer tous les secteurs de l'utilisateur + List getUserSectors() { + return sectors; + } + + /// Récupérer un secteur par son ID + SectorModel? getSectorById(int id) { + try { + if (Hive.isBoxOpen(AppKeys.sectorsBoxName)) { + return Hive.box(AppKeys.sectorsBoxName).get(id); + } + return null; + } catch (e) { + debugPrint('⚠️ Erreur récupération secteur: $e'); + return null; + } + } + + /// Récupérer toutes les amicales + List getAllAmicales() { + try { + if (Hive.isBoxOpen(AppKeys.amicaleBoxName)) { + return Hive.box(AppKeys.amicaleBoxName).values.toList(); + } + return []; + } catch (e) { + debugPrint('⚠️ Erreur récupération amicales: $e'); + return []; + } + } + + /// Récupérer tous les clients (entités de type 1) + List getAllClients() { + try { + return getAllAmicales().where((amicale) => amicale.fkType == 1).toList(); + } catch (e) { + debugPrint('⚠️ Erreur récupération clients: $e'); + return []; + } + } + + /// Récupérer une amicale par son ID + AmicaleModel? getAmicaleById(int id) { + try { + if (Hive.isBoxOpen(AppKeys.amicaleBoxName)) { + return Hive.box(AppKeys.amicaleBoxName).get(id); + } + return null; + } catch (e) { + debugPrint('⚠️ Erreur récupération amicale: $e'); + return null; + } + } + + /// Créer ou mettre à jour une amicale localement + Future saveAmicale(AmicaleModel amicale) async { + if (Hive.isBoxOpen(AppKeys.amicaleBoxName)) { + await Hive.box(AppKeys.amicaleBoxName).put(amicale.id, amicale); + notifyListeners(); + } + return amicale; + } + + // === SYNCHRONISATION === + + /// Synchroniser un utilisateur spécifique avec le serveur Future syncUser(UserModel user) async { try { - final hasConnection = await _apiService.hasInternetConnection(); + final hasConnection = await ApiService.instance.hasInternetConnection(); if (!hasConnection) { return user; @@ -1396,14 +516,11 @@ class UserRepository extends ChangeNotifier { UserModel syncedUser; if (!user.isSynced) { - // Si l'utilisateur n'est pas encore synchronisé, le créer sur le serveur - syncedUser = await _apiService.createUser(user); + syncedUser = await ApiService.instance.createUser(user); } else { - // Sinon, mettre à jour les informations - syncedUser = await _apiService.updateUser(user); + syncedUser = await ApiService.instance.updateUser(user); } - // Mettre à jour l'utilisateur local avec les informations du serveur final updatedUser = syncedUser.copyWith( isSynced: true, lastSyncedAt: DateTime.now(), @@ -1412,15 +529,14 @@ class UserRepository extends ChangeNotifier { await _userBox.put(updatedUser.id, updatedUser); return updatedUser; } catch (e) { - // En cas d'erreur, garder l'utilisateur local tel quel return user; } } - // Synchroniser tous les utilisateurs non synchronisés + /// Synchroniser tous les utilisateurs non synchronisés Future syncAllUsers() async { try { - final hasConnection = await _apiService.hasInternetConnection(); + final hasConnection = await ApiService.instance.hasInternetConnection(); if (!hasConnection) { return; @@ -1432,10 +548,8 @@ class UserRepository extends ChangeNotifier { return; } - // Synchroniser en batch - final result = await _apiService.syncData(users: unsyncedUsers); + final result = await ApiService.instance.syncData(users: unsyncedUsers); - // Mettre à jour les utilisateurs locaux if (result['users'] != null) { for (final userData in result['users']) { final syncedUser = UserModel.fromJson(userData); @@ -1449,24 +563,21 @@ class UserRepository extends ChangeNotifier { } } } catch (e) { - // Gérer les erreurs de synchronisation - print('Erreur de synchronisation des utilisateurs: $e'); + debugPrint('❌ Erreur de synchronisation des utilisateurs: $e'); } } - // Rafraîchir les données depuis le serveur + /// Rafraîchir les données depuis le serveur Future refreshFromServer() async { try { - final hasConnection = await _apiService.hasInternetConnection(); + final hasConnection = await ApiService.instance.hasInternetConnection(); if (!hasConnection) { return; } - // Récupérer tous les utilisateurs du serveur - final serverUsers = await _apiService.getUsers(); + final serverUsers = await ApiService.instance.getUsers(); - // Mettre à jour la base locale for (final serverUser in serverUsers) { final updatedUser = serverUser.copyWith( isSynced: true, @@ -1475,330 +586,21 @@ class UserRepository extends ChangeNotifier { await _userBox.put(updatedUser.id, updatedUser); } } catch (e) { - // Gérer les erreurs - print('Erreur lors du rafraîchissement des données: $e'); + debugPrint('❌ Erreur lors du rafraîchissement: $e'); } } - // Synchroniser les données utilisateur - Future syncUserData() async { - if (_syncService != null && currentUser != null) { - await _syncService!.syncUserData(currentUser!.id); - } - } + // === TRAITEMENT DES DONNÉES UTILISATEUR === - // Récupérer la dernière opération active (avec isActive == true) - OperationModel? getCurrentOperation() { - try { - // Récupérer toutes les opérations - final operations = _operationBox.values.toList(); - - // Filtrer pour ne garder que les opérations actives - final activeOperations = operations.where((op) => op.isActive).toList(); - - // Si aucune opération active n'est trouvée, retourner null - if (activeOperations.isEmpty) { - return operations.isNotEmpty ? operations.last : null; - } - - // Retourner la dernière opération active - return activeOperations.last; - } catch (e) { - debugPrint('Erreur lors de la récupération de l\'opération actuelle: $e'); - return null; - } - } - - // Récupérer tous les secteurs de l'utilisateur - List getUserSectors() { - try { - return _sectorBox.values.toList(); - } catch (e) { - debugPrint('Erreur lors de la récupération des secteurs: $e'); - return []; - } - } - - // Récupérer un secteur par son ID - SectorModel? getSectorById(int id) { - try { - return _sectorBox.get(id); - } catch (e) { - debugPrint('Erreur lors de la récupération du secteur: $e'); - return null; - } - } - - // Récupérer toutes les amicales - List getAllAmicales() { - try { - _ensureBoxIsOpen(AppKeys.amicaleBoxName); - return _amicaleBox.values.toList(); - } catch (e) { - debugPrint('Erreur lors de la récupération des amicales: $e'); - return []; - } - } - - // Récupérer tous les clients (entités de type 1) - List getAllClients() { - try { - _ensureBoxIsOpen(AppKeys.amicaleBoxName); - return _amicaleBox.values.where((amicale) => amicale.fkType == 1).toList(); - } catch (e) { - debugPrint('Erreur lors de la récupération des clients: $e'); - return []; - } - } - - // Récupérer une amicale par son ID - AmicaleModel? getAmicaleById(int id) { - try { - _ensureBoxIsOpen(AppKeys.amicaleBoxName); - return _amicaleBox.get(id); - } catch (e) { - debugPrint('Erreur lors de la récupération de l\'amicale: $e'); - return null; - } - } - - // Récupérer l'amicale de l'utilisateur connecté - AmicaleModel? getCurrentUserAmicale() { - final user = getCurrentUser(); - if (user == null || user.fkEntite == null) { - return null; - } - - return getAmicaleById(user.fkEntite!); - } - - // Créer ou mettre à jour une amicale localement - Future saveAmicale(AmicaleModel amicale) async { - _ensureBoxIsOpen(AppKeys.amicaleBoxName); - await _amicaleBox.put(amicale.id, amicale); - notifyListeners(); // Notifier les changements pour mettre à jour l'UI - return amicale; - } - - // Méthode pour traiter les données des opérations reçues de l'API - Future _processOperations(dynamic operationsData) async { - try { - debugPrint('Traitement des données des opérations...'); - - // Vérifier que les données sont au bon format - if (operationsData == null) { - debugPrint('Aucune donnée d\'opération à traiter'); - return; - } - - List operationsList; - if (operationsData is List) { - operationsList = operationsData; - } else if (operationsData is Map && operationsData.containsKey('data')) { - operationsList = operationsData['data'] as List; - } else { - debugPrint('Format de données d\'opérations non reconnu'); - return; - } - - // Vider la boîte avant d'ajouter les nouvelles données - await _operationBox.clear(); - - // Traiter chaque opération - int count = 0; - for (final operationData in operationsList) { - try { - final operation = OperationModel.fromJson(operationData); - await _operationBox.put(operation.id, operation); - count++; - } catch (e) { - debugPrint('Erreur lors du traitement d\'une opération: $e'); - } - } - - debugPrint('$count opérations traitées et stockées'); - } catch (e) { - debugPrint('Erreur lors du traitement des opérations: $e'); - } - } - - // Méthode pour traiter les données des secteurs reçues de l'API - Future _processSectors(dynamic sectorsData) async { - try { - debugPrint('Traitement des données des secteurs...'); - - // Vérifier que les données sont au bon format - if (sectorsData == null) { - debugPrint('Aucune donnée de secteur à traiter'); - return; - } - - List sectorsList; - if (sectorsData is List) { - sectorsList = sectorsData; - } else if (sectorsData is Map && sectorsData.containsKey('data')) { - sectorsList = sectorsData['data'] as List; - } else { - debugPrint('Format de données de secteurs non reconnu'); - return; - } - - // Vider la boîte avant d'ajouter les nouvelles données - await _sectorBox.clear(); - - // Traiter chaque secteur - int count = 0; - for (final sectorData in sectorsList) { - try { - final sector = SectorModel.fromJson(sectorData); - await _sectorBox.put(sector.id, sector); - count++; - } catch (e) { - debugPrint('Erreur lors du traitement d\'un secteur: $e'); - } - } - - debugPrint('$count secteurs traités et stockés'); - } catch (e) { - debugPrint('Erreur lors du traitement des secteurs: $e'); - } - } - - // Méthode pour traiter les données des passages reçues de l'API - Future _processPassages(dynamic passagesData) async { - try { - debugPrint('Traitement des données des passages...'); - - // Vérifier que les données sont au bon format - if (passagesData == null) { - debugPrint('Aucune donnée de passage à traiter'); - return; - } - - List passagesList; - if (passagesData is List) { - passagesList = passagesData; - } else if (passagesData is Map && passagesData.containsKey('data')) { - passagesList = passagesData['data'] as List; - } else { - debugPrint('Format de données de passages non reconnu'); - return; - } - - // Vider la boîte avant d'ajouter les nouvelles données - await _passageBox.clear(); - - // Traiter chaque passage - int count = 0; - for (final passageData in passagesList) { - try { - final passage = PassageModel.fromJson(passageData); - await _passageBox.put(passage.id, passage); - count++; - } catch (e) { - debugPrint('Erreur lors du traitement d\'un passage: $e'); - } - } - - debugPrint('$count passages traités et stockés'); - } catch (e) { - debugPrint('Erreur lors du traitement des passages: $e'); - } - } - - // Méthode pour traiter les données des membres reçues de l'API - Future _processMembres(dynamic membresData) async { - try { - debugPrint('Traitement des données des membres...'); - - // Vérifier que les données sont au bon format - if (membresData == null) { - debugPrint('Aucune donnée de membre à traiter'); - return; - } - - List membresList; - if (membresData is List) { - membresList = membresData; - } else if (membresData is Map && membresData.containsKey('data')) { - membresList = membresData['data'] as List; - } else { - debugPrint('Format de données de membres non reconnu'); - return; - } - - // Vider la boîte avant d'ajouter les nouvelles données - await _membreBox.clear(); - - // Traiter chaque membre - int count = 0; - for (final membreData in membresList) { - try { - final membre = MembreModel.fromJson(membreData); - await _membreBox.put(membre.id, membre); - count++; - } catch (e) { - debugPrint('Erreur lors du traitement d\'un membre: $e'); - } - } - - debugPrint('$count membres traités et stockés'); - } catch (e) { - debugPrint('Erreur lors du traitement des membres: $e'); - } - } - - // Méthode pour traiter les données des associations utilisateurs-secteurs reçues de l'API - Future _processUserSectors(dynamic userSectorsData) async { - try { - debugPrint('Traitement des données des associations utilisateurs-secteurs...'); - - // Vérifier que les données sont au bon format - if (userSectorsData == null) { - debugPrint('Aucune donnée d\'association utilisateur-secteur à traiter'); - return; - } - - List userSectorsList; - if (userSectorsData is List) { - userSectorsList = userSectorsData; - } else if (userSectorsData is Map && userSectorsData.containsKey('data')) { - userSectorsList = userSectorsData['data'] as List; - } else { - debugPrint('Format de données d\'associations utilisateurs-secteurs non reconnu'); - return; - } - - // Vider la boîte avant d'ajouter les nouvelles données - await _userSectorBox.clear(); - - // Traiter chaque association utilisateur-secteur - int count = 0; - for (final userSectorData in userSectorsList) { - try { - final userSector = UserSectorModel.fromJson(userSectorData); - await _userSectorBox.put('${userSector.id}_${userSector.fkSector}', userSector); - count++; - } catch (e) { - debugPrint('Erreur lors du traitement d\'une association utilisateur-secteur: $e'); - } - } - - debugPrint('$count associations utilisateurs-secteurs traitées et stockées'); - } catch (e) { - debugPrint('Erreur lors du traitement des associations utilisateurs-secteurs: $e'); - } - } - - // Méthode pour traiter les données utilisateur reçues de l'API + /// Méthode pour traiter les données utilisateur reçues de l'API UserModel _processUserData(Map userData, String? sessionId, String? sessionExpiry) { - debugPrint('Traitement des données utilisateur: ${userData.toString()}'); + debugPrint('👤 Traitement des données utilisateur: ${userData.toString()}'); - // Convertir l'ID en int, qu'il soit déjà int ou string + // Convertir l'ID en int final dynamic rawId = userData['id']; final int id = rawId is String ? int.parse(rawId) : rawId as int; - // Convertir le rôle en int, qu'il soit déjà int ou string + // Convertir le rôle en int final dynamic rawRole = userData['fk_role']; int role; if (rawRole is String) { @@ -1806,7 +608,6 @@ class UserRepository extends ChangeNotifier { } else if (rawRole is int) { role = rawRole; } else { - // Valeur par défaut si le rôle n'est pas valide role = 1; } @@ -1837,7 +638,7 @@ class UserRepository extends ChangeNotifier { } } - debugPrint('Données traitées - id: $id, role: $role, fkEntite: $fkEntite'); + debugPrint('✅ Données traitées - id: $id, role: $role, fkEntite: $fkEntite'); // Créer un utilisateur avec toutes les données disponibles return UserModel( @@ -1847,7 +648,7 @@ class UserRepository extends ChangeNotifier { username: userData['username'], firstName: userData['first_name'], role: role, - createdAt: DateTime.now(), // Date actuelle comme fallback + createdAt: DateTime.now(), lastSyncedAt: DateTime.now(), isActive: true, isSynced: true, diff --git a/app/lib/core/services/api_service.dart b/app/lib/core/services/api_service.dart index 4275382f..054f5df3 100644 --- a/app/lib/core/services/api_service.dart +++ b/app/lib/core/services/api_service.dart @@ -342,38 +342,6 @@ class ApiService { debugPrint('GEOSECTOR 🔗 Environnement: $env, API: $_baseUrl'); } - ApiService() { - // Configurer l'environnement - _configureEnvironment(); - - // Configurer Dio - _dio.options.baseUrl = _baseUrl; - _dio.options.connectTimeout = AppKeys.connectionTimeout; - _dio.options.receiveTimeout = AppKeys.receiveTimeout; - - // Ajouter les en-têtes par défaut avec l'identifiant d'application adapté à l'environnement - final headers = Map.from(AppKeys.defaultHeaders); - headers['X-App-Identifier'] = _appIdentifier; - - _dio.options.headers.addAll(headers); - - // Ajouter des intercepteurs pour l'authentification par session - _dio.interceptors.add(InterceptorsWrapper(onRequest: (options, handler) { - // Ajouter le session_id comme token Bearer aux en-têtes si disponible - if (_sessionId != null) { - options.headers[AppKeys.sessionHeader] = 'Bearer $_sessionId'; - } - return handler.next(options); - }, onError: (DioException error, handler) { - // Gérer les erreurs d'authentification (401) - if (error.response?.statusCode == 401) { - // Session expirée ou invalide - _sessionId = null; - } - return handler.next(error); - })); - } - // Définir l'ID de session void setSessionId(String? sessionId) { _sessionId = sessionId; @@ -397,7 +365,7 @@ class ApiService { // Vérifier la connectivité réseau Future hasInternetConnection() async { final connectivityResult = await (Connectivity().checkConnectivity()); - return connectivityResult != ConnectivityResult.none; + return connectivityResult.contains(ConnectivityResult.none) == false; } // Méthode POST générique diff --git a/app/lib/core/services/data_loading_service.dart b/app/lib/core/services/data_loading_service.dart new file mode 100644 index 00000000..64bc241c --- /dev/null +++ b/app/lib/core/services/data_loading_service.dart @@ -0,0 +1,667 @@ +import 'dart:io'; +import 'package:flutter/foundation.dart'; +import 'package:hive_flutter/hive_flutter.dart'; +import 'package:path_provider/path_provider.dart'; +import 'package:geosector_app/core/constants/app_keys.dart'; +import 'package:geosector_app/core/services/api_service.dart'; +import 'package:geosector_app/core/services/hive_web_fix.dart'; +import 'package:geosector_app/core/data/models/operation_model.dart'; +import 'package:geosector_app/core/data/models/sector_model.dart'; +import 'package:geosector_app/core/data/models/passage_model.dart'; +import 'package:geosector_app/core/data/models/membre_model.dart'; +import 'package:geosector_app/core/data/models/user_sector_model.dart'; +import 'package:geosector_app/core/data/models/amicale_model.dart'; +import 'package:geosector_app/core/repositories/client_repository.dart'; +import 'package:geosector_app/core/repositories/amicale_repository.dart'; +import 'package:geosector_app/chat/models/conversation_model.dart'; +import 'package:geosector_app/chat/models/message_model.dart'; +import 'package:geosector_app/core/models/loading_state.dart'; + +/// Service singleton pour gérer le chargement et la gestion des données au login +class DataLoadingService extends ChangeNotifier { + static DataLoadingService? _instance; + static DataLoadingService get instance => _instance ??= DataLoadingService._internal(); + DataLoadingService._internal(); + + // État du chargement + LoadingState _loadingState = LoadingState.initial; + LoadingState get loadingState => _loadingState; + + // Callback pour les mises à jour de progression + Function(LoadingState)? _progressCallback; + + // Méthode pour définir un callback de progression + void setProgressCallback(Function(LoadingState)? callback) { + _progressCallback = callback; + } + + // Mettre à jour l'état du chargement + void _updateLoadingState(LoadingState newState) { + _loadingState = newState; + notifyListeners(); + _progressCallback?.call(newState); + } + + // === GETTERS POUR LES BOXES === + Box get _operationBox => Hive.box(AppKeys.operationsBoxName); + Box get _sectorBox => Hive.box(AppKeys.sectorsBoxName); + Box get _passageBox => Hive.box(AppKeys.passagesBoxName); + Box get _membreBox => Hive.box(AppKeys.membresBoxName); + Box get _userSectorBox => Hive.box(AppKeys.userSectorBoxName); + Box get _amicaleBox => Hive.box(AppKeys.amicaleBoxName); + Box get _chatConversationBox => Hive.box(AppKeys.chatConversationsBoxName); + Box get _chatMessageBox => Hive.box(AppKeys.chatMessagesBoxName); + Box get _settingsBox => Hive.box(AppKeys.settingsBoxName); + + // === NETTOYAGE ET PRÉPARATION DES DONNÉES === + + /// Nettoie toutes les données avant le login + Future cleanDataBeforeLogin() async { + try { + _updateLoadingState(LoadingState.initial.copyWith( + progress: 0.05, + message: 'Nettoyage des données...', + stepDescription: 'Suppression des données obsolètes', + )); + + debugPrint('🧹 Début du nettoyage des données avant login...'); + + // Étape 1: Nettoyage des boîtes non référencées (5%) + await _cleanNonDefinedBoxes(); + + // Étape 2: Nettoyage sécurisé des données (10%) + _updateLoadingState(_loadingState.copyWith( + progress: 0.10, + stepDescription: 'Préparation du stockage local', + )); + + if (kIsWeb) { + await HiveWebFix.safeCleanHiveBoxes(excludeBoxes: [AppKeys.userBoxName]); + } else if (Platform.isIOS) { + await _cleanHiveFilesOnIOS(); + } else if (Platform.isAndroid) { + await _cleanHiveFilesOnAndroid(); + } + + // Étape 3: Recréation des boîtes (15%) + _updateLoadingState(_loadingState.copyWith( + progress: 0.15, + stepDescription: 'Initialisation des bases de données', + )); + + await _clearAndRecreateBoxes(); + await _initializeBoxes(); + + debugPrint('✅ Nettoyage des données terminé'); + } catch (e) { + debugPrint('❌ Erreur lors du nettoyage des données: $e'); + _updateLoadingState(LoadingState.error('Erreur lors du nettoyage: $e')); + rethrow; + } + } + + /// Traite toutes les données reçues de l'API lors du login + Future processLoginData(Map apiResult) async { + try { + debugPrint('📊 Début du traitement des données de login...'); + + // Étape 4: Traitement des clients (35%) + _updateLoadingState(_loadingState.copyWith( + progress: 0.35, + stepDescription: 'Chargement des clients', + )); + + if (apiResult['clients'] != null) { + await _processClients(apiResult['clients']); + } + + // Étape 5: Traitement des opérations (50%) + _updateLoadingState(_loadingState.copyWith( + progress: 0.50, + stepDescription: 'Chargement des opérations', + )); + + if (apiResult['operations'] != null) { + await _processOperations(apiResult['operations']); + } + + // Étape 6: Traitement des secteurs (65%) + _updateLoadingState(_loadingState.copyWith( + progress: 0.65, + stepDescription: 'Chargement des secteurs', + )); + + if (apiResult['sectors'] != null) { + await _processSectors(apiResult['sectors']); + } + + // Étape 7: Traitement des passages (75%) + _updateLoadingState(_loadingState.copyWith( + progress: 0.75, + stepDescription: 'Chargement des passages', + )); + + if (apiResult['passages'] != null) { + await _processPassages(apiResult['passages']); + } + + // Étape 8: Traitement des membres (85%) + _updateLoadingState(_loadingState.copyWith( + progress: 0.85, + stepDescription: 'Chargement des membres', + )); + + final membresData = apiResult['membres'] ?? apiResult['members']; + if (membresData != null) { + await _processMembres(membresData); + } + + // Étape 9: Traitement des associations utilisateurs-secteurs (95%) + _updateLoadingState(_loadingState.copyWith( + progress: 0.95, + stepDescription: 'Finalisation du chargement', + )); + + if (apiResult['users_sectors'] != null) { + await _processUserSectors(apiResult['users_sectors']); + } + + // Étape finale: Chargement terminé (100%) + _updateLoadingState(LoadingState.completed.copyWith( + progress: 1.0, + message: 'Chargement terminé avec succès', + stepDescription: 'Données synchronisées', + )); + + debugPrint('✅ Traitement des données de login terminé'); + _logDataSummary(); + } catch (e) { + debugPrint('❌ Erreur lors du traitement des données de login: $e'); + _updateLoadingState(LoadingState.error('Erreur lors du chargement: $e')); + rethrow; + } + } + + /// Nettoie complètement toutes les données lors du logout + Future cleanDataAfterLogout() async { + try { + debugPrint('🧹 Début du nettoyage complet après logout...'); + await _deepCleanHiveBoxes(); + debugPrint('✅ Nettoyage complet terminé'); + } catch (e) { + debugPrint('❌ Erreur lors du nettoyage après logout: $e'); + rethrow; + } + } + + // === MÉTHODES PRIVÉES DE NETTOYAGE === + + Future _cleanNonDefinedBoxes() async { + final nonDefinedBoxes = ['auth', 'locations', 'messages']; + for (final boxName in nonDefinedBoxes) { + try { + if (Hive.isBoxOpen(boxName)) { + debugPrint('Fermeture de la boîte non référencée: $boxName'); + await Hive.box(boxName).close(); + } + await Hive.deleteBoxFromDisk(boxName); + debugPrint('✅ Box $boxName supprimée'); + } catch (e) { + debugPrint('⚠️ Erreur suppression box $boxName: $e'); + } + } + } + + Future _clearAndRecreateBoxes() async { + try { + debugPrint('🔄 Recréation des boîtes Hive...'); + + final boxesToDelete = [ + AppKeys.passagesBoxName, + AppKeys.operationsBoxName, + AppKeys.sectorsBoxName, + AppKeys.userSectorBoxName, + AppKeys.chatConversationsBoxName, + AppKeys.chatMessagesBoxName, + ]; + + // Vider chaque boîte sans la fermer + for (final boxName in boxesToDelete) { + try { + if (Hive.isBoxOpen(boxName)) { + final box = Hive.box(boxName); + await box.clear(); + debugPrint('✅ Box $boxName vidée'); + } else { + await Hive.deleteBoxFromDisk(boxName); + debugPrint('✅ Box $boxName supprimée du disque'); + } + } catch (e) { + debugPrint('⚠️ Erreur nettoyage box $boxName: $e'); + } + } + + await Future.delayed(const Duration(milliseconds: 500)); + } catch (e) { + debugPrint('❌ Erreur recréation des boîtes: $e'); + rethrow; + } + } + + Future _initializeBoxes() async { + debugPrint('📦 Initialisation des boîtes Hive...'); + + final boxesToInit = [ + AppKeys.operationsBoxName, + AppKeys.sectorsBoxName, + AppKeys.passagesBoxName, + AppKeys.membresBoxName, + AppKeys.userSectorBoxName, + AppKeys.chatConversationsBoxName, + AppKeys.chatMessagesBoxName, + ]; + + for (final boxName in boxesToInit) { + await _ensureBoxIsOpen(boxName); + } + + debugPrint('✅ Toutes les boîtes Hive sont ouvertes'); + } + + Future _ensureBoxIsOpen(String boxName) async { + try { + if (!Hive.isBoxOpen(boxName)) { + debugPrint('Ouverture de la boîte $boxName...'); + + switch (boxName) { + case AppKeys.passagesBoxName: + await Hive.openBox(boxName); + break; + case AppKeys.operationsBoxName: + await Hive.openBox(boxName); + break; + case AppKeys.sectorsBoxName: + await Hive.openBox(boxName); + break; + case AppKeys.membresBoxName: + await Hive.openBox(boxName); + break; + case AppKeys.userSectorBoxName: + await Hive.openBox(boxName); + break; + case AppKeys.chatConversationsBoxName: + await Hive.openBox(boxName); + break; + case AppKeys.chatMessagesBoxName: + await Hive.openBox(boxName); + break; + default: + await Hive.openBox(boxName); + } + } + } catch (e) { + debugPrint('❌ Erreur ouverture box $boxName: $e'); + throw Exception('Impossible d\'ouvrir la boîte $boxName: $e'); + } + } + + // === MÉTHODES DE TRAITEMENT DES DONNÉES === + + Future _processClients(dynamic clientsData) async { + try { + debugPrint('👥 Traitement des clients...'); + + List clientsList; + if (clientsData is List) { + clientsList = clientsData; + } else if (clientsData is Map && clientsData.containsKey('data')) { + clientsList = clientsData['data'] as List; + } else { + debugPrint('⚠️ Format de données clients non reconnu'); + return; + } + + if (clientsList.isNotEmpty) { + debugPrint('📊 Traitement de ${clientsList.length} clients de type 1'); + final clientRepository = ClientRepository(); + await clientRepository.processClientsData(clientsList); + debugPrint('✅ Clients traités via ClientRepository'); + } + } catch (e) { + debugPrint('❌ Erreur traitement clients: $e'); + } + } + + Future _processOperations(dynamic operationsData) async { + try { + debugPrint('⚙️ Traitement des opérations...'); + + if (operationsData == null) { + debugPrint('ℹ️ Aucune donnée d\'opération à traiter'); + return; + } + + List operationsList; + if (operationsData is List) { + operationsList = operationsData; + } else if (operationsData is Map && operationsData.containsKey('data')) { + operationsList = operationsData['data'] as List; + } else { + debugPrint('⚠️ Format de données d\'opérations non reconnu'); + return; + } + + await _operationBox.clear(); + + int count = 0; + for (final operationData in operationsList) { + try { + final operation = OperationModel.fromJson(operationData); + await _operationBox.put(operation.id, operation); + count++; + } catch (e) { + debugPrint('⚠️ Erreur traitement opération: $e'); + } + } + + debugPrint('✅ $count opérations stockées'); + } catch (e) { + debugPrint('❌ Erreur traitement opérations: $e'); + } + } + + Future _processSectors(dynamic sectorsData) async { + try { + debugPrint('📍 Traitement des secteurs...'); + + if (sectorsData == null) { + debugPrint('ℹ️ Aucune donnée de secteur à traiter'); + return; + } + + List sectorsList; + if (sectorsData is List) { + sectorsList = sectorsData; + } else if (sectorsData is Map && sectorsData.containsKey('data')) { + sectorsList = sectorsData['data'] as List; + } else { + debugPrint('⚠️ Format de données de secteurs non reconnu'); + return; + } + + await _sectorBox.clear(); + + int count = 0; + for (final sectorData in sectorsList) { + try { + final sector = SectorModel.fromJson(sectorData); + await _sectorBox.put(sector.id, sector); + count++; + } catch (e) { + debugPrint('⚠️ Erreur traitement secteur: $e'); + } + } + + debugPrint('✅ $count secteurs stockés'); + } catch (e) { + debugPrint('❌ Erreur traitement secteurs: $e'); + } + } + + Future _processPassages(dynamic passagesData) async { + try { + debugPrint('🚶 Traitement des passages...'); + + if (passagesData == null) { + debugPrint('ℹ️ Aucune donnée de passage à traiter'); + return; + } + + List passagesList; + if (passagesData is List) { + passagesList = passagesData; + } else if (passagesData is Map && passagesData.containsKey('data')) { + passagesList = passagesData['data'] as List; + } else { + debugPrint('⚠️ Format de données de passages non reconnu'); + return; + } + + await _passageBox.clear(); + + int count = 0; + for (final passageData in passagesList) { + try { + final passage = PassageModel.fromJson(passageData); + await _passageBox.put(passage.id, passage); + count++; + } catch (e) { + debugPrint('⚠️ Erreur traitement passage: $e'); + } + } + + debugPrint('✅ $count passages stockés'); + } catch (e) { + debugPrint('❌ Erreur traitement passages: $e'); + } + } + + Future _processMembres(dynamic membresData) async { + try { + debugPrint('👤 Traitement des membres...'); + + if (membresData == null) { + debugPrint('ℹ️ Aucune donnée de membre à traiter'); + return; + } + + List membresList; + if (membresData is List) { + membresList = membresData; + } else if (membresData is Map && membresData.containsKey('data')) { + membresList = membresData['data'] as List; + } else { + debugPrint('⚠️ Format de données de membres non reconnu'); + return; + } + + await _membreBox.clear(); + + int count = 0; + for (final membreData in membresList) { + try { + final membre = MembreModel.fromJson(membreData); + await _membreBox.put(membre.id, membre); + count++; + } catch (e) { + debugPrint('⚠️ Erreur traitement membre: $e'); + } + } + + debugPrint('✅ $count membres stockés'); + } catch (e) { + debugPrint('❌ Erreur traitement membres: $e'); + } + } + + Future _processUserSectors(dynamic userSectorsData) async { + try { + debugPrint('🔗 Traitement des associations utilisateurs-secteurs...'); + + if (userSectorsData == null) { + debugPrint('ℹ️ Aucune association utilisateur-secteur à traiter'); + return; + } + + List userSectorsList; + if (userSectorsData is List) { + userSectorsList = userSectorsData; + } else if (userSectorsData is Map && userSectorsData.containsKey('data')) { + userSectorsList = userSectorsData['data'] as List; + } else { + debugPrint('⚠️ Format de données d\'associations non reconnu'); + return; + } + + await _userSectorBox.clear(); + + int count = 0; + for (final userSectorData in userSectorsList) { + try { + final userSector = UserSectorModel.fromJson(userSectorData); + await _userSectorBox.put('${userSector.id}_${userSector.fkSector}', userSector); + count++; + } catch (e) { + debugPrint('⚠️ Erreur traitement association: $e'); + } + } + + debugPrint('✅ $count associations stockées'); + } catch (e) { + debugPrint('❌ Erreur traitement associations: $e'); + } + } + + // === MÉTHODES DE NETTOYAGE PLATEFORME SPÉCIFIQUES === + + Future _cleanHiveFilesOnIOS() async { + if (!kIsWeb && Platform.isIOS) { + try { + debugPrint('🍎 Nettoyage des fichiers Hive sur iOS...'); + final appDir = await getApplicationDocumentsDirectory(); + final hiveDir = Directory('${appDir.path}/hive'); + + if (await hiveDir.exists()) { + final entries = await hiveDir.list().toList(); + for (var entry in entries) { + final name = entry.path.split('/').last; + if (!name.contains(AppKeys.userBoxName)) { + if (entry is Directory) { + await entry.delete(recursive: true); + } else if (entry is File) { + await entry.delete(); + } + } + } + debugPrint('✅ Nettoyage iOS terminé'); + } + } catch (e) { + debugPrint('❌ Erreur nettoyage iOS: $e'); + } + } + } + + Future _cleanHiveFilesOnAndroid() async { + if (!kIsWeb && Platform.isAndroid) { + try { + debugPrint('🤖 Nettoyage des fichiers Hive sur Android...'); + final appDir = await getApplicationDocumentsDirectory(); + final entries = await appDir.list().toList(); + int filesDeleted = 0; + + for (var entry in entries) { + final name = entry.path.split('/').last; + if (name.endsWith('.hive') && !name.contains(AppKeys.userBoxName)) { + if (entry is File) { + await entry.delete(); + filesDeleted++; + + final lockFile = File('${entry.path}.lock'); + if (await lockFile.exists()) { + await lockFile.delete(); + } + } + } + } + + debugPrint('✅ Nettoyage Android terminé. $filesDeleted fichiers supprimés'); + } catch (e) { + debugPrint('❌ Erreur nettoyage Android: $e'); + } + } + } + + Future _deepCleanHiveBoxes() async { + try { + debugPrint('🧹 Nettoyage profond des boîtes Hive...'); + + // Vider toutes les boîtes + final boxesToClean = [ + AppKeys.operationsBoxName, + AppKeys.sectorsBoxName, + AppKeys.passagesBoxName, + AppKeys.membresBoxName, + AppKeys.userSectorBoxName, + AppKeys.amicaleBoxName, + AppKeys.chatConversationsBoxName, + AppKeys.chatMessagesBoxName, + AppKeys.settingsBoxName, + ]; + + for (final boxName in boxesToClean) { + try { + if (Hive.isBoxOpen(boxName)) { + await Hive.box(boxName).clear(); + debugPrint('✅ Box $boxName vidée'); + } + } catch (e) { + debugPrint('⚠️ Erreur vidage box $boxName: $e'); + } + } + + // Nettoyage spécifique à la plateforme + if (kIsWeb) { + await HiveWebFix.resetHiveCompletely(); + } else if (Platform.isIOS) { + await _cleanHiveFilesOnIOS(); + } else if (Platform.isAndroid) { + await _cleanHiveFilesOnAndroid(); + } + + await Future.delayed(const Duration(milliseconds: 800)); + debugPrint('✅ Nettoyage profond terminé'); + } catch (e) { + debugPrint('❌ Erreur nettoyage profond: $e'); + } + } + + // === MÉTHODES UTILITAIRES === + + /// Affiche un résumé des données chargées + void _logDataSummary() { + try { + debugPrint('📊 === RÉSUMÉ DES DONNÉES CHARGÉES ==='); + debugPrint('Opérations: ${_operationBox.length}'); + debugPrint('Secteurs: ${_sectorBox.length}'); + debugPrint('Passages: ${_passageBox.length}'); + debugPrint('Membres: ${_membreBox.length}'); + debugPrint('Associations User-Sector: ${_userSectorBox.length}'); + debugPrint('Amicales: ${_amicaleBox.length}'); + debugPrint('================================='); + } catch (e) { + debugPrint('⚠️ Erreur lors du résumé: $e'); + } + } + + /// Retourne un résumé des données pour l'UI + Map getDataSummary() { + try { + return { + 'operations': _operationBox.length, + 'sectors': _sectorBox.length, + 'passages': _passageBox.length, + 'membres': _membreBox.length, + 'userSectors': _userSectorBox.length, + 'amicales': _amicaleBox.length, + }; + } catch (e) { + debugPrint('⚠️ Erreur génération résumé: $e'); + return {}; + } + } + + // === RESET POUR TESTS === + static void reset() { + _instance = null; + } +} \ No newline at end of file diff --git a/app/lib/main.dart b/app/lib/main.dart index abe77f1f..6d681120 100644 --- a/app/lib/main.dart +++ b/app/lib/main.dart @@ -3,6 +3,9 @@ import 'package:flutter/services.dart'; import 'package:flutter/foundation.dart' show kIsWeb; import 'package:flutter_web_plugins/url_strategy.dart'; import 'package:geosector_app/core/services/app_info_service.dart'; +import 'package:geosector_app/core/services/api_service.dart'; +import 'package:geosector_app/core/services/current_user_service.dart'; +import 'package:geosector_app/core/services/current_amicale_service.dart'; import 'package:geosector_app/app.dart'; import 'package:hive_flutter/hive_flutter.dart'; import 'package:geosector_app/core/data/models/user_model.dart'; @@ -51,10 +54,20 @@ void main() async { /// Initialise les services essentiels Future _initializeServices() async { try { + // Initialiser ApiService en premier + await ApiService.initialize(); + debugPrint('✅ ApiService singleton initialisé'); + + // Les services CurrentUserService et CurrentAmicaleService s'initialisent automatiquement + // au premier accès via le pattern singleton lazy + debugPrint('✅ CurrentUserService prêt'); + debugPrint('✅ CurrentAmicaleService prêt'); + await AppInfoService.initialize(); - debugPrint('Services initialisés avec succès'); + debugPrint('✅ Tous les services initialisés avec succès'); } catch (e) { - debugPrint('Erreur lors de l\'initialisation des services: $e'); + debugPrint('❌ Erreur lors de l\'initialisation des services: $e'); + rethrow; // Important pour arrêter l'app si les services critiques échouent } } @@ -70,6 +83,11 @@ Future _initializeHive() async { // Ouvrir uniquement les boîtes essentielles au démarrage await _openEssentialHiveBoxes(); + // Charger les données depuis Hive au démarrage + await CurrentUserService.instance.loadFromHive(); + await CurrentAmicaleService.instance.loadFromHive(); + debugPrint('✅ Données utilisateur/amicale chargées depuis Hive'); + debugPrint('Hive initialisé avec succès'); return true; } catch (e) { @@ -130,7 +148,7 @@ void _registerHiveAdapters() { } } -/// Ouvre les boîtes Hive essentielles +/// Ouvre les boîtes Hive essentielles avec migration users -> user Future _openEssentialHiveBoxes() async { final boxesToOpen = [ {'name': AppKeys.userBoxName, 'type': 'UserModel'}, @@ -234,51 +252,4 @@ Future _doesBoxExist(String boxName) async { } catch (e) { return false; } -} - final boxesToOpen = [ - {'name': AppKeys.userBoxName, 'type': 'UserModel'}, - {'name': AppKeys.amicaleBoxName, 'type': 'AmicaleModel'}, - {'name': AppKeys.clientsBoxName, 'type': 'ClientModel'}, - {'name': AppKeys.settingsBoxName, 'type': 'dynamic'}, - {'name': AppKeys.chatConversationsBoxName, 'type': 'ConversationModel'}, - {'name': AppKeys.chatMessagesBoxName, 'type': 'MessageModel'}, - ]; - - for (final box in boxesToOpen) { - try { - final boxName = box['name'] as String; - final boxType = box['type'] as String; - - // Vérifier si la boîte est déjà ouverte - if (Hive.isBoxOpen(boxName)) { - debugPrint('Boîte $boxName déjà ouverte'); - continue; - } - - switch (boxType) { - case 'UserModel': - await Hive.openBox(boxName); - break; - case 'AmicaleModel': - await Hive.openBox(boxName); - break; - case 'ClientModel': - await Hive.openBox(boxName); - break; - case 'ConversationModel': - await Hive.openBox(boxName); - break; - case 'MessageModel': - await Hive.openBox(boxName); - break; - default: - await Hive.openBox(boxName); - } - - debugPrint('Boîte $boxName ouverte avec succès'); - } catch (e) { - debugPrint('Erreur lors de l\'ouverture de la boîte ${box['name']}: $e'); - // Ne pas lancer d'erreur, continuer avec les autres boîtes - } - } }