import 'package:flutter/material.dart'; import 'package:logging/logging.dart'; import 'package:relatica/services/network_status_service.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 'auth_service.dart'; import 'direct_message_service.dart'; class NotificationsManager extends ChangeNotifier { static final _logger = Logger('NotificationManager'); final _notifications = {}; final _pm = PagesManager, String>( idMapper: (nn) => nn.map((n) => n.id).toList(), onRequest: _clientGetNotificationsRequest, ); List get notifications { final result = List.from(_notifications.values); result.sort((n1, n2) { if (n1.dismissed == n2.dismissed) { return n2.timestamp.compareTo(n1.timestamp); } if (n1.dismissed && !n2.dismissed) { return 1; } return -1; }); return result; } void clear() { _notifications.clear(); } FutureResult, ExecError> updateNotifications() async { const initialPull = 25; final nn = []; if (_pm.pages.isEmpty) { final result = await _pm.initialize(initialPull); result.andThenSuccess((response) => nn.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(page.previous!); 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) => nn.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) => nn.addAll(response.data), onError: (error) => _logger.severe('Error getting previous page: $error')); } } for (final n in nn) { _notifications[n.id] = n; } _notifications.removeWhere( (key, value) => value.type == NotificationType.direct_message, ); getIt().startNotificationUpdate(); await getIt().updateThreads(); getIt().finishNotificationUpdate(); for (final n in buildUnreadMessageNotifications()) { _notifications[n.id] = n; } notifyListeners(); return Result.ok(notifications); } FutureResult, ExecError> loadNewerNotifications() async { final result = await _pm.previousFromBeginning(); 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, 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 markSeen(UserNotification notification) async { final result = await NotificationsClient(getIt().currentProfile) .clearNotification(notification); if (result.isSuccess) { notifyListeners(); } updateNotifications(); return result; } FutureResult, ExecError> markAllAsRead() async { final result = await NotificationsClient(getIt().currentProfile) .clearNotifications(); if (result.isFailure) { return result.errorCast(); } _pm.clear(); _notifications.clear(); return updateNotifications(); } List buildUnreadMessageNotifications() { final myId = getIt().currentProfile.userId; final result = getIt().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(); return result; } static FutureResult>, ExecError> _clientGetNotificationsRequest(PagingData page) async { final result = await NotificationsClient(getIt().currentProfile) .getNotifications(page); return result; } }