project_files/HedgewarsMobile/Classes/Appirater/Appirater.m
branchios-revival
changeset 11309 402baa6d4f64
equal deleted inserted replaced
11308:92b023095153 11309:402baa6d4f64
       
     1 /*
       
     2  This file is part of Appirater.
       
     3  
       
     4  Copyright (c) 2012, Arash Payan
       
     5  All rights reserved.
       
     6  
       
     7  Permission is hereby granted, free of charge, to any person
       
     8  obtaining a copy of this software and associated documentation
       
     9  files (the "Software"), to deal in the Software without
       
    10  restriction, including without limitation the rights to use,
       
    11  copy, modify, merge, publish, distribute, sublicense, and/or sell
       
    12  copies of the Software, and to permit persons to whom the
       
    13  Software is furnished to do so, subject to the following
       
    14  conditions:
       
    15  
       
    16  The above copyright notice and this permission notice shall be
       
    17  included in all copies or substantial portions of the Software.
       
    18  
       
    19  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
       
    20  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
       
    21  OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
       
    22  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
       
    23  HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
       
    24  WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
       
    25  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
       
    26  OTHER DEALINGS IN THE SOFTWARE.
       
    27  */
       
    28 /*
       
    29  * Appirater.m
       
    30  * appirater
       
    31  *
       
    32  * Created by Arash Payan on 9/5/09.
       
    33  * http://arashpayan.com
       
    34  * Copyright 2012 Arash Payan. All rights reserved.
       
    35  */
       
    36 
       
    37 #import "Appirater.h"
       
    38 #import <SystemConfiguration/SCNetworkReachability.h>
       
    39 #include <netinet/in.h>
       
    40 
       
    41 #if ! __has_feature(objc_arc)
       
    42 #warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC).
       
    43 #endif
       
    44 
       
    45 NSString *const kAppiraterFirstUseDate				= @"kAppiraterFirstUseDate";
       
    46 NSString *const kAppiraterUseCount					= @"kAppiraterUseCount";
       
    47 NSString *const kAppiraterSignificantEventCount		= @"kAppiraterSignificantEventCount";
       
    48 NSString *const kAppiraterCurrentVersion			= @"kAppiraterCurrentVersion";
       
    49 NSString *const kAppiraterRatedCurrentVersion		= @"kAppiraterRatedCurrentVersion";
       
    50 NSString *const kAppiraterDeclinedToRate			= @"kAppiraterDeclinedToRate";
       
    51 NSString *const kAppiraterReminderRequestDate		= @"kAppiraterReminderRequestDate";
       
    52 
       
    53 NSString *templateReviewURL = @"itms-apps://ax.itunes.apple.com/WebObjects/MZStore.woa/wa/viewContentsUserReviews?type=Purple+Software&id=APP_ID";
       
    54 NSString *templateReviewURLiOS7 = @"itms-apps://itunes.apple.com/app/idAPP_ID";
       
    55 NSString *templateReviewURLiOS8 = @"itms-apps://itunes.apple.com/WebObjects/MZStore.woa/wa/viewContentsUserReviews?id=APP_ID&onlyLatestVersion=true&pageNumber=0&sortOrdering=1&type=Purple+Software";
       
    56 
       
    57 static NSString *_appId;
       
    58 static double _daysUntilPrompt = 30;
       
    59 static NSInteger _usesUntilPrompt = 20;
       
    60 static NSInteger _significantEventsUntilPrompt = -1;
       
    61 static double _timeBeforeReminding = 1;
       
    62 static BOOL _debug = NO;
       
    63 #if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_5_0
       
    64 	static id<AppiraterDelegate> _delegate;
       
    65 #else
       
    66 	__weak static id<AppiraterDelegate> _delegate;
       
    67 #endif
       
    68 static BOOL _usesAnimation = TRUE;
       
    69 static UIStatusBarStyle _statusBarStyle;
       
    70 static BOOL _modalOpen = false;
       
    71 static BOOL _alwaysUseMainBundle = NO;
       
    72 
       
    73 @interface Appirater ()
       
    74 @property (nonatomic, copy) NSString *alertTitle;
       
    75 @property (nonatomic, copy) NSString *alertMessage;
       
    76 @property (nonatomic, copy) NSString *alertCancelTitle;
       
    77 @property (nonatomic, copy) NSString *alertRateTitle;
       
    78 @property (nonatomic, copy) NSString *alertRateLaterTitle;
       
    79 - (BOOL)connectedToNetwork;
       
    80 + (Appirater*)sharedInstance;
       
    81 - (void)showPromptWithChecks:(BOOL)withChecks
       
    82       displayRateLaterButton:(BOOL)displayRateLaterButton;
       
    83 - (void)showRatingAlert:(BOOL)displayRateLaterButton;
       
    84 - (void)showRatingAlert;
       
    85 - (BOOL)ratingAlertIsAppropriate;
       
    86 - (BOOL)ratingConditionsHaveBeenMet;
       
    87 - (void)incrementUseCount;
       
    88 - (void)hideRatingAlert;
       
    89 @end
       
    90 
       
    91 @implementation Appirater 
       
    92 
       
    93 @synthesize ratingAlert;
       
    94 
       
    95 + (void) setAppId:(NSString *)appId {
       
    96     _appId = appId;
       
    97 }
       
    98 
       
    99 + (void) setDaysUntilPrompt:(double)value {
       
   100     _daysUntilPrompt = value;
       
   101 }
       
   102 
       
   103 + (void) setUsesUntilPrompt:(NSInteger)value {
       
   104     _usesUntilPrompt = value;
       
   105 }
       
   106 
       
   107 + (void) setSignificantEventsUntilPrompt:(NSInteger)value {
       
   108     _significantEventsUntilPrompt = value;
       
   109 }
       
   110 
       
   111 + (void) setTimeBeforeReminding:(double)value {
       
   112     _timeBeforeReminding = value;
       
   113 }
       
   114 
       
   115 + (void) setCustomAlertTitle:(NSString *)title
       
   116 {
       
   117     [self sharedInstance].alertTitle = title;
       
   118 }
       
   119 
       
   120 + (void) setCustomAlertMessage:(NSString *)message
       
   121 {
       
   122     [self sharedInstance].alertMessage = message;
       
   123 }
       
   124 
       
   125 + (void) setCustomAlertCancelButtonTitle:(NSString *)cancelTitle
       
   126 {
       
   127     [self sharedInstance].alertCancelTitle = cancelTitle;
       
   128 }
       
   129 
       
   130 + (void) setCustomAlertRateButtonTitle:(NSString *)rateTitle
       
   131 {
       
   132     [self sharedInstance].alertRateTitle = rateTitle;
       
   133 }
       
   134 
       
   135 + (void) setCustomAlertRateLaterButtonTitle:(NSString *)rateLaterTitle
       
   136 {
       
   137     [self sharedInstance].alertRateLaterTitle = rateLaterTitle;
       
   138 }
       
   139 
       
   140 + (void) setDebug:(BOOL)debug {
       
   141     _debug = debug;
       
   142 }
       
   143 + (void)setDelegate:(id<AppiraterDelegate>)delegate{
       
   144 	_delegate = delegate;
       
   145 }
       
   146 + (void)setUsesAnimation:(BOOL)animation {
       
   147 	_usesAnimation = animation;
       
   148 }
       
   149 + (void)setOpenInAppStore:(BOOL)openInAppStore {
       
   150     [Appirater sharedInstance].openInAppStore = openInAppStore;
       
   151 }
       
   152 + (void)setStatusBarStyle:(UIStatusBarStyle)style {
       
   153 	_statusBarStyle = style;
       
   154 }
       
   155 + (void)setModalOpen:(BOOL)open {
       
   156 	_modalOpen = open;
       
   157 }
       
   158 + (void)setAlwaysUseMainBundle:(BOOL)alwaysUseMainBundle {
       
   159     _alwaysUseMainBundle = alwaysUseMainBundle;
       
   160 }
       
   161 
       
   162 + (NSBundle *)bundle
       
   163 {
       
   164     NSBundle *bundle;
       
   165 
       
   166     if (_alwaysUseMainBundle) {
       
   167         bundle = [NSBundle mainBundle];
       
   168     } else {
       
   169         NSURL *appiraterBundleURL = [[NSBundle mainBundle] URLForResource:@"Appirater" withExtension:@"bundle"];
       
   170 
       
   171         if (appiraterBundleURL) {
       
   172             // Appirater.bundle will likely only exist when used via CocoaPods
       
   173             bundle = [NSBundle bundleWithURL:appiraterBundleURL];
       
   174         } else {
       
   175             bundle = [NSBundle mainBundle];
       
   176         }
       
   177     }
       
   178 
       
   179     return bundle;
       
   180 }
       
   181 
       
   182 - (NSString *)alertTitle
       
   183 {
       
   184     return _alertTitle ? _alertTitle : APPIRATER_MESSAGE_TITLE;
       
   185 }
       
   186 
       
   187 - (NSString *)alertMessage
       
   188 {
       
   189     return _alertMessage ? _alertMessage : APPIRATER_MESSAGE;
       
   190 }
       
   191 
       
   192 - (NSString *)alertCancelTitle
       
   193 {
       
   194     return _alertCancelTitle ? _alertCancelTitle : APPIRATER_CANCEL_BUTTON;
       
   195 }
       
   196 
       
   197 - (NSString *)alertRateTitle
       
   198 {
       
   199     return _alertRateTitle ? _alertRateTitle : APPIRATER_RATE_BUTTON;
       
   200 }
       
   201 
       
   202 - (NSString *)alertRateLaterTitle
       
   203 {
       
   204     return _alertRateLaterTitle ? _alertRateLaterTitle : APPIRATER_RATE_LATER;
       
   205 }
       
   206 
       
   207 - (void)dealloc {
       
   208     [[NSNotificationCenter defaultCenter] removeObserver:self];
       
   209 }
       
   210 
       
   211 - (id)init {
       
   212     self = [super init];
       
   213     if (self) {
       
   214         if ([[UIDevice currentDevice].systemVersion floatValue] >= 7.0) {
       
   215             self.openInAppStore = YES;
       
   216         } else {
       
   217             self.openInAppStore = NO;
       
   218         }
       
   219     }
       
   220     
       
   221     return self;
       
   222 }
       
   223 
       
   224 - (BOOL)connectedToNetwork {
       
   225     // Create zero addy
       
   226     struct sockaddr_in zeroAddress;
       
   227     bzero(&zeroAddress, sizeof(zeroAddress));
       
   228     zeroAddress.sin_len = sizeof(zeroAddress);
       
   229     zeroAddress.sin_family = AF_INET;
       
   230 	
       
   231     // Recover reachability flags
       
   232     SCNetworkReachabilityRef defaultRouteReachability = SCNetworkReachabilityCreateWithAddress(NULL, (struct sockaddr *)&zeroAddress);
       
   233     SCNetworkReachabilityFlags flags;
       
   234 	
       
   235     Boolean didRetrieveFlags = SCNetworkReachabilityGetFlags(defaultRouteReachability, &flags);
       
   236     CFRelease(defaultRouteReachability);
       
   237 	
       
   238     if (!didRetrieveFlags)
       
   239     {
       
   240         NSLog(@"Error. Could not recover network reachability flags");
       
   241         return NO;
       
   242     }
       
   243 	
       
   244     BOOL isReachable = flags & kSCNetworkFlagsReachable;
       
   245     BOOL needsConnection = flags & kSCNetworkFlagsConnectionRequired;
       
   246 	BOOL nonWiFi = flags & kSCNetworkReachabilityFlagsTransientConnection;
       
   247 	
       
   248 	NSURL *testURL = [NSURL URLWithString:@"http://www.apple.com/"];
       
   249 	NSURLRequest *testRequest = [NSURLRequest requestWithURL:testURL  cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:20.0];
       
   250 	NSURLConnection *testConnection = [[NSURLConnection alloc] initWithRequest:testRequest delegate:self];
       
   251 	
       
   252     return ((isReachable && !needsConnection) || nonWiFi) ? (testConnection ? YES : NO) : NO;
       
   253 }
       
   254 
       
   255 + (Appirater*)sharedInstance {
       
   256 	static Appirater *appirater = nil;
       
   257 	if (appirater == nil)
       
   258 	{
       
   259         static dispatch_once_t onceToken;
       
   260         dispatch_once(&onceToken, ^{
       
   261             appirater = [[Appirater alloc] init];
       
   262 			appirater.delegate = _delegate;
       
   263             [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(appWillResignActive) name:
       
   264                 UIApplicationWillResignActiveNotification object:nil];
       
   265         });
       
   266 	}
       
   267 	
       
   268 	return appirater;
       
   269 }
       
   270 
       
   271 - (void)showRatingAlert:(BOOL)displayRateLaterButton {
       
   272   UIAlertView *alertView = nil;
       
   273   id <AppiraterDelegate> delegate = _delegate;
       
   274     
       
   275   if(delegate && [delegate respondsToSelector:@selector(appiraterShouldDisplayAlert:)] && ![delegate appiraterShouldDisplayAlert:self]) {
       
   276       return;
       
   277   }
       
   278   
       
   279   if (displayRateLaterButton) {
       
   280   	alertView = [[UIAlertView alloc] initWithTitle:self.alertTitle
       
   281                                            message:self.alertMessage
       
   282                                           delegate:self
       
   283                                  cancelButtonTitle:self.alertCancelTitle
       
   284                                  otherButtonTitles:self.alertRateTitle, self.alertRateLaterTitle, nil];
       
   285   } else {
       
   286   	alertView = [[UIAlertView alloc] initWithTitle:self.alertTitle
       
   287                                            message:self.alertMessage
       
   288                                           delegate:self
       
   289                                  cancelButtonTitle:self.alertCancelTitle
       
   290                                  otherButtonTitles:self.alertRateTitle, nil];
       
   291   }
       
   292 
       
   293 	self.ratingAlert = alertView;
       
   294     [alertView show];
       
   295 
       
   296     if (delegate && [delegate respondsToSelector:@selector(appiraterDidDisplayAlert:)]) {
       
   297              [delegate appiraterDidDisplayAlert:self];
       
   298     }
       
   299 }
       
   300 
       
   301 - (void)showRatingAlert
       
   302 {
       
   303   [self showRatingAlert:true];
       
   304 }
       
   305 
       
   306 // is this an ok time to show the alert? (regardless of whether the rating conditions have been met)
       
   307 //
       
   308 // things checked here:
       
   309 // * connectivity with network
       
   310 // * whether user has rated before
       
   311 // * whether user has declined to rate
       
   312 // * whether rating alert is currently showing visibly
       
   313 // things NOT checked here:
       
   314 // * time since first launch
       
   315 // * number of uses of app
       
   316 // * number of significant events
       
   317 // * time since last reminder
       
   318 - (BOOL)ratingAlertIsAppropriate {
       
   319     return ([self connectedToNetwork]
       
   320             && ![self userHasDeclinedToRate]
       
   321             && !self.ratingAlert.visible
       
   322             && ![self userHasRatedCurrentVersion]);
       
   323 }
       
   324 
       
   325 // have the rating conditions been met/earned? (regardless of whether this would be a moment when it's appropriate to show a new rating alert)
       
   326 //
       
   327 // things checked here:
       
   328 // * time since first launch
       
   329 // * number of uses of app
       
   330 // * number of significant events
       
   331 // * time since last reminder
       
   332 // things NOT checked here:
       
   333 // * connectivity with network
       
   334 // * whether user has rated before
       
   335 // * whether user has declined to rate
       
   336 // * whether rating alert is currently showing visibly
       
   337 - (BOOL)ratingConditionsHaveBeenMet {
       
   338 	if (_debug)
       
   339 		return YES;
       
   340 	
       
   341 	NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
       
   342 	
       
   343 	NSDate *dateOfFirstLaunch = [NSDate dateWithTimeIntervalSince1970:[userDefaults doubleForKey:kAppiraterFirstUseDate]];
       
   344 	NSTimeInterval timeSinceFirstLaunch = [[NSDate date] timeIntervalSinceDate:dateOfFirstLaunch];
       
   345 	NSTimeInterval timeUntilRate = 60 * 60 * 24 * _daysUntilPrompt;
       
   346 	if (timeSinceFirstLaunch < timeUntilRate)
       
   347 		return NO;
       
   348 	
       
   349 	// check if the app has been used enough
       
   350 	NSInteger useCount = [userDefaults integerForKey:kAppiraterUseCount];
       
   351 	if (useCount < _usesUntilPrompt)
       
   352 		return NO;
       
   353 	
       
   354 	// check if the user has done enough significant events
       
   355 	NSInteger sigEventCount = [userDefaults integerForKey:kAppiraterSignificantEventCount];
       
   356 	if (sigEventCount < _significantEventsUntilPrompt)
       
   357 		return NO;
       
   358 	
       
   359 	// if the user wanted to be reminded later, has enough time passed?
       
   360 	NSDate *reminderRequestDate = [NSDate dateWithTimeIntervalSince1970:[userDefaults doubleForKey:kAppiraterReminderRequestDate]];
       
   361 	NSTimeInterval timeSinceReminderRequest = [[NSDate date] timeIntervalSinceDate:reminderRequestDate];
       
   362 	NSTimeInterval timeUntilReminder = 60 * 60 * 24 * _timeBeforeReminding;
       
   363 	if (timeSinceReminderRequest < timeUntilReminder)
       
   364 		return NO;
       
   365 	
       
   366 	return YES;
       
   367 }
       
   368 
       
   369 - (void)incrementUseCount {
       
   370 	// get the app's version
       
   371 	NSString *version = [[[NSBundle mainBundle] infoDictionary] objectForKey:(NSString*)kCFBundleVersionKey];
       
   372 	
       
   373 	// get the version number that we've been tracking
       
   374 	NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
       
   375 	NSString *trackingVersion = [userDefaults stringForKey:kAppiraterCurrentVersion];
       
   376 	if (trackingVersion == nil)
       
   377 	{
       
   378 		trackingVersion = version;
       
   379 		[userDefaults setObject:version forKey:kAppiraterCurrentVersion];
       
   380 	}
       
   381 	
       
   382 	if (_debug)
       
   383 		NSLog(@"APPIRATER Tracking version: %@", trackingVersion);
       
   384 	
       
   385 	if ([trackingVersion isEqualToString:version])
       
   386 	{
       
   387 		// check if the first use date has been set. if not, set it.
       
   388 		NSTimeInterval timeInterval = [userDefaults doubleForKey:kAppiraterFirstUseDate];
       
   389 		if (timeInterval == 0)
       
   390 		{
       
   391 			timeInterval = [[NSDate date] timeIntervalSince1970];
       
   392 			[userDefaults setDouble:timeInterval forKey:kAppiraterFirstUseDate];
       
   393 		}
       
   394 		
       
   395 		// increment the use count
       
   396 		NSInteger useCount = [userDefaults integerForKey:kAppiraterUseCount];
       
   397 		useCount++;
       
   398 		[userDefaults setInteger:useCount forKey:kAppiraterUseCount];
       
   399 		if (_debug)
       
   400 			NSLog(@"APPIRATER Use count: %@", @(useCount));
       
   401 	}
       
   402 	else
       
   403 	{
       
   404 		// it's a new version of the app, so restart tracking
       
   405 		[userDefaults setObject:version forKey:kAppiraterCurrentVersion];
       
   406 		[userDefaults setDouble:[[NSDate date] timeIntervalSince1970] forKey:kAppiraterFirstUseDate];
       
   407 		[userDefaults setInteger:1 forKey:kAppiraterUseCount];
       
   408 		[userDefaults setInteger:0 forKey:kAppiraterSignificantEventCount];
       
   409 		[userDefaults setBool:NO forKey:kAppiraterRatedCurrentVersion];
       
   410 		[userDefaults setBool:NO forKey:kAppiraterDeclinedToRate];
       
   411 		[userDefaults setDouble:0 forKey:kAppiraterReminderRequestDate];
       
   412 	}
       
   413 	
       
   414 	[userDefaults synchronize];
       
   415 }
       
   416 
       
   417 - (void)incrementSignificantEventCount {
       
   418 	// get the app's version
       
   419 	NSString *version = [[[NSBundle mainBundle] infoDictionary] objectForKey:(NSString*)kCFBundleVersionKey];
       
   420 	
       
   421 	// get the version number that we've been tracking
       
   422 	NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
       
   423 	NSString *trackingVersion = [userDefaults stringForKey:kAppiraterCurrentVersion];
       
   424 	if (trackingVersion == nil)
       
   425 	{
       
   426 		trackingVersion = version;
       
   427 		[userDefaults setObject:version forKey:kAppiraterCurrentVersion];
       
   428 	}
       
   429 	
       
   430 	if (_debug)
       
   431 		NSLog(@"APPIRATER Tracking version: %@", trackingVersion);
       
   432 	
       
   433 	if ([trackingVersion isEqualToString:version])
       
   434 	{
       
   435 		// check if the first use date has been set. if not, set it.
       
   436 		NSTimeInterval timeInterval = [userDefaults doubleForKey:kAppiraterFirstUseDate];
       
   437 		if (timeInterval == 0)
       
   438 		{
       
   439 			timeInterval = [[NSDate date] timeIntervalSince1970];
       
   440 			[userDefaults setDouble:timeInterval forKey:kAppiraterFirstUseDate];
       
   441 		}
       
   442 		
       
   443 		// increment the significant event count
       
   444 		NSInteger sigEventCount = [userDefaults integerForKey:kAppiraterSignificantEventCount];
       
   445 		sigEventCount++;
       
   446 		[userDefaults setInteger:sigEventCount forKey:kAppiraterSignificantEventCount];
       
   447 		if (_debug)
       
   448 			NSLog(@"APPIRATER Significant event count: %@", @(sigEventCount));
       
   449 	}
       
   450 	else
       
   451 	{
       
   452 		// it's a new version of the app, so restart tracking
       
   453 		[userDefaults setObject:version forKey:kAppiraterCurrentVersion];
       
   454 		[userDefaults setDouble:0 forKey:kAppiraterFirstUseDate];
       
   455 		[userDefaults setInteger:0 forKey:kAppiraterUseCount];
       
   456 		[userDefaults setInteger:1 forKey:kAppiraterSignificantEventCount];
       
   457 		[userDefaults setBool:NO forKey:kAppiraterRatedCurrentVersion];
       
   458 		[userDefaults setBool:NO forKey:kAppiraterDeclinedToRate];
       
   459 		[userDefaults setDouble:0 forKey:kAppiraterReminderRequestDate];
       
   460 	}
       
   461 	
       
   462 	[userDefaults synchronize];
       
   463 }
       
   464 
       
   465 - (void)incrementAndRate:(BOOL)canPromptForRating {
       
   466 	[self incrementUseCount];
       
   467 	
       
   468 	if (canPromptForRating &&
       
   469         [self ratingConditionsHaveBeenMet] &&
       
   470         [self ratingAlertIsAppropriate])
       
   471 	{
       
   472         dispatch_async(dispatch_get_main_queue(),
       
   473                        ^{
       
   474                            [self showRatingAlert];
       
   475                        });
       
   476 	}
       
   477 }
       
   478 
       
   479 - (void)incrementSignificantEventAndRate:(BOOL)canPromptForRating {
       
   480 	[self incrementSignificantEventCount];
       
   481 	
       
   482     if (canPromptForRating &&
       
   483         [self ratingConditionsHaveBeenMet] &&
       
   484         [self ratingAlertIsAppropriate])
       
   485 	{
       
   486         dispatch_async(dispatch_get_main_queue(),
       
   487                        ^{
       
   488                            [self showRatingAlert];
       
   489                        });
       
   490 	}
       
   491 }
       
   492 
       
   493 - (BOOL)userHasDeclinedToRate {
       
   494     return [[NSUserDefaults standardUserDefaults] boolForKey:kAppiraterDeclinedToRate];
       
   495 }
       
   496 
       
   497 - (BOOL)userHasRatedCurrentVersion {
       
   498     return [[NSUserDefaults standardUserDefaults] boolForKey:kAppiraterRatedCurrentVersion];
       
   499 }
       
   500 
       
   501 #pragma GCC diagnostic push
       
   502 #pragma GCC diagnostic ignored "-Wdeprecated-implementations"
       
   503 + (void)appLaunched {
       
   504 	[Appirater appLaunched:YES];
       
   505 }
       
   506 #pragma GCC diagnostic pop
       
   507 
       
   508 + (void)appLaunched:(BOOL)canPromptForRating {
       
   509     dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0),
       
   510                    ^{
       
   511                        Appirater *a = [Appirater sharedInstance];
       
   512                        if (_debug) {
       
   513                            dispatch_async(dispatch_get_main_queue(),
       
   514                                           ^{
       
   515                                               [a showRatingAlert];
       
   516                                           });
       
   517                        } else {
       
   518                            [a incrementAndRate:canPromptForRating]; 
       
   519                        }
       
   520                    });
       
   521 }
       
   522 
       
   523 - (void)hideRatingAlert {
       
   524 	if (self.ratingAlert.visible) {
       
   525 		if (_debug)
       
   526 			NSLog(@"APPIRATER Hiding Alert");
       
   527 		[self.ratingAlert dismissWithClickedButtonIndex:-1 animated:NO];
       
   528 	}	
       
   529 }
       
   530 
       
   531 + (void)appWillResignActive {
       
   532 	if (_debug)
       
   533 		NSLog(@"APPIRATER appWillResignActive");
       
   534 	[[Appirater sharedInstance] hideRatingAlert];
       
   535 }
       
   536 
       
   537 + (void)appEnteredForeground:(BOOL)canPromptForRating {
       
   538     dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0),
       
   539                    ^{
       
   540                        [[Appirater sharedInstance] incrementAndRate:canPromptForRating];
       
   541                    });
       
   542 }
       
   543 
       
   544 + (void)userDidSignificantEvent:(BOOL)canPromptForRating {
       
   545     dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0),
       
   546                    ^{
       
   547                        [[Appirater sharedInstance] incrementSignificantEventAndRate:canPromptForRating];
       
   548                    });
       
   549 }
       
   550 
       
   551 #pragma GCC diagnostic push
       
   552 #pragma GCC diagnostic ignored "-Wdeprecated-implementations"
       
   553 + (void)showPrompt {
       
   554   [Appirater tryToShowPrompt];
       
   555 }
       
   556 #pragma GCC diagnostic pop
       
   557 
       
   558 + (void)tryToShowPrompt {
       
   559   [[Appirater sharedInstance] showPromptWithChecks:true
       
   560                             displayRateLaterButton:true];
       
   561 }
       
   562 
       
   563 + (void)forceShowPrompt:(BOOL)displayRateLaterButton {
       
   564   [[Appirater sharedInstance] showPromptWithChecks:false
       
   565                             displayRateLaterButton:displayRateLaterButton];
       
   566 }
       
   567 
       
   568 - (void)showPromptWithChecks:(BOOL)withChecks
       
   569       displayRateLaterButton:(BOOL)displayRateLaterButton {
       
   570   if (withChecks == NO || [self ratingAlertIsAppropriate]) {
       
   571     [self showRatingAlert:displayRateLaterButton];
       
   572   }
       
   573 }
       
   574 
       
   575 + (id)getRootViewController {
       
   576     UIWindow *window = [[UIApplication sharedApplication] keyWindow];
       
   577     if (window.windowLevel != UIWindowLevelNormal) {
       
   578         NSArray *windows = [[UIApplication sharedApplication] windows];
       
   579         for(window in windows) {
       
   580             if (window.windowLevel == UIWindowLevelNormal) {
       
   581                 break;
       
   582             }
       
   583         }
       
   584     }
       
   585     
       
   586     return [Appirater iterateSubViewsForViewController:window]; // iOS 8+ deep traverse
       
   587 }
       
   588 
       
   589 + (id)iterateSubViewsForViewController:(UIView *) parentView {
       
   590     for (UIView *subView in [parentView subviews]) {
       
   591         UIResponder *responder = [subView nextResponder];
       
   592         if([responder isKindOfClass:[UIViewController class]]) {
       
   593             return [self topMostViewController: (UIViewController *) responder];
       
   594         }
       
   595         id found = [Appirater iterateSubViewsForViewController:subView];
       
   596         if( nil != found) {
       
   597             return found;
       
   598         }
       
   599     }
       
   600     return nil;
       
   601 }
       
   602 
       
   603 + (UIViewController *) topMostViewController: (UIViewController *) controller {
       
   604 	BOOL isPresenting = NO;
       
   605 	do {
       
   606 		// this path is called only on iOS 6+, so -presentedViewController is fine here.
       
   607 		UIViewController *presented = [controller presentedViewController];
       
   608 		isPresenting = presented != nil;
       
   609 		if(presented != nil) {
       
   610 			controller = presented;
       
   611 		}
       
   612 		
       
   613 	} while (isPresenting);
       
   614 	
       
   615 	return controller;
       
   616 }
       
   617 
       
   618 + (void)rateApp {
       
   619 	
       
   620 	NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
       
   621 	[userDefaults setBool:YES forKey:kAppiraterRatedCurrentVersion];
       
   622 	[userDefaults synchronize];
       
   623 
       
   624 	//Use the in-app StoreKit view if available (iOS 6) and imported. This works in the simulator.
       
   625 	if (![Appirater sharedInstance].openInAppStore && NSStringFromClass([SKStoreProductViewController class]) != nil) {
       
   626 		
       
   627 		SKStoreProductViewController *storeViewController = [[SKStoreProductViewController alloc] init];
       
   628 		NSNumber *appId = [NSNumber numberWithInteger:_appId.integerValue];
       
   629 		[storeViewController loadProductWithParameters:@{SKStoreProductParameterITunesItemIdentifier:appId} completionBlock:nil];
       
   630 		storeViewController.delegate = self.sharedInstance;
       
   631         
       
   632         id <AppiraterDelegate> delegate = self.sharedInstance.delegate;
       
   633 		if ([delegate respondsToSelector:@selector(appiraterWillPresentModalView:animated:)]) {
       
   634 			[delegate appiraterWillPresentModalView:self.sharedInstance animated:_usesAnimation];
       
   635 		}
       
   636 		[[self getRootViewController] presentViewController:storeViewController animated:_usesAnimation completion:^{
       
   637 			[self setModalOpen:YES];
       
   638 			//Temporarily use a black status bar to match the StoreKit view.
       
   639 			[self setStatusBarStyle:[UIApplication sharedApplication].statusBarStyle];
       
   640 #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 70000
       
   641 			[[UIApplication sharedApplication]setStatusBarStyle:UIStatusBarStyleLightContent animated:_usesAnimation];
       
   642 #endif
       
   643 		}];
       
   644 	
       
   645 	//Use the standard openUrl method if StoreKit is unavailable.
       
   646 	} else {
       
   647 		
       
   648 		#if TARGET_IPHONE_SIMULATOR
       
   649 		NSLog(@"APPIRATER NOTE: iTunes App Store is not supported on the iOS simulator. Unable to open App Store page.");
       
   650 		#else
       
   651 		NSString *reviewURL = [templateReviewURL stringByReplacingOccurrencesOfString:@"APP_ID" withString:[NSString stringWithFormat:@"%@", _appId]];
       
   652 
       
   653 		// iOS 7 needs a different templateReviewURL @see https://github.com/arashpayan/appirater/issues/131
       
   654         // Fixes condition @see https://github.com/arashpayan/appirater/issues/205
       
   655 		if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 7.0 && [[[UIDevice currentDevice] systemVersion] floatValue] < 8.0) {
       
   656 			reviewURL = [templateReviewURLiOS7 stringByReplacingOccurrencesOfString:@"APP_ID" withString:[NSString stringWithFormat:@"%@", _appId]];
       
   657 		}
       
   658         // iOS 8 needs a different templateReviewURL also @see https://github.com/arashpayan/appirater/issues/182
       
   659         else if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 8.0)
       
   660         {
       
   661             reviewURL = [templateReviewURLiOS8 stringByReplacingOccurrencesOfString:@"APP_ID" withString:[NSString stringWithFormat:@"%@", _appId]];
       
   662         }
       
   663 
       
   664 		[[UIApplication sharedApplication] openURL:[NSURL URLWithString:reviewURL]];
       
   665 		#endif
       
   666 	}
       
   667 }
       
   668 
       
   669 - (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex {
       
   670 	NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
       
   671     
       
   672     id <AppiraterDelegate> delegate = _delegate;
       
   673 	
       
   674 	switch (buttonIndex) {
       
   675 		case 0:
       
   676 		{
       
   677 			// they don't want to rate it
       
   678 			[userDefaults setBool:YES forKey:kAppiraterDeclinedToRate];
       
   679 			[userDefaults synchronize];
       
   680 			if(delegate && [delegate respondsToSelector:@selector(appiraterDidDeclineToRate:)]){
       
   681 				[delegate appiraterDidDeclineToRate:self];
       
   682 			}
       
   683 			break;
       
   684 		}
       
   685 		case 1:
       
   686 		{
       
   687 			// they want to rate it
       
   688 			[Appirater rateApp];
       
   689 			if(delegate&& [delegate respondsToSelector:@selector(appiraterDidOptToRate:)]){
       
   690 				[delegate appiraterDidOptToRate:self];
       
   691 			}
       
   692 			break;
       
   693 		}
       
   694 		case 2:
       
   695 			// remind them later
       
   696 			[userDefaults setDouble:[[NSDate date] timeIntervalSince1970] forKey:kAppiraterReminderRequestDate];
       
   697 			[userDefaults synchronize];
       
   698 			if(delegate && [delegate respondsToSelector:@selector(appiraterDidOptToRemindLater:)]){
       
   699 				[delegate appiraterDidOptToRemindLater:self];
       
   700 			}
       
   701 			break;
       
   702 		default:
       
   703 			break;
       
   704 	}
       
   705 }
       
   706 
       
   707 //Delegate call from the StoreKit view.
       
   708 - (void)productViewControllerDidFinish:(SKStoreProductViewController *)viewController {
       
   709 	[Appirater closeModal];
       
   710 }
       
   711 
       
   712 //Close the in-app rating (StoreKit) view and restore the previous status bar style.
       
   713 + (void)closeModal {
       
   714 	if (_modalOpen) {
       
   715 		[[UIApplication sharedApplication]setStatusBarStyle:_statusBarStyle animated:_usesAnimation];
       
   716 		BOOL usedAnimation = _usesAnimation;
       
   717 		[self setModalOpen:NO];
       
   718 		
       
   719 		// get the top most controller (= the StoreKit Controller) and dismiss it
       
   720 		UIViewController *presentingController = [UIApplication sharedApplication].keyWindow.rootViewController;
       
   721 		presentingController = [self topMostViewController: presentingController];
       
   722 		[presentingController dismissViewControllerAnimated:_usesAnimation completion:^{
       
   723             id <AppiraterDelegate> delegate = self.sharedInstance.delegate;
       
   724 			if ([delegate respondsToSelector:@selector(appiraterDidDismissModalView:animated:)]) {
       
   725 				[delegate appiraterDidDismissModalView:(Appirater *)self animated:usedAnimation];
       
   726 			}
       
   727 		}];
       
   728 		[self.class setStatusBarStyle:(UIStatusBarStyle)nil];
       
   729 	}
       
   730 }
       
   731 
       
   732 @end