Skip to content

Commit a4c640a

Browse files
committed
recent-dm-conversations: Add latestMessagesByRecipient data structure
This data structure is used to keep track of the latest message of each recipient across all DM conversations.
1 parent e9eec48 commit a4c640a

File tree

3 files changed

+90
-12
lines changed

3 files changed

+90
-12
lines changed

lib/model/recent_dm_conversations.dart

+36-3
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import 'dart:math';
2+
13
import 'package:collection/collection.dart';
24
import 'package:flutter/foundation.dart';
35

@@ -19,16 +21,31 @@ class RecentDmConversationsView extends ChangeNotifier {
1921
DmNarrow.ofRecentDmConversation(conversation, selfUserId: selfUserId),
2022
conversation.maxMessageId,
2123
)).toList()..sort((a, b) => -a.value.compareTo(b.value));
24+
25+
final map = Map.fromEntries(entries);
26+
final sorted = QueueList.from(entries.map((e) => e.key));
27+
final latestMessagesByRecipient = <int, int>{};
28+
for (final entry in entries) {
29+
final dmNarrow = entry.key;
30+
final maxMessageId = entry.value;
31+
for (final userId in dmNarrow.otherRecipientIds) {
32+
// only take the latest message of a user across all the conversations.
33+
latestMessagesByRecipient.putIfAbsent(userId, () => maxMessageId);
34+
}
35+
}
36+
2237
return RecentDmConversationsView._(
23-
map: Map.fromEntries(entries),
24-
sorted: QueueList.from(entries.map((e) => e.key)),
38+
map: map,
39+
sorted: sorted,
40+
latestMessagesByRecipient: latestMessagesByRecipient,
2541
selfUserId: selfUserId,
2642
);
2743
}
2844

2945
RecentDmConversationsView._({
3046
required this.map,
3147
required this.sorted,
48+
required this.latestMessagesByRecipient,
3249
required this.selfUserId,
3350
});
3451

@@ -38,6 +55,15 @@ class RecentDmConversationsView extends ChangeNotifier {
3855
/// The [DmNarrow] keys of [map], sorted by latest message descending.
3956
final QueueList<DmNarrow> sorted;
4057

58+
/// Map from user ID to the latest message ID in any conversation with the user.
59+
///
60+
/// Both 1:1 and group DM conversations are considered.
61+
/// The self-user ID is excluded even if there is a self-DM conversation.
62+
///
63+
/// (The identified message was not necessarily sent by the identified user;
64+
/// it might have been sent by anyone in its conversation.)
65+
final Map<int, int> latestMessagesByRecipient;
66+
4167
final int selfUserId;
4268

4369
/// Insert the key at the proper place in [sorted].
@@ -58,7 +84,7 @@ class RecentDmConversationsView extends ChangeNotifier {
5884
}
5985
}
6086

61-
/// Handle [MessageEvent], updating [map] and [sorted].
87+
/// Handle [MessageEvent], updating [map], [sorted], and [latestMessagesByRecipient].
6288
///
6389
/// Can take linear time in general. That sounds inefficient...
6490
/// but it's what the webapp does, so must not be catastrophic. 🤷
@@ -117,6 +143,13 @@ class RecentDmConversationsView extends ChangeNotifier {
117143
_insertSorted(key, message.id);
118144
}
119145
}
146+
for (final recipient in key.otherRecipientIds) {
147+
latestMessagesByRecipient.update(
148+
recipient,
149+
(latestMessageId) => max(message.id, latestMessageId),
150+
ifAbsent: () => message.id,
151+
);
152+
}
120153
notifyListeners();
121154
}
122155

test/model/recent_dm_conversations_checks.dart

+2
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,6 @@ import 'package:zulip/model/recent_dm_conversations.dart';
66
extension RecentDmConversationsViewChecks on Subject<RecentDmConversationsView> {
77
Subject<Map<DmNarrow, int>> get map => has((v) => v.map, 'map');
88
Subject<QueueList<DmNarrow>> get sorted => has((v) => v.sorted, 'sorted');
9+
Subject<Map<int, int>> get latestMessagesByRecipient => has(
10+
(v) => v.latestMessagesByRecipient, 'latestMessagesByRecipient');
911
}

test/model/recent_dm_conversations_test.dart

+52-9
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ void main() {
2222
check(RecentDmConversationsView(selfUserId: eg.selfUser.userId,
2323
initial: []))
2424
..map.isEmpty()
25-
..sorted.isEmpty();
25+
..sorted.isEmpty()
26+
..latestMessagesByRecipient.isEmpty();
2627

2728
check(RecentDmConversationsView(selfUserId: eg.selfUser.userId,
2829
initial: [
@@ -35,7 +36,8 @@ void main() {
3536
key([]): 200,
3637
key([1]): 100,
3738
})
38-
..sorted.deepEquals([key([1, 2]), key([]), key([1])]);
39+
..sorted.deepEquals([key([1, 2]), key([]), key([1])])
40+
..latestMessagesByRecipient.deepEquals({1: 300, 2: 300});
3941
});
4042

4143
group('message event (new message)', () {
@@ -55,7 +57,8 @@ void main() {
5557
key([1]): 200,
5658
key([1, 2]): 100,
5759
})
58-
..sorted.deepEquals([key([1]), key([1, 2])]);
60+
..sorted.deepEquals([key([1]), key([1, 2])])
61+
..latestMessagesByRecipient.deepEquals({1: 200, 2: 100});
5962
});
6063

6164
test('stream message -> do nothing', () {
@@ -65,7 +68,8 @@ void main() {
6568
..addListener(() { listenersNotified = true; })
6669
..handleMessageEvent(MessageEvent(id: 1, message: eg.streamMessage()))
6770
) ..map.deepEquals(expected.map)
68-
..sorted.deepEquals(expected.sorted);
71+
..sorted.deepEquals(expected.sorted)
72+
..latestMessagesByRecipient.deepEquals(expected.latestMessagesByRecipient);
6973
check(listenersNotified).isFalse();
7074
});
7175

@@ -80,7 +84,8 @@ void main() {
8084
key([1]): 200,
8185
key([1, 2]): 100,
8286
})
83-
..sorted.deepEquals([key([2]), key([1]), key([1, 2])]);
87+
..sorted.deepEquals([key([2]), key([1]), key([1, 2])])
88+
..latestMessagesByRecipient.deepEquals({1: 200, 2: 300});
8489
check(listenersNotified).isTrue();
8590
});
8691

@@ -95,7 +100,8 @@ void main() {
95100
key([2]): 150,
96101
key([1, 2]): 100,
97102
})
98-
..sorted.deepEquals([key([1]), key([2]), key([1, 2])]);
103+
..sorted.deepEquals([key([1]), key([2]), key([1, 2])])
104+
..latestMessagesByRecipient.deepEquals({1: 200, 2: 150});
99105
check(listenersNotified).isTrue();
100106
});
101107

@@ -110,7 +116,8 @@ void main() {
110116
key([1, 2]): 300,
111117
key([1]): 200,
112118
})
113-
..sorted.deepEquals([key([1, 2]), key([1])]);
119+
..sorted.deepEquals([key([1, 2]), key([1])])
120+
..latestMessagesByRecipient.deepEquals({1: 300, 2: 300});
114121
check(listenersNotified).isTrue();
115122
});
116123

@@ -124,7 +131,8 @@ void main() {
124131
key([1]): 300,
125132
key([1, 2]): 100,
126133
})
127-
..sorted.deepEquals([key([1]), key([1, 2])]);
134+
..sorted.deepEquals([key([1]), key([1, 2])])
135+
..latestMessagesByRecipient.deepEquals({1: 300, 2: 100});
128136
check(listenersNotified).isTrue();
129137
});
130138

@@ -137,10 +145,45 @@ void main() {
137145
// ..addListener(() { listenersNotified = true; })
138146
..handleMessageEvent(MessageEvent(id: 1, message: message))
139147
) ..map.deepEquals(expected.map)
140-
..sorted.deepEquals(expected.sorted);
148+
..sorted.deepEquals(expected.sorted)
149+
..latestMessagesByRecipient.deepEquals(expected.latestMessagesByRecipient);
141150
// (listeners are notified unnecessarily, but that's OK)
142151
// check(listenersNotified).isFalse();
143152
});
153+
154+
test('new conversation with one existing and one new user, newest message', () {
155+
bool listenersNotified = false;
156+
final message = eg.dmMessage(id: 300, from: eg.selfUser,
157+
to: [eg.user(userId: 1), eg.user(userId: 3)]);
158+
check(setupView()
159+
..addListener(() { listenersNotified = true; })
160+
..handleMessageEvent(MessageEvent(id: 1, message: message))
161+
) ..map.deepEquals({
162+
key([1, 3]): 300,
163+
key([1]): 200,
164+
key([1, 2]): 100,
165+
})
166+
..sorted.deepEquals([key([1, 3]), key([1]), key([1, 2])])
167+
..latestMessagesByRecipient.deepEquals({1: 300, 2: 100, 3: 300});
168+
check(listenersNotified).isTrue();
169+
});
170+
171+
test('new conversation with one existing and one new user, not newest message', () {
172+
bool listenersNotified = false;
173+
final message = eg.dmMessage(id: 150, from: eg.selfUser,
174+
to: [eg.user(userId: 1), eg.user(userId: 3)]);
175+
check(setupView()
176+
..addListener(() { listenersNotified = true; })
177+
..handleMessageEvent(MessageEvent(id: 1, message: message))
178+
) ..map.deepEquals({
179+
key([1]): 200,
180+
key([1, 3]): 150,
181+
key([1, 2]): 100,
182+
})
183+
..sorted.deepEquals([key([1]), key([1, 3]), key([1, 2])])
184+
..latestMessagesByRecipient.deepEquals({1: 200, 2: 100, 3: 150});
185+
check(listenersNotified).isTrue();
186+
});
144187
});
145188
});
146189
}

0 commit comments

Comments
 (0)