Skip to content

Commit 75004d7

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 in all DM conversations.
1 parent 3a0b61e commit 75004d7

File tree

3 files changed

+89
-12
lines changed

3 files changed

+89
-12
lines changed

lib/model/recent_dm_conversations.dart

+35-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,30 @@ class RecentDmConversationsView extends ChangeNotifier {
1921
DmNarrow.ofRecentDmConversation(conversation, selfUserId: selfUserId),
2022
conversation.maxMessageId,
2123
)).toList()..sort((a, b) => -a.value.compareTo(b.value));
24+
final map = Map.fromEntries(entries);
25+
final sorted = QueueList.from(entries.map((e) => e.key));
26+
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+
}
2236
return RecentDmConversationsView._(
23-
map: Map.fromEntries(entries),
24-
sorted: QueueList.from(entries.map((e) => e.key)),
37+
map: map,
38+
sorted: sorted,
39+
latestMessagesByRecipient: latestMessagesByRecipient,
2540
selfUserId: selfUserId,
2641
);
2742
}
2843

2944
RecentDmConversationsView._({
3045
required this.map,
3146
required this.sorted,
47+
required this.latestMessagesByRecipient,
3248
required this.selfUserId,
3349
});
3450

@@ -38,6 +54,15 @@ class RecentDmConversationsView extends ChangeNotifier {
3854
/// The [DmNarrow] keys of [map], sorted by latest message descending.
3955
final QueueList<DmNarrow> sorted;
4056

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

4368
/// Insert the key at the proper place in [sorted].
@@ -58,7 +83,7 @@ class RecentDmConversationsView extends ChangeNotifier {
5883
}
5984
}
6085

61-
/// Handle [MessageEvent], updating [map] and [sorted].
86+
/// Handle [MessageEvent], updating [map], [sorted], and [latestMessagesByRecipient].
6287
///
6388
/// Can take linear time in general. That sounds inefficient...
6489
/// but it's what the webapp does, so must not be catastrophic. 🤷
@@ -117,6 +142,13 @@ class RecentDmConversationsView extends ChangeNotifier {
117142
_insertSorted(key, message.id);
118143
}
119144
}
145+
for (final recipient in key.otherRecipientIds) {
146+
latestMessagesByRecipient.update(
147+
recipient,
148+
(latestMessageId) => max(message.id, latestMessageId),
149+
ifAbsent: () => message.id,
150+
);
151+
}
120152
notifyListeners();
121153
}
122154

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).isTrue();
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)