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,13 +1,14 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'dart:math' as math;
|
||||
import 'dart:convert';
|
||||
import 'package:flutter/foundation.dart' show kIsWeb, kDebugMode;
|
||||
import 'package:flutter/foundation.dart' show kIsWeb, kDebugMode, debugPrint;
|
||||
import 'package:geosector_app/core/services/js_stub.dart'
|
||||
if (dart.library.js) 'dart:js' as js;
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:hive_flutter/hive_flutter.dart';
|
||||
import 'package:geosector_app/core/services/app_info_service.dart';
|
||||
import 'package:geosector_app/core/services/current_user_service.dart';
|
||||
import 'package:geosector_app/core/constants/app_keys.dart';
|
||||
import 'package:geosector_app/presentation/widgets/custom_button.dart';
|
||||
import 'package:geosector_app/presentation/widgets/custom_text_field.dart';
|
||||
@@ -163,7 +164,7 @@ class _LoginPageState extends State<LoginPage> {
|
||||
// Vérification du type de connexion (seulement si Hive est initialisé)
|
||||
if (widget.loginType == null) {
|
||||
// Si aucun type n'est spécifié, naviguer vers la splash page
|
||||
print(
|
||||
debugPrint(
|
||||
'LoginPage: Aucun type de connexion spécifié, navigation vers splash page');
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
GoRouter.of(context).go('/');
|
||||
@@ -171,7 +172,7 @@ class _LoginPageState extends State<LoginPage> {
|
||||
_loginType = '';
|
||||
} else {
|
||||
_loginType = widget.loginType!;
|
||||
print('LoginPage: Type de connexion utilisé: $_loginType');
|
||||
debugPrint('LoginPage: Type de connexion utilisé: $_loginType');
|
||||
}
|
||||
|
||||
// En mode web, essayer de détecter le paramètre dans l'URL directement
|
||||
@@ -222,17 +223,17 @@ class _LoginPageState extends State<LoginPage> {
|
||||
result.toLowerCase() == 'user') {
|
||||
setState(() {
|
||||
_loginType = 'user';
|
||||
print(
|
||||
debugPrint(
|
||||
'LoginPage: Type détecté depuis sessionStorage: $_loginType');
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
print('LoginPage: Erreur lors de l\'accès au sessionStorage: $e');
|
||||
debugPrint('LoginPage: Erreur lors de l\'accès au sessionStorage: $e');
|
||||
}
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
print('Erreur lors de la récupération des paramètres d\'URL: $e');
|
||||
debugPrint('Erreur lors de la récupération des paramètres d\'URL: $e');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -327,7 +328,7 @@ class _LoginPageState extends State<LoginPage> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
print('DEBUG BUILD: Reconstruction de LoginPage avec type: $_loginType');
|
||||
debugPrint('DEBUG BUILD: Reconstruction de LoginPage avec type: $_loginType');
|
||||
|
||||
// Utiliser l'instance globale de userRepository
|
||||
final theme = Theme.of(context);
|
||||
@@ -565,13 +566,13 @@ class _LoginPageState extends State<LoginPage> {
|
||||
_formKey.currentState!.validate()) {
|
||||
// Vérifier que le type de connexion est spécifié
|
||||
if (_loginType.isEmpty) {
|
||||
print(
|
||||
debugPrint(
|
||||
'Login: Type non spécifié, redirection vers la page de démarrage');
|
||||
context.go('/');
|
||||
return;
|
||||
}
|
||||
|
||||
print(
|
||||
debugPrint(
|
||||
'Login: Tentative avec type: $_loginType');
|
||||
|
||||
final success =
|
||||
@@ -615,19 +616,37 @@ class _LoginPageState extends State<LoginPage> {
|
||||
debugPrint(
|
||||
'Role de l\'utilisateur: $roleValue');
|
||||
|
||||
// Redirection simple basée sur le rôle
|
||||
if (roleValue > 1) {
|
||||
debugPrint(
|
||||
'Redirection vers /admin (rôle > 1)');
|
||||
if (context.mounted) {
|
||||
context.go('/admin');
|
||||
}
|
||||
} else {
|
||||
debugPrint(
|
||||
'Redirection vers /user (rôle = 1)');
|
||||
// Définir le mode d'affichage selon le type de connexion
|
||||
if (_loginType == 'user') {
|
||||
// Connexion en mode user : toujours mode user
|
||||
await CurrentUserService.instance.setDisplayMode('user');
|
||||
debugPrint('Mode d\'affichage défini: user');
|
||||
if (context.mounted) {
|
||||
context.go('/user');
|
||||
}
|
||||
} else {
|
||||
// Connexion en mode admin
|
||||
if (roleValue >= 2) {
|
||||
await CurrentUserService.instance.setDisplayMode('admin');
|
||||
debugPrint('Mode d\'affichage défini: admin');
|
||||
if (context.mounted) {
|
||||
context.go('/admin');
|
||||
}
|
||||
} else {
|
||||
// Un user (rôle 1) ne peut pas se connecter en mode admin
|
||||
debugPrint('Erreur: User (rôle 1) tentant de se connecter en mode admin');
|
||||
if (context.mounted) {
|
||||
ScaffoldMessenger.of(context)
|
||||
.showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text(
|
||||
'Accès administrateur non autorisé pour ce compte.'),
|
||||
backgroundColor: Colors.red,
|
||||
),
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else if (context.mounted) {
|
||||
ScaffoldMessenger.of(context)
|
||||
@@ -716,7 +735,7 @@ class _LoginPageState extends State<LoginPage> {
|
||||
|
||||
// Vérifier que le type de connexion est spécifié
|
||||
if (_loginType.isEmpty) {
|
||||
print(
|
||||
debugPrint(
|
||||
'Login: Type non spécifié, redirection vers la page de démarrage');
|
||||
if (context.mounted) {
|
||||
context.go('/');
|
||||
@@ -724,7 +743,7 @@ class _LoginPageState extends State<LoginPage> {
|
||||
return;
|
||||
}
|
||||
|
||||
print(
|
||||
debugPrint(
|
||||
'Login: Tentative avec type: $_loginType');
|
||||
|
||||
// Utiliser le nouveau spinner moderne pour la connexion
|
||||
@@ -773,19 +792,37 @@ class _LoginPageState extends State<LoginPage> {
|
||||
debugPrint(
|
||||
'Role de l\'utilisateur: $roleValue');
|
||||
|
||||
// Redirection simple basée sur le rôle
|
||||
if (roleValue > 1) {
|
||||
debugPrint(
|
||||
'Redirection vers /admin (rôle > 1)');
|
||||
if (context.mounted) {
|
||||
context.go('/admin');
|
||||
}
|
||||
} else {
|
||||
debugPrint(
|
||||
'Redirection vers /user (rôle = 1)');
|
||||
// Définir le mode d'affichage selon le type de connexion
|
||||
if (_loginType == 'user') {
|
||||
// Connexion en mode user : toujours mode user
|
||||
await CurrentUserService.instance.setDisplayMode('user');
|
||||
debugPrint('Mode d\'affichage défini: user');
|
||||
if (context.mounted) {
|
||||
context.go('/user');
|
||||
}
|
||||
} else {
|
||||
// Connexion en mode admin
|
||||
if (roleValue >= 2) {
|
||||
await CurrentUserService.instance.setDisplayMode('admin');
|
||||
debugPrint('Mode d\'affichage défini: admin');
|
||||
if (context.mounted) {
|
||||
context.go('/admin');
|
||||
}
|
||||
} else {
|
||||
// Un user (rôle 1) ne peut pas se connecter en mode admin
|
||||
debugPrint('Erreur: User (rôle 1) tentant de se connecter en mode admin');
|
||||
if (context.mounted) {
|
||||
ScaffoldMessenger.of(context)
|
||||
.showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text(
|
||||
'Accès administrateur non autorisé pour ce compte.'),
|
||||
backgroundColor: Colors.red,
|
||||
),
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else if (context.mounted) {
|
||||
ScaffoldMessenger.of(context)
|
||||
@@ -998,8 +1035,8 @@ class _LoginPageState extends State<LoginPage> {
|
||||
final baseUrl = Uri.base.origin;
|
||||
final apiUrl = '$baseUrl/api/lostpassword';
|
||||
|
||||
print('Envoi de la requête à: $apiUrl');
|
||||
print('Email: ${emailController.text.trim()}');
|
||||
debugPrint('Envoi de la requête à: $apiUrl');
|
||||
debugPrint('Email: ${emailController.text.trim()}');
|
||||
|
||||
http.Response? response;
|
||||
|
||||
@@ -1013,15 +1050,15 @@ class _LoginPageState extends State<LoginPage> {
|
||||
}),
|
||||
);
|
||||
|
||||
print('Réponse reçue: ${response.statusCode}');
|
||||
print('Corps de la réponse: ${response.body}');
|
||||
debugPrint('Réponse reçue: ${response.statusCode}');
|
||||
debugPrint('Corps de la réponse: ${response.body}');
|
||||
|
||||
// Si la réponse est 404, c'est peut-être un problème de route
|
||||
if (response.statusCode == 404) {
|
||||
// Essayer avec une URL alternative
|
||||
final alternativeUrl =
|
||||
'$baseUrl/api/index.php/lostpassword';
|
||||
print(
|
||||
debugPrint(
|
||||
'Tentative avec URL alternative: $alternativeUrl');
|
||||
|
||||
final alternativeResponse = await http.post(
|
||||
@@ -1032,9 +1069,9 @@ class _LoginPageState extends State<LoginPage> {
|
||||
}),
|
||||
);
|
||||
|
||||
print(
|
||||
debugPrint(
|
||||
'Réponse alternative reçue: ${alternativeResponse.statusCode}');
|
||||
print(
|
||||
debugPrint(
|
||||
'Corps de la réponse alternative: ${alternativeResponse.body}');
|
||||
|
||||
// Si la réponse alternative est un succès, utiliser cette réponse
|
||||
@@ -1043,7 +1080,7 @@ class _LoginPageState extends State<LoginPage> {
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
print(
|
||||
debugPrint(
|
||||
'Erreur lors de l\'envoi de la requête: $e');
|
||||
throw Exception('Erreur de connexion: $e');
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter/foundation.dart' show kIsWeb;
|
||||
import 'package:flutter/foundation.dart' show kIsWeb, debugPrint;
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'dart:math' as math;
|
||||
import 'dart:convert';
|
||||
@@ -256,7 +256,7 @@ class _RegisterPageState extends State<RegisterPage> {
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
print('Erreur lors de la récupération des villes: $e');
|
||||
debugPrint('Erreur lors de la récupération des villes: $e');
|
||||
setState(() {
|
||||
_cities = [];
|
||||
_isLoadingCities = false;
|
||||
|
||||
@@ -10,9 +10,14 @@ import 'dart:math' as math;
|
||||
import 'package:flutter/foundation.dart' show kIsWeb;
|
||||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
// Import conditionnel pour le web
|
||||
import 'package:universal_html/html.dart' as html;
|
||||
// Import des repositories pour reset du cache
|
||||
import 'package:geosector_app/app.dart' show passageRepository, sectorRepository, membreRepository;
|
||||
// Import des services pour la gestion de session F5
|
||||
import 'package:geosector_app/core/services/current_user_service.dart';
|
||||
import 'package:geosector_app/core/services/api_service.dart';
|
||||
import 'package:geosector_app/core/services/data_loading_service.dart';
|
||||
|
||||
class SplashPage extends StatefulWidget {
|
||||
/// Action à effectuer après l'initialisation (login ou register)
|
||||
@@ -130,18 +135,29 @@ class _SplashPageState extends State<SplashPage> with SingleTickerProviderStateM
|
||||
});
|
||||
}
|
||||
|
||||
// Étape 2: Sauvegarder les données de pending_requests
|
||||
debugPrint('💾 Sauvegarde des requêtes en attente...');
|
||||
// Étape 2: Sauvegarder les données critiques (pending_requests + app_version)
|
||||
debugPrint('💾 Sauvegarde des données critiques...');
|
||||
List<dynamic>? pendingRequests;
|
||||
String? savedAppVersion;
|
||||
try {
|
||||
// Sauvegarder pending_requests
|
||||
if (Hive.isBoxOpen(AppKeys.pendingRequestsBoxName)) {
|
||||
final pendingBox = Hive.box(AppKeys.pendingRequestsBoxName);
|
||||
pendingRequests = pendingBox.values.toList();
|
||||
debugPrint('📊 ${pendingRequests.length} requêtes en attente sauvegardées');
|
||||
await pendingBox.close();
|
||||
}
|
||||
|
||||
// Sauvegarder app_version pour éviter de perdre l'info de version
|
||||
if (Hive.isBoxOpen(AppKeys.settingsBoxName)) {
|
||||
final settingsBox = Hive.box(AppKeys.settingsBoxName);
|
||||
savedAppVersion = settingsBox.get('app_version') as String?;
|
||||
if (savedAppVersion != null) {
|
||||
debugPrint('📦 Version sauvegardée: $savedAppVersion');
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint('⚠️ Erreur lors de la sauvegarde des requêtes: $e');
|
||||
debugPrint('⚠️ Erreur lors de la sauvegarde: $e');
|
||||
}
|
||||
|
||||
if (mounted) {
|
||||
@@ -194,7 +210,7 @@ class _SplashPageState extends State<SplashPage> with SingleTickerProviderStateM
|
||||
await Future.delayed(const Duration(milliseconds: 500));
|
||||
await Hive.initFlutter();
|
||||
|
||||
// Étape 6: Restaurer les requêtes en attente
|
||||
// Étape 6: Restaurer les données critiques
|
||||
if (pendingRequests != null && pendingRequests.isNotEmpty) {
|
||||
debugPrint('♻️ Restauration des requêtes en attente...');
|
||||
final pendingBox = await Hive.openBox(AppKeys.pendingRequestsBoxName);
|
||||
@@ -204,6 +220,14 @@ class _SplashPageState extends State<SplashPage> with SingleTickerProviderStateM
|
||||
debugPrint('✅ ${pendingRequests.length} requêtes restaurées');
|
||||
}
|
||||
|
||||
// Restaurer app_version pour maintenir la détection de changement de version
|
||||
if (savedAppVersion != null) {
|
||||
debugPrint('♻️ Restauration de la version...');
|
||||
final settingsBox = await Hive.openBox(AppKeys.settingsBoxName);
|
||||
await settingsBox.put('app_version', savedAppVersion);
|
||||
debugPrint('✅ Version restaurée: $savedAppVersion');
|
||||
}
|
||||
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_statusMessage = "Nettoyage terminé !";
|
||||
@@ -211,13 +235,6 @@ class _SplashPageState extends State<SplashPage> with SingleTickerProviderStateM
|
||||
});
|
||||
}
|
||||
|
||||
// Étape 7: Sauvegarder la nouvelle version
|
||||
if (!manual && kIsWeb) {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setString('app_version', _appVersion);
|
||||
debugPrint('💾 Version $_appVersion sauvegardée');
|
||||
}
|
||||
|
||||
debugPrint('🎉 === NETTOYAGE TERMINÉ AVEC SUCCÈS === 🎉');
|
||||
|
||||
// Petit délai pour voir le message de succès
|
||||
@@ -250,6 +267,206 @@ class _SplashPageState extends State<SplashPage> with SingleTickerProviderStateM
|
||||
}
|
||||
}
|
||||
|
||||
/// Réinitialise le cache de tous les repositories après nettoyage complet
|
||||
void _resetAllRepositoriesCache() {
|
||||
try {
|
||||
debugPrint('🔄 === RESET DU CACHE DES REPOSITORIES === 🔄');
|
||||
|
||||
// Reset du cache des 3 repositories qui utilisent le pattern de cache
|
||||
passageRepository.resetCache();
|
||||
sectorRepository.resetCache();
|
||||
membreRepository.resetCache();
|
||||
|
||||
debugPrint('✅ Cache de tous les repositories réinitialisé');
|
||||
} catch (e) {
|
||||
debugPrint('⚠️ Erreur lors du reset des caches: $e');
|
||||
// Ne pas faire échouer le processus si le reset échoue
|
||||
}
|
||||
}
|
||||
|
||||
/// Détecte et gère le refresh (F5) avec session existante
|
||||
/// Retourne true si une session a été restaurée, false sinon
|
||||
Future<bool> _handleSessionRefreshIfNeeded() async {
|
||||
if (!kIsWeb) {
|
||||
debugPrint('📱 Plateforme mobile - pas de gestion F5');
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
debugPrint('🔍 Vérification d\'une session existante (F5)...');
|
||||
|
||||
// Charger l'utilisateur depuis Hive
|
||||
await CurrentUserService.instance.loadFromHive();
|
||||
|
||||
final isLoggedIn = CurrentUserService.instance.isLoggedIn;
|
||||
final displayMode = CurrentUserService.instance.displayMode;
|
||||
final sessionId = CurrentUserService.instance.sessionId;
|
||||
|
||||
if (!isLoggedIn || sessionId == null) {
|
||||
debugPrint('ℹ️ Aucune session active - affichage normal de la splash');
|
||||
return false;
|
||||
}
|
||||
|
||||
debugPrint('🔄 Session active détectée - mode: $displayMode');
|
||||
debugPrint('🔄 Rechargement des données depuis l\'API...');
|
||||
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_statusMessage = "Restauration de votre session...";
|
||||
_progress = 0.85;
|
||||
});
|
||||
}
|
||||
|
||||
// Configurer ApiService avec le sessionId existant
|
||||
ApiService.instance.setSessionId(sessionId);
|
||||
|
||||
// Appeler le nouvel endpoint API pour restaurer la session
|
||||
final response = await ApiService.instance.get(
|
||||
'/api/user/session',
|
||||
queryParameters: {'mode': displayMode},
|
||||
);
|
||||
|
||||
// Gestion des codes de retour HTTP
|
||||
final statusCode = response.statusCode ?? 0;
|
||||
final data = response.data as Map<String, dynamic>?;
|
||||
|
||||
switch (statusCode) {
|
||||
case 200:
|
||||
// Succès - traiter les données
|
||||
if (data == null || data['success'] != true) {
|
||||
debugPrint('❌ Format de réponse invalide (200 mais pas success=true)');
|
||||
await CurrentUserService.instance.clearUser();
|
||||
return false;
|
||||
}
|
||||
|
||||
debugPrint('✅ Données reçues de l\'API, traitement...');
|
||||
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_statusMessage = "Chargement de vos données...";
|
||||
_progress = 0.90;
|
||||
});
|
||||
}
|
||||
|
||||
// Traiter les données avec DataLoadingService
|
||||
final apiData = data['data'] as Map<String, dynamic>?;
|
||||
if (apiData == null) {
|
||||
debugPrint('❌ Données manquantes dans la réponse');
|
||||
await CurrentUserService.instance.clearUser();
|
||||
return false;
|
||||
}
|
||||
|
||||
await DataLoadingService.instance.processLoginData(apiData);
|
||||
debugPrint('✅ Session restaurée avec succès');
|
||||
break;
|
||||
|
||||
case 400:
|
||||
// Paramètre mode invalide - erreur technique
|
||||
debugPrint('❌ Paramètre mode invalide: $displayMode');
|
||||
await CurrentUserService.instance.clearUser();
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_statusMessage = "Erreur technique - veuillez vous reconnecter";
|
||||
});
|
||||
}
|
||||
return false;
|
||||
|
||||
case 401:
|
||||
// Session invalide ou expirée
|
||||
debugPrint('⚠️ Session invalide ou expirée');
|
||||
await CurrentUserService.instance.clearUser();
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_statusMessage = "Session expirée - veuillez vous reconnecter";
|
||||
});
|
||||
}
|
||||
return false;
|
||||
|
||||
case 403:
|
||||
// Accès interdit (membre → admin) ou entité inactive
|
||||
final message = data?['message'] ?? 'Accès interdit';
|
||||
debugPrint('🚫 Accès interdit: $message');
|
||||
await CurrentUserService.instance.clearUser();
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_statusMessage = "Accès interdit - veuillez vous reconnecter";
|
||||
});
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(message),
|
||||
backgroundColor: Colors.orange,
|
||||
duration: const Duration(seconds: 5),
|
||||
),
|
||||
);
|
||||
}
|
||||
return false;
|
||||
|
||||
case 500:
|
||||
// Erreur serveur
|
||||
final message = data?['message'] ?? 'Erreur serveur';
|
||||
debugPrint('❌ Erreur serveur: $message');
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_statusMessage = "Erreur serveur - veuillez réessayer";
|
||||
});
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text('Erreur serveur: $message'),
|
||||
backgroundColor: Colors.red,
|
||||
duration: const Duration(seconds: 5),
|
||||
),
|
||||
);
|
||||
}
|
||||
// Ne pas effacer la session en cas d'erreur serveur
|
||||
return false;
|
||||
|
||||
default:
|
||||
// Code de retour inattendu
|
||||
debugPrint('❌ Code HTTP inattendu: $statusCode');
|
||||
await CurrentUserService.instance.clearUser();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_statusMessage = "Session restaurée !";
|
||||
_progress = 0.95;
|
||||
});
|
||||
}
|
||||
|
||||
// Petit délai pour voir le message
|
||||
await Future.delayed(const Duration(milliseconds: 500));
|
||||
|
||||
// Rediriger vers la bonne interface selon le mode
|
||||
if (!mounted) return true;
|
||||
|
||||
if (displayMode == 'admin') {
|
||||
debugPrint('🔀 Redirection vers interface admin');
|
||||
context.go('/admin/home');
|
||||
} else {
|
||||
debugPrint('🔀 Redirection vers interface user');
|
||||
context.go('/user/field-mode');
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
} catch (e) {
|
||||
debugPrint('❌ Erreur lors de la restauration de session: $e');
|
||||
|
||||
// En cas d'erreur, effacer la session invalide
|
||||
await CurrentUserService.instance.clearUser();
|
||||
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_statusMessage = "Erreur de restauration - veuillez vous reconnecter";
|
||||
_progress = 0.0;
|
||||
});
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// Vérifie si une nouvelle version est disponible et nettoie si nécessaire
|
||||
Future<void> _checkVersionAndCleanIfNeeded() async {
|
||||
if (!kIsWeb) {
|
||||
@@ -258,9 +475,14 @@ class _SplashPageState extends State<SplashPage> with SingleTickerProviderStateM
|
||||
}
|
||||
|
||||
try {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final lastVersion = prefs.getString('app_version') ?? '';
|
||||
|
||||
String lastVersion = '';
|
||||
|
||||
// Lire la version depuis Hive settings
|
||||
if (Hive.isBoxOpen(AppKeys.settingsBoxName)) {
|
||||
final settingsBox = Hive.box(AppKeys.settingsBoxName);
|
||||
lastVersion = settingsBox.get('app_version', defaultValue: '') as String;
|
||||
}
|
||||
|
||||
debugPrint('🔍 Vérification de version:');
|
||||
debugPrint(' Version stockée: $lastVersion');
|
||||
debugPrint(' Version actuelle: $_appVersion');
|
||||
@@ -269,7 +491,7 @@ class _SplashPageState extends State<SplashPage> with SingleTickerProviderStateM
|
||||
if (lastVersion.isNotEmpty && lastVersion != _appVersion) {
|
||||
debugPrint('🆕 NOUVELLE VERSION DÉTECTÉE !');
|
||||
debugPrint(' Migration de $lastVersion vers $_appVersion');
|
||||
|
||||
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_statusMessage = "Nouvelle version détectée, mise à jour...";
|
||||
@@ -278,10 +500,17 @@ class _SplashPageState extends State<SplashPage> with SingleTickerProviderStateM
|
||||
|
||||
// Effectuer le nettoyage automatique
|
||||
await _performSelectiveCleanup(manual: false);
|
||||
|
||||
// Reset du cache des repositories après nettoyage
|
||||
_resetAllRepositoriesCache();
|
||||
} else if (lastVersion.isEmpty) {
|
||||
// Première installation
|
||||
debugPrint('🎉 Première installation détectée');
|
||||
await prefs.setString('app_version', _appVersion);
|
||||
if (Hive.isBoxOpen(AppKeys.settingsBoxName)) {
|
||||
final settingsBox = Hive.box(AppKeys.settingsBoxName);
|
||||
await settingsBox.put('app_version', _appVersion);
|
||||
debugPrint('💾 Version initiale sauvegardée dans Hive: $_appVersion');
|
||||
}
|
||||
} else {
|
||||
debugPrint('✅ Même version - pas de nettoyage nécessaire');
|
||||
}
|
||||
@@ -325,9 +554,6 @@ class _SplashPageState extends State<SplashPage> with SingleTickerProviderStateM
|
||||
try {
|
||||
debugPrint('🚀 Début de l\'initialisation complète de l\'application...');
|
||||
|
||||
// Étape 0: Vérifier et nettoyer si nouvelle version (Web uniquement)
|
||||
await _checkVersionAndCleanIfNeeded();
|
||||
|
||||
// Étape 1: Vérification des permissions GPS (obligatoire) - 0 à 10%
|
||||
if (!kIsWeb) {
|
||||
if (mounted) {
|
||||
@@ -402,7 +628,20 @@ class _SplashPageState extends State<SplashPage> with SingleTickerProviderStateM
|
||||
|
||||
// Étape 3: Ouverture des Box - 60 à 80%
|
||||
await HiveService.instance.ensureBoxesAreOpen();
|
||||
|
||||
|
||||
// NOUVEAU : Vérifier et nettoyer si nouvelle version (Web uniquement)
|
||||
// Maintenant que les boxes sont ouvertes, on peut vérifier la version dans Hive
|
||||
await _checkVersionAndCleanIfNeeded();
|
||||
|
||||
// NOUVEAU : Détecter et gérer le F5 (refresh de page web avec session existante)
|
||||
final sessionRestored = await _handleSessionRefreshIfNeeded();
|
||||
if (sessionRestored) {
|
||||
// Session restaurée avec succès, on arrête ici
|
||||
// L'utilisateur a été redirigé vers son interface
|
||||
debugPrint('✅ Session restaurée via F5 - fin de l\'initialisation');
|
||||
return;
|
||||
}
|
||||
|
||||
// Gérer la box pending_requests séparément pour préserver les données
|
||||
try {
|
||||
debugPrint('📦 Gestion de la box pending_requests...');
|
||||
@@ -907,62 +1146,66 @@ class _SplashPageState extends State<SplashPage> with SingleTickerProviderStateM
|
||||
|
||||
const SizedBox(height: 8),
|
||||
|
||||
// Bouton de nettoyage du cache (en noir)
|
||||
AnimatedOpacity(
|
||||
opacity: _showButtons ? 1.0 : 0.0,
|
||||
duration: const Duration(milliseconds: 500),
|
||||
child: TextButton.icon(
|
||||
onPressed: _isCleaningCache ? null : () async {
|
||||
// Confirmation avant nettoyage
|
||||
final confirm = await showDialog<bool>(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: const Text('Nettoyer le cache ?'),
|
||||
content: const Text(
|
||||
'Cette action va :\n'
|
||||
'• Supprimer toutes les données locales\n'
|
||||
'• Préserver les requêtes en attente\n'
|
||||
'• Forcer le rechargement de l\'application\n\n'
|
||||
'Continuer ?'
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(false),
|
||||
child: const Text('Annuler'),
|
||||
// Bouton de nettoyage du cache (Web uniquement)
|
||||
if (kIsWeb)
|
||||
AnimatedOpacity(
|
||||
opacity: _showButtons ? 1.0 : 0.0,
|
||||
duration: const Duration(milliseconds: 500),
|
||||
child: TextButton.icon(
|
||||
onPressed: _isCleaningCache ? null : () async {
|
||||
// Confirmation avant nettoyage
|
||||
final confirm = await showDialog<bool>(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: const Text('Nettoyer le cache ?'),
|
||||
content: const Text(
|
||||
'Cette action va :\n'
|
||||
'• Supprimer toutes les données locales\n'
|
||||
'• Préserver les requêtes en attente\n'
|
||||
'• Forcer le rechargement de l\'application\n\n'
|
||||
'Continuer ?'
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: () => Navigator.of(context).pop(true),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.orange,
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(false),
|
||||
child: const Text('Annuler'),
|
||||
),
|
||||
child: const Text('Nettoyer'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
ElevatedButton(
|
||||
onPressed: () => Navigator.of(context).pop(true),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.orange,
|
||||
),
|
||||
child: const Text('Nettoyer'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
if (confirm == true) {
|
||||
debugPrint('👤 Utilisateur a demandé un nettoyage manuel');
|
||||
await _performSelectiveCleanup(manual: true);
|
||||
|
||||
// Après le nettoyage, relancer l'initialisation
|
||||
_startInitialization();
|
||||
}
|
||||
},
|
||||
icon: Icon(
|
||||
Icons.cleaning_services,
|
||||
size: 18,
|
||||
color: _isCleaningCache ? Colors.grey : Colors.black87,
|
||||
),
|
||||
label: Text(
|
||||
_isCleaningCache ? 'Nettoyage...' : 'Nettoyer le cache',
|
||||
style: TextStyle(
|
||||
if (confirm == true) {
|
||||
debugPrint('👤 Utilisateur a demandé un nettoyage manuel');
|
||||
await _performSelectiveCleanup(manual: true);
|
||||
|
||||
// Reset du cache des repositories après nettoyage
|
||||
_resetAllRepositoriesCache();
|
||||
|
||||
// Après le nettoyage, relancer l'initialisation
|
||||
_startInitialization();
|
||||
}
|
||||
},
|
||||
icon: Icon(
|
||||
Icons.cleaning_services,
|
||||
size: 18,
|
||||
color: _isCleaningCache ? Colors.grey : Colors.black87,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
label: Text(
|
||||
_isCleaningCache ? 'Nettoyage...' : 'Nettoyer le cache',
|
||||
style: TextStyle(
|
||||
color: _isCleaningCache ? Colors.grey : Colors.black87,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
const Spacer(flex: 1),
|
||||
|
||||
Reference in New Issue
Block a user