Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.
Closed
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
15 changes: 15 additions & 0 deletions shell/platform/darwin/ios/framework/Source/FlutterEngine.mm
Original file line number Diff line number Diff line change
Expand Up @@ -1013,6 +1013,21 @@ - (void)flutterTextInputView:(FlutterTextInputView*)textInputView
arguments:@[ @(client) ]];
}

- (void)flutterTextInputView:(FlutterTextInputView*)textInputView
shareSelectedText:(NSString*)selectedText {
[self.platformPlugin showShareViewController:selectedText];
}

- (void)flutterTextInputView:(FlutterTextInputView*)textInputView
searchWebWithSelectedText:(NSString*)selectedText {
[self.platformPlugin searchWeb:selectedText];
}

- (void)flutterTextInputView:(FlutterTextInputView*)textInputView
lookUpSelectedText:(NSString*)selectedText {
[self.platformPlugin showLookUpViewController:selectedText];
}

#pragma mark - FlutterViewEngineDelegate

- (void)flutterTextInputView:(FlutterTextInputView*)textInputView showToolbar:(int)client {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@
- (instancetype)initWithEngine:(FlutterEngine*)engine NS_DESIGNATED_INITIALIZER;
- (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result;

- (void)showShareViewController:(NSString*)content;
- (void)searchWeb:(NSString*)searchTerm;
- (void)showLookUpViewController:(NSString*)term;
@end

namespace flutter {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,12 @@ typedef NS_ENUM(NSInteger, FlutterFloatingCursorDragState) {
didResignFirstResponderWithTextInputClient:(int)client;
- (void)flutterTextInputView:(FlutterTextInputView*)textInputView
willDismissEditMenuWithTextInputClient:(int)client;
- (void)flutterTextInputView:(FlutterTextInputView*)textInputView
shareSelectedText:(NSString*)selectedText;
- (void)flutterTextInputView:(FlutterTextInputView*)textInputView
searchWebWithSelectedText:(NSString*)selectedText;
- (void)flutterTextInputView:(FlutterTextInputView*)textInputView
lookUpSelectedText:(NSString*)selectedText;
@end

#endif // FLUTTER_SHELL_PLATFORM_DARWIN_IOS_FRAMEWORK_SOURCE_FLUTTERTEXTINPUTDELEGATE_H_
122 changes: 119 additions & 3 deletions shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm
Original file line number Diff line number Diff line change
Expand Up @@ -795,6 +795,7 @@ @interface FlutterTextInputView ()
// etc)
@property(nonatomic, copy) NSString* temporarilyDeletedComposedCharacter;
@property(nonatomic, assign) CGRect editMenuTargetRect;
@property(nonatomic, strong) NSArray<NSDictionary*>* editMenuItems;

- (void)setEditableTransform:(NSArray*)matrix;
@end
Expand Down Expand Up @@ -868,10 +869,123 @@ - (instancetype)initWithOwner:(FlutterTextInputPlugin*)textInputPlugin {
return self;
}

- (void)handleSearchWebAction {
[self.textInputDelegate flutterTextInputView:self
searchWebWithSelectedText:[self textInRange:_selectedTextRange]];
}

- (void)handleLookUpAction {
[self.textInputDelegate flutterTextInputView:self
lookUpSelectedText:[self textInRange:_selectedTextRange]];
}

- (void)handleShareAction {
[self.textInputDelegate flutterTextInputView:self
shareSelectedText:[self textInRange:_selectedTextRange]];
}

// DFS algorithm to search a UICommand from the menu tree.
- (UICommand*)searchCommandWithSelector:(SEL)selector
element:(UIMenuElement*)element API_AVAILABLE(ios(16.0)) {
Comment on lines +887 to +889
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this searching through the default menu (suggestedActions) trying to find the item given in the call to showSystemContextMenu? Won't all of the ones that we currently support never be nested? Or maybe I just don't understand how suggestedActions is structured.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually all of them are nested at least 1 level deep in my observation - Apple uses UIMenuOptionsDisplayInline to make the items appear on the top level. And since this is not an API contract, we have to do a tree search here.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it, thanks for the explanation!

if ([element isKindOfClass:UICommand.class]) {
UICommand* command = (UICommand*)element;
return command.action == selector ? command : nil;
} else if ([element isKindOfClass:UIMenu.class]) {
NSArray<UIMenuElement*>* children = ((UIMenu*)element).children;
for (UIMenuElement* child in children) {
UICommand* result = [self searchCommandWithSelector:selector element:child];
if (result) {
return result;
}
}
return nil;
} else {
return nil;
}
}

- (void)addBasicEditingCommandToItems:(NSMutableArray*)items
type:(NSString*)type
selector:(SEL)selector
suggestedMenu:(UIMenu*)suggestedMenu {
UICommand* command = [self searchCommandWithSelector:selector element:suggestedMenu];
if (command) {
[items addObject:command];
} else {
FML_LOG(ERROR) << "Cannot find context menu item of type \"" << type.UTF8String << "\".";
}
}

- (void)addAdditionalBasicCommandToItems:(NSMutableArray*)items
type:(NSString*)type
selector:(SEL)selector
encodedItem:(NSDictionary<NSString*, id>*)encodedItem {
NSString* title = encodedItem[@"title"];
if (title) {
UICommand* command = [UICommand commandWithTitle:title
image:nil
action:selector
propertyList:nil];
[items addObject:command];
} else {
FML_LOG(ERROR) << "Missing title for context menu item of type \"" << type.UTF8String << "\".";
}
}

- (UIMenu*)editMenuInteraction:(UIEditMenuInteraction*)interaction
menuForConfiguration:(UIEditMenuConfiguration*)configuration
suggestedActions:(NSArray<UIMenuElement*>*)suggestedActions API_AVAILABLE(ios(16.0)) {
return [UIMenu menuWithChildren:suggestedActions];
UIMenu* suggestedMenu = [UIMenu menuWithChildren:suggestedActions];
if (!_editMenuItems) {
return suggestedMenu;
}

NSMutableArray* items = [NSMutableArray array];
for (NSDictionary<NSString*, id>* encodedItem in _editMenuItems) {
NSString* type = encodedItem[@"type"];
if ([type isEqualToString:@"copy"]) {
[self addBasicEditingCommandToItems:items
type:type
selector:@selector(copy:)
suggestedMenu:suggestedMenu];
} else if ([type isEqualToString:@"paste"]) {
[self addBasicEditingCommandToItems:items
type:type
selector:@selector(paste:)
suggestedMenu:suggestedMenu];
} else if ([type isEqualToString:@"cut"]) {
[self addBasicEditingCommandToItems:items
type:type
selector:@selector(cut:)
suggestedMenu:suggestedMenu];
} else if ([type isEqualToString:@"delete"]) {
[self addBasicEditingCommandToItems:items
type:type
selector:@selector(delete:)
suggestedMenu:suggestedMenu];
} else if ([type isEqualToString:@"selectAll"]) {
[self addBasicEditingCommandToItems:items
type:type
selector:@selector(selectAll:)
suggestedMenu:suggestedMenu];
} else if ([type isEqualToString:@"searchWeb"]) {
[self addAdditionalBasicCommandToItems:items
type:type
selector:@selector(handleSearchWebAction)
encodedItem:encodedItem];
} else if ([type isEqualToString:@"share"]) {
[self addAdditionalBasicCommandToItems:items
type:type
selector:@selector(handleShareAction)
encodedItem:encodedItem];
} else if ([type isEqualToString:@"lookUp"]) {
[self addAdditionalBasicCommandToItems:items
type:type
selector:@selector(handleLookUpAction)
encodedItem:encodedItem];
}
}
return [UIMenu menuWithChildren:items];
}

- (void)editMenuInteraction:(UIEditMenuInteraction*)interaction
Expand All @@ -887,8 +1001,10 @@ - (CGRect)editMenuInteraction:(UIEditMenuInteraction*)interaction
return _editMenuTargetRect;
}

- (void)showEditMenuWithTargetRect:(CGRect)targetRect API_AVAILABLE(ios(16.0)) {
- (void)showEditMenuWithTargetRect:(CGRect)targetRect
items:(NSArray<NSDictionary*>*)items API_AVAILABLE(ios(16.0)) {
_editMenuTargetRect = targetRect;
_editMenuItems = items;
UIEditMenuConfiguration* config =
[UIEditMenuConfiguration configurationWithIdentifier:nil sourcePoint:CGPointZero];
[self.editMenuInteraction presentEditMenuWithConfiguration:config];
Expand Down Expand Up @@ -2560,7 +2676,7 @@ - (BOOL)showEditMenu:(NSDictionary*)args API_AVAILABLE(ios(16.0)) {
[encodedTargetRect[@"x"] doubleValue], [encodedTargetRect[@"y"] doubleValue],
[encodedTargetRect[@"width"] doubleValue], [encodedTargetRect[@"height"] doubleValue]);
CGRect localTargetRect = [self.hostView convertRect:globalTargetRect toView:self.activeView];
[self.activeView showEditMenuWithTargetRect:localTargetRect];
[self.activeView showEditMenuWithTargetRect:localTargetRect items:args[@"items"]];
return YES;
}

Expand Down
Loading