mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00

git-subtree-dir: submodules/HockeySDK-iOS git-subtree-mainline: 085acd26c4432939403765234266e3c1be0f3dd9 git-subtree-split: c7d0c7026303253e2ac576c02655691e5d314fe2
416 lines
16 KiB
Objective-C
416 lines
16 KiB
Objective-C
/*
|
|
* 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 "HockeySDK.h"
|
|
|
|
#if HOCKEYSDK_FEATURE_FEEDBACK
|
|
|
|
#import "BITImageAnnotationViewController.h"
|
|
#import "BITImageAnnotation.h"
|
|
#import "BITRectangleImageAnnotation.h"
|
|
#import "BITArrowImageAnnotation.h"
|
|
#import "BITBlurImageAnnotation.h"
|
|
#import "BITHockeyHelper.h"
|
|
#import "HockeySDKPrivate.h"
|
|
|
|
typedef NS_ENUM(NSInteger, BITImageAnnotationViewControllerInteractionMode) {
|
|
BITImageAnnotationViewControllerInteractionModeNone,
|
|
BITImageAnnotationViewControllerInteractionModeDraw,
|
|
BITImageAnnotationViewControllerInteractionModeMove
|
|
};
|
|
|
|
@interface BITImageAnnotationViewController ()
|
|
|
|
@property (nonatomic, strong) UIImageView *imageView;
|
|
@property (nonatomic, strong) UISegmentedControl *editingControls;
|
|
@property (nonatomic, strong) NSMutableArray *objects;
|
|
|
|
@property (nonatomic, strong) UITapGestureRecognizer *tapRecognizer;
|
|
@property (nonatomic, strong) UIPanGestureRecognizer *panRecognizer;
|
|
@property (nonatomic, strong) UIPinchGestureRecognizer *pinchRecognizer;
|
|
|
|
@property (nonatomic) CGFloat scaleFactor;
|
|
|
|
@property (nonatomic) CGPoint panStart;
|
|
@property (nonatomic,strong) BITImageAnnotation *currentAnnotation;
|
|
|
|
@property (nonatomic) BITImageAnnotationViewControllerInteractionMode currentInteraction;
|
|
|
|
@property (nonatomic) CGRect pinchStartingFrame;
|
|
|
|
@end
|
|
|
|
@implementation BITImageAnnotationViewController
|
|
|
|
#pragma mark - UIViewController
|
|
|
|
- (void)viewDidLoad {
|
|
[super viewDidLoad];
|
|
|
|
self.view.backgroundColor = [UIColor groupTableViewBackgroundColor];
|
|
|
|
NSArray *icons = @[@"Arrow.png",@"Rectangle.png", @"Blur.png"];
|
|
|
|
self.editingControls = [[UISegmentedControl alloc] initWithItems:@[@"Rectangle", @"Arrow", @"Blur"]];
|
|
int i=0;
|
|
for (NSString *imageName in icons){
|
|
[self.editingControls setImage:bit_imageNamed(imageName, BITHOCKEYSDK_BUNDLE) forSegmentAtIndex:i++];
|
|
}
|
|
|
|
#pragma clang diagnostic push
|
|
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
|
[self.editingControls setSegmentedControlStyle:UISegmentedControlStyleBar];
|
|
#pragma clang diagnostic pop
|
|
|
|
self.navigationItem.titleView = self.editingControls;
|
|
|
|
self.objects = [NSMutableArray new];
|
|
|
|
[self.editingControls addTarget:self action:@selector(editingAction:) forControlEvents:UIControlEventTouchUpInside];
|
|
[self.editingControls setSelectedSegmentIndex:0];
|
|
|
|
self.imageView = [[UIImageView alloc] initWithFrame:self.view.bounds];
|
|
|
|
self.imageView.clipsToBounds = YES;
|
|
|
|
self.imageView.image = self.image;
|
|
self.imageView.contentMode = UIViewContentModeScaleToFill;
|
|
|
|
self.view.frame = UIScreen.mainScreen.bounds;
|
|
|
|
[self.view addSubview:self.imageView];
|
|
// Erm.
|
|
self.imageView.frame = [UIScreen mainScreen].bounds;
|
|
|
|
self.panRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(panned:)];
|
|
self.pinchRecognizer = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(pinched:)];
|
|
self.tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapped:)];
|
|
|
|
[self.imageView addGestureRecognizer:self.pinchRecognizer];
|
|
[self.imageView addGestureRecognizer:self.panRecognizer];
|
|
[self.view addGestureRecognizer:self.tapRecognizer];
|
|
|
|
self.imageView.userInteractionEnabled = YES;
|
|
|
|
self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc ] initWithImage:bit_imageNamed(@"Cancel.png", BITHOCKEYSDK_BUNDLE) landscapeImagePhone:bit_imageNamed(@"Cancel.png", BITHOCKEYSDK_BUNDLE) style:UIBarButtonItemStylePlain target:self action:@selector(discard:)];
|
|
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc ] initWithImage:bit_imageNamed(@"Ok.png", BITHOCKEYSDK_BUNDLE) landscapeImagePhone:bit_imageNamed(@"Ok.png", BITHOCKEYSDK_BUNDLE) style:UIBarButtonItemStylePlain target:self action:@selector(save:)];
|
|
|
|
self.view.autoresizesSubviews = NO;
|
|
}
|
|
|
|
|
|
- (void)viewWillAppear:(BOOL)animated {
|
|
[super viewWillAppear:animated];
|
|
|
|
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(orientationDidChange:) name:UIDeviceOrientationDidChangeNotification object:nil];
|
|
|
|
[self fitImageViewFrame];
|
|
|
|
}
|
|
|
|
- (void)viewWillDisappear:(BOOL)animated {
|
|
[super viewWillDisappear:animated];
|
|
|
|
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIDeviceOrientationDidChangeNotification object:nil];
|
|
}
|
|
|
|
- (BOOL)prefersStatusBarHidden {
|
|
return self.navigationController.navigationBarHidden || self.navigationController.navigationBar.alpha == 0;
|
|
}
|
|
|
|
- (void)orientationDidChange:(NSNotification *) __unused notification {
|
|
[self fitImageViewFrame];
|
|
}
|
|
|
|
|
|
- (void)fitImageViewFrame {
|
|
|
|
CGSize size = [UIScreen mainScreen].bounds.size;
|
|
if (UIInterfaceOrientationIsLandscape([UIApplication sharedApplication].statusBarOrientation) && size.height > size.width){
|
|
size = CGSizeMake(size.height, size.width);
|
|
}
|
|
|
|
CGFloat heightScaleFactor = size.height / self.image.size.height;
|
|
CGFloat widthScaleFactor = size.width / self.image.size.width;
|
|
|
|
CGFloat factor = MIN(heightScaleFactor, widthScaleFactor);
|
|
self.scaleFactor = factor;
|
|
CGSize scaledImageSize = CGSizeMake(self.image.size.width * factor, self.image.size.height * factor);
|
|
|
|
CGRect baseFrame = CGRectMake(self.view.frame.size.width/2 - scaledImageSize.width/2, self.view.frame.size.height/2 - scaledImageSize.height/2, scaledImageSize.width, scaledImageSize.height);
|
|
|
|
self.imageView.frame = baseFrame;
|
|
}
|
|
|
|
- (void)editingAction:(id) __unused sender {
|
|
|
|
}
|
|
|
|
- (BITImageAnnotation *)annotationForCurrentMode {
|
|
if (self.editingControls.selectedSegmentIndex == 0){
|
|
return [[BITArrowImageAnnotation alloc] initWithFrame:CGRectZero];
|
|
} else if(self.editingControls.selectedSegmentIndex==1){
|
|
return [[BITRectangleImageAnnotation alloc] initWithFrame:CGRectZero];
|
|
} else {
|
|
return [[BITBlurImageAnnotation alloc] initWithFrame:CGRectZero];
|
|
}
|
|
}
|
|
|
|
#pragma mark - Actions
|
|
|
|
- (void)discard:(id) __unused sender {
|
|
[self.delegate annotationControllerDidCancel:self];
|
|
[self dismissViewControllerAnimated:YES completion:nil];
|
|
}
|
|
|
|
- (void)save:(id) __unused sender {
|
|
UIImage *image = [self extractImage];
|
|
[self.delegate annotationController:self didFinishWithImage:image];
|
|
[self dismissViewControllerAnimated:YES completion:nil];
|
|
}
|
|
|
|
- (UIImage *)extractImage {
|
|
UIGraphicsBeginImageContextWithOptions(self.image.size, YES, 0.0);
|
|
CGContextRef ctx = UIGraphicsGetCurrentContext();
|
|
[self.image drawInRect:CGRectMake(0, 0, self.image.size.width, self.image.size.height)];
|
|
CGContextScaleCTM(ctx,((CGFloat)1.0)/self.scaleFactor,((CGFloat)1.0)/self.scaleFactor);
|
|
|
|
// Drawing all the annotations onto the final image.
|
|
for (BITImageAnnotation *annotation in self.objects){
|
|
CGContextTranslateCTM(ctx, annotation.frame.origin.x, annotation.frame.origin.y);
|
|
[annotation.layer renderInContext:ctx];
|
|
CGContextTranslateCTM(ctx,-1 * annotation.frame.origin.x,-1 * annotation.frame.origin.y);
|
|
}
|
|
|
|
UIImage *renderedImageOfMyself = UIGraphicsGetImageFromCurrentImageContext();
|
|
UIGraphicsEndImageContext();
|
|
return renderedImageOfMyself;
|
|
}
|
|
|
|
#pragma mark - UIGestureRecognizers
|
|
|
|
- (void)panned:(UIPanGestureRecognizer *)gestureRecognizer {
|
|
BITImageAnnotation *annotationAtLocation = (BITImageAnnotation *)[self.view hitTest:[gestureRecognizer locationInView:self.view] withEvent:nil];
|
|
|
|
if (![annotationAtLocation isKindOfClass:[BITImageAnnotation class]]){
|
|
annotationAtLocation = nil;
|
|
}
|
|
|
|
// determine the interaction mode if none is set so far.
|
|
|
|
if (self.currentInteraction == BITImageAnnotationViewControllerInteractionModeNone){
|
|
if (annotationAtLocation){
|
|
self.currentInteraction = BITImageAnnotationViewControllerInteractionModeMove;
|
|
} else if ([self canDrawNewAnnotation]){
|
|
self.currentInteraction = BITImageAnnotationViewControllerInteractionModeDraw;
|
|
}
|
|
}
|
|
|
|
if (self.currentInteraction == BITImageAnnotationViewControllerInteractionModeNone){
|
|
return;
|
|
}
|
|
|
|
|
|
if (self.currentInteraction == BITImageAnnotationViewControllerInteractionModeDraw){
|
|
if (gestureRecognizer.state == UIGestureRecognizerStateBegan){
|
|
self.currentAnnotation = [self annotationForCurrentMode];
|
|
[self.objects addObject:self.currentAnnotation];
|
|
self.currentAnnotation.sourceImage = self.image;
|
|
|
|
if (self.imageView.subviews.count > 0 && [self.currentAnnotation isKindOfClass:[BITBlurImageAnnotation class]]){
|
|
[self.imageView insertSubview:self.currentAnnotation belowSubview:[self firstAnnotationThatIsNotBlur]];
|
|
} else {
|
|
[self.imageView addSubview:self.currentAnnotation];
|
|
}
|
|
|
|
self.panStart = [gestureRecognizer locationInView:self.imageView];
|
|
|
|
} else if (gestureRecognizer.state == UIGestureRecognizerStateChanged){
|
|
CGPoint bla = [gestureRecognizer locationInView:self.imageView];
|
|
self.currentAnnotation.frame = CGRectMake(self.panStart.x, self.panStart.y, bla.x - self.panStart.x, bla.y - self.panStart.y);
|
|
self.currentAnnotation.movedDelta = CGSizeMake(bla.x - self.panStart.x, bla.y - self.panStart.y);
|
|
self.currentAnnotation.imageFrame = [self.view convertRect:self.imageView.frame toView:self.currentAnnotation];
|
|
[self.currentAnnotation setNeedsLayout];
|
|
[self.currentAnnotation layoutIfNeeded];
|
|
} else {
|
|
[self.currentAnnotation setSelected:NO];
|
|
self.currentAnnotation = nil;
|
|
self.currentInteraction = BITImageAnnotationViewControllerInteractionModeNone;
|
|
}
|
|
} else if (self.currentInteraction == BITImageAnnotationViewControllerInteractionModeMove){
|
|
if (gestureRecognizer.state == UIGestureRecognizerStateBegan){
|
|
// find and possibly move an existing annotation.
|
|
|
|
|
|
if ([self.objects indexOfObject:annotationAtLocation] != NSNotFound){
|
|
self.currentAnnotation = annotationAtLocation;
|
|
[annotationAtLocation setSelected:YES];
|
|
}
|
|
|
|
|
|
} else if (gestureRecognizer.state == UIGestureRecognizerStateChanged && self.currentAnnotation){
|
|
CGPoint delta = [gestureRecognizer translationInView:self.view];
|
|
|
|
CGRect annotationFrame = self.currentAnnotation.frame;
|
|
annotationFrame.origin.x += delta.x;
|
|
annotationFrame.origin.y += delta.y;
|
|
self.currentAnnotation.frame = annotationFrame;
|
|
self.currentAnnotation.imageFrame = [self.view convertRect:self.imageView.frame toView:self.currentAnnotation];
|
|
|
|
[self.currentAnnotation setNeedsLayout];
|
|
[self.currentAnnotation layoutIfNeeded];
|
|
|
|
[gestureRecognizer setTranslation:CGPointZero inView:self.view];
|
|
|
|
} else {
|
|
[self.currentAnnotation setSelected:NO];
|
|
self.currentAnnotation = nil;
|
|
self.currentInteraction = BITImageAnnotationViewControllerInteractionModeNone;
|
|
}
|
|
}
|
|
}
|
|
|
|
- (void)pinched:(UIPinchGestureRecognizer *)gestureRecognizer {
|
|
if (gestureRecognizer.state == UIGestureRecognizerStateBegan){
|
|
// try to figure out which view we are talking about.
|
|
BITImageAnnotation *candidate = nil;
|
|
BOOL validView = YES;
|
|
|
|
for (uint i = 0; i < gestureRecognizer.numberOfTouches; i++){
|
|
BITImageAnnotation *newCandidate = (BITImageAnnotation *)[self.view hitTest:[gestureRecognizer locationOfTouch:i inView:self.view] withEvent:nil];
|
|
|
|
if (![newCandidate isKindOfClass:[BITImageAnnotation class]]){
|
|
newCandidate = nil;
|
|
}
|
|
|
|
if (candidate == nil){
|
|
candidate = newCandidate;
|
|
} else if (candidate != newCandidate){
|
|
validView = NO;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (validView && [candidate resizable]){
|
|
self.currentAnnotation = candidate;
|
|
self.pinchStartingFrame = self.currentAnnotation.frame;
|
|
[self.currentAnnotation setSelected:YES];
|
|
}
|
|
|
|
} else if (gestureRecognizer.state == UIGestureRecognizerStateChanged && self.currentAnnotation && gestureRecognizer.numberOfTouches>1){
|
|
CGRect newFrame= (self.pinchStartingFrame);
|
|
|
|
// upper point?
|
|
CGPoint point1 = [gestureRecognizer locationOfTouch:0 inView:self.view];
|
|
CGPoint point2 = [gestureRecognizer locationOfTouch:1 inView:self.view];
|
|
|
|
|
|
newFrame.origin.x = point1.x;
|
|
newFrame.origin.y = point1.y;
|
|
|
|
newFrame.origin.x = (point1.x > point2.x) ? point2.x : point1.x;
|
|
newFrame.origin.y = (point1.y > point2.y) ? point2.y : point1.y;
|
|
|
|
newFrame.size.width = (point1.x > point2.x) ? point1.x - point2.x : point2.x - point1.x;
|
|
newFrame.size.height = (point1.y > point2.y) ? point1.y - point2.y : point2.y - point1.y;
|
|
|
|
|
|
self.currentAnnotation.frame = newFrame;
|
|
self.currentAnnotation.imageFrame = [self.view convertRect:self.imageView.frame toView:self.currentAnnotation];
|
|
} else {
|
|
[self.currentAnnotation setSelected:NO];
|
|
self.currentAnnotation = nil;
|
|
}
|
|
}
|
|
|
|
- (void)tapped:(UIGestureRecognizer *) __unused tapRecognizer {
|
|
|
|
// TODO: remove pre-iOS 8 code.
|
|
|
|
// This toggles the nav and status bar. Since iOS7 and pre-iOS7 behave weirdly different,
|
|
// this might look rather hacky, but hiding the navbar under iOS6 leads to some ugly
|
|
// animation effect which is avoided by simply hiding the navbar setting it's alpha to 0. // moritzh
|
|
|
|
if (self.navigationController.navigationBar.alpha == 0 || self.navigationController.navigationBarHidden ){
|
|
|
|
[UIView animateWithDuration:0.35 animations:^{
|
|
[self.navigationController setNavigationBarHidden:NO animated:NO];
|
|
|
|
if ([self respondsToSelector:@selector(prefersStatusBarHidden)]) {
|
|
[self setNeedsStatusBarAppearanceUpdate];
|
|
} else {
|
|
#pragma clang diagnostic push
|
|
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
|
[[UIApplication sharedApplication] setStatusBarHidden:NO];
|
|
#pragma clang diagnostic pop
|
|
}
|
|
|
|
} completion:^(BOOL __unused finished) {
|
|
[self fitImageViewFrame];
|
|
|
|
}];
|
|
} else {
|
|
[UIView animateWithDuration:0.35 animations:^{
|
|
[self.navigationController setNavigationBarHidden:YES animated:NO];
|
|
|
|
if ([self respondsToSelector:@selector(prefersStatusBarHidden)]) {
|
|
[self setNeedsStatusBarAppearanceUpdate];
|
|
} else {
|
|
#pragma clang diagnostic push
|
|
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
|
[[UIApplication sharedApplication] setStatusBarHidden:YES];
|
|
#pragma clang diagnostic pop
|
|
}
|
|
|
|
} completion:^(BOOL __unused finished) {
|
|
[self fitImageViewFrame];
|
|
|
|
}];
|
|
}
|
|
|
|
}
|
|
|
|
#pragma mark - Helpers
|
|
|
|
- (UIView *)firstAnnotationThatIsNotBlur {
|
|
for (BITImageAnnotation *annotation in self.imageView.subviews){
|
|
if (![annotation isKindOfClass:[BITBlurImageAnnotation class]]){
|
|
return annotation;
|
|
}
|
|
}
|
|
|
|
return self.imageView;
|
|
}
|
|
|
|
- (BOOL)canDrawNewAnnotation {
|
|
return [self.editingControls selectedSegmentIndex] != UISegmentedControlNoSegment;
|
|
}
|
|
@end
|
|
|
|
#endif /* HOCKEYSDK_FEATURE_FEEDBACK */
|