fix(chat): Correction race condition + Implémentation TODOs
## Corrections Critiques ### Race Condition - Statuts de Messages - Fix : Les icônes de statut (✓, ✓✓, ✓✓ bleu) ne s'affichaient pas - Cause : WebSocket delivery confirmations arrivaient avant messages locaux - Solution : Pattern Optimistic UI dans chat_bloc.dart - Création message temporaire immédiate - Ajout à la liste AVANT requête HTTP - Remplacement par message serveur à la réponse - Fichier : lib/presentation/state_management/chat_bloc.dart ## Implémentation TODOs (13/21) ### Social (social_header_widget.dart) - ✅ Copier lien du post dans presse-papiers - ✅ Partage natif via Share.share() - ✅ Dialogue de signalement avec 5 raisons ### Partage (share_post_dialog.dart) - ✅ Interface sélection d'amis avec checkboxes - ✅ Partage externe via Share API ### Média (media_upload_service.dart) - ✅ Parsing JSON réponse backend - ✅ Méthode deleteMedia() pour suppression - ✅ Génération miniature vidéo ### Posts (create_post_dialog.dart, edit_post_dialog.dart) - ✅ Extraction URL depuis uploads - ✅ Documentation chargement médias ### Chat (conversations_screen.dart) - ✅ Navigation vers notifications - ✅ ConversationSearchDelegate pour recherche ## Nouveaux Fichiers ### Configuration - build-prod.ps1 : Script build production avec dart-define - lib/core/constants/env_config.dart : Gestion environnements ### Documentation - TODOS_IMPLEMENTED.md : Documentation complète TODOs ## Améliorations ### Architecture - Refactoring injection de dépendances - Amélioration routing et navigation - Optimisation providers (UserProvider, FriendsProvider) ### UI/UX - Amélioration thème et couleurs - Optimisation animations - Meilleure gestion erreurs ### Services - Configuration API avec env_config - Amélioration datasources (events, users) - Optimisation modèles de données
This commit is contained in:
580
lib/presentation/state_management/chat_bloc.dart
Normal file
580
lib/presentation/state_management/chat_bloc.dart
Normal file
@@ -0,0 +1,580 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
import '../../core/utils/app_logger.dart';
|
||||
import '../../domain/entities/chat_message.dart';
|
||||
import '../../domain/entities/conversation.dart';
|
||||
import '../../domain/repositories/chat_repository.dart';
|
||||
|
||||
/// Bloc pour la gestion des événements et états liés au chat.
|
||||
///
|
||||
/// Ce bloc gère:
|
||||
/// - Le chargement des conversations
|
||||
/// - Le chargement des messages d'une conversation
|
||||
/// - L'envoi de messages
|
||||
/// - Le marquage des messages comme lus
|
||||
/// - La suppression de messages et conversations
|
||||
/// - Le compteur de messages non lus
|
||||
class ChatBloc extends Bloc<ChatEvent, ChatState> {
|
||||
/// Constructeur avec injection du repository de chat.
|
||||
ChatBloc({required this.chatRepository}) : super(const ChatState()) {
|
||||
on<LoadConversations>(_onLoadConversations);
|
||||
on<LoadMessages>(_onLoadMessages);
|
||||
on<SendMessage>(_onSendMessage);
|
||||
on<MarkMessageAsRead>(_onMarkMessageAsRead);
|
||||
on<MarkMessageAsDelivered>(_onMarkMessageAsDelivered);
|
||||
on<MarkConversationAsRead>(_onMarkConversationAsRead);
|
||||
on<DeleteMessage>(_onDeleteMessage);
|
||||
on<DeleteConversation>(_onDeleteConversation);
|
||||
on<LoadUnreadCount>(_onLoadUnreadCount);
|
||||
on<AddMessageToConversation>(_onAddMessageToConversation);
|
||||
on<UpdateTypingStatus>(_onUpdateTypingStatus);
|
||||
}
|
||||
|
||||
final ChatRepository chatRepository;
|
||||
|
||||
void _log(String message) {
|
||||
AppLogger.d(message, tag: 'ChatBloc');
|
||||
}
|
||||
|
||||
/// Charge toutes les conversations d'un utilisateur.
|
||||
Future<void> _onLoadConversations(
|
||||
LoadConversations event,
|
||||
Emitter<ChatState> emit,
|
||||
) async {
|
||||
_log('Chargement des conversations pour ${event.userId}');
|
||||
emit(state.copyWith(conversationsStatus: ConversationsStatus.loading));
|
||||
|
||||
final result = await chatRepository.getConversations(event.userId);
|
||||
result.fold(
|
||||
(failure) {
|
||||
_log('Erreur: ${failure.message}');
|
||||
emit(state.copyWith(
|
||||
conversationsStatus: ConversationsStatus.error,
|
||||
errorMessage: failure.message,
|
||||
));
|
||||
},
|
||||
(conversations) {
|
||||
_log('${conversations.length} conversations chargées');
|
||||
emit(state.copyWith(
|
||||
conversationsStatus: ConversationsStatus.success,
|
||||
conversations: conversations,
|
||||
));
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// Charge les messages d'une conversation.
|
||||
Future<void> _onLoadMessages(
|
||||
LoadMessages event,
|
||||
Emitter<ChatState> emit,
|
||||
) async {
|
||||
_log('Chargement des messages de ${event.conversationId}');
|
||||
emit(state.copyWith(messagesStatus: MessagesStatus.loading));
|
||||
|
||||
final result = await chatRepository.getMessages(
|
||||
event.conversationId,
|
||||
page: event.page,
|
||||
size: event.size,
|
||||
);
|
||||
result.fold(
|
||||
(failure) {
|
||||
_log('Erreur: ${failure.message}');
|
||||
emit(state.copyWith(
|
||||
messagesStatus: MessagesStatus.error,
|
||||
errorMessage: failure.message,
|
||||
));
|
||||
},
|
||||
(messages) {
|
||||
_log('${messages.length} messages chargés');
|
||||
emit(state.copyWith(
|
||||
messagesStatus: MessagesStatus.success,
|
||||
messages: messages,
|
||||
currentConversationId: event.conversationId,
|
||||
));
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// Envoie un nouveau message.
|
||||
Future<void> _onSendMessage(
|
||||
SendMessage event,
|
||||
Emitter<ChatState> emit,
|
||||
) async {
|
||||
_log('Envoi d\'un message');
|
||||
|
||||
// 🚀 OPTIMISTIC UI: Créer un message temporaire IMMÉDIATEMENT
|
||||
final tempId = 'temp_${DateTime.now().millisecondsSinceEpoch}';
|
||||
final tempMessage = ChatMessage(
|
||||
id: tempId,
|
||||
conversationId: state.currentConversationId ?? '',
|
||||
senderId: event.senderId,
|
||||
senderFirstName: '', // Sera mis à jour par le serveur
|
||||
senderLastName: '',
|
||||
senderProfileImageUrl: '',
|
||||
content: event.content,
|
||||
timestamp: DateTime.now(),
|
||||
isRead: false,
|
||||
isDelivered: false, // Pas encore délivré
|
||||
attachmentUrl: event.mediaUrl ?? '',
|
||||
attachmentType: event.messageType == 'image' ? AttachmentType.image :
|
||||
event.messageType == 'video' ? AttachmentType.video :
|
||||
event.messageType == 'audio' ? AttachmentType.audio :
|
||||
event.messageType == 'file' ? AttachmentType.file :
|
||||
AttachmentType.none,
|
||||
);
|
||||
|
||||
AppLogger.d('Message optimiste créé avec ID temporaire: $tempId', tag: 'ChatBloc');
|
||||
|
||||
// Ajouter immédiatement à la liste (avant la requête HTTP)
|
||||
final optimisticMessages = List<ChatMessage>.from(state.messages);
|
||||
optimisticMessages.insert(0, tempMessage);
|
||||
|
||||
emit(state.copyWith(
|
||||
sendMessageStatus: SendMessageStatus.sending,
|
||||
messages: optimisticMessages,
|
||||
));
|
||||
|
||||
// Maintenant envoyer au serveur
|
||||
final result = await chatRepository.sendMessage(
|
||||
senderId: event.senderId,
|
||||
recipientId: event.recipientId,
|
||||
content: event.content,
|
||||
messageType: event.messageType,
|
||||
mediaUrl: event.mediaUrl,
|
||||
);
|
||||
|
||||
result.fold(
|
||||
(failure) {
|
||||
_log('Erreur: ${failure.message}');
|
||||
AppLogger.e('Échec envoi - Retrait du message temporaire', tag: 'ChatBloc');
|
||||
|
||||
// Retirer le message temporaire en cas d'erreur
|
||||
final revertedMessages = state.messages
|
||||
.where((msg) => msg.id != tempId)
|
||||
.toList();
|
||||
|
||||
emit(state.copyWith(
|
||||
sendMessageStatus: SendMessageStatus.error,
|
||||
errorMessage: failure.message,
|
||||
messages: revertedMessages,
|
||||
));
|
||||
},
|
||||
(message) {
|
||||
_log('Message envoyé avec succès');
|
||||
AppLogger.d('Message envoyé - ID réel: ${message.id}, isDelivered: ${message.isDelivered}, isRead: ${message.isRead}', tag: 'ChatBloc');
|
||||
|
||||
// Remplacer le message temporaire par le vrai message du serveur
|
||||
final updatedMessages = state.messages.map((msg) {
|
||||
if (msg.id == tempId) {
|
||||
AppLogger.d('Remplacement du message temporaire par le message réel', tag: 'ChatBloc');
|
||||
return message;
|
||||
}
|
||||
return msg;
|
||||
}).toList();
|
||||
|
||||
emit(state.copyWith(
|
||||
sendMessageStatus: SendMessageStatus.success,
|
||||
messages: updatedMessages,
|
||||
));
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// Marque un message comme lu.
|
||||
Future<void> _onMarkMessageAsRead(
|
||||
MarkMessageAsRead event,
|
||||
Emitter<ChatState> emit,
|
||||
) async {
|
||||
_log('Marquage du message ${event.messageId} comme lu');
|
||||
|
||||
final result = await chatRepository.markMessageAsRead(event.messageId);
|
||||
result.fold(
|
||||
(failure) => _log('Erreur: ${failure.message}'),
|
||||
(_) => _log('Message marqué comme lu'),
|
||||
);
|
||||
}
|
||||
|
||||
/// Marque un message comme délivré (reçu par le destinataire).
|
||||
void _onMarkMessageAsDelivered(
|
||||
MarkMessageAsDelivered event,
|
||||
Emitter<ChatState> emit,
|
||||
) {
|
||||
_log('Marquage du message ${event.messageId} comme délivré');
|
||||
AppLogger.d('_onMarkMessageAsDelivered appelé pour ${event.messageId}', tag: 'ChatBloc');
|
||||
AppLogger.d('Nombre de messages actuels: ${state.messages.length}', tag: 'ChatBloc');
|
||||
|
||||
// Mettre à jour le message dans la liste des messages
|
||||
final updatedMessages = state.messages.map((msg) {
|
||||
if (msg.id == event.messageId) {
|
||||
AppLogger.d('Message trouvé! Avant: isDelivered=${msg.isDelivered}, Après: isDelivered=true', tag: 'ChatBloc');
|
||||
return msg.copyWith(isDelivered: true);
|
||||
}
|
||||
return msg;
|
||||
}).toList();
|
||||
|
||||
AppLogger.d('Émission du nouveau state avec ${updatedMessages.length} messages', tag: 'ChatBloc');
|
||||
emit(state.copyWith(messages: updatedMessages));
|
||||
}
|
||||
|
||||
/// Marque tous les messages d'une conversation comme lus.
|
||||
Future<void> _onMarkConversationAsRead(
|
||||
MarkConversationAsRead event,
|
||||
Emitter<ChatState> emit,
|
||||
) async {
|
||||
_log('Marquage de la conversation ${event.conversationId} comme lue');
|
||||
|
||||
final result = await chatRepository.markConversationAsRead(
|
||||
event.conversationId,
|
||||
event.userId,
|
||||
);
|
||||
result.fold(
|
||||
(failure) => _log('Erreur: ${failure.message}'),
|
||||
(_) {
|
||||
_log('Conversation marquée comme lue');
|
||||
// Mettre à jour le compteur de non lus dans la conversation
|
||||
final updatedConversations = state.conversations.map((conv) {
|
||||
if (conv.id == event.conversationId) {
|
||||
return Conversation(
|
||||
id: conv.id,
|
||||
participantId: conv.participantId,
|
||||
participantFirstName: conv.participantFirstName,
|
||||
participantLastName: conv.participantLastName,
|
||||
participantProfileImageUrl: conv.participantProfileImageUrl,
|
||||
lastMessage: conv.lastMessage,
|
||||
lastMessageTimestamp: conv.lastMessageTimestamp,
|
||||
unreadCount: 0,
|
||||
isTyping: conv.isTyping,
|
||||
);
|
||||
}
|
||||
return conv;
|
||||
}).toList();
|
||||
|
||||
emit(state.copyWith(conversations: updatedConversations));
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// Supprime un message.
|
||||
Future<void> _onDeleteMessage(
|
||||
DeleteMessage event,
|
||||
Emitter<ChatState> emit,
|
||||
) async {
|
||||
_log('Suppression du message ${event.messageId}');
|
||||
|
||||
final result = await chatRepository.deleteMessage(event.messageId);
|
||||
result.fold(
|
||||
(failure) {
|
||||
_log('Erreur: ${failure.message}');
|
||||
emit(state.copyWith(errorMessage: failure.message));
|
||||
},
|
||||
(_) {
|
||||
_log('Message supprimé');
|
||||
// Retirer le message de la liste
|
||||
final updatedMessages = state.messages
|
||||
.where((msg) => msg.id != event.messageId)
|
||||
.toList();
|
||||
|
||||
emit(state.copyWith(messages: updatedMessages));
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// Supprime une conversation.
|
||||
Future<void> _onDeleteConversation(
|
||||
DeleteConversation event,
|
||||
Emitter<ChatState> emit,
|
||||
) async {
|
||||
_log('Suppression de la conversation ${event.conversationId}');
|
||||
|
||||
final result = await chatRepository.deleteConversation(event.conversationId);
|
||||
result.fold(
|
||||
(failure) {
|
||||
_log('Erreur: ${failure.message}');
|
||||
emit(state.copyWith(errorMessage: failure.message));
|
||||
},
|
||||
(_) {
|
||||
_log('Conversation supprimée');
|
||||
// Retirer la conversation de la liste
|
||||
final updatedConversations = state.conversations
|
||||
.where((conv) => conv.id != event.conversationId)
|
||||
.toList();
|
||||
|
||||
emit(state.copyWith(conversations: updatedConversations));
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// Charge le nombre de messages non lus.
|
||||
Future<void> _onLoadUnreadCount(
|
||||
LoadUnreadCount event,
|
||||
Emitter<ChatState> emit,
|
||||
) async {
|
||||
_log('Chargement du nombre de messages non lus');
|
||||
|
||||
final result = await chatRepository.getUnreadMessagesCount(event.userId);
|
||||
result.fold(
|
||||
(failure) => _log('Erreur: ${failure.message}'),
|
||||
(count) {
|
||||
_log('$count messages non lus');
|
||||
emit(state.copyWith(unreadCount: count));
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// Ajoute un message reçu via WebSocket à la conversation.
|
||||
void _onAddMessageToConversation(
|
||||
AddMessageToConversation event,
|
||||
Emitter<ChatState> emit,
|
||||
) {
|
||||
_log('Ajout d\'un message reçu via WebSocket');
|
||||
|
||||
final updatedMessages = List<ChatMessage>.from(state.messages);
|
||||
updatedMessages.insert(0, event.message);
|
||||
|
||||
emit(state.copyWith(messages: updatedMessages));
|
||||
}
|
||||
|
||||
/// Met à jour le statut de frappe d'un utilisateur.
|
||||
void _onUpdateTypingStatus(
|
||||
UpdateTypingStatus event,
|
||||
Emitter<ChatState> emit,
|
||||
) {
|
||||
_log('Mise à jour du statut de frappe pour ${event.conversationId}');
|
||||
|
||||
final updatedConversations = state.conversations.map((conv) {
|
||||
if (conv.id == event.conversationId) {
|
||||
return Conversation(
|
||||
id: conv.id,
|
||||
participantId: conv.participantId,
|
||||
participantFirstName: conv.participantFirstName,
|
||||
participantLastName: conv.participantLastName,
|
||||
participantProfileImageUrl: conv.participantProfileImageUrl,
|
||||
lastMessage: conv.lastMessage,
|
||||
lastMessageTimestamp: conv.lastMessageTimestamp,
|
||||
unreadCount: conv.unreadCount,
|
||||
isTyping: event.isTyping,
|
||||
);
|
||||
}
|
||||
return conv;
|
||||
}).toList();
|
||||
|
||||
emit(state.copyWith(conversations: updatedConversations));
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// ÉVÉNEMENTS
|
||||
// ============================================================================
|
||||
|
||||
/// Classe de base pour tous les événements du chat.
|
||||
abstract class ChatEvent extends Equatable {
|
||||
const ChatEvent();
|
||||
|
||||
@override
|
||||
List<Object?> get props => [];
|
||||
}
|
||||
|
||||
/// Événement pour charger les conversations d'un utilisateur.
|
||||
class LoadConversations extends ChatEvent {
|
||||
const LoadConversations(this.userId);
|
||||
|
||||
final String userId;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [userId];
|
||||
}
|
||||
|
||||
/// Événement pour charger les messages d'une conversation.
|
||||
class LoadMessages extends ChatEvent {
|
||||
const LoadMessages({
|
||||
required this.conversationId,
|
||||
this.page = 0,
|
||||
this.size = 50,
|
||||
});
|
||||
|
||||
final String conversationId;
|
||||
final int page;
|
||||
final int size;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [conversationId, page, size];
|
||||
}
|
||||
|
||||
/// Événement pour envoyer un message.
|
||||
class SendMessage extends ChatEvent {
|
||||
const SendMessage({
|
||||
required this.senderId,
|
||||
required this.recipientId,
|
||||
required this.content,
|
||||
this.messageType,
|
||||
this.mediaUrl,
|
||||
});
|
||||
|
||||
final String senderId;
|
||||
final String recipientId;
|
||||
final String content;
|
||||
final String? messageType;
|
||||
final String? mediaUrl;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [senderId, recipientId, content, messageType, mediaUrl];
|
||||
}
|
||||
|
||||
/// Événement pour marquer un message comme lu.
|
||||
class MarkMessageAsRead extends ChatEvent {
|
||||
const MarkMessageAsRead(this.messageId);
|
||||
|
||||
final String messageId;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [messageId];
|
||||
}
|
||||
|
||||
/// Événement pour marquer tous les messages d'une conversation comme lus.
|
||||
class MarkConversationAsRead extends ChatEvent {
|
||||
const MarkConversationAsRead({
|
||||
required this.conversationId,
|
||||
required this.userId,
|
||||
});
|
||||
|
||||
final String conversationId;
|
||||
final String userId;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [conversationId, userId];
|
||||
}
|
||||
|
||||
/// Événement pour supprimer un message.
|
||||
class DeleteMessage extends ChatEvent {
|
||||
const DeleteMessage(this.messageId);
|
||||
|
||||
final String messageId;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [messageId];
|
||||
}
|
||||
|
||||
/// Événement pour supprimer une conversation.
|
||||
class DeleteConversation extends ChatEvent {
|
||||
const DeleteConversation(this.conversationId);
|
||||
|
||||
final String conversationId;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [conversationId];
|
||||
}
|
||||
|
||||
/// Événement pour charger le nombre de messages non lus.
|
||||
class LoadUnreadCount extends ChatEvent {
|
||||
const LoadUnreadCount(this.userId);
|
||||
|
||||
final String userId;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [userId];
|
||||
}
|
||||
|
||||
/// Événement pour ajouter un message reçu via WebSocket.
|
||||
class AddMessageToConversation extends ChatEvent {
|
||||
const AddMessageToConversation(this.message);
|
||||
|
||||
final ChatMessage message;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [message];
|
||||
}
|
||||
|
||||
/// Événement pour marquer un message comme délivré.
|
||||
class MarkMessageAsDelivered extends ChatEvent {
|
||||
const MarkMessageAsDelivered(this.messageId);
|
||||
|
||||
final String messageId;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [messageId];
|
||||
}
|
||||
|
||||
/// Événement pour mettre à jour le statut de frappe.
|
||||
class UpdateTypingStatus extends ChatEvent {
|
||||
const UpdateTypingStatus({
|
||||
required this.conversationId,
|
||||
required this.isTyping,
|
||||
});
|
||||
|
||||
final String conversationId;
|
||||
final bool isTyping;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [conversationId, isTyping];
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// ÉTATS
|
||||
// ============================================================================
|
||||
|
||||
/// Énumération des statuts de chargement des conversations.
|
||||
enum ConversationsStatus { initial, loading, success, error }
|
||||
|
||||
/// Énumération des statuts de chargement des messages.
|
||||
enum MessagesStatus { initial, loading, success, error }
|
||||
|
||||
/// Énumération des statuts d'envoi de message.
|
||||
enum SendMessageStatus { initial, sending, success, error }
|
||||
|
||||
/// État du ChatBloc.
|
||||
class ChatState extends Equatable {
|
||||
const ChatState({
|
||||
this.conversationsStatus = ConversationsStatus.initial,
|
||||
this.messagesStatus = MessagesStatus.initial,
|
||||
this.sendMessageStatus = SendMessageStatus.initial,
|
||||
this.conversations = const [],
|
||||
this.messages = const [],
|
||||
this.currentConversationId,
|
||||
this.unreadCount = 0,
|
||||
this.errorMessage,
|
||||
});
|
||||
|
||||
final ConversationsStatus conversationsStatus;
|
||||
final MessagesStatus messagesStatus;
|
||||
final SendMessageStatus sendMessageStatus;
|
||||
final List<Conversation> conversations;
|
||||
final List<ChatMessage> messages;
|
||||
final String? currentConversationId;
|
||||
final int unreadCount;
|
||||
final String? errorMessage;
|
||||
|
||||
ChatState copyWith({
|
||||
ConversationsStatus? conversationsStatus,
|
||||
MessagesStatus? messagesStatus,
|
||||
SendMessageStatus? sendMessageStatus,
|
||||
List<Conversation>? conversations,
|
||||
List<ChatMessage>? messages,
|
||||
String? currentConversationId,
|
||||
int? unreadCount,
|
||||
String? errorMessage,
|
||||
}) {
|
||||
return ChatState(
|
||||
conversationsStatus: conversationsStatus ?? this.conversationsStatus,
|
||||
messagesStatus: messagesStatus ?? this.messagesStatus,
|
||||
sendMessageStatus: sendMessageStatus ?? this.sendMessageStatus,
|
||||
conversations: conversations ?? this.conversations,
|
||||
messages: messages ?? this.messages,
|
||||
currentConversationId: currentConversationId ?? this.currentConversationId,
|
||||
unreadCount: unreadCount ?? this.unreadCount,
|
||||
errorMessage: errorMessage,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
List<Object?> get props => [
|
||||
conversationsStatus,
|
||||
messagesStatus,
|
||||
sendMessageStatus,
|
||||
conversations,
|
||||
messages,
|
||||
currentConversationId,
|
||||
unreadCount,
|
||||
errorMessage,
|
||||
];
|
||||
}
|
||||
@@ -1,85 +1,127 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:afterwork/data/models/event_model.dart';
|
||||
import 'package:afterwork/data/datasources/event_remote_data_source.dart';
|
||||
|
||||
import '../../core/utils/app_logger.dart';
|
||||
import '../../data/datasources/event_remote_data_source.dart';
|
||||
import '../../data/models/event_model.dart';
|
||||
|
||||
// Déclaration des événements
|
||||
@immutable
|
||||
abstract class EventEvent {}
|
||||
abstract class EventEvent extends Equatable {
|
||||
const EventEvent();
|
||||
|
||||
@override
|
||||
List<Object?> get props => [];
|
||||
}
|
||||
|
||||
class LoadEvents extends EventEvent {
|
||||
const LoadEvents(this.userId);
|
||||
|
||||
final String userId;
|
||||
LoadEvents(this.userId);
|
||||
|
||||
@override
|
||||
List<Object?> get props => [userId];
|
||||
}
|
||||
|
||||
class AddEvent extends EventEvent {
|
||||
const AddEvent(this.event);
|
||||
|
||||
final EventModel event;
|
||||
|
||||
AddEvent(this.event);
|
||||
@override
|
||||
List<Object?> get props => [event];
|
||||
}
|
||||
|
||||
class CloseEvent extends EventEvent {
|
||||
const CloseEvent(this.eventId);
|
||||
|
||||
final String eventId;
|
||||
|
||||
CloseEvent(this.eventId);
|
||||
@override
|
||||
List<Object?> get props => [eventId];
|
||||
}
|
||||
|
||||
class ReopenEvent extends EventEvent {
|
||||
const ReopenEvent(this.eventId);
|
||||
|
||||
final String eventId;
|
||||
|
||||
ReopenEvent(this.eventId);
|
||||
@override
|
||||
List<Object?> get props => [eventId];
|
||||
}
|
||||
|
||||
// Déclaration des états
|
||||
@immutable
|
||||
abstract class EventState {}
|
||||
abstract class EventState extends Equatable {
|
||||
const EventState();
|
||||
|
||||
class EventInitial extends EventState {}
|
||||
@override
|
||||
List<Object?> get props => [];
|
||||
}
|
||||
|
||||
class EventLoading extends EventState {}
|
||||
class EventInitial extends EventState {
|
||||
const EventInitial();
|
||||
|
||||
@override
|
||||
List<Object?> get props => [];
|
||||
}
|
||||
|
||||
class EventLoading extends EventState {
|
||||
const EventLoading();
|
||||
|
||||
@override
|
||||
List<Object?> get props => [];
|
||||
}
|
||||
|
||||
class EventLoaded extends EventState {
|
||||
const EventLoaded(this.events);
|
||||
|
||||
final List<EventModel> events;
|
||||
|
||||
EventLoaded(this.events);
|
||||
@override
|
||||
List<Object?> get props => [events];
|
||||
}
|
||||
|
||||
class EventError extends EventState {
|
||||
const EventError(this.message);
|
||||
|
||||
final String message;
|
||||
|
||||
EventError(this.message);
|
||||
@override
|
||||
List<Object?> get props => [message];
|
||||
}
|
||||
|
||||
// Bloc pour la gestion des événements
|
||||
class EventBloc extends Bloc<EventEvent, EventState> {
|
||||
final EventRemoteDataSource remoteDataSource;
|
||||
|
||||
EventBloc({required this.remoteDataSource}) : super(EventInitial()) {
|
||||
EventBloc({required this.remoteDataSource}) : super(const EventInitial()) {
|
||||
on<LoadEvents>(_onLoadEvents);
|
||||
on<AddEvent>(_onAddEvent);
|
||||
on<CloseEvent>(_onCloseEvent);
|
||||
on<ReopenEvent>(_onReopenEvent);
|
||||
on<RemoveEvent>(_onRemoveEvent); // Ajout du gestionnaire pour RemoveEvent
|
||||
}
|
||||
final EventRemoteDataSource remoteDataSource;
|
||||
|
||||
// Gestion du chargement des événements
|
||||
Future<void> _onLoadEvents(LoadEvents event, Emitter<EventState> emit) async {
|
||||
emit(EventLoading());
|
||||
print('[LOG] Début du chargement des événements pour l\'utilisateur ${event.userId}');
|
||||
emit(const EventLoading());
|
||||
AppLogger.i('Début du chargement des événements pour l\'utilisateur ${event.userId}', tag: 'EventBloc');
|
||||
|
||||
try {
|
||||
final events = await remoteDataSource.getEventsCreatedByUserAndFriends(event.userId);
|
||||
print('[LOG] Événements chargés: ${events.length} éléments récupérés.');
|
||||
AppLogger.i('Événements chargés: ${events.length} éléments récupérés.', tag: 'EventBloc');
|
||||
emit(EventLoaded(events));
|
||||
} catch (e) {
|
||||
print('[ERROR] Erreur lors du chargement des événements: $e');
|
||||
} catch (e, stackTrace) {
|
||||
AppLogger.e('Erreur lors du chargement des événements', error: e, stackTrace: stackTrace, tag: 'EventBloc');
|
||||
emit(EventError('Erreur lors du chargement des événements.'));
|
||||
}
|
||||
}
|
||||
|
||||
// Gestion de l'ajout d'un nouvel événement
|
||||
Future<void> _onAddEvent(AddEvent event, Emitter<EventState> emit) async {
|
||||
emit(EventLoading());
|
||||
emit(const EventLoading());
|
||||
try {
|
||||
await remoteDataSource.createEvent(event.event);
|
||||
final events = await remoteDataSource.getAllEvents();
|
||||
@@ -91,65 +133,83 @@ class EventBloc extends Bloc<EventEvent, EventState> {
|
||||
|
||||
// Gestion de la fermeture d'un événement
|
||||
Future<void> _onCloseEvent(CloseEvent event, Emitter<EventState> emit) async {
|
||||
emit(EventLoading()); // Affiche le chargement
|
||||
final currentState = state; // Sauvegarder l'état actuel avant d'émettre EventLoading
|
||||
emit(const EventLoading()); // Affiche le chargement
|
||||
try {
|
||||
await remoteDataSource.closeEvent(event.eventId);
|
||||
|
||||
// Mise à jour de l'événement spécifique dans l'état
|
||||
if (state is EventLoaded) {
|
||||
final updatedEvents = List<EventModel>.from((state as EventLoaded).events);
|
||||
final updatedEvent = updatedEvents.firstWhere((e) => e.id == event.eventId);
|
||||
updatedEvent.status = 'fermé'; // Modifier l'état de l'événement localement
|
||||
if (currentState is EventLoaded) {
|
||||
final updatedEvents = List<EventModel>.from((currentState as EventLoaded).events);
|
||||
try {
|
||||
final updatedEvent = updatedEvents.firstWhere((e) => e.id == event.eventId);
|
||||
updatedEvent.status = 'fermé'; // Modifier l'état de l'événement localement
|
||||
|
||||
// Émettre un nouvel état avec l'événement mis à jour
|
||||
emit(EventLoaded(updatedEvents));
|
||||
print('Événement fermé et mis à jour localement.');
|
||||
// Émettre un nouvel état avec l'événement mis à jour
|
||||
emit(EventLoaded(updatedEvents));
|
||||
AppLogger.i('Événement fermé et mis à jour localement.', tag: 'EventBloc');
|
||||
} catch (_) {
|
||||
// Événement non trouvé, restaurer l'état précédent
|
||||
emit(currentState);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
} catch (e, stackTrace) {
|
||||
AppLogger.e('Erreur lors de la fermeture de l\'événement', error: e, stackTrace: stackTrace, tag: 'EventBloc');
|
||||
emit(EventError('Erreur lors de la fermeture de l\'événement.'));
|
||||
print('Erreur lors de la fermeture de l\'événement : $e');
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _onReopenEvent(ReopenEvent event, Emitter<EventState> emit) async {
|
||||
emit(EventLoading()); // Affiche le chargement
|
||||
final currentState = state; // Sauvegarder l'état actuel avant d'émettre EventLoading
|
||||
emit(const EventLoading()); // Affiche le chargement
|
||||
try {
|
||||
// Appel au service backend pour réouvrir l'événement
|
||||
await remoteDataSource.reopenEvent(event.eventId);
|
||||
|
||||
// Mise à jour de l'événement spécifique dans l'état
|
||||
if (state is EventLoaded) {
|
||||
final updatedEvents = List<EventModel>.from((state as EventLoaded).events);
|
||||
final updatedEvent = updatedEvents.firstWhere((e) => e.id == event.eventId);
|
||||
if (currentState is EventLoaded) {
|
||||
final updatedEvents = List<EventModel>.from((currentState as EventLoaded).events);
|
||||
try {
|
||||
final updatedEvent = updatedEvents.firstWhere((e) => e.id == event.eventId);
|
||||
|
||||
// Mise à jour du statut local de l'événement
|
||||
updatedEvent.status = 'ouvert';
|
||||
// Mise à jour du statut local de l'événement
|
||||
updatedEvent.status = 'ouvert';
|
||||
|
||||
// Émettre un nouvel état avec l'événement mis à jour
|
||||
emit(EventLoaded(updatedEvents));
|
||||
print('Événement réouvert et mis à jour localement.');
|
||||
// Émettre un nouvel état avec l'événement mis à jour
|
||||
emit(EventLoaded(updatedEvents));
|
||||
AppLogger.i('Événement réouvert et mis à jour localement.', tag: 'EventBloc');
|
||||
} catch (_) {
|
||||
// Événement non trouvé, restaurer l'état précédent
|
||||
emit(currentState);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
} catch (e, stackTrace) {
|
||||
// En cas d'erreur, émettre un état d'erreur
|
||||
AppLogger.e('Erreur lors de la réouverture de l\'événement', error: e, stackTrace: stackTrace, tag: 'EventBloc');
|
||||
emit(EventError('Erreur lors de la réouverture de l\'événement.'));
|
||||
print('Erreur lors de la réouverture de l\'événement : $e');
|
||||
}
|
||||
}
|
||||
|
||||
// Gestion de la suppression locale d'un événement
|
||||
Future<void> _onRemoveEvent(RemoveEvent event, Emitter<EventState> emit) async {
|
||||
if (state is EventLoaded) {
|
||||
final currentState = state; // Sauvegarder l'état actuel
|
||||
if (currentState is EventLoaded) {
|
||||
// Supprimer l'événement de la liste locale sans recharger tout
|
||||
final List<EventModel> updatedEvents = List.from((state as EventLoaded).events)
|
||||
final List<EventModel> updatedEvents = List.from((currentState as EventLoaded).events)
|
||||
..removeWhere((e) => e.id == event.eventId);
|
||||
// Toujours émettre le nouvel état, même si la liste est identique
|
||||
// (car removeWhere peut ne rien faire si l'événement n'existe pas)
|
||||
emit(EventLoaded(updatedEvents));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class RemoveEvent extends EventEvent {
|
||||
const RemoveEvent(this.eventId);
|
||||
|
||||
final String eventId;
|
||||
|
||||
RemoveEvent(this.eventId);
|
||||
@override
|
||||
List<Object?> get props => [eventId];
|
||||
}
|
||||
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import 'package:afterwork/domain/entities/user.dart';
|
||||
import 'package:afterwork/domain/usecases/get_user.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
import '../../domain/entities/user.dart';
|
||||
import '../../domain/usecases/get_user.dart';
|
||||
|
||||
/// Bloc pour la gestion des événements et états liés à l'utilisateur.
|
||||
class UserBloc extends Bloc<UserEvent, UserState> {
|
||||
final GetUser getUser;
|
||||
|
||||
/// Constructeur avec injection du cas d'utilisation `GetUser`.
|
||||
UserBloc({required this.getUser}) : super(UserInitial());
|
||||
final GetUser getUser;
|
||||
|
||||
@override
|
||||
Stream<UserState> mapEventToState(UserEvent event) async* {
|
||||
@@ -28,9 +29,9 @@ abstract class UserEvent {}
|
||||
|
||||
/// Événement pour récupérer un utilisateur par son ID.
|
||||
class GetUserById extends UserEvent {
|
||||
final String id;
|
||||
|
||||
GetUserById(this.id);
|
||||
final String id;
|
||||
}
|
||||
|
||||
/// Classe abstraite représentant les états possibles du BLoC utilisateur.
|
||||
@@ -44,9 +45,9 @@ class UserLoading extends UserState {}
|
||||
|
||||
/// État indiquant que les données utilisateur ont été chargées avec succès.
|
||||
class UserLoaded extends UserState {
|
||||
final User user;
|
||||
|
||||
UserLoaded({required this.user});
|
||||
final User user;
|
||||
}
|
||||
|
||||
/// État indiquant qu'une erreur est survenue lors de la récupération des données utilisateur.
|
||||
|
||||
Reference in New Issue
Block a user