Text selection menu show/hide cases (#35219)

Show and hide the text selection menu at the correct time with various gestures in the text field.
diff --git a/packages/flutter/lib/src/material/text_field.dart b/packages/flutter/lib/src/material/text_field.dart
index cce50a1..8b98aff 100644
--- a/packages/flutter/lib/src/material/text_field.dart
+++ b/packages/flutter/lib/src/material/text_field.dart
@@ -762,6 +762,7 @@
   }
 
   void _handleSingleTapUp(TapUpDetails details) {
+    _editableTextKey.currentState.hideToolbar();
     if (widget.selectionEnabled) {
       switch (Theme.of(context).platform) {
         case TargetPlatform.iOS:
diff --git a/packages/flutter/lib/src/widgets/editable_text.dart b/packages/flutter/lib/src/widgets/editable_text.dart
index 36809a8..6cb99d4 100644
--- a/packages/flutter/lib/src/widgets/editable_text.dart
+++ b/packages/flutter/lib/src/widgets/editable_text.dart
@@ -1447,7 +1447,7 @@
       _showCaretOnScreen();
       if (!_value.selection.isValid) {
         // Place cursor at the end if the selection is invalid when we receive focus.
-        widget.controller.selection = TextSelection.collapsed(offset: _value.text.length);
+        _handleSelectionChanged(TextSelection.collapsed(offset: _value.text.length), renderEditable, null);
       }
     } else {
       WidgetsBinding.instance.removeObserver(this);
@@ -1500,6 +1500,9 @@
 
   @override
   void hideToolbar() {
+    if (_selectionOverlay == null || !_selectionOverlay.toolbarIsVisible) {
+      return;
+    }
     _selectionOverlay?.hide();
   }
 
diff --git a/packages/flutter/test/cupertino/text_field_test.dart b/packages/flutter/test/cupertino/text_field_test.dart
index d0c2cc1..a4ebbd1 100644
--- a/packages/flutter/test/cupertino/text_field_test.dart
+++ b/packages/flutter/test/cupertino/text_field_test.dart
@@ -3650,5 +3650,31 @@
         expect(tester.getTopLeft(find.byType(EditableText)).dy, closeTo(329.0, .0001));
       });
     });
+
+    testWidgets(
+      'Long press on an autofocused field shows the selection menu',
+      (WidgetTester tester) async {
+        await tester.pumpWidget(
+          CupertinoApp(
+            home: Center(
+              child: ConstrainedBox(
+                constraints: BoxConstraints.loose(const Size(200, 200)),
+                child: const CupertinoTextField(
+                  autofocus: true,
+                ),
+              ),
+            ),
+          ),
+        );
+        // This extra pump allows the selection set by autofocus to propagate to
+        // the RenderEditable.
+        await tester.pump();
+
+        // Long press shows the selection menu.
+        await tester.longPressAt(textOffsetToPosition(tester, 0));
+        await tester.pump();
+        expect(find.text('Paste'), findsOneWidget);
+      },
+    );
   });
 }
diff --git a/packages/flutter/test/material/text_field_test.dart b/packages/flutter/test/material/text_field_test.dart
index 0605c0a..a432de8 100644
--- a/packages/flutter/test/material/text_field_test.dart
+++ b/packages/flutter/test/material/text_field_test.dart
@@ -92,19 +92,21 @@
 }
 
 Widget boilerplate({ Widget child }) {
-  return Localizations(
-    locale: const Locale('en', 'US'),
-    delegates: <LocalizationsDelegate<dynamic>>[
-      WidgetsLocalizationsDelegate(),
-      MaterialLocalizationsDelegate(),
-    ],
-    child: Directionality(
-      textDirection: TextDirection.ltr,
-      child: MediaQuery(
-        data: const MediaQueryData(size: Size(800.0, 600.0)),
-        child: Center(
-          child: Material(
-            child: child,
+  return MaterialApp(
+    home: Localizations(
+      locale: const Locale('en', 'US'),
+      delegates: <LocalizationsDelegate<dynamic>>[
+        WidgetsLocalizationsDelegate(),
+        MaterialLocalizationsDelegate(),
+      ],
+      child: Directionality(
+        textDirection: TextDirection.ltr,
+        child: MediaQuery(
+          data: const MediaQueryData(size: Size(800.0, 600.0)),
+          child: Center(
+            child: Material(
+              child: child,
+            ),
           ),
         ),
       ),
@@ -5342,6 +5344,66 @@
   );
 
   testWidgets(
+    'A single tap hides the selection menu',
+    (WidgetTester tester) async {
+      final TextEditingController controller = TextEditingController(
+        text: '',
+      );
+      await tester.pumpWidget(
+        MaterialApp(
+          home: Material(
+            child: Center(
+              child: TextField(
+                controller: controller,
+              ),
+            ),
+          ),
+        ),
+      );
+
+      // Long press shows the selection menu.
+      await tester.longPress(find.byType(TextField));
+      await tester.pump();
+      expect(find.text('PASTE'), findsOneWidget);
+
+      // Tap hides the selection menu.
+      await tester.tap(find.byType(TextField));
+      await tester.pump();
+      expect(find.text('PASTE'), findsNothing);
+    },
+  );
+
+  testWidgets(
+    'Long press on an autofocused field shows the selection menu',
+    (WidgetTester tester) async {
+      final TextEditingController controller = TextEditingController(
+        text: '',
+      );
+      await tester.pumpWidget(
+        MaterialApp(
+          home: Material(
+            child: Center(
+              child: TextField(
+                autofocus: true,
+                controller: controller,
+              ),
+            ),
+          ),
+        ),
+      );
+      // This extra pump allows the selection set by autofocus to propagate to
+      // the RenderEditable.
+      await tester.pump();
+
+      // Long press shows the selection menu.
+      expect(find.text('PASTE'), findsNothing);
+      await tester.longPress(find.byType(TextField));
+      await tester.pump();
+      expect(find.text('PASTE'), findsOneWidget);
+    },
+  );
+
+  testWidgets(
     'double tap hold selects word (iOS)',
     (WidgetTester tester) async {
       final TextEditingController controller = TextEditingController(
diff --git a/packages/flutter/test/widgets/editable_text_show_on_screen_test.dart b/packages/flutter/test/widgets/editable_text_show_on_screen_test.dart
index b097721..e778c75 100644
--- a/packages/flutter/test/widgets/editable_text_show_on_screen_test.dart
+++ b/packages/flutter/test/widgets/editable_text_show_on_screen_test.dart
@@ -202,27 +202,29 @@
     final PageController pageController = PageController(initialPage: 1);
 
     await tester.pumpWidget(
-      MediaQuery(
-        data: const MediaQueryData(devicePixelRatio: 1.0),
-        child: Directionality(
-          textDirection: TextDirection.ltr,
-          child: Material(
-            child: PageView(
-              controller: pageController,
-              children: <Widget>[
-                Container(
-                  color: Colors.red,
-                ),
-                Container(
-                  child: TextField(
-                    controller: textController,
+      MaterialApp(
+        home: MediaQuery(
+          data: const MediaQueryData(devicePixelRatio: 1.0),
+          child: Directionality(
+            textDirection: TextDirection.ltr,
+            child: Material(
+              child: PageView(
+                controller: pageController,
+                children: <Widget>[
+                  Container(
+                    color: Colors.red,
                   ),
-                  color: Colors.green,
-                ),
-                Container(
-                  color: Colors.red,
-                ),
-              ],
+                  Container(
+                    child: TextField(
+                      controller: textController,
+                    ),
+                    color: Colors.green,
+                  ),
+                  Container(
+                    color: Colors.red,
+                  ),
+                ],
+              ),
             ),
           ),
         ),
diff --git a/packages/flutter/test/widgets/editable_text_test.dart b/packages/flutter/test/widgets/editable_text_test.dart
index 0f5dce9..8b66e89 100644
--- a/packages/flutter/test/widgets/editable_text_test.dart
+++ b/packages/flutter/test/widgets/editable_text_test.dart
@@ -809,23 +809,25 @@
       return StatefulBuilder(
         builder: (BuildContext context, StateSetter setter) {
           setState = setter;
-          return MediaQuery(
-            data: const MediaQueryData(devicePixelRatio: 1.0),
-            child: Directionality(
-              textDirection: TextDirection.ltr,
-              child: Center(
-                child: Material(
-                  child: EditableText(
-                    backgroundCursorColor: Colors.grey,
-                    controller: currentController,
-                    focusNode: focusNode,
-                    style: Typography(platform: TargetPlatform.android)
-                        .black
-                        .subhead,
-                    cursorColor: Colors.blue,
-                    selectionControls: materialTextSelectionControls,
-                    keyboardType: TextInputType.text,
-                    onChanged: (String value) { },
+          return MaterialApp(
+            home: MediaQuery(
+              data: const MediaQueryData(devicePixelRatio: 1.0),
+              child: Directionality(
+                textDirection: TextDirection.ltr,
+                child: Center(
+                  child: Material(
+                    child: EditableText(
+                      backgroundCursorColor: Colors.grey,
+                      controller: currentController,
+                      focusNode: focusNode,
+                      style: Typography(platform: TargetPlatform.android)
+                          .black
+                          .subhead,
+                      cursorColor: Colors.blue,
+                      selectionControls: materialTextSelectionControls,
+                      keyboardType: TextInputType.text,
+                      onChanged: (String value) { },
+                    ),
                   ),
                 ),
               ),
diff --git a/packages/flutter/test/widgets/form_test.dart b/packages/flutter/test/widgets/form_test.dart
index 73cf030..33b2e7b 100644
--- a/packages/flutter/test/widgets/form_test.dart
+++ b/packages/flutter/test/widgets/form_test.dart
@@ -11,16 +11,18 @@
     String fieldValue;
 
     Widget builder() {
-      return MediaQuery(
-        data: const MediaQueryData(devicePixelRatio: 1.0),
-        child: Directionality(
-          textDirection: TextDirection.ltr,
-          child: Center(
-            child: Material(
-              child: Form(
-                key: formKey,
-                child: TextFormField(
-                  onSaved: (String value) { fieldValue = value; },
+      return MaterialApp(
+        home: MediaQuery(
+          data: const MediaQueryData(devicePixelRatio: 1.0),
+          child: Directionality(
+            textDirection: TextDirection.ltr,
+            child: Center(
+              child: Material(
+                child: Form(
+                  key: formKey,
+                  child: TextFormField(
+                    onSaved: (String value) { fieldValue = value; },
+                  ),
                 ),
               ),
             ),
@@ -36,7 +38,7 @@
     Future<void> checkText(String testValue) async {
       await tester.enterText(find.byType(TextFormField), testValue);
       formKey.currentState.save();
-      // pump'ing is unnecessary because callback happens regardless of frames
+      // Pumping is unnecessary because callback happens regardless of frames.
       expect(fieldValue, equals(testValue));
     }
 
@@ -48,15 +50,17 @@
     String fieldValue;
 
     Widget builder() {
-      return MediaQuery(
-        data: const MediaQueryData(devicePixelRatio: 1.0),
-        child: Directionality(
-          textDirection: TextDirection.ltr,
-          child: Center(
-            child: Material(
-              child: Form(
-                child: TextField(
-                  onChanged: (String value) { fieldValue = value; },
+      return MaterialApp(
+        home: MediaQuery(
+          data: const MediaQueryData(devicePixelRatio: 1.0),
+          child: Directionality(
+            textDirection: TextDirection.ltr,
+            child: Center(
+              child: Material(
+                child: Form(
+                  child: TextField(
+                    onChanged: (String value) { fieldValue = value; },
+                  ),
                 ),
               ),
             ),
@@ -84,17 +88,19 @@
     String errorText(String value) => value + '/error';
 
     Widget builder(bool autovalidate) {
-      return MediaQuery(
-        data: const MediaQueryData(devicePixelRatio: 1.0),
-        child: Directionality(
-          textDirection: TextDirection.ltr,
-          child: Center(
-            child: Material(
-              child: Form(
-                key: formKey,
-                autovalidate: autovalidate,
-                child: TextFormField(
-                  validator: errorText,
+      return MaterialApp(
+        home: MediaQuery(
+          data: const MediaQueryData(devicePixelRatio: 1.0),
+          child: Directionality(
+            textDirection: TextDirection.ltr,
+            child: Center(
+              child: Material(
+                child: Form(
+                  key: formKey,
+                  autovalidate: autovalidate,
+                  child: TextFormField(
+                    validator: errorText,
+                  ),
                 ),
               ),
             ),
@@ -138,24 +144,26 @@
     String errorText(String input) => '${fieldKey.currentState.value}/error';
 
     Widget builder() {
-      return MediaQuery(
-        data: const MediaQueryData(devicePixelRatio: 1.0),
-        child: Directionality(
-          textDirection: TextDirection.ltr,
-          child: Center(
-            child: Material(
-              child: Form(
-                key: formKey,
-                autovalidate: true,
-                child: ListView(
-                  children: <Widget>[
-                    TextFormField(
-                      key: fieldKey,
-                    ),
-                    TextFormField(
-                      validator: errorText,
-                    ),
-                  ],
+      return MaterialApp(
+        home: MediaQuery(
+          data: const MediaQueryData(devicePixelRatio: 1.0),
+          child: Directionality(
+            textDirection: TextDirection.ltr,
+            child: Center(
+              child: Material(
+                child: Form(
+                  key: formKey,
+                  autovalidate: true,
+                  child: ListView(
+                    children: <Widget>[
+                      TextFormField(
+                        key: fieldKey,
+                      ),
+                      TextFormField(
+                        validator: errorText,
+                      ),
+                    ],
+                  ),
                 ),
               ),
             ),
@@ -184,16 +192,18 @@
     final GlobalKey<FormFieldState<String>> inputKey = GlobalKey<FormFieldState<String>>();
 
     Widget builder() {
-      return MediaQuery(
-        data: const MediaQueryData(devicePixelRatio: 1.0),
-        child: Directionality(
-          textDirection: TextDirection.ltr,
-          child: Center(
-            child: Material(
-              child: Form(
-                child: TextFormField(
-                  key: inputKey,
-                  initialValue: 'hello',
+      return MaterialApp(
+        home: MediaQuery(
+          data: const MediaQueryData(devicePixelRatio: 1.0),
+          child: Directionality(
+            textDirection: TextDirection.ltr,
+            child: Center(
+              child: Material(
+                child: Form(
+                  child: TextFormField(
+                    key: inputKey,
+                    initialValue: 'hello',
+                  ),
                 ),
               ),
             ),
@@ -227,16 +237,18 @@
     final GlobalKey<FormFieldState<String>> inputKey = GlobalKey<FormFieldState<String>>();
 
     Widget builder() {
-      return MediaQuery(
-        data: const MediaQueryData(devicePixelRatio: 1.0),
-        child: Directionality(
-          textDirection: TextDirection.ltr,
-          child: Center(
-            child: Material(
-              child: Form(
-                child: TextFormField(
-                  key: inputKey,
-                  controller: controller,
+      return MaterialApp(
+        home: MediaQuery(
+          data: const MediaQueryData(devicePixelRatio: 1.0),
+          child: Directionality(
+            textDirection: TextDirection.ltr,
+            child: Center(
+              child: Material(
+                child: Form(
+                  child: TextFormField(
+                    key: inputKey,
+                    controller: controller,
+                  ),
                 ),
               ),
             ),
@@ -272,18 +284,20 @@
     final TextEditingController controller = TextEditingController(text: 'Plover');
 
     Widget builder() {
-      return MediaQuery(
-        data: const MediaQueryData(devicePixelRatio: 1.0),
-        child: Directionality(
-          textDirection: TextDirection.ltr,
-          child: Center(
-            child: Material(
-              child: Form(
-                key: formKey,
-                child: TextFormField(
-                  key: inputKey,
-                  controller: controller,
-                  // initialValue is 'Plover'
+      return MaterialApp(
+        home: MediaQuery(
+          data: const MediaQueryData(devicePixelRatio: 1.0),
+          child: Directionality(
+            textDirection: TextDirection.ltr,
+            child: Center(
+              child: Material(
+                child: Form(
+                  key: formKey,
+                  child: TextFormField(
+                    key: inputKey,
+                    controller: controller,
+                    // initialValue is 'Plover'
+                  ),
                 ),
               ),
             ),
@@ -322,16 +336,18 @@
       return StatefulBuilder(
         builder: (BuildContext context, StateSetter setter) {
           setState = setter;
-          return MediaQuery(
-            data: const MediaQueryData(devicePixelRatio: 1.0),
-            child: Directionality(
-              textDirection: TextDirection.ltr,
-              child: Center(
-                child: Material(
-                  child: Form(
-                    child: TextFormField(
-                      key: inputKey,
-                      controller: currentController,
+          return MaterialApp(
+            home: MediaQuery(
+              data: const MediaQueryData(devicePixelRatio: 1.0),
+              child: Directionality(
+                textDirection: TextDirection.ltr,
+                child: Center(
+                  child: Material(
+                    child: Form(
+                      child: TextFormField(
+                        key: inputKey,
+                        controller: currentController,
+                      ),
                     ),
                   ),
                 ),
@@ -420,18 +436,20 @@
     String fieldValue;
 
     Widget builder(bool remove) {
-      return MediaQuery(
-        data: const MediaQueryData(devicePixelRatio: 1.0),
-        child: Directionality(
-          textDirection: TextDirection.ltr,
-          child: Center(
-            child: Material(
-              child: Form(
-                key: formKey,
-                child: remove ? Container() : TextFormField(
-                  autofocus: true,
-                  onSaved: (String value) { fieldValue = value; },
-                  validator: (String value) { return value.isEmpty ? null : 'yes'; },
+      return MaterialApp(
+        home: MediaQuery(
+          data: const MediaQueryData(devicePixelRatio: 1.0),
+          child: Directionality(
+            textDirection: TextDirection.ltr,
+            child: Center(
+              child: Material(
+                child: Form(
+                  key: formKey,
+                  child: remove ? Container() : TextFormField(
+                    autofocus: true,
+                    onSaved: (String value) { fieldValue = value; },
+                    validator: (String value) { return value.isEmpty ? null : 'yes'; },
+                  ),
                 ),
               ),
             ),