feat: synchronisation mode deconnecte fin chat et stats
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import 'dart:async';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:geosector_app/core/data/models/amicale_model.dart';
|
||||
import 'package:geosector_app/core/services/api_service.dart';
|
||||
@@ -93,7 +94,7 @@ class AmicaleRepository extends ChangeNotifier {
|
||||
}
|
||||
|
||||
// Créer une amicale via l'API
|
||||
Future<bool> createAmicale(AmicaleModel amicale) async {
|
||||
Future<bool> createAmicale(AmicaleModel amicale, {BuildContext? context}) async {
|
||||
_isLoading = true;
|
||||
notifyListeners();
|
||||
|
||||
@@ -104,6 +105,39 @@ class AmicaleRepository extends ChangeNotifier {
|
||||
// Appeler l'API pour créer l'amicale
|
||||
final response = await ApiService.instance.post('/amicales', data: data);
|
||||
|
||||
// Vérifier si la requête a été mise en queue (mode offline)
|
||||
if (response.data != null && response.data['queued'] == true) {
|
||||
// Mode offline : NE PAS créer l'amicale dans Hive
|
||||
debugPrint('⏳ Création de l\'amicale mise en attente (mode hors ligne)');
|
||||
|
||||
// Informer l'utilisateur si un contexte est fourni
|
||||
if (context != null && context.mounted) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: const Row(
|
||||
children: [
|
||||
Icon(Icons.cloud_off, color: Colors.orange),
|
||||
SizedBox(width: 8),
|
||||
Text('Création en attente'),
|
||||
],
|
||||
),
|
||||
content: const Text(
|
||||
'L\'amicale sera créée dès que la connexion sera rétablie.\n\n'
|
||||
'La création a été ajoutée à la file d\'attente.',
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
child: const Text('Compris'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
return true; // Indiquer que l'opération a été acceptée (mise en queue)
|
||||
}
|
||||
|
||||
if (response.statusCode == 201 || response.statusCode == 200) {
|
||||
// Récupérer l'ID de la nouvelle amicale
|
||||
final amicaleId = response.data['id'] is String ? int.parse(response.data['id']) : response.data['id'] as int;
|
||||
@@ -163,6 +197,17 @@ class AmicaleRepository extends ChangeNotifier {
|
||||
// Appeler l'API pour mettre à jour l'amicale
|
||||
final response = await ApiService.instance.put('/amicales/${amicale.id}', data: data);
|
||||
|
||||
// Vérifier si la requête a été mise en queue (mode offline)
|
||||
if (response.data != null && response.data['queued'] == true) {
|
||||
// Mode offline : Sauvegarder localement avec indicateur
|
||||
final updatedAmicale = amicale.copyWith(
|
||||
updatedAt: DateTime.now(),
|
||||
);
|
||||
await saveAmicale(updatedAmicale);
|
||||
debugPrint('⏳ Modification de l\'amicale ${amicale.id} mise en attente (mode hors ligne)');
|
||||
return true;
|
||||
}
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
// Mettre à jour l'amicale localement avec updatedAt
|
||||
final updatedAmicale = amicale.copyWith(
|
||||
@@ -194,6 +239,14 @@ class AmicaleRepository extends ChangeNotifier {
|
||||
data: amicale.toJson(),
|
||||
);
|
||||
|
||||
// Vérifier si la requête a été mise en queue (mode offline)
|
||||
if (response.data != null && response.data['queued'] == true) {
|
||||
// Mode offline : Sauvegarder localement
|
||||
await saveAmicale(amicale);
|
||||
debugPrint('⏳ Modification de l\'amicale ${amicale.id} mise en attente (mode hors ligne)');
|
||||
return amicale; // Retourner l'amicale locale
|
||||
}
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
final updatedAmicaleData = response.data;
|
||||
final updatedAmicale = AmicaleModel.fromJson(updatedAmicaleData);
|
||||
@@ -221,6 +274,14 @@ class AmicaleRepository extends ChangeNotifier {
|
||||
// Appeler l'API pour supprimer l'amicale
|
||||
final response = await ApiService.instance.delete('/amicales/$id');
|
||||
|
||||
// Vérifier si la requête a été mise en queue (mode offline)
|
||||
if (response.data != null && response.data['queued'] == true) {
|
||||
// Mode offline : Supprimer localement immédiatement
|
||||
await deleteAmicale(id);
|
||||
debugPrint('⏳ Suppression de l\'amicale $id mise en attente (mode hors ligne)');
|
||||
return true;
|
||||
}
|
||||
|
||||
if (response.statusCode == 200 || response.statusCode == 204) {
|
||||
// Supprimer l'amicale localement
|
||||
await deleteAmicale(id);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'dart:async';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:geosector_app/core/data/models/client_model.dart';
|
||||
import 'package:geosector_app/core/services/api_service.dart';
|
||||
@@ -54,7 +55,7 @@ class ClientRepository extends ChangeNotifier {
|
||||
}
|
||||
|
||||
// Créer un client via l'API
|
||||
Future<bool> createClient(ClientModel client) async {
|
||||
Future<bool> createClient(ClientModel client, {BuildContext? context}) async {
|
||||
_isLoading = true;
|
||||
notifyListeners();
|
||||
|
||||
@@ -68,6 +69,39 @@ class ClientRepository extends ChangeNotifier {
|
||||
// Appeler l'API pour créer le client
|
||||
final response = await ApiService.instance.post('/clients', data: data);
|
||||
|
||||
// Vérifier si la requête a été mise en queue (mode offline)
|
||||
if (response.data != null && response.data['queued'] == true) {
|
||||
// Mode offline : NE PAS créer le client dans Hive
|
||||
debugPrint('⏳ Création du client mise en attente (mode hors ligne)');
|
||||
|
||||
// Informer l'utilisateur si un contexte est fourni
|
||||
if (context != null && context.mounted) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: const Row(
|
||||
children: [
|
||||
Icon(Icons.cloud_off, color: Colors.orange),
|
||||
SizedBox(width: 8),
|
||||
Text('Création en attente'),
|
||||
],
|
||||
),
|
||||
content: const Text(
|
||||
'Le client sera créé dès que la connexion sera rétablie.\n\n'
|
||||
'La création a été ajoutée à la file d\'attente.',
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
child: const Text('Compris'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
return true; // Indiquer que l'opération a été acceptée (mise en queue)
|
||||
}
|
||||
|
||||
if (response.statusCode == 201 || response.statusCode == 200) {
|
||||
// Récupérer l'ID du nouveau client
|
||||
final clientId = response.data['id'] is String ? int.parse(response.data['id']) : response.data['id'] as int;
|
||||
@@ -104,6 +138,18 @@ class ClientRepository extends ChangeNotifier {
|
||||
// Appeler l'API pour mettre à jour le client
|
||||
final response = await ApiService.instance.put('/clients/${client.id}', data: data);
|
||||
|
||||
// Vérifier si la requête a été mise en queue (mode offline)
|
||||
if (response.data != null && response.data['queued'] == true) {
|
||||
// Mode offline : Sauvegarder localement avec un indicateur
|
||||
final updatedClient = client.copyWith(
|
||||
updatedAt: DateTime.now(),
|
||||
// Note: isSynced n'existe pas dans ClientModel, on utilise updatedAt comme indicateur
|
||||
);
|
||||
await saveClient(updatedClient);
|
||||
debugPrint('⏳ Modification du client ${client.id} mise en attente (mode hors ligne)');
|
||||
return true;
|
||||
}
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
// Mettre à jour le client localement avec updatedAt
|
||||
final updatedClient = client.copyWith(
|
||||
@@ -132,6 +178,14 @@ class ClientRepository extends ChangeNotifier {
|
||||
// Appeler l'API pour supprimer le client
|
||||
final response = await ApiService.instance.delete('/clients/$id');
|
||||
|
||||
// Vérifier si la requête a été mise en queue (mode offline)
|
||||
if (response.data != null && response.data['queued'] == true) {
|
||||
// Mode offline : Supprimer localement immédiatement
|
||||
await deleteClient(id);
|
||||
debugPrint('⏳ Suppression du client $id mise en attente (mode hors ligne)');
|
||||
return true;
|
||||
}
|
||||
|
||||
if (response.statusCode == 200 || response.statusCode == 204) {
|
||||
// Supprimer le client localement
|
||||
await deleteClient(id);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'dart:async';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:geosector_app/core/data/models/membre_model.dart';
|
||||
import 'package:geosector_app/core/services/api_service.dart';
|
||||
@@ -122,7 +123,7 @@ class MembreRepository extends ChangeNotifier {
|
||||
// === MÉTHODES API ===
|
||||
|
||||
// Créer un membre via l'API
|
||||
Future<MembreModel?> createMembre(MembreModel membre, {String? password}) async {
|
||||
Future<MembreModel?> createMembre(MembreModel membre, {String? password, BuildContext? context}) async {
|
||||
_isLoading = true;
|
||||
notifyListeners();
|
||||
|
||||
@@ -171,6 +172,39 @@ class MembreRepository extends ChangeNotifier {
|
||||
// Appeler l'API users
|
||||
final response = await ApiService.instance.post('/users', data: data);
|
||||
|
||||
// Vérifier si la requête a été mise en queue (mode offline)
|
||||
if (response.data != null && response.data['queued'] == true) {
|
||||
// Mode offline : NE PAS créer le membre dans Hive
|
||||
debugPrint('⏳ Création du membre mise en attente (mode hors ligne)');
|
||||
|
||||
// Informer l'utilisateur si un contexte est fourni
|
||||
if (context != null && context.mounted) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: const Row(
|
||||
children: [
|
||||
Icon(Icons.cloud_off, color: Colors.orange),
|
||||
SizedBox(width: 8),
|
||||
Text('Création en attente'),
|
||||
],
|
||||
),
|
||||
content: const Text(
|
||||
'Le membre sera créé dès que la connexion sera rétablie.\n\n'
|
||||
'La création a été ajoutée à la file d\'attente.',
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
child: const Text('Compris'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
return null; // Retourner null car pas de membre créé localement
|
||||
}
|
||||
|
||||
// Vérifier d'abord si on a une réponse avec un statut d'erreur
|
||||
if (response.data != null && response.data is Map<String, dynamic>) {
|
||||
final responseData = response.data as Map<String, dynamic>;
|
||||
@@ -276,6 +310,14 @@ class MembreRepository extends ChangeNotifier {
|
||||
// Appeler l'API users au lieu de membres
|
||||
final response = await ApiService.instance.put('/users/${membre.id}', data: data);
|
||||
|
||||
// Vérifier si la requête a été mise en queue (mode offline)
|
||||
if (response.data != null && response.data['queued'] == true) {
|
||||
// Mode offline : Sauvegarder localement avec indicateur
|
||||
await saveMembreBox(membre);
|
||||
debugPrint('⏳ Modification du membre ${membre.id} mise en attente (mode hors ligne)');
|
||||
return true;
|
||||
}
|
||||
|
||||
// Si on arrive ici, c'est que la requête a réussi (200)
|
||||
// Sauvegarder le membre mis à jour localement
|
||||
await saveMembreBox(membre);
|
||||
@@ -362,6 +404,14 @@ class MembreRepository extends ChangeNotifier {
|
||||
|
||||
final response = await ApiService.instance.delete(endpoint);
|
||||
|
||||
// Vérifier si la requête a été mise en queue (mode offline)
|
||||
if (response.data != null && response.data['queued'] == true) {
|
||||
// Mode offline : Supprimer localement immédiatement
|
||||
await deleteMembreBox(membreId);
|
||||
debugPrint('⏳ Suppression du membre $membreId mise en attente (mode hors ligne)');
|
||||
return true;
|
||||
}
|
||||
|
||||
// Vérifier si on a une réponse avec un statut d'erreur
|
||||
if (response.data != null && response.data is Map<String, dynamic>) {
|
||||
final responseData = response.data as Map<String, dynamic>;
|
||||
|
||||
@@ -163,7 +163,7 @@ class OperationRepository extends ChangeNotifier {
|
||||
|
||||
// Créer une opération
|
||||
Future<bool> createOperation(
|
||||
String name, DateTime dateDebut, DateTime dateFin) async {
|
||||
String name, DateTime dateDebut, DateTime dateFin, {BuildContext? context}) async {
|
||||
_isLoading = true;
|
||||
notifyListeners();
|
||||
|
||||
@@ -183,6 +183,39 @@ class OperationRepository extends ChangeNotifier {
|
||||
final response =
|
||||
await ApiService.instance.post('/operations', data: data);
|
||||
|
||||
// Vérifier si la requête a été mise en queue (mode offline)
|
||||
if (response.data != null && response.data['queued'] == true) {
|
||||
// Mode offline : NE PAS créer l'opération dans Hive
|
||||
debugPrint('⏳ Création de l\'opération mise en attente (mode hors ligne)');
|
||||
|
||||
// Informer l'utilisateur si un contexte est fourni
|
||||
if (context != null && context.mounted) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: const Row(
|
||||
children: [
|
||||
Icon(Icons.cloud_off, color: Colors.orange),
|
||||
SizedBox(width: 8),
|
||||
Text('Création en attente'),
|
||||
],
|
||||
),
|
||||
content: const Text(
|
||||
'L\'opération sera créée dès que la connexion sera rétablie.\n\n'
|
||||
'La création a été ajoutée à la file d\'attente.',
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
child: const Text('Compris'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
return true; // Indiquer que l'opération a été acceptée (mise en queue)
|
||||
}
|
||||
|
||||
if (response.statusCode == 201 || response.statusCode == 200) {
|
||||
debugPrint('✅ Opération créée avec succès');
|
||||
|
||||
@@ -256,6 +289,7 @@ class OperationRepository extends ChangeNotifier {
|
||||
operation.name,
|
||||
operation.dateDebut,
|
||||
operation.dateFin,
|
||||
context: null, // Pas de contexte disponible ici
|
||||
);
|
||||
} else {
|
||||
// Opération existante - mettre à jour
|
||||
@@ -329,6 +363,23 @@ class OperationRepository extends ChangeNotifier {
|
||||
final response =
|
||||
await ApiService.instance.put('/operations/$id', data: data);
|
||||
|
||||
// Vérifier si la requête a été mise en queue (mode offline)
|
||||
if (response.data != null && response.data['queued'] == true) {
|
||||
// Mode offline : Sauvegarder localement avec indicateur
|
||||
final updatedOperation = existingOperation.copyWith(
|
||||
name: name,
|
||||
dateDebut: dateDebut,
|
||||
dateFin: dateFin,
|
||||
isActive: isActive,
|
||||
fkEntite: fkEntite,
|
||||
lastSyncedAt: null,
|
||||
isSynced: false,
|
||||
);
|
||||
await saveOperation(updatedOperation);
|
||||
debugPrint('⏳ Modification de l\'opération $id mise en attente (mode hors ligne)');
|
||||
return true;
|
||||
}
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
debugPrint('✅ Opération $id mise à jour avec succès');
|
||||
// Mettre à jour l'opération localement
|
||||
@@ -368,6 +419,14 @@ class OperationRepository extends ChangeNotifier {
|
||||
// Appeler l'API pour supprimer l'opération inactive
|
||||
final response = await ApiService.instance.delete('/operations/$id');
|
||||
|
||||
// Vérifier si la requête a été mise en queue (mode offline)
|
||||
if (response.data != null && response.data['queued'] == true) {
|
||||
// Mode offline : Supprimer localement immédiatement
|
||||
await deleteOperation(id);
|
||||
debugPrint('⏳ Suppression de l\'opération $id mise en attente (mode hors ligne)');
|
||||
return true;
|
||||
}
|
||||
|
||||
if (response.statusCode == 200 || response.statusCode == 204) {
|
||||
debugPrint('✅ Suppression réussie - Traitement de la réponse');
|
||||
|
||||
@@ -409,6 +468,15 @@ class OperationRepository extends ChangeNotifier {
|
||||
// Appeler l'API pour supprimer l'opération active
|
||||
final response = await ApiService.instance.delete('/operations/$id');
|
||||
|
||||
// Vérifier si la requête a été mise en queue (mode offline)
|
||||
if (response.data != null && response.data['queued'] == true) {
|
||||
// Mode offline : Supprimer localement et vider les données liées
|
||||
await _clearAllRelatedBoxes();
|
||||
await deleteOperation(id);
|
||||
debugPrint('⏳ Suppression de l\'opération active $id mise en attente (mode hors ligne)');
|
||||
return true;
|
||||
}
|
||||
|
||||
if (response.statusCode == 200 || response.statusCode == 204) {
|
||||
debugPrint(
|
||||
'✅ Suppression opération active réussie - Traitement complet');
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'dart:async';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:geosector_app/core/data/models/passage_model.dart';
|
||||
import 'package:geosector_app/core/services/api_service.dart';
|
||||
@@ -164,7 +165,7 @@ class PassageRepository extends ChangeNotifier {
|
||||
}
|
||||
|
||||
// Créer un passage via l'API
|
||||
Future<bool> createPassage(PassageModel passage) async {
|
||||
Future<bool> createPassage(PassageModel passage, {BuildContext? context}) async {
|
||||
_isLoading = true;
|
||||
notifyListeners();
|
||||
|
||||
@@ -175,6 +176,67 @@ class PassageRepository extends ChangeNotifier {
|
||||
// Appeler l'API pour créer le passage
|
||||
final response = await ApiService.instance.post('/passages', data: data);
|
||||
|
||||
// Vérifier si la requête a été mise en file d'attente
|
||||
if (response.data['queued'] == true) {
|
||||
// Mode offline : NE PAS créer le passage dans Hive
|
||||
// Il sera créé automatiquement après synchronisation
|
||||
|
||||
// Afficher un message explicatif
|
||||
if (context != null && context.mounted) {
|
||||
showDialog(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
builder: (dialogContext) => AlertDialog(
|
||||
icon: const Icon(Icons.cloud_queue, color: Colors.orange, size: 48),
|
||||
title: const Text('Création en attente'),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Text(
|
||||
'Votre passage a été enregistré et sera créé dès que la connexion sera rétablie.',
|
||||
style: TextStyle(fontSize: 14),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.orange.shade50,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
border: Border.all(color: Colors.orange.shade200),
|
||||
),
|
||||
child: const Row(
|
||||
children: [
|
||||
Icon(Icons.info_outline, size: 16, color: Colors.orange),
|
||||
SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: Text(
|
||||
'Le passage apparaîtra dans votre liste après synchronisation.',
|
||||
style: TextStyle(fontSize: 13),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
ElevatedButton(
|
||||
onPressed: () => Navigator.of(dialogContext).pop(),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.orange,
|
||||
),
|
||||
child: const Text('Compris'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Retourner true car la requête est bien en file d'attente
|
||||
return true;
|
||||
}
|
||||
|
||||
// Mode online : traitement normal
|
||||
if (response.statusCode == 201 || response.statusCode == 200) {
|
||||
// Récupérer l'ID du nouveau passage
|
||||
final passageId = response.data['id'] is String ? int.parse(response.data['id']) : response.data['id'] as int;
|
||||
@@ -200,7 +262,7 @@ class PassageRepository extends ChangeNotifier {
|
||||
}
|
||||
|
||||
// Mettre à jour un passage via l'API
|
||||
Future<bool> updatePassage(PassageModel passage) async {
|
||||
Future<bool> updatePassage(PassageModel passage, {BuildContext? context}) async {
|
||||
_isLoading = true;
|
||||
notifyListeners();
|
||||
|
||||
@@ -210,6 +272,42 @@ class PassageRepository extends ChangeNotifier {
|
||||
|
||||
// Appeler l'API pour mettre à jour le passage
|
||||
final response = await ApiService.instance.put('/passages/${passage.id}', data: data);
|
||||
|
||||
// Vérifier si la requête a été mise en file d'attente
|
||||
if (response.data['queued'] == true) {
|
||||
// Mode offline : mettre à jour localement et marquer comme non synchronisé
|
||||
final offlinePassage = passage.copyWith(
|
||||
lastSyncedAt: null,
|
||||
isSynced: false,
|
||||
);
|
||||
|
||||
await savePassage(offlinePassage);
|
||||
|
||||
// Afficher un message si un contexte est fourni
|
||||
if (context != null && context.mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Row(
|
||||
children: [
|
||||
Icon(Icons.cloud_queue, color: Colors.white),
|
||||
SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Text(
|
||||
'Modification en attente de synchronisation',
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
backgroundColor: Colors.orange,
|
||||
duration: Duration(seconds: 3),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Mode online : traitement normal
|
||||
if (response.statusCode == 200) {
|
||||
// Mettre à jour le passage localement
|
||||
final updatedPassage = passage.copyWith(
|
||||
@@ -231,7 +329,7 @@ class PassageRepository extends ChangeNotifier {
|
||||
}
|
||||
|
||||
// Supprimer un passage via l'API
|
||||
Future<bool> deletePassageViaApi(int id) async {
|
||||
Future<bool> deletePassageViaApi(int id, {BuildContext? context}) async {
|
||||
_isLoading = true;
|
||||
notifyListeners();
|
||||
|
||||
@@ -239,9 +337,31 @@ class PassageRepository extends ChangeNotifier {
|
||||
// Appeler l'API pour supprimer le passage
|
||||
final response = await ApiService.instance.delete('/passages/$id');
|
||||
|
||||
if (response.statusCode == 200 || response.statusCode == 204) {
|
||||
if (response.statusCode == 200 || response.statusCode == 204 || response.data['queued'] == true) {
|
||||
// Supprimer le passage localement
|
||||
await deletePassage(id);
|
||||
|
||||
// Si mis en file d'attente, informer l'utilisateur
|
||||
if (response.data['queued'] == true && context != null && context.mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Row(
|
||||
children: [
|
||||
Icon(Icons.cloud_queue, color: Colors.white),
|
||||
SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Text(
|
||||
'Suppression en attente de synchronisation',
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
backgroundColor: Colors.orange,
|
||||
duration: Duration(seconds: 3),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:geosector_app/core/data/models/sector_model.dart';
|
||||
@@ -138,7 +139,7 @@ class SectorRepository extends ChangeNotifier {
|
||||
}
|
||||
|
||||
// Créer un nouveau secteur via l'API
|
||||
Future<Map<String, dynamic>> createSector(SectorModel sector, {required List<int> users, required int fkEntite, required int operationId}) async {
|
||||
Future<Map<String, dynamic>> createSector(SectorModel sector, {required List<int> users, required int fkEntite, required int operationId, BuildContext? context}) async {
|
||||
try {
|
||||
// Préparer les données à envoyer
|
||||
final Map<String, dynamic> requestData = {
|
||||
@@ -153,8 +154,44 @@ class SectorRepository extends ChangeNotifier {
|
||||
data: requestData,
|
||||
);
|
||||
|
||||
// Vérifier si la requête a été mise en queue (mode offline)
|
||||
if (response.data != null && response.data['queued'] == true) {
|
||||
// Mode offline : NE PAS créer le secteur dans Hive
|
||||
debugPrint('⏳ Création du secteur mise en attente (mode hors ligne)');
|
||||
|
||||
// Informer l'utilisateur si un contexte est fourni
|
||||
if (context != null && context.mounted) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: const Row(
|
||||
children: [
|
||||
Icon(Icons.cloud_off, color: Colors.orange),
|
||||
SizedBox(width: 8),
|
||||
Text('Création en attente'),
|
||||
],
|
||||
),
|
||||
content: const Text(
|
||||
'Le secteur sera créé dès que la connexion sera rétablie.\n\n'
|
||||
'La création a été ajoutée à la file d\'attente.',
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
child: const Text('Compris'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
return {
|
||||
'status': 'queued',
|
||||
'message': 'Création mise en attente'
|
||||
};
|
||||
}
|
||||
|
||||
// Gérer la réponse correctement
|
||||
final dynamic responseRaw = response is Response ? response.data : response;
|
||||
final dynamic responseRaw = response.data;
|
||||
final Map<String, dynamic> responseData = Map<String, dynamic>.from(responseRaw as Map);
|
||||
|
||||
if (responseData['status'] == 'success') {
|
||||
@@ -282,8 +319,19 @@ class SectorRepository extends ChangeNotifier {
|
||||
data: requestData,
|
||||
);
|
||||
|
||||
// Vérifier si la requête a été mise en queue (mode offline)
|
||||
if (response.data != null && response.data['queued'] == true) {
|
||||
// Mode offline : Sauvegarder localement avec indicateur
|
||||
await saveSector(sector);
|
||||
debugPrint('⏳ Modification du secteur ${sector.id} mise en attente (mode hors ligne)');
|
||||
return {
|
||||
'status': 'queued',
|
||||
'message': 'Modification mise en attente'
|
||||
};
|
||||
}
|
||||
|
||||
// Gérer la réponse correctement
|
||||
final dynamic responseRaw = response is Response ? response.data : response;
|
||||
final dynamic responseRaw = response.data;
|
||||
final Map<String, dynamic> responseData = Map<String, dynamic>.from(responseRaw as Map);
|
||||
|
||||
if (responseData['status'] == 'success') {
|
||||
@@ -382,6 +430,19 @@ class SectorRepository extends ChangeNotifier {
|
||||
final response = await ApiService.instance.delete(
|
||||
'${AppKeys.sectorsEndpoint}/$id',
|
||||
);
|
||||
|
||||
// Vérifier si la requête a été mise en queue (mode offline)
|
||||
if (response.data != null && response.data['queued'] == true) {
|
||||
// Mode offline : Supprimer localement immédiatement
|
||||
await _deleteAllPassagesOfSector(id);
|
||||
await deleteSector(id);
|
||||
debugPrint('⏳ Suppression du secteur $id mise en attente (mode hors ligne)');
|
||||
return {
|
||||
'status': 'queued',
|
||||
'message': 'Suppression mise en attente'
|
||||
};
|
||||
}
|
||||
|
||||
final Map<String, dynamic> responseData = response.data as Map<String, dynamic>;
|
||||
|
||||
if (responseData['status'] == 'success') {
|
||||
|
||||
@@ -9,6 +9,8 @@ import 'package:geosector_app/core/services/current_amicale_service.dart';
|
||||
import 'package:geosector_app/core/services/data_loading_service.dart';
|
||||
import 'package:geosector_app/core/services/hive_service.dart';
|
||||
import 'package:geosector_app/core/services/hive_reset_state_service.dart';
|
||||
import 'package:geosector_app/core/services/chat_manager.dart';
|
||||
import 'package:geosector_app/chat/services/chat_info_service.dart';
|
||||
import 'package:geosector_app/core/data/models/user_model.dart';
|
||||
import 'package:geosector_app/core/data/models/amicale_model.dart';
|
||||
import 'package:geosector_app/core/data/models/operation_model.dart';
|
||||
@@ -17,7 +19,6 @@ import 'package:geosector_app/core/data/models/passage_model.dart';
|
||||
import 'package:geosector_app/core/data/models/membre_model.dart';
|
||||
import 'package:geosector_app/presentation/widgets/loading_spin_overlay.dart';
|
||||
import 'package:geosector_app/core/models/loading_state.dart';
|
||||
import 'package:geosector_app/chat/services/chat_info_service.dart';
|
||||
|
||||
class UserRepository extends ChangeNotifier {
|
||||
bool _isLoading = false;
|
||||
@@ -296,6 +297,15 @@ class UserRepository extends ChangeNotifier {
|
||||
debugPrint('⚠️ Connexion réussie mais avec des données partielles');
|
||||
}
|
||||
|
||||
// Initialiser le chat en arrière-plan après connexion réussie
|
||||
try {
|
||||
await ChatManager.instance.initializeChat();
|
||||
debugPrint('✅ Module chat initialisé en arrière-plan');
|
||||
} catch (chatError) {
|
||||
// Ne pas bloquer la connexion si le chat échoue
|
||||
debugPrint('⚠️ Erreur initialisation chat (non bloquant): $chatError');
|
||||
}
|
||||
|
||||
debugPrint('✅ Connexion réussie');
|
||||
return true;
|
||||
} catch (e) {
|
||||
@@ -314,6 +324,60 @@ class UserRepository extends ChangeNotifier {
|
||||
|
||||
try {
|
||||
debugPrint('🚪 Déconnexion en cours...');
|
||||
|
||||
// Vérifier les requêtes en attente AVANT de déconnecter
|
||||
int pendingCount = 0;
|
||||
try {
|
||||
if (Hive.isBoxOpen(AppKeys.pendingRequestsBoxName)) {
|
||||
final pendingBox = Hive.box(AppKeys.pendingRequestsBoxName);
|
||||
pendingCount = pendingBox.length;
|
||||
if (pendingCount > 0) {
|
||||
debugPrint('⏳ $pendingCount requêtes en attente trouvées');
|
||||
|
||||
// Afficher un avertissement non bloquant
|
||||
if (context.mounted) {
|
||||
showDialog(
|
||||
context: context,
|
||||
barrierDismissible: true,
|
||||
builder: (BuildContext dialogContext) => AlertDialog(
|
||||
title: const Row(
|
||||
children: [
|
||||
Icon(Icons.warning_amber, color: Colors.orange, size: 28),
|
||||
SizedBox(width: 12),
|
||||
Text('Données en attente'),
|
||||
],
|
||||
),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
pendingCount == 1
|
||||
? '1 requête sera synchronisée lors de votre prochaine connexion.'
|
||||
: '$pendingCount requêtes seront synchronisées lors de votre prochaine connexion.',
|
||||
style: const TextStyle(fontSize: 16),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
const Text(
|
||||
'Vos données sont conservées et seront envoyées automatiquement.',
|
||||
style: TextStyle(fontSize: 14, color: Colors.grey),
|
||||
),
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(dialogContext).pop(),
|
||||
child: const Text('Compris'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint('⚠️ Impossible de vérifier les requêtes en attente: $e');
|
||||
}
|
||||
|
||||
try {
|
||||
await logoutAPI();
|
||||
@@ -328,6 +392,9 @@ class UserRepository extends ChangeNotifier {
|
||||
await CurrentUserService.instance.clearUser();
|
||||
await CurrentAmicaleService.instance.clearAmicale();
|
||||
|
||||
// Arrêter le chat (stoppe les syncs)
|
||||
ChatManager.instance.dispose();
|
||||
|
||||
// Réinitialiser les infos chat
|
||||
ChatInfoService.instance.reset();
|
||||
|
||||
@@ -431,31 +498,47 @@ class UserRepository extends ChangeNotifier {
|
||||
|
||||
// D'ABORD essayer de synchroniser avec l'API
|
||||
try {
|
||||
final hasConnection = await ApiService.instance.hasInternetConnection();
|
||||
if (hasConnection) {
|
||||
// Tentative de mise à jour sur l'API
|
||||
await ApiService.instance.updateUser(updatedUser);
|
||||
debugPrint('✅ Utilisateur mis à jour sur l\'API');
|
||||
|
||||
// Si succès API, sauvegarder localement avec sync = true
|
||||
final syncedUser = updatedUser.copyWith(
|
||||
isSynced: true,
|
||||
lastSyncedAt: DateTime.now(),
|
||||
// Tentative de mise à jour sur l'API (gère automatiquement le mode offline)
|
||||
final response = await ApiService.instance.put('/users/${updatedUser.id}', data: updatedUser.toJson());
|
||||
|
||||
// Vérifier si la requête a été mise en queue (mode offline)
|
||||
if (response.data != null && response.data['queued'] == true) {
|
||||
// Mode offline : Sauvegarder localement avec isSynced = false
|
||||
final unsyncedUser = updatedUser.copyWith(
|
||||
isSynced: false,
|
||||
lastSyncedAt: null,
|
||||
);
|
||||
|
||||
await _userBox.put(syncedUser.id, syncedUser);
|
||||
|
||||
|
||||
await _userBox.put(unsyncedUser.id, unsyncedUser);
|
||||
|
||||
// Si c'est l'utilisateur connecté, mettre à jour le service
|
||||
if (currentUser?.id == syncedUser.id) {
|
||||
await CurrentUserService.instance.setUser(syncedUser);
|
||||
if (currentUser?.id == unsyncedUser.id) {
|
||||
await CurrentUserService.instance.setUser(unsyncedUser);
|
||||
}
|
||||
|
||||
|
||||
debugPrint('⏳ Modification utilisateur ${updatedUser.id} mise en attente (mode hors ligne)');
|
||||
notifyListeners();
|
||||
return syncedUser;
|
||||
} else {
|
||||
debugPrint('⚠️ Pas de connexion internet');
|
||||
throw Exception('Pas de connexion internet');
|
||||
return unsyncedUser;
|
||||
}
|
||||
|
||||
// Mode online : succès de la mise à jour
|
||||
debugPrint('✅ Utilisateur mis à jour sur l\'API');
|
||||
|
||||
// Si succès API, sauvegarder localement avec sync = true
|
||||
final syncedUser = updatedUser.copyWith(
|
||||
isSynced: true,
|
||||
lastSyncedAt: DateTime.now(),
|
||||
);
|
||||
|
||||
await _userBox.put(syncedUser.id, syncedUser);
|
||||
|
||||
// Si c'est l'utilisateur connecté, mettre à jour le service
|
||||
if (currentUser?.id == syncedUser.id) {
|
||||
await CurrentUserService.instance.setUser(syncedUser);
|
||||
}
|
||||
|
||||
notifyListeners();
|
||||
return syncedUser;
|
||||
} catch (apiError) {
|
||||
debugPrint('❌ Erreur API lors de la mise à jour: $apiError');
|
||||
// Relancer l'erreur pour qu'elle soit gérée par l'appelant
|
||||
|
||||
Reference in New Issue
Block a user