Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions sky/services/platform/ios/system_chrome_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,18 @@ class SystemChromeImpl : public SystemChrome {
uint32_t device_orientation_mask,
const SetPreferredOrientationsCallback& callback) override;

void SetApplicationSwitcherDescription(
ApplicationSwitcherDescriptionPtr description,
const SetApplicationSwitcherDescriptionCallback& callback) override;

void SetEnabledSystemUIOverlays(
uint32_t overlays,
const SetEnabledSystemUIOverlaysCallback& callback) override;

void SetSystemUIOverlayStyle(
SystemUIOverlayStyle style,
const SetSystemUIOverlayStyleCallback& callback) override;

private:
mojo::StrongBinding<SystemChrome> binding_;

Expand All @@ -34,6 +42,8 @@ class SystemChromeImpl : public SystemChrome {

extern const char* const kOrientationUpdateNotificationName;
extern const char* const kOrientationUpdateNotificationKey;
extern const char* const kOverlayStyleUpdateNotificationName;
extern const char* const kOverlayStyleUpdateNotificationKey;

} // namespace platform
} // namespace flutter
Expand Down
57 changes: 53 additions & 4 deletions sky/services/platform/ios/system_chrome_impl.mm
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

#include "sky/services/platform/ios/system_chrome_impl.h"
#include "base/mac/scoped_nsautorelease_pool.h"
#include <UIKit/UIApplication.h>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Redundant include. You are already including the umbrella header on the next line.

#include <UIKit/UIKit.h>

namespace flutter {
Expand Down Expand Up @@ -59,8 +60,15 @@ static constexpr bool IsSet(uint32_t mask, T orientation) {
callback.Run(true);
}

void SystemChromeImpl::SetApplicationSwitcherDescription(
ApplicationSwitcherDescriptionPtr description,
const SetApplicationSwitcherDescriptionCallback& callback) {
// No counterpart on iOS but is a benign operation. So no asserts.
callback.Run(true);
}

void SystemChromeImpl::SetEnabledSystemUIOverlays(
uint32_t overlays,
uint32_t overlay_mask,
const SetEnabledSystemUIOverlaysCallback& callback) {
// Checks if the top status bar should be visible. This platform ignores all
// other overlays
Expand All @@ -71,15 +79,56 @@ static constexpr bool IsSet(uint32_t mask, T orientation) {
// to be able to modify this on the fly. The key used is
// UIViewControllerBasedStatusBarAppearance
[UIApplication sharedApplication].statusBarHidden =
!IsSet(overlays, SystemUIOverlay::Top);
!IsSet(overlay_mask, SystemUIOverlay::Top);

callback.Run(true);
}

void SystemChromeImpl::SetSystemUIOverlayStyle(
SystemUIOverlayStyle style,
const SetSystemUIOverlayStyleCallback& callback) {
base::mac::ScopedNSAutoreleasePool pool;

UIStatusBarStyle statusBarStyle;
switch (style) {
case SystemUIOverlayStyle::Light:
statusBarStyle = UIStatusBarStyleLightContent;
break;
case SystemUIOverlayStyle::Dark:
statusBarStyle = UIStatusBarStyleDefault;
break;
}

NSNumber* infoValue = [[NSBundle mainBundle]
objectForInfoDictionaryKey:@"UIViewControllerBasedStatusBarAppearance"];
Boolean delegateToViewController =
(infoValue == nil || [infoValue boolValue]);

if (delegateToViewController) {
// This notification is respected by the iOS embedder
[[NSNotificationCenter defaultCenter]
postNotificationName:@(kOverlayStyleUpdateNotificationName)
object:nil
userInfo:@{
@(kOverlayStyleUpdateNotificationKey) : @(statusBarStyle)
}];
} else {
// Note: -[UIApplication setStatusBarStyle] is deprecated in iOS9
// in favor of delegating to the view controller
[[UIApplication sharedApplication] setStatusBarStyle:statusBarStyle];
}

callback.Run(true);
}

const char* const kOrientationUpdateNotificationName =
"SystemChromeOrientationNotificationName";
"io.flutter.SystemChromeOrientationNotificationName";
const char* const kOrientationUpdateNotificationKey =
"SystemChromeOrientationNotificationName";
"io.flutter.SystemChromeOrientationNotificationKey";
const char* const kOverlayStyleUpdateNotificationName =
"io.flutter.SystemChromeOverlayNotificationName";
const char* const kOverlayStyleUpdateNotificationKey =
"io.flutter.SystemChromeOverlayNotificationKey";

} // namespace platform
} // namespace flutter
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@

import android.app.Activity;
import android.content.pm.ActivityInfo;
import android.os.Build;
import android.view.View;

import org.chromium.mojo.system.MojoException;
import org.chromium.mojom.flutter.platform.ApplicationSwitcherDescription;
import org.chromium.mojom.flutter.platform.DeviceOrientation;
import org.chromium.mojom.flutter.platform.SystemChrome;
import org.chromium.mojom.flutter.platform.SystemUiOverlay;
Expand Down Expand Up @@ -54,6 +56,31 @@ public void setPreferredOrientations(int deviceOrientationMask,
callback.call(true);
}

@Override
public void setApplicationSwitcherDescription(
ApplicationSwitcherDescription description,
SetApplicationSwitcherDescriptionResponse callback) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
callback.call(true);
return;
}

int color = description.primaryColor;
if (color != 0) { // 0 means color isn't set, use system default
color = color | 0xFF000000; // color must be opaque if set
}

mActivity.setTaskDescription(
new android.app.ActivityManager.TaskDescription(
description.label,
null,
color
)
);

callback.call(true);
}

@Override
public void setEnabledSystemUiOverlays(int overlays,
SetEnabledSystemUiOverlaysResponse callback) {
Expand All @@ -71,4 +98,12 @@ public void setEnabledSystemUiOverlays(int overlays,
mActivity.getWindow().getDecorView().setSystemUiVisibility(flags);
callback.call(true);
}

@Override
public void setSystemUiOverlayStyle(int style, SetSystemUiOverlayStyleResponse callback) {
// You can change the navigation bar color (including translucent colors)
// in Android, but you can't change the color of the navigation buttons,
// so LIGHT vs DARK effectively isn't supported in Android.
callback.call(true);
}
}
62 changes: 57 additions & 5 deletions sky/services/platform/system_chrome.mojom
Original file line number Diff line number Diff line change
Expand Up @@ -39,29 +39,66 @@ enum SystemUIOverlay {
Bottom = 2,
};

/// Specifies a preference for the style of the system overlays. Certain
/// platforms may not respect this preference.
enum SystemUIOverlayStyle {
/// System overlays should be drawn with a light color. Intended for
/// applications with a dark background.
Light = 1,

/// System overlays should be drawn with a dark color. Intended for
/// applications with a light background.
Dark = 2,
};

/// Specifies a description of the application that is pertinent to the
/// embedder's application switcher (a.k.a. "recent tasks") user interface.
struct ApplicationSwitcherDescription {
/// A label and description of the current state of the application.
string? label;

/// The application's primary color.
uint32 primaryColor;
};

/// Controls specific aspects of the embedder interface.
[ServiceName="flutter::platform::SystemChrome"]
interface SystemChrome {
/// Specifies the set of orientations the application interface can
/// be displayed in.
///
/// The value 0 is synonymous with having all options enabled.
/// Arguments:
/// device_orientation_mask: A mask of `DeviceOrientation` enum values.
/// device_orientation_mask: A mask of `DeviceOrientation` enum values.
/// A value of 0 is synonymous with having all options enabled.
///
/// Return Value:
/// boolean indicating if the orientation mask is valid and the changes
/// could be conveyed successfully to the embedder.
SetPreferredOrientations(uint32 device_orientation_mask) => (bool success);

/// Specifies the description of the application within the embedder's
/// application switcher (a.k.a. "recent tasks") user interface.
///
/// Arguments:
/// description: The description of the current state of the application.
///
/// Return value:
/// boolean indicating if the preference was conveyed successfully to the
/// embedder.
///
/// Platform Specific Notes:
/// If application switcher metadata cannot be manually set on the platform,
/// specifying such metadata is a no-op and always return true.
SetApplicationSwitcherDescription(ApplicationSwitcherDescription description) => (bool success);

/// Specifies the set of overlays visible on the embedder when the
/// application is running. The embedder may choose to ignore unsupported
/// overlays
///
/// Arguments:
/// style: A mask of `SystemUIOverlay` enum values that denotes the overlays
/// to show.
/// overlay_mask: A mask of `SystemUIOverlay` enum values that denotes the
/// overlays to show. A value of 0 is synonymous with showing no
/// overlays.
///
/// Return Value:
/// boolean indicating if the preference was conveyed successfully to the
Expand All @@ -70,5 +107,20 @@ interface SystemChrome {
/// Platform Specific Notes:
/// If the overlay is unsupported on the platform, enabling or disabling
/// that overlay is a no-op and always return true.
SetEnabledSystemUIOverlays(uint32 overlays) => (bool success);
SetEnabledSystemUIOverlays(uint32 overlay_mask) => (bool success);

/// Specifies the style of the overlays that are visible on the embedder when
/// the applicatiomn is running.
///
/// Arguments:
/// style: A `SystemUIOverlayStyle` enum value that denotes the style
///
/// Return value:
/// boolean indicating if the preference was conveyed successfully to the
/// embedder.
///
/// Platform Specific Notes:
/// If overlay style is unsupported on the platform, specifying a style is
/// a no-op and always return true.
SetSystemUIOverlayStyle(SystemUIOverlayStyle style) => (bool success);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another approach would be a query function that returns what styles are available on the current platform. This kind of pattern emphasizes discoverability and helps avoid least-common-denominator problems (although in this case the number of supported styles is probably going to be pretty limited).

};
44 changes: 38 additions & 6 deletions sky/shell/platform/ios/framework/Source/FlutterViewController.mm
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ void FlutterInit(int argc, const char* argv[]) {
@implementation FlutterViewController {
base::scoped_nsprotocol<FlutterDartProject*> _dartProject;
UIInterfaceOrientationMask _orientationPreferences;
UIStatusBarStyle _statusBarStyle;
base::scoped_nsprotocol<FlutterDynamicServiceLoader*> _dynamicServiceLoader;
sky::ViewportMetricsPtr _viewportMetrics;
sky::shell::TouchMapper _touchMapper;
Expand Down Expand Up @@ -87,6 +88,7 @@ - (void)performCommonViewControllerInitialization {
_initialized = YES;

_orientationPreferences = UIInterfaceOrientationMaskAll;
_statusBarStyle = UIStatusBarStyleDefault;
_dynamicServiceLoader.reset([[FlutterDynamicServiceLoader alloc] init]);
_viewportMetrics = sky::ViewportMetrics::New();
_shellView =
Expand Down Expand Up @@ -116,6 +118,11 @@ - (void)setupNotificationCenterObservers {
name:@(flutter::platform::kOrientationUpdateNotificationName)
object:nil];

[center addObserver:self
selector:@selector(onPreferredStatusBarStyleUpdated:)
name:@(flutter::platform::kOverlayStyleUpdateNotificationName)
object:nil];

[center addObserver:self
selector:@selector(applicationBecameActive:)
name:UIApplicationDidBecomeActiveNotification
Expand Down Expand Up @@ -450,15 +457,38 @@ - (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
}

- (UIStatusBarStyle)preferredStatusBarStyle {
return UIStatusBarStyleLightContent;
}

- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
[super dealloc];
}

#pragma mark - Status bar style

- (UIStatusBarStyle)preferredStatusBarStyle {
return _statusBarStyle;
}

- (void)onPreferredStatusBarStyleUpdated:(NSNotification*)notification {
// Notifications may not be on the iOS UI thread
dispatch_async(dispatch_get_main_queue(), ^{
NSDictionary* info = notification.userInfo;

NSNumber* update =
info[@(flutter::platform::kOverlayStyleUpdateNotificationKey)];

if (update == nil) {
return;
}

NSInteger style = update.integerValue;

if (style != _statusBarStyle) {
_statusBarStyle = static_cast<UIStatusBarStyle>(style);
[self setNeedsStatusBarAppearanceUpdate];
}
});
}

#pragma mark - Application Messages

- (void)sendString:(NSString*)message withMessageName:(NSString*)messageName {
Expand Down Expand Up @@ -497,14 +527,16 @@ - (void)removeMessageListener:(NSObject<FlutterMessageListener>*)listener {
_appMessageReceiver.SetMessageListener(messageName.UTF8String, nil);
}

- (void)addAsyncMessageListener:(NSObject<FlutterAsyncMessageListener>*)listener {
- (void)addAsyncMessageListener:
(NSObject<FlutterAsyncMessageListener>*)listener {
NSAssert(listener, @"The listener must not be null");
NSString* messageName = listener.messageName;
NSAssert(messageName, @"The messageName must not be null");
_appMessageReceiver.SetAsyncMessageListener(messageName.UTF8String, listener);
}

- (void)removeAsyncMessageListener:(NSObject<FlutterAsyncMessageListener>*)listener {
- (void)removeAsyncMessageListener:
(NSObject<FlutterAsyncMessageListener>*)listener {
NSAssert(listener, @"The listener must not be null");
NSString* messageName = listener.messageName;
NSAssert(messageName, @"The messageName must not be null");
Expand Down