@@ -183,6 +183,7 @@ class MentionAutocompleteView extends ChangeNotifier {
183
183
@override
184
184
void dispose () {
185
185
store.autocompleteViewManager.unregisterMentionAutocomplete (this );
186
+ _sortedUsers = null ;
186
187
// We cancel in-progress computations by checking [hasListeners] between tasks.
187
188
// After [super.dispose] is called, [hasListeners] returns false.
188
189
// TODO test that logic (may involve detecting an unhandled Future rejection; how?)
@@ -206,6 +207,7 @@ class MentionAutocompleteView extends ChangeNotifier {
206
207
/// Called in particular when we get a [RealmUserEvent] .
207
208
void refreshStaleUserResults () {
208
209
if (_query != null ) {
210
+ _sortedUsers = null ;
209
211
_startSearch (_query! );
210
212
}
211
213
}
@@ -244,11 +246,89 @@ class MentionAutocompleteView extends ChangeNotifier {
244
246
notifyListeners ();
245
247
}
246
248
249
+ List <User >? _sortedUsers;
250
+
251
+ int compareByRelevance ({
252
+ required User userA,
253
+ required User userB,
254
+ required int ? streamId,
255
+ required String ? topic,
256
+ }) {
257
+ // TODO(#234): give preference to "all", "everyone" or "stream".
258
+
259
+ // TODO(#618): give preference to subscribed users first.
260
+
261
+ if (streamId != null ) {
262
+ final conversationPrecedence = store.recentSenders.compareByRecency (
263
+ userA: userA,
264
+ userB: userB,
265
+ streamId: streamId,
266
+ topic: topic);
267
+ if (conversationPrecedence != 0 ) {
268
+ return conversationPrecedence;
269
+ }
270
+ }
271
+
272
+ final dmPrecedence = store.recentDmConversationsView.compareByDms (userA, userB);
273
+ if (dmPrecedence != 0 ) {
274
+ return dmPrecedence;
275
+ }
276
+
277
+ if (! userA.isBot && userB.isBot) {
278
+ return - 1 ;
279
+ } else if (userA.isBot && ! userB.isBot) {
280
+ return 1 ;
281
+ }
282
+
283
+ final userAName = store.autocompleteViewManager.autocompleteDataCache
284
+ .nameForUser (userA);
285
+ final userBName = store.autocompleteViewManager.autocompleteDataCache
286
+ .nameForUser (userB);
287
+ return userAName.compareTo (userBName);
288
+ }
289
+
290
+ List <User > sortByRelevance ({
291
+ required List <User > users,
292
+ required Narrow narrow,
293
+ }) {
294
+ switch (narrow) {
295
+ case StreamNarrow ():
296
+ users.sort ((userA, userB) => compareByRelevance (
297
+ userA: userA,
298
+ userB: userB,
299
+ streamId: narrow.streamId,
300
+ topic: null ));
301
+ case TopicNarrow ():
302
+ users.sort ((userA, userB) => compareByRelevance (
303
+ userA: userA,
304
+ userB: userB,
305
+ streamId: narrow.streamId,
306
+ topic: narrow.topic));
307
+ case DmNarrow ():
308
+ users.sort ((userA, userB) => compareByRelevance (
309
+ userA: userA,
310
+ userB: userB,
311
+ streamId: null ,
312
+ topic: null ));
313
+ case AllMessagesNarrow ():
314
+ // do nothing in this case for now
315
+ }
316
+ return users;
317
+ }
318
+
319
+ void _sortUsers () {
320
+ final users = store.users.values.toList ();
321
+ _sortedUsers = sortByRelevance (users: users, narrow: narrow);
322
+ }
323
+
247
324
Future <List <MentionAutocompleteResult >?> _computeResults (MentionAutocompleteQuery query) async {
248
325
final List <MentionAutocompleteResult > results = [];
249
- final Iterable <User > users = store.users.values;
250
326
251
- final iterator = users.iterator;
327
+ if (_sortedUsers == null ) {
328
+ _sortUsers ();
329
+ }
330
+
331
+ final iterator = _sortedUsers! .iterator;
252
332
bool isDone = false ;
253
333
while (! isDone) {
254
334
// CPU perf: End this task; enqueue a new one for resuming this work
@@ -270,7 +350,7 @@ class MentionAutocompleteView extends ChangeNotifier {
270
350
}
271
351
}
272
352
}
273
- return results; // TODO(#228) sort for most relevant first
353
+ return results;
274
354
}
275
355
}
276
356
@@ -330,13 +410,25 @@ class MentionAutocompleteQuery {
330
410
}
331
411
332
412
class AutocompleteDataCache {
413
+ final Map <int , String > _namesByUser = {};
414
+
415
+ /// The lowercase `fullName` of [user] .
416
+ String nameForUser (User user) {
417
+ return _namesByUser[user.userId] ?? = user.fullName.toLowerCase ();
418
+ }
419
+
333
420
final Map <int , List <String >> _nameWordsByUser = {};
334
421
335
422
List <String > nameWordsForUser (User user) {
336
- return _nameWordsByUser[user.userId] ?? = user.fullName.toLowerCase ().split (' ' );
423
+ final nameWords = _nameWordsByUser[user.userId];
424
+ if (nameWords != null ) return nameWords;
425
+
426
+ final name = _namesByUser[user.userId] ?? = user.fullName.toLowerCase ();
427
+ return _nameWordsByUser[user.userId] = name.split (' ' );
337
428
}
338
429
339
430
void invalidateUser (int userId) {
431
+ _namesByUser.remove (userId);
340
432
_nameWordsByUser.remove (userId);
341
433
}
342
434
}
0 commit comments