feat: synchronisation mode deconnecte fin chat et stats

This commit is contained in:
2025-08-31 18:21:20 +02:00
parent f5bef999df
commit 96af94ad13
129 changed files with 125731 additions and 110375 deletions

View File

@@ -0,0 +1,269 @@
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<Box<PendingRequest>>(
valueListenable: Hive.box<PendingRequest>(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<PendingRequest> 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<Box<PendingRequest>>(
valueListenable: Hive.box<PendingRequest>(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,
),
),
],
),
);
},
);
}
}