Files
geo/flutt/lib/chat/services/notifications/mqtt_notification_service.dart

323 lines
9.3 KiB
Dart

import 'dart:async';
import 'dart:convert';
import 'package:mqtt5_client/mqtt5_client.dart';
import 'package:mqtt5_client/mqtt5_server_client.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
/// Service de gestion des notifications chat via MQTT
///
/// Utilise MQTT pour recevoir des notifications en temps réel
/// et afficher des notifications locales
class MqttNotificationService {
static final MqttNotificationService _instance = MqttNotificationService._internal();
factory MqttNotificationService() => _instance;
MqttNotificationService._internal();
late MqttServerClient _client;
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}';
/// 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;
}
/// Initialise le client MQTT
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;
}
}
/// Se connecte au broker MQTT
Future<void> _connect() async {
try {
await _client.connect();
} catch (e) {
debugPrint('Erreur de connexion MQTT : $e');
_client.disconnect();
rethrow;
}
}
/// 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);
}
/// 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), () {
_connect();
});
}
}
/// Callback lors de l'abonnement
void _onSubscribed(MqttSubscription subscription) {
debugPrint('Abonné au topic : ${subscription.topic.rawTopic}');
}
/// S'abonner aux topics de l'utilisateur
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);
}
/// Gère les messages reçus
void _onMessageReceived(List<MqttReceivedMessage<MqttMessage>> messages) {
for (var message in messages) {
final topic = message.topic;
final payload = message.payload as MqttPublishMessage;
final messageText = MqttUtilities.bytesToStringAsString(payload.payload.message!);
try {
final data = jsonDecode(messageText) as Map<String, dynamic>;
_handleNotification(topic, data);
} catch (e) {
debugPrint('Erreur lors du décodage du message : $e');
}
}
}
/// Traite la notification reçue
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';
body = data['content'] ?? '';
messageId = data['messageId'] ?? '';
conversationId = data['conversationId'] ?? '';
} else if (topic.startsWith('chat/announcement')) {
// Annonce
title = data['title'] ?? 'Annonce';
body = data['content'] ?? '';
messageId = data['messageId'] ?? '';
conversationId = data['conversationId'] ?? '';
}
// Afficher la notification locale
await _showLocalNotification(
title: title,
body: body,
payload: jsonEncode({
'messageId': messageId,
'conversationId': conversationId,
}),
);
// Appeler le callback si défini
onNotificationReceived?.call(data);
}
/// Vérifie si la notification doit être affichée
Future<bool> _shouldShowNotification(Map<String, dynamic> data) async {
// TODO: Vérifier les paramètres de notification de l'utilisateur
// - Notifications désactivées
// - Conversation en silencieux
// - Mode Ne pas déranger
return true;
}
/// Initialise les notifications locales
Future<void> _initializeLocalNotifications() async {
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,
);
}
/// Affiche une notification locale
Future<void> _showLocalNotification({
required String title,
required String body,
required String payload,
}) async {
const androidDetails = AndroidNotificationDetails(
'chat_messages',
'Messages de chat',
channelDescription: 'Notifications pour les nouveaux messages de chat',
importance: Importance.high,
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,
body,
notificationDetails,
payload: payload,
);
}
/// Handler pour le clic sur une notification
void _onNotificationTap(NotificationResponse response) {
final payload = response.payload;
if (payload != null) {
try {
final data = jsonDecode(payload) as Map<String, dynamic>;
final messageId = data['messageId'] as String?;
if (messageId != null) {
onMessageTap?.call(messageId);
}
} catch (e) {
debugPrint('Erreur lors du traitement du clic sur notification : $e');
}
}
}
/// Publie un message MQTT
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);
}
}
/// Se désabonner d'une conversation
Future<void> unsubscribeFromConversation(String conversationId) async {
if (_client.connectionStatus?.state == MqttConnectionState.connected) {
_client.unsubscribeStringTopic('chat/conversation/$conversationId');
}
}
/// Désactive temporairement les notifications
void pauseNotifications() {
_client.pause();
}
/// Réactive les notifications
void resumeNotifications() {
_client.resume();
}
/// Libère les ressources
void dispose() {
_messageSubscription?.cancel();
_client.disconnect();
_initialized = false;
}
}