Unverified Commit ff53fbe1 authored by Justin McCandless's avatar Justin McCandless Committed by GitHub

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.
parent ef42c36a
...@@ -762,6 +762,7 @@ class _TextFieldState extends State<TextField> with AutomaticKeepAliveClientMixi ...@@ -762,6 +762,7 @@ class _TextFieldState extends State<TextField> with AutomaticKeepAliveClientMixi
} }
void _handleSingleTapUp(TapUpDetails details) { void _handleSingleTapUp(TapUpDetails details) {
_editableTextKey.currentState.hideToolbar();
if (widget.selectionEnabled) { if (widget.selectionEnabled) {
switch (Theme.of(context).platform) { switch (Theme.of(context).platform) {
case TargetPlatform.iOS: case TargetPlatform.iOS:
......
...@@ -1447,7 +1447,7 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien ...@@ -1447,7 +1447,7 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
_showCaretOnScreen(); _showCaretOnScreen();
if (!_value.selection.isValid) { if (!_value.selection.isValid) {
// Place cursor at the end if the selection is invalid when we receive focus. // 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 { } else {
WidgetsBinding.instance.removeObserver(this); WidgetsBinding.instance.removeObserver(this);
...@@ -1500,6 +1500,9 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien ...@@ -1500,6 +1500,9 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
@override @override
void hideToolbar() { void hideToolbar() {
if (_selectionOverlay == null || !_selectionOverlay.toolbarIsVisible) {
return;
}
_selectionOverlay?.hide(); _selectionOverlay?.hide();
} }
......
...@@ -3650,5 +3650,31 @@ void main() { ...@@ -3650,5 +3650,31 @@ void main() {
expect(tester.getTopLeft(find.byType(EditableText)).dy, closeTo(329.0, .0001)); 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);
},
);
}); });
} }
...@@ -92,7 +92,8 @@ Widget overlayWithEntry(OverlayEntry entry) { ...@@ -92,7 +92,8 @@ Widget overlayWithEntry(OverlayEntry entry) {
} }
Widget boilerplate({ Widget child }) { Widget boilerplate({ Widget child }) {
return Localizations( return MaterialApp(
home: Localizations(
locale: const Locale('en', 'US'), locale: const Locale('en', 'US'),
delegates: <LocalizationsDelegate<dynamic>>[ delegates: <LocalizationsDelegate<dynamic>>[
WidgetsLocalizationsDelegate(), WidgetsLocalizationsDelegate(),
...@@ -109,6 +110,7 @@ Widget boilerplate({ Widget child }) { ...@@ -109,6 +110,7 @@ Widget boilerplate({ Widget child }) {
), ),
), ),
), ),
),
); );
} }
...@@ -5341,6 +5343,66 @@ void main() { ...@@ -5341,6 +5343,66 @@ void main() {
}, },
); );
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( testWidgets(
'double tap hold selects word (iOS)', 'double tap hold selects word (iOS)',
(WidgetTester tester) async { (WidgetTester tester) async {
......
...@@ -202,7 +202,8 @@ void main() { ...@@ -202,7 +202,8 @@ void main() {
final PageController pageController = PageController(initialPage: 1); final PageController pageController = PageController(initialPage: 1);
await tester.pumpWidget( await tester.pumpWidget(
MediaQuery( MaterialApp(
home: MediaQuery(
data: const MediaQueryData(devicePixelRatio: 1.0), data: const MediaQueryData(devicePixelRatio: 1.0),
child: Directionality( child: Directionality(
textDirection: TextDirection.ltr, textDirection: TextDirection.ltr,
...@@ -227,6 +228,7 @@ void main() { ...@@ -227,6 +228,7 @@ void main() {
), ),
), ),
), ),
),
); );
await tester.showKeyboard(find.byType(EditableText)); await tester.showKeyboard(find.byType(EditableText));
......
...@@ -809,7 +809,8 @@ void main() { ...@@ -809,7 +809,8 @@ void main() {
return StatefulBuilder( return StatefulBuilder(
builder: (BuildContext context, StateSetter setter) { builder: (BuildContext context, StateSetter setter) {
setState = setter; setState = setter;
return MediaQuery( return MaterialApp(
home: MediaQuery(
data: const MediaQueryData(devicePixelRatio: 1.0), data: const MediaQueryData(devicePixelRatio: 1.0),
child: Directionality( child: Directionality(
textDirection: TextDirection.ltr, textDirection: TextDirection.ltr,
...@@ -830,6 +831,7 @@ void main() { ...@@ -830,6 +831,7 @@ void main() {
), ),
), ),
), ),
),
); );
}, },
); );
......
...@@ -11,7 +11,8 @@ void main() { ...@@ -11,7 +11,8 @@ void main() {
String fieldValue; String fieldValue;
Widget builder() { Widget builder() {
return MediaQuery( return MaterialApp(
home: MediaQuery(
data: const MediaQueryData(devicePixelRatio: 1.0), data: const MediaQueryData(devicePixelRatio: 1.0),
child: Directionality( child: Directionality(
textDirection: TextDirection.ltr, textDirection: TextDirection.ltr,
...@@ -26,6 +27,7 @@ void main() { ...@@ -26,6 +27,7 @@ void main() {
), ),
), ),
), ),
),
); );
} }
...@@ -36,7 +38,7 @@ void main() { ...@@ -36,7 +38,7 @@ void main() {
Future<void> checkText(String testValue) async { Future<void> checkText(String testValue) async {
await tester.enterText(find.byType(TextFormField), testValue); await tester.enterText(find.byType(TextFormField), testValue);
formKey.currentState.save(); 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)); expect(fieldValue, equals(testValue));
} }
...@@ -48,7 +50,8 @@ void main() { ...@@ -48,7 +50,8 @@ void main() {
String fieldValue; String fieldValue;
Widget builder() { Widget builder() {
return MediaQuery( return MaterialApp(
home: MediaQuery(
data: const MediaQueryData(devicePixelRatio: 1.0), data: const MediaQueryData(devicePixelRatio: 1.0),
child: Directionality( child: Directionality(
textDirection: TextDirection.ltr, textDirection: TextDirection.ltr,
...@@ -62,6 +65,7 @@ void main() { ...@@ -62,6 +65,7 @@ void main() {
), ),
), ),
), ),
),
); );
} }
...@@ -84,7 +88,8 @@ void main() { ...@@ -84,7 +88,8 @@ void main() {
String errorText(String value) => value + '/error'; String errorText(String value) => value + '/error';
Widget builder(bool autovalidate) { Widget builder(bool autovalidate) {
return MediaQuery( return MaterialApp(
home: MediaQuery(
data: const MediaQueryData(devicePixelRatio: 1.0), data: const MediaQueryData(devicePixelRatio: 1.0),
child: Directionality( child: Directionality(
textDirection: TextDirection.ltr, textDirection: TextDirection.ltr,
...@@ -100,6 +105,7 @@ void main() { ...@@ -100,6 +105,7 @@ void main() {
), ),
), ),
), ),
),
); );
} }
...@@ -138,7 +144,8 @@ void main() { ...@@ -138,7 +144,8 @@ void main() {
String errorText(String input) => '${fieldKey.currentState.value}/error'; String errorText(String input) => '${fieldKey.currentState.value}/error';
Widget builder() { Widget builder() {
return MediaQuery( return MaterialApp(
home: MediaQuery(
data: const MediaQueryData(devicePixelRatio: 1.0), data: const MediaQueryData(devicePixelRatio: 1.0),
child: Directionality( child: Directionality(
textDirection: TextDirection.ltr, textDirection: TextDirection.ltr,
...@@ -161,6 +168,7 @@ void main() { ...@@ -161,6 +168,7 @@ void main() {
), ),
), ),
), ),
),
); );
} }
...@@ -184,7 +192,8 @@ void main() { ...@@ -184,7 +192,8 @@ void main() {
final GlobalKey<FormFieldState<String>> inputKey = GlobalKey<FormFieldState<String>>(); final GlobalKey<FormFieldState<String>> inputKey = GlobalKey<FormFieldState<String>>();
Widget builder() { Widget builder() {
return MediaQuery( return MaterialApp(
home: MediaQuery(
data: const MediaQueryData(devicePixelRatio: 1.0), data: const MediaQueryData(devicePixelRatio: 1.0),
child: Directionality( child: Directionality(
textDirection: TextDirection.ltr, textDirection: TextDirection.ltr,
...@@ -199,6 +208,7 @@ void main() { ...@@ -199,6 +208,7 @@ void main() {
), ),
), ),
), ),
),
); );
} }
...@@ -227,7 +237,8 @@ void main() { ...@@ -227,7 +237,8 @@ void main() {
final GlobalKey<FormFieldState<String>> inputKey = GlobalKey<FormFieldState<String>>(); final GlobalKey<FormFieldState<String>> inputKey = GlobalKey<FormFieldState<String>>();
Widget builder() { Widget builder() {
return MediaQuery( return MaterialApp(
home: MediaQuery(
data: const MediaQueryData(devicePixelRatio: 1.0), data: const MediaQueryData(devicePixelRatio: 1.0),
child: Directionality( child: Directionality(
textDirection: TextDirection.ltr, textDirection: TextDirection.ltr,
...@@ -242,6 +253,7 @@ void main() { ...@@ -242,6 +253,7 @@ void main() {
), ),
), ),
), ),
),
); );
} }
...@@ -272,7 +284,8 @@ void main() { ...@@ -272,7 +284,8 @@ void main() {
final TextEditingController controller = TextEditingController(text: 'Plover'); final TextEditingController controller = TextEditingController(text: 'Plover');
Widget builder() { Widget builder() {
return MediaQuery( return MaterialApp(
home: MediaQuery(
data: const MediaQueryData(devicePixelRatio: 1.0), data: const MediaQueryData(devicePixelRatio: 1.0),
child: Directionality( child: Directionality(
textDirection: TextDirection.ltr, textDirection: TextDirection.ltr,
...@@ -289,6 +302,7 @@ void main() { ...@@ -289,6 +302,7 @@ void main() {
), ),
), ),
), ),
),
); );
} }
await tester.pumpWidget(builder()); await tester.pumpWidget(builder());
...@@ -322,7 +336,8 @@ void main() { ...@@ -322,7 +336,8 @@ void main() {
return StatefulBuilder( return StatefulBuilder(
builder: (BuildContext context, StateSetter setter) { builder: (BuildContext context, StateSetter setter) {
setState = setter; setState = setter;
return MediaQuery( return MaterialApp(
home: MediaQuery(
data: const MediaQueryData(devicePixelRatio: 1.0), data: const MediaQueryData(devicePixelRatio: 1.0),
child: Directionality( child: Directionality(
textDirection: TextDirection.ltr, textDirection: TextDirection.ltr,
...@@ -337,6 +352,7 @@ void main() { ...@@ -337,6 +352,7 @@ void main() {
), ),
), ),
), ),
),
); );
}, },
); );
...@@ -420,7 +436,8 @@ void main() { ...@@ -420,7 +436,8 @@ void main() {
String fieldValue; String fieldValue;
Widget builder(bool remove) { Widget builder(bool remove) {
return MediaQuery( return MaterialApp(
home: MediaQuery(
data: const MediaQueryData(devicePixelRatio: 1.0), data: const MediaQueryData(devicePixelRatio: 1.0),
child: Directionality( child: Directionality(
textDirection: TextDirection.ltr, textDirection: TextDirection.ltr,
...@@ -437,6 +454,7 @@ void main() { ...@@ -437,6 +454,7 @@ void main() {
), ),
), ),
), ),
),
); );
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment