Initial commit: unionflow-mobile-apps

Application Flutter complète (sans build artifacts).

Signed-off-by: lions dev Team
This commit is contained in:
dahoud
2026-03-15 16:30:08 +00:00
commit d094d6db9c
1790 changed files with 507435 additions and 0 deletions

View File

@@ -0,0 +1,297 @@
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<DashboardEvent, DashboardState> {
final GetDashboardData getDashboardData;
final GetDashboardStats getDashboardStats;
final GetRecentActivities getRecentActivities;
final GetUpcomingEvents getUpcomingEvents;
final WebSocketService webSocketService;
StreamSubscription<WebSocketEvent>? _webSocketEventSubscription;
StreamSubscription<bool>? _webSocketConnectionSubscription;
DashboardBloc({
required this.getDashboardData,
required this.getDashboardStats,
required this.getRecentActivities,
required this.getUpcomingEvents,
required this.webSocketService,
}) : super(DashboardInitial()) {
on<LoadDashboardData>(_onLoadDashboardData);
on<RefreshDashboardData>(_onRefreshDashboardData);
on<LoadDashboardStats>(_onLoadDashboardStats);
on<LoadRecentActivities>(_onLoadRecentActivities);
on<LoadUpcomingEvents>(_onLoadUpcomingEvents);
on<RefreshDashboardFromWebSocket>(_onRefreshDashboardFromWebSocket);
on<WebSocketConnectionChanged>(_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) {
AppLogger.info('DashboardBloc: Event WebSocket reçu - ${event.eventType}');
// Dispatcher uniquement les events pertinents au dashboard
if (event is DashboardStatsEvent) {
add(RefreshDashboardFromWebSocket(event.data));
} else if (event is FinanceApprovalEvent) {
// Les approbations affectent les stats, rafraîchir
add(RefreshDashboardFromWebSocket(event.data));
} else if (event is MemberEvent) {
// Les changements de membres affectent les stats
add(RefreshDashboardFromWebSocket(event.data));
} else if (event is ContributionEvent) {
// Les cotisations affectent les stats financières
add(RefreshDashboardFromWebSocket(event.data));
}
},
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<void> _onLoadDashboardData(
LoadDashboardData event,
Emitter<DashboardState> emit,
) async {
emit(DashboardLoading());
final result = await getDashboardData(
GetDashboardDataParams(
organizationId: event.organizationId,
userId: event.userId,
),
);
result.fold(
(failure) => emit(DashboardError(_mapFailureToMessage(failure))),
(dashboardData) => emit(DashboardLoaded(dashboardData)),
);
}
Future<void> _onRefreshDashboardData(
RefreshDashboardData event,
Emitter<DashboardState> 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,
),
);
result.fold(
(failure) => emit(DashboardError(_mapFailureToMessage(failure))),
(dashboardData) => emit(DashboardLoaded(dashboardData)),
);
}
Future<void> _onLoadDashboardStats(
LoadDashboardStats event,
Emitter<DashboardState> 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<void> _onLoadRecentActivities(
LoadRecentActivities event,
Emitter<DashboardState> 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<void> _onLoadUpcomingEvents(
LoadUpcomingEvents event,
Emitter<DashboardState> 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<void> _onRefreshDashboardFromWebSocket(
RefreshDashboardFromWebSocket event,
Emitter<DashboardState> 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<DashboardState> 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) {
switch (failure.runtimeType) {
case ServerFailure:
return 'Erreur serveur. Veuillez réessayer.';
case NetworkFailure:
return 'Pas de connexion internet. Vérifiez votre connexion.';
default:
return 'Une erreur inattendue s\'est produite.';
}
}
@override
Future<void> 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();
}
}

View File

@@ -0,0 +1,97 @@
part of 'dashboard_bloc.dart';
abstract class DashboardEvent extends Equatable {
const DashboardEvent();
@override
List<Object> get props => [];
}
class LoadDashboardData extends DashboardEvent {
final String organizationId;
final String userId;
const LoadDashboardData({
required this.organizationId,
required this.userId,
});
@override
List<Object> get props => [organizationId, userId];
}
class RefreshDashboardData extends DashboardEvent {
final String organizationId;
final String userId;
const RefreshDashboardData({
required this.organizationId,
required this.userId,
});
@override
List<Object> get props => [organizationId, userId];
}
class LoadDashboardStats extends DashboardEvent {
final String organizationId;
final String userId;
const LoadDashboardStats({
required this.organizationId,
required this.userId,
});
@override
List<Object> get props => [organizationId, userId];
}
class LoadRecentActivities extends DashboardEvent {
final String organizationId;
final String userId;
final int limit;
const LoadRecentActivities({
required this.organizationId,
required this.userId,
this.limit = 10,
});
@override
List<Object> get props => [organizationId, userId, limit];
}
class LoadUpcomingEvents extends DashboardEvent {
final String organizationId;
final String userId;
final int limit;
const LoadUpcomingEvents({
required this.organizationId,
required this.userId,
this.limit = 5,
});
@override
List<Object> get props => [organizationId, userId, limit];
}
/// Event déclenché par WebSocket pour rafraîchir le dashboard
class RefreshDashboardFromWebSocket extends DashboardEvent {
final Map<String, dynamic> data;
const RefreshDashboardFromWebSocket(this.data);
@override
List<Object> get props => [data];
}
/// Event pour gérer les changements de statut WebSocket
class WebSocketConnectionChanged extends DashboardEvent {
final bool isConnected;
const WebSocketConnectionChanged(this.isConnected);
@override
List<Object> get props => [isConnected];
}

View File

@@ -0,0 +1,39 @@
part of 'dashboard_bloc.dart';
abstract class DashboardState extends Equatable {
const DashboardState();
@override
List<Object> get props => [];
}
class DashboardInitial extends DashboardState {}
class DashboardLoading extends DashboardState {}
class DashboardLoaded extends DashboardState {
final DashboardEntity dashboardData;
const DashboardLoaded(this.dashboardData);
@override
List<Object> get props => [dashboardData];
}
class DashboardRefreshing extends DashboardState {
final DashboardEntity dashboardData;
const DashboardRefreshing(this.dashboardData);
@override
List<Object> get props => [dashboardData];
}
class DashboardError extends DashboardState {
final String message;
const DashboardError(this.message);
@override
List<Object> get props => [message];
}

View File

@@ -0,0 +1,35 @@
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:injectable/injectable.dart';
import '../../data/repositories/finance_repository.dart';
import 'finance_event.dart';
import 'finance_state.dart';
@injectable
class FinanceBloc extends Bloc<FinanceEvent, FinanceState> {
final FinanceRepository _repository;
FinanceBloc(this._repository) : super(FinanceInitial()) {
on<LoadFinanceRequested>(_onLoadFinanceRequested);
on<FinancePaymentInitiated>(_onFinancePaymentInitiated);
}
Future<void> _onLoadFinanceRequested(LoadFinanceRequested event, Emitter<FinanceState> emit) async {
emit(FinanceLoading());
try {
final summary = await _repository.getFinancialSummary();
final transactions = await _repository.getTransactions();
emit(FinanceLoaded(summary: summary, transactions: transactions));
} catch (e) {
emit(FinanceError('Erreur chargement des finances: $e'));
}
}
void _onFinancePaymentInitiated(FinancePaymentInitiated event, Emitter<FinanceState> emit) {
// Intégration paiement: appeler le service Wave ou Orange Money (API paiement) selon le design métier.
// Pour l'instant, la transaction est gérée côté UI (payment_dialog) et le BLoC reste en FinanceLoaded.
if (state is FinanceLoaded) {
// Option: émettre FinancePaymentPending puis FinanceLoaded après confirmation API.
}
}
}

View File

@@ -0,0 +1,18 @@
import 'package:equatable/equatable.dart';
abstract class FinanceEvent extends Equatable {
const FinanceEvent();
@override
List<Object> get props => [];
}
class LoadFinanceRequested extends FinanceEvent {}
class FinancePaymentInitiated extends FinanceEvent {
final String contributionId;
const FinancePaymentInitiated(this.contributionId);
@override
List<Object> get props => [contributionId];
}

View File

@@ -0,0 +1,67 @@
import 'package:equatable/equatable.dart';
class FinanceSummary extends Equatable {
final double totalContributionsPaid;
final double totalContributionsPending;
final double epargneBalance;
const FinanceSummary({
required this.totalContributionsPaid,
required this.totalContributionsPending,
required this.epargneBalance,
});
@override
List<Object?> get props => [totalContributionsPaid, totalContributionsPending, epargneBalance];
}
class FinanceTransaction extends Equatable {
final String id;
final String title;
final String date;
final double amount;
final String status;
const FinanceTransaction({
required this.id,
required this.title,
required this.date,
required this.amount,
required this.status, // "Payé", "En attente"
});
@override
List<Object?> get props => [id, title, date, amount, status];
}
abstract class FinanceState extends Equatable {
const FinanceState();
@override
List<Object?> get props => [];
}
class FinanceInitial extends FinanceState {}
class FinanceLoading extends FinanceState {}
class FinanceLoaded extends FinanceState {
final FinanceSummary summary;
final List<FinanceTransaction> transactions;
const FinanceLoaded({
required this.summary,
required this.transactions,
});
@override
List<Object?> get props => [summary, transactions];
}
class FinanceError extends FinanceState {
final String message;
const FinanceError(this.message);
@override
List<Object?> get props => [message];
}