relatica/lib/services/notifications_manager.dart

266 wiersze
8.8 KiB
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';
import '../friendica_client/pages_manager.dart';
import '../friendica_client/paging_data.dart';
import '../globals.dart';
import '../models/exec_error.dart';
import '../models/user_notification.dart';
import '../serializers/mastodon/follow_request_mastodon_extensions.dart';
import '../utils/active_profile_selector.dart';
import 'auth_service.dart';
import 'direct_message_service.dart';
import 'feature_version_checker.dart';
import 'follow_requests_manager.dart';
import 'network_status_service.dart';
class NotificationsManager extends ChangeNotifier {
static final _logger = Logger('NotificationManager');
final _notifications = <String, UserNotification>{};
final _pm = PagesManager<List<UserNotification>, String>(
idMapper: (nn) => nn.map((n) => n.id).toList(),
onRequest: _clientGetNotificationsRequest,
);
var _firstLoad = true;
List<UserNotification> get notifications {
if (_notifications.isEmpty && _firstLoad) {
updateNotifications();
_firstLoad = false;
}
final dms = <UserNotification>[];
final connectionRequests = <UserNotification>[];
final unread = <UserNotification>[];
final read = <UserNotification>[];
for (final n in _notifications.values) {
if (n.dismissed) {
read.add(n);
continue;
}
switch (n.type) {
case NotificationType.direct_message:
dms.add(n);
break;
case NotificationType.follow:
case NotificationType.follow_request:
connectionRequests.add(n);
break;
default:
unread.add(n);
}
}
dms.sort();
connectionRequests.sort();
unread.sort();
read.sort();
return [...connectionRequests, ...dms, ...unread, ...read];
}
void clear() {
_notifications.clear();
_pm.clear();
}
FutureResult<List<UserNotification>, ExecError> updateNotifications() async {
const initialPull = 100;
final notificationsFromRefresh = <UserNotification>[];
if (_pm.pages.isEmpty) {
final result = await _pm.initialize(initialPull);
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];
PagingData? pd;
if (i == 0) {
if (page.next == null) {
_logger.severe(
"Expected first page to have a next page so can query on this page of data but doesn't exist.");
continue;
}
final response = await _clientGetNotificationsRequest(page.next!);
response.match(
onSuccess: (response) => pd = response.previous,
onError: (error) =>
_logger.severe('Error getting previous page: $error'));
if (pd == null && page.previous == null) {
_logger.severe(
'Next page returned no results and no previous page so need to re-initalize');
} else {
final response = await _clientGetNotificationsRequest(pd!);
response.match(
onSuccess: (response) => pd = response.next,
onError: (error) =>
_logger.severe('Error getting previous page: $error'));
}
if (pd == null) {
_logger.severe(
'Previous and next page both returned nulls so need to reinitialize');
_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(page.next!);
response.match(
onSuccess: (response) =>
notificationsFromRefresh.addAll(response.data),
onError: (error) =>
_logger.severe('Error getting previous page: $error'));
}
}
getIt<NetworkStatusService>().startNotificationUpdate();
await getIt<ActiveProfileSelector<DirectMessageService>>()
.activeEntry
.andThenSuccessAsync((dms) async => await dms.updateThreads());
final useActualRequests = getIt<FriendicaVersionChecker>()
.canUseFeature(RelaticaFeatures.usingActualFollowRequests);
if (useActualRequests) {
await getIt<ActiveProfileSelector<FollowRequestsManager>>()
.activeEntry
.andThenSuccessAsync((fm) async => fm.update());
}
_notifications.clear();
notificationsFromRefresh.removeWhere((n) =>
n.type == NotificationType.direct_message ||
(useActualRequests && n.type == NotificationType.follow_request));
for (final n in notificationsFromRefresh) {
_notifications[n.id] = n;
}
getIt<NetworkStatusService>().finishNotificationUpdate();
for (final n in buildUnreadMessageNotifications(useActualRequests)) {
_notifications[n.id] = n;
}
notifyListeners();
return Result.ok(notifications);
}
FutureResult<List<UserNotification>, ExecError>
loadNewerNotifications() async {
final result = await _pm.previousFromBeginning();
result.match(onSuccess: (response) {
if (response.data.isEmpty) {
return;
}
for (final n in response.data) {
_notifications[n.id] = n;
}
notifyListeners();
}, onError: (error) {
_logger.info('Error getting more updates: $error');
});
return result.mapValue((response) => response.data);
}
FutureResult<List<UserNotification>, ExecError>
loadOlderNotifications() async {
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');
});
return result.mapValue((response) => response.data);
}
FutureResult<bool, ExecError> markSeen(UserNotification notification) async {
final result =
await NotificationsClient(getIt<AccountsService>().currentProfile)
.clearNotification(notification);
if (result.isSuccess) {
notifyListeners();
}
updateNotifications();
return result;
}
FutureResult<List<UserNotification>, ExecError> markAllAsRead() async {
final result =
await NotificationsClient(getIt<AccountsService>().currentProfile)
.clearNotifications();
if (result.isFailure) {
return result.errorCast();
}
_pm.clear();
_notifications.clear();
return updateNotifications();
}
List<UserNotification> buildUnreadMessageNotifications(
bool useActualRequests) {
final myId = getIt<AccountsService>().currentProfile.userId;
final dmsResult = getIt<ActiveProfileSelector<DirectMessageService>>()
.activeEntry
.andThenSuccess((d) => d.getThreads(unreadyOnly: true).map((t) {
final fromAccount =
t.participants.firstWhere((p) => p.id != myId);
final latestMessage = t.messages
.reduce((s, m) => s.createdAt > m.createdAt ? s : m);
return UserNotification(
id: const Uuid().v4(),
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: '');
}).toList())
.getValueOrElse(() => []);
final followRequestResult = !useActualRequests
? []
: getIt<ActiveProfileSelector<FollowRequestsManager>>()
.activeEntry
.andThenSuccess(
(fm) => fm.requests.map((r) => r.toUserNotification()).toList())
.getValueOrElse(() => []);
return [...dmsResult, ...followRequestResult];
}
static FutureResult<PagedResponse<List<UserNotification>>, ExecError>
_clientGetNotificationsRequest(PagingData page) async {
final result =
await NotificationsClient(getIt<AccountsService>().currentProfile)
.getNotifications(page);
return result;
}
}