iRate.m 43 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237
  1. //
  2. // iRate.m
  3. //
  4. // Version 1.11.6
  5. //
  6. // Created by Nick Lockwood on 26/01/2011.
  7. // Copyright 2011 Charcoal Design
  8. //
  9. // Distributed under the permissive zlib license
  10. // Get the latest version from here:
  11. //
  12. // https://github.com/nicklockwood/iRate
  13. //
  14. // This software is provided 'as-is', without any express or implied
  15. // warranty. In no event will the authors be held liable for any damages
  16. // arising from the use of this software.
  17. //
  18. // Permission is granted to anyone to use this software for any purpose,
  19. // including commercial applications, and to alter it and redistribute it
  20. // freely, subject to the following restrictions:
  21. //
  22. // 1. The origin of this software must not be misrepresented; you must not
  23. // claim that you wrote the original software. If you use this software
  24. // in a product, an acknowledgment in the product documentation would be
  25. // appreciated but is not required.
  26. //
  27. // 2. Altered source versions must be plainly marked as such, and must not be
  28. // misrepresented as being the original software.
  29. //
  30. // 3. This notice may not be removed or altered from any source distribution.
  31. //
  32. #import "iRate.h"
  33. #import <Availability.h>
  34. #if !__has_feature(objc_arc)
  35. #error This class requires automatic reference counting
  36. #endif
  37. #pragma clang diagnostic ignored "-Wreceiver-is-weak"
  38. #pragma clang diagnostic ignored "-Warc-repeated-use-of-weak"
  39. #pragma clang diagnostic ignored "-Wobjc-missing-property-synthesis"
  40. #pragma clang diagnostic ignored "-Wdirect-ivar-access"
  41. #pragma clang diagnostic ignored "-Wpartial-availability"
  42. #pragma clang diagnostic ignored "-Wunused-macros"
  43. #pragma clang diagnostic ignored "-Wconversion"
  44. #pragma clang diagnostic ignored "-Wformat-nonliteral"
  45. #pragma clang diagnostic ignored "-Wdouble-promotion"
  46. #pragma clang diagnostic ignored "-Wselector"
  47. #pragma clang diagnostic ignored "-Wgnu"
  48. NSUInteger const iRateAppStoreGameGenreID = 6014;
  49. NSString *const iRateErrorDomain = @"iRateErrorDomain";
  50. NSString *const iRateMessageTitleKey = @"iRateMessageTitle";
  51. NSString *const iRateAppMessageKey = @"iRateAppMessage";
  52. NSString *const iRateGameMessageKey = @"iRateGameMessage";
  53. NSString *const iRateUpdateMessageKey = @"iRateUpdateMessage";
  54. NSString *const iRateCancelButtonKey = @"iRateCancelButton";
  55. NSString *const iRateRemindButtonKey = @"iRateRemindButton";
  56. NSString *const iRateRateButtonKey = @"iRateRateButton";
  57. NSString *const iRateCouldNotConnectToAppStore = @"iRateCouldNotConnectToAppStore";
  58. NSString *const iRateDidDetectAppUpdate = @"iRateDidDetectAppUpdate";
  59. NSString *const iRateDidPromptForRating = @"iRateDidPromptForRating";
  60. NSString *const iRateUserDidAttemptToRateApp = @"iRateUserDidAttemptToRateApp";
  61. NSString *const iRateUserDidDeclineToRateApp = @"iRateUserDidDeclineToRateApp";
  62. NSString *const iRateUserDidRequestReminderToRateApp = @"iRateUserDidRequestReminderToRateApp";
  63. NSString *const iRateDidOpenAppStore = @"iRateDidOpenAppStore";
  64. static NSString *const iRateAppStoreIDKey = @"iRateAppStoreID";
  65. static NSString *const iRateRatedVersionKey = @"iRateRatedVersionChecked";
  66. static NSString *const iRateDeclinedVersionKey = @"iRateDeclinedVersion";
  67. static NSString *const iRateLastRemindedKey = @"iRateLastReminded";
  68. static NSString *const iRateLastVersionUsedKey = @"iRateLastVersionUsed";
  69. static NSString *const iRateFirstUsedKey = @"iRateFirstUsed";
  70. static NSString *const iRateUseCountKey = @"iRateUseCount";
  71. static NSString *const iRateEventCountKey = @"iRateEventCount";
  72. static NSString *const iRateMacAppStoreBundleID = @"com.apple.appstore";
  73. static NSString *const iRateAppLookupURLFormat = @"https://itunes.apple.com/%@/lookup";
  74. static NSString *const iRateiOSAppStoreURLScheme = @"itms-apps";
  75. static NSString *const iRateiOSAppStoreURLFormat = @"itms-apps://itunes.apple.com/WebObjects/MZStore.woa/wa/viewContentsUserReviews?type=Purple+Software&id=%@&pageNumber=0&sortOrdering=2&mt=8";
  76. static NSString *const iRateiOS7AppStoreURLFormat = @"itms-apps://itunes.apple.com/app/id%@";
  77. static NSString *const iRateMacAppStoreURLFormat = @"macappstore://itunes.apple.com/app/id%@";
  78. #define IOS_7_0 7.0
  79. #define IOS_7_1 7.1
  80. #define SECONDS_IN_A_DAY 86400.0
  81. #define SECONDS_IN_A_WEEK 604800.0
  82. #define MAC_APP_STORE_REFRESH_DELAY 5.0
  83. #define REQUEST_TIMEOUT 60.0
  84. @interface iRate()
  85. @property (nonatomic, strong) id visibleAlert;
  86. @property (nonatomic, assign) BOOL checkingForPrompt;
  87. @property (nonatomic, assign) BOOL checkingForAppStoreID;
  88. @end
  89. @implementation iRate
  90. + (void)load
  91. {
  92. dispatch_async(dispatch_get_main_queue(), ^{
  93. [self sharedInstance];
  94. });
  95. }
  96. + (instancetype)sharedInstance
  97. {
  98. static dispatch_once_t once;
  99. static iRate *sharedInstance = nil;
  100. dispatch_once(&once, ^{
  101. sharedInstance = [(iRate *)[self alloc] init];
  102. });
  103. return sharedInstance;
  104. }
  105. - (NSString *)localizedStringForKey:(NSString *)key withDefault:(NSString *)defaultString
  106. {
  107. static NSBundle *bundle = nil;
  108. if (bundle == nil)
  109. {
  110. NSString *bundlePath = [[NSBundle bundleForClass:[iRate class]] pathForResource:@"iRate" ofType:@"bundle"];
  111. if (self.useAllAvailableLanguages)
  112. {
  113. bundle = [NSBundle bundleWithPath:bundlePath];
  114. NSString *language = [[NSLocale preferredLanguages] count]? [NSLocale preferredLanguages][0]: @"en";
  115. if (![[bundle localizations] containsObject:language])
  116. {
  117. language = [language componentsSeparatedByString:@"-"][0];
  118. }
  119. if ([[bundle localizations] containsObject:language])
  120. {
  121. bundlePath = [bundle pathForResource:language ofType:@"lproj"];
  122. }
  123. }
  124. bundle = [NSBundle bundleWithPath:bundlePath] ?: [NSBundle mainBundle];
  125. }
  126. defaultString = [bundle localizedStringForKey:key value:defaultString table:nil];
  127. return [[NSBundle mainBundle] localizedStringForKey:key value:defaultString table:nil];
  128. }
  129. - (iRate *)init
  130. {
  131. if ((self = [super init]))
  132. {
  133. #if TARGET_OS_IPHONE
  134. //register for iphone application events
  135. if (&UIApplicationWillEnterForegroundNotification)
  136. {
  137. [[NSNotificationCenter defaultCenter] addObserver:self
  138. selector:@selector(applicationWillEnterForeground)
  139. name:UIApplicationWillEnterForegroundNotification
  140. object:nil];
  141. }
  142. #endif
  143. //get country
  144. self.appStoreCountry = [(NSLocale *)[NSLocale currentLocale] objectForKey:NSLocaleCountryCode];
  145. if ([self.appStoreCountry isEqualToString:@"150"])
  146. {
  147. self.appStoreCountry = @"eu";
  148. }
  149. else if (!self.appStoreCountry || [[self.appStoreCountry stringByReplacingOccurrencesOfString:@"[A-Za-z]{2}" withString:@"" options:NSRegularExpressionSearch range:NSMakeRange(0, 2)] length])
  150. {
  151. self.appStoreCountry = @"us";
  152. }
  153. else if ([self.appStoreCountry isEqualToString:@"GI"])
  154. {
  155. self.appStoreCountry = @"GB";
  156. }
  157. //application version (use short version preferentially)
  158. self.applicationVersion = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"];
  159. if ([self.applicationVersion length] == 0)
  160. {
  161. self.applicationVersion = [[NSBundle mainBundle] objectForInfoDictionaryKey:(__bridge NSString *)kCFBundleVersionKey];
  162. }
  163. //localised application name
  164. self.applicationName = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleDisplayName"];
  165. if ([self.applicationName length] == 0)
  166. {
  167. self.applicationName = [[NSBundle mainBundle] objectForInfoDictionaryKey:(__bridge NSString *)kCFBundleNameKey];
  168. }
  169. //bundle id
  170. self.applicationBundleID = [[NSBundle mainBundle] bundleIdentifier];
  171. //default settings
  172. self.useAllAvailableLanguages = YES;
  173. self.promptForNewVersionIfUserRated = NO;
  174. self.onlyPromptIfLatestVersion = YES;
  175. self.onlyPromptIfMainWindowIsAvailable = YES;
  176. self.promptAtLaunch = YES;
  177. self.usesUntilPrompt = 10;
  178. self.eventsUntilPrompt = 10;
  179. self.daysUntilPrompt = 10.0;
  180. self.usesPerWeekForPrompt = 0.0;
  181. self.remindPeriod = 1.0;
  182. self.verboseLogging = NO;
  183. self.previewMode = NO;
  184. #if DEBUG
  185. //enable verbose logging in debug mode
  186. self.verboseLogging = YES;
  187. NSLog(@"iRate verbose logging enabled.");
  188. #endif
  189. //app launched
  190. dispatch_async(dispatch_get_main_queue(), ^{
  191. [self applicationLaunched];
  192. });
  193. }
  194. return self;
  195. }
  196. - (id<iRateDelegate>)delegate
  197. {
  198. if (_delegate == nil)
  199. {
  200. #if TARGET_OS_IPHONE
  201. #define APP_CLASS UIApplication
  202. #else
  203. #define APP_CLASS NSApplication
  204. #endif
  205. _delegate = (id<iRateDelegate>)[(APP_CLASS *)[APP_CLASS sharedApplication] delegate];
  206. }
  207. return _delegate;
  208. }
  209. - (NSString *)messageTitle
  210. {
  211. return [_messageTitle ?: [self localizedStringForKey:iRateMessageTitleKey withDefault:@"Rate %@"] stringByReplacingOccurrencesOfString:@"%@" withString:self.applicationName];
  212. }
  213. - (NSString *)message
  214. {
  215. NSString *message = _message;
  216. if (!message)
  217. {
  218. message = (self.appStoreGenreID == iRateAppStoreGameGenreID)? [self localizedStringForKey:iRateGameMessageKey withDefault:@"If you enjoy playing %@, would you mind taking a moment to rate it? It won’t take more than a minute. Thanks for your support!"]: [self localizedStringForKey:iRateAppMessageKey withDefault:@"If you enjoy using %@, would you mind taking a moment to rate it? It won’t take more than a minute. Thanks for your support!"];
  219. }
  220. return [message stringByReplacingOccurrencesOfString:@"%@" withString:self.applicationName];
  221. }
  222. - (NSString *)updateMessage
  223. {
  224. NSString *updateMessage = _updateMessage;
  225. if (!updateMessage)
  226. {
  227. updateMessage = [self localizedStringForKey:iRateUpdateMessageKey withDefault:self.message];
  228. }
  229. return [updateMessage stringByReplacingOccurrencesOfString:@"%@" withString:self.applicationName];
  230. }
  231. - (NSString *)cancelButtonLabel
  232. {
  233. return _cancelButtonLabel ?: [self localizedStringForKey:iRateCancelButtonKey withDefault:@"No, Thanks"];
  234. }
  235. - (NSString *)rateButtonLabel
  236. {
  237. return _rateButtonLabel ?: [self localizedStringForKey:iRateRateButtonKey withDefault:@"Rate It Now"];
  238. }
  239. - (NSString *)remindButtonLabel
  240. {
  241. return _remindButtonLabel ?: [self localizedStringForKey:iRateRemindButtonKey withDefault:@"Remind Me Later"];
  242. }
  243. - (NSURL *)ratingsURL
  244. {
  245. if (_ratingsURL)
  246. {
  247. return _ratingsURL;
  248. }
  249. if (!self.appStoreID && self.verboseLogging)
  250. {
  251. 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.");
  252. }
  253. NSString *URLString;
  254. #if TARGET_OS_IPHONE
  255. float iOSVersion = [[UIDevice currentDevice].systemVersion floatValue];
  256. if (iOSVersion >= IOS_7_0 && iOSVersion < IOS_7_1)
  257. {
  258. URLString = iRateiOS7AppStoreURLFormat;
  259. }
  260. else
  261. {
  262. URLString = iRateiOSAppStoreURLFormat;
  263. }
  264. #else
  265. URLString = iRateMacAppStoreURLFormat;
  266. #endif
  267. return [NSURL URLWithString:[NSString stringWithFormat:URLString, @(self.appStoreID)]];
  268. }
  269. - (NSUInteger)appStoreID
  270. {
  271. return _appStoreID ?: [[[NSUserDefaults standardUserDefaults] objectForKey:iRateAppStoreIDKey] unsignedIntegerValue];
  272. }
  273. - (NSDate *)firstUsed
  274. {
  275. return [[NSUserDefaults standardUserDefaults] objectForKey:iRateFirstUsedKey];
  276. }
  277. - (void)setFirstUsed:(NSDate *)date
  278. {
  279. [[NSUserDefaults standardUserDefaults] setObject:date forKey:iRateFirstUsedKey];
  280. [[NSUserDefaults standardUserDefaults] synchronize];
  281. }
  282. - (NSDate *)lastReminded
  283. {
  284. return [[NSUserDefaults standardUserDefaults] objectForKey:iRateLastRemindedKey];
  285. }
  286. - (void)setLastReminded:(NSDate *)date
  287. {
  288. [[NSUserDefaults standardUserDefaults] setObject:date forKey:iRateLastRemindedKey];
  289. [[NSUserDefaults standardUserDefaults] synchronize];
  290. }
  291. - (NSUInteger)usesCount
  292. {
  293. return [[NSUserDefaults standardUserDefaults] integerForKey:iRateUseCountKey];
  294. }
  295. - (void)setUsesCount:(NSUInteger)count
  296. {
  297. [[NSUserDefaults standardUserDefaults] setInteger:(NSInteger)count forKey:iRateUseCountKey];
  298. [[NSUserDefaults standardUserDefaults] synchronize];
  299. }
  300. - (NSUInteger)eventCount
  301. {
  302. return [[NSUserDefaults standardUserDefaults] integerForKey:iRateEventCountKey];
  303. }
  304. - (void)setEventCount:(NSUInteger)count
  305. {
  306. [[NSUserDefaults standardUserDefaults] setInteger:(NSInteger)count forKey:iRateEventCountKey];
  307. [[NSUserDefaults standardUserDefaults] synchronize];
  308. }
  309. - (float)usesPerWeek
  310. {
  311. return (float)self.usesCount / ([[NSDate date] timeIntervalSinceDate:self.firstUsed] / SECONDS_IN_A_WEEK);
  312. }
  313. - (BOOL)declinedThisVersion
  314. {
  315. return [[[NSUserDefaults standardUserDefaults] objectForKey:iRateDeclinedVersionKey] isEqualToString:self.applicationVersion];
  316. }
  317. - (void)setDeclinedThisVersion:(BOOL)declined
  318. {
  319. [[NSUserDefaults standardUserDefaults] setObject:(declined? self.applicationVersion: nil) forKey:iRateDeclinedVersionKey];
  320. [[NSUserDefaults standardUserDefaults] synchronize];
  321. }
  322. - (BOOL)declinedAnyVersion
  323. {
  324. return [(NSString *)[[NSUserDefaults standardUserDefaults] objectForKey:iRateDeclinedVersionKey] length] != 0;
  325. }
  326. - (BOOL)ratedVersion:(NSString *)version
  327. {
  328. return [[[NSUserDefaults standardUserDefaults] objectForKey:iRateRatedVersionKey] isEqualToString:version];
  329. }
  330. - (BOOL)ratedThisVersion
  331. {
  332. return [self ratedVersion:self.applicationVersion];
  333. }
  334. - (void)setRatedThisVersion:(BOOL)rated
  335. {
  336. [[NSUserDefaults standardUserDefaults] setObject:(rated? self.applicationVersion: nil) forKey:iRateRatedVersionKey];
  337. [[NSUserDefaults standardUserDefaults] synchronize];
  338. }
  339. - (BOOL)ratedAnyVersion
  340. {
  341. return [(NSString *)[[NSUserDefaults standardUserDefaults] objectForKey:iRateRatedVersionKey] length] != 0;
  342. }
  343. - (void)dealloc
  344. {
  345. [[NSNotificationCenter defaultCenter] removeObserver:self];
  346. }
  347. - (void)incrementUseCount
  348. {
  349. self.usesCount ++;
  350. }
  351. - (void)incrementEventCount
  352. {
  353. self.eventCount ++;
  354. }
  355. - (BOOL)shouldPromptForRating
  356. {
  357. //preview mode?
  358. if (self.previewMode)
  359. {
  360. NSLog(@"iRate preview mode is enabled - make sure you disable this for release");
  361. return YES;
  362. }
  363. //check if we've rated this version
  364. else if (self.ratedThisVersion)
  365. {
  366. if (self.verboseLogging)
  367. {
  368. NSLog(@"iRate did not prompt for rating because the user has already rated this version");
  369. }
  370. return NO;
  371. }
  372. //check if we've rated any version
  373. else if (self.ratedAnyVersion && !self.promptForNewVersionIfUserRated)
  374. {
  375. if (self.verboseLogging)
  376. {
  377. NSLog(@"iRate did not prompt for rating because the user has already rated this app, and promptForNewVersionIfUserRated is disabled");
  378. }
  379. return NO;
  380. }
  381. //check if we've declined to rate the app
  382. else if (self.declinedAnyVersion)
  383. {
  384. if (self.verboseLogging)
  385. {
  386. NSLog(@"iRate did not prompt for rating because the user has declined to rate the app");
  387. }
  388. return NO;
  389. }
  390. //check how long we've been using this version
  391. else if ([[NSDate date] timeIntervalSinceDate:self.firstUsed] < self.daysUntilPrompt * SECONDS_IN_A_DAY)
  392. {
  393. if (self.verboseLogging)
  394. {
  395. NSLog(@"iRate did not prompt for rating because the app was first used less than %g days ago", self.daysUntilPrompt);
  396. }
  397. return NO;
  398. }
  399. //check how many times we've used it and the number of significant events
  400. else if (self.usesCount < self.usesUntilPrompt && self.eventCount < self.eventsUntilPrompt)
  401. {
  402. if (self.verboseLogging)
  403. {
  404. NSLog(@"iRate did not prompt for rating because the app has only been used %@ times and only %@ events have been logged", @(self.usesCount), @(self.eventCount));
  405. }
  406. return NO;
  407. }
  408. //check if usage frequency is high enough
  409. else if (self.usesPerWeek < self.usesPerWeekForPrompt)
  410. {
  411. if (self.verboseLogging)
  412. {
  413. 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);
  414. }
  415. return NO;
  416. }
  417. //check if within the reminder period
  418. else if (self.lastReminded != nil && [[NSDate date] timeIntervalSinceDate:self.lastReminded] < self.remindPeriod * SECONDS_IN_A_DAY)
  419. {
  420. if (self.verboseLogging)
  421. {
  422. NSLog(@"iRate did not prompt for rating because the user last asked to be reminded less than %g days ago", self.remindPeriod);
  423. }
  424. return NO;
  425. }
  426. //lets prompt!
  427. return YES;
  428. }
  429. - (NSString *)valueForKey:(NSString *)key inJSON:(id)json
  430. {
  431. if ([json isKindOfClass:[NSString class]])
  432. {
  433. //use legacy parser
  434. NSRange keyRange = [json rangeOfString:[NSString stringWithFormat:@"\"%@\"", key]];
  435. if (keyRange.location != NSNotFound)
  436. {
  437. NSInteger start = keyRange.location + keyRange.length;
  438. NSRange valueStart = [json rangeOfString:@":" options:(NSStringCompareOptions)0 range:NSMakeRange(start, [(NSString *)json length] - start)];
  439. if (valueStart.location != NSNotFound)
  440. {
  441. start = valueStart.location + 1;
  442. NSRange valueEnd = [json rangeOfString:@"," options:(NSStringCompareOptions)0 range:NSMakeRange(start, [(NSString *)json length] - start)];
  443. if (valueEnd.location != NSNotFound)
  444. {
  445. NSString *value = [json substringWithRange:NSMakeRange(start, valueEnd.location - start)];
  446. value = [value stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
  447. while ([value hasPrefix:@"\""] && ![value hasSuffix:@"\""])
  448. {
  449. if (valueEnd.location == NSNotFound)
  450. {
  451. break;
  452. }
  453. NSInteger newStart = valueEnd.location + 1;
  454. valueEnd = [json rangeOfString:@"," options:(NSStringCompareOptions)0 range:NSMakeRange(newStart, [(NSString *)json length] - newStart)];
  455. value = [json substringWithRange:NSMakeRange(start, valueEnd.location - start)];
  456. value = [value stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
  457. }
  458. value = [value stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"\""]];
  459. value = [value stringByReplacingOccurrencesOfString:@"\\\\" withString:@"\\"];
  460. value = [value stringByReplacingOccurrencesOfString:@"\\/" withString:@"/"];
  461. value = [value stringByReplacingOccurrencesOfString:@"\\\"" withString:@"\""];
  462. value = [value stringByReplacingOccurrencesOfString:@"\\n" withString:@"\n"];
  463. value = [value stringByReplacingOccurrencesOfString:@"\\r" withString:@"\r"];
  464. value = [value stringByReplacingOccurrencesOfString:@"\\t" withString:@"\t"];
  465. value = [value stringByReplacingOccurrencesOfString:@"\\f" withString:@"\f"];
  466. value = [value stringByReplacingOccurrencesOfString:@"\\b" withString:@"\f"];
  467. while (YES)
  468. {
  469. NSRange unicode = [value rangeOfString:@"\\u"];
  470. if (unicode.location == NSNotFound || unicode.location + unicode.length == 0)
  471. {
  472. break;
  473. }
  474. uint32_t c = 0;
  475. NSString *hex = [value substringWithRange:NSMakeRange(unicode.location + 2, 4)];
  476. NSScanner *scanner = [NSScanner scannerWithString:hex];
  477. [scanner scanHexInt:&c];
  478. if (c <= 0xffff)
  479. {
  480. value = [value stringByReplacingCharactersInRange:NSMakeRange(unicode.location, 6) withString:[NSString stringWithFormat:@"%C", (unichar)c]];
  481. }
  482. else
  483. {
  484. //convert character to surrogate pair
  485. uint16_t x = (uint16_t)c;
  486. uint16_t u = (c >> 16) & ((1 << 5) - 1);
  487. uint16_t w = (uint16_t)u - 1;
  488. unichar high = 0xd800 | (w << 6) | x >> 10;
  489. unichar low = (uint16_t)(0xdc00 | (x & ((1 << 10) - 1)));
  490. value = [value stringByReplacingCharactersInRange:NSMakeRange(unicode.location, 6) withString:[NSString stringWithFormat:@"%C%C", high, low]];
  491. }
  492. }
  493. return value;
  494. }
  495. }
  496. }
  497. }
  498. else
  499. {
  500. return json[key];
  501. }
  502. return nil;
  503. }
  504. - (void)setAppStoreIDOnMainThread:(NSString *)appStoreIDString
  505. {
  506. _appStoreID = [appStoreIDString integerValue];
  507. [[NSUserDefaults standardUserDefaults] setInteger:_appStoreID forKey:iRateAppStoreIDKey];
  508. [[NSUserDefaults standardUserDefaults] synchronize];
  509. }
  510. - (void)connectionSucceeded
  511. {
  512. if (self.checkingForAppStoreID)
  513. {
  514. //no longer checking
  515. self.checkingForPrompt = NO;
  516. self.checkingForAppStoreID = NO;
  517. //open app store
  518. [self openRatingsPageInAppStore];
  519. }
  520. else if (self.checkingForPrompt)
  521. {
  522. //no longer checking
  523. self.checkingForPrompt = NO;
  524. //confirm with delegate
  525. if ([self.delegate respondsToSelector:@selector(iRateShouldPromptForRating)] && ![self.delegate iRateShouldPromptForRating])
  526. {
  527. if (self.verboseLogging)
  528. {
  529. NSLog(@"iRate did not display the rating prompt because the iRateShouldPromptForRating delegate method returned NO");
  530. }
  531. return;
  532. }
  533. //prompt user
  534. [self promptForRating];
  535. }
  536. }
  537. - (void)connectionError:(NSError *)error
  538. {
  539. if (self.checkingForPrompt || self.checkingForAppStoreID)
  540. {
  541. //no longer checking
  542. self.checkingForPrompt = NO;
  543. self.checkingForAppStoreID = NO;
  544. //log the error
  545. if (error)
  546. {
  547. NSLog(@"iRate rating process failed because: %@", [error localizedDescription]);
  548. }
  549. else
  550. {
  551. NSLog(@"iRate rating process failed because an unknown error occured");
  552. }
  553. //could not connect
  554. if ([self.delegate respondsToSelector:@selector(iRateCouldNotConnectToAppStore:)])
  555. {
  556. [self.delegate iRateCouldNotConnectToAppStore:error];
  557. }
  558. [[NSNotificationCenter defaultCenter] postNotificationName:iRateCouldNotConnectToAppStore
  559. object:error];
  560. }
  561. }
  562. - (void)checkForConnectivityInBackground
  563. {
  564. #if (defined(__IPHONE_OS_VERSION_MIN_REQUIRED) && __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_8_0) \
  565. || (defined(__MAC_OS_X_VERSION_MIN_REQUIRED) && __MAC_OS_X_VERSION_MIN_REQUIRED >= __MAC_10_10)
  566. #define IRATE_BACKGROUND_QUEUE QOS_CLASS_BACKGROUND
  567. #else
  568. #define IRATE_BACKGROUND_QUEUE DISPATCH_QUEUE_PRIORITY_BACKGROUND
  569. #endif
  570. dispatch_async(dispatch_get_global_queue(IRATE_BACKGROUND_QUEUE, 0), ^{
  571. [self checkForConnectivity];
  572. });
  573. }
  574. - (void)checkForConnectivity
  575. {
  576. @autoreleasepool
  577. {
  578. //first check iTunes
  579. NSString *iTunesServiceURL = [NSString stringWithFormat:iRateAppLookupURLFormat, self.appStoreCountry];
  580. if (_appStoreID) //important that we check ivar and not getter in case it has changed
  581. {
  582. iTunesServiceURL = [iTunesServiceURL stringByAppendingFormat:@"?id=%@", @(_appStoreID)];
  583. }
  584. else
  585. {
  586. iTunesServiceURL = [iTunesServiceURL stringByAppendingFormat:@"?bundleId=%@", self.applicationBundleID];
  587. }
  588. if (self.verboseLogging)
  589. {
  590. NSLog(@"iRate is checking %@ to retrieve the App Store details...", iTunesServiceURL);
  591. }
  592. NSError *error = nil;
  593. NSURLResponse *response = nil;
  594. NSURL *url = [NSURL URLWithString:iTunesServiceURL];
  595. NSURLRequest *request = [NSURLRequest requestWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:REQUEST_TIMEOUT];
  596. #pragma clang diagnostic push
  597. #pragma clang diagnostic ignored "-Wdeprecated-declarations"
  598. NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
  599. #pragma clang diagnostic pop
  600. NSInteger statusCode = ((NSHTTPURLResponse *)response).statusCode;
  601. if (data && statusCode == 200)
  602. {
  603. //in case error is garbage...
  604. error = nil;
  605. id json = nil;
  606. if ([NSJSONSerialization class])
  607. {
  608. json = [[NSJSONSerialization JSONObjectWithData:data options:(NSJSONReadingOptions)0 error:&error][@"results"] lastObject];
  609. }
  610. else
  611. {
  612. //convert to string
  613. json = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
  614. }
  615. if (!error)
  616. {
  617. //check bundle ID matches
  618. NSString *bundleID = [self valueForKey:@"bundleId" inJSON:json];
  619. if (bundleID)
  620. {
  621. if ([bundleID isEqualToString:self.applicationBundleID])
  622. {
  623. //get genre
  624. if (self.appStoreGenreID == 0)
  625. {
  626. self.appStoreGenreID = [[self valueForKey:@"primaryGenreId" inJSON:json] integerValue];
  627. }
  628. //get app id
  629. if (!_appStoreID)
  630. {
  631. NSString *appStoreIDString = [self valueForKey:@"trackId" inJSON:json];
  632. dispatch_sync(dispatch_get_main_queue(), ^{
  633. [self setAppStoreIDOnMainThread:appStoreIDString];
  634. });
  635. if (self.verboseLogging)
  636. {
  637. NSLog(@"iRate found the app on iTunes. The App Store ID is %@", appStoreIDString);
  638. }
  639. }
  640. //check version
  641. if (self.onlyPromptIfLatestVersion && !self.previewMode)
  642. {
  643. NSString *latestVersion = [self valueForKey:@"version" inJSON:json];
  644. if ([latestVersion compare:self.applicationVersion options:NSNumericSearch] == NSOrderedDescending)
  645. {
  646. if (self.verboseLogging)
  647. {
  648. NSLog(@"iRate found that the installed application version (%@) is not the latest version on the App Store, which is %@", self.applicationVersion, latestVersion);
  649. }
  650. error = [NSError errorWithDomain:iRateErrorDomain code:iRateErrorApplicationIsNotLatestVersion userInfo:@{NSLocalizedDescriptionKey: @"Installed app is not the latest version available"}];
  651. }
  652. }
  653. }
  654. else
  655. {
  656. if (self.verboseLogging)
  657. {
  658. 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 (%@)", self.applicationBundleID, bundleID, @(self.appStoreID));
  659. }
  660. error = [NSError errorWithDomain:iRateErrorDomain code:iRateErrorBundleIdDoesNotMatchAppStore userInfo:@{NSLocalizedDescriptionKey: [NSString stringWithFormat:@"Application bundle ID does not match expected value of %@", bundleID]}];
  661. }
  662. }
  663. else if (_appStoreID || !self.ratingsURL)
  664. {
  665. if (self.verboseLogging)
  666. {
  667. 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");
  668. }
  669. if (!self.previewMode)
  670. {
  671. error = [NSError errorWithDomain:iRateErrorDomain
  672. code:iRateErrorApplicationNotFoundOnAppStore
  673. userInfo:@{NSLocalizedDescriptionKey: @"The application could not be found on the App Store."}];
  674. }
  675. }
  676. else if (!_appStoreID && self.verboseLogging)
  677. {
  678. 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");
  679. }
  680. }
  681. }
  682. else if (statusCode >= 400)
  683. {
  684. //http error
  685. NSString *message = [NSString stringWithFormat:@"The server returned a %@ error", @(statusCode)];
  686. error = [NSError errorWithDomain:@"HTTPResponseErrorDomain" code:statusCode userInfo:@{NSLocalizedDescriptionKey: message}];
  687. }
  688. //handle errors (ignoring sandbox issues)
  689. if (error && !(error.code == EPERM && [error.domain isEqualToString:NSPOSIXErrorDomain] && _appStoreID))
  690. {
  691. dispatch_async(dispatch_get_main_queue(), ^{
  692. [self connectionError:error];
  693. });
  694. }
  695. else if (self.appStoreID || self.previewMode)
  696. {
  697. //show prompt
  698. dispatch_async(dispatch_get_main_queue(), ^{
  699. [self connectionSucceeded];
  700. });
  701. }
  702. }
  703. }
  704. - (void)promptIfNetworkAvailable
  705. {
  706. if (!self.checkingForPrompt && !self.checkingForAppStoreID)
  707. {
  708. self.checkingForPrompt = YES;
  709. [self checkForConnectivityInBackground];
  710. }
  711. }
  712. - (void)promptIfAllCriteriaMet
  713. {
  714. if ([self shouldPromptForRating])
  715. {
  716. [self promptIfNetworkAvailable];
  717. }
  718. }
  719. - (BOOL)showRemindButton
  720. {
  721. return [self.remindButtonLabel length];
  722. }
  723. - (BOOL)showCancelButton
  724. {
  725. return [self.cancelButtonLabel length];
  726. }
  727. - (void)promptForRating
  728. {
  729. if (!self.visibleAlert)
  730. {
  731. NSString *message = self.ratedAnyVersion? self.updateMessage: self.message;
  732. #if TARGET_OS_IPHONE
  733. UIViewController *topController = [UIApplication sharedApplication].delegate.window.rootViewController;
  734. while (topController.presentedViewController)
  735. {
  736. topController = topController.presentedViewController;
  737. }
  738. if ([UIAlertController class] && topController && self.useUIAlertControllerIfAvailable)
  739. {
  740. UIAlertController *alert = [UIAlertController alertControllerWithTitle:self.messageTitle message:message preferredStyle:UIAlertControllerStyleAlert];
  741. //rate action
  742. [alert addAction:[UIAlertAction actionWithTitle:self.rateButtonLabel style:UIAlertActionStyleDefault handler:^(__unused UIAlertAction *action) {
  743. [self didDismissAlert:alert withButtonAtIndex:0];
  744. }]];
  745. //cancel action
  746. if ([self showCancelButton])
  747. {
  748. [alert addAction:[UIAlertAction actionWithTitle:self.cancelButtonLabel style:UIAlertActionStyleCancel handler:^(__unused UIAlertAction *action) {
  749. [self didDismissAlert:alert withButtonAtIndex:1];
  750. }]];
  751. }
  752. //remind action
  753. if ([self showRemindButton])
  754. {
  755. [alert addAction:[UIAlertAction actionWithTitle:self.remindButtonLabel style:UIAlertActionStyleDefault handler:^(__unused UIAlertAction *action) {
  756. [self didDismissAlert:alert withButtonAtIndex:[self showCancelButton]? 2: 1];
  757. }]];
  758. }
  759. self.visibleAlert = alert;
  760. //get current view controller and present alert
  761. [topController presentViewController:alert animated:YES completion:NULL];
  762. }
  763. else
  764. {
  765. #pragma clang diagnostic push
  766. #pragma clang diagnostic ignored "-Wdeprecated-declarations"
  767. UIAlertView *alert = [[UIAlertView alloc] initWithTitle:self.messageTitle
  768. message:message
  769. delegate:(id<UIAlertViewDelegate>)self
  770. cancelButtonTitle:nil
  771. otherButtonTitles:self.rateButtonLabel, nil];
  772. #pragma clang diagnostic pop
  773. if ([self showCancelButton])
  774. {
  775. [alert addButtonWithTitle:self.cancelButtonLabel];
  776. alert.cancelButtonIndex = 1;
  777. }
  778. if ([self showRemindButton])
  779. {
  780. [alert addButtonWithTitle:self.remindButtonLabel];
  781. }
  782. self.visibleAlert = alert;
  783. [self.visibleAlert show];
  784. }
  785. #else
  786. //only show when main window is available
  787. if (self.onlyPromptIfMainWindowIsAvailable && ![[NSApplication sharedApplication] mainWindow])
  788. {
  789. [self performSelector:@selector(promptForRating) withObject:nil afterDelay:0.5];
  790. return;
  791. }
  792. NSAlert *alert = [[NSAlert alloc] init];
  793. alert.messageText = self.messageTitle;
  794. alert.informativeText = message;
  795. [alert addButtonWithTitle:self.rateButtonLabel];
  796. if ([self showCancelButton])
  797. {
  798. [alert addButtonWithTitle:self.cancelButtonLabel];
  799. }
  800. if ([self showRemindButton])
  801. {
  802. [alert addButtonWithTitle:self.remindButtonLabel];
  803. }
  804. self.visibleAlert = alert;
  805. #if __MAC_OS_X_VERSION_MIN_REQUIRED < __MAC_10_9
  806. if (![alert respondsToSelector:@selector(beginSheetModalForWindow:completionHandler:)])
  807. {
  808. [alert beginSheetModalForWindow:(__nonnull id)[NSApplication sharedApplication].mainWindow
  809. modalDelegate:self
  810. didEndSelector:@selector(alertDidEnd:returnCode:contextInfo:)
  811. contextInfo:nil];
  812. }
  813. else
  814. #endif
  815. {
  816. [alert beginSheetModalForWindow:(__nonnull id)[NSApplication sharedApplication].mainWindow completionHandler:^(NSModalResponse returnCode) {
  817. [self didDismissAlert:alert withButtonAtIndex:returnCode - NSAlertFirstButtonReturn];
  818. }];
  819. }
  820. #endif
  821. //inform about prompt
  822. if ([self.delegate respondsToSelector:@selector(iRateDidPromptForRating)])
  823. {
  824. [self.delegate iRateDidPromptForRating];
  825. }
  826. [[NSNotificationCenter defaultCenter] postNotificationName:iRateDidPromptForRating
  827. object:nil];
  828. }
  829. }
  830. - (void)applicationLaunched
  831. {
  832. //check if this is a new version
  833. NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
  834. NSString *lastUsedVersion = [defaults objectForKey:iRateLastVersionUsedKey];
  835. if (!self.firstUsed || ![lastUsedVersion isEqualToString:self.applicationVersion])
  836. {
  837. [defaults setObject:self.applicationVersion forKey:iRateLastVersionUsedKey];
  838. if (!self.firstUsed || [self ratedAnyVersion])
  839. {
  840. //reset defaults
  841. [defaults setObject:[NSDate date] forKey:iRateFirstUsedKey];
  842. [defaults setInteger:0 forKey:iRateUseCountKey];
  843. [defaults setInteger:0 forKey:iRateEventCountKey];
  844. [defaults setObject:nil forKey:iRateLastRemindedKey];
  845. [defaults synchronize];
  846. }
  847. else if ([[NSDate date] timeIntervalSinceDate:self.firstUsed] > (self.daysUntilPrompt - 1) * SECONDS_IN_A_DAY)
  848. {
  849. //if was previously installed, but we haven't yet prompted for a rating
  850. //don't reset, but make sure it won't rate for a day at least
  851. self.firstUsed = [[NSDate date] dateByAddingTimeInterval:(self.daysUntilPrompt - 1) * -SECONDS_IN_A_DAY];
  852. }
  853. //inform about app update
  854. if ([self.delegate respondsToSelector:@selector(iRateDidDetectAppUpdate)])
  855. {
  856. [self.delegate iRateDidDetectAppUpdate];
  857. }
  858. [[NSNotificationCenter defaultCenter] postNotificationName:iRateDidDetectAppUpdate
  859. object:nil];
  860. }
  861. [self incrementUseCount];
  862. [self checkForConnectivityInBackground];
  863. if (self.promptAtLaunch)
  864. {
  865. [self promptIfAllCriteriaMet];
  866. }
  867. }
  868. - (void)didDismissAlert:(__unused id)alertView withButtonAtIndex:(NSInteger)buttonIndex
  869. {
  870. dispatch_async(dispatch_get_main_queue(), ^{
  871. //get button indices
  872. NSInteger rateButtonIndex = 0;
  873. NSInteger cancelButtonIndex = [self showCancelButton]? 1: 0;
  874. NSInteger remindButtonIndex = [self showRemindButton]? cancelButtonIndex + 1: 0;
  875. if (buttonIndex == rateButtonIndex)
  876. {
  877. [self rate];
  878. }
  879. else if (buttonIndex == cancelButtonIndex)
  880. {
  881. [self declineThisVersion];
  882. }
  883. else if (buttonIndex == remindButtonIndex)
  884. {
  885. [self remindLater];
  886. }
  887. //release alert
  888. self.visibleAlert = nil;
  889. });
  890. }
  891. #if TARGET_OS_IPHONE
  892. - (void)applicationWillEnterForeground
  893. {
  894. if ([UIApplication sharedApplication].applicationState == UIApplicationStateBackground)
  895. {
  896. [self incrementUseCount];
  897. [self checkForConnectivityInBackground];
  898. if (self.promptAtLaunch)
  899. {
  900. [self promptIfAllCriteriaMet];
  901. }
  902. }
  903. }
  904. - (void)openRatingsPageInAppStore
  905. {
  906. if (!_ratingsURL && !self.appStoreID)
  907. {
  908. self.checkingForAppStoreID = YES;
  909. if (!self.checkingForPrompt)
  910. {
  911. [self checkForConnectivityInBackground];
  912. }
  913. return;
  914. }
  915. NSString *cantOpenMessage = nil;
  916. #if TARGET_IPHONE_SIMULATOR
  917. if ([[self.ratingsURL scheme] isEqualToString:iRateiOSAppStoreURLScheme])
  918. {
  919. cantOpenMessage = @"iRate could not open the ratings page because the App Store is not available on the iOS simulator";
  920. }
  921. #elif DEBUG
  922. if (![[UIApplication sharedApplication] canOpenURL:self.ratingsURL])
  923. {
  924. cantOpenMessage = [NSString stringWithFormat:@"iRate was unable to open the specified ratings URL: %@", self.ratingsURL];
  925. }
  926. #endif
  927. if (cantOpenMessage)
  928. {
  929. NSLog(@"%@", cantOpenMessage);
  930. NSError *error = [NSError errorWithDomain:iRateErrorDomain code:iRateErrorCouldNotOpenRatingPageURL userInfo:@{NSLocalizedDescriptionKey: cantOpenMessage}];
  931. if ([self.delegate respondsToSelector:@selector(iRateCouldNotConnectToAppStore:)])
  932. {
  933. [self.delegate iRateCouldNotConnectToAppStore:error];
  934. }
  935. [[NSNotificationCenter defaultCenter] postNotificationName:iRateCouldNotConnectToAppStore
  936. object:error];
  937. }
  938. else
  939. {
  940. if (self.verboseLogging)
  941. {
  942. NSLog(@"iRate will open the App Store ratings page using the following URL: %@", self.ratingsURL);
  943. }
  944. [[UIApplication sharedApplication] openURL:self.ratingsURL];
  945. if ([self.delegate respondsToSelector:@selector(iRateDidOpenAppStore)])
  946. {
  947. [self.delegate iRateDidOpenAppStore];
  948. }
  949. [[NSNotificationCenter defaultCenter] postNotificationName:iRateDidOpenAppStore
  950. object:nil];
  951. }
  952. }
  953. #pragma clang diagnostic push
  954. #pragma clang diagnostic ignored "-Wdeprecated-declarations"
  955. - (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex
  956. {
  957. [self didDismissAlert:alertView withButtonAtIndex:buttonIndex];
  958. }
  959. #pragma clang diagnostic pop
  960. #else
  961. - (void)openAppPageWhenAppStoreLaunched
  962. {
  963. //check if app store is running
  964. for (NSRunningApplication *app in [[NSWorkspace sharedWorkspace] runningApplications])
  965. {
  966. if ([app.bundleIdentifier isEqualToString:iRateMacAppStoreBundleID])
  967. {
  968. //open app page
  969. [[NSWorkspace sharedWorkspace] performSelector:@selector(openURL:) withObject:self.ratingsURL afterDelay:MAC_APP_STORE_REFRESH_DELAY];
  970. return;
  971. }
  972. }
  973. //try again
  974. [self performSelector:@selector(openAppPageWhenAppStoreLaunched) withObject:nil afterDelay:0.0];
  975. }
  976. - (void)openRatingsPageInAppStore
  977. {
  978. if (!_ratingsURL && !self.appStoreID)
  979. {
  980. self.checkingForAppStoreID = YES;
  981. if (!self.checkingForPrompt)
  982. {
  983. [self checkForConnectivityInBackground];
  984. }
  985. return;
  986. }
  987. if (self.verboseLogging)
  988. {
  989. NSLog(@"iRate will open the App Store ratings page using the following URL: %@", self.ratingsURL);
  990. }
  991. [[NSWorkspace sharedWorkspace] openURL:self.ratingsURL];
  992. [self openAppPageWhenAppStoreLaunched];
  993. if ([self.delegate respondsToSelector:@selector(iRateDidOpenAppStore)])
  994. {
  995. [self.delegate iRateDidOpenAppStore];
  996. }
  997. }
  998. - (void)alertDidEnd:(NSAlert *)alert returnCode:(NSInteger)returnCode contextInfo:(__unused void *)contextInfo
  999. {
  1000. [self didDismissAlert:alert withButtonAtIndex:returnCode - NSAlertFirstButtonReturn];
  1001. }
  1002. #endif
  1003. - (void)logEvent:(BOOL)deferPrompt
  1004. {
  1005. [self incrementEventCount];
  1006. if (!deferPrompt)
  1007. {
  1008. [self promptIfAllCriteriaMet];
  1009. }
  1010. }
  1011. #pragma mark - User's actions
  1012. - (void)declineThisVersion
  1013. {
  1014. //ignore this version
  1015. self.declinedThisVersion = YES;
  1016. //log event
  1017. if ([self.delegate respondsToSelector:@selector(iRateUserDidDeclineToRateApp)])
  1018. {
  1019. [self.delegate iRateUserDidDeclineToRateApp];
  1020. }
  1021. [[NSNotificationCenter defaultCenter] postNotificationName:iRateUserDidDeclineToRateApp
  1022. object:nil];
  1023. }
  1024. - (void)remindLater
  1025. {
  1026. //remind later
  1027. self.lastReminded = [NSDate date];
  1028. //log event
  1029. if ([self.delegate respondsToSelector:@selector(iRateUserDidRequestReminderToRateApp)])
  1030. {
  1031. [self.delegate iRateUserDidRequestReminderToRateApp];
  1032. }
  1033. [[NSNotificationCenter defaultCenter] postNotificationName:iRateUserDidRequestReminderToRateApp
  1034. object:nil];
  1035. }
  1036. - (void)rate
  1037. {
  1038. //mark as rated
  1039. self.ratedThisVersion = YES;
  1040. //log event
  1041. if ([self.delegate respondsToSelector:@selector(iRateUserDidAttemptToRateApp)])
  1042. {
  1043. [self.delegate iRateUserDidAttemptToRateApp];
  1044. }
  1045. [[NSNotificationCenter defaultCenter] postNotificationName:iRateUserDidAttemptToRateApp
  1046. object:nil];
  1047. // if the delegate has not implemented the method, or if it returns YES
  1048. if (![self.delegate respondsToSelector:@selector(iRateShouldOpenAppStore)] || [self.delegate iRateShouldOpenAppStore])
  1049. {
  1050. //launch mac app store
  1051. [self openRatingsPageInAppStore];
  1052. }
  1053. }
  1054. @end