diff --git a/Classes/BWApp.m b/Classes/BWApp.m index 49d5db38f3..265c92aa7d 100644 --- a/Classes/BWApp.m +++ b/Classes/BWApp.m @@ -41,20 +41,20 @@ #pragma mark static + (BWApp *)appFromDict:(NSDictionary *)dict { - BWApp *app = [[[[self class] alloc] init] autorelease]; - - // NSParameterAssert([dict isKindOfClass:[NSDictionary class]]); - if ([dict isKindOfClass:[NSDictionary class]]) { - app.name = [dict objectForKey:@"title"]; - app.version = [dict objectForKey:@"version"]; - app.shortVersion = [dict objectForKey:@"shortversion"]; - [app setDateWithTimestamp:[[dict objectForKey:@"timestamp"] doubleValue]]; - app.size = [dict objectForKey:@"appsize"]; - app.notes = [dict objectForKey:@"notes"]; - app.mandatory = [dict objectForKey:@"mandatory"]; - } - - return app; + BWApp *app = [[[[self class] alloc] init] autorelease]; + + // NSParameterAssert([dict isKindOfClass:[NSDictionary class]]); + if ([dict isKindOfClass:[NSDictionary class]]) { + app.name = [dict objectForKey:@"title"]; + app.version = [dict objectForKey:@"version"]; + app.shortVersion = [dict objectForKey:@"shortversion"]; + [app setDateWithTimestamp:[[dict objectForKey:@"timestamp"] doubleValue]]; + app.size = [dict objectForKey:@"appsize"]; + app.notes = [dict objectForKey:@"notes"]; + app.mandatory = [dict objectForKey:@"mandatory"]; + } + + return app; } /////////////////////////////////////////////////////////////////////////////////////////////////// @@ -62,43 +62,43 @@ #pragma mark NSObject - (void)dealloc { - [name_ release]; - [version_ release]; - [shortVersion_ release]; - [notes_ release]; - [date_ release]; - [size_ release]; - [mandatory_ release]; - - [super dealloc]; + [name_ release]; + [version_ release]; + [shortVersion_ release]; + [notes_ release]; + [date_ release]; + [size_ release]; + [mandatory_ release]; + + [super dealloc]; } - (BOOL)isEqual:(id)other { - if (other == self) - return YES; - if (!other || ![other isKindOfClass:[self class]]) - return NO; - return [self isEqualToBWApp:other]; + if (other == self) + return YES; + if (!other || ![other isKindOfClass:[self class]]) + return NO; + return [self isEqualToBWApp:other]; } - (BOOL)isEqualToBWApp:(BWApp *)anApp { - if (self == anApp) - return YES; - if (self.name != anApp.name && ![self.name isEqualToString:anApp.name]) - return NO; - if (self.version != anApp.version && ![self.version isEqualToString:anApp.version]) - return NO; - if (self.shortVersion != anApp.shortVersion && ![self.shortVersion isEqualToString:anApp.shortVersion]) - return NO; - if (self.notes != anApp.notes && ![self.notes isEqualToString:anApp.notes]) - return NO; - if (self.date != anApp.date && ![self.date isEqualToDate:anApp.date]) - return NO; - if (self.size != anApp.size && ![self.size isEqualToNumber:anApp.size]) - return NO; - if (self.mandatory != anApp.mandatory && ![self.mandatory isEqualToNumber:anApp.mandatory]) - return NO; + if (self == anApp) return YES; + if (self.name != anApp.name && ![self.name isEqualToString:anApp.name]) + return NO; + if (self.version != anApp.version && ![self.version isEqualToString:anApp.version]) + return NO; + if (self.shortVersion != anApp.shortVersion && ![self.shortVersion isEqualToString:anApp.shortVersion]) + return NO; + if (self.notes != anApp.notes && ![self.notes isEqualToString:anApp.notes]) + return NO; + if (self.date != anApp.date && ![self.date isEqualToDate:anApp.date]) + return NO; + if (self.size != anApp.size && ![self.size isEqualToNumber:anApp.size]) + return NO; + if (self.mandatory != anApp.mandatory && ![self.mandatory isEqualToNumber:anApp.mandatory]) + return NO; + return YES; } /////////////////////////////////////////////////////////////////////////////////////////////////// @@ -106,26 +106,26 @@ #pragma mark NSCoder - (void)encodeWithCoder:(NSCoder *)encoder { - [encoder encodeObject:self.name forKey:@"name"]; - [encoder encodeObject:self.version forKey:@"version"]; - [encoder encodeObject:self.shortVersion forKey:@"shortVersion"]; - [encoder encodeObject:self.notes forKey:@"notes"]; - [encoder encodeObject:self.date forKey:@"date"]; - [encoder encodeObject:self.size forKey:@"size"]; - [encoder encodeObject:self.mandatory forKey:@"mandatory"]; + [encoder encodeObject:self.name forKey:@"name"]; + [encoder encodeObject:self.version forKey:@"version"]; + [encoder encodeObject:self.shortVersion forKey:@"shortVersion"]; + [encoder encodeObject:self.notes forKey:@"notes"]; + [encoder encodeObject:self.date forKey:@"date"]; + [encoder encodeObject:self.size forKey:@"size"]; + [encoder encodeObject:self.mandatory forKey:@"mandatory"]; } - (id)initWithCoder:(NSCoder *)decoder { - if ((self = [super init])) { - self.name = [decoder decodeObjectForKey:@"name"]; - self.version = [decoder decodeObjectForKey:@"version"]; - self.shortVersion = [decoder decodeObjectForKey:@"shortVersion"]; - self.notes = [decoder decodeObjectForKey:@"notes"]; - self.date = [decoder decodeObjectForKey:@"date"]; - self.size = [decoder decodeObjectForKey:@"size"]; - self.mandatory = [decoder decodeObjectForKey:@"mandatory"]; - } - return self; + if ((self = [super init])) { + self.name = [decoder decodeObjectForKey:@"name"]; + self.version = [decoder decodeObjectForKey:@"version"]; + self.shortVersion = [decoder decodeObjectForKey:@"shortVersion"]; + self.notes = [decoder decodeObjectForKey:@"notes"]; + self.date = [decoder decodeObjectForKey:@"date"]; + self.size = [decoder decodeObjectForKey:@"size"]; + self.mandatory = [decoder decodeObjectForKey:@"mandatory"]; + } + return self; } /////////////////////////////////////////////////////////////////////////////////////////////////// @@ -133,54 +133,54 @@ #pragma mark Properties - (NSString *)nameAndVersionString { - NSString *appNameAndVersion = [NSString stringWithFormat:@"%@ %@", self.name, [self versionString]]; - return appNameAndVersion; + NSString *appNameAndVersion = [NSString stringWithFormat:@"%@ %@", self.name, [self versionString]]; + return appNameAndVersion; } - (NSString *)versionString { - NSString *shortString = ([self.shortVersion respondsToSelector:@selector(length)] && [self.shortVersion length]) ? [NSString stringWithFormat:@"%@", self.shortVersion] : @""; - NSString *versionString = [shortString length] ? [NSString stringWithFormat:@" (%@)", self.version] : self.version; - return [NSString stringWithFormat:@"%@ %@%@", BWHockeyLocalize(@"HockeyVersion"), shortString, versionString]; + NSString *shortString = ([self.shortVersion respondsToSelector:@selector(length)] && [self.shortVersion length]) ? [NSString stringWithFormat:@"%@", self.shortVersion] : @""; + NSString *versionString = [shortString length] ? [NSString stringWithFormat:@" (%@)", self.version] : self.version; + return [NSString stringWithFormat:@"%@ %@%@", BWHockeyLocalize(@"HockeyVersion"), shortString, versionString]; } - (NSString *)dateString { - NSDateFormatter *formatter = [[[NSDateFormatter alloc] init] autorelease]; - [formatter setDateStyle:NSDateFormatterMediumStyle]; - - return [formatter stringFromDate:self.date]; + NSDateFormatter *formatter = [[[NSDateFormatter alloc] init] autorelease]; + [formatter setDateStyle:NSDateFormatterMediumStyle]; + + return [formatter stringFromDate:self.date]; } - (NSString *)sizeInMB { - if ([size_ isKindOfClass: [NSNumber class]] && [size_ doubleValue] > 0) { - double appSizeInMB = [size_ doubleValue]/(1024*1024); - NSString *appSizeString = [NSString stringWithFormat:@"%.1f MB", appSizeInMB]; - return appSizeString; - } - - return @"0 MB"; + if ([size_ isKindOfClass: [NSNumber class]] && [size_ doubleValue] > 0) { + double appSizeInMB = [size_ doubleValue]/(1024*1024); + NSString *appSizeString = [NSString stringWithFormat:@"%.1f MB", appSizeInMB]; + return appSizeString; + } + + return @"0 MB"; } - (void)setDateWithTimestamp:(NSTimeInterval)timestamp { - if (timestamp) { - NSDate *appDate = [NSDate dateWithTimeIntervalSince1970:timestamp]; - self.date = appDate; - } else { - self.date = nil; - } + if (timestamp) { + NSDate *appDate = [NSDate dateWithTimeIntervalSince1970:timestamp]; + self.date = appDate; + } else { + self.date = nil; + } } - (NSString *)notesOrEmptyString { - if (self.notes) { - return self.notes; - }else { - return [NSString string]; - } + if (self.notes) { + return self.notes; + }else { + return [NSString string]; + } } // a valid app needs at least following properties: name, version, date - (BOOL)isValid { - BOOL valid = [self.name length] && [self.version length] && self.date; - return valid; + BOOL valid = [self.name length] && [self.version length] && self.date; + return valid; } @end diff --git a/Classes/BWGlobal.m b/Classes/BWGlobal.m index e26b1e3420..7f9e8e45d5 100644 --- a/Classes/BWGlobal.m +++ b/Classes/BWGlobal.m @@ -26,13 +26,13 @@ #include NSBundle *hockeyBundle(void) { - static NSBundle* bundle = nil; - if (!bundle) { - NSString* path = [[[NSBundle mainBundle] resourcePath] - stringByAppendingPathComponent:kHockeyBundleName]; - bundle = [[NSBundle bundleWithPath:path] retain]; - } - return bundle; + static NSBundle* bundle = nil; + if (!bundle) { + NSString* path = [[[NSBundle mainBundle] resourcePath] + stringByAppendingPathComponent:kHockeyBundleName]; + bundle = [[NSBundle bundleWithPath:path] retain]; + } + return bundle; } NSString *BWmd5(NSString *str) { @@ -40,22 +40,22 @@ NSString *BWmd5(NSString *str) { unsigned char result[CC_MD5_DIGEST_LENGTH]; CC_MD5( cStr, strlen(cStr), result ); return [NSString - stringWithFormat: @"%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X", - result[0], result[1], - result[2], result[3], - result[4], result[5], - result[6], result[7], - result[8], result[9], - result[10], result[11], - result[12], result[13], - result[14], result[15] - ]; + stringWithFormat: @"%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X", + result[0], result[1], + result[2], result[3], + result[4], result[5], + result[6], result[7], + result[8], result[9], + result[10], result[11], + result[12], result[13], + result[14], result[15] + ]; } NSString *BWHockeyLocalize(NSString *stringToken) { - if (hockeyBundle()) { - return NSLocalizedStringFromTableInBundle(stringToken, @"Hockey", hockeyBundle(), @""); - } else { - return stringToken; - } + if (hockeyBundle()) { + return NSLocalizedStringFromTableInBundle(stringToken, @"Hockey", hockeyBundle(), @""); + } else { + return stringToken; + } } diff --git a/Classes/BWHockeyManager.m b/Classes/BWHockeyManager.m index 3bef817265..c2899a76b7 100644 --- a/Classes/BWHockeyManager.m +++ b/Classes/BWHockeyManager.m @@ -70,12 +70,12 @@ // hockey api error domain typedef enum { - HockeyErrorUnknown, - HockeyAPIServerReturnedInvalidStatus, - HockeyAPIServerReturnedInvalidData, - HockeyAPIServerReturnedEmptyResponse, - HockeyAPIClientAuthorizationMissingSecret, - HockeyAPIClientCannotCreateConnection + HockeyErrorUnknown, + HockeyAPIServerReturnedInvalidStatus, + HockeyAPIServerReturnedInvalidData, + HockeyAPIServerReturnedEmptyResponse, + HockeyAPIClientAuthorizationMissingSecret, + HockeyAPIClientCannotCreateConnection } HockeyErrorReason; static NSString *kHockeyErrorDomain = @"HockeyErrorDomain"; @@ -120,27 +120,27 @@ static NSString *kHockeyErrorDomain = @"HockeyErrorDomain"; #if __IPHONE_OS_VERSION_MIN_REQUIRED >= 40000 +(BWHockeyManager *)sharedHockeyManager { - static BWHockeyManager *sharedInstance = nil; - static dispatch_once_t pred; - - if (sharedInstance) return sharedInstance; - - dispatch_once(&pred, ^{ - sharedInstance = [BWHockeyManager alloc]; - sharedInstance = [sharedInstance init]; - }); - - return sharedInstance; + static BWHockeyManager *sharedInstance = nil; + static dispatch_once_t pred; + + if (sharedInstance) return sharedInstance; + + dispatch_once(&pred, ^{ + sharedInstance = [BWHockeyManager alloc]; + sharedInstance = [sharedInstance init]; + }); + + return sharedInstance; } #else + (BWHockeyManager *)sharedHockeyManager { - static BWHockeyManager *hockeyManager = nil; - - if (hockeyManager == nil) { - hockeyManager = [[BWHockeyManager alloc] init]; - } - - return hockeyManager; + static BWHockeyManager *hockeyManager = nil; + + if (hockeyManager == nil) { + hockeyManager = [[BWHockeyManager alloc] init]; + } + + return hockeyManager; } #endif @@ -150,19 +150,19 @@ static NSString *kHockeyErrorDomain = @"HockeyErrorDomain"; #pragma mark private - (void)reportError_:(NSError *)error { - BWHockeyLog(@"Error: %@", [error localizedDescription]); - lastCheckFailed_ = YES; - - // only show error if we enable that - if (showFeedback_) { - UIAlertView *alert = [[UIAlertView alloc] initWithTitle:BWHockeyLocalize(@"HockeyError") - message:[error localizedDescription] - delegate:nil - cancelButtonTitle:BWHockeyLocalize(@"OK") otherButtonTitles:nil]; - [alert show]; - [alert release]; - showFeedback_ = NO; - } + BWHockeyLog(@"Error: %@", [error localizedDescription]); + lastCheckFailed_ = YES; + + // only show error if we enable that + if (showFeedback_) { + UIAlertView *alert = [[UIAlertView alloc] initWithTitle:BWHockeyLocalize(@"HockeyError") + message:[error localizedDescription] + delegate:nil + cancelButtonTitle:BWHockeyLocalize(@"OK") otherButtonTitles:nil]; + [alert show]; + [alert release]; + showFeedback_ = NO; + } } - (NSString *)encodedAppIdentifier_ { @@ -170,68 +170,68 @@ static NSString *kHockeyErrorDomain = @"HockeyErrorDomain"; } - (NSString *)getDevicePlatform_ { - size_t size; - sysctlbyname("hw.machine", NULL, &size, NULL, 0); - char *answer = (char*)malloc(size); - sysctlbyname("hw.machine", answer, &size, NULL, 0); - NSString *platform = [NSString stringWithCString:answer encoding: NSUTF8StringEncoding]; - free(answer); - return platform; + size_t size; + sysctlbyname("hw.machine", NULL, &size, NULL, 0); + char *answer = (char*)malloc(size); + sysctlbyname("hw.machine", answer, &size, NULL, 0); + NSString *platform = [NSString stringWithCString:answer encoding: NSUTF8StringEncoding]; + free(answer); + return platform; } - (void)connectionOpened_ { - if ([self.delegate respondsToSelector:@selector(connectionOpened)]) - [(id)self.delegate connectionOpened]; + if ([self.delegate respondsToSelector:@selector(connectionOpened)]) + [(id)self.delegate connectionOpened]; } - (void)connectionClosed_ { - if ([self.delegate respondsToSelector:@selector(connectionClosed)]) - [(id)self.delegate connectionClosed]; + if ([self.delegate respondsToSelector:@selector(connectionClosed)]) + [(id)self.delegate connectionClosed]; } - (void)startUsage { - self.usageStartTimestamp = [NSDate date]; - BOOL newVersion = NO; - - if (![[NSUserDefaults standardUserDefaults] valueForKey:kUsageTimeForVersionString]) { - newVersion = YES; - } else { - if ([(NSString *)[[NSUserDefaults standardUserDefaults] valueForKey:kUsageTimeForVersionString] compare:[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"]] != NSOrderedSame) { - newVersion = YES; - } + self.usageStartTimestamp = [NSDate date]; + BOOL newVersion = NO; + + if (![[NSUserDefaults standardUserDefaults] valueForKey:kUsageTimeForVersionString]) { + newVersion = YES; + } else { + if ([(NSString *)[[NSUserDefaults standardUserDefaults] valueForKey:kUsageTimeForVersionString] compare:[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"]] != NSOrderedSame) { + newVersion = YES; } - - if (newVersion) { - [[NSUserDefaults standardUserDefaults] setObject:[NSNumber numberWithDouble:[[NSDate date] timeIntervalSinceReferenceDate]] forKey:kDateOfVersionInstallation]; - [[NSUserDefaults standardUserDefaults] setObject:[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"] forKey:kUsageTimeForVersionString]; - [[NSUserDefaults standardUserDefaults] setObject:[NSNumber numberWithDouble:0] forKey:kUsageTimeOfCurrentVersion]; - [[NSUserDefaults standardUserDefaults] synchronize]; - } + } + + if (newVersion) { + [[NSUserDefaults standardUserDefaults] setObject:[NSNumber numberWithDouble:[[NSDate date] timeIntervalSinceReferenceDate]] forKey:kDateOfVersionInstallation]; + [[NSUserDefaults standardUserDefaults] setObject:[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"] forKey:kUsageTimeForVersionString]; + [[NSUserDefaults standardUserDefaults] setObject:[NSNumber numberWithDouble:0] forKey:kUsageTimeOfCurrentVersion]; + [[NSUserDefaults standardUserDefaults] synchronize]; + } } - (void)stopUsage { - double timeDifference = [[NSDate date] timeIntervalSinceReferenceDate] - [usageStartTimestamp_ timeIntervalSinceReferenceDate]; - double previousTimeDifference = [(NSNumber *)[[NSUserDefaults standardUserDefaults] valueForKey:kUsageTimeOfCurrentVersion] doubleValue]; - - [[NSUserDefaults standardUserDefaults] setObject:[NSNumber numberWithDouble:previousTimeDifference + timeDifference] forKey:kUsageTimeOfCurrentVersion]; - [[NSUserDefaults standardUserDefaults] synchronize]; + double timeDifference = [[NSDate date] timeIntervalSinceReferenceDate] - [usageStartTimestamp_ timeIntervalSinceReferenceDate]; + double previousTimeDifference = [(NSNumber *)[[NSUserDefaults standardUserDefaults] valueForKey:kUsageTimeOfCurrentVersion] doubleValue]; + + [[NSUserDefaults standardUserDefaults] setObject:[NSNumber numberWithDouble:previousTimeDifference + timeDifference] forKey:kUsageTimeOfCurrentVersion]; + [[NSUserDefaults standardUserDefaults] synchronize]; } - (NSString *)currentUsageString { - double currentUsageTime = [(NSNumber *)[[NSUserDefaults standardUserDefaults] valueForKey:kUsageTimeOfCurrentVersion] doubleValue]; - - if (currentUsageTime > 0) { - // round (up) to 1 minute - return [NSString stringWithFormat:@"%.0f", ceil(currentUsageTime / 60.0)*60]; - } else { - return @"0"; - } + double currentUsageTime = [(NSNumber *)[[NSUserDefaults standardUserDefaults] valueForKey:kUsageTimeOfCurrentVersion] doubleValue]; + + if (currentUsageTime > 0) { + // round (up) to 1 minute + return [NSString stringWithFormat:@"%.0f", ceil(currentUsageTime / 60.0)*60]; + } else { + return @"0"; + } } - (NSString *)installationDateString { - NSDateFormatter *formatter = [[[NSDateFormatter alloc] init] autorelease]; - [formatter setDateFormat:@"MM/dd/yyyy"]; - return [formatter stringFromDate:[NSDate dateWithTimeIntervalSinceReferenceDate:[(NSNumber *)[[NSUserDefaults standardUserDefaults] valueForKey:kDateOfVersionInstallation] doubleValue]]]; + NSDateFormatter *formatter = [[[NSDateFormatter alloc] init] autorelease]; + [formatter setDateFormat:@"MM/dd/yyyy"]; + return [formatter stringFromDate:[NSDate dateWithTimeIntervalSinceReferenceDate:[(NSNumber *)[[NSUserDefaults standardUserDefaults] valueForKey:kDateOfVersionInstallation] doubleValue]]]; } - (NSString *)deviceIdentifier { @@ -244,104 +244,104 @@ static NSString *kHockeyErrorDomain = @"HockeyErrorDomain"; } - (NSString *)authenticationToken { - return [BWmd5([NSString stringWithFormat:@"%@%@%@%@", - authenticationSecret_, - [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"], - [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleIdentifier"], - [self deviceIdentifier] - ] - ) lowercaseString]; + return [BWmd5([NSString stringWithFormat:@"%@%@%@%@", + authenticationSecret_, + [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"], + [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleIdentifier"], + [self deviceIdentifier] + ] + ) lowercaseString]; } - (HockeyAuthorizationState)authorizationState { - NSString *version = [[NSUserDefaults standardUserDefaults] objectForKey:kHockeyAuthorizedVersion]; - NSString *token = [[NSUserDefaults standardUserDefaults] objectForKey:kHockeyAuthorizedToken]; - - if (version != nil && token != nil) { - if ([version compare:[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"]] == NSOrderedSame) { - // if it is denied, block the screen permanently - if ([token compare:[self authenticationToken]] != NSOrderedSame) { - return HockeyAuthorizationDenied; - } else { - return HockeyAuthorizationAllowed; - } - } + NSString *version = [[NSUserDefaults standardUserDefaults] objectForKey:kHockeyAuthorizedVersion]; + NSString *token = [[NSUserDefaults standardUserDefaults] objectForKey:kHockeyAuthorizedToken]; + + if (version != nil && token != nil) { + if ([version compare:[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"]] == NSOrderedSame) { + // if it is denied, block the screen permanently + if ([token compare:[self authenticationToken]] != NSOrderedSame) { + return HockeyAuthorizationDenied; + } else { + return HockeyAuthorizationAllowed; + } } - return HockeyAuthorizationPending; + } + return HockeyAuthorizationPending; } - (void)checkUpdateAvailable_ { - // check if there is an update available - if (self.compareVersionType == HockeyComparisonResultGreater) { - self.updateAvailable = ([self.app.version versionCompare:self.currentAppVersion] == NSOrderedDescending); - } else { - self.updateAvailable = ([self.app.version compare:self.currentAppVersion] != NSOrderedSame); - } + // check if there is an update available + if (self.compareVersionType == HockeyComparisonResultGreater) { + self.updateAvailable = ([self.app.version versionCompare:self.currentAppVersion] == NSOrderedDescending); + } else { + self.updateAvailable = ([self.app.version compare:self.currentAppVersion] != NSOrderedSame); + } } - (void)loadAppCache_ { - NSData *savedHockeyData = [[NSUserDefaults standardUserDefaults] objectForKey:kArrayOfLastHockeyCheck]; - NSArray *savedHockeyCheck = nil; - if (savedHockeyData) { - savedHockeyCheck = [NSKeyedUnarchiver unarchiveObjectWithData:savedHockeyData]; - } - if (savedHockeyCheck) { - self.apps = [NSArray arrayWithArray:savedHockeyCheck]; - [self checkUpdateAvailable_]; - } else { - self.apps = nil; - } + NSData *savedHockeyData = [[NSUserDefaults standardUserDefaults] objectForKey:kArrayOfLastHockeyCheck]; + NSArray *savedHockeyCheck = nil; + if (savedHockeyData) { + savedHockeyCheck = [NSKeyedUnarchiver unarchiveObjectWithData:savedHockeyData]; + } + if (savedHockeyCheck) { + self.apps = [NSArray arrayWithArray:savedHockeyCheck]; + [self checkUpdateAvailable_]; + } else { + self.apps = nil; + } } - (void)saveAppCache_ { - NSData *data = [NSKeyedArchiver archivedDataWithRootObject:self.apps]; - [[NSUserDefaults standardUserDefaults] setObject:data forKey:kArrayOfLastHockeyCheck]; - [[NSUserDefaults standardUserDefaults] synchronize]; + NSData *data = [NSKeyedArchiver archivedDataWithRootObject:self.apps]; + [[NSUserDefaults standardUserDefaults] setObject:data forKey:kArrayOfLastHockeyCheck]; + [[NSUserDefaults standardUserDefaults] synchronize]; } - (UIWindow *)findVisibleWindow { - UIWindow *visibleWindow = nil; - - // if the rootViewController property (available >= iOS 4.0) of the main window is set, we present the modal view controller on top of the rootViewController - NSArray *windows = [[UIApplication sharedApplication] windows]; - for (UIWindow *window in windows) { - if (!window.hidden && !visibleWindow) { - visibleWindow = window; - } - if ([UIWindow instancesRespondToSelector:@selector(rootViewController)]) { - if ([window rootViewController]) { - visibleWindow = window; - BWHockeyLog(@"UIWindow with rootViewController found: %@", visibleWindow); - break; - } - } - } - - return visibleWindow; + UIWindow *visibleWindow = nil; + + // if the rootViewController property (available >= iOS 4.0) of the main window is set, we present the modal view controller on top of the rootViewController + NSArray *windows = [[UIApplication sharedApplication] windows]; + for (UIWindow *window in windows) { + if (!window.hidden && !visibleWindow) { + visibleWindow = window; + } + if ([UIWindow instancesRespondToSelector:@selector(rootViewController)]) { + if ([window rootViewController]) { + visibleWindow = window; + BWHockeyLog(@"UIWindow with rootViewController found: %@", visibleWindow); + break; + } + } + } + + return visibleWindow; } - (BOOL)canSendUserData { - if (self.shouldSendUserData) { - if (self.allowUserToDisableSendData) { - return self.userAllowsSendUserData; - } - - return YES; + if (self.shouldSendUserData) { + if (self.allowUserToDisableSendData) { + return self.userAllowsSendUserData; } - return NO; + return YES; + } + + return NO; } - (BOOL)canSendUsageTime { - if (self.shouldSendUsageTime) { - if (self.allowUserToDisableSendData) { - return self.userAllowsSendUsageTime; - } - - return YES; + if (self.shouldSendUsageTime) { + if (self.allowUserToDisableSendData) { + return self.userAllowsSendUsageTime; } - return NO; + return YES; + } + + return NO; } @@ -350,120 +350,120 @@ static NSString *kHockeyErrorDomain = @"HockeyErrorDomain"; #pragma mark NSObject - (id)init { - if ((self = [super init])) { - updateURL_ = nil; - appIdentifier_ = nil; - checkInProgress_ = NO; - dataFound = NO; - updateAvailable_ = NO; - lastCheckFailed_ = NO; - currentAppVersion_ = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"]; - navController_ = nil; - authorizeView_ = nil; - requireAuthorization_ = NO; - authenticationSecret_= nil; - loggingEnabled_ = NO; - lastCheck_ = nil; - - // check if we are really not in an app store environment - if ([[NSBundle mainBundle] pathForResource:@"embedded" ofType:@"mobileprovision"]) { - isAppStoreEnvironment_ = NO; - } else { - isAppStoreEnvironment_ = YES; - } - -#if TARGET_IPHONE_SIMULATOR - isAppStoreEnvironment_ = NO; -#endif - - // set defaults - self.showDirectInstallOption = NO; - self.requireAuthorization = NO; - self.sendUserData = YES; - self.sendUsageTime = YES; - self.allowUserToDisableSendData = YES; - self.alwaysShowUpdateReminder = YES; - self.checkForUpdateOnLaunch = YES; - self.showUserSettings = YES; - self.compareVersionType = HockeyComparisonResultGreater; - self.barStyle = UIBarStyleDefault; - self.modalPresentationStyle = UIModalPresentationFormSheet; - - // load update setting from user defaults and check value - if ([[NSUserDefaults standardUserDefaults] objectForKey:kHockeyAutoUpdateSetting]) { - self.updateSetting = (HockeyUpdateSetting)[[NSUserDefaults standardUserDefaults] integerForKey:kHockeyAutoUpdateSetting]; - } else { - self.updateSetting = HockeyUpdateCheckStartup; - } - - if ([[NSUserDefaults standardUserDefaults] objectForKey:kDateOfLastHockeyCheck]) { - // we did write something else in the past, so for compatibility reasons do this - id tempLastCheck = [[NSUserDefaults standardUserDefaults] objectForKey:kDateOfLastHockeyCheck]; - if ([tempLastCheck isKindOfClass:[NSDate class]]) { - lastCheck_ = tempLastCheck; - } - } - if (!lastCheck_) { - lastCheck_ = [NSDate distantPast]; - } - - if ([[NSUserDefaults standardUserDefaults] objectForKey:kHockeyAllowUserSetting]) { - self.userAllowsSendUserData = [[NSUserDefaults standardUserDefaults] boolForKey:kHockeyAllowUserSetting]; - } else { - self.userAllowsSendUserData = YES; - } - - if ([[NSUserDefaults standardUserDefaults] objectForKey:kHockeyAllowUsageSetting]) { - self.userAllowsSendUsageTime = [[NSUserDefaults standardUserDefaults] boolForKey:kHockeyAllowUsageSetting]; - } else { - self.userAllowsSendUsageTime = YES; - } - - if (!hockeyBundle()) { - NSLog(@"WARNING: Hockey.bundle is missing, make sure it is added!"); - } - - [self loadAppCache_]; + if ((self = [super init])) { + updateURL_ = nil; + appIdentifier_ = nil; + checkInProgress_ = NO; + dataFound = NO; + updateAvailable_ = NO; + lastCheckFailed_ = NO; + currentAppVersion_ = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"]; + navController_ = nil; + authorizeView_ = nil; + requireAuthorization_ = NO; + authenticationSecret_= nil; + loggingEnabled_ = NO; + lastCheck_ = nil; - [self startUsage]; - - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(startManager) - name:BWHockeyNetworkBecomeReachable - object:nil]; - - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(stopUsage) - name:UIApplicationWillTerminateNotification - object:nil]; + // check if we are really not in an app store environment + if ([[NSBundle mainBundle] pathForResource:@"embedded" ofType:@"mobileprovision"]) { + isAppStoreEnvironment_ = NO; + } else { + isAppStoreEnvironment_ = YES; } - return self; + +#if TARGET_IPHONE_SIMULATOR + isAppStoreEnvironment_ = NO; +#endif + + // set defaults + self.showDirectInstallOption = NO; + self.requireAuthorization = NO; + self.sendUserData = YES; + self.sendUsageTime = YES; + self.allowUserToDisableSendData = YES; + self.alwaysShowUpdateReminder = YES; + self.checkForUpdateOnLaunch = YES; + self.showUserSettings = YES; + self.compareVersionType = HockeyComparisonResultGreater; + self.barStyle = UIBarStyleDefault; + self.modalPresentationStyle = UIModalPresentationFormSheet; + + // load update setting from user defaults and check value + if ([[NSUserDefaults standardUserDefaults] objectForKey:kHockeyAutoUpdateSetting]) { + self.updateSetting = (HockeyUpdateSetting)[[NSUserDefaults standardUserDefaults] integerForKey:kHockeyAutoUpdateSetting]; + } else { + self.updateSetting = HockeyUpdateCheckStartup; + } + + if ([[NSUserDefaults standardUserDefaults] objectForKey:kDateOfLastHockeyCheck]) { + // we did write something else in the past, so for compatibility reasons do this + id tempLastCheck = [[NSUserDefaults standardUserDefaults] objectForKey:kDateOfLastHockeyCheck]; + if ([tempLastCheck isKindOfClass:[NSDate class]]) { + lastCheck_ = tempLastCheck; + } + } + if (!lastCheck_) { + lastCheck_ = [NSDate distantPast]; + } + + if ([[NSUserDefaults standardUserDefaults] objectForKey:kHockeyAllowUserSetting]) { + self.userAllowsSendUserData = [[NSUserDefaults standardUserDefaults] boolForKey:kHockeyAllowUserSetting]; + } else { + self.userAllowsSendUserData = YES; + } + + if ([[NSUserDefaults standardUserDefaults] objectForKey:kHockeyAllowUsageSetting]) { + self.userAllowsSendUsageTime = [[NSUserDefaults standardUserDefaults] boolForKey:kHockeyAllowUsageSetting]; + } else { + self.userAllowsSendUsageTime = YES; + } + + if (!hockeyBundle()) { + NSLog(@"WARNING: Hockey.bundle is missing, make sure it is added!"); + } + + [self loadAppCache_]; + + [self startUsage]; + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(startManager) + name:BWHockeyNetworkBecomeReachable + object:nil]; + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(stopUsage) + name:UIApplicationWillTerminateNotification + object:nil]; + } + return self; } - (void)dealloc { - [[NSNotificationCenter defaultCenter] removeObserver:self name:BWHockeyNetworkBecomeReachable object:nil]; - [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationWillTerminateNotification object:nil]; - - BW_IF_IOS4_OR_GREATER( - [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidBecomeActiveNotification object:nil]; - [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationWillResignActiveNotification object:nil]; - ) - self.delegate = nil; - - [urlConnection_ cancel]; - self.urlConnection = nil; - - [navController_ release]; - [authorizeView_ release]; - [currentHockeyViewController_ release]; - [updateURL_ release]; - [apps_ release]; - [receivedData_ release]; - [lastCheck_ release]; - [usageStartTimestamp_ release]; - [authenticationSecret_ release]; - - [super dealloc]; + [[NSNotificationCenter defaultCenter] removeObserver:self name:BWHockeyNetworkBecomeReachable object:nil]; + [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationWillTerminateNotification object:nil]; + + BW_IF_IOS4_OR_GREATER( + [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidBecomeActiveNotification object:nil]; + [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationWillResignActiveNotification object:nil]; + ) + self.delegate = nil; + + [urlConnection_ cancel]; + self.urlConnection = nil; + + [navController_ release]; + [authorizeView_ release]; + [currentHockeyViewController_ release]; + [updateURL_ release]; + [apps_ release]; + [receivedData_ release]; + [lastCheck_ release]; + [usageStartTimestamp_ release]; + [authenticationSecret_ release]; + + [super dealloc]; } /////////////////////////////////////////////////////////////////////////////////////////////////// @@ -471,157 +471,157 @@ static NSString *kHockeyErrorDomain = @"HockeyErrorDomain"; #pragma mark BetaUpdateUI - (BWHockeyViewController *)hockeyViewController:(BOOL)modal { - return [[[BWHockeyViewController alloc] init:self modal:modal] autorelease]; + return [[[BWHockeyViewController alloc] init:self modal:modal] autorelease]; } - (void)showUpdateView { - if (isAppStoreEnvironment_) { - NSLog(@"this should not be called from an app store build."); - return; + if (isAppStoreEnvironment_) { + NSLog(@"this should not be called from an app store build."); + return; + } + + if (currentHockeyViewController_) { + BWHockeyLog(@"update view already visible, aborting"); + return; + } + + UIViewController *parentViewController = nil; + + if ([[self delegate] respondsToSelector:@selector(viewControllerForHockeyController:)]) { + parentViewController = [[self delegate] viewControllerForHockeyController:self]; + } + + UIWindow *visibleWindow = [self findVisibleWindow]; + + if (parentViewController == nil && [UIWindow instancesRespondToSelector:@selector(rootViewController)]) { + parentViewController = [visibleWindow rootViewController]; + } + + // use topmost modal view + while (parentViewController.modalViewController) { + parentViewController = parentViewController.modalViewController; + } + + // special addition to get rootViewController from three20 which has it's own controller handling + if (NSClassFromString(@"TTNavigator")) { + parentViewController = [[NSClassFromString(@"TTNavigator") performSelector:(NSSelectorFromString(@"navigator"))] visibleViewController]; + } + + if (navController_ != nil) [navController_ release]; + + BWHockeyViewController *hockeyViewController = [self hockeyViewController:YES]; + navController_ = [[UINavigationController alloc] initWithRootViewController:hockeyViewController]; + navController_.navigationBar.barStyle = barStyle_; + navController_.modalPresentationStyle = modalPresentationStyle_; + + if (parentViewController) { + if ([navController_ respondsToSelector:@selector(setModalTransitionStyle:)]) { + navController_.modalTransitionStyle = UIModalTransitionStyleCoverVertical; } - if (currentHockeyViewController_) { - BWHockeyLog(@"update view already visible, aborting"); - return; + // page sheet for the iPad + if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad && [navController_ respondsToSelector:@selector(setModalPresentationStyle:)]) { + navController_.modalPresentationStyle = UIModalPresentationFormSheet; } - UIViewController *parentViewController = nil; - - if ([[self delegate] respondsToSelector:@selector(viewControllerForHockeyController:)]) { - parentViewController = [[self delegate] viewControllerForHockeyController:self]; - } - - UIWindow *visibleWindow = [self findVisibleWindow]; - - if (parentViewController == nil && [UIWindow instancesRespondToSelector:@selector(rootViewController)]) { - parentViewController = [visibleWindow rootViewController]; - } - - // use topmost modal view - while (parentViewController.modalViewController) { - parentViewController = parentViewController.modalViewController; - } - - // special addition to get rootViewController from three20 which has it's own controller handling - if (NSClassFromString(@"TTNavigator")) { - parentViewController = [[NSClassFromString(@"TTNavigator") performSelector:(NSSelectorFromString(@"navigator"))] visibleViewController]; - } - - if (navController_ != nil) [navController_ release]; - - BWHockeyViewController *hockeyViewController = [self hockeyViewController:YES]; - navController_ = [[UINavigationController alloc] initWithRootViewController:hockeyViewController]; - navController_.navigationBar.barStyle = barStyle_; - navController_.modalPresentationStyle = modalPresentationStyle_; - - if (parentViewController) { - if ([navController_ respondsToSelector:@selector(setModalTransitionStyle:)]) { - navController_.modalTransitionStyle = UIModalTransitionStyleCoverVertical; - } - - // page sheet for the iPad - if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad && [navController_ respondsToSelector:@selector(setModalPresentationStyle:)]) { - navController_.modalPresentationStyle = UIModalPresentationFormSheet; - } - - [parentViewController presentModalViewController:navController_ animated:YES]; - } else { - // if not, we add a subview to the window. A bit hacky but should work in most circumstances. - // Also, we don't get a nice animation for free, but hey, this is for beta not production users ;) - BWHockeyLog(@"No rootViewController found, using UIWindow-approach: %@", visibleWindow); - [visibleWindow addSubview:navController_.view]; - } + [parentViewController presentModalViewController:navController_ animated:YES]; + } else { + // if not, we add a subview to the window. A bit hacky but should work in most circumstances. + // Also, we don't get a nice animation for free, but hey, this is for beta not production users ;) + BWHockeyLog(@"No rootViewController found, using UIWindow-approach: %@", visibleWindow); + [visibleWindow addSubview:navController_.view]; + } } - (void)showCheckForUpdateAlert_ { - if (isAppStoreEnvironment_) return; + if (isAppStoreEnvironment_) return; - if (!updateAlertShowing_) { - if ([self.app.mandatory boolValue] ) { - UIAlertView *alertView = [[[UIAlertView alloc] initWithTitle:BWHockeyLocalize(@"HockeyUpdateAvailable") - message:[NSString stringWithFormat:BWHockeyLocalize(@"HockeyUpdateAlertMandatoryTextWithAppVersion"), [self.app nameAndVersionString]] - delegate:self - cancelButtonTitle:BWHockeyLocalize(@"HockeyInstallUpdate") - otherButtonTitles:nil - ] autorelease]; - [alertView setTag:2]; - [alertView show]; - updateAlertShowing_ = YES; - } else { - UIAlertView *alertView = [[[UIAlertView alloc] initWithTitle:BWHockeyLocalize(@"HockeyUpdateAvailable") - message:[NSString stringWithFormat:BWHockeyLocalize(@"HockeyUpdateAlertTextWithAppVersion"), [self.app nameAndVersionString]] - delegate:self - cancelButtonTitle:BWHockeyLocalize(@"HockeyIgnore") - otherButtonTitles:BWHockeyLocalize(@"HockeyShowUpdate"), nil - ] autorelease]; - BW_IF_IOS4_OR_GREATER( - if (self.isShowingDirectInstallOption) { - [alertView addButtonWithTitle:BWHockeyLocalize(@"HockeyInstallUpdate")]; - } - ) - [alertView setTag:0]; - [alertView show]; - updateAlertShowing_ = YES; - } + if (!updateAlertShowing_) { + if ([self.app.mandatory boolValue] ) { + UIAlertView *alertView = [[[UIAlertView alloc] initWithTitle:BWHockeyLocalize(@"HockeyUpdateAvailable") + message:[NSString stringWithFormat:BWHockeyLocalize(@"HockeyUpdateAlertMandatoryTextWithAppVersion"), [self.app nameAndVersionString]] + delegate:self + cancelButtonTitle:BWHockeyLocalize(@"HockeyInstallUpdate") + otherButtonTitles:nil + ] autorelease]; + [alertView setTag:2]; + [alertView show]; + updateAlertShowing_ = YES; + } else { + UIAlertView *alertView = [[[UIAlertView alloc] initWithTitle:BWHockeyLocalize(@"HockeyUpdateAvailable") + message:[NSString stringWithFormat:BWHockeyLocalize(@"HockeyUpdateAlertTextWithAppVersion"), [self.app nameAndVersionString]] + delegate:self + cancelButtonTitle:BWHockeyLocalize(@"HockeyIgnore") + otherButtonTitles:BWHockeyLocalize(@"HockeyShowUpdate"), nil + ] autorelease]; + BW_IF_IOS4_OR_GREATER( + if (self.isShowingDirectInstallOption) { + [alertView addButtonWithTitle:BWHockeyLocalize(@"HockeyInstallUpdate")]; + } + ) + [alertView setTag:0]; + [alertView show]; + updateAlertShowing_ = YES; } + } } // nag the user with neverending alerts if we cannot find out the window for presenting the covering sheet - (void)alertFallback:(NSString *)message { - UIAlertView *alertView = [[[UIAlertView alloc] initWithTitle:nil - message:message - delegate:self - cancelButtonTitle:@"Ok" - otherButtonTitles:nil - ] autorelease]; - [alertView setTag:1]; - [alertView show]; + UIAlertView *alertView = [[[UIAlertView alloc] initWithTitle:nil + message:message + delegate:self + cancelButtonTitle:@"Ok" + otherButtonTitles:nil + ] autorelease]; + [alertView setTag:1]; + [alertView show]; } // open an authorization screen - (void)showAuthorizationScreen:(NSString *)message image:(NSString *)image { - self.authorizeView = nil; + self.authorizeView = nil; + + UIWindow *visibleWindow = [self findVisibleWindow]; + if (visibleWindow == nil) { + [self alertFallback:message]; + return; + } + + CGRect frame = [visibleWindow frame]; + + self.authorizeView = [[[UIView alloc] initWithFrame:frame] autorelease]; + UIImageView *backgroundView = [[[UIImageView alloc] initWithImage:[UIImage bw_imageNamed:@"bg.png" bundle:kHockeyBundleName]] autorelease]; + backgroundView.contentMode = UIViewContentModeScaleAspectFill; + backgroundView.frame = frame; + [self.authorizeView addSubview:backgroundView]; + + if (image != nil) { + UIImageView *imageView = [[[UIImageView alloc] initWithImage:[UIImage bw_imageNamed:image bundle:kHockeyBundleName]] autorelease]; + imageView.contentMode = UIViewContentModeCenter; + imageView.frame = frame; + [self.authorizeView addSubview:imageView]; + } + + if (message != nil) { + frame.origin.x = 20; + frame.origin.y = frame.size.height - 140; + frame.size.width -= 40; + frame.size.height = 40; - UIWindow *visibleWindow = [self findVisibleWindow]; - if (visibleWindow == nil) { - [self alertFallback:message]; - return; - } + UILabel *label = [[[UILabel alloc] initWithFrame:frame] autorelease]; + label.text = message; + label.textAlignment = UITextAlignmentCenter; + label.numberOfLines = 2; + label.backgroundColor = [UIColor clearColor]; - CGRect frame = [visibleWindow frame]; - - self.authorizeView = [[[UIView alloc] initWithFrame:frame] autorelease]; - UIImageView *backgroundView = [[[UIImageView alloc] initWithImage:[UIImage bw_imageNamed:@"bg.png" bundle:kHockeyBundleName]] autorelease]; - backgroundView.contentMode = UIViewContentModeScaleAspectFill; - backgroundView.frame = frame; - [self.authorizeView addSubview:backgroundView]; - - if (image != nil) { - UIImageView *imageView = [[[UIImageView alloc] initWithImage:[UIImage bw_imageNamed:image bundle:kHockeyBundleName]] autorelease]; - imageView.contentMode = UIViewContentModeCenter; - imageView.frame = frame; - [self.authorizeView addSubview:imageView]; - } - - if (message != nil) { - frame.origin.x = 20; - frame.origin.y = frame.size.height - 140; - frame.size.width -= 40; - frame.size.height = 40; - - UILabel *label = [[[UILabel alloc] initWithFrame:frame] autorelease]; - label.text = message; - label.textAlignment = UITextAlignmentCenter; - label.numberOfLines = 2; - label.backgroundColor = [UIColor clearColor]; - - [self.authorizeView addSubview:label]; - } - - [visibleWindow addSubview:self.authorizeView]; + [self.authorizeView addSubview:label]; + } + + [visibleWindow addSubview:self.authorizeView]; } @@ -630,70 +630,70 @@ static NSString *kHockeyErrorDomain = @"HockeyErrorDomain"; #pragma mark JSONParsing - (id)parseJSONResultString:(NSString *)jsonString { - NSError *error = nil; - id feedResult = nil; - + NSError *error = nil; + id feedResult = nil; + #if BW_NATIVE_JSON_AVAILABLE - feedResult = [NSJSONSerialization JSONObjectWithData:[jsonString dataUsingEncoding:NSUTF8StringEncoding] options:kNilOptions error:&error]; + feedResult = [NSJSONSerialization JSONObjectWithData:[jsonString dataUsingEncoding:NSUTF8StringEncoding] options:kNilOptions error:&error]; #else - id nsjsonClass = NSClassFromString(@"NSJSONSerialization"); - SEL nsjsonSelect = NSSelectorFromString(@"JSONObjectWithData:options:error:"); - SEL sbJSONSelector = NSSelectorFromString(@"JSONValue"); - SEL jsonKitSelector = NSSelectorFromString(@"objectFromJSONStringWithParseOptions:error:"); - SEL yajlSelector = NSSelectorFromString(@"yajl_JSONWithOptions:error:"); + id nsjsonClass = NSClassFromString(@"NSJSONSerialization"); + SEL nsjsonSelect = NSSelectorFromString(@"JSONObjectWithData:options:error:"); + SEL sbJSONSelector = NSSelectorFromString(@"JSONValue"); + SEL jsonKitSelector = NSSelectorFromString(@"objectFromJSONStringWithParseOptions:error:"); + SEL yajlSelector = NSSelectorFromString(@"yajl_JSONWithOptions:error:"); + + if (nsjsonClass && [nsjsonClass respondsToSelector:nsjsonSelect]) { + NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[nsjsonClass methodSignatureForSelector:nsjsonSelect]]; + invocation.target = nsjsonClass; + invocation.selector = nsjsonSelect; + NSData *jsonData = [jsonString dataUsingEncoding:NSUTF8StringEncoding]; + [invocation setArgument:&jsonData atIndex:2]; // arguments 0 and 1 are self and _cmd respectively, automatically set by NSInvocation + NSUInteger readOptions = kNilOptions; + [invocation setArgument:&readOptions atIndex:3]; + [invocation setArgument:&error atIndex:4]; + [invocation invoke]; + [invocation getReturnValue:&feedResult]; + } else if (jsonKitSelector && [jsonString respondsToSelector:jsonKitSelector]) { + // first try JSONkit + NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[jsonString methodSignatureForSelector:jsonKitSelector]]; + invocation.target = jsonString; + invocation.selector = jsonKitSelector; + int parseOptions = 0; + [invocation setArgument:&parseOptions atIndex:2]; // arguments 0 and 1 are self and _cmd respectively, automatically set by NSInvocation + [invocation setArgument:&error atIndex:3]; + [invocation invoke]; + [invocation getReturnValue:&feedResult]; + } else if (sbJSONSelector && [jsonString respondsToSelector:sbJSONSelector]) { + // now try SBJson + NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[jsonString methodSignatureForSelector:sbJSONSelector]]; + invocation.target = jsonString; + invocation.selector = sbJSONSelector; + [invocation invoke]; + [invocation getReturnValue:&feedResult]; + } else if (yajlSelector && [jsonString respondsToSelector:yajlSelector]) { + NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[jsonString methodSignatureForSelector:yajlSelector]]; + invocation.target = jsonString; + invocation.selector = yajlSelector; - if (nsjsonClass && [nsjsonClass respondsToSelector:nsjsonSelect]) { - NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[nsjsonClass methodSignatureForSelector:nsjsonSelect]]; - invocation.target = nsjsonClass; - invocation.selector = nsjsonSelect; - NSData *jsonData = [jsonString dataUsingEncoding:NSUTF8StringEncoding]; - [invocation setArgument:&jsonData atIndex:2]; // arguments 0 and 1 are self and _cmd respectively, automatically set by NSInvocation - NSUInteger readOptions = kNilOptions; - [invocation setArgument:&readOptions atIndex:3]; - [invocation setArgument:&error atIndex:4]; - [invocation invoke]; - [invocation getReturnValue:&feedResult]; - } else if (jsonKitSelector && [jsonString respondsToSelector:jsonKitSelector]) { - // first try JSONkit - NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[jsonString methodSignatureForSelector:jsonKitSelector]]; - invocation.target = jsonString; - invocation.selector = jsonKitSelector; - int parseOptions = 0; - [invocation setArgument:&parseOptions atIndex:2]; // arguments 0 and 1 are self and _cmd respectively, automatically set by NSInvocation - [invocation setArgument:&error atIndex:3]; - [invocation invoke]; - [invocation getReturnValue:&feedResult]; - } else if (sbJSONSelector && [jsonString respondsToSelector:sbJSONSelector]) { - // now try SBJson - NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[jsonString methodSignatureForSelector:sbJSONSelector]]; - invocation.target = jsonString; - invocation.selector = sbJSONSelector; - [invocation invoke]; - [invocation getReturnValue:&feedResult]; - } else if (yajlSelector && [jsonString respondsToSelector:yajlSelector]) { - NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[jsonString methodSignatureForSelector:yajlSelector]]; - invocation.target = jsonString; - invocation.selector = yajlSelector; - - NSUInteger yajlParserOptions = 0; - [invocation setArgument:&yajlParserOptions atIndex:2]; // arguments 0 and 1 are self and _cmd respectively, automatically set by NSInvocation - [invocation setArgument:&error atIndex:3]; - - [invocation invoke]; - [invocation getReturnValue:&feedResult]; - } else { - NSLog(@"Error: You need a JSON Framework in your runtime!"); - [self doesNotRecognizeSelector:_cmd]; - } + NSUInteger yajlParserOptions = 0; + [invocation setArgument:&yajlParserOptions atIndex:2]; // arguments 0 and 1 are self and _cmd respectively, automatically set by NSInvocation + [invocation setArgument:&error atIndex:3]; + + [invocation invoke]; + [invocation getReturnValue:&feedResult]; + } else { + NSLog(@"Error: You need a JSON Framework in your runtime!"); + [self doesNotRecognizeSelector:_cmd]; + } #endif - - if (error) { - BWHockeyLog(@"Error while parsing response feed: %@", [error localizedDescription]); - [self reportError_:error]; - return nil; - } - - return feedResult; + + if (error) { + BWHockeyLog(@"Error while parsing response feed: %@", [error localizedDescription]); + [self reportError_:error]; + return nil; + } + + return feedResult; } /////////////////////////////////////////////////////////////////////////////////////////////////// @@ -701,238 +701,238 @@ static NSString *kHockeyErrorDomain = @"HockeyErrorDomain"; #pragma mark RequestComments - (BOOL)shouldCheckForUpdates { - BOOL checkForUpdate = NO; - switch (self.updateSetting) { - case HockeyUpdateCheckStartup: - checkForUpdate = YES; - break; - case HockeyUpdateCheckDaily: { - NSTimeInterval dateDiff = fabs([self.lastCheck timeIntervalSinceNow]); - if (dateDiff != 0) - dateDiff = dateDiff / (60*60*24); - - checkForUpdate = (dateDiff >= 1); - break; - } - case HockeyUpdateCheckManually: - checkForUpdate = NO; - break; - default: - break; + BOOL checkForUpdate = NO; + switch (self.updateSetting) { + case HockeyUpdateCheckStartup: + checkForUpdate = YES; + break; + case HockeyUpdateCheckDaily: { + NSTimeInterval dateDiff = fabs([self.lastCheck timeIntervalSinceNow]); + if (dateDiff != 0) + dateDiff = dateDiff / (60*60*24); + + checkForUpdate = (dateDiff >= 1); + break; } - return checkForUpdate; + case HockeyUpdateCheckManually: + checkForUpdate = NO; + break; + default: + break; + } + return checkForUpdate; } - (void)checkForAuthorization { - NSMutableString *parameter = [NSMutableString stringWithFormat:@"api/2/apps/%@", [self encodedAppIdentifier_]]; + NSMutableString *parameter = [NSMutableString stringWithFormat:@"api/2/apps/%@", [self encodedAppIdentifier_]]; + + [parameter appendFormat:@"?format=json&authorize=yes&app_version=%@&udid=%@", + [[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"] bw_URLEncodedString], + ([self isAppStoreEnvironment] ? @"appstore" : [[self deviceIdentifier] bw_URLEncodedString]) + ]; + + // build request & send + NSString *url = [NSString stringWithFormat:@"%@%@", self.updateURL, parameter]; + BWHockeyLog(@"sending api request to %@", url); + + NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:url] cachePolicy:1 timeoutInterval:10.0]; + [request setHTTPMethod:@"GET"]; + [request setValue:@"Hockey/iOS" forHTTPHeaderField:@"User-Agent"]; + + NSURLResponse *response = nil; + NSError *error = NULL; + BOOL failed = YES; + + NSData *responseData = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error]; + + if ([responseData length]) { + NSString *responseString = [[[NSString alloc] initWithBytes:[responseData bytes] length:[responseData length] encoding: NSUTF8StringEncoding] autorelease]; - [parameter appendFormat:@"?format=json&authorize=yes&app_version=%@&udid=%@", - [[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"] bw_URLEncodedString], - ([self isAppStoreEnvironment] ? @"appstore" : [[self deviceIdentifier] bw_URLEncodedString]) - ]; + NSDictionary *feedDict = (NSDictionary *)[self parseJSONResultString:responseString]; - // build request & send - NSString *url = [NSString stringWithFormat:@"%@%@", self.updateURL, parameter]; - BWHockeyLog(@"sending api request to %@", url); - - NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:url] cachePolicy:1 timeoutInterval:10.0]; - [request setHTTPMethod:@"GET"]; - [request setValue:@"Hockey/iOS" forHTTPHeaderField:@"User-Agent"]; - - NSURLResponse *response = nil; - NSError *error = NULL; - BOOL failed = YES; - - NSData *responseData = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error]; - - if ([responseData length]) { - NSString *responseString = [[[NSString alloc] initWithBytes:[responseData bytes] length:[responseData length] encoding: NSUTF8StringEncoding] autorelease]; + // server returned empty response? + if (![feedDict count]) { + [self reportError_:[NSError errorWithDomain:kHockeyErrorDomain code:HockeyAPIServerReturnedEmptyResponse userInfo: + [NSDictionary dictionaryWithObjectsAndKeys:@"Server returned empty response.", NSLocalizedDescriptionKey, nil]]]; + return; + } else { + BWHockeyLog(@"Received API response: %@", responseString); + NSString *token = [[feedDict objectForKey:@"authcode"] lowercaseString]; + failed = NO; + if ([[self authenticationToken] compare:token] == NSOrderedSame) { + // identical token, activate this version - NSDictionary *feedDict = (NSDictionary *)[self parseJSONResultString:responseString]; + // store the new data + [[NSUserDefaults standardUserDefaults] setObject:[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"] forKey:kHockeyAuthorizedVersion]; + [[NSUserDefaults standardUserDefaults] setObject:token forKey:kHockeyAuthorizedToken]; + [[NSUserDefaults standardUserDefaults] synchronize]; - // server returned empty response? - if (![feedDict count]) { - [self reportError_:[NSError errorWithDomain:kHockeyErrorDomain code:HockeyAPIServerReturnedEmptyResponse userInfo: - [NSDictionary dictionaryWithObjectsAndKeys:@"Server returned empty response.", NSLocalizedDescriptionKey, nil]]]; - return; - } else { - BWHockeyLog(@"Received API response: %@", responseString); - NSString *token = [[feedDict objectForKey:@"authcode"] lowercaseString]; - failed = NO; - if ([[self authenticationToken] compare:token] == NSOrderedSame) { - // identical token, activate this version - - // store the new data - [[NSUserDefaults standardUserDefaults] setObject:[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"] forKey:kHockeyAuthorizedVersion]; - [[NSUserDefaults standardUserDefaults] setObject:token forKey:kHockeyAuthorizedToken]; - [[NSUserDefaults standardUserDefaults] synchronize]; - - self.requireAuthorization = NO; - self.authorizeView = nil; - - // now continue with an update check right away - if (self.checkForUpdateOnLaunch) { - [self checkForUpdate]; - } - } else { - // different token, block this version - BWHockeyLog(@"AUTH FAILURE: %@", [self authenticationToken]); - - // store the new data - [[NSUserDefaults standardUserDefaults] setObject:[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"] forKey:kHockeyAuthorizedVersion]; - [[NSUserDefaults standardUserDefaults] setObject:token forKey:kHockeyAuthorizedToken]; - [[NSUserDefaults standardUserDefaults] synchronize]; - - [self showAuthorizationScreen:BWHockeyLocalize(@"HockeyAuthorizationDenied") image:@"authorize_denied.png"]; - } + self.requireAuthorization = NO; + self.authorizeView = nil; + + // now continue with an update check right away + if (self.checkForUpdateOnLaunch) { + [self checkForUpdate]; } + } else { + // different token, block this version + BWHockeyLog(@"AUTH FAILURE: %@", [self authenticationToken]); + // store the new data + [[NSUserDefaults standardUserDefaults] setObject:[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"] forKey:kHockeyAuthorizedVersion]; + [[NSUserDefaults standardUserDefaults] setObject:token forKey:kHockeyAuthorizedToken]; + [[NSUserDefaults standardUserDefaults] synchronize]; + + [self showAuthorizationScreen:BWHockeyLocalize(@"HockeyAuthorizationDenied") image:@"authorize_denied.png"]; + } } - if (failed) { - [self showAuthorizationScreen:BWHockeyLocalize(@"HockeyAuthorizationOffline") image:@"authorize_request.png"]; - } + } + + if (failed) { + [self showAuthorizationScreen:BWHockeyLocalize(@"HockeyAuthorizationOffline") image:@"authorize_request.png"]; + } } - (void)checkForUpdate { - if (self.requireAuthorization) return; - if (self.isUpdateAvailable && [self.app.mandatory boolValue]) { - [self showCheckForUpdateAlert_]; - return; - } - [self checkForUpdateShowFeedback:NO]; + if (self.requireAuthorization) return; + if (self.isUpdateAvailable && [self.app.mandatory boolValue]) { + [self showCheckForUpdateAlert_]; + return; + } + [self checkForUpdateShowFeedback:NO]; } - (void)checkForUpdateShowFeedback:(BOOL)feedback { - if (self.isCheckInProgress) return; - - showFeedback_ = feedback; - self.checkInProgress = YES; - - // do we need to update? - if (![self shouldCheckForUpdates] && !currentHockeyViewController_) { - BWHockeyLog(@"update not needed right now"); - self.checkInProgress = NO; - return; - } - - NSMutableString *parameter = [NSMutableString stringWithFormat:@"api/2/apps/%@?format=json&udid=%@", - [[self encodedAppIdentifier_] bw_URLEncodedString], - ([self isAppStoreEnvironment] ? @"appstore" : [[self deviceIdentifier] bw_URLEncodedString])]; - - // add additional statistics if user didn't disable flag - if ([self canSendUserData]) { - [parameter appendFormat:@"&app_version=%@&os=iOS&os_version=%@&device=%@&lang=%@&first_start_at=%@", - [[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"] bw_URLEncodedString], - [[[UIDevice currentDevice] systemVersion] bw_URLEncodedString], - [[self getDevicePlatform_] bw_URLEncodedString], - [[[[NSBundle mainBundle] preferredLocalizations] objectAtIndex:0] bw_URLEncodedString], - [[self installationDateString] bw_URLEncodedString] - ]; - if ([self canSendUsageTime]) { - [parameter appendFormat:@"&usage_time=%@", - [[self currentUsageString] bw_URLEncodedString] - ]; - } + if (self.isCheckInProgress) return; + + showFeedback_ = feedback; + self.checkInProgress = YES; + + // do we need to update? + if (![self shouldCheckForUpdates] && !currentHockeyViewController_) { + BWHockeyLog(@"update not needed right now"); + self.checkInProgress = NO; + return; + } + + NSMutableString *parameter = [NSMutableString stringWithFormat:@"api/2/apps/%@?format=json&udid=%@", + [[self encodedAppIdentifier_] bw_URLEncodedString], + ([self isAppStoreEnvironment] ? @"appstore" : [[self deviceIdentifier] bw_URLEncodedString])]; + + // add additional statistics if user didn't disable flag + if ([self canSendUserData]) { + [parameter appendFormat:@"&app_version=%@&os=iOS&os_version=%@&device=%@&lang=%@&first_start_at=%@", + [[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"] bw_URLEncodedString], + [[[UIDevice currentDevice] systemVersion] bw_URLEncodedString], + [[self getDevicePlatform_] bw_URLEncodedString], + [[[[NSBundle mainBundle] preferredLocalizations] objectAtIndex:0] bw_URLEncodedString], + [[self installationDateString] bw_URLEncodedString] + ]; + if ([self canSendUsageTime]) { + [parameter appendFormat:@"&usage_time=%@", + [[self currentUsageString] bw_URLEncodedString] + ]; } + } if ([self checkForTracker]) { [parameter appendFormat:@"&jmc=yes"]; } - - // build request & send - NSString *url = [NSString stringWithFormat:@"%@%@", self.updateURL, parameter]; - BWHockeyLog(@"sending api request to %@", url); - - NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:url] cachePolicy:1 timeoutInterval:10.0]; - [request setHTTPMethod:@"GET"]; - [request setValue:@"Hockey/iOS" forHTTPHeaderField:@"User-Agent"]; - [request setValue:@"gzip" forHTTPHeaderField:@"Accept-Encoding"]; - - self.urlConnection = [[[NSURLConnection alloc] initWithRequest:request delegate:self] autorelease]; - if (!urlConnection_) { - self.checkInProgress = NO; - [self reportError_:[NSError errorWithDomain:kHockeyErrorDomain code:HockeyAPIClientCannotCreateConnection userInfo: - [NSDictionary dictionaryWithObjectsAndKeys:@"Url Connection could not be created.", NSLocalizedDescriptionKey, nil]]]; - } + + // build request & send + NSString *url = [NSString stringWithFormat:@"%@%@", self.updateURL, parameter]; + BWHockeyLog(@"sending api request to %@", url); + + NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:url] cachePolicy:1 timeoutInterval:10.0]; + [request setHTTPMethod:@"GET"]; + [request setValue:@"Hockey/iOS" forHTTPHeaderField:@"User-Agent"]; + [request setValue:@"gzip" forHTTPHeaderField:@"Accept-Encoding"]; + + self.urlConnection = [[[NSURLConnection alloc] initWithRequest:request delegate:self] autorelease]; + if (!urlConnection_) { + self.checkInProgress = NO; + [self reportError_:[NSError errorWithDomain:kHockeyErrorDomain code:HockeyAPIClientCannotCreateConnection userInfo: + [NSDictionary dictionaryWithObjectsAndKeys:@"Url Connection could not be created.", NSLocalizedDescriptionKey, nil]]]; + } } - (BOOL)initiateAppDownload { - if ([self isAppStoreEnvironment]) return NO; + if ([self isAppStoreEnvironment]) return NO; - if (!self.isUpdateAvailable) { - BWHockeyLog(@"Warning: No update available. Aborting."); - return NO; - } - - BW_IF_PRE_IOS4 - ( - NSString *message = [NSString stringWithFormat:BWHockeyLocalize(@"HockeyiOS3Message"), self.updateURL]; - UIAlertView *alert = [[[UIAlertView alloc] initWithTitle:BWHockeyLocalize(@"HockeyWarning") message:message delegate:nil cancelButtonTitle:BWHockeyLocalize(@"HockeyOK") otherButtonTitles:nil] autorelease]; - [alert show]; - return NO; - ) - -#if TARGET_IPHONE_SIMULATOR - UIAlertView *alert = [[[UIAlertView alloc] initWithTitle:BWHockeyLocalize(@"HockeyWarning") message:BWHockeyLocalize(@"HockeySimulatorMessage") delegate:nil cancelButtonTitle:BWHockeyLocalize(@"HockeyOK") otherButtonTitles:nil] autorelease]; - [alert show]; + if (!self.isUpdateAvailable) { + BWHockeyLog(@"Warning: No update available. Aborting."); return NO; + } + + BW_IF_PRE_IOS4 + ( + NSString *message = [NSString stringWithFormat:BWHockeyLocalize(@"HockeyiOS3Message"), self.updateURL]; + UIAlertView *alert = [[[UIAlertView alloc] initWithTitle:BWHockeyLocalize(@"HockeyWarning") message:message delegate:nil cancelButtonTitle:BWHockeyLocalize(@"HockeyOK") otherButtonTitles:nil] autorelease]; + [alert show]; + return NO; + ) + +#if TARGET_IPHONE_SIMULATOR + UIAlertView *alert = [[[UIAlertView alloc] initWithTitle:BWHockeyLocalize(@"HockeyWarning") message:BWHockeyLocalize(@"HockeySimulatorMessage") delegate:nil cancelButtonTitle:BWHockeyLocalize(@"HockeyOK") otherButtonTitles:nil] autorelease]; + [alert show]; + return NO; #endif - - NSString *extraParameter = [NSString string]; - if ([self canSendUserData]) { - extraParameter = [NSString stringWithFormat:@"&udid=%@", [self deviceIdentifier]]; - } - - NSString *hockeyAPIURL = [NSString stringWithFormat:@"%@api/2/apps/%@?format=plist%@", self.updateURL, [self encodedAppIdentifier_], extraParameter]; - NSString *iOSUpdateURL = [NSString stringWithFormat:@"itms-services://?action=download-manifest&url=%@", [hockeyAPIURL bw_URLEncodedString]]; - - BWHockeyLog(@"API Server Call: %@, calling iOS with %@", hockeyAPIURL, iOSUpdateURL); - BOOL success = [[UIApplication sharedApplication] openURL:[NSURL URLWithString:iOSUpdateURL]]; - BWHockeyLog(@"System returned: %d", success); - return success; + + NSString *extraParameter = [NSString string]; + if ([self canSendUserData]) { + extraParameter = [NSString stringWithFormat:@"&udid=%@", [self deviceIdentifier]]; + } + + NSString *hockeyAPIURL = [NSString stringWithFormat:@"%@api/2/apps/%@?format=plist%@", self.updateURL, [self encodedAppIdentifier_], extraParameter]; + NSString *iOSUpdateURL = [NSString stringWithFormat:@"itms-services://?action=download-manifest&url=%@", [hockeyAPIURL bw_URLEncodedString]]; + + BWHockeyLog(@"API Server Call: %@, calling iOS with %@", hockeyAPIURL, iOSUpdateURL); + BOOL success = [[UIApplication sharedApplication] openURL:[NSURL URLWithString:iOSUpdateURL]]; + BWHockeyLog(@"System returned: %d", success); + return success; } // checks wether this app version is authorized - (BOOL)appVersionIsAuthorized { - if (self.requireAuthorization && !authenticationSecret_) { - [self reportError_:[NSError errorWithDomain:kHockeyErrorDomain code:HockeyAPIClientAuthorizationMissingSecret userInfo: - [NSDictionary dictionaryWithObjectsAndKeys:@"Authentication secret is not set but required.", NSLocalizedDescriptionKey, nil]]]; - - return NO; - } - - if (!self.requireAuthorization) { - self.authorizeView = nil; - return YES; - } - - HockeyAuthorizationState state = [self authorizationState]; - if (state == HockeyAuthorizationDenied) { - [self showAuthorizationScreen:BWHockeyLocalize(@"HockeyAuthorizationDenied") image:@"authorize_denied.png"]; - } else if (state == HockeyAuthorizationAllowed) { - self.requireAuthorization = NO; - return YES; - } + if (self.requireAuthorization && !authenticationSecret_) { + [self reportError_:[NSError errorWithDomain:kHockeyErrorDomain code:HockeyAPIClientAuthorizationMissingSecret userInfo: + [NSDictionary dictionaryWithObjectsAndKeys:@"Authentication secret is not set but required.", NSLocalizedDescriptionKey, nil]]]; return NO; + } + + if (!self.requireAuthorization) { + self.authorizeView = nil; + return YES; + } + + HockeyAuthorizationState state = [self authorizationState]; + if (state == HockeyAuthorizationDenied) { + [self showAuthorizationScreen:BWHockeyLocalize(@"HockeyAuthorizationDenied") image:@"authorize_denied.png"]; + } else if (state == HockeyAuthorizationAllowed) { + self.requireAuthorization = NO; + return YES; + } + + return NO; } // begin the startup process - (void)startManager { - if (![self appVersionIsAuthorized]) { - if ([self authorizationState] == HockeyAuthorizationPending) { - [self showAuthorizationScreen:BWHockeyLocalize(@"HockeyAuthorizationProgress") image:@"authorize_request.png"]; - - [self performSelector:@selector(checkForAuthorization) withObject:nil afterDelay:0.0f]; - } - } else { - if ([self shouldCheckForUpdates]) { - [self performSelector:@selector(checkForUpdate) withObject:nil afterDelay:0.0f]; - } + if (![self appVersionIsAuthorized]) { + if ([self authorizationState] == HockeyAuthorizationPending) { + [self showAuthorizationScreen:BWHockeyLocalize(@"HockeyAuthorizationProgress") image:@"authorize_request.png"]; + + [self performSelector:@selector(checkForAuthorization) withObject:nil afterDelay:0.0f]; } + } else { + if ([self shouldCheckForUpdates]) { + [self performSelector:@selector(checkForUpdate) withObject:nil afterDelay:0.0f]; + } + } } @@ -941,121 +941,121 @@ static NSString *kHockeyErrorDomain = @"HockeyErrorDomain"; #pragma mark NSURLRequest - (NSURLRequest *)connection:(NSURLConnection *)connection willSendRequest:(NSURLRequest *)request redirectResponse:(NSURLResponse *)redirectResponse { - [self connectionOpened_]; - NSURLRequest *newRequest = request; - if (redirectResponse) { - newRequest = nil; - } - return newRequest; + [self connectionOpened_]; + NSURLRequest *newRequest = request; + if (redirectResponse) { + newRequest = nil; + } + return newRequest; } - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response { - if ([response respondsToSelector:@selector(statusCode)]) { - int statusCode = [((NSHTTPURLResponse *)response) statusCode]; - if (statusCode == 404) { - [connection cancel]; // stop connecting; no more delegate messages - NSString *errorStr = [NSString stringWithFormat:@"Hockey API received HTTP Status Code %d", statusCode]; - [self connectionClosed_]; - [self reportError_:[NSError errorWithDomain:kHockeyErrorDomain code:HockeyAPIServerReturnedInvalidStatus userInfo: - [NSDictionary dictionaryWithObjectsAndKeys:errorStr, NSLocalizedDescriptionKey, nil]]]; - return; - } + if ([response respondsToSelector:@selector(statusCode)]) { + int statusCode = [((NSHTTPURLResponse *)response) statusCode]; + if (statusCode == 404) { + [connection cancel]; // stop connecting; no more delegate messages + NSString *errorStr = [NSString stringWithFormat:@"Hockey API received HTTP Status Code %d", statusCode]; + [self connectionClosed_]; + [self reportError_:[NSError errorWithDomain:kHockeyErrorDomain code:HockeyAPIServerReturnedInvalidStatus userInfo: + [NSDictionary dictionaryWithObjectsAndKeys:errorStr, NSLocalizedDescriptionKey, nil]]]; + return; } - - self.receivedData = [NSMutableData data]; - [receivedData_ setLength:0]; + } + + self.receivedData = [NSMutableData data]; + [receivedData_ setLength:0]; } - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data { - [receivedData_ appendData:data]; + [receivedData_ appendData:data]; } - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error { - [self connectionClosed_]; - self.receivedData = nil; - self.urlConnection = nil; - self.checkInProgress = NO; - [self reportError_:error]; + [self connectionClosed_]; + self.receivedData = nil; + self.urlConnection = nil; + self.checkInProgress = NO; + [self reportError_:error]; } // api call returned, parsing - (void)connectionDidFinishLoading:(NSURLConnection *)connection { - [self connectionClosed_]; - self.checkInProgress = NO; + [self connectionClosed_]; + self.checkInProgress = NO; + + if ([self.receivedData length]) { + NSString *responseString = [[[NSString alloc] initWithBytes:[receivedData_ bytes] length:[receivedData_ length] encoding: NSUTF8StringEncoding] autorelease]; + BWHockeyLog(@"Received API response: %@", responseString); - if ([self.receivedData length]) { - NSString *responseString = [[[NSString alloc] initWithBytes:[receivedData_ bytes] length:[receivedData_ length] encoding: NSUTF8StringEncoding] autorelease]; - BWHockeyLog(@"Received API response: %@", responseString); - - id json = [self parseJSONResultString:responseString]; - self.trackerConfig = ([self checkForTracker] ? [json valueForKey:@"tracker"] : nil); - - if (![self isAppStoreEnvironment]) { - NSArray *feedArray = (NSArray *)([self checkForTracker] ? [json valueForKey:@"versions"] : json); - - self.receivedData = nil; - self.urlConnection = nil; - - // remember that we just checked the server - self.lastCheck = [NSDate date]; - - // server returned empty response? - if (![feedArray count]) { - [self reportError_:[NSError errorWithDomain:kHockeyErrorDomain code:HockeyAPIServerReturnedEmptyResponse userInfo: - [NSDictionary dictionaryWithObjectsAndKeys:@"Server returned empty response.", NSLocalizedDescriptionKey, nil]]]; - return; - } else { - lastCheckFailed_ = NO; - } - - - NSString *currentAppCacheVersion = [[[self app].version copy] autorelease]; - - // clear cache and reload with new data - NSMutableArray *tmpApps = [NSMutableArray arrayWithCapacity:[feedArray count]]; - for (NSDictionary *dict in feedArray) { - BWApp *app = [BWApp appFromDict:dict]; - if ([app isValid]) { - [tmpApps addObject:app]; - } else { - [self reportError_:[NSError errorWithDomain:kHockeyErrorDomain code:HockeyAPIServerReturnedInvalidData userInfo: - [NSDictionary dictionaryWithObjectsAndKeys:@"Invalid data received from server.", NSLocalizedDescriptionKey, nil]]]; - } - } - // only set if different! - if (![self.apps isEqualToArray:tmpApps]) { - self.apps = [[tmpApps copy] autorelease]; - } - [self saveAppCache_]; - - [self checkUpdateAvailable_]; - BOOL newVersionDiffersFromCachedVersion = ![self.app.version isEqualToString:currentAppCacheVersion]; - - // show alert if we are on the latest & greatest - if (showFeedback_ && !self.isUpdateAvailable) { - // use currentVersionString, as version still may differ (e.g. server: 1.2, client: 1.3) - NSString *versionString = [self currentAppVersion]; - NSString *shortVersionString = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"]; - shortVersionString = shortVersionString ? [NSString stringWithFormat:@"%@ ", shortVersionString] : @""; - versionString = [shortVersionString length] ? [NSString stringWithFormat:@"(%@)", versionString] : versionString; - NSString *currentVersionString = [NSString stringWithFormat:@"%@ %@ %@%@", self.app.name, BWHockeyLocalize(@"HockeyVersion"), shortVersionString, versionString]; - NSString *alertMsg = [NSString stringWithFormat:BWHockeyLocalize(@"HockeyNoUpdateNeededMessage"), currentVersionString]; - UIAlertView *alert = [[UIAlertView alloc] initWithTitle:BWHockeyLocalize(@"HockeyNoUpdateNeededTitle") message:alertMsg delegate:nil cancelButtonTitle:BWHockeyLocalize(@"HockeyOK") otherButtonTitles:nil]; - [alert show]; - [alert release]; - } - - if (self.isUpdateAvailable && (self.alwaysShowUpdateReminder || newVersionDiffersFromCachedVersion || [self.app.mandatory boolValue])) { - if (updateAvailable_ && !currentHockeyViewController_) { - [self showCheckForUpdateAlert_]; - } - } - showFeedback_ = NO; - } - } else { + id json = [self parseJSONResultString:responseString]; + self.trackerConfig = ([self checkForTracker] ? [json valueForKey:@"tracker"] : nil); + + if (![self isAppStoreEnvironment]) { + NSArray *feedArray = (NSArray *)([self checkForTracker] ? [json valueForKey:@"versions"] : json); + + self.receivedData = nil; + self.urlConnection = nil; + + // remember that we just checked the server + self.lastCheck = [NSDate date]; + + // server returned empty response? + if (![feedArray count]) { [self reportError_:[NSError errorWithDomain:kHockeyErrorDomain code:HockeyAPIServerReturnedEmptyResponse userInfo: - [NSDictionary dictionaryWithObjectsAndKeys:@"Server returned an empty response.", NSLocalizedDescriptionKey, nil]]]; + [NSDictionary dictionaryWithObjectsAndKeys:@"Server returned empty response.", NSLocalizedDescriptionKey, nil]]]; + return; + } else { + lastCheckFailed_ = NO; + } + + + NSString *currentAppCacheVersion = [[[self app].version copy] autorelease]; + + // clear cache and reload with new data + NSMutableArray *tmpApps = [NSMutableArray arrayWithCapacity:[feedArray count]]; + for (NSDictionary *dict in feedArray) { + BWApp *app = [BWApp appFromDict:dict]; + if ([app isValid]) { + [tmpApps addObject:app]; + } else { + [self reportError_:[NSError errorWithDomain:kHockeyErrorDomain code:HockeyAPIServerReturnedInvalidData userInfo: + [NSDictionary dictionaryWithObjectsAndKeys:@"Invalid data received from server.", NSLocalizedDescriptionKey, nil]]]; + } + } + // only set if different! + if (![self.apps isEqualToArray:tmpApps]) { + self.apps = [[tmpApps copy] autorelease]; + } + [self saveAppCache_]; + + [self checkUpdateAvailable_]; + BOOL newVersionDiffersFromCachedVersion = ![self.app.version isEqualToString:currentAppCacheVersion]; + + // show alert if we are on the latest & greatest + if (showFeedback_ && !self.isUpdateAvailable) { + // use currentVersionString, as version still may differ (e.g. server: 1.2, client: 1.3) + NSString *versionString = [self currentAppVersion]; + NSString *shortVersionString = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"]; + shortVersionString = shortVersionString ? [NSString stringWithFormat:@"%@ ", shortVersionString] : @""; + versionString = [shortVersionString length] ? [NSString stringWithFormat:@"(%@)", versionString] : versionString; + NSString *currentVersionString = [NSString stringWithFormat:@"%@ %@ %@%@", self.app.name, BWHockeyLocalize(@"HockeyVersion"), shortVersionString, versionString]; + NSString *alertMsg = [NSString stringWithFormat:BWHockeyLocalize(@"HockeyNoUpdateNeededMessage"), currentVersionString]; + UIAlertView *alert = [[UIAlertView alloc] initWithTitle:BWHockeyLocalize(@"HockeyNoUpdateNeededTitle") message:alertMsg delegate:nil cancelButtonTitle:BWHockeyLocalize(@"HockeyOK") otherButtonTitles:nil]; + [alert show]; + [alert release]; + } + + if (self.isUpdateAvailable && (self.alwaysShowUpdateReminder || newVersionDiffersFromCachedVersion || [self.app.mandatory boolValue])) { + if (updateAvailable_ && !currentHockeyViewController_) { + [self showCheckForUpdateAlert_]; + } + } + showFeedback_ = NO; } + } else { + [self reportError_:[NSError errorWithDomain:kHockeyErrorDomain code:HockeyAPIServerReturnedEmptyResponse userInfo: + [NSDictionary dictionaryWithObjectsAndKeys:@"Server returned an empty response.", NSLocalizedDescriptionKey, nil]]]; + } } @@ -1064,131 +1064,131 @@ static NSString *kHockeyErrorDomain = @"HockeyErrorDomain"; #pragma mark Properties - (void)setCurrentHockeyViewController:(BWHockeyViewController *)aCurrentHockeyViewController { - if (currentHockeyViewController_ != aCurrentHockeyViewController) { - [currentHockeyViewController_ release]; - currentHockeyViewController_ = [aCurrentHockeyViewController retain]; - //BWHockeyLog(@"active hockey view controller: %@", aCurrentHockeyViewController); - } + if (currentHockeyViewController_ != aCurrentHockeyViewController) { + [currentHockeyViewController_ release]; + currentHockeyViewController_ = [aCurrentHockeyViewController retain]; + //BWHockeyLog(@"active hockey view controller: %@", aCurrentHockeyViewController); + } } - (void)setUpdateURL:(NSString *)anUpdateURL { - // ensure url ends with a trailing slash - if (![anUpdateURL hasSuffix:@"/"]) { - anUpdateURL = [NSString stringWithFormat:@"%@/", anUpdateURL]; - } - - BW_IF_IOS4_OR_GREATER( - // register/deregister logic - NSNotificationCenter *dnc = [NSNotificationCenter defaultCenter]; - if (!updateURL_ && anUpdateURL) { - [dnc addObserver:self selector:@selector(startUsage) name:UIApplicationDidBecomeActiveNotification object:nil]; - [dnc addObserver:self selector:@selector(stopUsage) name:UIApplicationWillResignActiveNotification object:nil]; - } else if (updateURL_ && !anUpdateURL) { - [dnc removeObserver:self name:UIApplicationDidBecomeActiveNotification object:nil]; - [dnc removeObserver:self name:UIApplicationWillResignActiveNotification object:nil]; - } - ) - - if (updateURL_ != anUpdateURL) { - [updateURL_ release]; - updateURL_ = [anUpdateURL copy]; - } - - [self performSelector:@selector(startManager) withObject:nil afterDelay:0.0f]; + // ensure url ends with a trailing slash + if (![anUpdateURL hasSuffix:@"/"]) { + anUpdateURL = [NSString stringWithFormat:@"%@/", anUpdateURL]; + } + + BW_IF_IOS4_OR_GREATER( + // register/deregister logic + NSNotificationCenter *dnc = [NSNotificationCenter defaultCenter]; + if (!updateURL_ && anUpdateURL) { + [dnc addObserver:self selector:@selector(startUsage) name:UIApplicationDidBecomeActiveNotification object:nil]; + [dnc addObserver:self selector:@selector(stopUsage) name:UIApplicationWillResignActiveNotification object:nil]; + } else if (updateURL_ && !anUpdateURL) { + [dnc removeObserver:self name:UIApplicationDidBecomeActiveNotification object:nil]; + [dnc removeObserver:self name:UIApplicationWillResignActiveNotification object:nil]; + } + ) + + if (updateURL_ != anUpdateURL) { + [updateURL_ release]; + updateURL_ = [anUpdateURL copy]; + } + + [self performSelector:@selector(startManager) withObject:nil afterDelay:0.0f]; } - (void)setAppIdentifier:(NSString *)anAppIdentifier { - if (appIdentifier_ != anAppIdentifier) { - [appIdentifier_ release]; - appIdentifier_ = [anAppIdentifier copy]; - } - - [self setUpdateURL:@"https://rink.hockeyapp.net/"]; + if (appIdentifier_ != anAppIdentifier) { + [appIdentifier_ release]; + appIdentifier_ = [anAppIdentifier copy]; + } + + [self setUpdateURL:@"https://rink.hockeyapp.net/"]; } - (void)setCheckForUpdateOnLaunch:(BOOL)flag { - if (checkForUpdateOnLaunch_ != flag) { - checkForUpdateOnLaunch_ = flag; - BW_IF_IOS4_OR_GREATER( - NSNotificationCenter *dnc = [NSNotificationCenter defaultCenter]; - if (flag) { - [dnc addObserver:self selector:@selector(checkForUpdate) name:UIApplicationDidBecomeActiveNotification object:nil]; - } else { - [dnc removeObserver:self name:UIApplicationDidBecomeActiveNotification object:nil]; - } - ) - } + if (checkForUpdateOnLaunch_ != flag) { + checkForUpdateOnLaunch_ = flag; + BW_IF_IOS4_OR_GREATER( + NSNotificationCenter *dnc = [NSNotificationCenter defaultCenter]; + if (flag) { + [dnc addObserver:self selector:@selector(checkForUpdate) name:UIApplicationDidBecomeActiveNotification object:nil]; + } else { + [dnc removeObserver:self name:UIApplicationDidBecomeActiveNotification object:nil]; + } + ) + } } - (void)setUserAllowsSendUserData:(BOOL)flag { - userAllowsSendUserData_ = flag; - - [[NSUserDefaults standardUserDefaults] setObject:[NSNumber numberWithInteger:userAllowsSendUserData_] forKey:kHockeyAllowUserSetting]; - [[NSUserDefaults standardUserDefaults] synchronize]; + userAllowsSendUserData_ = flag; + + [[NSUserDefaults standardUserDefaults] setObject:[NSNumber numberWithInteger:userAllowsSendUserData_] forKey:kHockeyAllowUserSetting]; + [[NSUserDefaults standardUserDefaults] synchronize]; } - (void)setUserAllowsSendUsageTime:(BOOL)flag { - userAllowsSendUsageTime_ = flag; - - [[NSUserDefaults standardUserDefaults] setObject:[NSNumber numberWithInteger:userAllowsSendUsageTime_] forKey:kHockeyAllowUsageSetting]; - [[NSUserDefaults standardUserDefaults] synchronize]; + userAllowsSendUsageTime_ = flag; + + [[NSUserDefaults standardUserDefaults] setObject:[NSNumber numberWithInteger:userAllowsSendUsageTime_] forKey:kHockeyAllowUsageSetting]; + [[NSUserDefaults standardUserDefaults] synchronize]; } - (NSString *)currentAppVersion { - return currentAppVersion_; + return currentAppVersion_; } - (void)setUpdateSetting:(HockeyUpdateSetting)anUpdateSetting { - if (anUpdateSetting > HockeyUpdateCheckManually) { - updateSetting_ = HockeyUpdateCheckStartup; - } - - updateSetting_ = anUpdateSetting; - [[NSUserDefaults standardUserDefaults] setObject:[NSNumber numberWithInteger:updateSetting_] forKey:kHockeyAutoUpdateSetting]; - [[NSUserDefaults standardUserDefaults] synchronize]; + if (anUpdateSetting > HockeyUpdateCheckManually) { + updateSetting_ = HockeyUpdateCheckStartup; + } + + updateSetting_ = anUpdateSetting; + [[NSUserDefaults standardUserDefaults] setObject:[NSNumber numberWithInteger:updateSetting_] forKey:kHockeyAutoUpdateSetting]; + [[NSUserDefaults standardUserDefaults] synchronize]; } - (void)setLastCheck:(NSDate *)aLastCheck { - if (lastCheck_ != aLastCheck) { - [lastCheck_ release]; - lastCheck_ = [aLastCheck copy]; - - [[NSUserDefaults standardUserDefaults] setObject:lastCheck_ forKey:kDateOfLastHockeyCheck]; - [[NSUserDefaults standardUserDefaults] synchronize]; - } + if (lastCheck_ != aLastCheck) { + [lastCheck_ release]; + lastCheck_ = [aLastCheck copy]; + + [[NSUserDefaults standardUserDefaults] setObject:lastCheck_ forKey:kDateOfLastHockeyCheck]; + [[NSUserDefaults standardUserDefaults] synchronize]; + } } - (void)setApps:(NSArray *)anApps { - if (apps_ != anApps || !apps_) { - [apps_ release]; - [self willChangeValueForKey:@"apps"]; - - // populate with default values (if empty) - if (![anApps count]) { - BWApp *defaultApp = [[[BWApp alloc] init] autorelease]; - defaultApp.name = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleName"]; - defaultApp.version = currentAppVersion_; - defaultApp.shortVersion = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"]; - apps_ = [[NSArray arrayWithObject:defaultApp] retain]; - }else { - apps_ = [anApps copy]; - } - [self didChangeValueForKey:@"apps"]; - } + if (apps_ != anApps || !apps_) { + [apps_ release]; + [self willChangeValueForKey:@"apps"]; + + // populate with default values (if empty) + if (![anApps count]) { + BWApp *defaultApp = [[[BWApp alloc] init] autorelease]; + defaultApp.name = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleName"]; + defaultApp.version = currentAppVersion_; + defaultApp.shortVersion = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"]; + apps_ = [[NSArray arrayWithObject:defaultApp] retain]; + }else { + apps_ = [anApps copy]; + } + [self didChangeValueForKey:@"apps"]; + } } - (BWApp *)app { - BWApp *app = [apps_ objectAtIndex:0]; - return app; + BWApp *app = [apps_ objectAtIndex:0]; + return app; } - (void)setAuthorizeView:(UIView *)anAuthorizeView { - if (authorizeView_ != anAuthorizeView) { - [authorizeView_ removeFromSuperview]; - [authorizeView_ release]; - authorizeView_ = [anAuthorizeView retain]; - } + if (authorizeView_ != anAuthorizeView) { + [authorizeView_ removeFromSuperview]; + [authorizeView_ release]; + authorizeView_ = [anAuthorizeView retain]; + } } /////////////////////////////////////////////////////////////////////////////////////////////////// @@ -1197,23 +1197,23 @@ static NSString *kHockeyErrorDomain = @"HockeyErrorDomain"; // invoke the selected action from the actionsheet for a location element - (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex { - if ([alertView tag] == 2) { - [self initiateAppDownload]; - updateAlertShowing_ = NO; - return; - } else if ([alertView tag] == 1) { - [self alertFallback:[alertView message]]; - return; - } - + if ([alertView tag] == 2) { + [self initiateAppDownload]; updateAlertShowing_ = NO; - if (buttonIndex == [alertView firstOtherButtonIndex]) { - // YES button has been clicked - [self showUpdateView]; - } else if (buttonIndex == [alertView firstOtherButtonIndex] + 1) { - // YES button has been clicked - [self initiateAppDownload]; - } + return; + } else if ([alertView tag] == 1) { + [self alertFallback:[alertView message]]; + return; + } + + updateAlertShowing_ = NO; + if (buttonIndex == [alertView firstOtherButtonIndex]) { + // YES button has been clicked + [self showUpdateView]; + } else if (buttonIndex == [alertView firstOtherButtonIndex] + 1) { + // YES button has been clicked + [self initiateAppDownload]; + } } @end diff --git a/Classes/BWHockeySettingsViewController.h b/Classes/BWHockeySettingsViewController.h index 7e318f417a..f0683baf14 100644 --- a/Classes/BWHockeySettingsViewController.h +++ b/Classes/BWHockeySettingsViewController.h @@ -7,11 +7,11 @@ // #import + @class BWHockeyManager; - @interface BWHockeySettingsViewController : UIViewController { - BWHockeyManager *hockeyManager_; + BWHockeyManager *hockeyManager_; } @property (nonatomic, retain) BWHockeyManager *hockeyManager; diff --git a/Classes/BWHockeySettingsViewController.m b/Classes/BWHockeySettingsViewController.m index cb64432ec1..34d94591e2 100644 --- a/Classes/BWHockeySettingsViewController.m +++ b/Classes/BWHockeySettingsViewController.m @@ -24,41 +24,41 @@ #pragma mark Initialization - (id)init:(BWHockeyManager *)newHockeyManager { - if ((self = [super init])) { - self.hockeyManager = newHockeyManager; - self.title = BWHockeyLocalize(@"HockeySettingsTitle"); - - CGRect frame = self.view.frame; - frame.origin = CGPointZero; - - UITableView *tableView_ = [[[UITableView alloc] initWithFrame:CGRectMake(0, self.view.frame.size.height - 260, self.view.frame.size.width, 260) style:UITableViewStyleGrouped] autorelease]; - tableView_.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleWidth; - - BW_IF_3_2_OR_GREATER( - if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) { - self.view.backgroundColor = BW_RGBCOLOR(200, 202, 204); - tableView_.backgroundColor = BW_RGBCOLOR(200, 202, 204); - } else { - tableView_.frame = frame; - tableView_.autoresizingMask = tableView_.autoresizingMask | UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin; - } - ) - BW_IF_PRE_3_2( - self.navigationItem.rightBarButtonItem = [[[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone - target:self - action:@selector(dismissSettings)] autorelease]; - tableView_.frame = frame; - tableView_.autoresizingMask = tableView_.autoresizingMask | UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin; - ) - - tableView_.delegate = self; - tableView_.dataSource = self; - tableView_.clipsToBounds = NO; - - [self.view addSubview:tableView_]; - - } - return self; + if ((self = [super init])) { + self.hockeyManager = newHockeyManager; + self.title = BWHockeyLocalize(@"HockeySettingsTitle"); + + CGRect frame = self.view.frame; + frame.origin = CGPointZero; + + UITableView *tableView_ = [[[UITableView alloc] initWithFrame:CGRectMake(0, self.view.frame.size.height - 260, self.view.frame.size.width, 260) style:UITableViewStyleGrouped] autorelease]; + tableView_.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleWidth; + + BW_IF_3_2_OR_GREATER( + if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) { + self.view.backgroundColor = BW_RGBCOLOR(200, 202, 204); + tableView_.backgroundColor = BW_RGBCOLOR(200, 202, 204); + } else { + tableView_.frame = frame; + tableView_.autoresizingMask = tableView_.autoresizingMask | UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin; + } + ) + BW_IF_PRE_3_2( + self.navigationItem.rightBarButtonItem = [[[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone + target:self + action:@selector(dismissSettings)] autorelease]; + tableView_.frame = frame; + tableView_.autoresizingMask = tableView_.autoresizingMask | UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin; + ) + + tableView_.delegate = self; + tableView_.dataSource = self; + tableView_.clipsToBounds = NO; + + [self.view addSubview:tableView_]; + + } + return self; } - (id)init { @@ -69,165 +69,165 @@ #pragma mark Table view data source - (int)numberOfSections { - int numberOfSections = 1; - - if ([self.hockeyManager isAllowUserToDisableSendData]) { - if ([self.hockeyManager shouldSendUserData]) numberOfSections++; - if ([self.hockeyManager shouldSendUsageTime]) numberOfSections++; - } - - return numberOfSections; + int numberOfSections = 1; + + if ([self.hockeyManager isAllowUserToDisableSendData]) { + if ([self.hockeyManager shouldSendUserData]) numberOfSections++; + if ([self.hockeyManager shouldSendUsageTime]) numberOfSections++; + } + + return numberOfSections; } - (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section { - if (section == [self numberOfSections] - 1) { - return BWHockeyLocalize(@"HockeySectionCheckTitle"); - } else { - return nil; - } + if (section == [self numberOfSections] - 1) { + return BWHockeyLocalize(@"HockeySectionCheckTitle"); + } else { + return nil; + } } - (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section { - if (section < [self numberOfSections] - 1) { - return 66; - } else return 0; + if (section < [self numberOfSections] - 1) { + return 66; + } else return 0; } - (UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section { - if ([self numberOfSections] > 1 && section < [self numberOfSections] - 1) { - UILabel *footer = [[[UILabel alloc] initWithFrame:CGRectMake(0, 0, 285, 66)] autorelease]; - footer.backgroundColor = [UIColor clearColor]; - footer.numberOfLines = 3; - footer.textAlignment = UITextAlignmentCenter; - footer.adjustsFontSizeToFitWidth = YES; - footer.textColor = [UIColor grayColor]; - footer.font = [UIFont systemFontOfSize:13]; - - if (section == 0 && [self.hockeyManager isAllowUserToDisableSendData] && [self.hockeyManager shouldSendUserData]) { - footer.text = BWHockeyLocalize(@"HockeySettingsUserDataDescription"); - } else if ([self.hockeyManager isAllowUserToDisableSendData] && section < [self numberOfSections]) { - footer.text = BWHockeyLocalize(@"HockeySettingsUsageDataDescription"); - } - - UIView* view = [[[UIView alloc] initWithFrame:CGRectMake(0, 0, 285, footer.frame.size.height + 6 + 11)] autorelease]; - [view setBackgroundColor:[UIColor clearColor]]; - - CGRect frame = footer.frame; - frame.origin.y = 8; - frame.origin.x = 16; - frame.size.width = 285; - footer.frame = frame; - - [view addSubview:footer]; - [view sizeToFit]; - - return view; + if ([self numberOfSections] > 1 && section < [self numberOfSections] - 1) { + UILabel *footer = [[[UILabel alloc] initWithFrame:CGRectMake(0, 0, 285, 66)] autorelease]; + footer.backgroundColor = [UIColor clearColor]; + footer.numberOfLines = 3; + footer.textAlignment = UITextAlignmentCenter; + footer.adjustsFontSizeToFitWidth = YES; + footer.textColor = [UIColor grayColor]; + footer.font = [UIFont systemFontOfSize:13]; + + if (section == 0 && [self.hockeyManager isAllowUserToDisableSendData] && [self.hockeyManager shouldSendUserData]) { + footer.text = BWHockeyLocalize(@"HockeySettingsUserDataDescription"); + } else if ([self.hockeyManager isAllowUserToDisableSendData] && section < [self numberOfSections]) { + footer.text = BWHockeyLocalize(@"HockeySettingsUsageDataDescription"); } - return nil; + UIView* view = [[[UIView alloc] initWithFrame:CGRectMake(0, 0, 285, footer.frame.size.height + 6 + 11)] autorelease]; + [view setBackgroundColor:[UIColor clearColor]]; + + CGRect frame = footer.frame; + frame.origin.y = 8; + frame.origin.x = 16; + frame.size.width = 285; + footer.frame = frame; + + [view addSubview:footer]; + [view sizeToFit]; + + return view; + } + + return nil; } - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { - // Return the number of sections. - return [self numberOfSections]; + // Return the number of sections. + return [self numberOfSections]; } - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { - // Return the number of rows in the section. - if (section == [self numberOfSections] - 1) - return 3; - else - return 1; + // Return the number of rows in the section. + if (section == [self numberOfSections] - 1) + return 3; + else + return 1; } - (void)sendUserData:(UISwitch *)switcher { - [self.hockeyManager setUserAllowsSendUserData:switcher.on]; + [self.hockeyManager setUserAllowsSendUserData:switcher.on]; } - (void)sendUsageData:(UISwitch *)switcher { - [self.hockeyManager setUserAllowsSendUsageTime:switcher.on]; + [self.hockeyManager setUserAllowsSendUsageTime:switcher.on]; } // Customize the appearance of table view cells. - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { + + static NSString *CheckmarkCellIdentifier = @"CheckmarkCell"; + static NSString *SwitchCellIdentifier = @"SwitchCell"; + + NSString *requiredIdentifier = nil; + UITableViewCellStyle cellStyle = UITableViewCellStyleSubtitle; + + if ((NSInteger)indexPath.section == [self numberOfSections] - 1) { + cellStyle = UITableViewCellStyleDefault; + requiredIdentifier = CheckmarkCellIdentifier; + } else { + cellStyle = UITableViewCellStyleValue1; + requiredIdentifier = SwitchCellIdentifier; + } + + UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:requiredIdentifier]; + if (cell == nil) { + cell = [[[UITableViewCell alloc] initWithStyle:cellStyle reuseIdentifier:requiredIdentifier] autorelease]; + } + + cell.accessoryType = UITableViewCellAccessoryNone; + cell.selectionStyle = UITableViewCellSelectionStyleNone; + + // Configure the cell... + if ((NSInteger)indexPath.section == [self numberOfSections] - 1) { + cell.selectionStyle = UITableViewCellSelectionStyleBlue; - static NSString *CheckmarkCellIdentifier = @"CheckmarkCell"; - static NSString *SwitchCellIdentifier = @"SwitchCell"; - - NSString *requiredIdentifier = nil; - UITableViewCellStyle cellStyle = UITableViewCellStyleSubtitle; - - if ((NSInteger)indexPath.section == [self numberOfSections] - 1) { - cellStyle = UITableViewCellStyleDefault; - requiredIdentifier = CheckmarkCellIdentifier; + // update check selection + HockeyUpdateSetting hockeyAutoUpdateSetting = [[BWHockeyManager sharedHockeyManager] updateSetting]; + if (indexPath.row == 0) { + // on startup + cell.textLabel.text = BWHockeyLocalize(@"HockeySectionCheckStartup"); + if (hockeyAutoUpdateSetting == HockeyUpdateCheckStartup) { + cell.accessoryType = UITableViewCellAccessoryCheckmark; + } + } else if (indexPath.row == 1) { + // daily + cell.textLabel.text = BWHockeyLocalize(@"HockeySectionCheckDaily"); + if (hockeyAutoUpdateSetting == HockeyUpdateCheckDaily) { + cell.accessoryType = UITableViewCellAccessoryCheckmark; + } } else { - cellStyle = UITableViewCellStyleValue1; - requiredIdentifier = SwitchCellIdentifier; + // manually + cell.textLabel.text = BWHockeyLocalize(@"HockeySectionCheckManually"); + if (hockeyAutoUpdateSetting == HockeyUpdateCheckManually) { + cell.accessoryType = UITableViewCellAccessoryCheckmark; + } + } + } else { + UISwitch *toggleSwitch = [[[UISwitch alloc] initWithFrame:CGRectZero] autorelease]; + + if (indexPath.section == 0 && [self.hockeyManager shouldSendUserData] && [self.hockeyManager isAllowUserToDisableSendData]) { + // send user data + cell.textLabel.text = BWHockeyLocalize(@"HockeySettingsUserData"); + [toggleSwitch addTarget:self action:@selector(sendUserData:) + forControlEvents:UIControlEventValueChanged]; + [toggleSwitch setOn:[self.hockeyManager doesUserAllowsSendUserData]]; + + } else if ([self.hockeyManager shouldSendUsageTime] && [self.hockeyManager isAllowUserToDisableSendData]) { + // send usage time + cell.textLabel.text = BWHockeyLocalize(@"HockeySettingsUsageData"); + [toggleSwitch addTarget:self action:@selector(sendUsageData:) + forControlEvents:UIControlEventValueChanged]; + [toggleSwitch setOn:[self.hockeyManager doesUserAllowsSendUsageTime]]; } - UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:requiredIdentifier]; - if (cell == nil) { - cell = [[[UITableViewCell alloc] initWithStyle:cellStyle reuseIdentifier:requiredIdentifier] autorelease]; - } + cell.accessoryView = toggleSwitch; - cell.accessoryType = UITableViewCellAccessoryNone; - cell.selectionStyle = UITableViewCellSelectionStyleNone; - - // Configure the cell... - if ((NSInteger)indexPath.section == [self numberOfSections] - 1) { - cell.selectionStyle = UITableViewCellSelectionStyleBlue; - - // update check selection - HockeyUpdateSetting hockeyAutoUpdateSetting = [[BWHockeyManager sharedHockeyManager] updateSetting]; - if (indexPath.row == 0) { - // on startup - cell.textLabel.text = BWHockeyLocalize(@"HockeySectionCheckStartup"); - if (hockeyAutoUpdateSetting == HockeyUpdateCheckStartup) { - cell.accessoryType = UITableViewCellAccessoryCheckmark; - } - } else if (indexPath.row == 1) { - // daily - cell.textLabel.text = BWHockeyLocalize(@"HockeySectionCheckDaily"); - if (hockeyAutoUpdateSetting == HockeyUpdateCheckDaily) { - cell.accessoryType = UITableViewCellAccessoryCheckmark; - } - } else { - // manually - cell.textLabel.text = BWHockeyLocalize(@"HockeySectionCheckManually"); - if (hockeyAutoUpdateSetting == HockeyUpdateCheckManually) { - cell.accessoryType = UITableViewCellAccessoryCheckmark; - } - } - } else { - UISwitch *toggleSwitch = [[[UISwitch alloc] initWithFrame:CGRectZero] autorelease]; - - if (indexPath.section == 0 && [self.hockeyManager shouldSendUserData] && [self.hockeyManager isAllowUserToDisableSendData]) { - // send user data - cell.textLabel.text = BWHockeyLocalize(@"HockeySettingsUserData"); - [toggleSwitch addTarget:self action:@selector(sendUserData:) - forControlEvents:UIControlEventValueChanged]; - [toggleSwitch setOn:[self.hockeyManager doesUserAllowsSendUserData]]; - - } else if ([self.hockeyManager shouldSendUsageTime] && [self.hockeyManager isAllowUserToDisableSendData]) { - // send usage time - cell.textLabel.text = BWHockeyLocalize(@"HockeySettingsUsageData"); - [toggleSwitch addTarget:self action:@selector(sendUsageData:) - forControlEvents:UIControlEventValueChanged]; - [toggleSwitch setOn:[self.hockeyManager doesUserAllowsSendUsageTime]]; - } - - cell.accessoryView = toggleSwitch; - - } - - return cell; + } + + return cell; } @@ -235,21 +235,21 @@ #pragma mark Table view delegate - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { - [tableView deselectRowAtIndexPath:indexPath animated:YES]; - - // update check interval selection - if (indexPath.row == 0) { - // on startup - [BWHockeyManager sharedHockeyManager].updateSetting = HockeyUpdateCheckStartup; - } else if (indexPath.row == 1) { - // daily - [BWHockeyManager sharedHockeyManager].updateSetting = HockeyUpdateCheckDaily; - } else { - // manually - [BWHockeyManager sharedHockeyManager].updateSetting = HockeyUpdateCheckManually; - } - - [tableView reloadData]; + [tableView deselectRowAtIndexPath:indexPath animated:YES]; + + // update check interval selection + if (indexPath.row == 0) { + // on startup + [BWHockeyManager sharedHockeyManager].updateSetting = HockeyUpdateCheckStartup; + } else if (indexPath.row == 1) { + // daily + [BWHockeyManager sharedHockeyManager].updateSetting = HockeyUpdateCheckDaily; + } else { + // manually + [BWHockeyManager sharedHockeyManager].updateSetting = HockeyUpdateCheckManually; + } + + [tableView reloadData]; } @@ -257,20 +257,20 @@ #pragma mark Memory management - (void)didReceiveMemoryWarning { - // Releases the view if it doesn't have a superview. - [super didReceiveMemoryWarning]; - - // Relinquish ownership any cached data, images, etc. that aren't in use. + // Releases the view if it doesn't have a superview. + [super didReceiveMemoryWarning]; + + // Relinquish ownership any cached data, images, etc. that aren't in use. } - (void)viewDidUnload { - // Relinquish ownership of anything that can be recreated in viewDidLoad or on demand. - // For example: self.myOutlet = nil; + // Relinquish ownership of anything that can be recreated in viewDidLoad or on demand. + // For example: self.myOutlet = nil; } - (void)dealloc { - [super dealloc]; + [super dealloc]; } @@ -279,15 +279,15 @@ #pragma mark Rotation - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation { - BOOL shouldAutorotate; - - if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) { - shouldAutorotate = (interfaceOrientation == UIInterfaceOrientationPortrait); - } else { - shouldAutorotate = YES; - } - - return shouldAutorotate; + BOOL shouldAutorotate; + + if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) { + shouldAutorotate = (interfaceOrientation == UIInterfaceOrientationPortrait); + } else { + shouldAutorotate = YES; + } + + return shouldAutorotate; } @end diff --git a/Classes/BWHockeyViewController.h b/Classes/BWHockeyViewController.h index 0cfb6011dd..b0efe70756 100644 --- a/Classes/BWHockeyViewController.h +++ b/Classes/BWHockeyViewController.h @@ -39,22 +39,22 @@ typedef enum { @class BWHockeyManager; @interface BWHockeyViewController : UITableViewController { - BWHockeyManager *hockeyManager_; - - NSDictionary *cellLayout; - - BOOL modal_; - BOOL kvoRegistered_; - BOOL showAllVersions_; - UIStatusBarStyle statusBarStyle_; - PSAppStoreHeader *appStoreHeader_; - PSStoreButton *appStoreButton_; - - id popOverController_; - - AppStoreButtonState appStoreButtonState_; - - NSMutableArray *cells_; + BWHockeyManager *hockeyManager_; + + NSDictionary *cellLayout; + + BOOL modal_; + BOOL kvoRegistered_; + BOOL showAllVersions_; + UIStatusBarStyle statusBarStyle_; + PSAppStoreHeader *appStoreHeader_; + PSStoreButton *appStoreButton_; + + id popOverController_; + + AppStoreButtonState appStoreButtonState_; + + NSMutableArray *cells_; } @property (nonatomic, retain) BWHockeyManager *hockeyManager; diff --git a/Classes/BWHockeyViewController.m b/Classes/BWHockeyViewController.m index 64011db658..2ac45cc329 100644 --- a/Classes/BWHockeyViewController.m +++ b/Classes/BWHockeyViewController.m @@ -56,174 +56,174 @@ #pragma mark private - (void)restoreStoreButtonStateAnimated_:(BOOL)animated { - if ([self.hockeyManager isAppStoreEnvironment]) { - [self setAppStoreButtonState:AppStoreButtonStateOffline animated:animated]; - } else if ([self.hockeyManager isUpdateAvailable]) { - [self setAppStoreButtonState:AppStoreButtonStateUpdate animated:animated]; - } else { - [self setAppStoreButtonState:AppStoreButtonStateCheck animated:animated]; - } + if ([self.hockeyManager isAppStoreEnvironment]) { + [self setAppStoreButtonState:AppStoreButtonStateOffline animated:animated]; + } else if ([self.hockeyManager isUpdateAvailable]) { + [self setAppStoreButtonState:AppStoreButtonStateUpdate animated:animated]; + } else { + [self setAppStoreButtonState:AppStoreButtonStateCheck animated:animated]; + } } - (void)updateAppStoreHeader_ { - BWApp *app = self.hockeyManager.app; - appStoreHeader_.headerLabel = app.name; - appStoreHeader_.middleHeaderLabel = [app versionString]; - NSDateFormatter *formatter = [[[NSDateFormatter alloc] init] autorelease]; - [formatter setDateStyle:NSDateFormatterMediumStyle]; - NSMutableString *subHeaderString = [NSMutableString string]; - if (app.date) { - [subHeaderString appendString:[formatter stringFromDate:app.date]]; + BWApp *app = self.hockeyManager.app; + appStoreHeader_.headerLabel = app.name; + appStoreHeader_.middleHeaderLabel = [app versionString]; + NSDateFormatter *formatter = [[[NSDateFormatter alloc] init] autorelease]; + [formatter setDateStyle:NSDateFormatterMediumStyle]; + NSMutableString *subHeaderString = [NSMutableString string]; + if (app.date) { + [subHeaderString appendString:[formatter stringFromDate:app.date]]; + } + if (app.size) { + if ([subHeaderString length]) { + [subHeaderString appendString:@" - "]; } - if (app.size) { - if ([subHeaderString length]) { - [subHeaderString appendString:@" - "]; - } - [subHeaderString appendString:app.sizeInMB]; - } - appStoreHeader_.subHeaderLabel = subHeaderString; + [subHeaderString appendString:app.sizeInMB]; + } + appStoreHeader_.subHeaderLabel = subHeaderString; } - (void)appDidBecomeActive_ { - if (self.appStoreButtonState == AppStoreButtonStateInstalling) { - [self setAppStoreButtonState:AppStoreButtonStateUpdate animated:YES]; - } else if (![self.hockeyManager isCheckInProgress]) { - [self restoreStoreButtonStateAnimated_:YES]; - } + if (self.appStoreButtonState == AppStoreButtonStateInstalling) { + [self setAppStoreButtonState:AppStoreButtonStateUpdate animated:YES]; + } else if (![self.hockeyManager isCheckInProgress]) { + [self restoreStoreButtonStateAnimated_:YES]; + } } - (void)openSettings:(id)sender { - BWHockeySettingsViewController *settings = [[[BWHockeySettingsViewController alloc] init] autorelease]; - - Class popoverControllerClass = NSClassFromString(@"UIPopoverController"); - if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad && popoverControllerClass) { - if (popOverController_ == nil) { - popOverController_ = [[popoverControllerClass alloc] initWithContentViewController:settings]; - } - if ([popOverController_ contentViewController].view.window) { - [popOverController_ dismissPopoverAnimated:YES]; - }else { - [popOverController_ setPopoverContentSize: CGSizeMake(320, 440)]; - [popOverController_ presentPopoverFromBarButtonItem:self.navigationItem.rightBarButtonItem - permittedArrowDirections:UIPopoverArrowDirectionUp animated:YES]; - } - } else { - - BW_IF_3_2_OR_GREATER( - settings.modalTransitionStyle = UIModalTransitionStylePartialCurl; - [self presentModalViewController:settings animated:YES]; - ) - BW_IF_PRE_3_2( - UINavigationController *navController = [[[UINavigationController alloc] initWithRootViewController:settings] autorelease]; - navController.modalTransitionStyle = UIModalTransitionStyleCoverVertical; - [self presentModalViewController:navController animated:YES]; - ) + BWHockeySettingsViewController *settings = [[[BWHockeySettingsViewController alloc] init] autorelease]; + + Class popoverControllerClass = NSClassFromString(@"UIPopoverController"); + if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad && popoverControllerClass) { + if (popOverController_ == nil) { + popOverController_ = [[popoverControllerClass alloc] initWithContentViewController:settings]; } + if ([popOverController_ contentViewController].view.window) { + [popOverController_ dismissPopoverAnimated:YES]; + }else { + [popOverController_ setPopoverContentSize: CGSizeMake(320, 440)]; + [popOverController_ presentPopoverFromBarButtonItem:self.navigationItem.rightBarButtonItem + permittedArrowDirections:UIPopoverArrowDirectionUp animated:YES]; + } + } else { + + BW_IF_3_2_OR_GREATER( + settings.modalTransitionStyle = UIModalTransitionStylePartialCurl; + [self presentModalViewController:settings animated:YES]; + ) + BW_IF_PRE_3_2( + UINavigationController *navController = [[[UINavigationController alloc] initWithRootViewController:settings] autorelease]; + navController.modalTransitionStyle = UIModalTransitionStyleCoverVertical; + [self presentModalViewController:navController animated:YES]; + ) + } } - (UIImage *)addGlossToImage_:(UIImage *)image { - BW_IF_IOS4_OR_GREATER(UIGraphicsBeginImageContextWithOptions(image.size, NO, 0.0);) - BW_IF_PRE_IOS4(UIGraphicsBeginImageContext(image.size);) - - [image drawAtPoint:CGPointZero]; - UIImage *iconGradient = [UIImage bw_imageNamed:@"IconGradient.png" bundle:kHockeyBundleName]; - [iconGradient drawInRect:CGRectMake(0, 0, image.size.width, image.size.height) blendMode:kCGBlendModeNormal alpha:0.5]; - - UIImage *result = UIGraphicsGetImageFromCurrentImageContext(); - UIGraphicsEndImageContext(); - - return result; + BW_IF_IOS4_OR_GREATER(UIGraphicsBeginImageContextWithOptions(image.size, NO, 0.0);) + BW_IF_PRE_IOS4(UIGraphicsBeginImageContext(image.size);) + + [image drawAtPoint:CGPointZero]; + UIImage *iconGradient = [UIImage bw_imageNamed:@"IconGradient.png" bundle:kHockeyBundleName]; + [iconGradient drawInRect:CGRectMake(0, 0, image.size.width, image.size.height) blendMode:kCGBlendModeNormal alpha:0.5]; + + UIImage *result = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); + + return result; } #define kMinPreviousVersionButtonHeight 50 - (void)realignPreviousVersionButton { - - // manually collect actual table height size - NSUInteger tableViewContentHeight = 0; - for (int i=0; i < [self tableView:self.tableView numberOfRowsInSection:0]; i++) { - tableViewContentHeight += [self tableView:self.tableView heightForRowAtIndexPath:[NSIndexPath indexPathForRow:i inSection:0]]; - } - tableViewContentHeight += self.tableView.tableHeaderView.frame.size.height; - - NSUInteger footerViewSize = kMinPreviousVersionButtonHeight; - NSUInteger frameHeight = self.view.frame.size.height; - if(tableViewContentHeight < frameHeight && (frameHeight - tableViewContentHeight > 100)) { - footerViewSize = frameHeight - tableViewContentHeight; - } - - // update footer view - if(self.tableView.tableFooterView) { - CGRect frame = self.tableView.tableFooterView.frame; - frame.size.height = footerViewSize; - self.tableView.tableFooterView.frame = frame; - } + + // manually collect actual table height size + NSUInteger tableViewContentHeight = 0; + for (int i=0; i < [self tableView:self.tableView numberOfRowsInSection:0]; i++) { + tableViewContentHeight += [self tableView:self.tableView heightForRowAtIndexPath:[NSIndexPath indexPathForRow:i inSection:0]]; + } + tableViewContentHeight += self.tableView.tableHeaderView.frame.size.height; + + NSUInteger footerViewSize = kMinPreviousVersionButtonHeight; + NSUInteger frameHeight = self.view.frame.size.height; + if(tableViewContentHeight < frameHeight && (frameHeight - tableViewContentHeight > 100)) { + footerViewSize = frameHeight - tableViewContentHeight; + } + + // update footer view + if(self.tableView.tableFooterView) { + CGRect frame = self.tableView.tableFooterView.frame; + frame.size.height = footerViewSize; + self.tableView.tableFooterView.frame = frame; + } } - (void)changePreviousVersionButtonBackground:(id)sender { - [(UIButton *)sender setBackgroundColor:BW_RGBCOLOR(183,183,183)]; + [(UIButton *)sender setBackgroundColor:BW_RGBCOLOR(183,183,183)]; } - (void)changePreviousVersionButtonBackgroundHighlighted:(id)sender { - [(UIButton *)sender setBackgroundColor:BW_RGBCOLOR(183,183,183)]; + [(UIButton *)sender setBackgroundColor:BW_RGBCOLOR(183,183,183)]; } - (void)showHidePreviousVersionsButton { - BOOL multipleVersionButtonNeeded = [self.hockeyManager.apps count] > 1 && !showAllVersions_; - - if(multipleVersionButtonNeeded) { - // align at the bottom if tableview is small - UIView *footerView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, kMinPreviousVersionButtonHeight)]; - footerView.autoresizingMask = UIViewAutoresizingFlexibleWidth; - footerView.backgroundColor = BW_RGBCOLOR(200, 202, 204); - UIButton *footerButton = [UIButton buttonWithType:UIButtonTypeCustom]; - BW_IF_IOS4_OR_GREATER( - //footerButton.layer.shadowOffset = CGSizeMake(-2, 2); - footerButton.layer.shadowColor = [[UIColor blackColor] CGColor]; - footerButton.layer.shadowRadius = 2.0f; - ) - footerButton.titleLabel.font = [UIFont boldSystemFontOfSize:14]; - [footerButton setTitle:BWHockeyLocalize(@"HockeyShowPreviousVersions") forState:UIControlStateNormal]; - [footerButton setTitleColor:[UIColor blackColor] forState:UIControlStateNormal]; - [footerButton setTitleColor:[UIColor whiteColor] forState:UIControlStateHighlighted]; - [footerButton setBackgroundImage:[UIImage bw_imageNamed:@"buttonHighlight.png" bundle:kHockeyBundleName] forState:UIControlStateHighlighted]; - footerButton.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleTopMargin; - [footerButton addTarget:self action:@selector(showPreviousVersionAction) forControlEvents:UIControlEventTouchUpInside]; - footerButton.frame = CGRectMake(0, kMinPreviousVersionButtonHeight-44, self.view.frame.size.width, 44); - footerButton.backgroundColor = BW_RGBCOLOR(183,183,183); - [footerView addSubview:footerButton]; - self.tableView.tableFooterView = footerView; - [self realignPreviousVersionButton]; - [footerView release]; - } else { - self.tableView.tableFooterView = nil; - self.tableView.backgroundColor = BW_RGBCOLOR(200, 202, 204); - } + BOOL multipleVersionButtonNeeded = [self.hockeyManager.apps count] > 1 && !showAllVersions_; + + if(multipleVersionButtonNeeded) { + // align at the bottom if tableview is small + UIView *footerView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, kMinPreviousVersionButtonHeight)]; + footerView.autoresizingMask = UIViewAutoresizingFlexibleWidth; + footerView.backgroundColor = BW_RGBCOLOR(200, 202, 204); + UIButton *footerButton = [UIButton buttonWithType:UIButtonTypeCustom]; + BW_IF_IOS4_OR_GREATER( + //footerButton.layer.shadowOffset = CGSizeMake(-2, 2); + footerButton.layer.shadowColor = [[UIColor blackColor] CGColor]; + footerButton.layer.shadowRadius = 2.0f; + ) + footerButton.titleLabel.font = [UIFont boldSystemFontOfSize:14]; + [footerButton setTitle:BWHockeyLocalize(@"HockeyShowPreviousVersions") forState:UIControlStateNormal]; + [footerButton setTitleColor:[UIColor blackColor] forState:UIControlStateNormal]; + [footerButton setTitleColor:[UIColor whiteColor] forState:UIControlStateHighlighted]; + [footerButton setBackgroundImage:[UIImage bw_imageNamed:@"buttonHighlight.png" bundle:kHockeyBundleName] forState:UIControlStateHighlighted]; + footerButton.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleTopMargin; + [footerButton addTarget:self action:@selector(showPreviousVersionAction) forControlEvents:UIControlEventTouchUpInside]; + footerButton.frame = CGRectMake(0, kMinPreviousVersionButtonHeight-44, self.view.frame.size.width, 44); + footerButton.backgroundColor = BW_RGBCOLOR(183,183,183); + [footerView addSubview:footerButton]; + self.tableView.tableFooterView = footerView; + [self realignPreviousVersionButton]; + [footerView release]; + } else { + self.tableView.tableFooterView = nil; + self.tableView.backgroundColor = BW_RGBCOLOR(200, 202, 204); + } } - (void)configureWebCell:(PSWebTableViewCell *)cell forApp_:(BWApp *)app { - // create web view for a version - NSString *installed = @""; - if ([app.version isEqualToString:[self.hockeyManager currentAppVersion]]) { - installed = [NSString stringWithFormat:@"%@", [app isEqual:self.hockeyManager.app] ? @"left" : @"right", BWHockeyLocalize(@"HockeyInstalled")]; - } - - if ([app isEqual:self.hockeyManager.app]) { - if ([app.notes length] > 0) { - installed = [NSString stringWithFormat:@"

 %@

", installed]; - cell.webViewContent = [NSString stringWithFormat:@"%@%@", installed, app.notes]; - } else { - cell.webViewContent = [NSString stringWithFormat:@"
%@
", BWHockeyLocalize(@"HockeyNoReleaseNotesAvailable")]; - } + // create web view for a version + NSString *installed = @""; + if ([app.version isEqualToString:[self.hockeyManager currentAppVersion]]) { + installed = [NSString stringWithFormat:@"%@", [app isEqual:self.hockeyManager.app] ? @"left" : @"right", BWHockeyLocalize(@"HockeyInstalled")]; + } + + if ([app isEqual:self.hockeyManager.app]) { + if ([app.notes length] > 0) { + installed = [NSString stringWithFormat:@"

 %@

", installed]; + cell.webViewContent = [NSString stringWithFormat:@"%@%@", installed, app.notes]; } else { - cell.webViewContent = [NSString stringWithFormat:@"

%@%@
%@

%@

", [app versionString], installed, [app dateString], [app notesOrEmptyString]]; + cell.webViewContent = [NSString stringWithFormat:@"
%@
", BWHockeyLocalize(@"HockeyNoReleaseNotesAvailable")]; } - cell.cellBackgroundColor = BW_RGBCOLOR(200, 202, 204); - - [cell addWebView]; - // hack - cell.textLabel.text = @""; - - [cell addObserver:self forKeyPath:@"webViewSize" options:0 context:nil]; + } else { + cell.webViewContent = [NSString stringWithFormat:@"

%@%@
%@

%@

", [app versionString], installed, [app dateString], [app notesOrEmptyString]]; + } + cell.cellBackgroundColor = BW_RGBCOLOR(200, 202, 204); + + [cell addWebView]; + // hack + cell.textLabel.text = @""; + + [cell addObserver:self forKeyPath:@"webViewSize" options:0 context:nil]; } /////////////////////////////////////////////////////////////////////////////////////////////////// @@ -231,22 +231,22 @@ #pragma mark NSObject - (id)init:(BWHockeyManager *)newHockeyManager modal:(BOOL)newModal { - if ((self = [super initWithStyle:UITableViewStylePlain])) { - self.hockeyManager = newHockeyManager; - self.modal = newModal; - self.title = BWHockeyLocalize(@"HockeyUpdateScreenTitle"); - - if ([self.hockeyManager shouldShowUserSettings]) { - self.navigationItem.rightBarButtonItem = [[[UIBarButtonItem alloc] initWithImage:[UIImage bw_imageNamed:@"gear.png" bundle:kHockeyBundleName] - style:UIBarButtonItemStyleBordered - target:self - action:@selector(openSettings:)] autorelease]; - } - - cells_ = [[NSMutableArray alloc] initWithCapacity:5]; - popOverController_ = nil; + if ((self = [super initWithStyle:UITableViewStylePlain])) { + self.hockeyManager = newHockeyManager; + self.modal = newModal; + self.title = BWHockeyLocalize(@"HockeyUpdateScreenTitle"); + + if ([self.hockeyManager shouldShowUserSettings]) { + self.navigationItem.rightBarButtonItem = [[[UIBarButtonItem alloc] initWithImage:[UIImage bw_imageNamed:@"gear.png" bundle:kHockeyBundleName] + style:UIBarButtonItemStyleBordered + target:self + action:@selector(openSettings:)] autorelease]; } - return self; + + cells_ = [[NSMutableArray alloc] initWithCapacity:5]; + popOverController_ = nil; + } + return self; } - (id)init { @@ -254,12 +254,12 @@ } - (void)dealloc { - [self viewDidUnload]; - for (UITableViewCell *cell in cells_) { - [cell removeObserver:self forKeyPath:@"webViewSize"]; - } - [cells_ release]; - [super dealloc]; + [self viewDidUnload]; + for (UITableViewCell *cell in cells_) { + [cell removeObserver:self forKeyPath:@"webViewSize"]; + } + [cells_ release]; + [super dealloc]; } @@ -268,30 +268,30 @@ #pragma mark View lifecycle - (void)onAction:(id)sender { - if (self.modal) { - // Note that as of 5.0, parentViewController will no longer return the presenting view controller - SEL presentingViewControllerSelector = NSSelectorFromString(@"presentingViewController"); - UIViewController *presentingViewController = nil; - if ([self respondsToSelector:presentingViewControllerSelector]) { - presentingViewController = [self performSelector:presentingViewControllerSelector]; - } - else { - presentingViewController = [self parentViewController]; - } - - // If there is no presenting view controller just remove view - if (presentingViewController) { - [presentingViewController dismissModalViewControllerAnimated:YES]; - } - else { - [self.navigationController.view removeFromSuperview]; - } + if (self.modal) { + // Note that as of 5.0, parentViewController will no longer return the presenting view controller + SEL presentingViewControllerSelector = NSSelectorFromString(@"presentingViewController"); + UIViewController *presentingViewController = nil; + if ([self respondsToSelector:presentingViewControllerSelector]) { + presentingViewController = [self performSelector:presentingViewControllerSelector]; } else { - [self.navigationController popViewControllerAnimated:YES]; + presentingViewController = [self parentViewController]; } - [[UIApplication sharedApplication] setStatusBarStyle:statusBarStyle_]; + // If there is no presenting view controller just remove view + if (presentingViewController) { + [presentingViewController dismissModalViewControllerAnimated:YES]; + } + else { + [self.navigationController.view removeFromSuperview]; + } + } + else { + [self.navigationController popViewControllerAnimated:YES]; + } + + [[UIApplication sharedApplication] setStatusBarStyle:statusBarStyle_]; } - (CAGradientLayer *)backgroundLayer { @@ -299,199 +299,199 @@ UIColor *colorTwo = [UIColor colorWithHue:0.625 saturation:0.0 brightness:0.85 alpha:1.0]; UIColor *colorThree = [UIColor colorWithHue:0.625 saturation:0.0 brightness:0.7 alpha:1.0]; UIColor *colorFour = [UIColor colorWithHue:0.625 saturation:0.0 brightness:0.4 alpha:1.0]; - + NSArray *colors = [NSArray arrayWithObjects:(id)colorOne.CGColor, colorTwo.CGColor, colorThree.CGColor, colorFour.CGColor, nil]; - + NSNumber *stopOne = [NSNumber numberWithFloat:0.0]; NSNumber *stopTwo = [NSNumber numberWithFloat:0.02]; NSNumber *stopThree = [NSNumber numberWithFloat:0.99]; NSNumber *stopFour = [NSNumber numberWithFloat:1.0]; - + NSArray *locations = [NSArray arrayWithObjects:stopOne, stopTwo, stopThree, stopFour, nil]; - + CAGradientLayer *headerLayer = [CAGradientLayer layer]; //headerLayer.frame = CGRectMake(0.0, 0.0, 320.0, 77.0); headerLayer.colors = colors; headerLayer.locations = locations; - + return headerLayer; } - (void)viewDidLoad { - [super viewDidLoad]; - - // add notifications only to loaded view - NSNotificationCenter *dnc = [NSNotificationCenter defaultCenter]; - [dnc addObserver:self selector:@selector(appDidBecomeActive_) name:UIApplicationDidBecomeActiveNotification object:nil]; - - // hook into manager with kvo! - [self.hockeyManager addObserver:self forKeyPath:@"checkInProgress" options:0 context:nil]; - [self.hockeyManager addObserver:self forKeyPath:@"isUpdateURLOffline" options:0 context:nil]; - [self.hockeyManager addObserver:self forKeyPath:@"updateAvailable" options:0 context:nil]; - [self.hockeyManager addObserver:self forKeyPath:@"apps" options:0 context:nil]; - kvoRegistered_ = YES; - - self.tableView.backgroundColor = BW_RGBCOLOR(200, 202, 204); - self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone; - - UIView *topView = [[[UIView alloc] initWithFrame:CGRectMake(0, -(600-kAppStoreViewHeight), self.view.frame.size.width, 600)] autorelease]; - topView.autoresizingMask = UIViewAutoresizingFlexibleWidth; - topView.backgroundColor = BW_RGBCOLOR(140, 141, 142); - [self.tableView addSubview:topView]; - - appStoreHeader_ = [[PSAppStoreHeader alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, kAppStoreViewHeight)]; - [self updateAppStoreHeader_]; - - NSString *iconString = nil; - NSArray *icons = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleIconFiles"]; - if (!icons) { - iconString = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleIconFile"]; - if (!iconString) { - iconString = @"Icon.png"; - } - } else { - BOOL useHighResIcon = NO; - BW_IF_IOS4_OR_GREATER(if ([UIScreen mainScreen].scale == 2.0f) useHighResIcon = YES;) - - for(NSString *icon in icons) { - iconString = icon; - UIImage *iconImage = [UIImage imageNamed:icon]; - - if (iconImage.size.height == 57 && !useHighResIcon) { - // found! - break; - } - if (iconImage.size.height == 114 && useHighResIcon) { - // found! - break; - } - } + [super viewDidLoad]; + + // add notifications only to loaded view + NSNotificationCenter *dnc = [NSNotificationCenter defaultCenter]; + [dnc addObserver:self selector:@selector(appDidBecomeActive_) name:UIApplicationDidBecomeActiveNotification object:nil]; + + // hook into manager with kvo! + [self.hockeyManager addObserver:self forKeyPath:@"checkInProgress" options:0 context:nil]; + [self.hockeyManager addObserver:self forKeyPath:@"isUpdateURLOffline" options:0 context:nil]; + [self.hockeyManager addObserver:self forKeyPath:@"updateAvailable" options:0 context:nil]; + [self.hockeyManager addObserver:self forKeyPath:@"apps" options:0 context:nil]; + kvoRegistered_ = YES; + + self.tableView.backgroundColor = BW_RGBCOLOR(200, 202, 204); + self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone; + + UIView *topView = [[[UIView alloc] initWithFrame:CGRectMake(0, -(600-kAppStoreViewHeight), self.view.frame.size.width, 600)] autorelease]; + topView.autoresizingMask = UIViewAutoresizingFlexibleWidth; + topView.backgroundColor = BW_RGBCOLOR(140, 141, 142); + [self.tableView addSubview:topView]; + + appStoreHeader_ = [[PSAppStoreHeader alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, kAppStoreViewHeight)]; + [self updateAppStoreHeader_]; + + NSString *iconString = nil; + NSArray *icons = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleIconFiles"]; + if (!icons) { + iconString = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleIconFile"]; + if (!iconString) { + iconString = @"Icon.png"; } - - BOOL addGloss = YES; - NSNumber *prerendered = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"UIPrerenderedIcon"]; - if (prerendered) { - addGloss = ![prerendered boolValue]; + } else { + BOOL useHighResIcon = NO; + BW_IF_IOS4_OR_GREATER(if ([UIScreen mainScreen].scale == 2.0f) useHighResIcon = YES;) + + for(NSString *icon in icons) { + iconString = icon; + UIImage *iconImage = [UIImage imageNamed:icon]; + + if (iconImage.size.height == 57 && !useHighResIcon) { + // found! + break; + } + if (iconImage.size.height == 114 && useHighResIcon) { + // found! + break; + } } - - if (addGloss) { - appStoreHeader_.iconImage = [self addGlossToImage_:[UIImage imageNamed:iconString]]; - } else { - appStoreHeader_.iconImage = [UIImage imageNamed:iconString]; - } - - self.tableView.tableHeaderView = appStoreHeader_; - - if (self.modal) { - self.navigationItem.leftBarButtonItem = [[[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone - target:self - action:@selector(onAction:)] autorelease]; - } - - PSStoreButton *storeButton = [[[PSStoreButton alloc] initWithPadding:CGPointMake(5, 40)] autorelease]; - storeButton.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin; - storeButton.buttonDelegate = self; - [self.tableView.tableHeaderView addSubview:storeButton]; - storeButton.buttonData = [PSStoreButtonData dataWithLabel:@"" colors:[PSStoreButton appStoreGrayColor] enabled:NO]; - self.appStoreButtonState = AppStoreButtonStateCheck; - [storeButton alignToSuperview]; - appStoreButton_ = [storeButton retain]; + } + + BOOL addGloss = YES; + NSNumber *prerendered = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"UIPrerenderedIcon"]; + if (prerendered) { + addGloss = ![prerendered boolValue]; + } + + if (addGloss) { + appStoreHeader_.iconImage = [self addGlossToImage_:[UIImage imageNamed:iconString]]; + } else { + appStoreHeader_.iconImage = [UIImage imageNamed:iconString]; + } + + self.tableView.tableHeaderView = appStoreHeader_; + + if (self.modal) { + self.navigationItem.leftBarButtonItem = [[[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone + target:self + action:@selector(onAction:)] autorelease]; + } + + PSStoreButton *storeButton = [[[PSStoreButton alloc] initWithPadding:CGPointMake(5, 40)] autorelease]; + storeButton.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin; + storeButton.buttonDelegate = self; + [self.tableView.tableHeaderView addSubview:storeButton]; + storeButton.buttonData = [PSStoreButtonData dataWithLabel:@"" colors:[PSStoreButton appStoreGrayColor] enabled:NO]; + self.appStoreButtonState = AppStoreButtonStateCheck; + [storeButton alignToSuperview]; + appStoreButton_ = [storeButton retain]; } - (void)viewWillAppear:(BOOL)animated { - if ([self.hockeyManager isAppStoreEnvironment]) - self.appStoreButtonState = AppStoreButtonStateOffline; - self.hockeyManager.currentHockeyViewController = self; - [super viewWillAppear:animated]; - statusBarStyle_ = [[UIApplication sharedApplication] statusBarStyle]; - [[UIApplication sharedApplication] setStatusBarStyle:(self.navigationController.navigationBar.barStyle == UIBarStyleDefault) ? UIStatusBarStyleDefault : UIStatusBarStyleBlackOpaque]; - [self redrawTableView]; + if ([self.hockeyManager isAppStoreEnvironment]) + self.appStoreButtonState = AppStoreButtonStateOffline; + self.hockeyManager.currentHockeyViewController = self; + [super viewWillAppear:animated]; + statusBarStyle_ = [[UIApplication sharedApplication] statusBarStyle]; + [[UIApplication sharedApplication] setStatusBarStyle:(self.navigationController.navigationBar.barStyle == UIBarStyleDefault) ? UIStatusBarStyleDefault : UIStatusBarStyleBlackOpaque]; + [self redrawTableView]; } - (void)viewWillDisappear:(BOOL)animated { - self.hockeyManager.currentHockeyViewController = nil; - //if the popover is still visible, dismiss it - [popOverController_ dismissPopoverAnimated:YES]; - [super viewWillDisappear:animated]; - [[UIApplication sharedApplication] setStatusBarStyle:statusBarStyle_]; + self.hockeyManager.currentHockeyViewController = nil; + //if the popover is still visible, dismiss it + [popOverController_ dismissPopoverAnimated:YES]; + [super viewWillDisappear:animated]; + [[UIApplication sharedApplication] setStatusBarStyle:statusBarStyle_]; } - (void)redrawTableView { - [self restoreStoreButtonStateAnimated_:NO]; - [self updateAppStoreHeader_]; - - // clean up and remove any pending overservers - for (UITableViewCell *cell in cells_) { - [cell removeObserver:self forKeyPath:@"webViewSize"]; - } - [cells_ removeAllObjects]; - - int i = 0; - BOOL breakAfterThisApp = NO; - for (BWApp *app in self.hockeyManager.apps) { - i++; - - // only show the newer version of the app by default, if we don't show all versions - if (!showAllVersions_) { - if ([app.version isEqualToString:[self.hockeyManager currentAppVersion]]) { - if (i == 1) { - breakAfterThisApp = YES; - } else { - break; - } - } + [self restoreStoreButtonStateAnimated_:NO]; + [self updateAppStoreHeader_]; + + // clean up and remove any pending overservers + for (UITableViewCell *cell in cells_) { + [cell removeObserver:self forKeyPath:@"webViewSize"]; + } + [cells_ removeAllObjects]; + + int i = 0; + BOOL breakAfterThisApp = NO; + for (BWApp *app in self.hockeyManager.apps) { + i++; + + // only show the newer version of the app by default, if we don't show all versions + if (!showAllVersions_) { + if ([app.version isEqualToString:[self.hockeyManager currentAppVersion]]) { + if (i == 1) { + breakAfterThisApp = YES; + } else { + break; } - - PSWebTableViewCell *cell = [[[PSWebTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:kWebCellIdentifier] autorelease]; - [self configureWebCell:cell forApp_:app]; - [cells_ addObject:cell]; - - if (breakAfterThisApp) break; + } } - - [self.tableView reloadData]; - [self showHidePreviousVersionsButton]; + + PSWebTableViewCell *cell = [[[PSWebTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:kWebCellIdentifier] autorelease]; + [self configureWebCell:cell forApp_:app]; + [cells_ addObject:cell]; + + if (breakAfterThisApp) break; + } + + [self.tableView reloadData]; + [self showHidePreviousVersionsButton]; } - (void)showPreviousVersionAction { - showAllVersions_ = YES; - BOOL showAllPending = NO; - - for (BWApp *app in self.hockeyManager.apps) { - if (!showAllPending) { - if ([app.version isEqualToString:[self.hockeyManager currentAppVersion]]) { - showAllPending = YES; - if (app == self.hockeyManager.app) { - continue; // skip this version already if it the latest version is the installed one - } - } else { - continue; // skip already shown - } + showAllVersions_ = YES; + BOOL showAllPending = NO; + + for (BWApp *app in self.hockeyManager.apps) { + if (!showAllPending) { + if ([app.version isEqualToString:[self.hockeyManager currentAppVersion]]) { + showAllPending = YES; + if (app == self.hockeyManager.app) { + continue; // skip this version already if it the latest version is the installed one } - - PSWebTableViewCell *cell = [[[PSWebTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:kWebCellIdentifier] autorelease]; - [self configureWebCell:cell forApp_:app]; - [cells_ addObject:cell]; + } else { + continue; // skip already shown + } } - [self.tableView reloadData]; - [self showHidePreviousVersionsButton]; + + PSWebTableViewCell *cell = [[[PSWebTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:kWebCellIdentifier] autorelease]; + [self configureWebCell:cell forApp_:app]; + [cells_ addObject:cell]; + } + [self.tableView reloadData]; + [self showHidePreviousVersionsButton]; } - (void)viewDidUnload { - [appStoreHeader_ release]; appStoreHeader_ = nil; - [popOverController_ release], popOverController_ = nil; - [[NSNotificationCenter defaultCenter] removeObserver:self]; - - // test if KVO's are registered. if class is destroyed before it was shown(viewDidLoad) no KVOs are registered. - if (kvoRegistered_) { - [self.hockeyManager removeObserver:self forKeyPath:@"checkInProgress"]; - [self.hockeyManager removeObserver:self forKeyPath:@"isUpdateURLOffline"]; - [self.hockeyManager removeObserver:self forKeyPath:@"updateAvailable"]; - [self.hockeyManager removeObserver:self forKeyPath:@"apps"]; - kvoRegistered_ = NO; - } - - [super viewDidUnload]; + [appStoreHeader_ release]; appStoreHeader_ = nil; + [popOverController_ release], popOverController_ = nil; + [[NSNotificationCenter defaultCenter] removeObserver:self]; + + // test if KVO's are registered. if class is destroyed before it was shown(viewDidLoad) no KVOs are registered. + if (kvoRegistered_) { + [self.hockeyManager removeObserver:self forKeyPath:@"checkInProgress"]; + [self.hockeyManager removeObserver:self forKeyPath:@"isUpdateURLOffline"]; + [self.hockeyManager removeObserver:self forKeyPath:@"updateAvailable"]; + [self.hockeyManager removeObserver:self forKeyPath:@"apps"]; + kvoRegistered_ = NO; + } + + [super viewDidUnload]; } /////////////////////////////////////////////////////////////////////////////////////////////////// @@ -499,32 +499,32 @@ #pragma mark Table view data source - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { - return 1; + return 1; } - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { - CGFloat rowHeight = 0; - - if ([cells_ count] > (NSUInteger)indexPath.row) { - PSWebTableViewCell *cell = [cells_ objectAtIndex:indexPath.row]; - rowHeight = cell.webViewSize.height; - } - - if ([self.hockeyManager.apps count] > 1 && !showAllVersions_) { - self.tableView.backgroundColor = BW_RGBCOLOR(183, 183, 183); - } - - if (rowHeight == 0) { - rowHeight = indexPath.row == 0 ? 250 : 44; // fill screen on startup - self.tableView.backgroundColor = BW_RGBCOLOR(200, 202, 204); - } - - return rowHeight; + CGFloat rowHeight = 0; + + if ([cells_ count] > (NSUInteger)indexPath.row) { + PSWebTableViewCell *cell = [cells_ objectAtIndex:indexPath.row]; + rowHeight = cell.webViewSize.height; + } + + if ([self.hockeyManager.apps count] > 1 && !showAllVersions_) { + self.tableView.backgroundColor = BW_RGBCOLOR(183, 183, 183); + } + + if (rowHeight == 0) { + rowHeight = indexPath.row == 0 ? 250 : 44; // fill screen on startup + self.tableView.backgroundColor = BW_RGBCOLOR(200, 202, 204); + } + + return rowHeight; } - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { - NSInteger cellCount = [cells_ count]; - return cellCount; + NSInteger cellCount = [cells_ count]; + return cellCount; } /////////////////////////////////////////////////////////////////////////////////////////////////// @@ -535,32 +535,32 @@ // only make changes if we are visible if(self.view.window) { if ([keyPath isEqualToString:@"webViewSize"]) { - [self.tableView reloadData]; - [self realignPreviousVersionButton]; + [self.tableView reloadData]; + [self realignPreviousVersionButton]; } else if ([keyPath isEqualToString:@"checkInProgress"]) { - if (self.hockeyManager.isCheckInProgress) { - [self setAppStoreButtonState:AppStoreButtonStateSearching animated:YES]; - }else { - [self restoreStoreButtonStateAnimated_:YES]; - } + if (self.hockeyManager.isCheckInProgress) { + [self setAppStoreButtonState:AppStoreButtonStateSearching animated:YES]; + }else { + [self restoreStoreButtonStateAnimated_:YES]; + } } else if ([keyPath isEqualToString:@"isUpdateURLOffline"]) { - [self restoreStoreButtonStateAnimated_:YES]; + [self restoreStoreButtonStateAnimated_:YES]; } else if ([keyPath isEqualToString:@"updateAvailable"]) { - [self restoreStoreButtonStateAnimated_:YES]; + [self restoreStoreButtonStateAnimated_:YES]; } else if ([keyPath isEqualToString:@"apps"]) { - [self redrawTableView]; + [self redrawTableView]; } } } // Customize the appearance of table view cells. - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { - if ([cells_ count] > (NSUInteger)indexPath.row) { - return [cells_ objectAtIndex:indexPath.row]; - } else { - BWHockeyLog(@"Warning: cells_ and indexPath do not match? forgot calling redrawTableView?"); - } - return nil; + if ([cells_ count] > (NSUInteger)indexPath.row) { + return [cells_ objectAtIndex:indexPath.row]; + } else { + BWHockeyLog(@"Warning: cells_ and indexPath do not match? forgot calling redrawTableView?"); + } + return nil; } @@ -569,22 +569,22 @@ #pragma mark Rotation - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation { - BOOL shouldAutorotate; - - if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) { - shouldAutorotate = (interfaceOrientation == UIInterfaceOrientationLandscapeLeft || - interfaceOrientation == UIInterfaceOrientationLandscapeRight || - interfaceOrientation == UIInterfaceOrientationPortrait); - } else { - shouldAutorotate = YES; - } - - return shouldAutorotate; + BOOL shouldAutorotate; + + if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) { + shouldAutorotate = (interfaceOrientation == UIInterfaceOrientationLandscapeLeft || + interfaceOrientation == UIInterfaceOrientationLandscapeRight || + interfaceOrientation == UIInterfaceOrientationPortrait); + } else { + shouldAutorotate = YES; + } + + return shouldAutorotate; } - (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation duration:(NSTimeInterval)duration { - // update all cells - [cells_ makeObjectsPerformSelector:@selector(addWebView)]; + // update all cells + [cells_ makeObjectsPerformSelector:@selector(addWebView)]; } /////////////////////////////////////////////////////////////////////////////////////////////////// @@ -592,46 +592,46 @@ #pragma mark PSAppStoreHeaderDelegate - (void)setAppStoreButtonState:(AppStoreButtonState)anAppStoreButtonState { - [self setAppStoreButtonState:anAppStoreButtonState animated:NO]; + [self setAppStoreButtonState:anAppStoreButtonState animated:NO]; } - (void)setAppStoreButtonState:(AppStoreButtonState)anAppStoreButtonState animated:(BOOL)animated { - appStoreButtonState_ = anAppStoreButtonState; - - switch (anAppStoreButtonState) { - case AppStoreButtonStateOffline: - [appStoreButton_ setButtonData:[PSStoreButtonData dataWithLabel:BWHockeyLocalize(@"HockeyButtonOffline") colors:[PSStoreButton appStoreGrayColor] enabled:NO] animated:animated]; - break; - case AppStoreButtonStateCheck: - [appStoreButton_ setButtonData:[PSStoreButtonData dataWithLabel:BWHockeyLocalize(@"HockeyButtonCheck") colors:[PSStoreButton appStoreGreenColor] enabled:YES] animated:animated]; - break; - case AppStoreButtonStateSearching: - [appStoreButton_ setButtonData:[PSStoreButtonData dataWithLabel:BWHockeyLocalize(@"HockeyButtonSearching") colors:[PSStoreButton appStoreGrayColor] enabled:NO] animated:animated]; - break; - case AppStoreButtonStateUpdate: - [appStoreButton_ setButtonData:[PSStoreButtonData dataWithLabel:BWHockeyLocalize(@"HockeyButtonUpdate") colors:[PSStoreButton appStoreBlueColor] enabled:YES] animated:animated]; - break; - case AppStoreButtonStateInstalling: - [appStoreButton_ setButtonData:[PSStoreButtonData dataWithLabel:BWHockeyLocalize(@"HockeyButtonInstalling") colors:[PSStoreButton appStoreGrayColor] enabled:NO] animated:animated]; - break; - default: - break; - } + appStoreButtonState_ = anAppStoreButtonState; + + switch (anAppStoreButtonState) { + case AppStoreButtonStateOffline: + [appStoreButton_ setButtonData:[PSStoreButtonData dataWithLabel:BWHockeyLocalize(@"HockeyButtonOffline") colors:[PSStoreButton appStoreGrayColor] enabled:NO] animated:animated]; + break; + case AppStoreButtonStateCheck: + [appStoreButton_ setButtonData:[PSStoreButtonData dataWithLabel:BWHockeyLocalize(@"HockeyButtonCheck") colors:[PSStoreButton appStoreGreenColor] enabled:YES] animated:animated]; + break; + case AppStoreButtonStateSearching: + [appStoreButton_ setButtonData:[PSStoreButtonData dataWithLabel:BWHockeyLocalize(@"HockeyButtonSearching") colors:[PSStoreButton appStoreGrayColor] enabled:NO] animated:animated]; + break; + case AppStoreButtonStateUpdate: + [appStoreButton_ setButtonData:[PSStoreButtonData dataWithLabel:BWHockeyLocalize(@"HockeyButtonUpdate") colors:[PSStoreButton appStoreBlueColor] enabled:YES] animated:animated]; + break; + case AppStoreButtonStateInstalling: + [appStoreButton_ setButtonData:[PSStoreButtonData dataWithLabel:BWHockeyLocalize(@"HockeyButtonInstalling") colors:[PSStoreButton appStoreGrayColor] enabled:NO] animated:animated]; + break; + default: + break; + } } - (void)storeButtonFired:(PSStoreButton *)button { - switch (appStoreButtonState_) { - case AppStoreButtonStateCheck: - [self.hockeyManager checkForUpdateShowFeedback:YES]; - break; - case AppStoreButtonStateUpdate: - if ([self.hockeyManager initiateAppDownload]) { - [self setAppStoreButtonState:AppStoreButtonStateInstalling animated:YES]; - }; - break; - default: - break; - } + switch (appStoreButtonState_) { + case AppStoreButtonStateCheck: + [self.hockeyManager checkForUpdateShowFeedback:YES]; + break; + case AppStoreButtonStateUpdate: + if ([self.hockeyManager initiateAppDownload]) { + [self setAppStoreButtonState:AppStoreButtonStateInstalling animated:YES]; + }; + break; + default: + break; + } } @end diff --git a/Classes/BWQuincyManager.h b/Classes/BWQuincyManager.h index f19e4a84a7..9b18128cba 100644 --- a/Classes/BWQuincyManager.h +++ b/Classes/BWQuincyManager.h @@ -57,61 +57,61 @@ typedef enum QuincyKitAlertType { } CrashAlertType; typedef enum CrashReportStatus { - // The status of the crash is queued, need to check later (HockeyApp) + // The status of the crash is queued, need to check later (HockeyApp) CrashReportStatusQueued = -80, - - // This app version is set to discontinued, no new crash reports accepted by the server + + // This app version is set to discontinued, no new crash reports accepted by the server CrashReportStatusFailureVersionDiscontinued = -30, - - // XML: Sender version string contains not allowed characters, only alphanumberical including space and . are allowed + + // XML: Sender version string contains not allowed characters, only alphanumberical including space and . are allowed CrashReportStatusFailureXMLSenderVersionNotAllowed = -21, - - // XML: Version string contains not allowed characters, only alphanumberical including space and . are allowed + + // XML: Version string contains not allowed characters, only alphanumberical including space and . are allowed CrashReportStatusFailureXMLVersionNotAllowed = -20, - - // SQL for adding a symoblicate todo entry in the database failed + + // SQL for adding a symoblicate todo entry in the database failed CrashReportStatusFailureSQLAddSymbolicateTodo = -18, - - // SQL for adding crash log in the database failed + + // SQL for adding crash log in the database failed CrashReportStatusFailureSQLAddCrashlog = -17, - - // SQL for adding a new version in the database failed + + // SQL for adding a new version in the database failed CrashReportStatusFailureSQLAddVersion = -16, - // SQL for checking if the version is already added in the database failed - CrashReportStatusFailureSQLCheckVersionExists = -15, + // SQL for checking if the version is already added in the database failed + CrashReportStatusFailureSQLCheckVersionExists = -15, - // SQL for creating a new pattern for this bug and set amount of occurrances to 1 in the database failed - CrashReportStatusFailureSQLAddPattern = -14, + // SQL for creating a new pattern for this bug and set amount of occurrances to 1 in the database failed + CrashReportStatusFailureSQLAddPattern = -14, - // SQL for checking the status of the bugfix version in the database failed - CrashReportStatusFailureSQLCheckBugfixStatus = -13, + // SQL for checking the status of the bugfix version in the database failed + CrashReportStatusFailureSQLCheckBugfixStatus = -13, - // SQL for updating the occurances of this pattern in the database failed - CrashReportStatusFailureSQLUpdatePatternOccurances = -12, + // SQL for updating the occurances of this pattern in the database failed + CrashReportStatusFailureSQLUpdatePatternOccurances = -12, - // SQL for getting all the known bug patterns for the current app version in the database failed - CrashReportStatusFailureSQLFindKnownPatterns = -11, + // SQL for getting all the known bug patterns for the current app version in the database failed + CrashReportStatusFailureSQLFindKnownPatterns = -11, - // SQL for finding the bundle identifier in the database failed - CrashReportStatusFailureSQLSearchAppName = -10, + // SQL for finding the bundle identifier in the database failed + CrashReportStatusFailureSQLSearchAppName = -10, - // the post request didn't contain valid data - CrashReportStatusFailureInvalidPostData = -3, + // the post request didn't contain valid data + CrashReportStatusFailureInvalidPostData = -3, - // incoming data may not be added, because e.g. bundle identifier wasn't found - CrashReportStatusFailureInvalidIncomingData = -2, + // incoming data may not be added, because e.g. bundle identifier wasn't found + CrashReportStatusFailureInvalidIncomingData = -2, - // database cannot be accessed, check hostname, username, password and database name settings in config.php - CrashReportStatusFailureDatabaseNotAvailable = -1, + // database cannot be accessed, check hostname, username, password and database name settings in config.php + CrashReportStatusFailureDatabaseNotAvailable = -1, - CrashReportStatusUnknown = 0, + CrashReportStatusUnknown = 0, - CrashReportStatusAssigned = 1, + CrashReportStatusAssigned = 1, - CrashReportStatusSubmitted = 2, + CrashReportStatusSubmitted = 2, - CrashReportStatusAvailable = 3, + CrashReportStatusAvailable = 3, } CrashReportStatus; // This protocol is used to send the image updates @@ -140,42 +140,42 @@ typedef enum CrashReportStatus { @end @interface BWQuincyManager : NSObject { - NSString *_submissionURL; - - id _delegate; - - BOOL _showAlwaysButton; - BOOL _feedbackActivated; - BOOL _autoSubmitCrashReport; - BOOL _autoSubmitDeviceUDID; - - BOOL _didCrashInLastSession; - - NSString *_appIdentifier; - - NSString *_feedbackRequestID; - float _feedbackDelayInterval; - + NSString *_submissionURL; + + id _delegate; + + BOOL _showAlwaysButton; + BOOL _feedbackActivated; + BOOL _autoSubmitCrashReport; + BOOL _autoSubmitDeviceUDID; + + BOOL _didCrashInLastSession; + + NSString *_appIdentifier; + + NSString *_feedbackRequestID; + float _feedbackDelayInterval; + NSMutableString *_contentOfProperty; CrashReportStatus _serverResult; - + int _analyzerStarted; NSString *_crashesDir; BOOL _crashIdenticalCurrentVersion; - BOOL _crashReportActivated; - + BOOL _crashReportActivated; + NSMutableArray *_crashFiles; NSMutableData *_responseData; NSInteger _statusCode; - - NSURLConnection *_urlConnection; - - NSData *_crashData; - - NSString *_languageStyle; - BOOL _sendingInProgress; + + NSURLConnection *_urlConnection; + + NSData *_crashData; + + NSString *_languageStyle; + BOOL _sendingInProgress; } + (BWQuincyManager *)sharedQuincyManager; diff --git a/Classes/BWQuincyManager.m b/Classes/BWQuincyManager.m index 75a07a902f..094ebaf06e 100644 --- a/Classes/BWQuincyManager.m +++ b/Classes/BWQuincyManager.m @@ -36,22 +36,22 @@ #include //needed for PRIx64 macro NSBundle *quincyBundle(void) { - static NSBundle* bundle = nil; - if (!bundle) { - NSString* path = [[[NSBundle mainBundle] resourcePath] - stringByAppendingPathComponent:kQuincyBundleName]; - bundle = [[NSBundle bundleWithPath:path] retain]; - } - return bundle; + static NSBundle* bundle = nil; + if (!bundle) { + NSString* path = [[[NSBundle mainBundle] resourcePath] + stringByAppendingPathComponent:kQuincyBundleName]; + bundle = [[NSBundle bundleWithPath:path] retain]; + } + return bundle; } NSString *BWQuincyLocalize(NSString *stringToken) { - if ([BWQuincyManager sharedQuincyManager].languageStyle == nil) - return NSLocalizedStringFromTableInBundle(stringToken, @"Quincy", quincyBundle(), @""); - else { - NSString *alternate = [NSString stringWithFormat:@"Quincy%@", [BWQuincyManager sharedQuincyManager].languageStyle]; - return NSLocalizedStringFromTableInBundle(stringToken, alternate, quincyBundle(), @""); - } + if ([BWQuincyManager sharedQuincyManager].languageStyle == nil) + return NSLocalizedStringFromTableInBundle(stringToken, @"Quincy", quincyBundle(), @""); + else { + NSString *alternate = [NSString stringWithFormat:@"Quincy%@", [BWQuincyManager sharedQuincyManager].languageStyle]; + return NSLocalizedStringFromTableInBundle(stringToken, alternate, quincyBundle(), @""); + } } @@ -93,15 +93,15 @@ NSString *BWQuincyLocalize(NSString *stringToken) { #if __IPHONE_OS_VERSION_MIN_REQUIRED >= 40000 +(BWQuincyManager *)sharedQuincyManager { - static BWQuincyManager *sharedInstance = nil; - static dispatch_once_t pred; - - dispatch_once(&pred, ^{ - sharedInstance = [BWQuincyManager alloc]; - sharedInstance = [sharedInstance init]; - }); - - return sharedInstance; + static BWQuincyManager *sharedInstance = nil; + static dispatch_once_t pred; + + dispatch_once(&pred, ^{ + sharedInstance = [BWQuincyManager alloc]; + sharedInstance = [sharedInstance init]; + }); + + return sharedInstance; } #else + (BWQuincyManager *)sharedQuincyManager { @@ -116,24 +116,24 @@ NSString *BWQuincyLocalize(NSString *stringToken) { #endif - (id) init { - if ((self = [super init])) { + if ((self = [super init])) { _serverResult = CrashReportStatusUnknown; _crashIdenticalCurrentVersion = YES; _crashData = nil; - _urlConnection = nil; + _urlConnection = nil; _submissionURL = nil; - _responseData = nil; - _appIdentifier = nil; - _sendingInProgress = NO; - _languageStyle = nil; - _didCrashInLastSession = NO; - + _responseData = nil; + _appIdentifier = nil; + _sendingInProgress = NO; + _languageStyle = nil; + _didCrashInLastSession = NO; + self.delegate = nil; - self.feedbackActivated = NO; - self.showAlwaysButton = NO; - self.autoSubmitCrashReport = NO; - self.autoSubmitDeviceUDID = NO; - + self.feedbackActivated = NO; + self.showAlwaysButton = NO; + self.autoSubmitCrashReport = NO; + self.autoSubmitDeviceUDID = NO; + NSString *testValue = [[NSUserDefaults standardUserDefaults] stringForKey:kQuincyKitAnalyzerStarted]; if (testValue) { _analyzerStarted = [[NSUserDefaults standardUserDefaults] integerForKey:kQuincyKitAnalyzerStarted]; @@ -149,7 +149,7 @@ NSString *BWQuincyLocalize(NSString *stringToken) { _crashReportActivated = YES; [[NSUserDefaults standardUserDefaults] setValue:[NSNumber numberWithBool:YES] forKey:kQuincyKitActivated]; } - + if (_crashReportActivated) { _crashFiles = [[NSMutableArray alloc] init]; NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES); @@ -163,52 +163,52 @@ NSString *BWQuincyLocalize(NSString *stringToken) { [fm createDirectoryAtPath:_crashesDir withIntermediateDirectories: YES attributes: attributes error: &theError]; } - + PLCrashReporter *crashReporter = [PLCrashReporter sharedReporter]; NSError *error = NULL; // Check if we previously crashed if ([crashReporter hasPendingCrashReport]) { - _didCrashInLastSession = YES; + _didCrashInLastSession = YES; [self handleCrashReport]; - } - + } + // Enable the Crash Reporter if (![crashReporter enableCrashReporterAndReturnError: &error]) NSLog(@"Warning: Could not enable crash reporter: %@", error); - - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(startManager) name:BWQuincyNetworkBecomeReachable object:nil]; + + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(startManager) name:BWQuincyNetworkBecomeReachable object:nil]; } - - if (!quincyBundle()) { + + if (!quincyBundle()) { NSLog(@"WARNING: Quincy.bundle is missing in the app bundle!"); - } + } } return self; } - (void) dealloc { - self.delegate = nil; - [[NSNotificationCenter defaultCenter] removeObserver:self name:BWQuincyNetworkBecomeReachable object:nil]; - - [_languageStyle release]; - - [_submissionURL release]; - _submissionURL = nil; - - [_appIdentifier release]; - _appIdentifier = nil; - - [_urlConnection cancel]; - [_urlConnection release]; - _urlConnection = nil; - - [_crashData release]; - + self.delegate = nil; + [[NSNotificationCenter defaultCenter] removeObserver:self name:BWQuincyNetworkBecomeReachable object:nil]; + + [_languageStyle release]; + + [_submissionURL release]; + _submissionURL = nil; + + [_appIdentifier release]; + _appIdentifier = nil; + + [_urlConnection cancel]; + [_urlConnection release]; + _urlConnection = nil; + + [_crashData release]; + [_crashesDir release]; [_crashFiles release]; - + [super dealloc]; } @@ -216,21 +216,21 @@ NSString *BWQuincyLocalize(NSString *stringToken) { #pragma mark - #pragma mark setter - (void)setSubmissionURL:(NSString *)anSubmissionURL { - if (_submissionURL != anSubmissionURL) { - [_submissionURL release]; - _submissionURL = [anSubmissionURL copy]; - } - - [self performSelector:@selector(startManager) withObject:nil afterDelay:1.0f]; + if (_submissionURL != anSubmissionURL) { + [_submissionURL release]; + _submissionURL = [anSubmissionURL copy]; + } + + [self performSelector:@selector(startManager) withObject:nil afterDelay:1.0f]; } - (void)setAppIdentifier:(NSString *)anAppIdentifier { - if (_appIdentifier != anAppIdentifier) { - [_appIdentifier release]; - _appIdentifier = [anAppIdentifier copy]; - } - - [self setSubmissionURL:@"https://rink.hockeyapp.net/"]; + if (_appIdentifier != anAppIdentifier) { + [_appIdentifier release]; + _appIdentifier = [anAppIdentifier copy]; + } + + [self setSubmissionURL:@"https://rink.hockeyapp.net/"]; } @@ -238,65 +238,65 @@ NSString *BWQuincyLocalize(NSString *stringToken) { #pragma mark private methods - (BOOL)autoSendCrashReports { - BOOL result = NO; - - if (!self.autoSubmitCrashReport) { - if (self.isShowingAlwaysButton && [[NSUserDefaults standardUserDefaults] boolForKey: kAutomaticallySendCrashReports]) { - result = YES; - } - } else { - result = YES; + BOOL result = NO; + + if (!self.autoSubmitCrashReport) { + if (self.isShowingAlwaysButton && [[NSUserDefaults standardUserDefaults] boolForKey: kAutomaticallySendCrashReports]) { + result = YES; } - - return result; + } else { + result = YES; + } + + return result; } // begin the startup process - (void)startManager { - if (!_sendingInProgress && [self hasPendingCrashReport]) { - _sendingInProgress = YES; - if (!quincyBundle()) { + if (!_sendingInProgress && [self hasPendingCrashReport]) { + _sendingInProgress = YES; + if (!quincyBundle()) { NSLog(@"Quincy.bundle is missing, sending report automatically!"); - [self _sendCrashReports]; - } else if (!self.autoSubmitCrashReport && [self hasNonApprovedCrashReports]) { - - if (self.delegate != nil && [self.delegate respondsToSelector:@selector(willShowSubmitCrashReportAlert)]) { - [self.delegate willShowSubmitCrashReportAlert]; - } - - NSString *appName = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleDisplayName"]; - - UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:[NSString stringWithFormat:BWQuincyLocalize(@"CrashDataFoundTitle"), appName] - message:[NSString stringWithFormat:BWQuincyLocalize(@"CrashDataFoundDescription"), appName] - delegate:self - cancelButtonTitle:BWQuincyLocalize(@"CrashDontSendReport") - otherButtonTitles:BWQuincyLocalize(@"CrashSendReport"), nil]; - - if ([self isShowingAlwaysButton]) { - [alertView addButtonWithTitle:BWQuincyLocalize(@"CrashSendReportAlways")]; - } - - [alertView setTag: QuincyKitAlertTypeSend]; - [alertView show]; - [alertView release]; - } else { - [self _sendCrashReports]; - } + [self _sendCrashReports]; + } else if (!self.autoSubmitCrashReport && [self hasNonApprovedCrashReports]) { + + if (self.delegate != nil && [self.delegate respondsToSelector:@selector(willShowSubmitCrashReportAlert)]) { + [self.delegate willShowSubmitCrashReportAlert]; + } + + NSString *appName = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleDisplayName"]; + + UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:[NSString stringWithFormat:BWQuincyLocalize(@"CrashDataFoundTitle"), appName] + message:[NSString stringWithFormat:BWQuincyLocalize(@"CrashDataFoundDescription"), appName] + delegate:self + cancelButtonTitle:BWQuincyLocalize(@"CrashDontSendReport") + otherButtonTitles:BWQuincyLocalize(@"CrashSendReport"), nil]; + + if ([self isShowingAlwaysButton]) { + [alertView addButtonWithTitle:BWQuincyLocalize(@"CrashSendReportAlways")]; + } + + [alertView setTag: QuincyKitAlertTypeSend]; + [alertView show]; + [alertView release]; + } else { + [self _sendCrashReports]; } + } } - (BOOL)hasNonApprovedCrashReports { - NSDictionary *approvedCrashReports = [[NSUserDefaults standardUserDefaults] dictionaryForKey: kApprovedCrashReports]; - - if (!approvedCrashReports || [approvedCrashReports count] == 0) return YES; - + NSDictionary *approvedCrashReports = [[NSUserDefaults standardUserDefaults] dictionaryForKey: kApprovedCrashReports]; + + if (!approvedCrashReports || [approvedCrashReports count] == 0) return YES; + for (NSUInteger i=0; i < [_crashFiles count]; i++) { NSString *filename = [_crashFiles objectAtIndex:i]; - - if (![approvedCrashReports objectForKey:filename]) return YES; - } - return NO; + if (![approvedCrashReports objectForKey:filename]) return YES; + } + + return NO; } - (BOOL)hasPendingCrashReport { @@ -305,8 +305,8 @@ NSString *BWQuincyLocalize(NSString *stringToken) { if ([_crashFiles count] == 0 && [fm fileExistsAtPath:_crashesDir]) { NSString *file = nil; - NSError *error = NULL; - + NSError *error = NULL; + NSDirectoryEnumerator *dirEnum = [fm enumeratorAtPath: _crashesDir]; while ((file = [dirEnum nextObject])) { @@ -330,31 +330,31 @@ NSString *BWQuincyLocalize(NSString *stringToken) { UIAlertView *alertView = nil; if (_serverResult >= CrashReportStatusAssigned && - _crashIdenticalCurrentVersion && - quincyBundle()) { + _crashIdenticalCurrentVersion && + quincyBundle()) { // show some feedback to the user about the crash status NSString *appName = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleDisplayName"]; switch (_serverResult) { case CrashReportStatusAssigned: alertView = [[UIAlertView alloc] initWithTitle: [NSString stringWithFormat:BWQuincyLocalize(@"CrashResponseTitle"), appName ] - message: [NSString stringWithFormat:BWQuincyLocalize(@"CrashResponseNextRelease"), appName] - delegate: self - cancelButtonTitle: BWQuincyLocalize(@"CrashResponseTitleOK") - otherButtonTitles: nil]; + message: [NSString stringWithFormat:BWQuincyLocalize(@"CrashResponseNextRelease"), appName] + delegate: self + cancelButtonTitle: BWQuincyLocalize(@"CrashResponseTitleOK") + otherButtonTitles: nil]; break; case CrashReportStatusSubmitted: alertView = [[UIAlertView alloc] initWithTitle: [NSString stringWithFormat:BWQuincyLocalize(@"CrashResponseTitle"), appName ] - message: [NSString stringWithFormat:BWQuincyLocalize(@"CrashResponseWaitingApple"), appName] - delegate: self - cancelButtonTitle: BWQuincyLocalize(@"CrashResponseTitleOK") - otherButtonTitles: nil]; + message: [NSString stringWithFormat:BWQuincyLocalize(@"CrashResponseWaitingApple"), appName] + delegate: self + cancelButtonTitle: BWQuincyLocalize(@"CrashResponseTitleOK") + otherButtonTitles: nil]; break; case CrashReportStatusAvailable: alertView = [[UIAlertView alloc] initWithTitle: [NSString stringWithFormat:BWQuincyLocalize(@"CrashResponseTitle"), appName ] - message: [NSString stringWithFormat:BWQuincyLocalize(@"CrashResponseAvailable"), appName] - delegate: self - cancelButtonTitle: BWQuincyLocalize(@"CrashResponseTitleOK") - otherButtonTitles: nil]; + message: [NSString stringWithFormat:BWQuincyLocalize(@"CrashResponseAvailable"), appName] + delegate: self + cancelButtonTitle: BWQuincyLocalize(@"CrashResponseTitleOK") + otherButtonTitles: nil]; break; default: alertView = nil; @@ -377,7 +377,7 @@ NSString *BWQuincyLocalize(NSString *stringToken) { if ([alertView tag] == QuincyKitAlertTypeSend) { switch (buttonIndex) { case 0: - _sendingInProgress = NO; + _sendingInProgress = NO; [self _cleanCrashReports]; break; case 1: @@ -404,7 +404,7 @@ NSString *BWQuincyLocalize(NSString *stringToken) { if ([elementName isEqualToString:@"result"]) { _contentOfProperty = [NSMutableString string]; - } + } } - (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName { @@ -412,12 +412,12 @@ NSString *BWQuincyLocalize(NSString *stringToken) { elementName = qName; } - // open source implementation + // open source implementation if ([elementName isEqualToString: @"result"]) { if ([_contentOfProperty intValue] > _serverResult) { _serverResult = (CrashReportStatus)[_contentOfProperty intValue]; } else { - CrashReportStatus errorcode = (CrashReportStatus)[_contentOfProperty intValue]; + CrashReportStatus errorcode = (CrashReportStatus)[_contentOfProperty intValue]; NSLog(@"CrashReporter ended in error code: %i", errorcode); } } @@ -458,18 +458,18 @@ NSString *BWQuincyLocalize(NSString *stringToken) { } - (void)_performSendingCrashReports { - NSMutableDictionary *approvedCrashReports = [NSMutableDictionary dictionaryWithDictionary:[[NSUserDefaults standardUserDefaults] dictionaryForKey: kApprovedCrashReports]]; - - NSFileManager *fm = [NSFileManager defaultManager]; + NSMutableDictionary *approvedCrashReports = [NSMutableDictionary dictionaryWithDictionary:[[NSUserDefaults standardUserDefaults] dictionaryForKey: kApprovedCrashReports]]; + + NSFileManager *fm = [NSFileManager defaultManager]; NSError *error = NULL; NSString *userid = @""; NSString *contact = @""; NSString *description = @""; - - if (self.autoSubmitDeviceUDID && [[NSBundle mainBundle] pathForResource:@"embedded" ofType:@"mobileprovision"]) { - userid = [self deviceIdentifier]; - } else if (self.delegate != nil && [self.delegate respondsToSelector:@selector(crashReportUserID)]) { + + if (self.autoSubmitDeviceUDID && [[NSBundle mainBundle] pathForResource:@"embedded" ofType:@"mobileprovision"]) { + userid = [self deviceIdentifier]; + } else if (self.delegate != nil && [self.delegate respondsToSelector:@selector(crashReportUserID)]) { userid = [self.delegate crashReportUserID] ?: @""; } @@ -481,9 +481,9 @@ NSString *BWQuincyLocalize(NSString *stringToken) { description = [self.delegate crashReportDescription] ?: @""; } - NSMutableString *crashes = nil; - _crashIdenticalCurrentVersion = NO; - + NSMutableString *crashes = nil; + _crashIdenticalCurrentVersion = NO; + for (NSUInteger i=0; i < [_crashFiles count]; i++) { NSString *filename = [_crashesDir stringByAppendingPathComponent:[_crashFiles objectAtIndex:i]]; NSData *crashData = [NSData dataWithContentsOfFile:filename]; @@ -491,87 +491,87 @@ NSString *BWQuincyLocalize(NSString *stringToken) { if ([crashData length] > 0) { PLCrashReport *report = [[[PLCrashReport alloc] initWithData:crashData error:&error] autorelease]; - if (report == nil) { - NSLog(@"Could not parse crash report"); - continue; - } - + if (report == nil) { + NSLog(@"Could not parse crash report"); + continue; + } + NSString *crashLogString = [PLCrashReportTextFormatter stringValueForCrashReport:report withTextFormat:PLCrashReportTextFormatiOS]; - + if ([report.applicationInfo.applicationVersion compare:[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"]] == NSOrderedSame) { _crashIdenticalCurrentVersion = YES; } - if (crashes == nil) { - crashes = [NSMutableString string]; - } - + if (crashes == nil) { + crashes = [NSMutableString string]; + } + [crashes appendFormat:@"%s%@%@%@%@%@%@%@", - [[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleExecutable"] UTF8String], - report.applicationInfo.applicationIdentifier, - report.systemInfo.operatingSystemVersion, - [self _getDevicePlatform], - [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"], - report.applicationInfo.applicationVersion, - [crashLogString stringByReplacingOccurrencesOfString:@"]]>" withString:@"]]" @"]]>" options:NSLiteralSearch range:NSMakeRange(0,crashLogString.length)], - userid, - contact, + [[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleExecutable"] UTF8String], + report.applicationInfo.applicationIdentifier, + report.systemInfo.operatingSystemVersion, + [self _getDevicePlatform], + [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"], + report.applicationInfo.applicationVersion, + [crashLogString stringByReplacingOccurrencesOfString:@"]]>" withString:@"]]" @"]]>" options:NSLiteralSearch range:NSMakeRange(0,crashLogString.length)], + userid, + contact, [description stringByReplacingOccurrencesOfString:@"]]>" withString:@"]]" @"]]>" options:NSLiteralSearch range:NSMakeRange(0,description.length)]]; - - - // store this crash report as user approved, so if it fails it will retry automatically - [approvedCrashReports setObject:[NSNumber numberWithBool:YES] forKey:[_crashFiles objectAtIndex:i]]; + + + // store this crash report as user approved, so if it fails it will retry automatically + [approvedCrashReports setObject:[NSNumber numberWithBool:YES] forKey:[_crashFiles objectAtIndex:i]]; } else { - // we cannot do anything with this report, so delete it - [fm removeItemAtPath:filename error:&error]; - } + // we cannot do anything with this report, so delete it + [fm removeItemAtPath:filename error:&error]; + } } - [[NSUserDefaults standardUserDefaults] setObject:approvedCrashReports forKey:kApprovedCrashReports]; - [[NSUserDefaults standardUserDefaults] synchronize]; - - if (crashes != nil) { - [self _postXML:[NSString stringWithFormat:@"%@", crashes] - toURL:[NSURL URLWithString:self.submissionURL]]; - - } + [[NSUserDefaults standardUserDefaults] setObject:approvedCrashReports forKey:kApprovedCrashReports]; + [[NSUserDefaults standardUserDefaults] synchronize]; + + if (crashes != nil) { + [self _postXML:[NSString stringWithFormat:@"%@", crashes] + toURL:[NSURL URLWithString:self.submissionURL]]; + + } } - (void)_cleanCrashReports { - NSError *error = NULL; - - NSFileManager *fm = [NSFileManager defaultManager]; - - for (NSUInteger i=0; i < [_crashFiles count]; i++) { - [fm removeItemAtPath:[_crashesDir stringByAppendingPathComponent:[_crashFiles objectAtIndex:i]] error:&error]; - } - [_crashFiles removeAllObjects]; - - [[NSUserDefaults standardUserDefaults] setObject:nil forKey:kApprovedCrashReports]; - [[NSUserDefaults standardUserDefaults] synchronize]; + NSError *error = NULL; + + NSFileManager *fm = [NSFileManager defaultManager]; + + for (NSUInteger i=0; i < [_crashFiles count]; i++) { + [fm removeItemAtPath:[_crashesDir stringByAppendingPathComponent:[_crashFiles objectAtIndex:i]] error:&error]; + } + [_crashFiles removeAllObjects]; + + [[NSUserDefaults standardUserDefaults] setObject:nil forKey:kApprovedCrashReports]; + [[NSUserDefaults standardUserDefaults] synchronize]; } - (void)_sendCrashReports { - // send it to the next runloop - [self performSelector:@selector(_performSendingCrashReports) withObject:nil afterDelay:0.0f]; + // send it to the next runloop + [self performSelector:@selector(_performSendingCrashReports) withObject:nil afterDelay:0.0f]; } - (void)_checkForFeedbackStatus { - NSMutableURLRequest *request = nil; - - request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:[NSString stringWithFormat:@"%@api/2/apps/%@/crashes/%@", - self.submissionURL, - [self.appIdentifier stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding], - _feedbackRequestID - ] - ]]; - + NSMutableURLRequest *request = nil; + + request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:[NSString stringWithFormat:@"%@api/2/apps/%@/crashes/%@", + self.submissionURL, + [self.appIdentifier stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding], + _feedbackRequestID + ] + ]]; + [request setCachePolicy: NSURLRequestReloadIgnoringLocalCacheData]; [request setValue:@"Quincy/iOS" forHTTPHeaderField:@"User-Agent"]; - [request setValue:@"gzip" forHTTPHeaderField:@"Accept-Encoding"]; + [request setValue:@"gzip" forHTTPHeaderField:@"Accept-Encoding"]; [request setTimeoutInterval: 15]; [request setHTTPMethod:@"GET"]; - + _serverResult = CrashReportStatusUnknown; _statusCode = 200; @@ -587,21 +587,21 @@ NSString *BWQuincyLocalize(NSString *stringToken) { - (void)_postXML:(NSString*)xml toURL:(NSURL*)url { NSMutableURLRequest *request = nil; - NSString *boundary = @"----FOO"; - - if (self.appIdentifier) { - request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:[NSString stringWithFormat:@"%@api/2/apps/%@/crashes", - self.submissionURL, - [self.appIdentifier stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding] - ] - ]]; - } else { - request = [NSMutableURLRequest requestWithURL:url]; - } - + NSString *boundary = @"----FOO"; + + if (self.appIdentifier) { + request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:[NSString stringWithFormat:@"%@api/2/apps/%@/crashes", + self.submissionURL, + [self.appIdentifier stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding] + ] + ]]; + } else { + request = [NSMutableURLRequest requestWithURL:url]; + } + [request setCachePolicy: NSURLRequestReloadIgnoringLocalCacheData]; [request setValue:@"Quincy/iOS" forHTTPHeaderField:@"User-Agent"]; - [request setValue:@"gzip" forHTTPHeaderField:@"Accept-Encoding"]; + [request setValue:@"gzip" forHTTPHeaderField:@"Accept-Encoding"]; [request setTimeoutInterval: 15]; [request setHTTPMethod:@"POST"]; NSString *contentType = [NSString stringWithFormat:@"multipart/form-data; boundary=%@", boundary]; @@ -609,16 +609,16 @@ NSString *BWQuincyLocalize(NSString *stringToken) { NSMutableData *postBody = [NSMutableData data]; [postBody appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]]; - if (self.appIdentifier) { - [postBody appendData:[@"Content-Disposition: form-data; name=\"xml\"; filename=\"crash.xml\"\r\n" dataUsingEncoding:NSUTF8StringEncoding]]; - [postBody appendData:[[NSString stringWithFormat:@"Content-Type: text/xml\r\n\r\n"] dataUsingEncoding:NSUTF8StringEncoding]]; - } else { - [postBody appendData:[@"Content-Disposition: form-data; name=\"xmlstring\"\r\n\r\n" dataUsingEncoding:NSUTF8StringEncoding]]; + if (self.appIdentifier) { + [postBody appendData:[@"Content-Disposition: form-data; name=\"xml\"; filename=\"crash.xml\"\r\n" dataUsingEncoding:NSUTF8StringEncoding]]; + [postBody appendData:[[NSString stringWithFormat:@"Content-Type: text/xml\r\n\r\n"] dataUsingEncoding:NSUTF8StringEncoding]]; + } else { + [postBody appendData:[@"Content-Disposition: form-data; name=\"xmlstring\"\r\n\r\n" dataUsingEncoding:NSUTF8StringEncoding]]; } - [postBody appendData:[xml dataUsingEncoding:NSUTF8StringEncoding]]; - [postBody appendData:[[NSString stringWithFormat:@"\r\n--%@--\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]]; - - [request setHTTPBody:postBody]; + [postBody appendData:[xml dataUsingEncoding:NSUTF8StringEncoding]]; + [postBody appendData:[[NSString stringWithFormat:@"\r\n--%@--\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]]; + + [request setHTTPBody:postBody]; _serverResult = CrashReportStatusUnknown; _statusCode = 200; @@ -631,10 +631,10 @@ NSString *BWQuincyLocalize(NSString *stringToken) { } _urlConnection = [[NSURLConnection alloc] initWithRequest:request delegate:self]; - - if (!_urlConnection) { - _sendingInProgress = NO; - } + + if (!_urlConnection) { + _sendingInProgress = NO; + } } #pragma mark NSURLConnection Delegate @@ -652,70 +652,70 @@ NSString *BWQuincyLocalize(NSString *stringToken) { - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error { [_responseData release]; _responseData = nil; - _urlConnection = nil; + _urlConnection = nil; if (self.delegate != nil && [self.delegate respondsToSelector:@selector(connectionClosed)]) { [self.delegate connectionClosed]; } - - _sendingInProgress = NO; + + _sendingInProgress = NO; } - (void)connectionDidFinishLoading:(NSURLConnection *)connection { if (_statusCode >= 200 && _statusCode < 400) { - [self _cleanCrashReports]; - - _feedbackRequestID = nil; - if (self.appIdentifier) { - // HockeyApp uses PList XML format - NSMutableDictionary *response = [NSPropertyListSerialization propertyListFromData:_responseData - mutabilityOption:NSPropertyListMutableContainersAndLeaves - format:nil - errorDescription:NULL]; - _serverResult = (CrashReportStatus)[[response objectForKey:@"status"] intValue]; - if ([response objectForKey:@"id"]) { - _feedbackRequestID = [[NSString alloc] initWithString:[response objectForKey:@"id"]]; - _feedbackDelayInterval = [[response objectForKey:@"delay"] floatValue]; - if (_feedbackDelayInterval > 0) - _feedbackDelayInterval *= 0.01; - } - } else { - NSXMLParser *parser = [[NSXMLParser alloc] initWithData:_responseData]; - // Set self as the delegate of the parser so that it will receive the parser delegate methods callbacks. - [parser setDelegate:self]; - // Depending on the XML document you're parsing, you may want to enable these features of NSXMLParser. - [parser setShouldProcessNamespaces:NO]; - [parser setShouldReportNamespacePrefixes:NO]; - [parser setShouldResolveExternalEntities:NO]; - - [parser parse]; - - [parser release]; - } - - if ([self isFeedbackActivated]) { - // only proceed if the server did not report any problem - if ((self.appIdentifier) && (_serverResult == CrashReportStatusQueued)) { - // the report is still in the queue - if (_feedbackRequestID) { - [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(_checkForFeedbackStatus) object:nil]; - [self performSelector:@selector(_checkForFeedbackStatus) withObject:nil afterDelay:_feedbackDelayInterval]; - } - } else { - [self showCrashStatusMessage]; - } + [self _cleanCrashReports]; + + _feedbackRequestID = nil; + if (self.appIdentifier) { + // HockeyApp uses PList XML format + NSMutableDictionary *response = [NSPropertyListSerialization propertyListFromData:_responseData + mutabilityOption:NSPropertyListMutableContainersAndLeaves + format:nil + errorDescription:NULL]; + _serverResult = (CrashReportStatus)[[response objectForKey:@"status"] intValue]; + if ([response objectForKey:@"id"]) { + _feedbackRequestID = [[NSString alloc] initWithString:[response objectForKey:@"id"]]; + _feedbackDelayInterval = [[response objectForKey:@"delay"] floatValue]; + if (_feedbackDelayInterval > 0) + _feedbackDelayInterval *= 0.01; + } + } else { + NSXMLParser *parser = [[NSXMLParser alloc] initWithData:_responseData]; + // Set self as the delegate of the parser so that it will receive the parser delegate methods callbacks. + [parser setDelegate:self]; + // Depending on the XML document you're parsing, you may want to enable these features of NSXMLParser. + [parser setShouldProcessNamespaces:NO]; + [parser setShouldReportNamespacePrefixes:NO]; + [parser setShouldResolveExternalEntities:NO]; + + [parser parse]; + + [parser release]; + } + + if ([self isFeedbackActivated]) { + // only proceed if the server did not report any problem + if ((self.appIdentifier) && (_serverResult == CrashReportStatusQueued)) { + // the report is still in the queue + if (_feedbackRequestID) { + [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(_checkForFeedbackStatus) object:nil]; + [self performSelector:@selector(_checkForFeedbackStatus) withObject:nil afterDelay:_feedbackDelayInterval]; } + } else { + [self showCrashStatusMessage]; + } + } } [_responseData release]; _responseData = nil; - _urlConnection = nil; + _urlConnection = nil; if (self.delegate != nil && [self.delegate respondsToSelector:@selector(connectionClosed)]) { [self.delegate connectionClosed]; } - - _sendingInProgress = NO; + + _sendingInProgress = NO; } #pragma mark PLCrashReporter @@ -727,22 +727,22 @@ NSString *BWQuincyLocalize(NSString *stringToken) { PLCrashReporter *crashReporter = [PLCrashReporter sharedReporter]; NSError *error = NULL; - // check if the next call ran successfully the last time + // check if the next call ran successfully the last time if (_analyzerStarted == 0) { // mark the start of the routine _analyzerStarted = 1; [[NSUserDefaults standardUserDefaults] setValue:[NSNumber numberWithInt:_analyzerStarted] forKey:kQuincyKitAnalyzerStarted]; [[NSUserDefaults standardUserDefaults] synchronize]; - - // Try loading the crash report - _crashData = [[NSData alloc] initWithData:[crashReporter loadPendingCrashReportDataAndReturnError: &error]]; - - NSString *cacheFilename = [NSString stringWithFormat: @"%.0f", [NSDate timeIntervalSinceReferenceDate]]; - - if (_crashData == nil) { - NSLog(@"Could not load crash report: %@", error); - } else { - [_crashData writeToFile:[_crashesDir stringByAppendingPathComponent: cacheFilename] atomically:YES]; + + // Try loading the crash report + _crashData = [[NSData alloc] initWithData:[crashReporter loadPendingCrashReportDataAndReturnError: &error]]; + + NSString *cacheFilename = [NSString stringWithFormat: @"%.0f", [NSDate timeIntervalSinceReferenceDate]]; + + if (_crashData == nil) { + NSLog(@"Could not load crash report: %@", error); + } else { + [_crashData writeToFile:[_crashesDir stringByAppendingPathComponent: cacheFilename] atomically:YES]; } } @@ -750,8 +750,8 @@ NSString *BWQuincyLocalize(NSString *stringToken) { // mark the end of the routine _analyzerStarted = 0; [[NSUserDefaults standardUserDefaults] setValue:[NSNumber numberWithInt:_analyzerStarted] forKey:kQuincyKitAnalyzerStarted]; - [[NSUserDefaults standardUserDefaults] synchronize]; - + [[NSUserDefaults standardUserDefaults] synchronize]; + [crashReporter purgePendingCrashReport]; return; } diff --git a/Classes/NSString+HockeyAdditions.m b/Classes/NSString+HockeyAdditions.m index 168cd65016..6bd0600d09 100755 --- a/Classes/NSString+HockeyAdditions.m +++ b/Classes/NSString+HockeyAdditions.m @@ -29,22 +29,22 @@ @implementation NSString (HockeyAdditions) - (NSString *)bw_URLEncodedString { - NSString *result = (NSString *)CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, - (CFStringRef)self, - NULL, - CFSTR("!*'();:@&=+$,/?%#[]"), - kCFStringEncodingUTF8); - [result autorelease]; - return result; + NSString *result = (NSString *)CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, + (CFStringRef)self, + NULL, + CFSTR("!*'();:@&=+$,/?%#[]"), + kCFStringEncodingUTF8); + [result autorelease]; + return result; } - (NSString*)bw_URLDecodedString { - NSString *result = (NSString *)CFURLCreateStringByReplacingPercentEscapesUsingEncoding(kCFAllocatorDefault, - (CFStringRef)self, - CFSTR(""), - kCFStringEncodingUTF8); - [result autorelease]; - return result; + NSString *result = (NSString *)CFURLCreateStringByReplacingPercentEscapesUsingEncoding(kCFAllocatorDefault, + (CFStringRef)self, + CFSTR(""), + kCFStringEncodingUTF8); + [result autorelease]; + return result; } - (NSComparisonResult)versionCompare:(NSString *)other diff --git a/Classes/PSAppStoreHeader.h b/Classes/PSAppStoreHeader.h index 1d9a8f7a3e..c885d1e607 100644 --- a/Classes/PSAppStoreHeader.h +++ b/Classes/PSAppStoreHeader.h @@ -26,12 +26,12 @@ #import @interface PSAppStoreHeader : UIView { - NSString *headerLabel_; - NSString *middleHeaderLabel_; - NSString *subHeaderLabel; - UIImage *iconImage_; - - UIImage *reflectedImage_; + NSString *headerLabel_; + NSString *middleHeaderLabel_; + NSString *subHeaderLabel; + UIImage *iconImage_; + + UIImage *reflectedImage_; } @property (nonatomic, copy) NSString *headerLabel; diff --git a/Classes/PSAppStoreHeader.m b/Classes/PSAppStoreHeader.m index c7898aeca0..76af969fcf 100644 --- a/Classes/PSAppStoreHeader.m +++ b/Classes/PSAppStoreHeader.m @@ -50,20 +50,20 @@ #pragma mark NSObject - (id)initWithFrame:(CGRect)frame { - if ((self = [super initWithFrame:frame])) { - self.autoresizingMask = UIViewAutoresizingFlexibleWidth; - self.backgroundColor = kLightGrayColor; - } - return self; + if ((self = [super initWithFrame:frame])) { + self.autoresizingMask = UIViewAutoresizingFlexibleWidth; + self.backgroundColor = kLightGrayColor; + } + return self; } - (void)dealloc { - [headerLabel_ release]; - [middleHeaderLabel_ release]; - [subHeaderLabel release]; - [iconImage_ release]; - - [super dealloc]; + [headerLabel_ release]; + [middleHeaderLabel_ release]; + [subHeaderLabel release]; + [iconImage_ release]; + + [super dealloc]; } @@ -72,56 +72,56 @@ #pragma mark UIView - (void)drawRect:(CGRect)rect { - CGRect bounds = self.bounds; - CGFloat globalWidth = self.frame.size.width; - CGContextRef context = UIGraphicsGetCurrentContext(); - - // draw the gradient - NSArray *colors = [NSArray arrayWithObjects:(id)kDarkGrayColor.CGColor, (id)kLightGrayColor.CGColor, nil]; - CGGradientRef gradient = CGGradientCreateWithColors(CGColorGetColorSpace((CGColorRef)[colors objectAtIndex:0]), (CFArrayRef)colors, (CGFloat[2]){0, 1}); - CGPoint top = CGPointMake(CGRectGetMidX(bounds), bounds.origin.y); - CGPoint bottom = CGPointMake(CGRectGetMidX(bounds), CGRectGetMaxY(bounds)-kReflectionHeight); - CGContextDrawLinearGradient(context, gradient, top, bottom, 0); - CGGradientRelease(gradient); - - // draw header name - UIColor *mainTextColor = BW_RGBCOLOR(0,0,0); - UIColor *secondaryTextColor = BW_RGBCOLOR(48,48,48); - UIFont *mainFont = [UIFont boldSystemFontOfSize:20]; - UIFont *secondaryFont = [UIFont boldSystemFontOfSize:12]; - UIFont *smallFont = [UIFont systemFontOfSize:12]; - - float myColorValues[] = {255, 255, 255, .6}; - CGColorSpaceRef myColorSpace = CGColorSpaceCreateDeviceRGB(); - CGColorRef myColor = CGColorCreate(myColorSpace, myColorValues); - - // icon - [iconImage_ drawAtPoint:CGPointMake(kImageMargin, kImageMargin)]; - [reflectedImage_ drawAtPoint:CGPointMake(kImageMargin, kImageMargin+kImageHeight)]; - - // shadows are a beast - NSInteger shadowOffset = 2; - BW_IF_IOS4_OR_GREATER(if([[UIScreen mainScreen] scale] == 2) shadowOffset = 1;) - BW_IF_IOS5_OR_GREATER(shadowOffset = 1;) // iOS5 changes this - again! - - BW_IF_3_2_OR_GREATER(CGContextSetShadowWithColor(context, CGSizeMake(shadowOffset, shadowOffset), 0, myColor);) - BW_IF_PRE_3_2(shadowOffset=1;CGContextSetShadowWithColor(context, CGSizeMake(shadowOffset, -shadowOffset), 0, myColor);) - - - [mainTextColor set]; - [headerLabel_ drawInRect:CGRectMake(kTextRow, kImageMargin, globalWidth-kTextRow, 20) withFont:mainFont lineBreakMode:UILineBreakModeTailTruncation]; - - // middle - [secondaryTextColor set]; - [middleHeaderLabel_ drawInRect:CGRectMake(kTextRow, kImageMargin + 25, globalWidth-kTextRow, 20) withFont:secondaryFont lineBreakMode:UILineBreakModeTailTruncation]; - CGContextSetShadowWithColor(context, CGSizeZero, 0, nil); - - // sub - [secondaryTextColor set]; - [subHeaderLabel drawAtPoint:CGPointMake(kTextRow, kImageMargin+kImageHeight-12) forWidth:globalWidth-kTextRow withFont:smallFont lineBreakMode:UILineBreakModeTailTruncation]; - - CGColorRelease(myColor); - CGColorSpaceRelease(myColorSpace); + CGRect bounds = self.bounds; + CGFloat globalWidth = self.frame.size.width; + CGContextRef context = UIGraphicsGetCurrentContext(); + + // draw the gradient + NSArray *colors = [NSArray arrayWithObjects:(id)kDarkGrayColor.CGColor, (id)kLightGrayColor.CGColor, nil]; + CGGradientRef gradient = CGGradientCreateWithColors(CGColorGetColorSpace((CGColorRef)[colors objectAtIndex:0]), (CFArrayRef)colors, (CGFloat[2]){0, 1}); + CGPoint top = CGPointMake(CGRectGetMidX(bounds), bounds.origin.y); + CGPoint bottom = CGPointMake(CGRectGetMidX(bounds), CGRectGetMaxY(bounds)-kReflectionHeight); + CGContextDrawLinearGradient(context, gradient, top, bottom, 0); + CGGradientRelease(gradient); + + // draw header name + UIColor *mainTextColor = BW_RGBCOLOR(0,0,0); + UIColor *secondaryTextColor = BW_RGBCOLOR(48,48,48); + UIFont *mainFont = [UIFont boldSystemFontOfSize:20]; + UIFont *secondaryFont = [UIFont boldSystemFontOfSize:12]; + UIFont *smallFont = [UIFont systemFontOfSize:12]; + + float myColorValues[] = {255, 255, 255, .6}; + CGColorSpaceRef myColorSpace = CGColorSpaceCreateDeviceRGB(); + CGColorRef myColor = CGColorCreate(myColorSpace, myColorValues); + + // icon + [iconImage_ drawAtPoint:CGPointMake(kImageMargin, kImageMargin)]; + [reflectedImage_ drawAtPoint:CGPointMake(kImageMargin, kImageMargin+kImageHeight)]; + + // shadows are a beast + NSInteger shadowOffset = 2; + BW_IF_IOS4_OR_GREATER(if([[UIScreen mainScreen] scale] == 2) shadowOffset = 1;) + BW_IF_IOS5_OR_GREATER(shadowOffset = 1;) // iOS5 changes this - again! + + BW_IF_3_2_OR_GREATER(CGContextSetShadowWithColor(context, CGSizeMake(shadowOffset, shadowOffset), 0, myColor);) + BW_IF_PRE_3_2(shadowOffset=1;CGContextSetShadowWithColor(context, CGSizeMake(shadowOffset, -shadowOffset), 0, myColor);) + + + [mainTextColor set]; + [headerLabel_ drawInRect:CGRectMake(kTextRow, kImageMargin, globalWidth-kTextRow, 20) withFont:mainFont lineBreakMode:UILineBreakModeTailTruncation]; + + // middle + [secondaryTextColor set]; + [middleHeaderLabel_ drawInRect:CGRectMake(kTextRow, kImageMargin + 25, globalWidth-kTextRow, 20) withFont:secondaryFont lineBreakMode:UILineBreakModeTailTruncation]; + CGContextSetShadowWithColor(context, CGSizeZero, 0, nil); + + // sub + [secondaryTextColor set]; + [subHeaderLabel drawAtPoint:CGPointMake(kTextRow, kImageMargin+kImageHeight-12) forWidth:globalWidth-kTextRow withFont:smallFont lineBreakMode:UILineBreakModeTailTruncation]; + + CGColorRelease(myColor); + CGColorSpaceRelease(myColorSpace); } /////////////////////////////////////////////////////////////////////////////////////////////////// @@ -129,45 +129,45 @@ #pragma mark Properties - (void)setHeaderLabel:(NSString *)anHeaderLabel { - if (headerLabel_ != anHeaderLabel) { - [headerLabel_ release]; - headerLabel_ = [anHeaderLabel copy]; - [self setNeedsDisplay]; - } + if (headerLabel_ != anHeaderLabel) { + [headerLabel_ release]; + headerLabel_ = [anHeaderLabel copy]; + [self setNeedsDisplay]; + } } - (void)setMiddleHeaderLabel:(NSString *)aMiddleHeaderLabel { - if (middleHeaderLabel_ != aMiddleHeaderLabel) { - [middleHeaderLabel_ release]; - middleHeaderLabel_ = [aMiddleHeaderLabel copy]; - [self setNeedsDisplay]; - } + if (middleHeaderLabel_ != aMiddleHeaderLabel) { + [middleHeaderLabel_ release]; + middleHeaderLabel_ = [aMiddleHeaderLabel copy]; + [self setNeedsDisplay]; + } } - (void)setSubHeaderLabel:(NSString *)aSubHeaderLabel { - if (subHeaderLabel != aSubHeaderLabel) { - [subHeaderLabel release]; - subHeaderLabel = [aSubHeaderLabel copy]; - [self setNeedsDisplay]; - } + if (subHeaderLabel != aSubHeaderLabel) { + [subHeaderLabel release]; + subHeaderLabel = [aSubHeaderLabel copy]; + [self setNeedsDisplay]; + } } - (void)setIconImage:(UIImage *)anIconImage { - if (iconImage_ != anIconImage) { - [iconImage_ release]; - - // scale, make borders and reflection - iconImage_ = [anIconImage bw_imageToFitSize:CGSizeMake(kImageHeight, kImageHeight) honorScaleFactor:YES]; - iconImage_ = [[iconImage_ bw_roundedCornerImage:kImageBorderRadius borderSize:0.0] retain]; - - // create reflected image - [reflectedImage_ release]; - reflectedImage_ = nil; - if (anIconImage) { - reflectedImage_ = [[iconImage_ bw_reflectedImageWithHeight:kReflectionHeight fromAlpha:0.5 toAlpha:0.0] retain]; - } - [self setNeedsDisplay]; + if (iconImage_ != anIconImage) { + [iconImage_ release]; + + // scale, make borders and reflection + iconImage_ = [anIconImage bw_imageToFitSize:CGSizeMake(kImageHeight, kImageHeight) honorScaleFactor:YES]; + iconImage_ = [[iconImage_ bw_roundedCornerImage:kImageBorderRadius borderSize:0.0] retain]; + + // create reflected image + [reflectedImage_ release]; + reflectedImage_ = nil; + if (anIconImage) { + reflectedImage_ = [[iconImage_ bw_reflectedImageWithHeight:kReflectionHeight fromAlpha:0.5 toAlpha:0.0] retain]; } + [self setNeedsDisplay]; + } } @end diff --git a/Classes/PSStoreButton.h b/Classes/PSStoreButton.h index 10356f0333..cf01390060 100644 --- a/Classes/PSStoreButton.h +++ b/Classes/PSStoreButton.h @@ -28,10 +28,10 @@ // defines a button action set (data container) @interface PSStoreButtonData : NSObject { - CGPoint customPadding_; - NSString *label_; - NSArray *colors_; - BOOL enabled_; + CGPoint customPadding_; + NSString *label_; + NSArray *colors_; + BOOL enabled_; } + (id)dataWithLabel:(NSString*)aLabel colors:(NSArray*)aColors enabled:(BOOL)flag; @@ -52,11 +52,11 @@ // Simulate the Paymeny-Button from the AppStore // The interface is flexible, so there is now fixed order @interface PSStoreButton : UIButton { - PSStoreButtonData *buttonData_; - id buttonDelegate_; - - CAGradientLayer *gradient_; - CGPoint customPadding_; + PSStoreButtonData *buttonData_; + id buttonDelegate_; + + CAGradientLayer *gradient_; + CGPoint customPadding_; } - (id)initWithFrame:(CGRect)frame; diff --git a/Classes/PSStoreButton.m b/Classes/PSStoreButton.m index 4eb23997cb..629d74b3ce 100644 --- a/Classes/PSStoreButton.m +++ b/Classes/PSStoreButton.m @@ -50,23 +50,23 @@ #pragma mark NSObject - (id)initWithLabel:(NSString*)aLabel colors:(NSArray*)aColors enabled:(BOOL)flag { - if ((self = [super init])) { - self.label = aLabel; - self.colors = aColors; - self.enabled = flag; - } - return self; + if ((self = [super init])) { + self.label = aLabel; + self.colors = aColors; + self.enabled = flag; + } + return self; } + (id)dataWithLabel:(NSString*)aLabel colors:(NSArray*)aColors enabled:(BOOL)flag { - return [[[[self class] alloc] initWithLabel:aLabel colors:aColors enabled:flag] autorelease]; + return [[[[self class] alloc] initWithLabel:aLabel colors:aColors enabled:flag] autorelease]; } - (void)dealloc { - [label_ release]; - [colors_ release]; - - [super dealloc]; + [label_ release]; + [colors_ release]; + + [super dealloc]; } @end @@ -88,86 +88,86 @@ #pragma mark private - (void)touchedUpOutside:(id)sender { - PSLog(@"touched outside..."); + PSLog(@"touched outside..."); } - (void)buttonPressed:(id)sender { - PSLog(@"calling delegate:storeButtonFired for %@", sender); - [buttonDelegate_ storeButtonFired:self]; + PSLog(@"calling delegate:storeButtonFired for %@", sender); + [buttonDelegate_ storeButtonFired:self]; } - (void)animationDidStop:(NSString *)animationID finished:(NSNumber *)finished context:(void *)context { - // show text again, but only if animation did finish (or else another animation is on the way) - if ([finished boolValue]) { - [self setTitle:self.buttonData.label forState:UIControlStateNormal]; - } + // show text again, but only if animation did finish (or else another animation is on the way) + if ([finished boolValue]) { + [self setTitle:self.buttonData.label forState:UIControlStateNormal]; + } } - (void)updateButtonAnimated:(BOOL)animated { + if (animated) { + // hide text, then start animation + [self setTitle:@"" forState:UIControlStateNormal]; + [UIView beginAnimations:@"storeButtonUpdate" context:nil]; + [UIView setAnimationBeginsFromCurrentState:YES]; + [UIView setAnimationDuration:kDefaultButtonAnimationTime]; + [UIView setAnimationDelegate:self]; + [UIView setAnimationDidStopSelector:@selector(animationDidStop:finished:context:)]; + }else { + [self setTitle:self.buttonData.label forState:UIControlStateNormal]; + } + + self.enabled = self.buttonData.isEnabled; + gradient_.colors = self.buttonData.colors; + + // show white or gray text, depending on the state + if (self.buttonData.isEnabled) { + [self setTitleShadowColor:[UIColor colorWithWhite:0.200 alpha:1.000] forState:UIControlStateNormal]; + [self.titleLabel setShadowOffset:CGSizeMake(0.0, -0.6)]; + [self setTitleColor:[UIColor colorWithWhite:1.0 alpha:1.000] forState:UIControlStateNormal]; + }else { + [self.titleLabel setShadowOffset:CGSizeMake(0.0, 0.0)]; + [self setTitleColor:PS_RGBCOLOR(148,150,151) forState:UIControlStateNormal]; + } + + // calculate optimal new size + CGSize sizeThatFits = [self sizeThatFits:CGSizeZero]; + + // move sublayer (can't be animated explcitely) + for (CALayer *aLayer in self.layer.sublayers) { + [CATransaction begin]; + if (animated) { - // hide text, then start animation - [self setTitle:@"" forState:UIControlStateNormal]; - [UIView beginAnimations:@"storeButtonUpdate" context:nil]; - [UIView setAnimationBeginsFromCurrentState:YES]; - [UIView setAnimationDuration:kDefaultButtonAnimationTime]; - [UIView setAnimationDelegate:self]; - [UIView setAnimationDidStopSelector:@selector(animationDidStop:finished:context:)]; + [CATransaction setAnimationDuration:kDefaultButtonAnimationTime]; + [CATransaction setAnimationTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]]; }else { - [self setTitle:self.buttonData.label forState:UIControlStateNormal]; + // frame is calculated and explicitely animated. so we absolutely need kCATransactionDisableActions + [CATransaction setValue:[NSNumber numberWithBool:YES] forKey:kCATransactionDisableActions]; } - - self.enabled = self.buttonData.isEnabled; - gradient_.colors = self.buttonData.colors; - - // show white or gray text, depending on the state - if (self.buttonData.isEnabled) { - [self setTitleShadowColor:[UIColor colorWithWhite:0.200 alpha:1.000] forState:UIControlStateNormal]; - [self.titleLabel setShadowOffset:CGSizeMake(0.0, -0.6)]; - [self setTitleColor:[UIColor colorWithWhite:1.0 alpha:1.000] forState:UIControlStateNormal]; - }else { - [self.titleLabel setShadowOffset:CGSizeMake(0.0, 0.0)]; - [self setTitleColor:PS_RGBCOLOR(148,150,151) forState:UIControlStateNormal]; - } - - // calculate optimal new size - CGSize sizeThatFits = [self sizeThatFits:CGSizeZero]; - - // move sublayer (can't be animated explcitely) - for (CALayer *aLayer in self.layer.sublayers) { - [CATransaction begin]; - - if (animated) { - [CATransaction setAnimationDuration:kDefaultButtonAnimationTime]; - [CATransaction setAnimationTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]]; - }else { - // frame is calculated and explicitely animated. so we absolutely need kCATransactionDisableActions - [CATransaction setValue:[NSNumber numberWithBool:YES] forKey:kCATransactionDisableActions]; - } - - CGRect newFrame = aLayer.frame; - newFrame.size.width = sizeThatFits.width; - aLayer.frame = newFrame; - - [CATransaction commit]; + + CGRect newFrame = aLayer.frame; + newFrame.size.width = sizeThatFits.width; + aLayer.frame = newFrame; + + [CATransaction commit]; } - - // set outer frame changes - self.titleEdgeInsets = UIEdgeInsetsMake(2.0, self.titleEdgeInsets.left, 0.0, 0.0); - [self alignToSuperview]; - - if (animated) { - [UIView commitAnimations]; - } + + // set outer frame changes + self.titleEdgeInsets = UIEdgeInsetsMake(2.0, self.titleEdgeInsets.left, 0.0, 0.0); + [self alignToSuperview]; + + if (animated) { + [UIView commitAnimations]; + } } - (void)alignToSuperview { - [self sizeToFit]; - if (self.superview) { - CGRect cr = self.frame; - cr.origin.y = customPadding_.y; - cr.origin.x = self.superview.frame.size.width - cr.size.width - customPadding_.x * 2; - self.frame = cr; - } + [self sizeToFit]; + if (self.superview) { + CGRect cr = self.frame; + cr.origin.y = customPadding_.y; + cr.origin.x = self.superview.frame.size.width - cr.size.width - customPadding_.x * 2; + self.frame = cr; + } } /////////////////////////////////////////////////////////////////////////////////////////////////// @@ -175,55 +175,55 @@ #pragma mark NSObject - (id)initWithFrame:(CGRect)frame { - if ((self = [super initWithFrame:frame])) { + if ((self = [super initWithFrame:frame])) { self.layer.needsDisplayOnBoundsChange = YES; - - // setup title label - [self.titleLabel setFont:[UIFont boldSystemFontOfSize:13.0]]; - - // register for touch events - [self addTarget:self action:@selector(touchedUpOutside:) forControlEvents:UIControlEventTouchUpOutside]; + + // setup title label + [self.titleLabel setFont:[UIFont boldSystemFontOfSize:13.0]]; + + // register for touch events + [self addTarget:self action:@selector(touchedUpOutside:) forControlEvents:UIControlEventTouchUpOutside]; [self addTarget:self action:@selector(buttonPressed:) forControlEvents:UIControlEventTouchUpInside]; - - // border layers for more sex! - CAGradientLayer *bevelLayer = [CAGradientLayer layer]; + + // border layers for more sex! + CAGradientLayer *bevelLayer = [CAGradientLayer layer]; bevelLayer.colors = [NSArray arrayWithObjects:(id)[[UIColor colorWithWhite:0.4 alpha:1.0] CGColor], [[UIColor whiteColor] CGColor], nil]; bevelLayer.frame = CGRectMake(0.0, 0.0, CGRectGetWidth(frame), CGRectGetHeight(frame)); bevelLayer.cornerRadius = 2.5; bevelLayer.needsDisplayOnBoundsChange = YES; - [self.layer addSublayer:bevelLayer]; - + [self.layer addSublayer:bevelLayer]; + CAGradientLayer *topBorderLayer = [CAGradientLayer layer]; topBorderLayer.colors = [NSArray arrayWithObjects:(id)[[UIColor darkGrayColor] CGColor], [[UIColor lightGrayColor] CGColor], nil]; topBorderLayer.frame = CGRectMake(0.5, 0.5, CGRectGetWidth(frame) - 1.0, CGRectGetHeight(frame) - 1.0); topBorderLayer.cornerRadius = 2.6; topBorderLayer.needsDisplayOnBoundsChange = YES; [self.layer addSublayer:topBorderLayer]; - - // main gradient layer - gradient_ = [[CAGradientLayer layer] retain]; - gradient_.locations = [NSArray arrayWithObjects:[NSNumber numberWithFloat:0.0], [NSNumber numberWithFloat:1.0], nil];//[NSNumber numberWithFloat:0.500], [NSNumber numberWithFloat:0.5001], + + // main gradient layer + gradient_ = [[CAGradientLayer layer] retain]; + gradient_.locations = [NSArray arrayWithObjects:[NSNumber numberWithFloat:0.0], [NSNumber numberWithFloat:1.0], nil];//[NSNumber numberWithFloat:0.500], [NSNumber numberWithFloat:0.5001], gradient_.frame = CGRectMake(0.75, 0.75, CGRectGetWidth(frame) - 1.5, CGRectGetHeight(frame) - 1.5); gradient_.cornerRadius = 2.5; gradient_.needsDisplayOnBoundsChange = YES; - [self.layer addSublayer:gradient_]; - [self bringSubviewToFront:self.titleLabel]; - } - return self; + [self.layer addSublayer:gradient_]; + [self bringSubviewToFront:self.titleLabel]; + } + return self; } - (id)initWithPadding:(CGPoint)padding { - if ((self = [self initWithFrame:CGRectMake(0, 0, 40, PS_MIN_HEIGHT)])) { - customPadding_ = padding; - } - return self; + if ((self = [self initWithFrame:CGRectMake(0, 0, 40, PS_MIN_HEIGHT)])) { + customPadding_ = padding; + } + return self; } - (void)dealloc { - [buttonData_ release]; - [gradient_ release]; - - [super dealloc]; + [buttonData_ release]; + [gradient_ release]; + + [super dealloc]; } /////////////////////////////////////////////////////////////////////////////////////////////////// @@ -231,25 +231,25 @@ #pragma mark UIView - (CGSize)sizeThatFits:(CGSize)size { - CGSize constr = (CGSize){.height = self.frame.size.height, .width = PS_MAX_WIDTH}; + CGSize constr = (CGSize){.height = self.frame.size.height, .width = PS_MAX_WIDTH}; CGSize newSize = [self.buttonData.label sizeWithFont:self.titleLabel.font constrainedToSize:constr lineBreakMode:UILineBreakModeMiddleTruncation]; CGFloat newWidth = newSize.width + (PS_PADDING * 2); - CGFloat newHeight = PS_MIN_HEIGHT > newSize.height ? PS_MIN_HEIGHT : newSize.height; - - CGSize sizeThatFits = CGSizeMake(newWidth, newHeight); - return sizeThatFits; + CGFloat newHeight = PS_MIN_HEIGHT > newSize.height ? PS_MIN_HEIGHT : newSize.height; + + CGSize sizeThatFits = CGSizeMake(newWidth, newHeight); + return sizeThatFits; } - (void)setFrame:(CGRect)aRect { - [super setFrame:aRect]; - - // copy frame changes to sublayers (but watch out for NaN's) - for (CALayer *aLayer in self.layer.sublayers) { - CGRect rect = aLayer.frame; - rect.size.width = self.frame.size.width; - rect.size.height = self.frame.size.height; - aLayer.frame = rect; - [aLayer layoutIfNeeded]; + [super setFrame:aRect]; + + // copy frame changes to sublayers (but watch out for NaN's) + for (CALayer *aLayer in self.layer.sublayers) { + CGRect rect = aLayer.frame; + rect.size.width = self.frame.size.width; + rect.size.height = self.frame.size.height; + aLayer.frame = rect; + [aLayer layoutIfNeeded]; } } @@ -258,16 +258,16 @@ #pragma mark Properties - (void)setButtonData:(PSStoreButtonData *)aButtonData { - [self setButtonData:aButtonData animated:NO]; + [self setButtonData:aButtonData animated:NO]; } - (void)setButtonData:(PSStoreButtonData *)aButtonData animated:(BOOL)animated { - if (buttonData_ != aButtonData) { - [buttonData_ release]; - buttonData_ = [aButtonData retain]; - } - - [self updateButtonAnimated:animated]; + if (buttonData_ != aButtonData) { + [buttonData_ release]; + buttonData_ = [aButtonData retain]; + } + + [self updateButtonAnimated:animated]; } /////////////////////////////////////////////////////////////////////////////////////////////////// @@ -275,21 +275,21 @@ #pragma mark Static + (NSArray *)appStoreGreenColor { - return [NSArray arrayWithObjects:(id) - [UIColor colorWithRed:0.482 green:0.674 blue:0.406 alpha:1.000].CGColor, - [UIColor colorWithRed:0.299 green:0.606 blue:0.163 alpha:1.000].CGColor, nil]; + return [NSArray arrayWithObjects:(id) + [UIColor colorWithRed:0.482 green:0.674 blue:0.406 alpha:1.000].CGColor, + [UIColor colorWithRed:0.299 green:0.606 blue:0.163 alpha:1.000].CGColor, nil]; } + (NSArray *)appStoreBlueColor { - return [NSArray arrayWithObjects:(id) - [UIColor colorWithRed:0.306 green:0.380 blue:0.547 alpha:1.000].CGColor, - [UIColor colorWithRed:0.129 green:0.220 blue:0.452 alpha:1.000].CGColor, nil]; + return [NSArray arrayWithObjects:(id) + [UIColor colorWithRed:0.306 green:0.380 blue:0.547 alpha:1.000].CGColor, + [UIColor colorWithRed:0.129 green:0.220 blue:0.452 alpha:1.000].CGColor, nil]; } + (NSArray *)appStoreGrayColor { - return [NSArray arrayWithObjects:(id) - PS_RGBCOLOR(187,189,191).CGColor, - PS_RGBCOLOR(210,210,210).CGColor, nil]; + return [NSArray arrayWithObjects:(id) + PS_RGBCOLOR(187,189,191).CGColor, + PS_RGBCOLOR(210,210,210).CGColor, nil]; } @end diff --git a/Classes/PSWebTableViewCell.h b/Classes/PSWebTableViewCell.h index b858e324e7..864f01e280 100644 --- a/Classes/PSWebTableViewCell.h +++ b/Classes/PSWebTableViewCell.h @@ -27,11 +27,11 @@ #import @interface PSWebTableViewCell : UITableViewCell { - UIWebView *webView_; - NSString *webViewContent_; - CGSize webViewSize_; - - UIColor *cellBackgroundColor_; + UIWebView *webView_; + NSString *webViewContent_; + CGSize webViewSize_; + + UIColor *cellBackgroundColor_; } @property (nonatomic, retain) UIWebView *webView; diff --git a/Classes/PSWebTableViewCell.m b/Classes/PSWebTableViewCell.m index 005d2738f2..a6dfc475a9 100644 --- a/Classes/PSWebTableViewCell.m +++ b/Classes/PSWebTableViewCell.m @@ -53,7 +53,7 @@ body { font: 13px 'Helvetica Neue', Helvetica; word-wrap:break-word; padding:8px - (void)addWebView { if(webViewContent_) { - CGRect webViewRect = CGRectMake(0, 0, self.frame.size.width, self.frame.size.height); + CGRect webViewRect = CGRectMake(0, 0, self.frame.size.width, self.frame.size.height); if(!webView_) { webView_ = [[[UIWebView alloc] initWithFrame:webViewRect] retain]; [self addSubview:webView_]; @@ -61,37 +61,37 @@ body { font: 13px 'Helvetica Neue', Helvetica; word-wrap:break-word; padding:8px webView_.backgroundColor = self.cellBackgroundColor; webView_.opaque = NO; webView_.delegate = self; - webView_.autoresizingMask = UIViewAutoresizingFlexibleWidth; - - for(UIView* subView in webView_.subviews){ - if([subView isKindOfClass:[UIScrollView class]]){ - // disable scrolling - UIScrollView *sv = (UIScrollView *)subView; - sv.scrollEnabled = NO; - sv.bounces = NO; - - // hide shadow - for (UIView* shadowView in [subView subviews]) { - if ([shadowView isKindOfClass:[UIImageView class]]) { - shadowView.hidden = YES; - } - } - } + webView_.autoresizingMask = UIViewAutoresizingFlexibleWidth; + + for(UIView* subView in webView_.subviews){ + if([subView isKindOfClass:[UIScrollView class]]){ + // disable scrolling + UIScrollView *sv = (UIScrollView *)subView; + sv.scrollEnabled = NO; + sv.bounces = NO; + + // hide shadow + for (UIView* shadowView in [subView subviews]) { + if ([shadowView isKindOfClass:[UIImageView class]]) { + shadowView.hidden = YES; } + } } + } + } else webView_.frame = webViewRect; - - NSString *deviceWidth = UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad ? [NSString stringWithFormat:@"%d", CGRectGetWidth(self.bounds)] : @"device-width"; - //BWHockeyLog(@"%@\n%@\%@", PSWebTableViewCellHtmlTemplate, deviceWidth, self.webViewContent); - NSString *contentHtml = [NSString stringWithFormat:PSWebTableViewCellHtmlTemplate, deviceWidth, self.webViewContent]; + + NSString *deviceWidth = UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad ? [NSString stringWithFormat:@"%d", CGRectGetWidth(self.bounds)] : @"device-width"; + //BWHockeyLog(@"%@\n%@\%@", PSWebTableViewCellHtmlTemplate, deviceWidth, self.webViewContent); + NSString *contentHtml = [NSString stringWithFormat:PSWebTableViewCellHtmlTemplate, deviceWidth, self.webViewContent]; [webView_ loadHTMLString:contentHtml baseURL:nil]; } } - (void)showWebView { webView_.hidden = NO; - self.textLabel.text = @""; + self.textLabel.text = @""; [self setNeedsDisplay]; } @@ -109,13 +109,13 @@ body { font: 13px 'Helvetica Neue', Helvetica; word-wrap:break-word; padding:8px - (void)setWebViewContent:(NSString *)aWebViewContent { - if (webViewContent_ != aWebViewContent) { - [webViewContent_ release]; - webViewContent_ = [aWebViewContent retain]; - - // add basic accessiblity (prevents "snarfed from ivar layout") logs - self.accessibilityLabel = aWebViewContent; - } + if (webViewContent_ != aWebViewContent) { + [webViewContent_ release]; + webViewContent_ = [aWebViewContent retain]; + + // add basic accessiblity (prevents "snarfed from ivar layout") logs + self.accessibilityLabel = aWebViewContent; + } } /////////////////////////////////////////////////////////////////////////////////////////////////// @@ -123,16 +123,16 @@ body { font: 13px 'Helvetica Neue', Helvetica; word-wrap:break-word; padding:8px #pragma mark NSObject - (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier { - if((self = [super initWithStyle:style reuseIdentifier:reuseIdentifier])) { - self.cellBackgroundColor = [UIColor clearColor]; - } - return self; + if((self = [super initWithStyle:style reuseIdentifier:reuseIdentifier])) { + self.cellBackgroundColor = [UIColor clearColor]; + } + return self; } - (void)dealloc { - [self removeWebView]; - [webViewContent_ release]; - [super dealloc]; + [self removeWebView]; + [webViewContent_ release]; + [super dealloc]; } /////////////////////////////////////////////////////////////////////////////////////////////////// @@ -140,12 +140,12 @@ body { font: 13px 'Helvetica Neue', Helvetica; word-wrap:break-word; padding:8px #pragma mark UIView - (void)setFrame:(CGRect)aFrame { - BOOL needChange = !CGRectEqualToRect(aFrame, self.frame); - [super setFrame:aFrame]; - - if (needChange) { - [self addWebView]; - } + BOOL needChange = !CGRectEqualToRect(aFrame, self.frame); + [super setFrame:aFrame]; + + if (needChange) { + [self addWebView]; + } } /////////////////////////////////////////////////////////////////////////////////////////////////// @@ -154,7 +154,7 @@ body { font: 13px 'Helvetica Neue', Helvetica; word-wrap:break-word; padding:8px - (void)prepareForReuse { [self removeWebView]; - self.webViewContent = nil; + self.webViewContent = nil; [super prepareForReuse]; } @@ -165,25 +165,25 @@ body { font: 13px 'Helvetica Neue', Helvetica; word-wrap:break-word; padding:8px - (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType { if(navigationType == UIWebViewNavigationTypeOther) return YES; - - return NO; + + return NO; } - (void)webViewDidFinishLoad:(UIWebView *)webView { if(webViewContent_) - [self showWebView]; - - CGRect frame = webView_.frame; - frame.size.height = 1; - webView_.frame = frame; - CGSize fittingSize = [webView_ sizeThatFits:CGSizeZero]; - frame.size = fittingSize; - webView_.frame = frame; - - // sizeThatFits is not reliable - use javascript for optimal height - NSString *output = [webView_ stringByEvaluatingJavaScriptFromString:@"document.body.scrollHeight;"]; - self.webViewSize = CGSizeMake(fittingSize.width, [output integerValue]); + [self showWebView]; + + CGRect frame = webView_.frame; + frame.size.height = 1; + webView_.frame = frame; + CGSize fittingSize = [webView_ sizeThatFits:CGSizeZero]; + frame.size = fittingSize; + webView_.frame = frame; + + // sizeThatFits is not reliable - use javascript for optimal height + NSString *output = [webView_ stringByEvaluatingJavaScriptFromString:@"document.body.scrollHeight;"]; + self.webViewSize = CGSizeMake(fittingSize.width, [output integerValue]); } @end diff --git a/Classes/UIImage+HockeyAdditions.m b/Classes/UIImage+HockeyAdditions.m index 79cd70042f..ee531fece4 100644 --- a/Classes/UIImage+HockeyAdditions.m +++ b/Classes/UIImage+HockeyAdditions.m @@ -38,107 +38,107 @@ CGImageRef CreateGradientImage(int pixelsWide, int pixelsHigh, float fromAlpha, // Returns true if the image has an alpha layer - (BOOL)hasAlpha { - CGImageAlphaInfo alpha = CGImageGetAlphaInfo(self.CGImage); - return (alpha == kCGImageAlphaFirst || - alpha == kCGImageAlphaLast || - alpha == kCGImageAlphaPremultipliedFirst || - alpha == kCGImageAlphaPremultipliedLast); + CGImageAlphaInfo alpha = CGImageGetAlphaInfo(self.CGImage); + return (alpha == kCGImageAlphaFirst || + alpha == kCGImageAlphaLast || + alpha == kCGImageAlphaPremultipliedFirst || + alpha == kCGImageAlphaPremultipliedLast); } // Returns a copy of the given image, adding an alpha channel if it doesn't already have one - (UIImage *)imageWithAlpha { - if ([self hasAlpha]) { - return self; - } - - CGImageRef imageRef = self.CGImage; - size_t width = CGImageGetWidth(imageRef) * self.scale; - size_t height = CGImageGetHeight(imageRef) * self.scale; - - // The bitsPerComponent and bitmapInfo values are hard-coded to prevent an "unsupported parameter combination" error - CGContextRef offscreenContext = CGBitmapContextCreate(NULL, - width, - height, - 8, - 0, - CGImageGetColorSpace(imageRef), - kCGBitmapByteOrderDefault | kCGImageAlphaPremultipliedFirst); - - // Draw the image into the context and retrieve the new image, which will now have an alpha layer - CGContextDrawImage(offscreenContext, CGRectMake(0, 0, width, height), imageRef); - CGImageRef imageRefWithAlpha = CGBitmapContextCreateImage(offscreenContext); - UIImage *imageWithAlpha = [UIImage imageWithCGImage:imageRefWithAlpha]; - - // Clean up - CGContextRelease(offscreenContext); - CGImageRelease(imageRefWithAlpha); - - return imageWithAlpha; + if ([self hasAlpha]) { + return self; + } + + CGImageRef imageRef = self.CGImage; + size_t width = CGImageGetWidth(imageRef) * self.scale; + size_t height = CGImageGetHeight(imageRef) * self.scale; + + // The bitsPerComponent and bitmapInfo values are hard-coded to prevent an "unsupported parameter combination" error + CGContextRef offscreenContext = CGBitmapContextCreate(NULL, + width, + height, + 8, + 0, + CGImageGetColorSpace(imageRef), + kCGBitmapByteOrderDefault | kCGImageAlphaPremultipliedFirst); + + // Draw the image into the context and retrieve the new image, which will now have an alpha layer + CGContextDrawImage(offscreenContext, CGRectMake(0, 0, width, height), imageRef); + CGImageRef imageRefWithAlpha = CGBitmapContextCreateImage(offscreenContext); + UIImage *imageWithAlpha = [UIImage imageWithCGImage:imageRefWithAlpha]; + + // Clean up + CGContextRelease(offscreenContext); + CGImageRelease(imageRefWithAlpha); + + return imageWithAlpha; } // Creates a copy of this image with rounded corners // If borderSize is non-zero, a transparent border of the given size will also be added // Original author: Björn Sållarp. Used with permission. See: http://blog.sallarp.com/iphone-uiimage-round-corners/ - (UIImage *)bw_roundedCornerImage:(NSInteger)cornerSize borderSize:(NSInteger)borderSize { - // If the image does not have an alpha layer, add one - + // If the image does not have an alpha layer, add one + UIImage *roundedImage = nil; #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 40000 BW_IF_IOS4_OR_GREATER( - UIGraphicsBeginImageContextWithOptions(self.size, NO, 0.0); // 0.0 for scale means "correct scale for device's main screen". - CGImageRef sourceImg = CGImageCreateWithImageInRect([self CGImage], CGRectMake(0, 0, self.size.width * self.scale, self.size.height * self.scale)); // cropping happens here. - - // Create a clipping path with rounded corners - CGContextRef context = UIGraphicsGetCurrentContext(); - CGContextBeginPath(context); - [self addRoundedRectToPath:CGRectMake(borderSize, borderSize, self.size.width - borderSize * 2, self.size.height - borderSize * 2) - context:context - ovalWidth:cornerSize - ovalHeight:cornerSize]; - CGContextClosePath(context); - CGContextClip(context); - - roundedImage = [UIImage imageWithCGImage:sourceImg scale:0.0 orientation:self.imageOrientation]; // create cropped UIImage. - [roundedImage drawInRect:CGRectMake(0, 0, self.size.width, self.size.height)]; // the actual scaling happens here, and orientation is taken care of automatically. - CGImageRelease(sourceImg); - roundedImage = UIGraphicsGetImageFromCurrentImageContext(); - UIGraphicsEndImageContext(); - ) + UIGraphicsBeginImageContextWithOptions(self.size, NO, 0.0); // 0.0 for scale means "correct scale for device's main screen". + CGImageRef sourceImg = CGImageCreateWithImageInRect([self CGImage], CGRectMake(0, 0, self.size.width * self.scale, self.size.height * self.scale)); // cropping happens here. + + // Create a clipping path with rounded corners + CGContextRef context = UIGraphicsGetCurrentContext(); + CGContextBeginPath(context); + [self addRoundedRectToPath:CGRectMake(borderSize, borderSize, self.size.width - borderSize * 2, self.size.height - borderSize * 2) + context:context + ovalWidth:cornerSize + ovalHeight:cornerSize]; + CGContextClosePath(context); + CGContextClip(context); + + roundedImage = [UIImage imageWithCGImage:sourceImg scale:0.0 orientation:self.imageOrientation]; // create cropped UIImage. + [roundedImage drawInRect:CGRectMake(0, 0, self.size.width, self.size.height)]; // the actual scaling happens here, and orientation is taken care of automatically. + CGImageRelease(sourceImg); + roundedImage = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); + ) #endif - if (!roundedImage) { - // Try older method. - UIImage *image = [self imageWithAlpha]; - - // Build a context that's the same dimensions as the new size - CGContextRef context = CGBitmapContextCreate(NULL, - image.size.width, - image.size.height, - CGImageGetBitsPerComponent(image.CGImage), - 0, - CGImageGetColorSpace(image.CGImage), - CGImageGetBitmapInfo(image.CGImage)); - - // Create a clipping path with rounded corners - CGContextBeginPath(context); - [self addRoundedRectToPath:CGRectMake(borderSize, borderSize, image.size.width - borderSize * 2, image.size.height - borderSize * 2) - context:context - ovalWidth:cornerSize - ovalHeight:cornerSize]; - CGContextClosePath(context); - CGContextClip(context); - - // Draw the image to the context; the clipping path will make anything outside the rounded rect transparent - CGContextDrawImage(context, CGRectMake(0, 0, image.size.width, image.size.height), image.CGImage); - - // Create a CGImage from the context - CGImageRef clippedImage = CGBitmapContextCreateImage(context); - CGContextRelease(context); - - // Create a UIImage from the CGImage - roundedImage = [UIImage imageWithCGImage:clippedImage]; - CGImageRelease(clippedImage); - } - return roundedImage; + if (!roundedImage) { + // Try older method. + UIImage *image = [self imageWithAlpha]; + + // Build a context that's the same dimensions as the new size + CGContextRef context = CGBitmapContextCreate(NULL, + image.size.width, + image.size.height, + CGImageGetBitsPerComponent(image.CGImage), + 0, + CGImageGetColorSpace(image.CGImage), + CGImageGetBitmapInfo(image.CGImage)); + + // Create a clipping path with rounded corners + CGContextBeginPath(context); + [self addRoundedRectToPath:CGRectMake(borderSize, borderSize, image.size.width - borderSize * 2, image.size.height - borderSize * 2) + context:context + ovalWidth:cornerSize + ovalHeight:cornerSize]; + CGContextClosePath(context); + CGContextClip(context); + + // Draw the image to the context; the clipping path will make anything outside the rounded rect transparent + CGContextDrawImage(context, CGRectMake(0, 0, image.size.width, image.size.height), image.CGImage); + + // Create a CGImage from the context + CGImageRef clippedImage = CGBitmapContextCreateImage(context); + CGContextRelease(context); + + // Create a UIImage from the CGImage + roundedImage = [UIImage imageWithCGImage:clippedImage]; + CGImageRelease(clippedImage); + } + return roundedImage; } #pragma mark - @@ -147,83 +147,83 @@ CGImageRef CreateGradientImage(int pixelsWide, int pixelsHigh, float fromAlpha, // Adds a rectangular path to the given context and rounds its corners by the given extents // Original author: Björn Sållarp. Used with permission. See: http://blog.sallarp.com/iphone-uiimage-round-corners/ - (void)addRoundedRectToPath:(CGRect)rect context:(CGContextRef)context ovalWidth:(CGFloat)ovalWidth ovalHeight:(CGFloat)ovalHeight { - if (ovalWidth == 0 || ovalHeight == 0) { - CGContextAddRect(context, rect); - return; - } - CGContextSaveGState(context); - CGContextTranslateCTM(context, CGRectGetMinX(rect), CGRectGetMinY(rect)); - CGContextScaleCTM(context, ovalWidth, ovalHeight); - CGFloat fw = CGRectGetWidth(rect) / ovalWidth; - CGFloat fh = CGRectGetHeight(rect) / ovalHeight; - CGContextMoveToPoint(context, fw, fh/2); - CGContextAddArcToPoint(context, fw, fh, fw/2, fh, 1); - CGContextAddArcToPoint(context, 0, fh, 0, fh/2, 1); - CGContextAddArcToPoint(context, 0, 0, fw/2, 0, 1); - CGContextAddArcToPoint(context, fw, 0, fw, fh/2, 1); - CGContextClosePath(context); - CGContextRestoreGState(context); + if (ovalWidth == 0 || ovalHeight == 0) { + CGContextAddRect(context, rect); + return; + } + CGContextSaveGState(context); + CGContextTranslateCTM(context, CGRectGetMinX(rect), CGRectGetMinY(rect)); + CGContextScaleCTM(context, ovalWidth, ovalHeight); + CGFloat fw = CGRectGetWidth(rect) / ovalWidth; + CGFloat fh = CGRectGetHeight(rect) / ovalHeight; + CGContextMoveToPoint(context, fw, fh/2); + CGContextAddArcToPoint(context, fw, fh, fw/2, fh, 1); + CGContextAddArcToPoint(context, 0, fh, 0, fh/2, 1); + CGContextAddArcToPoint(context, 0, 0, fw/2, 0, 1); + CGContextAddArcToPoint(context, fw, 0, fw, fh/2, 1); + CGContextClosePath(context); + CGContextRestoreGState(context); } - (UIImage *)bw_imageToFitSize:(CGSize)fitSize honorScaleFactor:(BOOL)honorScaleFactor { float imageScaleFactor = 1.0; #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 40000 - if (honorScaleFactor) { - if ([self respondsToSelector:@selector(scale)]) { - imageScaleFactor = [self scale]; - } + if (honorScaleFactor) { + if ([self respondsToSelector:@selector(scale)]) { + imageScaleFactor = [self scale]; } + } #endif - - float sourceWidth = [self size].width * imageScaleFactor; - float sourceHeight = [self size].height * imageScaleFactor; - float targetWidth = fitSize.width; - float targetHeight = fitSize.height; - - // Calculate aspect ratios - float sourceRatio = sourceWidth / sourceHeight; - float targetRatio = targetWidth / targetHeight; - - // Determine what side of the source image to use for proportional scaling - BOOL scaleWidth = (sourceRatio <= targetRatio); - // Deal with the case of just scaling proportionally to fit, without cropping - scaleWidth = !scaleWidth; - - // Proportionally scale source image - float scalingFactor, scaledWidth, scaledHeight; - if (scaleWidth) { - scalingFactor = 1.0 / sourceRatio; - scaledWidth = targetWidth; - scaledHeight = round(targetWidth * scalingFactor); - } else { - scalingFactor = sourceRatio; - scaledWidth = round(targetHeight * scalingFactor); - scaledHeight = targetHeight; - } - - // Calculate compositing rectangles - CGRect sourceRect, destRect; - sourceRect = CGRectMake(0, 0, sourceWidth, sourceHeight); - destRect = CGRectMake(0, 0, scaledWidth, scaledHeight); - - // Create appropriately modified image. + + float sourceWidth = [self size].width * imageScaleFactor; + float sourceHeight = [self size].height * imageScaleFactor; + float targetWidth = fitSize.width; + float targetHeight = fitSize.height; + + // Calculate aspect ratios + float sourceRatio = sourceWidth / sourceHeight; + float targetRatio = targetWidth / targetHeight; + + // Determine what side of the source image to use for proportional scaling + BOOL scaleWidth = (sourceRatio <= targetRatio); + // Deal with the case of just scaling proportionally to fit, without cropping + scaleWidth = !scaleWidth; + + // Proportionally scale source image + float scalingFactor, scaledWidth, scaledHeight; + if (scaleWidth) { + scalingFactor = 1.0 / sourceRatio; + scaledWidth = targetWidth; + scaledHeight = round(targetWidth * scalingFactor); + } else { + scalingFactor = sourceRatio; + scaledWidth = round(targetHeight * scalingFactor); + scaledHeight = targetHeight; + } + + // Calculate compositing rectangles + CGRect sourceRect, destRect; + sourceRect = CGRectMake(0, 0, sourceWidth, sourceHeight); + destRect = CGRectMake(0, 0, scaledWidth, scaledHeight); + + // Create appropriately modified image. UIImage *image = nil; BW_IF_IOS4_OR_GREATER - ( - UIGraphicsBeginImageContextWithOptions(destRect.size, NO, honorScaleFactor ? 0.0 : 1.0); // 0.0 for scale means "correct scale for device's main screen". - CGImageRef sourceImg = CGImageCreateWithImageInRect([self CGImage], sourceRect); // cropping happens here. - image = [UIImage imageWithCGImage:sourceImg scale:0.0 orientation:self.imageOrientation]; // create cropped UIImage. - [image drawInRect:destRect]; // the actual scaling happens here, and orientation is taken care of automatically. - CGImageRelease(sourceImg); - image = UIGraphicsGetImageFromCurrentImageContext(); - UIGraphicsEndImageContext(); - ) + ( + UIGraphicsBeginImageContextWithOptions(destRect.size, NO, honorScaleFactor ? 0.0 : 1.0); // 0.0 for scale means "correct scale for device's main screen". + CGImageRef sourceImg = CGImageCreateWithImageInRect([self CGImage], sourceRect); // cropping happens here. + image = [UIImage imageWithCGImage:sourceImg scale:0.0 orientation:self.imageOrientation]; // create cropped UIImage. + [image drawInRect:destRect]; // the actual scaling happens here, and orientation is taken care of automatically. + CGImageRelease(sourceImg); + image = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); + ) if (!image) { // Try older method. CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); CGContextRef context = CGBitmapContextCreate(NULL, scaledWidth, scaledHeight, 8, (fitSize.width * 4), - colorSpace, kCGImageAlphaPremultipliedLast); + colorSpace, kCGImageAlphaPremultipliedLast); CGImageRef sourceImg = CGImageCreateWithImageInRect([self CGImage], sourceRect); CGContextDrawImage(context, destRect, sourceImg); CGImageRelease(sourceImg); @@ -233,105 +233,105 @@ CGImageRef CreateGradientImage(int pixelsWide, int pixelsHigh, float fromAlpha, image = [UIImage imageWithCGImage:finalImage]; CGImageRelease(finalImage); } - - return image; + + return image; } CGImageRef CreateGradientImage(int pixelsWide, int pixelsHigh, float fromAlpha, float toAlpha) { CGImageRef theCGImage = NULL; - + // gradient is always black-white and the mask must be in the gray colorspace - CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceGray(); - + CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceGray(); + // create the bitmap context CGContextRef gradientBitmapContext = CGBitmapContextCreate(NULL, pixelsWide, pixelsHigh, - 8, 0, colorSpace, kCGImageAlphaNone); - + 8, 0, colorSpace, kCGImageAlphaNone); + // define the start and end grayscale values (with the alpha, even though // our bitmap context doesn't support alpha the gradient requires it) CGFloat colors[] = {toAlpha, 1.0, fromAlpha, 1.0}; - + // create the CGGradient and then release the gray color space CGGradientRef grayScaleGradient = CGGradientCreateWithColorComponents(colorSpace, colors, NULL, 2); CGColorSpaceRelease(colorSpace); - + // create the start and end points for the gradient vector (straight down) CGPoint gradientEndPoint = CGPointZero; CGPoint gradientStartPoint = CGPointMake(0, pixelsHigh); - + // draw the gradient into the gray bitmap context CGContextDrawLinearGradient(gradientBitmapContext, grayScaleGradient, gradientStartPoint, - gradientEndPoint, kCGGradientDrawsAfterEndLocation); + gradientEndPoint, kCGGradientDrawsAfterEndLocation); CGGradientRelease(grayScaleGradient); - + // convert the context into a CGImageRef and release the context theCGImage = CGBitmapContextCreateImage(gradientBitmapContext); CGContextRelease(gradientBitmapContext); - + // return the imageref containing the gradient - return theCGImage; + return theCGImage; } CGContextRef MyOpenBitmapContext(int pixelsWide, int pixelsHigh) { - CGSize size = CGSizeMake(pixelsWide, pixelsHigh); - if (UIGraphicsBeginImageContextWithOptions != NULL) { - UIGraphicsBeginImageContextWithOptions(size, NO, 0.0); - } - else { - UIGraphicsBeginImageContext(size); - } - - return UIGraphicsGetCurrentContext(); + CGSize size = CGSizeMake(pixelsWide, pixelsHigh); + if (UIGraphicsBeginImageContextWithOptions != NULL) { + UIGraphicsBeginImageContextWithOptions(size, NO, 0.0); + } + else { + UIGraphicsBeginImageContext(size); + } + + return UIGraphicsGetCurrentContext(); } - (UIImage *)bw_reflectedImageWithHeight:(NSUInteger)height fromAlpha:(float)fromAlpha toAlpha:(float)toAlpha { - if(height == 0) + if(height == 0) return nil; - + // create a bitmap graphics context the size of the image CGContextRef mainViewContentContext = MyOpenBitmapContext(self.size.width, height); - + // create a 2 bit CGImage containing a gradient that will be used for masking the // main view content to create the 'fade' of the reflection. The CGImageCreateWithMask // function will stretch the bitmap image as required, so we can create a 1 pixel wide gradient CGImageRef gradientMaskImage = CreateGradientImage(1, height, fromAlpha, toAlpha); - + // create an image by masking the bitmap of the mainView content with the gradient view // then release the pre-masked content bitmap and the gradient bitmap CGContextClipToMask(mainViewContentContext, CGRectMake(0.0, 0.0, self.size.width, height), gradientMaskImage); CGImageRelease(gradientMaskImage); - + // draw the image into the bitmap context CGContextDrawImage(mainViewContentContext, CGRectMake(0, 0, self.size.width, self.size.height), self.CGImage); - + // convert the finished reflection image to a UIImage UIImage *theImage = UIGraphicsGetImageFromCurrentImageContext(); // returns autoreleased - UIGraphicsEndImageContext(); - + UIGraphicsEndImageContext(); + return theImage; } - (id)bw_initWithContentsOfResolutionIndependentFile:(NSString *)path { - if ([UIScreen instancesRespondToSelector:@selector(scale)] && (int)[[UIScreen mainScreen] scale] == 2.0) { - NSString *path2x = [[path stringByDeletingLastPathComponent] - stringByAppendingPathComponent:[NSString stringWithFormat:@"%@@2x.%@", - [[path lastPathComponent] stringByDeletingPathExtension], - [path pathExtension]]]; - - if ([[NSFileManager defaultManager] fileExistsAtPath:path2x]) { - return [self initWithContentsOfFile:path2x]; - } - } + if ([UIScreen instancesRespondToSelector:@selector(scale)] && (int)[[UIScreen mainScreen] scale] == 2.0) { + NSString *path2x = [[path stringByDeletingLastPathComponent] + stringByAppendingPathComponent:[NSString stringWithFormat:@"%@@2x.%@", + [[path lastPathComponent] stringByDeletingPathExtension], + [path pathExtension]]]; - return [self initWithContentsOfFile:path]; + if ([[NSFileManager defaultManager] fileExistsAtPath:path2x]) { + return [self initWithContentsOfFile:path2x]; + } + } + + return [self initWithContentsOfFile:path]; } + (UIImage*)bw_imageWithContentsOfResolutionIndependentFile:(NSString *)path { #ifndef __clang_analyzer__ - // clang alayzer in 4.2b3 thinks here's a leak, which is not the case. - return [[[UIImage alloc] bw_initWithContentsOfResolutionIndependentFile:path] autorelease]; + // clang alayzer in 4.2b3 thinks here's a leak, which is not the case. + return [[[UIImage alloc] bw_initWithContentsOfResolutionIndependentFile:path] autorelease]; #endif }