Thu, 04 Jun 2015 14:50:33 +0200
Genesis of lecture sources for Droidcon Berlin 2015 in Postbahnhof.
michael@0 | 1 | /* |
michael@0 | 2 | Licensed to the Apache Software Foundation (ASF) under one |
michael@0 | 3 | or more contributor license agreements. See the NOTICE file |
michael@0 | 4 | distributed with this work for additional information |
michael@0 | 5 | regarding copyright ownership. The ASF licenses this file |
michael@0 | 6 | to you under the Apache License, Version 2.0 (the |
michael@0 | 7 | "License"); you may not use this file except in compliance |
michael@0 | 8 | with the License. You may obtain a copy of the License at |
michael@0 | 9 | |
michael@0 | 10 | http://www.apache.org/licenses/LICENSE-2.0 |
michael@0 | 11 | |
michael@0 | 12 | Unless required by applicable law or agreed to in writing, |
michael@0 | 13 | software distributed under the License is distributed on an |
michael@0 | 14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
michael@0 | 15 | KIND, either express or implied. See the License for the |
michael@0 | 16 | specific language governing permissions and limitations |
michael@0 | 17 | under the License. |
michael@0 | 18 | */ |
michael@0 | 19 | |
michael@0 | 20 | #import "CDVSplashScreen.h" |
michael@0 | 21 | #import <Cordova/CDVViewController.h> |
michael@0 | 22 | #import <Cordova/CDVScreenOrientationDelegate.h> |
michael@0 | 23 | |
michael@0 | 24 | #define kSplashScreenDurationDefault 0.25f |
michael@0 | 25 | |
michael@0 | 26 | |
michael@0 | 27 | @implementation CDVSplashScreen |
michael@0 | 28 | |
michael@0 | 29 | - (void)pluginInitialize |
michael@0 | 30 | { |
michael@0 | 31 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(pageDidLoad) name:CDVPageDidLoadNotification object:self.webView]; |
michael@0 | 32 | |
michael@0 | 33 | [self setVisible:YES]; |
michael@0 | 34 | } |
michael@0 | 35 | |
michael@0 | 36 | - (void)show:(CDVInvokedUrlCommand*)command |
michael@0 | 37 | { |
michael@0 | 38 | [self setVisible:YES]; |
michael@0 | 39 | } |
michael@0 | 40 | |
michael@0 | 41 | - (void)hide:(CDVInvokedUrlCommand*)command |
michael@0 | 42 | { |
michael@0 | 43 | [self setVisible:NO]; |
michael@0 | 44 | } |
michael@0 | 45 | |
michael@0 | 46 | - (void)pageDidLoad |
michael@0 | 47 | { |
michael@0 | 48 | id autoHideSplashScreenValue = [self.commandDelegate.settings objectForKey:[@"AutoHideSplashScreen" lowercaseString]]; |
michael@0 | 49 | |
michael@0 | 50 | // if value is missing, default to yes |
michael@0 | 51 | if ((autoHideSplashScreenValue == nil) || [autoHideSplashScreenValue boolValue]) { |
michael@0 | 52 | [self setVisible:NO]; |
michael@0 | 53 | } |
michael@0 | 54 | } |
michael@0 | 55 | |
michael@0 | 56 | - (void)observeValueForKeyPath:(NSString*)keyPath ofObject:(id)object change:(NSDictionary*)change context:(void*)context |
michael@0 | 57 | { |
michael@0 | 58 | [self updateImage]; |
michael@0 | 59 | } |
michael@0 | 60 | |
michael@0 | 61 | - (void)createViews |
michael@0 | 62 | { |
michael@0 | 63 | /* |
michael@0 | 64 | * The Activity View is the top spinning throbber in the status/battery bar. We init it with the default Grey Style. |
michael@0 | 65 | * |
michael@0 | 66 | * whiteLarge = UIActivityIndicatorViewStyleWhiteLarge |
michael@0 | 67 | * white = UIActivityIndicatorViewStyleWhite |
michael@0 | 68 | * gray = UIActivityIndicatorViewStyleGray |
michael@0 | 69 | * |
michael@0 | 70 | */ |
michael@0 | 71 | NSString* topActivityIndicator = [self.commandDelegate.settings objectForKey:[@"TopActivityIndicator" lowercaseString]]; |
michael@0 | 72 | UIActivityIndicatorViewStyle topActivityIndicatorStyle = UIActivityIndicatorViewStyleGray; |
michael@0 | 73 | |
michael@0 | 74 | if ([topActivityIndicator isEqualToString:@"whiteLarge"]) { |
michael@0 | 75 | topActivityIndicatorStyle = UIActivityIndicatorViewStyleWhiteLarge; |
michael@0 | 76 | } else if ([topActivityIndicator isEqualToString:@"white"]) { |
michael@0 | 77 | topActivityIndicatorStyle = UIActivityIndicatorViewStyleWhite; |
michael@0 | 78 | } else if ([topActivityIndicator isEqualToString:@"gray"]) { |
michael@0 | 79 | topActivityIndicatorStyle = UIActivityIndicatorViewStyleGray; |
michael@0 | 80 | } |
michael@0 | 81 | |
michael@0 | 82 | UIView* parentView = self.viewController.view; |
michael@0 | 83 | parentView.userInteractionEnabled = NO; // disable user interaction while splashscreen is shown |
michael@0 | 84 | _activityView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:topActivityIndicatorStyle]; |
michael@0 | 85 | _activityView.center = CGPointMake(parentView.bounds.size.width / 2, parentView.bounds.size.height / 2); |
michael@0 | 86 | _activityView.autoresizingMask = UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleLeftMargin |
michael@0 | 87 | | UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleRightMargin; |
michael@0 | 88 | [_activityView startAnimating]; |
michael@0 | 89 | |
michael@0 | 90 | // Set the frame & image later. |
michael@0 | 91 | _imageView = [[UIImageView alloc] init]; |
michael@0 | 92 | [parentView addSubview:_imageView]; |
michael@0 | 93 | |
michael@0 | 94 | id showSplashScreenSpinnerValue = [self.commandDelegate.settings objectForKey:[@"ShowSplashScreenSpinner" lowercaseString]]; |
michael@0 | 95 | // backwards compatibility - if key is missing, default to true |
michael@0 | 96 | if ((showSplashScreenSpinnerValue == nil) || [showSplashScreenSpinnerValue boolValue]) { |
michael@0 | 97 | [parentView addSubview:_activityView]; |
michael@0 | 98 | } |
michael@0 | 99 | |
michael@0 | 100 | // Frame is required when launching in portrait mode. |
michael@0 | 101 | // Bounds for landscape since it captures the rotation. |
michael@0 | 102 | [parentView addObserver:self forKeyPath:@"frame" options:0 context:nil]; |
michael@0 | 103 | [parentView addObserver:self forKeyPath:@"bounds" options:0 context:nil]; |
michael@0 | 104 | |
michael@0 | 105 | [self updateImage]; |
michael@0 | 106 | } |
michael@0 | 107 | |
michael@0 | 108 | - (void)destroyViews |
michael@0 | 109 | { |
michael@0 | 110 | [_imageView removeFromSuperview]; |
michael@0 | 111 | [_activityView removeFromSuperview]; |
michael@0 | 112 | _imageView = nil; |
michael@0 | 113 | _activityView = nil; |
michael@0 | 114 | _curImageName = nil; |
michael@0 | 115 | |
michael@0 | 116 | self.viewController.view.userInteractionEnabled = YES; // re-enable user interaction upon completion |
michael@0 | 117 | [self.viewController.view removeObserver:self forKeyPath:@"frame"]; |
michael@0 | 118 | [self.viewController.view removeObserver:self forKeyPath:@"bounds"]; |
michael@0 | 119 | } |
michael@0 | 120 | |
michael@0 | 121 | - (CDV_iOSDevice) getCurrentDevice |
michael@0 | 122 | { |
michael@0 | 123 | CDV_iOSDevice device; |
michael@0 | 124 | |
michael@0 | 125 | UIScreen* mainScreen = [UIScreen mainScreen]; |
michael@0 | 126 | CGFloat mainScreenHeight = mainScreen.bounds.size.height; |
michael@0 | 127 | CGFloat mainScreenWidth = mainScreen.bounds.size.width; |
michael@0 | 128 | |
michael@0 | 129 | int limit = MAX(mainScreenHeight,mainScreenWidth); |
michael@0 | 130 | |
michael@0 | 131 | device.iPad = (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad); |
michael@0 | 132 | device.iPhone = (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone); |
michael@0 | 133 | device.retina = ([mainScreen scale] == 2.0); |
michael@0 | 134 | device.iPhone5 = (device.iPhone && limit == 568.0); |
michael@0 | 135 | // note these below is not a true device detect, for example if you are on an |
michael@0 | 136 | // iPhone 6/6+ but the app is scaled it will prob set iPhone5 as true, but |
michael@0 | 137 | // this is appropriate for detecting the runtime screen environment |
michael@0 | 138 | device.iPhone6 = (device.iPhone && limit == 667.0); |
michael@0 | 139 | device.iPhone6Plus = (device.iPhone && limit == 736.0); |
michael@0 | 140 | |
michael@0 | 141 | return device; |
michael@0 | 142 | } |
michael@0 | 143 | |
michael@0 | 144 | - (NSString*)getImageName:(UIInterfaceOrientation)currentOrientation delegate:(id<CDVScreenOrientationDelegate>)orientationDelegate device:(CDV_iOSDevice)device |
michael@0 | 145 | { |
michael@0 | 146 | // Use UILaunchImageFile if specified in plist. Otherwise, use Default. |
michael@0 | 147 | NSString* imageName = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"UILaunchImageFile"]; |
michael@0 | 148 | |
michael@0 | 149 | NSUInteger supportedOrientations = [orientationDelegate supportedInterfaceOrientations]; |
michael@0 | 150 | |
michael@0 | 151 | // Checks to see if the developer has locked the orientation to use only one of Portrait or Landscape |
michael@0 | 152 | BOOL supportsLandscape = (supportedOrientations & UIInterfaceOrientationMaskLandscape); |
michael@0 | 153 | BOOL supportsPortrait = (supportedOrientations & UIInterfaceOrientationMaskPortrait || supportedOrientations & UIInterfaceOrientationMaskPortraitUpsideDown); |
michael@0 | 154 | // this means there are no mixed orientations in there |
michael@0 | 155 | BOOL isOrientationLocked = !(supportsPortrait && supportsLandscape); |
michael@0 | 156 | |
michael@0 | 157 | if (imageName) { |
michael@0 | 158 | imageName = [imageName stringByDeletingPathExtension]; |
michael@0 | 159 | } else { |
michael@0 | 160 | imageName = @"Default"; |
michael@0 | 161 | } |
michael@0 | 162 | |
michael@0 | 163 | if (device.iPhone5) { // does not support landscape |
michael@0 | 164 | imageName = [imageName stringByAppendingString:@"-568h"]; |
michael@0 | 165 | } else if (device.iPhone6) { // does not support landscape |
michael@0 | 166 | imageName = [imageName stringByAppendingString:@"-667h"]; |
michael@0 | 167 | } else if (device.iPhone6Plus) { // supports landscape |
michael@0 | 168 | if (isOrientationLocked) { |
michael@0 | 169 | imageName = [imageName stringByAppendingString:(supportsLandscape ? @"-Landscape" : @"")]; |
michael@0 | 170 | } else { |
michael@0 | 171 | switch (currentOrientation) { |
michael@0 | 172 | case UIInterfaceOrientationLandscapeLeft: |
michael@0 | 173 | case UIInterfaceOrientationLandscapeRight: |
michael@0 | 174 | imageName = [imageName stringByAppendingString:@"-Landscape"]; |
michael@0 | 175 | break; |
michael@0 | 176 | default: |
michael@0 | 177 | break; |
michael@0 | 178 | } |
michael@0 | 179 | } |
michael@0 | 180 | imageName = [imageName stringByAppendingString:@"-736h"]; |
michael@0 | 181 | |
michael@0 | 182 | } else if (device.iPad) { // supports landscape |
michael@0 | 183 | if (isOrientationLocked) { |
michael@0 | 184 | imageName = [imageName stringByAppendingString:(supportsLandscape ? @"-Landscape" : @"-Portrait")]; |
michael@0 | 185 | } else { |
michael@0 | 186 | switch (currentOrientation) { |
michael@0 | 187 | case UIInterfaceOrientationLandscapeLeft: |
michael@0 | 188 | case UIInterfaceOrientationLandscapeRight: |
michael@0 | 189 | imageName = [imageName stringByAppendingString:@"-Landscape"]; |
michael@0 | 190 | break; |
michael@0 | 191 | |
michael@0 | 192 | case UIInterfaceOrientationPortrait: |
michael@0 | 193 | case UIInterfaceOrientationPortraitUpsideDown: |
michael@0 | 194 | default: |
michael@0 | 195 | imageName = [imageName stringByAppendingString:@"-Portrait"]; |
michael@0 | 196 | break; |
michael@0 | 197 | } |
michael@0 | 198 | } |
michael@0 | 199 | } |
michael@0 | 200 | |
michael@0 | 201 | return imageName; |
michael@0 | 202 | } |
michael@0 | 203 | |
michael@0 | 204 | // Sets the view's frame and image. |
michael@0 | 205 | - (void)updateImage |
michael@0 | 206 | { |
michael@0 | 207 | NSString* imageName = [self getImageName:self.viewController.interfaceOrientation delegate:(id<CDVScreenOrientationDelegate>)self.viewController device:[self getCurrentDevice]]; |
michael@0 | 208 | |
michael@0 | 209 | if (![imageName isEqualToString:_curImageName]) { |
michael@0 | 210 | UIImage* img = [UIImage imageNamed:imageName]; |
michael@0 | 211 | _imageView.image = img; |
michael@0 | 212 | _curImageName = imageName; |
michael@0 | 213 | } |
michael@0 | 214 | |
michael@0 | 215 | // Check that splash screen's image exists before updating bounds |
michael@0 | 216 | if (_imageView.image) { |
michael@0 | 217 | [self updateBounds]; |
michael@0 | 218 | } else { |
michael@0 | 219 | NSLog(@"WARNING: The splashscreen image named %@ was not found", imageName); |
michael@0 | 220 | } |
michael@0 | 221 | } |
michael@0 | 222 | |
michael@0 | 223 | - (void)updateBounds |
michael@0 | 224 | { |
michael@0 | 225 | UIImage* img = _imageView.image; |
michael@0 | 226 | CGRect imgBounds = (img) ? CGRectMake(0, 0, img.size.width, img.size.height) : CGRectZero; |
michael@0 | 227 | |
michael@0 | 228 | CGSize screenSize = [self.viewController.view convertRect:[UIScreen mainScreen].bounds fromView:nil].size; |
michael@0 | 229 | UIInterfaceOrientation orientation = self.viewController.interfaceOrientation; |
michael@0 | 230 | CGAffineTransform imgTransform = CGAffineTransformIdentity; |
michael@0 | 231 | |
michael@0 | 232 | /* If and only if an iPhone application is landscape-only as per |
michael@0 | 233 | * UISupportedInterfaceOrientations, the view controller's orientation is |
michael@0 | 234 | * landscape. In this case the image must be rotated in order to appear |
michael@0 | 235 | * correctly. |
michael@0 | 236 | */ |
michael@0 | 237 | if (UIInterfaceOrientationIsLandscape(orientation) && !CDV_IsIPad()) { |
michael@0 | 238 | imgTransform = CGAffineTransformMakeRotation(M_PI / 2); |
michael@0 | 239 | imgBounds.size = CGSizeMake(imgBounds.size.height, imgBounds.size.width); |
michael@0 | 240 | } |
michael@0 | 241 | |
michael@0 | 242 | // There's a special case when the image is the size of the screen. |
michael@0 | 243 | if (CGSizeEqualToSize(screenSize, imgBounds.size)) { |
michael@0 | 244 | CGRect statusFrame = [self.viewController.view convertRect:[UIApplication sharedApplication].statusBarFrame fromView:nil]; |
michael@0 | 245 | if (!(IsAtLeastiOSVersion(@"7.0"))) { |
michael@0 | 246 | imgBounds.origin.y -= statusFrame.size.height; |
michael@0 | 247 | } |
michael@0 | 248 | } else if (imgBounds.size.width > 0) { |
michael@0 | 249 | CGRect viewBounds = self.viewController.view.bounds; |
michael@0 | 250 | CGFloat imgAspect = imgBounds.size.width / imgBounds.size.height; |
michael@0 | 251 | CGFloat viewAspect = viewBounds.size.width / viewBounds.size.height; |
michael@0 | 252 | // This matches the behaviour of the native splash screen. |
michael@0 | 253 | CGFloat ratio; |
michael@0 | 254 | if (viewAspect > imgAspect) { |
michael@0 | 255 | ratio = viewBounds.size.width / imgBounds.size.width; |
michael@0 | 256 | } else { |
michael@0 | 257 | ratio = viewBounds.size.height / imgBounds.size.height; |
michael@0 | 258 | } |
michael@0 | 259 | imgBounds.size.height *= ratio; |
michael@0 | 260 | imgBounds.size.width *= ratio; |
michael@0 | 261 | } |
michael@0 | 262 | |
michael@0 | 263 | _imageView.transform = imgTransform; |
michael@0 | 264 | _imageView.frame = imgBounds; |
michael@0 | 265 | } |
michael@0 | 266 | |
michael@0 | 267 | - (void)setVisible:(BOOL)visible |
michael@0 | 268 | { |
michael@0 | 269 | if (visible == _visible) { |
michael@0 | 270 | return; |
michael@0 | 271 | } |
michael@0 | 272 | _visible = visible; |
michael@0 | 273 | |
michael@0 | 274 | id fadeSplashScreenValue = [self.commandDelegate.settings objectForKey:[@"FadeSplashScreen" lowercaseString]]; |
michael@0 | 275 | id fadeSplashScreenDuration = [self.commandDelegate.settings objectForKey:[@"FadeSplashScreenDuration" lowercaseString]]; |
michael@0 | 276 | |
michael@0 | 277 | float fadeDuration = fadeSplashScreenDuration == nil ? kSplashScreenDurationDefault : [fadeSplashScreenDuration floatValue]; |
michael@0 | 278 | |
michael@0 | 279 | if ((fadeSplashScreenValue == nil) || ![fadeSplashScreenValue boolValue]) { |
michael@0 | 280 | fadeDuration = 0; |
michael@0 | 281 | } |
michael@0 | 282 | |
michael@0 | 283 | // Never animate the showing of the splash screen. |
michael@0 | 284 | if (visible) { |
michael@0 | 285 | if (_imageView == nil) { |
michael@0 | 286 | [self createViews]; |
michael@0 | 287 | } |
michael@0 | 288 | } else if (fadeDuration == 0) { |
michael@0 | 289 | [self destroyViews]; |
michael@0 | 290 | } else { |
michael@0 | 291 | [UIView transitionWithView:self.viewController.view |
michael@0 | 292 | duration:fadeDuration |
michael@0 | 293 | options:UIViewAnimationOptionTransitionNone |
michael@0 | 294 | animations:^(void) { |
michael@0 | 295 | [_imageView setAlpha:0]; |
michael@0 | 296 | [_activityView setAlpha:0]; |
michael@0 | 297 | } |
michael@0 | 298 | completion:^(BOOL finished) { |
michael@0 | 299 | if (finished) { |
michael@0 | 300 | [self destroyViews]; |
michael@0 | 301 | } |
michael@0 | 302 | } |
michael@0 | 303 | ]; |
michael@0 | 304 | } |
michael@0 | 305 | } |
michael@0 | 306 | |
michael@0 | 307 | @end |