From 5805f4544534ccb3200e50625f1dd79cd9881b1f Mon Sep 17 00:00:00 2001 From: Greg Spencer Date: Mon, 8 Aug 2022 16:22:04 -0700 Subject: [PATCH] Fix autocomplete selections (#109185) --- .../flutter/lib/src/widgets/autocomplete.dart | 51 ++++++++------ .../test/widgets/autocomplete_test.dart | 70 +++++++++++++++++++ 2 files changed, 98 insertions(+), 23 deletions(-) diff --git a/packages/flutter/lib/src/widgets/autocomplete.dart b/packages/flutter/lib/src/widgets/autocomplete.dart index 197e657e60..d5a3bb5af2 100644 --- a/packages/flutter/lib/src/widgets/autocomplete.dart +++ b/packages/flutter/lib/src/widgets/autocomplete.dart @@ -16,6 +16,7 @@ import 'framework.dart'; import 'inherited_notifier.dart'; import 'overlay.dart'; import 'shortcuts.dart'; +import 'tap_region.dart'; /// The type of the [RawAutocomplete] callback which computes the list of /// optional completions for the widget's field, based on the text the user has @@ -421,13 +422,15 @@ class _RawAutocompleteState extends State> link: _optionsLayerLink, showWhenUnlinked: false, targetAnchor: Alignment.bottomLeft, - child: AutocompleteHighlightedOption( - highlightIndexNotifier: _highlightedOptionIndex, - child: Builder( - builder: (BuildContext context) { - return widget.optionsViewBuilder(context, _select, _options); - } - ) + child: TextFieldTapRegion( + child: AutocompleteHighlightedOption( + highlightIndexNotifier: _highlightedOptionIndex, + child: Builder( + builder: (BuildContext context) { + return widget.optionsViewBuilder(context, _select, _options); + } + ) + ), ), ); }, @@ -527,22 +530,24 @@ class _RawAutocompleteState extends State> @override Widget build(BuildContext context) { - return Container( - key: _fieldKey, - child: Shortcuts( - shortcuts: _shortcuts, - child: Actions( - actions: _actionMap, - child: CompositedTransformTarget( - link: _optionsLayerLink, - child: widget.fieldViewBuilder == null - ? const SizedBox.shrink() - : widget.fieldViewBuilder!( - context, - _textEditingController, - _focusNode, - _onFieldSubmitted, - ), + return TextFieldTapRegion( + child: Container( + key: _fieldKey, + child: Shortcuts( + shortcuts: _shortcuts, + child: Actions( + actions: _actionMap, + child: CompositedTransformTarget( + link: _optionsLayerLink, + child: widget.fieldViewBuilder == null + ? const SizedBox.shrink() + : widget.fieldViewBuilder!( + context, + _textEditingController, + _focusNode, + _onFieldSubmitted, + ), + ), ), ), ), diff --git a/packages/flutter/test/widgets/autocomplete_test.dart b/packages/flutter/test/widgets/autocomplete_test.dart index 66c5e1188f..37c6f89337 100644 --- a/packages/flutter/test/widgets/autocomplete_test.dart +++ b/packages/flutter/test/widgets/autocomplete_test.dart @@ -129,6 +129,76 @@ void main() { expect(lastOptions.elementAt(5), 'northern white rhinoceros'); }); + testWidgets('tapping on an option selects it', (WidgetTester tester) async { + final GlobalKey fieldKey = GlobalKey(); + final GlobalKey optionsKey = GlobalKey(); + late Iterable lastOptions; + late FocusNode focusNode; + late TextEditingController textEditingController; + + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: RawAutocomplete( + optionsBuilder: (TextEditingValue textEditingValue) { + return kOptions.where((String option) { + return option.contains(textEditingValue.text.toLowerCase()); + }); + }, + fieldViewBuilder: (BuildContext context, TextEditingController fieldTextEditingController, FocusNode fieldFocusNode, VoidCallback onFieldSubmitted) { + focusNode = fieldFocusNode; + textEditingController = fieldTextEditingController; + return TextField( + key: fieldKey, + focusNode: focusNode, + controller: textEditingController, + ); + }, + optionsViewBuilder: (BuildContext context, AutocompleteOnSelected onSelected, Iterable options) { + lastOptions = options; + return Material( + elevation: 4.0, + child: ListView.builder( + key: optionsKey, + padding: const EdgeInsets.all(8.0), + itemCount: options.length, + itemBuilder: (BuildContext context, int index) { + final String option = options.elementAt(index); + return GestureDetector( + onTap: () { + onSelected(option); + }, + child: ListTile( + title: Text(option), + ), + ); + }, + ), + ); + }, + ), + ), + ), + ); + + // The field is always rendered, but the options are not unless needed. + expect(find.byKey(fieldKey), findsOneWidget); + expect(find.byKey(optionsKey), findsNothing); + + // Tap on the text field to open the options. + await tester.tap(find.byKey(fieldKey)); + await tester.pump(); + expect(find.byKey(optionsKey), findsOneWidget); + expect(lastOptions.length, kOptions.length); + + await tester.tap(find.text(kOptions[2])); + await tester.pump(); + + expect(find.byKey(optionsKey), findsNothing); + + expect(textEditingController.text, equals(kOptions[2])); + }); + testWidgets('can filter and select a list of custom User options', (WidgetTester tester) async { final GlobalKey fieldKey = GlobalKey(); final GlobalKey optionsKey = GlobalKey();