7#import <AudioToolbox/AudioToolbox.h>
8#import <Foundation/Foundation.h>
9#import <UIKit/UIApplication.h>
10#import <UIKit/UIKit.h>
13#import "flutter/shell/platform/darwin/common/InternalFlutterSwiftCommon/InternalFlutterSwiftCommon.h"
38 "io.flutter.plugin.platform.SystemChromeOrientationNotificationName";
40 "io.flutter.plugin.platform.SystemChromeOrientationNotificationKey";
42 "io.flutter.plugin.platform.SystemChromeOverlayNotificationName";
44 "io.flutter.plugin.platform.SystemChromeOverlayNotificationKey";
52 if (flutterApplication) {
53 flutterApplication.statusBarHidden = hidden;
55 [FlutterLogger logWarning:
@"Application based status bar styling is not available in app "
62 if (flutterApplication) {
65 [flutterApplication setStatusBarStyle:style];
67 [FlutterLogger logWarning:
@"Application based status bar styling is not available in app "
81@property(nonatomic, assign)
BOOL enableViewControllerBasedStatusBarAppearance;
87@property(nonatomic, strong) UITextField* textField;
98 NSObject* infoValue = [[NSBundle mainBundle]
99 objectForInfoDictionaryKey:@"UIViewControllerBasedStatusBarAppearance"];
100#if FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG
101 if (infoValue != nil && ![infoValue isKindOfClass:[NSNumber class]]) {
102 [FlutterLogger logError:@"The value of UIViewControllerBasedStatusBarAppearance in "
103 "Info.plist must be a Boolean type."];
106 _enableViewControllerBasedStatusBarAppearance =
107 (infoValue == nil || [(NSNumber*)infoValue boolValue]);
114 NSString* method = call.
method;
116 if ([method isEqualToString:
@"SystemSound.play"]) {
117 [
self playSystemSound:args];
119 }
else if ([method isEqualToString:
@"HapticFeedback.vibrate"]) {
120 [
self vibrateHapticFeedback:args];
122 }
else if ([method isEqualToString:
@"SystemChrome.setPreferredOrientations"]) {
123 [
self setSystemChromePreferredOrientations:args];
125 }
else if ([method isEqualToString:
@"SystemChrome.setApplicationSwitcherDescription"]) {
126 [
self setSystemChromeApplicationSwitcherDescription:args];
128 }
else if ([method isEqualToString:
@"SystemChrome.setEnabledSystemUIOverlays"]) {
129 [
self setSystemChromeEnabledSystemUIOverlays:args];
131 }
else if ([method isEqualToString:
@"SystemChrome.setEnabledSystemUIMode"]) {
132 [
self setSystemChromeEnabledSystemUIMode:args];
134 }
else if ([method isEqualToString:
@"SystemChrome.restoreSystemUIOverlays"]) {
135 [
self restoreSystemChromeSystemUIOverlays];
137 }
else if ([method isEqualToString:
@"SystemChrome.setSystemUIOverlayStyle"]) {
138 [
self setSystemChromeSystemUIOverlayStyle:args];
140 }
else if ([method isEqualToString:
@"SystemNavigator.pop"]) {
141 NSNumber* isAnimated =
args;
142 [
self popSystemNavigator:isAnimated.boolValue];
144 }
else if ([method isEqualToString:
@"Clipboard.getData"]) {
145 result([
self getClipboardData:
args]);
146 }
else if ([method isEqualToString:
@"Clipboard.setData"]) {
147 [
self setClipboardData:args];
149 }
else if ([method isEqualToString:
@"Clipboard.hasStrings"]) {
150 result([
self clipboardHasStrings]);
151 }
else if ([method isEqualToString:
@"LiveText.isLiveTextInputAvailable"]) {
152 result(@([
self isLiveTextInputAvailable]));
153 }
else if ([method isEqualToString:
@"SearchWeb.invoke"]) {
154 [
self searchWeb:args];
156 }
else if ([method isEqualToString:
@"LookUp.invoke"]) {
157 [
self showLookUpViewController:args];
159 }
else if ([method isEqualToString:
@"Share.invoke"]) {
160 [
self showShareViewController:args];
162 }
else if ([method isEqualToString:
@"ContextMenu.showSystemContextMenu"]) {
163 [
self showSystemContextMenu:args];
165 }
else if ([method isEqualToString:
@"ContextMenu.hideSystemContextMenu"]) {
166 [
self hideSystemContextMenu];
173- (void)showSystemContextMenu:(NSDictionary*)args {
174 if (@available(iOS 16.0, *)) {
177 if (!shownEditMenu) {
178 [FlutterLogger logError:@"Only text input supports system context menu for now. Ensure the "
179 "system context menu is shown with an active text input connection. "
180 "See https://github.com/flutter/flutter/issues/143033."];
185- (void)hideSystemContextMenu {
186 if (@available(iOS 16.0, *)) {
188 [textInputPlugin hideEditMenu];
192- (void)showShareViewController:(NSString*)content {
193 UIViewController* engineViewController = [
self.engine viewController];
195 NSArray* itemsToShare = @[ content ?: [NSNull null] ];
196 UIActivityViewController* activityViewController =
197 [[UIActivityViewController alloc] initWithActivityItems:itemsToShare
198 applicationActivities:nil];
200 if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
209 caretRectForPosition:(FlutterTextPosition*)range.start];
211 localRectFromFrameworkTransform:firstRect];
213 caretRectForPosition:(FlutterTextPosition*)range.end];
215 localRectFromFrameworkTransform:lastRect];
217 activityViewController.popoverPresentationController.sourceView = engineViewController.view;
219 activityViewController.popoverPresentationController.sourceRect =
220 CGRectMake(fmin(transformedFirstRect.origin.x, transformedLastRect.origin.x),
221 transformedFirstRect.origin.y,
222 abs(transformedLastRect.origin.x - transformedFirstRect.origin.x),
223 transformedFirstRect.size.height);
226 [engineViewController presentViewController:activityViewController animated:YES completion:nil];
229- (void)searchWeb:(NSString*)searchTerm {
231 if (flutterApplication == nil) {
232 [FlutterLogger logWarning:@"SearchWeb.invoke is not availabe in app extension."];
236 NSString* escapedText = [searchTerm
237 stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet
238 URLHostAllowedCharacterSet]];
239 NSString* searchURL = [NSString stringWithFormat:@"%@%@", kSearchURLPrefix, escapedText];
241 [flutterApplication openURL:[NSURL URLWithString:searchURL] options:@{} completionHandler:nil];
244- (void)playSystemSound:(NSString*)soundType {
245 if ([soundType isEqualToString:
@"SystemSoundType.click"]) {
248 AudioServicesPlaySystemSound(kKeyPressClickSoundId);
249 }
else if ([soundType isEqualToString:
@"SystemSoundType.tick"]) {
250 AudioServicesPlaySystemSound(kWheelsOfTimeSoundId);
254- (void)vibrateHapticFeedback:(NSString*)feedbackType {
256 AudioServicesPlaySystemSound(kSystemSoundID_Vibrate);
260 if ([
@"HapticFeedbackType.lightImpact" isEqualToString:feedbackType]) {
261 [[[UIImpactFeedbackGenerator alloc] initWithStyle:UIImpactFeedbackStyleLight] impactOccurred];
262 }
else if ([
@"HapticFeedbackType.mediumImpact" isEqualToString:feedbackType]) {
263 [[[UIImpactFeedbackGenerator alloc] initWithStyle:UIImpactFeedbackStyleMedium] impactOccurred];
264 }
else if ([
@"HapticFeedbackType.heavyImpact" isEqualToString:feedbackType]) {
265 [[[UIImpactFeedbackGenerator alloc] initWithStyle:UIImpactFeedbackStyleHeavy] impactOccurred];
266 }
else if ([
@"HapticFeedbackType.selectionClick" isEqualToString:feedbackType]) {
267 [[[UISelectionFeedbackGenerator alloc] init] selectionChanged];
268 }
else if ([
@"HapticFeedbackType.successNotification" isEqualToString:feedbackType]) {
269 [[[UINotificationFeedbackGenerator alloc] init]
270 notificationOccurred:UINotificationFeedbackTypeSuccess];
271 }
else if ([
@"HapticFeedbackType.warningNotification" isEqualToString:feedbackType]) {
272 [[[UINotificationFeedbackGenerator alloc] init]
273 notificationOccurred:UINotificationFeedbackTypeWarning];
274 }
else if ([
@"HapticFeedbackType.errorNotification" isEqualToString:feedbackType]) {
275 [[[UINotificationFeedbackGenerator alloc] init]
276 notificationOccurred:UINotificationFeedbackTypeError];
280- (void)setSystemChromePreferredOrientations:(NSArray*)orientations {
281 UIInterfaceOrientationMask mask = 0;
283 if (orientations.count == 0) {
284 mask |= UIInterfaceOrientationMaskAll;
286 for (NSString* orientation in orientations) {
287 if ([orientation isEqualToString:
@"DeviceOrientation.portraitUp"]) {
288 mask |= UIInterfaceOrientationMaskPortrait;
289 }
else if ([orientation isEqualToString:
@"DeviceOrientation.portraitDown"]) {
290 mask |= UIInterfaceOrientationMaskPortraitUpsideDown;
291 }
else if ([orientation isEqualToString:
@"DeviceOrientation.landscapeLeft"]) {
292 mask |= UIInterfaceOrientationMaskLandscapeLeft;
293 }
else if ([orientation isEqualToString:
@"DeviceOrientation.landscapeRight"]) {
294 mask |= UIInterfaceOrientationMaskLandscapeRight;
302 [[NSNotificationCenter defaultCenter]
303 postNotificationName:@(kOrientationUpdateNotificationName)
305 userInfo:@{@(kOrientationUpdateNotificationKey) : @(mask)}];
308- (void)setSystemChromeApplicationSwitcherDescription:(NSDictionary*)object {
312- (void)setSystemChromeEnabledSystemUIOverlays:(NSArray*)overlays {
313 BOOL statusBarShouldBeHidden = ![overlays containsObject:@"SystemUiOverlay.top"];
314 if ([overlays containsObject:
@"SystemUiOverlay.bottom"]) {
315 [[NSNotificationCenter defaultCenter]
316 postNotificationName:FlutterViewControllerShowHomeIndicator
319 [[NSNotificationCenter defaultCenter]
320 postNotificationName:FlutterViewControllerHideHomeIndicator
323 if (
self.enableViewControllerBasedStatusBarAppearance) {
324 [
self.engine viewController].prefersStatusBarHidden = statusBarShouldBeHidden;
336- (void)setSystemChromeEnabledSystemUIMode:(NSString*)mode {
337 BOOL edgeToEdge = [mode isEqualToString:@"SystemUiMode.edgeToEdge"];
338 if (
self.enableViewControllerBasedStatusBarAppearance) {
339 [
self.engine viewController].prefersStatusBarHidden = !edgeToEdge;
349 [[NSNotificationCenter defaultCenter]
350 postNotificationName:edgeToEdge ? FlutterViewControllerShowHomeIndicator
351 : FlutterViewControllerHideHomeIndicator
355- (void)restoreSystemChromeSystemUIOverlays {
359- (void)setSystemChromeSystemUIOverlayStyle:(NSDictionary*)message {
360 NSString* brightness =
message[@"statusBarBrightness"];
361 if (brightness == (
id)[NSNull null]) {
365 UIStatusBarStyle statusBarStyle;
366 if ([brightness isEqualToString:
@"Brightness.dark"]) {
367 statusBarStyle = UIStatusBarStyleLightContent;
368 }
else if ([brightness isEqualToString:
@"Brightness.light"]) {
369 statusBarStyle = UIStatusBarStyleDarkContent;
374 if (
self.enableViewControllerBasedStatusBarAppearance) {
376 [[NSNotificationCenter defaultCenter]
377 postNotificationName:@(kOverlayStyleUpdateNotificationName)
379 userInfo:@{@(kOverlayStyleUpdateNotificationKey) : @(statusBarStyle)}];
385- (void)popSystemNavigator:(
BOOL)isAnimated {
393 UINavigationController* navigationController = [engineViewController navigationController];
394 if (navigationController) {
395 [navigationController popViewControllerAnimated:isAnimated];
397 UIViewController* rootViewController = nil;
399 if (flutterApplication) {
400 rootViewController = flutterApplication.keyWindow.rootViewController;
402 if (@available(iOS 15.0, *)) {
404 [engineViewController flutterWindowSceneIfViewLoaded].keyWindow.rootViewController;
406 [FlutterLogger logWarning:@"rootViewController is not available in application extension "
407 "prior to iOS 15.0."];
411 if (engineViewController != rootViewController) {
412 [engineViewController dismissViewControllerAnimated:isAnimated completion:nil];
417- (NSDictionary*)getClipboardData:(NSString*)format {
418 UIPasteboard* pasteboard = [UIPasteboard generalPasteboard];
420 NSString* stringInPasteboard = pasteboard.string;
422 return stringInPasteboard == nil ? nil : @{
@"text" : stringInPasteboard};
427- (void)setClipboardData:(NSDictionary*)data {
428 UIPasteboard* pasteboard = [UIPasteboard generalPasteboard];
429 id copyText =
data[@"text"];
430 if ([copyText isKindOfClass:[NSString class]]) {
431 pasteboard.string = copyText;
433 pasteboard.string =
@"null";
437- (NSDictionary*)clipboardHasStrings {
438 return @{
@"value" : @([UIPasteboard generalPasteboard].hasStrings)};
441- (
BOOL)isLiveTextInputAvailable {
442 return [[
self textField] canPerformAction:@selector(captureTextFromCamera:) withSender:nil];
445- (void)showLookUpViewController:(NSString*)term {
446 UIViewController* engineViewController = [
self.engine viewController];
447 UIReferenceLibraryViewController* referenceLibraryViewController =
448 [[UIReferenceLibraryViewController alloc] initWithTerm:term];
449 [engineViewController presentViewController:referenceLibraryViewController
454- (UITextField*)textField {
455 if (_textField == nil) {
456 _textField = [[UITextField alloc] init];
void(^ FlutterResult)(id _Nullable result)
FLUTTER_DARWIN_EXPORT NSObject const * FlutterMethodNotImplemented
G_BEGIN_DECLS G_MODULE_EXPORT FlValue * args
G_BEGIN_DECLS GBytes * message
#define FML_DCHECK(condition)
UIApplication * application
UIView< UITextInput > * textInputView()
BOOL showEditMenu:(ios(16.0) API_AVAILABLE)
FlutterTextInputPlugin * textInputPlugin
FlutterTextInputPlugin * _textInputPlugin
constexpr char kTextPlainFormat[]
Clipboard plain text format.
const UInt32 kKeyPressClickSoundId
NSString *const kSearchURLPrefix
const UInt32 kWheelsOfTimeSoundId
const char *const kOrientationUpdateNotificationKey
const char *const kOverlayStyleUpdateNotificationName
const char *const kOverlayStyleUpdateNotificationKey
const char *const kOrientationUpdateNotificationName
std::shared_ptr< const fml::Mapping > data