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,19 +92,21 @@ Widget overlayWithEntry(OverlayEntry entry) { ...@@ -92,19 +92,21 @@ Widget overlayWithEntry(OverlayEntry entry) {
} }
Widget boilerplate({ Widget child }) { Widget boilerplate({ Widget child }) {
return Localizations( return MaterialApp(
locale: const Locale('en', 'US'), home: Localizations(
delegates: <LocalizationsDelegate<dynamic>>[ locale: const Locale('en', 'US'),
WidgetsLocalizationsDelegate(), delegates: <LocalizationsDelegate<dynamic>>[
MaterialLocalizationsDelegate(), WidgetsLocalizationsDelegate(),
], MaterialLocalizationsDelegate(),
child: Directionality( ],
textDirection: TextDirection.ltr, child: Directionality(
child: MediaQuery( textDirection: TextDirection.ltr,
data: const MediaQueryData(size: Size(800.0, 600.0)), child: MediaQuery(
child: Center( data: const MediaQueryData(size: Size(800.0, 600.0)),
child: Material( child: Center(
child: child, child: Material(
child: 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,27 +202,29 @@ void main() { ...@@ -202,27 +202,29 @@ void main() {
final PageController pageController = PageController(initialPage: 1); final PageController pageController = PageController(initialPage: 1);
await tester.pumpWidget( await tester.pumpWidget(
MediaQuery( MaterialApp(
data: const MediaQueryData(devicePixelRatio: 1.0), home: MediaQuery(
child: Directionality( data: const MediaQueryData(devicePixelRatio: 1.0),
textDirection: TextDirection.ltr, child: Directionality(
child: Material( textDirection: TextDirection.ltr,
child: PageView( child: Material(
controller: pageController, child: PageView(
children: <Widget>[ controller: pageController,
Container( children: <Widget>[
color: Colors.red, Container(
), color: Colors.red,
Container(
child: TextField(
controller: textController,
), ),
color: Colors.green, Container(
), child: TextField(
Container( controller: textController,
color: Colors.red, ),
), color: Colors.green,
], ),
Container(
color: Colors.red,
),
],
),
), ),
), ),
), ),
......
...@@ -809,23 +809,25 @@ void main() { ...@@ -809,23 +809,25 @@ void main() {
return StatefulBuilder( return StatefulBuilder(
builder: (BuildContext context, StateSetter setter) { builder: (BuildContext context, StateSetter setter) {
setState = setter; setState = setter;
return MediaQuery( return MaterialApp(
data: const MediaQueryData(devicePixelRatio: 1.0), home: MediaQuery(
child: Directionality( data: const MediaQueryData(devicePixelRatio: 1.0),
textDirection: TextDirection.ltr, child: Directionality(
child: Center( textDirection: TextDirection.ltr,
child: Material( child: Center(
child: EditableText( child: Material(
backgroundCursorColor: Colors.grey, child: EditableText(
controller: currentController, backgroundCursorColor: Colors.grey,
focusNode: focusNode, controller: currentController,
style: Typography(platform: TargetPlatform.android) focusNode: focusNode,
.black style: Typography(platform: TargetPlatform.android)
.subhead, .black
cursorColor: Colors.blue, .subhead,
selectionControls: materialTextSelectionControls, cursorColor: Colors.blue,
keyboardType: TextInputType.text, selectionControls: materialTextSelectionControls,
onChanged: (String value) { }, keyboardType: TextInputType.text,
onChanged: (String value) { },
),
), ),
), ),
), ),
......
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