Various Improvements

This commit is contained in:
Ilya Laktyushin 2021-07-21 14:10:17 +03:00
commit 007af103b9
68 changed files with 6177 additions and 6085 deletions

View File

@ -1692,6 +1692,8 @@ plist_fragment(
<key>UIAppFonts</key>
<array>
<string>SFCompactRounded-Semibold.otf</string>
<string>AremacFS-Regular.otf</string>
<string>AremacFS-Semibold.otf</string>
</array>
<key>UIBackgroundModes</key>
<array>

View File

@ -6571,3 +6571,8 @@ Sorry for the inconvenience.";
"TwoFactorRemember.Done.Title" = "Perfect!";
"TwoFactorRemember.Done.Text" = "You still remember your password.";
"TwoFactorRemember.Done.Action" = "Back to Settings";
"VoiceChat.VideoPreviewFrontCamera" = "Front Camera";
"VoiceChat.VideoPreviewBackCamera" = "Back Camera";
"VoiceChat.VideoPreviewContinue" = "Continue";
"VoiceChat.VideoPreviewShareScreenInfo" = "Everything on your screen, including notifications, will be shared.";

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "ic_cam_close.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,9 @@
{
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"provides-namespace" : true
}
}

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "ic_cam_flashoff (1).pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "ic_cam_flashon.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "ic_cam_flip.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -1,6 +1,6 @@
{
"info" : {
"version" : 1,
"author" : "xcode"
"author" : "xcode",
"version" : 1
}
}
}

View File

@ -80,7 +80,6 @@
#import <LegacyComponents/TGCameraMainView.h>
#import <LegacyComponents/TGCameraModeControl.h>
#import <LegacyComponents/TGCameraPreviewView.h>
#import <LegacyComponents/TGCameraSegmentsView.h>
#import <LegacyComponents/TGCameraShutterButton.h>
#import <LegacyComponents/TGCameraTimeCodeView.h>
#import <LegacyComponents/TGCameraZoomView.h>

View File

@ -26,7 +26,8 @@ typedef enum
PGCameraModeVideo,
PGCameraModeSquarePhoto,
PGCameraModeSquareVideo,
PGCameraModeSquareSwing
PGCameraModeSquareSwing,
PGCameraModePhotoScan
} PGCameraMode;
typedef enum
@ -132,4 +133,7 @@ typedef enum
+ (PGCameraAuthorizationStatus)cameraAuthorizationStatus;
+ (PGMicrophoneAuthorizationStatus)microphoneAuthorizationStatus;
+ (bool)isPhotoCameraMode:(PGCameraMode)mode;
+ (bool)isVideoCameraMode:(PGCameraMode)mode;
@end

View File

@ -3,6 +3,7 @@
#import <LegacyComponents/PGCamera.h>
@class PGCameraMovieWriter;
@class PGRectangleDetector;
@interface PGCameraCaptureSession : AVCaptureSession
@ -12,6 +13,7 @@
@property (nonatomic, readonly) AVCaptureAudioDataOutput *audioOutput;
@property (nonatomic, readonly) AVCaptureMetadataOutput *metadataOutput;
@property (nonatomic, readonly) PGCameraMovieWriter *movieWriter;
@property (nonatomic, readonly) PGRectangleDetector *rectangleDetector;
@property (nonatomic, assign) bool alwaysSetFlash;
@property (nonatomic, assign) PGCameraMode currentMode;

View File

@ -1,9 +1,12 @@
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
@class PGRectangle;
@interface PGCameraShotMetadata : NSObject
@property (nonatomic, assign) CGFloat deviceAngle;
@property (nonatomic, strong) PGRectangle *rectangle;
+ (CGFloat)relativeDeviceAngleFromAngle:(CGFloat)angle orientation:(UIInterfaceOrientation)orientation;

View File

@ -1,9 +1,16 @@
#import <LegacyComponents/TGMediaEditingContext.h>
@class TGPaintingData;
@class PGRectangle;
@interface PGPhotoEditorValues : NSObject <TGMediaEditAdjustments>
@property (nonatomic, readonly) PGRectangle *cropRectangle;
@property (nonatomic, readonly) CGSize cropSize;
@property (nonatomic, readonly) bool enhanceDocument;
+ (instancetype)editorValuesWithOriginalSize:(CGSize)originalSize cropRectangle:(PGRectangle *)cropRectangle cropOrientation:(UIImageOrientation)cropOrientation cropSize:(CGSize)cropSize enhanceDocument:(bool)enhanceDocument paintingData:(TGPaintingData *)paintingData;
+ (instancetype)editorValuesWithOriginalSize:(CGSize)originalSize cropRect:(CGRect)cropRect cropRotation:(CGFloat)cropRotation cropOrientation:(UIImageOrientation)cropOrientation cropLockedAspectRatio:(CGFloat)cropLockedAspectRatio cropMirrored:(bool)cropMirrored toolValues:(NSDictionary *)toolValues paintingData:(TGPaintingData *)paintingData sendAsGif:(bool)sendAsGif;
@end

View File

@ -3,15 +3,19 @@
#import <LegacyComponents/TGMediaSelectionContext.h>
@class PGCameraShotMetadata;
@class PGRectangle;
@interface TGCameraCapturedPhoto : NSObject <TGMediaEditableItem, TGMediaSelectableItem>
@property (nonatomic, readonly) NSURL *url;
@property (nonatomic, readonly) PGCameraShotMetadata *metadata;
@property (nonatomic, readonly) PGRectangle *rectangle;
- (instancetype)initWithImage:(UIImage *)image metadata:(PGCameraShotMetadata *)metadata;
- (instancetype)initWithExistingImage:(UIImage *)image;
- (instancetype)initWithImage:(UIImage *)image rectangle:(PGRectangle *)rectangle;
- (void)_cleanUp;
@end

View File

@ -2,7 +2,12 @@
@interface TGCameraFlipButton : TGModernButton
- (instancetype)initWithFrame:(CGRect)frame large:(bool)large;
- (void)setHidden:(bool)hidden animated:(bool)animated;
@end
@interface TGCameraCancelButton : TGModernButton
- (void)setHidden:(bool)hidden animated:(bool)animated;
@end

View File

@ -10,7 +10,9 @@
+ (UIColor *)panelBackgroundColor;
+ (UIColor *)transparentPanelBackgroundColor;
+ (UIColor *)transparentOverlayBackgroundColor;
+ (UIColor *)buttonColor;
+ (UIFont *)normalFontOfSize:(CGFloat)size;
+ (UIFont *)regularFontOfSize:(CGFloat)size;
+ (UIFont *)boldFontOfSize:(CGFloat)size;
@end

View File

@ -10,7 +10,7 @@
@class TGCameraFlipButton;
@class TGCameraTimeCodeView;
@class TGCameraZoomView;
@class TGCameraSegmentsView;
@class TGCameraToastView;
@class TGMediaPickerPhotoCounterButton;
@class TGMediaPickerPhotoStripView;
@class TGMediaPickerGallerySelectedItemsModel;
@ -26,6 +26,8 @@
TGCameraFlipButton *_flipButton;
TGCameraTimeCodeView *_timecodeView;
TGCameraToastView *_toastView;
TGMediaPickerPhotoCounterButton *_photoCounterButton;
TGMediaPickerPhotoStripView *_selectedPhotosView;
@ -51,8 +53,6 @@
@property (nonatomic, copy) void(^resultPressed)(NSInteger index);
@property (nonatomic, copy) void(^itemRemoved)(NSInteger index);
@property (nonatomic, copy) void (^deleteSegmentButtonPressed)(void);
@property (nonatomic, copy) NSTimeInterval(^requestedVideoRecordingDuration)(void);
@property (nonatomic, assign) CGRect previewViewFrame;
@ -64,6 +64,8 @@
- (void)updateForCameraModeChangeWithPreviousMode:(PGCameraMode)previousMode;
- (void)updateForCameraModeChangeAfterResize;
- (void)setToastMessage:(NSString *)message animated:(bool)animated;
- (void)setFlashMode:(PGCameraFlashMode)mode;
- (void)setFlashActive:(bool)active;
- (void)setFlashUnavailable:(bool)unavailable;

View File

@ -1,20 +0,0 @@
#import <UIKit/UIKit.h>
@interface TGCameraSegmentsView : UIView
@property (nonatomic, copy) void (^deletePressed)(void);
- (void)setSegments:(NSArray *)segments;
- (void)startCurrentSegment;
- (void)setCurrentSegment:(CGFloat)length;
- (void)commitCurrentSegmentWithCompletion:(void (^)(void))completion;
- (void)highlightLastSegment;
- (void)removeLastSegment;
- (void)setHidden:(bool)hidden animated:(bool)animated delay:(NSTimeInterval)delay;
- (void)setDeleteButtonHidden:(bool)hidden animated:(bool)animated;
@end

View File

@ -843,4 +843,14 @@ NSString *const PGCameraAdjustingFocusKey = @"adjustingFocus";
}
}
+ (bool)isPhotoCameraMode:(PGCameraMode)mode
{
return mode == PGCameraModePhoto || mode == PGCameraModeSquarePhoto || mode == PGCameraModePhotoScan;
}
+ (bool)isVideoCameraMode:(PGCameraMode)mode
{
return mode == PGCameraModeVideo || mode == PGCameraModeSquareVideo;
}
@end

View File

@ -1,5 +1,6 @@
#import "PGCameraCaptureSession.h"
#import "PGCameraMovieWriter.h"
#import "PGRectangleDetector.h"
#import <LegacyComponents/LegacyComponentsGlobals.h>
#import <LegacyComponents/TGPhotoEditorUtils.h>
@ -261,10 +262,15 @@ const NSInteger PGCameraFrameRate = 30;
{
case PGCameraModePhoto:
case PGCameraModeSquarePhoto:
case PGCameraModePhotoScan:
{
[self _removeAudioInputEndAudioSession:true];
self.sessionPreset = AVCaptureSessionPresetPhoto;
[self setFrameRate:0 forDevice:_videoDevice];
if (mode == PGCameraModePhotoScan) {
[self setCurrentCameraPosition:PGCameraPositionRear];
}
}
break;
@ -287,6 +293,14 @@ const NSInteger PGCameraFrameRate = 30;
[self _enableVideoStabilization];
[self commitConfiguration];
if (mode == PGCameraModePhotoScan) {
if (_rectangleDetector == nil) {
_rectangleDetector = [[PGRectangleDetector alloc] init];
}
} else {
_rectangleDetector = nil;
}
}
- (void)switchToBestVideoFormatForDevice:(AVCaptureDevice *)device
@ -630,9 +644,11 @@ const NSInteger PGCameraFrameRate = 30;
- (void)setCurrentCameraPosition:(PGCameraPosition)position
{
NSError *error;
AVCaptureDevice *deviceForTargetPosition = [PGCameraCaptureSession _deviceWithCameraPosition:position];
if ([_videoDevice isEqual:deviceForTargetPosition])
return;
NSError *error;
AVCaptureDeviceInput *newVideoInput = [[AVCaptureDeviceInput alloc] initWithDevice:deviceForTargetPosition error:&error];
if (newVideoInput != nil)
@ -924,6 +940,11 @@ static UIImageOrientation TGSnapshotOrientationForVideoOrientation(bool mirrored
if (_movieWriter.isRecording)
[_movieWriter _processSampleBuffer:sampleBuffer];
if (_rectangleDetector != nil) {
CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
[_rectangleDetector detectRectangle:imageBuffer];
}
if (!_captureNextFrame || captureOutput != _videoOutput)
return;

View File

@ -15,6 +15,20 @@
@synthesize sendAsGif = _sendAsGif;
@synthesize toolValues = _toolValues;
+ (instancetype)editorValuesWithOriginalSize:(CGSize)originalSize cropRectangle:(PGRectangle *)cropRectangle cropOrientation:(UIImageOrientation)cropOrientation cropSize:(CGSize)cropSize enhanceDocument:(bool)enhanceDocument paintingData:(TGPaintingData *)paintingData
{
PGPhotoEditorValues *values = [[PGPhotoEditorValues alloc] init];
values->_originalSize = originalSize;
values->_cropRect = CGRectMake(0.0, 0.0, cropSize.width, cropSize.height);
values->_cropSize = cropSize;
values->_cropRectangle = cropRectangle;
values->_cropOrientation = cropOrientation;
values->_enhanceDocument = enhanceDocument;
values->_paintingData = paintingData;
return values;
}
+ (instancetype)editorValuesWithOriginalSize:(CGSize)originalSize cropRect:(CGRect)cropRect cropRotation:(CGFloat)cropRotation cropOrientation:(UIImageOrientation)cropOrientation cropLockedAspectRatio:(CGFloat)cropLockedAspectRatio cropMirrored:(bool)cropMirrored toolValues:(NSDictionary *)toolValues paintingData:(TGPaintingData *)paintingData sendAsGif:(bool)sendAsGif
{
PGPhotoEditorValues *values = [[PGPhotoEditorValues alloc] init];
@ -27,7 +41,6 @@
values->_toolValues = toolValues;
values->_paintingData = paintingData;
values->_sendAsGif = sendAsGif;
return values;
}
@ -38,6 +51,9 @@
- (bool)cropAppliedForAvatar:(bool)forAvatar
{
if (_cropRectangle != nil)
return true;
CGRect defaultCropRect = CGRectMake(0, 0, _originalSize.width, _originalSize.height);
if (forAvatar)
{
@ -65,6 +81,9 @@
- (bool)toolsApplied
{
if (_enhanceDocument)
return true;
if (self.toolValues.count > 0)
return true;
@ -112,6 +131,9 @@
if (self.paintingData != values.paintingData && ![self.paintingData isEqual:values.paintingData])
return false;
if (self.enhanceDocument != values.enhanceDocument)
return false;
return true;
}

View File

@ -0,0 +1,24 @@
#import <UIKit/UIKit.h>
@interface PGRectangle : NSObject
@property (nonatomic, readonly) CGPoint topLeft;
@property (nonatomic, readonly) CGPoint topRight;
@property (nonatomic, readonly) CGPoint bottomLeft;
@property (nonatomic, readonly) CGPoint bottomRight;
- (PGRectangle *)transform:(CGAffineTransform)transform;
- (PGRectangle *)rotate90;
- (PGRectangle *)sort;
- (PGRectangle *)cartesian:(CGFloat)height;
@end
@interface PGRectangleDetector : NSObject
@property (nonatomic, copy) void(^update)(bool, PGRectangle *);
- (void)detectRectangle:(CVPixelBufferRef)pixelBuffer;
@end

View File

@ -0,0 +1,362 @@
#import "PGRectangleDetector.h"
#import "LegacyComponentsInternal.h"
#import <Vision/Vision.h>
#import <CoreImage/CoreImage.h>
#import <SSignalKit/SSignalKit.h>
@interface PGRectangle ()
- (instancetype)initWithRectangleFeature:(CIRectangleFeature *)rectangleFeature;
- (instancetype)initWithRectangleObservation:(VNRectangleObservation *)rectangleObservation API_AVAILABLE(ios(11.0));
- (CGFloat)size;
@end
@interface PGRectangleEntry : NSObject
@property (nonatomic, readonly) PGRectangle *rectangle;
@property (nonatomic, assign) NSInteger rate;
- (instancetype)initWithRectangle:(PGRectangle *)rectangle;
@end
@implementation PGRectangleEntry
- (instancetype)initWithRectangle:(PGRectangle *)rectangle
{
self = [super init];
if (self != nil)
{
_rectangle = rectangle;
_rate = 0;
}
return self;
}
@end
@implementation PGRectangle
- (instancetype)initWithRectangleFeature:(CIRectangleFeature *)rectangleFeature
{
self = [super init];
if (self != nil) {
_topLeft = rectangleFeature.topLeft;
_topRight = rectangleFeature.topRight;
_bottomLeft = rectangleFeature.bottomLeft;
_bottomRight = rectangleFeature.bottomRight;
}
return self;
}
- (instancetype)initWithRectangleObservation:(VNRectangleObservation *)rectangleObservation API_AVAILABLE(ios(11.0))
{
self = [super init];
if (self != nil) {
_topLeft = rectangleObservation.topLeft;
_topRight = rectangleObservation.topRight;
_bottomLeft = rectangleObservation.bottomLeft;
_bottomRight = rectangleObservation.bottomRight;
}
return self;
}
- (PGRectangle *)transform:(CGAffineTransform)transform
{
PGRectangle *rectangle = [[PGRectangle alloc] init];
rectangle->_topLeft = CGPointApplyAffineTransform(_topLeft, transform);
rectangle->_topRight = CGPointApplyAffineTransform(_topRight, transform);
rectangle->_bottomLeft = CGPointApplyAffineTransform(_bottomLeft, transform);
rectangle->_bottomRight = CGPointApplyAffineTransform(_bottomRight, transform);
return rectangle;
}
- (PGRectangle *)rotate90
{
PGRectangle *rectangle = [[PGRectangle alloc] init];
rectangle->_topLeft = CGPointMake(_topLeft.y, _topLeft.x);
rectangle->_topRight = CGPointMake(_topRight.y, _topRight.x);
rectangle->_bottomLeft = CGPointMake(_bottomLeft.y, _bottomLeft.x);
rectangle->_bottomRight = CGPointMake(_bottomRight.y, _bottomRight.x);
return rectangle;
}
- (PGRectangle *)sort
{
NSArray *points = @[ [NSValue valueWithCGPoint:_topLeft], [NSValue valueWithCGPoint:_topRight], [NSValue valueWithCGPoint:_bottomLeft], [NSValue valueWithCGPoint:_bottomRight] ];
NSArray *ySorted = [points sortedArrayUsingComparator:^NSComparisonResult(id firstObject, id secondObject) {
CGPoint firstPoint = [firstObject CGPointValue];
CGPoint secondPoint = [secondObject CGPointValue];
if (firstPoint.y < secondPoint.y) {
return NSOrderedAscending;
} else {
return NSOrderedDescending;
}
}];
NSArray *top = [ySorted subarrayWithRange:NSMakeRange(0, 2)];
NSArray *bottom = [ySorted subarrayWithRange:NSMakeRange(2, 2)];
NSArray *xSortedTop = [top sortedArrayUsingComparator:^NSComparisonResult(id firstObject, id secondObject) {
CGPoint firstPoint = [firstObject CGPointValue];
CGPoint secondPoint = [secondObject CGPointValue];
if (firstPoint.x < secondPoint.x) {
return NSOrderedAscending;
} else {
return NSOrderedDescending;
}
}];
NSArray *xSortedBottom = [bottom sortedArrayUsingComparator:^NSComparisonResult(id firstObject, id secondObject) {
CGPoint firstPoint = [firstObject CGPointValue];
CGPoint secondPoint = [secondObject CGPointValue];
if (firstPoint.x < secondPoint.x) {
return NSOrderedAscending;
} else {
return NSOrderedDescending;
}
}];
PGRectangle *rectangle = [[PGRectangle alloc] init];
rectangle->_topLeft = [xSortedTop[0] CGPointValue];
rectangle->_topRight = [xSortedTop[1] CGPointValue];
rectangle->_bottomLeft = [xSortedBottom[0] CGPointValue];
rectangle->_bottomRight = [xSortedBottom[1] CGPointValue];
return rectangle;
}
- (PGRectangle *)cartesian:(CGFloat)height
{
PGRectangle *rectangle = [[PGRectangle alloc] init];
rectangle->_topLeft = CGPointMake(_topLeft.x, height - _topLeft.y);
rectangle->_topRight = CGPointMake(_topRight.x, height - _topRight.y);
rectangle->_bottomLeft = CGPointMake(_bottomLeft.x, height - _bottomLeft.y);
rectangle->_bottomRight = CGPointMake(_bottomRight.x, height - _bottomRight.y);
return rectangle;
}
- (PGRectangle *)normalize:(CGSize)size
{
return [self transform:CGAffineTransformMakeScale(1.0 / size.width, 1.0 / size.height)];
}
+ (CGFloat)distance:(CGPoint)a to:(CGPoint)b
{
return hypot(a.x - b.x, a.y - b.y);
}
- (CGFloat)size
{
CGFloat sum = 0.0f;
sum += [PGRectangle distance:self.topLeft to:self.topRight];
sum += [PGRectangle distance:self.topRight to:self.bottomRight];
sum += [PGRectangle distance:self.bottomRight to:self.bottomLeft];
sum += [PGRectangle distance:self.bottomLeft to:self.topLeft];
return sum;
}
+ (CGRect)pointSquare:(CGPoint)point size:(CGFloat)size
{
return CGRectMake(point.x - size / 2.0, point.y - size / 2.0, size, size);
}
- (bool)matches:(PGRectangle *)other threshold:(CGFloat)threshold
{
if (!CGRectContainsPoint([PGRectangle pointSquare:self.topLeft size:threshold], other.topLeft))
return false;
if (!CGRectContainsPoint([PGRectangle pointSquare:self.topRight size:threshold], other.topRight))
return false;
if (!CGRectContainsPoint([PGRectangle pointSquare:self.bottomLeft size:threshold], other.bottomLeft))
return false;
if (!CGRectContainsPoint([PGRectangle pointSquare:self.bottomRight size:threshold], other.bottomRight))
return false;
return true;
}
@end
@implementation PGRectangleDetector
{
SQueue *_queue;
CIDetector *_detector;
bool _disabled;
CGSize _imageSize;
NSInteger _notFoundCount;
NSMutableArray *_rectangles;
PGRectangle *_detectedRectangle;
NSInteger _autoscanCount;
}
- (instancetype)init
{
self = [super init];
if (self != nil) {
_queue = [[SQueue alloc] init];
_rectangles = [[NSMutableArray alloc] init];
}
return self;
}
- (void)updateEntries
{
for (PGRectangleEntry *entry in _rectangles) {
entry.rate = 1;
}
for (NSInteger i = 0; i < _rectangles.count; i++) {
for (NSInteger j = 0; i < _rectangles.count; i++) {
if (j > i && [[_rectangles[i] rectangle] matches:_rectangles[j] threshold:40.0]) {
((PGRectangleEntry *)_rectangles[i]).rate += 1;
((PGRectangleEntry *)_rectangles[j]).rate += 1;
}
}
}
}
- (void)addRectangle:(PGRectangle *)rectangle
{
if (_disabled)
return;
PGRectangleEntry *entry = [[PGRectangleEntry alloc] initWithRectangle:rectangle];
[_rectangles addObject:entry];
if (_rectangles.count < 3)
return;
if (_rectangles.count > 8)
[_rectangles removeObjectAtIndex:0];
[self updateEntries];
__block PGRectangleEntry *best = nil;
[_rectangles enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(PGRectangleEntry *rectangle, NSUInteger idx, BOOL * stop) {
if (best == nil) {
best = rectangle;
return;
}
if (rectangle.rate > best.rate) {
best = rectangle;
} else if (rectangle.rate == best.rate) {
if (_detectedRectangle != nil) {
if ([rectangle.rectangle matches:_detectedRectangle threshold:40.0]) {
best = rectangle;
}
}
}
}];
if (_detectedRectangle != nil && [best.rectangle matches:_detectedRectangle threshold:24.0f]) {
_autoscanCount += 1;
_detectedRectangle = best.rectangle;
if (_autoscanCount > 20) {
_autoscanCount = 0;
self.update(true, [_detectedRectangle normalize:_imageSize]);
_detectedRectangle = nil;
_disabled = true;
TGDispatchAfter(2.0, _queue._dispatch_queue, ^{
_disabled = false;
});
}
} else {
_autoscanCount = 0;
_detectedRectangle = best.rectangle;
self.update(false, [_detectedRectangle normalize:_imageSize]);
}
}
- (void)processRectangle:(PGRectangle *)rectangle imageSize:(CGSize)imageSize
{
_imageSize = imageSize;
if (rectangle != nil) {
_notFoundCount = 0;
[self addRectangle:rectangle];
} else {
_notFoundCount += 1;
if (_notFoundCount > 3) {
_autoscanCount = 0;
_detectedRectangle = nil;
self.update(false, nil);
}
}
}
- (void)detectRectangle:(CVPixelBufferRef)pixelBuffer
{
CGSize size = CGSizeMake(CVPixelBufferGetWidth(pixelBuffer), CVPixelBufferGetHeight(pixelBuffer));
if (@available(iOS 11.0, *)) {
CVPixelBufferRetain(pixelBuffer);
NSError *error;
VNImageRequestHandler *handler = [[VNImageRequestHandler alloc] initWithCVPixelBuffer:pixelBuffer options:@{}];
VNDetectRectanglesRequest *request = [[VNDetectRectanglesRequest alloc] initWithCompletionHandler:^(VNRequest * _Nonnull request, NSError * _Nullable error) {
CVPixelBufferRelease(pixelBuffer);
[_queue dispatch:^{
if (error == nil && request.results.count > 0) {
PGRectangle *largestRectangle = nil;
for (VNRectangleObservation *result in request.results) {
if (![result isKindOfClass:[VNRectangleObservation class]])
continue;
PGRectangle *rectangle = [[PGRectangle alloc] initWithRectangleObservation:result];
if (largestRectangle == nil || largestRectangle.size < rectangle.size) {
largestRectangle = rectangle;
}
}
[self processRectangle:[largestRectangle transform:CGAffineTransformMakeScale(size.width, size.height)] imageSize:size];
} else {
[self processRectangle:nil imageSize:size];
}
}];
}];
request.minimumConfidence = 0.85f;
request.maximumObservations = 15;
request.minimumAspectRatio = 0.33;
request.minimumSize = 0.4;
[handler performRequests:@[request] error:&error];
} else {
CVPixelBufferRetain(pixelBuffer);
[_queue dispatch:^{
if (_detector == nil) {
_detector = [CIDetector detectorOfType:CIDetectorTypeRectangle context:[CIContext contextWithOptions:nil] options:@{ CIDetectorAccuracy: CIDetectorAccuracyHigh }];
}
CIImage *image = [[CIImage alloc] initWithCVPixelBuffer:pixelBuffer];
NSArray *results = [_detector featuresInImage:image];
CVPixelBufferRelease(pixelBuffer);
PGRectangle *largestRectangle = nil;
for (CIRectangleFeature *result in results) {
if (![result isKindOfClass:[CIRectangleFeature class]])
continue;
PGRectangle *rectangle = [[PGRectangle alloc] initWithRectangleFeature:result];
if (largestRectangle == nil || largestRectangle.size < rectangle.size) {
largestRectangle = rectangle;
}
}
[self processRectangle:largestRectangle imageSize:size];
}];
}
}
@end

View File

@ -32,6 +32,23 @@
return self;
}
- (instancetype)initWithImage:(UIImage *)image rectangle:(PGRectangle *)rectangle
{
self = [super init];
if (self != nil)
{
_identifier = [NSString stringWithFormat:@"%ld", lrand48()];
_dimensions = CGSizeMake(image.size.width * image.scale, image.size.height * image.scale);
PGCameraShotMetadata *metadata = [[PGCameraShotMetadata alloc] init];
metadata.rectangle = rectangle;
_metadata = metadata;
_thumbnail = [[SVariable alloc] init];
[self _saveToDisk:image];
}
return self;
}
- (instancetype)initWithExistingImage:(UIImage *)image
{
self = [super init];
@ -110,6 +127,11 @@
return [NSTemporaryDirectory() stringByAppendingPathComponent:[[NSString alloc] initWithFormat:@"camphoto_%@.jpg", _identifier]];
}
- (PGRectangle *)rectangle
{
return _metadata.rectangle;
}
- (NSURL *)url
{
return [NSURL fileURLWithPath:[self filePath]];

View File

@ -17,6 +17,7 @@
#import <LegacyComponents/TGCameraMainPhoneView.h>
#import <LegacyComponents/TGCameraMainTabletView.h>
#import "TGCameraFocusCrosshairsControl.h"
#import "TGCameraRectangleView.h"
#import <LegacyComponents/TGFullscreenContainerView.h>
#import <LegacyComponents/TGPhotoEditorController.h>
@ -50,6 +51,8 @@
#import "TGCameraCapturedVideo.h"
#import "PGPhotoEditor.h"
#import "PGRectangleDetector.h"
#import "TGWarpedView.h"
#import "TGAnimationUtils.h"
@ -106,9 +109,7 @@ static CGPoint TGCameraControllerClampPointToScreenSize(__unused id self, __unus
TGCameraMainView *_interfaceView;
UIView *_overlayView;
TGCameraFocusCrosshairsControl *_focusControl;
TGModernGalleryVideoView *_segmentPreviewView;
bool _previewingSegment;
TGCameraRectangleView *_rectangleView;
UISwipeGestureRecognizer *_photoSwipeGestureRecognizer;
UISwipeGestureRecognizer *_videoSwipeGestureRecognizer;
@ -281,6 +282,11 @@ static CGPoint TGCameraControllerClampPointToScreenSize(__unused id self, __unus
[_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:)];
@ -360,7 +366,7 @@ static CGPoint TGCameraControllerClampPointToScreenSize(__unused id self, __unus
if (strongSelf == nil)
return;
[strongSelf->_camera setCameraMode:mode];
[strongSelf _updateCameraMode:mode updateInterface:false];
};
_interfaceView.flashModeChanged = ^(PGCameraFlashMode mode)
@ -478,6 +484,25 @@ static CGPoint TGCameraControllerClampPointToScreenSize(__unused id self, __unus
[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;
@ -514,9 +539,11 @@ static CGPoint TGCameraControllerClampPointToScreenSize(__unused id self, __unus
strongSelf.view.userInteractionEnabled = false;
PGCameraMode currentMode = strongSelf->_camera.cameraMode;
bool generalModeNotChanged = (mode == PGCameraModePhoto && currentMode == PGCameraModeSquarePhoto) || (mode == PGCameraModeSquarePhoto && currentMode == PGCameraModePhoto) || (mode == PGCameraModeVideo && currentMode == PGCameraModeSquareVideo) || (mode == PGCameraModeSquareVideo && currentMode == PGCameraModeVideo);
if ((mode == PGCameraModeVideo || mode == PGCameraModeSquareVideo) && !generalModeNotChanged)
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];
}
@ -570,6 +597,21 @@ static CGPoint TGCameraControllerClampPointToScreenSize(__unused id self, __unus
[[[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];
}
});
};
}
}
});
};
@ -874,9 +916,7 @@ static CGPoint TGCameraControllerClampPointToScreenSize(__unused id self, __unus
}];
};
_camera.autoStartVideoRecording = true;
[_camera setCameraMode:PGCameraModeVideo];
[_interfaceView setCameraMode:PGCameraModeVideo];
[self _updateCameraMode:PGCameraModeVideo updateInterface:true];
}
else if (_camera.cameraMode == PGCameraModeVideo)
{
@ -968,7 +1008,7 @@ static CGPoint TGCameraControllerClampPointToScreenSize(__unused id self, __unus
__weak TGCameraController *weakSelf = self;
PGCameraMode cameraMode = _camera.cameraMode;
if (cameraMode == PGCameraModePhoto || cameraMode == PGCameraModeSquarePhoto)
if (cameraMode == PGCameraModePhoto || cameraMode == PGCameraModeSquarePhoto || cameraMode == PGCameraModePhotoScan)
{
_camera.disabled = true;
@ -987,7 +1027,7 @@ static CGPoint TGCameraControllerClampPointToScreenSize(__unused id self, __unus
_buttonHandler.enabled = false;
[_buttonHandler ignoreEventsFor:1.5f andDisable:true];
}
[_camera takePhotoWithCompletion:^(UIImage *result, PGCameraShotMetadata *metadata)
{
__strong TGCameraController *strongSelf = weakSelf;
@ -1060,6 +1100,140 @@ static CGPoint TGCameraControllerClampPointToScreenSize(__unused id self, __unus
}
}
- (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)
@ -1182,27 +1356,34 @@ static CGPoint TGCameraControllerClampPointToScreenSize(__unused id self, __unus
return galleryItems;
}
- (void)_createContextsIfNeeded
{
TGMediaEditingContext *editingContext = _editingContext;
if (editingContext == nil)
{
editingContext = [[TGMediaEditingContext alloc] init];
if (self.forcedCaption != nil)
[editingContext setForcedCaption:self.forcedCaption entities:self.forcedEntities];
_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;
if (editingContext == nil)
{
editingContext = [[TGMediaEditingContext alloc] init];
if (self.forcedCaption != nil)
[editingContext setForcedCaption:self.forcedCaption entities:self.forcedEntities];
_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;
}
if (editableItem == nil)
editableItem = _items.lastObject;
@ -1989,6 +2170,7 @@ static CGPoint TGCameraControllerClampPointToScreenSize(__unused id self, __unus
self.view.userInteractionEnabled = false;
_focusControl.active = false;
_rectangleView.hidden = true;
[UIView animateWithDuration:0.3f animations:^
{
@ -2256,21 +2438,27 @@ static CGPoint TGCameraControllerClampPointToScreenSize(__unused id self, __unus
PGCameraMode newMode = PGCameraModeUndefined;
if (gestureRecognizer == _photoSwipeGestureRecognizer)
{
newMode = PGCameraModePhoto;
if (_camera.cameraMode == PGCameraModePhoto && _intent == TGCameraControllerGenericIntent)
newMode = PGCameraModePhotoScan;
else if (_camera.cameraMode != PGCameraModePhotoScan)
newMode = PGCameraModePhoto;
}
else if (gestureRecognizer == _videoSwipeGestureRecognizer)
{
if (_intent == TGCameraControllerAvatarIntent) {
newMode = PGCameraModeSquareVideo;
if (_camera.cameraMode == PGCameraModePhotoScan) {
if (_items.count == 0)
newMode = PGCameraModePhoto;
} else {
newMode = PGCameraModeVideo;
if (_intent == TGCameraControllerAvatarIntent) {
newMode = PGCameraModeSquareVideo;
} else {
newMode = PGCameraModeVideo;
}
}
}
if (newMode != PGCameraModeUndefined && _camera.cameraMode != newMode)
{
[_camera setCameraMode:newMode];
[_interfaceView setCameraMode:newMode];
if (newMode != PGCameraModeUndefined && _camera.cameraMode != newMode) {
[self _updateCameraMode:newMode updateInterface:true];
}
}
@ -2403,6 +2591,8 @@ static CGPoint TGCameraControllerClampPointToScreenSize(__unused id self, __unus
{
if (widescreenWidth == 896.0f)
return CGRectMake(0, 121, screenSize.width, screenSize.height - 121 - 223);
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)
@ -2459,11 +2649,22 @@ static CGPoint TGCameraControllerClampPointToScreenSize(__unused id self, __unus
if (selectedItems.count == 0 && currentItem != nil)
[selectedItems addObject:currentItem];
if (storeAssets)
{
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 *
@ -2583,9 +2784,15 @@ static CGPoint TGCameraControllerClampPointToScreenSize(__unused id self, __unus
dict[@"timer"] = timer;
else if (groupedId != nil && !hasAnyTimers)
dict[@"groupedId"] = groupedId;
id generatedItem = descriptionGenerator(dict, caption, entities, nil);
return generatedItem;
if (isScan) {
if (caption != nil)
dict[@"caption"] = caption;
return dict;
} else {
id generatedItem = descriptionGenerator(dict, caption, entities, nil);
return generatedItem;
}
}];
SSignal *assetSignal = inlineSignal;
@ -2611,12 +2818,13 @@ static CGPoint TGCameraControllerClampPointToScreenSize(__unused id self, __unus
return [SSignal complete];
}] onCompletion:^
{
__strong TGMediaEditingContext *strongEditingContext = editingContext;
[strongEditingContext description];
}];
} else {
NSLog(@"Editing context is nil");
}
[signals addObject:[[imageSignal map:^NSDictionary *(UIImage *image)
[signals addObject:[[[imageSignal map:^NSDictionary *(UIImage *image)
{
NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
dict[@"type"] = @"editedPhoto";
@ -2663,11 +2871,19 @@ static CGPoint TGCameraControllerClampPointToScreenSize(__unused id self, __unus
else if (groupedId != nil && !hasAnyTimers)
dict[@"groupedId"] = groupedId;
id generatedItem = descriptionGenerator(dict, caption, entities, nil);
return generatedItem;
if (isScan) {
if (caption != nil)
dict[@"caption"] = caption;
return dict;
} else {
id generatedItem = descriptionGenerator(dict, caption, entities, nil);
return generatedItem;
}
}] catch:^SSignal *(__unused id error)
{
return inlineSignal;
}] onCompletion:^{
[editingContext description];
}]];
i++;
@ -2750,6 +2966,54 @@ static CGPoint TGCameraControllerClampPointToScreenSize(__unused id self, __unus
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, nil);
return generatedItem;
}];
signals = [NSMutableArray arrayWithObject:scanSignal];
}
return signals;
}

View File

@ -29,79 +29,16 @@ const CGFloat TGCameraFlashControlHeight = 44.0f;
{
self.hitTestEdgeInsets = UIEdgeInsetsMake(-10, -10, -10, -10);
_flashIconView = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, 34, 44)];
_flashIconView = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, 44, 44)];
_flashIconView.adjustsImageWhenHighlighted = false;
_flashIconView.contentMode = UIViewContentModeCenter;
_flashIconView.exclusiveTouch = true;
_flashIconView.hitTestEdgeInsets = UIEdgeInsetsMake(0, -10, 0, -10);
_flashIconView.tag = -1;
[_flashIconView setImage:TGComponentsImageNamed(@"CameraFlashButton") forState:UIControlStateNormal];
[_flashIconView setImage:[UIImage imageNamed:@"Camera/FlashOff"] forState:UIControlStateNormal];
[_flashIconView addTarget:self action:@selector(buttonPressed:) forControlEvents:UIControlEventTouchUpInside];
[self addSubview:_flashIconView];
static UIImage *highlightedIconImage = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^
{
UIImage *image = TGComponentsImageNamed(@"CameraFlashButton");
UIGraphicsBeginImageContextWithOptions(image.size, false, 0.0f);
CGContextRef context = UIGraphicsGetCurrentContext();
[image drawInRect:CGRectMake(0, 0, image.size.width, image.size.height)];
CGContextSetBlendMode (context, kCGBlendModeSourceAtop);
CGContextSetFillColorWithColor(context, [TGCameraInterfaceAssets accentColor].CGColor);
CGContextFillRect(context, CGRectMake(0, 0, image.size.width, image.size.height));
highlightedIconImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
});
[_flashIconView setImage:highlightedIconImage forState:UIControlStateSelected];
[_flashIconView setImage:highlightedIconImage forState:UIControlStateHighlighted | UIControlStateSelected];
_autoButton = [[UIButton alloc] init];
_autoButton.backgroundColor = [UIColor clearColor];
_autoButton.contentHorizontalAlignment = UIControlContentHorizontalAlignmentLeft;
_autoButton.exclusiveTouch = true;
_autoButton.hitTestEdgeInsets = UIEdgeInsetsMake(-10, -15, -10, -15);
_autoButton.tag = PGCameraFlashModeAuto;
_autoButton.titleLabel.font = [TGCameraInterfaceAssets normalFontOfSize:13];
[_autoButton setAttributedTitle:[[NSAttributedString alloc] initWithString:TGLocalized(@"Camera.FlashAuto") attributes:@{ NSForegroundColorAttributeName: [TGCameraInterfaceAssets normalColor], NSKernAttributeName: @2 }] forState:UIControlStateNormal];
[_autoButton setAttributedTitle:[[NSAttributedString alloc] initWithString:TGLocalized(@"Camera.FlashAuto") attributes:@{ NSForegroundColorAttributeName: [TGCameraInterfaceAssets accentColor], NSKernAttributeName: @2 }] forState:UIControlStateSelected];
[_autoButton setAttributedTitle:[_autoButton attributedTitleForState:UIControlStateSelected] forState:UIControlStateHighlighted | UIControlStateSelected];
[_autoButton addTarget:self action:@selector(buttonPressed:) forControlEvents:UIControlEventTouchUpInside];
[_autoButton sizeToFit];
_autoButton.frame = (CGRect){ CGPointZero, [TGCameraFlashControl _sizeForModeButtonWithTitle:[_autoButton attributedTitleForState:UIControlStateNormal]] };
[self addSubview:_autoButton];
_onButton = [[UIButton alloc] init];
_onButton.backgroundColor = [UIColor clearColor];
_onButton.contentHorizontalAlignment = UIControlContentHorizontalAlignmentLeft;
_onButton.exclusiveTouch = true;
_onButton.hitTestEdgeInsets = UIEdgeInsetsMake(-10, -15, -10, -15);
_onButton.tag = PGCameraFlashModeOn;
_onButton.titleLabel.font = [TGCameraInterfaceAssets normalFontOfSize:13];
[_onButton setAttributedTitle:[[NSAttributedString alloc] initWithString:TGLocalized(@"Camera.FlashOn") attributes:@{ NSForegroundColorAttributeName: [TGCameraInterfaceAssets normalColor], NSKernAttributeName: @2 }] forState:UIControlStateNormal];
[_onButton setAttributedTitle:[[NSAttributedString alloc] initWithString:TGLocalized(@"Camera.FlashOn") attributes:@{ NSForegroundColorAttributeName: [TGCameraInterfaceAssets accentColor], NSKernAttributeName: @2 }] forState:UIControlStateSelected];
[_onButton setAttributedTitle:[_onButton attributedTitleForState:UIControlStateSelected] forState:UIControlStateHighlighted | UIControlStateSelected];
[_onButton addTarget:self action:@selector(buttonPressed:) forControlEvents:UIControlEventTouchUpInside];
[_onButton sizeToFit];
_onButton.frame = (CGRect){ CGPointZero, [TGCameraFlashControl _sizeForModeButtonWithTitle:[_onButton attributedTitleForState:UIControlStateNormal]] };
[self addSubview:_onButton];
_offButton = [[UIButton alloc] init];
_offButton.backgroundColor = [UIColor clearColor];
_offButton.contentHorizontalAlignment = UIControlContentHorizontalAlignmentLeft;
_offButton.exclusiveTouch = true;
_offButton.hitTestEdgeInsets = UIEdgeInsetsMake(-10, -15, -10, -15);
_offButton.tag = PGCameraFlashModeOff;
_offButton.titleLabel.font = [TGCameraInterfaceAssets normalFontOfSize:13];
[_offButton setAttributedTitle:[[NSAttributedString alloc] initWithString:TGLocalized(@"Camera.FlashOff") attributes:@{ NSForegroundColorAttributeName: [TGCameraInterfaceAssets normalColor], NSKernAttributeName: @2 }] forState:UIControlStateNormal];
[_offButton setAttributedTitle:[[NSAttributedString alloc] initWithString:TGLocalized(@"Camera.FlashOff") attributes:@{ NSForegroundColorAttributeName: [TGCameraInterfaceAssets accentColor], NSKernAttributeName: @2 }] forState:UIControlStateSelected];
[_offButton setAttributedTitle:[_offButton attributedTitleForState:UIControlStateSelected] forState:UIControlStateHighlighted | UIControlStateSelected];
[_offButton addTarget:self action:@selector(buttonPressed:) forControlEvents:UIControlEventTouchUpInside];
[_offButton sizeToFit];
_offButton.frame = (CGRect){ CGPointZero, [TGCameraFlashControl _sizeForModeButtonWithTitle:[_offButton attributedTitleForState:UIControlStateNormal]] };
[self addSubview:_offButton];
[UIView performWithoutAnimation:^
{
self.mode = PGCameraFlashModeOff;
@ -123,305 +60,27 @@ const CGFloat TGCameraFlashControlHeight = 44.0f;
- (void)buttonPressed:(UIButton *)sender
{
if (!_active)
{
[self setActive:true animated:true];
}
else
{
if (sender != _flashIconView)
self.mode = (int)sender.tag;
else
self.mode = _mode;
if (self.modeChanged != nil)
self.modeChanged(self.mode);
if (_mode == PGCameraFlashModeOff) {
self.mode = PGCameraFlashModeOn;
[_flashIconView setImage:[UIImage imageNamed:@"Camera/FlashOn"] forState:UIControlStateNormal];
} else {
self.mode = PGCameraFlashModeOff;
[_flashIconView setImage:[UIImage imageNamed:@"Camera/FlashOff"] forState:UIControlStateNormal];
}
if (self.modeChanged != nil)
self.modeChanged(self.mode);
}
- (void)setFlashUnavailable:(bool)unavailable
{
self.userInteractionEnabled = !unavailable;
[self setActive:false animated:false];
}
- (void)setActive:(bool)active animated:(bool)animated
{
_active = active;
if (animated)
{
self.userInteractionEnabled = false;
if (active)
{
UIView *animatedView = nil;
UIView *snapshotView = nil;
CGRect targetFrame = CGRectZero;
if (self.mode != PGCameraFlashModeAuto)
{
_autoButton.frame = [self _autoButtonFrameForInterfaceOrientation:_interfaceOrientation];
_autoButton.alpha = 0.0f;
_autoButton.hidden = false;
}
else
{
animatedView = _autoButton;
targetFrame = [self _autoButtonFrameForInterfaceOrientation:_interfaceOrientation];
snapshotView = [animatedView snapshotViewAfterScreenUpdates:false];
}
_autoButton.selected = (self.mode == PGCameraFlashModeAuto);
if (self.mode != PGCameraFlashModeOn)
{
_onButton.frame = [self _onButtonFrameForInterfaceOrientation:_interfaceOrientation];
_onButton.alpha = 0.0f;
_onButton.hidden = false;
}
else
{
animatedView = _onButton;
targetFrame = [self _onButtonFrameForInterfaceOrientation:_interfaceOrientation];
}
_onButton.selected = (self.mode == PGCameraFlashModeOn);
if (self.mode != PGCameraFlashModeOff)
{
_offButton.frame = [self _offButtonFrameForInterfaceOrientation:_interfaceOrientation];
_offButton.alpha = 0.0f;
_offButton.hidden = false;
}
else
{
animatedView = _offButton;
targetFrame = [self _offButtonFrameForInterfaceOrientation:_interfaceOrientation];
snapshotView = [animatedView snapshotViewAfterScreenUpdates:false];
}
_offButton.selected = (self.mode == PGCameraFlashModeOff);
if (snapshotView != nil)
{
snapshotView.frame = animatedView.frame;
[animatedView.superview insertSubview:snapshotView belowSubview:animatedView];
animatedView.alpha = 0.0f;
}
UIView *iconSnapshotView = nil;
if (_flashIconView.selected)
{
iconSnapshotView = [_flashIconView snapshotViewAfterScreenUpdates:false];
iconSnapshotView.frame = _flashIconView.frame;
[_flashIconView.superview insertSubview:iconSnapshotView belowSubview:_flashIconView];
_flashIconView.selected = false;
_flashIconView.alpha = 0.0f;
}
[UIView animateWithDuration:0.25f delay:0.0f options:UIViewAnimationOptionCurveEaseInOut animations:^
{
_flashIconView.alpha = 1.0f;
_flashIconView.frame = [self _flashIconFrameForActive:active interfaceOrientation:_interfaceOrientation];
iconSnapshotView.frame = _flashIconView.frame;
_autoButton.alpha = 1.0f;
_onButton.alpha = 1.0f;
_offButton.alpha = 1.0f;
animatedView.alpha = 1.0f;
animatedView.frame = targetFrame;
snapshotView.frame = targetFrame;
} completion:^(BOOL finished)
{
[snapshotView removeFromSuperview];
[iconSnapshotView removeFromSuperview];
if (finished)
self.userInteractionEnabled = true;
}];
}
else
{
UIView *animatedView = nil;
UIView *snapshotView = nil;
UIView *iconSnapshotView = nil;
switch (self.mode)
{
case PGCameraFlashModeAuto:
{
animatedView = _autoButton;
snapshotView = [animatedView snapshotViewAfterScreenUpdates:false];
_autoButton.selected = false;
}
break;
case PGCameraFlashModeOn:
{
animatedView = _onButton;
if (!_onButton.selected)
{
snapshotView = [animatedView snapshotViewAfterScreenUpdates:false];
_onButton.selected = true;
}
iconSnapshotView = [_flashIconView snapshotViewAfterScreenUpdates:false];
iconSnapshotView.frame = _flashIconView.frame;
[_flashIconView.superview insertSubview:iconSnapshotView belowSubview:_flashIconView];
_flashIconView.selected = true;
_flashIconView.alpha = 0.0f;
}
break;
case PGCameraFlashModeOff:
{
animatedView = _offButton;
snapshotView = [animatedView snapshotViewAfterScreenUpdates:false];
_offButton.selected = false;
}
break;
default:
break;
}
if (snapshotView != nil)
{
snapshotView.frame = animatedView.frame;
[animatedView.superview insertSubview:snapshotView belowSubview:animatedView];
animatedView.alpha = 0.0f;
}
[UIView animateWithDuration:0.25f delay:0.0f options:UIViewAnimationOptionCurveEaseInOut animations:^
{
_flashIconView.alpha = 1.0f;
_flashIconView.frame = [self _flashIconFrameForActive:active interfaceOrientation:_interfaceOrientation];
iconSnapshotView.frame = _flashIconView.frame;
if (self.mode != PGCameraFlashModeAuto)
_autoButton.alpha = 0.0f;
if (self.mode != PGCameraFlashModeOn)
_onButton.alpha = 0.0f;
if (self.mode != PGCameraFlashModeOff)
_offButton.alpha = 0.0f;
animatedView.alpha = 1.0f;
animatedView.frame = [self _selectedButtonFrameForSize:animatedView.frame.size interfaceOrientation:_interfaceOrientation];
snapshotView.frame = animatedView.frame;
} completion:^(BOOL finished)
{
[snapshotView removeFromSuperview];
[iconSnapshotView removeFromSuperview];
if (finished)
{
self.userInteractionEnabled = true;
if (self.mode != PGCameraFlashModeAuto)
_autoButton.hidden = true;
if (self.mode != PGCameraFlashModeOn)
_onButton.hidden = true;
if (self.mode != PGCameraFlashModeOff)
_offButton.hidden = true;
}
}];
}
}
else
{
_flashIconView.frame = [self _flashIconFrameForActive:active interfaceOrientation:_interfaceOrientation];
if (active)
{
_flashIconView.selected = false;
_autoButton.frame = [self _autoButtonFrameForInterfaceOrientation:_interfaceOrientation];
_autoButton.alpha = 1.0f;
_autoButton.hidden = false;
_autoButton.selected = (self.mode == PGCameraFlashModeAuto);
_onButton.frame = [self _onButtonFrameForInterfaceOrientation:_interfaceOrientation];
_onButton.alpha = 1.0f;
_onButton.hidden = false;
_onButton.selected = (self.mode == PGCameraFlashModeOn);
_offButton.frame = [self _offButtonFrameForInterfaceOrientation:_interfaceOrientation];
_offButton.alpha = 1.0f;
_offButton.hidden = false;
_offButton.selected = (self.mode == PGCameraFlashModeOff);
}
else
{
switch (self.mode)
{
case PGCameraFlashModeOff:
{
_flashIconView.selected = false;
_autoButton.alpha = 0.0f;
_autoButton.hidden = true;
_autoButton.selected = false;
_onButton.alpha = 0.0f;
_onButton.hidden = true;
_onButton.selected = false;
_offButton.frame = [self _selectedButtonFrameForSize:_offButton.frame.size interfaceOrientation:_interfaceOrientation];
_offButton.alpha = 1.0f;
_offButton.hidden = false;
_offButton.selected = false;
}
break;
case PGCameraFlashModeOn:
{
_flashIconView.selected = true;
_autoButton.alpha = 0.0f;
_autoButton.hidden = true;
_autoButton.selected = false;
_onButton.frame = [self _selectedButtonFrameForSize:_onButton.frame.size interfaceOrientation:_interfaceOrientation];
_onButton.alpha = 1.0f;
_onButton.hidden = false;
_onButton.selected = true;
_offButton.alpha = 0.0f;
_offButton.hidden = true;
_offButton.selected = false;
}
break;
case PGCameraFlashModeAuto:
{
_flashIconView.selected = false;
_autoButton.frame = [self _selectedButtonFrameForSize:_autoButton.frame.size interfaceOrientation:_interfaceOrientation];
_autoButton.alpha = 1.0f;
_autoButton.hidden = false;
_autoButton.selected = false;
_onButton.alpha = 0.0f;
_onButton.hidden = true;
_onButton.selected = false;
_offButton.alpha = 0.0f;
_offButton.hidden = true;
_offButton.selected = false;
}
break;
default:
break;
}
}
}
if (active && self.becameActive != nil)
self.becameActive();
return;
}
- (void)setMode:(PGCameraFlashMode)mode
@ -484,130 +143,4 @@ const CGFloat TGCameraFlashControlHeight = 44.0f;
[self setActive:false animated:false];
}
- (CGRect)_flashIconFrameForActive:(bool)active interfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
CGPoint origin = CGPointZero;
CGSize size = self.frame.size;
if (UIInterfaceOrientationIsLandscape(interfaceOrientation))
size = CGSizeMake(size.height, size.width);
switch (interfaceOrientation)
{
case UIInterfaceOrientationLandscapeLeft:
{
if (active)
origin = CGPointMake(size.width - _flashIconView.frame.size.width - 5, (size.height - _flashIconView.frame.size.height) / 2);
else
origin = CGPointMake(size.width - _flashIconView.frame.size.width - 5, (size.height - _flashIconView.frame.size.height) / 2 - 9);
}
break;
case UIInterfaceOrientationLandscapeRight:
{
if (active)
origin = CGPointMake(5, (size.height - _flashIconView.frame.size.height) / 2);
else
origin = CGPointMake(5, (size.height - _flashIconView.frame.size.height) / 2 - 9);
}
break;
case UIInterfaceOrientationPortraitUpsideDown:
{
if (active)
{
origin = CGPointMake(0, 0);
}
else
{
CGFloat maxWidth = MAX(MAX(_offButton.frame.size.width, _onButton.frame.size.width), _autoButton.frame.size.width);
origin = CGPointMake(size.width - _flashIconView.frame.size.width - maxWidth, 0);
}
}
break;
default:
{
origin = CGPointZero;
}
break;
}
return CGRectMake(origin.x, origin.y, _flashIconView.frame.size.width, _flashIconView.frame.size.height);
}
- (CGRect)_selectedButtonFrameForSize:(CGSize)buttonSize interfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
CGPoint origin = CGPointZero;
CGSize size = self.frame.size;
if (UIInterfaceOrientationIsLandscape(interfaceOrientation))
size = CGSizeMake(size.height, size.width);
switch (interfaceOrientation)
{
case UIInterfaceOrientationLandscapeLeft:
case UIInterfaceOrientationLandscapeRight:
{
origin = CGPointMake(CGRectGetMidX(_flashIconView.frame) - buttonSize.width / 2, 21);
}
break;
case UIInterfaceOrientationPortraitUpsideDown:
{
CGRect iconFrame = [self _flashIconFrameForActive:false interfaceOrientation:interfaceOrientation];
origin = CGPointMake(iconFrame.origin.x + iconFrame.size.width - 3, (size.height - buttonSize.height) / 2);
}
break;
default:
{
origin = CGPointMake(_flashIconView.frame.size.width - 5,
(size.height - buttonSize.height) / 2);
}
break;
}
return CGRectMake(origin.x, origin.y, buttonSize.width, buttonSize.height);
}
- (CGRect)_autoButtonFrameForInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
CGSize size = self.frame.size;
if (UIInterfaceOrientationIsLandscape(interfaceOrientation))
size = CGSizeMake(size.height, size.width);
return CGRectMake(size.width / 4 - _autoButton.frame.size.width / 2,
(size.height - _autoButton.frame.size.height) / 2,
_autoButton.frame.size.width, _autoButton.frame.size.height);
}
- (CGRect)_onButtonFrameForInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
CGSize size = self.frame.size;
if (UIInterfaceOrientationIsLandscape(interfaceOrientation))
size = CGSizeMake(size.height, size.width);
return CGRectMake((size.width - _onButton.frame.size.width) / 2,
(size.height - _onButton.frame.size.height) / 2,
_onButton.frame.size.width, _onButton.frame.size.height);
}
- (CGRect)_offButtonFrameForInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
CGSize size = self.frame.size;
if (UIInterfaceOrientationIsLandscape(interfaceOrientation))
size = CGSizeMake(size.height, size.width);
return CGRectMake(size.width / 4 * 3 - _offButton.frame.size.width / 2,
(size.height - _offButton.frame.size.height) / 2,
_offButton.frame.size.width, _offButton.frame.size.height);
}
+ (CGSize)_sizeForModeButtonWithTitle:(NSAttributedString *)title
{
CGSize size = title.size;
CGFloat width = CGCeil(size.width);
if (iosMajorVersion() < 7)
width += 2;
return CGSizeMake(width, 20);
}
@end

View File

@ -1,18 +1,20 @@
#import "TGCameraFlipButton.h"
#import "TGCameraInterfaceAssets.h"
#import "LegacyComponentsInternal.h"
#import "TGImageUtils.h"
@implementation TGCameraFlipButton
- (instancetype)initWithFrame:(CGRect)frame large:(bool)large
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self != nil)
{
self.exclusiveTouch = true;
UIImage *image = large ? TGComponentsImageNamed(@"CameraLargeFlipButton") : TGTintedImage(TGComponentsImageNamed(@"CameraFlipButton"), [UIColor whiteColor]);
[self setImage:image forState:UIControlStateNormal];
self.backgroundColor = [TGCameraInterfaceAssets buttonColor];
self.layer.cornerRadius = 24.0;
[self setImage:[UIImage imageNamed:@"Camera/Flip"] forState:UIControlStateNormal];
}
return self;
}
@ -30,17 +32,65 @@
super.hidden = false;
self.userInteractionEnabled = false;
[UIView animateWithDuration:0.25f
animations:^
{
self.alpha = hidden ? 0.0f : 1.0f;
} completion:^(BOOL finished)
{
self.userInteractionEnabled = true;
[UIView animateWithDuration:0.25f animations:^
{
self.alpha = hidden ? 0.0f : 1.0f;
} completion:^(BOOL finished)
{
self.userInteractionEnabled = true;
if (finished)
self.hidden = hidden;
}];
if (finished)
self.hidden = hidden;
}];
}
else
{
self.alpha = hidden ? 0.0f : 1.0f;
super.hidden = hidden;
}
}
@end
@implementation TGCameraCancelButton
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self != nil)
{
self.exclusiveTouch = true;
self.backgroundColor = [TGCameraInterfaceAssets buttonColor];
self.layer.cornerRadius = 24.0;
[self setImage:[UIImage imageNamed:@"Camera/Cancel"] forState:UIControlStateNormal];
}
return self;
}
- (void)setHidden:(BOOL)hidden
{
self.alpha = hidden ? 0.0f : 1.0f;
super.hidden = hidden;
}
- (void)setHidden:(bool)hidden animated:(bool)animated
{
if (animated)
{
super.hidden = false;
self.userInteractionEnabled = false;
[UIView animateWithDuration:0.25f animations:^
{
self.alpha = hidden ? 0.0f : 1.0f;
} completion:^(BOOL finished)
{
self.userInteractionEnabled = true;
if (finished)
self.hidden = hidden;
}];
}
else
{

View File

@ -412,8 +412,8 @@
- (void)updateExposureIndicatorPositionForOrientation:(UIInterfaceOrientation)orientation
{
CGRect defaultPositionFrame = _exposureClipView.frame = CGRectMake(45 + _focusIndicatorImageView.frame.size.width + 5, 45 + (_focusIndicatorImageView.frame.size.height - 144) / 2, 25, 144);;
CGRect mirroredPositionFrame = _exposureClipView.frame = CGRectMake(15, 45 + (_focusIndicatorImageView.frame.size.height - 144) / 2, 25, 144);;
CGRect defaultPositionFrame = CGRectMake(45 + _focusIndicatorImageView.frame.size.width + 5, 45 + (_focusIndicatorImageView.frame.size.height - 144) / 2, 25, 144);
CGRect mirroredPositionFrame = CGRectMake(15, 45 + (_focusIndicatorImageView.frame.size.height - 144) / 2, 25, 144);
switch (orientation)
{
case UIInterfaceOrientationPortraitUpsideDown:

View File

@ -1,7 +1,22 @@
#import "TGCameraInterfaceAssets.h"
#import <CoreText/CoreText.h>
#import "LegacyComponentsInternal.h"
static NSString *TGCameraEncodeText(NSString *string, int key)
{
NSMutableString *result = [[NSMutableString alloc] init];
for (int i = 0; i < (int)[string length]; i++)
{
unichar c = [string characterAtIndex:i];
c += key;
[result appendString:[NSString stringWithCharacters:&c length:1]];
}
return result;
}
@implementation TGCameraInterfaceAssets
+ (UIColor *)normalColor
@ -11,12 +26,12 @@
+ (UIColor *)accentColor
{
return UIColorRGB(0xffcc00);
return UIColorRGB(0xffd60a);
}
+ (UIColor *)redColor
{
return UIColorRGB(0xf53333);
return UIColorRGB(0xfe3b30);
}
+ (UIColor *)panelBackgroundColor
@ -24,6 +39,11 @@
return [UIColor blackColor];
}
+ (UIColor *)buttonColor
{
return UIColorRGBA(0x393737, 0.6);
}
+ (UIColor *)transparentPanelBackgroundColor
{
return [UIColor colorWithWhite:0.0f alpha:0.5];
@ -34,9 +54,14 @@
return [UIColor colorWithWhite:0.0f alpha:0.7];
}
+ (UIFont *)normalFontOfSize:(CGFloat)size
+ (UIFont *)regularFontOfSize:(CGFloat)size
{
return [UIFont fontWithName:@"DINAlternate-Bold" size:size];
return [UIFont fontWithName:TGCameraEncodeText(@"TGDbnfsb.Sfhvmbs", -1) size:size];
}
+ (UIFont *)boldFontOfSize:(CGFloat)size
{
return [UIFont fontWithName:TGCameraEncodeText(@"TGDbnfsb.Tfnjcpme", -1) size:size];
}
@end

View File

@ -19,7 +19,7 @@
#import "TGCameraFlipButton.h"
#import "TGCameraTimeCodeView.h"
#import "TGCameraZoomView.h"
#import "TGCameraSegmentsView.h"
#import "TGCameraToastView.h"
#import "TGMenuView.h"
@ -127,8 +127,8 @@
_topPanelHeight = 44.0f;
_bottomPanelOffset = 94.0f;
_bottomPanelHeight = 123.0f;
_modeControlOffset = 0.0f;
_modeControlHeight = 52.0f;
_modeControlOffset = -5.0f;
_modeControlHeight = 56.0f;
_counterOffset = 7.0f;
shutterButtonWidth = 72.0f;
}
@ -206,16 +206,7 @@
_bottomPanelBackgroundView.backgroundColor = [TGCameraInterfaceAssets transparentPanelBackgroundColor];
[_bottomPanelView addSubview:_bottomPanelBackgroundView];
_cancelButton = [[TGModernButton alloc] initWithFrame:CGRectMake(0, 0, 60, 44)];
_cancelButton.backgroundColor = [UIColor clearColor];
_cancelButton.contentHorizontalAlignment = UIControlContentHorizontalAlignmentLeft;
_cancelButton.exclusiveTouch = true;
_cancelButton.titleLabel.font = TGSystemFontOfSize(18);
_cancelButton.contentEdgeInsets = UIEdgeInsetsMake(0, 20, 0, 0);
[_cancelButton setTitle:TGLocalized(@"Common.Cancel") forState:UIControlStateNormal];
[_cancelButton setTintColor:[TGCameraInterfaceAssets normalColor]];
[_cancelButton sizeToFit];
_cancelButton.frame = CGRectMake(0, 0, MAX(60.0f, _cancelButton.frame.size.width), 44);
_cancelButton = [[TGCameraCancelButton alloc] initWithFrame:CGRectMake(0, 0, 48, 48)];
[_cancelButton addTarget:self action:@selector(cancelButtonPressed) forControlEvents:UIControlEventTouchUpInside];
[_bottomPanelView addSubview:_cancelButton];
@ -241,19 +232,19 @@
_modeControl = [[TGCameraModeControl alloc] initWithFrame:CGRectMake(0, 0, frame.size.width, _modeControlHeight) avatar:avatar];
[_bottomPanelView addSubview:_modeControl];
_flipButton = [[TGCameraFlipButton alloc] initWithFrame:CGRectMake(0, 0, 56, 56) large:true];
_flipButton = [[TGCameraFlipButton alloc] initWithFrame:CGRectMake(0, 0, 48, 48)];
[_flipButton addTarget:self action:@selector(flipButtonPressed) forControlEvents:UIControlEventTouchUpInside];
[_bottomPanelView addSubview:_flipButton];
_flashControl = [[TGCameraFlashControl alloc] initWithFrame:CGRectMake(0, 0, frame.size.width, TGCameraFlashControlHeight)];
_flashControl = [[TGCameraFlashControl alloc] initWithFrame:CGRectMake(3.0, 0, TGCameraFlashControlHeight, TGCameraFlashControlHeight)];
[_topPanelView addSubview:_flashControl];
_topFlipButton = [[TGCameraFlipButton alloc] initWithFrame:CGRectMake(0, 0, 44, 44) large:false];
_topFlipButton = [[TGCameraFlipButton alloc] initWithFrame:CGRectMake(0, 0, 44, 44)];
_topFlipButton.hidden = true;
[_topFlipButton addTarget:self action:@selector(flipButtonPressed) forControlEvents:UIControlEventTouchUpInside];
[_topPanelView addSubview:_topFlipButton];
// [_topPanelView addSubview:_topFlipButton];
_timecodeView = [[TGCameraTimeCodeView alloc] initWithFrame:CGRectMake((frame.size.width - 120) / 2, 12, 120, 20)];
_timecodeView = [[TGCameraTimeCodeView alloc] initWithFrame:CGRectMake((frame.size.width - 120) / 2, 12, 120, 28)];
_timecodeView.hidden = true;
_timecodeView.requestedRecordingDuration = ^NSTimeInterval
{
@ -273,7 +264,10 @@
[self addSubview:_videoLandscapePanelView];
_flashActiveView = [[TGCameraFlashActiveView alloc] initWithFrame:CGRectMake((frame.size.width - 40) / 2, frame.size.height - _bottomPanelHeight - 37, 40, 21)];
[self addSubview:_flashActiveView];
// [self addSubview:_flashActiveView];
_toastView = [[TGCameraToastView alloc] initWithFrame:CGRectMake(0, frame.size.height - _bottomPanelHeight - 42, frame.size.width, 32)];
[self addSubview:_toastView];
_zoomView = [[TGCameraZoomView alloc] initWithFrame:CGRectMake(10, frame.size.height - _bottomPanelHeight - _bottomPanelOffset - 18, frame.size.width - 20, 1.5f)];
_zoomView.activityChanged = ^(bool active)
@ -386,9 +380,12 @@
else
{
_hasResults = true;
_topFlipButton.hidden = false;
_topFlipButton.hidden = _modeControl.cameraMode == PGCameraModePhotoScan;
_flipButton.hidden = true;
_doneButton.hidden = false;
if (_modeControl.cameraMode == PGCameraModePhotoScan) {
_modeControl.hidden = true;
}
}
}
@ -830,18 +827,18 @@
if (superview == _videoLandscapePanelView && superviewSize.width < superviewSize.height)
superviewSize = CGSizeMake(superviewSize.height, superviewSize.width);
if (UIInterfaceOrientationIsLandscape(orientation) && _flashControl.interfaceOrientation == orientation && _flashControl.superview == _topPanelView)
{
if (orientation == UIInterfaceOrientationLandscapeLeft)
_flashControl.frame = CGRectMake(7, 0, TGCameraFlashControlHeight, 370);
else if (orientation == UIInterfaceOrientationLandscapeRight)
_flashControl.frame = CGRectMake(7, 0, TGCameraFlashControlHeight, 370);
}
else
{
_flashControl.frame = CGRectMake(0, (superviewSize.height - TGCameraFlashControlHeight) / 2, superviewSize.width, TGCameraFlashControlHeight);
}
_timecodeView.frame = CGRectMake((superviewSize.width - 120) / 2, (superviewSize.height - 20) / 2, 120, 20);
// if (UIInterfaceOrientationIsLandscape(orientation) && _flashControl.interfaceOrientation == orientation && _flashControl.superview == _topPanelView)
// {
// if (orientation == UIInterfaceOrientationLandscapeLeft)
// _flashControl.frame = CGRectMake(7, 0, TGCameraFlashControlHeight, 370);
// else if (orientation == UIInterfaceOrientationLandscapeRight)
// _flashControl.frame = CGRectMake(7, 0, TGCameraFlashControlHeight, 370);
// }
// else
// {
// _flashControl.frame = CGRectMake(0, (superviewSize.height - TGCameraFlashControlHeight) / 2, superviewSize.width, TGCameraFlashControlHeight);
// }
_timecodeView.frame = CGRectMake((superviewSize.width - 120) / 2, (superviewSize.height - 28) / 2, 120, 28);
}
- (void)layoutPreviewRelativeViews
@ -865,13 +862,17 @@
_modeControl.frame = CGRectMake(0, _modeControlOffset, self.frame.size.width, _modeControlHeight);
_shutterButton.frame = CGRectMake(round((self.frame.size.width - _shutterButton.frame.size.width) / 2), _modeControlHeight + _modeControlOffset, _shutterButton.frame.size.width, _shutterButton.frame.size.height);
_cancelButton.frame = CGRectMake(0, round(_shutterButton.center.y - _cancelButton.frame.size.height / 2.0f), _cancelButton.frame.size.width, _cancelButton.frame.size.height);
_cancelButton.frame = CGRectMake(20.0, round(_shutterButton.center.y - _cancelButton.frame.size.height / 2.0f), _cancelButton.frame.size.width, _cancelButton.frame.size.height);
_doneButton.frame = CGRectMake(_bottomPanelView.frame.size.width - _doneButton.frame.size.width, round(_shutterButton.center.y - _doneButton.frame.size.height / 2.0f), _doneButton.frame.size.width, _doneButton.frame.size.height);
_flipButton.frame = CGRectMake(self.frame.size.width - _flipButton.frame.size.width - 4.0f - 7.0f, round(_shutterButton.center.y - _flipButton.frame.size.height / 2.0f), _flipButton.frame.size.width, _flipButton.frame.size.height);
_flipButton.frame = CGRectMake(self.frame.size.width - _flipButton.frame.size.width - 20.0f, round(_shutterButton.center.y - _flipButton.frame.size.height / 2.0f), _flipButton.frame.size.width, _flipButton.frame.size.height);
_topFlipButton.frame = CGRectMake(self.frame.size.width - _topFlipButton.frame.size.width - 4.0f, 0.0f, _topFlipButton.frame.size.width, _topFlipButton.frame.size.height);
_toastView.frame = CGRectMake(0, self.frame.size.height - _bottomPanelHeight - _bottomPanelOffset - 32 - 16, self.frame.size.width, 32);
CGFloat photosViewSize = TGPhotoThumbnailSizeForCurrentScreen().height + 4 * 2;
_photoCounterButton.frame = CGRectMake(self.frame.size.width - 56.0f - 10.0f, _counterOffset, 64, 38);
_selectedPhotosView.frame = CGRectMake(4.0f, [_photoCounterButton convertRect:_photoCounterButton.bounds toView:self].origin.y - photosViewSize - 20.0f, self.frame.size.width - 4.0f * 2.0f, photosViewSize);

View File

@ -100,7 +100,7 @@ const CGFloat TGCameraTabletPanelViewWidth = 102.0f;
};
[_panelView addSubview:_timecodeView];
_flipButton = [[TGCameraFlipButton alloc] initWithFrame:CGRectMake(0, 0, 44, 44) large:true];
_flipButton = [[TGCameraFlipButton alloc] initWithFrame:CGRectMake(0, 0, 44, 44)];
[_flipButton addTarget:self action:@selector(flipButtonPressed) forControlEvents:UIControlEventTouchUpInside];
[_panelView addSubview:_flipButton];

View File

@ -5,10 +5,11 @@
#import <LegacyComponents/TGModernButton.h>
#import "TGCameraShutterButton.h"
#import "TGCameraFlipButton.h"
#import "TGCameraModeControl.h"
#import "TGCameraTimeCodeView.h"
#import "TGCameraZoomView.h"
#import "TGCameraSegmentsView.h"
#import "TGCameraToastView.h"
#import "TGMediaPickerPhotoCounterButton.h"
#import "TGMediaPickerPhotoStripView.h"
@ -37,15 +38,22 @@
[self updateForCameraModeChangeWithPreviousMode:previousMode];
}
- (void)setToastMessage:(NSString *)message animated:(bool)animated
{
[_toastView setText:message animated:animated];
}
- (void)updateForCameraModeChangeWithPreviousMode:(PGCameraMode)__unused previousMode
{
switch (_modeControl.cameraMode)
{
case PGCameraModePhoto:
case PGCameraModeSquarePhoto:
case PGCameraModePhotoScan:
{
[_shutterButton setButtonMode:TGCameraShutterButtonNormalMode animated:true];
[_timecodeView setHidden:true animated:true];
[_flipButton setHidden:_modeControl.cameraMode == PGCameraModePhotoScan animated:true];
}
break;

View File

@ -26,10 +26,7 @@ const CGFloat TGCameraModeControlVerticalInteritemSpace = 29.0f;
self = [super initWithFrame:frame];
if (self != nil)
{
if (frame.size.width > frame.size.height)
_kerning = 3.5f;
else
_kerning = 2.0f;
_kerning = 0.75f;
_maskView = [[UIView alloc] initWithFrame:self.bounds];
_maskView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
@ -53,6 +50,7 @@ const CGFloat TGCameraModeControlVerticalInteritemSpace = 29.0f;
[
[self _createButtonForMode:PGCameraModeVideo title:TGLocalized(@"Camera.VideoMode")],
[self _createButtonForMode:PGCameraModePhoto title:TGLocalized(@"Camera.PhotoMode")]
// [self _createButtonForMode:PGCameraModePhotoScan title:TGLocalized(@"Camera.ScanMode")]
];
}
@ -72,7 +70,7 @@ const CGFloat TGCameraModeControlVerticalInteritemSpace = 29.0f;
_maskLayer = [CAGradientLayer layer];
_maskLayer.colors = @[ (id)[UIColor clearColor].CGColor, (id)[UIColor whiteColor].CGColor, (id)[UIColor whiteColor].CGColor, (id)[UIColor clearColor].CGColor ];
_maskLayer.locations = @[ @0.0f, @0.33f, @0.67f, @1.0f ];
_maskLayer.locations = @[ @0.0f, @0.4f, @0.6f, @1.0f ];
_maskLayer.startPoint = CGPointMake(0.0f, 0.5f);
_maskLayer.endPoint = CGPointMake(1.0f, 0.5f);
_maskView.layer.mask = _maskLayer;
@ -94,19 +92,14 @@ const CGFloat TGCameraModeControlVerticalInteritemSpace = 29.0f;
return self;
}
+ (UIFont *)_buttonFont
{
return [UIFont fontWithName:@"SFCompactText-Regular" size:14];
}
+ (CGFloat)_buttonHorizontalSpacing
{
return 19;
return 25;
}
+ (CGFloat)_buttonVerticalSpacing
{
return 19;
return 25;
}
- (UIButton *)_createButtonForMode:(PGCameraMode)mode title:(NSString *)title
@ -116,11 +109,17 @@ const CGFloat TGCameraModeControlVerticalInteritemSpace = 29.0f;
button.exclusiveTouch = true;
button.hitTestEdgeInsets = UIEdgeInsetsMake(-10, -10, -10, -10);
button.tag = mode;
button.titleLabel.font = [TGCameraInterfaceAssets normalFontOfSize:13];
[button setAttributedTitle:[[NSAttributedString alloc] initWithString:title attributes:@{ NSForegroundColorAttributeName: [TGCameraInterfaceAssets normalColor], NSKernAttributeName: @(_kerning) }] forState:UIControlStateNormal];
[button setAttributedTitle:[[NSAttributedString alloc] initWithString:title attributes:@{ NSForegroundColorAttributeName: [TGCameraInterfaceAssets accentColor], NSKernAttributeName: @(_kerning) }] forState:UIControlStateSelected];
[button setAttributedTitle:[[NSAttributedString alloc] initWithString:title attributes:@{ NSForegroundColorAttributeName: [TGCameraInterfaceAssets normalColor], NSKernAttributeName: @(_kerning), NSFontAttributeName: [TGCameraInterfaceAssets regularFontOfSize:14] }] forState:UIControlStateNormal];
[button setAttributedTitle:[[NSAttributedString alloc] initWithString:title attributes:@{ NSForegroundColorAttributeName: [TGCameraInterfaceAssets accentColor], NSKernAttributeName: @(_kerning), NSFontAttributeName: [TGCameraInterfaceAssets boldFontOfSize:14] }] forState:UIControlStateSelected];
[button setAttributedTitle:[button attributedTitleForState:UIControlStateSelected] forState:UIControlStateHighlighted | UIControlStateSelected];
[button sizeToFit];
button.titleLabel.shadowColor = [UIColor blackColor];
button.titleLabel.shadowOffset = CGSizeMake(0.0, 0.0);
button.titleLabel.layer.shadowRadius = 2.0;
button.titleLabel.layer.shadowOpacity = 0.3;
button.titleLabel.layer.masksToBounds = false;
button.titleLabel.layer.shouldRasterize = true;
button.frame = CGRectMake(0.0, 0.0, button.frame.size.width + 2.0, button.frame.size.height);
[button addTarget:self action:@selector(buttonPressed:) forControlEvents:UIControlEventTouchUpInside];
return button;
@ -191,7 +190,7 @@ const CGFloat TGCameraModeControlVerticalInteritemSpace = 29.0f;
CGFloat angle = ABS(offset / _wrapperView.frame.size.width * 0.99f);
CGFloat sign = offset > 0 ? 1.0f : -1.0f;
CATransform3D transform = CATransform3DTranslate(CATransform3DIdentity, -28 * angle * angle * sign, 0.0f, 0.0f);
CATransform3D transform = CATransform3DTranslate(CATransform3DIdentity, -2 * angle * angle * sign, 0.0f, 0.0f);
transform = CATransform3DRotate(transform, angle, 0.0f, sign, 0.0f);
return transform;
}

View File

@ -0,0 +1,14 @@
#import <UIKit/UIKit.h>
@class TGCameraPreviewView;
@class PGRectangle;
@interface TGCameraRectangleView : UIView
@property (nonatomic, weak) TGCameraPreviewView *previewView;
@property (nonatomic, assign) bool enabled;
- (void)drawRectangle:(PGRectangle *)rectangle;
@end

View File

@ -0,0 +1,102 @@
#import "TGCameraRectangleView.h"
#import "TGCameraInterfaceAssets.h"
#import "LegacyComponentsInternal.h"
#import "TGImageUtils.h"
#import "TGCameraPreviewView.h"
#import "PGRectangleDetector.h"
@interface TGCameraRectangleView ()
{
CAShapeLayer *_quadLayer;
bool _clearing;
}
@end
@implementation TGCameraRectangleView
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self != nil) {
_enabled = true;
self.backgroundColor = [UIColor clearColor];
self.alpha = 0.0f;
_quadLayer = [[CAShapeLayer alloc] init];
_quadLayer.strokeColor = [[TGCameraInterfaceAssets accentColor] colorWithAlphaComponent:0.7].CGColor;
_quadLayer.fillColor = [[TGCameraInterfaceAssets accentColor] colorWithAlphaComponent:0.45].CGColor;
_quadLayer.lineWidth = 2.0;
[self.layer addSublayer:_quadLayer];
}
return self;
}
- (CGPathRef)pathForRectangle:(PGRectangle *)rectangle
{
CGAffineTransform transform = CGAffineTransformMakeScale(self.previewView.frame.size.width, self.previewView.frame.size.height);
PGRectangle *displayRectangle = [[rectangle rotate90] transform:transform];
UIBezierPath *path = [[UIBezierPath alloc] init];
[path moveToPoint:displayRectangle.topLeft];
[path addLineToPoint:displayRectangle.topRight];
[path addLineToPoint:displayRectangle.bottomRight];
[path addLineToPoint:displayRectangle.bottomLeft];
[path closePath];
return path.CGPath;
}
- (void)drawRectangle:(PGRectangle *)rectangle
{
if (!_enabled) {
return;
}
if (rectangle == nil) {
[self clear];
return;
}
_clearing = false;
[self.layer removeAllAnimations];
bool animated = _quadLayer.path != nil;
if (animated) {
CAAnimation *animation = [CABasicAnimation animationWithKeyPath:@"path"];
animation.duration = 0.2;
[_quadLayer addAnimation:animation forKey:@"path"];
} else {
self.transform = CGAffineTransformMakeScale(1.1, 1.1);
[UIView animateWithDuration:0.2 delay:0.0 options:UIViewAnimationOptionAllowAnimatedContent animations:^{
self.transform = CGAffineTransformIdentity;
self.alpha = 1.0f;
} completion:nil];
}
_quadLayer.path = [self pathForRectangle:rectangle];
}
- (void)clear
{
if (_quadLayer.path == nil || _clearing)
return;
_clearing = true;
[UIView animateWithDuration:0.2 delay:0.0 options:UIViewAnimationOptionAllowAnimatedContent animations:^{
self.alpha = 0.0f;
} completion:^(BOOL finished) {
if (_clearing) {
_quadLayer.path = nil;
_clearing = false;
}
}];
}
- (void)layoutSubviews
{
_quadLayer.frame = self.bounds;
}
@end

View File

@ -1,260 +0,0 @@
#import "TGCameraSegmentsView.h"
#import "LegacyComponentsInternal.h"
#import "TGImageUtils.h"
#import "TGCameraInterfaceAssets.h"
#import <LegacyComponents/TGModernButton.h>
const CGFloat TGCameraSegmentsBackgroundInset = 21.0f;
const CGFloat TGCameraSegmentsBackgroundHeight = 10.0f;
const CGFloat TGCameraSegmentsSpacing = 1.5f;
const CGFloat TGCameraSegmentsMinimumWidth = 4.0f;
@interface TGCameraSegmentView : UIImageView
- (void)setBlinking;
- (void)setRecording;
- (void)setCommittingWithCompletion:(void (^)(void))completion;
@end
@interface TGCameraSegmentsView ()
{
UIImageView *_backgroundView;
UIView *_segmentWrapper;
NSArray *_segmentViews;
TGCameraSegmentView *_currentSegmentView;
CGFloat _currentSegment;
TGModernButton *_deleteButton;
}
@end
@implementation TGCameraSegmentsView
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self != nil)
{
static dispatch_once_t onceToken;
static UIImage *segmentImage = nil;
dispatch_once(&onceToken, ^
{
UIGraphicsBeginImageContextWithOptions(CGSizeMake(4, 4), false, 0.0f);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(context, [UIColor whiteColor].CGColor);
[[UIBezierPath bezierPathWithRoundedRect:CGRectMake(0, 0, 4, 4) cornerRadius:0.5f] fill];
segmentImage = [UIGraphicsGetImageFromCurrentImageContext() resizableImageWithCapInsets:UIEdgeInsetsMake(4, 4, 4, 4)];
UIGraphicsEndImageContext();
});
_backgroundView = [[UIImageView alloc] initWithImage:[TGComponentsImageNamed(@"CameraSegmentsBack") resizableImageWithCapInsets:UIEdgeInsetsMake(4, 4, 4, 4)]];
[self addSubview:_backgroundView];
_segmentWrapper = [[UIView alloc] init];
[_backgroundView addSubview:_segmentWrapper];
_currentSegmentView = [[TGCameraSegmentView alloc] initWithImage:[TGTintedImage(segmentImage, [TGCameraInterfaceAssets accentColor]) resizableImageWithCapInsets:UIEdgeInsetsMake(4, 4, 4, 4)]];
[_segmentWrapper addSubview:_currentSegmentView];
_deleteButton = [[TGModernButton alloc] initWithFrame:CGRectMake(0, 0, 32, 32)];
_deleteButton.exclusiveTouch = true;
[_deleteButton setImage:TGComponentsImageNamed(@"CameraDeleteIcon") forState:UIControlStateNormal];
[_deleteButton addTarget:self action:@selector(deleteButtonPressed) forControlEvents:UIControlEventTouchUpInside];
[self addSubview:_deleteButton];
[self setDeleteButtonHidden:true animated:false];
}
return self;
}
- (void)deleteButtonPressed
{
if (self.deletePressed != nil)
self.deletePressed();
}
- (void)setSegments:(NSArray *)__unused segments
{
}
- (void)startCurrentSegment
{
[_currentSegmentView setRecording];
}
- (void)setCurrentSegment:(CGFloat)length
{
_currentSegment = length;
[self _layoutSegmentViews];
}
- (void)commitCurrentSegmentWithCompletion:(void (^)(void))completion
{
__weak TGCameraSegmentView *weakSegmentView = _currentSegmentView;
[_currentSegmentView setCommittingWithCompletion:^
{
__strong TGCameraSegmentView *strongSegmentView = weakSegmentView;
if (strongSegmentView == nil)
return;
_currentSegment = 0;
if (completion != nil)
completion();
[strongSegmentView setBlinking];
}];
}
- (void)highlightLastSegment
{
}
- (void)removeLastSegment
{
}
- (void)setHidden:(BOOL)hidden
{
self.alpha = hidden ? 0.0f : 1.0f;
super.hidden = hidden;
if (!hidden)
[_currentSegmentView setBlinking];
}
- (void)setHidden:(bool)hidden animated:(bool)animated delay:(NSTimeInterval)delay
{
if (animated)
{
super.hidden = false;
[UIView animateWithDuration:0.25f delay:delay options:UIViewAnimationOptionCurveLinear animations:^
{
self.alpha = hidden ? 0.0f : 1.0f;
} completion:^(BOOL finished)
{
if (finished)
self.hidden = hidden;
if (!hidden)
[_currentSegmentView setBlinking];
}];
}
else
{
[self setHidden:hidden];
}
}
- (void)setDeleteButtonHidden:(bool)hidden animated:(bool)animated
{
if (animated)
{
_deleteButton.hidden = false;
[UIView animateWithDuration:0.25f animations:^
{
_deleteButton.alpha = hidden ? 0.0f : 1.0f;
} completion:^(BOOL finished)
{
if (finished)
_deleteButton.hidden = hidden;
}];
}
else
{
_deleteButton.hidden = hidden;
_deleteButton.alpha = hidden ? 0.0f : 1.0f;
}
}
- (void)_layoutBackgroundView
{
CGFloat backgroundRightPadding = 0.0f;
CGFloat deleteButtonMargin = _deleteButton.frame.size.width + 9.0f;
if (!_deleteButton.hidden)
backgroundRightPadding = deleteButtonMargin;
_backgroundView.frame = CGRectMake(TGCameraSegmentsBackgroundInset, (self.frame.size.height - TGCameraSegmentsBackgroundHeight) / 2, self.frame.size.width - TGCameraSegmentsBackgroundInset * 2 - backgroundRightPadding, TGCameraSegmentsBackgroundHeight);
_segmentWrapper.frame = CGRectMake(3, 3, self.frame.size.width - TGCameraSegmentsBackgroundInset * 2 - deleteButtonMargin, TGCameraSegmentsBackgroundHeight - 3 * 2);
}
- (void)_layoutDeleteButton
{
_deleteButton.frame = CGRectMake(CGRectGetMaxX(_backgroundView.frame) + 14, (self.frame.size.height - _deleteButton.frame.size.height) / 2, _deleteButton.frame.size.width, _deleteButton.frame.size.height);
}
- (void)_layoutSegmentViews
{
}
- (void)layoutSubviews
{
[self _layoutBackgroundView];
[self _layoutDeleteButton];
}
@end
@interface TGCameraSegmentView ()
{
}
@end
@implementation TGCameraSegmentView
- (instancetype)initWithImage:(UIImage *)image
{
self = [super initWithImage:image];
if (self != nil)
{
}
return self;
}
- (void)setBlinking
{
[self _playBlinkAnimation];
}
- (void)setRecording
{
[self _stopBlinkAnimation];
}
- (void)setCommittingWithCompletion:(void (^)(void))__unused completion
{
}
- (void)_playBlinkAnimation
{
CAKeyframeAnimation *blinkAnim = [CAKeyframeAnimation animationWithKeyPath:@"opacity"];
blinkAnim.duration = 1.2f;
blinkAnim.autoreverses = false;
blinkAnim.fillMode = kCAFillModeForwards;
blinkAnim.repeatCount = HUGE_VALF;
blinkAnim.keyTimes = @[ @0.0f, @0.4f, @0.5f, @0.9f, @1.0f ];
blinkAnim.values = @[ @1.0f, @1.0f, @0.0f, @0.0f, @1.0f ];
[self.layer addAnimation:blinkAnim forKey:@"opacity"];
}
- (void)_stopBlinkAnimation
{
[self.layer removeAllAnimations];
}
@end

View File

@ -1,4 +1,5 @@
#import "TGCameraShutterButton.h"
#import "TGImageUtils.h"
#import <LegacyComponents/JNWSpringAnimation.h>
@ -36,7 +37,7 @@
UIGraphicsBeginImageContextWithOptions(CGSizeMake(frame.size.width, frame.size.height), false, 0.0f);
CGContextRef context = UIGraphicsGetCurrentContext();
CGFloat thickness = (padding < 8.0f) ? 5.0f : 6.0f;
CGFloat thickness = 4.0 - TGScreenPixel;
CGContextSetStrokeColorWithColor(context, [TGCameraInterfaceAssets normalColor].CGColor);
CGContextSetLineWidth(context, thickness);
@ -70,17 +71,17 @@
- (CGFloat)innerPadding
{
if (self.frame.size.width == 50.0f)
return 7.0f;
return 6.0f;
return 8.0f;
return 6.0f;
}
- (CGFloat)squarePadding
{
if (self.frame.size.width == 50.0f)
return 15.0f;
return 19.0f;
return 19.0f;
return 23.0f;
}
- (void)setButtonMode:(TGCameraShutterButtonMode)mode animated:(bool)animated

View File

@ -7,7 +7,7 @@
@interface TGCameraTimeCodeView ()
{
UIImageView *_dotView;
UIView *_backgroundView;
UILabel *_timeLabel;
NSUInteger _recordingDurationSeconds;
@ -22,29 +22,17 @@
self = [super initWithFrame:frame];
if (self != nil)
{
static UIImage *dotImage = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^
{
UIGraphicsBeginImageContextWithOptions(CGSizeMake(6, 6), false, 0.0f);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(context, [TGCameraInterfaceAssets redColor].CGColor);
CGContextFillEllipseInRect(context, CGRectMake(0, 0, 6, 6));
dotImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
});
_dotView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 7, 6, 6)];
_dotView.layer.opacity = 0.0f;
_dotView.image = dotImage;
[self addSubview:_dotView];
_backgroundView = [[UIView alloc] init];
_backgroundView.clipsToBounds = true;
_backgroundView.layer.cornerRadius = 4.0;
_backgroundView.backgroundColor = [TGCameraInterfaceAssets redColor];
_backgroundView.alpha = 0.0;
[self addSubview:_backgroundView];
_timeLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, self.frame.size.width, self.frame.size.height)];
_timeLabel.backgroundColor = [UIColor clearColor];
_timeLabel.font = [TGCameraInterfaceAssets normalFontOfSize:21];
_timeLabel.text = @"00:00:00";
_timeLabel.font = [TGCameraInterfaceAssets regularFontOfSize:21];
_timeLabel.text = @"00:00";
_timeLabel.textAlignment = NSTextAlignmentCenter;
_timeLabel.textColor = [TGCameraInterfaceAssets normalColor];
[self addSubview:_timeLabel];
@ -59,7 +47,18 @@
- (void)_updateRecordingTime
{
_timeLabel.text = [NSString stringWithFormat:@"%02d:%02d:%02d", (int)(_recordingDurationSeconds / 3600), (int)(_recordingDurationSeconds / 60) % 60, (int)(_recordingDurationSeconds % 60)];
if (_recordingDurationSeconds > 60 * 60) {
_timeLabel.text = [NSString stringWithFormat:@"%02d:%02d:%02d", (int)(_recordingDurationSeconds / 3600), (int)(_recordingDurationSeconds / 60) % 60, (int)(_recordingDurationSeconds % 60)];
} else {
_timeLabel.text = [NSString stringWithFormat:@"%02d:%02d", (int)(_recordingDurationSeconds / 60) % 60, (int)(_recordingDurationSeconds % 60)];
}
[_timeLabel sizeToFit];
CGFloat inset = 8.0f;
CGFloat backgroundWidth = _timeLabel.frame.size.width + inset * 2.0;
_backgroundView.frame = CGRectMake(floor((self.frame.size.width - backgroundWidth) / 2.0), 0.0, backgroundWidth, 28.0);
_timeLabel.frame = CGRectMake(floor((self.frame.size.width - _timeLabel.frame.size.width) / 2.0), floor((28 - _timeLabel.frame.size.height) / 2.0), _timeLabel.frame.size.width, _timeLabel.frame.size.height);
}
- (void)startRecording
@ -67,22 +66,18 @@
[self reset];
_recordingTimer = [TGTimerTarget scheduledMainThreadTimerWithTarget:self action:@selector(recordingTimerEvent) interval:1.0 repeat:false];
[self playBlinkAnimation];
}
- (void)stopRecording
{
[_recordingTimer invalidate];
_recordingTimer = nil;
[self stopBlinkAnimation];
}
- (void)reset
{
_timeLabel.text = @"00:00:00";
_recordingDurationSeconds = 0;
[self _updateRecordingTime];
}
- (void)recordingTimerEvent
@ -108,24 +103,6 @@
}
}
- (void)playBlinkAnimation
{
CAKeyframeAnimation *blinkAnim = [CAKeyframeAnimation animationWithKeyPath:@"opacity"];
blinkAnim.duration = 0.75f;
blinkAnim.autoreverses = false;
blinkAnim.fillMode = kCAFillModeForwards;
blinkAnim.repeatCount = HUGE_VALF;
blinkAnim.keyTimes = @[ @0.0f, @0.4f, @0.5f, @0.9f, @1.0f ];
blinkAnim.values = @[ @1.0f, @1.0f, @0.0f, @0.0f, @1.0f ];
[_dotView.layer addAnimation:blinkAnim forKey:@"opacity"];
}
- (void)stopBlinkAnimation
{
[_dotView.layer removeAllAnimations];
}
- (void)setHidden:(BOOL)hidden
{
self.alpha = hidden ? 0.0f : 1.0f;
@ -156,7 +133,7 @@
- (void)layoutSubviews
{
_dotView.frame = CGRectMake(CGFloor(self.frame.size.width / 2 - 48), 7, 6, 6);
[self _updateRecordingTime];
}
@end

View File

@ -0,0 +1,7 @@
#import <UIKit/UIKit.h>
@interface TGCameraToastView : UIView
- (void)setText:(NSString *)text animated:(bool)animated;
@end

View File

@ -0,0 +1,70 @@
#import "TGCameraToastView.h"
#import "TGCameraInterfaceAssets.h"
#import "TGFont.h"
@implementation TGCameraToastView
{
UIView *_backgroundView;
UILabel *_label;
}
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self != nil)
{
_backgroundView = [[UIView alloc] init];
_backgroundView.alpha = 0.0f;
_backgroundView.clipsToBounds = true;
_backgroundView.layer.cornerRadius = 5.0f;
_backgroundView.backgroundColor = [TGCameraInterfaceAssets transparentPanelBackgroundColor];
[self addSubview:_backgroundView];
_label = [[UILabel alloc] init];
_label.alpha = 0.0f;
_label.textColor = [UIColor whiteColor];
_label.font = [TGFont systemFontOfSize:17.0f];
[self addSubview:_label];
}
return self;
}
- (void)setText:(NSString *)text animated:(bool)animated
{
if (text.length == 0)
{
if (animated) {
[UIView animateWithDuration:0.2 animations:^{
_backgroundView.alpha = 0.0f;
_label.alpha = 0.0f;
}];
} else {
_backgroundView.alpha = 0.0f;
_label.alpha = 0.0f;
}
return;
}
if (animated) {
[UIView animateWithDuration:0.2 animations:^{
_backgroundView.alpha = 1.0f;
_label.alpha = 1.0f;
}];
} else {
_backgroundView.alpha = 1.0f;
_label.alpha = 1.0f;
}
_label.text = text;
[_label sizeToFit];
CGFloat inset = 8.0f;
CGFloat backgroundWidth = _label.frame.size.width + inset * 2.0;
_backgroundView.frame = CGRectMake(floor((self.frame.size.width - backgroundWidth) / 2.0), 0.0, backgroundWidth, 32.0);
_label.frame = CGRectMake(floor((self.frame.size.width - _label.frame.size.width) / 2.0), floor((32 - _label.frame.size.height) / 2.0), _label.frame.size.width, _label.frame.size.height);
[self setNeedsLayout];
}
@end

View File

@ -117,7 +117,7 @@ UIFont *TGFixedSystemFontOfSize(CGFloat size)
+ (UIFont *)roundedFontOfSize:(CGFloat)size
{
if (@available(iOSApplicationExtension 13.0, iOS 13.0, *)) {
UIFontDescriptor *descriptor = [UIFont boldSystemFontOfSize: size].fontDescriptor;
UIFontDescriptor *descriptor = [UIFont boldSystemFontOfSize:size].fontDescriptor;
descriptor = [descriptor fontDescriptorWithDesign:UIFontDescriptorSystemDesignRounded];
return [UIFont fontWithDescriptor:descriptor size:size];
} else {

View File

@ -1266,7 +1266,7 @@ static CGFloat progressOfSampleBufferInTimeRange(CMSampleBufferRef sampleBuffer,
return (CGSize){ 1920.0f, 1920.0f };
case TGMediaVideoConversionPresetVideoMessage:
return (CGSize){ 240.0f, 240.0f };
return (CGSize){ 384.0f, 384.0f };
case TGMediaVideoConversionPresetProfileLow:
return (CGSize){ 720.0f, 720.0f };
@ -1375,7 +1375,7 @@ static CGFloat progressOfSampleBufferInTimeRange(CMSampleBufferRef sampleBuffer,
return 4000;
case TGMediaVideoConversionPresetVideoMessage:
return 300;
return 1000;
case TGMediaVideoConversionPresetProfile:
return 1500;

View File

@ -1,26 +0,0 @@
#import "TGPhotoEditorTabController.h"
@class PGPhotoEditor;
@class TGPhotoEditorPreviewView;
@class AVPlayer;
@interface TGPhotoAvatarCropController : TGPhotoEditorTabController
@property (nonatomic, readonly) UIView *transitionParentView;
@property (nonatomic, assign) bool switching;
@property (nonatomic, assign) bool skipTransitionIn;
@property (nonatomic, assign) bool fromCamera;
@property (nonatomic, copy) void (^finishedPhotoProcessing)(void);
- (instancetype)initWithContext:(id<LegacyComponentsContext>)context photoEditor:(PGPhotoEditor *)photoEditor previewView:(TGPhotoEditorPreviewView *)previewView;
- (void)setImage:(UIImage *)image;
- (void)setPlayer:(AVPlayer *)player;
- (void)setSnapshotImage:(UIImage *)snapshotImage;
- (void)setSnapshotView:(UIView *)snapshotView;
- (void)_finishedTransitionIn;
@end

View File

@ -1,707 +0,0 @@
#import "TGPhotoAvatarCropController.h"
#import "LegacyComponentsInternal.h"
#import "TGPhotoEditorInterfaceAssets.h"
#import <LegacyComponents/TGPhotoEditorAnimation.h>
#import <LegacyComponents/UIControl+HitTestEdgeInsets.h>
#import <LegacyComponents/TGPhotoEditorUtils.h>
#import "PGPhotoEditor.h"
#import "TGPhotoEditorPreviewView.h"
#import "TGPhotoAvatarCropView.h"
#import <LegacyComponents/TGModernButton.h>
#import "TGPhotoPaintController.h"
const CGFloat TGPhotoAvatarCropButtonsWrapperSize = 61.0f;
@interface TGPhotoAvatarCropController ()
{
UIView *_wrapperView;
UIView *_buttonsWrapperView;
TGModernButton *_rotateButton;
TGModernButton *_mirrorButton;
TGModernButton *_resetButton;
TGPhotoAvatarCropView *_cropView;
UIView *_snapshotView;
UIImage *_snapshotImage;
bool _appeared;
UIImage *_imagePendingLoad;
dispatch_semaphore_t _waitSemaphore;
}
@property (nonatomic, weak) PGPhotoEditor *photoEditor;
@property (nonatomic, weak) TGPhotoEditorPreviewView *previewView;
@end
@implementation TGPhotoAvatarCropController
- (instancetype)initWithContext:(id<LegacyComponentsContext>)context photoEditor:(PGPhotoEditor *)photoEditor previewView:(TGPhotoEditorPreviewView *)previewView
{
self = [super initWithContext:context];
if (self != nil)
{
self.photoEditor = photoEditor;
self.previewView = previewView;
_waitSemaphore = dispatch_semaphore_create(0);
}
return self;
}
- (void)loadView
{
[super loadView];
__weak TGPhotoAvatarCropController *weakSelf = self;
void(^interactionBegan)(void) = ^
{
__strong TGPhotoAvatarCropController *strongSelf = weakSelf;
if (strongSelf == nil)
return;
self.controlVideoPlayback(false);
};
void(^interactionEnded)(void) = ^
{
__strong TGPhotoAvatarCropController *strongSelf = weakSelf;
if (strongSelf == nil)
return;
if ([strongSelf shouldAutorotate])
[TGViewController attemptAutorotation];
self.controlVideoPlayback(true);
};
_wrapperView = [[UIView alloc] initWithFrame:self.view.bounds];
_wrapperView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
[self.view addSubview:_wrapperView];
PGPhotoEditor *photoEditor = self.photoEditor;
_cropView = [[TGPhotoAvatarCropView alloc] initWithOriginalSize:photoEditor.originalSize screenSize:[self referenceViewSize] fullPreviewView:nil fullPaintingView:nil fullEntitiesView:nil];
[_cropView setCropRect:photoEditor.cropRect];
[_cropView setCropOrientation:photoEditor.cropOrientation];
[_cropView setCropMirrored:photoEditor.cropMirrored];
_cropView.croppingChanged = ^
{
__strong TGPhotoAvatarCropController *strongSelf = weakSelf;
if (strongSelf == nil)
return;
photoEditor.cropRect = strongSelf->_cropView.cropRect;
photoEditor.cropOrientation = strongSelf->_cropView.cropOrientation;
photoEditor.cropMirrored = strongSelf->_cropView.cropMirrored;
};
if (_snapshotView != nil)
{
[_cropView setSnapshotView:_snapshotView];
_snapshotView = nil;
}
else if (_snapshotImage != nil)
{
[_cropView setSnapshotImage:_snapshotImage];
_snapshotImage = nil;
}
_cropView.interactionBegan = interactionBegan;
_cropView.interactionEnded = interactionEnded;
[_wrapperView addSubview:_cropView];
_buttonsWrapperView = [[UIView alloc] initWithFrame:CGRectZero];
[_wrapperView addSubview:_buttonsWrapperView];
_rotateButton = [[TGModernButton alloc] initWithFrame:CGRectMake(0, 0, 36, 36)];
_rotateButton.exclusiveTouch = true;
_rotateButton.hitTestEdgeInsets = UIEdgeInsetsMake(-10, -10, -10, -10);
[_rotateButton addTarget:self action:@selector(rotate) forControlEvents:UIControlEventTouchUpInside];
[_rotateButton setImage:TGComponentsImageNamed(@"PhotoEditorRotateIcon") forState:UIControlStateNormal];
// [_buttonsWrapperView addSubview:_rotateButton];
_mirrorButton = [[TGModernButton alloc] initWithFrame:CGRectMake(0, 0, 36, 36)];
_mirrorButton.exclusiveTouch = true;
_mirrorButton.imageEdgeInsets = UIEdgeInsetsMake(4.0f, 0.0f, 0.0f, 0.0f);
_mirrorButton.hitTestEdgeInsets = UIEdgeInsetsMake(-10, -10, -10, -10);
[_mirrorButton addTarget:self action:@selector(mirror) forControlEvents:UIControlEventTouchUpInside];
[_mirrorButton setImage:TGComponentsImageNamed(@"PhotoEditorMirrorIcon") forState:UIControlStateNormal];
// [_buttonsWrapperView addSubview:_mirrorButton];
_resetButton = [[TGModernButton alloc] init];
_resetButton.contentEdgeInsets = UIEdgeInsetsMake(0.0f, 8.0f, 0.0f, 8.0f);
_resetButton.exclusiveTouch = true;
_resetButton.hitTestEdgeInsets = UIEdgeInsetsMake(-10, -10, -10, -10);
_resetButton.titleLabel.font = [TGFont systemFontOfSize:13];
[_resetButton addTarget:self action:@selector(reset) forControlEvents:UIControlEventTouchUpInside];
[_resetButton setTitle:TGLocalized(@"PhotoEditor.CropReset") forState:UIControlStateNormal];
[_resetButton setTitleColor:[UIColor whiteColor]];
[_resetButton sizeToFit];
_resetButton.frame = CGRectMake(0, 0, _resetButton.frame.size.width, 24);
[_buttonsWrapperView addSubview:_resetButton];
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
if (_appeared)
return;
if (self.initialAppearance && self.skipTransitionIn)
{
[self _finishedTransitionInWithView:nil];
if (self.finishedTransitionIn != nil)
{
self.finishedTransitionIn();
self.finishedTransitionIn = nil;
}
}
else
{
[self transitionIn];
}
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
_appeared = true;
if (_imagePendingLoad != nil)
[_cropView setImage:_imagePendingLoad];
}
- (BOOL)shouldAutorotate
{
return (!_cropView.isTracking && [super shouldAutorotate]);
}
- (bool)isDismissAllowed
{
return _appeared && !_cropView.isTracking && !_cropView.isAnimating;
}
#pragma mark -
- (void)setImage:(UIImage *)image
{
if (_dismissing && !_switching)
return;
if (_waitSemaphore != nil)
dispatch_semaphore_signal(_waitSemaphore);
if (!_appeared)
{
_imagePendingLoad = image;
return;
}
[_cropView setImage:image];
}
- (void)setPlayer:(AVPlayer *)player
{
}
- (void)setSnapshotImage:(UIImage *)snapshotImage
{
_snapshotImage = snapshotImage;
[_cropView _replaceSnapshotImage:snapshotImage];
}
- (void)setSnapshotView:(UIView *)snapshotView
{
_snapshotView = snapshotView;
}
#pragma mark - Transition
- (void)prepareTransitionInWithReferenceView:(UIView *)referenceView referenceFrame:(CGRect)referenceFrame parentView:(UIView *)parentView noTransitionView:(bool)noTransitionView
{
[super prepareTransitionInWithReferenceView:referenceView referenceFrame:referenceFrame parentView:parentView noTransitionView:noTransitionView];
[self.view insertSubview:_transitionView belowSubview:_wrapperView];
}
- (void)transitionIn
{
_buttonsWrapperView.alpha = 0.0f;
[UIView animateWithDuration:0.3f animations:^
{
_buttonsWrapperView.alpha = 1.0f;
}];
[_cropView animateTransitionIn];
}
- (void)animateTransitionIn
{
if ([_transitionView isKindOfClass:[TGPhotoEditorPreviewView class]])
[(TGPhotoEditorPreviewView *)_transitionView performTransitionToCropAnimated:true];
[super animateTransitionIn];
}
- (void)_finishedTransitionInWithView:(UIView *)transitionView
{
if ([transitionView isKindOfClass:[TGPhotoEditorPreviewView class]]) {
} else {
[transitionView removeFromSuperview];
}
_buttonsWrapperView.alpha = 1.0f;
[_cropView transitionInFinishedFromCamera:(self.fromCamera && self.initialAppearance)];
}
- (void)_finishedTransitionIn
{
// [_cropView animateTransitionIn];
[_cropView transitionInFinishedFromCamera:true];
self.finishedTransitionIn();
self.finishedTransitionIn = nil;
}
- (void)prepareForCustomTransitionOut
{
[_cropView hideImageForCustomTransition];
[_cropView animateTransitionOutSwitching:false];
[UIView animateWithDuration:0.3f animations:^
{
_buttonsWrapperView.alpha = 0.0f;
} completion:nil];
}
- (void)transitionOutSwitching:(bool)switching completion:(void (^)(void))completion
{
_dismissing = true;
[_cropView animateTransitionOutSwitching:switching];
if (switching)
{
_switching = true;
TGPhotoEditorPreviewView *previewView = self.previewView;
[previewView performTransitionToCropAnimated:false];
[previewView setSnapshotView:[_cropView cropSnapshotView]];
PGPhotoEditor *photoEditor = self.photoEditor;
if (self.item.isVideo) {
if (!previewView.hidden)
[previewView performTransitionInWithCompletion:nil];
else
[previewView setNeedsTransitionIn];
if (self.finishedPhotoProcessing != nil)
self.finishedPhotoProcessing();
} else {
UIImage *image = _cropView.currentImage;
CGRect cropRect = _cropView.cropRect;
UIImageOrientation cropOrientation = _cropView.cropOrientation;
bool cropMirrored = _cropView.cropMirrored;
CGSize originalSize = _cropView.originalSize;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^
{
if (dispatch_semaphore_wait(_waitSemaphore, dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC))))
{
TGLegacyLog(@"Photo crop on switching failed");
return;
}
UIImage *croppedImage = TGPhotoEditorCrop(image, nil, cropOrientation, 0.0f, cropRect, false, TGPhotoEditorScreenImageMaxSize(), originalSize, true);
[photoEditor setImage:croppedImage forCropRect:cropRect cropRotation:0.0f cropOrientation:cropOrientation cropMirrored:cropMirrored fullSize:false];
[photoEditor processAnimated:false completion:^
{
TGDispatchOnMainThread(^
{
[previewView setSnapshotImage:croppedImage];
if (!previewView.hidden)
[previewView performTransitionInWithCompletion:nil];
else
[previewView setNeedsTransitionIn];
});
}];
if (self.finishedPhotoProcessing != nil)
self.finishedPhotoProcessing();
});
}
UIInterfaceOrientation orientation = self.effectiveOrientation;
CGRect cropRectFrame = [_cropView cropRectFrameForView:self.view];
CGSize referenceSize = [self referenceViewSizeForOrientation:orientation];
CGRect referenceBounds = CGRectMake(0, 0, referenceSize.width, referenceSize.height);
CGRect containerFrame = [TGPhotoEditorTabController photoContainerFrameForParentViewFrame:referenceBounds toolbarLandscapeSize:self.toolbarLandscapeSize orientation:orientation panelSize:TGPhotoEditorPanelSize hasOnScreenNavigation:self.hasOnScreenNavigation];
// if (self.switchingToTab == TGPhotoEditorPreviewTab)
// {
// containerFrame = [TGPhotoEditorTabController photoContainerFrameForParentViewFrame:referenceBounds toolbarLandscapeSize:self.toolbarLandscapeSize orientation:orientation panelSize:0 hasOnScreenNavigation:self.hasOnScreenNavigation];
// }
// else if (self.switchingToTab == TGPhotoEditorPaintTab)
// {
// containerFrame = [TGPhotoPaintController photoContainerFrameForParentViewFrame:referenceBounds toolbarLandscapeSize:self.toolbarLandscapeSize orientation:orientation panelSize:TGPhotoPaintTopPanelSize + TGPhotoPaintBottomPanelSize hasOnScreenNavigation:self.hasOnScreenNavigation];
// }
CGSize fittedSize = TGScaleToSize(cropRectFrame.size, containerFrame.size);
CGRect targetFrame = CGRectMake(containerFrame.origin.x + (containerFrame.size.width - fittedSize.width) / 2,
containerFrame.origin.y + (containerFrame.size.height - fittedSize.height) / 2,
fittedSize.width,
fittedSize.height);
UIView *snapshotView = [_cropView cropSnapshotView];
snapshotView.alpha = 0.0f;
snapshotView.frame = cropRectFrame;
[self.view addSubview:snapshotView];
CGRect targetCropViewFrame = [self.view convertRect:targetFrame toView:_wrapperView];
if (!self.item.isVideo) {
_previewView.hidden = true;
}
[UIView animateWithDuration:0.3f delay:0.0f options:UIViewAnimationOptionCurveEaseInOut | UIViewAnimationOptionLayoutSubviews animations:^
{
snapshotView.frame = targetFrame;
if (self.item.isVideo) {
_cropView.alpha = 0.0f;
} else {
snapshotView.alpha = 1.0f;
}
_cropView.frame = targetCropViewFrame;
[_cropView invalidateCropRect];
} completion:^(__unused BOOL finished)
{
_previewView.hidden = false;
if (self.finishedTransitionOut != nil)
self.finishedTransitionOut();
}];
}
[UIView animateWithDuration:0.3f animations:^
{
_buttonsWrapperView.alpha = 0.0f;
} completion:^(__unused BOOL finished)
{
if (completion != nil)
completion();
}];
}
- (void)transitionOutSaving:(bool)__unused saving completion:(void (^)(void))completion
{
CGRect referenceFrame = [_cropView contentFrameForView:self.view];
CGSize referenceSize = [self referenceViewSize];
UIImageView *snapshotView = [[UIImageView alloc] initWithImage:_cropView.image];
snapshotView.frame = [_wrapperView convertRect:referenceFrame fromView:nil];
snapshotView.alpha = 0.0f;
[_wrapperView insertSubview:snapshotView belowSubview:_cropView];
[self transitionOutSwitching:false completion:nil];
if (self.intent & TGPhotoEditorControllerFromCameraIntent && self.intent & (TGPhotoEditorControllerAvatarIntent | TGPhotoEditorControllerSignupAvatarIntent))
{
if (self.interfaceOrientation == UIInterfaceOrientationLandscapeLeft)
{
referenceFrame = CGRectMake(referenceSize.height - referenceFrame.size.height - referenceFrame.origin.y,
referenceSize.width - referenceFrame.size.width - referenceFrame.origin.x,
referenceFrame.size.height, referenceFrame.size.width);
}
else if (self.interfaceOrientation == UIInterfaceOrientationLandscapeRight)
{
referenceFrame = CGRectMake(referenceFrame.origin.y,
referenceFrame.origin.x,
referenceFrame.size.height, referenceFrame.size.width);
}
}
UIView *referenceView = nil;
UIView *parentView = nil;
if (self.beginTransitionOut != nil)
referenceView = self.beginTransitionOut(&referenceFrame, &parentView);
if (self.intent & TGPhotoEditorControllerFromCameraIntent && self.intent & (TGPhotoEditorControllerAvatarIntent | TGPhotoEditorControllerSignupAvatarIntent))
{
if (self.interfaceOrientation == UIInterfaceOrientationLandscapeLeft)
{
referenceFrame = CGRectMake(referenceSize.width - referenceFrame.size.height - referenceFrame.origin.y,
referenceFrame.origin.x,
referenceFrame.size.height, referenceFrame.size.width);
}
else if (self.interfaceOrientation == UIInterfaceOrientationLandscapeRight)
{
referenceFrame = CGRectMake(referenceFrame.origin.y,
referenceSize.height - referenceFrame.size.width - referenceFrame.origin.x,
referenceFrame.size.height, referenceFrame.size.width);
}
}
POPSpringAnimation *animation = [TGPhotoEditorAnimation prepareTransitionAnimationForPropertyNamed:kPOPViewFrame];
animation.fromValue = [NSValue valueWithCGRect:snapshotView.frame];
animation.toValue = [NSValue valueWithCGRect:[_wrapperView convertRect:referenceFrame fromView:nil]];
POPSpringAnimation *alphaAnimation = [TGPhotoEditorAnimation prepareTransitionAnimationForPropertyNamed:kPOPViewAlpha];
alphaAnimation.fromValue = @(snapshotView.alpha);
alphaAnimation.toValue = @(0.0f);
[TGPhotoEditorAnimation performBlock:^(__unused bool allFinished)
{
[snapshotView removeFromSuperview];
if (completion != nil)
completion();
} whenCompletedAllAnimations:@[ animation, alphaAnimation ]];
[snapshotView pop_addAnimation:animation forKey:@"frame"];
[snapshotView pop_addAnimation:alphaAnimation forKey:@"alpha"];
}
- (CGRect)_targetFrameForTransitionInFromFrame:(CGRect)fromFrame
{
CGSize referenceSize = [self referenceViewSize];
UIInterfaceOrientation orientation = self.effectiveOrientation;
CGRect containerFrame = [TGPhotoEditorTabController photoContainerFrameForParentViewFrame:CGRectMake(0, 0, referenceSize.width, referenceSize.height) toolbarLandscapeSize:self.toolbarLandscapeSize orientation:orientation panelSize:0.0f hasOnScreenNavigation:self.hasOnScreenNavigation];
CGRect targetFrame = CGRectZero;
CGFloat shortSide = MIN(referenceSize.width, referenceSize.height);
CGFloat diameter = shortSide - [TGPhotoAvatarCropView areaInsetSize].width * 2;
if (self.initialAppearance && (self.fromCamera || !self.skipTransitionIn))
{
CGSize referenceSize = fromFrame.size;
if ([_transitionView isKindOfClass:[UIImageView class]])
referenceSize = ((UIImageView *)_transitionView).image.size;
CGSize fittedSize = TGScaleToFill(referenceSize, CGSizeMake(diameter, diameter));
targetFrame = CGRectMake(containerFrame.origin.x + (containerFrame.size.width - fittedSize.width) / 2,
containerFrame.origin.y + (containerFrame.size.height - fittedSize.height) / 2,
fittedSize.width, fittedSize.height);
}
else
{
targetFrame = CGRectMake(containerFrame.origin.x + (containerFrame.size.width - diameter) / 2,
containerFrame.origin.y + (containerFrame.size.height - diameter) / 2,
diameter, diameter);
}
return targetFrame;
}
- (CGRect)transitionOutReferenceFrame
{
return [_cropView cropRectFrameForView:self.view];
}
- (UIView *)transitionOutReferenceView
{
return [_cropView cropSnapshotView];
}
- (id)currentResultRepresentation
{
return [_cropView cropSnapshotView];
}
#pragma mark - Actions
- (void)rotate
{
[_cropView rotate90DegreesCCWAnimated:true];
}
- (void)mirror
{
[_cropView mirror];
}
- (void)reset
{
[_cropView resetAnimated:true];
}
#pragma mark - Layout
- (void)viewWillLayoutSubviews
{
[super viewWillLayoutSubviews];
[self updateLayout:[[LegacyComponentsGlobals provider] applicationStatusBarOrientation]];
}
- (void)updateLayout:(UIInterfaceOrientation)orientation
{
if ([self inFormSheet] || [UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad)
{
_resetButton.hidden = true;
}
orientation = [self effectiveOrientation:orientation];
CGSize referenceSize = [self referenceViewSize];
if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad)
[_cropView updateCircleImageWithReferenceSize:referenceSize];
CGFloat screenSide = MAX(referenceSize.width, referenceSize.height) + 2 * TGPhotoEditorPanelSize;
_wrapperView.frame = CGRectMake((referenceSize.width - screenSide) / 2, (referenceSize.height - screenSide) / 2, screenSide, screenSide);
bool hasOnScreenNavigation = false;
if (iosMajorVersion() >= 11)
hasOnScreenNavigation = (self.viewLoaded && self.view.safeAreaInsets.bottom > FLT_EPSILON) || self.context.safeAreaInset.bottom > FLT_EPSILON;
UIEdgeInsets safeAreaInset = [TGViewController safeAreaInsetForOrientation:orientation hasOnScreenNavigation:hasOnScreenNavigation];
UIEdgeInsets screenEdges = UIEdgeInsetsMake((screenSide - self.view.frame.size.height) / 2, (screenSide - self.view.frame.size.width) / 2, (screenSide + self.view.frame.size.height) / 2, (screenSide + self.view.frame.size.width) / 2);
UIEdgeInsets initialScreenEdges = screenEdges;
screenEdges.top += safeAreaInset.top;
screenEdges.left += safeAreaInset.left;
screenEdges.bottom -= safeAreaInset.bottom;
screenEdges.right -= safeAreaInset.right;
switch (orientation)
{
case UIInterfaceOrientationLandscapeLeft:
{
[UIView performWithoutAnimation:^
{
_buttonsWrapperView.frame = CGRectMake(screenEdges.left + self.toolbarLandscapeSize,
screenEdges.top,
TGPhotoAvatarCropButtonsWrapperSize,
referenceSize.height);
_rotateButton.frame = CGRectMake(25 + 2.0f, 10, _rotateButton.frame.size.width, _rotateButton.frame.size.height);
_mirrorButton.frame = CGRectMake(25, 60, _mirrorButton.frame.size.width, _mirrorButton.frame.size.height);
_resetButton.transform = CGAffineTransformIdentity;
[_resetButton sizeToFit];
_resetButton.frame = CGRectMake(0, 0, _resetButton.frame.size.width, 24);
CGFloat xOrigin = 0;
if (_resetButton.frame.size.width > _buttonsWrapperView.frame.size.width)
{
_resetButton.transform = CGAffineTransformMakeRotation((CGFloat)M_PI_2);
xOrigin = 12;
}
_resetButton.frame = CGRectMake(_buttonsWrapperView.frame.size.width - _resetButton.frame.size.width - xOrigin,
(_buttonsWrapperView.frame.size.height - _resetButton.frame.size.height) / 2,
_resetButton.frame.size.width,
_resetButton.frame.size.height);
}];
}
break;
case UIInterfaceOrientationLandscapeRight:
{
[UIView performWithoutAnimation:^
{
_buttonsWrapperView.frame = CGRectMake(screenEdges.right - self.toolbarLandscapeSize - TGPhotoAvatarCropButtonsWrapperSize,
screenEdges.top,
TGPhotoAvatarCropButtonsWrapperSize,
referenceSize.height);
_rotateButton.frame = CGRectMake(_buttonsWrapperView.frame.size.width - _rotateButton.frame.size.width - 25 + 2.0f, 10, _rotateButton.frame.size.width, _rotateButton.frame.size.height);
_mirrorButton.frame = CGRectMake(_buttonsWrapperView.frame.size.width - _mirrorButton.frame.size.width - 25, 60, _mirrorButton.frame.size.width, _mirrorButton.frame.size.height);
_resetButton.transform = CGAffineTransformIdentity;
[_resetButton sizeToFit];
CGSize resetButtonSize = _resetButton.frame.size;
CGFloat xOrigin = 0;
if (resetButtonSize.width > _buttonsWrapperView.frame.size.width)
{
_resetButton.transform = CGAffineTransformMakeRotation((CGFloat)-M_PI_2);
xOrigin = 12;
}
_resetButton.frame = CGRectMake(xOrigin,
(_buttonsWrapperView.frame.size.height - _resetButton.frame.size.height) / 2,
_resetButton.frame.size.width,
_resetButton.frame.size.height);
}];
}
break;
default:
{
[UIView performWithoutAnimation:^
{
_buttonsWrapperView.frame = CGRectMake(screenEdges.left,
screenEdges.bottom - TGPhotoEditorToolbarSize - TGPhotoAvatarCropButtonsWrapperSize,
referenceSize.width,
TGPhotoAvatarCropButtonsWrapperSize);
_rotateButton.frame = CGRectMake(10, _buttonsWrapperView.frame.size.height - _rotateButton.frame.size.height - 25 + 2.0f, _rotateButton.frame.size.width, _rotateButton.frame.size.height);
_mirrorButton.frame = CGRectMake(60, _buttonsWrapperView.frame.size.height - _mirrorButton.frame.size.height - 25, _mirrorButton.frame.size.width, _mirrorButton.frame.size.height);
_resetButton.transform = CGAffineTransformIdentity;
[_resetButton sizeToFit];
_resetButton.frame = CGRectMake((_buttonsWrapperView.frame.size.width - _resetButton.frame.size.width) / 2,
10,
_resetButton.frame.size.width,
24);
}];
}
break;
}
if (_dismissing)
return;
CGRect containerFrame = [TGPhotoEditorTabController photoContainerFrameForParentViewFrame:CGRectMake(0, 0, referenceSize.width, referenceSize.height) toolbarLandscapeSize:self.toolbarLandscapeSize orientation:orientation panelSize:0.0f hasOnScreenNavigation:hasOnScreenNavigation];
containerFrame = CGRectOffset(containerFrame, initialScreenEdges.left, initialScreenEdges.top);
CGFloat shortSide = MIN(referenceSize.width, referenceSize.height);
CGFloat diameter = shortSide - [TGPhotoAvatarCropView areaInsetSize].width * 2;
_cropView.frame = CGRectMake(containerFrame.origin.x + (containerFrame.size.width - diameter) / 2,
containerFrame.origin.y + (containerFrame.size.height - diameter) / 2,
diameter,
diameter);
}
- (TGPhotoEditorTab)availableTabs
{
return TGPhotoEditorRotateTab | TGPhotoEditorMirrorTab;
}
- (void)handleTabAction:(TGPhotoEditorTab)tab
{
switch (tab)
{
case TGPhotoEditorRotateTab:
{
[self rotate];
}
break;
case TGPhotoEditorMirrorTab:
{
[self mirror];
}
break;
default:
break;
}
}
@end

View File

@ -0,0 +1,6 @@
#import "TGPhotoEditorTabController.h"
@interface TGPhotoRectangleCropController : TGPhotoEditorTabController
@end

View File

@ -0,0 +1,5 @@
#import "TGPhotoRectangleCropController.h"
@implementation TGPhotoRectangleCropController
@end

View File

@ -0,0 +1,7 @@
#import <UIKit/UIKit.h>
@interface TGWarpedView : UIImageView
- (void)transformToFitQuadTopLeft:(CGPoint)tl topRight:(CGPoint)tr bottomLeft:(CGPoint)bl bottomRight:(CGPoint)br;
@end

View File

@ -0,0 +1,92 @@
#import "TGWarpedView.h"
@implementation TGWarpedView
- (void)transformToFitQuadTopLeft:(CGPoint)tl topRight:(CGPoint)tr bottomLeft:(CGPoint)bl bottomRight:(CGPoint)br
{
CGRect boundingBox = [[self class] boundingBoxForQuadTR:tr tl:tl bl:bl br:br];
self.frame = boundingBox;
CGPoint frameTopLeft = boundingBox.origin;
CATransform3D transform = [[self class] rectToQuad:self.bounds
quadTL:CGPointMake(tl.x-frameTopLeft.x, tl.y-frameTopLeft.y)
quadTR:CGPointMake(tr.x-frameTopLeft.x, tr.y-frameTopLeft.y)
quadBL:CGPointMake(bl.x-frameTopLeft.x, bl.y-frameTopLeft.y)
quadBR:CGPointMake(br.x-frameTopLeft.x, br.y-frameTopLeft.y)];
self.layer.transform = transform;
}
+ (CGRect)boundingBoxForQuadTR:(CGPoint)tr tl:(CGPoint)tl bl:(CGPoint)bl br:(CGPoint)br
{
CGRect boundingBox = CGRectZero;
CGFloat xmin = MIN(MIN(MIN(tr.x, tl.x), bl.x),br.x);
CGFloat ymin = MIN(MIN(MIN(tr.y, tl.y), bl.y),br.y);
CGFloat xmax = MAX(MAX(MAX(tr.x, tl.x), bl.x),br.x);
CGFloat ymax = MAX(MAX(MAX(tr.y, tl.y), bl.y),br.y);
boundingBox.origin.x = xmin;
boundingBox.origin.y = ymin;
boundingBox.size.width = xmax - xmin;
boundingBox.size.height = ymax - ymin;
return boundingBox;
}
+ (CATransform3D)rectToQuad:(CGRect)rect
quadTL:(CGPoint)topLeft
quadTR:(CGPoint)topRight
quadBL:(CGPoint)bottomLeft
quadBR:(CGPoint)bottomRight
{
return [self rectToQuad:rect quadTLX:topLeft.x quadTLY:topLeft.y quadTRX:topRight.x quadTRY:topRight.y quadBLX:bottomLeft.x quadBLY:bottomLeft.y quadBRX:bottomRight.x quadBRY:bottomRight.y];
}
// http://stackoverflow.com/questions/9470493/transforming-a-rectangle-image-into-a-quadrilateral-using-a-catransform3d
+ (CATransform3D)rectToQuad:(CGRect)rect
quadTLX:(CGFloat)x1a
quadTLY:(CGFloat)y1a
quadTRX:(CGFloat)x2a
quadTRY:(CGFloat)y2a
quadBLX:(CGFloat)x3a
quadBLY:(CGFloat)y3a
quadBRX:(CGFloat)x4a
quadBRY:(CGFloat)y4a
{
CGFloat X = rect.origin.x;
CGFloat Y = rect.origin.y;
CGFloat W = rect.size.width;
CGFloat H = rect.size.height;
CGFloat y21 = y2a - y1a;
CGFloat y32 = y3a - y2a;
CGFloat y43 = y4a - y3a;
CGFloat y14 = y1a - y4a;
CGFloat y31 = y3a - y1a;
CGFloat y42 = y4a - y2a;
CGFloat a = -H*(x2a*x3a*y14 + x2a*x4a*y31 - x1a*x4a*y32 + x1a*x3a*y42);
CGFloat b = W*(x2a*x3a*y14 + x3a*x4a*y21 + x1a*x4a*y32 + x1a*x2a*y43);
CGFloat c = H*X*(x2a*x3a*y14 + x2a*x4a*y31 - x1a*x4a*y32 + x1a*x3a*y42) - H*W*x1a*(x4a*y32 - x3a*y42 + x2a*y43) - W*Y*(x2a*x3a*y14 + x3a*x4a*y21 + x1a*x4a*y32 + x1a*x2a*y43);
CGFloat d = H*(-x4a*y21*y3a + x2a*y1a*y43 - x1a*y2a*y43 - x3a*y1a*y4a + x3a*y2a*y4a);
CGFloat e = W*(x4a*y2a*y31 - x3a*y1a*y42 - x2a*y31*y4a + x1a*y3a*y42);
CGFloat f = -(W*(x4a*(Y*y2a*y31 + H*y1a*y32) - x3a*(H + Y)*y1a*y42 + H*x2a*y1a*y43 + x2a*Y*(y1a - y3a)*y4a + x1a*Y*y3a*(-y2a + y4a)) - H*X*(x4a*y21*y3a - x2a*y1a*y43 + x3a*(y1a - y2a)*y4a + x1a*y2a*(-y3a + y4a)));
CGFloat g = H*(x3a*y21 - x4a*y21 + (-x1a + x2a)*y43);
CGFloat h = W*(-x2a*y31 + x4a*y31 + (x1a - x3a)*y42);
CGFloat i = W*Y*(x2a*y31 - x4a*y31 - x1a*y42 + x3a*y42) + H*(X*(-(x3a*y21) + x4a*y21 + x1a*y43 - x2a*y43) + W*(-(x3a*y2a) + x4a*y2a + x2a*y3a - x4a*y3a - x2a*y4a + x3a*y4a));
const double kEpsilon = 0.0001;
if (fabs(i) < kEpsilon)
i = kEpsilon* (i > 0 ? 1.0 : -1.0);
CATransform3D transform = {a/i, d/i, 0, g/i, b/i, e/i, 0, h/i, 0, 0, 1, 0, c/i, f/i, 0, 1.0};
return transform;
}
@end

View File

@ -419,10 +419,21 @@ public func legacyAssetPickerEnqueueMessages(account: Account, signals: [Any]) -
case let .file(data, thumbnail, mimeType, name, caption):
switch data {
case let .tempFile(path):
var previewRepresentations: [TelegramMediaImageRepresentation] = []
if let thumbnail = thumbnail {
let resource = LocalFileMediaResource(fileId: Int64.random(in: Int64.min ... Int64.max))
let thumbnailSize = thumbnail.size.aspectFitted(CGSize(width: 320.0, height: 320.0))
let thumbnailImage = TGScaleImageToPixelSize(thumbnail, thumbnailSize)!
if let thumbnailData = thumbnailImage.jpegData(compressionQuality: 0.4) {
account.postbox.mediaBox.storeResourceData(resource.id, data: thumbnailData)
previewRepresentations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(thumbnailSize), resource: resource, progressiveSizes: [], immediateThumbnailData: nil))
}
}
var randomId: Int64 = 0
arc4random_buf(&randomId, 8)
let resource = LocalFileReferenceMediaResource(localFilePath: path, randomId: randomId)
let media = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: randomId), partialReference: nil, resource: resource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: mimeType, size: nil, attributes: [.FileName(fileName: name)])
let media = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: randomId), partialReference: nil, resource: resource, previewRepresentations: previewRepresentations, videoThumbnails: [], immediateThumbnailData: nil, mimeType: mimeType, size: nil, attributes: [.FileName(fileName: name)])
messages.append(LegacyAssetPickerEnqueueMessage(message: .message(text: caption ?? "", attributes: [], mediaReference: .standalone(media: media), replyToMessageId: nil, localGroupingKey: item.groupedId, correlationId: nil), uniqueId: item.uniqueId))
case let .asset(asset):
var randomId: Int64 = 0

View File

@ -20,6 +20,13 @@ public final class ManagedAnimationState {
var relativeTime: Double = 0.0
public var frameIndex: Int?
public var position: CGFloat {
if let frameIndex = frameIndex {
return CGFloat(frameIndex) / CGFloat(frameCount)
} else {
return 0.0
}
}
public var executedCallbacks = Set<Int>()

View File

@ -93,11 +93,13 @@ private final class SemanticStatusNodeIconContext: SemanticStatusNodeStateContex
let transitionFraction: CGFloat
let icon: SemanticStatusNodeIcon
let iconImage: UIImage?
init(transitionFraction: CGFloat, icon: SemanticStatusNodeIcon, iconImage: UIImage?) {
let iconOffset: CGFloat
init(transitionFraction: CGFloat, icon: SemanticStatusNodeIcon, iconImage: UIImage?, iconOffset: CGFloat) {
self.transitionFraction = transitionFraction
self.icon = icon
self.iconImage = iconImage
self.iconOffset = iconOffset
super.init()
}
@ -125,11 +127,11 @@ private final class SemanticStatusNodeIconContext: SemanticStatusNodeStateContex
let diameter = size.width
let factor = diameter / 50.0
let size: CGSize
var offset: CGFloat = 0.0
if let iconImage = self.iconImage {
size = iconImage.size
offset = self.iconOffset
} else {
offset = 1.5
size = CGSize(width: 15.0, height: 18.0)
@ -161,12 +163,15 @@ private final class SemanticStatusNodeIconContext: SemanticStatusNodeStateContex
let factor = diameter / 50.0
let size: CGSize
let offset: CGFloat
if let iconImage = self.iconImage {
size = iconImage.size
offset = self.iconOffset
} else {
size = CGSize(width: 15.0, height: 16.0)
offset = 0.0
}
context.translateBy(x: (diameter - size.width) / 2.0, y: (diameter - size.height) / 2.0)
context.translateBy(x: (diameter - size.width) / 2.0 + offset, y: (diameter - size.height) / 2.0)
if (diameter < 40.0) {
context.translateBy(x: size.width / 2.0, y: size.height / 2.0)
context.scaleBy(x: factor, y: factor)
@ -248,6 +253,7 @@ private final class SemanticStatusNodeIconContext: SemanticStatusNodeStateContex
var animationNode: PlayPauseIconNode?
var iconImage: UIImage?
var iconOffset: CGFloat = 0.0
init(icon: SemanticStatusNodeIcon) {
self.icon = icon
@ -255,10 +261,20 @@ private final class SemanticStatusNodeIconContext: SemanticStatusNodeStateContex
if [.play, .pause].contains(icon) {
self.animationNode = PlayPauseIconNode()
self.animationNode?.imageUpdated = { [weak self] image in
self?.iconImage = image
self?.requestUpdate()
if let strongSelf = self {
strongSelf.iconImage = image
if var position = strongSelf.animationNode?.state?.position {
position = position * 2.0
if position > 1.0 {
position = 2.0 - position
}
strongSelf.iconOffset = (1.0 - position) * 1.5
}
strongSelf.requestUpdate()
}
}
self.iconImage = self.animationNode?.image
self.iconOffset = 1.5
}
}
@ -269,7 +285,7 @@ private final class SemanticStatusNodeIconContext: SemanticStatusNodeStateContex
var requestUpdate: () -> Void = {}
func drawingState(transitionFraction: CGFloat) -> SemanticStatusNodeStateDrawingState {
return DrawingState(transitionFraction: transitionFraction, icon: self.icon, iconImage: self.iconImage)
return DrawingState(transitionFraction: transitionFraction, icon: self.icon, iconImage: self.iconImage, iconOffset: self.iconOffset)
}
}

View File

@ -26,7 +26,7 @@ private func interpolate(from: CGFloat, to: CGFloat, value: CGFloat) -> CGFloat
return (1.0 - value) * from + value * to
}
private final class CallVideoNode: ASDisplayNode {
private final class CallVideoNode: ASDisplayNode, PreviewVideoNode {
private let videoTransformContainer: ASDisplayNode
private let videoView: PresentationCallVideoView
@ -40,6 +40,11 @@ private final class CallVideoNode: ASDisplayNode {
private(set) var isReady: Bool = false
private var isReadyTimer: SwiftSignalKit.Timer?
private let readyPromise = ValuePromise(false)
var ready: Signal<Bool, NoError> {
return self.readyPromise.get()
}
private let isFlippedUpdated: (CallVideoNode) -> Void
private(set) var currentOrientation: PresentationCallVideoView.Orientation
@ -87,6 +92,7 @@ private final class CallVideoNode: ASDisplayNode {
}
if !strongSelf.isReady {
strongSelf.isReady = true
strongSelf.readyPromise.set(true)
strongSelf.isReadyTimer?.invalidate()
strongSelf.isReadyUpdated()
}
@ -122,6 +128,7 @@ private final class CallVideoNode: ASDisplayNode {
}
if !strongSelf.isReady {
strongSelf.isReady = true
strongSelf.readyPromise.set(true)
strongSelf.isReadyUpdated()
}
}, queue: .mainQueue())
@ -177,6 +184,10 @@ private final class CallVideoNode: ASDisplayNode {
})
}
func updateLayout(size: CGSize, layoutMode: VideoNodeLayoutMode, transition: ContainedViewLayoutTransition) {
self.updateLayout(size: size, cornerRadius: self.currentCornerRadius, isOutgoing: true, deviceOrientation: .portrait, isCompactLayout: false, transition: transition)
}
func updateLayout(size: CGSize, cornerRadius: CGFloat, isOutgoing: Bool, deviceOrientation: UIDeviceOrientation, isCompactLayout: Bool, transition: ContainedViewLayoutTransition) {
self.currentCornerRadius = cornerRadius
@ -582,13 +593,58 @@ final class CallControllerNode: ViewControllerTracingNode, CallControllerNodePro
strongSelf.call.requestVideo()
}
if strongSelf.displayedCameraConfirmation {
proceed()
} else {
strongSelf.present?(textAlertController(sharedContext: strongSelf.sharedContext, title: nil, text: strongSelf.presentationData.strings.Call_CameraConfirmationText, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Call_CameraConfirmationConfirm, action: {
proceed()
})]))
}
strongSelf.call.makeOutgoingVideoView(completion: { [weak self] outgoingVideoView in
guard let strongSelf = self else {
return
}
if let outgoingVideoView = outgoingVideoView {
outgoingVideoView.view.backgroundColor = .black
outgoingVideoView.view.clipsToBounds = true
var updateLayoutImpl: ((ContainerViewLayout, CGFloat) -> Void)?
let outgoingVideoNode = CallVideoNode(videoView: outgoingVideoView, disabledText: nil, assumeReadyAfterTimeout: true, isReadyUpdated: {
guard let strongSelf = self, let (layout, navigationBarHeight) = strongSelf.validLayout else {
return
}
updateLayoutImpl?(layout, navigationBarHeight)
}, orientationUpdated: {
guard let strongSelf = self, let (layout, navigationBarHeight) = strongSelf.validLayout else {
return
}
updateLayoutImpl?(layout, navigationBarHeight)
}, isFlippedUpdated: { _ in
guard let strongSelf = self, let (layout, navigationBarHeight) = strongSelf.validLayout else {
return
}
updateLayoutImpl?(layout, navigationBarHeight)
})
let controller = VoiceChatCameraPreviewController(sharedContext: strongSelf.sharedContext, cameraNode: outgoingVideoNode, shareCamera: { [weak self] _, _ in
if let strongSelf = self {
proceed()
}
}, switchCamera: { [weak self] in
Queue.mainQueue().after(0.1) {
self?.call.switchVideoCamera()
}
})
strongSelf.present?(controller)
updateLayoutImpl = { [weak controller] layout, navigationBarHeight in
controller?.containerLayoutUpdated(layout, transition: .immediate)
}
}
})
// if strongSelf.displayedCameraConfirmation {
// proceed()
// } else {
// strongSelf.present?(textAlertController(sharedContext: strongSelf.sharedContext, title: nil, text: strongSelf.presentationData.strings.Call_CameraConfirmationText, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Call_CameraConfirmationConfirm, action: {
// proceed()
// })]))
// }
})
} else {
strongSelf.call.disableVideo()

View File

@ -6,22 +6,20 @@ import SwiftSignalKit
import AccountContext
import ContextUI
final class GroupVideoNode: ASDisplayNode {
static let useBlurTransparency: Bool = !UIAccessibility.isReduceTransparencyEnabled
enum VideoNodeLayoutMode {
case fillOrFitToSquare
case fillHorizontal
case fillVertical
case fit
}
final class GroupVideoNode: ASDisplayNode, PreviewVideoNode {
enum Position {
case tile
case list
case mainstage
}
enum LayoutMode {
case fillOrFitToSquare
case fillHorizontal
case fillVertical
case fit
}
let sourceContainerNode: PinchSourceContainerNode
private let containerNode: ASDisplayNode
private let videoViewContainer: UIView
@ -29,15 +27,14 @@ final class GroupVideoNode: ASDisplayNode {
private let backdropVideoViewContainer: UIView
private let backdropVideoView: VideoRenderingView?
private var backdropEffectView: UIVisualEffectView?
private var effectView: UIVisualEffectView?
private var isBlurred: Bool = false
private var isEnabled: Bool = false
private var isBlurEnabled: Bool = false
private var validLayout: (CGSize, LayoutMode)?
private var validLayout: (CGSize, VideoNodeLayoutMode)?
var tapped: (() -> Void)?
@ -61,21 +58,6 @@ final class GroupVideoNode: ASDisplayNode {
super.init()
if let backdropVideoView = backdropVideoView {
self.backdropVideoViewContainer.addSubview(backdropVideoView)
self.view.addSubview(self.backdropVideoViewContainer)
let effect: UIVisualEffect
if #available(iOS 13.0, *) {
effect = UIBlurEffect(style: .systemThinMaterialDark)
} else {
effect = UIBlurEffect(style: .dark)
}
//let backdropEffectView = UIVisualEffectView(effect: effect)
//self.view.addSubview(backdropEffectView)
//self.backdropEffectView = backdropEffectView
}
self.videoViewContainer.addSubview(self.videoView)
self.addSubnode(self.sourceContainerNode)
self.containerNode.view.addSubview(self.videoViewContainer)
@ -204,7 +186,7 @@ final class GroupVideoNode: ASDisplayNode {
return rotatedAspect
}
func updateLayout(size: CGSize, layoutMode: LayoutMode, transition: ContainedViewLayoutTransition) {
func updateLayout(size: CGSize, layoutMode: VideoNodeLayoutMode, transition: ContainedViewLayoutTransition) {
self.validLayout = (size, layoutMode)
let bounds = CGRect(origin: CGPoint(), size: size)
self.sourceContainerNode.update(size: size, transition: .immediate)
@ -310,20 +292,6 @@ final class GroupVideoNode: ASDisplayNode {
self.backdropVideoView?.updateIsEnabled(self.isEnabled && self.isBlurEnabled)
if self.isBlurEnabled {
self.backdropVideoView?.isHidden = false
self.backdropEffectView?.isHidden = false
}
transition.updatePosition(layer: backdropVideoView.layer, position: rotatedVideoFrame.center, force: true, completion: { [weak self] value in
guard let strongSelf = self, value else {
return
}
if !strongSelf.isBlurEnabled {
strongSelf.backdropVideoView?.updateIsEnabled(false)
strongSelf.backdropVideoView?.isHidden = true
strongSelf.backdropEffectView?.isHidden = false
}
})
transition.updateBounds(layer: backdropVideoView.layer, bounds: CGRect(origin: CGPoint(), size: normalizedVideoSize))
let transformScale: CGFloat = rotatedVideoFrame.width / normalizedVideoSize.width
@ -334,21 +302,6 @@ final class GroupVideoNode: ASDisplayNode {
transition.updateTransformRotation(view: backdropVideoView, angle: angle)
}
if let backdropEffectView = self.backdropEffectView {
let maxSide = max(bounds.width, bounds.height) + 32.0
let squareBounds = CGRect(x: (bounds.width - maxSide) / 2.0, y: (bounds.height - maxSide) / 2.0, width: maxSide, height: maxSide)
if case let .animated(duration, .spring) = transition {
UIView.animate(withDuration: duration, delay: 0.0, usingSpringWithDamping: 500.0, initialSpringVelocity: 0.0, options: .layoutSubviews, animations: {
backdropEffectView.frame = squareBounds
})
} else {
transition.animateView {
backdropEffectView.frame = squareBounds
}
}
}
if let effectView = self.effectView {
if case let .animated(duration, .spring) = transition {
UIView.animate(withDuration: duration, delay: 0.0, usingSpringWithDamping: 500.0, initialSpringVelocity: 0.0, options: .layoutSubviews, animations: {

View File

@ -13,6 +13,7 @@ import TelegramPresentationData
import DeviceAccess
import UniversalMediaPlayer
import AccountContext
import DeviceProximity
final class PresentationCallToneRenderer {
let queue: Queue
@ -287,6 +288,8 @@ public final class PresentationCallImpl: PresentationCall {
private var useFrontCamera: Bool = true
private var videoCapturer: OngoingCallVideoCapturer?
private var proximityManagerIndex: Int?
init(
account: Account,
audioSession: ManagedAudioSession,
@ -455,6 +458,11 @@ public final class PresentationCallImpl: PresentationCall {
strongSelf.updateIsAudioSessionActive(value)
}
})
if callKitIntegration == nil {
self.proximityManagerIndex = DeviceProximityManager.shared().add { _ in
}
}
}
deinit {
@ -473,6 +481,10 @@ public final class PresentationCallImpl: PresentationCall {
self.callKitIntegration?.dropCall(uuid: self.internalId)
}
}
if let proximityManagerIndex = self.proximityManagerIndex {
DeviceProximityManager.shared().remove(proximityManagerIndex)
}
}
private func updateSessionState(sessionState: CallSession, callContextState: OngoingCallContextState?, reception: Int32?, audioSessionControl: ManagedAudioSessionControl?) {

View File

@ -15,23 +15,32 @@ import ReplayKit
private let accentColor: UIColor = UIColor(rgb: 0x007aff)
protocol PreviewVideoNode: ASDisplayNode {
var ready: Signal<Bool, NoError> { get }
func flip(withBackground: Bool)
func updateIsBlurred(isBlurred: Bool, light: Bool, animated: Bool)
func updateLayout(size: CGSize, layoutMode: VideoNodeLayoutMode, transition: ContainedViewLayoutTransition)
}
final class VoiceChatCameraPreviewController: ViewController {
private var controllerNode: VoiceChatCameraPreviewControllerNode {
return self.displayNode as! VoiceChatCameraPreviewControllerNode
}
private let context: AccountContext
private let sharedContext: SharedAccountContext
private var animatedIn = false
private let cameraNode: GroupVideoNode
private let cameraNode: PreviewVideoNode
private let shareCamera: (ASDisplayNode, Bool) -> Void
private let switchCamera: () -> Void
private var presentationDataDisposable: Disposable?
init(context: AccountContext, cameraNode: GroupVideoNode, shareCamera: @escaping (ASDisplayNode, Bool) -> Void, switchCamera: @escaping () -> Void) {
self.context = context
init(sharedContext: SharedAccountContext, cameraNode: PreviewVideoNode, shareCamera: @escaping (ASDisplayNode, Bool) -> Void, switchCamera: @escaping () -> Void) {
self.sharedContext = sharedContext
self.cameraNode = cameraNode
self.shareCamera = shareCamera
self.switchCamera = switchCamera
@ -42,7 +51,7 @@ final class VoiceChatCameraPreviewController: ViewController {
self.blocksBackgroundWhenInOverlay = true
self.presentationDataDisposable = (context.sharedContext.presentationData
self.presentationDataDisposable = (sharedContext.presentationData
|> deliverOnMainQueue).start(next: { [weak self] presentationData in
if let strongSelf = self {
strongSelf.controllerNode.updatePresentationData(presentationData)
@ -61,7 +70,7 @@ final class VoiceChatCameraPreviewController: ViewController {
}
override public func loadDisplayNode() {
self.displayNode = VoiceChatCameraPreviewControllerNode(controller: self, context: self.context, cameraNode: self.cameraNode)
self.displayNode = VoiceChatCameraPreviewControllerNode(controller: self, sharedContext: self.sharedContext, cameraNode: self.cameraNode)
self.controllerNode.shareCamera = { [weak self] unmuted in
if let strongSelf = self {
strongSelf.shareCamera(strongSelf.cameraNode, unmuted)
@ -106,10 +115,10 @@ final class VoiceChatCameraPreviewController: ViewController {
private class VoiceChatCameraPreviewControllerNode: ViewControllerTracingNode, UIScrollViewDelegate {
private weak var controller: VoiceChatCameraPreviewController?
private let context: AccountContext
private let sharedContext: SharedAccountContext
private var presentationData: PresentationData
private let cameraNode: GroupVideoNode
private let cameraNode: PreviewVideoNode
private let dimNode: ASDisplayNode
private let wrappingScrollNode: ASScrollNode
private let contentContainerNode: ASDisplayNode
@ -119,18 +128,19 @@ private class VoiceChatCameraPreviewControllerNode: ViewControllerTracingNode, U
private let titleNode: ASTextNode
private let previewContainerNode: ASDisplayNode
private let shimmerNode: ShimmerEffectForegroundNode
private let cameraButton: SolidRoundedButtonNode
private let screenButton: SolidRoundedButtonNode
private let doneButton: SolidRoundedButtonNode
private var broadcastPickerView: UIView?
private let cancelButton: SolidRoundedButtonNode
private let microphoneButton: HighlightTrackingButtonNode
private let microphoneEffectView: UIVisualEffectView
private let microphoneIconNode: VoiceChatMicrophoneNode
private let placeholderTextNode: ImmediateTextNode
private let placeholderIconNode: ASImageNode
private let switchCameraButton: HighlightTrackingButtonNode
private let switchCameraEffectView: UIVisualEffectView
private let switchCameraIconNode: ASImageNode
private let tabsNode: TabsSegmentedControlNode
private var selectedTabIndex: Int = 0
private var containerLayout: (ContainerViewLayout, CGFloat)?
@ -145,10 +155,10 @@ private class VoiceChatCameraPreviewControllerNode: ViewControllerTracingNode, U
var dismiss: (() -> Void)?
var cancel: (() -> Void)?
init(controller: VoiceChatCameraPreviewController, context: AccountContext, cameraNode: GroupVideoNode) {
init(controller: VoiceChatCameraPreviewController, sharedContext: SharedAccountContext, cameraNode: PreviewVideoNode) {
self.controller = controller
self.context = context
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
self.sharedContext = sharedContext
self.presentationData = sharedContext.currentPresentationData.with { $0 }
self.cameraNode = cameraNode
@ -185,16 +195,14 @@ private class VoiceChatCameraPreviewControllerNode: ViewControllerTracingNode, U
self.titleNode = ASTextNode()
self.titleNode.attributedText = NSAttributedString(string: title, font: Font.bold(17.0), textColor: textColor)
self.cameraButton = SolidRoundedButtonNode(theme: SolidRoundedButtonTheme(backgroundColor: accentColor, foregroundColor: .white), font: .bold, height: 52.0, cornerRadius: 11.0, gloss: false)
self.cameraButton.title = self.presentationData.strings.VoiceChat_VideoPreviewShareCamera
self.doneButton = SolidRoundedButtonNode(theme: SolidRoundedButtonTheme(backgroundColor: accentColor, foregroundColor: .white), font: .bold, height: 52.0, cornerRadius: 11.0, gloss: false)
self.doneButton.title = self.presentationData.strings.VoiceChat_VideoPreviewContinue
self.screenButton = SolidRoundedButtonNode(theme: SolidRoundedButtonTheme(backgroundColor: buttonColor, foregroundColor: buttonTextColor), font: .bold, height: 52.0, cornerRadius: 11.0, gloss: false)
self.screenButton.title = self.presentationData.strings.VoiceChat_VideoPreviewShareScreen
if #available(iOS 12.0, *) {
let broadcastPickerView = RPSystemBroadcastPickerView(frame: CGRect(x: 0, y: 0, width: 50, height: 52.0))
broadcastPickerView.alpha = 0.02
broadcastPickerView.preferredExtension = "\(self.context.sharedContext.applicationBindings.appBundleId).BroadcastUpload"
broadcastPickerView.isHidden = true
broadcastPickerView.preferredExtension = "\(self.sharedContext.applicationBindings.appBundleId).BroadcastUpload"
broadcastPickerView.showsMicrophoneButton = false
self.broadcastPickerView = broadcastPickerView
}
@ -209,27 +217,29 @@ private class VoiceChatCameraPreviewControllerNode: ViewControllerTracingNode, U
self.shimmerNode = ShimmerEffectForegroundNode(size: 200.0)
self.previewContainerNode.addSubnode(self.shimmerNode)
self.microphoneButton = HighlightTrackingButtonNode()
self.microphoneButton.isSelected = true
self.microphoneEffectView = UIVisualEffectView(effect: UIBlurEffect(style: .dark))
self.microphoneEffectView = UIVisualEffectView(effect: UIBlurEffect(style: .light))
self.microphoneEffectView.clipsToBounds = true
self.microphoneEffectView.layer.cornerRadius = 24.0
self.microphoneEffectView.isUserInteractionEnabled = false
self.microphoneIconNode = VoiceChatMicrophoneNode()
// self.microphoneIconNode.alpha = 0.75
self.microphoneIconNode.update(state: .init(muted: false, filled: true, color: .white), animated: false)
self.switchCameraButton = HighlightTrackingButtonNode()
self.switchCameraEffectView = UIVisualEffectView(effect: UIBlurEffect(style: .dark))
self.switchCameraEffectView.clipsToBounds = true
self.switchCameraEffectView.layer.cornerRadius = 24.0
self.switchCameraEffectView.isUserInteractionEnabled = false
self.tabsNode = TabsSegmentedControlNode(items: [TabsSegmentedControlNode.Item(title: "Front Camera"), TabsSegmentedControlNode.Item(title: "Back Camera"), TabsSegmentedControlNode.Item(title: "Share Screen")], selectedIndex: 0)
self.switchCameraIconNode = ASImageNode()
self.switchCameraIconNode.displaysAsynchronously = false
self.switchCameraIconNode.image = generateTintedImage(image: UIImage(bundleImageName: "Call/SwitchCameraIcon"), color: .white)
self.switchCameraIconNode.contentMode = .center
self.placeholderTextNode = ImmediateTextNode()
self.placeholderTextNode.alpha = 0.0
self.placeholderTextNode.maximumNumberOfLines = 3
self.placeholderTextNode.textAlignment = .center
self.placeholderIconNode = ASImageNode()
self.placeholderIconNode.alpha = 0.0
self.placeholderIconNode.contentMode = .scaleAspectFit
self.placeholderIconNode.displaysAsynchronously = false
super.init()
@ -248,8 +258,7 @@ private class VoiceChatCameraPreviewControllerNode: ViewControllerTracingNode, U
self.backgroundNode.addSubnode(self.effectNode)
self.backgroundNode.addSubnode(self.contentBackgroundNode)
self.contentContainerNode.addSubnode(self.titleNode)
self.contentContainerNode.addSubnode(self.cameraButton)
self.contentContainerNode.addSubnode(self.screenButton)
self.contentContainerNode.addSubnode(self.doneButton)
if let broadcastPickerView = self.broadcastPickerView {
self.contentContainerNode.view.addSubview(broadcastPickerView)
}
@ -258,14 +267,40 @@ private class VoiceChatCameraPreviewControllerNode: ViewControllerTracingNode, U
self.contentContainerNode.addSubnode(self.previewContainerNode)
self.previewContainerNode.addSubnode(self.cameraNode)
self.previewContainerNode.addSubnode(self.microphoneButton)
self.microphoneButton.view.addSubview(self.microphoneEffectView)
self.microphoneButton.addSubnode(self.microphoneIconNode)
self.previewContainerNode.addSubnode(self.switchCameraButton)
self.switchCameraButton.view.addSubview(self.switchCameraEffectView)
self.switchCameraButton.addSubnode(self.switchCameraIconNode)
self.cameraButton.pressed = { [weak self] in
self.previewContainerNode.addSubnode(self.placeholderIconNode)
self.previewContainerNode.addSubnode(self.placeholderTextNode)
if self.cameraNode is GroupVideoNode {
self.previewContainerNode.addSubnode(self.microphoneButton)
self.microphoneButton.view.addSubview(self.microphoneEffectView)
self.microphoneButton.addSubnode(self.microphoneIconNode)
}
self.previewContainerNode.addSubnode(self.tabsNode)
self.tabsNode.selectedIndexChanged = { [weak self] index in
if let strongSelf = self {
if (index == 0 && strongSelf.selectedTabIndex == 1) || (index == 1 && strongSelf.selectedTabIndex == 0) {
strongSelf.switchCamera?()
}
if index == 2 && [0, 1].contains(strongSelf.selectedTabIndex) {
strongSelf.broadcastPickerView?.isHidden = false
strongSelf.cameraNode.updateIsBlurred(isBlurred: true, light: false, animated: true)
let transition = ContainedViewLayoutTransition.animated(duration: 0.3, curve: .easeInOut)
transition.updateAlpha(node: strongSelf.placeholderTextNode, alpha: 1.0)
transition.updateAlpha(node: strongSelf.placeholderIconNode, alpha: 1.0)
} else if [0, 1].contains(index) && strongSelf.selectedTabIndex == 2 {
strongSelf.broadcastPickerView?.isHidden = true
strongSelf.cameraNode.updateIsBlurred(isBlurred: false, light: false, animated: true)
let transition = ContainedViewLayoutTransition.animated(duration: 0.3, curve: .easeInOut)
transition.updateAlpha(node: strongSelf.placeholderTextNode, alpha: 0.0)
transition.updateAlpha(node: strongSelf.placeholderIconNode, alpha: 0.0)
}
strongSelf.selectedTabIndex = index
}
}
self.doneButton.pressed = { [weak self] in
if let strongSelf = self {
strongSelf.shareCamera?(strongSelf.microphoneButton.isSelected)
}
@ -289,19 +324,6 @@ private class VoiceChatCameraPreviewControllerNode: ViewControllerTracingNode, U
}
}
self.switchCameraButton.addTarget(self, action: #selector(self.switchCameraPressed), forControlEvents: .touchUpInside)
self.switchCameraButton.highligthedChanged = { [weak self] highlighted in
if let strongSelf = self {
if highlighted {
let transition: ContainedViewLayoutTransition = .animated(duration: 0.3, curve: .spring)
transition.updateSublayerTransformScale(node: strongSelf.switchCameraButton, scale: 0.9)
} else {
let transition: ContainedViewLayoutTransition = .animated(duration: 0.5, curve: .spring)
transition.updateSublayerTransformScale(node: strongSelf.switchCameraButton, scale: 1.0)
}
}
}
self.readyDisposable.set(self.cameraNode.ready.start(next: { [weak self] ready in
if let strongSelf = self, ready {
Queue.mainQueue().after(0.07) {
@ -322,19 +344,7 @@ private class VoiceChatCameraPreviewControllerNode: ViewControllerTracingNode, U
self.microphoneButton.isSelected = !self.microphoneButton.isSelected
self.microphoneIconNode.update(state: .init(muted: !self.microphoneButton.isSelected, filled: true, color: .white), animated: true)
}
@objc private func switchCameraPressed() {
self.hapticFeedback.impact(.light)
self.switchCamera?()
let springDuration: Double = 0.7
let springDamping: CGFloat = 100.0
self.switchCameraButton.isUserInteractionEnabled = false
self.switchCameraIconNode.layer.animateSpring(from: 0.0 as NSNumber, to: CGFloat.pi as NSNumber, keyPath: "transform.rotation.z", duration: springDuration, damping: springDamping, completion: { [weak self] _ in
self?.switchCameraButton.isUserInteractionEnabled = true
})
}
func updatePresentationData(_ presentationData: PresentationData) {
self.presentationData = presentationData
}
@ -368,7 +378,7 @@ private class VoiceChatCameraPreviewControllerNode: ViewControllerTracingNode, U
self.dimNode.position = dimPosition
})
self.applicationStateDisposable = (self.context.sharedContext.applicationBindings.applicationIsActive
self.applicationStateDisposable = (self.sharedContext.applicationBindings.applicationIsActive
|> filter { !$0 }
|> take(1)
|> deliverOnMainQueue).start(next: { [weak self] _ in
@ -443,9 +453,6 @@ private class VoiceChatCameraPreviewControllerNode: ViewControllerTracingNode, U
insets.top = max(10.0, insets.top)
var buttonOffset: CGFloat = 60.0
if let _ = self.broadcastPickerView {
buttonOffset *= 2.0
}
let bottomInset: CGFloat = isTablet ? 31.0 : 10.0 + cleanInsets.bottom
let titleHeight: CGFloat = 54.0
var contentHeight = titleHeight + bottomInset + 52.0 + 17.0
@ -522,24 +529,27 @@ private class VoiceChatCameraPreviewControllerNode: ViewControllerTracingNode, U
self.cameraNode.frame = CGRect(origin: CGPoint(), size: previewSize)
self.cameraNode.updateLayout(size: previewSize, layoutMode: isLandscape ? .fillHorizontal : .fillVertical, transition: .immediate)
let microphoneFrame = CGRect(x: 16.0, y: previewSize.height - 48.0 - 16.0, width: 48.0, height: 48.0)
let microphoneFrame = CGRect(x: 8.0, y: previewSize.height - 48.0 - 8.0 - 48.0, width: 48.0, height: 48.0)
transition.updateFrame(node: self.microphoneButton, frame: microphoneFrame)
transition.updateFrame(view: self.microphoneEffectView, frame: CGRect(origin: CGPoint(), size: microphoneFrame.size))
transition.updateFrameAsPositionAndBounds(node: self.microphoneIconNode, frame: CGRect(origin: CGPoint(x: 1.0, y: 0.0), size: microphoneFrame.size).insetBy(dx: 6.0, dy: 6.0))
self.microphoneIconNode.transform = CATransform3DMakeScale(1.2, 1.2, 1.0)
let switchCameraFrame = CGRect(x: previewSize.width - 48.0 - 16.0, y: previewSize.height - 48.0 - 16.0, width: 48.0, height: 48.0)
transition.updateFrame(node: self.switchCameraButton, frame: switchCameraFrame)
transition.updateFrame(view: self.switchCameraEffectView, frame: CGRect(origin: CGPoint(), size: switchCameraFrame.size))
transition.updateFrame(node: self.switchCameraIconNode, frame: CGRect(origin: CGPoint(), size: switchCameraFrame.size))
let tabsFrame = CGRect(x: 8.0, y: previewSize.height - 40.0 - 8.0, width: previewSize.width - 16.0, height: 40.0)
self.tabsNode.updateLayout(size: tabsFrame.size, transition: transition)
transition.updateFrame(node: self.tabsNode, frame: tabsFrame)
self.placeholderTextNode.attributedText = NSAttributedString(string: presentationData.strings.VoiceChat_VideoPreviewShareScreenInfo, font: Font.semibold(14.0), textColor: .white)
self.placeholderIconNode.image = generateTintedImage(image: UIImage(bundleImageName: isTablet ? "Call/ScreenShareTablet" : "Call/ScreenSharePhone"), color: .white)
let placeholderTextSize = self.placeholderTextNode.updateLayout(CGSize(width: previewSize.width - 80.0, height: 100.0))
transition.updateFrame(node: self.placeholderTextNode, frame: CGRect(origin: CGPoint(x: floor((previewSize.width - placeholderTextSize.width) / 2.0), y: floorToScreenPixels(previewSize.height / 2.0) + 10.0), size: placeholderTextSize))
if let imageSize = self.placeholderIconNode.image?.size {
transition.updateFrame(node: self.placeholderIconNode, frame: CGRect(origin: CGPoint(x: floor((previewSize.width - imageSize.width) / 2.0), y: floorToScreenPixels(previewSize.height / 2.0) - imageSize.height - 8.0), size: imageSize))
}
if isLandscape {
var buttonsCount: Int = 2
if let _ = self.broadcastPickerView {
buttonsCount += 1
} else {
self.screenButton.isHidden = true
}
let buttonInset: CGFloat = 6.0
var leftButtonInset = buttonInset
@ -552,33 +562,19 @@ private class VoiceChatCameraPreviewControllerNode: ViewControllerTracingNode, U
}
let buttonWidth = floorToScreenPixels((availableWidth - CGFloat(buttonsCount + 1) * buttonInset) / CGFloat(buttonsCount))
let cameraButtonHeight = self.cameraButton.updateLayout(width: buttonWidth, transition: transition)
let screenButtonHeight = self.screenButton.updateLayout(width: buttonWidth, transition: transition)
let cameraButtonHeight = self.doneButton.updateLayout(width: buttonWidth, transition: transition)
let cancelButtonHeight = self.cancelButton.updateLayout(width: buttonWidth, transition: transition)
transition.updateFrame(node: self.cancelButton, frame: CGRect(x: layout.safeInsets.left + leftButtonInset, y: previewFrame.maxY + 10.0, width: buttonWidth, height: cancelButtonHeight))
if let broadcastPickerView = self.broadcastPickerView {
transition.updateFrame(node: self.screenButton, frame: CGRect(x: layout.safeInsets.left + leftButtonInset + buttonWidth + buttonInset, y: previewFrame.maxY + 10.0, width: buttonWidth, height: screenButtonHeight))
broadcastPickerView.frame = CGRect(x: layout.safeInsets.left + leftButtonInset + buttonWidth + buttonInset, y: previewFrame.maxY + 10.0, width: buttonWidth, height: screenButtonHeight)
transition.updateFrame(node: self.cameraButton, frame: CGRect(x: layout.safeInsets.left + leftButtonInset + buttonWidth + buttonInset + buttonWidth + buttonInset, y: previewFrame.maxY + 10.0, width: buttonWidth, height: cameraButtonHeight))
} else {
transition.updateFrame(node: self.cameraButton, frame: CGRect(x: layout.safeInsets.left + leftButtonInset + buttonWidth + buttonInset, y: previewFrame.maxY + 10.0, width: buttonWidth, height: cameraButtonHeight))
}
transition.updateFrame(node: self.doneButton, frame: CGRect(x: layout.safeInsets.left + leftButtonInset + buttonWidth + buttonInset, y: previewFrame.maxY + 10.0, width: buttonWidth, height: cameraButtonHeight))
self.broadcastPickerView?.frame = self.doneButton.frame
} else {
let bottomInset = isTablet ? 21.0 : insets.bottom + 16.0
let buttonInset: CGFloat = 16.0
let cameraButtonHeight = self.cameraButton.updateLayout(width: contentFrame.width - buttonInset * 2.0, transition: transition)
transition.updateFrame(node: self.cameraButton, frame: CGRect(x: buttonInset, y: contentHeight - cameraButtonHeight - bottomInset - buttonOffset, width: contentFrame.width, height: cameraButtonHeight))
let screenButtonHeight = self.screenButton.updateLayout(width: contentFrame.width - buttonInset * 2.0, transition: transition)
transition.updateFrame(node: self.screenButton, frame: CGRect(x: buttonInset, y: contentHeight - cameraButtonHeight - 8.0 - screenButtonHeight - bottomInset, width: contentFrame.width, height: screenButtonHeight))
if let broadcastPickerView = self.broadcastPickerView {
broadcastPickerView.frame = CGRect(x: buttonInset, y: contentHeight - cameraButtonHeight - 8.0 - screenButtonHeight - bottomInset, width: contentFrame.width + 1000.0, height: screenButtonHeight)
} else {
self.screenButton.isHidden = true
}
let cameraButtonHeight = self.doneButton.updateLayout(width: contentFrame.width - buttonInset * 2.0, transition: transition)
transition.updateFrame(node: self.doneButton, frame: CGRect(x: buttonInset, y: contentHeight - cameraButtonHeight - bottomInset - buttonOffset, width: contentFrame.width, height: cameraButtonHeight))
self.broadcastPickerView?.frame = self.doneButton.frame
let cancelButtonHeight = self.cancelButton.updateLayout(width: contentFrame.width - buttonInset * 2.0, transition: transition)
transition.updateFrame(node: self.cancelButton, frame: CGRect(x: buttonInset, y: contentHeight - cancelButtonHeight - bottomInset, width: contentFrame.width, height: cancelButtonHeight))
}
@ -586,3 +582,303 @@ private class VoiceChatCameraPreviewControllerNode: ViewControllerTracingNode, U
transition.updateFrame(node: self.contentContainerNode, frame: contentFrame)
}
}
private let textFont = Font.medium(14.0)
class TabsSegmentedControlNode: ASDisplayNode, UIGestureRecognizerDelegate {
struct Item: Equatable {
public let title: String
public init(title: String) {
self.title = title
}
}
private var blurEffectView: UIVisualEffectView?
private var vibrancyEffectView: UIVisualEffectView?
private let selectionNode: ASDisplayNode
private var itemNodes: [HighlightTrackingButtonNode]
private var highlightedItemNodes: [HighlightTrackingButtonNode]
private var validLayout: CGSize?
private var _items: [Item]
private var _selectedIndex: Int = 0
public var selectedIndex: Int {
get {
return self._selectedIndex
}
set {
guard newValue != self._selectedIndex else {
return
}
self._selectedIndex = newValue
if let size = self.validLayout {
self.updateLayout(size: size, transition: .immediate)
}
}
}
public func setSelectedIndex(_ index: Int, animated: Bool) {
guard index != self._selectedIndex else {
return
}
self._selectedIndex = index
if let size = self.validLayout {
self.updateLayout(size: size, transition: .animated(duration: 0.2, curve: .easeInOut))
}
}
public var selectedIndexChanged: (Int) -> Void = { _ in }
private var gestureRecognizer: UIPanGestureRecognizer?
private var gestureSelectedIndex: Int?
public init(items: [Item], selectedIndex: Int) {
self._items = items
self._selectedIndex = selectedIndex
self.selectionNode = ASDisplayNode()
self.selectionNode.clipsToBounds = true
self.selectionNode.backgroundColor = .black
self.selectionNode.alpha = 0.75
self.itemNodes = items.map { item in
let itemNode = HighlightTrackingButtonNode()
itemNode.contentEdgeInsets = UIEdgeInsets(top: 0.0, left: 8.0, bottom: 0.0, right: 8.0)
itemNode.imageNode.isHidden = true
itemNode.titleNode.maximumNumberOfLines = 1
itemNode.titleNode.truncationMode = .byTruncatingTail
itemNode.titleNode.alpha = 0.75
itemNode.accessibilityLabel = item.title
itemNode.accessibilityTraits = [.button]
itemNode.setTitle(item.title, with: textFont, with: .black, for: .normal)
return itemNode
}
self.highlightedItemNodes = items.map { item in
let itemNode = HighlightTrackingButtonNode()
itemNode.isUserInteractionEnabled = false
itemNode.isHidden = true
itemNode.contentEdgeInsets = UIEdgeInsets(top: 0.0, left: 8.0, bottom: 0.0, right: 8.0)
itemNode.imageNode.isHidden = true
itemNode.titleNode.maximumNumberOfLines = 1
itemNode.titleNode.truncationMode = .byTruncatingTail
itemNode.setTitle(item.title, with: textFont, with: .white, for: .normal)
return itemNode
}
super.init()
self.clipsToBounds = true
if #available(iOS 13.0, *) {
self.layer.cornerCurve = .continuous
self.selectionNode.layer.cornerCurve = .continuous
}
self.setupButtons()
}
override func didLoad() {
super.didLoad()
self.view.disablesInteractiveTransitionGestureRecognizer = true
let gestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(self.panGesture(_:)))
gestureRecognizer.delegate = self
self.view.addGestureRecognizer(gestureRecognizer)
self.gestureRecognizer = gestureRecognizer
let blurEffect = UIBlurEffect(style: .light)
let blurEffectView = UIVisualEffectView(effect: blurEffect)
self.blurEffectView = blurEffectView
self.view.addSubview(blurEffectView)
let vibrancyEffect: UIVibrancyEffect
if #available(iOS 13.0, *) {
vibrancyEffect = UIVibrancyEffect(blurEffect: blurEffect, style: .label)
} else {
vibrancyEffect = UIVibrancyEffect(blurEffect: blurEffect)
}
let vibrancyEffectView = UIVisualEffectView(effect: vibrancyEffect)
self.vibrancyEffectView = vibrancyEffectView
blurEffectView.contentView.addSubview(vibrancyEffectView)
self.itemNodes.forEach(vibrancyEffectView.contentView.addSubnode(_:))
vibrancyEffectView.contentView.addSubnode(self.selectionNode)
self.highlightedItemNodes.forEach(self.addSubnode(_:))
}
func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) {
self.validLayout = size
let bounds = CGRect(origin: CGPoint(), size: size)
self.cornerRadius = size.height / 2.0
if let blurEffectView = self.blurEffectView {
transition.updateFrame(view: blurEffectView, frame: bounds)
}
if let vibrancyEffectView = self.vibrancyEffectView {
transition.updateFrame(view: vibrancyEffectView, frame: bounds)
}
let selectedIndex: Int
if let gestureSelectedIndex = self.gestureSelectedIndex {
selectedIndex = gestureSelectedIndex
} else {
selectedIndex = self.selectedIndex
}
if !self.itemNodes.isEmpty {
let itemSize = CGSize(width: floorToScreenPixels(size.width / CGFloat(self.itemNodes.count)), height: size.height)
let selectionFrame = CGRect(origin: CGPoint(x: itemSize.width * CGFloat(selectedIndex), y: 0.0), size: itemSize).insetBy(dx: 4.0, dy: 4.0)
transition.updateFrameAsPositionAndBounds(node: self.selectionNode, frame: selectionFrame)
self.selectionNode.cornerRadius = selectionFrame.height / 2.0
for i in 0 ..< self.itemNodes.count {
let itemNode = self.itemNodes[i]
let highlightedItemNode = self.highlightedItemNodes[i]
let _ = itemNode.measure(itemSize)
transition.updateFrame(node: itemNode, frame: CGRect(origin: CGPoint(x: itemSize.width * CGFloat(i), y: (size.height - itemSize.height) / 2.0), size: itemSize))
transition.updateFrame(node: highlightedItemNode, frame: CGRect(origin: CGPoint(x: itemSize.width * CGFloat(i), y: (size.height - itemSize.height) / 2.0), size: itemSize))
let isSelected = selectedIndex == i
if itemNode.isSelected != isSelected {
if case .animated = transition {
UIView.transition(with: itemNode.view, duration: 0.2, options: .transitionCrossDissolve, animations: {
itemNode.isSelected = isSelected
highlightedItemNode.isHidden = !isSelected
}, completion: nil)
} else {
itemNode.isSelected = isSelected
highlightedItemNode.isHidden = !isSelected
}
if isSelected {
itemNode.accessibilityTraits.insert(.selected)
} else {
itemNode.accessibilityTraits.remove(.selected)
}
}
}
}
}
private func setupButtons() {
for i in 0 ..< self.itemNodes.count {
let itemNode = self.itemNodes[i]
itemNode.addTarget(self, action: #selector(self.buttonPressed(_:)), forControlEvents: .touchUpInside)
itemNode.highligthedChanged = { [weak self, weak itemNode] highlighted in
if let strongSelf = self, let itemNode = itemNode {
let transition = ContainedViewLayoutTransition.animated(duration: 0.25, curve: .easeInOut)
if strongSelf.selectedIndex == i {
if let gestureRecognizer = strongSelf.gestureRecognizer, case .began = gestureRecognizer.state {
} else {
strongSelf.updateButtonsHighlights(highlightedIndex: highlighted ? i : nil, gestureSelectedIndex: strongSelf.gestureSelectedIndex)
}
} else if highlighted {
transition.updateAlpha(node: itemNode, alpha: 0.4)
}
if !highlighted {
transition.updateAlpha(node: itemNode, alpha: 1.0)
}
}
}
}
}
private func updateButtonsHighlights(highlightedIndex: Int?, gestureSelectedIndex: Int?) {
let transition = ContainedViewLayoutTransition.animated(duration: 0.25, curve: .easeInOut)
if highlightedIndex == nil && gestureSelectedIndex == nil {
transition.updateTransformScale(node: self.selectionNode, scale: 1.0)
} else {
transition.updateTransformScale(node: self.selectionNode, scale: 0.96)
}
for i in 0 ..< self.itemNodes.count {
let itemNode = self.itemNodes[i]
let highlightedItemNode = self.highlightedItemNodes[i]
if i == highlightedIndex || i == gestureSelectedIndex {
transition.updateTransformScale(node: itemNode, scale: 0.96)
transition.updateTransformScale(node: highlightedItemNode, scale: 0.96)
} else {
transition.updateTransformScale(node: itemNode, scale: 1.0)
transition.updateTransformScale(node: highlightedItemNode, scale: 1.0)
}
}
}
private func updateButtonsHighlights() {
let transition = ContainedViewLayoutTransition.animated(duration: 0.25, curve: .easeInOut)
if let gestureSelectedIndex = self.gestureSelectedIndex {
for i in 0 ..< self.itemNodes.count {
let itemNode = self.itemNodes[i]
let highlightedItemNode = self.highlightedItemNodes[i]
transition.updateTransformScale(node: itemNode, scale: i == gestureSelectedIndex ? 0.96 : 1.0)
transition.updateTransformScale(node: highlightedItemNode, scale: i == gestureSelectedIndex ? 0.96 : 1.0)
}
} else {
for itemNode in self.itemNodes {
transition.updateTransformScale(node: itemNode, scale: 1.0)
}
for itemNode in self.highlightedItemNodes {
transition.updateTransformScale(node: itemNode, scale: 1.0)
}
}
}
@objc private func buttonPressed(_ button: HighlightTrackingButtonNode) {
guard let index = self.itemNodes.firstIndex(of: button) else {
return
}
self._selectedIndex = index
self.selectedIndexChanged(index)
if let size = self.validLayout {
self.updateLayout(size: size, transition: .animated(duration: 0.2, curve: .slide))
}
}
public override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
return self.selectionNode.frame.contains(gestureRecognizer.location(in: self.view))
}
@objc private func panGesture(_ recognizer: UIPanGestureRecognizer) {
let location = recognizer.location(in: self.view)
switch recognizer.state {
case .changed:
if !self.selectionNode.frame.contains(location) {
let point = CGPoint(x: max(0.0, min(self.bounds.width, location.x)), y: 1.0)
for i in 0 ..< self.itemNodes.count {
let itemNode = self.itemNodes[i]
if itemNode.frame.contains(point) {
if i != self.gestureSelectedIndex {
self.gestureSelectedIndex = i
self.updateButtonsHighlights(highlightedIndex: nil, gestureSelectedIndex: i)
if let size = self.validLayout {
self.updateLayout(size: size, transition: .animated(duration: 0.35, curve: .slide))
}
}
break
}
}
}
case .ended:
if let gestureSelectedIndex = self.gestureSelectedIndex {
if gestureSelectedIndex != self.selectedIndex {
self._selectedIndex = gestureSelectedIndex
self.selectedIndexChanged(gestureSelectedIndex)
}
self.gestureSelectedIndex = nil
}
self.updateButtonsHighlights(highlightedIndex: nil, gestureSelectedIndex: nil)
default:
break
}
}
}

View File

@ -3495,7 +3495,7 @@ public final class VoiceChatController: ViewController {
let input = videoCapturer.video()
if let videoView = strongSelf.videoRenderingContext.makeView(input: input, blur: false) {
let cameraNode = GroupVideoNode(videoView: videoView, backdropVideoView: nil)
let controller = VoiceChatCameraPreviewController(context: strongSelf.context, cameraNode: cameraNode, shareCamera: { [weak self] _, unmuted in
let controller = VoiceChatCameraPreviewController(sharedContext: strongSelf.context.sharedContext, cameraNode: cameraNode, shareCamera: { [weak self] _, unmuted in
if let strongSelf = self {
strongSelf.call.setIsMuted(action: unmuted ? .unmuted : .muted(isPushToTalkActive: false))
(strongSelf.call as! PresentationGroupCallImpl).requestVideo(capturer: videoCapturer)
@ -3512,29 +3512,6 @@ public final class VoiceChatController: ViewController {
})
strongSelf.controller?.present(controller, in: .window(.root))
}
/*strongSelf.call.makeOutgoingVideoView(requestClone: false, completion: { [weak self] view, _ in
guard let strongSelf = self, let view = view else {
return
}
let cameraNode = GroupVideoNode(videoView: view, backdropVideoView: nil)
let controller = VoiceChatCameraPreviewController(context: strongSelf.context, cameraNode: cameraNode, shareCamera: { [weak self] videoNode, unmuted in
if let strongSelf = self {
strongSelf.call.setIsMuted(action: unmuted ? .unmuted : .muted(isPushToTalkActive: false))
strongSelf.call.requestVideo()
if let (layout, navigationHeight) = strongSelf.validLayout {
strongSelf.animatingButtonsSwap = true
strongSelf.containerLayoutUpdated(layout, navigationHeight: navigationHeight, transition: .animated(duration: 0.4, curve: .spring))
}
}
}, switchCamera: { [weak self] in
Queue.mainQueue().after(0.1) {
self?.call.switchVideoCamera()
}
})
strongSelf.controller?.present(controller, in: .window(.root))
})*/
})
}
}

View File

@ -1090,7 +1090,7 @@ final class VoiceChatMainStageNode: ASDisplayNode {
let initialBottomInset = bottomInset
var bottomInset = bottomInset
let layoutMode: GroupVideoNode.LayoutMode
let layoutMode: VideoNodeLayoutMode
if case .immediate = transition, self.animatingIn {
layoutMode = .fillOrFitToSquare
bottomInset = 0.0

View File

@ -19,7 +19,7 @@ private let nameFont = Font.medium(14.0)
private let inlineBotPrefixFont = Font.regular(14.0)
private let inlineBotNameFont = nameFont
class ChatMessageInstantVideoItemNode: ChatMessageItemView {
class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureRecognizerDelegate {
let contextSourceNode: ContextExtractedContentContainingNode
private let containerNode: ContextControllerSourceNode
private let interactiveVideoNode: ChatMessageInteractiveInstantVideoNode
@ -53,6 +53,8 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView {
private var recognizer: TapLongTapOrDoubleTapGestureRecognizer?
private var seekRecognizer: UIPanGestureRecognizer?
private var currentSwipeAction: ChatControllerInteractionSwipeAction?
override var visibility: ListViewItemNodeVisibility {
@ -171,6 +173,12 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView {
return false
}
self.view.addGestureRecognizer(replyRecognizer)
let seekRecognizer = UIPanGestureRecognizer(target: self, action: #selector(self.seekGesture(_:)))
seekRecognizer.isEnabled = false
seekRecognizer.delegate = self
self.seekRecognizer = seekRecognizer
self.interactiveVideoNode.view.addGestureRecognizer(seekRecognizer)
}
override func updateAccessibilityData(_ accessibilityData: ChatMessageAccessibilityData) {
@ -326,9 +334,11 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView {
var isPlaying = false
var displaySize = layoutConstants.instantVideo.dimensions
let maximumDisplaySize = CGSize(width: params.width - 20.0, height: params.width - 20.0)
var effectiveAvatarInset = avatarInset
if item.associatedData.currentlyPlayingMessageId == item.message.index {
isPlaying = true
displaySize = maximumDisplaySize
effectiveAvatarInset = 0.0
}
var automaticDownload = true
@ -345,7 +355,7 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView {
let (videoLayout, videoApply) = makeVideoLayout(ChatMessageBubbleContentItem(context: item.context, controllerInteraction: item.controllerInteraction, message: item.message, read: item.read, chatLocation: item.chatLocation, presentationData: item.presentationData, associatedData: item.associatedData, attributes: item.content.firstMessageAttributes, isItemPinned: item.message.tags.contains(.pinned) && !isReplyThread, isItemEdited: false), params.width - params.leftInset - params.rightInset - avatarInset, displaySize, maximumDisplaySize, isPlaying ? 1.0 : 0.0, .free, automaticDownload)
let videoFrame = CGRect(origin: CGPoint(x: (incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + avatarInset + layoutConstants.bubble.contentInsets.left) : (params.width - params.rightInset - videoLayout.contentSize.width - layoutConstants.bubble.edgeInset - layoutConstants.bubble.contentInsets.left - deliveryFailedInset)), y: 0.0), size: videoLayout.contentSize)
let videoFrame = CGRect(origin: CGPoint(x: (incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + effectiveAvatarInset + layoutConstants.bubble.contentInsets.left) : (params.width - params.rightInset - videoLayout.contentSize.width - layoutConstants.bubble.edgeInset - layoutConstants.bubble.contentInsets.left - deliveryFailedInset)), y: 0.0), size: videoLayout.contentSize)
var viaBotApply: (TextNodeLayout, () -> TextNode)?
var replyInfoApply: (CGSize, () -> ChatMessageReplyInfoNode)?
@ -526,8 +536,6 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView {
strongSelf.updateAccessibilityData(accessibilityData)
let videoLayoutData: ChatMessageInstantVideoItemLayoutData
if incoming {
videoLayoutData = .constrained(left: 0.0, right: max(0.0, availableContentWidth - videoFrame.width))
@ -541,6 +549,9 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView {
videoApply(videoLayoutData, transition)
}
strongSelf.interactiveVideoNode.view.disablesInteractiveTransitionGestureRecognizer = isPlaying
strongSelf.seekRecognizer?.isEnabled = isPlaying
strongSelf.contextSourceNode.contentRect = videoFrame
strongSelf.containerNode.targetNodeForActivationProgressContentRect = strongSelf.contextSourceNode.contentRect
@ -854,6 +865,30 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView {
}
}
override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
if let panGestureRecognizer = gestureRecognizer as? UIPanGestureRecognizer, panGestureRecognizer === self.seekRecognizer {
let velocity = panGestureRecognizer.velocity(in: self.interactiveVideoNode.view)
return velocity.x > velocity.y
}
return true
}
private var wasPlaying = false
@objc func seekGesture(_ recognizer: UIPanGestureRecognizer) {
var location = recognizer.location(in: self.interactiveVideoNode.view)
switch recognizer.state {
case .began:
self.interactiveVideoNode.pause()
case .changed:
self.interactiveVideoNode.seekTo(Double(location.x / self.interactiveVideoNode.bounds.size.width))
case .ended, .cancelled:
self.interactiveVideoNode.seekTo(Double(location.x / self.interactiveVideoNode.bounds.size.width))
self.interactiveVideoNode.play()
default:
break
}
}
@objc func swipeToReplyGesture(_ recognizer: ChatSwipeToReplyRecognizer) {
switch recognizer.state {
case .began:
@ -1087,6 +1122,7 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView {
let targetSize: CGSize
let animationProgress: CGFloat = (currentValue - initialHeight) / (targetHeight - initialHeight)
let scaleProgress: CGFloat
var effectiveAvatarInset = avatarInset
if currentValue < targetHeight {
initialSize = displaySize
targetSize = maximumDisplaySize
@ -1100,12 +1136,13 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView {
targetSize = initialSize
scaleProgress = isPlaying ? 1.0 : 0.0
}
effectiveAvatarInset *= (1.0 - scaleProgress)
displaySize = CGSize(width: initialSize.width + (targetSize.width - initialSize.width) * animationProgress, height: initialSize.height + (targetSize.height - initialSize.height) * animationProgress)
let (videoLayout, videoApply) = makeVideoLayout(ChatMessageBubbleContentItem(context: item.context, controllerInteraction: item.controllerInteraction, message: item.message, read: item.read, chatLocation: item.chatLocation, presentationData: item.presentationData, associatedData: item.associatedData, attributes: item.content.firstMessageAttributes, isItemPinned: item.message.tags.contains(.pinned) && !isReplyThread, isItemEdited: false), params.width - params.leftInset - params.rightInset - avatarInset, displaySize, maximumDisplaySize, scaleProgress, .free, self.appliedAutomaticDownload)
let availableContentWidth = params.width - params.leftInset - params.rightInset - layoutConstants.bubble.edgeInset * 2.0 - avatarInset - layoutConstants.bubble.contentInsets.left
let videoFrame = CGRect(origin: CGPoint(x: (incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + avatarInset + layoutConstants.bubble.contentInsets.left) : (params.width - params.rightInset - videoLayout.contentSize.width - layoutConstants.bubble.edgeInset - layoutConstants.bubble.contentInsets.left - deliveryFailedInset)), y: 0.0), size: videoLayout.contentSize)
let videoFrame = CGRect(origin: CGPoint(x: (incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + effectiveAvatarInset + layoutConstants.bubble.contentInsets.left) : (params.width - params.rightInset - videoLayout.contentSize.width - layoutConstants.bubble.edgeInset - layoutConstants.bubble.contentInsets.left - deliveryFailedInset)), y: 0.0), size: videoLayout.contentSize)
self.interactiveVideoNode.frame = videoFrame
let videoLayoutData: ChatMessageInstantVideoItemLayoutData

View File

@ -317,7 +317,7 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode {
strongSelf.videoFrame = displayVideoFrame
strongSelf.secretProgressIcon = secretProgressIcon
strongSelf.automaticDownload = automaticDownload
if let updatedInfoBackgroundImage = updatedInfoBackgroundImage {
strongSelf.infoBackgroundNode.image = updatedInfoBackgroundImage
}
@ -832,6 +832,20 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode {
}
}
func seekTo(_ position: Double) {
if let duration = self.playbackStatusNode?.duration {
self.videoNode?.seek(position * duration)
}
}
func play() {
self.videoNode?.play()
}
func pause() {
self.videoNode?.pause()
}
func playMediaWithSound() -> (action: (Double?) -> Void, soundEnabled: Bool, isVideoMessage: Bool, isUnread: Bool, badgeNode: ASDisplayNode?)? {
if let item = self.item {
var isUnconsumed = false

View File

@ -40,6 +40,14 @@ final class InstantVideoRadialStatusNode: ASDisplayNode {
private var statusDisposable: Disposable?
private var statusValuePromise = Promise<MediaPlayerStatus?>()
var duration: Double? {
if let statusValue = self.statusValue {
return statusValue.duration
} else {
return nil
}
}
var status: Signal<MediaPlayerStatus, NoError>? {
didSet {
if let status = self.status {