Swiftgram/submodules/LegacyComponents/Sources/TGCameraPhotoPreviewController.m
Ilya Laktyushin 7741978b7e Video Editing
2020-05-23 13:26:53 +03:00

1194 lines
44 KiB
Objective-C

#import "TGCameraPhotoPreviewController.h"
#import "LegacyComponentsInternal.h"
#import <objc/runtime.h>
#import <LegacyComponents/PGCameraShotMetadata.h>
#import <LegacyComponents/PGPhotoEditorValues.h>
#import <LegacyComponents/TGPhotoEditorUtils.h>
#import <LegacyComponents/UIImage+TG.h>
#import "TGImageView.h"
#import <LegacyComponents/TGModernGalleryZoomableScrollView.h>
#import <LegacyComponents/TGFullscreenContainerView.h>
#import "TGPhotoEditorController.h"
#import "TGPhotoEditorTabController.h"
#import "TGPhotoToolbarView.h"
#import "TGPhotoEditorButton.h"
#import <LegacyComponents/TGPhotoEditorAnimation.h>
#import "TGSecretTimerMenu.h"
#import <LegacyComponents/TGMediaAssetsLibrary.h>
#import <LegacyComponents/UIImage+TGMediaEditableItem.h>
#import <LegacyComponents/TGPaintingData.h>
#import "TGPhotoEditorInterfaceAssets.h"
#import "TGPhotoCaptionInputMixin.h"
@interface TGCameraPhotoPreviewWrapperView : UIView
@end
@implementation TGCameraPhotoPreviewWrapperView
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
UIView *view = [super hitTest:point withEvent:event];
if (view != self)
return view;
return nil;
}
@end
@interface TGCameraPhotoPreviewController () <UIScrollViewDelegate>
{
TGMediaEditingContext *_editingContext;
UIImage *_image;
PGCameraShotMetadata *_metadata;
TGCameraPhotoPreviewWrapperView *_wrapperView;
UIView *_transitionParentView;
TGModernGalleryZoomableScrollView *_scrollView;
TGImageView *_imageView;
UIView *_temporaryRepView;
CGSize _imageSize;
UIImageView *_arrowView;
UILabel *_recipientLabel;
TGPhotoToolbarView *_portraitToolbarView;
TGPhotoToolbarView *_landscapeToolbarView;
bool _transitionInProgress;
bool _dismissing;
bool _appeared;
NSString *_recipientName;
NSString *_backButtonTitle;
NSString *_doneButtonTitle;
TGPhotoCaptionInputMixin *_captionMixin;
CGFloat _scrollViewVerticalOffset;
bool _saveCapturedMedia;
bool _saveEditedPhotos;
id<LegacyComponentsContext> _context;
}
@property (nonatomic, weak) TGPhotoEditorController *editorController;
@end
@implementation TGCameraPhotoPreviewController
- (instancetype)initWithContext:(id<LegacyComponentsContext>)context image:(UIImage *)image metadata:(PGCameraShotMetadata *)metadata recipientName:(NSString *)recipientName saveCapturedMedia:(bool)saveCapturedMedia saveEditedPhotos:(bool)saveEditedPhotos
{
return [self initWithContext:context image:image metadata:metadata recipientName:recipientName backButtonTitle:TGLocalized(@"Camera.Retake") doneButtonTitle:TGLocalized(@"MediaPicker.Send") saveCapturedMedia:saveCapturedMedia saveEditedPhotos:saveEditedPhotos];
}
- (instancetype)initWithContext:(id<LegacyComponentsContext>)context image:(UIImage *)image metadata:(PGCameraShotMetadata *)metadata recipientName:(NSString *)recipientName backButtonTitle:(NSString *)backButtonTitle doneButtonTitle:(NSString *)doneButtonTitle saveCapturedMedia:(bool)saveCapturedMedia saveEditedPhotos:(bool)saveEditedPhotos
{
self = [super initWithContext:context];
if (self != nil)
{
_context = context;
_image = image;
_metadata = metadata;
_imageSize = image.size;
_recipientName = recipientName;
_editingContext = [[TGMediaEditingContext alloc] init];
self.automaticallyManageScrollViewInsets = false;
_backButtonTitle = backButtonTitle;
_doneButtonTitle = doneButtonTitle;
_saveCapturedMedia = saveCapturedMedia;
_saveEditedPhotos = saveEditedPhotos;
}
return self;
}
- (void)loadView
{
[super loadView];
object_setClass(self.view, [TGFullscreenContainerView class]);
if (iosMajorVersion() >= 11)
self.view.accessibilityIgnoresInvertColors = true;
self.view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
self.view.backgroundColor = [UIColor clearColor];
_transitionParentView = [[UIView alloc] initWithFrame:self.view.bounds];
_transitionParentView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
[self.view addSubview:_transitionParentView];
CGRect containerFrame = self.view.bounds;
CGSize fittedSize = TGScaleToSize(_image.size, containerFrame.size);
_scrollView = [[TGModernGalleryZoomableScrollView alloc] initWithFrame:self.view.bounds hasDoubleTap:true];
_scrollView.clipsToBounds = false;
_scrollView.delegate = self;
_scrollView.showsHorizontalScrollIndicator = false;
_scrollView.showsVerticalScrollIndicator = false;
[self.view addSubview:_scrollView];
_imageView = [[TGImageView alloc] initWithFrame:CGRectMake(0, 0, fittedSize.width, fittedSize.height)];
[self.view addSubview:_imageView];
__weak TGCameraPhotoPreviewController *weakSelf = self;
void (^fadeOutRepView)(void) = ^
{
__strong TGCameraPhotoPreviewController *strongSelf = weakSelf;
if (strongSelf == nil)
return;
if (strongSelf->_temporaryRepView == nil)
return;
UIView *repView = strongSelf->_temporaryRepView;
strongSelf->_temporaryRepView = nil;
[UIView animateWithDuration:0.2f animations:^
{
repView.alpha = 0.0f;
} completion:^(__unused BOOL finished)
{
[repView removeFromSuperview];
}];
};
TGMediaEditingContext *editingContext = _editingContext;
SSignal *assetSignal = [SSignal single:_image];
SSignal *imageSignal = assetSignal;
if (editingContext != nil)
{
imageSignal = [[[editingContext imageSignalForItem:_image] deliverOn:[SQueue mainQueue]] mapToSignal:^SSignal *(id result)
{
__strong TGCameraPhotoPreviewController *strongSelf = weakSelf;
if (strongSelf == nil)
return [SSignal complete];
if (result == nil)
{
return [[assetSignal deliverOn:[SQueue mainQueue]] afterNext:^(__unused id next)
{
fadeOutRepView();
}];
}
else if ([result isKindOfClass:[UIView class]])
{
[strongSelf _setTemporaryRepView:result];
return [[SSignal single:nil] deliverOn:[SQueue mainQueue]];
}
else
{
return [[[SSignal single:result] deliverOn:[SQueue mainQueue]] afterNext:^(__unused id next)
{
fadeOutRepView();
}];
}
}];
}
[_imageView setSignal:[[imageSignal deliverOn:[SQueue mainQueue]] afterNext:^(id next)
{
__strong TGCameraPhotoPreviewController *strongSelf = weakSelf;
if (strongSelf == nil)
return;
if ([next isKindOfClass:[UIImage class]])
strongSelf->_imageSize = ((UIImage *)next).size;
[strongSelf reset];
}]];
_wrapperView = [[TGCameraPhotoPreviewWrapperView alloc] initWithFrame:CGRectZero];
[self.view addSubview:_wrapperView];
void (^cancelPressed)(void) = ^
{
__strong TGCameraPhotoPreviewController *strongSelf = weakSelf;
if (strongSelf == nil)
return;
if (strongSelf.retakePressed != nil)
strongSelf.retakePressed();
[strongSelf transitionOutWithCompletion:^
{
[strongSelf dismiss];
}];
};
void (^donePressed)(void) = ^
{
__strong TGCameraPhotoPreviewController *strongSelf = weakSelf;
if (strongSelf == nil || strongSelf->_dismissing)
return;
[strongSelf.view.window endEditing:true];
strongSelf->_dismissing = true;
strongSelf.view.userInteractionEnabled = false;
if (strongSelf.shouldStoreAssets && [strongSelf->_editingContext timerForItem:strongSelf->_image] == nil)
{
if (strongSelf->_saveCapturedMedia)
[[[TGMediaAssetsLibrary sharedLibrary] saveAssetWithImage:strongSelf->_image] startWithNext:nil];
if (strongSelf->_saveEditedPhotos)
{
[[[[[[editingContext fullSizeImageUrlForItem:strongSelf->_image] filter:^bool(id result)
{
return [result isKindOfClass:[NSURL class]];
}] startOn:[SQueue concurrentDefaultQueue]] deliverOn:[SQueue mainQueue]] mapToSignal:^SSignal *(NSURL *url)
{
return [[[TGMediaAssetsLibrary sharedLibrary] saveAssetWithImageAtUrl:url] onCompletion:^
{
__strong TGMediaEditingContext *strongEditingContext = editingContext;
[strongEditingContext description];
}];
}] startWithNext:nil];
}
}
SSignal *originalSignal = [[[SSignal single:strongSelf->_image] map:^id(UIImage *image)
{
return TGPhotoEditorCrop(image, nil, UIImageOrientationUp, 0, CGRectMake(0, 0, image.size.width, image.size.height), false, CGSizeMake(1280, 1280), image.size, true);
}] startOn:[SQueue concurrentDefaultQueue]];
SSignal *imageSignal = originalSignal;
if (editingContext != nil)
{
imageSignal = [[[[editingContext imageSignalForItem:strongSelf->_image withUpdates:true] filter:^bool(id result)
{
return result == nil || ([result isKindOfClass:[UIImage class]] && !((UIImage *)result).degraded);
}] take:1] mapToSignal:^SSignal *(id result)
{
if (result == nil)
{
return originalSignal;
}
else if ([result isKindOfClass:[UIImage class]])
{
UIImage *image = (UIImage *)result;
image.edited = true;
return [SSignal single:image];
}
return [SSignal complete];
}];
}
NSString *caption = [editingContext captionForItem:strongSelf->_image];
NSArray *entities = [editingContext entitiesForItem:strongSelf->_image];
NSArray *stickers = [editingContext adjustmentsForItem:strongSelf->_image].paintingData.stickers;
NSNumber *timer = [editingContext timerForItem:strongSelf->_image];
[[imageSignal deliverOn:[SQueue mainQueue]] startWithNext:^(UIImage *result)
{
strongSelf.sendPressed(self, result, caption, entities, stickers, timer);
strongSelf.view.userInteractionEnabled = true;
strongSelf->_dismissing = false;
}];
};
void (^tabPressed)(TGPhotoEditorTab) = ^(TGPhotoEditorTab tab)
{
__strong TGCameraPhotoPreviewController *strongSelf = weakSelf;
if (strongSelf == nil)
return;
if (tab == TGPhotoEditorTimerTab)
[strongSelf openTimerSetup];
else
[strongSelf presentPhotoEditorWithTab:tab];
};
TGPhotoEditorTab tabs = TGPhotoEditorCropTab;
if (iosMajorVersion() >= 7)
{
tabs |= TGPhotoEditorPaintTab;
tabs |= TGPhotoEditorToolsTab;
}
if (self.hasTimer)
tabs |= TGPhotoEditorTimerTab;
_captionMixin = [[TGPhotoCaptionInputMixin alloc] initWithKeyCommandController:[_context keyCommandController]];
_captionMixin.panelParentView = ^UIView *
{
__strong TGCameraPhotoPreviewController *strongSelf = weakSelf;
return strongSelf->_wrapperView;
};
_captionMixin.panelFocused = ^
{
__strong TGCameraPhotoPreviewController *strongSelf = weakSelf;
if (strongSelf == nil)
return;
[strongSelf setInterfaceHidden:true animated:true];
};
_captionMixin.finishedWithCaption = ^(NSString *caption, NSArray *entities)
{
__strong TGCameraPhotoPreviewController *strongSelf = weakSelf;
if (strongSelf == nil)
return;
[strongSelf->_editingContext setCaption:caption entities:entities forItem:strongSelf->_image];
PGPhotoEditorValues *values = (PGPhotoEditorValues *)[strongSelf->_editingContext adjustmentsForItem:strongSelf->_image];
[strongSelf updateEditorButtonsForEditorValues:values];
[strongSelf setInterfaceHidden:false animated:true];
};
_captionMixin.keyboardHeightChanged = ^(CGFloat keyboardHeight, NSTimeInterval duration, NSInteger animationCurve)
{
__strong TGCameraPhotoPreviewController *strongSelf = weakSelf;
if (strongSelf == nil)
return;
CGFloat offset = 0.0f;
if (keyboardHeight > 0)
offset = -keyboardHeight / 2.0f;
[UIView animateWithDuration:duration delay:0.0f options:animationCurve animations:^
{
[strongSelf setScrollViewVerticalOffset:offset];
} completion:nil];
};
_captionMixin.suggestionContext = self.suggestionContext;
[_captionMixin createInputPanelIfNeeded];
_portraitToolbarView = [[TGPhotoToolbarView alloc] initWithBackButton:TGPhotoEditorBackButtonBack doneButton:TGPhotoEditorDoneButtonSend solidBackground:false];
[_portraitToolbarView setToolbarTabs:tabs animated:false];
_portraitToolbarView.cancelPressed = cancelPressed;
_portraitToolbarView.donePressed = donePressed;
_portraitToolbarView.tabPressed = tabPressed;
[_wrapperView addSubview:_portraitToolbarView];
_landscapeToolbarView = [[TGPhotoToolbarView alloc] initWithBackButton:TGPhotoEditorBackButtonBack doneButton:TGPhotoEditorDoneButtonSend solidBackground:false];
[_landscapeToolbarView setToolbarTabs:tabs animated:false];
_landscapeToolbarView.cancelPressed = cancelPressed;
_landscapeToolbarView.donePressed = donePressed;
_landscapeToolbarView.tabPressed = tabPressed;
[_wrapperView addSubview:_landscapeToolbarView];
if (_recipientName.length > 0)
{
_arrowView = [[UIImageView alloc] initWithImage:TGComponentsImageNamed(@"PhotoPickerArrow")];
_arrowView.alpha = 0.45f;
[_wrapperView addSubview:_arrowView];
_recipientLabel = [[UILabel alloc] init];
_recipientLabel.backgroundColor = [UIColor clearColor];
_recipientLabel.font = TGBoldSystemFontOfSize(13.0f);
_recipientLabel.textColor = UIColorRGBA(0xffffff, 0.45f);
_recipientLabel.text = _recipientName;
_recipientLabel.userInteractionEnabled = false;
[_recipientLabel sizeToFit];
[_wrapperView addSubview:_recipientLabel];
}
}
- (void)openTimerSetup
{
id<TGMediaEditableItem> editableMediaItem = _image;
NSString *description = TGLocalized(@"SecretTimer.ImageDescription");
NSString *lastValueKey = @"mediaPickerLastTimerValue_v0";
NSNumber *value = [_editingContext timerForItem:editableMediaItem];
if (value == nil)
value = [[NSUserDefaults standardUserDefaults] objectForKey:lastValueKey];
__strong TGCameraPhotoPreviewController *weakSelf = self;
[TGSecretTimerMenu presentInParentController:self context:_context dark:true description:description values:[TGSecretTimerMenu secretMediaTimerValues] value:value completed:^(NSNumber *value)
{
__strong TGCameraPhotoPreviewController *strongSelf = weakSelf;
if (strongSelf != nil)
{
if (value == nil)
[[NSUserDefaults standardUserDefaults] removeObjectForKey:lastValueKey];
else
[[NSUserDefaults standardUserDefaults] setObject:value forKey:lastValueKey];
[strongSelf->_editingContext setTimer:value forItem:editableMediaItem];
PGPhotoEditorValues *values = (PGPhotoEditorValues *)[strongSelf->_editingContext adjustmentsForItem:strongSelf->_image];
[strongSelf updateEditorButtonsForEditorValues:values];
}
} dismissed:^
{
__strong TGCameraPhotoPreviewController *strongSelf = weakSelf;
if (strongSelf != nil)
[strongSelf setAllInterfaceHidden:false animated:true];
} sourceView:self.view sourceRect:^CGRect
{
__strong TGCameraPhotoPreviewController *strongSelf = weakSelf;
if (strongSelf == nil)
return CGRectZero;
return [[strongSelf timerButton] convertRect:[strongSelf timerButton].bounds toView:strongSelf.view];
}];
if (!TGIsPad())
[self setAllInterfaceHidden:true animated:true];
}
- (UIView *)timerButton
{
if (UIInterfaceOrientationIsPortrait(self.interfaceOrientation))
return [_portraitToolbarView buttonForTab:TGPhotoEditorTimerTab];
else
return [_landscapeToolbarView buttonForTab:TGPhotoEditorTimerTab];
}
- (void)_setTemporaryRepView:(UIView *)view
{
[_temporaryRepView removeFromSuperview];
_temporaryRepView = view;
_imageSize = TGScaleToSize(view.frame.size, self.view.frame.size);
view.hidden = _imageView.hidden;
view.frame = CGRectMake((self.view.frame.size.width - _imageSize.width) / 2.0f, (self.view.frame.size.height - _imageSize.height) / 2.0f, _imageSize.width, _imageSize.height);
[self.view insertSubview:view belowSubview:_wrapperView];
}
- (void)setInterfaceHidden:(bool)hidden animated:(bool)animated
{
CGFloat alpha = (hidden ? 0.0f : 1.0f);
if (animated)
{
[UIView animateWithDuration:0.2 delay:0.0 options:UIViewAnimationOptionCurveLinear | UIViewAnimationOptionBeginFromCurrentState animations:^
{
_arrowView.alpha = alpha * 0.45f;
_recipientLabel.alpha = alpha;
} completion:nil];
}
else
{
_arrowView.alpha = alpha * 0.45f;
_recipientLabel.alpha = alpha;
}
}
- (void)setAllInterfaceHidden:(bool)hidden animated:(bool)animated
{
CGFloat alpha = (hidden ? 0.0f : 1.0f);
if (animated)
{
[UIView animateWithDuration:0.2 delay:0.0 options:UIViewAnimationOptionCurveLinear | UIViewAnimationOptionBeginFromCurrentState animations:^
{
_arrowView.alpha = alpha * 0.45f;
_recipientLabel.alpha = alpha;
_portraitToolbarView.alpha = alpha;
_landscapeToolbarView.alpha = alpha;
_captionMixin.inputPanel.alpha = alpha;
} completion:^(BOOL finished)
{
if (finished)
{
_portraitToolbarView.userInteractionEnabled = !hidden;
_landscapeToolbarView.userInteractionEnabled = !hidden;
_captionMixin.inputPanel.userInteractionEnabled = !hidden;
}
}];
}
else
{
_arrowView.alpha = alpha * 0.45f;
_recipientLabel.alpha = alpha;
_portraitToolbarView.alpha = alpha;
_portraitToolbarView.userInteractionEnabled = !hidden;
_landscapeToolbarView.alpha = alpha;
_landscapeToolbarView.userInteractionEnabled = !hidden;
_captionMixin.inputPanel.alpha = alpha;
_captionMixin.inputPanel.userInteractionEnabled = !hidden;
}
}
- (void)dismiss
{
if (self.navigationController != nil)
{
TGOverlayController *parentController = (TGOverlayController *)self.navigationController.parentViewController;
[parentController dismiss];
}
else if (self.overlayWindow != nil)
{
[super dismiss];
}
else
{
[self.view removeFromSuperview];
[self removeFromParentViewController];
}
}
- (UIRectEdge)preferredScreenEdgesDeferringSystemGestures
{
if (self.childViewControllers.count > 0)
return [self.childViewControllers.lastObject preferredScreenEdgesDeferringSystemGestures];
return [super preferredScreenEdgesDeferringSystemGestures];
}
- (BOOL)prefersStatusBarHidden
{
if (self.childViewControllers.count > 0)
return [self.childViewControllers.lastObject prefersStatusBarHidden];
return [super prefersStatusBarHidden];
}
- (UIBarStyle)requiredNavigationBarStyle
{
return UIBarStyleDefault;
}
- (bool)navigationBarShouldBeHidden
{
return true;
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self transitionIn];
}
#pragma mark - Transition
- (void)transitionIn
{
[_context setApplicationStatusBarAlpha:0.0f];
if (_appeared)
return;
_appeared = true;
_transitionInProgress = true;
_captionMixin.inputPanel.alpha = 0.0f;
_portraitToolbarView.alpha = 0.0f;
_landscapeToolbarView.alpha = 0.0f;
_arrowView.alpha = 0.0f;
_recipientLabel.alpha = 0.0f;
[UIView animateWithDuration:0.3f delay:0.1f options:UIViewAnimationOptionCurveLinear animations:^
{
_captionMixin.inputPanel.alpha = 1.0f;
_portraitToolbarView.alpha = 1.0f;
_landscapeToolbarView.alpha = 1.0f;
_arrowView.alpha = 0.45f;
_recipientLabel.alpha = 1.0f;
} completion:nil];
CGSize referenceSize = [self referenceViewSizeForOrientation:self.interfaceOrientation];
CGRect referenceFrame = CGRectZero;
if (self.beginTransitionIn != nil)
referenceFrame = self.beginTransitionIn();
if (self.interfaceOrientation == UIInterfaceOrientationLandscapeLeft)
{
referenceFrame = CGRectMake(referenceSize.width - referenceFrame.size.height - referenceFrame.origin.y,
referenceFrame.origin.x,
referenceFrame.size.height, referenceFrame.size.width);
}
else if (self.interfaceOrientation == UIInterfaceOrientationLandscapeRight)
{
referenceFrame = CGRectMake(referenceFrame.origin.y,
referenceSize.height - referenceFrame.size.width - referenceFrame.origin.x,
referenceFrame.size.height, referenceFrame.size.width);
}
CGRect containerFrame = CGRectMake(0, 0, referenceSize.width, referenceSize.height);
CGSize fittedSize = TGScaleToSize(_imageView.image.size, containerFrame.size);
CGRect targetFrame = CGRectMake(containerFrame.origin.x + (containerFrame.size.width - fittedSize.width) / 2,
containerFrame.origin.y + (containerFrame.size.height - fittedSize.height) / 2,
fittedSize.width,
fittedSize.height);
CGFloat referenceAspectRatio = referenceFrame.size.width / referenceFrame.size.height;
CGFloat targetAspectRatio = targetFrame.size.width / targetFrame.size.height;
if (ABS(targetAspectRatio - referenceAspectRatio) > 0.03f)
{
CGSize newSize = CGSizeZero;
if (referenceFrame.size.width > referenceFrame.size.height)
newSize = CGSizeMake(referenceFrame.size.width, _imageView.image.size.height * referenceFrame.size.width / _imageView.image.size.width);
else
newSize = CGSizeMake(_imageView.image.size.width * referenceFrame.size.height / _imageView.image.size.height, referenceFrame.size.height);
referenceFrame = CGRectMake(CGRectGetMidX(referenceFrame) - newSize.width / 2,
CGRectGetMidY(referenceFrame) - newSize.height / 2,
newSize.width, newSize.height);
}
_imageView.frame = referenceFrame;
POPSpringAnimation *animation = [TGPhotoEditorAnimation prepareTransitionAnimationForPropertyNamed:kPOPViewFrame];
animation.fromValue = [NSValue valueWithCGRect:referenceFrame];
animation.toValue = [NSValue valueWithCGRect:targetFrame];
animation.completionBlock = ^(__unused POPAnimation *animation, __unused BOOL finished)
{
_transitionInProgress = false;
[_scrollView addSubview:_imageView];
_imageView.frame = CGRectMake(0, 0, _scrollView.frame.size.width, _scrollView.frame.size.height);
self.view.backgroundColor = [UIColor blackColor];
[self reset];
if (self.finishedTransitionIn != nil)
self.finishedTransitionIn();
};
[_imageView pop_addAnimation:animation forKey:@"frame"];
}
- (void)transitionOutWithCompletion:(void (^)(void))completion
{
_transitionInProgress = true;
self.view.backgroundColor = [UIColor clearColor];
CGRect frame = [self.view convertRect:_imageView.frame fromView:_scrollView];
[self.view addSubview:_imageView];
_imageView.frame = frame;
CGSize referenceSize = [self referenceViewSizeForOrientation:self.interfaceOrientation];
CGRect referenceFrame = _imageView.frame;
if (self.interfaceOrientation == UIInterfaceOrientationLandscapeLeft)
{
referenceFrame = CGRectMake(referenceSize.height - referenceFrame.size.height - referenceFrame.origin.y,
referenceFrame.origin.x,
referenceFrame.size.height, referenceFrame.size.width);
}
else if (self.interfaceOrientation == UIInterfaceOrientationLandscapeRight)
{
referenceFrame = CGRectMake(referenceFrame.origin.y,
referenceSize.width - referenceFrame.size.width - referenceFrame.origin.x,
referenceFrame.size.height, referenceFrame.size.width);
}
CGRect targetFrame = CGRectZero;
if (self.beginTransitionOut != nil)
targetFrame = self.beginTransitionOut(referenceFrame);
if (self.interfaceOrientation == UIInterfaceOrientationLandscapeLeft)
{
targetFrame = CGRectMake(referenceSize.width - targetFrame.size.height - targetFrame.origin.y,
targetFrame.origin.x,
targetFrame.size.height, targetFrame.size.width);
}
else if (self.interfaceOrientation == UIInterfaceOrientationLandscapeRight)
{
targetFrame = CGRectMake(targetFrame.origin.y,
referenceSize.height - targetFrame.size.width - targetFrame.origin.x,
targetFrame.size.height, targetFrame.size.width);
}
CGFloat referenceAspectRatio = referenceFrame.size.width / referenceFrame.size.height;
CGFloat targetAspectRatio = targetFrame.size.width / targetFrame.size.height;
if (ABS(targetAspectRatio - referenceAspectRatio) > 0.03f)
{
CGSize newSize = CGSizeZero;
if (targetFrame.size.width > targetFrame.size.height)
newSize = CGSizeMake(targetFrame.size.width, _imageView.image.size.height * targetFrame.size.width / _imageView.image.size.width);
else
newSize = CGSizeMake(_imageView.image.size.width * targetFrame.size.height / _imageView.image.size.height, targetFrame.size.height);
targetFrame = CGRectMake(CGRectGetMidX(targetFrame) - newSize.width / 2,
CGRectGetMidY(targetFrame) - newSize.height / 2,
newSize.width, newSize.height);
}
POPSpringAnimation *animation = [TGPhotoEditorAnimation prepareTransitionAnimationForPropertyNamed:kPOPViewFrame];
animation.fromValue = [NSValue valueWithCGRect:_imageView.frame];
animation.toValue = [NSValue valueWithCGRect:targetFrame];
[_imageView pop_addAnimation:animation forKey:@"frame"];
[UIView animateWithDuration:0.3f animations:^
{
_imageView.alpha = 0.0f;
_portraitToolbarView.alpha = 0.0f;
_landscapeToolbarView.alpha = 0.0f;
_captionMixin.inputPanel.alpha = 0.0f;
_arrowView.alpha = 0.0f;
_recipientLabel.alpha = 0.0f;
} completion:^(__unused BOOL finished)
{
if (completion != nil)
completion();
}];
}
#pragma mark - Scroll View
- (void)scrollViewDidZoom:(UIScrollView *)__unused scrollView
{
[self adjustZoom];
}
- (void)scrollViewDidEndZooming:(UIScrollView *)__unused scrollView withView:(UIView *)__unused view atScale:(CGFloat)__unused scale
{
[self adjustZoom];
if (_scrollView.zoomScale < _scrollView.normalZoomScale - FLT_EPSILON)
{
[TGHacks setAnimationDurationFactor:0.5f];
[_scrollView setZoomScale:_scrollView.normalZoomScale animated:true];
[TGHacks setAnimationDurationFactor:1.0f];
}
}
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView
{
if (_imageView.superview == scrollView)
return _imageView;
return nil;
}
- (CGSize)contentSize
{
return _imageSize;
}
- (void)reset
{
CGSize contentSize = [self contentSize];
_scrollView.minimumZoomScale = 1.0f;
_scrollView.maximumZoomScale = 1.0f;
_scrollView.normalZoomScale = 1.0f;
_scrollView.zoomScale = 1.0f;
_scrollView.contentSize = contentSize;
_imageView.frame = CGRectMake(0.0f, 0.0f, contentSize.width, contentSize.height);
[self adjustZoom];
_scrollView.zoomScale = _scrollView.normalZoomScale;
}
- (void)adjustZoom
{
CGSize contentSize = [self contentSize];
CGSize boundsSize = _scrollView.frame.size;
if (contentSize.width < FLT_EPSILON || contentSize.height < FLT_EPSILON || boundsSize.width < FLT_EPSILON || boundsSize.height < FLT_EPSILON)
return;
CGFloat scaleWidth = boundsSize.width / contentSize.width;
CGFloat scaleHeight = boundsSize.height / contentSize.height;
CGFloat minScale = MIN(scaleWidth, scaleHeight);
CGFloat maxScale = MAX(scaleWidth, scaleHeight);
maxScale = MAX(maxScale, minScale * 3.0f);
if (ABS(maxScale - minScale) < 0.01f)
maxScale = minScale;
if (_scrollView.minimumZoomScale != 0.05f)
_scrollView.minimumZoomScale = 0.05f;
if (_scrollView.normalZoomScale != minScale)
_scrollView.normalZoomScale = minScale;
if (_scrollView.maximumZoomScale != maxScale)
_scrollView.maximumZoomScale = maxScale;
CGRect contentFrame = _imageView.frame;
if (boundsSize.width > contentFrame.size.width)
contentFrame.origin.x = (boundsSize.width - contentFrame.size.width) / 2.0f;
else
contentFrame.origin.x = 0;
if (boundsSize.height > contentFrame.size.height)
contentFrame.origin.y = (boundsSize.height - contentFrame.size.height) / 2.0f;
else
contentFrame.origin.y = 0;
_imageView.frame = contentFrame;
}
#pragma mark -
- (void)updateEditorButtonsForEditorValues:(PGPhotoEditorValues *)editorValues
{
TGPhotoEditorTab highlightedButtons = [TGPhotoEditorTabController highlightedButtonsForEditorValues:editorValues forAvatar:false];
TGPhotoEditorButton *timerButton = [_portraitToolbarView buttonForTab:TGPhotoEditorTimerTab];
if (timerButton != nil)
{
NSInteger value = [[_editingContext timerForItem:_image] integerValue];
UIImage *defaultIcon = [TGPhotoEditorInterfaceAssets timerIconForValue:0];
UIImage *icon = [TGPhotoEditorInterfaceAssets timerIconForValue:value];
[timerButton setIconImage:defaultIcon activeIconImage:icon];
timerButton = [_landscapeToolbarView buttonForTab:TGPhotoEditorTimerTab];
[timerButton setIconImage:defaultIcon activeIconImage:icon];
if (value > 0)
highlightedButtons |= TGPhotoEditorTimerTab;
}
[_portraitToolbarView setEditButtonsHighlighted:highlightedButtons];
[_landscapeToolbarView setEditButtonsHighlighted:highlightedButtons];
}
- (UIView *)transitionContentView
{
if (_temporaryRepView != nil)
return _temporaryRepView;
return _imageView;
}
- (CGRect)transitionViewContentRect
{
UIView *contentView = [self transitionContentView];
return [self.view convertRect:contentView.frame fromView:contentView.superview];
}
- (void)presentPhotoEditorWithTab:(TGPhotoEditorTab)tab
{
__weak TGCameraPhotoPreviewController *weakSelf = self;
id<TGMediaEditableItem> editableMediaItem = _image;
UIView *referenceView = [self transitionContentView];
CGRect refFrame = [self transitionViewContentRect];
UIImage *screenImage = nil;
if ([referenceView isKindOfClass:[UIImageView class]])
screenImage = [(UIImageView *)referenceView image];
PGPhotoEditorValues *editorValues = (PGPhotoEditorValues *)[_editingContext adjustmentsForItem:_image];
NSString *caption = [_editingContext captionForItem:_image];
TGPhotoEditorController *controller = [[TGPhotoEditorController alloc] initWithContext:_context item:editableMediaItem intent:TGPhotoEditorControllerFromCameraIntent adjustments:editorValues caption:caption screenImage:screenImage availableTabs:_portraitToolbarView.currentTabs selectedTab:tab];
controller.editingContext = _editingContext;
self.editorController = controller;
controller.metadata = _metadata;
controller.suggestionContext = self.suggestionContext;
controller.didFinishRenderingFullSizeImage = ^(UIImage *image)
{
__strong TGCameraPhotoPreviewController *strongSelf = weakSelf;
if (strongSelf == nil)
return;
[strongSelf->_editingContext setFullSizeImage:image forItem:strongSelf->_image];
};
controller.willFinishEditing = ^(PGPhotoEditorValues *editorValues, id temporaryRep, bool hasChanges)
{
__strong TGCameraPhotoPreviewController *strongSelf = weakSelf;
if (strongSelf == nil)
return;
if (hasChanges)
{
[strongSelf->_editingContext setAdjustments:editorValues forItem:strongSelf->_image];
[strongSelf->_editingContext setTemporaryRep:temporaryRep forItem:strongSelf->_image];
}
};
controller.didFinishEditing = ^(PGPhotoEditorValues *editorValues, UIImage *resultImage, __unused UIImage *thumbnailImage, bool hasChanges)
{
#ifdef DEBUG
if (editorValues != nil && hasChanges)
NSAssert(resultImage != nil, @"resultImage should not be nil");
#endif
__strong TGCameraPhotoPreviewController *strongSelf = weakSelf;
if (strongSelf == nil)
return;
if (hasChanges)
[strongSelf->_editingContext setImage:resultImage thumbnailImage:nil forItem:strongSelf->_image synchronous:false];
PGPhotoEditorValues *values = !hasChanges ? (PGPhotoEditorValues *)[strongSelf->_editingContext adjustmentsForItem:strongSelf->_image] : editorValues;
[strongSelf updateEditorButtonsForEditorValues:values];
[strongSelf reset];
};
controller.captionSet = ^(NSString *caption, NSArray *entities)
{
__strong TGCameraPhotoPreviewController *strongSelf = weakSelf;
if (strongSelf == nil)
return;
[strongSelf reset];
[strongSelf->_editingContext setCaption:caption entities:entities forItem:strongSelf->_image];
};
controller.requestToolbarsHidden = ^(bool hidden, bool animated)
{
__strong TGCameraPhotoPreviewController *strongSelf = weakSelf;
if (strongSelf == nil)
return;
[strongSelf setToolbarsHidden:hidden animated:animated];
};
controller.beginTransitionIn = ^UIView *(CGRect *referenceFrame, UIView **parentView)
{
__strong TGCameraPhotoPreviewController *strongSelf = weakSelf;
if (strongSelf == nil)
return nil;
[strongSelf editorTransitionIn];
if (strongSelf.photoEditorShown != nil)
strongSelf.photoEditorShown();
strongSelf->_imageView.hidden = true;
strongSelf->_temporaryRepView.hidden = true;
*parentView = strongSelf->_transitionParentView;
*referenceFrame = refFrame;
[strongSelf reset];
if (iosMajorVersion() >= 7)
[strongSelf setNeedsStatusBarAppearanceUpdate];
else {
[_context setStatusBarHidden:true withAnimation:UIStatusBarAnimationNone];
}
return referenceView;
};
controller.beginTransitionOut = ^UIView *(CGRect *referenceFrame, UIView **parentView)
{
__strong TGCameraPhotoPreviewController *strongSelf = weakSelf;
if (strongSelf == nil)
return nil;
[strongSelf editorTransitionOut];
*parentView = strongSelf->_transitionParentView;
*referenceFrame = [strongSelf transitionViewContentRect];
return [strongSelf transitionContentView];
};
controller.finishedTransitionOut = ^(__unused bool saved)
{
__strong TGCameraPhotoPreviewController *strongSelf = weakSelf;
if (strongSelf == nil)
return;
if (strongSelf.photoEditorHidden != nil)
strongSelf.photoEditorHidden();
strongSelf->_imageView.hidden = false;
strongSelf->_temporaryRepView.hidden = false;
if (iosMajorVersion() >= 7)
[strongSelf setNeedsStatusBarAppearanceUpdate];
else {
[_context setStatusBarHidden:false withAnimation:UIStatusBarAnimationNone];
}
};
controller.requestThumbnailImage = ^(id<TGMediaEditableItem> editableItem)
{
return [editableItem thumbnailImageSignal];
};
controller.requestOriginalScreenSizeImage = ^(id<TGMediaEditableItem> editableItem, NSTimeInterval position)
{
return [editableItem screenImageSignal:position];
};
controller.requestOriginalFullSizeImage = ^(id<TGMediaEditableItem> editableItem, NSTimeInterval position)
{
return [editableItem originalImageSignal:position];
};
[self addChildViewController:controller];
[self.view addSubview:controller.view];
controller.view.clipsToBounds = true;
}
- (void)setToolbarsHidden:(bool)hidden animated:(bool)animated
{
if (hidden)
{
[_portraitToolbarView transitionOutAnimated:animated transparent:true hideOnCompletion:false];
[_landscapeToolbarView transitionOutAnimated:animated transparent:true hideOnCompletion:false];
}
else
{
[_portraitToolbarView transitionInAnimated:animated transparent:true];
[_landscapeToolbarView transitionInAnimated:animated transparent:true];
}
}
- (void)editorTransitionIn
{
[UIView animateWithDuration:0.2 animations:^
{
_arrowView.alpha = 0.0f;
_recipientLabel.alpha = 0.0f;
_captionMixin.inputPanel.alpha = 0.0f;
}];
}
- (void)editorTransitionOut
{
[UIView animateWithDuration:0.3 animations:^
{
_arrowView.alpha = 0.45f;
_recipientLabel.alpha = 1.0f;
_captionMixin.inputPanel.alpha = 1.0f;
}];
}
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
[super willRotateToInterfaceOrientation:toInterfaceOrientation duration:duration];
[_imageView pop_removeAllAnimations];
}
- (void)viewWillLayoutSubviews
{
[super viewWillLayoutSubviews];
[self updateLayout:[[LegacyComponentsGlobals provider] applicationStatusBarOrientation]];
}
- (void)setScrollViewVerticalOffset:(CGFloat)offset
{
_scrollViewVerticalOffset = offset;
CGRect scrollViewFrame = _scrollView.frame;
scrollViewFrame.origin.y = offset;
_scrollView.frame = scrollViewFrame;
}
- (void)_layoutRecipientLabelForOrientation:(UIInterfaceOrientation)orientation screenEdges:(UIEdgeInsets)screenEdges
{
CGFloat screenWidth = MIN(self.view.frame.size.width, self.view.frame.size.height);
CGFloat recipientWidth = MIN(_recipientLabel.frame.size.width, screenWidth - 100.0f);
if (self.controllerSafeAreaInset.top > 20.0f + FLT_EPSILON)
screenEdges.top += self.controllerSafeAreaInset.top;
screenEdges.left += self.controllerSafeAreaInset.left;
screenEdges.right -= self.controllerSafeAreaInset.right;
CGRect frame = CGRectZero;
switch (orientation)
{
case UIInterfaceOrientationLandscapeLeft:
frame = CGRectMake(screenEdges.right - recipientWidth - 28.0f, screenEdges.bottom - 24, _arrowView.frame.size.width, _arrowView.frame.size.height);
break;
case UIInterfaceOrientationLandscapeRight:
frame = CGRectMake(screenEdges.left + 14, screenEdges.bottom - 24, _arrowView.frame.size.width, _arrowView.frame.size.height);
break;
default:
frame = CGRectMake(screenEdges.left + 14, screenEdges.top + 16, _arrowView.frame.size.width, _arrowView.frame.size.height);
break;
}
_arrowView.frame = frame;
_recipientLabel.frame = CGRectMake(CGRectGetMaxX(_arrowView.frame) + 6.0f, _arrowView.frame.origin.y - 2.0f, recipientWidth, _recipientLabel.frame.size.height);
}
- (void)updateLayout:(UIInterfaceOrientation)orientation
{
UIInterfaceOrientation originalOrientation = orientation;
if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad)
{
_landscapeToolbarView.hidden = true;
orientation = UIInterfaceOrientationPortrait;
}
bool hasOnScreenNavigation = false;
if (iosMajorVersion() >= 11)
hasOnScreenNavigation = (self.viewLoaded && self.view.safeAreaInsets.bottom > FLT_EPSILON) || _context.safeAreaInset.bottom > FLT_EPSILON;
CGSize referenceSize = [self referenceViewSizeForOrientation:originalOrientation];
UIEdgeInsets safeAreaInset = [TGViewController safeAreaInsetForOrientation:orientation hasOnScreenNavigation:hasOnScreenNavigation];
[_captionMixin setContentAreaHeight:self.view.frame.size.height];
CGFloat screenSide = MAX(referenceSize.width, referenceSize.height);
_wrapperView.frame = CGRectMake((referenceSize.width - screenSide) / 2, (referenceSize.height - screenSide) / 2, screenSide, screenSide);
UIEdgeInsets screenEdges = UIEdgeInsetsMake((screenSide - referenceSize.height) / 2, (screenSide - referenceSize.width) / 2, (screenSide + referenceSize.height) / 2, (screenSide + referenceSize.width) / 2);
_landscapeToolbarView.interfaceOrientation = orientation;
CGFloat portraitToolbarViewBottomEdge = screenSide;
if (TGIsPad())
portraitToolbarViewBottomEdge = screenEdges.bottom;
_portraitToolbarView.frame = CGRectMake(screenEdges.left, portraitToolbarViewBottomEdge - TGPhotoEditorToolbarSize - safeAreaInset.bottom, referenceSize.width, TGPhotoEditorToolbarSize + safeAreaInset.bottom);
UIEdgeInsets captionEdgeInsets = screenEdges;
captionEdgeInsets.bottom = _portraitToolbarView.frame.size.height;
[_captionMixin updateLayoutWithFrame:self.view.bounds edgeInsets:captionEdgeInsets];
switch (orientation)
{
case UIInterfaceOrientationLandscapeLeft:
{
[UIView performWithoutAnimation:^
{
_landscapeToolbarView.frame = CGRectMake(screenEdges.left, screenEdges.top, TGPhotoEditorToolbarSize + safeAreaInset.left, referenceSize.height);
}];
}
break;
case UIInterfaceOrientationLandscapeRight:
{
[UIView performWithoutAnimation:^
{
_landscapeToolbarView.frame = CGRectMake(screenEdges.right - TGPhotoEditorToolbarSize - safeAreaInset.right, screenEdges.top, TGPhotoEditorToolbarSize + safeAreaInset.right, referenceSize.height);
}];
}
break;
default:
{
_landscapeToolbarView.frame = CGRectMake(_landscapeToolbarView.frame.origin.x, screenEdges.top, TGPhotoEditorToolbarSize, referenceSize.height);
}
break;
}
[self _layoutRecipientLabelForOrientation:orientation screenEdges:screenEdges];
if (_transitionInProgress)
return;
if (!CGRectEqualToRect(_scrollView.frame, self.view.bounds))
{
_scrollView.frame = CGRectMake(0.0f, _scrollViewVerticalOffset, self.view.bounds.size.width, self.view.bounds.size.height);
[self reset];
}
}
@end