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
}
void _handleSingleTapUp(TapUpDetails details) {
_editableTextKey.currentState.hideToolbar();
if (widget.selectionEnabled) {
switch (Theme.of(context).platform) {
case TargetPlatform.iOS:
......
......@@ -1447,7 +1447,7 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
_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 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
@override
void hideToolbar() {
if (_selectionOverlay == null || !_selectionOverlay.toolbarIsVisible) {
return;
}
_selectionOverlay?.hide();
}
......
......@@ -3650,5 +3650,31 @@ void main() {
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) {
}
Widget boilerplate({ Widget child }) {
return Localizations(
return MaterialApp(
home: Localizations(
locale: const Locale('en', 'US'),
delegates: <LocalizationsDelegate<dynamic>>[
WidgetsLocalizationsDelegate(),
......@@ -109,6 +110,7 @@ Widget boilerplate({ Widget child }) {
),
),
),
),
);
}
......@@ -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(
'double tap hold selects word (iOS)',
(WidgetTester tester) async {
......
......@@ -202,7 +202,8 @@ void main() {
final PageController pageController = PageController(initialPage: 1);
await tester.pumpWidget(
MediaQuery(
MaterialApp(
home: MediaQuery(
data: const MediaQueryData(devicePixelRatio: 1.0),
child: Directionality(
textDirection: TextDirection.ltr,
......@@ -227,6 +228,7 @@ void main() {
),
),
),
),
);
await tester.showKeyboard(find.byType(EditableText));
......
......@@ -809,7 +809,8 @@ void main() {
return StatefulBuilder(
builder: (BuildContext context, StateSetter setter) {
setState = setter;
return MediaQuery(
return MaterialApp(
home: MediaQuery(
data: const MediaQueryData(devicePixelRatio: 1.0),
child: Directionality(
textDirection: TextDirection.ltr,
......@@ -830,6 +831,7 @@ void main() {
),
),
),
),
);
},
);
......
......@@ -11,7 +11,8 @@ void main() {
String fieldValue;
Widget builder() {
return MediaQuery(
return MaterialApp(
home: MediaQuery(
data: const MediaQueryData(devicePixelRatio: 1.0),
child: Directionality(
textDirection: TextDirection.ltr,
......@@ -26,6 +27,7 @@ void main() {
),
),
),
),
);
}
......@@ -36,7 +38,7 @@ void main() {
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,7 +50,8 @@ void main() {
String fieldValue;
Widget builder() {
return MediaQuery(
return MaterialApp(
home: MediaQuery(
data: const MediaQueryData(devicePixelRatio: 1.0),
child: Directionality(
textDirection: TextDirection.ltr,
......@@ -62,6 +65,7 @@ void main() {
),
),
),
),
);
}
......@@ -84,7 +88,8 @@ void main() {
String errorText(String value) => value + '/error';
Widget builder(bool autovalidate) {
return MediaQuery(
return MaterialApp(
home: MediaQuery(
data: const MediaQueryData(devicePixelRatio: 1.0),
child: Directionality(
textDirection: TextDirection.ltr,
......@@ -100,6 +105,7 @@ void main() {
),
),
),
),
);
}
......@@ -138,7 +144,8 @@ void main() {
String errorText(String input) => '${fieldKey.currentState.value}/error';
Widget builder() {
return MediaQuery(
return MaterialApp(
home: MediaQuery(
data: const MediaQueryData(devicePixelRatio: 1.0),
child: Directionality(
textDirection: TextDirection.ltr,
......@@ -161,6 +168,7 @@ void main() {
),
),
),
),
);
}
......@@ -184,7 +192,8 @@ void main() {
final GlobalKey<FormFieldState<String>> inputKey = GlobalKey<FormFieldState<String>>();
Widget builder() {
return MediaQuery(
return MaterialApp(
home: MediaQuery(
data: const MediaQueryData(devicePixelRatio: 1.0),
child: Directionality(
textDirection: TextDirection.ltr,
......@@ -199,6 +208,7 @@ void main() {
),
),
),
),
);
}
......@@ -227,7 +237,8 @@ void main() {
final GlobalKey<FormFieldState<String>> inputKey = GlobalKey<FormFieldState<String>>();
Widget builder() {
return MediaQuery(
return MaterialApp(
home: MediaQuery(
data: const MediaQueryData(devicePixelRatio: 1.0),
child: Directionality(
textDirection: TextDirection.ltr,
......@@ -242,6 +253,7 @@ void main() {
),
),
),
),
);
}
......@@ -272,7 +284,8 @@ void main() {
final TextEditingController controller = TextEditingController(text: 'Plover');
Widget builder() {
return MediaQuery(
return MaterialApp(
home: MediaQuery(
data: const MediaQueryData(devicePixelRatio: 1.0),
child: Directionality(
textDirection: TextDirection.ltr,
......@@ -289,6 +302,7 @@ void main() {
),
),
),
),
);
}
await tester.pumpWidget(builder());
......@@ -322,7 +336,8 @@ void main() {
return StatefulBuilder(
builder: (BuildContext context, StateSetter setter) {
setState = setter;
return MediaQuery(
return MaterialApp(
home: MediaQuery(
data: const MediaQueryData(devicePixelRatio: 1.0),
child: Directionality(
textDirection: TextDirection.ltr,
......@@ -337,6 +352,7 @@ void main() {
),
),
),
),
);
},
);
......@@ -420,7 +436,8 @@ void main() {
String fieldValue;
Widget builder(bool remove) {
return MediaQuery(
return MaterialApp(
home: MediaQuery(
data: const MediaQueryData(devicePixelRatio: 1.0),
child: Directionality(
textDirection: TextDirection.ltr,
......@@ -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