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 95e9af23e2
commit 7e6431b5aa
26 changed files with 5698 additions and 5118 deletions

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(