Release v3.1.4 - Mode terrain et génération PDF #10
31
app/.dart_tool/extension_discovery/README.md
Normal file
31
app/.dart_tool/extension_discovery/README.md
Normal file
@@ -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.
|
||||||
1
app/.dart_tool/extension_discovery/vs_code.json
Normal file
1
app/.dart_tool/extension_discovery/vs_code.json
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"version":2,"entries":[{"package":"geosector_app","rootUri":"../","packageUri":"lib/"}]}
|
||||||
@@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:geosector_app/core/theme/app_theme.dart';
|
import 'package:geosector_app/core/theme/app_theme.dart';
|
||||||
import 'package:go_router/go_router.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/user_repository.dart';
|
||||||
import 'package:geosector_app/core/repositories/operation_repository.dart';
|
import 'package:geosector_app/core/repositories/operation_repository.dart';
|
||||||
import 'package:geosector_app/core/repositories/passage_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/admin/admin_dashboard_page.dart';
|
||||||
import 'package:geosector_app/presentation/user/user_dashboard_page.dart';
|
import 'package:geosector_app/presentation/user/user_dashboard_page.dart';
|
||||||
|
|
||||||
// Instances globales des services et repositories
|
// Instances globales des repositories (plus besoin d'injecter ApiService)
|
||||||
final apiService = ApiService();
|
final operationRepository = OperationRepository();
|
||||||
final operationRepository = OperationRepository(apiService);
|
final passageRepository = PassageRepository();
|
||||||
final passageRepository = PassageRepository(apiService);
|
final userRepository = UserRepository();
|
||||||
final userRepository = UserRepository(apiService);
|
final sectorRepository = SectorRepository();
|
||||||
final sectorRepository = SectorRepository(apiService);
|
final membreRepository = MembreRepository();
|
||||||
final membreRepository = MembreRepository(apiService);
|
final amicaleRepository = AmicaleRepository();
|
||||||
final amicaleRepository = AmicaleRepository(apiService);
|
|
||||||
final syncService = SyncService(userRepository: userRepository);
|
final syncService = SyncService(userRepository: userRepository);
|
||||||
final connectivityService = ConnectivityService();
|
final connectivityService = ConnectivityService();
|
||||||
|
|
||||||
@@ -60,8 +59,7 @@ class GeosectorApp extends StatelessWidget {
|
|||||||
name: 'login',
|
name: 'login',
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
// Récupérer le type depuis les query parameters ou extra data
|
// Récupérer le type depuis les query parameters ou extra data
|
||||||
final type = state.uri.queryParameters['type'] ??
|
final type = state.uri.queryParameters['type'] ?? (state.extra as Map<String, dynamic>?)?['type'] as String?;
|
||||||
(state.extra as Map<String, dynamic>?)?['type'] as String?;
|
|
||||||
|
|
||||||
debugPrint('GoRoute: Affichage de LoginPage avec type: $type');
|
debugPrint('GoRoute: Affichage de LoginPage avec type: $type');
|
||||||
return LoginPage(loginType: type);
|
return LoginPage(loginType: type);
|
||||||
@@ -125,44 +123,37 @@ class GeosectorApp extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Pages publiques qui ne nécessitent pas d'authentification
|
// Pages publiques qui ne nécessitent pas d'authentification
|
||||||
final publicPaths = [
|
final publicPaths = ['/login', '/login/user', '/login/admin', '/register'];
|
||||||
'/login',
|
|
||||||
'/login/user',
|
|
||||||
'/login/admin',
|
|
||||||
'/register'
|
|
||||||
];
|
|
||||||
if (publicPaths.any((path) => currentPath.startsWith(path))) {
|
if (publicPaths.any((path) => currentPath.startsWith(path))) {
|
||||||
debugPrint(
|
debugPrint('GoRouter.redirect: Page publique autorisée: $currentPath');
|
||||||
'GoRouter.redirect: Page publique autorisée: $currentPath');
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Vérifier l'authentification pour les pages protégées
|
// Vérifier l'authentification pour les pages protégées
|
||||||
try {
|
try {
|
||||||
final isAuthenticated = userRepository.isLoggedIn;
|
// Utiliser le nouveau CurrentUserService
|
||||||
final currentUser = userRepository.currentUser;
|
final userService = CurrentUserService.instance;
|
||||||
|
final isAuthenticated = userService.isLoggedIn;
|
||||||
|
final currentUser = userService.currentUser;
|
||||||
|
|
||||||
debugPrint('GoRouter.redirect: isAuthenticated = $isAuthenticated');
|
debugPrint('GoRouter.redirect: isAuthenticated = $isAuthenticated');
|
||||||
debugPrint('GoRouter.redirect: currentUser = ${currentUser?.email}');
|
debugPrint('GoRouter.redirect: currentUser = ${currentUser?.email}');
|
||||||
|
|
||||||
// Si pas authentifié, rediriger vers la splash page
|
// Si pas authentifié, rediriger vers la splash page
|
||||||
if (!isAuthenticated) {
|
if (!isAuthenticated) {
|
||||||
debugPrint(
|
debugPrint('GoRouter.redirect: Non authentifié, redirection vers /');
|
||||||
'GoRouter.redirect: Non authentifié, redirection vers /');
|
|
||||||
return '/';
|
return '/';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Vérifier les permissions pour les pages admin
|
// Vérifier les permissions pour les pages admin
|
||||||
if (currentPath.startsWith('/admin')) {
|
if (currentPath.startsWith('/admin')) {
|
||||||
final userRole = userRepository.getUserRole();
|
final userRole = userService.userRole;
|
||||||
final isAdmin = userRole > 1; // Admin = rôle 2 ou plus
|
final isAdmin = userService.canAccessAdmin;
|
||||||
|
|
||||||
debugPrint(
|
debugPrint('GoRouter.redirect: userRole = $userRole, canAccessAdmin = $isAdmin');
|
||||||
'GoRouter.redirect: userRole = $userRole, isAdmin = $isAdmin');
|
|
||||||
|
|
||||||
if (!isAdmin) {
|
if (!isAdmin) {
|
||||||
debugPrint(
|
debugPrint('GoRouter.redirect: Pas admin, redirection vers /user');
|
||||||
'GoRouter.redirect: Pas admin, redirection vers /user');
|
|
||||||
return '/user';
|
return '/user';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -171,15 +162,13 @@ class GeosectorApp extends StatelessWidget {
|
|||||||
debugPrint('GoRouter.redirect: Accès autorisé à $currentPath');
|
debugPrint('GoRouter.redirect: Accès autorisé à $currentPath');
|
||||||
return null;
|
return null;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
debugPrint(
|
debugPrint('GoRouter.redirect: Erreur lors de la vérification auth: $e');
|
||||||
'GoRouter.redirect: Erreur lors de la vérification auth: $e');
|
|
||||||
// En cas d'erreur, rediriger vers la splash page pour sécurité
|
// En cas d'erreur, rediriger vers la splash page pour sécurité
|
||||||
return '/';
|
return '/';
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// Listener pour déboguer les changements de route
|
// Listener pour déboguer les changements de route
|
||||||
refreshListenable:
|
refreshListenable: CurrentUserService.instance, // Écouter les changements dans CurrentUserService
|
||||||
userRepository, // Écouter les changements dans userRepository
|
|
||||||
debugLogDiagnostics: true, // Activer les logs de débogage
|
debugLogDiagnostics: true, // Activer les logs de débogage
|
||||||
errorBuilder: (context, state) {
|
errorBuilder: (context, state) {
|
||||||
debugPrint('GoRouter.errorBuilder: Erreur pour ${state.uri.path}');
|
debugPrint('GoRouter.errorBuilder: Erreur pour ${state.uri.path}');
|
||||||
|
|||||||
@@ -1,7 +1,209 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:hive_flutter/hive_flutter.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';
|
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<AmicaleModel> get _amicaleBox {
|
||||||
|
_ensureBoxIsOpen();
|
||||||
|
return Hive.box<AmicaleModel>(AppKeys.amicaleBoxName);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _isLoading = false;
|
||||||
|
|
||||||
|
// Getters
|
||||||
|
bool get isLoading => _isLoading;
|
||||||
|
List<AmicaleModel> get amicales => getAllAmicales();
|
||||||
|
|
||||||
|
// Méthode pour vérifier si la boîte est ouverte et l'ouvrir si nécessaire
|
||||||
|
Future<void> _ensureBoxIsOpen() async {
|
||||||
|
const boxName = AppKeys.amicaleBoxName;
|
||||||
|
if (!Hive.isBoxOpen(boxName)) {
|
||||||
|
debugPrint('Ouverture de la boîte $boxName dans AmicaleRepository...');
|
||||||
|
await Hive.openBox<AmicaleModel>(boxName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Récupérer toutes les amicales
|
||||||
|
List<AmicaleModel> 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<AmicaleModel> getAmicalesByType(int type) {
|
||||||
|
return _amicaleBox.values.where((amicale) => amicale.fkType == type).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Récupérer uniquement les clients (type 1)
|
||||||
|
List<AmicaleModel> getClients() {
|
||||||
|
return getAmicalesByType(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sauvegarder une amicale
|
||||||
|
Future<void> saveAmicale(AmicaleModel amicale) async {
|
||||||
|
await _amicaleBox.put(amicale.id, amicale);
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Supprimer une amicale
|
||||||
|
Future<void> deleteAmicale(int id) async {
|
||||||
|
await _amicaleBox.delete(id);
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Créer une amicale via l'API
|
||||||
|
Future<bool> 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<bool> 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<bool> 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<void> 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<dynamic> amicalesList;
|
||||||
|
if (amicalesData is List) {
|
||||||
|
amicalesList = amicalesData;
|
||||||
|
} else if (amicalesData is Map && amicalesData.containsKey('data')) {
|
||||||
|
amicalesList = amicalesData['data'] as List<dynamic>;
|
||||||
|
} 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/services/api_service.dart';
|
||||||
import 'package:geosector_app/core/data/models/amicale_model.dart';
|
import 'package:geosector_app/core/data/models/amicale_model.dart';
|
||||||
|
|
||||||
|
|||||||
@@ -1,81 +1,151 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:hive_flutter/hive_flutter.dart';
|
import 'package:hive/hive.dart';
|
||||||
import 'package:geosector_app/core/constants/app_keys.dart';
|
|
||||||
import 'package:geosector_app/core/services/api_service.dart';
|
|
||||||
import 'package:geosector_app/core/data/models/client_model.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 {
|
class ClientRepository extends ChangeNotifier {
|
||||||
// Utilisation de getters lazy pour n'accéder à la boîte que lorsque nécessaire
|
// Constructeur sans paramètres - utilise ApiService.instance
|
||||||
Box<ClientModel> get _clientBox =>
|
ClientRepository();
|
||||||
Hive.box<ClientModel>(AppKeys.clientsBoxName);
|
// 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<ClientModel> get _clientBox {
|
||||||
|
_ensureBoxIsOpen();
|
||||||
|
return Hive.box<ClientModel>(AppKeys.clientsBoxName);
|
||||||
|
}
|
||||||
|
|
||||||
final ApiService _apiService;
|
|
||||||
bool _isLoading = false;
|
bool _isLoading = false;
|
||||||
|
|
||||||
ClientRepository(this._apiService);
|
|
||||||
|
|
||||||
// Getters
|
// Getters
|
||||||
bool get isLoading => _isLoading;
|
bool get isLoading => _isLoading;
|
||||||
|
List<ClientModel> 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<void> _ensureBoxIsOpen() async {
|
Future<void> _ensureBoxIsOpen() async {
|
||||||
try {
|
const boxName = AppKeys.clientsBoxName;
|
||||||
if (!Hive.isBoxOpen(AppKeys.clientsBoxName)) {
|
if (!Hive.isBoxOpen(boxName)) {
|
||||||
debugPrint('Ouverture de la boîte clients...');
|
debugPrint('Ouverture de la boîte $boxName dans ClientRepository...');
|
||||||
await Hive.openBox<ClientModel>(AppKeys.clientsBoxName);
|
await Hive.openBox<ClientModel>(boxName);
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
debugPrint('Erreur lors de l\'ouverture de la boîte clients: $e');
|
|
||||||
throw Exception('Impossible d\'ouvrir la boîte clients: $e');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Récupérer tous les clients
|
// Récupérer tous les clients
|
||||||
List<ClientModel> getAllClients() {
|
List<ClientModel> getAllClients() {
|
||||||
try {
|
|
||||||
_ensureBoxIsOpen();
|
|
||||||
return _clientBox.values.toList();
|
return _clientBox.values.toList();
|
||||||
} catch (e) {
|
|
||||||
debugPrint('Erreur lors de la récupération des clients: $e');
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Récupérer un client par son ID
|
// Récupérer un client par son ID
|
||||||
ClientModel? getClientById(int id) {
|
ClientModel? getClientById(int id) {
|
||||||
try {
|
|
||||||
_ensureBoxIsOpen();
|
|
||||||
return _clientBox.get(id);
|
return _clientBox.get(id);
|
||||||
} catch (e) {
|
|
||||||
debugPrint('Erreur lors de la récupération du client: $e');
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Créer ou mettre à jour un client localement
|
// Sauvegarder un client
|
||||||
Future<ClientModel> saveClient(ClientModel client) async {
|
Future<void> saveClient(ClientModel client) async {
|
||||||
await _ensureBoxIsOpen();
|
|
||||||
await _clientBox.put(client.id, client);
|
await _clientBox.put(client.id, client);
|
||||||
notifyListeners(); // Notifier les changements pour mettre à jour l'UI
|
notifyListeners();
|
||||||
return client;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Supprimer un client localement
|
// Supprimer un client
|
||||||
Future<void> deleteClient(int id) async {
|
Future<void> deleteClient(int id) async {
|
||||||
await _ensureBoxIsOpen();
|
|
||||||
await _clientBox.delete(id);
|
await _clientBox.delete(id);
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Vider la boîte des clients
|
// Créer un client via l'API
|
||||||
Future<void> clearClients() async {
|
Future<bool> createClient(ClientModel client) async {
|
||||||
await _ensureBoxIsOpen();
|
_isLoading = true;
|
||||||
await _clientBox.clear();
|
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();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Traiter les données des clients reçues de l'API
|
// Mettre à jour un client via l'API
|
||||||
|
Future<bool> 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<bool> 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<void> processClientsData(dynamic clientsData) async {
|
Future<void> processClientsData(dynamic clientsData) async {
|
||||||
try {
|
try {
|
||||||
debugPrint('Traitement des données des clients...');
|
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
|
// Vider la boîte avant d'ajouter les nouvelles données
|
||||||
await _ensureBoxIsOpen();
|
|
||||||
await _clientBox.clear();
|
await _clientBox.clear();
|
||||||
|
|
||||||
// Traiter chaque client
|
// Traiter chaque client
|
||||||
@@ -107,7 +176,6 @@ class ClientRepository extends ChangeNotifier {
|
|||||||
final client = ClientModel.fromJson(clientData);
|
final client = ClientModel.fromJson(clientData);
|
||||||
await _clientBox.put(client.id, client);
|
await _clientBox.put(client.id, client);
|
||||||
count++;
|
count++;
|
||||||
debugPrint('Client traité: ${client.name} (ID: ${client.id})');
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
debugPrint('Erreur lors du traitement d\'un client: $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<void> clearClients() async {
|
||||||
|
await _ensureBoxIsOpen();
|
||||||
|
await _clientBox.clear();
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
// Récupérer les clients depuis l'API
|
// Récupérer les clients depuis l'API
|
||||||
Future<List<ClientModel>> fetchClientsFromApi() async {
|
Future<List<ClientModel>> fetchClientsFromApi() async {
|
||||||
_isLoading = true;
|
_isLoading = true;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final response = await _apiService.get('/clients');
|
final response = await ApiService.instance.get('/clients');
|
||||||
|
|
||||||
if (response.statusCode == 200) {
|
if (response.statusCode == 200) {
|
||||||
final clientsData = response.data;
|
final clientsData = response.data;
|
||||||
await processClientsData(clientsData);
|
await processClientsData(clientsData);
|
||||||
return getAllClients();
|
return getAllClients();
|
||||||
} else {
|
} else {
|
||||||
debugPrint(
|
debugPrint('Erreur lors de la récupération des clients: ${response.statusCode}');
|
||||||
'Erreur lors de la récupération des clients: ${response.statusCode}');
|
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -153,9 +227,7 @@ class ClientRepository extends ChangeNotifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final lowercaseQuery = query.toLowerCase();
|
final lowercaseQuery = query.toLowerCase();
|
||||||
return _clientBox.values
|
return _clientBox.values.where((client) => client.name.toLowerCase().contains(lowercaseQuery)).toList();
|
||||||
.where((client) => client.name.toLowerCase().contains(lowercaseQuery))
|
|
||||||
.toList();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Filtrer les clients par type
|
// Filtrer les clients par type
|
||||||
@@ -165,15 +237,11 @@ class ClientRepository extends ChangeNotifier {
|
|||||||
|
|
||||||
// Filtrer les clients par région
|
// Filtrer les clients par région
|
||||||
List<ClientModel> getClientsByRegion(int regionId) {
|
List<ClientModel> getClientsByRegion(int regionId) {
|
||||||
return _clientBox.values
|
return _clientBox.values.where((client) => client.fkRegion == regionId).toList();
|
||||||
.where((client) => client.fkRegion == regionId)
|
|
||||||
.toList();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Filtrer les clients actifs
|
// Filtrer les clients actifs
|
||||||
List<ClientModel> getActiveClients() {
|
List<ClientModel> getActiveClients() {
|
||||||
return _clientBox.values
|
return _clientBox.values.where((client) => client.chkActive == true).toList();
|
||||||
.where((client) => client.chkActive == true)
|
|
||||||
.toList();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,20 +1,22 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:hive/hive.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:geosector_app/core/data/models/membre_model.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 {
|
class MembreRepository extends ChangeNotifier {
|
||||||
// Utilisation de getters lazy pour n'accéder à la boîte que lorsque nécessaire
|
// Constructeur sans paramètres - utilise ApiService.instance
|
||||||
Box<MembreModel> get _membreBox => Hive.box<MembreModel>(AppKeys.membresBoxName);
|
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<MembreModel> get _membreBox {
|
||||||
|
_ensureBoxIsOpen();
|
||||||
|
return Hive.box<MembreModel>(AppKeys.membresBoxName);
|
||||||
|
}
|
||||||
|
|
||||||
final ApiService _apiService;
|
|
||||||
bool _isLoading = false;
|
bool _isLoading = false;
|
||||||
|
|
||||||
MembreRepository(this._apiService);
|
|
||||||
|
|
||||||
// Getters
|
// Getters
|
||||||
bool get isLoading => _isLoading;
|
bool get isLoading => _isLoading;
|
||||||
List<MembreModel> get membres => getAllMembres();
|
List<MembreModel> 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<void> _ensureBoxIsOpen() async {
|
Future<void> _ensureBoxIsOpen() async {
|
||||||
try {
|
const boxName = AppKeys.membresBoxName;
|
||||||
if (!Hive.isBoxOpen(AppKeys.membresBoxName)) {
|
if (!Hive.isBoxOpen(boxName)) {
|
||||||
debugPrint('Ouverture de la boîte ${AppKeys.membresBoxName}...');
|
debugPrint('Ouverture de la boîte $boxName dans MembreRepository...');
|
||||||
await Hive.openBox<MembreModel>(AppKeys.membresBoxName);
|
await Hive.openBox<MembreModel>(boxName);
|
||||||
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');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,12 +92,11 @@ class MembreRepository extends ChangeNotifier {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Créer ou mettre à jour un membre
|
// Sauvegarder un membre
|
||||||
Future<MembreModel> saveMembre(MembreModel membre) async {
|
Future<void> saveMembre(MembreModel membre) async {
|
||||||
await _ensureBoxIsOpen();
|
await _ensureBoxIsOpen();
|
||||||
await _membreBox.put(membre.id, membre);
|
await _membreBox.put(membre.id, membre);
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
return membre;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Supprimer un membre
|
// Supprimer un membre
|
||||||
@@ -110,72 +106,35 @@ class MembreRepository extends ChangeNotifier {
|
|||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Récupérer les membres depuis l'API (uniquement pour l'interface admin)
|
|
||||||
Future<List<MembreModel>> 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<dynamic> membresData = response.data['membres'];
|
|
||||||
|
|
||||||
// Vider la boîte avant d'ajouter les nouveaux membres
|
|
||||||
await _ensureBoxIsOpen();
|
|
||||||
await _membreBox.clear();
|
|
||||||
|
|
||||||
final List<MembreModel> 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
|
// Créer un membre via l'API
|
||||||
Future<MembreModel?> createMembreViaApi(MembreModel membre) async {
|
Future<bool> createMembre(MembreModel membre) async {
|
||||||
_isLoading = true;
|
_isLoading = true;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final hasConnection = await _apiService.hasInternetConnection();
|
// Préparer les données pour l'API
|
||||||
if (!hasConnection) {
|
final data = membre.toJson();
|
||||||
debugPrint('Pas de connexion Internet, impossible de créer le membre');
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Endpoint à adapter selon votre API
|
// Appeler l'API pour créer le membre
|
||||||
final response = await _apiService.post('/membres', data: membre.toJson());
|
final response = await ApiService.instance.post('/membres', data: data);
|
||||||
final membreData = response.data['membre'];
|
|
||||||
|
|
||||||
final newMembre = MembreModel.fromJson(membreData);
|
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);
|
await saveMembre(newMembre);
|
||||||
|
return true;
|
||||||
return newMembre;
|
}
|
||||||
|
return false;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
debugPrint('Erreur lors de la création du membre via l\'API: $e');
|
debugPrint('Erreur lors de la création du membre: $e');
|
||||||
return null;
|
return false;
|
||||||
} finally {
|
} finally {
|
||||||
_isLoading = false;
|
_isLoading = false;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
@@ -183,28 +142,32 @@ class MembreRepository extends ChangeNotifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Mettre à jour un membre via l'API
|
// Mettre à jour un membre via l'API
|
||||||
Future<MembreModel?> updateMembreViaApi(MembreModel membre) async {
|
Future<bool> updateMembre(MembreModel membre) async {
|
||||||
_isLoading = true;
|
_isLoading = true;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final hasConnection = await _apiService.hasInternetConnection();
|
// Préparer les données pour l'API
|
||||||
if (!hasConnection) {
|
final data = membre.toJson();
|
||||||
debugPrint('Pas de connexion Internet, impossible de mettre à jour le membre');
|
|
||||||
return null;
|
// 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
|
return false;
|
||||||
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;
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
debugPrint('Erreur lors de la mise à jour du membre via l\'API: $e');
|
debugPrint('Erreur lors de la mise à jour du membre: $e');
|
||||||
return null;
|
return false;
|
||||||
} finally {
|
} finally {
|
||||||
_isLoading = false;
|
_isLoading = false;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
@@ -217,25 +180,66 @@ class MembreRepository extends ChangeNotifier {
|
|||||||
notifyListeners();
|
notifyListeners();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final hasConnection = await _apiService.hasInternetConnection();
|
// Appeler l'API pour supprimer le membre
|
||||||
if (!hasConnection) {
|
final response = await ApiService.instance.delete('/membres/$id');
|
||||||
debugPrint('Pas de connexion Internet, impossible de supprimer le membre');
|
|
||||||
return false;
|
if (response.statusCode == 200 || response.statusCode == 204) {
|
||||||
|
// Supprimer le membre localement
|
||||||
|
await deleteMembre(id);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Endpoint à adapter selon votre API
|
return false;
|
||||||
await _apiService.delete('/membres/$id');
|
|
||||||
|
|
||||||
// Supprimer localement
|
|
||||||
await deleteMembre(id);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
} catch (e) {
|
} 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;
|
return false;
|
||||||
} finally {
|
} finally {
|
||||||
_isLoading = false;
|
_isLoading = false;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Traitement des données de membres depuis l'API
|
||||||
|
Future<void> 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<dynamic> membresList;
|
||||||
|
if (membresData is List) {
|
||||||
|
membresList = membresData;
|
||||||
|
} else if (membresData is Map && membresData.containsKey('data')) {
|
||||||
|
membresList = membresData['data'] as List<dynamic>;
|
||||||
|
} 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');
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,17 +15,17 @@ class OperationRepository extends ChangeNotifier {
|
|||||||
|
|
||||||
// Méthode pour vérifier si la 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<void> _ensureBoxIsOpen() async {
|
Future<void> _ensureBoxIsOpen() async {
|
||||||
final boxName = AppKeys.operationsBoxName;
|
const boxName = AppKeys.operationsBoxName;
|
||||||
if (!Hive.isBoxOpen(boxName)) {
|
if (!Hive.isBoxOpen(boxName)) {
|
||||||
debugPrint('Ouverture de la boîte $boxName dans OperationRepository...');
|
debugPrint('Ouverture de la boîte $boxName dans OperationRepository...');
|
||||||
await Hive.openBox<OperationModel>(boxName);
|
await Hive.openBox<OperationModel>(boxName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
final ApiService _apiService;
|
|
||||||
|
|
||||||
bool _isLoading = false;
|
bool _isLoading = false;
|
||||||
|
|
||||||
OperationRepository(this._apiService);
|
// Constructeur sans paramètres - utilise ApiService.instance
|
||||||
|
OperationRepository();
|
||||||
|
|
||||||
// Getters
|
// Getters
|
||||||
bool get isLoading => _isLoading;
|
bool get isLoading => _isLoading;
|
||||||
@@ -61,9 +61,7 @@ class OperationRepository extends ChangeNotifier {
|
|||||||
try {
|
try {
|
||||||
for (var operationData in operationsData) {
|
for (var operationData in operationsData) {
|
||||||
final operationJson = operationData as Map<String, dynamic>;
|
final operationJson = operationData as Map<String, dynamic>;
|
||||||
final operationId = operationJson['id'] is String
|
final operationId = operationJson['id'] is String ? int.parse(operationJson['id']) : operationJson['id'] as int;
|
||||||
? int.parse(operationJson['id'])
|
|
||||||
: operationJson['id'] as int;
|
|
||||||
|
|
||||||
// Vérifier si l'opération existe déjà
|
// Vérifier si l'opération existe déjà
|
||||||
OperationModel? existingOperation = getOperationById(operationId);
|
OperationModel? existingOperation = getOperationById(operationId);
|
||||||
@@ -106,13 +104,11 @@ class OperationRepository extends ChangeNotifier {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Appeler l'API pour créer l'opération
|
// 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) {
|
if (response.statusCode == 201 || response.statusCode == 200) {
|
||||||
// Récupérer l'ID de la nouvelle opération
|
// Récupérer l'ID de la nouvelle opération
|
||||||
final operationId = response.data['id'] is String
|
final operationId = response.data['id'] is String ? int.parse(response.data['id']) : response.data['id'] as int;
|
||||||
? int.parse(response.data['id'])
|
|
||||||
: response.data['id'] as int;
|
|
||||||
|
|
||||||
// Créer l'opération localement
|
// Créer l'opération localement
|
||||||
final newOperation = OperationModel(
|
final newOperation = OperationModel(
|
||||||
@@ -161,7 +157,7 @@ class OperationRepository extends ChangeNotifier {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Appeler l'API pour mettre à jour l'opération
|
// 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) {
|
if (response.statusCode == 200) {
|
||||||
// Mettre à jour l'opération localement
|
// Mettre à jour l'opération localement
|
||||||
@@ -195,7 +191,7 @@ class OperationRepository extends ChangeNotifier {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
// Appeler l'API pour supprimer l'opération
|
// 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) {
|
if (response.statusCode == 200 || response.statusCode == 204) {
|
||||||
// Supprimer l'opération localement
|
// Supprimer l'opération localement
|
||||||
|
|||||||
@@ -1,344 +1,141 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
import 'package:geosector_app/core/data/models/passage_model.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/services/api_service.dart';
|
||||||
import 'package:geosector_app/core/constants/app_keys.dart';
|
import 'package:geosector_app/core/constants/app_keys.dart';
|
||||||
|
|
||||||
class PassageRepository extends ChangeNotifier {
|
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
|
// 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
|
// et vérifier qu'elle est ouverte avant accès
|
||||||
Box<PassageModel>? _box;
|
|
||||||
|
|
||||||
Box<PassageModel> get _passageBox {
|
Box<PassageModel> get _passageBox {
|
||||||
if (_box != null && _box!.isOpen) {
|
_ensureBoxIsOpen();
|
||||||
return _box!;
|
return Hive.box<PassageModel>(AppKeys.passagesBoxName);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!Hive.isBoxOpen(AppKeys.passagesBoxName)) {
|
// Stream pour notifier des changements de passages
|
||||||
throw StateError(
|
StreamController<List<PassageModel>>? _passageStreamController;
|
||||||
'La boîte ${AppKeys.passagesBoxName} n\'est pas ouverte. Appelez _ensureBoxIsOpen() avant d\'accéder à la boîte.');
|
|
||||||
|
// Getter pour le stream des passages
|
||||||
|
Stream<List<PassageModel>> get passageStream {
|
||||||
|
_passageStreamController ??= StreamController<List<PassageModel>>.broadcast();
|
||||||
|
return _passageStreamController!.stream;
|
||||||
}
|
}
|
||||||
|
|
||||||
_box = Hive.box<PassageModel>(AppKeys.passagesBoxName);
|
@override
|
||||||
return _box!;
|
void dispose() {
|
||||||
|
_passageStreamController?.close();
|
||||||
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Méthode pour vérifier si la boîte est ouverte et l'ouvrir si nécessaire
|
// ID du secteur par défaut
|
||||||
Future<void> _ensureBoxIsOpen() async {
|
int defaultSectorId = 1;
|
||||||
final boxName = AppKeys.passagesBoxName;
|
|
||||||
|
|
||||||
// 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<PassageModel>(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<PassageModel>(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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
final ApiService _apiService;
|
|
||||||
|
|
||||||
bool _isLoading = false;
|
bool _isLoading = false;
|
||||||
|
|
||||||
PassageRepository(this._apiService);
|
|
||||||
|
|
||||||
// Getters
|
// Getters
|
||||||
bool get isLoading => _isLoading;
|
bool get isLoading => _isLoading;
|
||||||
List<PassageModel> get passages => getAllPassages();
|
List<PassageModel> get passages => getAllPassages();
|
||||||
|
|
||||||
|
// Méthode pour vérifier si la boîte est ouverte et l'ouvrir si nécessaire
|
||||||
|
Future<void> _ensureBoxIsOpen() async {
|
||||||
|
const boxName = AppKeys.passagesBoxName;
|
||||||
|
if (!Hive.isBoxOpen(boxName)) {
|
||||||
|
debugPrint('Ouverture de la boîte $boxName dans PassageRepository...');
|
||||||
|
await Hive.openBox<PassageModel>(boxName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Récupérer tous les passages
|
// Récupérer tous les passages
|
||||||
List<PassageModel> getAllPassages() {
|
List<PassageModel> 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();
|
return _passageBox.values.toList();
|
||||||
} catch (e) {
|
|
||||||
debugPrint('PassageRepository: Erreur dans getAllPassages: $e');
|
|
||||||
return []; // Retourner une liste vide en cas d'erreur
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Récupérer un passage par son ID
|
// Récupérer un passage par son ID
|
||||||
PassageModel? getPassageById(int 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);
|
return _passageBox.get(id);
|
||||||
} catch (e) {
|
|
||||||
debugPrint('PassageRepository: Erreur dans getPassageById: $e');
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Récupérer les passages par secteur
|
// Récupérer les passages par secteur
|
||||||
List<PassageModel> getPassagesBySector(int sectorId) {
|
List<PassageModel> getPassagesBySectorId(int sectorId) {
|
||||||
try {
|
return _passageBox.values.where((passage) => passage.fkSector == sectorId).toList();
|
||||||
// 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<PassageModel> 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 [];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Récupérer les passages par type
|
// Récupérer les passages par type
|
||||||
List<PassageModel> getPassagesByType(int typeId) {
|
List<PassageModel> getPassagesByType(int type) {
|
||||||
try {
|
return _passageBox.values.where((passage) => passage.fkType == type).toList();
|
||||||
// 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 [];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Récupérer les passages par type de règlement
|
// Récupérer les passages par date
|
||||||
List<PassageModel> getPassagesByPaymentType(int paymentTypeId) {
|
List<PassageModel> getPassagesByDate(DateTime date) {
|
||||||
try {
|
return _passageBox.values.where((passage) {
|
||||||
// S'assurer que la boîte est ouverte avant d'y accéder
|
final passageDate = DateTime(
|
||||||
_ensureBoxIsOpen().then((_) {
|
passage.passedAt.year,
|
||||||
debugPrint(
|
passage.passedAt.month,
|
||||||
'PassageRepository: Boîte ouverte avec succès pour getPassagesByPaymentType');
|
passage.passedAt.day,
|
||||||
}).catchError((e) {
|
);
|
||||||
debugPrint(
|
final searchDate = DateTime(date.year, date.month, date.day);
|
||||||
'PassageRepository: Erreur lors de l\'ouverture de la boîte pour getPassagesByPaymentType: $e');
|
return passageDate.isAtSameMomentAs(searchDate);
|
||||||
});
|
}).toList();
|
||||||
|
|
||||||
return _passageBox.values
|
|
||||||
.where((passage) => passage.fkTypeReglement == paymentTypeId)
|
|
||||||
.toList();
|
|
||||||
} catch (e) {
|
|
||||||
debugPrint('PassageRepository: Erreur dans getPassagesByPaymentType: $e');
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sauvegarder un passage
|
// Sauvegarder un passage
|
||||||
Future<void> savePassage(PassageModel passage) async {
|
Future<void> savePassage(PassageModel passage) async {
|
||||||
await _passageBox.put(passage.id, passage);
|
await _passageBox.put(passage.id, passage);
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
|
_notifyPassageStream();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sauvegarder plusieurs passages
|
||||||
|
Future<void> savePassages(List<PassageModel> passages) async {
|
||||||
|
for (final passage in passages) {
|
||||||
|
await _passageBox.put(passage.id, passage);
|
||||||
|
}
|
||||||
|
notifyListeners();
|
||||||
|
_notifyPassageStream();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Supprimer un passage
|
// Supprimer un passage
|
||||||
Future<void> deletePassage(int id) async {
|
Future<void> deletePassage(int id) async {
|
||||||
await _passageBox.delete(id);
|
await _passageBox.delete(id);
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
|
_notifyPassageStream();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Traiter les passages reçus de l'API
|
// Notifier le stream des changements
|
||||||
Future<void> processPassagesFromApi(List<dynamic> passagesData) async {
|
void _notifyPassageStream() {
|
||||||
_isLoading = true;
|
_passageStreamController?.add(getAllPassages());
|
||||||
notifyListeners();
|
|
||||||
|
|
||||||
try {
|
|
||||||
for (var passageData in passagesData) {
|
|
||||||
final passageJson = passageData as Map<String, dynamic>;
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Créer un nouveau passage
|
// Créer un passage via l'API
|
||||||
Future<bool> createPassage({
|
Future<bool> createPassage(PassageModel passage) async {
|
||||||
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 {
|
|
||||||
_isLoading = true;
|
_isLoading = true;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Préparer les données pour l'API
|
// Préparer les données pour l'API
|
||||||
final Map<String, dynamic> data = {
|
final data = passage.toJson();
|
||||||
'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,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Appeler l'API pour créer le passage
|
// 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) {
|
if (response.statusCode == 201 || response.statusCode == 200) {
|
||||||
// Récupérer l'ID du nouveau passage
|
// Récupérer l'ID du nouveau passage
|
||||||
final passageId = response.data['id'] is String
|
final passageId = response.data['id'] is String ? int.parse(response.data['id']) : response.data['id'] as int;
|
||||||
? int.parse(response.data['id'])
|
|
||||||
: response.data['id'] as int;
|
|
||||||
|
|
||||||
// Créer le modèle local
|
// Créer le passage localement avec l'ID retourné par l'API
|
||||||
final newPassage = PassageModel(
|
final newPassage = passage.copyWith(
|
||||||
id: passageId,
|
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(),
|
lastSyncedAt: DateTime.now(),
|
||||||
isActive: true,
|
|
||||||
isSynced: true,
|
isSynced: true,
|
||||||
);
|
);
|
||||||
|
|
||||||
await savePassage(newPassage);
|
await savePassage(newPassage);
|
||||||
return true;
|
return true;
|
||||||
} else {
|
|
||||||
debugPrint(
|
|
||||||
'Erreur lors de la création du passage: ${response.statusMessage}');
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
debugPrint('Erreur lors de la création du passage: $e');
|
debugPrint('Erreur lors de la création du passage: $e');
|
||||||
return false;
|
return false;
|
||||||
@@ -348,51 +145,30 @@ class PassageRepository extends ChangeNotifier {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mettre à jour un passage existant
|
// Mettre à jour un passage via l'API
|
||||||
Future<bool> updatePassage(PassageModel passage) async {
|
Future<bool> updatePassage(PassageModel passage) async {
|
||||||
_isLoading = true;
|
_isLoading = true;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Préparer les données pour l'API
|
// Préparer les données pour l'API
|
||||||
final Map<String, dynamic> data = passage.toJson();
|
final data = passage.toJson();
|
||||||
|
|
||||||
// Appeler l'API pour mettre à jour le passage
|
// Appeler l'API pour mettre à jour le passage
|
||||||
final response =
|
final response = await ApiService.instance.put('/passages/${passage.id}', data: data);
|
||||||
await _apiService.put('/passages/${passage.id}', data: data);
|
|
||||||
|
|
||||||
if (response.statusCode == 200) {
|
if (response.statusCode == 200) {
|
||||||
// Mettre à jour le modèle local
|
// Mettre à jour le passage localement
|
||||||
final updatedPassage = passage.copyWith(
|
final updatedPassage = passage.copyWith(
|
||||||
lastSyncedAt: DateTime.now(),
|
lastSyncedAt: DateTime.now(),
|
||||||
isSynced: true,
|
isSynced: true,
|
||||||
);
|
);
|
||||||
|
|
||||||
await savePassage(updatedPassage);
|
await savePassage(updatedPassage);
|
||||||
return true;
|
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) {
|
} catch (e) {
|
||||||
debugPrint('Erreur lors de la mise à jour du passage: $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;
|
return false;
|
||||||
} finally {
|
} finally {
|
||||||
_isLoading = false;
|
_isLoading = false;
|
||||||
@@ -400,103 +176,107 @@ class PassageRepository extends ChangeNotifier {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Supprimer un passage via l'API
|
||||||
|
Future<bool> deletePassageViaApi(int id) async {
|
||||||
|
_isLoading = true;
|
||||||
|
notifyListeners();
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Appeler l'API pour supprimer le passage
|
||||||
|
final response = await ApiService.instance.delete('/passages/$id');
|
||||||
|
|
||||||
|
if (response.statusCode == 200 || response.statusCode == 204) {
|
||||||
|
// Supprimer le passage localement
|
||||||
|
await deletePassage(id);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
} catch (e) {
|
||||||
|
debugPrint('Erreur lors de la suppression du passage: $e');
|
||||||
|
return false;
|
||||||
|
} finally {
|
||||||
|
_isLoading = false;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Traitement des données de passages depuis l'API
|
||||||
|
Future<void> processPassagesData(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<dynamic> passagesList;
|
||||||
|
if (passagesData is List) {
|
||||||
|
passagesList = passagesData;
|
||||||
|
} else if (passagesData is Map && passagesData.containsKey('data')) {
|
||||||
|
passagesList = passagesData['data'] as List<dynamic>;
|
||||||
|
} 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');
|
||||||
|
notifyListeners();
|
||||||
|
_notifyPassageStream();
|
||||||
|
} catch (e) {
|
||||||
|
debugPrint('Erreur lors du traitement des passages: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Synchroniser tous les passages non synchronisés
|
// Synchroniser tous les passages non synchronisés
|
||||||
Future<void> syncUnsyncedPassages() async {
|
Future<void> syncUnsyncedPassages() async {
|
||||||
try {
|
final unsyncedPassages = _passageBox.values.where((passage) => !passage.isSynced).toList();
|
||||||
final hasConnection = await _apiService.hasInternetConnection();
|
|
||||||
|
|
||||||
if (!hasConnection) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
final unsyncedPassages =
|
|
||||||
_passageBox.values.where((passage) => !passage.isSynced).toList();
|
|
||||||
|
|
||||||
if (unsyncedPassages.isEmpty) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_isLoading = true;
|
|
||||||
notifyListeners();
|
|
||||||
|
|
||||||
for (final passage in unsyncedPassages) {
|
for (final passage in unsyncedPassages) {
|
||||||
try {
|
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);
|
await updatePassage(passage);
|
||||||
}
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
debugPrint(
|
debugPrint('Erreur lors de la synchronisation du passage ${passage.id}: $e');
|
||||||
'Erreur lors de la synchronisation du passage ${passage.id}: $e');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
|
||||||
debugPrint('Erreur lors de la synchronisation des passages: $e');
|
|
||||||
} finally {
|
|
||||||
_isLoading = false;
|
|
||||||
notifyListeners();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Récupérer les passages depuis l'API
|
// Statistiques
|
||||||
Future<void> fetchPassages() async {
|
Map<String, int> getPassageStatistics() {
|
||||||
try {
|
final allPassages = getAllPassages();
|
||||||
final hasConnection = await _apiService.hasInternetConnection();
|
|
||||||
|
|
||||||
if (!hasConnection) {
|
return {
|
||||||
return;
|
'total': allPassages.length,
|
||||||
}
|
'effectues': allPassages.where((p) => p.fkType == 1).length,
|
||||||
|
'a_finaliser': allPassages.where((p) => p.fkType == 2).length,
|
||||||
_isLoading = true;
|
'refuses': allPassages.where((p) => p.fkType == 3).length,
|
||||||
notifyListeners();
|
'dons': allPassages.where((p) => p.fkType == 4).length,
|
||||||
|
'lots': allPassages.where((p) => p.fkType == 5).length,
|
||||||
final response = await _apiService.get('/passages');
|
'maisons_vides': allPassages.where((p) => p.fkType == 6).length,
|
||||||
|
};
|
||||||
if (response.statusCode == 200) {
|
|
||||||
final List<dynamic> passagesData = response.data;
|
|
||||||
await processPassagesFromApi(passagesData);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
debugPrint('Erreur lors de la récupération des passages: $e');
|
|
||||||
} finally {
|
|
||||||
_isLoading = false;
|
|
||||||
notifyListeners();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Vider tous les passages
|
// Vider tous les passages
|
||||||
Future<void> clearAllPassages() async {
|
Future<void> clearAllPassages() async {
|
||||||
await _passageBox.clear();
|
await _passageBox.clear();
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
|
_notifyPassageStream();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,81 +1,121 @@
|
|||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
import 'package:geosector_app/core/data/models/sector_model.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/services/api_service.dart';
|
||||||
import 'package:geosector_app/core/constants/app_keys.dart';
|
import 'package:geosector_app/core/constants/app_keys.dart';
|
||||||
|
|
||||||
class SectorRepository {
|
class SectorRepository extends ChangeNotifier {
|
||||||
final ApiService _apiService;
|
// Constructeur sans paramètres - utilise ApiService.instance
|
||||||
|
SectorRepository();
|
||||||
SectorRepository(this._apiService);
|
|
||||||
|
|
||||||
// Utiliser un getter lazy pour n'accéder à la boîte que lorsque nécessaire
|
// 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
|
// et vérifier qu'elle est ouverte avant accès
|
||||||
Box<SectorModel> get _sectorsBox {
|
Box<SectorModel> get _sectorBox {
|
||||||
_ensureBoxIsOpen();
|
_ensureBoxIsOpen();
|
||||||
return Hive.box<SectorModel>(AppKeys.sectorsBoxName);
|
return Hive.box<SectorModel>(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
|
// Méthode pour vérifier si la boîte est ouverte et l'ouvrir si nécessaire
|
||||||
Future<void> _ensureBoxIsOpen() async {
|
Future<void> _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)) {
|
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<SectorModel>(boxName);
|
await Hive.openBox<SectorModel>(boxName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Récupérer tous les secteurs depuis la base de données locale
|
// Récupérer tous les secteurs
|
||||||
List<SectorModel> getAllSectors() {
|
List<SectorModel> getAllSectors() {
|
||||||
return _sectorsBox.values.toList();
|
return _sectorBox.values.toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Récupérer un secteur par son ID
|
// Récupérer un secteur par son ID
|
||||||
SectorModel? getSectorById(int id) {
|
SectorModel? getSectorById(int id) {
|
||||||
try {
|
return _sectorBox.get(id);
|
||||||
return _sectorsBox.values.firstWhere(
|
|
||||||
(sector) => sector.id == id,
|
|
||||||
);
|
|
||||||
} catch (e) {
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sauvegarder un secteur
|
||||||
|
Future<void> saveSector(SectorModel sector) async {
|
||||||
|
await _sectorBox.put(sector.id, sector);
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Supprimer un secteur
|
||||||
|
Future<void> deleteSector(int id) async {
|
||||||
|
await _sectorBox.delete(id);
|
||||||
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sauvegarder les secteurs dans la base de données locale
|
// Sauvegarder les secteurs dans la base de données locale
|
||||||
Future<void> saveSectors(List<SectorModel> sectors) async {
|
Future<void> saveSectors(List<SectorModel> sectors) async {
|
||||||
// Vider la box avant d'ajouter les nouveaux secteurs
|
// Vider la box avant d'ajouter les nouveaux secteurs
|
||||||
await _sectorsBox.clear();
|
await _sectorBox.clear();
|
||||||
|
|
||||||
// Ajouter les nouveaux secteurs
|
// Ajouter les nouveaux secteurs
|
||||||
for (final sector in sectors) {
|
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<void> 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<dynamic> sectorsList;
|
||||||
|
if (sectorsData is List) {
|
||||||
|
sectorsList = sectorsData;
|
||||||
|
} else if (sectorsData is Map && sectorsData.containsKey('data')) {
|
||||||
|
sectorsList = sectorsData['data'] as List<dynamic>;
|
||||||
|
} 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');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ajouter ou mettre à jour un secteur
|
debugPrint('$count secteurs traités et stockés');
|
||||||
Future<void> saveSector(SectorModel sector) async {
|
notifyListeners();
|
||||||
await _sectorsBox.put(sector.id, sector);
|
} catch (e) {
|
||||||
|
debugPrint('Erreur lors du traitement des secteurs: $e');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Supprimer un secteur
|
|
||||||
Future<void> deleteSector(int id) async {
|
|
||||||
await _sectorsBox.delete(id);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Récupérer les secteurs depuis l'API
|
// Récupérer les secteurs depuis l'API
|
||||||
Future<List<SectorModel>> fetchSectorsFromApi() async {
|
Future<List<SectorModel>> fetchSectorsFromApi() async {
|
||||||
try {
|
try {
|
||||||
final response = await _apiService.get(AppKeys.sectorsEndpoint);
|
final response = await ApiService.instance.get(AppKeys.sectorsEndpoint);
|
||||||
final Map<String, dynamic> responseData = response as Map<String, dynamic>;
|
final Map<String, dynamic> responseData = response as Map<String, dynamic>;
|
||||||
|
|
||||||
if (responseData['status'] == 'success' && responseData['sectors'] != null) {
|
if (responseData['status'] == 'success' && responseData['sectors'] != null) {
|
||||||
final List<dynamic> sectorsJson = responseData['sectors'];
|
final List<dynamic> sectorsJson = responseData['sectors'];
|
||||||
final List<SectorModel> sectors = sectorsJson
|
final List<SectorModel> sectors = sectorsJson.map((json) => SectorModel.fromJson(json)).toList();
|
||||||
.map((json) => SectorModel.fromJson(json))
|
|
||||||
.toList();
|
|
||||||
|
|
||||||
// Sauvegarder les secteurs localement
|
// Sauvegarder les secteurs localement
|
||||||
await saveSectors(sectors);
|
await saveSectors(sectors);
|
||||||
|
|
||||||
return sectors;
|
return sectors;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -89,7 +129,7 @@ class SectorRepository {
|
|||||||
// Créer un nouveau secteur via l'API
|
// Créer un nouveau secteur via l'API
|
||||||
Future<SectorModel?> createSector(SectorModel sector) async {
|
Future<SectorModel?> createSector(SectorModel sector) async {
|
||||||
try {
|
try {
|
||||||
final response = await _apiService.post(
|
final response = await ApiService.instance.post(
|
||||||
AppKeys.sectorsEndpoint,
|
AppKeys.sectorsEndpoint,
|
||||||
data: sector.toJson(),
|
data: sector.toJson(),
|
||||||
);
|
);
|
||||||
@@ -110,7 +150,7 @@ class SectorRepository {
|
|||||||
// Mettre à jour un secteur via l'API
|
// Mettre à jour un secteur via l'API
|
||||||
Future<SectorModel?> updateSector(SectorModel sector) async {
|
Future<SectorModel?> updateSector(SectorModel sector) async {
|
||||||
try {
|
try {
|
||||||
final response = await _apiService.put(
|
final response = await ApiService.instance.put(
|
||||||
'${AppKeys.sectorsEndpoint}/${sector.id}',
|
'${AppKeys.sectorsEndpoint}/${sector.id}',
|
||||||
data: sector.toJson(),
|
data: sector.toJson(),
|
||||||
);
|
);
|
||||||
@@ -131,7 +171,7 @@ class SectorRepository {
|
|||||||
// Supprimer un secteur via l'API
|
// Supprimer un secteur via l'API
|
||||||
Future<bool> deleteSectorFromApi(int id) async {
|
Future<bool> deleteSectorFromApi(int id) async {
|
||||||
try {
|
try {
|
||||||
final response = await _apiService.delete(
|
final response = await ApiService.instance.delete(
|
||||||
'${AppKeys.sectorsEndpoint}/$id',
|
'${AppKeys.sectorsEndpoint}/$id',
|
||||||
);
|
);
|
||||||
final Map<String, dynamic> responseData = response as Map<String, dynamic>;
|
final Map<String, dynamic> responseData = response as Map<String, dynamic>;
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -342,38 +342,6 @@ class ApiService {
|
|||||||
debugPrint('GEOSECTOR 🔗 Environnement: $env, API: $_baseUrl');
|
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<String, String>.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
|
// Définir l'ID de session
|
||||||
void setSessionId(String? sessionId) {
|
void setSessionId(String? sessionId) {
|
||||||
_sessionId = sessionId;
|
_sessionId = sessionId;
|
||||||
@@ -397,7 +365,7 @@ class ApiService {
|
|||||||
// Vérifier la connectivité réseau
|
// Vérifier la connectivité réseau
|
||||||
Future<bool> hasInternetConnection() async {
|
Future<bool> hasInternetConnection() async {
|
||||||
final connectivityResult = await (Connectivity().checkConnectivity());
|
final connectivityResult = await (Connectivity().checkConnectivity());
|
||||||
return connectivityResult != ConnectivityResult.none;
|
return connectivityResult.contains(ConnectivityResult.none) == false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Méthode POST générique
|
// Méthode POST générique
|
||||||
|
|||||||
667
app/lib/core/services/data_loading_service.dart
Normal file
667
app/lib/core/services/data_loading_service.dart
Normal file
@@ -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<OperationModel> get _operationBox => Hive.box<OperationModel>(AppKeys.operationsBoxName);
|
||||||
|
Box<SectorModel> get _sectorBox => Hive.box<SectorModel>(AppKeys.sectorsBoxName);
|
||||||
|
Box<PassageModel> get _passageBox => Hive.box<PassageModel>(AppKeys.passagesBoxName);
|
||||||
|
Box<MembreModel> get _membreBox => Hive.box<MembreModel>(AppKeys.membresBoxName);
|
||||||
|
Box<UserSectorModel> get _userSectorBox => Hive.box<UserSectorModel>(AppKeys.userSectorBoxName);
|
||||||
|
Box<AmicaleModel> get _amicaleBox => Hive.box<AmicaleModel>(AppKeys.amicaleBoxName);
|
||||||
|
Box<ConversationModel> get _chatConversationBox => Hive.box<ConversationModel>(AppKeys.chatConversationsBoxName);
|
||||||
|
Box<MessageModel> get _chatMessageBox => Hive.box<MessageModel>(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<void> 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<void> processLoginData(Map<String, dynamic> 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<void> 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<void> _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<void> _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<void> _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<void> _ensureBoxIsOpen(String boxName) async {
|
||||||
|
try {
|
||||||
|
if (!Hive.isBoxOpen(boxName)) {
|
||||||
|
debugPrint('Ouverture de la boîte $boxName...');
|
||||||
|
|
||||||
|
switch (boxName) {
|
||||||
|
case AppKeys.passagesBoxName:
|
||||||
|
await Hive.openBox<PassageModel>(boxName);
|
||||||
|
break;
|
||||||
|
case AppKeys.operationsBoxName:
|
||||||
|
await Hive.openBox<OperationModel>(boxName);
|
||||||
|
break;
|
||||||
|
case AppKeys.sectorsBoxName:
|
||||||
|
await Hive.openBox<SectorModel>(boxName);
|
||||||
|
break;
|
||||||
|
case AppKeys.membresBoxName:
|
||||||
|
await Hive.openBox<MembreModel>(boxName);
|
||||||
|
break;
|
||||||
|
case AppKeys.userSectorBoxName:
|
||||||
|
await Hive.openBox<UserSectorModel>(boxName);
|
||||||
|
break;
|
||||||
|
case AppKeys.chatConversationsBoxName:
|
||||||
|
await Hive.openBox<ConversationModel>(boxName);
|
||||||
|
break;
|
||||||
|
case AppKeys.chatMessagesBoxName:
|
||||||
|
await Hive.openBox<MessageModel>(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<void> _processClients(dynamic clientsData) async {
|
||||||
|
try {
|
||||||
|
debugPrint('👥 Traitement des clients...');
|
||||||
|
|
||||||
|
List<dynamic> clientsList;
|
||||||
|
if (clientsData is List) {
|
||||||
|
clientsList = clientsData;
|
||||||
|
} else if (clientsData is Map && clientsData.containsKey('data')) {
|
||||||
|
clientsList = clientsData['data'] as List<dynamic>;
|
||||||
|
} 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<void> _processOperations(dynamic operationsData) async {
|
||||||
|
try {
|
||||||
|
debugPrint('⚙️ Traitement des opérations...');
|
||||||
|
|
||||||
|
if (operationsData == null) {
|
||||||
|
debugPrint('ℹ️ Aucune donnée d\'opération à traiter');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<dynamic> operationsList;
|
||||||
|
if (operationsData is List) {
|
||||||
|
operationsList = operationsData;
|
||||||
|
} else if (operationsData is Map && operationsData.containsKey('data')) {
|
||||||
|
operationsList = operationsData['data'] as List<dynamic>;
|
||||||
|
} 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<void> _processSectors(dynamic sectorsData) async {
|
||||||
|
try {
|
||||||
|
debugPrint('📍 Traitement des secteurs...');
|
||||||
|
|
||||||
|
if (sectorsData == null) {
|
||||||
|
debugPrint('ℹ️ Aucune donnée de secteur à traiter');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<dynamic> sectorsList;
|
||||||
|
if (sectorsData is List) {
|
||||||
|
sectorsList = sectorsData;
|
||||||
|
} else if (sectorsData is Map && sectorsData.containsKey('data')) {
|
||||||
|
sectorsList = sectorsData['data'] as List<dynamic>;
|
||||||
|
} 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<void> _processPassages(dynamic passagesData) async {
|
||||||
|
try {
|
||||||
|
debugPrint('🚶 Traitement des passages...');
|
||||||
|
|
||||||
|
if (passagesData == null) {
|
||||||
|
debugPrint('ℹ️ Aucune donnée de passage à traiter');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<dynamic> passagesList;
|
||||||
|
if (passagesData is List) {
|
||||||
|
passagesList = passagesData;
|
||||||
|
} else if (passagesData is Map && passagesData.containsKey('data')) {
|
||||||
|
passagesList = passagesData['data'] as List<dynamic>;
|
||||||
|
} 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<void> _processMembres(dynamic membresData) async {
|
||||||
|
try {
|
||||||
|
debugPrint('👤 Traitement des membres...');
|
||||||
|
|
||||||
|
if (membresData == null) {
|
||||||
|
debugPrint('ℹ️ Aucune donnée de membre à traiter');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<dynamic> membresList;
|
||||||
|
if (membresData is List) {
|
||||||
|
membresList = membresData;
|
||||||
|
} else if (membresData is Map && membresData.containsKey('data')) {
|
||||||
|
membresList = membresData['data'] as List<dynamic>;
|
||||||
|
} 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<void> _processUserSectors(dynamic userSectorsData) async {
|
||||||
|
try {
|
||||||
|
debugPrint('🔗 Traitement des associations utilisateurs-secteurs...');
|
||||||
|
|
||||||
|
if (userSectorsData == null) {
|
||||||
|
debugPrint('ℹ️ Aucune association utilisateur-secteur à traiter');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<dynamic> userSectorsList;
|
||||||
|
if (userSectorsData is List) {
|
||||||
|
userSectorsList = userSectorsData;
|
||||||
|
} else if (userSectorsData is Map && userSectorsData.containsKey('data')) {
|
||||||
|
userSectorsList = userSectorsData['data'] as List<dynamic>;
|
||||||
|
} 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<void> _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<void> _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<void> _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<String, int> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,6 +3,9 @@ import 'package:flutter/services.dart';
|
|||||||
import 'package:flutter/foundation.dart' show kIsWeb;
|
import 'package:flutter/foundation.dart' show kIsWeb;
|
||||||
import 'package:flutter_web_plugins/url_strategy.dart';
|
import 'package:flutter_web_plugins/url_strategy.dart';
|
||||||
import 'package:geosector_app/core/services/app_info_service.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:geosector_app/app.dart';
|
||||||
import 'package:hive_flutter/hive_flutter.dart';
|
import 'package:hive_flutter/hive_flutter.dart';
|
||||||
import 'package:geosector_app/core/data/models/user_model.dart';
|
import 'package:geosector_app/core/data/models/user_model.dart';
|
||||||
@@ -51,10 +54,20 @@ void main() async {
|
|||||||
/// Initialise les services essentiels
|
/// Initialise les services essentiels
|
||||||
Future<void> _initializeServices() async {
|
Future<void> _initializeServices() async {
|
||||||
try {
|
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();
|
await AppInfoService.initialize();
|
||||||
debugPrint('Services initialisés avec succès');
|
debugPrint('✅ Tous les services initialisés avec succès');
|
||||||
} catch (e) {
|
} 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<bool> _initializeHive() async {
|
|||||||
// Ouvrir uniquement les boîtes essentielles au démarrage
|
// Ouvrir uniquement les boîtes essentielles au démarrage
|
||||||
await _openEssentialHiveBoxes();
|
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');
|
debugPrint('Hive initialisé avec succès');
|
||||||
return true;
|
return true;
|
||||||
} catch (e) {
|
} 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<void> _openEssentialHiveBoxes() async {
|
Future<void> _openEssentialHiveBoxes() async {
|
||||||
final boxesToOpen = [
|
final boxesToOpen = [
|
||||||
{'name': AppKeys.userBoxName, 'type': 'UserModel'},
|
{'name': AppKeys.userBoxName, 'type': 'UserModel'},
|
||||||
@@ -234,51 +252,4 @@ Future<bool> _doesBoxExist(String boxName) async {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
return false;
|
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<UserModel>(boxName);
|
|
||||||
break;
|
|
||||||
case 'AmicaleModel':
|
|
||||||
await Hive.openBox<AmicaleModel>(boxName);
|
|
||||||
break;
|
|
||||||
case 'ClientModel':
|
|
||||||
await Hive.openBox<ClientModel>(boxName);
|
|
||||||
break;
|
|
||||||
case 'ConversationModel':
|
|
||||||
await Hive.openBox<ConversationModel>(boxName);
|
|
||||||
break;
|
|
||||||
case 'MessageModel':
|
|
||||||
await Hive.openBox<MessageModel>(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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user