diff --git a/em2rp/.firebase/hosting.YnVpbGRcd2Vi.cache b/em2rp/.firebase/hosting.YnVpbGRcd2Vi.cache
index d9608c8..fac4c7e 100644
--- a/em2rp/.firebase/hosting.YnVpbGRcd2Vi.cache
+++ b/em2rp/.firebase/hosting.YnVpbGRcd2Vi.cache
@@ -32,16 +32,16 @@ assets/assets/images/tshirt-incrust.webp,1737393735487,af7cb34adfca19c0b41c8eb63
assets/assets/icons/truss.svg,1761734811263,8ddfbbb4f96de5614348eb23fa55f61b2eb1edb064719a8bbd791c35883ec4cc
assets/assets/icons/tape.svg,1761734809221,631183f0ff972aa4dc3f9f51dc7abd41a607df749d1f9a44fa7e77202d95ccde
assets/assets/icons/flight-case.svg,1761734822495,0cef47fdf5d7efdd110763c32f792ef9735df35c4f42ae7d02d5fbda40e6148d
-version.json,1768397512647,ef03fbf6fee5e0631f357db9c08c049058ba45d629b1eaed3a69bec4200d8189
-index.html,1768397340273,4375d2aa848ac5af68053b56474f2d82bc054264d0fa528d5f535dab7b678836
-flutter_service_worker.js,1768397521163,40d38b159dc58d99e909ac7e9fa25df38b12dfe56ed3c8743905a8ffde7e2718
-flutter_bootstrap.js,1768397340272,d3a780bc468e1a267eb4b890677be070f210b07e33f3cae086abbd5bb6c962e5
-assets/FontManifest.json,1768397515663,e38b95988f5d060cf9b7ce97cb5ac9236d6f4cc04a11d69567df97b2b4cbc5e5
-assets/AssetManifest.json,1768397515664,1e1501af5844823ef215cf650f4cef4002c0389d88770225ac07576d57dc1067
-assets/AssetManifest.bin.json,1768397515663,f446eb3de964f3a6f9e76fcc98d79a81b0429e076c9c7bf30cf8edd0263a0b0a
-assets/AssetManifest.bin,1768397515663,72bbccb69d9a02d3885df0c5e58ebfed29e25a4919e10bf195b59542f4709ca3
-assets/packages/cupertino_icons/assets/CupertinoIcons.ttf,1768397520319,d41473de1f7708a0702d7f19327693486512db442f6ab0cf7774e6d6576f9fcb
-assets/shaders/ink_sparkle.frag,1768397516013,591c7517d5cb43eb91ea451e0d3f9f585cbf8298cf6c46a9144b77cb0775a406
-assets/fonts/MaterialIcons-Regular.otf,1768397520329,5539d621f88691414a2b6fbfedf34c3e12dc2c75c1238759301276d99a38a6ef
-assets/NOTICES,1768397515664,22a160781d4d3cf3f76d93e69d71c5368d8976bbba609788e8f17d302080d47e
-main.dart.js,1768397509656,352b1d4014b31defb1a0b24d098b2dcd943fd255912764e1af1f38459b02f444
+version.json,1768522475061,bddd1ea3e020d4aacf09b657c819bf5f36dc702aec076e506cb492d21cd8f52a
+index.html,1768522480171,4e8c00552c71ef134bead8bc03706952e7a415d70fca602a3839dc02a3f7ae10
+flutter_service_worker.js,1768522568665,eba5c918ba4c29ed004c54e9bd663630cb2c583c3c71f06a8f2020b5752e7b01
+assets/FontManifest.json,1768522564284,e38b95988f5d060cf9b7ce97cb5ac9236d6f4cc04a11d69567df97b2b4cbc5e5
+flutter_bootstrap.js,1768522480160,97c0e7a0a2dc5def7e6753ab86c08de3efaae84ac9f0203e29554f4274511bb2
+assets/AssetManifest.json,1768522564284,1e1501af5844823ef215cf650f4cef4002c0389d88770225ac07576d57dc1067
+assets/AssetManifest.bin.json,1768522564284,f446eb3de964f3a6f9e76fcc98d79a81b0429e076c9c7bf30cf8edd0263a0b0a
+assets/AssetManifest.bin,1768522564284,72bbccb69d9a02d3885df0c5e58ebfed29e25a4919e10bf195b59542f4709ca3
+assets/packages/cupertino_icons/assets/CupertinoIcons.ttf,1768522567845,d41473de1f7708a0702d7f19327693486512db442f6ab0cf7774e6d6576f9fcb
+assets/shaders/ink_sparkle.frag,1768522564576,591c7517d5cb43eb91ea451e0d3f9f585cbf8298cf6c46a9144b77cb0775a406
+assets/fonts/MaterialIcons-Regular.otf,1768522567854,33efc485968dd28630ace587c22d6df359c195821b1114aaa85383e4d5394eac
+assets/NOTICES,1768522564286,fc20c3c3c998057eb7e58ad2e009c7268bf748bfde685e95130431f4c54bd51c
+main.dart.js,1768522561626,c106504190e4bf61c5a50eb6a4826ea8cddccd2310ffa359df51f047bf891acd
diff --git a/em2rp/CHANGELOG.md b/em2rp/CHANGELOG.md
new file mode 100644
index 0000000..04548db
--- /dev/null
+++ b/em2rp/CHANGELOG.md
@@ -0,0 +1,37 @@
+# Changelog - EM2RP
+
+Toutes les modifications notables de ce projet seront documentées dans ce fichier.
+
+## 🚀 Nouveautés de la mise à jour
+
+Cette version apporte des outils majeurs pour faciliter la gestion de votre parc et de vos événements :
+
+* **Scanner QR Code :** Retrouvez instantanément la fiche d'un équipement ou d'un conteneur en scannant son code directement depuis l'application. La génération des codes a également été rendue plus fluide.
+* **Centre de Notifications & Alertes :** Ne ratez plus rien ! Un nouveau système d'alertes (dans l'app et par email) vous prévient des maintenances, équipements manquants ou conflits. Vous pouvez configurer vos préférences d'envoi.
+* **Checklist de Préparation 2.0 :** L'interface de préparation a été repensée. Elle regroupe désormais les objets par conteneurs et permet de suivre visuellement les équipements manquants ou perdus à chaque étape (chargement, retour, etc.).
+* **Sélecteur de Matériel Optimisé :** La recherche de matériel pour un événement est beaucoup plus rapide. Vous pouvez désormais masquer automatiquement les équipements déjà utilisés sur d'autres événements aux mêmes dates.
+* **Gestion & Administration :** Affichage clair des prix HT/TTC partout dans l'application. Pour les administrateurs, l'ajout d'utilisateurs et la réinitialisation de mot de passe sont simplifiés via l'envoi d'emails automatiques.
+### Ajouté
+- Système de vérification automatique des mises à jour
+- Dialog de notification de mise à jour avec notes de version
+- Rechargement automatique du cache après mise à jour
+
+## [1.0.0] - 2026-01-16
+
+### Ajouté
+- Scanner QR Code pour équipements et conteneurs
+- Génération de QR codes pour conteneurs
+- Indicateur de chargement pour génération QR
+- Sections repliables dans le dialog de sélection d'équipement
+- Filtrage des équipements en conflit
+- Filtrage des boîtes par catégorie
+
+### Amélioré
+- Performance du dialog de sélection d'équipement
+- Gestion du cache des équipements
+- Interface utilisateur générale
+
+### Corrigé
+- Problème de cache avec les équipements non affichés
+- Bouton de validation désactivé dans certains cas
+
diff --git a/em2rp/SYSTEME_MISE_A_JOUR.md b/em2rp/SYSTEME_MISE_A_JOUR.md
new file mode 100644
index 0000000..db72b84
--- /dev/null
+++ b/em2rp/SYSTEME_MISE_A_JOUR.md
@@ -0,0 +1,337 @@
+# Système de Gestion des Mises à Jour - EM2RP
+
+## 📋 Vue d'ensemble
+
+Ce système permet de gérer automatiquement les mises à jour de l'application web Flutter, en notifiant les utilisateurs et en forçant le rechargement du cache si nécessaire.
+
+---
+
+## 🔧 Architecture
+
+### Fichiers impliqués
+
+#### Configuration
+- **`lib/config/app_version.dart`** : Fichier source de vérité pour la version
+- **`web/version.json`** : Fichier déployé avec l'app pour vérification côté serveur
+
+#### Services
+- **`lib/services/update_service.dart`** : Service de vérification des mises à jour
+- **`lib/views/widgets/common/update_dialog.dart`** : Widget d'affichage du dialog de mise à jour
+
+#### Scripts
+- **`scripts/increment_version.js`** : Incrémente automatiquement la version
+- **`scripts/update_version_json.js`** : Génère version.json depuis app_version.dart
+- **`deploy.bat`** : Script de déploiement complet
+
+#### Documentation
+- **`CHANGELOG.md`** : Notes de version (utilisées dans le dialog)
+
+---
+
+## 🚀 Workflow de déploiement
+
+### 1. Développement normal
+Travaillez normalement sur votre code en mode développement.
+
+### 2. Déploiement d'une nouvelle version
+```bash
+deploy.bat
+```
+
+Ce script exécute automatiquement :
+1. ✅ Bascule en mode PRODUCTION
+2. ✅ **Incrémente la version** (0.3.8 → 0.3.9)
+3. ✅ **Incrémente le buildNumber** (1 → 2)
+4. ✅ **Génère version.json** depuis app_version.dart
+5. ✅ Build Flutter Web
+6. ✅ Déploie sur Firebase Hosting
+7. ✅ Retour en mode DÉVELOPPEMENT
+
+### 3. Mise à jour côté utilisateur
+Au prochain chargement de l'app (ou après 2 secondes) :
+- L'app vérifie `https://em2rp.web.app/version.json`
+- Compare avec la version locale dans `app_version.dart`
+- Si `buildNumber serveur > buildNumber local` → Affiche le dialog
+
+---
+
+## 📝 Format de version
+
+### app_version.dart
+```dart
+class AppVersion {
+ static const String version = '0.3.8'; // Version sémantique
+ static const int buildNumber = 1; // Numéro de build (incrémenté automatiquement)
+
+ static String get fullVersion => 'v$version';
+ static String get fullVersionWithBuild => 'v$version+$buildNumber';
+}
+```
+
+### version.json (déployé)
+```json
+{
+ "version": "0.3.8",
+ "buildNumber": 1,
+ "updateUrl": "https://em2rp.web.app",
+ "forceUpdate": false,
+ "releaseNotes": "• Scanner QR Code\n• Génération QR conteneurs\n• Performance améliorée"
+}
+```
+
+---
+
+## 🔄 Comparaison des versions
+
+Le système compare uniquement le **buildNumber** :
+- `buildNumber serveur > buildNumber local` → Mise à jour disponible
+- Ignore les versions identiques même si la version sémantique change
+
+**Exemple** :
+- Local : `0.3.8+1`
+- Serveur : `0.3.9+2`
+- Résultat : Mise à jour proposée (2 > 1) ✅
+
+---
+
+## 🎨 Expérience utilisateur
+
+### Mise à jour normale (forceUpdate: false)
+```
+┌────────────────────────────────────┐
+│ 🔄 Mise à jour disponible │
+├────────────────────────────────────┤
+│ Version actuelle : 0.3.8 (1) │
+│ Nouvelle version : 0.3.9 (2) │
+│ │
+│ Nouveautés : │
+│ • Scanner QR Code │
+│ • Performance améliorée │
+│ │
+│ [Plus tard] [Mettre à jour] 🔄 │
+└────────────────────────────────────┘
+```
+
+### Mise à jour forcée (forceUpdate: true)
+```
+┌────────────────────────────────────┐
+│ ⚠️ Mise à jour requise │
+├────────────────────────────────────┤
+│ Version actuelle : 0.3.8 (1) │
+│ Nouvelle version : 0.3.9 (2) │
+│ │
+│ ⚠️ Cette mise à jour est │
+│ obligatoire pour continuer │
+│ │
+│ [Mettre à jour] 🔄 │
+└────────────────────────────────────┘
+```
+
+---
+
+## 🛠️ Utilisation avancée
+
+### Forcer une mise à jour critique
+Si vous déployez un correctif critique :
+
+1. Modifiez `web/version.json` **après le déploiement** :
+```json
+{
+ "version": "0.3.9",
+ "buildNumber": 2,
+ "forceUpdate": true, // ← Changer à true
+ "releaseNotes": "🔴 Correctif de sécurité important"
+}
+```
+
+2. Les utilisateurs ne pourront plus fermer le dialog jusqu'à la mise à jour
+
+### Personnaliser les notes de version
+Éditez `CHANGELOG.md` avant le déploiement :
+
+```markdown
+## [0.3.9] - 2026-01-16
+
+### Ajouté
+- Scanner QR Code pour équipements
+- Génération QR pour conteneurs
+
+### Amélioré
+- Performance du dialog de sélection
+- Gestion du cache
+
+### Corrigé
+- Bug de cache des équipements
+```
+
+Les 5 premières lignes de la section seront utilisées dans le dialog.
+
+---
+
+## 🧪 Tests
+
+### Test 1 : Vérification de version locale
+```dart
+// Dans n'importe quel fichier
+import 'package:em2rp/config/app_version.dart';
+
+print('Version: ${AppVersion.version}');
+print('Build: ${AppVersion.buildNumber}');
+print('Full: ${AppVersion.fullVersionWithBuild}');
+```
+
+### Test 2 : Forcer l'affichage du dialog
+Modifiez temporairement `web/version.json` :
+```json
+{
+ "buildNumber": 999 // Très grand nombre
+}
+```
+
+Rechargez l'app → Le dialog s'affiche immédiatement
+
+### Test 3 : Tester le rechargement
+1. Cliquez sur "Mettre à jour"
+2. Vérifiez que la page se recharge
+3. Vérifiez que le cache est vidé (nouvelles ressources chargées)
+
+---
+
+## 📊 Logs de debug
+
+En mode debug, des logs sont affichés dans la console :
+
+```
+[UpdateService] Current version: 0.3.8+1
+[UpdateService] Server version: 0.3.9+2
+```
+
+Si pas de mise à jour disponible, rien ne s'affiche.
+
+---
+
+## 🔐 Sécurité
+
+### Headers HTTP pour forcer le non-cache
+Le fichier `web/index.html` contient :
+```html
+
+
+
+```
+
+### Cache-busting sur version.json
+Chaque requête ajoute un timestamp :
+```dart
+final timestamp = DateTime.now().millisecondsSinceEpoch;
+Uri.parse('$versionUrl?t=$timestamp')
+```
+
+Garantit que la version la plus récente est toujours récupérée.
+
+---
+
+## 🚨 Résolution de problèmes
+
+### Problème : Le dialog ne s'affiche pas
+**Causes possibles :**
+1. Le `buildNumber` serveur n'est pas supérieur au local
+2. Erreur réseau (timeout 10s)
+3. Le fichier `version.json` n'existe pas sur le serveur
+
+**Solution :**
+```bash
+# Vérifier la version déployée
+curl https://em2rp.web.app/version.json
+
+# Forcer un nouveau déploiement
+deploy.bat
+```
+
+### Problème : Le cache ne se vide pas
+**Causes possibles :**
+1. Service Worker actif (ancienne version)
+2. Cache navigateur très persistant
+
+**Solution :**
+```javascript
+// Dans les DevTools du navigateur
+navigator.serviceWorker.getRegistrations().then(registrations => {
+ registrations.forEach(r => r.unregister());
+});
+
+// Puis CTRL+SHIFT+R (rechargement forcé)
+```
+
+### Problème : Le script increment_version.js échoue
+**Solution :**
+```bash
+# Vérifier la syntaxe du fichier app_version.dart
+# Doit contenir exactement :
+static const String version = '0.3.8';
+static const int buildNumber = 1;
+```
+
+---
+
+## 📈 Évolution future
+
+### Fonctionnalités possibles
+- [ ] Afficher un changelog complet dans le dialog
+- [ ] Permettre de sauter une version (skip this version)
+- [ ] Notifications push pour les mises à jour critiques
+- [ ] Analytics sur le taux d'adoption des mises à jour
+- [ ] Support des mises à jour en arrière-plan
+
+### Améliorations techniques
+- [ ] Utiliser un CDN pour version.json
+- [ ] Implémenter un rollback automatique si erreur
+- [ ] Ajouter une vérification de santé post-déploiement
+
+---
+
+## 🎯 Commandes rapides
+
+```bash
+# Déployer une nouvelle version
+deploy.bat
+
+# Incrémenter manuellement la version
+node scripts\increment_version.js
+
+# Générer version.json manuellement
+node scripts\update_version_json.js
+
+# Vérifier la version actuelle
+type lib\config\app_version.dart
+
+# Vérifier la version déployée
+curl https://em2rp.web.app/version.json
+```
+
+---
+
+## ✅ Checklist de déploiement
+
+Avant chaque déploiement :
+
+- [ ] Tester l'application en local
+- [ ] Mettre à jour `CHANGELOG.md` avec les nouveautés
+- [ ] Vérifier que tous les tests passent
+- [ ] Exécuter `deploy.bat`
+- [ ] Vérifier le déploiement sur https://em2rp.web.app
+- [ ] Tester la mise à jour sur un navigateur propre
+- [ ] Informer l'équipe de la nouvelle version
+
+---
+
+## 📞 Support
+
+En cas de problème avec le système de mise à jour, vérifier :
+1. Les logs dans la console du navigateur
+2. Le fichier `version.json` déployé
+3. Le fichier `app_version.dart` local
+4. La connexion réseau de l'utilisateur
+
+**Le système est conçu pour échouer silencieusement** : Si une erreur se produit, l'utilisateur peut continuer à utiliser l'app normalement sans être bloqué.
+
diff --git a/em2rp/deploy.bat b/em2rp/deploy.bat
index 832ca19..b3b710c 100644
--- a/em2rp/deploy.bat
+++ b/em2rp/deploy.bat
@@ -25,6 +25,16 @@ if %ERRORLEVEL% NEQ 0 (
)
echo.
+echo [1.5/4] Mise à jour du fichier version.json...
+node scripts\update_version_json.js
+if %ERRORLEVEL% NEQ 0 (
+ echo Erreur lors de la mise à jour de version.json
+ node scripts\toggle_env.js dev
+ pause
+ exit /b 1
+)
+echo.
+
echo [2/4] Build Flutter Web...
call flutter build web --release
if %ERRORLEVEL% NEQ 0 (
diff --git a/em2rp/lib/config/app_version.dart b/em2rp/lib/config/app_version.dart
index 2bc8a84..c9fe19c 100644
--- a/em2rp/lib/config/app_version.dart
+++ b/em2rp/lib/config/app_version.dart
@@ -1,10 +1,11 @@
/// Configuration de la version de l'application
class AppVersion {
- static const String version = '0.3.8';
+ static const String version = '1.0.3';
/// Retourne la version complète de l'application
static String get fullVersion => 'v$version';
+
/// Retourne la version avec un préfixe personnalisé
static String getVersionWithPrefix(String prefix) => '$prefix $version';
}
diff --git a/em2rp/lib/main.dart b/em2rp/lib/main.dart
index 9e1c4e7..98f0ebd 100644
--- a/em2rp/lib/main.dart
+++ b/em2rp/lib/main.dart
@@ -29,6 +29,7 @@ import 'views/reset_password_page.dart';
import 'config/env.dart';
import 'config/api_config.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
+import 'views/widgets/common/update_dialog.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
@@ -96,9 +97,10 @@ class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
- return MaterialApp(
- title: 'EM2 ERP',
- theme: ThemeData(
+ return UpdateChecker(
+ child: MaterialApp(
+ title: 'EM2 ERP',
+ theme: ThemeData(
primarySwatch: Colors.red,
primaryColor: AppColors.noir,
colorScheme:
@@ -181,6 +183,7 @@ class MyApp extends StatelessWidget {
);
},
},
+ ),
);
}
}
diff --git a/em2rp/lib/services/update_service.dart b/em2rp/lib/services/update_service.dart
new file mode 100644
index 0000000..55d72f7
--- /dev/null
+++ b/em2rp/lib/services/update_service.dart
@@ -0,0 +1,117 @@
+import 'package:flutter/foundation.dart';
+import 'package:http/http.dart' as http;
+import 'dart:convert';
+import 'package:em2rp/config/app_version.dart';
+import 'package:url_launcher/url_launcher.dart';
+
+/// Service pour gérer les mises à jour de l'application
+class UpdateService {
+ // URL de votre version.json déployé sur Firebase Hosting
+ static const String versionUrl = 'https://app.em2events.fr/version.json';
+
+ /// Vérifie si une mise à jour est disponible
+ static Future checkForUpdate() async {
+ try {
+ // Récupérer la version actuelle depuis AppVersion
+ final currentVersion = AppVersion.version;
+
+ if (kDebugMode) {
+ print('[UpdateService] Current version: $currentVersion');
+ }
+
+ // Récupérer la version depuis le serveur (avec cache-busting)
+ final timestamp = DateTime.now().millisecondsSinceEpoch;
+ final response = await http.get(
+ Uri.parse('$versionUrl?t=$timestamp'),
+ headers: {
+ 'Cache-Control': 'no-cache, no-store, must-revalidate',
+ 'Pragma': 'no-cache',
+ 'Expires': '0',
+ },
+ ).timeout(const Duration(seconds: 10));
+
+ if (response.statusCode == 200) {
+ final data = json.decode(response.body);
+ final serverVersion = data['version'] as String;
+
+ if (kDebugMode) {
+ print('[UpdateService] Server version: $serverVersion');
+ }
+
+ // Comparer les versions
+ if (_isNewerVersion(serverVersion, currentVersion)) {
+ return UpdateInfo(
+ currentVersion: currentVersion,
+ newVersion: serverVersion,
+ updateUrl: data['updateUrl'] as String?,
+ releaseNotes: data['releaseNotes'] as String?,
+ forceUpdate: data['forceUpdate'] as bool? ?? false,
+ );
+ }
+ }
+
+ return null;
+ } catch (e) {
+ if (kDebugMode) {
+ print('[UpdateService] Error checking for update: $e');
+ }
+ return null;
+ }
+ }
+
+ /// Compare deux versions sémantiques (x.y.z)
+ /// Retourne true si newVersion > currentVersion
+ static bool _isNewerVersion(String newVersion, String currentVersion) {
+ final newParts = newVersion.split('.').map(int.parse).toList();
+ final currentParts = currentVersion.split('.').map(int.parse).toList();
+
+ // Comparer major
+ if (newParts[0] > currentParts[0]) return true;
+ if (newParts[0] < currentParts[0]) return false;
+
+ // Comparer minor
+ if (newParts[1] > currentParts[1]) return true;
+ if (newParts[1] < currentParts[1]) return false;
+
+ // Comparer patch
+ return newParts[2] > currentParts[2];
+ }
+
+ /// Force le rechargement de l'application (vide le cache)
+ static Future reloadApp() async {
+ if (kIsWeb) {
+ // Pour le web, recharger la page en utilisant JavaScript
+ final url = Uri.base;
+ await launchUrl(url, webOnlyWindowName: '_self');
+ }
+ }
+
+ /// Vérification automatique au démarrage
+ static Future checkOnStartup() async {
+ // Attendre un peu avant de vérifier (pour ne pas ralentir le démarrage)
+ await Future.delayed(const Duration(seconds: 2));
+ return await checkForUpdate();
+ }
+}
+
+/// Informations sur une mise à jour disponible
+class UpdateInfo {
+ final String currentVersion;
+ final String newVersion;
+ final String? updateUrl;
+ final String? releaseNotes;
+ final bool forceUpdate;
+
+ UpdateInfo({
+ required this.currentVersion,
+ required this.newVersion,
+ this.updateUrl,
+ this.releaseNotes,
+ this.forceUpdate = false,
+ });
+
+ String get versionDifference {
+ return 'Nouvelle version disponible';
+ }
+}
+
diff --git a/em2rp/lib/views/widgets/common/update_dialog.dart b/em2rp/lib/views/widgets/common/update_dialog.dart
new file mode 100644
index 0000000..213c94c
--- /dev/null
+++ b/em2rp/lib/views/widgets/common/update_dialog.dart
@@ -0,0 +1,223 @@
+import 'package:flutter/material.dart';
+import 'package:em2rp/services/update_service.dart';
+import 'package:em2rp/utils/colors.dart';
+
+/// Dialog pour informer l'utilisateur d'une mise à jour disponible
+class UpdateDialog extends StatelessWidget {
+ final UpdateInfo updateInfo;
+
+ const UpdateDialog({
+ super.key,
+ required this.updateInfo,
+ });
+
+ @override
+ Widget build(BuildContext context) {
+ return PopScope(
+ // Empêcher la fermeture si c'est une mise à jour forcée
+ canPop: !updateInfo.forceUpdate,
+ child: AlertDialog(
+ title: Row(
+ children: [
+ Icon(
+ updateInfo.forceUpdate ? Icons.update : Icons.system_update,
+ color: updateInfo.forceUpdate ? Colors.orange : AppColors.rouge,
+ size: 28,
+ ),
+ const SizedBox(width: 12),
+ Expanded(
+ child: Text(
+ updateInfo.forceUpdate
+ ? 'Mise à jour requise'
+ : 'Mise à jour disponible',
+ style: const TextStyle(fontSize: 20),
+ ),
+ ),
+ ],
+ ),
+ content: Column(
+ mainAxisSize: MainAxisSize.min,
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ // Versions
+ Container(
+ padding: const EdgeInsets.all(12),
+ decoration: BoxDecoration(
+ color: Colors.grey[100],
+ borderRadius: BorderRadius.circular(8),
+ ),
+ child: Column(
+ children: [
+ Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ const Text(
+ 'Version actuelle :',
+ style: TextStyle(fontWeight: FontWeight.w500),
+ ),
+ Text(
+ updateInfo.currentVersion,
+ style: const TextStyle(
+ fontFamily: 'monospace',
+ color: Colors.grey,
+ ),
+ ),
+ ],
+ ),
+ const SizedBox(height: 8),
+ Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ const Text(
+ 'Nouvelle version :',
+ style: TextStyle(fontWeight: FontWeight.bold),
+ ),
+ Text(
+ updateInfo.newVersion,
+ style: const TextStyle(
+ fontFamily: 'monospace',
+ fontWeight: FontWeight.bold,
+ color: AppColors.rouge,
+ ),
+ ),
+ ],
+ ),
+ ],
+ ),
+ ),
+
+ const SizedBox(height: 16),
+
+ // Message principal
+ if (updateInfo.forceUpdate) ...[
+ Container(
+ padding: const EdgeInsets.all(12),
+ decoration: BoxDecoration(
+ color: Colors.orange.withValues(alpha: 0.1),
+ borderRadius: BorderRadius.circular(8),
+ border: Border.all(color: Colors.orange, width: 2),
+ ),
+ child: const Row(
+ children: [
+ Icon(Icons.warning, color: Colors.orange),
+ SizedBox(width: 12),
+ Expanded(
+ child: Text(
+ 'Cette mise à jour est obligatoire pour continuer à utiliser l\'application.',
+ style: TextStyle(
+ fontWeight: FontWeight.bold,
+ color: Colors.orange,
+ ),
+ ),
+ ),
+ ],
+ ),
+ ),
+ const SizedBox(height: 16),
+ ],
+
+ Text(
+ updateInfo.forceUpdate
+ ? 'L\'application va se recharger pour appliquer la mise à jour.'
+ : 'Une nouvelle version de l\'application est disponible. Voulez-vous mettre à jour maintenant ?',
+ style: const TextStyle(fontSize: 15),
+ ),
+
+ // Notes de version
+ if (updateInfo.releaseNotes != null &&
+ updateInfo.releaseNotes!.isNotEmpty) ...[
+ const SizedBox(height: 16),
+ const Text(
+ 'Nouveautés :',
+ style: TextStyle(
+ fontWeight: FontWeight.bold,
+ fontSize: 15,
+ ),
+ ),
+ const SizedBox(height: 8),
+ Container(
+ padding: const EdgeInsets.all(12),
+ decoration: BoxDecoration(
+ color: Colors.blue.withValues(alpha: 0.05),
+ borderRadius: BorderRadius.circular(8),
+ border: Border.all(color: Colors.blue.withValues(alpha: 0.2)),
+ ),
+ child: Text(
+ updateInfo.releaseNotes!,
+ style: const TextStyle(fontSize: 14),
+ ),
+ ),
+ ],
+ ],
+ ),
+ actions: [
+ if (!updateInfo.forceUpdate)
+ TextButton(
+ onPressed: () => Navigator.of(context).pop(),
+ child: const Text('Plus tard'),
+ ),
+ ElevatedButton.icon(
+ onPressed: () async {
+ Navigator.of(context).pop();
+ // Recharger l'application
+ await UpdateService.reloadApp();
+ },
+ icon: const Icon(Icons.refresh, color: Colors.white),
+ label: Text(
+ updateInfo.forceUpdate ? 'Mettre à jour' : 'Mettre à jour maintenant',
+ style: const TextStyle(color: Colors.white),
+ ),
+ style: ElevatedButton.styleFrom(
+ backgroundColor: updateInfo.forceUpdate ? Colors.orange : AppColors.rouge,
+ padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12),
+ ),
+ ),
+ ],
+ ),
+ );
+ }
+}
+
+/// Widget pour vérifier automatiquement les mises à jour
+class UpdateChecker extends StatefulWidget {
+ final Widget child;
+
+ const UpdateChecker({
+ super.key,
+ required this.child,
+ });
+
+ @override
+ State createState() => _UpdateCheckerState();
+}
+
+class _UpdateCheckerState extends State {
+ @override
+ void initState() {
+ super.initState();
+ _checkForUpdate();
+ }
+
+ Future _checkForUpdate() async {
+ final updateInfo = await UpdateService.checkOnStartup();
+
+ if (updateInfo != null && mounted) {
+ // Attendre que l'interface soit complètement chargée
+ WidgetsBinding.instance.addPostFrameCallback((_) {
+ if (mounted) {
+ showDialog(
+ context: context,
+ barrierDismissible: !updateInfo.forceUpdate,
+ builder: (context) => UpdateDialog(updateInfo: updateInfo),
+ );
+ }
+ });
+ }
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return widget.child;
+ }
+}
+
diff --git a/em2rp/scripts/update_version_json.js b/em2rp/scripts/update_version_json.js
new file mode 100644
index 0000000..519f42f
--- /dev/null
+++ b/em2rp/scripts/update_version_json.js
@@ -0,0 +1,62 @@
+const fs = require('fs');
+const path = require('path');
+
+/**
+ * Script pour mettre à jour version.json avec les informations de app_version.dart
+ * Appelé automatiquement lors du build
+ */
+
+const versionFilePath = path.join(__dirname, '..', 'lib', 'config', 'app_version.dart');
+const versionJsonPath = path.join(__dirname, '..', 'web', 'version.json');
+const buildVersionJsonPath = path.join(__dirname, '..', 'build', 'web', 'version.json');
+
+// Lire le fichier app_version.dart
+const versionContent = fs.readFileSync(versionFilePath, 'utf8');
+
+// Extraire version
+const versionMatch = versionContent.match(/static const String version = '(\d+\.\d+\.\d+)';/);
+if (!versionMatch) {
+ console.error('❌ Impossible de trouver la version dans app_version.dart');
+ process.exit(1);
+}
+
+const version = versionMatch[1];
+
+console.log(`📦 Version trouvée: ${version}`);
+
+// Lire les notes de version si elles existent
+let releaseNotes = 'Mise à jour de l\'application';
+const changelogPath = path.join(__dirname, '..', 'CHANGELOG.md');
+if (fs.existsSync(changelogPath)) {
+ const changelogContent = fs.readFileSync(changelogPath, 'utf8');
+ // Extraire la première section
+ const firstSection = changelogContent.split('\n## ')[1];
+ if (firstSection) {
+ const lines = firstSection.split('\n').slice(1, 6); // Prendre les 5 premières lignes
+ releaseNotes = lines.join('\n').trim();
+ }
+}
+
+// Créer l'objet version
+const versionData = {
+ version,
+ updateUrl: 'https://app.em2events.fr',
+ forceUpdate: true, // Mettre à true si mise à jour critique
+ releaseNotes,
+ timestamp: new Date().toISOString()
+};
+
+// Écrire dans web/version.json
+fs.writeFileSync(versionJsonPath, JSON.stringify(versionData, null, 2));
+console.log(`✅ web/version.json mis à jour`);
+
+// Copier aussi dans build/web/ si le dossier existe
+const buildWebDir = path.join(__dirname, '..', 'build', 'web');
+if (fs.existsSync(buildWebDir)) {
+ fs.writeFileSync(buildVersionJsonPath, JSON.stringify(versionData, null, 2));
+ console.log(`✅ build/web/version.json mis à jour`);
+}
+
+console.log('\n📝 Contenu du fichier version:');
+console.log(JSON.stringify(versionData, null, 2));
+
diff --git a/em2rp/web/index.html b/em2rp/web/index.html
index 0872488..33fe3f2 100644
--- a/em2rp/web/index.html
+++ b/em2rp/web/index.html
@@ -20,6 +20,12 @@
+
+
+
+
+
+
diff --git a/em2rp/web/version.json b/em2rp/web/version.json
new file mode 100644
index 0000000..ec5a5f5
--- /dev/null
+++ b/em2rp/web/version.json
@@ -0,0 +1,7 @@
+{
+ "version": "1.0.3",
+ "updateUrl": "https://app.em2events.fr",
+ "forceUpdate": true,
+ "releaseNotes": "Cette version apporte des outils majeurs pour faciliter la gestion de votre parc et de vos événements :\r\n\r\n* **Scanner QR Code :** Retrouvez instantanément la fiche d'un équipement ou d'un conteneur en scannant son code directement depuis l'application. La génération des codes a également été rendue plus fluide.\r\n* **Centre de Notifications & Alertes :** Ne ratez plus rien ! Un nouveau système d'alertes (dans l'app et par email) vous prévient des maintenances, équipements manquants ou conflits. Vous pouvez configurer vos préférences d'envoi.",
+ "timestamp": "2026-01-16T00:14:35.059Z"
+}
\ No newline at end of file