import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import 'package:hive_flutter/hive_flutter.dart'; import 'package:geosector_app/core/services/app_info_service.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/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'; import 'dart:async'; import 'dart:math' as math; import 'package:flutter/foundation.dart' show kIsWeb; import 'package:package_info_plus/package_info_plus.dart'; import 'package:url_launcher/url_launcher.dart'; class SplashPage extends StatefulWidget { const SplashPage({super.key}); @override State createState() => _SplashPageState(); } // Class pour dessiner les petits points blancs sur le fond class DotsPainter extends CustomPainter { @override void paint(Canvas canvas, Size size) { final paint = Paint() ..color = Colors.white.withOpacity(0.5) ..style = PaintingStyle.fill; final random = math.Random(42); // Seed fixe pour consistance final numberOfDots = (size.width * size.height) ~/ 1500; for (int i = 0; i < numberOfDots; i++) { final x = random.nextDouble() * size.width; final y = random.nextDouble() * size.height; final radius = 1.0 + random.nextDouble() * 2.0; canvas.drawCircle(Offset(x, y), radius, paint); } } @override bool shouldRepaint(covariant CustomPainter oldDelegate) => false; } class _SplashPageState extends State with SingleTickerProviderStateMixin { late AnimationController _animationController; late Animation _scaleAnimation; bool _isInitializing = true; String _statusMessage = "Initialisation..."; double _progress = 0.0; bool _showButtons = false; String _appVersion = ''; Future _getAppVersion() async { try { final packageInfo = await PackageInfo.fromPlatform(); if (mounted) { setState(() { _appVersion = packageInfo.version; }); } } catch (e) { debugPrint('Erreur lors de la récupération de la version: $e'); if (mounted) { setState(() { _appVersion = AppInfoService.fullVersion.split(' ').last; }); } } } @override void initState() { super.initState(); // Animation controller _animationController = AnimationController( vsync: this, duration: const Duration(seconds: 5), ); _scaleAnimation = Tween( begin: 4.0, end: 1.0, ).animate( CurvedAnimation( parent: _animationController, curve: Curves.easeOutBack, ), ); _animationController.forward(); _getAppVersion(); _startInitialization(); } @override void dispose() { _animationController.dispose(); super.dispose(); } void _startInitialization() async { // Table rase complète et recréation propre await _completeReset(); // Finalisation if (mounted) { setState(() { _statusMessage = "Application prête !"; _progress = 1.0; _isInitializing = false; _showButtons = true; }); } } /// RESET COMPLET : Destruction totale et recréation propre Future _completeReset() async { try { debugPrint('🧹 RESET COMPLET : Destruction totale des données Hive...'); // Étape 1: Sauvegarder les utilisateurs existants (optionnel) Map? existingUsers; if (mounted) { setState(() { _statusMessage = "Sauvegarde des utilisateurs..."; _progress = 0.05; }); } try { if (Hive.isBoxOpen(AppKeys.userBoxName)) { final userBox = Hive.box(AppKeys.userBoxName); existingUsers = Map.from(userBox.toMap()); debugPrint('📦 ${existingUsers.length} utilisateurs sauvegardés'); } } catch (e) { debugPrint('⚠️ Erreur sauvegarde utilisateurs: $e'); existingUsers = null; } // Étape 2: DESTRUCTION RADICALE - Fermer tout ce qui peut être ouvert if (mounted) { setState(() { _statusMessage = "Fermeture de toutes les bases de données..."; _progress = 0.15; }); } await _closeAllKnownBoxes(); // Étape 3: DESTRUCTION RADICALE - Supprimer tout Hive du disque if (mounted) { setState(() { _statusMessage = "Suppression complète des anciennes données..."; _progress = 0.25; }); } await _nukeHiveCompletely(); // Étape 4: RECRÉATION PROPRE if (mounted) { setState(() { _statusMessage = "Création des nouvelles bases de données..."; _progress = 0.40; }); } await _createAllBoxesFresh(); // Étape 5: Restaurer les utilisateurs (optionnel) if (existingUsers != null && existingUsers.isNotEmpty) { if (mounted) { setState(() { _statusMessage = "Restauration des utilisateurs..."; _progress = 0.80; }); } await _restoreUsers(existingUsers); } // Étape 6: Vérification finale if (mounted) { setState(() { _statusMessage = "Vérification des bases de données..."; _progress = 0.90; }); } debugPrint('✅ RESET COMPLET terminé avec succès'); } catch (e) { debugPrint('❌ Erreur lors du reset complet: $e'); if (mounted) { setState(() { _statusMessage = "Erreur critique - Redémarrage recommandé"; _progress = 1.0; _isInitializing = false; _showButtons = true; }); } } } /// Ferme toutes les boîtes connues Future _closeAllKnownBoxes() async { try { final allKnownBoxes = [ AppKeys.userBoxName, AppKeys.amicaleBoxName, AppKeys.clientsBoxName, AppKeys.regionsBoxName, AppKeys.operationsBoxName, AppKeys.sectorsBoxName, AppKeys.passagesBoxName, AppKeys.membresBoxName, AppKeys.userSectorBoxName, AppKeys.settingsBoxName, AppKeys.chatConversationsBoxName, AppKeys.chatMessagesBoxName, // Boîtes potentiellement problématiques 'auth', 'locations', 'messages', 'temp' ]; debugPrint('🔒 Fermeture de ${allKnownBoxes.length} boîtes connues...'); for (final boxName in allKnownBoxes) { try { if (Hive.isBoxOpen(boxName)) { await Hive.box(boxName).close(); debugPrint('✅ Boîte $boxName fermée'); } } catch (e) { debugPrint('⚠️ Erreur fermeture $boxName: $e'); // Continuer même en cas d'erreur } } await Future.delayed(const Duration(milliseconds: 1000)); } catch (e) { debugPrint('❌ Erreur fermeture des boîtes: $e'); } } /// Suppression RADICALE de tout Hive Future _nukeHiveCompletely() async { try { debugPrint('💥 DESTRUCTION NUCLÉAIRE de Hive...'); if (kIsWeb) { // En version web, supprimer toutes les boîtes possibles une par une final allPossibleBoxes = [ AppKeys.userBoxName, AppKeys.amicaleBoxName, AppKeys.clientsBoxName, AppKeys.regionsBoxName, AppKeys.operationsBoxName, AppKeys.sectorsBoxName, AppKeys.passagesBoxName, AppKeys.membresBoxName, AppKeys.userSectorBoxName, AppKeys.settingsBoxName, AppKeys.chatConversationsBoxName, AppKeys.chatMessagesBoxName, // Toutes les boîtes potentiellement corrompues 'auth', 'locations', 'messages', 'temp', 'cache', 'data' ]; for (final boxName in allPossibleBoxes) { try { await Hive.deleteBoxFromDisk(boxName); debugPrint('✅ Boîte $boxName DÉTRUITE'); } catch (e) { debugPrint('⚠️ Erreur destruction $boxName: $e'); } } } else { // Sur mobile/desktop, destruction totale try { await Hive.deleteFromDisk(); debugPrint('✅ Hive COMPLÈTEMENT DÉTRUIT'); } catch (e) { debugPrint('⚠️ Erreur destruction totale: $e'); // Fallback : supprimer boîte par boîte await _deleteBoxesOneByOne(); } } // Attendre pour s'assurer que tout est détruit await Future.delayed(const Duration(seconds: 2)); } catch (e) { debugPrint('❌ Erreur destruction Hive: $e'); } } /// Fallback : supprimer les boîtes une par une Future _deleteBoxesOneByOne() async { final allBoxes = [ AppKeys.userBoxName, AppKeys.amicaleBoxName, AppKeys.clientsBoxName, AppKeys.regionsBoxName, AppKeys.operationsBoxName, AppKeys.sectorsBoxName, AppKeys.passagesBoxName, AppKeys.membresBoxName, AppKeys.userSectorBoxName, AppKeys.settingsBoxName, AppKeys.chatConversationsBoxName, AppKeys.chatMessagesBoxName, ]; for (final boxName in allBoxes) { try { await Hive.deleteBoxFromDisk(boxName); debugPrint('✅ Boîte $boxName supprimée (fallback)'); } catch (e) { debugPrint('⚠️ Erreur suppression fallback $boxName: $e'); } } } /// Recrée toutes les boîtes VIDES et PROPRES Future _createAllBoxesFresh() async { try { debugPrint('🆕 Création de toutes les boîtes vides...'); final boxesToCreate = [ {'name': AppKeys.userBoxName, 'type': 'UserModel'}, {'name': AppKeys.amicaleBoxName, 'type': 'AmicaleModel'}, {'name': AppKeys.clientsBoxName, 'type': 'ClientModel'}, {'name': AppKeys.regionsBoxName, 'type': 'dynamic'}, {'name': AppKeys.operationsBoxName, 'type': 'OperationModel'}, {'name': AppKeys.sectorsBoxName, 'type': 'SectorModel'}, {'name': AppKeys.passagesBoxName, 'type': 'PassageModel'}, {'name': AppKeys.membresBoxName, 'type': 'MembreModel'}, {'name': AppKeys.userSectorBoxName, 'type': 'UserSectorModel'}, {'name': AppKeys.settingsBoxName, 'type': 'dynamic'}, {'name': AppKeys.chatConversationsBoxName, 'type': 'ConversationModel'}, {'name': AppKeys.chatMessagesBoxName, 'type': 'MessageModel'}, ]; final progressIncrement = 0.35 / boxesToCreate.length; // De 0.40 à 0.75 for (int i = 0; i < boxesToCreate.length; i++) { final boxInfo = boxesToCreate[i]; final boxName = boxInfo['name'] as String; final boxType = boxInfo['type'] as String; if (mounted) { setState(() { _statusMessage = "Création de $boxName..."; _progress = 0.40 + (progressIncrement * i); }); } try { // Créer la boîte avec le bon type switch (boxType) { case 'UserModel': await Hive.openBox(boxName); break; case 'AmicaleModel': await Hive.openBox(boxName); break; case 'ClientModel': await Hive.openBox(boxName); break; case 'OperationModel': await Hive.openBox(boxName); break; case 'SectorModel': await Hive.openBox(boxName); break; case 'PassageModel': await Hive.openBox(boxName); break; case 'MembreModel': await Hive.openBox(boxName); break; case 'UserSectorModel': await Hive.openBox(boxName); break; case 'ConversationModel': await Hive.openBox(boxName); break; case 'MessageModel': await Hive.openBox(boxName); break; default: await Hive.openBox(boxName); } debugPrint('✅ Boîte $boxName créée (type: $boxType)'); } catch (e) { debugPrint('❌ Erreur création $boxName: $e'); // En cas d'erreur, essayer sans type try { await Hive.openBox(boxName); debugPrint('⚠️ Boîte $boxName créée sans type'); } catch (e2) { debugPrint('❌ Échec total création $boxName: $e2'); } } await Future.delayed(const Duration(milliseconds: 200)); } } catch (e) { debugPrint('❌ Erreur création des boîtes: $e'); } } /// Restaure les utilisateurs sauvegardés Future _restoreUsers(Map users) async { try { if (Hive.isBoxOpen(AppKeys.userBoxName)) { final userBox = Hive.box(AppKeys.userBoxName); for (final entry in users.entries) { try { await userBox.put(entry.key, entry.value); } catch (e) { debugPrint('⚠️ Erreur restauration utilisateur ${entry.key}: $e'); } } debugPrint('✅ ${users.length} utilisateurs restaurés'); } } catch (e) { debugPrint('❌ Erreur restauration utilisateurs: $e'); } } @override Widget build(BuildContext context) { final theme = Theme.of(context); return Scaffold( body: Stack( children: [ // Fond dégradé avec petits points blancs AnimatedContainer( duration: const Duration(milliseconds: 500), decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [Colors.white, Colors.blue.shade300], ), ), child: CustomPaint( painter: DotsPainter(), child: const SizedBox(width: double.infinity, height: double.infinity), ), ), // Contenu principal SafeArea( child: Center( child: Padding( padding: const EdgeInsets.symmetric(horizontal: 40), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ const Spacer(flex: 2), // Logo avec animation AnimatedBuilder( animation: _scaleAnimation, builder: (context, child) { return Transform.scale( scale: _scaleAnimation.value, child: child, ); }, child: Image.asset( 'assets/images/logo-geosector-1024.png', height: 180, ), ), const SizedBox(height: 24), // Titre AnimatedOpacity( opacity: _isInitializing ? 0.9 : 1.0, duration: const Duration(milliseconds: 500), child: Text( 'Geosector', style: theme.textTheme.headlineLarge?.copyWith( color: theme.colorScheme.primary, fontWeight: FontWeight.bold, letterSpacing: 1.2, ), ), ), const SizedBox(height: 16), // Sous-titre AnimatedOpacity( opacity: _isInitializing ? 0.8 : 1.0, duration: const Duration(milliseconds: 500), child: Text( 'Une application puissante et intuitive de gestion de vos distributions de calendriers', textAlign: TextAlign.center, style: theme.textTheme.bodyLarge?.copyWith( color: theme.colorScheme.onSurface.withOpacity(0.7), fontWeight: FontWeight.w500, ), ), ), const Spacer(flex: 1), // Indicateur de chargement if (_isInitializing) ...[ Padding( padding: const EdgeInsets.symmetric(horizontal: 40), child: ClipRRect( borderRadius: BorderRadius.circular(8), child: LinearProgressIndicator( value: _progress, backgroundColor: Colors.grey.withOpacity(0.2), valueColor: AlwaysStoppedAnimation( theme.colorScheme.primary, ), minHeight: 10, ), ), ), const SizedBox(height: 16), Text( _statusMessage, style: theme.textTheme.bodyMedium?.copyWith( color: theme.colorScheme.onSurface.withOpacity(0.7), ), ), ], // Boutons (reste identique) if (_showButtons) ...[ // Bouton Connexion Utilisateur AnimatedOpacity( opacity: _showButtons ? 1.0 : 0.0, duration: const Duration(milliseconds: 500), child: ElevatedButton( onPressed: () { context.go('/login/user'); }, style: ElevatedButton.styleFrom( backgroundColor: Colors.green, foregroundColor: Colors.white, padding: const EdgeInsets.symmetric( horizontal: 40, vertical: 16, ), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(30), ), elevation: 2, ), child: const Text( 'Connexion Utilisateur', style: TextStyle( fontSize: 16, fontWeight: FontWeight.bold, ), ), ), ), const SizedBox(height: 16), // Bouton Connexion Administrateur AnimatedOpacity( opacity: _showButtons ? 1.0 : 0.0, duration: const Duration(milliseconds: 500), child: ElevatedButton( onPressed: () { context.go('/login/admin'); }, style: ElevatedButton.styleFrom( backgroundColor: Colors.red, foregroundColor: Colors.white, padding: const EdgeInsets.symmetric( horizontal: 40, vertical: 16, ), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(30), ), elevation: 2, ), child: const Text( 'Connexion Administrateur', style: TextStyle( fontSize: 16, fontWeight: FontWeight.bold, ), ), ), ), const SizedBox(height: 32), // Bouton d'inscription AnimatedOpacity( opacity: _showButtons ? 1.0 : 0.0, duration: const Duration(milliseconds: 500), child: ElevatedButton( onPressed: () { context.go('/register'); }, style: ElevatedButton.styleFrom( backgroundColor: Colors.blue, foregroundColor: Colors.white, padding: const EdgeInsets.symmetric( horizontal: 40, vertical: 16, ), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(30), ), elevation: 2, ), child: const Text( 'Pas encore inscrit ?', style: TextStyle( fontSize: 16, fontWeight: FontWeight.bold, ), ), ), ), const SizedBox(height: 16), // Lien vers le site web AnimatedOpacity( opacity: _showButtons ? 1.0 : 0.0, duration: const Duration(milliseconds: 500), child: TextButton.icon( onPressed: () { String webUrl = 'https://geosector.fr'; if (kIsWeb) { final host = Uri.base.host; if (host.startsWith('dapp.')) { webUrl = 'https://dev.geosector.fr'; } else if (host.startsWith('rapp.')) { webUrl = 'https://rec.geosector.fr'; } else if (host.startsWith('app.')) { webUrl = 'https://geosector.fr'; } } launchUrl( Uri.parse(webUrl), mode: LaunchMode.externalApplication, ); }, icon: Icon( Icons.language, size: 18, color: theme.colorScheme.primary, ), label: Text( 'Site web Geosector', style: TextStyle( color: theme.colorScheme.primary, fontWeight: FontWeight.w500, ), ), ), ), ], const Spacer(flex: 1), ], ), ), ), ), // Badge de version if (_appVersion.isNotEmpty) Positioned( bottom: 16, right: 16, child: AnimatedOpacity( opacity: _showButtons ? 0.7 : 0.5, duration: const Duration(milliseconds: 500), child: Container( padding: const EdgeInsets.symmetric( horizontal: 8, vertical: 4, ), decoration: BoxDecoration( color: theme.colorScheme.primary.withOpacity(0.1), borderRadius: BorderRadius.circular(12), border: Border.all( color: theme.colorScheme.primary, width: 1, ), ), child: Text( 'v$_appVersion', style: theme.textTheme.bodySmall?.copyWith( color: theme.colorScheme.primary, fontSize: 10, fontWeight: FontWeight.w500, ), ), ), ), ), ], ), ); } }