aboutsummaryrefslogtreecommitdiff
path: root/platform/iphone/Appirater.m
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--platform/iphone/Appirater.m383
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