2024-12-13 03:16:54 +00:00
|
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
2022-11-19 05:00:17 +00:00
|
|
|
import 'package:logging/logging.dart';
|
|
|
|
import 'package:result_monad/result_monad.dart';
|
2024-11-27 02:18:08 +00:00
|
|
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
2024-12-19 21:24:37 +00:00
|
|
|
import 'package:stack_trace/stack_trace.dart';
|
2022-11-19 05:00:17 +00:00
|
|
|
|
|
|
|
import '../globals.dart';
|
2023-04-29 01:28:43 +00:00
|
|
|
import '../models/auth/profile.dart';
|
2024-12-20 01:23:23 +00:00
|
|
|
import '../models/connection.dart';
|
2024-11-27 02:44:28 +00:00
|
|
|
import '../models/exec_error.dart';
|
2024-12-10 13:49:20 +00:00
|
|
|
import '../models/networking/paged_response.dart';
|
|
|
|
import '../models/networking/pages_manager.dart';
|
|
|
|
import '../models/networking/paging_data.dart';
|
2024-11-27 02:44:28 +00:00
|
|
|
import '../models/user_notification.dart';
|
|
|
|
import '../serializers/mastodon/follow_request_mastodon_extensions.dart';
|
2025-06-11 19:18:33 +00:00
|
|
|
import '../utils/list_utils.dart';
|
2024-12-20 01:23:23 +00:00
|
|
|
import 'connection_manager_services.dart';
|
2024-11-27 02:44:28 +00:00
|
|
|
import 'direct_message_services.dart';
|
2024-12-10 11:54:45 +00:00
|
|
|
import 'feature_checker_services.dart';
|
2024-12-07 14:08:05 +00:00
|
|
|
import 'follow_requests_services.dart';
|
2024-12-13 03:16:54 +00:00
|
|
|
import 'networking/friendica_notifications_client_services.dart';
|
2024-11-27 02:18:08 +00:00
|
|
|
|
|
|
|
part 'notification_services.g.dart';
|
|
|
|
|
|
|
|
const _itemsPerQuery = 50;
|
|
|
|
const _minimumDmsAndCrsUpdateDuration = Duration(seconds: 30);
|
|
|
|
final _logger = Logger('NotificationManager');
|
|
|
|
|
2025-06-10 21:31:28 +00:00
|
|
|
@Riverpod(keepAlive: true)
|
2025-06-11 18:22:54 +00:00
|
|
|
class _NotificationsStore extends _$NotificationsStore {
|
2025-06-10 21:31:28 +00:00
|
|
|
final _values = <UserNotification>{};
|
|
|
|
|
|
|
|
@override
|
|
|
|
List<UserNotification> build(Profile profile, NotificationType type) {
|
2025-06-11 18:22:54 +00:00
|
|
|
_logger.fine('Build _NotificationStoreProvider($profile) for $type');
|
2025-06-10 21:31:28 +00:00
|
|
|
return _rval;
|
|
|
|
}
|
|
|
|
|
|
|
|
void upsert(UserNotification notification) {
|
|
|
|
_values.remove(notification);
|
|
|
|
_values.add(notification);
|
|
|
|
state = _rval;
|
|
|
|
}
|
|
|
|
|
|
|
|
void clear() {
|
|
|
|
_values.clear();
|
|
|
|
state = _rval;
|
|
|
|
}
|
|
|
|
|
|
|
|
void markAllRead() {
|
2025-06-20 02:50:14 +00:00
|
|
|
final updated = _values.map((n) => n.copy(dismissed: true)).toList();
|
2025-06-10 21:31:28 +00:00
|
|
|
_values.clear();
|
|
|
|
_values.addAll(updated);
|
2025-06-11 19:18:33 +00:00
|
|
|
state = _rval;
|
2025-06-10 21:31:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
List<UserNotification> get _rval => _values.toList()..sort();
|
|
|
|
|
|
|
|
@override
|
|
|
|
bool updateShouldNotify(
|
|
|
|
List<UserNotification> previous, List<UserNotification> next) {
|
2025-06-11 19:18:33 +00:00
|
|
|
final rval = !listEqualsWithComparer(
|
|
|
|
previous,
|
|
|
|
next,
|
|
|
|
equals: (u1, u2) => u1.exactlyEqual(u2),
|
|
|
|
);
|
|
|
|
|
|
|
|
return rval;
|
2025-06-10 21:31:28 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@riverpod
|
2025-06-11 18:22:54 +00:00
|
|
|
List<UserNotification> userNotificationsByType(
|
2025-06-10 21:31:28 +00:00
|
|
|
Ref ref, Profile profile, NotificationType type, bool isRead) {
|
2025-06-11 18:22:54 +00:00
|
|
|
_logger.fine('Build userNotificationsByTypeProvider($type,$isRead,$profile)');
|
|
|
|
final notifications = ref.watch(_NotificationsStoreProvider(profile, type));
|
2025-06-10 21:31:28 +00:00
|
|
|
return notifications.where((n) => n.dismissed == isRead).toList();
|
|
|
|
}
|
|
|
|
|
|
|
|
@riverpod
|
2025-06-11 18:22:54 +00:00
|
|
|
List<UserNotification> allNotifications(Ref ref, Profile profile, bool isRead) {
|
|
|
|
_logger.fine('Build allNotificationsProvider($profile)');
|
|
|
|
|
|
|
|
final notifications = <UserNotification>[];
|
|
|
|
for (NotificationType type in NotificationType.values) {
|
|
|
|
notifications.addAll(
|
|
|
|
ref.watch(userNotificationsByTypeProvider(profile, type, isRead)));
|
|
|
|
}
|
|
|
|
|
|
|
|
return notifications..sort();
|
|
|
|
}
|
|
|
|
|
|
|
|
@riverpod
|
|
|
|
bool hasNotifications(Ref ref, Profile profile, bool isRead) {
|
2025-06-10 21:31:28 +00:00
|
|
|
_logger.info('Build hasNotifications($profile)');
|
|
|
|
|
2025-06-11 18:22:54 +00:00
|
|
|
var hasNotifications = false;
|
2025-06-10 21:31:28 +00:00
|
|
|
// Go through all to watch all for changes
|
|
|
|
for (NotificationType type in NotificationType.values) {
|
2025-06-11 18:22:54 +00:00
|
|
|
hasNotifications |= ref
|
|
|
|
.watch(userNotificationsByTypeProvider(profile, type, isRead))
|
|
|
|
.isNotEmpty;
|
2025-06-10 21:31:28 +00:00
|
|
|
}
|
|
|
|
|
2025-06-11 18:22:54 +00:00
|
|
|
return hasNotifications;
|
|
|
|
}
|
|
|
|
|
|
|
|
@riverpod
|
|
|
|
bool hasAnyNotifications(Ref ref, Profile profile) {
|
|
|
|
_logger.info('Build hasNotifications($profile)');
|
|
|
|
final hasRead = ref.watch(hasNotificationsProvider(profile, true));
|
|
|
|
final hasUnread = ref.watch(hasNotificationsProvider(profile, false));
|
|
|
|
return hasRead || hasUnread;
|
|
|
|
}
|
|
|
|
|
|
|
|
@riverpod
|
|
|
|
(int low, int high) lowHighId(Ref ref, Profile profile, bool isRead) {
|
|
|
|
_logger.fine('Build lowHighIdProvider($profile) for isRead? $isRead');
|
|
|
|
final notifications = <UserNotification>[];
|
|
|
|
for (NotificationType type in NotificationType.values) {
|
|
|
|
notifications.addAll(
|
|
|
|
ref.watch(userNotificationsByTypeProvider(profile, type, isRead)));
|
|
|
|
}
|
|
|
|
|
|
|
|
final result = calcLowHigh(notifications);
|
|
|
|
_logger.finest(
|
|
|
|
'Result lowHighIdProvider($profile) for isRead? $isRead: $result');
|
|
|
|
|
|
|
|
return result;
|
2025-06-10 21:31:28 +00:00
|
|
|
}
|
|
|
|
|
2024-11-27 02:18:08 +00:00
|
|
|
@Riverpod(keepAlive: true)
|
|
|
|
class NotificationsManager extends _$NotificationsManager {
|
2023-08-04 16:34:51 +00:00
|
|
|
var lastDmsUpdate = DateTime(1900);
|
|
|
|
var lastCrUpdate = DateTime(1900);
|
2024-11-27 02:18:08 +00:00
|
|
|
|
|
|
|
@override
|
2025-06-11 18:22:54 +00:00
|
|
|
Future<bool> build(Profile profile) async {
|
2024-11-27 02:44:28 +00:00
|
|
|
_logger.info('Building');
|
2022-11-19 05:00:17 +00:00
|
|
|
|
2024-12-19 20:36:33 +00:00
|
|
|
await _initialize();
|
2025-06-11 18:22:54 +00:00
|
|
|
return true;
|
2024-12-19 20:36:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
Future<void> _initialize() async {
|
2025-06-16 13:15:43 +00:00
|
|
|
final result = await loadUnreadNotifications();
|
2025-06-16 12:40:01 +00:00
|
|
|
final hasNoNotifications = !ref.read(hasAnyNotificationsProvider(profile));
|
2025-06-11 18:22:54 +00:00
|
|
|
if (result.isSuccess && hasNoNotifications) {
|
2025-06-16 13:15:43 +00:00
|
|
|
await loadOlderNotifications();
|
2024-12-19 20:36:33 +00:00
|
|
|
}
|
2024-11-27 02:18:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void refreshNotifications() async {
|
2025-06-16 12:40:01 +00:00
|
|
|
for (final t in NotificationType.values) {
|
|
|
|
ref.read(_NotificationsStoreProvider(profile, t).notifier).clear();
|
|
|
|
}
|
|
|
|
_initialize();
|
2022-11-19 05:00:17 +00:00
|
|
|
}
|
|
|
|
|
2025-01-26 20:32:49 +00:00
|
|
|
Future<void> clearConnectionRequestNotifications() async {
|
|
|
|
_logger.info('clearConnectionRequestNotifications');
|
2025-06-10 21:31:28 +00:00
|
|
|
ref
|
2025-06-11 18:22:54 +00:00
|
|
|
.read(_NotificationsStoreProvider(
|
2025-06-10 21:31:28 +00:00
|
|
|
profile, NotificationType.follow_request)
|
|
|
|
.notifier)
|
|
|
|
.clear();
|
2025-06-11 18:22:54 +00:00
|
|
|
state = const AsyncData(true);
|
2025-01-26 20:32:49 +00:00
|
|
|
}
|
|
|
|
|
2024-12-17 03:47:44 +00:00
|
|
|
Future<void> refreshConnectionRequestNotifications() async {
|
|
|
|
_logger.info('refreshConnectionRequestNotifications');
|
2025-01-26 20:32:49 +00:00
|
|
|
clearConnectionRequestNotifications();
|
2024-12-20 01:23:23 +00:00
|
|
|
await _postFetchOperations(
|
|
|
|
[],
|
|
|
|
updateDms: false,
|
|
|
|
updateFollowRequests: true,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
Future<void> refreshDms() async {
|
|
|
|
_logger.info('refreshDms');
|
2025-06-10 21:31:28 +00:00
|
|
|
ref
|
2025-06-11 18:22:54 +00:00
|
|
|
.read(_NotificationsStoreProvider(
|
2025-06-10 21:31:28 +00:00
|
|
|
profile, NotificationType.direct_message)
|
|
|
|
.notifier)
|
|
|
|
.clear();
|
2024-12-20 01:23:23 +00:00
|
|
|
await _postFetchOperations(
|
|
|
|
[],
|
|
|
|
updateDms: true,
|
|
|
|
updateFollowRequests: false,
|
|
|
|
);
|
2024-12-17 03:47:44 +00:00
|
|
|
}
|
|
|
|
|
2025-06-16 13:15:43 +00:00
|
|
|
FutureResult<bool, ExecError> loadUnreadNotifications() async {
|
2023-03-21 18:27:38 +00:00
|
|
|
final notificationsFromRefresh = <UserNotification>[];
|
2023-08-04 16:34:51 +00:00
|
|
|
|
2024-12-13 03:16:54 +00:00
|
|
|
final pm = _buildPageManager(ref, profile, false);
|
2024-12-10 11:54:45 +00:00
|
|
|
final useActualRequests = ref.read(featureCheckProvider(
|
|
|
|
profile,
|
|
|
|
RelaticaFeatures.usingActualFollowRequests,
|
|
|
|
));
|
2023-08-04 16:34:51 +00:00
|
|
|
var hasMore = true;
|
|
|
|
var first = true;
|
2023-11-16 22:37:22 +00:00
|
|
|
const maxCalls = 3;
|
|
|
|
var count = 0;
|
|
|
|
while (hasMore && count < maxCalls) {
|
2023-08-04 16:34:51 +00:00
|
|
|
final result =
|
2024-11-27 02:18:08 +00:00
|
|
|
first ? await pm.initialize(_itemsPerQuery) : await pm.nextFromEnd();
|
2023-08-04 16:34:51 +00:00
|
|
|
|
|
|
|
first = false;
|
|
|
|
result.match(
|
2024-07-26 14:15:24 +00:00
|
|
|
onSuccess: (nd) =>
|
|
|
|
_logger.fine('Got ${nd.data.length} notifications'),
|
2024-12-19 21:24:37 +00:00
|
|
|
onError: (e) => _logger.severe(
|
|
|
|
'Error getting notification: $e',
|
|
|
|
Trace.current(),
|
|
|
|
));
|
2023-08-04 16:34:51 +00:00
|
|
|
final response = result.getValueOrElse(() => PagedResponse([]));
|
|
|
|
response.data
|
|
|
|
.where((n) =>
|
|
|
|
!useActualRequests || n.type != NotificationType.follow_request)
|
|
|
|
.forEach(notificationsFromRefresh.add);
|
|
|
|
hasMore = response.next != null;
|
2023-11-16 22:37:22 +00:00
|
|
|
count++;
|
2022-11-19 05:00:17 +00:00
|
|
|
}
|
|
|
|
|
2023-08-04 16:34:51 +00:00
|
|
|
// 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,
|
2024-12-19 20:36:33 +00:00
|
|
|
).mapValue((value) => value.isNotEmpty);
|
2022-11-19 05:00:17 +00:00
|
|
|
}
|
|
|
|
|
2024-11-27 02:18:08 +00:00
|
|
|
FutureResult<List<UserNotification>, ExecError> _postFetchOperations(
|
2025-06-16 13:15:43 +00:00
|
|
|
List<UserNotification> notificationsFromRefresh, {
|
2024-12-17 03:47:44 +00:00
|
|
|
bool updateDms = true,
|
|
|
|
bool updateFollowRequests = true,
|
|
|
|
}) async {
|
|
|
|
if (updateDms) {
|
|
|
|
if (DateTime.now().difference(lastDmsUpdate) >
|
|
|
|
_minimumDmsAndCrsUpdateDuration) {
|
|
|
|
await ref
|
2024-12-17 16:43:28 +00:00
|
|
|
.read(directMessageThreadIdsProvider(profile).notifier)
|
2024-12-17 03:47:44 +00:00
|
|
|
.update();
|
|
|
|
lastDmsUpdate = DateTime.now();
|
|
|
|
}
|
2024-11-27 02:18:08 +00:00
|
|
|
}
|
|
|
|
|
2024-12-10 11:54:45 +00:00
|
|
|
final useActualRequests = ref.read(featureCheckProvider(
|
|
|
|
profile,
|
|
|
|
RelaticaFeatures.usingActualFollowRequests,
|
|
|
|
));
|
2024-12-17 03:47:44 +00:00
|
|
|
if (updateFollowRequests) {
|
|
|
|
if (useActualRequests) {
|
|
|
|
if (DateTime.now().difference(lastCrUpdate) >
|
|
|
|
_minimumDmsAndCrsUpdateDuration) {
|
|
|
|
await ref.read(followRequestsProvider(profile).notifier).update();
|
|
|
|
lastCrUpdate = DateTime.now();
|
|
|
|
}
|
2024-11-27 02:18:08 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
final notifications = <String, UserNotification>{};
|
|
|
|
|
|
|
|
notificationsFromRefresh.removeWhere((n) =>
|
|
|
|
n.type == NotificationType.direct_message ||
|
|
|
|
(useActualRequests && n.type == NotificationType.follow_request));
|
|
|
|
for (final n in notificationsFromRefresh) {
|
|
|
|
notifications[n.id] = n;
|
|
|
|
}
|
|
|
|
|
2024-12-19 20:36:33 +00:00
|
|
|
for (final n in _buildUnreadMessageNotifications(useActualRequests)) {
|
2024-11-27 02:18:08 +00:00
|
|
|
notifications[n.id] = n;
|
|
|
|
}
|
|
|
|
|
|
|
|
_processNewNotifications(notifications.values);
|
|
|
|
|
|
|
|
return Result.ok(notifications.values.toList());
|
|
|
|
}
|
|
|
|
|
2025-06-16 13:15:43 +00:00
|
|
|
FutureResult<bool, ExecError> loadNewerNotifications() async {
|
2025-06-11 18:22:54 +00:00
|
|
|
final hasNoNotifications = !ref.read(hasAnyNotificationsProvider(profile));
|
|
|
|
final useIsRead = !ref.read(hasNotificationsProvider(profile, false));
|
|
|
|
final (_, highestId) = ref.read(lowHighIdProvider(profile, useIsRead));
|
2023-10-31 01:44:16 +00:00
|
|
|
final pm = _buildPageManager(
|
2024-12-13 03:16:54 +00:00
|
|
|
ref,
|
2023-08-04 16:34:51 +00:00
|
|
|
profile,
|
|
|
|
true,
|
2025-06-11 18:22:54 +00:00
|
|
|
initialPages: hasNoNotifications
|
2023-11-16 14:39:25 +00:00
|
|
|
? []
|
|
|
|
: [
|
|
|
|
PagedResponse(
|
|
|
|
<String>[],
|
2023-11-16 22:37:22 +00:00
|
|
|
previous: PagingData(
|
|
|
|
minId: highestId,
|
2024-11-27 02:18:08 +00:00
|
|
|
limit: _itemsPerQuery,
|
2023-11-16 22:37:22 +00:00
|
|
|
),
|
2023-11-16 14:39:25 +00:00
|
|
|
)
|
|
|
|
],
|
2023-08-04 16:34:51 +00:00
|
|
|
);
|
2023-02-10 14:36:41 +00:00
|
|
|
|
2025-06-11 18:22:54 +00:00
|
|
|
final stillNoNotifications =
|
|
|
|
!ref.read(hasAnyNotificationsProvider(profile));
|
|
|
|
final result = await (stillNoNotifications
|
2024-11-27 02:18:08 +00:00
|
|
|
? pm.initialize(_itemsPerQuery)
|
2023-08-04 16:34:51 +00:00
|
|
|
: pm.previousFromBeginning())
|
2023-05-08 18:24:45 +00:00
|
|
|
.andThenAsync(
|
2025-06-16 13:15:43 +00:00
|
|
|
(page) async => await _postFetchOperations(page.data),
|
2023-05-08 18:24:45 +00:00
|
|
|
)
|
|
|
|
.withError(
|
|
|
|
(error) => _logger.info('Error getting more updates: $error'));
|
2024-12-19 20:36:33 +00:00
|
|
|
return result.mapValue((value) => value.isNotEmpty).execErrorCast();
|
2023-02-10 14:36:41 +00:00
|
|
|
}
|
|
|
|
|
2025-06-16 13:15:43 +00:00
|
|
|
FutureResult<bool, ExecError> loadOlderNotifications() async {
|
2025-06-11 18:22:54 +00:00
|
|
|
final hasUnread = ref.read(hasNotificationsProvider(profile, false));
|
|
|
|
if (hasUnread) {
|
2025-06-16 13:15:43 +00:00
|
|
|
final result = await _loadOlderUnreadNotifications();
|
2024-06-28 17:28:36 +00:00
|
|
|
final nonDmAndConnectionNotifications = result
|
|
|
|
.getValueOrElse(() => [])
|
|
|
|
.where((n) =>
|
|
|
|
n.type != NotificationType.follow_request &&
|
|
|
|
n.type != NotificationType.direct_message)
|
|
|
|
.toList();
|
|
|
|
if (nonDmAndConnectionNotifications.isNotEmpty) {
|
2024-12-19 20:36:33 +00:00
|
|
|
return Result.ok(true);
|
2023-11-16 22:37:22 +00:00
|
|
|
}
|
2023-08-04 16:34:51 +00:00
|
|
|
}
|
|
|
|
|
2025-06-16 13:15:43 +00:00
|
|
|
return _loadOlderReadAndUnreadNotifications()
|
2024-12-19 20:36:33 +00:00
|
|
|
.mapValue((value) => value.isNotEmpty);
|
2023-08-04 16:34:51 +00:00
|
|
|
}
|
|
|
|
|
2022-11-22 04:46:34 +00:00
|
|
|
FutureResult<bool, ExecError> markSeen(UserNotification notification) async {
|
2025-06-11 18:22:54 +00:00
|
|
|
_logger.fine('Marking Notification Seen: $notification');
|
2024-12-13 03:16:54 +00:00
|
|
|
final result = await ref
|
|
|
|
.read(clearNotificationProvider(profile, notification).future)
|
2023-08-04 16:34:51 +00:00
|
|
|
.withResult((_) {
|
2025-06-11 18:22:54 +00:00
|
|
|
ref
|
|
|
|
.read(
|
|
|
|
_NotificationsStoreProvider(profile, notification.type).notifier)
|
|
|
|
.upsert(notification.copy(dismissed: true));
|
2023-08-04 16:34:51 +00:00
|
|
|
});
|
2022-11-19 05:00:17 +00:00
|
|
|
|
2023-08-04 16:34:51 +00:00
|
|
|
return result.execErrorCast();
|
2022-11-19 05:00:17 +00:00
|
|
|
}
|
2022-11-19 19:16:46 +00:00
|
|
|
|
2023-08-04 16:34:51 +00:00
|
|
|
FutureResult<bool, ExecError> markAllAsRead() async {
|
2024-12-13 03:16:54 +00:00
|
|
|
final result = await ref
|
|
|
|
.read(clearNotificationsProvider(profile).future)
|
2024-11-27 02:18:08 +00:00
|
|
|
.withResult((_) {
|
2025-06-11 18:22:54 +00:00
|
|
|
for (final t in NotificationType.values) {
|
|
|
|
ref
|
|
|
|
.read(_NotificationsStoreProvider(profile, t).notifier)
|
|
|
|
.markAllRead();
|
|
|
|
}
|
2023-08-04 16:34:51 +00:00
|
|
|
});
|
2023-02-14 13:38:08 +00:00
|
|
|
|
2023-08-04 16:34:51 +00:00
|
|
|
return result.execErrorCast();
|
2022-11-19 19:16:46 +00:00
|
|
|
}
|
2023-02-08 15:41:29 +00:00
|
|
|
|
2024-12-19 20:36:33 +00:00
|
|
|
List<UserNotification> _buildUnreadMessageNotifications(
|
2023-03-21 18:27:38 +00:00
|
|
|
bool useActualRequests) {
|
2024-12-17 16:43:28 +00:00
|
|
|
final myId = profile.userId;
|
2024-11-27 02:18:08 +00:00
|
|
|
final dmsResult = ref
|
2024-12-17 16:43:28 +00:00
|
|
|
.watch(directMessageThreadIdsProvider(profile))
|
2025-06-11 19:18:33 +00:00
|
|
|
.map((id) => ref.read(directMessageThreadServiceProvider(profile, id)))
|
2024-11-27 02:18:08 +00:00
|
|
|
.where((t) => !t.allSeen)
|
|
|
|
.map((t) {
|
2024-12-20 01:23:23 +00:00
|
|
|
final fromAccountId = t.participantIds.firstWhere((pid) => pid != myId);
|
|
|
|
final fromAccount = ref
|
|
|
|
.watch(connectionByIdProvider(profile, fromAccountId))
|
|
|
|
.getValueOrElse(() => Connection());
|
2024-11-27 02:18:08 +00:00
|
|
|
final latestMessage =
|
|
|
|
t.messages.reduce((s, m) => s.createdAt > m.createdAt ? s : m);
|
|
|
|
return UserNotification(
|
|
|
|
id: (fromAccount.hashCode ^ t.parentUri.hashCode ^ t.title.hashCode)
|
|
|
|
.toString(),
|
|
|
|
type: NotificationType.direct_message,
|
|
|
|
fromId: fromAccount.id,
|
|
|
|
fromName: fromAccount.name,
|
|
|
|
fromUrl: fromAccount.profileUrl,
|
|
|
|
timestamp: latestMessage.createdAt,
|
|
|
|
iid: t.parentUri,
|
|
|
|
dismissed: false,
|
|
|
|
content: '${fromAccount.name} sent you a direct message',
|
|
|
|
link: '');
|
|
|
|
});
|
2023-02-08 15:41:29 +00:00
|
|
|
|
2023-03-21 18:27:38 +00:00
|
|
|
final followRequestResult = !useActualRequests
|
2024-10-02 17:17:48 +00:00
|
|
|
? <UserNotification>[]
|
2024-12-07 14:08:05 +00:00
|
|
|
: ref
|
|
|
|
.watch(followRequestListProvider(profile))
|
|
|
|
.map((r) => r.toUserNotification())
|
|
|
|
.toList();
|
2023-03-21 18:27:38 +00:00
|
|
|
|
2024-11-27 02:18:08 +00:00
|
|
|
return [...dmsResult, ...followRequestResult];
|
2023-02-08 15:41:29 +00:00
|
|
|
}
|
2023-02-10 14:36:41 +00:00
|
|
|
|
2023-04-29 01:06:21 +00:00
|
|
|
Future<void> _processNewNotifications(
|
2023-08-04 16:34:51 +00:00
|
|
|
Iterable<UserNotification> notifications) async {
|
2023-05-08 18:24:45 +00:00
|
|
|
final st = Stopwatch()..start();
|
|
|
|
|
2023-04-29 01:06:21 +00:00
|
|
|
for (final n in notifications) {
|
2023-05-08 18:24:45 +00:00
|
|
|
if (st.elapsedMilliseconds > maxProcessingMillis) {
|
|
|
|
await Future.delayed(processingSleep, () => st.reset());
|
|
|
|
}
|
2025-06-11 18:22:54 +00:00
|
|
|
ref.read(_NotificationsStoreProvider(profile, n.type).notifier).upsert(n);
|
2023-04-29 01:06:21 +00:00
|
|
|
}
|
2023-11-16 22:37:22 +00:00
|
|
|
}
|
|
|
|
|
2025-06-16 13:15:43 +00:00
|
|
|
FutureResult<List<UserNotification>, ExecError>
|
|
|
|
_loadOlderUnreadNotifications() async {
|
2024-11-27 02:44:28 +00:00
|
|
|
_logger.finest('Loading Older Unread Notifications');
|
2025-06-11 18:22:54 +00:00
|
|
|
final (lowestId, _) = ref.read(lowHighIdProvider(profile, false));
|
2023-11-16 22:37:22 +00:00
|
|
|
final pm = _buildPageManager(
|
2024-12-13 03:16:54 +00:00
|
|
|
ref,
|
2023-11-16 22:37:22 +00:00
|
|
|
profile,
|
|
|
|
false,
|
|
|
|
initialPages: [
|
|
|
|
PagedResponse(
|
|
|
|
<String>[],
|
|
|
|
next: PagingData(
|
|
|
|
maxId: lowestId,
|
2024-11-27 02:18:08 +00:00
|
|
|
limit: _itemsPerQuery,
|
2023-11-16 22:37:22 +00:00
|
|
|
),
|
|
|
|
)
|
|
|
|
],
|
|
|
|
);
|
|
|
|
|
|
|
|
final result = await pm
|
|
|
|
.nextFromEnd()
|
|
|
|
.andThenAsync(
|
2025-06-16 13:15:43 +00:00
|
|
|
(page) async => await _postFetchOperations(page.data),
|
2023-11-16 22:37:22 +00:00
|
|
|
)
|
|
|
|
.withError(
|
|
|
|
(error) => _logger.info('Error getting more updates: $error'));
|
|
|
|
|
2024-11-27 02:44:28 +00:00
|
|
|
_logger.finest(
|
|
|
|
'Loaded Older Unread Notifications: ${result.getValueOrElse(() => []).length}');
|
2023-11-16 22:37:22 +00:00
|
|
|
return result.execErrorCast();
|
|
|
|
}
|
|
|
|
|
|
|
|
FutureResult<List<UserNotification>, ExecError>
|
2025-06-16 13:15:43 +00:00
|
|
|
_loadOlderReadAndUnreadNotifications() async {
|
2024-11-27 02:44:28 +00:00
|
|
|
_logger.finest('Loading Older Read and Unread Notifications');
|
2025-06-11 18:22:54 +00:00
|
|
|
final hasNoNotifications = !ref.read(hasAnyNotificationsProvider(profile));
|
|
|
|
final useIsRead = ref.read(hasNotificationsProvider(profile, true));
|
|
|
|
final (lowestId, _) = ref.read(lowHighIdProvider(profile, useIsRead));
|
2023-11-16 22:37:22 +00:00
|
|
|
final pm = _buildPageManager(
|
2024-12-13 03:16:54 +00:00
|
|
|
ref,
|
2023-11-16 22:37:22 +00:00
|
|
|
profile,
|
|
|
|
true,
|
2025-06-11 18:22:54 +00:00
|
|
|
initialPages: hasNoNotifications
|
2023-11-16 22:37:22 +00:00
|
|
|
? []
|
|
|
|
: [
|
|
|
|
PagedResponse(
|
|
|
|
<String>[],
|
|
|
|
next: PagingData(
|
|
|
|
maxId: lowestId,
|
2024-11-27 02:18:08 +00:00
|
|
|
limit: _itemsPerQuery,
|
2023-11-16 22:37:22 +00:00
|
|
|
),
|
|
|
|
)
|
|
|
|
],
|
|
|
|
);
|
2025-06-11 18:22:54 +00:00
|
|
|
final result = await (hasNoNotifications
|
2024-11-27 02:18:08 +00:00
|
|
|
? pm.initialize(_itemsPerQuery)
|
2023-11-16 22:37:22 +00:00
|
|
|
: pm.nextFromEnd())
|
|
|
|
.andThenAsync(
|
2025-06-16 13:15:43 +00:00
|
|
|
(page) async => await _postFetchOperations(page.data),
|
2023-11-16 22:37:22 +00:00
|
|
|
)
|
|
|
|
.withError(
|
|
|
|
(error) => _logger.info('Error getting more updates: $error'));
|
2024-11-27 02:44:28 +00:00
|
|
|
_logger.finest(
|
|
|
|
'Loaded Older Read and Unread Notifications: ${result.getValueOrElse(() => []).length}');
|
2023-11-16 22:37:22 +00:00
|
|
|
return result.execErrorCast();
|
2023-04-29 01:06:21 +00:00
|
|
|
}
|
2023-08-04 16:34:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
(int lowest, int highest) calcLowHigh(List<UserNotification> notifications) {
|
|
|
|
int highestNotificationId = -1;
|
|
|
|
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;
|
|
|
|
}
|
2023-04-29 01:06:21 +00:00
|
|
|
|
2023-08-04 16:34:51 +00:00
|
|
|
if (id < lowestNotificationId) {
|
|
|
|
lowestNotificationId = id;
|
|
|
|
}
|
2023-02-10 14:36:41 +00:00
|
|
|
}
|
2023-08-04 16:34:51 +00:00
|
|
|
|
|
|
|
return (lowestNotificationId, highestNotificationId);
|
2022-11-19 05:00:17 +00:00
|
|
|
}
|
2023-08-04 16:34:51 +00:00
|
|
|
|
|
|
|
PagesManager<List<UserNotification>, String> _buildPageManager(
|
2024-12-13 03:16:54 +00:00
|
|
|
Ref ref,
|
|
|
|
Profile profile,
|
|
|
|
bool includeAll, {
|
|
|
|
List<PagedResponse> initialPages = const [],
|
|
|
|
}) =>
|
2023-08-04 16:34:51 +00:00
|
|
|
PagesManager<List<UserNotification>, String>(
|
|
|
|
initialPages: initialPages,
|
|
|
|
idMapper: (nn) => nn.map((n) => n.id).toList(),
|
2024-12-13 03:16:54 +00:00
|
|
|
onRequest: (pd) async => await ref
|
|
|
|
.read(notificationsClientProvider(profile, pd, includeAll).future),
|
2023-08-04 16:34:51 +00:00
|
|
|
);
|