diff --git a/shell/platform/darwin/ios/BUILD.gn b/shell/platform/darwin/ios/BUILD.gn index f6b0c619d8127b764883218f7ffd11555f4fea22..e120b7eaee1056a5c1db789ca8162efe62a47cd6 100644 --- a/shell/platform/darwin/ios/BUILD.gn +++ b/shell/platform/darwin/ios/BUILD.gn @@ -19,6 +19,7 @@ shared_library("flutter_framework_dylib") { "framework/Headers/FlutterAppDelegate.h", "framework/Headers/FlutterAsyncMessageListener.h", "framework/Headers/FlutterDartProject.h", + "framework/Headers/FlutterJSONMessageListener.h", "framework/Headers/FlutterMacros.h", "framework/Headers/FlutterMessageListener.h", "framework/Headers/FlutterViewController.h", @@ -33,6 +34,9 @@ shared_library("flutter_framework_dylib") { "framework/Source/FlutterDartProject_Internal.h", "framework/Source/FlutterDartSource.h", "framework/Source/FlutterDartSource.mm", + "framework/Source/FlutterJSONMessageListener.mm", + "framework/Source/FlutterPlatformPlugin.h", + "framework/Source/FlutterPlatformPlugin.mm", "framework/Source/FlutterView.h", "framework/Source/FlutterView.mm", "framework/Source/FlutterViewController.mm", @@ -129,6 +133,7 @@ copy("framework_headers") { "framework/Headers/FlutterAppDelegate.h", "framework/Headers/FlutterAsyncMessageListener.h", "framework/Headers/FlutterDartProject.h", + "framework/Headers/FlutterJSONMessageListener.h", "framework/Headers/FlutterMacros.h", "framework/Headers/FlutterMessageListener.h", "framework/Headers/FlutterViewController.h", diff --git a/shell/platform/darwin/ios/framework/Headers/FlutterJSONMessageListener.h b/shell/platform/darwin/ios/framework/Headers/FlutterJSONMessageListener.h new file mode 100644 index 0000000000000000000000000000000000000000..557ed89e850fd98eb26c31cb2a8a75ce347dc8b6 --- /dev/null +++ b/shell/platform/darwin/ios/framework/Headers/FlutterJSONMessageListener.h @@ -0,0 +1,17 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef FLUTTER_FLUTTERJSONMESSAGELISTENER_H_ +#define FLUTTER_FLUTTERJSONMESSAGELISTENER_H_ + +#include "FlutterMessageListener.h" + +FLUTTER_EXPORT +@interface FlutterJSONMessageListener : NSObject + +- (NSDictionary*)didReceiveJSON:(NSDictionary*)message; + +@end + +#endif // FLUTTER_FLUTTERJSONMESSAGELISTENER_H_ diff --git a/shell/platform/darwin/ios/framework/Source/FlutterJSONMessageListener.mm b/shell/platform/darwin/ios/framework/Source/FlutterJSONMessageListener.mm new file mode 100644 index 0000000000000000000000000000000000000000..44359a807e3670f26a7862f48e7496ce0382eb55 --- /dev/null +++ b/shell/platform/darwin/ios/framework/Source/FlutterJSONMessageListener.mm @@ -0,0 +1,30 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "flutter/shell/platform/darwin/ios/framework/Headers/FlutterJSONMessageListener.h" + +@implementation FlutterJSONMessageListener + +- (NSString*)didReceiveString:(NSString*)message { + NSError *error = nil; + NSData* data = [message dataUsingEncoding:NSUTF8StringEncoding]; + NSDictionary* jsonObject = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error]; + if (error) + return nil; + NSDictionary* response = [self didReceiveJSON:jsonObject]; + if (!response) + return nil; + NSData* responseData = [NSJSONSerialization dataWithJSONObject:response options:0 error:nil]; + return [[[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding] autorelease]; +} + +- (NSDictionary*)didReceiveJSON:(NSDictionary*)message { + return nil; +} + +- (NSString *)messageName { + return nil; +} + +@end diff --git a/shell/platform/darwin/ios/framework/Source/FlutterPlatformPlugin.h b/shell/platform/darwin/ios/framework/Source/FlutterPlatformPlugin.h new file mode 100644 index 0000000000000000000000000000000000000000..7b6163bd0245ea9feff87e3d22ea49cba84db135 --- /dev/null +++ b/shell/platform/darwin/ios/framework/Source/FlutterPlatformPlugin.h @@ -0,0 +1,9 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "flutter/shell/platform/darwin/ios/framework/Headers/FlutterJSONMessageListener.h" + +@interface FlutterPlatformPlugin : FlutterJSONMessageListener + +@end diff --git a/shell/platform/darwin/ios/framework/Source/FlutterPlatformPlugin.mm b/shell/platform/darwin/ios/framework/Source/FlutterPlatformPlugin.mm new file mode 100644 index 0000000000000000000000000000000000000000..28c94648e8bf8398ff519dc6458886d1078eb60d --- /dev/null +++ b/shell/platform/darwin/ios/framework/Source/FlutterPlatformPlugin.mm @@ -0,0 +1,169 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformPlugin.h" + +#include +#include +#include +#include + +static NSDictionary* GetDirectoryOfType(NSSearchPathDirectory dir) { + NSArray* paths = + NSSearchPathForDirectoriesInDomains(dir, NSUserDomainMask, YES); + if (paths.count == 0) + return nil; + return @{ @"path": paths.firstObject }; +} + +namespace flutter { +namespace platform { + +// TODO(abarth): Move these definitions from system_chrome_impl.cc to here. +extern const char* const kOrientationUpdateNotificationName; +extern const char* const kOrientationUpdateNotificationKey; +extern const char* const kOverlayStyleUpdateNotificationName; +extern const char* const kOverlayStyleUpdateNotificationKey; + +} // namespace platform +} // namespace flutter + +using namespace flutter::platform; + +@implementation FlutterPlatformPlugin + +- (NSString *)messageName { + return @"flutter/platform"; +} + +- (NSDictionary*)didReceiveJSON:(NSDictionary*)message { + NSString* method = message[@"method"]; + NSArray* args = message[@"args"]; + if ([method isEqualToString:@"SystemSound.play"]) { + [self playSystemSound:args.firstObject]; + } else if ([method isEqualToString:@"HapticFeedback.vibrate"]) { + [self vibrateHapticFeedback]; + } else if ([method isEqualToString:@"UrlLauncher.launch"]) { + [self launchURL:args.firstObject]; + } else if ([method isEqualToString:@"SystemChrome.setPreferredOrientations"]) { + [self setSystemChromePreferredOrientatations:args.firstObject]; + } else if ([method isEqualToString:@"SystemChrome.setApplicationSwitcherDescription"]) { + [self setSystemChromeApplicationSwitcherDescription:args.firstObject]; + } else if ([method isEqualToString:@"SystemChrome.setEnabledSystemUIOverlays"]) { + [self setSystemChromeEnabledSystemUIOverlays:args.firstObject]; + } else if ([method isEqualToString:@"SystemChrome.setSystemUIOverlayStyle"]) { + [self setSystemChromeSystemUIOverlayStyle:args.firstObject]; + } else if ([method isEqualToString:@"PathProvider.getTemporaryDirectory"]) { + return [self getPathProviderTemporaryDirectory]; + } else if ([method isEqualToString:@"PathProvider.getApplicationDocumentsDirectory"]) { + return [self getPathProviderApplicationDocumentsDirectory]; + } else { + // TODO(abarth): We should signal an error here that gets reported back to + // Dart. + } + return nil; +} + +- (void)playSystemSound:(NSString*)soundType { + if ([soundType isEqualToString:@"SystemSoundType.click"]) { + // All feedback types are specific to Android and are treated as equal on + // iOS. The surface must (and does) adopt the UIInputViewAudioFeedback + // protocol + [[UIDevice currentDevice] playInputClick]; + } +} + +- (void)vibrateHapticFeedback { + AudioServicesPlayAlertSound(kSystemSoundID_Vibrate); +} + +- (NSDictionary*)launchURL:(NSString*)urlString { + NSURL* url = [NSURL URLWithString:urlString]; + UIApplication* application = [UIApplication sharedApplication]; + bool success = [application canOpenURL:url] && [application openURL:url]; + return @{ @"succes": @(success) }; +} + +- (void)setSystemChromePreferredOrientatations:(NSArray*)orientations { + UIInterfaceOrientationMask mask = 0; + + if (orientations.count == 0) { + mask |= UIInterfaceOrientationMaskAll; + } else { + for (NSString* orientation in orientations) { + if ([orientation isEqualToString:@"DeviceOrientation.portraitUp"]) + mask |= UIInterfaceOrientationMaskPortrait; + else if ([orientation isEqualToString:@"DeviceOrientation.portraitDown"]) + mask |= UIInterfaceOrientationMaskPortraitUpsideDown; + else if ([orientation isEqualToString:@"DeviceOrientation.landscapeLeft"]) + mask |= UIInterfaceOrientationMaskLandscapeLeft; + else if ([orientation isEqualToString:@"DeviceOrientation.landscapeRight"]) + mask |= UIInterfaceOrientationMaskLandscapeRight; + } + } + + if (!mask) + return; + [[NSNotificationCenter defaultCenter] + postNotificationName:@(kOrientationUpdateNotificationName) + object:nil + userInfo:@{ + @(kOrientationUpdateNotificationKey) : @(mask) + }]; + +} + +- (void)setSystemChromeApplicationSwitcherDescription:(NSDictionary*)object { + // No counterpart on iOS but is a benign operation. So no asserts. +} + +- (void)setSystemChromeEnabledSystemUIOverlays:(NSArray*)overlays { + // Checks if the top status bar should be visible. This platform ignores all + // other overlays + + // We opt out of view controller based status bar visibility since we want + // to be able to modify this on the fly. The key used is + // UIViewControllerBasedStatusBarAppearance + [UIApplication sharedApplication].statusBarHidden = + ![overlays containsObject:@"SystemUiOverlay.top"]; +} + +- (void)setSystemChromeSystemUIOverlayStyle:(NSString*)style { + UIStatusBarStyle statusBarStyle; + if ([style isEqualToString:@"SystemUiOverlayStyle.light"]) + statusBarStyle = UIStatusBarStyleLightContent; + else if ([style isEqualToString:@"SystemUiOverlayStyle.dark"]) + statusBarStyle = UIStatusBarStyleDefault; + else + return; + + NSNumber* infoValue = [[NSBundle mainBundle] + objectForInfoDictionaryKey:@"UIViewControllerBasedStatusBarAppearance"]; + Boolean delegateToViewController = + (infoValue == nil || [infoValue boolValue]); + + if (delegateToViewController) { + // This notification is respected by the iOS embedder + [[NSNotificationCenter defaultCenter] + postNotificationName:@(kOverlayStyleUpdateNotificationName) + object:nil + userInfo:@{ + @(kOverlayStyleUpdateNotificationKey) : @(statusBarStyle) + }]; + } else { + // Note: -[UIApplication setStatusBarStyle] is deprecated in iOS9 + // in favor of delegating to the view controller + [[UIApplication sharedApplication] setStatusBarStyle:statusBarStyle]; + } +} + +- (NSDictionary*)getPathProviderTemporaryDirectory { + return GetDirectoryOfType(NSCachesDirectory); +} + +- (NSDictionary*)getPathProviderApplicationDocumentsDirectory { + return GetDirectoryOfType(NSDocumentDirectory); +} + +@end diff --git a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm index c181e8aad863735ee39a7bef9b6aa7cc4bff8866..908d106a9285d8742b6c73aaec07c8fe078ae466 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm @@ -15,6 +15,7 @@ #include "flutter/shell/gpu/gpu_surface_gl.h" #include "flutter/shell/platform/darwin/common/platform_mac.h" #include "flutter/shell/platform/darwin/ios/framework/Source/FlutterDartProject_Internal.h" +#include "flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformPlugin.h" #include "flutter/shell/platform/darwin/ios/framework/Source/flutter_touch_mapper.h" #include "flutter/shell/platform/darwin/ios/platform_view_ios.h" #include "lib/ftl/functional/make_copyable.h" @@ -36,6 +37,7 @@ void FlutterInit(int argc, const char* argv[]) { sky::ViewportMetricsPtr _viewportMetrics; shell::TouchMapper _touchMapper; std::unique_ptr _platformView; + base::scoped_nsprotocol _platformPlugin; BOOL _initialized; } @@ -83,6 +85,9 @@ void FlutterInit(int argc, const char* argv[]) { reinterpret_cast(self.view.layer)); _platformView->SetupResourceContextOnIOThread(); + _platformPlugin.reset([[FlutterPlatformPlugin alloc] init]); + [self addMessageListener:_platformPlugin.get()]; + [self setupNotificationCenterObservers]; [self connectToEngineAndLoad];