From 9b57875a47dae4b9836fea9ac5b7c3bef28a79db Mon Sep 17 00:00:00 2001 From: moritz haarmann Date: Wed, 12 Feb 2014 18:15:24 +0100 Subject: [PATCH 01/92] Added Photo Picker functionality to Feedback Composer (#3) --- Classes/BITFeedbackComposeViewController.m | 143 ++++++++++++++++++-- Resources/iconCamera.png | Bin 0 -> 346 bytes Resources/iconCamera@2x.png | Bin 0 -> 642 bytes Support/HockeySDK.xcodeproj/project.pbxproj | 8 ++ 4 files changed, 143 insertions(+), 8 deletions(-) create mode 100755 Resources/iconCamera.png create mode 100755 Resources/iconCamera@2x.png diff --git a/Classes/BITFeedbackComposeViewController.m b/Classes/BITFeedbackComposeViewController.m index c4eda0230c..72004106d9 100644 --- a/Classes/BITFeedbackComposeViewController.m +++ b/Classes/BITFeedbackComposeViewController.m @@ -42,15 +42,22 @@ #import "BITHockeyHelper.h" -@interface BITFeedbackComposeViewController () { +@interface BITFeedbackComposeViewController () { UIStatusBarStyle _statusBarStyle; } @property (nonatomic, weak) BITFeedbackManager *manager; @property (nonatomic, strong) UITextView *textView; +@property (nonatomic, strong) UIView *contentViewContainer; +@property (nonatomic, strong) UIScrollView *photoScrollView; +@property (nonatomic, strong) NSMutableArray *photoScrollViewImageViews; @property (nonatomic, strong) NSString *text; +@property (nonatomic, strong) NSMutableArray *photos; + +@property (nonatomic, strong) UIView *textAccessoryView; + @end @@ -68,6 +75,8 @@ _blockUserDataScreen = NO; _delegate = nil; _manager = [BITHockeyManager sharedHockeyManager].feedbackManager; + _photos = [NSMutableArray new]; + _photoScrollViewImageViews = [NSMutableArray new]; _text = nil; } @@ -122,12 +131,12 @@ frame.size.height = windowSize.width - navBarHeight - modalGap - kbSize.width; } } - [self.textView setFrame:frame]; + [self.contentViewContainer setFrame:frame]; } - (void)keyboardWillBeHidden:(NSNotification*)aNotification { CGRect frame = CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height); - [self.textView setFrame:frame]; + [self.contentViewContainer setFrame:frame]; } @@ -142,20 +151,49 @@ self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemCancel target:self action:@selector(dismissAction:)]; - self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:BITHockeyLocalizedString(@"HockeyFeedbackComposeSend") style:UIBarButtonItemStyleDone target:self action:@selector(sendAction:)]; + + // Container that contains both the textfield and eventually the photo scroll view on the right side + self.contentViewContainer = [[UIView alloc] initWithFrame:self.view.bounds]; + [self.view addSubview:self.contentViewContainer]; + + // message input textfield - self.textView = [[UITextView alloc] initWithFrame:self.view.frame]; + self.textView = [[UITextView alloc] initWithFrame:self.view.bounds]; self.textView.font = [UIFont systemFontOfSize:17]; self.textView.delegate = self; self.textView.backgroundColor = [UIColor whiteColor]; self.textView.returnKeyType = UIReturnKeyDefault; - self.textView.autoresizingMask = UIViewAutoresizingFlexibleWidth; - [self.view addSubview:self.textView]; + self.textView.autoresizingMask = UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight; + + [self.contentViewContainer addSubview:self.textView]; + + // Add Photo Button + Container that's displayed above the keyboard. + self.textAccessoryView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, CGRectGetWidth(self.view.frame), 44)]; + self.textAccessoryView.backgroundColor = [UIColor colorWithRed:0.9f green:0.9f blue:0.9f alpha:1.0f]; + UIButton *addPhotoButton = [UIButton buttonWithType:UIButtonTypeSystem]; + [addPhotoButton setTitle:@"+ Add Photo" forState:UIControlStateNormal]; + addPhotoButton.frame = CGRectMake(0, 0, 100, 44); + + [addPhotoButton addTarget:self action:@selector(addPhotoAction:) forControlEvents:UIControlEventTouchUpInside]; + + [self.textAccessoryView addSubview:addPhotoButton]; + + self.textView.inputAccessoryView = self.textAccessoryView; + + self.photoScrollView = [[UIScrollView alloc] initWithFrame:CGRectZero]; + self.photoScrollView.scrollEnabled = YES; + self.photoScrollView.bounces = YES; + self.photoScrollView.autoresizingMask = UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight; + + + [self.contentViewContainer addSubview:self.photoScrollView]; + + } - (void)viewWillAppear:(BOOL)animated { @@ -178,7 +216,7 @@ [[UIApplication sharedApplication] setStatusBarStyle:(self.navigationController.navigationBar.barStyle == UIBarStyleDefault) ? UIStatusBarStyleDefault : UIStatusBarStyleBlackOpaque]; #endif - [self.textView setFrame:self.view.frame]; + // [self.textView setFrame:self.view.frame]; if (_text) { self.textView.text = _text; @@ -218,6 +256,69 @@ [super viewDidDisappear:animated]; } +-(void)refreshPhotoScrollview { + CGFloat scrollViewWidth = 0; + + if (self.photos.count){ + scrollViewWidth = 100; + } + + CGRect textViewFrame = self.textView.frame; + + CGRect scrollViewFrame = self.photoScrollView.frame; + + BOOL alreadySetup = CGRectGetWidth(scrollViewFrame) == scrollViewWidth; + + if (!alreadySetup){ + textViewFrame.size.width -= scrollViewWidth; + + // status bar? + + scrollViewFrame = CGRectMake(CGRectGetMaxX(textViewFrame), self.view.frame.origin.y, scrollViewWidth, CGRectGetHeight(textViewFrame)); + self.textView.frame = textViewFrame; + self.photoScrollView.frame = scrollViewFrame; + self.photoScrollView.contentInset = self.textView.contentInset; + } + + for (UIView *subview in self.photoScrollView.subviews){ + [subview removeFromSuperview]; + } + + if (self.photos.count > self.photoScrollViewImageViews.count){ + NSInteger numberOfViewsToCreate = self.photos.count - self.photoScrollViewImageViews.count; + for (int i = 0;iINyI0iW-~k|Lo>1XIWv&31>&1P{1u3$7^n+)n2Is#^nX$;2!R^XfJLqWDi?w)qM3+@sH0Fi6pLIani{P6fTn;L3#_5^YAkZA(bQl7 z79uS02jVqAJPR#YRwIe|<1>Jo$rJ;l`#=UqAqO%<1)7PDA_WV`<Fbobk21Yt?*O8Gi z90Mct2|_0Z7I=g_0p$sJgtTKP;0Z!Ure^HcjNR(78TiI2N)1>;c1+_X!{X9GwcqwU{GGA2NtL`0jvn8%2J_(6?$5`95I4&^M@qyo~8j zz?I-Tht6vSc$bdtBJqRMF7W`NTXm8ZC=1t6MH($KY<8OQIbaItHJN-`wK)S`06$Il ze42a#E`V81-7(;Vju1-emAmR=(wXD~IXD;zBU|#f9hl`H9pKtDolj`QKEnt}{FSsI zOVt3k;GKL1j5CN+BgVK!Cl}4w8@D~+5Z~ljx5+SoO0Nv^v4*%T!6BZ~>WLj{%JT+w zs4~@jXIcjNKyw1hBYcfXs`g52eCHW-j4;NBh$S~+ZM}Xb9*+oVUBee)%qL0?#Kfo( zC0w-UPvm)PAg&s)HR$?xKW|et=&j4TKQZVkojmnspE+MmUQsuBu86$rizc5iy(hTd zE}HzDJahga91XfkC-0NzouxO0rXBaIChrlC$3`GQ_n`o#bx@M3z-!t~76jUjx~t zO9`QfTmz|2KTnu&$ls^*)Epx`P4iXy1-uo%KDuRkri%YINt?+BE#-c6u{nBd*$(_& c_ Date: Thu, 13 Feb 2014 11:04:32 +0100 Subject: [PATCH 02/92] + Finalized preliminary feedback support. Now comes the polish. --- Classes/BITFeedbackComposeViewController.m | 2 +- Classes/BITFeedbackManager.m | 14 ++++++++++++-- Classes/BITFeedbackManagerPrivate.h | 2 +- Classes/BITFeedbackMessage.h | 1 + Classes/BITFeedbackMessage.m | 3 +++ Classes/BITHockeyAppClient.h | 16 ++++++++++++++-- Classes/BITHockeyAppClient.m | 8 ++++++-- 7 files changed, 38 insertions(+), 8 deletions(-) diff --git a/Classes/BITFeedbackComposeViewController.m b/Classes/BITFeedbackComposeViewController.m index 72004106d9..a33060af90 100644 --- a/Classes/BITFeedbackComposeViewController.m +++ b/Classes/BITFeedbackComposeViewController.m @@ -351,7 +351,7 @@ NSString *text = self.textView.text; - [self.manager submitMessageWithText:text]; + [self.manager submitMessageWithText:text andPhotos:self.photos]; [self dismissWithResult:BITFeedbackComposeResultSubmitted]; } diff --git a/Classes/BITFeedbackManager.m b/Classes/BITFeedbackManager.m index 903183262f..4344fab4b2 100644 --- a/Classes/BITFeedbackManager.m +++ b/Classes/BITFeedbackManager.m @@ -837,6 +837,15 @@ [postBody appendData:[[NSString stringWithFormat:@"--%@--\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]]; + NSInteger photoIndex = 0; + + for (UIImage *image in message.photos){ + NSString *contentType = @"image/png"; + NSData* imageData = UIImagePNGRepresentation(image); + [postBody appendData:[BITHockeyAppClient dataWithPostValue:imageData forKey:[NSString stringWithFormat:@"attachment%ld", (long)photoIndex] contentType:contentType boundary:boundary]]; + photoIndex++; + } + [request setHTTPBody:postBody]; } @@ -971,11 +980,12 @@ } } -- (void)submitMessageWithText:(NSString *)text { +- (void)submitMessageWithText:(NSString *)text andPhotos:(NSArray *)photos { BITFeedbackMessage *message = [[BITFeedbackMessage alloc] init]; message.text = text; [message setStatus:BITFeedbackMessageStatusSendPending]; - [message setToken:[self uuidAsLowerCaseAndShortened]]; + [message setToken:[self uuidAsLowerCaseAndShortened]]; + [message setPhotos:photos]; [message setUserMessage:YES]; [_feedbackList addObject:message]; diff --git a/Classes/BITFeedbackManagerPrivate.h b/Classes/BITFeedbackManagerPrivate.h index dd5f03a13f..9748b0dc11 100644 --- a/Classes/BITFeedbackManagerPrivate.h +++ b/Classes/BITFeedbackManagerPrivate.h @@ -65,7 +65,7 @@ - (NSUInteger)numberOfMessages; - (BITFeedbackMessage *)messageAtIndex:(NSUInteger)index; -- (void)submitMessageWithText:(NSString *)text; +- (void)submitMessageWithText:(NSString *)text andPhotos:(NSArray *)photos; - (void)submitPendingMessages; // Returns YES if manual user data can be entered, required or optional diff --git a/Classes/BITFeedbackMessage.h b/Classes/BITFeedbackMessage.h index c938e21722..78bc124fb2 100644 --- a/Classes/BITFeedbackMessage.h +++ b/Classes/BITFeedbackMessage.h @@ -69,6 +69,7 @@ typedef NS_ENUM(NSInteger, BITFeedbackMessageStatus) { @property (nonatomic, copy) NSDate *date; @property (nonatomic, copy) NSNumber *id; @property (nonatomic, copy) NSString *token; +@property (nonatomic, strong) NSArray *photos; @property (nonatomic) BITFeedbackMessageStatus status; @property (nonatomic) BOOL userMessage; diff --git a/Classes/BITFeedbackMessage.m b/Classes/BITFeedbackMessage.m index a5b0e0d8b2..b0860b6bdd 100644 --- a/Classes/BITFeedbackMessage.m +++ b/Classes/BITFeedbackMessage.m @@ -42,6 +42,7 @@ _email = nil; _date = [[NSDate alloc] init]; _token = nil; + _photos = nil; _id = [[NSNumber alloc] initWithInteger:0]; _status = BITFeedbackMessageStatusSendPending; _userMessage = NO; @@ -59,6 +60,7 @@ [encoder encodeObject:self.email forKey:@"email"]; [encoder encodeObject:self.date forKey:@"date"]; [encoder encodeObject:self.id forKey:@"id"]; + [encoder encodeObject:self.photos forKey:@"photos"]; [encoder encodeInteger:self.status forKey:@"status"]; [encoder encodeBool:self.userMessage forKey:@"userMessage"]; [encoder encodeObject:self.token forKey:@"token"]; @@ -72,6 +74,7 @@ self.email = [decoder decodeObjectForKey:@"email"]; self.date = [decoder decodeObjectForKey:@"date"]; self.id = [decoder decodeObjectForKey:@"id"]; + self.photos = [decoder decodeObjectForKey:@"photos"]; self.status = (BITFeedbackMessageStatus)[decoder decodeIntegerForKey:@"status"]; self.userMessage = [decoder decodeBoolForKey:@"userMessage"]; self.token = [decoder decodeObjectForKey:@"token"]; diff --git a/Classes/BITHockeyAppClient.h b/Classes/BITHockeyAppClient.h index a8d5fdbd15..617b9b392d 100644 --- a/Classes/BITHockeyAppClient.h +++ b/Classes/BITHockeyAppClient.h @@ -118,8 +118,8 @@ #pragma mark - Helpers /** - * create a post body from the given value, key and boundary - * c/p from HockeyBaseManager + * create a post body from the given value, key and boundary. This is a convenience call to + * dataWithPostValue:forKey:contentType:boundary and aimed at NSString-content. * * @param value - * @param key - @@ -128,4 +128,16 @@ * @return NSData instance configured to be attached on a (post) URLRequest */ + (NSData *)dataWithPostValue:(NSString *)value forKey:(NSString *)key boundary:(NSString *) boundary; + +/** + * create a post body from the given value, key and boundary and content type. + * + * @param value - + * @param key - + * @param boundary - + * + * @return NSData instance configured to be attached on a (post) URLRequest + */ ++ (NSData *)dataWithPostValue:(NSData *)value forKey:(NSString *)key contentType:(NSString *)contentType boundary:(NSString *) boundary; + @end diff --git a/Classes/BITHockeyAppClient.m b/Classes/BITHockeyAppClient.m index 168162b47c..442dbaf31b 100644 --- a/Classes/BITHockeyAppClient.m +++ b/Classes/BITHockeyAppClient.m @@ -85,12 +85,16 @@ } + (NSData *)dataWithPostValue:(NSString *)value forKey:(NSString *)key boundary:(NSString *) boundary { + return [self dataWithPostValue:[value dataUsingEncoding:NSUTF8StringEncoding] forKey:key contentType:@"text" boundary:boundary]; +} + ++ (NSData *)dataWithPostValue:(NSData *)value forKey:(NSString *)key contentType:(NSString *)contentType boundary:(NSString *) boundary { NSMutableData *postBody = [NSMutableData data]; [postBody appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]]; [postBody appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\";\r\n", key] dataUsingEncoding:NSUTF8StringEncoding]]; - [postBody appendData:[[NSString stringWithFormat:@"Content-Type: text\r\n\r\n"] dataUsingEncoding:NSUTF8StringEncoding]]; - [postBody appendData:[value dataUsingEncoding:NSUTF8StringEncoding]]; + [postBody appendData:[[NSString stringWithFormat:@"Content-Type: %@\r\n\r\n", contentType] dataUsingEncoding:NSUTF8StringEncoding]]; + [postBody appendData:value]; [postBody appendData:[@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]]; return postBody; From 4b359e7771c512df98601234e3363c198fb9f4ba Mon Sep 17 00:00:00 2001 From: moritz haarmann Date: Thu, 13 Feb 2014 11:32:54 +0100 Subject: [PATCH 03/92] + Fixes a warning --- Classes/BITFeedbackComposeViewController.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Classes/BITFeedbackComposeViewController.m b/Classes/BITFeedbackComposeViewController.m index a33060af90..c8b4e15457 100644 --- a/Classes/BITFeedbackComposeViewController.m +++ b/Classes/BITFeedbackComposeViewController.m @@ -42,7 +42,7 @@ #import "BITHockeyHelper.h" -@interface BITFeedbackComposeViewController () { +@interface BITFeedbackComposeViewController () { UIStatusBarStyle _statusBarStyle; } From 38af43bdb598444a4b0773d66b17e26a31aedef2 Mon Sep 17 00:00:00 2001 From: moritz haarmann Date: Mon, 17 Feb 2014 21:04:03 +0100 Subject: [PATCH 04/92] + Changing machines.. --- Classes/BITFeedbackComposeViewController.m | 25 +++--- Classes/BITFeedbackManager.m | 24 +++-- Classes/BITFeedbackManagerPrivate.h | 2 +- Classes/BITFeedbackMessage.h | 2 +- Classes/BITFeedbackMessage.m | 6 +- Classes/BITFeedbackMessageAttachment.h | 47 ++++++++++ Classes/BITFeedbackMessageAttachment.m | 97 +++++++++++++++++++++ Classes/BITImageAnnotationViewController.h | 15 ++++ Classes/BITImageAnnotationViewController.m | 57 ++++++++++++ Support/HockeySDK.xcodeproj/project.pbxproj | 16 +++- 10 files changed, 266 insertions(+), 25 deletions(-) create mode 100644 Classes/BITFeedbackMessageAttachment.h create mode 100644 Classes/BITFeedbackMessageAttachment.m create mode 100644 Classes/BITImageAnnotationViewController.h create mode 100644 Classes/BITImageAnnotationViewController.m diff --git a/Classes/BITFeedbackComposeViewController.m b/Classes/BITFeedbackComposeViewController.m index c8b4e15457..f22b0237c5 100644 --- a/Classes/BITFeedbackComposeViewController.m +++ b/Classes/BITFeedbackComposeViewController.m @@ -34,6 +34,7 @@ #import "HockeySDKPrivate.h" #import "BITFeedbackManagerPrivate.h" +#import "BITFeedbackMessageAttachment.h" #import "BITFeedbackComposeViewController.h" #import "BITFeedbackUserDataViewController.h" @@ -160,8 +161,6 @@ self.contentViewContainer = [[UIView alloc] initWithFrame:self.view.bounds]; [self.view addSubview:self.contentViewContainer]; - - // message input textfield self.textView = [[UITextView alloc] initWithFrame:self.view.bounds]; self.textView.font = [UIFont systemFontOfSize:17]; @@ -185,15 +184,13 @@ self.textView.inputAccessoryView = self.textAccessoryView; + // This could be a subclass, yet self.photoScrollView = [[UIScrollView alloc] initWithFrame:CGRectZero]; self.photoScrollView.scrollEnabled = YES; self.photoScrollView.bounces = YES; self.photoScrollView.autoresizingMask = UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight; - [self.contentViewContainer addSubview:self.photoScrollView]; - - } - (void)viewWillAppear:(BOOL)animated { @@ -271,9 +268,6 @@ if (!alreadySetup){ textViewFrame.size.width -= scrollViewWidth; - - // status bar? - scrollViewFrame = CGRectMake(CGRectGetMaxX(textViewFrame), self.view.frame.origin.y, scrollViewWidth, CGRectGetHeight(textViewFrame)); self.textView.frame = textViewFrame; self.photoScrollView.frame = scrollViewFrame; @@ -315,8 +309,6 @@ } [self.photoScrollView setContentSize:CGSizeMake(CGRectGetWidth(self.photoScrollView.frame), currentYOffset)]; - - } @@ -351,7 +343,16 @@ NSString *text = self.textView.text; - [self.manager submitMessageWithText:text andPhotos:self.photos]; + // Create attachments from the photos. + + NSMutableArray *attachments = [NSMutableArray new]; + + for (UIImage *photo in self.photos){ + BITFeedbackMessageAttachment *attachment = [BITFeedbackMessageAttachment attachmentWithData:UIImageJPEGRepresentation(photo, 0.7f) contentType:@"image/jpeg"]; + [attachments addObject:attachment]; + } + + [self.manager submitMessageWithText:text andAttachments:attachments]; [self dismissWithResult:BITFeedbackComposeResultSubmitted]; } @@ -392,7 +393,7 @@ } - (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker { - + [picker dismissModalViewControllerAnimated:YES]; } #pragma mark - BITFeedbackUserDataDelegate diff --git a/Classes/BITFeedbackManager.m b/Classes/BITFeedbackManager.m index 4344fab4b2..527e2d1b72 100644 --- a/Classes/BITFeedbackManager.m +++ b/Classes/BITFeedbackManager.m @@ -34,6 +34,7 @@ #import "HockeySDKPrivate.h" #import "BITFeedbackManager.h" +#import "BITFeedbackMessageAttachment.h" #import "BITFeedbackManagerPrivate.h" #import "BITHockeyBaseManagerPrivate.h" @@ -697,6 +698,14 @@ matchingSendInProgressOrInConflictMessage.date = [self parseRFC3339Date:[(NSDictionary *)objMessage objectForKey:@"created_at"]]; matchingSendInProgressOrInConflictMessage.id = messageID; matchingSendInProgressOrInConflictMessage.status = BITFeedbackMessageStatusRead; + NSArray *feedbackAttachments =[(NSDictionary *)objMessage objectForKey:@"attachments"]; + if (matchingSendInProgressOrInConflictMessage.attachments.count == feedbackAttachments.count) { + int attachmentIndex = 0; + for (BITFeedbackMessageAttachment* attachment in matchingSendInProgressOrInConflictMessage.attachments){ + attachment.id =feedbackAttachments[attachmentIndex][@"id"]; + attachmentIndex++; + } + } } else { if ([(NSDictionary *)objMessage objectForKey:@"clean_text"] || [(NSDictionary *)objMessage objectForKey:@"text"]) { BITFeedbackMessage *message = [[BITFeedbackMessage alloc] init]; @@ -835,17 +844,18 @@ [postBody appendData:[BITHockeyAppClient dataWithPostValue:self.userEmail forKey:@"email" boundary:boundary]]; } - [postBody appendData:[[NSString stringWithFormat:@"--%@--\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]]; NSInteger photoIndex = 0; - for (UIImage *image in message.photos){ - NSString *contentType = @"image/png"; - NSData* imageData = UIImagePNGRepresentation(image); - [postBody appendData:[BITHockeyAppClient dataWithPostValue:imageData forKey:[NSString stringWithFormat:@"attachment%ld", (long)photoIndex] contentType:contentType boundary:boundary]]; + for (BITFeedbackMessageAttachment *attachment in message.attachments){ + NSString *key = [NSString stringWithFormat:@"attachment%ld", (long)photoIndex]; + [postBody appendData:[BITHockeyAppClient dataWithPostValue:attachment.data forKey:key contentType:attachment.contentType boundary:boundary]]; photoIndex++; } + [postBody appendData:[[NSString stringWithFormat:@"--%@--\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]]; + + [request setHTTPBody:postBody]; } @@ -980,12 +990,12 @@ } } -- (void)submitMessageWithText:(NSString *)text andPhotos:(NSArray *)photos { +- (void)submitMessageWithText:(NSString *)text andAttachments:(NSArray *)attachments { BITFeedbackMessage *message = [[BITFeedbackMessage alloc] init]; message.text = text; [message setStatus:BITFeedbackMessageStatusSendPending]; [message setToken:[self uuidAsLowerCaseAndShortened]]; - [message setPhotos:photos]; + [message setAttachments:attachments]; [message setUserMessage:YES]; [_feedbackList addObject:message]; diff --git a/Classes/BITFeedbackManagerPrivate.h b/Classes/BITFeedbackManagerPrivate.h index 9748b0dc11..4deddcbd73 100644 --- a/Classes/BITFeedbackManagerPrivate.h +++ b/Classes/BITFeedbackManagerPrivate.h @@ -65,7 +65,7 @@ - (NSUInteger)numberOfMessages; - (BITFeedbackMessage *)messageAtIndex:(NSUInteger)index; -- (void)submitMessageWithText:(NSString *)text andPhotos:(NSArray *)photos; +- (void)submitMessageWithText:(NSString *)text andAttachments:(NSArray *)photos; - (void)submitPendingMessages; // Returns YES if manual user data can be entered, required or optional diff --git a/Classes/BITFeedbackMessage.h b/Classes/BITFeedbackMessage.h index 78bc124fb2..fe736d00ca 100644 --- a/Classes/BITFeedbackMessage.h +++ b/Classes/BITFeedbackMessage.h @@ -69,7 +69,7 @@ typedef NS_ENUM(NSInteger, BITFeedbackMessageStatus) { @property (nonatomic, copy) NSDate *date; @property (nonatomic, copy) NSNumber *id; @property (nonatomic, copy) NSString *token; -@property (nonatomic, strong) NSArray *photos; +@property (nonatomic, strong) NSArray *attachments; @property (nonatomic) BITFeedbackMessageStatus status; @property (nonatomic) BOOL userMessage; diff --git a/Classes/BITFeedbackMessage.m b/Classes/BITFeedbackMessage.m index b0860b6bdd..7d9fed3785 100644 --- a/Classes/BITFeedbackMessage.m +++ b/Classes/BITFeedbackMessage.m @@ -42,7 +42,7 @@ _email = nil; _date = [[NSDate alloc] init]; _token = nil; - _photos = nil; + _attachments = nil; _id = [[NSNumber alloc] initWithInteger:0]; _status = BITFeedbackMessageStatusSendPending; _userMessage = NO; @@ -60,7 +60,7 @@ [encoder encodeObject:self.email forKey:@"email"]; [encoder encodeObject:self.date forKey:@"date"]; [encoder encodeObject:self.id forKey:@"id"]; - [encoder encodeObject:self.photos forKey:@"photos"]; + [encoder encodeObject:self.attachments forKey:@"attachments"]; [encoder encodeInteger:self.status forKey:@"status"]; [encoder encodeBool:self.userMessage forKey:@"userMessage"]; [encoder encodeObject:self.token forKey:@"token"]; @@ -74,7 +74,7 @@ self.email = [decoder decodeObjectForKey:@"email"]; self.date = [decoder decodeObjectForKey:@"date"]; self.id = [decoder decodeObjectForKey:@"id"]; - self.photos = [decoder decodeObjectForKey:@"photos"]; + self.attachments = [decoder decodeObjectForKey:@"attachments"]; self.status = (BITFeedbackMessageStatus)[decoder decodeIntegerForKey:@"status"]; self.userMessage = [decoder decodeBoolForKey:@"userMessage"]; self.token = [decoder decodeObjectForKey:@"token"]; diff --git a/Classes/BITFeedbackMessageAttachment.h b/Classes/BITFeedbackMessageAttachment.h new file mode 100644 index 0000000000..082d9754c1 --- /dev/null +++ b/Classes/BITFeedbackMessageAttachment.h @@ -0,0 +1,47 @@ +/* + * Author: Moritz Haarmann + * + * Copyright (c) 2012-2014 HockeyApp, Bit Stadium GmbH. + * Copyright (c) 2011 Andreas Linde & Kent Sutherland. + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +#import +#import + +@interface BITFeedbackMessageAttachment : NSObject + +@property (nonatomic, copy) NSString *filename; +@property (nonatomic, copy) NSNumber *id; +@property (nonatomic, copy) NSString *contentType; + +@property (readonly) UIImage *imageRepresentation; +@property (readonly) NSData *data; + + ++ (BITFeedbackMessageAttachment *)attachmentWithData:(NSData *)data contentType:(NSString *)contentType; + +- (UIImage *)thumbnailWithSize:(CGSize)size; + +@end diff --git a/Classes/BITFeedbackMessageAttachment.m b/Classes/BITFeedbackMessageAttachment.m new file mode 100644 index 0000000000..f7d8ab90e9 --- /dev/null +++ b/Classes/BITFeedbackMessageAttachment.m @@ -0,0 +1,97 @@ +/* + * Author: Moritz Haarmann + * + * Copyright (c) 2012-2014 HockeyApp, Bit Stadium GmbH. + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + + +#import "BITFeedbackMessageAttachment.h" +#import "BITHockeyHelper.h" +#import "HockeySDKPrivate.h" + +@interface BITFeedbackMessageAttachment() + +@property (nonatomic, strong) NSData *data; +@property (nonatomic, strong) NSMutableDictionary *thumbnailRepresentations; + +@end + +@implementation BITFeedbackMessageAttachment + ++ (BITFeedbackMessageAttachment *)attachmentWithData:(NSData *)data contentType:(NSString *)contentType { + BITFeedbackMessageAttachment *newAttachment = [BITFeedbackMessageAttachment new]; + newAttachment.contentType = contentType; + newAttachment.data = data; + return newAttachment; +} + +-(id)init { + self = [super init]; + if (self){ + self.thumbnailRepresentations = [NSMutableDictionary new]; + } + return self; +} + +#pragma mark NSCoding + +- (void)encodeWithCoder:(NSCoder *)aCoder { + [aCoder encodeObject:self.contentType forKey:@"contentType"]; + [aCoder encodeObject:self.filename forKey:@"filename"]; + +} + +- (id)initWithCoder:(NSCoder *)aDecoder { + self = [super init]; + + if (self){ + self.contentType = [aDecoder decodeObjectForKey:@"contentType"]; + self.filename = [aDecoder decodeObjectForKey:@"filename"]; + self.thumbnailRepresentations = [NSMutableDictionary new]; + } + + return self; +} + +- (UIImage *)imageRepresentation { + if ([self.contentType rangeOfString:@"image"].location != NSNotFound){ + return [UIImage imageWithData:self.data]; + } else { + // return bit_imageNamed(@"feedbackActiviy.png", BITHOCKEYSDK_BUNDLE); + } +} + +- (UIImage *)thumbnailWithSize:(CGSize)size { + id cacheKey = [NSValue valueWithCGSize:size]; + +// if (!self.thumbnailRepresentations[cacheKey]){ +// UIImage *thumbnail =bit_imageToFitSize(self.imageRepresentation, size, YES); +// [self.thumbnailRepresentations setObject:thumbnail forKey:cacheKey]; +// } + + return self.thumbnailRepresentations[cacheKey]; +} + +@end diff --git a/Classes/BITImageAnnotationViewController.h b/Classes/BITImageAnnotationViewController.h new file mode 100644 index 0000000000..6321c9bd83 --- /dev/null +++ b/Classes/BITImageAnnotationViewController.h @@ -0,0 +1,15 @@ +// +// BITImageAnnotationViewController.h +// HockeySDK +// +// Created by Moritz Haarmann on 14.02.14. +// +// + +#import + +@interface BITImageAnnotationViewController : UIViewController + +@property (nonatomic, strong) UIImage *image; + +@end diff --git a/Classes/BITImageAnnotationViewController.m b/Classes/BITImageAnnotationViewController.m new file mode 100644 index 0000000000..e6101ab587 --- /dev/null +++ b/Classes/BITImageAnnotationViewController.m @@ -0,0 +1,57 @@ +// +// BITImageAnnotationViewController.m +// HockeySDK +// +// Created by Moritz Haarmann on 14.02.14. +// +// + +#import "BITImageAnnotationViewController.h" + +@interface BITImageAnnotationViewController () + +@property (nonatomic, strong) UIImageView *imageView; +@property (nonatomic, strong) UISegmentedControl *editingControls; +@property (nonatomic, strong) NSMutableArray *layers; + +@end + +@implementation BITImageAnnotationViewController + +- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil +{ + self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; + if (self) { + // Custom initialization + } + return self; +} + +- (void)viewDidLoad +{ + [super viewDidLoad]; + + self.editingControls = [[UISegmentedControl alloc] initWithItems:@[@"Arrow", @"Rect", @"Blur"]]; + + self.navigationItem.titleView = self.editingControls; + + [self.editingControls addTarget:self action:@selector(editingAction:) forControlEvents:UIControlEventTouchUpInside]; + + self.imageView = [[UIImageView alloc] initWithFrame:self.view.bounds]; + + self.imageView.image = self.image; + self.imageView.contentMode = UIViewContentModeScaleAspectFit; + // Do any additional setup after loading the view. +} + +-(void)editingAction:(id)sender { + +} + +- (void)didReceiveMemoryWarning +{ + [super didReceiveMemoryWarning]; + // Dispose of any resources that can be recreated. +} + +@end diff --git a/Support/HockeySDK.xcodeproj/project.pbxproj b/Support/HockeySDK.xcodeproj/project.pbxproj index c4c886eb2e..50cca02c96 100644 --- a/Support/HockeySDK.xcodeproj/project.pbxproj +++ b/Support/HockeySDK.xcodeproj/project.pbxproj @@ -133,6 +133,9 @@ 1EFF03E517F2485500A5F13C /* BITCrashManagerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 1EFF03E417F2485500A5F13C /* BITCrashManagerTests.m */; }; 97F0F9FD18ABAECD00EF50AA /* iconCamera.png in Resources */ = {isa = PBXBuildFile; fileRef = 97F0F9FB18ABAECD00EF50AA /* iconCamera.png */; }; 97F0F9FE18ABAECD00EF50AA /* iconCamera@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 97F0F9FC18ABAECD00EF50AA /* iconCamera@2x.png */; }; + 97F0FA0118AE375E00EF50AA /* BITImageAnnotationViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 97F0FA0018AE375E00EF50AA /* BITImageAnnotationViewController.m */; }; + 97F0FA0418AE5AED00EF50AA /* BITFeedbackMessageAttachment.m in Sources */ = {isa = PBXBuildFile; fileRef = 97F0FA0318AE5AED00EF50AA /* BITFeedbackMessageAttachment.m */; }; + 97F0FA0518B2294D00EF50AA /* BITFeedbackMessageAttachment.m in Sources */ = {isa = PBXBuildFile; fileRef = 97F0FA0318AE5AED00EF50AA /* BITFeedbackMessageAttachment.m */; }; E405266217A2AD300096359C /* BITFeedbackManagerDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = E405266117A2AD300096359C /* BITFeedbackManagerDelegate.h */; settings = {ATTRIBUTES = (Public, ); }; }; E40E0B0917DA19DC005E38C1 /* BITHockeyAppClientTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E40E0B0817DA19DC005E38C1 /* BITHockeyAppClientTests.m */; }; E40E0B0C17DA1AFF005E38C1 /* BITHockeyAppClient.h in Headers */ = {isa = PBXBuildFile; fileRef = E40E0B0A17DA1AFF005E38C1 /* BITHockeyAppClient.h */; }; @@ -293,6 +296,10 @@ 1EFF03E417F2485500A5F13C /* BITCrashManagerTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BITCrashManagerTests.m; sourceTree = ""; }; 97F0F9FB18ABAECD00EF50AA /* iconCamera.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = iconCamera.png; sourceTree = ""; }; 97F0F9FC18ABAECD00EF50AA /* iconCamera@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "iconCamera@2x.png"; sourceTree = ""; }; + 97F0F9FF18AE375E00EF50AA /* BITImageAnnotationViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BITImageAnnotationViewController.h; sourceTree = ""; }; + 97F0FA0018AE375E00EF50AA /* BITImageAnnotationViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BITImageAnnotationViewController.m; sourceTree = ""; }; + 97F0FA0218AE5AED00EF50AA /* BITFeedbackMessageAttachment.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BITFeedbackMessageAttachment.h; sourceTree = ""; }; + 97F0FA0318AE5AED00EF50AA /* BITFeedbackMessageAttachment.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BITFeedbackMessageAttachment.m; sourceTree = ""; }; BEE0207C16C5107E004426EA /* hu */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hu; path = hu.lproj/HockeySDK.strings; sourceTree = ""; }; E400561D148D79B500EB22B9 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; E405266117A2AD300096359C /* BITFeedbackManagerDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BITFeedbackManagerDelegate.h; sourceTree = ""; }; @@ -444,6 +451,8 @@ children = ( 1E49A4361612223B00463151 /* BITFeedbackMessage.h */, 1E49A4371612223B00463151 /* BITFeedbackMessage.m */, + 97F0FA0218AE5AED00EF50AA /* BITFeedbackMessageAttachment.h */, + 97F0FA0318AE5AED00EF50AA /* BITFeedbackMessageAttachment.m */, 1E49A42D1612223B00463151 /* BITFeedbackComposeViewController.h */, 1E49A42E1612223B00463151 /* BITFeedbackComposeViewController.m */, 1EF95CA9162CB313000AE3AD /* BITFeedbackComposeViewControllerDelegate.h */, @@ -459,6 +468,8 @@ 1E49A4341612223B00463151 /* BITFeedbackManager.m */, E405266117A2AD300096359C /* BITFeedbackManagerDelegate.h */, 1E49A4351612223B00463151 /* BITFeedbackManagerPrivate.h */, + 97F0F9FF18AE375E00EF50AA /* BITImageAnnotationViewController.h */, + 97F0FA0018AE375E00EF50AA /* BITImageAnnotationViewController.m */, ); name = Feedback; sourceTree = ""; @@ -887,11 +898,12 @@ 1E49A4601612223B00463151 /* BITFeedbackUserDataViewController.m in Sources */, 1E49A4701612226D00463151 /* BITAppVersionMetaInfo.m in Sources */, 1E49A4761612226D00463151 /* BITUpdateManager.m in Sources */, + 1E49A4C1161222B900463151 /* BITHockeyHelper.m in Sources */, 1E49A4821612226D00463151 /* BITUpdateViewController.m in Sources */, E4B4DB7E17B435550099C67F /* BITAuthenticationViewController.m in Sources */, 1E49A4B2161222B900463151 /* BITHockeyBaseManager.m in Sources */, 1E49A4BB161222B900463151 /* BITHockeyBaseViewController.m in Sources */, - 1E49A4C1161222B900463151 /* BITHockeyHelper.m in Sources */, + 97F0FA0518B2294D00EF50AA /* BITFeedbackMessageAttachment.m in Sources */, 1E49A4C7161222B900463151 /* BITAppStoreHeader.m in Sources */, 1E49A4CD161222B900463151 /* BITStoreButton.m in Sources */, 1E49A4D3161222B900463151 /* BITWebTableViewCell.m in Sources */, @@ -910,6 +922,8 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 97F0FA0118AE375E00EF50AA /* BITImageAnnotationViewController.m in Sources */, + 97F0FA0418AE5AED00EF50AA /* BITFeedbackMessageAttachment.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; From 02dc42390ac68f30bcd5495d54ecc47b5dd39960 Mon Sep 17 00:00:00 2001 From: Moritz Haarmann Date: Mon, 17 Feb 2014 22:10:39 +0100 Subject: [PATCH 05/92] + Fixes File upload. --- Classes/BITHockeyAppClient.m | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/Classes/BITHockeyAppClient.m b/Classes/BITHockeyAppClient.m index 442dbaf31b..c3db96ae75 100644 --- a/Classes/BITHockeyAppClient.m +++ b/Classes/BITHockeyAppClient.m @@ -92,8 +92,16 @@ NSMutableData *postBody = [NSMutableData data]; [postBody appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]]; - [postBody appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\";\r\n", key] dataUsingEncoding:NSUTF8StringEncoding]]; - [postBody appendData:[[NSString stringWithFormat:@"Content-Type: %@\r\n\r\n", contentType] dataUsingEncoding:NSUTF8StringEncoding]]; + + // There's certainly a better way to check if we are supposed to send binary data here. + if ([contentType rangeOfString:@"text"].location == NSNotFound){ + [postBody appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"; filename=\"image.jpg\"\r\n", key] dataUsingEncoding:NSUTF8StringEncoding]]; + [postBody appendData:[[NSString stringWithFormat:@"Content-Type: %@\r\n", contentType] dataUsingEncoding:NSUTF8StringEncoding]]; + [postBody appendData:[[NSString stringWithFormat:@"Content-Transfer-Encoding: binary\r\n\r\n"] dataUsingEncoding:NSUTF8StringEncoding]]; + } else { + [postBody appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"\r\n", key] dataUsingEncoding:NSUTF8StringEncoding]]; + [postBody appendData:[[NSString stringWithFormat:@"Content-Type: %@\r\n\r\n", contentType] dataUsingEncoding:NSUTF8StringEncoding]]; + } [postBody appendData:value]; [postBody appendData:[@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]]; From da2c8268d992aa3b5eea02e530d85eee6ea3d6cf Mon Sep 17 00:00:00 2001 From: Moritz Haarmann Date: Mon, 17 Feb 2014 23:35:04 +0100 Subject: [PATCH 06/92] + File persistence for Attachments --- Classes/BITFeedbackMessageAttachment.h | 2 +- Classes/BITFeedbackMessageAttachment.m | 66 +++++++++++++++++++++++--- 2 files changed, 60 insertions(+), 8 deletions(-) diff --git a/Classes/BITFeedbackMessageAttachment.h b/Classes/BITFeedbackMessageAttachment.h index 082d9754c1..053b1192ef 100644 --- a/Classes/BITFeedbackMessageAttachment.h +++ b/Classes/BITFeedbackMessageAttachment.h @@ -35,9 +35,9 @@ @property (nonatomic, copy) NSString *filename; @property (nonatomic, copy) NSNumber *id; @property (nonatomic, copy) NSString *contentType; +@property (nonatomic, readonly) NSData *data; @property (readonly) UIImage *imageRepresentation; -@property (readonly) NSData *data; + (BITFeedbackMessageAttachment *)attachmentWithData:(NSData *)data contentType:(NSString *)contentType; diff --git a/Classes/BITFeedbackMessageAttachment.m b/Classes/BITFeedbackMessageAttachment.m index f7d8ab90e9..95a6e31272 100644 --- a/Classes/BITFeedbackMessageAttachment.m +++ b/Classes/BITFeedbackMessageAttachment.m @@ -31,11 +31,12 @@ #import "BITHockeyHelper.h" #import "HockeySDKPrivate.h" +#define kCacheFolderName @"hockey_attachments" + @interface BITFeedbackMessageAttachment() -@property (nonatomic, strong) NSData *data; @property (nonatomic, strong) NSMutableDictionary *thumbnailRepresentations; - +@property (nonatomic, strong) NSData *internalData; @end @implementation BITFeedbackMessageAttachment @@ -55,6 +56,24 @@ return self; } +-(void)setData:(NSData *)data { + self->_internalData = data; + self.filename = [self createFilename]; + [self->_internalData writeToFile:self.filename atomically:NO]; +} + +-(NSData *)data { + if (!self->_internalData && self.filename){ + self.internalData = [NSData dataWithContentsOfFile:self.filename]; + } + + if (self.internalData){ + return self.internalData; + } + + return nil; +} + #pragma mark NSCoding - (void)encodeWithCoder:(NSCoder *)aCoder { @@ -75,23 +94,56 @@ return self; } +#pragma mark - Thubmnails / Image Representation + - (UIImage *)imageRepresentation { if ([self.contentType rangeOfString:@"image"].location != NSNotFound){ + NSData *imageData = self.data; return [UIImage imageWithData:self.data]; } else { - // return bit_imageNamed(@"feedbackActiviy.png", BITHOCKEYSDK_BUNDLE); + return bit_imageNamed(@"feedbackActiviy.png", BITHOCKEYSDK_BUNDLE); // TODO add another placeholder. } } - (UIImage *)thumbnailWithSize:(CGSize)size { id cacheKey = [NSValue valueWithCGSize:size]; -// if (!self.thumbnailRepresentations[cacheKey]){ -// UIImage *thumbnail =bit_imageToFitSize(self.imageRepresentation, size, YES); -// [self.thumbnailRepresentations setObject:thumbnail forKey:cacheKey]; -// } + if (!self.thumbnailRepresentations[cacheKey]){ + UIImage *image = self.imageRepresentation; + UIImage *thumbnail = bit_imageToFitSize(image, size, NO); + if (thumbnail){ + [self.thumbnailRepresentations setObject:thumbnail forKey:cacheKey]; + } + } return self.thumbnailRepresentations[cacheKey]; } +#pragma mark - Persistence Helpers + +- (NSString *)createFilename { + NSArray* cachePathArray = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES); + NSString* cachePath = [cachePathArray lastObject]; + cachePath = [cachePath stringByAppendingPathComponent:kCacheFolderName]; + + BOOL isDirectory; + + if (![[NSFileManager defaultManager] fileExistsAtPath:cachePath isDirectory:&isDirectory]){ + [[NSFileManager defaultManager] createDirectoryAtPath:cachePath withIntermediateDirectories:YES attributes:nil error:nil]; + } + + NSString *uniqueString = [BITFeedbackMessageAttachment GetUUID]; + cachePath = [cachePath stringByAppendingPathComponent:uniqueString]; + + return cachePath; +} + ++ (NSString *)GetUUID +{ + CFUUIDRef theUUID = CFUUIDCreate(NULL); + CFStringRef string = CFUUIDCreateString(NULL, theUUID); + CFRelease(theUUID); + return (__bridge NSString *)string; +} + @end From 6b09e75cd6e7ef520b6321247a9a23ee9db344cc Mon Sep 17 00:00:00 2001 From: Moritz Haarmann Date: Mon, 17 Feb 2014 23:49:49 +0100 Subject: [PATCH 07/92] + Original Filename of picked image is used as attachment name. --- Classes/BITFeedbackComposeViewController.m | 72 +++++++++++----------- Classes/BITFeedbackManager.m | 2 +- Classes/BITFeedbackMessageAttachment.h | 2 +- Classes/BITFeedbackMessageAttachment.m | 4 ++ Classes/BITHockeyAppClient.h | 3 +- Classes/BITHockeyAppClient.m | 9 +-- 6 files changed, 47 insertions(+), 45 deletions(-) diff --git a/Classes/BITFeedbackComposeViewController.m b/Classes/BITFeedbackComposeViewController.m index f22b0237c5..d492f39314 100644 --- a/Classes/BITFeedbackComposeViewController.m +++ b/Classes/BITFeedbackComposeViewController.m @@ -50,12 +50,12 @@ @property (nonatomic, weak) BITFeedbackManager *manager; @property (nonatomic, strong) UITextView *textView; @property (nonatomic, strong) UIView *contentViewContainer; -@property (nonatomic, strong) UIScrollView *photoScrollView; -@property (nonatomic, strong) NSMutableArray *photoScrollViewImageViews; +@property (nonatomic, strong) UIScrollView *attachmentScrollView; +@property (nonatomic, strong) NSMutableArray *attachmentScrollViewImageViews; @property (nonatomic, strong) NSString *text; -@property (nonatomic, strong) NSMutableArray *photos; +@property (nonatomic, strong) NSMutableArray *attachments; @property (nonatomic, strong) UIView *textAccessoryView; @@ -76,8 +76,8 @@ _blockUserDataScreen = NO; _delegate = nil; _manager = [BITHockeyManager sharedHockeyManager].feedbackManager; - _photos = [NSMutableArray new]; - _photoScrollViewImageViews = [NSMutableArray new]; + _attachments = [NSMutableArray new]; + _attachmentScrollViewImageViews = [NSMutableArray new]; _text = nil; } @@ -185,12 +185,12 @@ self.textView.inputAccessoryView = self.textAccessoryView; // This could be a subclass, yet - self.photoScrollView = [[UIScrollView alloc] initWithFrame:CGRectZero]; - self.photoScrollView.scrollEnabled = YES; - self.photoScrollView.bounces = YES; - self.photoScrollView.autoresizingMask = UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight; + self.attachmentScrollView = [[UIScrollView alloc] initWithFrame:CGRectZero]; + self.attachmentScrollView.scrollEnabled = YES; + self.attachmentScrollView.bounces = YES; + self.attachmentScrollView.autoresizingMask = UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight; - [self.contentViewContainer addSubview:self.photoScrollView]; + [self.contentViewContainer addSubview:self.attachmentScrollView]; } - (void)viewWillAppear:(BOOL)animated { @@ -256,13 +256,13 @@ -(void)refreshPhotoScrollview { CGFloat scrollViewWidth = 0; - if (self.photos.count){ + if (self.attachments.count){ scrollViewWidth = 100; } CGRect textViewFrame = self.textView.frame; - CGRect scrollViewFrame = self.photoScrollView.frame; + CGRect scrollViewFrame = self.attachmentScrollView.frame; BOOL alreadySetup = CGRectGetWidth(scrollViewFrame) == scrollViewWidth; @@ -270,19 +270,19 @@ textViewFrame.size.width -= scrollViewWidth; scrollViewFrame = CGRectMake(CGRectGetMaxX(textViewFrame), self.view.frame.origin.y, scrollViewWidth, CGRectGetHeight(textViewFrame)); self.textView.frame = textViewFrame; - self.photoScrollView.frame = scrollViewFrame; - self.photoScrollView.contentInset = self.textView.contentInset; + self.attachmentScrollView.frame = scrollViewFrame; + self.attachmentScrollView.contentInset = self.textView.contentInset; } - for (UIView *subview in self.photoScrollView.subviews){ + for (UIView *subview in self.attachmentScrollView.subviews){ [subview removeFromSuperview]; } - if (self.photos.count > self.photoScrollViewImageViews.count){ - NSInteger numberOfViewsToCreate = self.photos.count - self.photoScrollViewImageViews.count; + if (self.attachments.count > self.attachmentScrollViewImageViews.count){ + NSInteger numberOfViewsToCreate = self.attachments.count - self.attachmentScrollViewImageViews.count; for (int i = 0;i -@property (nonatomic, copy) NSString *filename; @property (nonatomic, copy) NSNumber *id; +@property (nonatomic, copy) NSString *originalFilename; @property (nonatomic, copy) NSString *contentType; @property (nonatomic, readonly) NSData *data; diff --git a/Classes/BITFeedbackMessageAttachment.m b/Classes/BITFeedbackMessageAttachment.m index 95a6e31272..99865897f9 100644 --- a/Classes/BITFeedbackMessageAttachment.m +++ b/Classes/BITFeedbackMessageAttachment.m @@ -37,6 +37,8 @@ @property (nonatomic, strong) NSMutableDictionary *thumbnailRepresentations; @property (nonatomic, strong) NSData *internalData; +@property (nonatomic, copy) NSString *filename; + @end @implementation BITFeedbackMessageAttachment @@ -79,6 +81,7 @@ - (void)encodeWithCoder:(NSCoder *)aCoder { [aCoder encodeObject:self.contentType forKey:@"contentType"]; [aCoder encodeObject:self.filename forKey:@"filename"]; + [aCoder encodeObject:self.originalFilename forKey:@"originalFilename"]; } @@ -89,6 +92,7 @@ self.contentType = [aDecoder decodeObjectForKey:@"contentType"]; self.filename = [aDecoder decodeObjectForKey:@"filename"]; self.thumbnailRepresentations = [NSMutableDictionary new]; + self.originalFilename = [aDecoder decodeObjectForKey:@"originalFilename"]; } return self; diff --git a/Classes/BITHockeyAppClient.h b/Classes/BITHockeyAppClient.h index 617b9b392d..8d2fce15e1 100644 --- a/Classes/BITHockeyAppClient.h +++ b/Classes/BITHockeyAppClient.h @@ -138,6 +138,5 @@ * * @return NSData instance configured to be attached on a (post) URLRequest */ -+ (NSData *)dataWithPostValue:(NSData *)value forKey:(NSString *)key contentType:(NSString *)contentType boundary:(NSString *) boundary; - ++ (NSData *)dataWithPostValue:(NSData *)value forKey:(NSString *)key contentType:(NSString *)contentType boundary:(NSString *) boundary filename:(NSString *)filename; @end diff --git a/Classes/BITHockeyAppClient.m b/Classes/BITHockeyAppClient.m index c3db96ae75..39944284d8 100644 --- a/Classes/BITHockeyAppClient.m +++ b/Classes/BITHockeyAppClient.m @@ -85,23 +85,24 @@ } + (NSData *)dataWithPostValue:(NSString *)value forKey:(NSString *)key boundary:(NSString *) boundary { - return [self dataWithPostValue:[value dataUsingEncoding:NSUTF8StringEncoding] forKey:key contentType:@"text" boundary:boundary]; + return [self dataWithPostValue:[value dataUsingEncoding:NSUTF8StringEncoding] forKey:key contentType:@"text" boundary:boundary filename:nil]; } -+ (NSData *)dataWithPostValue:(NSData *)value forKey:(NSString *)key contentType:(NSString *)contentType boundary:(NSString *) boundary { ++ (NSData *)dataWithPostValue:(NSData *)value forKey:(NSString *)key contentType:(NSString *)contentType boundary:(NSString *) boundary filename:(NSString *)filename { NSMutableData *postBody = [NSMutableData data]; [postBody appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]]; // There's certainly a better way to check if we are supposed to send binary data here. - if ([contentType rangeOfString:@"text"].location == NSNotFound){ - [postBody appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"; filename=\"image.jpg\"\r\n", key] dataUsingEncoding:NSUTF8StringEncoding]]; + if (filename){ + [postBody appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"; filename=\"%@\"\r\n", key, filename] dataUsingEncoding:NSUTF8StringEncoding]]; [postBody appendData:[[NSString stringWithFormat:@"Content-Type: %@\r\n", contentType] dataUsingEncoding:NSUTF8StringEncoding]]; [postBody appendData:[[NSString stringWithFormat:@"Content-Transfer-Encoding: binary\r\n\r\n"] dataUsingEncoding:NSUTF8StringEncoding]]; } else { [postBody appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"\r\n", key] dataUsingEncoding:NSUTF8StringEncoding]]; [postBody appendData:[[NSString stringWithFormat:@"Content-Type: %@\r\n\r\n", contentType] dataUsingEncoding:NSUTF8StringEncoding]]; } + [postBody appendData:value]; [postBody appendData:[@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]]; From 2146788f7b03ad69098958e99896c92fba7fcf7d Mon Sep 17 00:00:00 2001 From: moritz haarmann Date: Tue, 18 Feb 2014 10:56:40 +0100 Subject: [PATCH 08/92] Added Delete/Edit ActionSheet Fixed a Bug where the Feedback cannot be sent if no text is entered. --- Classes/BITFeedbackComposeViewController.m | 70 ++++++++++++++++------ 1 file changed, 52 insertions(+), 18 deletions(-) diff --git a/Classes/BITFeedbackComposeViewController.m b/Classes/BITFeedbackComposeViewController.m index d492f39314..1ab8c55785 100644 --- a/Classes/BITFeedbackComposeViewController.m +++ b/Classes/BITFeedbackComposeViewController.m @@ -43,7 +43,7 @@ #import "BITHockeyHelper.h" -@interface BITFeedbackComposeViewController () { +@interface BITFeedbackComposeViewController () { UIStatusBarStyle _statusBarStyle; } @@ -58,6 +58,7 @@ @property (nonatomic, strong) NSMutableArray *attachments; @property (nonatomic, strong) UIView *textAccessoryView; +@property (nonatomic) NSInteger selectedAttachmentIndex; @end @@ -217,10 +218,9 @@ if (_text) { self.textView.text = _text; - self.navigationItem.rightBarButtonItem.enabled = YES; - } else { - self.navigationItem.rightBarButtonItem.enabled = NO; } + + [self updateBarButtonState]; } - (void)viewDidAppear:(BOOL)animated { @@ -253,7 +253,7 @@ [super viewDidDisappear:animated]; } --(void)refreshPhotoScrollview { +-(void)refreshAttachmentScrollview { CGFloat scrollViewWidth = 0; if (self.attachments.count){ @@ -281,8 +281,9 @@ if (self.attachments.count > self.attachmentScrollViewImageViews.count){ NSInteger numberOfViewsToCreate = self.attachments.count - self.attachmentScrollViewImageViews.count; for (int i = 0;i Date: Tue, 18 Feb 2014 11:56:07 +0100 Subject: [PATCH 09/92] + Improved Persistence handling. --- Classes/BITFeedbackComposeViewController.m | 1 + Classes/BITFeedbackManager.m | 2 ++ Classes/BITFeedbackMessage.h | 7 +++++++ Classes/BITFeedbackMessage.m | 9 +++++++++ Classes/BITFeedbackMessageAttachment.h | 2 ++ Classes/BITFeedbackMessageAttachment.m | 8 ++++++++ Support/HockeySDK.xcodeproj/project.pbxproj | 2 -- 7 files changed, 29 insertions(+), 2 deletions(-) diff --git a/Classes/BITFeedbackComposeViewController.m b/Classes/BITFeedbackComposeViewController.m index 1ab8c55785..3d1bf6a144 100644 --- a/Classes/BITFeedbackComposeViewController.m +++ b/Classes/BITFeedbackComposeViewController.m @@ -458,6 +458,7 @@ if (self.selectedAttachmentIndex != NSNotFound){ BITFeedbackMessageAttachment* attachment = [self.attachments objectAtIndex:self.selectedAttachmentIndex]; + [attachment deleteContents]; // mandatory call to delete the files associatd. [self.attachments removeObject:attachment]; } diff --git a/Classes/BITFeedbackManager.m b/Classes/BITFeedbackManager.m index 75cf6a4882..58fb385137 100644 --- a/Classes/BITFeedbackManager.m +++ b/Classes/BITFeedbackManager.m @@ -572,6 +572,8 @@ - (BOOL)deleteMessageAtIndex:(NSUInteger)index { if (_feedbackList && [_feedbackList count] > index && [_feedbackList objectAtIndex:index]) { + BITFeedbackMessage *message = _feedbackList[index]; + [message deleteContents]; [_feedbackList removeObjectAtIndex:index]; [self saveMessages]; diff --git a/Classes/BITFeedbackMessage.h b/Classes/BITFeedbackMessage.h index fe736d00ca..baa6d6fdef 100644 --- a/Classes/BITFeedbackMessage.h +++ b/Classes/BITFeedbackMessage.h @@ -73,4 +73,11 @@ typedef NS_ENUM(NSInteger, BITFeedbackMessageStatus) { @property (nonatomic) BITFeedbackMessageStatus status; @property (nonatomic) BOOL userMessage; +/** + * This method must be called before a feedback message is deleted. It handles the + * deletion of any data stored on the device in association with the feedback message. + */ +-(void)deleteContents; + + @end diff --git a/Classes/BITFeedbackMessage.m b/Classes/BITFeedbackMessage.m index 7d9fed3785..eb44289600 100644 --- a/Classes/BITFeedbackMessage.m +++ b/Classes/BITFeedbackMessage.m @@ -28,6 +28,7 @@ #import "BITFeedbackMessage.h" +#import "BITFeedbackMessageAttachment.h" @implementation BITFeedbackMessage @@ -82,4 +83,12 @@ return self; } +#pragma mark - Deletion + +-(void)deleteContents { + for (BITFeedbackMessageAttachment *attachment in self.attachments){ + [attachment deleteContents]; + } +} + @end diff --git a/Classes/BITFeedbackMessageAttachment.h b/Classes/BITFeedbackMessageAttachment.h index 595bf51c65..77a4b92b11 100644 --- a/Classes/BITFeedbackMessageAttachment.h +++ b/Classes/BITFeedbackMessageAttachment.h @@ -44,4 +44,6 @@ - (UIImage *)thumbnailWithSize:(CGSize)size; +- (void)deleteContents; + @end diff --git a/Classes/BITFeedbackMessageAttachment.m b/Classes/BITFeedbackMessageAttachment.m index 99865897f9..ab8f8d8f46 100644 --- a/Classes/BITFeedbackMessageAttachment.m +++ b/Classes/BITFeedbackMessageAttachment.m @@ -150,4 +150,12 @@ return (__bridge NSString *)string; } +- (void)deleteContents { + if (self.filename){ + [[NSFileManager defaultManager] removeItemAtPath:self.filename error:nil]; + self.filename = nil; + } +} + + @end diff --git a/Support/HockeySDK.xcodeproj/project.pbxproj b/Support/HockeySDK.xcodeproj/project.pbxproj index 50cca02c96..d2e9b0864a 100644 --- a/Support/HockeySDK.xcodeproj/project.pbxproj +++ b/Support/HockeySDK.xcodeproj/project.pbxproj @@ -134,7 +134,6 @@ 97F0F9FD18ABAECD00EF50AA /* iconCamera.png in Resources */ = {isa = PBXBuildFile; fileRef = 97F0F9FB18ABAECD00EF50AA /* iconCamera.png */; }; 97F0F9FE18ABAECD00EF50AA /* iconCamera@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 97F0F9FC18ABAECD00EF50AA /* iconCamera@2x.png */; }; 97F0FA0118AE375E00EF50AA /* BITImageAnnotationViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 97F0FA0018AE375E00EF50AA /* BITImageAnnotationViewController.m */; }; - 97F0FA0418AE5AED00EF50AA /* BITFeedbackMessageAttachment.m in Sources */ = {isa = PBXBuildFile; fileRef = 97F0FA0318AE5AED00EF50AA /* BITFeedbackMessageAttachment.m */; }; 97F0FA0518B2294D00EF50AA /* BITFeedbackMessageAttachment.m in Sources */ = {isa = PBXBuildFile; fileRef = 97F0FA0318AE5AED00EF50AA /* BITFeedbackMessageAttachment.m */; }; E405266217A2AD300096359C /* BITFeedbackManagerDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = E405266117A2AD300096359C /* BITFeedbackManagerDelegate.h */; settings = {ATTRIBUTES = (Public, ); }; }; E40E0B0917DA19DC005E38C1 /* BITHockeyAppClientTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E40E0B0817DA19DC005E38C1 /* BITHockeyAppClientTests.m */; }; @@ -923,7 +922,6 @@ buildActionMask = 2147483647; files = ( 97F0FA0118AE375E00EF50AA /* BITImageAnnotationViewController.m in Sources */, - 97F0FA0418AE5AED00EF50AA /* BITFeedbackMessageAttachment.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; From 9c957b168803263612f4ea64163d2672c3798973 Mon Sep 17 00:00:00 2001 From: moritz haarmann Date: Mon, 24 Feb 2014 09:54:21 +0100 Subject: [PATCH 10/92] + Fixes Potentail crash in helper for resizing images. --- Classes/BITHockeyHelper.m | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Classes/BITHockeyHelper.m b/Classes/BITHockeyHelper.m index 11b29ad849..3a9cdca722 100644 --- a/Classes/BITHockeyHelper.m +++ b/Classes/BITHockeyHelper.m @@ -346,6 +346,11 @@ UIImage *bit_addGlossToImage(UIImage *inputImage) { #pragma mark UIImage helpers UIImage *bit_imageToFitSize(UIImage *inputImage, CGSize fitSize, BOOL honorScaleFactor) { + + if (!inputImage){ + return nil; + } + float imageScaleFactor = 1.0; if (honorScaleFactor) { if ([inputImage respondsToSelector:@selector(scale)]) { From 77b68f4508c20ee9ae9f59d97bd8477d23740b61 Mon Sep 17 00:00:00 2001 From: moritz haarmann Date: Mon, 24 Feb 2014 10:34:51 +0100 Subject: [PATCH 11/92] + List View Display of attachments. --- Classes/BITFeedbackListViewCell.h | 2 + Classes/BITFeedbackListViewCell.m | 69 +++++++++++++++++++++++-- Classes/BITFeedbackListViewController.m | 1 + 3 files changed, 68 insertions(+), 4 deletions(-) diff --git a/Classes/BITFeedbackListViewCell.h b/Classes/BITFeedbackListViewCell.h index 1a71800af1..4842cb1a18 100644 --- a/Classes/BITFeedbackListViewCell.h +++ b/Classes/BITFeedbackListViewCell.h @@ -71,4 +71,6 @@ typedef NS_ENUM(NSUInteger, BITFeedbackListViewCellBackgroundStyle) { + (CGFloat) heightForRowWithMessage:(BITFeedbackMessage *)message tableViewWidth:(CGFloat)width; +- (void)setAttachments:(NSArray *)attachments; + @end diff --git a/Classes/BITFeedbackListViewCell.m b/Classes/BITFeedbackListViewCell.m index 8c8e90bddb..6bf7ac6203 100644 --- a/Classes/BITFeedbackListViewCell.m +++ b/Classes/BITFeedbackListViewCell.m @@ -29,6 +29,7 @@ #import "BITFeedbackListViewCell.h" #import "HockeySDKPrivate.h" +#import "BITFeedbackMessageAttachment.h" #define BACKGROUNDCOLOR_DEFAULT BIT_RGBCOLOR(245, 245, 245) #define BACKGROUNDCOLOR_ALTERNATE BIT_RGBCOLOR(235, 235, 235) @@ -54,6 +55,9 @@ #define LABEL_TEXT_Y 25 +#define ATTACHMENT_SIZE 45 + + @interface BITFeedbackListViewCell () @property (nonatomic, strong) NSDateFormatter *dateFormatter; @@ -61,6 +65,8 @@ @property (nonatomic, strong) UILabel *labelTitle; +@property (nonatomic, strong) NSMutableArray *attachmentViews; + @end @@ -96,6 +102,8 @@ self.labelText.numberOfLines = 0; self.labelText.textAlignment = kBITTextLabelAlignmentLeft; self.labelText.dataDetectorTypes = UIDataDetectorTypeAll; + + self.attachmentViews = [NSMutableArray new]; } return self; } @@ -135,6 +143,19 @@ #pragma mark - Layout + (CGFloat) heightForRowWithMessage:(BITFeedbackMessage *)message tableViewWidth:(CGFloat)width { + + CGFloat baseHeight = [self heightForTextInRowWithMessage:message tableViewWidth:width]; + + CGFloat attachmentsPerRow = floorf(width / (FRAME_SIDE_BORDER + ATTACHMENT_SIZE)); + + CGFloat calculatedHeight = baseHeight + (FRAME_TOP_BORDER + ATTACHMENT_SIZE) * ceil(message.attachments.count/attachmentsPerRow); + + return ceil(calculatedHeight); +} + + + ++ (CGFloat) heightForTextInRowWithMessage:(BITFeedbackMessage *)message tableViewWidth:(CGFloat)width { CGFloat calculatedHeight; #if __IPHONE_OS_VERSION_MAX_ALLOWED > __IPHONE_6_1 @@ -143,7 +164,11 @@ options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading attributes:@{NSFontAttributeName:[UIFont systemFontOfSize:TEXT_FONTSIZE]} context:nil]; - calculatedHeight = calculatedRect.size.height + FRAME_TOP_BORDER + LABEL_TEXT_Y + FRAME_BOTTOM_BORDER; + calculatedHeight = calculatedRect.size.height + FRAME_TOP_BORDER + LABEL_TEXT_Y + FRAME_BOTTOM_BORDER; + + // added to make space for the images. + + } else { #endif #pragma clang diagnostic push @@ -151,6 +176,7 @@ calculatedHeight = [message.text sizeWithFont:[UIFont systemFontOfSize:TEXT_FONTSIZE] constrainedToSize:CGSizeMake(width - (2 * FRAME_SIDE_BORDER), CGFLOAT_MAX) ].height + FRAME_TOP_BORDER + LABEL_TEXT_Y + FRAME_BOTTOM_BORDER; + #pragma clang diagnostic pop #if __IPHONE_OS_VERSION_MAX_ALLOWED > __IPHONE_6_1 } @@ -159,6 +185,22 @@ return ceil(calculatedHeight); } +- (void)setAttachments:(NSArray *)attachments { + for (UIView *view in self.attachmentViews){ + [view removeFromSuperview]; + } + + [self.attachmentViews removeAllObjects]; + + for (BITFeedbackMessageAttachment *attachment in attachments){ + UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectZero]; + imageView.image = [attachment thumbnailWithSize:CGSizeMake(ATTACHMENT_SIZE, ATTACHMENT_SIZE)]; + [self.attachmentViews addObject:imageView]; + [self addSubview:imageView]; + } +} + + - (void)layoutSubviews { UIView *accessoryViewBackground = [[UIView alloc] initWithFrame:CGRectMake(0, 2, self.frame.size.width * 2, self.frame.size.height - 2)]; accessoryViewBackground.autoresizingMask = UIViewAutoresizingFlexibleHeight; @@ -208,13 +250,32 @@ // text [self.labelText setText:_message.text]; - CGSize size = CGSizeMake(self.frame.size.width - (2 * FRAME_SIDE_BORDER), - [[self class] heightForRowWithMessage:_message tableViewWidth:self.frame.size.width] - LABEL_TEXT_Y - FRAME_BOTTOM_BORDER); + CGSize sizeForTextLabel = CGSizeMake(self.frame.size.width - (2 * FRAME_SIDE_BORDER), + [[self class] heightForTextInRowWithMessage:_message tableViewWidth:self.frame.size.width] - LABEL_TEXT_Y - FRAME_BOTTOM_BORDER); - [self.labelText setFrame:CGRectMake(FRAME_SIDE_BORDER, LABEL_TEXT_Y, size.width, size.height)]; + [self.labelText setFrame:CGRectMake(FRAME_SIDE_BORDER, LABEL_TEXT_Y, sizeForTextLabel.width, sizeForTextLabel.height)]; [self addSubview:self.labelText]; + CGFloat baseOffsetOfText = CGRectGetMaxY(self.labelText.frame); + + + int i = 0; + + CGFloat attachmentsPerRow = ceilf(self.frame.size.width / (FRAME_SIDE_BORDER + ATTACHMENT_SIZE)); + + for ( UIImageView *imageView in self.attachmentViews){ + if ( !_message.userMessage){ + imageView.frame = CGRectMake(FRAME_SIDE_BORDER + (FRAME_SIDE_BORDER * ATTACHMENT_SIZE) * i , floor(i/attachmentsPerRow) + baseOffsetOfText , ATTACHMENT_SIZE, ATTACHMENT_SIZE); + } else { + imageView.frame = CGRectMake(self.frame.size.width - FRAME_SIDE_BORDER - ATTACHMENT_SIZE - ((FRAME_SIDE_BORDER * ATTACHMENT_SIZE) * (i) ), floor(i/attachmentsPerRow) + baseOffsetOfText , ATTACHMENT_SIZE, ATTACHMENT_SIZE); + + + }i++; + + } + + [super layoutSubviews]; } diff --git a/Classes/BITFeedbackListViewController.m b/Classes/BITFeedbackListViewController.m index 812634cac9..ff59a222f9 100644 --- a/Classes/BITFeedbackListViewController.m +++ b/Classes/BITFeedbackListViewController.m @@ -628,6 +628,7 @@ cell.message = message; cell.labelText.delegate = self; cell.labelText.userInteractionEnabled = YES; + [cell setAttachments:message.attachments]; if ( [self.manager isPreiOS7Environment] || From 1c120b95886d09c935c367f5dd7a910cec5e069f Mon Sep 17 00:00:00 2001 From: moritz haarmann Date: Mon, 24 Feb 2014 10:36:23 +0100 Subject: [PATCH 12/92] + Removes a warning. --- Classes/BITFeedbackMessageAttachment.m | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Classes/BITFeedbackMessageAttachment.m b/Classes/BITFeedbackMessageAttachment.m index ab8f8d8f46..9184a723cb 100644 --- a/Classes/BITFeedbackMessageAttachment.m +++ b/Classes/BITFeedbackMessageAttachment.m @@ -102,7 +102,6 @@ - (UIImage *)imageRepresentation { if ([self.contentType rangeOfString:@"image"].location != NSNotFound){ - NSData *imageData = self.data; return [UIImage imageWithData:self.data]; } else { return bit_imageNamed(@"feedbackActiviy.png", BITHOCKEYSDK_BUNDLE); // TODO add another placeholder. @@ -116,7 +115,7 @@ UIImage *image = self.imageRepresentation; UIImage *thumbnail = bit_imageToFitSize(image, size, NO); if (thumbnail){ - [self.thumbnailRepresentations setObject:thumbnail forKey:cacheKey]; + [self.thumbnailRepresentations setObject:thumbnail forKey:cacheKey]; } } From 3f7e6904a64bcef6dd3ef0a8c9ab6c6a47c66543 Mon Sep 17 00:00:00 2001 From: moritz haarmann Date: Mon, 24 Feb 2014 10:36:49 +0100 Subject: [PATCH 13/92] + Attachment handling --- Classes/BITFeedbackManager.m | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Classes/BITFeedbackManager.m b/Classes/BITFeedbackManager.m index 58fb385137..8eac1761af 100644 --- a/Classes/BITFeedbackManager.m +++ b/Classes/BITFeedbackManager.m @@ -709,7 +709,7 @@ } } } else { - if ([(NSDictionary *)objMessage objectForKey:@"clean_text"] || [(NSDictionary *)objMessage objectForKey:@"text"]) { + if ([(NSDictionary *)objMessage objectForKey:@"clean_text"] || [(NSDictionary *)objMessage objectForKey:@"text"] || [(NSDictionary *)objMessage objectForKey:@"attachments"]) { BITFeedbackMessage *message = [[BITFeedbackMessage alloc] init]; message.text = [(NSDictionary *)objMessage objectForKey:@"clean_text"] ?: [(NSDictionary *)objMessage objectForKey:@"text"] ?: @""; message.name = [(NSDictionary *)objMessage objectForKey:@"name"] ?: @""; @@ -1018,6 +1018,9 @@ } } + @end + + #endif /* HOCKEYSDK_FEATURE_FEEDBACK */ From 6cfc0ac2d2c1d40c9d30e48a238d8900183157b3 Mon Sep 17 00:00:00 2001 From: moritz haarmann Date: Mon, 24 Feb 2014 11:36:23 +0100 Subject: [PATCH 14/92] + Calculation problem. --- Classes/BITFeedbackListViewCell.m | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/Classes/BITFeedbackListViewCell.m b/Classes/BITFeedbackListViewCell.m index 6bf7ac6203..7be19e9a70 100644 --- a/Classes/BITFeedbackListViewCell.m +++ b/Classes/BITFeedbackListViewCell.m @@ -263,15 +263,16 @@ int i = 0; CGFloat attachmentsPerRow = ceilf(self.frame.size.width / (FRAME_SIDE_BORDER + ATTACHMENT_SIZE)); - for ( UIImageView *imageView in self.attachmentViews){ if ( !_message.userMessage){ - imageView.frame = CGRectMake(FRAME_SIDE_BORDER + (FRAME_SIDE_BORDER * ATTACHMENT_SIZE) * i , floor(i/attachmentsPerRow) + baseOffsetOfText , ATTACHMENT_SIZE, ATTACHMENT_SIZE); + imageView.frame = CGRectMake(FRAME_SIDE_BORDER + (FRAME_SIDE_BORDER + ATTACHMENT_SIZE) * i , floor(i/attachmentsPerRow) + baseOffsetOfText , ATTACHMENT_SIZE, ATTACHMENT_SIZE); } else { - imageView.frame = CGRectMake(self.frame.size.width - FRAME_SIDE_BORDER - ATTACHMENT_SIZE - ((FRAME_SIDE_BORDER * ATTACHMENT_SIZE) * (i) ), floor(i/attachmentsPerRow) + baseOffsetOfText , ATTACHMENT_SIZE, ATTACHMENT_SIZE); - - }i++; + imageView.frame = CGRectMake(self.frame.size.width - FRAME_SIDE_BORDER - ATTACHMENT_SIZE - ((FRAME_SIDE_BORDER + ATTACHMENT_SIZE) * (i) ), floor(i/attachmentsPerRow) + baseOffsetOfText , ATTACHMENT_SIZE, ATTACHMENT_SIZE); + + NSLog(@"ImageView %@", imageView); + } + i++; } From 21e5ca3b9db68411fc8ed1fdec3db9546d00ec8a Mon Sep 17 00:00:00 2001 From: moritz haarmann Date: Mon, 24 Feb 2014 11:37:18 +0100 Subject: [PATCH 15/92] + prepareWithItems accepts UIImages now. --- Classes/BITFeedbackComposeViewController.h | 1 + Classes/BITFeedbackComposeViewController.m | 11 ++++++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/Classes/BITFeedbackComposeViewController.h b/Classes/BITFeedbackComposeViewController.h index 488e3004e6..5df188c1da 100644 --- a/Classes/BITFeedbackComposeViewController.h +++ b/Classes/BITFeedbackComposeViewController.h @@ -70,6 +70,7 @@ The follwoing data object classes are currently supported: - NSString - NSURL + - UIImage These are automatically concatenated to one text string. diff --git a/Classes/BITFeedbackComposeViewController.m b/Classes/BITFeedbackComposeViewController.m index 3d1bf6a144..b9ab0f15c0 100644 --- a/Classes/BITFeedbackComposeViewController.m +++ b/Classes/BITFeedbackComposeViewController.m @@ -95,6 +95,9 @@ self.text = [(self.text ? self.text : @"") stringByAppendingFormat:@"%@%@", (self.text ? @" " : @""), item]; } else if ([item isKindOfClass:[NSURL class]]) { self.text = [(self.text ? self.text : @"") stringByAppendingFormat:@"%@%@", (self.text ? @" " : @""), [(NSURL *)item absoluteString]]; + } else if ([item isKindOfClass:[UIImage class]]) { + UIImage *image = item; + [self.attachments addObject:[BITFeedbackMessageAttachment attachmentWithData:UIImageJPEGRepresentation(image, 0.7f) contentType:@"image/jpeg"]]; } else { BITHockeyLog(@"Unknown item type %@", item); } @@ -221,6 +224,8 @@ } [self updateBarButtonState]; + + } - (void)viewDidAppear:(BOOL)animated { @@ -236,6 +241,9 @@ // Invoke delayed to fix iOS 7 iPad landscape bug, where this view will be moved if not called delayed [self.textView performSelector:@selector(becomeFirstResponder) withObject:nil afterDelay:0.0]; } + + [self refreshAttachmentScrollview]; + } - (void)viewWillDisappear:(BOOL)animated { @@ -264,7 +272,7 @@ CGRect scrollViewFrame = self.attachmentScrollView.frame; - BOOL alreadySetup = CGRectGetWidth(scrollViewFrame) == scrollViewWidth; + BOOL alreadySetup = CGRectGetWidth(scrollViewFrame) > 0; if (!alreadySetup){ textViewFrame.size.width -= scrollViewWidth; @@ -468,6 +476,7 @@ self.selectedAttachmentIndex = NSNotFound; } + @end #endif /* HOCKEYSDK_FEATURE_FEEDBACK */ From f1bb46e1b374bccea21c354855c4065772a83313 Mon Sep 17 00:00:00 2001 From: moritz haarmann Date: Mon, 24 Feb 2014 11:37:23 +0100 Subject: [PATCH 16/92] + More Cleanups --- Classes/BITFeedbackListViewCell.m | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Classes/BITFeedbackListViewCell.m b/Classes/BITFeedbackListViewCell.m index 7be19e9a70..c428d377af 100644 --- a/Classes/BITFeedbackListViewCell.m +++ b/Classes/BITFeedbackListViewCell.m @@ -264,14 +264,13 @@ CGFloat attachmentsPerRow = ceilf(self.frame.size.width / (FRAME_SIDE_BORDER + ATTACHMENT_SIZE)); for ( UIImageView *imageView in self.attachmentViews){ + if ( !_message.userMessage){ imageView.frame = CGRectMake(FRAME_SIDE_BORDER + (FRAME_SIDE_BORDER + ATTACHMENT_SIZE) * i , floor(i/attachmentsPerRow) + baseOffsetOfText , ATTACHMENT_SIZE, ATTACHMENT_SIZE); } else { - imageView.frame = CGRectMake(self.frame.size.width - FRAME_SIDE_BORDER - ATTACHMENT_SIZE - ((FRAME_SIDE_BORDER + ATTACHMENT_SIZE) * (i) ), floor(i/attachmentsPerRow) + baseOffsetOfText , ATTACHMENT_SIZE, ATTACHMENT_SIZE); - - NSLog(@"ImageView %@", imageView); } + i++; } From 1ddf9d5eb81745cf6912a575e7cce9d705aba826 Mon Sep 17 00:00:00 2001 From: moritz haarmann Date: Mon, 24 Feb 2014 12:12:51 +0100 Subject: [PATCH 17/92] + Added Feedback collection methods. --- Classes/BITFeedbackManager.h | 34 +++++++++++++++++++++++++ Classes/BITFeedbackManager.m | 49 ++++++++++++++++++++++++++++++++++-- 2 files changed, 81 insertions(+), 2 deletions(-) diff --git a/Classes/BITFeedbackManager.h b/Classes/BITFeedbackManager.h index 1f1543df26..e4059d1d2c 100644 --- a/Classes/BITFeedbackManager.h +++ b/Classes/BITFeedbackManager.h @@ -59,6 +59,25 @@ typedef NS_ENUM(NSInteger, BITFeedbackUserDataElement) { BITFeedbackUserDataElementRequired = 2 }; +/** + * Available modes for collecting automated feedback. + */ +typedef NS_ENUM(NSInteger, BITFeedbackObservationMode) { + /** + * No automatic feedback gathering. + */ + BITFeedbackObservationNone = 0, + /** + * Feedback compose form will open once a screenshot is taken. + */ + BITFeedbackObservationModeOnScreenshot = 1, + /** + * Feedback compose will open with a generated screenshot if the screen is tapped + * three fingers for three seconds. + */ + BITFeedbackObservationModeThreeFingersThreeSeconds = 2 +}; + @class BITFeedbackMessage; @protocol BITFeedbackManagerDelegate; @@ -241,6 +260,19 @@ typedef NS_ENUM(NSInteger, BITFeedbackUserDataElement) { */ - (void)showFeedbackComposeView; +/** + Present the modal feedback compose message user interface with the items given. + All NSString-Content in the array will be concatenated and result in the message, + while all UIImage and NSData-instances will be turned into attachments. + */ +- (void)showFeedbackComposeViewWithPreparedItems:(NSArray *)items; + +/** + Present the modal feedback compose message user interface with a screenshot that is taken + at the time of calling this method. + */ +- (void)showFeedbackComposeViewWithGeneratedScreenshot; + /** Create an feedback compose view @@ -262,5 +294,7 @@ typedef NS_ENUM(NSInteger, BITFeedbackUserDataElement) { */ - (BITFeedbackComposeViewController *)feedbackComposeViewController; +- (void)setFeedbackObservationMode:(BITFeedbackObservationMode)mode; + @end diff --git a/Classes/BITFeedbackManager.m b/Classes/BITFeedbackManager.m index 8eac1761af..c79dc986a3 100644 --- a/Classes/BITFeedbackManager.m +++ b/Classes/BITFeedbackManager.m @@ -29,6 +29,8 @@ #import "HockeySDK.h" +#import + #if HOCKEYSDK_FEATURE_FEEDBACK #import "HockeySDKPrivate.h" @@ -63,6 +65,8 @@ BOOL _incomingMessagesAlertShowing; BOOL _didEnterBackgroundState; BOOL _networkRequestInProgress; + + BITFeedbackObservationMode _observationMode; } #pragma mark - Initialization @@ -214,14 +218,23 @@ } - (void)showFeedbackComposeView { + [self showFeedbackComposeViewWithPreparedItems:nil]; +} + +- (void)showFeedbackComposeViewWithPreparedItems:(NSArray *)items{ if (_currentFeedbackComposeViewController) { BITHockeyLog(@"INFO: update view already visible, aborting"); return; } - - [self showView:[self feedbackComposeViewController]]; + BITFeedbackComposeViewController *composeView = [self feedbackComposeViewController]; + [composeView prepareWithItems:items]; + [self showView:composeView]; } +- (void)showFeedbackComposeViewWithGeneratedScreenshot { + UIImage *screenshot = bit_screenshot(); + [self showFeedbackComposeViewWithPreparedItems:@[screenshot]]; +} #pragma mark - Manager Control @@ -1018,6 +1031,38 @@ } } +#pragma mark - Observation Handling + +-(void)setFeedbackObservationMode:(BITFeedbackObservationMode)mode { + if (mode == BITFeedbackObservationModeOnScreenshot){ + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(screenshotNotificationReceived:) name:UIApplicationUserDidTakeScreenshotNotification object:nil]; + } +} + +-(void)screenshotNotificationReceived:(NSNotification *)notification { + +} + +-(void)extractLastPictureFromLibraryAndLaunchFeedback { + ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init]; + + [library enumerateGroupsWithTypes:ALAssetsGroupSavedPhotos usingBlock:^(ALAssetsGroup *group, BOOL *stop) { + + [group setAssetsFilter:[ALAssetsFilter allPhotos]]; + + [group enumerateAssetsWithOptions:NSEnumerationReverse usingBlock:^(ALAsset *alAsset, NSUInteger index, BOOL *innerStop) { + + if (alAsset) { + ALAssetRepresentation *representation = [alAsset defaultRepresentation]; + UIImage *latestPhoto = [UIImage imageWithCGImage:[representation fullScreenImage]]; + + *stop = YES; *innerStop = YES; + + // [self sendTweet:latestPhoto]; + } + }]; + } failureBlock: nil]; +} @end From 70e2b6635c9777a3f6b911b1fff7da5e67ecb2b7 Mon Sep 17 00:00:00 2001 From: moritz haarmann Date: Mon, 24 Feb 2014 12:17:34 +0100 Subject: [PATCH 18/92] + Screenshot Feedback Observation Mode is working on iOS7.. --- Classes/BITFeedbackManager.m | 5 ++--- Support/HockeySDK.xcodeproj/project.pbxproj | 4 ++++ 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/Classes/BITFeedbackManager.m b/Classes/BITFeedbackManager.m index c79dc986a3..e194a8fff0 100644 --- a/Classes/BITFeedbackManager.m +++ b/Classes/BITFeedbackManager.m @@ -1040,7 +1040,7 @@ } -(void)screenshotNotificationReceived:(NSNotification *)notification { - + [self extractLastPictureFromLibraryAndLaunchFeedback]; } -(void)extractLastPictureFromLibraryAndLaunchFeedback { @@ -1057,8 +1057,7 @@ UIImage *latestPhoto = [UIImage imageWithCGImage:[representation fullScreenImage]]; *stop = YES; *innerStop = YES; - - // [self sendTweet:latestPhoto]; + [self showFeedbackComposeViewWithPreparedItems:@[latestPhoto]]; } }]; } failureBlock: nil]; diff --git a/Support/HockeySDK.xcodeproj/project.pbxproj b/Support/HockeySDK.xcodeproj/project.pbxproj index d2e9b0864a..e9a92d28f4 100644 --- a/Support/HockeySDK.xcodeproj/project.pbxproj +++ b/Support/HockeySDK.xcodeproj/project.pbxproj @@ -131,6 +131,7 @@ 1EF95CA7162CB037000AE3AD /* BITFeedbackActivity.m in Sources */ = {isa = PBXBuildFile; fileRef = 1EF95CA5162CB036000AE3AD /* BITFeedbackActivity.m */; }; 1EF95CAA162CB314000AE3AD /* BITFeedbackComposeViewControllerDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 1EF95CA9162CB313000AE3AD /* BITFeedbackComposeViewControllerDelegate.h */; settings = {ATTRIBUTES = (Public, ); }; }; 1EFF03E517F2485500A5F13C /* BITCrashManagerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 1EFF03E417F2485500A5F13C /* BITCrashManagerTests.m */; }; + 9760F6C418BB4D2D00959B93 /* AssetsLibrary.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9760F6C318BB4D2D00959B93 /* AssetsLibrary.framework */; }; 97F0F9FD18ABAECD00EF50AA /* iconCamera.png in Resources */ = {isa = PBXBuildFile; fileRef = 97F0F9FB18ABAECD00EF50AA /* iconCamera.png */; }; 97F0F9FE18ABAECD00EF50AA /* iconCamera@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 97F0F9FC18ABAECD00EF50AA /* iconCamera@2x.png */; }; 97F0FA0118AE375E00EF50AA /* BITImageAnnotationViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 97F0FA0018AE375E00EF50AA /* BITImageAnnotationViewController.m */; }; @@ -293,6 +294,7 @@ 1EF95CA9162CB313000AE3AD /* BITFeedbackComposeViewControllerDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BITFeedbackComposeViewControllerDelegate.h; sourceTree = ""; }; 1EFF03D717F20F8300A5F13C /* BITCrashManagerPrivate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BITCrashManagerPrivate.h; sourceTree = ""; }; 1EFF03E417F2485500A5F13C /* BITCrashManagerTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BITCrashManagerTests.m; sourceTree = ""; }; + 9760F6C318BB4D2D00959B93 /* AssetsLibrary.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AssetsLibrary.framework; path = System/Library/Frameworks/AssetsLibrary.framework; sourceTree = SDKROOT; }; 97F0F9FB18ABAECD00EF50AA /* iconCamera.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = iconCamera.png; sourceTree = ""; }; 97F0F9FC18ABAECD00EF50AA /* iconCamera@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "iconCamera@2x.png"; sourceTree = ""; }; 97F0F9FF18AE375E00EF50AA /* BITImageAnnotationViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BITImageAnnotationViewController.h; sourceTree = ""; }; @@ -324,6 +326,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 9760F6C418BB4D2D00959B93 /* AssetsLibrary.framework in Frameworks */, 1E5954DC15B6F24A00A03429 /* Foundation.framework in Frameworks */, 1E5954DD15B6F24A00A03429 /* CrashReporter.framework in Frameworks */, ); @@ -573,6 +576,7 @@ E400561C148D79B500EB22B9 /* Frameworks */ = { isa = PBXGroup; children = ( + 9760F6C318BB4D2D00959B93 /* AssetsLibrary.framework */, E41EB48B148D7C4E0015DEDC /* CrashReporter.framework */, E400561D148D79B500EB22B9 /* Foundation.framework */, 1E5A459116F0DFC200B55C04 /* SenTestingKit.framework */, From 28eee0eb3d573ed2dd45cc5ae41b95c10220fccc Mon Sep 17 00:00:00 2001 From: moritz haarmann Date: Tue, 25 Feb 2014 15:07:59 +0100 Subject: [PATCH 19/92] + Some basic image editing functionality. --- Classes/BITFeedbackComposeViewController.m | 34 ++++++++- Classes/BITFeedbackManager.m | 2 +- Classes/BITFeedbackMessageAttachment.h | 2 + Classes/BITFeedbackMessageAttachment.m | 5 ++ Classes/BITImageAnnotation.h | 13 ++++ Classes/BITImageAnnotation.m | 34 +++++++++ Classes/BITImageAnnotationViewController.h | 10 +++ Classes/BITImageAnnotationViewController.m | 76 ++++++++++++++++++++- Classes/BITRectangleImageAnnotation.h | 13 ++++ Classes/BITRectangleImageAnnotation.m | 49 +++++++++++++ Support/HockeySDK.xcodeproj/project.pbxproj | 32 +++++++-- 11 files changed, 261 insertions(+), 9 deletions(-) create mode 100644 Classes/BITImageAnnotation.h create mode 100644 Classes/BITImageAnnotation.m create mode 100644 Classes/BITRectangleImageAnnotation.h create mode 100644 Classes/BITRectangleImageAnnotation.m diff --git a/Classes/BITFeedbackComposeViewController.m b/Classes/BITFeedbackComposeViewController.m index b9ab0f15c0..ac687dc263 100644 --- a/Classes/BITFeedbackComposeViewController.m +++ b/Classes/BITFeedbackComposeViewController.m @@ -42,8 +42,10 @@ #import "BITHockeyHelper.h" +#import "BITImageAnnotationViewController.h" -@interface BITFeedbackComposeViewController () { + +@interface BITFeedbackComposeViewController () { UIStatusBarStyle _statusBarStyle; } @@ -469,12 +471,38 @@ [attachment deleteContents]; // mandatory call to delete the files associatd. [self.attachments removeObject:attachment]; } - + self.selectedAttachmentIndex = NSNotFound; + + [self refreshAttachmentScrollview]; + } else { + if (self.selectedAttachmentIndex != NSNotFound){ + BITFeedbackMessageAttachment* attachment = [self.attachments objectAtIndex:self.selectedAttachmentIndex]; + BITImageAnnotationViewController *annotationEditor = [[BITImageAnnotationViewController alloc ] init]; + annotationEditor.delegate = self; + UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:annotationEditor]; + annotationEditor.image = attachment.imageRepresentation; + [self presentViewController:navController animated:YES completion:nil]; + } + + } + + +} + +#pragma mark - Image Annotation Delegate + +- (void)annotationController:(BITImageAnnotationViewController *)annotationController didFinishWithImage:(UIImage *)image { + if (self.selectedAttachmentIndex != NSNotFound){ + BITFeedbackMessageAttachment* attachment = [self.attachments objectAtIndex:self.selectedAttachmentIndex]; + [attachment replaceData:UIImageJPEGRepresentation(image, 0.7f)]; [self refreshAttachmentScrollview]; } self.selectedAttachmentIndex = NSNotFound; - +} + +- (void)annotationControllerDidCancel:(BITImageAnnotationViewController *)annotationController { + self.selectedAttachmentIndex = NSNotFound; } @end diff --git a/Classes/BITFeedbackManager.m b/Classes/BITFeedbackManager.m index e194a8fff0..e727ea5233 100644 --- a/Classes/BITFeedbackManager.m +++ b/Classes/BITFeedbackManager.m @@ -1035,7 +1035,7 @@ -(void)setFeedbackObservationMode:(BITFeedbackObservationMode)mode { if (mode == BITFeedbackObservationModeOnScreenshot){ - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(screenshotNotificationReceived:) name:UIApplicationUserDidTakeScreenshotNotification object:nil]; + // [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(screenshotNotificationReceived:) name:UIApplicationUserDidTakeScreenshotNotification object:nil]; } } diff --git a/Classes/BITFeedbackMessageAttachment.h b/Classes/BITFeedbackMessageAttachment.h index 77a4b92b11..4a9a8ea646 100644 --- a/Classes/BITFeedbackMessageAttachment.h +++ b/Classes/BITFeedbackMessageAttachment.h @@ -44,6 +44,8 @@ - (UIImage *)thumbnailWithSize:(CGSize)size; +- (void)replaceData:(NSData *)data; + - (void)deleteContents; @end diff --git a/Classes/BITFeedbackMessageAttachment.m b/Classes/BITFeedbackMessageAttachment.m index 9184a723cb..eebc4693e5 100644 --- a/Classes/BITFeedbackMessageAttachment.m +++ b/Classes/BITFeedbackMessageAttachment.m @@ -76,6 +76,11 @@ return nil; } +- (void)replaceData:(NSData *)data { + self.data = data; + self.thumbnailRepresentations = [NSMutableDictionary new]; +} + #pragma mark NSCoding - (void)encodeWithCoder:(NSCoder *)aCoder { diff --git a/Classes/BITImageAnnotation.h b/Classes/BITImageAnnotation.h new file mode 100644 index 0000000000..51e3a8c634 --- /dev/null +++ b/Classes/BITImageAnnotation.h @@ -0,0 +1,13 @@ +// +// BITImageAnnotation.h +// HockeySDK +// +// Created by Moritz Haarmann on 24.02.14. +// +// + +#import + +@interface BITImageAnnotation : UIView + +@end diff --git a/Classes/BITImageAnnotation.m b/Classes/BITImageAnnotation.m new file mode 100644 index 0000000000..644a27b379 --- /dev/null +++ b/Classes/BITImageAnnotation.m @@ -0,0 +1,34 @@ +// +// BITImageAnnotation.m +// HockeySDK +// +// Created by Moritz Haarmann on 24.02.14. +// +// + +#import "BITImageAnnotation.h" + +@implementation BITImageAnnotation + +- (id)initWithFrame:(CGRect)frame +{ + self = [super initWithFrame:frame]; + if (self) { + // Initialization code + //self.backgroundColor = [UIColor redColor]; + } + return self; +} + + + +/* +// Only override drawRect: if you perform custom drawing. +// An empty implementation adversely affects performance during animation. +- (void)drawRect:(CGRect)rect +{ + // Drawing code +} +*/ + +@end diff --git a/Classes/BITImageAnnotationViewController.h b/Classes/BITImageAnnotationViewController.h index 6321c9bd83..d333d612c2 100644 --- a/Classes/BITImageAnnotationViewController.h +++ b/Classes/BITImageAnnotationViewController.h @@ -8,8 +8,18 @@ #import +@class BITImageAnnotationViewController; + +@protocol BITImageAnnotationDelegate + +- (void)annotationControllerDidCancel:(BITImageAnnotationViewController *)annotationController; +- (void)annotationController:(BITImageAnnotationViewController *)annotationController didFinishWithImage:(UIImage *)image; + +@end + @interface BITImageAnnotationViewController : UIViewController @property (nonatomic, strong) UIImage *image; +@property (nonatomic, weak) id delegate; @end diff --git a/Classes/BITImageAnnotationViewController.m b/Classes/BITImageAnnotationViewController.m index e6101ab587..a49800dab7 100644 --- a/Classes/BITImageAnnotationViewController.m +++ b/Classes/BITImageAnnotationViewController.m @@ -7,12 +7,19 @@ // #import "BITImageAnnotationViewController.h" +#import "BITImageAnnotation.h" +#import "BITRectangleImageAnnotation.h" @interface BITImageAnnotationViewController () @property (nonatomic, strong) UIImageView *imageView; @property (nonatomic, strong) UISegmentedControl *editingControls; -@property (nonatomic, strong) NSMutableArray *layers; +@property (nonatomic, strong) NSMutableArray *objects; +@property (nonatomic, strong) UIPanGestureRecognizer *panRecognizer; +@property (nonatomic, strong) UITapGestureRecognizer *tapRecognizer; + +@property (nonatomic) CGPoint panStart; +@property (nonatomic,strong) BITImageAnnotation *currentAnnotation; @end @@ -41,11 +48,78 @@ self.imageView.image = self.image; self.imageView.contentMode = UIViewContentModeScaleAspectFit; + + + [self.view addSubview:self.imageView]; + self.imageView.frame = self.view.bounds; + + + self.tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapped:)]; + self.panRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(panned:)]; + + [self.tapRecognizer requireGestureRecognizerToFail:self.panRecognizer]; + + [self.view addGestureRecognizer:self.tapRecognizer]; + [self.view addGestureRecognizer:self.panRecognizer]; + + self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc ] initWithTitle:@"Discard" style:UIBarButtonItemStyleBordered target:self action:@selector(discard:)]; + self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc ] initWithTitle:@"Save" style:UIBarButtonItemStyleBordered target:self action:@selector(save:)]; + // Do any additional setup after loading the view. } -(void)editingAction:(id)sender { +} + +- (BITImageAnnotation *)annotationForCurrentMode { + if (self.editingControls.selectedSegmentIndex == 0){ + return [[BITRectangleImageAnnotation alloc] initWithFrame:CGRectZero]; + } else { + return [[BITImageAnnotation alloc] initWithFrame:CGRectZero]; + } +} + +#pragma mark - Actions + +- (void)discard:(id)sender { + [self.delegate annotationControllerDidCancel:self]; + [self dismissModalViewControllerAnimated:YES]; +} + +- (void)save:(id)sender { + UIImage *image = [self extractImage]; + [self.delegate annotationController:self didFinishWithImage:image]; + [self dismissModalViewControllerAnimated:YES]; +} + +- (UIImage *)extractImage { + UIGraphicsBeginImageContextWithOptions(self.view.bounds.size, YES, 0.0); + CGContextRef ctx = UIGraphicsGetCurrentContext(); + [self.view.layer renderInContext:ctx]; + UIImage *renderedImageOfMyself = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); + return renderedImageOfMyself; +} + +#pragma mark - Gesture Handling + +- (void)panned:(UIPanGestureRecognizer *)gestureRecognizer { + if (gestureRecognizer.state == UIGestureRecognizerStateBegan){ + self.currentAnnotation = [self annotationForCurrentMode]; + + [self.view insertSubview:self.currentAnnotation aboveSubview:self.imageView]; + self.panStart = [gestureRecognizer locationInView:self.imageView]; + } else if (gestureRecognizer.state == UIGestureRecognizerStateChanged){ + CGPoint bla = [gestureRecognizer translationInView:self.imageView]; + self.currentAnnotation.frame = CGRectMake(self.panStart.x, self.panStart.y, bla.x, bla.y); + } + +} + +-(void)tapped:(UITapGestureRecognizer *)gestureRecognizer { + + } - (void)didReceiveMemoryWarning diff --git a/Classes/BITRectangleImageAnnotation.h b/Classes/BITRectangleImageAnnotation.h new file mode 100644 index 0000000000..0ba16c0c68 --- /dev/null +++ b/Classes/BITRectangleImageAnnotation.h @@ -0,0 +1,13 @@ +// +// BITRectangleImageAnnotation.h +// HockeySDK +// +// Created by Moritz Haarmann on 25.02.14. +// +// + +#import "BITImageAnnotation.h" + +@interface BITRectangleImageAnnotation : BITImageAnnotation + +@end diff --git a/Classes/BITRectangleImageAnnotation.m b/Classes/BITRectangleImageAnnotation.m new file mode 100644 index 0000000000..465008fec2 --- /dev/null +++ b/Classes/BITRectangleImageAnnotation.m @@ -0,0 +1,49 @@ +// +// BITRectangleImageAnnotation.m +// HockeySDK +// +// Created by Moritz Haarmann on 25.02.14. +// +// + +#import "BITRectangleImageAnnotation.h" + +@interface BITRectangleImageAnnotation() + +@property (nonatomic, strong) CAShapeLayer *shapeLayer; + +@end + +@implementation BITRectangleImageAnnotation + +- (id)initWithFrame:(CGRect)frame +{ + self = [super initWithFrame:frame]; + if (self) { + self.shapeLayer = [CAShapeLayer layer]; + self.shapeLayer.strokeColor = [UIColor redColor].CGColor; + self.shapeLayer.lineWidth = 5; + self.shapeLayer.fillColor = [UIColor clearColor].CGColor; + [self.layer addSublayer:self.shapeLayer]; + + } + return self; +} + +- (void)layoutSubviews { + [super layoutSubviews]; + + self.shapeLayer.frame = self.bounds; + self.shapeLayer.path = [UIBezierPath bezierPathWithRoundedRect:self.bounds cornerRadius:10].CGPath; +} + +/* +// Only override drawRect: if you perform custom drawing. +// An empty implementation adversely affects performance during animation. +- (void)drawRect:(CGRect)rect +{ + // Drawing code +} +*/ + +@end diff --git a/Support/HockeySDK.xcodeproj/project.pbxproj b/Support/HockeySDK.xcodeproj/project.pbxproj index e9a92d28f4..002105aabb 100644 --- a/Support/HockeySDK.xcodeproj/project.pbxproj +++ b/Support/HockeySDK.xcodeproj/project.pbxproj @@ -131,10 +131,14 @@ 1EF95CA7162CB037000AE3AD /* BITFeedbackActivity.m in Sources */ = {isa = PBXBuildFile; fileRef = 1EF95CA5162CB036000AE3AD /* BITFeedbackActivity.m */; }; 1EF95CAA162CB314000AE3AD /* BITFeedbackComposeViewControllerDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 1EF95CA9162CB313000AE3AD /* BITFeedbackComposeViewControllerDelegate.h */; settings = {ATTRIBUTES = (Public, ); }; }; 1EFF03E517F2485500A5F13C /* BITCrashManagerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 1EFF03E417F2485500A5F13C /* BITCrashManagerTests.m */; }; + 973EC8B418BCA7BC00DBFFBB /* BITImageAnnotationViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 97F0FA0018AE375E00EF50AA /* BITImageAnnotationViewController.m */; }; + 973EC8B718BCA8A200DBFFBB /* BITRectangleImageAnnotation.h in Headers */ = {isa = PBXBuildFile; fileRef = 973EC8B518BCA8A200DBFFBB /* BITRectangleImageAnnotation.h */; }; + 973EC8B818BCA8A200DBFFBB /* BITRectangleImageAnnotation.m in Sources */ = {isa = PBXBuildFile; fileRef = 973EC8B618BCA8A200DBFFBB /* BITRectangleImageAnnotation.m */; }; 9760F6C418BB4D2D00959B93 /* AssetsLibrary.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9760F6C318BB4D2D00959B93 /* AssetsLibrary.framework */; }; + 9760F6CF18BB685600959B93 /* BITImageAnnotation.h in Headers */ = {isa = PBXBuildFile; fileRef = 9760F6CD18BB685600959B93 /* BITImageAnnotation.h */; }; + 9760F6D018BB685600959B93 /* BITImageAnnotation.m in Sources */ = {isa = PBXBuildFile; fileRef = 9760F6CE18BB685600959B93 /* BITImageAnnotation.m */; }; 97F0F9FD18ABAECD00EF50AA /* iconCamera.png in Resources */ = {isa = PBXBuildFile; fileRef = 97F0F9FB18ABAECD00EF50AA /* iconCamera.png */; }; 97F0F9FE18ABAECD00EF50AA /* iconCamera@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 97F0F9FC18ABAECD00EF50AA /* iconCamera@2x.png */; }; - 97F0FA0118AE375E00EF50AA /* BITImageAnnotationViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 97F0FA0018AE375E00EF50AA /* BITImageAnnotationViewController.m */; }; 97F0FA0518B2294D00EF50AA /* BITFeedbackMessageAttachment.m in Sources */ = {isa = PBXBuildFile; fileRef = 97F0FA0318AE5AED00EF50AA /* BITFeedbackMessageAttachment.m */; }; E405266217A2AD300096359C /* BITFeedbackManagerDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = E405266117A2AD300096359C /* BITFeedbackManagerDelegate.h */; settings = {ATTRIBUTES = (Public, ); }; }; E40E0B0917DA19DC005E38C1 /* BITHockeyAppClientTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E40E0B0817DA19DC005E38C1 /* BITHockeyAppClientTests.m */; }; @@ -294,7 +298,11 @@ 1EF95CA9162CB313000AE3AD /* BITFeedbackComposeViewControllerDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BITFeedbackComposeViewControllerDelegate.h; sourceTree = ""; }; 1EFF03D717F20F8300A5F13C /* BITCrashManagerPrivate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BITCrashManagerPrivate.h; sourceTree = ""; }; 1EFF03E417F2485500A5F13C /* BITCrashManagerTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BITCrashManagerTests.m; sourceTree = ""; }; + 973EC8B518BCA8A200DBFFBB /* BITRectangleImageAnnotation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BITRectangleImageAnnotation.h; sourceTree = ""; }; + 973EC8B618BCA8A200DBFFBB /* BITRectangleImageAnnotation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BITRectangleImageAnnotation.m; sourceTree = ""; }; 9760F6C318BB4D2D00959B93 /* AssetsLibrary.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AssetsLibrary.framework; path = System/Library/Frameworks/AssetsLibrary.framework; sourceTree = SDKROOT; }; + 9760F6CD18BB685600959B93 /* BITImageAnnotation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BITImageAnnotation.h; sourceTree = ""; }; + 9760F6CE18BB685600959B93 /* BITImageAnnotation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BITImageAnnotation.m; sourceTree = ""; }; 97F0F9FB18ABAECD00EF50AA /* iconCamera.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = iconCamera.png; sourceTree = ""; }; 97F0F9FC18ABAECD00EF50AA /* iconCamera@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "iconCamera@2x.png"; sourceTree = ""; }; 97F0F9FF18AE375E00EF50AA /* BITImageAnnotationViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BITImageAnnotationViewController.h; sourceTree = ""; }; @@ -451,6 +459,7 @@ 1E754E461621FA9A0070AB92 /* Feedback */ = { isa = PBXGroup; children = ( + 9760F6CC18BB684200959B93 /* Image Editor */, 1E49A4361612223B00463151 /* BITFeedbackMessage.h */, 1E49A4371612223B00463151 /* BITFeedbackMessage.m */, 97F0FA0218AE5AED00EF50AA /* BITFeedbackMessageAttachment.h */, @@ -470,8 +479,6 @@ 1E49A4341612223B00463151 /* BITFeedbackManager.m */, E405266117A2AD300096359C /* BITFeedbackManagerDelegate.h */, 1E49A4351612223B00463151 /* BITFeedbackManagerPrivate.h */, - 97F0F9FF18AE375E00EF50AA /* BITImageAnnotationViewController.h */, - 97F0FA0018AE375E00EF50AA /* BITImageAnnotationViewController.m */, ); name = Feedback; sourceTree = ""; @@ -548,6 +555,19 @@ name = Private; sourceTree = ""; }; + 9760F6CC18BB684200959B93 /* Image Editor */ = { + isa = PBXGroup; + children = ( + 97F0F9FF18AE375E00EF50AA /* BITImageAnnotationViewController.h */, + 97F0FA0018AE375E00EF50AA /* BITImageAnnotationViewController.m */, + 9760F6CD18BB685600959B93 /* BITImageAnnotation.h */, + 9760F6CE18BB685600959B93 /* BITImageAnnotation.m */, + 973EC8B518BCA8A200DBFFBB /* BITRectangleImageAnnotation.h */, + 973EC8B618BCA8A200DBFFBB /* BITRectangleImageAnnotation.m */, + ); + name = "Image Editor"; + sourceTree = ""; + }; E400560F148D79B500EB22B9 = { isa = PBXGroup; children = ( @@ -681,6 +701,7 @@ 1E49A4BE161222B900463151 /* BITHockeyHelper.h in Headers */, 1E49A4C4161222B900463151 /* BITAppStoreHeader.h in Headers */, 1E49A4CA161222B900463151 /* BITStoreButton.h in Headers */, + 973EC8B718BCA8A200DBFFBB /* BITRectangleImageAnnotation.h in Headers */, E405266217A2AD300096359C /* BITFeedbackManagerDelegate.h in Headers */, 1E49A4D0161222B900463151 /* BITWebTableViewCell.h in Headers */, 1E49A4D8161222D400463151 /* HockeySDKPrivate.h in Headers */, @@ -690,6 +711,7 @@ 1EACC97B162F041E007578C5 /* BITAttributedLabel.h in Headers */, 1E0FEE28173BDB260061331F /* BITKeychainUtils.h in Headers */, 1E94F9E416E9136B006570AD /* BITStoreUpdateManagerPrivate.h in Headers */, + 9760F6CF18BB685600959B93 /* BITImageAnnotation.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -894,6 +916,7 @@ 1E49A43F1612223B00463151 /* BITFeedbackComposeViewController.m in Sources */, E40E0B0D17DA1AFF005E38C1 /* BITHockeyAppClient.m in Sources */, 1E49A4451612223B00463151 /* BITFeedbackListViewCell.m in Sources */, + 973EC8B818BCA8A200DBFFBB /* BITRectangleImageAnnotation.m in Sources */, 1E49A44B1612223B00463151 /* BITFeedbackListViewController.m in Sources */, 1E49A4511612223B00463151 /* BITFeedbackManager.m in Sources */, E4933E8117B66CDA00B11ACC /* BITHTTPOperation.m in Sources */, @@ -905,6 +928,7 @@ 1E49A4821612226D00463151 /* BITUpdateViewController.m in Sources */, E4B4DB7E17B435550099C67F /* BITAuthenticationViewController.m in Sources */, 1E49A4B2161222B900463151 /* BITHockeyBaseManager.m in Sources */, + 9760F6D018BB685600959B93 /* BITImageAnnotation.m in Sources */, 1E49A4BB161222B900463151 /* BITHockeyBaseViewController.m in Sources */, 97F0FA0518B2294D00EF50AA /* BITFeedbackMessageAttachment.m in Sources */, 1E49A4C7161222B900463151 /* BITAppStoreHeader.m in Sources */, @@ -916,6 +940,7 @@ 1E754E611621FBB70070AB92 /* BITCrashReportTextFormatter.m in Sources */, 1EF95CA7162CB037000AE3AD /* BITFeedbackActivity.m in Sources */, 1EACC97C162F041E007578C5 /* BITAttributedLabel.m in Sources */, + 973EC8B418BCA7BC00DBFFBB /* BITImageAnnotationViewController.m in Sources */, 1E0FEE29173BDB260061331F /* BITKeychainUtils.m in Sources */, 1E94F9E216E91330006570AD /* BITStoreUpdateManager.m in Sources */, ); @@ -925,7 +950,6 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 97F0FA0118AE375E00EF50AA /* BITImageAnnotationViewController.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; From 35dc6c58d523144d016319b259d24c083371a357 Mon Sep 17 00:00:00 2001 From: moritz haarmann Date: Wed, 26 Feb 2014 10:10:56 +0100 Subject: [PATCH 20/92] + Fixes ActionSheet madness --- Classes/BITArrowImageAnnotation.h | 13 +++++++++ Classes/BITArrowImageAnnotation.m | 31 ++++++++++++++++++++++ Classes/BITFeedbackComposeViewController.m | 8 +++--- 3 files changed, 48 insertions(+), 4 deletions(-) create mode 100644 Classes/BITArrowImageAnnotation.h create mode 100644 Classes/BITArrowImageAnnotation.m diff --git a/Classes/BITArrowImageAnnotation.h b/Classes/BITArrowImageAnnotation.h new file mode 100644 index 0000000000..ff243af6a4 --- /dev/null +++ b/Classes/BITArrowImageAnnotation.h @@ -0,0 +1,13 @@ +// +// BITArrowImageAnnotation.h +// HockeySDK +// +// Created by Moritz Haarmann on 26.02.14. +// +// + +#import "BITImageAnnotation.h" + +@interface BITArrowImageAnnotation : BITImageAnnotation + +@end diff --git a/Classes/BITArrowImageAnnotation.m b/Classes/BITArrowImageAnnotation.m new file mode 100644 index 0000000000..d215bdb7a5 --- /dev/null +++ b/Classes/BITArrowImageAnnotation.m @@ -0,0 +1,31 @@ +// +// BITArrowImageAnnotation.m +// HockeySDK +// +// Created by Moritz Haarmann on 26.02.14. +// +// + +#import "BITArrowImageAnnotation.h" + +@implementation BITArrowImageAnnotation + +- (id)initWithFrame:(CGRect)frame +{ + self = [super initWithFrame:frame]; + if (self) { + // Initialization code + } + return self; +} + +/* +// Only override drawRect: if you perform custom drawing. +// An empty implementation adversely affects performance during animation. +- (void)drawRect:(CGRect)rect +{ + // Drawing code +} +*/ + +@end diff --git a/Classes/BITFeedbackComposeViewController.m b/Classes/BITFeedbackComposeViewController.m index ac687dc263..c2806e0dcb 100644 --- a/Classes/BITFeedbackComposeViewController.m +++ b/Classes/BITFeedbackComposeViewController.m @@ -421,8 +421,8 @@ self.selectedAttachmentIndex = index; UIActionSheet * actionSheet = [[UIActionSheet alloc] initWithTitle: nil delegate: self - cancelButtonTitle: @"Delete Attachment" - destructiveButtonTitle: nil + cancelButtonTitle:@"Cancel" + destructiveButtonTitle: @"Delete Attachment" otherButtonTitles: @"Edit Attachment", nil]; [actionSheet showFromRect: sender.frame inView: self.attachmentScrollView animated: YES]; @@ -464,7 +464,7 @@ #pragma mark - UIActionSheet Delegate - (void)actionSheet:(UIActionSheet *)actionSheet didDismissWithButtonIndex:(NSInteger)buttonIndex { - if (buttonIndex == [actionSheet cancelButtonIndex]){ + if (buttonIndex == [actionSheet destructiveButtonIndex]){ if (self.selectedAttachmentIndex != NSNotFound){ BITFeedbackMessageAttachment* attachment = [self.attachments objectAtIndex:self.selectedAttachmentIndex]; @@ -474,7 +474,7 @@ self.selectedAttachmentIndex = NSNotFound; [self refreshAttachmentScrollview]; - } else { + } else if(buttonIndex != [actionSheet cancelButtonIndex]){ if (self.selectedAttachmentIndex != NSNotFound){ BITFeedbackMessageAttachment* attachment = [self.attachments objectAtIndex:self.selectedAttachmentIndex]; BITImageAnnotationViewController *annotationEditor = [[BITImageAnnotationViewController alloc ] init]; From 77bab0d73df7a9d87f8f1c9c071cd8f355623b54 Mon Sep 17 00:00:00 2001 From: moritz haarmann Date: Wed, 26 Feb 2014 12:40:53 +0100 Subject: [PATCH 21/92] + Image Extraction from Annotation Controller is working --- Classes/BITArrowImageAnnotation.m | 100 +++++++++++++++++++- Classes/BITImageAnnotationViewController.m | 51 ++++++++-- Support/HockeySDK.xcodeproj/project.pbxproj | 8 ++ 3 files changed, 146 insertions(+), 13 deletions(-) diff --git a/Classes/BITArrowImageAnnotation.m b/Classes/BITArrowImageAnnotation.m index d215bdb7a5..472b5a019a 100644 --- a/Classes/BITArrowImageAnnotation.m +++ b/Classes/BITArrowImageAnnotation.m @@ -8,15 +8,47 @@ #import "BITArrowImageAnnotation.h" +#define kArrowPointCount 7 + + +@interface BITArrowImageAnnotation() + +@property (nonatomic, strong) CAShapeLayer *shapeLayer; + +@end + @implementation BITArrowImageAnnotation - (id)initWithFrame:(CGRect)frame { - self = [super initWithFrame:frame]; - if (self) { - // Initialization code - } - return self; + self = [super initWithFrame:frame]; + if (self) { + self.shapeLayer = [CAShapeLayer layer]; + self.shapeLayer.strokeColor = [UIColor redColor].CGColor; + self.shapeLayer.lineWidth = 5; + self.shapeLayer.fillColor = [UIColor clearColor].CGColor; + [self.layer addSublayer:self.shapeLayer]; + + } + return self; +} + +- (void)buildShape { + CGFloat topHeight = MAX(self.frame.size.width / 3.0f,20); + + CGFloat lineWidth = MAX(self.frame.size.width / 5.0f,20); + + UIBezierPath *path = [self bezierPathWithArrowFromPoint:CGPointMake(CGRectGetMinX(self.frame), CGRectGetMinY(self.frame)) toPoint:CGPointMake(CGRectGetMaxX(self.frame), CGRectGetMaxY(self.frame)) tailWidth:lineWidth headWidth:self.frame.size.height headLength:topHeight]; + + self.shapeLayer.path = path.CGPath; +} + +-(void)layoutSubviews{ + [super layoutSubviews]; + + [self buildShape]; + + self.shapeLayer.frame = self.bounds; } /* @@ -28,4 +60,62 @@ } */ +- (UIBezierPath *)bezierPathWithArrowFromPoint:(CGPoint)startPoint + toPoint:(CGPoint)endPoint + tailWidth:(CGFloat)tailWidth + headWidth:(CGFloat)headWidth + headLength:(CGFloat)headLength { + CGFloat length = hypotf(endPoint.x - startPoint.x, endPoint.y - startPoint.y); + + CGPoint points[kArrowPointCount]; + [self getAxisAlignedArrowPoints:points + forLength:length + tailWidth:tailWidth + headWidth:headWidth + headLength:headLength]; + + CGAffineTransform transform = [self transformForStartPoint:startPoint + endPoint:endPoint + length:length]; + + CGMutablePathRef cgPath = CGPathCreateMutable(); + CGPathAddLines(cgPath, &transform, points, sizeof points / sizeof *points); + CGPathCloseSubpath(cgPath); + + UIBezierPath *uiPath = [UIBezierPath bezierPathWithCGPath:cgPath]; + CGPathRelease(cgPath); + return uiPath; +} + +- (void)getAxisAlignedArrowPoints:(CGPoint[kArrowPointCount])points + forLength:(CGFloat)length + tailWidth:(CGFloat)tailWidth + headWidth:(CGFloat)headWidth + headLength:(CGFloat)headLength { + CGFloat tailLength = length - headLength; + points[0] = CGPointMake(0, tailWidth / 2); + points[1] = CGPointMake(tailLength, tailWidth / 2); + points[2] = CGPointMake(tailLength, headWidth / 2); + points[3] = CGPointMake(length, 0); + points[4] = CGPointMake(tailLength, -headWidth / 2); + points[5] = CGPointMake(tailLength, -tailWidth / 2); + points[6] = CGPointMake(0, -tailWidth / 2); +} + ++ (CGAffineTransform)dqd_transformForStartPoint:(CGPoint)startPoint + endPoint:(CGPoint)endPoint + length:(CGFloat)length { + CGFloat cosine = (endPoint.x - startPoint.x) / length; + CGFloat sine = (endPoint.y - startPoint.y) / length; + return (CGAffineTransform){ cosine, sine, -sine, cosine, startPoint.x, startPoint.y }; +} + +- (CGAffineTransform)transformForStartPoint:(CGPoint)startPoint + endPoint:(CGPoint)endPoint + length:(CGFloat)length { + CGFloat cosine = (endPoint.x - startPoint.x) / length; + CGFloat sine = (endPoint.y - startPoint.y) / length; + return (CGAffineTransform){ cosine, sine, -sine, cosine, startPoint.x, startPoint.y }; +} + @end diff --git a/Classes/BITImageAnnotationViewController.m b/Classes/BITImageAnnotationViewController.m index a49800dab7..2f37b351e8 100644 --- a/Classes/BITImageAnnotationViewController.m +++ b/Classes/BITImageAnnotationViewController.m @@ -9,6 +9,7 @@ #import "BITImageAnnotationViewController.h" #import "BITImageAnnotation.h" #import "BITRectangleImageAnnotation.h" +#import "BITArrowImageAnnotation.h" @interface BITImageAnnotationViewController () @@ -17,6 +18,7 @@ @property (nonatomic, strong) NSMutableArray *objects; @property (nonatomic, strong) UIPanGestureRecognizer *panRecognizer; @property (nonatomic, strong) UITapGestureRecognizer *tapRecognizer; +@property (nonatomic) CGFloat scaleFactor; @property (nonatomic) CGPoint panStart; @property (nonatomic,strong) BITImageAnnotation *currentAnnotation; @@ -38,32 +40,54 @@ { [super viewDidLoad]; + self.view.backgroundColor = [UIColor groupTableViewBackgroundColor]; + self.editingControls = [[UISegmentedControl alloc] initWithItems:@[@"Arrow", @"Rect", @"Blur"]]; self.navigationItem.titleView = self.editingControls; + + + self.objects = [NSMutableArray new]; [self.editingControls addTarget:self action:@selector(editingAction:) forControlEvents:UIControlEventTouchUpInside]; self.imageView = [[UIImageView alloc] initWithFrame:self.view.bounds]; + + + self.imageView.clipsToBounds = YES; + + self.imageView.layer.masksToBounds = YES; + self.imageView.image = self.image; - self.imageView.contentMode = UIViewContentModeScaleAspectFit; + self.imageView.contentMode = UIViewContentModeScaleToFill; [self.view addSubview:self.imageView]; self.imageView.frame = self.view.bounds; - self.tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapped:)]; self.panRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(panned:)]; [self.tapRecognizer requireGestureRecognizerToFail:self.panRecognizer]; - [self.view addGestureRecognizer:self.tapRecognizer]; - [self.view addGestureRecognizer:self.panRecognizer]; + [self.imageView addGestureRecognizer:self.tapRecognizer]; + [self.imageView addGestureRecognizer:self.panRecognizer]; + + self.imageView.userInteractionEnabled = YES; self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc ] initWithTitle:@"Discard" style:UIBarButtonItemStyleBordered target:self action:@selector(discard:)]; self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc ] initWithTitle:@"Save" style:UIBarButtonItemStyleBordered target:self action:@selector(save:)]; + + CGFloat heightScaleFactor = self.view.frame.size.height / self.image.size.height; + CGFloat widthScaleFactor = self.view.frame.size.width / self.image.size.width; + + CGFloat factor = MIN(heightScaleFactor, widthScaleFactor); + self.scaleFactor = factor; + CGSize scaledImageSize = CGSizeMake(self.image.size.width * factor, self.image.size.height * factor); + + self.imageView.frame = CGRectMake(self.view.frame.size.width/2 - scaledImageSize.width/2, self.view.frame.size.height/2 - scaledImageSize.height/2, scaledImageSize.width, scaledImageSize.height); + // Do any additional setup after loading the view. } @@ -75,6 +99,8 @@ - (BITImageAnnotation *)annotationForCurrentMode { if (self.editingControls.selectedSegmentIndex == 0){ return [[BITRectangleImageAnnotation alloc] initWithFrame:CGRectZero]; + } else if(self.editingControls.selectedSegmentIndex==1){ + return [[BITArrowImageAnnotation alloc] initWithFrame:CGRectZero]; } else { return [[BITImageAnnotation alloc] initWithFrame:CGRectZero]; } @@ -94,9 +120,18 @@ } - (UIImage *)extractImage { - UIGraphicsBeginImageContextWithOptions(self.view.bounds.size, YES, 0.0); + UIGraphicsBeginImageContextWithOptions(self.image.size, YES, 0.0); CGContextRef ctx = UIGraphicsGetCurrentContext(); - [self.view.layer renderInContext:ctx]; + [self.image drawInRect:CGRectMake(0, 0, self.image.size.width, self.image.size.height)]; + CGContextScaleCTM(ctx,1.0/self.scaleFactor,1.0f/self.scaleFactor); + + // Drawing all the annotations onto the final image. + for (BITImageAnnotation *annotation in self.objects){ + CGContextTranslateCTM(ctx, annotation.frame.origin.x, annotation.frame.origin.y); + [annotation.layer renderInContext:ctx]; + CGContextTranslateCTM(ctx,-1 * annotation.frame.origin.x,-1 * annotation.frame.origin.y); + } + UIImage *renderedImageOfMyself = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); return renderedImageOfMyself; @@ -107,8 +142,8 @@ - (void)panned:(UIPanGestureRecognizer *)gestureRecognizer { if (gestureRecognizer.state == UIGestureRecognizerStateBegan){ self.currentAnnotation = [self annotationForCurrentMode]; - - [self.view insertSubview:self.currentAnnotation aboveSubview:self.imageView]; + [self.objects addObject:self.currentAnnotation]; + [self.imageView insertSubview:self.currentAnnotation aboveSubview:self.imageView]; self.panStart = [gestureRecognizer locationInView:self.imageView]; } else if (gestureRecognizer.state == UIGestureRecognizerStateChanged){ CGPoint bla = [gestureRecognizer translationInView:self.imageView]; diff --git a/Support/HockeySDK.xcodeproj/project.pbxproj b/Support/HockeySDK.xcodeproj/project.pbxproj index 002105aabb..a2bce9973d 100644 --- a/Support/HockeySDK.xcodeproj/project.pbxproj +++ b/Support/HockeySDK.xcodeproj/project.pbxproj @@ -134,6 +134,8 @@ 973EC8B418BCA7BC00DBFFBB /* BITImageAnnotationViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 97F0FA0018AE375E00EF50AA /* BITImageAnnotationViewController.m */; }; 973EC8B718BCA8A200DBFFBB /* BITRectangleImageAnnotation.h in Headers */ = {isa = PBXBuildFile; fileRef = 973EC8B518BCA8A200DBFFBB /* BITRectangleImageAnnotation.h */; }; 973EC8B818BCA8A200DBFFBB /* BITRectangleImageAnnotation.m in Sources */ = {isa = PBXBuildFile; fileRef = 973EC8B618BCA8A200DBFFBB /* BITRectangleImageAnnotation.m */; }; + 973EC8BB18BDE29800DBFFBB /* BITArrowImageAnnotation.h in Headers */ = {isa = PBXBuildFile; fileRef = 973EC8B918BDE29800DBFFBB /* BITArrowImageAnnotation.h */; }; + 973EC8BC18BDE29800DBFFBB /* BITArrowImageAnnotation.m in Sources */ = {isa = PBXBuildFile; fileRef = 973EC8BA18BDE29800DBFFBB /* BITArrowImageAnnotation.m */; }; 9760F6C418BB4D2D00959B93 /* AssetsLibrary.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9760F6C318BB4D2D00959B93 /* AssetsLibrary.framework */; }; 9760F6CF18BB685600959B93 /* BITImageAnnotation.h in Headers */ = {isa = PBXBuildFile; fileRef = 9760F6CD18BB685600959B93 /* BITImageAnnotation.h */; }; 9760F6D018BB685600959B93 /* BITImageAnnotation.m in Sources */ = {isa = PBXBuildFile; fileRef = 9760F6CE18BB685600959B93 /* BITImageAnnotation.m */; }; @@ -300,6 +302,8 @@ 1EFF03E417F2485500A5F13C /* BITCrashManagerTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BITCrashManagerTests.m; sourceTree = ""; }; 973EC8B518BCA8A200DBFFBB /* BITRectangleImageAnnotation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BITRectangleImageAnnotation.h; sourceTree = ""; }; 973EC8B618BCA8A200DBFFBB /* BITRectangleImageAnnotation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BITRectangleImageAnnotation.m; sourceTree = ""; }; + 973EC8B918BDE29800DBFFBB /* BITArrowImageAnnotation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BITArrowImageAnnotation.h; sourceTree = ""; }; + 973EC8BA18BDE29800DBFFBB /* BITArrowImageAnnotation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BITArrowImageAnnotation.m; sourceTree = ""; }; 9760F6C318BB4D2D00959B93 /* AssetsLibrary.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AssetsLibrary.framework; path = System/Library/Frameworks/AssetsLibrary.framework; sourceTree = SDKROOT; }; 9760F6CD18BB685600959B93 /* BITImageAnnotation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BITImageAnnotation.h; sourceTree = ""; }; 9760F6CE18BB685600959B93 /* BITImageAnnotation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BITImageAnnotation.m; sourceTree = ""; }; @@ -564,6 +568,8 @@ 9760F6CE18BB685600959B93 /* BITImageAnnotation.m */, 973EC8B518BCA8A200DBFFBB /* BITRectangleImageAnnotation.h */, 973EC8B618BCA8A200DBFFBB /* BITRectangleImageAnnotation.m */, + 973EC8B918BDE29800DBFFBB /* BITArrowImageAnnotation.h */, + 973EC8BA18BDE29800DBFFBB /* BITArrowImageAnnotation.m */, ); name = "Image Editor"; sourceTree = ""; @@ -699,6 +705,7 @@ 1E49A4B5161222B900463151 /* BITHockeyBaseManagerPrivate.h in Headers */, E4933E8017B66CDA00B11ACC /* BITHTTPOperation.h in Headers */, 1E49A4BE161222B900463151 /* BITHockeyHelper.h in Headers */, + 973EC8BB18BDE29800DBFFBB /* BITArrowImageAnnotation.h in Headers */, 1E49A4C4161222B900463151 /* BITAppStoreHeader.h in Headers */, 1E49A4CA161222B900463151 /* BITStoreButton.h in Headers */, 973EC8B718BCA8A200DBFFBB /* BITRectangleImageAnnotation.h in Headers */, @@ -940,6 +947,7 @@ 1E754E611621FBB70070AB92 /* BITCrashReportTextFormatter.m in Sources */, 1EF95CA7162CB037000AE3AD /* BITFeedbackActivity.m in Sources */, 1EACC97C162F041E007578C5 /* BITAttributedLabel.m in Sources */, + 973EC8BC18BDE29800DBFFBB /* BITArrowImageAnnotation.m in Sources */, 973EC8B418BCA7BC00DBFFBB /* BITImageAnnotationViewController.m in Sources */, 1E0FEE29173BDB260061331F /* BITKeychainUtils.m in Sources */, 1E94F9E216E91330006570AD /* BITStoreUpdateManager.m in Sources */, From ad086273d7349b766047e2329c7618ecf2e0357e Mon Sep 17 00:00:00 2001 From: moritz haarmann Date: Wed, 26 Feb 2014 15:22:44 +0100 Subject: [PATCH 22/92] + Replicated Bugshot. --- Classes/BITArrowImageAnnotation.m | 39 ++++++++++++-- Classes/BITBlurImageAnnotation.h | 13 +++++ Classes/BITBlurImageAnnotation.m | 57 +++++++++++++++++++++ Classes/BITImageAnnotation.h | 4 +- Classes/BITImageAnnotationViewController.m | 17 +++--- Classes/BITRectangleImageAnnotation.m | 13 +++++ Support/HockeySDK.xcodeproj/project.pbxproj | 8 +++ 7 files changed, 138 insertions(+), 13 deletions(-) create mode 100644 Classes/BITBlurImageAnnotation.h create mode 100644 Classes/BITBlurImageAnnotation.m diff --git a/Classes/BITArrowImageAnnotation.m b/Classes/BITArrowImageAnnotation.m index 472b5a019a..b53bb502ff 100644 --- a/Classes/BITArrowImageAnnotation.m +++ b/Classes/BITArrowImageAnnotation.m @@ -14,6 +14,8 @@ @interface BITArrowImageAnnotation() @property (nonatomic, strong) CAShapeLayer *shapeLayer; +@property (nonatomic, strong) CAShapeLayer *strokeLayer; + @end @@ -27,7 +29,15 @@ self.shapeLayer.strokeColor = [UIColor redColor].CGColor; self.shapeLayer.lineWidth = 5; self.shapeLayer.fillColor = [UIColor clearColor].CGColor; + + self.strokeLayer = [CAShapeLayer layer]; + self.strokeLayer.strokeColor = [UIColor whiteColor].CGColor; + self.strokeLayer.lineWidth = 10; + self.strokeLayer.fillColor = [UIColor clearColor].CGColor; + [self.layer addSublayer:self.strokeLayer]; + [self.layer addSublayer:self.shapeLayer]; + } return self; @@ -35,20 +45,41 @@ - (void)buildShape { CGFloat topHeight = MAX(self.frame.size.width / 3.0f,20); + - CGFloat lineWidth = MAX(self.frame.size.width / 5.0f,20); + CGFloat lineWidth = MAX(self.frame.size.width / 10.0f,10); + CGFloat startX, startY, endX, endY; + if ( self.movedDelta.width > 0){ + startX = CGRectGetMinX(self.bounds); + endX = CGRectGetMaxX(self.bounds); + } else { + startX = CGRectGetMaxX(self.bounds); + endX = CGRectGetMinX(self.bounds); + + } - UIBezierPath *path = [self bezierPathWithArrowFromPoint:CGPointMake(CGRectGetMinX(self.frame), CGRectGetMinY(self.frame)) toPoint:CGPointMake(CGRectGetMaxX(self.frame), CGRectGetMaxY(self.frame)) tailWidth:lineWidth headWidth:self.frame.size.height headLength:topHeight]; + if ( self.movedDelta.height > 0){ + startY = CGRectGetMinY(self.bounds); + endY = CGRectGetMaxY(self.bounds); + } else { + startY = CGRectGetMaxY(self.bounds); + endY = CGRectGetMinY(self.bounds); + + } + + NSLog(@"Start X: %f, Y: %f, END: %f %f %@", startX, startY, endX,endY, self); + + UIBezierPath *path = [self bezierPathWithArrowFromPoint:CGPointMake(endX,endY) toPoint:CGPointMake(startX,startY) tailWidth:lineWidth headWidth:topHeight headLength:topHeight]; self.shapeLayer.path = path.CGPath; + self.strokeLayer.path = path.CGPath; } -(void)layoutSubviews{ [super layoutSubviews]; - + [self buildShape]; - self.shapeLayer.frame = self.bounds; } /* diff --git a/Classes/BITBlurImageAnnotation.h b/Classes/BITBlurImageAnnotation.h new file mode 100644 index 0000000000..b23d7f2d82 --- /dev/null +++ b/Classes/BITBlurImageAnnotation.h @@ -0,0 +1,13 @@ +// +// BITBlurImageAnnotation.h +// HockeySDK +// +// Created by Moritz Haarmann on 26.02.14. +// +// + +#import "BITImageAnnotation.h" + +@interface BITBlurImageAnnotation : BITImageAnnotation + +@end diff --git a/Classes/BITBlurImageAnnotation.m b/Classes/BITBlurImageAnnotation.m new file mode 100644 index 0000000000..898ce2a3c8 --- /dev/null +++ b/Classes/BITBlurImageAnnotation.m @@ -0,0 +1,57 @@ +// +// BITBlurImageAnnotation.m +// HockeySDK +// +// Created by Moritz Haarmann on 26.02.14. +// +// + +#import "BITBlurImageAnnotation.h" + +@interface BITBlurImageAnnotation() + +@property (nonatomic, strong) CALayer* imageLayer; +@property (nonatomic, strong) UIImage* scaledImage; + + +@end + +@implementation BITBlurImageAnnotation + +- (id)initWithFrame:(CGRect)frame +{ + self = [super initWithFrame:frame]; + if (self) { + self.clipsToBounds = YES; + self.imageLayer = [CALayer layer]; + [self.layer addSublayer:self.imageLayer]; + } + return self; +} + +-(void)setSourceImage:(UIImage *)sourceImage { + CGSize size = CGSizeMake(sourceImage.size.width/30, sourceImage.size.height/30); + + UIGraphicsBeginImageContext(size); + [sourceImage drawInRect:CGRectMake(0, 0, size.width, size.height)]; + self.scaledImage = UIGraphicsGetImageFromCurrentImageContext(); + self.imageLayer.contents = (id)self.scaledImage.CGImage; + UIGraphicsEndImageContext(); +} + +- (void)layoutSubviews { + [super layoutSubviews]; + self.imageLayer.frame = self.imageFrame; + self.imageLayer.masksToBounds = YES; +} + +/* +// Only override drawRect: if you perform custom drawing. +// An empty implementation adversely affects performance during animation. +- (void)drawRect:(CGRect)rect +{ + // Drawing code +} +*/ + +@end diff --git a/Classes/BITImageAnnotation.h b/Classes/BITImageAnnotation.h index 51e3a8c634..6449301275 100644 --- a/Classes/BITImageAnnotation.h +++ b/Classes/BITImageAnnotation.h @@ -9,5 +9,7 @@ #import @interface BITImageAnnotation : UIView - +@property (nonatomic) CGSize movedDelta; +@property (nonatomic, weak) UIImage *sourceImage; +@property (nonatomic) CGRect imageFrame; @end diff --git a/Classes/BITImageAnnotationViewController.m b/Classes/BITImageAnnotationViewController.m index 2f37b351e8..f997706023 100644 --- a/Classes/BITImageAnnotationViewController.m +++ b/Classes/BITImageAnnotationViewController.m @@ -10,6 +10,7 @@ #import "BITImageAnnotation.h" #import "BITRectangleImageAnnotation.h" #import "BITArrowImageAnnotation.h" +#import "BITBlurImageAnnotation.h" @interface BITImageAnnotationViewController () @@ -50,15 +51,12 @@ self.objects = [NSMutableArray new]; [self.editingControls addTarget:self action:@selector(editingAction:) forControlEvents:UIControlEventTouchUpInside]; + [self.editingControls setSelectedSegmentIndex:0]; self.imageView = [[UIImageView alloc] initWithFrame:self.view.bounds]; - - self.imageView.clipsToBounds = YES; - - self.imageView.layer.masksToBounds = YES; - + self.imageView.image = self.image; self.imageView.contentMode = UIViewContentModeScaleToFill; @@ -102,7 +100,7 @@ } else if(self.editingControls.selectedSegmentIndex==1){ return [[BITArrowImageAnnotation alloc] initWithFrame:CGRectZero]; } else { - return [[BITImageAnnotation alloc] initWithFrame:CGRectZero]; + return [[BITBlurImageAnnotation alloc] initWithFrame:CGRectZero]; } } @@ -143,11 +141,14 @@ if (gestureRecognizer.state == UIGestureRecognizerStateBegan){ self.currentAnnotation = [self annotationForCurrentMode]; [self.objects addObject:self.currentAnnotation]; + self.currentAnnotation.sourceImage = self.image; [self.imageView insertSubview:self.currentAnnotation aboveSubview:self.imageView]; self.panStart = [gestureRecognizer locationInView:self.imageView]; } else if (gestureRecognizer.state == UIGestureRecognizerStateChanged){ - CGPoint bla = [gestureRecognizer translationInView:self.imageView]; - self.currentAnnotation.frame = CGRectMake(self.panStart.x, self.panStart.y, bla.x, bla.y); + CGPoint bla = [gestureRecognizer locationInView:self.imageView]; + self.currentAnnotation.frame = CGRectMake(self.panStart.x, self.panStart.y, bla.x - self.panStart.x, bla.y - self.panStart.y); + self.currentAnnotation.movedDelta = CGSizeMake(bla.x - self.panStart.x, bla.y - self.panStart.y); + self.currentAnnotation.imageFrame = [self.view convertRect:self.imageView.frame toView:self.currentAnnotation]; } } diff --git a/Classes/BITRectangleImageAnnotation.m b/Classes/BITRectangleImageAnnotation.m index 465008fec2..5fd44c7536 100644 --- a/Classes/BITRectangleImageAnnotation.m +++ b/Classes/BITRectangleImageAnnotation.m @@ -11,6 +11,8 @@ @interface BITRectangleImageAnnotation() @property (nonatomic, strong) CAShapeLayer *shapeLayer; +@property (nonatomic, strong) CAShapeLayer *strokeLayer; + @end @@ -24,6 +26,13 @@ self.shapeLayer.strokeColor = [UIColor redColor].CGColor; self.shapeLayer.lineWidth = 5; self.shapeLayer.fillColor = [UIColor clearColor].CGColor; + + self.strokeLayer = [CAShapeLayer layer]; + self.strokeLayer.strokeColor = [UIColor whiteColor].CGColor; + self.strokeLayer.lineWidth = 10; + self.strokeLayer.fillColor = [UIColor clearColor].CGColor; + [self.layer addSublayer:self.strokeLayer]; + [self.layer addSublayer:self.shapeLayer]; } @@ -35,6 +44,10 @@ self.shapeLayer.frame = self.bounds; self.shapeLayer.path = [UIBezierPath bezierPathWithRoundedRect:self.bounds cornerRadius:10].CGPath; + + + self.strokeLayer.frame = self.bounds; + self.strokeLayer.path = [UIBezierPath bezierPathWithRoundedRect:self.bounds cornerRadius:10].CGPath; } /* diff --git a/Support/HockeySDK.xcodeproj/project.pbxproj b/Support/HockeySDK.xcodeproj/project.pbxproj index a2bce9973d..bf311915aa 100644 --- a/Support/HockeySDK.xcodeproj/project.pbxproj +++ b/Support/HockeySDK.xcodeproj/project.pbxproj @@ -136,6 +136,8 @@ 973EC8B818BCA8A200DBFFBB /* BITRectangleImageAnnotation.m in Sources */ = {isa = PBXBuildFile; fileRef = 973EC8B618BCA8A200DBFFBB /* BITRectangleImageAnnotation.m */; }; 973EC8BB18BDE29800DBFFBB /* BITArrowImageAnnotation.h in Headers */ = {isa = PBXBuildFile; fileRef = 973EC8B918BDE29800DBFFBB /* BITArrowImageAnnotation.h */; }; 973EC8BC18BDE29800DBFFBB /* BITArrowImageAnnotation.m in Sources */ = {isa = PBXBuildFile; fileRef = 973EC8BA18BDE29800DBFFBB /* BITArrowImageAnnotation.m */; }; + 973EC8BF18BE2B5B00DBFFBB /* BITBlurImageAnnotation.h in Headers */ = {isa = PBXBuildFile; fileRef = 973EC8BD18BE2B5B00DBFFBB /* BITBlurImageAnnotation.h */; }; + 973EC8C018BE2B5B00DBFFBB /* BITBlurImageAnnotation.m in Sources */ = {isa = PBXBuildFile; fileRef = 973EC8BE18BE2B5B00DBFFBB /* BITBlurImageAnnotation.m */; }; 9760F6C418BB4D2D00959B93 /* AssetsLibrary.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9760F6C318BB4D2D00959B93 /* AssetsLibrary.framework */; }; 9760F6CF18BB685600959B93 /* BITImageAnnotation.h in Headers */ = {isa = PBXBuildFile; fileRef = 9760F6CD18BB685600959B93 /* BITImageAnnotation.h */; }; 9760F6D018BB685600959B93 /* BITImageAnnotation.m in Sources */ = {isa = PBXBuildFile; fileRef = 9760F6CE18BB685600959B93 /* BITImageAnnotation.m */; }; @@ -304,6 +306,8 @@ 973EC8B618BCA8A200DBFFBB /* BITRectangleImageAnnotation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BITRectangleImageAnnotation.m; sourceTree = ""; }; 973EC8B918BDE29800DBFFBB /* BITArrowImageAnnotation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BITArrowImageAnnotation.h; sourceTree = ""; }; 973EC8BA18BDE29800DBFFBB /* BITArrowImageAnnotation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BITArrowImageAnnotation.m; sourceTree = ""; }; + 973EC8BD18BE2B5B00DBFFBB /* BITBlurImageAnnotation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BITBlurImageAnnotation.h; sourceTree = ""; }; + 973EC8BE18BE2B5B00DBFFBB /* BITBlurImageAnnotation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BITBlurImageAnnotation.m; sourceTree = ""; }; 9760F6C318BB4D2D00959B93 /* AssetsLibrary.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AssetsLibrary.framework; path = System/Library/Frameworks/AssetsLibrary.framework; sourceTree = SDKROOT; }; 9760F6CD18BB685600959B93 /* BITImageAnnotation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BITImageAnnotation.h; sourceTree = ""; }; 9760F6CE18BB685600959B93 /* BITImageAnnotation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BITImageAnnotation.m; sourceTree = ""; }; @@ -570,6 +574,8 @@ 973EC8B618BCA8A200DBFFBB /* BITRectangleImageAnnotation.m */, 973EC8B918BDE29800DBFFBB /* BITArrowImageAnnotation.h */, 973EC8BA18BDE29800DBFFBB /* BITArrowImageAnnotation.m */, + 973EC8BD18BE2B5B00DBFFBB /* BITBlurImageAnnotation.h */, + 973EC8BE18BE2B5B00DBFFBB /* BITBlurImageAnnotation.m */, ); name = "Image Editor"; sourceTree = ""; @@ -684,6 +690,7 @@ 1E49A4731612226D00463151 /* BITUpdateManager.h in Headers */, 1E49A4791612226D00463151 /* BITUpdateManagerDelegate.h in Headers */, 1E49A44E1612223B00463151 /* BITFeedbackManager.h in Headers */, + 973EC8BF18BE2B5B00DBFFBB /* BITBlurImageAnnotation.h in Headers */, E4B4DB7D17B435550099C67F /* BITAuthenticationViewController.h in Headers */, 1E49A4481612223B00463151 /* BITFeedbackListViewController.h in Headers */, 1E49A47F1612226D00463151 /* BITUpdateViewController.h in Headers */, @@ -935,6 +942,7 @@ 1E49A4821612226D00463151 /* BITUpdateViewController.m in Sources */, E4B4DB7E17B435550099C67F /* BITAuthenticationViewController.m in Sources */, 1E49A4B2161222B900463151 /* BITHockeyBaseManager.m in Sources */, + 973EC8C018BE2B5B00DBFFBB /* BITBlurImageAnnotation.m in Sources */, 9760F6D018BB685600959B93 /* BITImageAnnotation.m in Sources */, 1E49A4BB161222B900463151 /* BITHockeyBaseViewController.m in Sources */, 97F0FA0518B2294D00EF50AA /* BITFeedbackMessageAttachment.m in Sources */, From b269c8ee4aa7464fd4b46394e75d0ab1edced0cd Mon Sep 17 00:00:00 2001 From: moritz haarmann Date: Wed, 26 Feb 2014 15:23:03 +0100 Subject: [PATCH 23/92] + Wording --- Classes/BITImageAnnotationViewController.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Classes/BITImageAnnotationViewController.m b/Classes/BITImageAnnotationViewController.m index f997706023..b5f3aae62f 100644 --- a/Classes/BITImageAnnotationViewController.m +++ b/Classes/BITImageAnnotationViewController.m @@ -43,7 +43,7 @@ self.view.backgroundColor = [UIColor groupTableViewBackgroundColor]; - self.editingControls = [[UISegmentedControl alloc] initWithItems:@[@"Arrow", @"Rect", @"Blur"]]; + self.editingControls = [[UISegmentedControl alloc] initWithItems:@[@"Rectangle", @"Arrow", @"Blur"]]; self.navigationItem.titleView = self.editingControls; From 3890e9410123ad4b96557f160c61dbbde35e3839 Mon Sep 17 00:00:00 2001 From: moritz haarmann Date: Wed, 26 Feb 2014 15:32:00 +0100 Subject: [PATCH 24/92] + Removes Leak warning. --- Classes/BITFeedbackMessageAttachment.m | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/Classes/BITFeedbackMessageAttachment.m b/Classes/BITFeedbackMessageAttachment.m index eebc4693e5..b0d68b82fa 100644 --- a/Classes/BITFeedbackMessageAttachment.m +++ b/Classes/BITFeedbackMessageAttachment.m @@ -140,20 +140,12 @@ [[NSFileManager defaultManager] createDirectoryAtPath:cachePath withIntermediateDirectories:YES attributes:nil error:nil]; } - NSString *uniqueString = [BITFeedbackMessageAttachment GetUUID]; + NSString *uniqueString = bit_UUID(); cachePath = [cachePath stringByAppendingPathComponent:uniqueString]; return cachePath; } -+ (NSString *)GetUUID -{ - CFUUIDRef theUUID = CFUUIDCreate(NULL); - CFStringRef string = CFUUIDCreateString(NULL, theUUID); - CFRelease(theUUID); - return (__bridge NSString *)string; -} - - (void)deleteContents { if (self.filename){ [[NSFileManager defaultManager] removeItemAtPath:self.filename error:nil]; From cb48105663646fa57603e3e8c87a43afd694b8b0 Mon Sep 17 00:00:00 2001 From: Andreas Linde Date: Wed, 26 Feb 2014 15:33:03 +0100 Subject: [PATCH 25/92] Add required AssetsLibrary to the frameworks --- Support/HockeySDK.xcconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Support/HockeySDK.xcconfig b/Support/HockeySDK.xcconfig index 45a57e9a0e..012479d8a8 100644 --- a/Support/HockeySDK.xcconfig +++ b/Support/HockeySDK.xcconfig @@ -1,3 +1,3 @@ -OTHER_LDFLAGS=$(inherited) -framework CoreText -framework CoreGraphics -framework Foundation -framework QuartzCore -framework SystemConfiguration -framework UIKit -framework Security +OTHER_LDFLAGS=$(inherited) -framework CoreText -framework CoreGraphics -framework Foundation -framework QuartzCore -framework SystemConfiguration -framework UIKit -framework Security -framework AssetsLibrary HOCKEYSDK_DOCSET_NAME=HockeySDK-iOS GCC_PREPROCESSOR_DEFINITIONS=$(inherited) CONFIGURATION_$(CONFIGURATION) From 74f9e5d5ca4e91391768f5e7e41a5684228a016c Mon Sep 17 00:00:00 2001 From: moritz haarmann Date: Thu, 27 Feb 2014 11:16:22 +0100 Subject: [PATCH 26/92] + Prevent Messages without any text from ever being sent. --- Classes/BITFeedbackComposeViewController.m | 2 +- Classes/BITFeedbackListViewCell.m | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Classes/BITFeedbackComposeViewController.m b/Classes/BITFeedbackComposeViewController.m index c2806e0dcb..d4ab23793b 100644 --- a/Classes/BITFeedbackComposeViewController.m +++ b/Classes/BITFeedbackComposeViewController.m @@ -327,7 +327,7 @@ } - (void)updateBarButtonState { - if (self.textView.text.length == 0 && self.attachments.count == 0 ) { + if (self.textView.text.length > 0 ) { self.navigationItem.rightBarButtonItem.enabled = NO; } else { self.navigationItem.rightBarButtonItem.enabled = YES; diff --git a/Classes/BITFeedbackListViewCell.m b/Classes/BITFeedbackListViewCell.m index c428d377af..d0091e3b68 100644 --- a/Classes/BITFeedbackListViewCell.m +++ b/Classes/BITFeedbackListViewCell.m @@ -264,6 +264,7 @@ CGFloat attachmentsPerRow = ceilf(self.frame.size.width / (FRAME_SIDE_BORDER + ATTACHMENT_SIZE)); for ( UIImageView *imageView in self.attachmentViews){ + imageView.contentMode = UIViewContentModeScaleAspectFit; if ( !_message.userMessage){ imageView.frame = CGRectMake(FRAME_SIDE_BORDER + (FRAME_SIDE_BORDER + ATTACHMENT_SIZE) * i , floor(i/attachmentsPerRow) + baseOffsetOfText , ATTACHMENT_SIZE, ATTACHMENT_SIZE); From 672e38a7e296bb906106c920b6b9353edc80231b Mon Sep 17 00:00:00 2001 From: moritz haarmann Date: Thu, 6 Mar 2014 10:57:32 +0100 Subject: [PATCH 27/92] + Fixes a Bug where attachments in the compose view are hidden on an iPad. --- Classes/BITFeedbackComposeViewController.m | 7 +++++-- Classes/BITFeedbackManager.m | 2 ++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/Classes/BITFeedbackComposeViewController.m b/Classes/BITFeedbackComposeViewController.m index d4ab23793b..689e7083b5 100644 --- a/Classes/BITFeedbackComposeViewController.m +++ b/Classes/BITFeedbackComposeViewController.m @@ -104,6 +104,7 @@ BITHockeyLog(@"Unknown item type %@", item); } } + } @@ -165,6 +166,8 @@ // Container that contains both the textfield and eventually the photo scroll view on the right side self.contentViewContainer = [[UIView alloc] initWithFrame:self.view.bounds]; + self.contentViewContainer.autoresizingMask = UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight; + [self.view addSubview:self.contentViewContainer]; // message input textfield @@ -194,7 +197,7 @@ self.attachmentScrollView = [[UIScrollView alloc] initWithFrame:CGRectZero]; self.attachmentScrollView.scrollEnabled = YES; self.attachmentScrollView.bounces = YES; - self.attachmentScrollView.autoresizingMask = UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight; + self.attachmentScrollView.autoresizingMask = UIViewAutoresizingFlexibleHeight|UIViewAutoresizingFlexibleRightMargin; [self.contentViewContainer addSubview:self.attachmentScrollView]; } @@ -278,7 +281,7 @@ if (!alreadySetup){ textViewFrame.size.width -= scrollViewWidth; - scrollViewFrame = CGRectMake(CGRectGetMaxX(textViewFrame), self.view.frame.origin.y, scrollViewWidth, CGRectGetHeight(textViewFrame)); + scrollViewFrame = CGRectMake(CGRectGetMaxX(textViewFrame), self.view.frame.origin.y, scrollViewWidth, CGRectGetHeight(self.view.bounds)); self.textView.frame = textViewFrame; self.attachmentScrollView.frame = scrollViewFrame; self.attachmentScrollView.contentInset = self.textView.contentInset; diff --git a/Classes/BITFeedbackManager.m b/Classes/BITFeedbackManager.m index e727ea5233..efabd38d64 100644 --- a/Classes/BITFeedbackManager.m +++ b/Classes/BITFeedbackManager.m @@ -228,7 +228,9 @@ } BITFeedbackComposeViewController *composeView = [self feedbackComposeViewController]; [composeView prepareWithItems:items]; + [self showView:composeView]; + } - (void)showFeedbackComposeViewWithGeneratedScreenshot { From c08a308ce42ca31500801c09e4be6d09db284a02 Mon Sep 17 00:00:00 2001 From: moritz haarmann Date: Thu, 6 Mar 2014 15:49:25 +0100 Subject: [PATCH 28/92] + Gallery View Controller for Viewing Attachments --- Classes/BITAttachmentGalleryViewController.h | 15 ++ Classes/BITAttachmentGalleryViewController.m | 161 +++++++++++++++++++ Classes/BITFeedbackListViewCell.h | 10 ++ Classes/BITFeedbackListViewCell.m | 25 ++- Classes/BITFeedbackListViewController.m | 17 +- Support/HockeySDK.xcodeproj/project.pbxproj | 16 ++ 6 files changed, 237 insertions(+), 7 deletions(-) create mode 100644 Classes/BITAttachmentGalleryViewController.h create mode 100644 Classes/BITAttachmentGalleryViewController.m diff --git a/Classes/BITAttachmentGalleryViewController.h b/Classes/BITAttachmentGalleryViewController.h new file mode 100644 index 0000000000..c246390ff0 --- /dev/null +++ b/Classes/BITAttachmentGalleryViewController.h @@ -0,0 +1,15 @@ +// +// BITAttachmentGalleryViewController.h +// HockeySDK +// +// Created by Moritz Haarmann on 06.03.14. +// +// + +#import + +@interface BITAttachmentGalleryViewController : UIViewController + +@property (nonatomic, strong) NSArray *messages; + +@end diff --git a/Classes/BITAttachmentGalleryViewController.m b/Classes/BITAttachmentGalleryViewController.m new file mode 100644 index 0000000000..13ccfee547 --- /dev/null +++ b/Classes/BITAttachmentGalleryViewController.m @@ -0,0 +1,161 @@ +// +// BITAttachmentGalleryViewController.m +// HockeySDK +// +// Created by Moritz Haarmann on 06.03.14. +// +// + +#import "BITAttachmentGalleryViewController.h" + +#import "BITFeedbackMessage.h" +#import "BITFeedbackMessageAttachment.h" + +@interface BITAttachmentGalleryViewController () + +@property (nonatomic, strong) UIScrollView *scrollView; +@property (nonatomic, strong) NSArray *imageViews; +@property (nonatomic, strong) NSArray *extractedAttachments; +@property (nonatomic) NSInteger currentIndex; +@property (nonatomic, strong) UITapGestureRecognizer *tapognizer; + +@end + +@implementation BITAttachmentGalleryViewController + +- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil +{ + self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; + if (self) { + // Custom initialization + } + return self; +} + +- (void)viewDidLoad +{ + [super viewDidLoad]; + self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"Close" style:UIBarButtonItemStylePlain target:self action:@selector(close:)]; + + self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAction target:self action:@selector(share:)]; +} + +- (void)viewDidAppear:(BOOL)animated { + [super viewDidAppear:animated]; + + [self extractUsableAttachments]; + + [self setupScrollView]; + + self.tapognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapped:)]; + [self.view addGestureRecognizer:self.tapognizer]; + +} +- (void)setupScrollView { + self.scrollView = [[UIScrollView alloc] initWithFrame:self.view.bounds]; + [self.view addSubview:self.scrollView]; + self.scrollView.delegate = self; + self.scrollView.pagingEnabled = YES; + self.scrollView.backgroundColor = [UIColor groupTableViewBackgroundColor]; + self.scrollView.bounces = NO; + + + NSMutableArray *imageviews = [NSMutableArray new]; + + for (int i = 0; i<3; i++){ + UIImageView *newImageView = [[UIImageView alloc] initWithFrame:CGRectZero]; + [imageviews addObject:newImageView]; + newImageView.contentMode = UIViewContentModeScaleAspectFit; + [self.scrollView addSubview:newImageView]; + } + + self.imageViews = imageviews; + +} + +- (void)extractUsableAttachments { + NSMutableArray *extractedOnes = [NSMutableArray new]; + + for (BITFeedbackMessage *message in self.messages){ + for (BITFeedbackMessageAttachment *attachment in message.attachments){ + if ([attachment imageRepresentation]){ + [extractedOnes addObject:attachment]; + } + } + } + + self.extractedAttachments = extractedOnes; +} + +- (void)didReceiveMemoryWarning +{ + [super didReceiveMemoryWarning]; +} + +- (void)scrollViewDidScroll:(UIScrollView *)scrollView { + NSInteger newIndex = self.scrollView.contentOffset.x / self.scrollView.frame.size.width; + if (newIndex!=self.currentIndex){ + self.currentIndex = newIndex; + // requeue elements. + NSInteger baseIndex = MAX(0,self.currentIndex-1); + [self layoutViews]; + NSInteger z = baseIndex; + + + + for ( NSInteger i = baseIndex; i < MIN(baseIndex+2, self.extractedAttachments.count);i++ ){ + UIImageView *imageView = self.imageViews[z]; + BITFeedbackMessageAttachment *attachment = self.extractedAttachments[i]; + imageView.image =[attachment imageRepresentation]; + z++; + } + + + } +} + +- (void)layoutViews { + + self.scrollView.frame = self.view.bounds; + self.scrollView.contentSize = CGSizeMake(CGRectGetWidth(self.view.bounds) * self.extractedAttachments.count, CGRectGetHeight(self.view.bounds)); + + NSInteger baseIndex = MAX(0,self.currentIndex-1); + NSInteger z = baseIndex; + for ( NSInteger i = baseIndex; i < MIN(baseIndex+2, self.extractedAttachments.count);i++ ){ + UIImageView *imageView = self.imageViews[z]; + imageView.frame = [self frameForItemAtIndex:i]; + z++; + } +} + +- (BOOL)prefersStatusBarHidden { + return YES; +} + +- (void)close:(id)sender { + [self dismissModalViewControllerAnimated:YES]; +} + +- (void)share:(id)sender { + BITFeedbackMessageAttachment *attachment = self.extractedAttachments[self.currentIndex]; + + UIActivityViewController *activityVC = [[UIActivityViewController alloc] initWithActivityItems:[NSArray arrayWithObjects:attachment.originalFilename, attachment.imageRepresentation , nil] applicationActivities:nil]; + [self presentViewController:activityVC animated:YES completion:nil]; +} + +- (void)tapped:(UITapGestureRecognizer *)tapRecognizer { + if (self.navigationController.navigationBarHidden){ + [[UIApplication sharedApplication] setStatusBarHidden:NO]; + self.navigationController.navigationBarHidden = NO; + } else { + [[UIApplication sharedApplication] setStatusBarHidden:YES]; + self.navigationController.navigationBarHidden = YES; + } + [self layoutViews]; +} + +- (CGRect)frameForItemAtIndex:(NSInteger)index { + return CGRectMake(index * CGRectGetWidth(self.scrollView.frame), 0, CGRectGetWidth(self.scrollView.frame), CGRectGetHeight(self.scrollView.frame)); +} + +@end diff --git a/Classes/BITFeedbackListViewCell.h b/Classes/BITFeedbackListViewCell.h index 4842cb1a18..8ce9c3fcd2 100644 --- a/Classes/BITFeedbackListViewCell.h +++ b/Classes/BITFeedbackListViewCell.h @@ -31,6 +31,14 @@ #import "BITFeedbackMessage.h" #import "BITAttributedLabel.h" +@class BITFeedbackMessageAttachment; + +@protocol BITFeedbackListViewCellDelegate + +- (void)listCell:(id)cell didSelectAttachment:(BITFeedbackMessageAttachment *)attachment; + +@end + /** * Cell style depending on the iOS version */ @@ -69,6 +77,8 @@ typedef NS_ENUM(NSUInteger, BITFeedbackListViewCellBackgroundStyle) { @property (nonatomic, strong) BITAttributedLabel *labelText; +@property (nonatomic, weak) id delegate; + + (CGFloat) heightForRowWithMessage:(BITFeedbackMessage *)message tableViewWidth:(CGFloat)width; - (void)setAttachments:(NSArray *)attachments; diff --git a/Classes/BITFeedbackListViewCell.m b/Classes/BITFeedbackListViewCell.m index d0091e3b68..d622187627 100644 --- a/Classes/BITFeedbackListViewCell.m +++ b/Classes/BITFeedbackListViewCell.m @@ -193,8 +193,11 @@ [self.attachmentViews removeAllObjects]; for (BITFeedbackMessageAttachment *attachment in attachments){ - UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectZero]; - imageView.image = [attachment thumbnailWithSize:CGSizeMake(ATTACHMENT_SIZE, ATTACHMENT_SIZE)]; + UIButton *imageView = [UIButton buttonWithType:UIButtonTypeCustom]; + [imageView setImage:[attachment thumbnailWithSize:CGSizeMake(ATTACHMENT_SIZE, ATTACHMENT_SIZE)] forState:UIControlStateNormal]; + + [imageView addTarget:self action:@selector(imageButtonPressed:) forControlEvents:UIControlEventTouchUpInside]; + [self.attachmentViews addObject:imageView]; [self addSubview:imageView]; } @@ -263,13 +266,13 @@ int i = 0; CGFloat attachmentsPerRow = ceilf(self.frame.size.width / (FRAME_SIDE_BORDER + ATTACHMENT_SIZE)); - for ( UIImageView *imageView in self.attachmentViews){ - imageView.contentMode = UIViewContentModeScaleAspectFit; + for ( UIButton *imageButton in self.attachmentViews){ + imageButton.contentMode = UIViewContentModeScaleAspectFit; if ( !_message.userMessage){ - imageView.frame = CGRectMake(FRAME_SIDE_BORDER + (FRAME_SIDE_BORDER + ATTACHMENT_SIZE) * i , floor(i/attachmentsPerRow) + baseOffsetOfText , ATTACHMENT_SIZE, ATTACHMENT_SIZE); + imageButton.frame = CGRectMake(FRAME_SIDE_BORDER + (FRAME_SIDE_BORDER + ATTACHMENT_SIZE) * i , floor(i/attachmentsPerRow) + baseOffsetOfText , ATTACHMENT_SIZE, ATTACHMENT_SIZE); } else { - imageView.frame = CGRectMake(self.frame.size.width - FRAME_SIDE_BORDER - ATTACHMENT_SIZE - ((FRAME_SIDE_BORDER + ATTACHMENT_SIZE) * (i) ), floor(i/attachmentsPerRow) + baseOffsetOfText , ATTACHMENT_SIZE, ATTACHMENT_SIZE); + imageButton.frame = CGRectMake(self.frame.size.width - FRAME_SIDE_BORDER - ATTACHMENT_SIZE - ((FRAME_SIDE_BORDER + ATTACHMENT_SIZE) * (i) ), floor(i/attachmentsPerRow) + baseOffsetOfText , ATTACHMENT_SIZE, ATTACHMENT_SIZE); } i++; @@ -280,5 +283,15 @@ [super layoutSubviews]; } +- (void)imageButtonPressed:(id)sender { + if ([self.delegate respondsToSelector:@selector(listCell:didSelectAttachment:)]){ + NSInteger index = [self.attachmentViews indexOfObject:sender]; + if (index != NSNotFound){ + BITFeedbackMessageAttachment *attachment = self.message.attachments[index]; + [self.delegate listCell:self didSelectAttachment:attachment]; + } + } +} + @end diff --git a/Classes/BITFeedbackListViewController.m b/Classes/BITFeedbackListViewController.m index ff59a222f9..fe45d9e52d 100644 --- a/Classes/BITFeedbackListViewController.m +++ b/Classes/BITFeedbackListViewController.m @@ -40,6 +40,7 @@ #import "BITFeedbackUserDataViewController.h" #import "BITFeedbackMessage.h" #import "BITAttributedLabel.h" +#import "BITAttachmentGalleryViewController.h" #import "BITHockeyBaseManagerPrivate.h" @@ -64,7 +65,7 @@ #define BORDER_COLOR BIT_RGBCOLOR(215, 215, 215) -@interface BITFeedbackListViewController () +@interface BITFeedbackListViewController () @property (nonatomic, weak) BITFeedbackManager *manager; @property (nonatomic, strong) NSDateFormatter *lastUpdateDateFormatter; @@ -628,6 +629,7 @@ cell.message = message; cell.labelText.delegate = self; cell.labelText.userInteractionEnabled = YES; + cell.delegate = self; [cell setAttachments:message.attachments]; if ( @@ -769,6 +771,19 @@ } } +- (void)listCell:(id)cell didSelectAttachment:(BITFeedbackMessageAttachment *)attachment { + BITAttachmentGalleryViewController *galleryController = [BITAttachmentGalleryViewController new]; + + UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:galleryController]; + NSMutableArray *collectedMessages = [NSMutableArray new]; + for (int i = 0; i Date: Tue, 18 Mar 2014 23:24:30 +0100 Subject: [PATCH 29/92] + Fixes #60: Drawing direction of arrow. --- Classes/BITArrowImageAnnotation.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Classes/BITArrowImageAnnotation.m b/Classes/BITArrowImageAnnotation.m index b53bb502ff..c45e5e2f06 100644 --- a/Classes/BITArrowImageAnnotation.m +++ b/Classes/BITArrowImageAnnotation.m @@ -49,7 +49,7 @@ CGFloat lineWidth = MAX(self.frame.size.width / 10.0f,10); CGFloat startX, startY, endX, endY; - if ( self.movedDelta.width > 0){ + if ( self.movedDelta.width < 0){ startX = CGRectGetMinX(self.bounds); endX = CGRectGetMaxX(self.bounds); } else { @@ -58,7 +58,7 @@ } - if ( self.movedDelta.height > 0){ + if ( self.movedDelta.height < 0){ startY = CGRectGetMinY(self.bounds); endY = CGRectGetMaxY(self.bounds); } else { From b33ee9a61fd6784c96c2e65f62b5deab5e75511a Mon Sep 17 00:00:00 2001 From: Moritz Haarmann Date: Tue, 18 Mar 2014 23:46:49 +0100 Subject: [PATCH 30/92] + Added #63 (Moving drawn elements.) --- Classes/BITImageAnnotationViewController.m | 34 ++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/Classes/BITImageAnnotationViewController.m b/Classes/BITImageAnnotationViewController.m index b5f3aae62f..8e780f06de 100644 --- a/Classes/BITImageAnnotationViewController.m +++ b/Classes/BITImageAnnotationViewController.m @@ -19,11 +19,15 @@ @property (nonatomic, strong) NSMutableArray *objects; @property (nonatomic, strong) UIPanGestureRecognizer *panRecognizer; @property (nonatomic, strong) UITapGestureRecognizer *tapRecognizer; +@property (nonatomic, strong) UIPinchGestureRecognizer *pinchRecognizer; + @property (nonatomic) CGFloat scaleFactor; @property (nonatomic) CGPoint panStart; @property (nonatomic,strong) BITImageAnnotation *currentAnnotation; +@property (nonatomic) BOOL isDrawing; + @end @implementation BITImageAnnotationViewController @@ -138,17 +142,47 @@ #pragma mark - Gesture Handling - (void)panned:(UIPanGestureRecognizer *)gestureRecognizer { + if ([self.editingControls selectedSegmentIndex] != UISegmentedControlNoSegment || self.isDrawing ){ if (gestureRecognizer.state == UIGestureRecognizerStateBegan){ self.currentAnnotation = [self annotationForCurrentMode]; [self.objects addObject:self.currentAnnotation]; self.currentAnnotation.sourceImage = self.image; [self.imageView insertSubview:self.currentAnnotation aboveSubview:self.imageView]; self.panStart = [gestureRecognizer locationInView:self.imageView]; + + [self.editingControls setSelectedSegmentIndex:UISegmentedControlNoSegment]; + self.isDrawing = YES; + } else if (gestureRecognizer.state == UIGestureRecognizerStateChanged){ CGPoint bla = [gestureRecognizer locationInView:self.imageView]; self.currentAnnotation.frame = CGRectMake(self.panStart.x, self.panStart.y, bla.x - self.panStart.x, bla.y - self.panStart.y); self.currentAnnotation.movedDelta = CGSizeMake(bla.x - self.panStart.x, bla.y - self.panStart.y); self.currentAnnotation.imageFrame = [self.view convertRect:self.imageView.frame toView:self.currentAnnotation]; + } else { + self.currentAnnotation = nil; + self.isDrawing = NO; + } + } else { + if (gestureRecognizer.state == UIGestureRecognizerStateBegan){ + // find and possibly move an existing annotation. + BITImageAnnotation *selectedAnnotation = (BITImageAnnotation *)[self.view hitTest:[gestureRecognizer locationInView:self.view] withEvent:nil]; + + if ([self.objects indexOfObject:selectedAnnotation] != NSNotFound){ + self.currentAnnotation = selectedAnnotation; + } + } else if (gestureRecognizer.state == UIGestureRecognizerStateChanged && self.currentAnnotation){ + CGPoint delta = [gestureRecognizer translationInView:self.view]; + + CGRect annotationFrame = self.currentAnnotation.frame; + annotationFrame.origin.x += delta.x; + annotationFrame.origin.y += delta.y; + self.currentAnnotation.frame = annotationFrame; + + [gestureRecognizer setTranslation:CGPointZero inView:self.view]; + + } else { + self.currentAnnotation = nil; + } } } From 8c3351fdc530ff4d8a25cb9e03415664fa34960d Mon Sep 17 00:00:00 2001 From: Moritz Haarmann Date: Wed, 19 Mar 2014 11:05:33 +0100 Subject: [PATCH 31/92] + I'm in the middle of something here. --- Classes/BITImageAnnotationViewController.m | 112 +++++++++++++-------- 1 file changed, 68 insertions(+), 44 deletions(-) diff --git a/Classes/BITImageAnnotationViewController.m b/Classes/BITImageAnnotationViewController.m index 8e780f06de..3dc2f27b55 100644 --- a/Classes/BITImageAnnotationViewController.m +++ b/Classes/BITImageAnnotationViewController.m @@ -18,7 +18,6 @@ @property (nonatomic, strong) UISegmentedControl *editingControls; @property (nonatomic, strong) NSMutableArray *objects; @property (nonatomic, strong) UIPanGestureRecognizer *panRecognizer; -@property (nonatomic, strong) UITapGestureRecognizer *tapRecognizer; @property (nonatomic, strong) UIPinchGestureRecognizer *pinchRecognizer; @property (nonatomic) CGFloat scaleFactor; @@ -28,17 +27,19 @@ @property (nonatomic) BOOL isDrawing; +@property (nonatomic) CGRect pinchStartingFrame; + @end @implementation BITImageAnnotationViewController - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil { - self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; - if (self) { - // Custom initialization - } - return self; + self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; + if (self) { + // Custom initialization + } + return self; } - (void)viewDidLoad @@ -50,7 +51,7 @@ self.editingControls = [[UISegmentedControl alloc] initWithItems:@[@"Rectangle", @"Arrow", @"Blur"]]; self.navigationItem.titleView = self.editingControls; - + self.objects = [NSMutableArray new]; @@ -60,7 +61,7 @@ self.imageView = [[UIImageView alloc] initWithFrame:self.view.bounds]; self.imageView.clipsToBounds = YES; - + self.imageView.image = self.image; self.imageView.contentMode = UIViewContentModeScaleToFill; @@ -68,12 +69,10 @@ [self.view addSubview:self.imageView]; self.imageView.frame = self.view.bounds; - self.tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapped:)]; self.panRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(panned:)]; + self.pinchRecognizer = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(pinched:)]; - [self.tapRecognizer requireGestureRecognizerToFail:self.panRecognizer]; - - [self.imageView addGestureRecognizer:self.tapRecognizer]; + [self.imageView addGestureRecognizer:self.pinchRecognizer]; [self.imageView addGestureRecognizer:self.panRecognizer]; self.imageView.userInteractionEnabled = YES; @@ -89,8 +88,8 @@ CGSize scaledImageSize = CGSizeMake(self.image.size.width * factor, self.image.size.height * factor); self.imageView.frame = CGRectMake(self.view.frame.size.width/2 - scaledImageSize.width/2, self.view.frame.size.height/2 - scaledImageSize.height/2, scaledImageSize.width, scaledImageSize.height); - - + + // Do any additional setup after loading the view. } @@ -133,7 +132,7 @@ [annotation.layer renderInContext:ctx]; CGContextTranslateCTM(ctx,-1 * annotation.frame.origin.x,-1 * annotation.frame.origin.y); } - + UIImage *renderedImageOfMyself = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); return renderedImageOfMyself; @@ -143,33 +142,33 @@ - (void)panned:(UIPanGestureRecognizer *)gestureRecognizer { if ([self.editingControls selectedSegmentIndex] != UISegmentedControlNoSegment || self.isDrawing ){ - if (gestureRecognizer.state == UIGestureRecognizerStateBegan){ - self.currentAnnotation = [self annotationForCurrentMode]; - [self.objects addObject:self.currentAnnotation]; - self.currentAnnotation.sourceImage = self.image; - [self.imageView insertSubview:self.currentAnnotation aboveSubview:self.imageView]; - self.panStart = [gestureRecognizer locationInView:self.imageView]; - - [self.editingControls setSelectedSegmentIndex:UISegmentedControlNoSegment]; - self.isDrawing = YES; - - } else if (gestureRecognizer.state == UIGestureRecognizerStateChanged){ - CGPoint bla = [gestureRecognizer locationInView:self.imageView]; - self.currentAnnotation.frame = CGRectMake(self.panStart.x, self.panStart.y, bla.x - self.panStart.x, bla.y - self.panStart.y); - self.currentAnnotation.movedDelta = CGSizeMake(bla.x - self.panStart.x, bla.y - self.panStart.y); - self.currentAnnotation.imageFrame = [self.view convertRect:self.imageView.frame toView:self.currentAnnotation]; - } else { - self.currentAnnotation = nil; - self.isDrawing = NO; - } + if (gestureRecognizer.state == UIGestureRecognizerStateBegan){ + self.currentAnnotation = [self annotationForCurrentMode]; + [self.objects addObject:self.currentAnnotation]; + self.currentAnnotation.sourceImage = self.image; + [self.imageView insertSubview:self.currentAnnotation aboveSubview:self.imageView]; + self.panStart = [gestureRecognizer locationInView:self.imageView]; + + [self.editingControls setSelectedSegmentIndex:UISegmentedControlNoSegment]; + self.isDrawing = YES; + + } else if (gestureRecognizer.state == UIGestureRecognizerStateChanged){ + CGPoint bla = [gestureRecognizer locationInView:self.imageView]; + self.currentAnnotation.frame = CGRectMake(self.panStart.x, self.panStart.y, bla.x - self.panStart.x, bla.y - self.panStart.y); + self.currentAnnotation.movedDelta = CGSizeMake(bla.x - self.panStart.x, bla.y - self.panStart.y); + self.currentAnnotation.imageFrame = [self.view convertRect:self.imageView.frame toView:self.currentAnnotation]; + } else { + self.currentAnnotation = nil; + self.isDrawing = NO; + } } else { if (gestureRecognizer.state == UIGestureRecognizerStateBegan){ - // find and possibly move an existing annotation. - BITImageAnnotation *selectedAnnotation = (BITImageAnnotation *)[self.view hitTest:[gestureRecognizer locationInView:self.view] withEvent:nil]; - - if ([self.objects indexOfObject:selectedAnnotation] != NSNotFound){ + // find and possibly move an existing annotation. + BITImageAnnotation *selectedAnnotation = (BITImageAnnotation *)[self.view hitTest:[gestureRecognizer locationInView:self.view] withEvent:nil]; + + if ([self.objects indexOfObject:selectedAnnotation] != NSNotFound){ self.currentAnnotation = selectedAnnotation; - } + } } else if (gestureRecognizer.state == UIGestureRecognizerStateChanged && self.currentAnnotation){ CGPoint delta = [gestureRecognizer translationInView:self.view]; @@ -187,15 +186,40 @@ } --(void)tapped:(UITapGestureRecognizer *)gestureRecognizer { - - +-(void)pinched:(UIPinchGestureRecognizer *)gestureRecognizer { + if (gestureRecognizer.state == UIGestureRecognizerStateBegan){ + // try to figure out which view we are talking about. + BITImageAnnotation *candidate = nil; + BOOL validView = YES; + + for ( int i = 0; i Date: Wed, 19 Mar 2014 11:55:44 +0100 Subject: [PATCH 32/92] + Pinch to Zoom --- Classes/BITImageAnnotationViewController.m | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/Classes/BITImageAnnotationViewController.m b/Classes/BITImageAnnotationViewController.m index 3dc2f27b55..d5b944e19e 100644 --- a/Classes/BITImageAnnotationViewController.m +++ b/Classes/BITImageAnnotationViewController.m @@ -207,8 +207,27 @@ self.pinchStartingFrame = self.currentAnnotation.frame; } - } else if (gestureRecognizer.state == UIGestureRecognizerStateChanged && self.currentAnnotation){ + } else if (gestureRecognizer.state == UIGestureRecognizerStateChanged && self.currentAnnotation && gestureRecognizer.numberOfTouches>1){ CGRect newFrame= (self.pinchStartingFrame); + NSLog(@"%f", [gestureRecognizer scale]); + + // upper point? + CGPoint point1 = [gestureRecognizer locationOfTouch:0 inView:self.view]; + CGPoint point2 = [gestureRecognizer locationOfTouch:1 inView:self.view]; + + + newFrame.origin.x = point1.x; + newFrame.origin.y = point1.y; + + newFrame.origin.x = (point1.x > point2.x) ? point2.x : point1.x; + newFrame.origin.y = (point1.y > point2.y) ? point2.y : point1.y; + + newFrame.size.width = (point1.x > point2.x) ? point1.x - point2.x : point2.x - point1.x; + newFrame.size.height = (point1.y > point2.y) ? point1.y - point2.y : point2.y - point1.y; + + + self.currentAnnotation.frame = newFrame; + // we } else { From eba31ba2790f536f321e229d125de27dbbfafc6d Mon Sep 17 00:00:00 2001 From: moritz haarmann Date: Wed, 19 Mar 2014 13:10:44 +0100 Subject: [PATCH 33/92] + Fixed Blur z-index. --- Classes/BITArrowImageAnnotation.m | 4 +--- Classes/BITImageAnnotation.h | 3 +++ Classes/BITImageAnnotation.m | 10 ++------- Classes/BITImageAnnotationViewController.m | 24 +++++++++++++++++++--- Classes/BITRectangleImageAnnotation.m | 10 +++------ 5 files changed, 30 insertions(+), 21 deletions(-) diff --git a/Classes/BITArrowImageAnnotation.m b/Classes/BITArrowImageAnnotation.m index c45e5e2f06..0edc1af006 100644 --- a/Classes/BITArrowImageAnnotation.m +++ b/Classes/BITArrowImageAnnotation.m @@ -66,9 +66,7 @@ endY = CGRectGetMinY(self.bounds); } - - NSLog(@"Start X: %f, Y: %f, END: %f %f %@", startX, startY, endX,endY, self); - + UIBezierPath *path = [self bezierPathWithArrowFromPoint:CGPointMake(endX,endY) toPoint:CGPointMake(startX,startY) tailWidth:lineWidth headWidth:topHeight headLength:topHeight]; self.shapeLayer.path = path.CGPath; diff --git a/Classes/BITImageAnnotation.h b/Classes/BITImageAnnotation.h index 6449301275..f848ee2484 100644 --- a/Classes/BITImageAnnotation.h +++ b/Classes/BITImageAnnotation.h @@ -12,4 +12,7 @@ @property (nonatomic) CGSize movedDelta; @property (nonatomic, weak) UIImage *sourceImage; @property (nonatomic) CGRect imageFrame; + +-(BOOL)resizable; + @end diff --git a/Classes/BITImageAnnotation.m b/Classes/BITImageAnnotation.m index 644a27b379..d0e42614e3 100644 --- a/Classes/BITImageAnnotation.m +++ b/Classes/BITImageAnnotation.m @@ -21,14 +21,8 @@ } - -/* -// Only override drawRect: if you perform custom drawing. -// An empty implementation adversely affects performance during animation. -- (void)drawRect:(CGRect)rect -{ - // Drawing code +-(BOOL)resizable { + return NO; } -*/ @end diff --git a/Classes/BITImageAnnotationViewController.m b/Classes/BITImageAnnotationViewController.m index d5b944e19e..d555fa8856 100644 --- a/Classes/BITImageAnnotationViewController.m +++ b/Classes/BITImageAnnotationViewController.m @@ -52,7 +52,6 @@ self.navigationItem.titleView = self.editingControls; - self.objects = [NSMutableArray new]; [self.editingControls addTarget:self action:@selector(editingAction:) forControlEvents:UIControlEventTouchUpInside]; @@ -146,7 +145,13 @@ self.currentAnnotation = [self annotationForCurrentMode]; [self.objects addObject:self.currentAnnotation]; self.currentAnnotation.sourceImage = self.image; - [self.imageView insertSubview:self.currentAnnotation aboveSubview:self.imageView]; + + if (self.imageView.subviews.count > 0 && [self.currentAnnotation isKindOfClass:[BITBlurImageAnnotation class]]){ + [self.imageView insertSubview:self.currentAnnotation belowSubview:[self firstAnnotationThatIsNotBlur]]; + } else { + [self.imageView addSubview:self.currentAnnotation]; + } + self.panStart = [gestureRecognizer locationInView:self.imageView]; [self.editingControls setSelectedSegmentIndex:UISegmentedControlNoSegment]; @@ -176,6 +181,8 @@ annotationFrame.origin.x += delta.x; annotationFrame.origin.y += delta.y; self.currentAnnotation.frame = annotationFrame; + self.currentAnnotation.imageFrame = [self.view convertRect:self.imageView.frame toView:self.currentAnnotation]; + [gestureRecognizer setTranslation:CGPointZero inView:self.view]; @@ -183,7 +190,16 @@ self.currentAnnotation = nil; } } +} + +-(BITImageAnnotation *)firstAnnotationThatIsNotBlur { + for (BITImageAnnotation *annotation in self.imageView.subviews){ + if (![annotation isKindOfClass:[BITBlurImageAnnotation class]]){ + return annotation; + } + } + return self.imageView; } -(void)pinched:(UIPinchGestureRecognizer *)gestureRecognizer { @@ -202,7 +218,7 @@ } } - if (validView){ + if (validView && [candidate resizable]){ self.currentAnnotation = candidate; self.pinchStartingFrame = self.currentAnnotation.frame; } @@ -227,6 +243,8 @@ self.currentAnnotation.frame = newFrame; + self.currentAnnotation.imageFrame = [self.view convertRect:self.imageView.frame toView:self.currentAnnotation]; + // we diff --git a/Classes/BITRectangleImageAnnotation.m b/Classes/BITRectangleImageAnnotation.m index 5fd44c7536..4709d4f4cd 100644 --- a/Classes/BITRectangleImageAnnotation.m +++ b/Classes/BITRectangleImageAnnotation.m @@ -50,13 +50,9 @@ self.strokeLayer.path = [UIBezierPath bezierPathWithRoundedRect:self.bounds cornerRadius:10].CGPath; } -/* -// Only override drawRect: if you perform custom drawing. -// An empty implementation adversely affects performance during animation. -- (void)drawRect:(CGRect)rect -{ - // Drawing code +-(BOOL)resizable { + return YES; } -*/ + @end From e0057f650e60a5e142ea9c978438d9a10c94e7ec Mon Sep 17 00:00:00 2001 From: moritz haarmann Date: Wed, 19 Mar 2014 13:14:38 +0100 Subject: [PATCH 34/92] + Removed some Deprecation warnings. --- Classes/BITAttachmentGalleryViewController.m | 2 +- Classes/BITFeedbackComposeViewController.m | 6 +++--- Classes/BITImageAnnotationViewController.m | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Classes/BITAttachmentGalleryViewController.m b/Classes/BITAttachmentGalleryViewController.m index 13ccfee547..0352b4d25c 100644 --- a/Classes/BITAttachmentGalleryViewController.m +++ b/Classes/BITAttachmentGalleryViewController.m @@ -133,7 +133,7 @@ } - (void)close:(id)sender { - [self dismissModalViewControllerAnimated:YES]; + [self dismissViewControllerAnimated:YES completion:nil]; } - (void)share:(id)sender { diff --git a/Classes/BITFeedbackComposeViewController.m b/Classes/BITFeedbackComposeViewController.m index 689e7083b5..aaf2d2451d 100644 --- a/Classes/BITFeedbackComposeViewController.m +++ b/Classes/BITFeedbackComposeViewController.m @@ -393,7 +393,7 @@ pickerController.sourceType = UIImagePickerControllerSourceTypePhotoLibrary; pickerController.delegate = self; pickerController.editing = NO; - [self presentModalViewController:pickerController animated:YES]; + [self presentViewController:pickerController animated:YES completion:nil]; } #pragma mark - UIImagePickerControllerDelegate @@ -410,12 +410,12 @@ [self.attachments addObject:newAttachment]; } - [picker dismissModalViewControllerAnimated:YES]; + [picker dismissViewControllerAnimated:YES completion:nil]; [self refreshAttachmentScrollview]; } - (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker { - [picker dismissModalViewControllerAnimated:YES]; + [picker dismissViewControllerAnimated:YES completion:nil]; } - (void)imageButtonAction:(UIButton *)sender { diff --git a/Classes/BITImageAnnotationViewController.m b/Classes/BITImageAnnotationViewController.m index d555fa8856..2c412b9ecf 100644 --- a/Classes/BITImageAnnotationViewController.m +++ b/Classes/BITImageAnnotationViewController.m @@ -110,13 +110,13 @@ - (void)discard:(id)sender { [self.delegate annotationControllerDidCancel:self]; - [self dismissModalViewControllerAnimated:YES]; + [self dismissViewControllerAnimated:YES completion:nil]; } - (void)save:(id)sender { UIImage *image = [self extractImage]; [self.delegate annotationController:self didFinishWithImage:image]; - [self dismissModalViewControllerAnimated:YES]; + [self dismissViewControllerAnimated:YES completion:nil]; } - (UIImage *)extractImage { @@ -192,7 +192,7 @@ } } --(BITImageAnnotation *)firstAnnotationThatIsNotBlur { +-(UIView *)firstAnnotationThatIsNotBlur { for (BITImageAnnotation *annotation in self.imageView.subviews){ if (![annotation isKindOfClass:[BITBlurImageAnnotation class]]){ return annotation; From 7b19d21c49cf031af3b9c5051533ab3bf63225be Mon Sep 17 00:00:00 2001 From: moritz haarmann Date: Wed, 19 Mar 2014 13:27:55 +0100 Subject: [PATCH 35/92] + Vastly improved blur Annotation. --- Classes/BITBlurImageAnnotation.m | 21 ++++++++++++++------- Classes/BITImageAnnotationViewController.m | 4 ++++ 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/Classes/BITBlurImageAnnotation.m b/Classes/BITBlurImageAnnotation.m index 898ce2a3c8..9d8f762cd2 100644 --- a/Classes/BITBlurImageAnnotation.m +++ b/Classes/BITBlurImageAnnotation.m @@ -35,23 +35,30 @@ UIGraphicsBeginImageContext(size); [sourceImage drawInRect:CGRectMake(0, 0, size.width, size.height)]; self.scaledImage = UIGraphicsGetImageFromCurrentImageContext(); + self.imageLayer.shouldRasterize = YES; + self.imageLayer.rasterizationScale = 1; + self.imageLayer.magnificationFilter = kCAFilterNearest; self.imageLayer.contents = (id)self.scaledImage.CGImage; + + + UIGraphicsEndImageContext(); } - (void)layoutSubviews { [super layoutSubviews]; + + [CATransaction begin]; + [CATransaction setValue:(id)kCFBooleanTrue forKey:kCATransactionDisableActions]; + self.imageLayer.frame = self.imageFrame; self.imageLayer.masksToBounds = YES; + + [CATransaction commit]; } -/* -// Only override drawRect: if you perform custom drawing. -// An empty implementation adversely affects performance during animation. -- (void)drawRect:(CGRect)rect -{ - // Drawing code +-(BOOL)resizable { + return YES; } -*/ @end diff --git a/Classes/BITImageAnnotationViewController.m b/Classes/BITImageAnnotationViewController.m index 2c412b9ecf..b8579cf64d 100644 --- a/Classes/BITImageAnnotationViewController.m +++ b/Classes/BITImageAnnotationViewController.m @@ -162,6 +162,8 @@ self.currentAnnotation.frame = CGRectMake(self.panStart.x, self.panStart.y, bla.x - self.panStart.x, bla.y - self.panStart.y); self.currentAnnotation.movedDelta = CGSizeMake(bla.x - self.panStart.x, bla.y - self.panStart.y); self.currentAnnotation.imageFrame = [self.view convertRect:self.imageView.frame toView:self.currentAnnotation]; + [self.currentAnnotation setNeedsLayout]; + [self.currentAnnotation layoutIfNeeded]; } else { self.currentAnnotation = nil; self.isDrawing = NO; @@ -183,6 +185,8 @@ self.currentAnnotation.frame = annotationFrame; self.currentAnnotation.imageFrame = [self.view convertRect:self.imageView.frame toView:self.currentAnnotation]; + [self.currentAnnotation setNeedsLayout]; + [self.currentAnnotation layoutIfNeeded]; [gestureRecognizer setTranslation:CGPointZero inView:self.view]; From 14947a577c04207317a6f46d9a5df4ffa3e0f0b9 Mon Sep 17 00:00:00 2001 From: moritz haarmann Date: Wed, 19 Mar 2014 13:39:56 +0100 Subject: [PATCH 36/92] + Re-Enabled the ability to well, submit, reviews. --- Classes/BITFeedbackComposeViewController.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Classes/BITFeedbackComposeViewController.m b/Classes/BITFeedbackComposeViewController.m index aaf2d2451d..6f147dcfcd 100644 --- a/Classes/BITFeedbackComposeViewController.m +++ b/Classes/BITFeedbackComposeViewController.m @@ -331,9 +331,9 @@ - (void)updateBarButtonState { if (self.textView.text.length > 0 ) { - self.navigationItem.rightBarButtonItem.enabled = NO; - } else { self.navigationItem.rightBarButtonItem.enabled = YES; + } else { + self.navigationItem.rightBarButtonItem.enabled = NO; } } From 1c2b743b3b723370d7ea3fbba87adf208734be75 Mon Sep 17 00:00:00 2001 From: moritz haarmann Date: Wed, 19 Mar 2014 14:37:06 +0100 Subject: [PATCH 37/92] + Improved gallery display. --- Classes/BITAttachmentGalleryViewController.m | 43 ++++++++++++-------- 1 file changed, 26 insertions(+), 17 deletions(-) diff --git a/Classes/BITAttachmentGalleryViewController.m b/Classes/BITAttachmentGalleryViewController.m index 0352b4d25c..60fa6ff993 100644 --- a/Classes/BITAttachmentGalleryViewController.m +++ b/Classes/BITAttachmentGalleryViewController.m @@ -17,6 +17,7 @@ @property (nonatomic, strong) NSArray *imageViews; @property (nonatomic, strong) NSArray *extractedAttachments; @property (nonatomic) NSInteger currentIndex; +@property (nonatomic) NSInteger loadedImageIndex; @property (nonatomic, strong) UITapGestureRecognizer *tapognizer; @end @@ -35,17 +36,28 @@ - (void)viewDidLoad { [super viewDidLoad]; + self.automaticallyAdjustsScrollViewInsets = NO; + self.navigationController.navigationBar.translucent = YES; + self.edgesForExtendedLayout = YES; + self.extendedLayoutIncludesOpaqueBars = YES; self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"Close" style:UIBarButtonItemStylePlain target:self action:@selector(close:)]; self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAction target:self action:@selector(share:)]; + + + self.currentIndex = 0; + + [self extractUsableAttachments]; + [self setupScrollView]; + + [self layoutViews]; } - (void)viewDidAppear:(BOOL)animated { [super viewDidAppear:animated]; - [self extractUsableAttachments]; - [self setupScrollView]; + self.tapognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapped:)]; [self.view addGestureRecognizer:self.tapognizer]; @@ -53,6 +65,7 @@ } - (void)setupScrollView { self.scrollView = [[UIScrollView alloc] initWithFrame:self.view.bounds]; + self.view.autoresizesSubviews = NO; [self.view addSubview:self.scrollView]; self.scrollView.delegate = self; self.scrollView.pagingEnabled = YES; @@ -85,6 +98,8 @@ } self.extractedAttachments = extractedOnes; + + [self layoutViews]; } - (void)didReceiveMemoryWarning @@ -99,17 +114,7 @@ // requeue elements. NSInteger baseIndex = MAX(0,self.currentIndex-1); [self layoutViews]; - NSInteger z = baseIndex; - - - - for ( NSInteger i = baseIndex; i < MIN(baseIndex+2, self.extractedAttachments.count);i++ ){ - UIImageView *imageView = self.imageViews[z]; - BITFeedbackMessageAttachment *attachment = self.extractedAttachments[i]; - imageView.image =[attachment imageRepresentation]; - z++; - } - + } } @@ -123,13 +128,17 @@ NSInteger z = baseIndex; for ( NSInteger i = baseIndex; i < MIN(baseIndex+2, self.extractedAttachments.count);i++ ){ UIImageView *imageView = self.imageViews[z]; + BITFeedbackMessageAttachment *attachment = self.extractedAttachments[i]; + imageView.image =[attachment imageRepresentation]; imageView.frame = [self frameForItemAtIndex:i]; z++; } + + } - (BOOL)prefersStatusBarHidden { - return YES; + return self.navigationController.navigationBarHidden; } - (void)close:(id)sender { @@ -146,12 +155,12 @@ - (void)tapped:(UITapGestureRecognizer *)tapRecognizer { if (self.navigationController.navigationBarHidden){ [[UIApplication sharedApplication] setStatusBarHidden:NO]; - self.navigationController.navigationBarHidden = NO; + [self.navigationController setNavigationBarHidden:NO animated:YES]; } else { + [self.navigationController setNavigationBarHidden:YES animated:YES]; [[UIApplication sharedApplication] setStatusBarHidden:YES]; - self.navigationController.navigationBarHidden = YES; + } - [self layoutViews]; } - (CGRect)frameForItemAtIndex:(NSInteger)index { From 252137463c228242d016c65a9424e3b3d0b552f4 Mon Sep 17 00:00:00 2001 From: moritz haarmann Date: Mon, 24 Mar 2014 12:07:03 +0100 Subject: [PATCH 38/92] + Refined the documentation --- Classes/BITFeedbackComposeViewController.h | 3 ++- Classes/BITFeedbackManager.h | 7 +++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/Classes/BITFeedbackComposeViewController.h b/Classes/BITFeedbackComposeViewController.h index 5df188c1da..6bc132e66f 100644 --- a/Classes/BITFeedbackComposeViewController.h +++ b/Classes/BITFeedbackComposeViewController.h @@ -72,7 +72,8 @@ - NSURL - UIImage - These are automatically concatenated to one text string. + These are automatically concatenated to one text string, while any image attachments are + added as attachments to the feedback. @param items Array of data objects to prefill the feedback text message. */ diff --git a/Classes/BITFeedbackManager.h b/Classes/BITFeedbackManager.h index e4059d1d2c..cfc744f552 100644 --- a/Classes/BITFeedbackManager.h +++ b/Classes/BITFeedbackManager.h @@ -294,6 +294,13 @@ typedef NS_ENUM(NSInteger, BITFeedbackObservationMode) { */ - (BITFeedbackComposeViewController *)feedbackComposeViewController; +/** + Set the so-called feedback observation mode. Depending on the chosen mode, + the feedback manager will automatically launch once the event has been detected. + + You can choose from the modes in BITFeedbackObservationMode. The default mode is + BITFeedbackObservationNone. + */ - (void)setFeedbackObservationMode:(BITFeedbackObservationMode)mode; From 96c60ec3947bf859af1908e287c96c93a7b1819b Mon Sep 17 00:00:00 2001 From: moritz haarmann Date: Fri, 11 Apr 2014 14:57:23 +0200 Subject: [PATCH 39/92] + Added Icons to the Editing Screens. --- Classes/BITImageAnnotationViewController.m | 79 +++++++++++++++----- Resources/Arrow.png | Bin 0 -> 394 bytes Resources/Arrow@2x.png | Bin 0 -> 896 bytes Resources/Blur.png | Bin 0 -> 169 bytes Resources/Blur@2x.png | Bin 0 -> 250 bytes Resources/Cancel.png | Bin 0 -> 179 bytes Resources/Cancel@2x.png | Bin 0 -> 303 bytes Resources/Ok.png | Bin 0 -> 245 bytes Resources/Ok@2x.png | Bin 0 -> 363 bytes Resources/Rectangle.png | Bin 0 -> 176 bytes Resources/Rectangle@2x.png | Bin 0 -> 244 bytes Support/HockeySDK.xcodeproj/project.pbxproj | 40 ++++++++++ 12 files changed, 101 insertions(+), 18 deletions(-) create mode 100644 Resources/Arrow.png create mode 100644 Resources/Arrow@2x.png create mode 100644 Resources/Blur.png create mode 100644 Resources/Blur@2x.png create mode 100644 Resources/Cancel.png create mode 100644 Resources/Cancel@2x.png create mode 100644 Resources/Ok.png create mode 100644 Resources/Ok@2x.png create mode 100644 Resources/Rectangle.png create mode 100644 Resources/Rectangle@2x.png diff --git a/Classes/BITImageAnnotationViewController.m b/Classes/BITImageAnnotationViewController.m index b8579cf64d..910b81eaad 100644 --- a/Classes/BITImageAnnotationViewController.m +++ b/Classes/BITImageAnnotationViewController.m @@ -11,12 +11,16 @@ #import "BITRectangleImageAnnotation.h" #import "BITArrowImageAnnotation.h" #import "BITBlurImageAnnotation.h" +#import "BITHockeyHelper.h" +#import "HockeySDKPrivate.h" @interface BITImageAnnotationViewController () @property (nonatomic, strong) UIImageView *imageView; @property (nonatomic, strong) UISegmentedControl *editingControls; @property (nonatomic, strong) NSMutableArray *objects; + +@property (nonatomic, strong) UITapGestureRecognizer *tapRecognizer; @property (nonatomic, strong) UIPanGestureRecognizer *panRecognizer; @property (nonatomic, strong) UIPinchGestureRecognizer *pinchRecognizer; @@ -48,7 +52,15 @@ self.view.backgroundColor = [UIColor groupTableViewBackgroundColor]; + NSArray *icons = @[@"Rectangle.png", @"Arrow.png", @"Blur.png"]; + self.editingControls = [[UISegmentedControl alloc] initWithItems:@[@"Rectangle", @"Arrow", @"Blur"]]; + int i=0; + for (NSString *imageName in icons){ + [self.editingControls setImage:bit_imageNamed(imageName, BITHOCKEYSDK_BUNDLE) forSegmentAtIndex:i++]; + } + + [self.editingControls setSegmentedControlStyle:UISegmentedControlStyleBar]; self.navigationItem.titleView = self.editingControls; @@ -66,18 +78,32 @@ [self.view addSubview:self.imageView]; - self.imageView.frame = self.view.bounds; + // Erm. + self.imageView.frame = [UIScreen mainScreen].bounds; self.panRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(panned:)]; self.pinchRecognizer = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(pinched:)]; + self.tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapped:)]; + + [self.panRecognizer requireGestureRecognizerToFail:self.tapRecognizer]; [self.imageView addGestureRecognizer:self.pinchRecognizer]; [self.imageView addGestureRecognizer:self.panRecognizer]; + [self.view addGestureRecognizer:self.tapRecognizer]; self.imageView.userInteractionEnabled = YES; self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc ] initWithTitle:@"Discard" style:UIBarButtonItemStyleBordered target:self action:@selector(discard:)]; self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc ] initWithTitle:@"Save" style:UIBarButtonItemStyleBordered target:self action:@selector(save:)]; + + [self fitImageViewFrame]; + + + // Do any additional setup after loading the view. +} + +- (void)fitImageViewFrame { + CGFloat heightScaleFactor = self.view.frame.size.height / self.image.size.height; CGFloat widthScaleFactor = self.view.frame.size.width / self.image.size.width; @@ -87,9 +113,6 @@ CGSize scaledImageSize = CGSizeMake(self.image.size.width * factor, self.image.size.height * factor); self.imageView.frame = CGRectMake(self.view.frame.size.width/2 - scaledImageSize.width/2, self.view.frame.size.height/2 - scaledImageSize.height/2, scaledImageSize.width, scaledImageSize.height); - - - // Do any additional setup after loading the view. } -(void)editingAction:(id)sender { @@ -196,16 +219,6 @@ } } --(UIView *)firstAnnotationThatIsNotBlur { - for (BITImageAnnotation *annotation in self.imageView.subviews){ - if (![annotation isKindOfClass:[BITBlurImageAnnotation class]]){ - return annotation; - } - } - - return self.imageView; -} - -(void)pinched:(UIPinchGestureRecognizer *)gestureRecognizer { if (gestureRecognizer.state == UIGestureRecognizerStateBegan){ // try to figure out which view we are talking about. @@ -257,10 +270,40 @@ } } -- (void)didReceiveMemoryWarning -{ - [super didReceiveMemoryWarning]; - // Dispose of any resources that can be recreated. +-(void)tapped:(UIGestureRecognizer *)tapRecognizer { + if (self.navigationController.navigationBarHidden){ + // [[UIApplication sharedApplication] setStatusBarHidden:NO]; + [UIView animateWithDuration:0.35f animations:^{ + self.navigationController.navigationBar.alpha = 1; + } completion:^(BOOL finished) { + [self fitImageViewFrame]; + [self.navigationController setNavigationBarHidden:NO animated:NO]; + + + }]; + } else { + [UIView animateWithDuration:0.35f animations:^{ + self.navigationController.navigationBar.alpha = 0; + + } completion:^(BOOL finished) { + [self.navigationController setNavigationBarHidden:YES animated:NO]; + + [self fitImageViewFrame]; + + }]; + } + +} + +#pragma mark - Helpers +-(UIView *)firstAnnotationThatIsNotBlur { + for (BITImageAnnotation *annotation in self.imageView.subviews){ + if (![annotation isKindOfClass:[BITBlurImageAnnotation class]]){ + return annotation; + } + } + + return self.imageView; } @end diff --git a/Resources/Arrow.png b/Resources/Arrow.png new file mode 100644 index 0000000000000000000000000000000000000000..6d8fac10390585862e4384ef2e86820ba5da9356 GIT binary patch literal 394 zcmV;50d@X~P)VCQ8QMKOVG}7t5@%Go!kASp$#?^g*u~$2&~1xlGWoWwl~`bWi5HYH1T*3c zN@(E-S(q`*;1EL6Sv0;2IuWu}HFk3#>p7RmCalKAO%k!je_IqJ{~$Vv^(z zbVJ`U3%!lrs^bx|=0Eg%Z9^OC7U}_9;gQtaXuU$4bajM3tBsE|{R7-1XXJyrJsqK4 zXr4q9C&<{7);QD=nl*G`3eXL!m=lhE=p!_1jWSq+{&s?*uOn39Eu07K;Lr7Sgu2!r oSn6UUEeU;u$9!4;aC=q%1~jHedh8Y&asU7T07*qoM6N<$g8UGz%>V!Z literal 0 HcmV?d00001 diff --git a/Resources/Arrow@2x.png b/Resources/Arrow@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..116a47ee03672a17c2b662403bdcfa9158fc5a6c GIT binary patch literal 896 zcmV-`1AqL9P)0009=NklMY)7gdg>70Ezn)&KCd!Mzx@Be*k?X}ifXNns9ZGHK827bV8_zLyf8dCi^ z_?e-a>w4${_10!nxg9zQWl#xz{sQ}92-GVprrH-!13fd9H|9DOExY1wRwC<7^l7lTZ?F~GLO$l&nh0mXc)1KSz%?1wjJXT* zq6TL3pT&b`U}0WoLKG{-c^|l2s!;_?!A&S%n~$ThAD~yhLAm_e;jkTiV~YC*eUnkF zGtTSa6$AyUnMURu+ zm=$4;N!na53&9=EWcOhLL^T)vcsKzbztp-4bD$|`9l$(pIDNnkt2TqvI(6%W;sLN2 zIRf1vj`b&KH+%xMp2KR`nfbhnYhcT{(Z{!z>#HV+iF_SH#2qk^3wa5|+2IIl&@mHV z$yGhypr;wvV_>eQ3*ht3L1Q7V%@B3&2P1uh`;iF}IVkS3+hA3QPEXn#Ks%jnErx`} zYckn!2u67?_l|xcY%Zease<-}Nbs-b6dE3bNne8nxWS}t?uh2tpSEx|JrA{;z@5v) zmtYX2X>L|8LH%iI3g^-qh=Uz6u^rq3`5w1$&1sYHP`eub+)#1`CUtP?IR%}H7Vs3N zLt3^6A$48Pi4VO|i*nPb0Cz2mI0aoHsrlv~f|?7Gip7l|Mc_@ah!0Jp(4S0SNdDq85fF6^k41QOJ?4h$|2hSC-Lq2DA)$UeG3wRsJHbf$k}0@+;&l zI9Lwaf3<^TGjVwVvX-Ff&iVt!7C=8#vNV&>P8FIb%!oqwF~N2$}<=fCu_x|UA)P`KiKxv^n& U(aWb>fp#-^y85}Sb4q9e0Ejw8Qvd(} literal 0 HcmV?d00001 diff --git a/Resources/Blur@2x.png b/Resources/Blur@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..8cf6ffe5bc51406989170ce3e695f8e9bab7a385 GIT binary patch literal 250 zcmVtK?~9Qgpi#8)j^P+kk`K~aU~9=kg^o<6wF{E;s7IMDdH)Z!Q|tD81;G4BfA@H5(qP6+gJPp%e$iHljKYx`$K6RcZ_2lv|OI(SQqhA^l@uhgl z<|0m@EDeeHQoLky?3|&Q!S?jd)24&F%fD=?T35Dn4Z3kP`{3^-T=u5|C- zIl*s%cV)4n`UQVpwxz7I6e22oFZXq%v8;dV`7r!h=Fu0e73MOhjBQRYTf<|f(LDRa zVZ~JsO@2K-@9Z(Tczu0Hty$LAT<_YwXR@aYn@m!6j&WA}79gBH;UIIF+L6YHC9j{z c?w$CUt+LNxR!nOrBhVELp00i_>zopr0D&w*m;e9( literal 0 HcmV?d00001 diff --git a/Resources/Cancel@2x.png b/Resources/Cancel@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..3ddef8cb237955e0fc3133b1ef8ef9be76d43f92 GIT binary patch literal 303 zcmV+~0nq-5P)A3f$@P#^eJOuqfBk)bTyPmiX#Elq)8SxhT+s#H0zSXvG zCP9c{+J4I>MoZAjyOCZYZ=DK`3PkHWINKQR3pey5wuQUjT_^xDG5QN zK?(tJ6o*Y>#o9tvTtIOKo4J7;!BV+_vUCJjuyF>;1z1|lPGLim?<>VfGyH$`)x4Sc z-n@DLs1Tk$YNHtFH}Mv52=PluE7(Cbq`?&F3f&O7hjfQgh+IQTU2M@*@&sZ00000NkvXXu0mjfuOMVM literal 0 HcmV?d00001 diff --git a/Resources/Ok@2x.png b/Resources/Ok@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..f1915b84f885ce463de072db6185ffbee702968e GIT binary patch literal 363 zcmV-x0hIoUP)`$q!%F{{Kzr)=6uI<578(JD~J6nj3AiqaQ=i}h(lG;#dwnxp{qUzkvgDGs3Yo3 z5II~`gistr?OfQlw{wwpqn(Si6S|)7T#W74s3G>6iwBJLmRrDe4MlXx;q^N*;lX!M zF>f%0DylRWH#o0A#r(iC_{i~)atOs<>hRPQ(nK%BS0`lhnv0_R_bh&Z3fjQ{%H%y4 zWr=Uuum%``rT=^Llo=fdR94DC^U zvEvaMn~Tf}%+2GY+OyW!To~Wn&>q!+T`qU$KP>2|me8xq>ks$^WSTBB^J@SA002ov JPDHLkV1ka>sf_>t literal 0 HcmV?d00001 diff --git a/Resources/Rectangle.png b/Resources/Rectangle.png new file mode 100644 index 0000000000000000000000000000000000000000..49d81ea6b4e7033c300ccdbfd8b7ac056ac7683d GIT binary patch literal 176 zcmeAS@N?(olHy`uVBq!ia0vp^5bV9sh$l zo6j)Kk=Z6);G#7zrFnGH9xvXsQ4tT(EVnTT=OdE_|{FKaoucY5{YwNhv=rw*%V`*R4!0cpuQvpUfU~RH7jD=*{lJ<*}yL z$EJ#=>mQq2cJ+-!c-Wl166Lo-Hz&tL7Jf^*lNo-tuMnnC`tkX{3lCR4+Oyg3nI5;| of_gEh-0*@M?Ek_tH-D*NwiMW_C2FeQ5A+6ur>mdKI;Vst0J6wu1poj5 literal 0 HcmV?d00001 diff --git a/Support/HockeySDK.xcodeproj/project.pbxproj b/Support/HockeySDK.xcodeproj/project.pbxproj index 0ab1521d57..993fffc7ea 100644 --- a/Support/HockeySDK.xcodeproj/project.pbxproj +++ b/Support/HockeySDK.xcodeproj/project.pbxproj @@ -145,6 +145,16 @@ 9760F6C418BB4D2D00959B93 /* AssetsLibrary.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9760F6C318BB4D2D00959B93 /* AssetsLibrary.framework */; }; 9760F6CF18BB685600959B93 /* BITImageAnnotation.h in Headers */ = {isa = PBXBuildFile; fileRef = 9760F6CD18BB685600959B93 /* BITImageAnnotation.h */; }; 9760F6D018BB685600959B93 /* BITImageAnnotation.m in Sources */ = {isa = PBXBuildFile; fileRef = 9760F6CE18BB685600959B93 /* BITImageAnnotation.m */; }; + 9782023218F81BFC00A98D8B /* Arrow.png in Resources */ = {isa = PBXBuildFile; fileRef = 9782022818F81BFC00A98D8B /* Arrow.png */; }; + 9782023318F81BFC00A98D8B /* Arrow@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 9782022918F81BFC00A98D8B /* Arrow@2x.png */; }; + 9782023418F81BFC00A98D8B /* Blur.png in Resources */ = {isa = PBXBuildFile; fileRef = 9782022A18F81BFC00A98D8B /* Blur.png */; }; + 9782023518F81BFC00A98D8B /* Blur@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 9782022B18F81BFC00A98D8B /* Blur@2x.png */; }; + 9782023618F81BFC00A98D8B /* Cancel.png in Resources */ = {isa = PBXBuildFile; fileRef = 9782022C18F81BFC00A98D8B /* Cancel.png */; }; + 9782023718F81BFC00A98D8B /* Cancel@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 9782022D18F81BFC00A98D8B /* Cancel@2x.png */; }; + 9782023818F81BFC00A98D8B /* Ok.png in Resources */ = {isa = PBXBuildFile; fileRef = 9782022E18F81BFC00A98D8B /* Ok.png */; }; + 9782023918F81BFC00A98D8B /* Ok@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 9782022F18F81BFC00A98D8B /* Ok@2x.png */; }; + 9782023A18F81BFC00A98D8B /* Rectangle.png in Resources */ = {isa = PBXBuildFile; fileRef = 9782023018F81BFC00A98D8B /* Rectangle.png */; }; + 9782023B18F81BFC00A98D8B /* Rectangle@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 9782023118F81BFC00A98D8B /* Rectangle@2x.png */; }; 97F0F9FD18ABAECD00EF50AA /* iconCamera.png in Resources */ = {isa = PBXBuildFile; fileRef = 97F0F9FB18ABAECD00EF50AA /* iconCamera.png */; }; 97F0F9FE18ABAECD00EF50AA /* iconCamera@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 97F0F9FC18ABAECD00EF50AA /* iconCamera@2x.png */; }; 97F0FA0518B2294D00EF50AA /* BITFeedbackMessageAttachment.m in Sources */ = {isa = PBXBuildFile; fileRef = 97F0FA0318AE5AED00EF50AA /* BITFeedbackMessageAttachment.m */; }; @@ -319,6 +329,16 @@ 9760F6C318BB4D2D00959B93 /* AssetsLibrary.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AssetsLibrary.framework; path = System/Library/Frameworks/AssetsLibrary.framework; sourceTree = SDKROOT; }; 9760F6CD18BB685600959B93 /* BITImageAnnotation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BITImageAnnotation.h; sourceTree = ""; }; 9760F6CE18BB685600959B93 /* BITImageAnnotation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BITImageAnnotation.m; sourceTree = ""; }; + 9782022818F81BFC00A98D8B /* Arrow.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = Arrow.png; sourceTree = ""; }; + 9782022918F81BFC00A98D8B /* Arrow@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Arrow@2x.png"; sourceTree = ""; }; + 9782022A18F81BFC00A98D8B /* Blur.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = Blur.png; sourceTree = ""; }; + 9782022B18F81BFC00A98D8B /* Blur@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Blur@2x.png"; sourceTree = ""; }; + 9782022C18F81BFC00A98D8B /* Cancel.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = Cancel.png; sourceTree = ""; }; + 9782022D18F81BFC00A98D8B /* Cancel@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Cancel@2x.png"; sourceTree = ""; }; + 9782022E18F81BFC00A98D8B /* Ok.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = Ok.png; sourceTree = ""; }; + 9782022F18F81BFC00A98D8B /* Ok@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Ok@2x.png"; sourceTree = ""; }; + 9782023018F81BFC00A98D8B /* Rectangle.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = Rectangle.png; sourceTree = ""; }; + 9782023118F81BFC00A98D8B /* Rectangle@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Rectangle@2x.png"; sourceTree = ""; }; 97F0F9FB18ABAECD00EF50AA /* iconCamera.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = iconCamera.png; sourceTree = ""; }; 97F0F9FC18ABAECD00EF50AA /* iconCamera@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "iconCamera@2x.png"; sourceTree = ""; }; 97F0F9FF18AE375E00EF50AA /* BITImageAnnotationViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BITImageAnnotationViewController.h; sourceTree = ""; }; @@ -382,6 +402,16 @@ 1E5955A415B71BDC00A03429 /* Images */ = { isa = PBXGroup; children = ( + 9782022818F81BFC00A98D8B /* Arrow.png */, + 9782022918F81BFC00A98D8B /* Arrow@2x.png */, + 9782022A18F81BFC00A98D8B /* Blur.png */, + 9782022B18F81BFC00A98D8B /* Blur@2x.png */, + 9782022C18F81BFC00A98D8B /* Cancel.png */, + 9782022D18F81BFC00A98D8B /* Cancel@2x.png */, + 9782022E18F81BFC00A98D8B /* Ok.png */, + 9782022F18F81BFC00A98D8B /* Ok@2x.png */, + 9782023018F81BFC00A98D8B /* Rectangle.png */, + 9782023118F81BFC00A98D8B /* Rectangle@2x.png */, 97F0F9FB18ABAECD00EF50AA /* iconCamera.png */, 97F0F9FC18ABAECD00EF50AA /* iconCamera@2x.png */, 1E5955BB15B71C8600A03429 /* authorize_denied.png */, @@ -860,19 +890,29 @@ files = ( 1E5955C615B71C8600A03429 /* authorize_denied.png in Resources */, 1E5955C715B71C8600A03429 /* authorize_denied@2x.png in Resources */, + 9782023B18F81BFC00A98D8B /* Rectangle@2x.png in Resources */, 1E5955CA15B71C8600A03429 /* bg.png in Resources */, 97F0F9FD18ABAECD00EF50AA /* iconCamera.png in Resources */, + 9782023618F81BFC00A98D8B /* Cancel.png in Resources */, + 9782023318F81BFC00A98D8B /* Arrow@2x.png in Resources */, + 9782023818F81BFC00A98D8B /* Ok.png in Resources */, 1E5955CB15B71C8600A03429 /* buttonHighlight.png in Resources */, 1E5955CC15B71C8600A03429 /* buttonHighlight@2x.png in Resources */, 1E5955CF15B71C8600A03429 /* IconGradient.png in Resources */, 1E5955D015B71C8600A03429 /* IconGradient@2x.png in Resources */, + 9782023218F81BFC00A98D8B /* Arrow.png in Resources */, + 9782023A18F81BFC00A98D8B /* Rectangle.png in Resources */, 1EAF20A8162DC0F600957B1D /* feedbackActivity@2x~ipad.png in Resources */, 1EAF20A9162DC0F600957B1D /* feedbackActivity~ipad.png in Resources */, + 9782023918F81BFC00A98D8B /* Ok@2x.png in Resources */, + 9782023418F81BFC00A98D8B /* Blur.png in Resources */, 1EAF20AA162DC0F600957B1D /* feedbackActiviy.png in Resources */, 1EAF20AB162DC0F600957B1D /* feedbackActiviy@2x.png in Resources */, + 9782023518F81BFC00A98D8B /* Blur@2x.png in Resources */, 1E1127C416580C87007067A2 /* buttonRoundedDelete.png in Resources */, 1E1127C516580C87007067A2 /* buttonRoundedDelete@2x.png in Resources */, 1E1127C616580C87007067A2 /* buttonRoundedDeleteHighlighted.png in Resources */, + 9782023718F81BFC00A98D8B /* Cancel@2x.png in Resources */, 1E1127C716580C87007067A2 /* buttonRoundedDeleteHighlighted@2x.png in Resources */, 1E1127C816580C87007067A2 /* buttonRoundedRegular.png in Resources */, 97F0F9FE18ABAECD00EF50AA /* iconCamera@2x.png in Resources */, From 6151ed6ac79eedf00182099c33598cd28f4a199c Mon Sep 17 00:00:00 2001 From: moritz haarmann Date: Fri, 11 Apr 2014 15:01:24 +0200 Subject: [PATCH 40/92] + Adapted Add Photo Layout to iOS6 style. --- Classes/BITFeedbackComposeViewController.m | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Classes/BITFeedbackComposeViewController.m b/Classes/BITFeedbackComposeViewController.m index 6f147dcfcd..4ff3b4aad3 100644 --- a/Classes/BITFeedbackComposeViewController.m +++ b/Classes/BITFeedbackComposeViewController.m @@ -183,10 +183,10 @@ // Add Photo Button + Container that's displayed above the keyboard. self.textAccessoryView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, CGRectGetWidth(self.view.frame), 44)]; self.textAccessoryView.backgroundColor = [UIColor colorWithRed:0.9f green:0.9f blue:0.9f alpha:1.0f]; - UIButton *addPhotoButton = [UIButton buttonWithType:UIButtonTypeSystem]; + UIButton *addPhotoButton = [UIButton buttonWithType:UIButtonTypeCustom]; [addPhotoButton setTitle:@"+ Add Photo" forState:UIControlStateNormal]; - addPhotoButton.frame = CGRectMake(0, 0, 100, 44); - + [addPhotoButton setTitleColor:[UIColor darkGrayColor] forState:UIControlStateNormal]; + addPhotoButton.frame = CGRectMake(0, 0, CGRectGetWidth(self.view.frame), 44); [addPhotoButton addTarget:self action:@selector(addPhotoAction:) forControlEvents:UIControlEventTouchUpInside]; [self.textAccessoryView addSubview:addPhotoButton]; From 5e7ff0043ff318c35222c070f8f4209125991fa1 Mon Sep 17 00:00:00 2001 From: moritz haarmann Date: Fri, 11 Apr 2014 16:52:24 +0200 Subject: [PATCH 41/92] + Fixes List Display of Attachments for iOS6 --- Classes/BITFeedbackListViewCell.m | 33 ++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/Classes/BITFeedbackListViewCell.m b/Classes/BITFeedbackListViewCell.m index d622187627..9998a07648 100644 --- a/Classes/BITFeedbackListViewCell.m +++ b/Classes/BITFeedbackListViewCell.m @@ -67,6 +67,8 @@ @property (nonatomic, strong) NSMutableArray *attachmentViews; +@property (nonatomic, strong) UIView *accessoryBackgroundView; + @end @@ -112,6 +114,7 @@ #pragma mark - Private - (UIColor *)backgroundColor { + if (self.backgroundStyle == BITFeedbackListViewCellBackgroundStyleNormal) { if (self.style == BITFeedbackListViewCellPresentatationStyleDefault) { return BACKGROUNDCOLOR_DEFAULT; @@ -187,7 +190,7 @@ - (void)setAttachments:(NSArray *)attachments { for (UIView *view in self.attachmentViews){ - [view removeFromSuperview]; + [view removeFromSuperview]; } [self.attachmentViews removeAllObjects]; @@ -199,18 +202,25 @@ [imageView addTarget:self action:@selector(imageButtonPressed:) forControlEvents:UIControlEventTouchUpInside]; [self.attachmentViews addObject:imageView]; - [self addSubview:imageView]; } } - (void)layoutSubviews { - UIView *accessoryViewBackground = [[UIView alloc] initWithFrame:CGRectMake(0, 2, self.frame.size.width * 2, self.frame.size.height - 2)]; - accessoryViewBackground.autoresizingMask = UIViewAutoresizingFlexibleHeight; - accessoryViewBackground.clipsToBounds = YES; + if (!self.accessoryBackgroundView){ + self.accessoryBackgroundView = [[UIView alloc] initWithFrame:CGRectMake(0, 2, self.frame.size.width * 2, self.frame.size.height - 2)]; + self.accessoryBackgroundView.autoresizingMask = UIViewAutoresizingFlexibleHeight; + self.accessoryBackgroundView.clipsToBounds = YES; // colors - accessoryViewBackground.backgroundColor = [self backgroundColor]; + self.accessoryBackgroundView.backgroundColor = [self backgroundColor]; + } + + if (self.style == BITFeedbackListViewCellPresentatationStyleDefault) { + [self addSubview:self.accessoryBackgroundView]; + } else if (self.accessoryBackgroundView.superview){ + [self.accessoryBackgroundView removeFromSuperview]; + } self.contentView.backgroundColor = [self backgroundColor]; self.labelTitle.backgroundColor = [self backgroundColor]; self.labelText.backgroundColor = [self backgroundColor]; @@ -223,9 +233,7 @@ } // background for deletion accessory view - if (self.style == BITFeedbackListViewCellPresentatationStyleDefault) { - [self addSubview:accessoryViewBackground]; - } + // header NSString *dateString = @""; @@ -266,6 +274,7 @@ int i = 0; CGFloat attachmentsPerRow = ceilf(self.frame.size.width / (FRAME_SIDE_BORDER + ATTACHMENT_SIZE)); + for ( UIButton *imageButton in self.attachmentViews){ imageButton.contentMode = UIViewContentModeScaleAspectFit; @@ -275,11 +284,13 @@ imageButton.frame = CGRectMake(self.frame.size.width - FRAME_SIDE_BORDER - ATTACHMENT_SIZE - ((FRAME_SIDE_BORDER + ATTACHMENT_SIZE) * (i) ), floor(i/attachmentsPerRow) + baseOffsetOfText , ATTACHMENT_SIZE, ATTACHMENT_SIZE); } + if (!imageButton.superview){ + [self addSubview:imageButton]; + } + i++; - } - [super layoutSubviews]; } From cf17b298a7785b86d85b6e99d3af29fb1ff0173b Mon Sep 17 00:00:00 2001 From: moritz haarmann Date: Fri, 11 Apr 2014 16:52:41 +0200 Subject: [PATCH 42/92] + Fixes an iOS6 Crash in the gallery-component. --- Classes/BITAttachmentGalleryViewController.m | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Classes/BITAttachmentGalleryViewController.m b/Classes/BITAttachmentGalleryViewController.m index 60fa6ff993..7837fec26e 100644 --- a/Classes/BITAttachmentGalleryViewController.m +++ b/Classes/BITAttachmentGalleryViewController.m @@ -36,10 +36,12 @@ - (void)viewDidLoad { [super viewDidLoad]; - self.automaticallyAdjustsScrollViewInsets = NO; self.navigationController.navigationBar.translucent = YES; +#if __IPHONE_OS_VERSION_MIN_REQUIRED > __IPHONE_6_1 + self.automaticallyAdjustsScrollViewInsets = NO; self.edgesForExtendedLayout = YES; self.extendedLayoutIncludesOpaqueBars = YES; +#endif self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"Close" style:UIBarButtonItemStylePlain target:self action:@selector(close:)]; self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAction target:self action:@selector(share:)]; From b230c964a6f1b70cc8c31f40a5e57a5f62f0aab5 Mon Sep 17 00:00:00 2001 From: moritz haarmann Date: Fri, 11 Apr 2014 17:43:34 +0200 Subject: [PATCH 43/92] + WIP --- Classes/BITAttachmentGalleryViewController.m | 16 ++++++++++------ Classes/BITFeedbackComposeViewController.m | 3 +-- Classes/BITFeedbackMessageAttachment.m | 13 ++++++++++--- 3 files changed, 21 insertions(+), 11 deletions(-) diff --git a/Classes/BITAttachmentGalleryViewController.m b/Classes/BITAttachmentGalleryViewController.m index 7837fec26e..48b2d59419 100644 --- a/Classes/BITAttachmentGalleryViewController.m +++ b/Classes/BITAttachmentGalleryViewController.m @@ -52,6 +52,9 @@ [self extractUsableAttachments]; [self setupScrollView]; + self.view.frame = UIScreen.mainScreen.applicationFrame; + // self.view.frame.origin = CGPointZero; + [self layoutViews]; } @@ -80,7 +83,7 @@ for (int i = 0; i<3; i++){ UIImageView *newImageView = [[UIImageView alloc] initWithFrame:CGRectZero]; [imageviews addObject:newImageView]; - newImageView.contentMode = UIViewContentModeScaleAspectFit; + newImageView.contentMode = UIViewContentModeScaleAspectFit|UIViewContentModeBottom; [self.scrollView addSubview:newImageView]; } @@ -113,11 +116,7 @@ NSInteger newIndex = self.scrollView.contentOffset.x / self.scrollView.frame.size.width; if (newIndex!=self.currentIndex){ self.currentIndex = newIndex; - // requeue elements. - NSInteger baseIndex = MAX(0,self.currentIndex-1); [self layoutViews]; - - } } @@ -125,6 +124,10 @@ self.scrollView.frame = self.view.bounds; self.scrollView.contentSize = CGSizeMake(CGRectGetWidth(self.view.bounds) * self.extractedAttachments.count, CGRectGetHeight(self.view.bounds)); + + self.scrollView.contentInset = UIEdgeInsetsZero; + + self.scrollView.contentOffset = CGPointMake(self.scrollView.contentOffset.x, 0); NSInteger baseIndex = MAX(0,self.currentIndex-1); NSInteger z = baseIndex; @@ -163,10 +166,11 @@ [[UIApplication sharedApplication] setStatusBarHidden:YES]; } + [self layoutViews]; } - (CGRect)frameForItemAtIndex:(NSInteger)index { - return CGRectMake(index * CGRectGetWidth(self.scrollView.frame), 0, CGRectGetWidth(self.scrollView.frame), CGRectGetHeight(self.scrollView.frame)); + return CGRectMake(index * CGRectGetWidth(self.scrollView.frame), 0, CGRectGetWidth(self.scrollView.frame), CGRectGetHeight(self.view.bounds)); } @end diff --git a/Classes/BITFeedbackComposeViewController.m b/Classes/BITFeedbackComposeViewController.m index 4ff3b4aad3..bb35383251 100644 --- a/Classes/BITFeedbackComposeViewController.m +++ b/Classes/BITFeedbackComposeViewController.m @@ -184,8 +184,7 @@ self.textAccessoryView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, CGRectGetWidth(self.view.frame), 44)]; self.textAccessoryView.backgroundColor = [UIColor colorWithRed:0.9f green:0.9f blue:0.9f alpha:1.0f]; UIButton *addPhotoButton = [UIButton buttonWithType:UIButtonTypeCustom]; - [addPhotoButton setTitle:@"+ Add Photo" forState:UIControlStateNormal]; - [addPhotoButton setTitleColor:[UIColor darkGrayColor] forState:UIControlStateNormal]; + [addPhotoButton setTitle:@"+ Add Photo" forState:UIControlStateNormal]; [addPhotoButton setTitleColor:[UIColor darkGrayColor] forState:UIControlStateNormal]; addPhotoButton.frame = CGRectMake(0, 0, CGRectGetWidth(self.view.frame), 44); [addPhotoButton addTarget:self action:@selector(addPhotoAction:) forControlEvents:UIControlEventTouchUpInside]; diff --git a/Classes/BITFeedbackMessageAttachment.m b/Classes/BITFeedbackMessageAttachment.m index b0d68b82fa..e3b6ef3010 100644 --- a/Classes/BITFeedbackMessageAttachment.m +++ b/Classes/BITFeedbackMessageAttachment.m @@ -107,7 +107,7 @@ - (UIImage *)imageRepresentation { if ([self.contentType rangeOfString:@"image"].location != NSNotFound){ - return [UIImage imageWithData:self.data]; + return [UIImage imageWithData:self.data scale:[UIScreen mainScreen].scale]; } else { return bit_imageNamed(@"feedbackActiviy.png", BITHOCKEYSDK_BUNDLE); // TODO add another placeholder. } @@ -118,9 +118,16 @@ if (!self.thumbnailRepresentations[cacheKey]){ UIImage *image = self.imageRepresentation; - UIImage *thumbnail = bit_imageToFitSize(image, size, NO); + // consider the scale. + + CGFloat scale = [UIScreen mainScreen].scale; + + CGSize scaledSize = CGSizeApplyAffineTransform(size, CGAffineTransformMakeScale(scale, scale)); + UIImage *thumbnail = bit_imageToFitSize(image, scaledSize, NO) ; + + UIImage *scaledTumbnail = [UIImage imageWithCGImage:thumbnail.CGImage scale:scale orientation:thumbnail.imageOrientation]; if (thumbnail){ - [self.thumbnailRepresentations setObject:thumbnail forKey:cacheKey]; + [self.thumbnailRepresentations setObject:scaledTumbnail forKey:cacheKey]; } } From d891af1929bb57048c722e68ca1021282857ba37 Mon Sep 17 00:00:00 2001 From: moritz haarmann Date: Tue, 15 Apr 2014 16:13:53 +0200 Subject: [PATCH 44/92] + Fixes a layout problem in the attachment gallery. --- Classes/BITAttachmentGalleryViewController.m | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/Classes/BITAttachmentGalleryViewController.m b/Classes/BITAttachmentGalleryViewController.m index 48b2d59419..24c7316847 100644 --- a/Classes/BITAttachmentGalleryViewController.m +++ b/Classes/BITAttachmentGalleryViewController.m @@ -37,6 +37,7 @@ { [super viewDidLoad]; self.navigationController.navigationBar.translucent = YES; + self.navigationController.navigationBar.opaque = NO; #if __IPHONE_OS_VERSION_MIN_REQUIRED > __IPHONE_6_1 self.automaticallyAdjustsScrollViewInsets = NO; self.edgesForExtendedLayout = YES; @@ -55,15 +56,22 @@ self.view.frame = UIScreen.mainScreen.applicationFrame; // self.view.frame.origin = CGPointZero; +} +-(void)viewWillAppear:(BOOL)animated { + [super viewWillAppear:animated]; + + // Hide the navigation bar and stuff initially. + [self.navigationController setNavigationBarHidden:YES animated:NO]; + [[UIApplication sharedApplication] setStatusBarHidden:YES]; + [self layoutViews]; + + } - (void)viewDidAppear:(BOOL)animated { [super viewDidAppear:animated]; - - - self.tapognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapped:)]; [self.view addGestureRecognizer:self.tapognizer]; @@ -83,7 +91,7 @@ for (int i = 0; i<3; i++){ UIImageView *newImageView = [[UIImageView alloc] initWithFrame:CGRectZero]; [imageviews addObject:newImageView]; - newImageView.contentMode = UIViewContentModeScaleAspectFit|UIViewContentModeBottom; + newImageView.contentMode = UIViewContentModeScaleAspectFit; [self.scrollView addSubview:newImageView]; } @@ -126,6 +134,7 @@ self.scrollView.contentSize = CGSizeMake(CGRectGetWidth(self.view.bounds) * self.extractedAttachments.count, CGRectGetHeight(self.view.bounds)); self.scrollView.contentInset = UIEdgeInsetsZero; + self.scrollView.autoresizesSubviews = NO; self.scrollView.contentOffset = CGPointMake(self.scrollView.contentOffset.x, 0); From 1df69d131a47f18e4d9269bb88188cd7231f08a4 Mon Sep 17 00:00:00 2001 From: moritz haarmann Date: Tue, 15 Apr 2014 16:29:51 +0200 Subject: [PATCH 45/92] + Preselecting an attachment works. --- Classes/BITAttachmentGalleryViewController.h | 4 ++++ Classes/BITAttachmentGalleryViewController.m | 21 +++++++++++++++----- Classes/BITFeedbackListViewController.m | 6 ++++-- 3 files changed, 24 insertions(+), 7 deletions(-) diff --git a/Classes/BITAttachmentGalleryViewController.h b/Classes/BITAttachmentGalleryViewController.h index c246390ff0..a745b6dde0 100644 --- a/Classes/BITAttachmentGalleryViewController.h +++ b/Classes/BITAttachmentGalleryViewController.h @@ -8,8 +8,12 @@ #import +@class BITFeedbackMessageAttachment; + @interface BITAttachmentGalleryViewController : UIViewController @property (nonatomic, strong) NSArray *messages; +@property (nonatomic, strong) BITFeedbackMessageAttachment *preselectedAttachment; + @end diff --git a/Classes/BITAttachmentGalleryViewController.m b/Classes/BITAttachmentGalleryViewController.m index 24c7316847..f3e5b79ce7 100644 --- a/Classes/BITAttachmentGalleryViewController.m +++ b/Classes/BITAttachmentGalleryViewController.m @@ -54,8 +54,9 @@ [self setupScrollView]; self.view.frame = UIScreen.mainScreen.applicationFrame; - // self.view.frame.origin = CGPointZero; + self.tapognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapped:)]; + [self.view addGestureRecognizer:self.tapognizer]; } -(void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; @@ -64,6 +65,15 @@ [self.navigationController setNavigationBarHidden:YES animated:NO]; [[UIApplication sharedApplication] setStatusBarHidden:YES]; + if (self.preselectedAttachment){ + NSInteger indexOfSelectedAttachment = [self.extractedAttachments indexOfObject:self.preselectedAttachment]; + if (indexOfSelectedAttachment != NSNotFound){ + self.currentIndex = indexOfSelectedAttachment; + self.scrollView.contentOffset = CGPointMake(self.scrollView.frame.size.width * self.currentIndex, 0); + + } + } + [self layoutViews]; @@ -72,8 +82,7 @@ - (void)viewDidAppear:(BOOL)animated { [super viewDidAppear:animated]; - self.tapognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapped:)]; - [self.view addGestureRecognizer:self.tapognizer]; + } - (void)setupScrollView { @@ -129,14 +138,16 @@ } - (void)layoutViews { + CGPoint savedOffset = self.scrollView.contentOffset; + self.scrollView.delegate = nil; self.scrollView.frame = self.view.bounds; self.scrollView.contentSize = CGSizeMake(CGRectGetWidth(self.view.bounds) * self.extractedAttachments.count, CGRectGetHeight(self.view.bounds)); - + self.scrollView.delegate = self; self.scrollView.contentInset = UIEdgeInsetsZero; self.scrollView.autoresizesSubviews = NO; + self.scrollView.contentOffset = savedOffset; - self.scrollView.contentOffset = CGPointMake(self.scrollView.contentOffset.x, 0); NSInteger baseIndex = MAX(0,self.currentIndex-1); NSInteger z = baseIndex; diff --git a/Classes/BITFeedbackListViewController.m b/Classes/BITFeedbackListViewController.m index fe45d9e52d..70f58a1f8a 100644 --- a/Classes/BITFeedbackListViewController.m +++ b/Classes/BITFeedbackListViewController.m @@ -773,14 +773,16 @@ - (void)listCell:(id)cell didSelectAttachment:(BITFeedbackMessageAttachment *)attachment { BITAttachmentGalleryViewController *galleryController = [BITAttachmentGalleryViewController new]; - UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:galleryController]; + NSMutableArray *collectedMessages = [NSMutableArray new]; + for (int i = 0; i Date: Tue, 15 Apr 2014 17:06:06 +0200 Subject: [PATCH 46/92] + Fixes a problem with more than 3 attachments. --- Classes/BITAttachmentGalleryViewController.m | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Classes/BITAttachmentGalleryViewController.m b/Classes/BITAttachmentGalleryViewController.m index f3e5b79ce7..c1224ac647 100644 --- a/Classes/BITAttachmentGalleryViewController.m +++ b/Classes/BITAttachmentGalleryViewController.m @@ -120,8 +120,6 @@ } self.extractedAttachments = extractedOnes; - - [self layoutViews]; } - (void)didReceiveMemoryWarning @@ -152,7 +150,7 @@ NSInteger baseIndex = MAX(0,self.currentIndex-1); NSInteger z = baseIndex; for ( NSInteger i = baseIndex; i < MIN(baseIndex+2, self.extractedAttachments.count);i++ ){ - UIImageView *imageView = self.imageViews[z]; + UIImageView *imageView = self.imageViews[z%self.imageViews.count]; BITFeedbackMessageAttachment *attachment = self.extractedAttachments[i]; imageView.image =[attachment imageRepresentation]; imageView.frame = [self frameForItemAtIndex:i]; From f9b76db0d700bd3eef72a730a086809438a3f389 Mon Sep 17 00:00:00 2001 From: moritz haarmann Date: Tue, 15 Apr 2014 17:06:12 +0200 Subject: [PATCH 47/92] + Resolves a scaling issue. --- Classes/BITFeedbackMessageAttachment.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Classes/BITFeedbackMessageAttachment.m b/Classes/BITFeedbackMessageAttachment.m index e3b6ef3010..0c8d3a87de 100644 --- a/Classes/BITFeedbackMessageAttachment.m +++ b/Classes/BITFeedbackMessageAttachment.m @@ -107,7 +107,7 @@ - (UIImage *)imageRepresentation { if ([self.contentType rangeOfString:@"image"].location != NSNotFound){ - return [UIImage imageWithData:self.data scale:[UIScreen mainScreen].scale]; + return [UIImage imageWithData:self.data]; } else { return bit_imageNamed(@"feedbackActiviy.png", BITHOCKEYSDK_BUNDLE); // TODO add another placeholder. } From 5038c3e4e9d233c350183aeb1bb088257292dd42 Mon Sep 17 00:00:00 2001 From: moritz haarmann Date: Tue, 15 Apr 2014 17:25:06 +0200 Subject: [PATCH 48/92] + Fixed a problem where missing file names would prevent the submittal of attachments. --- Classes/BITFeedbackManager.m | 10 +++++++++- Classes/BITFeedbackMessageAttachment.m | 11 +++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/Classes/BITFeedbackManager.m b/Classes/BITFeedbackManager.m index efabd38d64..7d18f91c7e 100644 --- a/Classes/BITFeedbackManager.m +++ b/Classes/BITFeedbackManager.m @@ -866,7 +866,15 @@ for (BITFeedbackMessageAttachment *attachment in message.attachments){ NSString *key = [NSString stringWithFormat:@"attachment%ld", (long)photoIndex]; - [postBody appendData:[BITHockeyAppClient dataWithPostValue:attachment.data forKey:key contentType:attachment.contentType boundary:boundary filename:attachment.originalFilename]]; + + NSString *filename = attachment.originalFilename; + + if (!filename) { + filename = [NSString stringWithFormat:@"Attachment %ld", (long)photoIndex]; + } + + [postBody appendData:[BITHockeyAppClient dataWithPostValue:attachment.data forKey:key contentType:attachment.contentType boundary:boundary filename:filename]]; + photoIndex++; } diff --git a/Classes/BITFeedbackMessageAttachment.m b/Classes/BITFeedbackMessageAttachment.m index 0c8d3a87de..ba63a7f286 100644 --- a/Classes/BITFeedbackMessageAttachment.m +++ b/Classes/BITFeedbackMessageAttachment.m @@ -44,9 +44,20 @@ @implementation BITFeedbackMessageAttachment + (BITFeedbackMessageAttachment *)attachmentWithData:(NSData *)data contentType:(NSString *)contentType { + + static NSDateFormatter *formatter; + + if(!formatter){ + formatter = [NSDateFormatter new]; + formatter.dateStyle = NSDateFormatterShortStyle; + formatter.timeStyle = NSDateFormatterShortStyle; + + } + BITFeedbackMessageAttachment *newAttachment = [BITFeedbackMessageAttachment new]; newAttachment.contentType = contentType; newAttachment.data = data; + newAttachment.originalFilename = [NSString stringWithFormat:@"Attachment: %@", [formatter stringFromDate:[NSDate date]]]; return newAttachment; } From 3aa45bbb9f909a2c717f3e64a2f8c4554d1ff6e1 Mon Sep 17 00:00:00 2001 From: moritz haarmann Date: Wed, 16 Apr 2014 11:24:39 +0200 Subject: [PATCH 49/92] + Added preliminary load mechanics for Feedback Images. --- Classes/BITFeedbackManager.m | 137 ++++++++++++++++--------- Classes/BITFeedbackMessage.h | 4 + Classes/BITFeedbackMessage.m | 7 +- Classes/BITFeedbackMessageAttachment.h | 3 + Classes/BITFeedbackMessageAttachment.m | 11 +- 5 files changed, 113 insertions(+), 49 deletions(-) diff --git a/Classes/BITFeedbackManager.m b/Classes/BITFeedbackManager.m index 7d18f91c7e..5cd2091d1f 100644 --- a/Classes/BITFeedbackManager.m +++ b/Classes/BITFeedbackManager.m @@ -81,7 +81,7 @@ _requireUserEmail = BITFeedbackUserDataElementOptional; _showAlertOnIncomingMessages = YES; _showFirstRequiredPresentationModal = YES; - + _disableFeedbackManager = NO; _networkRequestInProgress = NO; _incomingMessagesAlertShowing = NO; @@ -90,9 +90,9 @@ _lastMessageID = nil; self.feedbackList = [NSMutableArray array]; - + _fileManager = [[NSFileManager alloc] init]; - + _settingsFile = [bit_settingsDir() stringByAppendingPathComponent:BITHOCKEY_FEEDBACK_SETTINGS]; _userID = nil; @@ -152,12 +152,12 @@ } if(nil == _networkDidBecomeReachableObserver) { _networkDidBecomeReachableObserver = [[NSNotificationCenter defaultCenter] addObserverForName:BITHockeyNetworkDidBecomeReachableNotification - object:nil - queue:NSOperationQueue.mainQueue - usingBlock:^(NSNotification *note) { - typeof(self) strongSelf = weakSelf; - [strongSelf didBecomeActiveActions]; - }]; + object:nil + queue:NSOperationQueue.mainQueue + usingBlock:^(NSNotification *note) { + typeof(self) strongSelf = weakSelf; + [strongSelf didBecomeActiveActions]; + }]; } } @@ -228,9 +228,9 @@ } BITFeedbackComposeViewController *composeView = [self feedbackComposeViewController]; [composeView prepareWithItems:items]; - + [self showView:composeView]; - + } - (void)showFeedbackComposeViewWithGeneratedScreenshot { @@ -242,7 +242,7 @@ - (void)startManager { if ([self isFeedbackManagerDisabled]) return; - + [self registerObservers]; // we are already delayed, so the notification already came in and this won't invoked twice @@ -289,12 +289,12 @@ userIDForHockeyManager:[BITHockeyManager sharedHockeyManager] componentManager:self]; } - + if (userID) { availableViaDelegate = YES; self.userID = userID; } - + return availableViaDelegate; } @@ -306,16 +306,16 @@ if ([BITHockeyManager sharedHockeyManager].delegate && [[BITHockeyManager sharedHockeyManager].delegate respondsToSelector:@selector(userNameForHockeyManager:componentManager:)]) { userName = [[BITHockeyManager sharedHockeyManager].delegate - userNameForHockeyManager:[BITHockeyManager sharedHockeyManager] - componentManager:self]; + userNameForHockeyManager:[BITHockeyManager sharedHockeyManager] + componentManager:self]; } - + if (userName) { availableViaDelegate = YES; self.userName = userName; self.requireUserName = BITFeedbackUserDataElementDontShow; } - + return availableViaDelegate; } @@ -323,20 +323,20 @@ BOOL availableViaDelegate = NO; NSString *userEmail = [self stringValueFromKeychainForKey:kBITHockeyMetaUserEmail]; - + if ([BITHockeyManager sharedHockeyManager].delegate && [[BITHockeyManager sharedHockeyManager].delegate respondsToSelector:@selector(userEmailForHockeyManager:componentManager:)]) { userEmail = [[BITHockeyManager sharedHockeyManager].delegate userEmailForHockeyManager:[BITHockeyManager sharedHockeyManager] componentManager:self]; } - + if (userEmail) { availableViaDelegate = YES; self.userEmail = userEmail; self.requireUserEmail = BITFeedbackUserDataElementDontShow; } - + return availableViaDelegate; } @@ -344,7 +344,7 @@ [self updateUserIDUsingKeychainAndDelegate]; [self updateUserNameUsingKeychainAndDelegate]; [self updateUserEmailUsingKeychainAndDelegate]; - + // if both values are shown via the delegates, we never ever did ask and will never ever ask for user data if (self.requireUserName == BITFeedbackUserDataElementDontShow && self.requireUserEmail == BITFeedbackUserDataElementDontShow) { @@ -361,7 +361,7 @@ if (![_fileManager fileExistsAtPath:_settingsFile]) return; - + NSData *codedData = [[NSData alloc] initWithContentsOfFile:_settingsFile]; if (codedData == nil) return; @@ -373,7 +373,7 @@ @catch (NSException *exception) { return; } - + if (!userIDViaDelegate) { if ([unarchiver containsValueForKey:kBITFeedbackUserID]) { self.userID = [unarchiver decodeObjectForKey:kBITFeedbackUserID]; @@ -389,7 +389,7 @@ } self.userName = [self stringValueFromKeychainForKey:kBITFeedbackName]; } - + if (!userEmailViaDelegate) { if ([unarchiver containsValueForKey:kBITFeedbackEmail]) { self.userEmail = [unarchiver decodeObjectForKey:kBITFeedbackEmail]; @@ -409,7 +409,7 @@ if ([unarchiver containsValueForKey:kBITFeedbackAppID]) { NSString *appID = [unarchiver decodeObjectForKey:kBITFeedbackAppID]; - + // the stored thread is from another application identifier, so clear the token // which will cause the new posts to create a new thread on the server for the // current app identifier @@ -432,9 +432,9 @@ // inform the UI to update its data in case the list is already showing [[NSNotificationCenter defaultCenter] postNotificationName:BITHockeyFeedbackMessagesLoadingFinished object:nil]; } - + [unarchiver finishDecoding]; - + if (!self.lastCheck) { self.lastCheck = [NSDate distantPast]; } @@ -446,7 +446,7 @@ NSMutableData *data = [[NSMutableData alloc] init]; NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data]; - + if (_didAskUserData) [archiver encodeObject:[NSNumber numberWithBool:YES] forKey:kBITFeedbackUserDataAsked]; @@ -631,7 +631,7 @@ - (BOOL)isManualUserDataAvailable { [self updateAppDefinedUserData]; - + if ((self.requireUserName != BITFeedbackUserDataElementDontShow && self.userName) || (self.requireUserEmail != BITFeedbackUserDataElementDontShow && self.userEmail)) return YES; @@ -645,23 +645,23 @@ - (void)updateMessageListFromResponse:(NSDictionary *)jsonDictionary { if (!jsonDictionary) { // nil is used when the server returns 404, so we need to mark all existing threads as archives and delete the discussion token - + NSArray *messagesSendInProgress = [self messagesWithStatus:BITFeedbackMessageStatusSendInProgress]; NSInteger pendingMessagesCount = [messagesSendInProgress count] + [[self messagesWithStatus:BITFeedbackMessageStatusSendPending] count]; - + [self markSendInProgressMessagesAsPending]; [_feedbackList enumerateObjectsUsingBlock:^(id objMessage, NSUInteger messagesIdx, BOOL *stop) { if ([(BITFeedbackMessage *)objMessage status] != BITFeedbackMessageStatusSendPending) [(BITFeedbackMessage *)objMessage setStatus:BITFeedbackMessageStatusArchived]; }]; - + if ([self token]) { self.token = nil; } NSInteger pendingMessagesCountAfterProcessing = [[self messagesWithStatus:BITFeedbackMessageStatusSendPending] count]; - + [self saveMessages]; // check if this request was successful and we have more messages pending and continue if positive @@ -710,7 +710,7 @@ *stop2 = YES; } }]; - + if (matchingSendInProgressOrInConflictMessage) { matchingSendInProgressOrInConflictMessage.date = [self parseRFC3339Date:[(NSDictionary *)objMessage objectForKey:@"created_at"]]; matchingSendInProgressOrInConflictMessage.id = messageID; @@ -734,6 +734,15 @@ message.id = [(NSDictionary *)objMessage objectForKey:@"id"]; message.status = BITFeedbackMessageStatusUnread; + for (NSDictionary *attachmentData in objMessage[@"attachments"]){ + BITFeedbackMessageAttachment *newAttachment = [BITFeedbackMessageAttachment new]; + newAttachment.originalFilename = attachmentData[@"file_name"]; + newAttachment.id = attachmentData[@"id"]; + newAttachment.sourceURL = attachmentData[@"url"]; + newAttachment.contentType = @"image/jpg"; + [message addAttachmentsObject:newAttachment]; + } + [_feedbackList addObject:message]; newMessage = YES; @@ -750,7 +759,7 @@ [self sortFeedbackList]; [self updateLastMessageID]; - + // we got a new incoming message, trigger user notification system if (newMessage) { // check if the latest message is from the users own email address, then don't show an alert since he answered using his own email @@ -759,12 +768,12 @@ BITFeedbackMessage *latestMessage = [self lastMessageHavingID]; if (self.userEmail && latestMessage.email && [self.userEmail compare:latestMessage.email] == NSOrderedSame) latestMessageFromUser = YES; - + if (!latestMessageFromUser) { if([self.delegate respondsToSelector:@selector(feedbackManagerDidReceiveNewFeedback:)]) { [self.delegate feedbackManagerDidReceiveNewFeedback:self]; } - + if(self.showAlertOnIncomingMessages && !self.currentFeedbackListViewController && !self.currentFeedbackComposeViewController) { UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:BITHockeyLocalizedString(@"HockeyFeedbackNewMessageTitle") message:BITHockeyLocalizedString(@"HockeyFeedbackNewMessageText") @@ -780,7 +789,7 @@ } NSInteger pendingMessagesCountAfterProcessing = [[self messagesWithStatus:BITFeedbackMessageStatusSendPending] count]; - + // check if this request was successful and we have more messages pending and continue if positive if (pendingMessagesCount > pendingMessagesCountAfterProcessing && pendingMessagesCountAfterProcessing > 0) { [self performSelector:@selector(submitPendingMessages) withObject:nil afterDelay:0.1]; @@ -790,18 +799,52 @@ [self markSendInProgressMessagesAsPending]; } + [self synchronizeMissingAttachments]; + [self saveMessages]; - + return; } +/** + Load all attachments without any local data to have them available. + */ +-(BOOL)synchronizeMissingAttachments { + // Extract all Attachments. + NSMutableArray *allAttachments = [NSMutableArray new]; + for (int i = 0; i < [self numberOfMessages]; i++){ + BITFeedbackMessage *message = [self messageAtIndex:i]; + for (BITFeedbackMessageAttachment *attachment in message.attachments){ + if (attachment.needsLoadingFromURL){ + [allAttachments addObject:attachment]; + } + } + } + + for (BITFeedbackMessageAttachment *attachment in allAttachments){ + // we will just update the objects here and perform a save after each successful load operation. + + NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:attachment.sourceURL]]; + __weak BITFeedbackManager *weakSelf = self; + [NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *responseData, NSError *err) { + if (responseData.length){ + [attachment replaceData:responseData]; + [weakSelf saveMessages]; + + } + }]; + + } + return NO; +} + - (void)sendNetworkRequestWithHTTPMethod:(NSString *)httpMethod withMessage:(BITFeedbackMessage *)message completionHandler:(void (^)(NSError *err))completionHandler { NSString *boundary = @"----FOO"; _networkRequestInProgress = YES; // inform the UI to update its data in case the list is already showing [[NSNotificationCenter defaultCenter] postNotificationName:BITHockeyFeedbackMessagesLoadingStarted object:nil]; - + NSString *tokenParameter = @""; if ([self token]) { tokenParameter = [NSString stringWithFormat:@"/%@", [self token]]; @@ -879,7 +922,7 @@ } [postBody appendData:[[NSString stringWithFormat:@"--%@--\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]]; - + [request setHTTPBody:postBody]; } @@ -902,14 +945,14 @@ if (!self.token) { // set the token to the first message token, since this is identical __block NSString *token = nil; - + [_feedbackList enumerateObjectsUsingBlock:^(id objMessage, NSUInteger messagesIdx, BOOL *stop) { if ([(BITFeedbackMessage *)objMessage status] == BITFeedbackMessageStatusSendInProgress) { token = [(BITFeedbackMessage *)objMessage token]; *stop = YES; } }]; - + if (token) { self.token = token; } @@ -982,7 +1025,7 @@ [self saveMessages]; NSArray *pendingMessages = [self messagesWithStatus:BITFeedbackMessageStatusSendPending]; - + if ([pendingMessages count] > 0) { // we send one message at a time BITFeedbackMessage *messageToSend = [pendingMessages objectAtIndex:0]; @@ -1041,11 +1084,11 @@ } } -#pragma mark - Observation Handling +#pragma mark - Observation Handling -(void)setFeedbackObservationMode:(BITFeedbackObservationMode)mode { if (mode == BITFeedbackObservationModeOnScreenshot){ - // [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(screenshotNotificationReceived:) name:UIApplicationUserDidTakeScreenshotNotification object:nil]; + // [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(screenshotNotificationReceived:) name:UIApplicationUserDidTakeScreenshotNotification object:nil]; } } diff --git a/Classes/BITFeedbackMessage.h b/Classes/BITFeedbackMessage.h index baa6d6fdef..44ff0e0d7e 100644 --- a/Classes/BITFeedbackMessage.h +++ b/Classes/BITFeedbackMessage.h @@ -29,6 +29,8 @@ #import +@class BITFeedbackMessageAttachment; + /** * Status for each feedback message */ @@ -79,5 +81,7 @@ typedef NS_ENUM(NSInteger, BITFeedbackMessageStatus) { */ -(void)deleteContents; +-(void)addAttachmentsObject:(BITFeedbackMessageAttachment *)object; + @end diff --git a/Classes/BITFeedbackMessage.m b/Classes/BITFeedbackMessage.m index eb44289600..446fb0da3c 100644 --- a/Classes/BITFeedbackMessage.m +++ b/Classes/BITFeedbackMessage.m @@ -90,5 +90,10 @@ [attachment deleteContents]; } } - +-(void)addAttachmentsObject:(BITFeedbackMessageAttachment *)object{ + if (!self.attachments){ + self.attachments = [NSArray array]; + } + self.attachments = [self.attachments arrayByAddingObject:object]; +} @end diff --git a/Classes/BITFeedbackMessageAttachment.h b/Classes/BITFeedbackMessageAttachment.h index 4a9a8ea646..1d05641971 100644 --- a/Classes/BITFeedbackMessageAttachment.h +++ b/Classes/BITFeedbackMessageAttachment.h @@ -35,6 +35,7 @@ @property (nonatomic, copy) NSNumber *id; @property (nonatomic, copy) NSString *originalFilename; @property (nonatomic, copy) NSString *contentType; +@property (nonatomic, copy) NSString *sourceURL; @property (nonatomic, readonly) NSData *data; @property (readonly) UIImage *imageRepresentation; @@ -48,4 +49,6 @@ - (void)deleteContents; +-(BOOL)needsLoadingFromURL; + @end diff --git a/Classes/BITFeedbackMessageAttachment.m b/Classes/BITFeedbackMessageAttachment.m index ba63a7f286..3350b96d24 100644 --- a/Classes/BITFeedbackMessageAttachment.m +++ b/Classes/BITFeedbackMessageAttachment.m @@ -39,6 +39,7 @@ @property (nonatomic, strong) NSData *internalData; @property (nonatomic, copy) NSString *filename; + @end @implementation BITFeedbackMessageAttachment @@ -92,12 +93,18 @@ self.thumbnailRepresentations = [NSMutableDictionary new]; } +-(BOOL)needsLoadingFromURL { + return (self.sourceURL); +} + #pragma mark NSCoding - (void)encodeWithCoder:(NSCoder *)aCoder { [aCoder encodeObject:self.contentType forKey:@"contentType"]; [aCoder encodeObject:self.filename forKey:@"filename"]; [aCoder encodeObject:self.originalFilename forKey:@"originalFilename"]; + [aCoder encodeObject:self.sourceURL forKey:@"url"]; + } @@ -109,6 +116,8 @@ self.filename = [aDecoder decodeObjectForKey:@"filename"]; self.thumbnailRepresentations = [NSMutableDictionary new]; self.originalFilename = [aDecoder decodeObjectForKey:@"originalFilename"]; + self.sourceURL = [aDecoder decodeObjectForKey:@"sourceURL"]; + } return self; @@ -117,7 +126,7 @@ #pragma mark - Thubmnails / Image Representation - (UIImage *)imageRepresentation { - if ([self.contentType rangeOfString:@"image"].location != NSNotFound){ + if ([self.contentType rangeOfString:@"image"].location != NSNotFound || [self.sourceURL rangeOfString:@"jpeg"].location != NSNotFound){ return [UIImage imageWithData:self.data]; } else { return bit_imageNamed(@"feedbackActiviy.png", BITHOCKEYSDK_BUNDLE); // TODO add another placeholder. From bc0245978ad18e31032812ac81913533ff416246 Mon Sep 17 00:00:00 2001 From: Moritz Haarmann Date: Mon, 28 Apr 2014 00:45:41 +0200 Subject: [PATCH 50/92] + Hides Statusbar --- Classes/BITImageAnnotationViewController.m | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/Classes/BITImageAnnotationViewController.m b/Classes/BITImageAnnotationViewController.m index 910b81eaad..596228032e 100644 --- a/Classes/BITImageAnnotationViewController.m +++ b/Classes/BITImageAnnotationViewController.m @@ -85,7 +85,7 @@ self.pinchRecognizer = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(pinched:)]; self.tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapped:)]; - [self.panRecognizer requireGestureRecognizerToFail:self.tapRecognizer]; + [self.tapRecognizer requireGestureRecognizerToFail:self.panRecognizer]; [self.imageView addGestureRecognizer:self.pinchRecognizer]; [self.imageView addGestureRecognizer:self.panRecognizer]; @@ -97,11 +97,13 @@ self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc ] initWithTitle:@"Save" style:UIBarButtonItemStyleBordered target:self action:@selector(save:)]; [self fitImageViewFrame]; - - - // Do any additional setup after loading the view. } +- (BOOL)prefersStatusBarHidden { + return self.navigationController.navigationBarHidden; +} + + - (void)fitImageViewFrame { @@ -278,6 +280,7 @@ } completion:^(BOOL finished) { [self fitImageViewFrame]; [self.navigationController setNavigationBarHidden:NO animated:NO]; + [[UIApplication sharedApplication] setStatusBarHidden:NO]; }]; @@ -287,6 +290,8 @@ } completion:^(BOOL finished) { [self.navigationController setNavigationBarHidden:YES animated:NO]; + [[UIApplication sharedApplication] setStatusBarHidden:YES]; + [self fitImageViewFrame]; @@ -296,6 +301,7 @@ } #pragma mark - Helpers + -(UIView *)firstAnnotationThatIsNotBlur { for (BITImageAnnotation *annotation in self.imageView.subviews){ if (![annotation isKindOfClass:[BITBlurImageAnnotation class]]){ From 35a7e50bb2f5c3a85c7076ee89a54234579d1bad Mon Sep 17 00:00:00 2001 From: Moritz Haarmann Date: Mon, 28 Apr 2014 00:53:58 +0200 Subject: [PATCH 51/92] + Added Edit icons. --- Classes/BITImageAnnotationViewController.m | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/Classes/BITImageAnnotationViewController.m b/Classes/BITImageAnnotationViewController.m index 596228032e..714080d2e9 100644 --- a/Classes/BITImageAnnotationViewController.m +++ b/Classes/BITImageAnnotationViewController.m @@ -85,16 +85,14 @@ self.pinchRecognizer = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(pinched:)]; self.tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapped:)]; - [self.tapRecognizer requireGestureRecognizerToFail:self.panRecognizer]; - [self.imageView addGestureRecognizer:self.pinchRecognizer]; [self.imageView addGestureRecognizer:self.panRecognizer]; [self.view addGestureRecognizer:self.tapRecognizer]; self.imageView.userInteractionEnabled = YES; - self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc ] initWithTitle:@"Discard" style:UIBarButtonItemStyleBordered target:self action:@selector(discard:)]; - self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc ] initWithTitle:@"Save" style:UIBarButtonItemStyleBordered target:self action:@selector(save:)]; + self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc ] initWithImage:bit_imageNamed(@"Cancel.png", BITHOCKEYSDK_BUNDLE) landscapeImagePhone:bit_imageNamed(@"Cancel.png", BITHOCKEYSDK_BUNDLE) style:UIBarButtonItemStyleBordered target:self action:@selector(discard:)]; + self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc ] initWithImage:bit_imageNamed(@"Ok.png", BITHOCKEYSDK_BUNDLE) landscapeImagePhone:bit_imageNamed(@"Ok.png", BITHOCKEYSDK_BUNDLE) style:UIBarButtonItemStyleBordered target:self action:@selector(save:)]; [self fitImageViewFrame]; } @@ -105,8 +103,6 @@ - (void)fitImageViewFrame { - - CGFloat heightScaleFactor = self.view.frame.size.height / self.image.size.height; CGFloat widthScaleFactor = self.view.frame.size.width / self.image.size.width; @@ -179,7 +175,7 @@ self.panStart = [gestureRecognizer locationInView:self.imageView]; - [self.editingControls setSelectedSegmentIndex:UISegmentedControlNoSegment]; + // [self.editingControls setSelectedSegmentIndex:UISegmentedControlNoSegment]; self.isDrawing = YES; } else if (gestureRecognizer.state == UIGestureRecognizerStateChanged){ From fb271e8acb64490e1be905a6d324d62f849550ab Mon Sep 17 00:00:00 2001 From: Moritz Haarmann Date: Mon, 28 Apr 2014 01:06:19 +0200 Subject: [PATCH 52/92] + Dynamic widths of the outlines, depending on the size of annotations. --- Classes/BITArrowImageAnnotation.m | 11 +++++++++-- Classes/BITRectangleImageAnnotation.m | 9 +++++++++ 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/Classes/BITArrowImageAnnotation.m b/Classes/BITArrowImageAnnotation.m index 0edc1af006..ae9d6b34fe 100644 --- a/Classes/BITArrowImageAnnotation.m +++ b/Classes/BITArrowImageAnnotation.m @@ -44,10 +44,10 @@ } - (void)buildShape { - CGFloat topHeight = MAX(self.frame.size.width / 3.0f,20); + CGFloat topHeight = MAX(self.frame.size.width / 3.0f,10); - CGFloat lineWidth = MAX(self.frame.size.width / 10.0f,10); + CGFloat lineWidth = MAX(self.frame.size.width / 10.0f,3); CGFloat startX, startY, endX, endY; if ( self.movedDelta.width < 0){ startX = CGRectGetMinX(self.bounds); @@ -71,6 +71,13 @@ self.shapeLayer.path = path.CGPath; self.strokeLayer.path = path.CGPath; + [CATransaction begin]; + [CATransaction setAnimationDuration:0]; + self.strokeLayer.lineWidth = lineWidth/1.5f; + self.shapeLayer.lineWidth = lineWidth / 3.0f; + + [CATransaction commit]; + } -(void)layoutSubviews{ diff --git a/Classes/BITRectangleImageAnnotation.m b/Classes/BITRectangleImageAnnotation.m index 4709d4f4cd..9b677cb360 100644 --- a/Classes/BITRectangleImageAnnotation.m +++ b/Classes/BITRectangleImageAnnotation.m @@ -48,6 +48,15 @@ self.strokeLayer.frame = self.bounds; self.strokeLayer.path = [UIBezierPath bezierPathWithRoundedRect:self.bounds cornerRadius:10].CGPath; + + CGFloat lineWidth = MAX(self.frame.size.width / 10.0f,10); + + [CATransaction begin]; + [CATransaction setAnimationDuration:0]; + self.strokeLayer.lineWidth = lineWidth/1.5f; + self.shapeLayer.lineWidth = lineWidth / 3.0f; + + [CATransaction commit]; } -(BOOL)resizable { From ab2ec512049a95d523d79e72c825f153eb25767a Mon Sep 17 00:00:00 2001 From: Moritz Haarmann Date: Mon, 28 Apr 2014 01:31:39 +0200 Subject: [PATCH 53/92] + Selection is highlighted when moved. --- Classes/BITBlurImageAnnotation.m | 22 ++++++++++++++++++++-- Classes/BITImageAnnotation.h | 8 +++++++- Classes/BITImageAnnotation.m | 8 ++++++++ Classes/BITImageAnnotationViewController.m | 19 +++++++++++++++---- 4 files changed, 50 insertions(+), 7 deletions(-) diff --git a/Classes/BITBlurImageAnnotation.m b/Classes/BITBlurImageAnnotation.m index 9d8f762cd2..983c49b891 100644 --- a/Classes/BITBlurImageAnnotation.m +++ b/Classes/BITBlurImageAnnotation.m @@ -12,6 +12,7 @@ @property (nonatomic, strong) CALayer* imageLayer; @property (nonatomic, strong) UIImage* scaledImage; +@property (nonatomic, strong) CALayer* selectedLayer; @end @@ -25,6 +26,13 @@ self.clipsToBounds = YES; self.imageLayer = [CALayer layer]; [self.layer addSublayer:self.imageLayer]; + + self.selectedLayer = [CALayer layer]; + [self.layer insertSublayer:self.selectedLayer above:self.imageLayer]; + + self.selectedLayer.backgroundColor = [[UIColor redColor] colorWithAlphaComponent:0.5f].CGColor; + self.selectedLayer.opacity = 0.6f; + self.clipsToBounds = YES; } return self; } @@ -33,6 +41,7 @@ CGSize size = CGSizeMake(sourceImage.size.width/30, sourceImage.size.height/30); UIGraphicsBeginImageContext(size); + [sourceImage drawInRect:CGRectMake(0, 0, size.width, size.height)]; self.scaledImage = UIGraphicsGetImageFromCurrentImageContext(); self.imageLayer.shouldRasterize = YES; @@ -40,11 +49,19 @@ self.imageLayer.magnificationFilter = kCAFilterNearest; self.imageLayer.contents = (id)self.scaledImage.CGImage; - - UIGraphicsEndImageContext(); } +- (void)setSelected:(BOOL)selected { + self->_selected = selected; + + if (selected){ + self.selectedLayer.opacity = 0.6f; + } else { + self.selectedLayer.opacity = 0.0f; + } +} + - (void)layoutSubviews { [super layoutSubviews]; @@ -54,6 +71,7 @@ self.imageLayer.frame = self.imageFrame; self.imageLayer.masksToBounds = YES; + self.selectedLayer.frame= self.bounds; [CATransaction commit]; } diff --git a/Classes/BITImageAnnotation.h b/Classes/BITImageAnnotation.h index f848ee2484..24d312e574 100644 --- a/Classes/BITImageAnnotation.h +++ b/Classes/BITImageAnnotation.h @@ -8,11 +8,17 @@ #import -@interface BITImageAnnotation : UIView +@interface BITImageAnnotation : UIView { + BOOL _selected; +} + @property (nonatomic) CGSize movedDelta; @property (nonatomic, weak) UIImage *sourceImage; @property (nonatomic) CGRect imageFrame; -(BOOL)resizable; +- (void)setSelected:(BOOL)selected; +- (BOOL)isSelected; + @end diff --git a/Classes/BITImageAnnotation.m b/Classes/BITImageAnnotation.m index d0e42614e3..473889a3ba 100644 --- a/Classes/BITImageAnnotation.m +++ b/Classes/BITImageAnnotation.m @@ -25,4 +25,12 @@ return NO; } +- (void)setSelected:(BOOL)selected { + self->_selected = selected; +} + +- (BOOL)isSelected { + return self->_selected; +} + @end diff --git a/Classes/BITImageAnnotationViewController.m b/Classes/BITImageAnnotationViewController.m index 714080d2e9..9ff33cb2d4 100644 --- a/Classes/BITImageAnnotationViewController.m +++ b/Classes/BITImageAnnotationViewController.m @@ -161,7 +161,13 @@ #pragma mark - Gesture Handling - (void)panned:(UIPanGestureRecognizer *)gestureRecognizer { - if ([self.editingControls selectedSegmentIndex] != UISegmentedControlNoSegment || self.isDrawing ){ + BITImageAnnotation *annotationAtLocation = (BITImageAnnotation *)[self.view hitTest:[gestureRecognizer locationInView:self.view] withEvent:nil]; + + if (![annotationAtLocation isKindOfClass:[BITImageAnnotation class]]){ + annotationAtLocation = nil; + } + + if (([self.editingControls selectedSegmentIndex] != UISegmentedControlNoSegment || self.isDrawing) && !annotationAtLocation ){ if (gestureRecognizer.state == UIGestureRecognizerStateBegan){ self.currentAnnotation = [self annotationForCurrentMode]; [self.objects addObject:self.currentAnnotation]; @@ -192,11 +198,14 @@ } else { if (gestureRecognizer.state == UIGestureRecognizerStateBegan){ // find and possibly move an existing annotation. - BITImageAnnotation *selectedAnnotation = (BITImageAnnotation *)[self.view hitTest:[gestureRecognizer locationInView:self.view] withEvent:nil]; + - if ([self.objects indexOfObject:selectedAnnotation] != NSNotFound){ - self.currentAnnotation = selectedAnnotation; + if ([self.objects indexOfObject:annotationAtLocation] != NSNotFound){ + self.currentAnnotation = annotationAtLocation; + [annotationAtLocation setSelected:YES]; } + + } else if (gestureRecognizer.state == UIGestureRecognizerStateChanged && self.currentAnnotation){ CGPoint delta = [gestureRecognizer translationInView:self.view]; @@ -213,6 +222,8 @@ } else { self.currentAnnotation = nil; + [annotationAtLocation setSelected:NO]; + } } } From ce835a0cee032a4ab64f6f585180ce85203e717d Mon Sep 17 00:00:00 2001 From: moritz haarmann Date: Mon, 28 Apr 2014 11:07:48 +0200 Subject: [PATCH 54/92] + Improved Selection Handling in the annotation view controller --- Classes/BITArrowImageAnnotation.m | 41 ++++++++++++++++++++-- Classes/BITImageAnnotationViewController.m | 40 ++++++++++++++++----- 2 files changed, 69 insertions(+), 12 deletions(-) diff --git a/Classes/BITArrowImageAnnotation.m b/Classes/BITArrowImageAnnotation.m index ae9d6b34fe..fee2b8a254 100644 --- a/Classes/BITArrowImageAnnotation.m +++ b/Classes/BITArrowImageAnnotation.m @@ -26,12 +26,12 @@ self = [super initWithFrame:frame]; if (self) { self.shapeLayer = [CAShapeLayer layer]; - self.shapeLayer.strokeColor = [UIColor redColor].CGColor; + self.shapeLayer.strokeColor = [UIColor whiteColor].CGColor; self.shapeLayer.lineWidth = 5; - self.shapeLayer.fillColor = [UIColor clearColor].CGColor; + self.shapeLayer.fillColor = [UIColor redColor].CGColor; self.strokeLayer = [CAShapeLayer layer]; - self.strokeLayer.strokeColor = [UIColor whiteColor].CGColor; + self.strokeLayer.strokeColor = [UIColor redColor].CGColor; self.strokeLayer.lineWidth = 10; self.strokeLayer.fillColor = [UIColor clearColor].CGColor; [self.layer addSublayer:self.strokeLayer]; @@ -154,4 +154,39 @@ return (CGAffineTransform){ cosine, sine, -sine, cosine, startPoint.x, startPoint.y }; } +#pragma mark - UIView + +- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event { + UIColor *color = [self colorAtPoint:point]; + CGFloat alpha, white; + [color getWhite:&white alpha:&alpha]; + if (white || alpha){ + return self; + } else { + return nil; + } + +} + +#pragma mark - Helpers + +// This is taken from http://stackoverflow.com/questions/12770181/how-to-get-the-pixel-color-on-touch +- (UIColor *)colorAtPoint:(CGPoint)point { + unsigned char pixel[4] = {0}; + CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); + CGContextRef context = CGBitmapContextCreate(pixel, + 1, 1, 8, 4, colorSpace, (CGBitmapInfo)kCGImageAlphaPremultipliedLast); + + CGContextTranslateCTM(context, -point.x, -point.y); + + [self.layer renderInContext:context]; + + CGContextRelease(context); + CGColorSpaceRelease(colorSpace); + UIColor *color = [UIColor colorWithRed:pixel[0]/255.0 + green:pixel[1]/255.0 blue:pixel[2]/255.0 + alpha:pixel[3]/255.0]; + return color; +} + @end diff --git a/Classes/BITImageAnnotationViewController.m b/Classes/BITImageAnnotationViewController.m index 9ff33cb2d4..0974412967 100644 --- a/Classes/BITImageAnnotationViewController.m +++ b/Classes/BITImageAnnotationViewController.m @@ -14,6 +14,12 @@ #import "BITHockeyHelper.h" #import "HockeySDKPrivate.h" +typedef NS_ENUM(NSInteger, BITImageAnnotationViewControllerInteractionMode) { + BITImageAnnotationViewControllerInteractionModeNone, + BITImageAnnotationViewControllerInteractionModeDraw, + BITImageAnnotationViewControllerInteractionModeMove +}; + @interface BITImageAnnotationViewController () @property (nonatomic, strong) UIImageView *imageView; @@ -29,7 +35,7 @@ @property (nonatomic) CGPoint panStart; @property (nonatomic,strong) BITImageAnnotation *currentAnnotation; -@property (nonatomic) BOOL isDrawing; +@property (nonatomic) BITImageAnnotationViewControllerInteractionMode currentInteraction; @property (nonatomic) CGRect pinchStartingFrame; @@ -158,7 +164,7 @@ return renderedImageOfMyself; } -#pragma mark - Gesture Handling +#pragma mark - UIGestureRecognizers - (void)panned:(UIPanGestureRecognizer *)gestureRecognizer { BITImageAnnotation *annotationAtLocation = (BITImageAnnotation *)[self.view hitTest:[gestureRecognizer locationInView:self.view] withEvent:nil]; @@ -167,7 +173,22 @@ annotationAtLocation = nil; } - if (([self.editingControls selectedSegmentIndex] != UISegmentedControlNoSegment || self.isDrawing) && !annotationAtLocation ){ + // determine the interaction mode if none is set so far. + + if (self.currentInteraction == BITImageAnnotationViewControllerInteractionModeNone){ + if (annotationAtLocation){ + self.currentInteraction = BITImageAnnotationViewControllerInteractionModeMove; + } else if ([self canDrawNewAnnotation]){ + self.currentInteraction = BITImageAnnotationViewControllerInteractionModeDraw; + } + } + + if (self.currentInteraction == BITImageAnnotationViewControllerInteractionModeNone){ + return; + } + + + if (self.currentInteraction == BITImageAnnotationViewControllerInteractionModeDraw){ if (gestureRecognizer.state == UIGestureRecognizerStateBegan){ self.currentAnnotation = [self annotationForCurrentMode]; [self.objects addObject:self.currentAnnotation]; @@ -182,7 +203,6 @@ self.panStart = [gestureRecognizer locationInView:self.imageView]; // [self.editingControls setSelectedSegmentIndex:UISegmentedControlNoSegment]; - self.isDrawing = YES; } else if (gestureRecognizer.state == UIGestureRecognizerStateChanged){ CGPoint bla = [gestureRecognizer locationInView:self.imageView]; @@ -193,9 +213,9 @@ [self.currentAnnotation layoutIfNeeded]; } else { self.currentAnnotation = nil; - self.isDrawing = NO; + self.currentInteraction = BITImageAnnotationViewControllerInteractionModeNone; } - } else { + } else if (self.currentInteraction == BITImageAnnotationViewControllerInteractionModeMove){ if (gestureRecognizer.state == UIGestureRecognizerStateBegan){ // find and possibly move an existing annotation. @@ -223,6 +243,8 @@ } else { self.currentAnnotation = nil; [annotationAtLocation setSelected:NO]; + self.currentInteraction = BITImageAnnotationViewControllerInteractionModeNone; + } } @@ -281,15 +303,12 @@ -(void)tapped:(UIGestureRecognizer *)tapRecognizer { if (self.navigationController.navigationBarHidden){ - // [[UIApplication sharedApplication] setStatusBarHidden:NO]; [UIView animateWithDuration:0.35f animations:^{ self.navigationController.navigationBar.alpha = 1; } completion:^(BOOL finished) { [self fitImageViewFrame]; [self.navigationController setNavigationBarHidden:NO animated:NO]; [[UIApplication sharedApplication] setStatusBarHidden:NO]; - - }]; } else { [UIView animateWithDuration:0.35f animations:^{ @@ -319,4 +338,7 @@ return self.imageView; } +- (BOOL)canDrawNewAnnotation { + return [self.editingControls selectedSegmentIndex] != UISegmentedControlNoSegment; +} @end From 2207f107e48d80ce3160354aee707fe6dfd23c15 Mon Sep 17 00:00:00 2001 From: moritz haarmann Date: Mon, 28 Apr 2014 11:08:57 +0200 Subject: [PATCH 55/92] + Fixed initial highlighting bug in blur annotation. --- Classes/BITImageAnnotationViewController.m | 1 + 1 file changed, 1 insertion(+) diff --git a/Classes/BITImageAnnotationViewController.m b/Classes/BITImageAnnotationViewController.m index 0974412967..a154af21a3 100644 --- a/Classes/BITImageAnnotationViewController.m +++ b/Classes/BITImageAnnotationViewController.m @@ -212,6 +212,7 @@ typedef NS_ENUM(NSInteger, BITImageAnnotationViewControllerInteractionMode) { [self.currentAnnotation setNeedsLayout]; [self.currentAnnotation layoutIfNeeded]; } else { + [self.currentAnnotation setSelected:NO]; self.currentAnnotation = nil; self.currentInteraction = BITImageAnnotationViewControllerInteractionModeNone; } From a2d850b5c346e779d7ed495ec7c88acdeef7c49c Mon Sep 17 00:00:00 2001 From: moritz haarmann Date: Mon, 28 Apr 2014 11:52:11 +0200 Subject: [PATCH 56/92] + Minimum Arrow Length. --- Classes/BITArrowImageAnnotation.m | 42 +++++++++++++++++++++++-------- 1 file changed, 31 insertions(+), 11 deletions(-) diff --git a/Classes/BITArrowImageAnnotation.m b/Classes/BITArrowImageAnnotation.m index fee2b8a254..99b4d0d060 100644 --- a/Classes/BITArrowImageAnnotation.m +++ b/Classes/BITArrowImageAnnotation.m @@ -44,30 +44,50 @@ } - (void)buildShape { - CGFloat topHeight = MAX(self.frame.size.width / 3.0f,10); + CGFloat baseWidth = MAX(self.frame.size.width, self.frame.size.height); + CGFloat topHeight = MAX(baseWidth / 3.0f,10); - CGFloat lineWidth = MAX(self.frame.size.width / 10.0f,3); + CGFloat lineWidth = MAX(baseWidth / 10.0f,3); CGFloat startX, startY, endX, endY; + + CGRect boundRect = CGRectInset(self.bounds, 0, 0); + CGFloat arrowLength= sqrt(pow(CGRectGetWidth(boundRect), 2) + pow(CGRectGetHeight(boundRect), 2)); + if (arrowLength < 30){ + + CGFloat factor = 30.f/arrowLength; + + boundRect = CGRectApplyAffineTransform(boundRect, CGAffineTransformMakeScale(factor,factor)); + } + if ( self.movedDelta.width < 0){ - startX = CGRectGetMinX(self.bounds); - endX = CGRectGetMaxX(self.bounds); + startX = CGRectGetMinX(boundRect); + endX = CGRectGetMaxX(boundRect); } else { - startX = CGRectGetMaxX(self.bounds); - endX = CGRectGetMinX(self.bounds); + startX = CGRectGetMaxX(boundRect); + endX = CGRectGetMinX(boundRect); } if ( self.movedDelta.height < 0){ - startY = CGRectGetMinY(self.bounds); - endY = CGRectGetMaxY(self.bounds); + startY = CGRectGetMinY(boundRect); + endY = CGRectGetMaxY(boundRect); } else { - startY = CGRectGetMaxY(self.bounds); - endY = CGRectGetMinY(self.bounds); + startY = CGRectGetMaxY(boundRect); + endY = CGRectGetMinY(boundRect); } + + + if (abs(CGRectGetWidth(boundRect)) < 30 || abs(CGRectGetHeight(boundRect)) < 30){ + CGFloat smallerOne = MIN(abs(CGRectGetHeight(boundRect)), abs(CGRectGetWidth(boundRect))); - UIBezierPath *path = [self bezierPathWithArrowFromPoint:CGPointMake(endX,endY) toPoint:CGPointMake(startX,startY) tailWidth:lineWidth headWidth:topHeight headLength:topHeight]; + CGFloat factor = smallerOne/30.f; + + CGRectApplyAffineTransform(boundRect, CGAffineTransformMakeScale(factor,factor)); + } + + UIBezierPath *path = [self bezierPathWithArrowFromPoint:CGPointMake(endX, endY) toPoint:CGPointMake(startX, startY) tailWidth:lineWidth headWidth:topHeight headLength:topHeight]; self.shapeLayer.path = path.CGPath; self.strokeLayer.path = path.CGPath; From aaccc5cc1d64d83ac60492012fbbdf247cf34191 Mon Sep 17 00:00:00 2001 From: moritz haarmann Date: Mon, 28 Apr 2014 11:57:03 +0200 Subject: [PATCH 57/92] + Fixes a crash relating to resizing annotations. --- Classes/BITImageAnnotationViewController.m | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Classes/BITImageAnnotationViewController.m b/Classes/BITImageAnnotationViewController.m index a154af21a3..3d40cde2d4 100644 --- a/Classes/BITImageAnnotationViewController.m +++ b/Classes/BITImageAnnotationViewController.m @@ -259,6 +259,11 @@ typedef NS_ENUM(NSInteger, BITImageAnnotationViewControllerInteractionMode) { for ( int i = 0; i Date: Mon, 28 Apr 2014 12:12:02 +0200 Subject: [PATCH 58/92] + Removed a deprecation warning. --- Classes/BITImageAnnotationViewController.m | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/Classes/BITImageAnnotationViewController.m b/Classes/BITImageAnnotationViewController.m index 3d40cde2d4..3a5d591222 100644 --- a/Classes/BITImageAnnotationViewController.m +++ b/Classes/BITImageAnnotationViewController.m @@ -66,7 +66,10 @@ typedef NS_ENUM(NSInteger, BITImageAnnotationViewControllerInteractionMode) { [self.editingControls setImage:bit_imageNamed(imageName, BITHOCKEYSDK_BUNDLE) forSegmentAtIndex:i++]; } +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" [self.editingControls setSegmentedControlStyle:UISegmentedControlStyleBar]; +#pragma clang diagnostic pop self.navigationItem.titleView = self.editingControls; @@ -279,7 +282,6 @@ typedef NS_ENUM(NSInteger, BITImageAnnotationViewControllerInteractionMode) { } else if (gestureRecognizer.state == UIGestureRecognizerStateChanged && self.currentAnnotation && gestureRecognizer.numberOfTouches>1){ CGRect newFrame= (self.pinchStartingFrame); - NSLog(@"%f", [gestureRecognizer scale]); // upper point? CGPoint point1 = [gestureRecognizer locationOfTouch:0 inView:self.view]; @@ -298,10 +300,6 @@ typedef NS_ENUM(NSInteger, BITImageAnnotationViewControllerInteractionMode) { self.currentAnnotation.frame = newFrame; self.currentAnnotation.imageFrame = [self.view convertRect:self.imageView.frame toView:self.currentAnnotation]; - - // we - - } else { self.currentAnnotation = nil; } From 59216cde17be3a769a612082a584c18b4284027d Mon Sep 17 00:00:00 2001 From: moritz haarmann Date: Mon, 28 Apr 2014 13:59:36 +0200 Subject: [PATCH 59/92] + Some iOS6 Fixes. --- Classes/BITArrowImageAnnotation.m | 4 +++- Classes/BITAttachmentGalleryViewController.m | 14 +++++++++----- Classes/BITImageAnnotationViewController.m | 14 ++++---------- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/Classes/BITArrowImageAnnotation.m b/Classes/BITArrowImageAnnotation.m index 99b4d0d060..f3abe3a821 100644 --- a/Classes/BITArrowImageAnnotation.m +++ b/Classes/BITArrowImageAnnotation.m @@ -180,7 +180,9 @@ UIColor *color = [self colorAtPoint:point]; CGFloat alpha, white; [color getWhite:&white alpha:&alpha]; - if (white || alpha){ + NSLog(@"%f %f", alpha,white); + + if ((int)white > 0 || (int)alpha > 0){ return self; } else { return nil; diff --git a/Classes/BITAttachmentGalleryViewController.m b/Classes/BITAttachmentGalleryViewController.m index c1224ac647..bcee160260 100644 --- a/Classes/BITAttachmentGalleryViewController.m +++ b/Classes/BITAttachmentGalleryViewController.m @@ -177,13 +177,17 @@ - (void)tapped:(UITapGestureRecognizer *)tapRecognizer { if (self.navigationController.navigationBarHidden){ - [[UIApplication sharedApplication] setStatusBarHidden:NO]; - [self.navigationController setNavigationBarHidden:NO animated:YES]; + [UIView animateWithDuration:0.35f animations:^{ + [[UIApplication sharedApplication] setStatusBarHidden:NO]; + [self.navigationController setNavigationBarHidden:NO animated:NO]; + } completion:nil]; } else { - [self.navigationController setNavigationBarHidden:YES animated:YES]; - [[UIApplication sharedApplication] setStatusBarHidden:YES]; - + [UIView animateWithDuration:0.35f animations:^{ + [[UIApplication sharedApplication] setStatusBarHidden:YES]; + [self.navigationController setNavigationBarHidden:YES animated:NO]; + } completion:nil]; } + [self layoutViews]; } diff --git a/Classes/BITImageAnnotationViewController.m b/Classes/BITImageAnnotationViewController.m index 3a5d591222..0513544e55 100644 --- a/Classes/BITImageAnnotationViewController.m +++ b/Classes/BITImageAnnotationViewController.m @@ -308,23 +308,17 @@ typedef NS_ENUM(NSInteger, BITImageAnnotationViewControllerInteractionMode) { -(void)tapped:(UIGestureRecognizer *)tapRecognizer { if (self.navigationController.navigationBarHidden){ [UIView animateWithDuration:0.35f animations:^{ - self.navigationController.navigationBar.alpha = 1; + [[UIApplication sharedApplication] setStatusBarHidden:NO]; + [self.navigationController setNavigationBarHidden:NO animated:NO]; } completion:^(BOOL finished) { [self fitImageViewFrame]; - [self.navigationController setNavigationBarHidden:NO animated:NO]; - [[UIApplication sharedApplication] setStatusBarHidden:NO]; }]; } else { [UIView animateWithDuration:0.35f animations:^{ - self.navigationController.navigationBar.alpha = 0; - - } completion:^(BOOL finished) { - [self.navigationController setNavigationBarHidden:YES animated:NO]; [[UIApplication sharedApplication] setStatusBarHidden:YES]; - - + [self.navigationController setNavigationBarHidden:YES animated:NO]; + } completion:^(BOOL finished) { [self fitImageViewFrame]; - }]; } From 9fd4ead1550c60c2392b1541abcca5b9d588d876 Mon Sep 17 00:00:00 2001 From: moritz haarmann Date: Mon, 28 Apr 2014 14:50:24 +0200 Subject: [PATCH 60/92] + Improved Performance of Attachment Viewer. --- Classes/BITAttachmentGalleryViewController.m | 81 ++++++++++++-------- Classes/BITImageAnnotationViewController.m | 3 +- 2 files changed, 52 insertions(+), 32 deletions(-) diff --git a/Classes/BITAttachmentGalleryViewController.m b/Classes/BITAttachmentGalleryViewController.m index bcee160260..0a10dbebaa 100644 --- a/Classes/BITAttachmentGalleryViewController.m +++ b/Classes/BITAttachmentGalleryViewController.m @@ -19,19 +19,13 @@ @property (nonatomic) NSInteger currentIndex; @property (nonatomic) NSInteger loadedImageIndex; @property (nonatomic, strong) UITapGestureRecognizer *tapognizer; +@property (nonatomic, strong) NSMutableDictionary *images; @end @implementation BITAttachmentGalleryViewController -- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil -{ - self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; - if (self) { - // Custom initialization - } - return self; -} +#pragma mark - UIViewController - (void)viewDidLoad { @@ -58,6 +52,7 @@ self.tapognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapped:)]; [self.view addGestureRecognizer:self.tapognizer]; } + -(void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; @@ -70,21 +65,34 @@ if (indexOfSelectedAttachment != NSNotFound){ self.currentIndex = indexOfSelectedAttachment; self.scrollView.contentOffset = CGPointMake(self.scrollView.frame.size.width * self.currentIndex, 0); - } } + self.images = [NSMutableDictionary new]; [self layoutViews]; - - } - (void)viewDidAppear:(BOOL)animated { [super viewDidAppear:animated]; - - - } + +-(void)viewWillDisappear:(BOOL)animated { + [super viewWillDisappear:animated]; + [self.images removeAllObjects]; +} + +- (void)didReceiveMemoryWarning +{ + [super didReceiveMemoryWarning]; + [self.images removeAllObjects]; +} + +- (BOOL)prefersStatusBarHidden { + return self.navigationController.navigationBarHidden; +} + +#pragma mark - Scroll View Content/Layout + - (void)setupScrollView { self.scrollView = [[UIScrollView alloc] initWithFrame:self.view.bounds]; self.view.autoresizesSubviews = NO; @@ -122,18 +130,6 @@ self.extractedAttachments = extractedOnes; } -- (void)didReceiveMemoryWarning -{ - [super didReceiveMemoryWarning]; -} - -- (void)scrollViewDidScroll:(UIScrollView *)scrollView { - NSInteger newIndex = self.scrollView.contentOffset.x / self.scrollView.frame.size.width; - if (newIndex!=self.currentIndex){ - self.currentIndex = newIndex; - [self layoutViews]; - } -} - (void)layoutViews { CGPoint savedOffset = self.scrollView.contentOffset; @@ -146,24 +142,32 @@ self.scrollView.autoresizesSubviews = NO; self.scrollView.contentOffset = savedOffset; - + NSInteger baseIndex = MAX(0,self.currentIndex-1); NSInteger z = baseIndex; for ( NSInteger i = baseIndex; i < MIN(baseIndex+2, self.extractedAttachments.count);i++ ){ UIImageView *imageView = self.imageViews[z%self.imageViews.count]; BITFeedbackMessageAttachment *attachment = self.extractedAttachments[i]; - imageView.image =[attachment imageRepresentation]; + imageView.image = [self imageForAttachment:attachment]; imageView.frame = [self frameForItemAtIndex:i]; z++; } - + } -- (BOOL)prefersStatusBarHidden { - return self.navigationController.navigationBarHidden; +#pragma mark - UIScrollViewDelegate + +- (void)scrollViewDidScroll:(UIScrollView *)scrollView { + NSInteger newIndex = self.scrollView.contentOffset.x / self.scrollView.frame.size.width; + if (newIndex!=self.currentIndex){ + self.currentIndex = newIndex; + [self layoutViews]; + } } +#pragma mark - IBActions + - (void)close:(id)sender { [self dismissViewControllerAnimated:YES completion:nil]; } @@ -175,6 +179,8 @@ [self presentViewController:activityVC animated:YES completion:nil]; } +#pragma mark - UIGestureRecognizer + - (void)tapped:(UITapGestureRecognizer *)tapRecognizer { if (self.navigationController.navigationBarHidden){ [UIView animateWithDuration:0.35f animations:^{ @@ -191,8 +197,21 @@ [self layoutViews]; } +#pragma mark - Layout Helpers + - (CGRect)frameForItemAtIndex:(NSInteger)index { return CGRectMake(index * CGRectGetWidth(self.scrollView.frame), 0, CGRectGetWidth(self.scrollView.frame), CGRectGetHeight(self.view.bounds)); } +- (UIImage *)imageForAttachment:(BITFeedbackMessageAttachment *)attachment { + UIImage *cachedObject = self.images[@([self.extractedAttachments indexOfObject:attachment])]; + + if (!cachedObject){ + cachedObject = [attachment imageRepresentation]; + self.images[@([self.extractedAttachments indexOfObject:attachment])] = cachedObject; + } + + return cachedObject; +} + @end diff --git a/Classes/BITImageAnnotationViewController.m b/Classes/BITImageAnnotationViewController.m index 0513544e55..69e2359053 100644 --- a/Classes/BITImageAnnotationViewController.m +++ b/Classes/BITImageAnnotationViewController.m @@ -85,7 +85,8 @@ typedef NS_ENUM(NSInteger, BITImageAnnotationViewControllerInteractionMode) { self.imageView.image = self.image; self.imageView.contentMode = UIViewContentModeScaleToFill; - + self.view.frame = UIScreen.mainScreen.applicationFrame; + [self.view addSubview:self.imageView]; // Erm. self.imageView.frame = [UIScreen mainScreen].bounds; From c7a3d6761a2117e9ac3cb4a4c9be1b2ca3427fad Mon Sep 17 00:00:00 2001 From: moritz haarmann Date: Mon, 28 Apr 2014 15:34:09 +0200 Subject: [PATCH 61/92] + Fixed a blur/highlighting selection bug. --- Classes/BITImageAnnotationViewController.m | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Classes/BITImageAnnotationViewController.m b/Classes/BITImageAnnotationViewController.m index 69e2359053..270180d383 100644 --- a/Classes/BITImageAnnotationViewController.m +++ b/Classes/BITImageAnnotationViewController.m @@ -246,11 +246,9 @@ typedef NS_ENUM(NSInteger, BITImageAnnotationViewControllerInteractionMode) { [gestureRecognizer setTranslation:CGPointZero inView:self.view]; } else { + [self.currentAnnotation setSelected:NO]; self.currentAnnotation = nil; - [annotationAtLocation setSelected:NO]; self.currentInteraction = BITImageAnnotationViewControllerInteractionModeNone; - - } } } @@ -279,6 +277,7 @@ typedef NS_ENUM(NSInteger, BITImageAnnotationViewControllerInteractionMode) { if (validView && [candidate resizable]){ self.currentAnnotation = candidate; self.pinchStartingFrame = self.currentAnnotation.frame; + [self.currentAnnotation setSelected:YES]; } } else if (gestureRecognizer.state == UIGestureRecognizerStateChanged && self.currentAnnotation && gestureRecognizer.numberOfTouches>1){ @@ -302,6 +301,7 @@ typedef NS_ENUM(NSInteger, BITImageAnnotationViewControllerInteractionMode) { self.currentAnnotation.frame = newFrame; self.currentAnnotation.imageFrame = [self.view convertRect:self.imageView.frame toView:self.currentAnnotation]; } else { + [self.currentAnnotation setSelected:NO]; self.currentAnnotation = nil; } } From 622d18dd685929dbe960da26eb895f1f862d12c6 Mon Sep 17 00:00:00 2001 From: moritz haarmann Date: Tue, 29 Apr 2014 15:14:31 +0200 Subject: [PATCH 62/92] + Some layout adjustments. --- Classes/BITImageAnnotationViewController.m | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/Classes/BITImageAnnotationViewController.m b/Classes/BITImageAnnotationViewController.m index 270180d383..1de321ddd1 100644 --- a/Classes/BITImageAnnotationViewController.m +++ b/Classes/BITImageAnnotationViewController.m @@ -43,14 +43,7 @@ typedef NS_ENUM(NSInteger, BITImageAnnotationViewControllerInteractionMode) { @implementation BITImageAnnotationViewController -- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil -{ - self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; - if (self) { - // Custom initialization - } - return self; -} +#pragma mark - UIViewController - (void)viewDidLoad { @@ -103,8 +96,13 @@ typedef NS_ENUM(NSInteger, BITImageAnnotationViewControllerInteractionMode) { self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc ] initWithImage:bit_imageNamed(@"Cancel.png", BITHOCKEYSDK_BUNDLE) landscapeImagePhone:bit_imageNamed(@"Cancel.png", BITHOCKEYSDK_BUNDLE) style:UIBarButtonItemStyleBordered target:self action:@selector(discard:)]; self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc ] initWithImage:bit_imageNamed(@"Ok.png", BITHOCKEYSDK_BUNDLE) landscapeImagePhone:bit_imageNamed(@"Ok.png", BITHOCKEYSDK_BUNDLE) style:UIBarButtonItemStyleBordered target:self action:@selector(save:)]; +} + +- (void)viewWillAppear:(BOOL)animated { + [super viewWillAppear:animated]; [self fitImageViewFrame]; + } - (BOOL)prefersStatusBarHidden { From 25b315f3b20d9d5ba882b9adcdc63dcad1de3087 Mon Sep 17 00:00:00 2001 From: moritz haarmann Date: Tue, 29 Apr 2014 16:53:22 +0200 Subject: [PATCH 63/92] + Fixed Navigationbar/Statusbar quirks in Annotation View Controller --- Classes/BITImageAnnotationViewController.m | 55 ++++++++++++++++------ 1 file changed, 40 insertions(+), 15 deletions(-) diff --git a/Classes/BITImageAnnotationViewController.m b/Classes/BITImageAnnotationViewController.m index 1de321ddd1..42713a00b0 100644 --- a/Classes/BITImageAnnotationViewController.m +++ b/Classes/BITImageAnnotationViewController.m @@ -79,7 +79,7 @@ typedef NS_ENUM(NSInteger, BITImageAnnotationViewControllerInteractionMode) { self.imageView.contentMode = UIViewContentModeScaleToFill; self.view.frame = UIScreen.mainScreen.applicationFrame; - + [self.view addSubview:self.imageView]; // Erm. self.imageView.frame = [UIScreen mainScreen].bounds; @@ -96,29 +96,33 @@ typedef NS_ENUM(NSInteger, BITImageAnnotationViewControllerInteractionMode) { self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc ] initWithImage:bit_imageNamed(@"Cancel.png", BITHOCKEYSDK_BUNDLE) landscapeImagePhone:bit_imageNamed(@"Cancel.png", BITHOCKEYSDK_BUNDLE) style:UIBarButtonItemStyleBordered target:self action:@selector(discard:)]; self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc ] initWithImage:bit_imageNamed(@"Ok.png", BITHOCKEYSDK_BUNDLE) landscapeImagePhone:bit_imageNamed(@"Ok.png", BITHOCKEYSDK_BUNDLE) style:UIBarButtonItemStyleBordered target:self action:@selector(save:)]; + + self.view.autoresizesSubviews = NO; } - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; [self fitImageViewFrame]; - + } - (BOOL)prefersStatusBarHidden { - return self.navigationController.navigationBarHidden; + return self.navigationController.navigationBarHidden || self.navigationController.navigationBar.alpha == 0.0f; } - (void)fitImageViewFrame { - CGFloat heightScaleFactor = self.view.frame.size.height / self.image.size.height; - CGFloat widthScaleFactor = self.view.frame.size.width / self.image.size.width; + CGFloat heightScaleFactor = [[UIScreen mainScreen] bounds].size.height / self.image.size.height; + CGFloat widthScaleFactor = [[UIScreen mainScreen] bounds].size.width / self.image.size.width; CGFloat factor = MIN(heightScaleFactor, widthScaleFactor); self.scaleFactor = factor; CGSize scaledImageSize = CGSizeMake(self.image.size.width * factor, self.image.size.height * factor); - self.imageView.frame = CGRectMake(self.view.frame.size.width/2 - scaledImageSize.width/2, self.view.frame.size.height/2 - scaledImageSize.height/2, scaledImageSize.width, scaledImageSize.height); + CGRect baseFrame = CGRectMake(self.view.frame.size.width/2 - scaledImageSize.width/2, self.view.frame.size.height - [[UIScreen mainScreen] bounds].size.height, scaledImageSize.width, scaledImageSize.height); + + self.imageView.frame = baseFrame; } -(void)editingAction:(id)sender { @@ -189,7 +193,7 @@ typedef NS_ENUM(NSInteger, BITImageAnnotationViewControllerInteractionMode) { return; } - + if (self.currentInteraction == BITImageAnnotationViewControllerInteractionModeDraw){ if (gestureRecognizer.state == UIGestureRecognizerStateBegan){ self.currentAnnotation = [self annotationForCurrentMode]; @@ -204,7 +208,7 @@ typedef NS_ENUM(NSInteger, BITImageAnnotationViewControllerInteractionMode) { self.panStart = [gestureRecognizer locationInView:self.imageView]; - // [self.editingControls setSelectedSegmentIndex:UISegmentedControlNoSegment]; + // [self.editingControls setSelectedSegmentIndex:UISegmentedControlNoSegment]; } else if (gestureRecognizer.state == UIGestureRecognizerStateChanged){ CGPoint bla = [gestureRecognizer locationInView:self.imageView]; @@ -221,7 +225,7 @@ typedef NS_ENUM(NSInteger, BITImageAnnotationViewControllerInteractionMode) { } else if (self.currentInteraction == BITImageAnnotationViewControllerInteractionModeMove){ if (gestureRecognizer.state == UIGestureRecognizerStateBegan){ // find and possibly move an existing annotation. - + if ([self.objects indexOfObject:annotationAtLocation] != NSNotFound){ self.currentAnnotation = annotationAtLocation; @@ -237,7 +241,7 @@ typedef NS_ENUM(NSInteger, BITImageAnnotationViewControllerInteractionMode) { annotationFrame.origin.y += delta.y; self.currentAnnotation.frame = annotationFrame; self.currentAnnotation.imageFrame = [self.view convertRect:self.imageView.frame toView:self.currentAnnotation]; - + [self.currentAnnotation setNeedsLayout]; [self.currentAnnotation layoutIfNeeded]; @@ -294,7 +298,7 @@ typedef NS_ENUM(NSInteger, BITImageAnnotationViewControllerInteractionMode) { newFrame.size.width = (point1.x > point2.x) ? point1.x - point2.x : point2.x - point1.x; newFrame.size.height = (point1.y > point2.y) ? point1.y - point2.y : point2.y - point1.y; - + self.currentAnnotation.frame = newFrame; self.currentAnnotation.imageFrame = [self.view convertRect:self.imageView.frame toView:self.currentAnnotation]; @@ -305,20 +309,41 @@ typedef NS_ENUM(NSInteger, BITImageAnnotationViewControllerInteractionMode) { } -(void)tapped:(UIGestureRecognizer *)tapRecognizer { - if (self.navigationController.navigationBarHidden){ + // This toggles the nav and status bar. Since iOS7 and pre-iOS7 behave weirdly different, + // this might look rather hacky, but hiding the navbar under iOS6 leads to some ugly + // animation effect which is avoided by simply hiding the navbar setting it's alpha to 0. // moritzh + + if (self.navigationController.navigationBar.alpha == 0 || self.navigationController.navigationBarHidden ){ + [UIView animateWithDuration:0.35f animations:^{ + + if (NSFoundationVersionNumber > NSFoundationVersionNumber_iOS_6_1) { + [self.navigationController setNavigationBarHidden:NO animated:NO]; + } else { + self.navigationController.navigationBar.alpha = 1.0; + } + [[UIApplication sharedApplication] setStatusBarHidden:NO]; - [self.navigationController setNavigationBarHidden:NO animated:NO]; + } completion:^(BOOL finished) { [self fitImageViewFrame]; + }]; } else { [UIView animateWithDuration:0.35f animations:^{ + + if (NSFoundationVersionNumber > NSFoundationVersionNumber_iOS_6_1) { + [self.navigationController setNavigationBarHidden:YES animated:NO]; + } else { + self.navigationController.navigationBar.alpha = 0.0; + } + [[UIApplication sharedApplication] setStatusBarHidden:YES]; - [self.navigationController setNavigationBarHidden:YES animated:NO]; + } completion:^(BOOL finished) { [self fitImageViewFrame]; - }]; + + }]; } } From 475d3e624c444fa1c5eb0036fc422132ed71365e Mon Sep 17 00:00:00 2001 From: moritz haarmann Date: Tue, 29 Apr 2014 17:58:51 +0200 Subject: [PATCH 64/92] + Massively improved arrow hit testing. --- Classes/BITArrowImageAnnotation.m | 49 +++++++------------------------ 1 file changed, 10 insertions(+), 39 deletions(-) diff --git a/Classes/BITArrowImageAnnotation.m b/Classes/BITArrowImageAnnotation.m index f3abe3a821..40c58e953f 100644 --- a/Classes/BITArrowImageAnnotation.m +++ b/Classes/BITArrowImageAnnotation.m @@ -16,7 +16,6 @@ @property (nonatomic, strong) CAShapeLayer *shapeLayer; @property (nonatomic, strong) CAShapeLayer *strokeLayer; - @end @implementation BITArrowImageAnnotation @@ -100,21 +99,6 @@ } --(void)layoutSubviews{ - [super layoutSubviews]; - - [self buildShape]; - -} - -/* -// Only override drawRect: if you perform custom drawing. -// An empty implementation adversely affects performance during animation. -- (void)drawRect:(CGRect)rect -{ - // Drawing code -} -*/ - (UIBezierPath *)bezierPathWithArrowFromPoint:(CGPoint)startPoint toPoint:(CGPoint)endPoint @@ -177,12 +161,14 @@ #pragma mark - UIView - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event { - UIColor *color = [self colorAtPoint:point]; - CGFloat alpha, white; - [color getWhite:&white alpha:&alpha]; - NSLog(@"%f %f", alpha,white); - if ((int)white > 0 || (int)alpha > 0){ + CGPathRef strokePath = CGPathCreateCopyByStrokingPath(self.shapeLayer.path, NULL, fmaxf(90.0f, self.shapeLayer.lineWidth), kCGLineCapRound,kCGLineJoinMiter,0); + + BOOL containsPoint = CGPathContainsPoint(strokePath, NULL, point, NO); + + CGPathRelease(strokePath); + + if (containsPoint){ return self; } else { return nil; @@ -190,25 +176,10 @@ } -#pragma mark - Helpers - -// This is taken from http://stackoverflow.com/questions/12770181/how-to-get-the-pixel-color-on-touch -- (UIColor *)colorAtPoint:(CGPoint)point { - unsigned char pixel[4] = {0}; - CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); - CGContextRef context = CGBitmapContextCreate(pixel, - 1, 1, 8, 4, colorSpace, (CGBitmapInfo)kCGImageAlphaPremultipliedLast); +-(void)layoutSubviews{ + [super layoutSubviews]; - CGContextTranslateCTM(context, -point.x, -point.y); - - [self.layer renderInContext:context]; - - CGContextRelease(context); - CGColorSpaceRelease(colorSpace); - UIColor *color = [UIColor colorWithRed:pixel[0]/255.0 - green:pixel[1]/255.0 blue:pixel[2]/255.0 - alpha:pixel[3]/255.0]; - return color; + [self buildShape]; } @end From ae5c87c99bb74637d094db653f37545f526a8cb5 Mon Sep 17 00:00:00 2001 From: moritz haarmann Date: Tue, 29 Apr 2014 18:02:30 +0200 Subject: [PATCH 65/92] Fixes #62: Arrow should be default. --- Classes/BITImageAnnotationViewController.m | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Classes/BITImageAnnotationViewController.m b/Classes/BITImageAnnotationViewController.m index 42713a00b0..5bf851a00a 100644 --- a/Classes/BITImageAnnotationViewController.m +++ b/Classes/BITImageAnnotationViewController.m @@ -51,7 +51,7 @@ typedef NS_ENUM(NSInteger, BITImageAnnotationViewControllerInteractionMode) { self.view.backgroundColor = [UIColor groupTableViewBackgroundColor]; - NSArray *icons = @[@"Rectangle.png", @"Arrow.png", @"Blur.png"]; + NSArray *icons = @[@"Arrow.png",@"Rectangle.png", @"Blur.png"]; self.editingControls = [[UISegmentedControl alloc] initWithItems:@[@"Rectangle", @"Arrow", @"Blur"]]; int i=0; @@ -131,9 +131,9 @@ typedef NS_ENUM(NSInteger, BITImageAnnotationViewControllerInteractionMode) { - (BITImageAnnotation *)annotationForCurrentMode { if (self.editingControls.selectedSegmentIndex == 0){ - return [[BITRectangleImageAnnotation alloc] initWithFrame:CGRectZero]; - } else if(self.editingControls.selectedSegmentIndex==1){ return [[BITArrowImageAnnotation alloc] initWithFrame:CGRectZero]; + } else if(self.editingControls.selectedSegmentIndex==1){ + return [[BITRectangleImageAnnotation alloc] initWithFrame:CGRectZero]; } else { return [[BITBlurImageAnnotation alloc] initWithFrame:CGRectZero]; } From afb46f66a06cbcf4e9a0ba3757135aaef92a2f94 Mon Sep 17 00:00:00 2001 From: moritz haarmann Date: Tue, 29 Apr 2014 18:21:16 +0200 Subject: [PATCH 66/92] + Ported a long due fix to the Attachment Gallery View Controller. --- Classes/BITAttachmentGalleryViewController.m | 50 ++++++++++++++++---- 1 file changed, 40 insertions(+), 10 deletions(-) diff --git a/Classes/BITAttachmentGalleryViewController.m b/Classes/BITAttachmentGalleryViewController.m index 0a10dbebaa..f3d1fc94d4 100644 --- a/Classes/BITAttachmentGalleryViewController.m +++ b/Classes/BITAttachmentGalleryViewController.m @@ -49,6 +49,8 @@ self.view.frame = UIScreen.mainScreen.applicationFrame; + + self.tapognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapped:)]; [self.view addGestureRecognizer:self.tapognizer]; } @@ -57,8 +59,7 @@ [super viewWillAppear:animated]; // Hide the navigation bar and stuff initially. - [self.navigationController setNavigationBarHidden:YES animated:NO]; - [[UIApplication sharedApplication] setStatusBarHidden:YES]; + [self tapped:nil]; if (self.preselectedAttachment){ NSInteger indexOfSelectedAttachment = [self.extractedAttachments indexOfObject:self.preselectedAttachment]; @@ -95,6 +96,11 @@ - (void)setupScrollView { self.scrollView = [[UIScrollView alloc] initWithFrame:self.view.bounds]; + CGRect frame = self.scrollView.frame; + + frame.origin.y = self.scrollView.frame.size.height - [[UIScreen mainScreen] bounds].size.height; + + self.scrollView.frame = frame; self.view.autoresizesSubviews = NO; [self.view addSubview:self.scrollView]; self.scrollView.delegate = self; @@ -136,7 +142,7 @@ self.scrollView.delegate = nil; self.scrollView.frame = self.view.bounds; - self.scrollView.contentSize = CGSizeMake(CGRectGetWidth(self.view.bounds) * self.extractedAttachments.count, CGRectGetHeight(self.view.bounds)); + self.scrollView.contentSize = CGSizeMake( [[UIScreen mainScreen] bounds].size.width * self.extractedAttachments.count, [[UIScreen mainScreen] bounds].size.height); self.scrollView.delegate = self; self.scrollView.contentInset = UIEdgeInsetsZero; self.scrollView.autoresizesSubviews = NO; @@ -153,6 +159,11 @@ z++; } + CGRect frame = self.scrollView.frame; + + frame.origin.y = self.scrollView.frame.size.height - [[UIScreen mainScreen] bounds].size.height; + + self.scrollView.frame = frame; } @@ -182,25 +193,44 @@ #pragma mark - UIGestureRecognizer - (void)tapped:(UITapGestureRecognizer *)tapRecognizer { - if (self.navigationController.navigationBarHidden){ + if (self.navigationController.navigationBar.alpha == 0 || self.navigationController.navigationBarHidden ){ + [UIView animateWithDuration:0.35f animations:^{ + + if (NSFoundationVersionNumber > NSFoundationVersionNumber_iOS_6_1) { + [self.navigationController setNavigationBarHidden:NO animated:NO]; + } else { + self.navigationController.navigationBar.alpha = 1.0; + } + [[UIApplication sharedApplication] setStatusBarHidden:NO]; - [self.navigationController setNavigationBarHidden:NO animated:NO]; - } completion:nil]; + + } completion:^(BOOL finished){ + [self layoutViews]; + }]; } else { [UIView animateWithDuration:0.35f animations:^{ + + if (NSFoundationVersionNumber > NSFoundationVersionNumber_iOS_6_1) { + [self.navigationController setNavigationBarHidden:YES animated:NO]; + } else { + self.navigationController.navigationBar.alpha = 0.0; + } + [[UIApplication sharedApplication] setStatusBarHidden:YES]; - [self.navigationController setNavigationBarHidden:YES animated:NO]; - } completion:nil]; + + } completion:^(BOOL finished){ + [self layoutViews]; + }]; } - [self layoutViews]; + } #pragma mark - Layout Helpers - (CGRect)frameForItemAtIndex:(NSInteger)index { - return CGRectMake(index * CGRectGetWidth(self.scrollView.frame), 0, CGRectGetWidth(self.scrollView.frame), CGRectGetHeight(self.view.bounds)); + return CGRectMake(index * [[UIScreen mainScreen] bounds].size.width, 0, [[UIScreen mainScreen] bounds].size.width, [[UIScreen mainScreen] bounds].size.height); } - (UIImage *)imageForAttachment:(BITFeedbackMessageAttachment *)attachment { From e4700dab9b73265604cc6c775841aa020e8205d5 Mon Sep 17 00:00:00 2001 From: moritz haarmann Date: Wed, 30 Apr 2014 16:53:20 +0200 Subject: [PATCH 67/92] + Refactored Preview of Attachments. --- Classes/BITAttachmentGalleryViewController.h | 19 -- Classes/BITAttachmentGalleryViewController.m | 247 ------------------ Classes/BITFeedbackListViewCell.m | 41 +-- Classes/BITFeedbackListViewController.h | 3 +- Classes/BITFeedbackListViewController.m | 47 +++- Classes/BITFeedbackManager.m | 3 +- Classes/BITFeedbackMessage.m | 2 + ...TFeedbackMessageAttachment+QLPreviewItem.h | 16 ++ ...TFeedbackMessageAttachment+QLPreviewItem.m | 21 ++ Classes/BITFeedbackMessageAttachment.h | 6 +- Classes/BITFeedbackMessageAttachment.m | 121 ++++++--- Classes/View.xib | 31 +++ Support/HockeySDK.xcodeproj/project.pbxproj | 30 +-- 13 files changed, 234 insertions(+), 353 deletions(-) delete mode 100644 Classes/BITAttachmentGalleryViewController.h delete mode 100644 Classes/BITAttachmentGalleryViewController.m create mode 100644 Classes/BITFeedbackMessageAttachment+QLPreviewItem.h create mode 100644 Classes/BITFeedbackMessageAttachment+QLPreviewItem.m create mode 100644 Classes/View.xib diff --git a/Classes/BITAttachmentGalleryViewController.h b/Classes/BITAttachmentGalleryViewController.h deleted file mode 100644 index a745b6dde0..0000000000 --- a/Classes/BITAttachmentGalleryViewController.h +++ /dev/null @@ -1,19 +0,0 @@ -// -// BITAttachmentGalleryViewController.h -// HockeySDK -// -// Created by Moritz Haarmann on 06.03.14. -// -// - -#import - -@class BITFeedbackMessageAttachment; - -@interface BITAttachmentGalleryViewController : UIViewController - -@property (nonatomic, strong) NSArray *messages; - -@property (nonatomic, strong) BITFeedbackMessageAttachment *preselectedAttachment; - -@end diff --git a/Classes/BITAttachmentGalleryViewController.m b/Classes/BITAttachmentGalleryViewController.m deleted file mode 100644 index f3d1fc94d4..0000000000 --- a/Classes/BITAttachmentGalleryViewController.m +++ /dev/null @@ -1,247 +0,0 @@ -// -// BITAttachmentGalleryViewController.m -// HockeySDK -// -// Created by Moritz Haarmann on 06.03.14. -// -// - -#import "BITAttachmentGalleryViewController.h" - -#import "BITFeedbackMessage.h" -#import "BITFeedbackMessageAttachment.h" - -@interface BITAttachmentGalleryViewController () - -@property (nonatomic, strong) UIScrollView *scrollView; -@property (nonatomic, strong) NSArray *imageViews; -@property (nonatomic, strong) NSArray *extractedAttachments; -@property (nonatomic) NSInteger currentIndex; -@property (nonatomic) NSInteger loadedImageIndex; -@property (nonatomic, strong) UITapGestureRecognizer *tapognizer; -@property (nonatomic, strong) NSMutableDictionary *images; - -@end - -@implementation BITAttachmentGalleryViewController - -#pragma mark - UIViewController - -- (void)viewDidLoad -{ - [super viewDidLoad]; - self.navigationController.navigationBar.translucent = YES; - self.navigationController.navigationBar.opaque = NO; -#if __IPHONE_OS_VERSION_MIN_REQUIRED > __IPHONE_6_1 - self.automaticallyAdjustsScrollViewInsets = NO; - self.edgesForExtendedLayout = YES; - self.extendedLayoutIncludesOpaqueBars = YES; -#endif - self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"Close" style:UIBarButtonItemStylePlain target:self action:@selector(close:)]; - - self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAction target:self action:@selector(share:)]; - - - self.currentIndex = 0; - - [self extractUsableAttachments]; - [self setupScrollView]; - - self.view.frame = UIScreen.mainScreen.applicationFrame; - - - - self.tapognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapped:)]; - [self.view addGestureRecognizer:self.tapognizer]; -} - --(void)viewWillAppear:(BOOL)animated { - [super viewWillAppear:animated]; - - // Hide the navigation bar and stuff initially. - [self tapped:nil]; - - if (self.preselectedAttachment){ - NSInteger indexOfSelectedAttachment = [self.extractedAttachments indexOfObject:self.preselectedAttachment]; - if (indexOfSelectedAttachment != NSNotFound){ - self.currentIndex = indexOfSelectedAttachment; - self.scrollView.contentOffset = CGPointMake(self.scrollView.frame.size.width * self.currentIndex, 0); - } - } - - self.images = [NSMutableDictionary new]; - [self layoutViews]; -} - -- (void)viewDidAppear:(BOOL)animated { - [super viewDidAppear:animated]; -} - --(void)viewWillDisappear:(BOOL)animated { - [super viewWillDisappear:animated]; - [self.images removeAllObjects]; -} - -- (void)didReceiveMemoryWarning -{ - [super didReceiveMemoryWarning]; - [self.images removeAllObjects]; -} - -- (BOOL)prefersStatusBarHidden { - return self.navigationController.navigationBarHidden; -} - -#pragma mark - Scroll View Content/Layout - -- (void)setupScrollView { - self.scrollView = [[UIScrollView alloc] initWithFrame:self.view.bounds]; - CGRect frame = self.scrollView.frame; - - frame.origin.y = self.scrollView.frame.size.height - [[UIScreen mainScreen] bounds].size.height; - - self.scrollView.frame = frame; - self.view.autoresizesSubviews = NO; - [self.view addSubview:self.scrollView]; - self.scrollView.delegate = self; - self.scrollView.pagingEnabled = YES; - self.scrollView.backgroundColor = [UIColor groupTableViewBackgroundColor]; - self.scrollView.bounces = NO; - - - NSMutableArray *imageviews = [NSMutableArray new]; - - for (int i = 0; i<3; i++){ - UIImageView *newImageView = [[UIImageView alloc] initWithFrame:CGRectZero]; - [imageviews addObject:newImageView]; - newImageView.contentMode = UIViewContentModeScaleAspectFit; - [self.scrollView addSubview:newImageView]; - } - - self.imageViews = imageviews; - -} - -- (void)extractUsableAttachments { - NSMutableArray *extractedOnes = [NSMutableArray new]; - - for (BITFeedbackMessage *message in self.messages){ - for (BITFeedbackMessageAttachment *attachment in message.attachments){ - if ([attachment imageRepresentation]){ - [extractedOnes addObject:attachment]; - } - } - } - - self.extractedAttachments = extractedOnes; -} - - -- (void)layoutViews { - CGPoint savedOffset = self.scrollView.contentOffset; - - self.scrollView.delegate = nil; - self.scrollView.frame = self.view.bounds; - self.scrollView.contentSize = CGSizeMake( [[UIScreen mainScreen] bounds].size.width * self.extractedAttachments.count, [[UIScreen mainScreen] bounds].size.height); - self.scrollView.delegate = self; - self.scrollView.contentInset = UIEdgeInsetsZero; - self.scrollView.autoresizesSubviews = NO; - self.scrollView.contentOffset = savedOffset; - - - NSInteger baseIndex = MAX(0,self.currentIndex-1); - NSInteger z = baseIndex; - for ( NSInteger i = baseIndex; i < MIN(baseIndex+2, self.extractedAttachments.count);i++ ){ - UIImageView *imageView = self.imageViews[z%self.imageViews.count]; - BITFeedbackMessageAttachment *attachment = self.extractedAttachments[i]; - imageView.image = [self imageForAttachment:attachment]; - imageView.frame = [self frameForItemAtIndex:i]; - z++; - } - - CGRect frame = self.scrollView.frame; - - frame.origin.y = self.scrollView.frame.size.height - [[UIScreen mainScreen] bounds].size.height; - - self.scrollView.frame = frame; - -} - -#pragma mark - UIScrollViewDelegate - -- (void)scrollViewDidScroll:(UIScrollView *)scrollView { - NSInteger newIndex = self.scrollView.contentOffset.x / self.scrollView.frame.size.width; - if (newIndex!=self.currentIndex){ - self.currentIndex = newIndex; - [self layoutViews]; - } -} - -#pragma mark - IBActions - -- (void)close:(id)sender { - [self dismissViewControllerAnimated:YES completion:nil]; -} - -- (void)share:(id)sender { - BITFeedbackMessageAttachment *attachment = self.extractedAttachments[self.currentIndex]; - - UIActivityViewController *activityVC = [[UIActivityViewController alloc] initWithActivityItems:[NSArray arrayWithObjects:attachment.originalFilename, attachment.imageRepresentation , nil] applicationActivities:nil]; - [self presentViewController:activityVC animated:YES completion:nil]; -} - -#pragma mark - UIGestureRecognizer - -- (void)tapped:(UITapGestureRecognizer *)tapRecognizer { - if (self.navigationController.navigationBar.alpha == 0 || self.navigationController.navigationBarHidden ){ - - [UIView animateWithDuration:0.35f animations:^{ - - if (NSFoundationVersionNumber > NSFoundationVersionNumber_iOS_6_1) { - [self.navigationController setNavigationBarHidden:NO animated:NO]; - } else { - self.navigationController.navigationBar.alpha = 1.0; - } - - [[UIApplication sharedApplication] setStatusBarHidden:NO]; - - } completion:^(BOOL finished){ - [self layoutViews]; - }]; - } else { - [UIView animateWithDuration:0.35f animations:^{ - - if (NSFoundationVersionNumber > NSFoundationVersionNumber_iOS_6_1) { - [self.navigationController setNavigationBarHidden:YES animated:NO]; - } else { - self.navigationController.navigationBar.alpha = 0.0; - } - - [[UIApplication sharedApplication] setStatusBarHidden:YES]; - - } completion:^(BOOL finished){ - [self layoutViews]; - }]; - } - - -} - -#pragma mark - Layout Helpers - -- (CGRect)frameForItemAtIndex:(NSInteger)index { - return CGRectMake(index * [[UIScreen mainScreen] bounds].size.width, 0, [[UIScreen mainScreen] bounds].size.width, [[UIScreen mainScreen] bounds].size.height); -} - -- (UIImage *)imageForAttachment:(BITFeedbackMessageAttachment *)attachment { - UIImage *cachedObject = self.images[@([self.extractedAttachments indexOfObject:attachment])]; - - if (!cachedObject){ - cachedObject = [attachment imageRepresentation]; - self.images[@([self.extractedAttachments indexOfObject:attachment])] = cachedObject; - } - - return cachedObject; -} - -@end diff --git a/Classes/BITFeedbackListViewCell.m b/Classes/BITFeedbackListViewCell.m index 9998a07648..e10afbb82a 100644 --- a/Classes/BITFeedbackListViewCell.m +++ b/Classes/BITFeedbackListViewCell.m @@ -87,15 +87,15 @@ self.dateFormatter = [[NSDateFormatter alloc] init]; [self.dateFormatter setTimeStyle:NSDateFormatterNoStyle]; [self.dateFormatter setDateStyle:NSDateFormatterMediumStyle]; - [self.dateFormatter setLocale:[NSLocale currentLocale]]; + [self.dateFormatter setLocale:[NSLocale currentLocale]]; [self.dateFormatter setDoesRelativeDateFormatting:YES]; - + self.timeFormatter = [[NSDateFormatter alloc] init]; [self.timeFormatter setTimeStyle:NSDateFormatterShortStyle]; [self.timeFormatter setDateStyle:NSDateFormatterNoStyle]; [self.timeFormatter setLocale:[NSLocale currentLocale]]; [self.timeFormatter setDoesRelativeDateFormatting:YES]; - + self.labelTitle = [[UILabel alloc] init]; self.labelTitle.font = [UIFont systemFontOfSize:TITLE_FONTSIZE]; @@ -171,7 +171,7 @@ // added to make space for the images. - + } else { #endif #pragma clang diagnostic push @@ -179,7 +179,7 @@ calculatedHeight = [message.text sizeWithFont:[UIFont systemFontOfSize:TEXT_FONTSIZE] constrainedToSize:CGSizeMake(width - (2 * FRAME_SIDE_BORDER), CGFLOAT_MAX) ].height + FRAME_TOP_BORDER + LABEL_TEXT_Y + FRAME_BOTTOM_BORDER; - + #pragma clang diagnostic pop #if __IPHONE_OS_VERSION_MAX_ALLOWED > __IPHONE_6_1 } @@ -190,7 +190,7 @@ - (void)setAttachments:(NSArray *)attachments { for (UIView *view in self.attachmentViews){ - [view removeFromSuperview]; + [view removeFromSuperview]; } [self.attachmentViews removeAllObjects]; @@ -198,9 +198,14 @@ for (BITFeedbackMessageAttachment *attachment in attachments){ UIButton *imageView = [UIButton buttonWithType:UIButtonTypeCustom]; [imageView setImage:[attachment thumbnailWithSize:CGSizeMake(ATTACHMENT_SIZE, ATTACHMENT_SIZE)] forState:UIControlStateNormal]; + [imageView setTitle:attachment.originalFilename forState:UIControlStateNormal]; + [imageView setContentVerticalAlignment:UIControlContentVerticalAlignmentCenter]; + [imageView setContentHorizontalAlignment:UIControlContentHorizontalAlignmentCenter]; [imageView addTarget:self action:@selector(imageButtonPressed:) forControlEvents:UIControlEventTouchUpInside]; + imageView.contentMode = UIViewContentModeScaleAspectFill; + [self.attachmentViews addObject:imageView]; } } @@ -208,12 +213,12 @@ - (void)layoutSubviews { if (!self.accessoryBackgroundView){ - self.accessoryBackgroundView = [[UIView alloc] initWithFrame:CGRectMake(0, 2, self.frame.size.width * 2, self.frame.size.height - 2)]; - self.accessoryBackgroundView.autoresizingMask = UIViewAutoresizingFlexibleHeight; - self.accessoryBackgroundView.clipsToBounds = YES; - - // colors - self.accessoryBackgroundView.backgroundColor = [self backgroundColor]; + self.accessoryBackgroundView = [[UIView alloc] initWithFrame:CGRectMake(0, 2, self.frame.size.width * 2, self.frame.size.height - 2)]; + self.accessoryBackgroundView.autoresizingMask = UIViewAutoresizingFlexibleHeight; + self.accessoryBackgroundView.clipsToBounds = YES; + + // colors + self.accessoryBackgroundView.backgroundColor = [self backgroundColor]; } if (self.style == BITFeedbackListViewCellPresentatationStyleDefault) { @@ -231,9 +236,9 @@ } else { [self.labelText setTextColor:TEXTCOLOR_DEFAULT]; } - + // background for deletion accessory view - + // header NSString *dateString = @""; @@ -248,7 +253,7 @@ } [self.labelTitle setText:dateString]; [self.labelTitle setFrame:CGRectMake(FRAME_SIDE_BORDER, FRAME_TOP_BORDER + LABEL_TITLE_Y, self.frame.size.width - (2 * FRAME_SIDE_BORDER), LABEL_TITLE_HEIGHT)]; - + if (_message.userMessage) { self.labelTitle.textAlignment = kBITTextLabelAlignmentRight; self.labelText.textAlignment = kBITTextLabelAlignmentRight; @@ -256,13 +261,13 @@ self.labelTitle.textAlignment = kBITTextLabelAlignmentLeft; self.labelText.textAlignment = kBITTextLabelAlignmentLeft; } - + [self addSubview:self.labelTitle]; - + // text [self.labelText setText:_message.text]; CGSize sizeForTextLabel = CGSizeMake(self.frame.size.width - (2 * FRAME_SIDE_BORDER), - [[self class] heightForTextInRowWithMessage:_message tableViewWidth:self.frame.size.width] - LABEL_TEXT_Y - FRAME_BOTTOM_BORDER); + [[self class] heightForTextInRowWithMessage:_message tableViewWidth:self.frame.size.width] - LABEL_TEXT_Y - FRAME_BOTTOM_BORDER); [self.labelText setFrame:CGRectMake(FRAME_SIDE_BORDER, LABEL_TEXT_Y, sizeForTextLabel.width, sizeForTextLabel.height)]; diff --git a/Classes/BITFeedbackListViewController.h b/Classes/BITFeedbackListViewController.h index 0a513904cf..8ee2b064d9 100644 --- a/Classes/BITFeedbackListViewController.h +++ b/Classes/BITFeedbackListViewController.h @@ -28,6 +28,7 @@ #import +#import #import "BITHockeyBaseViewController.h" @@ -54,7 +55,7 @@ This ensures that the presentation on iOS 6 and iOS 7 will use the corret design on each OS Version. */ -@interface BITFeedbackListViewController : BITHockeyBaseViewController { +@interface BITFeedbackListViewController : BITHockeyBaseViewController { } @end diff --git a/Classes/BITFeedbackListViewController.m b/Classes/BITFeedbackListViewController.m index 70f58a1f8a..88b92d2ef6 100644 --- a/Classes/BITFeedbackListViewController.m +++ b/Classes/BITFeedbackListViewController.m @@ -39,13 +39,15 @@ #import "BITFeedbackComposeViewController.h" #import "BITFeedbackUserDataViewController.h" #import "BITFeedbackMessage.h" +#import "BITFeedbackMessageAttachment.h" +#import "BITFeedbackMessageAttachment+QLPreviewItem.h" #import "BITAttributedLabel.h" -#import "BITAttachmentGalleryViewController.h" #import "BITHockeyBaseManagerPrivate.h" #import "BITHockeyHelper.h" #import +#import #define DEFAULT_BACKGROUNDCOLOR BIT_RGBCOLOR(245, 245, 245) @@ -70,6 +72,7 @@ @property (nonatomic, weak) BITFeedbackManager *manager; @property (nonatomic, strong) NSDateFormatter *lastUpdateDateFormatter; @property (nonatomic) BOOL userDataComposeFlow; +@property (nonatomic, strong) NSArray *cachedPreviewItems; @end @@ -330,6 +333,7 @@ -(void)userDataUpdateFinished { [self.manager saveMessages]; + [self refreshPreviewItems]; if (self.userDataComposeFlow) { if ([self.manager showFirstRequiredPresentationModal]) { @@ -771,19 +775,46 @@ } } +#pragma mark - ListViewCellDelegate + - (void)listCell:(id)cell didSelectAttachment:(BITFeedbackMessageAttachment *)attachment { - BITAttachmentGalleryViewController *galleryController = [BITAttachmentGalleryViewController new]; - UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:galleryController]; + QLPreviewController *previewController = [[QLPreviewController alloc] init]; + previewController.dataSource = self; + + [self presentViewController:previewController animated:YES completion:nil]; + + [previewController setCurrentPreviewItemIndex:[self.cachedPreviewItems indexOfObject:attachment]]; - NSMutableArray *collectedMessages = [NSMutableArray new]; +} +- (void)refreshPreviewItems { + self.cachedPreviewItems = nil; + NSMutableArray *collectedAttachments = [NSMutableArray new]; for (int i = 0; i) previewController: (QLPreviewController *) controller previewItemAtIndex: (NSInteger) index { + if (index>=0){ + return self.cachedPreviewItems[index];} + return nil; } @end diff --git a/Classes/BITFeedbackManager.m b/Classes/BITFeedbackManager.m index 5cd2091d1f..c2cf0facc5 100644 --- a/Classes/BITFeedbackManager.m +++ b/Classes/BITFeedbackManager.m @@ -739,7 +739,7 @@ newAttachment.originalFilename = attachmentData[@"file_name"]; newAttachment.id = attachmentData[@"id"]; newAttachment.sourceURL = attachmentData[@"url"]; - newAttachment.contentType = @"image/jpg"; + newAttachment.contentType = attachmentData[@"content_type"]; [message addAttachmentsObject:newAttachment]; } @@ -1048,7 +1048,6 @@ completionHandler:^(NSError *err){ if (err) { [self markSendInProgressMessagesAsPending]; - [self saveMessages]; } diff --git a/Classes/BITFeedbackMessage.m b/Classes/BITFeedbackMessage.m index 446fb0da3c..6d2ab49c11 100644 --- a/Classes/BITFeedbackMessage.m +++ b/Classes/BITFeedbackMessage.m @@ -96,4 +96,6 @@ } self.attachments = [self.attachments arrayByAddingObject:object]; } + + @end diff --git a/Classes/BITFeedbackMessageAttachment+QLPreviewItem.h b/Classes/BITFeedbackMessageAttachment+QLPreviewItem.h new file mode 100644 index 0000000000..5c05718ae1 --- /dev/null +++ b/Classes/BITFeedbackMessageAttachment+QLPreviewItem.h @@ -0,0 +1,16 @@ +// +// BITFeedbackMessageAttachment+QLPreviewItem.h +// HockeySDK +// +// Created by Moritz Haarmann on 30.04.14. +// +// + +#import "BITFeedbackMessageAttachment.h" +#import + +@interface BITFeedbackMessageAttachment (QLPreviewItem) + + + +@end diff --git a/Classes/BITFeedbackMessageAttachment+QLPreviewItem.m b/Classes/BITFeedbackMessageAttachment+QLPreviewItem.m new file mode 100644 index 0000000000..da5424ebeb --- /dev/null +++ b/Classes/BITFeedbackMessageAttachment+QLPreviewItem.m @@ -0,0 +1,21 @@ +// +// BITFeedbackMessageAttachment+QLPreviewItem.m +// HockeySDK +// +// Created by Moritz Haarmann on 30.04.14. +// +// + +#import "BITFeedbackMessageAttachment+QLPreviewItem.h" + +@implementation BITFeedbackMessageAttachment (QLPreviewItem) + +- (NSString *)previewItemTitle { + return self.originalFilename; +} + +- (NSURL *)previewItemURL { + return self.localURL; +} + +@end diff --git a/Classes/BITFeedbackMessageAttachment.h b/Classes/BITFeedbackMessageAttachment.h index 1d05641971..a76b995e68 100644 --- a/Classes/BITFeedbackMessageAttachment.h +++ b/Classes/BITFeedbackMessageAttachment.h @@ -49,6 +49,10 @@ - (void)deleteContents; --(BOOL)needsLoadingFromURL; +- (BOOL)needsLoadingFromURL; + +- (BOOL)isImage; + +- (NSURL *)localURL; @end diff --git a/Classes/BITFeedbackMessageAttachment.m b/Classes/BITFeedbackMessageAttachment.m index 3350b96d24..541d25fd53 100644 --- a/Classes/BITFeedbackMessageAttachment.m +++ b/Classes/BITFeedbackMessageAttachment.m @@ -30,6 +30,7 @@ #import "BITFeedbackMessageAttachment.h" #import "BITHockeyHelper.h" #import "HockeySDKPrivate.h" +#import #define kCacheFolderName @"hockey_attachments" @@ -52,7 +53,7 @@ formatter = [NSDateFormatter new]; formatter.dateStyle = NSDateFormatterShortStyle; formatter.timeStyle = NSDateFormatterShortStyle; - + } BITFeedbackMessageAttachment *newAttachment = [BITFeedbackMessageAttachment new]; @@ -97,6 +98,17 @@ return (self.sourceURL); } +- (BOOL)isImage { + return ([self.contentType rangeOfString:@"image"].location != NSNotFound); +} + +- (NSURL *)localURL { + if (self.filename){ + return [NSURL fileURLWithPath:self.filename]; + } else + { return nil;} +} + #pragma mark NSCoding - (void)encodeWithCoder:(NSCoder *)aCoder { @@ -104,8 +116,8 @@ [aCoder encodeObject:self.filename forKey:@"filename"]; [aCoder encodeObject:self.originalFilename forKey:@"originalFilename"]; [aCoder encodeObject:self.sourceURL forKey:@"url"]; - - + + } - (id)initWithCoder:(NSCoder *)aDecoder { @@ -117,7 +129,7 @@ self.thumbnailRepresentations = [NSMutableDictionary new]; self.originalFilename = [aDecoder decodeObjectForKey:@"originalFilename"]; self.sourceURL = [aDecoder decodeObjectForKey:@"sourceURL"]; - + } return self; @@ -126,10 +138,14 @@ #pragma mark - Thubmnails / Image Representation - (UIImage *)imageRepresentation { - if ([self.contentType rangeOfString:@"image"].location != NSNotFound || [self.sourceURL rangeOfString:@"jpeg"].location != NSNotFound){ + if ([self.contentType rangeOfString:@"image"].location != NSNotFound ){ return [UIImage imageWithData:self.data]; } else { - return bit_imageNamed(@"feedbackActiviy.png", BITHOCKEYSDK_BUNDLE); // TODO add another placeholder. + // Create a Icon .. + UIDocumentInteractionController* docController = [[UIDocumentInteractionController alloc] init]; + docController.name = self.originalFilename; + NSArray* icons = docController.icons; + return icons[0]; } } @@ -139,46 +155,69 @@ if (!self.thumbnailRepresentations[cacheKey]){ UIImage *image = self.imageRepresentation; // consider the scale. + if (!image) + return nil; CGFloat scale = [UIScreen mainScreen].scale; - CGSize scaledSize = CGSizeApplyAffineTransform(size, CGAffineTransformMakeScale(scale, scale)); - UIImage *thumbnail = bit_imageToFitSize(image, scaledSize, NO) ; + if (scale != image.scale){ + + CGSize scaledSize = CGSizeApplyAffineTransform(size, CGAffineTransformMakeScale(scale, scale)); + UIImage *thumbnail = bit_imageToFitSize(image, scaledSize, YES) ; + + UIImage *scaledTumbnail = [UIImage imageWithCGImage:thumbnail.CGImage scale:scale orientation:thumbnail.imageOrientation]; + if (thumbnail){ + [self.thumbnailRepresentations setObject:scaledTumbnail forKey:cacheKey]; + } + + } else { + UIImage *thumbnail = bit_imageToFitSize(image, size, YES) ; + + [self.thumbnailRepresentations setObject:thumbnail forKey:cacheKey]; + + } - UIImage *scaledTumbnail = [UIImage imageWithCGImage:thumbnail.CGImage scale:scale orientation:thumbnail.imageOrientation]; - if (thumbnail){ - [self.thumbnailRepresentations setObject:scaledTumbnail forKey:cacheKey]; + } + + return self.thumbnailRepresentations[cacheKey]; + } + +#pragma mark - Persistence Helpers + + - (NSString *)createFilename { + NSArray* cachePathArray = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES); + NSString* cachePath = [cachePathArray lastObject]; + cachePath = [cachePath stringByAppendingPathComponent:kCacheFolderName]; + + BOOL isDirectory; + + if (![[NSFileManager defaultManager] fileExistsAtPath:cachePath isDirectory:&isDirectory]){ + [[NSFileManager defaultManager] createDirectoryAtPath:cachePath withIntermediateDirectories:YES attributes:nil error:nil]; + } + + NSString *uniqueString = bit_UUID(); + cachePath = [cachePath stringByAppendingPathComponent:uniqueString]; + + // File extension that suits the Content type. + + CFStringRef mimeType = (__bridge CFStringRef)self.contentType; + CFStringRef uti = UTTypeCreatePreferredIdentifierForTag(kUTTagClassMIMEType, mimeType, NULL); + CFStringRef extension = UTTypeCopyPreferredTagWithClass(uti, kUTTagClassFilenameExtension); + + cachePath = [cachePath stringByAppendingPathExtension:(__bridge NSString *)(extension)]; + + CFRelease(uti); + CFRelease(extension); + + return cachePath; + } + + - (void)deleteContents { + if (self.filename){ + [[NSFileManager defaultManager] removeItemAtPath:self.filename error:nil]; + self.filename = nil; } } - return self.thumbnailRepresentations[cacheKey]; -} - -#pragma mark - Persistence Helpers - -- (NSString *)createFilename { - NSArray* cachePathArray = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES); - NSString* cachePath = [cachePathArray lastObject]; - cachePath = [cachePath stringByAppendingPathComponent:kCacheFolderName]; - BOOL isDirectory; - - if (![[NSFileManager defaultManager] fileExistsAtPath:cachePath isDirectory:&isDirectory]){ - [[NSFileManager defaultManager] createDirectoryAtPath:cachePath withIntermediateDirectories:YES attributes:nil error:nil]; - } - - NSString *uniqueString = bit_UUID(); - cachePath = [cachePath stringByAppendingPathComponent:uniqueString]; - - return cachePath; -} - -- (void)deleteContents { - if (self.filename){ - [[NSFileManager defaultManager] removeItemAtPath:self.filename error:nil]; - self.filename = nil; - } -} - - -@end + @end diff --git a/Classes/View.xib b/Classes/View.xib new file mode 100644 index 0000000000..8cd84946d0 --- /dev/null +++ b/Classes/View.xib @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Support/HockeySDK.xcodeproj/project.pbxproj b/Support/HockeySDK.xcodeproj/project.pbxproj index 993fffc7ea..3d5d956fde 100644 --- a/Support/HockeySDK.xcodeproj/project.pbxproj +++ b/Support/HockeySDK.xcodeproj/project.pbxproj @@ -140,8 +140,6 @@ 973EC8BC18BDE29800DBFFBB /* BITArrowImageAnnotation.m in Sources */ = {isa = PBXBuildFile; fileRef = 973EC8BA18BDE29800DBFFBB /* BITArrowImageAnnotation.m */; }; 973EC8BF18BE2B5B00DBFFBB /* BITBlurImageAnnotation.h in Headers */ = {isa = PBXBuildFile; fileRef = 973EC8BD18BE2B5B00DBFFBB /* BITBlurImageAnnotation.h */; }; 973EC8C018BE2B5B00DBFFBB /* BITBlurImageAnnotation.m in Sources */ = {isa = PBXBuildFile; fileRef = 973EC8BE18BE2B5B00DBFFBB /* BITBlurImageAnnotation.m */; }; - 973EC91818C87E7300DBFFBB /* BITAttachmentGalleryViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 973EC91618C87E7300DBFFBB /* BITAttachmentGalleryViewController.h */; }; - 973EC91918C87E7300DBFFBB /* BITAttachmentGalleryViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 973EC91718C87E7300DBFFBB /* BITAttachmentGalleryViewController.m */; }; 9760F6C418BB4D2D00959B93 /* AssetsLibrary.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9760F6C318BB4D2D00959B93 /* AssetsLibrary.framework */; }; 9760F6CF18BB685600959B93 /* BITImageAnnotation.h in Headers */ = {isa = PBXBuildFile; fileRef = 9760F6CD18BB685600959B93 /* BITImageAnnotation.h */; }; 9760F6D018BB685600959B93 /* BITImageAnnotation.m in Sources */ = {isa = PBXBuildFile; fileRef = 9760F6CE18BB685600959B93 /* BITImageAnnotation.m */; }; @@ -155,6 +153,9 @@ 9782023918F81BFC00A98D8B /* Ok@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 9782022F18F81BFC00A98D8B /* Ok@2x.png */; }; 9782023A18F81BFC00A98D8B /* Rectangle.png in Resources */ = {isa = PBXBuildFile; fileRef = 9782023018F81BFC00A98D8B /* Rectangle.png */; }; 9782023B18F81BFC00A98D8B /* Rectangle@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 9782023118F81BFC00A98D8B /* Rectangle@2x.png */; }; + 97BD9BD21911085A0043FD59 /* BITFeedbackMessageAttachment+QLPreviewItem.h in Headers */ = {isa = PBXBuildFile; fileRef = 97BD9BD01911085A0043FD59 /* BITFeedbackMessageAttachment+QLPreviewItem.h */; }; + 97BD9BD31911085A0043FD59 /* BITFeedbackMessageAttachment+QLPreviewItem.m in Sources */ = {isa = PBXBuildFile; fileRef = 97BD9BD11911085A0043FD59 /* BITFeedbackMessageAttachment+QLPreviewItem.m */; }; + 97BD9BD5191109730043FD59 /* QuickLook.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 97BD9BD4191109730043FD59 /* QuickLook.framework */; }; 97F0F9FD18ABAECD00EF50AA /* iconCamera.png in Resources */ = {isa = PBXBuildFile; fileRef = 97F0F9FB18ABAECD00EF50AA /* iconCamera.png */; }; 97F0F9FE18ABAECD00EF50AA /* iconCamera@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 97F0F9FC18ABAECD00EF50AA /* iconCamera@2x.png */; }; 97F0FA0518B2294D00EF50AA /* BITFeedbackMessageAttachment.m in Sources */ = {isa = PBXBuildFile; fileRef = 97F0FA0318AE5AED00EF50AA /* BITFeedbackMessageAttachment.m */; }; @@ -324,8 +325,6 @@ 973EC8BA18BDE29800DBFFBB /* BITArrowImageAnnotation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BITArrowImageAnnotation.m; sourceTree = ""; }; 973EC8BD18BE2B5B00DBFFBB /* BITBlurImageAnnotation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BITBlurImageAnnotation.h; sourceTree = ""; }; 973EC8BE18BE2B5B00DBFFBB /* BITBlurImageAnnotation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BITBlurImageAnnotation.m; sourceTree = ""; }; - 973EC91618C87E7300DBFFBB /* BITAttachmentGalleryViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BITAttachmentGalleryViewController.h; sourceTree = ""; }; - 973EC91718C87E7300DBFFBB /* BITAttachmentGalleryViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BITAttachmentGalleryViewController.m; sourceTree = ""; }; 9760F6C318BB4D2D00959B93 /* AssetsLibrary.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AssetsLibrary.framework; path = System/Library/Frameworks/AssetsLibrary.framework; sourceTree = SDKROOT; }; 9760F6CD18BB685600959B93 /* BITImageAnnotation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BITImageAnnotation.h; sourceTree = ""; }; 9760F6CE18BB685600959B93 /* BITImageAnnotation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BITImageAnnotation.m; sourceTree = ""; }; @@ -339,6 +338,10 @@ 9782022F18F81BFC00A98D8B /* Ok@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Ok@2x.png"; sourceTree = ""; }; 9782023018F81BFC00A98D8B /* Rectangle.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = Rectangle.png; sourceTree = ""; }; 9782023118F81BFC00A98D8B /* Rectangle@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Rectangle@2x.png"; sourceTree = ""; }; + 97BD9BD01911085A0043FD59 /* BITFeedbackMessageAttachment+QLPreviewItem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "BITFeedbackMessageAttachment+QLPreviewItem.h"; sourceTree = ""; }; + 97BD9BD11911085A0043FD59 /* BITFeedbackMessageAttachment+QLPreviewItem.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "BITFeedbackMessageAttachment+QLPreviewItem.m"; sourceTree = ""; }; + 97BD9BD4191109730043FD59 /* QuickLook.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuickLook.framework; path = System/Library/Frameworks/QuickLook.framework; sourceTree = SDKROOT; }; + 97BD9BDF19113F9B0043FD59 /* View.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = View.xib; sourceTree = ""; }; 97F0F9FB18ABAECD00EF50AA /* iconCamera.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = iconCamera.png; sourceTree = ""; }; 97F0F9FC18ABAECD00EF50AA /* iconCamera@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "iconCamera@2x.png"; sourceTree = ""; }; 97F0F9FF18AE375E00EF50AA /* BITImageAnnotationViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BITImageAnnotationViewController.h; sourceTree = ""; }; @@ -370,6 +373,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 97BD9BD5191109730043FD59 /* QuickLook.framework in Frameworks */, 9760F6C418BB4D2D00959B93 /* AssetsLibrary.framework in Frameworks */, 1E5954DC15B6F24A00A03429 /* Foundation.framework in Frameworks */, 1E5954DD15B6F24A00A03429 /* CrashReporter.framework in Frameworks */, @@ -505,7 +509,6 @@ 1E754E461621FA9A0070AB92 /* Feedback */ = { isa = PBXGroup; children = ( - 973EC91518C87E5A00DBFFBB /* Image Viewer */, 9760F6CC18BB684200959B93 /* Image Editor */, 1E49A4361612223B00463151 /* BITFeedbackMessage.h */, 1E49A4371612223B00463151 /* BITFeedbackMessage.m */, @@ -518,6 +521,7 @@ 1E49A4391612223B00463151 /* BITFeedbackUserDataViewController.m */, 1E49A42F1612223B00463151 /* BITFeedbackListViewCell.h */, 1E49A4301612223B00463151 /* BITFeedbackListViewCell.m */, + 97BD9BDF19113F9B0043FD59 /* View.xib */, 1E49A4311612223B00463151 /* BITFeedbackListViewController.h */, 1E49A4321612223B00463151 /* BITFeedbackListViewController.m */, 1EF95CA4162CB036000AE3AD /* BITFeedbackActivity.h */, @@ -526,6 +530,8 @@ 1E49A4341612223B00463151 /* BITFeedbackManager.m */, E405266117A2AD300096359C /* BITFeedbackManagerDelegate.h */, 1E49A4351612223B00463151 /* BITFeedbackManagerPrivate.h */, + 97BD9BD01911085A0043FD59 /* BITFeedbackMessageAttachment+QLPreviewItem.h */, + 97BD9BD11911085A0043FD59 /* BITFeedbackMessageAttachment+QLPreviewItem.m */, ); name = Feedback; sourceTree = ""; @@ -604,15 +610,6 @@ name = Private; sourceTree = ""; }; - 973EC91518C87E5A00DBFFBB /* Image Viewer */ = { - isa = PBXGroup; - children = ( - 973EC91618C87E7300DBFFBB /* BITAttachmentGalleryViewController.h */, - 973EC91718C87E7300DBFFBB /* BITAttachmentGalleryViewController.m */, - ); - name = "Image Viewer"; - sourceTree = ""; - }; 9760F6CC18BB684200959B93 /* Image Editor */ = { isa = PBXGroup; children = ( @@ -658,6 +655,7 @@ E400561C148D79B500EB22B9 /* Frameworks */ = { isa = PBXGroup; children = ( + 97BD9BD4191109730043FD59 /* QuickLook.framework */, 9760F6C318BB4D2D00959B93 /* AssetsLibrary.framework */, E41EB48B148D7C4E0015DEDC /* CrashReporter.framework */, E400561D148D79B500EB22B9 /* Foundation.framework */, @@ -735,6 +733,7 @@ 1E59559A15B6FDA500A03429 /* BITHockeyManager.h in Headers */, 1E5955FD15B7877B00A03429 /* BITHockeyManagerDelegate.h in Headers */, 1E754E5C1621FBB70070AB92 /* BITCrashManager.h in Headers */, + 97BD9BD21911085A0043FD59 /* BITFeedbackMessageAttachment+QLPreviewItem.h in Headers */, 1E754E5E1621FBB70070AB92 /* BITCrashManagerDelegate.h in Headers */, 1E49A4731612226D00463151 /* BITUpdateManager.h in Headers */, 1E49A4791612226D00463151 /* BITUpdateManagerDelegate.h in Headers */, @@ -760,7 +759,6 @@ 1E49A47C1612226D00463151 /* BITUpdateManagerPrivate.h in Headers */, 1E49A4851612226D00463151 /* BITUpdateViewControllerPrivate.h in Headers */, 1E49A4B5161222B900463151 /* BITHockeyBaseManagerPrivate.h in Headers */, - 973EC91818C87E7300DBFFBB /* BITAttachmentGalleryViewController.h in Headers */, E4933E8017B66CDA00B11ACC /* BITHTTPOperation.h in Headers */, 1E49A4BE161222B900463151 /* BITHockeyHelper.h in Headers */, 973EC8BB18BDE29800DBFFBB /* BITArrowImageAnnotation.h in Headers */, @@ -989,7 +987,7 @@ files = ( 1E5954D315B6F24A00A03429 /* BITHockeyManager.m in Sources */, 1E49A43F1612223B00463151 /* BITFeedbackComposeViewController.m in Sources */, - 973EC91918C87E7300DBFFBB /* BITAttachmentGalleryViewController.m in Sources */, + 97BD9BD31911085A0043FD59 /* BITFeedbackMessageAttachment+QLPreviewItem.m in Sources */, E40E0B0D17DA1AFF005E38C1 /* BITHockeyAppClient.m in Sources */, 1E49A4451612223B00463151 /* BITFeedbackListViewCell.m in Sources */, 973EC8B818BCA8A200DBFFBB /* BITRectangleImageAnnotation.m in Sources */, From e7635a9f8550788eb69db1183aa914bf7bd27dd4 Mon Sep 17 00:00:00 2001 From: moritz haarmann Date: Mon, 5 May 2014 10:34:56 +0200 Subject: [PATCH 68/92] + Removed XIB :) --- Classes/View.xib | 31 --------------------- Support/HockeySDK.xcodeproj/project.pbxproj | 2 -- 2 files changed, 33 deletions(-) delete mode 100644 Classes/View.xib diff --git a/Classes/View.xib b/Classes/View.xib deleted file mode 100644 index 8cd84946d0..0000000000 --- a/Classes/View.xib +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Support/HockeySDK.xcodeproj/project.pbxproj b/Support/HockeySDK.xcodeproj/project.pbxproj index 3d5d956fde..ad75c59f0a 100644 --- a/Support/HockeySDK.xcodeproj/project.pbxproj +++ b/Support/HockeySDK.xcodeproj/project.pbxproj @@ -341,7 +341,6 @@ 97BD9BD01911085A0043FD59 /* BITFeedbackMessageAttachment+QLPreviewItem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "BITFeedbackMessageAttachment+QLPreviewItem.h"; sourceTree = ""; }; 97BD9BD11911085A0043FD59 /* BITFeedbackMessageAttachment+QLPreviewItem.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "BITFeedbackMessageAttachment+QLPreviewItem.m"; sourceTree = ""; }; 97BD9BD4191109730043FD59 /* QuickLook.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuickLook.framework; path = System/Library/Frameworks/QuickLook.framework; sourceTree = SDKROOT; }; - 97BD9BDF19113F9B0043FD59 /* View.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = View.xib; sourceTree = ""; }; 97F0F9FB18ABAECD00EF50AA /* iconCamera.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = iconCamera.png; sourceTree = ""; }; 97F0F9FC18ABAECD00EF50AA /* iconCamera@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "iconCamera@2x.png"; sourceTree = ""; }; 97F0F9FF18AE375E00EF50AA /* BITImageAnnotationViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BITImageAnnotationViewController.h; sourceTree = ""; }; @@ -521,7 +520,6 @@ 1E49A4391612223B00463151 /* BITFeedbackUserDataViewController.m */, 1E49A42F1612223B00463151 /* BITFeedbackListViewCell.h */, 1E49A4301612223B00463151 /* BITFeedbackListViewCell.m */, - 97BD9BDF19113F9B0043FD59 /* View.xib */, 1E49A4311612223B00463151 /* BITFeedbackListViewController.h */, 1E49A4321612223B00463151 /* BITFeedbackListViewController.m */, 1EF95CA4162CB036000AE3AD /* BITFeedbackActivity.h */, From 9fd79bc79fc5f77bfa04441b3e3c9b2f85f7fbe5 Mon Sep 17 00:00:00 2001 From: moritz haarmann Date: Mon, 5 May 2014 11:01:45 +0200 Subject: [PATCH 69/92] + Enabled Feedback Observation mode Three-Finger-Tap --- Classes/BITFeedbackManager.h | 2 +- Classes/BITFeedbackManager.m | 46 +++++++++++++++++++++++++++++++++--- 2 files changed, 44 insertions(+), 4 deletions(-) diff --git a/Classes/BITFeedbackManager.h b/Classes/BITFeedbackManager.h index cfc744f552..02441bebf3 100644 --- a/Classes/BITFeedbackManager.h +++ b/Classes/BITFeedbackManager.h @@ -75,7 +75,7 @@ typedef NS_ENUM(NSInteger, BITFeedbackObservationMode) { * Feedback compose will open with a generated screenshot if the screen is tapped * three fingers for three seconds. */ - BITFeedbackObservationModeThreeFingersThreeSeconds = 2 + BITFeedbackObservationModeThreeFingerTap = 2 }; diff --git a/Classes/BITFeedbackManager.m b/Classes/BITFeedbackManager.m index c2cf0facc5..35a5e46f73 100644 --- a/Classes/BITFeedbackManager.m +++ b/Classes/BITFeedbackManager.m @@ -53,6 +53,12 @@ #define kBITFeedbackLastMessageID @"HockeyFeedbackLastMessageID" #define kBITFeedbackAppID @"HockeyFeedbackAppID" +@interface BITFeedbackManager() + +@property (nonatomic, strong) UITapGestureRecognizer *tapRecognizer; +@property (nonatomic) BOOL screenshotNotificationEnabled; + +@end @implementation BITFeedbackManager { NSFileManager *_fileManager; @@ -1087,12 +1093,34 @@ -(void)setFeedbackObservationMode:(BITFeedbackObservationMode)mode { if (mode == BITFeedbackObservationModeOnScreenshot){ - // [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(screenshotNotificationReceived:) name:UIApplicationUserDidTakeScreenshotNotification object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(screenshotNotificationReceived:) name:UIApplicationUserDidTakeScreenshotNotification object:nil]; + self.screenshotNotificationEnabled = YES; + if (self.tapRecognizer){ + [[[UIApplication sharedApplication] keyWindow] removeGestureRecognizer:self.tapRecognizer]; + self.tapRecognizer = nil; + } + } else if (mode == BITFeedbackObservationModeThreeFingerTap){ + if (!self.tapRecognizer){ + self.tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(screenshotTripleTap:)]; + self.tapRecognizer.numberOfTouchesRequired = 3; + self.tapRecognizer.delegate = self; + + dispatch_async(dispatch_get_main_queue(), ^{ + [[UIApplication sharedApplication].keyWindow addGestureRecognizer:self.tapRecognizer]; + }); + } + + if (self.screenshotNotificationEnabled){ + [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationUserDidTakeScreenshotNotification object:nil]; + self.screenshotNotificationEnabled = NO; + } } } -(void)screenshotNotificationReceived:(NSNotification *)notification { - [self extractLastPictureFromLibraryAndLaunchFeedback]; + dispatch_async(dispatch_get_main_queue(), ^{ + [self extractLastPictureFromLibraryAndLaunchFeedback]; + }); } -(void)extractLastPictureFromLibraryAndLaunchFeedback { @@ -1108,13 +1136,25 @@ ALAssetRepresentation *representation = [alAsset defaultRepresentation]; UIImage *latestPhoto = [UIImage imageWithCGImage:[representation fullScreenImage]]; - *stop = YES; *innerStop = YES; + *stop = YES; + *innerStop = YES; + [self showFeedbackComposeViewWithPreparedItems:@[latestPhoto]]; } }]; } failureBlock: nil]; } +- (void)screenshotTripleTap:(UITapGestureRecognizer *)tapRecognizer { + if (tapRecognizer.state == UIGestureRecognizerStateRecognized){ + [self showFeedbackComposeViewWithGeneratedScreenshot]; + } +} + +- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer { + return YES; +} + @end From 4c88a10a8f153c355134d4f07b02a418c6626248 Mon Sep 17 00:00:00 2001 From: moritz haarmann Date: Mon, 5 May 2014 11:20:20 +0200 Subject: [PATCH 70/92] + Feedback Observation Mode: Not supported message for screenshot observation in iOS6 --- Classes/BITFeedbackListViewController.m | 3 ++- Classes/BITFeedbackManager.h | 1 + Classes/BITFeedbackManager.m | 16 +++++++++++++--- 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/Classes/BITFeedbackListViewController.m b/Classes/BITFeedbackListViewController.m index 88b92d2ef6..27ed0d80ec 100644 --- a/Classes/BITFeedbackListViewController.m +++ b/Classes/BITFeedbackListViewController.m @@ -782,8 +782,9 @@ previewController.dataSource = self; [self presentViewController:previewController animated:YES completion:nil]; - + if (self.cachedPreviewItems.count > [self.cachedPreviewItems indexOfObject:attachment]){ [previewController setCurrentPreviewItemIndex:[self.cachedPreviewItems indexOfObject:attachment]]; + } } - (void)refreshPreviewItems { diff --git a/Classes/BITFeedbackManager.h b/Classes/BITFeedbackManager.h index 02441bebf3..01ad9a46a6 100644 --- a/Classes/BITFeedbackManager.h +++ b/Classes/BITFeedbackManager.h @@ -32,6 +32,7 @@ #import "BITHockeyBaseManager.h" #import "BITFeedbackListViewController.h" #import "BITFeedbackComposeViewController.h" +#import "HockeySDKPrivate.h" // Notification message which tells that loading messages finished diff --git a/Classes/BITFeedbackManager.m b/Classes/BITFeedbackManager.m index 35a5e46f73..9f905a3b81 100644 --- a/Classes/BITFeedbackManager.m +++ b/Classes/BITFeedbackManager.m @@ -1093,13 +1093,21 @@ -(void)setFeedbackObservationMode:(BITFeedbackObservationMode)mode { if (mode == BITFeedbackObservationModeOnScreenshot){ + if (NSFoundationVersionNumber > NSFoundationVersionNumber_iOS_6_1){ [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(screenshotNotificationReceived:) name:UIApplicationUserDidTakeScreenshotNotification object:nil]; + } else { + BITHockeyLog("Not enabling Screenshot notifications: iOS6.1 and lower is not supported."); + } + self.screenshotNotificationEnabled = YES; + if (self.tapRecognizer){ [[[UIApplication sharedApplication] keyWindow] removeGestureRecognizer:self.tapRecognizer]; self.tapRecognizer = nil; } - } else if (mode == BITFeedbackObservationModeThreeFingerTap){ + } + + if (mode == BITFeedbackObservationModeThreeFingerTap){ if (!self.tapRecognizer){ self.tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(screenshotTripleTap:)]; self.tapRecognizer.numberOfTouchesRequired = 3; @@ -1111,8 +1119,10 @@ } if (self.screenshotNotificationEnabled){ - [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationUserDidTakeScreenshotNotification object:nil]; - self.screenshotNotificationEnabled = NO; + if (NSFoundationVersionNumber > NSFoundationVersionNumber_iOS_6_1){ + [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationUserDidTakeScreenshotNotification object:nil]; + self.screenshotNotificationEnabled = NO; + } } } } From 1b4ea4492e9d1e0b26deb9c85cd5cd9ceaa85c08 Mon Sep 17 00:00:00 2001 From: moritz haarmann Date: Mon, 5 May 2014 12:12:22 +0200 Subject: [PATCH 71/92] + Landscape support in Attachment editor --- Classes/BITFeedbackListViewCell.m | 6 +----- Classes/BITImageAnnotationViewController.m | 24 +++++++++++++++++++--- 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/Classes/BITFeedbackListViewCell.m b/Classes/BITFeedbackListViewCell.m index e10afbb82a..d5c343eb13 100644 --- a/Classes/BITFeedbackListViewCell.m +++ b/Classes/BITFeedbackListViewCell.m @@ -198,15 +198,11 @@ for (BITFeedbackMessageAttachment *attachment in attachments){ UIButton *imageView = [UIButton buttonWithType:UIButtonTypeCustom]; [imageView setImage:[attachment thumbnailWithSize:CGSizeMake(ATTACHMENT_SIZE, ATTACHMENT_SIZE)] forState:UIControlStateNormal]; - [imageView setTitle:attachment.originalFilename forState:UIControlStateNormal]; - [imageView setContentVerticalAlignment:UIControlContentVerticalAlignmentCenter]; - [imageView setContentHorizontalAlignment:UIControlContentHorizontalAlignmentCenter]; [imageView addTarget:self action:@selector(imageButtonPressed:) forControlEvents:UIControlEventTouchUpInside]; - imageView.contentMode = UIViewContentModeScaleAspectFill; - [self.attachmentViews addObject:imageView]; + [self addSubview:imageView]; } } diff --git a/Classes/BITImageAnnotationViewController.m b/Classes/BITImageAnnotationViewController.m index 5bf851a00a..9f8c74f6d4 100644 --- a/Classes/BITImageAnnotationViewController.m +++ b/Classes/BITImageAnnotationViewController.m @@ -103,24 +103,42 @@ typedef NS_ENUM(NSInteger, BITImageAnnotationViewControllerInteractionMode) { - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; + + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(orientationDidChange:) name:UIDeviceOrientationDidChangeNotification object:nil]; + [self fitImageViewFrame]; } +- (void)viewWillDisappear:(BOOL)animated { + [[NSNotificationCenter defaultCenter] removeObserver:self name:UIDeviceOrientationDidChangeNotification object:nil]; + +} + - (BOOL)prefersStatusBarHidden { return self.navigationController.navigationBarHidden || self.navigationController.navigationBar.alpha == 0.0f; } +- (void)orientationDidChange:(NSNotification *)notification { + [self fitImageViewFrame]; +} + - (void)fitImageViewFrame { - CGFloat heightScaleFactor = [[UIScreen mainScreen] bounds].size.height / self.image.size.height; - CGFloat widthScaleFactor = [[UIScreen mainScreen] bounds].size.width / self.image.size.width; + + CGSize size = [UIScreen mainScreen].bounds.size; + if (UIInterfaceOrientationIsLandscape([UIApplication sharedApplication].statusBarOrientation)){ + size = CGSizeMake(size.height, size.width); + } + + CGFloat heightScaleFactor = size.height / self.image.size.height; + CGFloat widthScaleFactor = size.width / self.image.size.width; CGFloat factor = MIN(heightScaleFactor, widthScaleFactor); self.scaleFactor = factor; CGSize scaledImageSize = CGSizeMake(self.image.size.width * factor, self.image.size.height * factor); - CGRect baseFrame = CGRectMake(self.view.frame.size.width/2 - scaledImageSize.width/2, self.view.frame.size.height - [[UIScreen mainScreen] bounds].size.height, scaledImageSize.width, scaledImageSize.height); + CGRect baseFrame = CGRectMake(self.view.frame.size.width/2 - scaledImageSize.width/2, self.view.frame.size.height - size.height, scaledImageSize.width, scaledImageSize.height); self.imageView.frame = baseFrame; } From 926dc48aa6c3c30771ab50a75f8394bf139eedfc Mon Sep 17 00:00:00 2001 From: moritz haarmann Date: Mon, 5 May 2014 14:35:32 +0200 Subject: [PATCH 72/92] + Async Attachment loading. --- Classes/BITFeedbackListViewController.m | 16 +++++++++++++++- Classes/BITFeedbackManager.m | 3 ++- .../BITFeedbackMessageAttachment+QLPreviewItem.m | 6 +++++- Classes/BITFeedbackMessageAttachment.h | 7 +++++++ Classes/BITFeedbackMessageAttachment.m | 13 +++++++++---- Support/HockeySDK.xcodeproj/project.pbxproj | 2 ++ 6 files changed, 40 insertions(+), 7 deletions(-) diff --git a/Classes/BITFeedbackListViewController.m b/Classes/BITFeedbackListViewController.m index 27ed0d80ec..2b774217c5 100644 --- a/Classes/BITFeedbackListViewController.m +++ b/Classes/BITFeedbackListViewController.m @@ -814,7 +814,21 @@ - (id ) previewController: (QLPreviewController *) controller previewItemAtIndex: (NSInteger) index { if (index>=0){ - return self.cachedPreviewItems[index];} + BITFeedbackMessageAttachment *attachment = self.cachedPreviewItems[index]; + if (attachment.needsLoadingFromURL && !attachment.isLoading){ + attachment.isLoading = YES; + NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:attachment.sourceURL]]; + [NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue new] completionHandler:^(NSURLResponse *response, NSData *responseData, NSError *err) { + if (responseData.length){ + [attachment replaceData:responseData]; + [controller reloadData]; + } + }]; + return nil; + } else { + return self.cachedPreviewItems[index]; + } + } return nil; } diff --git a/Classes/BITFeedbackManager.m b/Classes/BITFeedbackManager.m index 9f905a3b81..5c89cf1cbd 100644 --- a/Classes/BITFeedbackManager.m +++ b/Classes/BITFeedbackManager.m @@ -805,7 +805,8 @@ [self markSendInProgressMessagesAsPending]; } - [self synchronizeMissingAttachments]; + // we'll load the images on demand. + //[self synchronizeMissingAttachments]; [self saveMessages]; diff --git a/Classes/BITFeedbackMessageAttachment+QLPreviewItem.m b/Classes/BITFeedbackMessageAttachment+QLPreviewItem.m index da5424ebeb..96b8a792bf 100644 --- a/Classes/BITFeedbackMessageAttachment+QLPreviewItem.m +++ b/Classes/BITFeedbackMessageAttachment+QLPreviewItem.m @@ -15,7 +15,11 @@ } - (NSURL *)previewItemURL { - return self.localURL; + if (self.localURL){ + return self.localURL; + } else { + return [NSURL fileURLWithPath:self.possibleFilename]; + } } @end diff --git a/Classes/BITFeedbackMessageAttachment.h b/Classes/BITFeedbackMessageAttachment.h index a76b995e68..5162b24e3e 100644 --- a/Classes/BITFeedbackMessageAttachment.h +++ b/Classes/BITFeedbackMessageAttachment.h @@ -36,8 +36,10 @@ @property (nonatomic, copy) NSString *originalFilename; @property (nonatomic, copy) NSString *contentType; @property (nonatomic, copy) NSString *sourceURL; +@property (nonatomic) BOOL isLoading; @property (nonatomic, readonly) NSData *data; + @property (readonly) UIImage *imageRepresentation; @@ -55,4 +57,9 @@ - (NSURL *)localURL; +/** + Used to determine whether QuickLook can preview this file or not. If not, we don't download it. + */ +- (NSString*)possibleFilename; + @end diff --git a/Classes/BITFeedbackMessageAttachment.m b/Classes/BITFeedbackMessageAttachment.m index 541d25fd53..9fb38bdd44 100644 --- a/Classes/BITFeedbackMessageAttachment.m +++ b/Classes/BITFeedbackMessageAttachment.m @@ -73,7 +73,7 @@ -(void)setData:(NSData *)data { self->_internalData = data; - self.filename = [self createFilename]; + self.filename = [self possibleFilename]; [self->_internalData writeToFile:self.filename atomically:NO]; } @@ -138,14 +138,18 @@ #pragma mark - Thubmnails / Image Representation - (UIImage *)imageRepresentation { - if ([self.contentType rangeOfString:@"image"].location != NSNotFound ){ + if ([self.contentType rangeOfString:@"image"].location != NSNotFound && self.filename ){ return [UIImage imageWithData:self.data]; } else { // Create a Icon .. UIDocumentInteractionController* docController = [[UIDocumentInteractionController alloc] init]; docController.name = self.originalFilename; NSArray* icons = docController.icons; - return icons[0]; + if (icons.count){ + return icons[0]; + } else { + return nil; + } } } @@ -181,10 +185,11 @@ return self.thumbnailRepresentations[cacheKey]; } + #pragma mark - Persistence Helpers - - (NSString *)createFilename { + - (NSString *)possibleFilename { NSArray* cachePathArray = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES); NSString* cachePath = [cachePathArray lastObject]; cachePath = [cachePath stringByAppendingPathComponent:kCacheFolderName]; diff --git a/Support/HockeySDK.xcodeproj/project.pbxproj b/Support/HockeySDK.xcodeproj/project.pbxproj index ad75c59f0a..bfef238ae0 100644 --- a/Support/HockeySDK.xcodeproj/project.pbxproj +++ b/Support/HockeySDK.xcodeproj/project.pbxproj @@ -156,6 +156,7 @@ 97BD9BD21911085A0043FD59 /* BITFeedbackMessageAttachment+QLPreviewItem.h in Headers */ = {isa = PBXBuildFile; fileRef = 97BD9BD01911085A0043FD59 /* BITFeedbackMessageAttachment+QLPreviewItem.h */; }; 97BD9BD31911085A0043FD59 /* BITFeedbackMessageAttachment+QLPreviewItem.m in Sources */ = {isa = PBXBuildFile; fileRef = 97BD9BD11911085A0043FD59 /* BITFeedbackMessageAttachment+QLPreviewItem.m */; }; 97BD9BD5191109730043FD59 /* QuickLook.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 97BD9BD4191109730043FD59 /* QuickLook.framework */; }; + 97CC11F71917AB7C0028768F /* QuickLook.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 97BD9BD4191109730043FD59 /* QuickLook.framework */; }; 97F0F9FD18ABAECD00EF50AA /* iconCamera.png in Resources */ = {isa = PBXBuildFile; fileRef = 97F0F9FB18ABAECD00EF50AA /* iconCamera.png */; }; 97F0F9FE18ABAECD00EF50AA /* iconCamera@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 97F0F9FC18ABAECD00EF50AA /* iconCamera@2x.png */; }; 97F0FA0518B2294D00EF50AA /* BITFeedbackMessageAttachment.m in Sources */ = {isa = PBXBuildFile; fileRef = 97F0FA0318AE5AED00EF50AA /* BITFeedbackMessageAttachment.m */; }; @@ -390,6 +391,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 97CC11F71917AB7C0028768F /* QuickLook.framework in Frameworks */, 1EA1170016F4D32C001C015C /* libHockeySDK.a in Frameworks */, 1E5A459216F0DFC200B55C04 /* SenTestingKit.framework in Frameworks */, 1EA1170116F4D354001C015C /* CrashReporter.framework in Frameworks */, From 64989bce0da5b98d6eabaaec32a9e30b8d1acb06 Mon Sep 17 00:00:00 2001 From: moritz haarmann Date: Mon, 5 May 2014 14:49:52 +0200 Subject: [PATCH 73/92] + Added MobileCoreServices framework. --- Support/HockeySDK.xcodeproj/project.pbxproj | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Support/HockeySDK.xcodeproj/project.pbxproj b/Support/HockeySDK.xcodeproj/project.pbxproj index bfef238ae0..365d97bfee 100644 --- a/Support/HockeySDK.xcodeproj/project.pbxproj +++ b/Support/HockeySDK.xcodeproj/project.pbxproj @@ -157,6 +157,8 @@ 97BD9BD31911085A0043FD59 /* BITFeedbackMessageAttachment+QLPreviewItem.m in Sources */ = {isa = PBXBuildFile; fileRef = 97BD9BD11911085A0043FD59 /* BITFeedbackMessageAttachment+QLPreviewItem.m */; }; 97BD9BD5191109730043FD59 /* QuickLook.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 97BD9BD4191109730043FD59 /* QuickLook.framework */; }; 97CC11F71917AB7C0028768F /* QuickLook.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 97BD9BD4191109730043FD59 /* QuickLook.framework */; }; + 97CC11F91917C0310028768F /* MobileCoreServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 97CC11F81917C0310028768F /* MobileCoreServices.framework */; }; + 97CC11FA1917C0390028768F /* MobileCoreServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 97CC11F81917C0310028768F /* MobileCoreServices.framework */; }; 97F0F9FD18ABAECD00EF50AA /* iconCamera.png in Resources */ = {isa = PBXBuildFile; fileRef = 97F0F9FB18ABAECD00EF50AA /* iconCamera.png */; }; 97F0F9FE18ABAECD00EF50AA /* iconCamera@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 97F0F9FC18ABAECD00EF50AA /* iconCamera@2x.png */; }; 97F0FA0518B2294D00EF50AA /* BITFeedbackMessageAttachment.m in Sources */ = {isa = PBXBuildFile; fileRef = 97F0FA0318AE5AED00EF50AA /* BITFeedbackMessageAttachment.m */; }; @@ -342,6 +344,7 @@ 97BD9BD01911085A0043FD59 /* BITFeedbackMessageAttachment+QLPreviewItem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "BITFeedbackMessageAttachment+QLPreviewItem.h"; sourceTree = ""; }; 97BD9BD11911085A0043FD59 /* BITFeedbackMessageAttachment+QLPreviewItem.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "BITFeedbackMessageAttachment+QLPreviewItem.m"; sourceTree = ""; }; 97BD9BD4191109730043FD59 /* QuickLook.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuickLook.framework; path = System/Library/Frameworks/QuickLook.framework; sourceTree = SDKROOT; }; + 97CC11F81917C0310028768F /* MobileCoreServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MobileCoreServices.framework; path = System/Library/Frameworks/MobileCoreServices.framework; sourceTree = SDKROOT; }; 97F0F9FB18ABAECD00EF50AA /* iconCamera.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = iconCamera.png; sourceTree = ""; }; 97F0F9FC18ABAECD00EF50AA /* iconCamera@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "iconCamera@2x.png"; sourceTree = ""; }; 97F0F9FF18AE375E00EF50AA /* BITImageAnnotationViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BITImageAnnotationViewController.h; sourceTree = ""; }; @@ -373,6 +376,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 97CC11F91917C0310028768F /* MobileCoreServices.framework in Frameworks */, 97BD9BD5191109730043FD59 /* QuickLook.framework in Frameworks */, 9760F6C418BB4D2D00959B93 /* AssetsLibrary.framework in Frameworks */, 1E5954DC15B6F24A00A03429 /* Foundation.framework in Frameworks */, @@ -391,6 +395,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 97CC11FA1917C0390028768F /* MobileCoreServices.framework in Frameworks */, 97CC11F71917AB7C0028768F /* QuickLook.framework in Frameworks */, 1EA1170016F4D32C001C015C /* libHockeySDK.a in Frameworks */, 1E5A459216F0DFC200B55C04 /* SenTestingKit.framework in Frameworks */, @@ -655,6 +660,7 @@ E400561C148D79B500EB22B9 /* Frameworks */ = { isa = PBXGroup; children = ( + 97CC11F81917C0310028768F /* MobileCoreServices.framework */, 97BD9BD4191109730043FD59 /* QuickLook.framework */, 9760F6C318BB4D2D00959B93 /* AssetsLibrary.framework */, E41EB48B148D7C4E0015DEDC /* CrashReporter.framework */, From 68791f592fb86ad0b9d038285fbd3db06cbdde1e Mon Sep 17 00:00:00 2001 From: moritz haarmann Date: Mon, 5 May 2014 14:57:38 +0200 Subject: [PATCH 74/92] + Fixed some async loading problems. --- Classes/BITFeedbackListViewCell.m | 2 +- Classes/BITFeedbackListViewController.m | 4 +++- Classes/BITFeedbackMessageAttachment.m | 10 ++++++---- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/Classes/BITFeedbackListViewCell.m b/Classes/BITFeedbackListViewCell.m index d5c343eb13..cc41817a18 100644 --- a/Classes/BITFeedbackListViewCell.m +++ b/Classes/BITFeedbackListViewCell.m @@ -198,7 +198,7 @@ for (BITFeedbackMessageAttachment *attachment in attachments){ UIButton *imageView = [UIButton buttonWithType:UIButtonTypeCustom]; [imageView setImage:[attachment thumbnailWithSize:CGSizeMake(ATTACHMENT_SIZE, ATTACHMENT_SIZE)] forState:UIControlStateNormal]; - + [imageView setContentMode:UIViewContentModeScaleAspectFit]; [imageView addTarget:self action:@selector(imageButtonPressed:) forControlEvents:UIControlEventTouchUpInside]; [self.attachmentViews addObject:imageView]; diff --git a/Classes/BITFeedbackListViewController.m b/Classes/BITFeedbackListViewController.m index 2b774217c5..535373af0f 100644 --- a/Classes/BITFeedbackListViewController.m +++ b/Classes/BITFeedbackListViewController.m @@ -34,6 +34,7 @@ #import "HockeySDKPrivate.h" #import "BITFeedbackManagerPrivate.h" +#import "BITFeedbackManager.h" #import "BITFeedbackListViewController.h" #import "BITFeedbackListViewCell.h" #import "BITFeedbackComposeViewController.h" @@ -794,7 +795,7 @@ for (int i = 0; i Date: Mon, 5 May 2014 15:02:52 +0200 Subject: [PATCH 75/92] + Fixed Row display for more than 5 attachments. --- Classes/BITFeedbackListViewCell.m | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Classes/BITFeedbackListViewCell.m b/Classes/BITFeedbackListViewCell.m index cc41817a18..3afd4db3bf 100644 --- a/Classes/BITFeedbackListViewCell.m +++ b/Classes/BITFeedbackListViewCell.m @@ -274,15 +274,15 @@ int i = 0; - CGFloat attachmentsPerRow = ceilf(self.frame.size.width / (FRAME_SIDE_BORDER + ATTACHMENT_SIZE)); + CGFloat attachmentsPerRow = floorf(self.frame.size.width / (FRAME_SIDE_BORDER + ATTACHMENT_SIZE)); for ( UIButton *imageButton in self.attachmentViews){ imageButton.contentMode = UIViewContentModeScaleAspectFit; if ( !_message.userMessage){ - imageButton.frame = CGRectMake(FRAME_SIDE_BORDER + (FRAME_SIDE_BORDER + ATTACHMENT_SIZE) * i , floor(i/attachmentsPerRow) + baseOffsetOfText , ATTACHMENT_SIZE, ATTACHMENT_SIZE); + imageButton.frame = CGRectMake(FRAME_SIDE_BORDER + (FRAME_SIDE_BORDER + ATTACHMENT_SIZE) * (i%(int)attachmentsPerRow) , floor(i/attachmentsPerRow)*(FRAME_SIDE_BORDER + ATTACHMENT_SIZE) + baseOffsetOfText , ATTACHMENT_SIZE, ATTACHMENT_SIZE); } else { - imageButton.frame = CGRectMake(self.frame.size.width - FRAME_SIDE_BORDER - ATTACHMENT_SIZE - ((FRAME_SIDE_BORDER + ATTACHMENT_SIZE) * (i) ), floor(i/attachmentsPerRow) + baseOffsetOfText , ATTACHMENT_SIZE, ATTACHMENT_SIZE); + imageButton.frame = CGRectMake(self.frame.size.width - FRAME_SIDE_BORDER - ATTACHMENT_SIZE - ((FRAME_SIDE_BORDER + ATTACHMENT_SIZE) * (i%(int)attachmentsPerRow) ), floor(i/attachmentsPerRow)*(FRAME_SIDE_BORDER + ATTACHMENT_SIZE) + baseOffsetOfText , ATTACHMENT_SIZE, ATTACHMENT_SIZE); } if (!imageButton.superview){ From 782a6f2e7960e18a2085fd271f5e6c30729741d9 Mon Sep 17 00:00:00 2001 From: moritz haarmann Date: Mon, 5 May 2014 15:04:14 +0200 Subject: [PATCH 76/92] + Scaling issues resolved. --- Classes/BITFeedbackListViewCell.m | 1 + 1 file changed, 1 insertion(+) diff --git a/Classes/BITFeedbackListViewCell.m b/Classes/BITFeedbackListViewCell.m index 3afd4db3bf..6e49f2f1a8 100644 --- a/Classes/BITFeedbackListViewCell.m +++ b/Classes/BITFeedbackListViewCell.m @@ -278,6 +278,7 @@ for ( UIButton *imageButton in self.attachmentViews){ imageButton.contentMode = UIViewContentModeScaleAspectFit; + imageButton.imageView.contentMode = UIViewContentModeScaleAspectFill; if ( !_message.userMessage){ imageButton.frame = CGRectMake(FRAME_SIDE_BORDER + (FRAME_SIDE_BORDER + ATTACHMENT_SIZE) * (i%(int)attachmentsPerRow) , floor(i/attachmentsPerRow)*(FRAME_SIDE_BORDER + ATTACHMENT_SIZE) + baseOffsetOfText , ATTACHMENT_SIZE, ATTACHMENT_SIZE); From 53713537e808d09685f22ed7dee0264e7c29dc7e Mon Sep 17 00:00:00 2001 From: moritz haarmann Date: Wed, 7 May 2014 14:54:31 +0200 Subject: [PATCH 77/92] + Fixed a bug where attachments would not load. --- Classes/BITFeedbackMessageAttachment.m | 1 + 1 file changed, 1 insertion(+) diff --git a/Classes/BITFeedbackMessageAttachment.m b/Classes/BITFeedbackMessageAttachment.m index 9e14ba0c91..45e4da3b9d 100644 --- a/Classes/BITFeedbackMessageAttachment.m +++ b/Classes/BITFeedbackMessageAttachment.m @@ -66,6 +66,7 @@ -(id)init { self = [super init]; if (self){ + self.isLoading = NO; self.thumbnailRepresentations = [NSMutableDictionary new]; } return self; From 7f0a04044ccc86d06056ce619bc681325947ce0c Mon Sep 17 00:00:00 2001 From: Moritz Haarmann Date: Mon, 12 May 2014 01:25:30 +0200 Subject: [PATCH 78/92] Some fixes, but too much movement. ATM. --- Classes/BITFeedbackComposeViewController.m | 41 +++++++++++++++++++--- 1 file changed, 37 insertions(+), 4 deletions(-) diff --git a/Classes/BITFeedbackComposeViewController.m b/Classes/BITFeedbackComposeViewController.m index bb35383251..b987112a6a 100644 --- a/Classes/BITFeedbackComposeViewController.m +++ b/Classes/BITFeedbackComposeViewController.m @@ -62,6 +62,10 @@ @property (nonatomic, strong) UIView *textAccessoryView; @property (nonatomic) NSInteger selectedAttachmentIndex; +@property (nonatomic) BOOL scrollToNewestAttachment; + +@property (nonatomic) BOOL reallyVisible; + @end @@ -228,6 +232,7 @@ } [self updateBarButtonState]; + } @@ -245,6 +250,10 @@ // Invoke delayed to fix iOS 7 iPad landscape bug, where this view will be moved if not called delayed [self.textView performSelector:@selector(becomeFirstResponder) withObject:nil afterDelay:0.0]; } + + + + self.reallyVisible = YES; [self refreshAttachmentScrollview]; @@ -258,6 +267,8 @@ [super viewWillDisappear:animated]; + self.reallyVisible = NO; + [[UIApplication sharedApplication] setStatusBarStyle:_statusBarStyle]; } @@ -268,6 +279,8 @@ -(void)refreshAttachmentScrollview { CGFloat scrollViewWidth = 0; + CGPoint preservedOffset = self.attachmentScrollView.contentOffset; + if (self.attachments.count){ scrollViewWidth = 100; } @@ -287,7 +300,7 @@ } for (UIView *subview in self.attachmentScrollView.subviews){ - [subview removeFromSuperview]; + // [subview removeFromSuperview]; } if (self.attachments.count > self.attachmentScrollViewImageViews.count){ @@ -296,6 +309,7 @@ UIButton *newImageButton = [UIButton buttonWithType:UIButtonTypeCustom]; [newImageButton addTarget:self action:@selector(imageButtonAction:) forControlEvents:UIControlEventTouchUpInside]; [self.attachmentScrollViewImageViews addObject:newImageButton]; + [self.attachmentScrollView addSubview:newImageButton]; } } @@ -303,6 +317,8 @@ CGFloat currentYOffset = 0.0f; + CGFloat heightOfLastAttachment = 0.0f; + for (BITFeedbackMessageAttachment* attachment in self.attachments){ UIButton *imageButton = self.attachmentScrollViewImageViews[index]; @@ -317,13 +333,23 @@ currentYOffset += height; - [self.attachmentScrollView addSubview:imageButton]; + // [self.attachmentScrollView addSubview:imageButton]; + + heightOfLastAttachment = height + 20; [imageButton setImage:image forState:UIControlStateNormal]; index++; } [self.attachmentScrollView setContentSize:CGSizeMake(CGRectGetWidth(self.attachmentScrollView.frame), currentYOffset)]; + NSLog(@"%f %f", self.attachmentScrollView.contentOffset.x, self.attachmentScrollView.contentOffset.y); + if (self.scrollToNewestAttachment && self.reallyVisible && currentYOffset - heightOfLastAttachment > 0){ + self.scrollToNewestAttachment = NO; + + [self.attachmentScrollView setContentOffset:CGPointMake(0,currentYOffset-heightOfLastAttachment)]; + } else { + self.attachmentScrollView.contentOffset = preservedOffset; + } [self updateBarButtonState]; } @@ -334,6 +360,13 @@ } else { self.navigationItem.rightBarButtonItem.enabled = NO; } + + if (self.attachments.count > 2){ + self.textView.inputAccessoryView = nil; + } else { + self.textView.inputAccessoryView = self.textAccessoryView; + + } } @@ -407,10 +440,10 @@ NSString *imageName = [imagePath lastPathComponent]; newAttachment.originalFilename = imageName; [self.attachments addObject:newAttachment]; + self.scrollToNewestAttachment = YES; } [picker dismissViewControllerAnimated:YES completion:nil]; - [self refreshAttachmentScrollview]; } - (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker { @@ -476,6 +509,7 @@ self.selectedAttachmentIndex = NSNotFound; [self refreshAttachmentScrollview]; + } else if(buttonIndex != [actionSheet cancelButtonIndex]){ if (self.selectedAttachmentIndex != NSNotFound){ BITFeedbackMessageAttachment* attachment = [self.attachments objectAtIndex:self.selectedAttachmentIndex]; @@ -497,7 +531,6 @@ if (self.selectedAttachmentIndex != NSNotFound){ BITFeedbackMessageAttachment* attachment = [self.attachments objectAtIndex:self.selectedAttachmentIndex]; [attachment replaceData:UIImageJPEGRepresentation(image, 0.7f)]; - [self refreshAttachmentScrollview]; } self.selectedAttachmentIndex = NSNotFound; From 901a2e1cdd953558f7589dd0a398fea2fcf0e7e4 Mon Sep 17 00:00:00 2001 From: Moritz Haarmann Date: Mon, 12 May 2014 02:05:45 +0200 Subject: [PATCH 79/92] + Async loading of attachments once shown. --- Classes/BITFeedbackListViewController.m | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/Classes/BITFeedbackListViewController.m b/Classes/BITFeedbackListViewController.m index 535373af0f..f9d2094694 100644 --- a/Classes/BITFeedbackListViewController.m +++ b/Classes/BITFeedbackListViewController.m @@ -74,6 +74,7 @@ @property (nonatomic, strong) NSDateFormatter *lastUpdateDateFormatter; @property (nonatomic) BOOL userDataComposeFlow; @property (nonatomic, strong) NSArray *cachedPreviewItems; +@property (nonatomic, strong) NSOperationQueue *thumbnailQueue; @end @@ -95,6 +96,8 @@ [self.lastUpdateDateFormatter setDateStyle:NSDateFormatterShortStyle]; [self.lastUpdateDateFormatter setTimeStyle:NSDateFormatterShortStyle]; self.lastUpdateDateFormatter.locale = [NSLocale currentLocale]; + + _thumbnailQueue = [NSOperationQueue new]; } return self; } @@ -636,6 +639,22 @@ cell.labelText.userInteractionEnabled = YES; cell.delegate = self; [cell setAttachments:message.attachments]; + + for (BITFeedbackMessageAttachment *attachment in message.attachments){ + if (attachment.needsLoadingFromURL && !attachment.isLoading){ + attachment.isLoading = YES; + NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:attachment.sourceURL]]; + [NSURLConnection sendAsynchronousRequest:request queue:self.thumbnailQueue completionHandler:^(NSURLResponse *response, NSData *responseData, NSError *err) { + if (responseData.length){ + [attachment replaceData:responseData]; + dispatch_async(dispatch_get_main_queue(), ^{ + [self.tableView reloadData]; + }); + [[BITHockeyManager sharedHockeyManager].feedbackManager saveMessages]; + } + }]; + } + } if ( [self.manager isPreiOS7Environment] || @@ -819,14 +838,14 @@ if (attachment.needsLoadingFromURL && !attachment.isLoading){ attachment.isLoading = YES; NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:attachment.sourceURL]]; - [NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue new] completionHandler:^(NSURLResponse *response, NSData *responseData, NSError *err) { + [NSURLConnection sendAsynchronousRequest:request queue:self.thumbnailQueue completionHandler:^(NSURLResponse *response, NSData *responseData, NSError *err) { if (responseData.length){ [attachment replaceData:responseData]; [controller reloadData]; [[BITHockeyManager sharedHockeyManager].feedbackManager saveMessages]; } }]; - return nil; + return attachment; } else { return self.cachedPreviewItems[index]; } From f9bc3d74cb07daa31571979108f45141cc670d2d Mon Sep 17 00:00:00 2001 From: moritz haarmann Date: Mon, 12 May 2014 12:01:29 +0200 Subject: [PATCH 80/92] + Now comes with vertical AND horizontal centering. --- Classes/BITImageAnnotationViewController.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Classes/BITImageAnnotationViewController.m b/Classes/BITImageAnnotationViewController.m index 9f8c74f6d4..987c23a371 100644 --- a/Classes/BITImageAnnotationViewController.m +++ b/Classes/BITImageAnnotationViewController.m @@ -138,7 +138,7 @@ typedef NS_ENUM(NSInteger, BITImageAnnotationViewControllerInteractionMode) { self.scaleFactor = factor; CGSize scaledImageSize = CGSizeMake(self.image.size.width * factor, self.image.size.height * factor); - CGRect baseFrame = CGRectMake(self.view.frame.size.width/2 - scaledImageSize.width/2, self.view.frame.size.height - size.height, scaledImageSize.width, scaledImageSize.height); + CGRect baseFrame = CGRectMake(self.view.frame.size.width/2 - scaledImageSize.width/2, self.view.frame.size.height/2 - scaledImageSize.height/2, scaledImageSize.width, scaledImageSize.height); self.imageView.frame = baseFrame; } From d736b35f04157e179fa8288ea08402c22780f26b Mon Sep 17 00:00:00 2001 From: moritz haarmann Date: Mon, 12 May 2014 12:28:00 +0200 Subject: [PATCH 81/92] + Append new Attachments on top. Solves some problems. --- Classes/BITFeedbackComposeViewController.m | 51 +++++++++------------- 1 file changed, 20 insertions(+), 31 deletions(-) diff --git a/Classes/BITFeedbackComposeViewController.m b/Classes/BITFeedbackComposeViewController.m index b987112a6a..f1da11adb3 100644 --- a/Classes/BITFeedbackComposeViewController.m +++ b/Classes/BITFeedbackComposeViewController.m @@ -61,10 +61,7 @@ @property (nonatomic, strong) UIView *textAccessoryView; @property (nonatomic) NSInteger selectedAttachmentIndex; - -@property (nonatomic) BOOL scrollToNewestAttachment; - -@property (nonatomic) BOOL reallyVisible; +@property (nonatomic, strong) UITapGestureRecognizer *tapRecognizer; @end @@ -85,6 +82,8 @@ _manager = [BITHockeyManager sharedHockeyManager].feedbackManager; _attachments = [NSMutableArray new]; _attachmentScrollViewImageViews = [NSMutableArray new]; + self.tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(scrollViewTapped:)]; + [self.attachmentScrollView addGestureRecognizer:self.tapRecognizer]; _text = nil; } @@ -144,6 +143,9 @@ } } [self.contentViewContainer setFrame:frame]; + + [self performSelector:@selector(refreshAttachmentScrollview) withObject:nil afterDelay:0.0f]; + } - (void)keyboardWillBeHidden:(NSNotification*)aNotification { @@ -200,6 +202,7 @@ self.attachmentScrollView = [[UIScrollView alloc] initWithFrame:CGRectZero]; self.attachmentScrollView.scrollEnabled = YES; self.attachmentScrollView.bounces = YES; + self.attachmentScrollView.autoresizesSubviews = NO; self.attachmentScrollView.autoresizingMask = UIViewAutoresizingFlexibleHeight|UIViewAutoresizingFlexibleRightMargin; [self.contentViewContainer addSubview:self.attachmentScrollView]; @@ -250,13 +253,7 @@ // Invoke delayed to fix iOS 7 iPad landscape bug, where this view will be moved if not called delayed [self.textView performSelector:@selector(becomeFirstResponder) withObject:nil afterDelay:0.0]; } - - - self.reallyVisible = YES; - - [self refreshAttachmentScrollview]; - } - (void)viewWillDisappear:(BOOL)animated { @@ -266,8 +263,6 @@ self.manager.currentFeedbackComposeViewController = nil; [super viewWillDisappear:animated]; - - self.reallyVisible = NO; [[UIApplication sharedApplication] setStatusBarStyle:_statusBarStyle]; } @@ -297,10 +292,7 @@ self.textView.frame = textViewFrame; self.attachmentScrollView.frame = scrollViewFrame; self.attachmentScrollView.contentInset = self.textView.contentInset; - } - - for (UIView *subview in self.attachmentScrollView.subviews){ - // [subview removeFromSuperview]; + } if (self.attachments.count > self.attachmentScrollViewImageViews.count){ @@ -317,9 +309,9 @@ CGFloat currentYOffset = 0.0f; - CGFloat heightOfLastAttachment = 0.0f; + NSEnumerator *reverseAttachments = self.attachments.reverseObjectEnumerator; - for (BITFeedbackMessageAttachment* attachment in self.attachments){ + for (BITFeedbackMessageAttachment* attachment in reverseAttachments.allObjects){ UIButton *imageButton = self.attachmentScrollViewImageViews[index]; UIImage *image = [attachment thumbnailWithSize:CGSizeMake(100, 100)]; @@ -333,23 +325,11 @@ currentYOffset += height; - // [self.attachmentScrollView addSubview:imageButton]; - - heightOfLastAttachment = height + 20; - [imageButton setImage:image forState:UIControlStateNormal]; index++; } [self.attachmentScrollView setContentSize:CGSizeMake(CGRectGetWidth(self.attachmentScrollView.frame), currentYOffset)]; - NSLog(@"%f %f", self.attachmentScrollView.contentOffset.x, self.attachmentScrollView.contentOffset.y); - if (self.scrollToNewestAttachment && self.reallyVisible && currentYOffset - heightOfLastAttachment > 0){ - self.scrollToNewestAttachment = NO; - - [self.attachmentScrollView setContentOffset:CGPointMake(0,currentYOffset-heightOfLastAttachment)]; - } else { - self.attachmentScrollView.contentOffset = preservedOffset; - } [self updateBarButtonState]; } @@ -428,6 +408,16 @@ [self presentViewController:pickerController animated:YES completion:nil]; } +- (void)scrollViewTapped:(id)unused { + UIMenuController *menuController = [UIMenuController sharedMenuController]; + [menuController setTargetRect:CGRectMake([self.tapRecognizer locationInView:self.view].x, [self.tapRecognizer locationInView:self.view].x, 1, 1) inView:self.view]; + [menuController setMenuVisible:YES animated:YES]; +} + +- (void)paste:(id)sender { + +} + #pragma mark - UIImagePickerControllerDelegate - (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info { @@ -440,7 +430,6 @@ NSString *imageName = [imagePath lastPathComponent]; newAttachment.originalFilename = imageName; [self.attachments addObject:newAttachment]; - self.scrollToNewestAttachment = YES; } [picker dismissViewControllerAnimated:YES completion:nil]; From 6082885d6592794a4f7af62b2d86b54f3fd01817 Mon Sep 17 00:00:00 2001 From: moritz haarmann Date: Mon, 12 May 2014 12:34:18 +0200 Subject: [PATCH 82/92] + Removed some Logging stuff. --- Classes/BITFeedbackListViewController.m | 1 - 1 file changed, 1 deletion(-) diff --git a/Classes/BITFeedbackListViewController.m b/Classes/BITFeedbackListViewController.m index f9d2094694..ede1c4f8d7 100644 --- a/Classes/BITFeedbackListViewController.m +++ b/Classes/BITFeedbackListViewController.m @@ -814,7 +814,6 @@ for (int i = 0; i Date: Mon, 12 May 2014 12:36:03 +0200 Subject: [PATCH 83/92] + Added License Headers --- Classes/BITArrowImageAnnotation.h | 34 +++++++++++++++++----- Classes/BITArrowImageAnnotation.m | 34 +++++++++++++++++----- Classes/BITBlurImageAnnotation.h | 34 +++++++++++++++++----- Classes/BITBlurImageAnnotation.m | 34 +++++++++++++++++----- Classes/BITImageAnnotation.h | 34 +++++++++++++++++----- Classes/BITImageAnnotation.m | 34 +++++++++++++++++----- Classes/BITImageAnnotationViewController.h | 34 +++++++++++++++++----- Classes/BITImageAnnotationViewController.m | 34 +++++++++++++++++----- Classes/BITRectangleImageAnnotation.h | 34 +++++++++++++++++----- Classes/BITRectangleImageAnnotation.m | 34 +++++++++++++++++----- 10 files changed, 270 insertions(+), 70 deletions(-) diff --git a/Classes/BITArrowImageAnnotation.h b/Classes/BITArrowImageAnnotation.h index ff243af6a4..feeca7613b 100644 --- a/Classes/BITArrowImageAnnotation.h +++ b/Classes/BITArrowImageAnnotation.h @@ -1,10 +1,30 @@ -// -// BITArrowImageAnnotation.h -// HockeySDK -// -// Created by Moritz Haarmann on 26.02.14. -// -// +/* + * Author: Moritz Haarmann + * + * Copyright (c) 2012-2014 HockeyApp, Bit Stadium GmbH. + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ #import "BITImageAnnotation.h" diff --git a/Classes/BITArrowImageAnnotation.m b/Classes/BITArrowImageAnnotation.m index 40c58e953f..2d739b4fdc 100644 --- a/Classes/BITArrowImageAnnotation.m +++ b/Classes/BITArrowImageAnnotation.m @@ -1,10 +1,30 @@ -// -// BITArrowImageAnnotation.m -// HockeySDK -// -// Created by Moritz Haarmann on 26.02.14. -// -// +/* + * Author: Moritz Haarmann + * + * Copyright (c) 2012-2014 HockeyApp, Bit Stadium GmbH. + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ #import "BITArrowImageAnnotation.h" diff --git a/Classes/BITBlurImageAnnotation.h b/Classes/BITBlurImageAnnotation.h index b23d7f2d82..cb5a1f3ecc 100644 --- a/Classes/BITBlurImageAnnotation.h +++ b/Classes/BITBlurImageAnnotation.h @@ -1,10 +1,30 @@ -// -// BITBlurImageAnnotation.h -// HockeySDK -// -// Created by Moritz Haarmann on 26.02.14. -// -// +/* + * Author: Moritz Haarmann + * + * Copyright (c) 2012-2014 HockeyApp, Bit Stadium GmbH. + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ #import "BITImageAnnotation.h" diff --git a/Classes/BITBlurImageAnnotation.m b/Classes/BITBlurImageAnnotation.m index 983c49b891..4760229d9f 100644 --- a/Classes/BITBlurImageAnnotation.m +++ b/Classes/BITBlurImageAnnotation.m @@ -1,10 +1,30 @@ -// -// BITBlurImageAnnotation.m -// HockeySDK -// -// Created by Moritz Haarmann on 26.02.14. -// -// +/* + * Author: Moritz Haarmann + * + * Copyright (c) 2012-2014 HockeyApp, Bit Stadium GmbH. + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ #import "BITBlurImageAnnotation.h" diff --git a/Classes/BITImageAnnotation.h b/Classes/BITImageAnnotation.h index 24d312e574..8f1354a617 100644 --- a/Classes/BITImageAnnotation.h +++ b/Classes/BITImageAnnotation.h @@ -1,10 +1,30 @@ -// -// BITImageAnnotation.h -// HockeySDK -// -// Created by Moritz Haarmann on 24.02.14. -// -// +/* + * Author: Moritz Haarmann + * + * Copyright (c) 2012-2014 HockeyApp, Bit Stadium GmbH. + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ #import diff --git a/Classes/BITImageAnnotation.m b/Classes/BITImageAnnotation.m index 473889a3ba..428e8b1163 100644 --- a/Classes/BITImageAnnotation.m +++ b/Classes/BITImageAnnotation.m @@ -1,10 +1,30 @@ -// -// BITImageAnnotation.m -// HockeySDK -// -// Created by Moritz Haarmann on 24.02.14. -// -// +/* + * Author: Moritz Haarmann + * + * Copyright (c) 2012-2014 HockeyApp, Bit Stadium GmbH. + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ #import "BITImageAnnotation.h" diff --git a/Classes/BITImageAnnotationViewController.h b/Classes/BITImageAnnotationViewController.h index d333d612c2..026968ceb3 100644 --- a/Classes/BITImageAnnotationViewController.h +++ b/Classes/BITImageAnnotationViewController.h @@ -1,10 +1,30 @@ -// -// BITImageAnnotationViewController.h -// HockeySDK -// -// Created by Moritz Haarmann on 14.02.14. -// -// +/* + * Author: Moritz Haarmann + * + * Copyright (c) 2012-2014 HockeyApp, Bit Stadium GmbH. + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ #import diff --git a/Classes/BITImageAnnotationViewController.m b/Classes/BITImageAnnotationViewController.m index 987c23a371..e9420da0c4 100644 --- a/Classes/BITImageAnnotationViewController.m +++ b/Classes/BITImageAnnotationViewController.m @@ -1,10 +1,30 @@ -// -// BITImageAnnotationViewController.m -// HockeySDK -// -// Created by Moritz Haarmann on 14.02.14. -// -// +/* + * Author: Moritz Haarmann + * + * Copyright (c) 2012-2014 HockeyApp, Bit Stadium GmbH. + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ #import "BITImageAnnotationViewController.h" #import "BITImageAnnotation.h" diff --git a/Classes/BITRectangleImageAnnotation.h b/Classes/BITRectangleImageAnnotation.h index 0ba16c0c68..024957998a 100644 --- a/Classes/BITRectangleImageAnnotation.h +++ b/Classes/BITRectangleImageAnnotation.h @@ -1,10 +1,30 @@ -// -// BITRectangleImageAnnotation.h -// HockeySDK -// -// Created by Moritz Haarmann on 25.02.14. -// -// +/* + * Author: Moritz Haarmann + * + * Copyright (c) 2012-2014 HockeyApp, Bit Stadium GmbH. + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ #import "BITImageAnnotation.h" diff --git a/Classes/BITRectangleImageAnnotation.m b/Classes/BITRectangleImageAnnotation.m index 9b677cb360..b08f5d302e 100644 --- a/Classes/BITRectangleImageAnnotation.m +++ b/Classes/BITRectangleImageAnnotation.m @@ -1,10 +1,30 @@ -// -// BITRectangleImageAnnotation.m -// HockeySDK -// -// Created by Moritz Haarmann on 25.02.14. -// -// +/* + * Author: Moritz Haarmann + * + * Copyright (c) 2012-2014 HockeyApp, Bit Stadium GmbH. + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ #import "BITRectangleImageAnnotation.h" From 3338486c539126c72aca4b4271a2b5a136f3df0a Mon Sep 17 00:00:00 2001 From: moritz haarmann Date: Mon, 12 May 2014 12:36:52 +0200 Subject: [PATCH 84/92] + Reintegrated categorized methods into BITFeedbackMessageAttachment. --- Classes/BITFeedbackComposeViewController.m | 4 +- Classes/BITFeedbackListViewController.m | 1 - ...TFeedbackMessageAttachment+QLPreviewItem.h | 16 --- ...TFeedbackMessageAttachment+QLPreviewItem.m | 25 ----- Classes/BITFeedbackMessageAttachment.h | 3 +- Classes/BITFeedbackMessageAttachment.m | 99 +++++++++++-------- Support/HockeySDK.xcodeproj/project.pbxproj | 8 -- 7 files changed, 59 insertions(+), 97 deletions(-) delete mode 100644 Classes/BITFeedbackMessageAttachment+QLPreviewItem.h delete mode 100644 Classes/BITFeedbackMessageAttachment+QLPreviewItem.m diff --git a/Classes/BITFeedbackComposeViewController.m b/Classes/BITFeedbackComposeViewController.m index f1da11adb3..7a5dff7e25 100644 --- a/Classes/BITFeedbackComposeViewController.m +++ b/Classes/BITFeedbackComposeViewController.m @@ -273,9 +273,7 @@ -(void)refreshAttachmentScrollview { CGFloat scrollViewWidth = 0; - - CGPoint preservedOffset = self.attachmentScrollView.contentOffset; - + if (self.attachments.count){ scrollViewWidth = 100; } diff --git a/Classes/BITFeedbackListViewController.m b/Classes/BITFeedbackListViewController.m index ede1c4f8d7..a384bc0b2d 100644 --- a/Classes/BITFeedbackListViewController.m +++ b/Classes/BITFeedbackListViewController.m @@ -41,7 +41,6 @@ #import "BITFeedbackUserDataViewController.h" #import "BITFeedbackMessage.h" #import "BITFeedbackMessageAttachment.h" -#import "BITFeedbackMessageAttachment+QLPreviewItem.h" #import "BITAttributedLabel.h" #import "BITHockeyBaseManagerPrivate.h" diff --git a/Classes/BITFeedbackMessageAttachment+QLPreviewItem.h b/Classes/BITFeedbackMessageAttachment+QLPreviewItem.h deleted file mode 100644 index 5c05718ae1..0000000000 --- a/Classes/BITFeedbackMessageAttachment+QLPreviewItem.h +++ /dev/null @@ -1,16 +0,0 @@ -// -// BITFeedbackMessageAttachment+QLPreviewItem.h -// HockeySDK -// -// Created by Moritz Haarmann on 30.04.14. -// -// - -#import "BITFeedbackMessageAttachment.h" -#import - -@interface BITFeedbackMessageAttachment (QLPreviewItem) - - - -@end diff --git a/Classes/BITFeedbackMessageAttachment+QLPreviewItem.m b/Classes/BITFeedbackMessageAttachment+QLPreviewItem.m deleted file mode 100644 index 96b8a792bf..0000000000 --- a/Classes/BITFeedbackMessageAttachment+QLPreviewItem.m +++ /dev/null @@ -1,25 +0,0 @@ -// -// BITFeedbackMessageAttachment+QLPreviewItem.m -// HockeySDK -// -// Created by Moritz Haarmann on 30.04.14. -// -// - -#import "BITFeedbackMessageAttachment+QLPreviewItem.h" - -@implementation BITFeedbackMessageAttachment (QLPreviewItem) - -- (NSString *)previewItemTitle { - return self.originalFilename; -} - -- (NSURL *)previewItemURL { - if (self.localURL){ - return self.localURL; - } else { - return [NSURL fileURLWithPath:self.possibleFilename]; - } -} - -@end diff --git a/Classes/BITFeedbackMessageAttachment.h b/Classes/BITFeedbackMessageAttachment.h index 5162b24e3e..96adde8531 100644 --- a/Classes/BITFeedbackMessageAttachment.h +++ b/Classes/BITFeedbackMessageAttachment.h @@ -29,8 +29,9 @@ #import #import +#import -@interface BITFeedbackMessageAttachment : NSObject +@interface BITFeedbackMessageAttachment : NSObject @property (nonatomic, copy) NSNumber *id; @property (nonatomic, copy) NSString *originalFilename; diff --git a/Classes/BITFeedbackMessageAttachment.m b/Classes/BITFeedbackMessageAttachment.m index 45e4da3b9d..ac8551011d 100644 --- a/Classes/BITFeedbackMessageAttachment.m +++ b/Classes/BITFeedbackMessageAttachment.m @@ -175,57 +175,70 @@ [self.thumbnailRepresentations setObject:scaledTumbnail forKey:cacheKey]; } - } else { - UIImage *thumbnail = bit_imageToFitSize(image, size, YES) ; - - [self.thumbnailRepresentations setObject:thumbnail forKey:cacheKey]; - - } + } else { + UIImage *thumbnail = bit_imageToFitSize(image, size, YES) ; + + [self.thumbnailRepresentations setObject:thumbnail forKey:cacheKey]; + + } } - return self.thumbnailRepresentations[cacheKey]; - } + return self.thumbnailRepresentations[cacheKey]; +} + - #pragma mark - Persistence Helpers + +- (NSString *)possibleFilename { + NSArray* cachePathArray = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES); + NSString* cachePath = [cachePathArray lastObject]; + cachePath = [cachePath stringByAppendingPathComponent:kCacheFolderName]; - - (NSString *)possibleFilename { - NSArray* cachePathArray = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES); - NSString* cachePath = [cachePathArray lastObject]; - cachePath = [cachePath stringByAppendingPathComponent:kCacheFolderName]; - - BOOL isDirectory; - - if (![[NSFileManager defaultManager] fileExistsAtPath:cachePath isDirectory:&isDirectory]){ - [[NSFileManager defaultManager] createDirectoryAtPath:cachePath withIntermediateDirectories:YES attributes:nil error:nil]; - } - - NSString *uniqueString = bit_UUID(); - cachePath = [cachePath stringByAppendingPathComponent:uniqueString]; - - // File extension that suits the Content type. - - CFStringRef mimeType = (__bridge CFStringRef)self.contentType; - CFStringRef uti = UTTypeCreatePreferredIdentifierForTag(kUTTagClassMIMEType, mimeType, NULL); - CFStringRef extension = UTTypeCopyPreferredTagWithClass(uti, kUTTagClassFilenameExtension); - if (extension){ + BOOL isDirectory; + + if (![[NSFileManager defaultManager] fileExistsAtPath:cachePath isDirectory:&isDirectory]){ + [[NSFileManager defaultManager] createDirectoryAtPath:cachePath withIntermediateDirectories:YES attributes:nil error:nil]; + } + + NSString *uniqueString = bit_UUID(); + cachePath = [cachePath stringByAppendingPathComponent:uniqueString]; + + // File extension that suits the Content type. + + CFStringRef mimeType = (__bridge CFStringRef)self.contentType; + CFStringRef uti = UTTypeCreatePreferredIdentifierForTag(kUTTagClassMIMEType, mimeType, NULL); + CFStringRef extension = UTTypeCopyPreferredTagWithClass(uti, kUTTagClassFilenameExtension); + if (extension){ cachePath = [cachePath stringByAppendingPathExtension:(__bridge NSString *)(extension)]; - CFRelease(extension); - - } + CFRelease(extension); - CFRelease(uti); - - return cachePath; } - - (void)deleteContents { - if (self.filename){ - [[NSFileManager defaultManager] removeItemAtPath:self.filename error:nil]; - self.filename = nil; - } + CFRelease(uti); + + return cachePath; +} + +- (void)deleteContents { + if (self.filename){ + [[NSFileManager defaultManager] removeItemAtPath:self.filename error:nil]; + self.filename = nil; } - - - @end +} + +#pragma mark - QLPreviewItem + +- (NSString *)previewItemTitle { + return self.originalFilename; +} + +- (NSURL *)previewItemURL { + if (self.localURL){ + return self.localURL; + } else { + return [NSURL fileURLWithPath:self.possibleFilename]; + } +} + +@end diff --git a/Support/HockeySDK.xcodeproj/project.pbxproj b/Support/HockeySDK.xcodeproj/project.pbxproj index 365d97bfee..7094783bcf 100644 --- a/Support/HockeySDK.xcodeproj/project.pbxproj +++ b/Support/HockeySDK.xcodeproj/project.pbxproj @@ -153,8 +153,6 @@ 9782023918F81BFC00A98D8B /* Ok@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 9782022F18F81BFC00A98D8B /* Ok@2x.png */; }; 9782023A18F81BFC00A98D8B /* Rectangle.png in Resources */ = {isa = PBXBuildFile; fileRef = 9782023018F81BFC00A98D8B /* Rectangle.png */; }; 9782023B18F81BFC00A98D8B /* Rectangle@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 9782023118F81BFC00A98D8B /* Rectangle@2x.png */; }; - 97BD9BD21911085A0043FD59 /* BITFeedbackMessageAttachment+QLPreviewItem.h in Headers */ = {isa = PBXBuildFile; fileRef = 97BD9BD01911085A0043FD59 /* BITFeedbackMessageAttachment+QLPreviewItem.h */; }; - 97BD9BD31911085A0043FD59 /* BITFeedbackMessageAttachment+QLPreviewItem.m in Sources */ = {isa = PBXBuildFile; fileRef = 97BD9BD11911085A0043FD59 /* BITFeedbackMessageAttachment+QLPreviewItem.m */; }; 97BD9BD5191109730043FD59 /* QuickLook.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 97BD9BD4191109730043FD59 /* QuickLook.framework */; }; 97CC11F71917AB7C0028768F /* QuickLook.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 97BD9BD4191109730043FD59 /* QuickLook.framework */; }; 97CC11F91917C0310028768F /* MobileCoreServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 97CC11F81917C0310028768F /* MobileCoreServices.framework */; }; @@ -341,8 +339,6 @@ 9782022F18F81BFC00A98D8B /* Ok@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Ok@2x.png"; sourceTree = ""; }; 9782023018F81BFC00A98D8B /* Rectangle.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = Rectangle.png; sourceTree = ""; }; 9782023118F81BFC00A98D8B /* Rectangle@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Rectangle@2x.png"; sourceTree = ""; }; - 97BD9BD01911085A0043FD59 /* BITFeedbackMessageAttachment+QLPreviewItem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "BITFeedbackMessageAttachment+QLPreviewItem.h"; sourceTree = ""; }; - 97BD9BD11911085A0043FD59 /* BITFeedbackMessageAttachment+QLPreviewItem.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "BITFeedbackMessageAttachment+QLPreviewItem.m"; sourceTree = ""; }; 97BD9BD4191109730043FD59 /* QuickLook.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuickLook.framework; path = System/Library/Frameworks/QuickLook.framework; sourceTree = SDKROOT; }; 97CC11F81917C0310028768F /* MobileCoreServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MobileCoreServices.framework; path = System/Library/Frameworks/MobileCoreServices.framework; sourceTree = SDKROOT; }; 97F0F9FB18ABAECD00EF50AA /* iconCamera.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = iconCamera.png; sourceTree = ""; }; @@ -535,8 +531,6 @@ 1E49A4341612223B00463151 /* BITFeedbackManager.m */, E405266117A2AD300096359C /* BITFeedbackManagerDelegate.h */, 1E49A4351612223B00463151 /* BITFeedbackManagerPrivate.h */, - 97BD9BD01911085A0043FD59 /* BITFeedbackMessageAttachment+QLPreviewItem.h */, - 97BD9BD11911085A0043FD59 /* BITFeedbackMessageAttachment+QLPreviewItem.m */, ); name = Feedback; sourceTree = ""; @@ -739,7 +733,6 @@ 1E59559A15B6FDA500A03429 /* BITHockeyManager.h in Headers */, 1E5955FD15B7877B00A03429 /* BITHockeyManagerDelegate.h in Headers */, 1E754E5C1621FBB70070AB92 /* BITCrashManager.h in Headers */, - 97BD9BD21911085A0043FD59 /* BITFeedbackMessageAttachment+QLPreviewItem.h in Headers */, 1E754E5E1621FBB70070AB92 /* BITCrashManagerDelegate.h in Headers */, 1E49A4731612226D00463151 /* BITUpdateManager.h in Headers */, 1E49A4791612226D00463151 /* BITUpdateManagerDelegate.h in Headers */, @@ -993,7 +986,6 @@ files = ( 1E5954D315B6F24A00A03429 /* BITHockeyManager.m in Sources */, 1E49A43F1612223B00463151 /* BITFeedbackComposeViewController.m in Sources */, - 97BD9BD31911085A0043FD59 /* BITFeedbackMessageAttachment+QLPreviewItem.m in Sources */, E40E0B0D17DA1AFF005E38C1 /* BITHockeyAppClient.m in Sources */, 1E49A4451612223B00463151 /* BITFeedbackListViewCell.m in Sources */, 973EC8B818BCA8A200DBFFBB /* BITRectangleImageAnnotation.m in Sources */, From 0df0347eb946e8370f0463ce36bc6d5463379573 Mon Sep 17 00:00:00 2001 From: moritz haarmann Date: Wed, 21 May 2014 12:54:32 +0200 Subject: [PATCH 85/92] + Improved Loading display in ListView. --- Classes/BITActivityIndicatorButton.h | 15 ++++++ Classes/BITActivityIndicatorButton.m | 58 +++++++++++++++++++++ Classes/BITFeedbackListViewCell.m | 18 +++++-- Support/HockeySDK.xcodeproj/project.pbxproj | 8 +++ 4 files changed, 95 insertions(+), 4 deletions(-) create mode 100644 Classes/BITActivityIndicatorButton.h create mode 100644 Classes/BITActivityIndicatorButton.m diff --git a/Classes/BITActivityIndicatorButton.h b/Classes/BITActivityIndicatorButton.h new file mode 100644 index 0000000000..52f0206b0b --- /dev/null +++ b/Classes/BITActivityIndicatorButton.h @@ -0,0 +1,15 @@ +// +// BITActivityIndicatorButton.h +// HockeySDK +// +// Created by Moritz Haarmann on 21.05.14. +// +// + +#import + +@interface BITActivityIndicatorButton : UIButton + +- (void)setShowsActivityIndicator:(BOOL)showsIndicator; + +@end diff --git a/Classes/BITActivityIndicatorButton.m b/Classes/BITActivityIndicatorButton.m new file mode 100644 index 0000000000..4e30bb0f5f --- /dev/null +++ b/Classes/BITActivityIndicatorButton.m @@ -0,0 +1,58 @@ +// +// BITActivityIndicatorButton.m +// HockeySDK +// +// Created by Moritz Haarmann on 21.05.14. +// +// + +#import "BITActivityIndicatorButton.h" + +@interface BITActivityIndicatorButton() + +@property (nonatomic, strong) UIActivityIndicatorView *indicator; +@property (nonatomic) BOOL indicatorVisible; + +@end + +@implementation BITActivityIndicatorButton + +- (void)setShowsActivityIndicator:(BOOL)showsIndicator { + if (self.indicatorVisible == showsIndicator){ + return; + } + + if (!self.indicator){ + self.indicator = [[UIActivityIndicatorView alloc] initWithFrame:self.bounds]; + [self addSubview:self.indicator]; + [self.indicator setColor:[UIColor blackColor]]; + } + + self.indicatorVisible = showsIndicator; + + if (showsIndicator){ + [self.indicator startAnimating]; + self.indicator.alpha = 1; + self.layer.borderWidth = 1; + self.layer.borderColor = [UIColor lightGrayColor].CGColor; + self.layer.cornerRadius = 5; + self.imageView.image = nil; + } else { + [self.indicator stopAnimating]; + self.layer.cornerRadius = 0; + self.indicator.alpha = 0; + self.layer.borderWidth = 0; + + } + +} + +- (void)layoutSubviews { + [super layoutSubviews]; + + [self.indicator setFrame:self.bounds]; + +} + + +@end diff --git a/Classes/BITFeedbackListViewCell.m b/Classes/BITFeedbackListViewCell.m index 6e49f2f1a8..912a377042 100644 --- a/Classes/BITFeedbackListViewCell.m +++ b/Classes/BITFeedbackListViewCell.m @@ -26,10 +26,11 @@ * OTHER DEALINGS IN THE SOFTWARE. */ +#import "HockeySDKPrivate.h" #import "BITFeedbackListViewCell.h" -#import "HockeySDKPrivate.h" #import "BITFeedbackMessageAttachment.h" +#import "BITActivityIndicatorButton.h" #define BACKGROUNDCOLOR_DEFAULT BIT_RGBCOLOR(245, 245, 245) #define BACKGROUNDCOLOR_ALTERNATE BIT_RGBCOLOR(235, 235, 235) @@ -196,13 +197,22 @@ [self.attachmentViews removeAllObjects]; for (BITFeedbackMessageAttachment *attachment in attachments){ - UIButton *imageView = [UIButton buttonWithType:UIButtonTypeCustom]; - [imageView setImage:[attachment thumbnailWithSize:CGSizeMake(ATTACHMENT_SIZE, ATTACHMENT_SIZE)] forState:UIControlStateNormal]; + BITActivityIndicatorButton *imageView = [BITActivityIndicatorButton buttonWithType:UIButtonTypeCustom]; + + if (attachment.localURL){ + [imageView setImage:[attachment thumbnailWithSize:CGSizeMake(ATTACHMENT_SIZE, ATTACHMENT_SIZE)] forState:UIControlStateNormal]; + [imageView setShowsActivityIndicator:NO]; + } else { + [imageView setImage:nil forState:UIControlStateNormal]; + [imageView setShowsActivityIndicator:YES]; + } [imageView setContentMode:UIViewContentModeScaleAspectFit]; [imageView addTarget:self action:@selector(imageButtonPressed:) forControlEvents:UIControlEventTouchUpInside]; [self.attachmentViews addObject:imageView]; [self addSubview:imageView]; + + } } @@ -276,7 +286,7 @@ CGFloat attachmentsPerRow = floorf(self.frame.size.width / (FRAME_SIDE_BORDER + ATTACHMENT_SIZE)); - for ( UIButton *imageButton in self.attachmentViews){ + for ( BITActivityIndicatorButton *imageButton in self.attachmentViews){ imageButton.contentMode = UIViewContentModeScaleAspectFit; imageButton.imageView.contentMode = UIViewContentModeScaleAspectFill; diff --git a/Support/HockeySDK.xcodeproj/project.pbxproj b/Support/HockeySDK.xcodeproj/project.pbxproj index 7094783bcf..802fe2d4ad 100644 --- a/Support/HockeySDK.xcodeproj/project.pbxproj +++ b/Support/HockeySDK.xcodeproj/project.pbxproj @@ -143,6 +143,8 @@ 9760F6C418BB4D2D00959B93 /* AssetsLibrary.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9760F6C318BB4D2D00959B93 /* AssetsLibrary.framework */; }; 9760F6CF18BB685600959B93 /* BITImageAnnotation.h in Headers */ = {isa = PBXBuildFile; fileRef = 9760F6CD18BB685600959B93 /* BITImageAnnotation.h */; }; 9760F6D018BB685600959B93 /* BITImageAnnotation.m in Sources */ = {isa = PBXBuildFile; fileRef = 9760F6CE18BB685600959B93 /* BITImageAnnotation.m */; }; + 9774BCFF192CB20A00085EB5 /* BITActivityIndicatorButton.h in Headers */ = {isa = PBXBuildFile; fileRef = 9774BCFD192CB20A00085EB5 /* BITActivityIndicatorButton.h */; }; + 9774BD00192CB20A00085EB5 /* BITActivityIndicatorButton.m in Sources */ = {isa = PBXBuildFile; fileRef = 9774BCFE192CB20A00085EB5 /* BITActivityIndicatorButton.m */; }; 9782023218F81BFC00A98D8B /* Arrow.png in Resources */ = {isa = PBXBuildFile; fileRef = 9782022818F81BFC00A98D8B /* Arrow.png */; }; 9782023318F81BFC00A98D8B /* Arrow@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 9782022918F81BFC00A98D8B /* Arrow@2x.png */; }; 9782023418F81BFC00A98D8B /* Blur.png in Resources */ = {isa = PBXBuildFile; fileRef = 9782022A18F81BFC00A98D8B /* Blur.png */; }; @@ -329,6 +331,8 @@ 9760F6C318BB4D2D00959B93 /* AssetsLibrary.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AssetsLibrary.framework; path = System/Library/Frameworks/AssetsLibrary.framework; sourceTree = SDKROOT; }; 9760F6CD18BB685600959B93 /* BITImageAnnotation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BITImageAnnotation.h; sourceTree = ""; }; 9760F6CE18BB685600959B93 /* BITImageAnnotation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BITImageAnnotation.m; sourceTree = ""; }; + 9774BCFD192CB20A00085EB5 /* BITActivityIndicatorButton.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BITActivityIndicatorButton.h; sourceTree = ""; }; + 9774BCFE192CB20A00085EB5 /* BITActivityIndicatorButton.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BITActivityIndicatorButton.m; sourceTree = ""; }; 9782022818F81BFC00A98D8B /* Arrow.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = Arrow.png; sourceTree = ""; }; 9782022918F81BFC00A98D8B /* Arrow@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Arrow@2x.png"; sourceTree = ""; }; 9782022A18F81BFC00A98D8B /* Blur.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = Blur.png; sourceTree = ""; }; @@ -531,6 +535,8 @@ 1E49A4341612223B00463151 /* BITFeedbackManager.m */, E405266117A2AD300096359C /* BITFeedbackManagerDelegate.h */, 1E49A4351612223B00463151 /* BITFeedbackManagerPrivate.h */, + 9774BCFD192CB20A00085EB5 /* BITActivityIndicatorButton.h */, + 9774BCFE192CB20A00085EB5 /* BITActivityIndicatorButton.m */, ); name = Feedback; sourceTree = ""; @@ -758,6 +764,7 @@ 1E49A47C1612226D00463151 /* BITUpdateManagerPrivate.h in Headers */, 1E49A4851612226D00463151 /* BITUpdateViewControllerPrivate.h in Headers */, 1E49A4B5161222B900463151 /* BITHockeyBaseManagerPrivate.h in Headers */, + 9774BCFF192CB20A00085EB5 /* BITActivityIndicatorButton.h in Headers */, E4933E8017B66CDA00B11ACC /* BITHTTPOperation.h in Headers */, 1E49A4BE161222B900463151 /* BITHockeyHelper.h in Headers */, 973EC8BB18BDE29800DBFFBB /* BITArrowImageAnnotation.h in Headers */, @@ -998,6 +1005,7 @@ 1E49A4701612226D00463151 /* BITAppVersionMetaInfo.m in Sources */, 1E49A4761612226D00463151 /* BITUpdateManager.m in Sources */, 1E49A4C1161222B900463151 /* BITHockeyHelper.m in Sources */, + 9774BD00192CB20A00085EB5 /* BITActivityIndicatorButton.m in Sources */, 1E49A4821612226D00463151 /* BITUpdateViewController.m in Sources */, E4B4DB7E17B435550099C67F /* BITAuthenticationViewController.m in Sources */, 1E49A4B2161222B900463151 /* BITHockeyBaseManager.m in Sources */, From ef6d5224da5b7f942f2be17a13c1fd63eb46fdd2 Mon Sep 17 00:00:00 2001 From: moritz haarmann Date: Wed, 21 May 2014 15:10:21 +0200 Subject: [PATCH 86/92] + Some corrections regarding the display and loading of attachments. - Attachments are no longer displayed in a cell if the system indicates that they cannot be displayed. - Fixed a bug that prevented devices running iOS6 from displaying attachments in the Overview List. --- Classes/BITFeedbackListViewCell.m | 10 +++++++--- Classes/BITFeedbackListViewController.m | 18 +++++++++--------- Classes/BITFeedbackMessage.h | 6 ++++++ Classes/BITFeedbackMessage.m | 13 +++++++++++++ 4 files changed, 35 insertions(+), 12 deletions(-) diff --git a/Classes/BITFeedbackListViewCell.m b/Classes/BITFeedbackListViewCell.m index 912a377042..c0526376cd 100644 --- a/Classes/BITFeedbackListViewCell.m +++ b/Classes/BITFeedbackListViewCell.m @@ -210,7 +210,7 @@ [imageView addTarget:self action:@selector(imageButtonPressed:) forControlEvents:UIControlEventTouchUpInside]; [self.attachmentViews addObject:imageView]; - [self addSubview:imageView]; + //[self addSubview:imageView]; } @@ -289,7 +289,7 @@ for ( BITActivityIndicatorButton *imageButton in self.attachmentViews){ imageButton.contentMode = UIViewContentModeScaleAspectFit; imageButton.imageView.contentMode = UIViewContentModeScaleAspectFill; - + if ( !_message.userMessage){ imageButton.frame = CGRectMake(FRAME_SIDE_BORDER + (FRAME_SIDE_BORDER + ATTACHMENT_SIZE) * (i%(int)attachmentsPerRow) , floor(i/attachmentsPerRow)*(FRAME_SIDE_BORDER + ATTACHMENT_SIZE) + baseOffsetOfText , ATTACHMENT_SIZE, ATTACHMENT_SIZE); } else { @@ -297,7 +297,11 @@ } if (!imageButton.superview){ - [self addSubview:imageButton]; + if (self.accessoryBackgroundView.superview){ + [self insertSubview:imageButton aboveSubview:self.accessoryBackgroundView]; + } else { + [self addSubview:imageButton]; + } } i++; diff --git a/Classes/BITFeedbackListViewController.m b/Classes/BITFeedbackListViewController.m index a384bc0b2d..c7595fa7e8 100644 --- a/Classes/BITFeedbackListViewController.m +++ b/Classes/BITFeedbackListViewController.m @@ -637,7 +637,7 @@ cell.labelText.delegate = self; cell.labelText.userInteractionEnabled = YES; cell.delegate = self; - [cell setAttachments:message.attachments]; + [cell setAttachments:message.previewableAttachments]; for (BITFeedbackMessageAttachment *attachment in message.attachments){ if (attachment.needsLoadingFromURL && !attachment.isLoading){ @@ -812,12 +812,7 @@ for (int i = 0; i) previewController: (QLPreviewController *) controller previewItemAtIndex: (NSInteger) index { if (index>=0){ + __weak QLPreviewController* blockController = controller; BITFeedbackMessageAttachment *attachment = self.cachedPreviewItems[index]; if (attachment.needsLoadingFromURL && !attachment.isLoading){ attachment.isLoading = YES; NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:attachment.sourceURL]]; [NSURLConnection sendAsynchronousRequest:request queue:self.thumbnailQueue completionHandler:^(NSURLResponse *response, NSData *responseData, NSError *err) { + attachment.isLoading = NO; if (responseData.length){ [attachment replaceData:responseData]; - [controller reloadData]; - [[BITHockeyManager sharedHockeyManager].feedbackManager saveMessages]; + [blockController reloadData]; + + [[BITHockeyManager sharedHockeyManager].feedbackManager saveMessages]; + } else { + [blockController reloadData]; } }]; return attachment; diff --git a/Classes/BITFeedbackMessage.h b/Classes/BITFeedbackMessage.h index 44ff0e0d7e..4587449d17 100644 --- a/Classes/BITFeedbackMessage.h +++ b/Classes/BITFeedbackMessage.h @@ -83,5 +83,11 @@ typedef NS_ENUM(NSInteger, BITFeedbackMessageStatus) { -(void)addAttachmentsObject:(BITFeedbackMessageAttachment *)object; +/** + * Returns an array of attachment objects that may be previewed on this device. + */ +- (NSArray *)previewableAttachments; + + @end diff --git a/Classes/BITFeedbackMessage.m b/Classes/BITFeedbackMessage.m index 6d2ab49c11..42da4e602f 100644 --- a/Classes/BITFeedbackMessage.m +++ b/Classes/BITFeedbackMessage.m @@ -90,6 +90,19 @@ [attachment deleteContents]; } } + +- (NSArray *)previewableAttachments { + NSMutableArray *returnArray = [NSMutableArray new]; + + for (BITFeedbackMessageAttachment *attachment in self.attachments){ + if ([QLPreviewController canPreviewItem:attachment ]){ + [returnArray addObject:attachment]; + } + } + + return returnArray; +} + -(void)addAttachmentsObject:(BITFeedbackMessageAttachment *)object{ if (!self.attachments){ self.attachments = [NSArray array]; From b48bb8fb12a920f62479cb3521f5a89590dde374 Mon Sep 17 00:00:00 2001 From: Andreas Linde Date: Sun, 25 May 2014 18:55:24 +0200 Subject: [PATCH 87/92] Cleanup and bugfixes - Remove not used code - Fixed some code style - use a subdirectory in the SDK caches folder, instead of creating another folder directly under caches - Height of feedback cells only considers attachments that actually can be previewed. - If an attachment has no source URL and no local URL, don't show it, not even a loading placeholder, don't consider to reserve space in the cell for it - refresh attachment cache when new data is loaded or one or all message(s) with attachments have been deleted locally - `feedbackObservationMode` is a property instead of a method --- Classes/BITActivityIndicatorButton.h | 32 ++++++-- Classes/BITActivityIndicatorButton.m | 32 ++++++-- Classes/BITCrashManager.m | 14 ++-- Classes/BITFeedbackListViewCell.h | 2 + Classes/BITFeedbackListViewCell.m | 47 ++++++----- Classes/BITFeedbackListViewController.m | 80 +++++++++++------- Classes/BITFeedbackManager.h | 47 +++++++---- Classes/BITFeedbackManager.m | 98 ++++++++-------------- Classes/BITFeedbackMessage.h | 20 +++-- Classes/BITFeedbackMessage.m | 6 +- Classes/BITFeedbackMessageAttachment.h | 1 - Classes/BITFeedbackMessageAttachment.m | 104 ++++++++++++++---------- 12 files changed, 272 insertions(+), 211 deletions(-) diff --git a/Classes/BITActivityIndicatorButton.h b/Classes/BITActivityIndicatorButton.h index 52f0206b0b..2c633222c9 100644 --- a/Classes/BITActivityIndicatorButton.h +++ b/Classes/BITActivityIndicatorButton.h @@ -1,10 +1,28 @@ -// -// BITActivityIndicatorButton.h -// HockeySDK -// -// Created by Moritz Haarmann on 21.05.14. -// -// +/* + * Copyright (c) 2012-2014 HockeyApp, Bit Stadium GmbH. + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ #import diff --git a/Classes/BITActivityIndicatorButton.m b/Classes/BITActivityIndicatorButton.m index 4e30bb0f5f..00966186f6 100644 --- a/Classes/BITActivityIndicatorButton.m +++ b/Classes/BITActivityIndicatorButton.m @@ -1,10 +1,28 @@ -// -// BITActivityIndicatorButton.m -// HockeySDK -// -// Created by Moritz Haarmann on 21.05.14. -// -// +/* + * Copyright (c) 2012-2014 HockeyApp, Bit Stadium GmbH. + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ #import "BITActivityIndicatorButton.h" diff --git a/Classes/BITCrashManager.m b/Classes/BITCrashManager.m index 18b3aaf270..ba8b200932 100644 --- a/Classes/BITCrashManager.m +++ b/Classes/BITCrashManager.m @@ -602,20 +602,22 @@ NSString *const kBITCrashManagerStatus = @"BITCrashManagerStatus"; if (_crashManagerStatus == BITCrashManagerStatusDisabled) return NO; if ([self.fileManager fileExistsAtPath:_crashesDir]) { - NSString *file = nil; NSError *error = NULL; - NSDirectoryEnumerator *dirEnum = [self.fileManager enumeratorAtPath: _crashesDir]; + NSArray *dirArray = [self.fileManager contentsOfDirectoryAtPath:_crashesDir error:&error]; - while ((file = [dirEnum nextObject])) { - NSDictionary *fileAttributes = [self.fileManager attributesOfItemAtPath:[_crashesDir stringByAppendingPathComponent:file] error:&error]; - if ([[fileAttributes objectForKey:NSFileSize] intValue] > 0 && + for (NSString *file in dirArray) { + NSString *filePath = [_crashesDir stringByAppendingPathComponent:file]; + + NSDictionary *fileAttributes = [self.fileManager attributesOfItemAtPath:filePath error:&error]; + if ([[fileAttributes objectForKey:NSFileType] isEqualToString:NSFileTypeRegular] && + [[fileAttributes objectForKey:NSFileSize] intValue] > 0 && ![file hasSuffix:@".DS_Store"] && ![file hasSuffix:@".analyzer"] && ![file hasSuffix:@".plist"] && ![file hasSuffix:@".data"] && ![file hasSuffix:@".meta"]) { - [_crashFiles addObject:[_crashesDir stringByAppendingPathComponent: file]]; + [_crashFiles addObject:filePath]; } } } diff --git a/Classes/BITFeedbackListViewCell.h b/Classes/BITFeedbackListViewCell.h index 8ce9c3fcd2..f146b8acf9 100644 --- a/Classes/BITFeedbackListViewCell.h +++ b/Classes/BITFeedbackListViewCell.h @@ -39,6 +39,7 @@ @end + /** * Cell style depending on the iOS version */ @@ -67,6 +68,7 @@ typedef NS_ENUM(NSUInteger, BITFeedbackListViewCellBackgroundStyle) { BITFeedbackListViewCellBackgroundStyleAlternate = 1 }; + @interface BITFeedbackListViewCell : UITableViewCell @property (nonatomic, strong) BITFeedbackMessage *message; diff --git a/Classes/BITFeedbackListViewCell.m b/Classes/BITFeedbackListViewCell.m index c0526376cd..367721ac39 100644 --- a/Classes/BITFeedbackListViewCell.m +++ b/Classes/BITFeedbackListViewCell.m @@ -152,7 +152,7 @@ CGFloat attachmentsPerRow = floorf(width / (FRAME_SIDE_BORDER + ATTACHMENT_SIZE)); - CGFloat calculatedHeight = baseHeight + (FRAME_TOP_BORDER + ATTACHMENT_SIZE) * ceil(message.attachments.count/attachmentsPerRow); + CGFloat calculatedHeight = baseHeight + (FRAME_TOP_BORDER + ATTACHMENT_SIZE) * ceil([message previewableAttachments].count / attachmentsPerRow); return ceil(calculatedHeight); } @@ -197,22 +197,21 @@ [self.attachmentViews removeAllObjects]; for (BITFeedbackMessageAttachment *attachment in attachments){ - BITActivityIndicatorButton *imageView = [BITActivityIndicatorButton buttonWithType:UIButtonTypeCustom]; - - if (attachment.localURL){ - [imageView setImage:[attachment thumbnailWithSize:CGSizeMake(ATTACHMENT_SIZE, ATTACHMENT_SIZE)] forState:UIControlStateNormal]; - [imageView setShowsActivityIndicator:NO]; - } else { - [imageView setImage:nil forState:UIControlStateNormal]; - [imageView setShowsActivityIndicator:YES]; + if (attachment.localURL || attachment.sourceURL) { + BITActivityIndicatorButton *imageView = [BITActivityIndicatorButton buttonWithType:UIButtonTypeCustom]; + + if (attachment.localURL){ + [imageView setImage:[attachment thumbnailWithSize:CGSizeMake(ATTACHMENT_SIZE, ATTACHMENT_SIZE)] forState:UIControlStateNormal]; + [imageView setShowsActivityIndicator:NO]; + } else { + [imageView setImage:nil forState:UIControlStateNormal]; + [imageView setShowsActivityIndicator:YES]; + } + [imageView setContentMode:UIViewContentModeScaleAspectFit]; + [imageView addTarget:self action:@selector(imageButtonPressed:) forControlEvents:UIControlEventTouchUpInside]; + + [self.attachmentViews addObject:imageView]; } - [imageView setContentMode:UIViewContentModeScaleAspectFit]; - [imageView addTarget:self action:@selector(imageButtonPressed:) forControlEvents:UIControlEventTouchUpInside]; - - [self.attachmentViews addObject:imageView]; - //[self addSubview:imageView]; - - } } @@ -286,18 +285,18 @@ CGFloat attachmentsPerRow = floorf(self.frame.size.width / (FRAME_SIDE_BORDER + ATTACHMENT_SIZE)); - for ( BITActivityIndicatorButton *imageButton in self.attachmentViews){ + for (BITActivityIndicatorButton *imageButton in self.attachmentViews) { imageButton.contentMode = UIViewContentModeScaleAspectFit; imageButton.imageView.contentMode = UIViewContentModeScaleAspectFill; - - if ( !_message.userMessage){ + + if (!_message.userMessage) { imageButton.frame = CGRectMake(FRAME_SIDE_BORDER + (FRAME_SIDE_BORDER + ATTACHMENT_SIZE) * (i%(int)attachmentsPerRow) , floor(i/attachmentsPerRow)*(FRAME_SIDE_BORDER + ATTACHMENT_SIZE) + baseOffsetOfText , ATTACHMENT_SIZE, ATTACHMENT_SIZE); } else { imageButton.frame = CGRectMake(self.frame.size.width - FRAME_SIDE_BORDER - ATTACHMENT_SIZE - ((FRAME_SIDE_BORDER + ATTACHMENT_SIZE) * (i%(int)attachmentsPerRow) ), floor(i/attachmentsPerRow)*(FRAME_SIDE_BORDER + ATTACHMENT_SIZE) + baseOffsetOfText , ATTACHMENT_SIZE, ATTACHMENT_SIZE); } - if (!imageButton.superview){ - if (self.accessoryBackgroundView.superview){ + if (!imageButton.superview) { + if (self.accessoryBackgroundView.superview) { [self insertSubview:imageButton aboveSubview:self.accessoryBackgroundView]; } else { [self addSubview:imageButton]; @@ -311,10 +310,10 @@ } - (void)imageButtonPressed:(id)sender { - if ([self.delegate respondsToSelector:@selector(listCell:didSelectAttachment:)]){ + if ([self.delegate respondsToSelector:@selector(listCell:didSelectAttachment:)]) { NSInteger index = [self.attachmentViews indexOfObject:sender]; - if (index != NSNotFound){ - BITFeedbackMessageAttachment *attachment = self.message.attachments[index]; + if (index != NSNotFound && [self.message previewableAttachments].count > index) { + BITFeedbackMessageAttachment *attachment = [self.message previewableAttachments][index]; [self.delegate listCell:self didSelectAttachment:attachment]; } } diff --git a/Classes/BITFeedbackListViewController.m b/Classes/BITFeedbackListViewController.m index c7595fa7e8..9397bf856a 100644 --- a/Classes/BITFeedbackListViewController.m +++ b/Classes/BITFeedbackListViewController.m @@ -105,7 +105,7 @@ - (void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self name:BITHockeyFeedbackMessagesLoadingStarted object:nil]; [[NSNotificationCenter defaultCenter] removeObserver:self name:BITHockeyFeedbackMessagesLoadingFinished object:nil]; - + [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(showDelayedUserDataViewController) object:nil]; } @@ -114,12 +114,12 @@ - (void)viewDidLoad { [super viewDidLoad]; - + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(startLoadingIndicator) name:BITHockeyFeedbackMessagesLoadingStarted object:nil]; - + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(updateList) name:BITHockeyFeedbackMessagesLoadingFinished @@ -135,13 +135,13 @@ [self.tableView setBackgroundColor:[UIColor colorWithRed:0.82 green:0.84 blue:0.84 alpha:1]]; [self.tableView setSeparatorColor:[UIColor colorWithRed:0.79 green:0.79 blue:0.79 alpha:1]]; } else { -// [self.tableView setBackgroundColor:[UIColor colorWithRed:1.0 green:1.0 blue:1.0 alpha:1]]; + // [self.tableView setBackgroundColor:[UIColor colorWithRed:1.0 green:1.0 blue:1.0 alpha:1]]; } if ([self.manager isPreiOS7Environment]) { self.view.backgroundColor = DEFAULT_BACKGROUNDCOLOR; } else { -// self.view.backgroundColor = DEFAULT_BACKGROUNDCOLOR_OS7; + // self.view.backgroundColor = DEFAULT_BACKGROUNDCOLOR_OS7; } id refreshClass = NSClassFromString(@"UIRefreshControl"); @@ -150,9 +150,9 @@ [self.refreshControl addTarget:self action:@selector(reloadList) forControlEvents:UIControlEventValueChanged]; } else { self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemRefresh - target:self - action:@selector(reloadList)]; - } + target:self + action:@selector(reloadList)]; + } } - (void)startLoadingIndicator { @@ -191,7 +191,9 @@ CGSize contentSize = self.tableView.contentSize; CGPoint contentOffset = self.tableView.contentOffset; + [self refreshPreviewItems]; [self.tableView reloadData]; + if (contentSize.height > 0 && self.tableView.contentSize.height > self.tableView.frame.size.height && self.tableView.contentSize.height > contentSize.height && @@ -199,7 +201,7 @@ [self.tableView setContentOffset:CGPointMake(contentOffset.x, self.tableView.contentSize.height - contentSize.height + contentOffset.y) animated:NO]; [self stopLoadingIndicator]; - + [self.tableView flashScrollIndicators]; } @@ -231,7 +233,7 @@ } else { [self.tableView reloadData]; } - + [super viewDidAppear:animated]; } @@ -272,6 +274,8 @@ - (void)deleteAllMessages { [_manager deleteAllMessages]; + [self refreshPreviewItems]; + [self.tableView reloadData]; } @@ -289,9 +293,9 @@ } else { UIAlertView *deleteAction = [[UIAlertView alloc] initWithTitle:BITHockeyLocalizedString(@"HockeyFeedbackListButonDeleteAllMessages") message:BITHockeyLocalizedString(@"HockeyFeedbackListDeleteAllTitle") - delegate:self - cancelButtonTitle:BITHockeyLocalizedString(@"HockeyFeedbackListDeleteAllCancel") - otherButtonTitles:BITHockeyLocalizedString(@"HockeyFeedbackListDeleteAllDelete"), nil]; + delegate:self + cancelButtonTitle:BITHockeyLocalizedString(@"HockeyFeedbackListDeleteAllCancel") + otherButtonTitles:BITHockeyLocalizedString(@"HockeyFeedbackListDeleteAllDelete"), nil]; [deleteAction setTag:0]; [deleteAction show]; @@ -495,7 +499,7 @@ if (!cell) { cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier]; - + cell.textLabel.font = [UIFont systemFontOfSize:14]; cell.textLabel.numberOfLines = 0; cell.accessoryType = UITableViewCellAccessoryNone; @@ -644,17 +648,18 @@ attachment.isLoading = YES; NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:attachment.sourceURL]]; [NSURLConnection sendAsynchronousRequest:request queue:self.thumbnailQueue completionHandler:^(NSURLResponse *response, NSData *responseData, NSError *err) { - if (responseData.length){ + if (responseData.length) { [attachment replaceData:responseData]; dispatch_async(dispatch_get_main_queue(), ^{ [self.tableView reloadData]; }); + [[BITHockeyManager sharedHockeyManager].feedbackManager saveMessages]; } }]; } } - + if ( [self.manager isPreiOS7Environment] || (![self.manager isPreiOS7Environment] && indexPath.row != 0) @@ -679,12 +684,19 @@ - (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath { if (editingStyle == UITableViewCellEditingStyleDelete) { + BITFeedbackMessage *message = [self.manager messageAtIndex:indexPath.row]; + BOOL messageHasAttachments = ([message attachments].count > 0); + if ([_manager deleteMessageAtIndex:indexPath.row]) { if ([_manager numberOfMessages] > 0) { [tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic]; } else { [tableView reloadData]; } + + if (messageHasAttachments) { + [self refreshPreviewItems]; + } } } } @@ -779,7 +791,7 @@ if (buttonIndex == actionSheet.cancelButtonIndex) { return; } - + if ([actionSheet tag] == 0) { if (buttonIndex == [actionSheet destructiveButtonIndex]) { [self deleteAllMessages]; @@ -794,60 +806,66 @@ } } + #pragma mark - ListViewCellDelegate - (void)listCell:(id)cell didSelectAttachment:(BITFeedbackMessageAttachment *)attachment { - QLPreviewController *previewController = [[QLPreviewController alloc] init]; - previewController.dataSource = self; - - [self presentViewController:previewController animated:YES completion:nil]; - if (self.cachedPreviewItems.count > [self.cachedPreviewItems indexOfObject:attachment]){ + QLPreviewController *previewController = [[QLPreviewController alloc] init]; + previewController.dataSource = self; + + [self presentViewController:previewController animated:YES completion:nil]; + + if (self.cachedPreviewItems.count > [self.cachedPreviewItems indexOfObject:attachment]) { [previewController setCurrentPreviewItemIndex:[self.cachedPreviewItems indexOfObject:attachment]]; } - } + - (void)refreshPreviewItems { self.cachedPreviewItems = nil; NSMutableArray *collectedAttachments = [NSMutableArray new]; - for (int i = 0; i) previewController: (QLPreviewController *) controller previewItemAtIndex: (NSInteger) index { - if (index>=0){ +- (id )previewController:(QLPreviewController *)controller previewItemAtIndex:(NSInteger)index { + if (index >= 0) { __weak QLPreviewController* blockController = controller; BITFeedbackMessageAttachment *attachment = self.cachedPreviewItems[index]; - if (attachment.needsLoadingFromURL && !attachment.isLoading){ + + if (attachment.needsLoadingFromURL && !attachment.isLoading) { attachment.isLoading = YES; NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:attachment.sourceURL]]; [NSURLConnection sendAsynchronousRequest:request queue:self.thumbnailQueue completionHandler:^(NSURLResponse *response, NSData *responseData, NSError *err) { attachment.isLoading = NO; - if (responseData.length){ + if (responseData.length) { [attachment replaceData:responseData]; [blockController reloadData]; - [[BITHockeyManager sharedHockeyManager].feedbackManager saveMessages]; + [[BITHockeyManager sharedHockeyManager].feedbackManager saveMessages]; } else { [blockController reloadData]; } }]; + return attachment; } else { return self.cachedPreviewItems[index]; } } + return nil; } diff --git a/Classes/BITFeedbackManager.h b/Classes/BITFeedbackManager.h index 01ad9a46a6..435a1f2ec2 100644 --- a/Classes/BITFeedbackManager.h +++ b/Classes/BITFeedbackManager.h @@ -61,20 +61,19 @@ typedef NS_ENUM(NSInteger, BITFeedbackUserDataElement) { }; /** - * Available modes for collecting automated feedback. + * Available modes for opening the feedback compose interface with a screenshot attached */ typedef NS_ENUM(NSInteger, BITFeedbackObservationMode) { /** - * No automatic feedback gathering. + * No SDK provided trigger is active. */ BITFeedbackObservationNone = 0, /** - * Feedback compose form will open once a screenshot is taken. + * Triggeres when the user takes a screenshot. Requires iOS 7 or later! */ BITFeedbackObservationModeOnScreenshot = 1, /** - * Feedback compose will open with a generated screenshot if the screen is tapped - * three fingers for three seconds. + * Triggers when the user tapps with three fingers for three seconds on the screen. */ BITFeedbackObservationModeThreeFingerTap = 2 }; @@ -212,6 +211,26 @@ typedef NS_ENUM(NSInteger, BITFeedbackObservationMode) { @property (nonatomic, readwrite) BOOL showAlertOnIncomingMessages; +/** + Define the trigger that opens the feedback composer and attaches a screenshot + + The following modes are available: + + - `BITFeedbackObservationNone`: No SDK based trigger is active. You can implement your + own trigger and then call `[[BITHockeyManager sharedHockeyManager].feedbackManager showFeedbackComposeViewWithGeneratedScreenshot];` to handle your custom events + that should trigger this. + - `BITFeedbackObservationModeOnScreenshot`: Triggeres when the user takes a screenshot. + Requires iOS 7 or later! + - `BITFeedbackObservationModeThreeFingerTap`: Triggers when the user tapps with three fingers + for three seconds on the screen. + + Default is `BITFeedbackObservationNone` + + @see showFeedbackComposeViewWithGeneratedScreenshot + */ +@property (nonatomic, readwrite) BITFeedbackObservationMode feedbackObservationMode; + + ///----------------------------------------------------------------------------- /// @name User Interface ///----------------------------------------------------------------------------- @@ -269,8 +288,13 @@ typedef NS_ENUM(NSInteger, BITFeedbackObservationMode) { - (void)showFeedbackComposeViewWithPreparedItems:(NSArray *)items; /** - Present the modal feedback compose message user interface with a screenshot that is taken - at the time of calling this method. + Presents a modal feedback compose interface with a screenshot attached which is taken at the time of calling this method. + + This should be used when your own trigger fires. The following code should be used: + + [[BITHockeyManager sharedHockeyManager].feedbackManager showFeedbackComposeViewWithGeneratedScreenshot]; + + @see feedbackObservationMode */ - (void)showFeedbackComposeViewWithGeneratedScreenshot; @@ -295,14 +319,5 @@ typedef NS_ENUM(NSInteger, BITFeedbackObservationMode) { */ - (BITFeedbackComposeViewController *)feedbackComposeViewController; -/** - Set the so-called feedback observation mode. Depending on the chosen mode, - the feedback manager will automatically launch once the event has been detected. - - You can choose from the modes in BITFeedbackObservationMode. The default mode is - BITFeedbackObservationNone. - */ -- (void)setFeedbackObservationMode:(BITFeedbackObservationMode)mode; - @end diff --git a/Classes/BITFeedbackManager.m b/Classes/BITFeedbackManager.m index 5c89cf1cbd..056266fc23 100644 --- a/Classes/BITFeedbackManager.m +++ b/Classes/BITFeedbackManager.m @@ -740,7 +740,7 @@ message.id = [(NSDictionary *)objMessage objectForKey:@"id"]; message.status = BITFeedbackMessageStatusUnread; - for (NSDictionary *attachmentData in objMessage[@"attachments"]){ + for (NSDictionary *attachmentData in objMessage[@"attachments"]) { BITFeedbackMessageAttachment *newAttachment = [BITFeedbackMessageAttachment new]; newAttachment.originalFilename = attachmentData[@"file_name"]; newAttachment.id = attachmentData[@"id"]; @@ -805,45 +805,11 @@ [self markSendInProgressMessagesAsPending]; } - // we'll load the images on demand. - //[self synchronizeMissingAttachments]; - [self saveMessages]; return; } -/** - Load all attachments without any local data to have them available. - */ --(BOOL)synchronizeMissingAttachments { - // Extract all Attachments. - NSMutableArray *allAttachments = [NSMutableArray new]; - for (int i = 0; i < [self numberOfMessages]; i++){ - BITFeedbackMessage *message = [self messageAtIndex:i]; - for (BITFeedbackMessageAttachment *attachment in message.attachments){ - if (attachment.needsLoadingFromURL){ - [allAttachments addObject:attachment]; - } - } - } - - for (BITFeedbackMessageAttachment *attachment in allAttachments){ - // we will just update the objects here and perform a save after each successful load operation. - - NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:attachment.sourceURL]]; - __weak BITFeedbackManager *weakSelf = self; - [NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *responseData, NSError *err) { - if (responseData.length){ - [attachment replaceData:responseData]; - [weakSelf saveMessages]; - - } - }]; - - } - return NO; -} - (void)sendNetworkRequestWithHTTPMethod:(NSString *)httpMethod withMessage:(BITFeedbackMessage *)message completionHandler:(void (^)(NSError *err))completionHandler { NSString *boundary = @"----FOO"; @@ -1092,37 +1058,41 @@ #pragma mark - Observation Handling --(void)setFeedbackObservationMode:(BITFeedbackObservationMode)mode { - if (mode == BITFeedbackObservationModeOnScreenshot){ - if (NSFoundationVersionNumber > NSFoundationVersionNumber_iOS_6_1){ - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(screenshotNotificationReceived:) name:UIApplicationUserDidTakeScreenshotNotification object:nil]; - } else { - BITHockeyLog("Not enabling Screenshot notifications: iOS6.1 and lower is not supported."); - } +- (void)setFeedbackObservationMode:(BITFeedbackObservationMode)feedbackObservationMode { + if (feedbackObservationMode != _feedbackObservationMode) { + _feedbackObservationMode = feedbackObservationMode; - self.screenshotNotificationEnabled = YES; - - if (self.tapRecognizer){ - [[[UIApplication sharedApplication] keyWindow] removeGestureRecognizer:self.tapRecognizer]; - self.tapRecognizer = nil; - } - } - - if (mode == BITFeedbackObservationModeThreeFingerTap){ - if (!self.tapRecognizer){ - self.tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(screenshotTripleTap:)]; - self.tapRecognizer.numberOfTouchesRequired = 3; - self.tapRecognizer.delegate = self; - - dispatch_async(dispatch_get_main_queue(), ^{ - [[UIApplication sharedApplication].keyWindow addGestureRecognizer:self.tapRecognizer]; - }); - } - - if (self.screenshotNotificationEnabled){ + if (feedbackObservationMode == BITFeedbackObservationModeOnScreenshot){ if (NSFoundationVersionNumber > NSFoundationVersionNumber_iOS_6_1){ - [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationUserDidTakeScreenshotNotification object:nil]; - self.screenshotNotificationEnabled = NO; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(screenshotNotificationReceived:) name:UIApplicationUserDidTakeScreenshotNotification object:nil]; + } else { + BITHockeyLog("WARNING: BITFeedbackObservationModeOnScreenshot requires iOS 7 or later."); + } + + self.screenshotNotificationEnabled = YES; + + if (self.tapRecognizer){ + [[[UIApplication sharedApplication] keyWindow] removeGestureRecognizer:self.tapRecognizer]; + self.tapRecognizer = nil; + } + } + + if (feedbackObservationMode == BITFeedbackObservationModeThreeFingerTap){ + if (!self.tapRecognizer){ + self.tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(screenshotTripleTap:)]; + self.tapRecognizer.numberOfTouchesRequired = 3; + self.tapRecognizer.delegate = self; + + dispatch_async(dispatch_get_main_queue(), ^{ + [[UIApplication sharedApplication].keyWindow addGestureRecognizer:self.tapRecognizer]; + }); + } + + if (self.screenshotNotificationEnabled){ + if (NSFoundationVersionNumber > NSFoundationVersionNumber_iOS_6_1){ + [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationUserDidTakeScreenshotNotification object:nil]; + self.screenshotNotificationEnabled = NO; + } } } } diff --git a/Classes/BITFeedbackMessage.h b/Classes/BITFeedbackMessage.h index 4587449d17..908e4fc8fd 100644 --- a/Classes/BITFeedbackMessage.h +++ b/Classes/BITFeedbackMessage.h @@ -76,18 +76,24 @@ typedef NS_ENUM(NSInteger, BITFeedbackMessageStatus) { @property (nonatomic) BOOL userMessage; /** - * This method must be called before a feedback message is deleted. It handles the - * deletion of any data stored on the device in association with the feedback message. + Delete local cached attachment data + + @warning This method must be called before a feedback message is deleted. */ --(void)deleteContents; +- (void)deleteContents; +/** + Add an attachment to a message + + @param object BITFeedbackMessageAttachment instance representing the attachment that should be added + */ -(void)addAttachmentsObject:(BITFeedbackMessageAttachment *)object; /** - * Returns an array of attachment objects that may be previewed on this device. - */ + Return the attachments that can be viewed + + @return NSArray containing the attachment objects that can be previewed + */ - (NSArray *)previewableAttachments; - - @end diff --git a/Classes/BITFeedbackMessage.m b/Classes/BITFeedbackMessage.m index 42da4e602f..7f8d60dea1 100644 --- a/Classes/BITFeedbackMessage.m +++ b/Classes/BITFeedbackMessage.m @@ -35,7 +35,7 @@ #pragma mark - NSObject -- (id) init { +- (instancetype) init { if ((self = [super init])) { _text = nil; _userID = nil; @@ -67,8 +67,8 @@ [encoder encodeObject:self.token forKey:@"token"]; } -- (id)initWithCoder:(NSCoder *)decoder { - if ((self = [super init])) { +- (instancetype)initWithCoder:(NSCoder *)decoder { + if ((self = [self init])) { self.text = [decoder decodeObjectForKey:@"text"]; self.userID = [decoder decodeObjectForKey:@"userID"]; self.name = [decoder decodeObjectForKey:@"name"]; diff --git a/Classes/BITFeedbackMessageAttachment.h b/Classes/BITFeedbackMessageAttachment.h index 96adde8531..dbb398b1da 100644 --- a/Classes/BITFeedbackMessageAttachment.h +++ b/Classes/BITFeedbackMessageAttachment.h @@ -2,7 +2,6 @@ * Author: Moritz Haarmann * * Copyright (c) 2012-2014 HockeyApp, Bit Stadium GmbH. - * Copyright (c) 2011 Andreas Linde & Kent Sutherland. * All rights reserved. * * Permission is hereby granted, free of charge, to any person diff --git a/Classes/BITFeedbackMessageAttachment.m b/Classes/BITFeedbackMessageAttachment.m index ac8551011d..1315e702f8 100644 --- a/Classes/BITFeedbackMessageAttachment.m +++ b/Classes/BITFeedbackMessageAttachment.m @@ -32,7 +32,7 @@ #import "HockeySDKPrivate.h" #import -#define kCacheFolderName @"hockey_attachments" +#define kCacheFolderName @"attachments" @interface BITFeedbackMessageAttachment() @@ -43,47 +43,63 @@ @end -@implementation BITFeedbackMessageAttachment +@implementation BITFeedbackMessageAttachment { + NSString *_tempFilename; + + NSString *_cachePath; + + NSFileManager *_fm; +} + + (BITFeedbackMessageAttachment *)attachmentWithData:(NSData *)data contentType:(NSString *)contentType { static NSDateFormatter *formatter; - if(!formatter){ + if(!formatter) { formatter = [NSDateFormatter new]; formatter.dateStyle = NSDateFormatterShortStyle; formatter.timeStyle = NSDateFormatterShortStyle; - } BITFeedbackMessageAttachment *newAttachment = [BITFeedbackMessageAttachment new]; newAttachment.contentType = contentType; newAttachment.data = data; newAttachment.originalFilename = [NSString stringWithFormat:@"Attachment: %@", [formatter stringFromDate:[NSDate date]]]; + return newAttachment; } --(id)init { - self = [super init]; - if (self){ +- (instancetype)init { + if ((self = [super init])) { self.isLoading = NO; self.thumbnailRepresentations = [NSMutableDictionary new]; + + _fm = [[NSFileManager alloc] init]; + _cachePath = [bit_settingsDir() stringByAppendingPathComponent:kCacheFolderName]; + + BOOL isDirectory; + + if (![_fm fileExistsAtPath:_cachePath isDirectory:&isDirectory]){ + [_fm createDirectoryAtPath:_cachePath withIntermediateDirectories:YES attributes:nil error:nil]; + } + } return self; } --(void)setData:(NSData *)data { +- (void)setData:(NSData *)data { self->_internalData = data; self.filename = [self possibleFilename]; [self->_internalData writeToFile:self.filename atomically:NO]; } --(NSData *)data { - if (!self->_internalData && self.filename){ +- (NSData *)data { + if (!self->_internalData && self.filename) { self.internalData = [NSData dataWithContentsOfFile:self.filename]; } - if (self.internalData){ + if (self.internalData) { return self.internalData; } @@ -95,8 +111,8 @@ self.thumbnailRepresentations = [NSMutableDictionary new]; } --(BOOL)needsLoadingFromURL { - return (self.sourceURL && ![[NSFileManager defaultManager] fileExistsAtPath:self.localURL.absoluteString]); +- (BOOL)needsLoadingFromURL { + return (self.sourceURL && ![_fm fileExistsAtPath:[self.localURL path]]); } - (BOOL)isImage { @@ -104,12 +120,14 @@ } - (NSURL *)localURL { - if (self.filename){ + if (self.filename && [_fm fileExistsAtPath:self.filename]) { return [NSURL fileURLWithPath:self.filename]; - } else - { return nil;} + } + + return nil; } + #pragma mark NSCoding - (void)encodeWithCoder:(NSCoder *)aCoder { @@ -117,29 +135,25 @@ [aCoder encodeObject:self.filename forKey:@"filename"]; [aCoder encodeObject:self.originalFilename forKey:@"originalFilename"]; [aCoder encodeObject:self.sourceURL forKey:@"url"]; - - } -- (id)initWithCoder:(NSCoder *)aDecoder { - self = [super init]; - - if (self){ +- (instancetype)initWithCoder:(NSCoder *)aDecoder { + if ((self = [self init])) { self.contentType = [aDecoder decodeObjectForKey:@"contentType"]; self.filename = [aDecoder decodeObjectForKey:@"filename"]; self.thumbnailRepresentations = [NSMutableDictionary new]; self.originalFilename = [aDecoder decodeObjectForKey:@"originalFilename"]; self.sourceURL = [aDecoder decodeObjectForKey:@"url"]; - } return self; } + #pragma mark - Thubmnails / Image Representation - (UIImage *)imageRepresentation { - if ([self.contentType rangeOfString:@"image"].location != NSNotFound && self.filename ){ + if ([self.contentType rangeOfString:@"image"].location != NSNotFound && self.filename ) { return [UIImage imageWithData:self.data]; } else { // Create a Icon .. @@ -157,21 +171,22 @@ - (UIImage *)thumbnailWithSize:(CGSize)size { id cacheKey = [NSValue valueWithCGSize:size]; - if (!self.thumbnailRepresentations[cacheKey]){ + if (!self.thumbnailRepresentations[cacheKey]) { UIImage *image = self.imageRepresentation; // consider the scale. - if (!image) + if (!image) { return nil; + } CGFloat scale = [UIScreen mainScreen].scale; - if (scale != image.scale){ + if (scale != image.scale) { CGSize scaledSize = CGSizeApplyAffineTransform(size, CGAffineTransformMakeScale(scale, scale)); UIImage *thumbnail = bit_imageToFitSize(image, scaledSize, YES) ; UIImage *scaledTumbnail = [UIImage imageWithCGImage:thumbnail.CGImage scale:scale orientation:thumbnail.imageOrientation]; - if (thumbnail){ + if (thumbnail) { [self.thumbnailRepresentations setObject:scaledTumbnail forKey:cacheKey]; } @@ -191,42 +206,36 @@ #pragma mark - Persistence Helpers - (NSString *)possibleFilename { - NSArray* cachePathArray = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES); - NSString* cachePath = [cachePathArray lastObject]; - cachePath = [cachePath stringByAppendingPathComponent:kCacheFolderName]; - - BOOL isDirectory; - - if (![[NSFileManager defaultManager] fileExistsAtPath:cachePath isDirectory:&isDirectory]){ - [[NSFileManager defaultManager] createDirectoryAtPath:cachePath withIntermediateDirectories:YES attributes:nil error:nil]; + if (_tempFilename) { + return _tempFilename; } NSString *uniqueString = bit_UUID(); - cachePath = [cachePath stringByAppendingPathComponent:uniqueString]; + _tempFilename = [_cachePath stringByAppendingPathComponent:uniqueString]; // File extension that suits the Content type. CFStringRef mimeType = (__bridge CFStringRef)self.contentType; CFStringRef uti = UTTypeCreatePreferredIdentifierForTag(kUTTagClassMIMEType, mimeType, NULL); CFStringRef extension = UTTypeCopyPreferredTagWithClass(uti, kUTTagClassFilenameExtension); - if (extension){ - cachePath = [cachePath stringByAppendingPathExtension:(__bridge NSString *)(extension)]; + if (extension) { + _tempFilename = [_tempFilename stringByAppendingPathExtension:(__bridge NSString *)(extension)]; CFRelease(extension); - } CFRelease(uti); - return cachePath; + return _tempFilename; } - (void)deleteContents { - if (self.filename){ - [[NSFileManager defaultManager] removeItemAtPath:self.filename error:nil]; + if (self.filename) { + [_fm removeItemAtPath:self.filename error:nil]; self.filename = nil; } } + #pragma mark - QLPreviewItem - (NSString *)previewItemTitle { @@ -236,9 +245,14 @@ - (NSURL *)previewItemURL { if (self.localURL){ return self.localURL; - } else { - return [NSURL fileURLWithPath:self.possibleFilename]; + } else if (self.sourceURL) { + NSString *filename = self.possibleFilename; + if (filename) { + return [NSURL fileURLWithPath:filename]; + } } + + return nil; } @end From 4f18945133e9647165e820032e5f938214d66a91 Mon Sep 17 00:00:00 2001 From: Andreas Linde Date: Sun, 25 May 2014 19:18:34 +0200 Subject: [PATCH 88/92] Add support for attaching NSData objects to a feedback compose view --- Classes/BITFeedbackComposeViewController.h | 5 +++-- Classes/BITFeedbackComposeViewController.m | 2 ++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/Classes/BITFeedbackComposeViewController.h b/Classes/BITFeedbackComposeViewController.h index 6bc132e66f..2ef4f84664 100644 --- a/Classes/BITFeedbackComposeViewController.h +++ b/Classes/BITFeedbackComposeViewController.h @@ -71,9 +71,10 @@ - NSString - NSURL - UIImage + - NSData - These are automatically concatenated to one text string, while any image attachments are - added as attachments to the feedback. + These are automatically concatenated to one text string, while any images and NSData + objects are added as attachments to the feedback. @param items Array of data objects to prefill the feedback text message. */ diff --git a/Classes/BITFeedbackComposeViewController.m b/Classes/BITFeedbackComposeViewController.m index 7a5dff7e25..014348e9b8 100644 --- a/Classes/BITFeedbackComposeViewController.m +++ b/Classes/BITFeedbackComposeViewController.m @@ -103,6 +103,8 @@ } else if ([item isKindOfClass:[UIImage class]]) { UIImage *image = item; [self.attachments addObject:[BITFeedbackMessageAttachment attachmentWithData:UIImageJPEGRepresentation(image, 0.7f) contentType:@"image/jpeg"]]; + } else if ([item isKindOfClass:[NSData class]]) { + [self.attachments addObject:[BITFeedbackMessageAttachment attachmentWithData:item contentType:@"'application/octet-stream'"]]; } else { BITHockeyLog(@"Unknown item type %@", item); } From 429af075bc68ddbc98041a9e5f263553c4c15d8e Mon Sep 17 00:00:00 2001 From: Andreas Linde Date: Sun, 25 May 2014 20:25:30 +0200 Subject: [PATCH 89/92] Improve handling of attachments in compose view - Only UIImage attachments can be edited - All others can only be deleted - Remove scrollview when the last attachment is removed (and avoid a crash if the user clicks on an attachment that doesn't exist any longer) - Add action sheet button texts to localization files (not localized!) - Delete local attachment files if compose view is cancelled - Only show image attachments in the compose view --- Classes/BITFeedbackComposeViewController.m | 71 +++++++++++++--------- Resources/de.lproj/HockeySDK.strings | 12 ++++ Resources/en.lproj/HockeySDK.strings | 12 ++++ Resources/es.lproj/HockeySDK.strings | 12 ++++ Resources/fr.lproj/HockeySDK.strings | 12 ++++ Resources/hr.lproj/HockeySDK.strings | 12 ++++ Resources/hu.lproj/HockeySDK.strings | 12 ++++ Resources/it.lproj/HockeySDK.strings | 12 ++++ Resources/ja.lproj/HockeySDK.strings | 12 ++++ Resources/nl.lproj/HockeySDK.strings | 12 ++++ Resources/pt-PT.lproj/HockeySDK.strings | 12 ++++ Resources/pt.lproj/HockeySDK.strings | 12 ++++ Resources/ro.lproj/HockeySDK.strings | 12 ++++ Resources/ru.lproj/HockeySDK.strings | 12 ++++ Resources/zh-Hans.lproj/HockeySDK.strings | 12 ++++ 15 files changed, 211 insertions(+), 28 deletions(-) diff --git a/Classes/BITFeedbackComposeViewController.m b/Classes/BITFeedbackComposeViewController.m index 014348e9b8..23b5cea7fe 100644 --- a/Classes/BITFeedbackComposeViewController.m +++ b/Classes/BITFeedbackComposeViewController.m @@ -58,6 +58,7 @@ @property (nonatomic, strong) NSString *text; @property (nonatomic, strong) NSMutableArray *attachments; +@property (nonatomic, strong) NSMutableArray *imageAttachments; @property (nonatomic, strong) UIView *textAccessoryView; @property (nonatomic) NSInteger selectedAttachmentIndex; @@ -81,6 +82,7 @@ _delegate = nil; _manager = [BITHockeyManager sharedHockeyManager].feedbackManager; _attachments = [NSMutableArray new]; + _imageAttachments = [NSMutableArray new]; _attachmentScrollViewImageViews = [NSMutableArray new]; self.tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(scrollViewTapped:)]; [self.attachmentScrollView addGestureRecognizer:self.tapRecognizer]; @@ -102,14 +104,15 @@ self.text = [(self.text ? self.text : @"") stringByAppendingFormat:@"%@%@", (self.text ? @" " : @""), [(NSURL *)item absoluteString]]; } else if ([item isKindOfClass:[UIImage class]]) { UIImage *image = item; - [self.attachments addObject:[BITFeedbackMessageAttachment attachmentWithData:UIImageJPEGRepresentation(image, 0.7f) contentType:@"image/jpeg"]]; + BITFeedbackMessageAttachment *attachment = [BITFeedbackMessageAttachment attachmentWithData:UIImageJPEGRepresentation(image, 0.7f) contentType:@"image/jpeg"]; + [self.attachments addObject:attachment]; + [self.imageAttachments addObject:attachment]; } else if ([item isKindOfClass:[NSData class]]) { [self.attachments addObject:[BITFeedbackMessageAttachment attachmentWithData:item contentType:@"'application/octet-stream'"]]; } else { BITHockeyLog(@"Unknown item type %@", item); } } - } @@ -192,7 +195,8 @@ self.textAccessoryView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, CGRectGetWidth(self.view.frame), 44)]; self.textAccessoryView.backgroundColor = [UIColor colorWithRed:0.9f green:0.9f blue:0.9f alpha:1.0f]; UIButton *addPhotoButton = [UIButton buttonWithType:UIButtonTypeCustom]; - [addPhotoButton setTitle:@"+ Add Photo" forState:UIControlStateNormal]; [addPhotoButton setTitleColor:[UIColor darkGrayColor] forState:UIControlStateNormal]; + [addPhotoButton setTitle:BITHockeyLocalizedString(@"HockeyFeedbackComposeAttachmentAddImage") forState:UIControlStateNormal]; + [addPhotoButton setTitleColor:[UIColor darkGrayColor] forState:UIControlStateNormal]; addPhotoButton.frame = CGRectMake(0, 0, CGRectGetWidth(self.view.frame), 44); [addPhotoButton addTarget:self action:@selector(addPhotoAction:) forControlEvents:UIControlEventTouchUpInside]; @@ -237,9 +241,6 @@ } [self updateBarButtonState]; - - - } - (void)viewDidAppear:(BOOL)animated { @@ -255,7 +256,6 @@ // Invoke delayed to fix iOS 7 iPad landscape bug, where this view will be moved if not called delayed [self.textView performSelector:@selector(becomeFirstResponder) withObject:nil afterDelay:0.0]; } - } - (void)viewWillDisappear:(BOOL)animated { @@ -276,7 +276,7 @@ -(void)refreshAttachmentScrollview { CGFloat scrollViewWidth = 0; - if (self.attachments.count){ + if (self.imageAttachments.count){ scrollViewWidth = 100; } @@ -286,18 +286,25 @@ BOOL alreadySetup = CGRectGetWidth(scrollViewFrame) > 0; - if (!alreadySetup){ + if (alreadySetup && self.imageAttachments.count == 0) { + textViewFrame.size.width += 100; + self.textView.frame = textViewFrame; + scrollViewFrame.size.width = 0; + self.attachmentScrollView.frame = scrollViewFrame; + return; + } + + if (!alreadySetup) { textViewFrame.size.width -= scrollViewWidth; scrollViewFrame = CGRectMake(CGRectGetMaxX(textViewFrame), self.view.frame.origin.y, scrollViewWidth, CGRectGetHeight(self.view.bounds)); self.textView.frame = textViewFrame; self.attachmentScrollView.frame = scrollViewFrame; self.attachmentScrollView.contentInset = self.textView.contentInset; - } - if (self.attachments.count > self.attachmentScrollViewImageViews.count){ - NSInteger numberOfViewsToCreate = self.attachments.count - self.attachmentScrollViewImageViews.count; - for (int i = 0;i self.attachmentScrollViewImageViews.count){ + NSInteger numberOfViewsToCreate = self.imageAttachments.count - self.attachmentScrollViewImageViews.count; + for (int i = 0; i 2){ + if (self.imageAttachments.count > 2){ self.textView.inputAccessoryView = nil; } else { self.textView.inputAccessoryView = self.textAccessoryView; @@ -372,6 +379,10 @@ #pragma mark - Actions - (void)dismissAction:(id)sender { + for (BITFeedbackMessageAttachment *attachment in self.attachments){ + [attachment deleteContents]; + } + [self dismissWithResult:BITFeedbackComposeResultCancelled]; } @@ -430,6 +441,7 @@ NSString *imageName = [imagePath lastPathComponent]; newAttachment.originalFilename = imageName; [self.attachments addObject:newAttachment]; + [self.imageAttachments addObject:newAttachment]; } [picker dismissViewControllerAnimated:YES completion:nil]; @@ -442,16 +454,19 @@ - (void)imageButtonAction:(UIButton *)sender { // determine the index of the feedback NSInteger index = [self.attachmentScrollViewImageViews indexOfObject:sender]; + self.selectedAttachmentIndex = index; - UIActionSheet * actionSheet = [[UIActionSheet alloc] initWithTitle: nil - delegate: self - cancelButtonTitle:@"Cancel" - destructiveButtonTitle: @"Delete Attachment" - otherButtonTitles: @"Edit Attachment", nil]; + + UIActionSheet *actionSheet = [[UIActionSheet alloc] initWithTitle: nil + delegate: self + cancelButtonTitle: BITHockeyLocalizedString(@"HockeyFeedbackComposeAttachmentCancel") + destructiveButtonTitle: BITHockeyLocalizedString(@"HockeyFeedbackComposeAttachmentDelete") + otherButtonTitles: BITHockeyLocalizedString(@"HockeyFeedbackComposeAttachmentEdit"), nil]; [actionSheet showFromRect: sender.frame inView: self.attachmentScrollView animated: YES]; } + #pragma mark - BITFeedbackUserDataDelegate - (void)userDataUpdateCancelled { @@ -485,14 +500,16 @@ [self updateBarButtonState]; } + #pragma mark - UIActionSheet Delegate - (void)actionSheet:(UIActionSheet *)actionSheet didDismissWithButtonIndex:(NSInteger)buttonIndex { if (buttonIndex == [actionSheet destructiveButtonIndex]){ if (self.selectedAttachmentIndex != NSNotFound){ - BITFeedbackMessageAttachment* attachment = [self.attachments objectAtIndex:self.selectedAttachmentIndex]; + BITFeedbackMessageAttachment *attachment = self.imageAttachments[self.selectedAttachmentIndex]; [attachment deleteContents]; // mandatory call to delete the files associatd. + [self.imageAttachments removeObject:attachment]; [self.attachments removeObject:attachment]; } self.selectedAttachmentIndex = NSNotFound; @@ -501,24 +518,22 @@ } else if(buttonIndex != [actionSheet cancelButtonIndex]){ if (self.selectedAttachmentIndex != NSNotFound){ - BITFeedbackMessageAttachment* attachment = [self.attachments objectAtIndex:self.selectedAttachmentIndex]; + BITFeedbackMessageAttachment *attachment = self.imageAttachments[self.selectedAttachmentIndex]; BITImageAnnotationViewController *annotationEditor = [[BITImageAnnotationViewController alloc ] init]; annotationEditor.delegate = self; UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:annotationEditor]; annotationEditor.image = attachment.imageRepresentation; [self presentViewController:navController animated:YES completion:nil]; } - } - - } + #pragma mark - Image Annotation Delegate - (void)annotationController:(BITImageAnnotationViewController *)annotationController didFinishWithImage:(UIImage *)image { if (self.selectedAttachmentIndex != NSNotFound){ - BITFeedbackMessageAttachment* attachment = [self.attachments objectAtIndex:self.selectedAttachmentIndex]; + BITFeedbackMessageAttachment *attachment = self.imageAttachments[self.selectedAttachmentIndex]; [attachment replaceData:UIImageJPEGRepresentation(image, 0.7f)]; } diff --git a/Resources/de.lproj/HockeySDK.strings b/Resources/de.lproj/HockeySDK.strings index 8a4b45b734..67a9dbcd19 100644 --- a/Resources/de.lproj/HockeySDK.strings +++ b/Resources/de.lproj/HockeySDK.strings @@ -212,6 +212,18 @@ /* Send button */ "HockeyFeedbackComposeSend" = "Senden"; +/* Add Image button for attachment actions */ +"HockeyFeedbackComposeAttachmentAddImage" = "+ Bild Hinzufügen"; + +/* Edit button for attachment actions */ +"HockeyFeedbackComposeAttachmentEdit" = "Anhang Bearbeiten"; + +/* Delete button for attachment actions */ +"HockeyFeedbackComposeAttachmentDelete" = "Anhang Löschen"; + +/* Cancel button for attachment actions */ +"HockeyFeedbackComposeAttachmentCancel" = "Abbrechen"; + /* Set User Data */ diff --git a/Resources/en.lproj/HockeySDK.strings b/Resources/en.lproj/HockeySDK.strings index 25229c843d..97cfa87843 100644 --- a/Resources/en.lproj/HockeySDK.strings +++ b/Resources/en.lproj/HockeySDK.strings @@ -212,6 +212,18 @@ /* Send button */ "HockeyFeedbackComposeSend" = "Send"; +/* Add Image button for attachment actions */ +"HockeyFeedbackComposeAttachmentAddImage" = "+ Add Image"; + +/* Edit button for attachment actions */ +"HockeyFeedbackComposeAttachmentEdit" = "Edit Attachment"; + +/* Delete button for attachment actions */ +"HockeyFeedbackComposeAttachmentDelete" = "Delete Attachment"; + +/* Cancel button for attachment actions */ +"HockeyFeedbackComposeAttachmentCancel" = "Cancel"; + /* Set User Data */ diff --git a/Resources/es.lproj/HockeySDK.strings b/Resources/es.lproj/HockeySDK.strings index 514fd282f8..935c961cda 100644 --- a/Resources/es.lproj/HockeySDK.strings +++ b/Resources/es.lproj/HockeySDK.strings @@ -212,6 +212,18 @@ /* Send button */ "HockeyFeedbackComposeSend" = "Enviar"; +/* Add Image button for attachment actions */ +"HockeyFeedbackComposeAttachmentAddImage" = "+ Add Image"; + +/* Edit button for attachment actions */ +"HockeyFeedbackComposeAttachmentEdit" = "Edit Attachment"; + +/* Delete button for attachment actions */ +"HockeyFeedbackComposeAttachmentDelete" = "Delete Attachment"; + +/* Cancel button for attachment actions */ +"HockeyFeedbackComposeAttachmentCancel" = "Cancel"; + /* Set User Data */ diff --git a/Resources/fr.lproj/HockeySDK.strings b/Resources/fr.lproj/HockeySDK.strings index a231bf4f4d..4cae9d6442 100644 --- a/Resources/fr.lproj/HockeySDK.strings +++ b/Resources/fr.lproj/HockeySDK.strings @@ -212,6 +212,18 @@ /* Send button */ "HockeyFeedbackComposeSend" = "Envoyer"; +/* Add Image button for attachment actions */ +"HockeyFeedbackComposeAttachmentAddImage" = "+ Add Image"; + +/* Edit button for attachment actions */ +"HockeyFeedbackComposeAttachmentEdit" = "Edit Attachment"; + +/* Delete button for attachment actions */ +"HockeyFeedbackComposeAttachmentDelete" = "Delete Attachment"; + +/* Cancel button for attachment actions */ +"HockeyFeedbackComposeAttachmentCancel" = "Cancel"; + /* Set User Data */ diff --git a/Resources/hr.lproj/HockeySDK.strings b/Resources/hr.lproj/HockeySDK.strings index 9f5a7520c8..142e4100a6 100644 --- a/Resources/hr.lproj/HockeySDK.strings +++ b/Resources/hr.lproj/HockeySDK.strings @@ -212,6 +212,18 @@ /* Send button */ "HockeyFeedbackComposeSend" = "Šalji"; +/* Add Image button for attachment actions */ +"HockeyFeedbackComposeAttachmentAddImage" = "+ Add Image"; + +/* Edit button for attachment actions */ +"HockeyFeedbackComposeAttachmentEdit" = "Edit Attachment"; + +/* Delete button for attachment actions */ +"HockeyFeedbackComposeAttachmentDelete" = "Delete Attachment"; + +/* Cancel button for attachment actions */ +"HockeyFeedbackComposeAttachmentCancel" = "Cancel"; + /* Set User Data */ diff --git a/Resources/hu.lproj/HockeySDK.strings b/Resources/hu.lproj/HockeySDK.strings index 7ecaea350b..0b248eb55d 100644 --- a/Resources/hu.lproj/HockeySDK.strings +++ b/Resources/hu.lproj/HockeySDK.strings @@ -212,6 +212,18 @@ /* Send button */ "HockeyFeedbackComposeSend" = "Küldés"; +/* Add Image button for attachment actions */ +"HockeyFeedbackComposeAttachmentAddImage" = "+ Add Image"; + +/* Edit button for attachment actions */ +"HockeyFeedbackComposeAttachmentEdit" = "Edit Attachment"; + +/* Delete button for attachment actions */ +"HockeyFeedbackComposeAttachmentDelete" = "Delete Attachment"; + +/* Cancel button for attachment actions */ +"HockeyFeedbackComposeAttachmentCancel" = "Cancel"; + /* Set User Data */ diff --git a/Resources/it.lproj/HockeySDK.strings b/Resources/it.lproj/HockeySDK.strings index 75753bab84..e3c965dea5 100644 --- a/Resources/it.lproj/HockeySDK.strings +++ b/Resources/it.lproj/HockeySDK.strings @@ -212,6 +212,18 @@ /* Send button */ "HockeyFeedbackComposeSend" = "Invia"; +/* Add Image button for attachment actions */ +"HockeyFeedbackComposeAttachmentAddImage" = "+ Add Image"; + +/* Edit button for attachment actions */ +"HockeyFeedbackComposeAttachmentEdit" = "Edit Attachment"; + +/* Delete button for attachment actions */ +"HockeyFeedbackComposeAttachmentDelete" = "Delete Attachment"; + +/* Cancel button for attachment actions */ +"HockeyFeedbackComposeAttachmentCancel" = "Cancel"; + /* Set User Data */ diff --git a/Resources/ja.lproj/HockeySDK.strings b/Resources/ja.lproj/HockeySDK.strings index 4532f8a583..8dbb471b05 100644 --- a/Resources/ja.lproj/HockeySDK.strings +++ b/Resources/ja.lproj/HockeySDK.strings @@ -212,6 +212,18 @@ /* Send button */ "HockeyFeedbackComposeSend" = "送信"; +/* Add Image button for attachment actions */ +"HockeyFeedbackComposeAttachmentAddImage" = "+ Add Image"; + +/* Edit button for attachment actions */ +"HockeyFeedbackComposeAttachmentEdit" = "Edit Attachment"; + +/* Delete button for attachment actions */ +"HockeyFeedbackComposeAttachmentDelete" = "Delete Attachment"; + +/* Cancel button for attachment actions */ +"HockeyFeedbackComposeAttachmentCancel" = "Cancel"; + /* Set User Data */ diff --git a/Resources/nl.lproj/HockeySDK.strings b/Resources/nl.lproj/HockeySDK.strings index 0d37d47206..854d891641 100644 --- a/Resources/nl.lproj/HockeySDK.strings +++ b/Resources/nl.lproj/HockeySDK.strings @@ -212,6 +212,18 @@ /* Send button */ "HockeyFeedbackComposeSend" = "Verstuur"; +/* Add Image button for attachment actions */ +"HockeyFeedbackComposeAttachmentAddImage" = "+ Add Image"; + +/* Edit button for attachment actions */ +"HockeyFeedbackComposeAttachmentEdit" = "Edit Attachment"; + +/* Delete button for attachment actions */ +"HockeyFeedbackComposeAttachmentDelete" = "Delete Attachment"; + +/* Cancel button for attachment actions */ +"HockeyFeedbackComposeAttachmentCancel" = "Cancel"; + /* Set User Data */ diff --git a/Resources/pt-PT.lproj/HockeySDK.strings b/Resources/pt-PT.lproj/HockeySDK.strings index 70928c4dcb..61df2895f3 100644 --- a/Resources/pt-PT.lproj/HockeySDK.strings +++ b/Resources/pt-PT.lproj/HockeySDK.strings @@ -212,6 +212,18 @@ /* Send button */ "HockeyFeedbackComposeSend" = "Enviar"; +/* Add Image button for attachment actions */ +"HockeyFeedbackComposeAttachmentAddImage" = "+ Add Image"; + +/* Edit button for attachment actions */ +"HockeyFeedbackComposeAttachmentEdit" = "Edit Attachment"; + +/* Delete button for attachment actions */ +"HockeyFeedbackComposeAttachmentDelete" = "Delete Attachment"; + +/* Cancel button for attachment actions */ +"HockeyFeedbackComposeAttachmentCancel" = "Cancel"; + /* Set User Data */ diff --git a/Resources/pt.lproj/HockeySDK.strings b/Resources/pt.lproj/HockeySDK.strings index 1e09c89615..8f41112c85 100644 --- a/Resources/pt.lproj/HockeySDK.strings +++ b/Resources/pt.lproj/HockeySDK.strings @@ -212,6 +212,18 @@ /* Send button */ "HockeyFeedbackComposeSend" = "Enviar"; +/* Add Image button for attachment actions */ +"HockeyFeedbackComposeAttachmentAddImage" = "+ Add Image"; + +/* Edit button for attachment actions */ +"HockeyFeedbackComposeAttachmentEdit" = "Edit Attachment"; + +/* Delete button for attachment actions */ +"HockeyFeedbackComposeAttachmentDelete" = "Delete Attachment"; + +/* Cancel button for attachment actions */ +"HockeyFeedbackComposeAttachmentCancel" = "Cancel"; + /* Set User Data */ diff --git a/Resources/ro.lproj/HockeySDK.strings b/Resources/ro.lproj/HockeySDK.strings index 6c0225cf8d..2cb78d86e5 100644 --- a/Resources/ro.lproj/HockeySDK.strings +++ b/Resources/ro.lproj/HockeySDK.strings @@ -212,6 +212,18 @@ /* Send button */ "HockeyFeedbackComposeSend" = "Trimite"; +/* Add Image button for attachment actions */ +"HockeyFeedbackComposeAttachmentAddImage" = "+ Add Image"; + +/* Edit button for attachment actions */ +"HockeyFeedbackComposeAttachmentEdit" = "Edit Attachment"; + +/* Delete button for attachment actions */ +"HockeyFeedbackComposeAttachmentDelete" = "Delete Attachment"; + +/* Cancel button for attachment actions */ +"HockeyFeedbackComposeAttachmentCancel" = "Cancel"; + /* Set User Data */ diff --git a/Resources/ru.lproj/HockeySDK.strings b/Resources/ru.lproj/HockeySDK.strings index da77e3f87b..abaa8e140c 100644 --- a/Resources/ru.lproj/HockeySDK.strings +++ b/Resources/ru.lproj/HockeySDK.strings @@ -212,6 +212,18 @@ /* Send button */ "HockeyFeedbackComposeSend" = "Отправить"; +/* Add Image button for attachment actions */ +"HockeyFeedbackComposeAttachmentAddImage" = "+ Add Image"; + +/* Edit button for attachment actions */ +"HockeyFeedbackComposeAttachmentEdit" = "Edit Attachment"; + +/* Delete button for attachment actions */ +"HockeyFeedbackComposeAttachmentDelete" = "Delete Attachment"; + +/* Cancel button for attachment actions */ +"HockeyFeedbackComposeAttachmentCancel" = "Cancel"; + /* Set User Data */ diff --git a/Resources/zh-Hans.lproj/HockeySDK.strings b/Resources/zh-Hans.lproj/HockeySDK.strings index fc8859be3c..fffe641d96 100644 --- a/Resources/zh-Hans.lproj/HockeySDK.strings +++ b/Resources/zh-Hans.lproj/HockeySDK.strings @@ -212,6 +212,18 @@ /* Send button */ "HockeyFeedbackComposeSend" = "发送"; +/* Add Image button for attachment actions */ +"HockeyFeedbackComposeAttachmentAddImage" = "+ Add Image"; + +/* Edit button for attachment actions */ +"HockeyFeedbackComposeAttachmentEdit" = "Edit Attachment"; + +/* Delete button for attachment actions */ +"HockeyFeedbackComposeAttachmentDelete" = "Delete Attachment"; + +/* Cancel button for attachment actions */ +"HockeyFeedbackComposeAttachmentCancel" = "Cancel"; + /* Set User Data */ From 0f142f0ec5acf04465aa0ba549caf42ebb0820c0 Mon Sep 17 00:00:00 2001 From: Andreas Linde Date: Sun, 25 May 2014 20:37:14 +0200 Subject: [PATCH 90/92] Improvements for feedback compose view on iPad When the actionsheet is visible, disable the add image button and remove the first responder from the textview --- Classes/BITFeedbackComposeViewController.m | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/Classes/BITFeedbackComposeViewController.m b/Classes/BITFeedbackComposeViewController.m index 23b5cea7fe..d6dd242c28 100644 --- a/Classes/BITFeedbackComposeViewController.m +++ b/Classes/BITFeedbackComposeViewController.m @@ -68,7 +68,9 @@ @implementation BITFeedbackComposeViewController { - BOOL _blockUserDataScreen; + BOOL _blockUserDataScreen; + + BOOL _actionSheetVisible; } @@ -79,6 +81,7 @@ if (self) { self.title = BITHockeyLocalizedString(@"HockeyFeedbackComposeTitle"); _blockUserDataScreen = NO; + _actionSheetVisible = NO; _delegate = nil; _manager = [BITHockeyManager sharedHockeyManager].feedbackManager; _attachments = [NSMutableArray new]; @@ -411,6 +414,8 @@ } -(void)addPhotoAction:(id)sender { + if (_actionSheetVisible) return; + // add photo. UIImagePickerController *pickerController = [[UIImagePickerController alloc] init]; pickerController.sourceType = UIImagePickerControllerSourceTypePhotoLibrary; @@ -464,6 +469,11 @@ otherButtonTitles: BITHockeyLocalizedString(@"HockeyFeedbackComposeAttachmentEdit"), nil]; [actionSheet showFromRect: sender.frame inView: self.attachmentScrollView animated: YES]; + + _actionSheetVisible = YES; + if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { + [self.textView resignFirstResponder]; + } } @@ -526,6 +536,10 @@ [self presentViewController:navController animated:YES completion:nil]; } } + _actionSheetVisible = NO; + if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { + [self.textView becomeFirstResponder]; + } } From 795b9e59e47e4ba4f3dd9114ccddbd4a579094af Mon Sep 17 00:00:00 2001 From: Andreas Linde Date: Sun, 25 May 2014 20:44:54 +0200 Subject: [PATCH 91/92] Improve feedback compose view on iPad further (for iOS 6.1) --- Classes/BITFeedbackComposeViewController.m | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/Classes/BITFeedbackComposeViewController.m b/Classes/BITFeedbackComposeViewController.m index d6dd242c28..1677536289 100644 --- a/Classes/BITFeedbackComposeViewController.m +++ b/Classes/BITFeedbackComposeViewController.m @@ -514,7 +514,7 @@ #pragma mark - UIActionSheet Delegate - (void)actionSheet:(UIActionSheet *)actionSheet didDismissWithButtonIndex:(NSInteger)buttonIndex { - if (buttonIndex == [actionSheet destructiveButtonIndex]){ + if (buttonIndex == [actionSheet destructiveButtonIndex]) { if (self.selectedAttachmentIndex != NSNotFound){ BITFeedbackMessageAttachment *attachment = self.imageAttachments[self.selectedAttachmentIndex]; @@ -526,7 +526,10 @@ [self refreshAttachmentScrollview]; - } else if(buttonIndex != [actionSheet cancelButtonIndex]){ + if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { + [self.textView becomeFirstResponder]; + } + } else if (buttonIndex != [actionSheet cancelButtonIndex]) { if (self.selectedAttachmentIndex != NSNotFound){ BITFeedbackMessageAttachment *attachment = self.imageAttachments[self.selectedAttachmentIndex]; BITImageAnnotationViewController *annotationEditor = [[BITImageAnnotationViewController alloc ] init]; @@ -535,11 +538,12 @@ annotationEditor.image = attachment.imageRepresentation; [self presentViewController:navController animated:YES completion:nil]; } + } else { + if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { + [self.textView becomeFirstResponder]; + } } _actionSheetVisible = NO; - if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { - [self.textView becomeFirstResponder]; - } } From 32bd3e93b17c391e8392cc7dd99a6f768af06583 Mon Sep 17 00:00:00 2001 From: Andreas Linde Date: Mon, 26 May 2014 00:11:59 +0200 Subject: [PATCH 92/92] Make it clear how the screenshot trigger gets the image --- Classes/BITFeedbackManager.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Classes/BITFeedbackManager.h b/Classes/BITFeedbackManager.h index 435a1f2ec2..2056d1486d 100644 --- a/Classes/BITFeedbackManager.h +++ b/Classes/BITFeedbackManager.h @@ -69,7 +69,7 @@ typedef NS_ENUM(NSInteger, BITFeedbackObservationMode) { */ BITFeedbackObservationNone = 0, /** - * Triggeres when the user takes a screenshot. Requires iOS 7 or later! + * Triggeres when the user takes a screenshot. This will grab the latest image from the camera roll. Requires iOS 7 or later! */ BITFeedbackObservationModeOnScreenshot = 1, /** @@ -220,7 +220,7 @@ typedef NS_ENUM(NSInteger, BITFeedbackObservationMode) { own trigger and then call `[[BITHockeyManager sharedHockeyManager].feedbackManager showFeedbackComposeViewWithGeneratedScreenshot];` to handle your custom events that should trigger this. - `BITFeedbackObservationModeOnScreenshot`: Triggeres when the user takes a screenshot. - Requires iOS 7 or later! + This will grab the latest image from the camera roll. Requires iOS 7 or later! - `BITFeedbackObservationModeThreeFingerTap`: Triggers when the user tapps with three fingers for three seconds on the screen.