feat: Version 3.3.4 - Nouvelle architecture pages, optimisations widgets Flutter et API
- Mise à jour VERSION vers 3.3.4 - Optimisations et révisions architecture API (deploy-api.sh, scripts de migration) - Ajout documentation Stripe Tap to Pay complète - Migration vers polices Inter Variable pour Flutter - Optimisations build Android et nettoyage fichiers temporaires - Amélioration système de déploiement avec gestion backups - Ajout scripts CRON et migrations base de données 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,4 @@
|
||||
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';
|
||||
@@ -167,6 +166,7 @@ class AmicaleRepository extends ChangeNotifier {
|
||||
chkMdpManuel: amicale.chkMdpManuel,
|
||||
chkUsernameManuel: amicale.chkUsernameManuel,
|
||||
chkUserDeletePass: amicale.chkUserDeletePass,
|
||||
chkLotActif: amicale.chkLotActif,
|
||||
createdAt: amicale.createdAt ?? DateTime.now(),
|
||||
updatedAt: DateTime.now(),
|
||||
);
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
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';
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
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';
|
||||
@@ -29,9 +28,10 @@ class MembreRepository extends ChangeNotifier {
|
||||
|
||||
bool _isLoading = false;
|
||||
|
||||
// Méthode pour réinitialiser le cache après modification de la box
|
||||
void _resetCache() {
|
||||
// Méthode publique pour réinitialiser le cache (ex: après nettoyage complet)
|
||||
void resetCache() {
|
||||
_cachedMembreBox = null;
|
||||
debugPrint('🔄 Cache MembreRepository réinitialisé');
|
||||
}
|
||||
|
||||
// Getters
|
||||
@@ -109,14 +109,14 @@ class MembreRepository extends ChangeNotifier {
|
||||
// Sauvegarder un membre
|
||||
Future<void> saveMembreBox(MembreModel membre) async {
|
||||
await _membreBox.put(membre.id, membre);
|
||||
_resetCache(); // Réinitialiser le cache après modification
|
||||
resetCache(); // Réinitialiser le cache après modification
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
// Supprimer un membre
|
||||
Future<void> deleteMembreBox(int id) async {
|
||||
await _membreBox.delete(id);
|
||||
_resetCache(); // Réinitialiser le cache après modification
|
||||
resetCache(); // Réinitialiser le cache après modification
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
@@ -479,7 +479,7 @@ class MembreRepository extends ChangeNotifier {
|
||||
}
|
||||
|
||||
debugPrint('$count membres traités et stockés');
|
||||
_resetCache(); // Réinitialiser le cache après traitement des données API
|
||||
resetCache(); // Réinitialiser le cache après traitement des données API
|
||||
notifyListeners();
|
||||
} catch (e) {
|
||||
debugPrint('Erreur lors du traitement des membres: $e');
|
||||
@@ -534,7 +534,7 @@ class MembreRepository extends ChangeNotifier {
|
||||
// Vider la boîte des membres
|
||||
Future<void> clearMembres() async {
|
||||
await _membreBox.clear();
|
||||
_resetCache(); // Réinitialiser le cache après suppression de toutes les données
|
||||
resetCache(); // Réinitialiser le cache après suppression de toutes les données
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
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';
|
||||
import 'package:geosector_app/core/services/current_user_service.dart';
|
||||
import 'package:geosector_app/core/constants/app_keys.dart';
|
||||
|
||||
class PassageRepository extends ChangeNotifier {
|
||||
@@ -28,9 +28,10 @@ class PassageRepository extends ChangeNotifier {
|
||||
return _cachedPassageBox!;
|
||||
}
|
||||
|
||||
// Méthode pour réinitialiser le cache après modification de la box
|
||||
void _resetCache() {
|
||||
// Méthode publique pour réinitialiser le cache (ex: après nettoyage complet)
|
||||
void resetCache() {
|
||||
_cachedPassageBox = null;
|
||||
debugPrint('🔄 Cache PassageRepository réinitialisé');
|
||||
}
|
||||
|
||||
// Méthode pour exposer la Box Hive (nécessaire pour ValueListenableBuilder)
|
||||
@@ -129,7 +130,7 @@ class PassageRepository extends ChangeNotifier {
|
||||
// Sauvegarder un passage
|
||||
Future<void> savePassage(PassageModel passage) async {
|
||||
await _passageBox.put(passage.id, passage);
|
||||
_resetCache(); // Réinitialiser le cache après modification
|
||||
resetCache(); // Réinitialiser le cache après modification
|
||||
notifyListeners();
|
||||
_notifyPassageStream();
|
||||
}
|
||||
@@ -146,7 +147,7 @@ class PassageRepository extends ChangeNotifier {
|
||||
// Sauvegarder tous les passages en une seule opération
|
||||
await _passageBox.putAll(passagesMap);
|
||||
|
||||
_resetCache(); // Réinitialiser le cache après modification massive
|
||||
resetCache(); // Réinitialiser le cache après modification massive
|
||||
notifyListeners();
|
||||
_notifyPassageStream();
|
||||
}
|
||||
@@ -154,7 +155,7 @@ class PassageRepository extends ChangeNotifier {
|
||||
// Supprimer un passage
|
||||
Future<void> deletePassage(int id) async {
|
||||
await _passageBox.delete(id);
|
||||
_resetCache(); // Réinitialiser le cache après suppression
|
||||
resetCache(); // Réinitialiser le cache après suppression
|
||||
notifyListeners();
|
||||
_notifyPassageStream();
|
||||
}
|
||||
@@ -164,7 +165,111 @@ class PassageRepository extends ChangeNotifier {
|
||||
_passageStreamController?.add(getAllPassages());
|
||||
}
|
||||
|
||||
// Créer un passage via l'API
|
||||
// Créer un passage via l'API et retourner le passage créé
|
||||
Future<PassageModel?> createPassageWithReturn(PassageModel passage, {BuildContext? context}) async {
|
||||
_isLoading = true;
|
||||
notifyListeners();
|
||||
|
||||
try {
|
||||
// Préparer les données pour l'API
|
||||
final data = passage.toJson();
|
||||
|
||||
// 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 (mode offline)
|
||||
if (response.data['queued'] == true) {
|
||||
// Mode offline : créer localement avec un ID temporaire
|
||||
final offlinePassage = passage.copyWith(
|
||||
id: DateTime.now().millisecondsSinceEpoch, // ID temporaire unique
|
||||
lastSyncedAt: null,
|
||||
isSynced: false,
|
||||
);
|
||||
|
||||
await savePassage(offlinePassage);
|
||||
|
||||
// Afficher le dialog d'information si un contexte est fourni
|
||||
if (context != null && context.mounted) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (dialogContext) => AlertDialog(
|
||||
title: const Row(
|
||||
children: [
|
||||
Icon(Icons.cloud_queue, color: Colors.orange),
|
||||
SizedBox(width: 12),
|
||||
Text('Mode hors ligne'),
|
||||
],
|
||||
),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text('Votre passage a été enregistré localement.'),
|
||||
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'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return offlinePassage; // Retourner le passage créé localement
|
||||
}
|
||||
|
||||
// Mode online : traitement normal
|
||||
if (response.statusCode == 201 || response.statusCode == 200) {
|
||||
// Récupérer l'ID du nouveau passage depuis la réponse
|
||||
final passageId = response.data['id'] is String ? int.parse(response.data['id']) : response.data['id'] as int;
|
||||
|
||||
// Créer le passage localement avec l'ID retourné par l'API
|
||||
final newPassage = passage.copyWith(
|
||||
id: passageId,
|
||||
lastSyncedAt: DateTime.now(),
|
||||
isSynced: true,
|
||||
);
|
||||
|
||||
await savePassage(newPassage);
|
||||
return newPassage; // Retourner le passage créé avec son ID réel
|
||||
}
|
||||
return null;
|
||||
} catch (e) {
|
||||
debugPrint('Erreur lors de la création du passage: $e');
|
||||
return null;
|
||||
} finally {
|
||||
_isLoading = false;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
// Créer un passage via l'API (ancienne méthode pour compatibilité)
|
||||
Future<bool> createPassage(PassageModel passage, {BuildContext? context}) async {
|
||||
_isLoading = true;
|
||||
notifyListeners();
|
||||
@@ -275,12 +380,16 @@ class PassageRepository extends ChangeNotifier {
|
||||
|
||||
// Vérifier si la requête a été mise en file d'attente
|
||||
if (response.data['queued'] == true) {
|
||||
// Récupérer l'utilisateur actuel
|
||||
final currentUserId = CurrentUserService.instance.userId;
|
||||
|
||||
// Mode offline : mettre à jour localement et marquer comme non synchronisé
|
||||
final offlinePassage = passage.copyWith(
|
||||
fkUser: currentUserId, // Le passage appartient maintenant à l'utilisateur qui l'a modifié
|
||||
lastSyncedAt: null,
|
||||
isSynced: false,
|
||||
);
|
||||
|
||||
|
||||
await savePassage(offlinePassage);
|
||||
|
||||
// Afficher un message si un contexte est fourni
|
||||
@@ -309,8 +418,12 @@ class PassageRepository extends ChangeNotifier {
|
||||
|
||||
// Mode online : traitement normal
|
||||
if (response.statusCode == 200) {
|
||||
// Mettre à jour le passage localement
|
||||
// Récupérer l'utilisateur actuel
|
||||
final currentUserId = CurrentUserService.instance.userId;
|
||||
|
||||
// Mettre à jour le passage localement avec le user actuel
|
||||
final updatedPassage = passage.copyWith(
|
||||
fkUser: currentUserId, // Le passage appartient maintenant à l'utilisateur qui l'a modifié
|
||||
lastSyncedAt: DateTime.now(),
|
||||
isSynced: true,
|
||||
);
|
||||
@@ -412,7 +525,7 @@ class PassageRepository extends ChangeNotifier {
|
||||
}
|
||||
|
||||
debugPrint('$count passages traités et stockés');
|
||||
_resetCache(); // Réinitialiser le cache après traitement des données API
|
||||
resetCache(); // Réinitialiser le cache après traitement des données API
|
||||
notifyListeners();
|
||||
_notifyPassageStream();
|
||||
} catch (e) {
|
||||
@@ -505,7 +618,7 @@ class PassageRepository extends ChangeNotifier {
|
||||
// Vider tous les passages
|
||||
Future<void> clearAllPassages() async {
|
||||
await _passageBox.clear();
|
||||
_resetCache(); // Réinitialiser le cache après suppression de toutes les données
|
||||
resetCache(); // Réinitialiser le cache après suppression de toutes les données
|
||||
notifyListeners();
|
||||
_notifyPassageStream();
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:geosector_app/core/data/models/sector_model.dart';
|
||||
@@ -29,9 +28,10 @@ class SectorRepository extends ChangeNotifier {
|
||||
// Constante pour l'ID par défaut
|
||||
static const int defaultSectorId = 1;
|
||||
|
||||
// Méthode pour réinitialiser le cache après modification de la box
|
||||
void _resetCache() {
|
||||
// Méthode publique pour réinitialiser le cache (ex: après nettoyage complet)
|
||||
void resetCache() {
|
||||
_cachedSectorBox = null;
|
||||
debugPrint('🔄 Cache SectorRepository réinitialisé');
|
||||
}
|
||||
|
||||
// Récupérer tous les secteurs
|
||||
@@ -47,14 +47,14 @@ class SectorRepository extends ChangeNotifier {
|
||||
// Sauvegarder un secteur
|
||||
Future<void> saveSector(SectorModel sector) async {
|
||||
await _sectorBox.put(sector.id, sector);
|
||||
_resetCache(); // Réinitialiser le cache après modification
|
||||
resetCache(); // Réinitialiser le cache après modification
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
// Supprimer un secteur
|
||||
Future<void> deleteSector(int id) async {
|
||||
await _sectorBox.delete(id);
|
||||
_resetCache(); // Réinitialiser le cache après modification
|
||||
resetCache(); // Réinitialiser le cache après modification
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ class SectorRepository extends ChangeNotifier {
|
||||
for (final sector in sectors) {
|
||||
await _sectorBox.put(sector.id, sector);
|
||||
}
|
||||
_resetCache(); // Réinitialiser le cache après modification massive
|
||||
resetCache(); // Réinitialiser le cache après modification massive
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
@@ -108,7 +108,7 @@ class SectorRepository extends ChangeNotifier {
|
||||
}
|
||||
|
||||
debugPrint('$count secteurs traités et stockés');
|
||||
_resetCache(); // Réinitialiser le cache après traitement des données API
|
||||
resetCache(); // Réinitialiser le cache après traitement des données API
|
||||
notifyListeners();
|
||||
} catch (e) {
|
||||
debugPrint('Erreur lors du traitement des secteurs: $e');
|
||||
|
||||
@@ -22,6 +22,7 @@ import 'package:geosector_app/core/models/loading_state.dart';
|
||||
|
||||
class UserRepository extends ChangeNotifier {
|
||||
bool _isLoading = false;
|
||||
Timer? _refreshTimer;
|
||||
|
||||
// Constructeur simplifié - plus d'injection d'ApiService
|
||||
UserRepository() {
|
||||
@@ -306,6 +307,12 @@ class UserRepository extends ChangeNotifier {
|
||||
debugPrint('⚠️ Erreur initialisation chat (non bloquant): $chatError');
|
||||
}
|
||||
|
||||
// Sauvegarder le timestamp de dernière sync après un login réussi
|
||||
await _saveLastSyncTimestamp(DateTime.now());
|
||||
|
||||
// Démarrer le timer de refresh automatique
|
||||
_startAutoRefreshTimer();
|
||||
|
||||
debugPrint('✅ Connexion réussie');
|
||||
return true;
|
||||
} catch (e) {
|
||||
@@ -388,13 +395,16 @@ class UserRepository extends ChangeNotifier {
|
||||
// Supprimer la session API
|
||||
setSessionId(null);
|
||||
|
||||
// Arrêter le timer de refresh automatique
|
||||
_stopAutoRefreshTimer();
|
||||
|
||||
// Effacer les données via les services singleton
|
||||
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();
|
||||
|
||||
@@ -633,6 +643,298 @@ class UserRepository extends ChangeNotifier {
|
||||
return amicale;
|
||||
}
|
||||
|
||||
// === SYNCHRONISATION ET REFRESH ===
|
||||
|
||||
/// Rafraîchir la session (soft login)
|
||||
/// Utilise un refresh partiel si la dernière sync date de moins de 24h
|
||||
/// Sinon fait un refresh complet
|
||||
Future<bool> refreshSession() async {
|
||||
try {
|
||||
debugPrint('🔄 Début du refresh de session...');
|
||||
|
||||
// Vérifier qu'on a bien une session valide
|
||||
if (!isLoggedIn || currentUser?.sessionId == null) {
|
||||
debugPrint('⚠️ Pas de session valide pour le refresh');
|
||||
return false;
|
||||
}
|
||||
|
||||
// NOUVEAU : Vérifier la connexion internet avant de faire des appels API
|
||||
final hasConnection = await ApiService.instance.hasInternetConnection();
|
||||
if (!hasConnection) {
|
||||
debugPrint('📵 Pas de connexion internet - refresh annulé');
|
||||
// On maintient la session locale mais on ne fait pas d'appel API
|
||||
return true; // Retourner true car ce n'est pas une erreur
|
||||
}
|
||||
|
||||
// S'assurer que le timer de refresh automatique est démarré
|
||||
if (_refreshTimer == null || !_refreshTimer!.isActive) {
|
||||
_startAutoRefreshTimer();
|
||||
}
|
||||
|
||||
// Récupérer la dernière date de sync depuis settings
|
||||
DateTime? lastSync;
|
||||
try {
|
||||
if (Hive.isBoxOpen(AppKeys.settingsBoxName)) {
|
||||
final settingsBox = Hive.box(AppKeys.settingsBoxName);
|
||||
final lastSyncString = settingsBox.get('last_sync') as String?;
|
||||
if (lastSyncString != null) {
|
||||
lastSync = DateTime.parse(lastSyncString);
|
||||
debugPrint('📅 Dernière sync: ${lastSync.toIso8601String()}');
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint('⚠️ Erreur lecture last_sync: $e');
|
||||
}
|
||||
|
||||
// Déterminer si on fait un refresh partiel ou complet
|
||||
// Refresh partiel si:
|
||||
// - On a une date de dernière sync
|
||||
// - Cette date est de moins de 24h
|
||||
final now = DateTime.now();
|
||||
final shouldPartialRefresh = lastSync != null &&
|
||||
now.difference(lastSync).inHours < 24;
|
||||
|
||||
if (shouldPartialRefresh) {
|
||||
debugPrint('⚡ Refresh partiel (dernière sync < 24h)');
|
||||
|
||||
try {
|
||||
// Appel API pour refresh partiel
|
||||
final response = await ApiService.instance.refreshSessionPartial(lastSync);
|
||||
|
||||
if (response.data != null && response.data['status'] == 'success') {
|
||||
// Traiter uniquement les données modifiées
|
||||
await _processPartialRefreshData(response.data);
|
||||
|
||||
// Mettre à jour last_sync
|
||||
await _saveLastSyncTimestamp(now);
|
||||
|
||||
debugPrint('✅ Refresh partiel réussi');
|
||||
return true;
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint('⚠️ Erreur refresh partiel: $e');
|
||||
|
||||
// Vérifier si c'est une erreur d'authentification
|
||||
if (_isAuthenticationError(e)) {
|
||||
debugPrint('🔒 Erreur d\'authentification détectée - nettoyage de la session locale');
|
||||
await _clearInvalidSession();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Sinon, on tente un refresh complet
|
||||
debugPrint('Tentative de refresh complet...');
|
||||
}
|
||||
}
|
||||
|
||||
// Refresh complet
|
||||
debugPrint('🔄 Refresh complet des données...');
|
||||
|
||||
try {
|
||||
final response = await ApiService.instance.refreshSessionAll();
|
||||
|
||||
if (response.data != null && response.data['status'] == 'success') {
|
||||
// Traiter toutes les données comme un login
|
||||
await DataLoadingService.instance.processLoginData(response.data);
|
||||
|
||||
// Mettre à jour last_sync
|
||||
await _saveLastSyncTimestamp(now);
|
||||
|
||||
debugPrint('✅ Refresh complet réussi');
|
||||
return true;
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint('❌ Erreur refresh complet: $e');
|
||||
|
||||
// Vérifier si c'est une erreur d'authentification
|
||||
if (_isAuthenticationError(e)) {
|
||||
debugPrint('🔒 Session invalide côté serveur - nettoyage de la session locale');
|
||||
await _clearInvalidSession();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
} catch (e) {
|
||||
debugPrint('❌ Erreur générale refresh session: $e');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// Traiter les données d'un refresh partiel
|
||||
Future<void> _processPartialRefreshData(Map<String, dynamic> data) async {
|
||||
try {
|
||||
debugPrint('📦 Traitement des données partielles...');
|
||||
|
||||
// Traiter les secteurs modifiés
|
||||
if (data['sectors'] != null && data['sectors'] is List) {
|
||||
final sectorsBox = Hive.box<SectorModel>(AppKeys.sectorsBoxName);
|
||||
for (final sectorData in data['sectors']) {
|
||||
final sector = SectorModel.fromJson(sectorData);
|
||||
await sectorsBox.put(sector.id, sector);
|
||||
}
|
||||
debugPrint('✅ ${data['sectors'].length} secteurs mis à jour');
|
||||
}
|
||||
|
||||
// Traiter les passages modifiés
|
||||
if (data['passages'] != null && data['passages'] is List) {
|
||||
final passagesBox = Hive.box<PassageModel>(AppKeys.passagesBoxName);
|
||||
for (final passageData in data['passages']) {
|
||||
final passage = PassageModel.fromJson(passageData);
|
||||
await passagesBox.put(passage.id, passage);
|
||||
}
|
||||
debugPrint('✅ ${data['passages'].length} passages mis à jour');
|
||||
}
|
||||
|
||||
// Traiter les opérations modifiées
|
||||
if (data['operations'] != null && data['operations'] is List) {
|
||||
final operationsBox = Hive.box<OperationModel>(AppKeys.operationsBoxName);
|
||||
for (final operationData in data['operations']) {
|
||||
final operation = OperationModel.fromJson(operationData);
|
||||
await operationsBox.put(operation.id, operation);
|
||||
}
|
||||
debugPrint('✅ ${data['operations'].length} opérations mises à jour');
|
||||
}
|
||||
|
||||
// Traiter les membres modifiés
|
||||
if (data['membres'] != null && data['membres'] is List) {
|
||||
final membresBox = Hive.box<MembreModel>(AppKeys.membresBoxName);
|
||||
for (final membreData in data['membres']) {
|
||||
final membre = MembreModel.fromJson(membreData);
|
||||
await membresBox.put(membre.id, membre);
|
||||
}
|
||||
debugPrint('✅ ${data['membres'].length} membres mis à jour');
|
||||
}
|
||||
|
||||
} catch (e) {
|
||||
debugPrint('❌ Erreur traitement données partielles: $e');
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
/// Sauvegarder le timestamp de la dernière sync
|
||||
Future<void> _saveLastSyncTimestamp(DateTime timestamp) async {
|
||||
try {
|
||||
if (Hive.isBoxOpen(AppKeys.settingsBoxName)) {
|
||||
final settingsBox = Hive.box(AppKeys.settingsBoxName);
|
||||
await settingsBox.put('last_sync', timestamp.toIso8601String());
|
||||
debugPrint('💾 Timestamp last_sync sauvegardé: ${timestamp.toIso8601String()}');
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint('❌ Erreur sauvegarde last_sync: $e');
|
||||
}
|
||||
}
|
||||
|
||||
/// Vérifie si l'erreur est une erreur d'authentification (401, 403)
|
||||
/// Retourne false pour les erreurs 404 (route non trouvée)
|
||||
bool _isAuthenticationError(dynamic error) {
|
||||
final errorMessage = error.toString().toLowerCase();
|
||||
|
||||
// Si c'est une erreur 404, ce n'est pas une erreur d'authentification
|
||||
// C'est juste que la route n'existe pas encore côté API
|
||||
if (errorMessage.contains('404') || errorMessage.contains('not found')) {
|
||||
debugPrint('⚠️ Route API non trouvée (404) - en attente de l\'implémentation côté serveur');
|
||||
return false;
|
||||
}
|
||||
|
||||
// Vérifier les vraies erreurs d'authentification
|
||||
return errorMessage.contains('401') ||
|
||||
errorMessage.contains('403') ||
|
||||
errorMessage.contains('unauthorized') ||
|
||||
errorMessage.contains('forbidden') ||
|
||||
errorMessage.contains('session expired') ||
|
||||
errorMessage.contains('authentication failed');
|
||||
}
|
||||
|
||||
/// Nettoie la session locale invalide
|
||||
Future<void> _clearInvalidSession() async {
|
||||
try {
|
||||
debugPrint('🗑️ Nettoyage de la session invalide...');
|
||||
|
||||
// Arrêter le timer de refresh
|
||||
_stopAutoRefreshTimer();
|
||||
|
||||
// Nettoyer les données de session
|
||||
await CurrentUserService.instance.clearUser();
|
||||
await CurrentAmicaleService.instance.clearAmicale();
|
||||
|
||||
// Nettoyer les IDs dans settings
|
||||
if (Hive.isBoxOpen(AppKeys.settingsBoxName)) {
|
||||
final settingsBox = Hive.box(AppKeys.settingsBoxName);
|
||||
await settingsBox.delete('current_user_id');
|
||||
await settingsBox.delete('current_amicale_id');
|
||||
await settingsBox.delete('last_sync');
|
||||
}
|
||||
|
||||
// Supprimer le sessionId de l'API
|
||||
ApiService.instance.setSessionId(null);
|
||||
|
||||
debugPrint('✅ Session locale nettoyée suite à erreur d\'authentification');
|
||||
} catch (e) {
|
||||
debugPrint('❌ Erreur lors du nettoyage de session: $e');
|
||||
}
|
||||
}
|
||||
|
||||
// === TIMER DE REFRESH AUTOMATIQUE ===
|
||||
|
||||
/// Démarre le timer de refresh automatique (toutes les 30 minutes)
|
||||
void _startAutoRefreshTimer() {
|
||||
// Arrêter le timer existant s'il y en a un
|
||||
_stopAutoRefreshTimer();
|
||||
|
||||
// Démarrer un nouveau timer qui se déclenche toutes les 30 minutes
|
||||
_refreshTimer = Timer.periodic(const Duration(minutes: 30), (timer) async {
|
||||
if (isLoggedIn) {
|
||||
debugPrint('⏰ Refresh automatique déclenché (30 minutes)');
|
||||
|
||||
// Vérifier la connexion avant de tenter le refresh
|
||||
final hasConnection = await ApiService.instance.hasInternetConnection();
|
||||
if (!hasConnection) {
|
||||
debugPrint('📵 Refresh automatique annulé - pas de connexion');
|
||||
return;
|
||||
}
|
||||
|
||||
// Appel silencieux du refresh - on ne veut pas spammer les logs
|
||||
try {
|
||||
await refreshSession();
|
||||
} catch (e) {
|
||||
// Si c'est une erreur 404, on ignore silencieusement
|
||||
if (e.toString().toLowerCase().contains('404')) {
|
||||
debugPrint('ℹ️ Refresh automatique ignoré (routes non disponibles)');
|
||||
} else {
|
||||
debugPrint('⚠️ Erreur refresh automatique: $e');
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Si l'utilisateur n'est plus connecté, arrêter le timer
|
||||
_stopAutoRefreshTimer();
|
||||
}
|
||||
});
|
||||
|
||||
debugPrint('⏰ Timer de refresh automatique démarré (interval: 30 minutes)');
|
||||
}
|
||||
|
||||
/// Arrête le timer de refresh automatique
|
||||
void _stopAutoRefreshTimer() {
|
||||
if (_refreshTimer != null && _refreshTimer!.isActive) {
|
||||
_refreshTimer!.cancel();
|
||||
_refreshTimer = null;
|
||||
debugPrint('⏰ Timer de refresh automatique arrêté');
|
||||
}
|
||||
}
|
||||
|
||||
/// Déclenche manuellement un refresh (peut être appelé depuis l'UI)
|
||||
Future<void> triggerManualRefresh() async {
|
||||
debugPrint('🔄 Refresh manuel déclenché par l\'utilisateur');
|
||||
await refreshSession();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_stopAutoRefreshTimer();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
// === SYNCHRONISATION ===
|
||||
|
||||
/// Synchroniser un utilisateur spécifique avec le serveur
|
||||
|
||||
Reference in New Issue
Block a user