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 [Api1_Photo photoEmptyWithPid:pid];
} 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;
if ((flags = [Api1__Environment parseObject:_data offset:_offset implicitSignature:(int32_t)0xa8509bda metaInfo:nil]) == nil)

View File

@ -3,7 +3,7 @@
@implementation Serialization
- (NSUInteger)currentLayer {
return 115;
return 116;
}
- (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.RemoveVideo" = "Remove Video";
"Settings.EditProfileMedia" = "Edit";
"Conversation.Unarchive" = "Unarchive";
"Conversation.UnarchiveDone" = "The chat was moved to your main list.";
"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.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 WebsiteType
import OpenInExternalAppUI
import ScreenCaptureDetection
private func tagsForMessage(_ message: Message) -> MessageTags? {
for media in message.media {
@ -381,6 +382,8 @@ public class GalleryController: ViewController, StandalonePresentableController
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) {
self.context = context
self.source = source
@ -808,6 +811,20 @@ public class GalleryController: ViewController, StandalonePresentableController
self.blocksBackgroundWhenInOverlay = true
self.acceptsFocusWhenInOverlay = 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) {
@ -822,6 +839,7 @@ public class GalleryController: ViewController, StandalonePresentableController
self.context.sharedContext.mediaManager.galleryHiddenMediaManager.removeSource(hiddenMediaManagerIndex)
}
self.updateVisibleDisposable.dispose()
self.screenCaptureEventsDisposable?.dispose()
}
@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) {
var updateItems: [GalleryPagerUpdateItem] = []
var deleteItems: [Int] = []
@ -326,14 +327,20 @@ public final class GalleryPagerNode: ASDisplayNode, UIScrollViewDelegate, UIGest
}
}
for i in 0 ..< items.count {
if i == previousIndexById[items[i].id] {
updateItems.append(GalleryPagerUpdateItem(index: i, previousIndex: i, item: items[i]))
} else {
if self.updateOnReplacement {
for i in 0 ..< items.count {
if (previousIndexById[items[i].id] == nil) {
insertItems.append(GalleryPagerInsertItem(index: i, item: items[i], previousIndex: previousIndexById[items[i].id]))
} else {
updateItems.append(GalleryPagerUpdateItem(index: i, previousIndex: i, item: items[i]))
}
}
} else {
for i in 0 ..< items.count {
insertItems.append(GalleryPagerInsertItem(index: i, item: items[i], previousIndex: previousIndexById[items[i].id]))
}
}
self.transaction(GalleryPagerTransaction(deleteItems: deleteItems, insertItems: insertItems, updateItems: updateItems, focusOnItem: centralItemIndex, synchronous: synchronous))
}
@ -404,7 +411,7 @@ public final class GalleryPagerNode: ASDisplayNode, UIScrollViewDelegate, UIGest
self.ignoreCentralItemIndexUpdate = true
self.centralItemIndex = focusOnItem
self.ignoreCentralItemIndexUpdate = false
self.updateItemNodes(transition: .immediate, forceOffsetReset: true)
self.updateItemNodes(transition: .immediate, forceOffsetReset: true, synchronous: transaction.synchronous)
}
}

View File

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

View File

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

View File

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

View File

@ -2,6 +2,8 @@
@interface TGPhotoVideoEditor : NSObject
+ (void)presentWithContext:(id<LegacyComponentsContext>)context parentController:(TGViewController *)parentController image:(UIImage *)image video:(NSURL *)video didFinishWithImage:(void (^)(UIImage *image))didFinishWithImage didFinishWithVideo:(void (^)(UIImage *image, NSURL *url, TGVideoEditAdjustments *adjustments))didFinishWithVideo 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;
@end

View File

@ -131,7 +131,7 @@
{
for (GPUImageOutput<GPUImageInput> *currentFilter in _initialFilters)
{
[currentFilter setInputRotation:newInputRotation atIndex:(NSInteger)textureIndex];
[currentFilter setInputRotation:newInputRotation atIndex:(NSInteger)textureIndex];
}
}

View File

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

View File

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

View File

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

View File

@ -179,13 +179,16 @@
- (void)updatePassParameters
{
PGBlurToolValue *value = (PGBlurToolValue *)self.displayValue;
PGPhotoBlurPass *blurPass = (PGPhotoBlurPass *)_pass;
blurPass.type = value.type;
blurPass.size = value.size;
blurPass.point = value.point;
blurPass.angle = value.angle;
blurPass.falloff = value.falloff;
if ([value isKindOfClass:[PGBlurToolValue class]]) {
blurPass.type = value.type;
blurPass.size = value.size;
blurPass.point = value.point;
blurPass.angle = value.angle;
blurPass.falloff = value.falloff;
} else {
blurPass.type = PGBlurToolTypeNone;
}
}
- (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)setCIImage:(CIImage *)ciImage;
- (void)updateProcessChain:(bool)force;
- (void)processAnimated:(bool)animated completion:(void (^)(void))completion;
- (void)reprocess;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -983,18 +983,13 @@ typedef enum
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);
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)
{
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)
{
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)
@ -1023,8 +1018,6 @@ typedef enum
[self removeHandleAnimation];
_scrubberHandle.frame = frame;
}
_dotHandle.frame = dotFrame;
}
- (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
{
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);
if (CGSizeEqualToSize(transformedRect.size, CGSizeZero))
transformedRect = CGRectMake(0, 0, videoTrack.naturalSize.width, videoTrack.naturalSize.height);
@ -350,6 +350,10 @@
if (TGOrientationIsSideward(adjustments.cropOrientation, NULL))
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];
[compositionTrack insertTimeRange:timeRange ofTrack:videoTrack atTime:kCMTimeZero error:NULL];
@ -368,7 +372,7 @@
UIImage *overlayImage = nil;
if (adjustments.paintingData.imagePath != nil)
overlayImage = [UIImage imageWithContentsOfFile:adjustments.paintingData.imagePath];
AVMutableVideoComposition *videoComposition;
if (entityRenderer != nil || adjustments.toolsApplied) {
PGPhotoEditor *editor = nil;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -59,6 +59,8 @@
TGPhotoEditorTab _currentTab;
TGPhotoEditorTabController *_currentTabController;
TGMediaEditingContext *_standaloneEditingContext;
UIView *_backgroundView;
UIView *_containerView;
UIView *_wrapperView;
@ -140,6 +142,7 @@
{
_context = context;
_actionHandle = [[ASHandle alloc] initWithDelegate:self releaseOnMainThread:true];
_standaloneEditingContext = [[TGMediaEditingContext alloc] init];
self.automaticallyManageScrollViewInsets = false;
self.autoManageStatusBarBackground = false;
@ -325,7 +328,7 @@
_previewView.clipsToBounds = true;
[_previewView setSnapshotImage:_screenImage];
[_photoEditor setPreviewOutput:_previewView];
[self updatePreviewView];
[self updatePreviewView:true];
if ([self presentedForAvatarCreation]) {
CGSize fittedSize = TGScaleToSize(_photoEditor.originalSize, CGSizeMake(1024, 1024));
@ -352,6 +355,7 @@
if ([self presentedForAvatarCreation] && _item.isVideo) {
_scrubberView = [[TGMediaPickerGalleryVideoScrubber alloc] initWithFrame:CGRectMake(0.0f, 0.0, _portraitToolbarView.frame.size.width, 68.0f)];
_scrubberView.layer.allowsGroupOpacity = true;
_scrubberView.hasDotPicker = true;
_scrubberView.dataSource = self;
_scrubberView.delegate = self;
@ -560,6 +564,9 @@
if (strongSelf->_ignoreDefaultPreviewViewTransitionIn)
{
__strong TGPhotoEditorController *strongSelf = weakSelf;
if (strongSelf == nil)
return;
TGDispatchOnMainThread(^
{
if (strongSelf->_dismissed)
@ -574,13 +581,17 @@
{
[photoEditor processAnimated:false completion:^
{
__strong TGPhotoEditorController *strongSelf = weakSelf;
if (strongSelf == nil)
return;
TGDispatchOnMainThread(^
{
if (strongSelf->_dismissed)
return;
[strongSelf->_previewView performTransitionInWithCompletion:^
{
[strongSelf->_previewView setSnapshotImage:next];
if (!strongSelf.skipInitialTransition)
[strongSelf->_previewView setSnapshotImage:next];
}];
});
}];
@ -837,6 +848,16 @@
[_portraitToolbarView 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
@ -1079,7 +1100,13 @@
rep = imageView;
}
[_currentTabController prepareForCustomTransitionOut];
self.beginCustomTransitionOut([_currentTabController transitionOutReferenceFrame], rep, completion);
TGPhotoEditorTabController *tabController = _currentTabController;
self.beginCustomTransitionOut([_currentTabController transitionOutReferenceFrame], rep, ^{
[tabController finishCustomTransitionOut];
if (completion)
completion();
});
}
else
{
@ -1114,6 +1141,8 @@
{
if (![currentController isDismissAllowed])
return;
[self savePaintingData];
currentController.switchingToTab = tab;
[currentController transitionOutSwitching:true completion:^
@ -1160,12 +1189,16 @@
}
}
if ([self presentedForAvatarCreation])
if ([self presentedForAvatarCreation] && ![self presentedFromCamera])
transitionNoTransitionView = true;
snapshotImage = _screenImage;
}
if (_currentTabController == nil && self.skipInitialTransition) {
[self presentAnimated:true];
}
_switchingTab = true;
if ([_currentTabController isKindOfClass:[TGPhotoAvatarPreviewController class]]) {
@ -1192,7 +1225,11 @@
{
bool skipInitialTransition = (![self presentedFromCamera] && self.navigationController != nil) || self.skipInitialTransition;
TGPhotoAvatarPreviewController *cropController = [[TGPhotoAvatarPreviewController alloc] initWithContext:_context photoEditor:_photoEditor previewView:_previewView scrubberView:_scrubberView dotImageView:_dotImageView fullPreviewView:_fullPreviewView];
TGPhotoAvatarPreviewController *cropController = [[TGPhotoAvatarPreviewController alloc] initWithContext:_context photoEditor:_photoEditor previewView:_previewView];
cropController.scrubberView = _scrubberView;
cropController.dotImageView = _dotImageView;
cropController.dotMarkerView = _dotMarkerView;
cropController.fullPreviewView = _fullPreviewView;
cropController.fromCamera = [self presentedFromCamera];
cropController.skipTransitionIn = skipInitialTransition;
if (snapshotImage != nil)
@ -1230,6 +1267,8 @@
strongSelf->_dotImageView.cropOrientation = strongSelf->_photoEditor.cropOrientation;
strongSelf->_dotImageView.cropMirrored = strongSelf->_photoEditor.cropMirrored;
[strongSelf->_dotImageView updateCropping:true];
[strongSelf updatePreviewView:false];
}
};
cropController.beginTransitionIn = ^UIView *(CGRect *referenceFrame, UIView **parentView, bool *noTransitionView)
@ -1547,7 +1586,7 @@
{
__strong TGPhotoEditorController *strongSelf = weakSelf;
if (strongSelf != nil)
[strongSelf updatePreviewView];
[strongSelf updatePreviewView:true];
};
_currentTabController.tabsChanged = ^
{
@ -1573,10 +1612,16 @@
[self setNeedsUpdateOfScreenEdgesDeferringSystemGestures];
}
- (void)updatePreviewView
- (void)updatePreviewView:(bool)full
{
[_previewView setPaintingImageWithData:_photoEditor.paintingData];
[_previewView setCropRect:_photoEditor.cropRect cropOrientation:_photoEditor.cropOrientation cropRotation:_photoEditor.cropRotation cropMirrored:_photoEditor.cropMirrored originalSize:_photoEditor.originalSize];
if (full)
[_previewView setPaintingImageWithData:_photoEditor.paintingData];
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
@ -1605,6 +1650,27 @@
}
#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
{
_dismissed = true;
@ -1625,16 +1691,20 @@
self.view.frame = targetFrame;
} completion:^(__unused BOOL finished)
{
[_currentTabController finishCustomTransitionOut];
if (self.navigationController != nil) {
[self.navigationController popViewControllerAnimated:false];
} else {
[self dismiss];
if (self.onDismiss)
self.onDismiss();
}
}];
}
else
{
if (self.navigationController != nil) {
[_currentTabController finishCustomTransitionOut];
[self.navigationController popViewControllerAnimated:false];
} else {
[self dismiss];
@ -1667,7 +1737,9 @@
strongSelf.view.userInteractionEnabled = 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];
}
@ -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
{
if (![_currentTabController isDismissAllowed])
@ -1757,8 +1844,6 @@
self.view.userInteractionEnabled = false;
[_currentTabController prepareTransitionOutSaving:true];
TGPaintingData *paintingData = _photoEditor.paintingData;
bool saving = true;
NSTimeInterval videoStartValue = 0.0;
NSTimeInterval trimStartValue = 0.0;
@ -1766,12 +1851,7 @@
if ([_currentTabController isKindOfClass:[TGPhotoPaintController class]])
{
TGPhotoPaintController *paintController = (TGPhotoPaintController *)_currentTabController;
paintingData = [paintController paintingData];
_photoEditor.paintingData = paintingData;
if (paintingData != nil)
[TGPaintingData storePaintingData:paintingData inContext:_editingContext forItem:_item forVideo:(_intent == TGPhotoEditorControllerVideoIntent)];
[self savePaintingData];
}
else if ([_currentTabController isKindOfClass:[TGPhotoQualityController class]])
{
@ -1789,6 +1869,7 @@
[self stopVideoPlayback:true];
TGPaintingData *paintingData = _photoEditor.paintingData;
TGVideoEditAdjustments *adjustments = [_photoEditor exportAdjustmentsWithPaintingData:paintingData];
if ([self presentedForAvatarCreation] && _item.isVideo) {
[[SQueue concurrentDefaultQueue] dispatch:^
@ -1826,17 +1907,21 @@
if (adjustments.toolsApplied) {
image = [PGPhotoEditor resultImageForImage:image adjustments:adjustments];
CGSize fillSize = TGScaleToFillSize(videoDimensions, image.size);
UIGraphicsBeginImageContextWithOptions(fillSize, true, 0.0f);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetInterpolationQuality(context, kCGInterpolationMedium);
[image drawInRect:CGRectMake(0, 0, fillSize.width, fillSize.height)];
[paintingImage drawInRect:CGRectMake(0, 0, fillSize.width, fillSize.height)];
fullImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
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);
UIGraphicsBeginImageContextWithOptions(fillSize, true, 0.0f);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetInterpolationQuality(context, kCGInterpolationMedium);
[image drawInRect:CGRectMake(0, 0, fillSize.width, fillSize.height)];
[paintingImage drawInRect:CGRectMake(0, 0, fillSize.width, fillSize.height)];
fullImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
}
} else {
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();
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
{
if (_intent == TGPhotoEditorControllerVideoIntent)
@ -2218,6 +2311,8 @@
_initializedScrubber = true;
[_scrubberView reloadData];
[_scrubberView resetToStart];
if (_isPlaying)
[_scrubberView _updateScrubberAnimationsAndResetCurrentPosition:true];
} else {
if (previousWidth != _portraitToolbarView.frame.size.width)
[_scrubberView reloadThumbnails];
@ -2307,14 +2402,16 @@
if (self.requestOriginalScreenSizeImage == nil)
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)
return [SSignal fail:nil];
return [SSignal single:result];
}];
SSignal *imageSignal = [self.requestOriginalScreenSizeImage(item, 0) take:1];
SSignal *detectSignal = [[imageSignal filter:^bool(UIImage *image)
SSignal *imageSignal = self.requestOriginalScreenSizeImage(item, 0);
SSignal *detectSignal = [[[imageSignal filter:^bool(UIImage *image)
{
if (![image isKindOfClass:[UIImage class]])
return false;
@ -2323,7 +2420,7 @@
return false;
return true;
}] mapToSignal:^SSignal *(UIImage *image) {
}] take:1] mapToSignal:^SSignal *(UIImage *image) {
return [[TGPaintFaceDetector detectFacesInImage:image originalSize:originalSize] startOn:[SQueue concurrentDefaultQueue]];
}];
@ -2572,7 +2669,7 @@
return !strongSelf->_scrubberView.isScrubbing;
}];
dispatch_async(dispatch_get_main_queue(), ^{
TGDispatchAfter(0.16, dispatch_get_main_queue(), ^{
[self updateDotImage:true];
});
}
@ -2675,29 +2772,36 @@
return;
id<TGMediaEditAdjustments> adjustments = [_photoEditor exportAdjustments];
__weak TGPhotoEditorController *weakSelf = self;
SSignal *thumbnailsSignal = nil;
if (_cachedThumbnails != nil) {
thumbnailsSignal = [SSignal single:_cachedThumbnails];
} 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) {
__strong TGPhotoEditorController *strongSelf = weakSelf;
if (strongSelf == nil)
return;
if (strongSelf->_cachedThumbnails == nil)
strongSelf->_cachedThumbnails = 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]];
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;
__weak TGPhotoEditorController *weakSelf = self;
[_thumbnailsDisposable setDisposable:[[[[thumbnailsSignal onNext:^(NSArray *images) {
__strong TGPhotoEditorController *strongSelf = weakSelf;
if (strongSelf == nil)
return;
if (strongSelf->_cachedThumbnails == nil)
strongSelf->_cachedThumbnails = images;
}] map:^NSArray *(NSArray *images) {
[_thumbnailsDisposable setDisposable:[[[thumbnailsSignal map:^NSArray *(NSArray *images) {
if (adjustments.toolsApplied) {
NSMutableArray *editedImages = [[NSMutableArray alloc] init];
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
{
@ -300,6 +305,9 @@ const CGFloat TGPhotoEditorToolbarSize = 49.0f;
[parentView addSubview:toTransitionView];
if (_noTransitionToSnapshot)
toTransitionView.alpha = 0.0f;
UIInterfaceOrientation orientation = [[LegacyComponentsGlobals provider] applicationStatusBarOrientation];
if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad)
orientation = UIInterfaceOrientationPortrait;
@ -498,4 +506,9 @@ const CGFloat TGPhotoEditorToolbarSize = 49.0f;
return highlightedButtons;
}
- (bool)presentedForAvatarCreation
{
return _intent & (TGPhotoEditorControllerAvatarIntent | TGPhotoEditorControllerSignupAvatarIntent);
}
@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 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);
*mirrored = (scale.width < 0);
}
if (fabs(videoRotation - M_PI) < FLT_EPSILON)
if (fabs(videoRotation - M_PI) < FLT_EPSILON) {
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;
else if (fabs(videoRotation + M_PI_2) < FLT_EPSILON)
} else if (fabs(videoRotation + M_PI_2) < FLT_EPSILON) {
return UIImageOrientationDown;
else
} else {
return UIImageOrientationRight;
}
}
UIImageOrientation TGVideoFinalOrientationForOrientation(UIImageOrientation videoOrientation, UIImageOrientation cropOrientation)

View File

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

View File

@ -160,8 +160,9 @@ const CGFloat TGPhotoEditorToolsLandscapePanelSize = TGPhotoEditorToolsPanelSize
if (strongSelf != nil) {
[strongSelf->_hudView setText:nil];
if (forVideo) {
strongSelf->_photoEditor.disableAll = false;
strongSelf->_photoEditor.disableAll = false;
if (!forVideo) {
[strongSelf->_photoEditor processAnimated:false completion:nil];
}
}
};
@ -171,8 +172,9 @@ const CGFloat TGPhotoEditorToolsLandscapePanelSize = TGPhotoEditorToolsPanelSize
if (strongSelf != nil) {
[strongSelf->_hudView setText:TGLocalized(@"PhotoEditor.Original")];
if (forVideo) {
strongSelf->_photoEditor.disableAll = true;
strongSelf->_photoEditor.disableAll = true;
if (!forVideo) {
[strongSelf->_photoEditor processAnimated:false completion:nil];
}
}
};
@ -944,6 +946,14 @@ const CGFloat TGPhotoEditorToolsLandscapePanelSize = TGPhotoEditorToolsPanelSize
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];
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);
[UIView performWithoutAnimation:^

View File

@ -8,8 +8,99 @@
#import "TGMediaPickerGalleryVideoItemView.h"
#import "LegacyComponentsInternal.h"
@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
{
id<LegacyComponentsOverlayWindowManager> windowManager = [context makeOverlayWindowManager];

View File

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

View File

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

View File

@ -408,8 +408,24 @@ public func legacyAssetPickerEnqueueMessages(account: Account, signals: [Any]) -
var finalDuration: Double
switch data {
case let .asset(asset):
finalDimensions = asset.dimensions
finalDuration = asset.videoDuration
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
finalDuration = asset.videoDuration
}
case let .tempFile(_, dimensions, duration):
finalDimensions = dimensions
finalDuration = duration

View File

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

View File

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

View File

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

View File

@ -10,6 +10,9 @@ import SyncCore
import TelegramPresentationData
import AccountContext
import GalleryUI
import LegacyComponents
import LegacyMediaPickerUI
import SaveToCameraRoll
public enum AvatarGalleryEntryId: Hashable {
case topImage
@ -180,10 +183,14 @@ public class AvatarGalleryController: ViewController, StandalonePresentableContr
private let centralItemTitle = Promise<String>()
private let centralItemTitleView = Promise<UIView?>()
private let centralItemRightBarButtonItems = Promise<[UIBarButtonItem]?>(nil)
private let centralItemNavigationStyle = Promise<GalleryItemNodeNavigationStyle>()
private let centralItemFooterContentNode = Promise<(GalleryFooterContentNode?, GalleryOverlayContentNode?)>()
private let centralItemAttributesDisposable = DisposableSet();
public var avatarPhotoEditCompletion: ((UIImage) -> Void)?
public var avatarVideoEditCompletion: ((UIImage, URL, TGVideoEditAdjustments?) -> Void)?
private let _hiddenMedia = Promise<AvatarGalleryEntry?>(nil)
public var hiddenMedia: Signal<AvatarGalleryEntry?, NoError> {
return self._hiddenMedia.get()
@ -191,6 +198,8 @@ public class AvatarGalleryController: ViewController, StandalonePresentableContr
private let replaceRootController: (ViewController, Promise<Bool>?) -> Void
private let editDisposable = MetaDisposable ()
public init(context: AccountContext, peer: Peer, sourceHasRoundCorners: Bool = true, remoteEntries: Promise<[AvatarGalleryEntry]>? = nil, centralEntryIndex: Int? = nil, replaceRootController: @escaping (ViewController, Promise<Bool>?) -> Void, synchronousLoad: Bool = false) {
self.context = context
self.peer = peer
@ -264,7 +273,9 @@ public class AvatarGalleryController: ViewController, StandalonePresentableContr
self?.deleteEntry(entry)
} : nil, setMain: { [weak self] in
self?.setMainEntry(entry)
})
}, edit: { [weak self] in
self?.editEntry(entry)
})
}), centralItemIndex: strongSelf.centralEntryIndex, synchronous: !isFirstTime)
let ready = strongSelf.galleryNode.pager.ready() |> timeout(2.0, queue: Queue.mainQueue(), alternate: .single(Void())) |> afterNext { [weak strongSelf] _ in
@ -313,6 +324,10 @@ public class AvatarGalleryController: ViewController, StandalonePresentableContr
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?.galleryNode.updatePresentationState({
$0.withUpdatedFooterContentNode(footerContentNode)
@ -327,6 +342,7 @@ public class AvatarGalleryController: ViewController, StandalonePresentableContr
deinit {
self.disposable.dispose()
self.centralItemAttributesDisposable.dispose()
self.editDisposable.dispose()
}
@objc func donePressed() {
@ -379,6 +395,7 @@ public class AvatarGalleryController: ViewController, StandalonePresentableContr
self.displayNode = GalleryControllerNode(controllerInteraction: controllerInteraction)
self.displayNodeDidLoad()
self.galleryNode.pager.updateOnReplacement = true
self.galleryNode.statusBar = self.statusBar
self.galleryNode.navigationBar = self.navigationBar
@ -421,6 +438,8 @@ public class AvatarGalleryController: ViewController, StandalonePresentableContr
self?.deleteEntry(entry)
} : nil, setMain: { [weak self] in
self?.setMainEntry(entry)
}, edit: { [weak self] in
self?.editEntry(entry)
}) }), centralItemIndex: self.centralEntryIndex)
self.galleryNode.pager.centralItemIndexUpdated = { [weak self] index in
@ -432,6 +451,7 @@ public class AvatarGalleryController: ViewController, StandalonePresentableContr
if let node = strongSelf.galleryNode.pager.centralItemNode() {
strongSelf.centralItemTitle.set(node.title())
strongSelf.centralItemTitleView.set(node.titleView())
strongSelf.centralItemRightBarButtonItems.set(node.rightBarButtonItems())
strongSelf.centralItemNavigationStyle.set(node.navigationStyle())
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 {
self.centralItemTitle.set(centralItemNode.title())
self.centralItemTitleView.set(centralItemNode.titleView())
self.centralItemRightBarButtonItems.set(centralItemNode.rightBarButtonItems())
self.centralItemNavigationStyle.set(centralItemNode.navigationStyle())
self.centralItemFooterContentNode.set(centralItemNode.footerContent())
@ -577,7 +598,9 @@ public class AvatarGalleryController: ViewController, StandalonePresentableContr
self?.deleteEntry(entry)
} : nil, setMain: { [weak self] in
self?.setMainEntry(entry)
}) }), centralItemIndex: 0)
}, edit: { [weak self] in
self?.editEntry(entry)
}) }), centralItemIndex: 0, synchronous: true)
self.entries = entries
}
} else {
@ -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) {
var entry = rawEntry
if case .topImage = entry, !self.entries.isEmpty {

View File

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

View File

@ -640,7 +640,7 @@ func dataAndStorageController(context: AccountContext, focusOnItemTag: DataAndSt
pushControllerImpl?(controller)
}, openIntents: {
let controller = intentsSettingsController(context: context)
pushControllerImpl?(controller)
pushControllerImpl?(controller)
}, toggleEnableSensitiveContent: { value in
let _ = (contentSettingsConfiguration.get()
|> take(1)

View File

@ -549,7 +549,7 @@ func editSettingsController(context: AccountContext, currentName: ItemListAvatar
hasPhotos = true
}
let completedPhotoImpl: (UIImage) -> Void = { image in
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)
@ -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) {
let photoResource = LocalFileMediaResource(fileId: arc4random64())
context.account.postbox.mediaBox.storeResourceData(photoResource.id, data: data)
@ -675,18 +675,18 @@ func editSettingsController(context: AccountContext, currentName: ItemListAvatar
mixin.requestSearchController = { assetsController in
let controller = WebSearchController(context: context, peer: peer, configuration: searchBotsConfiguration, mode: .avatar(initialQuery: nil, completion: { result in
assetsController?.dismiss()
completedPhotoImpl(result)
completedProfilePhotoImpl(result)
}))
presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
}
mixin.didFinishWithImage = { image in
if let image = image {
completedPhotoImpl(image)
completedProfilePhotoImpl(image)
}
}
mixin.didFinishWithVideo = { image, url, adjustments in
if let image = image, let url = url {
completedVideoImpl(image, url, adjustments)
completedProfileVideoImpl(image, url, adjustments)
}
}
mixin.didFinishWithDelete = {
@ -727,6 +727,12 @@ func editSettingsController(context: AccountContext, currentName: ItemListAvatar
if peer.smallProfileImage != nil {
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
return nil
}))

View File

@ -60,6 +60,7 @@ private enum PrivacyAndSecuritySection: Int32 {
public enum PrivacyAndSecurityEntryTag: ItemListItemTag {
case accountTimeout
case autoArchive
public func isEqual(to other: ItemListItemTag) -> Bool {
if let other = other as? PrivacyAndSecurityEntryTag, self == other {
@ -333,7 +334,7 @@ private enum PrivacyAndSecurityEntry: ItemListNodeEntry {
case let .autoArchive(text, value):
return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, sectionId: self.section, style: .blocks, updated: { value in
arguments.toggleArchiveAndMuteNonContacts(value)
})
}, tag: PrivacyAndSecurityEntryTag.autoArchive)
case let .autoArchiveInfo(text):
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
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
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 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: {
var updating = false
updateState {
@ -1013,6 +1126,12 @@ public func settingsController(context: AccountContext, accountManager: AccountM
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
avatarAndNameInfoContext.hiddenAvatarRepresentation = entry?.representations.last?.representation
updateHiddenAvatarImpl?()
@ -1061,7 +1180,7 @@ public func settingsController(context: AccountContext, accountManager: AccountM
blockedPeers.set(.single(blockedPeersContext))
}, updatedHasTwoStepAuth: { hasTwoStepAuthValue in
hasTwoStepAuthPromise.set(.single(hasTwoStepAuthValue))
}, activeSessionsContext: activeSessionsContext, webSessionsContext: webSessionsContext, blockedPeersContext: blockedPeersContext, hasTwoStepAuth: hasTwoStepAuth))
}, focusOnItemTag: nil, activeSessionsContext: activeSessionsContext, webSessionsContext: webSessionsContext, blockedPeersContext: blockedPeersContext, hasTwoStepAuth: hasTwoStepAuth))
})
})
}, openDataAndStorage: {
@ -1284,137 +1403,24 @@ public func settingsController(context: AccountContext, accountManager: AccountM
if let peer = peer, !peer.profileImageRepresentations.isEmpty {
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 _ = currentAvatarMixin.swap(mixin)
mixin.requestSearchController = { assetsController in
let controller = WebSearchController(context: context, peer: peer, configuration: searchBotsConfiguration, mode: .avatar(initialQuery: nil, completion: { result in
assetsController?.dismiss()
completedImpl(result)
completedProfilePhotoImpl(result)
}))
presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
}
mixin.didFinishWithImage = { image in
if let image = image {
completedImpl(image)
completedProfilePhotoImpl(image)
}
}
mixin.didFinishWithVideo = { image, url, adjustments in
if let image = image, let url = url {
completedVideoImpl(image, url, adjustments)
completedProfileVideoImpl(image, url, adjustments)
}
}
mixin.didFinishWithDelete = {
@ -2041,5 +2047,5 @@ private func accountContextMenuItems(context: AccountContext, logout: @escaping
}
public func makePrivacyAndSecurityController(context: AccountContext) -> ViewController {
return privacyAndSecurityController(context: context)
return privacyAndSecurityController(context: context, focusOnItemTag: PrivacyAndSecurityEntryTag.autoArchive)
}

View File

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

View File

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

View File

@ -16,6 +16,9 @@ import PresentationDataUtils
import AppBundle
import GraphUI
import ItemListPeerItem
import ItemListPeerActionItem
private let maxUsersDisplayedLimit: Int32 = 10
private final class GroupStatsControllerArguments {
let context: AccountContext
@ -24,17 +27,23 @@ private final class GroupStatsControllerArguments {
let openPeerHistory: (PeerId) -> Void
let openPeerAdminActions: (PeerId) -> Void
let promotePeer: (PeerId) -> Void
let expandTopPosters: () -> Void
let expandTopAdmins: () -> Void
let expandTopInviters: () -> Void
let setPostersPeerIdWithRevealedOptions: (PeerId?, PeerId?) -> Void
let setAdminsPeerIdWithRevealedOptions: (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.loadDetailedGraph = loadDetailedGraph
self.openPeer = openPeer
self.openPeerHistory = openPeerHistory
self.openPeerAdminActions = openPeerAdminActions
self.promotePeer = promotePeer
self.expandTopPosters = expandTopPosters
self.expandTopAdmins = expandTopAdmins
self.expandTopInviters = expandTopInviters
self.setPostersPeerIdWithRevealedOptions = setPostersPeerIdWithRevealedOptions
self.setAdminsPeerIdWithRevealedOptions = setAdminsPeerIdWithRevealedOptions
self.setInvitersPeerIdWithRevealedOptions = setInvitersPeerIdWithRevealedOptions
@ -86,13 +95,16 @@ private enum StatsEntry: ItemListNodeEntry {
case topPostersTitle(PresentationTheme, String, String)
case topPoster(Int32, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, Peer, GroupStatsTopPoster, Bool)
case topPostersExpand(PresentationTheme, String)
case topAdminsTitle(PresentationTheme, String, String)
case topAdmin(Int32, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, Peer, GroupStatsTopAdmin, Bool)
case topAdminsExpand(PresentationTheme, String)
case topInvitersTitle(PresentationTheme, String, String)
case topInviter(Int32, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, Peer, GroupStatsTopInviter, Bool)
case topInvitersExpand(PresentationTheme, String)
var section: ItemListSectionId {
switch self {
case .overviewTitle, .overview:
@ -113,11 +125,11 @@ private enum StatsEntry: ItemListNodeEntry {
return StatsSection.topHours.rawValue
case .topWeekdaysTitle, .topWeekdaysGraph:
return StatsSection.topWeekdays.rawValue
case .topPostersTitle, .topPoster:
case .topPostersTitle, .topPoster, .topPostersExpand:
return StatsSection.topPosters.rawValue
case .topAdminsTitle, .topAdmin:
case .topAdminsTitle, .topAdmin, .topAdminsExpand:
return StatsSection.topAdmins.rawValue
case .topInvitersTitle, .topInviter:
case .topInvitersTitle, .topInviter, .topInvitersExpand:
return StatsSection.topInviters.rawValue
}
}
@ -164,14 +176,20 @@ private enum StatsEntry: ItemListNodeEntry {
return 1000
case let .topPoster(index, _, _, _, _, _, _):
return 1001 + index
case .topPostersExpand:
return 1999
case .topAdminsTitle:
return 2000
case let .topAdmin(index, _, _, _, _, _, _):
return 2001 + index
case .topAdminsExpand:
return 2999
case .topInvitersTitle:
return 3000
case let .topInviter(index, _, _, _, _, _, _):
return 30001 + index
return 3001 + index
case .topInvitersExpand:
return 3999
}
}
@ -297,6 +315,12 @@ private enum StatsEntry: ItemListNodeEntry {
} else {
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):
if case let .topAdminsTitle(rhsTheme, rhsText, rhsDates) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsDates == rhsDates {
return true
@ -309,6 +333,12 @@ private enum StatsEntry: ItemListNodeEntry {
} else {
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):
if case let .topInvitersTitle(rhsTheme, rhsText, rhsDates) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsDates == rhsDates {
return true
@ -321,6 +351,12 @@ private enum StatsEntry: ItemListNodeEntry {
} else {
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
arguments.setPostersPeerIdWithRevealedOptions(peerId, fromPeerId)
}, 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):
var textComponents: [String] = []
if topAdmin.deletedCount > 0 {
@ -400,6 +440,10 @@ private enum StatsEntry: ItemListNodeEntry {
}, setPeerIdWithRevealedOptions: { peerId, fromPeerId in
arguments.setAdminsPeerIdWithRevealedOptions(peerId, fromPeerId)
}, 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):
var textComponents: [String] = []
textComponents.append(strings.Stats_GroupTopInviterInvites(topInviter.inviteCount))
@ -415,6 +459,10 @@ private enum StatsEntry: ItemListNodeEntry {
}, setPeerIdWithRevealedOptions: { peerId, fromPeerId in
arguments.setInvitersPeerIdWithRevealedOptions(peerId, fromPeerId)
}, 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 {
entries.append(.topPostersTitle(presentationData.theme, presentationData.strings.Stats_GroupTopPostersTitle, dates))
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 {
entries.append(.topPoster(index, presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, peer, topPoster, topPoster.peerId == state.posterPeerIdWithRevealedOptions))
index += 1
}
}
if !effectiveExpanded {
entries.append(.topPostersExpand(presentationData.theme, presentationData.strings.Stats_GroupShowMoreTopPosters(Int32(data.topPosters.count) - maxUsersDisplayedLimit)))
}
}
if !data.topAdmins.isEmpty {
entries.append(.topAdminsTitle(presentationData.theme, presentationData.strings.Stats_GroupTopAdminsTitle, dates))
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 {
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))
index += 1
}
}
if !effectiveExpanded {
entries.append(.topAdminsExpand(presentationData.theme, presentationData.strings.Stats_GroupShowMoreTopAdmins(Int32(data.topAdmins.count) - maxUsersDisplayedLimit)))
}
}
if !data.topInviters.isEmpty {
entries.append(.topInvitersTitle(presentationData.theme, presentationData.strings.Stats_GroupTopInvitersTitle, dates))
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 {
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))
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 {
let topPostersExpanded: Bool
let topAdminsExpanded: Bool
let topInvitersExpanded: Bool
let posterPeerIdWithRevealedOptions: PeerId?
let adminPeerIdWithRevealedOptions: PeerId?
let inviterPeerIdWithRevealedOptions: PeerId?
init() {
self.topPostersExpanded = false
self.topAdminsExpanded = false
self.topInvitersExpanded = false
self.posterPeerIdWithRevealedOptions = nil
self.adminPeerIdWithRevealedOptions = 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.adminPeerIdWithRevealedOptions = adminPeerIdWithRevealedOptions
self.inviterPeerIdWithRevealedOptions = inviterPeerIdWithRevealedOptions
}
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 {
return false
}
@ -537,16 +642,28 @@ private struct GroupStatsState: Equatable {
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 {
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 {
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 {
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)
}, promotePeer: { peerId in
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
updateState { state in
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
return updated
})

View File

@ -982,13 +982,15 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
let indicatorTranslation = max(0.0, self.actionButtons.micButton.cancelTranslation - cancelTransformThreshold)
audioRecordingCancelIndicator.frame = CGRect(
let audioRecordingCancelIndicatorFrame = CGRect(
origin: CGPoint(
x: leftInset + floor((baseWidth - audioRecordingCancelIndicator.bounds.size.width - indicatorTranslation) / 2.0),
y: panelHeight - minimalHeight + floor((minimalHeight - audioRecordingCancelIndicator.bounds.size.height) / 2.0)),
size: audioRecordingCancelIndicator.bounds.size)
audioRecordingCancelIndicator.frame = audioRecordingCancelIndicatorFrame
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
} else {
audioRecordingCancelIndicator.alpha = 1

View File

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