- 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>
365 lines
13 KiB
Dart
Executable File
365 lines
13 KiB
Dart
Executable File
import 'package:hive/hive.dart';
|
|
import 'package:uuid/uuid.dart';
|
|
import '../../core/constants/app_keys.dart';
|
|
import '../models/conversation_model.dart';
|
|
import '../models/message_model.dart';
|
|
import '../models/participant_model.dart';
|
|
import '../services/chat_api_service.dart';
|
|
import '../services/notifications/mqtt_notification_service.dart';
|
|
|
|
/// Repository pour la gestion des fonctionnalités de chat
|
|
///
|
|
/// Ce repository centralise toutes les opérations liées au chat,
|
|
/// y compris la gestion des conversations, des messages et des participants
|
|
|
|
class ChatRepository {
|
|
final ChatApiService _apiService;
|
|
final MqttNotificationService _mqttService;
|
|
|
|
ChatRepository(this._apiService, this._mqttService);
|
|
|
|
/// Liste des conversations de l'utilisateur
|
|
Future<List<ConversationModel>> getConversations({bool forceRefresh = false}) async {
|
|
try {
|
|
// Récupérer depuis Hive
|
|
var box = await Hive.openBox<ConversationModel>(AppKeys.chatConversationsBoxName);
|
|
var localConversations = box.values.toList();
|
|
|
|
// Si on force le rafraîchissement ou qu'on n'a pas de données locales
|
|
if (forceRefresh || localConversations.isEmpty) {
|
|
try {
|
|
// Récupérer depuis l'API
|
|
var apiConversations = await _apiService.getConversations();
|
|
|
|
// Mettre à jour Hive
|
|
await box.clear();
|
|
for (var conversation in apiConversations) {
|
|
await box.put(conversation.id, conversation);
|
|
}
|
|
|
|
return apiConversations;
|
|
} catch (e) {
|
|
// Si l'API échoue, utiliser les données locales
|
|
if (localConversations.isNotEmpty) {
|
|
return localConversations;
|
|
}
|
|
rethrow;
|
|
}
|
|
}
|
|
|
|
return localConversations;
|
|
} catch (e) {
|
|
throw Exception('Erreur lors de la récupération des conversations: $e');
|
|
}
|
|
}
|
|
|
|
/// Récupère une conversation spécifique
|
|
Future<ConversationModel> getConversation(String id) async {
|
|
try {
|
|
// Vérifier d'abord dans Hive
|
|
var box = await Hive.openBox<ConversationModel>(AppKeys.chatConversationsBoxName);
|
|
var localConversation = box.get(id);
|
|
|
|
if (localConversation != null) {
|
|
return localConversation;
|
|
}
|
|
|
|
// Sinon récupérer depuis l'API
|
|
var apiConversation = await _apiService.getConversation(id);
|
|
await box.put(id, apiConversation);
|
|
|
|
return apiConversation;
|
|
} catch (e) {
|
|
throw Exception('Erreur lors de la récupération de la conversation: $e');
|
|
}
|
|
}
|
|
|
|
/// Crée une nouvelle conversation
|
|
Future<ConversationModel> createConversation(Map<String, dynamic> data) async {
|
|
try {
|
|
// Créer via l'API
|
|
var conversation = await _apiService.createConversation(data);
|
|
|
|
// Sauvegarder dans Hive
|
|
var box = await Hive.openBox<ConversationModel>(AppKeys.chatConversationsBoxName);
|
|
await box.put(conversation.id, conversation);
|
|
|
|
// S'abonner aux notifications de la conversation
|
|
await _mqttService.subscribeToConversation(conversation.id);
|
|
|
|
return conversation;
|
|
} catch (e) {
|
|
throw Exception('Erreur lors de la création de la conversation: $e');
|
|
}
|
|
}
|
|
|
|
/// Supprime une conversation
|
|
Future<void> deleteConversation(String id) async {
|
|
try {
|
|
// Supprimer via l'API
|
|
await _apiService.deleteConversation(id);
|
|
|
|
// Supprimer de Hive
|
|
var box = await Hive.openBox<ConversationModel>(AppKeys.chatConversationsBoxName);
|
|
await box.delete(id);
|
|
|
|
// Se désabonner des notifications
|
|
await _mqttService.unsubscribeFromConversation(id);
|
|
} catch (e) {
|
|
throw Exception('Erreur lors de la suppression de la conversation: $e');
|
|
}
|
|
}
|
|
|
|
/// Épingle/désépingle une conversation
|
|
Future<void> pinConversation(String id, bool isPinned) async {
|
|
try {
|
|
await _apiService.pinConversation(id, isPinned);
|
|
|
|
// Mettre à jour dans Hive
|
|
var box = await Hive.openBox<ConversationModel>(AppKeys.chatConversationsBoxName);
|
|
var conversation = box.get(id);
|
|
if (conversation != null) {
|
|
await box.put(id, conversation.copyWith(isPinned: isPinned));
|
|
}
|
|
} catch (e) {
|
|
throw Exception('Erreur lors de l\'épinglage de la conversation: $e');
|
|
}
|
|
}
|
|
|
|
/// Met à jour les permissions de réponse
|
|
Future<void> updateReplyPermission(String id, String replyPermission) async {
|
|
try {
|
|
await _apiService.updateReplyPermission(id, replyPermission);
|
|
|
|
// Mettre à jour dans Hive
|
|
var box = await Hive.openBox<ConversationModel>(AppKeys.chatConversationsBoxName);
|
|
var conversation = box.get(id);
|
|
if (conversation != null) {
|
|
await box.put(id, conversation.copyWith(replyPermission: replyPermission));
|
|
}
|
|
} catch (e) {
|
|
throw Exception('Erreur lors de la mise à jour des permissions: $e');
|
|
}
|
|
}
|
|
|
|
/// Récupère les messages d'une conversation
|
|
Future<List<MessageModel>> getMessages(String conversationId, {int page = 1, int limit = 50}) async {
|
|
try {
|
|
// Récupérer depuis Hive
|
|
var box = await Hive.openBox<MessageModel>(AppKeys.chatMessagesBoxName);
|
|
var localMessages = box.values
|
|
.where((m) => m.conversationId == conversationId)
|
|
.toList()
|
|
..sort((a, b) => b.createdAt.compareTo(a.createdAt));
|
|
|
|
// Si on a assez de messages localement
|
|
if (localMessages.length >= page * limit) {
|
|
return localMessages.skip((page - 1) * limit).take(limit).toList();
|
|
}
|
|
|
|
try {
|
|
// Récupérer depuis l'API
|
|
var apiMessages = await _apiService.getMessages(conversationId, page: page, limit: limit);
|
|
|
|
// Mettre à jour Hive
|
|
for (var message in apiMessages) {
|
|
await box.put(message.id, message);
|
|
}
|
|
|
|
return apiMessages;
|
|
} catch (e) {
|
|
// Si l'API échoue, utiliser les données locales
|
|
if (localMessages.isNotEmpty) {
|
|
return localMessages.skip((page - 1) * limit).take(limit).toList();
|
|
}
|
|
rethrow;
|
|
}
|
|
} catch (e) {
|
|
throw Exception('Erreur lors de la récupération des messages: $e');
|
|
}
|
|
}
|
|
|
|
/// Envoie un message via MQTT
|
|
Future<void> sendMessage(String conversationId, Map<String, dynamic> messageData) async {
|
|
try {
|
|
// Générer un ID unique pour le message
|
|
var messageId = const Uuid().v4();
|
|
var userId = messageData['senderId'] as String?;
|
|
|
|
// Créer le message
|
|
var message = MessageModel(
|
|
id: messageId,
|
|
conversationId: conversationId,
|
|
senderId: userId,
|
|
senderType: 'user',
|
|
content: messageData['content'] as String,
|
|
contentType: messageData['contentType'] as String? ?? 'text',
|
|
createdAt: DateTime.now(),
|
|
status: 'sent',
|
|
isAnnouncement: messageData['isAnnouncement'] as bool? ?? false,
|
|
);
|
|
|
|
// Sauvegarder temporairement dans Hive
|
|
var box = await Hive.openBox<MessageModel>(AppKeys.chatMessagesBoxName);
|
|
await box.put(messageId, message);
|
|
|
|
// Publier via MQTT
|
|
await _mqttService.publishMessage('chat/message/send', {
|
|
'messageId': messageId,
|
|
'conversationId': conversationId,
|
|
'senderId': userId,
|
|
'content': message.content,
|
|
'contentType': message.contentType,
|
|
'timestamp': message.createdAt.toIso8601String(),
|
|
'isAnnouncement': message.isAnnouncement,
|
|
});
|
|
} catch (e) {
|
|
throw Exception('Erreur lors de l\'envoi du message: $e');
|
|
}
|
|
}
|
|
|
|
/// Marque un message comme lu
|
|
Future<void> markMessageAsRead(String messageId) async {
|
|
try {
|
|
// Mettre à jour via l'API
|
|
await _apiService.markMessageAsRead(messageId);
|
|
|
|
// Mettre à jour dans Hive
|
|
var box = await Hive.openBox<MessageModel>(AppKeys.chatMessagesBoxName);
|
|
var message = box.get(messageId);
|
|
if (message != null) {
|
|
await box.put(messageId, message.copyWith(
|
|
status: 'read',
|
|
readAt: DateTime.now(),
|
|
));
|
|
}
|
|
} catch (e) {
|
|
throw Exception('Erreur lors du marquage comme lu: $e');
|
|
}
|
|
}
|
|
|
|
/// Ajoute un participant à une conversation
|
|
Future<void> addParticipant(String conversationId, Map<String, dynamic> participantData) async {
|
|
try {
|
|
await _apiService.addParticipant(conversationId, participantData);
|
|
|
|
// Mettre à jour la conversation dans Hive
|
|
var conversationBox = await Hive.openBox<ConversationModel>(AppKeys.chatConversationsBoxName);
|
|
var conversation = conversationBox.get(conversationId);
|
|
if (conversation != null) {
|
|
var updatedParticipants = List<ParticipantModel>.from(conversation.participants);
|
|
updatedParticipants.add(ParticipantModel.fromJson(participantData));
|
|
await conversationBox.put(conversationId, conversation.copyWith(participants: updatedParticipants));
|
|
}
|
|
} catch (e) {
|
|
throw Exception('Erreur lors de l\'ajout du participant: $e');
|
|
}
|
|
}
|
|
|
|
/// Retire un participant d'une conversation
|
|
Future<void> removeParticipant(String conversationId, String participantId) async {
|
|
try {
|
|
await _apiService.removeParticipant(conversationId, participantId);
|
|
|
|
// Mettre à jour la conversation dans Hive
|
|
var conversationBox = await Hive.openBox<ConversationModel>(AppKeys.chatConversationsBoxName);
|
|
var conversation = conversationBox.get(conversationId);
|
|
if (conversation != null) {
|
|
var updatedParticipants = List<ParticipantModel>.from(conversation.participants);
|
|
updatedParticipants.removeWhere((p) => p.id == participantId);
|
|
await conversationBox.put(conversationId, conversation.copyWith(participants: updatedParticipants));
|
|
}
|
|
} catch (e) {
|
|
throw Exception('Erreur lors du retrait du participant: $e');
|
|
}
|
|
}
|
|
|
|
/// Crée un utilisateur anonyme (pour Resalice)
|
|
Future<String> createAnonymousUser({String? name, String? email}) async {
|
|
try {
|
|
return await _apiService.createAnonymousUser(name: name, email: email);
|
|
} catch (e) {
|
|
throw Exception('Erreur lors de la création de l\'utilisateur anonyme: $e');
|
|
}
|
|
}
|
|
|
|
/// Convertit un utilisateur anonyme en utilisateur authentifié
|
|
Future<void> convertAnonymousToUser(String anonymousId, String userId) async {
|
|
try {
|
|
// Mettre à jour tous les messages de l'utilisateur anonyme
|
|
var messageBox = await Hive.openBox<MessageModel>(AppKeys.chatMessagesBoxName);
|
|
var messages = messageBox.values.where((m) => m.senderId == anonymousId).toList();
|
|
|
|
for (var message in messages) {
|
|
await messageBox.put(message.id, message.copyWith(
|
|
senderId: userId,
|
|
senderType: 'user',
|
|
));
|
|
}
|
|
} catch (e) {
|
|
throw Exception('Erreur lors de la conversion de l\'utilisateur: $e');
|
|
}
|
|
}
|
|
|
|
/// Récupère les annonces
|
|
Future<List<ConversationModel>> getAnnouncements({bool forceRefresh = false}) async {
|
|
try {
|
|
// Filtrer les conversations pour n'avoir que les annonces
|
|
var conversations = await getConversations(forceRefresh: forceRefresh);
|
|
return conversations.where((c) => c.type == 'announcement').toList();
|
|
} catch (e) {
|
|
throw Exception('Erreur lors de la récupération des annonces: $e');
|
|
}
|
|
}
|
|
|
|
/// Crée une nouvelle annonce
|
|
Future<ConversationModel> createAnnouncement(Map<String, dynamic> data) async {
|
|
try {
|
|
// Créer la conversation comme une annonce
|
|
data['type'] = 'announcement';
|
|
return await createConversation(data);
|
|
} catch (e) {
|
|
throw Exception('Erreur lors de la création de l\'annonce: $e');
|
|
}
|
|
}
|
|
|
|
/// Récupère les statistiques d'une annonce
|
|
Future<Map<String, dynamic>> getAnnouncementStats(String conversationId) async {
|
|
try {
|
|
return await _apiService.getAnnouncementStats(conversationId);
|
|
} catch (e) {
|
|
throw Exception('Erreur lors de la récupération des statistiques: $e');
|
|
}
|
|
}
|
|
|
|
/// Récupère les cibles d'audience disponibles
|
|
Future<List<Map<String, dynamic>>> getAvailableAudienceTargets() async {
|
|
try {
|
|
return await _apiService.getAvailableAudienceTargets();
|
|
} catch (e) {
|
|
throw Exception('Erreur lors de la récupération des cibles: $e');
|
|
}
|
|
}
|
|
|
|
/// Ajoute une cible d'audience
|
|
Future<void> addAudienceTarget(String conversationId, Map<String, dynamic> targetData) async {
|
|
try {
|
|
// L'ajout des cibles d'audience est géré lors de la création de l'annonce
|
|
// Mais on pourrait avoir besoin de modifier les cibles plus tard
|
|
throw UnimplementedError('Ajout de cible non encore implémenté');
|
|
} catch (e) {
|
|
throw Exception('Erreur lors de l\'ajout de cible: $e');
|
|
}
|
|
}
|
|
|
|
/// Retire une cible d'audience
|
|
Future<void> removeAudienceTarget(String conversationId, String targetId) async {
|
|
try {
|
|
// Le retrait des cibles d'audience est géré lors de la création de l'annonce
|
|
throw UnimplementedError('Retrait de cible non encore implémenté');
|
|
} catch (e) {
|
|
throw Exception('Erreur lors du retrait de cible: $e');
|
|
}
|
|
}
|
|
}
|