Changed indentation to 2 spaces for all files.

This commit is contained in:
Thomas Dohmke 2011-12-05 16:07:59 +01:00
parent 37bcbb7793
commit d444c177df
17 changed files with 2439 additions and 2439 deletions

View File

@ -41,20 +41,20 @@
#pragma mark static #pragma mark static
+ (BWApp *)appFromDict:(NSDictionary *)dict { + (BWApp *)appFromDict:(NSDictionary *)dict {
BWApp *app = [[[[self class] alloc] init] autorelease]; BWApp *app = [[[[self class] alloc] init] autorelease];
// NSParameterAssert([dict isKindOfClass:[NSDictionary class]]); // NSParameterAssert([dict isKindOfClass:[NSDictionary class]]);
if ([dict isKindOfClass:[NSDictionary class]]) { if ([dict isKindOfClass:[NSDictionary class]]) {
app.name = [dict objectForKey:@"title"]; app.name = [dict objectForKey:@"title"];
app.version = [dict objectForKey:@"version"]; app.version = [dict objectForKey:@"version"];
app.shortVersion = [dict objectForKey:@"shortversion"]; app.shortVersion = [dict objectForKey:@"shortversion"];
[app setDateWithTimestamp:[[dict objectForKey:@"timestamp"] doubleValue]]; [app setDateWithTimestamp:[[dict objectForKey:@"timestamp"] doubleValue]];
app.size = [dict objectForKey:@"appsize"]; app.size = [dict objectForKey:@"appsize"];
app.notes = [dict objectForKey:@"notes"]; app.notes = [dict objectForKey:@"notes"];
app.mandatory = [dict objectForKey:@"mandatory"]; app.mandatory = [dict objectForKey:@"mandatory"];
} }
return app; return app;
} }
/////////////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////////////
@ -62,43 +62,43 @@
#pragma mark NSObject #pragma mark NSObject
- (void)dealloc { - (void)dealloc {
[name_ release]; [name_ release];
[version_ release]; [version_ release];
[shortVersion_ release]; [shortVersion_ release];
[notes_ release]; [notes_ release];
[date_ release]; [date_ release];
[size_ release]; [size_ release];
[mandatory_ release]; [mandatory_ release];
[super dealloc]; [super dealloc];
} }
- (BOOL)isEqual:(id)other { - (BOOL)isEqual:(id)other {
if (other == self) if (other == self)
return YES; return YES;
if (!other || ![other isKindOfClass:[self class]]) if (!other || ![other isKindOfClass:[self class]])
return NO; return NO;
return [self isEqualToBWApp:other]; return [self isEqualToBWApp:other];
} }
- (BOOL)isEqualToBWApp:(BWApp *)anApp { - (BOOL)isEqualToBWApp:(BWApp *)anApp {
if (self == 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;
return YES; 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 #pragma mark NSCoder
- (void)encodeWithCoder:(NSCoder *)encoder { - (void)encodeWithCoder:(NSCoder *)encoder {
[encoder encodeObject:self.name forKey:@"name"]; [encoder encodeObject:self.name forKey:@"name"];
[encoder encodeObject:self.version forKey:@"version"]; [encoder encodeObject:self.version forKey:@"version"];
[encoder encodeObject:self.shortVersion forKey:@"shortVersion"]; [encoder encodeObject:self.shortVersion forKey:@"shortVersion"];
[encoder encodeObject:self.notes forKey:@"notes"]; [encoder encodeObject:self.notes forKey:@"notes"];
[encoder encodeObject:self.date forKey:@"date"]; [encoder encodeObject:self.date forKey:@"date"];
[encoder encodeObject:self.size forKey:@"size"]; [encoder encodeObject:self.size forKey:@"size"];
[encoder encodeObject:self.mandatory forKey:@"mandatory"]; [encoder encodeObject:self.mandatory forKey:@"mandatory"];
} }
- (id)initWithCoder:(NSCoder *)decoder { - (id)initWithCoder:(NSCoder *)decoder {
if ((self = [super init])) { if ((self = [super init])) {
self.name = [decoder decodeObjectForKey:@"name"]; self.name = [decoder decodeObjectForKey:@"name"];
self.version = [decoder decodeObjectForKey:@"version"]; self.version = [decoder decodeObjectForKey:@"version"];
self.shortVersion = [decoder decodeObjectForKey:@"shortVersion"]; self.shortVersion = [decoder decodeObjectForKey:@"shortVersion"];
self.notes = [decoder decodeObjectForKey:@"notes"]; self.notes = [decoder decodeObjectForKey:@"notes"];
self.date = [decoder decodeObjectForKey:@"date"]; self.date = [decoder decodeObjectForKey:@"date"];
self.size = [decoder decodeObjectForKey:@"size"]; self.size = [decoder decodeObjectForKey:@"size"];
self.mandatory = [decoder decodeObjectForKey:@"mandatory"]; self.mandatory = [decoder decodeObjectForKey:@"mandatory"];
} }
return self; return self;
} }
/////////////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////////////
@ -133,54 +133,54 @@
#pragma mark Properties #pragma mark Properties
- (NSString *)nameAndVersionString { - (NSString *)nameAndVersionString {
NSString *appNameAndVersion = [NSString stringWithFormat:@"%@ %@", self.name, [self versionString]]; NSString *appNameAndVersion = [NSString stringWithFormat:@"%@ %@", self.name, [self versionString]];
return appNameAndVersion; return appNameAndVersion;
} }
- (NSString *)versionString { - (NSString *)versionString {
NSString *shortString = ([self.shortVersion respondsToSelector:@selector(length)] && [self.shortVersion length]) ? [NSString stringWithFormat:@"%@", self.shortVersion] : @""; NSString *shortString = ([self.shortVersion respondsToSelector:@selector(length)] && [self.shortVersion length]) ? [NSString stringWithFormat:@"%@", self.shortVersion] : @"";
NSString *versionString = [shortString length] ? [NSString stringWithFormat:@" (%@)", self.version] : self.version; NSString *versionString = [shortString length] ? [NSString stringWithFormat:@" (%@)", self.version] : self.version;
return [NSString stringWithFormat:@"%@ %@%@", BWHockeyLocalize(@"HockeyVersion"), shortString, versionString]; return [NSString stringWithFormat:@"%@ %@%@", BWHockeyLocalize(@"HockeyVersion"), shortString, versionString];
} }
- (NSString *)dateString { - (NSString *)dateString {
NSDateFormatter *formatter = [[[NSDateFormatter alloc] init] autorelease]; NSDateFormatter *formatter = [[[NSDateFormatter alloc] init] autorelease];
[formatter setDateStyle:NSDateFormatterMediumStyle]; [formatter setDateStyle:NSDateFormatterMediumStyle];
return [formatter stringFromDate:self.date]; return [formatter stringFromDate:self.date];
} }
- (NSString *)sizeInMB { - (NSString *)sizeInMB {
if ([size_ isKindOfClass: [NSNumber class]] && [size_ doubleValue] > 0) { if ([size_ isKindOfClass: [NSNumber class]] && [size_ doubleValue] > 0) {
double appSizeInMB = [size_ doubleValue]/(1024*1024); double appSizeInMB = [size_ doubleValue]/(1024*1024);
NSString *appSizeString = [NSString stringWithFormat:@"%.1f MB", appSizeInMB]; NSString *appSizeString = [NSString stringWithFormat:@"%.1f MB", appSizeInMB];
return appSizeString; return appSizeString;
} }
return @"0 MB"; return @"0 MB";
} }
- (void)setDateWithTimestamp:(NSTimeInterval)timestamp { - (void)setDateWithTimestamp:(NSTimeInterval)timestamp {
if (timestamp) { if (timestamp) {
NSDate *appDate = [NSDate dateWithTimeIntervalSince1970:timestamp]; NSDate *appDate = [NSDate dateWithTimeIntervalSince1970:timestamp];
self.date = appDate; self.date = appDate;
} else { } else {
self.date = nil; self.date = nil;
} }
} }
- (NSString *)notesOrEmptyString { - (NSString *)notesOrEmptyString {
if (self.notes) { if (self.notes) {
return self.notes; return self.notes;
}else { }else {
return [NSString string]; return [NSString string];
} }
} }
// a valid app needs at least following properties: name, version, date // a valid app needs at least following properties: name, version, date
- (BOOL)isValid { - (BOOL)isValid {
BOOL valid = [self.name length] && [self.version length] && self.date; BOOL valid = [self.name length] && [self.version length] && self.date;
return valid; return valid;
} }
@end @end

View File

@ -26,13 +26,13 @@
#include <CommonCrypto/CommonDigest.h> #include <CommonCrypto/CommonDigest.h>
NSBundle *hockeyBundle(void) { NSBundle *hockeyBundle(void) {
static NSBundle* bundle = nil; static NSBundle* bundle = nil;
if (!bundle) { if (!bundle) {
NSString* path = [[[NSBundle mainBundle] resourcePath] NSString* path = [[[NSBundle mainBundle] resourcePath]
stringByAppendingPathComponent:kHockeyBundleName]; stringByAppendingPathComponent:kHockeyBundleName];
bundle = [[NSBundle bundleWithPath:path] retain]; bundle = [[NSBundle bundleWithPath:path] retain];
} }
return bundle; return bundle;
} }
NSString *BWmd5(NSString *str) { NSString *BWmd5(NSString *str) {
@ -40,22 +40,22 @@ NSString *BWmd5(NSString *str) {
unsigned char result[CC_MD5_DIGEST_LENGTH]; unsigned char result[CC_MD5_DIGEST_LENGTH];
CC_MD5( cStr, strlen(cStr), result ); CC_MD5( cStr, strlen(cStr), result );
return [NSString return [NSString
stringWithFormat: @"%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X", stringWithFormat: @"%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X",
result[0], result[1], result[0], result[1],
result[2], result[3], result[2], result[3],
result[4], result[5], result[4], result[5],
result[6], result[7], result[6], result[7],
result[8], result[9], result[8], result[9],
result[10], result[11], result[10], result[11],
result[12], result[13], result[12], result[13],
result[14], result[15] result[14], result[15]
]; ];
} }
NSString *BWHockeyLocalize(NSString *stringToken) { NSString *BWHockeyLocalize(NSString *stringToken) {
if (hockeyBundle()) { if (hockeyBundle()) {
return NSLocalizedStringFromTableInBundle(stringToken, @"Hockey", hockeyBundle(), @""); return NSLocalizedStringFromTableInBundle(stringToken, @"Hockey", hockeyBundle(), @"");
} else { } else {
return stringToken; return stringToken;
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -7,11 +7,11 @@
// //
#import <UIKit/UIKit.h> #import <UIKit/UIKit.h>
@class BWHockeyManager; @class BWHockeyManager;
@interface BWHockeySettingsViewController : UIViewController <UITableViewDelegate, UITableViewDataSource> { @interface BWHockeySettingsViewController : UIViewController <UITableViewDelegate, UITableViewDataSource> {
BWHockeyManager *hockeyManager_; BWHockeyManager *hockeyManager_;
} }
@property (nonatomic, retain) BWHockeyManager *hockeyManager; @property (nonatomic, retain) BWHockeyManager *hockeyManager;

View File

@ -24,41 +24,41 @@
#pragma mark Initialization #pragma mark Initialization
- (id)init:(BWHockeyManager *)newHockeyManager { - (id)init:(BWHockeyManager *)newHockeyManager {
if ((self = [super init])) { if ((self = [super init])) {
self.hockeyManager = newHockeyManager; self.hockeyManager = newHockeyManager;
self.title = BWHockeyLocalize(@"HockeySettingsTitle"); self.title = BWHockeyLocalize(@"HockeySettingsTitle");
CGRect frame = self.view.frame; CGRect frame = self.view.frame;
frame.origin = CGPointZero; 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]; 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; tableView_.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleWidth;
BW_IF_3_2_OR_GREATER( BW_IF_3_2_OR_GREATER(
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) { if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) {
self.view.backgroundColor = BW_RGBCOLOR(200, 202, 204); self.view.backgroundColor = BW_RGBCOLOR(200, 202, 204);
tableView_.backgroundColor = BW_RGBCOLOR(200, 202, 204); tableView_.backgroundColor = BW_RGBCOLOR(200, 202, 204);
} else { } else {
tableView_.frame = frame; tableView_.frame = frame;
tableView_.autoresizingMask = tableView_.autoresizingMask | UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin; tableView_.autoresizingMask = tableView_.autoresizingMask | UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin;
} }
) )
BW_IF_PRE_3_2( BW_IF_PRE_3_2(
self.navigationItem.rightBarButtonItem = [[[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone self.navigationItem.rightBarButtonItem = [[[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone
target:self target:self
action:@selector(dismissSettings)] autorelease]; action:@selector(dismissSettings)] autorelease];
tableView_.frame = frame; tableView_.frame = frame;
tableView_.autoresizingMask = tableView_.autoresizingMask | UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin; tableView_.autoresizingMask = tableView_.autoresizingMask | UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin;
) )
tableView_.delegate = self; tableView_.delegate = self;
tableView_.dataSource = self; tableView_.dataSource = self;
tableView_.clipsToBounds = NO; tableView_.clipsToBounds = NO;
[self.view addSubview:tableView_]; [self.view addSubview:tableView_];
} }
return self; return self;
} }
- (id)init { - (id)init {
@ -69,165 +69,165 @@
#pragma mark Table view data source #pragma mark Table view data source
- (int)numberOfSections { - (int)numberOfSections {
int numberOfSections = 1; int numberOfSections = 1;
if ([self.hockeyManager isAllowUserToDisableSendData]) { if ([self.hockeyManager isAllowUserToDisableSendData]) {
if ([self.hockeyManager shouldSendUserData]) numberOfSections++; if ([self.hockeyManager shouldSendUserData]) numberOfSections++;
if ([self.hockeyManager shouldSendUsageTime]) numberOfSections++; if ([self.hockeyManager shouldSendUsageTime]) numberOfSections++;
} }
return numberOfSections; return numberOfSections;
} }
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section { - (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
if (section == [self numberOfSections] - 1) { if (section == [self numberOfSections] - 1) {
return BWHockeyLocalize(@"HockeySectionCheckTitle"); return BWHockeyLocalize(@"HockeySectionCheckTitle");
} else { } else {
return nil; return nil;
} }
} }
- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section { - (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section {
if (section < [self numberOfSections] - 1) { if (section < [self numberOfSections] - 1) {
return 66; return 66;
} else return 0; } else return 0;
} }
- (UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section { - (UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section {
if ([self numberOfSections] > 1 && section < [self numberOfSections] - 1) { if ([self numberOfSections] > 1 && section < [self numberOfSections] - 1) {
UILabel *footer = [[[UILabel alloc] initWithFrame:CGRectMake(0, 0, 285, 66)] autorelease]; UILabel *footer = [[[UILabel alloc] initWithFrame:CGRectMake(0, 0, 285, 66)] autorelease];
footer.backgroundColor = [UIColor clearColor]; footer.backgroundColor = [UIColor clearColor];
footer.numberOfLines = 3; footer.numberOfLines = 3;
footer.textAlignment = UITextAlignmentCenter; footer.textAlignment = UITextAlignmentCenter;
footer.adjustsFontSizeToFitWidth = YES; footer.adjustsFontSizeToFitWidth = YES;
footer.textColor = [UIColor grayColor]; footer.textColor = [UIColor grayColor];
footer.font = [UIFont systemFontOfSize:13]; footer.font = [UIFont systemFontOfSize:13];
if (section == 0 && [self.hockeyManager isAllowUserToDisableSendData] && [self.hockeyManager shouldSendUserData]) { if (section == 0 && [self.hockeyManager isAllowUserToDisableSendData] && [self.hockeyManager shouldSendUserData]) {
footer.text = BWHockeyLocalize(@"HockeySettingsUserDataDescription"); footer.text = BWHockeyLocalize(@"HockeySettingsUserDataDescription");
} else if ([self.hockeyManager isAllowUserToDisableSendData] && section < [self numberOfSections]) { } else if ([self.hockeyManager isAllowUserToDisableSendData] && section < [self numberOfSections]) {
footer.text = BWHockeyLocalize(@"HockeySettingsUsageDataDescription"); 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;
} }
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 { - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
// Return the number of sections. // Return the number of sections.
return [self numberOfSections]; return [self numberOfSections];
} }
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
// Return the number of rows in the section. // Return the number of rows in the section.
if (section == [self numberOfSections] - 1) if (section == [self numberOfSections] - 1)
return 3; return 3;
else else
return 1; return 1;
} }
- (void)sendUserData:(UISwitch *)switcher { - (void)sendUserData:(UISwitch *)switcher {
[self.hockeyManager setUserAllowsSendUserData:switcher.on]; [self.hockeyManager setUserAllowsSendUserData:switcher.on];
} }
- (void)sendUsageData:(UISwitch *)switcher { - (void)sendUsageData:(UISwitch *)switcher {
[self.hockeyManager setUserAllowsSendUsageTime:switcher.on]; [self.hockeyManager setUserAllowsSendUsageTime:switcher.on];
} }
// Customize the appearance of table view cells. // Customize the appearance of table view cells.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { - (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"; // update check selection
static NSString *SwitchCellIdentifier = @"SwitchCell"; HockeyUpdateSetting hockeyAutoUpdateSetting = [[BWHockeyManager sharedHockeyManager] updateSetting];
if (indexPath.row == 0) {
NSString *requiredIdentifier = nil; // on startup
UITableViewCellStyle cellStyle = UITableViewCellStyleSubtitle; cell.textLabel.text = BWHockeyLocalize(@"HockeySectionCheckStartup");
if (hockeyAutoUpdateSetting == HockeyUpdateCheckStartup) {
if ((NSInteger)indexPath.section == [self numberOfSections] - 1) { cell.accessoryType = UITableViewCellAccessoryCheckmark;
cellStyle = UITableViewCellStyleDefault; }
requiredIdentifier = CheckmarkCellIdentifier; } else if (indexPath.row == 1) {
// daily
cell.textLabel.text = BWHockeyLocalize(@"HockeySectionCheckDaily");
if (hockeyAutoUpdateSetting == HockeyUpdateCheckDaily) {
cell.accessoryType = UITableViewCellAccessoryCheckmark;
}
} else { } else {
cellStyle = UITableViewCellStyleValue1; // manually
requiredIdentifier = SwitchCellIdentifier; 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]; cell.accessoryView = toggleSwitch;
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:cellStyle reuseIdentifier:requiredIdentifier] autorelease];
}
cell.accessoryType = UITableViewCellAccessoryNone; }
cell.selectionStyle = UITableViewCellSelectionStyleNone;
return cell;
// 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;
} }
@ -235,21 +235,21 @@
#pragma mark Table view delegate #pragma mark Table view delegate
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
[tableView deselectRowAtIndexPath:indexPath animated:YES]; [tableView deselectRowAtIndexPath:indexPath animated:YES];
// update check interval selection // update check interval selection
if (indexPath.row == 0) { if (indexPath.row == 0) {
// on startup // on startup
[BWHockeyManager sharedHockeyManager].updateSetting = HockeyUpdateCheckStartup; [BWHockeyManager sharedHockeyManager].updateSetting = HockeyUpdateCheckStartup;
} else if (indexPath.row == 1) { } else if (indexPath.row == 1) {
// daily // daily
[BWHockeyManager sharedHockeyManager].updateSetting = HockeyUpdateCheckDaily; [BWHockeyManager sharedHockeyManager].updateSetting = HockeyUpdateCheckDaily;
} else { } else {
// manually // manually
[BWHockeyManager sharedHockeyManager].updateSetting = HockeyUpdateCheckManually; [BWHockeyManager sharedHockeyManager].updateSetting = HockeyUpdateCheckManually;
} }
[tableView reloadData]; [tableView reloadData];
} }
@ -257,20 +257,20 @@
#pragma mark Memory management #pragma mark Memory management
- (void)didReceiveMemoryWarning { - (void)didReceiveMemoryWarning {
// Releases the view if it doesn't have a superview. // Releases the view if it doesn't have a superview.
[super didReceiveMemoryWarning]; [super didReceiveMemoryWarning];
// Relinquish ownership any cached data, images, etc. that aren't in use. // Relinquish ownership any cached data, images, etc. that aren't in use.
} }
- (void)viewDidUnload { - (void)viewDidUnload {
// Relinquish ownership of anything that can be recreated in viewDidLoad or on demand. // Relinquish ownership of anything that can be recreated in viewDidLoad or on demand.
// For example: self.myOutlet = nil; // For example: self.myOutlet = nil;
} }
- (void)dealloc { - (void)dealloc {
[super dealloc]; [super dealloc];
} }
@ -279,15 +279,15 @@
#pragma mark Rotation #pragma mark Rotation
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation { - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
BOOL shouldAutorotate; BOOL shouldAutorotate;
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) { if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) {
shouldAutorotate = (interfaceOrientation == UIInterfaceOrientationPortrait); shouldAutorotate = (interfaceOrientation == UIInterfaceOrientationPortrait);
} else { } else {
shouldAutorotate = YES; shouldAutorotate = YES;
} }
return shouldAutorotate; return shouldAutorotate;
} }
@end @end

View File

@ -39,22 +39,22 @@ typedef enum {
@class BWHockeyManager; @class BWHockeyManager;
@interface BWHockeyViewController : UITableViewController <PSStoreButtonDelegate> { @interface BWHockeyViewController : UITableViewController <PSStoreButtonDelegate> {
BWHockeyManager *hockeyManager_; BWHockeyManager *hockeyManager_;
NSDictionary *cellLayout; NSDictionary *cellLayout;
BOOL modal_; BOOL modal_;
BOOL kvoRegistered_; BOOL kvoRegistered_;
BOOL showAllVersions_; BOOL showAllVersions_;
UIStatusBarStyle statusBarStyle_; UIStatusBarStyle statusBarStyle_;
PSAppStoreHeader *appStoreHeader_; PSAppStoreHeader *appStoreHeader_;
PSStoreButton *appStoreButton_; PSStoreButton *appStoreButton_;
id popOverController_; id popOverController_;
AppStoreButtonState appStoreButtonState_; AppStoreButtonState appStoreButtonState_;
NSMutableArray *cells_; NSMutableArray *cells_;
} }
@property (nonatomic, retain) BWHockeyManager *hockeyManager; @property (nonatomic, retain) BWHockeyManager *hockeyManager;

View File

@ -56,174 +56,174 @@
#pragma mark private #pragma mark private
- (void)restoreStoreButtonStateAnimated_:(BOOL)animated { - (void)restoreStoreButtonStateAnimated_:(BOOL)animated {
if ([self.hockeyManager isAppStoreEnvironment]) { if ([self.hockeyManager isAppStoreEnvironment]) {
[self setAppStoreButtonState:AppStoreButtonStateOffline animated:animated]; [self setAppStoreButtonState:AppStoreButtonStateOffline animated:animated];
} else if ([self.hockeyManager isUpdateAvailable]) { } else if ([self.hockeyManager isUpdateAvailable]) {
[self setAppStoreButtonState:AppStoreButtonStateUpdate animated:animated]; [self setAppStoreButtonState:AppStoreButtonStateUpdate animated:animated];
} else { } else {
[self setAppStoreButtonState:AppStoreButtonStateCheck animated:animated]; [self setAppStoreButtonState:AppStoreButtonStateCheck animated:animated];
} }
} }
- (void)updateAppStoreHeader_ { - (void)updateAppStoreHeader_ {
BWApp *app = self.hockeyManager.app; BWApp *app = self.hockeyManager.app;
appStoreHeader_.headerLabel = app.name; appStoreHeader_.headerLabel = app.name;
appStoreHeader_.middleHeaderLabel = [app versionString]; appStoreHeader_.middleHeaderLabel = [app versionString];
NSDateFormatter *formatter = [[[NSDateFormatter alloc] init] autorelease]; NSDateFormatter *formatter = [[[NSDateFormatter alloc] init] autorelease];
[formatter setDateStyle:NSDateFormatterMediumStyle]; [formatter setDateStyle:NSDateFormatterMediumStyle];
NSMutableString *subHeaderString = [NSMutableString string]; NSMutableString *subHeaderString = [NSMutableString string];
if (app.date) { if (app.date) {
[subHeaderString appendString:[formatter stringFromDate:app.date]]; [subHeaderString appendString:[formatter stringFromDate:app.date]];
}
if (app.size) {
if ([subHeaderString length]) {
[subHeaderString appendString:@" - "];
} }
if (app.size) { [subHeaderString appendString:app.sizeInMB];
if ([subHeaderString length]) { }
[subHeaderString appendString:@" - "]; appStoreHeader_.subHeaderLabel = subHeaderString;
}
[subHeaderString appendString:app.sizeInMB];
}
appStoreHeader_.subHeaderLabel = subHeaderString;
} }
- (void)appDidBecomeActive_ { - (void)appDidBecomeActive_ {
if (self.appStoreButtonState == AppStoreButtonStateInstalling) { if (self.appStoreButtonState == AppStoreButtonStateInstalling) {
[self setAppStoreButtonState:AppStoreButtonStateUpdate animated:YES]; [self setAppStoreButtonState:AppStoreButtonStateUpdate animated:YES];
} else if (![self.hockeyManager isCheckInProgress]) { } else if (![self.hockeyManager isCheckInProgress]) {
[self restoreStoreButtonStateAnimated_:YES]; [self restoreStoreButtonStateAnimated_:YES];
} }
} }
- (void)openSettings:(id)sender { - (void)openSettings:(id)sender {
BWHockeySettingsViewController *settings = [[[BWHockeySettingsViewController alloc] init] autorelease]; BWHockeySettingsViewController *settings = [[[BWHockeySettingsViewController alloc] init] autorelease];
Class popoverControllerClass = NSClassFromString(@"UIPopoverController"); Class popoverControllerClass = NSClassFromString(@"UIPopoverController");
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad && popoverControllerClass) { if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad && popoverControllerClass) {
if (popOverController_ == nil) { if (popOverController_ == nil) {
popOverController_ = [[popoverControllerClass alloc] initWithContentViewController:settings]; 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];
)
} }
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 { - (UIImage *)addGlossToImage_:(UIImage *)image {
BW_IF_IOS4_OR_GREATER(UIGraphicsBeginImageContextWithOptions(image.size, NO, 0.0);) BW_IF_IOS4_OR_GREATER(UIGraphicsBeginImageContextWithOptions(image.size, NO, 0.0);)
BW_IF_PRE_IOS4(UIGraphicsBeginImageContext(image.size);) BW_IF_PRE_IOS4(UIGraphicsBeginImageContext(image.size);)
[image drawAtPoint:CGPointZero]; [image drawAtPoint:CGPointZero];
UIImage *iconGradient = [UIImage bw_imageNamed:@"IconGradient.png" bundle:kHockeyBundleName]; 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]; [iconGradient drawInRect:CGRectMake(0, 0, image.size.width, image.size.height) blendMode:kCGBlendModeNormal alpha:0.5];
UIImage *result = UIGraphicsGetImageFromCurrentImageContext(); UIImage *result = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext(); UIGraphicsEndImageContext();
return result; return result;
} }
#define kMinPreviousVersionButtonHeight 50 #define kMinPreviousVersionButtonHeight 50
- (void)realignPreviousVersionButton { - (void)realignPreviousVersionButton {
// manually collect actual table height size // manually collect actual table height size
NSUInteger tableViewContentHeight = 0; NSUInteger tableViewContentHeight = 0;
for (int i=0; i < [self tableView:self.tableView numberOfRowsInSection:0]; i++) { 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:self.tableView heightForRowAtIndexPath:[NSIndexPath indexPathForRow:i inSection:0]];
} }
tableViewContentHeight += self.tableView.tableHeaderView.frame.size.height; tableViewContentHeight += self.tableView.tableHeaderView.frame.size.height;
NSUInteger footerViewSize = kMinPreviousVersionButtonHeight; NSUInteger footerViewSize = kMinPreviousVersionButtonHeight;
NSUInteger frameHeight = self.view.frame.size.height; NSUInteger frameHeight = self.view.frame.size.height;
if(tableViewContentHeight < frameHeight && (frameHeight - tableViewContentHeight > 100)) { if(tableViewContentHeight < frameHeight && (frameHeight - tableViewContentHeight > 100)) {
footerViewSize = frameHeight - tableViewContentHeight; footerViewSize = frameHeight - tableViewContentHeight;
} }
// update footer view // update footer view
if(self.tableView.tableFooterView) { if(self.tableView.tableFooterView) {
CGRect frame = self.tableView.tableFooterView.frame; CGRect frame = self.tableView.tableFooterView.frame;
frame.size.height = footerViewSize; frame.size.height = footerViewSize;
self.tableView.tableFooterView.frame = frame; self.tableView.tableFooterView.frame = frame;
} }
} }
- (void)changePreviousVersionButtonBackground:(id)sender { - (void)changePreviousVersionButtonBackground:(id)sender {
[(UIButton *)sender setBackgroundColor:BW_RGBCOLOR(183,183,183)]; [(UIButton *)sender setBackgroundColor:BW_RGBCOLOR(183,183,183)];
} }
- (void)changePreviousVersionButtonBackgroundHighlighted:(id)sender { - (void)changePreviousVersionButtonBackgroundHighlighted:(id)sender {
[(UIButton *)sender setBackgroundColor:BW_RGBCOLOR(183,183,183)]; [(UIButton *)sender setBackgroundColor:BW_RGBCOLOR(183,183,183)];
} }
- (void)showHidePreviousVersionsButton { - (void)showHidePreviousVersionsButton {
BOOL multipleVersionButtonNeeded = [self.hockeyManager.apps count] > 1 && !showAllVersions_; BOOL multipleVersionButtonNeeded = [self.hockeyManager.apps count] > 1 && !showAllVersions_;
if(multipleVersionButtonNeeded) { if(multipleVersionButtonNeeded) {
// align at the bottom if tableview is small // align at the bottom if tableview is small
UIView *footerView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, kMinPreviousVersionButtonHeight)]; UIView *footerView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, kMinPreviousVersionButtonHeight)];
footerView.autoresizingMask = UIViewAutoresizingFlexibleWidth; footerView.autoresizingMask = UIViewAutoresizingFlexibleWidth;
footerView.backgroundColor = BW_RGBCOLOR(200, 202, 204); footerView.backgroundColor = BW_RGBCOLOR(200, 202, 204);
UIButton *footerButton = [UIButton buttonWithType:UIButtonTypeCustom]; UIButton *footerButton = [UIButton buttonWithType:UIButtonTypeCustom];
BW_IF_IOS4_OR_GREATER( BW_IF_IOS4_OR_GREATER(
//footerButton.layer.shadowOffset = CGSizeMake(-2, 2); //footerButton.layer.shadowOffset = CGSizeMake(-2, 2);
footerButton.layer.shadowColor = [[UIColor blackColor] CGColor]; footerButton.layer.shadowColor = [[UIColor blackColor] CGColor];
footerButton.layer.shadowRadius = 2.0f; footerButton.layer.shadowRadius = 2.0f;
) )
footerButton.titleLabel.font = [UIFont boldSystemFontOfSize:14]; footerButton.titleLabel.font = [UIFont boldSystemFontOfSize:14];
[footerButton setTitle:BWHockeyLocalize(@"HockeyShowPreviousVersions") forState:UIControlStateNormal]; [footerButton setTitle:BWHockeyLocalize(@"HockeyShowPreviousVersions") forState:UIControlStateNormal];
[footerButton setTitleColor:[UIColor blackColor] forState:UIControlStateNormal]; [footerButton setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
[footerButton setTitleColor:[UIColor whiteColor] forState:UIControlStateHighlighted]; [footerButton setTitleColor:[UIColor whiteColor] forState:UIControlStateHighlighted];
[footerButton setBackgroundImage:[UIImage bw_imageNamed:@"buttonHighlight.png" bundle:kHockeyBundleName] forState:UIControlStateHighlighted]; [footerButton setBackgroundImage:[UIImage bw_imageNamed:@"buttonHighlight.png" bundle:kHockeyBundleName] forState:UIControlStateHighlighted];
footerButton.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleTopMargin; footerButton.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleTopMargin;
[footerButton addTarget:self action:@selector(showPreviousVersionAction) forControlEvents:UIControlEventTouchUpInside]; [footerButton addTarget:self action:@selector(showPreviousVersionAction) forControlEvents:UIControlEventTouchUpInside];
footerButton.frame = CGRectMake(0, kMinPreviousVersionButtonHeight-44, self.view.frame.size.width, 44); footerButton.frame = CGRectMake(0, kMinPreviousVersionButtonHeight-44, self.view.frame.size.width, 44);
footerButton.backgroundColor = BW_RGBCOLOR(183,183,183); footerButton.backgroundColor = BW_RGBCOLOR(183,183,183);
[footerView addSubview:footerButton]; [footerView addSubview:footerButton];
self.tableView.tableFooterView = footerView; self.tableView.tableFooterView = footerView;
[self realignPreviousVersionButton]; [self realignPreviousVersionButton];
[footerView release]; [footerView release];
} else { } else {
self.tableView.tableFooterView = nil; self.tableView.tableFooterView = nil;
self.tableView.backgroundColor = BW_RGBCOLOR(200, 202, 204); self.tableView.backgroundColor = BW_RGBCOLOR(200, 202, 204);
} }
} }
- (void)configureWebCell:(PSWebTableViewCell *)cell forApp_:(BWApp *)app { - (void)configureWebCell:(PSWebTableViewCell *)cell forApp_:(BWApp *)app {
// create web view for a version // create web view for a version
NSString *installed = @""; NSString *installed = @"";
if ([app.version isEqualToString:[self.hockeyManager currentAppVersion]]) { if ([app.version isEqualToString:[self.hockeyManager currentAppVersion]]) {
installed = [NSString stringWithFormat:@"<span style=\"float:%@;text-shadow:rgba(255,255,255,0.6) 1px 1px 0px;\"><b>%@</b></span>", [app isEqual:self.hockeyManager.app] ? @"left" : @"right", BWHockeyLocalize(@"HockeyInstalled")]; installed = [NSString stringWithFormat:@"<span style=\"float:%@;text-shadow:rgba(255,255,255,0.6) 1px 1px 0px;\"><b>%@</b></span>", [app isEqual:self.hockeyManager.app] ? @"left" : @"right", BWHockeyLocalize(@"HockeyInstalled")];
} }
if ([app isEqual:self.hockeyManager.app]) { if ([app isEqual:self.hockeyManager.app]) {
if ([app.notes length] > 0) { if ([app.notes length] > 0) {
installed = [NSString stringWithFormat:@"<p>&nbsp;%@</p>", installed]; installed = [NSString stringWithFormat:@"<p>&nbsp;%@</p>", installed];
cell.webViewContent = [NSString stringWithFormat:@"%@%@", installed, app.notes]; cell.webViewContent = [NSString stringWithFormat:@"%@%@", installed, app.notes];
} else {
cell.webViewContent = [NSString stringWithFormat:@"<div style=\"min-height:200px;vertical-align:middle;text-align:center;text-shadow:rgba(255,255,255,0.6) 1px 1px 0px;\">%@</div>", BWHockeyLocalize(@"HockeyNoReleaseNotesAvailable")];
}
} else { } else {
cell.webViewContent = [NSString stringWithFormat:@"<p><b style=\"text-shadow:rgba(255,255,255,0.6) 1px 1px 0px;\">%@</b>%@<br/><small>%@</small></p><p>%@</p>", [app versionString], installed, [app dateString], [app notesOrEmptyString]]; cell.webViewContent = [NSString stringWithFormat:@"<div style=\"min-height:200px;vertical-align:middle;text-align:center;text-shadow:rgba(255,255,255,0.6) 1px 1px 0px;\">%@</div>", BWHockeyLocalize(@"HockeyNoReleaseNotesAvailable")];
} }
cell.cellBackgroundColor = BW_RGBCOLOR(200, 202, 204); } else {
cell.webViewContent = [NSString stringWithFormat:@"<p><b style=\"text-shadow:rgba(255,255,255,0.6) 1px 1px 0px;\">%@</b>%@<br/><small>%@</small></p><p>%@</p>", [app versionString], installed, [app dateString], [app notesOrEmptyString]];
[cell addWebView]; }
// hack cell.cellBackgroundColor = BW_RGBCOLOR(200, 202, 204);
cell.textLabel.text = @"";
[cell addWebView];
[cell addObserver:self forKeyPath:@"webViewSize" options:0 context:nil]; // hack
cell.textLabel.text = @"";
[cell addObserver:self forKeyPath:@"webViewSize" options:0 context:nil];
} }
/////////////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////////////
@ -231,22 +231,22 @@
#pragma mark NSObject #pragma mark NSObject
- (id)init:(BWHockeyManager *)newHockeyManager modal:(BOOL)newModal { - (id)init:(BWHockeyManager *)newHockeyManager modal:(BOOL)newModal {
if ((self = [super initWithStyle:UITableViewStylePlain])) { if ((self = [super initWithStyle:UITableViewStylePlain])) {
self.hockeyManager = newHockeyManager; self.hockeyManager = newHockeyManager;
self.modal = newModal; self.modal = newModal;
self.title = BWHockeyLocalize(@"HockeyUpdateScreenTitle"); self.title = BWHockeyLocalize(@"HockeyUpdateScreenTitle");
if ([self.hockeyManager shouldShowUserSettings]) { if ([self.hockeyManager shouldShowUserSettings]) {
self.navigationItem.rightBarButtonItem = [[[UIBarButtonItem alloc] initWithImage:[UIImage bw_imageNamed:@"gear.png" bundle:kHockeyBundleName] self.navigationItem.rightBarButtonItem = [[[UIBarButtonItem alloc] initWithImage:[UIImage bw_imageNamed:@"gear.png" bundle:kHockeyBundleName]
style:UIBarButtonItemStyleBordered style:UIBarButtonItemStyleBordered
target:self target:self
action:@selector(openSettings:)] autorelease]; action:@selector(openSettings:)] autorelease];
}
cells_ = [[NSMutableArray alloc] initWithCapacity:5];
popOverController_ = nil;
} }
return self;
cells_ = [[NSMutableArray alloc] initWithCapacity:5];
popOverController_ = nil;
}
return self;
} }
- (id)init { - (id)init {
@ -254,12 +254,12 @@
} }
- (void)dealloc { - (void)dealloc {
[self viewDidUnload]; [self viewDidUnload];
for (UITableViewCell *cell in cells_) { for (UITableViewCell *cell in cells_) {
[cell removeObserver:self forKeyPath:@"webViewSize"]; [cell removeObserver:self forKeyPath:@"webViewSize"];
} }
[cells_ release]; [cells_ release];
[super dealloc]; [super dealloc];
} }
@ -268,30 +268,30 @@
#pragma mark View lifecycle #pragma mark View lifecycle
- (void)onAction:(id)sender { - (void)onAction:(id)sender {
if (self.modal) { if (self.modal) {
// Note that as of 5.0, parentViewController will no longer return the presenting view controller // Note that as of 5.0, parentViewController will no longer return the presenting view controller
SEL presentingViewControllerSelector = NSSelectorFromString(@"presentingViewController"); SEL presentingViewControllerSelector = NSSelectorFromString(@"presentingViewController");
UIViewController *presentingViewController = nil; UIViewController *presentingViewController = nil;
if ([self respondsToSelector:presentingViewControllerSelector]) { if ([self respondsToSelector:presentingViewControllerSelector]) {
presentingViewController = [self performSelector: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];
}
} }
else { 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 { - (CAGradientLayer *)backgroundLayer {
@ -299,199 +299,199 @@
UIColor *colorTwo = [UIColor colorWithHue:0.625 saturation:0.0 brightness:0.85 alpha:1.0]; 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 *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]; 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]; NSArray *colors = [NSArray arrayWithObjects:(id)colorOne.CGColor, colorTwo.CGColor, colorThree.CGColor, colorFour.CGColor, nil];
NSNumber *stopOne = [NSNumber numberWithFloat:0.0]; NSNumber *stopOne = [NSNumber numberWithFloat:0.0];
NSNumber *stopTwo = [NSNumber numberWithFloat:0.02]; NSNumber *stopTwo = [NSNumber numberWithFloat:0.02];
NSNumber *stopThree = [NSNumber numberWithFloat:0.99]; NSNumber *stopThree = [NSNumber numberWithFloat:0.99];
NSNumber *stopFour = [NSNumber numberWithFloat:1.0]; NSNumber *stopFour = [NSNumber numberWithFloat:1.0];
NSArray *locations = [NSArray arrayWithObjects:stopOne, stopTwo, stopThree, stopFour, nil]; NSArray *locations = [NSArray arrayWithObjects:stopOne, stopTwo, stopThree, stopFour, nil];
CAGradientLayer *headerLayer = [CAGradientLayer layer]; CAGradientLayer *headerLayer = [CAGradientLayer layer];
//headerLayer.frame = CGRectMake(0.0, 0.0, 320.0, 77.0); //headerLayer.frame = CGRectMake(0.0, 0.0, 320.0, 77.0);
headerLayer.colors = colors; headerLayer.colors = colors;
headerLayer.locations = locations; headerLayer.locations = locations;
return headerLayer; return headerLayer;
} }
- (void)viewDidLoad { - (void)viewDidLoad {
[super viewDidLoad]; [super viewDidLoad];
// add notifications only to loaded view // add notifications only to loaded view
NSNotificationCenter *dnc = [NSNotificationCenter defaultCenter]; NSNotificationCenter *dnc = [NSNotificationCenter defaultCenter];
[dnc addObserver:self selector:@selector(appDidBecomeActive_) name:UIApplicationDidBecomeActiveNotification object:nil]; [dnc addObserver:self selector:@selector(appDidBecomeActive_) name:UIApplicationDidBecomeActiveNotification object:nil];
// hook into manager with kvo! // hook into manager with kvo!
[self.hockeyManager addObserver:self forKeyPath:@"checkInProgress" options:0 context:nil]; [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:@"isUpdateURLOffline" options:0 context:nil];
[self.hockeyManager addObserver:self forKeyPath:@"updateAvailable" options:0 context:nil]; [self.hockeyManager addObserver:self forKeyPath:@"updateAvailable" options:0 context:nil];
[self.hockeyManager addObserver:self forKeyPath:@"apps" options:0 context:nil]; [self.hockeyManager addObserver:self forKeyPath:@"apps" options:0 context:nil];
kvoRegistered_ = YES; kvoRegistered_ = YES;
self.tableView.backgroundColor = BW_RGBCOLOR(200, 202, 204); self.tableView.backgroundColor = BW_RGBCOLOR(200, 202, 204);
self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone; self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
UIView *topView = [[[UIView alloc] initWithFrame:CGRectMake(0, -(600-kAppStoreViewHeight), self.view.frame.size.width, 600)] autorelease]; UIView *topView = [[[UIView alloc] initWithFrame:CGRectMake(0, -(600-kAppStoreViewHeight), self.view.frame.size.width, 600)] autorelease];
topView.autoresizingMask = UIViewAutoresizingFlexibleWidth; topView.autoresizingMask = UIViewAutoresizingFlexibleWidth;
topView.backgroundColor = BW_RGBCOLOR(140, 141, 142); topView.backgroundColor = BW_RGBCOLOR(140, 141, 142);
[self.tableView addSubview:topView]; [self.tableView addSubview:topView];
appStoreHeader_ = [[PSAppStoreHeader alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, kAppStoreViewHeight)]; appStoreHeader_ = [[PSAppStoreHeader alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, kAppStoreViewHeight)];
[self updateAppStoreHeader_]; [self updateAppStoreHeader_];
NSString *iconString = nil; NSString *iconString = nil;
NSArray *icons = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleIconFiles"]; NSArray *icons = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleIconFiles"];
if (!icons) { if (!icons) {
iconString = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleIconFile"]; iconString = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleIconFile"];
if (!iconString) { if (!iconString) {
iconString = @"Icon.png"; 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;
}
}
} }
} else {
BOOL addGloss = YES; BOOL useHighResIcon = NO;
NSNumber *prerendered = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"UIPrerenderedIcon"]; BW_IF_IOS4_OR_GREATER(if ([UIScreen mainScreen].scale == 2.0f) useHighResIcon = YES;)
if (prerendered) {
addGloss = ![prerendered boolValue]; 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]]; BOOL addGloss = YES;
} else { NSNumber *prerendered = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"UIPrerenderedIcon"];
appStoreHeader_.iconImage = [UIImage imageNamed:iconString]; if (prerendered) {
} addGloss = ![prerendered boolValue];
}
self.tableView.tableHeaderView = appStoreHeader_;
if (addGloss) {
if (self.modal) { appStoreHeader_.iconImage = [self addGlossToImage_:[UIImage imageNamed:iconString]];
self.navigationItem.leftBarButtonItem = [[[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone } else {
target:self appStoreHeader_.iconImage = [UIImage imageNamed:iconString];
action:@selector(onAction:)] autorelease]; }
}
self.tableView.tableHeaderView = appStoreHeader_;
PSStoreButton *storeButton = [[[PSStoreButton alloc] initWithPadding:CGPointMake(5, 40)] autorelease];
storeButton.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin; if (self.modal) {
storeButton.buttonDelegate = self; self.navigationItem.leftBarButtonItem = [[[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone
[self.tableView.tableHeaderView addSubview:storeButton]; target:self
storeButton.buttonData = [PSStoreButtonData dataWithLabel:@"" colors:[PSStoreButton appStoreGrayColor] enabled:NO]; action:@selector(onAction:)] autorelease];
self.appStoreButtonState = AppStoreButtonStateCheck; }
[storeButton alignToSuperview];
appStoreButton_ = [storeButton retain]; 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 { - (void)viewWillAppear:(BOOL)animated {
if ([self.hockeyManager isAppStoreEnvironment]) if ([self.hockeyManager isAppStoreEnvironment])
self.appStoreButtonState = AppStoreButtonStateOffline; self.appStoreButtonState = AppStoreButtonStateOffline;
self.hockeyManager.currentHockeyViewController = self; self.hockeyManager.currentHockeyViewController = self;
[super viewWillAppear:animated]; [super viewWillAppear:animated];
statusBarStyle_ = [[UIApplication sharedApplication] statusBarStyle]; statusBarStyle_ = [[UIApplication sharedApplication] statusBarStyle];
[[UIApplication sharedApplication] setStatusBarStyle:(self.navigationController.navigationBar.barStyle == UIBarStyleDefault) ? UIStatusBarStyleDefault : UIStatusBarStyleBlackOpaque]; [[UIApplication sharedApplication] setStatusBarStyle:(self.navigationController.navigationBar.barStyle == UIBarStyleDefault) ? UIStatusBarStyleDefault : UIStatusBarStyleBlackOpaque];
[self redrawTableView]; [self redrawTableView];
} }
- (void)viewWillDisappear:(BOOL)animated { - (void)viewWillDisappear:(BOOL)animated {
self.hockeyManager.currentHockeyViewController = nil; self.hockeyManager.currentHockeyViewController = nil;
//if the popover is still visible, dismiss it //if the popover is still visible, dismiss it
[popOverController_ dismissPopoverAnimated:YES]; [popOverController_ dismissPopoverAnimated:YES];
[super viewWillDisappear:animated]; [super viewWillDisappear:animated];
[[UIApplication sharedApplication] setStatusBarStyle:statusBarStyle_]; [[UIApplication sharedApplication] setStatusBarStyle:statusBarStyle_];
} }
- (void)redrawTableView { - (void)redrawTableView {
[self restoreStoreButtonStateAnimated_:NO]; [self restoreStoreButtonStateAnimated_:NO];
[self updateAppStoreHeader_]; [self updateAppStoreHeader_];
// clean up and remove any pending overservers // clean up and remove any pending overservers
for (UITableViewCell *cell in cells_) { for (UITableViewCell *cell in cells_) {
[cell removeObserver:self forKeyPath:@"webViewSize"]; [cell removeObserver:self forKeyPath:@"webViewSize"];
} }
[cells_ removeAllObjects]; [cells_ removeAllObjects];
int i = 0; int i = 0;
BOOL breakAfterThisApp = NO; BOOL breakAfterThisApp = NO;
for (BWApp *app in self.hockeyManager.apps) { for (BWApp *app in self.hockeyManager.apps) {
i++; i++;
// only show the newer version of the app by default, if we don't show all versions // only show the newer version of the app by default, if we don't show all versions
if (!showAllVersions_) { if (!showAllVersions_) {
if ([app.version isEqualToString:[self.hockeyManager currentAppVersion]]) { if ([app.version isEqualToString:[self.hockeyManager currentAppVersion]]) {
if (i == 1) { if (i == 1) {
breakAfterThisApp = YES; breakAfterThisApp = YES;
} else { } else {
break; break;
}
}
} }
}
PSWebTableViewCell *cell = [[[PSWebTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:kWebCellIdentifier] autorelease];
[self configureWebCell:cell forApp_:app];
[cells_ addObject:cell];
if (breakAfterThisApp) break;
} }
[self.tableView reloadData]; PSWebTableViewCell *cell = [[[PSWebTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:kWebCellIdentifier] autorelease];
[self showHidePreviousVersionsButton]; [self configureWebCell:cell forApp_:app];
[cells_ addObject:cell];
if (breakAfterThisApp) break;
}
[self.tableView reloadData];
[self showHidePreviousVersionsButton];
} }
- (void)showPreviousVersionAction { - (void)showPreviousVersionAction {
showAllVersions_ = YES; showAllVersions_ = YES;
BOOL showAllPending = NO; BOOL showAllPending = NO;
for (BWApp *app in self.hockeyManager.apps) { for (BWApp *app in self.hockeyManager.apps) {
if (!showAllPending) { if (!showAllPending) {
if ([app.version isEqualToString:[self.hockeyManager currentAppVersion]]) { if ([app.version isEqualToString:[self.hockeyManager currentAppVersion]]) {
showAllPending = YES; showAllPending = YES;
if (app == self.hockeyManager.app) { if (app == self.hockeyManager.app) {
continue; // skip this version already if it the latest version is the installed one continue; // skip this version already if it the latest version is the installed one
}
} else {
continue; // skip already shown
}
} }
} else {
PSWebTableViewCell *cell = [[[PSWebTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:kWebCellIdentifier] autorelease]; continue; // skip already shown
[self configureWebCell:cell forApp_:app]; }
[cells_ addObject:cell];
} }
[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 { - (void)viewDidUnload {
[appStoreHeader_ release]; appStoreHeader_ = nil; [appStoreHeader_ release]; appStoreHeader_ = nil;
[popOverController_ release], popOverController_ = nil; [popOverController_ release], popOverController_ = nil;
[[NSNotificationCenter defaultCenter] removeObserver:self]; [[NSNotificationCenter defaultCenter] removeObserver:self];
// test if KVO's are registered. if class is destroyed before it was shown(viewDidLoad) no KVOs are registered. // test if KVO's are registered. if class is destroyed before it was shown(viewDidLoad) no KVOs are registered.
if (kvoRegistered_) { if (kvoRegistered_) {
[self.hockeyManager removeObserver:self forKeyPath:@"checkInProgress"]; [self.hockeyManager removeObserver:self forKeyPath:@"checkInProgress"];
[self.hockeyManager removeObserver:self forKeyPath:@"isUpdateURLOffline"]; [self.hockeyManager removeObserver:self forKeyPath:@"isUpdateURLOffline"];
[self.hockeyManager removeObserver:self forKeyPath:@"updateAvailable"]; [self.hockeyManager removeObserver:self forKeyPath:@"updateAvailable"];
[self.hockeyManager removeObserver:self forKeyPath:@"apps"]; [self.hockeyManager removeObserver:self forKeyPath:@"apps"];
kvoRegistered_ = NO; kvoRegistered_ = NO;
} }
[super viewDidUnload]; [super viewDidUnload];
} }
/////////////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////////////
@ -499,32 +499,32 @@
#pragma mark Table view data source #pragma mark Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1; return 1;
} }
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
CGFloat rowHeight = 0; CGFloat rowHeight = 0;
if ([cells_ count] > (NSUInteger)indexPath.row) { if ([cells_ count] > (NSUInteger)indexPath.row) {
PSWebTableViewCell *cell = [cells_ objectAtIndex:indexPath.row]; PSWebTableViewCell *cell = [cells_ objectAtIndex:indexPath.row];
rowHeight = cell.webViewSize.height; rowHeight = cell.webViewSize.height;
} }
if ([self.hockeyManager.apps count] > 1 && !showAllVersions_) { if ([self.hockeyManager.apps count] > 1 && !showAllVersions_) {
self.tableView.backgroundColor = BW_RGBCOLOR(183, 183, 183); self.tableView.backgroundColor = BW_RGBCOLOR(183, 183, 183);
} }
if (rowHeight == 0) { if (rowHeight == 0) {
rowHeight = indexPath.row == 0 ? 250 : 44; // fill screen on startup rowHeight = indexPath.row == 0 ? 250 : 44; // fill screen on startup
self.tableView.backgroundColor = BW_RGBCOLOR(200, 202, 204); self.tableView.backgroundColor = BW_RGBCOLOR(200, 202, 204);
} }
return rowHeight; return rowHeight;
} }
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
NSInteger cellCount = [cells_ count]; NSInteger cellCount = [cells_ count];
return cellCount; return cellCount;
} }
/////////////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////////////
@ -535,32 +535,32 @@
// only make changes if we are visible // only make changes if we are visible
if(self.view.window) { if(self.view.window) {
if ([keyPath isEqualToString:@"webViewSize"]) { if ([keyPath isEqualToString:@"webViewSize"]) {
[self.tableView reloadData]; [self.tableView reloadData];
[self realignPreviousVersionButton]; [self realignPreviousVersionButton];
} else if ([keyPath isEqualToString:@"checkInProgress"]) { } else if ([keyPath isEqualToString:@"checkInProgress"]) {
if (self.hockeyManager.isCheckInProgress) { if (self.hockeyManager.isCheckInProgress) {
[self setAppStoreButtonState:AppStoreButtonStateSearching animated:YES]; [self setAppStoreButtonState:AppStoreButtonStateSearching animated:YES];
}else { }else {
[self restoreStoreButtonStateAnimated_:YES]; [self restoreStoreButtonStateAnimated_:YES];
} }
} else if ([keyPath isEqualToString:@"isUpdateURLOffline"]) { } else if ([keyPath isEqualToString:@"isUpdateURLOffline"]) {
[self restoreStoreButtonStateAnimated_:YES]; [self restoreStoreButtonStateAnimated_:YES];
} else if ([keyPath isEqualToString:@"updateAvailable"]) { } else if ([keyPath isEqualToString:@"updateAvailable"]) {
[self restoreStoreButtonStateAnimated_:YES]; [self restoreStoreButtonStateAnimated_:YES];
} else if ([keyPath isEqualToString:@"apps"]) { } else if ([keyPath isEqualToString:@"apps"]) {
[self redrawTableView]; [self redrawTableView];
} }
} }
} }
// Customize the appearance of table view cells. // Customize the appearance of table view cells.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
if ([cells_ count] > (NSUInteger)indexPath.row) { if ([cells_ count] > (NSUInteger)indexPath.row) {
return [cells_ objectAtIndex:indexPath.row]; return [cells_ objectAtIndex:indexPath.row];
} else { } else {
BWHockeyLog(@"Warning: cells_ and indexPath do not match? forgot calling redrawTableView?"); BWHockeyLog(@"Warning: cells_ and indexPath do not match? forgot calling redrawTableView?");
} }
return nil; return nil;
} }
@ -569,22 +569,22 @@
#pragma mark Rotation #pragma mark Rotation
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation { - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
BOOL shouldAutorotate; BOOL shouldAutorotate;
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) { if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) {
shouldAutorotate = (interfaceOrientation == UIInterfaceOrientationLandscapeLeft || shouldAutorotate = (interfaceOrientation == UIInterfaceOrientationLandscapeLeft ||
interfaceOrientation == UIInterfaceOrientationLandscapeRight || interfaceOrientation == UIInterfaceOrientationLandscapeRight ||
interfaceOrientation == UIInterfaceOrientationPortrait); interfaceOrientation == UIInterfaceOrientationPortrait);
} else { } else {
shouldAutorotate = YES; shouldAutorotate = YES;
} }
return shouldAutorotate; return shouldAutorotate;
} }
- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation duration:(NSTimeInterval)duration { - (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation duration:(NSTimeInterval)duration {
// update all cells // update all cells
[cells_ makeObjectsPerformSelector:@selector(addWebView)]; [cells_ makeObjectsPerformSelector:@selector(addWebView)];
} }
/////////////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////////////
@ -592,46 +592,46 @@
#pragma mark PSAppStoreHeaderDelegate #pragma mark PSAppStoreHeaderDelegate
- (void)setAppStoreButtonState:(AppStoreButtonState)anAppStoreButtonState { - (void)setAppStoreButtonState:(AppStoreButtonState)anAppStoreButtonState {
[self setAppStoreButtonState:anAppStoreButtonState animated:NO]; [self setAppStoreButtonState:anAppStoreButtonState animated:NO];
} }
- (void)setAppStoreButtonState:(AppStoreButtonState)anAppStoreButtonState animated:(BOOL)animated { - (void)setAppStoreButtonState:(AppStoreButtonState)anAppStoreButtonState animated:(BOOL)animated {
appStoreButtonState_ = anAppStoreButtonState; appStoreButtonState_ = anAppStoreButtonState;
switch (anAppStoreButtonState) { switch (anAppStoreButtonState) {
case AppStoreButtonStateOffline: case AppStoreButtonStateOffline:
[appStoreButton_ setButtonData:[PSStoreButtonData dataWithLabel:BWHockeyLocalize(@"HockeyButtonOffline") colors:[PSStoreButton appStoreGrayColor] enabled:NO] animated:animated]; [appStoreButton_ setButtonData:[PSStoreButtonData dataWithLabel:BWHockeyLocalize(@"HockeyButtonOffline") colors:[PSStoreButton appStoreGrayColor] enabled:NO] animated:animated];
break; break;
case AppStoreButtonStateCheck: case AppStoreButtonStateCheck:
[appStoreButton_ setButtonData:[PSStoreButtonData dataWithLabel:BWHockeyLocalize(@"HockeyButtonCheck") colors:[PSStoreButton appStoreGreenColor] enabled:YES] animated:animated]; [appStoreButton_ setButtonData:[PSStoreButtonData dataWithLabel:BWHockeyLocalize(@"HockeyButtonCheck") colors:[PSStoreButton appStoreGreenColor] enabled:YES] animated:animated];
break; break;
case AppStoreButtonStateSearching: case AppStoreButtonStateSearching:
[appStoreButton_ setButtonData:[PSStoreButtonData dataWithLabel:BWHockeyLocalize(@"HockeyButtonSearching") colors:[PSStoreButton appStoreGrayColor] enabled:NO] animated:animated]; [appStoreButton_ setButtonData:[PSStoreButtonData dataWithLabel:BWHockeyLocalize(@"HockeyButtonSearching") colors:[PSStoreButton appStoreGrayColor] enabled:NO] animated:animated];
break; break;
case AppStoreButtonStateUpdate: case AppStoreButtonStateUpdate:
[appStoreButton_ setButtonData:[PSStoreButtonData dataWithLabel:BWHockeyLocalize(@"HockeyButtonUpdate") colors:[PSStoreButton appStoreBlueColor] enabled:YES] animated:animated]; [appStoreButton_ setButtonData:[PSStoreButtonData dataWithLabel:BWHockeyLocalize(@"HockeyButtonUpdate") colors:[PSStoreButton appStoreBlueColor] enabled:YES] animated:animated];
break; break;
case AppStoreButtonStateInstalling: case AppStoreButtonStateInstalling:
[appStoreButton_ setButtonData:[PSStoreButtonData dataWithLabel:BWHockeyLocalize(@"HockeyButtonInstalling") colors:[PSStoreButton appStoreGrayColor] enabled:NO] animated:animated]; [appStoreButton_ setButtonData:[PSStoreButtonData dataWithLabel:BWHockeyLocalize(@"HockeyButtonInstalling") colors:[PSStoreButton appStoreGrayColor] enabled:NO] animated:animated];
break; break;
default: default:
break; break;
} }
} }
- (void)storeButtonFired:(PSStoreButton *)button { - (void)storeButtonFired:(PSStoreButton *)button {
switch (appStoreButtonState_) { switch (appStoreButtonState_) {
case AppStoreButtonStateCheck: case AppStoreButtonStateCheck:
[self.hockeyManager checkForUpdateShowFeedback:YES]; [self.hockeyManager checkForUpdateShowFeedback:YES];
break; break;
case AppStoreButtonStateUpdate: case AppStoreButtonStateUpdate:
if ([self.hockeyManager initiateAppDownload]) { if ([self.hockeyManager initiateAppDownload]) {
[self setAppStoreButtonState:AppStoreButtonStateInstalling animated:YES]; [self setAppStoreButtonState:AppStoreButtonStateInstalling animated:YES];
}; };
break; break;
default: default:
break; break;
} }
} }
@end @end

View File

@ -57,61 +57,61 @@ typedef enum QuincyKitAlertType {
} CrashAlertType; } CrashAlertType;
typedef enum CrashReportStatus { 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, 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, 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, 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, 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, CrashReportStatusFailureSQLAddSymbolicateTodo = -18,
// SQL for adding crash log in the database failed // SQL for adding crash log in the database failed
CrashReportStatusFailureSQLAddCrashlog = -17, CrashReportStatusFailureSQLAddCrashlog = -17,
// SQL for adding a new version in the database failed // SQL for adding a new version in the database failed
CrashReportStatusFailureSQLAddVersion = -16, CrashReportStatusFailureSQLAddVersion = -16,
// SQL for checking if the version is already added in the database failed // SQL for checking if the version is already added in the database failed
CrashReportStatusFailureSQLCheckVersionExists = -15, CrashReportStatusFailureSQLCheckVersionExists = -15,
// SQL for creating a new pattern for this bug and set amount of occurrances to 1 in the database failed // SQL for creating a new pattern for this bug and set amount of occurrances to 1 in the database failed
CrashReportStatusFailureSQLAddPattern = -14, CrashReportStatusFailureSQLAddPattern = -14,
// SQL for checking the status of the bugfix version in the database failed // SQL for checking the status of the bugfix version in the database failed
CrashReportStatusFailureSQLCheckBugfixStatus = -13, CrashReportStatusFailureSQLCheckBugfixStatus = -13,
// SQL for updating the occurances of this pattern in the database failed // SQL for updating the occurances of this pattern in the database failed
CrashReportStatusFailureSQLUpdatePatternOccurances = -12, CrashReportStatusFailureSQLUpdatePatternOccurances = -12,
// SQL for getting all the known bug patterns for the current app version in the database failed // SQL for getting all the known bug patterns for the current app version in the database failed
CrashReportStatusFailureSQLFindKnownPatterns = -11, CrashReportStatusFailureSQLFindKnownPatterns = -11,
// SQL for finding the bundle identifier in the database failed // SQL for finding the bundle identifier in the database failed
CrashReportStatusFailureSQLSearchAppName = -10, CrashReportStatusFailureSQLSearchAppName = -10,
// the post request didn't contain valid data // the post request didn't contain valid data
CrashReportStatusFailureInvalidPostData = -3, CrashReportStatusFailureInvalidPostData = -3,
// incoming data may not be added, because e.g. bundle identifier wasn't found // incoming data may not be added, because e.g. bundle identifier wasn't found
CrashReportStatusFailureInvalidIncomingData = -2, CrashReportStatusFailureInvalidIncomingData = -2,
// database cannot be accessed, check hostname, username, password and database name settings in config.php // database cannot be accessed, check hostname, username, password and database name settings in config.php
CrashReportStatusFailureDatabaseNotAvailable = -1, CrashReportStatusFailureDatabaseNotAvailable = -1,
CrashReportStatusUnknown = 0, CrashReportStatusUnknown = 0,
CrashReportStatusAssigned = 1, CrashReportStatusAssigned = 1,
CrashReportStatusSubmitted = 2, CrashReportStatusSubmitted = 2,
CrashReportStatusAvailable = 3, CrashReportStatusAvailable = 3,
} CrashReportStatus; } CrashReportStatus;
// This protocol is used to send the image updates // This protocol is used to send the image updates
@ -140,42 +140,42 @@ typedef enum CrashReportStatus {
@end @end
@interface BWQuincyManager : NSObject <NSXMLParserDelegate> { @interface BWQuincyManager : NSObject <NSXMLParserDelegate> {
NSString *_submissionURL; NSString *_submissionURL;
id <BWQuincyManagerDelegate> _delegate; id <BWQuincyManagerDelegate> _delegate;
BOOL _showAlwaysButton; BOOL _showAlwaysButton;
BOOL _feedbackActivated; BOOL _feedbackActivated;
BOOL _autoSubmitCrashReport; BOOL _autoSubmitCrashReport;
BOOL _autoSubmitDeviceUDID; BOOL _autoSubmitDeviceUDID;
BOOL _didCrashInLastSession; BOOL _didCrashInLastSession;
NSString *_appIdentifier; NSString *_appIdentifier;
NSString *_feedbackRequestID; NSString *_feedbackRequestID;
float _feedbackDelayInterval; float _feedbackDelayInterval;
NSMutableString *_contentOfProperty; NSMutableString *_contentOfProperty;
CrashReportStatus _serverResult; CrashReportStatus _serverResult;
int _analyzerStarted; int _analyzerStarted;
NSString *_crashesDir; NSString *_crashesDir;
BOOL _crashIdenticalCurrentVersion; BOOL _crashIdenticalCurrentVersion;
BOOL _crashReportActivated; BOOL _crashReportActivated;
NSMutableArray *_crashFiles; NSMutableArray *_crashFiles;
NSMutableData *_responseData; NSMutableData *_responseData;
NSInteger _statusCode; NSInteger _statusCode;
NSURLConnection *_urlConnection; NSURLConnection *_urlConnection;
NSData *_crashData; NSData *_crashData;
NSString *_languageStyle; NSString *_languageStyle;
BOOL _sendingInProgress; BOOL _sendingInProgress;
} }
+ (BWQuincyManager *)sharedQuincyManager; + (BWQuincyManager *)sharedQuincyManager;

View File

@ -36,22 +36,22 @@
#include <inttypes.h> //needed for PRIx64 macro #include <inttypes.h> //needed for PRIx64 macro
NSBundle *quincyBundle(void) { NSBundle *quincyBundle(void) {
static NSBundle* bundle = nil; static NSBundle* bundle = nil;
if (!bundle) { if (!bundle) {
NSString* path = [[[NSBundle mainBundle] resourcePath] NSString* path = [[[NSBundle mainBundle] resourcePath]
stringByAppendingPathComponent:kQuincyBundleName]; stringByAppendingPathComponent:kQuincyBundleName];
bundle = [[NSBundle bundleWithPath:path] retain]; bundle = [[NSBundle bundleWithPath:path] retain];
} }
return bundle; return bundle;
} }
NSString *BWQuincyLocalize(NSString *stringToken) { NSString *BWQuincyLocalize(NSString *stringToken) {
if ([BWQuincyManager sharedQuincyManager].languageStyle == nil) if ([BWQuincyManager sharedQuincyManager].languageStyle == nil)
return NSLocalizedStringFromTableInBundle(stringToken, @"Quincy", quincyBundle(), @""); return NSLocalizedStringFromTableInBundle(stringToken, @"Quincy", quincyBundle(), @"");
else { else {
NSString *alternate = [NSString stringWithFormat:@"Quincy%@", [BWQuincyManager sharedQuincyManager].languageStyle]; NSString *alternate = [NSString stringWithFormat:@"Quincy%@", [BWQuincyManager sharedQuincyManager].languageStyle];
return NSLocalizedStringFromTableInBundle(stringToken, alternate, quincyBundle(), @""); return NSLocalizedStringFromTableInBundle(stringToken, alternate, quincyBundle(), @"");
} }
} }
@ -93,15 +93,15 @@ NSString *BWQuincyLocalize(NSString *stringToken) {
#if __IPHONE_OS_VERSION_MIN_REQUIRED >= 40000 #if __IPHONE_OS_VERSION_MIN_REQUIRED >= 40000
+(BWQuincyManager *)sharedQuincyManager +(BWQuincyManager *)sharedQuincyManager
{ {
static BWQuincyManager *sharedInstance = nil; static BWQuincyManager *sharedInstance = nil;
static dispatch_once_t pred; static dispatch_once_t pred;
dispatch_once(&pred, ^{ dispatch_once(&pred, ^{
sharedInstance = [BWQuincyManager alloc]; sharedInstance = [BWQuincyManager alloc];
sharedInstance = [sharedInstance init]; sharedInstance = [sharedInstance init];
}); });
return sharedInstance; return sharedInstance;
} }
#else #else
+ (BWQuincyManager *)sharedQuincyManager { + (BWQuincyManager *)sharedQuincyManager {
@ -116,24 +116,24 @@ NSString *BWQuincyLocalize(NSString *stringToken) {
#endif #endif
- (id) init { - (id) init {
if ((self = [super init])) { if ((self = [super init])) {
_serverResult = CrashReportStatusUnknown; _serverResult = CrashReportStatusUnknown;
_crashIdenticalCurrentVersion = YES; _crashIdenticalCurrentVersion = YES;
_crashData = nil; _crashData = nil;
_urlConnection = nil; _urlConnection = nil;
_submissionURL = nil; _submissionURL = nil;
_responseData = nil; _responseData = nil;
_appIdentifier = nil; _appIdentifier = nil;
_sendingInProgress = NO; _sendingInProgress = NO;
_languageStyle = nil; _languageStyle = nil;
_didCrashInLastSession = NO; _didCrashInLastSession = NO;
self.delegate = nil; self.delegate = nil;
self.feedbackActivated = NO; self.feedbackActivated = NO;
self.showAlwaysButton = NO; self.showAlwaysButton = NO;
self.autoSubmitCrashReport = NO; self.autoSubmitCrashReport = NO;
self.autoSubmitDeviceUDID = NO; self.autoSubmitDeviceUDID = NO;
NSString *testValue = [[NSUserDefaults standardUserDefaults] stringForKey:kQuincyKitAnalyzerStarted]; NSString *testValue = [[NSUserDefaults standardUserDefaults] stringForKey:kQuincyKitAnalyzerStarted];
if (testValue) { if (testValue) {
_analyzerStarted = [[NSUserDefaults standardUserDefaults] integerForKey:kQuincyKitAnalyzerStarted]; _analyzerStarted = [[NSUserDefaults standardUserDefaults] integerForKey:kQuincyKitAnalyzerStarted];
@ -149,7 +149,7 @@ NSString *BWQuincyLocalize(NSString *stringToken) {
_crashReportActivated = YES; _crashReportActivated = YES;
[[NSUserDefaults standardUserDefaults] setValue:[NSNumber numberWithBool:YES] forKey:kQuincyKitActivated]; [[NSUserDefaults standardUserDefaults] setValue:[NSNumber numberWithBool:YES] forKey:kQuincyKitActivated];
} }
if (_crashReportActivated) { if (_crashReportActivated) {
_crashFiles = [[NSMutableArray alloc] init]; _crashFiles = [[NSMutableArray alloc] init];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES); NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
@ -163,52 +163,52 @@ NSString *BWQuincyLocalize(NSString *stringToken) {
[fm createDirectoryAtPath:_crashesDir withIntermediateDirectories: YES attributes: attributes error: &theError]; [fm createDirectoryAtPath:_crashesDir withIntermediateDirectories: YES attributes: attributes error: &theError];
} }
PLCrashReporter *crashReporter = [PLCrashReporter sharedReporter]; PLCrashReporter *crashReporter = [PLCrashReporter sharedReporter];
NSError *error = NULL; NSError *error = NULL;
// Check if we previously crashed // Check if we previously crashed
if ([crashReporter hasPendingCrashReport]) { if ([crashReporter hasPendingCrashReport]) {
_didCrashInLastSession = YES; _didCrashInLastSession = YES;
[self handleCrashReport]; [self handleCrashReport];
} }
// Enable the Crash Reporter // Enable the Crash Reporter
if (![crashReporter enableCrashReporterAndReturnError: &error]) if (![crashReporter enableCrashReporterAndReturnError: &error])
NSLog(@"Warning: Could not enable crash reporter: %@", 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!"); NSLog(@"WARNING: Quincy.bundle is missing in the app bundle!");
} }
} }
return self; return self;
} }
- (void) dealloc { - (void) dealloc {
self.delegate = nil; self.delegate = nil;
[[NSNotificationCenter defaultCenter] removeObserver:self name:BWQuincyNetworkBecomeReachable object:nil]; [[NSNotificationCenter defaultCenter] removeObserver:self name:BWQuincyNetworkBecomeReachable object:nil];
[_languageStyle release]; [_languageStyle release];
[_submissionURL release]; [_submissionURL release];
_submissionURL = nil; _submissionURL = nil;
[_appIdentifier release]; [_appIdentifier release];
_appIdentifier = nil; _appIdentifier = nil;
[_urlConnection cancel]; [_urlConnection cancel];
[_urlConnection release]; [_urlConnection release];
_urlConnection = nil; _urlConnection = nil;
[_crashData release]; [_crashData release];
[_crashesDir release]; [_crashesDir release];
[_crashFiles release]; [_crashFiles release];
[super dealloc]; [super dealloc];
} }
@ -216,21 +216,21 @@ NSString *BWQuincyLocalize(NSString *stringToken) {
#pragma mark - #pragma mark -
#pragma mark setter #pragma mark setter
- (void)setSubmissionURL:(NSString *)anSubmissionURL { - (void)setSubmissionURL:(NSString *)anSubmissionURL {
if (_submissionURL != anSubmissionURL) { if (_submissionURL != anSubmissionURL) {
[_submissionURL release]; [_submissionURL release];
_submissionURL = [anSubmissionURL copy]; _submissionURL = [anSubmissionURL copy];
} }
[self performSelector:@selector(startManager) withObject:nil afterDelay:1.0f]; [self performSelector:@selector(startManager) withObject:nil afterDelay:1.0f];
} }
- (void)setAppIdentifier:(NSString *)anAppIdentifier { - (void)setAppIdentifier:(NSString *)anAppIdentifier {
if (_appIdentifier != anAppIdentifier) { if (_appIdentifier != anAppIdentifier) {
[_appIdentifier release]; [_appIdentifier release];
_appIdentifier = [anAppIdentifier copy]; _appIdentifier = [anAppIdentifier copy];
} }
[self setSubmissionURL:@"https://rink.hockeyapp.net/"]; [self setSubmissionURL:@"https://rink.hockeyapp.net/"];
} }
@ -238,65 +238,65 @@ NSString *BWQuincyLocalize(NSString *stringToken) {
#pragma mark private methods #pragma mark private methods
- (BOOL)autoSendCrashReports { - (BOOL)autoSendCrashReports {
BOOL result = NO; BOOL result = NO;
if (!self.autoSubmitCrashReport) { if (!self.autoSubmitCrashReport) {
if (self.isShowingAlwaysButton && [[NSUserDefaults standardUserDefaults] boolForKey: kAutomaticallySendCrashReports]) { if (self.isShowingAlwaysButton && [[NSUserDefaults standardUserDefaults] boolForKey: kAutomaticallySendCrashReports]) {
result = YES; result = YES;
}
} else {
result = YES;
} }
} else {
return result; result = YES;
}
return result;
} }
// begin the startup process // begin the startup process
- (void)startManager { - (void)startManager {
if (!_sendingInProgress && [self hasPendingCrashReport]) { if (!_sendingInProgress && [self hasPendingCrashReport]) {
_sendingInProgress = YES; _sendingInProgress = YES;
if (!quincyBundle()) { if (!quincyBundle()) {
NSLog(@"Quincy.bundle is missing, sending report automatically!"); NSLog(@"Quincy.bundle is missing, sending report automatically!");
[self _sendCrashReports]; [self _sendCrashReports];
} else if (!self.autoSubmitCrashReport && [self hasNonApprovedCrashReports]) { } else if (!self.autoSubmitCrashReport && [self hasNonApprovedCrashReports]) {
if (self.delegate != nil && [self.delegate respondsToSelector:@selector(willShowSubmitCrashReportAlert)]) { if (self.delegate != nil && [self.delegate respondsToSelector:@selector(willShowSubmitCrashReportAlert)]) {
[self.delegate willShowSubmitCrashReportAlert]; [self.delegate willShowSubmitCrashReportAlert];
} }
NSString *appName = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleDisplayName"]; NSString *appName = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleDisplayName"];
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:[NSString stringWithFormat:BWQuincyLocalize(@"CrashDataFoundTitle"), appName] UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:[NSString stringWithFormat:BWQuincyLocalize(@"CrashDataFoundTitle"), appName]
message:[NSString stringWithFormat:BWQuincyLocalize(@"CrashDataFoundDescription"), appName] message:[NSString stringWithFormat:BWQuincyLocalize(@"CrashDataFoundDescription"), appName]
delegate:self delegate:self
cancelButtonTitle:BWQuincyLocalize(@"CrashDontSendReport") cancelButtonTitle:BWQuincyLocalize(@"CrashDontSendReport")
otherButtonTitles:BWQuincyLocalize(@"CrashSendReport"), nil]; otherButtonTitles:BWQuincyLocalize(@"CrashSendReport"), nil];
if ([self isShowingAlwaysButton]) { if ([self isShowingAlwaysButton]) {
[alertView addButtonWithTitle:BWQuincyLocalize(@"CrashSendReportAlways")]; [alertView addButtonWithTitle:BWQuincyLocalize(@"CrashSendReportAlways")];
} }
[alertView setTag: QuincyKitAlertTypeSend]; [alertView setTag: QuincyKitAlertTypeSend];
[alertView show]; [alertView show];
[alertView release]; [alertView release];
} else { } else {
[self _sendCrashReports]; [self _sendCrashReports];
}
} }
}
} }
- (BOOL)hasNonApprovedCrashReports { - (BOOL)hasNonApprovedCrashReports {
NSDictionary *approvedCrashReports = [[NSUserDefaults standardUserDefaults] dictionaryForKey: kApprovedCrashReports]; NSDictionary *approvedCrashReports = [[NSUserDefaults standardUserDefaults] dictionaryForKey: kApprovedCrashReports];
if (!approvedCrashReports || [approvedCrashReports count] == 0) return YES; if (!approvedCrashReports || [approvedCrashReports count] == 0) return YES;
for (NSUInteger i=0; i < [_crashFiles count]; i++) { for (NSUInteger i=0; i < [_crashFiles count]; i++) {
NSString *filename = [_crashFiles objectAtIndex:i]; NSString *filename = [_crashFiles objectAtIndex:i];
if (![approvedCrashReports objectForKey:filename]) return YES;
}
return NO; if (![approvedCrashReports objectForKey:filename]) return YES;
}
return NO;
} }
- (BOOL)hasPendingCrashReport { - (BOOL)hasPendingCrashReport {
@ -305,8 +305,8 @@ NSString *BWQuincyLocalize(NSString *stringToken) {
if ([_crashFiles count] == 0 && [fm fileExistsAtPath:_crashesDir]) { if ([_crashFiles count] == 0 && [fm fileExistsAtPath:_crashesDir]) {
NSString *file = nil; NSString *file = nil;
NSError *error = NULL; NSError *error = NULL;
NSDirectoryEnumerator *dirEnum = [fm enumeratorAtPath: _crashesDir]; NSDirectoryEnumerator *dirEnum = [fm enumeratorAtPath: _crashesDir];
while ((file = [dirEnum nextObject])) { while ((file = [dirEnum nextObject])) {
@ -330,31 +330,31 @@ NSString *BWQuincyLocalize(NSString *stringToken) {
UIAlertView *alertView = nil; UIAlertView *alertView = nil;
if (_serverResult >= CrashReportStatusAssigned && if (_serverResult >= CrashReportStatusAssigned &&
_crashIdenticalCurrentVersion && _crashIdenticalCurrentVersion &&
quincyBundle()) { quincyBundle()) {
// show some feedback to the user about the crash status // show some feedback to the user about the crash status
NSString *appName = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleDisplayName"]; NSString *appName = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleDisplayName"];
switch (_serverResult) { switch (_serverResult) {
case CrashReportStatusAssigned: case CrashReportStatusAssigned:
alertView = [[UIAlertView alloc] initWithTitle: [NSString stringWithFormat:BWQuincyLocalize(@"CrashResponseTitle"), appName ] alertView = [[UIAlertView alloc] initWithTitle: [NSString stringWithFormat:BWQuincyLocalize(@"CrashResponseTitle"), appName ]
message: [NSString stringWithFormat:BWQuincyLocalize(@"CrashResponseNextRelease"), appName] message: [NSString stringWithFormat:BWQuincyLocalize(@"CrashResponseNextRelease"), appName]
delegate: self delegate: self
cancelButtonTitle: BWQuincyLocalize(@"CrashResponseTitleOK") cancelButtonTitle: BWQuincyLocalize(@"CrashResponseTitleOK")
otherButtonTitles: nil]; otherButtonTitles: nil];
break; break;
case CrashReportStatusSubmitted: case CrashReportStatusSubmitted:
alertView = [[UIAlertView alloc] initWithTitle: [NSString stringWithFormat:BWQuincyLocalize(@"CrashResponseTitle"), appName ] alertView = [[UIAlertView alloc] initWithTitle: [NSString stringWithFormat:BWQuincyLocalize(@"CrashResponseTitle"), appName ]
message: [NSString stringWithFormat:BWQuincyLocalize(@"CrashResponseWaitingApple"), appName] message: [NSString stringWithFormat:BWQuincyLocalize(@"CrashResponseWaitingApple"), appName]
delegate: self delegate: self
cancelButtonTitle: BWQuincyLocalize(@"CrashResponseTitleOK") cancelButtonTitle: BWQuincyLocalize(@"CrashResponseTitleOK")
otherButtonTitles: nil]; otherButtonTitles: nil];
break; break;
case CrashReportStatusAvailable: case CrashReportStatusAvailable:
alertView = [[UIAlertView alloc] initWithTitle: [NSString stringWithFormat:BWQuincyLocalize(@"CrashResponseTitle"), appName ] alertView = [[UIAlertView alloc] initWithTitle: [NSString stringWithFormat:BWQuincyLocalize(@"CrashResponseTitle"), appName ]
message: [NSString stringWithFormat:BWQuincyLocalize(@"CrashResponseAvailable"), appName] message: [NSString stringWithFormat:BWQuincyLocalize(@"CrashResponseAvailable"), appName]
delegate: self delegate: self
cancelButtonTitle: BWQuincyLocalize(@"CrashResponseTitleOK") cancelButtonTitle: BWQuincyLocalize(@"CrashResponseTitleOK")
otherButtonTitles: nil]; otherButtonTitles: nil];
break; break;
default: default:
alertView = nil; alertView = nil;
@ -377,7 +377,7 @@ NSString *BWQuincyLocalize(NSString *stringToken) {
if ([alertView tag] == QuincyKitAlertTypeSend) { if ([alertView tag] == QuincyKitAlertTypeSend) {
switch (buttonIndex) { switch (buttonIndex) {
case 0: case 0:
_sendingInProgress = NO; _sendingInProgress = NO;
[self _cleanCrashReports]; [self _cleanCrashReports];
break; break;
case 1: case 1:
@ -404,7 +404,7 @@ NSString *BWQuincyLocalize(NSString *stringToken) {
if ([elementName isEqualToString:@"result"]) { if ([elementName isEqualToString:@"result"]) {
_contentOfProperty = [NSMutableString string]; _contentOfProperty = [NSMutableString string];
} }
} }
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName { - (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName {
@ -412,12 +412,12 @@ NSString *BWQuincyLocalize(NSString *stringToken) {
elementName = qName; elementName = qName;
} }
// open source implementation // open source implementation
if ([elementName isEqualToString: @"result"]) { if ([elementName isEqualToString: @"result"]) {
if ([_contentOfProperty intValue] > _serverResult) { if ([_contentOfProperty intValue] > _serverResult) {
_serverResult = (CrashReportStatus)[_contentOfProperty intValue]; _serverResult = (CrashReportStatus)[_contentOfProperty intValue];
} else { } else {
CrashReportStatus errorcode = (CrashReportStatus)[_contentOfProperty intValue]; CrashReportStatus errorcode = (CrashReportStatus)[_contentOfProperty intValue];
NSLog(@"CrashReporter ended in error code: %i", errorcode); NSLog(@"CrashReporter ended in error code: %i", errorcode);
} }
} }
@ -458,18 +458,18 @@ NSString *BWQuincyLocalize(NSString *stringToken) {
} }
- (void)_performSendingCrashReports { - (void)_performSendingCrashReports {
NSMutableDictionary *approvedCrashReports = [NSMutableDictionary dictionaryWithDictionary:[[NSUserDefaults standardUserDefaults] dictionaryForKey: kApprovedCrashReports]]; NSMutableDictionary *approvedCrashReports = [NSMutableDictionary dictionaryWithDictionary:[[NSUserDefaults standardUserDefaults] dictionaryForKey: kApprovedCrashReports]];
NSFileManager *fm = [NSFileManager defaultManager]; NSFileManager *fm = [NSFileManager defaultManager];
NSError *error = NULL; NSError *error = NULL;
NSString *userid = @""; NSString *userid = @"";
NSString *contact = @""; NSString *contact = @"";
NSString *description = @""; NSString *description = @"";
if (self.autoSubmitDeviceUDID && [[NSBundle mainBundle] pathForResource:@"embedded" ofType:@"mobileprovision"]) { if (self.autoSubmitDeviceUDID && [[NSBundle mainBundle] pathForResource:@"embedded" ofType:@"mobileprovision"]) {
userid = [self deviceIdentifier]; userid = [self deviceIdentifier];
} else if (self.delegate != nil && [self.delegate respondsToSelector:@selector(crashReportUserID)]) { } else if (self.delegate != nil && [self.delegate respondsToSelector:@selector(crashReportUserID)]) {
userid = [self.delegate crashReportUserID] ?: @""; userid = [self.delegate crashReportUserID] ?: @"";
} }
@ -481,9 +481,9 @@ NSString *BWQuincyLocalize(NSString *stringToken) {
description = [self.delegate crashReportDescription] ?: @""; description = [self.delegate crashReportDescription] ?: @"";
} }
NSMutableString *crashes = nil; NSMutableString *crashes = nil;
_crashIdenticalCurrentVersion = NO; _crashIdenticalCurrentVersion = NO;
for (NSUInteger i=0; i < [_crashFiles count]; i++) { for (NSUInteger i=0; i < [_crashFiles count]; i++) {
NSString *filename = [_crashesDir stringByAppendingPathComponent:[_crashFiles objectAtIndex:i]]; NSString *filename = [_crashesDir stringByAppendingPathComponent:[_crashFiles objectAtIndex:i]];
NSData *crashData = [NSData dataWithContentsOfFile:filename]; NSData *crashData = [NSData dataWithContentsOfFile:filename];
@ -491,87 +491,87 @@ NSString *BWQuincyLocalize(NSString *stringToken) {
if ([crashData length] > 0) { if ([crashData length] > 0) {
PLCrashReport *report = [[[PLCrashReport alloc] initWithData:crashData error:&error] autorelease]; PLCrashReport *report = [[[PLCrashReport alloc] initWithData:crashData error:&error] autorelease];
if (report == nil) { if (report == nil) {
NSLog(@"Could not parse crash report"); NSLog(@"Could not parse crash report");
continue; continue;
} }
NSString *crashLogString = [PLCrashReportTextFormatter stringValueForCrashReport:report withTextFormat:PLCrashReportTextFormatiOS]; NSString *crashLogString = [PLCrashReportTextFormatter stringValueForCrashReport:report withTextFormat:PLCrashReportTextFormatiOS];
if ([report.applicationInfo.applicationVersion compare:[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"]] == NSOrderedSame) { if ([report.applicationInfo.applicationVersion compare:[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"]] == NSOrderedSame) {
_crashIdenticalCurrentVersion = YES; _crashIdenticalCurrentVersion = YES;
} }
if (crashes == nil) { if (crashes == nil) {
crashes = [NSMutableString string]; crashes = [NSMutableString string];
} }
[crashes appendFormat:@"<crash><applicationname>%s</applicationname><bundleidentifier>%@</bundleidentifier><systemversion>%@</systemversion><platform>%@</platform><senderversion>%@</senderversion><version>%@</version><log><![CDATA[%@]]></log><userid>%@</userid><contact>%@</contact><description><![CDATA[%@]]></description></crash>", [crashes appendFormat:@"<crash><applicationname>%s</applicationname><bundleidentifier>%@</bundleidentifier><systemversion>%@</systemversion><platform>%@</platform><senderversion>%@</senderversion><version>%@</version><log><![CDATA[%@]]></log><userid>%@</userid><contact>%@</contact><description><![CDATA[%@]]></description></crash>",
[[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleExecutable"] UTF8String], [[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleExecutable"] UTF8String],
report.applicationInfo.applicationIdentifier, report.applicationInfo.applicationIdentifier,
report.systemInfo.operatingSystemVersion, report.systemInfo.operatingSystemVersion,
[self _getDevicePlatform], [self _getDevicePlatform],
[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"], [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"],
report.applicationInfo.applicationVersion, report.applicationInfo.applicationVersion,
[crashLogString stringByReplacingOccurrencesOfString:@"]]>" withString:@"]]" @"]]><![CDATA[" @">" options:NSLiteralSearch range:NSMakeRange(0,crashLogString.length)], [crashLogString stringByReplacingOccurrencesOfString:@"]]>" withString:@"]]" @"]]><![CDATA[" @">" options:NSLiteralSearch range:NSMakeRange(0,crashLogString.length)],
userid, userid,
contact, contact,
[description stringByReplacingOccurrencesOfString:@"]]>" withString:@"]]" @"]]><![CDATA[" @">" options:NSLiteralSearch range:NSMakeRange(0,description.length)]]; [description stringByReplacingOccurrencesOfString:@"]]>" withString:@"]]" @"]]><![CDATA[" @">" options:NSLiteralSearch range:NSMakeRange(0,description.length)]];
// store this crash report as user approved, so if it fails it will retry automatically // store this crash report as user approved, so if it fails it will retry automatically
[approvedCrashReports setObject:[NSNumber numberWithBool:YES] forKey:[_crashFiles objectAtIndex:i]]; [approvedCrashReports setObject:[NSNumber numberWithBool:YES] forKey:[_crashFiles objectAtIndex:i]];
} else { } else {
// we cannot do anything with this report, so delete it // we cannot do anything with this report, so delete it
[fm removeItemAtPath:filename error:&error]; [fm removeItemAtPath:filename error:&error];
} }
} }
[[NSUserDefaults standardUserDefaults] setObject:approvedCrashReports forKey:kApprovedCrashReports]; [[NSUserDefaults standardUserDefaults] setObject:approvedCrashReports forKey:kApprovedCrashReports];
[[NSUserDefaults standardUserDefaults] synchronize]; [[NSUserDefaults standardUserDefaults] synchronize];
if (crashes != nil) { if (crashes != nil) {
[self _postXML:[NSString stringWithFormat:@"<crashes>%@</crashes>", crashes] [self _postXML:[NSString stringWithFormat:@"<crashes>%@</crashes>", crashes]
toURL:[NSURL URLWithString:self.submissionURL]]; toURL:[NSURL URLWithString:self.submissionURL]];
} }
} }
- (void)_cleanCrashReports { - (void)_cleanCrashReports {
NSError *error = NULL; NSError *error = NULL;
NSFileManager *fm = [NSFileManager defaultManager]; NSFileManager *fm = [NSFileManager defaultManager];
for (NSUInteger i=0; i < [_crashFiles count]; i++) { for (NSUInteger i=0; i < [_crashFiles count]; i++) {
[fm removeItemAtPath:[_crashesDir stringByAppendingPathComponent:[_crashFiles objectAtIndex:i]] error:&error]; [fm removeItemAtPath:[_crashesDir stringByAppendingPathComponent:[_crashFiles objectAtIndex:i]] error:&error];
} }
[_crashFiles removeAllObjects]; [_crashFiles removeAllObjects];
[[NSUserDefaults standardUserDefaults] setObject:nil forKey:kApprovedCrashReports]; [[NSUserDefaults standardUserDefaults] setObject:nil forKey:kApprovedCrashReports];
[[NSUserDefaults standardUserDefaults] synchronize]; [[NSUserDefaults standardUserDefaults] synchronize];
} }
- (void)_sendCrashReports { - (void)_sendCrashReports {
// send it to the next runloop // send it to the next runloop
[self performSelector:@selector(_performSendingCrashReports) withObject:nil afterDelay:0.0f]; [self performSelector:@selector(_performSendingCrashReports) withObject:nil afterDelay:0.0f];
} }
- (void)_checkForFeedbackStatus { - (void)_checkForFeedbackStatus {
NSMutableURLRequest *request = nil; NSMutableURLRequest *request = nil;
request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:[NSString stringWithFormat:@"%@api/2/apps/%@/crashes/%@", request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:[NSString stringWithFormat:@"%@api/2/apps/%@/crashes/%@",
self.submissionURL, self.submissionURL,
[self.appIdentifier stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding], [self.appIdentifier stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding],
_feedbackRequestID _feedbackRequestID
] ]
]]; ]];
[request setCachePolicy: NSURLRequestReloadIgnoringLocalCacheData]; [request setCachePolicy: NSURLRequestReloadIgnoringLocalCacheData];
[request setValue:@"Quincy/iOS" forHTTPHeaderField:@"User-Agent"]; [request setValue:@"Quincy/iOS" forHTTPHeaderField:@"User-Agent"];
[request setValue:@"gzip" forHTTPHeaderField:@"Accept-Encoding"]; [request setValue:@"gzip" forHTTPHeaderField:@"Accept-Encoding"];
[request setTimeoutInterval: 15]; [request setTimeoutInterval: 15];
[request setHTTPMethod:@"GET"]; [request setHTTPMethod:@"GET"];
_serverResult = CrashReportStatusUnknown; _serverResult = CrashReportStatusUnknown;
_statusCode = 200; _statusCode = 200;
@ -587,21 +587,21 @@ NSString *BWQuincyLocalize(NSString *stringToken) {
- (void)_postXML:(NSString*)xml toURL:(NSURL*)url { - (void)_postXML:(NSString*)xml toURL:(NSURL*)url {
NSMutableURLRequest *request = nil; NSMutableURLRequest *request = nil;
NSString *boundary = @"----FOO"; NSString *boundary = @"----FOO";
if (self.appIdentifier) { if (self.appIdentifier) {
request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:[NSString stringWithFormat:@"%@api/2/apps/%@/crashes", request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:[NSString stringWithFormat:@"%@api/2/apps/%@/crashes",
self.submissionURL, self.submissionURL,
[self.appIdentifier stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding] [self.appIdentifier stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]
] ]
]]; ]];
} else { } else {
request = [NSMutableURLRequest requestWithURL:url]; request = [NSMutableURLRequest requestWithURL:url];
} }
[request setCachePolicy: NSURLRequestReloadIgnoringLocalCacheData]; [request setCachePolicy: NSURLRequestReloadIgnoringLocalCacheData];
[request setValue:@"Quincy/iOS" forHTTPHeaderField:@"User-Agent"]; [request setValue:@"Quincy/iOS" forHTTPHeaderField:@"User-Agent"];
[request setValue:@"gzip" forHTTPHeaderField:@"Accept-Encoding"]; [request setValue:@"gzip" forHTTPHeaderField:@"Accept-Encoding"];
[request setTimeoutInterval: 15]; [request setTimeoutInterval: 15];
[request setHTTPMethod:@"POST"]; [request setHTTPMethod:@"POST"];
NSString *contentType = [NSString stringWithFormat:@"multipart/form-data; boundary=%@", boundary]; NSString *contentType = [NSString stringWithFormat:@"multipart/form-data; boundary=%@", boundary];
@ -609,16 +609,16 @@ NSString *BWQuincyLocalize(NSString *stringToken) {
NSMutableData *postBody = [NSMutableData data]; NSMutableData *postBody = [NSMutableData data];
[postBody appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]]; [postBody appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
if (self.appIdentifier) { if (self.appIdentifier) {
[postBody appendData:[@"Content-Disposition: form-data; name=\"xml\"; filename=\"crash.xml\"\r\n" dataUsingEncoding:NSUTF8StringEncoding]]; [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]]; [postBody appendData:[[NSString stringWithFormat:@"Content-Type: text/xml\r\n\r\n"] dataUsingEncoding:NSUTF8StringEncoding]];
} else { } else {
[postBody appendData:[@"Content-Disposition: form-data; name=\"xmlstring\"\r\n\r\n" dataUsingEncoding:NSUTF8StringEncoding]]; [postBody appendData:[@"Content-Disposition: form-data; name=\"xmlstring\"\r\n\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
} }
[postBody appendData:[xml dataUsingEncoding:NSUTF8StringEncoding]]; [postBody appendData:[xml dataUsingEncoding:NSUTF8StringEncoding]];
[postBody appendData:[[NSString stringWithFormat:@"\r\n--%@--\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]]; [postBody appendData:[[NSString stringWithFormat:@"\r\n--%@--\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
[request setHTTPBody:postBody]; [request setHTTPBody:postBody];
_serverResult = CrashReportStatusUnknown; _serverResult = CrashReportStatusUnknown;
_statusCode = 200; _statusCode = 200;
@ -631,10 +631,10 @@ NSString *BWQuincyLocalize(NSString *stringToken) {
} }
_urlConnection = [[NSURLConnection alloc] initWithRequest:request delegate:self]; _urlConnection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
if (!_urlConnection) { if (!_urlConnection) {
_sendingInProgress = NO; _sendingInProgress = NO;
} }
} }
#pragma mark NSURLConnection Delegate #pragma mark NSURLConnection Delegate
@ -652,70 +652,70 @@ NSString *BWQuincyLocalize(NSString *stringToken) {
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error { - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
[_responseData release]; [_responseData release];
_responseData = nil; _responseData = nil;
_urlConnection = nil; _urlConnection = nil;
if (self.delegate != nil && [self.delegate respondsToSelector:@selector(connectionClosed)]) { if (self.delegate != nil && [self.delegate respondsToSelector:@selector(connectionClosed)]) {
[self.delegate connectionClosed]; [self.delegate connectionClosed];
} }
_sendingInProgress = NO; _sendingInProgress = NO;
} }
- (void)connectionDidFinishLoading:(NSURLConnection *)connection { - (void)connectionDidFinishLoading:(NSURLConnection *)connection {
if (_statusCode >= 200 && _statusCode < 400) { if (_statusCode >= 200 && _statusCode < 400) {
[self _cleanCrashReports]; [self _cleanCrashReports];
_feedbackRequestID = nil; _feedbackRequestID = nil;
if (self.appIdentifier) { if (self.appIdentifier) {
// HockeyApp uses PList XML format // HockeyApp uses PList XML format
NSMutableDictionary *response = [NSPropertyListSerialization propertyListFromData:_responseData NSMutableDictionary *response = [NSPropertyListSerialization propertyListFromData:_responseData
mutabilityOption:NSPropertyListMutableContainersAndLeaves mutabilityOption:NSPropertyListMutableContainersAndLeaves
format:nil format:nil
errorDescription:NULL]; errorDescription:NULL];
_serverResult = (CrashReportStatus)[[response objectForKey:@"status"] intValue]; _serverResult = (CrashReportStatus)[[response objectForKey:@"status"] intValue];
if ([response objectForKey:@"id"]) { if ([response objectForKey:@"id"]) {
_feedbackRequestID = [[NSString alloc] initWithString:[response objectForKey:@"id"]]; _feedbackRequestID = [[NSString alloc] initWithString:[response objectForKey:@"id"]];
_feedbackDelayInterval = [[response objectForKey:@"delay"] floatValue]; _feedbackDelayInterval = [[response objectForKey:@"delay"] floatValue];
if (_feedbackDelayInterval > 0) if (_feedbackDelayInterval > 0)
_feedbackDelayInterval *= 0.01; _feedbackDelayInterval *= 0.01;
} }
} else { } else {
NSXMLParser *parser = [[NSXMLParser alloc] initWithData:_responseData]; NSXMLParser *parser = [[NSXMLParser alloc] initWithData:_responseData];
// Set self as the delegate of the parser so that it will receive the parser delegate methods callbacks. // Set self as the delegate of the parser so that it will receive the parser delegate methods callbacks.
[parser setDelegate:self]; [parser setDelegate:self];
// Depending on the XML document you're parsing, you may want to enable these features of NSXMLParser. // Depending on the XML document you're parsing, you may want to enable these features of NSXMLParser.
[parser setShouldProcessNamespaces:NO]; [parser setShouldProcessNamespaces:NO];
[parser setShouldReportNamespacePrefixes:NO]; [parser setShouldReportNamespacePrefixes:NO];
[parser setShouldResolveExternalEntities:NO]; [parser setShouldResolveExternalEntities:NO];
[parser parse]; [parser parse];
[parser release]; [parser release];
} }
if ([self isFeedbackActivated]) { if ([self isFeedbackActivated]) {
// only proceed if the server did not report any problem // only proceed if the server did not report any problem
if ((self.appIdentifier) && (_serverResult == CrashReportStatusQueued)) { if ((self.appIdentifier) && (_serverResult == CrashReportStatusQueued)) {
// the report is still in the queue // the report is still in the queue
if (_feedbackRequestID) { if (_feedbackRequestID) {
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(_checkForFeedbackStatus) object:nil]; [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(_checkForFeedbackStatus) object:nil];
[self performSelector:@selector(_checkForFeedbackStatus) withObject:nil afterDelay:_feedbackDelayInterval]; [self performSelector:@selector(_checkForFeedbackStatus) withObject:nil afterDelay:_feedbackDelayInterval];
}
} else {
[self showCrashStatusMessage];
}
} }
} else {
[self showCrashStatusMessage];
}
}
} }
[_responseData release]; [_responseData release];
_responseData = nil; _responseData = nil;
_urlConnection = nil; _urlConnection = nil;
if (self.delegate != nil && [self.delegate respondsToSelector:@selector(connectionClosed)]) { if (self.delegate != nil && [self.delegate respondsToSelector:@selector(connectionClosed)]) {
[self.delegate connectionClosed]; [self.delegate connectionClosed];
} }
_sendingInProgress = NO; _sendingInProgress = NO;
} }
#pragma mark PLCrashReporter #pragma mark PLCrashReporter
@ -727,22 +727,22 @@ NSString *BWQuincyLocalize(NSString *stringToken) {
PLCrashReporter *crashReporter = [PLCrashReporter sharedReporter]; PLCrashReporter *crashReporter = [PLCrashReporter sharedReporter];
NSError *error = NULL; 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) { if (_analyzerStarted == 0) {
// mark the start of the routine // mark the start of the routine
_analyzerStarted = 1; _analyzerStarted = 1;
[[NSUserDefaults standardUserDefaults] setValue:[NSNumber numberWithInt:_analyzerStarted] forKey:kQuincyKitAnalyzerStarted]; [[NSUserDefaults standardUserDefaults] setValue:[NSNumber numberWithInt:_analyzerStarted] forKey:kQuincyKitAnalyzerStarted];
[[NSUserDefaults standardUserDefaults] synchronize]; [[NSUserDefaults standardUserDefaults] synchronize];
// Try loading the crash report // Try loading the crash report
_crashData = [[NSData alloc] initWithData:[crashReporter loadPendingCrashReportDataAndReturnError: &error]]; _crashData = [[NSData alloc] initWithData:[crashReporter loadPendingCrashReportDataAndReturnError: &error]];
NSString *cacheFilename = [NSString stringWithFormat: @"%.0f", [NSDate timeIntervalSinceReferenceDate]]; NSString *cacheFilename = [NSString stringWithFormat: @"%.0f", [NSDate timeIntervalSinceReferenceDate]];
if (_crashData == nil) { if (_crashData == nil) {
NSLog(@"Could not load crash report: %@", error); NSLog(@"Could not load crash report: %@", error);
} else { } else {
[_crashData writeToFile:[_crashesDir stringByAppendingPathComponent: cacheFilename] atomically:YES]; [_crashData writeToFile:[_crashesDir stringByAppendingPathComponent: cacheFilename] atomically:YES];
} }
} }
@ -750,8 +750,8 @@ NSString *BWQuincyLocalize(NSString *stringToken) {
// mark the end of the routine // mark the end of the routine
_analyzerStarted = 0; _analyzerStarted = 0;
[[NSUserDefaults standardUserDefaults] setValue:[NSNumber numberWithInt:_analyzerStarted] forKey:kQuincyKitAnalyzerStarted]; [[NSUserDefaults standardUserDefaults] setValue:[NSNumber numberWithInt:_analyzerStarted] forKey:kQuincyKitAnalyzerStarted];
[[NSUserDefaults standardUserDefaults] synchronize]; [[NSUserDefaults standardUserDefaults] synchronize];
[crashReporter purgePendingCrashReport]; [crashReporter purgePendingCrashReport];
return; return;
} }

View File

@ -29,22 +29,22 @@
@implementation NSString (HockeyAdditions) @implementation NSString (HockeyAdditions)
- (NSString *)bw_URLEncodedString { - (NSString *)bw_URLEncodedString {
NSString *result = (NSString *)CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, NSString *result = (NSString *)CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault,
(CFStringRef)self, (CFStringRef)self,
NULL, NULL,
CFSTR("!*'();:@&=+$,/?%#[]"), CFSTR("!*'();:@&=+$,/?%#[]"),
kCFStringEncodingUTF8); kCFStringEncodingUTF8);
[result autorelease]; [result autorelease];
return result; return result;
} }
- (NSString*)bw_URLDecodedString { - (NSString*)bw_URLDecodedString {
NSString *result = (NSString *)CFURLCreateStringByReplacingPercentEscapesUsingEncoding(kCFAllocatorDefault, NSString *result = (NSString *)CFURLCreateStringByReplacingPercentEscapesUsingEncoding(kCFAllocatorDefault,
(CFStringRef)self, (CFStringRef)self,
CFSTR(""), CFSTR(""),
kCFStringEncodingUTF8); kCFStringEncodingUTF8);
[result autorelease]; [result autorelease];
return result; return result;
} }
- (NSComparisonResult)versionCompare:(NSString *)other - (NSComparisonResult)versionCompare:(NSString *)other

View File

@ -26,12 +26,12 @@
#import <UIKit/UIKit.h> #import <UIKit/UIKit.h>
@interface PSAppStoreHeader : UIView { @interface PSAppStoreHeader : UIView {
NSString *headerLabel_; NSString *headerLabel_;
NSString *middleHeaderLabel_; NSString *middleHeaderLabel_;
NSString *subHeaderLabel; NSString *subHeaderLabel;
UIImage *iconImage_; UIImage *iconImage_;
UIImage *reflectedImage_; UIImage *reflectedImage_;
} }
@property (nonatomic, copy) NSString *headerLabel; @property (nonatomic, copy) NSString *headerLabel;

View File

@ -50,20 +50,20 @@
#pragma mark NSObject #pragma mark NSObject
- (id)initWithFrame:(CGRect)frame { - (id)initWithFrame:(CGRect)frame {
if ((self = [super initWithFrame:frame])) { if ((self = [super initWithFrame:frame])) {
self.autoresizingMask = UIViewAutoresizingFlexibleWidth; self.autoresizingMask = UIViewAutoresizingFlexibleWidth;
self.backgroundColor = kLightGrayColor; self.backgroundColor = kLightGrayColor;
} }
return self; return self;
} }
- (void)dealloc { - (void)dealloc {
[headerLabel_ release]; [headerLabel_ release];
[middleHeaderLabel_ release]; [middleHeaderLabel_ release];
[subHeaderLabel release]; [subHeaderLabel release];
[iconImage_ release]; [iconImage_ release];
[super dealloc]; [super dealloc];
} }
@ -72,56 +72,56 @@
#pragma mark UIView #pragma mark UIView
- (void)drawRect:(CGRect)rect { - (void)drawRect:(CGRect)rect {
CGRect bounds = self.bounds; CGRect bounds = self.bounds;
CGFloat globalWidth = self.frame.size.width; CGFloat globalWidth = self.frame.size.width;
CGContextRef context = UIGraphicsGetCurrentContext(); CGContextRef context = UIGraphicsGetCurrentContext();
// draw the gradient // draw the gradient
NSArray *colors = [NSArray arrayWithObjects:(id)kDarkGrayColor.CGColor, (id)kLightGrayColor.CGColor, nil]; 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}); CGGradientRef gradient = CGGradientCreateWithColors(CGColorGetColorSpace((CGColorRef)[colors objectAtIndex:0]), (CFArrayRef)colors, (CGFloat[2]){0, 1});
CGPoint top = CGPointMake(CGRectGetMidX(bounds), bounds.origin.y); CGPoint top = CGPointMake(CGRectGetMidX(bounds), bounds.origin.y);
CGPoint bottom = CGPointMake(CGRectGetMidX(bounds), CGRectGetMaxY(bounds)-kReflectionHeight); CGPoint bottom = CGPointMake(CGRectGetMidX(bounds), CGRectGetMaxY(bounds)-kReflectionHeight);
CGContextDrawLinearGradient(context, gradient, top, bottom, 0); CGContextDrawLinearGradient(context, gradient, top, bottom, 0);
CGGradientRelease(gradient); CGGradientRelease(gradient);
// draw header name // draw header name
UIColor *mainTextColor = BW_RGBCOLOR(0,0,0); UIColor *mainTextColor = BW_RGBCOLOR(0,0,0);
UIColor *secondaryTextColor = BW_RGBCOLOR(48,48,48); UIColor *secondaryTextColor = BW_RGBCOLOR(48,48,48);
UIFont *mainFont = [UIFont boldSystemFontOfSize:20]; UIFont *mainFont = [UIFont boldSystemFontOfSize:20];
UIFont *secondaryFont = [UIFont boldSystemFontOfSize:12]; UIFont *secondaryFont = [UIFont boldSystemFontOfSize:12];
UIFont *smallFont = [UIFont systemFontOfSize:12]; UIFont *smallFont = [UIFont systemFontOfSize:12];
float myColorValues[] = {255, 255, 255, .6}; float myColorValues[] = {255, 255, 255, .6};
CGColorSpaceRef myColorSpace = CGColorSpaceCreateDeviceRGB(); CGColorSpaceRef myColorSpace = CGColorSpaceCreateDeviceRGB();
CGColorRef myColor = CGColorCreate(myColorSpace, myColorValues); CGColorRef myColor = CGColorCreate(myColorSpace, myColorValues);
// icon // icon
[iconImage_ drawAtPoint:CGPointMake(kImageMargin, kImageMargin)]; [iconImage_ drawAtPoint:CGPointMake(kImageMargin, kImageMargin)];
[reflectedImage_ drawAtPoint:CGPointMake(kImageMargin, kImageMargin+kImageHeight)]; [reflectedImage_ drawAtPoint:CGPointMake(kImageMargin, kImageMargin+kImageHeight)];
// shadows are a beast // shadows are a beast
NSInteger shadowOffset = 2; NSInteger shadowOffset = 2;
BW_IF_IOS4_OR_GREATER(if([[UIScreen mainScreen] scale] == 2) shadowOffset = 1;) 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_IOS5_OR_GREATER(shadowOffset = 1;) // iOS5 changes this - again!
BW_IF_3_2_OR_GREATER(CGContextSetShadowWithColor(context, CGSizeMake(shadowOffset, shadowOffset), 0, myColor);) 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);) BW_IF_PRE_3_2(shadowOffset=1;CGContextSetShadowWithColor(context, CGSizeMake(shadowOffset, -shadowOffset), 0, myColor);)
[mainTextColor set]; [mainTextColor set];
[headerLabel_ drawInRect:CGRectMake(kTextRow, kImageMargin, globalWidth-kTextRow, 20) withFont:mainFont lineBreakMode:UILineBreakModeTailTruncation]; [headerLabel_ drawInRect:CGRectMake(kTextRow, kImageMargin, globalWidth-kTextRow, 20) withFont:mainFont lineBreakMode:UILineBreakModeTailTruncation];
// middle // middle
[secondaryTextColor set]; [secondaryTextColor set];
[middleHeaderLabel_ drawInRect:CGRectMake(kTextRow, kImageMargin + 25, globalWidth-kTextRow, 20) withFont:secondaryFont lineBreakMode:UILineBreakModeTailTruncation]; [middleHeaderLabel_ drawInRect:CGRectMake(kTextRow, kImageMargin + 25, globalWidth-kTextRow, 20) withFont:secondaryFont lineBreakMode:UILineBreakModeTailTruncation];
CGContextSetShadowWithColor(context, CGSizeZero, 0, nil); CGContextSetShadowWithColor(context, CGSizeZero, 0, nil);
// sub // sub
[secondaryTextColor set]; [secondaryTextColor set];
[subHeaderLabel drawAtPoint:CGPointMake(kTextRow, kImageMargin+kImageHeight-12) forWidth:globalWidth-kTextRow withFont:smallFont lineBreakMode:UILineBreakModeTailTruncation]; [subHeaderLabel drawAtPoint:CGPointMake(kTextRow, kImageMargin+kImageHeight-12) forWidth:globalWidth-kTextRow withFont:smallFont lineBreakMode:UILineBreakModeTailTruncation];
CGColorRelease(myColor); CGColorRelease(myColor);
CGColorSpaceRelease(myColorSpace); CGColorSpaceRelease(myColorSpace);
} }
/////////////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////////////
@ -129,45 +129,45 @@
#pragma mark Properties #pragma mark Properties
- (void)setHeaderLabel:(NSString *)anHeaderLabel { - (void)setHeaderLabel:(NSString *)anHeaderLabel {
if (headerLabel_ != anHeaderLabel) { if (headerLabel_ != anHeaderLabel) {
[headerLabel_ release]; [headerLabel_ release];
headerLabel_ = [anHeaderLabel copy]; headerLabel_ = [anHeaderLabel copy];
[self setNeedsDisplay]; [self setNeedsDisplay];
} }
} }
- (void)setMiddleHeaderLabel:(NSString *)aMiddleHeaderLabel { - (void)setMiddleHeaderLabel:(NSString *)aMiddleHeaderLabel {
if (middleHeaderLabel_ != aMiddleHeaderLabel) { if (middleHeaderLabel_ != aMiddleHeaderLabel) {
[middleHeaderLabel_ release]; [middleHeaderLabel_ release];
middleHeaderLabel_ = [aMiddleHeaderLabel copy]; middleHeaderLabel_ = [aMiddleHeaderLabel copy];
[self setNeedsDisplay]; [self setNeedsDisplay];
} }
} }
- (void)setSubHeaderLabel:(NSString *)aSubHeaderLabel { - (void)setSubHeaderLabel:(NSString *)aSubHeaderLabel {
if (subHeaderLabel != aSubHeaderLabel) { if (subHeaderLabel != aSubHeaderLabel) {
[subHeaderLabel release]; [subHeaderLabel release];
subHeaderLabel = [aSubHeaderLabel copy]; subHeaderLabel = [aSubHeaderLabel copy];
[self setNeedsDisplay]; [self setNeedsDisplay];
} }
} }
- (void)setIconImage:(UIImage *)anIconImage { - (void)setIconImage:(UIImage *)anIconImage {
if (iconImage_ != anIconImage) { if (iconImage_ != anIconImage) {
[iconImage_ release]; [iconImage_ release];
// scale, make borders and reflection // scale, make borders and reflection
iconImage_ = [anIconImage bw_imageToFitSize:CGSizeMake(kImageHeight, kImageHeight) honorScaleFactor:YES]; iconImage_ = [anIconImage bw_imageToFitSize:CGSizeMake(kImageHeight, kImageHeight) honorScaleFactor:YES];
iconImage_ = [[iconImage_ bw_roundedCornerImage:kImageBorderRadius borderSize:0.0] retain]; iconImage_ = [[iconImage_ bw_roundedCornerImage:kImageBorderRadius borderSize:0.0] retain];
// create reflected image // create reflected image
[reflectedImage_ release]; [reflectedImage_ release];
reflectedImage_ = nil; reflectedImage_ = nil;
if (anIconImage) { if (anIconImage) {
reflectedImage_ = [[iconImage_ bw_reflectedImageWithHeight:kReflectionHeight fromAlpha:0.5 toAlpha:0.0] retain]; reflectedImage_ = [[iconImage_ bw_reflectedImageWithHeight:kReflectionHeight fromAlpha:0.5 toAlpha:0.0] retain];
}
[self setNeedsDisplay];
} }
[self setNeedsDisplay];
}
} }
@end @end

View File

@ -28,10 +28,10 @@
// defines a button action set (data container) // defines a button action set (data container)
@interface PSStoreButtonData : NSObject { @interface PSStoreButtonData : NSObject {
CGPoint customPadding_; CGPoint customPadding_;
NSString *label_; NSString *label_;
NSArray *colors_; NSArray *colors_;
BOOL enabled_; BOOL enabled_;
} }
+ (id)dataWithLabel:(NSString*)aLabel colors:(NSArray*)aColors enabled:(BOOL)flag; + (id)dataWithLabel:(NSString*)aLabel colors:(NSArray*)aColors enabled:(BOOL)flag;
@ -52,11 +52,11 @@
// Simulate the Paymeny-Button from the AppStore // Simulate the Paymeny-Button from the AppStore
// The interface is flexible, so there is now fixed order // The interface is flexible, so there is now fixed order
@interface PSStoreButton : UIButton { @interface PSStoreButton : UIButton {
PSStoreButtonData *buttonData_; PSStoreButtonData *buttonData_;
id<PSStoreButtonDelegate> buttonDelegate_; id<PSStoreButtonDelegate> buttonDelegate_;
CAGradientLayer *gradient_; CAGradientLayer *gradient_;
CGPoint customPadding_; CGPoint customPadding_;
} }
- (id)initWithFrame:(CGRect)frame; - (id)initWithFrame:(CGRect)frame;

View File

@ -50,23 +50,23 @@
#pragma mark NSObject #pragma mark NSObject
- (id)initWithLabel:(NSString*)aLabel colors:(NSArray*)aColors enabled:(BOOL)flag { - (id)initWithLabel:(NSString*)aLabel colors:(NSArray*)aColors enabled:(BOOL)flag {
if ((self = [super init])) { if ((self = [super init])) {
self.label = aLabel; self.label = aLabel;
self.colors = aColors; self.colors = aColors;
self.enabled = flag; self.enabled = flag;
} }
return self; return self;
} }
+ (id)dataWithLabel:(NSString*)aLabel colors:(NSArray*)aColors enabled:(BOOL)flag { + (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 { - (void)dealloc {
[label_ release]; [label_ release];
[colors_ release]; [colors_ release];
[super dealloc]; [super dealloc];
} }
@end @end
@ -88,86 +88,86 @@
#pragma mark private #pragma mark private
- (void)touchedUpOutside:(id)sender { - (void)touchedUpOutside:(id)sender {
PSLog(@"touched outside..."); PSLog(@"touched outside...");
} }
- (void)buttonPressed:(id)sender { - (void)buttonPressed:(id)sender {
PSLog(@"calling delegate:storeButtonFired for %@", sender); PSLog(@"calling delegate:storeButtonFired for %@", sender);
[buttonDelegate_ storeButtonFired:self]; [buttonDelegate_ storeButtonFired:self];
} }
- (void)animationDidStop:(NSString *)animationID finished:(NSNumber *)finished context:(void *)context { - (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) // show text again, but only if animation did finish (or else another animation is on the way)
if ([finished boolValue]) { if ([finished boolValue]) {
[self setTitle:self.buttonData.label forState:UIControlStateNormal]; [self setTitle:self.buttonData.label forState:UIControlStateNormal];
} }
} }
- (void)updateButtonAnimated:(BOOL)animated { - (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) { if (animated) {
// hide text, then start animation [CATransaction setAnimationDuration:kDefaultButtonAnimationTime];
[self setTitle:@"" forState:UIControlStateNormal]; [CATransaction setAnimationTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]];
[UIView beginAnimations:@"storeButtonUpdate" context:nil];
[UIView setAnimationBeginsFromCurrentState:YES];
[UIView setAnimationDuration:kDefaultButtonAnimationTime];
[UIView setAnimationDelegate:self];
[UIView setAnimationDidStopSelector:@selector(animationDidStop:finished:context:)];
}else { }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; CGRect newFrame = aLayer.frame;
gradient_.colors = self.buttonData.colors; newFrame.size.width = sizeThatFits.width;
aLayer.frame = newFrame;
// show white or gray text, depending on the state
if (self.buttonData.isEnabled) { [CATransaction commit];
[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];
} }
// set outer frame changes // set outer frame changes
self.titleEdgeInsets = UIEdgeInsetsMake(2.0, self.titleEdgeInsets.left, 0.0, 0.0); self.titleEdgeInsets = UIEdgeInsetsMake(2.0, self.titleEdgeInsets.left, 0.0, 0.0);
[self alignToSuperview]; [self alignToSuperview];
if (animated) { if (animated) {
[UIView commitAnimations]; [UIView commitAnimations];
} }
} }
- (void)alignToSuperview { - (void)alignToSuperview {
[self sizeToFit]; [self sizeToFit];
if (self.superview) { if (self.superview) {
CGRect cr = self.frame; CGRect cr = self.frame;
cr.origin.y = customPadding_.y; cr.origin.y = customPadding_.y;
cr.origin.x = self.superview.frame.size.width - cr.size.width - customPadding_.x * 2; cr.origin.x = self.superview.frame.size.width - cr.size.width - customPadding_.x * 2;
self.frame = cr; self.frame = cr;
} }
} }
/////////////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////////////
@ -175,55 +175,55 @@
#pragma mark NSObject #pragma mark NSObject
- (id)initWithFrame:(CGRect)frame { - (id)initWithFrame:(CGRect)frame {
if ((self = [super initWithFrame:frame])) { if ((self = [super initWithFrame:frame])) {
self.layer.needsDisplayOnBoundsChange = YES; self.layer.needsDisplayOnBoundsChange = YES;
// setup title label // setup title label
[self.titleLabel setFont:[UIFont boldSystemFontOfSize:13.0]]; [self.titleLabel setFont:[UIFont boldSystemFontOfSize:13.0]];
// register for touch events // register for touch events
[self addTarget:self action:@selector(touchedUpOutside:) forControlEvents:UIControlEventTouchUpOutside]; [self addTarget:self action:@selector(touchedUpOutside:) forControlEvents:UIControlEventTouchUpOutside];
[self addTarget:self action:@selector(buttonPressed:) forControlEvents:UIControlEventTouchUpInside]; [self addTarget:self action:@selector(buttonPressed:) forControlEvents:UIControlEventTouchUpInside];
// border layers for more sex! // border layers for more sex!
CAGradientLayer *bevelLayer = [CAGradientLayer layer]; CAGradientLayer *bevelLayer = [CAGradientLayer layer];
bevelLayer.colors = [NSArray arrayWithObjects:(id)[[UIColor colorWithWhite:0.4 alpha:1.0] CGColor], [[UIColor whiteColor] CGColor], nil]; 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.frame = CGRectMake(0.0, 0.0, CGRectGetWidth(frame), CGRectGetHeight(frame));
bevelLayer.cornerRadius = 2.5; bevelLayer.cornerRadius = 2.5;
bevelLayer.needsDisplayOnBoundsChange = YES; bevelLayer.needsDisplayOnBoundsChange = YES;
[self.layer addSublayer:bevelLayer]; [self.layer addSublayer:bevelLayer];
CAGradientLayer *topBorderLayer = [CAGradientLayer layer]; CAGradientLayer *topBorderLayer = [CAGradientLayer layer];
topBorderLayer.colors = [NSArray arrayWithObjects:(id)[[UIColor darkGrayColor] CGColor], [[UIColor lightGrayColor] CGColor], nil]; 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.frame = CGRectMake(0.5, 0.5, CGRectGetWidth(frame) - 1.0, CGRectGetHeight(frame) - 1.0);
topBorderLayer.cornerRadius = 2.6; topBorderLayer.cornerRadius = 2.6;
topBorderLayer.needsDisplayOnBoundsChange = YES; topBorderLayer.needsDisplayOnBoundsChange = YES;
[self.layer addSublayer:topBorderLayer]; [self.layer addSublayer:topBorderLayer];
// main gradient layer // main gradient layer
gradient_ = [[CAGradientLayer layer] retain]; 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_.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_.frame = CGRectMake(0.75, 0.75, CGRectGetWidth(frame) - 1.5, CGRectGetHeight(frame) - 1.5);
gradient_.cornerRadius = 2.5; gradient_.cornerRadius = 2.5;
gradient_.needsDisplayOnBoundsChange = YES; gradient_.needsDisplayOnBoundsChange = YES;
[self.layer addSublayer:gradient_]; [self.layer addSublayer:gradient_];
[self bringSubviewToFront:self.titleLabel]; [self bringSubviewToFront:self.titleLabel];
} }
return self; return self;
} }
- (id)initWithPadding:(CGPoint)padding { - (id)initWithPadding:(CGPoint)padding {
if ((self = [self initWithFrame:CGRectMake(0, 0, 40, PS_MIN_HEIGHT)])) { if ((self = [self initWithFrame:CGRectMake(0, 0, 40, PS_MIN_HEIGHT)])) {
customPadding_ = padding; customPadding_ = padding;
} }
return self; return self;
} }
- (void)dealloc { - (void)dealloc {
[buttonData_ release]; [buttonData_ release];
[gradient_ release]; [gradient_ release];
[super dealloc]; [super dealloc];
} }
/////////////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////////////
@ -231,25 +231,25 @@
#pragma mark UIView #pragma mark UIView
- (CGSize)sizeThatFits:(CGSize)size { - (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]; CGSize newSize = [self.buttonData.label sizeWithFont:self.titleLabel.font constrainedToSize:constr lineBreakMode:UILineBreakModeMiddleTruncation];
CGFloat newWidth = newSize.width + (PS_PADDING * 2); CGFloat newWidth = newSize.width + (PS_PADDING * 2);
CGFloat newHeight = PS_MIN_HEIGHT > newSize.height ? PS_MIN_HEIGHT : newSize.height; CGFloat newHeight = PS_MIN_HEIGHT > newSize.height ? PS_MIN_HEIGHT : newSize.height;
CGSize sizeThatFits = CGSizeMake(newWidth, newHeight); CGSize sizeThatFits = CGSizeMake(newWidth, newHeight);
return sizeThatFits; return sizeThatFits;
} }
- (void)setFrame:(CGRect)aRect { - (void)setFrame:(CGRect)aRect {
[super setFrame:aRect]; [super setFrame:aRect];
// copy frame changes to sublayers (but watch out for NaN's) // copy frame changes to sublayers (but watch out for NaN's)
for (CALayer *aLayer in self.layer.sublayers) { for (CALayer *aLayer in self.layer.sublayers) {
CGRect rect = aLayer.frame; CGRect rect = aLayer.frame;
rect.size.width = self.frame.size.width; rect.size.width = self.frame.size.width;
rect.size.height = self.frame.size.height; rect.size.height = self.frame.size.height;
aLayer.frame = rect; aLayer.frame = rect;
[aLayer layoutIfNeeded]; [aLayer layoutIfNeeded];
} }
} }
@ -258,16 +258,16 @@
#pragma mark Properties #pragma mark Properties
- (void)setButtonData:(PSStoreButtonData *)aButtonData { - (void)setButtonData:(PSStoreButtonData *)aButtonData {
[self setButtonData:aButtonData animated:NO]; [self setButtonData:aButtonData animated:NO];
} }
- (void)setButtonData:(PSStoreButtonData *)aButtonData animated:(BOOL)animated { - (void)setButtonData:(PSStoreButtonData *)aButtonData animated:(BOOL)animated {
if (buttonData_ != aButtonData) { if (buttonData_ != aButtonData) {
[buttonData_ release]; [buttonData_ release];
buttonData_ = [aButtonData retain]; buttonData_ = [aButtonData retain];
} }
[self updateButtonAnimated:animated]; [self updateButtonAnimated:animated];
} }
/////////////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////////////
@ -275,21 +275,21 @@
#pragma mark Static #pragma mark Static
+ (NSArray *)appStoreGreenColor { + (NSArray *)appStoreGreenColor {
return [NSArray arrayWithObjects:(id) return [NSArray arrayWithObjects:(id)
[UIColor colorWithRed:0.482 green:0.674 blue:0.406 alpha:1.000].CGColor, [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]; [UIColor colorWithRed:0.299 green:0.606 blue:0.163 alpha:1.000].CGColor, nil];
} }
+ (NSArray *)appStoreBlueColor { + (NSArray *)appStoreBlueColor {
return [NSArray arrayWithObjects:(id) return [NSArray arrayWithObjects:(id)
[UIColor colorWithRed:0.306 green:0.380 blue:0.547 alpha:1.000].CGColor, [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]; [UIColor colorWithRed:0.129 green:0.220 blue:0.452 alpha:1.000].CGColor, nil];
} }
+ (NSArray *)appStoreGrayColor { + (NSArray *)appStoreGrayColor {
return [NSArray arrayWithObjects:(id) return [NSArray arrayWithObjects:(id)
PS_RGBCOLOR(187,189,191).CGColor, PS_RGBCOLOR(187,189,191).CGColor,
PS_RGBCOLOR(210,210,210).CGColor, nil]; PS_RGBCOLOR(210,210,210).CGColor, nil];
} }
@end @end

View File

@ -27,11 +27,11 @@
#import <UIKit/UIKit.h> #import <UIKit/UIKit.h>
@interface PSWebTableViewCell : UITableViewCell <UIWebViewDelegate> { @interface PSWebTableViewCell : UITableViewCell <UIWebViewDelegate> {
UIWebView *webView_; UIWebView *webView_;
NSString *webViewContent_; NSString *webViewContent_;
CGSize webViewSize_; CGSize webViewSize_;
UIColor *cellBackgroundColor_; UIColor *cellBackgroundColor_;
} }
@property (nonatomic, retain) UIWebView *webView; @property (nonatomic, retain) UIWebView *webView;

View File

@ -53,7 +53,7 @@ body { font: 13px 'Helvetica Neue', Helvetica; word-wrap:break-word; padding:8px
- (void)addWebView { - (void)addWebView {
if(webViewContent_) { 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_) { if(!webView_) {
webView_ = [[[UIWebView alloc] initWithFrame:webViewRect] retain]; webView_ = [[[UIWebView alloc] initWithFrame:webViewRect] retain];
[self addSubview:webView_]; [self addSubview:webView_];
@ -61,37 +61,37 @@ body { font: 13px 'Helvetica Neue', Helvetica; word-wrap:break-word; padding:8px
webView_.backgroundColor = self.cellBackgroundColor; webView_.backgroundColor = self.cellBackgroundColor;
webView_.opaque = NO; webView_.opaque = NO;
webView_.delegate = self; webView_.delegate = self;
webView_.autoresizingMask = UIViewAutoresizingFlexibleWidth; webView_.autoresizingMask = UIViewAutoresizingFlexibleWidth;
for(UIView* subView in webView_.subviews){ for(UIView* subView in webView_.subviews){
if([subView isKindOfClass:[UIScrollView class]]){ if([subView isKindOfClass:[UIScrollView class]]){
// disable scrolling // disable scrolling
UIScrollView *sv = (UIScrollView *)subView; UIScrollView *sv = (UIScrollView *)subView;
sv.scrollEnabled = NO; sv.scrollEnabled = NO;
sv.bounces = NO; sv.bounces = NO;
// hide shadow // hide shadow
for (UIView* shadowView in [subView subviews]) { for (UIView* shadowView in [subView subviews]) {
if ([shadowView isKindOfClass:[UIImageView class]]) { if ([shadowView isKindOfClass:[UIImageView class]]) {
shadowView.hidden = YES; shadowView.hidden = YES;
}
}
}
} }
}
} }
}
}
else else
webView_.frame = webViewRect; webView_.frame = webViewRect;
NSString *deviceWidth = UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad ? [NSString stringWithFormat:@"%d", CGRectGetWidth(self.bounds)] : @"device-width"; NSString *deviceWidth = UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad ? [NSString stringWithFormat:@"%d", CGRectGetWidth(self.bounds)] : @"device-width";
//BWHockeyLog(@"%@\n%@\%@", PSWebTableViewCellHtmlTemplate, deviceWidth, self.webViewContent); //BWHockeyLog(@"%@\n%@\%@", PSWebTableViewCellHtmlTemplate, deviceWidth, self.webViewContent);
NSString *contentHtml = [NSString stringWithFormat:PSWebTableViewCellHtmlTemplate, deviceWidth, self.webViewContent]; NSString *contentHtml = [NSString stringWithFormat:PSWebTableViewCellHtmlTemplate, deviceWidth, self.webViewContent];
[webView_ loadHTMLString:contentHtml baseURL:nil]; [webView_ loadHTMLString:contentHtml baseURL:nil];
} }
} }
- (void)showWebView { - (void)showWebView {
webView_.hidden = NO; webView_.hidden = NO;
self.textLabel.text = @""; self.textLabel.text = @"";
[self setNeedsDisplay]; [self setNeedsDisplay];
} }
@ -109,13 +109,13 @@ body { font: 13px 'Helvetica Neue', Helvetica; word-wrap:break-word; padding:8px
- (void)setWebViewContent:(NSString *)aWebViewContent { - (void)setWebViewContent:(NSString *)aWebViewContent {
if (webViewContent_ != aWebViewContent) { if (webViewContent_ != aWebViewContent) {
[webViewContent_ release]; [webViewContent_ release];
webViewContent_ = [aWebViewContent retain]; webViewContent_ = [aWebViewContent retain];
// add basic accessiblity (prevents "snarfed from ivar layout") logs // add basic accessiblity (prevents "snarfed from ivar layout") logs
self.accessibilityLabel = aWebViewContent; self.accessibilityLabel = aWebViewContent;
} }
} }
/////////////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////////////
@ -123,16 +123,16 @@ body { font: 13px 'Helvetica Neue', Helvetica; word-wrap:break-word; padding:8px
#pragma mark NSObject #pragma mark NSObject
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier { - (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
if((self = [super initWithStyle:style reuseIdentifier:reuseIdentifier])) { if((self = [super initWithStyle:style reuseIdentifier:reuseIdentifier])) {
self.cellBackgroundColor = [UIColor clearColor]; self.cellBackgroundColor = [UIColor clearColor];
} }
return self; return self;
} }
- (void)dealloc { - (void)dealloc {
[self removeWebView]; [self removeWebView];
[webViewContent_ release]; [webViewContent_ release];
[super dealloc]; [super dealloc];
} }
/////////////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////////////
@ -140,12 +140,12 @@ body { font: 13px 'Helvetica Neue', Helvetica; word-wrap:break-word; padding:8px
#pragma mark UIView #pragma mark UIView
- (void)setFrame:(CGRect)aFrame { - (void)setFrame:(CGRect)aFrame {
BOOL needChange = !CGRectEqualToRect(aFrame, self.frame); BOOL needChange = !CGRectEqualToRect(aFrame, self.frame);
[super setFrame:aFrame]; [super setFrame:aFrame];
if (needChange) { if (needChange) {
[self addWebView]; [self addWebView];
} }
} }
/////////////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////////////
@ -154,7 +154,7 @@ body { font: 13px 'Helvetica Neue', Helvetica; word-wrap:break-word; padding:8px
- (void)prepareForReuse { - (void)prepareForReuse {
[self removeWebView]; [self removeWebView];
self.webViewContent = nil; self.webViewContent = nil;
[super prepareForReuse]; [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 { - (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
if(navigationType == UIWebViewNavigationTypeOther) if(navigationType == UIWebViewNavigationTypeOther)
return YES; return YES;
return NO; return NO;
} }
- (void)webViewDidFinishLoad:(UIWebView *)webView { - (void)webViewDidFinishLoad:(UIWebView *)webView {
if(webViewContent_) if(webViewContent_)
[self showWebView]; [self showWebView];
CGRect frame = webView_.frame; CGRect frame = webView_.frame;
frame.size.height = 1; frame.size.height = 1;
webView_.frame = frame; webView_.frame = frame;
CGSize fittingSize = [webView_ sizeThatFits:CGSizeZero]; CGSize fittingSize = [webView_ sizeThatFits:CGSizeZero];
frame.size = fittingSize; frame.size = fittingSize;
webView_.frame = frame; webView_.frame = frame;
// sizeThatFits is not reliable - use javascript for optimal height // sizeThatFits is not reliable - use javascript for optimal height
NSString *output = [webView_ stringByEvaluatingJavaScriptFromString:@"document.body.scrollHeight;"]; NSString *output = [webView_ stringByEvaluatingJavaScriptFromString:@"document.body.scrollHeight;"];
self.webViewSize = CGSizeMake(fittingSize.width, [output integerValue]); self.webViewSize = CGSizeMake(fittingSize.width, [output integerValue]);
} }
@end @end

View File

@ -38,107 +38,107 @@ CGImageRef CreateGradientImage(int pixelsWide, int pixelsHigh, float fromAlpha,
// Returns true if the image has an alpha layer // Returns true if the image has an alpha layer
- (BOOL)hasAlpha { - (BOOL)hasAlpha {
CGImageAlphaInfo alpha = CGImageGetAlphaInfo(self.CGImage); CGImageAlphaInfo alpha = CGImageGetAlphaInfo(self.CGImage);
return (alpha == kCGImageAlphaFirst || return (alpha == kCGImageAlphaFirst ||
alpha == kCGImageAlphaLast || alpha == kCGImageAlphaLast ||
alpha == kCGImageAlphaPremultipliedFirst || alpha == kCGImageAlphaPremultipliedFirst ||
alpha == kCGImageAlphaPremultipliedLast); alpha == kCGImageAlphaPremultipliedLast);
} }
// Returns a copy of the given image, adding an alpha channel if it doesn't already have one // Returns a copy of the given image, adding an alpha channel if it doesn't already have one
- (UIImage *)imageWithAlpha { - (UIImage *)imageWithAlpha {
if ([self hasAlpha]) { if ([self hasAlpha]) {
return self; return self;
} }
CGImageRef imageRef = self.CGImage; CGImageRef imageRef = self.CGImage;
size_t width = CGImageGetWidth(imageRef) * self.scale; size_t width = CGImageGetWidth(imageRef) * self.scale;
size_t height = CGImageGetHeight(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 // The bitsPerComponent and bitmapInfo values are hard-coded to prevent an "unsupported parameter combination" error
CGContextRef offscreenContext = CGBitmapContextCreate(NULL, CGContextRef offscreenContext = CGBitmapContextCreate(NULL,
width, width,
height, height,
8, 8,
0, 0,
CGImageGetColorSpace(imageRef), CGImageGetColorSpace(imageRef),
kCGBitmapByteOrderDefault | kCGImageAlphaPremultipliedFirst); kCGBitmapByteOrderDefault | kCGImageAlphaPremultipliedFirst);
// Draw the image into the context and retrieve the new image, which will now have an alpha layer // 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); CGContextDrawImage(offscreenContext, CGRectMake(0, 0, width, height), imageRef);
CGImageRef imageRefWithAlpha = CGBitmapContextCreateImage(offscreenContext); CGImageRef imageRefWithAlpha = CGBitmapContextCreateImage(offscreenContext);
UIImage *imageWithAlpha = [UIImage imageWithCGImage:imageRefWithAlpha]; UIImage *imageWithAlpha = [UIImage imageWithCGImage:imageRefWithAlpha];
// Clean up // Clean up
CGContextRelease(offscreenContext); CGContextRelease(offscreenContext);
CGImageRelease(imageRefWithAlpha); CGImageRelease(imageRefWithAlpha);
return imageWithAlpha; return imageWithAlpha;
} }
// Creates a copy of this image with rounded corners // 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 // 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/ // 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 { - (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; UIImage *roundedImage = nil;
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 40000 #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 40000
BW_IF_IOS4_OR_GREATER( BW_IF_IOS4_OR_GREATER(
UIGraphicsBeginImageContextWithOptions(self.size, NO, 0.0); // 0.0 for scale means "correct scale for device's main screen". 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. 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 // Create a clipping path with rounded corners
CGContextRef context = UIGraphicsGetCurrentContext(); CGContextRef context = UIGraphicsGetCurrentContext();
CGContextBeginPath(context); CGContextBeginPath(context);
[self addRoundedRectToPath:CGRectMake(borderSize, borderSize, self.size.width - borderSize * 2, self.size.height - borderSize * 2) [self addRoundedRectToPath:CGRectMake(borderSize, borderSize, self.size.width - borderSize * 2, self.size.height - borderSize * 2)
context:context context:context
ovalWidth:cornerSize ovalWidth:cornerSize
ovalHeight:cornerSize]; ovalHeight:cornerSize];
CGContextClosePath(context); CGContextClosePath(context);
CGContextClip(context); CGContextClip(context);
roundedImage = [UIImage imageWithCGImage:sourceImg scale:0.0 orientation:self.imageOrientation]; // create cropped UIImage. 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. [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); CGImageRelease(sourceImg);
roundedImage = UIGraphicsGetImageFromCurrentImageContext(); roundedImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext(); UIGraphicsEndImageContext();
) )
#endif #endif
if (!roundedImage) { if (!roundedImage) {
// Try older method. // Try older method.
UIImage *image = [self imageWithAlpha]; UIImage *image = [self imageWithAlpha];
// Build a context that's the same dimensions as the new size // Build a context that's the same dimensions as the new size
CGContextRef context = CGBitmapContextCreate(NULL, CGContextRef context = CGBitmapContextCreate(NULL,
image.size.width, image.size.width,
image.size.height, image.size.height,
CGImageGetBitsPerComponent(image.CGImage), CGImageGetBitsPerComponent(image.CGImage),
0, 0,
CGImageGetColorSpace(image.CGImage), CGImageGetColorSpace(image.CGImage),
CGImageGetBitmapInfo(image.CGImage)); CGImageGetBitmapInfo(image.CGImage));
// Create a clipping path with rounded corners // Create a clipping path with rounded corners
CGContextBeginPath(context); CGContextBeginPath(context);
[self addRoundedRectToPath:CGRectMake(borderSize, borderSize, image.size.width - borderSize * 2, image.size.height - borderSize * 2) [self addRoundedRectToPath:CGRectMake(borderSize, borderSize, image.size.width - borderSize * 2, image.size.height - borderSize * 2)
context:context context:context
ovalWidth:cornerSize ovalWidth:cornerSize
ovalHeight:cornerSize]; ovalHeight:cornerSize];
CGContextClosePath(context); CGContextClosePath(context);
CGContextClip(context); CGContextClip(context);
// Draw the image to the context; the clipping path will make anything outside the rounded rect transparent // 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); CGContextDrawImage(context, CGRectMake(0, 0, image.size.width, image.size.height), image.CGImage);
// Create a CGImage from the context // Create a CGImage from the context
CGImageRef clippedImage = CGBitmapContextCreateImage(context); CGImageRef clippedImage = CGBitmapContextCreateImage(context);
CGContextRelease(context); CGContextRelease(context);
// Create a UIImage from the CGImage // Create a UIImage from the CGImage
roundedImage = [UIImage imageWithCGImage:clippedImage]; roundedImage = [UIImage imageWithCGImage:clippedImage];
CGImageRelease(clippedImage); CGImageRelease(clippedImage);
} }
return roundedImage; return roundedImage;
} }
#pragma mark - #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 // 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/ // 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 { - (void)addRoundedRectToPath:(CGRect)rect context:(CGContextRef)context ovalWidth:(CGFloat)ovalWidth ovalHeight:(CGFloat)ovalHeight {
if (ovalWidth == 0 || ovalHeight == 0) { if (ovalWidth == 0 || ovalHeight == 0) {
CGContextAddRect(context, rect); CGContextAddRect(context, rect);
return; return;
} }
CGContextSaveGState(context); CGContextSaveGState(context);
CGContextTranslateCTM(context, CGRectGetMinX(rect), CGRectGetMinY(rect)); CGContextTranslateCTM(context, CGRectGetMinX(rect), CGRectGetMinY(rect));
CGContextScaleCTM(context, ovalWidth, ovalHeight); CGContextScaleCTM(context, ovalWidth, ovalHeight);
CGFloat fw = CGRectGetWidth(rect) / ovalWidth; CGFloat fw = CGRectGetWidth(rect) / ovalWidth;
CGFloat fh = CGRectGetHeight(rect) / ovalHeight; CGFloat fh = CGRectGetHeight(rect) / ovalHeight;
CGContextMoveToPoint(context, fw, fh/2); CGContextMoveToPoint(context, fw, fh/2);
CGContextAddArcToPoint(context, fw, fh, fw/2, fh, 1); CGContextAddArcToPoint(context, fw, fh, fw/2, fh, 1);
CGContextAddArcToPoint(context, 0, fh, 0, fh/2, 1); CGContextAddArcToPoint(context, 0, fh, 0, fh/2, 1);
CGContextAddArcToPoint(context, 0, 0, fw/2, 0, 1); CGContextAddArcToPoint(context, 0, 0, fw/2, 0, 1);
CGContextAddArcToPoint(context, fw, 0, fw, fh/2, 1); CGContextAddArcToPoint(context, fw, 0, fw, fh/2, 1);
CGContextClosePath(context); CGContextClosePath(context);
CGContextRestoreGState(context); CGContextRestoreGState(context);
} }
- (UIImage *)bw_imageToFitSize:(CGSize)fitSize honorScaleFactor:(BOOL)honorScaleFactor - (UIImage *)bw_imageToFitSize:(CGSize)fitSize honorScaleFactor:(BOOL)honorScaleFactor
{ {
float imageScaleFactor = 1.0; float imageScaleFactor = 1.0;
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 40000 #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 40000
if (honorScaleFactor) { if (honorScaleFactor) {
if ([self respondsToSelector:@selector(scale)]) { if ([self respondsToSelector:@selector(scale)]) {
imageScaleFactor = [self scale]; imageScaleFactor = [self scale];
}
} }
}
#endif #endif
float sourceWidth = [self size].width * imageScaleFactor; float sourceWidth = [self size].width * imageScaleFactor;
float sourceHeight = [self size].height * imageScaleFactor; float sourceHeight = [self size].height * imageScaleFactor;
float targetWidth = fitSize.width; float targetWidth = fitSize.width;
float targetHeight = fitSize.height; float targetHeight = fitSize.height;
// Calculate aspect ratios // Calculate aspect ratios
float sourceRatio = sourceWidth / sourceHeight; float sourceRatio = sourceWidth / sourceHeight;
float targetRatio = targetWidth / targetHeight; float targetRatio = targetWidth / targetHeight;
// Determine what side of the source image to use for proportional scaling // Determine what side of the source image to use for proportional scaling
BOOL scaleWidth = (sourceRatio <= targetRatio); BOOL scaleWidth = (sourceRatio <= targetRatio);
// Deal with the case of just scaling proportionally to fit, without cropping // Deal with the case of just scaling proportionally to fit, without cropping
scaleWidth = !scaleWidth; scaleWidth = !scaleWidth;
// Proportionally scale source image // Proportionally scale source image
float scalingFactor, scaledWidth, scaledHeight; float scalingFactor, scaledWidth, scaledHeight;
if (scaleWidth) { if (scaleWidth) {
scalingFactor = 1.0 / sourceRatio; scalingFactor = 1.0 / sourceRatio;
scaledWidth = targetWidth; scaledWidth = targetWidth;
scaledHeight = round(targetWidth * scalingFactor); scaledHeight = round(targetWidth * scalingFactor);
} else { } else {
scalingFactor = sourceRatio; scalingFactor = sourceRatio;
scaledWidth = round(targetHeight * scalingFactor); scaledWidth = round(targetHeight * scalingFactor);
scaledHeight = targetHeight; scaledHeight = targetHeight;
} }
// Calculate compositing rectangles // Calculate compositing rectangles
CGRect sourceRect, destRect; CGRect sourceRect, destRect;
sourceRect = CGRectMake(0, 0, sourceWidth, sourceHeight); sourceRect = CGRectMake(0, 0, sourceWidth, sourceHeight);
destRect = CGRectMake(0, 0, scaledWidth, scaledHeight); destRect = CGRectMake(0, 0, scaledWidth, scaledHeight);
// Create appropriately modified image. // Create appropriately modified image.
UIImage *image = nil; UIImage *image = nil;
BW_IF_IOS4_OR_GREATER 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". 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. CGImageRef sourceImg = CGImageCreateWithImageInRect([self CGImage], sourceRect); // cropping happens here.
image = [UIImage imageWithCGImage:sourceImg scale:0.0 orientation:self.imageOrientation]; // create cropped UIImage. 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. [image drawInRect:destRect]; // the actual scaling happens here, and orientation is taken care of automatically.
CGImageRelease(sourceImg); CGImageRelease(sourceImg);
image = UIGraphicsGetImageFromCurrentImageContext(); image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext(); UIGraphicsEndImageContext();
) )
if (!image) { if (!image) {
// Try older method. // Try older method.
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef context = CGBitmapContextCreate(NULL, scaledWidth, scaledHeight, 8, (fitSize.width * 4), CGContextRef context = CGBitmapContextCreate(NULL, scaledWidth, scaledHeight, 8, (fitSize.width * 4),
colorSpace, kCGImageAlphaPremultipliedLast); colorSpace, kCGImageAlphaPremultipliedLast);
CGImageRef sourceImg = CGImageCreateWithImageInRect([self CGImage], sourceRect); CGImageRef sourceImg = CGImageCreateWithImageInRect([self CGImage], sourceRect);
CGContextDrawImage(context, destRect, sourceImg); CGContextDrawImage(context, destRect, sourceImg);
CGImageRelease(sourceImg); CGImageRelease(sourceImg);
@ -233,105 +233,105 @@ CGImageRef CreateGradientImage(int pixelsWide, int pixelsHigh, float fromAlpha,
image = [UIImage imageWithCGImage:finalImage]; image = [UIImage imageWithCGImage:finalImage];
CGImageRelease(finalImage); CGImageRelease(finalImage);
} }
return image; return image;
} }
CGImageRef CreateGradientImage(int pixelsWide, int pixelsHigh, float fromAlpha, float toAlpha) { CGImageRef CreateGradientImage(int pixelsWide, int pixelsHigh, float fromAlpha, float toAlpha) {
CGImageRef theCGImage = NULL; CGImageRef theCGImage = NULL;
// gradient is always black-white and the mask must be in the gray colorspace // gradient is always black-white and the mask must be in the gray colorspace
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceGray(); CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceGray();
// create the bitmap context // create the bitmap context
CGContextRef gradientBitmapContext = CGBitmapContextCreate(NULL, pixelsWide, pixelsHigh, 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 // define the start and end grayscale values (with the alpha, even though
// our bitmap context doesn't support alpha the gradient requires it) // our bitmap context doesn't support alpha the gradient requires it)
CGFloat colors[] = {toAlpha, 1.0, fromAlpha, 1.0}; CGFloat colors[] = {toAlpha, 1.0, fromAlpha, 1.0};
// create the CGGradient and then release the gray color space // create the CGGradient and then release the gray color space
CGGradientRef grayScaleGradient = CGGradientCreateWithColorComponents(colorSpace, colors, NULL, 2); CGGradientRef grayScaleGradient = CGGradientCreateWithColorComponents(colorSpace, colors, NULL, 2);
CGColorSpaceRelease(colorSpace); CGColorSpaceRelease(colorSpace);
// create the start and end points for the gradient vector (straight down) // create the start and end points for the gradient vector (straight down)
CGPoint gradientEndPoint = CGPointZero; CGPoint gradientEndPoint = CGPointZero;
CGPoint gradientStartPoint = CGPointMake(0, pixelsHigh); CGPoint gradientStartPoint = CGPointMake(0, pixelsHigh);
// draw the gradient into the gray bitmap context // draw the gradient into the gray bitmap context
CGContextDrawLinearGradient(gradientBitmapContext, grayScaleGradient, gradientStartPoint, CGContextDrawLinearGradient(gradientBitmapContext, grayScaleGradient, gradientStartPoint,
gradientEndPoint, kCGGradientDrawsAfterEndLocation); gradientEndPoint, kCGGradientDrawsAfterEndLocation);
CGGradientRelease(grayScaleGradient); CGGradientRelease(grayScaleGradient);
// convert the context into a CGImageRef and release the context // convert the context into a CGImageRef and release the context
theCGImage = CGBitmapContextCreateImage(gradientBitmapContext); theCGImage = CGBitmapContextCreateImage(gradientBitmapContext);
CGContextRelease(gradientBitmapContext); CGContextRelease(gradientBitmapContext);
// return the imageref containing the gradient // return the imageref containing the gradient
return theCGImage; return theCGImage;
} }
CGContextRef MyOpenBitmapContext(int pixelsWide, int pixelsHigh) { CGContextRef MyOpenBitmapContext(int pixelsWide, int pixelsHigh) {
CGSize size = CGSizeMake(pixelsWide, pixelsHigh); CGSize size = CGSizeMake(pixelsWide, pixelsHigh);
if (UIGraphicsBeginImageContextWithOptions != NULL) { if (UIGraphicsBeginImageContextWithOptions != NULL) {
UIGraphicsBeginImageContextWithOptions(size, NO, 0.0); UIGraphicsBeginImageContextWithOptions(size, NO, 0.0);
} }
else { else {
UIGraphicsBeginImageContext(size); UIGraphicsBeginImageContext(size);
} }
return UIGraphicsGetCurrentContext(); return UIGraphicsGetCurrentContext();
} }
- (UIImage *)bw_reflectedImageWithHeight:(NSUInteger)height fromAlpha:(float)fromAlpha toAlpha:(float)toAlpha { - (UIImage *)bw_reflectedImageWithHeight:(NSUInteger)height fromAlpha:(float)fromAlpha toAlpha:(float)toAlpha {
if(height == 0) if(height == 0)
return nil; return nil;
// create a bitmap graphics context the size of the image // create a bitmap graphics context the size of the image
CGContextRef mainViewContentContext = MyOpenBitmapContext(self.size.width, height); CGContextRef mainViewContentContext = MyOpenBitmapContext(self.size.width, height);
// create a 2 bit CGImage containing a gradient that will be used for masking the // 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 // 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 // function will stretch the bitmap image as required, so we can create a 1 pixel wide gradient
CGImageRef gradientMaskImage = CreateGradientImage(1, height, fromAlpha, toAlpha); CGImageRef gradientMaskImage = CreateGradientImage(1, height, fromAlpha, toAlpha);
// create an image by masking the bitmap of the mainView content with the gradient view // 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 // then release the pre-masked content bitmap and the gradient bitmap
CGContextClipToMask(mainViewContentContext, CGRectMake(0.0, 0.0, self.size.width, height), gradientMaskImage); CGContextClipToMask(mainViewContentContext, CGRectMake(0.0, 0.0, self.size.width, height), gradientMaskImage);
CGImageRelease(gradientMaskImage); CGImageRelease(gradientMaskImage);
// draw the image into the bitmap context // draw the image into the bitmap context
CGContextDrawImage(mainViewContentContext, CGRectMake(0, 0, self.size.width, self.size.height), self.CGImage); CGContextDrawImage(mainViewContentContext, CGRectMake(0, 0, self.size.width, self.size.height), self.CGImage);
// convert the finished reflection image to a UIImage // convert the finished reflection image to a UIImage
UIImage *theImage = UIGraphicsGetImageFromCurrentImageContext(); // returns autoreleased UIImage *theImage = UIGraphicsGetImageFromCurrentImageContext(); // returns autoreleased
UIGraphicsEndImageContext(); UIGraphicsEndImageContext();
return theImage; return theImage;
} }
- (id)bw_initWithContentsOfResolutionIndependentFile:(NSString *)path { - (id)bw_initWithContentsOfResolutionIndependentFile:(NSString *)path {
if ([UIScreen instancesRespondToSelector:@selector(scale)] && (int)[[UIScreen mainScreen] scale] == 2.0) { if ([UIScreen instancesRespondToSelector:@selector(scale)] && (int)[[UIScreen mainScreen] scale] == 2.0) {
NSString *path2x = [[path stringByDeletingLastPathComponent] NSString *path2x = [[path stringByDeletingLastPathComponent]
stringByAppendingPathComponent:[NSString stringWithFormat:@"%@@2x.%@", stringByAppendingPathComponent:[NSString stringWithFormat:@"%@@2x.%@",
[[path lastPathComponent] stringByDeletingPathExtension], [[path lastPathComponent] stringByDeletingPathExtension],
[path pathExtension]]]; [path pathExtension]]];
if ([[NSFileManager defaultManager] fileExistsAtPath:path2x]) {
return [self initWithContentsOfFile:path2x];
}
}
return [self initWithContentsOfFile:path]; if ([[NSFileManager defaultManager] fileExistsAtPath:path2x]) {
return [self initWithContentsOfFile:path2x];
}
}
return [self initWithContentsOfFile:path];
} }
+ (UIImage*)bw_imageWithContentsOfResolutionIndependentFile:(NSString *)path { + (UIImage*)bw_imageWithContentsOfResolutionIndependentFile:(NSString *)path {
#ifndef __clang_analyzer__ #ifndef __clang_analyzer__
// clang alayzer in 4.2b3 thinks here's a leak, which is not the case. // clang alayzer in 4.2b3 thinks here's a leak, which is not the case.
return [[[UIImage alloc] bw_initWithContentsOfResolutionIndependentFile:path] autorelease]; return [[[UIImage alloc] bw_initWithContentsOfResolutionIndependentFile:path] autorelease];
#endif #endif
} }