Files
unionflow-mobile-apps/lib/features/events/bloc/evenements_bloc.dart
dahoud 70cbd1c873 fix(mobile): URL changement mdp corrigée + v3.0 — multi-org, AppAuth, sécurité prod
Auth:
- profile_repository.dart: /api/auth/change-password → /api/membres/auth/change-password

Multi-org (Phase 3):
- OrgSelectorPage, OrgSwitcherBloc, OrgSwitcherEntry
- org_context_service.dart: headers X-Active-Organisation-Id + X-Active-Role

Navigation:
- MorePage: navigation conditionnelle par typeOrganisation
- Suppression adaptive_navigation (remplacé par main_navigation_layout)

Auth AppAuth:
- keycloak_webview_auth_service: fixes AppAuth Android
- AuthBloc: gestion REAUTH_REQUIS + premierLoginComplet

Onboarding:
- Nouveaux états: payment_method_page, onboarding_shared_widgets
- SouscriptionStatusModel mis à jour StatutValidationSouscription

Android:
- build.gradle: ProGuard/R8, network_security_config
- Gradle wrapper mis à jour
2026-04-07 20:56:03 +00:00

459 lines
14 KiB
Dart

/// BLoC pour la gestion des événements
library evenements_bloc;
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:dio/dio.dart';
import 'package:injectable/injectable.dart';
import 'evenements_event.dart';
import 'evenements_state.dart';
import '../domain/usecases/get_events.dart';
import '../domain/usecases/get_event_by_id.dart';
import '../domain/usecases/create_event.dart' as uc;
import '../domain/usecases/update_event.dart' as uc;
import '../domain/usecases/delete_event.dart' as uc;
import '../domain/usecases/register_for_event.dart';
import '../domain/usecases/cancel_registration.dart';
import '../domain/usecases/get_event_participants.dart';
import '../domain/repositories/evenement_repository.dart';
/// BLoC pour la gestion des événements (Clean Architecture)
@injectable
class EvenementsBloc extends Bloc<EvenementsEvent, EvenementsState> {
final GetEvents _getEvents;
final GetEventById _getEventById;
final uc.CreateEvent _createEvent;
final uc.UpdateEvent _updateEvent;
final uc.DeleteEvent _deleteEvent;
final RegisterForEvent _registerForEvent;
final CancelRegistration _cancelRegistration;
final GetEventParticipants _getEventParticipants;
final IEvenementRepository _repository; // Pour méthodes non-couvertes par use cases
EvenementsBloc(
this._getEvents,
this._getEventById,
this._createEvent,
this._updateEvent,
this._deleteEvent,
this._registerForEvent,
this._cancelRegistration,
this._getEventParticipants,
this._repository,
) : super(const EvenementsInitial()) {
on<LoadEvenements>(_onLoadEvenements);
on<LoadEvenementById>(_onLoadEvenementById);
on<CreateEvenement>(_onCreateEvenement);
on<UpdateEvenement>(_onUpdateEvenement);
on<DeleteEvenement>(_onDeleteEvenement);
on<LoadEvenementsAVenir>(_onLoadEvenementsAVenir);
on<LoadEvenementsEnCours>(_onLoadEvenementsEnCours);
on<LoadEvenementsPasses>(_onLoadEvenementsPasses);
on<InscrireEvenement>(_onInscrireEvenement);
on<DesinscrireEvenement>(_onDesinscrireEvenement);
on<LoadParticipants>(_onLoadParticipants);
on<LoadEvenementsStats>(_onLoadEvenementsStats);
}
/// Charge la liste des événements
Future<void> _onLoadEvenements(
LoadEvenements event,
Emitter<EvenementsState> emit,
) async {
try {
if (event.refresh && state is EvenementsLoaded) {
final currentState = state as EvenementsLoaded;
emit(EvenementsRefreshing(currentState.evenements));
} else {
emit(const EvenementsLoading());
}
final result = await _getEvents(
page: event.page,
size: event.size,
recherche: event.recherche,
);
emit(EvenementsLoaded(
evenements: result.evenements,
total: result.total,
page: result.page,
size: result.size,
totalPages: result.totalPages,
));
} on DioException catch (e) {
_emitDioError(e, emit);
} catch (e) {
if (e is DioException && e.type == DioExceptionType.cancel) return;
emit(EvenementsError(
message: 'Erreur lors du chargement des événements. Veuillez réessayer.',
error: e,
));
}
}
/// Charge un événement par ID
Future<void> _onLoadEvenementById(
LoadEvenementById event,
Emitter<EvenementsState> emit,
) async {
try {
emit(const EvenementsLoading());
final evenement = await _getEventById(event.id);
if (evenement != null) {
emit(EvenementDetailLoaded(evenement));
} else {
emit(const EvenementsError(
message: 'Événement non trouvé',
code: '404',
));
}
} on DioException catch (e) {
_emitDioError(e, emit);
} catch (e) {
if (e is DioException && e.type == DioExceptionType.cancel) return;
emit(EvenementsError(
message: 'Erreur lors du chargement de l\'événement. Veuillez réessayer.',
error: e,
));
}
}
/// Crée un nouvel événement
Future<void> _onCreateEvenement(
CreateEvenement event,
Emitter<EvenementsState> emit,
) async {
try {
emit(const EvenementsLoading());
final evenement = await _createEvent(event.evenement);
emit(EvenementCreated(evenement));
} on DioException catch (e) {
if (e.type == DioExceptionType.cancel) return;
if (e.response?.statusCode == 400) {
final errors = _extractValidationErrors(e.response?.data);
emit(EvenementsValidationError(
message: 'Erreur de validation',
validationErrors: errors,
code: '400',
));
} else {
_emitDioError(e, emit);
}
} catch (e) {
if (e is DioException && e.type == DioExceptionType.cancel) return;
emit(EvenementsError(
message: 'Erreur lors de la création de l\'événement. Veuillez réessayer.',
error: e,
));
}
}
/// Met à jour un événement
Future<void> _onUpdateEvenement(
UpdateEvenement event,
Emitter<EvenementsState> emit,
) async {
try {
emit(const EvenementsLoading());
final evenement = await _updateEvent(event.id, event.evenement);
emit(EvenementUpdated(evenement));
} on DioException catch (e) {
if (e.type == DioExceptionType.cancel) return;
if (e.response?.statusCode == 400) {
final errors = _extractValidationErrors(e.response?.data);
emit(EvenementsValidationError(
message: 'Erreur de validation',
validationErrors: errors,
code: '400',
));
} else {
_emitDioError(e, emit);
}
} catch (e) {
if (e is DioException && e.type == DioExceptionType.cancel) return;
emit(EvenementsError(
message: 'Erreur lors de la mise à jour de l\'événement. Veuillez réessayer.',
error: e,
));
}
}
/// Supprime un événement
Future<void> _onDeleteEvenement(
DeleteEvenement event,
Emitter<EvenementsState> emit,
) async {
try {
emit(const EvenementsLoading());
await _deleteEvent(event.id);
emit(EvenementDeleted(event.id));
} on DioException catch (e) {
_emitDioError(e, emit);
} catch (e) {
if (e is DioException && e.type == DioExceptionType.cancel) return;
emit(EvenementsError(
message: 'Erreur lors de la suppression de l\'événement. Veuillez réessayer.',
error: e,
));
}
}
/// Charge les événements à venir
Future<void> _onLoadEvenementsAVenir(
LoadEvenementsAVenir event,
Emitter<EvenementsState> emit,
) async {
try {
emit(const EvenementsLoading());
final result = await _repository.getEvenementsAVenir(
page: event.page,
size: event.size,
);
emit(EvenementsLoaded(
evenements: result.evenements,
total: result.total,
page: result.page,
size: result.size,
totalPages: result.totalPages,
));
} on DioException catch (e) {
_emitDioError(e, emit);
} catch (e) {
if (e is DioException && e.type == DioExceptionType.cancel) return;
emit(EvenementsError(
message: 'Erreur lors du chargement des événements à venir. Veuillez réessayer.',
error: e,
));
}
}
/// Charge les événements en cours
Future<void> _onLoadEvenementsEnCours(
LoadEvenementsEnCours event,
Emitter<EvenementsState> emit,
) async {
try {
emit(const EvenementsLoading());
final result = await _repository.getEvenementsEnCours(
page: event.page,
size: event.size,
);
emit(EvenementsLoaded(
evenements: result.evenements,
total: result.total,
page: result.page,
size: result.size,
totalPages: result.totalPages,
));
} on DioException catch (e) {
_emitDioError(e, emit);
} catch (e) {
if (e is DioException && e.type == DioExceptionType.cancel) return;
emit(EvenementsError(
message: 'Erreur lors du chargement des événements en cours. Veuillez réessayer.',
error: e,
));
}
}
/// Charge les événements passés
Future<void> _onLoadEvenementsPasses(
LoadEvenementsPasses event,
Emitter<EvenementsState> emit,
) async {
try {
emit(const EvenementsLoading());
final result = await _repository.getEvenementsPasses(
page: event.page,
size: event.size,
);
emit(EvenementsLoaded(
evenements: result.evenements,
total: result.total,
page: result.page,
size: result.size,
totalPages: result.totalPages,
));
} on DioException catch (e) {
_emitDioError(e, emit);
} catch (e) {
if (e is DioException && e.type == DioExceptionType.cancel) return;
emit(EvenementsError(
message: 'Erreur lors du chargement des événements passés. Veuillez réessayer.',
error: e,
));
}
}
/// S'inscrire à un événement
Future<void> _onInscrireEvenement(
InscrireEvenement event,
Emitter<EvenementsState> emit,
) async {
try {
emit(const EvenementsLoading());
await _registerForEvent(event.evenementId);
emit(EvenementInscrit(event.evenementId));
} on DioException catch (e) {
_emitDioError(e, emit);
} catch (e) {
if (e is DioException && e.type == DioExceptionType.cancel) return;
emit(EvenementsError(
message: 'Erreur lors de l\'inscription à l\'événement. Veuillez réessayer.',
error: e,
));
}
}
/// Se désinscrire d'un événement
Future<void> _onDesinscrireEvenement(
DesinscrireEvenement event,
Emitter<EvenementsState> emit,
) async {
try {
emit(const EvenementsLoading());
await _cancelRegistration(event.evenementId);
emit(EvenementDesinscrit(event.evenementId));
} on DioException catch (e) {
_emitDioError(e, emit);
} catch (e) {
if (e is DioException && e.type == DioExceptionType.cancel) return;
emit(EvenementsError(
message: 'Erreur lors de la désinscription de l\'événement. Veuillez réessayer.',
error: e,
));
}
}
/// Charge les participants
Future<void> _onLoadParticipants(
LoadParticipants event,
Emitter<EvenementsState> emit,
) async {
try {
emit(const EvenementsLoading());
final participants = await _getEventParticipants(event.evenementId);
emit(ParticipantsLoaded(
evenementId: event.evenementId,
participants: participants,
));
} on DioException catch (e) {
_emitDioError(e, emit);
} catch (e) {
if (e is DioException && e.type == DioExceptionType.cancel) return;
emit(EvenementsError(
message: 'Erreur lors du chargement des participants. Veuillez réessayer.',
error: e,
));
}
}
/// Charge les statistiques
Future<void> _onLoadEvenementsStats(
LoadEvenementsStats event,
Emitter<EvenementsState> emit,
) async {
try {
emit(const EvenementsLoading());
final stats = await _repository.getEvenementsStats();
emit(EvenementsStatsLoaded(stats));
} on DioException catch (e) {
_emitDioError(e, emit);
} catch (e) {
if (e is DioException && e.type == DioExceptionType.cancel) return;
emit(EvenementsError(
message: 'Erreur lors du chargement des statistiques. Veuillez réessayer.',
error: e,
));
}
}
/// Extrait les erreurs de validation
Map<String, String> _extractValidationErrors(dynamic data) {
final errors = <String, String>{};
if (data is Map<String, dynamic> && data.containsKey('errors')) {
final errorsData = data['errors'];
if (errorsData is Map<String, dynamic>) {
errorsData.forEach((key, value) {
errors[key] = value.toString();
});
}
}
return errors;
}
/// Génère un message d'erreur réseau approprié
String _getNetworkErrorMessage(DioException e) {
switch (e.type) {
case DioExceptionType.connectionTimeout:
return 'Délai de connexion dépassé. Vérifiez votre connexion internet.';
case DioExceptionType.sendTimeout:
return 'Délai d\'envoi dépassé. Vérifiez votre connexion internet.';
case DioExceptionType.receiveTimeout:
return 'Délai de réception dépassé. Vérifiez votre connexion internet.';
case DioExceptionType.badResponse:
final statusCode = e.response?.statusCode;
if (statusCode == 401) {
return 'Non autorisé. Veuillez vous reconnecter.';
} else if (statusCode == 403) {
return 'Accès refusé. Vous n\'avez pas les permissions nécessaires.';
} else if (statusCode == 404) {
return 'Ressource non trouvée.';
} else if (statusCode == 409) {
return 'Conflit. Cette ressource existe déjà.';
} else if (statusCode != null && statusCode >= 500) {
return 'Erreur serveur. Veuillez réessayer plus tard.';
}
return 'Erreur lors de la communication avec le serveur.';
case DioExceptionType.cancel:
return 'Requête annulée.';
case DioExceptionType.unknown:
return 'Erreur de connexion. Vérifiez votre connexion internet.';
default:
return 'Erreur réseau inattendue.';
}
}
/// Émet le bon état selon le type d'erreur :
/// 401/403 → EvenementsError (autorisation), autres → EvenementsNetworkError (réseau).
void _emitDioError(DioException e, Emitter<EvenementsState> emit) {
if (e.type == DioExceptionType.cancel) return;
final statusCode = e.response?.statusCode;
if (statusCode == 401 || statusCode == 403) {
emit(EvenementsError(
message: _getNetworkErrorMessage(e),
error: e,
));
} else {
emit(EvenementsNetworkError(
message: _getNetworkErrorMessage(e),
code: statusCode?.toString(),
error: e,
));
}
}
}