@@ -145,6 +145,18 @@ class AutocompleteViewManager {
145
145
autocompleteDataCache.invalidateUser (event.userId);
146
146
}
147
147
148
+ void handleMessageEvent (MessageEvent event) {
149
+ for (final view in _mentionAutocompleteViews) {
150
+ view.refreshStaleUserResults ();
151
+ }
152
+ }
153
+
154
+ void handleOlderMessages () {
155
+ for (final view in _mentionAutocompleteViews) {
156
+ view.refreshStaleUserResults ();
157
+ }
158
+ }
159
+
148
160
/// Called when the app is reassembled during debugging, e.g. for hot reload.
149
161
///
150
162
/// Calls [MentionAutocompleteView.reassemble] for all that are registered.
@@ -190,6 +202,7 @@ class MentionAutocompleteView extends ChangeNotifier {
190
202
@override
191
203
void dispose () {
192
204
store.autocompleteViewManager.unregisterMentionAutocomplete (this );
205
+ _sortedUsers = null ;
193
206
// We cancel in-progress computations by checking [hasListeners] between tasks.
194
207
// After [super.dispose] is called, [hasListeners] returns false.
195
208
// TODO test that logic (may involve detecting an unhandled Future rejection; how?)
@@ -213,6 +226,7 @@ class MentionAutocompleteView extends ChangeNotifier {
213
226
/// Called in particular when we get a [RealmUserEvent] .
214
227
void refreshStaleUserResults () {
215
228
if (_query != null ) {
229
+ _sortedUsers = null ;
216
230
_startSearch (_query! );
217
231
}
218
232
}
@@ -222,6 +236,7 @@ class MentionAutocompleteView extends ChangeNotifier {
222
236
/// This will redo the search from scratch for the current query, if any.
223
237
void reassemble () {
224
238
if (_query != null ) {
239
+ _sortedUsers = null ;
225
240
_startSearch (_query! );
226
241
}
227
242
}
@@ -251,11 +266,95 @@ class MentionAutocompleteView extends ChangeNotifier {
251
266
notifyListeners ();
252
267
}
253
268
269
+ List <User >? _sortedUsers;
270
+
271
+ int compareByRelevance ({
272
+ required User userA,
273
+ required User userB,
274
+ required int ? streamId,
275
+ required String ? topic,
276
+ }) {
277
+ // TODO(#234): give preference to "all", "everyone" or "stream".
278
+
279
+ // TODO(#618): give preference to subscribed users first.
280
+
281
+ if (streamId != null ) {
282
+ final conversationPrecedence = store.recentSenders.compareByRecency (
283
+ userA,
284
+ userB,
285
+ streamId: streamId,
286
+ topic: topic);
287
+ if (conversationPrecedence != 0 ) {
288
+ return conversationPrecedence;
289
+ }
290
+ }
291
+
292
+ final dmPrecedence = store.recentDmConversationsView.compareByDms (userA, userB);
293
+ if (dmPrecedence != 0 ) {
294
+ return dmPrecedence;
295
+ }
296
+
297
+ if (! userA.isBot && userB.isBot) {
298
+ return - 1 ;
299
+ } else if (userA.isBot && ! userB.isBot) {
300
+ return 1 ;
301
+ }
302
+
303
+ final userAName = store.autocompleteViewManager.autocompleteDataCache
304
+ .normalizedNameForUser (userA);
305
+ final userBName = store.autocompleteViewManager.autocompleteDataCache
306
+ .normalizedNameForUser (userB);
307
+ return userAName.compareTo (userBName);
308
+ }
309
+
310
+ List <User > sortByRelevance ({
311
+ required List <User > users,
312
+ required Narrow narrow,
313
+ }) {
314
+ switch (narrow) {
315
+ case StreamNarrow ():
316
+ users.sort ((userA, userB) => compareByRelevance (
317
+ userA: userA,
318
+ userB: userB,
319
+ streamId: narrow.streamId,
320
+ topic: null ));
321
+ case TopicNarrow ():
322
+ users.sort ((userA, userB) => compareByRelevance (
323
+ userA: userA,
324
+ userB: userB,
325
+ streamId: narrow.streamId,
326
+ topic: narrow.topic));
327
+ case DmNarrow ():
328
+ users.sort ((userA, userB) => compareByRelevance (
329
+ userA: userA,
330
+ userB: userB,
331
+ streamId: null ,
332
+ topic: null ));
333
+ case AllMessagesNarrow ():
334
+ // do nothing in this case for now
335
+ }
336
+ return users;
337
+ }
338
+
339
+ void _sortUsers () {
340
+ final users = store.users.values.toList ();
341
+ _sortedUsers = sortByRelevance (users: users, narrow: narrow);
342
+ }
343
+
344
+ bool _areUsersModified = false ;
345
+ void _usersModified () {
346
+ _areUsersModified = true ;
347
+ }
348
+
254
349
Future <List <MentionAutocompleteResult >?> _computeResults (MentionAutocompleteQuery query) async {
255
350
final List <MentionAutocompleteResult > results = [];
256
- final Iterable <User > users = store.users.values;
257
351
258
- final iterator = users.iterator;
352
+ if (_sortedUsers == null ) {
353
+ _sortUsers ();
354
+ }
355
+
356
+ final iterator = _sortedUsers! .iterator;
357
+ store.users.addListener (_usersModified);
259
358
bool isDone = false ;
260
359
while (! isDone) {
261
360
// CPU perf: End this task; enqueue a new one for resuming this work
@@ -266,7 +365,12 @@ class MentionAutocompleteView extends ChangeNotifier {
266
365
}
267
366
268
367
for (int i = 0 ; i < 1000 ; i++ ) {
269
- if (! iterator.moveNext ()) { // Can throw ConcurrentModificationError
368
+ if (_areUsersModified) {
369
+ _areUsersModified = false ;
370
+ _sortedUsers = null ;
371
+ throw ConcurrentModificationError ();
372
+ }
373
+ if (! iterator.moveNext ()) {
270
374
isDone = true ;
271
375
break ;
272
376
}
@@ -277,7 +381,8 @@ class MentionAutocompleteView extends ChangeNotifier {
277
381
}
278
382
}
279
383
}
280
- return results; // TODO(#228) sort for most relevant first
384
+ store.users.removeListener (_usersModified);
385
+ return results;
281
386
}
282
387
}
283
388
@@ -337,13 +442,22 @@ class MentionAutocompleteQuery {
337
442
}
338
443
339
444
class AutocompleteDataCache {
445
+ final Map <int , String > _normalizedNamesByUser = {};
446
+
447
+ /// The lowercase `fullName` of [user] .
448
+ String normalizedNameForUser (User user) {
449
+ return _normalizedNamesByUser[user.userId] ?? = user.fullName.toLowerCase ();
450
+ }
451
+
340
452
final Map <int , List <String >> _nameWordsByUser = {};
341
453
342
454
List <String > nameWordsForUser (User user) {
343
- return _nameWordsByUser[user.userId] ?? = user.fullName.toLowerCase ().split (' ' );
455
+ return _nameWordsByUser[user.userId] ?? =
456
+ normalizedNameForUser (user).split (' ' );
344
457
}
345
458
346
459
void invalidateUser (int userId) {
460
+ _normalizedNamesByUser.remove (userId);
347
461
_nameWordsByUser.remove (userId);
348
462
}
349
463
}
0 commit comments