#import "TGCameraController.h"

#import "LegacyComponentsInternal.h"

#import <objc/runtime.h>

#import <LegacyComponents/TGPaintUtils.h>
#import <LegacyComponents/TGPhotoEditorUtils.h>
#import <LegacyComponents/TGPhotoEditorAnimation.h>

#import <LegacyComponents/PGCamera.h>
#import <LegacyComponents/PGCameraCaptureSession.h>
#import <LegacyComponents/PGCameraDeviceAngleSampler.h>
#import <LegacyComponents/PGCameraVolumeButtonHandler.h>

#import <LegacyComponents/TGCameraPreviewView.h>
#import <LegacyComponents/TGCameraMainPhoneView.h>
#import <LegacyComponents/TGCameraMainTabletView.h>
#import "TGCameraFocusCrosshairsControl.h"
#import "TGCameraRectangleView.h"

#import <LegacyComponents/TGFullscreenContainerView.h>
#import <LegacyComponents/TGPhotoEditorController.h>

#import <LegacyComponents/TGModernGalleryController.h>
#import <LegacyComponents/TGMediaPickerGalleryModel.h>
#import <LegacyComponents/TGMediaPickerGalleryPhotoItem.h>
#import <LegacyComponents/TGMediaPickerGalleryVideoItem.h>
#import <LegacyComponents/TGMediaPickerGalleryVideoItemView.h>
#import <LegacyComponents/TGModernGalleryVideoView.h>

#import "TGMediaVideoConverter.h"
#import <LegacyComponents/TGMediaAssetImageSignals.h>
#import <LegacyComponents/PGPhotoEditorValues.h>
#import <LegacyComponents/TGVideoEditAdjustments.h>
#import <LegacyComponents/TGPaintingData.h>
#import <LegacyComponents/UIImage+TGMediaEditableItem.h>
#import <LegacyComponents/AVURLAsset+TGMediaItem.h>

#import <LegacyComponents/TGModernGalleryZoomableScrollViewSwipeGestureRecognizer.h>

#import <LegacyComponents/TGMediaAssetsLibrary.h>

#import <LegacyComponents/TGTimerTarget.h>

#import <LegacyComponents/TGMenuSheetController.h>
#import <LegacyComponents/TGMediaPickerSendActionSheetController.h>

#import "TGMediaPickerGallerySelectedItemsModel.h"
#import "TGCameraCapturedPhoto.h"
#import "TGCameraCapturedVideo.h"

#import "PGPhotoEditor.h"
#import "PGRectangleDetector.h"
#import "TGWarpedView.h"

#import "TGAnimationUtils.h"

const CGFloat TGCameraSwipeMinimumVelocity = 600.0f;
const CGFloat TGCameraSwipeVelocityThreshold = 700.0f;
const CGFloat TGCameraSwipeDistanceThreshold = 128.0f;
const NSTimeInterval TGCameraMinimumClipDuration = 4.0f;

@implementation TGCameraControllerWindow

static CGPoint TGCameraControllerClampPointToScreenSize(__unused id self, __unused SEL _cmd, CGPoint point)
{
    CGSize screenSize = TGScreenSize();
    return CGPointMake(MAX(0, MIN(point.x, screenSize.width)), MAX(0, MIN(point.y, screenSize.height)));
}

+ (void)initialize
{
    static bool initialized = false;
    if (!initialized)
    {
        initialized = true;
        
        if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPhone && (iosMajorVersion() > 8 || (iosMajorVersion() == 8 && iosMinorVersion() >= 3)))
        {
            FreedomDecoration instanceDecorations[] =
            {
                { .name = 0x4ea0b831U,
                    .imp = (IMP)&TGCameraControllerClampPointToScreenSize,
                    .newIdentifier = FreedomIdentifierEmpty,
                    .newEncoding = FreedomIdentifierEmpty
                }
            };
            
            freedomClassAutoDecorate(0x913b3af6, NULL, 0, instanceDecorations, sizeof(instanceDecorations) / sizeof(instanceDecorations[0]));
        }
    }
}

@end

@interface TGCameraController () <UIGestureRecognizerDelegate>
{
    bool _standalone;
    
    TGCameraControllerIntent _intent;
    PGCamera *_camera;
    PGCameraVolumeButtonHandler *_buttonHandler;
    
    UIView *_autorotationCorrectionView;
    
    UIView *_backgroundView;
    TGCameraPreviewView *_previewView;
    TGCameraMainView *_interfaceView;
    TGCameraCornersView *_cornersView;
    UIView *_overlayView;
    TGCameraFocusCrosshairsControl *_focusControl;
    TGCameraRectangleView *_rectangleView;
    
    UISwipeGestureRecognizer *_photoSwipeGestureRecognizer;
    UISwipeGestureRecognizer *_videoSwipeGestureRecognizer;
    TGModernGalleryZoomableScrollViewSwipeGestureRecognizer *_panGestureRecognizer;
    UIPinchGestureRecognizer *_pinchGestureRecognizer;
    
    CGFloat _dismissProgress;
    bool _dismissing;
    bool _finishedWithResult;
    
    TGMediaPickerGallerySelectedItemsModel *_selectedItemsModel;
    NSMutableArray<id<TGMediaEditableItem, TGMediaSelectableItem>> *_items;
    TGMediaEditingContext *_editingContext;
    TGMediaSelectionContext *_selectionContext;
    
    NSTimer *_switchToVideoTimer;
    NSTimer *_startRecordingTimer;
    bool _stopRecordingOnRelease;
    bool _shownMicrophoneAlert;
    
    id<LegacyComponentsContext> _context;
    bool _saveEditedPhotos;
    bool _saveCapturedMedia;
    
    bool _shutterIsBusy;
    bool _crossfadingForZoom;
    
    UIImpactFeedbackGenerator *_feedbackGenerator;
    
    NSMutableSet *_previousQRCodes;
}
@end

@implementation TGCameraController

- (instancetype)initWithContext:(id<LegacyComponentsContext>)context saveEditedPhotos:(bool)saveEditedPhotos saveCapturedMedia:(bool)saveCapturedMedia
{
    return [self initWithContext:context saveEditedPhotos:saveEditedPhotos saveCapturedMedia:saveCapturedMedia intent:TGCameraControllerGenericIntent];
}

- (instancetype)initWithContext:(id<LegacyComponentsContext>)context saveEditedPhotos:(bool)saveEditedPhotos saveCapturedMedia:(bool)saveCapturedMedia intent:(TGCameraControllerIntent)intent
{
    return [self initWithContext:context saveEditedPhotos:saveEditedPhotos saveCapturedMedia:saveCapturedMedia camera:[[PGCamera alloc] init] previewView:nil intent:intent];
}

- (instancetype)initWithContext:(id<LegacyComponentsContext>)context saveEditedPhotos:(bool)saveEditedPhotos saveCapturedMedia:(bool)saveCapturedMedia camera:(PGCamera *)camera previewView:(TGCameraPreviewView *)previewView intent:(TGCameraControllerIntent)intent
{
    self = [super initWithContext:context];
    if (self != nil)
    {
        _context = context;
        if (previewView == nil)
            _standalone = true;
        _intent = intent;
        _camera = camera;
        _previewView = previewView;
        
        _items = [[NSMutableArray alloc] init];
        
        if (_intent != TGCameraControllerGenericIntent) {
            _allowCaptions = false;
        }
        if (_intent == TGCameraControllerGenericVideoOnlyIntent || _intent == TGCameraControllerGenericPhotoOnlyIntent) {
            _allowCaptions = false;
        }
        _saveEditedPhotos = saveEditedPhotos;
        _saveCapturedMedia = saveCapturedMedia;
        
        _previousQRCodes = [[NSMutableSet alloc] init];
        
        if (iosMajorVersion() >= 10) {
            _feedbackGenerator = [[UIImpactFeedbackGenerator alloc] initWithStyle:UIImpactFeedbackStyleLight];
        }
    }
    return self;
}

- (void)dealloc
{
    _camera.beganModeChange = nil;
    _camera.finishedModeChange = nil;
    _camera.beganPositionChange = nil;
    _camera.finishedPositionChange = nil;
    _camera.beganAdjustingFocus = nil;
    _camera.finishedAdjustingFocus = nil;
    _camera.flashActivityChanged = nil;
    _camera.flashAvailabilityChanged = nil;
    _camera.beganVideoRecording = nil;
    _camera.finishedVideoRecording = nil;
    _camera.captureInterrupted = nil;
    _camera.requestedCurrentInterfaceOrientation = nil;
    _camera.deviceAngleSampler.deviceOrientationChanged = nil;

    PGCamera *camera = _camera;
    if (_finishedWithResult || _standalone)
        [camera stopCaptureForPause:false completion:nil];
    
    [[[LegacyComponentsGlobals provider] applicationInstance] setIdleTimerDisabled:false];
}

- (void)loadView
{
    [super loadView];
    object_setClass(self.view, [TGFullscreenContainerView class]);
    
    CGSize screenSize = TGScreenSize();
    CGRect screenBounds = CGRectMake(0, 0, screenSize.width, screenSize.height);
    
    if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPhone)
        self.view.frame = screenBounds;
    
    _autorotationCorrectionView = [[UIView alloc] initWithFrame:screenBounds];
    _autorotationCorrectionView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
    [self.view addSubview:_autorotationCorrectionView];
        
    _backgroundView = [[UIView alloc] initWithFrame:screenBounds];
    _backgroundView.backgroundColor = [UIColor blackColor];
    [_autorotationCorrectionView addSubview:_backgroundView];
    
    if (_previewView == nil)
    {
        _previewView = [[TGCameraPreviewView alloc] initWithFrame:[TGCameraController _cameraPreviewFrameForScreenSize:screenSize mode:PGCameraModePhoto]];
        [_camera attachPreviewView:_previewView];
        [_autorotationCorrectionView addSubview:_previewView];
    }
    
    _overlayView = [[UIView alloc] initWithFrame:screenBounds];
    _overlayView.clipsToBounds = true;
    _overlayView.frame = [TGCameraController _cameraPreviewFrameForScreenSize:screenSize mode:_camera.cameraMode];
    [_autorotationCorrectionView addSubview:_overlayView];
    
    UIInterfaceOrientation interfaceOrientation = [[LegacyComponentsGlobals provider] applicationStatusBarOrientation];
    
    if (interfaceOrientation == UIInterfaceOrientationPortrait)
        interfaceOrientation = [TGCameraController _interfaceOrientationForDeviceOrientation:_camera.deviceAngleSampler.deviceOrientation];
    
    __weak TGCameraController *weakSelf = self;
    _focusControl = [[TGCameraFocusCrosshairsControl alloc] initWithFrame:_overlayView.bounds];
    _focusControl.enabled = (_camera.supportsFocusPOI || _camera.supportsExposurePOI);
    _focusControl.stopAutomatically = (_focusControl.enabled && !_camera.supportsFocusPOI);
    _focusControl.previewView = _previewView;
    _focusControl.focusPOIChanged = ^(CGPoint point)
    {
        __strong TGCameraController *strongSelf = weakSelf;
        if (strongSelf == nil)
            return;
        
        [strongSelf->_camera setFocusPoint:point];
    };
    _focusControl.beganExposureChange = ^
    {
        __strong TGCameraController *strongSelf = weakSelf;
        if (strongSelf == nil)
            return;
        
        [strongSelf->_camera beginExposureTargetBiasChange];
    };
    _focusControl.exposureChanged = ^(CGFloat value)
    {
        __strong TGCameraController *strongSelf = weakSelf;
        if (strongSelf == nil)
            return;
        
        [strongSelf->_camera setExposureTargetBias:value];
    };
    _focusControl.endedExposureChange = ^
    {
        __strong TGCameraController *strongSelf = weakSelf;
        if (strongSelf == nil)
            return;
        
        [strongSelf->_camera endExposureTargetBiasChange];
    };
    [_focusControl setInterfaceOrientation:interfaceOrientation animated:false];
    [_overlayView addSubview:_focusControl];
    
    _rectangleView = [[TGCameraRectangleView alloc] initWithFrame:_overlayView.bounds];
    _rectangleView.previewView = _previewView;
    _rectangleView.hidden = true;
    [_overlayView addSubview:_rectangleView];
    
    if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone)
    {
        _panGestureRecognizer = [[TGModernGalleryZoomableScrollViewSwipeGestureRecognizer alloc] initWithTarget:self action:@selector(handlePan:)];
        _panGestureRecognizer.delegate = self;
        _panGestureRecognizer.delaysTouchesBegan = true;
        _panGestureRecognizer.cancelsTouchesInView = false;
        [_overlayView addGestureRecognizer:_panGestureRecognizer];
    }
    
    _pinchGestureRecognizer = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(handlePinch:)];
    _pinchGestureRecognizer.delegate = self;
    [_overlayView addGestureRecognizer:_pinchGestureRecognizer];
    
    if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone)
    {
        _interfaceView = [[TGCameraMainPhoneView alloc] initWithFrame:screenBounds avatar:_intent == TGCameraControllerAvatarIntent videoModeByDefault:_intent == TGCameraControllerGenericVideoOnlyIntent hasUltrawideCamera:_camera.hasUltrawideCamera hasTelephotoCamera:_camera.hasTelephotoCamera camera:_camera];
        [_interfaceView setInterfaceOrientation:interfaceOrientation animated:false];
    }
    else
    {
        _interfaceView = [[TGCameraMainTabletView alloc] initWithFrame:screenBounds avatar:_intent == TGCameraControllerAvatarIntent videoModeByDefault:_intent == TGCameraControllerGenericVideoOnlyIntent hasUltrawideCamera:_camera.hasUltrawideCamera hasTelephotoCamera:_camera.hasTelephotoCamera camera:_camera];
        [_interfaceView setInterfaceOrientation:interfaceOrientation animated:false];
        
        CGSize referenceSize = [self referenceViewSizeForOrientation:interfaceOrientation];
        if (referenceSize.width > referenceSize.height)
            referenceSize = CGSizeMake(referenceSize.height, referenceSize.width);
        
        _interfaceView.transform = CGAffineTransformMakeRotation(TGRotationForInterfaceOrientation(interfaceOrientation));
        _interfaceView.frame = CGRectMake(0, 0, referenceSize.width, referenceSize.height);
    }
    
    _cornersView = [[TGCameraCornersView alloc] init];
    
    if (_intent == TGCameraControllerPassportIdIntent)
        [_interfaceView setDocumentFrameHidden:false];
    _selectedItemsModel = [[TGMediaPickerGallerySelectedItemsModel alloc] initWithSelectionContext:nil items:[_items copy]];
    [_interfaceView setSelectedItemsModel:_selectedItemsModel];
    _selectedItemsModel.selectionUpdated = ^(bool reload, bool incremental, bool add, NSInteger index)
    {
        __strong TGCameraController *strongSelf = weakSelf;
        if (strongSelf == nil)
            return;
        
        [strongSelf->_interfaceView updateSelectedPhotosView:reload incremental:incremental add:add index:index];
        NSInteger count = strongSelf->_items.count;
        [strongSelf->_interfaceView updateSelectionInterface:count counterVisible:count > 0 animated:true];
    };
    _interfaceView.thumbnailSignalForItem = ^SSignal *(id item)
    {
        __strong TGCameraController *strongSelf = weakSelf;
        if (strongSelf != nil)
            return [strongSelf _signalForItem:item];
        return nil;
    };
    _interfaceView.requestedVideoRecordingDuration = ^NSTimeInterval
    {
        __strong TGCameraController *strongSelf = weakSelf;
        if (strongSelf == nil)
            return 0.0;
        
        return strongSelf->_camera.videoRecordingDuration;
    };
    
    _interfaceView.cameraFlipped = ^
    {
        __strong TGCameraController *strongSelf = weakSelf;
        if (strongSelf == nil)
            return;
        
        [strongSelf->_camera togglePosition];
    };
    
    _interfaceView.cameraShouldLeaveMode = ^bool(__unused PGCameraMode mode)
    {
        return true;
    };
    _interfaceView.cameraModeChanged = ^(PGCameraMode mode)
    {
        __strong TGCameraController *strongSelf = weakSelf;
        if (strongSelf == nil)
            return;
        
        [strongSelf _updateCameraMode:mode updateInterface:false];
    };
    
    _interfaceView.flashModeChanged = ^(PGCameraFlashMode mode)
    {
        __strong TGCameraController *strongSelf = weakSelf;
        if (strongSelf == nil)
            return;
        
        [strongSelf->_camera setFlashMode:mode];
    };
    
    _interfaceView.zoomChanged = ^(CGFloat level, bool animated)
    {
        __strong TGCameraController *strongSelf = weakSelf;
        if (strongSelf == nil)
            return;
        
        [strongSelf->_camera setZoomLevel:level animated:animated];
    };
    
    _interfaceView.shutterPressed = ^(bool fromHardwareButton)
    {
        __strong TGCameraController *strongSelf = weakSelf;
        if (strongSelf == nil)
            return;
        
        if (fromHardwareButton)
            [strongSelf->_interfaceView setShutterButtonHighlighted:true];
        
        [strongSelf shutterPressed];
    };
        
    _interfaceView.shutterReleased = ^(bool fromHardwareButton)
    {
        __strong TGCameraController *strongSelf = weakSelf;
        if (strongSelf == nil)
            return;
        
        if (fromHardwareButton)
            [strongSelf->_interfaceView setShutterButtonHighlighted:false];
        
        if (strongSelf->_previewView.hidden)
            return;
        
        [strongSelf shutterReleased];
    };
    
    _interfaceView.shutterPanGesture = ^(UIPanGestureRecognizer *gesture) {
        __strong TGCameraController *strongSelf = weakSelf;
        if (strongSelf == nil)
            return;
        
        [strongSelf handleRamp:gesture];
    };
    
    _interfaceView.cancelPressed = ^
    {
        __strong TGCameraController *strongSelf = weakSelf;
        if (strongSelf != nil)
            [strongSelf cancelPressed];
    };
    _interfaceView.resultPressed = ^(NSInteger index)
    {
        __strong TGCameraController *strongSelf = weakSelf;
        if (strongSelf != nil)
            [strongSelf presentResultControllerForItem:index == -1 ? nil : strongSelf->_items[index] completion:nil];
    };
    _interfaceView.itemRemoved = ^(NSInteger index)
    {
        __strong TGCameraController *strongSelf = weakSelf;
        if (strongSelf != nil)
        {
            id item = [strongSelf->_items objectAtIndex:index];
            [strongSelf->_selectionContext setItem:item selected:false];
            [strongSelf->_items removeObjectAtIndex:index];
            [strongSelf->_selectedItemsModel removeSelectedItem:item];
            [strongSelf->_interfaceView setResults:[strongSelf->_items copy]];
        }
    };
    
    if (_intent != TGCameraControllerGenericIntent && _intent != TGCameraControllerAvatarIntent) {
        [_interfaceView setHasModeControl:false];
    }
    if (_intent == TGCameraControllerGenericVideoOnlyIntent || _intent == TGCameraControllerGenericPhotoOnlyIntent) {
        [_interfaceView setHasModeControl:false];
    }

    if (@available(iOS 11.0, *)) {
        _backgroundView.accessibilityIgnoresInvertColors = true;
        _interfaceView.accessibilityIgnoresInvertColors = true;
        _focusControl.accessibilityIgnoresInvertColors = true;
    }
    
    [_autorotationCorrectionView addSubview:_interfaceView];
    if ((int)self.view.frame.size.width > 320 || [[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone) {
        [_autorotationCorrectionView addSubview:_cornersView];
    }
     
    _photoSwipeGestureRecognizer = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(handleSwipe:)];
    _photoSwipeGestureRecognizer.delegate = self;
    [_autorotationCorrectionView addGestureRecognizer:_photoSwipeGestureRecognizer];
    
    _videoSwipeGestureRecognizer = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(handleSwipe:)];
    _videoSwipeGestureRecognizer.delegate = self;
    [_autorotationCorrectionView addGestureRecognizer:_videoSwipeGestureRecognizer];
    
    if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone)
    {
        _photoSwipeGestureRecognizer.direction = UISwipeGestureRecognizerDirectionLeft;
        _videoSwipeGestureRecognizer.direction = UISwipeGestureRecognizerDirectionRight;
    }
    else
    {
        _photoSwipeGestureRecognizer.direction = UISwipeGestureRecognizerDirectionUp;
        _videoSwipeGestureRecognizer.direction = UISwipeGestureRecognizerDirectionDown;
    }
    
    void (^buttonPressed)(void) = ^
    {
        __strong TGCameraController *strongSelf = weakSelf;
        if (strongSelf == nil)
            return;
        
        strongSelf->_interfaceView.shutterPressed(true);
    };

    void (^buttonReleased)(void) = ^
    {
        __strong TGCameraController *strongSelf = weakSelf;
        if (strongSelf == nil)
            return;
        
        strongSelf->_interfaceView.shutterReleased(true);
    };
    
    _buttonHandler = [[PGCameraVolumeButtonHandler alloc] initWithIsCameraSpecific:true eventView:self.view upButtonPressedBlock:buttonPressed upButtonReleasedBlock:buttonReleased downButtonPressedBlock:buttonPressed downButtonReleasedBlock:buttonReleased];
    
    [self _configureCamera];
}

- (void)_updateCameraMode:(PGCameraMode)mode updateInterface:(bool)updateInterface {
    [_camera setCameraMode:mode];
    if (updateInterface)
        [_interfaceView setCameraMode:mode];
    
    _focusControl.hidden = mode == PGCameraModePhotoScan;
    _rectangleView.hidden = mode != PGCameraModePhotoScan;
    
    if (mode == PGCameraModePhotoScan) {
        [self _createContextsIfNeeded];
        
        if (_items.count == 0) {
            [_interfaceView setToastMessage:@"Position the document in view" animated:true];
        } else {
            
        }
    }
}

- (void)_configureCamera
{
    __weak TGCameraController *weakSelf = self;
    _camera.requestedCurrentInterfaceOrientation = ^UIInterfaceOrientation(bool *mirrored)
    {
        __strong TGCameraController *strongSelf = weakSelf;
        if (strongSelf == nil)
            return UIInterfaceOrientationUnknown;
        
        if (strongSelf->_intent == TGCameraControllerPassportIdIntent)
            return UIInterfaceOrientationPortrait;
        
        if (mirrored != NULL)
        {
            TGCameraPreviewView *previewView = strongSelf->_previewView;
            if (previewView != nil)
                *mirrored = previewView.captureConnection.videoMirrored;
        }
        
        return [strongSelf->_interfaceView interfaceOrientation];
    };
    
    _camera.beganModeChange = ^(PGCameraMode mode, void(^commitBlock)(void))
    {
        __strong TGCameraController *strongSelf = weakSelf;
        if (strongSelf == nil)
            return;
        
        strongSelf->_buttonHandler.ignoring = true;
        
        [strongSelf->_focusControl reset];
        strongSelf->_focusControl.active = false;
        
        strongSelf.view.userInteractionEnabled = false;
        
        PGCameraMode currentMode = strongSelf->_camera.cameraMode;
        bool generalModeNotChanged = [PGCamera isPhotoCameraMode:mode] == [PGCamera isPhotoCameraMode:currentMode];
        if (strongSelf->_camera.captureSession.currentCameraPosition == PGCameraPositionFront && mode == PGCameraModePhotoScan) {
            generalModeNotChanged = false;
        }
        if ([PGCamera isVideoCameraMode:mode] && !generalModeNotChanged)
        {
            [[LegacyComponentsGlobals provider] pauseMusicPlayback];
        }
        
        if (generalModeNotChanged)
        {
            if (commitBlock != nil)
                commitBlock();
        }
        else
        {
            [strongSelf->_camera captureNextFrameCompletion:^(UIImage *image)
            {
                if (commitBlock != nil)
                    commitBlock();
                 
                image = TGCameraModeSwitchImage(image, CGSizeMake(image.size.width, image.size.height));
                 
                TGDispatchOnMainThread(^
                {
                    [strongSelf->_previewView beginTransitionWithSnapshotImage:image animated:true];
                });
            }];
        }
    };
    
    _camera.finishedModeChange = ^
    {
        __strong TGCameraController *strongSelf = weakSelf;
        if (strongSelf == nil)
            return;
        
        TGDispatchOnMainThread(^
        {
            [strongSelf->_interfaceView setZoomLevel:1.0f displayNeeded:false];
            
            if (!strongSelf->_dismissing)
            {
                strongSelf.view.userInteractionEnabled = true;
                [strongSelf resizePreviewViewForCameraMode:strongSelf->_camera.cameraMode];
                
                strongSelf->_focusControl.active = true;
                [strongSelf->_interfaceView setFlashMode:strongSelf->_camera.flashMode];

                [strongSelf->_buttonHandler enableIn:1.5f];
                
                if (strongSelf->_camera.cameraMode == PGCameraModeVideo && ([PGCamera microphoneAuthorizationStatus] == PGMicrophoneAuthorizationStatusRestricted || [PGCamera microphoneAuthorizationStatus] == PGMicrophoneAuthorizationStatusDenied) && !strongSelf->_shownMicrophoneAlert)
                {
                    [[[LegacyComponentsGlobals provider] accessChecker] checkMicrophoneAuthorizationStatusForIntent:TGMicrophoneAccessIntentVideo alertDismissCompletion:nil];
                    strongSelf->_shownMicrophoneAlert = true;
                }
                
                if (strongSelf->_camera.cameraMode == PGCameraModePhotoScan) {
                    strongSelf->_camera.captureSession.rectangleDetector.update = ^(bool capture, PGRectangle *rectangle) {
                        __strong TGCameraController *strongSelf = weakSelf;
                        if (strongSelf == nil)
                            return;
                        
                        TGDispatchOnMainThread(^{
                            [strongSelf->_rectangleView drawRectangle:rectangle];
                            if (capture) {
                                [strongSelf _makeScan:rectangle];
                            }
                        });
                    };
                }
            }
            
            [strongSelf->_previewView endTransitionAnimated:true];
        });
    };
    
    _camera.beganPositionChange = ^(bool targetPositionHasFlash, bool targetPositionHasZoom, void(^commitBlock)(void))
    {
        __strong TGCameraController *strongSelf = weakSelf;
        if (strongSelf == nil)
            return;
        
        [strongSelf->_focusControl reset];
        
        [strongSelf->_interfaceView setHasFlash:targetPositionHasFlash];
        if (!targetPositionHasZoom) {
            [strongSelf->_interfaceView setHasZoom:targetPositionHasZoom];
        }
        strongSelf->_camera.zoomLevel = 0.0f;
        
        strongSelf.view.userInteractionEnabled = false;
        
        [strongSelf->_camera captureNextFrameCompletion:^(UIImage *image)
        {
            if (commitBlock != nil)
                commitBlock();
             
            image = TGCameraPositionSwitchImage(image, CGSizeMake(image.size.width, image.size.height));
             
            TGDispatchOnMainThread(^
            {
                [UIView transitionWithView:strongSelf->_previewView duration:0.4f options:UIViewAnimationOptionTransitionFlipFromLeft | UIViewAnimationOptionCurveEaseOut animations:^
                {
                    [strongSelf->_previewView beginTransitionWithSnapshotImage:image animated:false];
                } completion:^(__unused BOOL finished)
                {
                    strongSelf.view.userInteractionEnabled = true;
                }];
            });
        }];
        
        if (@available(iOS 13.0, *)) {
            [strongSelf->_feedbackGenerator impactOccurredWithIntensity:0.5];
        } else {
            [strongSelf->_feedbackGenerator impactOccurred];
        }
    };
    
    _camera.finishedPositionChange = ^(bool targetPositionHasZoom)
    {
        __strong TGCameraController *strongSelf = weakSelf;
        if (strongSelf == nil)
            return;
        
        TGDispatchOnMainThread(^
        {
            [strongSelf->_previewView endTransitionAnimated:true];
            [strongSelf->_interfaceView setZoomLevel:1.0f displayNeeded:false];

            if (targetPositionHasZoom) {
                [strongSelf->_interfaceView setHasZoom:targetPositionHasZoom];
            }
            
            if (strongSelf->_camera.hasFlash && strongSelf->_camera.flashActive)
                [strongSelf->_interfaceView setFlashActive:true];
                                   
            strongSelf->_focusControl.enabled = (strongSelf->_camera.supportsFocusPOI || strongSelf->_camera.supportsExposurePOI);
            strongSelf->_focusControl.stopAutomatically = (strongSelf->_focusControl.enabled && !strongSelf->_camera.supportsFocusPOI);
        });
    };
    
    _camera.beganAdjustingFocus = ^
    {
        __strong TGCameraController *strongSelf = weakSelf;
        if (strongSelf == nil)
            return;
        
        [strongSelf->_focusControl playAutoFocusAnimation];
    };
    
    _camera.finishedAdjustingFocus = ^
    {
        __strong TGCameraController *strongSelf = weakSelf;
        if (strongSelf == nil)
            return;
        
        [strongSelf->_focusControl stopAutoFocusAnimation];
    };
    
    _camera.flashActivityChanged = ^(bool active)
    {
        __strong TGCameraController *strongSelf = weakSelf;
        if (strongSelf == nil)
            return;
        
        if (strongSelf->_camera.flashMode != PGCameraFlashModeAuto)
            active = false;
        
        TGDispatchOnMainThread(^
        {
            [strongSelf->_interfaceView setFlashActive:active];
        });
    };
    
    _camera.flashAvailabilityChanged = ^(bool available)
    {
        __strong TGCameraController *strongSelf = weakSelf;
        if (strongSelf == nil)
            return;
        
        [strongSelf->_interfaceView setFlashUnavailable:!available];
    };
    
    _camera.beganVideoRecording = ^(__unused bool moment)
    {
        __strong TGCameraController *strongSelf = weakSelf;
        if (strongSelf == nil)
            return;
        
        strongSelf->_focusControl.ignoreAutofocusing = true;
        [strongSelf->_interfaceView setRecordingVideo:true animated:true];
    };
    
    _camera.captureInterrupted = ^(AVCaptureSessionInterruptionReason reason)
    {
        __strong TGCameraController *strongSelf = weakSelf;
        if (strongSelf == nil)
            return;
        
        if (reason == AVCaptureSessionInterruptionReasonVideoDeviceNotAvailableWithMultipleForegroundApps)
            [strongSelf beginTransitionOutWithVelocity:0.0f];
    };
    
    _camera.finishedVideoRecording = ^(__unused bool moment)
    {
        __strong TGCameraController *strongSelf = weakSelf;
        if (strongSelf == nil)
            return;
        
        strongSelf->_focusControl.ignoreAutofocusing = false;
        [strongSelf->_interfaceView setFlashMode:PGCameraFlashModeOff];
    };
    
    _camera.deviceAngleSampler.deviceOrientationChanged = ^(UIDeviceOrientation orientation)
    {
        __strong TGCameraController *strongSelf = weakSelf;
        if (strongSelf == nil)
            return;
        
        [strongSelf handleDeviceOrientationChangedTo:orientation];
    };
    
    _camera.captureSession.recognizedQRCode = ^(NSString *value, AVMetadataMachineReadableCodeObject *object)
    {
        __strong TGCameraController *strongSelf = weakSelf;
        if (strongSelf != nil && strongSelf.recognizedQRCode != nil)
        {
            if (![strongSelf->_previousQRCodes containsObject:value])
            {
                strongSelf.recognizedQRCode(value);
                [strongSelf->_previousQRCodes addObject:value];
            }
        }
    };
    
//    _camera.captureSession.crossfadeNeeded = ^{
//        __strong TGCameraController *strongSelf = weakSelf;
//        if (strongSelf != nil)
//        {
//            if (strongSelf->_crossfadingForZoom) {
//                return;
//            }
//            strongSelf->_crossfadingForZoom = true;
//            
//            [strongSelf->_camera captureNextFrameCompletion:^(UIImage *image)
//            {
//                TGDispatchOnMainThread(^
//                {
//                    [strongSelf->_previewView beginTransitionWithSnapshotImage:image animated:false];
//                    
//                    TGDispatchAfter(0.15, dispatch_get_main_queue(), ^{
//                        [strongSelf->_previewView endTransitionAnimated:true];
//                        strongSelf->_crossfadingForZoom = false;
//                    });
//                });
//            }];
//        };
//    };
}

#pragma mark - View Life Cycle

- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];
    
    [UIView animateWithDuration:0.3f animations:^
    {
        [_context setApplicationStatusBarAlpha:0.0f];
    }];
    
    [[[LegacyComponentsGlobals provider] applicationInstance] setIdleTimerDisabled:true];
    
    if (!_camera.isCapturing)
        [_camera startCaptureForResume:false completion:nil];
}

- (void)viewWillDisappear:(BOOL)animated
{
    [super viewWillDisappear:animated];
    
    [UIView animateWithDuration:0.3f animations:^
    {
        //[_context setApplicationStatusBarAlpha:1.0f];
    }];
}

- (void)viewWillLayoutSubviews
{
    [super viewWillLayoutSubviews];
    
    if ([self shouldCorrectAutorotation])
        [self applyAutorotationCorrectingTransformForOrientation:[[LegacyComponentsGlobals provider] applicationStatusBarOrientation]];
}

- (bool)shouldCorrectAutorotation
{
    return [UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad;
}

- (void)applyAutorotationCorrectingTransformForOrientation:(UIInterfaceOrientation)orientation
{
    CGSize screenSize = TGScreenSize();
    CGRect screenBounds = CGRectMake(0, 0, screenSize.width, screenSize.height);
    
    _autorotationCorrectionView.transform = CGAffineTransformIdentity;
    _autorotationCorrectionView.frame = screenBounds;
    
    CGAffineTransform transform = CGAffineTransformIdentity;
    switch (orientation)
    {
        case UIInterfaceOrientationPortraitUpsideDown:
            transform = CGAffineTransformMakeRotation(M_PI);
            break;
            
        case UIInterfaceOrientationLandscapeLeft:
            transform = CGAffineTransformMakeRotation(M_PI_2);
            break;
            
        case UIInterfaceOrientationLandscapeRight:
            transform = CGAffineTransformMakeRotation(-M_PI_2);
            break;
            
        default:
            break;
    }
    
    _autorotationCorrectionView.transform = transform;
    CGSize bounds = [_context fullscreenBounds].size;
    _autorotationCorrectionView.center = CGPointMake(bounds.width / 2, bounds.height / 2);
}

- (UIInterfaceOrientationMask)supportedInterfaceOrientations
{
    if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad)
        return UIInterfaceOrientationMaskAll;
    
    return UIInterfaceOrientationMaskPortrait;
}

- (BOOL)shouldAutorotate
{
    if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad)
        return true;
    
    return false;
}

- (void)setInterfaceHidden:(bool)hidden animated:(bool)animated
{
    if (animated)
    {
        if (hidden && _interfaceView.alpha < FLT_EPSILON)
            return;

        CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"opacity"];
        animation.fromValue = @(_interfaceView.alpha);
        animation.toValue = @(hidden ? 0.0f : 1.0f);
        animation.duration = 0.2f;
        [_interfaceView.layer addAnimation:animation forKey:@"opacity"];
        
        CABasicAnimation *cornersAnimation = [CABasicAnimation animationWithKeyPath:@"opacity"];
        cornersAnimation.fromValue = @(_cornersView.alpha);
        cornersAnimation.toValue = @(hidden ? 0.0f : 1.0f);
        cornersAnimation.duration = 0.2f;
        [_cornersView.layer addAnimation:cornersAnimation forKey:@"opacity"];
        
        _interfaceView.alpha = hidden ? 0.0f : 1.0f;
        _cornersView.alpha = hidden ? 0.0 : 1.0;
    }
    else
    {
        [_interfaceView.layer removeAllAnimations];
        _interfaceView.alpha = hidden ? 0.0 : 1.0;
        
        [_cornersView.layer removeAllAnimations];
        _cornersView.alpha = hidden ? 0.0 : 1.0;
    }
}

#pragma mark - 

- (void)startVideoRecording
{
    __weak TGCameraController *weakSelf = self;
    if (_camera.cameraMode == PGCameraModePhoto)
    {
        _switchToVideoTimer = nil;
        
        _camera.onAutoStartVideoRecording = ^
        {
            __strong TGCameraController *strongSelf = weakSelf;
            if (strongSelf == nil)
                return;
            
            strongSelf->_stopRecordingOnRelease = true;
            
            [strongSelf->_camera startVideoRecordingForMoment:false completion:^(NSURL *outputURL, __unused CGAffineTransform transform, CGSize dimensions, NSTimeInterval duration, bool success)
            {
                __strong TGCameraController *strongSelf = weakSelf;
                if (strongSelf == nil)
                    return;
                
                if (success)
                {
                    TGCameraCapturedVideo *capturedVideo = [[TGCameraCapturedVideo alloc] initWithURL:outputURL];
                    [strongSelf addResultItem:capturedVideo];
                    
                    if (![strongSelf maybePresentResultControllerForItem:capturedVideo completion:nil])
                    {
                        strongSelf->_camera.disabled = false;
                        [strongSelf->_interfaceView setRecordingVideo:false animated:true];
                    }
                }
                else
                {
                    [strongSelf->_interfaceView setRecordingVideo:false animated:false];
                }
            }];
        };
        _camera.autoStartVideoRecording = true;
        [self _updateCameraMode:PGCameraModeVideo updateInterface:true];
    }
    else if (_camera.cameraMode == PGCameraModeVideo)
    {
        _startRecordingTimer = nil;
        
        [_camera startVideoRecordingForMoment:false completion:^(NSURL *outputURL, __unused CGAffineTransform transform, CGSize dimensions, NSTimeInterval duration, bool success)
        {
            __strong TGCameraController *strongSelf = weakSelf;
            if (strongSelf == nil)
                return;
            
            if (success)
            {
                TGCameraCapturedVideo *capturedVideo = [[TGCameraCapturedVideo alloc] initWithURL:outputURL];
                [strongSelf addResultItem:capturedVideo];
                
                if (![strongSelf maybePresentResultControllerForItem:capturedVideo completion:nil])
                {
                    strongSelf->_camera.disabled = false;
                    [strongSelf->_interfaceView setRecordingVideo:false animated:true];
                }
            }
            else
            {
                [strongSelf->_interfaceView setRecordingVideo:false animated:false];
            }
        }];

        _stopRecordingOnRelease = true;
    }
}

- (void)shutterPressed
{
    if (@available(iOS 13.0, *)) {
        [_feedbackGenerator impactOccurredWithIntensity:0.5];
    } else {
        [_feedbackGenerator impactOccurred];
    }
    
    PGCameraMode cameraMode = _camera.cameraMode;
    switch (cameraMode)
    {
        case PGCameraModePhoto:
        {
            if (_intent == TGCameraControllerGenericIntent || _intent == TGCameraControllerAvatarIntent)
            {
                _switchToVideoTimer = [TGTimerTarget scheduledMainThreadTimerWithTarget:self action:@selector(startVideoRecording) interval:0.25 repeat:false];
            }
        }
            break;
    
        case PGCameraModeVideo:
        case PGCameraModeSquareVideo:
        case PGCameraModeSquareSwing:
        {
            if (!_camera.isRecordingVideo)
            {
                _startRecordingTimer = [TGTimerTarget scheduledMainThreadTimerWithTarget:self action:@selector(startVideoRecording) interval:0.25 repeat:false];
            }
            else
            {
                _stopRecordingOnRelease = true;
            }
        }
            break;
            
        default:
            break;
    }
}

- (void)shutterReleased
{
    if (@available(iOS 13.0, *)) {
        [_feedbackGenerator impactOccurredWithIntensity:0.6];
    } else {
        [_feedbackGenerator impactOccurred];
    }
    
    [_switchToVideoTimer invalidate];
    _switchToVideoTimer = nil;
    
    [_startRecordingTimer invalidate];
    _startRecordingTimer = nil;
 
    if (_shutterIsBusy)
        return;
    
    __weak TGCameraController *weakSelf = self;
    PGCameraMode cameraMode = _camera.cameraMode;
    if (cameraMode == PGCameraModePhoto || cameraMode == PGCameraModeSquarePhoto || cameraMode == PGCameraModePhotoScan)
    {
        _camera.disabled = true;

        _shutterIsBusy = true;
        
        TGDispatchAfter(0.05, dispatch_get_main_queue(), ^
        {
            [_previewView blink];
        });
        
        if (![self willPresentResultController])
        {
        }
        else
        {
            _buttonHandler.enabled = false;
            [_buttonHandler ignoreEventsFor:1.5f andDisable:true];
        }
                
        [_camera takePhotoWithCompletion:^(UIImage *result, PGCameraShotMetadata *metadata)
        {
            __strong TGCameraController *strongSelf = weakSelf;
            if (strongSelf == nil)
                return;
        
            TGDispatchOnMainThread(^
            {
                strongSelf->_shutterIsBusy = false;
                
                if (strongSelf->_intent == TGCameraControllerAvatarIntent || strongSelf->_intent == TGCameraControllerSignupAvatarIntent)
                {
                    [strongSelf presentPhotoResultControllerWithImage:result metadata:metadata completion:^{}];
                }
                else
                {
                    TGCameraCapturedPhoto *capturedPhoto = [[TGCameraCapturedPhoto alloc] initWithImage:result metadata:metadata];
                    [strongSelf addResultItem:capturedPhoto];
                    
                    if (![strongSelf maybePresentResultControllerForItem:capturedPhoto completion:nil])
                        strongSelf->_camera.disabled = false;
                }
            });
            
            [[SQueue concurrentDefaultQueue] dispatch:^{
                [TGCameraController generateStartImageWithImage:result];
            }];
        }];
    }
    else if (cameraMode == PGCameraModeVideo || cameraMode == PGCameraModeSquareVideo || cameraMode == PGCameraModeSquareSwing)
    {
        if (!_camera.isRecordingVideo)
        {
            [_buttonHandler ignoreEventsFor:1.0f andDisable:false];
            
            [_camera startVideoRecordingForMoment:false completion:^(NSURL *outputURL, __unused CGAffineTransform transform, CGSize dimensions, NSTimeInterval duration, bool success)
            {
                __strong TGCameraController *strongSelf = weakSelf;
                if (strongSelf == nil)
                    return;
                
                if (success)
                {
                    TGCameraCapturedVideo *capturedVideo = [[TGCameraCapturedVideo alloc] initWithURL:outputURL];
                    if (strongSelf->_intent == TGCameraControllerAvatarIntent || strongSelf->_intent == TGCameraControllerSignupAvatarIntent)
                    {
                        [strongSelf presentPhotoResultControllerWithImage:capturedVideo metadata:nil completion:^{
                             [strongSelf->_interfaceView setRecordingVideo:false animated:true];
                        }];
                    } else {
                        [strongSelf addResultItem:capturedVideo];
                        if (![strongSelf maybePresentResultControllerForItem:capturedVideo completion:nil])
                        {
                            strongSelf->_camera.disabled = false;
                            [strongSelf->_interfaceView setRecordingVideo:false animated:true];
                        }
                    }
                }
                else
                {
                    [strongSelf->_interfaceView setRecordingVideo:false animated:false];
                }
            }];
        }
        else if (_stopRecordingOnRelease)
        {
            _stopRecordingOnRelease = false;
            
            [_camera stopVideoRecording];
            TGDispatchAfter(0.3, dispatch_get_main_queue(), ^{
                [_camera setZoomLevel:1.0];
                [_interfaceView setZoomLevel:1.0 displayNeeded:false];
                _camera.disabled = true;
            });
            
            [_buttonHandler ignoreEventsFor:1.0f andDisable:[self willPresentResultController]];
        }
    }
}

- (void)_makeScan:(PGRectangle *)rectangle
{
    if (_shutterIsBusy)
        return;
    
    _camera.disabled = true;
    _shutterIsBusy = true;
    
    __weak TGCameraController *weakSelf = self;
    [_camera takePhotoWithCompletion:^(UIImage *result, PGCameraShotMetadata *metadata)
    {
        __strong TGCameraController *strongSelf = weakSelf;
        if (strongSelf == nil)
            return;
        
        TGDispatchOnMainThread(^
        {
            [strongSelf->_interfaceView setToastMessage:nil animated:true];
            
            strongSelf->_shutterIsBusy = false;
            
            [strongSelf->_rectangleView drawRectangle:nil];
            strongSelf->_rectangleView.enabled = false;
            
            TGDispatchAfter(2.0, dispatch_get_main_queue(), ^{
                strongSelf->_rectangleView.enabled = true;
            });
            
            TGCameraCapturedPhoto *capturedPhoto = [[TGCameraCapturedPhoto alloc] initWithImage:result rectangle:rectangle];
            [strongSelf addResultItem:capturedPhoto];
            
            PGRectangle *cropRectangle = [[rectangle rotate90] transform:CGAffineTransformMakeScale(result.size.width, result.size.height)];
            PGRectangle *convertedRectangle = [cropRectangle sort];
            convertedRectangle = [convertedRectangle cartesian:result.size.height];
            
            CIImage *ciImage = [[CIImage alloc] initWithImage:result];
            CIImage *croppedImage = [ciImage imageByApplyingFilter:@"CIPerspectiveCorrection" withInputParameters:@{
                @"inputTopLeft": [CIVector vectorWithCGPoint:convertedRectangle.topLeft],
                @"inputTopRight": [CIVector vectorWithCGPoint:convertedRectangle.topRight],
                @"inputBottomLeft": [CIVector vectorWithCGPoint:convertedRectangle.bottomLeft],
                @"inputBottomRight": [CIVector vectorWithCGPoint:convertedRectangle.bottomRight]
            }];
            CIImage *enhancedImage = [croppedImage imageByApplyingFilter:@"CIDocumentEnhancer" withInputParameters:@{}];
            
            CIContext *context = [CIContext contextWithOptions:nil];
            UIImage *editedImage = [UIImage imageWithCGImage:[context createCGImage:enhancedImage fromRect:enhancedImage.extent]];
            UIImage *thumbnailImage = TGScaleImage(editedImage, TGScaleToFillSize(editedImage.size, TGPhotoThumbnailSizeForCurrentScreen()));
            [strongSelf->_editingContext setImage:editedImage thumbnailImage:thumbnailImage forItem:capturedPhoto synchronous:true];
            [strongSelf->_editingContext setAdjustments:[PGPhotoEditorValues editorValuesWithOriginalSize:result.size cropRectangle:cropRectangle cropOrientation:UIImageOrientationUp cropSize:editedImage.size enhanceDocument:true paintingData:nil] forItem:capturedPhoto];
            
            [strongSelf _playScanAnimation:editedImage rectangle:rectangle completion:^{
                [strongSelf->_selectedItemsModel addSelectedItem:capturedPhoto];
                [strongSelf->_selectionContext setItem:capturedPhoto selected:true];
                [strongSelf->_interfaceView setResults:[strongSelf->_items copy]];
                
                TGDispatchAfter(0.5, dispatch_get_main_queue(), ^{
                    [strongSelf->_interfaceView setToastMessage:@"Ready for next scan" animated:true];
                });
            }];
            
            strongSelf->_camera.disabled = false;
        });
    }];
}

- (void)_playScanAnimation:(UIImage *)image rectangle:(PGRectangle *)rectangle completion:(void(^)(void))completion
{
    TGWarpedView *warpedView = [[TGWarpedView alloc] initWithImage:image];
    warpedView.layer.anchorPoint = CGPointMake(0, 0);
    warpedView.frame = _rectangleView.frame;
    [_rectangleView.superview addSubview:warpedView];
    
    CGAffineTransform transform = CGAffineTransformMakeScale(_previewView.frame.size.width, _previewView.frame.size.height);
    PGRectangle *displayRectangle = [[[rectangle rotate90] transform:transform] sort];
    [warpedView transformToFitQuadTopLeft:displayRectangle.topLeft topRight:displayRectangle.topRight bottomLeft:displayRectangle.bottomLeft bottomRight:displayRectangle.bottomRight];
    
    CGFloat inset = 16.0f;
    CGSize targetSize = TGScaleToFit(image.size, CGSizeMake(_previewView.frame.size.width - inset * 2.0, _previewView.frame.size.height - inset * 2.0));
    CGRect targetRect = CGRectMake(floor((_previewView.frame.size.width - targetSize.width) / 2.0), floor((_previewView.frame.size.height - targetSize.height) / 2.0), targetSize.width, targetSize.height);
    
    [UIView animateWithDuration:0.3 delay:0.0 options:(7 << 16) animations:^{
        [warpedView transformToFitQuadTopLeft:CGPointMake(targetRect.origin.x, targetRect.origin.y) topRight:CGPointMake(targetRect.origin.x + targetRect.size.width, targetRect.origin.y) bottomLeft:CGPointMake(targetRect.origin.x, targetRect.origin.y + targetRect.size.height) bottomRight:CGPointMake(targetRect.origin.x + targetRect.size.width, targetRect.origin.y + targetRect.size.height)];
    } completion:^(BOOL finished) {
        UIImageView *outView = [[UIImageView alloc] initWithImage:image];
        outView.frame = targetRect;
        [warpedView.superview addSubview:outView];
        [warpedView removeFromSuperview];
        
        TGDispatchAfter(0.2, dispatch_get_main_queue(), ^{
            CGPoint sourcePoint = outView.center;
            CGPoint targetPoint = CGPointMake(_previewView.frame.size.width - 44.0, _previewView.frame.size.height - 44.0);
            CGPoint midPoint = CGPointMake((sourcePoint.x + targetPoint.x) / 2.0, sourcePoint.y - 30.0);
            
            CGFloat x1 = sourcePoint.x;
            CGFloat y1 = sourcePoint.y;
            CGFloat x2 = midPoint.x;
            CGFloat y2 = midPoint.y;
            CGFloat x3 = targetPoint.x;
            CGFloat y3 = targetPoint.y;

            CGFloat a = (x3 * (y2 - y1) + x2 * (y1 - y3) + x1 * (y3 - y2)) / ((x1 - x2) * (x1 - x3) * (x2 - x3));
            CGFloat b = (x1 * x1 * (y2 - y3) + x3 * x3 * (y1 - y2) + x2 * x2 * (y3 - y1)) / ((x1 - x2) * (x1 - x3) * (x2 - x3));
            CGFloat c = (x2 * x2 * (x3 * y1 - x1 * y3) + x2 * (x1 * x1 * y3 - x3 * x3 * y1) + x1 * x3 * (x3 - x1) * y2) / ((x1 - x2) * (x1 - x3) * (x2 - x3));
            
            [UIView animateWithDuration:0.3 animations:^{
                outView.transform = CGAffineTransformMakeScale(0.1, 0.1);
            } completion:^(BOOL finished) {
                [outView removeFromSuperview];
            }];
            
            TGDispatchAfter(0.28, dispatch_get_main_queue(), ^{
                completion();
            });
            
            CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:@"position"];
            NSMutableArray *values = [[NSMutableArray alloc] init];
            NSMutableArray *keyTimes = [[NSMutableArray alloc] init];
            for (NSInteger i = 0; i < 10; i++) {
                CGFloat k = (CGFloat)i / (CGFloat)(10 - 1);
                CGFloat x = sourcePoint.x * (1.0 - k) + targetPoint.x * k;
                CGFloat y = a * x * x + b * x + c;
                
                [values addObject:[NSValue valueWithCGPoint:CGPointMake(x, y)]];
                [keyTimes addObject:@(k)];
            }
            animation.values = values;
            animation.keyTimes = keyTimes;
            animation.duration = 0.35;
            animation.removedOnCompletion = false;
            [outView.layer addAnimation:animation forKey:@"position"];
        });
    }];
}

- (void)cancelPressed
{
    if (_items.count > 0)
    {
        __weak TGCameraController *weakSelf = self;
        
        TGMenuSheetController *controller = [[TGMenuSheetController alloc] initWithContext:_context dark:false];
        controller.dismissesByOutsideTap = true;
        controller.narrowInLandscape = true;
        __weak TGMenuSheetController *weakController = controller;
        
        NSArray *items = @
        [
         [[TGMenuSheetButtonItemView alloc] initWithTitle:TGLocalized(@"Camera.Discard") type:TGMenuSheetButtonTypeDefault fontSize:20.0 action:^
          {
              __strong TGMenuSheetController *strongController = weakController;
              if (strongController == nil)
                  return;
              
              __strong TGCameraController *strongSelf = weakSelf;
              if (strongSelf == nil)
                  return;
              
              [strongController dismissAnimated:true manual:false completion:nil];
              [strongSelf beginTransitionOutWithVelocity:0.0f];
          }],
         [[TGMenuSheetButtonItemView alloc] initWithTitle:TGLocalized(@"Common.Cancel") type:TGMenuSheetButtonTypeCancel fontSize:20.0 action:^
          {
              __strong TGMenuSheetController *strongController = weakController;
              if (strongController != nil)
                  [strongController dismissAnimated:true];
          }]
         ];
        
        [controller setItemViews:items];
        controller.sourceRect = ^
        {
            __strong TGCameraController *strongSelf = weakSelf;
            if (strongSelf == nil)
                return CGRectZero;
            
            UIButton *cancelButton = strongSelf->_interfaceView->_cancelButton;
            return [cancelButton convertRect:cancelButton.bounds toView:strongSelf.view];
        };
        controller.permittedArrowDirections = UIPopoverArrowDirectionAny;
        [controller presentInViewController:self sourceView:self.view animated:true];
    }
    else
    {
        [self beginTransitionOutWithVelocity:0.0f];
    }
}

#pragma mark - Result

- (void)addResultItem:(id<TGMediaEditableItem, TGMediaSelectableItem>)item
{
    [_items addObject:item];
}

- (bool)willPresentResultController
{
    return _items.count == 0 || (_items.count > 0 && (_items.count + 1) % 10 == 0);
}

- (bool)shouldPresentResultController
{
    return _items.count == 1 || (_items.count > 0 && _items.count % 10 == 0);
}

- (bool)maybePresentResultControllerForItem:(id<TGMediaEditableItem, TGMediaSelectableItem>)editableItem completion:(void (^)(void))completion
{
    if ([self shouldPresentResultController])
    {
        [self presentResultControllerForItem:editableItem completion:^
        {
            [_selectedItemsModel addSelectedItem:editableItem];
            [_selectionContext setItem:editableItem selected:true];
            [_interfaceView setResults:[_items copy]];
            if (completion != nil)
                completion();
        }];
        return true;
    }
    else
    {
        [_selectedItemsModel addSelectedItem:editableItem];
        [_selectionContext setItem:editableItem selected:true];
        [_interfaceView setResults:[_items copy]];
        return false;
    }
}

- (NSArray *)prepareGalleryItemsForResults:(void (^)(TGMediaPickerGalleryItem *))enumerationBlock
{
    NSMutableArray *galleryItems = [[NSMutableArray alloc] init];
    for (id<TGMediaEditableItem, TGMediaSelectableItem> item in _items)
    {
        TGMediaPickerGalleryItem<TGModernGallerySelectableItem, TGModernGalleryEditableItem> *galleryItem = nil;
        if ([item isKindOfClass:[TGCameraCapturedPhoto class]])
        {
            galleryItem = [[TGMediaPickerGalleryPhotoItem alloc] initWithAsset:item];
        }
        else if ([item isKindOfClass:[TGCameraCapturedVideo class]])
        {
            galleryItem = [[TGMediaPickerGalleryVideoItem alloc] initWithAsset:item];
        }

        galleryItem.selectionContext = _selectionContext;
        galleryItem.editingContext = _editingContext;
        galleryItem.stickersContext = _stickersContext;
        
        if (enumerationBlock != nil)
            enumerationBlock(galleryItem);
        
        if (galleryItem != nil)
            [galleryItems addObject:galleryItem];
    }
    
    return galleryItems;
}

- (void)_createContextsIfNeeded
{
    TGMediaEditingContext *editingContext = _editingContext;
    if (editingContext == nil)
    {
        editingContext = [[TGMediaEditingContext alloc] init];
        if (self.forcedCaption != nil)
            [editingContext setForcedCaption:self.forcedCaption];
        _editingContext = editingContext;
        _interfaceView.editingContext = editingContext;
    }
    TGMediaSelectionContext *selectionContext = _selectionContext;
    if (selectionContext == nil)
    {
        selectionContext = [[TGMediaSelectionContext alloc] initWithGroupingAllowed:self.allowGrouping selectionLimit:100];
        if (self.allowGrouping)
            selectionContext.grouping = true;
        _selectionContext = selectionContext;
    }
}

- (void)presentResultControllerForItem:(id<TGMediaEditableItem, TGMediaSelectableItem>)editableItemValue completion:(void (^)(void))completion
{
    __block id<TGMediaEditableItem, TGMediaSelectableItem> editableItem = editableItemValue;
    UIViewController *(^begin)(id<LegacyComponentsContext>) = ^(id<LegacyComponentsContext> windowContext) {
        [self _createContextsIfNeeded];
        TGMediaEditingContext *editingContext = _editingContext;
        TGMediaSelectionContext *selectionContext = _selectionContext;
        
        if (editableItem == nil)
            editableItem = _items.lastObject;
        
        [[[LegacyComponentsGlobals provider] applicationInstance] setIdleTimerDisabled:false];
        
        if (_intent == TGCameraControllerPassportIdIntent)
        {
            TGCameraCapturedPhoto *photo = (TGCameraCapturedPhoto *)editableItem;
            CGSize size = photo.originalSize;
            CGFloat height = size.width * 0.704f;
            PGPhotoEditorValues *values = [PGPhotoEditorValues editorValuesWithOriginalSize:size cropRect:CGRectMake(0, floor((size.height - height) / 2.0f), size.width, height) cropRotation:0.0f cropOrientation:UIImageOrientationUp cropLockedAspectRatio:0.0f cropMirrored:false toolValues:nil paintingData:nil sendAsGif:false sendAsTelescope:false];
            
            SSignal *cropSignal = [[photo originalImageSignal:0.0] map:^UIImage *(UIImage *image)
            {
                UIImage *croppedImage = TGPhotoEditorCrop(image, nil, UIImageOrientationUp, 0.0f, values.cropRect, false, TGPhotoEditorResultImageMaxSize, size, true);
                return croppedImage;
            }];
            
            [cropSignal startWithNext:^(UIImage *image)
            {
                CGSize fillSize = TGPhotoThumbnailSizeForCurrentScreen();
                fillSize.width = CGCeil(fillSize.width);
                fillSize.height = CGCeil(fillSize.height);
                
                CGSize size = TGScaleToFillSize(image.size, fillSize);
                
                UIGraphicsBeginImageContextWithOptions(size, true, 0.0f);
                CGContextRef context = UIGraphicsGetCurrentContext();
                CGContextSetInterpolationQuality(context, kCGInterpolationMedium);
                
                [image drawInRect:CGRectMake(0, 0, size.width, size.height)];
                
                UIImage *thumbnailImage = UIGraphicsGetImageFromCurrentImageContext();
                UIGraphicsEndImageContext();
                
                [editingContext setAdjustments:values forItem:photo];
                [editingContext setImage:image thumbnailImage:thumbnailImage forItem:photo synchronous:true];
            }];
        }

        __weak TGCameraController *weakSelf = self;
        TGModernGalleryController *galleryController = [[TGModernGalleryController alloc] initWithContext:windowContext];
        galleryController.adjustsStatusBarVisibility = false;
        galleryController.hasFadeOutTransition = true;
        
        __block id<TGModernGalleryItem> focusItem = nil;
        NSArray *galleryItems = [self prepareGalleryItemsForResults:^(TGMediaPickerGalleryItem *item)
        {
            if (focusItem == nil && [item.asset isEqual:editableItem])
            {
                focusItem = item;
                
                if ([item.asset isKindOfClass:[TGCameraCapturedVideo class]])
                {
                    AVAssetImageGenerator *generator = [[AVAssetImageGenerator alloc] initWithAsset:((TGCameraCapturedVideo *)item.asset).immediateAVAsset];
                    generator.appliesPreferredTrackTransform = true;
                    generator.maximumSize = CGSizeMake(640.0f, 640.0f);
                    CGImageRef imageRef = [generator copyCGImageAtTime:kCMTimeZero actualTime:NULL error:NULL];
                    UIImage *thumbnailImage = [[UIImage alloc] initWithCGImage:imageRef];
                    CGImageRelease(imageRef);
                    
                    item.immediateThumbnailImage = thumbnailImage;
                }
            }
        }];
        
        bool hasCamera = !self.inhibitMultipleCapture && (((_intent == TGCameraControllerGenericIntent || _intent == TGCameraControllerGenericPhotoOnlyIntent || _intent == TGCameraControllerGenericVideoOnlyIntent) && !_shortcut) || (_intent == TGCameraControllerPassportMultipleIntent));
        TGMediaPickerGalleryModel *model = [[TGMediaPickerGalleryModel alloc] initWithContext:windowContext items:galleryItems focusItem:focusItem selectionContext:_items.count > 1 ? selectionContext : nil editingContext:editingContext hasCaptions:self.allowCaptions allowCaptionEntities:self.allowCaptionEntities hasTimer:self.hasTimer onlyCrop:_intent == TGCameraControllerPassportIntent || _intent == TGCameraControllerPassportIdIntent || _intent == TGCameraControllerPassportMultipleIntent inhibitDocumentCaptions:self.inhibitDocumentCaptions hasSelectionPanel:true hasCamera:hasCamera recipientName:self.recipientName isScheduledMessages:false canShowTelescope:false canSendTelescope:false];
        model.inhibitMute = self.inhibitMute;
        model.controller = galleryController;
        model.stickersContext = self.stickersContext;
        
        __weak TGModernGalleryController *weakGalleryController = galleryController;
        __weak TGMediaPickerGalleryModel *weakModel = model;
        
        model.interfaceView.doneLongPressed = ^(TGMediaPickerGalleryItem *item) {
            __strong TGCameraController *strongSelf = weakSelf;
            __strong TGMediaPickerGalleryModel *strongModel = weakModel;
            if (strongSelf == nil || !(strongSelf.hasSilentPosting || strongSelf.hasSchedule) || strongSelf->_shortcut)
                return;
            
            if (iosMajorVersion() >= 10) {
                UIImpactFeedbackGenerator *generator = [[UIImpactFeedbackGenerator alloc] initWithStyle:UIImpactFeedbackStyleMedium];
                [generator impactOccurred];
            }
            
            bool effectiveHasSchedule = strongSelf.hasSchedule;
            for (id item in strongModel.selectionContext.selectedItems)
            {
                if ([item isKindOfClass:[TGMediaAsset class]])
                {
                    if ([[strongSelf->_editingContext timerForItem:item] integerValue] > 0)
                    {
                        effectiveHasSchedule = false;
                        break;
                    }
                }
            }
            
            TGMediaPickerSendActionSheetController *controller = [[TGMediaPickerSendActionSheetController alloc] initWithContext:strongSelf->_context isDark:true sendButtonFrame:strongModel.interfaceView.doneButtonFrame canSendSilently:strongSelf->_hasSilentPosting canSendWhenOnline:effectiveHasSchedule canSchedule:effectiveHasSchedule reminder:strongSelf->_reminder hasTimer:strongSelf->_hasTimer];
            controller.send = ^{
                __strong TGCameraController *strongSelf = weakSelf;
                __strong TGMediaPickerGalleryModel *strongModel = weakModel;
    
                if (strongSelf == nil || strongModel == nil)
                    return;
                
                __strong TGModernGalleryController *strongController = weakGalleryController;
                if (strongController == nil)
                    return;
                
                if ([item isKindOfClass:[TGMediaPickerGalleryVideoItem class]])
                {
                    TGMediaPickerGalleryVideoItemView *itemView = (TGMediaPickerGalleryVideoItemView *)[strongController itemViewForItem:item];
                    [itemView stop];
                    [itemView setPlayButtonHidden:true animated:true];
                }
                
                if (strongSelf->_selectionContext.allowGrouping)
                    [[NSUserDefaults standardUserDefaults] setObject:@(!strongSelf->_selectionContext.grouping) forKey:@"TG_mediaGroupingDisabled_v0"];
                
                if (strongSelf.finishedWithResults != nil)
                    strongSelf.finishedWithResults(strongController, strongSelf->_selectionContext, strongSelf->_editingContext, item.asset, false, 0);
                
                [strongSelf _dismissTransitionForResultController:strongController];
            };
            controller.sendSilently = ^{
                __strong TGCameraController *strongSelf = weakSelf;
                __strong TGMediaPickerGalleryModel *strongModel = weakModel;
                
                if (strongSelf == nil || strongModel == nil)
                    return;
                
                __strong TGModernGalleryController *strongController = weakGalleryController;
                if (strongController == nil)
                    return;
                
                if ([item isKindOfClass:[TGMediaPickerGalleryVideoItem class]])
                {
                    TGMediaPickerGalleryVideoItemView *itemView = (TGMediaPickerGalleryVideoItemView *)[strongController itemViewForItem:item];
                    [itemView stop];
                    [itemView setPlayButtonHidden:true animated:true];
                }
                
                if (strongSelf->_selectionContext.allowGrouping)
                    [[NSUserDefaults standardUserDefaults] setObject:@(!strongSelf->_selectionContext.grouping) forKey:@"TG_mediaGroupingDisabled_v0"];
                
                if (strongSelf.finishedWithResults != nil)
                    strongSelf.finishedWithResults(strongController, strongSelf->_selectionContext, strongSelf->_editingContext, item.asset, true, 0);
                
                [strongSelf _dismissTransitionForResultController:strongController];
            };
            controller.sendWhenOnline = ^{
                __strong TGCameraController *strongSelf = weakSelf;
                __strong TGMediaPickerGalleryModel *strongModel = weakModel;
                
                if (strongSelf == nil || strongModel == nil)
                    return;
                
                __strong TGModernGalleryController *strongController = weakGalleryController;
                if (strongController == nil)
                    return;
                
                if ([item isKindOfClass:[TGMediaPickerGalleryVideoItem class]])
                {
                    TGMediaPickerGalleryVideoItemView *itemView = (TGMediaPickerGalleryVideoItemView *)[strongController itemViewForItem:item];
                    [itemView stop];
                    [itemView setPlayButtonHidden:true animated:true];
                }
                
                if (strongSelf->_selectionContext.allowGrouping)
                    [[NSUserDefaults standardUserDefaults] setObject:@(!strongSelf->_selectionContext.grouping) forKey:@"TG_mediaGroupingDisabled_v0"];
                
                if (strongSelf.finishedWithResults != nil)
                    strongSelf.finishedWithResults(strongController, strongSelf->_selectionContext, strongSelf->_editingContext, item.asset, false, 0x7ffffffe);
                
                [strongSelf _dismissTransitionForResultController:strongController];
            };
            controller.schedule = ^{
                __strong TGCameraController *strongSelf = weakSelf;
                if (strongSelf == nil)
                    return;
                
                strongSelf.presentScheduleController(true, ^(int32_t time) {
                    __strong TGCameraController *strongSelf = weakSelf;
                    __strong TGMediaPickerGalleryModel *strongModel = weakModel;
                    
                    if (strongSelf == nil || strongModel == nil)
                        return;
                    
                    __strong TGModernGalleryController *strongController = weakGalleryController;
                    if (strongController == nil)
                        return;
                    
                    if ([item isKindOfClass:[TGMediaPickerGalleryVideoItem class]])
                    {
                        TGMediaPickerGalleryVideoItemView *itemView = (TGMediaPickerGalleryVideoItemView *)[strongController itemViewForItem:item];
                        [itemView stop];
                        [itemView setPlayButtonHidden:true animated:true];
                    }
                    
                    if (strongSelf->_selectionContext.allowGrouping)
                        [[NSUserDefaults standardUserDefaults] setObject:@(!strongSelf->_selectionContext.grouping) forKey:@"TG_mediaGroupingDisabled_v0"];
                    
                    if (strongSelf.finishedWithResults != nil)
                        strongSelf.finishedWithResults(strongController, strongSelf->_selectionContext, strongSelf->_editingContext, item.asset, false, time);
                    
                    [strongSelf _dismissTransitionForResultController:strongController];
                });
            };
            controller.sendWithTimer = ^{
                __strong TGCameraController *strongSelf = weakSelf;
                if (strongSelf == nil)
                    return;
                
                strongSelf.presentTimerController(^(int32_t time) {
                    __strong TGCameraController *strongSelf = weakSelf;
                    __strong TGMediaPickerGalleryModel *strongModel = weakModel;
                    
                    if (strongSelf == nil || strongModel == nil)
                        return;
                    
                    __strong TGModernGalleryController *strongController = weakGalleryController;
                    if (strongController == nil)
                        return;
                    
                    if ([item isKindOfClass:[TGMediaPickerGalleryVideoItem class]])
                    {
                        TGMediaPickerGalleryVideoItemView *itemView = (TGMediaPickerGalleryVideoItemView *)[strongController itemViewForItem:item];
                        [itemView stop];
                        [itemView setPlayButtonHidden:true animated:true];
                    }
                    
                    TGMediaEditingContext *editingContext = strongSelf->_editingContext;
                    NSMutableArray *items = [strongSelf->_selectionContext.selectedItems mutableCopy];
                    [items addObject:item.asset];
                    
                    for (id<TGMediaEditableItem> editableItem in items) {
                        [editingContext setTimer:@(time) forItem:editableItem];
                    }
                    
                    if (strongSelf.finishedWithResults != nil)
                        strongSelf.finishedWithResults(strongController, strongSelf->_selectionContext, strongSelf->_editingContext, item.asset, false, 0);
                    
                    [strongSelf _dismissTransitionForResultController:strongController];
                });
            };
            
            id<LegacyComponentsOverlayWindowManager> windowManager = nil;
            id<LegacyComponentsContext> windowContext = nil;
            windowManager = [strongSelf->_context makeOverlayWindowManager];
            windowContext = [windowManager context];
            
            TGOverlayControllerWindow *controllerWindow = [[TGOverlayControllerWindow alloc] initWithManager:windowManager parentController:strongSelf contentController:(TGOverlayController *)controller];
            controllerWindow.hidden = false;
        };
        
        model.willFinishEditingItem = ^(id<TGMediaEditableItem> editableItem, id<TGMediaEditAdjustments> adjustments, id representation, bool hasChanges)
        {
            __strong TGCameraController *strongSelf = weakSelf;
            if (strongSelf == nil)
                return;

            if (hasChanges)
            {
                [editingContext setAdjustments:adjustments forItem:editableItem];
                [editingContext setTemporaryRep:representation forItem:editableItem];
            }
        };

        model.didFinishEditingItem = ^(id<TGMediaEditableItem> editableItem, __unused id<TGMediaEditAdjustments> adjustments, UIImage *resultImage, UIImage *thumbnailImage)
        {
            [editingContext setImage:resultImage thumbnailImage:thumbnailImage forItem:editableItem synchronous:false];
        };

        model.saveItemCaption = ^(id<TGMediaEditableItem> editableItem, NSAttributedString *caption)
        {
            __strong TGCameraController *strongSelf = weakSelf;
            if (strongSelf != nil)
                [strongSelf->_editingContext setCaption:caption forItem:editableItem];
        };

        model.interfaceView.hasSwipeGesture = false;
        galleryController.model = model;

        if (_items.count > 1)
            [model.interfaceView updateSelectionInterface:selectionContext.count counterVisible:(selectionContext.count > 0) animated:false];
        else
            [model.interfaceView updateSelectionInterface:1 counterVisible:false animated:false];
        model.interfaceView.thumbnailSignalForItem = ^SSignal *(id item)
        {
            __strong TGCameraController *strongSelf = weakSelf;
            if (strongSelf != nil)
                return [strongSelf _signalForItem:item];
            return nil;
        };
        model.interfaceView.donePressed = ^(TGMediaPickerGalleryItem *item)
        {
            __strong TGCameraController *strongSelf = weakSelf;
            if (strongSelf == nil)
                return;

            TGMediaPickerGalleryModel *strongModel = weakModel;
            if (strongModel == nil)
                return;

            __strong TGModernGalleryController *strongController = weakGalleryController;
            if (strongController == nil)
                return;

            if ([item isKindOfClass:[TGMediaPickerGalleryVideoItem class]])
            {
                TGMediaPickerGalleryVideoItemView *itemView = (TGMediaPickerGalleryVideoItemView *)[strongController itemViewForItem:item];
                [itemView stop];
                [itemView setPlayButtonHidden:true animated:true];
            }
            
            if (strongSelf->_selectionContext.allowGrouping)
                [[NSUserDefaults standardUserDefaults] setObject:@(!strongSelf->_selectionContext.grouping) forKey:@"TG_mediaGroupingDisabled_v0"];

            if (strongSelf.finishedWithResults != nil)
                strongSelf.finishedWithResults(strongController, strongSelf->_selectionContext, strongSelf->_editingContext, item.asset, false, 0);
            
            if (strongSelf->_shortcut)
                return;

            [strongSelf _dismissTransitionForResultController:strongController];
        };

        CGSize snapshotSize = TGScaleToFill(CGSizeMake(480, 640), CGSizeMake(self.view.frame.size.width, self.view.frame.size.width));
        UIView *snapshotView = [_previewView snapshotViewAfterScreenUpdates:false];
        snapshotView.contentMode = UIViewContentModeScaleAspectFill;
        snapshotView.frame = CGRectMake(_previewView.center.x - snapshotSize.width / 2, _previewView.center.y - snapshotSize.height / 2, snapshotSize.width, snapshotSize.height);
        snapshotView.hidden = true;
        [_previewView.superview insertSubview:snapshotView aboveSubview:_previewView];

        galleryController.beginTransitionIn = ^UIView *(__unused TGMediaPickerGalleryItem *item, __unused TGModernGalleryItemView *itemView)
        {
            __strong TGCameraController *strongSelf = weakSelf;
            if (strongSelf != nil)
            {
                TGModernGalleryController *strongGalleryController = weakGalleryController;
                strongGalleryController.view.alpha = 0.0f;
                [UIView animateWithDuration:0.3f animations:^
                 {
                     strongGalleryController.view.alpha = 1.0f;
                     strongSelf->_interfaceView.alpha = 0.0f;
                 }];
                return snapshotView;
            }
            return nil;
        };

        galleryController.finishedTransitionIn = ^(__unused TGMediaPickerGalleryItem *item, __unused TGModernGalleryItemView *itemView)
        {
            __strong TGCameraController *strongSelf = weakSelf;
            if (strongSelf == nil)
                return;
            
            TGMediaPickerGalleryModel *strongModel = weakModel;
            if (strongModel == nil)
                return;

            [strongModel.interfaceView setSelectedItemsModel:strongModel.selectedItemsModel];
            
            [strongSelf->_camera stopCaptureForPause:true completion:nil];

            snapshotView.hidden = true;

            if (completion != nil)
                completion();
        };

        galleryController.beginTransitionOut = ^UIView *(__unused TGMediaPickerGalleryItem *item, __unused TGModernGalleryItemView *itemView)
        {
            __strong TGCameraController *strongSelf = weakSelf;
            if (strongSelf != nil)
            {
                TGMediaPickerGalleryModel *strongModel = weakModel;
                if (strongModel == nil)
                    return nil;
                
                [[[LegacyComponentsGlobals provider] applicationInstance] setIdleTimerDisabled:true];

                if (strongSelf->_camera.cameraMode == PGCameraModeVideo)
                    [strongSelf->_interfaceView setRecordingVideo:false animated:false];
            
                strongSelf->_buttonHandler.enabled = true;
                [strongSelf->_buttonHandler ignoreEventsFor:2.0f andDisable:false];

                strongSelf->_camera.disabled = false;
                [strongSelf->_camera startCaptureForResume:true completion:nil];

                [UIView animateWithDuration:0.3f delay:0.1f options:UIViewAnimationOptionCurveLinear animations:^
                {
                    strongSelf->_interfaceView.alpha = 1.0f;
                } completion:nil];
                
                if (!strongModel.interfaceView.capturing)
                {
                    [strongSelf->_items removeAllObjects];
                    [strongSelf->_interfaceView setResults:nil];
                    [strongSelf->_selectionContext clear];
                    [strongSelf->_selectedItemsModel clear];
                    
                    [strongSelf->_interfaceView updateSelectionInterface:0 counterVisible:false animated:false];
                }

                return snapshotView;
            }
            return nil;
        };
        
        void (^dismissGalleryImpl)() = nil;

        galleryController.completedTransitionOut = ^
        {
            [snapshotView removeFromSuperview];
            
            TGModernGalleryController *strongGalleryController = weakGalleryController;
            if (strongGalleryController == nil) {
                return;
            }
            if (strongGalleryController.customDismissSelf) {
                strongGalleryController.customDismissSelf();
            }
        };

        TGOverlayController *contentController = galleryController;
        if (_shortcut)
        {
            contentController = [[TGOverlayController alloc] initWithContext:_context];

            TGNavigationController *navigationController = [TGNavigationController navigationControllerWithControllers:@[galleryController]];
            galleryController.navigationBarShouldBeHidden = true;

            [contentController addChildViewController:navigationController];
            [contentController.view addSubview:navigationController.view];
        }
        
        if (_customPresentOverlayController) {
            dismissGalleryImpl = ^{
                TGModernGalleryController *strongGalleryController = weakGalleryController;
                if (strongGalleryController == nil) {
                    return;
                }
                if (strongGalleryController.customDismissSelf) {
                    strongGalleryController.customDismissSelf();
                }
            };
        } else {
            dismissGalleryImpl = ^{
                TGModernGalleryController *strongGalleryController = weakGalleryController;
                if (strongGalleryController != nil && strongGalleryController.overlayWindow == nil)
                {
                    TGNavigationController *navigationController = (TGNavigationController *)strongGalleryController.navigationController;
                    TGOverlayControllerWindow *window = (TGOverlayControllerWindow *)navigationController.view.window;
                    if ([window isKindOfClass:[TGOverlayControllerWindow class]])
                        [window dismiss];
                }
            };
        }
        galleryController.view.clipsToBounds = true;
        return contentController;
    };
    
    if (_customPresentOverlayController) {
        _customPresentOverlayController(^TGOverlayController * (id<LegacyComponentsContext> context) {
            return (TGOverlayController *)begin(context);
        });
    } else {
        id<LegacyComponentsOverlayWindowManager> windowManager = nil;
        id<LegacyComponentsContext> windowContext = nil;
        windowManager = [_context makeOverlayWindowManager];
        windowContext = [windowManager context];
        
        UIViewController *controller = begin(windowContext);
        
        TGOverlayControllerWindow *controllerWindow = [[TGOverlayControllerWindow alloc] initWithManager:windowManager parentController:self contentController:(TGOverlayController *)controller];
        controllerWindow.hidden = false;
        controllerWindow.windowLevel = self.view.window.windowLevel + 0.0001f;
    }
}

- (SSignal *)_signalForItem:(id<TGMediaEditableItem>)item
{
    SSignal *assetSignal = [item thumbnailImageSignal];
    if (_editingContext == nil)
        return assetSignal;
    
    return [[_editingContext thumbnailImageSignalForItem:item] mapToSignal:^SSignal *(id result)
    {
        if (result != nil)
            return [SSignal single:result];
        else
            return assetSignal;
    }];
}

#pragma mark - Legacy Photo Result

- (void)presentPhotoResultControllerWithImage:(id<TGMediaEditableItem>)input metadata:(PGCameraShotMetadata *)metadata completion:(void (^)(void))completion
{
    [[[LegacyComponentsGlobals provider] applicationInstance] setIdleTimerDisabled:false];
 
    if (input == nil || ([input isKindOfClass:[UIImage class]] && ((UIImage *)input).size.width < FLT_EPSILON))
    {
        [self beginTransitionOutWithVelocity:0.0f];
        return;
    }
 
    UIImage *image = nil;
    if ([input isKindOfClass:[UIImage class]]) {
        image = (UIImage *)input;
    } else if ([input isKindOfClass:[TGCameraCapturedVideo class]]) {
        AVAsset *asset = ((TGCameraCapturedVideo *)input).immediateAVAsset;
        
        AVAssetImageGenerator *generator = [[AVAssetImageGenerator alloc] initWithAsset:asset];
        generator.appliesPreferredTrackTransform = true;
        generator.maximumSize = CGSizeMake(640.0f, 640.0f);
        CGImageRef imageRef = [generator copyCGImageAtTime:kCMTimeZero actualTime:NULL error:NULL];
        image = [[UIImage alloc] initWithCGImage:imageRef];
        CGImageRelease(imageRef);
    }
    
    id<LegacyComponentsOverlayWindowManager> windowManager = nil;
    id<LegacyComponentsContext> windowContext = nil;
    if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPhone) {
        windowManager = [_context makeOverlayWindowManager];
        windowContext = [windowManager context];
    } else {
        windowContext = _context;
    }
    
    __weak TGCameraController *weakSelf = self;
    TGOverlayController *overlayController = nil;
    
    _focusControl.ignoreAutofocusing = true;
    
    TGPhotoEditorControllerIntent intent = TGPhotoEditorControllerAvatarIntent;
    if (_intent == TGCameraControllerSignupAvatarIntent) {
        intent = TGPhotoEditorControllerSignupAvatarIntent;
    }
    TGPhotoEditorController *controller = [[TGPhotoEditorController alloc] initWithContext:windowContext item:input intent:(TGPhotoEditorControllerFromCameraIntent | intent) adjustments:nil caption:nil screenImage:image availableTabs:[TGPhotoEditorController defaultTabsForAvatarIntent:_intent != TGCameraControllerSignupAvatarIntent] selectedTab:TGPhotoEditorCropTab];
    controller.stickersContext = _stickersContext;
    __weak TGPhotoEditorController *weakController = controller;
    controller.beginTransitionIn = ^UIView *(CGRect *referenceFrame, __unused UIView **parentView)
    {
        __strong TGCameraController *strongSelf = weakSelf;
        if (strongSelf == nil)
            return nil;
        
        strongSelf->_previewView.hidden = true;
        *referenceFrame = strongSelf->_previewView.frame;
        
        UIImageView *imageView = [[UIImageView alloc] initWithFrame:strongSelf->_previewView.frame];
        imageView.image = image;
        
        return imageView;
    };
    
    controller.beginTransitionOut = ^UIView *(CGRect *referenceFrame, __unused UIView **parentView, __unused bool saving)
    {
        __strong TGCameraController *strongSelf = weakSelf;
        if (strongSelf == nil)
            return nil;
        
        CGRect startFrame = CGRectZero;
        if (referenceFrame != NULL)
        {
            startFrame = *referenceFrame;
            *referenceFrame = strongSelf->_previewView.frame;
        }
        
        [strongSelf transitionBackFromResultControllerWithReferenceFrame:startFrame];
        
        return strongSelf->_previewView;
    };
    
    controller.didFinishEditing = ^(PGPhotoEditorValues *editorValues, UIImage *resultImage, __unused UIImage *thumbnailImage, bool hasChanges, void(^commit)(void))
    {
        if (!hasChanges)
            return;
        
        __strong TGCameraController *strongSelf = weakSelf;
        if (strongSelf == nil)
            return;
        
        TGDispatchOnMainThread(^
        {
            if (editorValues.paintingData.hasAnimation) {
                TGVideoEditAdjustments *adjustments = [TGVideoEditAdjustments editAdjustmentsWithPhotoEditorValues:(PGPhotoEditorValues *)editorValues preset:TGMediaVideoConversionPresetProfileVeryHigh];

                NSString *filePath = [NSTemporaryDirectory() stringByAppendingPathComponent:[[NSString alloc] initWithFormat:@"gifvideo_%x.jpg", (int)arc4random()]];
                NSData *data = UIImageJPEGRepresentation(resultImage, 0.8);
                [data writeToFile:filePath atomically:true];
                
                UIImage *previewImage = resultImage;
                if ([adjustments cropAppliedForAvatar:false] || adjustments.hasPainting || adjustments.toolsApplied)
                {
                    UIImage *paintingImage = adjustments.paintingData.stillImage;
                    if (paintingImage == nil) {
                        paintingImage = adjustments.paintingData.image;
                    }
                    UIImage *croppedPaintingImage = TGPhotoEditorPaintingCrop(paintingImage, adjustments.cropOrientation, adjustments.cropRotation, adjustments.cropRect, adjustments.cropMirrored, resultImage.size, adjustments.originalSize, true, true, false);
                    UIImage *thumbnailImage = TGPhotoEditorVideoExtCrop(resultImage, croppedPaintingImage, adjustments.cropOrientation, adjustments.cropRotation, adjustments.cropRect, adjustments.cropMirrored, TGScaleToFill(resultImage.size, CGSizeMake(800, 800)), adjustments.originalSize, true, true, true, true);
                    if (thumbnailImage != nil) {
                        previewImage = thumbnailImage;
                    }
                }
                
                if (strongSelf.finishedWithVideo != nil)
                    strongSelf.finishedWithVideo(nil, [NSURL fileURLWithPath:filePath], previewImage, 0, CGSizeZero, adjustments, nil, nil, nil);
            } else {
                if (strongSelf.finishedWithPhoto != nil)
                    strongSelf.finishedWithPhoto(nil, resultImage, nil, nil, nil);
            }
                        
            if (strongSelf.shouldStoreCapturedAssets && [input isKindOfClass:[UIImage class]])
            {
                [strongSelf _savePhotoToCameraRollWithOriginalImage:image editedImage:[editorValues toolsApplied] ? resultImage : nil];
            }
            
            __strong TGPhotoEditorController *strongController = weakController;
            if (strongController != nil)
            {
                [strongController updateStatusBarAppearanceForDismiss];
                [strongSelf _dismissTransitionForResultController:(TGOverlayController *)strongController];
            }
            
            commit();
        });
    };
    
    controller.didFinishEditingVideo = ^(AVAsset *asset, id<TGMediaEditAdjustments> adjustments, UIImage *resultImage, UIImage *thumbnailImage, bool hasChanges, void(^commit)(void)) {
        if (!hasChanges)
            return;
        
        __strong TGCameraController *strongSelf = weakSelf;
        if (strongSelf == nil)
            return;
        
        TGDispatchOnMainThread(^
        {
            if (strongSelf.finishedWithVideo != nil)
                strongSelf.finishedWithVideo(nil, [(AVURLAsset *)asset URL], resultImage, 0, CGSizeZero, adjustments, nil, nil, nil);
        
            __strong TGPhotoEditorController *strongController = weakController;
            if (strongController != nil)
            {
                [strongController updateStatusBarAppearanceForDismiss];
                [strongSelf _dismissTransitionForResultController:(TGOverlayController *)strongController];
            }
            
            commit();
        });
    };
    
    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];
        }
    };
    
    overlayController = (TGOverlayController *)controller;
            
    if (windowManager != nil)
    {
        TGOverlayController *contentController = overlayController;
        if (_shortcut)
        {
            contentController = [[TGOverlayController alloc] init];
            
            TGNavigationController *navigationController = [TGNavigationController navigationControllerWithControllers:@[overlayController]];
            overlayController.navigationBarShouldBeHidden = true;
            [contentController addChildViewController:navigationController];
            [contentController.view addSubview:navigationController.view];
        }
        
        TGOverlayControllerWindow *controllerWindow = [[TGOverlayControllerWindow alloc] initWithManager:windowManager parentController:self contentController:contentController];
        controllerWindow.windowLevel = self.view.window.windowLevel + 0.0001f;
        controllerWindow.hidden = false;
    }
    else
    {
        [self addChildViewController:overlayController];
        [self.view addSubview:overlayController.view];
    }
    
    if (completion != nil)
        completion();
    
    [UIView animateWithDuration:0.3f animations:^
    {
        _interfaceView.alpha = 0.0f;
    }];
}

- (void)_savePhotoToCameraRollWithOriginalImage:(UIImage *)originalImage editedImage:(UIImage *)editedImage
{
    if (!_saveEditedPhotos || originalImage == nil)
        return;
    
    SSignal *savePhotoSignal = _saveCapturedMedia ? [[TGMediaAssetsLibrary sharedLibrary] saveAssetWithImage:originalImage] : [SSignal complete];
    if (_saveEditedPhotos && editedImage != nil)
        savePhotoSignal = [savePhotoSignal then:[[TGMediaAssetsLibrary sharedLibrary] saveAssetWithImage:editedImage]];
    
    [savePhotoSignal startWithNext:nil];
}

- (void)_saveVideoToCameraRollWithURL:(NSURL *)url completion:(void (^)(void))completion
{
    if (!_saveCapturedMedia)
        return;
    
    [[[TGMediaAssetsLibrary sharedLibrary] saveAssetWithVideoAtUrl:url] startWithNext:nil error:^(__unused NSError *error)
    {
        if (completion != nil)
            completion();
    } completed:completion];
}

- (CGRect)transitionBackFromResultControllerWithReferenceFrame:(CGRect)referenceFrame
{
    _camera.disabled = false;
    
    _buttonHandler.enabled = true;
    [_buttonHandler ignoreEventsFor:2.0f andDisable:false];
    _previewView.hidden = false;
    
    _focusControl.ignoreAutofocusing = false;
    
    CGRect targetFrame = _previewView.frame;

    _previewView.frame = referenceFrame;
    POPSpringAnimation *animation = [TGPhotoEditorAnimation prepareTransitionAnimationForPropertyNamed:kPOPViewFrame];
    animation.fromValue = [NSValue valueWithCGRect:referenceFrame];
    animation.toValue = [NSValue valueWithCGRect:targetFrame];
    [_previewView pop_addAnimation:animation forKey:@"frame"];
    
    [UIView animateWithDuration:0.3f delay:0.1f options:UIViewAnimationOptionCurveLinear animations:^
    {
        _interfaceView.alpha = 1.0f;
        _cornersView.alpha = 1.0;
    } completion:nil];
    
    _interfaceView.previewViewFrame = _previewView.frame;
    
    return targetFrame;
}

#pragma mark - Transition

- (void)beginTransitionInFromRect:(CGRect)rect
{
    [_autorotationCorrectionView insertSubview:_previewView aboveSubview:_backgroundView];
    
    _previewView.frame = rect;
    
    _backgroundView.alpha = 0.0f;
    _interfaceView.alpha = 0.0f;
    _cornersView.alpha = 0.0;
    
    [UIView animateWithDuration:0.3f animations:^
    {
        _backgroundView.alpha = 1.0f;
        _interfaceView.alpha = 1.0f;
        _cornersView.alpha = 1.0;
    }];
    
    CGRect fromFrame = rect;
    CGRect toFrame = [TGCameraController _cameraPreviewFrameForScreenSize:TGScreenSize() mode:_camera.cameraMode];

    if (!CGRectEqualToRect(fromFrame, CGRectZero))
    {
        __weak TGCameraController *weakSelf = self;
        POPSpringAnimation *frameAnimation = [POPSpringAnimation animationWithPropertyNamed:kPOPViewFrame];
        frameAnimation.fromValue = [NSValue valueWithCGRect:fromFrame];
        frameAnimation.toValue = [NSValue valueWithCGRect:toFrame];
        frameAnimation.springSpeed = 20;
        frameAnimation.springBounciness = 1;
        frameAnimation.completionBlock = ^(POPAnimation *anim, BOOL finished) {
            __strong TGCameraController *strongSelf = weakSelf;
            if (strongSelf == nil)
                return;
            
            if (strongSelf.finishedTransitionIn != NULL) {
                ;strongSelf.finishedTransitionIn();
            }
        };
        [_previewView pop_addAnimation:frameAnimation forKey:@"frame"];
        
        POPSpringAnimation *cornersFrameAnimation = [POPSpringAnimation animationWithPropertyNamed:kPOPViewFrame];
        cornersFrameAnimation.fromValue = [NSValue valueWithCGRect:fromFrame];
        cornersFrameAnimation.toValue = [NSValue valueWithCGRect:toFrame];
        cornersFrameAnimation.springSpeed = 20;
        cornersFrameAnimation.springBounciness = 1;
        [_cornersView pop_addAnimation:cornersFrameAnimation forKey:@"frame"];
    }
    else
    {
        _previewView.frame = toFrame;
        _cornersView.frame = toFrame;
    }
    
    _interfaceView.previewViewFrame = toFrame;
}

+ (void)generateStartImageWithImage:(UIImage *)frameImage {
    CGFloat minSize = MIN(frameImage.size.width, frameImage.size.height);
    UIImage *image = TGPhotoEditorCrop(frameImage, nil, UIImageOrientationUp, 0.0f, CGRectMake((frameImage.size.width - minSize) / 2.0f, (frameImage.size.height - minSize) / 2.0f, minSize, minSize), false, CGSizeMake(240.0f, 240.0f), frameImage.size, true);
    UIImage *startImage = TGSecretBlurredAttachmentImage(image, image.size, NULL, false, 0);
    TGDispatchOnMainThread(^{
        [TGCameraController saveStartImage:startImage];
    });
}

- (void)beginTransitionOutWithVelocity:(CGFloat)velocity
{
    _dismissing = true;
    self.view.userInteractionEnabled = false;
    
    
    _focusControl.active = false;
    _rectangleView.hidden = true;
    
    [self setInterfaceHidden:true animated:true];
    
    [UIView animateWithDuration:0.25f animations:^
    {
        _backgroundView.alpha = 0.0f;
        _cornersView.alpha = 0.0;
    }];
    
    CGRect referenceFrame = CGRectZero;
    if (self.beginTransitionOut != nil)
        referenceFrame = self.beginTransitionOut();
    
    __weak TGCameraController *weakSelf = self;
    [_camera captureNextFrameCompletion:^(UIImage *frameImage) {
        [[SQueue concurrentDefaultQueue] dispatch:^{
            [TGCameraController generateStartImageWithImage:frameImage];
        }];
    }];
    if (_standalone)
    {
        [self simpleTransitionOutWithVelocity:velocity completion:^
        {
            __strong TGCameraController *strongSelf = weakSelf;
            if (strongSelf == nil)
                return;
            
            if (strongSelf.finishedTransitionOut != nil)
                strongSelf.finishedTransitionOut();
            
            [strongSelf dismiss];
        }];
        return;
    }

    bool resetNeeded = _camera.isResetNeeded;
    if (resetNeeded)
        [_previewView beginResetTransitionAnimated:true];

    [_camera resetSynchronous:false completion:^
    {
        TGDispatchOnMainThread(^
        {
            if (resetNeeded)
                [_previewView endResetTransitionAnimated:true];
        });
    }];
    
    [_previewView.layer removeAllAnimations];
    
    if (!CGRectIsEmpty(referenceFrame))
    {
        POPSpringAnimation *frameAnimation = [POPSpringAnimation animationWithPropertyNamed:kPOPViewFrame];
        frameAnimation.fromValue = [NSValue valueWithCGRect:_previewView.frame];
        frameAnimation.toValue = [NSValue valueWithCGRect:referenceFrame];
        frameAnimation.springSpeed = 20;
        frameAnimation.springBounciness = 1;
        frameAnimation.completionBlock = ^(__unused POPAnimation *animation, __unused BOOL finished)
        {
            __strong TGCameraController *strongSelf = weakSelf;
            if (strongSelf == nil)
                return;

            if (strongSelf.finishedTransitionOut != nil)
                strongSelf.finishedTransitionOut();

            [strongSelf dismiss];
        };
        [_previewView pop_addAnimation:frameAnimation forKey:@"frame"];
        
        POPSpringAnimation *cornersAnimation = [POPSpringAnimation animationWithPropertyNamed:kPOPViewFrame];
        cornersAnimation.fromValue = [NSValue valueWithCGRect:_cornersView.frame];
        cornersAnimation.toValue = [NSValue valueWithCGRect:referenceFrame];
        cornersAnimation.springSpeed = 20;
        cornersAnimation.springBounciness = 1;
        [_cornersView pop_addAnimation:cornersAnimation forKey:@"frame"];
    }
    else
    {
        if (self.finishedTransitionOut != nil)
            self.finishedTransitionOut();
        
        [self dismiss];
    }
}

- (void)_dismissTransitionForResultController:(TGOverlayController *)resultController
{
    _finishedWithResult = true;
    
    //[_context setApplicationStatusBarAlpha:1.0f];
    
    self.view.hidden = true;
    
    __weak TGCameraController *weakSelf = self;
    __weak TGOverlayController *weakResultController = resultController;
    
    [resultController.view.layer animatePositionFrom:resultController.view.layer.position to:CGPointMake(resultController.view.layer.position.x, resultController.view.layer.position.y + resultController.view.bounds.size.height) duration:0.3 timingFunction:kCAMediaTimingFunctionSpring removeOnCompletion:false completion:^(__unused bool finished) {
        TGCameraController *strongSelf = weakSelf;
        TGOverlayController *strongResultController = weakResultController;
        
        if (strongResultController) {
            if (strongResultController.customDismissSelf) {
                strongResultController.customDismissSelf();
            } else {
                [strongResultController dismiss];
            }
        }
        if (strongSelf) {
            [strongSelf dismiss];
        }
    }];
}

- (void)simpleTransitionOutWithVelocity:(CGFloat)velocity completion:(void (^)())completion
{
    self.view.userInteractionEnabled = false;
    
    const CGFloat minVelocity = 4000.0f;
    if (ABS(velocity) < minVelocity)
        velocity = (velocity < 0.0f ? -1.0f : 1.0f) * minVelocity;
    CGFloat distance = (velocity < FLT_EPSILON ? -1.0f : 1.0f) * self.view.frame.size.height;
    CGRect targetFrame = (CGRect){{_previewView.frame.origin.x, distance}, _previewView.frame.size};
    
    [UIView animateWithDuration:ABS(distance / velocity) delay:0.0 options:UIViewAnimationOptionCurveEaseInOut animations:^
    {
        _previewView.frame = targetFrame;
        _cornersView.frame = targetFrame;
    } completion:^(__unused BOOL finished)
    {
        if (completion)
            completion();
    }];
}

- (void)_updateDismissTransitionMovementWithDistance:(CGFloat)distance animated:(bool)animated
{
    CGRect originalFrame = [TGCameraController _cameraPreviewFrameForScreenSize:TGScreenSize() mode:_camera.cameraMode];
    CGRect frame = (CGRect){ { originalFrame.origin.x, originalFrame.origin.y + distance }, originalFrame.size };
    if (animated)
    {
        [UIView animateWithDuration:0.3 animations:^
        {
            _previewView.frame = frame;
            _cornersView.frame = frame;
        }];
    }
    else
    {
        _previewView.frame = frame;
        _cornersView.frame = frame;
    }
}

- (void)_updateDismissTransitionWithProgress:(CGFloat)progress animated:(bool)animated
{
    CGFloat alpha = 1.0f - MAX(0.0f, MIN(1.0f, progress * 4.0f));
    CGFloat transitionProgress = MAX(0.0f, MIN(1.0f, progress * 2.0f));
    
    if (transitionProgress > FLT_EPSILON)
    {
        [self setInterfaceHidden:true animated:true];
        _focusControl.active = false;
    }
    else if (animated)
    {
        [self setInterfaceHidden:false animated:true];
        _focusControl.active = true;
    }
    
    if (animated)
    {
        [UIView animateWithDuration:0.3 animations:^
        {
            _backgroundView.alpha = alpha;
        }];
    }
    else
    {
        _backgroundView.alpha = alpha;
    }
}

- (void)resizePreviewViewForCameraMode:(PGCameraMode)mode
{
    CGRect frame = [TGCameraController _cameraPreviewFrameForScreenSize:TGScreenSize() mode:mode];
    _interfaceView.previewViewFrame = frame;
    [_interfaceView updateForCameraModeChangeAfterResize];
    
    [UIView animateWithDuration:0.3f delay:0.0f options:UIViewAnimationOptionCurveEaseInOut | UIViewAnimationOptionLayoutSubviews animations:^
    {
        _previewView.frame = frame;
        _cornersView.frame = frame;
        _overlayView.frame = frame;
    } completion:nil];
}

- (void)handleDeviceOrientationChangedTo:(UIDeviceOrientation)deviceOrientation
{
    if (_camera.isRecordingVideo || _intent == TGCameraControllerPassportIdIntent)
        return;
    
    UIInterfaceOrientation orientation = [TGCameraController _interfaceOrientationForDeviceOrientation:deviceOrientation];
    if ([_interfaceView isKindOfClass:[TGCameraMainPhoneView class]])
    {
        [_interfaceView setInterfaceOrientation:orientation animated:true];
    }
    else
    {
        if (orientation == UIInterfaceOrientationUnknown)
            return;
        
        switch (deviceOrientation)
        {
            case UIDeviceOrientationPortrait:
            {
                _photoSwipeGestureRecognizer.direction = UISwipeGestureRecognizerDirectionUp;
                _videoSwipeGestureRecognizer.direction = UISwipeGestureRecognizerDirectionDown;
            }
                break;
            case UIDeviceOrientationPortraitUpsideDown:
            {
                _photoSwipeGestureRecognizer.direction = UISwipeGestureRecognizerDirectionDown;
                _videoSwipeGestureRecognizer.direction = UISwipeGestureRecognizerDirectionUp;
            }
                break;
            case UIDeviceOrientationLandscapeLeft:
            {
                _photoSwipeGestureRecognizer.direction = UISwipeGestureRecognizerDirectionRight;
                _videoSwipeGestureRecognizer.direction = UISwipeGestureRecognizerDirectionLeft;
            }
                break;
            case UIDeviceOrientationLandscapeRight:
            {
                _photoSwipeGestureRecognizer.direction = UISwipeGestureRecognizerDirectionLeft;
                _videoSwipeGestureRecognizer.direction = UISwipeGestureRecognizerDirectionRight;
            }
                break;
                
            default:
                break;
        }
        
        [_interfaceView setInterfaceOrientation:orientation animated:false];
        CGSize referenceSize = [self referenceViewSizeForOrientation:orientation];
        if (referenceSize.width > referenceSize.height)
            referenceSize = CGSizeMake(referenceSize.height, referenceSize.width);
        
        self.view.userInteractionEnabled = false;
        [UIView animateWithDuration:0.5f delay:0.0f options:UIViewAnimationOptionBeginFromCurrentState | UIViewAnimationOptionLayoutSubviews animations:^
        {
            _interfaceView.transform = CGAffineTransformMakeRotation(TGRotationForInterfaceOrientation(orientation));
            _interfaceView.frame = CGRectMake(0, 0, referenceSize.width, referenceSize.height);
            [_interfaceView setNeedsLayout];
        } completion:^(BOOL finished)
        {
            if (finished)
                self.view.userInteractionEnabled = true;
        }];
    }
    
    [_focusControl setInterfaceOrientation:orientation animated:true];
}

#pragma mark - Gesture Recognizers

- (CGFloat)dismissProgressForSwipeDistance:(CGFloat)distance
{
    return MAX(0.0f, MIN(1.0f, ABS(distance / 150.0f)));
}

- (void)handleSwipe:(UISwipeGestureRecognizer *)gestureRecognizer
{
    PGCameraMode newMode = PGCameraModeUndefined;
    if (gestureRecognizer == _photoSwipeGestureRecognizer)
    {
        newMode = PGCameraModePhoto;
    }
    else if (gestureRecognizer == _videoSwipeGestureRecognizer)
    {
        if (_intent == TGCameraControllerAvatarIntent) {
            newMode = PGCameraModeSquareVideo;
        } else {
            newMode = PGCameraModeVideo;
        }
    }
    
    if (newMode != PGCameraModeUndefined && _camera.cameraMode != newMode)
    {
        [_camera setCameraMode:newMode];
        [_interfaceView setCameraMode:newMode];
    }
}

- (void)handlePan:(TGModernGalleryZoomableScrollViewSwipeGestureRecognizer *)gestureRecognizer
{
    switch (gestureRecognizer.state)
    {
        case UIGestureRecognizerStateChanged:
        {
            _dismissProgress = [self dismissProgressForSwipeDistance:[gestureRecognizer swipeDistance]];
            [self _updateDismissTransitionWithProgress:_dismissProgress animated:false];
            [self _updateDismissTransitionMovementWithDistance:[gestureRecognizer swipeDistance] animated:false];
        }
            break;
            
        case UIGestureRecognizerStateEnded:
        {
            CGFloat swipeVelocity = [gestureRecognizer swipeVelocity];
            if (ABS(swipeVelocity) < TGCameraSwipeMinimumVelocity)
                swipeVelocity = (swipeVelocity < 0.0f ? -1.0f : 1.0f) * TGCameraSwipeMinimumVelocity;
            
            __weak TGCameraController *weakSelf = self;
            bool(^transitionOut)(CGFloat) = ^bool(CGFloat swipeVelocity)
            {
                __strong TGCameraController *strongSelf = weakSelf;
                if (strongSelf == nil)
                    return false;
                
                [strongSelf beginTransitionOutWithVelocity:swipeVelocity];
                
                return true;
            };
            
            if ((ABS(swipeVelocity) < TGCameraSwipeVelocityThreshold && ABS([gestureRecognizer swipeDistance]) < TGCameraSwipeDistanceThreshold) || !transitionOut(swipeVelocity))
            {
                _dismissProgress = 0.0f;
                [self _updateDismissTransitionWithProgress:0.0f animated:true];
                [self _updateDismissTransitionMovementWithDistance:0.0f animated:true];
            }
        }
            break;
            
        case UIGestureRecognizerStateCancelled:
        {
            _dismissProgress = 0.0f;
            [self _updateDismissTransitionWithProgress:0.0f animated:true];
            [self _updateDismissTransitionMovementWithDistance:0.0f animated:true];
        }
            break;
            
        default:
            break;
    }
}

- (void)handlePinch:(UIPinchGestureRecognizer *)gestureRecognizer
{
    switch (gestureRecognizer.state)
    {
        case UIGestureRecognizerStateChanged:
        {
            CGFloat delta = (gestureRecognizer.scale - 1.0f) * 1.25;
            if (_camera.zoomLevel > 2.0) {
                delta *= 2.0;
            }
            CGFloat value = MAX(_camera.minZoomLevel, MIN(_camera.maxZoomLevel, _camera.zoomLevel + delta));
            
            [_camera setZoomLevel:value];
            [_interfaceView setZoomLevel:value displayNeeded:true];
            
            gestureRecognizer.scale = 1.0f;
        }
            break;
            
        case UIGestureRecognizerStateEnded:
        case UIGestureRecognizerStateCancelled:
        {
            [_interfaceView zoomChangingEnded];
        }
            break;
            
        default:
            break;
    }
}

- (void)handleRamp:(UIPanGestureRecognizer *)gestureRecognizer
{
    if (!_stopRecordingOnRelease) {
        [gestureRecognizer setTranslation:CGPointZero inView:self.view];
        return;
    }
    switch (gestureRecognizer.state)
    {
        case UIGestureRecognizerStateChanged:
        {
            CGPoint translation = [gestureRecognizer translationInView:self.view];
          
            CGFloat value = 1.0;
            if (translation.y < 0.0) {
                value = MIN(8.0, value + ABS(translation.y) / 60.0);
            }
            
            [_camera setZoomLevel:value];
            [_interfaceView setZoomLevel:value displayNeeded:true];
        }
            break;
        case UIGestureRecognizerStateEnded:
        {
            [self shutterReleased];
        }
            break;
        default:
            break;
    }
}

- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
{
    if (gestureRecognizer == _panGestureRecognizer)
        return !_camera.isRecordingVideo && _items.count == 0;
    else if (gestureRecognizer == _photoSwipeGestureRecognizer || gestureRecognizer == _videoSwipeGestureRecognizer)
        return (_intent == TGCameraControllerGenericIntent || _intent == TGCameraControllerAvatarIntent) && !_camera.isRecordingVideo;
    else if (gestureRecognizer == _pinchGestureRecognizer)
        return _camera.isZoomAvailable;
    
    return true;
}

+ (CGRect)_cameraPreviewFrameForScreenSize:(CGSize)screenSize mode:(PGCameraMode)mode
{
    CGFloat widescreenWidth = MAX(screenSize.width, screenSize.height);

    if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPhone)
    {
        switch (mode)
        {
            case PGCameraModeVideo:
            {
                if (widescreenWidth == 926.0f)
                    return CGRectMake(0, 82, screenSize.width, screenSize.height - 82 - 83);
                else if (widescreenWidth == 896.0f)
                    return CGRectMake(0, 77, screenSize.width, screenSize.height - 77 - 83);
                else if (widescreenWidth == 812.0f)
                    return CGRectMake(0, 77, screenSize.width, screenSize.height - 77 - 68);
                else
                    return CGRectMake(0, 0, screenSize.width, screenSize.height);
            }
                break;
            
            case PGCameraModeSquarePhoto:
            case PGCameraModeSquareVideo:
            case PGCameraModeSquareSwing:
            {
                CGRect rect = [self _cameraPreviewFrameForScreenSize:screenSize mode:PGCameraModePhoto];
                CGFloat topOffset = CGRectGetMidY(rect) - rect.size.width / 2;
                
                if (widescreenWidth - 480.0f < FLT_EPSILON)
                    topOffset = 40.0f;
                
                return CGRectMake(0, floor(topOffset), rect.size.width, rect.size.width);
            }
                break;
            
            default:
            {
                if (widescreenWidth == 932.0f)
                    return CGRectMake(0, 136, screenSize.width, screenSize.height - 136 - 223);
                else if (widescreenWidth == 926.0f)
                    return CGRectMake(0, 121, screenSize.width, screenSize.height - 121 - 234);
                else if (widescreenWidth == 896.0f)
                    return CGRectMake(0, 121, screenSize.width, screenSize.height - 121 - 223);
                if (widescreenWidth == 852.0f)
                    return CGRectMake(0, 136, screenSize.width, screenSize.height - 136 - 192);
                else if (widescreenWidth == 844.0f)
                    return CGRectMake(0, 77, screenSize.width, screenSize.height - 77 - 191);
                else if (widescreenWidth == 812.0f)
                    return CGRectMake(0, 121, screenSize.width, screenSize.height - 121 - 191);
                else if (widescreenWidth >= 736.0f - FLT_EPSILON)
                    return CGRectMake(0, 44, screenSize.width, screenSize.height - 50 - 136);
                else if (widescreenWidth >= 667.0f - FLT_EPSILON)
                    return CGRectMake(0, 44, screenSize.width, screenSize.height - 44 - 123);
                else if (widescreenWidth >= 568.0f - FLT_EPSILON)
                    return CGRectMake(0, 40, screenSize.width, screenSize.height - 40 - 101);
                else
                    return CGRectMake(0, 0, screenSize.width, screenSize.height);
            }
                break;
        }
    }
    else
    {
        if (mode == PGCameraModeSquarePhoto || mode == PGCameraModeSquareVideo || mode == PGCameraModeSquareSwing)
            return CGRectMake(0, (screenSize.height - screenSize.width) / 2, screenSize.width, screenSize.width);
        
        return CGRectMake(0, 0, screenSize.width, screenSize.height);
    }
}

+ (UIInterfaceOrientation)_interfaceOrientationForDeviceOrientation:(UIDeviceOrientation)orientation
{
    switch (orientation)
    {
        case UIDeviceOrientationPortrait:
            return UIInterfaceOrientationPortrait;
            
        case UIDeviceOrientationPortraitUpsideDown:
            return UIInterfaceOrientationPortraitUpsideDown;
            
        case UIDeviceOrientationLandscapeLeft:
            return UIInterfaceOrientationLandscapeRight;
            
        case UIDeviceOrientationLandscapeRight:
            return UIInterfaceOrientationLandscapeLeft;
            
        default:
            return UIInterfaceOrientationUnknown;
    }
}

+ (NSArray *)resultSignalsForSelectionContext:(TGMediaSelectionContext *)selectionContext editingContext:(TGMediaEditingContext *)editingContext currentItem:(id<TGMediaSelectableItem>)currentItem storeAssets:(bool)storeAssets saveEditedPhotos:(bool)saveEditedPhotos descriptionGenerator:(id (^)(id, NSAttributedString *, NSString *))descriptionGenerator
{
    NSMutableArray *signals = [[NSMutableArray alloc] init];
    NSMutableArray *selectedItems = selectionContext.selectedItems != nil ? [selectionContext.selectedItems mutableCopy] : [[NSMutableArray alloc] init];
    if (selectedItems.count == 0 && currentItem != nil)
        [selectedItems addObject:currentItem];
    
    bool isScan = false;
    for (id<TGMediaEditableItem> item in selectedItems) {
        if ([item isKindOfClass:[TGCameraCapturedPhoto class]] && ((TGCameraCapturedPhoto *)item).rectangle != nil) {
            isScan = true;
            break;
        }
    }
    
    if (storeAssets && !isScan) {
        NSMutableArray *fullSizeSignals = [[NSMutableArray alloc] init];
        for (id<TGMediaEditableItem> item in selectedItems)
        {
            if ([item isKindOfClass:[TGCameraCapturedPhoto class]] && ((TGCameraCapturedPhoto *)item).rectangle != nil) {
                isScan = true;
            }
            
            if ([editingContext timerForItem:item] == nil)
            {
                SSignal *saveMedia = [SSignal defer:^SSignal *
                {
                    if ([item isKindOfClass:[TGCameraCapturedPhoto class]])
                    {
                        TGCameraCapturedPhoto *photo = (TGCameraCapturedPhoto *)item;
                        return [SSignal single:@{@"type": @"photo", @"url": photo.url}];
                    }
                    else if ([item isKindOfClass:[TGCameraCapturedVideo class]])
                    {
                        TGCameraCapturedVideo *video = (TGCameraCapturedVideo *)item;
                        return [[video avAsset] mapToSignal:^SSignal *(AVURLAsset *avAsset) {
                            return [SSignal single:@{@"type": @"video", @"url": avAsset.URL}];
                        }];
                    }
                    
                    return [SSignal complete];
                }];
                
                [fullSizeSignals addObject:saveMedia];
                
                if (saveEditedPhotos)
                {
                    [fullSizeSignals addObject:[[[editingContext fullSizeImageUrlForItem:item] filter:^bool(id result)
                    {
                        return [result isKindOfClass:[NSURL class]];
                    }] mapToSignal:^SSignal *(NSURL *url)
                    {
                        return [SSignal single:@{@"type": @"photo", @"url": url}];
                    }]];
                }
            }
        }
        
        SSignal *combinedSignal = nil;
        SQueue *queue = [SQueue concurrentDefaultQueue];
        
        for (SSignal *signal in fullSizeSignals)
        {
            if (combinedSignal == nil)
                combinedSignal = [signal startOn:queue];
            else
                combinedSignal = [[combinedSignal then:signal] startOn:queue];
        }
        
        [[[combinedSignal deliverOn:[SQueue mainQueue]] mapToSignal:^SSignal *(NSDictionary *desc)
        {
            if ([desc[@"type"] isEqualToString:@"photo"])
            {
                return [[TGMediaAssetsLibrary sharedLibrary] saveAssetWithImageAtUrl:desc[@"url"]];
            }
            else if ([desc[@"type"] isEqualToString:@"video"])
            {
                return [[TGMediaAssetsLibrary sharedLibrary] saveAssetWithVideoAtUrl:desc[@"url"]];
            }
            else
            {
                return [SSignal complete];
            }
        }] startWithNext:nil];
    }
    
    static dispatch_once_t onceToken;
    static UIImage *blankImage;
    dispatch_once(&onceToken, ^
    {
        UIGraphicsBeginImageContextWithOptions(CGSizeMake(1, 1), true, 0.0f);
        
        CGContextRef context = UIGraphicsGetCurrentContext();
        CGContextSetFillColorWithColor(context, [UIColor blackColor].CGColor);
        CGContextFillRect(context, CGRectMake(0, 0, 1, 1));
        
        blankImage = UIGraphicsGetImageFromCurrentImageContext();
        UIGraphicsEndImageContext();
    });
    
    SSignal *(^inlineThumbnailSignal)(id<TGMediaEditableItem>) = ^SSignal *(id<TGMediaEditableItem> item)
    {
        return [item thumbnailImageSignal];
    };
    
    NSNumber *groupedId;
    NSInteger i = 0;
    if (selectionContext.grouping && selectedItems.count > 1)
        groupedId = @([TGCameraController generateGroupedId]);
    
    bool hasAnyTimers = false;
    if (editingContext != nil)
    {
        for (id<TGMediaEditableItem> item in selectedItems)
        {
            if ([editingContext timerForItem:item] != nil)
            {
                hasAnyTimers = true;
                break;
            }
        }
    }
    
    for (id<TGMediaEditableItem> asset in selectedItems)
    {
        if ([asset isKindOfClass:[TGCameraCapturedPhoto class]])
        {
            NSAttributedString *caption = [editingContext captionForItem:asset];
            id<TGMediaEditAdjustments> adjustments = [editingContext adjustmentsForItem:asset];
            NSNumber *timer = [editingContext timerForItem:asset];

            SSignal *inlineSignal = [[asset screenImageSignal:0.0] map:^id(UIImage *originalImage)
            {
                NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
                dict[@"type"] = @"editedPhoto";
                dict[@"image"] = originalImage;
                                         
                if (timer != nil)
                    dict[@"timer"] = timer;
                else if (groupedId != nil && !hasAnyTimers)
                    dict[@"groupedId"] = groupedId;
                
                if (isScan) {
                    if (caption != nil)
                        dict[@"caption"] = caption;
                    return dict;
                } else {
                    id generatedItem = descriptionGenerator(dict, caption, nil);
                    return generatedItem;
                }
            }];
            
            SSignal *assetSignal = inlineSignal;
            SSignal *imageSignal = assetSignal;
            if (editingContext != nil)
            {
                imageSignal = [[[[[editingContext imageSignalForItem:asset 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 [SSignal fail:nil];
                    }
                    else if ([result isKindOfClass:[UIImage class]])
                    {
                        UIImage *image = (UIImage *)result;
                        image.edited = true;
                        return [SSignal single:image];
                    }
                    
                    return [SSignal complete];
                }] onCompletion:^
                {
                    
                }];
            } else {
                NSLog(@"Editing context is nil");
            }
            
            [signals addObject:[[[imageSignal map:^NSDictionary *(UIImage *image)
            {
                NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
                dict[@"type"] = @"editedPhoto";
                dict[@"image"] = image;
                if (adjustments.paintingData.stickers.count > 0)
                    dict[@"stickers"] = adjustments.paintingData.stickers;
                
                bool animated = adjustments.paintingData.hasAnimation;
                if (animated) {
                    dict[@"isAnimation"] = @true;
                    if ([adjustments isKindOfClass:[PGPhotoEditorValues class]]) {
                        dict[@"adjustments"] = [TGVideoEditAdjustments editAdjustmentsWithPhotoEditorValues:(PGPhotoEditorValues *)adjustments preset:TGMediaVideoConversionPresetAnimation];
                    } else {
                        dict[@"adjustments"] = adjustments;
                    }
                    
                    NSString *filePath = [NSTemporaryDirectory() stringByAppendingPathComponent:[[NSString alloc] initWithFormat:@"gifvideo_%x.jpg", (int)arc4random()]];
                    NSData *data = UIImageJPEGRepresentation(image, 0.8);
                    [data writeToFile:filePath atomically:true];
                    dict[@"url"] = [NSURL fileURLWithPath:filePath];
                    
                    if ([adjustments cropAppliedForAvatar:false] || adjustments.hasPainting || adjustments.toolsApplied)
                    {
                        UIImage *paintingImage = adjustments.paintingData.stillImage;
                        if (paintingImage == nil) {
                            paintingImage = adjustments.paintingData.image;
                        }
                        UIImage *thumbnailImage = TGPhotoEditorVideoExtCrop(image, paintingImage, adjustments.cropOrientation, adjustments.cropRotation, adjustments.cropRect, adjustments.cropMirrored, TGScaleToFill(image.size, CGSizeMake(512, 512)), adjustments.originalSize, true, true, true, false);
                        if (thumbnailImage != nil) {
                            dict[@"previewImage"] = thumbnailImage;
                        }
                    }
                }
                
                if (timer != nil)
                    dict[@"timer"] = timer;
                else if (groupedId != nil && !hasAnyTimers)
                    dict[@"groupedId"] = groupedId;
                
                if (isScan) {
                    if (caption != nil)
                        dict[@"caption"] = caption;
                    return dict;
                } else {
                    id generatedItem = descriptionGenerator(dict, caption, nil);
                    return generatedItem;
                }
            }] catch:^SSignal *(__unused id error)
            {
                return inlineSignal;
            }] onCompletion:^{
                [editingContext description];
            }]];
            
            i++;
        }
        else if ([asset isKindOfClass:[TGCameraCapturedVideo class]])
        {
            TGCameraCapturedVideo *video = (TGCameraCapturedVideo *)asset;
            
            TGVideoEditAdjustments *adjustments = (TGVideoEditAdjustments *)[editingContext adjustmentsForItem:asset];
            NSAttributedString *caption = [editingContext captionForItem:asset];
            NSNumber *timer = [editingContext timerForItem:asset];
            
            UIImage *(^cropVideoThumbnail)(UIImage *, CGSize, CGSize, bool) = ^UIImage *(UIImage *image, CGSize targetSize, CGSize sourceSize, bool resize)
            {
                if ([adjustments cropAppliedForAvatar:false] || adjustments.hasPainting || adjustments.toolsApplied)
                {
                    CGRect scaledCropRect = CGRectMake(adjustments.cropRect.origin.x * image.size.width / adjustments.originalSize.width, adjustments.cropRect.origin.y * image.size.height / adjustments.originalSize.height, adjustments.cropRect.size.width * image.size.width / adjustments.originalSize.width, adjustments.cropRect.size.height * image.size.height / adjustments.originalSize.height);
                    UIImage *paintingImage = adjustments.paintingData.stillImage;
                    if (paintingImage == nil) {
                        paintingImage = adjustments.paintingData.image;
                    }
                    if (adjustments.toolsApplied) {
                        image = [PGPhotoEditor resultImageForImage:image adjustments:adjustments];
                    }
                    return TGPhotoEditorCrop(image, paintingImage, adjustments.cropOrientation, 0, scaledCropRect, adjustments.cropMirrored, targetSize, sourceSize, resize);
                }
                
                return image;
            };
            
            CGSize imageSize = TGFillSize(asset.originalSize, CGSizeMake(512, 512));
            SSignal *trimmedVideoThumbnailSignal = [[video avAsset] mapToSignal:^SSignal *(AVURLAsset *avAsset) {
                return [[TGMediaAssetImageSignals videoThumbnailForAVAsset:avAsset size:imageSize timestamp:CMTimeMakeWithSeconds(adjustments.trimStartValue, NSEC_PER_SEC)] map:^UIImage *(UIImage *image)
                {
                    return cropVideoThumbnail(image, TGScaleToFill(asset.originalSize, CGSizeMake(512, 512)), asset.originalSize, true);
                }];
            }];
            
            SSignal *videoThumbnailSignal = [inlineThumbnailSignal(asset) map:^UIImage *(UIImage *image)
            {
                return cropVideoThumbnail(image, image.size, image.size, false);
            }];
            
            SSignal *thumbnailSignal = adjustments.trimStartValue > FLT_EPSILON ? trimmedVideoThumbnailSignal : videoThumbnailSignal;
            
            TGMediaVideoConversionPreset preset = [TGMediaVideoConverter presetFromAdjustments:adjustments];
            CGSize dimensions = [TGMediaVideoConverter dimensionsFor:asset.originalSize adjustments:adjustments preset:preset];
            NSTimeInterval duration = adjustments.trimApplied ? (adjustments.trimEndValue - adjustments.trimStartValue) : video.videoDuration;
            
            [signals addObject:[thumbnailSignal mapToSignal:^id(UIImage *image)
            {
                return [video.avAsset map:^id(AVURLAsset *avAsset) {
                    NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
                    dict[@"type"] = @"cameraVideo";
                    dict[@"url"] = avAsset.URL;
                    dict[@"previewImage"] = image;
                    dict[@"adjustments"] = adjustments;
                    dict[@"dimensions"] = [NSValue valueWithCGSize:dimensions];
                    dict[@"duration"] = @(duration);
                    
                    if (adjustments.paintingData.stickers.count > 0)
                        dict[@"stickers"] = adjustments.paintingData.stickers;
                    if (timer != nil)
                        dict[@"timer"] = timer;
                    else if (groupedId != nil && !hasAnyTimers)
                        dict[@"groupedId"] = groupedId;
                    
                    id generatedItem = descriptionGenerator(dict, caption, nil);
                    return generatedItem;
                }];
            }]];
            
            i++;
        }
     
        if (groupedId != nil && i == 10)
        {
            i = 0;
            groupedId = @([TGCameraController generateGroupedId]);
        }
    }
    
    if (isScan) {
        SSignal *scanSignal = [[SSignal combineSignals:signals] map:^NSDictionary *(NSArray *results) {
            NSMutableData *data = [[NSMutableData alloc] init];
            UIImage *previewImage = nil;
            UIGraphicsBeginPDFContextToData(data, CGRectZero, nil);
            for (NSDictionary *dict in results) {
                if ([dict[@"type"] isEqual:@"editedPhoto"]) {
                    UIImage *image = dict[@"image"];
                    if (previewImage == nil) {
                        previewImage = image;
                    }
                    if (image != nil) {
                        CGRect rect = CGRectMake(0, 0, image.size.width, image.size.height);
                        UIGraphicsBeginPDFPageWithInfo(rect, nil);
                        CGContextRef pdfContext = UIGraphicsGetCurrentContext();
                        
                        CGContextTranslateCTM(pdfContext, 0, image.size.height);
                        CGContextScaleCTM(pdfContext, 1.0, -1.0);
                        
                        NSData *jpegData = UIImageJPEGRepresentation(image, 0.65);
                        CGDataProviderRef dataProvider = CGDataProviderCreateWithCFData((__bridge CFDataRef)jpegData);
                        CGImageRef cgImage = CGImageCreateWithJPEGDataProvider(dataProvider, NULL, true, kCGRenderingIntentDefault);
                        CGContextDrawImage(pdfContext, rect, cgImage);
                        
                        CGDataProviderRelease(dataProvider);
                        CGImageRelease(cgImage);
                    }
                }
            }
            UIGraphicsEndPDFContext();
            
            NSString *filePath = [NSTemporaryDirectory() stringByAppendingPathComponent:[[NSString alloc] initWithFormat:@"scan_%x.pdf", (int)arc4random()]];
            [data writeToFile:filePath atomically:true];
            
            NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
            dict[@"type"] = @"file";
            dict[@"previewImage"] = previewImage;
            dict[@"tempFileUrl"] = [NSURL fileURLWithPath:filePath];
            dict[@"fileName"] = @"Document Scan.pdf";
            dict[@"mimeType"] = @"application/pdf";
            
            id generatedItem = descriptionGenerator(dict, dict[@"caption"], nil);
            return generatedItem;
        }];
        signals = [NSMutableArray arrayWithObject:scanSignal];
    }
    
    return signals;
}

+ (int64_t)generateGroupedId
{
    int64_t value;
    arc4random_buf(&value, sizeof(int64_t));
    return value;
}

#pragma mark - Start Image

static UIImage *startImage = nil;

+ (UIImage *)startImage
{
    if (startImage == nil)
        startImage = TGComponentsImageNamed (@"CameraPlaceholder.jpg");
    
    return startImage;
}

+ (void)saveStartImage:(UIImage *)image
{
    if (image == nil)
        return;
    
    startImage = image;
}

@end