Unverified Commit 142dd602 authored by Renzo Olivares's avatar Renzo Olivares Committed by GitHub

Match iOS Longpress behavior with native (#123630)

Match iOS Longpress behavior with native
parent 9f2ac971
...@@ -2005,6 +2005,14 @@ class TextSelectionGestureDetectorBuilder { ...@@ -2005,6 +2005,14 @@ class TextSelectionGestureDetectorBuilder {
// cursor will not move on drag update. // cursor will not move on drag update.
bool? _dragBeganOnPreviousSelection; bool? _dragBeganOnPreviousSelection;
// For iOS long press behavior when the field is not focused. iOS uses this value
// to determine if a long press began on a field that was not focused.
//
// If the field was not focused when the long press began, a long press will select
// the word and a long press move will select word-by-word. If the field was
// focused, the cursor moves to the long press position.
bool _longPressStartedWithoutFocus = false;
/// Handler for [TextSelectionGestureDetector.onTapDown]. /// Handler for [TextSelectionGestureDetector.onTapDown].
/// ///
/// By default, it forwards the tap to [RenderEditable.handleTapDown] and sets /// By default, it forwards the tap to [RenderEditable.handleTapDown] and sets
...@@ -2240,10 +2248,15 @@ class TextSelectionGestureDetectorBuilder { ...@@ -2240,10 +2248,15 @@ class TextSelectionGestureDetectorBuilder {
switch (defaultTargetPlatform) { switch (defaultTargetPlatform) {
case TargetPlatform.iOS: case TargetPlatform.iOS:
case TargetPlatform.macOS: case TargetPlatform.macOS:
if (!renderEditable.hasFocus) {
_longPressStartedWithoutFocus = true;
renderEditable.selectWord(cause: SelectionChangedCause.longPress);
} else {
renderEditable.selectPositionAt( renderEditable.selectPositionAt(
from: details.globalPosition, from: details.globalPosition,
cause: SelectionChangedCause.longPress, cause: SelectionChangedCause.longPress,
); );
}
case TargetPlatform.android: case TargetPlatform.android:
case TargetPlatform.fuchsia: case TargetPlatform.fuchsia:
case TargetPlatform.linux: case TargetPlatform.linux:
...@@ -2291,10 +2304,18 @@ class TextSelectionGestureDetectorBuilder { ...@@ -2291,10 +2304,18 @@ class TextSelectionGestureDetectorBuilder {
switch (defaultTargetPlatform) { switch (defaultTargetPlatform) {
case TargetPlatform.iOS: case TargetPlatform.iOS:
case TargetPlatform.macOS: case TargetPlatform.macOS:
if (_longPressStartedWithoutFocus) {
renderEditable.selectWordsInRange(
from: details.globalPosition - details.offsetFromOrigin - editableOffset - scrollableOffset,
to: details.globalPosition,
cause: SelectionChangedCause.longPress,
);
} else {
renderEditable.selectPositionAt( renderEditable.selectPositionAt(
from: details.globalPosition, from: details.globalPosition,
cause: SelectionChangedCause.longPress, cause: SelectionChangedCause.longPress,
); );
}
case TargetPlatform.android: case TargetPlatform.android:
case TargetPlatform.fuchsia: case TargetPlatform.fuchsia:
case TargetPlatform.linux: case TargetPlatform.linux:
...@@ -2342,6 +2363,7 @@ class TextSelectionGestureDetectorBuilder { ...@@ -2342,6 +2363,7 @@ class TextSelectionGestureDetectorBuilder {
if (shouldShowSelectionToolbar) { if (shouldShowSelectionToolbar) {
editableText.showToolbar(); editableText.showToolbar();
} }
_longPressStartedWithoutFocus = false;
_dragStartViewportOffset = 0.0; _dragStartViewportOffset = 0.0;
_dragStartScrollOffset = 0.0; _dragStartScrollOffset = 0.0;
} }
......
...@@ -1591,6 +1591,7 @@ void main() { ...@@ -1591,6 +1591,7 @@ void main() {
home: Column( home: Column(
children: <Widget>[ children: <Widget>[
CupertinoTextField( CupertinoTextField(
autofocus: true,
controller: controller, controller: controller,
toolbarOptions: const ToolbarOptions(copy: true), toolbarOptions: const ToolbarOptions(copy: true),
), ),
...@@ -1599,6 +1600,9 @@ void main() { ...@@ -1599,6 +1600,9 @@ void main() {
), ),
); );
// This extra pump is so autofocus can propagate to renderEditable.
await tester.pump();
// Long press to put the cursor after the "w". // Long press to put the cursor after the "w".
const int index = 3; const int index = 3;
await tester.longPressAt(textOffsetToPosition(tester, index)); await tester.longPressAt(textOffsetToPosition(tester, index));
...@@ -2060,12 +2064,16 @@ void main() { ...@@ -2060,12 +2064,16 @@ void main() {
CupertinoApp( CupertinoApp(
home: Center( home: Center(
child: CupertinoTextField( child: CupertinoTextField(
autofocus: true,
controller: controller, controller: controller,
), ),
), ),
), ),
); );
// This extra pump is so autofocus can propagate to renderEditable.
await tester.pump();
// Long press to put the cursor after the "w". // Long press to put the cursor after the "w".
const int index = 3; const int index = 3;
await tester.longPressAt(textOffsetToPosition(tester, index)); await tester.longPressAt(textOffsetToPosition(tester, index));
...@@ -2830,12 +2838,16 @@ void main() { ...@@ -2830,12 +2838,16 @@ void main() {
CupertinoApp( CupertinoApp(
home: Center( home: Center(
child: CupertinoTextField( child: CupertinoTextField(
autofocus: true,
controller: controller, controller: controller,
), ),
), ),
), ),
); );
// This extra pump is so autofocus can propagate to renderEditable.
await tester.pump();
final Offset textFieldStart = tester.getTopLeft(find.byType(CupertinoTextField)); final Offset textFieldStart = tester.getTopLeft(find.byType(CupertinoTextField));
await tester.longPressAt(textFieldStart + const Offset(50.0, 5.0)); await tester.longPressAt(textFieldStart + const Offset(50.0, 5.0));
...@@ -2870,12 +2882,16 @@ void main() { ...@@ -2870,12 +2882,16 @@ void main() {
CupertinoApp( CupertinoApp(
home: Center( home: Center(
child: CupertinoTextField( child: CupertinoTextField(
autofocus: true,
controller: controller, controller: controller,
), ),
), ),
), ),
); );
// This extra pump is so autofocus can propagate to renderEditable.
await tester.pump();
final Offset ePos = textOffsetToPosition(tester, 6); // Index of 'Atwate|r' final Offset ePos = textOffsetToPosition(tester, 6); // Index of 'Atwate|r'
await tester.longPressAt(ePos); await tester.longPressAt(ePos);
...@@ -2971,7 +2987,7 @@ void main() { ...@@ -2971,7 +2987,7 @@ void main() {
); );
testWidgets( testWidgets(
'long press drag moves the cursor under the drag and shows toolbar on lift on Apple platforms', 'long press drag on a focused TextField moves the cursor under the drag and shows toolbar on lift on Apple platforms',
(WidgetTester tester) async { (WidgetTester tester) async {
final TextEditingController controller = TextEditingController( final TextEditingController controller = TextEditingController(
text: 'Atwater Peel Sherbrooke Bonaventure', text: 'Atwater Peel Sherbrooke Bonaventure',
...@@ -2980,12 +2996,16 @@ void main() { ...@@ -2980,12 +2996,16 @@ void main() {
CupertinoApp( CupertinoApp(
home: Center( home: Center(
child: CupertinoTextField( child: CupertinoTextField(
autofocus: true,
controller: controller, controller: controller,
), ),
), ),
), ),
); );
// This extra pump is so autofocus can propagate to renderEditable.
await tester.pump();
final Offset textFieldStart = tester.getTopLeft(find.byType(CupertinoTextField)); final Offset textFieldStart = tester.getTopLeft(find.byType(CupertinoTextField));
final TestGesture gesture = final TestGesture gesture =
...@@ -3135,12 +3155,16 @@ void main() { ...@@ -3135,12 +3155,16 @@ void main() {
CupertinoApp( CupertinoApp(
home: Center( home: Center(
child: CupertinoTextField( child: CupertinoTextField(
autofocus: true,
controller: controller, controller: controller,
), ),
), ),
), ),
); );
// This extra pump is so autofocus can propagate to renderEditable.
await tester.pump();
final RenderEditable renderEditable = tester.renderObject<RenderEditable>( final RenderEditable renderEditable = tester.renderObject<RenderEditable>(
find.byElementPredicate((Element element) => element.renderObject is RenderEditable).last, find.byElementPredicate((Element element) => element.renderObject is RenderEditable).last,
); );
...@@ -3289,12 +3313,16 @@ void main() { ...@@ -3289,12 +3313,16 @@ void main() {
CupertinoApp( CupertinoApp(
home: Center( home: Center(
child: CupertinoTextField( child: CupertinoTextField(
autofocus: true,
controller: controller, controller: controller,
), ),
), ),
), ),
); );
// This extra pump is so autofocus can propagate to renderEditable.
await tester.pump();
// Use a position higher than wPos to avoid tapping the context menu on // Use a position higher than wPos to avoid tapping the context menu on
// desktop. // desktop.
final Offset pPos = textOffsetToPosition(tester, 9) + const Offset(0.0, -20.0); // Index of 'P|eel' final Offset pPos = textOffsetToPosition(tester, 9) + const Offset(0.0, -20.0); // Index of 'P|eel'
...@@ -7313,6 +7341,7 @@ void main() { ...@@ -7313,6 +7341,7 @@ void main() {
child: Column( child: Column(
children: <Widget>[ children: <Widget>[
CupertinoTextField( CupertinoTextField(
autofocus: true,
key: const Key('field0'), key: const Key('field0'),
controller: controller, controller: controller,
style: const TextStyle(height: 4, color: ui.Color.fromARGB(100, 0, 0, 0)), style: const TextStyle(height: 4, color: ui.Color.fromARGB(100, 0, 0, 0)),
...@@ -7329,6 +7358,9 @@ void main() { ...@@ -7329,6 +7358,9 @@ void main() {
), ),
); );
// This extra pump is so autofocus can propagate to renderEditable.
await tester.pump();
final Offset textFieldStart = tester.getTopLeft(find.byKey(const Key('field0'))); final Offset textFieldStart = tester.getTopLeft(find.byKey(const Key('field0')));
await tester.longPressAt(textFieldStart + const Offset(50.0, 2.0)); await tester.longPressAt(textFieldStart + const Offset(50.0, 2.0));
...@@ -7363,6 +7395,7 @@ void main() { ...@@ -7363,6 +7395,7 @@ void main() {
child: Column( child: Column(
children: <Widget>[ children: <Widget>[
CupertinoTextField( CupertinoTextField(
autofocus: true,
key: const Key('field0'), key: const Key('field0'),
controller: controller, controller: controller,
style: const TextStyle(height: 4, color: ui.Color.fromARGB(100, 0, 0, 0)), style: const TextStyle(height: 4, color: ui.Color.fromARGB(100, 0, 0, 0)),
...@@ -7378,6 +7411,9 @@ void main() { ...@@ -7378,6 +7411,9 @@ void main() {
), ),
); );
// This extra pump is so autofocus can propagate to renderEditable.
await tester.pump();
final Offset textFieldStart = tester.getTopLeft(find.byKey(const Key('field0'))); final Offset textFieldStart = tester.getTopLeft(find.byKey(const Key('field0')));
await tester.longPressAt(textFieldStart + const Offset(50.0, 2.0)); await tester.longPressAt(textFieldStart + const Offset(50.0, 2.0));
......
...@@ -200,6 +200,7 @@ void main() { ...@@ -200,6 +200,7 @@ void main() {
data: const MediaQueryData(size: Size(800.0, 600.0)), data: const MediaQueryData(size: Size(800.0, 600.0)),
child: Center( child: Center(
child: CupertinoTextField( child: CupertinoTextField(
autofocus: true,
controller: controller, controller: controller,
), ),
), ),
...@@ -207,6 +208,9 @@ void main() { ...@@ -207,6 +208,9 @@ void main() {
), ),
)); ));
// This extra pump is so autofocus can propagate to renderEditable.
await tester.pump();
// Initially, the menu isn't shown at all. // Initially, the menu isn't shown at all.
expect(find.text('Cut'), findsNothing); expect(find.text('Cut'), findsNothing);
expect(find.text('Copy'), findsNothing); expect(find.text('Copy'), findsNothing);
...@@ -432,6 +436,7 @@ void main() { ...@@ -432,6 +436,7 @@ void main() {
data: const MediaQueryData(size: Size(800.0, 600.0)), data: const MediaQueryData(size: Size(800.0, 600.0)),
child: Center( child: Center(
child: CupertinoTextField( child: CupertinoTextField(
autofocus: true,
controller: controller, controller: controller,
), ),
), ),
...@@ -439,6 +444,9 @@ void main() { ...@@ -439,6 +444,9 @@ void main() {
), ),
)); ));
// This extra pump is so autofocus can propagate to renderEditable.
await tester.pump();
// Initially, the menu isn't shown at all. // Initially, the menu isn't shown at all.
expect(find.text(_longLocalizations.cutButtonLabel), findsNothing); expect(find.text(_longLocalizations.cutButtonLabel), findsNothing);
expect(find.text(_longLocalizations.copyButtonLabel), findsNothing); expect(find.text(_longLocalizations.copyButtonLabel), findsNothing);
...@@ -546,6 +554,7 @@ void main() { ...@@ -546,6 +554,7 @@ void main() {
data: const MediaQueryData(size: Size(800.0, 600.0)), data: const MediaQueryData(size: Size(800.0, 600.0)),
child: Center( child: Center(
child: CupertinoTextField( child: CupertinoTextField(
autofocus: true,
padding: const EdgeInsets.all(8.0), padding: const EdgeInsets.all(8.0),
controller: controller, controller: controller,
maxLines: 2, maxLines: 2,
...@@ -555,6 +564,9 @@ void main() { ...@@ -555,6 +564,9 @@ void main() {
), ),
)); ));
// This extra pump is so autofocus can propagate to renderEditable.
await tester.pump();
// Initially, the menu isn't shown at all. // Initially, the menu isn't shown at all.
expect(find.text('Cut'), findsNothing); expect(find.text('Cut'), findsNothing);
expect(find.text('Copy'), findsNothing); expect(find.text('Copy'), findsNothing);
......
...@@ -460,8 +460,12 @@ void main() { ...@@ -460,8 +460,12 @@ void main() {
expect(dragEndCount, 1); expect(dragEndCount, 1);
}); });
testWidgets('test TextSelectionGestureDetectorBuilder long press on Apple Platforms', (WidgetTester tester) async { testWidgets('test TextSelectionGestureDetectorBuilder long press on Apple Platforms - focused renderEditable', (WidgetTester tester) async {
await pumpTextSelectionGestureDetectorBuilder(tester); await pumpTextSelectionGestureDetectorBuilder(tester);
final FakeEditableTextState state = tester.state(find.byType(FakeEditableText));
final FakeRenderEditable renderEditable = tester.renderObject(find.byType(FakeEditable));
renderEditable.hasFocus = true;
final TestGesture gesture = await tester.startGesture( final TestGesture gesture = await tester.startGesture(
const Offset(200.0, 200.0), const Offset(200.0, 200.0),
pointer: 0, pointer: 0,
...@@ -470,13 +474,29 @@ void main() { ...@@ -470,13 +474,29 @@ void main() {
await gesture.up(); await gesture.up();
await tester.pumpAndSettle(); await tester.pumpAndSettle();
final FakeEditableTextState state = tester.state(find.byType(FakeEditableText));
final FakeRenderEditable renderEditable = tester.renderObject(find.byType(FakeEditable));
expect(state.showToolbarCalled, isTrue); expect(state.showToolbarCalled, isTrue);
expect(renderEditable.selectPositionAtCalled, isTrue); expect(renderEditable.selectPositionAtCalled, isTrue);
expect(renderEditable.lastCause, SelectionChangedCause.longPress); expect(renderEditable.lastCause, SelectionChangedCause.longPress);
}, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS })); }, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS }));
testWidgets('test TextSelectionGestureDetectorBuilder long press on iOS - renderEditable not focused', (WidgetTester tester) async {
await pumpTextSelectionGestureDetectorBuilder(tester);
final FakeEditableTextState state = tester.state(find.byType(FakeEditableText));
final FakeRenderEditable renderEditable = tester.renderObject(find.byType(FakeEditable));
final TestGesture gesture = await tester.startGesture(
const Offset(200.0, 200.0),
pointer: 0,
);
await tester.pump(const Duration(seconds: 2));
await gesture.up();
await tester.pumpAndSettle();
expect(state.showToolbarCalled, isTrue);
expect(renderEditable.selectWordCalled, isTrue);
expect(renderEditable.lastCause, SelectionChangedCause.longPress);
}, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS }));
testWidgets('test TextSelectionGestureDetectorBuilder long press on non-Apple Platforms', (WidgetTester tester) async { testWidgets('test TextSelectionGestureDetectorBuilder long press on non-Apple Platforms', (WidgetTester tester) async {
await pumpTextSelectionGestureDetectorBuilder(tester); await pumpTextSelectionGestureDetectorBuilder(tester);
final TestGesture gesture = await tester.startGesture( final TestGesture gesture = await tester.startGesture(
......
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