mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
Reland "ios: remove shared_application and support app extension build #44732" (flutter/engine#45351)
Relands https://github.com/flutter/engine/pull/44732 with fix. The original PR returns nil when the assets is not reachable, in some cases, the assets are not loaded yet but will be loaded later, so we should return the asset URL regardless. Also added a fallback to main bundle to match the previous implementation. The original PR was failed in internal tests in b/297654739 Now with the fix, all tests passed: cl/561449914 fixes https://github.com/flutter/flutter/issues/124289 [C++, Objective-C, Java style guides]: https://github.com/flutter/engine/blob/main/CONTRIBUTING.md#style
This commit is contained in:
parent
67c3c25948
commit
b3fc48b91b
@ -9,12 +9,14 @@
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
// Finds a bundle with the named `bundleID` within `searchURL`.
|
||||
extern const NSString* kDefaultAssetPath;
|
||||
|
||||
// Finds a bundle with the named `flutterFrameworkBundleID` within `searchURL`.
|
||||
//
|
||||
// Returns `nil` if the bundle cannot be found or if errors are encountered.
|
||||
NSBundle* FLTFrameworkBundleInternal(NSString* bundleID, NSURL* searchURL);
|
||||
NSBundle* FLTFrameworkBundleInternal(NSString* flutterFrameworkBundleID, NSURL* searchURL);
|
||||
|
||||
// Finds a bundle with the named `bundleID`.
|
||||
// Finds a bundle with the named `flutterFrameworkBundleID`.
|
||||
//
|
||||
// `+[NSBundle bundleWithIdentifier:]` is slow, and can take in the order of
|
||||
// tens of milliseconds in a minimal flutter app, and closer to 100 milliseconds
|
||||
@ -28,7 +30,25 @@ NSBundle* FLTFrameworkBundleInternal(NSString* bundleID, NSURL* searchURL);
|
||||
// frameworks used by this file are placed. If the desired bundle cannot be
|
||||
// found here, the implementation falls back to
|
||||
// `+[NSBundle bundleWithIdentifier:]`.
|
||||
NSBundle* FLTFrameworkBundleWithIdentifier(NSString* bundleID);
|
||||
NSBundle* FLTFrameworkBundleWithIdentifier(NSString* flutterFrameworkBundleID);
|
||||
|
||||
// Finds the bundle of the application.
|
||||
//
|
||||
// Returns [NSBundle mainBundle] if the current running process is the application.
|
||||
NSBundle* FLTGetApplicationBundle();
|
||||
|
||||
// Gets the flutter assets path directory from `bundle`.
|
||||
//
|
||||
// Returns `kDefaultAssetPath` if unable to find asset path from info.plist in `bundle`.
|
||||
NSString* FLTAssetPath(NSBundle* bundle);
|
||||
|
||||
// Finds the Flutter asset directory from `bundle`.
|
||||
//
|
||||
// The raw path can be set by the application via info.plist's `FLTAssetsPath` key.
|
||||
// If the key is not set, `flutter_assets` is used as the raw path value.
|
||||
//
|
||||
// If no valid asset is found under the raw path, returns nil.
|
||||
NSURL* FLTAssetsURLFromBundle(NSBundle* bundle);
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
|
||||
@ -8,7 +8,9 @@
|
||||
|
||||
FLUTTER_ASSERT_ARC
|
||||
|
||||
NSBundle* FLTFrameworkBundleInternal(NSString* bundleID, NSURL* searchURL) {
|
||||
const NSString* kDefaultAssetPath = @"Frameworks/App.framework/flutter_assets";
|
||||
|
||||
NSBundle* FLTFrameworkBundleInternal(NSString* flutterFrameworkBundleID, NSURL* searchURL) {
|
||||
NSDirectoryEnumerator<NSURL*>* frameworkEnumerator = [NSFileManager.defaultManager
|
||||
enumeratorAtURL:searchURL
|
||||
includingPropertiesForKeys:nil
|
||||
@ -18,19 +20,49 @@ NSBundle* FLTFrameworkBundleInternal(NSString* bundleID, NSURL* searchURL) {
|
||||
errorHandler:nil];
|
||||
|
||||
for (NSURL* candidate in frameworkEnumerator) {
|
||||
NSBundle* bundle = [NSBundle bundleWithURL:candidate];
|
||||
if ([bundle.bundleIdentifier isEqualToString:bundleID]) {
|
||||
return bundle;
|
||||
NSBundle* flutterFrameworkBundle = [NSBundle bundleWithURL:candidate];
|
||||
if ([flutterFrameworkBundle.bundleIdentifier isEqualToString:flutterFrameworkBundleID]) {
|
||||
return flutterFrameworkBundle;
|
||||
}
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
NSBundle* FLTFrameworkBundleWithIdentifier(NSString* bundleID) {
|
||||
NSBundle* bundle = FLTFrameworkBundleInternal(bundleID, NSBundle.mainBundle.privateFrameworksURL);
|
||||
if (bundle != nil) {
|
||||
return bundle;
|
||||
NSBundle* FLTGetApplicationBundle() {
|
||||
NSBundle* mainBundle = [NSBundle mainBundle];
|
||||
// App extension bundle is in <AppName>.app/PlugIns/Extension.appex.
|
||||
if ([mainBundle.bundleURL.pathExtension isEqualToString:@"appex"]) {
|
||||
// Up two levels.
|
||||
return [NSBundle bundleWithURL:mainBundle.bundleURL.URLByDeletingLastPathComponent
|
||||
.URLByDeletingLastPathComponent];
|
||||
}
|
||||
// Fallback to slow implementation.
|
||||
return [NSBundle bundleWithIdentifier:bundleID];
|
||||
return mainBundle;
|
||||
}
|
||||
|
||||
NSBundle* FLTFrameworkBundleWithIdentifier(NSString* flutterFrameworkBundleID) {
|
||||
NSBundle* appBundle = FLTGetApplicationBundle();
|
||||
NSBundle* flutterFrameworkBundle =
|
||||
FLTFrameworkBundleInternal(flutterFrameworkBundleID, appBundle.privateFrameworksURL);
|
||||
if (flutterFrameworkBundle == nil) {
|
||||
// Fallback to slow implementation.
|
||||
flutterFrameworkBundle = [NSBundle bundleWithIdentifier:flutterFrameworkBundleID];
|
||||
}
|
||||
if (flutterFrameworkBundle == nil) {
|
||||
flutterFrameworkBundle = [NSBundle mainBundle];
|
||||
}
|
||||
return flutterFrameworkBundle;
|
||||
}
|
||||
|
||||
NSString* FLTAssetPath(NSBundle* bundle) {
|
||||
return [bundle objectForInfoDictionaryKey:@"FLTAssetsPath"] ?: kDefaultAssetPath;
|
||||
}
|
||||
|
||||
NSURL* FLTAssetsURLFromBundle(NSBundle* bundle) {
|
||||
NSString* flutterAssetsPath = FLTAssetPath(bundle);
|
||||
NSURL* assets = [bundle URLForResource:flutterAssetsPath withExtension:nil];
|
||||
|
||||
if (!assets) {
|
||||
assets = [[NSBundle mainBundle] URLForResource:flutterAssetsPath withExtension:nil];
|
||||
}
|
||||
return assets;
|
||||
}
|
||||
|
||||
@ -348,6 +348,10 @@ shared_library("create_flutter_framework_dylib") {
|
||||
|
||||
ldflags = [ "-Wl,-install_name,@rpath/Flutter.framework/Flutter" ]
|
||||
|
||||
if (darwin_extension_safe) {
|
||||
ldflags += [ "-fapplication-extension" ]
|
||||
}
|
||||
|
||||
public = _flutter_framework_headers
|
||||
|
||||
deps = [
|
||||
@ -438,7 +442,10 @@ copy("copy_license") {
|
||||
shared_library("copy_and_verify_framework_module") {
|
||||
framework_search_path = rebase_path("$root_out_dir")
|
||||
visibility = [ ":*" ]
|
||||
cflags_objc = [ "-F$framework_search_path" ]
|
||||
cflags_objc = [
|
||||
"-F$framework_search_path",
|
||||
"-fapplication-extension",
|
||||
]
|
||||
|
||||
sources = [ "framework/Source/FlutterUmbrellaImport.m" ]
|
||||
deps = [
|
||||
@ -446,6 +453,17 @@ shared_library("copy_and_verify_framework_module") {
|
||||
":copy_framework_info_plist",
|
||||
":copy_framework_module_map",
|
||||
]
|
||||
|
||||
if (darwin_extension_safe) {
|
||||
ldflags = [
|
||||
"-F$framework_search_path",
|
||||
"-fapplication-extension",
|
||||
"-Xlinker",
|
||||
"-fatal_warnings",
|
||||
]
|
||||
deps += [ ":copy_dylib" ]
|
||||
frameworks = [ "Flutter.framework" ]
|
||||
}
|
||||
}
|
||||
|
||||
group("universal_flutter_framework") {
|
||||
|
||||
@ -353,7 +353,8 @@ typedef enum {
|
||||
*
|
||||
* @param delegate The receiving object, such as the plugin's main class.
|
||||
*/
|
||||
- (void)addApplicationDelegate:(NSObject<FlutterPlugin>*)delegate;
|
||||
- (void)addApplicationDelegate:(NSObject<FlutterPlugin>*)delegate
|
||||
NS_EXTENSION_UNAVAILABLE_IOS("Disallowed in plugins used in app extensions");
|
||||
|
||||
/**
|
||||
* Returns the file name for the given asset.
|
||||
|
||||
@ -41,16 +41,13 @@ flutter::Settings FLTDefaultSettingsForBundle(NSBundle* bundle, NSProcessInfo* p
|
||||
// 3. Settings from the NSBundle with the default bundle ID.
|
||||
// 4. Settings from the main NSBundle and default values.
|
||||
|
||||
NSBundle* mainBundle = [NSBundle mainBundle];
|
||||
NSBundle* mainBundle = FLTGetApplicationBundle();
|
||||
NSBundle* engineBundle = [NSBundle bundleForClass:[FlutterViewController class]];
|
||||
|
||||
bool hasExplicitBundle = bundle != nil;
|
||||
if (bundle == nil) {
|
||||
bundle = FLTFrameworkBundleWithIdentifier([FlutterDartProject defaultBundleIdentifier]);
|
||||
}
|
||||
if (bundle == nil) {
|
||||
bundle = mainBundle;
|
||||
}
|
||||
|
||||
auto settings = flutter::SettingsFromCommandLine(command_line);
|
||||
|
||||
@ -122,29 +119,24 @@ flutter::Settings FLTDefaultSettingsForBundle(NSBundle* bundle, NSProcessInfo* p
|
||||
|
||||
// Checks to see if the flutter assets directory is already present.
|
||||
if (settings.assets_path.empty()) {
|
||||
NSString* assetsName = [FlutterDartProject flutterAssetsName:bundle];
|
||||
NSString* assetsPath = [bundle pathForResource:assetsName ofType:@""];
|
||||
NSURL* assetsURL = FLTAssetsURLFromBundle(bundle);
|
||||
|
||||
if (assetsPath.length == 0) {
|
||||
assetsPath = [mainBundle pathForResource:assetsName ofType:@""];
|
||||
}
|
||||
|
||||
if (assetsPath.length == 0) {
|
||||
NSLog(@"Failed to find assets path for \"%@\"", assetsName);
|
||||
if (!assetsURL) {
|
||||
NSLog(@"Failed to find assets path for \"%@\"", bundle);
|
||||
} else {
|
||||
settings.assets_path = assetsPath.UTF8String;
|
||||
settings.assets_path = assetsURL.path.UTF8String;
|
||||
|
||||
// Check if there is an application kernel snapshot in the assets directory we could
|
||||
// potentially use. Looking for the snapshot makes sense only if we have a VM that can use
|
||||
// it.
|
||||
if (!flutter::DartVM::IsRunningPrecompiledCode()) {
|
||||
NSURL* applicationKernelSnapshotURL =
|
||||
[NSURL URLWithString:@(kApplicationKernelSnapshotFileName)
|
||||
relativeToURL:[NSURL fileURLWithPath:assetsPath]];
|
||||
if ([[NSFileManager defaultManager] fileExistsAtPath:applicationKernelSnapshotURL.path]) {
|
||||
[assetsURL URLByAppendingPathComponent:@(kApplicationKernelSnapshotFileName)];
|
||||
NSError* error;
|
||||
if ([applicationKernelSnapshotURL checkResourceIsReachableAndReturnError:&error]) {
|
||||
settings.application_kernel_asset = applicationKernelSnapshotURL.path.UTF8String;
|
||||
} else {
|
||||
NSLog(@"Failed to find snapshot: %@", applicationKernelSnapshotURL.path);
|
||||
NSLog(@"Failed to find snapshot at %@: %@", applicationKernelSnapshotURL.path, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -339,14 +331,7 @@ flutter::Settings FLTDefaultSettingsForBundle(NSBundle* bundle, NSProcessInfo* p
|
||||
if (bundle == nil) {
|
||||
bundle = FLTFrameworkBundleWithIdentifier([FlutterDartProject defaultBundleIdentifier]);
|
||||
}
|
||||
if (bundle == nil) {
|
||||
bundle = [NSBundle mainBundle];
|
||||
}
|
||||
NSString* flutterAssetsName = [bundle objectForInfoDictionaryKey:@"FLTAssetsPath"];
|
||||
if (flutterAssetsName == nil) {
|
||||
flutterAssetsName = @"Frameworks/App.framework/flutter_assets";
|
||||
}
|
||||
return flutterAssetsName;
|
||||
return FLTAssetPath(bundle);
|
||||
}
|
||||
|
||||
+ (NSString*)domainNetworkPolicy:(NSDictionary*)appTransportSecurity {
|
||||
|
||||
@ -73,6 +73,50 @@ FLUTTER_ASSERT_ARC
|
||||
XCTAssertNotNil(found);
|
||||
}
|
||||
|
||||
- (void)testFLTGetApplicationBundleWhenCurrentTargetIsNotExtension {
|
||||
NSBundle* bundle = FLTGetApplicationBundle();
|
||||
XCTAssertEqual(bundle, [NSBundle mainBundle]);
|
||||
}
|
||||
|
||||
- (void)testFLTGetApplicationBundleWhenCurrentTargetIsExtension {
|
||||
id mockMainBundle = OCMPartialMock([NSBundle mainBundle]);
|
||||
NSURL* url = [[NSBundle mainBundle].bundleURL URLByAppendingPathComponent:@"foo/ext.appex"];
|
||||
OCMStub([mockMainBundle bundleURL]).andReturn(url);
|
||||
NSBundle* bundle = FLTGetApplicationBundle();
|
||||
[mockMainBundle stopMocking];
|
||||
XCTAssertEqualObjects(bundle.bundleURL, [NSBundle mainBundle].bundleURL);
|
||||
}
|
||||
|
||||
- (void)testFLTAssetsURLFromBundle {
|
||||
{
|
||||
// Found asset path in info.plist (even not reachable)
|
||||
id mockBundle = OCMClassMock([NSBundle class]);
|
||||
OCMStub([mockBundle objectForInfoDictionaryKey:@"FLTAssetsPath"]).andReturn(@"foo/assets");
|
||||
NSURL* mockAssetsURL = OCMClassMock([NSURL class]);
|
||||
OCMStub([mockBundle URLForResource:@"foo/assets" withExtension:nil]).andReturn(mockAssetsURL);
|
||||
OCMStub([mockAssetsURL checkResourceIsReachableAndReturnError:NULL]).andReturn(NO);
|
||||
OCMStub([mockAssetsURL path]).andReturn(@"foo/assets");
|
||||
NSURL* url = FLTAssetsURLFromBundle(mockBundle);
|
||||
XCTAssertEqualObjects(url.path, @"foo/assets");
|
||||
}
|
||||
{
|
||||
// No asset path in info.plist, defaults to main bundle
|
||||
id mockBundle = OCMClassMock([NSBundle class]);
|
||||
id mockMainBundle = OCMPartialMock([NSBundle mainBundle]);
|
||||
NSURL* mockAssetsURL = OCMClassMock([NSURL class]);
|
||||
OCMStub([mockBundle URLForResource:@"Frameworks/App.framework/flutter_assets"
|
||||
withExtension:nil])
|
||||
.andReturn(nil);
|
||||
OCMStub([mockAssetsURL checkResourceIsReachableAndReturnError:NULL]).andReturn(NO);
|
||||
OCMStub([mockAssetsURL path]).andReturn(@"path/to/foo/assets");
|
||||
OCMStub([mockMainBundle URLForResource:@"Frameworks/App.framework/flutter_assets"
|
||||
withExtension:nil])
|
||||
.andReturn(mockAssetsURL);
|
||||
NSURL* url = FLTAssetsURLFromBundle(mockBundle);
|
||||
XCTAssertEqualObjects(url.path, @"path/to/foo/assets");
|
||||
}
|
||||
}
|
||||
|
||||
- (void)testDisableImpellerSettingIsCorrectlyParsed {
|
||||
id mockMainBundle = OCMPartialMock([NSBundle mainBundle]);
|
||||
OCMStub([mockMainBundle objectForInfoDictionaryKey:@"FLTEnableImpeller"]).andReturn(@"NO");
|
||||
|
||||
@ -1515,7 +1515,8 @@ static void SetEntryPoint(flutter::Settings* settings, NSString* entrypoint, NSS
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)addApplicationDelegate:(NSObject<FlutterPlugin>*)delegate {
|
||||
- (void)addApplicationDelegate:(NSObject<FlutterPlugin>*)delegate
|
||||
NS_EXTENSION_UNAVAILABLE_IOS("Disallowed in plugins used in app extensions") {
|
||||
id<UIApplicationDelegate> appDelegate = [[UIApplication sharedApplication] delegate];
|
||||
if ([appDelegate conformsToProtocol:@protocol(FlutterAppLifeCycleProvider)]) {
|
||||
id<FlutterAppLifeCycleProvider> lifeCycleProvider =
|
||||
|
||||
@ -17,7 +17,10 @@ namespace {
|
||||
|
||||
constexpr char kTextPlainFormat[] = "text/plain";
|
||||
const UInt32 kKeyPressClickSoundId = 1306;
|
||||
|
||||
#if not APPLICATION_EXTENSION_API_ONLY
|
||||
const NSString* searchURLPrefix = @"x-web-search://?";
|
||||
#endif
|
||||
|
||||
} // namespace
|
||||
|
||||
@ -37,6 +40,24 @@ const char* const kOverlayStyleUpdateNotificationKey =
|
||||
|
||||
using namespace flutter;
|
||||
|
||||
static void SetStatusBarHiddenForSharedApplication(BOOL hidden) {
|
||||
#if APPLICATION_EXTENSION_API_ONLY
|
||||
[UIApplication sharedApplication].statusBarHidden = hidden;
|
||||
#else
|
||||
FML_LOG(WARNING) << "Application based status bar styling is not available in app extension.";
|
||||
#endif
|
||||
}
|
||||
|
||||
static void SetStatusBarStyleForSharedApplication(UIStatusBarStyle style) {
|
||||
#if APPLICATION_EXTENSION_API_ONLY
|
||||
// Note: -[UIApplication setStatusBarStyle] is deprecated in iOS9
|
||||
// in favor of delegating to the view controller.
|
||||
[[UIApplication sharedApplication] setStatusBarStyle:style];
|
||||
#else
|
||||
FML_LOG(WARNING) << "Application based status bar styling is not available in app extension.";
|
||||
#endif
|
||||
}
|
||||
|
||||
@interface FlutterPlatformPlugin ()
|
||||
|
||||
/**
|
||||
@ -141,6 +162,9 @@ using namespace flutter;
|
||||
}
|
||||
|
||||
- (void)searchWeb:(NSString*)searchTerm {
|
||||
#if APPLICATION_EXTENSION_API_ONLY
|
||||
FML_LOG(WARNING) << "SearchWeb.invoke is not availabe in app extension.";
|
||||
#else
|
||||
NSString* escapedText = [searchTerm
|
||||
stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet
|
||||
URLHostAllowedCharacterSet]];
|
||||
@ -149,6 +173,7 @@ using namespace flutter;
|
||||
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:searchURL]
|
||||
options:@{}
|
||||
completionHandler:nil];
|
||||
#endif
|
||||
}
|
||||
|
||||
- (void)playSystemSound:(NSString*)soundType {
|
||||
@ -231,7 +256,7 @@ using namespace flutter;
|
||||
// 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;
|
||||
SetStatusBarHiddenForSharedApplication(statusBarShouldBeHidden);
|
||||
}
|
||||
}
|
||||
|
||||
@ -246,7 +271,7 @@ using namespace flutter;
|
||||
// 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;
|
||||
SetStatusBarHiddenForSharedApplication(!edgeToEdge);
|
||||
}
|
||||
[[NSNotificationCenter defaultCenter]
|
||||
postNotificationName:edgeToEdge ? FlutterViewControllerShowHomeIndicator
|
||||
@ -284,9 +309,7 @@ using namespace flutter;
|
||||
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];
|
||||
SetStatusBarStyleForSharedApplication(statusBarStyle);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -50,9 +50,11 @@
|
||||
|
||||
FlutterResult result = ^(id result) {
|
||||
OCMVerify([mockPlugin searchWeb:@"Testing Word!"]);
|
||||
#if not APPLICATION_EXTENSION_API_ONLY
|
||||
OCMVerify([mockApplication openURL:[NSURL URLWithString:@"x-web-search://?Testing%20Word!"]
|
||||
options:@{}
|
||||
completionHandler:nil]);
|
||||
#endif
|
||||
[invokeExpectation fulfill];
|
||||
};
|
||||
|
||||
@ -82,9 +84,11 @@
|
||||
|
||||
FlutterResult result = ^(id result) {
|
||||
OCMVerify([mockPlugin searchWeb:@"Test"]);
|
||||
#if not APPLICATION_EXTENSION_API_ONLY
|
||||
OCMVerify([mockApplication openURL:[NSURL URLWithString:@"x-web-search://?Test"]
|
||||
options:@{}
|
||||
completionHandler:nil]);
|
||||
#endif
|
||||
[invokeExpectation fulfill];
|
||||
};
|
||||
|
||||
|
||||
@ -17,11 +17,16 @@ static const SEL kSelectorsHandledByPlugins[] = {
|
||||
@selector(application:performFetchWithCompletionHandler:)};
|
||||
|
||||
@interface FlutterPluginAppLifeCycleDelegate ()
|
||||
- (void)handleDidEnterBackground:(NSNotification*)notification;
|
||||
- (void)handleWillEnterForeground:(NSNotification*)notification;
|
||||
- (void)handleWillResignActive:(NSNotification*)notification;
|
||||
- (void)handleDidBecomeActive:(NSNotification*)notification;
|
||||
- (void)handleWillTerminate:(NSNotification*)notification;
|
||||
- (void)handleDidEnterBackground:(NSNotification*)notification
|
||||
NS_EXTENSION_UNAVAILABLE_IOS("Disallowed in app extensions");
|
||||
- (void)handleWillEnterForeground:(NSNotification*)notification
|
||||
NS_EXTENSION_UNAVAILABLE_IOS("Disallowed in app extensions");
|
||||
- (void)handleWillResignActive:(NSNotification*)notification
|
||||
NS_EXTENSION_UNAVAILABLE_IOS("Disallowed in app extensions");
|
||||
- (void)handleDidBecomeActive:(NSNotification*)notification
|
||||
NS_EXTENSION_UNAVAILABLE_IOS("Disallowed in app extensions");
|
||||
- (void)handleWillTerminate:(NSNotification*)notification
|
||||
NS_EXTENSION_UNAVAILABLE_IOS("Disallowed in app extensions");
|
||||
@end
|
||||
|
||||
@implementation FlutterPluginAppLifeCycleDelegate {
|
||||
@ -46,6 +51,7 @@ static const SEL kSelectorsHandledByPlugins[] = {
|
||||
_notificationUnsubscribers = [[NSMutableArray alloc] init];
|
||||
std::string cachePath = fml::paths::JoinPaths({getenv("HOME"), kCallbackCacheSubDir});
|
||||
[FlutterCallbackCache setCachePath:[NSString stringWithUTF8String:cachePath.c_str()]];
|
||||
#if not APPLICATION_EXTENSION_API_ONLY
|
||||
[self addObserverFor:UIApplicationDidEnterBackgroundNotification
|
||||
selector:@selector(handleDidEnterBackground:)];
|
||||
[self addObserverFor:UIApplicationWillEnterForegroundNotification
|
||||
@ -56,6 +62,7 @@ static const SEL kSelectorsHandledByPlugins[] = {
|
||||
selector:@selector(handleDidBecomeActive:)];
|
||||
[self addObserverFor:UIApplicationWillTerminateNotification
|
||||
selector:@selector(handleWillTerminate:)];
|
||||
#endif
|
||||
_delegates = [[NSPointerArray weakObjectsPointerArray] retain];
|
||||
_debugBackgroundTask = UIBackgroundTaskInvalid;
|
||||
}
|
||||
@ -134,7 +141,8 @@ static BOOL IsPowerOfTwo(NSUInteger x) {
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)handleDidEnterBackground:(NSNotification*)notification {
|
||||
- (void)handleDidEnterBackground:(NSNotification*)notification
|
||||
NS_EXTENSION_UNAVAILABLE_IOS("Disallowed in app extensions") {
|
||||
UIApplication* application = [UIApplication sharedApplication];
|
||||
#if FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG
|
||||
// The following keeps the Flutter session alive when the device screen locks
|
||||
@ -166,7 +174,8 @@ static BOOL IsPowerOfTwo(NSUInteger x) {
|
||||
}
|
||||
}
|
||||
|
||||
- (void)handleWillEnterForeground:(NSNotification*)notification {
|
||||
- (void)handleWillEnterForeground:(NSNotification*)notification
|
||||
NS_EXTENSION_UNAVAILABLE_IOS("Disallowed in app extensions") {
|
||||
UIApplication* application = [UIApplication sharedApplication];
|
||||
#if FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG
|
||||
if (_debugBackgroundTask != UIBackgroundTaskInvalid) {
|
||||
@ -184,7 +193,8 @@ static BOOL IsPowerOfTwo(NSUInteger x) {
|
||||
}
|
||||
}
|
||||
|
||||
- (void)handleWillResignActive:(NSNotification*)notification {
|
||||
- (void)handleWillResignActive:(NSNotification*)notification
|
||||
NS_EXTENSION_UNAVAILABLE_IOS("Disallowed in app extensions") {
|
||||
UIApplication* application = [UIApplication sharedApplication];
|
||||
for (NSObject<FlutterApplicationLifeCycleDelegate>* delegate in _delegates) {
|
||||
if (!delegate) {
|
||||
@ -196,7 +206,8 @@ static BOOL IsPowerOfTwo(NSUInteger x) {
|
||||
}
|
||||
}
|
||||
|
||||
- (void)handleDidBecomeActive:(NSNotification*)notification {
|
||||
- (void)handleDidBecomeActive:(NSNotification*)notification
|
||||
NS_EXTENSION_UNAVAILABLE_IOS("Disallowed in app extensions") {
|
||||
UIApplication* application = [UIApplication sharedApplication];
|
||||
for (NSObject<FlutterApplicationLifeCycleDelegate>* delegate in _delegates) {
|
||||
if (!delegate) {
|
||||
@ -208,7 +219,8 @@ static BOOL IsPowerOfTwo(NSUInteger x) {
|
||||
}
|
||||
}
|
||||
|
||||
- (void)handleWillTerminate:(NSNotification*)notification {
|
||||
- (void)handleWillTerminate:(NSNotification*)notification
|
||||
NS_EXTENSION_UNAVAILABLE_IOS("Disallowed in app extensions") {
|
||||
UIApplication* application = [UIApplication sharedApplication];
|
||||
for (NSObject<FlutterApplicationLifeCycleDelegate>* delegate in _delegates) {
|
||||
if (!delegate) {
|
||||
|
||||
@ -20,6 +20,7 @@ FLUTTER_ASSERT_ARC
|
||||
XCTAssertNotNil(delegate);
|
||||
}
|
||||
|
||||
#if not APPLICATION_EXTENSION_API_ONLY
|
||||
- (void)testDidEnterBackground {
|
||||
XCTNSNotificationExpectation* expectation = [[XCTNSNotificationExpectation alloc]
|
||||
initWithName:UIApplicationDidEnterBackgroundNotification];
|
||||
@ -88,5 +89,6 @@ FLUTTER_ASSERT_ARC
|
||||
[self waitForExpectations:@[ expectation ] timeout:5.0];
|
||||
OCMVerify([plugin applicationWillTerminate:[UIApplication sharedApplication]]);
|
||||
}
|
||||
#endif
|
||||
|
||||
@end
|
||||
|
||||
@ -1973,7 +1973,7 @@ static flutter::PointerData::DeviceKind DeviceKindFromTouchType(UITouch* touch)
|
||||
#endif
|
||||
[self requestGeometryUpdateForWindowScenes:scenes];
|
||||
} else {
|
||||
UIInterfaceOrientationMask currentInterfaceOrientation;
|
||||
UIInterfaceOrientationMask currentInterfaceOrientation = 0;
|
||||
if (@available(iOS 13.0, *)) {
|
||||
UIWindowScene* windowScene = [self flutterWindowSceneIfViewLoaded];
|
||||
if (!windowScene) {
|
||||
@ -1983,7 +1983,13 @@ static flutter::PointerData::DeviceKind DeviceKindFromTouchType(UITouch* touch)
|
||||
}
|
||||
currentInterfaceOrientation = 1 << windowScene.interfaceOrientation;
|
||||
} else {
|
||||
#if APPLICATION_EXTENSION_API_ONLY
|
||||
FML_LOG(ERROR) << "Application based status bar orentiation update is not supported in "
|
||||
"app extension. Orientation: "
|
||||
<< currentInterfaceOrientation;
|
||||
#else
|
||||
currentInterfaceOrientation = 1 << [[UIApplication sharedApplication] statusBarOrientation];
|
||||
#endif
|
||||
}
|
||||
if (!(_orientationPreferences & currentInterfaceOrientation)) {
|
||||
[UIViewController attemptRotationToDeviceOrientation];
|
||||
@ -2108,6 +2114,10 @@ static flutter::PointerData::DeviceKind DeviceKindFromTouchType(UITouch* touch)
|
||||
}
|
||||
|
||||
- (CGFloat)textScaleFactor {
|
||||
#if APPLICATION_EXTENSION_API_ONLY
|
||||
FML_LOG(WARNING) << "Dynamic content size update is not supported in app extension.";
|
||||
return 1.0;
|
||||
#else
|
||||
UIContentSizeCategory category = [UIApplication sharedApplication].preferredContentSizeCategory;
|
||||
// The delta is computed by approximating Apple's typography guidelines:
|
||||
// https://developer.apple.com/ios/human-interface-guidelines/visual-design/typography/
|
||||
@ -2158,6 +2168,7 @@ static flutter::PointerData::DeviceKind DeviceKindFromTouchType(UITouch* touch)
|
||||
} else {
|
||||
return 1.0;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
- (BOOL)isAlwaysUse24HourFormat {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user