1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243 |
- //
- // iRate.m
- //
- // Version 1.7.4
- //
- // Created by Nick Lockwood on 26/01/2011.
- // Copyright 2011 Charcoal Design
- //
- // Distributed under the permissive zlib license
- // Get the latest version from here:
- //
- // https://github.com/nicklockwood/iRate
- //
- // This software is provided 'as-is', without any express or implied
- // warranty. In no event will the authors be held liable for any damages
- // arising from the use of this software.
- //
- // Permission is granted to anyone to use this software for any purpose,
- // including commercial applications, and to alter it and redistribute it
- // freely, subject to the following restrictions:
- //
- // 1. The origin of this software must not be misrepresented; you must not
- // claim that you wrote the original software. If you use this software
- // in a product, an acknowledgment in the product documentation would be
- // appreciated but is not required.
- //
- // 2. Altered source versions must be plainly marked as such, and must not be
- // misrepresented as being the original software.
- //
- // 3. This notice may not be removed or altered from any source distribution.
- //
- #import "iRate.h"
- #if !VERSION_DMG
- #import <StoreKit/StoreKit.h>
- #endif
- #import <Availability.h>
- //#if !__has_feature(objc_arc)
- //#error This class requires automatic reference counting
- //#endif
- //#import "KMHomeWindowController.h"
- NSUInteger const iRateAppStoreGameGenreID = 6014;
- NSString *const iRateErrorDomain = @"iRateErrorDomain";
- static NSString *const iRateRatedVersionKey = @"iRateRatedVersionChecked";
- static NSString *const iRateDeclinedVersionKey = @"iRateDeclinedVersion";
- static NSString *const iRateLastRemindedKey = @"iRateLastReminded";
- static NSString *const iRateLastVersionUsedKey = @"iRateLastVersionUsed";
- static NSString *const iRateFirstUsedKey = @"iRateFirstUsed";
- static NSString *const iRateUseCountKey = @"iRateUseCount";
- static NSString *const iRateEventCountKey = @"iRateEventCount";
- static NSString *const iRateDeclinedCountKey = @"iRateDeclinedCount";
- static NSString *const iRateMacAppStoreBundleID = @"com.apple.appstore";
- static NSString *const iRateAppLookupURLFormat = @"http://itunes.apple.com/%@/lookup";
- static NSString *const iRateiOSAppStoreURLFormat = @"itms-apps://itunes.apple.com/WebObjects/MZStore.woa/wa/viewContentsUserReviews?type=Purple+Software&id=%u";
- static NSString *const iRateMacAppStoreURLFormat = @"macappstore://itunes.apple.com/app/id%u";
- #define SECONDS_IN_A_DAY 86400.0
- #define SECONDS_IN_A_WEEK 604800.0
- #define MAC_APP_STORE_REFRESH_DELAY 5.0
- #define REQUEST_TIMEOUT 60.0
- @interface iRate()
- @property (nonatomic, strong) id visibleAlert;
- @property (nonatomic, assign) int previousOrientation;
- @property (nonatomic, assign) BOOL currentlyChecking;
- @end
- @implementation iRate
- #pragma mark -
- #pragma mark Lifecycle methods
- + (void)load
- {
- [self performSelectorOnMainThread:@selector(sharedInstance) withObject:nil waitUntilDone:NO];
- }
- + (iRate *)sharedInstance
- {
- static iRate *sharedInstance = nil;
- if (sharedInstance == nil)
- {
- sharedInstance = [[iRate alloc] init];
- }
- return sharedInstance;
- }
- - (NSString *)localizedStringForKey:(NSString *)key withDefault:(NSString *)defaultString
- {
- static NSBundle *bundle = nil;
- if (bundle == nil)
- {
- NSString *bundlePath = [[NSBundle mainBundle] pathForResource:@"iRate" ofType:@"bundle"];
- bundle = [NSBundle bundleWithPath:bundlePath] ?: [NSBundle mainBundle];
- if (self.useAllAvailableLanguages)
- {
- //manually select the desired lproj folder
- for (NSString *language in [NSLocale preferredLanguages])
- {
- if ([[bundle localizations] containsObject:language])
- {
- bundlePath = [bundle pathForResource:language ofType:@"lproj"];
- bundle = [NSBundle bundleWithPath:bundlePath];
- break;
- }
- }
- }
- }
- defaultString = [bundle localizedStringForKey:key value:defaultString table:nil];
- return [[NSBundle mainBundle] localizedStringForKey:key value:defaultString table:nil];
- }
- - (iRate *)init
- {
- if ((self = [super init]))
- {
-
- #ifdef __IPHONE_OS_VERSION_MAX_ALLOWED
-
- //register for iphone application events
- if (&UIApplicationWillEnterForegroundNotification)
- {
- [[NSNotificationCenter defaultCenter] addObserver:self
- selector:@selector(applicationWillEnterForeground:)
- name:UIApplicationWillEnterForegroundNotification
- object:nil];
- }
-
- self.previousOrientation = [UIApplication sharedApplication].statusBarOrientation;
- [[NSNotificationCenter defaultCenter] addObserver:self
- selector:@selector(willRotate)
- name:UIDeviceOrientationDidChangeNotification
- object:nil];
-
- #endif
-
- //get country
- self.appStoreCountry = [(NSLocale *)[NSLocale currentLocale] objectForKey:NSLocaleCountryCode];
-
- //application version (use short version preferentially)
- self.applicationVersion = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"];
- if ([self.applicationVersion length] == 0)
- {
- self.applicationVersion = [[NSBundle mainBundle] objectForInfoDictionaryKey:(NSString *)kCFBundleVersionKey];
- }
-
- //localised application name
- self.applicationName = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleDisplayName"];
- if ([self.applicationName length] == 0)
- {
- self.applicationName = [[NSBundle mainBundle] objectForInfoDictionaryKey:(NSString *)kCFBundleNameKey];
- }
-
- //bundle id
- self.applicationBundleID = [[NSBundle mainBundle] bundleIdentifier];
-
- //default settings
- self.useAllAvailableLanguages = YES;
- self.disableAlertViewResizing = NO;
- self.onlyPromptIfLatestVersion = YES;
- self.onlyPromptIfMainWindowIsAvailable = YES;
- self.displayAppUsingStorekitIfAvailable = YES;
- self.promptAgainForEachNewVersion = YES;
- self.promptAtLaunch = NO;
- self.usesUntilPrompt = 3;
- self.eventsUntilPrompt = -1;
- self.daysUntilPrompt = 4.0f;
- self.usesPerWeekForPrompt = 0.0f;
- self.remindPeriod = 7.0f;
- self.verboseLogging = NO;
- self.previewMode = NO;
-
- #ifdef DEBUG
-
- //enable verbose logging in debug mode
- self.verboseLogging = YES;
-
- #endif
-
- //app launched
- [self performSelectorOnMainThread:@selector(applicationLaunched) withObject:nil waitUntilDone:NO];
- }
- return self;
- }
- - (id<iRateDelegate>)delegate
- {
- if (_delegate == nil)
- {
-
- #ifdef __IPHONE_OS_VERSION_MAX_ALLOWED
-
- _delegate = (id<iRateDelegate>)[[UIApplication sharedApplication] delegate];
- #else
- _delegate = (id<iRateDelegate>)[[NSApplication sharedApplication] delegate];
- #endif
-
- }
- return _delegate;
- }
- - (NSString *)messageTitle
- {
- return [_messageTitle ?: [self localizedStringForKey:iRateMessageTitleKey withDefault:NSLocalizedString(@"Rate %@", nil)] stringByReplacingOccurrencesOfString:@"%@" withString:self.applicationName];
- }
- - (NSString *)message
- {
- NSString *message = _message;
- if (!message)
- {
- NSString *defaultMessage = [NSString stringWithFormat:NSLocalizedString(@"Share your love to %@! Please take one minute to give us a great review on the App Store.", nil),self.applicationName];
- message = (self.appStoreGenreID == iRateAppStoreGameGenreID)? [self localizedStringForKey:iRateGameMessageKey withDefault:defaultMessage]: [self localizedStringForKey:iRateAppMessageKey withDefault:defaultMessage];
- }
- return [message stringByReplacingOccurrencesOfString:@"%@" withString:self.applicationName];
- }
- - (NSString *)cancelButtonLabel
- {
- return _cancelButtonLabel ?: [self localizedStringForKey:iRateCancelButtonKey withDefault:NSLocalizedString(@"Give a suggestion", nil)];
- }
- - (NSString *)rateButtonLabel
- {
- return _rateButtonLabel ?: [self localizedStringForKey:iRateRateButtonKey withDefault:NSLocalizedString(@"Give a 5-Star", nil)];
- }
- - (NSString *)remindButtonLabel
- {
- return _remindButtonLabel ?: [self localizedStringForKey:iRateRemindButtonKey withDefault:NSLocalizedString(@"Remind me later", nil)];
- }
- - (NSURL *)ratingsURL
- {
- if (_ratingsURL)
- {
- return _ratingsURL;
- }
-
- if (!self.appStoreID)
- {
- NSLog(@"iRate could not find the App Store ID for this application. If the application is not intended for App Store release then you must specify a custom ratingsURL.");
- }
-
- #ifdef __IPHONE_OS_VERSION_MAX_ALLOWED
-
- return [NSURL URLWithString:[NSString stringWithFormat:iRateiOSAppStoreURLFormat, (unsigned int)self.appStoreID]];
-
- #else
-
- return [NSURL URLWithString:[NSString stringWithFormat:iRateMacAppStoreURLFormat, (unsigned int)self.appStoreID]];
-
- #endif
-
- }
- - (NSDate *)firstUsed
- {
- return [[NSUserDefaults standardUserDefaults] objectForKey:iRateFirstUsedKey];
- }
- - (void)setFirstUsed:(NSDate *)date
- {
- [[NSUserDefaults standardUserDefaults] setObject:date forKey:iRateFirstUsedKey];
- [[NSUserDefaults standardUserDefaults] synchronize];
- }
- - (NSDate *)lastReminded
- {
- return [[NSUserDefaults standardUserDefaults] objectForKey:iRateLastRemindedKey];
- }
- - (void)setLastReminded:(NSDate *)date
- {
- [[NSUserDefaults standardUserDefaults] setObject:date forKey:iRateLastRemindedKey];
- [[NSUserDefaults standardUserDefaults] synchronize];
- }
- - (NSUInteger)usesCount
- {
- return [[NSUserDefaults standardUserDefaults] integerForKey:iRateUseCountKey];
- }
- - (void)setUsesCount:(NSUInteger)count
- {
- [[NSUserDefaults standardUserDefaults] setInteger:count forKey:iRateUseCountKey];
- [[NSUserDefaults standardUserDefaults] synchronize];
- }
- - (NSUInteger)eventCount;
- {
- return [[NSUserDefaults standardUserDefaults] integerForKey:iRateEventCountKey];
- }
- - (void)setEventCount:(NSUInteger)count
- {
- [[NSUserDefaults standardUserDefaults] setInteger:count forKey:iRateEventCountKey];
- [[NSUserDefaults standardUserDefaults] synchronize];
- }
- - (NSUInteger)declinedCount
- {
- return [[NSUserDefaults standardUserDefaults] integerForKey:iRateDeclinedCountKey];
- }
- - (void)setDeclinedCount:(NSUInteger)count
- {
- [[NSUserDefaults standardUserDefaults] setInteger:count forKey:iRateDeclinedCountKey];
- [[NSUserDefaults standardUserDefaults] synchronize];
- }
- - (float)usesPerWeek
- {
- return (float)self.usesCount / ([[NSDate date] timeIntervalSinceDate:self.firstUsed] / SECONDS_IN_A_WEEK);
- }
- - (BOOL)declinedThisVersion
- {
- return [[[NSUserDefaults standardUserDefaults] objectForKey:iRateDeclinedVersionKey] isEqualToString:self.applicationVersion];
- }
- - (void)setDeclinedThisVersion:(BOOL)declined
- {
- [[NSUserDefaults standardUserDefaults] setObject:(declined? self.applicationVersion: nil) forKey:iRateDeclinedVersionKey];
- [[NSUserDefaults standardUserDefaults] synchronize];
- }
- - (BOOL)declinedAnyVersion
- {
- return [[[NSUserDefaults standardUserDefaults] objectForKey:iRateDeclinedVersionKey] length];
- }
- - (BOOL)ratedThisVersion
- {
- return [[[NSUserDefaults standardUserDefaults] objectForKey:iRateRatedVersionKey] isEqualToString:self.applicationVersion];
- }
- - (void)setRatedThisVersion:(BOOL)rated
- {
- [[NSUserDefaults standardUserDefaults] setObject:(rated? self.applicationVersion: nil) forKey:iRateRatedVersionKey];
- [[NSUserDefaults standardUserDefaults] synchronize];
- }
- - (BOOL)ratedAnyVersion
- {
- return [[[NSUserDefaults standardUserDefaults] objectForKey:iRateRatedVersionKey] length];
- }
- - (void)dealloc
- {
- [[NSNotificationCenter defaultCenter] removeObserver:self];
-
- }
- #pragma mark -
- #pragma mark Methods
- - (void)incrementUseCount
- {
- self.usesCount ++;
- }
- - (void)incrementEventCount
- {
- self.eventCount ++;
- }
- - (BOOL)shouldPromptForRating
- {
- //preview mode?
- if (self.previewMode)
- {
- NSLog(@"iRate preview mode is enabled - make sure you disable this for release");
- return YES;
- }
-
- //check if we've rated this version
- else if (self.ratedThisVersion)
- {
- if (self.verboseLogging)
- {
- NSLog(@"iRate did not prompt for rating because the user has already rated this version");
- }
- return NO;
- }
-
- //check if we've rated any version
- else if (!self.promptAgainForEachNewVersion && self.ratedAnyVersion)
- {
- if (self.verboseLogging)
- {
- NSLog(@"iRate did not prompt for rating because the user has already rated this app, and promptAgainForEachNewVersion is disabled");
- }
- return NO;
- }
-
- //check if we've declined to rate this version
- else if (self.declinedThisVersion)
- {
- if (self.verboseLogging)
- {
- NSLog(@"iRate did not prompt for rating because the user has declined to rate this version");
- }
- return NO;
- }
-
- //check for first launch
- else if ((self.daysUntilPrompt > 0.0f || self.usesPerWeekForPrompt) && self.firstUsed == nil)
- {
- if (self.verboseLogging)
- {
- NSLog(@"iRate did not prompt for rating because this is the first time the app has been launched");
- }
- return NO;
- }
-
- //check how long we've been using this version
- else if ([[NSDate date] timeIntervalSinceDate:self.firstUsed] < self.daysUntilPrompt * SECONDS_IN_A_DAY)
- {
- if (self.verboseLogging)
- {
- NSLog(@"iRate did not prompt for rating because the app was first used less than %g days ago", self.daysUntilPrompt);
- }
- return NO;
- }
-
- //check how many times we've used it and the number of significant events
- // else if (self.usesCount < self.usesUntilPrompt && self.eventCount < self.eventsUntilPrompt)
- else if (self.usesCount < self.usesUntilPrompt)
- {
- if (self.verboseLogging)
- {
- NSLog(@"iRate did not prompt for rating because the app has only been used %i times and only %i events have been logged", (int)self.usesCount, (int)self.eventCount);
- }
- return NO;
- }
-
- // //check if usage frequency is high enough
- // else if (self.usesPerWeek < self.usesPerWeekForPrompt)
- // {
- // if (self.verboseLogging)
- // {
- // NSLog(@"iRate did not prompt for rating because the app has only been used %g times per week on average since it was installed", self.usesPerWeek);
- // }
- // return NO;
- // }
- //check if within the reminder period
- else if (self.lastReminded != nil && [[NSDate date] timeIntervalSinceDate:self.lastReminded] < self.remindPeriod * SECONDS_IN_A_DAY)
- {
- if (self.verboseLogging)
- {
- NSLog(@"iRate did not prompt for rating because the user last asked to be reminded less than %g days ago", self.remindPeriod);
- }
- return NO;
- }
-
- //lets prompt!
- return YES;
- }
- - (NSString *)valueForKey:(NSString *)key inJSON:(NSString *)json
- {
- NSRange keyRange = [json rangeOfString:[NSString stringWithFormat:@"\"%@\"", key]];
- if (keyRange.location != NSNotFound)
- {
- NSInteger start = keyRange.location + keyRange.length;
- NSRange valueStart = [json rangeOfString:@":" options:0 range:NSMakeRange(start, [json length] - start)];
- if (valueStart.location != NSNotFound)
- {
- start = valueStart.location + 1;
- NSRange valueEnd = [json rangeOfString:@"," options:0 range:NSMakeRange(start, [json length] - start)];
- if (valueEnd.location != NSNotFound)
- {
- NSString *value = [json substringWithRange:NSMakeRange(start, valueEnd.location - start)];
- value = [value stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
- while ([value hasPrefix:@"\""] && ![value hasSuffix:@"\""])
- {
- if (valueEnd.location == NSNotFound)
- {
- break;
- }
- NSInteger newStart = valueEnd.location + 1;
- valueEnd = [json rangeOfString:@"," options:0 range:NSMakeRange(newStart, [json length] - newStart)];
- value = [json substringWithRange:NSMakeRange(start, valueEnd.location - start)];
- value = [value stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
- }
-
- value = [value stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"\""]];
- value = [value stringByReplacingOccurrencesOfString:@"\\\\" withString:@"\\"];
- value = [value stringByReplacingOccurrencesOfString:@"\\/" withString:@"/"];
- value = [value stringByReplacingOccurrencesOfString:@"\\\"" withString:@"\""];
- value = [value stringByReplacingOccurrencesOfString:@"\\n" withString:@"\n"];
- value = [value stringByReplacingOccurrencesOfString:@"\\r" withString:@"\r"];
- value = [value stringByReplacingOccurrencesOfString:@"\\t" withString:@"\t"];
- value = [value stringByReplacingOccurrencesOfString:@"\\f" withString:@"\f"];
- value = [value stringByReplacingOccurrencesOfString:@"\\b" withString:@"\f"];
-
- while (YES)
- {
- NSRange unicode = [value rangeOfString:@"\\u"];
- if (unicode.location == NSNotFound)
- {
- break;
- }
-
- uint32_t c = 0;
- NSString *hex = [value substringWithRange:NSMakeRange(unicode.location + 2, 4)];
- NSScanner *scanner = [NSScanner scannerWithString:hex];
- [scanner scanHexInt:&c];
-
- if (c <= 0xffff)
- {
- value = [value stringByReplacingCharactersInRange:NSMakeRange(unicode.location, 6) withString:[NSString stringWithFormat:@"%C", (unichar)c]];
- }
- else
- {
- //convert character to surrogate pair
- uint16_t x = (uint16_t)c;
- uint16_t u = (c >> 16) & ((1 << 5) - 1);
- uint16_t w = (uint16_t)u - 1;
- unichar high = 0xd800 | (w << 6) | x >> 10;
- unichar low = (uint16_t)(0xdc00 | (x & ((1 << 10) - 1)));
-
- value = [value stringByReplacingCharactersInRange:NSMakeRange(unicode.location, 6) withString:[NSString stringWithFormat:@"%C%C", high, low]];
- }
- }
- return value;
- }
- }
- }
- return nil;
- }
- - (void)setAppStoreIDOnMainThread:(NSString *)appStoreIDString
- {
- self.appStoreID = (NSUInteger)[appStoreIDString longLongValue];
- }
- - (void)connectionSucceeded
- {
- //no longer checking
- self.currentlyChecking = NO;
-
- if ([self ratingConditionsHaveBeenMet]) {
-
- //confirm with delegate
- if ([self.delegate respondsToSelector:@selector(iRateShouldPromptForRating)])
- {
- if (![self.delegate iRateShouldPromptForRating])
- {
- if (self.verboseLogging)
- {
- NSLog(@"iRate did not display the rating prompt because the iRateShouldPromptForRating delegate method returned NO");
- }
- return;
- }
- }
-
- //prompt user
- [self promptForRating];
-
- }
- }
- - (BOOL)ratingConditionsHaveBeenMet {
-
- // check if the app has been used enough
- NSInteger useCount = [self usesCount];
- if (useCount <= APPIRATER_USES_UNTIL_PROMPT) {
- return NO;
- } else if ((useCount % 5) == 0) {
- // Apple审核被拒,网页端显示 Free Download 按钮,每开10个文档后检测一次
- // [[KMHomeWindowController homeWindow] checkDefaultOpen];
- }
-
- NSDate *dateOfFirstLaunch = [self firstUsed];
- NSTimeInterval timeSinceFirstLaunch = [[NSDate date] timeIntervalSinceDate:dateOfFirstLaunch];
- NSTimeInterval timeUntilRate = 60 * 60 * 24 * APPIRATER_DAYS_UNTIL_PROMPT;
- if (timeSinceFirstLaunch < timeUntilRate)
- return NO;
-
- // has the user previously declined to rate this version of the app?
- NSInteger declinedCount = [self declinedCount];
- if (declinedCount > APPIRATER_PROMPT_TIMES_UNTIL_REJECT)
- return NO;
-
- // has the user already rated the app?
- if (self.ratedThisVersion)
- return NO;
-
- // if the user wanted to be reminded later, has enough time passed?
- NSDate *reminderRequestDate = [self lastReminded];
- NSTimeInterval timeSinceReminderRequest = [[NSDate date] timeIntervalSinceDate:reminderRequestDate];
- NSTimeInterval timeUntilReminder = 60 * 60 * 24 * APPIRATER_TIME_BEFORE_REMINDING;
- if (timeSinceReminderRequest < timeUntilReminder)
- return NO;
-
- return YES;
- }
- - (void)connectionError:(NSError *)error
- {
- //no longer checking
- self.currentlyChecking = NO;
-
- //log the error
- if (error)
- {
- NSLog(@"iRate rating process failed because: %@", [error localizedDescription]);
- }
- else
- {
- NSLog(@"iRate rating process failed because an unknown error occured");
- }
-
- //could not connect
- if ([self.delegate respondsToSelector:@selector(iRateCouldNotConnectToAppStore:)])
- {
- [self.delegate iRateCouldNotConnectToAppStore:error];
- }
- }
- - (void)checkForConnectivityInBackground
- {
- @synchronized (self)
- {
- @autoreleasepool
- {
- //first check iTunes
- NSString *iTunesServiceURL = [NSString stringWithFormat:iRateAppLookupURLFormat, self.appStoreCountry];
- if (self.appStoreID)
- {
- iTunesServiceURL = [iTunesServiceURL stringByAppendingFormat:@"?id=%u", (unsigned int)self.appStoreID];
- }
- else
- {
- iTunesServiceURL = [iTunesServiceURL stringByAppendingFormat:@"?bundleId=%@", self.applicationBundleID];
- }
-
- if (self.verboseLogging)
- {
- NSLog(@"iRate is checking %@ to retrieve the App Store details...", iTunesServiceURL);
- }
-
- NSError *error = nil;
- NSURLResponse *response = nil;
- NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:iTunesServiceURL] cachePolicy:NSURLRequestReturnCacheDataElseLoad timeoutInterval:REQUEST_TIMEOUT];
- NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
- if (data)
- {
- //convert to string
- NSString *json = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
-
- //check bundle ID matches
- NSString *bundleID = [self valueForKey:@"bundleId" inJSON:json];
- if (bundleID)
- {
- if ([bundleID isEqualToString:self.applicationBundleID])
- {
- //get genre
- if (self.appStoreGenreID == 0)
- {
- _appStoreGenreID = [[self valueForKey:@"primaryGenreId" inJSON:json] integerValue];
- }
-
- //get app id
- if (!self.appStoreID)
- {
- NSString *appStoreIDString = [self valueForKey:@"trackId" inJSON:json];
- [self performSelectorOnMainThread:@selector(setAppStoreIDOnMainThread:) withObject:appStoreIDString waitUntilDone:YES];
-
- if (self.verboseLogging)
- {
- NSLog(@"iRate found the app on iTunes. The App Store ID is %@", appStoreIDString);
- }
- }
-
- //check version
- if (self.onlyPromptIfLatestVersion && !self.previewMode)
- {
- NSString *latestVersion = [self valueForKey:@"version" inJSON:json];
- if ([latestVersion compare:self.applicationVersion options:NSNumericSearch] == NSOrderedDescending)
- {
- if (self.verboseLogging)
- {
- NSLog(@"iRate found that the installed application version (%@) is not the latest version on the App Store, which is %@",
- self.applicationVersion, latestVersion);
- }
-
- error = [NSError errorWithDomain:iRateErrorDomain code:iRateErrorApplicationIsNotLatestVersion userInfo:@{NSLocalizedDescriptionKey: @"Installed app is not the latest version available"}];
- }
- }
- }
- else
- {
- if (self.verboseLogging)
- {
- NSLog(@"iRate found that the application bundle ID (%@) does not match the bundle ID of the app found on iTunes (%@) with the specified App Store ID (%i)", self.applicationBundleID, bundleID, (int)self.appStoreID);
- }
-
- error = [NSError errorWithDomain:iRateErrorDomain code:iRateErrorBundleIdDoesNotMatchAppStore userInfo:@{NSLocalizedDescriptionKey: [NSString stringWithFormat:@"Application bundle ID does not match expected value of %@", bundleID]}];
- }
- }
- else if (self.appStoreID || !self.ratingsURL)
- {
- if (self.verboseLogging)
- {
- NSLog(@"iRate could not find this application on iTunes. If your app is not intended for App Store release then you must specify a custom ratingsURL. If this is the first release of your application then it's not a problem that it cannot be found on the store yet");
- }
-
- error = [NSError errorWithDomain:iRateErrorDomain
- code:iRateErrorApplicationNotFoundOnAppStore
- userInfo:@{NSLocalizedDescriptionKey: @"The application could not be found on the App Store."}];
- }
- else if (!self.appStoreID && self.verboseLogging)
- {
- NSLog(@"iRate could not find your app on iTunes. If your app is not yet on the store or is not intended for App Store release then don't worry about this");
- }
- }
-
- if (error && !(error.code == EPERM && [error.domain isEqualToString:NSPOSIXErrorDomain] && self.appStoreID))
- {
- [self performSelectorOnMainThread:@selector(connectionError:) withObject:error waitUntilDone:YES];
- }
- else if (self.appStoreID || self.previewMode)
- {
- //show prompt
- [self performSelectorOnMainThread:@selector(connectionSucceeded) withObject:nil waitUntilDone:YES];
- }
- }
- }
- }
- - (void)promptIfNetworkAvailable
- {
- if (!self.currentlyChecking)
- {
- self.currentlyChecking = YES;
- [self performSelectorInBackground:@selector(checkForConnectivityInBackground) withObject:nil];
- }
- }
- - (void)promptForRating
- {
- if (!self.visibleAlert)
- {
-
- #ifdef __IPHONE_OS_VERSION_MAX_ALLOWED
-
- UIAlertView *alert = [[UIAlertView alloc] initWithTitle:self.messageTitle
- message:self.message
- delegate:(id <UIAlertViewDelegate>)self
- cancelButtonTitle:[self.cancelButtonLabel length] ? self.cancelButtonLabel: nil
- otherButtonTitles:self.rateButtonLabel, nil];
- // if ([self.remindButtonLabel length])
- // {
- // [alert addButtonWithTitle:self.remindButtonLabel];
- // }
-
- self.visibleAlert = alert;
- [self.visibleAlert show];
- #else
- //only show when main window is available
- if (self.onlyPromptIfMainWindowIsAvailable && ![[NSApplication sharedApplication] mainWindow])
- {
- [self performSelector:@selector(promptForRating) withObject:nil afterDelay:0.5];
- return;
- }
-
- self.visibleAlert = [NSAlert alertWithMessageText:self.messageTitle
- defaultButton:self.rateButtonLabel
- alternateButton:self.cancelButtonLabel
- otherButton:nil
- informativeTextWithFormat:@"%@", self.message];
-
- // if ([self.remindButtonLabel length])
- // {
- // [self.visibleAlert addButtonWithTitle:self.remindButtonLabel];
- // }
-
- [self.visibleAlert beginSheetModalForWindow:[[NSApplication sharedApplication] mainWindow]
- modalDelegate:self
- didEndSelector:@selector(alertDidEnd:returnCode:contextInfo:)
- contextInfo:nil];
- #endif
- //inform about prompt
- if ([self.delegate respondsToSelector:@selector(iRateDidPromptForRating)])
- {
- [self.delegate iRateDidPromptForRating];
- }
- }
- }
- - (void)applicationLaunched
- {
- //check if this is a new version
- NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
- if (![[defaults objectForKey:iRateLastVersionUsedKey] isEqualToString:self.applicationVersion])
- {
- // has the user already rated the app?
- if (self.ratedAnyVersion)
- {
- NSDate *dateOfFirstLaunch = [self firstUsed];
- NSTimeInterval timeSinceFirstLaunch = [[NSDate date] timeIntervalSinceDate:dateOfFirstLaunch];
- NSTimeInterval timeUntilRate = 60 * 60 * 24 * APPIRATER_TIME_BEFORE_REMINDING * (APPIRATER_PROMPT_TIMES_UNTIL_REJECT * 3);
-
- if (timeSinceFirstLaunch >= timeUntilRate)
- {
- [defaults removeObjectForKey:iRateRatedVersionKey];
- [defaults setObject:[NSDate date] forKey:iRateFirstUsedKey];
- }
- } else {
-
- [defaults setObject:[NSDate date] forKey:iRateFirstUsedKey];
-
- // has the user previously declined to rate this version of the app?
- NSInteger declinedCount = [self declinedCount];
- if (declinedCount > APPIRATER_PROMPT_TIMES_UNTIL_REJECT)
- {
- NSDate *dateOfFirstLaunch = [self lastReminded];
- NSTimeInterval timeSinceFirstLaunch = [[NSDate date] timeIntervalSinceDate:dateOfFirstLaunch];
- NSTimeInterval timeUntilRate = 60 * 60 * 24 * APPIRATER_TIME_BEFORE_REMINDING * (APPIRATER_DAYS_UNTIL_PROMPT * 3);
-
- if (timeSinceFirstLaunch >= timeUntilRate)
- {
- [self setDeclinedCount:0];
- [defaults setObject:nil forKey:iRateLastRemindedKey];
- }
- } else {
- [defaults setObject:nil forKey:iRateLastRemindedKey];
- }
- }
-
- //reset counts
- [defaults setObject:self.applicationVersion forKey:iRateLastVersionUsedKey];
- [defaults setInteger:0 forKey:iRateUseCountKey];
- [defaults setInteger:0 forKey:iRateEventCountKey];
- [defaults synchronize];
-
- self.declinedThisVersion = NO;
- //inform about app update
- if ([self.delegate respondsToSelector:@selector(iRateDidDetectAppUpdate)])
- {
- [self.delegate iRateDidDetectAppUpdate];
- }
- }
-
- [self incrementUseCount];
- if (self.promptAtLaunch && [self shouldPromptForRating])
- {
- [self promptIfNetworkAvailable];
- }
- }
- - (void)appLaunched {
- [self incrementUseCount];
- if ([self shouldPromptForRating])
- {
- [self promptIfNetworkAvailable];
- }
- }
- #ifdef __IPHONE_OS_VERSION_MAX_ALLOWED
- - (void)applicationWillEnterForeground:(NSNotification *)notification
- {
- if ([UIApplication sharedApplication].applicationState == UIApplicationStateBackground)
- {
- [self incrementUseCount];
- if (self.promptAtLaunch && [self shouldPromptForRating])
- {
- [self promptIfNetworkAvailable];
- }
- }
- }
- #endif
- #pragma mark -
- #pragma mark UIAlertView methods
- #ifdef __IPHONE_OS_VERSION_MAX_ALLOWED
- - (void)openRatingsPageInAppStore
- {
- if (_displayAppUsingStorekitIfAvailable && [SKStoreProductViewController class])
- {
- if (self.verboseLogging)
- {
- NSLog(@"iRate will attempt to open the StoreKit in-app product page using the following app store ID: %i", self.appStoreID);
- }
-
- //create store view controller
- SKStoreProductViewController *productController = [[SKStoreProductViewController alloc] init];
- productController.delegate = (id<SKStoreProductViewControllerDelegate>)self;
-
- //load product details
- NSDictionary *productParameters = @{SKStoreProductParameterITunesItemIdentifier: [@(_appStoreID) description]};
- [productController loadProductWithParameters:productParameters completionBlock:^(BOOL result, NSError *error) {
-
- if (!result)
- {
- //log the error
- if (error)
- {
- NSLog(@"iRate rating process failed because: %@", [error localizedDescription]);
- }
- else
- {
- NSLog(@"iRate rating process failed because an unknown error occured");
- }
-
- self.ratedThisVersion = NO;
- if ([self.delegate respondsToSelector:@selector(iRateCouldNotConnectToAppStore:)])
- {
- [self.delegate iRateCouldNotConnectToAppStore:error];
- }
- }
- }];
-
- //get root view controller
- UIViewController *rootViewController = nil;
- id appDelegate = [[UIApplication sharedApplication] delegate];
- if ([appDelegate respondsToSelector:@selector(viewController)])
- {
- rootViewController = [appDelegate valueForKey:@"viewController"];
- }
- if (!rootViewController && [appDelegate respondsToSelector:@selector(window)])
- {
- UIWindow *window = [appDelegate valueForKey:@"window"];
- rootViewController = window.rootViewController;
- }
- if (!rootViewController)
- {
- UIWindow *window = [UIApplication sharedApplication].keyWindow;
- rootViewController = window.rootViewController;
- }
- if (!rootViewController)
- {
- if (self.verboseLogging)
- {
- NSLog(@"iRate couldn't find root view controller from which to display StoreKit product page");
- }
- }
- else
- {
- while (rootViewController.presentedViewController)
- {
- rootViewController = rootViewController.presentedViewController;
- }
-
- //present product view controller
- [rootViewController presentViewController:productController animated:YES completion:nil];
- if ([self.delegate respondsToSelector:@selector(iRateDidPresentStoreKitModal)])
- {
- [self.delegate iRateDidPresentStoreKitModal];
- }
- return;
- }
- }
- if (self.verboseLogging)
- {
- NSLog(@"iRate will open the App Store ratings page using the following URL: %@", self.ratingsURL);
- }
-
- [[UIApplication sharedApplication] openURL:self.ratingsURL];
- }
- - (void)productViewControllerDidFinish:(SKStoreProductViewController *)controller
- {
- [controller.presentingViewController dismissViewControllerAnimated:YES completion:NULL];
- if ([self.delegate respondsToSelector:@selector(iRateDidDismissStoreKitModal)])
- {
- [self.delegate iRateDidDismissStoreKitModal];
- }
- }
- - (void)resizeAlertView:(UIAlertView *)alertView
- {
- if (!self.disableAlertViewResizing)
- {
- NSInteger imageCount = 0;
- CGFloat offset = 0.0f;
- CGFloat messageOffset = 0.0f;
- for (UIView *view in alertView.subviews)
- {
- CGRect frame = view.frame;
- if ([view isKindOfClass:[UILabel class]])
- {
- UILabel *label = (UILabel *)view;
- if ([label.text isEqualToString:alertView.title])
- {
- [label sizeToFit];
- offset = label.frame.size.height - fmax(0.0f, 45.f - label.frame.size.height);
- if (label.frame.size.height > frame.size.height)
- {
- offset = messageOffset = label.frame.size.height - frame.size.height;
- frame.size.height = label.frame.size.height;
- }
- }
- else if ([label.text isEqualToString:alertView.message])
- {
- label.lineBreakMode = NSLineBreakByWordWrapping;
- label.numberOfLines = 0;
- label.alpha = 1.0f;
- [label sizeToFit];
- offset += label.frame.size.height - frame.size.height;
- frame.origin.y += messageOffset;
- frame.size.height = label.frame.size.height;
- }
- }
- else if ([view isKindOfClass:[UITextView class]])
- {
- view.alpha = 0.0f;
- }
- else if ([view isKindOfClass:[UIImageView class]])
- {
- if (imageCount++ > 0)
- {
- view.alpha = 0.0f;
- }
- }
- else if ([view isKindOfClass:[UIControl class]])
- {
- frame.origin.y += offset;
- }
- view.frame = frame;
- }
- CGRect frame = alertView.frame;
- frame.origin.y -= roundf(offset/2.0f);
- frame.size.height += offset;
- alertView.frame = frame;
- }
- }
- - (void)willRotate
- {
- [self performSelectorOnMainThread:@selector(didRotate) withObject:nil waitUntilDone:NO];
- }
- - (void)didRotate
- {
- if (self.previousOrientation != [UIApplication sharedApplication].statusBarOrientation)
- {
- self.previousOrientation = [UIApplication sharedApplication].statusBarOrientation;
- [self resizeAlertView:self.visibleAlert];
- }
- }
- - (void)willPresentAlertView:(UIAlertView *)alertView
- {
- [self resizeAlertView:alertView];
- }
- - (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex
- {
- if (buttonIndex == alertView.cancelButtonIndex)
- {
- //ignore this version
- self.declinedThisVersion = YES;
-
- //log event
- if ([self.delegate respondsToSelector:@selector(iRateUserDidDeclineToRateApp)])
- {
- [self.delegate iRateUserDidDeclineToRateApp];
- }
- }
- else if (([self.cancelButtonLabel length] && buttonIndex == 2) ||
- ([self.cancelButtonLabel length] == 0 && buttonIndex == 1))
- {
- //remind later
- self.lastReminded = [NSDate date];
-
- //log event
- if ([self.delegate respondsToSelector:@selector(iRateUserDidRequestReminderToRateApp)])
- {
- [self.delegate iRateUserDidRequestReminderToRateApp];
- }
- }
- else
- {
- //mark as rated
- self.ratedThisVersion = YES;
-
- //log event
- if ([self.delegate respondsToSelector:@selector(iRateUserDidAttemptToRateApp)])
- {
- [self.delegate iRateUserDidAttemptToRateApp];
- }
-
- if (![self.delegate respondsToSelector:@selector(iRateShouldOpenAppStore)] || [_delegate iRateShouldOpenAppStore])
- {
- //go to ratings page
- [self openRatingsPageInAppStore];
- }
- }
-
- //release alert
- self.visibleAlert = nil;
- }
- #else
- - (void)openAppPageWhenAppStoreLaunched
- {
- //check if app store is running
- ProcessSerialNumber psn = { kNoProcess, kNoProcess };
- while (GetNextProcess(&psn) == noErr)
- {
- CFDictionaryRef cfDict = ProcessInformationCopyDictionary(&psn, kProcessDictionaryIncludeAllInformationMask);
- NSString *bundleID = [(__bridge NSDictionary *)cfDict objectForKey:(__bridge NSString *)kCFBundleIdentifierKey];
- if ([iRateMacAppStoreBundleID isEqualToString:[bundleID lowercaseString]])
- {
- //open app page
- [[NSWorkspace sharedWorkspace] performSelector:@selector(openURL:) withObject:self.ratingsURL afterDelay:MAC_APP_STORE_REFRESH_DELAY];
- CFRelease(cfDict);
- return;
- }
- CFRelease(cfDict);
- }
-
- //try again
- [self performSelector:@selector(openAppPageWhenAppStoreLaunched) withObject:nil afterDelay:0.0];
- }
- - (void)openRatingsPageInAppStore
- {
- if (self.verboseLogging)
- {
- NSLog(@"iRate will open the App Store ratings page using the following URL: %@", self.ratingsURL);
- }
-
- [[NSWorkspace sharedWorkspace] openURL:self.ratingsURL];
- [self openAppPageWhenAppStoreLaunched];
- }
- - (void)alertDidEnd:(NSAlert *)alert returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo
- {
- switch (returnCode)
- {
- case NSAlertAlternateReturn:
- {
- //remind later
- self.lastReminded = [NSDate date];
-
- NSInteger declinedCount = [self declinedCount];
- declinedCount++;
- [self setDeclinedCount:declinedCount];
-
- //log event
- if ([self.delegate respondsToSelector:@selector(iRateUserDidDeclineToRateApp)])
- {
- [self.delegate iRateUserDidDeclineToRateApp];
- }
- break;
- }
- case NSAlertDefaultReturn:
- {
- //remind later
- self.lastReminded = [NSDate date];
-
- //mark as rated
- self.ratedThisVersion = YES;
-
- //log event
- if ([self.delegate respondsToSelector:@selector(iRateUserDidAttemptToRateApp)])
- {
- [self.delegate iRateUserDidAttemptToRateApp];
- }
-
- if (![self.delegate respondsToSelector:@selector(iRateShouldOpenAppStore)] || [_delegate iRateShouldOpenAppStore])
- {
- //launch mac app store
- [self openRatingsPageInAppStore];
- }
- break;
- }
- default:
- {
- //remind later
- self.lastReminded = [NSDate date];
-
- //log event
- if ([self.delegate respondsToSelector:@selector(iRateUserDidRequestReminderToRateApp)])
- {
- [self.delegate iRateUserDidRequestReminderToRateApp];
- }
- }
- }
-
- //release alert
- self.visibleAlert = nil;
- }
- #endif
- - (void)logEvent:(BOOL)deferPrompt
- {
- [self incrementEventCount];
- if (!deferPrompt && [self shouldPromptForRating])
- {
- [self promptIfNetworkAvailable];
- }
- }
- - (void)remindMeLater
- {
- //remind later
- self.lastReminded = [NSDate date];
-
- NSInteger declinedCount = [self declinedCount];
- declinedCount++;
- [self setDeclinedCount:declinedCount];
-
- //log event
- if ([self.delegate respondsToSelector:@selector(iRateUserDidDeclineToRateApp)])
- {
- [self.delegate iRateUserDidDeclineToRateApp];
- }
- }
- @end
|