kopia lustrzana https://gitlab.com/mysocialportal/relatica
				
				
				
			Refactor notifications system
							rodzic
							
								
									98d4bfeb9b
								
							
						
					
					
						commit
						e828b134ed
					
				|  | @ -421,9 +421,10 @@ class NotificationsClient extends FriendicaClient { | ||||||
|   NotificationsClient(super.credentials) : super(); |   NotificationsClient(super.credentials) : super(); | ||||||
| 
 | 
 | ||||||
|   FutureResult<PagedResponse<List<UserNotification>>, ExecError> |   FutureResult<PagedResponse<List<UserNotification>>, ExecError> | ||||||
|       getNotifications(PagingData page) async { |       getNotifications(PagingData page, bool includeAll) async { | ||||||
|     _networkStatusService.startNotificationUpdate(); |     _networkStatusService.startNotificationUpdate(); | ||||||
|     final url = 'https://$serverName/api/v1/notifications?include_all=true'; |     final url = | ||||||
|  |         'https://$serverName/api/v1/notifications?include_all=$includeAll'; | ||||||
|     final request = Uri.parse('$url&${page.toQueryParameters()}'); |     final request = Uri.parse('$url&${page.toQueryParameters()}'); | ||||||
|     _logger.finest(() => 'Getting new notifications'); |     _logger.finest(() => 'Getting new notifications'); | ||||||
|     final result = |     final result = | ||||||
|  |  | ||||||
|  | @ -1,12 +1,14 @@ | ||||||
| import 'connection.dart'; | import 'connection.dart'; | ||||||
| 
 | 
 | ||||||
| class FollowRequest { | class FollowRequest { | ||||||
|  |   final String id; | ||||||
|   final Connection connection; |   final Connection connection; | ||||||
|   final DateTime createdAt; |   final int createdAtEpochSeconds; | ||||||
| 
 | 
 | ||||||
|   const FollowRequest({ |   const FollowRequest({ | ||||||
|  |     required this.id, | ||||||
|     required this.connection, |     required this.connection, | ||||||
|     required this.createdAt, |     required this.createdAtEpochSeconds, | ||||||
|   }); |   }); | ||||||
| 
 | 
 | ||||||
|   @override |   @override | ||||||
|  | @ -14,8 +16,11 @@ class FollowRequest { | ||||||
|       identical(this, other) || |       identical(this, other) || | ||||||
|       other is FollowRequest && |       other is FollowRequest && | ||||||
|           runtimeType == other.runtimeType && |           runtimeType == other.runtimeType && | ||||||
|           connection == other.connection; |           id == other.id && | ||||||
|  |           connection == other.connection && | ||||||
|  |           createdAtEpochSeconds == other.createdAtEpochSeconds; | ||||||
| 
 | 
 | ||||||
|   @override |   @override | ||||||
|   int get hashCode => connection.hashCode; |   int get hashCode => | ||||||
|  |       id.hashCode ^ connection.hashCode ^ createdAtEpochSeconds.hashCode; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -75,6 +75,22 @@ class UserNotification implements Comparable<UserNotification> { | ||||||
|     required this.link, |     required this.link, | ||||||
|   }); |   }); | ||||||
| 
 | 
 | ||||||
|  |   UserNotification copy({ | ||||||
|  |     bool? dismissed, | ||||||
|  |   }) => | ||||||
|  |       UserNotification( | ||||||
|  |         id: id, | ||||||
|  |         type: type, | ||||||
|  |         fromId: fromId, | ||||||
|  |         fromName: fromName, | ||||||
|  |         fromUrl: fromUrl, | ||||||
|  |         timestamp: timestamp, | ||||||
|  |         iid: iid, | ||||||
|  |         dismissed: dismissed ?? this.dismissed, | ||||||
|  |         content: content, | ||||||
|  |         link: link, | ||||||
|  |       ); | ||||||
|  | 
 | ||||||
|   @override |   @override | ||||||
|   String toString() { |   String toString() { | ||||||
|     return 'UserNotification{id: $id, seen: $dismissed, fromName: $fromName, content: $content}'; |     return 'UserNotification{id: $id, seen: $dismissed, fromName: $fromName, content: $content}'; | ||||||
|  | @ -92,4 +108,14 @@ class UserNotification implements Comparable<UserNotification> { | ||||||
| 
 | 
 | ||||||
|     return -1; |     return -1; | ||||||
|   } |   } | ||||||
|  | 
 | ||||||
|  |   @override | ||||||
|  |   bool operator ==(Object other) => | ||||||
|  |       identical(this, other) || | ||||||
|  |       other is UserNotification && | ||||||
|  |           runtimeType == other.runtimeType && | ||||||
|  |           id == other.id; | ||||||
|  | 
 | ||||||
|  |   @override | ||||||
|  |   int get hashCode => id.hashCode; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -262,6 +262,6 @@ class _FollowRequestAdjudicationScreenState | ||||||
|     followRequestsManager.update(); |     followRequestsManager.update(); | ||||||
|     getIt<ActiveProfileSelector<NotificationsManager>>() |     getIt<ActiveProfileSelector<NotificationsManager>>() | ||||||
|         .activeEntry |         .activeEntry | ||||||
|         .andThenSuccess((m) => m.updateNotifications()); |         .andThenSuccess((m) => m.refreshNotifications()); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -19,9 +19,8 @@ class NotificationsScreen extends StatelessWidget { | ||||||
| 
 | 
 | ||||||
|   const NotificationsScreen({super.key}); |   const NotificationsScreen({super.key}); | ||||||
| 
 | 
 | ||||||
|   Future<void> update(NotificationsManager manager) async { |   void update(NotificationsManager manager) { | ||||||
|     await manager.updateNotifications(); |     manager.refreshNotifications(); | ||||||
|     await manager.loadNewerNotifications(); |  | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   @override |   @override | ||||||
|  | @ -61,9 +60,7 @@ class NotificationsScreen extends StatelessWidget { | ||||||
|           )), |           )), | ||||||
|         ); |         ); | ||||||
|       } else { |       } else { | ||||||
|         final unreadCount = notifications.where((e) => !e.dismissed).length; |         title = 'Notifications'; | ||||||
|         title = |  | ||||||
|             'Notifications'; //TODO wire in the summary count data if has that endpoint |  | ||||||
|         body = RefreshIndicator( |         body = RefreshIndicator( | ||||||
|           onRefresh: () async { |           onRefresh: () async { | ||||||
|             update(manager); |             update(manager); | ||||||
|  |  | ||||||
|  | @ -2,24 +2,35 @@ import 'package:uuid/uuid.dart'; | ||||||
| 
 | 
 | ||||||
| import '../../models/follow_request.dart'; | import '../../models/follow_request.dart'; | ||||||
| import '../../models/user_notification.dart'; | import '../../models/user_notification.dart'; | ||||||
|  | import '../../utils/dateutils.dart'; | ||||||
| import 'connection_mastodon_extensions.dart'; | import 'connection_mastodon_extensions.dart'; | ||||||
| 
 | 
 | ||||||
| extension FollowRequestMastodonExtension on FollowRequest { | extension FollowRequestMastodonExtension on FollowRequest { | ||||||
|   static FollowRequest fromJson(Map<String, dynamic> json) { |   static FollowRequest fromJson(Map<String, dynamic> json) { | ||||||
|     final connection = ConnectionMastodonExtensions.fromJson(json); |     final connection = ConnectionMastodonExtensions.fromJson(json); | ||||||
|     final createdAt = |     final id = json['id'] ?? Uuid().v4(); | ||||||
|         DateTime.tryParse(json['created_at'] ?? '') ?? DateTime.now(); |     final int timestamp = json.containsKey('created_at') | ||||||
|     return FollowRequest(connection: connection, createdAt: createdAt); |         ? OffsetDateTimeUtils.epochSecTimeFromTimeZoneString(json['created_at']) | ||||||
|  |             .fold( | ||||||
|  |             onSuccess: (value) => value, | ||||||
|  |             onError: (error) => 0, | ||||||
|  |           ) | ||||||
|  |         : 0; | ||||||
|  |     return FollowRequest( | ||||||
|  |       id: id, | ||||||
|  |       connection: connection, | ||||||
|  |       createdAtEpochSeconds: timestamp, | ||||||
|  |     ); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   UserNotification toUserNotification() { |   UserNotification toUserNotification() { | ||||||
|     return UserNotification( |     return UserNotification( | ||||||
|       id: Uuid().v4(), |       id: id, | ||||||
|       type: NotificationType.follow_request, |       type: NotificationType.follow_request, | ||||||
|       fromId: connection.id, |       fromId: connection.id, | ||||||
|       fromName: connection.name, |       fromName: connection.name, | ||||||
|       fromUrl: connection.profileUrl, |       fromUrl: connection.profileUrl, | ||||||
|       timestamp: createdAt.millisecondsSinceEpoch, |       timestamp: createdAtEpochSeconds, | ||||||
|       iid: '', |       iid: '', | ||||||
|       dismissed: false, |       dismissed: false, | ||||||
|       content: |       content: | ||||||
|  |  | ||||||
|  | @ -20,34 +20,31 @@ import 'follow_requests_manager.dart'; | ||||||
| import 'network_status_service.dart'; | import 'network_status_service.dart'; | ||||||
| 
 | 
 | ||||||
| class NotificationsManager extends ChangeNotifier { | class NotificationsManager extends ChangeNotifier { | ||||||
|  |   static const itemsPerQuery = 10; | ||||||
|  |   static const minimumDmsAndCrsUpdateDuration = Duration(seconds: 30); | ||||||
|   static final _logger = Logger('NotificationManager'); |   static final _logger = Logger('NotificationManager'); | ||||||
|   late final PagesManager<List<UserNotification>, String> _pm; |  | ||||||
|   final Profile profile; |   final Profile profile; | ||||||
|   final dms = <UserNotification>[]; |   final dms = <UserNotification>[]; | ||||||
|   final connectionRequests = <UserNotification>[]; |   final connectionRequests = <UserNotification>[]; | ||||||
|   final unread = <UserNotification>[]; |   final unread = <UserNotification>[]; | ||||||
|   final read = <UserNotification>[]; |   final read = <UserNotification>[]; | ||||||
|  |   var lastDmsUpdate = DateTime(1900); | ||||||
|  |   var lastCrUpdate = DateTime(1900); | ||||||
| 
 | 
 | ||||||
|   NotificationsManager(this.profile) { |   NotificationsManager(this.profile); | ||||||
|     _pm = PagesManager<List<UserNotification>, String>( |  | ||||||
|         idMapper: (nn) => nn.map((n) => n.id).toList(), |  | ||||||
|         onRequest: (pd) async => |  | ||||||
|             await _clientGetNotificationsRequest(profile, pd)); |  | ||||||
|   } |  | ||||||
| 
 | 
 | ||||||
|   var _firstLoad = true; |   var _firstLoad = true; | ||||||
| 
 | 
 | ||||||
|   List<UserNotification> get notifications { |   List<UserNotification> get notifications { | ||||||
|     if (_firstLoad) { |     if (_firstLoad) { | ||||||
|       updateNotifications(); |       loadUnreadNotifications(true); | ||||||
|       _firstLoad = false; |       _firstLoad = false; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     return [...connectionRequests, ...dms, ...unread, ...read]; |     return [...connectionRequests, ...dms, ...unread, ...read]; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   void clear() { |   void clear({bool withListenerNotification = true}) { | ||||||
|     _pm.clear(); |  | ||||||
|     dms.clear(); |     dms.clear(); | ||||||
|     connectionRequests.clear(); |     connectionRequests.clear(); | ||||||
|     unread.clear(); |     unread.clear(); | ||||||
|  | @ -56,148 +53,148 @@ class NotificationsManager extends ChangeNotifier { | ||||||
|     notifyListeners(); |     notifyListeners(); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   FutureResult<List<UserNotification>, ExecError> updateNotifications() async { |   void refreshNotifications() async { | ||||||
|     const initialPull = 25; |     clear(withListenerNotification: false); | ||||||
|     final notificationsFromRefresh = <UserNotification>[]; |     await loadUnreadNotifications(false); | ||||||
|     if (_pm.pages.isEmpty) { |     if (unread.isEmpty && read.isEmpty) { | ||||||
|       final result = await _pm.initialize(initialPull); |       await loadOlderNotifications(withListenerNotification: false); | ||||||
|       result.andThenSuccess( |  | ||||||
|           (response) => notificationsFromRefresh.addAll(response.data)); |  | ||||||
|     } else { |  | ||||||
|       for (var i = 0; i < _pm.pages.length; i++) { |  | ||||||
|         if (i > 0 && i == _pm.pages.length - 1) { |  | ||||||
|           continue; |  | ||||||
|     } |     } | ||||||
|         final page = _pm.pages[i]; |  | ||||||
|         if (i == 0) { |  | ||||||
|           PagingData? pd; |  | ||||||
|           bool initializedFirstPage = false; |  | ||||||
|           if (page.next != null) { |  | ||||||
|             final response = await _clientGetNotificationsRequest( |  | ||||||
|               profile, |  | ||||||
|               page.next!, |  | ||||||
|             ); |  | ||||||
|             response.match( |  | ||||||
|                 onSuccess: (response) => pd = response.previous, |  | ||||||
|                 onError: (error) => |  | ||||||
|                     _logger.severe('Error getting previous page: $error')); |  | ||||||
|             if (pd != null) { |  | ||||||
|               final response = await _clientGetNotificationsRequest( |  | ||||||
|                 profile, |  | ||||||
|                 pd!, |  | ||||||
|               ); |  | ||||||
|               response.match( |  | ||||||
|                   onSuccess: (response) { |  | ||||||
|                     initializedFirstPage = true; |  | ||||||
|                     notificationsFromRefresh.addAll(response.data); |  | ||||||
|                   }, |  | ||||||
|                   onError: (error) => |  | ||||||
|                       _logger.severe('Error getting previous page: $error')); |  | ||||||
|             } else if (pd == null && page.previous != null) { |  | ||||||
|               final response = await _clientGetNotificationsRequest( |  | ||||||
|                 profile, |  | ||||||
|                 page.previous!, |  | ||||||
|               ).andThenAsync((previousData) async => previousData.next != null |  | ||||||
|                   ? await _clientGetNotificationsRequest( |  | ||||||
|                       profile, |  | ||||||
|                       previousData.next!, |  | ||||||
|                     ) |  | ||||||
|                   : buildErrorResult( |  | ||||||
|                       type: ErrorType.rangeError, |  | ||||||
|                       message: 'No "next" page from previous data either')); |  | ||||||
|               response.match( |  | ||||||
|                   onSuccess: (response) { |  | ||||||
|                     initializedFirstPage = true; |  | ||||||
|                     notificationsFromRefresh.addAll(response.data); |  | ||||||
|                   }, |  | ||||||
|                   onError: (error) => |  | ||||||
|                       _logger.severe('Error getting previous page: $error')); |  | ||||||
|             } else if (pd == null && page.previous == null) { |  | ||||||
|               _logger.severe( |  | ||||||
|                   'Next page returned no results and no previous page so will need to re-initalize'); |  | ||||||
|             } |  | ||||||
|           } else { |  | ||||||
|             _logger.severe( |  | ||||||
|                 'There is no next page to query so will be forced to reset'); |  | ||||||
|           } |  | ||||||
| 
 |  | ||||||
|           if (!initializedFirstPage) { |  | ||||||
|             _logger.severe( |  | ||||||
|                 'Unable to determine call to rebuild initial page so resetting'); |  | ||||||
|             _pm.clear(); |  | ||||||
|             final result = await _pm.initialize(initialPull); |  | ||||||
|             result.andThenSuccess( |  | ||||||
|                 (response) => notificationsFromRefresh.addAll(response.data)); |  | ||||||
|           } |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         if (page.next == null) { |  | ||||||
|           if (i != _pm.pages.length - 2) { |  | ||||||
|             _logger |  | ||||||
|                 .severe('No forward paging data in middle page but expected'); |  | ||||||
|           } |  | ||||||
|           continue; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         final response = await _clientGetNotificationsRequest( |  | ||||||
|           profile, |  | ||||||
|           page.next!, |  | ||||||
|         ); |  | ||||||
|         response.match( |  | ||||||
|             onSuccess: (response) => |  | ||||||
|                 notificationsFromRefresh.addAll(response.data), |  | ||||||
|             onError: (error) => |  | ||||||
|                 _logger.severe('Error getting next page: $error')); |  | ||||||
|       } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     return await _postFetchOperations(notificationsFromRefresh, true); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   FutureResult<List<UserNotification>, ExecError> |  | ||||||
|       loadNewerNotifications() async { |  | ||||||
|     final result = await _pm |  | ||||||
|         .previousFromBeginning() |  | ||||||
|         .andThenAsync( |  | ||||||
|           (page) async => await _postFetchOperations(page.data, false), |  | ||||||
|         ) |  | ||||||
|         .withError( |  | ||||||
|             (error) => _logger.info('Error getting more updates: $error')); |  | ||||||
|     return result.execErrorCast(); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   FutureResult<List<UserNotification>, ExecError> |  | ||||||
|       loadOlderNotifications() async { |  | ||||||
|     final result = await _pm |  | ||||||
|         .nextFromEnd() |  | ||||||
|         .andThenAsync( |  | ||||||
|           (page) async => await _postFetchOperations(page.data, false), |  | ||||||
|         ) |  | ||||||
|         .withError( |  | ||||||
|             (error) => _logger.info('Error getting more updates: $error')); |  | ||||||
|     return result.execErrorCast(); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   FutureResult<bool, ExecError> markSeen(UserNotification notification) async { |  | ||||||
|     final result = |  | ||||||
|         await NotificationsClient(profile).clearNotification(notification); |  | ||||||
|     if (result.isSuccess) { |  | ||||||
|     notifyListeners(); |     notifyListeners(); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|     updateNotifications(); |   FutureResult<List<UserNotification>, ExecError> loadUnreadNotifications( | ||||||
|     return result; |       bool withListenerNotification) async { | ||||||
|  |     final notificationsFromRefresh = <UserNotification>[]; | ||||||
|  | 
 | ||||||
|  |     final pm = _buildPageManager(profile, false); | ||||||
|  |     final useActualRequests = getIt<FriendicaVersionChecker>() | ||||||
|  |         .canUseFeature(RelaticaFeatures.usingActualFollowRequests); | ||||||
|  |     var hasMore = true; | ||||||
|  |     var first = true; | ||||||
|  |     while (hasMore) { | ||||||
|  |       final result = | ||||||
|  |           first ? await pm.initialize(itemsPerQuery) : await pm.nextFromEnd(); | ||||||
|  | 
 | ||||||
|  |       first = false; | ||||||
|  |       result.match( | ||||||
|  |           onSuccess: (nd) => print('Got ${nd.data.length} notifications'), | ||||||
|  |           onError: (e) => debugPrint('Error getting notification: $e')); | ||||||
|  |       final response = result.getValueOrElse(() => PagedResponse([])); | ||||||
|  |       response.data | ||||||
|  |           .where((n) => | ||||||
|  |               !useActualRequests || n.type != NotificationType.follow_request) | ||||||
|  |           .forEach(notificationsFromRefresh.add); | ||||||
|  |       hasMore = response.next != null; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|   FutureResult<List<UserNotification>, ExecError> markAllAsRead() async { |     // filter out connection requests if going to use the real service for that when doing the query | ||||||
|  |     // get earliest and latest notification ID from unread notifications | ||||||
|  | 
 | ||||||
|  |     // query all notifications over that in page increments of 25 | ||||||
|  |     // query unread notifications in increments of 25 after the latest ID | ||||||
|  |     return await _postFetchOperations( | ||||||
|  |       notificationsFromRefresh, | ||||||
|  |       withListenerNotification, | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   FutureResult<List<UserNotification>, ExecError> loadNewerNotifications({ | ||||||
|  |     bool withListenerNotification = true, | ||||||
|  |   }) async { | ||||||
|  |     final (_, highestId) = | ||||||
|  |         unread.isNotEmpty ? calcLowHigh(unread) : calcLowHigh(read); | ||||||
|  |     final pm = await _buildPageManager( | ||||||
|  |       profile, | ||||||
|  |       true, | ||||||
|  |       initialPages: [ | ||||||
|  |         PagedResponse( | ||||||
|  |           <String>[], | ||||||
|  |           next: PagingData(minId: highestId), | ||||||
|  |         ) | ||||||
|  |       ], | ||||||
|  |     ); | ||||||
|  | 
 | ||||||
|  |     final result = await (unread.isEmpty && read.isEmpty | ||||||
|  |             ? pm.initialize(itemsPerQuery) | ||||||
|  |             : pm.previousFromBeginning()) | ||||||
|  |         .andThenAsync( | ||||||
|  |           (page) async => | ||||||
|  |               await _postFetchOperations(page.data, withListenerNotification), | ||||||
|  |         ) | ||||||
|  |         .withError( | ||||||
|  |             (error) => _logger.info('Error getting more updates: $error')); | ||||||
|  |     return result.execErrorCast(); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   FutureResult<List<UserNotification>, ExecError> loadOlderNotifications( | ||||||
|  |       {bool withListenerNotification = true}) async { | ||||||
|  |     final (lowestId, _) = | ||||||
|  |         read.isNotEmpty ? calcLowHigh(read) : calcLowHigh(unread); | ||||||
|  |     final pm = _buildPageManager( | ||||||
|  |       profile, | ||||||
|  |       true, | ||||||
|  |       initialPages: read.isEmpty | ||||||
|  |           ? [] | ||||||
|  |           : [ | ||||||
|  |               PagedResponse( | ||||||
|  |                 <String>[], | ||||||
|  |                 next: PagingData(maxId: lowestId), | ||||||
|  |               ) | ||||||
|  |             ], | ||||||
|  |     ); | ||||||
|  | 
 | ||||||
|  |     final notifications = <UserNotification>[]; | ||||||
|  |     if (read.isEmpty) { | ||||||
|  |       var hasReadNotification = false; | ||||||
|  |       var hasMorePages = false; | ||||||
|  |       do { | ||||||
|  |         await (notifications.isEmpty | ||||||
|  |                 ? pm.initialize(itemsPerQuery) | ||||||
|  |                 : pm.nextFromEnd()) | ||||||
|  |             .match(onSuccess: (r) { | ||||||
|  |           notifications.addAll(r.data); | ||||||
|  |           hasMorePages = r.next != null; | ||||||
|  |           hasReadNotification = r.data.map((e) => e.dismissed).firstWhere( | ||||||
|  |                 (t) => t == true, | ||||||
|  |                 orElse: () => false, | ||||||
|  |               ); | ||||||
|  |         }, onError: (e) { | ||||||
|  |           hasMorePages = false; | ||||||
|  |           print('Error getting older notifications: $e'); | ||||||
|  |         }); | ||||||
|  |       } while (!hasReadNotification && hasMorePages); | ||||||
|  |     } else { | ||||||
|  |       await pm.nextFromEnd().withResult((r) => notifications.addAll(r.data)); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return _postFetchOperations(notifications, withListenerNotification); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   FutureResult<bool, ExecError> markSeen(UserNotification notification) async { | ||||||
|  |     final result = await NotificationsClient(profile) | ||||||
|  |         .clearNotification(notification) | ||||||
|  |         .withResult((_) { | ||||||
|  |       unread.remove(notification); | ||||||
|  |       read.add(notification.copy(dismissed: true)); | ||||||
|  |       read.sort(); | ||||||
|  |       notifyListeners(); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     return result.execErrorCast(); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   FutureResult<bool, ExecError> markAllAsRead() async { | ||||||
|     final result = |     final result = | ||||||
|         await NotificationsClient(getIt<AccountsService>().currentProfile) |         await NotificationsClient(getIt<AccountsService>().currentProfile) | ||||||
|             .clearNotifications(); |             .clearNotifications() | ||||||
|     if (result.isFailure) { |             .withResult((_) { | ||||||
|       return result.errorCast(); |       unread.map((n) => n.copy(dismissed: true)).forEach(read.add); | ||||||
|     } |       unread.clear(); | ||||||
|  |       read.sort(); | ||||||
|  |       notifyListeners(); | ||||||
|  |     }); | ||||||
| 
 | 
 | ||||||
|     return updateNotifications(); |     return result.execErrorCast(); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   List<UserNotification> buildUnreadMessageNotifications( |   List<UserNotification> buildUnreadMessageNotifications( | ||||||
|  | @ -238,22 +235,32 @@ class NotificationsManager extends ChangeNotifier { | ||||||
|     return [...dmsResult, ...followRequestResult]; |     return [...dmsResult, ...followRequestResult]; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   void updateNotification(UserNotification notification) {} | ||||||
|  | 
 | ||||||
|   FutureResult<List<UserNotification>, ExecError> _postFetchOperations( |   FutureResult<List<UserNotification>, ExecError> _postFetchOperations( | ||||||
|     List<UserNotification> notificationsFromRefresh, |     List<UserNotification> notificationsFromRefresh, | ||||||
|     bool clearAtStart, |     bool withListenerNotification, | ||||||
|   ) async { |   ) async { | ||||||
|     getIt<NetworkStatusService>().startNotificationUpdate(); |     getIt<NetworkStatusService>().startNotificationUpdate(); | ||||||
|  |     if (DateTime.now().difference(lastDmsUpdate) > | ||||||
|  |         minimumDmsAndCrsUpdateDuration) { | ||||||
|       await getIt<ActiveProfileSelector<DirectMessageService>>() |       await getIt<ActiveProfileSelector<DirectMessageService>>() | ||||||
|           .getForProfile(profile) |           .getForProfile(profile) | ||||||
|           .transformAsync((dms) async => await dms.updateThreads()); |           .transformAsync((dms) async => await dms.updateThreads()); | ||||||
|  |       lastDmsUpdate = DateTime.now(); | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|     final useActualRequests = getIt<FriendicaVersionChecker>() |     final useActualRequests = getIt<FriendicaVersionChecker>() | ||||||
|         .canUseFeature(RelaticaFeatures.usingActualFollowRequests); |         .canUseFeature(RelaticaFeatures.usingActualFollowRequests); | ||||||
| 
 | 
 | ||||||
|     if (useActualRequests) { |     if (useActualRequests) { | ||||||
|  |       if (DateTime.now().difference(lastCrUpdate) > | ||||||
|  |           minimumDmsAndCrsUpdateDuration) { | ||||||
|         await getIt<ActiveProfileSelector<FollowRequestsManager>>() |         await getIt<ActiveProfileSelector<FollowRequestsManager>>() | ||||||
|             .getForProfile(profile) |             .getForProfile(profile) | ||||||
|             .transformAsync((fm) async => fm.update()); |             .transformAsync((fm) async => fm.update()); | ||||||
|  |         lastCrUpdate = DateTime.now(); | ||||||
|  |       } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     final notifications = <String, UserNotification>{}; |     final notifications = <String, UserNotification>{}; | ||||||
|  | @ -270,23 +277,23 @@ class NotificationsManager extends ChangeNotifier { | ||||||
|       notifications[n.id] = n; |       notifications[n.id] = n; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     _processNewNotifications(notifications.values, clearAtStart: clearAtStart); |     _processNewNotifications(notifications.values); | ||||||
| 
 | 
 | ||||||
|  |     if (withListenerNotification) { | ||||||
|       notifyListeners(); |       notifyListeners(); | ||||||
|  |     } | ||||||
|     return Result.ok(notifications.values.toList()); |     return Result.ok(notifications.values.toList()); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   Future<void> _processNewNotifications( |   Future<void> _processNewNotifications( | ||||||
|     Iterable<UserNotification> notifications, { |       Iterable<UserNotification> notifications) async { | ||||||
|     bool clearAtStart = false, |  | ||||||
|   }) async { |  | ||||||
|     final dmsMap = <String, UserNotification>{}; |     final dmsMap = <String, UserNotification>{}; | ||||||
|     final crMap = <String, UserNotification>{}; |     final crMap = <String, UserNotification>{}; | ||||||
|     final unreadMap = <String, UserNotification>{}; |     final unreadMap = <String, UserNotification>{}; | ||||||
|     final readMap = <String, UserNotification>{}; |     final readMap = <String, UserNotification>{}; | ||||||
| 
 | 
 | ||||||
|     final st = Stopwatch()..start(); |     final st = Stopwatch()..start(); | ||||||
|     if (!clearAtStart) { | 
 | ||||||
|     for (int i = 0; i < dms.length; i++) { |     for (int i = 0; i < dms.length; i++) { | ||||||
|       dmsMap[dms[i].id] = dms[i]; |       dmsMap[dms[i].id] = dms[i]; | ||||||
|     } |     } | ||||||
|  | @ -314,7 +321,7 @@ class NotificationsManager extends ChangeNotifier { | ||||||
|     for (int i = 0; i < read.length; i++) { |     for (int i = 0; i < read.length; i++) { | ||||||
|       readMap[read[i].id] = read[i]; |       readMap[read[i].id] = read[i]; | ||||||
|     } |     } | ||||||
|     } | 
 | ||||||
|     dms.clear(); |     dms.clear(); | ||||||
|     connectionRequests.clear(); |     connectionRequests.clear(); | ||||||
|     unread.clear(); |     unread.clear(); | ||||||
|  | @ -337,7 +344,6 @@ class NotificationsManager extends ChangeNotifier { | ||||||
|         case NotificationType.direct_message: |         case NotificationType.direct_message: | ||||||
|           dmsMap[n.id] = n; |           dmsMap[n.id] = n; | ||||||
|           break; |           break; | ||||||
|         case NotificationType.follow: |  | ||||||
|         case NotificationType.follow_request: |         case NotificationType.follow_request: | ||||||
|           crMap[n.id] = n; |           crMap[n.id] = n; | ||||||
|           break; |           break; | ||||||
|  | @ -359,9 +365,36 @@ class NotificationsManager extends ChangeNotifier { | ||||||
|       ..addAll(readMap.values) |       ..addAll(readMap.values) | ||||||
|       ..sort(); |       ..sort(); | ||||||
|   } |   } | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
|   static FutureResult<PagedResponse<List<UserNotification>>, ExecError> | (int lowest, int highest) calcLowHigh(List<UserNotification> notifications) { | ||||||
|       _clientGetNotificationsRequest(Profile profile, PagingData page) async { |   int highestNotificationId = -1; | ||||||
|     return NotificationsClient(profile).getNotifications(page); |   int lowestNotificationId = 0x7FFFFFFFFFFFFFFF; | ||||||
|  |   final ids = notifications | ||||||
|  |       .where((n) => | ||||||
|  |           n.type != NotificationType.direct_message && | ||||||
|  |           n.type != NotificationType.follow_request) | ||||||
|  |       .map((n) => int.parse(n.id)); | ||||||
|  | 
 | ||||||
|  |   for (var id in ids) { | ||||||
|  |     if (id > highestNotificationId) { | ||||||
|  |       highestNotificationId = id; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if (id < lowestNotificationId) { | ||||||
|  |       lowestNotificationId = id; | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  | 
 | ||||||
|  |   return (lowestNotificationId, highestNotificationId); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | PagesManager<List<UserNotification>, String> _buildPageManager( | ||||||
|  |         Profile profile, bool includeAll, | ||||||
|  |         {List<PagedResponse> initialPages = const []}) => | ||||||
|  |     PagesManager<List<UserNotification>, String>( | ||||||
|  |       initialPages: initialPages, | ||||||
|  |       idMapper: (nn) => nn.map((n) => n.id).toList(), | ||||||
|  |       onRequest: (pd) async => | ||||||
|  |           await NotificationsClient(profile).getNotifications(pd, includeAll), | ||||||
|  |     ); | ||||||
|  |  | ||||||
		Ładowanie…
	
		Reference in New Issue
	
	 Hank Grabowski
						Hank Grabowski