@@ -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/channels.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
@@ -112,6 +123,7 @@ class AutocompleteIntent<QueryT extends AutocompleteQuery> {
112
123
/// On reassemble, call [reassemble] .
113
124
class AutocompleteViewManager {
114
125
final Set <MentionAutocompleteView > _mentionAutocompleteViews = {};
126
+ final Set <TopicAutocompleteView > _topicAutocompleteViews = {};
115
127
116
128
AutocompleteDataCache autocompleteDataCache = AutocompleteDataCache ();
117
129
@@ -125,6 +137,16 @@ class AutocompleteViewManager {
125
137
assert (removed);
126
138
}
127
139
140
+ void registerTopicAutocomplete (TopicAutocompleteView view) {
141
+ final added = _topicAutocompleteViews.add (view);
142
+ assert (added);
143
+ }
144
+
145
+ void unregisterTopicAutocomplete (TopicAutocompleteView view) {
146
+ final removed = _topicAutocompleteViews.remove (view);
147
+ assert (removed);
148
+ }
149
+
128
150
void handleRealmUserRemoveEvent (RealmUserRemoveEvent event) {
129
151
autocompleteDataCache.invalidateUser (event.userId);
130
152
}
@@ -492,3 +514,78 @@ class UserMentionAutocompleteResult extends MentionAutocompleteResult {
492
514
// TODO(#233): // class UserGroupMentionAutocompleteResult extends MentionAutocompleteResult {
493
515
494
516
// TODO(#234): // class WildcardMentionAutocompleteResult extends MentionAutocompleteResult {
517
+
518
+ class TopicAutocompleteView extends AutocompleteView <TopicAutocompleteQuery , TopicAutocompleteResult , String > {
519
+ TopicAutocompleteView ._({required super .store, required this .streamId});
520
+
521
+ factory TopicAutocompleteView .init ({required PerAccountStore store, required int streamId}) {
522
+ final view = TopicAutocompleteView ._(store: store, streamId: streamId);
523
+ store.autocompleteViewManager.registerTopicAutocomplete (view);
524
+ view._fetch ();
525
+ return view;
526
+ }
527
+
528
+ final int streamId;
529
+ Iterable <String > _topics = [];
530
+ bool _isFetching = false ;
531
+
532
+
533
+ /// Fetches topics of the current stream narrow, expected to fetch
534
+ /// only once per lifecycle.
535
+ ///
536
+ /// Starts fetching once the stream narrow is active, then when results
537
+ /// are fetched it restarts search to refresh UI showing the newly
538
+ /// fetched topics.
539
+ Future <void > _fetch () async {
540
+ if (_isFetching) return ;
541
+ _isFetching = true ;
542
+ final result = await getStreamTopics (store.connection, streamId: streamId);
543
+ _topics = result.topics.map ((e) => e.name);
544
+ _isFetching = false ;
545
+ if (_query != null ) _startSearch (_query! );
546
+ }
547
+
548
+ @override
549
+ Iterable <String > getSortedItemsToTest (TopicAutocompleteQuery query) => _topics;
550
+
551
+ @override
552
+ TopicAutocompleteResult ? testItem (TopicAutocompleteQuery query, String item) {
553
+ if (query.testTopic (item)) {
554
+ return TopicAutocompleteResult (topic: item);
555
+ }
556
+ return null ;
557
+ }
558
+
559
+ @override
560
+ void dispose () {
561
+ store.autocompleteViewManager.unregisterTopicAutocomplete (this );
562
+ super .dispose ();
563
+ }
564
+ }
565
+
566
+ class TopicAutocompleteQuery extends AutocompleteQuery {
567
+ TopicAutocompleteQuery (super .raw);
568
+
569
+ bool testTopic (String topic) => topic.isNotEmpty
570
+ && topic != raw
571
+ && topic.toLowerCase ().contains (raw.toLowerCase ());
572
+
573
+ @override
574
+ String toString () {
575
+ return '${objectRuntimeType (this , 'TopicAutocompleteQuery' )}(raw: $raw )' ;
576
+ }
577
+
578
+ @override
579
+ bool operator == (Object other) {
580
+ return other is TopicAutocompleteQuery && other.raw == raw;
581
+ }
582
+
583
+ @override
584
+ int get hashCode => Object .hash ('TopicAutocompleteQuery' , raw);
585
+ }
586
+
587
+ class TopicAutocompleteResult extends AutocompleteResult {
588
+ final String topic;
589
+
590
+ TopicAutocompleteResult ({required this .topic});
591
+ }
0 commit comments