500 lines
16 KiB
Dart
500 lines
16 KiB
Dart
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';
|
||
import 'package:geosector_app/chat/models/conversation_model.dart';
|
||
import 'package:geosector_app/chat/models/message_model.dart';
|
||
|
||
/// 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'),
|
||
HiveBoxConfig<ConversationModel>(AppKeys.chatConversationsBoxName, 'ConversationModel'),
|
||
HiveBoxConfig<MessageModel>(AppKeys.chatMessagesBoxName, 'MessageModel'),
|
||
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;
|
||
case 'ConversationModel':
|
||
await Hive.openBox<ConversationModel>(config.name);
|
||
break;
|
||
case 'MessageModel':
|
||
await Hive.openBox<MessageModel>(config.name);
|
||
break;
|
||
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;
|
||
}
|
||
|
||
/// 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);
|
||
}
|