mirror of
https://gitlab.com/mysocialportal/relatica
synced 2024-10-18 15:53:32 +00:00
Merge branch 'notifications-enhancement' into 'main'
Notifications enhancement See merge request mysocialportal/relatica!19
This commit is contained in:
commit
04fa5395d3
11 changed files with 127 additions and 27 deletions
|
@ -92,6 +92,12 @@ class NotificationControl extends StatelessWidget {
|
|||
_goToStatus(context);
|
||||
};
|
||||
break;
|
||||
case NotificationType.direct_message:
|
||||
onTap = () => context.pushNamed(
|
||||
ScreenPaths.thread,
|
||||
queryParams: {'uri': notification.iid},
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
return ListTile(
|
||||
|
@ -110,7 +116,8 @@ class NotificationControl extends StatelessWidget {
|
|||
ElapsedDateUtils.epochSecondsToString(notification.timestamp),
|
||||
),
|
||||
),
|
||||
trailing: notification.dismissed
|
||||
trailing: notification.dismissed ||
|
||||
notification.type == NotificationType.direct_message
|
||||
? null
|
||||
: IconButton(
|
||||
onPressed: () async {
|
||||
|
|
|
@ -24,15 +24,18 @@ class ObjectBoxConnectionsRepo implements IConnectionsRepo {
|
|||
|
||||
@override
|
||||
bool addAllConnections(Iterable<Connection> newConnections) {
|
||||
memCache.addAllConnections(newConnections);
|
||||
final result = box.putMany(newConnections.toList());
|
||||
return result.length == newConnections.length;
|
||||
var allNew = true;
|
||||
for (final c in newConnections) {
|
||||
allNew &= addConnection(c);
|
||||
}
|
||||
return allNew;
|
||||
}
|
||||
|
||||
@override
|
||||
bool addConnection(Connection connection) {
|
||||
memCache.addConnection(connection);
|
||||
box.putAsync(connection);
|
||||
if (memCache.addConnection(connection)) {
|
||||
box.putAsync(connection);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -232,6 +232,23 @@ class FriendicaClient {
|
|||
return Result.ok(connection.copy(status: status));
|
||||
}
|
||||
|
||||
FutureResult<PagedResponse<List<Connection>>, ExecError>
|
||||
getConnectionRequests(PagingData page) async {
|
||||
_logger.finest(() => 'Getting connection requests with page data $page');
|
||||
_networkStatusService.startConnectionUpdateStatus();
|
||||
final baseUrl = 'https://$serverName/api/v1/follow_requests';
|
||||
final result1 = await _getApiListRequest(
|
||||
Uri.parse('$baseUrl&${page.toQueryParameters()}'),
|
||||
);
|
||||
|
||||
_networkStatusService.finishConnectionUpdateStatus();
|
||||
return result1
|
||||
.andThenSuccess((response) => response.map((jsonArray) => jsonArray
|
||||
.map((json) => ConnectionMastodonExtensions.fromJson(json))
|
||||
.toList()))
|
||||
.execErrorCast();
|
||||
}
|
||||
|
||||
// TODO Convert groups for connection to using paging for real (if available)
|
||||
FutureResult<List<GroupData>, ExecError> getMemberGroupsForConnection(
|
||||
String connectionId) async {
|
||||
|
@ -578,20 +595,25 @@ class FriendicaClient {
|
|||
|
||||
FutureResult<List<DirectMessage>, ExecError> getDirectMessages(
|
||||
PagingData page) async {
|
||||
_networkStatusService.startDirectMessageUpdateStatus();
|
||||
final baseUrl = 'https://$serverName/api/direct_messages/all';
|
||||
final pagingQP = page.toQueryParameters(limitKeyword: 'count');
|
||||
final url = '$baseUrl?$pagingQP';
|
||||
final request = Uri.parse(url);
|
||||
_logger.finest(() => 'Getting direct messages with paging data $page');
|
||||
return (await _getApiListRequest(request).andThenSuccessAsync(
|
||||
final result = (await _getApiListRequest(request).andThenSuccessAsync(
|
||||
(response) async => response.data
|
||||
.map((json) => DirectMessageFriendicaExtension.fromJson(json))
|
||||
.toList()))
|
||||
.execErrorCast();
|
||||
|
||||
_networkStatusService.finishDirectMessageUpdateStatus();
|
||||
return result;
|
||||
}
|
||||
|
||||
FutureResult<DirectMessage, ExecError> markDirectMessageRead(
|
||||
DirectMessage message) async {
|
||||
_networkStatusService.startDirectMessageUpdateStatus();
|
||||
final id = message.id;
|
||||
final url = Uri.parse(
|
||||
'https://$serverName/api/friendica/direct_messages_setseen?id=$id');
|
||||
|
@ -599,7 +621,7 @@ class FriendicaClient {
|
|||
await _postUrl(url, {}).andThenSuccessAsync((jsonString) async {
|
||||
return message.copy(seen: true);
|
||||
});
|
||||
|
||||
_networkStatusService.finishDirectMessageUpdateStatus();
|
||||
return result.execErrorCast();
|
||||
}
|
||||
|
||||
|
@ -608,6 +630,7 @@ class FriendicaClient {
|
|||
String receivingUserId,
|
||||
String text,
|
||||
) async {
|
||||
_networkStatusService.startDirectMessageUpdateStatus();
|
||||
final url = Uri.parse('https://$serverName/api/direct_messages/new');
|
||||
final body = {
|
||||
'user_id': receivingUserId,
|
||||
|
@ -626,6 +649,7 @@ class FriendicaClient {
|
|||
DirectMessageFriendicaExtension.fromJson(jsonDecode(jsonString)));
|
||||
});
|
||||
|
||||
_networkStatusService.finishDirectMessageUpdateStatus();
|
||||
return result.execErrorCast();
|
||||
}
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@ enum NotificationType {
|
|||
reshare,
|
||||
reblog,
|
||||
status,
|
||||
direct_message,
|
||||
unknown;
|
||||
|
||||
String toVerb() {
|
||||
|
@ -27,6 +28,8 @@ enum NotificationType {
|
|||
return 'reshared';
|
||||
case NotificationType.status:
|
||||
return 'updated';
|
||||
case NotificationType.direct_message:
|
||||
return 'has sent you a new direct message';
|
||||
case NotificationType.unknown:
|
||||
return 'unknowned';
|
||||
}
|
||||
|
|
|
@ -4,16 +4,25 @@ import 'package:provider/provider.dart';
|
|||
|
||||
import '../controls/image_control.dart';
|
||||
import '../controls/standard_appbar.dart';
|
||||
import '../controls/status_and_refresh_button.dart';
|
||||
import '../globals.dart';
|
||||
import '../routes.dart';
|
||||
import '../services/direct_message_service.dart';
|
||||
import '../services/network_status_service.dart';
|
||||
import '../utils/dateutils.dart';
|
||||
|
||||
class MessagesScreen extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final service = context.watch<DirectMessageService>();
|
||||
final nss = getIt<NetworkStatusService>();
|
||||
return Scaffold(
|
||||
appBar: StandardAppBar.build(context, 'Direct Message Threads', actions: [
|
||||
StatusAndRefreshButton(
|
||||
valueListenable: nss.directMessageUpdateStatus,
|
||||
refreshFunction: () async => await service.updateThreads(),
|
||||
busyColor: Theme.of(context).colorScheme.background,
|
||||
),
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
context.push('/messages/new_thread');
|
||||
|
@ -23,7 +32,7 @@ class MessagesScreen extends StatelessWidget {
|
|||
]),
|
||||
body: RefreshIndicator(
|
||||
onRefresh: () async {
|
||||
await service.updateThreads();
|
||||
service.updateThreads();
|
||||
},
|
||||
child: Center(child: buildBody(context, service)),
|
||||
),
|
||||
|
@ -31,7 +40,7 @@ class MessagesScreen extends StatelessWidget {
|
|||
}
|
||||
|
||||
Widget buildBody(BuildContext context, DirectMessageService service) {
|
||||
final threads = service.threads;
|
||||
final threads = service.getThreads();
|
||||
threads.sort((t1, t2) =>
|
||||
t2.messages.last.createdAt.compareTo(t1.messages.last.createdAt));
|
||||
return threads.isEmpty
|
||||
|
@ -42,7 +51,7 @@ class MessagesScreen extends StatelessWidget {
|
|||
final thread = threads[index];
|
||||
final style = thread.allSeen
|
||||
? null
|
||||
: TextStyle(fontWeight: FontWeight.bold);
|
||||
: const TextStyle(fontWeight: FontWeight.bold);
|
||||
return ListTile(
|
||||
onTap: () => context.pushNamed(
|
||||
ScreenPaths.thread,
|
||||
|
|
|
@ -36,17 +36,22 @@ class NotificationsScreen extends StatelessWidget {
|
|||
} else {
|
||||
final unreadCount = notifications.where((e) => !e.dismissed).length;
|
||||
title = 'Notifications ($unreadCount)';
|
||||
body = ListView.separated(
|
||||
itemBuilder: (context, index) {
|
||||
return NotificationControl(notification: notifications[index]);
|
||||
},
|
||||
separatorBuilder: (context, index) {
|
||||
return const Divider(
|
||||
color: Colors.black54,
|
||||
height: 0.0,
|
||||
);
|
||||
},
|
||||
itemCount: notifications.length + 1);
|
||||
body = RefreshIndicator(
|
||||
onRefresh: () async {
|
||||
manager.updateNotifications();
|
||||
},
|
||||
child: ListView.separated(
|
||||
itemBuilder: (context, index) {
|
||||
return NotificationControl(notification: notifications[index]);
|
||||
},
|
||||
separatorBuilder: (context, index) {
|
||||
return const Divider(
|
||||
color: Colors.black54,
|
||||
height: 0.0,
|
||||
);
|
||||
},
|
||||
itemCount: notifications.length + 1),
|
||||
);
|
||||
}
|
||||
|
||||
return Scaffold(
|
||||
|
|
|
@ -5,7 +5,7 @@ import '../../services/auth_service.dart';
|
|||
extension ConnectionMastodonExtensions on Connection {
|
||||
static Connection fromJson(Map<String, dynamic> json) {
|
||||
final name = json['display_name'] ?? '';
|
||||
final id = json['id'] ?? '';
|
||||
final id = json['id']?.toString() ?? '';
|
||||
final profileUrl = Uri.parse(json['url'] ?? '');
|
||||
const network = 'Unknown';
|
||||
final avatar = Uri.tryParse(json['avatar_static'] ?? '') ?? Uri();
|
||||
|
|
|
@ -54,7 +54,7 @@ class ConnectionsManager extends ChangeNotifier {
|
|||
notifyListeners();
|
||||
},
|
||||
onError: (error) {
|
||||
_logger.severe('Error following ${connection.name}');
|
||||
_logger.severe('Error following ${connection.name}: $error');
|
||||
},
|
||||
);
|
||||
}
|
||||
|
@ -73,7 +73,7 @@ class ConnectionsManager extends ChangeNotifier {
|
|||
notifyListeners();
|
||||
},
|
||||
onError: (error) {
|
||||
_logger.severe('Error following ${connection.name}');
|
||||
_logger.severe('Error following ${connection.name}: $error');
|
||||
},
|
||||
);
|
||||
}
|
||||
|
|
|
@ -14,11 +14,15 @@ class DirectMessageService extends ChangeNotifier {
|
|||
static final _logger = Logger('$DirectMessageService');
|
||||
final _threads = <String, DirectMessageThread>{};
|
||||
|
||||
List<DirectMessageThread> get threads {
|
||||
List<DirectMessageThread> getThreads({bool unreadyOnly = false}) {
|
||||
if (_threads.isEmpty) {
|
||||
updateThreads();
|
||||
}
|
||||
|
||||
if (unreadyOnly) {
|
||||
return _threads.values.where((t) => !t.allSeen).toList();
|
||||
}
|
||||
|
||||
return _threads.values.toList();
|
||||
}
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@ import 'package:flutter/foundation.dart';
|
|||
|
||||
class NetworkStatusService {
|
||||
final connectionUpdateStatus = ValueNotifier<bool>(false);
|
||||
final directMessageUpdateStatus = ValueNotifier<bool>(false);
|
||||
final notificationsUpdateStatus = ValueNotifier<bool>(false);
|
||||
final interactionsLoadingStatus = ValueNotifier<bool>(false);
|
||||
final timelineLoadingStatus = ValueNotifier<bool>(false);
|
||||
|
@ -14,7 +15,15 @@ class NetworkStatusService {
|
|||
void finishConnectionUpdateStatus() {
|
||||
connectionUpdateStatus.value = false;
|
||||
}
|
||||
|
||||
|
||||
void startDirectMessageUpdateStatus() {
|
||||
directMessageUpdateStatus.value = true;
|
||||
}
|
||||
|
||||
void finishDirectMessageUpdateStatus() {
|
||||
directMessageUpdateStatus.value = false;
|
||||
}
|
||||
|
||||
void startNotificationUpdate() {
|
||||
notificationsUpdateStatus.value = true;
|
||||
}
|
||||
|
|
|
@ -1,11 +1,14 @@
|
|||
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 '../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');
|
||||
|
@ -50,6 +53,16 @@ class NotificationsManager extends ChangeNotifier {
|
|||
_notifications[n.id] = n;
|
||||
}
|
||||
|
||||
_notifications.removeWhere(
|
||||
(key, value) => value.type == NotificationType.direct_message,
|
||||
);
|
||||
getIt<NetworkStatusService>().startNotificationUpdate();
|
||||
await getIt<DirectMessageService>().updateThreads();
|
||||
getIt<NetworkStatusService>().finishNotificationUpdate();
|
||||
for (final n in buildUnreadMessageNotifications()) {
|
||||
_notifications[n.id] = n;
|
||||
}
|
||||
|
||||
notifyListeners();
|
||||
return Result.ok(notifications);
|
||||
}
|
||||
|
@ -88,4 +101,27 @@ class NotificationsManager extends ChangeNotifier {
|
|||
notifyListeners();
|
||||
return updateNotifications();
|
||||
}
|
||||
|
||||
List<UserNotification> buildUnreadMessageNotifications() {
|
||||
final myId = getIt<AuthService>().currentId;
|
||||
final result =
|
||||
getIt<DirectMessageService>().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;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue