@@ -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,15 @@ extension ComposeContentAutocomplete on ComposeContentController {
43
44
}
44
45
}
45
46
47
+ extension ComposeTopicAutocomplete on ComposeTopicController {
48
+ AutocompleteIntent <TopicAutocompleteQuery >? autocompleteIntent () {
49
+ return AutocompleteIntent (
50
+ syntaxStart: 0 ,
51
+ query: TopicAutocompleteQuery (value.text),
52
+ textEditingValue: value);
53
+ }
54
+ }
55
+
46
56
final RegExp mentionAutocompleteMarkerRegex = (() {
47
57
// What's likely to come before an @-mention: the start of the string,
48
58
// whitespace, or punctuation. Letters are unlikely; in that case an email
@@ -112,6 +122,7 @@ class AutocompleteIntent<QueryT extends AutocompleteQuery> {
112
122
/// On reassemble, call [reassemble] .
113
123
class AutocompleteViewManager {
114
124
final Set <MentionAutocompleteView > _mentionAutocompleteViews = {};
125
+ final Set <TopicAutocompleteView > _topicAutocompleteViews = {};
115
126
116
127
AutocompleteDataCache autocompleteDataCache = AutocompleteDataCache ();
117
128
@@ -125,6 +136,16 @@ class AutocompleteViewManager {
125
136
assert (removed);
126
137
}
127
138
139
+ void registerTopicAutocomplete (TopicAutocompleteView view) {
140
+ final added = _topicAutocompleteViews.add (view);
141
+ assert (added);
142
+ }
143
+
144
+ void unregisterTopicAutocomplete (TopicAutocompleteView view) {
145
+ final removed = _topicAutocompleteViews.remove (view);
146
+ assert (removed);
147
+ }
148
+
128
149
void handleRealmUserRemoveEvent (RealmUserRemoveEvent event) {
129
150
autocompleteDataCache.invalidateUser (event.userId);
130
151
}
@@ -135,12 +156,15 @@ class AutocompleteViewManager {
135
156
136
157
/// Called when the app is reassembled during debugging, e.g. for hot reload.
137
158
///
138
- /// Calls [MentionAutocompleteView .reassemble] for all that are registered.
159
+ /// Calls [AutocompleteView .reassemble] for all that are registered.
139
160
///
140
161
void reassemble () {
141
162
for (final view in _mentionAutocompleteViews) {
142
163
view.reassemble ();
143
164
}
165
+ for (final view in _topicAutocompleteViews) {
166
+ view.reassemble ();
167
+ }
144
168
}
145
169
146
170
// No `dispose` method, because there's nothing for it to do.
@@ -531,3 +555,78 @@ class UserMentionAutocompleteResult extends MentionAutocompleteResult {
531
555
// TODO(#233): // class UserGroupMentionAutocompleteResult extends MentionAutocompleteResult {
532
556
533
557
// TODO(#234): // class WildcardMentionAutocompleteResult extends MentionAutocompleteResult {
558
+
559
+ class TopicAutocompleteView extends AutocompleteView <TopicAutocompleteQuery , TopicAutocompleteResult , String > {
560
+ TopicAutocompleteView ._({required super .store, required this .streamId});
561
+
562
+ factory TopicAutocompleteView .init ({required PerAccountStore store, required int streamId}) {
563
+ final view = TopicAutocompleteView ._(store: store, streamId: streamId);
564
+ store.autocompleteViewManager.registerTopicAutocomplete (view);
565
+ view._fetch ();
566
+ return view;
567
+ }
568
+
569
+ final int streamId;
570
+ Iterable <String > _topics = [];
571
+ bool _isFetching = false ;
572
+
573
+ /// Fetches topics of the current stream narrow, expected to fetch
574
+ /// only once per lifecycle.
575
+ ///
576
+ /// Starts fetching once the stream narrow is active, then when results
577
+ /// are fetched it restarts search to refresh UI showing the newly
578
+ /// fetched topics.
579
+ Future <void > _fetch () async {
580
+ assert (! _isFetching);
581
+ _isFetching = true ;
582
+ final result = await getStreamTopics (store.connection, streamId: streamId);
583
+ _topics = result.topics.map ((e) => e.name);
584
+ _isFetching = false ;
585
+ if (_query != null ) _startSearch (_query! );
586
+ }
587
+
588
+ @override
589
+ Iterable <String > getSortedItemsToTest (TopicAutocompleteQuery query) => _topics;
590
+
591
+ @override
592
+ TopicAutocompleteResult ? testItem (TopicAutocompleteQuery query, String item) {
593
+ if (query.testTopic (item)) {
594
+ return TopicAutocompleteResult (topic: item);
595
+ }
596
+ return null ;
597
+ }
598
+
599
+ @override
600
+ void dispose () {
601
+ store.autocompleteViewManager.unregisterTopicAutocomplete (this );
602
+ super .dispose ();
603
+ }
604
+ }
605
+
606
+ class TopicAutocompleteQuery extends AutocompleteQuery {
607
+ TopicAutocompleteQuery (super .raw);
608
+
609
+ bool testTopic (String topic) {
610
+ // TODO(#881): Sort by match relevance, like web does.
611
+ return topic != raw && topic.toLowerCase ().contains (raw.toLowerCase ());
612
+ }
613
+
614
+ @override
615
+ String toString () {
616
+ return '${objectRuntimeType (this , 'TopicAutocompleteQuery' )}(raw: $raw )' ;
617
+ }
618
+
619
+ @override
620
+ bool operator == (Object other) {
621
+ return other is TopicAutocompleteQuery && other.raw == raw;
622
+ }
623
+
624
+ @override
625
+ int get hashCode => Object .hash ('TopicAutocompleteQuery' , raw);
626
+ }
627
+
628
+ class TopicAutocompleteResult extends AutocompleteResult {
629
+ final String topic;
630
+
631
+ TopicAutocompleteResult ({required this .topic});
632
+ }
0 commit comments