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,96 @@
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:injectable/injectable.dart';
import '../../../../core/utils/logger.dart';
import 'unified_feed_event.dart';
import 'unified_feed_state.dart';
import '../../domain/entities/feed_item.dart';
import '../../data/repositories/feed_repository.dart';
/// BLoC Centralisé pour le mur d'actualité (DRY).
/// Aucune logique graphique, juste la gestion d'états.
@injectable
class UnifiedFeedBloc extends Bloc<UnifiedFeedEvent, UnifiedFeedState> {
final FeedRepository _repository;
UnifiedFeedBloc(this._repository) : super(UnifiedFeedInitial()) {
on<LoadFeedRequested>(_onLoadFeedRequested);
on<FeedLoadMoreRequested>(_onLoadMoreRequested);
on<ClearLoadMoreError>(_onClearLoadMoreError);
on<FeedItemLiked>(_onFeedItemLiked);
}
void _onClearLoadMoreError(ClearLoadMoreError event, Emitter<UnifiedFeedState> emit) {
if (state is UnifiedFeedLoaded) {
emit((state as UnifiedFeedLoaded).copyWith(loadMoreErrorMessage: null));
}
}
Future<void> _onLoadFeedRequested(LoadFeedRequested event, Emitter<UnifiedFeedState> emit) async {
if (!event.isRefresh) {
emit(UnifiedFeedLoading());
}
try {
final items = await _repository.getFeed(page: 0, size: 10);
// On suppose qu'on n'a pas atteint la fin si on a reçu la taille max demandée (10)
final hasReachedMax = items.length < 10;
emit(UnifiedFeedLoaded(items: items, hasReachedMax: hasReachedMax));
} catch (e) {
emit(UnifiedFeedError('Erreur de chargement du flux: $e'));
}
}
Future<void> _onLoadMoreRequested(FeedLoadMoreRequested event, Emitter<UnifiedFeedState> emit) async {
if (state is UnifiedFeedLoaded) {
final currentState = state as UnifiedFeedLoaded;
if (currentState.hasReachedMax || currentState.isFetchingMore) return;
emit(currentState.copyWith(isFetchingMore: true));
try {
final nextPage = (currentState.items.length / 10).floor();
final moreItems = await _repository.getFeed(page: nextPage, size: 10);
emit(currentState.copyWith(
items: List.of(currentState.items)..addAll(moreItems),
hasReachedMax: moreItems.isEmpty,
isFetchingMore: false,
));
} catch (e, st) {
AppLogger.error('UnifiedFeedBloc: chargement supplémentaire échoué', error: e, stackTrace: st);
emit(currentState.copyWith(
isFetchingMore: false,
loadMoreErrorMessage: 'Impossible de charger plus',
));
}
}
}
void _onFeedItemLiked(FeedItemLiked event, Emitter<UnifiedFeedState> emit) {
if (state is UnifiedFeedLoaded) {
final currentState = state as UnifiedFeedLoaded;
final updatedItems = currentState.items.map((item) {
if (item.id == event.itemId) {
return FeedItem(
id: item.id,
type: item.type,
authorName: item.authorName,
authorAvatarUrl: item.authorAvatarUrl,
createdAt: item.createdAt,
content: item.content,
likesCount: item.isLikedByMe ? item.likesCount - 1 : item.likesCount + 1,
commentsCount: item.commentsCount,
isLikedByMe: !item.isLikedByMe,
customActionLabel: item.customActionLabel,
actionUrlTarget: item.actionUrlTarget,
);
}
return item;
}).toList();
emit(currentState.copyWith(items: updatedItems));
}
}
}

View File

@@ -0,0 +1,30 @@
import 'package:equatable/equatable.dart';
abstract class UnifiedFeedEvent extends Equatable {
const UnifiedFeedEvent();
@override
List<Object> get props => [];
}
class LoadFeedRequested extends UnifiedFeedEvent {
final bool isRefresh;
const LoadFeedRequested({this.isRefresh = false});
@override
List<Object> get props => [isRefresh];
}
class FeedLoadMoreRequested extends UnifiedFeedEvent {}
/// Efface le message d'erreur « load more » après affichage du SnackBar.
class ClearLoadMoreError extends UnifiedFeedEvent {}
// Exemples d'événements interactifs sans tout polluer
class FeedItemLiked extends UnifiedFeedEvent {
final String itemId;
const FeedItemLiked(this.itemId);
@override
List<Object> get props => [itemId];
}

View File

@@ -0,0 +1,54 @@
import 'package:equatable/equatable.dart';
import '../../domain/entities/feed_item.dart';
abstract class UnifiedFeedState extends Equatable {
const UnifiedFeedState();
@override
List<Object?> get props => [];
}
class UnifiedFeedInitial extends UnifiedFeedState {}
class UnifiedFeedLoading extends UnifiedFeedState {}
class UnifiedFeedLoaded extends UnifiedFeedState {
final List<FeedItem> items;
final bool hasReachedMax;
final bool isFetchingMore;
/// Message d'erreur affiché une fois (ex. « Impossible de charger plus »), à consommer puis effacer par l'UI.
final String? loadMoreErrorMessage;
const UnifiedFeedLoaded({
required this.items,
this.hasReachedMax = false,
this.isFetchingMore = false,
this.loadMoreErrorMessage,
});
UnifiedFeedLoaded copyWith({
List<FeedItem>? items,
bool? hasReachedMax,
bool? isFetchingMore,
String? loadMoreErrorMessage,
}) {
return UnifiedFeedLoaded(
items: items ?? this.items,
hasReachedMax: hasReachedMax ?? this.hasReachedMax,
isFetchingMore: isFetchingMore ?? false,
loadMoreErrorMessage: loadMoreErrorMessage,
);
}
@override
List<Object?> get props => [items, hasReachedMax, isFetchingMore, loadMoreErrorMessage];
}
class UnifiedFeedError extends UnifiedFeedState {
final String message;
const UnifiedFeedError(this.message);
@override
List<Object?> get props => [message];
}