Files
geo/app/lib/core/services/hive_service.dart
Pierre 3443277d4a feat: Release version 3.1.4 - Mode terrain et génération PDF
 Nouvelles fonctionnalités:
- Ajout du mode terrain pour utilisation mobile hors connexion
- Génération automatique de reçus PDF avec template personnalisé
- Révision complète du système de cartes avec amélioration des performances

🔧 Améliorations techniques:
- Refactoring du module chat avec architecture simplifiée
- Optimisation du système de sécurité NIST SP 800-63B
- Amélioration de la gestion des secteurs géographiques
- Support UTF-8 étendu pour les noms d'utilisateurs

📱 Application mobile:
- Nouveau mode terrain dans user_field_mode_page
- Interface utilisateur adaptative pour conditions difficiles
- Synchronisation offline améliorée

🗺️ Cartographie:
- Optimisation des performances MapBox
- Meilleure gestion des tuiles hors ligne
- Amélioration de l'affichage des secteurs

📄 Documentation:
- Ajout guide Android (ANDROID-GUIDE.md)
- Documentation sécurité API (API-SECURITY.md)
- Guide module chat (CHAT_MODULE.md)

🐛 Corrections:
- Résolution des erreurs 400 lors de la création d'utilisateurs
- Correction de la validation des noms d'utilisateurs
- Fix des problèmes de synchronisation chat

🤖 Generated with Claude Code (https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-19 19:38:03 +02:00

525 lines
16 KiB
Dart
Executable File
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:hive_flutter/hive_flutter.dart';
import 'package:path_provider/path_provider.dart';
import 'package:geosector_app/core/constants/app_keys.dart';
import 'package:geosector_app/core/services/hive_web_fix.dart';
import 'package:geosector_app/core/services/hive_adapters.dart';
// Import de tous les modèles typés
import 'package:geosector_app/core/data/models/user_model.dart';
import 'package:geosector_app/core/data/models/amicale_model.dart';
import 'package:geosector_app/core/data/models/client_model.dart';
import 'package:geosector_app/core/data/models/operation_model.dart';
import 'package:geosector_app/core/data/models/sector_model.dart';
import 'package:geosector_app/core/data/models/passage_model.dart';
import 'package:geosector_app/core/data/models/membre_model.dart';
import 'package:geosector_app/core/data/models/user_sector_model.dart';
// Chat imports removed - using new simplified chat module
/// Service singleton centralisé pour la gestion complète des Box Hive
/// Utilisé par main.dart pour l'initialisation et par logout pour le nettoyage
class HiveService {
static HiveService? _instance;
static HiveService get instance => _instance ??= HiveService._internal();
HiveService._internal();
/// Configuration des Box typées de l'application
static const List<HiveBoxConfig> _boxConfigs = [
HiveBoxConfig<UserModel>(AppKeys.userBoxName, 'UserModel'),
HiveBoxConfig<AmicaleModel>(AppKeys.amicaleBoxName, 'AmicaleModel'),
HiveBoxConfig<ClientModel>(AppKeys.clientsBoxName, 'ClientModel'),
HiveBoxConfig<OperationModel>(AppKeys.operationsBoxName, 'OperationModel'),
HiveBoxConfig<SectorModel>(AppKeys.sectorsBoxName, 'SectorModel'),
HiveBoxConfig<PassageModel>(AppKeys.passagesBoxName, 'PassageModel'),
HiveBoxConfig<MembreModel>(AppKeys.membresBoxName, 'MembreModel'),
HiveBoxConfig<UserSectorModel>(AppKeys.userSectorBoxName, 'UserSectorModel'),
// Chat boxes removed - handled by new chat module
HiveBoxConfig<dynamic>(AppKeys.settingsBoxName, 'Settings'),
HiveBoxConfig<dynamic>(AppKeys.regionsBoxName, 'Regions'),
];
bool _isInitialized = false;
bool get isInitialized => _isInitialized;
// === INITIALISATION COMPLÈTE (appelée par main.dart) ===
/// Initialisation complète de Hive avec réinitialisation totale
Future<void> initializeAndResetHive() async {
if (_isInitialized) {
debugPrint(' HiveService déjà initialisé');
return;
}
try {
debugPrint('🔧 Initialisation complète de Hive avec reset...');
// 1. Initialisation de base de Hive
await Hive.initFlutter();
debugPrint('✅ Hive.initFlutter() terminé');
// 2. Enregistrement des adaptateurs
_registerAdapters();
// 3. Destruction complète des anciennes données
await _destroyAllData();
// 4. Création des Box vides et propres
await _createAllBoxes();
_isInitialized = true;
debugPrint('✅ HiveService initialisé avec succès');
} catch (e) {
debugPrint('❌ Erreur lors de l\'initialisation complète: $e');
_isInitialized = false;
rethrow;
}
}
// === INITIALISATION SIMPLE (appelée par splash_page si besoin) ===
/// Initialisation simple sans reset (utilisée par splash_page si déjà initialisé)
Future<void> ensureBoxesAreOpen() async {
try {
debugPrint('🔍 Vérification et ouverture des Box...');
// Vérifier si toutes les Box sont ouvertes
bool allOpen = true;
for (final config in _boxConfigs) {
if (!Hive.isBoxOpen(config.name)) {
allOpen = false;
break;
}
}
if (allOpen) {
debugPrint('✅ Toutes les Box sont déjà ouvertes');
return;
}
// Ouvrir les Box manquantes
await _createAllBoxes();
debugPrint('✅ Box manquantes ouvertes');
} catch (e) {
debugPrint('❌ Erreur lors de la vérification des Box: $e');
rethrow;
}
}
// === NETTOYAGE LOGOUT (appelé lors du logout) ===
/// Nettoyage sélectif lors du logout (préserve les utilisateurs)
Future<void> cleanDataOnLogout() async {
try {
debugPrint('🧹 Nettoyage des données au logout...');
// Nettoyer toutes les Box sauf les utilisateurs
for (final config in _boxConfigs) {
if (config.name != AppKeys.userBoxName) {
await _clearSingleBox(config.name);
}
}
debugPrint('✅ Nettoyage logout terminé (utilisateurs préservés)');
} catch (e) {
debugPrint('❌ Erreur lors du nettoyage logout: $e');
rethrow;
}
}
// === MÉTHODES PRIVÉES D'INITIALISATION ===
/// Enregistrement de tous les adaptateurs Hive
void _registerAdapters() {
try {
if (!Hive.isAdapterRegistered(0)) {
// Utiliser HiveAdapters existant pour enregistrer tous les adaptateurs
HiveAdapters.registerAll();
debugPrint('🔌 Adaptateurs Hive enregistrés via HiveAdapters');
} else {
debugPrint(' Adaptateurs déjà enregistrés');
}
} catch (e) {
debugPrint('❌ Erreur enregistrement adaptateurs: $e');
// Ne pas faire échouer l'initialisation
}
}
/// Destruction complète de toutes les données selon la plateforme
Future<void> _destroyAllData() async {
try {
debugPrint('💥 Destruction complète des données Hive...');
// 1. Fermer toutes les Box ouvertes
await _closeAllOpenBoxes();
// 2. Suppression selon la plateforme
if (kIsWeb) {
await _destroyDataWeb();
} else if (Platform.isIOS) {
await _destroyDataIOS();
} else if (Platform.isAndroid) {
await _destroyDataAndroid();
} else {
await _destroyDataDesktop();
}
// 3. Attendre pour s'assurer que tout est détruit
await Future.delayed(const Duration(seconds: 1));
debugPrint('✅ Destruction complète terminée');
} catch (e) {
debugPrint('❌ Erreur destruction: $e');
// Continuer malgré l'erreur
}
}
/// Fermeture de toutes les Box ouvertes
Future<void> _closeAllOpenBoxes() async {
try {
debugPrint('🔒 Fermeture de toutes les Box ouvertes...');
// Fermer les Box configurées
for (final config in _boxConfigs) {
try {
if (Hive.isBoxOpen(config.name)) {
await Hive.box(config.name).close();
debugPrint('🔒 Box ${config.name} fermée');
}
} catch (e) {
debugPrint('⚠️ Erreur fermeture ${config.name}: $e');
}
}
// Fermer aussi les Box potentiellement orphelines
final orphanBoxes = ['auth', 'temp', 'cache', 'locations', 'messages'];
for (final boxName in orphanBoxes) {
try {
if (Hive.isBoxOpen(boxName)) {
await Hive.box(boxName).close();
debugPrint('🔒 Box orpheline $boxName fermée');
}
} catch (e) {
debugPrint('⚠️ Erreur fermeture orpheline $boxName: $e');
}
}
} catch (e) {
debugPrint('❌ Erreur fermeture générale: $e');
}
}
/// Destruction des données sur Web
Future<void> _destroyDataWeb() async {
try {
debugPrint('🌐 Destruction Web...');
// Sur Web, utiliser le HiveWebFix si disponible
try {
await HiveWebFix.resetHiveCompletely();
debugPrint('✅ Destruction Web via HiveWebFix');
return;
} catch (e) {
debugPrint('⚠️ HiveWebFix échoué, fallback...');
}
// Fallback : supprimer Box par Box
for (final config in _boxConfigs) {
try {
await Hive.deleteBoxFromDisk(config.name);
debugPrint('🗑️ Box Web ${config.name} supprimée');
} catch (e) {
debugPrint('⚠️ Erreur suppression Web ${config.name}: $e');
}
}
} catch (e) {
debugPrint('❌ Erreur destruction Web: $e');
}
}
/// Destruction des données sur iOS
Future<void> _destroyDataIOS() async {
try {
debugPrint('🍎 Destruction iOS...');
// Méthode 1: Destruction totale
try {
await Hive.deleteFromDisk();
debugPrint('✅ Destruction iOS complète');
return;
} catch (e) {
debugPrint('⚠️ Destruction iOS totale échouée, méthode alternative...');
}
// Méthode 2: Suppression des fichiers manuellement
try {
final appDir = await getApplicationDocumentsDirectory();
final hiveDir = Directory('${appDir.path}/hive');
if (await hiveDir.exists()) {
await hiveDir.delete(recursive: true);
debugPrint('✅ Dossier Hive iOS supprimé');
}
} catch (e) {
debugPrint('⚠️ Suppression dossier iOS échouée: $e');
}
// Méthode 3: Fallback Box par Box
await _fallbackDeleteBoxes();
} catch (e) {
debugPrint('❌ Erreur destruction iOS: $e');
}
}
/// Destruction des données sur Android
Future<void> _destroyDataAndroid() async {
try {
debugPrint('🤖 Destruction Android...');
// Méthode 1: Destruction totale
try {
await Hive.deleteFromDisk();
debugPrint('✅ Destruction Android complète');
return;
} catch (e) {
debugPrint('⚠️ Destruction Android totale échouée, méthode alternative...');
}
// Méthode 2: Suppression des fichiers .hive et .lock
try {
final appDir = await getApplicationDocumentsDirectory();
final files = await appDir.list().toList();
int deletedCount = 0;
for (final file in files) {
final fileName = file.path.split('/').last;
if (fileName.endsWith('.hive') || fileName.endsWith('.lock')) {
try {
await file.delete();
deletedCount++;
} catch (e) {
debugPrint('⚠️ Erreur suppression fichier $fileName: $e');
}
}
}
debugPrint('$deletedCount fichiers Android supprimés');
} catch (e) {
debugPrint('⚠️ Suppression fichiers Android échouée: $e');
}
// Méthode 3: Fallback Box par Box
await _fallbackDeleteBoxes();
} catch (e) {
debugPrint('❌ Erreur destruction Android: $e');
}
}
/// Destruction des données sur Desktop
Future<void> _destroyDataDesktop() async {
try {
debugPrint('🖥️ Destruction Desktop...');
// Destruction totale
await Hive.deleteFromDisk();
debugPrint('✅ Destruction Desktop complète');
} catch (e) {
debugPrint('⚠️ Destruction Desktop échouée, fallback...');
await _fallbackDeleteBoxes();
}
}
/// Fallback : suppression Box par Box
Future<void> _fallbackDeleteBoxes() async {
debugPrint('🔄 Fallback: suppression Box par Box...');
for (final config in _boxConfigs) {
try {
await Hive.deleteBoxFromDisk(config.name);
debugPrint('🗑️ Box fallback ${config.name} supprimée');
} catch (e) {
debugPrint('⚠️ Erreur suppression fallback ${config.name}: $e');
}
}
}
/// Création de toutes les Box vides et propres
Future<void> _createAllBoxes() async {
try {
debugPrint('🆕 Création de toutes les Box...');
for (int i = 0; i < _boxConfigs.length; i++) {
final config = _boxConfigs[i];
try {
await _createSingleBox(config);
debugPrint('✅ Box ${config.name} créée (${i + 1}/${_boxConfigs.length})');
} catch (e) {
debugPrint('❌ Erreur création ${config.name}: $e');
// Continuer même en cas d'erreur
}
// Petite pause entre les créations
await Future.delayed(const Duration(milliseconds: 100));
}
debugPrint('✅ Toutes les Box ont été créées');
} catch (e) {
debugPrint('❌ Erreur création des Box: $e');
rethrow;
}
}
/// Création d'une Box individuelle avec le bon type
Future<void> _createSingleBox(HiveBoxConfig config) async {
try {
if (Hive.isBoxOpen(config.name)) {
debugPrint(' Box ${config.name} déjà ouverte');
return;
}
switch (config.type) {
case 'UserModel':
await Hive.openBox<UserModel>(config.name);
break;
case 'AmicaleModel':
await Hive.openBox<AmicaleModel>(config.name);
break;
case 'ClientModel':
await Hive.openBox<ClientModel>(config.name);
break;
case 'OperationModel':
await Hive.openBox<OperationModel>(config.name);
break;
case 'SectorModel':
await Hive.openBox<SectorModel>(config.name);
break;
case 'PassageModel':
await Hive.openBox<PassageModel>(config.name);
break;
case 'MembreModel':
await Hive.openBox<MembreModel>(config.name);
break;
case 'UserSectorModel':
await Hive.openBox<UserSectorModel>(config.name);
break;
// Chat boxes removed - handled by new chat module
default:
// Pour Settings, Regions, etc.
await Hive.openBox(config.name);
}
} catch (e) {
debugPrint('❌ Erreur création spécifique ${config.name}: $e');
// Fallback : essayer sans type
try {
await Hive.openBox(config.name);
debugPrint('⚠️ Box ${config.name} créée sans type');
} catch (e2) {
debugPrint('❌ Échec total ${config.name}: $e2');
rethrow;
}
}
}
// === MÉTHODES UTILITAIRES ===
/// Vider une Box individuelle
Future<void> _clearSingleBox(String boxName) async {
try {
if (Hive.isBoxOpen(boxName)) {
await Hive.box(boxName).clear();
debugPrint('🧹 Box $boxName vidée');
} else {
debugPrint(' Box $boxName n\'est pas ouverte, impossible de la vider');
}
} catch (e) {
debugPrint('⚠️ Erreur vidage $boxName: $e');
}
}
/// Vérification que toutes les Box sont ouvertes
bool areAllBoxesOpen() {
for (final config in _boxConfigs) {
if (!Hive.isBoxOpen(config.name)) {
debugPrint('❌ Box ${config.name} n\'est pas ouverte');
return false;
}
}
debugPrint('✅ Toutes les Box sont ouvertes');
return true;
}
/// Vérification rapide que les boxes critiques sont initialisées
/// Utilisé par LoginPage pour détecter si une redirection vers SplashPage est nécessaire
bool areBoxesInitialized() {
try {
// Vérifier seulement les boxes critiques pour le login
final criticalBoxes = [
AppKeys.userBoxName, // Nécessaire pour getCurrentUser
AppKeys.membresBoxName, // Nécessaire pour le pré-remplissage
AppKeys.settingsBoxName, // Nécessaire pour les préférences
];
for (final boxName in criticalBoxes) {
if (!Hive.isBoxOpen(boxName)) {
debugPrint('⚠️ Box critique non ouverte: $boxName');
return false;
}
}
// Vérifier aussi le flag d'initialisation
if (!_isInitialized) {
debugPrint('⚠️ HiveService non initialisé');
return false;
}
return true;
} catch (e) {
debugPrint('❌ Erreur vérification boxes: $e');
return false;
}
}
/// Récupération sécurisée d'une Box typée
Box<T> getTypedBox<T>(String boxName) {
if (!Hive.isBoxOpen(boxName)) {
throw Exception('La Box $boxName n\'est pas ouverte');
}
return Hive.box<T>(boxName);
}
/// Récupération sécurisée d'une Box non-typée
Box getBox(String boxName) {
if (!Hive.isBoxOpen(boxName)) {
throw Exception('La Box $boxName n\'est pas ouverte');
}
return Hive.box(boxName);
}
/// Liste des noms de toutes les Box configurées
List<String> getAllBoxNames() {
return _boxConfigs.map((config) => config.name).toList();
}
/// Diagnostic complet de l'état des Box
Map<String, bool> getDiagnostic() {
final diagnostic = <String, bool>{};
for (final config in _boxConfigs) {
diagnostic[config.name] = Hive.isBoxOpen(config.name);
}
return diagnostic;
}
/// Reset complet du service (pour tests)
static void reset() {
_instance = null;
}
}
/// Configuration d'une Box Hive avec type
class HiveBoxConfig<T> {
final String name;
final String type;
const HiveBoxConfig(this.name, this.type);
}