11import 'dart:async' ;
22
3+ import 'package:bloc_concurrency/bloc_concurrency.dart' ;
34import 'package:bloc/bloc.dart' ;
45import 'package:collection/collection.dart' ;
56import 'package:core/core.dart' ;
@@ -35,13 +36,19 @@ class InAppNotificationCenterBloc
3536 on < InAppNotificationCenterMarkAllAsRead > (_onMarkAllAsRead);
3637 on < InAppNotificationCenterTabChanged > (_onTabChanged);
3738 on < InAppNotificationCenterMarkOneAsRead > (_onMarkOneAsRead);
39+ on < InAppNotificationCenterFetchMoreRequested > (
40+ _onFetchMoreRequested,
41+ transformer: droppable (),
42+ );
3843 }
3944
4045 final DataRepository <InAppNotification > _inAppNotificationRepository;
4146 final AppBloc _appBloc;
4247 final Logger _logger;
4348
4449 /// Handles the request to load all notifications for the current user.
50+ /// This now only fetches the first page of notifications. Subsequent pages
51+ /// are loaded by [_onFetchMoreRequested] .
4552 Future <void > _onSubscriptionRequested (
4653 InAppNotificationCenterSubscriptionRequested event,
4754 Emitter <InAppNotificationCenterState > emit,
@@ -58,38 +65,37 @@ class InAppNotificationCenterBloc
5865 try {
5966 final response = await _inAppNotificationRepository.readAll (
6067 userId: userId,
68+ // Fetch the first page with a defined limit.
69+ pagination: const PaginationOptions (limit: 20 ),
6170 sort: [const SortOption ('createdAt' , SortOrder .desc)],
6271 );
6372
6473 final allNotifications = response.items;
6574
6675 final breakingNews = < InAppNotification > [];
6776 final digests = < InAppNotification > [];
77+ _filterAndAddNotifications (
78+ notifications: allNotifications,
79+ breakingNewsList: breakingNews,
80+ digestList: digests,
81+ );
6882
69- // Filter notifications into their respective categories, prioritizing
70- // 'notificationType' from the backend, then falling back to 'contentType'.
71- for (final n in allNotifications) {
72- final notificationType = n.payload.data['notificationType' ] as String ? ;
73- final contentType = n.payload.data['contentType' ] as String ? ;
74-
75- if (notificationType ==
76- PushNotificationSubscriptionDeliveryType .dailyDigest.name ||
77- notificationType ==
78- PushNotificationSubscriptionDeliveryType .weeklyRoundup.name ||
79- contentType == 'digest' ) {
80- digests.add (n);
81- } else {
82- // All other types (including 'breakingOnly' notificationType,
83- // 'headline' contentType, or any unknown types) go to breaking news.
84- breakingNews.add (n);
85- }
86- }
83+ // Since we are fetching all notifications together and then filtering,
84+ // the pagination cursor and hasMore status will be the same for both lists.
85+ // This assumes the backend doesn't support filtering by notification type
86+ // in the query itself.
87+ final hasMore = response.hasMore;
88+ final cursor = response.cursor;
8789
8890 emit (
8991 state.copyWith (
9092 status: InAppNotificationCenterStatus .success,
9193 breakingNewsNotifications: breakingNews,
9294 digestNotifications: digests,
95+ breakingNewsHasMore: hasMore,
96+ breakingNewsCursor: cursor,
97+ digestHasMore: hasMore,
98+ digestCursor: cursor,
9399 ),
94100 );
95101 } on HttpException catch (e, s) {
@@ -112,6 +118,89 @@ class InAppNotificationCenterBloc
112118 }
113119 }
114120
121+ /// Handles fetching the next page of notifications when the user scrolls.
122+ Future <void > _onFetchMoreRequested (
123+ InAppNotificationCenterFetchMoreRequested event,
124+ Emitter <InAppNotificationCenterState > emit,
125+ ) async {
126+ final isBreakingNewsTab = state.currentTabIndex == 0 ;
127+ final hasMore = isBreakingNewsTab
128+ ? state.breakingNewsHasMore
129+ : state.digestHasMore;
130+
131+ if (state.status == InAppNotificationCenterStatus .loadingMore || ! hasMore) {
132+ return ;
133+ }
134+
135+ emit (state.copyWith (status: InAppNotificationCenterStatus .loadingMore));
136+
137+ final userId = _appBloc.state.user? .id;
138+ if (userId == null ) {
139+ _logger.warning (
140+ 'Cannot fetch more notifications: user is not logged in.' ,
141+ );
142+ emit (state.copyWith (status: InAppNotificationCenterStatus .failure));
143+ return ;
144+ }
145+
146+ final cursor = isBreakingNewsTab
147+ ? state.breakingNewsCursor
148+ : state.digestCursor;
149+
150+ try {
151+ final response = await _inAppNotificationRepository.readAll (
152+ userId: userId,
153+ pagination: PaginationOptions (limit: 20 , cursor: cursor),
154+ sort: [const SortOption ('createdAt' , SortOrder .desc)],
155+ );
156+
157+ final newNotifications = response.items;
158+ final newBreakingNews = < InAppNotification > [];
159+ final newDigests = < InAppNotification > [];
160+
161+ _filterAndAddNotifications (
162+ notifications: newNotifications,
163+ breakingNewsList: newBreakingNews,
164+ digestList: newDigests,
165+ );
166+
167+ final nextCursor = response.cursor;
168+ final nextHasMore = response.hasMore;
169+
170+ emit (
171+ state.copyWith (
172+ status: InAppNotificationCenterStatus .success,
173+ breakingNewsNotifications: [
174+ ...state.breakingNewsNotifications,
175+ ...newBreakingNews,
176+ ],
177+ digestNotifications: [...state.digestNotifications, ...newDigests],
178+ breakingNewsHasMore: nextHasMore,
179+ breakingNewsCursor: nextCursor,
180+ digestHasMore: nextHasMore,
181+ digestCursor: nextCursor,
182+ ),
183+ );
184+ } on HttpException catch (e, s) {
185+ _logger.severe ('Failed to fetch more in-app notifications.' , e, s);
186+ emit (
187+ state.copyWith (status: InAppNotificationCenterStatus .failure, error: e),
188+ );
189+ } catch (e, s) {
190+ _logger.severe (
191+ 'An unexpected error occurred while fetching more in-app notifications.' ,
192+ e,
193+ s,
194+ );
195+ emit (
196+ state.copyWith (
197+ status: InAppNotificationCenterStatus .failure,
198+ error: UnknownException (e.toString ()),
199+ ),
200+ );
201+ }
202+ }
203+
115204 /// Handles the event to change the active tab.
116205 Future <void > _onTabChanged (
117206 InAppNotificationCenterTabChanged event,
@@ -280,4 +369,28 @@ class InAppNotificationCenterBloc
280369 );
281370 }
282371 }
372+
373+ /// A helper method to filter a list of notifications into "Breaking News"
374+ /// and "Digests" categories and add them to the provided lists.
375+ void _filterAndAddNotifications ({
376+ required List <InAppNotification > notifications,
377+ required List <InAppNotification > breakingNewsList,
378+ required List <InAppNotification > digestList,
379+ }) {
380+ for (final n in notifications) {
381+ final notificationType = n.payload.data['notificationType' ] as String ? ;
382+ final contentType = n.payload.data['contentType' ] as String ? ;
383+
384+ if (notificationType ==
385+ PushNotificationSubscriptionDeliveryType .dailyDigest.name ||
386+ notificationType ==
387+ PushNotificationSubscriptionDeliveryType .weeklyRoundup.name ||
388+ contentType == 'digest' ) {
389+ digestList.add (n);
390+ } else {
391+ // All other types go to breaking news.
392+ breakingNewsList.add (n);
393+ }
394+ }
395+ }
283396}
0 commit comments