Files
wortis_tpe/lib/services/connectivity_service.dart

601 lines
20 KiB
Dart
Raw Permalink Normal View History

2025-12-01 10:56:37 +01:00
// 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<NavigatorState>? navigatorKey;
/// Initialiser le service avec la clé navigator
static void setNavigatorKey(GlobalKey<NavigatorState> 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<void> 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<bool> _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<bool> _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<double>(
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<AuthController>(
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<String, dynamic> 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';
}
}
}