import 'dart:async'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:finlytic_app/features/news/repositories/news_repository.dart'; import 'news_event.dart'; import 'news_state.dart'; class NewsBloc extends Bloc { final NewsRepository repository; static const int pageSize = 20; StreamSubscription? _liveNewsSubscription; NewsBloc({required this.repository}) : super(NewsInitial()) { on(_onFetchNews); on(_onLoadMoreNews); on(_onReceiveLiveNews); // Subscribe to live news pushed over the central SignalRService's // `/hubs/news` connection (authenticated, managed in main.dart). _liveNewsSubscription = repository.liveNewsStream.listen((article) { add(ReceiveLiveNews(article)); }); } Future _onFetchNews(FetchNews event, Emitter emit) async { try { emit(NewsLoading()); final articles = await repository.fetchNews( page: 1, pageSize: pageSize, symbol: event.symbol, isin: event.isin, date: event.date, ); emit(NewsLoaded( articles: articles, hasReachedMax: articles.length < pageSize, currentPage: 1, symbol: event.symbol, isin: event.isin, date: event.date, )); } catch (e) { emit(NewsError("Failed to fetch news. Please try again.")); } } Future _onLoadMoreNews(LoadMoreNews event, Emitter emit) async { if (state is NewsLoaded) { final currentState = state as NewsLoaded; if (currentState.hasReachedMax) return; try { final nextPage = currentState.currentPage + 1; final articles = await repository.fetchNews( page: nextPage, pageSize: pageSize, symbol: currentState.symbol, isin: currentState.isin, date: currentState.date, ); if (articles.isEmpty) { emit(currentState.copyWith(hasReachedMax: true)); } else { emit(NewsLoaded( articles: currentState.articles + articles, hasReachedMax: articles.length < pageSize, currentPage: nextPage, symbol: currentState.symbol, isin: currentState.isin, date: currentState.date, )); } } catch (e) { emit(NewsError("Failed to load more news.")); } } } void _onReceiveLiveNews(ReceiveLiveNews event, Emitter emit) { if (state is NewsLoaded) { final currentState = state as NewsLoaded; // Check if the article matches current filters before prepending bool matchesFilter = true; if (currentState.symbol != null || currentState.isin != null) { // Needs proper matching logic if needed, but for now we prepend if there's no filter or if it matches // Assuming we prepend it regardless for live feed, or we could filter based on MatchedAssets if added to model. } if (matchesFilter) { // Prepend new article to the top of the list! final updatedArticles = [event.article, ...currentState.articles]; emit(currentState.copyWith(articles: updatedArticles)); } } } @override Future close() { _liveNewsSubscription?.cancel(); repository.dispose(); return super.close(); } }