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/README.md
Normal file → Executable file
0
app/lib/chat/README.md
Normal file → Executable file
1
app/lib/chat/chat.dart
Normal file → Executable file
1
app/lib/chat/chat.dart
Normal file → Executable file
@@ -2,6 +2,7 @@
|
||||
///
|
||||
/// Ce fichier centralise les exportations du module chat
|
||||
/// pour faciliter l'importation dans d'autres parties de l'application
|
||||
library;
|
||||
|
||||
// Models
|
||||
export 'models/conversation_model.dart';
|
||||
|
||||
0
app/lib/chat/chat_updated.md
Normal file → Executable file
0
app/lib/chat/chat_updated.md
Normal file → Executable file
1
app/lib/chat/constants/chat_constants.dart
Normal file → Executable file
1
app/lib/chat/constants/chat_constants.dart
Normal file → Executable file
@@ -1,4 +1,5 @@
|
||||
/// Constantes spécifiques au module chat
|
||||
library;
|
||||
|
||||
class ChatConstants {
|
||||
// Types de conversations
|
||||
|
||||
8
app/lib/chat/example_integration/mqtt_integration_example.dart
Normal file → Executable file
8
app/lib/chat/example_integration/mqtt_integration_example.dart
Normal file → Executable file
@@ -55,7 +55,6 @@ class _MqttIntegrationExampleState extends State<MqttIntegrationExample> {
|
||||
_isInitialized = true;
|
||||
_status = 'Service MQTT initialisé';
|
||||
});
|
||||
|
||||
} catch (e) {
|
||||
setState(() {
|
||||
_status = 'Erreur : $e';
|
||||
@@ -127,7 +126,8 @@ class _MqttIntegrationExampleState extends State<MqttIntegrationExample> {
|
||||
'chat/user/${_getCurrentUserId()}/messages',
|
||||
{
|
||||
'type': 'chat_message',
|
||||
'messageId': 'test_${DateTime.now().millisecondsSinceEpoch}',
|
||||
'messageId':
|
||||
'test_${DateTime.now().millisecondsSinceEpoch}',
|
||||
'content': 'Message de test',
|
||||
'senderId': '999',
|
||||
'senderName': 'Système',
|
||||
@@ -159,8 +159,8 @@ class MyApp extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
home: const MqttIntegrationExample(),
|
||||
return const MaterialApp(
|
||||
home: MqttIntegrationExample(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
0
app/lib/chat/models/anonymous_user_model.dart
Normal file → Executable file
0
app/lib/chat/models/anonymous_user_model.dart
Normal file → Executable file
0
app/lib/chat/models/audience_target_model.dart
Normal file → Executable file
0
app/lib/chat/models/audience_target_model.dart
Normal file → Executable file
0
app/lib/chat/models/chat_adapters.dart
Normal file → Executable file
0
app/lib/chat/models/chat_adapters.dart
Normal file → Executable file
0
app/lib/chat/models/chat_config.dart
Normal file → Executable file
0
app/lib/chat/models/chat_config.dart
Normal file → Executable file
0
app/lib/chat/models/conversation_model.dart
Normal file → Executable file
0
app/lib/chat/models/conversation_model.dart
Normal file → Executable file
0
app/lib/chat/models/message_model.dart
Normal file → Executable file
0
app/lib/chat/models/message_model.dart
Normal file → Executable file
0
app/lib/chat/models/notification_settings.dart
Normal file → Executable file
0
app/lib/chat/models/notification_settings.dart
Normal file → Executable file
0
app/lib/chat/models/participant_model.dart
Normal file → Executable file
0
app/lib/chat/models/participant_model.dart
Normal file → Executable file
8
app/lib/chat/pages/chat_page.dart
Normal file → Executable file
8
app/lib/chat/pages/chat_page.dart
Normal file → Executable file
@@ -32,7 +32,8 @@ class _ChatPageState extends State<ChatPage> {
|
||||
child: ConversationsList(
|
||||
onConversationSelected: (conversation) {
|
||||
setState(() {
|
||||
_selectedConversationId = 'conversation-id'; // TODO: obtenir l'ID de la conversation
|
||||
_selectedConversationId =
|
||||
'conversation-id'; // TODO: obtenir l'ID de la conversation
|
||||
});
|
||||
},
|
||||
),
|
||||
@@ -66,8 +67,9 @@ class _ChatPageState extends State<ChatPage> {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => ChatScreen(
|
||||
conversationId: 'conversation-id', // TODO: obtenir l'ID de la conversation
|
||||
builder: (context) => const ChatScreen(
|
||||
conversationId:
|
||||
'conversation-id', // TODO: obtenir l'ID de la conversation
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
0
app/lib/chat/repositories/chat_repository.dart
Normal file → Executable file
0
app/lib/chat/repositories/chat_repository.dart
Normal file → Executable file
0
app/lib/chat/scripts/chat_tables.sql
Normal file → Executable file
0
app/lib/chat/scripts/chat_tables.sql
Normal file → Executable file
0
app/lib/chat/scripts/mqtt_notification_sender.php
Normal file → Executable file
0
app/lib/chat/scripts/mqtt_notification_sender.php
Normal file → Executable file
0
app/lib/chat/scripts/send_notification.php
Normal file → Executable file
0
app/lib/chat/scripts/send_notification.php
Normal file → Executable file
28
app/lib/chat/services/chat_api_service.dart
Normal file → Executable file
28
app/lib/chat/services/chat_api_service.dart
Normal file → Executable file
@@ -1,6 +1,7 @@
|
||||
/// Service API pour la communication avec le backend du chat
|
||||
///
|
||||
/// Ce service gère toutes les requêtes HTTP vers l'API chat
|
||||
library;
|
||||
|
||||
class ChatApiService {
|
||||
final String baseUrl;
|
||||
@@ -18,19 +19,22 @@ class ChatApiService {
|
||||
}
|
||||
|
||||
/// Récupère les messages d'une conversation
|
||||
Future<Map<String, dynamic>> fetchMessages(String conversationId, {int page = 1, int limit = 50}) async {
|
||||
Future<Map<String, dynamic>> fetchMessages(String conversationId,
|
||||
{int page = 1, int limit = 50}) async {
|
||||
// TODO: Implémenter la requête HTTP
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
/// Crée une nouvelle conversation
|
||||
Future<Map<String, dynamic>> createConversation(Map<String, dynamic> data) async {
|
||||
Future<Map<String, dynamic>> createConversation(
|
||||
Map<String, dynamic> data) async {
|
||||
// TODO: Implémenter la requête HTTP
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
/// Envoie un message
|
||||
Future<Map<String, dynamic>> sendMessage(String conversationId, Map<String, dynamic> messageData) async {
|
||||
Future<Map<String, dynamic>> sendMessage(
|
||||
String conversationId, Map<String, dynamic> messageData) async {
|
||||
// TODO: Implémenter la requête HTTP
|
||||
throw UnimplementedError();
|
||||
}
|
||||
@@ -42,19 +46,22 @@ class ChatApiService {
|
||||
}
|
||||
|
||||
/// Ajoute un participant
|
||||
Future<Map<String, dynamic>> addParticipant(String conversationId, Map<String, dynamic> participantData) async {
|
||||
Future<Map<String, dynamic>> addParticipant(
|
||||
String conversationId, Map<String, dynamic> participantData) async {
|
||||
// TODO: Implémenter la requête HTTP
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
/// Retire un participant
|
||||
Future<Map<String, dynamic>> removeParticipant(String conversationId, String participantId) async {
|
||||
Future<Map<String, dynamic>> removeParticipant(
|
||||
String conversationId, String participantId) async {
|
||||
// TODO: Implémenter la requête HTTP
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
/// Crée un utilisateur anonyme
|
||||
Future<Map<String, dynamic>> createAnonymousUser({String? name, String? email}) async {
|
||||
Future<Map<String, dynamic>> createAnonymousUser(
|
||||
{String? name, String? email}) async {
|
||||
// TODO: Implémenter la requête HTTP
|
||||
throw UnimplementedError();
|
||||
}
|
||||
@@ -66,13 +73,15 @@ class ChatApiService {
|
||||
}
|
||||
|
||||
/// Crée une annonce
|
||||
Future<Map<String, dynamic>> createAnnouncement(Map<String, dynamic> data) async {
|
||||
Future<Map<String, dynamic>> createAnnouncement(
|
||||
Map<String, dynamic> data) async {
|
||||
// TODO: Implémenter la requête HTTP
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
/// Récupère les statistiques d'une annonce
|
||||
Future<Map<String, dynamic>> fetchAnnouncementStats(String conversationId) async {
|
||||
Future<Map<String, dynamic>> fetchAnnouncementStats(
|
||||
String conversationId) async {
|
||||
// TODO: Implémenter la requête HTTP
|
||||
throw UnimplementedError();
|
||||
}
|
||||
@@ -84,7 +93,8 @@ class ChatApiService {
|
||||
}
|
||||
|
||||
/// Met à jour une conversation
|
||||
Future<Map<String, dynamic>> updateConversation(String id, Map<String, dynamic> data) async {
|
||||
Future<Map<String, dynamic>> updateConversation(
|
||||
String id, Map<String, dynamic> data) async {
|
||||
// TODO: Implémenter la requête HTTP
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
4
app/lib/chat/services/offline_queue_service.dart
Normal file → Executable file
4
app/lib/chat/services/offline_queue_service.dart
Normal file → Executable file
@@ -2,6 +2,7 @@
|
||||
///
|
||||
/// Ce service gère les opérations chat en mode hors ligne
|
||||
/// et les synchronise lorsque la connexion revient
|
||||
library;
|
||||
|
||||
class OfflineQueueService {
|
||||
// TODO: Ajouter le service de connectivité
|
||||
@@ -9,7 +10,8 @@ class OfflineQueueService {
|
||||
OfflineQueueService();
|
||||
|
||||
/// Ajoute une opération en attente
|
||||
Future<void> addPendingOperation(String operationType, Map<String, dynamic> data) async {
|
||||
Future<void> addPendingOperation(
|
||||
String operationType, Map<String, dynamic> data) async {
|
||||
// TODO: Implémenter l'ajout à la file d'attente
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
0
app/lib/chat/widgets/chat_input.dart
Normal file → Executable file
0
app/lib/chat/widgets/chat_input.dart
Normal file → Executable file
0
app/lib/chat/widgets/chat_screen.dart
Normal file → Executable file
0
app/lib/chat/widgets/chat_screen.dart
Normal file → Executable file
0
app/lib/chat/widgets/conversations_list.dart
Normal file → Executable file
0
app/lib/chat/widgets/conversations_list.dart
Normal file → Executable file
15
app/lib/chat/widgets/message_bubble.dart
Normal file → Executable file
15
app/lib/chat/widgets/message_bubble.dart
Normal file → Executable file
@@ -30,32 +30,35 @@ class MessageBubble extends StatelessWidget {
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (showSenderInfo) CircleAvatar(child: Text('S')),
|
||||
if (showSenderInfo) const CircleAvatar(child: Text('S')),
|
||||
Expanded(
|
||||
child: Container(
|
||||
constraints: BoxConstraints(maxWidth: maxWidth),
|
||||
margin: const EdgeInsets.only(left: 8),
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: isAnnouncement ? Colors.orange.shade100 : Colors.blue.shade100,
|
||||
color: isAnnouncement
|
||||
? Colors.orange.shade100
|
||||
: Colors.blue.shade100,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (showSenderInfo)
|
||||
Text(
|
||||
const Text(
|
||||
'Expéditeur',
|
||||
style: TextStyle(fontWeight: FontWeight.bold),
|
||||
),
|
||||
Text('Contenu du message...'),
|
||||
const Text('Contenu du message...'),
|
||||
if (showTimestamp || showStatus)
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
if (showTimestamp) Text('12:34', style: TextStyle(fontSize: 12)),
|
||||
if (showTimestamp)
|
||||
const Text('12:34', style: TextStyle(fontSize: 12)),
|
||||
if (showStatus) const SizedBox(width: 4),
|
||||
if (showStatus) Icon(Icons.check, size: 16),
|
||||
if (showStatus) const Icon(Icons.check, size: 16),
|
||||
],
|
||||
),
|
||||
],
|
||||
|
||||
0
app/lib/chat/widgets/notification_settings_widget.dart
Normal file → Executable file
0
app/lib/chat/widgets/notification_settings_widget.dart
Normal file → Executable file
Reference in New Issue
Block a user