@@ -41,6 +41,7 @@ class Unreads extends PerAccountStoreBase with ChangeNotifier {
41
41
required CorePerAccountStore core,
42
42
required ChannelStore channelStore,
43
43
}) {
44
+ final locatorMap = < int , Narrow > {};
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,11 @@ 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
+ final Map <int , Narrow > locatorMap;
112
+
95
113
// TODO excluded for now; would need to handle nuances around muting etc.
96
114
// int count;
97
115
@@ -233,11 +251,8 @@ class Unreads extends PerAccountStoreBase with ChangeNotifier {
233
251
/// The unread state for [messageId] , or null if unknown.
234
252
///
235
253
/// May be unknown only if [oldUnreadsMissing] .
236
- ///
237
- /// This is inefficient; it iterates through [dms] and [channels] .
238
- // TODO implement efficiently
239
254
bool ? isUnread (int messageId) {
240
- final isPresent = _slowIsPresentInDms (messageId) || _slowIsPresentInStreams (messageId);
255
+ final isPresent = locatorMap. containsKey (messageId);
241
256
if (oldUnreadsMissing && ! isPresent) return null ;
242
257
return isPresent;
243
258
}
@@ -250,9 +265,12 @@ class Unreads extends PerAccountStoreBase with ChangeNotifier {
250
265
251
266
switch (message) {
252
267
case StreamMessage ():
268
+ final narrow = TopicNarrow .ofMessage (message);
269
+ locatorMap[event.message.id] = narrow;
253
270
_addLastInStreamTopic (message.id, message.streamId, message.topic);
254
271
case DmMessage ():
255
272
final narrow = DmNarrow .ofMessage (message, selfUserId: selfUserId);
273
+ locatorMap[event.message.id] = narrow;
256
274
_addLastInDm (message.id, narrow);
257
275
}
258
276
if (
@@ -346,9 +364,16 @@ class Unreads extends PerAccountStoreBase with ChangeNotifier {
346
364
// Unreads moved to an unsubscribed channel; just drop them.
347
365
// See also:
348
366
// https://chat.zulip.org/#narrow/channel/378-api-design/topic/mark-as-read.20events.20with.20message.20moves.3F/near/2101926
367
+ for (final messageId in messageToMoveIds) {
368
+ locatorMap.remove (messageId);
369
+ }
349
370
return true ;
350
371
}
351
372
373
+ final narrow = TopicNarrow (newStreamId, newTopic);
374
+ for (final messageId in messageToMoveIds) {
375
+ locatorMap[messageId] = narrow;
376
+ }
352
377
_addAllInStreamTopic (messageToMoveIds, newStreamId, newTopic);
353
378
354
379
return true ;
@@ -363,7 +388,10 @@ class Unreads extends PerAccountStoreBase with ChangeNotifier {
363
388
final topic = event.topic! ;
364
389
_removeAllInStreamTopic (messageIdsSet, streamId, topic);
365
390
case MessageType .direct:
366
- _slowRemoveAllInDms (messageIdsSet);
391
+ _removeAllInDms (messageIdsSet);
392
+ }
393
+ for (final messageId in event.messageIds) {
394
+ locatorMap.remove (messageId);
367
395
}
368
396
369
397
// TODO skip notifyListeners if unchanged?
@@ -405,15 +433,19 @@ class Unreads extends PerAccountStoreBase with ChangeNotifier {
405
433
switch (event) {
406
434
case UpdateMessageFlagsAddEvent ():
407
435
if (event.all) {
436
+ locatorMap.clear ();
408
437
streams.clear ();
409
438
dms.clear ();
410
439
mentions.clear ();
411
440
oldUnreadsMissing = false ;
412
441
} else {
413
442
final messageIdsSet = Set .of (event.messages);
414
443
mentions.removeAll (messageIdsSet);
415
- _slowRemoveAllInStreams (messageIdsSet);
416
- _slowRemoveAllInDms (messageIdsSet);
444
+ _removeAllInStreams (messageIdsSet);
445
+ _removeAllInDms (messageIdsSet);
446
+ for (final messageId in event.messages) {
447
+ locatorMap.remove (messageId);
448
+ }
417
449
}
418
450
case UpdateMessageFlagsRemoveEvent ():
419
451
final newlyUnreadInStreams = < int , TopicKeyedMap <QueueList <int >>> {};
@@ -431,12 +463,15 @@ class Unreads extends PerAccountStoreBase with ChangeNotifier {
431
463
}
432
464
switch (detail.type) {
433
465
case MessageType .stream:
434
- final topics = (newlyUnreadInStreams[detail.streamId! ] ?? = makeTopicKeyedMap ());
435
- final messageIds = (topics[detail.topic! ] ?? = QueueList ());
466
+ final UpdateMessageFlagsMessageDetail (: streamId, : topic) = detail;
467
+ locatorMap[messageId] = TopicNarrow (streamId! , topic! );
468
+ final topics = (newlyUnreadInStreams[streamId] ?? = makeTopicKeyedMap ());
469
+ final messageIds = (topics[topic] ?? = QueueList ());
436
470
messageIds.add (messageId);
437
471
case MessageType .direct:
438
472
final narrow = DmNarrow .ofUpdateMessageFlagsMessageDetail (selfUserId: selfUserId,
439
473
detail);
474
+ locatorMap[messageId] = narrow;
440
475
(newlyUnreadInDms[narrow] ?? = QueueList ())
441
476
.add (messageId);
442
477
}
@@ -489,15 +524,6 @@ class Unreads extends PerAccountStoreBase with ChangeNotifier {
489
524
notifyListeners ();
490
525
}
491
526
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
527
void _addLastInStreamTopic (int messageId, int streamId, TopicName topic) {
502
528
((streams[streamId] ?? = makeTopicKeyedMap ())[topic] ?? = QueueList ())
503
529
.addLast (messageId);
@@ -517,26 +543,23 @@ class Unreads extends PerAccountStoreBase with ChangeNotifier {
517
543
);
518
544
}
519
545
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
- }
546
+ /// Remove any of [idsToRemove] that are in [streams] .
547
+ void _removeAllInStreams (Set <int > idsToRemove) {
548
+ for (final messageId in idsToRemove) {
549
+ final narrow = locatorMap[messageId];
550
+ if (narrow == null ) continue ;
551
+ if (narrow is ! TopicNarrow ) continue ;
552
+
553
+ final messageIds = streams[narrow.streamId]? [narrow.topic];
554
+ if (messageIds == null ) continue ;
555
+
556
+ messageIds.remove (messageId);
557
+ if (messageIds.isEmpty) {
558
+ streams[narrow.streamId]! .remove (narrow.topic);
530
559
}
531
- for ( final topic in newlyEmptyTopics ) {
532
- topics .remove (topic );
560
+ if (streams[narrow.streamId] ! .isEmpty ) {
561
+ streams .remove (narrow.streamId );
533
562
}
534
- if (topics.isEmpty) {
535
- newlyEmptyStreams.add (streamId);
536
- }
537
- }
538
- for (final streamId in newlyEmptyStreams) {
539
- streams.remove (streamId);
540
563
}
541
564
}
542
565
@@ -599,11 +622,6 @@ class Unreads extends PerAccountStoreBase with ChangeNotifier {
599
622
return poppedMessageIds;
600
623
}
601
624
602
- // TODO use efficient model lookups
603
- bool _slowIsPresentInDms (int messageId) {
604
- return dms.values.any ((ids) => ids.contains (messageId));
605
- }
606
-
607
625
void _addLastInDm (int messageId, DmNarrow narrow) {
608
626
(dms[narrow] ?? = QueueList ()).addLast (messageId);
609
627
}
@@ -619,17 +637,19 @@ class Unreads extends PerAccountStoreBase with ChangeNotifier {
619
637
);
620
638
}
621
639
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));
640
+ /// Remove any of [idsToRemove] that are in [dms] .
641
+ void _removeAllInDms (Set <int > idsToRemove) {
642
+ for (final messageId in idsToRemove) {
643
+ final narrow = locatorMap[messageId];
644
+ if (narrow == null ) continue ;
645
+ if (narrow is ! DmNarrow ) continue ;
646
+
647
+ final messageIds = dms[narrow];
648
+ if (messageIds == null ) continue ;
649
+ messageIds.remove (messageId);
627
650
if (messageIds.isEmpty) {
628
- newlyEmptyDms. add (dmNarrow );
651
+ dms. remove (narrow );
629
652
}
630
653
}
631
- for (final dmNarrow in newlyEmptyDms) {
632
- dms.remove (dmNarrow);
633
- }
634
654
}
635
655
}
0 commit comments