mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-11-07 01:10:09 +00:00
Initial document scan implementation
This commit is contained in:
parent
bdd0ef6285
commit
1e6e92c3de
@ -5715,3 +5715,5 @@ Any member of this group will be able to see messages in the channel.";
|
||||
"Stats.MessageOverview" = "Overview";
|
||||
"Stats.MessageInteractionsTitle" = "Interactions";
|
||||
"Stats.MessagePublicForwardsTitle" = "Public Shares";
|
||||
|
||||
"Camera.ScanMode" = "SCAN";
|
||||
|
||||
@ -22,6 +22,7 @@ public enum ChatListSearchItemHeaderType: Int32 {
|
||||
case chats
|
||||
case chatTypes
|
||||
case faq
|
||||
case otherSubscribers
|
||||
}
|
||||
|
||||
public final class ChatListSearchItemHeader: ListViewItemHeader {
|
||||
@ -110,6 +111,8 @@ public final class ChatListSearchItemHeaderNode: ListViewItemHeaderNode {
|
||||
self.sectionHeaderNode.title = strings.ChatList_ChatTypesSection.uppercased()
|
||||
case .faq:
|
||||
self.sectionHeaderNode.title = strings.Settings_FrequentlyAskedQuestions.uppercased()
|
||||
case .otherSubscribers:
|
||||
self.sectionHeaderNode.title = "OTHER SUBSCRIBERS"
|
||||
}
|
||||
|
||||
self.sectionHeaderNode.action = actionTitle
|
||||
@ -162,6 +165,8 @@ public final class ChatListSearchItemHeaderNode: ListViewItemHeaderNode {
|
||||
self.sectionHeaderNode.title = strings.ChatList_ChatTypesSection.uppercased()
|
||||
case .faq:
|
||||
self.sectionHeaderNode.title = strings.Settings_FrequentlyAskedQuestions.uppercased()
|
||||
case .otherSubscribers:
|
||||
self.sectionHeaderNode.title = "OTHER SUBSCRIBERS"
|
||||
}
|
||||
|
||||
self.sectionHeaderNode.action = actionTitle
|
||||
|
||||
@ -14,16 +14,16 @@ public enum ContactListActionItemHighlight {
|
||||
case alpha
|
||||
}
|
||||
|
||||
class ContactListActionItem: ListViewItem, ListViewItemWithHeader {
|
||||
public class ContactListActionItem: ListViewItem, ListViewItemWithHeader {
|
||||
let presentationData: ItemListPresentationData
|
||||
let title: String
|
||||
let icon: ContactListActionItemIcon
|
||||
let highlight: ContactListActionItemHighlight
|
||||
let clearHighlightAutomatically: Bool
|
||||
let action: () -> Void
|
||||
let header: ListViewItemHeader?
|
||||
public let header: ListViewItemHeader?
|
||||
|
||||
init(presentationData: ItemListPresentationData, title: String, icon: ContactListActionItemIcon, highlight: ContactListActionItemHighlight = .cell, clearHighlightAutomatically: Bool = true, header: ListViewItemHeader?, action: @escaping () -> Void) {
|
||||
public init(presentationData: ItemListPresentationData, title: String, icon: ContactListActionItemIcon, highlight: ContactListActionItemHighlight = .cell, clearHighlightAutomatically: Bool = true, header: ListViewItemHeader?, action: @escaping () -> Void) {
|
||||
self.presentationData = presentationData
|
||||
self.title = title
|
||||
self.icon = icon
|
||||
@ -33,7 +33,7 @@ class ContactListActionItem: ListViewItem, ListViewItemWithHeader {
|
||||
self.action = action
|
||||
}
|
||||
|
||||
func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal<Void, NoError>?, (ListViewItemApply) -> Void)) -> Void) {
|
||||
public func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal<Void, NoError>?, (ListViewItemApply) -> Void)) -> Void) {
|
||||
async {
|
||||
let node = ContactListActionItemNode()
|
||||
let (_, last, firstWithHeader) = ContactListActionItem.mergeType(item: self, previousItem: previousItem, nextItem: nextItem)
|
||||
@ -50,7 +50,7 @@ class ContactListActionItem: ListViewItem, ListViewItemWithHeader {
|
||||
}
|
||||
}
|
||||
|
||||
func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping (ListViewItemApply) -> Void) -> Void) {
|
||||
public func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping (ListViewItemApply) -> Void) -> Void) {
|
||||
Queue.mainQueue().async {
|
||||
if let nodeValue = node() as? ContactListActionItemNode {
|
||||
let makeLayout = nodeValue.asyncLayout()
|
||||
@ -68,9 +68,9 @@ class ContactListActionItem: ListViewItem, ListViewItemWithHeader {
|
||||
}
|
||||
}
|
||||
|
||||
var selectable: Bool = true
|
||||
public var selectable: Bool = true
|
||||
|
||||
func selected(listView: ListView){
|
||||
public func selected(listView: ListView){
|
||||
self.action()
|
||||
if self.clearHighlightAutomatically {
|
||||
listView.clearHighlightAnimated(true)
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -11,6 +11,7 @@
|
||||
+ (UIColor *)transparentPanelBackgroundColor;
|
||||
+ (UIColor *)transparentOverlayBackgroundColor;
|
||||
|
||||
+ (UIFont *)normalFontOfSize:(CGFloat)size;
|
||||
+ (UIFont *)regularFontOfSize:(CGFloat)size;
|
||||
+ (UIFont *)boldFontOfSize:(CGFloat)size;
|
||||
|
||||
@end
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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
|
||||
@ -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
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
24
submodules/LegacyComponents/Sources/PGRectangleDetector.h
Normal file
24
submodules/LegacyComponents/Sources/PGRectangleDetector.h
Normal 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
|
||||
|
||||
362
submodules/LegacyComponents/Sources/PGRectangleDetector.m
Normal file
362
submodules/LegacyComponents/Sources/PGRectangleDetector.m
Normal 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
|
||||
@ -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]];
|
||||
|
||||
@ -19,6 +19,7 @@
|
||||
#import <LegacyComponents/TGCameraMainPhoneView.h>
|
||||
#import <LegacyComponents/TGCameraMainTabletView.h>
|
||||
#import "TGCameraFocusCrosshairsControl.h"
|
||||
#import "TGCameraRectangleView.h"
|
||||
|
||||
#import <LegacyComponents/TGFullscreenContainerView.h>
|
||||
#import <LegacyComponents/TGPhotoEditorController.h>
|
||||
@ -52,6 +53,8 @@
|
||||
#import "TGCameraCapturedVideo.h"
|
||||
|
||||
#import "PGPhotoEditor.h"
|
||||
#import "PGRectangleDetector.h"
|
||||
#import "TGWarpedView.h"
|
||||
|
||||
#import "TGAnimationUtils.h"
|
||||
|
||||
@ -108,9 +111,7 @@ static CGPoint TGCameraControllerClampPointToScreenSize(__unused id self, __unus
|
||||
TGCameraMainView *_interfaceView;
|
||||
UIView *_overlayView;
|
||||
TGCameraFocusCrosshairsControl *_focusControl;
|
||||
|
||||
TGModernGalleryVideoView *_segmentPreviewView;
|
||||
bool _previewingSegment;
|
||||
TGCameraRectangleView *_rectangleView;
|
||||
|
||||
UISwipeGestureRecognizer *_photoSwipeGestureRecognizer;
|
||||
UISwipeGestureRecognizer *_videoSwipeGestureRecognizer;
|
||||
@ -277,6 +278,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:)];
|
||||
@ -356,7 +362,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)
|
||||
@ -474,6 +480,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;
|
||||
@ -510,9 +535,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];
|
||||
}
|
||||
@ -566,6 +593,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];
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
@ -870,9 +912,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)
|
||||
{
|
||||
@ -952,7 +992,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;
|
||||
|
||||
@ -1042,6 +1082,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)
|
||||
@ -1164,27 +1338,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;
|
||||
@ -1971,6 +2152,7 @@ static CGPoint TGCameraControllerClampPointToScreenSize(__unused id self, __unus
|
||||
self.view.userInteractionEnabled = false;
|
||||
|
||||
_focusControl.active = false;
|
||||
_rectangleView.hidden = true;
|
||||
|
||||
[UIView animateWithDuration:0.3f animations:^
|
||||
{
|
||||
@ -2238,21 +2420,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];
|
||||
}
|
||||
}
|
||||
|
||||
@ -2441,11 +2629,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 *
|
||||
@ -2566,8 +2765,14 @@ 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;
|
||||
}
|
||||
}];
|
||||
|
||||
SSignal *assetSignal = inlineSignal;
|
||||
@ -2593,12 +2798,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";
|
||||
@ -2645,11 +2851,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++;
|
||||
@ -2732,6 +2946,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;
|
||||
}
|
||||
|
||||
|
||||
@ -63,7 +63,7 @@ const CGFloat TGCameraFlashControlHeight = 44.0f;
|
||||
_autoButton.exclusiveTouch = true;
|
||||
_autoButton.hitTestEdgeInsets = UIEdgeInsetsMake(-10, -15, -10, -15);
|
||||
_autoButton.tag = PGCameraFlashModeAuto;
|
||||
_autoButton.titleLabel.font = [TGCameraInterfaceAssets normalFontOfSize:13];
|
||||
_autoButton.titleLabel.font = [TGCameraInterfaceAssets regularFontOfSize: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];
|
||||
@ -78,7 +78,7 @@ const CGFloat TGCameraFlashControlHeight = 44.0f;
|
||||
_onButton.exclusiveTouch = true;
|
||||
_onButton.hitTestEdgeInsets = UIEdgeInsetsMake(-10, -15, -10, -15);
|
||||
_onButton.tag = PGCameraFlashModeOn;
|
||||
_onButton.titleLabel.font = [TGCameraInterfaceAssets normalFontOfSize:13];
|
||||
_onButton.titleLabel.font = [TGCameraInterfaceAssets regularFontOfSize: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];
|
||||
@ -93,7 +93,7 @@ const CGFloat TGCameraFlashControlHeight = 44.0f;
|
||||
_offButton.exclusiveTouch = true;
|
||||
_offButton.hitTestEdgeInsets = UIEdgeInsetsMake(-10, -15, -10, -15);
|
||||
_offButton.tag = PGCameraFlashModeOff;
|
||||
_offButton.titleLabel.font = [TGCameraInterfaceAssets normalFontOfSize:13];
|
||||
_offButton.titleLabel.font = [TGCameraInterfaceAssets regularFontOfSize: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];
|
||||
@ -605,9 +605,7 @@ const CGFloat TGCameraFlashControlHeight = 44.0f;
|
||||
{
|
||||
CGSize size = title.size;
|
||||
CGFloat width = CGCeil(size.width);
|
||||
if (iosMajorVersion() < 7)
|
||||
width += 2;
|
||||
return CGSizeMake(width, 20);
|
||||
return CGSizeMake(width + 2.0, 20);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@ -30,17 +30,16 @@
|
||||
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
|
||||
{
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
#import "TGCameraInterfaceAssets.h"
|
||||
#import <CoreText/CoreText.h>
|
||||
|
||||
#import "LegacyComponentsInternal.h"
|
||||
|
||||
@ -11,12 +12,12 @@
|
||||
|
||||
+ (UIColor *)accentColor
|
||||
{
|
||||
return UIColorRGB(0xffcc00);
|
||||
return UIColorRGB(0xf8d74a);
|
||||
}
|
||||
|
||||
+ (UIColor *)redColor
|
||||
{
|
||||
return UIColorRGB(0xf53333);
|
||||
return UIColorRGB(0xea4e3d);
|
||||
}
|
||||
|
||||
+ (UIColor *)panelBackgroundColor
|
||||
@ -34,9 +35,54 @@
|
||||
return [UIColor colorWithWhite:0.0f alpha:0.7];
|
||||
}
|
||||
|
||||
+ (UIFont *)normalFontOfSize:(CGFloat)size
|
||||
+ (UIFont *)regularFontOfSize:(CGFloat)size
|
||||
{
|
||||
return [UIFont fontWithName:@"DINAlternate-Bold" size:size];
|
||||
if (@available(iOSApplicationExtension 13.0, iOS 13.0, *)) {
|
||||
UIFontDescriptor *descriptor = [UIFont systemFontOfSize:size].fontDescriptor;
|
||||
descriptor = [descriptor fontDescriptorWithSymbolicTraits:UIFontDescriptorTraitCondensed];
|
||||
|
||||
NSMutableArray *features = [[NSMutableArray alloc] init];
|
||||
[features addObject:@{
|
||||
UIFontFeatureTypeIdentifierKey : @(kStylisticAlternativesType),
|
||||
UIFontFeatureSelectorIdentifierKey : @(kStylisticAltThreeOnSelector)
|
||||
}];
|
||||
[features addObject:@{
|
||||
UIFontFeatureTypeIdentifierKey : @(kNumberSpacingType),
|
||||
UIFontFeatureSelectorIdentifierKey : @(kMonospacedNumbersSelector)
|
||||
}];
|
||||
|
||||
NSMutableDictionary *traits = [[NSMutableDictionary alloc] init];
|
||||
traits[UIFontWidthTrait] = @(UIFontWeightMedium);
|
||||
|
||||
descriptor = [descriptor fontDescriptorByAddingAttributes:@{ UIFontDescriptorFeatureSettingsAttribute: features}];
|
||||
|
||||
return [UIFont fontWithDescriptor:descriptor size:size];
|
||||
} else {
|
||||
return [UIFont fontWithName:@"DINAlternate-Bold" size:size];
|
||||
}
|
||||
}
|
||||
|
||||
+ (UIFont *)boldFontOfSize:(CGFloat)size
|
||||
{
|
||||
if (@available(iOSApplicationExtension 13.0, iOS 13.0, *)) {
|
||||
UIFontDescriptor *descriptor = [UIFont systemFontOfSize:size weight:UIFontWeightSemibold].fontDescriptor;
|
||||
descriptor = [descriptor fontDescriptorWithSymbolicTraits:UIFontDescriptorTraitCondensed];
|
||||
|
||||
NSMutableArray *features = [[NSMutableArray alloc] init];
|
||||
[features addObject:@{
|
||||
UIFontFeatureTypeIdentifierKey : @(kStylisticAlternativesType),
|
||||
UIFontFeatureSelectorIdentifierKey : @(kStylisticAltThreeOnSelector)
|
||||
}];
|
||||
[features addObject:@{
|
||||
UIFontFeatureTypeIdentifierKey : @(kNumberSpacingType),
|
||||
UIFontFeatureSelectorIdentifierKey : @(kMonospacedNumbersSelector)
|
||||
}];
|
||||
|
||||
descriptor = [descriptor fontDescriptorByAddingAttributes:@{ UIFontDescriptorFeatureSettingsAttribute: features}];
|
||||
return [UIFont fontWithDescriptor:descriptor size:size];
|
||||
} else {
|
||||
return [UIFont fontWithName:@"DINAlternate-Bold" size:size];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@ -19,7 +19,7 @@
|
||||
#import "TGCameraFlipButton.h"
|
||||
#import "TGCameraTimeCodeView.h"
|
||||
#import "TGCameraZoomView.h"
|
||||
#import "TGCameraSegmentsView.h"
|
||||
#import "TGCameraToastView.h"
|
||||
|
||||
#import "TGMenuView.h"
|
||||
|
||||
@ -116,8 +116,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;
|
||||
}
|
||||
@ -253,6 +253,9 @@
|
||||
_flashActiveView = [[TGCameraFlashActiveView alloc] initWithFrame:CGRectMake((frame.size.width - 40) / 2, frame.size.height - _bottomPanelHeight - 37, 40, 21)];
|
||||
[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)
|
||||
{
|
||||
@ -364,9 +367,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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -850,6 +856,8 @@
|
||||
|
||||
_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);
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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 = 1.4f;
|
||||
|
||||
_maskView = [[UIView alloc] initWithFrame:self.bounds];
|
||||
_maskView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
|
||||
@ -52,7 +49,8 @@ const CGFloat TGCameraModeControlVerticalInteritemSpace = 29.0f;
|
||||
_buttons = @
|
||||
[
|
||||
[self _createButtonForMode:PGCameraModeVideo title:TGLocalized(@"Camera.VideoMode")],
|
||||
[self _createButtonForMode:PGCameraModePhoto title:TGLocalized(@"Camera.PhotoMode")]
|
||||
[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,11 @@ 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:13] }] forState:UIControlStateNormal];
|
||||
[button setAttributedTitle:[[NSAttributedString alloc] initWithString:title attributes:@{ NSForegroundColorAttributeName: [TGCameraInterfaceAssets accentColor], NSKernAttributeName: @(_kerning), NSFontAttributeName: [TGCameraInterfaceAssets boldFontOfSize:13] }] forState:UIControlStateSelected];
|
||||
[button setAttributedTitle:[button attributedTitleForState:UIControlStateSelected] forState:UIControlStateHighlighted | UIControlStateSelected];
|
||||
[button sizeToFit];
|
||||
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;
|
||||
|
||||
14
submodules/LegacyComponents/Sources/TGCameraRectangleView.h
Normal file
14
submodules/LegacyComponents/Sources/TGCameraRectangleView.h
Normal 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
|
||||
|
||||
102
submodules/LegacyComponents/Sources/TGCameraRectangleView.m
Normal file
102
submodules/LegacyComponents/Sources/TGCameraRectangleView.m
Normal 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
|
||||
@ -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
|
||||
@ -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
|
||||
|
||||
@ -43,7 +43,7 @@
|
||||
|
||||
_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.font = [TGCameraInterfaceAssets regularFontOfSize:21];
|
||||
_timeLabel.text = @"00:00:00";
|
||||
_timeLabel.textAlignment = NSTextAlignmentCenter;
|
||||
_timeLabel.textColor = [TGCameraInterfaceAssets normalColor];
|
||||
|
||||
7
submodules/LegacyComponents/Sources/TGCameraToastView.h
Normal file
7
submodules/LegacyComponents/Sources/TGCameraToastView.h
Normal file
@ -0,0 +1,7 @@
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@interface TGCameraToastView : UIView
|
||||
|
||||
- (void)setText:(NSString *)text animated:(bool)animated;
|
||||
|
||||
@end
|
||||
70
submodules/LegacyComponents/Sources/TGCameraToastView.m
Normal file
70
submodules/LegacyComponents/Sources/TGCameraToastView.m
Normal 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
|
||||
@ -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 {
|
||||
|
||||
@ -185,8 +185,6 @@
|
||||
|
||||
- (void)cleanup
|
||||
{
|
||||
[_diskCache cleanup];
|
||||
|
||||
[[NSFileManager defaultManager] removeItemAtPath:_fullSizeResultsUrl.path error:nil];
|
||||
[[NSFileManager defaultManager] removeItemAtPath:_paintingImagesUrl.path error:nil];
|
||||
[[NSFileManager defaultManager] removeItemAtPath:_paintingDatasUrl.path error:nil];
|
||||
|
||||
@ -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
|
||||
@ -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
|
||||
@ -1223,8 +1223,10 @@ const CGFloat TGPhotoPaintStickerKeyboardSize = 260.0f;
|
||||
NSTimeInterval currentTime = editorController.currentTime;
|
||||
__strong TGPhotoStickerEntityView *strongStickerView = weakStickerView;
|
||||
if (strongStickerView != nil) {
|
||||
[strongStickerView seekTo:currentTime];
|
||||
[strongStickerView play];
|
||||
if(!isnan(currentTime)) {
|
||||
[strongStickerView seekTo:currentTime];
|
||||
[strongStickerView play];
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@ -0,0 +1,6 @@
|
||||
#import "TGPhotoEditorTabController.h"
|
||||
|
||||
@interface TGPhotoRectangleCropController : TGPhotoEditorTabController
|
||||
|
||||
@end
|
||||
|
||||
@ -0,0 +1,5 @@
|
||||
#import "TGPhotoRectangleCropController.h"
|
||||
|
||||
@implementation TGPhotoRectangleCropController
|
||||
|
||||
@end
|
||||
7
submodules/LegacyComponents/Sources/TGWarpedView.h
Normal file
7
submodules/LegacyComponents/Sources/TGWarpedView.h
Normal 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
|
||||
92
submodules/LegacyComponents/Sources/TGWarpedView.m
Normal file
92
submodules/LegacyComponents/Sources/TGWarpedView.m
Normal 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
|
||||
@ -372,10 +372,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: arc4random64())
|
||||
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: []))
|
||||
}
|
||||
}
|
||||
|
||||
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(.message(text: caption ?? "", attributes: [], mediaReference: .standalone(media: media), replyToMessageId: nil, localGroupingKey: item.groupedId))
|
||||
case let .asset(asset):
|
||||
var randomId: Int64 = 0
|
||||
|
||||
610
submodules/TelegramUI/Sources/ChannelParticipantsScreen.swift
Normal file
610
submodules/TelegramUI/Sources/ChannelParticipantsScreen.swift
Normal file
@ -0,0 +1,610 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import SwiftSignalKit
|
||||
import Display
|
||||
import TelegramCore
|
||||
import SyncCore
|
||||
import Postbox
|
||||
import TelegramPresentationData
|
||||
import TelegramStringFormatting
|
||||
import AccountContext
|
||||
import MergeLists
|
||||
import SearchBarNode
|
||||
import SearchUI
|
||||
import ContactListUI
|
||||
import ContactsPeerItem
|
||||
import ItemListUI
|
||||
import ChatListSearchItemHeader
|
||||
import PresentationDataUtils
|
||||
|
||||
class ChannelParticipantsInteraction {
|
||||
let addMember: () -> Void
|
||||
let openPeer: (PeerId) -> Void
|
||||
|
||||
init(addMember: @escaping () -> Void, openPeer: @escaping (PeerId) -> Void) {
|
||||
self.addMember = addMember
|
||||
self.openPeer = openPeer
|
||||
}
|
||||
}
|
||||
|
||||
private struct ChannelParticipantsTransaction {
|
||||
let deletions: [ListViewDeleteItem]
|
||||
let insertions: [ListViewInsertItem]
|
||||
let updates: [ListViewUpdateItem]
|
||||
let isLoading: Bool
|
||||
let isEmpty: Bool
|
||||
let crossFade: Bool
|
||||
}
|
||||
|
||||
private enum ChannelParticipantsEntryId: Hashable {
|
||||
case action(Int)
|
||||
case peer(PeerId)
|
||||
}
|
||||
|
||||
private enum ChannelParticipantsEntry: Comparable, Identifiable {
|
||||
case action(Int, PresentationTheme, ContactListAdditionalOption)
|
||||
case peer(Int, PresentationTheme, PresentationStrings, RenderedChannelParticipant, ListViewItemHeader?, Bool)
|
||||
|
||||
var stableId: ChannelParticipantsEntryId {
|
||||
switch self {
|
||||
case let .action(index, _, _):
|
||||
return .action(index)
|
||||
case let .peer(_, _ , _, participant, _, _):
|
||||
return .peer(participant.peer.id)
|
||||
}
|
||||
}
|
||||
|
||||
static func ==(lhs: ChannelParticipantsEntry, rhs: ChannelParticipantsEntry) -> Bool {
|
||||
switch lhs {
|
||||
case let .action(lhsIndex, lhsTheme, lhsOption):
|
||||
if case let .action(rhsIndex, rhsTheme, rhsOption) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsOption == rhsOption {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .peer(lhsIndex, lhsTheme, lhsStrings, lhsParticipant, lhsHeader, lhsExpanded):
|
||||
if case let .peer(rhsIndex, rhsTheme, rhsStrings, rhsParticipant, rhsHeader, rhsExpanded) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsParticipant == rhsParticipant, lhsHeader?.id == rhsHeader?.id, lhsExpanded == rhsExpanded {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static func <(lhs: ChannelParticipantsEntry, rhs: ChannelParticipantsEntry) -> Bool {
|
||||
switch lhs {
|
||||
case let .action(lhsIndex, _, _):
|
||||
switch rhs {
|
||||
case let .action(rhsIndex, _, _):
|
||||
return lhsIndex < rhsIndex
|
||||
case .peer:
|
||||
return true
|
||||
}
|
||||
case let .peer(lhsIndex, _, _, _, _, _):
|
||||
switch rhs {
|
||||
case .action:
|
||||
return false
|
||||
case let .peer(rhsIndex, _, _, _, _, _):
|
||||
return lhsIndex < rhsIndex
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func item(context: AccountContext, presentationData: PresentationData, interaction: ChannelParticipantsInteraction?) -> ListViewItem {
|
||||
switch self {
|
||||
case let .action(_, _, option):
|
||||
return ContactListActionItem(presentationData: ItemListPresentationData(presentationData), title: option.title, icon: option.icon, clearHighlightAutomatically: false, header: nil, action: option.action)
|
||||
case let .peer(_, theme, strings, participant, header, expanded):
|
||||
var status: String
|
||||
if case let .member(_, invitedAt, _, _, _) = participant.participant {
|
||||
status = "joined \(stringForFullDate(timestamp: invitedAt, strings: strings, dateTimeFormat: presentationData.dateTimeFormat))"
|
||||
} else {
|
||||
status = "owner"
|
||||
}
|
||||
return ContactsPeerItem(presentationData: ItemListPresentationData(presentationData), sortOrder: presentationData.nameSortOrder, displayOrder: presentationData.nameDisplayOrder, context: context, peerMode: .peer, peer: .peer(peer: participant.peer, chatPeer: nil), status: .custom(string: status, multiline: false), enabled: true, selection: .none, editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), index: nil, header: header, action: { _ in
|
||||
interaction?.openPeer(participant.peer.id)
|
||||
}, itemHighlighting: ContactItemHighlighting(), contextAction: nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func preparedTransaction(from fromEntries: [ChannelParticipantsEntry], to toEntries: [ChannelParticipantsEntry], isLoading: Bool, isEmpty: Bool, crossFade: Bool, context: AccountContext, presentationData: PresentationData, interaction: ChannelParticipantsInteraction?) -> ChannelParticipantsTransaction {
|
||||
let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries)
|
||||
|
||||
let deletions = deleteIndices.map { ListViewDeleteItem(index: $0, directionHint: nil) }
|
||||
let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, presentationData: presentationData, interaction: interaction), directionHint: nil) }
|
||||
let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, presentationData: presentationData, interaction: interaction), directionHint: nil) }
|
||||
|
||||
return ChannelParticipantsTransaction(deletions: deletions, insertions: insertions, updates: updates, isLoading: isLoading, isEmpty: isEmpty, crossFade: crossFade)
|
||||
}
|
||||
|
||||
private struct ChannelParticipantsState {
|
||||
var editing: Bool
|
||||
|
||||
init() {
|
||||
self.editing = false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private class ChannelParticipantsScreenNode: ViewControllerTracingNode {
|
||||
private let context: AccountContext
|
||||
private let peerId: PeerId
|
||||
private var presentationData: PresentationData
|
||||
private var presentationDataPromise: Promise<PresentationData>
|
||||
private let membersContext: PeerInfoMembersContext
|
||||
private var interaction: ChannelParticipantsInteraction?
|
||||
|
||||
private let listNode: ListView
|
||||
|
||||
var navigationBar: NavigationBar?
|
||||
private var searchDisplayController: SearchDisplayController?
|
||||
|
||||
var requestActivateSearch: (() -> Void)?
|
||||
var requestDeactivateSearch: (() -> Void)?
|
||||
var requestAddMember: (() -> Void)?
|
||||
var requestOpenPeer: ((PeerId) -> Void)?
|
||||
|
||||
var contentOffsetChanged: ((ListViewVisibleContentOffset) -> Void)?
|
||||
var contentScrollingEnded: ((ListView) -> Bool)?
|
||||
|
||||
private var disposable: Disposable?
|
||||
private var state: ChannelParticipantsState
|
||||
private let statePromise: Promise<ChannelParticipantsState>
|
||||
|
||||
private var currentEntries: [ChannelParticipantsEntry] = []
|
||||
private var enqueuedTransactions: [ChannelParticipantsTransaction] = []
|
||||
|
||||
private var containerLayout: (ContainerViewLayout, CGFloat, CGFloat)?
|
||||
|
||||
init(context: AccountContext, peerId: PeerId) {
|
||||
self.context = context
|
||||
self.peerId = peerId
|
||||
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
self.presentationDataPromise = Promise(presentationData)
|
||||
self.membersContext = PeerInfoMembersContext(context: context, peerId: peerId)
|
||||
|
||||
self.state = ChannelParticipantsState()
|
||||
self.statePromise = Promise(self.state)
|
||||
|
||||
self.listNode = ListView()
|
||||
self.listNode.backgroundColor = self.presentationData.theme.list.plainBackgroundColor
|
||||
self.listNode.verticalScrollIndicatorColor = UIColor(white: 0.0, alpha: 0.3)
|
||||
self.listNode.verticalScrollIndicatorFollowsOverscroll = true
|
||||
|
||||
super.init()
|
||||
|
||||
self.interaction = ChannelParticipantsInteraction(addMember: { [weak self] in
|
||||
self?.requestAddMember?()
|
||||
}, openPeer: { [weak self] peerId in
|
||||
self?.requestOpenPeer?(peerId)
|
||||
})
|
||||
|
||||
self.backgroundColor = self.presentationData.theme.list.plainBackgroundColor
|
||||
|
||||
self.addSubnode(self.listNode)
|
||||
|
||||
self.disposable = (combineLatest(self.presentationDataPromise.get(), self.statePromise.get(), self.membersContext.state, context.account.postbox.combinedView(keys: [.basicPeer(peerId)]))
|
||||
|> deliverOnMainQueue).start(next: { [weak self] presentationData, state, members, combinedView in
|
||||
guard let strongSelf = self, let basicPeerView = combinedView.views[.basicPeer(peerId)] as? BasicPeerView, let enclosingPeer = basicPeerView.peer else {
|
||||
return
|
||||
}
|
||||
|
||||
strongSelf.updateState(enclosingPeer: enclosingPeer, members: members, presentationData: presentationData)
|
||||
})
|
||||
}
|
||||
|
||||
private func updateState(enclosingPeer: Peer, members: PeerInfoMembersState, presentationData: PresentationData) {
|
||||
var entries: [ChannelParticipantsEntry] = []
|
||||
|
||||
entries.append(.action(0, presentationData.theme, ContactListAdditionalOption(title: "Add Subscribers", icon: .generic(UIImage(bundleImageName: "Contact List/AddMemberIcon")!), action: { [weak self] in
|
||||
// self?.interaction?
|
||||
})))
|
||||
|
||||
let contacts = ChatListSearchItemHeader(type: .contacts, theme: presentationData.theme, strings: presentationData.strings, actionTitle: nil, action: nil)
|
||||
let otherSubscribers = ChatListSearchItemHeader(type: .otherSubscribers, theme: presentationData.theme, strings: presentationData.strings, actionTitle: nil, action: nil)
|
||||
|
||||
var known: [RenderedChannelParticipant] = []
|
||||
var other: [RenderedChannelParticipant] = []
|
||||
|
||||
// Ilya Laktyushin 572439
|
||||
// Nikolay Kudashov 552564
|
||||
// Alexander Stepanov 215491
|
||||
// Michael Filimonov 438078
|
||||
// Peter Iakovlev 903523
|
||||
// Вася Бабич 763171
|
||||
// Denis Prokopov 949693
|
||||
// **Dmitrybot** 230212
|
||||
// Dmitry Moskovsky 659864346
|
||||
// Nick Kudashov jjrjrtest 76745538
|
||||
// В 102439374
|
||||
// Michael Filimonov 264037907
|
||||
// example 3735744
|
||||
// California Kai ☀️ 12549969
|
||||
// Pushtest 640083077
|
||||
|
||||
let unknown: Set<Int64> = Set([102439374, 76745538, 264037907, 3735744, 12549969, 640083077])
|
||||
|
||||
for member in members.members {
|
||||
if case let .channelMember(participant) = member {
|
||||
if case .member = participant.participant {
|
||||
if unknown.contains(participant.peer.id.toInt64()) {
|
||||
other.append(participant)
|
||||
} else {
|
||||
known.append(participant)
|
||||
}
|
||||
// entries.append(.peer(entries.count, presentationData.theme, presentationData.strings, participant, nil, false))
|
||||
// print(participant.peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) + " " + "\(participant.peer.id.toInt64())")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for participant in known.sorted(by: { lhs, rhs in
|
||||
if case let .member(_, lhsInvitedAt, _, _, _) = lhs.participant, case let .member(_, rhsInvitedAt, _, _, _) = rhs.participant {
|
||||
return lhsInvitedAt > rhsInvitedAt
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}) {
|
||||
entries.append(.peer(entries.count, presentationData.theme, presentationData.strings, participant, contacts, false))
|
||||
}
|
||||
|
||||
for participant in other.sorted(by: { lhs, rhs in
|
||||
if case let .member(_, lhsInvitedAt, _, _, _) = lhs.participant, case let .member(_, rhsInvitedAt, _, _, _) = rhs.participant {
|
||||
return lhsInvitedAt > rhsInvitedAt
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
|
||||
}) {
|
||||
entries.append(.peer(entries.count, presentationData.theme, presentationData.strings, participant, otherSubscribers, false))
|
||||
}
|
||||
|
||||
|
||||
let transaction = preparedTransaction(from: self.currentEntries, to: entries, isLoading: false, isEmpty: false, crossFade: false, context: self.context, presentationData: presentationData, interaction: self.interaction)
|
||||
// let transaction = preparedTransition(from: self.currentEntries, to: entries, context: self.context, presentationData: presentationData, enclosingPeer: enclosingPeer, action: { [weak self] member, action in
|
||||
//// self?.action(member, action)
|
||||
// })
|
||||
// self.enclosingPeer = enclosingPeer
|
||||
self.currentEntries = entries
|
||||
self.enqueueTransaction(transaction)
|
||||
}
|
||||
|
||||
func activateSearch(placeholderNode: SearchBarPlaceholderNode) {
|
||||
guard let (containerLayout, navigationBarHeight, _) = self.containerLayout, let navigationBar = self.navigationBar else {
|
||||
return
|
||||
}
|
||||
|
||||
// self.searchDisplayController = SearchDisplayController(presentationData: self.presentationData, contentNode: ChatListSearchContainerNode(context: self.context, filter: self.filter, groupId: .root, openPeer: { [weak self] peer, _ in
|
||||
// if let requestOpenPeerFromSearch = self?.requestOpenPeerFromSearch {
|
||||
// requestOpenPeerFromSearch(peer)
|
||||
// }
|
||||
// }, openDisabledPeer: { [weak self] peer in
|
||||
// self?.requestOpenDisabledPeer?(peer)
|
||||
// }, openRecentPeerOptions: { _ in
|
||||
// }, openMessage: { [weak self] peer, messageId in
|
||||
// if let requestOpenMessageFromSearch = self?.requestOpenMessageFromSearch {
|
||||
// requestOpenMessageFromSearch(peer, messageId)
|
||||
// }
|
||||
// }, addContact: nil, peerContextAction: nil, present: { _ in
|
||||
// }), cancel: { [weak self] in
|
||||
// if let requestDeactivateSearch = self?.requestDeactivateSearch {
|
||||
// requestDeactivateSearch()
|
||||
// }
|
||||
// })
|
||||
//
|
||||
// self.searchDisplayController?.containerLayoutUpdated(containerLayout, navigationBarHeight: navigationBarHeight, transition: .immediate)
|
||||
// self.searchDisplayController?.activate(insertSubnode: { [weak self, weak placeholderNode] subnode, isSearchBar in
|
||||
// if let strongSelf = self, let strongPlaceholderNode = placeholderNode {
|
||||
// if isSearchBar {
|
||||
// strongPlaceholderNode.supernode?.insertSubnode(subnode, aboveSubnode: strongPlaceholderNode)
|
||||
// } else {
|
||||
// strongSelf.insertSubnode(subnode, belowSubnode: navigationBar)
|
||||
// }
|
||||
// }
|
||||
// }, placeholder: placeholderNode)
|
||||
}
|
||||
|
||||
func deactivateSearch(placeholderNode: SearchBarPlaceholderNode) {
|
||||
if let searchDisplayController = self.searchDisplayController {
|
||||
searchDisplayController.deactivate(placeholder: placeholderNode)
|
||||
self.searchDisplayController = nil
|
||||
}
|
||||
}
|
||||
|
||||
func scrollToTop() {
|
||||
|
||||
}
|
||||
|
||||
private func enqueueTransaction(_ transaction: ChannelParticipantsTransaction) {
|
||||
self.enqueuedTransactions.append(transaction)
|
||||
|
||||
if let _ = self.containerLayout {
|
||||
while !self.enqueuedTransactions.isEmpty {
|
||||
self.dequeueTransaction()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func dequeueTransaction() {
|
||||
guard let layout = self.containerLayout, let transition = self.enqueuedTransactions.first else {
|
||||
return
|
||||
}
|
||||
self.enqueuedTransactions.remove(at: 0)
|
||||
|
||||
var options = ListViewDeleteAndInsertOptions()
|
||||
if transition.crossFade {
|
||||
options.insert(.AnimateCrossfade)
|
||||
}
|
||||
|
||||
self.listNode.transaction(deleteIndices: transition.deletions, insertIndicesAndItems: transition.insertions, updateIndicesAndItems: transition.updates, options: options, updateSizeAndInsets: nil, updateOpaqueState: nil, completion: { [weak self] _ in
|
||||
if let strongSelf = self {
|
||||
// strongSelf.activityIndicator.isHidden = !transition.isLoading
|
||||
// strongSelf.emptyResultsTextNode.isHidden = transition.isLoading || !transition.isEmpty
|
||||
//
|
||||
// strongSelf.emptyResultsTextNode.attributedText = NSAttributedString(string: strongSelf.presentationData.strings.Map_NoPlacesNearby, font: Font.regular(15.0), textColor: strongSelf.presentationData.theme.list.freeTextColor)
|
||||
//
|
||||
// strongSelf.layoutActivityIndicator(transition: .immediate)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, actualNavigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||
let isFirstLayout = self.containerLayout == nil
|
||||
self.containerLayout = (layout, navigationBarHeight, actualNavigationBarHeight)
|
||||
|
||||
var insets = layout.insets(options: [.input])
|
||||
insets.top += max(navigationBarHeight, layout.insets(options: [.statusBar]).top)
|
||||
insets.left += layout.safeInsets.left
|
||||
insets.right += layout.safeInsets.right
|
||||
|
||||
let (duration, curve) = listViewAnimationDurationAndCurve(transition: transition)
|
||||
let updateSizeAndInsets = ListViewUpdateSizeAndInsets(size: layout.size, insets: insets, duration: duration, curve: curve)
|
||||
self.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: nil, updateSizeAndInsets: updateSizeAndInsets, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in })
|
||||
|
||||
self.listNode.bounds = CGRect(x: 0.0, y: 0.0, width: layout.size.width, height: layout.size.height)
|
||||
self.listNode.position = CGPoint(x: layout.size.width / 2.0, y: layout.size.height / 2.0)
|
||||
|
||||
if isFirstLayout {
|
||||
while !self.enqueuedTransactions.isEmpty {
|
||||
self.dequeueTransaction()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class ChannelParticipantsScreen: ViewController {
|
||||
private let context: AccountContext
|
||||
private let peerId: PeerId
|
||||
private var presentationData: PresentationData
|
||||
private var presentationDataDisposable: Disposable?
|
||||
|
||||
private var controllerNode: ChannelParticipantsScreenNode {
|
||||
return self.displayNode as! ChannelParticipantsScreenNode
|
||||
}
|
||||
|
||||
let addMembersDisposable = MetaDisposable()
|
||||
|
||||
// private let _ready = Promise<Bool>()
|
||||
// override public var ready: Promise<Bool> {
|
||||
// return self._ready
|
||||
// }
|
||||
|
||||
private var searchContentNode: NavigationBarSearchContentNode?
|
||||
|
||||
public init(context: AccountContext, peerId: PeerId) {
|
||||
self.context = context
|
||||
self.peerId = peerId
|
||||
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
|
||||
super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData))
|
||||
|
||||
self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style
|
||||
|
||||
self.title = "Subscribers"
|
||||
|
||||
self.scrollToTop = { [weak self] in
|
||||
if let strongSelf = self {
|
||||
if let searchContentNode = strongSelf.searchContentNode {
|
||||
searchContentNode.updateExpansionProgress(1.0, animated: true)
|
||||
}
|
||||
strongSelf.controllerNode.scrollToTop()
|
||||
}
|
||||
}
|
||||
|
||||
self.presentationDataDisposable = (self.context.sharedContext.presentationData
|
||||
|> deliverOnMainQueue).start(next: { [weak self] presentationData in
|
||||
if let strongSelf = self {
|
||||
let previousTheme = strongSelf.presentationData.theme
|
||||
let previousStrings = strongSelf.presentationData.strings
|
||||
|
||||
strongSelf.presentationData = presentationData
|
||||
|
||||
if previousTheme !== presentationData.theme || previousStrings !== presentationData.strings {
|
||||
// strongSelf.updateThemeAndStrings()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
self.searchContentNode = NavigationBarSearchContentNode(theme: self.presentationData.theme, placeholder: self.presentationData.strings.Common_Search, activate: { [weak self] in
|
||||
self?.activateSearch()
|
||||
})
|
||||
self.navigationBar?.setContentNode(self.searchContentNode, animated: false)
|
||||
}
|
||||
|
||||
required init(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.presentationDataDisposable?.dispose()
|
||||
self.addMembersDisposable.dispose()
|
||||
}
|
||||
|
||||
override public func loadDisplayNode() {
|
||||
self.displayNode = ChannelParticipantsScreenNode(context: self.context, peerId: self.peerId)
|
||||
|
||||
self.controllerNode.navigationBar = self.navigationBar
|
||||
|
||||
self.controllerNode.requestDeactivateSearch = { [weak self] in
|
||||
self?.deactivateSearch()
|
||||
}
|
||||
|
||||
self.controllerNode.requestActivateSearch = { [weak self] in
|
||||
self?.activateSearch()
|
||||
}
|
||||
|
||||
self.controllerNode.requestAddMember = { [weak self] in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
// let disabledIds = members?.compactMap({$0.peer.id}) ?? []
|
||||
|
||||
let context = strongSelf.context
|
||||
let peerId = strongSelf.peerId
|
||||
let presentationData = strongSelf.presentationData
|
||||
|
||||
let contactsController = context.sharedContext.makeContactMultiselectionController(ContactMultiselectionControllerParams(context: context, mode: .peerSelection(searchChatList: false, searchGroups: false, searchChannels: false), options: [], filters: [.excludeSelf, .disable([])]))
|
||||
contactsController.navigationPresentation = .modal
|
||||
|
||||
strongSelf.addMembersDisposable.set((contactsController.result
|
||||
|> deliverOnMainQueue
|
||||
|> castError(AddChannelMemberError.self)
|
||||
|> mapToSignal { [weak contactsController] result -> Signal<Never, AddChannelMemberError> in
|
||||
contactsController?.displayProgress = true
|
||||
|
||||
var contacts: [ContactListPeerId] = []
|
||||
if case let .result(peerIdsValue, _) = result {
|
||||
contacts = peerIdsValue
|
||||
}
|
||||
|
||||
let signal = context.peerChannelMemberCategoriesContextsManager.addMembers(account: context.account, peerId: peerId, memberIds: contacts.compactMap({ contact -> PeerId? in
|
||||
switch contact {
|
||||
case let .peer(contactId):
|
||||
return contactId
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}))
|
||||
|
||||
return signal
|
||||
|> ignoreValues
|
||||
|> deliverOnMainQueue
|
||||
|> afterCompleted {
|
||||
contactsController?.dismiss()
|
||||
}
|
||||
}).start(error: { [weak self, weak contactsController] error in
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
let text: String
|
||||
switch error {
|
||||
case .limitExceeded:
|
||||
text = presentationData.strings.Channel_ErrorAddTooMuch
|
||||
case .tooMuchJoined:
|
||||
text = presentationData.strings.Invite_ChannelsTooMuch
|
||||
case .generic:
|
||||
text = presentationData.strings.Login_UnknownError
|
||||
case .restricted:
|
||||
text = presentationData.strings.Channel_ErrorAddBlocked
|
||||
case .notMutualContact:
|
||||
text = presentationData.strings.GroupInfo_AddUserLeftError
|
||||
case let .bot(memberId):
|
||||
let _ = (context.account.postbox.transaction { transaction in
|
||||
return transaction.getPeer(peerId)
|
||||
}
|
||||
|> deliverOnMainQueue).start(next: { peer in
|
||||
guard let peer = peer as? TelegramChannel else {
|
||||
self?.present(textAlertController(context: context, title: nil, text: presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root))
|
||||
contactsController?.dismiss()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if peer.hasPermission(.addAdmins) {
|
||||
contactsController?.displayProgress = false
|
||||
self?.present(textAlertController(context: context, title: nil, text: presentationData.strings.Channel_AddBotErrorHaveRights, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .defaultAction, title: presentationData.strings.Channel_AddBotAsAdmin, action: {
|
||||
contactsController?.dismiss()
|
||||
|
||||
// pushControllerImpl?(channelAdminController(context: context, peerId: peerId, adminId: memberId, initialParticipant: nil, updated: { _ in
|
||||
// }, upgradedToSupergroup: { _, f in f () }, transferedOwnership: { _ in }))
|
||||
})]), in: .window(.root))
|
||||
} else {
|
||||
self?.present(textAlertController(context: context, title: nil, text: presentationData.strings.Channel_AddBotErrorHaveRights, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root))
|
||||
}
|
||||
|
||||
contactsController?.dismiss()
|
||||
})
|
||||
return
|
||||
case .botDoesntSupportGroups:
|
||||
text = presentationData.strings.Channel_BotDoesntSupportGroups
|
||||
case .tooMuchBots:
|
||||
text = presentationData.strings.Channel_TooMuchBots
|
||||
}
|
||||
self?.present(textAlertController(context: context, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root))
|
||||
contactsController?.dismiss()
|
||||
}))
|
||||
|
||||
strongSelf.push(contactsController)
|
||||
}
|
||||
|
||||
self.controllerNode.requestOpenPeer = { [weak self] peerId in
|
||||
// if let strongSelf = self, let peerSelected = strongSelf.peerSelected {
|
||||
// peerSelected(peerId)
|
||||
// }
|
||||
}
|
||||
|
||||
var isProcessingContentOffsetChanged = false
|
||||
self.controllerNode.contentOffsetChanged = { [weak self] offset in
|
||||
if isProcessingContentOffsetChanged {
|
||||
return
|
||||
}
|
||||
isProcessingContentOffsetChanged = true
|
||||
if let strongSelf = self, let searchContentNode = strongSelf.searchContentNode {
|
||||
searchContentNode.updateListVisibleContentOffset(offset)
|
||||
isProcessingContentOffsetChanged = false
|
||||
}
|
||||
}
|
||||
|
||||
self.controllerNode.contentScrollingEnded = { [weak self] listView in
|
||||
if let strongSelf = self, let searchContentNode = strongSelf.searchContentNode {
|
||||
return fixNavigationSearchableListNodeScrolling(listView, searchNode: searchContentNode)
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
self.displayNodeDidLoad()
|
||||
|
||||
// self._ready.set(self.controllerNode.ready)
|
||||
}
|
||||
|
||||
private func updatePresentationData() {
|
||||
self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style
|
||||
}
|
||||
|
||||
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
||||
super.containerLayoutUpdated(layout, transition: transition)
|
||||
|
||||
self.controllerNode.containerLayoutUpdated(layout, navigationBarHeight: self.navigationInsetHeight, actualNavigationBarHeight: self.navigationHeight, transition: transition)
|
||||
}
|
||||
|
||||
private func activateSearch() {
|
||||
if self.displayNavigationBar {
|
||||
if let scrollToTop = self.scrollToTop {
|
||||
scrollToTop()
|
||||
}
|
||||
if let searchContentNode = self.searchContentNode {
|
||||
self.controllerNode.activateSearch(placeholderNode: searchContentNode.placeholderNode)
|
||||
}
|
||||
self.setDisplayNavigationBar(false, transition: .animated(duration: 0.5, curve: .spring))
|
||||
}
|
||||
}
|
||||
|
||||
private func deactivateSearch() {
|
||||
if !self.displayNavigationBar {
|
||||
self.setDisplayNavigationBar(true, transition: .animated(duration: 0.5, curve: .spring))
|
||||
if let searchContentNode = self.searchContentNode {
|
||||
self.controllerNode.deactivateSearch(placeholderNode: searchContentNode.placeholderNode)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user