@@ -3,6 +3,7 @@ import 'package:flutter/services.dart';
33
44import '../api/model/events.dart' ;
55import '../api/model/model.dart' ;
6+ import '../api/route/channels.dart' ;
67import '../widgets/compose_box.dart' ;
78import 'narrow.dart' ;
89import 'store.dart' ;
@@ -43,6 +44,16 @@ extension ComposeContentAutocomplete on ComposeContentController {
4344 }
4445}
4546
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+
4657final RegExp mentionAutocompleteMarkerRegex = (() {
4758 // What's likely to come before an @-mention: the start of the string,
4859 // whitespace, or punctuation. Letters are unlikely; in that case an email
@@ -112,6 +123,7 @@ class AutocompleteIntent<QueryT extends AutocompleteQuery> {
112123/// On reassemble, call [reassemble] .
113124class AutocompleteViewManager {
114125 final Set <MentionAutocompleteView > _mentionAutocompleteViews = {};
126+ final Set <TopicAutocompleteView > _topicAutocompleteViews = {};
115127
116128 AutocompleteDataCache autocompleteDataCache = AutocompleteDataCache ();
117129
@@ -125,6 +137,16 @@ class AutocompleteViewManager {
125137 assert (removed);
126138 }
127139
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+
128150 void handleRealmUserRemoveEvent (RealmUserRemoveEvent event) {
129151 autocompleteDataCache.invalidateUser (event.userId);
130152 }
@@ -492,3 +514,78 @@ class UserMentionAutocompleteResult extends MentionAutocompleteResult {
492514// TODO(#233): // class UserGroupMentionAutocompleteResult extends MentionAutocompleteResult {
493515
494516// 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