diff --git a/lib/friendica_client/friendica_client.dart b/lib/friendica_client/friendica_client.dart index 2a05566..456b673 100644 --- a/lib/friendica_client/friendica_client.dart +++ b/lib/friendica_client/friendica_client.dart @@ -41,9 +41,6 @@ import '../services/network_status_service.dart'; import '../utils/network_utils.dart'; import 'paging_data.dart'; -const _maxProcessingMillis = 3; -const _processingSleep = Duration(milliseconds: 1); - class DirectMessagingClient extends FriendicaClient { static final _logger = Logger('$DirectMessagingClient'); @@ -435,8 +432,8 @@ class NotificationsClient extends FriendicaClient { final st = Stopwatch()..start(); for (final json in response.data) { - if (st.elapsedMilliseconds > _maxProcessingMillis) { - await Future.delayed(_processingSleep, () => st.reset()); + if (st.elapsedMilliseconds > maxProcessingMillis) { + await Future.delayed(processingSleep, () => st.reset()); } notifications.add(NotificationMastodonExtension.fromJson(json)); } @@ -483,8 +480,8 @@ class RelationshipsClient extends FriendicaClient { final st = Stopwatch()..start(); for (final json in response.data) { - if (st.elapsedMilliseconds > _maxProcessingMillis) { - await Future.delayed(_processingSleep, () => st.reset()); + if (st.elapsedMilliseconds > maxProcessingMillis) { + await Future.delayed(processingSleep, () => st.reset()); } blocks.add( ConnectionMastodonExtensions.fromJson(json) @@ -1029,8 +1026,8 @@ class TimelineClient extends FriendicaClient { final finalResult = []; final st = Stopwatch()..start(); for (final json in postsJson) { - if (st.elapsedMilliseconds > _maxProcessingMillis) { - await Future.delayed(_processingSleep, () => st.reset()); + if (st.elapsedMilliseconds > maxProcessingMillis) { + await Future.delayed(processingSleep, () => st.reset()); } finalResult.add(TimelineEntryMastodonExtensions.fromJson(json)); } diff --git a/lib/globals.dart b/lib/globals.dart index 856064b..a1ddb3b 100644 --- a/lib/globals.dart +++ b/lib/globals.dart @@ -20,6 +20,9 @@ const usePhpDebugging = true; const maxViewPortalHeight = 750.0; const maxViewPortalWidth = 750.0; +const maxProcessingMillis = 3; +const processingSleep = Duration(milliseconds: 1); + Future showConfirmDialog(BuildContext context, String caption) { return showDialog( context: context, diff --git a/lib/services/notifications_manager.dart b/lib/services/notifications_manager.dart index 8ae49e5..3d6ec8b 100644 --- a/lib/services/notifications_manager.dart +++ b/lib/services/notifications_manager.dart @@ -1,7 +1,7 @@ +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:logging/logging.dart'; import 'package:result_monad/result_monad.dart'; -import 'package:uuid/uuid.dart'; import '../friendica_client/friendica_client.dart'; import '../friendica_client/paged_response.dart'; @@ -57,7 +57,7 @@ class NotificationsManager extends ChangeNotifier { } FutureResult, ExecError> updateNotifications() async { - const initialPull = 100; + const initialPull = 25; final notificationsFromRefresh = []; if (_pm.pages.isEmpty) { final result = await _pm.initialize(initialPull); @@ -151,75 +151,31 @@ class NotificationsManager extends ChangeNotifier { } } - getIt().startNotificationUpdate(); - await getIt>() - .getForProfile(profile) - .transformAsync((dms) async => await dms.updateThreads()); - - final useActualRequests = getIt() - .canUseFeature(RelaticaFeatures.usingActualFollowRequests); - - if (useActualRequests) { - await getIt>() - .getForProfile(profile) - .transformAsync((fm) async => fm.update()); - } - - final notifications = {}; - - notificationsFromRefresh.removeWhere((n) => - n.type == NotificationType.direct_message || - (useActualRequests && n.type == NotificationType.follow_request)); - for (final n in notificationsFromRefresh) { - notifications[n.id] = n; - } - - getIt().finishNotificationUpdate(); - for (final n in buildUnreadMessageNotifications(useActualRequests)) { - notifications[n.id] = n; - } - - _processNewNotifications(notifications.values, clearAtStart: true); - - notifyListeners(); - return Result.ok(notifications.values.toList()); + return await _postFetchOperations(notificationsFromRefresh, true); } FutureResult, ExecError> loadNewerNotifications() async { - final notifications = {}; - final result = await _pm.previousFromBeginning(); - result.match(onSuccess: (response) { - if (response.data.isEmpty) { - return; - } - for (final n in response.data) { - notifications[n.id] = n; - } - _processNewNotifications(notifications.values); - notifyListeners(); - }, onError: (error) { - _logger.info('Error getting more updates: $error'); - }); - - return result.mapValue((response) => response.data); + 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, ExecError> loadOlderNotifications() async { - final notifications = {}; - final result = await _pm.nextFromEnd(); - result.match(onSuccess: (response) { - for (final n in response.data) { - notifications[n.id] = n; - } - notifyListeners(); - }, onError: (error) { - _logger.info('Error getting more updates: $error'); - }); - - _processNewNotifications(notifications.values); - return result.mapValue((response) => response.data); + 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 markSeen(UserNotification notification) async { @@ -255,7 +211,10 @@ class NotificationsManager extends ChangeNotifier { final latestMessage = t.messages .reduce((s, m) => s.createdAt > m.createdAt ? s : m); return UserNotification( - id: const Uuid().v4(), + id: (fromAccount.hashCode ^ + t.parentUri.hashCode ^ + t.title.hashCode) + .toString(), type: NotificationType.direct_message, fromId: fromAccount.id, fromName: fromAccount.name, @@ -279,40 +238,126 @@ class NotificationsManager extends ChangeNotifier { return [...dmsResult, ...followRequestResult]; } + FutureResult, ExecError> _postFetchOperations( + List notificationsFromRefresh, + bool clearAtStart, + ) async { + getIt().startNotificationUpdate(); + await getIt>() + .getForProfile(profile) + .transformAsync((dms) async => await dms.updateThreads()); + + final useActualRequests = getIt() + .canUseFeature(RelaticaFeatures.usingActualFollowRequests); + + if (useActualRequests) { + await getIt>() + .getForProfile(profile) + .transformAsync((fm) async => fm.update()); + } + + final notifications = {}; + + notificationsFromRefresh.removeWhere((n) => + n.type == NotificationType.direct_message || + (useActualRequests && n.type == NotificationType.follow_request)); + for (final n in notificationsFromRefresh) { + notifications[n.id] = n; + } + + getIt().finishNotificationUpdate(); + for (final n in buildUnreadMessageNotifications(useActualRequests)) { + notifications[n.id] = n; + } + + _processNewNotifications(notifications.values, clearAtStart: clearAtStart); + + notifyListeners(); + return Result.ok(notifications.values.toList()); + } + Future _processNewNotifications( Iterable notifications, { bool clearAtStart = false, }) async { - if (clearAtStart) { - dms.clear(); - connectionRequests.clear(); - unread.clear(); - read.clear(); + final dmsMap = {}; + final crMap = {}; + final unreadMap = {}; + final readMap = {}; + + final st = Stopwatch()..start(); + if (!clearAtStart) { + for (int i = 0; i < dms.length; i++) { + dmsMap[dms[i].id] = dms[i]; + } + + if (st.elapsedMilliseconds > maxProcessingMillis) { + await Future.delayed(processingSleep, () => st.reset()); + } + + for (int i = 0; i < connectionRequests.length; i++) { + crMap[connectionRequests[i].id] = connectionRequests[i]; + } + + if (st.elapsedMilliseconds > maxProcessingMillis) { + await Future.delayed(processingSleep, () => st.reset()); + } + + for (int i = 0; i < unread.length; i++) { + unreadMap[unread[i].id] = unread[i]; + } + + if (st.elapsedMilliseconds > maxProcessingMillis) { + await Future.delayed(processingSleep, () => st.reset()); + } + + for (int i = 0; i < read.length; i++) { + readMap[read[i].id] = read[i]; + } } + dms.clear(); + connectionRequests.clear(); + unread.clear(); + read.clear(); for (final n in notifications) { + if (st.elapsedMilliseconds > maxProcessingMillis) { + await Future.delayed(processingSleep, () => st.reset()); + } + dmsMap.remove(n.id); + crMap.remove(n.id); + unreadMap.remove(n.id); + readMap.remove(n.id); if (n.dismissed) { - read.add(n); + readMap[n.id] = n; continue; } switch (n.type) { case NotificationType.direct_message: - dms.add(n); + dmsMap[n.id] = n; break; case NotificationType.follow: case NotificationType.follow_request: - connectionRequests.add(n); + crMap[n.id] = n; break; default: - unread.add(n); + unreadMap[n.id] = n; } } - dms.sort(); - connectionRequests.sort(); - unread.sort(); - read.sort(); + dms + ..addAll(dmsMap.values) + ..sort(); + connectionRequests + ..addAll(crMap.values) + ..sort(); + unread + ..addAll(unreadMap.values) + ..sort(); + read + ..addAll(readMap.values) + ..sort(); } static FutureResult>, ExecError>