Skip to content

Commit 1bd73ff

Browse files
committed
autocomplete: Add "recent activity in current stream" criterion
In @-mention autocomplete, users are suggested based on: 1. Recent activity in the current stream. 2. Recent DM conversations. Fixes part of: #228
1 parent 600220a commit 1bd73ff

File tree

3 files changed

+130
-4
lines changed

3 files changed

+130
-4
lines changed

lib/model/autocomplete.dart

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,42 @@ class MentionAutocompleteView extends ChangeNotifier {
267267

268268
List<User>? _sortedUsers;
269269

270+
/// Determines which of the two users has more recent activity.
271+
///
272+
/// First checks for the activity in [topic] if provided.
273+
///
274+
/// If no [topic] is provided, or the activity in the topic is the same (which
275+
/// is extremely rare) or there is no activity in the topic at all, then
276+
/// checks for the activity in the stream with [streamId].
277+
///
278+
/// Returns a negative number if [userA] has more recent activity than [userB],
279+
/// returns a positive number if [userB] has more recent activity than [userA],
280+
/// and returns `0` if both [userA] and [userB] have the same recent activity
281+
/// (which is extremely rare) or has no activity at all.
282+
int compareByRecency(
283+
User userA,
284+
User userB, {
285+
required int streamId,
286+
required String? topic,
287+
}) {
288+
final recentSenders = store.recentSenders;
289+
if (topic != null) {
290+
final aMessageId = recentSenders.latestMessageIdOfSenderInTopic(
291+
streamId: streamId, topic: topic, senderId: userA.userId);
292+
final bMessageId = recentSenders.latestMessageIdOfSenderInTopic(
293+
streamId: streamId, topic: topic, senderId: userB.userId);
294+
295+
final result = bMessageId.compareTo(aMessageId);
296+
if (result != 0) return result;
297+
}
298+
299+
final aMessageId =
300+
recentSenders.latestMessageIdOfSenderInStream(streamId: streamId, senderId: userA.userId);
301+
final bMessageId =
302+
recentSenders.latestMessageIdOfSenderInStream(streamId: streamId, senderId: userB.userId);
303+
return bMessageId.compareTo(aMessageId);
304+
}
305+
270306
/// Determines which of the two users are more recent in DM conversations.
271307
///
272308
/// Returns a negative number if [userA] is more recent than [userB],
@@ -284,7 +320,20 @@ class MentionAutocompleteView extends ChangeNotifier {
284320
int compareByRelevance({
285321
required User userA,
286322
required User userB,
323+
required int? streamId,
324+
required String? topic,
287325
}) {
326+
if (streamId != null) {
327+
final conversationPrecedence = compareByRecency(
328+
userA,
329+
userB,
330+
streamId: streamId,
331+
topic: topic);
332+
if (conversationPrecedence != 0) {
333+
return conversationPrecedence;
334+
}
335+
}
336+
288337
final dmPrecedence = compareByDms(userA, userB);
289338
return dmPrecedence;
290339
}
@@ -295,11 +344,23 @@ class MentionAutocompleteView extends ChangeNotifier {
295344
}) {
296345
switch (narrow) {
297346
case StreamNarrow():
347+
users.sort((userA, userB) => compareByRelevance(
348+
userA: userA,
349+
userB: userB,
350+
streamId: narrow.streamId,
351+
topic: null));
298352
case TopicNarrow():
353+
users.sort((userA, userB) => compareByRelevance(
354+
userA: userA,
355+
userB: userB,
356+
streamId: narrow.streamId,
357+
topic: narrow.topic));
299358
case DmNarrow():
300359
users.sort((userA, userB) => compareByRelevance(
301360
userA: userA,
302-
userB: userB));
361+
userB: userB,
362+
streamId: null,
363+
topic: null));
303364
case AllMessagesNarrow():
304365
// do nothing in this case for now
305366
}

lib/model/message_list.dart

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -381,6 +381,7 @@ class MessageListView with ChangeNotifier, _MessageSequence {
381381
for (final message in result.messages) {
382382
if (_messageVisible(message)) {
383383
_addMessage(message);
384+
store.recentSenders.handleMessage(message);
384385
}
385386
}
386387
_fetched = true;
@@ -417,6 +418,9 @@ class MessageListView with ChangeNotifier, _MessageSequence {
417418
? result.messages // Avoid unnecessarily copying the list.
418419
: result.messages.where(_messageVisible);
419420

421+
for (final message in fetchedMessages) {
422+
store.recentSenders.handleMessage(message);
423+
}
420424
store.autocompleteViewManager.handleOlderMessages();
421425

422426
_insertAllMessages(0, fetchedMessages);

test/model/autocomplete_test.dart

Lines changed: 64 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -356,7 +356,7 @@ void main() {
356356
late PerAccountStore store;
357357
late MentionAutocompleteView view;
358358

359-
prepare({
359+
void prepare({
360360
required List<User> users,
361361
required List<RecentDmConversation> dmConversations,
362362
required Narrow narrow,
@@ -371,7 +371,67 @@ void main() {
371371
view = MentionAutocompleteView.init(store: store, narrow: narrow);
372372
}
373373

374-
test('compareByDms give priority to user with DM exchanged more recently', () {
374+
group('compareByRecency gives priority to the user with latter message in topic/stream', () {
375+
final userA = eg.otherUser;
376+
final userB = eg.thirdUser;
377+
final stream = eg.stream();
378+
const topic1 = 'topic1';
379+
const topic2 = 'topic2';
380+
381+
void addMessage(User sender, String topic) {
382+
store.addMessage(eg.streamMessage(
383+
sender: sender,
384+
stream: stream,
385+
topic: topic,
386+
));
387+
}
388+
389+
/// Determines the priority between [userA] and [userB] based on their activity.
390+
///
391+
/// The activity is first looked for in [topic] then in [stream].
392+
///
393+
/// Returns a negative number if [userA] has more recent activity,
394+
/// returns a positive number if [userB] has more recent activity, and
395+
/// returns `0` if the activity is the same or there is no activity at all.
396+
int compareAB({required String? topic}) {
397+
return view.compareByRecency(
398+
userA,
399+
userB,
400+
streamId: stream.streamId,
401+
topic: topic,
402+
);
403+
}
404+
405+
test('prioritizes the user with more recent activity in the topic', () {
406+
prepare(users: [], dmConversations: [], narrow: const AllMessagesNarrow());
407+
addMessage(userA, topic1);
408+
addMessage(userB, topic1);
409+
check(compareAB(topic: topic1)).isGreaterThan(0);
410+
});
411+
412+
test('prioritizes the user with more recent activity in the stream '
413+
'if there is no activity in the topic from both users', () {
414+
prepare(users: [], dmConversations: [], narrow: const AllMessagesNarrow());
415+
addMessage(userA, topic1);
416+
addMessage(userB, topic1);
417+
check(compareAB(topic: topic2)).isGreaterThan(0);
418+
});
419+
420+
test('prioritizes the user with more recent activity in the stream '
421+
'if there is no topic provided', () {
422+
prepare(users: [], dmConversations: [], narrow: const AllMessagesNarrow());
423+
addMessage(userA, topic1);
424+
addMessage(userB, topic2);
425+
check(compareAB(topic: null)).isGreaterThan(0);
426+
});
427+
428+
test('prioritizes none of the users if there is no activity in the stream from both users', () {
429+
prepare(users: [], dmConversations: [], narrow: const AllMessagesNarrow());
430+
check(compareAB(topic: null)).equals(0);
431+
});
432+
});
433+
434+
test('compareByDms gives priority to user with DM exchanged more recently', () {
375435
prepare(
376436
users: [],
377437
dmConversations: [
@@ -388,7 +448,8 @@ void main() {
388448
});
389449

390450
test('autocomplete suggests relevant users in the following order: '
391-
'1. Users most recent in the DM conversations', () async {
451+
'1. User most recent in the current topic/stream '
452+
'2. Users most recent in the DM conversations', () async {
392453
final users = [
393454
eg.user(userId: 1),
394455
eg.user(userId: 2),

0 commit comments

Comments
 (0)