From 9ae7979a52e0f262ea733244d928a70ddfbc55ec Mon Sep 17 00:00:00 2001 From: Chris Yang Date: Wed, 7 Jun 2023 14:40:16 -0700 Subject: [PATCH 1/6] view controller based status bar fiox rename test --- .../framework/Source/FlutterPlatformPlugin.h | 2 + .../framework/Source/FlutterPlatformPlugin.mm | 76 ++++++++++------ .../Source/FlutterPlatformPluginTest.mm | 90 +++++++++++++++++++ .../framework/Source/FlutterViewController.mm | 36 ++++++++ 4 files changed, 177 insertions(+), 27 deletions(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterPlatformPlugin.h b/shell/platform/darwin/ios/framework/Source/FlutterPlatformPlugin.h index 41dd485dc2c41..1b3304451a940 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterPlatformPlugin.h +++ b/shell/platform/darwin/ios/framework/Source/FlutterPlatformPlugin.h @@ -23,6 +23,8 @@ extern const char* const kOrientationUpdateNotificationName; extern const char* const kOrientationUpdateNotificationKey; extern const char* const kOverlayStyleUpdateNotificationName; extern const char* const kOverlayStyleUpdateNotificationKey; +extern const char* const kStatusBarHiddenUpdateNotificationName; +extern const char* const kStatusBarHiddenUpdateNotificationKey; } // namespace flutter diff --git a/shell/platform/darwin/ios/framework/Source/FlutterPlatformPlugin.mm b/shell/platform/darwin/ios/framework/Source/FlutterPlatformPlugin.mm index 3e81a39f2c779..c77d2dd63683a 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterPlatformPlugin.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterPlatformPlugin.mm @@ -30,11 +30,21 @@ "io.flutter.plugin.platform.SystemChromeOverlayNotificationName"; const char* const kOverlayStyleUpdateNotificationKey = "io.flutter.plugin.platform.SystemChromeOverlayNotificationKey"; +const char* const kStatusBarHiddenUpdateNotificationName = + "io.flutter.plugin.platform.StatusBarHiddenNotificationName"; +const char* const kStatusBarHiddenUpdateNotificationKey = + "io.flutter.plugin.platform.StatusBarHiddenNotificationKey"; } // namespace flutter using namespace flutter; +@interface FlutterPlatformPlugin () + +@property(nonatomic, assign) BOOL enableViewControllerBasedStatusBarAppearance; + +@end + @implementation FlutterPlatformPlugin { fml::WeakPtr _engine; // Used to detect whether this device has live text input ability or not. @@ -47,6 +57,9 @@ - (instancetype)initWithEngine:(fml::WeakPtr)engine { if (self) { _engine = engine; + NSNumber* infoValue = [[NSBundle mainBundle] + objectForInfoDictionaryKey:@"UIViewControllerBasedStatusBarAppearance"]; + _enableViewControllerBasedStatusBarAppearance = (infoValue == nil || [infoValue boolValue]); } return self; @@ -158,14 +171,7 @@ - (void)setSystemChromeApplicationSwitcherDescription:(NSDictionary*)object { } - (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"]; + BOOL statusBarShouldBeHidden = ![overlays containsObject:@"SystemUiOverlay.top"]; if ([overlays containsObject:@"SystemUiOverlay.bottom"]) { [[NSNotificationCenter defaultCenter] postNotificationName:FlutterViewControllerShowHomeIndicator @@ -175,26 +181,46 @@ - (void)setSystemChromeEnabledSystemUIOverlays:(NSArray*)overlays { postNotificationName:FlutterViewControllerHideHomeIndicator object:nil]; } + if (self.enableViewControllerBasedStatusBarAppearance) { + // This notification is respected by the iOS embedder + [[NSNotificationCenter defaultCenter] + postNotificationName:@(kStatusBarHiddenUpdateNotificationName) + object:nil + userInfo:@{ + @(kStatusBarHiddenUpdateNotificationKey) : @(statusBarShouldBeHidden) + }]; + } else { + // 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 = statusBarShouldBeHidden; + } } - (void)setSystemChromeEnabledSystemUIMode:(NSString*)mode { - // Checks if the top status bar should be visible, reflected by edge to edge setting. This - // platform ignores all other system ui modes. - - // 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 = - ![mode isEqualToString:@"SystemUiMode.edgeToEdge"]; - if ([mode isEqualToString:@"SystemUiMode.edgeToEdge"]) { + BOOL edgeToEdge = [mode isEqualToString:@"SystemUiMode.edgeToEdge"]; + if (self.enableViewControllerBasedStatusBarAppearance) { + // This notification is respected by the iOS embedder [[NSNotificationCenter defaultCenter] - postNotificationName:FlutterViewControllerShowHomeIndicator - object:nil]; + postNotificationName:@(kStatusBarHiddenUpdateNotificationName) + object:nil + userInfo:@{@(kStatusBarHiddenUpdateNotificationKey) : @(!edgeToEdge)}]; } else { - [[NSNotificationCenter defaultCenter] - postNotificationName:FlutterViewControllerHideHomeIndicator - object:nil]; + // Checks if the top status bar should be visible, reflected by edge to edge setting. This + // platform ignores all other system ui modes. + + // 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 = !edgeToEdge; } + [[NSNotificationCenter defaultCenter] + postNotificationName:edgeToEdge ? FlutterViewControllerShowHomeIndicator + : FlutterViewControllerHideHomeIndicator + object:nil]; } - (void)restoreSystemChromeSystemUIOverlays { @@ -220,11 +246,7 @@ - (void)setSystemChromeSystemUIOverlayStyle:(NSDictionary*)message { return; } - NSNumber* infoValue = [[NSBundle mainBundle] - objectForInfoDictionaryKey:@"UIViewControllerBasedStatusBarAppearance"]; - Boolean delegateToViewController = (infoValue == nil || [infoValue boolValue]); - - if (delegateToViewController) { + if (self.enableViewControllerBasedStatusBarAppearance) { // This notification is respected by the iOS embedder [[NSNotificationCenter defaultCenter] postNotificationName:@(kOverlayStyleUpdateNotificationName) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterPlatformPluginTest.mm b/shell/platform/darwin/ios/framework/Source/FlutterPlatformPluginTest.mm index fec7983d7a8c2..b07261352abaf 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterPlatformPluginTest.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterPlatformPluginTest.mm @@ -8,6 +8,7 @@ #import "flutter/shell/platform/darwin/common/framework/Headers/FlutterBinaryMessenger.h" #import "flutter/shell/platform/darwin/common/framework/Headers/FlutterMacros.h" #import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterViewController.h" +#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterEngine_Internal.h" #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformPlugin.h" #import "flutter/shell/platform/darwin/ios/platform_view_ios.h" @@ -138,4 +139,93 @@ - (void)testWhetherDeviceHasLiveTextInputInvokeCorrectly { [self waitForExpectationsWithTimeout:1 handler:nil]; } +- (void)testViewControllerBasedStatusBarHiddenUpdate { + id bundleMock = OCMPartialMock([NSBundle mainBundle]); + OCMStub([bundleMock objectForInfoDictionaryKey:@"UIViewControllerBasedStatusBarAppearance"]) + .andReturn(@YES); + { + // Enabling system UI overlays to update status bar. + FlutterEngine* engine = [[[FlutterEngine alloc] initWithName:@"test" project:nil] autorelease]; + [engine runWithEntrypoint:nil]; + FlutterViewController* flutterViewController = + [[FlutterViewController alloc] initWithEngine:engine nibName:nil bundle:nil]; + std::unique_ptr> _weakFactory = + std::make_unique>(engine); + XCTAssertFalse(flutterViewController.prefersStatusBarHidden); + + // Update to hidden. + FlutterPlatformPlugin* plugin = [engine platformPlugin]; + + XCTestExpectation* enableSystemUIOverlaysCalled = + [self expectationWithDescription:@"setEnabledSystemUIOverlays"]; + FlutterResult resultSet = ^(id result) { + [enableSystemUIOverlaysCalled fulfill]; + }; + FlutterMethodCall* methodCallSet = + [FlutterMethodCall methodCallWithMethodName:@"SystemChrome.setEnabledSystemUIOverlays" + arguments:@[ @"SystemUiOverlay.bottom" ]]; + [plugin handleMethodCall:methodCallSet result:resultSet]; + [self waitForExpectationsWithTimeout:1 handler:nil]; + XCTAssertTrue(flutterViewController.prefersStatusBarHidden); + + // Update to shown. + XCTestExpectation* enableSystemUIOverlaysCalled2 = + [self expectationWithDescription:@"setEnabledSystemUIOverlays"]; + FlutterResult resultSet2 = ^(id result) { + [enableSystemUIOverlaysCalled2 fulfill]; + }; + FlutterMethodCall* methodCallSet2 = + [FlutterMethodCall methodCallWithMethodName:@"SystemChrome.setEnabledSystemUIOverlays" + arguments:@[ @"SystemUiOverlay.top" ]]; + [plugin handleMethodCall:methodCallSet2 result:resultSet2]; + [self waitForExpectationsWithTimeout:1 handler:nil]; + XCTAssertFalse(flutterViewController.prefersStatusBarHidden); + + [flutterViewController deregisterNotifications]; + [flutterViewController release]; + } + { + // Enable system UI mode to update status bar. + FlutterEngine* engine = [[[FlutterEngine alloc] initWithName:@"test" project:nil] autorelease]; + [engine runWithEntrypoint:nil]; + FlutterViewController* flutterViewController = + [[FlutterViewController alloc] initWithEngine:engine nibName:nil bundle:nil]; + std::unique_ptr> _weakFactory = + std::make_unique>(engine); + XCTAssertFalse(flutterViewController.prefersStatusBarHidden); + + // Update to hidden. + FlutterPlatformPlugin* plugin = [engine platformPlugin]; + + XCTestExpectation* enableSystemUIModeCalled = + [self expectationWithDescription:@"setEnabledSystemUIMode"]; + FlutterResult resultSet = ^(id result) { + [enableSystemUIModeCalled fulfill]; + }; + FlutterMethodCall* methodCallSet = + [FlutterMethodCall methodCallWithMethodName:@"SystemChrome.setEnabledSystemUIMode" + arguments:@"SystemUiMode.immersive"]; + [plugin handleMethodCall:methodCallSet result:resultSet]; + [self waitForExpectationsWithTimeout:1 handler:nil]; + XCTAssertTrue(flutterViewController.prefersStatusBarHidden); + + // Update to shown. + XCTestExpectation* enableSystemUIModeCalled2 = + [self expectationWithDescription:@"setEnabledSystemUIMode"]; + FlutterResult resultSet2 = ^(id result) { + [enableSystemUIModeCalled2 fulfill]; + }; + FlutterMethodCall* methodCallSet2 = + [FlutterMethodCall methodCallWithMethodName:@"SystemChrome.setEnabledSystemUIMode" + arguments:@"SystemUiMode.edgeToEdge"]; + [plugin handleMethodCall:methodCallSet2 result:resultSet2]; + [self waitForExpectationsWithTimeout:1 handler:nil]; + XCTAssertFalse(flutterViewController.prefersStatusBarHidden); + + [flutterViewController deregisterNotifications]; + [flutterViewController release]; + } + [bundleMock stopMocking]; +} + @end diff --git a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm index 1831b4d2f546b..63771af3769a9 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm @@ -103,6 +103,13 @@ @interface FlutterViewController ()