diff --git a/submodules/AnimatedStickerNode/Sources/AnimatedStickerNode.swift b/submodules/AnimatedStickerNode/Sources/AnimatedStickerNode.swift index 889af3cae8..ed5d1758fb 100644 --- a/submodules/AnimatedStickerNode/Sources/AnimatedStickerNode.swift +++ b/submodules/AnimatedStickerNode/Sources/AnimatedStickerNode.swift @@ -56,6 +56,7 @@ public enum AnimatedStickerPlaybackPosition { case start case end case timestamp(Double) + case frameIndex(Int) } public enum AnimatedStickerPlaybackMode { @@ -94,6 +95,7 @@ public protocol AnimatedStickerFrameSource: class { func takeFrame(draw: Bool) -> AnimatedStickerFrame? func skipToEnd() + func skipToFrameIndex(_ index: Int) } private final class AnimatedStickerFrameSourceWrapper { @@ -271,6 +273,9 @@ public final class AnimatedStickerCachedFrameSource: AnimatedStickerFrameSource public func skipToEnd() { } + + public func skipToFrameIndex(_ index: Int) { + } } private func wrappedWrite(_ fd: Int32, _ data: UnsafeRawPointer, _ count: Int) -> Int { @@ -656,6 +661,10 @@ private final class AnimatedStickerDirectFrameSource: AnimatedStickerFrameSource func skipToEnd() { self.currentFrame = self.frameCount - 1 } + + func skipToFrameIndex(_ index: Int) { + self.currentFrame = index + } } public final class AnimatedStickerFrameQueue { @@ -748,6 +757,8 @@ public final class AnimatedStickerNode: ASDisplayNode { public var completed: (Bool) -> Void = { _ in } public var frameUpdated: (Int, Int) -> Void = { _, _ in } + public private(set) var currentFrameIndex: Int = 0 + private var playFromIndex: Int? private let timer = Atomic(value: nil) private let frameSource = Atomic?>(value: nil) @@ -842,7 +853,9 @@ public final class AnimatedStickerNode: ASDisplayNode { strongSelf.isSetUpForPlayback = false strongSelf.isPlaying = true } - strongSelf.play() + var fromIndex = strongSelf.playFromIndex + strongSelf.playFromIndex = nil + strongSelf.play(fromIndex: fromIndex) } else if strongSelf.canDisplayFirstFrame { strongSelf.play(firstFrame: true) } @@ -911,7 +924,7 @@ public final class AnimatedStickerNode: ASDisplayNode { private var isSetUpForPlayback = false - public func play(firstFrame: Bool = false) { + public func play(firstFrame: Bool = false, fromIndex: Int? = nil) { switch self.playbackMode { case .once: self.isPlaying = true @@ -949,6 +962,9 @@ public final class AnimatedStickerNode: ASDisplayNode { guard let frameSource = maybeFrameSource else { return } + if let fromIndex = fromIndex { + frameSource.skipToFrameIndex(fromIndex) + } let frameQueue = QueueLocalObject(queue: queue, generate: { return AnimatedStickerFrameQueue(queue: queue, length: 1, source: frameSource) }) @@ -978,6 +994,7 @@ public final class AnimatedStickerNode: ASDisplayNode { }) strongSelf.frameUpdated(frame.index, frame.totalFrames) + strongSelf.currentFrameIndex = frame.index if frame.isLastFrame { var stopped = false @@ -1016,6 +1033,9 @@ public final class AnimatedStickerNode: ASDisplayNode { self.isSetUpForPlayback = true let directData = self.directData let cachedData = self.cachedData + if directData == nil && cachedData == nil { + self.playFromIndex = fromIndex + } let queue = self.queue let timerHolder = self.timer let frameSourceHolder = self.frameSource @@ -1039,6 +1059,9 @@ public final class AnimatedStickerNode: ASDisplayNode { guard let frameSource = maybeFrameSource else { return } + if let fromIndex = fromIndex { + frameSource.skipToFrameIndex(fromIndex) + } let frameQueue = QueueLocalObject(queue: queue, generate: { return AnimatedStickerFrameQueue(queue: queue, length: 1, source: frameSource) }) @@ -1068,6 +1091,7 @@ public final class AnimatedStickerNode: ASDisplayNode { }) strongSelf.frameUpdated(frame.index, frame.totalFrames) + strongSelf.currentFrameIndex = frame.index if frame.isLastFrame { var stopped = false @@ -1163,6 +1187,21 @@ public final class AnimatedStickerNode: ASDisplayNode { return } + var delta = targetFrame - frameSource.frameIndex + if delta < 0 { + delta = frameSource.frameCount + delta + } + for i in 0 ..< delta { + maybeFrame = frameQueue.syncWith { frameQueue in + return frameQueue.take(draw: i == delta - 1) + } + } + } else if case let .frameIndex(frameIndex) = position { + let targetFrame = frameIndex + if targetFrame == frameSource.frameIndex { + return + } + var delta = targetFrame - frameSource.frameIndex if delta < 0 { delta = frameSource.frameCount + delta diff --git a/submodules/Display/Source/ListView.swift b/submodules/Display/Source/ListView.swift index c527ad0c90..d188c563c7 100644 --- a/submodules/Display/Source/ListView.swift +++ b/submodules/Display/Source/ListView.swift @@ -2850,6 +2850,7 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture for itemNode in self.itemNodes { itemNode.applyAbsoluteOffset(value: CGPoint(x: 0.0, y: -completeOffset), animationCurve: animationCurve, duration: animationDuration) } + //self.didScrollWithOffset?(-completeOffset, ContainedViewLayoutTransition.animated(duration: animationDuration, curve: animationCurve), nil) } } else { self.visibleSize = updateSizeAndInsets.size @@ -3089,22 +3090,6 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture animation = springAnimation reverseAnimation = reverseSpringAnimation - /* - case let .Custom(duration, cp1x, cp1y, cp2x, cp2y): - headerNodesTransition = (.animated(duration: duration, curve: .custom(cp1x, cp1y, cp2x, cp2y)), false, -completeOffset) - animationCurve = .custom(cp1x, cp1y, cp2x, cp2y) - let springAnimation = CABasicAnimation(keyPath: "sublayerTransform") - springAnimation.timingFunction = CAMediaTimingFunction(controlPoints: cp1x, cp1y, cp2x, cp2y) - springAnimation.duration = duration * UIView.animationDurationFactor() - springAnimation.fromValue = NSValue(caTransform3D: CATransform3DMakeTranslation(0.0, -completeOffset, 0.0)) - springAnimation.toValue = NSValue(caTransform3D: CATransform3DIdentity) - springAnimation.isRemovedOnCompletion = true - - animationDuration = duration - - springAnimation.isAdditive = true - animation = springAnimation - */ case let .Custom(duration, cp1x, cp1y, cp2x, cp2y): animationCurve = .custom(cp1x, cp1y, cp2x, cp2y) animationDuration = duration @@ -3188,6 +3173,7 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture for itemNode in temporaryPreviousNodes { itemNode.applyAbsoluteOffset(value: CGPoint(x: 0.0, y: -offset), animationCurve: animationCurve, duration: animationDuration) } + self.didScrollWithOffset?(offset, .animated(duration: animationDuration, curve: animationCurve), nil) if let verticalScrollIndicator = self.verticalScrollIndicator { verticalScrollIndicator.layer.add(reverseAnimation, forKey: nil) } diff --git a/submodules/Display/Source/ListViewAnimation.swift b/submodules/Display/Source/ListViewAnimation.swift index aed2d242c3..eb936f51a4 100644 --- a/submodules/Display/Source/ListViewAnimation.swift +++ b/submodules/Display/Source/ListViewAnimation.swift @@ -183,27 +183,19 @@ public final class ListViewAnimation { } public func listViewAnimationDurationAndCurve(transition: ContainedViewLayoutTransition) -> (Double, ListViewAnimationCurve) { - var duration: Double = 0.0 - var curve: UInt = 0 switch transition { case .immediate: - break + return (0.0, .Default(duration: 0.0)) case let .animated(animationDuration, animationCurve): - duration = animationDuration switch animationCurve { - case .linear, .easeInOut, .custom: - break - case .spring: - curve = 7 + case .linear: + return (animationDuration, .Default(duration: animationDuration)) + case .easeInOut: + return (animationDuration, .Default(duration: animationDuration)) + case .spring: + return (animationDuration, .Spring(duration: animationDuration)) + case let .custom(c1x, c1y, c2x, c2y): + return (animationDuration, .Custom(duration: animationDuration, c1x, c1y, c2x, c2y)) } } - - let listViewCurve: ListViewAnimationCurve - if curve == 7 { - listViewCurve = .Spring(duration: duration) - } else { - listViewCurve = .Default(duration: duration) - } - - return (duration, listViewCurve) } diff --git a/submodules/LegacyComponents/Sources/TGModernConversationInputMicButton.m b/submodules/LegacyComponents/Sources/TGModernConversationInputMicButton.m index 34099398e4..21c0567fc6 100644 --- a/submodules/LegacyComponents/Sources/TGModernConversationInputMicButton.m +++ b/submodules/LegacyComponents/Sources/TGModernConversationInputMicButton.m @@ -249,7 +249,7 @@ static const CGFloat outerCircleMinScale = innerCircleRadius / outerCircleRadius _innerCircleView.center = centerPoint; _outerCircleView.center = centerPoint; _decoration.center = centerPoint; - _innerIconWrapperView.center = centerPoint; + _innerIconWrapperView.center = CGPointMake(_decoration.frame.size.width / 2.0f, _decoration.frame.size.height / 2.0f); _lockPanelWrapperView.frame = CGRectMake(floor(centerPoint.x - _lockPanelWrapperView.frame.size.width / 2.0f), floor(centerPoint.y - 122.0f - _lockPanelWrapperView.frame.size.height / 2.0f), _lockPanelWrapperView.frame.size.width, _lockPanelWrapperView.frame.size.height); @@ -421,7 +421,8 @@ static const CGFloat outerCircleMinScale = innerCircleRadius / outerCircleRadius _innerIconWrapperView.userInteractionEnabled = false; [_innerIconWrapperView addSubview:_innerIconView]; - [[_presentation view] addSubview:_innerIconWrapperView]; + //[[_presentation view] addSubview:_innerIconWrapperView]; + [_decoration addSubview:_innerIconWrapperView]; if (_lock == nil) { _stopButton = [[TGModernButton alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 40.0f, 40.0f)]; diff --git a/submodules/TelegramStringFormatting/Sources/CurrencyFormat.swift b/submodules/TelegramStringFormatting/Sources/CurrencyFormat.swift index 632299a592..9d9361cf14 100644 --- a/submodules/TelegramStringFormatting/Sources/CurrencyFormat.swift +++ b/submodules/TelegramStringFormatting/Sources/CurrencyFormat.swift @@ -61,7 +61,9 @@ public func setupCurrencyNumberFormatter(currency: String) -> NumberFormatter { result.append("#") - result.append(entry.decimalSeparator) + if entry.decimalDigits != 0 { + result.append(entry.decimalSeparator) + } for _ in 0 ..< entry.decimalDigits { result.append("#") @@ -143,7 +145,9 @@ public func formatCurrencyAmount(_ amount: Int64, currency: String) -> String { } } result.append("\(integerPart)") - result.append(entry.decimalSeparator) + if !fractional.isEmpty { + result.append(entry.decimalSeparator) + } for i in 0 ..< fractional.count { result.append(fractional[fractional.count - i - 1]) } @@ -187,7 +191,9 @@ public func formatCurrencyAmountCustom(_ amount: Int64, currency: String) -> (St } } result.append("\(integerPart)") - result.append(entry.decimalSeparator) + if !fractional.isEmpty { + result.append(entry.decimalSeparator) + } for i in 0 ..< fractional.count { result.append(fractional[fractional.count - i - 1]) } diff --git a/submodules/TelegramUI/Resources/currencies.json b/submodules/TelegramUI/Resources/currencies.json index 5e332a88fa..93730660d1 100644 --- a/submodules/TelegramUI/Resources/currencies.json +++ b/submodules/TelegramUI/Resources/currencies.json @@ -267,7 +267,7 @@ "decimalSeparator": ",", "symbolOnLeft": true, "spaceBetweenAmountAndSymbol": true, - "decimalDigits": 2 + "decimalDigits": 0 }, "CNY": { "code": "CNY", @@ -1356,7 +1356,7 @@ "decimalSeparator": ",", "symbolOnLeft": false, "spaceBetweenAmountAndSymbol": true, - "decimalDigits": 1 + "decimalDigits": 0 }, "VUV": { "code": "VUV", diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 00b5a85e8f..b6afb42431 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -1042,8 +1042,38 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G if let query = query { attributes.append(EmojiSearchQueryMessageAttribute(query: query)) } + + let correlationId = Int64.random(in: 0 ..< Int64.max) + + var replyPanel: ReplyAccessoryPanelNode? + if let accessoryPanelNode = strongSelf.chatDisplayNode.accessoryPanelNode as? ReplyAccessoryPanelNode { + 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 + } + + 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: {}) + } - strongSelf.sendMessages([.message(text: "", attributes: attributes, mediaReference: fileReference.abstract, replyToMessageId: strongSelf.presentationInterfaceState.interfaceState.replyMessageId, localGroupingKey: nil, correlationId: nil)]) + strongSelf.sendMessages([.message(text: "", attributes: attributes, mediaReference: fileReference.abstract, replyToMessageId: strongSelf.presentationInterfaceState.interfaceState.replyMessageId, localGroupingKey: nil, correlationId: correlationId)]) return true }, sendGif: { [weak self] fileReference, sourceNode, sourceRect in if let strongSelf = self { @@ -4616,8 +4646,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } } - self.chatDisplayNode.requestUpdateChatInterfaceState = { [weak self] animated, saveInterfaceState, f in - self?.updateChatPresentationInterfaceState(animated: animated, interactive: true, saveInterfaceState: saveInterfaceState, { $0.updatedInterfaceState(f) }) + self.chatDisplayNode.requestUpdateChatInterfaceState = { [weak self] transition, saveInterfaceState, f in + self?.updateChatPresentationInterfaceState(transition: transition, interactive: true, saveInterfaceState: saveInterfaceState, { $0.updatedInterfaceState(f) }) } self.chatDisplayNode.requestUpdateInterfaceState = { [weak self] transition, interactive, f in @@ -9758,7 +9788,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G switch updatedAction { case .dismiss: self.chatDisplayNode.updateRecordedMediaDeleted(true) - break + self.audioRecorder.set(.single(nil)) case .preview: self.updateChatPresentationInterfaceState(animated: true, interactive: true, { $0.updatedInputTextPanelState { panelState in @@ -9789,6 +9819,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } } }) + self.audioRecorder.set(.single(nil)) case .send: self.chatDisplayNode.updateRecordedMediaDeleted(false) let _ = (audioRecorderValue.takenRecordedData() @@ -9797,6 +9828,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G if data.duration < 0.5 { strongSelf.recorderFeedback?.error() strongSelf.recorderFeedback = nil + strongSelf.audioRecorder.set(.single(nil)) } else { let randomId = arc4random64() @@ -9815,8 +9847,21 @@ 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 { + strongSelf.chatDisplayNode.messageTransitionNode.add(correlationId: correlationId, source: .audioMicInput(ChatMessageTransitionNode.Source.AudioMicInput(micButton: micButton)), initiated: { + guard let strongSelf = self else { + return + } + strongSelf.audioRecorder.set(.single(nil)) + }) + } else { + strongSelf.audioRecorder.set(.single(nil)) + } - strongSelf.sendMessages([.message(text: "", attributes: [], mediaReference: .standalone(media: TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: randomId), partialReference: nil, resource: resource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "audio/ogg", size: data.compressedData.count, attributes: [.Audio(isVoice: true, duration: Int(data.duration), title: nil, performer: nil, waveform: waveformBuffer)])), replyToMessageId: strongSelf.presentationInterfaceState.interfaceState.replyMessageId, localGroupingKey: nil, correlationId: nil)]) + strongSelf.sendMessages([.message(text: "", attributes: [], mediaReference: .standalone(media: TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: randomId), partialReference: nil, resource: resource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "audio/ogg", size: data.compressedData.count, attributes: [.Audio(isVoice: true, duration: Int(data.duration), title: nil, performer: nil, waveform: waveformBuffer)])), replyToMessageId: strongSelf.presentationInterfaceState.interfaceState.replyMessageId, localGroupingKey: nil, correlationId: correlationId)]) strongSelf.recorderFeedback?.tap() strongSelf.recorderFeedback = nil @@ -9824,7 +9869,6 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } }) } - self.audioRecorder.set(.single(nil)) } else if let videoRecorderValue = self.videoRecorderValue { if case .send = updatedAction { self.chatDisplayNode.updateRecordedMediaDeleted(false) diff --git a/submodules/TelegramUI/Sources/ChatControllerNode.swift b/submodules/TelegramUI/Sources/ChatControllerNode.swift index d82bf18b66..24aafe741c 100644 --- a/submodules/TelegramUI/Sources/ChatControllerNode.swift +++ b/submodules/TelegramUI/Sources/ChatControllerNode.swift @@ -55,6 +55,7 @@ private struct ChatControllerNodeDerivedLayoutState { var inputContextPanelsFrame: CGRect var inputContextPanelsOverMainPanelFrame: CGRect var inputNodeHeight: CGFloat? + var inputNodeAdditionalHeight: CGFloat? var upperInputPositionBound: CGFloat? } @@ -329,7 +330,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { private var inputPanelNode: ChatInputPanelNode? private weak var currentDismissedInputPanelNode: ASDisplayNode? private var secondaryInputPanelNode: ChatInputPanelNode? - private var accessoryPanelNode: AccessoryPanelNode? + private(set) var accessoryPanelNode: AccessoryPanelNode? private var inputContextPanelNode: ChatInputContextPanelNode? private let inputContextPanelContainer: ChatControllerTitlePanelNodeContainer private var overlayContextPanelNode: ChatInputContextPanelNode? @@ -337,7 +338,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { private var inputNode: ChatInputNode? private var disappearingNode: ChatInputNode? - private var textInputPanelNode: ChatTextInputPanelNode? + private(set) var textInputPanelNode: ChatTextInputPanelNode? private var inputMediaNode: ChatMediaInputNode? let navigateButtons: ChatHistoryNavigationButtons @@ -377,7 +378,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { } } - var requestUpdateChatInterfaceState: (Bool, Bool, (ChatInterfaceState) -> ChatInterfaceState) -> Void = { _, _, _ in } + var requestUpdateChatInterfaceState: (ContainedViewLayoutTransition, Bool, (ChatInterfaceState) -> ChatInterfaceState) -> Void = { _, _, _ in } var requestUpdateInterfaceState: (ContainedViewLayoutTransition, Bool, (ChatPresentationInterfaceState) -> ChatPresentationInterfaceState) -> Void = { _, _, _ in } var sendMessages: ([EnqueueMessage], Bool?, Int32?, Bool) -> Void = { _, _, _, _ in } var displayAttachmentMenu: () -> Void = { } @@ -1299,9 +1300,9 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { accessoryPanelNode.dismiss = { [weak self, weak accessoryPanelNode] in if let strongSelf = self, let accessoryPanelNode = accessoryPanelNode, strongSelf.accessoryPanelNode === accessoryPanelNode { if let _ = accessoryPanelNode as? ReplyAccessoryPanelNode { - strongSelf.requestUpdateChatInterfaceState(true, false, { $0.withUpdatedReplyMessageId(nil) }) + strongSelf.requestUpdateChatInterfaceState(.animated(duration: 0.4, curve: .spring), false, { $0.withUpdatedReplyMessageId(nil) }) } else if let _ = accessoryPanelNode as? ForwardAccessoryPanelNode { - strongSelf.requestUpdateChatInterfaceState(true, false, { $0.withUpdatedForwardMessageIds(nil) }) + strongSelf.requestUpdateChatInterfaceState(.animated(duration: 0.4, curve: .spring), false, { $0.withUpdatedForwardMessageIds(nil) }) } else if let _ = accessoryPanelNode as? EditAccessoryPanelNode { strongSelf.interfaceInteraction?.setupEditMessage(nil, { _ in }) } else if let _ = accessoryPanelNode as? WebpagePreviewAccessoryPanelNode { @@ -1873,8 +1874,14 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { } self.updatePlainInputSeparator(transition: transition) - - self.derivedLayoutState = ChatControllerNodeDerivedLayoutState(inputContextPanelsFrame: inputContextPanelsFrame, inputContextPanelsOverMainPanelFrame: inputContextPanelsOverMainPanelFrame, inputNodeHeight: inputNodeHeightAndOverflow?.0, upperInputPositionBound: inputNodeHeightAndOverflow?.0 != nil ? self.upperInputPositionBound : nil) + + 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) + } + + self.derivedLayoutState = ChatControllerNodeDerivedLayoutState(inputContextPanelsFrame: inputContextPanelsFrame, inputContextPanelsOverMainPanelFrame: inputContextPanelsOverMainPanelFrame, inputNodeHeight: inputNodeHeightAndOverflow?.0, inputNodeAdditionalHeight: inputNodeHeightAndOverflow?.1, upperInputPositionBound: inputNodeHeightAndOverflow?.0 != nil ? self.upperInputPositionBound : nil) //self.notifyTransitionCompletionListeners(transition: transition) } @@ -2656,7 +2663,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { if let strongSelf = self, let textInputPanelNode = strongSelf.inputPanelNode as? ChatTextInputPanelNode { strongSelf.ignoreUpdateHeight = true textInputPanelNode.text = "" - strongSelf.requestUpdateChatInterfaceState(false, true, { $0.withUpdatedReplyMessageId(nil).withUpdatedForwardMessageIds(nil).withUpdatedComposeDisableUrlPreview(nil) }) + strongSelf.requestUpdateChatInterfaceState(.immediate, true, { $0.withUpdatedReplyMessageId(nil).withUpdatedForwardMessageIds(nil).withUpdatedComposeDisableUrlPreview(nil) }) strongSelf.ignoreUpdateHeight = false } }) diff --git a/submodules/TelegramUI/Sources/ChatMediaInputStickerGridItem.swift b/submodules/TelegramUI/Sources/ChatMediaInputStickerGridItem.swift index e4aa206ec3..c65e85329d 100644 --- a/submodules/TelegramUI/Sources/ChatMediaInputStickerGridItem.swift +++ b/submodules/TelegramUI/Sources/ChatMediaInputStickerGridItem.swift @@ -174,8 +174,8 @@ final class ChatMediaInputStickerGridItemNode: GridItemNode { private var currentState: (Account, StickerPackItem, CGSize)? private var currentSize: CGSize? let imageNode: TransformImageNode - var animationNode: AnimatedStickerNode? - private var placeholderNode: StickerShimmerEffectNode? + private(set) var animationNode: AnimatedStickerNode? + private(set) var placeholderNode: StickerShimmerEffectNode? private var didSetUpAnimationNode = false private var item: ChatMediaInputStickerGridItem? @@ -308,9 +308,13 @@ final class ChatMediaInputStickerGridItemNode: GridItemNode { if let (_, _, mediaDimensions) = self.currentState { let imageSize = mediaDimensions.aspectFitted(boundingSize) self.imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets()))() - self.imageNode.frame = CGRect(origin: CGPoint(x: floor((size.width - imageSize.width) / 2.0), y: (size.height - imageSize.height) / 2.0), size: imageSize) + if self.imageNode.supernode === self { + self.imageNode.frame = CGRect(origin: CGPoint(x: floor((size.width - imageSize.width) / 2.0), y: (size.height - imageSize.height) / 2.0), size: imageSize) + } if let animationNode = self.animationNode { - animationNode.frame = CGRect(origin: CGPoint(x: floor((size.width - imageSize.width) / 2.0), y: (size.height - imageSize.height) / 2.0), size: imageSize) + if animationNode.supernode === self { + animationNode.frame = CGRect(origin: CGPoint(x: floor((size.width - imageSize.width) / 2.0), y: (size.height - imageSize.height) / 2.0), size: imageSize) + } animationNode.updateLayout(size: imageSize) } } @@ -318,7 +322,9 @@ final class ChatMediaInputStickerGridItemNode: GridItemNode { if let placeholderNode = self.placeholderNode { let placeholderFrame = CGRect(origin: CGPoint(x: floor((size.width - boundingSize.width) / 2.0), y: floor((size.height - boundingSize.height) / 2.0)), size: boundingSize) - placeholderNode.frame = placeholderFrame + if placeholderNode.supernode === self { + placeholderNode.frame = placeholderFrame + } let theme = item.theme placeholderNode.update(backgroundColor: theme.chat.inputMediaPanel.stickersBackgroundColor.withAlphaComponent(1.0), foregroundColor: theme.chat.inputMediaPanel.stickersSectionTextColor.blitOver(theme.chat.inputMediaPanel.stickersBackgroundColor, alpha: 0.15), shimmeringColor: theme.list.itemBlocksBackgroundColor.withAlphaComponent(0.3), data: item.stickerItem.file.immediateThumbnailData, size: placeholderFrame.size) diff --git a/submodules/TelegramUI/Sources/ChatMessageAnimatedStickerItemNode.swift b/submodules/TelegramUI/Sources/ChatMessageAnimatedStickerItemNode.swift index fe7c0e93fa..e89fe77d1b 100644 --- a/submodules/TelegramUI/Sources/ChatMessageAnimatedStickerItemNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageAnimatedStickerItemNode.swift @@ -29,14 +29,25 @@ private let inlineBotNameFont = nameFont protocol GenericAnimatedStickerNode: ASDisplayNode { func setOverlayColor(_ color: UIColor?, animated: Bool) + + var currentFrameIndex: Int { get } + func setFrameIndex(_ frameIndex: Int) } extension AnimatedStickerNode: GenericAnimatedStickerNode { - + func setFrameIndex(_ frameIndex: Int) { + self.stop() + self.play(fromIndex: frameIndex) + } } extension SlotMachineAnimationNode: GenericAnimatedStickerNode { - + var currentFrameIndex: Int { + return 0 + } + + func setFrameIndex(_ frameIndex: Int) { + } } class ChatMessageShareButton: HighlightableButtonNode { @@ -141,11 +152,12 @@ class ChatMessageShareButton: HighlightableButtonNode { } class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { - private let contextSourceNode: ContextExtractedContentContainingNode + let contextSourceNode: ContextExtractedContentContainingNode private let containerNode: ContextControllerSourceNode let imageNode: TransformImageNode - private var placeholderNode: StickerShimmerEffectNode - private var animationNode: GenericAnimatedStickerNode? + private var enableSynchronousImageApply: Bool = false + private(set) var placeholderNode: StickerShimmerEffectNode + private(set) var animationNode: GenericAnimatedStickerNode? private var didSetUpAnimationNode = false private var isPlaying = false private var animateGreeting = false @@ -250,8 +262,12 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { } if image != nil { if firstTime && !strongSelf.placeholderNode.isEmpty && !strongSelf.animateGreeting && !strongSelf.animatingGreeting { - strongSelf.animationNode?.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) - strongSelf.removePlaceholder(animated: true) + if strongSelf.enableSynchronousImageApply { + strongSelf.removePlaceholder(animated: false) + } else { + strongSelf.animationNode?.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + strongSelf.removePlaceholder(animated: true) + } } else { strongSelf.removePlaceholder(animated: true) } @@ -436,15 +452,15 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { } } - override func setupItem(_ item: ChatMessageItem) { - super.setupItem(item) + override func setupItem(_ item: ChatMessageItem, synchronousLoad: Bool) { + super.setupItem(item, synchronousLoad: synchronousLoad) for media in item.message.media { if let telegramFile = media as? TelegramMediaFile { if self.telegramFile?.id != telegramFile.id { self.telegramFile = telegramFile let dimensions = telegramFile.dimensions ?? PixelDimensions(width: 512, height: 512) - self.imageNode.setSignal(chatMessageAnimatedSticker(postbox: item.context.account.postbox, file: telegramFile, small: false, size: dimensions.cgSize.aspectFitted(CGSize(width: 384.0, height: 384.0)), thumbnail: false)) + self.imageNode.setSignal(chatMessageAnimatedSticker(postbox: item.context.account.postbox, file: telegramFile, small: false, size: dimensions.cgSize.aspectFitted(CGSize(width: 384.0, height: 384.0)), thumbnail: false, synchronousLoad: synchronousLoad), attemptSynchronously: synchronousLoad) self.updateVisibility() self.disposable.set(freeMediaFileInteractiveFetched(account: item.context.account, fileReference: .message(message: MessageReference(item.message), media: telegramFile)).start()) } @@ -485,7 +501,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { if let fitz = fitz { fitzModifier = EmojiFitzModifier(emoji: fitz) } - self.imageNode.setSignal(chatMessageAnimatedSticker(postbox: item.context.account.postbox, file: emojiFile, small: false, size: dimensions.cgSize.aspectFilled(CGSize(width: 384.0, height: 384.0)), fitzModifier: fitzModifier, thumbnail: false)) + self.imageNode.setSignal(chatMessageAnimatedSticker(postbox: item.context.account.postbox, file: emojiFile, small: false, size: dimensions.cgSize.aspectFilled(CGSize(width: 384.0, height: 384.0)), fitzModifier: fitzModifier, thumbnail: false, synchronousLoad: synchronousLoad), attemptSynchronously: synchronousLoad) self.disposable.set(freeMediaFileInteractiveFetched(account: item.context.account, fileReference: .standalone(media: emojiFile)).start()) } self.updateVisibility() @@ -1060,7 +1076,10 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { if let animationNode = strongSelf.animationNode as? AnimatedStickerNode, strongSelf.animationNode?.supernode === strongSelf.contextSourceNode.contentNode { animationNode.updateLayout(size: updatedContentFrame.insetBy(dx: imageInset, dy: imageInset).size) } + + strongSelf.enableSynchronousImageApply = true imageApply() + strongSelf.enableSynchronousImageApply = false strongSelf.contextSourceNode.contentRect = strongSelf.imageNode.frame strongSelf.containerNode.targetNodeForActivationProgressContentRect = strongSelf.contextSourceNode.contentRect @@ -1090,7 +1109,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { if let updatedReplyBackgroundNode = updatedReplyBackgroundNode { if strongSelf.replyBackgroundNode == nil { strongSelf.replyBackgroundNode = updatedReplyBackgroundNode - strongSelf.addSubnode(updatedReplyBackgroundNode) + strongSelf.contextSourceNode.contentNode.addSubnode(updatedReplyBackgroundNode) updatedReplyBackgroundNode.image = replyBackgroundImage } else { strongSelf.replyBackgroundNode?.image = replyBackgroundImage @@ -1118,7 +1137,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { let replyInfoNode = replyInfoApply() if strongSelf.replyInfoNode == nil { strongSelf.replyInfoNode = replyInfoNode - strongSelf.addSubnode(replyInfoNode) + strongSelf.contextSourceNode.contentNode.addSubnode(replyInfoNode) } var viaBotSize = CGSize() if let viaBotNode = strongSelf.viaBotNode { @@ -1669,6 +1688,10 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { } } } + + override func cancelInsertionAnimations() { + self.layer.removeAllAnimations() + } override func animateInsertion(_ currentTimestamp: Double, duration: Double, short: Bool) { super.animateInsertion(currentTimestamp, duration: duration, short: short) @@ -1707,6 +1730,168 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { override func addAccessoryItemNode(_ accessoryItemNode: ListViewAccessoryItemNode) { self.contextSourceNode.contentNode.addSubnode(accessoryItemNode) } + + func animateContentFromTextInputField(textInput: ChatMessageTransitionNode.Source.TextInput, transition: ContainedViewLayoutTransition) { + guard let _ = self.item else { + return + } + + let localSourceContentFrame = self.contextSourceNode.contentNode.view.convert(textInput.contentView.frame.offsetBy(dx: self.contextSourceNode.contentRect.minX, dy: self.contextSourceNode.contentRect.minY), to: self.contextSourceNode.contentNode.view) + textInput.contentView.frame = localSourceContentFrame + + self.contextSourceNode.contentNode.view.addSubview(textInput.contentView) + + let sourceCenter = CGPoint( + x: localSourceContentFrame.minX + 11.2, + y: localSourceContentFrame.midY - 1.8 + ) + let localSourceCenter = CGPoint( + x: sourceCenter.x - localSourceContentFrame.minX, + y: sourceCenter.y - localSourceContentFrame.minY + ) + let localSourceOffset = CGPoint( + x: localSourceCenter.x - localSourceContentFrame.width / 2.0, + y: localSourceCenter.y - localSourceContentFrame.height / 2.0 + ) + + let sourceScale: CGFloat = 28.0 / self.imageNode.frame.height + + let offset = CGPoint( + x: sourceCenter.x - self.imageNode.frame.midX, + y: sourceCenter.y - self.imageNode.frame.midY + ) + + transition.animatePositionAdditive(node: self.imageNode, offset: offset) + transition.animateTransformScale(node: self.imageNode, from: sourceScale) + if let animationNode = self.animationNode { + transition.animatePositionAdditive(node: animationNode, offset: offset) + transition.animateTransformScale(node: animationNode, from: sourceScale) + } + transition.animatePositionAdditive(node: self.placeholderNode, offset: offset) + transition.animateTransformScale(node: self.placeholderNode, from: sourceScale) + + let inverseScale = 1.0 / sourceScale + + transition.animatePositionAdditive(layer: textInput.contentView.layer, offset: CGPoint(), to: CGPoint( + x: -offset.x - localSourceOffset.x * (inverseScale - 1.0), + y: -offset.y - localSourceOffset.y * (inverseScale - 1.0) + ), removeOnCompletion: false) + transition.updateTransformScale(layer: textInput.contentView.layer, scale: 1.0 / sourceScale) + + textInput.contentView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false, completion: { _ in + textInput.contentView.removeFromSuperview() + }) + + self.imageNode.layer.animateAlpha(from: 0.0, to: self.imageNode.alpha, duration: 0.1) + if let animationNode = self.animationNode { + animationNode.layer.animateAlpha(from: 0.0, to: animationNode.alpha, duration: 0.1) + } + self.placeholderNode.layer.animateAlpha(from: 0.0, to: self.placeholderNode.alpha, duration: 0.1) + + self.dateAndStatusNode.layer.animateAlpha(from: 0.0, to: self.dateAndStatusNode.alpha, duration: 0.15, delay: 0.16) + } + + func animateContentFromStickerGridItem(stickerSource: ChatMessageTransitionNode.Sticker, transition: ContainedViewLayoutTransition) { + guard let _ = self.item else { + return + } + + let localSourceContentFrame = CGRect( + origin: CGPoint( + x: self.imageNode.frame.minX + self.imageNode.frame.size.width / 2.0 - stickerSource.imageNode.frame.size.width / 2.0, + y: self.imageNode.frame.minY + self.imageNode.frame.size.height / 2.0 - stickerSource.imageNode.frame.size.height / 2.0 + ), + size: stickerSource.imageNode.frame.size + ) + + var snapshotView: UIView? + if let animationNode = stickerSource.animationNode { + snapshotView = animationNode.view.snapshotContentTree() + } else { + snapshotView = stickerSource.imageNode.view.snapshotContentTree() + } + snapshotView?.frame = localSourceContentFrame + + if let snapshotView = snapshotView { + self.contextSourceNode.contentNode.view.addSubview(snapshotView) + } + + let sourceCenter = CGPoint( + x: localSourceContentFrame.midX, + y: localSourceContentFrame.midY + ) + let localSourceCenter = CGPoint( + x: sourceCenter.x - localSourceContentFrame.minX, + y: sourceCenter.y - localSourceContentFrame.minY + ) + let localSourceOffset = CGPoint( + x: localSourceCenter.x - localSourceContentFrame.width / 2.0, + y: localSourceCenter.y - localSourceContentFrame.height / 2.0 + ) + + let sourceScale: CGFloat = stickerSource.imageNode.frame.height / self.imageNode.frame.height + + let offset = CGPoint( + x: sourceCenter.x - self.imageNode.frame.midX, + y: sourceCenter.y - self.imageNode.frame.midY + ) + + transition.animatePositionAdditive(node: self.imageNode, offset: offset) + transition.animateTransformScale(node: self.imageNode, from: sourceScale) + if let animationNode = self.animationNode { + transition.animatePositionAdditive(node: animationNode, offset: offset) + transition.animateTransformScale(node: animationNode, from: sourceScale) + } + transition.animatePositionAdditive(node: self.placeholderNode, offset: offset) + transition.animateTransformScale(node: self.placeholderNode, from: sourceScale) + + let inverseScale = 1.0 / sourceScale + + if let snapshotView = snapshotView { + transition.animatePositionAdditive(layer: snapshotView.layer, offset: CGPoint(), to: CGPoint( + x: -offset.x - localSourceOffset.x * (inverseScale - 1.0), + y: -offset.y - localSourceOffset.y * (inverseScale - 1.0) + ), removeOnCompletion: false) + transition.updateTransformScale(layer: snapshotView.layer, scale: 1.0 / sourceScale) + + snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.08, removeOnCompletion: false, completion: { [weak snapshotView] _ in + snapshotView?.removeFromSuperview() + }) + + self.imageNode.layer.animateAlpha(from: 0.0, to: self.imageNode.alpha, duration: 0.05) + if let animationNode = self.animationNode { + animationNode.layer.animateAlpha(from: 0.0, to: animationNode.alpha, duration: 0.05) + } + self.placeholderNode.layer.animateAlpha(from: 0.0, to: self.placeholderNode.alpha, duration: 0.05) + } + + self.dateAndStatusNode.layer.animateAlpha(from: 0.0, to: self.dateAndStatusNode.alpha, duration: 0.15, delay: 0.16) + + if let animationNode = stickerSource.animationNode { + animationNode.layer.animateScale(from: 0.1, to: 1.0, duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring) + animationNode.layer.animateAlpha(from: 0.0, to: animationNode.alpha, duration: 0.4) + } + + stickerSource.imageNode.layer.animateScale(from: 0.1, to: 1.0, duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring) + stickerSource.imageNode.layer.animateAlpha(from: 0.0, to: stickerSource.imageNode.alpha, duration: 0.4) + + if let placeholderNode = stickerSource.placeholderNode { + placeholderNode.layer.animateScale(from: 0.1, to: 1.0, duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring) + placeholderNode.layer.animateAlpha(from: 0.0, to: placeholderNode.alpha, duration: 0.4) + } + } + + func animateReplyPanel(sourceReplyPanel: ChatMessageTransitionNode.ReplyPanel, transition: ContainedViewLayoutTransition) { + if let replyInfoNode = self.replyInfoNode { + let localRect = self.contextSourceNode.contentNode.view.convert(sourceReplyPanel.relativeSourceRect, to: replyInfoNode.view) + + let offset = replyInfoNode.animateFromInputPanel(sourceReplyPanel: sourceReplyPanel, localRect: localRect, transition: transition) + if let replyBackgroundNode = self.replyBackgroundNode { + transition.animatePositionAdditive(node: replyBackgroundNode, offset: offset) + replyBackgroundNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1) + } + } + } } struct AnimatedEmojiSoundsConfiguration { diff --git a/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift b/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift index 7d418890bc..a91b68606a 100644 --- a/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift @@ -367,7 +367,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode private let backgroundWallpaperNode: ChatMessageBubbleBackdrop private let backgroundNode: ChatMessageBackground private let shadowNode: ChatMessageShadowNode - private var transitionClippingNode: ASDisplayNode? + private var clippingNode: ASDisplayNode override var extractedBackgroundNode: ASDisplayNode? { return self.shadowNode @@ -430,6 +430,10 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode self.backgroundNode = ChatMessageBackground() self.shadowNode = ChatMessageShadowNode() + + self.clippingNode = ASDisplayNode() + self.clippingNode.clipsToBounds = true + self.messageAccessibilityArea = AccessibilityAreaNode() super.init(layerBacked: false) @@ -482,6 +486,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode self.mainContextSourceNode.contentNode.addSubnode(self.backgroundWallpaperNode) self.mainContextSourceNode.contentNode.addSubnode(self.backgroundNode) self.mainContextSourceNode.contentNode.addSubnode(self.contentContainersWrapperNode) + self.mainContextSourceNode.contentNode.addSubnode(self.clippingNode) self.addSubnode(self.messageAccessibilityArea) self.messageAccessibilityArea.activate = { [weak self] in @@ -656,6 +661,9 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode let widthDifference = self.backgroundNode.frame.width - textInput.backgroundView.frame.width textInput.backgroundView.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: textInput.backgroundView.bounds.size) + + transition.animateFrame(node: self.clippingNode, from: CGRect(origin: self.clippingNode.frame.origin, size: textInput.backgroundView.frame.size)) + self.backgroundWallpaperNode.animateFrom(sourceView: textInput.backgroundView, mediaBox: item.context.account.postbox.mediaBox, transition: transition) self.backgroundNode.animateFrom(sourceView: textInput.backgroundView, transition: transition) @@ -674,6 +682,28 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode replyInfoNode.animateFromInputPanel(sourceReplyPanel: sourceReplyPanel, localRect: localRect, transition: transition) } } + + func animateFromMicInput(micInputNode: UIView, transition: ContainedViewLayoutTransition) -> ContextExtractedContentContainingNode? { + for contentNode in self.contentNodes { + if let contentNode = contentNode as? ChatMessageFileBubbleContentNode { + if let statusContainerNode = contentNode.interactiveFileNode.statusContainerNode { + let scale = statusContainerNode.contentRect.height / 100.0 + micInputNode.transform = CGAffineTransform(scaleX: scale, y: scale) + micInputNode.center = CGPoint(x: statusContainerNode.contentRect.midX, y: statusContainerNode.contentRect.midY) + statusContainerNode.contentNode.view.addSubview(micInputNode) + + transition.updateAlpha(layer: micInputNode.layer, alpha: 0.0, completion: { [weak micInputNode] _ in + micInputNode?.removeFromSuperview() + }) + + transition.animateTransformScale(node: statusContainerNode.contentNode, from: 1.0 / scale) + + return statusContainerNode + } + } + } + return nil + } override func didLoad() { super.didLoad() @@ -2301,7 +2331,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode if !nameNode.isNodeLoaded { nameNode.isUserInteractionEnabled = false } - strongSelf.mainContextSourceNode.contentNode.addSubnode(nameNode) + strongSelf.clippingNode.addSubnode(nameNode) } nameNode.frame = CGRect(origin: CGPoint(x: contentOrigin.x + layoutConstants.text.bubbleInsets.left, y: layoutConstants.bubble.contentInsets.top + nameNodeOriginY), size: nameNodeSizeApply.0) nameNode.displaysAsynchronously = !item.presentationData.isPreview @@ -2313,7 +2343,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode } else { credibilityIconNode = ASImageNode() strongSelf.credibilityIconNode = credibilityIconNode - strongSelf.mainContextSourceNode.contentNode.addSubnode(credibilityIconNode) + strongSelf.clippingNode.addSubnode(credibilityIconNode) } credibilityIconNode.frame = CGRect(origin: CGPoint(x: nameNode.frame.maxX + 4.0, y: nameNode.frame.minY), size: credibilityIconImage.size) credibilityIconNode.image = credibilityIconImage @@ -2329,7 +2359,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode if !adminBadgeNode.isNodeLoaded { adminBadgeNode.isUserInteractionEnabled = false } - strongSelf.mainContextSourceNode.contentNode.addSubnode(adminBadgeNode) + strongSelf.clippingNode.addSubnode(adminBadgeNode) adminBadgeNode.frame = adminBadgeFrame } else { let previousAdminBadgeFrame = adminBadgeNode.frame @@ -2351,7 +2381,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode strongSelf.forwardInfoNode = forwardInfoNode var animateFrame = true if forwardInfoNode.supernode == nil { - strongSelf.mainContextSourceNode.contentNode.addSubnode(forwardInfoNode) + strongSelf.clippingNode.addSubnode(forwardInfoNode) animateFrame = false forwardInfoNode.openPsa = { [weak strongSelf] type, sourceNode in guard let strongSelf = strongSelf, let item = strongSelf.item else { @@ -2578,12 +2608,15 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode updatedContentNodes.append(contentNode) let contextSourceNode: ContextExtractedContentContainingNode + let containerSupernode: ASDisplayNode if isAttachent { contextSourceNode = strongSelf.mainContextSourceNode + containerSupernode = strongSelf.clippingNode } else { contextSourceNode = strongSelf.contentContainers.first(where: { $0.contentMessageStableId == contentNodeMessage.stableId })?.sourceNode ?? strongSelf.mainContextSourceNode + containerSupernode = strongSelf.contentContainers.first(where: { $0.contentMessageStableId == contentNodeMessage.stableId })?.sourceNode ?? strongSelf.clippingNode } - contextSourceNode.contentNode.addSubnode(contentNode) + containerSupernode.addSubnode(contentNode) contentNode.visibility = strongSelf.visibility contentNode.updateIsTextSelectionActive = { [weak contextSourceNode] value in @@ -2659,7 +2692,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode if mosaicStatusNode !== strongSelf.mosaicStatusNode { strongSelf.mosaicStatusNode?.removeFromSupernode() strongSelf.mosaicStatusNode = mosaicStatusNode - strongSelf.mainContextSourceNode.contentNode.addSubnode(mosaicStatusNode) + strongSelf.clippingNode.addSubnode(mosaicStatusNode) } let absoluteOrigin = mosaicStatusOrigin.offsetBy(dx: contentOrigin.x, dy: contentOrigin.y) mosaicStatusNode.frame = CGRect(origin: CGPoint(x: absoluteOrigin.x - layoutConstants.image.statusInsets.right - size.width, y: absoluteOrigin.y - layoutConstants.image.statusInsets.bottom - size.height), size: size) @@ -2688,7 +2721,6 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode if case .System = animation, !strongSelf.mainContextSourceNode.isExtractedToContextPreview { if !strongSelf.backgroundNode.frame.equalTo(backgroundFrame) { strongSelf.backgroundFrameTransition = (strongSelf.backgroundNode.frame, backgroundFrame) - strongSelf.enableTransitionClippingNode() } if let shareButtonNode = strongSelf.shareButtonNode { let currentBackgroundFrame = strongSelf.backgroundNode.frame @@ -2703,15 +2735,20 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode if let shareButtonNode = strongSelf.shareButtonNode { shareButtonNode.frame = CGRect(origin: CGPoint(x: backgroundFrame.maxX + 8.0, y: backgroundFrame.maxY - 30.0), size: CGSize(width: 29.0, height: 29.0)) } - strongSelf.disableTransitionClippingNode() if case .System = animation, strongSelf.mainContextSourceNode.isExtractedToContextPreview { transition.updateFrame(node: strongSelf.backgroundNode, frame: backgroundFrame) + + transition.updateFrame(node: strongSelf.clippingNode, frame: backgroundFrame) + transition.updateBounds(node: strongSelf.clippingNode, bounds: CGRect(origin: CGPoint(x: backgroundFrame.minX, y: backgroundFrame.minY), size: backgroundFrame.size)) + strongSelf.backgroundNode.updateLayout(size: backgroundFrame.size, transition: transition) strongSelf.backgroundWallpaperNode.updateFrame(backgroundFrame, transition: transition) strongSelf.shadowNode.updateLayout(backgroundFrame: backgroundFrame, transition: transition) } else { strongSelf.backgroundNode.frame = backgroundFrame + strongSelf.clippingNode.frame = backgroundFrame + strongSelf.clippingNode.bounds = CGRect(origin: CGPoint(x: backgroundFrame.minX, y: backgroundFrame.minY), size: backgroundFrame.size) strongSelf.backgroundNode.updateLayout(size: backgroundFrame.size, transition: .immediate) strongSelf.backgroundWallpaperNode.frame = backgroundFrame strongSelf.shadowNode.updateLayout(backgroundFrame: backgroundFrame, transition: .immediate) @@ -2769,7 +2806,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode strongSelf.updateSearchTextHighlightState() - if let (awaitingAppliedReaction, f) = strongSelf.awaitingAppliedReaction { + /*if let (awaitingAppliedReaction, f) = strongSelf.awaitingAppliedReaction { var bounds = strongSelf.bounds let offset = bounds.origin.x bounds.origin.x = 0.0 @@ -2806,7 +2843,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode } strongSelf.reactionRecognizer?.complete(into: targetNode, hideTarget: hideTarget)*/ f() - } + }*/ } override func updateAccessibilityData(_ accessibilityData: ChatMessageAccessibilityData) { @@ -2849,52 +2886,6 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode } } - private func enableTransitionClippingNode() { - if self.transitionClippingNode == nil { - let node = ASDisplayNode() - node.clipsToBounds = true - var backgroundFrame = self.backgroundNode.frame - backgroundFrame = backgroundFrame.insetBy(dx: 0.0, dy: 1.0) - node.frame = backgroundFrame - node.bounds = CGRect(origin: CGPoint(x: backgroundFrame.origin.x, y: backgroundFrame.origin.y), size: backgroundFrame.size) - if let forwardInfoNode = self.forwardInfoNode { - node.addSubnode(forwardInfoNode) - } - if let replyInfoNode = self.replyInfoNode { - node.addSubnode(replyInfoNode) - } - if !self.contentContainers.isEmpty { - node.addSubnode(self.contentContainersWrapperNode) - } else { - for contentNode in self.contentNodes { - node.addSubnode(contentNode) - } - } - self.mainContextSourceNode.contentNode.addSubnode(node) - self.transitionClippingNode = node - } - } - - private func disableTransitionClippingNode() { - if let transitionClippingNode = self.transitionClippingNode { - if let forwardInfoNode = self.forwardInfoNode { - self.mainContextSourceNode.contentNode.addSubnode(forwardInfoNode) - } - if let replyInfoNode = self.replyInfoNode { - self.mainContextSourceNode.contentNode.addSubnode(replyInfoNode) - } - if !self.contentContainers.isEmpty { - self.mainContextSourceNode.contentNode.addSubnode(self.contentContainersWrapperNode) - } else { - for contentNode in self.contentNodes { - self.mainContextSourceNode.contentNode.addSubnode(contentNode) - } - } - transitionClippingNode.removeFromSupernode() - self.transitionClippingNode = nil - } - } - override func shouldAnimateHorizontalFrameTransition() -> Bool { if let _ = self.backgroundFrameTransition { return true @@ -2909,6 +2900,10 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode if let backgroundFrameTransition = self.backgroundFrameTransition { let backgroundFrame = CGRect.interpolator()(backgroundFrameTransition.0, backgroundFrameTransition.1, progress) as! CGRect self.backgroundNode.frame = backgroundFrame + + self.clippingNode.frame = backgroundFrame + self.clippingNode.bounds = CGRect(origin: CGPoint(x: backgroundFrame.minX, y: backgroundFrame.minY), size: backgroundFrame.size) + self.backgroundNode.updateLayout(size: backgroundFrame.size, transition: .immediate) self.backgroundWallpaperNode.frame = backgroundFrame self.shadowNode.updateLayout(backgroundFrame: backgroundFrame, transition: .immediate) @@ -2935,18 +2930,6 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode shareButtonNode.frame = CGRect(origin: CGPoint(x: backgroundFrame.maxX + 8.0, y: backgroundFrame.maxY - 30.0), size: CGSize(width: 29.0, height: 29.0)) } - if let transitionClippingNode = self.transitionClippingNode { - var fixedBackgroundFrame = backgroundFrame - fixedBackgroundFrame = fixedBackgroundFrame.insetBy(dx: 0.0, dy: self.backgroundNode.type == ChatMessageBackgroundType.none ? 0.0 : 1.0) - - transitionClippingNode.frame = fixedBackgroundFrame - transitionClippingNode.bounds = CGRect(origin: CGPoint(x: fixedBackgroundFrame.origin.x, y: fixedBackgroundFrame.origin.y), size: fixedBackgroundFrame.size) - - if progress >= 1.0 - CGFloat.ulpOfOne { - self.disableTransitionClippingNode() - } - } - if CGFloat(1.0).isLessThanOrEqualTo(progress) { self.backgroundFrameTransition = nil } diff --git a/submodules/TelegramUI/Sources/ChatMessageFileBubbleContentNode.swift b/submodules/TelegramUI/Sources/ChatMessageFileBubbleContentNode.swift index 749a02796f..4e374f4aa3 100644 --- a/submodules/TelegramUI/Sources/ChatMessageFileBubbleContentNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageFileBubbleContentNode.swift @@ -9,7 +9,7 @@ import SyncCore import TelegramUIPreferences class ChatMessageFileBubbleContentNode: ChatMessageBubbleContentNode { - private let interactiveFileNode: ChatMessageInteractiveFileNode + let interactiveFileNode: ChatMessageInteractiveFileNode override var visibility: ListViewItemNodeVisibility { didSet { diff --git a/submodules/TelegramUI/Sources/ChatMessageInteractiveFileNode.swift b/submodules/TelegramUI/Sources/ChatMessageInteractiveFileNode.swift index 718f66a8b3..072496df97 100644 --- a/submodules/TelegramUI/Sources/ChatMessageInteractiveFileNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageInteractiveFileNode.swift @@ -17,6 +17,7 @@ import FileMediaResourceStatus import CheckNode import MusicAlbumArtResources import AudioBlob +import ContextUI private struct FetchControls { let fetch: () -> Void @@ -38,6 +39,7 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode { private let consumableContentNode: ASImageNode private var iconNode: TransformImageNode? + private(set) var statusContainerNode: ContextExtractedContentContainingNode? private var statusNode: SemanticStatusNode? private var playbackAudioLevelView: VoiceBlobView? private var streamingStatusNode: SemanticStatusNode? @@ -129,6 +131,8 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode { self.dateAndStatusNode = ChatMessageDateAndStatusNode() self.consumableContentNode = ASImageNode() + + self.statusContainerNode = ContextExtractedContentContainingNode() super.init() @@ -136,6 +140,9 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode { self.addSubnode(self.descriptionNode) self.addSubnode(self.fetchingTextNode) self.addSubnode(self.fetchingCompactTextNode) + if let statusContainerNode = self.statusContainerNode { + self.addSubnode(statusContainerNode) + } } deinit { @@ -683,11 +690,20 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode { strongSelf.waveformNode.displaysAsynchronously = !presentationData.isPreview strongSelf.statusNode?.displaysAsynchronously = !presentationData.isPreview - strongSelf.statusNode?.frame = progressFrame + strongSelf.statusNode?.frame = CGRect(origin: CGPoint(), size: progressFrame.size) + + strongSelf.statusContainerNode?.frame = progressFrame + strongSelf.statusContainerNode?.contentRect = CGRect(origin: CGPoint(), size: progressFrame.size) + strongSelf.statusContainerNode?.contentNode.frame = CGRect(origin: CGPoint(), size: progressFrame.size) + strongSelf.playbackAudioLevelView?.frame = progressFrame.insetBy(dx: -12.0, dy: -12.0) strongSelf.progressFrame = progressFrame strongSelf.streamingCacheStatusFrame = streamingCacheStatusFrame strongSelf.fileIconImage = fileIconImage + + strongSelf.statusContainerNode?.frame = progressFrame + strongSelf.statusContainerNode?.contentRect = CGRect(origin: CGPoint(), size: progressFrame.size) + strongSelf.statusContainerNode?.contentNode.frame = CGRect(origin: CGPoint(), size: progressFrame.size) if let updatedFetchControls = updatedFetchControls { let _ = strongSelf.fetchControls.swap(updatedFetchControls) @@ -937,8 +953,12 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode { } let statusNode = SemanticStatusNode(backgroundNodeColor: backgroundNodeColor, foregroundNodeColor: foregroundNodeColor, image: image, overlayForegroundNodeColor: presentationData.theme.theme.chat.message.mediaOverlayControlColors.foregroundColor) self.statusNode = statusNode - statusNode.frame = progressFrame - self.addSubnode(statusNode) + + self.statusContainerNode?.contentNode.insertSubnode(statusNode, at: 0) + self.statusContainerNode?.frame = progressFrame + self.statusContainerNode?.contentRect = CGRect(origin: CGPoint(), size: progressFrame.size) + self.statusContainerNode?.contentNode.frame = CGRect(origin: CGPoint(), size: progressFrame.size) + statusNode.frame = CGRect(origin: CGPoint(), size: progressFrame.size) } else if let statusNode = self.statusNode { statusNode.backgroundNodeColor = backgroundNodeColor } diff --git a/submodules/TelegramUI/Sources/ChatMessageItem.swift b/submodules/TelegramUI/Sources/ChatMessageItem.swift index 3392533409..606da3ec2a 100644 --- a/submodules/TelegramUI/Sources/ChatMessageItem.swift +++ b/submodules/TelegramUI/Sources/ChatMessageItem.swift @@ -434,7 +434,7 @@ public final class ChatMessageItem: ListViewItem, CustomStringConvertible { let configure = { let node = (viewClassName as! ChatMessageItemView.Type).init() - node.setupItem(self) + node.setupItem(self, synchronousLoad: synchronousLoads) let nodeLayout = node.asyncLayout() let (top, bottom, dateAtBottom) = self.mergedWithItems(top: previousItem, bottom: nextItem) @@ -500,7 +500,7 @@ public final class ChatMessageItem: ListViewItem, CustomStringConvertible { public func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping (ListViewItemApply) -> Void) -> Void) { Queue.mainQueue().async { if let nodeValue = node() as? ChatMessageItemView { - nodeValue.setupItem(self) + nodeValue.setupItem(self, synchronousLoad: false) let nodeLayout = nodeValue.asyncLayout() diff --git a/submodules/TelegramUI/Sources/ChatMessageItemView.swift b/submodules/TelegramUI/Sources/ChatMessageItemView.swift index a35dd9040a..a640ad2c7d 100644 --- a/submodules/TelegramUI/Sources/ChatMessageItemView.swift +++ b/submodules/TelegramUI/Sources/ChatMessageItemView.swift @@ -705,7 +705,7 @@ public class ChatMessageItemView: ListViewItemNode { self.frame = CGRect() } - func setupItem(_ item: ChatMessageItem) { + func setupItem(_ item: ChatMessageItem, synchronousLoad: Bool) { self.item = item } diff --git a/submodules/TelegramUI/Sources/ChatMessageReplyInfoNode.swift b/submodules/TelegramUI/Sources/ChatMessageReplyInfoNode.swift index deaeef52ce..bdea98855b 100644 --- a/submodules/TelegramUI/Sources/ChatMessageReplyInfoNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageReplyInfoNode.swift @@ -230,7 +230,7 @@ class ChatMessageReplyInfoNode: ASDisplayNode { } } - func animateFromInputPanel(sourceReplyPanel: ChatMessageTransitionNode.ReplyPanel, localRect: CGRect, transition: ContainedViewLayoutTransition) { + func animateFromInputPanel(sourceReplyPanel: ChatMessageTransitionNode.ReplyPanel, localRect: CGRect, transition: ContainedViewLayoutTransition) -> CGPoint { if let titleNode = self.titleNode { let offset = CGPoint( x: localRect.minX + sourceReplyPanel.titleNode.frame.minX - titleNode.frame.minX, @@ -307,6 +307,8 @@ class ChatMessageReplyInfoNode: ASDisplayNode { sourceReplyPanel.lineNode.frame = sourceReplyPanel.lineNode.frame.offsetBy(dx: localRect.minX - offset.x, dy: localRect.minY - offset.y) transition.animatePositionAdditive(node: sourceReplyPanel.lineNode, offset: CGPoint(x: offset.x, y: offset.y), removeOnCompletion: false) + + return offset } } } diff --git a/submodules/TelegramUI/Sources/ChatMessageStickerItemNode.swift b/submodules/TelegramUI/Sources/ChatMessageStickerItemNode.swift index 3a5ee9c215..3785c23349 100644 --- a/submodules/TelegramUI/Sources/ChatMessageStickerItemNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageStickerItemNode.swift @@ -19,7 +19,7 @@ private let inlineBotPrefixFont = Font.regular(14.0) private let inlineBotNameFont = nameFont class ChatMessageStickerItemNode: ChatMessageItemView { - private let contextSourceNode: ContextExtractedContentContainingNode + let contextSourceNode: ContextExtractedContentContainingNode private let containerNode: ContextControllerSourceNode let imageNode: TransformImageNode private var placeholderNode: StickerShimmerEffectNode @@ -49,6 +49,8 @@ class ChatMessageStickerItemNode: ChatMessageItemView { private var currentSwipeToReplyTranslation: CGFloat = 0.0 private var currentSwipeAction: ChatControllerInteractionSwipeAction? + + private var enableSynchronousImageApply: Bool = false required init() { self.contextSourceNode = ContextExtractedContentContainingNode() @@ -68,9 +70,13 @@ class ChatMessageStickerItemNode: ChatMessageItemView { } if image != nil { if firstTime && !strongSelf.placeholderNode.isEmpty { - strongSelf.imageNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2, completion: { [weak self] _ in - self?.removePlaceholder(animated: false) - }) + if strongSelf.enableSynchronousImageApply { + strongSelf.removePlaceholder(animated: false) + } else { + strongSelf.imageNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2, completion: { [weak self] _ in + self?.removePlaceholder(animated: false) + }) + } } else { strongSelf.removePlaceholder(animated: true) } @@ -214,15 +220,15 @@ class ChatMessageStickerItemNode: ChatMessageItemView { self.view.addGestureRecognizer(replyRecognizer) } - override func setupItem(_ item: ChatMessageItem) { - super.setupItem(item) + override func setupItem(_ item: ChatMessageItem, synchronousLoad: Bool) { + super.setupItem(item, synchronousLoad: synchronousLoad) for media in item.message.media { if let telegramFile = media as? TelegramMediaFile { if self.telegramFile != telegramFile { - let signal = chatMessageSticker(account: item.context.account, file: telegramFile, small: false, onlyFullSize: self.telegramFile != nil) + let signal = chatMessageSticker(account: item.context.account, file: telegramFile, small: false, onlyFullSize: self.telegramFile != nil, synchronousLoad: synchronousLoad) self.telegramFile = telegramFile - self.imageNode.setSignal(signal) + self.imageNode.setSignal(signal, attemptSynchronously: synchronousLoad) self.fetchDisposable.set(freeMediaFileInteractiveFetched(account: item.context.account, fileReference: .message(message: MessageReference(item.message), media: telegramFile)).start()) } @@ -642,7 +648,9 @@ class ChatMessageStickerItemNode: ChatMessageItemView { strongSelf.updateAccessibilityData(accessibilityData) transition.updateFrame(node: strongSelf.imageNode, frame: updatedImageFrame) + strongSelf.enableSynchronousImageApply = true imageApply() + strongSelf.enableSynchronousImageApply = false if let immediateThumbnailData = telegramFile?.immediateThumbnailData { let foregroundColor = bubbleVariableColor(variableColor: item.presentationData.theme.theme.chat.message.stickerPlaceholderColor, wallpaper: item.presentationData.theme.wallpaper) @@ -684,7 +692,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView { if let updatedReplyBackgroundNode = updatedReplyBackgroundNode { if strongSelf.replyBackgroundNode == nil { strongSelf.replyBackgroundNode = updatedReplyBackgroundNode - strongSelf.addSubnode(updatedReplyBackgroundNode) + strongSelf.contextSourceNode.contentNode.addSubnode(updatedReplyBackgroundNode) updatedReplyBackgroundNode.image = replyBackgroundImage } else { strongSelf.replyBackgroundNode?.image = replyBackgroundImage @@ -711,7 +719,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView { let replyInfoNode = replyInfoApply() if strongSelf.replyInfoNode == nil { strongSelf.replyInfoNode = replyInfoNode - strongSelf.addSubnode(replyInfoNode) + strongSelf.contextSourceNode.contentNode.addSubnode(replyInfoNode) } replyInfoNode.frame = replyInfoFrame strongSelf.replyBackgroundNode?.frame = replyBackgroundFrame ?? CGRect() @@ -1160,6 +1168,10 @@ class ChatMessageStickerItemNode: ChatMessageItemView { } } } + + override func cancelInsertionAnimations() { + self.layer.removeAllAnimations() + } override func animateInsertion(_ currentTimestamp: Double, duration: Double, short: Bool) { super.animateInsertion(currentTimestamp, duration: duration, short: short) @@ -1186,4 +1198,152 @@ class ChatMessageStickerItemNode: ChatMessageItemView { override func addAccessoryItemNode(_ accessoryItemNode: ListViewAccessoryItemNode) { self.contextSourceNode.contentNode.addSubnode(accessoryItemNode) } + + func animateContentFromTextInputField(textInput: ChatMessageTransitionNode.Source.TextInput, transition: ContainedViewLayoutTransition) { + guard let _ = self.item else { + return + } + + let localSourceContentFrame = self.contextSourceNode.contentNode.view.convert(textInput.contentView.frame.offsetBy(dx: self.contextSourceNode.contentRect.minX, dy: self.contextSourceNode.contentRect.minY), to: self.contextSourceNode.contentNode.view) + textInput.contentView.frame = localSourceContentFrame + + self.contextSourceNode.contentNode.view.addSubview(textInput.contentView) + + let sourceCenter = CGPoint( + x: localSourceContentFrame.minX + 11.2, + y: localSourceContentFrame.midY - 1.8 + ) + let localSourceCenter = CGPoint( + x: sourceCenter.x - localSourceContentFrame.minX, + y: sourceCenter.y - localSourceContentFrame.minY + ) + let localSourceOffset = CGPoint( + x: localSourceCenter.x - localSourceContentFrame.width / 2.0, + y: localSourceCenter.y - localSourceContentFrame.height / 2.0 + ) + + let sourceScale: CGFloat = 28.0 / self.imageNode.frame.height + + let offset = CGPoint( + x: sourceCenter.x - self.imageNode.frame.midX, + y: sourceCenter.y - self.imageNode.frame.midY + ) + + transition.animatePositionAdditive(node: self.imageNode, offset: offset) + transition.animateTransformScale(node: self.imageNode, from: sourceScale) + transition.animatePositionAdditive(node: self.placeholderNode, offset: offset) + transition.animateTransformScale(node: self.placeholderNode, from: sourceScale) + + let inverseScale = 1.0 / sourceScale + + transition.animatePositionAdditive(layer: textInput.contentView.layer, offset: CGPoint(), to: CGPoint( + x: -offset.x - localSourceOffset.x * (inverseScale - 1.0), + y: -offset.y - localSourceOffset.y * (inverseScale - 1.0) + ), removeOnCompletion: false) + transition.updateTransformScale(layer: textInput.contentView.layer, scale: 1.0 / sourceScale) + + textInput.contentView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false, completion: { _ in + textInput.contentView.removeFromSuperview() + }) + + self.imageNode.layer.animateAlpha(from: 0.0, to: self.imageNode.alpha, duration: 0.1) + self.placeholderNode.layer.animateAlpha(from: 0.0, to: self.placeholderNode.alpha, duration: 0.1) + + self.dateAndStatusNode.layer.animateAlpha(from: 0.0, to: self.dateAndStatusNode.alpha, duration: 0.15, delay: 0.16) + } + + func animateContentFromStickerGridItem(stickerSource: ChatMessageTransitionNode.Sticker, transition: ContainedViewLayoutTransition) { + guard let _ = self.item else { + return + } + + let localSourceContentFrame = CGRect( + origin: CGPoint( + x: self.imageNode.frame.minX + self.imageNode.frame.size.width / 2.0 - stickerSource.imageNode.frame.size.width / 2.0, + y: self.imageNode.frame.minY + self.imageNode.frame.size.height / 2.0 - stickerSource.imageNode.frame.size.height / 2.0 + ), + size: stickerSource.imageNode.frame.size + ) + + var snapshotView: UIView? + if let animationNode = stickerSource.animationNode { + snapshotView = animationNode.view.snapshotContentTree() + } else { + snapshotView = stickerSource.imageNode.view.snapshotContentTree() + } + snapshotView?.frame = localSourceContentFrame + + if let snapshotView = snapshotView { + self.contextSourceNode.contentNode.view.addSubview(snapshotView) + } + + let sourceCenter = CGPoint( + x: localSourceContentFrame.midX, + y: localSourceContentFrame.midY + ) + let localSourceCenter = CGPoint( + x: sourceCenter.x - localSourceContentFrame.minX, + y: sourceCenter.y - localSourceContentFrame.minY + ) + let localSourceOffset = CGPoint( + x: localSourceCenter.x - localSourceContentFrame.width / 2.0, + y: localSourceCenter.y - localSourceContentFrame.height / 2.0 + ) + + let sourceScale: CGFloat = stickerSource.imageNode.frame.height / self.imageNode.frame.height + + let offset = CGPoint( + x: sourceCenter.x - self.imageNode.frame.midX, + y: sourceCenter.y - self.imageNode.frame.midY + ) + + transition.animatePositionAdditive(node: self.imageNode, offset: offset) + transition.animateTransformScale(node: self.imageNode, from: sourceScale) + transition.animatePositionAdditive(node: self.placeholderNode, offset: offset) + transition.animateTransformScale(node: self.placeholderNode, from: sourceScale) + + let inverseScale = 1.0 / sourceScale + + if let snapshotView = snapshotView { + transition.animatePositionAdditive(layer: snapshotView.layer, offset: CGPoint(), to: CGPoint( + x: -offset.x - localSourceOffset.x * (inverseScale - 1.0), + y: -offset.y - localSourceOffset.y * (inverseScale - 1.0) + ), removeOnCompletion: false) + transition.updateTransformScale(layer: snapshotView.layer, scale: 1.0 / sourceScale) + + snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.06, removeOnCompletion: false, completion: { [weak snapshotView] _ in + snapshotView?.removeFromSuperview() + }) + + self.imageNode.layer.animateAlpha(from: 0.0, to: self.imageNode.alpha, duration: 0.03) + self.placeholderNode.layer.animateAlpha(from: 0.0, to: self.placeholderNode.alpha, duration: 0.03) + } + + self.dateAndStatusNode.layer.animateAlpha(from: 0.0, to: self.dateAndStatusNode.alpha, duration: 0.15, delay: 0.16) + + if let animationNode = stickerSource.animationNode { + animationNode.layer.animateScale(from: 0.1, to: 1.0, duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring) + animationNode.layer.animateAlpha(from: 0.0, to: animationNode.alpha, duration: 0.4) + } + + stickerSource.imageNode.layer.animateScale(from: 0.1, to: 1.0, duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring) + stickerSource.imageNode.layer.animateAlpha(from: 0.0, to: stickerSource.imageNode.alpha, duration: 0.4) + + if let placeholderNode = stickerSource.placeholderNode { + placeholderNode.layer.animateScale(from: 0.1, to: 1.0, duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring) + placeholderNode.layer.animateAlpha(from: 0.0, to: placeholderNode.alpha, duration: 0.4) + } + } + + func animateReplyPanel(sourceReplyPanel: ChatMessageTransitionNode.ReplyPanel, transition: ContainedViewLayoutTransition) { + if let replyInfoNode = self.replyInfoNode { + let localRect = self.contextSourceNode.contentNode.view.convert(sourceReplyPanel.relativeSourceRect, to: replyInfoNode.view) + + let offset = replyInfoNode.animateFromInputPanel(sourceReplyPanel: sourceReplyPanel, localRect: localRect, transition: transition) + if let replyBackgroundNode = self.replyBackgroundNode { + transition.animatePositionAdditive(node: replyBackgroundNode, offset: offset) + replyBackgroundNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1) + } + } + } } diff --git a/submodules/TelegramUI/Sources/ChatMessageTransitionNode.swift b/submodules/TelegramUI/Sources/ChatMessageTransitionNode.swift index 5e448be1f6..8e02f75132 100644 --- a/submodules/TelegramUI/Sources/ChatMessageTransitionNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageTransitionNode.swift @@ -3,6 +3,83 @@ import UIKit import AsyncDisplayKit import Display import ContextUI +import AnimatedStickerNode +import SwiftSignalKit + +private final class OverlayTransitionContainerNode: ViewControllerTracingNode { + override init() { + super.init() + } + + deinit { + } + + override func didLoad() { + super.didLoad() + } + + func updateLayout(layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { + } + + override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + return nil + } +} + +private final class OverlayTransitionContainerController: ViewController, StandalonePresentableController { + private let _ready = Promise() + override public var ready: Promise { + return self._ready + } + + private var controllerNode: OverlayTransitionContainerNode { + return self.displayNode as! OverlayTransitionContainerNode + } + + private var wasDismissed: Bool = false + + init() { + super.init(navigationBarPresentationData: nil) + + self.statusBar.statusBarStyle = .Ignore + } + + required init(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + deinit { + } + + override public func loadDisplayNode() { + self.displayNode = OverlayTransitionContainerNode() + + self.displayNodeDidLoad() + + self._ready.set(.single(true)) + } + + override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { + super.containerLayoutUpdated(layout, transition: transition) + + self.controllerNode.updateLayout(layout: layout, transition: transition) + } + + override public func viewDidAppear(_ animated: Bool) { + if self.ignoreAppearanceMethodInvocations() { + return + } + super.viewDidAppear(animated) + } + + override public func dismiss(completion: (() -> Void)? = nil) { + if !self.wasDismissed { + self.wasDismissed = true + self.presentingViewController?.dismiss(animated: false, completion: nil) + completion?() + } + } +} final class ChatMessageTransitionNode: ASDisplayNode { final class ReplyPanel { @@ -21,6 +98,20 @@ final class ChatMessageTransitionNode: ASDisplayNode { } } + final class Sticker { + let imageNode: TransformImageNode + let animationNode: GenericAnimatedStickerNode? + let placeholderNode: ASDisplayNode? + let relativeSourceRect: CGRect + + init(imageNode: TransformImageNode, animationNode: GenericAnimatedStickerNode?, placeholderNode: ASDisplayNode?, relativeSourceRect: CGRect) { + self.imageNode = imageNode + self.animationNode = animationNode + self.placeholderNode = placeholderNode + self.relativeSourceRect = relativeSourceRect + } + } + enum Source { final class TextInput { let backgroundView: UIView @@ -34,7 +125,23 @@ final class ChatMessageTransitionNode: ASDisplayNode { } } + enum StickerInput { + case inputPanel(itemNode: ChatMediaInputStickerGridItemNode) + case mediaPanel(itemNode: HorizontalStickerGridItemNode) + case inputPanelSearch(itemNode: StickerPaneSearchStickerItemNode) + } + + final class AudioMicInput { + let micButton: ChatTextInputMediaRecordingButton + + init(micButton: ChatTextInputMediaRecordingButton) { + self.micButton = micButton + } + } + case textInput(textInput: TextInput, replyPanel: ReplyAccessoryPanelNode?) + case stickerMediaInput(input: StickerInput, replyPanel: ReplyAccessoryPanelNode?) + case audioMicInput(AudioMicInput) } private final class AnimatingItemNode: ASDisplayNode { @@ -45,6 +152,8 @@ final class ChatMessageTransitionNode: ASDisplayNode { private let scrollingContainer: ASDisplayNode private let containerNode: ASDisplayNode + weak var overlayController: OverlayTransitionContainerController? + var animationEnded: (() -> Void)? init(itemNode: ChatMessageItemView, contextSourceNode: ContextExtractedContentContainingNode, source: ChatMessageTransitionNode.Source) { @@ -86,29 +195,159 @@ final class ChatMessageTransitionNode: ASDisplayNode { self.itemNode.cancelInsertionAnimations() - let duration: Double = 0.5 + let verticalDuration: Double = 0.5 + let horizontalDuration: Double = verticalDuration * 0.7 let delay: Double = 0.0 - let transition: ContainedViewLayoutTransition = .animated(duration: duration * 0.5, curve: .custom(0.33, 0.0, 0.0, 1.0)) + let transition: ContainedViewLayoutTransition = .animated(duration: horizontalDuration, curve: .custom(0.33, 0.0, 0.0, 1.0)) if let itemNode = self.itemNode as? ChatMessageBubbleItemNode { itemNode.animateContentFromTextInputField(textInput: textInput, transition: transition) if let sourceReplyPanel = sourceReplyPanel { itemNode.animateReplyPanel(sourceReplyPanel: sourceReplyPanel, transition: transition) } + } else if let itemNode = self.itemNode as? ChatMessageAnimatedStickerItemNode { + itemNode.animateContentFromTextInputField(textInput: textInput, transition: transition) + if let sourceReplyPanel = sourceReplyPanel { + itemNode.animateReplyPanel(sourceReplyPanel: sourceReplyPanel, transition: transition) + } + } else if let itemNode = self.itemNode as? ChatMessageStickerItemNode { + itemNode.animateContentFromTextInputField(textInput: textInput, transition: transition) + if let sourceReplyPanel = sourceReplyPanel { + itemNode.animateReplyPanel(sourceReplyPanel: sourceReplyPanel, transition: transition) + } } - self.containerNode.frame = targetAbsoluteRect.offsetBy(dx: -self.contextSourceNode.contentRect.minX, dy: self.contextSourceNode.contentRect.minY) + self.containerNode.frame = targetAbsoluteRect.offsetBy(dx: -self.contextSourceNode.contentRect.minX, dy: -self.contextSourceNode.contentRect.minY) self.contextSourceNode.updateAbsoluteRect?(self.containerNode.frame, UIScreen.main.bounds.size) - self.containerNode.layer.animatePosition(from: CGPoint(x: 0.0, y: sourceAbsoluteRect.minY - targetAbsoluteRect.minY), to: CGPoint(), duration: duration, delay: delay, mediaTimingFunction: CAMediaTimingFunction(controlPoints: 0.33, 0.0, 0.0, 1.0), additive: true, force: true, completion: { [weak self] _ in + self.containerNode.layer.animatePosition(from: CGPoint(x: 0.0, y: sourceAbsoluteRect.minY - targetAbsoluteRect.minY), to: CGPoint(), duration: verticalDuration, delay: delay, mediaTimingFunction: CAMediaTimingFunction(controlPoints: 0.33, 0.0, 0.0, 1.0), additive: true, force: true, completion: { [weak self] _ in guard let strongSelf = self else { return } strongSelf.endAnimation() }) - self.contextSourceNode.applyAbsoluteOffset?(CGPoint(x: sourceAbsoluteRect.minX - targetAbsoluteRect.minX, y: 0.0), .custom(0.33, 0.0, 0.0, 1.0), duration * 0.5) - self.contextSourceNode.applyAbsoluteOffset?(CGPoint(x: 0.0, y: sourceAbsoluteRect.minY - targetAbsoluteRect.minY), .custom(0.33, 0.0, 0.0, 1.0), duration) - self.containerNode.layer.animatePosition(from: CGPoint(x: sourceAbsoluteRect.minX - targetAbsoluteRect.minX, y: 0.0), to: CGPoint(), duration: duration * 0.5, delay: delay, mediaTimingFunction: CAMediaTimingFunction(controlPoints: 0.33, 0.0, 0.0, 1.0), additive: true) + self.contextSourceNode.applyAbsoluteOffset?(CGPoint(x: sourceAbsoluteRect.minX - targetAbsoluteRect.minX, y: 0.0), .custom(0.33, 0.0, 0.0, 1.0), horizontalDuration) + self.contextSourceNode.applyAbsoluteOffset?(CGPoint(x: 0.0, y: sourceAbsoluteRect.minY - targetAbsoluteRect.minY), .custom(0.33, 0.0, 0.0, 1.0), verticalDuration) + self.containerNode.layer.animatePosition(from: CGPoint(x: sourceAbsoluteRect.minX - targetAbsoluteRect.minX, y: 0.0), to: CGPoint(), duration: horizontalDuration, delay: delay, mediaTimingFunction: CAMediaTimingFunction(controlPoints: 0.33, 0.0, 0.0, 1.0), additive: true) + case let .stickerMediaInput(stickerMediaInput, replyPanel): + self.itemNode.cancelInsertionAnimations() + + self.contextSourceNode.isExtractedToContextPreview = true + self.contextSourceNode.isExtractedToContextPreviewUpdated?(true) + + self.containerNode.addSubnode(self.contextSourceNode.contentNode) + + let stickerSource: Sticker + let sourceAbsoluteRect: CGRect + switch stickerMediaInput { + case let .inputPanel(sourceItemNode): + stickerSource = Sticker(imageNode: sourceItemNode.imageNode, animationNode: sourceItemNode.animationNode, placeholderNode: sourceItemNode.placeholderNode, relativeSourceRect: sourceItemNode.imageNode.frame) + sourceAbsoluteRect = sourceItemNode.view.convert(stickerSource.imageNode.frame, to: nil) + case let .mediaPanel(sourceItemNode): + stickerSource = Sticker(imageNode: sourceItemNode.imageNode, animationNode: sourceItemNode.animationNode, placeholderNode: sourceItemNode.placeholderNode, relativeSourceRect: sourceItemNode.imageNode.frame) + sourceAbsoluteRect = sourceItemNode.view.convert(stickerSource.imageNode.frame, to: nil) + case let .inputPanelSearch(sourceItemNode): + stickerSource = Sticker(imageNode: sourceItemNode.imageNode, animationNode: sourceItemNode.animationNode, placeholderNode: nil, relativeSourceRect: sourceItemNode.imageNode.frame) + sourceAbsoluteRect = sourceItemNode.view.convert(stickerSource.imageNode.frame, to: nil) + } + + let targetAbsoluteRect = self.contextSourceNode.view.convert(self.contextSourceNode.contentRect, to: nil) + + var sourceReplyPanel: ReplyPanel? + if let replyPanel = replyPanel, let replyPanelParentView = replyPanel.view.superview { + var replySourceAbsoluteFrame = replyPanelParentView.convert(replyPanel.originalFrameBeforeDismissed ?? replyPanel.frame, to: nil) + replySourceAbsoluteFrame.origin.x -= sourceAbsoluteRect.midX - self.contextSourceNode.contentRect.midX + replySourceAbsoluteFrame.origin.y -= sourceAbsoluteRect.midY - self.contextSourceNode.contentRect.midY + + sourceReplyPanel = ReplyPanel(titleNode: replyPanel.titleNode, textNode: replyPanel.textNode, lineNode: replyPanel.lineNode, imageNode: replyPanel.imageNode, relativeSourceRect: replySourceAbsoluteFrame) + } + + self.itemNode.cancelInsertionAnimations() + + let verticalDuration: Double = 0.5 + let horizontalDuration: Double = verticalDuration * 0.7 + let delay: Double = 0.0 + + let transition: ContainedViewLayoutTransition = .animated(duration: horizontalDuration, curve: .custom(0.33, 0.0, 0.0, 1.0)) + + if let itemNode = self.itemNode as? ChatMessageAnimatedStickerItemNode { + itemNode.animateContentFromStickerGridItem(stickerSource: stickerSource, transition: transition) + if let sourceAnimationNode = stickerSource.animationNode { + itemNode.animationNode?.setFrameIndex(sourceAnimationNode.currentFrameIndex) + } + if let sourceReplyPanel = sourceReplyPanel { + itemNode.animateReplyPanel(sourceReplyPanel: sourceReplyPanel, transition: transition) + } + } else if let itemNode = self.itemNode as? ChatMessageStickerItemNode { + itemNode.animateContentFromStickerGridItem(stickerSource: stickerSource, transition: transition) + if let sourceReplyPanel = sourceReplyPanel { + itemNode.animateReplyPanel(sourceReplyPanel: sourceReplyPanel, transition: transition) + } + } + + self.containerNode.frame = targetAbsoluteRect.offsetBy(dx: -self.contextSourceNode.contentRect.minX, dy: -self.contextSourceNode.contentRect.minY) + self.contextSourceNode.updateAbsoluteRect?(self.containerNode.frame, UIScreen.main.bounds.size) + self.containerNode.layer.animatePosition(from: CGPoint(x: 0.0, y: sourceAbsoluteRect.midY - targetAbsoluteRect.midY), to: CGPoint(), duration: verticalDuration, delay: delay, mediaTimingFunction: CAMediaTimingFunction(controlPoints: 0.33, 0.0, 0.0, 1.0), additive: true, force: true, completion: { [weak self] _ in + guard let strongSelf = self else { + return + } + strongSelf.endAnimation() + }) + self.contextSourceNode.applyAbsoluteOffset?(CGPoint(x: sourceAbsoluteRect.midX - targetAbsoluteRect.midX, y: 0.0), .custom(0.33, 0.0, 0.0, 1.0), horizontalDuration) + self.contextSourceNode.applyAbsoluteOffset?(CGPoint(x: 0.0, y: sourceAbsoluteRect.midY - targetAbsoluteRect.midY), .custom(0.33, 0.0, 0.0, 1.0), verticalDuration) + self.containerNode.layer.animatePosition(from: CGPoint(x: sourceAbsoluteRect.midX - targetAbsoluteRect.midX, y: 0.0), to: CGPoint(), duration: horizontalDuration, delay: delay, mediaTimingFunction: CAMediaTimingFunction(controlPoints: 0.33, 0.0, 0.0, 1.0), additive: true) + + switch stickerMediaInput { + case .inputPanel: + break + case let .mediaPanel(sourceItemNode): + sourceItemNode.isHidden = true + case let .inputPanelSearch(sourceItemNode): + sourceItemNode.isHidden = true + } + case let .audioMicInput(audioMicInput): + if let (container, localRect) = audioMicInput.micButton.contentContainer { + let snapshotView = container.snapshotView(afterScreenUpdates: false) + if let snapshotView = snapshotView { + let sourceAbsoluteRect = container.convert(localRect, to: nil) + snapshotView.frame = sourceAbsoluteRect + + container.isHidden = true + + let verticalDuration: Double = 0.5 + let horizontalDuration: Double = verticalDuration * 0.7 + let delay: Double = 0.0 + + let transition: ContainedViewLayoutTransition = .animated(duration: horizontalDuration, curve: .custom(0.33, 0.0, 0.0, 1.0)) + + if let itemNode = self.itemNode as? ChatMessageBubbleItemNode { + if let contextContainer = itemNode.animateFromMicInput(micInputNode: snapshotView, transition: transition) { + self.containerNode.addSubnode(contextContainer.contentNode) + + let targetAbsoluteRect = contextContainer.view.convert(contextContainer.contentRect, to: nil) + + self.containerNode.frame = targetAbsoluteRect.offsetBy(dx: -contextContainer.contentRect.minX, dy: -contextContainer.contentRect.minY) + contextContainer.updateAbsoluteRect?(self.containerNode.frame, UIScreen.main.bounds.size) + self.containerNode.layer.animatePosition(from: CGPoint(x: 0.0, y: sourceAbsoluteRect.midY - targetAbsoluteRect.midY), to: CGPoint(), duration: verticalDuration, delay: delay, mediaTimingFunction: CAMediaTimingFunction(controlPoints: 0.33, 0.0, 0.0, 1.0), additive: true, force: true, completion: { [weak self, weak contextContainer, weak container] _ in + guard let strongSelf = self else { + return + } + if let contextContainer = contextContainer { + contextContainer.isExtractedToContextPreview = false + contextContainer.isExtractedToContextPreviewUpdated?(false) + contextContainer.addSubnode(contextContainer.contentNode) + } + + container?.isHidden = false + + strongSelf.endAnimation() + }) + + self.containerNode.layer.animatePosition(from: CGPoint(x: sourceAbsoluteRect.midX - targetAbsoluteRect.midX, y: 0.0), to: CGPoint(), duration: horizontalDuration, delay: delay, mediaTimingFunction: CAMediaTimingFunction(controlPoints: 0.33, 0.0, 0.0, 1.0), additive: true) + } + } + } + } } } @@ -164,16 +403,33 @@ final class ChatMessageTransitionNode: ASDisplayNode { } private func beginAnimation(itemNode: ChatMessageItemView, source: Source) { + var contextSourceNode: ContextExtractedContentContainingNode? if let itemNode = itemNode as? ChatMessageBubbleItemNode { - let animatingItemNode = AnimatingItemNode(itemNode: itemNode, contextSourceNode: itemNode.mainContextSourceNode, source: source) + contextSourceNode = itemNode.mainContextSourceNode + } else if let itemNode = itemNode as? ChatMessageStickerItemNode { + contextSourceNode = itemNode.contextSourceNode + } else if let itemNode = itemNode as? ChatMessageAnimatedStickerItemNode { + contextSourceNode = itemNode.contextSourceNode + } + + if let contextSourceNode = contextSourceNode { + let animatingItemNode = AnimatingItemNode(itemNode: itemNode, contextSourceNode: contextSourceNode, source: source) self.animatingItemNodes.append(animatingItemNode) - self.addSubnode(animatingItemNode) + if case .audioMicInput = source { + let overlayController = OverlayTransitionContainerController() + overlayController.displayNode.addSubnode(animatingItemNode) + animatingItemNode.overlayController = overlayController + itemNode.item?.context.sharedContext.mainWindow?.presentInGlobalOverlay(overlayController) + } else { + self.addSubnode(animatingItemNode) + } animatingItemNode.animationEnded = { [weak self, weak animatingItemNode] in guard let strongSelf = self, let animatingItemNode = animatingItemNode else { return } animatingItemNode.removeFromSupernode() + animatingItemNode.overlayController?.dismiss() if let index = strongSelf.animatingItemNodes.firstIndex(where: { $0 === animatingItemNode }) { strongSelf.animatingItemNodes.remove(at: index) } @@ -181,10 +437,6 @@ final class ChatMessageTransitionNode: ASDisplayNode { animatingItemNode.frame = self.bounds animatingItemNode.beginAnimation() - } else if let itemNode = itemNode as? ChatMessageStickerItemNode { - - } else if let itemNode = itemNode as? ChatMessageAnimatedStickerItemNode { - } } diff --git a/submodules/TelegramUI/Sources/ChatTextInputMediaRecordingButton.swift b/submodules/TelegramUI/Sources/ChatTextInputMediaRecordingButton.swift index d476cb3509..fbaa25b24e 100644 --- a/submodules/TelegramUI/Sources/ChatTextInputMediaRecordingButton.swift +++ b/submodules/TelegramUI/Sources/ChatTextInputMediaRecordingButton.swift @@ -97,7 +97,7 @@ private final class ChatTextInputMediaRecordingButtonPresenterControllerNode: Vi private final class ChatTextInputMediaRecordingButtonPresenter : NSObject, TGModernConversationInputMicButtonPresentation { private let account: Account? private let presentController: (ViewController) -> Void - private let container: ChatTextInputMediaRecordingButtonPresenterContainer + let container: ChatTextInputMediaRecordingButtonPresenterContainer private var presentationController: ChatTextInputMediaRecordingButtonPresenterController? init(account: Account, presentController: @escaping (ViewController) -> Void) { @@ -176,6 +176,16 @@ final class ChatTextInputMediaRecordingButton: TGModernConversationInputMicButto private(set) var cancelTranslation: CGFloat = 0.0 private var micLevelDisposable: MetaDisposable? + + private weak var currentPresenter: UIView? + + var contentContainer: (UIView, CGRect)? { + if let _ = self.currentPresenter { + return (self.micDecoration, self.micDecoration.bounds) + } else { + return nil + } + } var audioRecorder: ManagedAudioRecorder? { didSet { @@ -410,7 +420,9 @@ final class ChatTextInputMediaRecordingButton: TGModernConversationInputMicButto } func micButtonPresenter() -> TGModernConversationInputMicButtonPresentation! { - return ChatTextInputMediaRecordingButtonPresenter(account: self.account!, presentController: self.presentController) + let presenter = ChatTextInputMediaRecordingButtonPresenter(account: self.account!, presentController: self.presentController) + self.currentPresenter = presenter.view() + return presenter } func micButtonDecoration() -> (UIView & TGModernConversationInputMicButtonDecoration)! { diff --git a/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift b/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift index 6fa624b188..db1950e252 100644 --- a/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift @@ -294,6 +294,10 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate { self.actionButtons.micButton.account = self.context?.account } } + + var micButton: ChatTextInputMediaRecordingButton? { + return self.actionButtons.micButton + } private let statusDisposable = MetaDisposable() override var interfaceInteraction: ChatPanelInterfaceInteraction? { diff --git a/submodules/TelegramUI/Sources/HorizontalStickerGridItem.swift b/submodules/TelegramUI/Sources/HorizontalStickerGridItem.swift index 3f7d1c7fe6..c8072be6b2 100755 --- a/submodules/TelegramUI/Sources/HorizontalStickerGridItem.swift +++ b/submodules/TelegramUI/Sources/HorizontalStickerGridItem.swift @@ -49,9 +49,9 @@ final class HorizontalStickerGridItem: GridItem { final class HorizontalStickerGridItemNode: GridItemNode { private var currentState: (Account, HorizontalStickerGridItem, CGSize)? - private let imageNode: TransformImageNode - private var animationNode: AnimatedStickerNode? - private var placeholderNode: StickerShimmerEffectNode? + let imageNode: TransformImageNode + private(set) var animationNode: AnimatedStickerNode? + private(set) var placeholderNode: StickerShimmerEffectNode? private let stickerFetchedDisposable = MetaDisposable() diff --git a/submodules/TelegramUI/Sources/ManagedDiceAnimationNode.swift b/submodules/TelegramUI/Sources/ManagedDiceAnimationNode.swift index 40b9492a53..add33bf816 100644 --- a/submodules/TelegramUI/Sources/ManagedDiceAnimationNode.swift +++ b/submodules/TelegramUI/Sources/ManagedDiceAnimationNode.swift @@ -206,4 +206,11 @@ final class ManagedDiceAnimationNode: ManagedAnimationNode, GenericAnimatedStick func setOverlayColor(_ color: UIColor?, animated: Bool) { } + + func setFrameIndex(_ frameIndex: Int) { + } + + var currentFrameIndex: Int { + return 0 + } } diff --git a/submodules/TelegramUI/Sources/StickerPaneSearchStickerItem.swift b/submodules/TelegramUI/Sources/StickerPaneSearchStickerItem.swift index 3d729acda4..31c9514759 100644 --- a/submodules/TelegramUI/Sources/StickerPaneSearchStickerItem.swift +++ b/submodules/TelegramUI/Sources/StickerPaneSearchStickerItem.swift @@ -107,8 +107,8 @@ private let textFont = Font.regular(20.0) final class StickerPaneSearchStickerItemNode: GridItemNode { private var currentState: (Account, FoundStickerItem, CGSize)? - private let imageNode: TransformImageNode - private var animationNode: AnimatedStickerNode? + let imageNode: TransformImageNode + private(set) var animationNode: AnimatedStickerNode? private let textNode: ASTextNode private let stickerFetchedDisposable = MetaDisposable()