@@ -3,7 +3,9 @@ import 'package:flutter/services.dart';
3
3
4
4
import '../api/model/events.dart' ;
5
5
import '../api/model/model.dart' ;
6
+ import '../api/route/streams.dart' ;
6
7
import '../widgets/compose_box.dart' ;
8
+ import 'narrow.dart' ;
7
9
import 'store.dart' ;
8
10
9
11
extension ComposeContentAutocomplete on ComposeContentController {
@@ -42,6 +44,16 @@ extension ComposeContentAutocomplete on ComposeContentController {
42
44
}
43
45
}
44
46
47
+ extension ComposeTopicAutocomplete on ComposeTopicController {
48
+ AutocompleteIntent <TopicAutocompleteQuery >? autocompleteIntent () {
49
+ if (! selection.isValid || ! selection.isNormalized) return null ;
50
+ return AutocompleteIntent (
51
+ syntaxStart: 0 ,
52
+ query: TopicAutocompleteQuery (value.text),
53
+ textEditingValue: value);
54
+ }
55
+ }
56
+
45
57
final RegExp mentionAutocompleteMarkerRegex = (() {
46
58
// What's likely to come before an @-mention: the start of the string,
47
59
// whitespace, or punctuation. Letters are unlikely; in that case an email
@@ -157,6 +169,12 @@ class AutocompleteViewManager {
157
169
autocompleteDataCache.invalidateUser (event.userId);
158
170
}
159
171
172
+ void handleTopicsFetchCompleted () {
173
+ for (final view in _getViewsOfType <TopicAutocompleteView >()) {
174
+ view.reassemble ();
175
+ }
176
+ }
177
+
160
178
/// Called when the app is reassembled during debugging, e.g. for hot reload.
161
179
///
162
180
/// Calls [AutocompleteView.reassemble] for all that are registered.
@@ -399,6 +417,7 @@ class MentionAutocompleteQuery extends AutocompleteQuery {
399
417
400
418
class AutocompleteDataCache {
401
419
final Map <int , List <String >> _nameWordsByUser = {};
420
+ final Map <int , List <String >> _nameWordsByTopic = {};
402
421
403
422
List <String > nameWordsForUser (User user) {
404
423
return _nameWordsByUser[user.userId] ?? = user.fullName.toLowerCase ().split (' ' );
@@ -407,6 +426,14 @@ class AutocompleteDataCache {
407
426
void invalidateUser (int userId) {
408
427
_nameWordsByUser.remove (userId);
409
428
}
429
+
430
+ List <String > nameWordsForTopic (Topic topic) {
431
+ return _nameWordsByTopic[topic.maxId] ?? = topic.name.toLowerCase ().split (' ' );
432
+ }
433
+
434
+ void invalidateTopic (int topicMaxId) {
435
+ _nameWordsByTopic.remove (topicMaxId);
436
+ }
410
437
}
411
438
412
439
class AutocompleteResult {}
@@ -422,3 +449,94 @@ class UserMentionAutocompleteResult extends MentionAutocompleteResult {
422
449
// TODO(#233): // class UserGroupMentionAutocompleteResult extends MentionAutocompleteResult {
423
450
424
451
// TODO(#234): // class WildcardMentionAutocompleteResult extends MentionAutocompleteResult {
452
+
453
+ class TopicAutocompleteDataProvider extends AutocompleteDataProvider <Topic , TopicAutocompleteQuery , TopicAutocompleteResult > {
454
+ final PerAccountStore store;
455
+ final StreamNarrow narrow;
456
+
457
+ TopicAutocompleteDataProvider ({required this .store, required this .topics, required this .narrow});
458
+
459
+ Iterable <Topic >? topics;
460
+
461
+ Future <void > fetch () async {
462
+ if (topics == null ) {
463
+ final result = await getStreamTopics (store.connection, streamId: narrow.streamId);
464
+ topics = result.topics;
465
+ store.autocompleteViewManager.handleTopicsFetchCompleted ();
466
+ }
467
+ }
468
+
469
+ @override
470
+ Iterable <Topic > getDataForQuery (TopicAutocompleteQuery query) {
471
+ return topics ?? [];
472
+ }
473
+
474
+ @override
475
+ TopicAutocompleteResult ? testItem (TopicAutocompleteQuery query, Topic item) {
476
+ if (query.testTopic (item, store.autocompleteViewManager.autocompleteDataCache)) {
477
+ return TopicAutocompleteResult (topic: item);
478
+ }
479
+ return null ;
480
+ }
481
+ }
482
+
483
+ class TopicAutocompleteView extends AutocompleteView <TopicAutocompleteQuery , TopicAutocompleteResult > {
484
+ TopicAutocompleteView .init ({
485
+ required super .store,
486
+ required StreamNarrow narrow,
487
+ }) : super (dataProvider: TopicAutocompleteDataProvider (
488
+ store: store,
489
+ topics: null ,
490
+ narrow: narrow
491
+ )..fetch ());
492
+ }
493
+
494
+ class TopicAutocompleteQuery extends AutocompleteQuery {
495
+ TopicAutocompleteQuery (super .raw)
496
+ : _lowercaseWords = raw.toLowerCase ().split (' ' );
497
+
498
+ final List <String > _lowercaseWords;
499
+
500
+ bool testTopic (Topic topic, AutocompleteDataCache cache) {
501
+ return _testName (topic, cache);
502
+ }
503
+
504
+ bool _testName (Topic topic, AutocompleteDataCache cache) {
505
+ final List <String > nameWords = cache.nameWordsForTopic (topic);
506
+
507
+ int nameWordsIndex = 0 ;
508
+ int queryWordsIndex = 0 ;
509
+ while (true ) {
510
+ if (queryWordsIndex == _lowercaseWords.length) {
511
+ return true ;
512
+ }
513
+ if (nameWordsIndex == nameWords.length) {
514
+ return false ;
515
+ }
516
+
517
+ if (nameWords[nameWordsIndex].startsWith (_lowercaseWords[queryWordsIndex])) {
518
+ queryWordsIndex++ ;
519
+ }
520
+ nameWordsIndex++ ;
521
+ }
522
+ }
523
+
524
+ @override
525
+ String toString () {
526
+ return '${objectRuntimeType (this , 'TopicAutocompleteQuery' )}(raw: $raw )' ;
527
+ }
528
+
529
+ @override
530
+ bool operator == (Object other) {
531
+ return other is TopicAutocompleteQuery && other.raw == raw;
532
+ }
533
+
534
+ @override
535
+ int get hashCode => Object .hash ('TopicAutocompleteQuery' , raw);
536
+ }
537
+
538
+ class TopicAutocompleteResult extends AutocompleteResult {
539
+ final Topic topic;
540
+
541
+ TopicAutocompleteResult ({required this .topic});
542
+ }
0 commit comments