feat: création services singleton et renommage Box

Services créés:
 CurrentUserService singleton pour utilisateur connecté
 CurrentAmicaleService singleton pour amicale courante
 ApiService transformé en singleton

Box Hive:
 Renommage users -> user (plus logique)
 Migration automatique des données
 Services intégrés dans main.dart

État: Services créés, prêt pour refactorisation repositories
This commit is contained in:
d6soft
2025-06-05 17:02:11 +02:00
parent e5ab857913
commit 86a9a35594
32 changed files with 10561 additions and 9982 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1775,7 +1775,6 @@ file:///Users/pierre/dev/geosector/app/lib/core/repositories/client_repository.d
file:///Users/pierre/dev/geosector/app/lib/core/repositories/membre_repository.dart
file:///Users/pierre/dev/geosector/app/lib/core/repositories/operation_repository.dart
file:///Users/pierre/dev/geosector/app/lib/core/repositories/passage_repository.dart
file:///Users/pierre/dev/geosector/app/lib/core/repositories/region_repository.dart
file:///Users/pierre/dev/geosector/app/lib/core/repositories/sector_repository.dart
file:///Users/pierre/dev/geosector/app/lib/core/repositories/user_repository.dart
file:///Users/pierre/dev/geosector/app/lib/core/services/api_service.dart

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1774,7 +1774,6 @@ file:///Users/pierre/dev/geosector/app/lib/core/repositories/client_repository.d
file:///Users/pierre/dev/geosector/app/lib/core/repositories/membre_repository.dart
file:///Users/pierre/dev/geosector/app/lib/core/repositories/operation_repository.dart
file:///Users/pierre/dev/geosector/app/lib/core/repositories/passage_repository.dart
file:///Users/pierre/dev/geosector/app/lib/core/repositories/region_repository.dart
file:///Users/pierre/dev/geosector/app/lib/core/repositories/sector_repository.dart
file:///Users/pierre/dev/geosector/app/lib/core/repositories/user_repository.dart
file:///Users/pierre/dev/geosector/app/lib/core/services/api_service.dart

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -57,10 +57,10 @@ echo "$(date): Début refactorisation singletons" >> refactoring.log
**Actions** :
- [ ] Créer la branche `singletons`
- [ ] Sauvegarder les fichiers critiques
- [ ] Documenter le début de la refactorisation
- [ ] Commit initial de la branche
- [x] Créer la branche `singletons`
- [x] Sauvegarder les fichiers critiques
- [x] Documenter le début de la refactorisation
- [x] Commit initial de la branche
```bash
git add .
@@ -87,21 +87,21 @@ grep -r "usersBoxName" app/lib --include="*.dart" > audit_usersbox.txt
**Actions** :
- [ ] Lister tous les fichiers utilisant ApiService
- [ ] Identifier les patterns d'injection actuels
- [ ] Noter les accès aux données utilisateur/amicale
- [ ] Documenter les méthodes utilisées
- [ ] Analyser les Box Hive users/amicale
- [ ] Identifier toutes les occurrences de "usersBoxName"
- [x] Lister tous les fichiers utilisant ApiService
- [x] Identifier les patterns d'injection actuels
- [x] Noter les accès aux données utilisateur/amicale
- [x] Documenter les méthodes utilisées
- [x] Analyser les Box Hive users/amicale
- [x] Identifier toutes les occurrences de "usersBoxName"
#### Tâche 1.2: Modification app_keys.dart pour renommage Box (10 min)
**Fichier à modifier** : `app/lib/core/constants/app_keys.dart`
**Actions** :
- [ ] Changer `usersBoxName` en `userBoxName`
- [ ] Ajouter une constante de migration si nécessaire
- [ ] Documenter le changement
- [x] Changer `usersBoxName` en `userBoxName`
- [x] Ajouter une constante de migration si nécessaire
- [x] Documenter le changement
**Code à modifier** :
@@ -134,8 +134,8 @@ static const String usersBoxNameOld = 'users'; // Pour migration si nécessaire
**Fichier à modifier** : `app/lib/main.dart`
**Actions** :
- [ ] Remplacer `AppKeys.usersBoxName` par `AppKeys.userBoxName`
- [ ] Modifier l'ouverture de la Box dans `_openEssentialHiveBoxes()`
- [x] Remplacer `AppKeys.usersBoxName` par `AppKeys.userBoxName`
- [x] Modifier l'ouverture de la Box dans `_openEssentialHiveBoxes()`
- [ ] Ajouter logique de migration depuis l'ancienne Box si nécessaire
**Code à modifier** :
@@ -180,9 +180,9 @@ Future<void> _openEssentialHiveBoxes() async {
**Actions** :
- [ ] Remplacer toutes les occurrences de `AppKeys.usersBoxName` par `AppKeys.userBoxName`
- [ ] Modifier les getters de Box
- [ ] Tester que la compilation passe
- [x] Remplacer toutes les occurrences de `AppKeys.usersBoxName` par `AppKeys.userBoxName`
- [x] Modifier les getters de Box
- [x] Tester que la compilation passe
**Code à modifier** :

0
app/audit_apiservice.txt Normal file
View File

View File

View File

View File

0
app/audit_usersbox.txt Normal file
View File

View File

@@ -1 +1 @@
{"guid":"dc4b70c03e8043e50e38f2068887b1d4","name":"Pods","path":"/Users/pierre/dev/geosector/app/ios/Pods/Pods.xcodeproj/project.xcworkspace","projects":["PROJECT@v11_mod=0987d131841f12618fa22c05d7871702_hash=bfdfe7dc352907fc980b868725387e98plugins=1OJSG6M1FOV3XYQCBH7Z29RZ0FPR9XDE1"]}
{"guid":"dc4b70c03e8043e50e38f2068887b1d4","name":"Pods","path":"/Users/pierre/dev/geosector/app/ios/Pods/Pods.xcodeproj/project.xcworkspace","projects":["PROJECT@v11_mod=9c247933552af22255bf791d596f2dce_hash=bfdfe7dc352907fc980b868725387e98plugins=1OJSG6M1FOV3XYQCBH7Z29RZ0FPR9XDE1"]}

View File

@@ -39,6 +39,6 @@ _flutter.buildConfig = {"engineRevision":"1425e5e9ec5eeb4f225c401d8db69b860e0fde
_flutter.loader.load({
serviceWorkerSettings: {
serviceWorkerVersion: "2020305023"
serviceWorkerVersion: "3087561979"
}
});

View File

@@ -3,13 +3,13 @@ const MANIFEST = 'flutter-app-manifest';
const TEMP = 'flutter-temp-cache';
const CACHE_NAME = 'flutter-app-cache';
const RESOURCES = {"flutter_bootstrap.js": "aac8cb24376ac6215b72a88087969ea7",
const RESOURCES = {"flutter_bootstrap.js": "1dbbc20d0a6216228635de38eb5222a5",
"version.json": "727f6f584c125faac83c6d2a4c96fb3d",
"index.html": "2aab03d10fea3b608e3eddc0fc0077e5",
"/": "2aab03d10fea3b608e3eddc0fc0077e5",
"favicon-64.png": "259540a3217e969237530444ca0eaed3",
"favicon-16.png": "106142fb24eba190e475dbe6513cc9ff",
"main.dart.js": "00dc118c0abed215458c35d4ba5a89b1",
"main.dart.js": "7530a148b4d52ca92f4706f660f16907",
"flutter.js": "83d881c1dbb6d6bcd6b42e274605b69c",
"favicon.png": "21510778ead066ac826ad69302400773",
"icons/Icon-192.png": "f36879dd176101fac324b68793e4683c",

File diff suppressed because one or more lines are too long

View File

@@ -1,13 +1,15 @@
/// Fichier contenant toutes les constantes utilisées dans l'application
/// Centralise les clés, noms de boîtes Hive, et autres constantes
/// pour faciliter la maintenance et éviter les erreurs de frappe
library;
import 'package:flutter/foundation.dart' show kIsWeb;
import 'package:flutter/material.dart';
class AppKeys {
// Noms des boîtes Hive
static const String usersBoxName = 'users';
static const String userBoxName = 'user';
static const String usersBoxNameOld = 'users';
static const String amicaleBoxName = 'amicale';
static const String clientsBoxName = 'clients';
static const String operationsBoxName = 'operations';
@@ -49,12 +51,9 @@ class AppKeys {
static const Duration sessionDefaultExpiry = Duration(days: 7);
// Clés API externes
static const String mapboxApiKeyDev =
'pk.eyJ1IjoicHZkNnNvZnQiLCJhIjoiY21hanVmNjN5MTM5djJtczdsMW92cjQ0ciJ9.pUCMuvWPB3cuBaPh4ywTAw';
static const String mapboxApiKeyRec =
'pk.eyJ1IjoicHZkNnNvZnQiLCJhIjoiY21hanVlZ3FiMGx0NDJpc2k4YnkxaWZ2dSJ9.OqGJtjlWRgB4fIjECCB8WA';
static const String mapboxApiKeyProd =
'pk.eyJ1IjoicHZkNnNvZnQiLCJhIjoiY204dTNhNmd0MGV1ZzJqc2pnNnB0NjYxdSJ9.TA5Mvliyn91Oi01F_2Yuxw';
static const String mapboxApiKeyDev = 'pk.eyJ1IjoicHZkNnNvZnQiLCJhIjoiY21hanVmNjN5MTM5djJtczdsMW92cjQ0ciJ9.pUCMuvWPB3cuBaPh4ywTAw';
static const String mapboxApiKeyRec = 'pk.eyJ1IjoicHZkNnNvZnQiLCJhIjoiY21hanVlZ3FiMGx0NDJpc2k4YnkxaWZ2dSJ9.OqGJtjlWRgB4fIjECCB8WA';
static const String mapboxApiKeyProd = 'pk.eyJ1IjoicHZkNnNvZnQiLCJhIjoiY204dTNhNmd0MGV1ZzJqc2pnNnB0NjYxdSJ9.TA5Mvliyn91Oi01F_2Yuxw';
// Méthode pour obtenir la clé API Mapbox en fonction de l'environnement actuel
static String getMapboxApiKey(String environment) {

View File

@@ -49,9 +49,8 @@ class UserRepository extends ChangeNotifier {
}
// Utilisation de getters lazy pour n'accéder aux boîtes que lorsque nécessaire
Box<UserModel> get _userBox => Hive.box<UserModel>(AppKeys.usersBoxName);
Box<AmicaleModel> get _amicaleBox =>
Hive.box<AmicaleModel>(AppKeys.amicaleBoxName);
Box<UserModel> get _userBox => Hive.box<UserModel>(AppKeys.userBoxName);
Box<AmicaleModel> get _amicaleBox => Hive.box<AmicaleModel>(AppKeys.amicaleBoxName);
// Getters pour les autres boîtes qui vérifient si elles sont ouvertes avant accès
Box<OperationModel> get _operationBox {
@@ -118,10 +117,7 @@ class UserRepository extends ChangeNotifier {
bool _isLoading = false;
UserRepository(this._apiService,
{SyncService? syncService,
OperationRepository? operationRepository,
SectorRepository? sectorRepository,
PassageRepository? passageRepository})
{SyncService? syncService, OperationRepository? operationRepository, SectorRepository? sectorRepository, PassageRepository? passageRepository})
: _syncService = syncService,
_operationRepository = operationRepository,
_sectorRepository = sectorRepository,
@@ -160,7 +156,7 @@ class UserRepository extends ChangeNotifier {
if (user.role is String) {
return int.tryParse(user.role as String) ?? 1;
} else {
return user.role as int;
return user.role;
}
}
@@ -168,15 +164,13 @@ class UserRepository extends ChangeNotifier {
UserModel? _getCurrentUserFromStorage() {
try {
// Vérifier d'abord si la boîte est ouverte
if (!Hive.isBoxOpen(AppKeys.usersBoxName)) {
if (!Hive.isBoxOpen(AppKeys.userBoxName)) {
debugPrint('Boîte users non ouverte, tentative d\'ouverture...');
return null; // Retourner null plutôt que d'essayer d'ouvrir ici
}
// Chercher un utilisateur avec une session active
final activeUsers = _userBox.values
.where((user) => user.sessionId != null && user.sessionId!.isNotEmpty)
.toList();
final activeUsers = _userBox.values.where((user) => user.sessionId != null && user.sessionId!.isNotEmpty).toList();
// S'il y a des utilisateurs actifs, retourner le premier
if (activeUsers.isNotEmpty) {
@@ -185,8 +179,7 @@ class UserRepository extends ChangeNotifier {
return null;
} catch (e) {
debugPrint(
'Erreur lors de la récupération de l\'utilisateur depuis le stockage: $e');
debugPrint('Erreur lors de la récupération de l\'utilisateur depuis le stockage: $e');
return null;
}
}
@@ -218,8 +211,7 @@ class UserRepository extends ChangeNotifier {
}
// Login API PHP
Future<Map<String, dynamic>> loginAPI(String username, String password,
{required String type}) async {
Future<Map<String, dynamic>> loginAPI(String username, String password, {required String type}) async {
try {
return await _apiService.login(username, password, type: type);
} catch (e) {
@@ -229,19 +221,11 @@ class UserRepository extends ChangeNotifier {
}
// Register API PHP - Uniquement pour les administrateurs
Future<Map<String, dynamic>> registerAPI(String email, String name,
String amicaleName, String postalCode, String cityName) async {
Future<Map<String, dynamic>> registerAPI(String email, String name, String amicaleName, String postalCode, String cityName) async {
try {
final Map<String, dynamic> data = {
'email': email,
'name': name,
'amicale_name': amicaleName,
'postal_code': postalCode,
'city_name': cityName
};
final Map<String, dynamic> data = {'email': email, 'name': name, 'amicale_name': amicaleName, 'postal_code': postalCode, 'city_name': cityName};
final response =
await _apiService.post(AppKeys.registerEndpoint, data: data);
final response = await _apiService.post(AppKeys.registerEndpoint, data: data);
return response.data;
} catch (e) {
debugPrint('Erreur register API: $e');
@@ -270,20 +254,16 @@ class UserRepository extends ChangeNotifier {
}
// Méthode d'inscription (uniquement pour les administrateurs)
Future<bool> register(String email, String password, String name,
String amicaleName, String postalCode, String cityName) async {
Future<bool> register(String email, String password, String name, String amicaleName, String postalCode, String cityName) async {
_isLoading = true;
notifyListeners();
try {
// Enregistrer l'administrateur via l'API
final apiResult =
await registerAPI(email, name, amicaleName, postalCode, cityName);
final apiResult = await registerAPI(email, name, amicaleName, postalCode, cityName);
// Créer l'administrateur local
final int userId = apiResult['user_id'] is String
? int.parse(apiResult['user_id'])
: apiResult['user_id'];
final int userId = apiResult['user_id'] is String ? int.parse(apiResult['user_id']) : apiResult['user_id'];
final now = DateTime.now();
final newAdmin = UserModel(
id: userId,
@@ -316,8 +296,7 @@ class UserRepository extends ChangeNotifier {
}
// Login complet avec suivi de progression
Future<bool> login(String username, String password,
{required String type}) async {
Future<bool> login(String username, String password, {required String type}) async {
_isLoading = true;
_updateLoadingState(LoadingState.initial.copyWith(
message: 'Connexion en cours...',
@@ -346,8 +325,7 @@ class UserRepository extends ChangeNotifier {
await Hive.deleteBoxFromDisk(boxName);
debugPrint('Nettoyage: Box $boxName supprimée');
} catch (e) {
debugPrint(
'Erreur lors de la suppression de la boîte non référencée $boxName: $e');
debugPrint('Erreur lors de la suppression de la boîte non référencée $boxName: $e');
}
}
@@ -361,8 +339,7 @@ class UserRepository extends ChangeNotifier {
// Sur le web, utiliser notre méthode sécurisée pour nettoyer les boîtes Hive
if (kIsWeb) {
await HiveWebFix.safeCleanHiveBoxes(
excludeBoxes: [AppKeys.usersBoxName]);
await HiveWebFix.safeCleanHiveBoxes(excludeBoxes: [AppKeys.userBoxName]);
}
// Sur iOS, nettoyer les fichiers Hive directement
else if (!kIsWeb && Platform.isIOS) {
@@ -403,8 +380,7 @@ class UserRepository extends ChangeNotifier {
// Si le statut n'est pas 'success', retourner false
if (status != 'success') {
debugPrint('Échec de connexion: $message');
_updateLoadingState(
LoadingState.error(message ?? 'Échec de connexion'));
_updateLoadingState(LoadingState.error(message ?? 'Échec de connexion'));
return false;
}
@@ -421,8 +397,7 @@ class UserRepository extends ChangeNotifier {
});
// Si la clé 'user' existe, examiner son contenu
if (apiResult['user'] != null &&
apiResult['user'] is Map<String, dynamic>) {
if (apiResult['user'] != null && apiResult['user'] is Map<String, dynamic>) {
debugPrint('Détails utilisateur:');
final userDetails = apiResult['user'] as Map<String, dynamic>;
userDetails.forEach((key, value) {
@@ -430,8 +405,7 @@ class UserRepository extends ChangeNotifier {
});
// Construire un UserModel à partir des données utilisateur
final user = _processUserData(
userDetails, apiResult['session_id'], apiResult['session_expiry']);
final user = _processUserData(userDetails, apiResult['session_id'], apiResult['session_expiry']);
// Supprimer les anciennes références à interface et utiliser directement le rôle
await saveUser(user);
@@ -463,8 +437,7 @@ class UserRepository extends ChangeNotifier {
if (apiResult['clients'] is List) {
clientsList = apiResult['clients'] as List<dynamic>;
} else if (apiResult['clients'] is Map &&
apiResult['clients'].containsKey('data')) {
} else if (apiResult['clients'] is Map && apiResult['clients'].containsKey('data')) {
clientsList = apiResult['clients']['data'] as List<dynamic>;
} else {
debugPrint('Format de données de clients non reconnu');
@@ -529,8 +502,7 @@ class UserRepository extends ChangeNotifier {
));
// Traitement des membres
if (apiResult.containsKey('membres') ||
apiResult.containsKey('members')) {
if (apiResult.containsKey('membres') || apiResult.containsKey('members')) {
final membresData = apiResult['membres'] ?? apiResult['members'];
if (membresData != null) {
await _processMembres(membresData);
@@ -547,8 +519,7 @@ class UserRepository extends ChangeNotifier {
// Traitement des associations utilisateurs-secteurs
if (apiResult.containsKey('users_sectors')) {
await _processUserSectors(apiResult['users_sectors']);
debugPrint(
'Nombre d\'associations utilisateurs-secteurs chargées: ${_userSectorBox.length}');
debugPrint('Nombre d\'associations utilisateurs-secteurs chargées: ${_userSectorBox.length}');
}
// Vérification finale du remplissage des boîtes
@@ -570,8 +541,7 @@ class UserRepository extends ChangeNotifier {
for (final userSector in _userSectorBox.values) {
if (displayCount < 5) {
// Limiter à 5 pour éviter de surcharger la console
debugPrint(
' User ${userSector.id} (${userSector.firstName}) -> Secteur ${userSector.fkSector} (${userSector.name})');
debugPrint(' User ${userSector.id} (${userSector.firstName}) -> Secteur ${userSector.fkSector} (${userSector.name})');
displayCount++;
} else {
debugPrint(' ... et ${userSectorCount - 5} autres associations');
@@ -591,27 +561,21 @@ class UserRepository extends ChangeNotifier {
if (apiResult.containsKey('passages')) {
if (apiResult['passages'] is List) {
passagesCountInResponse = (apiResult['passages'] as List).length;
} else if (apiResult['passages'] is Map &&
apiResult['passages'].containsKey('data')) {
passagesCountInResponse =
(apiResult['passages']['data'] as List).length;
} else if (apiResult['passages'] is Map && apiResult['passages'].containsKey('data')) {
passagesCountInResponse = (apiResult['passages']['data'] as List).length;
}
}
int passagesCountInBox = _passageBox.length;
debugPrint(
'Nombre de passages dans la réponse API: $passagesCountInResponse');
debugPrint('Nombre de passages dans la réponse API: $passagesCountInResponse');
debugPrint('Nombre de passages dans la Hive Box: $passagesCountInBox');
// Si les nombres ne correspondent pas, attendre un peu et revérifier
if (passagesCountInResponse > 0 &&
passagesCountInBox < passagesCountInResponse) {
debugPrint(
'Attente supplémentaire pour finaliser le chargement des passages...');
if (passagesCountInResponse > 0 && passagesCountInBox < passagesCountInResponse) {
debugPrint('Attente supplémentaire pour finaliser le chargement des passages...');
await Future.delayed(const Duration(seconds: 1));
passagesCountInBox = _passageBox.length;
debugPrint(
'Après attente: Nombre de passages dans la Hive Box: $passagesCountInBox');
debugPrint('Après attente: Nombre de passages dans la Hive Box: $passagesCountInBox');
}
// Étape 12: Chargement terminé (100%)
@@ -652,7 +616,7 @@ class UserRepository extends ChangeNotifier {
await Hive.openBox<OperationModel>(boxName);
} else if (boxName == AppKeys.sectorsBoxName) {
await Hive.openBox<SectorModel>(boxName);
} else if (boxName == AppKeys.usersBoxName) {
} else if (boxName == AppKeys.userBoxName) {
await Hive.openBox<UserModel>(boxName);
} else if (boxName == AppKeys.membresBoxName) {
await Hive.openBox<MembreModel>(boxName);
@@ -696,15 +660,13 @@ class UserRepository extends ChangeNotifier {
await Hive.deleteBoxFromDisk(boxName);
debugPrint('Nettoyage: Box $boxName supprimée');
} catch (e) {
debugPrint(
'Erreur lors de la suppression de la boîte non référencée $boxName: $e');
debugPrint('Erreur lors de la suppression de la boîte non référencée $boxName: $e');
}
}
// Sur le web, utiliser notre méthode sécurisée pour nettoyer les boîtes Hive
if (kIsWeb) {
await HiveWebFix.safeCleanHiveBoxes(
excludeBoxes: [AppKeys.usersBoxName]);
await HiveWebFix.safeCleanHiveBoxes(excludeBoxes: [AppKeys.userBoxName]);
}
// Sur iOS, nettoyer les fichiers Hive directement
else if (Platform.isIOS) {
@@ -758,8 +720,7 @@ class UserRepository extends ChangeNotifier {
try {
await Hive.deleteBoxFromDisk(boxName);
} catch (deleteError) {
debugPrint(
'Impossible de supprimer la boîte $boxName: $deleteError');
debugPrint('Impossible de supprimer la boîte $boxName: $deleteError');
}
}
}
@@ -854,15 +815,13 @@ class UserRepository extends ChangeNotifier {
// Si la boîte contient des éléments, c'est anormal après recréation
if (count > 0) {
debugPrint(
'ATTENTION: La boîte $boxName contient encore des données après recréation');
debugPrint('ATTENTION: La boîte $boxName contient encore des données après recréation');
// Essayer de vider la boîte une dernière fois
await box.clear();
debugPrint('Vidage forcé de la boîte $boxName effectué');
}
} catch (typeError) {
debugPrint(
'Erreur de typage lors de la vérification de $boxName: $typeError');
debugPrint('Erreur de typage lors de la vérification de $boxName: $typeError');
// Tentative alternative sans typage spécifique
try {
@@ -872,17 +831,14 @@ class UserRepository extends ChangeNotifier {
if (count > 0) {
await box.clear();
debugPrint(
'Vidage forcé de la boîte $boxName (sans typage) effectué');
debugPrint('Vidage forcé de la boîte $boxName (sans typage) effectué');
}
} catch (e2) {
debugPrint(
'Impossible de vérifier la boîte $boxName même sans typage: $e2');
debugPrint('Impossible de vérifier la boîte $boxName même sans typage: $e2');
}
}
} else {
debugPrint(
'Boîte $boxName non ouverte, impossible de vérifier l\'intégrité');
debugPrint('Boîte $boxName non ouverte, impossible de vérifier l\'intégrité');
}
} catch (e) {
debugPrint('Erreur lors de la vérification de la boîte $boxName: $e');
@@ -891,8 +847,7 @@ class UserRepository extends ChangeNotifier {
debugPrint('Vérification d\'intégrité terminée');
} catch (e) {
debugPrint(
'Erreur lors de la vérification d\'intégrité des boîtes Hive: $e');
debugPrint('Erreur lors de la vérification d\'intégrité des boîtes Hive: $e');
}
}
@@ -906,7 +861,7 @@ class UserRepository extends ChangeNotifier {
'''
var request = indexedDB.deleteDatabase("geosector_app");
request.onsuccess = function() { console.log("IndexedDB nettoyé avec succès"); };
request.onerror = function() { console.log("Erreur lors du nettoyage d\'IndexedDB"); };
request.onerror = function() { console.log("Erreur lors du nettoyage d'IndexedDB"); };
'''
]);
await Future.delayed(const Duration(milliseconds: 500));
@@ -932,7 +887,7 @@ class UserRepository extends ChangeNotifier {
for (var entry in entries) {
final name = entry.path.split('/').last;
// Ne pas supprimer la boîte des utilisateurs
if (!name.contains(AppKeys.usersBoxName)) {
if (!name.contains(AppKeys.userBoxName)) {
debugPrint('Suppression de: ${entry.path}');
if (entry is Directory) {
await entry.delete(recursive: true);
@@ -957,7 +912,7 @@ class UserRepository extends ChangeNotifier {
try {
debugPrint('Nettoyage des fichiers Hive sur Android...');
final appDir = await getApplicationDocumentsDirectory();
final hiveDir = Directory('${appDir.path}');
final hiveDir = Directory(appDir.path);
if (await hiveDir.exists()) {
debugPrint('Recherche des fichiers Hive dans: ${hiveDir.path}');
@@ -968,8 +923,7 @@ class UserRepository extends ChangeNotifier {
for (var entry in entries) {
final name = entry.path.split('/').last;
// Ne supprimer que les fichiers Hive, mais pas la boîte des utilisateurs
if (name.endsWith('.hive') &&
!name.contains(AppKeys.usersBoxName)) {
if (name.endsWith('.hive') && !name.contains(AppKeys.userBoxName)) {
debugPrint('Suppression du fichier Hive: ${entry.path}');
if (entry is File) {
await entry.delete();
@@ -985,23 +939,19 @@ class UserRepository extends ChangeNotifier {
}
}
debugPrint(
'Nettoyage des fichiers Hive sur Android terminé. $filesDeleted fichiers supprimés.');
debugPrint('Nettoyage des fichiers Hive sur Android terminé. $filesDeleted fichiers supprimés.');
} else {
debugPrint('Répertoire d\'application non trouvé');
}
} catch (e) {
debugPrint(
'Erreur lors du nettoyage des fichiers Hive sur Android: $e');
debugPrint('Erreur lors du nettoyage des fichiers Hive sur Android: $e');
}
}
}
/// Méthode de connexion avec affichage d'un overlay de chargement avec progression
/// Cette méthode remplace AuthService.login et utilise le nouvel overlay avec barre de progression
Future<bool> loginWithUI(
BuildContext context, String username, String password,
{required String type}) async {
Future<bool> loginWithUI(BuildContext context, String username, String password, {required String type}) async {
try {
// Réinitialiser l'état de chargement
_updateLoadingState(LoadingState.initial.copyWith(
@@ -1019,7 +969,7 @@ class UserRepository extends ChangeNotifier {
);
// Écouter les changements d'état pour mettre à jour l'overlay
final listener = () {
listener() {
if (_progressOverlay != null) {
// Mettre à jour l'overlay avec les nouvelles valeurs
LoadingProgressOverlayUtils.update(
@@ -1029,7 +979,7 @@ class UserRepository extends ChangeNotifier {
stepDescription: _loadingState.stepDescription,
);
}
};
}
// Ajouter l'écouteur
addListener(listener);
@@ -1164,24 +1114,21 @@ class UserRepository extends ChangeNotifier {
String? lastUsername;
int? lastRole;
UserModel? lastUser;
if (Hive.isBoxOpen(AppKeys.usersBoxName) && _userBox.isNotEmpty) {
if (Hive.isBoxOpen(AppKeys.userBoxName) && _userBox.isNotEmpty) {
try {
// Récupérer l'utilisateur actuel ou le dernier utilisateur connecté
lastUser = getCurrentUser() ?? _userBox.values.first;
if (lastUser != null) {
lastUsername = lastUser.username;
lastUsername = lastUser.username;
// Convertir le rôle en int si nécessaire
if (lastUser.role is String) {
lastRole = int.tryParse(lastUser.role as String) ?? 0;
} else {
lastRole = lastUser.role as int;
}
debugPrint(
'Username sauvegardé pour pré-remplissage: $lastUsername');
debugPrint('Rôle sauvegardé pour pré-remplissage: $lastRole');
// Convertir le rôle en int si nécessaire
if (lastUser.role is String) {
lastRole = int.tryParse(lastUser.role as String) ?? 0;
} else {
lastRole = lastUser.role;
}
debugPrint('Username sauvegardé pour pré-remplissage: $lastUsername');
debugPrint('Rôle sauvegardé pour pré-remplissage: $lastRole');
} catch (e) {
debugPrint('Erreur lors de la sauvegarde du username et du rôle: $e');
}
@@ -1189,7 +1136,7 @@ class UserRepository extends ChangeNotifier {
// 1. Vider toutes les boîtes sans les fermer
debugPrint('Vidage des boîtes Hive...');
if (Hive.isBoxOpen(AppKeys.usersBoxName)) {
if (Hive.isBoxOpen(AppKeys.userBoxName)) {
try {
await _userBox.clear();
debugPrint('Boîte users vidée');
@@ -1200,16 +1147,14 @@ class UserRepository extends ChangeNotifier {
id: lastUser?.id ?? DateTime.now().millisecondsSinceEpoch,
email: lastUser?.email ?? '',
username: lastUsername,
role: lastRole ??
0, // Conserver le rôle pour la vérification dans la page de login
role: lastRole ?? 0, // Conserver le rôle pour la vérification dans la page de login
createdAt: DateTime.now(),
lastSyncedAt: DateTime.now(),
isActive: false,
isSynced: false,
);
await _userBox.put(minimalUser.id, minimalUser);
debugPrint(
'Utilisateur minimal créé pour pré-remplissage du username avec rôle: $lastRole');
debugPrint('Utilisateur minimal créé pour pré-remplissage du username avec rôle: $lastRole');
}
} catch (e) {
debugPrint('Erreur lors du vidage de la boîte users: $e');
@@ -1266,8 +1211,7 @@ class UserRepository extends ChangeNotifier {
await _chatConversationBox.clear();
debugPrint('Boîte chat_conversations vidée');
} catch (e) {
debugPrint(
'Erreur lors du vidage de la boîte chat_conversations: $e');
debugPrint('Erreur lors du vidage de la boîte chat_conversations: $e');
}
}
@@ -1319,15 +1263,15 @@ class UserRepository extends ChangeNotifier {
try {
// Vérifier si la boîte est ouverte avant de tenter de la fermer
if (Hive.isBoxOpen(AppKeys.usersBoxName)) {
if (Hive.isBoxOpen(AppKeys.userBoxName)) {
debugPrint('Fermeture de la boîte users...');
try {
await Hive.box<UserModel>(AppKeys.usersBoxName).close();
await Hive.box<UserModel>(AppKeys.userBoxName).close();
debugPrint('Boîte users fermée avec succès');
} catch (e) {
debugPrint('Erreur lors de la fermeture de la boîte users: $e');
// Ne pas continuer avec la suppression si la fermeture a échoué
throw e;
rethrow;
}
// Attendre un peu pour s'assurer que la fermeture est terminée
@@ -1336,27 +1280,24 @@ class UserRepository extends ChangeNotifier {
// Supprimer la boîte du disque seulement si la fermeture a réussi
debugPrint('Suppression de la boîte users du disque...');
try {
await Hive.deleteBoxFromDisk(AppKeys.usersBoxName);
await Hive.deleteBoxFromDisk(AppKeys.userBoxName);
debugPrint('Boîte users supprimée du disque avec succès');
} catch (e) {
debugPrint('Erreur lors de la suppression de la boîte users: $e');
// Ne pas continuer avec la réouverture si la suppression a échoué
throw e;
rethrow;
}
} else {
debugPrint(
'La boîte users est déjà fermée, tentative de suppression directe...');
debugPrint('La boîte users est déjà fermée, tentative de suppression directe...');
try {
await Hive.deleteBoxFromDisk(AppKeys.usersBoxName);
await Hive.deleteBoxFromDisk(AppKeys.userBoxName);
debugPrint('Boîte users supprimée du disque avec succès');
} catch (e) {
debugPrint(
'Erreur lors de la suppression directe de la boîte users: $e');
debugPrint('Erreur lors de la suppression directe de la boîte users: $e');
}
}
} catch (e) {
debugPrint(
'Erreur lors du processus de nettoyage de la boîte users: $e');
debugPrint('Erreur lors du processus de nettoyage de la boîte users: $e');
// Continuer malgré l'erreur, mais ne pas tenter de réouvrir la boîte
return;
}
@@ -1366,12 +1307,11 @@ class UserRepository extends ChangeNotifier {
// Rouvrir la boîte (elle sera vide)
debugPrint('Réouverture de la boîte users (vide)...');
await Hive.openBox<UserModel>(AppKeys.usersBoxName);
await Hive.openBox<UserModel>(AppKeys.userBoxName);
// Vérifier que la boîte est bien vide
final checkUsers = _userBox.values.toList();
debugPrint(
'Après approche radicale: ${checkUsers.length} utilisateurs restants');
debugPrint('Après approche radicale: ${checkUsers.length} utilisateurs restants');
// Forcer la réinitialisation du cache
_cachedCurrentUser = null;
@@ -1486,8 +1426,7 @@ class UserRepository extends ChangeNotifier {
return;
}
final unsyncedUsers =
_userBox.values.where((user) => !user.isSynced).toList();
final unsyncedUsers = _userBox.values.where((user) => !user.isSynced).toList();
if (unsyncedUsers.isEmpty) {
return;
@@ -1605,9 +1544,7 @@ class UserRepository extends ChangeNotifier {
List<AmicaleModel> getAllClients() {
try {
_ensureBoxIsOpen(AppKeys.amicaleBoxName);
return _amicaleBox.values
.where((amicale) => amicale.fkType == 1)
.toList();
return _amicaleBox.values.where((amicale) => amicale.fkType == 1).toList();
} catch (e) {
debugPrint('Erreur lors de la récupération des clients: $e');
return [];
@@ -1814,25 +1751,21 @@ class UserRepository extends ChangeNotifier {
// Méthode pour traiter les données des associations utilisateurs-secteurs reçues de l'API
Future<void> _processUserSectors(dynamic userSectorsData) async {
try {
debugPrint(
'Traitement des données des associations utilisateurs-secteurs...');
debugPrint('Traitement des données des associations utilisateurs-secteurs...');
// Vérifier que les données sont au bon format
if (userSectorsData == null) {
debugPrint(
'Aucune donnée d\'association utilisateur-secteur à traiter');
debugPrint('Aucune donnée d\'association utilisateur-secteur à traiter');
return;
}
List<dynamic> userSectorsList;
if (userSectorsData is List) {
userSectorsList = userSectorsData;
} else if (userSectorsData is Map &&
userSectorsData.containsKey('data')) {
} else if (userSectorsData is Map && userSectorsData.containsKey('data')) {
userSectorsList = userSectorsData['data'] as List<dynamic>;
} else {
debugPrint(
'Format de données d\'associations utilisateurs-secteurs non reconnu');
debugPrint('Format de données d\'associations utilisateurs-secteurs non reconnu');
return;
}
@@ -1844,26 +1777,21 @@ class UserRepository extends ChangeNotifier {
for (final userSectorData in userSectorsList) {
try {
final userSector = UserSectorModel.fromJson(userSectorData);
await _userSectorBox.put(
'${userSector.id}_${userSector.fkSector}', userSector);
await _userSectorBox.put('${userSector.id}_${userSector.fkSector}', userSector);
count++;
} catch (e) {
debugPrint(
'Erreur lors du traitement d\'une association utilisateur-secteur: $e');
debugPrint('Erreur lors du traitement d\'une association utilisateur-secteur: $e');
}
}
debugPrint(
'$count associations utilisateurs-secteurs traitées et stockées');
debugPrint('$count associations utilisateurs-secteurs traitées et stockées');
} catch (e) {
debugPrint(
'Erreur lors du traitement des associations utilisateurs-secteurs: $e');
debugPrint('Erreur lors du traitement des associations utilisateurs-secteurs: $e');
}
}
// Méthode pour traiter les données utilisateur reçues de l'API
UserModel _processUserData(
Map<String, dynamic> userData, String? sessionId, String? sessionExpiry) {
UserModel _processUserData(Map<String, dynamic> userData, String? sessionId, String? sessionExpiry) {
debugPrint('Traitement des données utilisateur: ${userData.toString()}');
// Convertir l'ID en int, qu'il soit déjà int ou string
@@ -1884,20 +1812,15 @@ class UserRepository extends ChangeNotifier {
// Convertir fk_entite en int si présent
final dynamic rawFkEntite = userData['fk_entite'];
final int? fkEntite = rawFkEntite != null
? (rawFkEntite is String ? int.parse(rawFkEntite) : rawFkEntite as int)
: null;
final int? fkEntite = rawFkEntite != null ? (rawFkEntite is String ? int.parse(rawFkEntite) : rawFkEntite as int) : null;
// Convertir fk_titre en int si présent
final dynamic rawFkTitre = userData['fk_titre'];
final int? fkTitre = rawFkTitre != null
? (rawFkTitre is String ? int.parse(rawFkTitre) : rawFkTitre as int)
: null;
final int? fkTitre = rawFkTitre != null ? (rawFkTitre is String ? int.parse(rawFkTitre) : rawFkTitre as int) : null;
// Traiter les dates si présentes
DateTime? dateNaissance;
if (userData['date_naissance'] != null &&
userData['date_naissance'] != '') {
if (userData['date_naissance'] != null && userData['date_naissance'] != '') {
try {
dateNaissance = DateTime.parse(userData['date_naissance']);
} catch (e) {
@@ -1929,8 +1852,7 @@ class UserRepository extends ChangeNotifier {
isActive: true,
isSynced: true,
sessionId: sessionId,
sessionExpiry:
sessionExpiry != null ? DateTime.parse(sessionExpiry) : null,
sessionExpiry: sessionExpiry != null ? DateTime.parse(sessionExpiry) : null,
sectName: userData['sect_name'],
fkEntite: fkEntite,
fkTitre: fkTitre,

View File

@@ -9,6 +9,295 @@ import 'package:retry/retry.dart';
import 'package:universal_html/html.dart' as html;
class ApiService {
static ApiService? _instance;
static final Object _lock = Object();
// Propriétés existantes conservées
final Dio _dio = Dio();
late final String _baseUrl;
late final String _appIdentifier;
String? _sessionId;
// Singleton thread-safe
static ApiService get instance {
if (_instance == null) {
throw Exception('ApiService non initialisé. Appelez initialize() d\'abord.');
}
return _instance!;
}
static Future<void> initialize() async {
if (_instance == null) {
synchronized(_lock, () {
if (_instance == null) {
_instance = ApiService._internal();
debugPrint('✅ ApiService singleton initialisé');
}
});
}
}
// Constructeur privé avec toute la logique existante
ApiService._internal() {
_configureEnvironment();
_dio.options.baseUrl = _baseUrl;
_dio.options.connectTimeout = AppKeys.connectionTimeout;
_dio.options.receiveTimeout = AppKeys.receiveTimeout;
final headers = Map<String, String>.from(AppKeys.defaultHeaders);
headers['X-App-Identifier'] = _appIdentifier;
_dio.options.headers.addAll(headers);
_dio.interceptors.add(InterceptorsWrapper(
onRequest: (options, handler) {
if (_sessionId != null) {
options.headers[AppKeys.sessionHeader] = 'Bearer $_sessionId';
}
handler.next(options);
},
onError: (DioException error, handler) {
if (error.response?.statusCode == 401) {
_sessionId = null;
}
handler.next(error);
},
));
debugPrint('🔗 ApiService configuré pour $_baseUrl');
}
// Fonction synchronized simple pour éviter les imports supplémentaires
static T synchronized<T>(Object lock, T Function() computation) {
return computation();
}
// === TOUTES LES MÉTHODES EXISTANTES RESTENT IDENTIQUES ===
// Détermine l'environnement actuel (DEV, REC, PROD) en fonction de l'URL
String _determineEnvironment() {
if (!kIsWeb) {
// En mode non-web, utiliser l'environnement de développement par défaut
return 'DEV';
}
final currentUrl = html.window.location.href.toLowerCase();
if (currentUrl.contains('dapp.geosector.fr')) {
return 'DEV';
} else if (currentUrl.contains('rapp.geosector.fr')) {
return 'REC';
} else {
return 'PROD';
}
}
// Configure l'URL de base API et l'identifiant d'application selon l'environnement
void _configureEnvironment() {
final env = _determineEnvironment();
switch (env) {
case 'DEV':
_baseUrl = AppKeys.baseApiUrlDev;
_appIdentifier = AppKeys.appIdentifierDev;
break;
case 'REC':
_baseUrl = AppKeys.baseApiUrlRec;
_appIdentifier = AppKeys.appIdentifierRec;
break;
default: // PROD
_baseUrl = AppKeys.baseApiUrlProd;
_appIdentifier = AppKeys.appIdentifierProd;
}
debugPrint('GEOSECTOR 🔗 Environnement: $env, API: $_baseUrl');
}
// Définir l'ID de session
void setSessionId(String? sessionId) {
_sessionId = sessionId;
}
// Obtenir l'environnement actuel (utile pour le débogage)
String getCurrentEnvironment() {
return _determineEnvironment();
}
// Obtenir l'URL API actuelle (utile pour le débogage)
String getCurrentApiUrl() {
return _baseUrl;
}
// Obtenir l'identifiant d'application actuel (utile pour le débogage)
String getCurrentAppIdentifier() {
return _appIdentifier;
}
// Vérifier la connectivité réseau
Future<bool> hasInternetConnection() async {
final connectivityResult = await (Connectivity().checkConnectivity());
return connectivityResult != ConnectivityResult.none;
}
// Méthode POST générique
Future<Response> post(String path, {dynamic data}) async {
try {
return await _dio.post(path, data: data);
} catch (e) {
rethrow;
}
}
// Méthode GET générique
Future<Response> get(String path, {Map<String, dynamic>? queryParameters}) async {
try {
return await _dio.get(path, queryParameters: queryParameters);
} catch (e) {
rethrow;
}
}
// Méthode PUT générique
Future<Response> put(String path, {dynamic data}) async {
try {
return await _dio.put(path, data: data);
} catch (e) {
rethrow;
}
}
// Méthode DELETE générique
Future<Response> delete(String path) async {
try {
return await _dio.delete(path);
} catch (e) {
rethrow;
}
}
// Authentification avec PHP session
Future<Map<String, dynamic>> login(String username, String password, {required String type}) async {
try {
final response = await _dio.post(AppKeys.loginEndpoint, data: {
'username': username,
'password': password,
'type': type, // Ajouter le type de connexion (user ou admin)
});
// Vérifier la structure de la réponse
final data = response.data as Map<String, dynamic>;
final status = data['status'] as String?;
// Afficher le message en cas d'erreur
if (status != 'success') {
final message = data['message'] as String?;
debugPrint('Erreur d\'authentification: $message');
}
// Si le statut est 'success', récupérer le session_id
if (status == 'success' && data.containsKey('session_id')) {
final sessionId = data['session_id'];
// Définir la session pour les futures requêtes
if (sessionId != null) {
setSessionId(sessionId);
}
}
return data;
} catch (e) {
rethrow;
}
}
// Déconnexion
Future<void> logout() async {
try {
if (_sessionId != null) {
await _dio.post(AppKeys.logoutEndpoint);
_sessionId = null;
}
} catch (e) {
// Même en cas d'erreur, on réinitialise la session
_sessionId = null;
rethrow;
}
}
// Utilisateurs
Future<List<UserModel>> getUsers() async {
try {
final response = await retry(
() => _dio.get('/users'),
retryIf: (e) => e is SocketException || e is TimeoutException,
);
return (response.data as List).map((json) => UserModel.fromJson(json)).toList();
} catch (e) {
// Gérer les erreurs
rethrow;
}
}
Future<UserModel> getUserById(int id) async {
try {
final response = await _dio.get('/users/$id');
return UserModel.fromJson(response.data);
} catch (e) {
rethrow;
}
}
Future<UserModel> createUser(UserModel user) async {
try {
final response = await _dio.post('/users', data: user.toJson());
return UserModel.fromJson(response.data);
} catch (e) {
rethrow;
}
}
Future<UserModel> updateUser(UserModel user) async {
try {
final response = await _dio.put('/users/${user.id}', data: user.toJson());
return UserModel.fromJson(response.data);
} catch (e) {
rethrow;
}
}
Future<void> deleteUser(String id) async {
try {
await _dio.delete('/users/$id');
} catch (e) {
rethrow;
}
}
// Espace réservé pour les futures méthodes de gestion des profils
// Espace réservé pour les futures méthodes de gestion des données
// Synchronisation en batch
Future<Map<String, dynamic>> syncData({
List<UserModel>? users,
}) async {
try {
final Map<String, dynamic> payload = {
if (users != null) 'users': users.map((u) => u.toJson()).toList(),
};
final response = await _dio.post('/sync', data: payload);
return response.data;
} catch (e) {
rethrow;
}
}
// Méthode de nettoyage pour les tests
static void reset() {
_instance = null;
}
}
final Dio _dio = Dio();
late final String _baseUrl;
late final String _appIdentifier;

View File

@@ -0,0 +1,144 @@
import 'package:flutter/foundation.dart';
import 'package:hive_flutter/hive_flutter.dart';
import 'package:geosector_app/core/constants/app_keys.dart';
import 'package:geosector_app/core/data/models/amicale_model.dart';
import 'package:geosector_app/core/services/current_user_service.dart';
class CurrentAmicaleService extends ChangeNotifier {
static CurrentAmicaleService? _instance;
static CurrentAmicaleService get instance => _instance ??= CurrentAmicaleService._internal();
CurrentAmicaleService._internal();
AmicaleModel? _currentAmicale;
// === GETTERS ===
AmicaleModel? get currentAmicale => _currentAmicale;
bool get hasAmicale => _currentAmicale != null;
int? get amicaleId => _currentAmicale?.id;
String? get amicaleName => _currentAmicale?.name;
String? get amicaleEmail => _currentAmicale?.email;
String? get amicalePhone => _currentAmicale?.phone;
String? get amicaleMobile => _currentAmicale?.mobile;
String? get amicaleAddress => _currentAmicale != null
? '${_currentAmicale!.adresse1} ${_currentAmicale!.adresse2}'.trim()
: null;
String? get amicaleFullAddress => _currentAmicale != null
? '${amicaleAddress ?? ''} ${_currentAmicale!.codePostal} ${_currentAmicale!.ville}'.trim()
: null;
bool get amicaleIsActive => _currentAmicale?.chkActive ?? false;
bool get isClient => _currentAmicale?.fkType == 1;
// Géolocalisation
bool get hasGpsCoordinates =>
_currentAmicale?.gpsLat.isNotEmpty == true &&
_currentAmicale?.gpsLng.isNotEmpty == true;
double? get latitude => hasGpsCoordinates
? double.tryParse(_currentAmicale!.gpsLat)
: null;
double? get longitude => hasGpsCoordinates
? double.tryParse(_currentAmicale!.gpsLng)
: null;
// === SETTERS ===
Future<void> setAmicale(AmicaleModel? amicale) async {
_currentAmicale = amicale;
await _saveToHive();
notifyListeners();
debugPrint('🏢 Amicale définie: ${amicale?.name ?? 'null'}');
}
Future<void> updateAmicale(AmicaleModel updatedAmicale) async {
_currentAmicale = updatedAmicale;
await _saveToHive();
notifyListeners();
debugPrint('🏢 Amicale mise à jour: ${updatedAmicale.name}');
}
Future<void> clearAmicale() async {
final amicaleName = _currentAmicale?.name;
_currentAmicale = null;
await _clearFromHive();
notifyListeners();
debugPrint('🏢 Amicale effacée: $amicaleName');
}
// === AUTO-LOAD BASÉ SUR L'UTILISATEUR ===
Future<void> loadUserAmicale() async {
final user = CurrentUserService.instance.currentUser;
if (user?.fkEntite != null) {
await loadAmicaleById(user!.fkEntite!);
} else {
await clearAmicale();
}
}
Future<void> loadAmicaleById(int amicaleId) async {
try {
final box = Hive.box<AmicaleModel>(AppKeys.amicaleBoxName);
final amicale = box.get('current_amicale');
if (amicale?.id == amicaleId) {
_currentAmicale = amicale;
debugPrint('📥 Amicale chargée depuis Hive: ${amicale?.name}');
} else {
// Si l'amicale n'est pas la bonne, la chercher ou l'effacer
_currentAmicale = null;
debugPrint('⚠️ Amicale ${amicaleId} non trouvée dans Hive');
}
notifyListeners();
} catch (e) {
debugPrint('❌ Erreur chargement amicale depuis Hive: $e');
_currentAmicale = null;
}
}
// === PERSISTENCE HIVE ===
Future<void> _saveToHive() async {
try {
if (_currentAmicale != null) {
final box = Hive.box<AmicaleModel>(AppKeys.amicaleBoxName);
await box.clear();
await box.put('current_amicale', _currentAmicale!);
debugPrint('💾 Amicale sauvegardée dans Hive');
}
} catch (e) {
debugPrint('❌ Erreur sauvegarde amicale Hive: $e');
}
}
Future<void> _clearFromHive() async {
try {
final box = Hive.box<AmicaleModel>(AppKeys.amicaleBoxName);
await box.clear();
debugPrint('🗑️ Box amicale effacée');
} catch (e) {
debugPrint('❌ Erreur effacement amicale Hive: $e');
}
}
Future<void> loadFromHive() async {
try {
final box = Hive.box<AmicaleModel>(AppKeys.amicaleBoxName);
_currentAmicale = box.get('current_amicale');
if (_currentAmicale != null) {
debugPrint('📥 Amicale chargée depuis Hive: ${_currentAmicale!.name}');
} else {
debugPrint(' Aucune amicale trouvée dans Hive');
}
notifyListeners();
} catch (e) {
debugPrint('❌ Erreur chargement amicale depuis Hive: $e');
_currentAmicale = null;
}
}
// === RESET POUR TESTS ===
static void reset() {
_instance?._currentAmicale = null;
_instance = null;
}
}

View File

@@ -0,0 +1,156 @@
import 'package:flutter/foundation.dart';
import 'package:hive_flutter/hive_flutter.dart';
import 'package:geosector_app/core/constants/app_keys.dart';
import 'package:geosector_app/core/data/models/user_model.dart';
import 'package:geosector_app/core/services/current_amicale_service.dart';
class CurrentUserService extends ChangeNotifier {
static CurrentUserService? _instance;
static CurrentUserService get instance => _instance ??= CurrentUserService._internal();
CurrentUserService._internal();
UserModel? _currentUser;
// === GETTERS ===
UserModel? get currentUser => _currentUser;
bool get isLoggedIn => _currentUser?.hasValidSession ?? false;
int get userRole => _currentUser?.role ?? 0;
int? get userId => _currentUser?.id;
String? get userEmail => _currentUser?.email;
String? get userName => _currentUser?.name;
String? get userFirstName => _currentUser?.firstName;
String? get sessionId => _currentUser?.sessionId;
int? get fkEntite => _currentUser?.fkEntite;
String? get userPhone => _currentUser?.phone;
String? get userMobile => _currentUser?.mobile;
// Vérifications de rôles
bool get isUser => userRole == 1;
bool get isAdminAmicale => userRole == 2;
bool get isSuperAdmin => userRole >= 3;
bool get canAccessAdmin => isAdminAmicale || isSuperAdmin;
// === SETTERS ===
Future<void> setUser(UserModel? user) async {
_currentUser = user;
await _saveToHive();
notifyListeners();
debugPrint('👤 Utilisateur défini: ${user?.email ?? 'null'}');
// Auto-synchroniser l'amicale si l'utilisateur a une entité
if (user?.fkEntite != null) {
await CurrentAmicaleService.instance.loadUserAmicale();
} else {
await CurrentAmicaleService.instance.clearAmicale();
}
}
Future<void> updateUser(UserModel updatedUser) async {
_currentUser = updatedUser;
await _saveToHive();
notifyListeners();
debugPrint('👤 Utilisateur mis à jour: ${updatedUser.email}');
}
Future<void> clearUser() async {
final userEmail = _currentUser?.email;
_currentUser = null;
await _clearFromHive();
notifyListeners();
debugPrint('👤 Utilisateur effacé: $userEmail');
}
// === PERSISTENCE HIVE (nouvelle Box user) ===
Future<void> _saveToHive() async {
try {
if (_currentUser != null) {
final box = Hive.box<UserModel>(AppKeys.userBoxName); // Nouvelle Box
await box.clear();
await box.put('current_user', _currentUser!);
debugPrint('💾 Utilisateur sauvegardé dans Box user');
}
} catch (e) {
debugPrint('❌ Erreur sauvegarde utilisateur Hive: $e');
}
}
Future<void> _clearFromHive() async {
try {
final box = Hive.box<UserModel>(AppKeys.userBoxName); // Nouvelle Box
await box.clear();
debugPrint('🗑️ Box user effacée');
} catch (e) {
debugPrint('❌ Erreur effacement utilisateur Hive: $e');
}
}
Future<void> loadFromHive() async {
try {
final box = Hive.box<UserModel>(AppKeys.userBoxName); // Nouvelle Box
final user = box.get('current_user');
if (user?.hasValidSession == true) {
_currentUser = user;
debugPrint('📥 Utilisateur chargé depuis Hive: ${user?.email}');
} else {
_currentUser = null;
debugPrint(' Aucun utilisateur valide trouvé dans Hive');
}
notifyListeners();
} catch (e) {
debugPrint('❌ Erreur chargement utilisateur depuis Hive: $e');
_currentUser = null;
}
}
// === MÉTHODES UTILITAIRES ===
Future<void> updateLastPath(String path) async {
if (_currentUser != null) {
await updateUser(_currentUser!.copyWith(lastPath: path));
}
}
String? getLastPath() => _currentUser?.lastPath;
String getDefaultRoute() {
if (!isLoggedIn) return '/';
return canAccessAdmin ? '/admin' : '/user';
}
String getRoleLabel() {
switch (userRole) {
case 1:
return 'Utilisateur';
case 2:
return 'Admin Amicale';
case 3:
return 'Super Admin';
default:
return 'Inconnu';
}
}
bool hasPermission(String permission) {
switch (permission) {
case 'admin':
return canAccessAdmin;
case 'super_admin':
return isSuperAdmin;
case 'manage_amicale':
return canAccessAdmin;
case 'manage_users':
return isSuperAdmin;
default:
return isLoggedIn;
}
}
// === RESET POUR TESTS ===
static void reset() {
_instance?._currentUser = null;
_instance = null;
}
}

View File

@@ -22,8 +22,7 @@ class HiveResetService {
/// Réinitialise complètement Hive et recrée les boîtes nécessaires
static Future<bool> resetAndRecreateHiveBoxes() async {
try {
debugPrint(
'HiveResetService: Début de la réinitialisation complète de Hive');
debugPrint('HiveResetService: Début de la réinitialisation complète de Hive');
// Approche plus radicale pour le web : supprimer directement IndexedDB
if (kIsWeb) {
@@ -68,8 +67,7 @@ class HiveResetService {
// Rouvrir les boîtes essentielles
await _reopenEssentialBoxes();
debugPrint(
'HiveResetService: Réinitialisation complète terminée avec succès');
debugPrint('HiveResetService: Réinitialisation complète terminée avec succès');
return true;
} catch (e) {
debugPrint('HiveResetService: Erreur lors de la réinitialisation: $e');
@@ -80,7 +78,7 @@ class HiveResetService {
/// Ferme toutes les boîtes Hive ouvertes
static Future<void> _closeAllBoxes() async {
final boxNames = [
AppKeys.usersBoxName,
AppKeys.userBoxName,
AppKeys.amicaleBoxName,
AppKeys.clientsBoxName,
AppKeys.operationsBoxName,
@@ -137,7 +135,7 @@ class HiveResetService {
debugPrint('HiveResetService: Réouverture des boîtes essentielles');
// Ouvrir les boîtes essentielles au démarrage
await Hive.openBox<UserModel>(AppKeys.usersBoxName);
await Hive.openBox<UserModel>(AppKeys.userBoxName);
await Hive.openBox<AmicaleModel>(AppKeys.amicaleBoxName);
await Hive.openBox<ClientModel>(AppKeys.clientsBoxName);
await Hive.openBox(AppKeys.settingsBoxName);

View File

@@ -12,8 +12,7 @@ class HiveWebFix {
if (!kIsWeb) return;
try {
debugPrint(
'HiveWebFix: Nettoyage sécurisé des boîtes Hive en version web');
debugPrint('HiveWebFix: Nettoyage sécurisé des boîtes Hive en version web');
// Liste des boîtes à nettoyer
final boxesToClean = [
@@ -36,16 +35,14 @@ class HiveWebFix {
await box.clear();
debugPrint('HiveWebFix: Boîte $boxName nettoyée avec succès');
} else {
debugPrint(
'HiveWebFix: La boîte $boxName n\'est pas ouverte, ouverture temporaire');
debugPrint('HiveWebFix: La boîte $boxName n\'est pas ouverte, ouverture temporaire');
final box = await Hive.openBox(boxName);
await box.clear();
await box.close();
debugPrint('HiveWebFix: Boîte $boxName nettoyée et fermée');
}
} catch (e) {
debugPrint(
'HiveWebFix: Erreur lors du nettoyage de la boîte $boxName: $e');
debugPrint('HiveWebFix: Erreur lors du nettoyage de la boîte $boxName: $e');
}
}
@@ -65,14 +62,13 @@ class HiveWebFix {
// Vérifier si IndexedDB est accessible
final isIndexedDBAvailable = js.context.hasProperty('indexedDB');
if (!isIndexedDBAvailable) {
debugPrint(
'HiveWebFix: IndexedDB n\'est pas disponible dans ce navigateur');
debugPrint('HiveWebFix: IndexedDB n\'est pas disponible dans ce navigateur');
return false;
}
// Liste des boîtes essentielles
final essentialBoxes = [
AppKeys.usersBoxName,
AppKeys.userBoxName,
AppKeys.settingsBoxName,
];
@@ -80,8 +76,7 @@ class HiveWebFix {
for (final boxName in essentialBoxes) {
try {
if (!Hive.isBoxOpen(boxName)) {
debugPrint(
'HiveWebFix: Ouverture de la boîte essentielle $boxName');
debugPrint('HiveWebFix: Ouverture de la boîte essentielle $boxName');
await Hive.openBox(boxName);
}
@@ -89,15 +84,13 @@ class HiveWebFix {
final box = Hive.box(boxName);
// Tenter une opération simple pour vérifier l'intégrité
final length = box.length;
debugPrint(
'HiveWebFix: Boîte $boxName accessible avec $length éléments');
debugPrint('HiveWebFix: Boîte $boxName accessible avec $length éléments');
} catch (e) {
debugPrint('HiveWebFix: Erreur d\'accès à la boîte $boxName: $e');
// Tenter de réparer en réinitialisant Hive
try {
debugPrint(
'HiveWebFix: Tentative de réparation de la boîte $boxName');
debugPrint('HiveWebFix: Tentative de réparation de la boîte $boxName');
// Fermer la boîte si elle est ouverte
if (Hive.isBoxOpen(boxName)) {
await Hive.box(boxName).close();
@@ -107,8 +100,7 @@ class HiveWebFix {
await Hive.openBox(boxName);
debugPrint('HiveWebFix: Boîte $boxName réparée avec succès');
} catch (repairError) {
debugPrint(
'HiveWebFix: Échec de la réparation de la boîte $boxName: $repairError');
debugPrint('HiveWebFix: Échec de la réparation de la boîte $boxName: $repairError');
return false;
}
}
@@ -132,7 +124,7 @@ class HiveWebFix {
// Fermer toutes les boîtes ouvertes
final boxesToClose = [
AppKeys.usersBoxName,
AppKeys.userBoxName,
AppKeys.operationsBoxName,
AppKeys.sectorsBoxName,
AppKeys.passagesBoxName,

View File

@@ -133,7 +133,110 @@ void _registerHiveAdapters() {
/// Ouvre les boîtes Hive essentielles
Future<void> _openEssentialHiveBoxes() async {
final boxesToOpen = [
{'name': AppKeys.usersBoxName, 'type': 'UserModel'},
{'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'},
];
// Logique de migration de l'ancienne box users vers user
try {
// Vérifier si l'ancienne box users existe
if (await _doesBoxExist(AppKeys.usersBoxNameOld)) {
debugPrint('🔄 Migration détectée: box users -> user');
// Ouvrir l'ancienne box
final oldBox = await Hive.openBox<UserModel>(AppKeys.usersBoxNameOld);
// Ouvrir la nouvelle box
final newBox = await Hive.openBox<UserModel>(AppKeys.userBoxName);
// Migrer les données si la nouvelle box est vide
if (oldBox.isNotEmpty && newBox.isEmpty) {
debugPrint('📦 Migration des données users -> user...');
// Chercher l'utilisateur actuel dans l'ancienne box
final userData = oldBox.get('current_user') ?? oldBox.values.firstOrNull;
if (userData != null) {
await newBox.put('current_user', userData);
debugPrint('✅ Migration de users -> user réussie pour ${userData.email}');
}
}
// Fermer et supprimer l'ancienne box
await oldBox.close();
await Hive.deleteBoxFromDisk(AppKeys.usersBoxNameOld);
debugPrint('🗑️ Ancienne box users supprimée');
} else {
// Ouvrir normalement la nouvelle box
await Hive.openBox<UserModel>(AppKeys.userBoxName);
}
} catch (e) {
debugPrint('⚠️ Erreur migration box users: $e');
// En cas d'erreur, ouvrir quand même la nouvelle box
try {
await Hive.openBox<UserModel>(AppKeys.userBoxName);
} catch (e2) {
debugPrint('❌ Impossible d\'ouvrir la box user: $e2');
}
}
// Ouvrir les autres boîtes
for (final box in boxesToOpen) {
try {
final boxName = box['name'] as String;
final boxType = box['type'] as String;
// Skip userBoxName car déjà traité dans la migration
if (boxName == AppKeys.userBoxName) continue;
// 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 '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
}
}
}
/// Vérifie si une box Hive existe sur le disque
Future<bool> _doesBoxExist(String boxName) async {
try {
// Tentative d'ouverture pour vérifier l'existence
final box = await Hive.openBox<UserModel>(boxName);
final exists = box.isNotEmpty;
await box.close();
return exists;
} catch (e) {
return false;
}
}
final boxesToOpen = [
{'name': AppKeys.userBoxName, 'type': 'UserModel'},
{'name': AppKeys.amicaleBoxName, 'type': 'AmicaleModel'},
{'name': AppKeys.clientsBoxName, 'type': 'ClientModel'},
{'name': AppKeys.settingsBoxName, 'type': 'dynamic'},

View File

@@ -49,8 +49,7 @@ class DotsPainter extends CustomPainter {
bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
}
class _SplashPageState extends State<SplashPage>
with SingleTickerProviderStateMixin {
class _SplashPageState extends State<SplashPage> with SingleTickerProviderStateMixin {
late AnimationController _animationController;
late Animation<double> _scaleAnimation;
bool _isInitializing = true;
@@ -72,9 +71,7 @@ class _SplashPageState extends State<SplashPage>
// Fallback sur la version du AppInfoService si elle existe
if (mounted) {
setState(() {
_appVersion = AppInfoService.fullVersion
.split(' ')
.last; // Extraire juste le numéro
_appVersion = AppInfoService.fullVersion.split(' ').last; // Extraire juste le numéro
});
}
}
@@ -154,30 +151,18 @@ class _SplashPageState extends State<SplashPage>
// Structure pour les boîtes à ouvrir avec leurs noms d'affichage
final boxesToOpen = [
{'name': AppKeys.usersBoxName, 'display': 'Préparation utilisateurs'},
{'name': AppKeys.userBoxName, 'display': 'Préparation utilisateurs'},
{'name': AppKeys.amicaleBoxName, 'display': 'Préparation amicale'},
{'name': AppKeys.clientsBoxName, 'display': 'Préparation clients'},
{'name': AppKeys.regionsBoxName, 'display': 'Préparation régions'},
{
'name': AppKeys.operationsBoxName,
'display': 'Préparation opérations'
},
{'name': AppKeys.operationsBoxName, 'display': 'Préparation opérations'},
{'name': AppKeys.sectorsBoxName, 'display': 'Préparation secteurs'},
{'name': AppKeys.passagesBoxName, 'display': 'Préparation passages'},
{'name': AppKeys.membresBoxName, 'display': 'Préparation membres'},
{
'name': AppKeys.userSectorBoxName,
'display': 'Préparation secteurs utilisateurs'
},
{'name': AppKeys.userSectorBoxName, 'display': 'Préparation secteurs utilisateurs'},
{'name': AppKeys.settingsBoxName, 'display': 'Préparation paramètres'},
{
'name': AppKeys.chatConversationsBoxName,
'display': 'Préparation conversations'
},
{
'name': AppKeys.chatMessagesBoxName,
'display': 'Préparation messages'
},
{'name': AppKeys.chatConversationsBoxName, 'display': 'Préparation conversations'},
{'name': AppKeys.chatMessagesBoxName, 'display': 'Préparation messages'},
];
// Calculer l'incrément de progression pour chaque boîte (0.75 / nombre de boîtes)
@@ -202,7 +187,7 @@ class _SplashPageState extends State<SplashPage>
debugPrint('Ouverture de la boîte $boxName ($displayName)...');
// Ouvrir la boîte avec le type approprié
if (boxName == AppKeys.usersBoxName) {
if (boxName == AppKeys.userBoxName) {
await Hive.openBox<UserModel>(boxName);
} else if (boxName == AppKeys.amicaleBoxName) {
await Hive.openBox<AmicaleModel>(boxName);
@@ -296,7 +281,7 @@ class _SplashPageState extends State<SplashPage>
),
child: CustomPaint(
painter: DotsPainter(),
child: Container(width: double.infinity, height: double.infinity),
child: const SizedBox(width: double.infinity, height: double.infinity),
),
),
@@ -351,8 +336,7 @@ class _SplashPageState extends State<SplashPage>
'Une application puissante et intuitive de gestion de vos distributions de calendriers',
textAlign: TextAlign.center,
style: theme.textTheme.bodyLarge?.copyWith(
color:
theme.colorScheme.onBackground.withOpacity(0.7),
color: theme.colorScheme.onSurface.withOpacity(0.7),
fontWeight: FontWeight.w500,
),
),
@@ -380,8 +364,7 @@ class _SplashPageState extends State<SplashPage>
Text(
_statusMessage,
style: theme.textTheme.bodyMedium?.copyWith(
color:
theme.colorScheme.onBackground.withOpacity(0.7),
color: theme.colorScheme.onSurface.withOpacity(0.7),
),
),
],
@@ -394,8 +377,7 @@ class _SplashPageState extends State<SplashPage>
duration: const Duration(milliseconds: 500),
child: ElevatedButton(
onPressed: () {
context.go(
'/login/user'); // Utiliser la route spécifique
context.go('/login/user'); // Utiliser la route spécifique
},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.green,
@@ -426,8 +408,7 @@ class _SplashPageState extends State<SplashPage>
duration: const Duration(milliseconds: 500),
child: ElevatedButton(
onPressed: () {
context.go(
'/login/admin'); // Utiliser la route spécifique
context.go('/login/admin'); // Utiliser la route spécifique
},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.red,
@@ -451,8 +432,7 @@ class _SplashPageState extends State<SplashPage>
),
),
const SizedBox(
height: 32), // 2 espaces sous le bouton précédent
const SizedBox(height: 32), // 2 espaces sous le bouton précédent
// Bouton d'inscription
AnimatedOpacity(