Skip to content

Commit 3d19e8c

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 3b96461 commit 3d19e8c

File tree

3 files changed

+87
-11
lines changed

3 files changed

+87
-11
lines changed

lib/model/recent_dm_conversations.dart

Lines changed: 35 additions & 3 deletions
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

Lines changed: 2 additions & 0 deletions
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

Lines changed: 50 additions & 8 deletions
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

@@ -140,6 +148,40 @@ void main() {
140148
..sorted.deepEquals(expected.sorted);
141149
check(listenersNotified).isTrue();
142150
});
151+
152+
test('new conversation with one existing and one new user, newest message', () {
153+
bool listenersNotified = false;
154+
final message = eg.dmMessage(id: 300, from: eg.selfUser,
155+
to: [eg.user(userId: 1), eg.user(userId: 3)]);
156+
check(setupView()
157+
..addListener(() { listenersNotified = true; })
158+
..handleMessageEvent(MessageEvent(id: 1, message: message))
159+
) ..map.deepEquals({
160+
key([1, 3]): 300,
161+
key([1]): 200,
162+
key([1, 2]): 100,
163+
})
164+
..sorted.deepEquals([key([1, 3]), key([1]), key([1, 2])])
165+
..latestMessagesByRecipient.deepEquals({1: 300, 2: 100, 3: 300});
166+
check(listenersNotified).isTrue();
167+
});
168+
169+
test('new conversation with one existing and one new user, not newest message', () {
170+
bool listenersNotified = false;
171+
final message = eg.dmMessage(id: 150, from: eg.selfUser,
172+
to: [eg.user(userId: 1), eg.user(userId: 3)]);
173+
check(setupView()
174+
..addListener(() { listenersNotified = true; })
175+
..handleMessageEvent(MessageEvent(id: 1, message: message))
176+
) ..map.deepEquals({
177+
key([1]): 200,
178+
key([1, 3]): 150,
179+
key([1, 2]): 100,
180+
})
181+
..sorted.deepEquals([key([1]), key([1, 3]), key([1, 2])])
182+
..latestMessagesByRecipient.deepEquals({1: 200, 2: 100, 3: 150});
183+
check(listenersNotified).isTrue();
184+
});
143185
});
144186
});
145187
}

0 commit comments

Comments
 (0)