Skip to content

Commit ad074b2

Browse files
committed
unreads [nfc]: Add StreamUnreads class, to store per-stream unread count
We'll give it a `count` field next.
1 parent aeb570f commit ad074b2

File tree

3 files changed

+36
-23
lines changed

3 files changed

+36
-23
lines changed

lib/model/unreads.dart

Lines changed: 28 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -41,14 +41,15 @@ import 'narrow.dart';
4141
class Unreads extends ChangeNotifier {
4242
factory Unreads({required UnreadMessagesSnapshot initial, required selfUserId}) {
4343
int totalCount = 0;
44-
final streams = <int, Map<String, QueueList<int>>>{};
44+
final streams = <int, StreamUnreads>{};
4545
final dms = <DmNarrow, QueueList<int>>{};
4646
final mentions = Set.of(initial.mentions);
4747

4848
for (final unreadStreamSnapshot in initial.streams) {
4949
final streamId = unreadStreamSnapshot.streamId;
5050
final topic = unreadStreamSnapshot.topic;
51-
(streams[streamId] ??= {})[topic] = QueueList.from(unreadStreamSnapshot.unreadMessageIds);
51+
(streams[streamId] ??= StreamUnreads.empty())
52+
.topics[topic] = QueueList.from(unreadStreamSnapshot.unreadMessageIds);
5253
totalCount += unreadStreamSnapshot.unreadMessageIds.length;
5354
}
5455

@@ -96,7 +97,7 @@ class Unreads extends ChangeNotifier {
9697
int _totalCount;
9798

9899
/// Unread stream messages, as: stream ID → topic → message ID.
99-
final Map<int, Map<String, QueueList<int>>> streams;
100+
final Map<int, StreamUnreads> streams;
100101

101102
/// Unread DM messages, as: DM narrow → message ID.
102103
final Map<DmNarrow, QueueList<int>> dms;
@@ -327,22 +328,23 @@ class Unreads extends ChangeNotifier {
327328
// TODO use efficient lookups
328329
bool _slowIsPresentInStreams(int messageId) {
329330
return streams.values.any(
330-
(topics) => topics.values.any(
331+
(streamUnreads) => streamUnreads.topics.values.any(
331332
(messageIds) => messageIds.contains(messageId),
332333
),
333334
);
334335
}
335336

336337
void _addLastInStreamTopic(int messageId, int streamId, String topic) {
337-
((streams[streamId] ??= {})[topic] ??= QueueList()).addLast(messageId);
338+
final streamUnreads = streams[streamId] ??= StreamUnreads.empty();
339+
(streamUnreads.topics[topic] ??= QueueList()).addLast(messageId);
338340
_totalCount += 1;
339341
}
340342

341343
// [messageIds] must be sorted ascending and without duplicates.
342344
void _addAllInStreamTopic(QueueList<int> messageIds, int streamId, String topic) {
343345
int numAdded = 0;
344-
final topics = streams[streamId] ??= {};
345-
topics.update(topic,
346+
final streamUnreads = streams[streamId] ??= StreamUnreads.empty();
347+
streamUnreads.topics.update(topic,
346348
ifAbsent: () {
347349
numAdded = messageIds.length;
348350
return messageIds;
@@ -363,9 +365,9 @@ class Unreads extends ChangeNotifier {
363365
void _slowRemoveAllInStreams(Set<int> idsToRemove) {
364366
int numRemoved = 0;
365367
final newlyEmptyStreams = [];
366-
for (final MapEntry(key: streamId, value: topics) in streams.entries) {
368+
for (final MapEntry(key: streamId, value: streamUnreads) in streams.entries) {
367369
final newlyEmptyTopics = [];
368-
for (final MapEntry(key: topic, value: messageIds) in topics.entries) {
370+
for (final MapEntry(key: topic, value: messageIds) in streamUnreads.topics.entries) {
369371
final lengthBefore = messageIds.length;
370372
messageIds.removeWhere((id) => idsToRemove.contains(id));
371373
numRemoved += lengthBefore - messageIds.length;
@@ -374,9 +376,9 @@ class Unreads extends ChangeNotifier {
374376
}
375377
}
376378
for (final topic in newlyEmptyTopics) {
377-
topics.remove(topic);
379+
streamUnreads.topics.remove(topic);
378380
}
379-
if (topics.isEmpty) {
381+
if (streamUnreads.topics.isEmpty) {
380382
newlyEmptyStreams.add(streamId);
381383
}
382384
}
@@ -387,18 +389,18 @@ class Unreads extends ChangeNotifier {
387389
}
388390

389391
void _removeAllInStreamTopic(Set<int> incomingMessageIds, int streamId, String topic) {
390-
final topics = streams[streamId];
391-
if (topics == null) return;
392-
final messageIds = topics[topic];
392+
final streamUnreads = streams[streamId];
393+
if (streamUnreads == null) return;
394+
final messageIds = streamUnreads.topics[topic];
393395
if (messageIds == null) return;
394396

395397
// ([QueueList] doesn't have a `removeAll`)
396398
final lengthBefore = messageIds.length;
397399
messageIds.removeWhere((id) => incomingMessageIds.contains(id));
398400
_totalCount -= lengthBefore - messageIds.length;
399401
if (messageIds.isEmpty) {
400-
topics.remove(topic);
401-
if (topics.isEmpty) {
402+
streamUnreads.topics.remove(topic);
403+
if (streamUnreads.topics.isEmpty) {
402404
streams.remove(streamId);
403405
}
404406
}
@@ -452,3 +454,13 @@ class Unreads extends ChangeNotifier {
452454
_totalCount -= numRemoved;
453455
}
454456
}
457+
458+
class StreamUnreads {
459+
StreamUnreads({required this.topics});
460+
StreamUnreads.empty() : topics = {};
461+
462+
Map<String, QueueList<int>> topics;
463+
464+
@visibleForTesting
465+
Map<String, dynamic> toJson() => {'topics': topics};
466+
}

test/model/unreads_checks.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import 'package:zulip/model/unreads.dart';
55

66
extension UnreadsChecks on Subject<Unreads> {
77
Subject<int> get count => has((u) => u.totalCount, 'count');
8-
Subject<Map<int, Map<String, QueueList<int>>>> get streams => has((u) => u.streams, 'streams');
8+
Subject<Map<int, StreamUnreads>> get streams => has((u) => u.streams, 'streams');
99
Subject<Map<DmNarrow, QueueList<int>>> get dms => has((u) => u.dms, 'dms');
1010
Subject<Set<int>> get mentions => has((u) => u.mentions, 'mentions');
1111
Subject<bool> get oldUnreadsMissing => has((u) => u.oldUnreadsMissing, 'oldUnreadsMissing');

test/model/unreads_test.dart

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import 'package:zulip/model/narrow.dart';
88
import 'package:zulip/model/unreads.dart';
99

1010
import '../example_data.dart' as eg;
11+
import '../stdlib_checks.dart';
1112
import 'unreads_checks.dart';
1213

1314
void main() {
@@ -53,7 +54,7 @@ void main() {
5354
assert(Set.of(messages.map((m) => m.id)).length == messages.length,
5455
'checkMatchesMessages: duplicate messages in test input');
5556

56-
final Map<int, Map<String, QueueList<int>>> expectedStreams = {};
57+
final Map<int, StreamUnreads> expectedStreams = {};
5758
final Map<DmNarrow, QueueList<int>> expectedDms = {};
5859
final Set<int> expectedMentions = {};
5960
for (final message in messages) {
@@ -62,8 +63,8 @@ void main() {
6263
}
6364
switch (message) {
6465
case StreamMessage():
65-
final perTopic = expectedStreams[message.streamId] ??= {};
66-
final messageIds = perTopic[message.subject] ??= QueueList();
66+
final streamUnreads = expectedStreams[message.streamId] ??= StreamUnreads.empty();
67+
final messageIds = streamUnreads.topics[message.subject] ??= QueueList();
6768
messageIds.add(message.id);
6869
case DmMessage():
6970
final narrow = DmNarrow.ofMessage(message, selfUserId: eg.selfUser.userId);
@@ -77,8 +78,8 @@ void main() {
7778
expectedMentions.add(message.id);
7879
}
7980
}
80-
for (final perTopic in expectedStreams.values) {
81-
for (final messageIds in perTopic.values) {
81+
for (final streamUnreads in expectedStreams.values) {
82+
for (final messageIds in streamUnreads.topics.values) {
8283
messageIds.sort();
8384
}
8485
}
@@ -88,7 +89,7 @@ void main() {
8889

8990
check(model)
9091
..count.equals(messages.where((m) => !m.flags.contains(MessageFlag.read)).length)
91-
..streams.deepEquals(expectedStreams)
92+
..streams.jsonEquals(expectedStreams)
9293
..dms.deepEquals(expectedDms)
9394
..mentions.unorderedEquals(expectedMentions);
9495
}

0 commit comments

Comments
 (0)