mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-08-02 00:17:02 +00:00
Merge commit '04ba76c7e6e52c7c42afb3ad499837b892a2e072' into beta
This commit is contained in:
commit
bd39760895
@ -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)
|
||||
|
@ -3,7 +3,7 @@
|
||||
@implementation Serialization
|
||||
|
||||
- (NSUInteger)currentLayer {
|
||||
return 115;
|
||||
return 116;
|
||||
}
|
||||
|
||||
- (id _Nullable)parseMessage:(NSData * _Nullable)data {
|
||||
|
@ -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";
|
||||
|
@ -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() {
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -39,6 +39,8 @@
|
||||
- (void)closeCurtains;
|
||||
- (void)openCurtains;
|
||||
|
||||
- (void)flash:(void (^)(void))completion;
|
||||
|
||||
- (void)invalidateCropRect;
|
||||
|
||||
- (UIImage *)currentImage;
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -131,7 +131,7 @@
|
||||
{
|
||||
for (GPUImageOutput<GPUImageInput> *currentFilter in _initialFilters)
|
||||
{
|
||||
[currentFilter setInputRotation:newInputRotation atIndex:(NSInteger)textureIndex];
|
||||
[currentFilter setInputRotation:newInputRotation atIndex:(NSInteger)textureIndex];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -15,6 +15,8 @@ extern NSString *const kGPUImageTwoInputTextureVertexShaderString;
|
||||
BOOL firstFrameCheckDisabled, secondFrameCheckDisabled;
|
||||
}
|
||||
|
||||
@property (nonatomic, assign) bool rotateOnlyFirstTexture;
|
||||
|
||||
- (void)disableFirstFrameCheck;
|
||||
- (void)disableSecondFrameCheck;
|
||||
|
||||
|
@ -176,7 +176,7 @@ NSString *const kGPUImageTwoInputTextureVertexShaderString = SHADER_STRING
|
||||
{
|
||||
inputRotation = newInputRotation;
|
||||
}
|
||||
else
|
||||
else if (textureIndex == 1 && !_rotateOnlyFirstTexture)
|
||||
{
|
||||
inputRotation2 = newInputRotation;
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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];
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
|
@ -48,6 +48,8 @@
|
||||
|
||||
- (CGPoint)scrubberPositionForPosition:(NSTimeInterval)position;
|
||||
|
||||
- (void)_updateScrubberAnimationsAndResetCurrentPosition:(bool)resetCurrentPosition;
|
||||
|
||||
@end
|
||||
|
||||
@protocol TGMediaPickerGalleryVideoScrubberDelegate <NSObject>
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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(), ^{
|
||||
|
@ -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];
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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:^
|
||||
|
@ -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];
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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 {
|
||||
|
@ -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))
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
}))
|
||||
|
@ -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):
|
||||
|
@ -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))
|
||||
}),
|
||||
]
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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",
|
||||
|
@ -27,6 +27,7 @@ swift_library(
|
||||
"//submodules/GraphUI:GraphUI",
|
||||
"//submodules/AnimatedStickerNode:AnimatedStickerNode",
|
||||
"//submodules/ItemListPeerItem:ItemListPeerItem",
|
||||
"//submodules/ItemListPeerActionItem:ItemListPeerActionItem",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
})
|
||||
|
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@ -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
|
||||
|
@ -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()
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user