Change notifications to be only triggered by websocket stream

This commit is contained in:
Nutcake 2023-05-05 12:40:19 +02:00
parent 3eaf6ea9c6
commit b1a945a501
3 changed files with 78 additions and 34 deletions

View file

@ -42,10 +42,12 @@ class MessagingClient {
final ApiClient _apiClient; final ApiClient _apiClient;
final Map<String, Friend> _friendsCache = {}; final Map<String, Friend> _friendsCache = {};
final Map<String, MessageCache> _messageCache = {}; final Map<String, MessageCache> _messageCache = {};
final Map<String, Function> _updateListeners = {}; final Map<String, Function> _messageUpdateListeners = {};
final Map<String, List<Message>> _unreads = {};
final Logger _logger = Logger("NeosHub"); final Logger _logger = Logger("NeosHub");
final Workmanager _workmanager = Workmanager(); final Workmanager _workmanager = Workmanager();
final NotificationClient _notificationClient; final NotificationClient _notificationClient;
Function? _unreadsUpdateListener;
WebSocket? _wsChannel; WebSocket? _wsChannel;
bool _isConnecting = false; bool _isConnecting = false;
@ -66,6 +68,46 @@ class MessagingClient {
} }
} }
void updateAllUnreads(List<Message> messages) {
_unreads.clear();
for (final msg in messages) {
if (msg.senderId != _apiClient.userId) {
final value = _unreads[msg.senderId];
if (value == null) {
_unreads[msg.senderId] = [msg];
} else {
value.add(msg);
}
}
}
}
void addUnread(Message message) {
var messages = _unreads[message.senderId];
if (messages == null) {
messages = [message];
_unreads[message.senderId] = messages;
} else {
messages.add(message);
}
messages.sort();
_notificationClient.showUnreadMessagesNotification(messages.reversed);
notifyUnreadListener();
}
void clearUnreadsForFriend(Friend friend) {
_unreads[friend.id]?.clear();
notifyUnreadListener();
}
List<Message> getUnreadsForFriend(Friend friend) => _unreads[friend.id] ?? [];
bool friendHasUnreads(Friend friend) => _unreads.containsKey(friend.id);
bool messageIsUnread(Message message) {
return _unreads[message.senderId]?.any((element) => element.id == message.id) ?? false;
}
Friend? getAsFriend(String userId) => _friendsCache[userId]; Friend? getAsFriend(String userId) => _friendsCache[userId];
Future<MessageCache> getMessageCache(String userId) async { Future<MessageCache> getMessageCache(String userId) async {
@ -153,9 +195,13 @@ class MessagingClient {
} }
} }
void registerListener(String userId, Function function) => _updateListeners[userId] = function; void registerMessageListener(String userId, Function function) => _messageUpdateListeners[userId] = function;
void unregisterListener(String userId) => _updateListeners.remove(userId); void unregisterMessageListener(String userId) => _messageUpdateListeners.remove(userId);
void notifyListener(String userId) => _updateListeners[userId]?.call(); void notifyMessageListener(String userId) => _messageUpdateListeners[userId]?.call();
void registerUnreadListener(Function function) => _unreadsUpdateListener = function;
void unregisterUnreadListener() => _unreadsUpdateListener = null;
void notifyUnreadListener() => _unreadsUpdateListener?.call();
void _handleEvent(event) { void _handleEvent(event) {
final body = jsonDecode((event.toString().replaceAll(eofChar, ""))); final body = jsonDecode((event.toString().replaceAll(eofChar, "")));
@ -186,17 +232,17 @@ class MessagingClient {
final message = Message.fromMap(msg, withState: MessageState.sent); final message = Message.fromMap(msg, withState: MessageState.sent);
final cache = await getMessageCache(message.recipientId); final cache = await getMessageCache(message.recipientId);
cache.addMessage(message); cache.addMessage(message);
notifyListener(message.recipientId); notifyMessageListener(message.recipientId);
break; break;
case EventTarget.receiveMessage: case EventTarget.receiveMessage:
final msg = args[0]; final msg = args[0];
final message = Message.fromMap(msg); final message = Message.fromMap(msg);
final cache = await getMessageCache(message.senderId); final cache = await getMessageCache(message.senderId);
cache.addMessage(message); cache.addMessage(message);
if (!_updateListeners.containsKey(message.senderId)) { if (!_messageUpdateListeners.containsKey(message.senderId)) {
_notificationClient.showUnreadMessagesNotification([message]); addUnread(message);
} }
notifyListener(message.senderId); notifyMessageListener(message.senderId);
break; break;
case EventTarget.messagesRead: case EventTarget.messagesRead:
final messageIds = args[0]["ids"] as List; final messageIds = args[0]["ids"] as List;
@ -205,7 +251,7 @@ class MessagingClient {
for (var id in messageIds) { for (var id in messageIds) {
cache.setMessageState(id, MessageState.read); cache.setMessageState(id, MessageState.read);
} }
notifyListener(recipientId); notifyMessageListener(recipientId);
break; break;
} }
} }
@ -222,7 +268,7 @@ class MessagingClient {
_sendData(data); _sendData(data);
final cache = await getMessageCache(message.recipientId); final cache = await getMessageCache(message.recipientId);
cache.messages.add(message); cache.messages.add(message);
notifyListener(message.recipientId); notifyMessageListener(message.recipientId);
} }
void markMessagesRead(MarkReadBatch batch) { void markMessagesRead(MarkReadBatch batch) {
@ -260,13 +306,12 @@ class NotificationClient {
) )
); );
Future<void> showUnreadMessagesNotification(List<Message> messages) async { Future<void> showUnreadMessagesNotification(Iterable<Message> messages) async {
if (messages.isEmpty) return; if (messages.isEmpty) return;
final bySender = groupBy(messages, (p0) => p0.senderId); final bySender = groupBy(messages, (p0) => p0.senderId);
for (final entry in bySender.entries) { for (final entry in bySender.entries) {
final uname = entry.key.stripUid(); final uname = entry.key.stripUid();
await _notifier.show( await _notifier.show(
uname.hashCode, uname.hashCode,

View file

@ -31,7 +31,6 @@ class FriendsList extends StatefulWidget {
class _FriendsListState extends State<FriendsList> { class _FriendsListState extends State<FriendsList> {
static const Duration _autoRefreshDuration = Duration(seconds: 90); static const Duration _autoRefreshDuration = Duration(seconds: 90);
static const Duration _refreshTimeoutDuration = Duration(seconds: 30); static const Duration _refreshTimeoutDuration = Duration(seconds: 30);
final _unreads = <String, List<Message>>{};
Future<List<Friend>>? _friendsFuture; Future<List<Friend>>? _friendsFuture;
ClientHolder? _clientHolder; ClientHolder? _clientHolder;
Timer? _autoRefresh; Timer? _autoRefresh;
@ -51,6 +50,14 @@ class _FriendsListState extends State<FriendsList> {
final clientHolder = ClientHolder.of(context); final clientHolder = ClientHolder.of(context);
if (_clientHolder != clientHolder) { if (_clientHolder != clientHolder) {
_clientHolder = clientHolder; _clientHolder = clientHolder;
final mClient = _clientHolder!.messagingClient;
mClient.registerUnreadListener(() {
if (context.mounted) {
setState(() {});
} else {
mClient.unregisterUnreadListener();
}
});
_refreshFriendsList(); _refreshFriendsList();
} }
} }
@ -59,22 +66,14 @@ class _FriendsListState extends State<FriendsList> {
if (_refreshTimeout?.isActive == true) return; if (_refreshTimeout?.isActive == true) return;
_friendsFuture = FriendApi.getFriendsList(_clientHolder!.apiClient).then((Iterable<Friend> value) async { _friendsFuture = FriendApi.getFriendsList(_clientHolder!.apiClient).then((Iterable<Friend> value) async {
final unreadMessages = await MessageApi.getUserMessages(_clientHolder!.apiClient, unreadOnly: true); final unreadMessages = await MessageApi.getUserMessages(_clientHolder!.apiClient, unreadOnly: true);
_unreads.clear(); final mClient = _clientHolder?.messagingClient;
for (final msg in unreadMessages) { if (mClient == null) return [];
if (msg.senderId != _clientHolder!.apiClient.userId) { mClient.updateAllUnreads(unreadMessages.toList());
final value = _unreads[msg.senderId];
if (value == null) {
_unreads[msg.senderId] = [msg];
} else {
value.add(msg);
}
}
}
final friends = value.toList() final friends = value.toList()
..sort((a, b) { ..sort((a, b) {
var aVal = _unreads.containsKey(a.id) ? -3 : 0; var aVal = mClient.friendHasUnreads(a) ? -3 : 0;
var bVal = _unreads.containsKey(b.id) ? -3 : 0; var bVal = mClient.friendHasUnreads(b) ? -3 : 0;
aVal -= a.userStatus.lastStatusChange.compareTo(b.userStatus.lastStatusChange); aVal -= a.userStatus.lastStatusChange.compareTo(b.userStatus.lastStatusChange);
aVal += a.userStatus.onlineStatus.compareTo(b.userStatus.onlineStatus) * 2; aVal += a.userStatus.onlineStatus.compareTo(b.userStatus.onlineStatus) * 2;
@ -164,21 +163,21 @@ class _FriendsListState extends State<FriendsList> {
itemCount: friends.length, itemCount: friends.length,
itemBuilder: (context, index) { itemBuilder: (context, index) {
final friend = friends[index]; final friend = friends[index];
final unread = _unreads[friend.id] ?? []; final unreads = _clientHolder?.messagingClient.getUnreadsForFriend(friend) ?? [];
return FriendListTile( return FriendListTile(
friend: friend, friend: friend,
unreads: unread.length, unreads: unreads.length,
onTap: () async { onTap: () async {
if (unread.isNotEmpty) { if (unreads.isNotEmpty) {
final readBatch = MarkReadBatch( final readBatch = MarkReadBatch(
senderId: _clientHolder!.apiClient.userId, senderId: _clientHolder!.apiClient.userId,
ids: unread.map((e) => e.id).toList(), ids: unreads.map((e) => e.id).toList(),
readTime: DateTime.now(), readTime: DateTime.now(),
); );
_clientHolder!.messagingClient.markMessagesRead(readBatch); _clientHolder!.messagingClient.markMessagesRead(readBatch);
} }
setState(() { setState(() {
unread.clear(); unreads.clear();
}); });
}, },
); );

View file

@ -51,18 +51,18 @@ class _MessagesListState extends State<MessagesList> {
.whenComplete(() => _messageCacheFutureComplete = true); .whenComplete(() => _messageCacheFutureComplete = true);
final mClient = _clientHolder?.messagingClient; final mClient = _clientHolder?.messagingClient;
final id = widget.friend.id; final id = widget.friend.id;
mClient?.registerListener(id, () { mClient?.registerMessageListener(id, () {
if (context.mounted) { if (context.mounted) {
setState(() {}); setState(() {});
} else { } else {
mClient.unregisterListener(id); mClient.unregisterMessageListener(id);
} }
}); });
} }
@override @override
void dispose() { void dispose() {
_clientHolder?.messagingClient.unregisterListener(widget.friend.id); _clientHolder?.messagingClient.unregisterMessageListener(widget.friend.id);
_messageTextController.dispose(); _messageTextController.dispose();
_sessionListScrollController.dispose(); _sessionListScrollController.dispose();
super.dispose(); super.dispose();