mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
Fix/ios share context menu (#176199)
This PR fixes https://github.com/flutter/flutter/issues/173491 by adding the missing 'Share' option to the default iOS SystemContextMenu when shareEnabled is true. Changes: Added IOSSystemContextMenuItemShare to getDefaultItems in system_context_menu.dart. Added a widget test to ensure Share is present in the default items for non-empty selections on iOS. Rationale: This aligns Flutter's default iOS text selection context menu with native iOS behavior, ensuring users see the expected 'Share' option when selecting text. Demo: Video showing Share option in iOS context menu: https://github.com/user-attachments/assets/e04cd1f9-7d92-4147-a09b-719f03d9c625
This commit is contained in:
parent
59868c551d
commit
eb7fb9a3d7
@ -10,6 +10,7 @@ import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
import 'basic.dart';
|
||||
import 'context_menu_button_item.dart';
|
||||
import 'editable_text.dart';
|
||||
import 'framework.dart';
|
||||
import 'localizations.dart';
|
||||
@ -156,15 +157,36 @@ class SystemContextMenu extends StatefulWidget {
|
||||
/// * [EditableTextState.contextMenuButtonItems], which provides the default
|
||||
/// [ContextMenuButtonItem]s for the Flutter-rendered context menu.
|
||||
static List<IOSSystemContextMenuItem> getDefaultItems(EditableTextState editableTextState) {
|
||||
return <IOSSystemContextMenuItem>[
|
||||
if (editableTextState.copyEnabled) const IOSSystemContextMenuItemCopy(),
|
||||
if (editableTextState.cutEnabled) const IOSSystemContextMenuItemCut(),
|
||||
if (editableTextState.pasteEnabled) const IOSSystemContextMenuItemPaste(),
|
||||
if (editableTextState.selectAllEnabled) const IOSSystemContextMenuItemSelectAll(),
|
||||
if (editableTextState.lookUpEnabled) const IOSSystemContextMenuItemLookUp(),
|
||||
if (editableTextState.searchWebEnabled) const IOSSystemContextMenuItemSearchWeb(),
|
||||
if (editableTextState.liveTextInputEnabled) const IOSSystemContextMenuItemLiveText(),
|
||||
];
|
||||
final items = <IOSSystemContextMenuItem>[];
|
||||
|
||||
// Use the generic Flutter-rendered context menu model as the single source of truth.
|
||||
for (final ContextMenuButtonItem button in editableTextState.contextMenuButtonItems) {
|
||||
switch (button.type) {
|
||||
case ContextMenuButtonType.copy:
|
||||
items.add(const IOSSystemContextMenuItemCopy());
|
||||
case ContextMenuButtonType.cut:
|
||||
items.add(const IOSSystemContextMenuItemCut());
|
||||
case ContextMenuButtonType.paste:
|
||||
items.add(const IOSSystemContextMenuItemPaste());
|
||||
case ContextMenuButtonType.selectAll:
|
||||
items.add(const IOSSystemContextMenuItemSelectAll());
|
||||
case ContextMenuButtonType.lookUp:
|
||||
items.add(const IOSSystemContextMenuItemLookUp());
|
||||
case ContextMenuButtonType.searchWeb:
|
||||
items.add(const IOSSystemContextMenuItemSearchWeb());
|
||||
case ContextMenuButtonType.share:
|
||||
items.add(const IOSSystemContextMenuItemShare());
|
||||
case ContextMenuButtonType.liveTextInput:
|
||||
items.add(const IOSSystemContextMenuItemLiveText());
|
||||
case ContextMenuButtonType.delete:
|
||||
// No native iOS system menu button for Delete — intentionally ignored.
|
||||
case ContextMenuButtonType.custom:
|
||||
// Custom items are provided explicitly via SystemContextMenu.items,
|
||||
// not via defaults. Intentionally ignore in default mapping.
|
||||
}
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@ -1332,4 +1332,74 @@ void main() {
|
||||
skip: kIsWeb, // [intended]
|
||||
variant: TargetPlatformVariant.only(TargetPlatform.iOS),
|
||||
);
|
||||
|
||||
testWidgets(
|
||||
'Default iOS SystemContextMenu includes Share for non-empty selection',
|
||||
(WidgetTester tester) async {
|
||||
final itemsReceived = <List<IOSSystemContextMenuItemData>>[];
|
||||
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(
|
||||
SystemChannels.platform,
|
||||
(MethodCall methodCall) async {
|
||||
if (methodCall.method == 'ContextMenu.showSystemContextMenu') {
|
||||
final arguments = methodCall.arguments as Map<String, dynamic>;
|
||||
final untypedItems = arguments['items'] as List<dynamic>;
|
||||
final lastItems = <IOSSystemContextMenuItemData>[
|
||||
for (final dynamic value in untypedItems)
|
||||
systemContextMenuItemDataFromJson(value as Map<String, dynamic>),
|
||||
];
|
||||
itemsReceived.add(lastItems);
|
||||
}
|
||||
return;
|
||||
},
|
||||
);
|
||||
addTearDown(() {
|
||||
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(
|
||||
SystemChannels.platform,
|
||||
null,
|
||||
);
|
||||
});
|
||||
|
||||
final controller = TextEditingController(text: 'Hello world');
|
||||
addTearDown(controller.dispose);
|
||||
await tester.pumpWidget(
|
||||
MediaQuery(
|
||||
data: const MediaQueryData(supportsShowingSystemContextMenu: true),
|
||||
child: MaterialApp(
|
||||
home: Scaffold(
|
||||
body: Center(
|
||||
child: TextField(
|
||||
controller: controller,
|
||||
contextMenuBuilder: (BuildContext context, EditableTextState editableTextState) {
|
||||
return SystemContextMenu.editableText(editableTextState: editableTextState);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// Focus the field first (this establishes the TextInputConnection).
|
||||
await tester.tap(find.byType(TextField));
|
||||
await tester.pump();
|
||||
final EditableTextState state = tester.state<EditableTextState>(find.byType(EditableText));
|
||||
|
||||
// Set a non-empty selection to enable sharing.
|
||||
controller.selection = const TextSelection(baseOffset: 0, extentOffset: 5); // "Hello"
|
||||
await tester.pump();
|
||||
|
||||
// Nit: ensure no platform message sent before showing toolbar.
|
||||
expect(itemsReceived, isEmpty);
|
||||
|
||||
// Show the context menu.
|
||||
expect(state.showToolbar(), true);
|
||||
await tester.pump();
|
||||
|
||||
// Assert that the platform message included a Share item.
|
||||
expect(itemsReceived, isNotEmpty);
|
||||
expect(itemsReceived.last, contains(isA<IOSSystemContextMenuItemDataShare>()));
|
||||
},
|
||||
skip: kIsWeb, // [intended]
|
||||
variant: TargetPlatformVariant.only(TargetPlatform.iOS),
|
||||
);
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user