refactor: Amélioration des performances et migration des Cloud Functions

Cette mise à jour majeure vise à améliorer significativement les performances de l'application, en particulier au démarrage, et à standardiser l'infrastructure backend. Les principaux changements incluent la migration de toutes les Cloud Functions vers une région européenne (`europe-west9`), l'optimisation du chargement des données, et l'introduction d'un moniteur de performance pour le débogage.

**Changements Backend (Cloud Functions) :**

-   **Migration de la Région :**
    -   Toutes les Cloud Functions ont été déplacées de `us-central1` à `europe-west9` (Paris) pour réduire la latence pour les utilisateurs européens. Cela concerne les appels depuis le frontend (ex: `api_config.dart`, `email_service.dart`) et les définitions des fonctions elles-mêmes (`index.js`, etc.).
-   **Standardisation des Fonctions :**
    -   La plupart des fonctions `onCall` (v1) ont été migrées vers le format `onRequest` (v2) avec une gestion d'authentification et de CORS unifiée, améliorant la robustesse et la cohérence.
    -   Les triggers Firestore (`onDocumentCreated`, `onDocumentUpdated`) et les tâches planifiées (`onSchedule`) ont été mis à jour pour spécifier explicitement la région `europe-west9`.
-   **Mise à jour des Index Firestore :**
    -   Les index `firestore.indexes.json` ont été mis à jour pour supporter les nouvelles requêtes de l'application et optimiser les performances de filtrage.

**Améliorations des Performances Frontend :**

-   **Chargement Asynchrone et Mis en Cache :**
    -   Le chargement des données utilisateur (`LocalUserProvider`) et des événements (`EventProvider`) a été optimisé pour utiliser un cache local à court terme (5 minutes pour l'utilisateur, 30 secondes pour les événements).
    -   Les données ne sont rechargées que si le cache a expiré ou si un rechargement est forcé, évitant des appels réseau redondants et accélérant la navigation.
-   **Démarrage de l'Application Optimisé :**
    -   Le processus de connexion automatique (`main.dart`) a été revu. L'application navigue désormais immédiatement vers la page demandée sans attendre la fin du chargement des données utilisateur, qui s'effectue en arrière-plan.
    -   Un écran de chargement plus esthétique avec le logo de l'entreprise a été ajouté, remplaçant l'indicateur de chargement simple.
-   **Chargement de la Page Calendrier :**
    -   Le chargement et la sélection de l'événement par défaut sur la page `CalendarPage` sont maintenant entièrement asynchrones, rendant l'affichage de la page quasi instantané.

**Nouveaux Outils et Améliorations UX :**

-   **Moniteur de Performance :**
    -   Ajout d'un nouvel outil `PerformanceMonitor` (`lib/utils/performance_monitor.dart`) pour mesurer précisément le temps d'exécution des opérations critiques (appels API, parsing, etc.) en mode débogage. Il aide à identifier les goulots d'étranglement.
-   **Amélioration du Formulaire de Connexion :**
    -   Les champs "Email" et "Mot de passe" sur la page de connexion (`LoginPage`) supportent désormais l'autocomplétion du navigateur (`AutofillGroup`).
    -   Appuyer sur "Entrée" dans l'un des champs déclenche désormais la connexion, améliorant l'ergonomie.

**Mise à jour de la version :**

-   La version de l'application a été incrémentée à `1.0.9`.
This commit is contained in:
ElPoyo
2026-02-09 10:14:52 +01:00
parent a7e5f91a21
commit 8cd4854924
24 changed files with 545 additions and 515 deletions

View File

@@ -1,5 +1,6 @@
import 'package:em2rp/providers/local_user_provider.dart';
import 'package:em2rp/providers/event_provider.dart';
import 'package:em2rp/utils/performance_monitor.dart';
import 'package:flutter/material.dart';
import 'package:em2rp/views/widgets/nav/custom_app_bar.dart';
import 'package:em2rp/views/widgets/nav/main_drawer.dart';
@@ -35,52 +36,75 @@ class _CalendarPageState extends State<CalendarPage> {
void initState() {
super.initState();
initializeDateFormatting('fr_FR', null);
Future.microtask(() => _loadEvents());
// Sélection automatique de l'événement le plus proche de maintenant
WidgetsBinding.instance.addPostFrameCallback((_) {
final eventProvider = Provider.of<EventProvider>(context, listen: false);
final events = eventProvider.events;
if (events.isNotEmpty) {
final now = DateTime.now();
// Pour mobile : sélectionner le premier événement du jour ou le prochain événement à venir
final todayEvents = events
.where((e) =>
e.startDateTime.year == now.year &&
e.startDateTime.month == now.month &&
e.startDateTime.day == now.day)
.toList()
..sort((a, b) => a.startDateTime.compareTo(b.startDateTime));
EventModel? selected;
DateTime? selectedDay;
if (todayEvents.isNotEmpty) {
selected = todayEvents[0];
selectedDay = DateTime(now.year, now.month, now.day);
} else {
// Chercher le prochain événement à venir
final futureEvents = events
.where((e) => e.startDateTime.isAfter(now))
.toList()
..sort((a, b) => a.startDateTime.compareTo(b.startDateTime));
if (futureEvents.isNotEmpty) {
selected = futureEvents[0];
selectedDay = DateTime(selected.startDateTime.year,
selected.startDateTime.month, selected.startDateTime.day);
} else {
// Aucun événement à venir, prendre le plus proche dans le passé
events.sort((a, b) => a.startDateTime.compareTo(b.startDateTime));
selected = events.last;
selectedDay = DateTime(selected.startDateTime.year,
selected.startDateTime.month, selected.startDateTime.day);
}
}
setState(() {
_selectedDay = selectedDay;
_focusedDay = selectedDay!;
_selectedEventIndex = 0;
_selectedEvent = selected;
});
// Charger les événements de manière asynchrone sans bloquer l'UI
_loadEventsAsync();
}
/// Charge les événements de manière asynchrone et sélectionne l'événement approprié
Future<void> _loadEventsAsync() async {
PerformanceMonitor.start('CalendarPage.loadEventsAsync');
await _loadEvents();
// Sélectionner l'événement approprié après le chargement
if (mounted) {
PerformanceMonitor.start('CalendarPage.selectDefaultEvent');
_selectDefaultEvent();
PerformanceMonitor.end('CalendarPage.selectDefaultEvent');
}
PerformanceMonitor.end('CalendarPage.loadEventsAsync');
}
/// Sélectionne automatiquement l'événement le plus proche de maintenant
void _selectDefaultEvent() {
final eventProvider = Provider.of<EventProvider>(context, listen: false);
final events = eventProvider.events;
if (events.isEmpty) return;
final now = DateTime.now();
// Trouver les événements d'aujourd'hui
final todayEvents = events.where((e) {
final start = e.startDateTime;
return start.year == now.year &&
start.month == now.month &&
start.day == now.day;
}).toList()..sort((a, b) => a.startDateTime.compareTo(b.startDateTime));
EventModel? selected;
DateTime? selectedDay;
if (todayEvents.isNotEmpty) {
selected = todayEvents[0];
selectedDay = DateTime(now.year, now.month, now.day);
} else {
// Chercher le prochain événement à venir
final futureEvents = events
.where((e) => e.startDateTime.isAfter(now))
.toList()..sort((a, b) => a.startDateTime.compareTo(b.startDateTime));
if (futureEvents.isNotEmpty) {
selected = futureEvents[0];
final start = selected.startDateTime;
selectedDay = DateTime(start.year, start.month, start.day);
} else {
// Aucun événement à venir, prendre le plus récent
final sortedEvents = events.toList()
..sort((a, b) => b.startDateTime.compareTo(a.startDateTime));
selected = sortedEvents.first;
final start = selected.startDateTime;
selectedDay = DateTime(start.year, start.month, start.day);
}
});
}
if (mounted) {
setState(() {
_selectedDay = selectedDay;
_focusedDay = selectedDay!;
_selectedEventIndex = 0;
_selectedEvent = selected;
});
}
}
Future<void> _loadEvents() async {