Skip to content

Commit d8d8afe

Browse files
4e554c4cemersion
authored andcommitted
models: add support for reactions
This commit adds support for reactions in the IRC client, and routes them into the model. This commit does not yet allow for viewing reactions, but it should store them correctly in the model for doing so. This commit also fixes a bug where TAGMSGs (and other non- PRIVMSG/NOTICE) commands were stored in the model. In the future maybe this can be prevented by using a type-safe description of irc messages. We allow PRIVMSG-based reacts, and assume that they are a fallback for clients which do not support reactions. Thus we do not show the PRIVMSG, only the reaction.
1 parent 0626d58 commit d8d8afe

File tree

2 files changed

+93
-20
lines changed

2 files changed

+93
-20
lines changed

lib/client_controller.dart

Lines changed: 39 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -714,7 +714,6 @@ class ClientController {
714714
if (typing != null && !client.isMyNick(msg.source.name)) {
715715
_bufferList.get(target, network)?.setTyping(msg.source.name, typing == 'active');
716716
}
717-
break;
718717
}
719718
return _handleChatMessages(target, [msg]);
720719
case 'INVITE':
@@ -801,6 +800,8 @@ class ClientController {
801800
if (msg.cmd == 'NOTICE') {
802801
notices.add(msg);
803802
continue;
803+
} else if (msg.cmd != 'PRIVMSG') {
804+
continue;
804805
}
805806

806807
// Disregard non-/me CTCP messages
@@ -843,16 +844,39 @@ class ClientController {
843844
return;
844845
}
845846

846-
var entries = messages.map((msg) => MessageEntry(msg, buf!.id)).toList();
847-
await _db.storeMessages(entries);
847+
List<MessageEntry> privmsgs = [];
848+
List<ReactionEntry> reactions = [];
849+
for (var msg in messages) {
850+
var reply = msg.tags['+draft/reply'];
851+
var react = msg.tags['+draft/react'];
852+
if (reply != null && react != null) {
853+
reactions.add(ReactionEntry(msg, buf.id));
854+
} else if (msg.cmd == 'NOTICE' || msg.cmd == 'PRIVMSG') {
855+
privmsgs.add(MessageEntry(msg, buf.id));
856+
}
857+
}
858+
859+
if (reactions.isNotEmpty) {
860+
await _db.storeReactions(reactions);
861+
}
862+
if (privmsgs.isNotEmpty) {
863+
await _db.storeMessages(privmsgs);
864+
}
865+
848866
if (buf.messageHistoryLoaded) {
849-
var models = await buildMessageModelList(_db, entries);
867+
var models = await buildMessageModelList(_db, privmsgs);
850868
buf.addMessages(models, append: !isHistory);
869+
buf.addReactions(reactions);
870+
}
871+
872+
// Only privmsgs affects unread count / lastReadTime
873+
if (privmsgs.isEmpty) {
874+
return;
851875
}
852876

853-
String t = entries.first.time;
877+
String t = privmsgs.first.time;
854878
List<MessageEntry> unread = [];
855-
for (var entry in entries) {
879+
for (var entry in privmsgs) {
856880
if (entry.time.compareTo(t) > 0) {
857881
t = entry.time;
858882
}
@@ -1194,27 +1218,29 @@ IrcUri? _uriFromBouncerNetworkModel(BouncerNetworkModel bouncerNetwork) {
11941218
);
11951219
}
11961220

1197-
Future<Iterable<MessageModel>> buildMessageModelList(DB db, List<MessageEntry> entries) async {
1221+
Future<List<MessageModel>> buildMessageModelList(DB db, List<MessageEntry> entries) async {
11981222
if (entries.isEmpty) {
11991223
return [];
12001224
}
12011225

1202-
List<String> msgids = [];
1226+
List<String> parentMsgids = [];
12031227
for (var entry in entries) {
12041228
var parentMsgid = entry.msg.tags['+draft/reply'];
12051229
if (parentMsgid != null) {
1206-
msgids.add(parentMsgid);
1230+
parentMsgids.add(parentMsgid);
12071231
}
12081232
}
12091233

12101234
var bufferId = entries.first.buffer;
1211-
var parents = await db.fetchMessageSetByNetworkMsgid(bufferId, msgids);
1235+
var parentMap = await db.fetchMessageSetByNetworkMsgid(bufferId, parentMsgids);
1236+
var reactionMap = await db.fetchReactionSetBetweenMessages(bufferId, entries.first, entries.last);
12121237
return entries.map((entry) {
12131238
MessageEntry? replyTo;
12141239
var parentMsgid = entry.msg.tags['+draft/reply'];
12151240
if (parentMsgid != null) {
1216-
replyTo = parents[parentMsgid];
1241+
replyTo = parentMap[parentMsgid];
12171242
}
1218-
return MessageModel(entry: entry, replyTo: replyTo);
1219-
});
1243+
var reacts = reactionMap[entry.networkMsgid] ?? [];
1244+
return MessageModel(entry: entry, replyTo: replyTo, reactions: reacts);
1245+
}).toList();
12201246
}

lib/models.dart

Lines changed: 54 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -398,6 +398,7 @@ class BufferModel extends ChangeNotifier {
398398
String? _lastDeliveredTime;
399399
bool _messageHistoryLoaded = false;
400400
List<MessageModel> _messages = [];
401+
final Map<String, MessageModel> _messagesByNetworkMsgid = {};
401402
final Map<String, Timer> _typing = {};
402403

403404
// Kept in sync by BufferPageState
@@ -497,18 +498,52 @@ class BufferModel extends ChangeNotifier {
497498
notifyListeners();
498499
}
499500

500-
void addMessages(Iterable<MessageModel> msgs, { bool append = false }) {
501+
void _populateMessagesById(List<MessageModel> msgs) {
502+
for (var msg in msgs) {
503+
if (msg.entry.networkMsgid != null) {
504+
_messagesByNetworkMsgid[msg.entry.networkMsgid!] = msg;
505+
}
506+
}
507+
}
508+
509+
void _appendMessages(List<MessageModel> msgs) {
510+
_messages.addAll(msgs);
511+
_populateMessagesById(msgs);
512+
}
513+
514+
void _prependMessages(List<MessageModel> msgs) {
515+
assert(msgs.last.entry.time.compareTo(_messages.first.entry.time) <= 0);
516+
_messages = [...msgs, ..._messages];
517+
_populateMessagesById(msgs);
518+
}
519+
520+
void addMessages(List<MessageModel> msgs, { bool append = false }) {
501521
assert(messageHistoryLoaded);
502522
if (msgs.isEmpty) {
503523
return;
504524
}
525+
505526
if (append) {
506-
_messages.addAll(msgs);
527+
_appendMessages(msgs);
507528
} else {
508529
// TODO: optimize this case
509-
_messages.addAll(msgs);
530+
_appendMessages(msgs);
510531
_messages.sort(_compareMessageModels);
511532
}
533+
534+
notifyListeners();
535+
}
536+
537+
void addReactions(List<ReactionEntry> reacts) {
538+
assert(messageHistoryLoaded);
539+
if (reacts.isEmpty) {
540+
return;
541+
}
542+
543+
for (var reaction in reacts) {
544+
_messagesByNetworkMsgid[reaction.replyNetworkMsgid]?._addReaction(reaction);
545+
}
546+
512547
notifyListeners();
513548
}
514549

@@ -517,11 +552,10 @@ class BufferModel extends ChangeNotifier {
517552
// must always come before the existing messages
518553
if (!_messageHistoryLoaded) {
519554
assert(_messages.isEmpty);
520-
_messages = l;
555+
_appendMessages(l);
521556
_messageHistoryLoaded = true;
522557
} else if (!l.isEmpty) {
523-
assert(l.last.entry.time.compareTo(_messages.first.entry.time) <= 0);
524-
_messages = [...l, ..._messages];
558+
_prependMessages(l);
525559
}
526560
notifyListeners();
527561
}
@@ -566,12 +600,25 @@ class MessageModel {
566600
final MessageEntry entry;
567601
final MessageEntry? replyTo;
568602

569-
MessageModel({ required this.entry, this.replyTo }) {
603+
final List<ReactionEntry> _reactions;
604+
605+
MessageModel({
606+
required this.entry,
607+
this.replyTo,
608+
Iterable<ReactionEntry>? reactions,
609+
}) :
610+
// Our reaction list needs to be mutable. This is why we spread
611+
// instead of taking a list and storing it.
612+
_reactions = [...?reactions],
570613
assert(entry.id != null);
614+
615+
void _addReaction(ReactionEntry reaction) {
616+
_reactions.add(reaction);
571617
}
572618

573619
int get id => entry.id!;
574620
IrcMessage get msg => entry.msg;
621+
UnmodifiableListView<ReactionEntry> get reactions => UnmodifiableListView(_reactions);
575622
}
576623

577624
class MemberListModel extends ChangeNotifier {

0 commit comments

Comments
 (0)