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'); // Fallback sur la version du AppInfoService si elle existe if (mounted) { setState(() { _appVersion = AppInfoService.fullVersion.split(' ').last; // Extraire juste le numéro }); } } } @override void initState() { super.initState(); // Animation controller sur 5 secondes (augmenté de 3 à 5 secondes) _animationController = AnimationController( vsync: this, duration: const Duration(seconds: 5), ); // Animation de 4x la taille à 1x la taille (augmenté de 3x à 4x) _scaleAnimation = Tween( begin: 4.0, // Commencer à 4x la taille end: 1.0, // Terminer à la taille normale ).animate( CurvedAnimation( parent: _animationController, curve: Curves.easeOutBack, // Curve pour un effet de rebond ), ); // Démarrer l'animation immédiatement _animationController.forward(); _getAppVersion(); // Simuler le processus d'initialisation _startInitialization(); } @override void dispose() { _animationController.dispose(); super.dispose(); } void _startInitialization() async { // Étape 1: Initialisation des boîtes Hive (0% à 75%) if (mounted) { setState(() { _statusMessage = "Initialisation des données..."; _progress = 0.0; }); } // Initialiser toutes les boîtes Hive await _initializeAllHiveBoxes(); await Future.delayed(const Duration(milliseconds: 500)); // Étape 2: Initialisation des services (75% à 100%) if (mounted) { setState(() { _statusMessage = "Préparation de l'application..."; _progress = 0.75; }); } await Future.delayed(const Duration(milliseconds: 500)); if (mounted) { setState(() { _isInitializing = false; _showButtons = true; _progress = 1.0; // S'assurer que la barre est à 100% }); } } // Méthode pour initialiser toutes les boîtes Hive Future _initializeAllHiveBoxes() async { try { debugPrint('Initialisation de toutes les boîtes Hive...'); // Structure pour les boîtes à ouvrir avec leurs noms d'affichage final boxesToOpen = [ {'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.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.settingsBoxName, 'display': 'Préparation paramètres'}, {'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) final progressIncrement = 0.75 / boxesToOpen.length; double currentProgress = 0.0; // Ouvrir chaque boîte si elle n'est pas déjà ouverte for (int i = 0; i < boxesToOpen.length; i++) { final boxName = boxesToOpen[i]['name'] as String; final displayName = boxesToOpen[i]['display'] as String; // Mettre à jour la barre de progression et le message currentProgress = progressIncrement * (i + 1); if (mounted) { setState(() { _statusMessage = displayName; _progress = currentProgress; }); } if (!Hive.isBoxOpen(boxName)) { debugPrint('Ouverture de la boîte $boxName ($displayName)...'); // Ouvrir la boîte avec le type approprié if (boxName == AppKeys.userBoxName) { await Hive.openBox(boxName); } else if (boxName == AppKeys.amicaleBoxName) { await Hive.openBox(boxName); } else if (boxName == AppKeys.clientsBoxName) { await Hive.openBox(boxName); } else if (boxName == AppKeys.regionsBoxName) { // Ouvrir la boîte des régions sans type spécifique pour l'instant // car RegionModelAdapter n'est pas encore enregistré await Hive.openBox(boxName); } else if (boxName == AppKeys.operationsBoxName) { await Hive.openBox(boxName); } else if (boxName == AppKeys.sectorsBoxName) { await Hive.openBox(boxName); } else if (boxName == AppKeys.passagesBoxName) { await Hive.openBox(boxName); } else if (boxName == AppKeys.membresBoxName) { await Hive.openBox(boxName); } else if (boxName == AppKeys.userSectorBoxName) { await Hive.openBox(boxName); } else if (boxName == AppKeys.chatConversationsBoxName) { await Hive.openBox(boxName); } else if (boxName == AppKeys.chatMessagesBoxName) { await Hive.openBox(boxName); } else { await Hive.openBox(boxName); } debugPrint('Boîte $boxName ouverte avec succès'); } else { debugPrint('Boîte $boxName déjà ouverte'); } // Ajouter une temporisation entre chaque ouverture await Future.delayed(const Duration(milliseconds: 500)); } // Mettre à jour la barre de progression à 0.2 (20%) à la fin if (mounted) { setState(() { _statusMessage = 'Toutes les boîtes sont prêtes'; _progress = 0.8; }); await Future.delayed(const Duration(milliseconds: 500)); } if (mounted) { setState(() { _statusMessage = "Préparation de l'application..."; _progress = 0.9; }); await Future.delayed(const Duration(milliseconds: 500)); } // Finalisation if (mounted) { setState(() { _isInitializing = false; _showButtons = true; _progress = 1.0; }); } debugPrint('Toutes les boîtes Hive sont maintenant ouvertes'); } catch (e) { debugPrint('Erreur lors de l\'initialisation des boîtes Hive: $e'); // En cas d'erreur, mettre à jour le message if (mounted) { setState(() { _statusMessage = 'Erreur lors de l\'initialisation des données'; }); } } } @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 de réduction 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, // Augmenté de 140 à 180 ), ), const SizedBox(height: 24), // Titre avec animation fade-in 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 avec nouveau slogan 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, // Augmenté de 6 à 10 ), ), ), const SizedBox(height: 16), Text( _statusMessage, style: theme.textTheme.bodyMedium?.copyWith( color: theme.colorScheme.onSurface.withOpacity(0.7), ), ), ], // Boutons après l'initialisation if (_showButtons) ...[ // Bouton Connexion Utilisateur AnimatedOpacity( opacity: _showButtons ? 1.0 : 0.0, duration: const Duration(milliseconds: 500), child: ElevatedButton( onPressed: () { context.go('/login/user'); // Utiliser la route spécifique }, 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'); // Utiliser la route spécifique }, 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), // 2 espaces sous le bouton précédent // 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: () { // Déterminer l'URL du site web en fonction de l'environnement 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'; } } // Ouvrir l'URL dans une nouvelle fenêtre/onglet 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 en bas à droite 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, ), ), ), ), ), ], ), ); } }