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
// 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 <UIKit/UIKit.h>

View File

@@ -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"

View File

@@ -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];
}
}
}

View File

@@ -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;

View File

@@ -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];
}
}

View File

@@ -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<self.manager.numberOfMessages;i++){
for (int i = 0; i < self.manager.numberOfMessages; i++) {
BITFeedbackMessage *message = [self.manager messageAtIndex:i];
[collectedAttachments addObjectsFromArray:message.previewableAttachments];
[collectedAttachments addObjectsFromArray:message.previewableAttachments];
}
self.cachedPreviewItems = collectedAttachments;
}
- (NSInteger) numberOfPreviewItemsInPreviewController: (QLPreviewController *) controller {
- (NSInteger)numberOfPreviewItemsInPreviewController:(QLPreviewController *)controller {
if (!self.cachedPreviewItems){
[self refreshPreviewItems];
}
return self.cachedPreviewItems.count;
}
- (id <QLPreviewItem>) previewController: (QLPreviewController *) controller previewItemAtIndex: (NSInteger) index {
if (index>=0){
- (id <QLPreviewItem>)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;
}

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) {
/**
* 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

View File

@@ -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;
}
}
}
}

View File

@@ -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

View File

@@ -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"];

View File

@@ -2,7 +2,6 @@
* Author: Moritz Haarmann <post@moritzhaarmann.de>
*
* 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

View File

@@ -32,7 +32,7 @@
#import "HockeySDKPrivate.h"
#import <MobileCoreServices/MobileCoreServices.h>
#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<NSCopying> 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