11import 'package:flutter/material.dart' ;
22
33import '../generated/l10n/zulip_localizations.dart' ;
4+ import '../model/content.dart' ;
45import '../model/emoji.dart' ;
56import '../model/store.dart' ;
7+ import 'content.dart' ;
68import 'emoji.dart' ;
79import 'icons.dart' ;
810import 'store.dart' ;
@@ -45,8 +47,7 @@ class _AutocompleteFieldState<QueryT extends AutocompleteQuery, ResultT extends
4547 }
4648
4749 void _handleControllerChange () {
48- var newQuery = widget.autocompleteIntent ()? .query;
49- if (newQuery is ChannelLinkAutocompleteQuery ) newQuery = null ; // TODO(#124)
50+ final newQuery = widget.autocompleteIntent ()? .query;
5051 // First, tear down the old view-model if necessary.
5152 if (_viewModel != null
5253 && (newQuery == null
@@ -227,8 +228,17 @@ class ComposeAutocomplete extends AutocompleteField<ComposeAutocompleteQuery, Co
227228 // TODO(#1805) language-appropriate space character; check active keyboard?
228229 // (maybe handle centrally in `controller`)
229230 replacementString = '${userGroupMention (userGroup .name , silent : query .silent )} ' ;
230- case ChannelLinkAutocompleteResult ():
231- throw UnimplementedError (); // TODO(#124)
231+ case ChannelLinkAutocompleteResult (: final channelId):
232+ if (query is ! ChannelLinkAutocompleteQuery ) {
233+ return ; // Shrug; similar to `intent == null` case above.
234+ }
235+ final channel = store.streams[channelId];
236+ if (channel == null ) {
237+ // Don't crash on theoretical race between async results-filtering
238+ // and losing data for the channel.
239+ return ;
240+ }
241+ replacementString = '${channelLinkSyntax (channel , store : store )} ' ;
232242 }
233243
234244 controller.value = intent.textEditingValue.replaced (
@@ -246,7 +256,7 @@ class ComposeAutocomplete extends AutocompleteField<ComposeAutocompleteQuery, Co
246256 final child = switch (option) {
247257 MentionAutocompleteResult () => MentionAutocompleteItem (
248258 option: option, narrow: narrow),
249- ChannelLinkAutocompleteResult () => throw UnimplementedError (), // TODO(#124)
259+ ChannelLinkAutocompleteResult () => _ChannelLinkAutocompleteItem (option : option),
250260 EmojiAutocompleteResult () => _EmojiAutocompleteItem (option: option),
251261 };
252262 return InkWell (
@@ -415,6 +425,69 @@ class _EmojiAutocompleteItem extends StatelessWidget {
415425 }
416426}
417427
428+ class _ChannelLinkAutocompleteItem extends StatelessWidget {
429+ const _ChannelLinkAutocompleteItem ({required this .option});
430+
431+ final ChannelLinkAutocompleteResult option;
432+
433+ @override
434+ Widget build (BuildContext context) {
435+ final store = PerAccountStoreWidget .of (context);
436+ final zulipLocalizations = ZulipLocalizations .of (context);
437+ final designVariables = DesignVariables .of (context);
438+
439+ final channel = store.streams[option.channelId];
440+
441+ // A null [Icon.icon] makes a blank space.
442+ IconData ? icon;
443+ Color ? iconColor;
444+ String label;
445+ String ? subLabel;
446+ if (channel != null ) {
447+ icon = iconDataForStream (channel);
448+ iconColor = colorSwatchFor (context, store.subscriptions[channel.streamId])
449+ .iconOnPlainBackground;
450+ label = channel.name;
451+ subLabel = channel.renderedDescription.isNotEmpty
452+ ? channel.renderedDescription : null ;
453+ } else {
454+ icon = null ;
455+ iconColor = null ;
456+ label = zulipLocalizations.unknownChannelName;
457+ subLabel = null ;
458+ }
459+
460+ final labelWidget = Text (label,
461+ overflow: TextOverflow .ellipsis,
462+ style: TextStyle (
463+ fontSize: 18 , height: 20 / 18 ,
464+ color: designVariables.contextMenuItemLabel,
465+ ).merge (weightVariableTextStyle (context, wght: 600 )));
466+
467+ final subLabelWidget = subLabel == null ? null
468+ // Adapted from [MessageContent].
469+ : DefaultTextStyle (
470+ style: ContentTheme .of (context).textStylePlainParagraph.merge (
471+ TextStyle (fontSize: 14 , height: 16 / 14 ,
472+ overflow: TextOverflow .ellipsis,
473+ color: designVariables.contextMenuItemMeta)),
474+ child: BlockContentList (
475+ nodes: parseContent (channel! .renderedDescription).nodes),
476+ );
477+
478+ return Padding (
479+ padding: EdgeInsetsGeometry .fromSTEB (12 , 4 , 10 , 4 ),
480+ child: Row (spacing: 10 , children: [
481+ SizedBox .square (dimension: 24 ,
482+ child: Icon (size: 18 , color: iconColor, icon)),
483+ Expanded (child: Column (
484+ crossAxisAlignment: CrossAxisAlignment .start,
485+ children: [labelWidget, ? subLabelWidget])),
486+ ]),
487+ );
488+ }
489+ }
490+
418491class TopicAutocomplete extends AutocompleteField <TopicAutocompleteQuery , TopicAutocompleteResult > {
419492 const TopicAutocomplete ({
420493 super .key,
0 commit comments