// lib/services/connectivity_service.dart - VERSION AVEC SUPPORT ENTREPRISE import 'dart:async'; import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:provider/provider.dart'; import '../main.dart'; // Pour accéder à AuthController class ConnectivityService extends ChangeNotifier { static final ConnectivityService _instance = ConnectivityService._internal(); factory ConnectivityService() => _instance; ConnectivityService._internal(); bool _isConnected = true; bool _isChecking = false; Timer? _periodicTimer; bool _hasNetworkInterface = true; DateTime? _lastDisconnectionTime; int _reconnectionAttempts = 0; bool get isConnected => _isConnected; bool get hasNetworkInterface => _hasNetworkInterface; DateTime? get lastDisconnectionTime => _lastDisconnectionTime; int get reconnectionAttempts => _reconnectionAttempts; // Clé globale pour accéder au navigator static GlobalKey? navigatorKey; /// Initialiser le service avec la clé navigator static void setNavigatorKey(GlobalKey key) { navigatorKey = key; print('✅ NavigatorKey configuré pour ConnectivityService'); } /// Démarrer la surveillance void startMonitoring() { print('🌐 Démarrage de la surveillance de connectivité'); _periodicTimer?.cancel(); _periodicTimer = Timer.periodic(Duration(seconds: 5), (timer) { checkConnectivity(); }); // Vérification initiale après un délai Future.delayed(Duration(seconds: 2), () { checkConnectivity(); }); } /// Vérifier la connectivité Future checkConnectivity() async { if (_isChecking) return; _isChecking = true; try { print('🔍 Vérification de la connectivité...'); // Étape 1: Vérifier les interfaces réseau _hasNetworkInterface = await _checkNetworkInterfaces(); if (!_hasNetworkInterface) { print('❌ Aucune interface réseau trouvée'); _updateConnectivityStatus(false); return; } // Étape 2: Tester la connectivité internet bool hasInternetAccess = await _testInternetConnectivity(); print('🌍 Accès internet: $hasInternetAccess'); _updateConnectivityStatus(hasInternetAccess); } catch (e) { print('❌ Erreur lors de la vérification: $e'); _updateConnectivityStatus(false); } finally { _isChecking = false; } } /// Vérifier les interfaces réseau Future _checkNetworkInterfaces() async { try { final interfaces = await NetworkInterface.list( includeLinkLocal: false, type: InternetAddressType.any, ); bool hasActiveInterface = interfaces.any( (interface) => !interface.name.toLowerCase().contains('lo') && interface.addresses.isNotEmpty, ); print('📡 Interfaces réseau actives: $hasActiveInterface'); if (hasActiveInterface) { print( ' Interfaces trouvées: ${interfaces.map((i) => i.name).join(", ")}', ); } return hasActiveInterface; } catch (e) { print('❌ Erreur vérification interfaces: $e'); return false; } } /// Tester la connectivité internet avec plusieurs cibles Future _testInternetConnectivity() async { final testTargets = [ {'host': '1.1.1.1', 'port': 53, 'name': 'Cloudflare DNS'}, {'host': '8.8.8.8', 'port': 53, 'name': 'Google DNS'}, {'host': 'google.com', 'port': 80, 'name': 'Google'}, ]; for (var target in testTargets) { try { final socket = await Socket.connect( target['host'] as String, target['port'] as int, timeout: Duration(seconds: 3), ); socket.destroy(); print('✅ Connexion réussie à ${target['name']}'); return true; } catch (e) { print('⚠️ Échec connexion à ${target['name']}: $e'); } } print('❌ Toutes les tentatives de connexion ont échoué'); return false; } /// Mettre à jour le statut de connectivité void _updateConnectivityStatus(bool isConnected) { if (isConnected != _isConnected) { print('🔄 Changement connectivité: $_isConnected -> $isConnected'); final previousStatus = _isConnected; _isConnected = isConnected; if (!_isConnected) { _lastDisconnectionTime = DateTime.now(); _reconnectionAttempts = 0; _showNoConnectionDialog(); } else if (previousStatus == false && _isConnected) { // Reconnexion réussie print('✅ Reconnexion établie'); _reconnectionAttempts = 0; _showReconnectionSuccess(); } notifyListeners(); } } /// Afficher le dialog de perte de connexion void _showNoConnectionDialog() { if (navigatorKey?.currentState == null) { print('⚠️ NavigatorState non disponible'); return; } print('🚨 Affichage du popup de perte de connexion'); navigatorKey!.currentState!.push( PageRouteBuilder( opaque: false, barrierDismissible: false, barrierColor: Colors.black54, pageBuilder: (BuildContext context, _, __) { return WillPopScope( onWillPop: () async => false, child: Dialog( shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(20), ), child: _buildDialogContent(context), ), ); }, ), ); } /// Construire le contenu du dialog de déconnexion Widget _buildDialogContent(BuildContext context) { return Container( padding: EdgeInsets.all(24), child: Column( mainAxisSize: MainAxisSize.min, children: [ // Icône animée TweenAnimationBuilder( duration: Duration(milliseconds: 800), tween: Tween(begin: 0.0, end: 1.0), builder: (context, value, child) { return Transform.scale( scale: value, child: Container( width: 80, height: 80, decoration: BoxDecoration( gradient: LinearGradient( colors: [Colors.red[400]!, Colors.red[600]!], begin: Alignment.topLeft, end: Alignment.bottomRight, ), shape: BoxShape.circle, boxShadow: [ BoxShadow( color: Colors.red.withOpacity(0.3), blurRadius: 20, offset: Offset(0, 10), ), ], ), child: Icon( Icons.wifi_off_rounded, color: Colors.white, size: 40, ), ), ); }, ), SizedBox(height: 24), // Titre Text( 'Problème de connexion', style: TextStyle( fontSize: 24, fontWeight: FontWeight.bold, color: Color(0xFF006699), ), textAlign: TextAlign.center, ), SizedBox(height: 16), // Message détaillé Container( padding: EdgeInsets.all(16), decoration: BoxDecoration( color: Colors.red[50], borderRadius: BorderRadius.circular(12), border: Border.all(color: Colors.red[200]!, width: 1), ), child: Row( children: [ Icon( _hasNetworkInterface ? Icons.cloud_off_rounded : Icons.wifi_off_rounded, color: Colors.red[700], size: 24, ), SizedBox(width: 12), Expanded( child: Text( _getDetailedMessage(), style: TextStyle( fontSize: 14, color: Colors.red[900], height: 1.5, ), ), ), ], ), ), SizedBox(height: 24), // Informations utilisateur (si connecté) Consumer( builder: (context, authController, child) { if (authController.isLoggedIn) { final isEnterprise = authController.isEnterpriseMember; return Container( padding: EdgeInsets.all(12), margin: EdgeInsets.only(bottom: 16), decoration: BoxDecoration( color: isEnterprise ? Color(0xFF8B5CF6).withOpacity(0.1) : Color(0xFF006699).withOpacity(0.1), borderRadius: BorderRadius.circular(12), border: Border.all( color: isEnterprise ? Color(0xFF8B5CF6).withOpacity(0.3) : Color(0xFF006699).withOpacity(0.3), ), ), child: Row( children: [ Icon( isEnterprise ? Icons.business : Icons.person, color: isEnterprise ? Color(0xFF8B5CF6) : Color(0xFF006699), size: 20, ), SizedBox(width: 8), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( authController.agentName ?? 'Agent', style: TextStyle( fontSize: 14, fontWeight: FontWeight.w600, color: Colors.black87, ), overflow: TextOverflow.ellipsis, ), if (isEnterprise && authController.enterprise?.nomEntreprise != null) ...[ SizedBox(height: 2), Text( authController.enterprise!.nomEntreprise, style: TextStyle( fontSize: 12, color: Colors.grey[600], ), overflow: TextOverflow.ellipsis, ), ], ], ), ), Container( padding: EdgeInsets.symmetric( horizontal: 8, vertical: 4, ), decoration: BoxDecoration( color: isEnterprise ? Color(0xFF8B5CF6) : Color(0xFF006699), borderRadius: BorderRadius.circular(8), ), child: Text( isEnterprise ? 'Entreprise' : 'Agent', style: TextStyle( color: Colors.white, fontSize: 10, fontWeight: FontWeight.w600, ), ), ), ], ), ); } return SizedBox.shrink(); }, ), // Boutons d'action Row( children: [ Expanded( child: OutlinedButton.icon( onPressed: () { Navigator.of(context).pop(); _reconnectionAttempts++; checkConnectivity(); }, icon: Icon(Icons.refresh_rounded, size: 20), label: Text('Réessayer'), style: OutlinedButton.styleFrom( padding: EdgeInsets.symmetric(vertical: 16), side: BorderSide(color: Color(0xFF006699), width: 2), foregroundColor: Color(0xFF006699), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), ), ), ), SizedBox(width: 12), Expanded( child: ElevatedButton.icon( onPressed: () { Navigator.of(context).pop(); _openWifiSettings(); }, icon: Icon(Icons.wifi_find_rounded, size: 20), label: Text('WiFi'), style: ElevatedButton.styleFrom( backgroundColor: Color(0xFF006699), foregroundColor: Colors.white, padding: EdgeInsets.symmetric(vertical: 16), elevation: 2, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), ), ), ), ], ), // Compteur de tentatives if (_reconnectionAttempts > 0) ...[ SizedBox(height: 12), Text( 'Tentative ${_reconnectionAttempts + 1}', style: TextStyle( fontSize: 12, color: Colors.grey[600], fontStyle: FontStyle.italic, ), ), ], ], ), ); } /// Message détaillé selon le type de problème String _getDetailedMessage() { if (!_hasNetworkInterface) { return 'Aucun réseau WiFi détecté. Veuillez vous connecter à un réseau WiFi pour utiliser WORTIS Agent.'; } else { return 'Connexion internet indisponible. Vérifiez que votre réseau WiFi a accès à internet.'; } } /// Afficher un message de reconnexion réussie void _showReconnectionSuccess() { if (navigatorKey?.currentContext == null) return; final context = navigatorKey!.currentContext!; ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Row( children: [ Container( padding: EdgeInsets.all(8), decoration: BoxDecoration( color: Colors.white.withOpacity(0.2), shape: BoxShape.circle, ), child: Icon(Icons.wifi_rounded, color: Colors.white, size: 20), ), SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ Text( 'Connexion rétablie', style: TextStyle( color: Colors.white, fontSize: 16, fontWeight: FontWeight.w600, ), ), SizedBox(height: 2), Text( 'Vous pouvez continuer à utiliser l\'application', style: TextStyle( color: Colors.white.withOpacity(0.9), fontSize: 12, ), ), ], ), ), ], ), backgroundColor: Colors.green[600], behavior: SnackBarBehavior.floating, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), duration: Duration(seconds: 3), margin: EdgeInsets.all(16), ), ); } /// Ouvrir les paramètres WiFi void _openWifiSettings() async { try { print('📱 Ouverture des paramètres WiFi'); const platform = MethodChannel('com.wortis.agent/settings'); // Sortir du mode kiosque si activé try { await platform.invokeMethod('exitKioskMode'); } catch (e) { print('⚠️ Mode kiosque non actif ou erreur: $e'); } // Ouvrir les paramètres WiFi await platform.invokeMethod('openWifiSettings'); // Fermer le dialog if (navigatorKey?.currentContext != null) { Navigator.of(navigatorKey!.currentContext!).pop(); } } catch (e) { print('❌ Erreur ouverture paramètres WiFi: $e'); if (navigatorKey?.currentContext != null) { ScaffoldMessenger.of(navigatorKey!.currentContext!).showSnackBar( SnackBar( content: Text('Impossible d\'ouvrir les paramètres WiFi'), backgroundColor: Colors.red, ), ); } } } /// Forcer un test manuel (pour debug) void forceCheck() { print('🔧 Test forcé de connectivité'); checkConnectivity(); } /// Méthode de test pour forcer l'affichage du popup void showTestPopup() { print('🧪 Test d\'affichage du popup de déconnexion'); _isConnected = false; _lastDisconnectionTime = DateTime.now(); _reconnectionAttempts = 0; _showNoConnectionDialog(); } /// Simuler une reconnexion (pour test) void simulateReconnection() { print('🧪 Simulation de reconnexion'); _updateConnectivityStatus(true); } /// Obtenir les statistiques de connectivité Map getConnectivityStats() { return { 'is_connected': _isConnected, 'has_network_interface': _hasNetworkInterface, 'last_disconnection': _lastDisconnectionTime?.toIso8601String(), 'reconnection_attempts': _reconnectionAttempts, 'is_checking': _isChecking, }; } /// Réinitialiser les statistiques void resetStats() { _reconnectionAttempts = 0; _lastDisconnectionTime = null; print('📊 Statistiques de connectivité réinitialisées'); } @override void dispose() { print('🛑 Arrêt du service de connectivité'); _periodicTimer?.cancel(); super.dispose(); } } /// Extension pour des méthodes utilitaires extension ConnectivityServiceExtension on ConnectivityService { /// Vérifier si la connexion est stable bool get isStable { return _isConnected && _reconnectionAttempts == 0; } /// Obtenir le temps depuis la dernière déconnexion Duration? get timeSinceLastDisconnection { if (_lastDisconnectionTime == null) return null; return DateTime.now().difference(_lastDisconnectionTime!); } /// Obtenir un message de statut lisible String get statusMessage { if (_isConnected) { if (_reconnectionAttempts > 0) { return 'Connexion rétablie après $_reconnectionAttempts tentatives'; } return 'Connecté'; } else { if (!_hasNetworkInterface) { return 'Aucun réseau WiFi'; } return 'Pas de connexion internet'; } } }