@@ -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.
@@ -392,6 +410,7 @@ class MentionAutocompleteQuery extends AutocompleteQuery {
392
410
393
411
class AutocompleteDataCache {
394
412
final Map <int , List <String >> _nameWordsByUser = {};
413
+ final Map <int , List <String >> _nameWordsByTopic = {};
395
414
396
415
List <String > nameWordsForUser (User user) {
397
416
return _nameWordsByUser[user.userId] ?? = user.fullName.toLowerCase ().split (' ' );
@@ -400,6 +419,14 @@ class AutocompleteDataCache {
400
419
void invalidateUser (int userId) {
401
420
_nameWordsByUser.remove (userId);
402
421
}
422
+
423
+ List <String > nameWordsForTopic (Topic topic) {
424
+ return _nameWordsByTopic[topic.maxId] ?? = topic.name.toLowerCase ().split (' ' );
425
+ }
426
+
427
+ void invalidateTopic (int topicMaxId) {
428
+ _nameWordsByTopic.remove (topicMaxId);
429
+ }
403
430
}
404
431
405
432
class AutocompleteResult {}
@@ -415,3 +442,94 @@ class UserMentionAutocompleteResult extends MentionAutocompleteResult {
415
442
// TODO(#233): // class UserGroupMentionAutocompleteResult extends MentionAutocompleteResult {
416
443
417
444
// TODO(#234): // class WildcardMentionAutocompleteResult extends MentionAutocompleteResult {
445
+
446
+ class TopicAutocompleteDataProvider extends AutocompleteDataProvider <Topic , TopicAutocompleteQuery , TopicAutocompleteResult > {
447
+ final PerAccountStore store;
448
+ final StreamNarrow narrow;
449
+
450
+ TopicAutocompleteDataProvider ({required this .store, required this .topics, required this .narrow});
451
+
452
+ Iterable <Topic >? topics;
453
+
454
+ Future <void > fetch () async {
455
+ if (topics == null ) {
456
+ final result = await getStreamTopics (store.connection, streamId: narrow.streamId);
457
+ topics = result.topics;
458
+ store.autocompleteViewManager.handleTopicsFetchCompleted ();
459
+ }
460
+ }
461
+
462
+ @override
463
+ Iterable <Topic > getDataForQuery (TopicAutocompleteQuery query) {
464
+ return topics ?? [];
465
+ }
466
+
467
+ @override
468
+ TopicAutocompleteResult ? testItem (TopicAutocompleteQuery query, Topic item) {
469
+ if (query.testTopic (item, store.autocompleteViewManager.autocompleteDataCache)) {
470
+ return TopicAutocompleteResult (topic: item);
471
+ }
472
+ return null ;
473
+ }
474
+ }
475
+
476
+ class TopicAutocompleteView extends AutocompleteView <TopicAutocompleteQuery , TopicAutocompleteResult > {
477
+ TopicAutocompleteView .init ({
478
+ required super .store,
479
+ required StreamNarrow narrow,
480
+ }) : super (dataProvider: TopicAutocompleteDataProvider (
481
+ store: store,
482
+ topics: null ,
483
+ narrow: narrow
484
+ )..fetch ());
485
+ }
486
+
487
+ class TopicAutocompleteQuery extends AutocompleteQuery {
488
+ TopicAutocompleteQuery (super .raw)
489
+ : _lowercaseWords = raw.toLowerCase ().split (' ' );
490
+
491
+ final List <String > _lowercaseWords;
492
+
493
+ bool testTopic (Topic topic, AutocompleteDataCache cache) {
494
+ return _testName (topic, cache);
495
+ }
496
+
497
+ bool _testName (Topic topic, AutocompleteDataCache cache) {
498
+ final List <String > nameWords = cache.nameWordsForTopic (topic);
499
+
500
+ int nameWordsIndex = 0 ;
501
+ int queryWordsIndex = 0 ;
502
+ while (true ) {
503
+ if (queryWordsIndex == _lowercaseWords.length) {
504
+ return true ;
505
+ }
506
+ if (nameWordsIndex == nameWords.length) {
507
+ return false ;
508
+ }
509
+
510
+ if (nameWords[nameWordsIndex].startsWith (_lowercaseWords[queryWordsIndex])) {
511
+ queryWordsIndex++ ;
512
+ }
513
+ nameWordsIndex++ ;
514
+ }
515
+ }
516
+
517
+ @override
518
+ String toString () {
519
+ return '${objectRuntimeType (this , 'TopicAutocompleteQuery' )}(raw: $raw )' ;
520
+ }
521
+
522
+ @override
523
+ bool operator == (Object other) {
524
+ return other is TopicAutocompleteQuery && other.raw == raw;
525
+ }
526
+
527
+ @override
528
+ int get hashCode => Object .hash ('TopicAutocompleteQuery' , raw);
529
+ }
530
+
531
+ class TopicAutocompleteResult extends AutocompleteResult {
532
+ final Topic topic;
533
+
534
+ TopicAutocompleteResult ({required this .topic});
535
+ }
0 commit comments