Skip to content

Commit 142dd60

Browse files
Match iOS Longpress behavior with native (#123630)
Match iOS Longpress behavior with native
1 parent 9f2ac97 commit 142dd60

File tree

5 files changed

+327
-15
lines changed

5 files changed

+327
-15
lines changed

packages/flutter/lib/src/widgets/text_selection.dart

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2005,6 +2005,14 @@ class TextSelectionGestureDetectorBuilder {
20052005
// cursor will not move on drag update.
20062006
bool? _dragBeganOnPreviousSelection;
20072007

2008+
// For iOS long press behavior when the field is not focused. iOS uses this value
2009+
// to determine if a long press began on a field that was not focused.
2010+
//
2011+
// If the field was not focused when the long press began, a long press will select
2012+
// the word and a long press move will select word-by-word. If the field was
2013+
// focused, the cursor moves to the long press position.
2014+
bool _longPressStartedWithoutFocus = false;
2015+
20082016
/// Handler for [TextSelectionGestureDetector.onTapDown].
20092017
///
20102018
/// By default, it forwards the tap to [RenderEditable.handleTapDown] and sets
@@ -2240,10 +2248,15 @@ class TextSelectionGestureDetectorBuilder {
22402248
switch (defaultTargetPlatform) {
22412249
case TargetPlatform.iOS:
22422250
case TargetPlatform.macOS:
2243-
renderEditable.selectPositionAt(
2244-
from: details.globalPosition,
2245-
cause: SelectionChangedCause.longPress,
2246-
);
2251+
if (!renderEditable.hasFocus) {
2252+
_longPressStartedWithoutFocus = true;
2253+
renderEditable.selectWord(cause: SelectionChangedCause.longPress);
2254+
} else {
2255+
renderEditable.selectPositionAt(
2256+
from: details.globalPosition,
2257+
cause: SelectionChangedCause.longPress,
2258+
);
2259+
}
22472260
case TargetPlatform.android:
22482261
case TargetPlatform.fuchsia:
22492262
case TargetPlatform.linux:
@@ -2291,10 +2304,18 @@ class TextSelectionGestureDetectorBuilder {
22912304
switch (defaultTargetPlatform) {
22922305
case TargetPlatform.iOS:
22932306
case TargetPlatform.macOS:
2294-
renderEditable.selectPositionAt(
2295-
from: details.globalPosition,
2296-
cause: SelectionChangedCause.longPress,
2297-
);
2307+
if (_longPressStartedWithoutFocus) {
2308+
renderEditable.selectWordsInRange(
2309+
from: details.globalPosition - details.offsetFromOrigin - editableOffset - scrollableOffset,
2310+
to: details.globalPosition,
2311+
cause: SelectionChangedCause.longPress,
2312+
);
2313+
} else {
2314+
renderEditable.selectPositionAt(
2315+
from: details.globalPosition,
2316+
cause: SelectionChangedCause.longPress,
2317+
);
2318+
}
22982319
case TargetPlatform.android:
22992320
case TargetPlatform.fuchsia:
23002321
case TargetPlatform.linux:
@@ -2342,6 +2363,7 @@ class TextSelectionGestureDetectorBuilder {
23422363
if (shouldShowSelectionToolbar) {
23432364
editableText.showToolbar();
23442365
}
2366+
_longPressStartedWithoutFocus = false;
23452367
_dragStartViewportOffset = 0.0;
23462368
_dragStartScrollOffset = 0.0;
23472369
}

packages/flutter/test/cupertino/text_field_test.dart

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1591,6 +1591,7 @@ void main() {
15911591
home: Column(
15921592
children: <Widget>[
15931593
CupertinoTextField(
1594+
autofocus: true,
15941595
controller: controller,
15951596
toolbarOptions: const ToolbarOptions(copy: true),
15961597
),
@@ -1599,6 +1600,9 @@ void main() {
15991600
),
16001601
);
16011602

1603+
// This extra pump is so autofocus can propagate to renderEditable.
1604+
await tester.pump();
1605+
16021606
// Long press to put the cursor after the "w".
16031607
const int index = 3;
16041608
await tester.longPressAt(textOffsetToPosition(tester, index));
@@ -2060,12 +2064,16 @@ void main() {
20602064
CupertinoApp(
20612065
home: Center(
20622066
child: CupertinoTextField(
2067+
autofocus: true,
20632068
controller: controller,
20642069
),
20652070
),
20662071
),
20672072
);
20682073

2074+
// This extra pump is so autofocus can propagate to renderEditable.
2075+
await tester.pump();
2076+
20692077
// Long press to put the cursor after the "w".
20702078
const int index = 3;
20712079
await tester.longPressAt(textOffsetToPosition(tester, index));
@@ -2830,12 +2838,16 @@ void main() {
28302838
CupertinoApp(
28312839
home: Center(
28322840
child: CupertinoTextField(
2841+
autofocus: true,
28332842
controller: controller,
28342843
),
28352844
),
28362845
),
28372846
);
28382847

2848+
// This extra pump is so autofocus can propagate to renderEditable.
2849+
await tester.pump();
2850+
28392851
final Offset textFieldStart = tester.getTopLeft(find.byType(CupertinoTextField));
28402852

28412853
await tester.longPressAt(textFieldStart + const Offset(50.0, 5.0));
@@ -2870,12 +2882,16 @@ void main() {
28702882
CupertinoApp(
28712883
home: Center(
28722884
child: CupertinoTextField(
2885+
autofocus: true,
28732886
controller: controller,
28742887
),
28752888
),
28762889
),
28772890
);
28782891

2892+
// This extra pump is so autofocus can propagate to renderEditable.
2893+
await tester.pump();
2894+
28792895
final Offset ePos = textOffsetToPosition(tester, 6); // Index of 'Atwate|r'
28802896

28812897
await tester.longPressAt(ePos);
@@ -2971,7 +2987,7 @@ void main() {
29712987
);
29722988

29732989
testWidgets(
2974-
'long press drag moves the cursor under the drag and shows toolbar on lift on Apple platforms',
2990+
'long press drag on a focused TextField moves the cursor under the drag and shows toolbar on lift on Apple platforms',
29752991
(WidgetTester tester) async {
29762992
final TextEditingController controller = TextEditingController(
29772993
text: 'Atwater Peel Sherbrooke Bonaventure',
@@ -2980,12 +2996,16 @@ void main() {
29802996
CupertinoApp(
29812997
home: Center(
29822998
child: CupertinoTextField(
2999+
autofocus: true,
29833000
controller: controller,
29843001
),
29853002
),
29863003
),
29873004
);
29883005

3006+
// This extra pump is so autofocus can propagate to renderEditable.
3007+
await tester.pump();
3008+
29893009
final Offset textFieldStart = tester.getTopLeft(find.byType(CupertinoTextField));
29903010

29913011
final TestGesture gesture =
@@ -3135,12 +3155,16 @@ void main() {
31353155
CupertinoApp(
31363156
home: Center(
31373157
child: CupertinoTextField(
3158+
autofocus: true,
31383159
controller: controller,
31393160
),
31403161
),
31413162
),
31423163
);
31433164

3165+
// This extra pump is so autofocus can propagate to renderEditable.
3166+
await tester.pump();
3167+
31443168
final RenderEditable renderEditable = tester.renderObject<RenderEditable>(
31453169
find.byElementPredicate((Element element) => element.renderObject is RenderEditable).last,
31463170
);
@@ -3289,12 +3313,16 @@ void main() {
32893313
CupertinoApp(
32903314
home: Center(
32913315
child: CupertinoTextField(
3316+
autofocus: true,
32923317
controller: controller,
32933318
),
32943319
),
32953320
),
32963321
);
32973322

3323+
// This extra pump is so autofocus can propagate to renderEditable.
3324+
await tester.pump();
3325+
32983326
// Use a position higher than wPos to avoid tapping the context menu on
32993327
// desktop.
33003328
final Offset pPos = textOffsetToPosition(tester, 9) + const Offset(0.0, -20.0); // Index of 'P|eel'
@@ -7313,6 +7341,7 @@ void main() {
73137341
child: Column(
73147342
children: <Widget>[
73157343
CupertinoTextField(
7344+
autofocus: true,
73167345
key: const Key('field0'),
73177346
controller: controller,
73187347
style: const TextStyle(height: 4, color: ui.Color.fromARGB(100, 0, 0, 0)),
@@ -7329,6 +7358,9 @@ void main() {
73297358
),
73307359
);
73317360

7361+
// This extra pump is so autofocus can propagate to renderEditable.
7362+
await tester.pump();
7363+
73327364
final Offset textFieldStart = tester.getTopLeft(find.byKey(const Key('field0')));
73337365

73347366
await tester.longPressAt(textFieldStart + const Offset(50.0, 2.0));
@@ -7363,6 +7395,7 @@ void main() {
73637395
child: Column(
73647396
children: <Widget>[
73657397
CupertinoTextField(
7398+
autofocus: true,
73667399
key: const Key('field0'),
73677400
controller: controller,
73687401
style: const TextStyle(height: 4, color: ui.Color.fromARGB(100, 0, 0, 0)),
@@ -7378,6 +7411,9 @@ void main() {
73787411
),
73797412
);
73807413

7414+
// This extra pump is so autofocus can propagate to renderEditable.
7415+
await tester.pump();
7416+
73817417
final Offset textFieldStart = tester.getTopLeft(find.byKey(const Key('field0')));
73827418

73837419
await tester.longPressAt(textFieldStart + const Offset(50.0, 2.0));

packages/flutter/test/cupertino/text_selection_test.dart

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,13 +200,17 @@ void main() {
200200
data: const MediaQueryData(size: Size(800.0, 600.0)),
201201
child: Center(
202202
child: CupertinoTextField(
203+
autofocus: true,
203204
controller: controller,
204205
),
205206
),
206207
),
207208
),
208209
));
209210

211+
// This extra pump is so autofocus can propagate to renderEditable.
212+
await tester.pump();
213+
210214
// Initially, the menu isn't shown at all.
211215
expect(find.text('Cut'), findsNothing);
212216
expect(find.text('Copy'), findsNothing);
@@ -432,13 +436,17 @@ void main() {
432436
data: const MediaQueryData(size: Size(800.0, 600.0)),
433437
child: Center(
434438
child: CupertinoTextField(
439+
autofocus: true,
435440
controller: controller,
436441
),
437442
),
438443
),
439444
),
440445
));
441446

447+
// This extra pump is so autofocus can propagate to renderEditable.
448+
await tester.pump();
449+
442450
// Initially, the menu isn't shown at all.
443451
expect(find.text(_longLocalizations.cutButtonLabel), findsNothing);
444452
expect(find.text(_longLocalizations.copyButtonLabel), findsNothing);
@@ -546,6 +554,7 @@ void main() {
546554
data: const MediaQueryData(size: Size(800.0, 600.0)),
547555
child: Center(
548556
child: CupertinoTextField(
557+
autofocus: true,
549558
padding: const EdgeInsets.all(8.0),
550559
controller: controller,
551560
maxLines: 2,
@@ -555,6 +564,9 @@ void main() {
555564
),
556565
));
557566

567+
// This extra pump is so autofocus can propagate to renderEditable.
568+
await tester.pump();
569+
558570
// Initially, the menu isn't shown at all.
559571
expect(find.text('Cut'), findsNothing);
560572
expect(find.text('Copy'), findsNothing);

0 commit comments

Comments
 (0)