5#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformPlugin.h"
7#import <AudioToolbox/AudioToolbox.h>
8#import <Foundation/Foundation.h>
9#import <UIKit/UIApplication.h>
10#import <UIKit/UIKit.h>
12#include "flutter/fml/logging.h"
13#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterEngine_Internal.h"
14#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h"
15#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterViewController_Internal.h"
16#import "flutter/shell/platform/darwin/ios/framework/Source/UIViewController+FlutterScreenAndSceneIfLoaded.h"
21const UInt32 kKeyPressClickSoundId = 1306;
23#if not APPLICATION_EXTENSION_API_ONLY
24const NSString* searchURLPrefix =
@"x-web-search://?";
33 "io.flutter.plugin.platform.SystemChromeOrientationNotificationName";
35 "io.flutter.plugin.platform.SystemChromeOrientationNotificationKey";
37 "io.flutter.plugin.platform.SystemChromeOverlayNotificationName";
39 "io.flutter.plugin.platform.SystemChromeOverlayNotificationKey";
46#if not APPLICATION_EXTENSION_API_ONLY
47 [UIApplication sharedApplication].statusBarHidden = hidden;
49 FML_LOG(WARNING) <<
"Application based status bar styling is not available in app extension.";
54#if not APPLICATION_EXTENSION_API_ONLY
57 [[UIApplication sharedApplication] setStatusBarStyle:style];
59 FML_LOG(WARNING) <<
"Application based status bar styling is not available in app extension.";
88 NSObject* infoValue = [[NSBundle mainBundle]
89 objectForInfoDictionaryKey:@"UIViewControllerBasedStatusBarAppearance"];
90#if FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG
91 if (infoValue != nil && ![infoValue isKindOfClass:[NSNumber
class]]) {
92 FML_LOG(
ERROR) <<
"The value of UIViewControllerBasedStatusBarAppearance in info.plist must "
96 _enableViewControllerBasedStatusBarAppearance =
97 (infoValue == nil || [(NSNumber*)infoValue boolValue]);
104 NSString* method =
call.method;
106 if ([method isEqualToString:
@"SystemSound.play"]) {
107 [
self playSystemSound:args];
109 }
else if ([method isEqualToString:
@"HapticFeedback.vibrate"]) {
110 [
self vibrateHapticFeedback:args];
112 }
else if ([method isEqualToString:
@"SystemChrome.setPreferredOrientations"]) {
113 [
self setSystemChromePreferredOrientations:args];
115 }
else if ([method isEqualToString:
@"SystemChrome.setApplicationSwitcherDescription"]) {
116 [
self setSystemChromeApplicationSwitcherDescription:args];
118 }
else if ([method isEqualToString:
@"SystemChrome.setEnabledSystemUIOverlays"]) {
119 [
self setSystemChromeEnabledSystemUIOverlays:args];
121 }
else if ([method isEqualToString:
@"SystemChrome.setEnabledSystemUIMode"]) {
122 [
self setSystemChromeEnabledSystemUIMode:args];
124 }
else if ([method isEqualToString:
@"SystemChrome.restoreSystemUIOverlays"]) {
125 [
self restoreSystemChromeSystemUIOverlays];
127 }
else if ([method isEqualToString:
@"SystemChrome.setSystemUIOverlayStyle"]) {
128 [
self setSystemChromeSystemUIOverlayStyle:args];
130 }
else if ([method isEqualToString:
@"SystemNavigator.pop"]) {
131 NSNumber* isAnimated =
args;
132 [
self popSystemNavigator:isAnimated.boolValue];
134 }
else if ([method isEqualToString:
@"Clipboard.getData"]) {
136 }
else if ([method isEqualToString:
@"Clipboard.setData"]) {
137 [
self setClipboardData:args];
139 }
else if ([method isEqualToString:
@"Clipboard.hasStrings"]) {
141 }
else if ([method isEqualToString:
@"LiveText.isLiveTextInputAvailable"]) {
143 }
else if ([method isEqualToString:
@"SearchWeb.invoke"]) {
144 [
self searchWeb:args];
146 }
else if ([method isEqualToString:
@"LookUp.invoke"]) {
147 [
self showLookUpViewController:args];
149 }
else if ([method isEqualToString:
@"Share.invoke"]) {
150 [
self showShareViewController:args];
152 }
else if ([method isEqualToString:
@"ContextMenu.showSystemContextMenu"]) {
153 [
self showSystemContextMenu:args];
155 }
else if ([method isEqualToString:
@"ContextMenu.hideSystemContextMenu"]) {
156 [
self hideSystemContextMenu];
163- (void)showSystemContextMenu:(NSDictionary*)args {
164 if (@available(iOS 16.0, *)) {
167 if (!shownEditMenu) {
168 FML_LOG(
ERROR) <<
"Only text input supports system context menu for now. Ensure the system "
169 "context menu is shown with an active text input connection. See "
170 "https://github.com/flutter/flutter/issues/143033.";
175- (void)hideSystemContextMenu {
176 if (@available(iOS 16.0, *)) {
182- (void)showShareViewController:(NSString*)content {
183 UIViewController* engineViewController = [_engine.get() viewController];
185 NSArray* itemsToShare = @[ content ?: [NSNull null] ];
186 UIActivityViewController* activityViewController =
187 [[[UIActivityViewController alloc] initWithActivityItems:itemsToShare
188 applicationActivities:nil] autorelease];
190 if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
199 caretRectForPosition:(FlutterTextPosition*)range.start];
201 localRectFromFrameworkTransform:firstRect];
203 caretRectForPosition:(FlutterTextPosition*)range.end];
205 localRectFromFrameworkTransform:lastRect];
207 activityViewController.popoverPresentationController.sourceView = engineViewController.view;
209 activityViewController.popoverPresentationController.sourceRect =
210 CGRectMake(fmin(transformedFirstRect.origin.x, transformedLastRect.origin.x),
211 transformedFirstRect.origin.y,
212 abs(transformedLastRect.origin.x - transformedFirstRect.origin.x),
213 transformedFirstRect.size.height);
216 [engineViewController presentViewController:activityViewController animated:YES completion:nil];
219- (void)searchWeb:(NSString*)searchTerm {
220#if APPLICATION_EXTENSION_API_ONLY
221 FML_LOG(WARNING) <<
"SearchWeb.invoke is not availabe in app extension.";
223 NSString* escapedText = [searchTerm
224 stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet
225 URLHostAllowedCharacterSet]];
226 NSString* searchURL = [NSString stringWithFormat:@"%@%@", searchURLPrefix, escapedText];
228 [[UIApplication sharedApplication] openURL:[NSURL URLWithString:searchURL]
230 completionHandler:nil];
234- (void)playSystemSound:(NSString*)soundType {
235 if ([soundType isEqualToString:
@"SystemSoundType.click"]) {
238 AudioServicesPlaySystemSound(kKeyPressClickSoundId);
242- (void)vibrateHapticFeedback:(NSString*)feedbackType {
244 AudioServicesPlaySystemSound(kSystemSoundID_Vibrate);
248 if ([
@"HapticFeedbackType.lightImpact" isEqualToString:feedbackType]) {
249 [[[[UIImpactFeedbackGenerator alloc] initWithStyle:UIImpactFeedbackStyleLight] autorelease]
251 }
else if ([
@"HapticFeedbackType.mediumImpact" isEqualToString:feedbackType]) {
252 [[[[UIImpactFeedbackGenerator alloc] initWithStyle:UIImpactFeedbackStyleMedium] autorelease]
254 }
else if ([
@"HapticFeedbackType.heavyImpact" isEqualToString:feedbackType]) {
255 [[[[UIImpactFeedbackGenerator alloc] initWithStyle:UIImpactFeedbackStyleHeavy] autorelease]
257 }
else if ([
@"HapticFeedbackType.selectionClick" isEqualToString:feedbackType]) {
258 [[[[UISelectionFeedbackGenerator alloc] init] autorelease] selectionChanged];
262- (void)setSystemChromePreferredOrientations:(NSArray*)orientations {
263 UIInterfaceOrientationMask mask = 0;
265 if (orientations.count == 0) {
266 mask |= UIInterfaceOrientationMaskAll;
268 for (NSString* orientation in orientations) {
269 if ([orientation isEqualToString:
@"DeviceOrientation.portraitUp"]) {
270 mask |= UIInterfaceOrientationMaskPortrait;
271 }
else if ([orientation isEqualToString:
@"DeviceOrientation.portraitDown"]) {
272 mask |= UIInterfaceOrientationMaskPortraitUpsideDown;
273 }
else if ([orientation isEqualToString:
@"DeviceOrientation.landscapeLeft"]) {
274 mask |= UIInterfaceOrientationMaskLandscapeLeft;
275 }
else if ([orientation isEqualToString:
@"DeviceOrientation.landscapeRight"]) {
276 mask |= UIInterfaceOrientationMaskLandscapeRight;
284 [[NSNotificationCenter defaultCenter]
285 postNotificationName:@(kOrientationUpdateNotificationName)
287 userInfo:@{@(kOrientationUpdateNotificationKey) : @(mask)}];
290- (void)setSystemChromeApplicationSwitcherDescription:(NSDictionary*)object {
294- (void)setSystemChromeEnabledSystemUIOverlays:(NSArray*)overlays {
295 BOOL statusBarShouldBeHidden = ![overlays containsObject:@"SystemUiOverlay.top"];
296 if ([overlays containsObject:
@"SystemUiOverlay.bottom"]) {
297 [[NSNotificationCenter defaultCenter]
298 postNotificationName:FlutterViewControllerShowHomeIndicator
301 [[NSNotificationCenter defaultCenter]
302 postNotificationName:FlutterViewControllerHideHomeIndicator
305 if (
self.enableViewControllerBasedStatusBarAppearance) {
306 [_engine.get() viewController].prefersStatusBarHidden = statusBarShouldBeHidden;
318- (void)setSystemChromeEnabledSystemUIMode:(NSString*)mode {
319 BOOL edgeToEdge = [mode isEqualToString:@"SystemUiMode.edgeToEdge"];
320 if (
self.enableViewControllerBasedStatusBarAppearance) {
321 [_engine.get() viewController].prefersStatusBarHidden = !edgeToEdge;
331 [[NSNotificationCenter defaultCenter]
332 postNotificationName:edgeToEdge ? FlutterViewControllerShowHomeIndicator
333 : FlutterViewControllerHideHomeIndicator
337- (void)restoreSystemChromeSystemUIOverlays {
341- (void)setSystemChromeSystemUIOverlayStyle:(NSDictionary*)message {
342 NSString* brightness =
message[@"statusBarBrightness"];
343 if (brightness == (
id)[NSNull null]) {
347 UIStatusBarStyle statusBarStyle;
348 if ([brightness isEqualToString:
@"Brightness.dark"]) {
349 statusBarStyle = UIStatusBarStyleLightContent;
350 }
else if ([brightness isEqualToString:
@"Brightness.light"]) {
351 if (@available(iOS 13, *)) {
352 statusBarStyle = UIStatusBarStyleDarkContent;
354 statusBarStyle = UIStatusBarStyleDefault;
360 if (
self.enableViewControllerBasedStatusBarAppearance) {
362 [[NSNotificationCenter defaultCenter]
363 postNotificationName:@(kOverlayStyleUpdateNotificationName)
365 userInfo:@{@(kOverlayStyleUpdateNotificationKey) : @(statusBarStyle)}];
371- (void)popSystemNavigator:(
BOOL)isAnimated {
379 UINavigationController* navigationController = [engineViewController navigationController];
380 if (navigationController) {
381 [navigationController popViewControllerAnimated:isAnimated];
383 UIViewController* rootViewController = nil;
384#if APPLICATION_EXTENSION_API_ONLY
385 if (@available(iOS 15.0, *)) {
387 [engineViewController flutterWindowSceneIfViewLoaded].keyWindow.rootViewController;
390 <<
"rootViewController is not available in application extension prior to iOS 15.0.";
393 rootViewController = [UIApplication sharedApplication].keyWindow.rootViewController;
395 if (engineViewController != rootViewController) {
396 [engineViewController dismissViewControllerAnimated:isAnimated completion:nil];
401- (NSDictionary*)getClipboardData:(NSString*)format {
402 UIPasteboard* pasteboard = [UIPasteboard generalPasteboard];
404 NSString* stringInPasteboard = pasteboard.string;
406 return stringInPasteboard == nil ? nil : @{
@"text" : stringInPasteboard};
411- (void)setClipboardData:(NSDictionary*)data {
412 UIPasteboard* pasteboard = [UIPasteboard generalPasteboard];
413 id copyText =
data[@"text"];
414 if ([copyText isKindOfClass:[NSString
class]]) {
415 pasteboard.string = copyText;
417 pasteboard.string =
@"null";
421- (NSDictionary*)clipboardHasStrings {
422 return @{
@"value" : @([UIPasteboard generalPasteboard].hasStrings)};
425- (
BOOL)isLiveTextInputAvailable {
426 return [[
self textField] canPerformAction:@selector(captureTextFromCamera:) withSender:nil];
429- (void)showLookUpViewController:(NSString*)term {
430 UIViewController* engineViewController = [_engine.get() viewController];
431 UIReferenceLibraryViewController* referenceLibraryViewController =
432 [[[UIReferenceLibraryViewController alloc] initWithTerm:term] autorelease];
433 [engineViewController presentViewController:referenceLibraryViewController
438- (UITextField*)textField {
446 [_textField release];
void(^ FlutterResult)(id _Nullable result)
FLUTTER_DARWIN_EXPORT NSObject const * FlutterMethodNotImplemented
G_BEGIN_DECLS G_MODULE_EXPORT FlValue * args
uint32_t uint32_t * format
#define FML_LOG(severity)
#define FML_DCHECK(condition)
BOOL showEditMenu:(ios(16.0) API_AVAILABLE)
fml::scoped_nsobject< FlutterTextInputPlugin > _textInputPlugin
FlutterTextInputPlugin * textInputPlugin
fml::scoped_nsobject< FlutterEngine > _engine
constexpr char kTextPlainFormat[]
Clipboard plain text format.
const char *const kOrientationUpdateNotificationKey
const char *const kOverlayStyleUpdateNotificationName
const char *const kOverlayStyleUpdateNotificationKey
const char *const kOrientationUpdateNotificationName
SIN Vec< N, float > abs(const Vec< N, float > &x)
std::shared_ptr< const fml::Mapping > data