| Flutter iOS Embedder |

FlutterPlatformPlugin.mm
Go to the documentation of this file.
1 // Copyright 2013 The Flutter Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformPlugin.h"
6
7 #import <AudioToolbox/AudioToolbox.h>
8 #import <Foundation/Foundation.h>
9 #import <UIKit/UIApplication.h>
10 #import <UIKit/UIKit.h>
11
12 #include "flutter/fml/logging.h"
13 #import "flutter/shell/platform/darwin/common/InternalFlutterSwiftCommon/InternalFlutterSwiftCommon.h"
14 #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterEngine_Internal.h"
15 #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterSharedApplication.h"
16 #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h"
17 #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterViewController_Internal.h"
18 #import "flutter/shell/platform/darwin/ios/framework/Source/UIViewController+FlutterScreenAndSceneIfLoaded.h"
19
20 FLUTTER_ASSERT_ARC
21
22 namespace {
23
24 constexpr char kTextPlainFormat[] = "text/plain";
25 // Some of the official iOS system sounds. A full list can be found in many places online, such as:
26 // https://github.com/p-x9/swift-system-sound/blob/cb4327b223d55d01e9156539c8442db16f4b1f85/SystemSoundTable.md
27 const UInt32 kKeyPressClickSoundId = 1306;
28 const UInt32 kWheelsOfTimeSoundId = 1157;
29
30 NSString* const kSearchURLPrefix = @"x-web-search://?";
31
32 } // namespace
33
34 namespace flutter {
35
36 // TODO(abarth): Move these definitions from system_chrome_impl.cc to here.
37 const char* const kOrientationUpdateNotificationName =
38 "io.flutter.plugin.platform.SystemChromeOrientationNotificationName";
39 const char* const kOrientationUpdateNotificationKey =
40 "io.flutter.plugin.platform.SystemChromeOrientationNotificationKey";
41 const char* const kOverlayStyleUpdateNotificationName =
42 "io.flutter.plugin.platform.SystemChromeOverlayNotificationName";
43 const char* const kOverlayStyleUpdateNotificationKey =
44 "io.flutter.plugin.platform.SystemChromeOverlayNotificationKey";
45
46 } // namespace flutter
47
48 using namespace flutter;
49
50 static void SetStatusBarHiddenForSharedApplication(BOOL hidden) {
51 UIApplication* flutterApplication = FlutterSharedApplication.application;
52 if (flutterApplication) {
53 flutterApplication.statusBarHidden = hidden;
54 } else {
55 [FlutterLogger logWarning:@"Application based status bar styling is not available in app "
56 "extension."];
57 }
58 }
59
60 static void SetStatusBarStyleForSharedApplication(UIStatusBarStyle style) {
61 UIApplication* flutterApplication = FlutterSharedApplication.application;
62 if (flutterApplication) {
63 // Note: -[UIApplication setStatusBarStyle] is deprecated in iOS9
64 // in favor of delegating to the view controller.
65 [flutterApplication setStatusBarStyle:style];
66 } else {
67 [FlutterLogger logWarning:@"Application based status bar styling is not available in app "
68 "extension."];
69 }
70 }
71
72 @interface FlutterPlatformPlugin ()
73
74 /**
75 * @brief Whether the status bar appearance is based on the style preferred for this ViewController.
76 *
77 * The default value is YES.
78 * Explicitly add `UIViewControllerBasedStatusBarAppearance` as `false` in
79 * info.plist makes this value to be false.
80 */
81 @property(nonatomic, assign) BOOL enableViewControllerBasedStatusBarAppearance;
82 @property(nonatomic, weak) FlutterEngine* engine;
83
84 /**
85 * @brief Used to detect whether or not this device supports live text input from the camera.
86 */
87 @property(nonatomic, strong) UITextField* textField;
88 @end
89
90 @implementation FlutterPlatformPlugin
91
92 - (instancetype)initWithEngine:(FlutterEngine*)engine {
93 FML_DCHECK(engine) << "engine must be set";
94 self = [super init];
95
96 if (self) {
97 _engine = engine;
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."];
104 }
105 #endif
106 _enableViewControllerBasedStatusBarAppearance =
107 (infoValue == nil || [(NSNumber*)infoValue boolValue]);
108 }
109
110 return self;
111 }
112
113 - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result {
114 NSString* method = call.method;
115 id args = call.arguments;
116 if ([method isEqualToString:@"SystemSound.play"]) {
117 [self playSystemSound:args];
118 result(nil);
119 } else if ([method isEqualToString:@"HapticFeedback.vibrate"]) {
120 [self vibrateHapticFeedback:args];
121 result(nil);
122 } else if ([method isEqualToString:@"SystemChrome.setPreferredOrientations"]) {
123 [self setSystemChromePreferredOrientations:args];
124 result(nil);
125 } else if ([method isEqualToString:@"SystemChrome.setApplicationSwitcherDescription"]) {
126 [self setSystemChromeApplicationSwitcherDescription:args];
127 result(nil);
128 } else if ([method isEqualToString:@"SystemChrome.setEnabledSystemUIOverlays"]) {
129 [self setSystemChromeEnabledSystemUIOverlays:args];
130 result(nil);
131 } else if ([method isEqualToString:@"SystemChrome.setEnabledSystemUIMode"]) {
132 [self setSystemChromeEnabledSystemUIMode:args];
133 result(nil);
134 } else if ([method isEqualToString:@"SystemChrome.restoreSystemUIOverlays"]) {
135 [self restoreSystemChromeSystemUIOverlays];
136 result(nil);
137 } else if ([method isEqualToString:@"SystemChrome.setSystemUIOverlayStyle"]) {
138 [self setSystemChromeSystemUIOverlayStyle:args];
139 result(nil);
140 } else if ([method isEqualToString:@"SystemNavigator.pop"]) {
141 NSNumber* isAnimated = args;
142 [self popSystemNavigator:isAnimated.boolValue];
143 result(nil);
144 } else if ([method isEqualToString:@"Clipboard.getData"]) {
145 result([self getClipboardData:args]);
146 } else if ([method isEqualToString:@"Clipboard.setData"]) {
147 [self setClipboardData:args];
148 result(nil);
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];
155 result(nil);
156 } else if ([method isEqualToString:@"LookUp.invoke"]) {
157 [self showLookUpViewController:args];
158 result(nil);
159 } else if ([method isEqualToString:@"Share.invoke"]) {
160 [self showShareViewController:args];
161 result(nil);
162 } else if ([method isEqualToString:@"ContextMenu.showSystemContextMenu"]) {
163 [self showSystemContextMenu:args];
164 result(nil);
165 } else if ([method isEqualToString:@"ContextMenu.hideSystemContextMenu"]) {
166 [self hideSystemContextMenu];
167 result(nil);
168 } else {
169 result(FlutterMethodNotImplemented);
170 }
171 }
172
173 - (void)showSystemContextMenu:(NSDictionary*)args {
174 if (@available(iOS 16.0, *)) {
175 FlutterTextInputPlugin* textInputPlugin = [self.engine textInputPlugin];
176 BOOL shownEditMenu = [textInputPlugin showEditMenu:args];
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."];
181 }
182 }
183 }
184
185 - (void)hideSystemContextMenu {
186 if (@available(iOS 16.0, *)) {
187 FlutterTextInputPlugin* textInputPlugin = [self.engine textInputPlugin];
188 [textInputPlugin hideEditMenu];
189 }
190 }
191
192 - (void)showShareViewController:(NSString*)content {
193 UIViewController* engineViewController = [self.engine viewController];
194
195 NSArray* itemsToShare = @[ content ?: [NSNull null] ];
196 UIActivityViewController* activityViewController =
197 [[UIActivityViewController alloc] initWithActivityItems:itemsToShare
198 applicationActivities:nil];
199
200 if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
201 // On iPad, the share screen is presented in a popover view, and requires a
202 // sourceView and sourceRect
203 FlutterTextInputPlugin* _textInputPlugin = [self.engine textInputPlugin];
204 UITextRange* range = _textInputPlugin.textInputView.selectedTextRange;
205
206 // firstRectForRange cannot be used here as it's current implementation does
207 // not always return the full rect of the range.
208 CGRect firstRect = [(FlutterTextInputView*)_textInputPlugin.textInputView
209 caretRectForPosition:(FlutterTextPosition*)range.start];
210 CGRect transformedFirstRect = [(FlutterTextInputView*)_textInputPlugin.textInputView
211 localRectFromFrameworkTransform:firstRect];
212 CGRect lastRect = [(FlutterTextInputView*)_textInputPlugin.textInputView
213 caretRectForPosition:(FlutterTextPosition*)range.end];
214 CGRect transformedLastRect = [(FlutterTextInputView*)_textInputPlugin.textInputView
215 localRectFromFrameworkTransform:lastRect];
216
217 activityViewController.popoverPresentationController.sourceView = engineViewController.view;
218 // In case of RTL Language, get the minimum x coordinate
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);
224 }
225
226 [engineViewController presentViewController:activityViewController animated:YES completion:nil];
227 }
228
229 - (void)searchWeb:(NSString*)searchTerm {
230 UIApplication* flutterApplication = FlutterSharedApplication.application;
231 if (flutterApplication == nil) {
232 [FlutterLogger logWarning:@"SearchWeb.invoke is not availabe in app extension."];
233 return;
234 }
235
236 NSString* escapedText = [searchTerm
237 stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet
238 URLHostAllowedCharacterSet]];
239 NSString* searchURL = [NSString stringWithFormat:@"%@%@", kSearchURLPrefix, escapedText];
240
241 [flutterApplication openURL:[NSURL URLWithString:searchURL] options:@{} completionHandler:nil];
242 }
243
244 - (void)playSystemSound:(NSString*)soundType {
245 if ([soundType isEqualToString:@"SystemSoundType.click"]) {
246 // All feedback types are specific to Android and are treated as equal on
247 // iOS.
248 AudioServicesPlaySystemSound(kKeyPressClickSoundId);
249 } else if ([soundType isEqualToString:@"SystemSoundType.tick"]) {
250 AudioServicesPlaySystemSound(kWheelsOfTimeSoundId);
251 }
252 }
253
254 - (void)vibrateHapticFeedback:(NSString*)feedbackType {
255 if (!feedbackType) {
256 AudioServicesPlaySystemSound(kSystemSoundID_Vibrate);
257 return;
258 }
259
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];
277 }
278 }
279
280 - (void)setSystemChromePreferredOrientations:(NSArray*)orientations {
281 UIInterfaceOrientationMask mask = 0;
282
283 if (orientations.count == 0) {
284 mask |= UIInterfaceOrientationMaskAll;
285 } else {
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;
295 }
296 }
297 }
298
299 if (!mask) {
300 return;
301 }
302 [[NSNotificationCenter defaultCenter]
303 postNotificationName:@(kOrientationUpdateNotificationName)
304 object:nil
305 userInfo:@{@(kOrientationUpdateNotificationKey) : @(mask)}];
306 }
307
308 - (void)setSystemChromeApplicationSwitcherDescription:(NSDictionary*)object {
309 // No counterpart on iOS but is a benign operation. So no asserts.
310 }
311
312 - (void)setSystemChromeEnabledSystemUIOverlays:(NSArray*)overlays {
313 BOOL statusBarShouldBeHidden = ![overlays containsObject:@"SystemUiOverlay.top"];
314 if ([overlays containsObject:@"SystemUiOverlay.bottom"]) {
315 [[NSNotificationCenter defaultCenter]
316 postNotificationName:FlutterViewControllerShowHomeIndicator
317 object:nil];
318 } else {
319 [[NSNotificationCenter defaultCenter]
320 postNotificationName:FlutterViewControllerHideHomeIndicator
321 object:nil];
322 }
323 if (self.enableViewControllerBasedStatusBarAppearance) {
324 [self.engine viewController].prefersStatusBarHidden = statusBarShouldBeHidden;
325 } else {
326 // Checks if the top status bar should be visible. This platform ignores all
327 // other overlays
328
329 // We opt out of view controller based status bar visibility since we want
330 // to be able to modify this on the fly. The key used is
331 // UIViewControllerBasedStatusBarAppearance.
332 SetStatusBarHiddenForSharedApplication(statusBarShouldBeHidden);
333 }
334 }
335
336 - (void)setSystemChromeEnabledSystemUIMode:(NSString*)mode {
337 BOOL edgeToEdge = [mode isEqualToString:@"SystemUiMode.edgeToEdge"];
338 if (self.enableViewControllerBasedStatusBarAppearance) {
339 [self.engine viewController].prefersStatusBarHidden = !edgeToEdge;
340 } else {
341 // Checks if the top status bar should be visible, reflected by edge to edge setting. This
342 // platform ignores all other system ui modes.
343
344 // We opt out of view controller based status bar visibility since we want
345 // to be able to modify this on the fly. The key used is
346 // UIViewControllerBasedStatusBarAppearance.
347 SetStatusBarHiddenForSharedApplication(!edgeToEdge);
348 }
349 [[NSNotificationCenter defaultCenter]
350 postNotificationName:edgeToEdge ? FlutterViewControllerShowHomeIndicator
351 : FlutterViewControllerHideHomeIndicator
352 object:nil];
353 }
354
355 - (void)restoreSystemChromeSystemUIOverlays {
356 // Nothing to do on iOS.
357 }
358
359 - (void)setSystemChromeSystemUIOverlayStyle:(NSDictionary*)message {
360 NSString* brightness = message[@"statusBarBrightness"];
361 if (brightness == (id)[NSNull null]) {
362 return;
363 }
364
365 UIStatusBarStyle statusBarStyle;
366 if ([brightness isEqualToString:@"Brightness.dark"]) {
367 statusBarStyle = UIStatusBarStyleLightContent;
368 } else if ([brightness isEqualToString:@"Brightness.light"]) {
369 statusBarStyle = UIStatusBarStyleDarkContent;
370 } else {
371 return;
372 }
373
374 if (self.enableViewControllerBasedStatusBarAppearance) {
375 // This notification is respected by the iOS embedder.
376 [[NSNotificationCenter defaultCenter]
377 postNotificationName:@(kOverlayStyleUpdateNotificationName)
378 object:nil
379 userInfo:@{@(kOverlayStyleUpdateNotificationKey) : @(statusBarStyle)}];
380 } else {
381 SetStatusBarStyleForSharedApplication(statusBarStyle);
382 }
383 }
384
385 - (void)popSystemNavigator:(BOOL)isAnimated {
386 // Apple's human user guidelines say not to terminate iOS applications. However, if the
387 // root view of the app is a navigation controller, it is instructed to back up a level
388 // in the navigation hierarchy.
389 // It's also possible in an Add2App scenario that the FlutterViewController was presented
390 // outside the context of a UINavigationController, and still wants to be popped.
391
392 FlutterViewController* engineViewController = [self.engine viewController];
393 UINavigationController* navigationController = [engineViewController navigationController];
394 if (navigationController) {
395 [navigationController popViewControllerAnimated:isAnimated];
396 } else {
397 UIViewController* rootViewController = nil;
398 UIApplication* flutterApplication = FlutterSharedApplication.application;
399 if (flutterApplication) {
400 rootViewController = flutterApplication.keyWindow.rootViewController;
401 } else {
402 if (@available(iOS 15.0, *)) {
403 rootViewController =
404 [engineViewController flutterWindowSceneIfViewLoaded].keyWindow.rootViewController;
405 } else {
406 [FlutterLogger logWarning:@"rootViewController is not available in application extension "
407 "prior to iOS 15.0."];
408 }
409 }
410
411 if (engineViewController != rootViewController) {
412 [engineViewController dismissViewControllerAnimated:isAnimated completion:nil];
413 }
414 }
415 }
416
417 - (NSDictionary*)getClipboardData:(NSString*)format {
418 UIPasteboard* pasteboard = [UIPasteboard generalPasteboard];
419 if (!format || [format isEqualToString:@(kTextPlainFormat)]) {
420 NSString* stringInPasteboard = pasteboard.string;
421 // The pasteboard may contain an item but it may not be a string (an image for instance).
422 return stringInPasteboard == nil ? nil : @{@"text" : stringInPasteboard};
423 }
424 return nil;
425 }
426
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;
432 } else {
433 pasteboard.string = @"null";
434 }
435 }
436
437 - (NSDictionary*)clipboardHasStrings {
438 return @{@"value" : @([UIPasteboard generalPasteboard].hasStrings)};
439 }
440
441 - (BOOL)isLiveTextInputAvailable {
442 return [[self textField] canPerformAction:@selector(captureTextFromCamera:) withSender:nil];
443 }
444
445 - (void)showLookUpViewController:(NSString*)term {
446 UIViewController* engineViewController = [self.engine viewController];
447 UIReferenceLibraryViewController* referenceLibraryViewController =
448 [[UIReferenceLibraryViewController alloc] initWithTerm:term];
449 [engineViewController presentViewController:referenceLibraryViewController
450 animated:YES
451 completion:nil];
452 }
453
454 - (UITextField*)textField {
455 if (_textField == nil) {
456 _textField = [[UITextField alloc] init];
457 }
458 return _textField;
459 }
460
461 @end
FlutterResult
void(^ FlutterResult)(id _Nullable result)
Definition: FlutterChannels.h:194
FlutterMethodNotImplemented
FLUTTER_DARWIN_EXPORT NSObject const * FlutterMethodNotImplemented
FlutterEngine_Internal.h
FlutterPlatformPlugin.h
SetStatusBarStyleForSharedApplication
static void SetStatusBarStyleForSharedApplication(UIStatusBarStyle style)
Definition: FlutterPlatformPlugin.mm:60
SetStatusBarHiddenForSharedApplication
static void SetStatusBarHiddenForSharedApplication(BOOL hidden)
Definition: FlutterPlatformPlugin.mm:50
FlutterSharedApplication.h
FlutterTextInputPlugin.h
textInputPlugin
FlutterTextInputPlugin * textInputPlugin
Definition: FlutterTextInputPluginTest.mm:138
engine
id engine
Definition: FlutterTextInputPluginTest.mm:137
FlutterViewController_Internal.h
UIViewController+FlutterScreenAndSceneIfLoaded.h
FlutterTextInputView
Definition: FlutterTextInputPlugin.mm:812
FlutterEngine
Definition: FlutterEngine.h:61
FlutterMethodCall
Definition: FlutterCodecs.h:221
FlutterMethodCall::method
NSString * method
Definition: FlutterCodecs.h:233
FlutterMethodCall::arguments
id arguments
Definition: FlutterCodecs.h:238
FlutterPlatformPlugin
Definition: FlutterPlatformPlugin.h:12
FlutterSharedApplication
Definition: FlutterSharedApplication.h:13
FlutterSharedApplication::application
UIApplication * application
Definition: FlutterSharedApplication.h:27
FlutterTextInputPlugin
Definition: FlutterTextInputPlugin.h:34
-[FlutterTextInputPlugin textInputView]
UIView< UITextInput > * textInputView()
Definition: FlutterTextInputPlugin.mm:2554
-[FlutterTextInputPlugin showEditMenu:]
BOOL showEditMenu:(ios(16.0) API_AVAILABLE)
Definition: FlutterTextInputPlugin.mm:2735
FlutterViewController
Definition: FlutterViewController.h:57
FLUTTER_ASSERT_ARC
Definition: FlutterChannelKeyResponder.mm:13
FLUTTER_ASSERT_ARC::kTextPlainFormat
constexpr char kTextPlainFormat[]
Definition: FlutterPlatformPlugin.mm:24
FLUTTER_ASSERT_ARC::kKeyPressClickSoundId
const UInt32 kKeyPressClickSoundId
Definition: FlutterPlatformPlugin.mm:27
FLUTTER_ASSERT_ARC::kSearchURLPrefix
NSString *const kSearchURLPrefix
Definition: FlutterPlatformPlugin.mm:30
FLUTTER_ASSERT_ARC::kWheelsOfTimeSoundId
const UInt32 kWheelsOfTimeSoundId
Definition: FlutterPlatformPlugin.mm:28
flutter
Definition: accessibility_bridge.h:26
flutter::kOrientationUpdateNotificationKey
const char *const kOrientationUpdateNotificationKey
Definition: FlutterPlatformPlugin.mm:39
flutter::kOverlayStyleUpdateNotificationName
const char *const kOverlayStyleUpdateNotificationName
Definition: FlutterPlatformPlugin.mm:41
flutter::kOverlayStyleUpdateNotificationKey
const char *const kOverlayStyleUpdateNotificationKey
Definition: FlutterPlatformPlugin.mm:43
flutter::kOrientationUpdateNotificationName
const char *const kOrientationUpdateNotificationName
Definition: FlutterPlatformPlugin.mm:37