@@ -41,6 +41,7 @@ class Unreads extends PerAccountStoreBase with ChangeNotifier {
41
41
required ChannelStore channelStore,
42
42
required UnreadMessagesSnapshot initial,
43
43
}) {
44
+ final locatorMap = < int , SendableNarrow > {};
44
45
final streams = < int , TopicKeyedMap <QueueList <int >>> {};
45
46
final dms = < DmNarrow , QueueList <int >> {};
46
47
final mentions = Set .of (initial.mentions);
@@ -57,23 +58,34 @@ class Unreads extends PerAccountStoreBase with ChangeNotifier {
57
58
// TODO(server-10) simplify away
58
59
(value) => setUnion (value, unreadChannelSnapshot.unreadMessageIds),
59
60
ifAbsent: () => QueueList .from (unreadChannelSnapshot.unreadMessageIds));
61
+ final narrow = TopicNarrow (streamId, topic);
62
+ for (final messageId in unreadChannelSnapshot.unreadMessageIds) {
63
+ locatorMap[messageId] = narrow;
64
+ }
60
65
}
61
66
62
67
for (final unreadDmSnapshot in initial.dms) {
63
68
final otherUserId = unreadDmSnapshot.otherUserId;
64
69
final narrow = DmNarrow .withUser (otherUserId, selfUserId: core.selfUserId);
65
70
dms[narrow] = QueueList .from (unreadDmSnapshot.unreadMessageIds);
71
+ for (final messageId in dms[narrow]! ) {
72
+ locatorMap[messageId] = narrow;
73
+ }
66
74
}
67
75
68
76
for (final unreadHuddleSnapshot in initial.huddles) {
69
77
final narrow = DmNarrow .ofUnreadHuddleSnapshot (unreadHuddleSnapshot,
70
78
selfUserId: core.selfUserId);
71
79
dms[narrow] = QueueList .from (unreadHuddleSnapshot.unreadMessageIds);
80
+ for (final messageId in dms[narrow]! ) {
81
+ locatorMap[messageId] = narrow;
82
+ }
72
83
}
73
84
74
85
return Unreads ._(
75
86
core: core,
76
87
channelStore: channelStore,
88
+ locatorMap: locatorMap,
77
89
streams: streams,
78
90
dms: dms,
79
91
mentions: mentions,
@@ -84,6 +96,7 @@ class Unreads extends PerAccountStoreBase with ChangeNotifier {
84
96
Unreads ._({
85
97
required super .core,
86
98
required this .channelStore,
99
+ required this .locatorMap,
87
100
required this .streams,
88
101
required this .dms,
89
102
required this .mentions,
@@ -92,6 +105,12 @@ class Unreads extends PerAccountStoreBase with ChangeNotifier {
92
105
93
106
final ChannelStore channelStore;
94
107
108
+ /// All unread messages, as: message ID → narrow ([TopicNarrow] or [DmNarrow] ).
109
+ ///
110
+ /// Enables efficient [isUnread] and efficient lookups in [streams] and [dms] .
111
+ @visibleForTesting
112
+ final Map <int , SendableNarrow > locatorMap;
113
+
95
114
// TODO excluded for now; would need to handle nuances around muting etc.
96
115
// int count;
97
116
@@ -233,11 +252,8 @@ class Unreads extends PerAccountStoreBase with ChangeNotifier {
233
252
/// The unread state for [messageId] , or null if unknown.
234
253
///
235
254
/// May be unknown only if [oldUnreadsMissing] .
236
- ///
237
- /// This is inefficient; it iterates through [dms] and [channels] .
238
- // TODO implement efficiently
239
255
bool ? isUnread (int messageId) {
240
- final isPresent = _slowIsPresentInDms (messageId) || _slowIsPresentInStreams (messageId);
256
+ final isPresent = locatorMap. containsKey (messageId);
241
257
if (oldUnreadsMissing && ! isPresent) return null ;
242
258
return isPresent;
243
259
}
@@ -250,9 +266,12 @@ class Unreads extends PerAccountStoreBase with ChangeNotifier {
250
266
251
267
switch (message) {
252
268
case StreamMessage ():
269
+ final narrow = TopicNarrow .ofMessage (message);
270
+ locatorMap[event.message.id] = narrow;
253
271
_addLastInStreamTopic (message.id, message.streamId, message.topic);
254
272
case DmMessage ():
255
273
final narrow = DmNarrow .ofMessage (message, selfUserId: selfUserId);
274
+ locatorMap[event.message.id] = narrow;
256
275
_addLastInDm (message.id, narrow);
257
276
}
258
277
if (
@@ -346,24 +365,35 @@ class Unreads extends PerAccountStoreBase with ChangeNotifier {
346
365
// Unreads moved to an unsubscribed channel; just drop them.
347
366
// See also:
348
367
// https://chat.zulip.org/#narrow/channel/378-api-design/topic/mark-as-read.20events.20with.20message.20moves.3F/near/2101926
368
+ for (final messageId in messageToMoveIds) {
369
+ locatorMap.remove (messageId);
370
+ }
349
371
return true ;
350
372
}
351
373
374
+ final narrow = TopicNarrow (newStreamId, newTopic);
375
+ for (final messageId in messageToMoveIds) {
376
+ locatorMap[messageId] = narrow;
377
+ }
352
378
_addAllInStreamTopic (messageToMoveIds, newStreamId, newTopic);
353
379
354
380
return true ;
355
381
}
356
382
357
383
void handleDeleteMessageEvent (DeleteMessageEvent event) {
358
384
mentions.removeAll (event.messageIds);
359
- final messageIdsSet = Set .of (event.messageIds);
360
385
switch (event.messageType) {
361
386
case MessageType .stream:
387
+ // All the messages are in [event.streamId] and [event.topic],
388
+ // so we can be more efficient than _removeAllInStreamsAndDms.
362
389
final streamId = event.streamId! ;
363
390
final topic = event.topic! ;
364
- _removeAllInStreamTopic (messageIdsSet , streamId, topic);
391
+ _removeAllInStreamTopic (Set . of (event.messageIds) , streamId, topic);
365
392
case MessageType .direct:
366
- _slowRemoveAllInDms (messageIdsSet);
393
+ _removeAllInStreamsAndDms (event.messageIds, expectOnlyDms: true );
394
+ }
395
+ for (final messageId in event.messageIds) {
396
+ locatorMap.remove (messageId);
367
397
}
368
398
369
399
// TODO skip notifyListeners if unchanged?
@@ -405,15 +435,18 @@ class Unreads extends PerAccountStoreBase with ChangeNotifier {
405
435
switch (event) {
406
436
case UpdateMessageFlagsAddEvent ():
407
437
if (event.all) {
438
+ locatorMap.clear ();
408
439
streams.clear ();
409
440
dms.clear ();
410
441
mentions.clear ();
411
442
oldUnreadsMissing = false ;
412
443
} else {
413
- final messageIdsSet = Set .of (event.messages);
414
- mentions.removeAll (messageIdsSet);
415
- _slowRemoveAllInStreams (messageIdsSet);
416
- _slowRemoveAllInDms (messageIdsSet);
444
+ final messageIds = event.messages;
445
+ mentions.removeAll (messageIds);
446
+ _removeAllInStreamsAndDms (messageIds);
447
+ for (final messageId in messageIds) {
448
+ locatorMap.remove (messageId);
449
+ }
417
450
}
418
451
case UpdateMessageFlagsRemoveEvent ():
419
452
final newlyUnreadInStreams = < int , TopicKeyedMap <QueueList <int >>> {};
@@ -431,12 +464,15 @@ class Unreads extends PerAccountStoreBase with ChangeNotifier {
431
464
}
432
465
switch (detail.type) {
433
466
case MessageType .stream:
434
- final topics = (newlyUnreadInStreams[detail.streamId! ] ?? = makeTopicKeyedMap ());
435
- final messageIds = (topics[detail.topic! ] ?? = QueueList ());
467
+ final UpdateMessageFlagsMessageDetail (: streamId, : topic) = detail;
468
+ locatorMap[messageId] = TopicNarrow (streamId! , topic! );
469
+ final topics = (newlyUnreadInStreams[streamId] ?? = makeTopicKeyedMap ());
470
+ final messageIds = (topics[topic] ?? = QueueList ());
436
471
messageIds.add (messageId);
437
472
case MessageType .direct:
438
473
final narrow = DmNarrow .ofUpdateMessageFlagsMessageDetail (selfUserId: selfUserId,
439
474
detail);
475
+ locatorMap[messageId] = narrow;
440
476
(newlyUnreadInDms[narrow] ?? = QueueList ())
441
477
.add (messageId);
442
478
}
@@ -489,15 +525,6 @@ class Unreads extends PerAccountStoreBase with ChangeNotifier {
489
525
notifyListeners ();
490
526
}
491
527
492
- // TODO use efficient lookups
493
- bool _slowIsPresentInStreams (int messageId) {
494
- return streams.values.any (
495
- (topics) => topics.values.any (
496
- (messageIds) => messageIds.contains (messageId),
497
- ),
498
- );
499
- }
500
-
501
528
void _addLastInStreamTopic (int messageId, int streamId, TopicName topic) {
502
529
((streams[streamId] ?? = makeTopicKeyedMap ())[topic] ?? = QueueList ())
503
530
.addLast (messageId);
@@ -517,26 +544,32 @@ class Unreads extends PerAccountStoreBase with ChangeNotifier {
517
544
);
518
545
}
519
546
520
- // TODO use efficient model lookups
521
- void _slowRemoveAllInStreams (Set <int > idsToRemove) {
522
- final newlyEmptyStreams = < int > [];
523
- for (final MapEntry (key: streamId, value: topics) in streams.entries) {
524
- final newlyEmptyTopics = < TopicName > [];
525
- for (final MapEntry (key: topic, value: messageIds) in topics.entries) {
526
- messageIds.removeWhere ((id) => idsToRemove.contains (id));
527
- if (messageIds.isEmpty) {
528
- newlyEmptyTopics.add (topic);
529
- }
530
- }
531
- for (final topic in newlyEmptyTopics) {
532
- topics.remove (topic);
533
- }
534
- if (topics.isEmpty) {
535
- newlyEmptyStreams.add (streamId);
536
- }
547
+ /// Remove [idsToRemove] from [streams] and [dms] .
548
+ void _removeAllInStreamsAndDms (Iterable <int > idsToRemove, {bool expectOnlyDms = false }) {
549
+ final idsPresentByNarrow = < SendableNarrow , Set <int >> {};
550
+ for (final id in idsToRemove) {
551
+ final narrow = locatorMap[id];
552
+ if (narrow == null ) continue ;
553
+ (idsPresentByNarrow[narrow] ?? = {}).add (id);
537
554
}
538
- for (final streamId in newlyEmptyStreams) {
539
- streams.remove (streamId);
555
+
556
+ for (final MapEntry (key: narrow, value: ids) in idsPresentByNarrow.entries) {
557
+ switch (narrow) {
558
+ case TopicNarrow ():
559
+ if (expectOnlyDms) {
560
+ // TODO(log)?
561
+ }
562
+ _removeAllInStreamTopic (ids, narrow.streamId, narrow.topic);
563
+ case DmNarrow ():
564
+ final messageIds = dms[narrow];
565
+ if (messageIds == null ) return ;
566
+
567
+ // ([QueueList] doesn't have a `removeAll`)
568
+ messageIds.removeWhere ((id) => ids.contains (id));
569
+ if (messageIds.isEmpty) {
570
+ dms.remove (narrow);
571
+ }
572
+ }
540
573
}
541
574
}
542
575
@@ -599,11 +632,6 @@ class Unreads extends PerAccountStoreBase with ChangeNotifier {
599
632
return poppedMessageIds;
600
633
}
601
634
602
- // TODO use efficient model lookups
603
- bool _slowIsPresentInDms (int messageId) {
604
- return dms.values.any ((ids) => ids.contains (messageId));
605
- }
606
-
607
635
void _addLastInDm (int messageId, DmNarrow narrow) {
608
636
(dms[narrow] ?? = QueueList ()).addLast (messageId);
609
637
}
@@ -618,18 +646,4 @@ class Unreads extends PerAccountStoreBase with ChangeNotifier {
618
646
(existing) => setUnion (existing, messageIds),
619
647
);
620
648
}
621
-
622
- // TODO use efficient model lookups
623
- void _slowRemoveAllInDms (Set <int > idsToRemove) {
624
- final newlyEmptyDms = < DmNarrow > [];
625
- for (final MapEntry (key: dmNarrow, value: messageIds) in dms.entries) {
626
- messageIds.removeWhere ((id) => idsToRemove.contains (id));
627
- if (messageIds.isEmpty) {
628
- newlyEmptyDms.add (dmNarrow);
629
- }
630
- }
631
- for (final dmNarrow in newlyEmptyDms) {
632
- dms.remove (dmNarrow);
633
- }
634
- }
635
649
}
0 commit comments