From 9b271e228bff91816172bdfbbd5f756ea94e7b8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Fri, 14 Jul 2023 11:44:20 +0200 Subject: [PATCH] ios --- cordova-plugin-moodleapp/plugin.xml | 11 + .../src/ios/CDVSystemUI.h | 50 ++ .../src/ios/CDVSystemUI.m | 492 ++++++++++++++++++ package-lock.json | 5 - package.json | 4 +- 5 files changed, 554 insertions(+), 8 deletions(-) create mode 100644 cordova-plugin-moodleapp/src/ios/CDVSystemUI.h create mode 100644 cordova-plugin-moodleapp/src/ios/CDVSystemUI.m diff --git a/cordova-plugin-moodleapp/plugin.xml b/cordova-plugin-moodleapp/plugin.xml index daf3a73b7a6..8282a3f0c9b 100644 --- a/cordova-plugin-moodleapp/plugin.xml +++ b/cordova-plugin-moodleapp/plugin.xml @@ -16,5 +16,16 @@ + + + + + + + + + + + diff --git a/cordova-plugin-moodleapp/src/ios/CDVSystemUI.h b/cordova-plugin-moodleapp/src/ios/CDVSystemUI.h new file mode 100644 index 00000000000..e13035352ed --- /dev/null +++ b/cordova-plugin-moodleapp/src/ios/CDVSystemUI.h @@ -0,0 +1,50 @@ +/* + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + */ + +#import +#import + +@interface CDVSystemUI : CDVPlugin { + @protected + BOOL _statusBarOverlaysWebView; + UIView* _statusBarBackgroundView; + BOOL _uiviewControllerBasedStatusBarAppearance; + UIColor* _statusBarBackgroundColor; + NSString* _eventsCallbackId; +} + +@property (atomic, assign) BOOL statusBarOverlaysWebView; +@property (atomic, assign) BOOL statusBarVisible; + +- (void) overlaysWebView:(CDVInvokedUrlCommand*)command; + +- (void) styleDefault:(CDVInvokedUrlCommand*)command; +- (void) styleLightContent:(CDVInvokedUrlCommand*)command; +- (void) styleBlackTranslucent:(CDVInvokedUrlCommand*)command; +- (void) styleBlackOpaque:(CDVInvokedUrlCommand*)command; + +- (void) backgroundColorByName:(CDVInvokedUrlCommand*)command; +- (void) backgroundColorByHexString:(CDVInvokedUrlCommand*)command; + +- (void) hide:(CDVInvokedUrlCommand*)command; +- (void) show:(CDVInvokedUrlCommand*)command; + +- (void) _ready:(CDVInvokedUrlCommand*)command; + +@end diff --git a/cordova-plugin-moodleapp/src/ios/CDVSystemUI.m b/cordova-plugin-moodleapp/src/ios/CDVSystemUI.m new file mode 100644 index 00000000000..93c0688c9a4 --- /dev/null +++ b/cordova-plugin-moodleapp/src/ios/CDVSystemUI.m @@ -0,0 +1,492 @@ +/* + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + */ + +/* + NOTE: plugman/cordova cli should have already installed this, + but you need the value UIViewControllerBasedStatusBarAppearance + in your Info.plist as well to set the styles in iOS 7 + */ + +#import "CDVSystemUI.h" +#import +#import + +static const void *kHideStatusBar = &kHideStatusBar; +static const void *kStatusBarStyle = &kStatusBarStyle; + +@interface CDVViewController (SystemUI) + +@property (nonatomic, retain) id sb_hideStatusBar; +@property (nonatomic, retain) id sb_statusBarStyle; + +@end + +@implementation CDVViewController (StatusBar) + +@dynamic sb_hideStatusBar; +@dynamic sb_statusBarStyle; + +- (id)sb_hideStatusBar { + return objc_getAssociatedObject(self, kHideStatusBar); +} + +- (void)setSb_hideStatusBar:(id)newHideStatusBar { + objc_setAssociatedObject(self, kHideStatusBar, newHideStatusBar, OBJC_ASSOCIATION_RETAIN_NONATOMIC); +} + +- (id)sb_statusBarStyle { + return objc_getAssociatedObject(self, kStatusBarStyle); +} + +- (void)setSb_statusBarStyle:(id)newStatusBarStyle { + objc_setAssociatedObject(self, kStatusBarStyle, newStatusBarStyle, OBJC_ASSOCIATION_RETAIN_NONATOMIC); +} + +- (BOOL) prefersStatusBarHidden { + return [self.sb_hideStatusBar boolValue]; +} + +- (UIStatusBarStyle)preferredStatusBarStyle +{ + return (UIStatusBarStyle)[self.sb_statusBarStyle intValue]; +} + +@end + + +@interface CDVSystemUI () +- (void)fireTappedEvent; +- (void)updateIsVisible:(BOOL)visible; +@end + +@implementation CDVSystemUI + +- (id)settingForKey:(NSString*)key +{ + return [self.commandDelegate.settings objectForKey:[key lowercaseString]]; +} + +- (void)observeValueForKeyPath:(NSString*)keyPath ofObject:(id)object change:(NSDictionary*)change context:(void*)context +{ + if ([keyPath isEqual:@"statusBarHidden"]) { + NSNumber* newValue = [change objectForKey:NSKeyValueChangeNewKey]; + [self updateIsVisible:![newValue boolValue]]; + } +} + +-(void)cordovaViewWillAppear:(NSNotification*)notification +{ + //add a small delay ( 0.1 seconds ) or statusbar size will be wrong + __weak CDVSystemUI* weakSelf = self; + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.1 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{ + [weakSelf resizeWebView]; + }); +} + +-(void)statusBarDidChangeFrame:(NSNotification*)notification +{ + //add a small delay ( 0.1 seconds ) or statusbar size will be wrong + __weak CDVSystemUI* weakSelf = self; + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.1 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{ + [weakSelf resizeStatusBarBackgroundView]; + [weakSelf resizeWebView]; + }); +} + +- (void)pluginInitialize +{ + // init + NSNumber* uiviewControllerBasedStatusBarAppearance = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"UIViewControllerBasedStatusBarAppearance"]; + _uiviewControllerBasedStatusBarAppearance = (uiviewControllerBasedStatusBarAppearance == nil || [uiviewControllerBasedStatusBarAppearance boolValue]); + + // observe the statusBarHidden property + [[UIApplication sharedApplication] addObserver:self forKeyPath:@"statusBarHidden" options:NSKeyValueObservingOptionNew context:NULL]; + + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(statusBarDidChangeFrame:) name: UIApplicationDidChangeStatusBarFrameNotification object:nil]; + + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(cordovaViewWillAppear:) name: @"CDVViewWillAppearNotification" object:nil]; + + _statusBarOverlaysWebView = YES; // default + + [self initializeStatusBarBackgroundView]; + + self.viewController.view.autoresizesSubviews = YES; + + NSString* setting; + + setting = @"StatusBarBackgroundColor"; + if ([self settingForKey:setting]) { + [self _backgroundColorByHexString:[self settingForKey:setting]]; + } + + setting = @"StatusBarStyle"; + if ([self settingForKey:setting]) { + NSString * styleSetting = [self settingForKey:setting]; + if ([styleSetting isEqualToString:@"blacktranslucent"] || [styleSetting isEqualToString:@"blackopaque"]) { + NSLog(@"%@ is deprecated and will be removed in next major release, use lightcontent", styleSetting); + } + [self setStatusBarStyle:styleSetting]; + } + + setting = @"StatusBarDefaultScrollToTop"; + if ([self settingForKey:setting]) { + self.webView.scrollView.scrollsToTop = [(NSNumber*)[self settingForKey:setting] boolValue]; + } else { + self.webView.scrollView.scrollsToTop = NO; + } + + // blank scroll view to intercept status bar taps + UIScrollView *fakeScrollView = [[UIScrollView alloc] initWithFrame:UIScreen.mainScreen.bounds]; + fakeScrollView.delegate = self; + fakeScrollView.scrollsToTop = YES; + [self.viewController.view addSubview:fakeScrollView]; // Add scrollview to the view heirarchy so that it will begin accepting status bar taps + [self.viewController.view sendSubviewToBack:fakeScrollView]; // Send it to the very back of the view heirarchy + fakeScrollView.contentSize = CGSizeMake(UIScreen.mainScreen.bounds.size.width, UIScreen.mainScreen.bounds.size.height * 2.0f); // Make the scroll view longer than the screen itself + fakeScrollView.contentOffset = CGPointMake(0.0f, UIScreen.mainScreen.bounds.size.height); // Scroll down so a tap will take scroll view back to the top + + _statusBarVisible = ![UIApplication sharedApplication].isStatusBarHidden; +} + +- (void)onReset { + _eventsCallbackId = nil; +} + +- (void)fireTappedEvent { + if (_eventsCallbackId == nil) { + return; + } + NSDictionary* payload = @{@"type": @"tap"}; + CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:payload]; + [result setKeepCallbackAsBool:YES]; + [self.commandDelegate sendPluginResult:result callbackId:_eventsCallbackId]; +} + +- (void)updateIsVisible:(BOOL)visible { + if (_eventsCallbackId == nil) { + return; + } + CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsBool:visible]; + [result setKeepCallbackAsBool:YES]; + [self.commandDelegate sendPluginResult:result callbackId:_eventsCallbackId]; +} + +- (void) _ready:(CDVInvokedUrlCommand*)command +{ + _eventsCallbackId = command.callbackId; + [self updateIsVisible:![UIApplication sharedApplication].statusBarHidden]; + NSString* setting = @"StatusBarOverlaysWebView"; + if ([self settingForKey:setting]) { + self.statusBarOverlaysWebView = [(NSNumber*)[self settingForKey:setting] boolValue]; + if (self.statusBarOverlaysWebView) { + [self resizeWebView]; + } + } +} + +- (void) initializeStatusBarBackgroundView +{ + CGRect statusBarFrame = [UIApplication sharedApplication].statusBarFrame; + + if ([[UIApplication sharedApplication]statusBarOrientation] == UIInterfaceOrientationPortraitUpsideDown && + statusBarFrame.size.height + statusBarFrame.origin.y == [self.viewController.view.window bounds].size.height) { + + // When started in upside-down orientation on iOS 7, status bar will be bound to lower edge of the + // screen (statusBarFrame.origin.y will be somewhere around screen height). In this case we need to + // correct frame's coordinates + statusBarFrame.origin.y = 0; + } + + _statusBarBackgroundView = [[UIView alloc] initWithFrame:statusBarFrame]; + _statusBarBackgroundView.backgroundColor = _statusBarBackgroundColor; + _statusBarBackgroundView.autoresizingMask = (UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleBottomMargin); + _statusBarBackgroundView.autoresizesSubviews = YES; +} + +- (void) setStatusBarOverlaysWebView:(BOOL)statusBarOverlaysWebView +{ + // we only care about the latest iOS version or a change in setting + if (statusBarOverlaysWebView == _statusBarOverlaysWebView) { + return; + } + + _statusBarOverlaysWebView = statusBarOverlaysWebView; + + [self resizeWebView]; + + if (statusBarOverlaysWebView) { + + [_statusBarBackgroundView removeFromSuperview]; + + } else { + + [self initializeStatusBarBackgroundView]; + [self.webView.superview addSubview:_statusBarBackgroundView]; + + } + +} + +- (BOOL) statusBarOverlaysWebView +{ + return _statusBarOverlaysWebView; +} + +- (void) overlaysWebView:(CDVInvokedUrlCommand*)command +{ + id value = [command argumentAtIndex:0]; + if (!([value isKindOfClass:[NSNumber class]])) { + value = [NSNumber numberWithBool:YES]; + } + + self.statusBarOverlaysWebView = [value boolValue]; +} + +- (void) refreshStatusBarAppearance +{ + SEL sel = NSSelectorFromString(@"setNeedsStatusBarAppearanceUpdate"); + if ([self.viewController respondsToSelector:sel]) { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Warc-performSelector-leaks" + [self.viewController performSelector:sel withObject:nil]; +#pragma clang diagnostic pop + } +} + +- (void) setStyleForStatusBar:(UIStatusBarStyle)style +{ + if (_uiviewControllerBasedStatusBarAppearance) { + CDVViewController* vc = (CDVViewController*)self.viewController; + vc.sb_statusBarStyle = [NSNumber numberWithInt:style]; + [self refreshStatusBarAppearance]; + + } else { + [[UIApplication sharedApplication] setStatusBarStyle:style]; + } +} + +- (void) setStatusBarStyle:(NSString*)statusBarStyle +{ + // default, lightContent, blackTranslucent, blackOpaque + NSString* lcStatusBarStyle = [statusBarStyle lowercaseString]; + + if ([lcStatusBarStyle isEqualToString:@"default"]) { + [self styleDefault:nil]; + } else if ([lcStatusBarStyle isEqualToString:@"lightcontent"]) { + [self styleLightContent:nil]; + } else if ([lcStatusBarStyle isEqualToString:@"blacktranslucent"]) { + [self styleBlackTranslucent:nil]; + } else if ([lcStatusBarStyle isEqualToString:@"blackopaque"]) { + [self styleBlackOpaque:nil]; + } +} + +- (void) styleDefault:(CDVInvokedUrlCommand*)command +{ + if (@available(iOS 13.0, *)) { + // TODO - Replace with UIStatusBarStyleDarkContent once Xcode 10 support is dropped + [self setStyleForStatusBar:3]; + } else { + [self setStyleForStatusBar:UIStatusBarStyleDefault]; + } +} + +- (void) styleLightContent:(CDVInvokedUrlCommand*)command +{ + [self setStyleForStatusBar:UIStatusBarStyleLightContent]; +} + +- (void) styleBlackTranslucent:(CDVInvokedUrlCommand*)command +{ + [self setStyleForStatusBar:UIStatusBarStyleLightContent]; +} + +- (void) styleBlackOpaque:(CDVInvokedUrlCommand*)command +{ + [self setStyleForStatusBar:UIStatusBarStyleLightContent]; +} + +- (void) backgroundColorByName:(CDVInvokedUrlCommand*)command +{ + id value = [command argumentAtIndex:0]; + if (!([value isKindOfClass:[NSString class]])) { + value = @"black"; + } + + SEL selector = NSSelectorFromString([value stringByAppendingString:@"Color"]); + if ([UIColor respondsToSelector:selector]) { + _statusBarBackgroundView.backgroundColor = [UIColor performSelector:selector]; + } +} + +- (void) _backgroundColorByHexString:(NSString*)hexString +{ + unsigned int rgbValue = 0; + NSScanner* scanner = [NSScanner scannerWithString:hexString]; + [scanner setScanLocation:1]; + [scanner scanHexInt:&rgbValue]; + + _statusBarBackgroundColor = [UIColor colorWithRed:((rgbValue & 0xFF0000) >> 16)/255.0 green:((rgbValue & 0xFF00) >> 8)/255.0 blue:(rgbValue & 0xFF)/255.0 alpha:1.0]; + _statusBarBackgroundView.backgroundColor = _statusBarBackgroundColor; +} + +- (void) backgroundColorByHexString:(CDVInvokedUrlCommand*)command +{ + NSString* value = [command argumentAtIndex:0]; + if (!([value isKindOfClass:[NSString class]])) { + value = @"#000000"; + } + + if (![value hasPrefix:@"#"] || [value length] < 7) { + return; + } + + [self _backgroundColorByHexString:value]; +} + +- (void) hideStatusBar +{ + if (_uiviewControllerBasedStatusBarAppearance) { + CDVViewController* vc = (CDVViewController*)self.viewController; + vc.sb_hideStatusBar = [NSNumber numberWithBool:YES]; + [self refreshStatusBarAppearance]; + + } else { + UIApplication* app = [UIApplication sharedApplication]; + [app setStatusBarHidden:YES]; + } +} + +- (void) hide:(CDVInvokedUrlCommand*)command +{ + _statusBarVisible = NO; + UIApplication* app = [UIApplication sharedApplication]; + + if (!app.isStatusBarHidden) + { + + [self hideStatusBar]; + + [_statusBarBackgroundView removeFromSuperview]; + + [self resizeWebView]; + + _statusBarBackgroundView.hidden = YES; + } +} + +- (void) showStatusBar +{ + if (_uiviewControllerBasedStatusBarAppearance) { + CDVViewController* vc = (CDVViewController*)self.viewController; + vc.sb_hideStatusBar = [NSNumber numberWithBool:NO]; + [self refreshStatusBarAppearance]; + + } else { + UIApplication* app = [UIApplication sharedApplication]; + [app setStatusBarHidden:NO]; + } +} + +- (void) show:(CDVInvokedUrlCommand*)command +{ + _statusBarVisible = YES; + UIApplication* app = [UIApplication sharedApplication]; + + if (app.isStatusBarHidden) + { + [self showStatusBar]; + [self resizeWebView]; + + if (!self.statusBarOverlaysWebView) { + + // there is a possibility that when the statusbar was hidden, it was in a different orientation + // from the current one. Therefore we need to expand the statusBarBackgroundView as well to the + // statusBar's current size + [self resizeStatusBarBackgroundView]; + [self.webView.superview addSubview:_statusBarBackgroundView]; + + } + + _statusBarBackgroundView.hidden = NO; + } +} + +-(void)resizeStatusBarBackgroundView { + CGRect statusBarFrame = [UIApplication sharedApplication].statusBarFrame; + CGRect sbBgFrame = _statusBarBackgroundView.frame; + sbBgFrame.size = statusBarFrame.size; + _statusBarBackgroundView.frame = sbBgFrame; +} + +-(void)resizeWebView +{ + BOOL isIOS11 = (IsAtLeastiOSVersion(@"11.0")); + + CGRect bounds = [self.viewController.view.window bounds]; + if (CGRectEqualToRect(bounds, CGRectZero)) { + bounds = [[UIScreen mainScreen] bounds]; + } + + self.viewController.view.frame = bounds; + + self.webView.frame = bounds; + + CGRect statusBarFrame = [UIApplication sharedApplication].statusBarFrame; + CGRect frame = self.webView.frame; + CGFloat height = statusBarFrame.size.height; + + if (!self.statusBarOverlaysWebView) { + frame.origin.y = height; + } else { + frame.origin.y = height >= 20 ? height - 20 : 0; + if (isIOS11) { +#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 110000 + if (@available(iOS 11.0, *)) { + float safeAreaTop = self.webView.safeAreaInsets.top; + if (height >= safeAreaTop && safeAreaTop >0) { + // Sometimes when in-call/recording/hotspot larger status bar is present, the safeAreaTop is 40 but we want frame.origin.y to be 20 + frame.origin.y = safeAreaTop == 40 ? 20 : height - safeAreaTop; + } else { + frame.origin.y = 0; + } + } +#endif + } + } + frame.size.height -= frame.origin.y; + self.webView.frame = frame; + +} + +- (void) dealloc +{ + [[UIApplication sharedApplication] removeObserver:self forKeyPath:@"statusBarHidden"]; + [[NSNotificationCenter defaultCenter]removeObserver:self name:UIApplicationDidChangeStatusBarOrientationNotification object:nil]; +} + + +#pragma mark - UIScrollViewDelegate + +- (BOOL)scrollViewShouldScrollToTop:(UIScrollView *)scrollView +{ + [self fireTappedEvent]; + return NO; +} + +@end diff --git a/package-lock.json b/package-lock.json index bcf4cc08932..ae9adf221f5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12224,11 +12224,6 @@ "resolved": "https://registry.npmjs.org/cordova-plugin-screen-orientation/-/cordova-plugin-screen-orientation-3.0.3.tgz", "integrity": "sha512-Dt8lO8BECZfE/pKbYQZ72Wr811fYMScxw7c9v/gJ3etOPCBrgl8xIHOOZu4nY2ehRyxFPtZi3VeGvIG+3DZoZQ==" }, - "cordova-plugin-statusbar": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cordova-plugin-statusbar/-/cordova-plugin-statusbar-3.0.0.tgz", - "integrity": "sha512-nzkeWeyLA6+1FryzO0aeB6NS8MZ45gnBYeq2VZqfdNbddZEgtpI4XPYdBVxvm9NhcVoJ3tdA1OBnQD9JryoV0Q==" - }, "cordova-plugin-wkuserscript": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/cordova-plugin-wkuserscript/-/cordova-plugin-wkuserscript-1.0.1.tgz", diff --git a/package.json b/package.json index 7b7ee7642fd..81e078eb452 100644 --- a/package.json +++ b/package.json @@ -112,7 +112,6 @@ "cordova-plugin-network-information": "^3.0.0", "cordova-plugin-prevent-override": "^1.0.1", "cordova-plugin-screen-orientation": "^3.0.2", - "cordova-plugin-statusbar": "^3.0.0", "cordova-plugin-wkuserscript": "^1.0.1", "cordova-plugin-wkwebview-cookies": "^1.0.1", "cordova-sqlite-storage": "^6.1.0", @@ -229,7 +228,6 @@ "cordova-plugin-media-capture": {}, "cordova-plugin-network-information": {}, "@moodlehq/cordova-plugin-qrscanner": {}, - "cordova-plugin-statusbar": {}, "cordova-plugin-wkuserscript": {}, "cordova-plugin-wkwebview-cookies": {}, "@moodlehq/cordova-plugin-zip": {}, @@ -255,4 +253,4 @@ "optionalDependencies": { "keytar": "^7.2.0" } -} +} \ No newline at end of file