Files
geo/app/lib/core/services/hive_service.dart
pierre 2f5946a184 feat: Version 3.5.2 - Configuration Stripe et gestion des immeubles
- Configuration complète Stripe pour les 3 environnements (DEV/REC/PROD)
  * DEV: Clés TEST Pierre (mode test)
  * REC: Clés TEST Client (mode test)
  * PROD: Clés LIVE Client (mode live)
- Ajout de la gestion des bases de données immeubles/bâtiments
  * Configuration buildings_database pour DEV/REC/PROD
  * Service BuildingService pour enrichissement des adresses
- Optimisations pages et améliorations ergonomie
- Mises à jour des dépendances Composer
- Nettoyage des fichiers obsolètes

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-09 18:26:27 +01:00

627 lines
20 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 'dart:async';
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/core/data/models/pending_request.dart';
import 'package:geosector_app/chat/models/room.dart';
import 'package:geosector_app/chat/models/message.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'),
// Chat boxes
HiveBoxConfig<Room>(AppKeys.chatRoomsBoxName, 'Room'),
HiveBoxConfig<Message>(AppKeys.chatMessagesBoxName, 'Message'),
// Queue offline boxes
HiveBoxConfig<PendingRequest>(AppKeys.pendingRequestsBoxName, 'PendingRequest'),
HiveBoxConfig<dynamic>(AppKeys.tempEntitiesBoxName, 'TempEntities'),
// Dynamic boxes
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...');
// PROTECTION CRITIQUE : Vérifier la box pending_requests
if (Hive.isBoxOpen(AppKeys.pendingRequestsBoxName)) {
final pendingBox = Hive.box(AppKeys.pendingRequestsBoxName);
if (pendingBox.isNotEmpty) {
debugPrint('⚠️ ATTENTION: ${pendingBox.length} requêtes en attente trouvées dans pending_requests');
debugPrint('⚠️ Cette box NE SERA PAS supprimée pour préserver les données');
// On ne supprime PAS cette box si elle contient des données
}
}
// 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 {
// PROTECTION : Ne pas supprimer pending_requests si elle contient des données
if (config.name == AppKeys.pendingRequestsBoxName) {
if (Hive.isBoxOpen(AppKeys.pendingRequestsBoxName)) {
final box = Hive.box(AppKeys.pendingRequestsBoxName);
if (box.isNotEmpty) {
debugPrint('⏭️ Box ${config.name} ignorée (contient ${box.length} requêtes en attente)');
continue;
}
}
}
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 {
// Ajouter un timeout de 10 secondes pour éviter les blocages infinis
await _createSingleBox(config).timeout(
const Duration(seconds: 10),
onTimeout: () {
throw TimeoutException('Timeout lors de la création de la box ${config.name}');
},
);
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 'Room':
await Hive.openBox<Room>(config.name);
break;
case 'Message':
await Hive.openBox<Message>(config.name);
break;
case 'PendingRequest':
await Hive.openBox<PendingRequest>(config.name);
break;
case 'TempEntities':
// Box dynamique pour stocker les entités temporaires
await Hive.openBox(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)) {
// Récupérer la configuration pour connaître le type
final config = _boxConfigs.firstWhere(
(c) => c.name == boxName,
orElse: () => HiveBoxConfig(boxName, 'dynamic'),
);
// Utiliser la box typée selon le modèle
switch (config.type) {
case 'UserModel':
await Hive.box<UserModel>(boxName).clear();
break;
case 'AmicaleModel':
await Hive.box<AmicaleModel>(boxName).clear();
break;
case 'ClientModel':
await Hive.box<ClientModel>(boxName).clear();
break;
case 'OperationModel':
await Hive.box<OperationModel>(boxName).clear();
break;
case 'SectorModel':
await Hive.box<SectorModel>(boxName).clear();
break;
case 'PassageModel':
await Hive.box<PassageModel>(boxName).clear();
break;
case 'MembreModel':
await Hive.box<MembreModel>(boxName).clear();
break;
case 'UserSectorModel':
await Hive.box<UserSectorModel>(boxName).clear();
break;
case 'Room':
await Hive.box<Room>(boxName).clear();
break;
case 'Message':
await Hive.box<Message>(boxName).clear();
break;
case 'PendingRequest':
// ATTENTION : Ne jamais vider pending_requests si elle contient des données critiques
final pendingBox = Hive.box<PendingRequest>(boxName);
if (pendingBox.isNotEmpty) {
debugPrint('⚠️ ATTENTION: Box $boxName contient ${pendingBox.length} requêtes - Vidage ignoré');
return; // Ne pas vider cette box
}
await pendingBox.clear();
break;
case 'TempEntities':
await Hive.box(boxName).clear();
break;
default:
// Pour les box non typées (settings, regions, etc.)
await Hive.box(boxName).clear();
break;
}
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);
}