Browse Source

merged iphoen changes

Ariel Manzur 9 years ago
parent
commit
b50c00ff90

+ 451 - 102
platform/iphone/Appirater.m

@@ -1,9 +1,7 @@
-#ifdef APPIRATER_ENABLED
-
 /*
  This file is part of Appirater.
  
- Copyright (c) 2010, Arash Payan
+ Copyright (c) 2012, Arash Payan
  All rights reserved.
  
  Permission is hereby granted, free of charge, to any person
@@ -33,13 +31,17 @@
  *
  * Created by Arash Payan on 9/5/09.
  * http://arashpayan.com
- * Copyright 2010 Arash Payan. All rights reserved.
+ * Copyright 2012 Arash Payan. All rights reserved.
  */
 
 #import "Appirater.h"
 #import <SystemConfiguration/SCNetworkReachability.h>
 #include <netinet/in.h>
 
+#if ! __has_feature(objc_arc)
+#warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC).
+#endif
+
 NSString *const kAppiraterFirstUseDate				= @"kAppiraterFirstUseDate";
 NSString *const kAppiraterUseCount					= @"kAppiraterUseCount";
 NSString *const kAppiraterSignificantEventCount		= @"kAppiraterSignificantEventCount";
@@ -49,18 +51,175 @@ NSString *const kAppiraterDeclinedToRate			= @"kAppiraterDeclinedToRate";
 NSString *const kAppiraterReminderRequestDate		= @"kAppiraterReminderRequestDate";
 
 NSString *templateReviewURL = @"itms-apps://ax.itunes.apple.com/WebObjects/MZStore.woa/wa/viewContentsUserReviews?type=Purple+Software&id=APP_ID";
+NSString *templateReviewURLiOS7 = @"itms-apps://itunes.apple.com/app/idAPP_ID";
+NSString *templateReviewURLiOS8 = @"itms-apps://itunes.apple.com/WebObjects/MZStore.woa/wa/viewContentsUserReviews?id=APP_ID&onlyLatestVersion=true&pageNumber=0&sortOrdering=1&type=Purple+Software";
 
-static int app_id = 0;
+static NSString *_appId;
+static double _daysUntilPrompt = 30;
+static NSInteger _usesUntilPrompt = 20;
+static NSInteger _significantEventsUntilPrompt = -1;
+static double _timeBeforeReminding = 1;
+static BOOL _debug = NO;
+#if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_5_0
+	static id<AppiraterDelegate> _delegate;
+#else
+	__weak static id<AppiraterDelegate> _delegate;
+#endif
+static BOOL _usesAnimation = TRUE;
+static UIStatusBarStyle _statusBarStyle;
+static BOOL _modalOpen = false;
+static BOOL _alwaysUseMainBundle = NO;
 
-@interface Appirater (hidden)
+@interface Appirater ()
+@property (nonatomic, copy) NSString *alertTitle;
+@property (nonatomic, copy) NSString *alertMessage;
+@property (nonatomic, copy) NSString *alertCancelTitle;
+@property (nonatomic, copy) NSString *alertRateTitle;
+@property (nonatomic, copy) NSString *alertRateLaterTitle;
 - (BOOL)connectedToNetwork;
 + (Appirater*)sharedInstance;
+- (void)showPromptWithChecks:(BOOL)withChecks
+      displayRateLaterButton:(BOOL)displayRateLaterButton;
+- (void)showRatingAlert:(BOOL)displayRateLaterButton;
 - (void)showRatingAlert;
+- (BOOL)ratingAlertIsAppropriate;
 - (BOOL)ratingConditionsHaveBeenMet;
 - (void)incrementUseCount;
+- (void)hideRatingAlert;
 @end
 
-@implementation Appirater (hidden)
+@implementation Appirater 
+
+@synthesize ratingAlert;
+
++ (void) setAppId:(NSString *)appId {
+    _appId = appId;
+}
+
++ (void) setDaysUntilPrompt:(double)value {
+    _daysUntilPrompt = value;
+}
+
++ (void) setUsesUntilPrompt:(NSInteger)value {
+    _usesUntilPrompt = value;
+}
+
++ (void) setSignificantEventsUntilPrompt:(NSInteger)value {
+    _significantEventsUntilPrompt = value;
+}
+
++ (void) setTimeBeforeReminding:(double)value {
+    _timeBeforeReminding = value;
+}
+
++ (void) setCustomAlertTitle:(NSString *)title
+{
+    [self sharedInstance].alertTitle = title;
+}
+
++ (void) setCustomAlertMessage:(NSString *)message
+{
+    [self sharedInstance].alertMessage = message;
+}
+
++ (void) setCustomAlertCancelButtonTitle:(NSString *)cancelTitle
+{
+    [self sharedInstance].alertCancelTitle = cancelTitle;
+}
+
++ (void) setCustomAlertRateButtonTitle:(NSString *)rateTitle
+{
+    [self sharedInstance].alertRateTitle = rateTitle;
+}
+
++ (void) setCustomAlertRateLaterButtonTitle:(NSString *)rateLaterTitle
+{
+    [self sharedInstance].alertRateLaterTitle = rateLaterTitle;
+}
+
++ (void) setDebug:(BOOL)debug {
+    _debug = debug;
+}
++ (void)setDelegate:(id<AppiraterDelegate>)delegate{
+	_delegate = delegate;
+}
++ (void)setUsesAnimation:(BOOL)animation {
+	_usesAnimation = animation;
+}
++ (void)setOpenInAppStore:(BOOL)openInAppStore {
+    [Appirater sharedInstance].openInAppStore = openInAppStore;
+}
++ (void)setStatusBarStyle:(UIStatusBarStyle)style {
+	_statusBarStyle = style;
+}
++ (void)setModalOpen:(BOOL)open {
+	_modalOpen = open;
+}
++ (void)setAlwaysUseMainBundle:(BOOL)alwaysUseMainBundle {
+    _alwaysUseMainBundle = alwaysUseMainBundle;
+}
+
++ (NSBundle *)bundle
+{
+    NSBundle *bundle;
+
+    if (_alwaysUseMainBundle) {
+        bundle = [NSBundle mainBundle];
+    } else {
+        NSURL *appiraterBundleURL = [[NSBundle mainBundle] URLForResource:@"Appirater" withExtension:@"bundle"];
+
+        if (appiraterBundleURL) {
+            // Appirater.bundle will likely only exist when used via CocoaPods
+            bundle = [NSBundle bundleWithURL:appiraterBundleURL];
+        } else {
+            bundle = [NSBundle mainBundle];
+        }
+    }
+
+    return bundle;
+}
+
+- (NSString *)alertTitle
+{
+    return _alertTitle ? _alertTitle : APPIRATER_MESSAGE_TITLE;
+}
+
+- (NSString *)alertMessage
+{
+    return _alertMessage ? _alertMessage : APPIRATER_MESSAGE;
+}
+
+- (NSString *)alertCancelTitle
+{
+    return _alertCancelTitle ? _alertCancelTitle : APPIRATER_CANCEL_BUTTON;
+}
+
+- (NSString *)alertRateTitle
+{
+    return _alertRateTitle ? _alertRateTitle : APPIRATER_RATE_BUTTON;
+}
+
+- (NSString *)alertRateLaterTitle
+{
+    return _alertRateLaterTitle ? _alertRateLaterTitle : APPIRATER_RATE_LATER;
+}
+
+- (void)dealloc {
+    [[NSNotificationCenter defaultCenter] removeObserver:self];
+}
+
+- (id)init {
+    self = [super init];
+    if (self) {
+        if ([[UIDevice currentDevice].systemVersion floatValue] >= 7.0) {
+            self.openInAppStore = YES;
+        } else {
+            self.openInAppStore = NO;
+        }
+    }
+    
+    return self;
+}
 
 - (BOOL)connectedToNetwork {
     // Create zero addy
@@ -73,7 +232,7 @@ static int app_id = 0;
     SCNetworkReachabilityRef defaultRouteReachability = SCNetworkReachabilityCreateWithAddress(NULL, (struct sockaddr *)&zeroAddress);
     SCNetworkReachabilityFlags flags;
 	
-    BOOL didRetrieveFlags = SCNetworkReachabilityGetFlags(defaultRouteReachability, &flags);
+    Boolean didRetrieveFlags = SCNetworkReachabilityGetFlags(defaultRouteReachability, &flags);
     CFRelease(defaultRouteReachability);
 	
     if (!didRetrieveFlags)
@@ -97,61 +256,110 @@ static int app_id = 0;
 	static Appirater *appirater = nil;
 	if (appirater == nil)
 	{
-		@synchronized(self) {
-			if (appirater == nil) {
-				appirater = [[Appirater alloc] init];
-                [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(appWillResignActive) name:@"UIApplicationWillResignActiveNotification" object:nil];
-            }
-        }
+        static dispatch_once_t onceToken;
+        dispatch_once(&onceToken, ^{
+            appirater = [[Appirater alloc] init];
+			appirater.delegate = _delegate;
+            [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(appWillResignActive) name:
+                UIApplicationWillResignActiveNotification object:nil];
+        });
 	}
 	
 	return appirater;
 }
 
-- (void)showRatingAlert {
-	UIAlertView *alertView = [[[UIAlertView alloc] initWithTitle:APPIRATER_MESSAGE_TITLE
-														 message:APPIRATER_MESSAGE
-														delegate:self
-											   cancelButtonTitle:APPIRATER_CANCEL_BUTTON
-											   otherButtonTitles:APPIRATER_RATE_BUTTON, APPIRATER_RATE_LATER, nil] autorelease];
+- (void)showRatingAlert:(BOOL)displayRateLaterButton {
+  UIAlertView *alertView = nil;
+  id <AppiraterDelegate> delegate = _delegate;
+    
+  if(delegate && [delegate respondsToSelector:@selector(appiraterShouldDisplayAlert:)] && ![delegate appiraterShouldDisplayAlert:self]) {
+      return;
+  }
+  
+  if (displayRateLaterButton) {
+  	alertView = [[UIAlertView alloc] initWithTitle:self.alertTitle
+                                           message:self.alertMessage
+                                          delegate:self
+                                 cancelButtonTitle:self.alertCancelTitle
+                                 otherButtonTitles:self.alertRateTitle, self.alertRateLaterTitle, nil];
+  } else {
+  	alertView = [[UIAlertView alloc] initWithTitle:self.alertTitle
+                                           message:self.alertMessage
+                                          delegate:self
+                                 cancelButtonTitle:self.alertCancelTitle
+                                 otherButtonTitles:self.alertRateTitle, nil];
+  }
+
 	self.ratingAlert = alertView;
-	[alertView show];
+    [alertView show];
+
+    if (delegate && [delegate respondsToSelector:@selector(appiraterDidDisplayAlert:)]) {
+             [delegate appiraterDidDisplayAlert:self];
+    }
+}
+
+- (void)showRatingAlert
+{
+  [self showRatingAlert:true];
 }
 
+// is this an ok time to show the alert? (regardless of whether the rating conditions have been met)
+//
+// things checked here:
+// * connectivity with network
+// * whether user has rated before
+// * whether user has declined to rate
+// * whether rating alert is currently showing visibly
+// things NOT checked here:
+// * time since first launch
+// * number of uses of app
+// * number of significant events
+// * time since last reminder
+- (BOOL)ratingAlertIsAppropriate {
+    return ([self connectedToNetwork]
+            && ![self userHasDeclinedToRate]
+            && !self.ratingAlert.visible
+            && ![self userHasRatedCurrentVersion]);
+}
+
+// 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)
+//
+// things checked here:
+// * time since first launch
+// * number of uses of app
+// * number of significant events
+// * time since last reminder
+// things NOT checked here:
+// * connectivity with network
+// * whether user has rated before
+// * whether user has declined to rate
+// * whether rating alert is currently showing visibly
 - (BOOL)ratingConditionsHaveBeenMet {
-	if (APPIRATER_DEBUG)
+	if (_debug)
 		return YES;
 	
 	NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
 	
 	NSDate *dateOfFirstLaunch = [NSDate dateWithTimeIntervalSince1970:[userDefaults doubleForKey:kAppiraterFirstUseDate]];
 	NSTimeInterval timeSinceFirstLaunch = [[NSDate date] timeIntervalSinceDate:dateOfFirstLaunch];
-	NSTimeInterval timeUntilRate = 60 * 60 * 24 * APPIRATER_DAYS_UNTIL_PROMPT;
+	NSTimeInterval timeUntilRate = 60 * 60 * 24 * _daysUntilPrompt;
 	if (timeSinceFirstLaunch < timeUntilRate)
 		return NO;
 	
 	// check if the app has been used enough
-	int useCount = [userDefaults integerForKey:kAppiraterUseCount];
-	if (useCount <= APPIRATER_USES_UNTIL_PROMPT)
+	NSInteger useCount = [userDefaults integerForKey:kAppiraterUseCount];
+	if (useCount < _usesUntilPrompt)
 		return NO;
 	
 	// check if the user has done enough significant events
-	int sigEventCount = [userDefaults integerForKey:kAppiraterSignificantEventCount];
-	if (sigEventCount <= APPIRATER_SIG_EVENTS_UNTIL_PROMPT)
-		return NO;
-	
-	// has the user previously declined to rate this version of the app?
-	if ([userDefaults boolForKey:kAppiraterDeclinedToRate])
-		return NO;
-	
-	// has the user already rated the app?
-	if ([userDefaults boolForKey:kAppiraterRatedCurrentVersion])
+	NSInteger sigEventCount = [userDefaults integerForKey:kAppiraterSignificantEventCount];
+	if (sigEventCount < _significantEventsUntilPrompt)
 		return NO;
 	
 	// if the user wanted to be reminded later, has enough time passed?
 	NSDate *reminderRequestDate = [NSDate dateWithTimeIntervalSince1970:[userDefaults doubleForKey:kAppiraterReminderRequestDate]];
 	NSTimeInterval timeSinceReminderRequest = [[NSDate date] timeIntervalSinceDate:reminderRequestDate];
-	NSTimeInterval timeUntilReminder = 60 * 60 * 24 * APPIRATER_TIME_BEFORE_REMINDING;
+	NSTimeInterval timeUntilReminder = 60 * 60 * 24 * _timeBeforeReminding;
 	if (timeSinceReminderRequest < timeUntilReminder)
 		return NO;
 	
@@ -171,7 +379,7 @@ static int app_id = 0;
 		[userDefaults setObject:version forKey:kAppiraterCurrentVersion];
 	}
 	
-	if (APPIRATER_DEBUG)
+	if (_debug)
 		NSLog(@"APPIRATER Tracking version: %@", trackingVersion);
 	
 	if ([trackingVersion isEqualToString:version])
@@ -185,11 +393,11 @@ static int app_id = 0;
 		}
 		
 		// increment the use count
-		int useCount = [userDefaults integerForKey:kAppiraterUseCount];
+		NSInteger useCount = [userDefaults integerForKey:kAppiraterUseCount];
 		useCount++;
 		[userDefaults setInteger:useCount forKey:kAppiraterUseCount];
-		if (APPIRATER_DEBUG)
-			NSLog(@"APPIRATER Use count: %d", useCount);
+		if (_debug)
+			NSLog(@"APPIRATER Use count: %@", @(useCount));
 	}
 	else
 	{
@@ -219,7 +427,7 @@ static int app_id = 0;
 		[userDefaults setObject:version forKey:kAppiraterCurrentVersion];
 	}
 	
-	if (APPIRATER_DEBUG)
+	if (_debug)
 		NSLog(@"APPIRATER Tracking version: %@", trackingVersion);
 	
 	if ([trackingVersion isEqualToString:version])
@@ -233,11 +441,11 @@ static int app_id = 0;
 		}
 		
 		// increment the significant event count
-		int sigEventCount = [userDefaults integerForKey:kAppiraterSignificantEventCount];
+		NSInteger sigEventCount = [userDefaults integerForKey:kAppiraterSignificantEventCount];
 		sigEventCount++;
 		[userDefaults setInteger:sigEventCount forKey:kAppiraterSignificantEventCount];
-		if (APPIRATER_DEBUG)
-			NSLog(@"APPIRATER Significant event count: %d", sigEventCount);
+		if (_debug)
+			NSLog(@"APPIRATER Significant event count: %@", @(sigEventCount));
 	}
 	else
 	{
@@ -254,105 +462,214 @@ static int app_id = 0;
 	[userDefaults synchronize];
 }
 
-@end
-
-
-@interface Appirater ()
-- (void)hideRatingAlert;
-@end
-
-@implementation Appirater
-
-@synthesize ratingAlert;
-
-- (void)incrementAndRate:(NSNumber*)_canPromptForRating {
-	NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
-	
+- (void)incrementAndRate:(BOOL)canPromptForRating {
 	[self incrementUseCount];
 	
-	if ([_canPromptForRating boolValue] == YES &&
-		[self ratingConditionsHaveBeenMet] &&
-		[self connectedToNetwork])
+	if (canPromptForRating &&
+        [self ratingConditionsHaveBeenMet] &&
+        [self ratingAlertIsAppropriate])
 	{
-		[self performSelectorOnMainThread:@selector(showRatingAlert) withObject:nil waitUntilDone:NO];
+        dispatch_async(dispatch_get_main_queue(),
+                       ^{
+                           [self showRatingAlert];
+                       });
 	}
-	
-	[pool release];
 }
 
-- (void)incrementSignificantEventAndRate:(NSNumber*)_canPromptForRating {
-	NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
-	
+- (void)incrementSignificantEventAndRate:(BOOL)canPromptForRating {
 	[self incrementSignificantEventCount];
 	
-	if ([_canPromptForRating boolValue] == YES &&
-		[self ratingConditionsHaveBeenMet] &&
-		[self connectedToNetwork])
+    if (canPromptForRating &&
+        [self ratingConditionsHaveBeenMet] &&
+        [self ratingAlertIsAppropriate])
 	{
-		[self performSelectorOnMainThread:@selector(showRatingAlert) withObject:nil waitUntilDone:NO];
+        dispatch_async(dispatch_get_main_queue(),
+                       ^{
+                           [self showRatingAlert];
+                       });
 	}
-	
-	[pool release];
 }
 
-+ (void)appLaunched:(int)p_app_id {
-	app_id = p_app_id;
+- (BOOL)userHasDeclinedToRate {
+    return [[NSUserDefaults standardUserDefaults] boolForKey:kAppiraterDeclinedToRate];
+}
+
+- (BOOL)userHasRatedCurrentVersion {
+    return [[NSUserDefaults standardUserDefaults] boolForKey:kAppiraterRatedCurrentVersion];
+}
+
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wdeprecated-implementations"
++ (void)appLaunched {
 	[Appirater appLaunched:YES];
 }
+#pragma GCC diagnostic pop
 
-+ (void)appLaunched:(BOOL)canPromptForRating app_id:(int)p_app_id {
-	app_id = p_app_id;
-	NSNumber *_canPromptForRating = [[NSNumber alloc] initWithBool:canPromptForRating];
-	[NSThread detachNewThreadSelector:@selector(incrementAndRate:)
-							 toTarget:[Appirater sharedInstance]
-						   withObject:_canPromptForRating];
-	[_canPromptForRating release];
++ (void)appLaunched:(BOOL)canPromptForRating {
+    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0),
+                   ^{
+                       Appirater *a = [Appirater sharedInstance];
+                       if (_debug) {
+                           dispatch_async(dispatch_get_main_queue(),
+                                          ^{
+                                              [a showRatingAlert];
+                                          });
+                       } else {
+                           [a incrementAndRate:canPromptForRating]; 
+                       }
+                   });
 }
 
 - (void)hideRatingAlert {
 	if (self.ratingAlert.visible) {
-		if (APPIRATER_DEBUG)
+		if (_debug)
 			NSLog(@"APPIRATER Hiding Alert");
 		[self.ratingAlert dismissWithClickedButtonIndex:-1 animated:NO];
 	}	
 }
 
 + (void)appWillResignActive {
-	if (APPIRATER_DEBUG)
+	if (_debug)
 		NSLog(@"APPIRATER appWillResignActive");
 	[[Appirater sharedInstance] hideRatingAlert];
 }
 
 + (void)appEnteredForeground:(BOOL)canPromptForRating {
-	NSNumber *_canPromptForRating = [[NSNumber alloc] initWithBool:canPromptForRating];
-	[NSThread detachNewThreadSelector:@selector(incrementAndRate:)
-							 toTarget:[Appirater sharedInstance]
-						   withObject:_canPromptForRating];
-	[_canPromptForRating release];
+    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0),
+                   ^{
+                       [[Appirater sharedInstance] incrementAndRate:canPromptForRating];
+                   });
 }
 
 + (void)userDidSignificantEvent:(BOOL)canPromptForRating {
-	NSNumber *_canPromptForRating = [[NSNumber alloc] initWithBool:canPromptForRating];
-	[NSThread detachNewThreadSelector:@selector(incrementSignificantEventAndRate:)
-							 toTarget:[Appirater sharedInstance]
-						   withObject:_canPromptForRating];
-	[_canPromptForRating release];
+    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0),
+                   ^{
+                       [[Appirater sharedInstance] incrementSignificantEventAndRate:canPromptForRating];
+                   });
+}
+
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wdeprecated-implementations"
++ (void)showPrompt {
+  [Appirater tryToShowPrompt];
+}
+#pragma GCC diagnostic pop
+
++ (void)tryToShowPrompt {
+  [[Appirater sharedInstance] showPromptWithChecks:true
+                            displayRateLaterButton:true];
+}
+
++ (void)forceShowPrompt:(BOOL)displayRateLaterButton {
+  [[Appirater sharedInstance] showPromptWithChecks:false
+                            displayRateLaterButton:displayRateLaterButton];
+}
+
+- (void)showPromptWithChecks:(BOOL)withChecks
+      displayRateLaterButton:(BOOL)displayRateLaterButton {
+  if (withChecks == NO || [self ratingAlertIsAppropriate]) {
+    [self showRatingAlert:displayRateLaterButton];
+  }
+}
+
++ (id)getRootViewController {
+    UIWindow *window = [[UIApplication sharedApplication] keyWindow];
+    if (window.windowLevel != UIWindowLevelNormal) {
+        NSArray *windows = [[UIApplication sharedApplication] windows];
+        for(window in windows) {
+            if (window.windowLevel == UIWindowLevelNormal) {
+                break;
+            }
+        }
+    }
+    
+    return [Appirater iterateSubViewsForViewController:window]; // iOS 8+ deep traverse
+}
+
++ (id)iterateSubViewsForViewController:(UIView *) parentView {
+    for (UIView *subView in [parentView subviews]) {
+        UIResponder *responder = [subView nextResponder];
+        if([responder isKindOfClass:[UIViewController class]]) {
+            return [self topMostViewController: (UIViewController *) responder];
+        }
+        id found = [Appirater iterateSubViewsForViewController:subView];
+        if( nil != found) {
+            return found;
+        }
+    }
+    return nil;
+}
+
++ (UIViewController *) topMostViewController: (UIViewController *) controller {
+	BOOL isPresenting = NO;
+	do {
+		// this path is called only on iOS 6+, so -presentedViewController is fine here.
+		UIViewController *presented = [controller presentedViewController];
+		isPresenting = presented != nil;
+		if(presented != nil) {
+			controller = presented;
+		}
+		
+	} while (isPresenting);
+	
+	return controller;
 }
 
 + (void)rateApp {
-#if TARGET_IPHONE_SIMULATOR
-	NSLog(@"APPIRATER NOTE: iTunes App Store is not supported on the iOS simulator. Unable to open App Store page.");
-#else
+	
 	NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
-	NSString *reviewURL = [templateReviewURL stringByReplacingOccurrencesOfString:@"APP_ID" withString:[NSString stringWithFormat:@"%d", app_id]];
 	[userDefaults setBool:YES forKey:kAppiraterRatedCurrentVersion];
 	[userDefaults synchronize];
-	[[UIApplication sharedApplication] openURL:[NSURL URLWithString:reviewURL]];
+
+	//Use the in-app StoreKit view if available (iOS 6) and imported. This works in the simulator.
+	if (![Appirater sharedInstance].openInAppStore && NSStringFromClass([SKStoreProductViewController class]) != nil) {
+		
+		SKStoreProductViewController *storeViewController = [[SKStoreProductViewController alloc] init];
+		NSNumber *appId = [NSNumber numberWithInteger:_appId.integerValue];
+		[storeViewController loadProductWithParameters:@{SKStoreProductParameterITunesItemIdentifier:appId} completionBlock:nil];
+		storeViewController.delegate = self.sharedInstance;
+        
+        id <AppiraterDelegate> delegate = self.sharedInstance.delegate;
+		if ([delegate respondsToSelector:@selector(appiraterWillPresentModalView:animated:)]) {
+			[delegate appiraterWillPresentModalView:self.sharedInstance animated:_usesAnimation];
+		}
+		[[self getRootViewController] presentViewController:storeViewController animated:_usesAnimation completion:^{
+			[self setModalOpen:YES];
+			//Temporarily use a black status bar to match the StoreKit view.
+			[self setStatusBarStyle:[UIApplication sharedApplication].statusBarStyle];
+#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 70000
+			[[UIApplication sharedApplication]setStatusBarStyle:UIStatusBarStyleLightContent animated:_usesAnimation];
 #endif
+		}];
+	
+	//Use the standard openUrl method if StoreKit is unavailable.
+	} else {
+		
+		#if TARGET_IPHONE_SIMULATOR
+		NSLog(@"APPIRATER NOTE: iTunes App Store is not supported on the iOS simulator. Unable to open App Store page.");
+		#else
+		NSString *reviewURL = [templateReviewURL stringByReplacingOccurrencesOfString:@"APP_ID" withString:[NSString stringWithFormat:@"%@", _appId]];
+
+		// iOS 7 needs a different templateReviewURL @see https://github.com/arashpayan/appirater/issues/131
+        // Fixes condition @see https://github.com/arashpayan/appirater/issues/205
+		if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 7.0 && [[[UIDevice currentDevice] systemVersion] floatValue] < 8.0) {
+			reviewURL = [templateReviewURLiOS7 stringByReplacingOccurrencesOfString:@"APP_ID" withString:[NSString stringWithFormat:@"%@", _appId]];
+		}
+        // iOS 8 needs a different templateReviewURL also @see https://github.com/arashpayan/appirater/issues/182
+        else if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 8.0)
+        {
+            reviewURL = [templateReviewURLiOS8 stringByReplacingOccurrencesOfString:@"APP_ID" withString:[NSString stringWithFormat:@"%@", _appId]];
+        }
+
+		[[UIApplication sharedApplication] openURL:[NSURL URLWithString:reviewURL]];
+		#endif
+	}
 }
 
-- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex {
+- (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex {
 	NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
+    
+    id <AppiraterDelegate> delegate = _delegate;
 	
 	switch (buttonIndex) {
 		case 0:
@@ -360,24 +677,56 @@ static int app_id = 0;
 			// they don't want to rate it
 			[userDefaults setBool:YES forKey:kAppiraterDeclinedToRate];
 			[userDefaults synchronize];
+			if(delegate && [delegate respondsToSelector:@selector(appiraterDidDeclineToRate:)]){
+				[delegate appiraterDidDeclineToRate:self];
+			}
 			break;
 		}
 		case 1:
 		{
 			// they want to rate it
 			[Appirater rateApp];
+			if(delegate&& [delegate respondsToSelector:@selector(appiraterDidOptToRate:)]){
+				[delegate appiraterDidOptToRate:self];
+			}
 			break;
 		}
 		case 2:
 			// remind them later
 			[userDefaults setDouble:[[NSDate date] timeIntervalSince1970] forKey:kAppiraterReminderRequestDate];
 			[userDefaults synchronize];
+			if(delegate && [delegate respondsToSelector:@selector(appiraterDidOptToRemindLater:)]){
+				[delegate appiraterDidOptToRemindLater:self];
+			}
 			break;
 		default:
 			break;
 	}
 }
 
-@end
+//Delegate call from the StoreKit view.
+- (void)productViewControllerDidFinish:(SKStoreProductViewController *)viewController {
+	[Appirater closeModal];
+}
 
-#endif
+//Close the in-app rating (StoreKit) view and restore the previous status bar style.
++ (void)closeModal {
+	if (_modalOpen) {
+		[[UIApplication sharedApplication]setStatusBarStyle:_statusBarStyle animated:_usesAnimation];
+		BOOL usedAnimation = _usesAnimation;
+		[self setModalOpen:NO];
+		
+		// get the top most controller (= the StoreKit Controller) and dismiss it
+		UIViewController *presentingController = [UIApplication sharedApplication].keyWindow.rootViewController;
+		presentingController = [self topMostViewController: presentingController];
+		[presentingController dismissViewControllerAnimated:_usesAnimation completion:^{
+            id <AppiraterDelegate> delegate = self.sharedInstance.delegate;
+			if ([delegate respondsToSelector:@selector(appiraterDidDismissModalView:animated:)]) {
+				[delegate appiraterDidDismissModalView:(Appirater *)self animated:usedAnimation];
+			}
+		}];
+		[self.class setStatusBarStyle:(UIStatusBarStyle)nil];
+	}
+}
+
+@end

+ 23 - 0
platform/iphone/AppiraterDelegate.h

@@ -0,0 +1,23 @@
+//
+//  AppiraterDelegate.h
+//  Banana Stand
+//
+//  Created by Robert Haining on 9/25/12.
+//  Copyright (c) 2012 News.me. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+
+@class Appirater;
+
+@protocol AppiraterDelegate <NSObject>
+
+@optional
+-(BOOL)appiraterShouldDisplayAlert:(Appirater *)appirater;
+-(void)appiraterDidDisplayAlert:(Appirater *)appirater;
+-(void)appiraterDidDeclineToRate:(Appirater *)appirater;
+-(void)appiraterDidOptToRate:(Appirater *)appirater;
+-(void)appiraterDidOptToRemindLater:(Appirater *)appirater;
+-(void)appiraterWillPresentModalView:(Appirater *)appirater animated:(BOOL)animated;
+-(void)appiraterDidDismissModalView:(Appirater *)appirater animated:(BOOL)animated;
+@end

+ 1 - 0
platform/iphone/SCsub

@@ -14,6 +14,7 @@ iphone_lib = [
 	'in_app_store.mm',
 	'icloud.mm',
 	'Appirater.m',
+	'ios.mm',
 ]
 
 #env.Depends('#core/math/vector3.h', 'vector3_psp.h')

+ 20 - 0
platform/iphone/ios.h

@@ -0,0 +1,20 @@
+#ifndef IOS_H
+#define IOS_H
+
+#include "core/object.h"
+
+class iOS : public Object {
+
+	OBJ_TYPE(iOS, Object);
+
+	static void _bind_methods();
+
+public:
+
+	String get_rate_url(int p_app_id) const;
+
+	iOS();
+
+};
+
+#endif

+ 34 - 0
platform/iphone/ios.mm

@@ -0,0 +1,34 @@
+#include "ios.h"
+
+#import <UIKit/UIKit.h>
+
+void iOS::_bind_methods() {
+
+	ObjectTypeDB::bind_method(_MD("get_rate_url","app_id"),&iOS::get_rate_url);
+};
+
+String iOS::get_rate_url(int p_app_id) const {
+	String templ = "itms-apps://ax.itunes.apple.com/WebObjects/MZStore.woa/wa/viewContentsUserReviews?type=Purple+Software&id=APP_ID";
+	String templ_iOS7 = "itms-apps://itunes.apple.com/app/idAPP_ID";
+	String templ_iOS8 = "itms-apps://itunes.apple.com/WebObjects/MZStore.woa/wa/viewContentsUserReviews?id=APP_ID&onlyLatestVersion=true&pageNumber=0&sortOrdering=1&type=Purple+Software";
+
+	//ios7 before
+	String ret = templ;
+
+	// iOS 7 needs a different templateReviewURL @see https://github.com/arashpayan/appirater/issues/131
+	if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 7.0 && [[[UIDevice currentDevice] systemVersion] floatValue] < 7.1)
+	{
+		ret = templ_iOS7;
+	}
+	// iOS 8 needs a different templateReviewURL also @see https://github.com/arashpayan/appirater/issues/182
+	else if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 8.0)
+	{
+		ret = templ_iOS8;
+	}
+
+	ret = ret.replace("APP_ID", String::num(p_app_id));
+
+	return ret;
+};
+
+iOS::iOS() {};

+ 3 - 0
platform/iphone/os_iphone.cpp

@@ -46,6 +46,8 @@
 
 #include "sem_iphone.h"
 
+#include "ios.h"
+
 int OSIPhone::get_video_driver_count() const {
 
 	return 1;
@@ -167,6 +169,7 @@ void OSIPhone::initialize(const VideoMode& p_desired,int p_video_driver,int p_au
 	Globals::get_singleton()->add_singleton(Globals::Singleton("ICloud", icloud));
 	//icloud->connect();
 #endif
+	Globals::get_singleton()->add_singleton(Globals::Singleton("iOS", memnew(iOS)));
 };
 
 MainLoop *OSIPhone::get_main_loop() const {