import 'dart:async'; import 'package:injectable/injectable.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:equatable/equatable.dart'; import '../../domain/entities/dashboard_entity.dart'; import '../../domain/usecases/get_dashboard_data.dart'; import '../../../../core/error/failures.dart'; import '../../../../core/websocket/websocket_service.dart'; import '../../../../core/utils/logger.dart'; part 'dashboard_event.dart'; part 'dashboard_state.dart'; @injectable class DashboardBloc extends Bloc { final GetDashboardData getDashboardData; final GetDashboardStats getDashboardStats; final GetRecentActivities getRecentActivities; final GetUpcomingEvents getUpcomingEvents; final WebSocketService webSocketService; StreamSubscription? _webSocketEventSubscription; StreamSubscription? _webSocketConnectionSubscription; DashboardBloc({ required this.getDashboardData, required this.getDashboardStats, required this.getRecentActivities, required this.getUpcomingEvents, required this.webSocketService, }) : super(DashboardInitial()) { on(_onLoadDashboardData); on(_onRefreshDashboardData); on(_onLoadDashboardStats); on(_onLoadRecentActivities); on(_onLoadUpcomingEvents); on(_onRefreshDashboardFromWebSocket); on(_onWebSocketConnectionChanged); // Initialiser WebSocket et écouter les events _initializeWebSocket(); } /// Initialise la connexion WebSocket et écoute les events void _initializeWebSocket() { // Connexion au WebSocket webSocketService.connect(); AppLogger.info('DashboardBloc: WebSocket initialisé'); // Écouter les events WebSocket _webSocketEventSubscription = webSocketService.eventStream.listen( (event) { try { AppLogger.info('DashboardBloc: Event WebSocket reçu - ${event.eventType}'); if (isClosed) return; // Dispatcher uniquement les events pertinents au dashboard if (event is DashboardStatsEvent) { add(RefreshDashboardFromWebSocket(event.data)); } else if (event is FinanceApprovalEvent) { add(RefreshDashboardFromWebSocket(event.data)); } else if (event is MemberEvent) { add(RefreshDashboardFromWebSocket(event.data)); } else if (event is ContributionEvent) { add(RefreshDashboardFromWebSocket(event.data)); } } catch (e, s) { AppLogger.error('DashboardBloc: erreur lors du traitement WebSocket event', error: e); } }, onError: (error) { AppLogger.error('DashboardBloc: Erreur WebSocket', error: error); }, ); // Écouter le statut de connexion WebSocket _webSocketConnectionSubscription = webSocketService.connectionStatusStream.listen( (isConnected) { AppLogger.info('DashboardBloc: WebSocket ${isConnected ? "connecté" : "déconnecté"}'); add(WebSocketConnectionChanged(isConnected)); }, ); } Future _onLoadDashboardData( LoadDashboardData event, Emitter emit, ) async { emit(DashboardLoading()); final result = await getDashboardData( GetDashboardDataParams( organizationId: event.organizationId, userId: event.userId, useGlobalDashboard: event.useGlobalDashboard, ), ); result.fold( (failure) { if (failure is NotFoundFailure) { emit(const DashboardMemberNotRegistered()); } else { emit(DashboardError(_mapFailureToMessage(failure))); } }, (dashboardData) => emit(DashboardLoaded(dashboardData)), ); } Future _onRefreshDashboardData( RefreshDashboardData event, Emitter emit, ) async { // Garde l'état actuel pendant le refresh if (state is DashboardLoaded) { emit(DashboardRefreshing((state as DashboardLoaded).dashboardData)); } else { emit(DashboardLoading()); } final result = await getDashboardData( GetDashboardDataParams( organizationId: event.organizationId, userId: event.userId, useGlobalDashboard: event.useGlobalDashboard, ), ); result.fold( (failure) => emit(DashboardError(_mapFailureToMessage(failure))), (dashboardData) => emit(DashboardLoaded(dashboardData)), ); } Future _onLoadDashboardStats( LoadDashboardStats event, Emitter emit, ) async { final result = await getDashboardStats( GetDashboardStatsParams( organizationId: event.organizationId, userId: event.userId, ), ); result.fold( (failure) => emit(DashboardError(_mapFailureToMessage(failure))), (stats) { if (state is DashboardLoaded) { final currentData = (state as DashboardLoaded).dashboardData; final updatedData = DashboardEntity( stats: stats, recentActivities: currentData.recentActivities, upcomingEvents: currentData.upcomingEvents, userPreferences: currentData.userPreferences, organizationId: currentData.organizationId, userId: currentData.userId, ); emit(DashboardLoaded(updatedData)); } }, ); } Future _onLoadRecentActivities( LoadRecentActivities event, Emitter emit, ) async { final result = await getRecentActivities( GetRecentActivitiesParams( organizationId: event.organizationId, userId: event.userId, limit: event.limit, ), ); result.fold( (failure) => emit(DashboardError(_mapFailureToMessage(failure))), (activities) { if (state is DashboardLoaded) { final currentData = (state as DashboardLoaded).dashboardData; final updatedData = DashboardEntity( stats: currentData.stats, recentActivities: activities, upcomingEvents: currentData.upcomingEvents, userPreferences: currentData.userPreferences, organizationId: currentData.organizationId, userId: currentData.userId, ); emit(DashboardLoaded(updatedData)); } }, ); } Future _onLoadUpcomingEvents( LoadUpcomingEvents event, Emitter emit, ) async { final result = await getUpcomingEvents( GetUpcomingEventsParams( organizationId: event.organizationId, userId: event.userId, limit: event.limit, ), ); result.fold( (failure) => emit(DashboardError(_mapFailureToMessage(failure))), (events) { if (state is DashboardLoaded) { final currentData = (state as DashboardLoaded).dashboardData; final updatedData = DashboardEntity( stats: currentData.stats, recentActivities: currentData.recentActivities, upcomingEvents: events, userPreferences: currentData.userPreferences, organizationId: currentData.organizationId, userId: currentData.userId, ); emit(DashboardLoaded(updatedData)); } }, ); } /// Rafraîchit le dashboard suite à un event WebSocket Future _onRefreshDashboardFromWebSocket( RefreshDashboardFromWebSocket event, Emitter emit, ) async { AppLogger.info('DashboardBloc: Rafraîchissement depuis WebSocket'); // Si le dashboard est chargé, on rafraîchit uniquement les stats // pour éviter de recharger toutes les données if (state is DashboardLoaded) { final currentData = (state as DashboardLoaded).dashboardData; // Rafraîchir les stats depuis le backend final result = await getDashboardStats( GetDashboardStatsParams( organizationId: currentData.organizationId, userId: currentData.userId, ), ); result.fold( (failure) { AppLogger.error('Erreur rafraîchissement stats WebSocket', error: failure); // Ne pas émettre d'erreur, garder les données actuelles }, (stats) { final updatedData = DashboardEntity( stats: stats, recentActivities: currentData.recentActivities, upcomingEvents: currentData.upcomingEvents, userPreferences: currentData.userPreferences, organizationId: currentData.organizationId, userId: currentData.userId, ); emit(DashboardLoaded(updatedData)); AppLogger.info('DashboardBloc: Stats rafraîchies depuis WebSocket'); }, ); } } /// Gère les changements de statut de connexion WebSocket void _onWebSocketConnectionChanged( WebSocketConnectionChanged event, Emitter emit, ) { // Pour l'instant, on log juste le statut // On pourrait ajouter un indicateur visuel dans l'UI plus tard if (event.isConnected) { AppLogger.info('DashboardBloc: WebSocket connecté - Temps réel actif'); } else { AppLogger.warning('DashboardBloc: WebSocket déconnecté - Reconnexion en cours...'); } } String _mapFailureToMessage(Failure failure) { if (failure is NetworkFailure) { return 'Pas de connexion internet. Vérifiez votre connexion.'; } if (failure is UnauthorizedFailure) { return 'Session expirée. Veuillez vous reconnecter.'; } if (failure is NotFoundFailure) { return failure.userFriendlyMessage ?? 'Ressource non trouvée.'; } return 'Erreur serveur. Veuillez réessayer.'; } @override Future close() { // Annuler les subscriptions WebSocket _webSocketEventSubscription?.cancel(); _webSocketConnectionSubscription?.cancel(); // Déconnecter le WebSocket webSocketService.disconnect(); AppLogger.info('DashboardBloc: Fermé et WebSocket déconnecté'); return super.close(); } }