diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index 1ce1014efc..829579c69f 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -5636,5 +5636,5 @@ Any member of this group will be able to see messages in the channel."; "Settings.SetProfilePhotoOrVideo" = "Set Profile Photo or Video"; "Settings.SetNewProfilePhotoOrVideo" = "Set New Profile Photo or Video"; -"Conversation.Unarchive" = "Unarhive"; +"Conversation.Unarchive" = "Unarchive"; "Conversation.UnarchiveDone" = "The chat was moved to your main list."; diff --git a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoToolbarView.h b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoToolbarView.h index 7b49f99965..0b1199d65f 100644 --- a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoToolbarView.h +++ b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoToolbarView.h @@ -3,15 +3,15 @@ typedef NS_OPTIONS(NSUInteger, TGPhotoEditorTab) { TGPhotoEditorNoneTab = 0, TGPhotoEditorCropTab = 1 << 0, - TGPhotoEditorPaintTab = 1 << 1, - TGPhotoEditorEraserTab = 1 << 2, - TGPhotoEditorStickerTab = 1 << 3, - TGPhotoEditorTextTab = 1 << 4, - TGPhotoEditorToolsTab = 1 << 5, - TGPhotoEditorRotateTab = 1 << 6, - TGPhotoEditorQualityTab = 1 << 7, - TGPhotoEditorTimerTab = 1 << 8, - TGPhotoEditorMirrorTab = 1 << 9, + TGPhotoEditorRotateTab = 1 << 1, + TGPhotoEditorMirrorTab = 1 << 2, + TGPhotoEditorPaintTab = 1 << 3, + TGPhotoEditorEraserTab = 1 << 4, + TGPhotoEditorStickerTab = 1 << 5, + TGPhotoEditorTextTab = 1 << 6, + TGPhotoEditorToolsTab = 1 << 7, + TGPhotoEditorQualityTab = 1 << 8, + TGPhotoEditorTimerTab = 1 << 9, TGPhotoEditorAspectRatioTab = 1 << 10, TGPhotoEditorTintTab = 1 << 11, TGPhotoEditorBlurTab = 1 << 12, diff --git a/submodules/LegacyComponents/Sources/TGAttachmentCarouselItemView.m b/submodules/LegacyComponents/Sources/TGAttachmentCarouselItemView.m index ec3d86da96..e0df01fcd5 100644 --- a/submodules/LegacyComponents/Sources/TGAttachmentCarouselItemView.m +++ b/submodules/LegacyComponents/Sources/TGAttachmentCarouselItemView.m @@ -908,8 +908,6 @@ const NSUInteger TGAttachmentDisplayedAssetLimit = 500; if (strongSelf.avatarCompletionBlock != nil) strongSelf.avatarCompletionBlock(resultImage); - - [strongController dismissAnimated:true]; }; controller.didFinishEditingVideo = ^(NSURL *url, id adjustments, UIImage *resultImage, UIImage *thumbnailImage, bool hasChanges) { if (!hasChanges) @@ -925,8 +923,6 @@ const NSUInteger TGAttachmentDisplayedAssetLimit = 500; if (strongSelf.avatarVideoCompletionBlock != nil) strongSelf.avatarVideoCompletionBlock(resultImage, url, adjustments); - - [strongController dismissAnimated:true]; }; controller.requestThumbnailImage = ^(id editableItem) { diff --git a/submodules/LegacyComponents/Sources/TGCameraController.m b/submodules/LegacyComponents/Sources/TGCameraController.m index 782fce4872..506b5790fb 100644 --- a/submodules/LegacyComponents/Sources/TGCameraController.m +++ b/submodules/LegacyComponents/Sources/TGCameraController.m @@ -1012,7 +1012,9 @@ static CGPoint TGCameraControllerClampPointToScreenSize(__unused id self, __unus TGCameraCapturedVideo *capturedVideo = [[TGCameraCapturedVideo alloc] initWithURL:outputURL]; if (strongSelf->_intent == TGCameraControllerAvatarIntent || strongSelf->_intent == TGCameraControllerSignupAvatarIntent) { - [strongSelf presentPhotoResultControllerWithImage:capturedVideo metadata:nil completion:^{}]; + [strongSelf presentPhotoResultControllerWithImage:capturedVideo metadata:nil completion:^{ + [strongSelf->_interfaceView setRecordingVideo:false animated:true]; + }]; } else { [strongSelf addResultItem:capturedVideo]; if (![strongSelf maybePresentResultControllerForItem:capturedVideo completion:nil]) diff --git a/submodules/LegacyComponents/Sources/TGMediaPickerGalleryVideoScrubber.h b/submodules/LegacyComponents/Sources/TGMediaPickerGalleryVideoScrubber.h index 0dca57f0e3..9f1d7478af 100644 --- a/submodules/LegacyComponents/Sources/TGMediaPickerGalleryVideoScrubber.h +++ b/submodules/LegacyComponents/Sources/TGMediaPickerGalleryVideoScrubber.h @@ -44,6 +44,8 @@ - (void)setRecipientName:(NSString *)recipientName; +- (CGPoint)scrubberPositionForPosition:(NSTimeInterval)position; + @end @protocol TGMediaPickerGalleryVideoScrubberDelegate diff --git a/submodules/LegacyComponents/Sources/TGMediaPickerGalleryVideoScrubber.m b/submodules/LegacyComponents/Sources/TGMediaPickerGalleryVideoScrubber.m index 5890822d49..a9425d6988 100644 --- a/submodules/LegacyComponents/Sources/TGMediaPickerGalleryVideoScrubber.m +++ b/submodules/LegacyComponents/Sources/TGMediaPickerGalleryVideoScrubber.m @@ -1286,6 +1286,11 @@ typedef enum } } +- (CGPoint)scrubberPositionForPosition:(NSTimeInterval)position +{ + return [self _scrubberPositionForPosition:position duration:_duration]; +} + - (CGPoint)_scrubberPositionForPosition:(NSTimeInterval)position duration:(NSTimeInterval)duration { return [self _scrubberPositionForPosition:position duration:duration zoomedIn:_zoomedIn]; diff --git a/submodules/LegacyComponents/Sources/TGPhotoAvatarCropController.m b/submodules/LegacyComponents/Sources/TGPhotoAvatarCropController.m index cf938b2d81..f5322f081f 100644 --- a/submodules/LegacyComponents/Sources/TGPhotoAvatarCropController.m +++ b/submodules/LegacyComponents/Sources/TGPhotoAvatarCropController.m @@ -374,15 +374,24 @@ const CGFloat TGPhotoAvatarCropButtonsWrapperSize = 61.0f; [self.view addSubview:snapshotView]; CGRect targetCropViewFrame = [self.view convertRect:targetFrame toView:_wrapperView]; + + if (!self.item.isVideo) { + _previewView.hidden = true; + } [UIView animateWithDuration:0.3f delay:0.0f options:UIViewAnimationOptionCurveEaseInOut | UIViewAnimationOptionLayoutSubviews animations:^ { snapshotView.frame = targetFrame; - snapshotView.alpha = 1.0f; + if (self.item.isVideo) { + _cropView.alpha = 0.0f; + } else { + snapshotView.alpha = 1.0f; + } _cropView.frame = targetCropViewFrame; [_cropView invalidateCropRect]; } completion:^(__unused BOOL finished) { + _previewView.hidden = false; if (self.finishedTransitionOut != nil) self.finishedTransitionOut(); }]; diff --git a/submodules/LegacyComponents/Sources/TGPhotoAvatarPreviewController.h b/submodules/LegacyComponents/Sources/TGPhotoAvatarPreviewController.h index 6d2fd865ce..a189c70d52 100644 --- a/submodules/LegacyComponents/Sources/TGPhotoAvatarPreviewController.h +++ b/submodules/LegacyComponents/Sources/TGPhotoAvatarPreviewController.h @@ -7,10 +7,9 @@ @interface TGPhotoAvatarPreviewController : TGPhotoEditorTabController -- (instancetype)initWithContext:(id)context photoEditor:(PGPhotoEditor *)photoEditor previewView:(TGPhotoEditorPreviewView *)previewView scrubberView:(TGMediaPickerGalleryVideoScrubber *)scrubberView; +- (instancetype)initWithContext:(id)context photoEditor:(PGPhotoEditor *)photoEditor previewView:(TGPhotoEditorPreviewView *)previewView scrubberView:(TGMediaPickerGalleryVideoScrubber *)scrubberView dotImageView:(UIImageView *)dotImageView; -- (void)beginScrubbing; -- (void)endScrubbing:(bool (^)(void))completion; -- (void)setPlayButtonHidden:(bool)hidden animated:(bool)animated; +- (void)beginScrubbing:(bool)flash; +- (void)endScrubbing:(bool)flash completion:(bool (^)(void))completion; @end diff --git a/submodules/LegacyComponents/Sources/TGPhotoAvatarPreviewController.m b/submodules/LegacyComponents/Sources/TGPhotoAvatarPreviewController.m index 8ddf18c572..e1400c8d4a 100644 --- a/submodules/LegacyComponents/Sources/TGPhotoAvatarPreviewController.m +++ b/submodules/LegacyComponents/Sources/TGPhotoAvatarPreviewController.m @@ -26,6 +26,9 @@ const CGFloat TGPhotoAvatarPreviewLandscapePanelSize = TGPhotoAvatarPreviewPanel TGPhotoEditorSparseView *_wrapperView; TGMediaPickerGalleryVideoScrubber *_scrubberView; + + UIImageView *_dotImageView; + UIView *_portraitToolsWrapperView; UIView *_landscapeToolsWrapperView; UIView *_portraitWrapperBackgroundView; @@ -35,8 +38,6 @@ const CGFloat TGPhotoAvatarPreviewLandscapePanelSize = TGPhotoAvatarPreviewPanel UIView *_flashView; UIView *_portraitToolControlView; UIView *_landscapeToolControlView; - UIImageView *_areaMaskView; - CGFloat _currentDiameter; UILabel *_coverLabel; } @@ -47,7 +48,7 @@ const CGFloat TGPhotoAvatarPreviewLandscapePanelSize = TGPhotoAvatarPreviewPanel @implementation TGPhotoAvatarPreviewController -- (instancetype)initWithContext:(id)context photoEditor:(PGPhotoEditor *)photoEditor previewView:(TGPhotoEditorPreviewView *)previewView scrubberView:(TGMediaPickerGalleryVideoScrubber *)scrubberView +- (instancetype)initWithContext:(id)context photoEditor:(PGPhotoEditor *)photoEditor previewView:(TGPhotoEditorPreviewView *)previewView scrubberView:(TGMediaPickerGalleryVideoScrubber *)scrubberView dotImageView:(UIImageView *)dotImageView { self = [super initWithContext:context]; if (self != nil) @@ -55,6 +56,8 @@ const CGFloat TGPhotoAvatarPreviewLandscapePanelSize = TGPhotoAvatarPreviewPanel self.photoEditor = photoEditor; self.previewView = previewView; _scrubberView = scrubberView; + + _dotImageView = dotImageView; } return self; } @@ -92,28 +95,26 @@ const CGFloat TGPhotoAvatarPreviewLandscapePanelSize = TGPhotoAvatarPreviewPanel _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]; + _coverLabel.font = TGSystemFontOfSize(14.0f); + _coverLabel.textColor = [UIColor whiteColor]; + _coverLabel.text = TGLocalized(@"PhotoEditor.SelectCoverFrame"); + [_coverLabel sizeToFit]; + [_portraitToolsWrapperView addSubview:_coverLabel]; + + [_wrapperView addSubview:_dotImageView]; } - - _flashView = [[UIView alloc] init]; - _flashView.alpha = 0.0; - _flashView.backgroundColor = [UIColor whiteColor]; - _flashView.userInteractionEnabled = false; - [_videoAreaView addSubview:_flashView]; - - _areaMaskView = [[UIImageView alloc] init]; - _areaMaskView.alpha = 0.0f; - [self.view insertSubview:_areaMaskView aboveSubview:_videoAreaView]; - - [_portraitToolsWrapperView addSubview:_scrubberView]; - - _coverLabel = [[UILabel alloc] init]; - _coverLabel.alpha = 0.7f; - _coverLabel.backgroundColor = [UIColor clearColor]; - _coverLabel.font = TGSystemFontOfSize(14.0f); - _coverLabel.textColor = [UIColor whiteColor]; - _coverLabel.text = TGLocalized(@"PhotoEditor.SelectCoverFrame"); - [_coverLabel sizeToFit]; - [_portraitToolsWrapperView addSubview:_coverLabel]; } - (void)viewDidAppear:(BOOL)animated @@ -232,7 +233,6 @@ const CGFloat TGPhotoAvatarPreviewLandscapePanelSize = TGPhotoAvatarPreviewPanel _portraitToolsWrapperView.alpha = 0.0f; _landscapeToolsWrapperView.alpha = 0.0f; _videoAreaView.alpha = 0.0f; - _areaMaskView.alpha = 0.0f; } completion:^(__unused BOOL finished) { if (completion != nil) @@ -320,7 +320,6 @@ const CGFloat TGPhotoAvatarPreviewLandscapePanelSize = TGPhotoAvatarPreviewPanel _portraitToolsWrapperView.alpha = 0.0f; _landscapeToolsWrapperView.alpha = 0.0f; _videoAreaView.alpha = 0.0f; - _areaMaskView.alpha = 0.0f; } completion:nil]; } @@ -400,12 +399,10 @@ const CGFloat TGPhotoAvatarPreviewLandscapePanelSize = TGPhotoAvatarPreviewPanel CGSize referenceSize = [self referenceViewSize]; - CGFloat screenSide = MAX(referenceSize.width, referenceSize.height) + 2 * TGPhotoAvatarPreviewPanelSize; + CGFloat screenSide = MAX(referenceSize.width, referenceSize.height); _wrapperView.frame = CGRectMake((referenceSize.width - screenSide) / 2, (referenceSize.height - screenSide) / 2, screenSide, screenSide); CGFloat panelSize = UIInterfaceOrientationIsPortrait(orientation) ? TGPhotoAvatarPreviewPanelSize : TGPhotoAvatarPreviewLandscapePanelSize; -// if (_portraitToolControlView != nil) -// panelSize = TGPhotoEditorPanelSize; CGFloat panelToolbarPortraitSize = panelSize + TGPhotoEditorToolbarSize; CGFloat panelToolbarLandscapeSize = panelSize + TGPhotoEditorToolbarSize; @@ -497,36 +494,9 @@ const CGFloat TGPhotoAvatarPreviewLandscapePanelSize = TGPhotoAvatarPreviewPanel { _videoAreaView.frame = _previewView.frame; _flashView.frame = _videoAreaView.bounds; - _areaMaskView.frame = _previewView.frame; - - [self updateCircleImage]; }]; } -- (void)updateCircleImage -{ - CGFloat diameter = _areaMaskView.frame.size.width; - - if (fabs(diameter - _currentDiameter) < DBL_EPSILON) - return; - - _currentDiameter = diameter; - - UIGraphicsBeginImageContextWithOptions(CGSizeMake(diameter, diameter), false, 0.0f); - CGContextRef context = UIGraphicsGetCurrentContext(); - CGContextSetFillColorWithColor(context, [TGPhotoEditorInterfaceAssets cropTransparentOverlayColor].CGColor); - - UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(0, 0, diameter, diameter)]; - [path appendPath:[UIBezierPath bezierPathWithRect:CGRectMake(0, 0, diameter, diameter)]]; - path.usesEvenOddFillRule = true; - [path fill]; - - UIImage *areaMaskImage = UIGraphicsGetImageFromCurrentImageContext(); - UIGraphicsEndImageContext(); - - _areaMaskView.image = areaMaskImage; -} - - (void)updateLayout:(UIInterfaceOrientation)orientation { if ([self inFormSheet] || TGIsPad()) @@ -540,7 +510,7 @@ const CGFloat TGPhotoAvatarPreviewLandscapePanelSize = TGPhotoAvatarPreviewPanel - (TGPhotoEditorTab)availableTabs { - return TGPhotoEditorCropTab | TGPhotoEditorPaintTab | TGPhotoEditorToolsTab; + return TGPhotoEditorRotateTab | TGPhotoEditorMirrorTab | TGPhotoEditorPaintTab | TGPhotoEditorToolsTab; } - (TGPhotoEditorTab)activeTab @@ -561,58 +531,74 @@ const CGFloat TGPhotoAvatarPreviewLandscapePanelSize = TGPhotoAvatarPreviewPanel return tabs; } -- (void)setPlayButtonHidden:(bool)hidden animated:(bool)animated +- (void)handleTabAction:(TGPhotoEditorTab)tab { -// if (animated) -// { -// _actionButton.hidden = false; -// [UIView animateWithDuration:0.15f animations:^ -// { -// _actionButton.alpha = hidden ? 0.0f : 1.0f; -// } completion:^(BOOL finished) -// { -// if (finished) -// _actionButton.hidden = hidden; -// }]; -// } -// else -// { -// _actionButton.alpha = hidden ? 0.0f : 1.0f; -// _actionButton.hidden = hidden; -// } + switch (tab) + { + case TGPhotoEditorRotateTab: + { + [self rotate]; + } + break; + + case TGPhotoEditorMirrorTab: + { + [self mirror]; + } + break; + + default: + break; + } } -- (void)beginScrubbing -{ - _coverLabel.alpha = 1.0f; - - [self setPlayButtonHidden:true animated:false]; - - [UIView animateWithDuration:0.2 animations:^{ - _areaMaskView.alpha = 1.0f; - }]; +#pragma mark - Cropping + +- (void)rotate { +// [_cropView rotate90DegreesCCWAnimated:true]; } -- (void)endScrubbing:(bool (^)(void))completion +- (void)mirror { +// [_cropView mirror]; +} + +- (void)beginScrubbing:(bool)flash { - [UIView animateWithDuration:0.12 animations:^{ - _flashView.alpha = 1.0f; - } completion:^(BOOL finished) { - [UIView animateWithDuration:0.2 animations:^{ - _flashView.alpha = 0.0f; + 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) { - TGDispatchAfter(1.0, dispatch_get_main_queue(), ^{ - if (completion()) { - [UIView animateWithDuration:0.2 animations:^{ - _areaMaskView.alpha = 0.0f; - _coverLabel.alpha = 0.7f; - }]; - - self.controlVideoPlayback(true); - } - }); + [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); + } + }); + }]; }]; - }]; + } else { + TGDispatchAfter(1.32, dispatch_get_main_queue(), ^{ + if (completion()) { + [UIView animateWithDuration:0.2 animations:^{ + _coverLabel.alpha = 0.7f; + }]; + + self.controlVideoPlayback(true); + } + }); + } } @end diff --git a/submodules/LegacyComponents/Sources/TGPhotoEditorController.m b/submodules/LegacyComponents/Sources/TGPhotoEditorController.m index 09ba175861..9f7312c17d 100644 --- a/submodules/LegacyComponents/Sources/TGPhotoEditorController.m +++ b/submodules/LegacyComponents/Sources/TGPhotoEditorController.m @@ -75,10 +75,13 @@ CMTime _chaseTime; bool _chasingTime; + bool _isPlaying; AVPlayerItem *_playerItem; SMetaDisposable *_playerItemDisposable; id _playerStartedObserver; id _playerReachedEndObserver; + bool _registeredKeypathObserver; + NSTimer *_positionTimer; bool _scheduledVideoPlayback; id _initialAdjustments; @@ -94,15 +97,22 @@ TGMenuContainerView *_menuContainerView; UIDocumentInteractionController *_documentController; + bool _dismissed; + bool _hadProgress; bool _progressVisible; TGMessageImageViewOverlayView *_progressView; SMetaDisposable *_faceDetectorDisposable; + bool _wasPlaying; bool _initializedScrubber; TGMediaPickerGalleryVideoScrubber *_scrubberView; - PGPhotoEditorView *_dotVideoView; + + NSTimeInterval _dotPosition; + UIImageView *_dotMarkerView; + UIImageView *_dotImageView; + UIView *_dotImageSnapshotView; bool _requestingThumbnails; SMetaDisposable *_thumbnailsDisposable; @@ -166,7 +176,6 @@ - (void)dealloc { - [self stopVideoPlayback:true]; [_actionHandle reset]; [_faceDetectorDisposable dispose]; [_thumbnailsDisposable dispose]; @@ -308,10 +317,22 @@ [_photoEditor setPreviewOutput:_previewView]; [self updatePreviewView]; + + _dotMarkerView = [[UIImageView alloc] initWithImage:TGCircleImage(7.0, [TGPhotoEditorInterfaceAssets accentColor])]; + [_scrubberView addSubview:_dotMarkerView]; + _dotMarkerView.center = CGPointMake(30.0, -20.0); + + _dotImageView = [[UIImageView alloc] initWithFrame:CGRectMake(0.0, 0.0, 160.0, 160.0)]; + _dotImageView.userInteractionEnabled = true; + + UITapGestureRecognizer *dotTapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleDotTap)]; + [_dotImageView addGestureRecognizer:dotTapRecognizer]; + if ([self presentedForAvatarCreation] && _item.isVideo) { _scrubberView = [[TGMediaPickerGalleryVideoScrubber alloc] initWithFrame:CGRectMake(0.0f, 0.0, _portraitToolbarView.frame.size.width, 68.0f)]; _scrubberView.dataSource = self; _scrubberView.delegate = self; + _scrubberView.clipsToBounds = false; } [self detectFaces]; @@ -319,6 +340,28 @@ [self presentEditorTab:_currentTab]; } +- (void)handleDotTap { + TGPhotoAvatarPreviewController *previewController = (TGPhotoAvatarPreviewController *)_currentTabController; + if (![previewController isKindOfClass:[TGPhotoAvatarPreviewController class]]) + return; + + [self stopVideoPlayback:false]; + [self seekVideo:_dotPosition]; + + [previewController beginScrubbing:false]; + + [_scrubberView setValue:_dotPosition resetPosition:true]; + + __weak TGPhotoEditorController *weakSelf = self; + [previewController endScrubbing:false completion:^bool{ + __strong TGPhotoEditorController *strongSelf = weakSelf; + if (strongSelf == nil) + return false; + + return !strongSelf->_scrubberView.isScrubbing; + }]; +} + - (void)setToolbarHidden:(bool)hidden animated:(bool)animated { if (self.requestToolbarsHidden == nil) @@ -383,7 +426,6 @@ if (self.item.isVideo) { _scrubberView.allowsTrimming = self.item.originalDuration >= TGVideoEditMinimumTrimmableDuration; - _scrubberView.hasDotPicker = true; _scrubberView.disableZoom = true; _scrubberView.disableTimeDisplay = true; _scrubberView.trimStartValue = 0.0; @@ -423,36 +465,46 @@ } } + __weak TGPhotoEditorController *weakSelf = self; + PGPhotoEditor *photoEditor = _photoEditor; + [signal startWithNext:^(id next) { + __strong TGPhotoEditorController *strongSelf = weakSelf; + if (strongSelf == nil) + return; + + if (strongSelf->_dismissed) + return; + CGFloat progress = 0.0; bool progressVisible = false; bool doneEnabled = true; if ([next isKindOfClass:[UIImage class]]) { - [_photoEditor setImage:(UIImage *)next forCropRect:_photoEditor.cropRect cropRotation:_photoEditor.cropRotation cropOrientation:_photoEditor.cropOrientation cropMirrored:_photoEditor.cropMirrored fullSize:false]; + [photoEditor setImage:(UIImage *)next forCropRect:photoEditor.cropRect cropRotation:photoEditor.cropRotation cropOrientation:photoEditor.cropOrientation cropMirrored:photoEditor.cropMirrored fullSize:false]; if (!((UIImage *)next).degraded) { progress = 1.0f; } } else if ([next isKindOfClass:[AVAsset class]]) { - _playerItem = [AVPlayerItem playerItemWithAsset:(AVAsset *)next]; - _player = [AVPlayer playerWithPlayerItem:_playerItem]; - _player.actionAtItemEnd = AVPlayerActionAtItemEndNone; - _player.muted = true; + strongSelf->_playerItem = [AVPlayerItem playerItemWithAsset:(AVAsset *)next]; + strongSelf->_player = [AVPlayer playerWithPlayerItem:strongSelf->_playerItem]; + strongSelf->_player.actionAtItemEnd = AVPlayerActionAtItemEndNone; + strongSelf->_player.muted = true; - [_photoEditor setPlayerItem:_playerItem forCropRect:_photoEditor.cropRect cropRotation:0.0 cropOrientation:_photoEditor.cropOrientation cropMirrored:_photoEditor.cropMirrored]; + [photoEditor setPlayerItem:strongSelf->_playerItem forCropRect:photoEditor.cropRect cropRotation:0.0 cropOrientation:photoEditor.cropOrientation cropMirrored:photoEditor.cropMirrored]; TGDispatchOnMainThread(^ { - if ([_currentTabController isKindOfClass:[TGPhotoAvatarCropController class]]) - [(TGPhotoAvatarCropController *)_currentTabController setPlayer:_player]; + if ([strongSelf->_currentTabController isKindOfClass:[TGPhotoAvatarCropController class]]) + [(TGPhotoAvatarCropController *)strongSelf->_currentTabController setPlayer:strongSelf->_player]; - [_previewView performTransitionInWithCompletion:^ + [strongSelf->_previewView performTransitionInWithCompletion:^ { }]; - if (_scheduledVideoPlayback) { - _scheduledVideoPlayback = false; - [self startVideoPlayback:true]; + if (strongSelf->_scheduledVideoPlayback) { + strongSelf->_scheduledVideoPlayback = false; + [strongSelf startVideoPlayback:true]; } }); progress = 1.0f; @@ -464,14 +516,16 @@ } TGDispatchOnMainThread(^{ - if (progressVisible) - _hadProgress = true; - [self setProgressVisible:progressVisible value:progress animated:true]; - [self updateDoneButtonEnabled:doneEnabled animated:true]; + if (strongSelf->_dismissed) + return; - if (_hadProgress) { - [_scrubberView reloadThumbnails]; - [self updateDotImage]; + if (progressVisible) + strongSelf->_hadProgress = true; + [strongSelf setProgressVisible:progressVisible value:progress animated:true]; + [strongSelf updateDoneButtonEnabled:doneEnabled animated:true]; + + if (strongSelf->_hadProgress) { + [strongSelf->_scrubberView reloadThumbnails]; } }); @@ -479,25 +533,29 @@ return; } - if (_ignoreDefaultPreviewViewTransitionIn) + if (strongSelf->_ignoreDefaultPreviewViewTransitionIn) { TGDispatchOnMainThread(^ { - if ([_currentTabController isKindOfClass:[TGPhotoQualityController class]]) - [_previewView setSnapshotImageOnTransition:next]; + if (strongSelf->_dismissed) + return; + if ([strongSelf->_currentTabController isKindOfClass:[TGPhotoQualityController class]]) + [strongSelf->_previewView setSnapshotImageOnTransition:next]; else - [_previewView setSnapshotImage:next]; + [strongSelf->_previewView setSnapshotImage:next]; }); } else { - [_photoEditor processAnimated:false completion:^ + [photoEditor processAnimated:false completion:^ { TGDispatchOnMainThread(^ { - [_previewView performTransitionInWithCompletion:^ + if (strongSelf->_dismissed) + return; + [strongSelf->_previewView performTransitionInWithCompletion:^ { - [_previewView setSnapshotImage:next]; + [strongSelf->_previewView setSnapshotImage:next]; }]; }); }]; @@ -544,8 +602,9 @@ _playerReachedEndObserver = [_player addBoundaryTimeObserverForTimes:@[[NSValue valueWithCMTime:endTime]] queue:NULL usingBlock:^ { __strong TGPhotoEditorController *strongSelf = weakSelf; - if (strongSelf != nil) { + if (strongSelf != nil && !strongSelf->_dismissed) { [strongSelf->_player seekToTime:startTime]; + [strongSelf->_scrubberView setValue:strongSelf->_photoEditor.trimEndValue resetPosition:true]; } }]; } @@ -565,9 +624,19 @@ [_player.currentItem seekToTime:targetTime toleranceBefore:kCMTimeZero toleranceAfter:kCMTimeZero]; [self _setupPlaybackStartedObserver]; + + if (!_registeredKeypathObserver) { + [_player addObserver:self forKeyPath:@"rate" options:NSKeyValueObservingOptionNew context:nil]; + _registeredKeypathObserver = true; + } } + _isPlaying = true; [_player play]; + + [_positionTimer invalidate]; + _positionTimer = [TGTimerTarget scheduledMainThreadTimerWithTarget:self action:@selector(positionTimerEvent) interval:0.25 repeat:true]; + [self positionTimerEvent]; } - (void)stopVideoPlayback:(bool)reset { @@ -576,8 +645,35 @@ [_player removeTimeObserver:_playerStartedObserver]; if (_playerReachedEndObserver != nil) [_player removeTimeObserver:_playerReachedEndObserver]; + + if (_registeredKeypathObserver) { + [_player removeObserver:self forKeyPath:@"rate" context:nil]; + _registeredKeypathObserver = false; + } } + + _isPlaying = false; [_player pause]; + + [_positionTimer invalidate]; + _positionTimer = nil; +} + +- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)__unused change context:(void *)__unused context +{ + if (object == _player && [keyPath isEqualToString:@"rate"]) + { + if ([_currentTabController isKindOfClass:[TGPhotoAvatarPreviewController class]]) { + [_scrubberView setIsPlaying:_player.rate > FLT_EPSILON]; + } + } +} + +- (void)positionTimerEvent +{ + if ([_currentTabController isKindOfClass:[TGPhotoAvatarPreviewController class]]) { + [_scrubberView setValue:CMTimeGetSeconds(_player.currentItem.currentTime) resetPosition:false]; + } } - (void)seekVideo:(NSTimeInterval)position { @@ -730,10 +826,11 @@ - (void)createEditedImageWithEditorValues:(id)editorValues createThumbnail:(bool)createThumbnail saveOnly:(bool)saveOnly completion:(void (^)(UIImage *))completion { + bool avatar = [self presentedForAvatarCreation]; + if (!saveOnly) { - bool forAvatar = [self presentedForAvatarCreation]; - if (!forAvatar && [editorValues isDefaultValuesForAvatar:false]) + if (!avatar && [editorValues isDefaultValuesForAvatar:false]) { if (self.willFinishEditing != nil) self.willFinishEditing(nil, [_currentTabController currentResultRepresentation], true); @@ -751,7 +848,7 @@ if (!saveOnly && self.willFinishEditing != nil) self.willFinishEditing(editorValues, [_currentTabController currentResultRepresentation], true); - if (!saveOnly && completion != nil) + if (!saveOnly && !avatar && completion != nil) completion(nil); UIImage *fullSizeImage = self.fullSizeImage; @@ -832,19 +929,22 @@ } else { + void (^didFinishRenderingFullSizeImage)(UIImage *) = self.didFinishRenderingFullSizeImage; + void (^didFinishEditing)(id, UIImage *, UIImage *, bool ) = self.didFinishEditing; + [[[[renderedImageSignal map:^id(UIImage *image) { if (!hasImageAdjustments) { - if (hasPainting && !hasAnimation && self.didFinishRenderingFullSizeImage != nil) - self.didFinishRenderingFullSizeImage(image); + if (hasPainting && !hasAnimation && didFinishRenderingFullSizeImage != nil) + didFinishRenderingFullSizeImage(image); return image; } else { - if (!saveOnly && !hasAnimation && self.didFinishRenderingFullSizeImage != nil) - self.didFinishRenderingFullSizeImage(image); + if (!saveOnly && !hasAnimation && didFinishRenderingFullSizeImage != nil) + didFinishRenderingFullSizeImage(image); return TGPhotoEditorFitImage(image, TGPhotoEditorResultImageMaxSize); } @@ -881,8 +981,11 @@ UIImage *image = result[@"image"]; UIImage *thumbnailImage = result[@"thumbnail"]; - if (!saveOnly && self.didFinishEditing != nil) - self.didFinishEditing(editorValues, image, thumbnailImage, true); + if (avatar && completion != nil) + completion(image); + + if (!saveOnly && didFinishEditing != nil) + didFinishEditing(editorValues, image, thumbnailImage, true); } error:^(__unused id error) { TGLegacyLog(@"renderedImageSignal error"); @@ -923,6 +1026,11 @@ - (void)transitionOutSaving:(bool)saving completion:(void (^)(void))completion { + _dismissed = true; + if (!saving) { + [self stopVideoPlayback:true]; + } + [UIView animateWithDuration:0.3f animations:^ { _portraitToolbarView.alpha = 0.0f; @@ -932,7 +1040,7 @@ _currentTabController.beginTransitionOut = self.beginTransitionOut; [self setToolbarHidden:false animated:true]; - if (self.beginCustomTransitionOut != nil) + if (self.beginCustomTransitionOut != nil && !saving) { id rep = [_currentTabController currentResultRepresentation]; if ([rep isKindOfClass:[UIImage class]]) @@ -1373,10 +1481,10 @@ { if ([_currentTabController isKindOfClass:[TGPhotoToolsController class]]) { [_scrubberView reloadDataAndReset:false]; - [self updateDotImage]; + [self updateDotImage:false]; } - TGPhotoAvatarPreviewController *previewController = [[TGPhotoAvatarPreviewController alloc] initWithContext:_context photoEditor:_photoEditor previewView:_previewView scrubberView:_scrubberView]; + TGPhotoAvatarPreviewController *previewController = [[TGPhotoAvatarPreviewController alloc] initWithContext:_context photoEditor:_photoEditor previewView:_previewView scrubberView:_scrubberView dotImageView:_dotImageView]; previewController.item = _item; previewController.toolbarLandscapeSize = TGPhotoEditorToolbarSize; previewController.beginTransitionIn = ^UIView *(CGRect *referenceFrame, UIView **parentView, bool *noTransitionView) @@ -1386,8 +1494,16 @@ *noTransitionView = transitionNoTransitionView; __strong TGPhotoEditorController *strongSelf = weakSelf; - if (strongSelf != nil) - [strongSelf startVideoPlayback:true]; + if (strongSelf != nil) { + if ([currentController isKindOfClass:[TGPhotoAvatarCropController class]]) { + dispatch_async(dispatch_get_main_queue(), ^{ + [strongSelf stopVideoPlayback:false]; + [strongSelf seekVideo:0]; + }); + } else { + [strongSelf startVideoPlayback:true]; + } + } return transitionReferenceView; }; @@ -1400,6 +1516,10 @@ if (isInitialAppearance && strongSelf.finishedTransitionIn != nil) strongSelf.finishedTransitionIn(); + if ([currentController isKindOfClass:[TGPhotoAvatarCropController class]]) { + [strongSelf startVideoPlayback:false]; + } + strongSelf->_switchingTab = false; }; previewController.controlVideoPlayback = ^(bool play) { @@ -1522,8 +1642,13 @@ - (void)dismissAnimated:(bool)animated { + _dismissed = true; + self.view.userInteractionEnabled = false; + if (self.navigationController != nil) + animated = false; + if (animated) { const CGFloat velocity = 2000.0f; @@ -1535,12 +1660,20 @@ self.view.frame = targetFrame; } completion:^(__unused BOOL finished) { - [self dismiss]; + if (self.navigationController != nil) { + [self.navigationController popViewControllerAnimated:false]; + } else { + [self dismiss]; + } }]; } else { - [self dismiss]; + if (self.navigationController != nil) { + [self.navigationController popViewControllerAnimated:false]; + } else { + [self dismiss]; + } } } @@ -1684,11 +1817,13 @@ [[NSUserDefaults standardUserDefaults] setObject:@(qualityController.preset) forKey:@"TG_preferredVideoPreset_v0"]; } else if ([_currentTabController isKindOfClass:[TGPhotoAvatarPreviewController class]]) { - videoStartValue = _scrubberView.value; + videoStartValue = _dotPosition; trimStartValue = _scrubberView.trimStartValue; trimEndValue = _scrubberView.trimEndValue; } + [self stopVideoPlayback:true]; + TGVideoEditAdjustments *adjustments = [_photoEditor exportAdjustmentsWithPaintingData:paintingData]; if ([self presentedForAvatarCreation] && _item.isVideo) { [[SQueue concurrentDefaultQueue] dispatch:^ @@ -1745,10 +1880,7 @@ if (self.didFinishEditingVideo != nil) self.didFinishEditingVideo(asset.URL, [adjustments editAdjustmentsWithPreset:TGMediaVideoConversionPresetProfile videoStartValue:videoStartValue trimStartValue:trimStartValue trimEndValue:trimEndValue], fullImage, nil, true); - [self transitionOutSaving:true completion:^ - { - [self dismiss]; - }]; + [self dismissAnimated:true]; }); }]; }]; @@ -1766,9 +1898,10 @@ [NSObject cancelPreviousPerformRequestsWithTarget:progressWindow selector:@selector(showAnimated) object:nil]; [progressWindow dismiss:true]; - if (forAvatar) + if (forAvatar) { + [self dismissAnimated:true]; return; - + } [self transitionOutSaving:true completion:^ { [self dismiss]; @@ -1856,10 +1989,14 @@ if (self.didFinishEditing != nil) self.didFinishEditing(hasChanges ? adjustments : nil, nil, nil, hasChanges); - [self transitionOutSaving:saving completion:^ - { - [self dismiss]; - }]; + if ([self presentedForAvatarCreation]) { + [self dismissAnimated:true]; + } else { + [self transitionOutSaving:saving completion:^ + { + [self dismiss]; + }]; + } } } @@ -2040,6 +2177,12 @@ return orientation; } +- (UIEdgeInsets)screenEdges { + CGSize referenceSize = [self referenceViewSize]; + CGFloat screenSide = MAX(referenceSize.width, referenceSize.height); + return UIEdgeInsetsMake((screenSide - referenceSize.height) / 2, (screenSide - referenceSize.width) / 2, (screenSide + referenceSize.height) / 2, (screenSide + referenceSize.width) / 2); +} + - (void)updateLayout:(UIInterfaceOrientation)orientation { orientation = [self effectiveOrientation:orientation]; @@ -2098,8 +2241,6 @@ _initializedScrubber = true; [_scrubberView reloadData]; [_scrubberView resetToStart]; - - [self updateDotImage]; } else { [_scrubberView reloadThumbnails]; } @@ -2240,6 +2381,27 @@ return avatarTabs; } +- (void)setPlayButtonHidden:(bool)hidden animated:(bool)animated +{ +// if (animated) +// { +// _actionButton.hidden = false; +// [UIView animateWithDuration:0.15f animations:^ +// { +// _actionButton.alpha = hidden ? 0.0f : 1.0f; +// } completion:^(BOOL finished) +// { +// if (finished) +// _actionButton.hidden = hidden; +// }]; +// } +// else +// { +// _actionButton.alpha = hidden ? 0.0f : 1.0f; +// _actionButton.hidden = hidden; +// } +} + #pragma mark - Video Scrubber Data Source & Delegate #pragma mark Scrubbing @@ -2264,24 +2426,17 @@ - (void)videoScrubberDidBeginScrubbing:(TGMediaPickerGalleryVideoScrubber *)__unused videoScrubber { [self stopVideoPlayback:false]; - - if (_dotVideoView == nil) { - _dotVideoView = [[PGPhotoEditorView alloc] initWithFrame:CGRectMake(0.0, 0.0, 26.0, 44.0)]; - [_scrubberView setDotVideoView:_dotVideoView]; - - _photoEditor.additionalOutputs = @[_dotVideoView]; - } else { - _dotVideoView.hidden = false; - } + + [self setPlayButtonHidden:true animated:false]; TGPhotoAvatarPreviewController *previewController = (TGPhotoAvatarPreviewController *)_currentTabController; if (![previewController isKindOfClass:[TGPhotoAvatarPreviewController class]]) return; - [previewController beginScrubbing]; + [previewController beginScrubbing:true]; } -- (void)updateDotImage { +- (void)updateDotImage:(bool)animated { AVPlayer *player = _player; if (player == nil) { return; @@ -2290,9 +2445,10 @@ [[SQueue concurrentDefaultQueue] dispatch:^{ AVAssetImageGenerator *generator = [[AVAssetImageGenerator alloc] initWithAsset:player.currentItem.asset]; generator.appliesPreferredTrackTransform = true; - generator.maximumSize = CGSizeMake(128.0f, 128.0f); + generator.maximumSize = CGSizeMake(160.0f, 160.0f); generator.requestedTimeToleranceAfter = kCMTimeZero; generator.requestedTimeToleranceBefore = kCMTimeZero; + CGImageRef imageRef = [generator copyCGImageAtTime:player.currentItem.currentTime actualTime:NULL error:NULL]; UIImage *image = [[UIImage alloc] initWithCGImage:imageRef]; CGImageRelease(imageRef); @@ -2304,21 +2460,102 @@ image = editor.currentResultImage; } + UIGraphicsBeginImageContextWithOptions(CGSizeMake(160.0, 160.0), false, 1.0); + CGContextRef context = UIGraphicsGetCurrentContext(); + + CGContextAddEllipseInRect(context, CGRectMake(0.0, 0.0, 160.0, 160.0)); + CGContextClip(context); + + CGSize filledSize = TGScaleToFill(image.size, CGSizeMake(160, 160)); + [image drawInRect:CGRectMake((160.0 - filledSize.width) / 2.0, (160.0 - filledSize.height) / 2.0, filledSize.width, filledSize.height)]; + + UIImage *finalImage = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); + TGDispatchOnMainThread(^{ - [_scrubberView setDotImage:image]; - _dotVideoView.hidden = true; + if (animated) { + UIView *snapshotView = nil; + UIView *dotSnapshotView = nil; + if (_dotImageView.image != nil) { + dotSnapshotView = [_dotMarkerView snapshotViewAfterScreenUpdates:false]; + dotSnapshotView.frame = _dotMarkerView.frame; + [_dotMarkerView.superview addSubview:dotSnapshotView]; + + snapshotView = [_dotImageView snapshotViewAfterScreenUpdates:false]; + snapshotView.frame = [_dotImageView.superview convertRect:_dotImageView.frame toView:_dotMarkerView.superview]; + [_dotMarkerView.superview addSubview:snapshotView]; + } + + if (snapshotView != nil) { + [UIView animateWithDuration:0.15 animations:^{ + snapshotView.center = _dotMarkerView.center; + snapshotView.transform = CGAffineTransformMakeScale(0.05, 0.05); + snapshotView.alpha = 0.0f; + dotSnapshotView.transform = CGAffineTransformMakeScale(0.3, 0.3); + dotSnapshotView.alpha = 0.0f; + } completion:^(BOOL finished) { + [snapshotView removeFromSuperview]; + [dotSnapshotView removeFromSuperview]; + }]; + } + + _dotImageView.image = finalImage; + [_scrubberView addSubview:_dotMarkerView]; + + _dotMarkerView.center = CGPointMake([_scrubberView scrubberPositionForPosition:_dotPosition].x + 7.0, 9.5); + _dotMarkerView.transform = CGAffineTransformMakeScale(0.3, 0.3); + _dotMarkerView.alpha = 0.0; + [UIView animateWithDuration:0.3 animations:^{ + _dotMarkerView.transform = CGAffineTransformIdentity; + _dotMarkerView.alpha = 1.0; + }]; + + UIEdgeInsets screenEdges = [self screenEdges]; + CGSize referenceSize = [self referenceViewSize]; + CGRect containerFrame = [TGPhotoEditorTabController photoContainerFrameForParentViewFrame:CGRectMake(0, 0, referenceSize.width, referenceSize.height) toolbarLandscapeSize:self.toolbarLandscapeSize orientation:self.effectiveOrientation panelSize:0.0 hasOnScreenNavigation:self.hasOnScreenNavigation]; + containerFrame.origin.x += screenEdges.left; + containerFrame.origin.y += screenEdges.top; + + CGFloat scale = containerFrame.size.width / 160.0; + _dotImageView.center = CGPointMake(CGRectGetMidX(containerFrame), CGRectGetMidY(containerFrame)); + _dotImageView.transform = CGAffineTransformMakeScale(scale, scale); + + CGPoint targetCenter = [_dotMarkerView.superview convertPoint:_dotMarkerView.center toView:_wrapperView]; + targetCenter.y -= 27.0; + [UIView animateWithDuration:0.4 delay:0.0 usingSpringWithDamping:1.1 initialSpringVelocity:0.1 options:kNilOptions animations:^{ + _dotImageView.center = targetCenter; + _dotImageView.transform = CGAffineTransformMakeScale(0.225, 0.225); + } completion:^(BOOL finished) { + + }]; + } else { + if (_dotImageView.image != nil) { + [_scrubberView addSubview:_dotMarkerView]; + + UIView *snapshotView; + if (_dotImageView.image != nil) { + _dotImageSnapshotView = [_dotImageView snapshotViewAfterScreenUpdates:false]; + snapshotView.frame = _dotImageView.bounds; + [_dotImageView addSubview:snapshotView]; + } + + _dotImageView.image = finalImage; + } + } }); }]; } -- (void)videoScrubberDidEndScrubbing:(TGMediaPickerGalleryVideoScrubber *)__unused videoScrubber +- (void)videoScrubberDidEndScrubbing:(TGMediaPickerGalleryVideoScrubber *)videoScrubber { __weak TGPhotoEditorController *weakSelf = self; TGPhotoAvatarPreviewController *previewController = (TGPhotoAvatarPreviewController *)_currentTabController; if (![previewController isKindOfClass:[TGPhotoAvatarPreviewController class]]) return; - [previewController endScrubbing:^bool{ + _dotPosition = videoScrubber.value; + + [previewController endScrubbing:true completion:^bool{ __strong TGPhotoEditorController *strongSelf = weakSelf; if (strongSelf == nil) return false; @@ -2327,7 +2564,7 @@ }]; dispatch_async(dispatch_get_main_queue(), ^{ - [self updateDotImage]; + [self updateDotImage:true]; }); } @@ -2352,11 +2589,7 @@ { [self stopVideoPlayback:false]; - TGPhotoAvatarPreviewController *previewController = (TGPhotoAvatarPreviewController *)_currentTabController; - if (![previewController isKindOfClass:[TGPhotoAvatarPreviewController class]]) - return; - - [previewController setPlayButtonHidden:true animated:false]; + [self setPlayButtonHidden:true animated:false]; } - (void)videoScrubberDidEndEditing:(TGMediaPickerGalleryVideoScrubber *)videoScrubber @@ -2366,11 +2599,7 @@ [self seekVideo:videoScrubber.trimStartValue]; [self stopVideoPlayback:false]; - TGPhotoAvatarPreviewController *previewController = (TGPhotoAvatarPreviewController *)_currentTabController; - if (![previewController isKindOfClass:[TGPhotoAvatarPreviewController class]]) - return; - - [previewController setPlayButtonHidden:true animated:false]; + [self setPlayButtonHidden:true animated:false]; } - (void)videoScrubber:(TGMediaPickerGalleryVideoScrubber *)__unused videoScrubber editingStartValueDidChange:(NSTimeInterval)startValue @@ -2454,6 +2683,15 @@ if (index < timestamps.count) [strongSelf->_scrubberView setThumbnailImage:image forTimestamp:[timestamps[index] doubleValue] isSummaryThubmnail:isSummaryThumbnails]; }]; + + if (strongSelf->_dotImageSnapshotView != nil) { + [UIView animateWithDuration:0.2 animations:^{ + strongSelf->_dotImageSnapshotView.alpha = 0.0f; + } completion:^(BOOL finished) { + [strongSelf->_dotImageSnapshotView removeFromSuperview]; + strongSelf->_dotImageSnapshotView = nil; + }]; + } } completed:^ { __strong TGPhotoEditorController *strongSelf = weakSelf; diff --git a/submodules/PeerAvatarGalleryUI/Sources/AvatarGalleryController.swift b/submodules/PeerAvatarGalleryUI/Sources/AvatarGalleryController.swift index a6ac123700..93ae220944 100644 --- a/submodules/PeerAvatarGalleryUI/Sources/AvatarGalleryController.swift +++ b/submodules/PeerAvatarGalleryUI/Sources/AvatarGalleryController.swift @@ -113,8 +113,8 @@ public func fetchedAvatarGalleryEntries(account: Account, peer: Peer) -> Signal< var index: Int32 = 0 for photo in photos { let indexData = GalleryItemIndexData(position: index, totalCount: Int32(photos.count)) - if result.isEmpty, let first = initialEntries.first, photo.image.videoRepresentations.isEmpty { - result.append(.image(photo.image.imageId, photo.image.reference, first.representations, first.videoRepresentations, peer, photo.date, indexData, photo.messageId, photo.image.immediateThumbnailData)) + if result.isEmpty, let first = initialEntries.first { + result.append(.image(photo.image.imageId, photo.image.reference, first.representations, photo.image.videoRepresentations, peer, photo.date, indexData, photo.messageId, photo.image.immediateThumbnailData)) } else { result.append(.image(photo.image.imageId, photo.image.reference, photo.image.representations.map({ ImageRepresentationWithReference(representation: $0, reference: MediaResourceReference.standalone(resource: $0.resource)) }), photo.image.videoRepresentations, peer, photo.date, indexData, photo.messageId, photo.image.immediateThumbnailData)) } @@ -140,8 +140,8 @@ public func fetchedAvatarGalleryEntries(account: Account, peer: Peer, firstEntry var index: Int32 = 0 for photo in photos { let indexData = GalleryItemIndexData(position: index, totalCount: Int32(photos.count)) - if result.isEmpty, let first = initialEntries.first, photo.image.videoRepresentations.isEmpty { - result.append(.image(photo.image.imageId, photo.image.reference, first.representations, first.videoRepresentations, peer, photo.date, indexData, photo.messageId, photo.image.immediateThumbnailData)) + if result.isEmpty, let first = initialEntries.first { + result.append(.image(photo.image.imageId, photo.image.reference, first.representations, photo.image.videoRepresentations, peer, photo.date, indexData, photo.messageId, photo.image.immediateThumbnailData)) } else { result.append(.image(photo.image.imageId, photo.image.reference, photo.image.representations.map({ ImageRepresentationWithReference(representation: $0, reference: MediaResourceReference.standalone(resource: $0.resource)) }), photo.image.videoRepresentations, peer, photo.date, indexData, photo.messageId, photo.image.immediateThumbnailData)) } diff --git a/submodules/SettingsUI/Sources/SettingsController.swift b/submodules/SettingsUI/Sources/SettingsController.swift index 8326652653..2c993a0ebc 100644 --- a/submodules/SettingsUI/Sources/SettingsController.swift +++ b/submodules/SettingsUI/Sources/SettingsController.swift @@ -1415,7 +1415,7 @@ public func settingsController(context: AccountContext, accountManager: AccountM if let profileImage = peer?.smallProfileImage { state.updatingAvatar = .image(profileImage, false) } else { - state.updatingAvatar = .none + state.updatingAvatar = ItemListAvatarAndNameInfoItemUpdatingAvatar.none } return state } diff --git a/submodules/TelegramUI/Sources/ChatMessageInteractiveMediaNode.swift b/submodules/TelegramUI/Sources/ChatMessageInteractiveMediaNode.swift index b78d6d6f2c..e8ffcdb661 100644 --- a/submodules/TelegramUI/Sources/ChatMessageInteractiveMediaNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageInteractiveMediaNode.swift @@ -1121,7 +1121,7 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio if file.isAnimated { badgeContent = .mediaDownload(backgroundColor: messageTheme.mediaDateAndStatusFillColor, foregroundColor: messageTheme.mediaDateAndStatusTextColor, duration: "\(gifTitle)", size: nil, muted: false, active: false) } else { - badgeContent = .mediaDownload(backgroundColor: messageTheme.mediaDateAndStatusFillColor, foregroundColor: messageTheme.mediaDateAndStatusTextColor, duration: strings.Conversation_Processing, size: nil, muted: false, active: active) + badgeContent = .mediaDownload(backgroundColor: messageTheme.mediaDateAndStatusFillColor, foregroundColor: messageTheme.mediaDateAndStatusTextColor, duration: strings.Conversation_Processing, size: nil, muted: false, active: false) } } if file.isAnimated && isMediaStreamable(message: message, media: file) { diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift index 4e4315d248..dc050b11c6 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift @@ -236,7 +236,7 @@ final class PeerInfoAvatarListItemNode: ASDisplayNode { if let video = videoRepresentations.last, let id = id { let mediaManager = self.context.sharedContext.mediaManager let videoFileReference = FileMediaReference.standalone(media: TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: video.resource, previewRepresentations: representations.map { $0.representation }, videoThumbnails: [], immediateThumbnailData: immediateThumbnailData, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: video.dimensions, flags: [])])) - let videoContent = NativeVideoContent(id: .profileVideo(id), fileReference: videoFileReference, streamVideo: isMediaStreamable(resource: video.resource) ? .conservative : .none, loopVideo: true, enableSound: false, fetchAutomatically: true, onlyFullSizeThumbnail: true, continuePlayingWithoutSoundOnLostAudioSession: false, placeholderColor: .clear) + let videoContent = NativeVideoContent(id: .profileVideo(id), fileReference: videoFileReference, streamVideo: isMediaStreamable(resource: video.resource) ? .conservative : .none, loopVideo: true, enableSound: false, fetchAutomatically: true, onlyFullSizeThumbnail: true, autoFetchFullSizeThumbnail: true, continuePlayingWithoutSoundOnLostAudioSession: false, placeholderColor: .clear) let videoNode = UniversalVideoNode(postbox: self.context.account.postbox, audioSession: mediaManager.audioSession, manager: mediaManager.universalVideoManager, decoration: GalleryVideoDecoration(), content: videoContent, priority: .embedded) videoNode.isUserInteractionEnabled = false videoNode.ownsContentNodeUpdated = { [weak self] owns in @@ -626,7 +626,7 @@ final class PeerInfoAvatarListContainerNode: ASDisplayNode { strongSelf.items = items strongSelf.itemsUpdated?(items) if let size = strongSelf.validLayout { - strongSelf.updateItems(size: size, update: true, transition: .immediate, stripTransition: .immediate, synchronous: true) + strongSelf.updateItems(size: size, update: true, transition: .immediate, stripTransition: .immediate, synchronous: synchronous) } if items.isEmpty { if !strongSelf.didSetReady { @@ -762,7 +762,7 @@ final class PeerInfoAvatarTransformContainerNode: ASDisplayNode { let context: AccountContext let avatarNode: AvatarNode - private var videoNode: UniversalVideoNode? + fileprivate var videoNode: UniversalVideoNode? private var videoContent: NativeVideoContent? var tapped: (() -> Void)? @@ -788,6 +788,21 @@ final class PeerInfoAvatarTransformContainerNode: ASDisplayNode { } } + func reattachVideoNode() { + if let videoNode = self.videoNode { + let maskPath = UIBezierPath(ovalIn: CGRect(origin: CGPoint(), size: self.avatarNode.frame.size)) + let shape = CAShapeLayer() + shape.path = maskPath.cgPath + videoNode.layer.mask = shape + + videoNode.transform = CATransform3DIdentity + videoNode.updateLayout(size: self.avatarNode.frame.size, transition: .immediate) + videoNode.frame = self.avatarNode.frame + + self.addSubnode(videoNode) + } + } + func update(peer: Peer?, item: PeerInfoAvatarListItem?, theme: PresentationTheme, avatarSize: CGFloat, isExpanded: Bool) { if let peer = peer { var overrideImage: AvatarNodeImageOverride? @@ -803,7 +818,7 @@ final class PeerInfoAvatarTransformContainerNode: ASDisplayNode { if let item = item, case let .image(reference, representations, videoRepresentations, immediateThumbnailData) = item, let video = videoRepresentations.last, case let .cloud(imageId, _, _) = reference { let id = imageId let videoFileReference = FileMediaReference.standalone(media: TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: video.resource, previewRepresentations: representations.map { $0.representation }, videoThumbnails: [], immediateThumbnailData: immediateThumbnailData, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: video.dimensions, flags: [])])) - let videoContent = NativeVideoContent(id: .profileVideo(id), fileReference: videoFileReference, streamVideo: isMediaStreamable(resource: video.resource) ? .conservative : .none, loopVideo: true, enableSound: false, fetchAutomatically: true, onlyFullSizeThumbnail: false, continuePlayingWithoutSoundOnLostAudioSession: false, placeholderColor: .clear) + let videoContent = NativeVideoContent(id: .profileVideo(id), fileReference: videoFileReference, streamVideo: isMediaStreamable(resource: video.resource) ? .conservative : .none, loopVideo: true, enableSound: false, fetchAutomatically: true, onlyFullSizeThumbnail: false, autoFetchFullSizeThumbnail: true, continuePlayingWithoutSoundOnLostAudioSession: false, placeholderColor: .clear) if videoContent.id != self.videoContent?.id { let mediaManager = self.context.sharedContext.mediaManager let videoNode = UniversalVideoNode(postbox: self.context.account.postbox, audioSession: mediaManager.audioSession, manager: mediaManager.universalVideoManager, decoration: GalleryVideoDecoration(), content: videoContent, priority: .embedded) @@ -835,9 +850,19 @@ final class PeerInfoAvatarTransformContainerNode: ASDisplayNode { videoNode.frame = self.avatarNode.frame if isExpanded == videoNode.canAttachContent { - videoNode.canAttachContent = !isExpanded - if videoNode.canAttachContent { - videoNode.play() + let update = { + videoNode.canAttachContent = !isExpanded + if videoNode.canAttachContent { + videoNode.seek(0.0) + videoNode.play() + } + } + if isExpanded { + DispatchQueue.main.async { + update() + } + } else { + update() } } } @@ -1011,20 +1036,28 @@ final class PeerInfoAvatarListNode: ASDisplayNode { } func animateAvatarCollapse(transition: ContainedViewLayoutTransition) { - if let currentItemNode = self.listContainerNode.currentItemNode, let unroundedImage = self.avatarContainerNode.avatarNode.unroundedImage, case .animated = transition { - let avatarCopyView = UIImageView() - avatarCopyView.image = unroundedImage - avatarCopyView.frame = self.avatarContainerNode.avatarNode.frame - avatarCopyView.center = currentItemNode.imageNode.position - currentItemNode.view.addSubview(avatarCopyView) - let scale = currentItemNode.imageNode.bounds.height / avatarCopyView.bounds.height - avatarCopyView.layer.transform = CATransform3DMakeScale(scale, scale, scale) - avatarCopyView.alpha = 0.0 - transition.updateAlpha(layer: avatarCopyView.layer, alpha: 1.0, completion: { [weak avatarCopyView] _ in - Queue.mainQueue().after(0.1, { - avatarCopyView?.removeFromSuperview() + if let currentItemNode = self.listContainerNode.currentItemNode, case .animated = transition { + if let videoNode = self.avatarContainerNode.videoNode { +// videoNode.position = currentItemNode.imageNode.position +// currentItemNode.addSubnode(videoNode) +// let scale = currentItemNode.imageNode.bounds.height / videoNode.bounds.height +// avatarCopyView.layer.transform = CATransform3DMakeScale(scale, scale, scale) +// self.avatarContainerNode.reattachVideoNode() + } else if let unroundedImage = self.avatarContainerNode.avatarNode.unroundedImage { + let avatarCopyView = UIImageView() + avatarCopyView.image = unroundedImage + avatarCopyView.frame = self.avatarContainerNode.avatarNode.frame + avatarCopyView.center = currentItemNode.imageNode.position + currentItemNode.view.addSubview(avatarCopyView) + let scale = currentItemNode.imageNode.bounds.height / avatarCopyView.bounds.height + avatarCopyView.layer.transform = CATransform3DMakeScale(scale, scale, scale) + avatarCopyView.alpha = 0.0 + transition.updateAlpha(layer: avatarCopyView.layer, alpha: 1.0, completion: { [weak avatarCopyView] _ in + Queue.mainQueue().after(0.1, { + avatarCopyView?.removeFromSuperview() + }) }) - }) + } } } } diff --git a/submodules/TelegramUI/Sources/StickerPaneSearchGlobaltem.swift b/submodules/TelegramUI/Sources/StickerPaneSearchGlobaltem.swift index 24b6e2ada3..f331f789c1 100644 --- a/submodules/TelegramUI/Sources/StickerPaneSearchGlobaltem.swift +++ b/submodules/TelegramUI/Sources/StickerPaneSearchGlobaltem.swift @@ -103,7 +103,7 @@ final class StickerPaneSearchGlobalItem: GridItem { } } - return (128.0 + additionalHeight, false) + return (128.0 + additionalHeight, !self.listAppearance) } init(account: Account, theme: PresentationTheme, strings: PresentationStrings, listAppearance: Bool, info: StickerPackCollectionInfo, topItems: [StickerPackItem], topSeparator: Bool, regularInsets: Bool, installed: Bool, installing: Bool = false, unread: Bool, open: @escaping () -> Void, install: @escaping () -> Void, getItemIsPreviewed: @escaping (StickerPackItem) -> Bool, itemContext: StickerPaneSearchGlobalItemContext, sectionTitle: String? = nil) { diff --git a/submodules/TelegramUniversalVideoContent/Sources/NativeVideoContent.swift b/submodules/TelegramUniversalVideoContent/Sources/NativeVideoContent.swift index 5fe146c046..9ae5df8bbf 100644 --- a/submodules/TelegramUniversalVideoContent/Sources/NativeVideoContent.swift +++ b/submodules/TelegramUniversalVideoContent/Sources/NativeVideoContent.swift @@ -31,11 +31,12 @@ public final class NativeVideoContent: UniversalVideoContent { public let baseRate: Double let fetchAutomatically: Bool let onlyFullSizeThumbnail: Bool + let autoFetchFullSizeThumbnail: Bool let continuePlayingWithoutSoundOnLostAudioSession: Bool let placeholderColor: UIColor let tempFilePath: String? - public init(id: NativeVideoContentId, fileReference: FileMediaReference, imageReference: ImageMediaReference? = nil, streamVideo: MediaPlayerStreaming = .none, loopVideo: Bool = false, enableSound: Bool = true, baseRate: Double = 1.0, fetchAutomatically: Bool = true, onlyFullSizeThumbnail: Bool = false, continuePlayingWithoutSoundOnLostAudioSession: Bool = false, placeholderColor: UIColor = .white, tempFilePath: String? = nil) { + public init(id: NativeVideoContentId, fileReference: FileMediaReference, imageReference: ImageMediaReference? = nil, streamVideo: MediaPlayerStreaming = .none, loopVideo: Bool = false, enableSound: Bool = true, baseRate: Double = 1.0, fetchAutomatically: Bool = true, onlyFullSizeThumbnail: Bool = false, autoFetchFullSizeThumbnail: Bool = false, continuePlayingWithoutSoundOnLostAudioSession: Bool = false, placeholderColor: UIColor = .white, tempFilePath: String? = nil) { self.id = id self.nativeId = id self.fileReference = fileReference @@ -60,13 +61,14 @@ public final class NativeVideoContent: UniversalVideoContent { self.baseRate = baseRate self.fetchAutomatically = fetchAutomatically self.onlyFullSizeThumbnail = onlyFullSizeThumbnail + self.autoFetchFullSizeThumbnail = autoFetchFullSizeThumbnail self.continuePlayingWithoutSoundOnLostAudioSession = continuePlayingWithoutSoundOnLostAudioSession self.placeholderColor = placeholderColor self.tempFilePath = tempFilePath } public func makeContentNode(postbox: Postbox, audioSession: ManagedAudioSession) -> UniversalVideoContentNode & ASDisplayNode { - return NativeVideoContentNode(postbox: postbox, audioSessionManager: audioSession, fileReference: self.fileReference, imageReference: self.imageReference, streamVideo: self.streamVideo, loopVideo: self.loopVideo, enableSound: self.enableSound, baseRate: self.baseRate, fetchAutomatically: self.fetchAutomatically, onlyFullSizeThumbnail: self.onlyFullSizeThumbnail, continuePlayingWithoutSoundOnLostAudioSession: self.continuePlayingWithoutSoundOnLostAudioSession, placeholderColor: self.placeholderColor, tempFilePath: self.tempFilePath) + return NativeVideoContentNode(postbox: postbox, audioSessionManager: audioSession, fileReference: self.fileReference, imageReference: self.imageReference, streamVideo: self.streamVideo, loopVideo: self.loopVideo, enableSound: self.enableSound, baseRate: self.baseRate, fetchAutomatically: self.fetchAutomatically, onlyFullSizeThumbnail: self.onlyFullSizeThumbnail, autoFetchFullSizeThumbnail: self.autoFetchFullSizeThumbnail, continuePlayingWithoutSoundOnLostAudioSession: self.continuePlayingWithoutSoundOnLostAudioSession, placeholderColor: self.placeholderColor, tempFilePath: self.tempFilePath) } public func isEqual(to other: UniversalVideoContent) -> Bool { @@ -126,7 +128,7 @@ private final class NativeVideoContentNode: ASDisplayNode, UniversalVideoContent private var shouldPlay: Bool = false - init(postbox: Postbox, audioSessionManager: ManagedAudioSession, fileReference: FileMediaReference, imageReference: ImageMediaReference?, streamVideo: MediaPlayerStreaming, loopVideo: Bool, enableSound: Bool, baseRate: Double, fetchAutomatically: Bool, onlyFullSizeThumbnail: Bool, continuePlayingWithoutSoundOnLostAudioSession: Bool = false, placeholderColor: UIColor, tempFilePath: String?) { + init(postbox: Postbox, audioSessionManager: ManagedAudioSession, fileReference: FileMediaReference, imageReference: ImageMediaReference?, streamVideo: MediaPlayerStreaming, loopVideo: Bool, enableSound: Bool, baseRate: Double, fetchAutomatically: Bool, onlyFullSizeThumbnail: Bool, autoFetchFullSizeThumbnail: Bool, continuePlayingWithoutSoundOnLostAudioSession: Bool = false, placeholderColor: UIColor, tempFilePath: String?) { self.postbox = postbox self.fileReference = fileReference self.placeholderColor = placeholderColor @@ -163,7 +165,7 @@ private final class NativeVideoContentNode: ASDisplayNode, UniversalVideoContent self?.performActionAtEnd() } - self.imageNode.setSignal(internalMediaGridMessageVideo(postbox: postbox, videoReference: fileReference, imageReference: imageReference, onlyFullSize: onlyFullSizeThumbnail, autoFetchFullSizeThumbnail: fileReference.media.isInstantVideo) |> map { [weak self] getSize, getData in + self.imageNode.setSignal(internalMediaGridMessageVideo(postbox: postbox, videoReference: fileReference, imageReference: imageReference, onlyFullSize: onlyFullSizeThumbnail, autoFetchFullSizeThumbnail: autoFetchFullSizeThumbnail || fileReference.media.isInstantVideo) |> map { [weak self] getSize, getData in Queue.mainQueue().async { if let strongSelf = self, strongSelf.dimensions == nil { if let dimensions = getSize() {