@@ -41,6 +41,7 @@ class Unreads extends PerAccountStoreBase with ChangeNotifier {
4141 required CorePerAccountStore core,
4242 required ChannelStore channelStore,
4343 }) {
44+ final locatorMap = < int , Narrow > {};
4445 final streams = < int , TopicKeyedMap <QueueList <int >>> {};
4546 final dms = < DmNarrow , QueueList <int >> {};
4647 final mentions = Set .of (initial.mentions);
@@ -57,23 +58,34 @@ class Unreads extends PerAccountStoreBase with ChangeNotifier {
5758 // TODO(server-10) simplify away
5859 (value) => setUnion (value, unreadChannelSnapshot.unreadMessageIds),
5960 ifAbsent: () => QueueList .from (unreadChannelSnapshot.unreadMessageIds));
61+ final narrow = TopicNarrow (streamId, topic);
62+ for (final messageId in unreadChannelSnapshot.unreadMessageIds) {
63+ locatorMap[messageId] = narrow;
64+ }
6065 }
6166
6267 for (final unreadDmSnapshot in initial.dms) {
6368 final otherUserId = unreadDmSnapshot.otherUserId;
6469 final narrow = DmNarrow .withUser (otherUserId, selfUserId: core.selfUserId);
6570 dms[narrow] = QueueList .from (unreadDmSnapshot.unreadMessageIds);
71+ for (final messageId in dms[narrow]! ) {
72+ locatorMap[messageId] = narrow;
73+ }
6674 }
6775
6876 for (final unreadHuddleSnapshot in initial.huddles) {
6977 final narrow = DmNarrow .ofUnreadHuddleSnapshot (unreadHuddleSnapshot,
7078 selfUserId: core.selfUserId);
7179 dms[narrow] = QueueList .from (unreadHuddleSnapshot.unreadMessageIds);
80+ for (final messageId in dms[narrow]! ) {
81+ locatorMap[messageId] = narrow;
82+ }
7283 }
7384
7485 return Unreads ._(
7586 core: core,
7687 channelStore: channelStore,
88+ locatorMap: locatorMap,
7789 streams: streams,
7890 dms: dms,
7991 mentions: mentions,
@@ -84,6 +96,7 @@ class Unreads extends PerAccountStoreBase with ChangeNotifier {
8496 Unreads ._({
8597 required super .core,
8698 required this .channelStore,
99+ required this .locatorMap,
87100 required this .streams,
88101 required this .dms,
89102 required this .mentions,
@@ -92,6 +105,11 @@ class Unreads extends PerAccountStoreBase with ChangeNotifier {
92105
93106 final ChannelStore channelStore;
94107
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+
95113 // TODO excluded for now; would need to handle nuances around muting etc.
96114 // int count;
97115
@@ -233,11 +251,8 @@ class Unreads extends PerAccountStoreBase with ChangeNotifier {
233251 /// The unread state for [messageId] , or null if unknown.
234252 ///
235253 /// May be unknown only if [oldUnreadsMissing] .
236- ///
237- /// This is inefficient; it iterates through [dms] and [channels] .
238- // TODO implement efficiently
239254 bool ? isUnread (int messageId) {
240- final isPresent = _slowIsPresentInDms (messageId) || _slowIsPresentInStreams (messageId);
255+ final isPresent = locatorMap. containsKey (messageId);
241256 if (oldUnreadsMissing && ! isPresent) return null ;
242257 return isPresent;
243258 }
@@ -248,6 +263,12 @@ class Unreads extends PerAccountStoreBase with ChangeNotifier {
248263 return ;
249264 }
250265
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+
251272 switch (message) {
252273 case StreamMessage ():
253274 _addLastInStreamTopic (message.id, message.streamId, message.topic);
@@ -346,9 +367,16 @@ class Unreads extends PerAccountStoreBase with ChangeNotifier {
346367 // Unreads moved to an unsubscribed channel; just drop them.
347368 // See also:
348369 // 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+ }
349373 return true ;
350374 }
351375
376+ final narrow = TopicNarrow (newStreamId, newTopic);
377+ for (final messageId in messageToMoveIds) {
378+ locatorMap[messageId] = narrow;
379+ }
352380 _addAllInStreamTopic (messageToMoveIds, newStreamId, newTopic);
353381
354382 return true ;
@@ -363,7 +391,10 @@ class Unreads extends PerAccountStoreBase with ChangeNotifier {
363391 final topic = event.topic! ;
364392 _removeAllInStreamTopic (messageIdsSet, streamId, topic);
365393 case MessageType .direct:
366- _slowRemoveAllInDms (messageIdsSet);
394+ _removeAllInDms (messageIdsSet);
395+ }
396+ for (final messageId in event.messageIds) {
397+ locatorMap.remove (messageId);
367398 }
368399
369400 // TODO skip notifyListeners if unchanged?
@@ -405,15 +436,19 @@ class Unreads extends PerAccountStoreBase with ChangeNotifier {
405436 switch (event) {
406437 case UpdateMessageFlagsAddEvent ():
407438 if (event.all) {
439+ locatorMap.clear ();
408440 streams.clear ();
409441 dms.clear ();
410442 mentions.clear ();
411443 oldUnreadsMissing = false ;
412444 } else {
413445 final messageIdsSet = Set .of (event.messages);
414446 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+ }
417452 }
418453 case UpdateMessageFlagsRemoveEvent ():
419454 final newlyUnreadInStreams = < int , TopicKeyedMap <QueueList <int >>> {};
@@ -431,12 +466,15 @@ class Unreads extends PerAccountStoreBase with ChangeNotifier {
431466 }
432467 switch (detail.type) {
433468 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 ());
436473 messageIds.add (messageId);
437474 case MessageType .direct:
438475 final narrow = DmNarrow .ofUpdateMessageFlagsMessageDetail (selfUserId: selfUserId,
439476 detail);
477+ locatorMap[messageId] = narrow;
440478 (newlyUnreadInDms[narrow] ?? = QueueList ())
441479 .add (messageId);
442480 }
@@ -489,15 +527,6 @@ class Unreads extends PerAccountStoreBase with ChangeNotifier {
489527 notifyListeners ();
490528 }
491529
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-
501530 void _addLastInStreamTopic (int messageId, int streamId, TopicName topic) {
502531 ((streams[streamId] ?? = makeTopicKeyedMap ())[topic] ?? = QueueList ())
503532 .addLast (messageId);
@@ -517,26 +546,23 @@ class Unreads extends PerAccountStoreBase with ChangeNotifier {
517546 );
518547 }
519548
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);
530562 }
531- for ( final topic in newlyEmptyTopics ) {
532- topics .remove (topic );
563+ if (streams[narrow.streamId] ! .isEmpty ) {
564+ streams .remove (narrow.streamId );
533565 }
534- if (topics.isEmpty) {
535- newlyEmptyStreams.add (streamId);
536- }
537- }
538- for (final streamId in newlyEmptyStreams) {
539- streams.remove (streamId);
540566 }
541567 }
542568
@@ -599,11 +625,6 @@ class Unreads extends PerAccountStoreBase with ChangeNotifier {
599625 return poppedMessageIds;
600626 }
601627
602- // TODO use efficient model lookups
603- bool _slowIsPresentInDms (int messageId) {
604- return dms.values.any ((ids) => ids.contains (messageId));
605- }
606-
607628 void _addLastInDm (int messageId, DmNarrow narrow) {
608629 (dms[narrow] ?? = QueueList ()).addLast (messageId);
609630 }
@@ -619,17 +640,19 @@ class Unreads extends PerAccountStoreBase with ChangeNotifier {
619640 );
620641 }
621642
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);
627653 if (messageIds.isEmpty) {
628- newlyEmptyDms. add (dmNarrow );
654+ dms. remove (narrow );
629655 }
630656 }
631- for (final dmNarrow in newlyEmptyDms) {
632- dms.remove (dmNarrow);
633- }
634657 }
635658}
0 commit comments