From 52a11eae259b04e7e53c9c92af98f8ff91bf7d06 Mon Sep 17 00:00:00 2001 From: Louise Hsu Date: Mon, 20 Nov 2023 03:27:37 -0800 Subject: [PATCH 1/6] initial commit --- .../framework/Source/FlutterPlatformPlugin.mm | 7 ++++ .../Source/FlutterPlatformPluginTest.mm | 38 +++++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterPlatformPlugin.mm b/shell/platform/darwin/ios/framework/Source/FlutterPlatformPlugin.mm index c1d7eaea3296e..d2bd2a6986ea6 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterPlatformPlugin.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterPlatformPlugin.mm @@ -158,6 +158,13 @@ - (void)showShareViewController:(NSString*)content { UIActivityViewController* activityViewController = [[[UIActivityViewController alloc] initWithActivityItems:itemsToShare applicationActivities:nil] autorelease]; + + if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { + activityViewController.popoverPresentationController.sourceView = engineViewController.view; + activityViewController.popoverPresentationController.sourceRect = CGRectMake(CGRectGetWidth(engineViewController.view.bounds)/2, CGRectGetHeight(engineViewController.view.bounds)/2, 0, 0); + activityViewController.popoverPresentationController.permittedArrowDirections = UIPopoverArrowDirection(); + } + [engineViewController presentViewController:activityViewController animated:YES completion:nil]; } diff --git a/shell/platform/darwin/ios/framework/Source/FlutterPlatformPluginTest.mm b/shell/platform/darwin/ios/framework/Source/FlutterPlatformPluginTest.mm index 894e1f30cd5e1..25907899edbdb 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterPlatformPluginTest.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterPlatformPluginTest.mm @@ -166,6 +166,44 @@ - (void)testShareScreenInvoked { [self waitForExpectationsWithTimeout:1 handler:nil]; } +- (void)testShareScreenInvokedOnIPad { + FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"test" project:nil]; + [engine runWithEntrypoint:nil]; + std::unique_ptr> _weakFactory = + std::make_unique>(engine); + + XCTestExpectation* presentExpectation = + [self expectationWithDescription:@"Share view controller presented on iPad"]; + + FlutterViewController* engineViewController = [[FlutterViewController alloc] initWithEngine:engine + nibName:nil + bundle:nil]; + FlutterViewController* mockEngineViewController = OCMPartialMock(engineViewController); + OCMStub([mockEngineViewController + presentViewController:[OCMArg isKindOfClass:[UIActivityViewController class]] + animated:YES + completion:nil]); + + id mockTraitCollection = OCMClassMock([UITraitCollection class]); + OCMStub([mockTraitCollection userInterfaceIdiom]).andReturn(UIUserInterfaceIdiomPad); + + FlutterPlatformPlugin* plugin = + [[FlutterPlatformPlugin alloc] initWithEngine:_weakFactory->GetWeakNSObject()]; + FlutterPlatformPlugin* mockPlugin = OCMPartialMock(plugin); + + FlutterMethodCall* methodCall = [FlutterMethodCall methodCallWithMethodName:@"Share.invoke" + arguments:@"Test"]; + FlutterResult result = ^(id result) { + OCMVerify([mockEngineViewController + presentViewController:[OCMArg isKindOfClass:[UIActivityViewController class]] + animated:YES + completion:nil]); + [presentExpectation fulfill]; + }; + [mockPlugin handleMethodCall:methodCall result:result]; + [self waitForExpectationsWithTimeout:1 handler:nil]; +} + - (void)testClipboardHasCorrectStrings { [UIPasteboard generalPasteboard].string = nil; FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"test" project:nil]; From 2d678fa4b7c818660f247d6cda4de6d0612aecd8 Mon Sep 17 00:00:00 2001 From: Louise Hsu Date: Mon, 20 Nov 2023 03:29:10 -0800 Subject: [PATCH 2/6] formatting --- .../darwin/ios/framework/Source/FlutterPlatformPlugin.mm | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterPlatformPlugin.mm b/shell/platform/darwin/ios/framework/Source/FlutterPlatformPlugin.mm index d2bd2a6986ea6..722c1f8023185 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterPlatformPlugin.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterPlatformPlugin.mm @@ -161,8 +161,11 @@ - (void)showShareViewController:(NSString*)content { if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { activityViewController.popoverPresentationController.sourceView = engineViewController.view; - activityViewController.popoverPresentationController.sourceRect = CGRectMake(CGRectGetWidth(engineViewController.view.bounds)/2, CGRectGetHeight(engineViewController.view.bounds)/2, 0, 0); - activityViewController.popoverPresentationController.permittedArrowDirections = UIPopoverArrowDirection(); + activityViewController.popoverPresentationController.sourceRect = + CGRectMake(CGRectGetWidth(engineViewController.view.bounds) / 2, + CGRectGetHeight(engineViewController.view.bounds) / 2, 0, 0); + activityViewController.popoverPresentationController.permittedArrowDirections = + UIPopoverArrowDirection(); } [engineViewController presentViewController:activityViewController animated:YES completion:nil]; From 3b171dc01dcab20d5d9547d271a6d7db7e5a517a Mon Sep 17 00:00:00 2001 From: Louise Hsu Date: Tue, 21 Nov 2023 03:26:24 -0800 Subject: [PATCH 3/6] add positioning for ipad share menu --- .../framework/Source/FlutterPlatformPlugin.mm | 23 +++++++++++++++---- .../framework/Source/FlutterTextInputPlugin.h | 2 ++ 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterPlatformPlugin.mm b/shell/platform/darwin/ios/framework/Source/FlutterPlatformPlugin.mm index 722c1f8023185..f687337980377 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterPlatformPlugin.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterPlatformPlugin.mm @@ -10,6 +10,8 @@ #import #include "flutter/fml/logging.h" +#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterEngine_Internal.h" +#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h" #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterViewController_Internal.h" #import "flutter/shell/platform/darwin/ios/framework/Source/UIViewController+FlutterScreenAndSceneIfLoaded.h" @@ -154,18 +156,31 @@ - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { - (void)showShareViewController:(NSString*)content { UIViewController* engineViewController = [_engine.get() viewController]; + NSArray* itemsToShare = @[ content ?: [NSNull null] ]; UIActivityViewController* activityViewController = [[[UIActivityViewController alloc] initWithActivityItems:itemsToShare applicationActivities:nil] autorelease]; if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { + // On iPad, the share screen is presented in a popover view, and requires a CGRect + FlutterTextInputPlugin* _textInputPlugin = [_engine.get() textInputPlugin]; + UITextRange* range = _textInputPlugin.textInputView.selectedTextRange; + + CGRect firstRect = [(FlutterTextInputView*)_textInputPlugin.textInputView + caretRectForPosition:(FlutterTextPosition*)range.start]; + CGRect transformedRectLeft = [(FlutterTextInputView*)_textInputPlugin.textInputView + localRectFromFrameworkTransform:firstRect]; + CGRect lastRect = [(FlutterTextInputView*)_textInputPlugin.textInputView + caretRectForPosition:(FlutterTextPosition*)range.end]; + CGRect transformedRectRight = [(FlutterTextInputView*)_textInputPlugin.textInputView + localRectFromFrameworkTransform:lastRect]; + activityViewController.popoverPresentationController.sourceView = engineViewController.view; activityViewController.popoverPresentationController.sourceRect = - CGRectMake(CGRectGetWidth(engineViewController.view.bounds) / 2, - CGRectGetHeight(engineViewController.view.bounds) / 2, 0, 0); - activityViewController.popoverPresentationController.permittedArrowDirections = - UIPopoverArrowDirection(); + CGRectMake(transformedRectLeft.origin.x, transformedRectLeft.origin.y, + transformedRectRight.origin.x - transformedRectLeft.origin.x, + transformedRectLeft.size.height); } [engineViewController presentViewController:activityViewController animated:YES completion:nil]; diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h index a7a996cb39995..5010e57206ff4 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h +++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h @@ -163,6 +163,8 @@ FLUTTER_DARWIN_EXPORT - (instancetype)initWithFrame:(CGRect)frame NS_UNAVAILABLE; - (instancetype)initWithOwner:(FlutterTextInputPlugin*)textInputPlugin NS_DESIGNATED_INITIALIZER; +- (CGRect)localRectFromFrameworkTransform:(CGRect)incomingRect; +- (CGRect)caretRectForPosition:(UITextPosition*)position; @end @interface UIView (FindFirstResponder) From 7e72f937d29a7e08005b60e8d3944ed10e9648e7 Mon Sep 17 00:00:00 2001 From: Louise Hsu Date: Tue, 21 Nov 2023 13:46:56 -0800 Subject: [PATCH 4/6] add todo --- .../darwin/ios/framework/Source/FlutterPlatformPlugin.mm | 1 + 1 file changed, 1 insertion(+) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterPlatformPlugin.mm b/shell/platform/darwin/ios/framework/Source/FlutterPlatformPlugin.mm index f687337980377..8cd7ef1c8d3f2 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterPlatformPlugin.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterPlatformPlugin.mm @@ -163,6 +163,7 @@ - (void)showShareViewController:(NSString*)content { applicationActivities:nil] autorelease]; if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { + // TODO(louisehsu): consider moving this to TextInputPlugin as functionality is very similar // On iPad, the share screen is presented in a popover view, and requires a CGRect FlutterTextInputPlugin* _textInputPlugin = [_engine.get() textInputPlugin]; UITextRange* range = _textInputPlugin.textInputView.selectedTextRange; From ae1880dff793e5fd6ecf8d4bcf2c8e21114ee52b Mon Sep 17 00:00:00 2001 From: Louise Hsu Date: Tue, 21 Nov 2023 14:34:38 -0800 Subject: [PATCH 5/6] fix for RTL language, more TODOs --- .../darwin/ios/framework/Source/FlutterPlatformPlugin.mm | 9 ++++++--- .../darwin/ios/framework/Source/FlutterTextInputPlugin.h | 2 ++ 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterPlatformPlugin.mm b/shell/platform/darwin/ios/framework/Source/FlutterPlatformPlugin.mm index 8cd7ef1c8d3f2..6edf2252b8380 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterPlatformPlugin.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterPlatformPlugin.mm @@ -163,11 +163,12 @@ - (void)showShareViewController:(NSString*)content { applicationActivities:nil] autorelease]; if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { - // TODO(louisehsu): consider moving this to TextInputPlugin as functionality is very similar // On iPad, the share screen is presented in a popover view, and requires a CGRect FlutterTextInputPlugin* _textInputPlugin = [_engine.get() textInputPlugin]; UITextRange* range = _textInputPlugin.textInputView.selectedTextRange; + // firstRectForRange cannot be used here as it's current implementation does + // not always return the full rect of the range. CGRect firstRect = [(FlutterTextInputView*)_textInputPlugin.textInputView caretRectForPosition:(FlutterTextPosition*)range.start]; CGRect transformedRectLeft = [(FlutterTextInputView*)_textInputPlugin.textInputView @@ -178,9 +179,11 @@ - (void)showShareViewController:(NSString*)content { localRectFromFrameworkTransform:lastRect]; activityViewController.popoverPresentationController.sourceView = engineViewController.view; + // In case of RTL Language, get the minimum x coordinate activityViewController.popoverPresentationController.sourceRect = - CGRectMake(transformedRectLeft.origin.x, transformedRectLeft.origin.y, - transformedRectRight.origin.x - transformedRectLeft.origin.x, + CGRectMake(fmin(transformedRectLeft.origin.x, transformedRectRight.origin.x), + transformedRectLeft.origin.y, + abs(transformedRectRight.origin.x - transformedRectLeft.origin.x), transformedRectLeft.size.height); } diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h index 5010e57206ff4..fe26c40c4fca5 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h +++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h @@ -163,6 +163,8 @@ FLUTTER_DARWIN_EXPORT - (instancetype)initWithFrame:(CGRect)frame NS_UNAVAILABLE; - (instancetype)initWithOwner:(FlutterTextInputPlugin*)textInputPlugin NS_DESIGNATED_INITIALIZER; +// TODO(louisehsu): These are being exposed to support Share in FlutterPlatformPlugin +// Consider moving that feature into FlutterTextInputPlugin to avoid exposing extra methods - (CGRect)localRectFromFrameworkTransform:(CGRect)incomingRect; - (CGRect)caretRectForPosition:(UITextPosition*)position; @end From 12e23aa329fd3beca6460e04bb2983896b81bbbc Mon Sep 17 00:00:00 2001 From: Louise Hsu Date: Wed, 22 Nov 2023 11:52:04 -0800 Subject: [PATCH 6/6] fix naming, update comments --- .../ios/framework/Source/FlutterPlatformPlugin.mm | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterPlatformPlugin.mm b/shell/platform/darwin/ios/framework/Source/FlutterPlatformPlugin.mm index 6edf2252b8380..4739c3119fc23 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterPlatformPlugin.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterPlatformPlugin.mm @@ -163,7 +163,8 @@ - (void)showShareViewController:(NSString*)content { applicationActivities:nil] autorelease]; if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { - // On iPad, the share screen is presented in a popover view, and requires a CGRect + // On iPad, the share screen is presented in a popover view, and requires a + // sourceView and sourceRect FlutterTextInputPlugin* _textInputPlugin = [_engine.get() textInputPlugin]; UITextRange* range = _textInputPlugin.textInputView.selectedTextRange; @@ -171,20 +172,20 @@ - (void)showShareViewController:(NSString*)content { // not always return the full rect of the range. CGRect firstRect = [(FlutterTextInputView*)_textInputPlugin.textInputView caretRectForPosition:(FlutterTextPosition*)range.start]; - CGRect transformedRectLeft = [(FlutterTextInputView*)_textInputPlugin.textInputView + CGRect transformedFirstRect = [(FlutterTextInputView*)_textInputPlugin.textInputView localRectFromFrameworkTransform:firstRect]; CGRect lastRect = [(FlutterTextInputView*)_textInputPlugin.textInputView caretRectForPosition:(FlutterTextPosition*)range.end]; - CGRect transformedRectRight = [(FlutterTextInputView*)_textInputPlugin.textInputView + CGRect transformedLastRect = [(FlutterTextInputView*)_textInputPlugin.textInputView localRectFromFrameworkTransform:lastRect]; activityViewController.popoverPresentationController.sourceView = engineViewController.view; // In case of RTL Language, get the minimum x coordinate activityViewController.popoverPresentationController.sourceRect = - CGRectMake(fmin(transformedRectLeft.origin.x, transformedRectRight.origin.x), - transformedRectLeft.origin.y, - abs(transformedRectRight.origin.x - transformedRectLeft.origin.x), - transformedRectLeft.size.height); + CGRectMake(fmin(transformedFirstRect.origin.x, transformedLastRect.origin.x), + transformedFirstRect.origin.y, + abs(transformedLastRect.origin.x - transformedFirstRect.origin.x), + transformedFirstRect.size.height); } [engineViewController presentViewController:activityViewController animated:YES completion:nil];