@@ -3,6 +3,7 @@ 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' ;
7
8
import 'narrow.dart' ;
8
9
import 'store.dart' ;
@@ -43,6 +44,16 @@ extension ComposeContentAutocomplete on ComposeContentController {
43
44
}
44
45
}
45
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
+
46
57
final RegExp mentionAutocompleteMarkerRegex = (() {
47
58
// What's likely to come before an @-mention: the start of the string,
48
59
// whitespace, or punctuation. Letters are unlikely; in that case an email
@@ -150,6 +161,12 @@ class AutocompleteViewManager {
150
161
autocompleteDataCache.invalidateUser (event.userId);
151
162
}
152
163
164
+ void handleTopicsFetchCompleted () {
165
+ for (final view in _getViewsOfType <TopicAutocompleteView >()) {
166
+ view.reassemble ();
167
+ }
168
+ }
169
+
153
170
/// Called when the app is reassembled during debugging, e.g. for hot reload.
154
171
///
155
172
/// Calls [AutocompleteView.reassemble] for all that are registered.
@@ -418,6 +435,7 @@ class MentionAutocompleteQuery extends AutocompleteQuery {
418
435
419
436
class AutocompleteDataCache {
420
437
final Map <int , List <String >> _nameWordsByUser = {};
438
+ final Map <int , List <String >> _nameWordsByTopic = {};
421
439
422
440
List <String > nameWordsForUser (User user) {
423
441
return _nameWordsByUser[user.userId] ?? = user.fullName.toLowerCase ().split (' ' );
@@ -426,6 +444,14 @@ class AutocompleteDataCache {
426
444
void invalidateUser (int userId) {
427
445
_nameWordsByUser.remove (userId);
428
446
}
447
+
448
+ List <String > nameWordsForTopic (Topic topic) {
449
+ return _nameWordsByTopic[topic.maxId] ?? = topic.name.toLowerCase ().split (' ' );
450
+ }
451
+
452
+ void invalidateTopic (int topicMaxId) {
453
+ _nameWordsByTopic.remove (topicMaxId);
454
+ }
429
455
}
430
456
431
457
class AutocompleteResult {}
@@ -441,3 +467,77 @@ class UserMentionAutocompleteResult extends MentionAutocompleteResult {
441
467
// TODO(#233): // class UserGroupMentionAutocompleteResult extends MentionAutocompleteResult {
442
468
443
469
// TODO(#234): // class WildcardMentionAutocompleteResult extends MentionAutocompleteResult {
470
+
471
+ class TopicAutocompleteDataProvider extends AutocompleteDataProvider <Topic , TopicAutocompleteQuery , TopicAutocompleteResult > {
472
+ final PerAccountStore store;
473
+ final int streamId;
474
+ Iterable <Topic >? topics;
475
+ bool isFetching = false ;
476
+
477
+ TopicAutocompleteDataProvider ({required this .store, required this .streamId});
478
+
479
+ /// Fetches topics of the current stream narrow, expected to fetch
480
+ /// only once per lifecycle.
481
+ ///
482
+ /// Starts fetching once the stream narrow is active, then when results
483
+ /// are fetched it notifies `autocompleteViewManager` to refresh UI
484
+ /// showing the newly fetched topics.
485
+ Future <void > fetch () async {
486
+ if (topics != null && ! isFetching) return ;
487
+ isFetching = true ;
488
+ final result = await getStreamTopics (store.connection, streamId: streamId);
489
+ topics = result.topics;
490
+ store.autocompleteViewManager.handleTopicsFetchCompleted ();
491
+ isFetching = false ;
492
+ }
493
+
494
+ @override
495
+ Iterable <Topic > getDataForQuery (TopicAutocompleteQuery query) {
496
+ return topics ?? [];
497
+ }
498
+
499
+ @override
500
+ TopicAutocompleteResult ? testItem (TopicAutocompleteQuery query, Topic item) {
501
+ if (query.testTopic (item, store.autocompleteViewManager.autocompleteDataCache)) {
502
+ return TopicAutocompleteResult (topic: item);
503
+ }
504
+ return null ;
505
+ }
506
+ }
507
+
508
+ class TopicAutocompleteView extends AutocompleteView <TopicAutocompleteQuery , TopicAutocompleteResult > {
509
+ TopicAutocompleteView .init ({
510
+ required super .store,
511
+ required int streamId,
512
+ }) : super (dataProvider: TopicAutocompleteDataProvider (
513
+ store: store,
514
+ streamId: streamId
515
+ )..fetch ());
516
+ }
517
+
518
+ class TopicAutocompleteQuery extends AutocompleteQuery {
519
+ TopicAutocompleteQuery (super .raw);
520
+
521
+ bool testTopic (Topic topic, AutocompleteDataCache cache) {
522
+ return _testContainsQueryWords (cache.nameWordsForTopic (topic));
523
+ }
524
+
525
+ @override
526
+ String toString () {
527
+ return '${objectRuntimeType (this , 'TopicAutocompleteQuery' )}(raw: $raw )' ;
528
+ }
529
+
530
+ @override
531
+ bool operator == (Object other) {
532
+ return other is TopicAutocompleteQuery && other.raw == raw;
533
+ }
534
+
535
+ @override
536
+ int get hashCode => Object .hash ('TopicAutocompleteQuery' , raw);
537
+ }
538
+
539
+ class TopicAutocompleteResult extends AutocompleteResult {
540
+ final Topic topic;
541
+
542
+ TopicAutocompleteResult ({required this .topic});
543
+ }
0 commit comments