diff --git a/platform/iphone/Appirater.m b/platform/iphone/Appirater.m index d9144eda3ee..951b892032f 100644 --- a/platform/iphone/Appirater.m +++ b/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 #include +#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 _delegate; +#else + __weak static id _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)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 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 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 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 delegate = self.sharedInstance.delegate; + if ([delegate respondsToSelector:@selector(appiraterDidDismissModalView:animated:)]) { + [delegate appiraterDidDismissModalView:(Appirater *)self animated:usedAnimation]; + } + }]; + [self.class setStatusBarStyle:(UIStatusBarStyle)nil]; + } +} + +@end diff --git a/platform/iphone/AppiraterDelegate.h b/platform/iphone/AppiraterDelegate.h new file mode 100644 index 00000000000..cbe0cfad5b2 --- /dev/null +++ b/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 + +@class Appirater; + +@protocol AppiraterDelegate + +@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 diff --git a/platform/iphone/SCsub b/platform/iphone/SCsub index 922a324694b..1f28441fbd6 100644 --- a/platform/iphone/SCsub +++ b/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') diff --git a/platform/iphone/ios.h b/platform/iphone/ios.h new file mode 100644 index 00000000000..0e4661520b5 --- /dev/null +++ b/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 diff --git a/platform/iphone/ios.mm b/platform/iphone/ios.mm new file mode 100644 index 00000000000..882002a2927 --- /dev/null +++ b/platform/iphone/ios.mm @@ -0,0 +1,34 @@ +#include "ios.h" + +#import + +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() {}; diff --git a/platform/iphone/os_iphone.cpp b/platform/iphone/os_iphone.cpp index 56dffc8aa4a..93496e82254 100644 --- a/platform/iphone/os_iphone.cpp +++ b/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 {