From cc05c84067765e2eba4a6d78209c16de36017ff0 Mon Sep 17 00:00:00 2001 From: Ali <> Date: Tue, 20 Apr 2021 17:47:33 +0400 Subject: [PATCH] Improve animations --- .../TGVideoMessageCaptureController.h | 5 +- .../TGModernConversationInputMicButton.m | 11 ++- .../Sources/TGVideoCameraPipeline.h | 2 +- .../Sources/TGVideoCameraPipeline.m | 10 +-- .../Sources/TGVideoMessageCaptureController.m | 38 +++++++++-- .../TelegramUI/Sources/ChatController.swift | 68 ++++++++++++------- .../Sources/ChatControllerNode.swift | 23 +++++-- .../ChatMessageInstantVideoItemNode.swift | 13 +++- ...atMessageInteractiveInstantVideoNode.swift | 25 ++++++- .../Sources/ChatMessageTransitionNode.swift | 49 ++++++++++++- .../LegacyInstantVideoController.swift | 45 ++++++++---- 11 files changed, 226 insertions(+), 63 deletions(-) diff --git a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGVideoMessageCaptureController.h b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGVideoMessageCaptureController.h index 73b0488a07..1a522e7eb0 100644 --- a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGVideoMessageCaptureController.h +++ b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGVideoMessageCaptureController.h @@ -36,7 +36,7 @@ - (CGRect)frameForSendButton; - (void)complete; -- (void)dismiss; +- (void)dismiss:(bool)cancelled; - (bool)stop; + (void)clearStartImage; @@ -44,4 +44,7 @@ + (void)requestCameraAccess:(void (^)(bool granted, bool wasNotDetermined))resultBlock; + (void)requestMicrophoneAccess:(void (^)(bool granted, bool wasNotDetermined))resultBlock; +- (UIView *)extractVideoContent; +- (void)hideVideoContent; + @end diff --git a/submodules/LegacyComponents/Sources/TGModernConversationInputMicButton.m b/submodules/LegacyComponents/Sources/TGModernConversationInputMicButton.m index 21c0567fc6..0b7799d810 100644 --- a/submodules/LegacyComponents/Sources/TGModernConversationInputMicButton.m +++ b/submodules/LegacyComponents/Sources/TGModernConversationInputMicButton.m @@ -420,8 +420,7 @@ static const CGFloat outerCircleMinScale = innerCircleRadius / outerCircleRadius _innerIconWrapperView.alpha = 0.0f; _innerIconWrapperView.userInteractionEnabled = false; [_innerIconWrapperView addSubview:_innerIconView]; - - //[[_presentation view] addSubview:_innerIconWrapperView]; + [_decoration addSubview:_innerIconWrapperView]; if (_lock == nil) { @@ -449,7 +448,7 @@ static const CGFloat outerCircleMinScale = innerCircleRadius / outerCircleRadius block(); dispatch_async(dispatch_get_main_queue(), block); - _innerIconWrapperView.transform = CGAffineTransformIdentity; + //_innerIconWrapperView.transform = CGAffineTransformIdentity; _innerCircleView.transform = CGAffineTransformMakeScale(0.2f, 0.2f); _outerCircleView.transform = CGAffineTransformMakeScale(0.2f, 0.2f); _decoration.transform = CGAffineTransformMakeScale(0.2f, 0.2f); @@ -516,11 +515,11 @@ static const CGFloat outerCircleMinScale = innerCircleRadius / outerCircleRadius _outerCircleView.transform = CGAffineTransformMakeScale(0.2f, 0.2f); if (toSmallSize) { _decoration.transform = CGAffineTransformConcat(CGAffineTransformMakeScale(0.33f, 0.33f), CGAffineTransformMakeTranslation(0, 2 - TGScreenPixel)); - _innerIconWrapperView.transform = CGAffineTransformConcat(CGAffineTransformMakeScale(0.492f, 0.492f), CGAffineTransformMakeTranslation(-TGScreenPixel, 1)); + //_innerIconWrapperView.transform = CGAffineTransformConcat(CGAffineTransformMakeScale(0.492f, 0.492f), CGAffineTransformMakeTranslation(-TGScreenPixel, 1)); } else { _decoration.transform = CGAffineTransformMakeScale(0.2f, 0.2f); _decoration.alpha = 0.0; - _innerIconWrapperView.transform = CGAffineTransformMakeScale(0.2f, 0.2f); + //_innerIconWrapperView.transform = CGAffineTransformMakeScale(0.2f, 0.2f); _innerIconWrapperView.alpha = 0.0f; } _innerCircleView.alpha = 0.0f; @@ -865,7 +864,7 @@ static const CGFloat outerCircleMinScale = innerCircleRadius / outerCircleRadius transform = CGAffineTransformTranslate(transform, _cancelTranslation, 0); _innerCircleView.transform = transform; - _innerIconWrapperView.transform = transform; + //_innerIconWrapperView.transform = transform; _decoration.transform = transform; } } diff --git a/submodules/LegacyComponents/Sources/TGVideoCameraPipeline.h b/submodules/LegacyComponents/Sources/TGVideoCameraPipeline.h index 6d79b12348..0cb6464f84 100644 --- a/submodules/LegacyComponents/Sources/TGVideoCameraPipeline.h +++ b/submodules/LegacyComponents/Sources/TGVideoCameraPipeline.h @@ -21,7 +21,7 @@ - (void)stopRunning; - (void)startRecording:(NSURL *)url preset:(TGMediaVideoConversionPreset)preset liveUpload:(bool)liveUpload; -- (void)stopRecording:(void (^)())completed; +- (void)stopRecording:(void (^)(bool))completed; - (CGAffineTransform)transformForOrientation:(AVCaptureVideoOrientation)orientation; diff --git a/submodules/LegacyComponents/Sources/TGVideoCameraPipeline.m b/submodules/LegacyComponents/Sources/TGVideoCameraPipeline.m index 304099a130..f249087a91 100644 --- a/submodules/LegacyComponents/Sources/TGVideoCameraPipeline.m +++ b/submodules/LegacyComponents/Sources/TGVideoCameraPipeline.m @@ -135,7 +135,7 @@ const NSInteger TGVideoCameraRetainedBufferCount = 16; { _running = false; - [self stopRecording:^{}]; + [self stopRecording:^(__unused bool success) {}]; [_captureSession stopRunning]; [self captureSessionDidStopRunning]; @@ -300,7 +300,7 @@ const NSInteger TGVideoCameraRetainedBufferCount = 16; - (void)captureSessionDidStopRunning { - [self stopRecording:^{}]; + [self stopRecording:^(__unused bool success) {}]; [self destroyVideoPipeline]; } @@ -701,7 +701,7 @@ const NSInteger TGVideoCameraRetainedBufferCount = 16; [recorder prepareToRecord]; } -- (void)stopRecording:(void (^)())completed +- (void)stopRecording:(void (^)(bool))completed { [[TGVideoCameraPipeline cameraQueue] dispatch:^ { @@ -709,7 +709,7 @@ const NSInteger TGVideoCameraRetainedBufferCount = 16; { if (_recordingStatus != TGVideoCameraRecordingStatusRecording) { if (completed) { - completed(); + completed(false); } return; } @@ -721,7 +721,7 @@ const NSInteger TGVideoCameraRetainedBufferCount = 16; [_recorder finishRecording:^{ __unused __auto_type description = [self description]; if (completed) { - completed(); + completed(true); } }]; }]; diff --git a/submodules/LegacyComponents/Sources/TGVideoMessageCaptureController.m b/submodules/LegacyComponents/Sources/TGVideoMessageCaptureController.m index cf05bf2cba..9285d031bc 100644 --- a/submodules/LegacyComponents/Sources/TGVideoMessageCaptureController.m +++ b/submodules/LegacyComponents/Sources/TGVideoMessageCaptureController.m @@ -658,9 +658,10 @@ typedef enum return; [_activityDisposable dispose]; - [self stopRecording:^{ + [self stopRecording:^() { TGDispatchOnMainThread(^{ - [self dismiss:false]; + //[self dismiss:false]; + [self description]; }); }]; } @@ -955,7 +956,20 @@ typedef enum - (void)stopRecording:(void (^)())completed { - [_capturePipeline stopRecording:completed]; + __weak TGVideoMessageCaptureController *weakSelf = self; + [_capturePipeline stopRecording:^(bool success) { + TGDispatchOnMainThread(^{ + __strong TGVideoMessageCaptureController *strongSelf = weakSelf; + if (strongSelf == nil) { + return; + } + if (!success) { + if (!strongSelf->_dismissed && strongSelf.finishedWithVideo != nil) { + strongSelf.finishedWithVideo(nil, nil, 0, 0.0, CGSizeZero, nil, nil, false, 0); + } + } + }); + }]; [_buttonHandler ignoreEventsFor:1.0f andDisable:true]; [_capturePipeline stopRunning]; } @@ -1015,10 +1029,14 @@ typedef enum } } - if (!_dismissed && self.finishedWithVideo != nil) + if (!_dismissed) { self.finishedWithVideo(url, image, fileSize, duration, dimensions, liveUploadData, adjustments, isSilent, scheduleTimestamp); - else + } else { [[NSFileManager defaultManager] removeItemAtURL:url error:NULL]; + if (self.finishedWithVideo != nil) { + self.finishedWithVideo(nil, nil, 0, 0.0, CGSizeZero, nil, nil, false, 0); + } + } } - (UIImageOrientation)orientationForThumbnailWithTransform:(CGAffineTransform)transform mirrored:(bool)mirrored @@ -1501,6 +1519,16 @@ static UIImage *startImage = nil; return CGSizeMake(240.0f, 240.0f); } +- (UIView *)extractVideoContent { + UIView *result = [_circleView snapshotViewAfterScreenUpdates:false]; + result.frame = [_circleView convertRect:_circleView.bounds toView:nil]; + return result; +} + +- (void)hideVideoContent { + _circleWrapperView.alpha = 0.02f; +} + @end diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 57b01bc1d3..f56d8216f1 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -1050,27 +1050,29 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G replyPanel = accessoryPanelNode } - if let sourceNode = sourceNode as? ChatMediaInputStickerGridItemNode { - strongSelf.chatDisplayNode.messageTransitionNode.add(correlationId: correlationId, source: .stickerMediaInput(input: .inputPanel(itemNode: sourceNode), replyPanel: replyPanel), initiated: { - guard let strongSelf = self else { - return - } - strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, { current in - var current = current - current = current.updatedInputMode { current in - if case let .media(mode, maybeExpanded) = current, maybeExpanded != nil { - return .media(mode: mode, expanded: nil) - } - return current + if strongSelf.chatDisplayNode.shouldAnimateMessageTransition { + if let sourceNode = sourceNode as? ChatMediaInputStickerGridItemNode { + strongSelf.chatDisplayNode.messageTransitionNode.add(correlationId: correlationId, source: .stickerMediaInput(input: .inputPanel(itemNode: sourceNode), replyPanel: replyPanel), initiated: { + guard let strongSelf = self else { + return } + strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, { current in + var current = current + current = current.updatedInputMode { current in + if case let .media(mode, maybeExpanded) = current, maybeExpanded != nil { + return .media(mode: mode, expanded: nil) + } + return current + } - return current + return current + }) }) - }) - } else if let sourceNode = sourceNode as? HorizontalStickerGridItemNode { - strongSelf.chatDisplayNode.messageTransitionNode.add(correlationId: correlationId, source: .stickerMediaInput(input: .mediaPanel(itemNode: sourceNode), replyPanel: replyPanel), initiated: {}) - } else if let sourceNode = sourceNode as? StickerPaneSearchStickerItemNode { - strongSelf.chatDisplayNode.messageTransitionNode.add(correlationId: correlationId, source: .stickerMediaInput(input: .inputPanelSearch(itemNode: sourceNode), replyPanel: replyPanel), initiated: {}) + } else if let sourceNode = sourceNode as? HorizontalStickerGridItemNode { + strongSelf.chatDisplayNode.messageTransitionNode.add(correlationId: correlationId, source: .stickerMediaInput(input: .mediaPanel(itemNode: sourceNode), replyPanel: replyPanel), initiated: {}) + } else if let sourceNode = sourceNode as? StickerPaneSearchStickerItemNode { + strongSelf.chatDisplayNode.messageTransitionNode.add(correlationId: correlationId, source: .stickerMediaInput(input: .inputPanelSearch(itemNode: sourceNode), replyPanel: replyPanel), initiated: {}) + } } strongSelf.sendMessages([.message(text: "", attributes: attributes, mediaReference: fileReference.abstract, replyToMessageId: strongSelf.presentationInterfaceState.interfaceState.replyMessageId, localGroupingKey: nil, correlationId: correlationId)]) @@ -4512,7 +4514,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } else { isScheduledMessages = false } - strongSelf.chatDisplayNode.containerLayoutUpdated(validLayout, navigationBarHeight: strongSelf.navigationHeight, transition: .animated(duration: 0.5, curve: .custom(0.33, 0.0, 0.0, 1.0)), listViewTransaction: { updateSizeAndInsets, _, _, _ in + strongSelf.chatDisplayNode.containerLayoutUpdated(validLayout, navigationBarHeight: strongSelf.navigationHeight, transition: .animated(duration: strongSelf.chatDisplayNode.messageTransitionNode.hasScheduledTransitions ? 0.5 : 0.4, curve: strongSelf.chatDisplayNode.messageTransitionNode.hasScheduledTransitions ? .custom(0.33, 0.0, 0.0, 1.0) : .spring), listViewTransaction: { updateSizeAndInsets, _, _, _ in var options = transition.options let _ = options.insert(.Synchronous) let _ = options.insert(.LowLatency) @@ -9740,8 +9742,13 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G isScheduledMessages = true } - self.videoRecorder.set(.single(legacyInstantVideoController(theme: self.presentationData.theme, panelFrame: self.view.convert(currentInputPanelFrame, to: nil), context: self.context, peerId: peerId, slowmodeState: !isScheduledMessages ? self.presentationInterfaceState.slowmodeState : nil, hasSchedule: !isScheduledMessages && peerId.namespace != Namespaces.Peer.SecretChat, send: { [weak self] message in + self.videoRecorder.set(.single(legacyInstantVideoController(theme: self.presentationData.theme, panelFrame: self.view.convert(currentInputPanelFrame, to: nil), context: self.context, peerId: peerId, slowmodeState: !isScheduledMessages ? self.presentationInterfaceState.slowmodeState : nil, hasSchedule: !isScheduledMessages && peerId.namespace != Namespaces.Peer.SecretChat, send: { [weak self] videoController, message in if let strongSelf = self { + guard let message = message else { + strongSelf.videoRecorder.set(.single(nil)) + return + } + let replyMessageId = strongSelf.presentationInterfaceState.interfaceState.replyMessageId strongSelf.chatDisplayNode.setupSendActionOnViewUpdate({ if let strongSelf = self { @@ -9750,7 +9757,23 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G }) } }) - let updatedMessage = message.withUpdatedReplyToMessageId(replyMessageId) + let correlationId = Int64.random(in: 0 ..< Int64.max) + let updatedMessage = message + .withUpdatedReplyToMessageId(replyMessageId) + .withUpdatedCorrelationId(correlationId) + + if strongSelf.chatDisplayNode.shouldAnimateMessageTransition, let extractedView = videoController.extractVideoSnapshot() { + strongSelf.chatDisplayNode.messageTransitionNode.add(correlationId: correlationId, source: .videoMessage(ChatMessageTransitionNode.Source.VideoMessage(view: extractedView)), initiated: { [weak videoController] in + videoController?.hideVideoSnapshot() + guard let strongSelf = self else { + return + } + strongSelf.videoRecorder.set(.single(nil)) + }) + } else { + strongSelf.videoRecorder.set(.single(nil)) + } + strongSelf.sendMessages([updatedMessage]) } }, displaySlowmodeTooltip: { [weak self] node, rect in @@ -9850,7 +9873,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G let correlationId = Int64.random(in: 0 ..< Int64.max) - if let textInputPanelNode = strongSelf.chatDisplayNode.textInputPanelNode, let micButton = textInputPanelNode.micButton { + if strongSelf.chatDisplayNode.shouldAnimateMessageTransition, let textInputPanelNode = strongSelf.chatDisplayNode.textInputPanelNode, let micButton = textInputPanelNode.micButton { strongSelf.chatDisplayNode.messageTransitionNode.add(correlationId: correlationId, source: .audioMicInput(ChatMessageTransitionNode.Source.AudioMicInput(micButton: micButton)), initiated: { guard let strongSelf = self else { return @@ -9873,7 +9896,6 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G if case .send = updatedAction { self.chatDisplayNode.updateRecordedMediaDeleted(false) videoRecorderValue.completeVideo() - self.videoRecorder.set(.single(nil)) } else { if case .dismiss = updatedAction { self.chatDisplayNode.updateRecordedMediaDeleted(true) diff --git a/submodules/TelegramUI/Sources/ChatControllerNode.swift b/submodules/TelegramUI/Sources/ChatControllerNode.swift index b987887b6a..6c3c6b2841 100644 --- a/submodules/TelegramUI/Sources/ChatControllerNode.swift +++ b/submodules/TelegramUI/Sources/ChatControllerNode.swift @@ -782,6 +782,11 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { } else { transition = protoTransition } + + var previousListBottomInset: CGFloat? + if !self.historyNode.frame.isEmpty { + previousListBottomInset = self.historyNode.insets.top + } self.scheduledLayoutTransitionRequest = nil if case .overlay = self.chatPresentationInterfaceState.mode { @@ -1875,10 +1880,9 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { self.updatePlainInputSeparator(transition: transition) - let previousInputHeightValue = (self.derivedLayoutState?.inputNodeHeight ?? 0.0) + (self.derivedLayoutState?.inputNodeAdditionalHeight ?? 0.0) - let updatedInputHeight = (inputNodeHeightAndOverflow?.0 ?? 0.0) + (inputNodeHeightAndOverflow?.1 ?? 0.0) - if previousInputHeightValue != updatedInputHeight { - self.historyNode.didScrollWithOffset?(previousInputHeightValue - updatedInputHeight, transition, nil) + let listBottomInset = self.historyNode.insets.bottom + if let previousListBottomInset = previousListBottomInset, listBottomInset != previousListBottomInset { + self.historyNode.didScrollWithOffset?(listBottomInset - previousListBottomInset, transition, nil) } self.derivedLayoutState = ChatControllerNodeDerivedLayoutState(inputContextPanelsFrame: inputContextPanelsFrame, inputContextPanelsOverMainPanelFrame: inputContextPanelsOverMainPanelFrame, inputNodeHeight: inputNodeHeightAndOverflow?.0, inputNodeAdditionalHeight: inputNodeHeightAndOverflow?.1, upperInputPositionBound: inputNodeHeightAndOverflow?.0 != nil ? self.upperInputPositionBound : nil) @@ -2683,7 +2687,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { if let accessoryPanelNode = self.accessoryPanelNode as? ReplyAccessoryPanelNode { replyPanel = accessoryPanelNode } - if let inputPanelNode = self.inputPanelNode as? ChatTextInputPanelNode, let textInput = inputPanelNode.makeSnapshotForTransition() { + if self.shouldAnimateMessageTransition, let inputPanelNode = self.inputPanelNode as? ChatTextInputPanelNode, let textInput = inputPanelNode.makeSnapshotForTransition() { let source: ChatMessageTransitionNode.Source = .textInput(textInput: textInput, replyPanel: replyPanel) self.messageTransitionNode.add(correlationId: correlationId, source: source, initiated: { }) @@ -2893,4 +2897,13 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { } self.historyNode.isHidden = isBlurred } + + var shouldAnimateMessageTransition: Bool { + switch self.historyNode.visibleContentOffset() { + case .known(0.0): + return true + default: + return false + } + } } diff --git a/submodules/TelegramUI/Sources/ChatMessageInstantVideoItemNode.swift b/submodules/TelegramUI/Sources/ChatMessageInstantVideoItemNode.swift index 30c9380450..d77afd6a67 100644 --- a/submodules/TelegramUI/Sources/ChatMessageInstantVideoItemNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageInstantVideoItemNode.swift @@ -20,7 +20,7 @@ private let inlineBotPrefixFont = Font.regular(14.0) private let inlineBotNameFont = nameFont class ChatMessageInstantVideoItemNode: ChatMessageItemView { - private let contextSourceNode: ContextExtractedContentContainingNode + let contextSourceNode: ContextExtractedContentContainingNode private let containerNode: ContextControllerSourceNode private let interactiveVideoNode: ChatMessageInteractiveInstantVideoNode @@ -985,6 +985,10 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView { } } } + + override func cancelInsertionAnimations() { + self.layer.removeAllAnimations() + } override func animateInsertion(_ currentTimestamp: Double, duration: Double, short: Bool) { super.animateInsertion(currentTimestamp, duration: duration, short: short) @@ -1003,7 +1007,12 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView { self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) } - + + func animateFromSnapshot(snapshotView: UIView, transition: ContainedViewLayoutTransition) { + snapshotView.frame = self.interactiveVideoNode.view.convert(snapshotView.frame, from: self.contextSourceNode.contentNode.view) + self.interactiveVideoNode.animateFromSnapshot(snapshotView: snapshotView, transition: transition) + } + override func playMediaWithSound() -> ((Double?) -> Void, Bool, Bool, Bool, ASDisplayNode?)? { return self.interactiveVideoNode.playMediaWithSound() } diff --git a/submodules/TelegramUI/Sources/ChatMessageInteractiveInstantVideoNode.swift b/submodules/TelegramUI/Sources/ChatMessageInteractiveInstantVideoNode.swift index b11555a80f..d65ffa39ca 100644 --- a/submodules/TelegramUI/Sources/ChatMessageInteractiveInstantVideoNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageInteractiveInstantVideoNode.swift @@ -39,7 +39,7 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { private var statusNode: RadialStatusNode? private var playbackStatusNode: InstantVideoRadialStatusNode? - private var videoFrame: CGRect? + private(set) var videoFrame: CGRect? private var item: ChatMessageBubbleContentItem? private var automaticDownload: Bool? @@ -832,5 +832,28 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { return nil } } + + func animateFromSnapshot(snapshotView: UIView, transition: ContainedViewLayoutTransition) { + guard let videoFrame = self.videoFrame else { + return + } + + let scale = videoFrame.height / snapshotView.frame.height + snapshotView.transform = CGAffineTransform(scaleX: scale, y: scale) + snapshotView.center = CGPoint(x: videoFrame.midX, y: videoFrame.midY) + + self.view.addSubview(snapshotView) + + transition.updateAlpha(layer: snapshotView.layer, alpha: 0.0, completion: { [weak snapshotView] _ in + snapshotView?.removeFromSuperview() + }) + + transition.animateTransformScale(node: self, from: 1.0 / scale) + + self.dateAndStatusNode.layer.animateAlpha(from: 0.0, to: self.dateAndStatusNode.alpha, duration: 0.15, delay: 0.18) + if let durationNode = self.durationNode { + durationNode.layer.animateAlpha(from: 0.0, to: durationNode.alpha, duration: 0.15, delay: 0.18) + } + } } diff --git a/submodules/TelegramUI/Sources/ChatMessageTransitionNode.swift b/submodules/TelegramUI/Sources/ChatMessageTransitionNode.swift index 5ae9f755ab..518f584a25 100644 --- a/submodules/TelegramUI/Sources/ChatMessageTransitionNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageTransitionNode.swift @@ -139,9 +139,18 @@ final class ChatMessageTransitionNode: ASDisplayNode { } } + final class VideoMessage { + let view: UIView + + init(view: UIView) { + self.view = view + } + } + case textInput(textInput: TextInput, replyPanel: ReplyAccessoryPanelNode?) case stickerMediaInput(input: StickerInput, replyPanel: ReplyAccessoryPanelNode?) case audioMicInput(AudioMicInput) + case videoMessage(VideoMessage) } private final class AnimatingItemNode: ASDisplayNode { @@ -340,6 +349,35 @@ final class ChatMessageTransitionNode: ASDisplayNode { } } } + case let .videoMessage(videoMessage): + let transition: ContainedViewLayoutTransition = .animated(duration: verticalDuration, curve: .custom(0.33, 0.0, 0.0, 1.0)) + + if let itemNode = self.itemNode as? ChatMessageInstantVideoItemNode { + itemNode.cancelInsertionAnimations() + + self.contextSourceNode.isExtractedToContextPreview = true + self.contextSourceNode.isExtractedToContextPreviewUpdated?(true) + + self.containerNode.addSubnode(self.contextSourceNode.contentNode) + + let sourceAbsoluteRect = videoMessage.view.frame + let targetAbsoluteRect = self.contextSourceNode.view.convert(self.contextSourceNode.contentRect, to: nil) + + videoMessage.view.frame = videoMessage.view.frame.offsetBy(dx: targetAbsoluteRect.midX - sourceAbsoluteRect.midX, dy: targetAbsoluteRect.midY - sourceAbsoluteRect.midY) + + self.containerNode.frame = targetAbsoluteRect.offsetBy(dx: -self.contextSourceNode.contentRect.minX, dy: -self.contextSourceNode.contentRect.minY) + self.containerNode.layer.animatePosition(from: CGPoint(x: 0.0, y: sourceAbsoluteRect.midY - targetAbsoluteRect.midY), to: CGPoint(), duration: horizontalDuration, delay: delay, mediaTimingFunction: CAMediaTimingFunction(controlPoints: 0.33, 0.0, 0.0, 1.0), additive: true, force: true) + + self.containerNode.layer.animatePosition(from: CGPoint(x: sourceAbsoluteRect.midX - targetAbsoluteRect.midX, y: 0.0), to: CGPoint(), duration: verticalDuration, delay: delay, mediaTimingFunction: CAMediaTimingFunction(controlPoints: 0.33, 0.0, 0.0, 1.0), additive: true, completion: { [weak self] _ in + guard let strongSelf = self else { + return + } + + strongSelf.endAnimation() + }) + + itemNode.animateFromSnapshot(snapshotView: videoMessage.view, transition: transition) + } } } @@ -372,6 +410,10 @@ final class ChatMessageTransitionNode: ASDisplayNode { private var animatingItemNodes: [AnimatingItemNode] = [] + var hasScheduledTransitions: Bool { + return self.currentPendingItem != nil + } + init(listNode: ChatHistoryListNode) { self.listNode = listNode @@ -402,17 +444,20 @@ final class ChatMessageTransitionNode: ASDisplayNode { contextSourceNode = itemNode.contextSourceNode } else if let itemNode = itemNode as? ChatMessageAnimatedStickerItemNode { contextSourceNode = itemNode.contextSourceNode + } else if let itemNode = itemNode as? ChatMessageInstantVideoItemNode { + contextSourceNode = itemNode.contextSourceNode } if let contextSourceNode = contextSourceNode { let animatingItemNode = AnimatingItemNode(itemNode: itemNode, contextSourceNode: contextSourceNode, source: source) self.animatingItemNodes.append(animatingItemNode) - if case .audioMicInput = source { + switch source { + case .audioMicInput, .videoMessage: let overlayController = OverlayTransitionContainerController() overlayController.displayNode.addSubnode(animatingItemNode) animatingItemNode.overlayController = overlayController itemNode.item?.context.sharedContext.mainWindow?.presentInGlobalOverlay(overlayController) - } else { + default: self.addSubnode(animatingItemNode) } diff --git a/submodules/TelegramUI/Sources/LegacyInstantVideoController.swift b/submodules/TelegramUI/Sources/LegacyInstantVideoController.swift index 94ea2786eb..6206d9cdb8 100644 --- a/submodules/TelegramUI/Sources/LegacyInstantVideoController.swift +++ b/submodules/TelegramUI/Sources/LegacyInstantVideoController.swift @@ -35,8 +35,9 @@ final class InstantVideoController: LegacyController, StandalonePresentableContr private let micLevelValue = ValuePromise(0.0) private let durationValue = ValuePromise(0.0) let audioStatus: InstantVideoControllerRecordingStatus - - private var dismissedVideo = false + + private var completed = false + private var dismissed = false override init(presentation: LegacyControllerPresentation, theme: PresentationTheme?, strings: PresentationStrings? = nil, initialLayout: ContainerViewLayout? = nil) { self.audioStatus = InstantVideoControllerRecordingStatus(micLevel: self.micLevelValue.get(), duration: self.durationValue.get()) @@ -61,8 +62,8 @@ final class InstantVideoController: LegacyController, StandalonePresentableContr } captureController.onDismiss = { [weak self] _, isCancelled in guard let strongSelf = self else { return } - if !strongSelf.dismissedVideo { - self?.dismissedVideo = true + if !strongSelf.dismissed { + self?.dismissed = true self?.onDismiss?(isCancelled) } } @@ -73,18 +74,33 @@ final class InstantVideoController: LegacyController, StandalonePresentableContr } func dismissVideo() { - if let captureController = self.captureController, !self.dismissedVideo { - self.dismissedVideo = true - captureController.dismiss() + if let captureController = self.captureController, !self.dismissed { + self.dismissed = true + captureController.dismiss(true) } } + + func extractVideoSnapshot() -> UIView? { + self.captureController?.extractVideoContent() + } + + func hideVideoSnapshot() { + self.captureController?.hideVideoContent() + } func completeVideo() { - if let captureController = self.captureController, !self.dismissedVideo { - self.dismissedVideo = true + if let captureController = self.captureController, !self.completed { + self.completed = true captureController.complete() } } + + func dismissAnimated() { + if let captureController = self.captureController, !self.dismissed { + self.dismissed = true + captureController.dismiss(false) + } + } func stopVideo() -> Bool { if let captureController = self.captureController { @@ -111,7 +127,7 @@ func legacyInputMicPalette(from theme: PresentationTheme) -> TGModernConversatio return TGModernConversationInputMicPallete(dark: theme.overallDarkAppearance, buttonColor: inputPanelTheme.actionControlFillColor, iconColor: inputPanelTheme.actionControlForegroundColor, backgroundColor: inputPanelTheme.panelBackgroundColor, borderColor: inputPanelTheme.panelSeparatorColor, lock: inputPanelTheme.panelControlAccentColor, textColor: inputPanelTheme.primaryTextColor, secondaryTextColor: inputPanelTheme.secondaryTextColor, recording: inputPanelTheme.mediaRecordingDotColor) } -func legacyInstantVideoController(theme: PresentationTheme, panelFrame: CGRect, context: AccountContext, peerId: PeerId, slowmodeState: ChatSlowmodeState?, hasSchedule: Bool, send: @escaping (EnqueueMessage) -> Void, displaySlowmodeTooltip: @escaping (ASDisplayNode, CGRect) -> Void, presentSchedulePicker: @escaping (@escaping (Int32) -> Void) -> Void) -> InstantVideoController { +func legacyInstantVideoController(theme: PresentationTheme, panelFrame: CGRect, context: AccountContext, peerId: PeerId, slowmodeState: ChatSlowmodeState?, hasSchedule: Bool, send: @escaping (InstantVideoController, EnqueueMessage?) -> Void, displaySlowmodeTooltip: @escaping (ASDisplayNode, CGRect) -> Void, presentSchedulePicker: @escaping (@escaping (Int32) -> Void) -> Void) -> InstantVideoController { let isSecretChat = peerId.namespace == Namespaces.Peer.SecretChat let legacyController = InstantVideoController(presentation: .custom, theme: theme) @@ -147,8 +163,13 @@ func legacyInstantVideoController(theme: PresentationTheme, panelFrame: CGRect, done?(time) } } - controller.finishedWithVideo = { videoUrl, previewImage, _, duration, dimensions, liveUploadData, adjustments, isSilent, scheduleTimestamp in + controller.finishedWithVideo = { [weak legacyController] videoUrl, previewImage, _, duration, dimensions, liveUploadData, adjustments, isSilent, scheduleTimestamp in + guard let legacyController = legacyController else { + return + } + guard let videoUrl = videoUrl else { + send(legacyController, nil) return } @@ -220,7 +241,7 @@ func legacyInstantVideoController(theme: PresentationTheme, panelFrame: CGRect, return attributes } - send(message) + send(legacyController, message) } controller.didDismiss = { [weak legacyController] in if let legacyController = legacyController {