import 'package:flutter/material.dart'; import 'package:hive_flutter/hive_flutter.dart'; import 'package:geosector_app/core/constants/app_keys.dart'; import 'package:geosector_app/core/data/models/pending_request.dart'; /// Widget qui affiche le nombre de requêtes en attente de synchronisation /// S'affiche uniquement quand il y a au moins une requête en attente /// Se met à jour automatiquement grâce au ValueListenableBuilder class PendingRequestsCounter extends StatelessWidget { final bool showDetails; final Color? backgroundColor; final Color? textColor; const PendingRequestsCounter({ Key? key, this.showDetails = false, this.backgroundColor, this.textColor, }) : super(key: key); @override Widget build(BuildContext context) { // Vérifier si la box est ouverte if (!Hive.isBoxOpen(AppKeys.pendingRequestsBoxName)) { return const SizedBox.shrink(); } return ValueListenableBuilder>( valueListenable: Hive.box(AppKeys.pendingRequestsBoxName).listenable(), builder: (context, box, child) { final count = box.length; // Ne rien afficher s'il n'y a pas de requêtes en attente if (count == 0) { return const SizedBox.shrink(); } return Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), decoration: BoxDecoration( color: backgroundColor ?? Colors.orange.shade100, borderRadius: BorderRadius.circular(20), border: Border.all( color: Colors.orange.shade300, width: 1, ), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Icon( Icons.sync, size: 16, color: textColor ?? Colors.orange.shade700, ), const SizedBox(width: 6), Text( count == 1 ? '1 requête en attente' : '$count requêtes en attente', style: TextStyle( fontSize: 13, fontWeight: FontWeight.w500, color: textColor ?? Colors.orange.shade700, ), ), if (showDetails) ...[ const SizedBox(width: 6), InkWell( onTap: () => _showPendingRequestsDialog(context, box), child: Icon( Icons.info_outline, size: 16, color: textColor ?? Colors.orange.shade700, ), ), ], ], ), ); }, ); } void _showPendingRequestsDialog(BuildContext context, Box box) { showDialog( context: context, builder: (context) => AlertDialog( title: const Text('Requêtes en attente'), content: SizedBox( width: double.maxFinite, child: ListView.builder( shrinkWrap: true, itemCount: box.length, itemBuilder: (context, index) { final request = box.getAt(index); if (request == null) return const SizedBox.shrink(); return Card( margin: const EdgeInsets.symmetric(vertical: 4), child: Padding( padding: const EdgeInsets.all(8.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Container( padding: const EdgeInsets.symmetric( horizontal: 6, vertical: 2, ), decoration: BoxDecoration( color: _getMethodColor(request.method), borderRadius: BorderRadius.circular(4), ), child: Text( request.method, style: const TextStyle( color: Colors.white, fontSize: 11, fontWeight: FontWeight.bold, ), ), ), const SizedBox(width: 8), Expanded( child: Text( request.path, style: const TextStyle(fontSize: 12), overflow: TextOverflow.ellipsis, ), ), ], ), const SizedBox(height: 4), Text( 'Créée: ${_formatDateTime(request.createdAt)}', style: TextStyle( fontSize: 11, color: Colors.grey.shade600, ), ), if (request.retryCount > 0) ...[ const SizedBox(height: 2), Text( 'Tentatives: ${request.retryCount}', style: TextStyle( fontSize: 11, color: Colors.orange.shade700, ), ), ], if (request.errorMessage != null) ...[ const SizedBox(height: 2), Text( 'Erreur: ${request.errorMessage}', style: const TextStyle( fontSize: 11, color: Colors.red, ), maxLines: 2, overflow: TextOverflow.ellipsis, ), ], ], ), ), ); }, ), ), actions: [ TextButton( onPressed: () => Navigator.of(context).pop(), child: const Text('Fermer'), ), ], ), ); } Color _getMethodColor(String method) { switch (method.toUpperCase()) { case 'GET': return Colors.blue; case 'POST': return Colors.green; case 'PUT': return Colors.orange; case 'DELETE': return Colors.red; default: return Colors.grey; } } String _formatDateTime(DateTime dateTime) { final now = DateTime.now(); final difference = now.difference(dateTime); if (difference.inSeconds < 60) { return 'Il y a ${difference.inSeconds}s'; } else if (difference.inMinutes < 60) { return 'Il y a ${difference.inMinutes}min'; } else if (difference.inHours < 24) { return 'Il y a ${difference.inHours}h'; } else { return 'Il y a ${difference.inDays}j'; } } } /// Version compacte du compteur pour les barres d'outils class PendingRequestsCounterCompact extends StatelessWidget { final Color? color; const PendingRequestsCounterCompact({ Key? key, this.color, }) : super(key: key); @override Widget build(BuildContext context) { // Vérifier si la box est ouverte if (!Hive.isBoxOpen(AppKeys.pendingRequestsBoxName)) { return const SizedBox.shrink(); } return ValueListenableBuilder>( valueListenable: Hive.box(AppKeys.pendingRequestsBoxName).listenable(), builder: (context, box, child) { final count = box.length; // Ne rien afficher s'il n'y a pas de requêtes en attente if (count == 0) { return const SizedBox.shrink(); } return Container( padding: const EdgeInsets.all(6), decoration: BoxDecoration( color: color ?? Colors.orange, shape: BoxShape.circle, ), child: Row( mainAxisSize: MainAxisSize.min, children: [ const Icon( Icons.sync, size: 14, color: Colors.white, ), const SizedBox(width: 4), Text( count.toString(), style: const TextStyle( fontSize: 12, fontWeight: FontWeight.bold, color: Colors.white, ), ), ], ), ); }, ); } }