- synced git repo with local repo

This commit is contained in:
Guy Saldanha committed 2015-03-11 11:24:35 +01:00
1 parent 2454809fae
commit a5b56b1a53
370 files changed
+2822 -41290

No files matched your search

Binary file not shown.
+22
View File
@@ -0,0 +1,22 @@
AFNetworking
// AFNetworking.h
//
// Copyright (c) 2013 AFNetworking (http://afnetworking.com/)
//
// 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.
-1
View File
@@ -2,5 +2,4 @@ source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '7.1'
link_with ['SAPTracker', 'SAPTrackerTests']
pod 'AFNetworking', '~> 2.4'
pod 'HockeySDK', '~> 3.6.1'
pod 'AFNetworkActivityLogger', :configurations => ['Debug']
-3
View File
@@ -23,16 +23,13 @@ PODS:
- AFNetworking/UIKit (2.5.0):
- AFNetworking/NSURLConnection
- AFNetworking/NSURLSession
- HockeySDK (3.6.2)
DEPENDENCIES:
- AFNetworkActivityLogger
- AFNetworking (~> 2.4)
- HockeySDK (~> 3.6.1)
SPEC CHECKSUMS:
AFNetworkActivityLogger: d6a2d2755fada3a1fbf1f490ec0dd92224e092a4
AFNetworking: 0f54cb5d16ce38c1b76948faffb8d5fb705021c7
HockeySDK: bfd1f5ac75938b07499c4ac12932244b72a2e70b
COCOAPODS: 0.35.0
@@ -1 +0,0 @@
../../../HockeySDK/Classes/BITActivityIndicatorButton.h
-1
View File
@@ -1 +0,0 @@
../../../HockeySDK/Classes/BITAppStoreHeader.h
@@ -1 +0,0 @@
../../../HockeySDK/Classes/BITAppVersionMetaInfo.h
@@ -1 +0,0 @@
../../../HockeySDK/Classes/BITArrowImageAnnotation.h
-1
View File
@@ -1 +0,0 @@
../../../HockeySDK/Classes/BITAttributedLabel.h
@@ -1 +0,0 @@
../../../HockeySDK/Classes/BITAuthenticationViewController.h
-1
View File
@@ -1 +0,0 @@
../../../HockeySDK/Classes/BITAuthenticator.h
@@ -1 +0,0 @@
../../../HockeySDK/Classes/BITBlurImageAnnotation.h
-1
View File
@@ -1 +0,0 @@
../../../HockeySDK/Classes/BITCrashAttachment.h
-1
View File
@@ -1 +0,0 @@
../../../HockeySDK/Classes/BITCrashDetails.h
-1
View File
@@ -1 +0,0 @@
../../../HockeySDK/Classes/BITCrashManager.h
@@ -1 +0,0 @@
../../../HockeySDK/Classes/BITCrashManagerDelegate.h
-1
View File
@@ -1 +0,0 @@
../../../HockeySDK/Classes/BITCrashMetaData.h
@@ -1 +0,0 @@
../../../HockeySDK/Classes/BITCrashReportTextFormatter.h
-1
View File
@@ -1 +0,0 @@
../../../HockeySDK/Classes/BITFeedbackActivity.h
@@ -1 +0,0 @@
../../../HockeySDK/Classes/BITFeedbackComposeViewController.h
@@ -1 +0,0 @@
../../../HockeySDK/Classes/BITFeedbackComposeViewControllerDelegate.h
@@ -1 +0,0 @@
../../../HockeySDK/Classes/BITFeedbackListViewCell.h
@@ -1 +0,0 @@
../../../HockeySDK/Classes/BITFeedbackListViewController.h
-1
View File
@@ -1 +0,0 @@
../../../HockeySDK/Classes/BITFeedbackManager.h
@@ -1 +0,0 @@
../../../HockeySDK/Classes/BITFeedbackManagerDelegate.h
-1
View File
@@ -1 +0,0 @@
../../../HockeySDK/Classes/BITFeedbackMessage.h
@@ -1 +0,0 @@
../../../HockeySDK/Classes/BITFeedbackMessageAttachment.h
@@ -1 +0,0 @@
../../../HockeySDK/Classes/BITFeedbackUserDataViewController.h
-1
View File
@@ -1 +0,0 @@
../../../HockeySDK/Classes/BITHTTPOperation.h
-1
View File
@@ -1 +0,0 @@
../../../HockeySDK/Classes/BITHockeyAppClient.h
-1
View File
@@ -1 +0,0 @@
../../../HockeySDK/Classes/BITHockeyAttachment.h
@@ -1 +0,0 @@
../../../HockeySDK/Classes/BITHockeyBaseManager.h
@@ -1 +0,0 @@
../../../HockeySDK/Classes/BITHockeyBaseViewController.h
-1
View File
@@ -1 +0,0 @@
../../../HockeySDK/Classes/BITHockeyHelper.h
-1
View File
@@ -1 +0,0 @@
../../../HockeySDK/Classes/BITHockeyManager.h
@@ -1 +0,0 @@
../../../HockeySDK/Classes/BITHockeyManagerDelegate.h
-1
View File
@@ -1 +0,0 @@
../../../HockeySDK/Classes/BITImageAnnotation.h
@@ -1 +0,0 @@
../../../HockeySDK/Classes/BITImageAnnotationViewController.h
-1
View File
@@ -1 +0,0 @@
../../../HockeySDK/Classes/BITKeychainUtils.h
@@ -1 +0,0 @@
../../../HockeySDK/Classes/BITRectangleImageAnnotation.h
-1
View File
@@ -1 +0,0 @@
../../../HockeySDK/Classes/BITStoreButton.h
@@ -1 +0,0 @@
../../../HockeySDK/Classes/BITStoreUpdateManager.h
@@ -1 +0,0 @@
../../../HockeySDK/Classes/BITStoreUpdateManagerDelegate.h
-1
View File
@@ -1 +0,0 @@
../../../HockeySDK/Classes/BITUpdateManager.h
@@ -1 +0,0 @@
../../../HockeySDK/Classes/BITUpdateManagerDelegate.h
@@ -1 +0,0 @@
../../../HockeySDK/Classes/BITUpdateViewController.h
-1
View File
@@ -1 +0,0 @@
../../../HockeySDK/Classes/BITWebTableViewCell.h
-1
View File
@@ -1 +0,0 @@
../../../HockeySDK/Classes/HockeySDK.h
@@ -1 +0,0 @@
../../../HockeySDK/Classes/HockeySDKFeatureConfig.h
-33
View File
@@ -1,33 +0,0 @@
/*
* 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>
@interface BITActivityIndicatorButton : UIButton
- (void)setShowsActivityIndicator:(BOOL)showsIndicator;
@end
-76
View File
@@ -1,76 +0,0 @@
/*
* 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"
@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
-60
View File
@@ -1,60 +0,0 @@
/*
* Author: Andreas Linde <mail@andreaslinde.de>
* Peter Steinberger
*
* Copyright (c) 2012-2014 HockeyApp, Bit Stadium GmbH.
* Copyright (c) 2011-2012 Peter Steinberger.
* 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>
#ifndef __IPHONE_6_1
#define __IPHONE_6_1 60100
#endif
/**
* Header style depending on the iOS version
*/
typedef NS_ENUM(NSUInteger, BITAppStoreHeaderStyle) {
/**
* Default is iOS 6 style
*/
BITAppStoreHeaderStyleDefault = 0,
/**
* Draw header in the iOS 7 style
*/
BITAppStoreHeaderStyleOS7 = 1
};
@interface BITAppStoreHeader : UIView
@property (nonatomic, copy) NSString *headerText;
@property (nonatomic, copy) NSString *subHeaderText;
@property (nonatomic, strong) UIImage *iconImage;
@property (nonatomic, assign) BITAppStoreHeaderStyle style;
@end
-160
View File
@@ -1,160 +0,0 @@
/*
* Author: Andreas Linde <mail@andreaslinde.de>
* Peter Steinberger
*
* Copyright (c) 2012-2014 HockeyApp, Bit Stadium GmbH.
* Copyright (c) 2011-2012 Peter Steinberger.
* 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 "BITAppStoreHeader.h"
#import "BITHockeyHelper.h"
#import "HockeySDKPrivate.h"
#define kLightGrayColor BIT_RGBCOLOR(235, 235, 235)
#define kDarkGrayColor BIT_RGBCOLOR(186, 186, 186)
#define kWhiteBackgroundColorDefault BIT_RGBCOLOR(245, 245, 245)
#define kWhiteBackgroundColorOS7 BIT_RGBCOLOR(255, 255, 255)
#define kImageHeight 72
#define kImageBorderRadius 12
#define kImageBorderRadiusiOS7 16.5
#define kImageLeftMargin 14
#define kImageTopMargin 12
#define kTextRow kImageTopMargin*2 + kImageHeight
@implementation BITAppStoreHeader {
UILabel *_headerLabelView;
UILabel *_middleLabelView;
}
#pragma mark - NSObject
- (instancetype)initWithFrame:(CGRect)frame {
if ((self = [super initWithFrame:frame])) {
self.autoresizingMask = UIViewAutoresizingFlexibleWidth;
self.backgroundColor = kWhiteBackgroundColorDefault;
self.style = BITAppStoreHeaderStyleDefault;
}
return self;
}
#pragma mark - UIView
- (void)drawRect:(CGRect)rect {
CGRect bounds = self.bounds;
CGContextRef context = UIGraphicsGetCurrentContext();
if (self.style == BITAppStoreHeaderStyleDefault) {
// draw the gradient
NSArray *colors = [NSArray arrayWithObjects:(id)kDarkGrayColor.CGColor, (id)kLightGrayColor.CGColor, nil];
CGGradientRef gradient = CGGradientCreateWithColors(CGColorGetColorSpace((__bridge CGColorRef)[colors objectAtIndex:0]), (__bridge CFArrayRef)colors, (CGFloat[2]){0, 1});
CGPoint top = CGPointMake(CGRectGetMidX(bounds), bounds.size.height - 3);
CGPoint bottom = CGPointMake(CGRectGetMidX(bounds), CGRectGetMaxY(bounds));
CGContextDrawLinearGradient(context, gradient, top, bottom, 0);
CGGradientRelease(gradient);
} else {
// draw the line
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextSetLineWidth(ctx, 1.0);
CGContextSetStrokeColorWithColor(ctx, kDarkGrayColor.CGColor);
CGContextMoveToPoint(ctx, 0, CGRectGetMaxY(bounds));
CGContextAddLineToPoint( ctx, CGRectGetMaxX(bounds), CGRectGetMaxY(bounds));
CGContextStrokePath(ctx);
}
// icon
[_iconImage drawAtPoint:CGPointMake(kImageLeftMargin, kImageTopMargin)];
[super drawRect:rect];
}
- (void)layoutSubviews {
if (self.style == BITAppStoreHeaderStyleOS7)
self.backgroundColor = kWhiteBackgroundColorOS7;
[super layoutSubviews];
CGFloat globalWidth = self.frame.size.width;
// draw header name
UIColor *mainTextColor = BIT_RGBCOLOR(61, 61, 61);
UIColor *secondaryTextColor = BIT_RGBCOLOR(100, 100, 100);
UIFont *mainFont = [UIFont boldSystemFontOfSize:15];
UIFont *secondaryFont = [UIFont systemFontOfSize:10];
if (!_headerLabelView) _headerLabelView = [[UILabel alloc] init];
[_headerLabelView setFont:mainFont];
[_headerLabelView setFrame:CGRectMake(kTextRow, kImageTopMargin, globalWidth-kTextRow, 20)];
[_headerLabelView setTextColor:mainTextColor];
[_headerLabelView setBackgroundColor:[UIColor clearColor]];
[_headerLabelView setText:_headerText];
[self addSubview:_headerLabelView];
// middle
if (!_middleLabelView) _middleLabelView = [[UILabel alloc] init];
[_middleLabelView setFont:secondaryFont];
[_middleLabelView setFrame:CGRectMake(kTextRow, kImageTopMargin + 17, globalWidth-kTextRow, 20)];
[_middleLabelView setTextColor:secondaryTextColor];
[_middleLabelView setBackgroundColor:[UIColor clearColor]];
[_middleLabelView setText:_subHeaderText];
[self addSubview:_middleLabelView];
}
#pragma mark - Properties
- (void)setHeaderText:(NSString *)anHeaderText {
if (_headerText != anHeaderText) {
_headerText = [anHeaderText copy];
[self setNeedsDisplay];
}
}
- (void)setSubHeaderText:(NSString *)aSubHeaderText {
if (_subHeaderText != aSubHeaderText) {
_subHeaderText = [aSubHeaderText copy];
[self setNeedsDisplay];
}
}
- (void)setIconImage:(UIImage *)anIconImage {
if (_iconImage != anIconImage) {
// scale, make borders and reflection
_iconImage = bit_imageToFitSize(anIconImage, CGSizeMake(kImageHeight, kImageHeight), YES);
CGFloat radius = kImageBorderRadius;
if (self.style == BITAppStoreHeaderStyleOS7)
radius = kImageBorderRadiusiOS7;
_iconImage = bit_roundedCornerImage(_iconImage, radius, 0.0);
[self setNeedsDisplay];
}
}
@end
-58
View File
@@ -1,58 +0,0 @@
/*
* Author: Peter Steinberger
* Andreas Linde
*
* Copyright (c) 2012-2014 HockeyApp, Bit Stadium GmbH.
* Copyright (c) 2011 Andreas Linde, Peter Steinberger.
* 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 <Foundation/Foundation.h>
@interface BITAppVersionMetaInfo : NSObject {
}
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *version;
@property (nonatomic, copy) NSString *shortVersion;
@property (nonatomic, copy) NSString *minOSVersion;
@property (nonatomic, copy) NSString *notes;
@property (nonatomic, copy) NSDate *date;
@property (nonatomic, copy) NSNumber *size;
@property (nonatomic, copy) NSNumber *mandatory;
@property (nonatomic, copy) NSNumber *versionID;
@property (nonatomic, copy) NSDictionary *uuids;
- (NSString *)nameAndVersionString;
- (NSString *)versionString;
- (NSString *)dateString;
- (NSString *)sizeInMB;
- (NSString *)notesOrEmptyString;
- (void)setDateWithTimestamp:(NSTimeInterval)timestamp;
- (BOOL)isValid;
- (BOOL)hasUUID:(NSString *)uuid;
- (BOOL)isEqualToAppVersionMetaInfo:(BITAppVersionMetaInfo *)anAppVersionMetaInfo;
+ (BITAppVersionMetaInfo *)appVersionMetaInfoFromDict:(NSDictionary *)dict;
@end
-228
View File
@@ -1,228 +0,0 @@
/*
* Author: Peter Steinberger
* Andreas Linde
*
* Copyright (c) 2012-2014 HockeyApp, Bit Stadium GmbH.
* Copyright (c) 2011 Andreas Linde, Peter Steinberger.
* 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 "BITAppVersionMetaInfo.h"
#import "HockeySDKPrivate.h"
@implementation BITAppVersionMetaInfo
#pragma mark - Static
+ (BITAppVersionMetaInfo *)appVersionMetaInfoFromDict:(NSDictionary *)dict {
BITAppVersionMetaInfo *appVersionMetaInfo = [[[self class] alloc] init];
if ([dict isKindOfClass:[NSDictionary class]]) {
appVersionMetaInfo.name = [dict objectForKey:@"title"];
appVersionMetaInfo.version = [dict objectForKey:@"version"];
appVersionMetaInfo.shortVersion = [dict objectForKey:@"shortversion"];
appVersionMetaInfo.minOSVersion = [dict objectForKey:@"minimum_os_version"];
[appVersionMetaInfo setDateWithTimestamp:[[dict objectForKey:@"timestamp"] doubleValue]];
appVersionMetaInfo.size = [dict objectForKey:@"appsize"];
appVersionMetaInfo.notes = [dict objectForKey:@"notes"];
appVersionMetaInfo.mandatory = [dict objectForKey:@"mandatory"];
appVersionMetaInfo.versionID = [dict objectForKey:@"id"];
appVersionMetaInfo.uuids = [dict objectForKey:@"uuids"];
}
return appVersionMetaInfo;
}
#pragma mark - NSObject
- (BOOL)isEqual:(id)other {
if (other == self)
return YES;
if (!other || ![other isKindOfClass:[self class]])
return NO;
return [self isEqualToAppVersionMetaInfo:other];
}
- (BOOL)isEqualComparingString:(NSString *)stringA withString:(NSString *)stringB {
if ([stringA isKindOfClass:[NSString class]] && [stringB isKindOfClass:[NSString class]]) {
return [stringA isEqualToString:stringB];
}
return NO;
}
- (BOOL)isEqualComparingNumber:(NSNumber *)numberA withNumber:(NSNumber *)numberB {
if ([numberA isKindOfClass:[NSNumber class]] && [numberB isKindOfClass:[NSNumber class]]) {
return [numberA isEqualToNumber:numberB];
}
return NO;
}
- (BOOL)isEqualComparingDate:(NSDate *)dateA withDate:(NSDate *)dateB {
if ([dateA isKindOfClass:[NSDate class]] && [dateB isKindOfClass:[NSDate class]]) {
return [dateA isEqualToDate:dateB];
}
return NO;
}
- (BOOL)isEqualComparingDictionary:(NSDictionary *)dictA withDate:(NSDictionary *)dictB {
if ([dictA isKindOfClass:[NSDictionary class]] && [dictB isKindOfClass:[NSDictionary class]]) {
return [dictA isEqualToDictionary:dictB];
}
return NO;
}
- (BOOL)isEqualToAppVersionMetaInfo:(BITAppVersionMetaInfo *)anAppVersionMetaInfo {
if (self == anAppVersionMetaInfo)
return YES;
if (![self isEqualComparingString:self.name withString:anAppVersionMetaInfo.name])
return NO;
if (![self isEqualComparingString:self.version withString:anAppVersionMetaInfo.version])
return NO;
if (![self isEqualComparingString:self.shortVersion withString:anAppVersionMetaInfo.shortVersion])
return NO;
if (![self isEqualComparingString:self.minOSVersion withString:anAppVersionMetaInfo.minOSVersion])
return NO;
if (![self isEqualComparingString:self.notes withString:anAppVersionMetaInfo.notes])
return NO;
if (![self isEqualComparingDate:self.date withDate:anAppVersionMetaInfo.date])
return NO;
if (![self isEqualComparingNumber:self.size withNumber:anAppVersionMetaInfo.size])
return NO;
if (![self isEqualComparingNumber:self.mandatory withNumber:anAppVersionMetaInfo.mandatory])
return NO;
if (![self isEqualComparingDictionary:self.uuids withDate:anAppVersionMetaInfo.uuids])
return NO;
return YES;
}
#pragma mark - NSCoder
- (void)encodeWithCoder:(NSCoder *)encoder {
[encoder encodeObject:self.name forKey:@"name"];
[encoder encodeObject:self.version forKey:@"version"];
[encoder encodeObject:self.shortVersion forKey:@"shortVersion"];
[encoder encodeObject:self.minOSVersion forKey:@"minOSVersion"];
[encoder encodeObject:self.notes forKey:@"notes"];
[encoder encodeObject:self.date forKey:@"date"];
[encoder encodeObject:self.size forKey:@"size"];
[encoder encodeObject:self.mandatory forKey:@"mandatory"];
[encoder encodeObject:self.versionID forKey:@"versionID"];
[encoder encodeObject:self.uuids forKey:@"uuids"];
}
- (instancetype)initWithCoder:(NSCoder *)decoder {
if ((self = [super init])) {
self.name = [decoder decodeObjectForKey:@"name"];
self.version = [decoder decodeObjectForKey:@"version"];
self.shortVersion = [decoder decodeObjectForKey:@"shortVersion"];
self.minOSVersion = [decoder decodeObjectForKey:@"minOSVersion"];
self.notes = [decoder decodeObjectForKey:@"notes"];
self.date = [decoder decodeObjectForKey:@"date"];
self.size = [decoder decodeObjectForKey:@"size"];
self.mandatory = [decoder decodeObjectForKey:@"mandatory"];
self.versionID = [decoder decodeObjectForKey:@"versionID"];
self.uuids = [decoder decodeObjectForKey:@"uuids"];
}
return self;
}
#pragma mark - Properties
- (NSString *)nameAndVersionString {
NSString *appNameAndVersion = [NSString stringWithFormat:@"%@ %@", self.name, [self versionString]];
return appNameAndVersion;
}
- (NSString *)versionString {
NSString *shortString = ([self.shortVersion respondsToSelector:@selector(length)] && [self.shortVersion length]) ? [NSString stringWithFormat:@"%@", self.shortVersion] : @"";
NSString *versionString = [shortString length] ? [NSString stringWithFormat:@" (%@)", self.version] : self.version;
return [NSString stringWithFormat:@"%@ %@%@", BITHockeyLocalizedString(@"UpdateVersion"), shortString, versionString];
}
- (NSString *)dateString {
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateStyle:NSDateFormatterMediumStyle];
return [formatter stringFromDate:self.date];
}
- (NSString *)sizeInMB {
if ([_size isKindOfClass: [NSNumber class]] && [_size doubleValue] > 0) {
double appSizeInMB = [_size doubleValue]/(1024*1024);
NSString *appSizeString = [NSString stringWithFormat:@"%.1f MB", appSizeInMB];
return appSizeString;
}
return @"0 MB";
}
- (void)setDateWithTimestamp:(NSTimeInterval)timestamp {
if (timestamp) {
NSDate *appDate = [NSDate dateWithTimeIntervalSince1970:timestamp];
self.date = appDate;
} else {
self.date = nil;
}
}
- (NSString *)notesOrEmptyString {
if (self.notes) {
return self.notes;
}else {
return [NSString string];
}
}
// A valid app needs at least following properties: name, version, date
- (BOOL)isValid {
BOOL valid = [self.name length] && [self.version length] && self.date;
return valid;
}
- (BOOL)hasUUID:(NSString *)uuid {
if (!uuid) return NO;
if (!self.uuids) return NO;
__block BOOL hasUUID = NO;
[self.uuids enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop){
if (obj && [uuid compare:obj] == NSOrderedSame) {
hasUUID = YES;
*stop = YES;
}
}];
return hasUUID;
}
@end
-33
View File
@@ -1,33 +0,0 @@
/*
* Author: Moritz Haarmann <post@moritzhaarmann.de>
*
* 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"
@interface BITArrowImageAnnotation : BITImageAnnotation
@end
-204
View File
@@ -1,204 +0,0 @@
/*
* Author: Moritz Haarmann <post@moritzhaarmann.de>
*
* 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"
#define kArrowPointCount 7
@interface BITArrowImageAnnotation()
@property (nonatomic, strong) CAShapeLayer *shapeLayer;
@property (nonatomic, strong) CAShapeLayer *strokeLayer;
@end
@implementation BITArrowImageAnnotation
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
self.shapeLayer = [CAShapeLayer layer];
self.shapeLayer.strokeColor = [UIColor whiteColor].CGColor;
self.shapeLayer.lineWidth = 5;
self.shapeLayer.fillColor = [UIColor redColor].CGColor;
self.strokeLayer = [CAShapeLayer layer];
self.strokeLayer.strokeColor = [UIColor redColor].CGColor;
self.strokeLayer.lineWidth = 10;
self.strokeLayer.fillColor = [UIColor clearColor].CGColor;
[self.layer addSublayer:self.strokeLayer];
[self.layer addSublayer:self.shapeLayer];
}
return self;
}
- (void)buildShape {
CGFloat baseWidth = MAX(self.frame.size.width, self.frame.size.height);
CGFloat topHeight = MAX(baseWidth / 3.0f,10);
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(boundRect);
endX = CGRectGetMaxX(boundRect);
} else {
startX = CGRectGetMaxX(boundRect);
endX = CGRectGetMinX(boundRect);
}
if ( self.movedDelta.height < 0){
startY = CGRectGetMinY(boundRect);
endY = CGRectGetMaxY(boundRect);
} else {
startY = CGRectGetMaxY(boundRect);
endY = CGRectGetMinY(boundRect);
}
if (abs(CGRectGetWidth(boundRect)) < 30 || abs(CGRectGetHeight(boundRect)) < 30){
CGFloat smallerOne = MIN(abs(CGRectGetHeight(boundRect)), abs(CGRectGetWidth(boundRect)));
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;
[CATransaction begin];
[CATransaction setAnimationDuration:0];
self.strokeLayer.lineWidth = lineWidth/1.5f;
self.shapeLayer.lineWidth = lineWidth / 3.0f;
[CATransaction commit];
}
- (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 };
}
#pragma mark - UIView
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
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;
}
}
- (void)layoutSubviews{
[super layoutSubviews];
[self buildShape];
}
@end
-330
View File
@@ -1,330 +0,0 @@
// TTTAttributedLabel.h
//
// Copyright (c) 2011 Mattt Thompson (http://mattt.me)
//
// 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 <CoreText/CoreText.h>
/**
Vertical alignment for text in a label whose bounds are larger than its text bounds
*/
typedef enum {
BITAttributedLabelVerticalAlignmentCenter = 0,
BITAttributedLabelVerticalAlignmentTop = 1,
BITAttributedLabelVerticalAlignmentBottom = 2,
} BITAttributedLabelVerticalAlignment;
/**
Determines whether the text to which this attribute applies has a strikeout drawn through itself.
*/
extern NSString * const kBITStrikeOutAttributeName;
/**
The background fill color. Value must be a `CGColorRef`. Default value is `nil` (no fill).
*/
extern NSString * const kBITBackgroundFillColorAttributeName;
/**
The background stroke color. Value must be a `CGColorRef`. Default value is `nil` (no stroke).
*/
extern NSString * const kBITBackgroundStrokeColorAttributeName;
/**
The background stroke line width. Value must be an `NSNumber`. Default value is `1.0f`.
*/
extern NSString * const kBITBackgroundLineWidthAttributeName;
/**
The background corner radius. Value must be an `NSNumber`. Default value is `5.0f`.
*/
extern NSString * const kBITBackgroundCornerRadiusAttributeName;
@protocol BITAttributedLabelDelegate;
// Override UILabel @property to accept both NSString and NSAttributedString
@protocol BITAttributedLabel <NSObject>
@property (nonatomic, copy) id text;
@end
/**
`TTTAttributedLabel` is a drop-in replacement for `UILabel` that supports `NSAttributedString`, as well as automatically-detected and manually-added links to URLs, addresses, phone numbers, and dates.
# Differences Between `TTTAttributedLabel` and `UILabel`
For the most part, `TTTAttributedLabel` behaves just like `UILabel`. The following are notable exceptions, in which `TTTAttributedLabel` properties may act differently:
- `text` - This property now takes an `id` type argument, which can either be a kind of `NSString` or `NSAttributedString` (mutable or immutable in both cases)
- `lineBreakMode` - This property displays only the first line when the value is `UILineBreakModeHeadTruncation`, `UILineBreakModeTailTruncation`, or `UILineBreakModeMiddleTruncation`
- `adjustsFontsizeToFitWidth` - Supported in iOS 5 and greater, this property is effective for any value of `numberOfLines` greater than zero. In iOS 4, setting `numberOfLines` to a value greater than 1 with `adjustsFontSizeToFitWidth` set to `YES` may cause `sizeToFit` to execute indefinitely.
Any properties affecting text or paragraph styling, such as `shadowRadius` or `firstLineIndent` will only apply when text is set with an `NSString`. If the text is set with an `NSAttributedString`, these properties will not apply.
@warning Any properties changed on the label after setting the text will not be reflected until a subsequent call to `setText:` or `setText:afterInheritingLabelAttributesAndConfiguringWithBlock:`. This is to say, order of operations matters in this case. For example, if the label text color is originally black when the text is set, changing the text color to red will have no effect on the display of the label until the text is set once again.
*/
@interface BITAttributedLabel : UILabel <BITAttributedLabel, UIGestureRecognizerDelegate>
///-----------------------------
/// @name Accessing the Delegate
///-----------------------------
/**
The receiver's delegate.
@discussion A `TTTAttributedLabel` delegate responds to messages sent by tapping on links in the label. You can use the delegate to respond to links referencing a URL, address, phone number, date, or date with a specified time zone and duration.
*/
@property (nonatomic, unsafe_unretained) id <BITAttributedLabelDelegate> delegate;
///--------------------------------------------
/// @name Detecting, Accessing, & Styling Links
///--------------------------------------------
/**
A bitmask of `UIDataDetectorTypes` which are used to automatically detect links in the label text. This is `UIDataDetectorTypeNone` by default.
@warning You must specify `dataDetectorTypes` before setting the `text`, with either `setText:` or `setText:afterInheritingLabelAttributesAndConfiguringWithBlock:`.
*/
@property (nonatomic, assign) UIDataDetectorTypes dataDetectorTypes;
/**
An array of `NSTextCheckingResult` objects for links detected or manually added to the label text.
*/
@property (readonly, nonatomic, strong) NSArray *links;
/**
A dictionary containing the `NSAttributedString` attributes to be applied to links detected or manually added to the label text. The default link style is blue and underlined.
@warning You must specify `linkAttributes` before setting autodetecting or manually-adding links for these attributes to be applied.
*/
@property (nonatomic, strong) NSDictionary *linkAttributes;
/**
A dictionary containing the `NSAttributedString` attributes to be applied to links when they are in the active state. Supply `nil` or an empty dictionary to opt out of active link styling. The default active link style is red and underlined.
*/
@property (nonatomic, strong) NSDictionary *activeLinkAttributes;
///---------------------------------------
/// @name Accessing Text Style Attributes
///---------------------------------------
/**
The shadow blur radius for the label. A value of 0 indicates no blur, while larger values produce correspondingly larger blurring. This value must not be negative. The default value is 0.
*/
@property (nonatomic, assign) CGFloat shadowRadius;
///--------------------------------------------
/// @name Accessing Paragraph Style Attributes
///--------------------------------------------
/**
The distance, in points, from the leading margin of a frame to the beginning of the paragraph's first line. This value is always nonnegative, and is 0.0 by default.
*/
@property (nonatomic, assign) CGFloat firstLineIndent;
/**
The space in points added between lines within the paragraph. This value is always nonnegative and is 0.0 by default.
*/
@property (nonatomic, assign) CGFloat leading;
/**
The line height multiple. This value is 0.0 by default.
*/
@property (nonatomic, assign) CGFloat lineHeightMultiple;
/**
The distance, in points, from the margin to the text container. This value is `UIEdgeInsetsZero` by default.
@discussion The `UIEdgeInset` members correspond to paragraph style properties rather than a particular geometry, and can change depending on the writing direction.
## `UIEdgeInset` Member Correspondence With `CTParagraphStyleSpecifier` Values:
- `top`: `kCTParagraphStyleSpecifierParagraphSpacingBefore`
- `left`: `kCTParagraphStyleSpecifierHeadIndent`
- `bottom`: `kCTParagraphStyleSpecifierParagraphSpacing`
- `right`: `kCTParagraphStyleSpecifierTailIndent`
*/
@property (nonatomic, assign) UIEdgeInsets textInsets;
/**
The vertical text alignment for the label, for when the frame size is greater than the text rect size. The vertical alignment is `TTTAttributedLabelVerticalAlignmentCenter` by default.
*/
@property (nonatomic, assign) BITAttributedLabelVerticalAlignment verticalAlignment;
///----------------------------------
/// @name Setting the Text Attributes
///----------------------------------
/**
Sets the text displayed by the label.
@param text An `NSString` or `NSAttributedString` object to be displayed by the label. If the specified text is an `NSString`, the label will display the text like a `UILabel`, inheriting the text styles of the label. If the specified text is an `NSAttributedString`, the label text styles will be overridden by the styles specified in the attributed string.
@discussion This method overrides `UILabel -setText:` to accept both `NSString` and `NSAttributedString` objects. This string is `nil` by default.
*/
- (void)setText:(id)text;
/**
Sets the text displayed by the label, after configuring an attributed string containing the text attributes inherited from the label in a block.
@param text An `NSString` or `NSAttributedString` object to be displayed by the label.
@param block A block object that returns an `NSMutableAttributedString` object and takes a single argument, which is an `NSMutableAttributedString` object with the text from the first parameter, and the text attributes inherited from the label text styles. For example, if you specified the `font` of the label to be `[UIFont boldSystemFontOfSize:14]` and `textColor` to be `[UIColor redColor]`, the `NSAttributedString` argument of the block would be contain the `NSAttributedString` attribute equivalents of those properties. In this block, you can set further attributes on particular ranges.
@discussion This string is `nil` by default.
*/
- (void)setText:(id)text afterInheritingLabelAttributesAndConfiguringWithBlock:(NSMutableAttributedString *(^)(NSMutableAttributedString *mutableAttributedString))block;
///----------------------------------
/// @name Accessing the Text Attributes
///----------------------------------
/**
A copy of the label's current attributedText. This returns `nil` if an attributed string has never been set on the label.
*/
@property (readwrite, nonatomic, copy) NSAttributedString *attributedText;
///-------------------
/// @name Adding Links
///-------------------
/**
Adds a link to an `NSTextCheckingResult`.
@param result An `NSTextCheckingResult` representing the link's location and type.
*/
- (void)addLinkWithTextCheckingResult:(NSTextCheckingResult *)result;
/**
Adds a link to an `NSTextCheckingResult`.
@param result An `NSTextCheckingResult` representing the link's location and type.
@param attributes The attributes to be added to the text in the range of the specified link. If `nil`, no attributes are added.
*/
- (void)addLinkWithTextCheckingResult:(NSTextCheckingResult *)result attributes:(NSDictionary *)attributes;
/**
Adds a link to a URL for a specified range in the label text.
@param url The url to be linked to
@param range The range in the label text of the link. The range must not exceed the bounds of the receiver.
*/
- (void)addLinkToURL:(NSURL *)url withRange:(NSRange)range;
/**
Adds a link to an address for a specified range in the label text.
@param addressComponents A dictionary of address components for the address to be linked to
@param range The range in the label text of the link. The range must not exceed the bounds of the receiver.
@discussion The address component dictionary keys are described in `NSTextCheckingResult`'s "Keys for Address Components."
*/
- (void)addLinkToAddress:(NSDictionary *)addressComponents withRange:(NSRange)range;
/**
Adds a link to a phone number for a specified range in the label text.
@param phoneNumber The phone number to be linked to.
@param range The range in the label text of the link. The range must not exceed the bounds of the receiver.
*/
- (void)addLinkToPhoneNumber:(NSString *)phoneNumber withRange:(NSRange)range;
/**
Adds a link to a date for a specified range in the label text.
@param date The date to be linked to.
@param range The range in the label text of the link. The range must not exceed the bounds of the receiver.
*/
- (void)addLinkToDate:(NSDate *)date withRange:(NSRange)range;
/**
Adds a link to a date with a particular time zone and duration for a specified range in the label text.
@param date The date to be linked to.
@param timeZone The time zone of the specified date.
@param duration The duration, in seconds from the specified date.
@param range The range in the label text of the link. The range must not exceed the bounds of the receiver.
*/
- (void)addLinkToDate:(NSDate *)date timeZone:(NSTimeZone *)timeZone duration:(NSTimeInterval)duration withRange:(NSRange)range;
@end
/**
The `TTTAttributedLabelDelegate` protocol defines the messages sent to an attributed label delegate when links are tapped. All of the methods of this protocol are optional.
*/
@protocol BITAttributedLabelDelegate <NSObject>
///-----------------------------------
/// @name Responding to Link Selection
///-----------------------------------
@optional
/**
Tells the delegate that the user did select a link to a URL.
@param label The label whose link was selected.
@param url The URL for the selected link.
*/
- (void)attributedLabel:(BITAttributedLabel *)label didSelectLinkWithURL:(NSURL *)url;
/**
Tells the delegate that the user did select a link to an address.
@param label The label whose link was selected.
@param addressComponents The components of the address for the selected link.
*/
- (void)attributedLabel:(BITAttributedLabel *)label didSelectLinkWithAddress:(NSDictionary *)addressComponents;
/**
Tells the delegate that the user did select a link to a phone number.
@param label The label whose link was selected.
@param phoneNumber The phone number for the selected link.
*/
- (void)attributedLabel:(BITAttributedLabel *)label didSelectLinkWithPhoneNumber:(NSString *)phoneNumber;
/**
Tells the delegate that the user did select a link to a date.
@param label The label whose link was selected.
@param date The date for the selected link.
*/
- (void)attributedLabel:(BITAttributedLabel *)label didSelectLinkWithDate:(NSDate *)date;
/**
Tells the delegate that the user did select a link to a date with a time zone and duration.
@param label The label whose link was selected.
@param date The date for the selected link.
@param timeZone The time zone of the date for the selected link.
@param duration The duration, in seconds from the date for the selected link.
*/
- (void)attributedLabel:(BITAttributedLabel *)label didSelectLinkWithDate:(NSDate *)date timeZone:(NSTimeZone *)timeZone duration:(NSTimeInterval)duration;
/**
Tells the delegate that the user did select a link to a text checking result.
@discussion This method is called if no other delegate method was called, which can occur by either now implementing the method in `TTTAttributedLabelDelegate` corresponding to a particular link, or the link was added by passing an instance of a custom `NSTextCheckingResult` subclass into `-addLinkWithTextCheckingResult:`.
@param label The label whose link was selected.
@param result The custom text checking result.
*/
- (void)attributedLabel:(BITAttributedLabel *)label didSelectLinkWithTextCheckingResult:(NSTextCheckingResult *)result;
@end
File diff suppressed because it is too large. Load diff
@@ -1,94 +0,0 @@
/*
* Author: Stephan Diederich
*
* Copyright (c) 2013-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>
@protocol BITAuthenticationViewControllerDelegate;
@class BITAuthenticator;
@class BITHockeyAppClient;
/**
* View controller handling user interaction for `BITAuthenticator`
*/
@interface BITAuthenticationViewController : UITableViewController
- (instancetype) initWithDelegate:(id<BITAuthenticationViewControllerDelegate>) delegate;
/**
* can be set to YES to show an additional button + description text
* and allowing to login via external website/UDID.
* if this is set to yes, no further email/password options are shown
*
* defaults to NO
*/
@property (nonatomic, assign) BOOL showsLoginViaWebButton;
/**
* Description shown on top of view. Should tell why this view
* was presented and what's next.
*/
@property (nonatomic, copy) NSString* tableViewTitle;
/**
* can be set to YES to also require the users password
*
* defaults to NO
*/
@property (nonatomic, assign) BOOL requirePassword;
@property (nonatomic, weak) id<BITAuthenticationViewControllerDelegate> delegate;
/**
* allows to pre-fill the email-addy
*/
@property (nonatomic, copy) NSString* email;
@end
/**
* BITAuthenticationViewController protocol
*/
@protocol BITAuthenticationViewControllerDelegate<NSObject>
- (void) authenticationViewControllerDidTapWebButton:(UIViewController*) viewController;
/**
* called when the user wants to login
*
* @param viewController the delegating viewcontroller
* @param email the content of the email-field
* @param password the content of the password-field (if existent)
* @param completion Must be called by the delegate once the auth-task completed
* This viewcontroller shows an activity-indicator in between and blocks
* the UI. if succeeded is NO, it shows an alertView presenting the error
* given by the completion block
*/
- (void) authenticationViewController:(UIViewController*) viewController
handleAuthenticationWithEmail:(NSString*) email
password:(NSString*) password
completion:(void(^)(BOOL succeeded, NSError *error)) completion;
@end
@@ -1,325 +0,0 @@
/*
* Author: Stephan Diederich
*
* Copyright (c) 2013-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 "BITAuthenticationViewController.h"
#import "BITAuthenticator_Private.h"
#import "HockeySDKPrivate.h"
#import "HockeySDK.h"
#import "BITHockeyHelper.h"
#import "BITHockeyAppClient.h"
@interface BITAuthenticationViewController ()<UITextFieldDelegate> {
UIStatusBarStyle _statusBarStyle;
__weak UITextField *_emailField;
}
@property (nonatomic, copy) NSString *password;
@end
@implementation BITAuthenticationViewController
- (instancetype) initWithDelegate:(id<BITAuthenticationViewControllerDelegate>)delegate {
self = [super initWithStyle:UITableViewStyleGrouped];
if (self) {
self.title = BITHockeyLocalizedString(@"HockeyAuthenticatorViewControllerTitle");
_delegate = delegate;
}
return self;
}
#pragma mark - view lifecycle
- (void)viewDidLoad {
[super viewDidLoad];
[self.tableView setScrollEnabled:NO];
[self updateWebLoginButton];
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
_statusBarStyle = [[UIApplication sharedApplication] statusBarStyle];
#if __IPHONE_OS_VERSION_MAX_ALLOWED > __IPHONE_6_1
[[UIApplication sharedApplication] setStatusBarStyle:(self.navigationController.navigationBar.barStyle == UIBarStyleDefault) ? UIStatusBarStyleDefault : UIStatusBarStyleLightContent];
#else
[[UIApplication sharedApplication] setStatusBarStyle:(self.navigationController.navigationBar.barStyle == UIBarStyleDefault) ? UIStatusBarStyleDefault : UIStatusBarStyleBlackOpaque];
#endif
[self updateBarButtons];
self.navigationItem.rightBarButtonItem.enabled = [self allRequiredFieldsEntered];
}
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
[[UIApplication sharedApplication] setStatusBarStyle:_statusBarStyle];
}
#pragma mark - Property overrides
- (void) updateBarButtons {
if(self.showsLoginViaWebButton) {
self.navigationItem.rightBarButtonItem = nil;
} else {
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone
target:self
action:@selector(saveAction:)];
}
}
- (void)setShowsLoginViaWebButton:(BOOL)showsLoginViaWebButton {
if(_showsLoginViaWebButton != showsLoginViaWebButton) {
_showsLoginViaWebButton = showsLoginViaWebButton;
if(self.isViewLoaded) {
[self.tableView reloadData];
[self updateBarButtons];
[self updateWebLoginButton];
}
}
}
- (void) updateWebLoginButton {
if(self.showsLoginViaWebButton) {
static const CGFloat kFooterHeight = 60.f;
UIView *containerView = [[UIView alloc] initWithFrame:CGRectMake(0, 0,
CGRectGetWidth(self.tableView.bounds),
kFooterHeight)];
UIButton *button = [UIButton buttonWithType:kBITButtonTypeSystem];
[button setTitle:BITHockeyLocalizedString(@"HockeyAuthenticationViewControllerWebLoginButtonTitle") forState:UIControlStateNormal];
CGSize buttonSize = [button sizeThatFits:CGSizeMake(CGRectGetWidth(self.tableView.bounds),
kFooterHeight)];
button.frame = CGRectMake(floorf((CGRectGetWidth(containerView.bounds) - buttonSize.width) / 2.f),
floorf((kFooterHeight - buttonSize.height) / 2.f),
buttonSize.width,
buttonSize.height);
button.autoresizingMask = UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin;
if ([UIButton instancesRespondToSelector:(NSSelectorFromString(@"setTintColor:"))]) {
[button setTitleColor:BIT_RGBCOLOR(0, 122, 255) forState:UIControlStateNormal];
}
[containerView addSubview:button];
[button addTarget:self
action:@selector(handleWebLoginButton:)
forControlEvents:UIControlEventTouchUpInside];
self.tableView.tableFooterView = containerView;
} else {
self.tableView.tableFooterView = nil;
}
}
- (IBAction) handleWebLoginButton:(id)sender {
[self.delegate authenticationViewControllerDidTapWebButton:self];
}
- (void)setEmail:(NSString *)email {
_email = email;
if(self.isViewLoaded) {
_emailField.text = email;
}
}
- (void)setTableViewTitle:(NSString *)viewDescription {
_tableViewTitle = [viewDescription copy];
if(self.isViewLoaded) {
[self.tableView reloadData];
}
}
#pragma mark - UIViewController Rotation
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)orientation {
return YES;
}
#pragma mark - Private methods
- (BOOL)allRequiredFieldsEntered {
if (self.requirePassword && [self.password length] == 0)
return NO;
if (![self.email length] || !bit_validateEmail(self.email))
return NO;
return YES;
}
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 2;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
if (section == 0) return 0;
if(self.showsLoginViaWebButton) {
return 0;
} else {
NSInteger rows = 1;
if ([self requirePassword]) rows ++;
return rows;
}
}
- (NSString *)tableView:(UITableView *)tableView titleForFooterInSection:(NSInteger)section {
if (section == 0) {
return self.tableViewTitle;
}
return nil;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = @"InputCell";
UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
cell.accessoryType = UITableViewCellAccessoryNone;
cell.selectionStyle = UITableViewCellSelectionStyleNone;
cell.backgroundColor = [UIColor whiteColor];
UITextField *textField = [[UITextField alloc] initWithFrame:CGRectMake(130, 11, self.view.frame.size.width - 130 - 25, 24)];
if (UI_USER_INTERFACE_IDIOM() != UIUserInterfaceIdiomPad) {
textField.autoresizingMask = UIViewAutoresizingFlexibleWidth;
}
textField.adjustsFontSizeToFitWidth = YES;
textField.textColor = [UIColor blackColor];
textField.backgroundColor = [UIColor lightGrayColor];
if (0 == [indexPath row]) {
textField.placeholder = BITHockeyLocalizedString(@"HockeyAuthenticationViewControllerEmailPlaceholder");
textField.text = self.email;
_emailField = textField;
textField.keyboardType = UIKeyboardTypeEmailAddress;
if ([self requirePassword])
textField.returnKeyType = UIReturnKeyNext;
else
textField.returnKeyType = UIReturnKeyDone;
[textField addTarget:self action:@selector(userEmailEntered:) forControlEvents:UIControlEventEditingChanged];
[textField becomeFirstResponder];
} else {
textField.placeholder = BITHockeyLocalizedString(@"HockeyAuthenticationViewControllerPasswordPlaceholder");
textField.text = self.password;
textField.keyboardType = UIKeyboardTypeDefault;
textField.returnKeyType = UIReturnKeyDone;
textField.secureTextEntry = YES;
[textField addTarget:self action:@selector(userPasswordEntered:) forControlEvents:UIControlEventEditingChanged];
}
textField.backgroundColor = [UIColor whiteColor];
textField.autocorrectionType = UITextAutocorrectionTypeNo;
textField.autocapitalizationType = UITextAutocapitalizationTypeNone;
textField.textAlignment = kBITTextLabelAlignmentLeft;
textField.delegate = self;
textField.tag = indexPath.row;
textField.clearButtonMode = UITextFieldViewModeWhileEditing;
[textField setEnabled: YES];
[cell addSubview:textField];
}
if (0 == [indexPath row]) {
cell.textLabel.text = BITHockeyLocalizedString(@"HockeyAuthenticationViewControllerEmailDescription");
} else {
cell.textLabel.text = BITHockeyLocalizedString(@"HockeyAuthenticationViewControllerPasswordDescription");
}
return cell;
}
- (void)userEmailEntered:(id)sender {
self.email = [(UITextField *)sender text];
self.navigationItem.rightBarButtonItem.enabled = [self allRequiredFieldsEntered];
}
- (void)userPasswordEntered:(id)sender {
self.password = [(UITextField *)sender text];
self.navigationItem.rightBarButtonItem.enabled = [self allRequiredFieldsEntered];
}
#pragma mark - UITextFieldDelegate
- (BOOL)textFieldShouldReturn:(UITextField *)textField {
NSInteger nextTag = textField.tag + 1;
UIResponder* nextResponder = [self.view viewWithTag:nextTag];
if (nextResponder) {
[nextResponder becomeFirstResponder];
} else {
if ([self allRequiredFieldsEntered]) {
if ([textField isFirstResponder])
[textField resignFirstResponder];
[self saveAction:nil];
}
}
return NO;
}
#pragma mark - Actions
- (void)saveAction:(id)sender {
[self setLoginUIEnabled:NO];
__weak typeof(self) weakSelf = self;
[self.delegate authenticationViewController:self
handleAuthenticationWithEmail:self.email
password:self.password
completion:^(BOOL succeeded, NSError *error) {
if(succeeded) {
//controller should dismiss us shortly..
} else {
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:nil
message:error.localizedDescription
delegate:nil
cancelButtonTitle:BITHockeyLocalizedString(@"OK")
otherButtonTitles:nil];
[alertView show];
typeof(self) strongSelf = weakSelf;
[strongSelf setLoginUIEnabled:YES];
}
}];
}
- (void) setLoginUIEnabled:(BOOL) enabled {
self.navigationItem.rightBarButtonItem.enabled = enabled;
self.tableView.userInteractionEnabled = enabled;
}
@end
-385
View File
@@ -1,385 +0,0 @@
/*
* Author: Stephan Diederich, Andreas Linde
*
* Copyright (c) 2013-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 <Foundation/Foundation.h>
#import "BITHockeyBaseManager.h"
/**
* Identification Types
*/
typedef NS_ENUM(NSUInteger, BITAuthenticatorIdentificationType) {
/**
* Assigns this app an anonymous user id.
* <br/><br/>
* The user will not be asked anything and an anonymous ID will be generated.
* This helps identifying this installation being unique but HockeyApp won't be able
* to identify who actually is running this installation and on which device
* the app is installed.
*/
BITAuthenticatorIdentificationTypeAnonymous,
/**
* Ask for the HockeyApp account email
* <br/><br/>
* This will present a user interface requesting the user to provide their
* HockeyApp user email address.
* <br/><br/>
* The provided email address has to match an email address of a registered
* HockeyApp user who is a member or tester of the app
*/
BITAuthenticatorIdentificationTypeHockeyAppEmail,
/**
* Ask for the HockeyApp account by email and password
* <br/><br/>
* This will present a user interface requesting the user to provide their
* HockeyApp user credentials.
* <br/><br/>
* The provided user account has to match a registered HockeyApp user who is
* a member or tester of the app
*/
BITAuthenticatorIdentificationTypeHockeyAppUser,
/**
* Identifies the current device
* <br/><br/>
* This will open the HockeyApp web page on the device in Safari and request the user
* to submit the device's unique identifier to the app. If the web page session is not aware
* of the current devices UDID, it will request the user to install the HockeyApp web clip
* which will provide the UDID to users session in the browser.
* <br/><br/>
* This requires the app to register an URL scheme. See the linked property and methods
* for further documentation on this.
*/
BITAuthenticatorIdentificationTypeDevice,
/**
* Ask for the HockeyApp account email.
* <br/><br/>
* This will present a user interface requesting the user to start a Safari based
* flow to login to HockeyApp (if not already logged in) and to share the hockeyapp
* account's email.
* <br/><br/>
* If restrictApplicationUsage is enabled, the provided user account has to match a
* registered HockeyApp user who is a member or tester of the app.
* For identification purpose any HockeyApp user is allowed.
*/
BITAuthenticatorIdentificationTypeWebAuth,
};
/**
* Restriction enforcement styles
*
* Specifies how often the Authenticator checks if the user is allowed to use
* this app.
*/
typedef NS_ENUM(NSUInteger, BITAuthenticatorAppRestrictionEnforcementFrequency) {
/**
* Checks if the user is allowed to use the app at the first time a version is started
*/
BITAuthenticatorAppRestrictionEnforcementOnFirstLaunch,
/**
* Checks if the user is allowed to use the app everytime the app becomes active
*/
BITAuthenticatorAppRestrictionEnforcementOnAppActive,
};
@protocol BITAuthenticatorDelegate;
/**
* Identify and authenticate users of Ad-Hoc or Enterprise builds
*
* `BITAuthenticator` serves 2 purposes:
*
* 1. Identifying who is running your Ad-Hoc or Enterprise builds
* `BITAuthenticator` provides an identifier for the rest of the HockeySDK
* to work with, e.g. in-app update checks and crash reports.
*
* 2. Optional regular checking if an identified user is still allowed
* to run this application. The `BITAuthenticator` can be used to make
* sure only users who are testers of your app are allowed to run it.
*
* This module automatically disables itself when running in an App Store build by default!
*
* @warning It is mandatory to call `authenticateInstallation` somewhen after calling
* `[[BITHockeyManager sharedHockeyManager] startManager]` or fully customize the identification
* and validation workflow yourself.
* If your app shows a modal view on startup, make sure to call `authenticateInstallation`
* either once your modal view is fully presented (e.g. its `viewDidLoad:` method is processed)
* or once your modal view is dismissed.
*/
@interface BITAuthenticator : BITHockeyBaseManager
#pragma mark - Configuration
///-----------------------------------------------------------------------------
/// @name Configuration
///-----------------------------------------------------------------------------
/**
* Defines the identification mechanism to be used
*
* _Default_: `BITAuthenticatorIdentificationTypeAnonymous`
*
* @see BITAuthenticatorIdentificationType
*/
@property (nonatomic, assign) BITAuthenticatorIdentificationType identificationType;
/**
* Enables or disables checking if the user is allowed to run this app
*
* If disabled, the Authenticator never validates, besides initial identification,
* if the user is allowed to run this application.
*
* If enabled, the Authenticator checks depending on `restrictionEnforcementFrequency`
* if the user is allowed to use this application.
*
* Enabling this property and setting `identificationType` to `BITAuthenticatorIdentificationTypeHockeyAppEmail`,
* `BITAuthenticatorIdentificationTypeHockeyAppUser` or `BITAuthenticatorIdentificationTypeWebAuth` also allows
* to remove access for users by removing them from the app's users list on HockeyApp.
*
* _Default_: `NO`
*
* @warning if `identificationType` is set to `BITAuthenticatorIdentificationTypeAnonymous`,
* this property has no effect.
*
* @see BITAuthenticatorIdentificationType
* @see restrictionEnforcementFrequency
*/
@property (nonatomic, assign) BOOL restrictApplicationUsage;
/**
* Defines how often the BITAuthenticator checks if the user is allowed
* to run this application
*
* This requires `restrictApplicationUsage` to be enabled.
*
* _Default_: `BITAuthenticatorAppRestrictionEnforcementOnFirstLaunch`
*
* @see BITAuthenticatorAppRestrictionEnforcementFrequency
* @see restrictApplicationUsage
*/
@property (nonatomic, assign) BITAuthenticatorAppRestrictionEnforcementFrequency restrictionEnforcementFrequency;
/**
* The authentication secret from HockeyApp. To find the right secret,
* click on your app on the HockeyApp dashboard, then on Show next to
* "Secret:".
*
* This is only needed if `identificationType` is set to `BITAuthenticatorIdentificationTypeHockeyAppEmail`
*
* @see identificationType
*/
@property (nonatomic, copy) NSString *authenticationSecret;
#pragma mark - Device based identification
///-----------------------------------------------------------------------------
/// @name Device based identification
///-----------------------------------------------------------------------------
/**
* The baseURL of the webpage the user is redirected to if `identificationType` is
* set to `BITAuthenticatorIdentificationTypeDevice`; defaults to https://rink.hockeyapp.net.
*
* @see identificationType
*/
@property (nonatomic, strong) NSURL *webpageURL;
/**
* URL to query the device's id via external webpage
* Built with the baseURL set in `webpageURL`.
*/
- (NSURL*) deviceAuthenticationURL;
/**
* The url-scheme used to idenfify via `BITAuthenticatorIdentificationTypeDevice`
*
* Please make sure that the URL scheme is unique and not shared with other apps.
*
* If set to nil, the default scheme is used which is `ha<APP_ID>`.
*
* @see identificationType
* @see handleOpenURL:sourceApplication:annotation:
*/
@property (nonatomic, strong) NSString *urlScheme;
/**
Should be used by the app-delegate to forward handle application:openURL:sourceApplication:annotation: calls.
This is required if `identificationType` is set to `BITAuthenticatorIdentificationTypeDevice`.
Your app needs to implement the default `ha<APP_ID>` URL scheme or register its own scheme
via `urlScheme`.
BITAuthenticator checks if the given URL is actually meant to be parsed by it and will
return NO if it doesn't think so. It does this by checking the 'host'-part of the URL to be 'authorize', as well
as checking the protocol part.
Please make sure that if you're using a custom URL scheme, it does _not_ conflict with BITAuthenticator's.
If BITAuthenticator thinks the URL was meant to be an authorization URL, but could not find a valid token, it will
reset the stored identification token and state.
Sample usage (in AppDelegate):
- (BOOL)application:(UIApplication *)application
openURL:(NSURL *)url
sourceApplication:(NSString *)sourceApplication annotation:(id)annotation {
if ([[BITHockeyManager sharedHockeyManager].authenticator handleOpenURL:url
sourceApplication:sourceApplication
annotation:annotation]) {
return YES;
} else {
//do your own URL handling, return appropriate value
}
return NO;
}
@param url Param `url` that was passed to the app
@param sourceApplication Param `sourceApplication` that was passed to the app
@param annotation Param `annotation` that was passed to the app
@return YES if the URL request was handled, NO if the URL could not be handled/identified.
@see identificationType
@see urlScheme
*/
- (BOOL) handleOpenURL:(NSURL *) url
sourceApplication:(NSString *) sourceApplication
annotation:(id) annotation;
#pragma mark - Authentication
///-----------------------------------------------------------------------------
/// @name Authentication
///-----------------------------------------------------------------------------
/**
* Invoked automatic identification and validation
*
* If the `BITAuthenticator` is in automatic mode this will initiate identifying
* the current user according to the type specified in `identificationType` and
* validate if the identified user is allowed to run this application.
*
* If the user is not yet identified it will present a modal view asking the user to
* provide the required information.
*
* If your app provides it's own startup modal screen, e.g. a guide or a login, then
* you might either call this method once that UI is fully presented or once
* the user e.g. did actually login already.
*
* @warning You need to call this method in your code even if automatic mode is enabled!
*
* @see identificationType
*/
- (void) authenticateInstallation;
/**
* Identifies the user according to the type specified in `identificationType`.
*
* If the `BITAuthenticator` is in manual mode, it's your responsibility to call
* this method. Depending on the `identificationType`, this method
* might present a viewController to let the user enter his/her credentials.
*
* If the Authenticator is in auto-mode, this is called by the authenticator itself
* once needed.
*
* @see identificationType
* @see authenticateInstallation
* @see validateWithCompletion:
*
* @param completion Block being executed once identification completed
*/
- (void) identifyWithCompletion:(void(^)(BOOL identified, NSError *error)) completion;
/**
* Returns YES if this app is identified according to the setting in `identificationType`.
*
* Since the identification process is done asynchronously (contacting the server),
* you need to observe the value change via KVO.
*
* @see identificationType
*/
@property (nonatomic, assign, readonly, getter = isIdentified) BOOL identified;
/**
* Validates if the identified user is allowed to run this application. This checks
* with the HockeyApp backend and calls the completion-block once completed.
*
* If the `BITAuthenticator` is in manual mode, it's your responsibility to call
* this method. If the application is not yet identified, validation is not possible
* and the completion-block is called with an error set.
*
* If the `BITAuthenticator` is in auto-mode, this is called by the authenticator itself
* once needed.
*
* @see identificationType
* @see authenticateInstallation
* @see identifyWithCompletion:
*
* @param completion Block being executed once validation completed
*/
- (void) validateWithCompletion:(void(^)(BOOL validated, NSError *error)) completion;
/**
* Indicates if this installation is validated.
*/
@property (nonatomic, assign, readonly, getter = isValidated) BOOL validated;
/**
* Removes all previously stored authentication tokens, UDIDs, etc.
*/
- (void) cleanupInternalStorage;
/**
* Returns different values depending on `identificationType`. This can be used
* by the application to identify the user.
*
* @see identificationType
*/
- (NSString*) publicInstallationIdentifier;
@end
#pragma mark - Protocol
/**
* `BITAuthenticator` protocol
*/
@protocol BITAuthenticatorDelegate <NSObject>
@optional
/**
* If the authentication (or validation) needs to identify the user,
* this delegate method is called with the viewController that we'll present.
*
* @param authenticator `BITAuthenticator` object
* @param viewController `UIViewController` used to identify the user
*
*/
- (void) authenticator:(BITAuthenticator *)authenticator willShowAuthenticationController:(UIViewController*) viewController;
@end
-916
View File
@@ -1,916 +0,0 @@
/*
* Author: Stephan Diederich
*
* Copyright (c) 2013-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 "BITAuthenticator.h"
#import "HockeySDK.h"
#import "HockeySDKPrivate.h"
#import "BITAuthenticator_Private.h"
#import "BITHTTPOperation.h"
#import "BITHockeyAppClient.h"
#import "BITHockeyHelper.h"
#include <sys/stat.h>
static NSString* const kBITAuthenticatorUUIDKey = @"BITAuthenticatorUUIDKey";
static NSString* const kBITAuthenticatorIdentifierKey = @"BITAuthenticatorIdentifierKey";
static NSString* const kBITAuthenticatorIdentifierTypeKey = @"BITAuthenticatorIdentifierTypeKey";
static NSString* const kBITAuthenticatorLastAuthenticatedVersionKey = @"BITAuthenticatorLastAuthenticatedVersionKey";
static NSString* const kBITAuthenticatorUserEmailKey = @"BITAuthenticatorUserEmailKey";
//deprecated
static NSString* const kBITAuthenticatorAuthTokenKey = @"BITAuthenticatorAuthTokenKey";
static NSString* const kBITAuthenticatorAuthTokenTypeKey = @"BITAuthenticatorAuthTokenTypeKey";
typedef unsigned int bit_uint32;
static unsigned char kBITPNGHeader[8] = {137, 80, 78, 71, 13, 10, 26, 10};
static unsigned char kBITPNGEndChunk[4] = {0x49, 0x45, 0x4e, 0x44};
@implementation BITAuthenticator {
id _appDidBecomeActiveObserver;
id _appDidEnterBackgroundOberser;
UIViewController *_authenticationController;
BOOL _isSetup;
}
- (void)dealloc {
[self unregisterObservers];
}
- (instancetype) initWithAppIdentifier:(NSString *)appIdentifier isAppStoreEnvironment:(BOOL)isAppStoreEnvironment {
self = [super initWithAppIdentifier:appIdentifier isAppStoreEnvironment:isAppStoreEnvironment];
if( self ) {
_webpageURL = [NSURL URLWithString:@"https://rink.hockeyapp.net/"];
_identificationType = BITAuthenticatorIdentificationTypeAnonymous;
_isSetup = NO;
_restrictApplicationUsage = NO;
_restrictionEnforcementFrequency = BITAuthenticatorAppRestrictionEnforcementOnFirstLaunch;
}
return self;
}
#pragma mark - BITHockeyBaseManager overrides
- (void)startManager {
//disabled in the appStore
if([self isAppStoreEnvironment]) return;
_isSetup = YES;
}
#pragma mark -
- (void)dismissAuthenticationControllerAnimated:(BOOL)animated completion:(void (^)(void))completion {
if (!_authenticationController) return;
UIViewController *presentingViewController = [_authenticationController presentingViewController];
// If there is no presenting view controller just remove view
if (presentingViewController) {
[_authenticationController dismissViewControllerAnimated:animated completion:completion];
} else {
[_authenticationController.navigationController.view removeFromSuperview];
if (completion) {
completion();
}
}
_authenticationController = nil;
}
- (void)authenticateInstallation {
//disabled in the appStore
if([self isAppStoreEnvironment]) return;
// make sure this is called after startManager so all modules are fully setup
if (!_isSetup) {
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(authenticateInstallation) object:nil];
[self performSelector:@selector(authenticateInstallation) withObject:nil afterDelay:0.1];
} else {
switch ([[UIApplication sharedApplication] applicationState]) {
case UIApplicationStateActive:
[self authenticate];
break;
case UIApplicationStateBackground:
case UIApplicationStateInactive:
// do nothing, wait for active state
break;
}
}
[self registerObservers];
}
- (void) authenticate {
[self identifyWithCompletion:^(BOOL identified, NSError *error) {
if(identified) {
if([self needsValidation]) {
[self validate];
} else {
[self dismissAuthenticationControllerAnimated:YES completion:nil];
}
} else {
BITHockeyLog(@"Failed to identify. Error: %@", error);
}
}];
}
- (BOOL) needsValidation {
if(BITAuthenticatorIdentificationTypeAnonymous == self.identificationType) {
return NO;
}
if(NO == self.restrictApplicationUsage) {
return NO;
}
if(self.restrictionEnforcementFrequency == BITAuthenticatorAppRestrictionEnforcementOnFirstLaunch &&
![self.executableUUID isEqualToString:self.lastAuthenticatedVersion]) {
return YES;
}
if(NO == self.isValidated && self.restrictionEnforcementFrequency == BITAuthenticatorAppRestrictionEnforcementOnAppActive) {
return YES;
}
return NO;
}
- (void)alertOnFailureStoringTokenInKeychain {
if ([[UIApplication sharedApplication] applicationState] != UIApplicationStateActive) {
return;
}
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:nil
message:BITHockeyLocalizedString(@"HockeyAuthenticationViewControllerStorageError")
delegate:self
cancelButtonTitle:BITHockeyLocalizedString(@"HockeyOK")
otherButtonTitles:nil];
[alertView setTag:1];
[alertView show];
}
- (void) identifyWithCompletion:(void (^)(BOOL identified, NSError *))completion {
if(_authenticationController) {
BITHockeyLog(@"Authentication controller already visible. Ignoring identify request");
if(completion) completion(NO, nil);
return;
}
//first check if the stored identification type matches the one currently configured
NSString *storedTypeString = [self stringValueFromKeychainForKey:kBITAuthenticatorIdentifierTypeKey];
NSString *configuredTypeString = [self.class stringForIdentificationType:self.identificationType];
if(storedTypeString && ![storedTypeString isEqualToString:configuredTypeString]) {
BITHockeyLog(@"Identification type mismatch for stored auth-token. Resetting.");
[self storeInstallationIdentifier:nil withType:BITAuthenticatorIdentificationTypeAnonymous];
}
NSString *identification = [self installationIdentifier];
if(identification) {
self.identified = YES;
if(completion) completion(YES, nil);
return;
}
[self processFullSizeImage];
if (self.identified) {
if(completion) completion(YES, nil);
return;
}
//it's not identified yet, do it now
BITAuthenticationViewController *viewController = nil;
switch (self.identificationType) {
case BITAuthenticatorIdentificationTypeAnonymous:
[self storeInstallationIdentifier:bit_UUID() withType:BITAuthenticatorIdentificationTypeAnonymous];
self.identified = YES;
if(completion) completion(YES, nil);
return;
break;
case BITAuthenticatorIdentificationTypeHockeyAppUser:
viewController = [[BITAuthenticationViewController alloc] initWithDelegate:self];
viewController.requirePassword = YES;
viewController.tableViewTitle = BITHockeyLocalizedString(@"HockeyAuthenticationViewControllerDataEmailAndPasswordDescription");
break;
case BITAuthenticatorIdentificationTypeDevice:
viewController = [[BITAuthenticationViewController alloc] initWithDelegate:self];
viewController.requirePassword = NO;
viewController.showsLoginViaWebButton = YES;
viewController.tableViewTitle = BITHockeyLocalizedString(@"HockeyAuthenticationViewControllerWebUDIDLoginDescription");
break;
case BITAuthenticatorIdentificationTypeWebAuth:
viewController = [[BITAuthenticationViewController alloc] initWithDelegate:self];
viewController.requirePassword = NO;
viewController.showsLoginViaWebButton = YES;
viewController.tableViewTitle = BITHockeyLocalizedString(@"HockeyAuthenticationViewControllerWebAuthLoginDescription");
break;
case BITAuthenticatorIdentificationTypeHockeyAppEmail:
if(nil == self.authenticationSecret) {
NSError *error = [NSError errorWithDomain:kBITAuthenticatorErrorDomain
code:BITAuthenticatorAuthorizationSecretMissing
userInfo:@{NSLocalizedDescriptionKey : @"For email identification, the authentication secret must be set"}];
if(completion) completion(NO, error);
return;
}
viewController = [[BITAuthenticationViewController alloc] initWithDelegate:self];
viewController.requirePassword = NO;
viewController.tableViewTitle = BITHockeyLocalizedString(@"HockeyAuthenticationViewControllerDataEmailDescription");
break;
}
if([self.delegate respondsToSelector:@selector(authenticator:willShowAuthenticationController:)]) {
[self.delegate authenticator:self willShowAuthenticationController:viewController];
}
NSAssert(viewController, @"ViewController should've been created");
viewController.email = [self stringValueFromKeychainForKey:kBITAuthenticatorUserEmailKey];
_authenticationController = viewController;
_identificationCompletion = completion;
[self showView:viewController];
}
#pragma mark - Validation
- (void) validate {
[self validateWithCompletion:^(BOOL validated, NSError *error) {
if(validated) {
[self dismissAuthenticationControllerAnimated:YES completion:nil];
} else {
BITHockeyLog(@"Validation failed with error: %@", error);
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:nil
message:error.localizedDescription
delegate:self
cancelButtonTitle:BITHockeyLocalizedString(@"HockeyOK")
otherButtonTitles:nil];
[alertView setTag:0];
[alertView show];
}
}];
}
- (void) validateWithCompletion:(void (^)(BOOL validated, NSError *))completion {
BOOL requirementsFulfilled = YES;
NSError *error = nil;
switch(self.identificationType) {
case BITAuthenticatorIdentificationTypeAnonymous: {
error = [NSError errorWithDomain:kBITAuthenticatorErrorDomain
code:BITAuthenticatorErrorUnknown
userInfo:@{NSLocalizedDescriptionKey : @"Anonymous users can't be validated"}];
requirementsFulfilled = NO;
break;
}
case BITAuthenticatorIdentificationTypeHockeyAppEmail:
if(nil == self.authenticationSecret) {
error = [NSError errorWithDomain:kBITAuthenticatorErrorDomain
code:BITAuthenticatorAuthorizationSecretMissing
userInfo:@{NSLocalizedDescriptionKey : @"For email validation, the authentication secret must be set"}];
requirementsFulfilled = NO;
break;
}
//no break
case BITAuthenticatorIdentificationTypeDevice:
case BITAuthenticatorIdentificationTypeHockeyAppUser:
case BITAuthenticatorIdentificationTypeWebAuth:
if(nil == self.installationIdentifier) {
error = [NSError errorWithDomain:kBITAuthenticatorErrorDomain
code:BITAuthenticatorNotIdentified
userInfo:@{NSLocalizedDescriptionKey : @"Make sure to identify the installation first"}];
requirementsFulfilled = NO;
}
break;
}
if(NO == requirementsFulfilled) {
if(completion) {
completion(NO, error);
}
return;
}
NSString *validationPath = [NSString stringWithFormat:@"api/3/apps/%@/identity/validate", self.encodedAppIdentifier];
__weak typeof (self) weakSelf = self;
[self.hockeyAppClient getPath:validationPath
parameters:[self validationParameters]
completion:^(BITHTTPOperation *operation, NSData* responseData, NSError *error) {
typeof (self) strongSelf = weakSelf;
if(nil == responseData) {
NSDictionary *userInfo = @{NSLocalizedDescriptionKey : BITHockeyLocalizedString(@"HockeyAuthenticationFailedAuthenticate")};
if(error) {
NSMutableDictionary *dict = [userInfo mutableCopy];
dict[NSUnderlyingErrorKey] = error;
userInfo = dict;
}
NSError *error = [NSError errorWithDomain:kBITAuthenticatorErrorDomain
code:BITAuthenticatorNetworkError
userInfo:userInfo];
strongSelf.validated = NO;
if(completion) completion(NO, error);
} else {
NSError *validationParseError = nil;
BOOL valid = [strongSelf.class isValidationResponseValid:responseData error:&validationParseError];
strongSelf.validated = valid;
if(valid) {
[self setLastAuthenticatedVersion:self.executableUUID];
}
if(completion) completion(valid, validationParseError);
}
}];
}
- (NSDictionary*) validationParameters {
NSParameterAssert(self.installationIdentifier);
NSParameterAssert(self.installationIdentifierParameterString);
NSString *installString = bit_appAnonID();
if (installString) {
return @{self.installationIdentifierParameterString : self.installationIdentifier, @"install_string": installString};
}
return @{self.installationIdentifierParameterString : self.installationIdentifier};
}
+ (BOOL) isValidationResponseValid:(id) response error:(NSError **) error {
NSParameterAssert(response);
NSError *jsonParseError = nil;
id jsonObject = [NSJSONSerialization JSONObjectWithData:response
options:0
error:&jsonParseError];
if(nil == jsonObject) {
if(error) {
*error = [NSError errorWithDomain:kBITAuthenticatorErrorDomain
code:BITAuthenticatorAPIServerReturnedInvalidResponse
userInfo:@{NSLocalizedDescriptionKey : BITHockeyLocalizedString(@"HockeyAuthenticationFailedAuthenticate")}];
}
return NO;
}
if(![jsonObject isKindOfClass:[NSDictionary class]]) {
if(error) {
*error = [NSError errorWithDomain:kBITAuthenticatorErrorDomain
code:BITAuthenticatorAPIServerReturnedInvalidResponse
userInfo:@{NSLocalizedDescriptionKey : BITHockeyLocalizedString(@"HockeyAuthenticationFailedAuthenticate")}];
}
return NO;
}
NSString *status = jsonObject[@"status"];
if([status isEqualToString:@"not authorized"]) {
if(error) {
*error = [NSError errorWithDomain:kBITAuthenticatorErrorDomain
code:BITAuthenticatorNotAuthorized
userInfo:@{NSLocalizedDescriptionKey : BITHockeyLocalizedString(@"HockeyAuthenticationNotMember")}];
}
return NO;
} else if([status isEqualToString:@"not found"]) {
if(error) {
*error = [NSError errorWithDomain:kBITAuthenticatorErrorDomain
code:BITAuthenticatorUnknownApplicationID
userInfo:@{NSLocalizedDescriptionKey : BITHockeyLocalizedString(@"HockeyAuthenticationContactDeveloper")}];
}
return NO;
} else if([status isEqualToString:@"validated"]) {
return YES;
} else {
if(error) {
*error = [NSError errorWithDomain:kBITAuthenticatorErrorDomain
code:BITAuthenticatorAPIServerReturnedInvalidResponse
userInfo:@{NSLocalizedDescriptionKey : BITHockeyLocalizedString(@"HockeyAuthenticationFailedAuthenticate")}];
}
return NO;
}
}
#pragma mark - AuthenticationViewControllerDelegate
- (void)authenticationViewController:(UIViewController *)viewController
handleAuthenticationWithEmail:(NSString *)email
password:(NSString *)password
completion:(void (^)(BOOL, NSError *))completion {
NSParameterAssert(email && email.length);
NSParameterAssert(self.identificationType == BITAuthenticatorIdentificationTypeHockeyAppEmail || (password && password.length));
NSURLRequest* request = [self requestForAuthenticationEmail:email password:password];
__weak typeof (self) weakSelf = self;
BITHTTPOperation *operation = [self.hockeyAppClient operationWithURLRequest:request
completion:^(BITHTTPOperation *operation, NSData* responseData, NSError *error) {
typeof (self) strongSelf = weakSelf;
NSError *authParseError = nil;
NSString *authToken = [strongSelf.class authenticationTokenFromURLResponse:operation.response
data:responseData
error:&authParseError];
BOOL identified;
if(authToken) {
identified = YES;
[strongSelf storeInstallationIdentifier:authToken withType:strongSelf.identificationType];
[strongSelf dismissAuthenticationControllerAnimated:YES completion:nil];
strongSelf->_authenticationController = nil;
BOOL success = [self addStringValueToKeychain:email forKey:kBITAuthenticatorUserEmailKey];
if (!success) {
[strongSelf alertOnFailureStoringTokenInKeychain];
}
} else {
identified = NO;
}
strongSelf.identified = identified;
completion(identified, authParseError);
if(strongSelf.identificationCompletion) strongSelf.identificationCompletion(identified, authParseError);
strongSelf.identificationCompletion = nil;
}];
[self.hockeyAppClient enqeueHTTPOperation:operation];
}
- (NSURLRequest *) requestForAuthenticationEmail:(NSString*) email password:(NSString*) password {
NSString *authenticationPath = [self authenticationPath];
NSMutableDictionary *params = [NSMutableDictionary dictionary];
NSString *installString = bit_appAnonID();
if (installString) {
params[@"install_string"] = installString;
}
if(BITAuthenticatorIdentificationTypeHockeyAppEmail == self.identificationType) {
NSString *authCode = BITHockeyMD5([NSString stringWithFormat:@"%@%@",
self.authenticationSecret ? : @"",
email ? : @""]);
params[@"email"] = email ? : @"";
params[@"authcode"] = authCode.lowercaseString;
}
NSMutableURLRequest *request = [self.hockeyAppClient requestWithMethod:@"POST"
path:authenticationPath
parameters:params];
if(BITAuthenticatorIdentificationTypeHockeyAppUser == self.identificationType) {
NSString *authStr = [NSString stringWithFormat:@"%@:%@", email, password];
NSData *authData = [authStr dataUsingEncoding:NSUTF8StringEncoding];
NSString *authValue = [NSString stringWithFormat:@"Basic %@", bit_base64String(authData, authData.length)];
[request setValue:authValue forHTTPHeaderField:@"Authorization"];
}
return request;
}
- (NSString *) authenticationPath {
if(BITAuthenticatorIdentificationTypeHockeyAppUser == self.identificationType) {
return [NSString stringWithFormat:@"api/3/apps/%@/identity/authorize", self.encodedAppIdentifier];
} else {
return [NSString stringWithFormat:@"api/3/apps/%@/identity/check", self.encodedAppIdentifier];
}
}
+ (NSString *) authenticationTokenFromURLResponse:(NSHTTPURLResponse*) urlResponse data:(NSData*) data error:(NSError **) error {
if(nil == urlResponse) {
if(error) {
*error = [NSError errorWithDomain:kBITAuthenticatorErrorDomain
code:BITAuthenticatorAPIServerReturnedInvalidResponse
userInfo:@{ NSLocalizedDescriptionKey : BITHockeyLocalizedString(@"HockeyAuthenticationFailedAuthenticate")}];
}
return nil;
}
switch (urlResponse.statusCode) {
case 401:
if(error) {
*error = [NSError errorWithDomain:kBITAuthenticatorErrorDomain
code:BITAuthenticatorNotAuthorized
userInfo:@{
NSLocalizedDescriptionKey : BITHockeyLocalizedString(@"HockeyAuthenticationWrongEmailPassword")
}];
}
break;
case 200:
case 404:
//Do nothing, handled below
break;
default:
if(error) {
*error = [NSError errorWithDomain:kBITAuthenticatorErrorDomain
code:BITAuthenticatorAPIServerReturnedInvalidResponse
userInfo:@{ NSLocalizedDescriptionKey : BITHockeyLocalizedString(@"HockeyAuthenticationFailedAuthenticate")}];
}
break;
}
if(200 != urlResponse.statusCode && 404 != urlResponse.statusCode) {
//make sure we have an error created if user wanted to have one
NSParameterAssert(nil == error || *error);
return nil;
}
NSError *jsonParseError = nil;
id jsonObject = [NSJSONSerialization JSONObjectWithData:data
options:0
error:&jsonParseError];
//no json or unexpected json
if(nil == jsonObject || ![jsonObject isKindOfClass:[NSDictionary class]]) {
if(error) {
NSDictionary *userInfo = @{NSLocalizedDescriptionKey: BITHockeyLocalizedString(@"HockeyAuthenticationFailedAuthenticate")};
if(jsonParseError) {
NSMutableDictionary *userInfoMutable = [userInfo mutableCopy];
userInfoMutable[NSUnderlyingErrorKey] = jsonParseError;
userInfo = userInfoMutable;
}
*error = [NSError errorWithDomain:kBITAuthenticatorErrorDomain
code:BITAuthenticatorAPIServerReturnedInvalidResponse
userInfo:userInfo];
}
return nil;
}
NSString *status = jsonObject[@"status"];
NSString *authToken = nil;
if([status isEqualToString:@"identified"]) {
authToken = jsonObject[@"iuid"];
} else if([status isEqualToString:@"authorized"]) {
authToken = jsonObject[@"auid"];
} else if([status isEqualToString:@"not authorized"]) {
if(error) {
*error = [NSError errorWithDomain:kBITAuthenticatorErrorDomain
code:BITAuthenticatorNotAuthorized
userInfo:@{NSLocalizedDescriptionKey: BITHockeyLocalizedString(@"HockeyAuthenticationNotMember")}];
}
}
//if no error is set yet, but error parameter is given, return a generic error
if(nil == authToken && error && nil == *error) {
*error = [NSError errorWithDomain:kBITAuthenticatorErrorDomain
code:BITAuthenticatorAPIServerReturnedInvalidResponse
userInfo:@{NSLocalizedDescriptionKey: BITHockeyLocalizedString(@"HockeyAuthenticationFailedAuthenticate")}];
}
return authToken;
}
- (NSURL *)deviceAuthenticationURL {
NSString *whatParameter = nil;
switch (self.identificationType) {
case BITAuthenticatorIdentificationTypeWebAuth:
whatParameter = @"email";
break;
case BITAuthenticatorIdentificationTypeDevice:
whatParameter = @"udid";
break;
case BITAuthenticatorIdentificationTypeAnonymous:
case BITAuthenticatorIdentificationTypeHockeyAppEmail:
case BITAuthenticatorIdentificationTypeHockeyAppUser:
return nil;
break;
}
NSURL *url = [self.webpageURL URLByAppendingPathComponent:[NSString stringWithFormat:@"apps/%@/authorize", self.encodedAppIdentifier]];
NSParameterAssert(whatParameter && url.absoluteString);
url = [NSURL URLWithString:[NSString stringWithFormat:@"%@?what=%@", url.absoluteString, whatParameter]];
return url;
}
- (void)authenticationViewControllerDidTapWebButton:(UIViewController *)viewController {
NSURL *url = [self deviceAuthenticationURL];
if(url) {
[[UIApplication sharedApplication] openURL:url];
}
}
- (BOOL) handleOpenURL:(NSURL *) url
sourceApplication:(NSString *) sourceApplication
annotation:(id) annotation {
//check if this URL was meant for us, if not return NO so the user can
//handle it
NSString *const kAuthorizationHost = @"authorize";
NSString *urlScheme = _urlScheme ? : [NSString stringWithFormat:@"ha%@", self.appIdentifier];
if(!([[url scheme] isEqualToString:urlScheme] && [[url host] isEqualToString:kAuthorizationHost])) {
BITHockeyLog(@"URL scheme for authentication doesn't match!");
return NO;
}
NSString *installationIdentifier = nil;
NSString *localizedErrorDescription = nil;
switch (self.identificationType) {
case BITAuthenticatorIdentificationTypeWebAuth: {
NSString *email = nil;
[self.class email:&email andIUID:&installationIdentifier fromOpenURL:url];
if(email) {
BOOL success = [self addStringValueToKeychain:email forKey:kBITAuthenticatorUserEmailKey];
if (!success) {
[self alertOnFailureStoringTokenInKeychain];
}
} else {
BITHockeyLog(@"No email found in URL: %@", url);
}
localizedErrorDescription = @"Failed to retrieve parameters from URL.";
break;
}
case BITAuthenticatorIdentificationTypeDevice: {
installationIdentifier = [self.class UDIDFromOpenURL:url annotation:annotation];
localizedErrorDescription = @"Failed to retrieve UDID from URL.";
break;
}
case BITAuthenticatorIdentificationTypeHockeyAppEmail:
case BITAuthenticatorIdentificationTypeAnonymous:
case BITAuthenticatorIdentificationTypeHockeyAppUser:
return NO;
}
if(installationIdentifier){
BITHockeyLog(@"Authentication succeeded.");
if(NO == self.restrictApplicationUsage) {
[self dismissAuthenticationControllerAnimated:YES completion:nil];
}
[self storeInstallationIdentifier:installationIdentifier withType:self.identificationType];
self.identified = YES;
if(self.identificationCompletion) {
self.identificationCompletion(YES, nil);
self.identificationCompletion = nil;
}
} else {
//reset token
BITHockeyLog(@"Resetting authentication token");
[self storeInstallationIdentifier:nil withType:self.identificationType];
self.identified = NO;
if(self.identificationCompletion) {
NSError *error = [NSError errorWithDomain:kBITAuthenticatorErrorDomain
code:BITAuthenticatorErrorUnknown
userInfo:@{NSLocalizedDescriptionKey : localizedErrorDescription}];
self.identificationCompletion(NO, error);
self.identificationCompletion = nil;
}
}
return YES;
}
+ (NSString *) UDIDFromOpenURL:(NSURL *) url annotation:(id) annotation {
NSString *query = [url query];
NSString *udid = nil;
//there should actually only one
static NSString * const UDIDQuerySpecifier = @"udid";
for(NSString *queryComponents in [query componentsSeparatedByString:@"&"]) {
NSArray *parameterComponents = [queryComponents componentsSeparatedByString:@"="];
if(2 == parameterComponents.count && [parameterComponents[0] isEqualToString:UDIDQuerySpecifier]) {
udid = parameterComponents[1];
break;
}
}
return udid;
}
+ (void) email:(NSString**) email andIUID:(NSString**) iuid fromOpenURL:(NSURL *) url {
NSString *query = [url query];
//there should actually only one
static NSString * const EmailQuerySpecifier = @"email";
static NSString * const IUIDQuerySpecifier = @"iuid";
for(NSString *queryComponents in [query componentsSeparatedByString:@"&"]) {
NSArray *parameterComponents = [queryComponents componentsSeparatedByString:@"="];
if(email && 2 == parameterComponents.count && [parameterComponents[0] isEqualToString:EmailQuerySpecifier]) {
*email = parameterComponents[1];
} else if(iuid && 2 == parameterComponents.count && [parameterComponents[0] isEqualToString:IUIDQuerySpecifier]) {
*iuid = parameterComponents[1];
}
}
}
#pragma mark - Private helpers
- (void) cleanupInternalStorage {
[self removeKeyFromKeychain:kBITAuthenticatorIdentifierTypeKey];
[self removeKeyFromKeychain:kBITAuthenticatorIdentifierKey];
[self removeKeyFromKeychain:kBITAuthenticatorUUIDKey];
[self removeKeyFromKeychain:kBITAuthenticatorUserEmailKey];
[self setLastAuthenticatedVersion:nil];
//cleanup values stored from 3.5 Beta1..Beta3
[self removeKeyFromKeychain:kBITAuthenticatorAuthTokenKey];
[self removeKeyFromKeychain:kBITAuthenticatorAuthTokenTypeKey];
}
- (void)processFullSizeImage {
#ifdef BIT_INTERNAL_DEBUG
NSString* path = [[NSBundle mainBundle] pathForResource:@"iTunesArtwork" ofType:@"png"];
#else
NSString* path = [[[NSBundle mainBundle] bundlePath] stringByAppendingString:@"/../iTunesArtwork"];
#endif
struct stat fs;
int fd = open([path UTF8String], O_RDONLY, 0);
if (fstat(fd, &fs) < 0) {
// File not found
return;
}
BITHockeyLog(@"Processing full size image for possible authentication");
unsigned char *buffer, *source;
source = (unsigned char *)malloc((unsigned long)fs.st_size);
if (read(fd, source, (unsigned long)fs.st_size) != fs.st_size) {
// Couldn't read file
free(source);
return;
}
if ((fs.st_size < 20) || (memcmp(source, kBITPNGHeader, 8))) {
// Not a PNG
free(source);
return;
}
buffer = source + 8;
NSString *result = nil;
bit_uint32 length;
unsigned char *name;
unsigned char *data;
int chunk_index = 0;
long long bytes_left = fs.st_size - 8;
do {
memcpy(&length, buffer, 4);
length = ntohl(length);
buffer += 4;
name = (unsigned char *)malloc(5);
name[4] = 0;
memcpy(name, buffer, 4);
buffer += 4;
data = (unsigned char *)malloc(length + 1);
if (bytes_left >= length) {
memcpy(data, buffer, length);
buffer += length;
buffer += 4;
if (!strcmp((const char *)name, "tEXt")) {
data[length] = 0;
NSString *key = [NSString stringWithCString:(char *)data encoding:NSUTF8StringEncoding];
if ([key isEqualToString:@"Data"]) {
result = [NSString stringWithCString:(char *)(data + key.length + 1) encoding:NSUTF8StringEncoding];
}
}
if (!memcmp(name, kBITPNGEndChunk, 4)){
chunk_index = 128;
}
}
free(data);
free(name);
bytes_left -= (length + 3 * 4);
} while ((chunk_index++ < 128) && (bytes_left > 8));
free(source);
if (result) {
BITHockeyLog(@"Authenticating using full size image information: %@", result);
[self handleOpenURL:[NSURL URLWithString:result] sourceApplication:nil annotation:nil];
} else {
BITHockeyLog(@"No authentication information found");
}
}
#pragma mark - KVO
- (void) registerObservers {
__weak typeof(self) weakSelf = self;
if(nil == _appDidBecomeActiveObserver) {
_appDidBecomeActiveObserver = [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationDidBecomeActiveNotification
object:nil
queue:NSOperationQueue.mainQueue
usingBlock:^(NSNotification *note) {
typeof(self) strongSelf = weakSelf;
[strongSelf applicationDidBecomeActive:note];
}];
}
if(nil == _appDidEnterBackgroundOberser) {
_appDidEnterBackgroundOberser = [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationDidEnterBackgroundNotification
object:nil
queue:NSOperationQueue.mainQueue
usingBlock:^(NSNotification *note) {
typeof(self) strongSelf = weakSelf;
[strongSelf applicationDidEnterBackground:note];
}];
}
}
- (void) unregisterObservers {
if(_appDidBecomeActiveObserver) {
[[NSNotificationCenter defaultCenter] removeObserver:_appDidBecomeActiveObserver];
_appDidBecomeActiveObserver = nil;
}
if(_appDidEnterBackgroundOberser) {
[[NSNotificationCenter defaultCenter] removeObserver:_appDidEnterBackgroundOberser];
_appDidEnterBackgroundOberser = nil;
}
}
#pragma mark - Property overrides
- (void)storeInstallationIdentifier:(NSString *)installationIdentifier withType:(BITAuthenticatorIdentificationType) type {
if(nil == installationIdentifier) {
[self removeKeyFromKeychain:kBITAuthenticatorIdentifierKey];
[self removeKeyFromKeychain:kBITAuthenticatorIdentifierTypeKey];
} else {
BOOL success1 = [self addStringValueToKeychainForThisDeviceOnly:installationIdentifier
forKey:kBITAuthenticatorIdentifierKey];
NSParameterAssert(success1);
BOOL success2 = [self addStringValueToKeychainForThisDeviceOnly:[self.class stringForIdentificationType:type]
forKey:kBITAuthenticatorIdentifierTypeKey];
NSParameterAssert(success2);
if (!success1 || !success2) {
[self alertOnFailureStoringTokenInKeychain];
}
}
}
- (NSString*) installationIdentifier {
NSString *identifier = [self stringValueFromKeychainForKey:kBITAuthenticatorIdentifierKey];
return identifier;
}
- (void)setLastAuthenticatedVersion:(NSString *)lastAuthenticatedVersion {
NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
if(nil == lastAuthenticatedVersion){
[defaults removeObjectForKey:kBITAuthenticatorLastAuthenticatedVersionKey];
} else {
[defaults setObject:lastAuthenticatedVersion
forKey:kBITAuthenticatorLastAuthenticatedVersionKey];
[defaults synchronize];
}
}
- (NSString *)lastAuthenticatedVersion {
return [[NSUserDefaults standardUserDefaults] objectForKey:kBITAuthenticatorLastAuthenticatedVersionKey];
}
- (NSString *)installationIdentifierParameterString {
switch(self.identificationType) {
case BITAuthenticatorIdentificationTypeHockeyAppEmail:
case BITAuthenticatorIdentificationTypeWebAuth:
return @"iuid";
case BITAuthenticatorIdentificationTypeHockeyAppUser: return @"auid";
case BITAuthenticatorIdentificationTypeDevice: return @"udid";
case BITAuthenticatorIdentificationTypeAnonymous: return @"uuid";
}
}
+ (NSString *)stringForIdentificationType:(BITAuthenticatorIdentificationType) identificationType {
switch(identificationType) {
case BITAuthenticatorIdentificationTypeHockeyAppEmail: return @"iuid";
case BITAuthenticatorIdentificationTypeWebAuth: return @"webAuth";
case BITAuthenticatorIdentificationTypeHockeyAppUser: return @"auid";
case BITAuthenticatorIdentificationTypeDevice: return @"udid";
case BITAuthenticatorIdentificationTypeAnonymous: return @"uuid";
}
}
- (void)setIdentificationType:(BITAuthenticatorIdentificationType)identificationType {
if(_identificationType != identificationType) {
_identificationType = identificationType;
self.identified = NO;
self.validated = NO;
}
}
- (NSString *)publicInstallationIdentifier {
switch (self.identificationType) {
case BITAuthenticatorIdentificationTypeHockeyAppEmail:
case BITAuthenticatorIdentificationTypeHockeyAppUser:
case BITAuthenticatorIdentificationTypeWebAuth:
return [self stringValueFromKeychainForKey:kBITAuthenticatorUserEmailKey];
case BITAuthenticatorIdentificationTypeAnonymous:
case BITAuthenticatorIdentificationTypeDevice:
return [self stringValueFromKeychainForKey:kBITAuthenticatorIdentifierKey];
}
}
#pragma mark - Application Lifecycle
- (void)applicationDidBecomeActive:(NSNotification *)note {
[self authenticate];
}
- (void)applicationDidEnterBackground:(NSNotification *)note {
if(BITAuthenticatorAppRestrictionEnforcementOnAppActive == self.restrictionEnforcementFrequency) {
self.validated = NO;
}
}
#pragma mark - UIAlertViewDelegate
- (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex {
if (alertView.tag == 0) {
[self validate];
}
}
@end
-95
View File
@@ -1,95 +0,0 @@
/*
* Author: Stephan Diederich
*
* Copyright (c) 2013-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 "BITAuthenticator.h"
#import "BITHockeyBaseManagerPrivate.h"
#import "BITAuthenticationViewController.h"
@class BITHockeyAppClient;
@interface BITAuthenticator ()<BITAuthenticationViewControllerDelegate, UIAlertViewDelegate>
/**
Delegate that can be used to do any last minute configurations on the
presented viewController.
The delegate is automatically set by using `[BITHockeyManager setDelegate:]`. You
should not need to set this delegate individually.
@see `[BITHockeyManager setDelegate:]`
@see BITAuthenticatorDelegate
*/
@property (nonatomic, weak) id<BITAuthenticatorDelegate> delegate;
/**
* must be set
*/
@property (nonatomic, strong) BITHockeyAppClient *hockeyAppClient;
#pragma mark -
/**
* holds the identifier of the last version that was authenticated
* only used if validation is set BITAuthenticatorValidationTypeOnFirstLaunch
*/
@property (nonatomic, copy) NSString *lastAuthenticatedVersion;
/**
* returns the type of the string stored in installationIdentifierParameterString
*/
@property (nonatomic, copy, readonly) NSString *installationIdentifierParameterString;
/**
* returns the string used to identify this app against the HockeyApp backend.
*/
@property (nonatomic, copy, readonly) NSString *installationIdentifier;
/**
* method registered as observer for applicationDidEnterBackground events
*
* @param note NSNotification
*/
- (void) applicationDidEnterBackground:(NSNotification*) note;
/**
* method registered as observer for applicationsDidBecomeActive events
*
* @param note NSNotification
*/
- (void) applicationDidBecomeActive:(NSNotification*) note;
@property (nonatomic, copy) void(^identificationCompletion)(BOOL identified, NSError* error);
#pragma mark - Overrides
@property (nonatomic, assign, readwrite, getter = isIdentified) BOOL identified;
@property (nonatomic, assign, readwrite, getter = isValidated) BOOL validated;
#pragma mark - Testing
- (void) storeInstallationIdentifier:(NSString*) identifier withType:(BITAuthenticatorIdentificationType) type;
- (BOOL) needsValidation;
- (void) authenticate;
@end
-33
View File
@@ -1,33 +0,0 @@
/*
* Author: Moritz Haarmann <post@moritzhaarmann.de>
*
* 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"
@interface BITBlurImageAnnotation : BITImageAnnotation
@end
-101
View File
@@ -1,101 +0,0 @@
/*
* Author: Moritz Haarmann <post@moritzhaarmann.de>
*
* 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"
@interface BITBlurImageAnnotation()
@property (nonatomic, strong) CALayer* imageLayer;
@property (nonatomic, strong) UIImage* scaledImage;
@property (nonatomic, strong) CALayer* selectedLayer;
@end
@implementation BITBlurImageAnnotation
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
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;
}
- (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.shouldRasterize = YES;
self.imageLayer.rasterizationScale = 1;
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];
[CATransaction begin];
[CATransaction setValue:(id)kCFBooleanTrue forKey:kCATransactionDisableActions];
self.imageLayer.frame = self.imageFrame;
self.imageLayer.masksToBounds = YES;
self.selectedLayer.frame= self.bounds;
[CATransaction commit];
}
- (BOOL)resizable {
return YES;
}
@end
-59
View File
@@ -1,59 +0,0 @@
/*
* Author: Andreas Linde <mail@andreaslinde.de>
*
* Copyright (c) 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 "BITHockeyAttachment.h"
/**
Deprecated: Provides support to add binary attachments to crash reports
This class is not needed any longer and exists for compatiblity purposes with
HockeySDK-iOS 3.5.5.
It is a subclass of `BITHockeyAttachment` which only provides an initializer
that is compatible with the one of HockeySDK-iOS 3.5.5.
This is used by `[BITCrashManagerDelegate attachmentForCrashManager:]`
@see BITHockeyAttachment
*/
@interface BITCrashAttachment : BITHockeyAttachment
/**
Create an BITCrashAttachment instance with a given filename and NSData object
@param filename The filename the attachment should get
@param crashAttachmentData The attachment data as NSData
@param contentType The content type of your data as MIME type
@return An instsance of BITCrashAttachment
*/
- (instancetype)initWithFilename:(NSString *)filename
crashAttachmentData:(NSData *)crashAttachmentData
contentType:(NSString *)contentType;
@end
-42
View File
@@ -1,42 +0,0 @@
/*
* Author: Andreas Linde <mail@andreaslinde.de>
*
* Copyright (c) 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 "BITCrashAttachment.h"
@implementation BITCrashAttachment
- (instancetype)initWithFilename:(NSString *)filename
crashAttachmentData:(NSData *)crashAttachmentData
contentType:(NSString *)contentType
{
self = [super initWithFilename:filename hockeyAttachmentData:crashAttachmentData contentType:contentType];
return self;
}
@end
-110
View File
@@ -1,110 +0,0 @@
/*
* Author: Andreas Linde <mail@andreaslinde.de>
*
* 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 <Foundation/Foundation.h>
/**
* Provides details about the crash that occured in the previous app session
*/
@interface BITCrashDetails : NSObject
/**
* UUID for the crash report
*/
@property (nonatomic, readonly, strong) NSString *incidentIdentifier;
/**
* UUID for the app installation on the device
*/
@property (nonatomic, readonly, strong) NSString *reporterKey;
/**
* Signal that caused the crash
*/
@property (nonatomic, readonly, strong) NSString *signal;
/**
* Exception name that triggered the crash, nil if the crash was not caused by an exception
*/
@property (nonatomic, readonly, strong) NSString *exceptionName;
/**
* Exception reason, nil if the crash was not caused by an exception
*/
@property (nonatomic, readonly, strong) NSString *exceptionReason;
/**
* Date and time the app started, nil if unknown
*/
@property (nonatomic, readonly, strong) NSDate *appStartTime;
/**
* Date and time the crash occured, nil if unknown
*/
@property (nonatomic, readonly, strong) NSDate *crashTime;
/**
* Operation System version string the app was running on when it crashed.
*/
@property (nonatomic, readonly, strong) NSString *osVersion;
/**
* Operation System build string the app was running on when it crashed
*
* This may be unavailable.
*/
@property (nonatomic, readonly, strong) NSString *osBuild;
/**
* CFBundleVersion value of the app that crashed
*/
@property (nonatomic, readonly, strong) NSString *appBuild;
/**
Indicates if the app was killed while being in foreground from the iOS
If `[BITCrashManager enableAppNotTerminatingCleanlyDetection]` is enabled, use this on startup
to check if the app starts the first time after it was killed by iOS in the previous session.
This can happen if it consumed too much memory or the watchdog killed the app because it
took too long to startup or blocks the main thread for too long, or other reasons. See Apple
documentation: https://developer.apple.com/library/ios/qa/qa1693/_index.html
See `[BITCrashManager enableAppNotTerminatingCleanlyDetection]` for more details about which kind of kills can be detected.
@warning This property only has a correct value, once `[BITHockeyManager startManager]` was
invoked! In addition, it is automatically disabled while a debugger session is active!
@see `[BITCrashManager enableAppNotTerminatingCleanlyDetection]`
@see `[BITCrashManager didReceiveMemoryWarningInLastSession]`
@return YES if the details represent an app kill instead of a crash
*/
- (BOOL)isAppKill;
@end
-71
View File
@@ -1,71 +0,0 @@
/*
* Author: Andreas Linde <mail@andreaslinde.de>
*
* 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 "BITCrashDetails.h"
#import "BITCrashDetailsPrivate.h"
NSString *const kBITCrashKillSignal = @"SIGKILL";
@implementation BITCrashDetails
- (instancetype)initWithIncidentIdentifier:(NSString *)incidentIdentifier
reporterKey:(NSString *)reporterKey
signal:(NSString *)signal
exceptionName:(NSString *)exceptionName
exceptionReason:(NSString *)exceptionReason
appStartTime:(NSDate *)appStartTime
crashTime:(NSDate *)crashTime
osVersion:(NSString *)osVersion
osBuild:(NSString *)osBuild
appBuild:(NSString *)appBuild
{
if ((self = [super init])) {
_incidentIdentifier = incidentIdentifier;
_reporterKey = reporterKey;
_signal = signal;
_exceptionName = exceptionName;
_exceptionReason = exceptionReason;
_appStartTime = appStartTime;
_crashTime = crashTime;
_osVersion = osVersion;
_osBuild = osBuild;
_appBuild = appBuild;
}
return self;
}
- (BOOL)isAppKill {
BOOL result = NO;
if (_signal && [[_signal uppercaseString] isEqualToString:kBITCrashKillSignal])
result = YES;
return result;
}
@end
-48
View File
@@ -1,48 +0,0 @@
/*
* Author: Andreas Linde <mail@andreaslinde.de>
*
* 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 <HockeySDK/HockeySDK.h>
extern NSString *const __attribute__((unused)) kBITCrashKillSignal;
@interface BITCrashDetails () {
}
- (instancetype)initWithIncidentIdentifier:(NSString *)incidentIdentifier
reporterKey:(NSString *)reporterKey
signal:(NSString *)signal
exceptionName:(NSString *)exceptionName
exceptionReason:(NSString *)exceptionReason
appStartTime:(NSDate *)appStartTime
crashTime:(NSDate *)crashTime
osVersion:(NSString *)osVersion
osBuild:(NSString *)osBuild
appBuild:(NSString *)appBuild;
@end
-436
View File
@@ -1,436 +0,0 @@
/*
* Author: Andreas Linde <mail@andreaslinde.de>
* Kent Sutherland
*
* 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 <Foundation/Foundation.h>
#import "BITHockeyBaseManager.h"
@class BITCrashDetails;
@class BITCrashMetaData;
/**
* Custom block that handles the alert that prompts the user whether he wants to send crash reports
*/
typedef void(^BITCustomAlertViewHandler)();
/**
* Crash Manager status
*/
typedef NS_ENUM(NSUInteger, BITCrashManagerStatus) {
/**
* Crash reporting is disabled
*/
BITCrashManagerStatusDisabled = 0,
/**
* User is asked each time before sending
*/
BITCrashManagerStatusAlwaysAsk = 1,
/**
* Each crash report is send automatically
*/
BITCrashManagerStatusAutoSend = 2
};
/**
* Prototype of a callback function used to execute additional user code. Called upon completion of crash
* handling, after the crash report has been written to disk.
*
* @param context The API client's supplied context value.
*
* @see `BITCrashManagerCallbacks`
* @see `[BITCrashManager setCrashCallbacks:]`
*/
typedef void (*BITCrashManagerPostCrashSignalCallback)(void *context);
/**
* This structure contains callbacks supported by `BITCrashManager` to allow the host application to perform
* additional tasks prior to program termination after a crash has occured.
*
* @see `BITCrashManagerPostCrashSignalCallback`
* @see `[BITCrashManager setCrashCallbacks:]`
*/
typedef struct BITCrashManagerCallbacks {
/** An arbitrary user-supplied context value. This value may be NULL. */
void *context;
/**
* The callback used to report caught signal information.
*/
BITCrashManagerPostCrashSignalCallback handleSignal;
} BITCrashManagerCallbacks;
/**
* Crash Manager alert user input
*/
typedef NS_ENUM(NSUInteger, BITCrashManagerUserInput) {
/**
* User chose not to send the crash report
*/
BITCrashManagerUserInputDontSend = 0,
/**
* User wants the crash report to be sent
*/
BITCrashManagerUserInputSend = 1,
/**
* User chose to always send crash reports
*/
BITCrashManagerUserInputAlwaysSend = 2
};
@protocol BITCrashManagerDelegate;
/**
The crash reporting module.
This is the HockeySDK module for handling crash reports, including when distributed via the App Store.
As a foundation it is using the open source, reliable and async-safe crash reporting framework
[PLCrashReporter](https://code.google.com/p/plcrashreporter/).
This module works as a wrapper around the underlying crash reporting framework and provides functionality to
detect new crashes, queues them if networking is not available, present a user interface to approve sending
the reports to the HockeyApp servers and more.
It also provides options to add additional meta information to each crash report, like `userName`, `userEmail`
via `BITHockeyManagerDelegate` protocol, and additional textual log information via `BITCrashManagerDelegate`
protocol and a way to detect startup crashes so you can adjust your startup process to get these crash reports
too and delay your app initialization.
Crashes are send the next time the app starts. If `crashManagerStatus` is set to `BITCrashManagerStatusAutoSend`,
crashes will be send without any user interaction, otherwise an alert will appear allowing the users to decide
whether they want to send the report or not. This module is not sending the reports right when the crash happens
deliberately, because if is not safe to implement such a mechanism while being async-safe (any Objective-C code
is _NOT_ async-safe!) and not causing more danger like a deadlock of the device, than helping. We found that users
do start the app again because most don't know what happened, and you will get by far most of the reports.
Sending the reports on startup is done asynchronously (non-blocking). This is the only safe way to ensure
that the app won't be possibly killed by the iOS watchdog process, because startup could take too long
and the app could not react to any user input when network conditions are bad or connectivity might be
very slow.
It is possible to check upon startup if the app crashed before using `didCrashInLastSession` and also how much
time passed between the app launch and the crash using `timeintervalCrashInLastSessionOccured`. This allows you
to add additional code to your app delaying the app start until the crash has been successfully send if the crash
occured within a critical startup timeframe, e.g. after 10 seconds. The `BITCrashManagerDelegate` protocol provides
various delegates to inform the app about it's current status so you can continue the remaining app startup setup
after sending has been completed. The documentation contains a guide
[How to handle Crashes on startup](HowTo-Handle-Crashes-On-Startup) with an example on how to do that.
More background information on this topic can be found in the following blog post by Landon Fuller, the
developer of [PLCrashReporter](https://www.plcrashreporter.org), about writing reliable and
safe crash reporting: [Reliable Crash Reporting](http://goo.gl/WvTBR)
@warning If you start the app with the Xcode debugger attached, detecting crashes will _NOT_ be enabled!
*/
@interface BITCrashManager : BITHockeyBaseManager
///-----------------------------------------------------------------------------
/// @name Configuration
///-----------------------------------------------------------------------------
/** Set the default status of the Crash Manager
Defines if the crash reporting feature should be disabled, ask the user before
sending each crash report or send crash reportings automatically without
asking.
The default value is `BITCrashManagerStatusAlwaysAsk`. The user can switch to
`BITCrashManagerStatusAutoSend` by choosing "Always" in the dialog (since
`showAlwaysButton` default is _YES_).
The current value is always stored in User Defaults with the key
`BITCrashManagerStatus`.
If you intend to implement a user setting to let them enable or disable
crash reporting, this delegate should be used to return that value. You also
have to make sure the new value is stored in the UserDefaults with the key
`BITCrashManagerStatus`.
@see BITCrashManagerStatus
@see showAlwaysButton
*/
@property (nonatomic, assign) BITCrashManagerStatus crashManagerStatus;
/**
* Trap fatal signals via a Mach exception server.
*
* By default the SDK is using the safe and proven in-process BSD Signals for catching crashes.
* This option provides an option to enable catching fatal signals via a Mach exception server
* instead.
*
* We strongly advice _NOT_ to enable Mach exception handler in release versions of your apps!
*
* Default: _NO_
*
* @warning The Mach exception handler executes in-process, and will interfere with debuggers when
* they attempt to suspend all active threads (which will include the Mach exception handler).
* Mach-based handling should _NOT_ be used when a debugger is attached. The SDK will not
* enabled catching exceptions if the app is started with the debugger running. If you attach
* the debugger during runtime, this may cause issues the Mach exception handler is enabled!
* @see isDebuggerAttached
*/
@property (nonatomic, assign, getter=isMachExceptionHandlerEnabled) BOOL enableMachExceptionHandler;
/**
* Enable on device symbolication for system symbols
*
* By default, the SDK does not symbolicate on the device, since this can
* take a few seconds at each crash. Also note that symbolication on the
* device might not be able to retrieve all symbols.
*
* Enable if you want to analyze crashes on unreleased OS versions.
*
* Default: _NO_
*/
@property (nonatomic, assign, getter=isOnDeviceSymbolicationEnabled) BOOL enableOnDeviceSymbolication;
/**
* EXPERIMENTAL: Enable heuristics to detect the app not terminating cleanly
*
* This allows it to get a crash report if the app got killed while being in the foreground
* because of now of the following reasons:
* - The main thread was blocked for too long
* - The app took too long to start up
* - The app tried to allocate too much memory. If iOS did send a memory warning before killing the app because of this reason, `didReceiveMemoryWarningInLastSession` returns `YES`.
* - Permitted background duration if main thread is running in an endless loop
* - App failed to resume in time if main thread is running in an endless loop
* - If `enableMachExceptionHandler` is not activated, crashed due to stackoverflow will also be reported
*
* The following kills can _NOT_ be detected:
* - Terminating the app takes too long
* - Permitted background duration too long for all other cases
* - App failed to resume in time for all other cases
* - possibly more cases
*
* Crash reports triggered by this mechanisms do _NOT_ contain any stack traces since the time of the kill
* cannot be intercepted and hence no stack trace of the time of the kill event can't be gathered.
*
* The heuristic is implemented as follows:
* If the app never gets a `UIApplicationDidEnterBackgroundNotification` or `UIApplicationWillTerminateNotification`
* notification, PLCrashReporter doesn't detect a crash itself, and the app starts up again, it is assumed that
* the app got either killed by iOS while being in foreground or a crash occured that couldn't be detected.
*
* Default: _NO_
*
* @warning This is a heuristic and it _MAY_ report false positives! It has been tested with iOS 6.1 and iOS 7.
* Depending on Apple changing notification events, new iOS version may cause more false positives!
*
* @see lastSessionCrashDetails
* @see didReceiveMemoryWarningInLastSession
* @see `BITCrashManagerDelegate considerAppNotTerminatedCleanlyReportForCrashManager:`
* @see [Apple Technical Note TN2151](https://developer.apple.com/library/ios/technotes/tn2151/_index.html)
* @see [Apple Technical Q&A QA1693](https://developer.apple.com/library/ios/qa/qa1693/_index.html)
*/
@property (nonatomic, assign, getter = isAppNotTerminatingCleanlyDetectionEnabled) BOOL enableAppNotTerminatingCleanlyDetection;
/**
* Set the callbacks that will be executed prior to program termination after a crash has occurred
*
* PLCrashReporter provides support for executing an application specified function in the context
* of the crash reporter's signal handler, after the crash report has been written to disk.
*
* Writing code intended for execution inside of a signal handler is exceptionally difficult, and is _NOT_ recommended!
*
* _Program Flow and Signal Handlers_
*
* When the signal handler is called the normal flow of the program is interrupted, and your program is an unknown state. Locks may be held, the heap may be corrupt (or in the process of being updated), and your signal handler may invoke a function that was being executed at the time of the signal. This may result in deadlocks, data corruption, and program termination.
*
* _Async-Safe Functions_
*
* A subset of functions are defined to be async-safe by the OS, and are safely callable from within a signal handler. If you do implement a custom post-crash handler, it must be async-safe. A table of POSIX-defined async-safe functions and additional information is available from the [CERT programming guide - SIG30-C](https://www.securecoding.cert.org/confluence/display/seccode/SIG30-C.+Call+only+asynchronous-safe+functions+within+signal+handlers).
*
* Most notably, the Objective-C runtime itself is not async-safe, and Objective-C may not be used within a signal handler.
*
* Documentation taken from PLCrashReporter: https://www.plcrashreporter.org/documentation/api/v1.2-rc2/async_safety.html
*
* @see BITCrashManagerPostCrashSignalCallback
* @see BITCrashManagerCallbacks
*
* @param callbacks A pointer to an initialized PLCrashReporterCallback structure, see https://www.plcrashreporter.org/documentation/api/v1.2-rc2/struct_p_l_crash_reporter_callbacks.html
*/
- (void)setCrashCallbacks: (BITCrashManagerCallbacks *) callbacks;
/**
Flag that determines if an "Always" option should be shown
If enabled the crash reporting alert will also present an "Always" option, so
the user doesn't have to approve every single crash over and over again.
If If `crashManagerStatus` is set to `BITCrashManagerStatusAutoSend`, this property
has no effect, since no alert will be presented.
Default: _YES_
@see crashManagerStatus
*/
@property (nonatomic, assign, getter=shouldShowAlwaysButton) BOOL showAlwaysButton;
///-----------------------------------------------------------------------------
/// @name Crash Meta Information
///-----------------------------------------------------------------------------
/**
Indicates if the app crash in the previous session
Use this on startup, to check if the app starts the first time after it crashed
previously. You can use this also to disable specific events, like asking
the user to rate your app.
@warning This property only has a correct value, once `[BITHockeyManager startManager]` was
invoked!
@see lastSessionCrashDetails
*/
@property (nonatomic, readonly) BOOL didCrashInLastSession;
/**
Provides an interface to pass user input from a custom alert to a crash report
@param userInput Defines the users action wether to send, always send, or not to send the crash report.
@param userProvidedMetaData The content of this optional BITCrashMetaData instance will be attached to the crash report and allows to ask the user for e.g. additional comments or info.
@return Returns YES if the input is a valid option and successfully triggered further processing of the crash report
@see BITCrashManagerUserInput
@see BITCrashMetaData
*/
- (BOOL)handleUserInput:(BITCrashManagerUserInput)userInput withUserProvidedMetaData:(BITCrashMetaData *)userProvidedMetaData;
/**
Lets you set a custom block which handles showing a custom UI and asking the user
whether he wants to send the crash report.
This replaces the default alert the SDK would show!
You can use this to present any kind of user interface which asks the user for additional information,
e.g. what they did in the app before the app crashed.
In addition to this you should always ask your users if they agree to send crash reports, send them
always or not and return the result when calling `handleUserInput:withUserProvidedCrashDescription`.
@param alertViewHandler A block that is responsible for loading, presenting and and dismissing your custom user interface which prompts the user if he wants to send crash reports. The block is also responsible for triggering further processing of the crash reports.
@warning Block needs to call the `[BITCrashManager handleUserInput:withUserProvidedMetaData:]` method!
@warning This needs to be set before calling `[BITHockeyManager startManager]`!
*/
- (void)setAlertViewHandler:(BITCustomAlertViewHandler)alertViewHandler;
/**
* Provides details about the crash that occured in the last app session
*/
@property (nonatomic, readonly) BITCrashDetails *lastSessionCrashDetails;
/**
Indicates if the app did receive a low memory warning in the last session
It may happen that low memory warning where send but couldn't be logged, since iOS
killed the app before updating the flag in the filesystem did complete.
This property may be true in case of low memory kills, but it doesn't have to be! Apps
can also be killed without the app ever receiving a low memory warning.
Also the app could have received a low memory warning, but the reason for being killed was
actually different.
@warning This property only has a correct value, once `[BITHockeyManager startManager]` was
invoked!
@see enableAppNotTerminatingCleanlyDetection
@see lastSessionCrashDetails
*/
@property (nonatomic, readonly) BOOL didReceiveMemoryWarningInLastSession;
/**
Provides the time between startup and crash in seconds
Use this in together with `didCrashInLastSession` to detect if the app crashed very
early after startup. This can be used to delay app initialization until the crash
report has been sent to the server or if you want to do any other actions like
cleaning up some cache data etc.
Note that sending a crash reports starts as early as 1.5 seconds after the application
did finish launching!
The `BITCrashManagerDelegate` protocol provides some delegates to inform if sending
a crash report was finished successfully, ended in error or was cancelled by the user.
*Default*: _-1_
@see didCrashInLastSession
@see BITCrashManagerDelegate
*/
@property (nonatomic, readonly) NSTimeInterval timeintervalCrashInLastSessionOccured;
///-----------------------------------------------------------------------------
/// @name Helper
///-----------------------------------------------------------------------------
/**
* Detect if a debugger is attached to the app process
*
* This is only invoked once on app startup and can not detect if the debugger is being
* attached during runtime!
*
* @return BOOL if the debugger is attached on app startup
*/
- (BOOL)isDebuggerAttached;
/**
* Lets the app crash for easy testing of the SDK
*
* The best way to use this is to trigger the crash with a button action.
*
* Make sure not to let the app crash in `applicationDidFinishLaunching` or any other
* startup method! Since otherwise the app would crash before the SDK could process it.
*
* Note that our SDK provides support for handling crashes that happen early on startup.
* Check the documentation for more information on how to use this.
*
* If the SDK detects an App Store environment, it will _NOT_ cause the app to crash!
*/
- (void)generateTestCrash;
@end
File diff suppressed because it is too large. Load diff
-183
View File
@@ -1,183 +0,0 @@
/*
* Author: Andreas Linde <mail@andreaslinde.de>
*
* 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 <Foundation/Foundation.h>
@class BITCrashManager;
@class BITHockeyAttachment;
/**
The `BITCrashManagerDelegate` formal protocol defines methods further configuring
the behaviour of `BITCrashManager`.
*/
@protocol BITCrashManagerDelegate <NSObject>
@optional
///-----------------------------------------------------------------------------
/// @name Additional meta data
///-----------------------------------------------------------------------------
/** Return any log string based data the crash report being processed should contain
@param crashManager The `BITCrashManager` instance invoking this delegate
@see attachmentForCrashManager:
@see userNameForCrashManager:
@see userEmailForCrashManager:
*/
-(NSString *)applicationLogForCrashManager:(BITCrashManager *)crashManager;
/** Return a BITHockeyAttachment object providing an NSData object the crash report
being processed should contain
Please limit your attachments to reasonable files to avoid high traffic costs for your users.
Example implementation:
- (BITHockeyAttachment *)attachmentForCrashManager:(BITCrashManager *)crashManager {
NSData *data = [NSData dataWithContentsOfURL:@"mydatafile"];
BITHockeyAttachment *attachment = [[BITHockeyAttachment alloc] initWithFilename:@"myfile.data"
hockeyAttachmentData:data
contentType:@"'application/octet-stream"];
return attachment;
}
@param crashManager The `BITCrashManager` instance invoking this delegate
@see BITHockeyAttachment
@see applicationLogForCrashManager:
@see userNameForCrashManager:
@see userEmailForCrashManager:
*/
-(BITHockeyAttachment *)attachmentForCrashManager:(BITCrashManager *)crashManager;
/** Return the user name or userid that should be send along each crash report
@param crashManager The `BITCrashManager` instance invoking this delegate
@see applicationLogForCrashManager:
@see attachmentForCrashManager:
@see userEmailForCrashManager:
@deprecated Please use `BITHockeyManagerDelegate userNameForHockeyManager:componentManager:` instead
@warning When returning a non nil value, crash reports are not anonymous any
more and the alerts will not show the "anonymous" word!
*/
-(NSString *)userNameForCrashManager:(BITCrashManager *)crashManager DEPRECATED_ATTRIBUTE;
/** Return the users email address that should be send along each crash report
@param crashManager The `BITCrashManager` instance invoking this delegate
@see applicationLogForCrashManager:
@see attachmentForCrashManager:
@see userNameForCrashManager:
@deprecated Please use `BITHockeyManagerDelegate userEmailForHockeyManager:componentManager:` instead
@warning When returning a non nil value, crash reports are not anonymous any
more and the alerts will not show the "anonymous" word!
*/
-(NSString *)userEmailForCrashManager:(BITCrashManager *)crashManager DEPRECATED_ATTRIBUTE;
///-----------------------------------------------------------------------------
/// @name Alert
///-----------------------------------------------------------------------------
/** Invoked before the user is asked to send a crash report, so you can do additional actions.
E.g. to make sure not to ask the user for an app rating :)
@param crashManager The `BITCrashManager` instance invoking this delegate
*/
-(void)crashManagerWillShowSubmitCrashReportAlert:(BITCrashManager *)crashManager;
/** Invoked after the user did choose _NOT_ to send a crash in the alert
@param crashManager The `BITCrashManager` instance invoking this delegate
*/
-(void)crashManagerWillCancelSendingCrashReport:(BITCrashManager *)crashManager;
/** Invoked after the user did choose to send crashes always in the alert
@param crashManager The `BITCrashManager` instance invoking this delegate
*/
-(void)crashManagerWillSendCrashReportsAlways:(BITCrashManager *)crashManager;
///-----------------------------------------------------------------------------
/// @name Networking
///-----------------------------------------------------------------------------
/** Invoked right before sending crash reports will start
@param crashManager The `BITCrashManager` instance invoking this delegate
*/
- (void)crashManagerWillSendCrashReport:(BITCrashManager *)crashManager;
/** Invoked after sending crash reports failed
@param crashManager The `BITCrashManager` instance invoking this delegate
@param error The error returned from the NSURLConnection call or `kBITCrashErrorDomain`
with reason of type `BITCrashErrorReason`.
*/
- (void)crashManager:(BITCrashManager *)crashManager didFailWithError:(NSError *)error;
/** Invoked after sending crash reports succeeded
@param crashManager The `BITCrashManager` instance invoking this delegate
*/
- (void)crashManagerDidFinishSendingCrashReport:(BITCrashManager *)crashManager;
///-----------------------------------------------------------------------------
/// @name Experimental
///-----------------------------------------------------------------------------
/** Define if a report should be considered as a crash report
Due to the risk, that these reports may be false positives, this delegates allows the
developer to influence which reports detected by the heuristic should actually be reported.
The developer can use the following property to get more information about the crash scenario:
- `[BITCrashManager didReceiveMemoryWarningInLastSession]`: Did the app receive a low memory warning
This allows only reports to be considered where at least one low memory warning notification was
received by the app to reduce to possibility of having false positives.
@param crashManager The `BITCrashManager` instance invoking this delegate
@return `YES` if the heuristic based detected report should be reported, otherwise `NO`
@see `[BITCrashManager didReceiveMemoryWarningInLastSession]`
*/
-(BOOL)considerAppNotTerminatedCleanlyReportForCrashManager:(BITCrashManager *)crashManager;
@end
-109
View File
@@ -1,109 +0,0 @@
/*
* Author: Andreas Linde <mail@andreaslinde.de>
*
* Copyright (c) 2013-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 "HockeySDK.h"
#if HOCKEYSDK_FEATURE_CRASH_REPORTER
#import <CrashReporter/CrashReporter.h>
@class BITHockeyAppClient;
@interface BITCrashManager () {
}
///-----------------------------------------------------------------------------
/// @name Delegate
///-----------------------------------------------------------------------------
/**
Sets the optional `BITCrashManagerDelegate` delegate.
The delegate is automatically set by using `[BITHockeyManager setDelegate:]`. You
should not need to set this delegate individually.
@see `[BITHockeyManager setDelegate:]`
*/
@property (nonatomic, weak) id delegate;
/**
* must be set
*/
@property (nonatomic, strong) BITHockeyAppClient *hockeyAppClient;
@property (nonatomic) NSUncaughtExceptionHandler *exceptionHandler;
@property (nonatomic, strong) NSFileManager *fileManager;
@property (nonatomic, strong) BITPLCrashReporter *plCrashReporter;
@property (nonatomic) NSString *lastCrashFilename;
@property (nonatomic, copy, setter = setAlertViewHandler:) BITCustomAlertViewHandler alertViewHandler;
@property (nonatomic, strong) NSString *crashesDir;
#if HOCKEYSDK_FEATURE_AUTHENTICATOR
// Only set via BITAuthenticator
@property (nonatomic, strong) NSString *installationIdentification;
// Only set via BITAuthenticator
@property (nonatomic) BITAuthenticatorIdentificationType installationIdentificationType;
// Only set via BITAuthenticator
@property (nonatomic) BOOL installationIdentified;
#endif /* HOCKEYSDK_FEATURE_AUTHENTICATOR */
- (void)cleanCrashReports;
- (NSString *)userIDForCrashReport;
- (NSString *)userEmailForCrashReport;
- (NSString *)userNameForCrashReport;
- (void)handleCrashReport;
- (BOOL)hasPendingCrashReport;
- (NSString *)firstNotApprovedCrashReport;
- (void)persistUserProvidedMetaData:(BITCrashMetaData *)userProvidedMetaData;
- (void)persistAttachment:(BITHockeyAttachment *)attachment withFilename:(NSString *)filename;
- (BITHockeyAttachment *)attachmentForCrashReport:(NSString *)filename;
- (void)invokeDelayedProcessing;
- (void)sendNextCrashReport;
- (void)setLastCrashFilename:(NSString *)lastCrashFilename;
@end
#endif /* HOCKEYSDK_FEATURE_CRASH_REPORTER */
-57
View File
@@ -1,57 +0,0 @@
/*
* Author: Andreas Linde <mail@andreaslinde.de>
*
* 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 <Foundation/Foundation.h>
/**
* This class provides properties that can be attached to a crash report via a custom alert view flow
*/
@interface BITCrashMetaData : NSObject
/**
* User provided description that should be attached to the crash report as plain text
*/
@property (nonatomic, copy) NSString *userDescription;
/**
* User name that should be attached to the crash report
*/
@property (nonatomic, copy) NSString *userName;
/**
* User email that should be attached to the crash report
*/
@property (nonatomic, copy) NSString *userEmail;
/**
* User ID that should be attached to the crash report
*/
@property (nonatomic, copy) NSString *userID;
@end
-34
View File
@@ -1,34 +0,0 @@
/*
* Author: Andreas Linde <mail@andreaslinde.de>
*
* 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 "BITCrashMetaData.h"
@implementation BITCrashMetaData
@end
-54
View File
@@ -1,54 +0,0 @@
/*
* Authors:
* Landon Fuller <landonf@plausiblelabs.com>
* Damian Morris <damian@moso.com.au>
* Andreas Linde <mail@andreaslinde.de>
*
* Copyright (c) 2008-2013 Plausible Labs Cooperative, Inc.
* Copyright (c) 2010 MOSO Corporation, Pty Ltd.
* 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 <Foundation/Foundation.h>
#import <CrashReporter/PLCrashReport.h>
// Dictionary keys for array elements returned by arrayOfAppUUIDsForCrashReport:
#ifndef kBITBinaryImageKeyUUID
#define kBITBinaryImageKeyUUID @"uuid"
#define kBITBinaryImageKeyArch @"arch"
#define kBITBinaryImageKeyType @"type"
#endif
@interface BITCrashReportTextFormatter : NSObject {
}
+ (NSString *)stringValueForCrashReport:(PLCrashReport *)report crashReporterKey:(NSString *)crashReporterKey;
+ (NSArray *)arrayOfAppUUIDsForCrashReport:(PLCrashReport *)report;
+ (NSString *)bit_archNameFromCPUType:(uint64_t)cpuType subType:(uint64_t)subType;
@end
-822
View File
@@ -1,822 +0,0 @@
/*
* Authors:
* Landon Fuller <landonf@plausiblelabs.com>
* Damian Morris <damian@moso.com.au>
* Andreas Linde <mail@andreaslinde.de>
*
* Copyright (c) 2008-2013 Plausible Labs Cooperative, Inc.
* Copyright (c) 2010 MOSO Corporation, Pty Ltd.
* 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 <CrashReporter/CrashReporter.h>
#import <mach-o/dyld.h>
#import <mach-o/getsect.h>
#import <mach-o/ldsyms.h>
#import <dlfcn.h>
#import <Availability.h>
#if defined(__OBJC2__)
#define SEL_NAME_SECT "__objc_methname"
#else
#define SEL_NAME_SECT "__cstring"
#endif
#import "BITCrashReportTextFormatter.h"
/*
* XXX: The ARM64 CPU type, and ARM_V7S and ARM_V8 Mach-O CPU subtypes are not
* defined in the Mac OS X 10.8 headers.
*/
#ifndef CPU_SUBTYPE_ARM_V7S
# define CPU_SUBTYPE_ARM_V7S 11
#endif
#ifndef CPU_TYPE_ARM64
#define CPU_TYPE_ARM64 (CPU_TYPE_ARM | CPU_ARCH_ABI64)
#endif
#ifndef CPU_SUBTYPE_ARM_V8
# define CPU_SUBTYPE_ARM_V8 13
#endif
/**
* Sort PLCrashReportBinaryImageInfo instances by their starting address.
*/
static NSInteger bit_binaryImageSort(id binary1, id binary2, void *context) {
uint64_t addr1 = [binary1 imageBaseAddress];
uint64_t addr2 = [binary2 imageBaseAddress];
if (addr1 < addr2)
return NSOrderedAscending;
else if (addr1 > addr2)
return NSOrderedDescending;
else
return NSOrderedSame;
}
/**
* Validates that the given @a string terminates prior to @a limit.
*/
static const char *safer_string_read (const char *string, const char *limit) {
const char *p = string;
do {
if (p >= limit || p+1 >= limit) {
return NULL;
}
p++;
} while (*p != '\0');
return string;
}
/*
* The relativeAddress should be `<ecx/rsi/r1/x1 ...> - <image base>`, extracted from the crash report's thread
* and binary image list.
*
* For the (architecture-specific) registers to attempt, see:
* http://sealiesoftware.com/blog/archive/2008/09/22/objc_explain_So_you_crashed_in_objc_msgSend.html
*/
static const char *findSEL (const char *imageName, NSString *imageUUID, uint64_t relativeAddress) {
unsigned int images_count = _dyld_image_count();
for (unsigned int i = 0; i < images_count; ++i) {
intptr_t slide = _dyld_get_image_vmaddr_slide(i);
const struct mach_header *header = _dyld_get_image_header(i);
const struct mach_header_64 *header64 = (const struct mach_header_64 *) header;
const char *name = _dyld_get_image_name(i);
/* Image disappeared? */
if (name == NULL || header == NULL)
continue;
/* Check if this is the correct image. If we were being even more careful, we'd check the LC_UUID */
if (strcmp(name, imageName) != 0)
continue;
/* Determine whether this is a 64-bit or 32-bit Mach-O file */
BOOL m64 = NO;
if (header->magic == MH_MAGIC_64)
m64 = YES;
NSString *uuidString = nil;
const uint8_t *command;
uint32_t ncmds;
if (m64) {
command = (const uint8_t *)(header64 + 1);
ncmds = header64->ncmds;
} else {
command = (const uint8_t *)(header + 1);
ncmds = header->ncmds;
}
for (uint32_t idx = 0; idx < ncmds; ++idx) {
const struct load_command *load_command = (const struct load_command *)command;
if (load_command->cmd == LC_UUID) {
const struct uuid_command *uuid_command = (const struct uuid_command *)command;
const uint8_t *uuid = uuid_command->uuid;
uuidString = [[NSString stringWithFormat:@"%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X",
uuid[0], uuid[1], uuid[2], uuid[3],
uuid[4], uuid[5], uuid[6], uuid[7],
uuid[8], uuid[9], uuid[10], uuid[11],
uuid[12], uuid[13], uuid[14], uuid[15]]
lowercaseString];
break;
} else {
command += load_command->cmdsize;
}
}
// Check if this is the correct image by comparing the UUIDs
if (!uuidString || ![uuidString isEqualToString:imageUUID])
continue;
/* Fetch the __objc_methname section */
const char *methname_sect;
uint64_t methname_sect_size;
if (m64) {
methname_sect = getsectdatafromheader_64(header64, SEG_TEXT, SEL_NAME_SECT, &methname_sect_size);
} else {
uint32_t meth_size_32;
methname_sect = getsectdatafromheader(header, SEG_TEXT, SEL_NAME_SECT, &meth_size_32);
methname_sect_size = meth_size_32;
}
/* Apply the slide, as per getsectdatafromheader(3) */
methname_sect += slide;
if (methname_sect == NULL) {
return NULL;
}
/* Calculate the target address within this image, and verify that it is within __objc_methname */
const char *target = ((const char *)header) + relativeAddress;
const char *limit = methname_sect + methname_sect_size;
if (target < methname_sect || target >= limit) {
return NULL;
}
/* Read the actual method name */
return safer_string_read(target, limit);
}
return NULL;
}
/**
* Formats PLCrashReport data as human-readable text.
*/
@implementation BITCrashReportTextFormatter
/**
* Formats the provided @a report as human-readable text in the given @a textFormat, and return
* the formatted result as a string.
*
* @param report The report to format.
* @param textFormat The text format to use.
*
* @return Returns the formatted result on success, or nil if an error occurs.
*/
+ (NSString *)stringValueForCrashReport:(BITPLCrashReport *)report crashReporterKey:(NSString *)crashReporterKey {
NSMutableString* text = [NSMutableString string];
boolean_t lp64 = true; // quiesce GCC uninitialized value warning
/* Header */
/* Map to apple style OS nane */
NSString *osName;
switch (report.systemInfo.operatingSystem) {
case PLCrashReportOperatingSystemMacOSX:
osName = @"Mac OS X";
break;
case PLCrashReportOperatingSystemiPhoneOS:
osName = @"iPhone OS";
break;
case PLCrashReportOperatingSystemiPhoneSimulator:
osName = @"Mac OS X";
break;
default:
osName = [NSString stringWithFormat: @"Unknown (%d)", report.systemInfo.operatingSystem];
break;
}
/* Map to Apple-style code type, and mark whether architecture is LP64 (64-bit) */
NSString *codeType = nil;
{
/* Attempt to derive the code type from the binary images */
for (BITPLCrashReportBinaryImageInfo *image in report.images) {
/* Skip images with no specified type */
if (image.codeType == nil)
continue;
/* Skip unknown encodings */
if (image.codeType.typeEncoding != PLCrashReportProcessorTypeEncodingMach)
continue;
switch (image.codeType.type) {
case CPU_TYPE_ARM:
codeType = @"ARM";
lp64 = false;
break;
case CPU_TYPE_ARM64:
codeType = @"ARM-64";
lp64 = true;
break;
case CPU_TYPE_X86:
codeType = @"X86";
lp64 = false;
break;
case CPU_TYPE_X86_64:
codeType = @"X86-64";
lp64 = true;
break;
case CPU_TYPE_POWERPC:
codeType = @"PPC";
lp64 = false;
break;
default:
// Do nothing, handled below.
break;
}
/* Stop immediately if code type was discovered */
if (codeType != nil)
break;
}
/* If we were unable to determine the code type, fall back on the legacy architecture value. */
if (codeType == nil) {
switch (report.systemInfo.architecture) {
case PLCrashReportArchitectureARMv6:
case PLCrashReportArchitectureARMv7:
codeType = @"ARM";
lp64 = false;
break;
case PLCrashReportArchitectureX86_32:
codeType = @"X86";
lp64 = false;
break;
case PLCrashReportArchitectureX86_64:
codeType = @"X86-64";
lp64 = true;
break;
case PLCrashReportArchitecturePPC:
codeType = @"PPC";
lp64 = false;
break;
default:
codeType = [NSString stringWithFormat: @"Unknown (%d)", report.systemInfo.architecture];
lp64 = true;
break;
}
}
}
{
NSString *reporterKey = @"???";
if (crashReporterKey && [crashReporterKey length] > 0)
reporterKey = crashReporterKey;
NSString *hardwareModel = @"???";
if (report.hasMachineInfo && report.machineInfo.modelName != nil)
hardwareModel = report.machineInfo.modelName;
NSString *incidentIdentifier = @"???";
if (report.uuidRef != NULL) {
incidentIdentifier = (NSString *) CFBridgingRelease(CFUUIDCreateString(NULL, report.uuidRef));
}
[text appendFormat: @"Incident Identifier: %@\n", incidentIdentifier];
[text appendFormat: @"CrashReporter Key: %@\n", reporterKey];
[text appendFormat: @"Hardware Model: %@\n", hardwareModel];
}
/* Application and process info */
{
NSString *unknownString = @"???";
NSString *processName = unknownString;
NSString *processId = unknownString;
NSString *processPath = unknownString;
NSString *parentProcessName = unknownString;
NSString *parentProcessId = unknownString;
/* Process information was not available in earlier crash report versions */
if (report.hasProcessInfo) {
/* Process Name */
if (report.processInfo.processName != nil)
processName = report.processInfo.processName;
/* PID */
processId = [[NSNumber numberWithUnsignedInteger: report.processInfo.processID] stringValue];
/* Process Path */
if (report.processInfo.processPath != nil) {
processPath = report.processInfo.processPath;
/* Remove username from the path */
if ([processPath length] > 0)
processPath = [processPath stringByAbbreviatingWithTildeInPath];
if ([processPath length] > 0 && [[processPath substringToIndex:1] isEqualToString:@"~"])
processPath = [NSString stringWithFormat:@"/Users/USER%@", [processPath substringFromIndex:1]];
}
/* Parent Process Name */
if (report.processInfo.parentProcessName != nil)
parentProcessName = report.processInfo.parentProcessName;
/* Parent Process ID */
parentProcessId = [@(report.processInfo.parentProcessID) stringValue];
}
[text appendFormat: @"Process: %@ [%@]\n", processName, processId];
[text appendFormat: @"Path: %@\n", processPath];
[text appendFormat: @"Identifier: %@\n", report.applicationInfo.applicationIdentifier];
[text appendFormat: @"Version: %@\n", report.applicationInfo.applicationVersion];
[text appendFormat: @"Code Type: %@\n", codeType];
[text appendFormat: @"Parent Process: %@ [%@]\n", parentProcessName, parentProcessId];
}
[text appendString: @"\n"];
/* System info */
{
NSString *osBuild = @"???";
if (report.systemInfo.operatingSystemBuild != nil)
osBuild = report.systemInfo.operatingSystemBuild;
NSLocale *enUSPOSIXLocale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"];
NSDateFormatter *rfc3339Formatter = [[NSDateFormatter alloc] init];
[rfc3339Formatter setLocale:enUSPOSIXLocale];
[rfc3339Formatter setDateFormat:@"yyyy'-'MM'-'dd'T'HH':'mm':'ss'Z'"];
[rfc3339Formatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]];
[text appendFormat: @"Date/Time: %@\n", [rfc3339Formatter stringFromDate:report.systemInfo.timestamp]];
[text appendFormat: @"OS Version: %@ %@ (%@)\n", osName, report.systemInfo.operatingSystemVersion, osBuild];
[text appendFormat: @"Report Version: 104\n"];
}
[text appendString: @"\n"];
/* Exception code */
[text appendFormat: @"Exception Type: %@\n", report.signalInfo.name];
[text appendFormat: @"Exception Codes: %@ at 0x%" PRIx64 "\n", report.signalInfo.code, report.signalInfo.address];
for (BITPLCrashReportThreadInfo *thread in report.threads) {
if (thread.crashed) {
[text appendFormat: @"Crashed Thread: %ld\n", (long) thread.threadNumber];
break;
}
}
[text appendString: @"\n"];
BITPLCrashReportThreadInfo *crashed_thread = nil;
for (BITPLCrashReportThreadInfo *thread in report.threads) {
if (thread.crashed) {
crashed_thread = thread;
break;
}
}
/* Uncaught Exception */
if (report.hasExceptionInfo) {
[text appendFormat: @"Application Specific Information:\n"];
[text appendFormat: @"*** Terminating app due to uncaught exception '%@', reason: '%@'\n",
report.exceptionInfo.exceptionName, report.exceptionInfo.exceptionReason];
[text appendString: @"\n"];
} else if (crashed_thread != nil) {
// try to find the selector in case this was a crash in obj_msgSend
// we search this wether the crash happend in obj_msgSend or not since we don't have the symbol!
NSString *foundSelector = nil;
// search the registers value for the current arch
#if TARGET_IPHONE_SIMULATOR
if (lp64) {
foundSelector = [[self class] selectorForRegisterWithName:@"rsi" ofThread:crashed_thread report:report];
if (foundSelector == NULL)
foundSelector = [[self class] selectorForRegisterWithName:@"rdx" ofThread:crashed_thread report:report];
} else {
foundSelector = [[self class] selectorForRegisterWithName:@"ecx" ofThread:crashed_thread report:report];
}
#else
if (lp64) {
foundSelector = [[self class] selectorForRegisterWithName:@"x1" ofThread:crashed_thread report:report];
} else {
foundSelector = [[self class] selectorForRegisterWithName:@"r1" ofThread:crashed_thread report:report];
if (foundSelector == NULL)
foundSelector = [[self class] selectorForRegisterWithName:@"r2" ofThread:crashed_thread report:report];
}
#endif
if (foundSelector) {
[text appendFormat: @"Application Specific Information:\n"];
[text appendFormat: @"Selector name found in current argument registers: %@\n", foundSelector];
[text appendString: @"\n"];
}
}
/* If an exception stack trace is available, output an Apple-compatible backtrace. */
if (report.exceptionInfo != nil && report.exceptionInfo.stackFrames != nil && [report.exceptionInfo.stackFrames count] > 0) {
BITPLCrashReportExceptionInfo *exception = report.exceptionInfo;
/* Create the header. */
[text appendString: @"Last Exception Backtrace:\n"];
/* Write out the frames. In raw reports, Apple writes this out as a simple list of PCs. In the minimally
* post-processed report, Apple writes this out as full frame entries. We use the latter format. */
for (NSUInteger frame_idx = 0; frame_idx < [exception.stackFrames count]; frame_idx++) {
BITPLCrashReportStackFrameInfo *frameInfo = [exception.stackFrames objectAtIndex: frame_idx];
[text appendString: [[self class] bit_formatStackFrame: frameInfo frameIndex: frame_idx report: report lp64: lp64]];
}
[text appendString: @"\n"];
}
/* Threads */
NSInteger maxThreadNum = 0;
for (BITPLCrashReportThreadInfo *thread in report.threads) {
if (thread.crashed) {
[text appendFormat: @"Thread %ld Crashed:\n", (long) thread.threadNumber];
} else {
[text appendFormat: @"Thread %ld:\n", (long) thread.threadNumber];
}
for (NSUInteger frame_idx = 0; frame_idx < [thread.stackFrames count]; frame_idx++) {
BITPLCrashReportStackFrameInfo *frameInfo = [thread.stackFrames objectAtIndex: frame_idx];
[text appendString: [[self class] bit_formatStackFrame: frameInfo frameIndex: frame_idx report: report lp64: lp64]];
}
[text appendString: @"\n"];
/* Track the highest thread number */
maxThreadNum = MAX(maxThreadNum, thread.threadNumber);
}
/* Registers */
if (crashed_thread != nil) {
[text appendFormat: @"Thread %ld crashed with %@ Thread State:\n", (long) crashed_thread.threadNumber, codeType];
int regColumn = 0;
for (BITPLCrashReportRegisterInfo *reg in crashed_thread.registers) {
NSString *reg_fmt;
/* Use 32-bit or 64-bit fixed width format for the register values */
if (lp64)
reg_fmt = @"%6s: 0x%016" PRIx64 " ";
else
reg_fmt = @"%6s: 0x%08" PRIx64 " ";
/* Remap register names to match Apple's crash reports */
NSString *regName = reg.registerName;
if (report.machineInfo != nil && report.machineInfo.processorInfo.typeEncoding == PLCrashReportProcessorTypeEncodingMach) {
BITPLCrashReportProcessorInfo *pinfo = report.machineInfo.processorInfo;
cpu_type_t arch_type = pinfo.type & ~CPU_ARCH_MASK;
/* Apple uses 'ip' rather than 'r12' on ARM */
if (arch_type == CPU_TYPE_ARM && [regName isEqual: @"r12"]) {
regName = @"ip";
}
}
[text appendFormat: reg_fmt, [regName UTF8String], reg.registerValue];
regColumn++;
if (regColumn == 4) {
[text appendString: @"\n"];
regColumn = 0;
}
}
if (regColumn != 0)
[text appendString: @"\n"];
[text appendString: @"\n"];
}
/* Images. The iPhone crash report format sorts these in ascending order, by the base address */
[text appendString: @"Binary Images:\n"];
for (BITPLCrashReportBinaryImageInfo *imageInfo in [report.images sortedArrayUsingFunction: bit_binaryImageSort context: nil]) {
NSString *uuid;
/* Fetch the UUID if it exists */
if (imageInfo.hasImageUUID)
uuid = imageInfo.imageUUID;
else
uuid = @"???";
/* Determine the architecture string */
NSString *archName = [[self class] bit_archNameFromImageInfo:imageInfo];
/* Determine if this is the main executable or an app specific framework*/
NSString *binaryDesignator = @" ";
NSString *imagePath = [imageInfo.imageName stringByStandardizingPath];
NSString *appBundleContentsPath = [[report.processInfo.processPath stringByDeletingLastPathComponent] stringByDeletingLastPathComponent];
// exclude iOS swift dylibs
if ([imageInfo.imageName rangeOfString:@".app/Frameworks/libswift"].location == NSNotFound) {
if ([imagePath isEqual: report.processInfo.processPath] ||
[imagePath hasPrefix:appBundleContentsPath] ||
[imageInfo.imageName hasPrefix:appBundleContentsPath]) // Fix issue with iOS 8 `stringByStandardizingPath` removing leading `/private` path (when not running in the debugger only)
binaryDesignator = @"+";
}
/* base_address - terminating_address [designator]file_name arch <uuid> file_path */
NSString *fmt = nil;
if (lp64) {
fmt = @"%18#" PRIx64 " - %18#" PRIx64 " %@%@ %@ <%@> %@\n";
} else {
fmt = @"%10#" PRIx64 " - %10#" PRIx64 " %@%@ %@ <%@> %@\n";
}
/* Remove username from the image path */
NSString *imageName = @"";
if (imageInfo.imageName && [imageInfo.imageName length] > 0)
imageName = [imageInfo.imageName stringByAbbreviatingWithTildeInPath];
if ([imageName length] > 0 && [[imageName substringToIndex:1] isEqualToString:@"~"])
imageName = [NSString stringWithFormat:@"/Users/USER%@", [imageName substringFromIndex:1]];
[text appendFormat: fmt,
imageInfo.imageBaseAddress,
imageInfo.imageBaseAddress + (MAX(1, imageInfo.imageSize) - 1), // The Apple format uses an inclusive range
binaryDesignator,
[imageInfo.imageName lastPathComponent],
archName,
uuid,
imageName];
}
return text;
}
/**
* Return the selector string of a given register name
*
* @param regName The name of the register to use for getting the address
* @param thread The crashed thread
* @param images NSArray of binary images
*
* @return The selector as a C string or NULL if no selector was found
*/
+ (NSString *)selectorForRegisterWithName:(NSString *)regName ofThread:(BITPLCrashReportThreadInfo *)thread report:(BITPLCrashReport *)report {
// get the address for the register
uint64_t regAddress = 0;
for (BITPLCrashReportRegisterInfo *reg in thread.registers) {
if ([reg.registerName isEqualToString:regName]) {
regAddress = reg.registerValue;
break;
}
}
if (regAddress == 0)
return nil;
BITPLCrashReportBinaryImageInfo *imageForRegAddress = [report imageForAddress:regAddress];
if (imageForRegAddress) {
// get the SEL
const char *foundSelector = findSEL([imageForRegAddress.imageName UTF8String], imageForRegAddress.imageUUID, regAddress - (uint64_t)imageForRegAddress.imageBaseAddress);
if (foundSelector != NULL) {
return [NSString stringWithUTF8String:foundSelector];
}
}
return nil;
}
/**
* Returns an array of app UUIDs and their architecture
* As a dictionary for each element
*
* @param report The report to format.
*
* @return Returns the formatted result on success, or nil if an error occurs.
*/
+ (NSArray *)arrayOfAppUUIDsForCrashReport:(BITPLCrashReport *)report {
NSMutableArray* appUUIDs = [NSMutableArray array];
/* Images. The iPhone crash report format sorts these in ascending order, by the base address */
for (BITPLCrashReportBinaryImageInfo *imageInfo in [report.images sortedArrayUsingFunction: bit_binaryImageSort context: nil]) {
NSString *uuid;
/* Fetch the UUID if it exists */
if (imageInfo.hasImageUUID)
uuid = imageInfo.imageUUID;
else
uuid = @"???";
/* Determine the architecture string */
NSString *archName = [[self class] bit_archNameFromImageInfo:imageInfo];
/* Determine if this is the app executable or app specific framework */
NSString *imagePath = [imageInfo.imageName stringByStandardizingPath];
NSString *appBundleContentsPath = [[report.processInfo.processPath stringByDeletingLastPathComponent] stringByDeletingLastPathComponent];
NSString *imageType = @"";
if ([imageInfo.imageName isEqual: report.processInfo.processPath]) {
imageType = @"app";
} else {
imageType = @"framework";
}
if ([imagePath isEqual: report.processInfo.processPath] || [imagePath hasPrefix:appBundleContentsPath]) {
[appUUIDs addObject:@{kBITBinaryImageKeyUUID: uuid, kBITBinaryImageKeyArch: archName, kBITBinaryImageKeyType: imageType}];
}
}
return appUUIDs;
}
+ (NSString *)bit_archNameFromImageInfo:(BITPLCrashReportBinaryImageInfo *)imageInfo
{
NSString *archName = @"???";
if (imageInfo.codeType != nil && imageInfo.codeType.typeEncoding == PLCrashReportProcessorTypeEncodingMach) {
archName = [BITCrashReportTextFormatter bit_archNameFromCPUType:imageInfo.codeType.type subType:imageInfo.codeType.subtype];
}
return archName;
}
+ (NSString *)bit_archNameFromCPUType:(uint64_t)cpuType subType:(uint64_t)subType {
NSString *archName = @"???";
switch (cpuType) {
case CPU_TYPE_ARM:
/* Apple includes subtype for ARM binaries. */
switch (subType) {
case CPU_SUBTYPE_ARM_V6:
archName = @"armv6";
break;
case CPU_SUBTYPE_ARM_V7:
archName = @"armv7";
break;
case CPU_SUBTYPE_ARM_V7S:
archName = @"armv7s";
break;
default:
archName = @"arm-unknown";
break;
}
break;
case CPU_TYPE_ARM64:
/* Apple includes subtype for ARM64 binaries. */
switch (subType) {
case CPU_SUBTYPE_ARM_ALL:
archName = @"arm64";
break;
case CPU_SUBTYPE_ARM_V8:
archName = @"arm64";
break;
default:
archName = @"arm64-unknown";
break;
}
break;
case CPU_TYPE_X86:
archName = @"i386";
break;
case CPU_TYPE_X86_64:
archName = @"x86_64";
break;
case CPU_TYPE_POWERPC:
archName = @"powerpc";
break;
default:
// Use the default archName value (initialized above).
break;
}
return archName;
}
/**
* Format a stack frame for display in a thread backtrace.
*
* @param frameInfo The stack frame to format
* @param frameIndex The frame's index
* @param report The report from which this frame was acquired.
* @param lp64 If YES, the report was generated by an LP64 system.
*
* @return Returns a formatted frame line.
*/
+ (NSString *)bit_formatStackFrame: (BITPLCrashReportStackFrameInfo *) frameInfo
frameIndex: (NSUInteger) frameIndex
report: (BITPLCrashReport *) report
lp64: (BOOL) lp64
{
/* Base image address containing instrumention pointer, offset of the IP from that base
* address, and the associated image name */
uint64_t baseAddress = 0x0;
uint64_t pcOffset = 0x0;
NSString *imageName = @"\?\?\?";
NSString *symbolString = nil;
BITPLCrashReportBinaryImageInfo *imageInfo = [report imageForAddress: frameInfo.instructionPointer];
if (imageInfo != nil) {
imageName = [imageInfo.imageName lastPathComponent];
baseAddress = imageInfo.imageBaseAddress;
pcOffset = frameInfo.instructionPointer - imageInfo.imageBaseAddress;
}
/* Make sure UTF8/16 characters are handled correctly */
NSInteger offset = 0;
NSInteger index = 0;
for (index = 0; index < [imageName length]; index++) {
NSRange range = [imageName rangeOfComposedCharacterSequenceAtIndex:index];
if (range.length > 1) {
offset += range.length - 1;
index += range.length - 1;
}
if (index > 32) {
imageName = [NSString stringWithFormat:@"%@... ", [imageName substringToIndex:index - 1]];
index += 3;
break;
}
}
if (index-offset < 36) {
imageName = [imageName stringByPaddingToLength:36+offset withString:@" " startingAtIndex:0];
}
/* If symbol info is available, the format used in Apple's reports is Sym + OffsetFromSym. Otherwise,
* the format used is imageBaseAddress + offsetToIP */
NSString *imagePath = [imageInfo.imageName stringByStandardizingPath];
NSString *appBundleContentsPath = [[report.processInfo.processPath stringByDeletingLastPathComponent] stringByDeletingLastPathComponent];
if (frameInfo.symbolInfo != nil &&
![imagePath isEqual: report.processInfo.processPath] &&
![imagePath hasPrefix:appBundleContentsPath]) {
NSString *symbolName = frameInfo.symbolInfo.symbolName;
/* Apple strips the _ symbol prefix in their reports. Only OS X makes use of an
* underscore symbol prefix by default. */
if ([symbolName rangeOfString: @"_"].location == 0 && [symbolName length] > 1) {
switch (report.systemInfo.operatingSystem) {
case PLCrashReportOperatingSystemMacOSX:
case PLCrashReportOperatingSystemiPhoneOS:
case PLCrashReportOperatingSystemiPhoneSimulator:
symbolName = [symbolName substringFromIndex: 1];
break;
default:
NSLog(@"Symbol prefix rules are unknown for this OS!");
break;
}
}
uint64_t symOffset = frameInfo.instructionPointer - frameInfo.symbolInfo.startAddress;
symbolString = [NSString stringWithFormat: @"%@ + %" PRId64, symbolName, symOffset];
} else {
symbolString = [NSString stringWithFormat: @"0x%" PRIx64 " + %" PRId64, baseAddress, pcOffset];
}
/* Note that width specifiers are ignored for %@, but work for C strings.
* UTF-8 is not correctly handled with %s (it depends on the system encoding), but
* UTF-16 is supported via %S, so we use it here */
return [NSString stringWithFormat: @"%-4ld%-35S 0x%0*" PRIx64 " %@\n",
(long) frameIndex,
(const uint16_t *)[imageName cStringUsingEncoding: NSUTF16StringEncoding],
lp64 ? 16 : 8, frameInfo.instructionPointer,
symbolString];
}
@end
-75
View File
@@ -1,75 +0,0 @@
/*
* Author: Andreas Linde <mail@andreaslinde.de>
*
* 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>
#import "BITFeedbackComposeViewControllerDelegate.h"
/**
UIActivity subclass allowing to use the feedback interface to share content with the developer
This activity can be added into an UIActivityViewController and it will use the activity data
objects to prefill the content of `BITFeedbackComposeViewController`.
This can be useful if you present some data that users can not only share but also
report back to the developer because they have some problems, e.g. webcams not working
any more.
The activity provide a default title and image that can be further customized
via `customActivityImage` and `customActivityTitle`.
*/
@interface BITFeedbackActivity : UIActivity <BITFeedbackComposeViewControllerDelegate>
///-----------------------------------------------------------------------------
/// @name BITFeedbackActivity customisation
///-----------------------------------------------------------------------------
/**
Define the image shown when using `BITFeedbackActivity`
If not set a default icon is being used.
@see customActivityTitle
*/
@property (nonatomic, strong) UIImage *customActivityImage;
/**
Define the title shown when using `BITFeedbackActivity`
If not set, a default string is shown by using the apps name
and adding the localized string "Feedback" to it.
@see customActivityImage
*/
@property (nonatomic, strong) NSString *customActivityTitle;
@end
-150
View File
@@ -1,150 +0,0 @@
/*
* Author: Andreas Linde <mail@andreaslinde.de>
*
* 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 "HockeySDK.h"
#if HOCKEYSDK_FEATURE_FEEDBACK
#import "HockeySDKPrivate.h"
#import "BITFeedbackActivity.h"
#import "BITHockeyHelper.h"
#import "BITFeedbackManagerPrivate.h"
#import "BITHockeyBaseManagerPrivate.h"
#import "BITHockeyAttachment.h"
@interface BITFeedbackActivity()
@property (nonatomic, strong) NSMutableArray *items;
@end
@implementation BITFeedbackActivity
{
UIViewController *_activityViewController;
}
#pragma mark - NSObject
- (instancetype)init {
if ((self = [super init])) {
_customActivityImage = nil;
_customActivityTitle = nil;
_items = [NSMutableArray array];
}
return self;
}
#pragma mark - UIActivity
- (NSString *)activityType {
return @"UIActivityTypePostToHockeySDKFeedback";
}
- (NSString *)activityTitle {
if (self.customActivityTitle)
return self.customActivityTitle;
NSString *appName = bit_appName(BITHockeyLocalizedString(@"HockeyFeedbackActivityAppPlaceholder"));
return [NSString stringWithFormat:BITHockeyLocalizedString(@"HockeyFeedbackActivityButtonTitle"), appName];
}
- (UIImage *)activityImage {
if (self.customActivityImage)
return self.customActivityImage;
return bit_imageNamed(@"feedbackActiviy.png", BITHOCKEYSDK_BUNDLE);
}
- (BOOL)canPerformWithActivityItems:(NSArray *)activityItems {
if ([BITHockeyManager sharedHockeyManager].disableFeedbackManager) return NO;
for (UIActivityItemProvider *item in activityItems) {
if ([item isKindOfClass:[NSString class]]) {
return YES;
} else if ([item isKindOfClass:[UIImage class]]) {
return YES;
} else if ([item isKindOfClass:[NSData class]]) {
return YES;
} else if ([item isKindOfClass:[BITHockeyAttachment class]]) {
return YES;
} else if ([item isKindOfClass:[NSURL class]]) {
return YES;
}
}
return NO;
}
- (void)prepareWithActivityItems:(NSArray *)activityItems {
for (id item in activityItems) {
if ([item isKindOfClass:[NSString class]] ||
[item isKindOfClass:[UIImage class]] ||
[item isKindOfClass:[NSData class]] ||
[item isKindOfClass:[BITHockeyAttachment class]] ||
[item isKindOfClass:[NSURL class]]) {
[_items addObject:item];
} else {
BITHockeyLog(@"Unknown item type %@", item);
}
}
}
- (UIViewController *)activityViewController {
if (!_activityViewController) {
// TODO: return compose controller with activity content added
BITFeedbackManager *manager = [BITHockeyManager sharedHockeyManager].feedbackManager;
BITFeedbackComposeViewController *composeViewController = [manager feedbackComposeViewController];
composeViewController.delegate = self;
[composeViewController prepareWithItems:_items];
_activityViewController = [manager customNavigationControllerWithRootViewController:composeViewController
presentationStyle:UIModalPresentationFormSheet];
_activityViewController.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
}
return _activityViewController;
}
- (void)feedbackComposeViewController:(BITFeedbackComposeViewController *)composeViewController didFinishWithResult:(BITFeedbackComposeResult)composeResult {
[self activityDidFinish:composeResult == BITFeedbackComposeResultSubmitted];
}
@end
#endif /* HOCKEYSDK_FEATURE_FEEDBACK */
@@ -1,89 +0,0 @@
/*
* Author: Andreas Linde <mail@andreaslinde.de>
*
* 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>
#import "BITFeedbackComposeViewControllerDelegate.h"
/**
View controller allowing the user to write and send new feedback
To add this view controller to your own app and push it onto a navigation stack,
don't create the intance yourself, but use the following code to get a correct instance:
[[BITHockeyManager sharedHockeyManager].feedbackManager feedbackComposeViewController]
To show it modally, use the following code instead:
[[BITHockeyManager sharedHockeyManager].feedbackManager showFeedbackComposeView]
*/
@interface BITFeedbackComposeViewController : UIViewController <UITextViewDelegate>
///-----------------------------------------------------------------------------
/// @name Delegate
///-----------------------------------------------------------------------------
/**
Sets the `BITFeedbackComposeViewControllerDelegate` delegate.
The delegate is automatically set by using `[BITHockeyManager setDelegate:]`. You
should not need to set this delegate individually.
@see `[BITHockeyManager setDelegate:`]
*/
@property (nonatomic, weak) id<BITFeedbackComposeViewControllerDelegate> delegate;
///-----------------------------------------------------------------------------
/// @name Presetting content
///-----------------------------------------------------------------------------
/**
An array of data objects that should be used to prefill the compose view content
The follwoing data object classes are currently supported:
- NSString
- NSURL
- UIImage
- NSData
- `BITHockeyAttachment`
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.
*/
- (void)prepareWithItems:(NSArray *)items;
@end
@@ -1,597 +0,0 @@
/*
* Author: Andreas Linde <mail@andreaslinde.de>
*
* 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 "HockeySDK.h"
#if HOCKEYSDK_FEATURE_FEEDBACK
#import "HockeySDKPrivate.h"
#import "BITFeedbackManagerPrivate.h"
#import "BITFeedbackMessageAttachment.h"
#import "BITFeedbackComposeViewController.h"
#import "BITFeedbackUserDataViewController.h"
#import "BITHockeyBaseManagerPrivate.h"
#import "BITHockeyHelper.h"
#import "BITImageAnnotationViewController.h"
#import "BITHockeyAttachment.h"
@interface BITFeedbackComposeViewController () <BITFeedbackUserDataDelegate, UIImagePickerControllerDelegate, UINavigationControllerDelegate, UIActionSheetDelegate, BITImageAnnotationDelegate> {
UIStatusBarStyle _statusBarStyle;
}
@property (nonatomic, weak) BITFeedbackManager *manager;
@property (nonatomic, strong) UITextView *textView;
@property (nonatomic, strong) UIView *contentViewContainer;
@property (nonatomic, strong) UIScrollView *attachmentScrollView;
@property (nonatomic, strong) NSMutableArray *attachmentScrollViewImageViews;
@property (nonatomic, strong) NSString *text;
@property (nonatomic, strong) NSMutableArray *attachments;
@property (nonatomic, strong) NSMutableArray *imageAttachments;
@property (nonatomic, strong) UIView *textAccessoryView;
@property (nonatomic) NSInteger selectedAttachmentIndex;
@property (nonatomic, strong) UITapGestureRecognizer *tapRecognizer;
@end
@implementation BITFeedbackComposeViewController {
BOOL _blockUserDataScreen;
BOOL _actionSheetVisible;
}
#pragma mark - NSObject
- (instancetype)init {
self = [super init];
if (self) {
_blockUserDataScreen = NO;
_actionSheetVisible = NO;
_delegate = nil;
_manager = [BITHockeyManager sharedHockeyManager].feedbackManager;
_attachments = [NSMutableArray new];
_imageAttachments = [NSMutableArray new];
_attachmentScrollViewImageViews = [NSMutableArray new];
_tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(scrollViewTapped:)];
[_attachmentScrollView addGestureRecognizer:self.tapRecognizer];
_text = nil;
}
return self;
}
#pragma mark - Public
- (void)prepareWithItems:(NSArray *)items {
for (id item in items) {
if ([item isKindOfClass:[NSString class]]) {
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;
BITFeedbackMessageAttachment *attachment = [BITFeedbackMessageAttachment attachmentWithData:UIImageJPEGRepresentation(image, 0.7f) contentType:@"image/jpeg"];
attachment.originalFilename = [NSString stringWithFormat:@"Image_%li.jpg", (unsigned long)[self.attachments count]];
[self.attachments addObject:attachment];
[self.imageAttachments addObject:attachment];
} else if ([item isKindOfClass:[NSData class]]) {
BITFeedbackMessageAttachment *attachment = [BITFeedbackMessageAttachment attachmentWithData:item contentType:@"application/octet-stream"];
attachment.originalFilename = [NSString stringWithFormat:@"Attachment_%li.data", (unsigned long)[self.attachments count]];
[self.attachments addObject:attachment];
} else if ([item isKindOfClass:[BITHockeyAttachment class]]) {
BITHockeyAttachment *sourceAttachment = (BITHockeyAttachment *)item;
if (!sourceAttachment.hockeyAttachmentData) {
BITHockeyLog(@"BITHockeyAttachment instance doesn't contain any data.");
continue;
}
NSString *filename = [NSString stringWithFormat:@"Attachment_%li.data", (unsigned long)[self.attachments count]];
if (sourceAttachment.filename) {
filename = sourceAttachment.filename;
}
BITFeedbackMessageAttachment *attachment = [BITFeedbackMessageAttachment attachmentWithData:sourceAttachment.hockeyAttachmentData contentType:sourceAttachment.contentType];
attachment.originalFilename = filename;
[self.attachments addObject:attachment];
} else {
BITHockeyLog(@"Unknown item type %@", item);
}
}
}
#pragma mark - Keyboard
- (void)keyboardWasShown:(NSNotification*)aNotification {
NSDictionary* info = [aNotification userInfo];
CGSize kbSize = [[info objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size;
BOOL isPortraitOrientation = NO;
#if __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_8_0
isPortraitOrientation = UIInterfaceOrientationIsPortrait([[UIApplication sharedApplication] statusBarOrientation]);
#else
isPortraitOrientation = UIInterfaceOrientationIsPortrait(self.interfaceOrientation);
#endif
CGRect frame = CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height);
if (UI_USER_INTERFACE_IDIOM() != UIUserInterfaceIdiomPad) {
if (!bit_isPreiOS8Environment() || isPortraitOrientation) {
frame.size.height -= kbSize.height;
} else {
frame.size.height -= kbSize.width;
}
} else {
CGSize windowSize = [[UIScreen mainScreen] bounds].size;
CGFloat windowHeight = windowSize.height - 20;
CGFloat navBarHeight = self.navigationController.navigationBar.frame.size.height;
if (!bit_isPreiOS8Environment() || isPortraitOrientation) {
CGFloat modalGap = (windowHeight - self.view.bounds.size.height) / 2;
frame.size.height = windowHeight - navBarHeight - kbSize.height;
if (bit_isPreiOS8Environment()) {
frame.size.height -= modalGap;
}
} else {
windowHeight = windowSize.width - 20;
CGFloat modalGap = 0.0f;
if (windowHeight - kbSize.width < self.view.bounds.size.height) {
modalGap = 30;
} else {
modalGap = (windowHeight - self.view.bounds.size.height) / 2;
}
frame.size.height = windowSize.width - navBarHeight - modalGap - kbSize.width;
}
}
[self.contentViewContainer setFrame:frame];
[self performSelector:@selector(refreshAttachmentScrollview) withObject:nil afterDelay:0.0f];
}
- (void)keyboardWillBeHidden:(NSNotification*)aNotification {
CGRect frame = CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height);
[self.contentViewContainer setFrame:frame];
}
#pragma mark - View lifecycle
- (void)viewDidLoad {
[super viewDidLoad];
self.title = BITHockeyLocalizedString(@"HockeyFeedbackComposeTitle");
self.view.backgroundColor = [UIColor whiteColor];
// Do any additional setup after loading the view.
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.contentViewContainer.autoresizingMask = UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight;
[self.view addSubview:self.contentViewContainer];
// message input textfield
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|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:UIButtonTypeCustom];
[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];
[self.textAccessoryView addSubview:addPhotoButton];
self.textView.inputAccessoryView = self.textAccessoryView;
// This could be a subclass, yet
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];
}
- (void)viewWillAppear:(BOOL)animated {
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(keyboardWasShown:)
name:UIKeyboardDidShowNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(keyboardWillBeHidden:)
name:UIKeyboardWillHideNotification object:nil];
self.manager.currentFeedbackComposeViewController = self;
[super viewWillAppear:animated];
_statusBarStyle = [[UIApplication sharedApplication] statusBarStyle];
#if __IPHONE_OS_VERSION_MAX_ALLOWED > __IPHONE_6_1
[[UIApplication sharedApplication] setStatusBarStyle:(self.navigationController.navigationBar.barStyle == UIBarStyleDefault) ? UIStatusBarStyleDefault : UIStatusBarStyleLightContent];
#else
[[UIApplication sharedApplication] setStatusBarStyle:(self.navigationController.navigationBar.barStyle == UIBarStyleDefault) ? UIStatusBarStyleDefault : UIStatusBarStyleBlackOpaque];
#endif
if (_text && self.textView.text.length == 0) {
self.textView.text = _text;
}
[self updateBarButtonState];
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
if ([self.manager askManualUserDataAvailable] &&
([self.manager requireManualUserDataMissing] ||
![self.manager didAskUserData])
) {
if (!_blockUserDataScreen)
[self setUserDataAction];
} else {
// 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 {
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardDidShowNotification object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillHideNotification object:nil];
self.manager.currentFeedbackComposeViewController = nil;
[super viewWillDisappear:animated];
[[UIApplication sharedApplication] setStatusBarStyle:_statusBarStyle];
}
- (void)viewDidDisappear:(BOOL)animated {
[super viewDidDisappear:animated];
}
- (void)refreshAttachmentScrollview {
CGFloat scrollViewWidth = 0;
if (self.imageAttachments.count){
scrollViewWidth = 100;
}
CGRect textViewFrame = self.textView.frame;
CGRect scrollViewFrame = self.attachmentScrollView.frame;
BOOL alreadySetup = CGRectGetWidth(scrollViewFrame) > 0;
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.imageAttachments.count > self.attachmentScrollViewImageViews.count){
NSInteger numberOfViewsToCreate = self.imageAttachments.count - self.attachmentScrollViewImageViews.count;
for (int i = 0; i <numberOfViewsToCreate; i++) {
UIButton *newImageButton = [UIButton buttonWithType:UIButtonTypeCustom];
[newImageButton addTarget:self action:@selector(imageButtonAction:) forControlEvents:UIControlEventTouchUpInside];
[self.attachmentScrollViewImageViews addObject:newImageButton];
[self.attachmentScrollView addSubview:newImageButton];
}
}
int index = 0;
CGFloat currentYOffset = 0.0f;
NSEnumerator *reverseAttachments = self.imageAttachments.reverseObjectEnumerator;
for (BITFeedbackMessageAttachment *attachment in reverseAttachments.allObjects){
UIButton *imageButton = self.attachmentScrollViewImageViews[index];
UIImage *image = [attachment thumbnailWithSize:CGSizeMake(100, 100)];
// determine the factor by which we scale..
CGFloat scaleFactor = CGRectGetWidth(self.attachmentScrollView.frame) / image.size.width;
CGFloat height = image.size.height * scaleFactor;
imageButton.frame = CGRectInset(CGRectMake(0, currentYOffset, scaleFactor * image.size.width, height), 10, 10);
currentYOffset += height;
[imageButton setImage:image forState:UIControlStateNormal];
index++;
}
[self.attachmentScrollView setContentSize:CGSizeMake(CGRectGetWidth(self.attachmentScrollView.frame), currentYOffset)];
[self updateBarButtonState];
}
- (void)updateBarButtonState {
if (self.textView.text.length > 0 ) {
self.navigationItem.rightBarButtonItem.enabled = YES;
} else {
self.navigationItem.rightBarButtonItem.enabled = NO;
}
if (self.imageAttachments.count > 2){
self.textView.inputAccessoryView = nil;
} else {
self.textView.inputAccessoryView = self.textAccessoryView;
}
}
#pragma mark - UIViewController Rotation
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)orientation {
return YES;
}
#pragma mark - Private methods
- (void)setUserDataAction {
BITFeedbackUserDataViewController *userController = [[BITFeedbackUserDataViewController alloc] initWithStyle:UITableViewStyleGrouped];
userController.delegate = self;
UINavigationController *navController = [self.manager customNavigationControllerWithRootViewController:userController
presentationStyle:UIModalPresentationFormSheet];
[self presentViewController:navController animated:YES completion:nil];
}
#pragma mark - Actions
- (void)dismissAction:(id)sender {
for (BITFeedbackMessageAttachment *attachment in self.attachments){
[attachment deleteContents];
}
[self dismissWithResult:BITFeedbackComposeResultCancelled];
}
- (void)sendAction:(id)sender {
if ([self.textView isFirstResponder])
[self.textView resignFirstResponder];
NSString *text = self.textView.text;
[self.manager submitMessageWithText:text andAttachments:self.attachments];
[self dismissWithResult:BITFeedbackComposeResultSubmitted];
}
- (void)dismissWithResult:(BITFeedbackComposeResult) result {
if(self.delegate && [self.delegate respondsToSelector:@selector(feedbackComposeViewController:didFinishWithResult:)]) {
[self.delegate feedbackComposeViewController:self didFinishWithResult:result];
} else if (self.delegate && [self.delegate respondsToSelector:@selector(feedbackComposeViewControllerDidFinish:)]) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated"
[self.delegate feedbackComposeViewControllerDidFinish:self];
#pragma clang diagnostic pop
} else {
[self dismissViewControllerAnimated:YES completion:nil];
}
}
- (void)addPhotoAction:(id)sender {
if (_actionSheetVisible) return;
// add photo.
UIImagePickerController *pickerController = [[UIImagePickerController alloc] init];
pickerController.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
pickerController.delegate = self;
pickerController.editing = NO;
[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 {
UIImage *pickedImage = info[UIImagePickerControllerOriginalImage];
if (pickedImage){
NSData *imageData = UIImageJPEGRepresentation(pickedImage, 0.7f);
BITFeedbackMessageAttachment *newAttachment = [BITFeedbackMessageAttachment attachmentWithData:imageData contentType:@"image/jpeg"];
NSURL *imagePath = [info objectForKey:@"UIImagePickerControllerReferenceURL"];
NSString *imageName = [imagePath lastPathComponent];
newAttachment.originalFilename = imageName;
[self.attachments addObject:newAttachment];
[self.imageAttachments addObject:newAttachment];
}
[picker dismissViewControllerAnimated:YES completion:nil];
}
- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker {
[picker dismissViewControllerAnimated:YES completion:nil];
}
- (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: BITHockeyLocalizedString(@"HockeyFeedbackComposeAttachmentCancel")
destructiveButtonTitle: BITHockeyLocalizedString(@"HockeyFeedbackComposeAttachmentDelete")
otherButtonTitles: BITHockeyLocalizedString(@"HockeyFeedbackComposeAttachmentEdit"), nil];
[actionSheet showFromRect: sender.frame inView: self.attachmentScrollView animated: YES];
_actionSheetVisible = YES;
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
[self.textView resignFirstResponder];
}
}
#pragma mark - BITFeedbackUserDataDelegate
- (void)userDataUpdateCancelled {
_blockUserDataScreen = YES;
if ([self.manager requireManualUserDataMissing]) {
if ([self.navigationController respondsToSelector:@selector(dismissViewControllerAnimated:completion:)]) {
[self.navigationController dismissViewControllerAnimated:YES
completion:^(void) {
[self dismissViewControllerAnimated:YES completion:nil];
}];
} else {
[self dismissViewControllerAnimated:YES completion:nil];
[self performSelector:@selector(dismissAction:) withObject:nil afterDelay:0.4];
}
} else {
[self.navigationController dismissViewControllerAnimated:YES completion:nil];
}
}
- (void)userDataUpdateFinished {
[self.manager saveMessages];
[self.navigationController dismissViewControllerAnimated:YES completion:nil];
}
#pragma mark - UITextViewDelegate
- (void)textViewDidChange:(UITextView *)textView {
[self updateBarButtonState];
}
#pragma mark - UIActionSheet Delegate
- (void)actionSheet:(UIActionSheet *)actionSheet didDismissWithButtonIndex:(NSInteger)buttonIndex {
if (buttonIndex == [actionSheet destructiveButtonIndex]) {
if (self.selectedAttachmentIndex != NSNotFound){
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;
[self refreshAttachmentScrollview];
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];
annotationEditor.delegate = self;
UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:annotationEditor];
annotationEditor.image = attachment.imageRepresentation;
[self presentViewController:navController animated:YES completion:nil];
}
} else {
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
[self.textView becomeFirstResponder];
}
}
_actionSheetVisible = NO;
}
#pragma mark - Image Annotation Delegate
- (void)annotationController:(BITImageAnnotationViewController *)annotationController didFinishWithImage:(UIImage *)image {
if (self.selectedAttachmentIndex != NSNotFound){
BITFeedbackMessageAttachment *attachment = self.imageAttachments[self.selectedAttachmentIndex];
[attachment replaceData:UIImageJPEGRepresentation(image, 0.7f)];
}
self.selectedAttachmentIndex = NSNotFound;
}
- (void)annotationControllerDidCancel:(BITImageAnnotationViewController *)annotationController {
self.selectedAttachmentIndex = NSNotFound;
}
@end
#endif /* HOCKEYSDK_FEATURE_FEEDBACK */
@@ -1,80 +0,0 @@
/*
* Author: Andreas Linde <mail@andreaslinde.de>
*
* 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 <Foundation/Foundation.h>
/**
* The users action when composing a message
*/
typedef NS_ENUM(NSUInteger, BITFeedbackComposeResult) {
/**
* user hit cancel
*/
BITFeedbackComposeResultCancelled,
/**
* user hit submit
*/
BITFeedbackComposeResultSubmitted,
};
@class BITFeedbackComposeViewController;
/**
* The `BITFeedbackComposeViewControllerDelegate` formal protocol defines methods further configuring
* the behaviour of `BITFeedbackComposeViewController`.
*/
@protocol BITFeedbackComposeViewControllerDelegate <NSObject>
@optional
///-----------------------------------------------------------------------------
/// @name View Controller Management
///-----------------------------------------------------------------------------
/**
* Invoked once the compose screen is finished via send or cancel
*
* If this is implemented, it's the responsibility of this method to dismiss the presented
* `BITFeedbackComposeViewController`
*
* @param composeViewController The `BITFeedbackComposeViewController` instance invoking this delegate
* @param composeResult The user action the lead to closing the compose view
*/
- (void)feedbackComposeViewController:(BITFeedbackComposeViewController *)composeViewController
didFinishWithResult:(BITFeedbackComposeResult) composeResult;
#pragma mark - Deprecated methods
/**
* This method is deprecated. If feedbackComposeViewController:didFinishWithResult: is implemented, this will not be called
*
* @param composeViewController The `BITFeedbackComposeViewController` instance invoking this delegate
*/
- (void)feedbackComposeViewControllerDidFinish:(BITFeedbackComposeViewController *)composeViewController __attribute__((deprecated("Use feedbackComposeViewController:didFinishWithResult: instead")));
@end
-88
View File
@@ -1,88 +0,0 @@
/*
* Author: Andreas Linde <mail@andreaslinde.de>
*
* 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>
#import "BITFeedbackMessage.h"
#import "BITAttributedLabel.h"
@class BITFeedbackMessageAttachment;
@protocol BITFeedbackListViewCellDelegate <NSObject>
- (void)listCell:(id)cell didSelectAttachment:(BITFeedbackMessageAttachment *)attachment;
@end
/**
* Cell style depending on the iOS version
*/
typedef NS_ENUM(NSUInteger, BITFeedbackListViewCellPresentatationStyle) {
/**
* Default is iOS 6 style
*/
BITFeedbackListViewCellPresentatationStyleDefault = 0,
/**
* Draw cells in the iOS 7 style
*/
BITFeedbackListViewCellPresentatationStyleOS7 = 1
};
/**
* Cell background style
*/
typedef NS_ENUM(NSUInteger, BITFeedbackListViewCellBackgroundStyle) {
/**
* For even rows
*/
BITFeedbackListViewCellBackgroundStyleNormal = 0,
/**
* For uneven rows
*/
BITFeedbackListViewCellBackgroundStyleAlternate = 1
};
@interface BITFeedbackListViewCell : UITableViewCell
@property (nonatomic, strong) BITFeedbackMessage *message;
@property (nonatomic) BITFeedbackListViewCellPresentatationStyle style;
@property (nonatomic) BITFeedbackListViewCellBackgroundStyle backgroundStyle;
@property (nonatomic, strong) BITAttributedLabel *labelText;
@property (nonatomic, weak) id<BITFeedbackListViewCellDelegate> delegate;
+ (CGFloat) heightForRowWithMessage:(BITFeedbackMessage *)message tableViewWidth:(CGFloat)width;
- (void)setAttachments:(NSArray *)attachments;
@end
-328
View File
@@ -1,328 +0,0 @@
/*
* Author: Andreas Linde <mail@andreaslinde.de>
*
* 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 "HockeySDKPrivate.h"
#import "BITFeedbackListViewCell.h"
#import "BITFeedbackMessageAttachment.h"
#import "BITActivityIndicatorButton.h"
#define BACKGROUNDCOLOR_DEFAULT BIT_RGBCOLOR(245, 245, 245)
#define BACKGROUNDCOLOR_ALTERNATE BIT_RGBCOLOR(235, 235, 235)
#define BACKGROUNDCOLOR_DEFAULT_OS7 BIT_RGBCOLOR(255, 255, 255)
#define BACKGROUNDCOLOR_ALTERNATE_OS7 BIT_RGBCOLOR(255, 255, 255)
#define TEXTCOLOR_TITLE BIT_RGBCOLOR(75, 75, 75)
#define TEXTCOLOR_DEFAULT BIT_RGBCOLOR(25, 25, 25)
#define TEXTCOLOR_PENDING BIT_RGBCOLOR(75, 75, 75)
#define TITLE_FONTSIZE 12
#define TEXT_FONTSIZE 15
#define FRAME_SIDE_BORDER 10
#define FRAME_TOP_BORDER 8
#define FRAME_BOTTOM_BORDER 5
#define FRAME_LEFT_RESPONSE_BORDER 20
#define LABEL_TITLE_Y 3
#define LABEL_TITLE_HEIGHT 15
#define LABEL_TEXT_Y 25
#define ATTACHMENT_SIZE 45
@interface BITFeedbackListViewCell ()
@property (nonatomic, strong) NSDateFormatter *dateFormatter;
@property (nonatomic, strong) NSDateFormatter *timeFormatter;
@property (nonatomic, strong) UILabel *labelTitle;
@property (nonatomic, strong) NSMutableArray *attachmentViews;
@property (nonatomic, strong) UIView *accessoryBackgroundView;
@end
@implementation BITFeedbackListViewCell
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
// Initialization code
_backgroundStyle = BITFeedbackListViewCellBackgroundStyleNormal;
_style = BITFeedbackListViewCellPresentatationStyleDefault;
_message = nil;
_dateFormatter = [[NSDateFormatter alloc] init];
[_dateFormatter setTimeStyle:NSDateFormatterNoStyle];
[_dateFormatter setDateStyle:NSDateFormatterMediumStyle];
[_dateFormatter setLocale:[NSLocale currentLocale]];
[_dateFormatter setDoesRelativeDateFormatting:YES];
_timeFormatter = [[NSDateFormatter alloc] init];
[_timeFormatter setTimeStyle:NSDateFormatterShortStyle];
[_timeFormatter setDateStyle:NSDateFormatterNoStyle];
[_timeFormatter setLocale:[NSLocale currentLocale]];
[_timeFormatter setDoesRelativeDateFormatting:YES];
_labelTitle = [[UILabel alloc] init];
_labelTitle.font = [UIFont systemFontOfSize:TITLE_FONTSIZE];
_labelText = [[BITAttributedLabel alloc] init];
_labelText.font = [UIFont systemFontOfSize:TEXT_FONTSIZE];
_labelText.numberOfLines = 0;
_labelText.textAlignment = kBITTextLabelAlignmentLeft;
_labelText.dataDetectorTypes = UIDataDetectorTypeAll;
_attachmentViews = [NSMutableArray new];
}
return self;
}
#pragma mark - Private
- (UIColor *)backgroundColor {
if (self.backgroundStyle == BITFeedbackListViewCellBackgroundStyleNormal) {
if (self.style == BITFeedbackListViewCellPresentatationStyleDefault) {
return BACKGROUNDCOLOR_DEFAULT;
} else {
return BACKGROUNDCOLOR_DEFAULT_OS7;
}
} else {
if (self.style == BITFeedbackListViewCellPresentatationStyleDefault) {
return BACKGROUNDCOLOR_ALTERNATE;
} else {
return BACKGROUNDCOLOR_ALTERNATE_OS7;
}
}
}
- (BOOL)isSameDayWithDate1:(NSDate*)date1 date2:(NSDate*)date2 {
NSCalendar* calendar = [NSCalendar currentCalendar];
#if __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_8_0
unsigned unitFlags = NSCalendarUnitYear | NSCalendarUnitMonth | NSCalendarUnitDay;
#else
unsigned unitFlags = NSYearCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit;
#endif
NSDateComponents *dateComponent1 = [calendar components:unitFlags fromDate:date1];
NSDateComponents *dateComponent2 = [calendar components:unitFlags fromDate:date2];
return ([dateComponent1 day] == [dateComponent2 day] &&
[dateComponent1 month] == [dateComponent2 month] &&
[dateComponent1 year] == [dateComponent2 year]);
}
#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 previewableAttachments].count / attachmentsPerRow);
return ceil(calculatedHeight);
}
+ (CGFloat) heightForTextInRowWithMessage:(BITFeedbackMessage *)message tableViewWidth:(CGFloat)width {
CGFloat calculatedHeight;
#if __IPHONE_OS_VERSION_MAX_ALLOWED > __IPHONE_6_1
if ([message.text respondsToSelector:@selector(boundingRectWithSize:options:attributes:context:)]) {
CGRect calculatedRect = [message.text boundingRectWithSize:CGSizeMake(width - (2 * FRAME_SIDE_BORDER), CGFLOAT_MAX)
options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading
attributes:@{NSFontAttributeName:[UIFont systemFontOfSize:TEXT_FONTSIZE]}
context:nil];
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
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
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
}
#endif
return ceil(calculatedHeight);
}
- (void)setAttachments:(NSArray *)attachments {
for (UIView *view in self.attachmentViews){
[view removeFromSuperview];
}
[self.attachmentViews removeAllObjects];
for (BITFeedbackMessageAttachment *attachment in attachments){
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];
}
}
}
- (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];
}
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];
self.labelTitle.textColor = TEXTCOLOR_TITLE;
if (_message.status == BITFeedbackMessageStatusSendPending || _message.status == BITFeedbackMessageStatusSendInProgress) {
[self.labelText setTextColor:TEXTCOLOR_PENDING];
} else {
[self.labelText setTextColor:TEXTCOLOR_DEFAULT];
}
// background for deletion accessory view
// header
NSString *dateString = @"";
if (_message.status == BITFeedbackMessageStatusSendPending || _message.status == BITFeedbackMessageStatusSendInProgress) {
dateString = BITHockeyLocalizedString(@"Pending");
} else if (_message.date) {
if ([self isSameDayWithDate1:[NSDate date] date2:_message.date]) {
dateString = [self.timeFormatter stringFromDate:_message.date];
} else {
dateString = [self.dateFormatter stringFromDate:_message.date];
}
}
[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;
} else {
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.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 = floorf(self.frame.size.width / (FRAME_SIDE_BORDER + ATTACHMENT_SIZE));
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 {
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) {
[self insertSubview:imageButton aboveSubview:self.accessoryBackgroundView];
} else {
[self addSubview:imageButton];
}
}
i++;
}
[super layoutSubviews];
}
- (void)imageButtonPressed:(id)sender {
if ([self.delegate respondsToSelector:@selector(listCell:didSelectAttachment:)]) {
NSInteger index = [self.attachmentViews indexOfObject:sender];
if (index != NSNotFound && [self.message previewableAttachments].count > index) {
BITFeedbackMessageAttachment *attachment = [self.message previewableAttachments][index];
[self.delegate listCell:self didSelectAttachment:attachment];
}
}
}
@end
@@ -1,61 +0,0 @@
/*
* Author: Andreas Linde <mail@andreaslinde.de>
*
* 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>
#import <QuickLook/QuickLook.h>
#import "BITHockeyBaseViewController.h"
/**
View controller providing a default interface to manage feedback
The message list interface contains options to locally delete single messages
by swiping over them, or deleting all messages. This will not delete the messages
on the server though!
It is also integrates actions to invoke the user interface to compose a new messages,
reload the list content from the server and changing the users name or email if these
are allowed to be set.
To add this view controller to your own app and push it onto a navigation stack,
don't create the intance yourself, but use the following code to get a correct instance:
[[BITHockeyManager sharedHockeyManager].feedbackManager feedbackListViewController:NO]
To show it modally, use the following code instead:
[[BITHockeyManager sharedHockeyManager].feedbackManager feedbackListViewController:YES]
This ensures that the presentation on iOS 6 and iOS 7 will use the corret design on each OS Version.
*/
@interface BITFeedbackListViewController : BITHockeyBaseViewController <UITableViewDelegate, UITableViewDataSource, UIActionSheetDelegate, UIAlertViewDelegate, QLPreviewControllerDataSource> {
}
@end
-873
View File
@@ -1,873 +0,0 @@
/*
* Author: Andreas Linde <mail@andreaslinde.de>
*
* 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 "HockeySDK.h"
#if HOCKEYSDK_FEATURE_FEEDBACK
#import "HockeySDKPrivate.h"
#import "BITFeedbackManagerPrivate.h"
#import "BITFeedbackManager.h"
#import "BITFeedbackListViewController.h"
#import "BITFeedbackListViewCell.h"
#import "BITFeedbackComposeViewController.h"
#import "BITFeedbackUserDataViewController.h"
#import "BITFeedbackMessage.h"
#import "BITFeedbackMessageAttachment.h"
#import "BITAttributedLabel.h"
#import "BITHockeyBaseManagerPrivate.h"
#import "BITHockeyHelper.h"
#import <QuartzCore/QuartzCore.h>
#import <QuickLook/QuickLook.h>
#define DEFAULT_BACKGROUNDCOLOR BIT_RGBCOLOR(245, 245, 245)
#define DEFAULT_BACKGROUNDCOLOR_OS7 BIT_RGBCOLOR(255, 255, 255)
#define DEFAULT_TEXTCOLOR BIT_RGBCOLOR(75, 75, 75)
#define BUTTON_BORDERCOLOR BIT_RGBCOLOR(175, 175, 175)
#define BUTTON_BACKGROUNDCOLOR BIT_RGBCOLOR(225, 225, 225)
#define BUTTON_TEXTCOLOR BIT_RGBCOLOR(58, 58, 58)
#define BUTTON_TEXTCOLOR_SHADOW BIT_RGBCOLOR(255, 255, 255)
#define BUTTON_DELETE_BORDERCOLOR BIT_RGBCOLOR(61, 61, 61)
#define BUTTON_DELETE_BACKGROUNDCOLOR BIT_RGBCOLOR(225, 0, 0)
#define BUTTON_DELETE_TEXTCOLOR BIT_RGBCOLOR(240, 240, 240)
#define BUTTON_DELETE_TEXTCOLOR_SHADOW BIT_RGBCOLOR(125, 0, 0)
#define BORDER_COLOR BIT_RGBCOLOR(215, 215, 215)
@interface BITFeedbackListViewController () <BITFeedbackUserDataDelegate, BITFeedbackComposeViewControllerDelegate, BITAttributedLabelDelegate, BITFeedbackListViewCellDelegate>
@property (nonatomic, weak) BITFeedbackManager *manager;
@property (nonatomic, strong) NSDateFormatter *lastUpdateDateFormatter;
@property (nonatomic) BOOL userDataComposeFlow;
@property (nonatomic, strong) NSArray *cachedPreviewItems;
@property (nonatomic, strong) NSOperationQueue *thumbnailQueue;
@end
@implementation BITFeedbackListViewController {
NSInteger _deleteButtonSection;
NSInteger _userButtonSection;
}
- (instancetype)initWithStyle:(UITableViewStyle)style {
if ((self = [super initWithStyle:style])) {
_manager = [BITHockeyManager sharedHockeyManager].feedbackManager;
_deleteButtonSection = -1;
_userButtonSection = -1;
_userDataComposeFlow = NO;
_lastUpdateDateFormatter = [[NSDateFormatter alloc] init];
[_lastUpdateDateFormatter setDateStyle:NSDateFormatterShortStyle];
[_lastUpdateDateFormatter setTimeStyle:NSDateFormatterShortStyle];
_lastUpdateDateFormatter.locale = [NSLocale currentLocale];
_thumbnailQueue = [NSOperationQueue new];
}
return self;
}
- (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];
}
#pragma mark - View lifecycle
- (void)viewDidLoad {
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(startLoadingIndicator)
name:BITHockeyFeedbackMessagesLoadingStarted
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(updateList)
name:BITHockeyFeedbackMessagesLoadingFinished
object:nil];
self.title = BITHockeyLocalizedString(@"HockeyFeedbackListTitle");
self.tableView.delegate = self;
self.tableView.dataSource = self;
self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
[self.tableView setAutoresizingMask:UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth];
if ([self.manager isPreiOS7Environment]) {
[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]];
}
if ([self.manager isPreiOS7Environment]) {
self.view.backgroundColor = DEFAULT_BACKGROUNDCOLOR;
} else {
// self.view.backgroundColor = DEFAULT_BACKGROUNDCOLOR_OS7;
}
id refreshClass = NSClassFromString(@"UIRefreshControl");
if (refreshClass) {
self.refreshControl = [[UIRefreshControl alloc] init];
[self.refreshControl addTarget:self action:@selector(reloadList) forControlEvents:UIControlEventValueChanged];
} else {
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemRefresh
target:self
action:@selector(reloadList)];
}
}
- (void)startLoadingIndicator {
id refreshClass = NSClassFromString(@"UIRefreshControl");
if (refreshClass) {
[self.refreshControl beginRefreshing];
} else {
self.navigationItem.rightBarButtonItem.enabled = NO;
}
}
- (void)stopLoadingIndicator {
id refreshClass = NSClassFromString(@"UIRefreshControl");
if (refreshClass) {
[self.refreshControl endRefreshing];
} else {
self.navigationItem.rightBarButtonItem.enabled = YES;
}
}
- (BOOL)isRefreshingWithNewControl {
id refreshClass = NSClassFromString(@"UIRefreshControl");
if (refreshClass) {
return [self.refreshControl isRefreshing];
}
return NO;
}
- (void)reloadList {
[self startLoadingIndicator];
[self.manager updateMessagesList];
}
- (void)updateList {
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 &&
![self isRefreshingWithNewControl])
[self.tableView setContentOffset:CGPointMake(contentOffset.x, self.tableView.contentSize.height - contentSize.height + contentOffset.y) animated:NO];
[self stopLoadingIndicator];
[self.tableView flashScrollIndicators];
}
- (void)viewDidAppear:(BOOL)animated {
if (self.userDataComposeFlow) {
self.userDataComposeFlow = NO;
}
self.manager.currentFeedbackListViewController = self;
[self.manager updateMessagesListIfRequired];
if ([self.manager numberOfMessages] == 0 &&
[self.manager askManualUserDataAvailable] &&
[self.manager requireManualUserDataMissing] &&
![self.manager didAskUserData]
) {
self.userDataComposeFlow = YES;
if ([self.manager showFirstRequiredPresentationModal]) {
[self setUserDataAction:nil];
} else {
// In case of presenting the feedback in a UIPopoverController it appears
// that the animation is not yet finished (though it should) and pushing
// the user data view on top of the navigation stack right away will
// cause the following warning to appear in the console:
// "nested push animation can result in corrupted navigation bar"
[self performSelector:@selector(showDelayedUserDataViewController) withObject:nil afterDelay:0.0];
}
} else {
[self.tableView reloadData];
}
[super viewDidAppear:animated];
}
- (void)viewWillDisappear:(BOOL)animated {
self.manager.currentFeedbackListViewController = nil;
[super viewWillDisappear:animated];
}
#pragma mark - Private methods
- (void)showDelayedUserDataViewController {
BITFeedbackUserDataViewController *userController = [[BITFeedbackUserDataViewController alloc] initWithStyle:UITableViewStyleGrouped];
userController.delegate = self;
[self.navigationController pushViewController:userController animated:YES];
}
- (void)setUserDataAction:(id)sender {
BITFeedbackUserDataViewController *userController = [[BITFeedbackUserDataViewController alloc] initWithStyle:UITableViewStyleGrouped];
userController.delegate = self;
UINavigationController *navController = [self.manager customNavigationControllerWithRootViewController:userController
presentationStyle:UIModalPresentationFormSheet];
[self presentViewController:navController animated:YES completion:nil];
}
- (void)newFeedbackAction:(id)sender {
BITFeedbackComposeViewController *composeController = [self.manager feedbackComposeViewController];
UINavigationController *navController = [self.manager customNavigationControllerWithRootViewController:composeController
presentationStyle:UIModalPresentationFormSheet];
[self presentViewController:navController animated:YES completion:nil];
}
- (void)deleteAllMessages {
[_manager deleteAllMessages];
[self refreshPreviewItems];
[self.tableView reloadData];
}
- (void)deleteAllMessagesAction:(id)sender {
if (UI_USER_INTERFACE_IDIOM() != UIUserInterfaceIdiomPad) {
UIActionSheet *deleteAction = [[UIActionSheet alloc] initWithTitle:BITHockeyLocalizedString(@"HockeyFeedbackListDeleteAllTitle")
delegate:self
cancelButtonTitle:BITHockeyLocalizedString(@"HockeyFeedbackListDeleteAllCancel")
destructiveButtonTitle:BITHockeyLocalizedString(@"HockeyFeedbackListDeleteAllDelete")
otherButtonTitles:nil
];
[deleteAction setTag:0];
[deleteAction setActionSheetStyle:UIActionSheetStyleBlackTranslucent];
[deleteAction showInView:[self viewForShowingActionSheetOnPhone]];
} else {
UIAlertView *deleteAction = [[UIAlertView alloc] initWithTitle:BITHockeyLocalizedString(@"HockeyFeedbackListButonDeleteAllMessages")
message:BITHockeyLocalizedString(@"HockeyFeedbackListDeleteAllTitle")
delegate:self
cancelButtonTitle:BITHockeyLocalizedString(@"HockeyFeedbackListDeleteAllCancel")
otherButtonTitles:BITHockeyLocalizedString(@"HockeyFeedbackListDeleteAllDelete"), nil];
[deleteAction setTag:0];
[deleteAction show];
}
}
- (UIView*) viewForShowingActionSheetOnPhone {
//find the topmost presented viewcontroller
//and use its view
UIViewController* topMostPresentedViewController = self.view.window.rootViewController;
while(topMostPresentedViewController.presentedViewController) {
topMostPresentedViewController = topMostPresentedViewController.presentedViewController;
}
UIView* view = topMostPresentedViewController.view;
if(nil == view) {
//hope for the best. Should work
//on simple view(controller) hierarchies
view = self.view;
}
return view;
}
#pragma mark - BITFeedbackUserDataDelegate
-(void)userDataUpdateCancelled {
if (self.userDataComposeFlow) {
if ([self.manager showFirstRequiredPresentationModal]) {
__weak typeof(self) weakSelf = self;
[self dismissViewControllerAnimated:YES completion:^(void){
typeof(self) strongSelf = weakSelf;
[strongSelf.tableView reloadData];
}];
} else {
[self.navigationController popToViewController:self animated:YES];
}
} else {
[self dismissViewControllerAnimated:YES completion:^(void){}];
}
}
-(void)userDataUpdateFinished {
[self.manager saveMessages];
[self refreshPreviewItems];
if (self.userDataComposeFlow) {
if ([self.manager showFirstRequiredPresentationModal]) {
__weak typeof(self) weakSelf = self;
[self dismissViewControllerAnimated:YES completion:^(void){
typeof(self) strongSelf = weakSelf;
[strongSelf newFeedbackAction:nil];
}];
} else {
BITFeedbackComposeViewController *composeController = [[BITFeedbackComposeViewController alloc] init];
composeController.delegate = self;
[self.navigationController pushViewController:composeController animated:YES];
}
} else {
[self dismissViewControllerAnimated:YES completion:^(void){}];
}
}
#pragma mark - BITFeedbackComposeViewControllerDelegate
- (void)feedbackComposeViewController:(BITFeedbackComposeViewController *)composeViewController
didFinishWithResult:(BITFeedbackComposeResult)composeResult {
if (self.userDataComposeFlow) {
if ([self.manager showFirstRequiredPresentationModal]) {
__weak typeof(self) weakSelf = self;
[self dismissViewControllerAnimated:YES completion:^(void){
typeof(self) strongSelf = weakSelf;
[strongSelf.tableView reloadData];
}];
} else {
[self.navigationController popToViewController:self animated:YES];
}
} else {
[self dismissViewControllerAnimated:YES completion:^(void){}];
}
if (self.manager.delegate &&
[self.manager.delegate respondsToSelector:@selector(feedbackComposeViewController:didFinishWithResult:)]) {
[self.manager.delegate feedbackComposeViewController:composeViewController didFinishWithResult:composeResult];
} else if (self.manager.delegate && [self.manager.delegate respondsToSelector:@selector(feedbackComposeViewControllerDidFinish:)]) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated"
[self.manager.delegate feedbackComposeViewControllerDidFinish:composeViewController];
#pragma clang diagnostic pop
}
}
#pragma mark - UIViewController Rotation
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
[self.tableView beginUpdates];
[self.tableView endUpdates];
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)orientation {
return YES;
}
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
NSInteger sections = 2;
_deleteButtonSection = -1;
_userButtonSection = -1;
if ([self.manager isManualUserDataAvailable] || [self.manager didAskUserData]) {
_userButtonSection = sections;
sections++;
}
if ([self.manager numberOfMessages] > 0) {
_deleteButtonSection = sections;
sections++;
}
return sections;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
if (section == 1) {
return [self.manager numberOfMessages];
} else {
return 1;
}
}
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section {
if (![self.manager isPreiOS7Environment]) {
if (section == 0) {
return 30;
}
}
return [super tableView:tableView heightForHeaderInSection:section];
}
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
if (![self.manager isPreiOS7Environment] && section == 0) {
UIView *containerView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, 30.0f)];
UILabel *textLabel = [[UILabel alloc] initWithFrame:CGRectMake(16.0f, 5.0f, self.view.frame.size.width - 32.0f, 25.0f)];
textLabel.text = [NSString stringWithFormat:BITHockeyLocalizedString(@"HockeyFeedbackListLastUpdated"),
[self.manager lastCheck] ? [self.lastUpdateDateFormatter stringFromDate:[self.manager lastCheck]] : BITHockeyLocalizedString(@"HockeyFeedbackListNeverUpdated")];
textLabel.font = [UIFont systemFontOfSize:10];
textLabel.textColor = DEFAULT_TEXTCOLOR;
[containerView addSubview:textLabel];
return containerView;
}
return [super tableView:tableView viewForHeaderInSection:section];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = @"MessageCell";
static NSString *LastUpdateIdentifier = @"LastUpdateCell";
static NSString *ButtonTopIdentifier = @"ButtonTopCell";
static NSString *ButtonBottomIdentifier = @"ButtonBottomCell";
static NSString *ButtonDeleteIdentifier = @"ButtonDeleteCell";
if (indexPath.section == 0 && indexPath.row == 1 && ![self.manager isPreiOS7Environment]) {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:LastUpdateIdentifier];
if (!cell) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:LastUpdateIdentifier];
cell.textLabel.font = [UIFont systemFontOfSize:10];
cell.textLabel.textColor = DEFAULT_TEXTCOLOR;
cell.accessoryType = UITableViewCellAccessoryNone;
cell.selectionStyle = UITableViewCellSelectionStyleNone;
cell.textLabel.textAlignment = kBITTextLabelAlignmentCenter;
}
cell.textLabel.text = [NSString stringWithFormat:BITHockeyLocalizedString(@"HockeyFeedbackListLastUpdated"),
[self.manager lastCheck] ? [self.lastUpdateDateFormatter stringFromDate:[self.manager lastCheck]] : BITHockeyLocalizedString(@"HockeyFeedbackListNeverUpdated")];
return cell;
} else if (indexPath.section == 0 || indexPath.section >= 2) {
CGFloat topGap = 0.0f;
UITableViewCell *cell = nil;
NSString *identifier = nil;
if (indexPath.section == 0) {
identifier = ButtonTopIdentifier;
} else if (indexPath.section == _userButtonSection) {
identifier = ButtonBottomIdentifier;
} else {
identifier = ButtonDeleteIdentifier;
}
cell = [tableView dequeueReusableCellWithIdentifier:identifier];
if (!cell) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier];
cell.textLabel.font = [UIFont systemFontOfSize:14];
cell.textLabel.numberOfLines = 0;
cell.accessoryType = UITableViewCellAccessoryNone;
if ([self.manager isPreiOS7Environment]) {
cell.selectionStyle = UITableViewCellSelectionStyleNone;
} else {
cell.selectionStyle = UITableViewCellSelectionStyleGray;
}
}
// button
NSString *titleString = nil;
SEL actionSelector = nil;
UIColor *titleColor = BIT_RGBCOLOR(35, 111, 251);
UIButton *button = nil;
if ([self.manager isPreiOS7Environment]) {
button = [UIButton buttonWithType:UIButtonTypeCustom];
button.autoresizingMask = UIViewAutoresizingFlexibleWidth;
UIImage *stretchableButton = [bit_imageNamed(@"buttonRoundedRegular.png", BITHOCKEYSDK_BUNDLE) stretchableImageWithLeftCapWidth:10 topCapHeight:0];
UIImage *stretchableHighlightedButton = [bit_imageNamed(@"buttonRoundedRegularHighlighted.png", BITHOCKEYSDK_BUNDLE) stretchableImageWithLeftCapWidth:10 topCapHeight:0];
[button setBackgroundImage:stretchableButton forState:UIControlStateNormal];
[button setBackgroundImage:stretchableHighlightedButton forState:UIControlStateHighlighted];
[[button titleLabel] setShadowOffset:CGSizeMake(0, 1)];
[[button titleLabel] setFont:[UIFont boldSystemFontOfSize:14.0]];
[button setTitleColor:BUTTON_TEXTCOLOR forState:UIControlStateNormal];
[button setTitleShadowColor:BUTTON_TEXTCOLOR_SHADOW forState:UIControlStateNormal];
}
if (indexPath.section == 0) {
topGap = 22;
if ([self.manager numberOfMessages] == 0) {
titleString = BITHockeyLocalizedString(@"HockeyFeedbackListButonWriteFeedback");
} else {
titleString = BITHockeyLocalizedString(@"HockeyFeedbackListButonWriteResponse");
}
actionSelector = @selector(newFeedbackAction:);
} else if (indexPath.section == _userButtonSection) {
topGap = 6.0f;
if ([self.manager requireUserName] == BITFeedbackUserDataElementRequired ||
([self.manager requireUserName] == BITFeedbackUserDataElementOptional && [self.manager userName] != nil)
) {
titleString = [NSString stringWithFormat:BITHockeyLocalizedString(@"HockeyFeedbackListButonUserDataWithName"), [self.manager userName] ?: @"-"];
} else if ([self.manager requireUserEmail] == BITFeedbackUserDataElementRequired ||
([self.manager requireUserEmail] == BITFeedbackUserDataElementOptional && [self.manager userEmail] != nil)
) {
titleString = [NSString stringWithFormat:BITHockeyLocalizedString(@"HockeyFeedbackListButonUserDataWithEmail"), [self.manager userEmail] ?: @"-"];
} else if ([self.manager requireUserName] == BITFeedbackUserDataElementOptional) {
titleString = BITHockeyLocalizedString(@"HockeyFeedbackListButonUserDataSetName");
} else {
titleString = BITHockeyLocalizedString(@"HockeyFeedbackListButonUserDataSetEmail");
}
actionSelector = @selector(setUserDataAction:);
} else {
topGap = 0.0f;
if ([self.manager isPreiOS7Environment]) {
[[button titleLabel] setShadowOffset:CGSizeMake(0, -1)];
UIImage *stretchableDeleteButton = [bit_imageNamed(@"buttonRoundedDelete.png", BITHOCKEYSDK_BUNDLE) stretchableImageWithLeftCapWidth:10 topCapHeight:0];
UIImage *stretchableDeleteHighlightedButton = [bit_imageNamed(@"buttonRoundedDeleteHighlighted.png", BITHOCKEYSDK_BUNDLE) stretchableImageWithLeftCapWidth:10 topCapHeight:0];
[button setBackgroundImage:stretchableDeleteButton forState:UIControlStateNormal];
[button setBackgroundImage:stretchableDeleteHighlightedButton forState:UIControlStateHighlighted];
[button setTitleColor:BUTTON_DELETE_TEXTCOLOR forState:UIControlStateNormal];
[button setTitleShadowColor:BUTTON_DELETE_TEXTCOLOR_SHADOW forState:UIControlStateNormal];
}
titleString = BITHockeyLocalizedString(@"HockeyFeedbackListButonDeleteAllMessages");
titleColor = BIT_RGBCOLOR(251, 35, 35);
actionSelector = @selector(deleteAllMessagesAction:);
}
if ([self.manager isPreiOS7Environment]) {
if (titleString)
[button setTitle:titleString forState:UIControlStateNormal];
if (actionSelector)
[button addTarget:self action:actionSelector forControlEvents:UIControlEventTouchUpInside];
[button setFrame: CGRectMake( 10.0f, topGap + 12.0f, cell.frame.size.width - 20.0f, 42.0f)];
[cell addSubview:button];
} else {
cell.textLabel.text = titleString;
cell.textLabel.textColor = titleColor;
}
if ([self.manager isPreiOS7Environment]) {
// status label or shadow lines
if (indexPath.section == 0) {
UILabel *statusLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 6, cell.frame.size.width, 28)];
statusLabel.font = [UIFont systemFontOfSize:10];
statusLabel.textColor = DEFAULT_TEXTCOLOR;
statusLabel.textAlignment = kBITTextLabelAlignmentCenter;
if ([self.manager isPreiOS7Environment]) {
statusLabel.backgroundColor = DEFAULT_BACKGROUNDCOLOR;
} else {
statusLabel.backgroundColor = DEFAULT_BACKGROUNDCOLOR_OS7;
}
statusLabel.autoresizingMask = UIViewAutoresizingFlexibleWidth;
statusLabel.text = [NSString stringWithFormat:BITHockeyLocalizedString(@"HockeyFeedbackListLastUpdated"),
[self.manager lastCheck] ? [self.lastUpdateDateFormatter stringFromDate:[self.manager lastCheck]] : BITHockeyLocalizedString(@"HockeyFeedbackListNeverUpdated")];
[cell addSubview:statusLabel];
} else if (indexPath.section == 2) {
UIView *lineView1 = [[UIView alloc] initWithFrame:CGRectMake(0, 0, cell.frame.size.width, 1)];
lineView1.backgroundColor = BORDER_COLOR;
lineView1.autoresizingMask = UIViewAutoresizingFlexibleWidth;
[cell addSubview:lineView1];
}
}
return cell;
} else {
BITFeedbackListViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (!cell) {
cell = [[BITFeedbackListViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
cell.accessoryType = UITableViewCellAccessoryNone;
cell.selectionStyle = UITableViewCellSelectionStyleNone;
}
if (indexPath.row == 0 || indexPath.row % 2 == 0) {
cell.backgroundStyle = BITFeedbackListViewCellBackgroundStyleAlternate;
} else {
cell.backgroundStyle = BITFeedbackListViewCellBackgroundStyleNormal;
}
if ([self.manager isPreiOS7Environment]) {
cell.style = BITFeedbackListViewCellPresentatationStyleDefault;
} else {
cell.style = BITFeedbackListViewCellPresentatationStyleOS7;
}
BITFeedbackMessage *message = [self.manager messageAtIndex:indexPath.row];
cell.message = message;
cell.labelText.delegate = self;
cell.labelText.userInteractionEnabled = YES;
cell.delegate = self;
[cell setAttachments:message.previewableAttachments];
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) {
dispatch_async(dispatch_get_main_queue(), ^{
[attachment replaceData:responseData];
[self.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone];
[[BITHockeyManager sharedHockeyManager].feedbackManager saveMessages];
});
}
}];
}
}
if (
[self.manager isPreiOS7Environment] ||
(![self.manager isPreiOS7Environment] && indexPath.row != 0)
) {
UIView *lineView1 = [[UIView alloc] initWithFrame:CGRectMake(0, 0, cell.frame.size.width, 1)];
lineView1.backgroundColor = BORDER_COLOR;
lineView1.autoresizingMask = UIViewAutoresizingFlexibleWidth;
[cell addSubview:lineView1];
}
return cell;
}
}
- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath {
if (indexPath.section == 1)
return YES;
return NO;
}
- (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];
}
}
}
}
#pragma mark - Table view delegate
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
if (indexPath.section == 0 ) {
if ([self.manager isPreiOS7Environment])
return 87;
else
return 44;
}
if (indexPath.section >= 2) {
if ([self.manager isPreiOS7Environment])
return 65;
else
return 44;
}
BITFeedbackMessage *message = [self.manager messageAtIndex:indexPath.row];
if (!message) return 44;
return [BITFeedbackListViewCell heightForRowWithMessage:message tableViewWidth:self.view.frame.size.width];
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
if (![self.manager isPreiOS7Environment]) {
if (indexPath.section == 0) {
[self newFeedbackAction:self];
} else if (indexPath.section == _userButtonSection) {
[self setUserDataAction:self];
} else if (indexPath.section == _deleteButtonSection) {
[self deleteAllMessagesAction:self];
}
}
}
#pragma mark - BITAttributedLabelDelegate
- (void)attributedLabel:(BITAttributedLabel *)label didSelectLinkWithURL:(NSURL *)url {
if (UI_USER_INTERFACE_IDIOM() != UIUserInterfaceIdiomPad) {
UIActionSheet *linkAction = [[UIActionSheet alloc] initWithTitle:[url absoluteString]
delegate:self
cancelButtonTitle:BITHockeyLocalizedString(@"HockeyFeedbackListLinkActionCancel")
destructiveButtonTitle:nil
otherButtonTitles:BITHockeyLocalizedString(@"HockeyFeedbackListLinkActionOpen"), BITHockeyLocalizedString(@"HockeyFeedbackListLinkActionCopy"), nil
];
[linkAction setTag:1];
[linkAction setActionSheetStyle:UIActionSheetStyleBlackTranslucent];
[linkAction showInView:[self viewForShowingActionSheetOnPhone]];
} else {
UIAlertView *linkAction = [[UIAlertView alloc] initWithTitle:[url absoluteString]
message:nil
delegate:self
cancelButtonTitle:BITHockeyLocalizedString(@"HockeyFeedbackListLinkActionCancel")
otherButtonTitles:BITHockeyLocalizedString(@"HockeyFeedbackListLinkActionOpen"), BITHockeyLocalizedString(@"HockeyFeedbackListLinkActionCopy"), nil
];
[linkAction setTag:1];
[linkAction show];
}
}
#pragma mark - UIAlertViewDelegate
- (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex {
if (buttonIndex == alertView.cancelButtonIndex) {
return;
}
if ([alertView tag] == 0) {
if (buttonIndex == [alertView firstOtherButtonIndex]) {
[self deleteAllMessages];
}
} else {
if (buttonIndex == [alertView firstOtherButtonIndex]) {
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:alertView.title]];
} else {
UIPasteboard *pasteboard = [UIPasteboard generalPasteboard];
pasteboard.URL = [NSURL URLWithString:alertView.title];
}
}
}
#pragma mark - UIActionSheetDelegate
- (void)actionSheet:(UIActionSheet *)actionSheet didDismissWithButtonIndex:(NSInteger)buttonIndex {
if (buttonIndex == actionSheet.cancelButtonIndex) {
return;
}
if ([actionSheet tag] == 0) {
if (buttonIndex == [actionSheet destructiveButtonIndex]) {
[self deleteAllMessages];
}
} else {
if (buttonIndex == [actionSheet firstOtherButtonIndex]) {
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:actionSheet.title]];
} else {
UIPasteboard *pasteboard = [UIPasteboard generalPasteboard];
pasteboard.URL = [NSURL URLWithString:actionSheet.title];
}
}
}
#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]) {
[previewController setCurrentPreviewItemIndex:[self.cachedPreviewItems indexOfObject:attachment]];
}
}
- (void)refreshPreviewItems {
self.cachedPreviewItems = nil;
NSMutableArray *collectedAttachments = [NSMutableArray new];
for (int i = 0; i < self.manager.numberOfMessages; i++) {
BITFeedbackMessage *message = [self.manager messageAtIndex:i];
[collectedAttachments addObjectsFromArray:message.previewableAttachments];
}
self.cachedPreviewItems = collectedAttachments;
}
- (NSInteger)numberOfPreviewItemsInPreviewController:(QLPreviewController *)controller {
if (!self.cachedPreviewItems){
[self refreshPreviewItems];
}
return self.cachedPreviewItems.count;
}
- (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) {
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];
[blockController reloadData];
[[BITHockeyManager sharedHockeyManager].feedbackManager saveMessages];
} else {
[blockController reloadData];
}
}];
return attachment;
} else {
return self.cachedPreviewItems[index];
}
}
return nil;
}
@end
#endif /* HOCKEYSDK_FEATURE_FEEDBACK */
-327
View File
@@ -1,327 +0,0 @@
/*
* Author: Andreas Linde <mail@andreaslinde.de>
*
* 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 <Foundation/Foundation.h>
#import "BITHockeyBaseManager.h"
#import "BITFeedbackListViewController.h"
#import "BITFeedbackComposeViewController.h"
// Notification message which tells that loading messages finished
#define BITHockeyFeedbackMessagesLoadingStarted @"BITHockeyFeedbackMessagesLoadingStarted"
// Notification message which tells that loading messages finished
#define BITHockeyFeedbackMessagesLoadingFinished @"BITHockeyFeedbackMessagesLoadingFinished"
/**
* Defines behavior of the user data field
*/
typedef NS_ENUM(NSInteger, BITFeedbackUserDataElement) {
/**
* don't ask for this user data element at all
*/
BITFeedbackUserDataElementDontShow = 0,
/**
* the user may provide it, but does not have to
*/
BITFeedbackUserDataElementOptional = 1,
/**
* the user has to provide this to continue
*/
BITFeedbackUserDataElementRequired = 2
};
/**
* Available modes for opening the feedback compose interface with a screenshot attached
*/
typedef NS_ENUM(NSInteger, BITFeedbackObservationMode) {
/**
* No SDK provided trigger is active.
*/
BITFeedbackObservationNone = 0,
/**
* Triggers when the user takes a screenshot. This will grab the latest image from the camera roll. Requires iOS 7 or later!
*/
BITFeedbackObservationModeOnScreenshot = 1,
/**
* Triggers when the user taps with three fingers on the screen.
*/
BITFeedbackObservationModeThreeFingerTap = 2
};
@class BITFeedbackMessage;
@protocol BITFeedbackManagerDelegate;
/**
The feedback module.
This is the HockeySDK module for letting your users communicate directly with you via
the app and an integrated user interface. It provides a single threaded
discussion with a user running your app.
You should never create your own instance of `BITFeedbackManager` but use the one provided
by the `[BITHockeyManager sharedHockeyManager]`:
[BITHockeyManager sharedHockeyManager].feedbackManager
The user interface provides a list view than can be presented modally using
`[BITFeedbackManager showFeedbackListView]` or adding
`[BITFeedbackManager feedbackListViewController:]` to push onto a navigation stack.
This list integrates all features to load new messages, write new messages, view messages
and ask the user for additional (optional) data like name and email.
If the user provides the email address, all responses from the server will also be sent
to the user via email and the user is also able to respond directly via email, too.
The message list interface also contains options to locally delete single messages
by swiping over them, or deleting all messages. This will not delete the messages
on the server, though!
It also integrates actions to invoke the user interface to compose a new message,
reload the list content from the server and change the users name or email if these
are allowed to be set.
It is also possible to invoke the user interface to compose a new message in your
own code, by calling `[BITFeedbackManager showFeedbackComposeView]` modally or adding
`[BITFeedbackManager feedbackComposeViewController]` to push onto a navigation stack.
If new messages are written while the device is offline, the SDK automatically retries to
send them once the app starts again or gets active again, or if the notification
`BITHockeyNetworkDidBecomeReachableNotification` is fired.
A third option is to include the `BITFeedbackActivity` into an UIActivityViewController.
This can be useful if you present some data that users can not only share but also
report back to the developer because they have some problems, e.g. webcams not working
any more. The activity provides a default title and image that can also be customized.
New messages are automatically loaded on startup, when the app becomes active again
or when the notification `BITHockeyNetworkDidBecomeReachableNotification` is fired. This
only happens if the user ever did initiate a conversation by writing the first
feedback message.
Implementing the `BITFeedbackManagerDelegate` protocol will notify your app when a new
message was received from the server. The `BITFeedbackComposeViewControllerDelegate`
protocol informs your app about events related to sending feedback messages.
*/
@interface BITFeedbackManager : BITHockeyBaseManager
///-----------------------------------------------------------------------------
/// @name General settings
///-----------------------------------------------------------------------------
/**
Define if a name has to be provided by the user when providing feedback
- `BITFeedbackUserDataElementDontShow`: Don't ask for this user data element at all
- `BITFeedbackUserDataElementOptional`: The user may provide it, but does not have to
- `BITFeedbackUserDataElementRequired`: The user has to provide this to continue
The default value is `BITFeedbackUserDataElementOptional`.
@warning If you provide a non nil value for the `BITFeedbackManager` class via
`[BITHockeyManagerDelegate userNameForHockeyManager:componentManager:]` then this
property will automatically be set to `BITFeedbackUserDataElementDontShow`
@see BITFeedbackUserDataElement
@see requireUserEmail
@see `[BITHockeyManagerDelegate userNameForHockeyManager:componentManager:]`
*/
@property (nonatomic, readwrite) BITFeedbackUserDataElement requireUserName;
/**
Define if an email address has to be provided by the user when providing feedback
If the user provides the email address, all responses from the server will also be send
to the user via email and the user is also able to respond directly via email too.
- `BITFeedbackUserDataElementDontShow`: Don't ask for this user data element at all
- `BITFeedbackUserDataElementOptional`: The user may provide it, but does not have to
- `BITFeedbackUserDataElementRequired`: The user has to provide this to continue
The default value is `BITFeedbackUserDataElementOptional`.
@warning If you provide a non nil value for the `BITFeedbackManager` class via
`[BITHockeyManagerDelegate userEmailForHockeyManager:componentManager:]` then this
property will automatically be set to `BITFeedbackUserDataElementDontShow`
@see BITFeedbackUserDataElement
@see requireUserName
@see `[BITHockeyManagerDelegate userEmailForHockeyManager:componentManager:]`
*/
@property (nonatomic, readwrite) BITFeedbackUserDataElement requireUserEmail;
/**
Indicates if an alert should be shown when new messages have arrived
This lets the user view the new feedback by choosing the appropriate option
in the alert sheet, and the `BITFeedbackListViewController` will be shown.
The alert is only shown, if the newest message didn't originate from the current user.
This requires the users email address to be present! The optional userid property
cannot be used, because users could also answer via email and then this information
is not available.
Default is `YES`
@see feedbackListViewController:
@see requireUserEmail
@see `[BITHockeyManagerDelegate userEmailForHockeyManager:componentManager:]`
*/
@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`: Triggers when the user takes a screenshot.
This will grab the latest image from the camera roll. Requires iOS 7 or later!
- `BITFeedbackObservationModeThreeFingerTap`: Triggers when the user taps on the screen for three seconds with three fingers.
Default is `BITFeedbackObservationNone`
@see showFeedbackComposeViewWithGeneratedScreenshot
*/
@property (nonatomic, readwrite) BITFeedbackObservationMode feedbackObservationMode;
///-----------------------------------------------------------------------------
/// @name User Interface
///-----------------------------------------------------------------------------
/**
Indicates if a forced user data UI presentation is shown modal
If `requireUserName` and/or `requireUserEmail` are enabled, the first presentation
of `feedbackListViewController:` and subsequent `feedbackComposeViewController:`
will automatically present a UI that lets the user provide this data and compose
a message. By default this is shown (since SDK 3.1) as a modal sheet.
If you want the SDK to push this UI onto the navigation stack in this specific scenario,
then change the property to `NO`.
@warning If you are presenting the `BITFeedbackListViewController` in a popover, this property should not be changed!
Default is `YES`
@see requireUserName
@see requireUserEmail
@see showFeedbackComposeView
@see feedbackComposeViewController
@see showFeedbackListView
@see feedbackListViewController:
*/
@property (nonatomic, readwrite) BOOL showFirstRequiredPresentationModal;
/**
Return a screenshot UIImage intance from the current visiable screen
@return UIImage instance containing a screenshot of the current screen
*/
- (UIImage *)screenshot;
/**
Present the modal feedback list user interface.
*/
- (void)showFeedbackListView;
/**
Create an feedback list view
@param modal Return a view ready for modal presentation with integrated navigation bar
@return `BITFeedbackListViewController` The feedback list view controller,
e.g. to push it onto a navigation stack.
*/
- (BITFeedbackListViewController *)feedbackListViewController:(BOOL)modal;
/**
Present the modal feedback compose message user interface.
*/
- (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.
@param items an NSArray with objects that should be attached
@see `[BITFeedbackComposeViewController prepareWithItems:]`
*/
- (void)showFeedbackComposeViewWithPreparedItems:(NSArray *)items;
/**
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;
/**
Create a feedback compose view
Example to show a modal feedback compose UI with prefilled text
BITFeedbackComposeViewController *feedbackCompose = [[BITHockeyManager sharedHockeyManager].feedbackManager feedbackComposeViewController];
[feedbackCompose prepareWithItems:
@[@"Adding some example default text and also adding a link.",
[NSURL URLWithString:@"http://hockeayyp.net/"]]];
UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:feedbackCompose];
navController.modalPresentationStyle = UIModalPresentationFormSheet;
[self presentViewController:navController animated:YES completion:nil];
@return `BITFeedbackComposeViewController` The compose feedback view controller,
e.g. to push it onto a navigation stack.
*/
- (BITFeedbackComposeViewController *)feedbackComposeViewController;
@end
File diff suppressed because it is too large. Load diff
-50
View File
@@ -1,50 +0,0 @@
/*
* Author: Stephan Diederich
*
* Copyright (c) 2013-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 <Foundation/Foundation.h>
@class BITFeedbackManager;
@protocol BITFeedbackComposeViewControllerDelegate;
/**
* Delegate protocol which is notified about changes in the feedbackManager
* @TODO
* * move shouldShowUpdateAlert from feedbackManager here
*/
@protocol BITFeedbackManagerDelegate <NSObject, BITFeedbackComposeViewControllerDelegate>
@optional
/**
* can be implemented to know when new feedback from the server arrived
*
* @param feedbackManager The feedbackManager which did detect the new messages
*/
- (void) feedbackManagerDidReceiveNewFeedback:(BITFeedbackManager*) feedbackManager;
@end
-120
View File
@@ -1,120 +0,0 @@
/*
* Author: Andreas Linde <mail@andreaslinde.de>
* Kent Sutherland
*
* 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.
*/
#if HOCKEYSDK_FEATURE_FEEDBACK
#import "BITFeedbackMessage.h"
@interface BITFeedbackManager () <UIAlertViewDelegate> {
}
///-----------------------------------------------------------------------------
/// @name Delegate
///-----------------------------------------------------------------------------
/**
Sets the `BITFeedbackManagerDelegate` delegate.
Can be set to be notified when new feedback is received from the server.
The delegate is automatically set by using `[BITHockeyManager setDelegate:]`. You
should not need to set this delegate individually.
@see `[BITHockeyManager setDelegate:]`
*/
@property (nonatomic, weak) id<BITFeedbackManagerDelegate> delegate;
@property (nonatomic, strong) NSMutableArray *feedbackList;
@property (nonatomic, strong) NSString *token;
// used by BITHockeyManager if disable status is changed
@property (nonatomic, getter = isFeedbackManagerDisabled) BOOL disableFeedbackManager;
@property (nonatomic, strong) BITFeedbackListViewController *currentFeedbackListViewController;
@property (nonatomic, strong) BITFeedbackComposeViewController *currentFeedbackComposeViewController;
@property (nonatomic) BOOL didAskUserData;
@property (nonatomic, strong) NSDate *lastCheck;
@property (nonatomic, strong) NSNumber *lastMessageID;
@property (nonatomic, copy) NSString *userID;
@property (nonatomic, copy) NSString *userName;
@property (nonatomic, copy) NSString *userEmail;
// Fetch user meta data
- (BOOL)updateUserIDUsingKeychainAndDelegate;
- (BOOL)updateUserNameUsingKeychainAndDelegate;
- (BOOL)updateUserEmailUsingKeychainAndDelegate;
// load new messages from the server
- (void)updateMessagesList;
// load new messages from the server if the last request is too long ago
- (void)updateMessagesListIfRequired;
- (NSUInteger)numberOfMessages;
- (BITFeedbackMessage *)messageAtIndex:(NSUInteger)index;
- (void)submitMessageWithText:(NSString *)text andAttachments:(NSArray *)photos;
- (void)submitPendingMessages;
// Returns YES if manual user data can be entered, required or optional
- (BOOL)askManualUserDataAvailable;
// Returns YES if required user data is missing?
- (BOOL)requireManualUserDataMissing;
// Returns YES if user data is available and can be edited
- (BOOL)isManualUserDataAvailable;
// used in the user data screen
- (void)updateDidAskUserData;
- (BITFeedbackMessage *)messageWithID:(NSNumber *)messageID;
- (NSArray *)messagesWithStatus:(BITFeedbackMessageStatus)status;
- (void)saveMessages;
- (void)fetchMessageUpdates;
- (void)updateMessageListFromResponse:(NSDictionary *)jsonDictionary;
- (BOOL)deleteMessageAtIndex:(NSUInteger)index;
- (void)deleteAllMessages;
@end
#endif /* HOCKEYSDK_FEATURE_FEEDBACK */
-102
View File
@@ -1,102 +0,0 @@
/*
* Author: Andreas Linde <mail@andreaslinde.de>
*
* 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 <Foundation/Foundation.h>
@class BITFeedbackMessageAttachment;
/**
* Status for each feedback message
*/
typedef NS_ENUM(NSInteger, BITFeedbackMessageStatus) {
/**
* default and new messages from SDK per default
*/
BITFeedbackMessageStatusSendPending = 0,
/**
* message is in conflict, happens if the message is already stored on the server and tried sending it again
*/
BITFeedbackMessageStatusInConflict = 1,
/**
* sending of message is in progress
*/
BITFeedbackMessageStatusSendInProgress = 2,
/**
* new messages from server
*/
BITFeedbackMessageStatusUnread = 3,
/**
* messages from server once read and new local messages once successful send from SDK
*/
BITFeedbackMessageStatusRead = 4,
/**
* message is archived, happens if the thread is deleted from the server
*/
BITFeedbackMessageStatusArchived = 5
};
/**
* An individual feedback message
*/
@interface BITFeedbackMessage : NSObject {
}
@property (nonatomic, copy) NSString *text;
@property (nonatomic, copy) NSString *userID;
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *email;
@property (nonatomic, copy) NSDate *date;
@property (nonatomic, copy) NSNumber *identifier;
@property (nonatomic, copy) NSString *token;
@property (nonatomic, strong) NSArray *attachments;
@property (nonatomic) BITFeedbackMessageStatus status;
@property (nonatomic) BOOL userMessage;
/**
Delete local cached attachment data
@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;
/**
Return the attachments that can be viewed
@return NSArray containing the attachment objects that can be previewed
*/
- (NSArray *)previewableAttachments;
@end
-114
View File
@@ -1,114 +0,0 @@
/*
* Author: Andreas Linde <mail@andreaslinde.de>
*
* 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 "BITFeedbackMessage.h"
#import "BITFeedbackMessageAttachment.h"
@implementation BITFeedbackMessage
#pragma mark - NSObject
- (instancetype) init {
if ((self = [super init])) {
_text = nil;
_userID = nil;
_name = nil;
_email = nil;
_date = [[NSDate alloc] init];
_token = nil;
_attachments = nil;
_identifier = [[NSNumber alloc] initWithInteger:0];
_status = BITFeedbackMessageStatusSendPending;
_userMessage = NO;
}
return self;
}
#pragma mark - NSCoder
- (void)encodeWithCoder:(NSCoder *)encoder {
[encoder encodeObject:self.text forKey:@"text"];
[encoder encodeObject:self.userID forKey:@"userID"];
[encoder encodeObject:self.name forKey:@"name"];
[encoder encodeObject:self.email forKey:@"email"];
[encoder encodeObject:self.date forKey:@"date"];
[encoder encodeObject:self.identifier forKey:@"id"];
[encoder encodeObject:self.attachments forKey:@"attachments"];
[encoder encodeInteger:self.status forKey:@"status"];
[encoder encodeBool:self.userMessage forKey:@"userMessage"];
[encoder encodeObject:self.token forKey:@"token"];
}
- (instancetype)initWithCoder:(NSCoder *)decoder {
if ((self = [self init])) {
self.text = [decoder decodeObjectForKey:@"text"];
self.userID = [decoder decodeObjectForKey:@"userID"];
self.name = [decoder decodeObjectForKey:@"name"];
self.email = [decoder decodeObjectForKey:@"email"];
self.date = [decoder decodeObjectForKey:@"date"];
self.identifier = [decoder decodeObjectForKey:@"id"];
self.attachments = [decoder decodeObjectForKey:@"attachments"];
self.status = (BITFeedbackMessageStatus)[decoder decodeIntegerForKey:@"status"];
self.userMessage = [decoder decodeBoolForKey:@"userMessage"];
self.token = [decoder decodeObjectForKey:@"token"];
}
return self;
}
#pragma mark - Deletion
- (void)deleteContents {
for (BITFeedbackMessageAttachment *attachment in self.attachments){
[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];
}
self.attachments = [self.attachments arrayByAddingObject:object];
}
@end
@@ -1,68 +0,0 @@
/*
* Author: Moritz Haarmann <post@moritzhaarmann.de>
*
* 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 <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import <QuickLook/QuickLook.h>
/**
* An individual feedback message attachment
*/
@interface BITFeedbackMessageAttachment : NSObject<NSCoding, QLPreviewItem>
@property (nonatomic, copy) NSNumber *identifier;
@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;
+ (BITFeedbackMessageAttachment *)attachmentWithData:(NSData *)data contentType:(NSString *)contentType;
- (UIImage *)thumbnailWithSize:(CGSize)size;
- (void)replaceData:(NSData *)data;
- (void)deleteContents;
- (BOOL)needsLoadingFromURL;
- (BOOL)isImage;
- (NSURL *)localURL;
/**
Used to determine whether QuickLook can preview this file or not. If not, we don't download it.
*/
- (NSString*)possibleFilename;
@end
-258
View File
@@ -1,258 +0,0 @@
/*
* Author: Moritz Haarmann <post@moritzhaarmann.de>
*
* 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"
#import <MobileCoreServices/MobileCoreServices.h>
#define kCacheFolderName @"attachments"
@interface BITFeedbackMessageAttachment()
@property (nonatomic, strong) NSMutableDictionary *thumbnailRepresentations;
@property (nonatomic, strong) NSData *internalData;
@property (nonatomic, copy) NSString *filename;
@end
@implementation BITFeedbackMessageAttachment {
NSString *_tempFilename;
NSString *_cachePath;
NSFileManager *_fm;
}
+ (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;
}
- (instancetype)init {
if ((self = [super init])) {
_isLoading = NO;
_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 {
self->_internalData = data;
self.filename = [self possibleFilename];
[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;
}
- (void)replaceData:(NSData *)data {
self.data = data;
self.thumbnailRepresentations = [NSMutableDictionary new];
}
- (BOOL)needsLoadingFromURL {
return (self.sourceURL && ![_fm fileExistsAtPath:[self.localURL path]]);
}
- (BOOL)isImage {
return ([self.contentType rangeOfString:@"image"].location != NSNotFound);
}
- (NSURL *)localURL {
if (self.filename && [_fm fileExistsAtPath:self.filename]) {
return [NSURL fileURLWithPath:self.filename];
}
return nil;
}
#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"];
}
- (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 ) {
return [UIImage imageWithData:self.data];
} else {
// Create a Icon ..
UIDocumentInteractionController* docController = [[UIDocumentInteractionController alloc] init];
docController.name = self.originalFilename;
NSArray* icons = docController.icons;
if (icons.count){
return icons[0];
} else {
return nil;
}
}
}
- (UIImage *)thumbnailWithSize:(CGSize)size {
id<NSCopying> cacheKey = [NSValue valueWithCGSize:size];
if (!self.thumbnailRepresentations[cacheKey]) {
UIImage *image = self.imageRepresentation;
// consider the scale.
if (!image) {
return nil;
}
CGFloat scale = [UIScreen mainScreen].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) {
[self.thumbnailRepresentations setObject:scaledTumbnail forKey:cacheKey];
}
} else {
UIImage *thumbnail = bit_imageToFitSize(image, size, YES) ;
[self.thumbnailRepresentations setObject:thumbnail forKey:cacheKey];
}
}
return self.thumbnailRepresentations[cacheKey];
}
#pragma mark - Persistence Helpers
- (NSString *)possibleFilename {
if (_tempFilename) {
return _tempFilename;
}
NSString *uniqueString = bit_UUID();
_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) {
_tempFilename = [_tempFilename stringByAppendingPathExtension:(__bridge NSString *)(extension)];
CFRelease(extension);
}
CFRelease(uti);
return _tempFilename;
}
- (void)deleteContents {
if (self.filename) {
[_fm removeItemAtPath:self.filename error:nil];
self.filename = nil;
}
}
#pragma mark - QLPreviewItem
- (NSString *)previewItemTitle {
return self.originalFilename;
}
- (NSURL *)previewItemURL {
if (self.localURL){
return self.localURL;
} else if (self.sourceURL) {
NSString *filename = self.possibleFilename;
if (filename) {
return [NSURL fileURLWithPath:filename];
}
}
return nil;
}
@end
@@ -1,51 +0,0 @@
/*
* Author: Andreas Linde <mail@andreaslinde.de>
*
* 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>
@protocol BITFeedbackUserDataDelegate;
@interface BITFeedbackUserDataViewController : UITableViewController <UITextFieldDelegate>
@property (nonatomic, weak) id <BITFeedbackUserDataDelegate> delegate;
@end
///////////////////////////////////////////////////////////////////////////////////////////////////
@protocol BITFeedbackUserDataDelegate <NSObject>
@required
// cancel action is invoked
- (void)userDataUpdateCancelled;
// save action is invoked and all required data available
- (void)userDataUpdateFinished;
@end
@@ -1,272 +0,0 @@
/*
* Author: Andreas Linde <mail@andreaslinde.de>
*
* 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 "HockeySDK.h"
#if HOCKEYSDK_FEATURE_FEEDBACK
#import "HockeySDKPrivate.h"
#import "BITHockeyHelper.h"
#import "BITFeedbackUserDataViewController.h"
#import "BITFeedbackManagerPrivate.h"
@interface BITFeedbackUserDataViewController () {
UIStatusBarStyle _statusBarStyle;
}
@property (nonatomic, weak) BITFeedbackManager *manager;
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *email;
@end
@implementation BITFeedbackUserDataViewController
- (instancetype)initWithStyle:(UITableViewStyle)style {
self = [super initWithStyle:style];
if (self) {
_delegate = nil;
_manager = [BITHockeyManager sharedHockeyManager].feedbackManager;
_name = @"";
_email = @"";
}
return self;
}
- (void)viewDidLoad {
[super viewDidLoad];
self.title = BITHockeyLocalizedString(@"HockeyFeedbackUserDataTitle");
[self.tableView setScrollEnabled:NO];
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
_statusBarStyle = [[UIApplication sharedApplication] statusBarStyle];
#if __IPHONE_OS_VERSION_MAX_ALLOWED > __IPHONE_6_1
[[UIApplication sharedApplication] setStatusBarStyle:(self.navigationController.navigationBar.barStyle == UIBarStyleDefault) ? UIStatusBarStyleDefault : UIStatusBarStyleLightContent];
#else
[[UIApplication sharedApplication] setStatusBarStyle:(self.navigationController.navigationBar.barStyle == UIBarStyleDefault) ? UIStatusBarStyleDefault : UIStatusBarStyleBlackOpaque];
#endif
// Do any additional setup after loading the view.
self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemCancel
target:self
action:@selector(dismissAction:)];
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemSave
target:self
action:@selector(saveAction:)];
if ([self.manager userName])
self.name = [self.manager userName];
if ([self.manager userEmail])
self.email = [self.manager userEmail];
[self.manager updateDidAskUserData];
self.navigationItem.rightBarButtonItem.enabled = [self allRequiredFieldsEntered];
}
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
[[UIApplication sharedApplication] setStatusBarStyle:_statusBarStyle];
}
#pragma mark - UIViewController Rotation
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)orientation {
return YES;
}
#pragma mark - Private methods
- (BOOL)allRequiredFieldsEntered {
if ([self.manager requireUserName] == BITFeedbackUserDataElementRequired && [self.name length] == 0)
return NO;
if ([self.manager requireUserEmail] == BITFeedbackUserDataElementRequired && [self.email length] == 0)
return NO;
if ([self.email length] > 0 && !bit_validateEmail(self.email))
return NO;
return YES;
}
- (void)userNameEntered:(id)sender {
self.name = [(UITextField *)sender text];
self.navigationItem.rightBarButtonItem.enabled = [self allRequiredFieldsEntered];
}
- (void)userEmailEntered:(id)sender {
self.email = [(UITextField *)sender text];
self.navigationItem.rightBarButtonItem.enabled = [self allRequiredFieldsEntered];
}
- (void)dismissAction:(id)sender {
[self.delegate userDataUpdateCancelled];
}
- (void)saveAction:(id)sender {
if ([self.manager requireUserName]) {
[self.manager setUserName:self.name];
}
if ([self.manager requireUserEmail]) {
[self.manager setUserEmail:self.email];
}
[self.delegate userDataUpdateFinished];
}
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
NSInteger rows = 0;
if ([self.manager requireUserName] != BITFeedbackUserDataElementDontShow)
rows ++;
if ([self.manager requireUserEmail] != BITFeedbackUserDataElementDontShow)
rows ++;
return rows;
}
- (NSString *)tableView:(UITableView *)tableView titleForFooterInSection:(NSInteger)section {
if (section == 0) {
return BITHockeyLocalizedString(@"HockeyFeedbackUserDataDescription");
}
return nil;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = @"InputCell";
UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
cell.accessoryType = UITableViewCellAccessoryNone;
cell.selectionStyle = UITableViewCellSelectionStyleNone;
cell.backgroundColor = [UIColor whiteColor];
UITextField *textField = [[UITextField alloc] initWithFrame:CGRectMake(110, 11, self.view.frame.size.width - 110 - 35, 24)];
if (UI_USER_INTERFACE_IDIOM() != UIUserInterfaceIdiomPad) {
textField.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin;
}
textField.adjustsFontSizeToFitWidth = YES;
textField.textColor = [UIColor blackColor];
textField.backgroundColor = [UIColor lightGrayColor];
if ([indexPath row] == 0 && [self.manager requireUserName] != BITFeedbackUserDataElementDontShow) {
textField.placeholder = BITHockeyLocalizedString(@"HockeyFeedbackUserDataNamePlaceHolder");
textField.text = self.name;
textField.keyboardType = UIKeyboardTypeDefault;
if ([self.manager requireUserEmail])
textField.returnKeyType = UIReturnKeyNext;
else
textField.returnKeyType = UIReturnKeyDone;
textField.autocapitalizationType = UITextAutocapitalizationTypeWords;
[textField addTarget:self action:@selector(userNameEntered:) forControlEvents:UIControlEventEditingChanged];
[textField becomeFirstResponder];
} else {
textField.placeholder = BITHockeyLocalizedString(@"HockeyFeedbackUserDataEmailPlaceholder");
textField.text = self.email;
textField.keyboardType = UIKeyboardTypeEmailAddress;
textField.returnKeyType = UIReturnKeyDone;
textField.autocapitalizationType = UITextAutocapitalizationTypeNone;
[textField addTarget:self action:@selector(userEmailEntered:) forControlEvents:UIControlEventEditingChanged];
if (![self.manager requireUserName])
[textField becomeFirstResponder];
}
textField.backgroundColor = [UIColor whiteColor];
textField.autocorrectionType = UITextAutocorrectionTypeNo;
textField.textAlignment = kBITTextLabelAlignmentLeft;
textField.delegate = self;
textField.tag = indexPath.row;
textField.clearButtonMode = UITextFieldViewModeWhileEditing;
[textField setEnabled: YES];
[cell addSubview:textField];
}
if ([indexPath row] == 0 && [self.manager requireUserName] != BITFeedbackUserDataElementDontShow) {
cell.textLabel.text = BITHockeyLocalizedString(@"HockeyFeedbackUserDataName");
} else {
cell.textLabel.text = BITHockeyLocalizedString(@"HockeyFeedbackUserDataEmail");
}
return cell;
}
#pragma mark - UITextFieldDelegate
- (BOOL)textFieldShouldReturn:(UITextField *)textField {
NSInteger nextTag = textField.tag + 1;
UIResponder* nextResponder = [self.view viewWithTag:nextTag];
if (nextResponder) {
[nextResponder becomeFirstResponder];
} else {
if ([self allRequiredFieldsEntered]) {
if ([textField isFirstResponder])
[textField resignFirstResponder];
[self saveAction:nil];
}
}
return NO;
}
@end
#endif /* HOCKEYSDK_FEATURE_FEEDBACK */
-47
View File
@@ -1,47 +0,0 @@
/*
* Author: Stephan Diederich
*
* Copyright (c) 2013-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 <Foundation/Foundation.h>
@class BITHTTPOperation;
typedef void (^BITNetworkCompletionBlock)(BITHTTPOperation* operation, NSData* data, NSError* error);
@interface BITHTTPOperation : NSOperation
+ (instancetype) operationWithRequest:(NSURLRequest *) urlRequest;
@property (nonatomic, readonly) NSURLRequest *URLRequest;
//the completion is only called if the operation wasn't cancelled
- (void) setCompletion:(BITNetworkCompletionBlock) completionBlock;
@property (nonatomic, readonly) NSHTTPURLResponse *response;
@property (nonatomic, readonly) NSData *data;
@property (nonatomic, readonly) NSError *error;
@end
-148
View File
@@ -1,148 +0,0 @@
/*
* Author: Stephan Diederich
*
* Copyright (c) 2013-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 "BITHTTPOperation.h"
@interface BITHTTPOperation()<NSURLConnectionDelegate>
@end
@implementation BITHTTPOperation {
NSURLRequest *_URLRequest;
NSURLConnection *_connection;
NSMutableData *_data;
BOOL _isExecuting;
BOOL _isFinished;
}
+ (instancetype)operationWithRequest:(NSURLRequest *)urlRequest {
BITHTTPOperation *op = [[self class] new];
op->_URLRequest = urlRequest;
return op;
}
#pragma mark - NSOperation overrides
- (BOOL)isConcurrent {
return YES;
}
- (void)cancel {
[_connection cancel];
[super cancel];
}
- (void) start {
if(self.isCancelled) {
[self finish];
return;
}
if (![[NSThread currentThread] isMainThread]) {
[self performSelector:@selector(start) onThread:NSThread.mainThread withObject:nil waitUntilDone:NO];
return;
}
if(self.isCancelled) {
[self finish];
return;
}
[self willChangeValueForKey:@"isExecuting"];
_isExecuting = YES;
[self didChangeValueForKey:@"isExecuting"];
_connection = [[NSURLConnection alloc] initWithRequest:_URLRequest
delegate:self
startImmediately:YES];
}
- (void) finish {
[self willChangeValueForKey:@"isExecuting"];
[self willChangeValueForKey:@"isFinished"];
_isExecuting = NO;
_isFinished = YES;
[self didChangeValueForKey:@"isExecuting"];
[self didChangeValueForKey:@"isFinished"];
}
#pragma mark - NSURLConnectionDelegate
-(void)connection:(NSURLConnection*)connection didReceiveResponse:(NSURLResponse*)response {
_data = [[NSMutableData alloc] init];
_response = (id)response;
}
-(void)connection:(NSURLConnection*)connection didReceiveData:(NSData*)data {
[_data appendData:data];
}
-(void)connection:(NSURLConnection*)connection didFailWithError:(NSError*)error {
//FINISHED and failed
_error = error;
_data = nil;
[self finish];
}
-(void)connectionDidFinishLoading:(NSURLConnection*)connection {
[self finish];
}
#pragma mark - Public interface
- (NSData *)data {
return _data;
}
- (void)setCompletion:(BITNetworkCompletionBlock)completion {
if(!completion) {
[super setCompletionBlock:nil];
} else {
__weak typeof(self) weakSelf = self;
[super setCompletionBlock:^{
typeof(self) strongSelf = weakSelf;
if(strongSelf) {
dispatch_async(dispatch_get_main_queue(), ^{
if(!strongSelf.isCancelled) {
completion(strongSelf, strongSelf->_data, strongSelf->_error);
}
[strongSelf setCompletionBlock:nil];
});
}
}];
}
}
- (BOOL)isFinished {
return _isFinished;
}
- (BOOL)isExecuting {
return _isExecuting;
}
@end
Loaded 100 of 370 files, more files were not shown because too many files have changed in this diff. Show more