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