Merge commit '5e36741afa9f5f28e55af8fd13e718a300c98870'

This commit is contained in:
Ali 2020-07-02 22:09:21 +04:00
commit 9cd7e3a8f1
23 changed files with 386 additions and 93 deletions

View File

@ -312,6 +312,7 @@ public final class GalleryPagerNode: ASDisplayNode, UIScrollViewDelegate, UIGest
}
}
public var updateOnReplacement = false
public func replaceItems(_ items: [GalleryItem], centralItemIndex: Int?, synchronous: Bool = false) {
var updateItems: [GalleryPagerUpdateItem] = []
var deleteItems: [Int] = []
@ -326,14 +327,20 @@ public final class GalleryPagerNode: ASDisplayNode, UIScrollViewDelegate, UIGest
}
}
for i in 0 ..< items.count {
if i == previousIndexById[items[i].id] {
updateItems.append(GalleryPagerUpdateItem(index: i, previousIndex: i, item: items[i]))
} else {
if self.updateOnReplacement {
for i in 0 ..< items.count {
if (previousIndexById[items[i].id] == nil) {
insertItems.append(GalleryPagerInsertItem(index: i, item: items[i], previousIndex: previousIndexById[items[i].id]))
} else {
updateItems.append(GalleryPagerUpdateItem(index: i, previousIndex: i, item: items[i]))
}
}
} else {
for i in 0 ..< items.count {
insertItems.append(GalleryPagerInsertItem(index: i, item: items[i], previousIndex: previousIndexById[items[i].id]))
}
}
self.transaction(GalleryPagerTransaction(deleteItems: deleteItems, insertItems: insertItems, updateItems: updateItems, focusOnItem: centralItemIndex, synchronous: synchronous))
}
@ -404,7 +411,7 @@ public final class GalleryPagerNode: ASDisplayNode, UIScrollViewDelegate, UIGest
self.ignoreCentralItemIndexUpdate = true
self.centralItemIndex = focusOnItem
self.ignoreCentralItemIndexUpdate = false
self.updateItemNodes(transition: .immediate, forceOffsetReset: true)
self.updateItemNodes(transition: .immediate, forceOffsetReset: true, synchronous: transaction.synchronous)
}
}

View File

@ -39,6 +39,8 @@
- (void)closeCurtains;
- (void)openCurtains;
- (void)flash:(void (^)(void))completion;
- (void)invalidateCropRect;
- (UIImage *)currentImage;

View File

@ -44,6 +44,7 @@
- (void)prepareTransitionOutSaving:(bool)saving;
- (void)prepareForCustomTransitionOut;
- (void)finishCustomTransitionOut;
- (void)animateTransitionIn;
- (CGRect)_targetFrameForTransitionInFromFrame:(CGRect)fromFrame;

View File

@ -2,6 +2,8 @@
@interface TGPhotoVideoEditor : NSObject
+ (void)presentWithContext:(id<LegacyComponentsContext>)context parentController:(TGViewController *)parentController image:(UIImage *)image video:(NSURL *)video didFinishWithImage:(void (^)(UIImage *image))didFinishWithImage didFinishWithVideo:(void (^)(UIImage *image, NSURL *url, TGVideoEditAdjustments *adjustments))didFinishWithVideo;
+ (void)presentWithContext:(id<LegacyComponentsContext>)context controller:(TGViewController *)controller caption:(NSString *)caption entities:(NSArray *)entities withItem:(id<TGMediaEditableItem, TGMediaSelectableItem>)item recipientName:(NSString *)recipientName stickersContext:(id<TGPhotoPaintStickersContext>)stickersContext completion:(void (^)(id<TGMediaEditableItem>, TGMediaEditingContext *))completion dismissed:(void (^)())dismissed;
@end

View File

@ -484,7 +484,6 @@
[_finalFilter addTarget:self.previewOutput.imageView];
}
}
if (_histogramGenerator != nil && !self.standalone) {
[_finalFilter addTarget:_histogramGenerator];

View File

@ -23,8 +23,12 @@
#import <LegacyComponents/TGMenuView.h>
#import "TGPaintFaceDetector.h"
@interface TGMediaPickerGalleryPhotoItemView ()
{
SMetaDisposable *_facesDisposable;
UILabel *_fileInfoLabel;
TGMessageImageViewOverlayView *_progressView;
@ -57,6 +61,8 @@
self = [super initWithFrame:frame];
if (self != nil)
{
_facesDisposable = [[SMetaDisposable alloc] init];
__weak TGMediaPickerGalleryPhotoItemView *weakSelf = self;
_imageView = [[TGModernGalleryImageItemImageView alloc] init];
_imageView.clipsToBounds = true;
@ -101,6 +107,7 @@
{
[_adjustmentsDisposable dispose];
[_attributesDisposable dispose];
[_facesDisposable dispose];
}
- (void)setHiddenAsBeingEdited:(bool)hidden
@ -226,8 +233,11 @@
}]];
if (!item.asFile)
if (!item.asFile) {
[_facesDisposable setDisposable:[[TGPaintFaceDetector detectFacesInItem:item.editableMediaItem editingContext:item.editingContext] startWithNext:nil]];
return;
}
_fileInfoLabel.text = nil;

View File

@ -41,6 +41,7 @@
#import <LegacyComponents/TGMenuView.h>
#import "PGPhotoEditor.h"
#import "TGPaintFaceDetector.h"
@interface TGMediaPickerGalleryVideoItemView() <TGMediaPickerGalleryVideoScrubberDataSource, TGMediaPickerGalleryVideoScrubberDelegate>
{
@ -96,6 +97,7 @@
SMetaDisposable *_adjustmentsDisposable;
SMetaDisposable *_attributesDisposable;
SMetaDisposable *_downloadDisposable;
SMetaDisposable *_facesDisposable;
SMetaDisposable *_currentAudioSession;
SVariable *_editableItemVariable;
@ -131,6 +133,7 @@
_currentAudioSession = [[SMetaDisposable alloc] init];
_playerItemDisposable = [[SMetaDisposable alloc] init];
_facesDisposable = [[SMetaDisposable alloc] init];
_videoDurationVar = [[SVariable alloc] init];
_videoDurationDisposable = [[SMetaDisposable alloc] init];
@ -251,6 +254,7 @@
[_thumbnailsDisposable dispose];
[_attributesDisposable dispose];
[_downloadDisposable dispose];
[_facesDisposable dispose];
[self stopPlayer];
[self releaseVolumeOverlay];
@ -385,8 +389,13 @@
[super setItem:item synchronously:synchronously];
if (itemChanged)
if (itemChanged) {
[self _playerCleanup];
if (!item.asFile) {
[_facesDisposable setDisposable:[[TGPaintFaceDetector detectFacesInItem:item.editableMediaItem editingContext:item.editingContext] startWithNext:nil]];
}
}
_scrubberView.allowsTrimming = false;
_videoDimensions = item.dimensions;

View File

@ -1,6 +1,9 @@
#import <UIKit/UIKit.h>
#import <SSignalKit/SSignalKit.h>
@protocol TGMediaEditableItem;
@class TGMediaEditingContext;
@interface TGPaintFaceFeature : NSObject
{
CGPoint _position;
@ -49,6 +52,8 @@
+ (SSignal *)detectFacesInImage:(UIImage *)image originalSize:(CGSize)originalSize;
+ (SSignal *)detectFacesInItem:(id<TGMediaEditableItem>)item editingContext:(TGMediaEditingContext *)editingContext;
@end

View File

@ -3,6 +3,9 @@
#import <LegacyComponents/TGPaintUtils.h>
#import <ImageIO/ImageIO.h>
#import "TGMediaEditingContext.h"
#import "UIImage+TG.h"
@interface TGPaintFace ()
+ (instancetype)faceWithBounds:(CGRect)bounds angle:(CGFloat)angle leftEye:(TGPaintFaceEye *)leftEye rightEye:(TGPaintFaceEye *)rightEye mouth:(TGPaintFaceMouth *)mouth;
@ -26,6 +29,41 @@
@implementation TGPaintFaceDetector
+ (SSignal *)detectFacesInItem:(id<TGMediaEditableItem>)item editingContext:(TGMediaEditingContext *)editingContext
{
CGSize originalSize = item.originalSize;
SSignal *cachedFaces = [editingContext facesForItem:item];
SSignal *cachedSignal = [cachedFaces mapToSignal:^SSignal *(id result)
{
if (result == nil)
return [SSignal fail:nil];
return [SSignal single:result];
}];
SSignal *imageSignal = [item screenImageSignal:0];
SSignal *detectSignal = [[[imageSignal filter:^bool(UIImage *image)
{
if (![image isKindOfClass:[UIImage class]])
return false;
if (image.degraded)
return false;
return true;
}] take:1] mapToSignal:^SSignal *(UIImage *image) {
return [[TGPaintFaceDetector detectFacesInImage:image originalSize:originalSize] startOn:[SQueue concurrentDefaultQueue]];
}];
return [[[cachedSignal catch:^SSignal *(__unused id error)
{
return detectSignal;
}] deliverOn:[SQueue mainQueue]] onNext:^(NSArray *next)
{
[editingContext setFaces:next forItem:item];
}];
}
+ (SSignal *)detectFacesInImage:(UIImage *)image originalSize:(CGSize)originalSize
{
return [[SSignal alloc] initWithGenerator:^id<SDisposable>(SSubscriber *subscriber)

View File

@ -26,6 +26,8 @@ const CGFloat TGPhotoAvatarCropViewCurtainMargin = 200;
UIView *_snapshotView;
CGSize _snapshotSize;
UIView *_flashView;
UIView *_topOverlayView;
UIView *_leftOverlayView;
UIView *_rightOverlayView;
@ -39,7 +41,7 @@ const CGFloat TGPhotoAvatarCropViewCurtainMargin = 200;
CGFloat _currentDiameter;
PGPhotoEditorView *_fullPreviewView;
__weak PGPhotoEditorView *_fullPreviewView;
}
@end
@ -82,6 +84,12 @@ const CGFloat TGPhotoAvatarCropViewCurtainMargin = 200;
_fullPreviewView.userInteractionEnabled = false;
[_wrapperView addSubview:_fullPreviewView];
_flashView = [[UIView alloc] init];
_flashView.alpha = 0.0;
_flashView.backgroundColor = [UIColor whiteColor];
_flashView.userInteractionEnabled = false;
[self addSubview:_flashView];
_topCurtainView = [[UIView alloc] initWithFrame:CGRectZero];
_topCurtainView.backgroundColor = [UIColor blackColor];
[self addSubview:_topCurtainView];
@ -122,6 +130,11 @@ const CGFloat TGPhotoAvatarCropViewCurtainMargin = 200;
return self;
}
- (void)dealloc
{
_scrollView.delegate = nil;
}
- (void)handleTap:(UITapGestureRecognizer *)gestureRecognizer {
if (self.tapped != nil)
self.tapped();
@ -611,6 +624,8 @@ const CGFloat TGPhotoAvatarCropViewCurtainMargin = 200;
{
[self _layoutOverlayViews];
_flashView.frame = self.bounds;
if (_scrollView.superview == nil)
{
_scrollView.frame = self.bounds;
@ -647,4 +662,16 @@ const CGFloat TGPhotoAvatarCropViewCurtainMargin = 200;
return CGSizeMake(20, 20);
}
- (void)flash:(void (^)(void))completion {
[UIView animateWithDuration:0.12 animations:^{
_flashView.alpha = 1.0f;
} completion:^(BOOL finished) {
[UIView animateWithDuration:0.2 animations:^{
_flashView.alpha = 0.0f;
} completion:^(BOOL finished) {
completion();
}];
}];
}
@end

View File

@ -15,7 +15,12 @@
@property (nonatomic, copy) void (^croppingChanged)(void);
@property (nonatomic, copy) void (^togglePlayback)(void);
- (instancetype)initWithContext:(id<LegacyComponentsContext>)context photoEditor:(PGPhotoEditor *)photoEditor previewView:(TGPhotoEditorPreviewView *)previewView scrubberView:(TGMediaPickerGalleryVideoScrubber *)scrubberView dotImageView:(UIView *)dotImageView fullPreviewView:(PGPhotoEditorView *)fullPreviewView;
@property (nonatomic, weak) UIView *dotImageView;
@property (nonatomic, weak) UIView *dotMarkerView;
@property (nonatomic, weak) PGPhotoEditorView *fullPreviewView;
@property (nonatomic, weak) TGMediaPickerGalleryVideoScrubber *scrubberView;
- (instancetype)initWithContext:(id<LegacyComponentsContext>)context photoEditor:(PGPhotoEditor *)photoEditor previewView:(TGPhotoEditorPreviewView *)previewView;
- (void)setImage:(UIImage *)image;
- (void)setSnapshotImage:(UIImage *)snapshotImage;

View File

@ -32,18 +32,13 @@ const CGFloat TGPhotoAvatarPreviewLandscapePanelSize = TGPhotoAvatarPreviewPanel
UIView *_wrapperView;
TGPhotoAvatarCropView *_cropView;
PGPhotoEditorView *_fullPreviewView;
__weak TGPhotoAvatarCropView *_cropView;
UIView *_portraitToolsWrapperView;
UIView *_landscapeToolsWrapperView;
UIView *_portraitWrapperBackgroundView;
UIView *_landscapeWrapperBackgroundView;
TGMediaPickerGalleryVideoScrubber *_scrubberView;
UIView *_dotImageView;
UIView *_videoAreaView;
UIView *_flashView;
UIView *_portraitToolControlView;
UIView *_landscapeToolControlView;
UILabel *_coverLabel;
@ -58,21 +53,20 @@ const CGFloat TGPhotoAvatarPreviewLandscapePanelSize = TGPhotoAvatarPreviewPanel
@implementation TGPhotoAvatarPreviewController
- (instancetype)initWithContext:(id<LegacyComponentsContext>)context photoEditor:(PGPhotoEditor *)photoEditor previewView:(TGPhotoEditorPreviewView *)previewView scrubberView:(TGMediaPickerGalleryVideoScrubber *)scrubberView dotImageView:(UIView *)dotImageView fullPreviewView:(PGPhotoEditorView *)fullPreviewView
{
- (instancetype)initWithContext:(id<LegacyComponentsContext>)context photoEditor:(PGPhotoEditor *)photoEditor previewView:(TGPhotoEditorPreviewView *)previewView {
self = [super initWithContext:context];
if (self != nil)
{
self.photoEditor = photoEditor;
self.previewView = previewView;
_fullPreviewView = fullPreviewView;
_scrubberView = scrubberView;
_dotImageView = dotImageView;
}
return self;
}
- (void)dealloc {
NSLog(@"");
}
- (void)loadView
{
[super loadView];
@ -90,7 +84,7 @@ const CGFloat TGPhotoAvatarPreviewLandscapePanelSize = TGPhotoAvatarPreviewPanel
if (strongSelf == nil)
return;
self.controlVideoPlayback(false);
strongSelf.controlVideoPlayback(false);
};
void(^interactionEnded)(void) = ^
{
@ -101,11 +95,12 @@ const CGFloat TGPhotoAvatarPreviewLandscapePanelSize = TGPhotoAvatarPreviewPanel
if ([strongSelf shouldAutorotate])
[TGViewController attemptAutorotation];
self.controlVideoPlayback(true);
strongSelf.controlVideoPlayback(true);
};
PGPhotoEditor *photoEditor = self.photoEditor;
_cropView = [[TGPhotoAvatarCropView alloc] initWithOriginalSize:photoEditor.originalSize screenSize:[self referenceViewSize] fullPreviewView:_fullPreviewView];
TGPhotoAvatarCropView *cropView = [[TGPhotoAvatarCropView alloc] initWithOriginalSize:photoEditor.originalSize screenSize:[self referenceViewSize] fullPreviewView:_fullPreviewView];
_cropView = cropView;
[_cropView setCropRect:photoEditor.cropRect];
[_cropView setCropOrientation:photoEditor.cropOrientation];
[_cropView setCropMirrored:photoEditor.cropMirrored];
@ -142,7 +137,7 @@ const CGFloat TGPhotoAvatarPreviewLandscapePanelSize = TGPhotoAvatarPreviewPanel
}
_cropView.interactionBegan = interactionBegan;
_cropView.interactionEnded = interactionEnded;
[_wrapperView addSubview:_cropView];
[_wrapperView addSubview:cropView];
_portraitToolsWrapperView = [[UIView alloc] initWithFrame:CGRectZero];
[_wrapperView addSubview:_portraitToolsWrapperView];
@ -162,18 +157,9 @@ const CGFloat TGPhotoAvatarPreviewLandscapePanelSize = TGPhotoAvatarPreviewPanel
_landscapeWrapperBackgroundView.backgroundColor = [TGPhotoEditorInterfaceAssets toolbarTransparentBackgroundColor];
_landscapeWrapperBackgroundView.userInteractionEnabled = false;
[_landscapeToolsWrapperView addSubview:_landscapeWrapperBackgroundView];
_videoAreaView = [[UIView alloc] init];
[self.view insertSubview:_videoAreaView belowSubview:_wrapperView];
[_portraitToolsWrapperView addSubview:_scrubberView];
_flashView = [[UIView alloc] init];
_flashView.alpha = 0.0;
_flashView.backgroundColor = [UIColor whiteColor];
_flashView.userInteractionEnabled = false;
[_videoAreaView addSubview:_flashView];
_coverLabel = [[UILabel alloc] init];
_coverLabel.alpha = 0.7f;
_coverLabel.backgroundColor = [UIColor clearColor];
@ -183,7 +169,6 @@ const CGFloat TGPhotoAvatarPreviewLandscapePanelSize = TGPhotoAvatarPreviewPanel
[_coverLabel sizeToFit];
[_portraitToolsWrapperView addSubview:_coverLabel];
_dotImageView.alpha = 1.0f;
[_wrapperView addSubview:_dotImageView];
}
}
@ -263,7 +248,9 @@ const CGFloat TGPhotoAvatarPreviewLandscapePanelSize = TGPhotoAvatarPreviewPanel
- (void)prepareTransitionInWithReferenceView:(UIView *)referenceView referenceFrame:(CGRect)referenceFrame parentView:(UIView *)parentView noTransitionView:(bool)noTransitionView
{
[super prepareTransitionInWithReferenceView:referenceView referenceFrame:referenceFrame parentView:parentView noTransitionView:noTransitionView];
[self.view insertSubview:_transitionView belowSubview:_wrapperView];
if (self.initialAppearance && self.fromCamera)
[self.view insertSubview:_transitionView belowSubview:_wrapperView];
}
- (void)transitionIn
@ -282,6 +269,8 @@ const CGFloat TGPhotoAvatarPreviewLandscapePanelSize = TGPhotoAvatarPreviewPanel
{
_portraitToolsWrapperView.alpha = 1.0f;
_landscapeToolsWrapperView.alpha = 1.0f;
_dotImageView.alpha = 1.0f;
_dotMarkerView.alpha = 1.0f;
} completion:^(BOOL finished) {
_scrubberView.layer.shouldRasterize = false;
}];
@ -387,6 +376,7 @@ const CGFloat TGPhotoAvatarPreviewLandscapePanelSize = TGPhotoAvatarPreviewPanel
_cropView.transform = CGAffineTransformMakeScale(targetCropViewScale, targetCropViewScale);
} completion:^(__unused BOOL finished)
{
[_cropView removeFromSuperview];
_previewView.alpha = 1.0;
if (self.finishedTransitionOut != nil)
self.finishedTransitionOut();
@ -394,8 +384,9 @@ const CGFloat TGPhotoAvatarPreviewLandscapePanelSize = TGPhotoAvatarPreviewPanel
if (completion != nil)
completion();
}];
} else if (self.fromCamera) {
_previewView.alpha = 0.0f;
} else {
if (self.fromCamera)
_previewView.alpha = 0.0f;
}
switch (self.effectiveOrientation)
@ -438,9 +429,11 @@ const CGFloat TGPhotoAvatarPreviewLandscapePanelSize = TGPhotoAvatarPreviewPanel
_portraitToolsWrapperView.alpha = 0.0f;
_landscapeToolsWrapperView.alpha = 0.0f;
_dotImageView.alpha = 0.0f;
_dotMarkerView.alpha = 0.0f;
} completion:^(__unused BOOL finished)
{
if (!switching) {
[_cropView removeFromSuperview];
if (completion != nil)
completion();
}
@ -544,17 +537,21 @@ const CGFloat TGPhotoAvatarPreviewLandscapePanelSize = TGPhotoAvatarPreviewPanel
{
_portraitToolsWrapperView.alpha = 0.0f;
_landscapeToolsWrapperView.alpha = 0.0f;
_videoAreaView.alpha = 0.0f;
_dotImageView.alpha = 0.0f;
} completion:nil];
}
- (void)finishCustomTransitionOut
{
[_cropView removeFromSuperview];
}
- (CGRect)transitionOutReferenceFrame
{
if (_dismissingToCamera) {
return _fullPreviewView.frame;
return [_fullPreviewView.superview convertRect:_fullPreviewView.frame toView:self.view];
} else {
return _previewView.frame;
return [_wrapperView convertRect:_cropView.frame toView:self.view];
}
}
@ -735,12 +732,6 @@ const CGFloat TGPhotoAvatarPreviewLandscapePanelSize = TGPhotoAvatarPreviewPanel
CGRect containerFrame = [TGPhotoAvatarPreviewController photoContainerFrameForParentViewFrame:CGRectMake(0, 0, referenceSize.width, referenceSize.height) toolbarLandscapeSize:self.toolbarLandscapeSize orientation:self.effectiveOrientation panelSize:0 hasOnScreenNavigation:self.hasOnScreenNavigation];
CGSize fittedSize = TGScaleToSize(photoEditor.rotatedCropSize, containerFrame.size);
previewView.frame = CGRectMake(containerFrame.origin.x + (containerFrame.size.width - fittedSize.width) / 2, containerFrame.origin.y + (containerFrame.size.height - fittedSize.height) / 2, fittedSize.width, fittedSize.height);
[UIView performWithoutAnimation:^
{
_videoAreaView.frame = _previewView.frame;
_flashView.frame = _videoAreaView.bounds;
}];
}
- (void)updateLayout:(UIInterfaceOrientation)orientation
@ -826,29 +817,24 @@ const CGFloat TGPhotoAvatarPreviewLandscapePanelSize = TGPhotoAvatarPreviewPanel
- (void)beginScrubbing:(bool)flash
{
if (flash)
if (flash) {
_coverLabel.alpha = 1.0f;
}
}
- (void)endScrubbing:(bool)flash completion:(bool (^)(void))completion
{
if (flash) {
[UIView animateWithDuration:0.12 animations:^{
_flashView.alpha = 1.0f;
} completion:^(BOOL finished) {
[UIView animateWithDuration:0.2 animations:^{
_flashView.alpha = 0.0f;
} completion:^(BOOL finished) {
TGDispatchAfter(1.0, dispatch_get_main_queue(), ^{
if (completion()) {
[UIView animateWithDuration:0.2 animations:^{
_coverLabel.alpha = 0.7f;
}];
self.controlVideoPlayback(true);
}
});
}];
[_cropView flash:^{
TGDispatchAfter(1.0, dispatch_get_main_queue(), ^{
if (completion()) {
[UIView animateWithDuration:0.2 animations:^{
_coverLabel.alpha = 0.7f;
}];
self.controlVideoPlayback(true);
}
});
}];
} else {
TGDispatchAfter(1.32, dispatch_get_main_queue(), ^{

View File

@ -59,6 +59,8 @@
TGPhotoEditorTab _currentTab;
TGPhotoEditorTabController *_currentTabController;
TGMediaEditingContext *_standaloneEditingContext;
UIView *_backgroundView;
UIView *_containerView;
UIView *_wrapperView;
@ -140,6 +142,7 @@
{
_context = context;
_actionHandle = [[ASHandle alloc] initWithDelegate:self releaseOnMainThread:true];
_standaloneEditingContext = [[TGMediaEditingContext alloc] init];
self.automaticallyManageScrollViewInsets = false;
self.autoManageStatusBarBackground = false;
@ -561,6 +564,9 @@
if (strongSelf->_ignoreDefaultPreviewViewTransitionIn)
{
__strong TGPhotoEditorController *strongSelf = weakSelf;
if (strongSelf == nil)
return;
TGDispatchOnMainThread(^
{
if (strongSelf->_dismissed)
@ -575,6 +581,9 @@
{
[photoEditor processAnimated:false completion:^
{
__strong TGPhotoEditorController *strongSelf = weakSelf;
if (strongSelf == nil)
return;
TGDispatchOnMainThread(^
{
if (strongSelf->_dismissed)
@ -1090,7 +1099,13 @@
rep = imageView;
}
[_currentTabController prepareForCustomTransitionOut];
self.beginCustomTransitionOut([_currentTabController transitionOutReferenceFrame], rep, completion);
TGPhotoEditorTabController *tabController = _currentTabController;
self.beginCustomTransitionOut([_currentTabController transitionOutReferenceFrame], rep, ^{
[tabController finishCustomTransitionOut];
if (completion)
completion();
});
}
else
{
@ -1125,6 +1140,8 @@
{
if (![currentController isDismissAllowed])
return;
[self savePaintingData];
currentController.switchingToTab = tab;
[currentController transitionOutSwitching:true completion:^
@ -1203,7 +1220,11 @@
{
bool skipInitialTransition = (![self presentedFromCamera] && self.navigationController != nil) || self.skipInitialTransition;
TGPhotoAvatarPreviewController *cropController = [[TGPhotoAvatarPreviewController alloc] initWithContext:_context photoEditor:_photoEditor previewView:_previewView scrubberView:_scrubberView dotImageView:_dotImageView fullPreviewView:_fullPreviewView];
TGPhotoAvatarPreviewController *cropController = [[TGPhotoAvatarPreviewController alloc] initWithContext:_context photoEditor:_photoEditor previewView:_previewView];
cropController.scrubberView = _scrubberView;
cropController.dotImageView = _dotImageView;
cropController.dotMarkerView = _dotMarkerView;
cropController.fullPreviewView = _fullPreviewView;
cropController.fromCamera = [self presentedFromCamera];
cropController.skipTransitionIn = skipInitialTransition;
if (snapshotImage != nil)
@ -1636,6 +1657,7 @@
self.view.frame = targetFrame;
} completion:^(__unused BOOL finished)
{
[_currentTabController finishCustomTransitionOut];
if (self.navigationController != nil) {
[self.navigationController popViewControllerAnimated:false];
} else {
@ -1646,6 +1668,7 @@
else
{
if (self.navigationController != nil) {
[_currentTabController finishCustomTransitionOut];
[self.navigationController popViewControllerAnimated:false];
} else {
[self dismiss];
@ -1760,6 +1783,18 @@
}
}
- (void)savePaintingData {
if (![_currentTabController isKindOfClass:[TGPhotoPaintController class]])
return;
TGPhotoPaintController *paintController = (TGPhotoPaintController *)_currentTabController;
TGPaintingData *paintingData = [paintController paintingData];
_photoEditor.paintingData = paintingData;
if (paintingData != nil)
[TGPaintingData storePaintingData:paintingData inContext:self.editingContext forItem:_item forVideo:(_intent == TGPhotoEditorControllerVideoIntent)];
}
- (void)applyEditor
{
if (![_currentTabController isDismissAllowed])
@ -1768,8 +1803,6 @@
self.view.userInteractionEnabled = false;
[_currentTabController prepareTransitionOutSaving:true];
TGPaintingData *paintingData = _photoEditor.paintingData;
bool saving = true;
NSTimeInterval videoStartValue = 0.0;
NSTimeInterval trimStartValue = 0.0;
@ -1777,12 +1810,7 @@
if ([_currentTabController isKindOfClass:[TGPhotoPaintController class]])
{
TGPhotoPaintController *paintController = (TGPhotoPaintController *)_currentTabController;
paintingData = [paintController paintingData];
_photoEditor.paintingData = paintingData;
if (paintingData != nil)
[TGPaintingData storePaintingData:paintingData inContext:_editingContext forItem:_item forVideo:(_intent == TGPhotoEditorControllerVideoIntent)];
[self savePaintingData];
}
else if ([_currentTabController isKindOfClass:[TGPhotoQualityController class]])
{
@ -1800,6 +1828,7 @@
[self stopVideoPlayback:true];
TGPaintingData *paintingData = _photoEditor.paintingData;
TGVideoEditAdjustments *adjustments = [_photoEditor exportAdjustmentsWithPaintingData:paintingData];
if ([self presentedForAvatarCreation] && _item.isVideo) {
[[SQueue concurrentDefaultQueue] dispatch:^
@ -1968,7 +1997,7 @@
thumbnailImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
[_editingContext setImage:fullImage thumbnailImage:thumbnailImage forItem:_item synchronous:true];
[self.editingContext setImage:fullImage thumbnailImage:thumbnailImage forItem:_item synchronous:true];
}];
}];
}
@ -1990,6 +2019,14 @@
}
}
- (TGMediaEditingContext *)editingContext
{
if (_editingContext)
return _editingContext;
else
return _standaloneEditingContext;
}
- (void)doneButtonLongPressed:(UIButton *)sender
{
if (_intent == TGPhotoEditorControllerVideoIntent)
@ -2591,7 +2628,7 @@
return !strongSelf->_scrubberView.isScrubbing;
}];
dispatch_async(dispatch_get_main_queue(), ^{
TGDispatchAfter(0.16, dispatch_get_main_queue(), ^{
[self updateDotImage:true];
});
}

View File

@ -215,6 +215,11 @@ const CGFloat TGPhotoEditorToolbarSize = 49.0f;
}
- (void)finishCustomTransitionOut
{
}
- (void)transitionOutSwitching:(bool)__unused switching completion:(void (^)(void))__unused completion
{

View File

@ -1842,7 +1842,7 @@ const CGFloat TGPhotoPaintStickerKeyboardSize = 260.0f;
_appeared = true;
if ([transitionView isKindOfClass:[TGPhotoEditorPreviewView class]]) {
[_containerView insertSubview:transitionView belowSubview:_paintingWrapperView];
} else {
[transitionView removeFromSuperview];
}
@ -1853,6 +1853,7 @@ const CGFloat TGPhotoPaintStickerKeyboardSize = 260.0f;
[previewView setPaintingHidden:true];
previewView.hidden = false;
[_containerView insertSubview:previewView belowSubview:_paintingWrapperView];
[self updateContentViewLayout];
[previewView performTransitionInIfNeeded];
CGRect rect = [self fittedCropRect:true];
@ -2142,6 +2143,14 @@ const CGFloat TGPhotoPaintStickerKeyboardSize = 260.0f;
[self updateLayout:toInterfaceOrientation];
}
- (void)updateContentViewLayout
{
CGAffineTransform rotationTransform = CGAffineTransformMakeRotation(TGRotationForOrientation(_photoEditor.cropOrientation));
_contentView.transform = rotationTransform;
_contentView.frame = self.previewView.frame;
[self resetScrollView];
}
- (void)updateLayout:(UIInterfaceOrientation)orientation
{
if ([self inFormSheet] || [UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad)

View File

@ -160,8 +160,9 @@ const CGFloat TGPhotoEditorToolsLandscapePanelSize = TGPhotoEditorToolsPanelSize
if (strongSelf != nil) {
[strongSelf->_hudView setText:nil];
if (forVideo) {
strongSelf->_photoEditor.disableAll = false;
strongSelf->_photoEditor.disableAll = false;
if (!forVideo) {
[strongSelf->_photoEditor processAnimated:false completion:nil];
}
}
};
@ -171,8 +172,9 @@ const CGFloat TGPhotoEditorToolsLandscapePanelSize = TGPhotoEditorToolsPanelSize
if (strongSelf != nil) {
[strongSelf->_hudView setText:TGLocalized(@"PhotoEditor.Original")];
if (forVideo) {
strongSelf->_photoEditor.disableAll = true;
strongSelf->_photoEditor.disableAll = true;
if (!forVideo) {
[strongSelf->_photoEditor processAnimated:false completion:nil];
}
}
};

View File

@ -10,6 +10,59 @@
@implementation TGPhotoVideoEditor
+ (void)presentWithContext:(id<LegacyComponentsContext>)context parentController:(TGViewController *)parentController image:(UIImage *)image video:(NSURL *)video didFinishWithImage:(void (^)(UIImage *image))didFinishWithImage didFinishWithVideo:(void (^)(UIImage *image, NSURL *url, TGVideoEditAdjustments *adjustments))didFinishWithVideo
{
id<LegacyComponentsOverlayWindowManager> windowManager = [context makeOverlayWindowManager];
id<TGMediaEditableItem> editableItem;
if (image != nil) {
editableItem = image;
} else if (video != nil) {
editableItem = [[TGCameraCapturedVideo alloc] initWithURL:video];
}
TGPhotoEditorController *controller = [[TGPhotoEditorController alloc] initWithContext:[windowManager context] item:editableItem intent:TGPhotoEditorControllerAvatarIntent adjustments:nil caption:nil screenImage:nil availableTabs:[TGPhotoEditorController defaultTabsForAvatarIntent] selectedTab:TGPhotoEditorCropTab];
// controller.stickersContext = _stickersContext;
controller.dontHideStatusBar = true;
controller.didFinishEditing = ^(__unused id<TGMediaEditAdjustments> adjustments, UIImage *resultImage, __unused UIImage *thumbnailImage, __unused bool hasChanges)
{
if (didFinishWithImage != nil)
didFinishWithImage(resultImage);
};
controller.didFinishEditingVideo = ^(NSURL *url, id<TGMediaEditAdjustments> adjustments, UIImage *resultImage, UIImage *thumbnailImage, bool hasChanges) {
if (didFinishWithVideo != nil)
didFinishWithVideo(resultImage, url, adjustments);
};
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)
{
if (editableItem.isVideo) {
if ([editableItem isKindOfClass:[TGMediaAsset class]]) {
return [TGMediaAssetImageSignals avAssetForVideoAsset:(TGMediaAsset *)editableItem allowNetworkAccess:true];
} else if ([editableItem isKindOfClass:[TGCameraCapturedVideo class]]) {
return ((TGCameraCapturedVideo *)editableItem).avAsset;
} else {
return [editableItem originalImageSignal:position];
}
} else {
return [editableItem originalImageSignal:position];
}
};
TGOverlayControllerWindow *controllerWindow = [[TGOverlayControllerWindow alloc] initWithManager:windowManager parentController:controller contentController:controller];
controllerWindow.hidden = false;
controller.view.clipsToBounds = true;
}
+ (void)presentWithContext:(id<LegacyComponentsContext>)context controller:(TGViewController *)controller caption:(NSString *)caption entities:(NSArray *)entities withItem:(id<TGMediaEditableItem, TGMediaSelectableItem>)item recipientName:(NSString *)recipientName stickersContext:(id<TGPhotoPaintStickersContext>)stickersContext completion:(void (^)(id<TGMediaEditableItem>, TGMediaEditingContext *))completion dismissed:(void (^)())dismissed
{
id<LegacyComponentsOverlayWindowManager> windowManager = [context makeOverlayWindowManager];

View File

@ -6,6 +6,30 @@ import LegacyComponents
import TelegramPresentationData
import LegacyUI
public func presentLegacyAvatarEditor(theme: PresentationTheme, image: UIImage?, video: URL?, present: (ViewController, Any?) -> Void, imageCompletion: @escaping (UIImage) -> Void, videoCompletion: @escaping (UIImage, URL, TGVideoEditAdjustments?) -> Void) {
let legacyController = LegacyController(presentation: .custom, theme: theme)
legacyController.statusBar.statusBarStyle = .Ignore
let emptyController = LegacyEmptyController(context: legacyController.context)!
let navigationController = makeLegacyNavigationController(rootController: emptyController)
navigationController.setNavigationBarHidden(true, animated: false)
navigationController.navigationBar.transform = CGAffineTransform(translationX: -1000.0, y: 0.0)
legacyController.bind(controller: navigationController)
present(legacyController, nil)
TGPhotoVideoEditor.present(with: legacyController.context, parentController: emptyController, image: image, video: video, didFinishWithImage: { image in
if let image = image {
imageCompletion(image)
}
}, didFinishWithVideo: { image, url, adjustments in
if let image = image, let url = url {
videoCompletion(image, url, adjustments)
}
})
}
public func presentLegacyAvatarPicker(holder: Atomic<NSObject?>, signup: Bool, theme: PresentationTheme, present: (ViewController, Any?) -> Void, openCurrent: (() -> Void)?, completion: @escaping (UIImage) -> Void) {
let legacyController = LegacyController(presentation: .custom, theme: theme)
legacyController.statusBar.statusBarStyle = .Ignore

View File

@ -161,7 +161,7 @@ public final class MediaTrackFrameBuffer {
if self.endOfStream, let decodedFrame = self.decoder.takeRemainingFrame() {
return .frame(decodedFrame)
} else {
if let bufferedUntilTime = bufferedUntilTime {
if let bufferedUntilTime = self.bufferedUntilTime {
if CMTimeCompare(bufferedUntilTime, self.duration) >= 0 || self.endOfStream {
return .finished
}

View File

@ -21,6 +21,8 @@ static_library(
"//submodules/RadialStatusNode:RadialStatusNode",
"//submodules/ShareController:ShareController",
"//submodules/AppBundle:AppBundle",
"//submodules/LegacyMediaPickerUI:LegacyMediaPickerUI",
"//submodules/SaveToCameraRoll:SaveToCameraRoll",
],
frameworks = [
"$SDKROOT/System/Library/Frameworks/Foundation.framework",

View File

@ -22,6 +22,8 @@ swift_library(
"//submodules/RadialStatusNode:RadialStatusNode",
"//submodules/ShareController:ShareController",
"//submodules/AppBundle:AppBundle",
"//submodules/LegacyMediaPickerUI:LegacyMediaPickerUI",
"//submodules/SaveToCameraRoll:SaveToCameraRoll",
],
visibility = [
"//visibility:public",

View File

@ -10,6 +10,8 @@ import SyncCore
import TelegramPresentationData
import AccountContext
import GalleryUI
import LegacyMediaPickerUI
import SaveToCameraRoll
public enum AvatarGalleryEntryId: Hashable {
case topImage
@ -192,6 +194,8 @@ public class AvatarGalleryController: ViewController, StandalonePresentableContr
private let replaceRootController: (ViewController, Promise<Bool>?) -> Void
private let editDisposable = MetaDisposable ()
public init(context: AccountContext, peer: Peer, sourceHasRoundCorners: Bool = true, remoteEntries: Promise<[AvatarGalleryEntry]>? = nil, centralEntryIndex: Int? = nil, replaceRootController: @escaping (ViewController, Promise<Bool>?) -> Void, synchronousLoad: Bool = false) {
self.context = context
self.peer = peer
@ -265,7 +269,9 @@ public class AvatarGalleryController: ViewController, StandalonePresentableContr
self?.deleteEntry(entry)
} : nil, setMain: { [weak self] in
self?.setMainEntry(entry)
})
}, edit: { [weak self] in
self?.editEntry(entry)
})
}), centralItemIndex: strongSelf.centralEntryIndex, synchronous: !isFirstTime)
let ready = strongSelf.galleryNode.pager.ready() |> timeout(2.0, queue: Queue.mainQueue(), alternate: .single(Void())) |> afterNext { [weak strongSelf] _ in
@ -332,6 +338,7 @@ public class AvatarGalleryController: ViewController, StandalonePresentableContr
deinit {
self.disposable.dispose()
self.centralItemAttributesDisposable.dispose()
self.editDisposable.dispose()
}
@objc func donePressed() {
@ -384,6 +391,7 @@ public class AvatarGalleryController: ViewController, StandalonePresentableContr
self.displayNode = GalleryControllerNode(controllerInteraction: controllerInteraction)
self.displayNodeDidLoad()
self.galleryNode.pager.updateOnReplacement = true
self.galleryNode.statusBar = self.statusBar
self.galleryNode.navigationBar = self.navigationBar
@ -426,6 +434,8 @@ public class AvatarGalleryController: ViewController, StandalonePresentableContr
self?.deleteEntry(entry)
} : nil, setMain: { [weak self] in
self?.setMainEntry(entry)
}, edit: { [weak self] in
self?.editEntry(entry)
}) }), centralItemIndex: self.centralEntryIndex)
self.galleryNode.pager.centralItemIndexUpdated = { [weak self] index in
@ -584,7 +594,9 @@ public class AvatarGalleryController: ViewController, StandalonePresentableContr
self?.deleteEntry(entry)
} : nil, setMain: { [weak self] in
self?.setMainEntry(entry)
}) }), centralItemIndex: 0)
}, edit: { [weak self] in
self?.editEntry(entry)
}) }), centralItemIndex: 0, synchronous: true)
self.entries = entries
}
} else {
@ -605,6 +617,50 @@ public class AvatarGalleryController: ViewController, StandalonePresentableContr
}
}
private func editEntry(_ rawEntry: AvatarGalleryEntry) {
let mediaReference: AnyMediaReference
if let video = rawEntry.videoRepresentations.last {
mediaReference = .standalone(media: TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: video.resource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: video.dimensions, flags: [])]))
} else {
let media = TelegramMediaImage(imageId: MediaId(namespace: 0, id: 0), representations: rawEntry.representations.map({ $0.representation }), immediateThumbnailData: nil, reference: nil, partialReference: nil, flags: [])
mediaReference = .standalone(media: media)
}
self.editDisposable.set((fetchMediaData(context: self.context, postbox: self.context.account.postbox, mediaReference: mediaReference)
|> deliverOnMainQueue).start(next: { [weak self] state, isImage in
guard let strongSelf = self else {
return
}
switch state {
case let .progress(value):
break
case let .data(data):
let image: UIImage?
let video: URL?
if isImage {
if let fileData = try? Data(contentsOf: URL(fileURLWithPath: data.path)) {
image = UIImage(data: fileData)
} else {
image = nil
}
video = nil
} else {
image = nil
video = URL(fileURLWithPath: data.path)
}
presentLegacyAvatarEditor(theme: strongSelf.presentationData.theme, image: image, video: video, present: { [weak self] c, a in
if let strongSelf = self {
strongSelf.present(c, in: .window(.root), with: a, blockInteraction: true)
}
}, imageCompletion: { [weak self] image in
}, videoCompletion: { [weak self] image, url, adjustments in
})
}
}))
}
private func deleteEntry(_ rawEntry: AvatarGalleryEntry) {
var entry = rawEntry
if case .topImage = entry, !self.entries.isEmpty {

View File

@ -54,8 +54,9 @@ class PeerAvatarImageGalleryItem: GalleryItem {
let sourceHasRoundCorners: Bool
let delete: (() -> Void)?
let setMain: (() -> Void)?
let edit: (() -> Void)?
init(context: AccountContext, peer: Peer, presentationData: PresentationData, entry: AvatarGalleryEntry, sourceHasRoundCorners: Bool, delete: (() -> Void)?, setMain: (() -> Void)?) {
init(context: AccountContext, peer: Peer, presentationData: PresentationData, entry: AvatarGalleryEntry, sourceHasRoundCorners: Bool, delete: (() -> Void)?, setMain: (() -> Void)?, edit: (() -> Void)?) {
self.context = context
self.peer = peer
self.presentationData = presentationData
@ -63,6 +64,7 @@ class PeerAvatarImageGalleryItem: GalleryItem {
self.sourceHasRoundCorners = sourceHasRoundCorners
self.delete = delete
self.setMain = setMain
self.edit = edit
}
func node(synchronous: Bool) -> GalleryItemNode {
@ -75,6 +77,7 @@ class PeerAvatarImageGalleryItem: GalleryItem {
node.setEntry(self.entry, synchronous: synchronous)
node.footerContentNode.delete = self.delete
node.footerContentNode.setMain = self.setMain
node.edit = self.edit
return node
}
@ -84,10 +87,17 @@ class PeerAvatarImageGalleryItem: GalleryItem {
if let indexData = self.entry.indexData {
node._title.set(.single(self.presentationData.strings.Items_NOfM("\(indexData.position + 1)", "\(indexData.totalCount)").0))
}
let previousContentAnimations = node.imageNode.contentAnimations
if synchronous {
node.imageNode.contentAnimations = []
}
node.setEntry(self.entry, synchronous: synchronous)
if synchronous {
node.imageNode.contentAnimations = previousContentAnimations
}
node.footerContentNode.delete = self.delete
node.footerContentNode.setMain = self.setMain
node.edit = self.edit
}
}
@ -125,7 +135,7 @@ final class PeerAvatarImageGalleryItemNode: ZoomableContentGalleryItemNode {
private var entry: AvatarGalleryEntry?
private let contentNode: PeerAvatarImageGalleryContentNode
private let imageNode: TransformImageNode
fileprivate let imageNode: TransformImageNode
private var videoNode: UniversalVideoNode?
private var videoContent: NativeVideoContent?
@ -142,6 +152,8 @@ final class PeerAvatarImageGalleryItemNode: ZoomableContentGalleryItemNode {
private var status: MediaResourceStatus?
private let playbackStatusDisposable = MetaDisposable()
fileprivate var edit: (() -> Void)?
init(context: AccountContext, presentationData: PresentationData, peer: Peer, sourceHasRoundCorners: Bool) {
self.context = context
self.presentationData = presentationData
@ -212,7 +224,7 @@ final class PeerAvatarImageGalleryItemNode: ZoomableContentGalleryItemNode {
var footerContent: AvatarGalleryItemFooterContent
if self.peer.id == self.context.account.peerId {
footerContent = .own((entry.indexData?.position ?? 0) == 0)
let rightBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Settings_EditProfileMedia, style: .plain, target: self, action: #selector(editPressed))
let rightBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Settings_EditProfileMedia, style: .plain, target: self, action: #selector(self.editPressed))
barButtonItems.append(rightBarButtonItem)
} else {
footerContent = .info
@ -507,7 +519,7 @@ final class PeerAvatarImageGalleryItemNode: ZoomableContentGalleryItemNode {
}
@objc private func editPressed() {
self.edit?()
}
override func footerContent() -> Signal<(GalleryFooterContentNode?, GalleryOverlayContentNode?), NoError> {