michael@0: /* michael@0: Licensed to the Apache Software Foundation (ASF) under one michael@0: or more contributor license agreements. See the NOTICE file michael@0: distributed with this work for additional information michael@0: regarding copyright ownership. The ASF licenses this file michael@0: to you under the Apache License, Version 2.0 (the michael@0: "License"); you may not use this file except in compliance michael@0: with the License. You may obtain a copy of the License at michael@0: michael@0: http://www.apache.org/licenses/LICENSE-2.0 michael@0: michael@0: Unless required by applicable law or agreed to in writing, michael@0: software distributed under the License is distributed on an michael@0: "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY michael@0: KIND, either express or implied. See the License for the michael@0: specific language governing permissions and limitations michael@0: under the License. michael@0: */ michael@0: michael@0: #import "CDVSplashScreen.h" michael@0: #import michael@0: #import michael@0: michael@0: #define kSplashScreenDurationDefault 0.25f michael@0: michael@0: michael@0: @implementation CDVSplashScreen michael@0: michael@0: - (void)pluginInitialize michael@0: { michael@0: [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(pageDidLoad) name:CDVPageDidLoadNotification object:self.webView]; michael@0: michael@0: [self setVisible:YES]; michael@0: } michael@0: michael@0: - (void)show:(CDVInvokedUrlCommand*)command michael@0: { michael@0: [self setVisible:YES]; michael@0: } michael@0: michael@0: - (void)hide:(CDVInvokedUrlCommand*)command michael@0: { michael@0: [self setVisible:NO]; michael@0: } michael@0: michael@0: - (void)pageDidLoad michael@0: { michael@0: id autoHideSplashScreenValue = [self.commandDelegate.settings objectForKey:[@"AutoHideSplashScreen" lowercaseString]]; michael@0: michael@0: // if value is missing, default to yes michael@0: if ((autoHideSplashScreenValue == nil) || [autoHideSplashScreenValue boolValue]) { michael@0: [self setVisible:NO]; michael@0: } michael@0: } michael@0: michael@0: - (void)observeValueForKeyPath:(NSString*)keyPath ofObject:(id)object change:(NSDictionary*)change context:(void*)context michael@0: { michael@0: [self updateImage]; michael@0: } michael@0: michael@0: - (void)createViews michael@0: { michael@0: /* michael@0: * The Activity View is the top spinning throbber in the status/battery bar. We init it with the default Grey Style. michael@0: * michael@0: * whiteLarge = UIActivityIndicatorViewStyleWhiteLarge michael@0: * white = UIActivityIndicatorViewStyleWhite michael@0: * gray = UIActivityIndicatorViewStyleGray michael@0: * michael@0: */ michael@0: NSString* topActivityIndicator = [self.commandDelegate.settings objectForKey:[@"TopActivityIndicator" lowercaseString]]; michael@0: UIActivityIndicatorViewStyle topActivityIndicatorStyle = UIActivityIndicatorViewStyleGray; michael@0: michael@0: if ([topActivityIndicator isEqualToString:@"whiteLarge"]) { michael@0: topActivityIndicatorStyle = UIActivityIndicatorViewStyleWhiteLarge; michael@0: } else if ([topActivityIndicator isEqualToString:@"white"]) { michael@0: topActivityIndicatorStyle = UIActivityIndicatorViewStyleWhite; michael@0: } else if ([topActivityIndicator isEqualToString:@"gray"]) { michael@0: topActivityIndicatorStyle = UIActivityIndicatorViewStyleGray; michael@0: } michael@0: michael@0: UIView* parentView = self.viewController.view; michael@0: parentView.userInteractionEnabled = NO; // disable user interaction while splashscreen is shown michael@0: _activityView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:topActivityIndicatorStyle]; michael@0: _activityView.center = CGPointMake(parentView.bounds.size.width / 2, parentView.bounds.size.height / 2); michael@0: _activityView.autoresizingMask = UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleLeftMargin michael@0: | UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleRightMargin; michael@0: [_activityView startAnimating]; michael@0: michael@0: // Set the frame & image later. michael@0: _imageView = [[UIImageView alloc] init]; michael@0: [parentView addSubview:_imageView]; michael@0: michael@0: id showSplashScreenSpinnerValue = [self.commandDelegate.settings objectForKey:[@"ShowSplashScreenSpinner" lowercaseString]]; michael@0: // backwards compatibility - if key is missing, default to true michael@0: if ((showSplashScreenSpinnerValue == nil) || [showSplashScreenSpinnerValue boolValue]) { michael@0: [parentView addSubview:_activityView]; michael@0: } michael@0: michael@0: // Frame is required when launching in portrait mode. michael@0: // Bounds for landscape since it captures the rotation. michael@0: [parentView addObserver:self forKeyPath:@"frame" options:0 context:nil]; michael@0: [parentView addObserver:self forKeyPath:@"bounds" options:0 context:nil]; michael@0: michael@0: [self updateImage]; michael@0: } michael@0: michael@0: - (void)destroyViews michael@0: { michael@0: [_imageView removeFromSuperview]; michael@0: [_activityView removeFromSuperview]; michael@0: _imageView = nil; michael@0: _activityView = nil; michael@0: _curImageName = nil; michael@0: michael@0: self.viewController.view.userInteractionEnabled = YES; // re-enable user interaction upon completion michael@0: [self.viewController.view removeObserver:self forKeyPath:@"frame"]; michael@0: [self.viewController.view removeObserver:self forKeyPath:@"bounds"]; michael@0: } michael@0: michael@0: - (CDV_iOSDevice) getCurrentDevice michael@0: { michael@0: CDV_iOSDevice device; michael@0: michael@0: UIScreen* mainScreen = [UIScreen mainScreen]; michael@0: CGFloat mainScreenHeight = mainScreen.bounds.size.height; michael@0: CGFloat mainScreenWidth = mainScreen.bounds.size.width; michael@0: michael@0: int limit = MAX(mainScreenHeight,mainScreenWidth); michael@0: michael@0: device.iPad = (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad); michael@0: device.iPhone = (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone); michael@0: device.retina = ([mainScreen scale] == 2.0); michael@0: device.iPhone5 = (device.iPhone && limit == 568.0); michael@0: // note these below is not a true device detect, for example if you are on an michael@0: // iPhone 6/6+ but the app is scaled it will prob set iPhone5 as true, but michael@0: // this is appropriate for detecting the runtime screen environment michael@0: device.iPhone6 = (device.iPhone && limit == 667.0); michael@0: device.iPhone6Plus = (device.iPhone && limit == 736.0); michael@0: michael@0: return device; michael@0: } michael@0: michael@0: - (NSString*)getImageName:(UIInterfaceOrientation)currentOrientation delegate:(id)orientationDelegate device:(CDV_iOSDevice)device michael@0: { michael@0: // Use UILaunchImageFile if specified in plist. Otherwise, use Default. michael@0: NSString* imageName = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"UILaunchImageFile"]; michael@0: michael@0: NSUInteger supportedOrientations = [orientationDelegate supportedInterfaceOrientations]; michael@0: michael@0: // Checks to see if the developer has locked the orientation to use only one of Portrait or Landscape michael@0: BOOL supportsLandscape = (supportedOrientations & UIInterfaceOrientationMaskLandscape); michael@0: BOOL supportsPortrait = (supportedOrientations & UIInterfaceOrientationMaskPortrait || supportedOrientations & UIInterfaceOrientationMaskPortraitUpsideDown); michael@0: // this means there are no mixed orientations in there michael@0: BOOL isOrientationLocked = !(supportsPortrait && supportsLandscape); michael@0: michael@0: if (imageName) { michael@0: imageName = [imageName stringByDeletingPathExtension]; michael@0: } else { michael@0: imageName = @"Default"; michael@0: } michael@0: michael@0: if (device.iPhone5) { // does not support landscape michael@0: imageName = [imageName stringByAppendingString:@"-568h"]; michael@0: } else if (device.iPhone6) { // does not support landscape michael@0: imageName = [imageName stringByAppendingString:@"-667h"]; michael@0: } else if (device.iPhone6Plus) { // supports landscape michael@0: if (isOrientationLocked) { michael@0: imageName = [imageName stringByAppendingString:(supportsLandscape ? @"-Landscape" : @"")]; michael@0: } else { michael@0: switch (currentOrientation) { michael@0: case UIInterfaceOrientationLandscapeLeft: michael@0: case UIInterfaceOrientationLandscapeRight: michael@0: imageName = [imageName stringByAppendingString:@"-Landscape"]; michael@0: break; michael@0: default: michael@0: break; michael@0: } michael@0: } michael@0: imageName = [imageName stringByAppendingString:@"-736h"]; michael@0: michael@0: } else if (device.iPad) { // supports landscape michael@0: if (isOrientationLocked) { michael@0: imageName = [imageName stringByAppendingString:(supportsLandscape ? @"-Landscape" : @"-Portrait")]; michael@0: } else { michael@0: switch (currentOrientation) { michael@0: case UIInterfaceOrientationLandscapeLeft: michael@0: case UIInterfaceOrientationLandscapeRight: michael@0: imageName = [imageName stringByAppendingString:@"-Landscape"]; michael@0: break; michael@0: michael@0: case UIInterfaceOrientationPortrait: michael@0: case UIInterfaceOrientationPortraitUpsideDown: michael@0: default: michael@0: imageName = [imageName stringByAppendingString:@"-Portrait"]; michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: michael@0: return imageName; michael@0: } michael@0: michael@0: // Sets the view's frame and image. michael@0: - (void)updateImage michael@0: { michael@0: NSString* imageName = [self getImageName:self.viewController.interfaceOrientation delegate:(id)self.viewController device:[self getCurrentDevice]]; michael@0: michael@0: if (![imageName isEqualToString:_curImageName]) { michael@0: UIImage* img = [UIImage imageNamed:imageName]; michael@0: _imageView.image = img; michael@0: _curImageName = imageName; michael@0: } michael@0: michael@0: // Check that splash screen's image exists before updating bounds michael@0: if (_imageView.image) { michael@0: [self updateBounds]; michael@0: } else { michael@0: NSLog(@"WARNING: The splashscreen image named %@ was not found", imageName); michael@0: } michael@0: } michael@0: michael@0: - (void)updateBounds michael@0: { michael@0: UIImage* img = _imageView.image; michael@0: CGRect imgBounds = (img) ? CGRectMake(0, 0, img.size.width, img.size.height) : CGRectZero; michael@0: michael@0: CGSize screenSize = [self.viewController.view convertRect:[UIScreen mainScreen].bounds fromView:nil].size; michael@0: UIInterfaceOrientation orientation = self.viewController.interfaceOrientation; michael@0: CGAffineTransform imgTransform = CGAffineTransformIdentity; michael@0: michael@0: /* If and only if an iPhone application is landscape-only as per michael@0: * UISupportedInterfaceOrientations, the view controller's orientation is michael@0: * landscape. In this case the image must be rotated in order to appear michael@0: * correctly. michael@0: */ michael@0: if (UIInterfaceOrientationIsLandscape(orientation) && !CDV_IsIPad()) { michael@0: imgTransform = CGAffineTransformMakeRotation(M_PI / 2); michael@0: imgBounds.size = CGSizeMake(imgBounds.size.height, imgBounds.size.width); michael@0: } michael@0: michael@0: // There's a special case when the image is the size of the screen. michael@0: if (CGSizeEqualToSize(screenSize, imgBounds.size)) { michael@0: CGRect statusFrame = [self.viewController.view convertRect:[UIApplication sharedApplication].statusBarFrame fromView:nil]; michael@0: if (!(IsAtLeastiOSVersion(@"7.0"))) { michael@0: imgBounds.origin.y -= statusFrame.size.height; michael@0: } michael@0: } else if (imgBounds.size.width > 0) { michael@0: CGRect viewBounds = self.viewController.view.bounds; michael@0: CGFloat imgAspect = imgBounds.size.width / imgBounds.size.height; michael@0: CGFloat viewAspect = viewBounds.size.width / viewBounds.size.height; michael@0: // This matches the behaviour of the native splash screen. michael@0: CGFloat ratio; michael@0: if (viewAspect > imgAspect) { michael@0: ratio = viewBounds.size.width / imgBounds.size.width; michael@0: } else { michael@0: ratio = viewBounds.size.height / imgBounds.size.height; michael@0: } michael@0: imgBounds.size.height *= ratio; michael@0: imgBounds.size.width *= ratio; michael@0: } michael@0: michael@0: _imageView.transform = imgTransform; michael@0: _imageView.frame = imgBounds; michael@0: } michael@0: michael@0: - (void)setVisible:(BOOL)visible michael@0: { michael@0: if (visible == _visible) { michael@0: return; michael@0: } michael@0: _visible = visible; michael@0: michael@0: id fadeSplashScreenValue = [self.commandDelegate.settings objectForKey:[@"FadeSplashScreen" lowercaseString]]; michael@0: id fadeSplashScreenDuration = [self.commandDelegate.settings objectForKey:[@"FadeSplashScreenDuration" lowercaseString]]; michael@0: michael@0: float fadeDuration = fadeSplashScreenDuration == nil ? kSplashScreenDurationDefault : [fadeSplashScreenDuration floatValue]; michael@0: michael@0: if ((fadeSplashScreenValue == nil) || ![fadeSplashScreenValue boolValue]) { michael@0: fadeDuration = 0; michael@0: } michael@0: michael@0: // Never animate the showing of the splash screen. michael@0: if (visible) { michael@0: if (_imageView == nil) { michael@0: [self createViews]; michael@0: } michael@0: } else if (fadeDuration == 0) { michael@0: [self destroyViews]; michael@0: } else { michael@0: [UIView transitionWithView:self.viewController.view michael@0: duration:fadeDuration michael@0: options:UIViewAnimationOptionTransitionNone michael@0: animations:^(void) { michael@0: [_imageView setAlpha:0]; michael@0: [_activityView setAlpha:0]; michael@0: } michael@0: completion:^(BOOL finished) { michael@0: if (finished) { michael@0: [self destroyViews]; michael@0: } michael@0: } michael@0: ]; michael@0: } michael@0: } michael@0: michael@0: @end