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
This commit is contained in:
Andreas Linde
2014-05-25 18:55:24 +02:00
parent ef6d5224da
commit b48bb8fb12
12 changed files with 272 additions and 211 deletions

View File

@@ -1,10 +1,28 @@
// /*
// BITActivityIndicatorButton.h * Copyright (c) 2012-2014 HockeyApp, Bit Stadium GmbH.
// HockeySDK * All rights reserved.
// *
// Created by Moritz Haarmann on 21.05.14. * 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 <UIKit/UIKit.h> #import <UIKit/UIKit.h>

View File

@@ -1,10 +1,28 @@
// /*
// BITActivityIndicatorButton.m * Copyright (c) 2012-2014 HockeyApp, Bit Stadium GmbH.
// HockeySDK * All rights reserved.
// *
// Created by Moritz Haarmann on 21.05.14. * 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" #import "BITActivityIndicatorButton.h"

View File

@@ -602,20 +602,22 @@ NSString *const kBITCrashManagerStatus = @"BITCrashManagerStatus";
if (_crashManagerStatus == BITCrashManagerStatusDisabled) return NO; if (_crashManagerStatus == BITCrashManagerStatusDisabled) return NO;
if ([self.fileManager fileExistsAtPath:_crashesDir]) { if ([self.fileManager fileExistsAtPath:_crashesDir]) {
NSString *file = nil;
NSError *error = NULL; NSError *error = NULL;
NSDirectoryEnumerator *dirEnum = [self.fileManager enumeratorAtPath: _crashesDir]; NSArray *dirArray = [self.fileManager contentsOfDirectoryAtPath:_crashesDir error:&error];
while ((file = [dirEnum nextObject])) { for (NSString *file in dirArray) {
NSDictionary *fileAttributes = [self.fileManager attributesOfItemAtPath:[_crashesDir stringByAppendingPathComponent:file] error:&error]; NSString *filePath = [_crashesDir stringByAppendingPathComponent:file];
if ([[fileAttributes objectForKey:NSFileSize] intValue] > 0 &&
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:@".DS_Store"] &&
![file hasSuffix:@".analyzer"] && ![file hasSuffix:@".analyzer"] &&
![file hasSuffix:@".plist"] && ![file hasSuffix:@".plist"] &&
![file hasSuffix:@".data"] && ![file hasSuffix:@".data"] &&
![file hasSuffix:@".meta"]) { ![file hasSuffix:@".meta"]) {
[_crashFiles addObject:[_crashesDir stringByAppendingPathComponent: file]]; [_crashFiles addObject:filePath];
} }
} }
} }

View File

@@ -39,6 +39,7 @@
@end @end
/** /**
* Cell style depending on the iOS version * Cell style depending on the iOS version
*/ */
@@ -67,6 +68,7 @@ typedef NS_ENUM(NSUInteger, BITFeedbackListViewCellBackgroundStyle) {
BITFeedbackListViewCellBackgroundStyleAlternate = 1 BITFeedbackListViewCellBackgroundStyleAlternate = 1
}; };
@interface BITFeedbackListViewCell : UITableViewCell @interface BITFeedbackListViewCell : UITableViewCell
@property (nonatomic, strong) BITFeedbackMessage *message; @property (nonatomic, strong) BITFeedbackMessage *message;

View File

@@ -152,7 +152,7 @@
CGFloat attachmentsPerRow = floorf(width / (FRAME_SIDE_BORDER + ATTACHMENT_SIZE)); 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); return ceil(calculatedHeight);
} }
@@ -197,6 +197,7 @@
[self.attachmentViews removeAllObjects]; [self.attachmentViews removeAllObjects];
for (BITFeedbackMessageAttachment *attachment in attachments){ for (BITFeedbackMessageAttachment *attachment in attachments){
if (attachment.localURL || attachment.sourceURL) {
BITActivityIndicatorButton *imageView = [BITActivityIndicatorButton buttonWithType:UIButtonTypeCustom]; BITActivityIndicatorButton *imageView = [BITActivityIndicatorButton buttonWithType:UIButtonTypeCustom];
if (attachment.localURL){ if (attachment.localURL){
@@ -210,9 +211,7 @@
[imageView addTarget:self action:@selector(imageButtonPressed:) forControlEvents:UIControlEventTouchUpInside]; [imageView addTarget:self action:@selector(imageButtonPressed:) forControlEvents:UIControlEventTouchUpInside];
[self.attachmentViews addObject:imageView]; [self.attachmentViews addObject:imageView];
//[self addSubview:imageView]; }
} }
} }
@@ -286,18 +285,18 @@
CGFloat attachmentsPerRow = floorf(self.frame.size.width / (FRAME_SIDE_BORDER + ATTACHMENT_SIZE)); 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.contentMode = UIViewContentModeScaleAspectFit;
imageButton.imageView.contentMode = UIViewContentModeScaleAspectFill; 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); 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 { } 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); 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 (!imageButton.superview) {
if (self.accessoryBackgroundView.superview){ if (self.accessoryBackgroundView.superview) {
[self insertSubview:imageButton aboveSubview:self.accessoryBackgroundView]; [self insertSubview:imageButton aboveSubview:self.accessoryBackgroundView];
} else { } else {
[self addSubview:imageButton]; [self addSubview:imageButton];
@@ -311,10 +310,10 @@
} }
- (void)imageButtonPressed:(id)sender { - (void)imageButtonPressed:(id)sender {
if ([self.delegate respondsToSelector:@selector(listCell:didSelectAttachment:)]){ if ([self.delegate respondsToSelector:@selector(listCell:didSelectAttachment:)]) {
NSInteger index = [self.attachmentViews indexOfObject:sender]; NSInteger index = [self.attachmentViews indexOfObject:sender];
if (index != NSNotFound){ if (index != NSNotFound && [self.message previewableAttachments].count > index) {
BITFeedbackMessageAttachment *attachment = self.message.attachments[index]; BITFeedbackMessageAttachment *attachment = [self.message previewableAttachments][index];
[self.delegate listCell:self didSelectAttachment:attachment]; [self.delegate listCell:self didSelectAttachment:attachment];
} }
} }

View File

@@ -135,13 +135,13 @@
[self.tableView setBackgroundColor:[UIColor colorWithRed:0.82 green:0.84 blue:0.84 alpha:1]]; [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]]; [self.tableView setSeparatorColor:[UIColor colorWithRed:0.79 green:0.79 blue:0.79 alpha:1]];
} else { } 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]) { if ([self.manager isPreiOS7Environment]) {
self.view.backgroundColor = DEFAULT_BACKGROUNDCOLOR; self.view.backgroundColor = DEFAULT_BACKGROUNDCOLOR;
} else { } else {
// self.view.backgroundColor = DEFAULT_BACKGROUNDCOLOR_OS7; // self.view.backgroundColor = DEFAULT_BACKGROUNDCOLOR_OS7;
} }
id refreshClass = NSClassFromString(@"UIRefreshControl"); id refreshClass = NSClassFromString(@"UIRefreshControl");
@@ -191,7 +191,9 @@
CGSize contentSize = self.tableView.contentSize; CGSize contentSize = self.tableView.contentSize;
CGPoint contentOffset = self.tableView.contentOffset; CGPoint contentOffset = self.tableView.contentOffset;
[self refreshPreviewItems];
[self.tableView reloadData]; [self.tableView reloadData];
if (contentSize.height > 0 && if (contentSize.height > 0 &&
self.tableView.contentSize.height > self.tableView.frame.size.height && self.tableView.contentSize.height > self.tableView.frame.size.height &&
self.tableView.contentSize.height > contentSize.height && self.tableView.contentSize.height > contentSize.height &&
@@ -272,6 +274,8 @@
- (void)deleteAllMessages { - (void)deleteAllMessages {
[_manager deleteAllMessages]; [_manager deleteAllMessages];
[self refreshPreviewItems];
[self.tableView reloadData]; [self.tableView reloadData];
} }
@@ -644,11 +648,12 @@
attachment.isLoading = YES; attachment.isLoading = YES;
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:attachment.sourceURL]]; NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:attachment.sourceURL]];
[NSURLConnection sendAsynchronousRequest:request queue:self.thumbnailQueue completionHandler:^(NSURLResponse *response, NSData *responseData, NSError *err) { [NSURLConnection sendAsynchronousRequest:request queue:self.thumbnailQueue completionHandler:^(NSURLResponse *response, NSData *responseData, NSError *err) {
if (responseData.length){ if (responseData.length) {
[attachment replaceData:responseData]; [attachment replaceData:responseData];
dispatch_async(dispatch_get_main_queue(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
[self.tableView reloadData]; [self.tableView reloadData];
}); });
[[BITHockeyManager sharedHockeyManager].feedbackManager saveMessages]; [[BITHockeyManager sharedHockeyManager].feedbackManager saveMessages];
} }
}]; }];
@@ -679,12 +684,19 @@
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath { - (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
if (editingStyle == UITableViewCellEditingStyleDelete) { if (editingStyle == UITableViewCellEditingStyleDelete) {
BITFeedbackMessage *message = [self.manager messageAtIndex:indexPath.row];
BOOL messageHasAttachments = ([message attachments].count > 0);
if ([_manager deleteMessageAtIndex:indexPath.row]) { if ([_manager deleteMessageAtIndex:indexPath.row]) {
if ([_manager numberOfMessages] > 0) { if ([_manager numberOfMessages] > 0) {
[tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic]; [tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
} else { } else {
[tableView reloadData]; [tableView reloadData];
} }
if (messageHasAttachments) {
[self refreshPreviewItems];
}
} }
} }
} }
@@ -794,6 +806,7 @@
} }
} }
#pragma mark - ListViewCellDelegate #pragma mark - ListViewCellDelegate
- (void)listCell:(id)cell didSelectAttachment:(BITFeedbackMessageAttachment *)attachment { - (void)listCell:(id)cell didSelectAttachment:(BITFeedbackMessageAttachment *)attachment {
@@ -801,16 +814,17 @@
previewController.dataSource = self; previewController.dataSource = self;
[self presentViewController:previewController animated:YES completion:nil]; [self presentViewController:previewController animated:YES completion:nil];
if (self.cachedPreviewItems.count > [self.cachedPreviewItems indexOfObject:attachment]){
if (self.cachedPreviewItems.count > [self.cachedPreviewItems indexOfObject:attachment]) {
[previewController setCurrentPreviewItemIndex:[self.cachedPreviewItems indexOfObject:attachment]]; [previewController setCurrentPreviewItemIndex:[self.cachedPreviewItems indexOfObject:attachment]];
} }
} }
- (void)refreshPreviewItems { - (void)refreshPreviewItems {
self.cachedPreviewItems = nil; self.cachedPreviewItems = nil;
NSMutableArray *collectedAttachments = [NSMutableArray new]; NSMutableArray *collectedAttachments = [NSMutableArray new];
for (int i = 0; i<self.manager.numberOfMessages;i++){ for (int i = 0; i < self.manager.numberOfMessages; i++) {
BITFeedbackMessage *message = [self.manager messageAtIndex:i]; BITFeedbackMessage *message = [self.manager messageAtIndex:i];
[collectedAttachments addObjectsFromArray:message.previewableAttachments]; [collectedAttachments addObjectsFromArray:message.previewableAttachments];
} }
@@ -818,23 +832,25 @@
self.cachedPreviewItems = collectedAttachments; self.cachedPreviewItems = collectedAttachments;
} }
- (NSInteger) numberOfPreviewItemsInPreviewController: (QLPreviewController *) controller { - (NSInteger)numberOfPreviewItemsInPreviewController:(QLPreviewController *)controller {
if (!self.cachedPreviewItems){ if (!self.cachedPreviewItems){
[self refreshPreviewItems]; [self refreshPreviewItems];
} }
return self.cachedPreviewItems.count; return self.cachedPreviewItems.count;
} }
- (id <QLPreviewItem>) previewController: (QLPreviewController *) controller previewItemAtIndex: (NSInteger) index { - (id <QLPreviewItem>)previewController:(QLPreviewController *)controller previewItemAtIndex:(NSInteger)index {
if (index>=0){ if (index >= 0) {
__weak QLPreviewController* blockController = controller; __weak QLPreviewController* blockController = controller;
BITFeedbackMessageAttachment *attachment = self.cachedPreviewItems[index]; BITFeedbackMessageAttachment *attachment = self.cachedPreviewItems[index];
if (attachment.needsLoadingFromURL && !attachment.isLoading){
if (attachment.needsLoadingFromURL && !attachment.isLoading) {
attachment.isLoading = YES; attachment.isLoading = YES;
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:attachment.sourceURL]]; NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:attachment.sourceURL]];
[NSURLConnection sendAsynchronousRequest:request queue:self.thumbnailQueue completionHandler:^(NSURLResponse *response, NSData *responseData, NSError *err) { [NSURLConnection sendAsynchronousRequest:request queue:self.thumbnailQueue completionHandler:^(NSURLResponse *response, NSData *responseData, NSError *err) {
attachment.isLoading = NO; attachment.isLoading = NO;
if (responseData.length){ if (responseData.length) {
[attachment replaceData:responseData]; [attachment replaceData:responseData];
[blockController reloadData]; [blockController reloadData];
@@ -843,11 +859,13 @@
[blockController reloadData]; [blockController reloadData];
} }
}]; }];
return attachment; return attachment;
} else { } else {
return self.cachedPreviewItems[index]; return self.cachedPreviewItems[index];
} }
} }
return nil; return nil;
} }

View File

@@ -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) { typedef NS_ENUM(NSInteger, BITFeedbackObservationMode) {
/** /**
* No automatic feedback gathering. * No SDK provided trigger is active.
*/ */
BITFeedbackObservationNone = 0, 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, BITFeedbackObservationModeOnScreenshot = 1,
/** /**
* Feedback compose will open with a generated screenshot if the screen is tapped * Triggers when the user tapps with three fingers for three seconds on the screen.
* three fingers for three seconds.
*/ */
BITFeedbackObservationModeThreeFingerTap = 2 BITFeedbackObservationModeThreeFingerTap = 2
}; };
@@ -212,6 +211,26 @@ typedef NS_ENUM(NSInteger, BITFeedbackObservationMode) {
@property (nonatomic, readwrite) BOOL showAlertOnIncomingMessages; @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 /// @name User Interface
///----------------------------------------------------------------------------- ///-----------------------------------------------------------------------------
@@ -269,8 +288,13 @@ typedef NS_ENUM(NSInteger, BITFeedbackObservationMode) {
- (void)showFeedbackComposeViewWithPreparedItems:(NSArray *)items; - (void)showFeedbackComposeViewWithPreparedItems:(NSArray *)items;
/** /**
Present the modal feedback compose message user interface with a screenshot that is taken Presents a modal feedback compose interface with a screenshot attached which is taken at the time of calling this method.
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; - (void)showFeedbackComposeViewWithGeneratedScreenshot;
@@ -295,14 +319,5 @@ typedef NS_ENUM(NSInteger, BITFeedbackObservationMode) {
*/ */
- (BITFeedbackComposeViewController *)feedbackComposeViewController; - (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 @end

View File

@@ -740,7 +740,7 @@
message.id = [(NSDictionary *)objMessage objectForKey:@"id"]; message.id = [(NSDictionary *)objMessage objectForKey:@"id"];
message.status = BITFeedbackMessageStatusUnread; message.status = BITFeedbackMessageStatusUnread;
for (NSDictionary *attachmentData in objMessage[@"attachments"]){ for (NSDictionary *attachmentData in objMessage[@"attachments"]) {
BITFeedbackMessageAttachment *newAttachment = [BITFeedbackMessageAttachment new]; BITFeedbackMessageAttachment *newAttachment = [BITFeedbackMessageAttachment new];
newAttachment.originalFilename = attachmentData[@"file_name"]; newAttachment.originalFilename = attachmentData[@"file_name"];
newAttachment.id = attachmentData[@"id"]; newAttachment.id = attachmentData[@"id"];
@@ -805,45 +805,11 @@
[self markSendInProgressMessagesAsPending]; [self markSendInProgressMessagesAsPending];
} }
// we'll load the images on demand.
//[self synchronizeMissingAttachments];
[self saveMessages]; [self saveMessages];
return; 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 { - (void)sendNetworkRequestWithHTTPMethod:(NSString *)httpMethod withMessage:(BITFeedbackMessage *)message completionHandler:(void (^)(NSError *err))completionHandler {
NSString *boundary = @"----FOO"; NSString *boundary = @"----FOO";
@@ -1092,12 +1058,15 @@
#pragma mark - Observation Handling #pragma mark - Observation Handling
-(void)setFeedbackObservationMode:(BITFeedbackObservationMode)mode { - (void)setFeedbackObservationMode:(BITFeedbackObservationMode)feedbackObservationMode {
if (mode == BITFeedbackObservationModeOnScreenshot){ if (feedbackObservationMode != _feedbackObservationMode) {
_feedbackObservationMode = feedbackObservationMode;
if (feedbackObservationMode == BITFeedbackObservationModeOnScreenshot){
if (NSFoundationVersionNumber > NSFoundationVersionNumber_iOS_6_1){ if (NSFoundationVersionNumber > NSFoundationVersionNumber_iOS_6_1){
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(screenshotNotificationReceived:) name:UIApplicationUserDidTakeScreenshotNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(screenshotNotificationReceived:) name:UIApplicationUserDidTakeScreenshotNotification object:nil];
} else { } else {
BITHockeyLog("Not enabling Screenshot notifications: iOS6.1 and lower is not supported."); BITHockeyLog("WARNING: BITFeedbackObservationModeOnScreenshot requires iOS 7 or later.");
} }
self.screenshotNotificationEnabled = YES; self.screenshotNotificationEnabled = YES;
@@ -1108,7 +1077,7 @@
} }
} }
if (mode == BITFeedbackObservationModeThreeFingerTap){ if (feedbackObservationMode == BITFeedbackObservationModeThreeFingerTap){
if (!self.tapRecognizer){ if (!self.tapRecognizer){
self.tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(screenshotTripleTap:)]; self.tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(screenshotTripleTap:)];
self.tapRecognizer.numberOfTouchesRequired = 3; self.tapRecognizer.numberOfTouchesRequired = 3;
@@ -1126,6 +1095,7 @@
} }
} }
} }
}
} }
-(void)screenshotNotificationReceived:(NSNotification *)notification { -(void)screenshotNotificationReceived:(NSNotification *)notification {

View File

@@ -76,18 +76,24 @@ typedef NS_ENUM(NSInteger, BITFeedbackMessageStatus) {
@property (nonatomic) BOOL userMessage; @property (nonatomic) BOOL userMessage;
/** /**
* This method must be called before a feedback message is deleted. It handles the Delete local cached attachment data
* deletion of any data stored on the device in association with the feedback message.
*/
-(void)deleteContents;
@warning This method must be called before a feedback message is deleted.
*/
- (void)deleteContents;
/**
Add an attachment to a message
@param object BITFeedbackMessageAttachment instance representing the attachment that should be added
*/
-(void)addAttachmentsObject:(BITFeedbackMessageAttachment *)object; -(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; - (NSArray *)previewableAttachments;
@end @end

View File

@@ -35,7 +35,7 @@
#pragma mark - NSObject #pragma mark - NSObject
- (id) init { - (instancetype) init {
if ((self = [super init])) { if ((self = [super init])) {
_text = nil; _text = nil;
_userID = nil; _userID = nil;
@@ -67,8 +67,8 @@
[encoder encodeObject:self.token forKey:@"token"]; [encoder encodeObject:self.token forKey:@"token"];
} }
- (id)initWithCoder:(NSCoder *)decoder { - (instancetype)initWithCoder:(NSCoder *)decoder {
if ((self = [super init])) { if ((self = [self init])) {
self.text = [decoder decodeObjectForKey:@"text"]; self.text = [decoder decodeObjectForKey:@"text"];
self.userID = [decoder decodeObjectForKey:@"userID"]; self.userID = [decoder decodeObjectForKey:@"userID"];
self.name = [decoder decodeObjectForKey:@"name"]; self.name = [decoder decodeObjectForKey:@"name"];

View File

@@ -2,7 +2,6 @@
* Author: Moritz Haarmann <post@moritzhaarmann.de> * Author: Moritz Haarmann <post@moritzhaarmann.de>
* *
* Copyright (c) 2012-2014 HockeyApp, Bit Stadium GmbH. * Copyright (c) 2012-2014 HockeyApp, Bit Stadium GmbH.
* Copyright (c) 2011 Andreas Linde & Kent Sutherland.
* All rights reserved. * All rights reserved.
* *
* Permission is hereby granted, free of charge, to any person * Permission is hereby granted, free of charge, to any person

View File

@@ -32,7 +32,7 @@
#import "HockeySDKPrivate.h" #import "HockeySDKPrivate.h"
#import <MobileCoreServices/MobileCoreServices.h> #import <MobileCoreServices/MobileCoreServices.h>
#define kCacheFolderName @"hockey_attachments" #define kCacheFolderName @"attachments"
@interface BITFeedbackMessageAttachment() @interface BITFeedbackMessageAttachment()
@@ -43,47 +43,63 @@
@end @end
@implementation BITFeedbackMessageAttachment @implementation BITFeedbackMessageAttachment {
NSString *_tempFilename;
NSString *_cachePath;
NSFileManager *_fm;
}
+ (BITFeedbackMessageAttachment *)attachmentWithData:(NSData *)data contentType:(NSString *)contentType { + (BITFeedbackMessageAttachment *)attachmentWithData:(NSData *)data contentType:(NSString *)contentType {
static NSDateFormatter *formatter; static NSDateFormatter *formatter;
if(!formatter){ if(!formatter) {
formatter = [NSDateFormatter new]; formatter = [NSDateFormatter new];
formatter.dateStyle = NSDateFormatterShortStyle; formatter.dateStyle = NSDateFormatterShortStyle;
formatter.timeStyle = NSDateFormatterShortStyle; formatter.timeStyle = NSDateFormatterShortStyle;
} }
BITFeedbackMessageAttachment *newAttachment = [BITFeedbackMessageAttachment new]; BITFeedbackMessageAttachment *newAttachment = [BITFeedbackMessageAttachment new];
newAttachment.contentType = contentType; newAttachment.contentType = contentType;
newAttachment.data = data; newAttachment.data = data;
newAttachment.originalFilename = [NSString stringWithFormat:@"Attachment: %@", [formatter stringFromDate:[NSDate date]]]; newAttachment.originalFilename = [NSString stringWithFormat:@"Attachment: %@", [formatter stringFromDate:[NSDate date]]];
return newAttachment; return newAttachment;
} }
-(id)init { - (instancetype)init {
self = [super init]; if ((self = [super init])) {
if (self){
self.isLoading = NO; self.isLoading = NO;
self.thumbnailRepresentations = [NSMutableDictionary new]; 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; return self;
} }
-(void)setData:(NSData *)data { - (void)setData:(NSData *)data {
self->_internalData = data; self->_internalData = data;
self.filename = [self possibleFilename]; self.filename = [self possibleFilename];
[self->_internalData writeToFile:self.filename atomically:NO]; [self->_internalData writeToFile:self.filename atomically:NO];
} }
-(NSData *)data { - (NSData *)data {
if (!self->_internalData && self.filename){ if (!self->_internalData && self.filename) {
self.internalData = [NSData dataWithContentsOfFile:self.filename]; self.internalData = [NSData dataWithContentsOfFile:self.filename];
} }
if (self.internalData){ if (self.internalData) {
return self.internalData; return self.internalData;
} }
@@ -95,8 +111,8 @@
self.thumbnailRepresentations = [NSMutableDictionary new]; self.thumbnailRepresentations = [NSMutableDictionary new];
} }
-(BOOL)needsLoadingFromURL { - (BOOL)needsLoadingFromURL {
return (self.sourceURL && ![[NSFileManager defaultManager] fileExistsAtPath:self.localURL.absoluteString]); return (self.sourceURL && ![_fm fileExistsAtPath:[self.localURL path]]);
} }
- (BOOL)isImage { - (BOOL)isImage {
@@ -104,12 +120,14 @@
} }
- (NSURL *)localURL { - (NSURL *)localURL {
if (self.filename){ if (self.filename && [_fm fileExistsAtPath:self.filename]) {
return [NSURL fileURLWithPath:self.filename]; return [NSURL fileURLWithPath:self.filename];
} else }
{ return nil;}
return nil;
} }
#pragma mark NSCoding #pragma mark NSCoding
- (void)encodeWithCoder:(NSCoder *)aCoder { - (void)encodeWithCoder:(NSCoder *)aCoder {
@@ -117,29 +135,25 @@
[aCoder encodeObject:self.filename forKey:@"filename"]; [aCoder encodeObject:self.filename forKey:@"filename"];
[aCoder encodeObject:self.originalFilename forKey:@"originalFilename"]; [aCoder encodeObject:self.originalFilename forKey:@"originalFilename"];
[aCoder encodeObject:self.sourceURL forKey:@"url"]; [aCoder encodeObject:self.sourceURL forKey:@"url"];
} }
- (id)initWithCoder:(NSCoder *)aDecoder { - (instancetype)initWithCoder:(NSCoder *)aDecoder {
self = [super init]; if ((self = [self init])) {
if (self){
self.contentType = [aDecoder decodeObjectForKey:@"contentType"]; self.contentType = [aDecoder decodeObjectForKey:@"contentType"];
self.filename = [aDecoder decodeObjectForKey:@"filename"]; self.filename = [aDecoder decodeObjectForKey:@"filename"];
self.thumbnailRepresentations = [NSMutableDictionary new]; self.thumbnailRepresentations = [NSMutableDictionary new];
self.originalFilename = [aDecoder decodeObjectForKey:@"originalFilename"]; self.originalFilename = [aDecoder decodeObjectForKey:@"originalFilename"];
self.sourceURL = [aDecoder decodeObjectForKey:@"url"]; self.sourceURL = [aDecoder decodeObjectForKey:@"url"];
} }
return self; return self;
} }
#pragma mark - Thubmnails / Image Representation #pragma mark - Thubmnails / Image Representation
- (UIImage *)imageRepresentation { - (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]; return [UIImage imageWithData:self.data];
} else { } else {
// Create a Icon .. // Create a Icon ..
@@ -157,21 +171,22 @@
- (UIImage *)thumbnailWithSize:(CGSize)size { - (UIImage *)thumbnailWithSize:(CGSize)size {
id<NSCopying> cacheKey = [NSValue valueWithCGSize:size]; id<NSCopying> cacheKey = [NSValue valueWithCGSize:size];
if (!self.thumbnailRepresentations[cacheKey]){ if (!self.thumbnailRepresentations[cacheKey]) {
UIImage *image = self.imageRepresentation; UIImage *image = self.imageRepresentation;
// consider the scale. // consider the scale.
if (!image) if (!image) {
return nil; return nil;
}
CGFloat scale = [UIScreen mainScreen].scale; CGFloat scale = [UIScreen mainScreen].scale;
if (scale != image.scale){ if (scale != image.scale) {
CGSize scaledSize = CGSizeApplyAffineTransform(size, CGAffineTransformMakeScale(scale, scale)); CGSize scaledSize = CGSizeApplyAffineTransform(size, CGAffineTransformMakeScale(scale, scale));
UIImage *thumbnail = bit_imageToFitSize(image, scaledSize, YES) ; UIImage *thumbnail = bit_imageToFitSize(image, scaledSize, YES) ;
UIImage *scaledTumbnail = [UIImage imageWithCGImage:thumbnail.CGImage scale:scale orientation:thumbnail.imageOrientation]; UIImage *scaledTumbnail = [UIImage imageWithCGImage:thumbnail.CGImage scale:scale orientation:thumbnail.imageOrientation];
if (thumbnail){ if (thumbnail) {
[self.thumbnailRepresentations setObject:scaledTumbnail forKey:cacheKey]; [self.thumbnailRepresentations setObject:scaledTumbnail forKey:cacheKey];
} }
@@ -191,42 +206,36 @@
#pragma mark - Persistence Helpers #pragma mark - Persistence Helpers
- (NSString *)possibleFilename { - (NSString *)possibleFilename {
NSArray* cachePathArray = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES); if (_tempFilename) {
NSString* cachePath = [cachePathArray lastObject]; return _tempFilename;
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(); NSString *uniqueString = bit_UUID();
cachePath = [cachePath stringByAppendingPathComponent:uniqueString]; _tempFilename = [_cachePath stringByAppendingPathComponent:uniqueString];
// File extension that suits the Content type. // File extension that suits the Content type.
CFStringRef mimeType = (__bridge CFStringRef)self.contentType; CFStringRef mimeType = (__bridge CFStringRef)self.contentType;
CFStringRef uti = UTTypeCreatePreferredIdentifierForTag(kUTTagClassMIMEType, mimeType, NULL); CFStringRef uti = UTTypeCreatePreferredIdentifierForTag(kUTTagClassMIMEType, mimeType, NULL);
CFStringRef extension = UTTypeCopyPreferredTagWithClass(uti, kUTTagClassFilenameExtension); CFStringRef extension = UTTypeCopyPreferredTagWithClass(uti, kUTTagClassFilenameExtension);
if (extension){ if (extension) {
cachePath = [cachePath stringByAppendingPathExtension:(__bridge NSString *)(extension)]; _tempFilename = [_tempFilename stringByAppendingPathExtension:(__bridge NSString *)(extension)];
CFRelease(extension); CFRelease(extension);
} }
CFRelease(uti); CFRelease(uti);
return cachePath; return _tempFilename;
} }
- (void)deleteContents { - (void)deleteContents {
if (self.filename){ if (self.filename) {
[[NSFileManager defaultManager] removeItemAtPath:self.filename error:nil]; [_fm removeItemAtPath:self.filename error:nil];
self.filename = nil; self.filename = nil;
} }
} }
#pragma mark - QLPreviewItem #pragma mark - QLPreviewItem
- (NSString *)previewItemTitle { - (NSString *)previewItemTitle {
@@ -236,9 +245,14 @@
- (NSURL *)previewItemURL { - (NSURL *)previewItemURL {
if (self.localURL){ if (self.localURL){
return self.localURL; return self.localURL;
} else { } else if (self.sourceURL) {
return [NSURL fileURLWithPath:self.possibleFilename]; NSString *filename = self.possibleFilename;
if (filename) {
return [NSURL fileURLWithPath:filename];
} }
}
return nil;
} }
@end @end