import 'package:flutter/material.dart'; import 'package:connectivity_plus/connectivity_plus.dart'; import 'package:hive_flutter/hive_flutter.dart'; import 'package:geosector_app/app.dart'; // Pour accéder aux instances globales import 'package:geosector_app/core/constants/app_keys.dart'; import 'package:geosector_app/core/data/models/pending_request.dart'; /// Widget qui affiche l'état de la connexion Internet et le nombre de requêtes en attente class ConnectivityIndicator extends StatefulWidget { /// Si true, affiche un message d'erreur lorsque l'appareil est déconnecté final bool showErrorMessage; /// Si true, affiche un badge avec le type de connexion (WiFi, données mobiles) final bool showConnectionType; /// Callback appelé lorsque l'état de la connexion change final Function(bool isConnected)? onConnectivityChanged; const ConnectivityIndicator({ super.key, this.showErrorMessage = true, this.showConnectionType = true, this.onConnectivityChanged, }); @override State createState() => _ConnectivityIndicatorState(); } class _ConnectivityIndicatorState extends State with SingleTickerProviderStateMixin { late AnimationController _animationController; late Animation _animation; @override void initState() { super.initState(); // Configuration de l'animation de clignotement _animationController = AnimationController( duration: const Duration(milliseconds: 1000), vsync: this, ); _animation = Tween( begin: 1.0, end: 0.3, ).animate(CurvedAnimation( parent: _animationController, curve: Curves.easeInOut, )); } @override void dispose() { _animationController.dispose(); super.dispose(); } void _updateAnimation(int pendingCount) { if (pendingCount > 0) { // Démarrer l'animation de clignotement si des requêtes sont en attente if (!_animationController.isAnimating) { _animationController.repeat(reverse: true); } } else { // Arrêter l'animation quand il n'y a plus de requêtes _animationController.stop(); _animationController.value = 1.0; } } @override Widget build(BuildContext context) { final theme = Theme.of(context); // Utiliser l'instance globale de connectivityService définie dans app.dart final isConnected = connectivityService.isConnected; final connectionType = connectivityService.connectionType; final connectionStatus = connectivityService.connectionStatus; // Appeler le callback si fourni, mais pas directement dans le build // pour éviter les problèmes de rendu WidgetsBinding.instance.addPostFrameCallback((_) { if (widget.onConnectivityChanged != null) { widget.onConnectivityChanged!(isConnected); } }); // Vérifier si la box des requêtes en attente est ouverte if (!Hive.isBoxOpen(AppKeys.pendingRequestsBoxName)) { return _buildBasicIndicator(context, isConnected, connectionType, connectionStatus, theme, 0); } // Utiliser ValueListenableBuilder pour surveiller les requêtes en attente return ValueListenableBuilder>( valueListenable: Hive.box(AppKeys.pendingRequestsBoxName).listenable(), builder: (context, box, child) { final pendingCount = box.length; // Mettre à jour l'animation en fonction du nombre de requêtes _updateAnimation(pendingCount); if (!isConnected && widget.showErrorMessage) { return Container( padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 12), margin: const EdgeInsets.only(bottom: 8), decoration: BoxDecoration( color: theme.colorScheme.error.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(8), border: Border.all( color: theme.colorScheme.error.withValues(alpha: 0.3), ), ), child: Row( children: [ Icon( Icons.wifi_off, color: theme.colorScheme.error, size: 18, ), const SizedBox(width: 8), Expanded( child: Text( pendingCount > 0 ? 'Hors ligne - $pendingCount requête${pendingCount > 1 ? 's' : ''} en attente' : 'Aucune connexion Internet. Certaines fonctionnalités peuvent être limitées.', style: theme.textTheme.bodySmall?.copyWith( color: theme.colorScheme.error, ), ), ), if (pendingCount > 0) AnimatedBuilder( animation: _animation, builder: (context, child) { return Opacity( opacity: _animation.value, child: Container( margin: const EdgeInsets.only(left: 8), padding: const EdgeInsets.all(4), decoration: BoxDecoration( color: Colors.orange, shape: BoxShape.circle, ), child: Text( pendingCount.toString(), style: const TextStyle( color: Colors.white, fontSize: 11, fontWeight: FontWeight.bold, ), ), ), ); }, ), ], ), ); } else if (isConnected && widget.showConnectionType) { return _buildConnectedIndicator( context, connectionStatus, connectionType, theme, pendingCount ); } // Si aucune condition n'est remplie return const SizedBox.shrink(); }, ); } Widget _buildConnectedIndicator( BuildContext context, List connectionStatus, String connectionType, ThemeData theme, int pendingCount, ) { // Obtenir la couleur et l'icône en fonction du type de connexion final color = _getConnectionColor(connectionStatus, theme); final icon = _getConnectionIcon(connectionStatus); return AnimatedBuilder( animation: _animation, builder: (context, child) { return Container( padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 8), decoration: BoxDecoration( color: pendingCount > 0 ? Colors.orange.withValues(alpha: 0.1 * _animation.value) : color.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(16), border: Border.all( color: pendingCount > 0 ? Colors.orange.withValues(alpha: 0.3 * _animation.value) : color.withValues(alpha: 0.3), ), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Icon( pendingCount > 0 ? Icons.sync : icon, color: pendingCount > 0 ? Colors.orange : color, size: 14, ), const SizedBox(width: 4), Text( pendingCount > 0 ? '$pendingCount en attente' : connectionType, style: theme.textTheme.bodySmall?.copyWith( color: pendingCount > 0 ? Colors.orange : color, fontWeight: FontWeight.bold, ), ), ], ), ); }, ); } Widget _buildBasicIndicator( BuildContext context, bool isConnected, String connectionType, List connectionStatus, ThemeData theme, int pendingCount, ) { if (!isConnected && widget.showErrorMessage) { return Container( padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 12), margin: const EdgeInsets.only(bottom: 8), decoration: BoxDecoration( color: theme.colorScheme.error.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(8), border: Border.all( color: theme.colorScheme.error.withValues(alpha: 0.3), ), ), child: Row( children: [ Icon( Icons.wifi_off, color: theme.colorScheme.error, size: 18, ), const SizedBox(width: 8), Expanded( child: Text( 'Aucune connexion Internet. Certaines fonctionnalités peuvent être limitées.', style: theme.textTheme.bodySmall?.copyWith( color: theme.colorScheme.error, ), ), ), ], ), ); } else if (isConnected && widget.showConnectionType) { final color = _getConnectionColor(connectionStatus, theme); final icon = _getConnectionIcon(connectionStatus); return Container( padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 8), decoration: BoxDecoration( color: color.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(16), border: Border.all( color: color.withValues(alpha: 0.3), ), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Icon( icon, color: color, size: 14, ), const SizedBox(width: 4), Text( connectionType, style: theme.textTheme.bodySmall?.copyWith( color: color, fontWeight: FontWeight.bold, ), ), ], ), ); } return const SizedBox.shrink(); } /// Retourne l'icône correspondant au type de connexion IconData _getConnectionIcon(List statusList) { // Utiliser le premier type de connexion qui n'est pas 'none' ConnectivityResult status = statusList.firstWhere( (result) => result != ConnectivityResult.none, orElse: () => ConnectivityResult.none); switch (status) { case ConnectivityResult.wifi: return Icons.wifi; case ConnectivityResult.mobile: return Icons.signal_cellular_alt; case ConnectivityResult.ethernet: return Icons.lan; case ConnectivityResult.bluetooth: return Icons.bluetooth; case ConnectivityResult.vpn: return Icons.vpn_key; default: return Icons.wifi_off; } } /// Retourne la couleur correspondant au type de connexion Color _getConnectionColor( List statusList, ThemeData theme) { // Utiliser le premier type de connexion qui n'est pas 'none' ConnectivityResult status = statusList.firstWhere( (result) => result != ConnectivityResult.none, orElse: () => ConnectivityResult.none); switch (status) { case ConnectivityResult.wifi: return Colors.green; case ConnectivityResult.mobile: return Colors.blue; case ConnectivityResult.ethernet: return Colors.purple; case ConnectivityResult.bluetooth: return Colors.indigo; case ConnectivityResult.vpn: return Colors.orange; default: return theme.colorScheme.error; } } }