Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions packages/flutter/lib/src/cupertino/text_field.dart
Original file line number Diff line number Diff line change
Expand Up @@ -808,6 +808,9 @@ class CupertinoTextField extends StatefulWidget {
BuildContext context,
EditableTextState editableTextState,
) {
if (defaultTargetPlatform == TargetPlatform.iOS && SystemContextMenu.isSupported(context)) {
return SystemContextMenu.editableText(editableTextState: editableTextState);
}
return CupertinoAdaptiveTextSelectionToolbar.editableText(editableTextState: editableTextState);
}

Expand Down
4 changes: 4 additions & 0 deletions packages/flutter/lib/src/cupertino/text_form_field_row.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';

Expand Down Expand Up @@ -271,6 +272,9 @@ class CupertinoTextFormFieldRow extends FormField<String> {
BuildContext context,
EditableTextState editableTextState,
) {
if (defaultTargetPlatform == TargetPlatform.iOS && SystemContextMenu.isSupported(context)) {
return SystemContextMenu.editableText(editableTextState: editableTextState);
}
return CupertinoAdaptiveTextSelectionToolbar.editableText(editableTextState: editableTextState);
}

Expand Down
4 changes: 4 additions & 0 deletions packages/flutter/lib/src/material/search_anchor.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import 'dart:async';
import 'dart:math' as math;
import 'dart:ui';

import 'package:flutter/foundation.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';

Expand Down Expand Up @@ -1522,6 +1523,9 @@ class SearchBar extends StatefulWidget {
BuildContext context,
EditableTextState editableTextState,
) {
if (defaultTargetPlatform == TargetPlatform.iOS && SystemContextMenu.isSupported(context)) {
return SystemContextMenu.editableText(editableTextState: editableTextState);
}
return AdaptiveTextSelectionToolbar.editableText(editableTextState: editableTextState);
}

Expand Down
3 changes: 3 additions & 0 deletions packages/flutter/lib/src/material/selectable_text.dart
Original file line number Diff line number Diff line change
Expand Up @@ -447,6 +447,9 @@ class SelectableText extends StatefulWidget {
BuildContext context,
EditableTextState editableTextState,
) {
if (defaultTargetPlatform == TargetPlatform.iOS && SystemContextMenu.isSupported(context)) {
return SystemContextMenu.editableText(editableTextState: editableTextState);
}
return AdaptiveTextSelectionToolbar.editableText(editableTextState: editableTextState);
}

Expand Down
3 changes: 3 additions & 0 deletions packages/flutter/lib/src/material/text_field.dart
Original file line number Diff line number Diff line change
Expand Up @@ -849,6 +849,9 @@ class TextField extends StatefulWidget {
BuildContext context,
EditableTextState editableTextState,
) {
if (defaultTargetPlatform == TargetPlatform.iOS && SystemContextMenu.isSupported(context)) {
return SystemContextMenu.editableText(editableTextState: editableTextState);
}
return AdaptiveTextSelectionToolbar.editableText(editableTextState: editableTextState);
}

Expand Down
4 changes: 4 additions & 0 deletions packages/flutter/lib/src/material/text_form_field.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import 'dart:ui' as ui show BoxHeightStyle, BoxWidthStyle;

import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
Expand Down Expand Up @@ -326,6 +327,9 @@ class TextFormField extends FormField<String> {
BuildContext context,
EditableTextState editableTextState,
) {
if (defaultTargetPlatform == TargetPlatform.iOS && SystemContextMenu.isSupported(context)) {
return SystemContextMenu.editableText(editableTextState: editableTextState);
}
return AdaptiveTextSelectionToolbar.editableText(editableTextState: editableTextState);
}

Expand Down
60 changes: 54 additions & 6 deletions packages/flutter/test/cupertino/text_field_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,7 @@ import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';

import '../widgets/clipboard_utils.dart';
import '../widgets/editable_text_utils.dart'
show OverflowWidgetTextEditingController, isContextMenuProvidedByPlatform;
import '../widgets/editable_text_utils.dart';
import '../widgets/live_text_utils.dart';
import '../widgets/semantics_tester.dart';
import '../widgets/text_selection_toolbar_utils.dart';
Expand Down Expand Up @@ -238,10 +237,6 @@ void main() {
return endpoints[0].point;
}

// Web has a less threshold for downstream/upstream text position.
Offset textOffsetToPosition(WidgetTester tester, int offset) =>
textOffsetToBottomLeftPosition(tester, offset) + const Offset(kIsWeb ? 1 : 0, -2);

setUp(() async {
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(
SystemChannels.platform,
Expand Down Expand Up @@ -8840,6 +8835,39 @@ void main() {
},
skip: kIsWeb, // [intended] on web the browser handles the context menu.
);

testWidgets(
'iOS uses the system context menu by default if supported',
(WidgetTester tester) async {
TestWidgetsFlutterBinding.instance.platformDispatcher.supportsShowingSystemContextMenu =
true;
_updateMediaQueryFromView(tester);
addTearDown(() {
TestWidgetsFlutterBinding.instance.platformDispatcher
.resetSupportsShowingSystemContextMenu();
_updateMediaQueryFromView(tester);
});

final TextEditingController controller = TextEditingController(text: 'one two three');
addTearDown(controller.dispose);
await tester.pumpWidget(CupertinoApp(home: CupertinoTextField(controller: controller)));

// No context menu shown.
expect(find.byType(CupertinoAdaptiveTextSelectionToolbar), findsNothing);
expect(find.byType(SystemContextMenu), findsNothing);

// Double tap to select the first word and show the menu.
await tester.tapAt(textOffsetToPosition(tester, 1));
await tester.pump(const Duration(milliseconds: 50));
await tester.tapAt(textOffsetToPosition(tester, 1));
await tester.pump(SelectionOverlay.fadeDuration);

expect(find.byType(CupertinoAdaptiveTextSelectionToolbar), findsNothing);
expect(find.byType(SystemContextMenu), findsOneWidget);
},
skip: kIsWeb, // [intended] on web the browser handles the context menu.
variant: TargetPlatformVariant.only(TargetPlatform.iOS),
);
});

group('magnifier', () {
Expand Down Expand Up @@ -9880,3 +9908,23 @@ void main() {
variant: TargetPlatformVariant.all(),
);
}

// Trigger MediaQuery to update itself based on the View, which is not
// recreated between tests. This is necessary when changing something on
// TestPlatformDispatcher and expecting it to be picked up by MediaQuery.
// TODO(justinmc): This hack can be removed if
// https://github.com/flutter/flutter/issues/165519 is fixed.
void _updateMediaQueryFromView(WidgetTester tester) {
expect(find.byType(MediaQuery), findsOneWidget);
final WidgetsBindingObserver widgetsBindingObserver =
tester.state(
find.ancestor(
of: find.byType(MediaQuery),
matching: find.byWidgetPredicate(
(Widget w) => '${w.runtimeType}' == '_MediaQueryFromView',
),
),
)
as WidgetsBindingObserver;
widgetsBindingObserver.didChangeMetrics();
}
58 changes: 58 additions & 0 deletions packages/flutter/test/cupertino/text_form_field_row_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,13 @@
// found in the LICENSE file.

import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/src/services/spell_check.dart';
import 'package:flutter_test/flutter_test.dart';

import '../widgets/editable_text_utils.dart';

void main() {
testWidgets('Passes textAlign to underlying CupertinoTextField', (WidgetTester tester) async {
const TextAlign alignment = TextAlign.center;
Expand Down Expand Up @@ -518,4 +521,59 @@ void main() {
expect(stateKey.currentState!.value, 'initialValue');
expect(value, 'initialValue');
});

group('context menu', () {
testWidgets(
'iOS uses the system context menu by default if supported',
(WidgetTester tester) async {
TestWidgetsFlutterBinding.instance.platformDispatcher.supportsShowingSystemContextMenu =
true;
_updateMediaQueryFromView(tester);
addTearDown(() {
TestWidgetsFlutterBinding.instance.platformDispatcher
.resetSupportsShowingSystemContextMenu();
_updateMediaQueryFromView(tester);
});

final TextEditingController controller = TextEditingController(text: 'one two three');
addTearDown(controller.dispose);
await tester.pumpWidget(CupertinoApp(home: CupertinoTextField(controller: controller)));

// No context menu shown.
expect(find.byType(CupertinoAdaptiveTextSelectionToolbar), findsNothing);
expect(find.byType(SystemContextMenu), findsNothing);

// Double tap to select the first word and show the menu.
await tester.tapAt(textOffsetToPosition(tester, 1));
await tester.pump(const Duration(milliseconds: 50));
await tester.tapAt(textOffsetToPosition(tester, 1));
await tester.pump(SelectionOverlay.fadeDuration);

expect(find.byType(CupertinoAdaptiveTextSelectionToolbar), findsNothing);
expect(find.byType(SystemContextMenu), findsOneWidget);
},
skip: kIsWeb, // [intended] on web the browser handles the context menu.
variant: TargetPlatformVariant.only(TargetPlatform.iOS),
);
});
}

// Trigger MediaQuery to update itself based on the View, which is not
// recreated between tests. This is necessary when changing something on
// TestPlatformDispatcher and expecting it to be picked up by MediaQuery.
// TODO(justinmc): This hack can be removed if
// https://github.com/flutter/flutter/issues/165519 is fixed.
void _updateMediaQueryFromView(WidgetTester tester) {
expect(find.byType(MediaQuery), findsOneWidget);
final WidgetsBindingObserver widgetsBindingObserver =
tester.state(
find.ancestor(
of: find.byType(MediaQuery),
matching: find.byWidgetPredicate(
(Widget w) => '${w.runtimeType}' == '_MediaQueryFromView',
),
),
)
as WidgetsBindingObserver;
widgetsBindingObserver.didChangeMetrics();
}
55 changes: 55 additions & 0 deletions packages/flutter/test/material/search_anchor_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3687,6 +3687,41 @@ void main() {

expect(find.byType(Placeholder), findsOneWidget);
});

testWidgets(
'iOS uses the system context menu by default if supported',
(WidgetTester tester) async {
TestWidgetsFlutterBinding.instance.platformDispatcher.supportsShowingSystemContextMenu =
true;
_updateMediaQueryFromView(tester);
addTearDown(() {
TestWidgetsFlutterBinding.instance.platformDispatcher
.resetSupportsShowingSystemContextMenu();
_updateMediaQueryFromView(tester);
});

final TextEditingController controller = TextEditingController(text: 'one two three');
addTearDown(controller.dispose);
await tester.pumpWidget(
MaterialApp(home: Material(child: TextField(controller: controller))),
);

// No context menu shown.
expect(find.byType(AdaptiveTextSelectionToolbar), findsNothing);
expect(find.byType(SystemContextMenu), findsNothing);

// Double tap to select the first word and show the menu.
await tester.tapAt(textOffsetToPosition(tester, 1));
await tester.pump(const Duration(milliseconds: 50));
await tester.tapAt(textOffsetToPosition(tester, 1));
await tester.pump(SelectionOverlay.fadeDuration);

expect(find.byType(AdaptiveTextSelectionToolbar), findsNothing);
expect(find.byType(SystemContextMenu), findsOneWidget);
},
skip: kIsWeb, // [intended] on web the browser handles the context menu.
variant: TargetPlatformVariant.only(TargetPlatform.iOS),
);
});

testWidgets('SearchAnchor does not dispose external SearchController', (
Expand Down Expand Up @@ -4011,3 +4046,23 @@ Material getSearchViewMaterial(WidgetTester tester) {
find.descendant(of: findViewContent(), matching: find.byType(Material)).first,
);
}

// Trigger MediaQuery to update itself based on the View, which is not
// recreated between tests. This is necessary when changing something on
// TestPlatformDispatcher and expecting it to be picked up by MediaQuery.
// TODO(justinmc): This hack can be removed if
// https://github.com/flutter/flutter/issues/165519 is fixed.
void _updateMediaQueryFromView(WidgetTester tester) {
expect(find.byType(MediaQuery), findsOneWidget);
final WidgetsBindingObserver widgetsBindingObserver =
tester.state(
find.ancestor(
of: find.byType(MediaQuery),
matching: find.byWidgetPredicate(
(Widget w) => '${w.runtimeType}' == '_MediaQueryFromView',
),
),
)
as WidgetsBindingObserver;
widgetsBindingObserver.didChangeMetrics();
}
57 changes: 57 additions & 0 deletions packages/flutter/test/material/text_field_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16147,6 +16147,43 @@ void main() {
},
skip: kIsWeb, // [intended] on web the browser handles the context menu.
);

testWidgets(
'iOS uses the system context menu by default if supported',
(WidgetTester tester) async {
TestWidgetsFlutterBinding.instance.platformDispatcher.supportsShowingSystemContextMenu =
true;
_updateMediaQueryFromView(tester);
addTearDown(() {
TestWidgetsFlutterBinding.instance.platformDispatcher
.resetSupportsShowingSystemContextMenu();
_updateMediaQueryFromView(tester);
});

await tester.pumpWidget(
MaterialApp(
home: Material(
child: TextField(controller: _textEditingController(text: 'one two three')),
),
),
);

// No context menu shown.
expect(find.byType(AdaptiveTextSelectionToolbar), findsNothing);
expect(find.byType(SystemContextMenu), findsNothing);

// Double tap to select the first word and show the menu.
await tester.tapAt(textOffsetToPosition(tester, 1));
await tester.pump(const Duration(milliseconds: 50));
await tester.tapAt(textOffsetToPosition(tester, 1));
await tester.pump(SelectionOverlay.fadeDuration);

expect(find.byType(AdaptiveTextSelectionToolbar), findsNothing);
expect(find.byType(SystemContextMenu), findsOneWidget);
},
skip: kIsWeb, // [intended] on web the browser handles the context menu.
variant: TargetPlatformVariant.only(TargetPlatform.iOS),
);
});

group('magnifier builder', () {
Expand Down Expand Up @@ -17668,3 +17705,23 @@ TextEditingController _textEditingController({String text = ''}) {
addTearDown(result.dispose);
return result;
}

// Trigger MediaQuery to update itself based on the View, which is not
// recreated between tests. This is necessary when changing something on
// TestPlatformDispatcher and expecting it to be picked up by MediaQuery.
// TODO(justinmc): This hack can be removed if
// https://github.com/flutter/flutter/issues/165519 is fixed.
void _updateMediaQueryFromView(WidgetTester tester) {
expect(find.byType(MediaQuery), findsOneWidget);
final WidgetsBindingObserver widgetsBindingObserver =
tester.state(
find.ancestor(
of: find.byType(MediaQuery),
matching: find.byWidgetPredicate(
(Widget w) => '${w.runtimeType}' == '_MediaQueryFromView',
),
),
)
as WidgetsBindingObserver;
widgetsBindingObserver.didChangeMetrics();
}
Loading