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:
dahoud
2026-01-10 10:43:17 +00:00
parent 06031b01f2
commit 92612abbd7
321 changed files with 43137 additions and 4285 deletions

View 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,
];
}

View File

@@ -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];
}

View File

@@ -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.