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`.
150 lines
4.6 KiB
Dart
150 lines
4.6 KiB
Dart
import 'package:cloud_functions/cloud_functions.dart';
|
|
import 'package:em2rp/models/alert_model.dart';
|
|
import 'package:em2rp/utils/debug_log.dart';
|
|
import 'package:firebase_auth/firebase_auth.dart';
|
|
|
|
/// Service d'envoi d'emails via Cloud Functions
|
|
class EmailService {
|
|
final FirebaseFunctions _functions = FirebaseFunctions.instanceFor(region: 'europe-west9');
|
|
|
|
/// Envoie un email d'alerte à un utilisateur
|
|
///
|
|
/// [alert] : L'alerte à envoyer
|
|
/// [userId] : ID de l'utilisateur destinataire
|
|
/// [templateType] : Type de template à utiliser (par défaut: 'alert-individual')
|
|
Future<bool> sendAlertEmail({
|
|
required AlertModel alert,
|
|
required String userId,
|
|
String templateType = 'alert-individual',
|
|
}) async {
|
|
try {
|
|
// Vérifier que l'utilisateur est authentifié
|
|
final currentUser = FirebaseAuth.instance.currentUser;
|
|
if (currentUser == null) {
|
|
DebugLog.error('[EmailService] Utilisateur non authentifié');
|
|
return false;
|
|
}
|
|
|
|
DebugLog.info('[EmailService] Envoi email alerte ${alert.id} à $userId');
|
|
|
|
final result = await _functions.httpsCallable('sendAlertEmail').call({
|
|
'alertId': alert.id,
|
|
'userId': userId,
|
|
'templateType': templateType,
|
|
});
|
|
|
|
final data = result.data as Map<String, dynamic>;
|
|
final success = data['success'] as bool? ?? false;
|
|
final skipped = data['skipped'] as bool? ?? false;
|
|
|
|
if (skipped) {
|
|
final reason = data['reason'] as String? ?? 'unknown';
|
|
DebugLog.info('[EmailService] Email non envoyé: $reason');
|
|
return false;
|
|
}
|
|
|
|
if (success) {
|
|
DebugLog.info('[EmailService] Email envoyé avec succès');
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
} catch (e) {
|
|
DebugLog.error('[EmailService] Erreur envoi email', e);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// Envoie un email d'alerte à plusieurs utilisateurs
|
|
///
|
|
/// [alert] : L'alerte à envoyer
|
|
/// [userIds] : Liste des IDs des utilisateurs destinataires
|
|
Future<Map<String, bool>> sendAlertEmailToMultipleUsers({
|
|
required AlertModel alert,
|
|
required List<String> userIds,
|
|
String templateType = 'alert-individual',
|
|
}) async {
|
|
final results = <String, bool>{};
|
|
|
|
DebugLog.info('[EmailService] Envoi emails à ${userIds.length} utilisateurs');
|
|
|
|
// Envoyer en parallèle (max 5 à la fois pour éviter surcharge)
|
|
final batches = <List<String>>[];
|
|
for (var i = 0; i < userIds.length; i += 5) {
|
|
batches.add(userIds.sublist(
|
|
i,
|
|
i + 5 > userIds.length ? userIds.length : i + 5,
|
|
));
|
|
}
|
|
|
|
for (final batch in batches) {
|
|
final futures = batch.map((userId) => sendAlertEmail(
|
|
alert: alert,
|
|
userId: userId,
|
|
templateType: templateType,
|
|
));
|
|
|
|
final batchResults = await Future.wait(futures);
|
|
|
|
for (var i = 0; i < batch.length; i++) {
|
|
results[batch[i]] = batchResults[i];
|
|
}
|
|
}
|
|
|
|
final successCount = results.values.where((v) => v).length;
|
|
DebugLog.info('[EmailService] $successCount/${ userIds.length} emails envoyés');
|
|
|
|
return results;
|
|
}
|
|
|
|
/// Détermine si une alerte doit être envoyée immédiatement ou en digest
|
|
///
|
|
/// [alert] : L'alerte à vérifier
|
|
/// Returns: true si immédiat, false si digest
|
|
bool shouldSendImmediate(AlertModel alert) {
|
|
// Les alertes critiques sont envoyées immédiatement
|
|
if (alert.severity == AlertSeverity.critical) {
|
|
return true;
|
|
}
|
|
|
|
// Types d'alertes toujours immédiates
|
|
const immediateTypes = [
|
|
AlertType.lost, // Équipement perdu
|
|
AlertType.eventCancelled, // Événement annulé
|
|
];
|
|
|
|
return immediateTypes.contains(alert.type);
|
|
}
|
|
|
|
/// Envoie un email d'alerte en tenant compte des préférences
|
|
///
|
|
/// [alert] : L'alerte à envoyer
|
|
/// [userIds] : Liste des IDs des utilisateurs destinataires
|
|
Future<void> sendAlertWithPreferences({
|
|
required AlertModel alert,
|
|
required List<String> userIds,
|
|
}) async {
|
|
if (userIds.isEmpty) {
|
|
DebugLog.warning('[EmailService] Aucun utilisateur à notifier');
|
|
return;
|
|
}
|
|
|
|
final immediate = shouldSendImmediate(alert);
|
|
|
|
if (immediate) {
|
|
DebugLog.info('[EmailService] Envoi immédiat (alerte critique)');
|
|
await sendAlertEmailToMultipleUsers(
|
|
alert: alert,
|
|
userIds: userIds,
|
|
templateType: 'alert-individual',
|
|
);
|
|
} else {
|
|
DebugLog.info('[EmailService] Ajout au digest (alerte non critique)');
|
|
// Les alertes non critiques seront envoyées dans le digest quotidien
|
|
// La Cloud Function sendDailyDigest s'en occupera
|
|
// Rien à faire ici, les alertes sont déjà dans Firestore
|
|
}
|
|
}
|
|
}
|
|
|