Skip to content

Commit b37e7aa

Browse files
authored
[ios][secure_paste]show menu item based on info sent from framework (flutter#161103)
This is moved from flutter/engine#56362 *List which issues are fixed by this PR. You must list at least one issue. An issue is not required if the PR fixes something trivial like a typo.* *If you had to change anything in the [flutter/tests] repo, include a link to the migration guide as per the [breaking change policy].* ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [x] I listed at least one issue that this PR fixes in the description above. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [x] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. <!-- Links --> [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
1 parent 6a412a5 commit b37e7aa

File tree

5 files changed

+404
-3
lines changed

5 files changed

+404
-3
lines changed

engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1050,6 +1050,21 @@ - (void)flutterTextInputView:(FlutterTextInputView*)textInputView
10501050
arguments:@[ @(client) ]];
10511051
}
10521052

1053+
- (void)flutterTextInputView:(FlutterTextInputView*)textInputView
1054+
shareSelectedText:(NSString*)selectedText {
1055+
[self.platformPlugin showShareViewController:selectedText];
1056+
}
1057+
1058+
- (void)flutterTextInputView:(FlutterTextInputView*)textInputView
1059+
searchWebWithSelectedText:(NSString*)selectedText {
1060+
[self.platformPlugin searchWeb:selectedText];
1061+
}
1062+
1063+
- (void)flutterTextInputView:(FlutterTextInputView*)textInputView
1064+
lookUpSelectedText:(NSString*)selectedText {
1065+
[self.platformPlugin showLookUpViewController:selectedText];
1066+
}
1067+
10531068
#pragma mark - FlutterViewEngineDelegate
10541069

10551070
- (void)flutterTextInputView:(FlutterTextInputView*)textInputView showToolbar:(int)client {

engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformPlugin.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@
1414
- (instancetype)initWithEngine:(FlutterEngine*)engine NS_DESIGNATED_INITIALIZER;
1515
- (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result;
1616

17+
- (void)showShareViewController:(NSString*)content;
18+
- (void)searchWeb:(NSString*)searchTerm;
19+
- (void)showLookUpViewController:(NSString*)term;
1720
@end
1821

1922
namespace flutter {

engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputDelegate.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,12 @@ typedef NS_ENUM(NSInteger, FlutterFloatingCursorDragState) {
6767
didResignFirstResponderWithTextInputClient:(int)client;
6868
- (void)flutterTextInputView:(FlutterTextInputView*)textInputView
6969
willDismissEditMenuWithTextInputClient:(int)client;
70+
- (void)flutterTextInputView:(FlutterTextInputView*)textInputView
71+
shareSelectedText:(NSString*)selectedText;
72+
- (void)flutterTextInputView:(FlutterTextInputView*)textInputView
73+
searchWebWithSelectedText:(NSString*)selectedText;
74+
- (void)flutterTextInputView:(FlutterTextInputView*)textInputView
75+
lookUpSelectedText:(NSString*)selectedText;
7076
@end
7177

7278
#endif // FLUTTER_SHELL_PLATFORM_DARWIN_IOS_FRAMEWORK_SOURCE_FLUTTERTEXTINPUTDELEGATE_H_

engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm

Lines changed: 119 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -802,6 +802,7 @@ @interface FlutterTextInputView ()
802802
// etc)
803803
@property(nonatomic, copy) NSString* temporarilyDeletedComposedCharacter;
804804
@property(nonatomic, assign) CGRect editMenuTargetRect;
805+
@property(nonatomic, strong) NSArray<NSDictionary*>* editMenuItems;
805806

806807
- (void)setEditableTransform:(NSArray*)matrix;
807808
@end
@@ -875,10 +876,123 @@ - (instancetype)initWithOwner:(FlutterTextInputPlugin*)textInputPlugin {
875876
return self;
876877
}
877878

879+
- (void)handleSearchWebAction {
880+
[self.textInputDelegate flutterTextInputView:self
881+
searchWebWithSelectedText:[self textInRange:_selectedTextRange]];
882+
}
883+
884+
- (void)handleLookUpAction {
885+
[self.textInputDelegate flutterTextInputView:self
886+
lookUpSelectedText:[self textInRange:_selectedTextRange]];
887+
}
888+
889+
- (void)handleShareAction {
890+
[self.textInputDelegate flutterTextInputView:self
891+
shareSelectedText:[self textInRange:_selectedTextRange]];
892+
}
893+
894+
// DFS algorithm to search a UICommand from the menu tree.
895+
- (UICommand*)searchCommandWithSelector:(SEL)selector
896+
element:(UIMenuElement*)element API_AVAILABLE(ios(16.0)) {
897+
if ([element isKindOfClass:UICommand.class]) {
898+
UICommand* command = (UICommand*)element;
899+
return command.action == selector ? command : nil;
900+
} else if ([element isKindOfClass:UIMenu.class]) {
901+
NSArray<UIMenuElement*>* children = ((UIMenu*)element).children;
902+
for (UIMenuElement* child in children) {
903+
UICommand* result = [self searchCommandWithSelector:selector element:child];
904+
if (result) {
905+
return result;
906+
}
907+
}
908+
return nil;
909+
} else {
910+
return nil;
911+
}
912+
}
913+
914+
- (void)addBasicEditingCommandToItems:(NSMutableArray*)items
915+
type:(NSString*)type
916+
selector:(SEL)selector
917+
suggestedMenu:(UIMenu*)suggestedMenu {
918+
UICommand* command = [self searchCommandWithSelector:selector element:suggestedMenu];
919+
if (command) {
920+
[items addObject:command];
921+
} else {
922+
FML_LOG(ERROR) << "Cannot find context menu item of type \"" << type.UTF8String << "\".";
923+
}
924+
}
925+
926+
- (void)addAdditionalBasicCommandToItems:(NSMutableArray*)items
927+
type:(NSString*)type
928+
selector:(SEL)selector
929+
encodedItem:(NSDictionary<NSString*, id>*)encodedItem {
930+
NSString* title = encodedItem[@"title"];
931+
if (title) {
932+
UICommand* command = [UICommand commandWithTitle:title
933+
image:nil
934+
action:selector
935+
propertyList:nil];
936+
[items addObject:command];
937+
} else {
938+
FML_LOG(ERROR) << "Missing title for context menu item of type \"" << type.UTF8String << "\".";
939+
}
940+
}
941+
878942
- (UIMenu*)editMenuInteraction:(UIEditMenuInteraction*)interaction
879943
menuForConfiguration:(UIEditMenuConfiguration*)configuration
880944
suggestedActions:(NSArray<UIMenuElement*>*)suggestedActions API_AVAILABLE(ios(16.0)) {
881-
return [UIMenu menuWithChildren:suggestedActions];
945+
UIMenu* suggestedMenu = [UIMenu menuWithChildren:suggestedActions];
946+
if (!_editMenuItems) {
947+
return suggestedMenu;
948+
}
949+
950+
NSMutableArray* items = [NSMutableArray array];
951+
for (NSDictionary<NSString*, id>* encodedItem in _editMenuItems) {
952+
NSString* type = encodedItem[@"type"];
953+
if ([type isEqualToString:@"copy"]) {
954+
[self addBasicEditingCommandToItems:items
955+
type:type
956+
selector:@selector(copy:)
957+
suggestedMenu:suggestedMenu];
958+
} else if ([type isEqualToString:@"paste"]) {
959+
[self addBasicEditingCommandToItems:items
960+
type:type
961+
selector:@selector(paste:)
962+
suggestedMenu:suggestedMenu];
963+
} else if ([type isEqualToString:@"cut"]) {
964+
[self addBasicEditingCommandToItems:items
965+
type:type
966+
selector:@selector(cut:)
967+
suggestedMenu:suggestedMenu];
968+
} else if ([type isEqualToString:@"delete"]) {
969+
[self addBasicEditingCommandToItems:items
970+
type:type
971+
selector:@selector(delete:)
972+
suggestedMenu:suggestedMenu];
973+
} else if ([type isEqualToString:@"selectAll"]) {
974+
[self addBasicEditingCommandToItems:items
975+
type:type
976+
selector:@selector(selectAll:)
977+
suggestedMenu:suggestedMenu];
978+
} else if ([type isEqualToString:@"searchWeb"]) {
979+
[self addAdditionalBasicCommandToItems:items
980+
type:type
981+
selector:@selector(handleSearchWebAction)
982+
encodedItem:encodedItem];
983+
} else if ([type isEqualToString:@"share"]) {
984+
[self addAdditionalBasicCommandToItems:items
985+
type:type
986+
selector:@selector(handleShareAction)
987+
encodedItem:encodedItem];
988+
} else if ([type isEqualToString:@"lookUp"]) {
989+
[self addAdditionalBasicCommandToItems:items
990+
type:type
991+
selector:@selector(handleLookUpAction)
992+
encodedItem:encodedItem];
993+
}
994+
}
995+
return [UIMenu menuWithChildren:items];
882996
}
883997

884998
- (void)editMenuInteraction:(UIEditMenuInteraction*)interaction
@@ -894,8 +1008,10 @@ - (CGRect)editMenuInteraction:(UIEditMenuInteraction*)interaction
8941008
return _editMenuTargetRect;
8951009
}
8961010

897-
- (void)showEditMenuWithTargetRect:(CGRect)targetRect API_AVAILABLE(ios(16.0)) {
1011+
- (void)showEditMenuWithTargetRect:(CGRect)targetRect
1012+
items:(NSArray<NSDictionary*>*)items API_AVAILABLE(ios(16.0)) {
8981013
_editMenuTargetRect = targetRect;
1014+
_editMenuItems = items;
8991015
UIEditMenuConfiguration* config =
9001016
[UIEditMenuConfiguration configurationWithIdentifier:nil sourcePoint:CGPointZero];
9011017
[self.editMenuInteraction presentEditMenuWithConfiguration:config];
@@ -2574,7 +2690,7 @@ - (BOOL)showEditMenu:(NSDictionary*)args API_AVAILABLE(ios(16.0)) {
25742690
[encodedTargetRect[@"x"] doubleValue], [encodedTargetRect[@"y"] doubleValue],
25752691
[encodedTargetRect[@"width"] doubleValue], [encodedTargetRect[@"height"] doubleValue]);
25762692
CGRect localTargetRect = [self.hostView convertRect:globalTargetRect toView:self.activeView];
2577-
[self.activeView showEditMenuWithTargetRect:localTargetRect];
2693+
[self.activeView showEditMenuWithTargetRect:localTargetRect items:args[@"items"]];
25782694
return YES;
25792695
}
25802696

0 commit comments

Comments
 (0)