diff options
Diffstat (limited to '')
| -rw-r--r-- | platform/iphone/Appirater.m | 383 |
1 files changed, 383 insertions, 0 deletions
diff --git a/platform/iphone/Appirater.m b/platform/iphone/Appirater.m new file mode 100644 index 000000000..d9144eda3 --- /dev/null +++ b/platform/iphone/Appirater.m @@ -0,0 +1,383 @@ +#ifdef APPIRATER_ENABLED + +/* + This file is part of Appirater. + + Copyright (c) 2010, Arash Payan + All rights reserved. + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, + copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following + conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. + */ +/* + * Appirater.m + * appirater + * + * Created by Arash Payan on 9/5/09. + * http://arashpayan.com + * Copyright 2010 Arash Payan. All rights reserved. + */ + +#import "Appirater.h" +#import <SystemConfiguration/SCNetworkReachability.h> +#include <netinet/in.h> + +NSString *const kAppiraterFirstUseDate = @"kAppiraterFirstUseDate"; +NSString *const kAppiraterUseCount = @"kAppiraterUseCount"; +NSString *const kAppiraterSignificantEventCount = @"kAppiraterSignificantEventCount"; +NSString *const kAppiraterCurrentVersion = @"kAppiraterCurrentVersion"; +NSString *const kAppiraterRatedCurrentVersion = @"kAppiraterRatedCurrentVersion"; +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"; + +static int app_id = 0; + +@interface Appirater (hidden) +- (BOOL)connectedToNetwork; ++ (Appirater*)sharedInstance; +- (void)showRatingAlert; +- (BOOL)ratingConditionsHaveBeenMet; +- (void)incrementUseCount; +@end + +@implementation Appirater (hidden) + +- (BOOL)connectedToNetwork { + // Create zero addy + struct sockaddr_in zeroAddress; + bzero(&zeroAddress, sizeof(zeroAddress)); + zeroAddress.sin_len = sizeof(zeroAddress); + zeroAddress.sin_family = AF_INET; + + // Recover reachability flags + SCNetworkReachabilityRef defaultRouteReachability = SCNetworkReachabilityCreateWithAddress(NULL, (struct sockaddr *)&zeroAddress); + SCNetworkReachabilityFlags flags; + + BOOL didRetrieveFlags = SCNetworkReachabilityGetFlags(defaultRouteReachability, &flags); + CFRelease(defaultRouteReachability); + + if (!didRetrieveFlags) + { + NSLog(@"Error. Could not recover network reachability flags"); + return NO; + } + + BOOL isReachable = flags & kSCNetworkFlagsReachable; + BOOL needsConnection = flags & kSCNetworkFlagsConnectionRequired; + BOOL nonWiFi = flags & kSCNetworkReachabilityFlagsTransientConnection; + + NSURL *testURL = [NSURL URLWithString:@"http://www.apple.com/"]; + NSURLRequest *testRequest = [NSURLRequest requestWithURL:testURL cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:20.0]; + NSURLConnection *testConnection = [[NSURLConnection alloc] initWithRequest:testRequest delegate:self]; + + return ((isReachable && !needsConnection) || nonWiFi) ? (testConnection ? YES : NO) : NO; +} + ++ (Appirater*)sharedInstance { + 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]; + } + } + } + + 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]; + self.ratingAlert = alertView; + [alertView show]; +} + +- (BOOL)ratingConditionsHaveBeenMet { + if (APPIRATER_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; + if (timeSinceFirstLaunch < timeUntilRate) + return NO; + + // check if the app has been used enough + int useCount = [userDefaults integerForKey:kAppiraterUseCount]; + if (useCount <= APPIRATER_USES_UNTIL_PROMPT) + 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]) + 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; + if (timeSinceReminderRequest < timeUntilReminder) + return NO; + + return YES; +} + +- (void)incrementUseCount { + // get the app's version + NSString *version = [[[NSBundle mainBundle] infoDictionary] objectForKey:(NSString*)kCFBundleVersionKey]; + + // get the version number that we've been tracking + NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults]; + NSString *trackingVersion = [userDefaults stringForKey:kAppiraterCurrentVersion]; + if (trackingVersion == nil) + { + trackingVersion = version; + [userDefaults setObject:version forKey:kAppiraterCurrentVersion]; + } + + if (APPIRATER_DEBUG) + NSLog(@"APPIRATER Tracking version: %@", trackingVersion); + + if ([trackingVersion isEqualToString:version]) + { + // check if the first use date has been set. if not, set it. + NSTimeInterval timeInterval = [userDefaults doubleForKey:kAppiraterFirstUseDate]; + if (timeInterval == 0) + { + timeInterval = [[NSDate date] timeIntervalSince1970]; + [userDefaults setDouble:timeInterval forKey:kAppiraterFirstUseDate]; + } + + // increment the use count + int useCount = [userDefaults integerForKey:kAppiraterUseCount]; + useCount++; + [userDefaults setInteger:useCount forKey:kAppiraterUseCount]; + if (APPIRATER_DEBUG) + NSLog(@"APPIRATER Use count: %d", useCount); + } + else + { + // it's a new version of the app, so restart tracking + [userDefaults setObject:version forKey:kAppiraterCurrentVersion]; + [userDefaults setDouble:[[NSDate date] timeIntervalSince1970] forKey:kAppiraterFirstUseDate]; + [userDefaults setInteger:1 forKey:kAppiraterUseCount]; + [userDefaults setInteger:0 forKey:kAppiraterSignificantEventCount]; + [userDefaults setBool:NO forKey:kAppiraterRatedCurrentVersion]; + [userDefaults setBool:NO forKey:kAppiraterDeclinedToRate]; + [userDefaults setDouble:0 forKey:kAppiraterReminderRequestDate]; + } + + [userDefaults synchronize]; +} + +- (void)incrementSignificantEventCount { + // get the app's version + NSString *version = [[[NSBundle mainBundle] infoDictionary] objectForKey:(NSString*)kCFBundleVersionKey]; + + // get the version number that we've been tracking + NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults]; + NSString *trackingVersion = [userDefaults stringForKey:kAppiraterCurrentVersion]; + if (trackingVersion == nil) + { + trackingVersion = version; + [userDefaults setObject:version forKey:kAppiraterCurrentVersion]; + } + + if (APPIRATER_DEBUG) + NSLog(@"APPIRATER Tracking version: %@", trackingVersion); + + if ([trackingVersion isEqualToString:version]) + { + // check if the first use date has been set. if not, set it. + NSTimeInterval timeInterval = [userDefaults doubleForKey:kAppiraterFirstUseDate]; + if (timeInterval == 0) + { + timeInterval = [[NSDate date] timeIntervalSince1970]; + [userDefaults setDouble:timeInterval forKey:kAppiraterFirstUseDate]; + } + + // increment the significant event count + int sigEventCount = [userDefaults integerForKey:kAppiraterSignificantEventCount]; + sigEventCount++; + [userDefaults setInteger:sigEventCount forKey:kAppiraterSignificantEventCount]; + if (APPIRATER_DEBUG) + NSLog(@"APPIRATER Significant event count: %d", sigEventCount); + } + else + { + // it's a new version of the app, so restart tracking + [userDefaults setObject:version forKey:kAppiraterCurrentVersion]; + [userDefaults setDouble:0 forKey:kAppiraterFirstUseDate]; + [userDefaults setInteger:0 forKey:kAppiraterUseCount]; + [userDefaults setInteger:1 forKey:kAppiraterSignificantEventCount]; + [userDefaults setBool:NO forKey:kAppiraterRatedCurrentVersion]; + [userDefaults setBool:NO forKey:kAppiraterDeclinedToRate]; + [userDefaults setDouble:0 forKey:kAppiraterReminderRequestDate]; + } + + [userDefaults synchronize]; +} + +@end + + +@interface Appirater () +- (void)hideRatingAlert; +@end + +@implementation Appirater + +@synthesize ratingAlert; + +- (void)incrementAndRate:(NSNumber*)_canPromptForRating { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + + [self incrementUseCount]; + + if ([_canPromptForRating boolValue] == YES && + [self ratingConditionsHaveBeenMet] && + [self connectedToNetwork]) + { + [self performSelectorOnMainThread:@selector(showRatingAlert) withObject:nil waitUntilDone:NO]; + } + + [pool release]; +} + +- (void)incrementSignificantEventAndRate:(NSNumber*)_canPromptForRating { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + + [self incrementSignificantEventCount]; + + if ([_canPromptForRating boolValue] == YES && + [self ratingConditionsHaveBeenMet] && + [self connectedToNetwork]) + { + [self performSelectorOnMainThread:@selector(showRatingAlert) withObject:nil waitUntilDone:NO]; + } + + [pool release]; +} + ++ (void)appLaunched:(int)p_app_id { + app_id = p_app_id; + [Appirater appLaunched:YES]; +} + ++ (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)hideRatingAlert { + if (self.ratingAlert.visible) { + if (APPIRATER_DEBUG) + NSLog(@"APPIRATER Hiding Alert"); + [self.ratingAlert dismissWithClickedButtonIndex:-1 animated:NO]; + } +} + ++ (void)appWillResignActive { + if (APPIRATER_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]; +} + ++ (void)userDidSignificantEvent:(BOOL)canPromptForRating { + NSNumber *_canPromptForRating = [[NSNumber alloc] initWithBool:canPromptForRating]; + [NSThread detachNewThreadSelector:@selector(incrementSignificantEventAndRate:) + toTarget:[Appirater sharedInstance] + withObject:_canPromptForRating]; + [_canPromptForRating release]; +} + ++ (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]]; +#endif +} + +- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex { + NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults]; + + switch (buttonIndex) { + case 0: + { + // they don't want to rate it + [userDefaults setBool:YES forKey:kAppiraterDeclinedToRate]; + [userDefaults synchronize]; + break; + } + case 1: + { + // they want to rate it + [Appirater rateApp]; + break; + } + case 2: + // remind them later + [userDefaults setDouble:[[NSDate date] timeIntervalSince1970] forKey:kAppiraterReminderRequestDate]; + [userDefaults synchronize]; + break; + default: + break; + } +} + +@end + +#endif |
