Merge commit '04ba76c7e6e52c7c42afb3ad499837b892a2e072' into beta

This commit is contained in:
Ilya Laktyushin 2020-07-03 02:46:23 +03:00
commit bd39760895
54 changed files with 4911 additions and 4135 deletions

View File

@ -369,7 +369,7 @@ static const char *Api1__Serializer_Key = "Api1__Serializer";
return nil; return nil;
return [Api1_Photo photoEmptyWithPid:pid]; return [Api1_Photo photoEmptyWithPid:pid];
} copy]; } copy];
parsers[@((int32_t)0xd07504a5)] = [^id (NSData *_data, NSUInteger* _offset, __unused id metaInfo) parsers[@((int32_t)0xfb197a65)] = [^id (NSData *_data, NSUInteger* _offset, __unused id metaInfo)
{ {
NSNumber * flags = nil; NSNumber * flags = nil;
if ((flags = [Api1__Environment parseObject:_data offset:_offset implicitSignature:(int32_t)0xa8509bda metaInfo:nil]) == nil) if ((flags = [Api1__Environment parseObject:_data offset:_offset implicitSignature:(int32_t)0xa8509bda metaInfo:nil]) == nil)

View File

@ -3,7 +3,7 @@
@implementation Serialization @implementation Serialization
- (NSUInteger)currentLayer { - (NSUInteger)currentLayer {
return 115; return 116;
} }
- (id _Nullable)parseMessage:(NSData * _Nullable)data { - (id _Nullable)parseMessage:(NSData * _Nullable)data {

View File

@ -5638,9 +5638,34 @@ Any member of this group will be able to see messages in the channel.";
"Settings.ViewVideo" = "View Video"; "Settings.ViewVideo" = "View Video";
"Settings.RemoveVideo" = "Remove Video"; "Settings.RemoveVideo" = "Remove Video";
"Settings.EditProfileMedia" = "Edit";
"Conversation.Unarchive" = "Unarchive"; "Conversation.Unarchive" = "Unarchive";
"Conversation.UnarchiveDone" = "The chat was moved to your main list."; "Conversation.UnarchiveDone" = "The chat was moved to your main list.";
"ChatList.AutoarchiveSuggestion.Title" = "Hide new chats?"; "ChatList.AutoarchiveSuggestion.Title" = "Hide new chats?";
"ChatList.AutoarchiveSuggestion.Text" = "You are receiving lots of new chats from users who are not in your Contact List. Do you want to have such chats **automatically muted** and **archived**?"; "ChatList.AutoarchiveSuggestion.Text" = "You are receiving lots of new chats from users who are not in your Contact List. Do you want to have such chats **automatically muted** and **archived**?";
"ChatList.AutoarchiveSuggestion.OpenSettings" = "Go to Settings"; "ChatList.AutoarchiveSuggestion.OpenSettings" = "Go to Settings";
"SettingsSearch.Synonyms.ChatSettings.IntentsSettings" = "Siri Suggestions";
"Stats.GroupShowMoreTopPosters_0" = "Show %@ More";
"Stats.GroupShowMoreTopPosters_1" = "Show %@ More";
"Stats.GroupShowMoreTopPosters_2" = "Show %@ More";
"Stats.GroupShowMoreTopPosters_3_10" = "Show %@ More";
"Stats.GroupShowMoreTopPosters_many" = "Show %@ More";
"Stats.GroupShowMoreTopPosters_any" = "Show %@ More";
"Stats.GroupShowMoreTopAdmins_0" = "Show %@ More";
"Stats.GroupShowMoreTopAdmins_1" = "Show %@ More";
"Stats.GroupShowMoreTopAdmins_2" = "Show %@ More";
"Stats.GroupShowMoreTopAdmins_3_10" = "Show %@ More";
"Stats.GroupShowMoreTopAdmins_many" = "Show %@ More";
"Stats.GroupShowMoreTopAdmins_any" = "Show %@ More";
"Stats.GroupShowMoreTopInviters_0" = "Show %@ More";
"Stats.GroupShowMoreTopInviters_1" = "Show %@ More";
"Stats.GroupShowMoreTopInviters_2" = "Show %@ More";
"Stats.GroupShowMoreTopInviters_3_10" = "Show %@ More";
"Stats.GroupShowMoreTopInviters_many" = "Show %@ More";
"Stats.GroupShowMoreTopInviters_any" = "Show %@ More";

View File

@ -14,6 +14,7 @@ import AccountContext
import TelegramUniversalVideoContent import TelegramUniversalVideoContent
import WebsiteType import WebsiteType
import OpenInExternalAppUI import OpenInExternalAppUI
import ScreenCaptureDetection
private func tagsForMessage(_ message: Message) -> MessageTags? { private func tagsForMessage(_ message: Message) -> MessageTags? {
for media in message.media { for media in message.media {
@ -381,6 +382,8 @@ public class GalleryController: ViewController, StandalonePresentableController
private let updateVisibleDisposable = MetaDisposable() private let updateVisibleDisposable = MetaDisposable()
private var screenCaptureEventsDisposable: Disposable?
public init(context: AccountContext, source: GalleryControllerItemSource, invertItemOrder: Bool = false, streamSingleVideo: Bool = false, fromPlayingVideo: Bool = false, landscape: Bool = false, timecode: Double? = nil, synchronousLoad: Bool = false, replaceRootController: @escaping (ViewController, Promise<Bool>?) -> Void, baseNavigationController: NavigationController?, actionInteraction: GalleryControllerActionInteraction? = nil) { public init(context: AccountContext, source: GalleryControllerItemSource, invertItemOrder: Bool = false, streamSingleVideo: Bool = false, fromPlayingVideo: Bool = false, landscape: Bool = false, timecode: Double? = nil, synchronousLoad: Bool = false, replaceRootController: @escaping (ViewController, Promise<Bool>?) -> Void, baseNavigationController: NavigationController?, actionInteraction: GalleryControllerActionInteraction? = nil) {
self.context = context self.context = context
self.source = source self.source = source
@ -808,6 +811,20 @@ public class GalleryController: ViewController, StandalonePresentableController
self.blocksBackgroundWhenInOverlay = true self.blocksBackgroundWhenInOverlay = true
self.acceptsFocusWhenInOverlay = true self.acceptsFocusWhenInOverlay = true
self.isOpaqueWhenInOverlay = true self.isOpaqueWhenInOverlay = true
switch source {
case let .peerMessagesAtId(id):
if id.peerId.namespace == Namespaces.Peer.SecretChat {
self.screenCaptureEventsDisposable = (screenCaptureEvents()
|> deliverOnMainQueue).start(next: { [weak self] _ in
if let strongSelf = self, strongSelf.traceVisibility() {
let _ = addSecretChatMessageScreenshot(account: strongSelf.context.account, peerId: id.peerId).start()
}
})
}
default:
break
}
} }
required init(coder aDecoder: NSCoder) { required init(coder aDecoder: NSCoder) {
@ -822,6 +839,7 @@ public class GalleryController: ViewController, StandalonePresentableController
self.context.sharedContext.mediaManager.galleryHiddenMediaManager.removeSource(hiddenMediaManagerIndex) self.context.sharedContext.mediaManager.galleryHiddenMediaManager.removeSource(hiddenMediaManagerIndex)
} }
self.updateVisibleDisposable.dispose() self.updateVisibleDisposable.dispose()
self.screenCaptureEventsDisposable?.dispose()
} }
@objc private func donePressed() { @objc private func donePressed() {

View File

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

View File

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

View File

@ -35,6 +35,8 @@ typedef enum {
@property (nonatomic, copy) UIView *(^beginTransitionOut)(CGRect *referenceFrame, UIView **parentView); @property (nonatomic, copy) UIView *(^beginTransitionOut)(CGRect *referenceFrame, UIView **parentView);
@property (nonatomic, copy) void (^finishedTransitionOut)(bool saved); @property (nonatomic, copy) void (^finishedTransitionOut)(bool saved);
@property (nonatomic, copy) void (^onDismiss)();
@property (nonatomic, copy) void (^beginCustomTransitionOut)(CGRect, UIView *, void(^)(void)); @property (nonatomic, copy) void (^beginCustomTransitionOut)(CGRect, UIView *, void(^)(void));
@property (nonatomic, copy) SSignal *(^requestThumbnailImage)(id<TGMediaEditableItem> item); @property (nonatomic, copy) SSignal *(^requestThumbnailImage)(id<TGMediaEditableItem> item);

View File

@ -8,6 +8,7 @@
{ {
bool _dismissing; bool _dismissing;
UIView *_transitionView; UIView *_transitionView;
bool _noTransitionToSnapshot;
} }
@property (nonatomic, weak) id<TGMediaEditableItem> item; @property (nonatomic, weak) id<TGMediaEditableItem> item;
@ -43,6 +44,7 @@
- (void)prepareTransitionOutSaving:(bool)saving; - (void)prepareTransitionOutSaving:(bool)saving;
- (void)prepareForCustomTransitionOut; - (void)prepareForCustomTransitionOut;
- (void)finishCustomTransitionOut;
- (void)animateTransitionIn; - (void)animateTransitionIn;
- (CGRect)_targetFrameForTransitionInFromFrame:(CGRect)fromFrame; - (CGRect)_targetFrameForTransitionInFromFrame:(CGRect)fromFrame;
@ -71,6 +73,8 @@
- (TGPhotoEditorTab)activeTab; - (TGPhotoEditorTab)activeTab;
- (TGPhotoEditorTab)highlightedTabs; - (TGPhotoEditorTab)highlightedTabs;
- (bool)presentedForAvatarCreation;
+ (CGRect)photoContainerFrameForParentViewFrame:(CGRect)parentViewFrame toolbarLandscapeSize:(CGFloat)toolbarLandscapeSize orientation:(UIInterfaceOrientation)orientation panelSize:(CGFloat)panelSize hasOnScreenNavigation:(bool)hasOnScreenNavigation; + (CGRect)photoContainerFrameForParentViewFrame:(CGRect)parentViewFrame toolbarLandscapeSize:(CGFloat)toolbarLandscapeSize orientation:(UIInterfaceOrientation)orientation panelSize:(CGFloat)panelSize hasOnScreenNavigation:(bool)hasOnScreenNavigation;
+ (TGPhotoEditorTab)highlightedButtonsForEditorValues:(id<TGMediaEditAdjustments>)editorValues forAvatar:(bool)forAvatar; + (TGPhotoEditorTab)highlightedButtonsForEditorValues:(id<TGMediaEditAdjustments>)editorValues forAvatar:(bool)forAvatar;

View File

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

View File

@ -194,11 +194,11 @@ NSString *const kGPUImageThreeInputTextureVertexShaderString = SHADER_STRING
{ {
inputRotation = newInputRotation; inputRotation = newInputRotation;
} }
else if (textureIndex == 1) else if (textureIndex == 1 && !self.rotateOnlyFirstTexture)
{ {
inputRotation2 = newInputRotation; inputRotation2 = newInputRotation;
} }
else else if (textureIndex == 2 && !self.rotateOnlyFirstTexture)
{ {
inputRotation3 = newInputRotation; inputRotation3 = newInputRotation;
} }

View File

@ -15,6 +15,8 @@ extern NSString *const kGPUImageTwoInputTextureVertexShaderString;
BOOL firstFrameCheckDisabled, secondFrameCheckDisabled; BOOL firstFrameCheckDisabled, secondFrameCheckDisabled;
} }
@property (nonatomic, assign) bool rotateOnlyFirstTexture;
- (void)disableFirstFrameCheck; - (void)disableFirstFrameCheck;
- (void)disableSecondFrameCheck; - (void)disableSecondFrameCheck;

View File

@ -176,7 +176,7 @@ NSString *const kGPUImageTwoInputTextureVertexShaderString = SHADER_STRING
{ {
inputRotation = newInputRotation; inputRotation = newInputRotation;
} }
else else if (textureIndex == 1 && !_rotateOnlyFirstTexture)
{ {
inputRotation2 = newInputRotation; inputRotation2 = newInputRotation;
} }

View File

@ -179,13 +179,16 @@
- (void)updatePassParameters - (void)updatePassParameters
{ {
PGBlurToolValue *value = (PGBlurToolValue *)self.displayValue; PGBlurToolValue *value = (PGBlurToolValue *)self.displayValue;
PGPhotoBlurPass *blurPass = (PGPhotoBlurPass *)_pass; PGPhotoBlurPass *blurPass = (PGPhotoBlurPass *)_pass;
if ([value isKindOfClass:[PGBlurToolValue class]]) {
blurPass.type = value.type; blurPass.type = value.type;
blurPass.size = value.size; blurPass.size = value.size;
blurPass.point = value.point; blurPass.point = value.point;
blurPass.angle = value.angle; blurPass.angle = value.angle;
blurPass.falloff = value.falloff; blurPass.falloff = value.falloff;
} else {
blurPass.type = PGBlurToolTypeNone;
}
} }
- (bool)shouldBeSkipped - (bool)shouldBeSkipped

View File

@ -46,6 +46,8 @@
- (void)setPlayerItem:(AVPlayerItem *)playerItem forCropRect:(CGRect)cropRect cropRotation:(CGFloat)cropRotation cropOrientation:(UIImageOrientation)cropOrientation cropMirrored:(bool)cropMirrored; - (void)setPlayerItem:(AVPlayerItem *)playerItem forCropRect:(CGRect)cropRect cropRotation:(CGFloat)cropRotation cropOrientation:(UIImageOrientation)cropOrientation cropMirrored:(bool)cropMirrored;
- (void)setCIImage:(CIImage *)ciImage; - (void)setCIImage:(CIImage *)ciImage;
- (void)updateProcessChain:(bool)force;
- (void)processAnimated:(bool)animated completion:(void (^)(void))completion; - (void)processAnimated:(bool)animated completion:(void (^)(void))completion;
- (void)reprocess; - (void)reprocess;

View File

@ -383,6 +383,10 @@
} }
- (void)updateProcessChain { - (void)updateProcessChain {
[self updateProcessChain:false];
}
- (void)updateProcessChain:(bool)force {
[GPUImageFramebuffer setMark:self.forVideo]; [GPUImageFramebuffer setMark:self.forVideo];
NSMutableArray *processChain = [NSMutableArray array]; NSMutableArray *processChain = [NSMutableArray array];
@ -398,7 +402,7 @@
TGPhotoEditorPreviewView *previewOutput = self.previewOutput; TGPhotoEditorPreviewView *previewOutput = self.previewOutput;
if (![_currentProcessChain isEqualToArray:processChain]) if (![_currentProcessChain isEqualToArray:processChain] || force)
{ {
[_currentInput removeAllTargets]; [_currentInput removeAllTargets];
[_cropFilter removeAllTargets]; [_cropFilter removeAllTargets];
@ -485,7 +489,6 @@
} }
} }
if (_histogramGenerator != nil && !self.standalone) { if (_histogramGenerator != nil && !self.standalone) {
[_finalFilter addTarget:_histogramGenerator]; [_finalFilter addTarget:_histogramGenerator];
} }

View File

@ -2217,8 +2217,12 @@ static CGPoint TGCameraControllerClampPointToScreenSize(__unused id self, __unus
} }
else if (gestureRecognizer == _videoSwipeGestureRecognizer) else if (gestureRecognizer == _videoSwipeGestureRecognizer)
{ {
if (_intent == TGCameraControllerAvatarIntent) {
newMode = PGCameraModeSquareVideo;
} else {
newMode = PGCameraModeVideo; newMode = PGCameraModeVideo;
} }
}
if (newMode != PGCameraModeUndefined && _camera.cameraMode != newMode) if (newMode != PGCameraModeUndefined && _camera.cameraMode != newMode)
{ {

View File

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

View File

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

View File

@ -48,6 +48,8 @@
- (CGPoint)scrubberPositionForPosition:(NSTimeInterval)position; - (CGPoint)scrubberPositionForPosition:(NSTimeInterval)position;
- (void)_updateScrubberAnimationsAndResetCurrentPosition:(bool)resetCurrentPosition;
@end @end
@protocol TGMediaPickerGalleryVideoScrubberDelegate <NSObject> @protocol TGMediaPickerGalleryVideoScrubberDelegate <NSObject>

View File

@ -983,18 +983,13 @@ typedef enum
CGPoint point = [self _scrubberPositionForPosition:_value duration:_duration zoomedIn:zoomedIn]; CGPoint point = [self _scrubberPositionForPosition:_value duration:_duration zoomedIn:zoomedIn];
CGRect frame = CGRectMake(CGFloor(point.x) - _scrubberHandle.frame.size.width / 2, _scrubberHandle.frame.origin.y, _scrubberHandle.frame.size.width, _scrubberHandle.frame.size.height); CGRect frame = CGRectMake(CGFloor(point.x) - _scrubberHandle.frame.size.width / 2, _scrubberHandle.frame.origin.y, _scrubberHandle.frame.size.width, _scrubberHandle.frame.size.height);
CGPoint dotPoint = [self _dotPositionForPosition:_value duration:_duration];
CGRect dotFrame = CGRectMake(CGFloor(dotPoint.x) - _dotHandle.frame.size.width / 2, _dotHandle.frame.origin.y, _dotHandle.frame.size.width, _dotHandle.frame.size.height);
if (_trimStartValue > DBL_EPSILON && fabs(_value - _trimStartValue) < 0.01) if (_trimStartValue > DBL_EPSILON && fabs(_value - _trimStartValue) < 0.01)
{ {
frame = CGRectMake(_trimView.frame.origin.x + [self _scrubbingRectZoomedIn:zoomedIn].origin.x, _scrubberHandle.frame.origin.y, _scrubberHandle.frame.size.width, _scrubberHandle.frame.size.height); frame = CGRectMake(_trimView.frame.origin.x + [self _scrubbingRectZoomedIn:zoomedIn].origin.x, _scrubberHandle.frame.origin.y, _scrubberHandle.frame.size.width, _scrubberHandle.frame.size.height);
dotFrame = CGRectMake(_trimView.frame.origin.x + [self _scrubbingRectZoomedIn:false].origin.x, _dotHandle.frame.origin.y, _dotHandle.frame.size.width, _dotHandle.frame.size.height);
} }
else if (fabs(_value - _trimEndValue) < 0.01) else if (fabs(_value - _trimEndValue) < 0.01)
{ {
frame = CGRectMake(_trimView.frame.origin.x + _trimView.frame.size.width - [self _scrubbingRectZoomedIn:zoomedIn].origin.x - _scrubberHandle.frame.size.width, _scrubberHandle.frame.origin.y, _scrubberHandle.frame.size.width, _scrubberHandle.frame.size.height); frame = CGRectMake(_trimView.frame.origin.x + _trimView.frame.size.width - [self _scrubbingRectZoomedIn:zoomedIn].origin.x - _scrubberHandle.frame.size.width, _scrubberHandle.frame.origin.y, _scrubberHandle.frame.size.width, _scrubberHandle.frame.size.height);
dotFrame = CGRectMake(_trimView.frame.origin.x + _trimView.frame.size.width - [self _scrubbingRectZoomedIn:false].origin.x - _dotHandle.frame.size.width, _dotHandle.frame.origin.y, _dotHandle.frame.size.width, _dotHandle.frame.size.height);
} }
if (_isPlaying) if (_isPlaying)
@ -1023,8 +1018,6 @@ typedef enum
[self removeHandleAnimation]; [self removeHandleAnimation];
_scrubberHandle.frame = frame; _scrubberHandle.frame = frame;
} }
_dotHandle.frame = dotFrame;
} }
- (void)addHandleAnimationFromFrame:(CGRect)fromFrame toFrame:(CGRect)toFrame duration:(NSTimeInterval)duration - (void)addHandleAnimationFromFrame:(CGRect)fromFrame toFrame:(CGRect)toFrame duration:(NSTimeInterval)duration

View File

@ -331,7 +331,7 @@
+ (AVAssetReaderVideoCompositionOutput *)setupVideoCompositionOutputWithAVAsset:(AVAsset *)avAsset image:(UIImage *)image composition:(AVMutableComposition *)composition videoTrack:(AVAssetTrack *)videoTrack preset:(TGMediaVideoConversionPreset)preset entityRenderer:(id<TGPhotoPaintEntityRenderer>)entityRenderer adjustments:(TGMediaVideoEditAdjustments *)adjustments timeRange:(CMTimeRange)timeRange outputSettings:(NSDictionary **)outputSettings dimensions:(CGSize *)dimensions conversionContext:(SAtomic *)conversionContext + (AVAssetReaderVideoCompositionOutput *)setupVideoCompositionOutputWithAVAsset:(AVAsset *)avAsset image:(UIImage *)image composition:(AVMutableComposition *)composition videoTrack:(AVAssetTrack *)videoTrack preset:(TGMediaVideoConversionPreset)preset entityRenderer:(id<TGPhotoPaintEntityRenderer>)entityRenderer adjustments:(TGMediaVideoEditAdjustments *)adjustments timeRange:(CMTimeRange)timeRange outputSettings:(NSDictionary **)outputSettings dimensions:(CGSize *)dimensions conversionContext:(SAtomic *)conversionContext
{ {
CGSize transformedSize = CGRectApplyAffineTransform((CGRect){CGPointZero, videoTrack.naturalSize}, videoTrack.preferredTransform).size;; CGSize transformedSize = CGRectApplyAffineTransform((CGRect){CGPointZero, videoTrack.naturalSize}, videoTrack.preferredTransform).size;
CGRect transformedRect = CGRectMake(0, 0, transformedSize.width, transformedSize.height); CGRect transformedRect = CGRectMake(0, 0, transformedSize.width, transformedSize.height);
if (CGSizeEqualToSize(transformedRect.size, CGSizeZero)) if (CGSizeEqualToSize(transformedRect.size, CGSizeZero))
transformedRect = CGRectMake(0, 0, videoTrack.naturalSize.width, videoTrack.naturalSize.height); transformedRect = CGRectMake(0, 0, videoTrack.naturalSize.width, videoTrack.naturalSize.height);
@ -351,6 +351,10 @@
if (TGOrientationIsSideward(adjustments.cropOrientation, NULL)) if (TGOrientationIsSideward(adjustments.cropOrientation, NULL))
outputDimensions = CGSizeMake(outputDimensions.height, outputDimensions.width); outputDimensions = CGSizeMake(outputDimensions.height, outputDimensions.width);
if ((preset == TGMediaVideoConversionPresetProfile || preset == TGMediaVideoConversionPresetProfileHigh || preset == TGMediaVideoConversionPresetProfileVeryHigh) && MIN(outputDimensions.width, outputDimensions.height) < 160.0) {
outputDimensions = CGSizeMake(160.0, 160.0);
}
AVMutableCompositionTrack *compositionTrack = [composition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid]; AVMutableCompositionTrack *compositionTrack = [composition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid];
[compositionTrack insertTimeRange:timeRange ofTrack:videoTrack atTime:kCMTimeZero error:NULL]; [compositionTrack insertTimeRange:timeRange ofTrack:videoTrack atTime:kCMTimeZero error:NULL];

View File

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

View File

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

View File

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

View File

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

View File

@ -24,6 +24,7 @@ const CGFloat TGPhotoAvatarPreviewLandscapePanelSize = TGPhotoAvatarPreviewPanel
@interface TGPhotoAvatarPreviewController () @interface TGPhotoAvatarPreviewController ()
{ {
bool _dismissingToCamera;
bool _appeared; bool _appeared;
UIImage *_imagePendingLoad; UIImage *_imagePendingLoad;
UIView *_snapshotView; UIView *_snapshotView;
@ -31,18 +32,13 @@ const CGFloat TGPhotoAvatarPreviewLandscapePanelSize = TGPhotoAvatarPreviewPanel
UIView *_wrapperView; UIView *_wrapperView;
TGPhotoAvatarCropView *_cropView; __weak TGPhotoAvatarCropView *_cropView;
PGPhotoEditorView *_fullPreviewView;
UIView *_portraitToolsWrapperView; UIView *_portraitToolsWrapperView;
UIView *_landscapeToolsWrapperView; UIView *_landscapeToolsWrapperView;
UIView *_portraitWrapperBackgroundView; UIView *_portraitWrapperBackgroundView;
UIView *_landscapeWrapperBackgroundView; UIView *_landscapeWrapperBackgroundView;
TGMediaPickerGalleryVideoScrubber *_scrubberView;
UIView *_dotImageView;
UIView *_videoAreaView;
UIView *_flashView;
UIView *_portraitToolControlView; UIView *_portraitToolControlView;
UIView *_landscapeToolControlView; UIView *_landscapeToolControlView;
UILabel *_coverLabel; UILabel *_coverLabel;
@ -57,21 +53,20 @@ const CGFloat TGPhotoAvatarPreviewLandscapePanelSize = TGPhotoAvatarPreviewPanel
@implementation TGPhotoAvatarPreviewController @implementation TGPhotoAvatarPreviewController
- (instancetype)initWithContext:(id<LegacyComponentsContext>)context photoEditor:(PGPhotoEditor *)photoEditor previewView:(TGPhotoEditorPreviewView *)previewView scrubberView:(TGMediaPickerGalleryVideoScrubber *)scrubberView dotImageView:(UIView *)dotImageView fullPreviewView:(PGPhotoEditorView *)fullPreviewView - (instancetype)initWithContext:(id<LegacyComponentsContext>)context photoEditor:(PGPhotoEditor *)photoEditor previewView:(TGPhotoEditorPreviewView *)previewView {
{
self = [super initWithContext:context]; self = [super initWithContext:context];
if (self != nil) if (self != nil)
{ {
self.photoEditor = photoEditor; self.photoEditor = photoEditor;
self.previewView = previewView; self.previewView = previewView;
_fullPreviewView = fullPreviewView;
_scrubberView = scrubberView;
_dotImageView = dotImageView;
} }
return self; return self;
} }
- (void)dealloc {
NSLog(@"");
}
- (void)loadView - (void)loadView
{ {
[super loadView]; [super loadView];
@ -89,7 +84,7 @@ const CGFloat TGPhotoAvatarPreviewLandscapePanelSize = TGPhotoAvatarPreviewPanel
if (strongSelf == nil) if (strongSelf == nil)
return; return;
self.controlVideoPlayback(false); strongSelf.controlVideoPlayback(false);
}; };
void(^interactionEnded)(void) = ^ void(^interactionEnded)(void) = ^
{ {
@ -100,11 +95,12 @@ const CGFloat TGPhotoAvatarPreviewLandscapePanelSize = TGPhotoAvatarPreviewPanel
if ([strongSelf shouldAutorotate]) if ([strongSelf shouldAutorotate])
[TGViewController attemptAutorotation]; [TGViewController attemptAutorotation];
self.controlVideoPlayback(true); strongSelf.controlVideoPlayback(true);
}; };
PGPhotoEditor *photoEditor = self.photoEditor; PGPhotoEditor *photoEditor = self.photoEditor;
_cropView = [[TGPhotoAvatarCropView alloc] initWithOriginalSize:photoEditor.originalSize screenSize:[self referenceViewSize] fullPreviewView:_fullPreviewView]; TGPhotoAvatarCropView *cropView = [[TGPhotoAvatarCropView alloc] initWithOriginalSize:photoEditor.originalSize screenSize:[self referenceViewSize] fullPreviewView:_fullPreviewView];
_cropView = cropView;
[_cropView setCropRect:photoEditor.cropRect]; [_cropView setCropRect:photoEditor.cropRect];
[_cropView setCropOrientation:photoEditor.cropOrientation]; [_cropView setCropOrientation:photoEditor.cropOrientation];
[_cropView setCropMirrored:photoEditor.cropMirrored]; [_cropView setCropMirrored:photoEditor.cropMirrored];
@ -141,7 +137,7 @@ const CGFloat TGPhotoAvatarPreviewLandscapePanelSize = TGPhotoAvatarPreviewPanel
} }
_cropView.interactionBegan = interactionBegan; _cropView.interactionBegan = interactionBegan;
_cropView.interactionEnded = interactionEnded; _cropView.interactionEnded = interactionEnded;
[_wrapperView addSubview:_cropView]; [_wrapperView addSubview:cropView];
_portraitToolsWrapperView = [[UIView alloc] initWithFrame:CGRectZero]; _portraitToolsWrapperView = [[UIView alloc] initWithFrame:CGRectZero];
[_wrapperView addSubview:_portraitToolsWrapperView]; [_wrapperView addSubview:_portraitToolsWrapperView];
@ -162,17 +158,8 @@ const CGFloat TGPhotoAvatarPreviewLandscapePanelSize = TGPhotoAvatarPreviewPanel
_landscapeWrapperBackgroundView.userInteractionEnabled = false; _landscapeWrapperBackgroundView.userInteractionEnabled = false;
[_landscapeToolsWrapperView addSubview:_landscapeWrapperBackgroundView]; [_landscapeToolsWrapperView addSubview:_landscapeWrapperBackgroundView];
_videoAreaView = [[UIView alloc] init];
[self.view insertSubview:_videoAreaView belowSubview:_wrapperView];
[_portraitToolsWrapperView addSubview:_scrubberView]; [_portraitToolsWrapperView addSubview:_scrubberView];
_flashView = [[UIView alloc] init];
_flashView.alpha = 0.0;
_flashView.backgroundColor = [UIColor whiteColor];
_flashView.userInteractionEnabled = false;
[_videoAreaView addSubview:_flashView];
_coverLabel = [[UILabel alloc] init]; _coverLabel = [[UILabel alloc] init];
_coverLabel.alpha = 0.7f; _coverLabel.alpha = 0.7f;
_coverLabel.backgroundColor = [UIColor clearColor]; _coverLabel.backgroundColor = [UIColor clearColor];
@ -182,7 +169,6 @@ const CGFloat TGPhotoAvatarPreviewLandscapePanelSize = TGPhotoAvatarPreviewPanel
[_coverLabel sizeToFit]; [_coverLabel sizeToFit];
[_portraitToolsWrapperView addSubview:_coverLabel]; [_portraitToolsWrapperView addSubview:_coverLabel];
_dotImageView.alpha = 1.0f;
[_wrapperView addSubview:_dotImageView]; [_wrapperView addSubview:_dotImageView];
} }
} }
@ -259,6 +245,14 @@ const CGFloat TGPhotoAvatarPreviewLandscapePanelSize = TGPhotoAvatarPreviewPanel
#pragma mark - Transition #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];
if (self.initialAppearance && self.fromCamera)
[self.view insertSubview:_transitionView belowSubview:_wrapperView];
}
- (void)transitionIn - (void)transitionIn
{ {
if (_portraitToolsWrapperView.frame.size.height < FLT_EPSILON) { if (_portraitToolsWrapperView.frame.size.height < FLT_EPSILON) {
@ -275,6 +269,8 @@ const CGFloat TGPhotoAvatarPreviewLandscapePanelSize = TGPhotoAvatarPreviewPanel
{ {
_portraitToolsWrapperView.alpha = 1.0f; _portraitToolsWrapperView.alpha = 1.0f;
_landscapeToolsWrapperView.alpha = 1.0f; _landscapeToolsWrapperView.alpha = 1.0f;
_dotImageView.alpha = 1.0f;
_dotMarkerView.alpha = 1.0f;
} completion:^(BOOL finished) { } completion:^(BOOL finished) {
_scrubberView.layer.shouldRasterize = false; _scrubberView.layer.shouldRasterize = false;
}]; }];
@ -320,14 +316,30 @@ const CGFloat TGPhotoAvatarPreviewLandscapePanelSize = TGPhotoAvatarPreviewPanel
} }
} }
- (void)transitionOutSaving:(bool)saving completion:(void (^)(void))completion
{
if (!saving && self.fromCamera) {
_dismissingToCamera = true;
_noTransitionToSnapshot = true;
_fullPreviewView.frame = [_fullPreviewView.superview convertRect:_fullPreviewView.frame toView:self.view];
[self.view insertSubview:_fullPreviewView belowSubview:_wrapperView];
[_cropView hideImageForCustomTransition];
}
[super transitionOutSaving:saving completion:completion];
}
- (void)transitionOutSwitching:(bool)switching completion:(void (^)(void))completion - (void)transitionOutSwitching:(bool)switching completion:(void (^)(void))completion
{ {
if (switching) { if (switching) {
_dismissing = true; _dismissing = true;
} }
if (!self.fromCamera || switching) {
[self.view insertSubview:_previewView belowSubview:_wrapperView]; [self.view insertSubview:_previewView belowSubview:_wrapperView];
_previewView.frame = [_wrapperView convertRect:_cropView.frame toView:self.view]; _previewView.frame = [_wrapperView convertRect:_cropView.frame toView:self.view];
}
[_cropView animateTransitionOut]; [_cropView animateTransitionOut];
@ -364,6 +376,7 @@ const CGFloat TGPhotoAvatarPreviewLandscapePanelSize = TGPhotoAvatarPreviewPanel
_cropView.transform = CGAffineTransformMakeScale(targetCropViewScale, targetCropViewScale); _cropView.transform = CGAffineTransformMakeScale(targetCropViewScale, targetCropViewScale);
} completion:^(__unused BOOL finished) } completion:^(__unused BOOL finished)
{ {
[_cropView removeFromSuperview];
_previewView.alpha = 1.0; _previewView.alpha = 1.0;
if (self.finishedTransitionOut != nil) if (self.finishedTransitionOut != nil)
self.finishedTransitionOut(); self.finishedTransitionOut();
@ -371,6 +384,9 @@ const CGFloat TGPhotoAvatarPreviewLandscapePanelSize = TGPhotoAvatarPreviewPanel
if (completion != nil) if (completion != nil)
completion(); completion();
}]; }];
} else {
if (self.fromCamera)
_previewView.alpha = 0.0f;
} }
switch (self.effectiveOrientation) switch (self.effectiveOrientation)
@ -413,9 +429,11 @@ const CGFloat TGPhotoAvatarPreviewLandscapePanelSize = TGPhotoAvatarPreviewPanel
_portraitToolsWrapperView.alpha = 0.0f; _portraitToolsWrapperView.alpha = 0.0f;
_landscapeToolsWrapperView.alpha = 0.0f; _landscapeToolsWrapperView.alpha = 0.0f;
_dotImageView.alpha = 0.0f; _dotImageView.alpha = 0.0f;
_dotMarkerView.alpha = 0.0f;
} completion:^(__unused BOOL finished) } completion:^(__unused BOOL finished)
{ {
if (!switching) { if (!switching) {
[_cropView removeFromSuperview];
if (completion != nil) if (completion != nil)
completion(); completion();
} }
@ -426,8 +444,12 @@ const CGFloat TGPhotoAvatarPreviewLandscapePanelSize = TGPhotoAvatarPreviewPanel
{ {
_dismissing = true; _dismissing = true;
TGPhotoEditorPreviewView *previewView = self.previewView; UIView *previewView = self.previewView;
[previewView prepareForTransitionOut]; if (_dismissingToCamera) {
previewView = _fullPreviewView;
} else {
[self.previewView prepareForTransitionOut];
}
UIView *snapshotView = nil; UIView *snapshotView = nil;
POPSpringAnimation *snapshotAnimation = nil; POPSpringAnimation *snapshotAnimation = nil;
@ -488,6 +510,7 @@ const CGFloat TGPhotoAvatarPreviewLandscapePanelSize = TGPhotoAvatarPreviewPanel
previewView.hidden = true; previewView.hidden = true;
[previewView performTransitionInIfNeeded]; [previewView performTransitionInIfNeeded];
if (!self.initialAppearance)
[_cropView openCurtains]; [_cropView openCurtains];
[_cropView transitionInFinishedFromCamera:(self.fromCamera && self.initialAppearance)]; [_cropView transitionInFinishedFromCamera:(self.fromCamera && self.initialAppearance)];
@ -500,8 +523,10 @@ const CGFloat TGPhotoAvatarPreviewLandscapePanelSize = TGPhotoAvatarPreviewPanel
// [_cropView animateTransitionIn]; // [_cropView animateTransitionIn];
[_cropView transitionInFinishedFromCamera:true]; [_cropView transitionInFinishedFromCamera:true];
if (self.finishedTransitionIn) {
self.finishedTransitionIn(); self.finishedTransitionIn();
self.finishedTransitionIn = nil; self.finishedTransitionIn = nil;
}
} }
- (void)prepareForCustomTransitionOut - (void)prepareForCustomTransitionOut
@ -514,20 +539,31 @@ const CGFloat TGPhotoAvatarPreviewLandscapePanelSize = TGPhotoAvatarPreviewPanel
{ {
_portraitToolsWrapperView.alpha = 0.0f; _portraitToolsWrapperView.alpha = 0.0f;
_landscapeToolsWrapperView.alpha = 0.0f; _landscapeToolsWrapperView.alpha = 0.0f;
_videoAreaView.alpha = 0.0f;
_dotImageView.alpha = 0.0f; _dotImageView.alpha = 0.0f;
} completion:nil]; } completion:nil];
} }
- (void)finishCustomTransitionOut
{
[_cropView removeFromSuperview];
}
- (CGRect)transitionOutReferenceFrame - (CGRect)transitionOutReferenceFrame
{ {
TGPhotoEditorPreviewView *previewView = _previewView; if (_dismissingToCamera) {
return previewView.frame; return [_fullPreviewView.superview convertRect:_fullPreviewView.frame toView:self.view];
} else {
return [_wrapperView convertRect:_cropView.frame toView:self.view];
}
} }
- (UIView *)transitionOutReferenceView - (UIView *)transitionOutReferenceView
{ {
if (_dismissingToCamera) {
return _fullPreviewView;
} else {
return _previewView; return _previewView;
}
} }
- (id)currentResultRepresentation - (id)currentResultRepresentation
@ -698,12 +734,6 @@ const CGFloat TGPhotoAvatarPreviewLandscapePanelSize = TGPhotoAvatarPreviewPanel
CGRect containerFrame = [TGPhotoAvatarPreviewController photoContainerFrameForParentViewFrame:CGRectMake(0, 0, referenceSize.width, referenceSize.height) toolbarLandscapeSize:self.toolbarLandscapeSize orientation:self.effectiveOrientation panelSize:0 hasOnScreenNavigation:self.hasOnScreenNavigation]; CGRect containerFrame = [TGPhotoAvatarPreviewController photoContainerFrameForParentViewFrame:CGRectMake(0, 0, referenceSize.width, referenceSize.height) toolbarLandscapeSize:self.toolbarLandscapeSize orientation:self.effectiveOrientation panelSize:0 hasOnScreenNavigation:self.hasOnScreenNavigation];
CGSize fittedSize = TGScaleToSize(photoEditor.rotatedCropSize, containerFrame.size); CGSize fittedSize = TGScaleToSize(photoEditor.rotatedCropSize, containerFrame.size);
previewView.frame = CGRectMake(containerFrame.origin.x + (containerFrame.size.width - fittedSize.width) / 2, containerFrame.origin.y + (containerFrame.size.height - fittedSize.height) / 2, fittedSize.width, fittedSize.height); previewView.frame = CGRectMake(containerFrame.origin.x + (containerFrame.size.width - fittedSize.width) / 2, containerFrame.origin.y + (containerFrame.size.height - fittedSize.height) / 2, fittedSize.width, fittedSize.height);
[UIView performWithoutAnimation:^
{
_videoAreaView.frame = _previewView.frame;
_flashView.frame = _videoAreaView.bounds;
}];
} }
- (void)updateLayout:(UIInterfaceOrientation)orientation - (void)updateLayout:(UIInterfaceOrientation)orientation
@ -789,19 +819,15 @@ const CGFloat TGPhotoAvatarPreviewLandscapePanelSize = TGPhotoAvatarPreviewPanel
- (void)beginScrubbing:(bool)flash - (void)beginScrubbing:(bool)flash
{ {
if (flash) if (flash) {
_coverLabel.alpha = 1.0f; _coverLabel.alpha = 1.0f;
}
} }
- (void)endScrubbing:(bool)flash completion:(bool (^)(void))completion - (void)endScrubbing:(bool)flash completion:(bool (^)(void))completion
{ {
if (flash) { if (flash) {
[UIView animateWithDuration:0.12 animations:^{ [_cropView flash:^{
_flashView.alpha = 1.0f;
} completion:^(BOOL finished) {
[UIView animateWithDuration:0.2 animations:^{
_flashView.alpha = 0.0f;
} completion:^(BOOL finished) {
TGDispatchAfter(1.0, dispatch_get_main_queue(), ^{ TGDispatchAfter(1.0, dispatch_get_main_queue(), ^{
if (completion()) { if (completion()) {
[UIView animateWithDuration:0.2 animations:^{ [UIView animateWithDuration:0.2 animations:^{
@ -812,7 +838,6 @@ const CGFloat TGPhotoAvatarPreviewLandscapePanelSize = TGPhotoAvatarPreviewPanel
} }
}); });
}]; }];
}];
} else { } else {
TGDispatchAfter(1.32, dispatch_get_main_queue(), ^{ TGDispatchAfter(1.32, dispatch_get_main_queue(), ^{
if (completion()) { if (completion()) {

View File

@ -59,6 +59,8 @@
TGPhotoEditorTab _currentTab; TGPhotoEditorTab _currentTab;
TGPhotoEditorTabController *_currentTabController; TGPhotoEditorTabController *_currentTabController;
TGMediaEditingContext *_standaloneEditingContext;
UIView *_backgroundView; UIView *_backgroundView;
UIView *_containerView; UIView *_containerView;
UIView *_wrapperView; UIView *_wrapperView;
@ -140,6 +142,7 @@
{ {
_context = context; _context = context;
_actionHandle = [[ASHandle alloc] initWithDelegate:self releaseOnMainThread:true]; _actionHandle = [[ASHandle alloc] initWithDelegate:self releaseOnMainThread:true];
_standaloneEditingContext = [[TGMediaEditingContext alloc] init];
self.automaticallyManageScrollViewInsets = false; self.automaticallyManageScrollViewInsets = false;
self.autoManageStatusBarBackground = false; self.autoManageStatusBarBackground = false;
@ -325,7 +328,7 @@
_previewView.clipsToBounds = true; _previewView.clipsToBounds = true;
[_previewView setSnapshotImage:_screenImage]; [_previewView setSnapshotImage:_screenImage];
[_photoEditor setPreviewOutput:_previewView]; [_photoEditor setPreviewOutput:_previewView];
[self updatePreviewView]; [self updatePreviewView:true];
if ([self presentedForAvatarCreation]) { if ([self presentedForAvatarCreation]) {
CGSize fittedSize = TGScaleToSize(_photoEditor.originalSize, CGSizeMake(1024, 1024)); CGSize fittedSize = TGScaleToSize(_photoEditor.originalSize, CGSizeMake(1024, 1024));
@ -352,6 +355,7 @@
if ([self presentedForAvatarCreation] && _item.isVideo) { if ([self presentedForAvatarCreation] && _item.isVideo) {
_scrubberView = [[TGMediaPickerGalleryVideoScrubber alloc] initWithFrame:CGRectMake(0.0f, 0.0, _portraitToolbarView.frame.size.width, 68.0f)]; _scrubberView = [[TGMediaPickerGalleryVideoScrubber alloc] initWithFrame:CGRectMake(0.0f, 0.0, _portraitToolbarView.frame.size.width, 68.0f)];
_scrubberView.layer.allowsGroupOpacity = true;
_scrubberView.hasDotPicker = true; _scrubberView.hasDotPicker = true;
_scrubberView.dataSource = self; _scrubberView.dataSource = self;
_scrubberView.delegate = self; _scrubberView.delegate = self;
@ -560,6 +564,9 @@
if (strongSelf->_ignoreDefaultPreviewViewTransitionIn) if (strongSelf->_ignoreDefaultPreviewViewTransitionIn)
{ {
__strong TGPhotoEditorController *strongSelf = weakSelf;
if (strongSelf == nil)
return;
TGDispatchOnMainThread(^ TGDispatchOnMainThread(^
{ {
if (strongSelf->_dismissed) if (strongSelf->_dismissed)
@ -574,12 +581,16 @@
{ {
[photoEditor processAnimated:false completion:^ [photoEditor processAnimated:false completion:^
{ {
__strong TGPhotoEditorController *strongSelf = weakSelf;
if (strongSelf == nil)
return;
TGDispatchOnMainThread(^ TGDispatchOnMainThread(^
{ {
if (strongSelf->_dismissed) if (strongSelf->_dismissed)
return; return;
[strongSelf->_previewView performTransitionInWithCompletion:^ [strongSelf->_previewView performTransitionInWithCompletion:^
{ {
if (!strongSelf.skipInitialTransition)
[strongSelf->_previewView setSnapshotImage:next]; [strongSelf->_previewView setSnapshotImage:next];
}]; }];
}); });
@ -837,6 +848,16 @@
[_portraitToolbarView setDoneButtonEnabled:enabled animated:animated]; [_portraitToolbarView setDoneButtonEnabled:enabled animated:animated];
[_landscapeToolbarView setDoneButtonEnabled:enabled animated:animated]; [_landscapeToolbarView setDoneButtonEnabled:enabled animated:animated];
if (animated) {
[UIView animateWithDuration:0.2 animations:^{
_scrubberView.alpha = enabled ? 1.0 : 0.2;
}];
} else {
_scrubberView.alpha = enabled ? 1.0 : 0.2;
}
_scrubberView.userInteractionEnabled = enabled;
} }
- (void)updateStatusBarAppearanceForDismiss - (void)updateStatusBarAppearanceForDismiss
@ -1079,7 +1100,13 @@
rep = imageView; rep = imageView;
} }
[_currentTabController prepareForCustomTransitionOut]; [_currentTabController prepareForCustomTransitionOut];
self.beginCustomTransitionOut([_currentTabController transitionOutReferenceFrame], rep, completion);
TGPhotoEditorTabController *tabController = _currentTabController;
self.beginCustomTransitionOut([_currentTabController transitionOutReferenceFrame], rep, ^{
[tabController finishCustomTransitionOut];
if (completion)
completion();
});
} }
else else
{ {
@ -1115,6 +1142,8 @@
if (![currentController isDismissAllowed]) if (![currentController isDismissAllowed])
return; return;
[self savePaintingData];
currentController.switchingToTab = tab; currentController.switchingToTab = tab;
[currentController transitionOutSwitching:true completion:^ [currentController transitionOutSwitching:true completion:^
{ {
@ -1160,12 +1189,16 @@
} }
} }
if ([self presentedForAvatarCreation]) if ([self presentedForAvatarCreation] && ![self presentedFromCamera])
transitionNoTransitionView = true; transitionNoTransitionView = true;
snapshotImage = _screenImage; snapshotImage = _screenImage;
} }
if (_currentTabController == nil && self.skipInitialTransition) {
[self presentAnimated:true];
}
_switchingTab = true; _switchingTab = true;
if ([_currentTabController isKindOfClass:[TGPhotoAvatarPreviewController class]]) { if ([_currentTabController isKindOfClass:[TGPhotoAvatarPreviewController class]]) {
@ -1192,7 +1225,11 @@
{ {
bool skipInitialTransition = (![self presentedFromCamera] && self.navigationController != nil) || self.skipInitialTransition; bool skipInitialTransition = (![self presentedFromCamera] && self.navigationController != nil) || self.skipInitialTransition;
TGPhotoAvatarPreviewController *cropController = [[TGPhotoAvatarPreviewController alloc] initWithContext:_context photoEditor:_photoEditor previewView:_previewView scrubberView:_scrubberView dotImageView:_dotImageView fullPreviewView:_fullPreviewView]; TGPhotoAvatarPreviewController *cropController = [[TGPhotoAvatarPreviewController alloc] initWithContext:_context photoEditor:_photoEditor previewView:_previewView];
cropController.scrubberView = _scrubberView;
cropController.dotImageView = _dotImageView;
cropController.dotMarkerView = _dotMarkerView;
cropController.fullPreviewView = _fullPreviewView;
cropController.fromCamera = [self presentedFromCamera]; cropController.fromCamera = [self presentedFromCamera];
cropController.skipTransitionIn = skipInitialTransition; cropController.skipTransitionIn = skipInitialTransition;
if (snapshotImage != nil) if (snapshotImage != nil)
@ -1230,6 +1267,8 @@
strongSelf->_dotImageView.cropOrientation = strongSelf->_photoEditor.cropOrientation; strongSelf->_dotImageView.cropOrientation = strongSelf->_photoEditor.cropOrientation;
strongSelf->_dotImageView.cropMirrored = strongSelf->_photoEditor.cropMirrored; strongSelf->_dotImageView.cropMirrored = strongSelf->_photoEditor.cropMirrored;
[strongSelf->_dotImageView updateCropping:true]; [strongSelf->_dotImageView updateCropping:true];
[strongSelf updatePreviewView:false];
} }
}; };
cropController.beginTransitionIn = ^UIView *(CGRect *referenceFrame, UIView **parentView, bool *noTransitionView) cropController.beginTransitionIn = ^UIView *(CGRect *referenceFrame, UIView **parentView, bool *noTransitionView)
@ -1547,7 +1586,7 @@
{ {
__strong TGPhotoEditorController *strongSelf = weakSelf; __strong TGPhotoEditorController *strongSelf = weakSelf;
if (strongSelf != nil) if (strongSelf != nil)
[strongSelf updatePreviewView]; [strongSelf updatePreviewView:true];
}; };
_currentTabController.tabsChanged = ^ _currentTabController.tabsChanged = ^
{ {
@ -1573,10 +1612,16 @@
[self setNeedsUpdateOfScreenEdgesDeferringSystemGestures]; [self setNeedsUpdateOfScreenEdgesDeferringSystemGestures];
} }
- (void)updatePreviewView - (void)updatePreviewView:(bool)full
{ {
if (full)
[_previewView setPaintingImageWithData:_photoEditor.paintingData]; [_previewView setPaintingImageWithData:_photoEditor.paintingData];
[_previewView setCropRect:_photoEditor.cropRect cropOrientation:_photoEditor.cropOrientation cropRotation:_photoEditor.cropRotation cropMirrored:_photoEditor.cropMirrored originalSize:_photoEditor.originalSize];
UIImageOrientation cropOrientation = _photoEditor.cropOrientation;
if ([self presentedForAvatarCreation]) {
cropOrientation = UIImageOrientationUp;
}
[_previewView setCropRect:_photoEditor.cropRect cropOrientation:cropOrientation cropRotation:_photoEditor.cropRotation cropMirrored:_photoEditor.cropMirrored originalSize:_photoEditor.originalSize];
} }
- (void)updateEditorButtons - (void)updateEditorButtons
@ -1605,6 +1650,27 @@
} }
#pragma mark - #pragma mark -
- (void)presentAnimated:(bool)animated
{
if (animated)
{
const CGFloat velocity = 2000.0f;
CGFloat duration = self.view.frame.size.height / velocity;
CGRect targetFrame = self.view.frame;
self.view.frame = CGRectOffset(self.view.frame, 0, self.view.frame.size.height);
[UIView animateWithDuration:duration animations:^
{
self.view.frame = targetFrame;
} completion:^(__unused BOOL finished)
{
TGDispatchAfter(1.0, dispatch_get_main_queue(), ^{
[_photoEditor updateProcessChain:true];
});
}];
}
}
- (void)dismissAnimated:(bool)animated - (void)dismissAnimated:(bool)animated
{ {
_dismissed = true; _dismissed = true;
@ -1625,16 +1691,20 @@
self.view.frame = targetFrame; self.view.frame = targetFrame;
} completion:^(__unused BOOL finished) } completion:^(__unused BOOL finished)
{ {
[_currentTabController finishCustomTransitionOut];
if (self.navigationController != nil) { if (self.navigationController != nil) {
[self.navigationController popViewControllerAnimated:false]; [self.navigationController popViewControllerAnimated:false];
} else { } else {
[self dismiss]; [self dismiss];
if (self.onDismiss)
self.onDismiss();
} }
}]; }];
} }
else else
{ {
if (self.navigationController != nil) { if (self.navigationController != nil) {
[_currentTabController finishCustomTransitionOut];
[self.navigationController popViewControllerAnimated:false]; [self.navigationController popViewControllerAnimated:false];
} else { } else {
[self dismiss]; [self dismiss];
@ -1667,7 +1737,9 @@
strongSelf.view.userInteractionEnabled = false; strongSelf.view.userInteractionEnabled = false;
[strongSelf->_currentTabController prepareTransitionOutSaving:false]; [strongSelf->_currentTabController prepareTransitionOutSaving:false];
if (strongSelf.navigationController != nil && [strongSelf.navigationController.viewControllers containsObject:strongSelf]) if (self.skipInitialTransition) {
[strongSelf dismissAnimated:true];
} else if (strongSelf.navigationController != nil && [strongSelf.navigationController.viewControllers containsObject:strongSelf])
{ {
[strongSelf.navigationController popViewControllerAnimated:true]; [strongSelf.navigationController popViewControllerAnimated:true];
} }
@ -1749,6 +1821,21 @@
} }
} }
- (void)savePaintingData {
if (![_currentTabController isKindOfClass:[TGPhotoPaintController class]])
return;
TGPhotoPaintController *paintController = (TGPhotoPaintController *)_currentTabController;
TGPaintingData *paintingData = [paintController paintingData];
_photoEditor.paintingData = paintingData;
if (paintingData != nil)
[TGPaintingData storePaintingData:paintingData inContext:self.editingContext forItem:_item forVideo:(_intent == TGPhotoEditorControllerVideoIntent)];
[_previewView setPaintingImageWithData:_photoEditor.paintingData];
[_previewView setPaintingHidden:false];
}
- (void)applyEditor - (void)applyEditor
{ {
if (![_currentTabController isDismissAllowed]) if (![_currentTabController isDismissAllowed])
@ -1757,8 +1844,6 @@
self.view.userInteractionEnabled = false; self.view.userInteractionEnabled = false;
[_currentTabController prepareTransitionOutSaving:true]; [_currentTabController prepareTransitionOutSaving:true];
TGPaintingData *paintingData = _photoEditor.paintingData;
bool saving = true; bool saving = true;
NSTimeInterval videoStartValue = 0.0; NSTimeInterval videoStartValue = 0.0;
NSTimeInterval trimStartValue = 0.0; NSTimeInterval trimStartValue = 0.0;
@ -1766,12 +1851,7 @@
if ([_currentTabController isKindOfClass:[TGPhotoPaintController class]]) if ([_currentTabController isKindOfClass:[TGPhotoPaintController class]])
{ {
TGPhotoPaintController *paintController = (TGPhotoPaintController *)_currentTabController; [self savePaintingData];
paintingData = [paintController paintingData];
_photoEditor.paintingData = paintingData;
if (paintingData != nil)
[TGPaintingData storePaintingData:paintingData inContext:_editingContext forItem:_item forVideo:(_intent == TGPhotoEditorControllerVideoIntent)];
} }
else if ([_currentTabController isKindOfClass:[TGPhotoQualityController class]]) else if ([_currentTabController isKindOfClass:[TGPhotoQualityController class]])
{ {
@ -1789,6 +1869,7 @@
[self stopVideoPlayback:true]; [self stopVideoPlayback:true];
TGPaintingData *paintingData = _photoEditor.paintingData;
TGVideoEditAdjustments *adjustments = [_photoEditor exportAdjustmentsWithPaintingData:paintingData]; TGVideoEditAdjustments *adjustments = [_photoEditor exportAdjustmentsWithPaintingData:paintingData];
if ([self presentedForAvatarCreation] && _item.isVideo) { if ([self presentedForAvatarCreation] && _item.isVideo) {
[[SQueue concurrentDefaultQueue] dispatch:^ [[SQueue concurrentDefaultQueue] dispatch:^
@ -1826,6 +1907,9 @@
if (adjustments.toolsApplied) { if (adjustments.toolsApplied) {
image = [PGPhotoEditor resultImageForImage:image adjustments:adjustments]; image = [PGPhotoEditor resultImageForImage:image adjustments:adjustments];
if ([self presentedForAvatarCreation]) {
fullImage = TGPhotoEditorVideoCrop(image, paintingImage, adjustments.cropOrientation, adjustments.cropRotation, adjustments.cropRect, adjustments.cropMirrored, CGSizeMake(640, 640), item.originalSize, true, false);
} else {
CGSize fillSize = TGScaleToFillSize(videoDimensions, image.size); CGSize fillSize = TGScaleToFillSize(videoDimensions, image.size);
UIGraphicsBeginImageContextWithOptions(fillSize, true, 0.0f); UIGraphicsBeginImageContextWithOptions(fillSize, true, 0.0f);
@ -1837,6 +1921,7 @@
fullImage = UIGraphicsGetImageFromCurrentImageContext(); fullImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext(); UIGraphicsEndImageContext();
}
} else { } else {
fullImage = TGPhotoEditorVideoCrop(image, paintingImage, adjustments.cropOrientation, adjustments.cropRotation, adjustments.cropRect, adjustments.cropMirrored, CGSizeMake(640, 640), item.originalSize, true, false); fullImage = TGPhotoEditorVideoCrop(image, paintingImage, adjustments.cropOrientation, adjustments.cropRotation, adjustments.cropRect, adjustments.cropMirrored, CGSizeMake(640, 640), item.originalSize, true, false);
} }
@ -1953,7 +2038,7 @@
thumbnailImage = UIGraphicsGetImageFromCurrentImageContext(); thumbnailImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext(); UIGraphicsEndImageContext();
[_editingContext setImage:fullImage thumbnailImage:thumbnailImage forItem:_item synchronous:true]; [self.editingContext setImage:fullImage thumbnailImage:thumbnailImage forItem:_item synchronous:true];
}]; }];
}]; }];
} }
@ -1975,6 +2060,14 @@
} }
} }
- (TGMediaEditingContext *)editingContext
{
if (_editingContext)
return _editingContext;
else
return _standaloneEditingContext;
}
- (void)doneButtonLongPressed:(UIButton *)sender - (void)doneButtonLongPressed:(UIButton *)sender
{ {
if (_intent == TGPhotoEditorControllerVideoIntent) if (_intent == TGPhotoEditorControllerVideoIntent)
@ -2218,6 +2311,8 @@
_initializedScrubber = true; _initializedScrubber = true;
[_scrubberView reloadData]; [_scrubberView reloadData];
[_scrubberView resetToStart]; [_scrubberView resetToStart];
if (_isPlaying)
[_scrubberView _updateScrubberAnimationsAndResetCurrentPosition:true];
} else { } else {
if (previousWidth != _portraitToolbarView.frame.size.width) if (previousWidth != _portraitToolbarView.frame.size.width)
[_scrubberView reloadThumbnails]; [_scrubberView reloadThumbnails];
@ -2307,14 +2402,16 @@
if (self.requestOriginalScreenSizeImage == nil) if (self.requestOriginalScreenSizeImage == nil)
return; return;
SSignal *cachedSignal = [[self.editingContext facesForItem:item] mapToSignal:^SSignal *(id result) SSignal *cachedFaces = self.editingContext != nil ? [self.editingContext facesForItem:item] : [SSignal single:nil];
SSignal *cachedSignal = [cachedFaces mapToSignal:^SSignal *(id result)
{ {
if (result == nil) if (result == nil)
return [SSignal fail:nil]; return [SSignal fail:nil];
return [SSignal single:result]; return [SSignal single:result];
}]; }];
SSignal *imageSignal = [self.requestOriginalScreenSizeImage(item, 0) take:1]; SSignal *imageSignal = self.requestOriginalScreenSizeImage(item, 0);
SSignal *detectSignal = [[imageSignal filter:^bool(UIImage *image) SSignal *detectSignal = [[[imageSignal filter:^bool(UIImage *image)
{ {
if (![image isKindOfClass:[UIImage class]]) if (![image isKindOfClass:[UIImage class]])
return false; return false;
@ -2323,7 +2420,7 @@
return false; return false;
return true; return true;
}] mapToSignal:^SSignal *(UIImage *image) { }] take:1] mapToSignal:^SSignal *(UIImage *image) {
return [[TGPaintFaceDetector detectFacesInImage:image originalSize:originalSize] startOn:[SQueue concurrentDefaultQueue]]; return [[TGPaintFaceDetector detectFacesInImage:image originalSize:originalSize] startOn:[SQueue concurrentDefaultQueue]];
}]; }];
@ -2572,7 +2669,7 @@
return !strongSelf->_scrubberView.isScrubbing; return !strongSelf->_scrubberView.isScrubbing;
}]; }];
dispatch_async(dispatch_get_main_queue(), ^{ TGDispatchAfter(0.16, dispatch_get_main_queue(), ^{
[self updateDotImage:true]; [self updateDotImage:true];
}); });
} }
@ -2676,28 +2773,35 @@
id<TGMediaEditAdjustments> adjustments = [_photoEditor exportAdjustments]; id<TGMediaEditAdjustments> adjustments = [_photoEditor exportAdjustments];
__weak TGPhotoEditorController *weakSelf = self;
SSignal *thumbnailsSignal = nil; SSignal *thumbnailsSignal = nil;
if (_cachedThumbnails != nil) { if (_cachedThumbnails != nil) {
thumbnailsSignal = [SSignal single:_cachedThumbnails]; thumbnailsSignal = [SSignal single:_cachedThumbnails];
} else if ([self.item isKindOfClass:[TGMediaAsset class]]) { } else if ([self.item isKindOfClass:[TGMediaAsset class]]) {
thumbnailsSignal = [[SSignal single:[self _placeholderThumbnails:timestamps]] then:[TGMediaAssetImageSignals videoThumbnailsForAsset:(TGMediaAsset *)self.item size:size timestamps:timestamps]]; thumbnailsSignal = [[SSignal single:[self _placeholderThumbnails:timestamps]] then:[[TGMediaAssetImageSignals videoThumbnailsForAsset:(TGMediaAsset *)self.item size:size timestamps:timestamps] onNext:^(NSArray *images) {
} else if ([self.item isKindOfClass:[TGCameraCapturedVideo class]]) {
thumbnailsSignal = [[((TGCameraCapturedVideo *)self.item).avAsset takeLast] mapToSignal:^SSignal *(AVAsset *avAsset) {
return [[SSignal single:[self _placeholderThumbnails:timestamps]] then:[TGMediaAssetImageSignals videoThumbnailsForAVAsset:avAsset size:size timestamps:timestamps]];
}];
}
_requestingThumbnails = true;
__weak TGPhotoEditorController *weakSelf = self;
[_thumbnailsDisposable setDisposable:[[[[thumbnailsSignal onNext:^(NSArray *images) {
__strong TGPhotoEditorController *strongSelf = weakSelf; __strong TGPhotoEditorController *strongSelf = weakSelf;
if (strongSelf == nil) if (strongSelf == nil)
return; return;
if (strongSelf->_cachedThumbnails == nil) if (strongSelf->_cachedThumbnails == nil)
strongSelf->_cachedThumbnails = images; strongSelf->_cachedThumbnails = images;
}] map:^NSArray *(NSArray *images) { }]];
} else if ([self.item isKindOfClass:[TGCameraCapturedVideo class]]) {
thumbnailsSignal = [[((TGCameraCapturedVideo *)self.item).avAsset takeLast] mapToSignal:^SSignal *(AVAsset *avAsset) {
return [[SSignal single:[self _placeholderThumbnails:timestamps]] then:[[TGMediaAssetImageSignals videoThumbnailsForAVAsset:avAsset size:size timestamps:timestamps] onNext:^(NSArray *images) {
__strong TGPhotoEditorController *strongSelf = weakSelf;
if (strongSelf == nil)
return;
if (strongSelf->_cachedThumbnails == nil)
strongSelf->_cachedThumbnails = images;
}]];
}];
}
_requestingThumbnails = true;
[_thumbnailsDisposable setDisposable:[[[thumbnailsSignal map:^NSArray *(NSArray *images) {
if (adjustments.toolsApplied) { if (adjustments.toolsApplied) {
NSMutableArray *editedImages = [[NSMutableArray alloc] init]; NSMutableArray *editedImages = [[NSMutableArray alloc] init];
PGPhotoEditor *editor = [[PGPhotoEditor alloc] initWithOriginalSize:adjustments.originalSize adjustments:adjustments forVideo:false enableStickers:true]; PGPhotoEditor *editor = [[PGPhotoEditor alloc] initWithOriginalSize:adjustments.originalSize adjustments:adjustments forVideo:false enableStickers:true];

View File

@ -215,6 +215,11 @@ const CGFloat TGPhotoEditorToolbarSize = 49.0f;
} }
- (void)finishCustomTransitionOut
{
}
- (void)transitionOutSwitching:(bool)__unused switching completion:(void (^)(void))__unused completion - (void)transitionOutSwitching:(bool)__unused switching completion:(void (^)(void))__unused completion
{ {
@ -300,6 +305,9 @@ const CGFloat TGPhotoEditorToolbarSize = 49.0f;
[parentView addSubview:toTransitionView]; [parentView addSubview:toTransitionView];
if (_noTransitionToSnapshot)
toTransitionView.alpha = 0.0f;
UIInterfaceOrientation orientation = [[LegacyComponentsGlobals provider] applicationStatusBarOrientation]; UIInterfaceOrientation orientation = [[LegacyComponentsGlobals provider] applicationStatusBarOrientation];
if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad) if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad)
orientation = UIInterfaceOrientationPortrait; orientation = UIInterfaceOrientationPortrait;
@ -498,4 +506,9 @@ const CGFloat TGPhotoEditorToolbarSize = 49.0f;
return highlightedButtons; return highlightedButtons;
} }
- (bool)presentedForAvatarCreation
{
return _intent & (TGPhotoEditorControllerAvatarIntent | TGPhotoEditorControllerSignupAvatarIntent);
}
@end @end

View File

@ -501,23 +501,23 @@ UIImageOrientation TGVideoOrientationForAsset(AVAsset *asset, bool *mirrored)
{ {
CGFloat scaleX = sqrt(t.a * t.a + t.c * t.c); CGFloat scaleX = sqrt(t.a * t.a + t.c * t.c);
CGFloat scaleY = sqrt(t.b * t.b + t.d * t.d); CGFloat scaleY = sqrt(t.b * t.b + t.d * t.d);
/*UIView *tempView = [[UIView alloc] init];
tempView.transform = t;
CGSize scale = CGSizeMake([[tempView.layer valueForKeyPath: @"transform.scale.x"] floatValue],
[[tempView.layer valueForKeyPath: @"transform.scale.y"] floatValue]);*/
CGSize scale = CGSizeMake(scaleX, scaleY); CGSize scale = CGSizeMake(scaleX, scaleY);
*mirrored = (scale.width < 0); *mirrored = (scale.width < 0);
} }
if (fabs(videoRotation - M_PI) < FLT_EPSILON) if (fabs(videoRotation - M_PI) < FLT_EPSILON) {
return UIImageOrientationLeft; return UIImageOrientationLeft;
else if (fabs(videoRotation - M_PI_2) < FLT_EPSILON) } else if (fabs(videoRotation - M_PI_2) < FLT_EPSILON) {
if (t.c == 1 && mirrored != NULL) {
*mirrored = true;
}
return UIImageOrientationUp; return UIImageOrientationUp;
else if (fabs(videoRotation + M_PI_2) < FLT_EPSILON) } else if (fabs(videoRotation + M_PI_2) < FLT_EPSILON) {
return UIImageOrientationDown; return UIImageOrientationDown;
else } else {
return UIImageOrientationRight; return UIImageOrientationRight;
}
} }
UIImageOrientation TGVideoFinalOrientationForOrientation(UIImageOrientation videoOrientation, UIImageOrientation cropOrientation) UIImageOrientation TGVideoFinalOrientationForOrientation(UIImageOrientation videoOrientation, UIImageOrientation cropOrientation)

View File

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

View File

@ -160,8 +160,9 @@ const CGFloat TGPhotoEditorToolsLandscapePanelSize = TGPhotoEditorToolsPanelSize
if (strongSelf != nil) { if (strongSelf != nil) {
[strongSelf->_hudView setText:nil]; [strongSelf->_hudView setText:nil];
if (forVideo) {
strongSelf->_photoEditor.disableAll = false; strongSelf->_photoEditor.disableAll = false;
if (!forVideo) {
[strongSelf->_photoEditor processAnimated:false completion:nil];
} }
} }
}; };
@ -171,8 +172,9 @@ const CGFloat TGPhotoEditorToolsLandscapePanelSize = TGPhotoEditorToolsPanelSize
if (strongSelf != nil) { if (strongSelf != nil) {
[strongSelf->_hudView setText:TGLocalized(@"PhotoEditor.Original")]; [strongSelf->_hudView setText:TGLocalized(@"PhotoEditor.Original")];
if (forVideo) {
strongSelf->_photoEditor.disableAll = true; strongSelf->_photoEditor.disableAll = true;
if (!forVideo) {
[strongSelf->_photoEditor processAnimated:false completion:nil];
} }
} }
}; };
@ -944,6 +946,14 @@ const CGFloat TGPhotoEditorToolsLandscapePanelSize = TGPhotoEditorToolsPanelSize
CGSize referenceSize = [self referenceViewSize]; CGSize referenceSize = [self referenceViewSize];
CGRect containerFrame = _preview ? CGRectMake(0.0f, 0.0f, referenceSize.width, referenceSize.height) : [TGPhotoToolsController photoContainerFrameForParentViewFrame:CGRectMake(0, 0, referenceSize.width, referenceSize.height) toolbarLandscapeSize:self.toolbarLandscapeSize orientation:self.effectiveOrientation panelSize:TGPhotoEditorPanelSize hasOnScreenNavigation:self.hasOnScreenNavigation]; CGRect containerFrame = _preview ? CGRectMake(0.0f, 0.0f, referenceSize.width, referenceSize.height) : [TGPhotoToolsController photoContainerFrameForParentViewFrame:CGRectMake(0, 0, referenceSize.width, referenceSize.height) toolbarLandscapeSize:self.toolbarLandscapeSize orientation:self.effectiveOrientation panelSize:TGPhotoEditorPanelSize hasOnScreenNavigation:self.hasOnScreenNavigation];
CGSize fittedSize = TGScaleToSize(photoEditor.rotatedCropSize, containerFrame.size); CGSize fittedSize = TGScaleToSize(photoEditor.rotatedCropSize, containerFrame.size);
if ([self presentedForAvatarCreation]) {
CGAffineTransform transform = CGAffineTransformMakeRotation(TGRotationForOrientation(photoEditor.cropOrientation));
if (photoEditor.cropMirrored)
transform = CGAffineTransformScale(transform, -1.0f, 1.0f);
previewView.transform = transform;
}
previewView.frame = CGRectMake(containerFrame.origin.x + (containerFrame.size.width - fittedSize.width) / 2, containerFrame.origin.y + (containerFrame.size.height - fittedSize.height) / 2, fittedSize.width, fittedSize.height); previewView.frame = CGRectMake(containerFrame.origin.x + (containerFrame.size.width - fittedSize.width) / 2, containerFrame.origin.y + (containerFrame.size.height - fittedSize.height) / 2, fittedSize.width, fittedSize.height);
[UIView performWithoutAnimation:^ [UIView performWithoutAnimation:^

View File

@ -8,8 +8,99 @@
#import "TGMediaPickerGalleryVideoItemView.h" #import "TGMediaPickerGalleryVideoItemView.h"
#import "LegacyComponentsInternal.h"
@implementation TGPhotoVideoEditor @implementation TGPhotoVideoEditor
+ (void)presentWithContext:(id<LegacyComponentsContext>)context parentController:(TGViewController *)parentController image:(UIImage *)image video:(NSURL *)video didFinishWithImage:(void (^)(UIImage *image))didFinishWithImage didFinishWithVideo:(void (^)(UIImage *image, NSURL *url, TGVideoEditAdjustments *adjustments))didFinishWithVideo dismissed:(void (^)(void))dismissed
{
id<LegacyComponentsOverlayWindowManager> windowManager = [context makeOverlayWindowManager];
id<TGMediaEditableItem> editableItem;
if (image != nil) {
editableItem = image;
} else if (video != nil) {
if (![video.path.lowercaseString hasSuffix:@".mp4"]) {
NSString *tmpPath = NSTemporaryDirectory();
int64_t fileId = 0;
arc4random_buf(&fileId, sizeof(fileId));
NSString *videoMp4FilePath = [tmpPath stringByAppendingPathComponent:[NSString stringWithFormat:@"%" PRId64 ".mp4", fileId]];
[[NSFileManager defaultManager] removeItemAtPath:videoMp4FilePath error:nil];
[[NSFileManager defaultManager] copyItemAtPath:video.path toPath:videoMp4FilePath error:nil];
video = [NSURL fileURLWithPath:videoMp4FilePath];
}
editableItem = [[TGCameraCapturedVideo alloc] initWithURL:video];
}
void (^present)(UIImage *) = ^(UIImage *screenImage) {
TGPhotoEditorController *controller = [[TGPhotoEditorController alloc] initWithContext:[windowManager context] item:editableItem intent:TGPhotoEditorControllerAvatarIntent adjustments:nil caption:nil screenImage:screenImage availableTabs:[TGPhotoEditorController defaultTabsForAvatarIntent] selectedTab:TGPhotoEditorCropTab];
// controller.stickersContext = _stickersContext;
controller.skipInitialTransition = true;
controller.dontHideStatusBar = true;
controller.didFinishEditing = ^(__unused id<TGMediaEditAdjustments> adjustments, UIImage *resultImage, __unused UIImage *thumbnailImage, __unused bool hasChanges)
{
if (didFinishWithImage != nil)
didFinishWithImage(resultImage);
};
controller.didFinishEditingVideo = ^(NSURL *url, id<TGMediaEditAdjustments> adjustments, UIImage *resultImage, UIImage *thumbnailImage, bool hasChanges) {
if (didFinishWithVideo != nil)
didFinishWithVideo(resultImage, url, adjustments);
};
controller.requestThumbnailImage = ^(id<TGMediaEditableItem> editableItem)
{
return [editableItem thumbnailImageSignal];
};
controller.requestOriginalScreenSizeImage = ^(id<TGMediaEditableItem> editableItem, NSTimeInterval position)
{
return [editableItem screenImageSignal:position];
};
controller.requestOriginalFullSizeImage = ^(id<TGMediaEditableItem> editableItem, NSTimeInterval position)
{
if (editableItem.isVideo) {
if ([editableItem isKindOfClass:[TGMediaAsset class]]) {
return [TGMediaAssetImageSignals avAssetForVideoAsset:(TGMediaAsset *)editableItem allowNetworkAccess:true];
} else if ([editableItem isKindOfClass:[TGCameraCapturedVideo class]]) {
return ((TGCameraCapturedVideo *)editableItem).avAsset;
} else {
return [editableItem originalImageSignal:position];
}
} else {
return [editableItem originalImageSignal:position];
}
};
controller.onDismiss = ^{
dismissed();
};
TGOverlayControllerWindow *controllerWindow = [[TGOverlayControllerWindow alloc] initWithManager:windowManager parentController:controller contentController:controller];
controllerWindow.hidden = false;
controller.view.clipsToBounds = true;
};
if (image != nil) {
present(image);
} else if (video != nil) {
AVAssetImageGenerator *imageGenerator = [[AVAssetImageGenerator alloc] initWithAsset:[AVURLAsset assetWithURL:video]];
imageGenerator.appliesPreferredTrackTransform = true;
imageGenerator.maximumSize = CGSizeMake(1280, 1280);
imageGenerator.requestedTimeToleranceBefore = kCMTimeZero;
imageGenerator.requestedTimeToleranceAfter = kCMTimeZero;
[imageGenerator generateCGImagesAsynchronouslyForTimes:@[ [NSValue valueWithCMTime:kCMTimeZero] ] completionHandler:^(CMTime requestedTime, CGImageRef _Nullable image, CMTime actualTime, AVAssetImageGeneratorResult result, NSError * _Nullable error) {
if (result == AVAssetImageGeneratorSucceeded) {
UIImage *screenImage = [UIImage imageWithCGImage:image];
TGDispatchOnMainThread(^{
present(screenImage);
});
}
}];
}
}
+ (void)presentWithContext:(id<LegacyComponentsContext>)context controller:(TGViewController *)controller caption:(NSString *)caption entities:(NSArray *)entities withItem:(id<TGMediaEditableItem, TGMediaSelectableItem>)item recipientName:(NSString *)recipientName stickersContext:(id<TGPhotoPaintStickersContext>)stickersContext completion:(void (^)(id<TGMediaEditableItem>, TGMediaEditingContext *))completion dismissed:(void (^)())dismissed + (void)presentWithContext:(id<LegacyComponentsContext>)context controller:(TGViewController *)controller caption:(NSString *)caption entities:(NSArray *)entities withItem:(id<TGMediaEditableItem, TGMediaSelectableItem>)item recipientName:(NSString *)recipientName stickersContext:(id<TGPhotoPaintStickersContext>)stickersContext completion:(void (^)(id<TGMediaEditableItem>, TGMediaEditingContext *))completion dismissed:(void (^)())dismissed
{ {
id<LegacyComponentsOverlayWindowManager> windowManager = [context makeOverlayWindowManager]; id<LegacyComponentsOverlayWindowManager> windowManager = [context makeOverlayWindowManager];

View File

@ -60,7 +60,7 @@ SHADER_STRING
void main() { void main() {
vec4 image = texture2D(sourceImage, texCoord); vec4 image = texture2D(sourceImage, texCoord);
vec4 blurredImage = texture2D(inputImageTexture2, texCoord); vec4 blurredImage = texture2D(inputImageTexture2, texCoord2);
gl_FragColor = vec4((image.rgb - blurredImage.rgb + vec3(0.5,0.5,0.5)), image.a); gl_FragColor = vec4((image.rgb - blurredImage.rgb + vec3(0.5,0.5,0.5)), image.a);
} }
); );
@ -202,8 +202,8 @@ SHADER_STRING
void main() { void main() {
vec4 image = texture2D(sourceImage, texCoord); vec4 image = texture2D(sourceImage, texCoord);
vec4 toneCurvedImage = texture2D(inputImageTexture2, texCoord); vec4 toneCurvedImage = texture2D(inputImageTexture2, texCoord2);
vec4 mask = texture2D(inputImageTexture3, texCoord); vec4 mask = texture2D(inputImageTexture3, texCoord3);
gl_FragColor = vec4(mix(image.rgb,toneCurvedImage.rgb,1.0 - mask.b),1.0); gl_FragColor = vec4(mix(image.rgb,toneCurvedImage.rgb,1.0 - mask.b),1.0);
} }
); );
@ -240,12 +240,14 @@ SHADER_STRING
self.skinToneCurveFilter = skinToneCurveFilter; self.skinToneCurveFilter = skinToneCurveFilter;
GPUImageDissolveBlendFilter *dissolveFilter = [[GPUImageDissolveBlendFilter alloc] init]; GPUImageDissolveBlendFilter *dissolveFilter = [[GPUImageDissolveBlendFilter alloc] init];
dissolveFilter.rotateOnlyFirstTexture = true;
[self addFilter:dissolveFilter]; [self addFilter:dissolveFilter];
self.dissolveFilter = dissolveFilter; self.dissolveFilter = dissolveFilter;
[skinToneCurveFilter addTarget:dissolveFilter atTextureLocation:1]; [skinToneCurveFilter addTarget:dissolveFilter atTextureLocation:1];
GPUImageThreeInputFilter *composeFilter = [[GPUImageThreeInputFilter alloc] initWithFragmentShaderFromString:YUGPUImageHighpassSkinSmoothingCompositingFilterFragmentShaderString]; GPUImageThreeInputFilter *composeFilter = [[GPUImageThreeInputFilter alloc] initWithFragmentShaderFromString:YUGPUImageHighpassSkinSmoothingCompositingFilterFragmentShaderString];
composeFilter.rotateOnlyFirstTexture = true;
[self addFilter:composeFilter]; [self addFilter:composeFilter];
[maskGenerator addTarget:composeFilter atTextureLocation:2]; [maskGenerator addTarget:composeFilter atTextureLocation:2];
@ -281,6 +283,10 @@ SHADER_STRING
[self updateHighPassRadius]; [self updateHighPassRadius];
} }
- (void)setInputRotation:(GPUImageRotationMode)newInputRotation atIndex:(NSInteger)textureIndex {
[super setInputRotation:newInputRotation atIndex:textureIndex];
}
- (void)updateHighPassRadius { - (void)updateHighPassRadius {
CGSize inputSize = self.currentInputSize; CGSize inputSize = self.currentInputSize;
if (inputSize.width * inputSize.height > 0) { if (inputSize.width * inputSize.height > 0) {

View File

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

View File

@ -408,8 +408,24 @@ public func legacyAssetPickerEnqueueMessages(account: Account, signals: [Any]) -
var finalDuration: Double var finalDuration: Double
switch data { switch data {
case let .asset(asset): case let .asset(asset):
if let adjustments = adjustments {
if adjustments.cropApplied(forAvatar: false) {
finalDimensions = adjustments.cropRect.size
if adjustments.cropOrientation == .left || adjustments.cropOrientation == .right {
finalDimensions = CGSize(width: finalDimensions.height, height: finalDimensions.width)
}
} else {
finalDimensions = asset.dimensions
}
if adjustments.trimEndValue > 0.0 {
finalDuration = adjustments.trimEndValue - adjustments.trimStartValue
} else {
finalDuration = asset.videoDuration
}
} else {
finalDimensions = asset.dimensions finalDimensions = asset.dimensions
finalDuration = asset.videoDuration finalDuration = asset.videoDuration
}
case let .tempFile(_, dimensions, duration): case let .tempFile(_, dimensions, duration):
finalDimensions = dimensions finalDimensions = dimensions
finalDuration = duration finalDuration = duration

View File

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

View File

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

View File

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

View File

@ -10,6 +10,9 @@ import SyncCore
import TelegramPresentationData import TelegramPresentationData
import AccountContext import AccountContext
import GalleryUI import GalleryUI
import LegacyComponents
import LegacyMediaPickerUI
import SaveToCameraRoll
public enum AvatarGalleryEntryId: Hashable { public enum AvatarGalleryEntryId: Hashable {
case topImage case topImage
@ -180,10 +183,14 @@ public class AvatarGalleryController: ViewController, StandalonePresentableContr
private let centralItemTitle = Promise<String>() private let centralItemTitle = Promise<String>()
private let centralItemTitleView = Promise<UIView?>() private let centralItemTitleView = Promise<UIView?>()
private let centralItemRightBarButtonItems = Promise<[UIBarButtonItem]?>(nil)
private let centralItemNavigationStyle = Promise<GalleryItemNodeNavigationStyle>() private let centralItemNavigationStyle = Promise<GalleryItemNodeNavigationStyle>()
private let centralItemFooterContentNode = Promise<(GalleryFooterContentNode?, GalleryOverlayContentNode?)>() private let centralItemFooterContentNode = Promise<(GalleryFooterContentNode?, GalleryOverlayContentNode?)>()
private let centralItemAttributesDisposable = DisposableSet(); private let centralItemAttributesDisposable = DisposableSet();
public var avatarPhotoEditCompletion: ((UIImage) -> Void)?
public var avatarVideoEditCompletion: ((UIImage, URL, TGVideoEditAdjustments?) -> Void)?
private let _hiddenMedia = Promise<AvatarGalleryEntry?>(nil) private let _hiddenMedia = Promise<AvatarGalleryEntry?>(nil)
public var hiddenMedia: Signal<AvatarGalleryEntry?, NoError> { public var hiddenMedia: Signal<AvatarGalleryEntry?, NoError> {
return self._hiddenMedia.get() return self._hiddenMedia.get()
@ -191,6 +198,8 @@ public class AvatarGalleryController: ViewController, StandalonePresentableContr
private let replaceRootController: (ViewController, Promise<Bool>?) -> Void private let replaceRootController: (ViewController, Promise<Bool>?) -> Void
private let editDisposable = MetaDisposable ()
public init(context: AccountContext, peer: Peer, sourceHasRoundCorners: Bool = true, remoteEntries: Promise<[AvatarGalleryEntry]>? = nil, centralEntryIndex: Int? = nil, replaceRootController: @escaping (ViewController, Promise<Bool>?) -> Void, synchronousLoad: Bool = false) { public init(context: AccountContext, peer: Peer, sourceHasRoundCorners: Bool = true, remoteEntries: Promise<[AvatarGalleryEntry]>? = nil, centralEntryIndex: Int? = nil, replaceRootController: @escaping (ViewController, Promise<Bool>?) -> Void, synchronousLoad: Bool = false) {
self.context = context self.context = context
self.peer = peer self.peer = peer
@ -264,6 +273,8 @@ public class AvatarGalleryController: ViewController, StandalonePresentableContr
self?.deleteEntry(entry) self?.deleteEntry(entry)
} : nil, setMain: { [weak self] in } : nil, setMain: { [weak self] in
self?.setMainEntry(entry) self?.setMainEntry(entry)
}, edit: { [weak self] in
self?.editEntry(entry)
}) })
}), centralItemIndex: strongSelf.centralEntryIndex, synchronous: !isFirstTime) }), centralItemIndex: strongSelf.centralEntryIndex, synchronous: !isFirstTime)
@ -313,6 +324,10 @@ public class AvatarGalleryController: ViewController, StandalonePresentableContr
self?.navigationItem.titleView = titleView self?.navigationItem.titleView = titleView
})) }))
self.centralItemAttributesDisposable.add(self.centralItemRightBarButtonItems.get().start(next: { [weak self] rightBarButtonItems in
self?.navigationItem.rightBarButtonItems = rightBarButtonItems
}))
self.centralItemAttributesDisposable.add(self.centralItemFooterContentNode.get().start(next: { [weak self] footerContentNode, _ in self.centralItemAttributesDisposable.add(self.centralItemFooterContentNode.get().start(next: { [weak self] footerContentNode, _ in
self?.galleryNode.updatePresentationState({ self?.galleryNode.updatePresentationState({
$0.withUpdatedFooterContentNode(footerContentNode) $0.withUpdatedFooterContentNode(footerContentNode)
@ -327,6 +342,7 @@ public class AvatarGalleryController: ViewController, StandalonePresentableContr
deinit { deinit {
self.disposable.dispose() self.disposable.dispose()
self.centralItemAttributesDisposable.dispose() self.centralItemAttributesDisposable.dispose()
self.editDisposable.dispose()
} }
@objc func donePressed() { @objc func donePressed() {
@ -379,6 +395,7 @@ public class AvatarGalleryController: ViewController, StandalonePresentableContr
self.displayNode = GalleryControllerNode(controllerInteraction: controllerInteraction) self.displayNode = GalleryControllerNode(controllerInteraction: controllerInteraction)
self.displayNodeDidLoad() self.displayNodeDidLoad()
self.galleryNode.pager.updateOnReplacement = true
self.galleryNode.statusBar = self.statusBar self.galleryNode.statusBar = self.statusBar
self.galleryNode.navigationBar = self.navigationBar self.galleryNode.navigationBar = self.navigationBar
@ -421,6 +438,8 @@ public class AvatarGalleryController: ViewController, StandalonePresentableContr
self?.deleteEntry(entry) self?.deleteEntry(entry)
} : nil, setMain: { [weak self] in } : nil, setMain: { [weak self] in
self?.setMainEntry(entry) self?.setMainEntry(entry)
}, edit: { [weak self] in
self?.editEntry(entry)
}) }), centralItemIndex: self.centralEntryIndex) }) }), centralItemIndex: self.centralEntryIndex)
self.galleryNode.pager.centralItemIndexUpdated = { [weak self] index in self.galleryNode.pager.centralItemIndexUpdated = { [weak self] index in
@ -432,6 +451,7 @@ public class AvatarGalleryController: ViewController, StandalonePresentableContr
if let node = strongSelf.galleryNode.pager.centralItemNode() { if let node = strongSelf.galleryNode.pager.centralItemNode() {
strongSelf.centralItemTitle.set(node.title()) strongSelf.centralItemTitle.set(node.title())
strongSelf.centralItemTitleView.set(node.titleView()) strongSelf.centralItemTitleView.set(node.titleView())
strongSelf.centralItemRightBarButtonItems.set(node.rightBarButtonItems())
strongSelf.centralItemNavigationStyle.set(node.navigationStyle()) strongSelf.centralItemNavigationStyle.set(node.navigationStyle())
strongSelf.centralItemFooterContentNode.set(node.footerContent()) strongSelf.centralItemFooterContentNode.set(node.footerContent())
} }
@ -460,6 +480,7 @@ public class AvatarGalleryController: ViewController, StandalonePresentableContr
if let centralItemNode = self.galleryNode.pager.centralItemNode(), let presentationArguments = self.presentationArguments as? AvatarGalleryControllerPresentationArguments { if let centralItemNode = self.galleryNode.pager.centralItemNode(), let presentationArguments = self.presentationArguments as? AvatarGalleryControllerPresentationArguments {
self.centralItemTitle.set(centralItemNode.title()) self.centralItemTitle.set(centralItemNode.title())
self.centralItemTitleView.set(centralItemNode.titleView()) self.centralItemTitleView.set(centralItemNode.titleView())
self.centralItemRightBarButtonItems.set(centralItemNode.rightBarButtonItems())
self.centralItemNavigationStyle.set(centralItemNode.navigationStyle()) self.centralItemNavigationStyle.set(centralItemNode.navigationStyle())
self.centralItemFooterContentNode.set(centralItemNode.footerContent()) self.centralItemFooterContentNode.set(centralItemNode.footerContent())
@ -577,7 +598,9 @@ public class AvatarGalleryController: ViewController, StandalonePresentableContr
self?.deleteEntry(entry) self?.deleteEntry(entry)
} : nil, setMain: { [weak self] in } : nil, setMain: { [weak self] in
self?.setMainEntry(entry) self?.setMainEntry(entry)
}) }), centralItemIndex: 0) }, edit: { [weak self] in
self?.editEntry(entry)
}) }), centralItemIndex: 0, synchronous: true)
self.entries = entries self.entries = entries
} }
} else { } else {
@ -598,6 +621,76 @@ public class AvatarGalleryController: ViewController, StandalonePresentableContr
} }
} }
private func editEntry(_ rawEntry: AvatarGalleryEntry) {
let mediaReference: AnyMediaReference
if let video = rawEntry.videoRepresentations.last {
mediaReference = .standalone(media: TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: video.resource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: video.dimensions, flags: [])]))
} else {
let media = TelegramMediaImage(imageId: MediaId(namespace: 0, id: 0), representations: rawEntry.representations.map({ $0.representation }), immediateThumbnailData: nil, reference: nil, partialReference: nil, flags: [])
mediaReference = .standalone(media: media)
}
// var cancelImpl: (() -> Void)?
// let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
// let progressSignal = Signal<Never, NoError> { subscriber in
// let controller = OverlayStatusController(theme: presentationData.theme, type: .loading(cancelled: {
// cancelImpl?()
// }))
// strongSelf.present(controller, in: .window(.root), with: ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
// return ActionDisposable { [weak controller] in
// Queue.mainQueue().async() {
// controller?.dismiss()
// }
// }
// }
// |> runOn(Queue.mainQueue())
// |> delay(0.15, queue: Queue.mainQueue())
self.editDisposable.set((fetchMediaData(context: self.context, postbox: self.context.account.postbox, mediaReference: mediaReference)
|> deliverOnMainQueue).start(next: { [weak self] state, isImage in
guard let strongSelf = self else {
return
}
switch state {
case let .progress(value):
break
case let .data(data):
let image: UIImage?
let video: URL?
if isImage {
if let fileData = try? Data(contentsOf: URL(fileURLWithPath: data.path)) {
image = UIImage(data: fileData)
} else {
image = nil
}
video = nil
} else {
image = nil
video = URL(fileURLWithPath: data.path)
}
let avatarPhotoEditCompletion = strongSelf.avatarPhotoEditCompletion
let avatarVideoEditCompletion = strongSelf.avatarVideoEditCompletion
presentLegacyAvatarEditor(theme: strongSelf.presentationData.theme, image: image, video: video, present: { [weak self] c, a in
if let strongSelf = self {
strongSelf.present(c, in: .window(.root), with: a, blockInteraction: true)
}
}, imageCompletion: { image in
avatarPhotoEditCompletion?(image)
}, videoCompletion: { image, url, adjustments in
avatarVideoEditCompletion?(image, url, adjustments)
})
Queue.mainQueue().after(0.4) {
strongSelf.dismiss(forceAway: true)
}
}
}))
}
private func deleteEntry(_ rawEntry: AvatarGalleryEntry) { private func deleteEntry(_ rawEntry: AvatarGalleryEntry) {
var entry = rawEntry var entry = rawEntry
if case .topImage = entry, !self.entries.isEmpty { if case .topImage = entry, !self.entries.isEmpty {

View File

@ -54,8 +54,9 @@ class PeerAvatarImageGalleryItem: GalleryItem {
let sourceHasRoundCorners: Bool let sourceHasRoundCorners: Bool
let delete: (() -> Void)? let delete: (() -> Void)?
let setMain: (() -> Void)? let setMain: (() -> Void)?
let edit: (() -> Void)?
init(context: AccountContext, peer: Peer, presentationData: PresentationData, entry: AvatarGalleryEntry, sourceHasRoundCorners: Bool, delete: (() -> Void)?, setMain: (() -> Void)?) { init(context: AccountContext, peer: Peer, presentationData: PresentationData, entry: AvatarGalleryEntry, sourceHasRoundCorners: Bool, delete: (() -> Void)?, setMain: (() -> Void)?, edit: (() -> Void)?) {
self.context = context self.context = context
self.peer = peer self.peer = peer
self.presentationData = presentationData self.presentationData = presentationData
@ -63,6 +64,7 @@ class PeerAvatarImageGalleryItem: GalleryItem {
self.sourceHasRoundCorners = sourceHasRoundCorners self.sourceHasRoundCorners = sourceHasRoundCorners
self.delete = delete self.delete = delete
self.setMain = setMain self.setMain = setMain
self.edit = edit
} }
func node(synchronous: Bool) -> GalleryItemNode { func node(synchronous: Bool) -> GalleryItemNode {
@ -75,6 +77,7 @@ class PeerAvatarImageGalleryItem: GalleryItem {
node.setEntry(self.entry, synchronous: synchronous) node.setEntry(self.entry, synchronous: synchronous)
node.footerContentNode.delete = self.delete node.footerContentNode.delete = self.delete
node.footerContentNode.setMain = self.setMain node.footerContentNode.setMain = self.setMain
node.edit = self.edit
return node return node
} }
@ -84,10 +87,17 @@ class PeerAvatarImageGalleryItem: GalleryItem {
if let indexData = self.entry.indexData { if let indexData = self.entry.indexData {
node._title.set(.single(self.presentationData.strings.Items_NOfM("\(indexData.position + 1)", "\(indexData.totalCount)").0)) node._title.set(.single(self.presentationData.strings.Items_NOfM("\(indexData.position + 1)", "\(indexData.totalCount)").0))
} }
let previousContentAnimations = node.imageNode.contentAnimations
if synchronous {
node.imageNode.contentAnimations = []
}
node.setEntry(self.entry, synchronous: synchronous) node.setEntry(self.entry, synchronous: synchronous)
if synchronous {
node.imageNode.contentAnimations = previousContentAnimations
}
node.footerContentNode.delete = self.delete node.footerContentNode.delete = self.delete
node.footerContentNode.setMain = self.setMain node.footerContentNode.setMain = self.setMain
node.edit = self.edit
} }
} }
@ -118,18 +128,21 @@ private class PeerAvatarImageGalleryContentNode: ASDisplayNode {
final class PeerAvatarImageGalleryItemNode: ZoomableContentGalleryItemNode { final class PeerAvatarImageGalleryItemNode: ZoomableContentGalleryItemNode {
private let context: AccountContext private let context: AccountContext
private let presentationData: PresentationData
private let peer: Peer private let peer: Peer
private let sourceHasRoundCorners: Bool private let sourceHasRoundCorners: Bool
private var entry: AvatarGalleryEntry? private var entry: AvatarGalleryEntry?
private let contentNode: PeerAvatarImageGalleryContentNode private let contentNode: PeerAvatarImageGalleryContentNode
private let imageNode: TransformImageNode fileprivate let imageNode: TransformImageNode
private var videoNode: UniversalVideoNode? private var videoNode: UniversalVideoNode?
private var videoContent: NativeVideoContent? private var videoContent: NativeVideoContent?
fileprivate let _ready = Promise<Void>() fileprivate let _ready = Promise<Void>()
fileprivate let _title = Promise<String>() fileprivate let _title = Promise<String>()
fileprivate let _rightBarButtonItems = Promise<[UIBarButtonItem]?>()
private let statusNodeContainer: HighlightableButtonNode private let statusNodeContainer: HighlightableButtonNode
private let statusNode: RadialStatusNode private let statusNode: RadialStatusNode
fileprivate let footerContentNode: AvatarGalleryItemFooterContentNode fileprivate let footerContentNode: AvatarGalleryItemFooterContentNode
@ -139,8 +152,11 @@ final class PeerAvatarImageGalleryItemNode: ZoomableContentGalleryItemNode {
private var status: MediaResourceStatus? private var status: MediaResourceStatus?
private let playbackStatusDisposable = MetaDisposable() private let playbackStatusDisposable = MetaDisposable()
fileprivate var edit: (() -> Void)?
init(context: AccountContext, presentationData: PresentationData, peer: Peer, sourceHasRoundCorners: Bool) { init(context: AccountContext, presentationData: PresentationData, peer: Peer, sourceHasRoundCorners: Bool) {
self.context = context self.context = context
self.presentationData = presentationData
self.peer = peer self.peer = peer
self.sourceHasRoundCorners = sourceHasRoundCorners self.sourceHasRoundCorners = sourceHasRoundCorners
@ -204,12 +220,16 @@ final class PeerAvatarImageGalleryItemNode: ZoomableContentGalleryItemNode {
if self.entry != entry { if self.entry != entry {
self.entry = entry self.entry = entry
var barButtonItems: [UIBarButtonItem] = []
var footerContent: AvatarGalleryItemFooterContent var footerContent: AvatarGalleryItemFooterContent
if self.peer.id == self.context.account.peerId { if self.peer.id == self.context.account.peerId {
footerContent = .own((entry.indexData?.position ?? 0) == 0) footerContent = .own((entry.indexData?.position ?? 0) == 0)
let rightBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Settings_EditProfileMedia, style: .plain, target: self, action: #selector(self.editPressed))
barButtonItems.append(rightBarButtonItem)
} else { } else {
footerContent = .info footerContent = .info
} }
self._rightBarButtonItems.set(.single(barButtonItems))
self.footerContentNode.setEntry(entry, content: footerContent) self.footerContentNode.setEntry(entry, content: footerContent)
@ -471,6 +491,10 @@ final class PeerAvatarImageGalleryItemNode: ZoomableContentGalleryItemNode {
return self._title.get() return self._title.get()
} }
override func rightBarButtonItems() -> Signal<[UIBarButtonItem]?, NoError> {
return self._rightBarButtonItems.get()
}
@objc func statusPressed() { @objc func statusPressed() {
if let entry = self.entry, let largestSize = largestImageRepresentation(entry.representations.map({ $0.representation })), let status = self.status { if let entry = self.entry, let largestSize = largestImageRepresentation(entry.representations.map({ $0.representation })), let status = self.status {
switch status { switch status {
@ -494,6 +518,10 @@ final class PeerAvatarImageGalleryItemNode: ZoomableContentGalleryItemNode {
} }
} }
@objc private func editPressed() {
self.edit?()
}
override func footerContent() -> Signal<(GalleryFooterContentNode?, GalleryOverlayContentNode?), NoError> { override func footerContent() -> Signal<(GalleryFooterContentNode?, GalleryOverlayContentNode?), NoError> {
return .single((self.footerContentNode, nil)) return .single((self.footerContentNode, nil))
} }

View File

@ -549,7 +549,7 @@ func editSettingsController(context: AccountContext, currentName: ItemListAvatar
hasPhotos = true hasPhotos = true
} }
let completedPhotoImpl: (UIImage) -> Void = { image in let completedProfilePhotoImpl: (UIImage) -> Void = { image in
if let data = image.jpegData(compressionQuality: 0.6) { if let data = image.jpegData(compressionQuality: 0.6) {
let resource = LocalFileMediaResource(fileId: arc4random64()) let resource = LocalFileMediaResource(fileId: arc4random64())
context.account.postbox.mediaBox.storeResourceData(resource.id, data: data) context.account.postbox.mediaBox.storeResourceData(resource.id, data: data)
@ -580,7 +580,7 @@ func editSettingsController(context: AccountContext, currentName: ItemListAvatar
} }
} }
let completedVideoImpl: (UIImage, URL, TGVideoEditAdjustments?) -> Void = { image, url, adjustments in let completedProfileVideoImpl: (UIImage, URL, TGVideoEditAdjustments?) -> Void = { image, url, adjustments in
if let data = image.jpegData(compressionQuality: 0.6) { if let data = image.jpegData(compressionQuality: 0.6) {
let photoResource = LocalFileMediaResource(fileId: arc4random64()) let photoResource = LocalFileMediaResource(fileId: arc4random64())
context.account.postbox.mediaBox.storeResourceData(photoResource.id, data: data) context.account.postbox.mediaBox.storeResourceData(photoResource.id, data: data)
@ -675,18 +675,18 @@ func editSettingsController(context: AccountContext, currentName: ItemListAvatar
mixin.requestSearchController = { assetsController in mixin.requestSearchController = { assetsController in
let controller = WebSearchController(context: context, peer: peer, configuration: searchBotsConfiguration, mode: .avatar(initialQuery: nil, completion: { result in let controller = WebSearchController(context: context, peer: peer, configuration: searchBotsConfiguration, mode: .avatar(initialQuery: nil, completion: { result in
assetsController?.dismiss() assetsController?.dismiss()
completedPhotoImpl(result) completedProfilePhotoImpl(result)
})) }))
presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
} }
mixin.didFinishWithImage = { image in mixin.didFinishWithImage = { image in
if let image = image { if let image = image {
completedPhotoImpl(image) completedProfilePhotoImpl(image)
} }
} }
mixin.didFinishWithVideo = { image, url, adjustments in mixin.didFinishWithVideo = { image, url, adjustments in
if let image = image, let url = url { if let image = image, let url = url {
completedVideoImpl(image, url, adjustments) completedProfileVideoImpl(image, url, adjustments)
} }
} }
mixin.didFinishWithDelete = { mixin.didFinishWithDelete = {
@ -727,6 +727,12 @@ func editSettingsController(context: AccountContext, currentName: ItemListAvatar
if peer.smallProfileImage != nil { if peer.smallProfileImage != nil {
let galleryController = AvatarGalleryController(context: context, peer: peer, remoteEntries: cachedAvatarEntries.with { $0 }, replaceRootController: { controller, ready in let galleryController = AvatarGalleryController(context: context, peer: peer, remoteEntries: cachedAvatarEntries.with { $0 }, replaceRootController: { controller, ready in
}) })
galleryController.avatarPhotoEditCompletion = { image in
completedProfilePhotoImpl(image)
}
galleryController.avatarVideoEditCompletion = { image, url, adjustments in
completedProfileVideoImpl(image, url, adjustments)
}
presentControllerImpl?(galleryController, AvatarGalleryControllerPresentationArguments(transitionArguments: { entry in presentControllerImpl?(galleryController, AvatarGalleryControllerPresentationArguments(transitionArguments: { entry in
return nil return nil
})) }))

View File

@ -60,6 +60,7 @@ private enum PrivacyAndSecuritySection: Int32 {
public enum PrivacyAndSecurityEntryTag: ItemListItemTag { public enum PrivacyAndSecurityEntryTag: ItemListItemTag {
case accountTimeout case accountTimeout
case autoArchive
public func isEqual(to other: ItemListItemTag) -> Bool { public func isEqual(to other: ItemListItemTag) -> Bool {
if let other = other as? PrivacyAndSecurityEntryTag, self == other { if let other = other as? PrivacyAndSecurityEntryTag, self == other {
@ -333,7 +334,7 @@ private enum PrivacyAndSecurityEntry: ItemListNodeEntry {
case let .autoArchive(text, value): case let .autoArchive(text, value):
return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, sectionId: self.section, style: .blocks, updated: { value in return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, sectionId: self.section, style: .blocks, updated: { value in
arguments.toggleArchiveAndMuteNonContacts(value) arguments.toggleArchiveAndMuteNonContacts(value)
}) }, tag: PrivacyAndSecurityEntryTag.autoArchive)
case let .autoArchiveInfo(text): case let .autoArchiveInfo(text):
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section) return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
case let .accountHeader(theme, text): case let .accountHeader(theme, text):

View File

@ -627,7 +627,10 @@ private func dataSearchableItems(context: AccountContext) -> [SettingsSearchable
}), }),
SettingsSearchableItem(id: .data(14), title: strings.ChatSettings_OpenLinksIn, alternate: synonyms(strings.SettingsSearch_Synonyms_ChatSettings_OpenLinksIn), icon: icon, breadcrumbs: [strings.Settings_ChatSettings], present: { context, _, present in SettingsSearchableItem(id: .data(14), title: strings.ChatSettings_OpenLinksIn, alternate: synonyms(strings.SettingsSearch_Synonyms_ChatSettings_OpenLinksIn), icon: icon, breadcrumbs: [strings.Settings_ChatSettings], present: { context, _, present in
present(.push, webBrowserSettingsController(context: context)) present(.push, webBrowserSettingsController(context: context))
}) }),
SettingsSearchableItem(id: .data(15), title: strings.ChatSettings_IntentsSettings, alternate: synonyms(strings.SettingsSearch_Synonyms_ChatSettings_IntentsSettings), icon: icon, breadcrumbs: [strings.Settings_ChatSettings], present: { context, _, present in
present(.push, intentsSettingsController(context: context))
}),
] ]
} }

View File

@ -994,6 +994,119 @@ public func settingsController(context: AccountContext, accountManager: AccountM
let blockedPeers = Promise<BlockedPeersContext?>(nil) let blockedPeers = Promise<BlockedPeersContext?>(nil)
let hasTwoStepAuthPromise = Promise<Bool?>(nil) let hasTwoStepAuthPromise = Promise<Bool?>(nil)
let completedProfilePhotoImpl: (UIImage) -> Void = { image in
if let data = image.jpegData(compressionQuality: 0.6) {
let resource = LocalFileMediaResource(fileId: arc4random64())
context.account.postbox.mediaBox.storeResourceData(resource.id, data: data)
let representation = TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: 640, height: 640), resource: resource)
updateState { state in
var state = state
state.updatingAvatar = .image(representation, true)
return state
}
updateAvatarDisposable.set((updateAccountPhoto(account: context.account, resource: resource, videoResource: nil, videoStartTimestamp: nil, mapResourceToAvatarSizes: { resource, representations in
return mapResourceToAvatarSizes(postbox: context.account.postbox, resource: resource, representations: representations)
}) |> deliverOnMainQueue).start(next: { result in
switch result {
case .complete:
updateState { state in
var state = state
state.updatingAvatar = nil
return state
}
case .progress:
break
}
}))
}
}
let completedProfileVideoImpl: (UIImage, URL, TGVideoEditAdjustments?) -> Void = { image, url, adjustments in
if let data = image.jpegData(compressionQuality: 0.6) {
let photoResource = LocalFileMediaResource(fileId: arc4random64())
context.account.postbox.mediaBox.storeResourceData(photoResource.id, data: data)
let representation = TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: 640, height: 640), resource: photoResource)
updateState { state in
var state = state
state.updatingAvatar = .image(representation, true)
return state
}
var videoStartTimestamp: Double? = nil
if let adjustments = adjustments, adjustments.videoStartValue > 0.0 {
videoStartTimestamp = adjustments.videoStartValue - adjustments.trimStartValue
}
let signal = Signal<TelegramMediaResource, UploadPeerPhotoError> { subscriber in
var filteredPath = url.path
if filteredPath.hasPrefix("file://") {
filteredPath = String(filteredPath[filteredPath.index(filteredPath.startIndex, offsetBy: "file://".count)])
}
let avAsset = AVURLAsset(url: URL(fileURLWithPath: filteredPath))
let entityRenderer: LegacyPaintEntityRenderer? = adjustments.flatMap { adjustments in
if let paintingData = adjustments.paintingData, paintingData.hasAnimation {
return LegacyPaintEntityRenderer(account: context.account, adjustments: adjustments)
} else {
return nil
}
}
let uploadInterface = LegacyLiveUploadInterface(account: context.account)
let signal = TGMediaVideoConverter.convert(avAsset, adjustments: adjustments, watcher: uploadInterface, entityRenderer: entityRenderer)!
let signalDisposable = signal.start(next: { next in
if let result = next as? TGMediaVideoConversionResult {
if let image = result.coverImage, let data = image.jpegData(compressionQuality: 0.7) {
context.account.postbox.mediaBox.storeResourceData(photoResource.id, data: data)
}
var value = stat()
if stat(result.fileURL.path, &value) == 0 {
if let data = try? Data(contentsOf: result.fileURL) {
let resource: TelegramMediaResource
if let liveUploadData = result.liveUploadData as? LegacyLiveUploadInterfaceResult {
resource = LocalFileMediaResource(fileId: liveUploadData.id)
} else {
resource = LocalFileMediaResource(fileId: arc4random64())
}
context.account.postbox.mediaBox.storeResourceData(resource.id, data: data, synchronous: true)
subscriber.putNext(resource)
}
}
subscriber.putCompletion()
}
}, error: { _ in
}, completed: nil)
let disposable = ActionDisposable {
signalDisposable?.dispose()
}
return ActionDisposable {
disposable.dispose()
}
}
updateAvatarDisposable.set((signal
|> mapToSignal { videoResource in
return updateAccountPhoto(account: context.account, resource: photoResource, videoResource: videoResource, videoStartTimestamp: videoStartTimestamp, mapResourceToAvatarSizes: { resource, representations in
return mapResourceToAvatarSizes(postbox: context.account.postbox, resource: resource, representations: representations)
})
} |> deliverOnMainQueue).start(next: { result in
switch result {
case .complete:
updateState { state in
var state = state
state.updatingAvatar = nil
return state
}
case .progress:
break
}
}))
}
}
let arguments = SettingsItemArguments(sharedContext: context.sharedContext, avatarAndNameInfoContext: avatarAndNameInfoContext, avatarTapAction: { let arguments = SettingsItemArguments(sharedContext: context.sharedContext, avatarAndNameInfoContext: avatarAndNameInfoContext, avatarTapAction: {
var updating = false var updating = false
updateState { updateState {
@ -1013,6 +1126,12 @@ public func settingsController(context: AccountContext, accountManager: AccountM
let galleryController = AvatarGalleryController(context: context, peer: peer, replaceRootController: { controller, ready in let galleryController = AvatarGalleryController(context: context, peer: peer, replaceRootController: { controller, ready in
}) })
galleryController.avatarPhotoEditCompletion = { image in
completedProfilePhotoImpl(image)
}
galleryController.avatarVideoEditCompletion = { image, url, adjustments in
completedProfileVideoImpl(image, url, adjustments)
}
hiddenAvatarRepresentationDisposable.set((galleryController.hiddenMedia |> deliverOnMainQueue).start(next: { entry in hiddenAvatarRepresentationDisposable.set((galleryController.hiddenMedia |> deliverOnMainQueue).start(next: { entry in
avatarAndNameInfoContext.hiddenAvatarRepresentation = entry?.representations.last?.representation avatarAndNameInfoContext.hiddenAvatarRepresentation = entry?.representations.last?.representation
updateHiddenAvatarImpl?() updateHiddenAvatarImpl?()
@ -1061,7 +1180,7 @@ public func settingsController(context: AccountContext, accountManager: AccountM
blockedPeers.set(.single(blockedPeersContext)) blockedPeers.set(.single(blockedPeersContext))
}, updatedHasTwoStepAuth: { hasTwoStepAuthValue in }, updatedHasTwoStepAuth: { hasTwoStepAuthValue in
hasTwoStepAuthPromise.set(.single(hasTwoStepAuthValue)) hasTwoStepAuthPromise.set(.single(hasTwoStepAuthValue))
}, activeSessionsContext: activeSessionsContext, webSessionsContext: webSessionsContext, blockedPeersContext: blockedPeersContext, hasTwoStepAuth: hasTwoStepAuth)) }, focusOnItemTag: nil, activeSessionsContext: activeSessionsContext, webSessionsContext: webSessionsContext, blockedPeersContext: blockedPeersContext, hasTwoStepAuth: hasTwoStepAuth))
}) })
}) })
}, openDataAndStorage: { }, openDataAndStorage: {
@ -1285,136 +1404,23 @@ public func settingsController(context: AccountContext, accountManager: AccountM
hasPhotos = true hasPhotos = true
} }
let completedImpl: (UIImage) -> Void = { image in
if let data = image.jpegData(compressionQuality: 0.6) {
let resource = LocalFileMediaResource(fileId: arc4random64())
context.account.postbox.mediaBox.storeResourceData(resource.id, data: data)
let representation = TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: 640, height: 640), resource: resource)
updateState { state in
var state = state
state.updatingAvatar = .image(representation, true)
return state
}
updateAvatarDisposable.set((updateAccountPhoto(account: context.account, resource: resource, videoResource: nil, videoStartTimestamp: nil, mapResourceToAvatarSizes: { resource, representations in
return mapResourceToAvatarSizes(postbox: context.account.postbox, resource: resource, representations: representations)
}) |> deliverOnMainQueue).start(next: { result in
switch result {
case .complete:
updateState { state in
var state = state
state.updatingAvatar = nil
return state
}
case .progress:
break
}
}))
}
}
let completedVideoImpl: (UIImage, URL, TGVideoEditAdjustments?) -> Void = { image, url, adjustments in
if let data = image.jpegData(compressionQuality: 0.6) {
let photoResource = LocalFileMediaResource(fileId: arc4random64())
context.account.postbox.mediaBox.storeResourceData(photoResource.id, data: data)
let representation = TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: 640, height: 640), resource: photoResource)
updateState { state in
var state = state
state.updatingAvatar = .image(representation, true)
return state
}
var videoStartTimestamp: Double? = nil
if let adjustments = adjustments, adjustments.videoStartValue > 0.0 {
videoStartTimestamp = adjustments.videoStartValue - adjustments.trimStartValue
}
let signal = Signal<TelegramMediaResource, UploadPeerPhotoError> { subscriber in
var filteredPath = url.path
if filteredPath.hasPrefix("file://") {
filteredPath = String(filteredPath[filteredPath.index(filteredPath.startIndex, offsetBy: "file://".count)])
}
let avAsset = AVURLAsset(url: URL(fileURLWithPath: filteredPath))
let entityRenderer: LegacyPaintEntityRenderer? = adjustments.flatMap { adjustments in
if let paintingData = adjustments.paintingData, paintingData.hasAnimation {
return LegacyPaintEntityRenderer(account: context.account, adjustments: adjustments)
} else {
return nil
}
}
let uploadInterface = LegacyLiveUploadInterface(account: context.account)
let signal = TGMediaVideoConverter.convert(avAsset, adjustments: adjustments, watcher: uploadInterface, entityRenderer: entityRenderer)!
let signalDisposable = signal.start(next: { next in
if let result = next as? TGMediaVideoConversionResult {
if let image = result.coverImage, let data = image.jpegData(compressionQuality: 0.7) {
context.account.postbox.mediaBox.storeResourceData(photoResource.id, data: data)
}
var value = stat()
if stat(result.fileURL.path, &value) == 0 {
if let data = try? Data(contentsOf: result.fileURL) {
let resource: TelegramMediaResource
if let liveUploadData = result.liveUploadData as? LegacyLiveUploadInterfaceResult {
resource = LocalFileMediaResource(fileId: liveUploadData.id)
} else {
resource = LocalFileMediaResource(fileId: arc4random64())
}
context.account.postbox.mediaBox.storeResourceData(resource.id, data: data, synchronous: true)
subscriber.putNext(resource)
}
}
subscriber.putCompletion()
}
}, error: { _ in
}, completed: nil)
let disposable = ActionDisposable {
signalDisposable?.dispose()
}
return ActionDisposable {
disposable.dispose()
}
}
updateAvatarDisposable.set((signal
|> mapToSignal { videoResource in
return updateAccountPhoto(account: context.account, resource: photoResource, videoResource: videoResource, videoStartTimestamp: videoStartTimestamp, mapResourceToAvatarSizes: { resource, representations in
return mapResourceToAvatarSizes(postbox: context.account.postbox, resource: resource, representations: representations)
})
} |> deliverOnMainQueue).start(next: { result in
switch result {
case .complete:
updateState { state in
var state = state
state.updatingAvatar = nil
return state
}
case .progress:
break
}
}))
}
}
let mixin = TGMediaAvatarMenuMixin(context: legacyController.context, parentController: emptyController, hasSearchButton: true, hasDeleteButton: hasPhotos, hasViewButton: false, personalPhoto: true, isVideo: false, saveEditedPhotos: false, saveCapturedMedia: false, signup: false)! let mixin = TGMediaAvatarMenuMixin(context: legacyController.context, parentController: emptyController, hasSearchButton: true, hasDeleteButton: hasPhotos, hasViewButton: false, personalPhoto: true, isVideo: false, saveEditedPhotos: false, saveCapturedMedia: false, signup: false)!
let _ = currentAvatarMixin.swap(mixin) let _ = currentAvatarMixin.swap(mixin)
mixin.requestSearchController = { assetsController in mixin.requestSearchController = { assetsController in
let controller = WebSearchController(context: context, peer: peer, configuration: searchBotsConfiguration, mode: .avatar(initialQuery: nil, completion: { result in let controller = WebSearchController(context: context, peer: peer, configuration: searchBotsConfiguration, mode: .avatar(initialQuery: nil, completion: { result in
assetsController?.dismiss() assetsController?.dismiss()
completedImpl(result) completedProfilePhotoImpl(result)
})) }))
presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
} }
mixin.didFinishWithImage = { image in mixin.didFinishWithImage = { image in
if let image = image { if let image = image {
completedImpl(image) completedProfilePhotoImpl(image)
} }
} }
mixin.didFinishWithVideo = { image, url, adjustments in mixin.didFinishWithVideo = { image, url, adjustments in
if let image = image, let url = url { if let image = image, let url = url {
completedVideoImpl(image, url, adjustments) completedProfileVideoImpl(image, url, adjustments)
} }
} }
mixin.didFinishWithDelete = { mixin.didFinishWithDelete = {
@ -2041,5 +2047,5 @@ private func accountContextMenuItems(context: AccountContext, logout: @escaping
} }
public func makePrivacyAndSecurityController(context: AccountContext) -> ViewController { public func makePrivacyAndSecurityController(context: AccountContext) -> ViewController {
return privacyAndSecurityController(context: context) return privacyAndSecurityController(context: context, focusOnItemTag: PrivacyAndSecurityEntryTag.autoArchive)
} }

View File

@ -26,6 +26,7 @@ static_library(
"//submodules/GraphUI:GraphUI", "//submodules/GraphUI:GraphUI",
"//submodules/AnimatedStickerNode:AnimatedStickerNode", "//submodules/AnimatedStickerNode:AnimatedStickerNode",
"//submodules/ItemListPeerItem:ItemListPeerItem", "//submodules/ItemListPeerItem:ItemListPeerItem",
"//submodules/ItemListPeerActionItem:ItemListPeerActionItem",
], ],
frameworks = [ frameworks = [
"$SDKROOT/System/Library/Frameworks/Foundation.framework", "$SDKROOT/System/Library/Frameworks/Foundation.framework",

View File

@ -27,6 +27,7 @@ swift_library(
"//submodules/GraphUI:GraphUI", "//submodules/GraphUI:GraphUI",
"//submodules/AnimatedStickerNode:AnimatedStickerNode", "//submodules/AnimatedStickerNode:AnimatedStickerNode",
"//submodules/ItemListPeerItem:ItemListPeerItem", "//submodules/ItemListPeerItem:ItemListPeerItem",
"//submodules/ItemListPeerActionItem:ItemListPeerActionItem",
], ],
visibility = [ visibility = [
"//visibility:public", "//visibility:public",

View File

@ -16,6 +16,9 @@ import PresentationDataUtils
import AppBundle import AppBundle
import GraphUI import GraphUI
import ItemListPeerItem import ItemListPeerItem
import ItemListPeerActionItem
private let maxUsersDisplayedLimit: Int32 = 10
private final class GroupStatsControllerArguments { private final class GroupStatsControllerArguments {
let context: AccountContext let context: AccountContext
@ -24,17 +27,23 @@ private final class GroupStatsControllerArguments {
let openPeerHistory: (PeerId) -> Void let openPeerHistory: (PeerId) -> Void
let openPeerAdminActions: (PeerId) -> Void let openPeerAdminActions: (PeerId) -> Void
let promotePeer: (PeerId) -> Void let promotePeer: (PeerId) -> Void
let expandTopPosters: () -> Void
let expandTopAdmins: () -> Void
let expandTopInviters: () -> Void
let setPostersPeerIdWithRevealedOptions: (PeerId?, PeerId?) -> Void let setPostersPeerIdWithRevealedOptions: (PeerId?, PeerId?) -> Void
let setAdminsPeerIdWithRevealedOptions: (PeerId?, PeerId?) -> Void let setAdminsPeerIdWithRevealedOptions: (PeerId?, PeerId?) -> Void
let setInvitersPeerIdWithRevealedOptions: (PeerId?, PeerId?) -> Void let setInvitersPeerIdWithRevealedOptions: (PeerId?, PeerId?) -> Void
init(context: AccountContext, loadDetailedGraph: @escaping (StatsGraph, Int64) -> Signal<StatsGraph?, NoError>, openPeer: @escaping (PeerId) -> Void, openPeerHistory: @escaping (PeerId) -> Void, openPeerAdminActions: @escaping (PeerId) -> Void, promotePeer: @escaping (PeerId) -> Void, setPostersPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void, setAdminsPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void, setInvitersPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void) { init(context: AccountContext, loadDetailedGraph: @escaping (StatsGraph, Int64) -> Signal<StatsGraph?, NoError>, openPeer: @escaping (PeerId) -> Void, openPeerHistory: @escaping (PeerId) -> Void, openPeerAdminActions: @escaping (PeerId) -> Void, promotePeer: @escaping (PeerId) -> Void, expandTopPosters: @escaping () -> Void, expandTopAdmins: @escaping () -> Void, expandTopInviters: @escaping () -> Void, setPostersPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void, setAdminsPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void, setInvitersPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void) {
self.context = context self.context = context
self.loadDetailedGraph = loadDetailedGraph self.loadDetailedGraph = loadDetailedGraph
self.openPeer = openPeer self.openPeer = openPeer
self.openPeerHistory = openPeerHistory self.openPeerHistory = openPeerHistory
self.openPeerAdminActions = openPeerAdminActions self.openPeerAdminActions = openPeerAdminActions
self.promotePeer = promotePeer self.promotePeer = promotePeer
self.expandTopPosters = expandTopPosters
self.expandTopAdmins = expandTopAdmins
self.expandTopInviters = expandTopInviters
self.setPostersPeerIdWithRevealedOptions = setPostersPeerIdWithRevealedOptions self.setPostersPeerIdWithRevealedOptions = setPostersPeerIdWithRevealedOptions
self.setAdminsPeerIdWithRevealedOptions = setAdminsPeerIdWithRevealedOptions self.setAdminsPeerIdWithRevealedOptions = setAdminsPeerIdWithRevealedOptions
self.setInvitersPeerIdWithRevealedOptions = setInvitersPeerIdWithRevealedOptions self.setInvitersPeerIdWithRevealedOptions = setInvitersPeerIdWithRevealedOptions
@ -86,12 +95,15 @@ private enum StatsEntry: ItemListNodeEntry {
case topPostersTitle(PresentationTheme, String, String) case topPostersTitle(PresentationTheme, String, String)
case topPoster(Int32, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, Peer, GroupStatsTopPoster, Bool) case topPoster(Int32, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, Peer, GroupStatsTopPoster, Bool)
case topPostersExpand(PresentationTheme, String)
case topAdminsTitle(PresentationTheme, String, String) case topAdminsTitle(PresentationTheme, String, String)
case topAdmin(Int32, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, Peer, GroupStatsTopAdmin, Bool) case topAdmin(Int32, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, Peer, GroupStatsTopAdmin, Bool)
case topAdminsExpand(PresentationTheme, String)
case topInvitersTitle(PresentationTheme, String, String) case topInvitersTitle(PresentationTheme, String, String)
case topInviter(Int32, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, Peer, GroupStatsTopInviter, Bool) case topInviter(Int32, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, Peer, GroupStatsTopInviter, Bool)
case topInvitersExpand(PresentationTheme, String)
var section: ItemListSectionId { var section: ItemListSectionId {
switch self { switch self {
@ -113,11 +125,11 @@ private enum StatsEntry: ItemListNodeEntry {
return StatsSection.topHours.rawValue return StatsSection.topHours.rawValue
case .topWeekdaysTitle, .topWeekdaysGraph: case .topWeekdaysTitle, .topWeekdaysGraph:
return StatsSection.topWeekdays.rawValue return StatsSection.topWeekdays.rawValue
case .topPostersTitle, .topPoster: case .topPostersTitle, .topPoster, .topPostersExpand:
return StatsSection.topPosters.rawValue return StatsSection.topPosters.rawValue
case .topAdminsTitle, .topAdmin: case .topAdminsTitle, .topAdmin, .topAdminsExpand:
return StatsSection.topAdmins.rawValue return StatsSection.topAdmins.rawValue
case .topInvitersTitle, .topInviter: case .topInvitersTitle, .topInviter, .topInvitersExpand:
return StatsSection.topInviters.rawValue return StatsSection.topInviters.rawValue
} }
} }
@ -164,14 +176,20 @@ private enum StatsEntry: ItemListNodeEntry {
return 1000 return 1000
case let .topPoster(index, _, _, _, _, _, _): case let .topPoster(index, _, _, _, _, _, _):
return 1001 + index return 1001 + index
case .topPostersExpand:
return 1999
case .topAdminsTitle: case .topAdminsTitle:
return 2000 return 2000
case let .topAdmin(index, _, _, _, _, _, _): case let .topAdmin(index, _, _, _, _, _, _):
return 2001 + index return 2001 + index
case .topAdminsExpand:
return 2999
case .topInvitersTitle: case .topInvitersTitle:
return 3000 return 3000
case let .topInviter(index, _, _, _, _, _, _): case let .topInviter(index, _, _, _, _, _, _):
return 30001 + index return 3001 + index
case .topInvitersExpand:
return 3999
} }
} }
@ -297,6 +315,12 @@ private enum StatsEntry: ItemListNodeEntry {
} else { } else {
return false return false
} }
case let .topPostersExpand(lhsTheme, lhsText):
if case let .topPostersExpand(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true
} else {
return false
}
case let .topAdminsTitle(lhsTheme, lhsText, lhsDates): case let .topAdminsTitle(lhsTheme, lhsText, lhsDates):
if case let .topAdminsTitle(rhsTheme, rhsText, rhsDates) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsDates == rhsDates { if case let .topAdminsTitle(rhsTheme, rhsText, rhsDates) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsDates == rhsDates {
return true return true
@ -309,6 +333,12 @@ private enum StatsEntry: ItemListNodeEntry {
} else { } else {
return false return false
} }
case let .topAdminsExpand(lhsTheme, lhsText):
if case let .topAdminsExpand(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true
} else {
return false
}
case let .topInvitersTitle(lhsTheme, lhsText, lhsDates): case let .topInvitersTitle(lhsTheme, lhsText, lhsDates):
if case let .topInvitersTitle(rhsTheme, rhsText, rhsDates) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsDates == rhsDates { if case let .topInvitersTitle(rhsTheme, rhsText, rhsDates) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsDates == rhsDates {
return true return true
@ -321,6 +351,12 @@ private enum StatsEntry: ItemListNodeEntry {
} else { } else {
return false return false
} }
case let .topInvitersExpand(lhsTheme, lhsText):
if case let .topInvitersExpand(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true
} else {
return false
}
} }
} }
@ -377,6 +413,10 @@ private enum StatsEntry: ItemListNodeEntry {
}, setPeerIdWithRevealedOptions: { peerId, fromPeerId in }, setPeerIdWithRevealedOptions: { peerId, fromPeerId in
arguments.setPostersPeerIdWithRevealedOptions(peerId, fromPeerId) arguments.setPostersPeerIdWithRevealedOptions(peerId, fromPeerId)
}, removePeer: { _ in }) }, removePeer: { _ in })
case let .topPostersExpand(theme, title):
return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.downArrowImage(theme), title: title, sectionId: self.section, editing: false, action: {
arguments.expandTopPosters()
})
case let .topAdmin(_, _, strings, dateTimeFormat, peer, topAdmin, revealed): case let .topAdmin(_, _, strings, dateTimeFormat, peer, topAdmin, revealed):
var textComponents: [String] = [] var textComponents: [String] = []
if topAdmin.deletedCount > 0 { if topAdmin.deletedCount > 0 {
@ -400,6 +440,10 @@ private enum StatsEntry: ItemListNodeEntry {
}, setPeerIdWithRevealedOptions: { peerId, fromPeerId in }, setPeerIdWithRevealedOptions: { peerId, fromPeerId in
arguments.setAdminsPeerIdWithRevealedOptions(peerId, fromPeerId) arguments.setAdminsPeerIdWithRevealedOptions(peerId, fromPeerId)
}, removePeer: { _ in }) }, removePeer: { _ in })
case let .topAdminsExpand(theme, title):
return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.downArrowImage(theme), title: title, sectionId: self.section, editing: false, action: {
arguments.expandTopAdmins()
})
case let .topInviter(_, _, strings, dateTimeFormat, peer, topInviter, revealed): case let .topInviter(_, _, strings, dateTimeFormat, peer, topInviter, revealed):
var textComponents: [String] = [] var textComponents: [String] = []
textComponents.append(strings.Stats_GroupTopInviterInvites(topInviter.inviteCount)) textComponents.append(strings.Stats_GroupTopInviterInvites(topInviter.inviteCount))
@ -415,6 +459,10 @@ private enum StatsEntry: ItemListNodeEntry {
}, setPeerIdWithRevealedOptions: { peerId, fromPeerId in }, setPeerIdWithRevealedOptions: { peerId, fromPeerId in
arguments.setInvitersPeerIdWithRevealedOptions(peerId, fromPeerId) arguments.setInvitersPeerIdWithRevealedOptions(peerId, fromPeerId)
}, removePeer: { _ in }) }, removePeer: { _ in })
case let .topInvitersExpand(theme, title):
return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.downArrowImage(theme), title: title, sectionId: self.section, editing: false, action: {
arguments.expandTopInviters()
})
} }
} }
} }
@ -474,32 +522,71 @@ private func groupStatsControllerEntries(state: GroupStatsState, data: GroupStat
if !data.topPosters.isEmpty { if !data.topPosters.isEmpty {
entries.append(.topPostersTitle(presentationData.theme, presentationData.strings.Stats_GroupTopPostersTitle, dates)) entries.append(.topPostersTitle(presentationData.theme, presentationData.strings.Stats_GroupTopPostersTitle, dates))
var index: Int32 = 0 var index: Int32 = 0
for topPoster in data.topPosters {
var topPosters = data.topPosters
var effectiveExpanded = state.topPostersExpanded
if topPosters.count > maxUsersDisplayedLimit && !effectiveExpanded {
topPosters = Array(topPosters.prefix(Int(maxUsersDisplayedLimit)))
} else {
effectiveExpanded = true
}
for topPoster in topPosters {
if let peer = peers[topPoster.peerId], topPoster.messageCount > 0 { if let peer = peers[topPoster.peerId], topPoster.messageCount > 0 {
entries.append(.topPoster(index, presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, peer, topPoster, topPoster.peerId == state.posterPeerIdWithRevealedOptions)) entries.append(.topPoster(index, presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, peer, topPoster, topPoster.peerId == state.posterPeerIdWithRevealedOptions))
index += 1 index += 1
} }
} }
if !effectiveExpanded {
entries.append(.topPostersExpand(presentationData.theme, presentationData.strings.Stats_GroupShowMoreTopPosters(Int32(data.topPosters.count) - maxUsersDisplayedLimit)))
}
} }
if !data.topAdmins.isEmpty { if !data.topAdmins.isEmpty {
entries.append(.topAdminsTitle(presentationData.theme, presentationData.strings.Stats_GroupTopAdminsTitle, dates)) entries.append(.topAdminsTitle(presentationData.theme, presentationData.strings.Stats_GroupTopAdminsTitle, dates))
var index: Int32 = 0 var index: Int32 = 0
var topAdmins = data.topAdmins
var effectiveExpanded = state.topAdminsExpanded
if topAdmins.count > maxUsersDisplayedLimit && !effectiveExpanded {
topAdmins = Array(topAdmins.prefix(Int(maxUsersDisplayedLimit)))
} else {
effectiveExpanded = true
}
for topAdmin in data.topAdmins { for topAdmin in data.topAdmins {
if let peer = peers[topAdmin.peerId], (topAdmin.deletedCount + topAdmin.kickedCount + topAdmin.bannedCount) > 0 { if let peer = peers[topAdmin.peerId], (topAdmin.deletedCount + topAdmin.kickedCount + topAdmin.bannedCount) > 0 {
entries.append(.topAdmin(index, presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, peer, topAdmin, topAdmin.peerId == state.adminPeerIdWithRevealedOptions)) entries.append(.topAdmin(index, presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, peer, topAdmin, topAdmin.peerId == state.adminPeerIdWithRevealedOptions))
index += 1 index += 1
} }
} }
if !effectiveExpanded {
entries.append(.topAdminsExpand(presentationData.theme, presentationData.strings.Stats_GroupShowMoreTopAdmins(Int32(data.topAdmins.count) - maxUsersDisplayedLimit)))
}
} }
if !data.topInviters.isEmpty { if !data.topInviters.isEmpty {
entries.append(.topInvitersTitle(presentationData.theme, presentationData.strings.Stats_GroupTopInvitersTitle, dates)) entries.append(.topInvitersTitle(presentationData.theme, presentationData.strings.Stats_GroupTopInvitersTitle, dates))
var index: Int32 = 0 var index: Int32 = 0
var topInviters = data.topInviters
var effectiveExpanded = state.topInvitersExpanded
if topInviters.count > maxUsersDisplayedLimit && !effectiveExpanded {
topInviters = Array(topInviters.prefix(Int(maxUsersDisplayedLimit)))
} else {
effectiveExpanded = true
}
for topInviter in data.topInviters { for topInviter in data.topInviters {
if let peer = peers[topInviter.peerId], topInviter.inviteCount > 0 { if let peer = peers[topInviter.peerId], topInviter.inviteCount > 0 {
entries.append(.topInviter(index, presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, peer, topInviter, topInviter.peerId == state.inviterPeerIdWithRevealedOptions)) entries.append(.topInviter(index, presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, peer, topInviter, topInviter.peerId == state.inviterPeerIdWithRevealedOptions))
index += 1 index += 1
} }
} }
if !effectiveExpanded {
entries.append(.topInvitersExpand(presentationData.theme, presentationData.strings.Stats_GroupShowMoreTopInviters(Int32(data.topInviters.count) - maxUsersDisplayedLimit)))
}
} }
} }
} }
@ -508,23 +595,41 @@ private func groupStatsControllerEntries(state: GroupStatsState, data: GroupStat
} }
private struct GroupStatsState: Equatable { private struct GroupStatsState: Equatable {
let topPostersExpanded: Bool
let topAdminsExpanded: Bool
let topInvitersExpanded: Bool
let posterPeerIdWithRevealedOptions: PeerId? let posterPeerIdWithRevealedOptions: PeerId?
let adminPeerIdWithRevealedOptions: PeerId? let adminPeerIdWithRevealedOptions: PeerId?
let inviterPeerIdWithRevealedOptions: PeerId? let inviterPeerIdWithRevealedOptions: PeerId?
init() { init() {
self.topPostersExpanded = false
self.topAdminsExpanded = false
self.topInvitersExpanded = false
self.posterPeerIdWithRevealedOptions = nil self.posterPeerIdWithRevealedOptions = nil
self.adminPeerIdWithRevealedOptions = nil self.adminPeerIdWithRevealedOptions = nil
self.inviterPeerIdWithRevealedOptions = nil self.inviterPeerIdWithRevealedOptions = nil
} }
init(posterPeerIdWithRevealedOptions: PeerId?, adminPeerIdWithRevealedOptions: PeerId?, inviterPeerIdWithRevealedOptions: PeerId?) { init(topPostersExpanded: Bool, topAdminsExpanded: Bool, topInvitersExpanded: Bool, posterPeerIdWithRevealedOptions: PeerId?, adminPeerIdWithRevealedOptions: PeerId?, inviterPeerIdWithRevealedOptions: PeerId?) {
self.topPostersExpanded = topPostersExpanded
self.topAdminsExpanded = topAdminsExpanded
self.topInvitersExpanded = topInvitersExpanded
self.posterPeerIdWithRevealedOptions = posterPeerIdWithRevealedOptions self.posterPeerIdWithRevealedOptions = posterPeerIdWithRevealedOptions
self.adminPeerIdWithRevealedOptions = adminPeerIdWithRevealedOptions self.adminPeerIdWithRevealedOptions = adminPeerIdWithRevealedOptions
self.inviterPeerIdWithRevealedOptions = inviterPeerIdWithRevealedOptions self.inviterPeerIdWithRevealedOptions = inviterPeerIdWithRevealedOptions
} }
static func ==(lhs: GroupStatsState, rhs: GroupStatsState) -> Bool { static func ==(lhs: GroupStatsState, rhs: GroupStatsState) -> Bool {
if lhs.topPostersExpanded != rhs.topPostersExpanded {
return false
}
if lhs.topAdminsExpanded != rhs.topAdminsExpanded {
return false
}
if lhs.topInvitersExpanded != rhs.topInvitersExpanded {
return false
}
if lhs.posterPeerIdWithRevealedOptions != rhs.posterPeerIdWithRevealedOptions { if lhs.posterPeerIdWithRevealedOptions != rhs.posterPeerIdWithRevealedOptions {
return false return false
} }
@ -537,16 +642,28 @@ private struct GroupStatsState: Equatable {
return true return true
} }
func withUpdatedTopPostersExpanded(_ topPostersExpanded: Bool) -> GroupStatsState {
return GroupStatsState(topPostersExpanded: topPostersExpanded, topAdminsExpanded: self.topAdminsExpanded, topInvitersExpanded: self.topInvitersExpanded, posterPeerIdWithRevealedOptions: self.posterPeerIdWithRevealedOptions, adminPeerIdWithRevealedOptions: self.adminPeerIdWithRevealedOptions, inviterPeerIdWithRevealedOptions: self.inviterPeerIdWithRevealedOptions)
}
func withUpdatedTopAdminsExpanded(_ topAdminsExpanded: Bool) -> GroupStatsState {
return GroupStatsState(topPostersExpanded: self.topPostersExpanded, topAdminsExpanded: topAdminsExpanded, topInvitersExpanded: self.topInvitersExpanded, posterPeerIdWithRevealedOptions: self.posterPeerIdWithRevealedOptions, adminPeerIdWithRevealedOptions: self.adminPeerIdWithRevealedOptions, inviterPeerIdWithRevealedOptions: self.inviterPeerIdWithRevealedOptions)
}
func withUpdatedTopInvitersExpanded(_ topInvitersExpanded: Bool) -> GroupStatsState {
return GroupStatsState(topPostersExpanded: self.topPostersExpanded, topAdminsExpanded: self.topAdminsExpanded, topInvitersExpanded: topInvitersExpanded, posterPeerIdWithRevealedOptions: self.posterPeerIdWithRevealedOptions, adminPeerIdWithRevealedOptions: self.adminPeerIdWithRevealedOptions, inviterPeerIdWithRevealedOptions: self.inviterPeerIdWithRevealedOptions)
}
func withUpdatedPosterPeerIdWithRevealedOptions(_ posterPeerIdWithRevealedOptions: PeerId?) -> GroupStatsState { func withUpdatedPosterPeerIdWithRevealedOptions(_ posterPeerIdWithRevealedOptions: PeerId?) -> GroupStatsState {
return GroupStatsState(posterPeerIdWithRevealedOptions: posterPeerIdWithRevealedOptions, adminPeerIdWithRevealedOptions: posterPeerIdWithRevealedOptions != nil ? nil : self.adminPeerIdWithRevealedOptions, inviterPeerIdWithRevealedOptions: posterPeerIdWithRevealedOptions != nil ? nil : self.inviterPeerIdWithRevealedOptions) return GroupStatsState(topPostersExpanded: self.topPostersExpanded, topAdminsExpanded: self.topAdminsExpanded, topInvitersExpanded: self.topInvitersExpanded, posterPeerIdWithRevealedOptions: posterPeerIdWithRevealedOptions, adminPeerIdWithRevealedOptions: posterPeerIdWithRevealedOptions != nil ? nil : self.adminPeerIdWithRevealedOptions, inviterPeerIdWithRevealedOptions: posterPeerIdWithRevealedOptions != nil ? nil : self.inviterPeerIdWithRevealedOptions)
} }
func withUpdatedAdminPeerIdWithRevealedOptions(_ adminPeerIdWithRevealedOptions: PeerId?) -> GroupStatsState { func withUpdatedAdminPeerIdWithRevealedOptions(_ adminPeerIdWithRevealedOptions: PeerId?) -> GroupStatsState {
return GroupStatsState(posterPeerIdWithRevealedOptions: adminPeerIdWithRevealedOptions != nil ? nil : self.posterPeerIdWithRevealedOptions, adminPeerIdWithRevealedOptions: adminPeerIdWithRevealedOptions, inviterPeerIdWithRevealedOptions: adminPeerIdWithRevealedOptions != nil ? nil : self.inviterPeerIdWithRevealedOptions) return GroupStatsState(topPostersExpanded: self.topPostersExpanded, topAdminsExpanded: self.topAdminsExpanded, topInvitersExpanded: self.topInvitersExpanded, posterPeerIdWithRevealedOptions: adminPeerIdWithRevealedOptions != nil ? nil : self.posterPeerIdWithRevealedOptions, adminPeerIdWithRevealedOptions: adminPeerIdWithRevealedOptions, inviterPeerIdWithRevealedOptions: adminPeerIdWithRevealedOptions != nil ? nil : self.inviterPeerIdWithRevealedOptions)
} }
func withUpdatedInviterPeerIdWithRevealedOptions(_ inviterPeerIdWithRevealedOptions: PeerId?) -> GroupStatsState { func withUpdatedInviterPeerIdWithRevealedOptions(_ inviterPeerIdWithRevealedOptions: PeerId?) -> GroupStatsState {
return GroupStatsState(posterPeerIdWithRevealedOptions: inviterPeerIdWithRevealedOptions != nil ? nil : self.posterPeerIdWithRevealedOptions, adminPeerIdWithRevealedOptions: inviterPeerIdWithRevealedOptions != nil ? nil : self.adminPeerIdWithRevealedOptions, inviterPeerIdWithRevealedOptions: inviterPeerIdWithRevealedOptions) return GroupStatsState(topPostersExpanded: self.topPostersExpanded, topAdminsExpanded: self.topAdminsExpanded, topInvitersExpanded: self.topInvitersExpanded, posterPeerIdWithRevealedOptions: inviterPeerIdWithRevealedOptions != nil ? nil : self.posterPeerIdWithRevealedOptions, adminPeerIdWithRevealedOptions: inviterPeerIdWithRevealedOptions != nil ? nil : self.adminPeerIdWithRevealedOptions, inviterPeerIdWithRevealedOptions: inviterPeerIdWithRevealedOptions)
} }
} }
@ -633,6 +750,18 @@ public func groupStatsController(context: AccountContext, peerId: PeerId, cached
openPeerAdminActionsImpl?(peerId) openPeerAdminActionsImpl?(peerId)
}, promotePeer: { peerId in }, promotePeer: { peerId in
promotePeerImpl?(peerId) promotePeerImpl?(peerId)
}, expandTopPosters: {
updateState { state in
return state.withUpdatedTopPostersExpanded(true)
}
}, expandTopAdmins: {
updateState { state in
return state.withUpdatedTopAdminsExpanded(true)
}
}, expandTopInviters: {
updateState { state in
return state.withUpdatedTopInvitersExpanded(true)
}
}, setPostersPeerIdWithRevealedOptions: { peerId, fromPeerId in }, setPostersPeerIdWithRevealedOptions: { peerId, fromPeerId in
updateState { state in updateState { state in
if (peerId == nil && fromPeerId == state.posterPeerIdWithRevealedOptions) || (peerId != nil && fromPeerId == nil) { if (peerId == nil && fromPeerId == state.posterPeerIdWithRevealedOptions) || (peerId != nil && fromPeerId == nil) {

View File

@ -416,6 +416,19 @@ func fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPeerId: PeerI
} }
} }
if let participantResult = participantResult {
switch participantResult {
case let .channelParticipant(_, users):
for user in users {
let telegramUser = TelegramUser(user: user)
peers.append(telegramUser)
if let presence = TelegramUserPresence(apiUser: user) {
peerPresences[telegramUser.id] = presence
}
}
}
}
updatePeers(transaction: transaction, peers: peers, update: { _, updated -> Peer in updatePeers(transaction: transaction, peers: peers, update: { _, updated -> Peer in
return updated return updated
}) })

View File

@ -982,13 +982,15 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
let indicatorTranslation = max(0.0, self.actionButtons.micButton.cancelTranslation - cancelTransformThreshold) let indicatorTranslation = max(0.0, self.actionButtons.micButton.cancelTranslation - cancelTransformThreshold)
audioRecordingCancelIndicator.frame = CGRect( let audioRecordingCancelIndicatorFrame = CGRect(
origin: CGPoint( origin: CGPoint(
x: leftInset + floor((baseWidth - audioRecordingCancelIndicator.bounds.size.width - indicatorTranslation) / 2.0), x: leftInset + floor((baseWidth - audioRecordingCancelIndicator.bounds.size.width - indicatorTranslation) / 2.0),
y: panelHeight - minimalHeight + floor((minimalHeight - audioRecordingCancelIndicator.bounds.size.height) / 2.0)), y: panelHeight - minimalHeight + floor((minimalHeight - audioRecordingCancelIndicator.bounds.size.height) / 2.0)),
size: audioRecordingCancelIndicator.bounds.size) size: audioRecordingCancelIndicator.bounds.size)
audioRecordingCancelIndicator.frame = audioRecordingCancelIndicatorFrame
if self.actionButtons.micButton.cancelTranslation > cancelTransformThreshold { if self.actionButtons.micButton.cancelTranslation > cancelTransformThreshold {
let progress = 1 - (self.actionButtons.micButton.cancelTranslation - cancelTransformThreshold) / 80 //let progress = 1 - (self.actionButtons.micButton.cancelTranslation - cancelTransformThreshold) / 80
let progress: CGFloat = max(0.0, min(1.0, (audioRecordingCancelIndicatorFrame.minX - 100.0) / 10.0))
audioRecordingCancelIndicator.alpha = progress audioRecordingCancelIndicator.alpha = progress
} else { } else {
audioRecordingCancelIndicator.alpha = 1 audioRecordingCancelIndicator.alpha = 1

View File

@ -1082,6 +1082,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
private let toggleShouldChannelMessagesSignaturesDisposable = MetaDisposable() private let toggleShouldChannelMessagesSignaturesDisposable = MetaDisposable()
private let selectAddMemberDisposable = MetaDisposable() private let selectAddMemberDisposable = MetaDisposable()
private let addMemberDisposable = MetaDisposable() private let addMemberDisposable = MetaDisposable()
private let preloadHistoryDisposable = MetaDisposable()
private let updateAvatarDisposable = MetaDisposable() private let updateAvatarDisposable = MetaDisposable()
private let currentAvatarMixin = Atomic<TGMediaAvatarMenuMixin?>(value: nil) private let currentAvatarMixin = Atomic<TGMediaAvatarMenuMixin?>(value: nil)
@ -2018,6 +2019,8 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
}) })
if let _ = nearbyPeerDistance { if let _ = nearbyPeerDistance {
self.preloadHistoryDisposable.set(self.context.account.addAdditionalPreloadHistoryPeerId(peerId: peerId))
self.preloadedSticker.set(.single(nil) self.preloadedSticker.set(.single(nil)
|> then(randomGreetingSticker(account: context.account) |> then(randomGreetingSticker(account: context.account)
|> map { item in |> map { item in
@ -2049,6 +2052,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
self.updateAvatarDisposable.dispose() self.updateAvatarDisposable.dispose()
self.selectAddMemberDisposable.dispose() self.selectAddMemberDisposable.dispose()
self.addMemberDisposable.dispose() self.addMemberDisposable.dispose()
self.preloadHistoryDisposable.dispose()
self.preloadStickerDisposable.dispose() self.preloadStickerDisposable.dispose()
} }