Restructuration majeure du projet: migration de flutt vers app, ajout de l'API et mise à jour du site web
This commit is contained in:
@@ -0,0 +1,322 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user