feat: Gestion des secteurs et migration v3.0.4+304
- Ajout système complet de gestion des secteurs avec contours géographiques - Import des contours départementaux depuis GeoJSON - API REST pour la gestion des secteurs (/api/sectors) - Service de géolocalisation pour déterminer les secteurs - Migration base de données avec tables x_departements_contours et sectors_adresses - Interface Flutter pour visualisation et gestion des secteurs - Ajout thème sombre dans l'application - Corrections diverses et optimisations 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
0
app/lib/chat/services/notifications/README_MQTT.md
Normal file → Executable file
0
app/lib/chat/services/notifications/README_MQTT.md
Normal file → Executable file
43
app/lib/chat/services/notifications/chat_notification_service.dart
Normal file → Executable file
43
app/lib/chat/services/notifications/chat_notification_service.dart
Normal file → Executable file
@@ -3,17 +3,19 @@ import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
/// Service de gestion des notifications chat
|
||||
///
|
||||
///
|
||||
/// Gère l'envoi et la réception des notifications pour le module chat
|
||||
|
||||
class ChatNotificationService {
|
||||
static final ChatNotificationService _instance = ChatNotificationService._internal();
|
||||
static final ChatNotificationService _instance =
|
||||
ChatNotificationService._internal();
|
||||
factory ChatNotificationService() => _instance;
|
||||
ChatNotificationService._internal();
|
||||
|
||||
final FirebaseMessaging _firebaseMessaging = FirebaseMessaging.instance;
|
||||
final FlutterLocalNotificationsPlugin _localNotifications = FlutterLocalNotificationsPlugin();
|
||||
|
||||
final FlutterLocalNotificationsPlugin _localNotifications =
|
||||
FlutterLocalNotificationsPlugin();
|
||||
|
||||
// Callback pour les actions sur les notifications
|
||||
Function(String messageId)? onMessageTap;
|
||||
Function(Map<String, dynamic>)? onBackgroundMessage;
|
||||
@@ -22,13 +24,13 @@ class ChatNotificationService {
|
||||
Future<void> initialize() async {
|
||||
// Demander les permissions
|
||||
await _requestPermissions();
|
||||
|
||||
|
||||
// Initialiser les notifications locales
|
||||
await _initializeLocalNotifications();
|
||||
|
||||
|
||||
// Configurer les handlers de messages
|
||||
_configureFirebaseHandlers();
|
||||
|
||||
|
||||
// Obtenir le token du device
|
||||
await _initializeDeviceToken();
|
||||
}
|
||||
@@ -47,10 +49,11 @@ class ChatNotificationService {
|
||||
|
||||
/// Initialise les notifications locales
|
||||
Future<void> _initializeLocalNotifications() async {
|
||||
const AndroidInitializationSettings androidSettings =
|
||||
const AndroidInitializationSettings androidSettings =
|
||||
AndroidInitializationSettings('@mipmap/ic_launcher');
|
||||
|
||||
final DarwinInitializationSettings iosSettings = DarwinInitializationSettings(
|
||||
|
||||
final DarwinInitializationSettings iosSettings =
|
||||
DarwinInitializationSettings(
|
||||
requestAlertPermission: true,
|
||||
requestBadgePermission: true,
|
||||
requestSoundPermission: true,
|
||||
@@ -72,10 +75,10 @@ class ChatNotificationService {
|
||||
void _configureFirebaseHandlers() {
|
||||
// Message reçu quand l'app est au premier plan
|
||||
FirebaseMessaging.onMessage.listen(_onForegroundMessage);
|
||||
|
||||
|
||||
// Message reçu quand l'app est en arrière-plan
|
||||
FirebaseMessaging.onMessageOpenedApp.listen(_onBackgroundMessageOpened);
|
||||
|
||||
|
||||
// Handler pour les messages en arrière-plan terminé
|
||||
FirebaseMessaging.onBackgroundMessage(_firebaseBackgroundHandler);
|
||||
}
|
||||
@@ -106,7 +109,8 @@ class ChatNotificationService {
|
||||
required String body,
|
||||
required String payload,
|
||||
}) async {
|
||||
const AndroidNotificationDetails androidDetails = AndroidNotificationDetails(
|
||||
const AndroidNotificationDetails androidDetails =
|
||||
AndroidNotificationDetails(
|
||||
'chat_messages',
|
||||
'Messages de chat',
|
||||
channelDescription: 'Notifications pour les nouveaux messages de chat',
|
||||
@@ -144,21 +148,20 @@ class ChatNotificationService {
|
||||
}
|
||||
|
||||
/// Handler pour les notifications iOS reçues au premier plan
|
||||
void _onDidReceiveLocalNotification(int id, String? title, String? body, String? payload) {
|
||||
void _onDidReceiveLocalNotification(
|
||||
int id, String? title, String? body, String? payload) {
|
||||
// Traitement spécifique iOS si nécessaire
|
||||
}
|
||||
|
||||
/// Obtient et stocke le token du device
|
||||
Future<String?> _initializeDeviceToken() async {
|
||||
String? token = await _firebaseMessaging.getToken();
|
||||
if (token != null) {
|
||||
// Envoyer le token au serveur pour stocker
|
||||
await _sendTokenToServer(token);
|
||||
}
|
||||
|
||||
// Envoyer le token au serveur pour stocker
|
||||
await _sendTokenToServer(token);
|
||||
|
||||
// Écouter les changements de token
|
||||
_firebaseMessaging.onTokenRefresh.listen(_sendTokenToServer);
|
||||
|
||||
|
||||
return token;
|
||||
}
|
||||
|
||||
|
||||
23
app/lib/chat/services/notifications/mqtt_config.dart
Normal file → Executable file
23
app/lib/chat/services/notifications/mqtt_config.dart
Normal file → Executable file
@@ -1,6 +1,7 @@
|
||||
/// Configuration pour le broker MQTT
|
||||
///
|
||||
/// Centralise les paramètres de connexion au broker MQTT
|
||||
library;
|
||||
|
||||
class MqttConfig {
|
||||
// Configuration du serveur MQTT
|
||||
@@ -8,32 +9,32 @@ class MqttConfig {
|
||||
static const int port = 1883;
|
||||
static const int securePort = 8883;
|
||||
static const bool useSsl = false;
|
||||
|
||||
|
||||
// Configuration d'authentification
|
||||
static const String username = 'geosector_chat';
|
||||
static const String password = 'secure_password_here';
|
||||
|
||||
|
||||
// Préfixes des topics MQTT
|
||||
static const String topicBase = 'chat';
|
||||
static const String topicUserMessages = '$topicBase/user';
|
||||
static const String topicAnnouncements = '$topicBase/announcement';
|
||||
static const String topicGroups = '$topicBase/groups';
|
||||
static const String topicConversations = '$topicBase/conversation';
|
||||
|
||||
|
||||
// Configuration des sessions
|
||||
static const int keepAliveInterval = 60;
|
||||
static const int reconnectInterval = 5;
|
||||
static const bool cleanSession = true;
|
||||
|
||||
|
||||
// Configuration des notifications
|
||||
static const int notificationRetryCount = 3;
|
||||
static const Duration notificationTimeout = Duration(seconds: 30);
|
||||
|
||||
|
||||
/// Génère un client ID unique pour chaque session
|
||||
static String generateClientId(String userId) {
|
||||
return 'chat_${userId}_${DateTime.now().millisecondsSinceEpoch}';
|
||||
}
|
||||
|
||||
|
||||
/// Retourne l'URL complète du broker selon la configuration SSL
|
||||
static String get brokerUrl {
|
||||
if (useSsl) {
|
||||
@@ -42,27 +43,27 @@ class MqttConfig {
|
||||
return '$host:$port';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Retourne le topic pour les messages d'un utilisateur
|
||||
static String getUserMessageTopic(String userId) {
|
||||
return '$topicUserMessages/$userId/messages';
|
||||
}
|
||||
|
||||
|
||||
/// Retourne le topic pour les annonces globales
|
||||
static String getAnnouncementTopic() {
|
||||
return topicAnnouncements;
|
||||
}
|
||||
|
||||
|
||||
/// Retourne le topic pour une conversation spécifique
|
||||
static String getConversationTopic(String conversationId) {
|
||||
return '$topicConversations/$conversationId';
|
||||
}
|
||||
|
||||
|
||||
/// Retourne le topic pour un groupe spécifique
|
||||
static String getGroupTopic(String groupId) {
|
||||
return '$topicGroups/$groupId';
|
||||
}
|
||||
|
||||
|
||||
/// Retourne les topics auxquels un utilisateur doit s'abonner
|
||||
static List<String> getUserSubscriptionTopics(String userId) {
|
||||
return [
|
||||
|
||||
85
app/lib/chat/services/notifications/mqtt_notification_service.dart
Normal file → Executable file
85
app/lib/chat/services/notifications/mqtt_notification_service.dart
Normal file → Executable file
@@ -11,49 +11,52 @@ import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
||||
/// et afficher des notifications locales
|
||||
|
||||
class MqttNotificationService {
|
||||
static final MqttNotificationService _instance = MqttNotificationService._internal();
|
||||
static final MqttNotificationService _instance =
|
||||
MqttNotificationService._internal();
|
||||
factory MqttNotificationService() => _instance;
|
||||
MqttNotificationService._internal();
|
||||
|
||||
late MqttServerClient _client;
|
||||
final FlutterLocalNotificationsPlugin _localNotifications = FlutterLocalNotificationsPlugin();
|
||||
|
||||
final FlutterLocalNotificationsPlugin _localNotifications =
|
||||
FlutterLocalNotificationsPlugin();
|
||||
|
||||
// Configuration
|
||||
final String mqttHost;
|
||||
final int mqttPort;
|
||||
final String mqttUsername;
|
||||
final String mqttPassword;
|
||||
final String clientId;
|
||||
|
||||
|
||||
// État
|
||||
bool _initialized = false;
|
||||
String? _userId;
|
||||
StreamSubscription? _messageSubscription;
|
||||
|
||||
|
||||
// Callbacks
|
||||
Function(String messageId)? onMessageTap;
|
||||
Function(Map<String, dynamic>)? onNotificationReceived;
|
||||
|
||||
|
||||
MqttNotificationService({
|
||||
this.mqttHost = 'mqtt.geosector.fr',
|
||||
this.mqttPort = 1883,
|
||||
this.mqttUsername = '',
|
||||
this.mqttPassword = '',
|
||||
String? clientId,
|
||||
}) : clientId = clientId ?? 'geosector_chat_${DateTime.now().millisecondsSinceEpoch}';
|
||||
}) : clientId = clientId ??
|
||||
'geosector_chat_${DateTime.now().millisecondsSinceEpoch}';
|
||||
|
||||
/// Initialise le service de notifications
|
||||
Future<void> initialize({required String userId}) async {
|
||||
if (_initialized) return;
|
||||
|
||||
|
||||
_userId = userId;
|
||||
|
||||
|
||||
// Initialiser les notifications locales
|
||||
await _initializeLocalNotifications();
|
||||
|
||||
|
||||
// Initialiser le client MQTT
|
||||
await _initializeMqttClient();
|
||||
|
||||
|
||||
_initialized = true;
|
||||
}
|
||||
|
||||
@@ -61,26 +64,25 @@ class MqttNotificationService {
|
||||
Future<void> _initializeMqttClient() async {
|
||||
try {
|
||||
_client = MqttServerClient.withPort(mqttHost, clientId, mqttPort);
|
||||
|
||||
|
||||
_client.logging(on: kDebugMode);
|
||||
_client.keepAlivePeriod = 60;
|
||||
_client.onConnected = _onConnected;
|
||||
_client.onDisconnected = _onDisconnected;
|
||||
_client.onSubscribed = _onSubscribed;
|
||||
_client.autoReconnect = true;
|
||||
|
||||
|
||||
// Configurer les options de connexion
|
||||
final connMessage = MqttConnectMessage()
|
||||
.authenticateAs(mqttUsername, mqttPassword)
|
||||
.withClientIdentifier(clientId)
|
||||
.startClean()
|
||||
.keepAliveFor(60);
|
||||
|
||||
|
||||
_client.connectionMessage = connMessage;
|
||||
|
||||
|
||||
// Se connecter
|
||||
await _connect();
|
||||
|
||||
} catch (e) {
|
||||
debugPrint('Erreur lors de l\'initialisation MQTT : $e');
|
||||
rethrow;
|
||||
@@ -101,20 +103,20 @@ class MqttNotificationService {
|
||||
/// Callback lors de la connexion
|
||||
void _onConnected() {
|
||||
debugPrint('Connecté au broker MQTT');
|
||||
|
||||
|
||||
// S'abonner aux topics de l'utilisateur
|
||||
if (_userId != null) {
|
||||
_subscribeToUserTopics(_userId!);
|
||||
}
|
||||
|
||||
|
||||
// Écouter les messages
|
||||
_messageSubscription = _client.updates?.listen(_onMessageReceived);
|
||||
_messageSubscription = _client.updates.listen(_onMessageReceived);
|
||||
}
|
||||
|
||||
/// Callback lors de la déconnexion
|
||||
void _onDisconnected() {
|
||||
debugPrint('Déconnecté du broker MQTT');
|
||||
|
||||
|
||||
// Tenter une reconnexion
|
||||
if (_client.autoReconnect) {
|
||||
Future.delayed(const Duration(seconds: 5), () {
|
||||
@@ -132,10 +134,10 @@ class MqttNotificationService {
|
||||
void _subscribeToUserTopics(String userId) {
|
||||
// Topic pour les messages personnels
|
||||
_client.subscribe('chat/user/$userId/messages', MqttQos.atLeastOnce);
|
||||
|
||||
|
||||
// Topic pour les annonces
|
||||
_client.subscribe('chat/announcement', MqttQos.atLeastOnce);
|
||||
|
||||
|
||||
// Topic pour les groupes de l'utilisateur (si disponibles)
|
||||
_client.subscribe('chat/user/$userId/groups/+', MqttQos.atLeastOnce);
|
||||
}
|
||||
@@ -145,8 +147,9 @@ class MqttNotificationService {
|
||||
for (var message in messages) {
|
||||
final topic = message.topic;
|
||||
final payload = message.payload as MqttPublishMessage;
|
||||
final messageText = MqttUtilities.bytesToStringAsString(payload.payload.message!);
|
||||
|
||||
final messageText =
|
||||
MqttUtilities.bytesToStringAsString(payload.payload.message!);
|
||||
|
||||
try {
|
||||
final data = jsonDecode(messageText) as Map<String, dynamic>;
|
||||
_handleNotification(topic, data);
|
||||
@@ -157,17 +160,18 @@ class MqttNotificationService {
|
||||
}
|
||||
|
||||
/// Traite la notification reçue
|
||||
Future<void> _handleNotification(String topic, Map<String, dynamic> data) async {
|
||||
Future<void> _handleNotification(
|
||||
String topic, Map<String, dynamic> data) async {
|
||||
// Vérifier les paramètres de notification de l'utilisateur
|
||||
if (!await _shouldShowNotification(data)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
String title = '';
|
||||
String body = '';
|
||||
String messageId = '';
|
||||
String conversationId = '';
|
||||
|
||||
|
||||
if (topic.startsWith('chat/user/')) {
|
||||
// Message personnel
|
||||
title = data['senderName'] ?? 'Nouveau message';
|
||||
@@ -181,7 +185,7 @@ class MqttNotificationService {
|
||||
messageId = data['messageId'] ?? '';
|
||||
conversationId = data['conversationId'] ?? '';
|
||||
}
|
||||
|
||||
|
||||
// Afficher la notification locale
|
||||
await _showLocalNotification(
|
||||
title: title,
|
||||
@@ -191,7 +195,7 @@ class MqttNotificationService {
|
||||
'conversationId': conversationId,
|
||||
}),
|
||||
);
|
||||
|
||||
|
||||
// Appeler le callback si défini
|
||||
onNotificationReceived?.call(data);
|
||||
}
|
||||
@@ -207,18 +211,19 @@ class MqttNotificationService {
|
||||
|
||||
/// Initialise les notifications locales
|
||||
Future<void> _initializeLocalNotifications() async {
|
||||
const androidSettings = AndroidInitializationSettings('@mipmap/ic_launcher');
|
||||
const androidSettings =
|
||||
AndroidInitializationSettings('@mipmap/ic_launcher');
|
||||
const iosSettings = DarwinInitializationSettings(
|
||||
requestAlertPermission: true,
|
||||
requestBadgePermission: true,
|
||||
requestSoundPermission: true,
|
||||
);
|
||||
|
||||
|
||||
const initSettings = InitializationSettings(
|
||||
android: androidSettings,
|
||||
iOS: iosSettings,
|
||||
);
|
||||
|
||||
|
||||
await _localNotifications.initialize(
|
||||
initSettings,
|
||||
onDidReceiveNotificationResponse: _onNotificationTap,
|
||||
@@ -239,18 +244,18 @@ class MqttNotificationService {
|
||||
priority: Priority.high,
|
||||
icon: '@mipmap/ic_launcher',
|
||||
);
|
||||
|
||||
|
||||
const iosDetails = DarwinNotificationDetails(
|
||||
presentAlert: true,
|
||||
presentBadge: true,
|
||||
presentSound: true,
|
||||
);
|
||||
|
||||
|
||||
const notificationDetails = NotificationDetails(
|
||||
android: androidDetails,
|
||||
iOS: iosDetails,
|
||||
);
|
||||
|
||||
|
||||
await _localNotifications.show(
|
||||
DateTime.now().microsecondsSinceEpoch,
|
||||
title,
|
||||
@@ -277,22 +282,24 @@ class MqttNotificationService {
|
||||
}
|
||||
|
||||
/// Publie un message MQTT
|
||||
Future<void> publishMessage(String topic, Map<String, dynamic> message) async {
|
||||
Future<void> publishMessage(
|
||||
String topic, Map<String, dynamic> message) async {
|
||||
if (_client.connectionStatus?.state != MqttConnectionState.connected) {
|
||||
await _connect();
|
||||
}
|
||||
|
||||
|
||||
final messagePayload = jsonEncode(message);
|
||||
final builder = MqttPayloadBuilder();
|
||||
builder.addString(messagePayload);
|
||||
|
||||
|
||||
_client.publishMessage(topic, MqttQos.atLeastOnce, builder.payload!);
|
||||
}
|
||||
|
||||
/// S'abonner à une conversation spécifique
|
||||
Future<void> subscribeToConversation(String conversationId) async {
|
||||
if (_client.connectionStatus?.state == MqttConnectionState.connected) {
|
||||
_client.subscribe('chat/conversation/$conversationId', MqttQos.atLeastOnce);
|
||||
_client.subscribe(
|
||||
'chat/conversation/$conversationId', MqttQos.atLeastOnce);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user