From d7a4c81223093017ec9ecbb41cc3bdb4d6a94ef1 Mon Sep 17 00:00:00 2001 From: Matt Carroll Date: Tue, 9 Jul 2019 00:44:41 -0700 Subject: [PATCH 1/7] Forwards iOS dark mode trait to the Flutter framework (#34441). --- .../framework/Source/FlutterViewController.mm | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm index f94593913b190..29379f356df3a 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm @@ -411,6 +411,8 @@ - (void)viewWillAppear:(BOOL)animated { [_engine.get() setViewController:self]; _engineNeedsLaunch = NO; } + + [self onUserSettingsChanged:nil]; // Only recreate surface on subsequent appearances when viewport metrics are known. // First time surface creation is done on viewDidLayoutSubviews. @@ -505,11 +507,15 @@ - (void)applicationWillResignActive:(NSNotification*)notification { - (void)applicationDidEnterBackground:(NSNotification*)notification { TRACE_EVENT0("flutter", "applicationDidEnterBackground"); [[_engine.get() lifecycleChannel] sendMessage:@"AppLifecycleState.paused"]; + + [self onUserSettingsChanged:nil]; } - (void)applicationWillEnterForeground:(NSNotification*)notification { TRACE_EVENT0("flutter", "applicationWillEnterForeground"); [[_engine.get() lifecycleChannel] sendMessage:@"AppLifecycleState.inactive"]; + + [self onUserSettingsChanged:nil]; } #pragma mark - Touch event handling @@ -681,6 +687,11 @@ - (void)updateViewportMetrics { [_engine.get() updateViewportMetrics:_viewportMetrics]; } +- (void)updateViewConstraints { + [super updateViewConstraints]; + [self onUserSettingsChanged:nil]; +} + - (CGFloat)statusBarPadding { UIScreen* screen = self.view.window.screen; CGRect statusFrame = [UIApplication sharedApplication].statusBarFrame; @@ -690,6 +701,11 @@ - (CGFloat)statusBarPadding { return CGRectIsNull(intersection) ? 0.0 : intersection.size.height; } +- (void)viewWillLayoutSubviews { + [super viewWillLayoutSubviews]; + [self onUserSettingsChanged:nil]; +} + - (void)viewDidLayoutSubviews { CGSize viewSize = self.view.bounds.size; CGFloat scale = [UIScreen mainScreen].scale; @@ -702,6 +718,7 @@ - (void)viewDidLayoutSubviews { [self updateViewportPadding]; [self updateViewportMetrics]; + [self onUserSettingsChanged:nil]; // This must run after updateViewportMetrics so that the surface creation tasks are queued after // the viewport metrics update tasks. @@ -863,10 +880,16 @@ - (void)onLocaleUpdated:(NSNotification*)notification { #pragma mark - Set user settings +- (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection { + [super traitCollectionDidChange:previousTraitCollection]; + [self onUserSettingsChanged:nil]; +} + - (void)onUserSettingsChanged:(NSNotification*)notification { [[_engine.get() settingsChannel] sendMessage:@{ @"textScaleFactor" : @([self textScaleFactor]), @"alwaysUse24HourFormat" : @([self isAlwaysUse24HourFormat]), + @"platformBrightness" : [self brightnessMode] }]; } @@ -940,6 +963,20 @@ - (BOOL)isAlwaysUse24HourFormat { return [dateFormat rangeOfString:@"a"].location == NSNotFound; } +- (NSString*)brightnessMode { + if (@available(iOS 13, *)) { + UIUserInterfaceStyle style = UITraitCollection.currentTraitCollection.userInterfaceStyle; + + if (style == UIUserInterfaceStyleDark) { + return @"dark"; + } else { + return @"light"; + } + } else { + return @"light"; + } +} + #pragma mark - Status Bar touch event handling // Standard iOS status bar height in pixels. From 95a0329efb6f23bb97b64637e5ed19fa7da4d695 Mon Sep 17 00:00:00 2001 From: Matt Carroll Date: Tue, 9 Jul 2019 01:15:59 -0700 Subject: [PATCH 2/7] Formatting update. --- .../ios/framework/Source/FlutterViewController.mm | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm index 29379f356df3a..da04ed7c657f2 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm @@ -411,7 +411,7 @@ - (void)viewWillAppear:(BOOL)animated { [_engine.get() setViewController:self]; _engineNeedsLaunch = NO; } - + [self onUserSettingsChanged:nil]; // Only recreate surface on subsequent appearances when viewport metrics are known. @@ -688,8 +688,8 @@ - (void)updateViewportMetrics { } - (void)updateViewConstraints { - [super updateViewConstraints]; - [self onUserSettingsChanged:nil]; + [super updateViewConstraints]; + [self onUserSettingsChanged:nil]; } - (CGFloat)statusBarPadding { @@ -881,8 +881,8 @@ - (void)onLocaleUpdated:(NSNotification*)notification { #pragma mark - Set user settings - (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection { - [super traitCollectionDidChange:previousTraitCollection]; - [self onUserSettingsChanged:nil]; + [super traitCollectionDidChange:previousTraitCollection]; + [self onUserSettingsChanged:nil]; } - (void)onUserSettingsChanged:(NSNotification*)notification { @@ -966,7 +966,7 @@ - (BOOL)isAlwaysUse24HourFormat { - (NSString*)brightnessMode { if (@available(iOS 13, *)) { UIUserInterfaceStyle style = UITraitCollection.currentTraitCollection.userInterfaceStyle; - + if (style == UIUserInterfaceStyleDark) { return @"dark"; } else { From 94c04bfee085fda03be3b2623e3a68900ab651a6 Mon Sep 17 00:00:00 2001 From: Matt Carroll Date: Tue, 9 Jul 2019 01:42:15 -0700 Subject: [PATCH 3/7] Removed unnecessary calls to onUserSettingsChanged for Dark Mode. --- .../darwin/ios/framework/Source/FlutterViewController.mm | 6 ------ 1 file changed, 6 deletions(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm index da04ed7c657f2..968c56eac4a35 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm @@ -412,8 +412,6 @@ - (void)viewWillAppear:(BOOL)animated { _engineNeedsLaunch = NO; } - [self onUserSettingsChanged:nil]; - // Only recreate surface on subsequent appearances when viewport metrics are known. // First time surface creation is done on viewDidLayoutSubviews. if (_viewportMetrics.physical_width) { @@ -507,15 +505,11 @@ - (void)applicationWillResignActive:(NSNotification*)notification { - (void)applicationDidEnterBackground:(NSNotification*)notification { TRACE_EVENT0("flutter", "applicationDidEnterBackground"); [[_engine.get() lifecycleChannel] sendMessage:@"AppLifecycleState.paused"]; - - [self onUserSettingsChanged:nil]; } - (void)applicationWillEnterForeground:(NSNotification*)notification { TRACE_EVENT0("flutter", "applicationWillEnterForeground"); [[_engine.get() lifecycleChannel] sendMessage:@"AppLifecycleState.inactive"]; - - [self onUserSettingsChanged:nil]; } #pragma mark - Touch event handling From ba5e02dab1013980c4fbd84c3040e3ff039621db Mon Sep 17 00:00:00 2001 From: Matt Carroll Date: Tue, 9 Jul 2019 03:28:15 -0700 Subject: [PATCH 4/7] Formatting update. --- .../darwin/ios/framework/Source/FlutterViewController.mm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm index 968c56eac4a35..f05f7fd675cee 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm @@ -874,7 +874,7 @@ - (void)onLocaleUpdated:(NSNotification*)notification { #pragma mark - Set user settings -- (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection { +- (void)traitCollectionDidChange:(UITraitCollection*)previousTraitCollection { [super traitCollectionDidChange:previousTraitCollection]; [self onUserSettingsChanged:nil]; } @@ -960,7 +960,7 @@ - (BOOL)isAlwaysUse24HourFormat { - (NSString*)brightnessMode { if (@available(iOS 13, *)) { UIUserInterfaceStyle style = UITraitCollection.currentTraitCollection.userInterfaceStyle; - + if (style == UIUserInterfaceStyleDark) { return @"dark"; } else { From ee521814c5a15bb6fb4a5cbdc58a23e642b0e047 Mon Sep 17 00:00:00 2001 From: Matt Carroll Date: Wed, 7 Aug 2019 21:21:30 -0700 Subject: [PATCH 5/7] Added tests. --- .../Source/FlutterViewControllerTest.m | 83 +++++++++++++++++++ testing/ios/IosUnitTests/run_tests.sh | 2 +- 2 files changed, 84 insertions(+), 1 deletion(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest.m b/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest.m index 928a551059c74..838726b532f31 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest.m +++ b/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest.m @@ -6,6 +6,8 @@ #import #import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterViewController.h" +#include "FlutterBinaryMessenger.h" + @interface FlutterViewControllerTest : XCTestCase @end @@ -23,4 +25,85 @@ - (void)testBinaryMessenger { OCMVerify([engine binaryMessenger]); } +- (void)testItReportsLightPlatformBrightnessByDefault { + // Setup test. + id engine = OCMClassMock([FlutterEngine class]); + + id settingsChannel = OCMClassMock([FlutterBasicMessageChannel class]); + OCMStub([engine settingsChannel]).andReturn(settingsChannel); + + FlutterViewController* vc = [[FlutterViewController alloc] initWithEngine:engine + nibName:nil + bundle:nil]; + + // Exercise behavior under test. + [vc traitCollectionDidChange:nil]; + + // Verify behavior. + OCMVerify([settingsChannel sendMessage:[OCMArg checkWithBlock:^BOOL(id message) { + return [message[@"platformBrightness"] isEqualToString:@"light"]; + }]]); +} + +- (void)testItReportsDarkPlatformBrightnessWhenTraitCollectionRequestsIt { + // Setup test. + id engine = OCMClassMock([FlutterEngine class]); + + id settingsChannel = OCMClassMock([FlutterBasicMessageChannel class]); + OCMStub([engine settingsChannel]).andReturn(settingsChannel); + + FlutterViewController* vc = [[FlutterViewController alloc] initWithEngine:engine + nibName:nil + bundle:nil]; + id mockTraitCollection = [self setupFakeUserInterfaceStyle:UIUserInterfaceStyleDark]; + + // Exercise behavior under test. + [vc traitCollectionDidChange:nil]; + + // Verify behavior. + OCMVerify([settingsChannel sendMessage:[OCMArg checkWithBlock:^BOOL(id message) { + return [message[@"platformBrightness"] isEqualToString:@"dark"]; + }]]); + + // Restore UIUserInterfaceStyle + [mockTraitCollection stopMocking]; +} + +- (void)testItReportsPlatformBrightnessWhenExpected { + // Setup test. + id engine = OCMClassMock([FlutterEngine class]); + + id settingsChannel = OCMClassMock([FlutterBasicMessageChannel class]); + OCMStub([engine settingsChannel]).andReturn(settingsChannel); + + FlutterViewController* vc = [[FlutterViewController alloc] initWithEngine:engine + nibName:nil + bundle:nil]; + id mockTraitCollection = [self setupFakeUserInterfaceStyle:UIUserInterfaceStyleDark]; + + __block int messageCount = 0; + OCMStub([settingsChannel sendMessage:[OCMArg any]]).andDo(^(NSInvocation* invocation) { + messageCount = messageCount + 1; + }); + + // Exercise behavior under test. + [vc updateViewConstraints]; + [vc viewWillLayoutSubviews]; + [vc traitCollectionDidChange:nil]; + [vc onUserSettingsChanged:nil]; + + // Verify behavior. + XCTAssertEqual(messageCount, 4); + + // Restore UIUserInterfaceStyle + [mockTraitCollection stopMocking]; +} + +- (UITraitCollection*)setupFakeUserInterfaceStyle:(UIUserInterfaceStyle) style { + id mockTraitCollection = OCMClassMock([UITraitCollection class]); + OCMStub([mockTraitCollection userInterfaceStyle]).andReturn(UIUserInterfaceStyleDark); + OCMStub([mockTraitCollection currentTraitCollection]).andReturn(mockTraitCollection); + return mockTraitCollection; +} + @end diff --git a/testing/ios/IosUnitTests/run_tests.sh b/testing/ios/IosUnitTests/run_tests.sh index b4efa3d237795..a9eac157e1496 100755 --- a/testing/ios/IosUnitTests/run_tests.sh +++ b/testing/ios/IosUnitTests/run_tests.sh @@ -12,6 +12,6 @@ fi set -o pipefail && xcodebuild -sdk iphonesimulator \ -scheme IosUnitTests \ - -destination 'platform=iOS Simulator,name=iPhone SE,OS=12.2' \ + -destination 'platform=iOS Simulator,name=iPhone 8,OS=13.0' \ test \ FLUTTER_ENGINE=$FLUTTER_ENGINE | $PRETTY From 0e74ad12fa56bc34198b03503ea3c0e6732e9933 Mon Sep 17 00:00:00 2001 From: Matt Carroll Date: Wed, 7 Aug 2019 23:50:00 -0700 Subject: [PATCH 6/7] Format update. --- .../Source/FlutterViewControllerTest.m | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest.m b/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest.m index 838726b532f31..a18a749e1e1f4 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest.m +++ b/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest.m @@ -28,17 +28,17 @@ - (void)testBinaryMessenger { - (void)testItReportsLightPlatformBrightnessByDefault { // Setup test. id engine = OCMClassMock([FlutterEngine class]); - + id settingsChannel = OCMClassMock([FlutterBasicMessageChannel class]); OCMStub([engine settingsChannel]).andReturn(settingsChannel); - + FlutterViewController* vc = [[FlutterViewController alloc] initWithEngine:engine nibName:nil bundle:nil]; - + // Exercise behavior under test. [vc traitCollectionDidChange:nil]; - + // Verify behavior. OCMVerify([settingsChannel sendMessage:[OCMArg checkWithBlock:^BOOL(id message) { return [message[@"platformBrightness"] isEqualToString:@"light"]; @@ -48,23 +48,23 @@ - (void)testItReportsLightPlatformBrightnessByDefault { - (void)testItReportsDarkPlatformBrightnessWhenTraitCollectionRequestsIt { // Setup test. id engine = OCMClassMock([FlutterEngine class]); - + id settingsChannel = OCMClassMock([FlutterBasicMessageChannel class]); OCMStub([engine settingsChannel]).andReturn(settingsChannel); - + FlutterViewController* vc = [[FlutterViewController alloc] initWithEngine:engine nibName:nil bundle:nil]; id mockTraitCollection = [self setupFakeUserInterfaceStyle:UIUserInterfaceStyleDark]; - + // Exercise behavior under test. [vc traitCollectionDidChange:nil]; - + // Verify behavior. OCMVerify([settingsChannel sendMessage:[OCMArg checkWithBlock:^BOOL(id message) { return [message[@"platformBrightness"] isEqualToString:@"dark"]; }]]); - + // Restore UIUserInterfaceStyle [mockTraitCollection stopMocking]; } @@ -72,29 +72,29 @@ - (void)testItReportsDarkPlatformBrightnessWhenTraitCollectionRequestsIt { - (void)testItReportsPlatformBrightnessWhenExpected { // Setup test. id engine = OCMClassMock([FlutterEngine class]); - + id settingsChannel = OCMClassMock([FlutterBasicMessageChannel class]); OCMStub([engine settingsChannel]).andReturn(settingsChannel); - + FlutterViewController* vc = [[FlutterViewController alloc] initWithEngine:engine nibName:nil bundle:nil]; id mockTraitCollection = [self setupFakeUserInterfaceStyle:UIUserInterfaceStyleDark]; - + __block int messageCount = 0; OCMStub([settingsChannel sendMessage:[OCMArg any]]).andDo(^(NSInvocation* invocation) { messageCount = messageCount + 1; }); - + // Exercise behavior under test. [vc updateViewConstraints]; [vc viewWillLayoutSubviews]; [vc traitCollectionDidChange:nil]; [vc onUserSettingsChanged:nil]; - + // Verify behavior. XCTAssertEqual(messageCount, 4); - + // Restore UIUserInterfaceStyle [mockTraitCollection stopMocking]; } From 6980f2bda660742930daabf144c58a3cc5d6d7ca Mon Sep 17 00:00:00 2001 From: Matt Carroll Date: Thu, 8 Aug 2019 00:02:59 -0700 Subject: [PATCH 7/7] Format update. --- .../ios/framework/Source/FlutterViewControllerTest.m | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest.m b/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest.m index a18a749e1e1f4..26f99b9903f36 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest.m +++ b/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest.m @@ -41,8 +41,8 @@ - (void)testItReportsLightPlatformBrightnessByDefault { // Verify behavior. OCMVerify([settingsChannel sendMessage:[OCMArg checkWithBlock:^BOOL(id message) { - return [message[@"platformBrightness"] isEqualToString:@"light"]; - }]]); + return [message[@"platformBrightness"] isEqualToString:@"light"]; + }]]); } - (void)testItReportsDarkPlatformBrightnessWhenTraitCollectionRequestsIt { @@ -62,8 +62,8 @@ - (void)testItReportsDarkPlatformBrightnessWhenTraitCollectionRequestsIt { // Verify behavior. OCMVerify([settingsChannel sendMessage:[OCMArg checkWithBlock:^BOOL(id message) { - return [message[@"platformBrightness"] isEqualToString:@"dark"]; - }]]); + return [message[@"platformBrightness"] isEqualToString:@"light"]; + }]]); // Restore UIUserInterfaceStyle [mockTraitCollection stopMocking]; @@ -99,7 +99,7 @@ - (void)testItReportsPlatformBrightnessWhenExpected { [mockTraitCollection stopMocking]; } -- (UITraitCollection*)setupFakeUserInterfaceStyle:(UIUserInterfaceStyle) style { +- (UITraitCollection*)setupFakeUserInterfaceStyle:(UIUserInterfaceStyle)style { id mockTraitCollection = OCMClassMock([UITraitCollection class]); OCMStub([mockTraitCollection userInterfaceStyle]).andReturn(UIUserInterfaceStyleDark); OCMStub([mockTraitCollection currentTraitCollection]).andReturn(mockTraitCollection);