@@ -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
}
@@ -248,6 +263,12 @@ class Unreads extends PerAccountStoreBase with ChangeNotifier {
248
263
return ;
249
264
}
250
265
266
+ final narrow = switch (message) {
267
+ StreamMessage () => TopicNarrow .ofMessage (message),
268
+ DmMessage () => DmNarrow .ofMessage (message, selfUserId: selfUserId),
269
+ };
270
+ locatorMap[event.message.id] = narrow;
271
+
251
272
switch (message) {
252
273
case StreamMessage ():
253
274
_addLastInStreamTopic (message.id, message.streamId, message.topic);
@@ -346,9 +367,16 @@ class Unreads extends PerAccountStoreBase with ChangeNotifier {
346
367
// Unreads moved to an unsubscribed channel; just drop them.
347
368
// See also:
348
369
// https://chat.zulip.org/#narrow/channel/378-api-design/topic/mark-as-read.20events.20with.20message.20moves.3F/near/2101926
370
+ for (final messageId in messageToMoveIds) {
371
+ locatorMap.remove (messageId);
372
+ }
349
373
return true ;
350
374
}
351
375
376
+ final narrow = TopicNarrow (newStreamId, newTopic);
377
+ for (final messageId in messageToMoveIds) {
378
+ locatorMap[messageId] = narrow;
379
+ }
352
380
_addAllInStreamTopic (messageToMoveIds, newStreamId, newTopic);
353
381
354
382
return true ;
@@ -363,7 +391,10 @@ class Unreads extends PerAccountStoreBase with ChangeNotifier {
363
391
final topic = event.topic! ;
364
392
_removeAllInStreamTopic (messageIdsSet, streamId, topic);
365
393
case MessageType .direct:
366
- _slowRemoveAllInDms (messageIdsSet);
394
+ _removeAllInDms (messageIdsSet);
395
+ }
396
+ for (final messageId in event.messageIds) {
397
+ locatorMap.remove (messageId);
367
398
}
368
399
369
400
// TODO skip notifyListeners if unchanged?
@@ -405,15 +436,19 @@ class Unreads extends PerAccountStoreBase with ChangeNotifier {
405
436
switch (event) {
406
437
case UpdateMessageFlagsAddEvent ():
407
438
if (event.all) {
439
+ locatorMap.clear ();
408
440
streams.clear ();
409
441
dms.clear ();
410
442
mentions.clear ();
411
443
oldUnreadsMissing = false ;
412
444
} else {
413
445
final messageIdsSet = Set .of (event.messages);
414
446
mentions.removeAll (messageIdsSet);
415
- _slowRemoveAllInStreams (messageIdsSet);
416
- _slowRemoveAllInDms (messageIdsSet);
447
+ _removeAllInStreams (messageIdsSet);
448
+ _removeAllInDms (messageIdsSet);
449
+ for (final messageId in event.messages) {
450
+ locatorMap.remove (messageId);
451
+ }
417
452
}
418
453
case UpdateMessageFlagsRemoveEvent ():
419
454
final newlyUnreadInStreams = < int , TopicKeyedMap <QueueList <int >>> {};
@@ -431,12 +466,15 @@ class Unreads extends PerAccountStoreBase with ChangeNotifier {
431
466
}
432
467
switch (detail.type) {
433
468
case MessageType .stream:
434
- final topics = (newlyUnreadInStreams[detail.streamId! ] ?? = makeTopicKeyedMap ());
435
- final messageIds = (topics[detail.topic! ] ?? = QueueList ());
469
+ final UpdateMessageFlagsMessageDetail (: streamId, : topic) = detail;
470
+ locatorMap[messageId] = TopicNarrow (streamId! , topic! );
471
+ final topics = (newlyUnreadInStreams[streamId] ?? = makeTopicKeyedMap ());
472
+ final messageIds = (topics[topic] ?? = QueueList ());
436
473
messageIds.add (messageId);
437
474
case MessageType .direct:
438
475
final narrow = DmNarrow .ofUpdateMessageFlagsMessageDetail (selfUserId: selfUserId,
439
476
detail);
477
+ locatorMap[messageId] = narrow;
440
478
(newlyUnreadInDms[narrow] ?? = QueueList ())
441
479
.add (messageId);
442
480
}
@@ -489,15 +527,6 @@ class Unreads extends PerAccountStoreBase with ChangeNotifier {
489
527
notifyListeners ();
490
528
}
491
529
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
530
void _addLastInStreamTopic (int messageId, int streamId, TopicName topic) {
502
531
((streams[streamId] ?? = makeTopicKeyedMap ())[topic] ?? = QueueList ())
503
532
.addLast (messageId);
@@ -517,26 +546,23 @@ class Unreads extends PerAccountStoreBase with ChangeNotifier {
517
546
);
518
547
}
519
548
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
- }
549
+ /// Remove any of [idsToRemove] that are in [streams] .
550
+ void _removeAllInStreams (Set <int > idsToRemove) {
551
+ for (final messageId in idsToRemove) {
552
+ final narrow = locatorMap[messageId];
553
+ if (narrow == null ) continue ;
554
+ if (narrow is ! TopicNarrow ) continue ;
555
+
556
+ final messageIds = streams[narrow.streamId]? [narrow.topic];
557
+ if (messageIds == null ) continue ;
558
+
559
+ messageIds.remove (messageId);
560
+ if (messageIds.isEmpty) {
561
+ streams[narrow.streamId]! .remove (narrow.topic);
530
562
}
531
- for ( final topic in newlyEmptyTopics ) {
532
- topics .remove (topic );
563
+ if (streams[narrow.streamId] ! .isEmpty ) {
564
+ streams .remove (narrow.streamId );
533
565
}
534
- if (topics.isEmpty) {
535
- newlyEmptyStreams.add (streamId);
536
- }
537
- }
538
- for (final streamId in newlyEmptyStreams) {
539
- streams.remove (streamId);
540
566
}
541
567
}
542
568
@@ -599,11 +625,6 @@ class Unreads extends PerAccountStoreBase with ChangeNotifier {
599
625
return poppedMessageIds;
600
626
}
601
627
602
- // TODO use efficient model lookups
603
- bool _slowIsPresentInDms (int messageId) {
604
- return dms.values.any ((ids) => ids.contains (messageId));
605
- }
606
-
607
628
void _addLastInDm (int messageId, DmNarrow narrow) {
608
629
(dms[narrow] ?? = QueueList ()).addLast (messageId);
609
630
}
@@ -619,17 +640,19 @@ class Unreads extends PerAccountStoreBase with ChangeNotifier {
619
640
);
620
641
}
621
642
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));
643
+ /// Remove any of [idsToRemove] that are in [dms] .
644
+ void _removeAllInDms (Set <int > idsToRemove) {
645
+ for (final messageId in idsToRemove) {
646
+ final narrow = locatorMap[messageId];
647
+ if (narrow == null ) continue ;
648
+ if (narrow is ! DmNarrow ) continue ;
649
+
650
+ final messageIds = dms[narrow];
651
+ if (messageIds == null ) continue ;
652
+ messageIds.remove (messageId);
627
653
if (messageIds.isEmpty) {
628
- newlyEmptyDms. add (dmNarrow );
654
+ dms. remove (narrow );
629
655
}
630
656
}
631
- for (final dmNarrow in newlyEmptyDms) {
632
- dms.remove (dmNarrow);
633
- }
634
657
}
635
658
}
0 commit comments