Update message animations

This commit is contained in:
Ali 2021-04-16 19:43:53 +04:00
parent b9d3c2e1d0
commit 25d7287068
23 changed files with 887 additions and 181 deletions

View File

@ -56,6 +56,7 @@ public enum AnimatedStickerPlaybackPosition {
case start case start
case end case end
case timestamp(Double) case timestamp(Double)
case frameIndex(Int)
} }
public enum AnimatedStickerPlaybackMode { public enum AnimatedStickerPlaybackMode {
@ -94,6 +95,7 @@ public protocol AnimatedStickerFrameSource: class {
func takeFrame(draw: Bool) -> AnimatedStickerFrame? func takeFrame(draw: Bool) -> AnimatedStickerFrame?
func skipToEnd() func skipToEnd()
func skipToFrameIndex(_ index: Int)
} }
private final class AnimatedStickerFrameSourceWrapper { private final class AnimatedStickerFrameSourceWrapper {
@ -271,6 +273,9 @@ public final class AnimatedStickerCachedFrameSource: AnimatedStickerFrameSource
public func skipToEnd() { public func skipToEnd() {
} }
public func skipToFrameIndex(_ index: Int) {
}
} }
private func wrappedWrite(_ fd: Int32, _ data: UnsafeRawPointer, _ count: Int) -> Int { private func wrappedWrite(_ fd: Int32, _ data: UnsafeRawPointer, _ count: Int) -> Int {
@ -656,6 +661,10 @@ private final class AnimatedStickerDirectFrameSource: AnimatedStickerFrameSource
func skipToEnd() { func skipToEnd() {
self.currentFrame = self.frameCount - 1 self.currentFrame = self.frameCount - 1
} }
func skipToFrameIndex(_ index: Int) {
self.currentFrame = index
}
} }
public final class AnimatedStickerFrameQueue { public final class AnimatedStickerFrameQueue {
@ -748,6 +757,8 @@ public final class AnimatedStickerNode: ASDisplayNode {
public var completed: (Bool) -> Void = { _ in } public var completed: (Bool) -> Void = { _ in }
public var frameUpdated: (Int, Int) -> Void = { _, _ in } public var frameUpdated: (Int, Int) -> Void = { _, _ in }
public private(set) var currentFrameIndex: Int = 0
private var playFromIndex: Int?
private let timer = Atomic<SwiftSignalKit.Timer?>(value: nil) private let timer = Atomic<SwiftSignalKit.Timer?>(value: nil)
private let frameSource = Atomic<QueueLocalObject<AnimatedStickerFrameSourceWrapper>?>(value: nil) private let frameSource = Atomic<QueueLocalObject<AnimatedStickerFrameSourceWrapper>?>(value: nil)
@ -842,7 +853,9 @@ public final class AnimatedStickerNode: ASDisplayNode {
strongSelf.isSetUpForPlayback = false strongSelf.isSetUpForPlayback = false
strongSelf.isPlaying = true strongSelf.isPlaying = true
} }
strongSelf.play() var fromIndex = strongSelf.playFromIndex
strongSelf.playFromIndex = nil
strongSelf.play(fromIndex: fromIndex)
} else if strongSelf.canDisplayFirstFrame { } else if strongSelf.canDisplayFirstFrame {
strongSelf.play(firstFrame: true) strongSelf.play(firstFrame: true)
} }
@ -911,7 +924,7 @@ public final class AnimatedStickerNode: ASDisplayNode {
private var isSetUpForPlayback = false private var isSetUpForPlayback = false
public func play(firstFrame: Bool = false) { public func play(firstFrame: Bool = false, fromIndex: Int? = nil) {
switch self.playbackMode { switch self.playbackMode {
case .once: case .once:
self.isPlaying = true self.isPlaying = true
@ -949,6 +962,9 @@ public final class AnimatedStickerNode: ASDisplayNode {
guard let frameSource = maybeFrameSource else { guard let frameSource = maybeFrameSource else {
return return
} }
if let fromIndex = fromIndex {
frameSource.skipToFrameIndex(fromIndex)
}
let frameQueue = QueueLocalObject<AnimatedStickerFrameQueue>(queue: queue, generate: { let frameQueue = QueueLocalObject<AnimatedStickerFrameQueue>(queue: queue, generate: {
return AnimatedStickerFrameQueue(queue: queue, length: 1, source: frameSource) return AnimatedStickerFrameQueue(queue: queue, length: 1, source: frameSource)
}) })
@ -978,6 +994,7 @@ public final class AnimatedStickerNode: ASDisplayNode {
}) })
strongSelf.frameUpdated(frame.index, frame.totalFrames) strongSelf.frameUpdated(frame.index, frame.totalFrames)
strongSelf.currentFrameIndex = frame.index
if frame.isLastFrame { if frame.isLastFrame {
var stopped = false var stopped = false
@ -1016,6 +1033,9 @@ public final class AnimatedStickerNode: ASDisplayNode {
self.isSetUpForPlayback = true self.isSetUpForPlayback = true
let directData = self.directData let directData = self.directData
let cachedData = self.cachedData let cachedData = self.cachedData
if directData == nil && cachedData == nil {
self.playFromIndex = fromIndex
}
let queue = self.queue let queue = self.queue
let timerHolder = self.timer let timerHolder = self.timer
let frameSourceHolder = self.frameSource let frameSourceHolder = self.frameSource
@ -1039,6 +1059,9 @@ public final class AnimatedStickerNode: ASDisplayNode {
guard let frameSource = maybeFrameSource else { guard let frameSource = maybeFrameSource else {
return return
} }
if let fromIndex = fromIndex {
frameSource.skipToFrameIndex(fromIndex)
}
let frameQueue = QueueLocalObject<AnimatedStickerFrameQueue>(queue: queue, generate: { let frameQueue = QueueLocalObject<AnimatedStickerFrameQueue>(queue: queue, generate: {
return AnimatedStickerFrameQueue(queue: queue, length: 1, source: frameSource) return AnimatedStickerFrameQueue(queue: queue, length: 1, source: frameSource)
}) })
@ -1068,6 +1091,7 @@ public final class AnimatedStickerNode: ASDisplayNode {
}) })
strongSelf.frameUpdated(frame.index, frame.totalFrames) strongSelf.frameUpdated(frame.index, frame.totalFrames)
strongSelf.currentFrameIndex = frame.index
if frame.isLastFrame { if frame.isLastFrame {
var stopped = false var stopped = false
@ -1163,6 +1187,21 @@ public final class AnimatedStickerNode: ASDisplayNode {
return 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 var delta = targetFrame - frameSource.frameIndex
if delta < 0 { if delta < 0 {
delta = frameSource.frameCount + delta delta = frameSource.frameCount + delta

View File

@ -2850,6 +2850,7 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
for itemNode in self.itemNodes { for itemNode in self.itemNodes {
itemNode.applyAbsoluteOffset(value: CGPoint(x: 0.0, y: -completeOffset), animationCurve: animationCurve, duration: animationDuration) itemNode.applyAbsoluteOffset(value: CGPoint(x: 0.0, y: -completeOffset), animationCurve: animationCurve, duration: animationDuration)
} }
//self.didScrollWithOffset?(-completeOffset, ContainedViewLayoutTransition.animated(duration: animationDuration, curve: animationCurve), nil)
} }
} else { } else {
self.visibleSize = updateSizeAndInsets.size self.visibleSize = updateSizeAndInsets.size
@ -3089,22 +3090,6 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
animation = springAnimation animation = springAnimation
reverseAnimation = reverseSpringAnimation 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): case let .Custom(duration, cp1x, cp1y, cp2x, cp2y):
animationCurve = .custom(cp1x, cp1y, cp2x, cp2y) animationCurve = .custom(cp1x, cp1y, cp2x, cp2y)
animationDuration = duration animationDuration = duration
@ -3188,6 +3173,7 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
for itemNode in temporaryPreviousNodes { for itemNode in temporaryPreviousNodes {
itemNode.applyAbsoluteOffset(value: CGPoint(x: 0.0, y: -offset), animationCurve: animationCurve, duration: animationDuration) 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 { if let verticalScrollIndicator = self.verticalScrollIndicator {
verticalScrollIndicator.layer.add(reverseAnimation, forKey: nil) verticalScrollIndicator.layer.add(reverseAnimation, forKey: nil)
} }

View File

@ -183,27 +183,19 @@ public final class ListViewAnimation {
} }
public func listViewAnimationDurationAndCurve(transition: ContainedViewLayoutTransition) -> (Double, ListViewAnimationCurve) { public func listViewAnimationDurationAndCurve(transition: ContainedViewLayoutTransition) -> (Double, ListViewAnimationCurve) {
var duration: Double = 0.0
var curve: UInt = 0
switch transition { switch transition {
case .immediate: case .immediate:
break return (0.0, .Default(duration: 0.0))
case let .animated(animationDuration, animationCurve): case let .animated(animationDuration, animationCurve):
duration = animationDuration
switch animationCurve { switch animationCurve {
case .linear, .easeInOut, .custom: case .linear:
break return (animationDuration, .Default(duration: animationDuration))
case .spring: case .easeInOut:
curve = 7 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)
} }

View File

@ -249,7 +249,7 @@ static const CGFloat outerCircleMinScale = innerCircleRadius / outerCircleRadius
_innerCircleView.center = centerPoint; _innerCircleView.center = centerPoint;
_outerCircleView.center = centerPoint; _outerCircleView.center = centerPoint;
_decoration.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); _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.userInteractionEnabled = false;
[_innerIconWrapperView addSubview:_innerIconView]; [_innerIconWrapperView addSubview:_innerIconView];
[[_presentation view] addSubview:_innerIconWrapperView]; //[[_presentation view] addSubview:_innerIconWrapperView];
[_decoration addSubview:_innerIconWrapperView];
if (_lock == nil) { if (_lock == nil) {
_stopButton = [[TGModernButton alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 40.0f, 40.0f)]; _stopButton = [[TGModernButton alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 40.0f, 40.0f)];

View File

@ -61,7 +61,9 @@ public func setupCurrencyNumberFormatter(currency: String) -> NumberFormatter {
result.append("#") result.append("#")
result.append(entry.decimalSeparator) if entry.decimalDigits != 0 {
result.append(entry.decimalSeparator)
}
for _ in 0 ..< entry.decimalDigits { for _ in 0 ..< entry.decimalDigits {
result.append("#") result.append("#")
@ -143,7 +145,9 @@ public func formatCurrencyAmount(_ amount: Int64, currency: String) -> String {
} }
} }
result.append("\(integerPart)") result.append("\(integerPart)")
result.append(entry.decimalSeparator) if !fractional.isEmpty {
result.append(entry.decimalSeparator)
}
for i in 0 ..< fractional.count { for i in 0 ..< fractional.count {
result.append(fractional[fractional.count - i - 1]) result.append(fractional[fractional.count - i - 1])
} }
@ -187,7 +191,9 @@ public func formatCurrencyAmountCustom(_ amount: Int64, currency: String) -> (St
} }
} }
result.append("\(integerPart)") result.append("\(integerPart)")
result.append(entry.decimalSeparator) if !fractional.isEmpty {
result.append(entry.decimalSeparator)
}
for i in 0 ..< fractional.count { for i in 0 ..< fractional.count {
result.append(fractional[fractional.count - i - 1]) result.append(fractional[fractional.count - i - 1])
} }

View File

@ -267,7 +267,7 @@
"decimalSeparator": ",", "decimalSeparator": ",",
"symbolOnLeft": true, "symbolOnLeft": true,
"spaceBetweenAmountAndSymbol": true, "spaceBetweenAmountAndSymbol": true,
"decimalDigits": 2 "decimalDigits": 0
}, },
"CNY": { "CNY": {
"code": "CNY", "code": "CNY",
@ -1356,7 +1356,7 @@
"decimalSeparator": ",", "decimalSeparator": ",",
"symbolOnLeft": false, "symbolOnLeft": false,
"spaceBetweenAmountAndSymbol": true, "spaceBetweenAmountAndSymbol": true,
"decimalDigits": 1 "decimalDigits": 0
}, },
"VUV": { "VUV": {
"code": "VUV", "code": "VUV",

View File

@ -1042,8 +1042,38 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
if let query = query { if let query = query {
attributes.append(EmojiSearchQueryMessageAttribute(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 return true
}, sendGif: { [weak self] fileReference, sourceNode, sourceRect in }, sendGif: { [weak self] fileReference, sourceNode, sourceRect in
if let strongSelf = self { 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.chatDisplayNode.requestUpdateChatInterfaceState = { [weak self] transition, saveInterfaceState, f in
self?.updateChatPresentationInterfaceState(animated: animated, interactive: true, saveInterfaceState: saveInterfaceState, { $0.updatedInterfaceState(f) }) self?.updateChatPresentationInterfaceState(transition: transition, interactive: true, saveInterfaceState: saveInterfaceState, { $0.updatedInterfaceState(f) })
} }
self.chatDisplayNode.requestUpdateInterfaceState = { [weak self] transition, interactive, f in self.chatDisplayNode.requestUpdateInterfaceState = { [weak self] transition, interactive, f in
@ -9758,7 +9788,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
switch updatedAction { switch updatedAction {
case .dismiss: case .dismiss:
self.chatDisplayNode.updateRecordedMediaDeleted(true) self.chatDisplayNode.updateRecordedMediaDeleted(true)
break self.audioRecorder.set(.single(nil))
case .preview: case .preview:
self.updateChatPresentationInterfaceState(animated: true, interactive: true, { self.updateChatPresentationInterfaceState(animated: true, interactive: true, {
$0.updatedInputTextPanelState { panelState in $0.updatedInputTextPanelState { panelState in
@ -9789,6 +9819,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
} }
} }
}) })
self.audioRecorder.set(.single(nil))
case .send: case .send:
self.chatDisplayNode.updateRecordedMediaDeleted(false) self.chatDisplayNode.updateRecordedMediaDeleted(false)
let _ = (audioRecorderValue.takenRecordedData() let _ = (audioRecorderValue.takenRecordedData()
@ -9797,6 +9828,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
if data.duration < 0.5 { if data.duration < 0.5 {
strongSelf.recorderFeedback?.error() strongSelf.recorderFeedback?.error()
strongSelf.recorderFeedback = nil strongSelf.recorderFeedback = nil
strongSelf.audioRecorder.set(.single(nil))
} else { } else {
let randomId = arc4random64() 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?.tap()
strongSelf.recorderFeedback = nil 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 { } else if let videoRecorderValue = self.videoRecorderValue {
if case .send = updatedAction { if case .send = updatedAction {
self.chatDisplayNode.updateRecordedMediaDeleted(false) self.chatDisplayNode.updateRecordedMediaDeleted(false)

View File

@ -55,6 +55,7 @@ private struct ChatControllerNodeDerivedLayoutState {
var inputContextPanelsFrame: CGRect var inputContextPanelsFrame: CGRect
var inputContextPanelsOverMainPanelFrame: CGRect var inputContextPanelsOverMainPanelFrame: CGRect
var inputNodeHeight: CGFloat? var inputNodeHeight: CGFloat?
var inputNodeAdditionalHeight: CGFloat?
var upperInputPositionBound: CGFloat? var upperInputPositionBound: CGFloat?
} }
@ -329,7 +330,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
private var inputPanelNode: ChatInputPanelNode? private var inputPanelNode: ChatInputPanelNode?
private weak var currentDismissedInputPanelNode: ASDisplayNode? private weak var currentDismissedInputPanelNode: ASDisplayNode?
private var secondaryInputPanelNode: ChatInputPanelNode? private var secondaryInputPanelNode: ChatInputPanelNode?
private var accessoryPanelNode: AccessoryPanelNode? private(set) var accessoryPanelNode: AccessoryPanelNode?
private var inputContextPanelNode: ChatInputContextPanelNode? private var inputContextPanelNode: ChatInputContextPanelNode?
private let inputContextPanelContainer: ChatControllerTitlePanelNodeContainer private let inputContextPanelContainer: ChatControllerTitlePanelNodeContainer
private var overlayContextPanelNode: ChatInputContextPanelNode? private var overlayContextPanelNode: ChatInputContextPanelNode?
@ -337,7 +338,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
private var inputNode: ChatInputNode? private var inputNode: ChatInputNode?
private var disappearingNode: ChatInputNode? private var disappearingNode: ChatInputNode?
private var textInputPanelNode: ChatTextInputPanelNode? private(set) var textInputPanelNode: ChatTextInputPanelNode?
private var inputMediaNode: ChatMediaInputNode? private var inputMediaNode: ChatMediaInputNode?
let navigateButtons: ChatHistoryNavigationButtons 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 requestUpdateInterfaceState: (ContainedViewLayoutTransition, Bool, (ChatPresentationInterfaceState) -> ChatPresentationInterfaceState) -> Void = { _, _, _ in }
var sendMessages: ([EnqueueMessage], Bool?, Int32?, Bool) -> Void = { _, _, _, _ in } var sendMessages: ([EnqueueMessage], Bool?, Int32?, Bool) -> Void = { _, _, _, _ in }
var displayAttachmentMenu: () -> Void = { } var displayAttachmentMenu: () -> Void = { }
@ -1299,9 +1300,9 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
accessoryPanelNode.dismiss = { [weak self, weak accessoryPanelNode] in accessoryPanelNode.dismiss = { [weak self, weak accessoryPanelNode] in
if let strongSelf = self, let accessoryPanelNode = accessoryPanelNode, strongSelf.accessoryPanelNode === accessoryPanelNode { if let strongSelf = self, let accessoryPanelNode = accessoryPanelNode, strongSelf.accessoryPanelNode === accessoryPanelNode {
if let _ = accessoryPanelNode as? ReplyAccessoryPanelNode { 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 { } 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 { } else if let _ = accessoryPanelNode as? EditAccessoryPanelNode {
strongSelf.interfaceInteraction?.setupEditMessage(nil, { _ in }) strongSelf.interfaceInteraction?.setupEditMessage(nil, { _ in })
} else if let _ = accessoryPanelNode as? WebpagePreviewAccessoryPanelNode { } else if let _ = accessoryPanelNode as? WebpagePreviewAccessoryPanelNode {
@ -1873,8 +1874,14 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
} }
self.updatePlainInputSeparator(transition: transition) 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) //self.notifyTransitionCompletionListeners(transition: transition)
} }
@ -2656,7 +2663,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
if let strongSelf = self, let textInputPanelNode = strongSelf.inputPanelNode as? ChatTextInputPanelNode { if let strongSelf = self, let textInputPanelNode = strongSelf.inputPanelNode as? ChatTextInputPanelNode {
strongSelf.ignoreUpdateHeight = true strongSelf.ignoreUpdateHeight = true
textInputPanelNode.text = "" 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 strongSelf.ignoreUpdateHeight = false
} }
}) })

View File

@ -174,8 +174,8 @@ final class ChatMediaInputStickerGridItemNode: GridItemNode {
private var currentState: (Account, StickerPackItem, CGSize)? private var currentState: (Account, StickerPackItem, CGSize)?
private var currentSize: CGSize? private var currentSize: CGSize?
let imageNode: TransformImageNode let imageNode: TransformImageNode
var animationNode: AnimatedStickerNode? private(set) var animationNode: AnimatedStickerNode?
private var placeholderNode: StickerShimmerEffectNode? private(set) var placeholderNode: StickerShimmerEffectNode?
private var didSetUpAnimationNode = false private var didSetUpAnimationNode = false
private var item: ChatMediaInputStickerGridItem? private var item: ChatMediaInputStickerGridItem?
@ -308,9 +308,13 @@ final class ChatMediaInputStickerGridItemNode: GridItemNode {
if let (_, _, mediaDimensions) = self.currentState { if let (_, _, mediaDimensions) = self.currentState {
let imageSize = mediaDimensions.aspectFitted(boundingSize) let imageSize = mediaDimensions.aspectFitted(boundingSize)
self.imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets()))() 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 { 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) animationNode.updateLayout(size: imageSize)
} }
} }
@ -318,7 +322,9 @@ final class ChatMediaInputStickerGridItemNode: GridItemNode {
if let placeholderNode = self.placeholderNode { 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) 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 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) 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)

View File

@ -29,14 +29,25 @@ private let inlineBotNameFont = nameFont
protocol GenericAnimatedStickerNode: ASDisplayNode { protocol GenericAnimatedStickerNode: ASDisplayNode {
func setOverlayColor(_ color: UIColor?, animated: Bool) func setOverlayColor(_ color: UIColor?, animated: Bool)
var currentFrameIndex: Int { get }
func setFrameIndex(_ frameIndex: Int)
} }
extension AnimatedStickerNode: GenericAnimatedStickerNode { extension AnimatedStickerNode: GenericAnimatedStickerNode {
func setFrameIndex(_ frameIndex: Int) {
self.stop()
self.play(fromIndex: frameIndex)
}
} }
extension SlotMachineAnimationNode: GenericAnimatedStickerNode { extension SlotMachineAnimationNode: GenericAnimatedStickerNode {
var currentFrameIndex: Int {
return 0
}
func setFrameIndex(_ frameIndex: Int) {
}
} }
class ChatMessageShareButton: HighlightableButtonNode { class ChatMessageShareButton: HighlightableButtonNode {
@ -141,11 +152,12 @@ class ChatMessageShareButton: HighlightableButtonNode {
} }
class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
private let contextSourceNode: ContextExtractedContentContainingNode let contextSourceNode: ContextExtractedContentContainingNode
private let containerNode: ContextControllerSourceNode private let containerNode: ContextControllerSourceNode
let imageNode: TransformImageNode let imageNode: TransformImageNode
private var placeholderNode: StickerShimmerEffectNode private var enableSynchronousImageApply: Bool = false
private var animationNode: GenericAnimatedStickerNode? private(set) var placeholderNode: StickerShimmerEffectNode
private(set) var animationNode: GenericAnimatedStickerNode?
private var didSetUpAnimationNode = false private var didSetUpAnimationNode = false
private var isPlaying = false private var isPlaying = false
private var animateGreeting = false private var animateGreeting = false
@ -250,8 +262,12 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
} }
if image != nil { if image != nil {
if firstTime && !strongSelf.placeholderNode.isEmpty && !strongSelf.animateGreeting && !strongSelf.animatingGreeting { if firstTime && !strongSelf.placeholderNode.isEmpty && !strongSelf.animateGreeting && !strongSelf.animatingGreeting {
strongSelf.animationNode?.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) if strongSelf.enableSynchronousImageApply {
strongSelf.removePlaceholder(animated: true) strongSelf.removePlaceholder(animated: false)
} else {
strongSelf.animationNode?.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
strongSelf.removePlaceholder(animated: true)
}
} else { } else {
strongSelf.removePlaceholder(animated: true) strongSelf.removePlaceholder(animated: true)
} }
@ -436,15 +452,15 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
} }
} }
override func setupItem(_ item: ChatMessageItem) { override func setupItem(_ item: ChatMessageItem, synchronousLoad: Bool) {
super.setupItem(item) super.setupItem(item, synchronousLoad: synchronousLoad)
for media in item.message.media { for media in item.message.media {
if let telegramFile = media as? TelegramMediaFile { if let telegramFile = media as? TelegramMediaFile {
if self.telegramFile?.id != telegramFile.id { if self.telegramFile?.id != telegramFile.id {
self.telegramFile = telegramFile self.telegramFile = telegramFile
let dimensions = telegramFile.dimensions ?? PixelDimensions(width: 512, height: 512) 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.updateVisibility()
self.disposable.set(freeMediaFileInteractiveFetched(account: item.context.account, fileReference: .message(message: MessageReference(item.message), media: telegramFile)).start()) 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 { if let fitz = fitz {
fitzModifier = EmojiFitzModifier(emoji: 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.disposable.set(freeMediaFileInteractiveFetched(account: item.context.account, fileReference: .standalone(media: emojiFile)).start())
} }
self.updateVisibility() self.updateVisibility()
@ -1060,7 +1076,10 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
if let animationNode = strongSelf.animationNode as? AnimatedStickerNode, strongSelf.animationNode?.supernode === strongSelf.contextSourceNode.contentNode { if let animationNode = strongSelf.animationNode as? AnimatedStickerNode, strongSelf.animationNode?.supernode === strongSelf.contextSourceNode.contentNode {
animationNode.updateLayout(size: updatedContentFrame.insetBy(dx: imageInset, dy: imageInset).size) animationNode.updateLayout(size: updatedContentFrame.insetBy(dx: imageInset, dy: imageInset).size)
} }
strongSelf.enableSynchronousImageApply = true
imageApply() imageApply()
strongSelf.enableSynchronousImageApply = false
strongSelf.contextSourceNode.contentRect = strongSelf.imageNode.frame strongSelf.contextSourceNode.contentRect = strongSelf.imageNode.frame
strongSelf.containerNode.targetNodeForActivationProgressContentRect = strongSelf.contextSourceNode.contentRect strongSelf.containerNode.targetNodeForActivationProgressContentRect = strongSelf.contextSourceNode.contentRect
@ -1090,7 +1109,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
if let updatedReplyBackgroundNode = updatedReplyBackgroundNode { if let updatedReplyBackgroundNode = updatedReplyBackgroundNode {
if strongSelf.replyBackgroundNode == nil { if strongSelf.replyBackgroundNode == nil {
strongSelf.replyBackgroundNode = updatedReplyBackgroundNode strongSelf.replyBackgroundNode = updatedReplyBackgroundNode
strongSelf.addSubnode(updatedReplyBackgroundNode) strongSelf.contextSourceNode.contentNode.addSubnode(updatedReplyBackgroundNode)
updatedReplyBackgroundNode.image = replyBackgroundImage updatedReplyBackgroundNode.image = replyBackgroundImage
} else { } else {
strongSelf.replyBackgroundNode?.image = replyBackgroundImage strongSelf.replyBackgroundNode?.image = replyBackgroundImage
@ -1118,7 +1137,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
let replyInfoNode = replyInfoApply() let replyInfoNode = replyInfoApply()
if strongSelf.replyInfoNode == nil { if strongSelf.replyInfoNode == nil {
strongSelf.replyInfoNode = replyInfoNode strongSelf.replyInfoNode = replyInfoNode
strongSelf.addSubnode(replyInfoNode) strongSelf.contextSourceNode.contentNode.addSubnode(replyInfoNode)
} }
var viaBotSize = CGSize() var viaBotSize = CGSize()
if let viaBotNode = strongSelf.viaBotNode { 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) { override func animateInsertion(_ currentTimestamp: Double, duration: Double, short: Bool) {
super.animateInsertion(currentTimestamp, duration: duration, short: short) super.animateInsertion(currentTimestamp, duration: duration, short: short)
@ -1707,6 +1730,168 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
override func addAccessoryItemNode(_ accessoryItemNode: ListViewAccessoryItemNode) { override func addAccessoryItemNode(_ accessoryItemNode: ListViewAccessoryItemNode) {
self.contextSourceNode.contentNode.addSubnode(accessoryItemNode) 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 { struct AnimatedEmojiSoundsConfiguration {

View File

@ -367,7 +367,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
private let backgroundWallpaperNode: ChatMessageBubbleBackdrop private let backgroundWallpaperNode: ChatMessageBubbleBackdrop
private let backgroundNode: ChatMessageBackground private let backgroundNode: ChatMessageBackground
private let shadowNode: ChatMessageShadowNode private let shadowNode: ChatMessageShadowNode
private var transitionClippingNode: ASDisplayNode? private var clippingNode: ASDisplayNode
override var extractedBackgroundNode: ASDisplayNode? { override var extractedBackgroundNode: ASDisplayNode? {
return self.shadowNode return self.shadowNode
@ -430,6 +430,10 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
self.backgroundNode = ChatMessageBackground() self.backgroundNode = ChatMessageBackground()
self.shadowNode = ChatMessageShadowNode() self.shadowNode = ChatMessageShadowNode()
self.clippingNode = ASDisplayNode()
self.clippingNode.clipsToBounds = true
self.messageAccessibilityArea = AccessibilityAreaNode() self.messageAccessibilityArea = AccessibilityAreaNode()
super.init(layerBacked: false) super.init(layerBacked: false)
@ -482,6 +486,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
self.mainContextSourceNode.contentNode.addSubnode(self.backgroundWallpaperNode) self.mainContextSourceNode.contentNode.addSubnode(self.backgroundWallpaperNode)
self.mainContextSourceNode.contentNode.addSubnode(self.backgroundNode) self.mainContextSourceNode.contentNode.addSubnode(self.backgroundNode)
self.mainContextSourceNode.contentNode.addSubnode(self.contentContainersWrapperNode) self.mainContextSourceNode.contentNode.addSubnode(self.contentContainersWrapperNode)
self.mainContextSourceNode.contentNode.addSubnode(self.clippingNode)
self.addSubnode(self.messageAccessibilityArea) self.addSubnode(self.messageAccessibilityArea)
self.messageAccessibilityArea.activate = { [weak self] in self.messageAccessibilityArea.activate = { [weak self] in
@ -656,6 +661,9 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
let widthDifference = self.backgroundNode.frame.width - textInput.backgroundView.frame.width 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) 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.backgroundWallpaperNode.animateFrom(sourceView: textInput.backgroundView, mediaBox: item.context.account.postbox.mediaBox, transition: transition)
self.backgroundNode.animateFrom(sourceView: textInput.backgroundView, 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) 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() { override func didLoad() {
super.didLoad() super.didLoad()
@ -2301,7 +2331,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
if !nameNode.isNodeLoaded { if !nameNode.isNodeLoaded {
nameNode.isUserInteractionEnabled = false 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.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 nameNode.displaysAsynchronously = !item.presentationData.isPreview
@ -2313,7 +2343,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
} else { } else {
credibilityIconNode = ASImageNode() credibilityIconNode = ASImageNode()
strongSelf.credibilityIconNode = credibilityIconNode 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.frame = CGRect(origin: CGPoint(x: nameNode.frame.maxX + 4.0, y: nameNode.frame.minY), size: credibilityIconImage.size)
credibilityIconNode.image = credibilityIconImage credibilityIconNode.image = credibilityIconImage
@ -2329,7 +2359,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
if !adminBadgeNode.isNodeLoaded { if !adminBadgeNode.isNodeLoaded {
adminBadgeNode.isUserInteractionEnabled = false adminBadgeNode.isUserInteractionEnabled = false
} }
strongSelf.mainContextSourceNode.contentNode.addSubnode(adminBadgeNode) strongSelf.clippingNode.addSubnode(adminBadgeNode)
adminBadgeNode.frame = adminBadgeFrame adminBadgeNode.frame = adminBadgeFrame
} else { } else {
let previousAdminBadgeFrame = adminBadgeNode.frame let previousAdminBadgeFrame = adminBadgeNode.frame
@ -2351,7 +2381,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
strongSelf.forwardInfoNode = forwardInfoNode strongSelf.forwardInfoNode = forwardInfoNode
var animateFrame = true var animateFrame = true
if forwardInfoNode.supernode == nil { if forwardInfoNode.supernode == nil {
strongSelf.mainContextSourceNode.contentNode.addSubnode(forwardInfoNode) strongSelf.clippingNode.addSubnode(forwardInfoNode)
animateFrame = false animateFrame = false
forwardInfoNode.openPsa = { [weak strongSelf] type, sourceNode in forwardInfoNode.openPsa = { [weak strongSelf] type, sourceNode in
guard let strongSelf = strongSelf, let item = strongSelf.item else { guard let strongSelf = strongSelf, let item = strongSelf.item else {
@ -2578,12 +2608,15 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
updatedContentNodes.append(contentNode) updatedContentNodes.append(contentNode)
let contextSourceNode: ContextExtractedContentContainingNode let contextSourceNode: ContextExtractedContentContainingNode
let containerSupernode: ASDisplayNode
if isAttachent { if isAttachent {
contextSourceNode = strongSelf.mainContextSourceNode contextSourceNode = strongSelf.mainContextSourceNode
containerSupernode = strongSelf.clippingNode
} else { } else {
contextSourceNode = strongSelf.contentContainers.first(where: { $0.contentMessageStableId == contentNodeMessage.stableId })?.sourceNode ?? strongSelf.mainContextSourceNode 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.visibility = strongSelf.visibility
contentNode.updateIsTextSelectionActive = { [weak contextSourceNode] value in contentNode.updateIsTextSelectionActive = { [weak contextSourceNode] value in
@ -2659,7 +2692,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
if mosaicStatusNode !== strongSelf.mosaicStatusNode { if mosaicStatusNode !== strongSelf.mosaicStatusNode {
strongSelf.mosaicStatusNode?.removeFromSupernode() strongSelf.mosaicStatusNode?.removeFromSupernode()
strongSelf.mosaicStatusNode = mosaicStatusNode strongSelf.mosaicStatusNode = mosaicStatusNode
strongSelf.mainContextSourceNode.contentNode.addSubnode(mosaicStatusNode) strongSelf.clippingNode.addSubnode(mosaicStatusNode)
} }
let absoluteOrigin = mosaicStatusOrigin.offsetBy(dx: contentOrigin.x, dy: contentOrigin.y) 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) 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 case .System = animation, !strongSelf.mainContextSourceNode.isExtractedToContextPreview {
if !strongSelf.backgroundNode.frame.equalTo(backgroundFrame) { if !strongSelf.backgroundNode.frame.equalTo(backgroundFrame) {
strongSelf.backgroundFrameTransition = (strongSelf.backgroundNode.frame, backgroundFrame) strongSelf.backgroundFrameTransition = (strongSelf.backgroundNode.frame, backgroundFrame)
strongSelf.enableTransitionClippingNode()
} }
if let shareButtonNode = strongSelf.shareButtonNode { if let shareButtonNode = strongSelf.shareButtonNode {
let currentBackgroundFrame = strongSelf.backgroundNode.frame let currentBackgroundFrame = strongSelf.backgroundNode.frame
@ -2703,15 +2735,20 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
if let shareButtonNode = strongSelf.shareButtonNode { 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)) 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 { if case .System = animation, strongSelf.mainContextSourceNode.isExtractedToContextPreview {
transition.updateFrame(node: strongSelf.backgroundNode, frame: backgroundFrame) 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.backgroundNode.updateLayout(size: backgroundFrame.size, transition: transition)
strongSelf.backgroundWallpaperNode.updateFrame(backgroundFrame, transition: transition) strongSelf.backgroundWallpaperNode.updateFrame(backgroundFrame, transition: transition)
strongSelf.shadowNode.updateLayout(backgroundFrame: backgroundFrame, transition: transition) strongSelf.shadowNode.updateLayout(backgroundFrame: backgroundFrame, transition: transition)
} else { } else {
strongSelf.backgroundNode.frame = backgroundFrame 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.backgroundNode.updateLayout(size: backgroundFrame.size, transition: .immediate)
strongSelf.backgroundWallpaperNode.frame = backgroundFrame strongSelf.backgroundWallpaperNode.frame = backgroundFrame
strongSelf.shadowNode.updateLayout(backgroundFrame: backgroundFrame, transition: .immediate) strongSelf.shadowNode.updateLayout(backgroundFrame: backgroundFrame, transition: .immediate)
@ -2769,7 +2806,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
strongSelf.updateSearchTextHighlightState() strongSelf.updateSearchTextHighlightState()
if let (awaitingAppliedReaction, f) = strongSelf.awaitingAppliedReaction { /*if let (awaitingAppliedReaction, f) = strongSelf.awaitingAppliedReaction {
var bounds = strongSelf.bounds var bounds = strongSelf.bounds
let offset = bounds.origin.x let offset = bounds.origin.x
bounds.origin.x = 0.0 bounds.origin.x = 0.0
@ -2806,7 +2843,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
} }
strongSelf.reactionRecognizer?.complete(into: targetNode, hideTarget: hideTarget)*/ strongSelf.reactionRecognizer?.complete(into: targetNode, hideTarget: hideTarget)*/
f() f()
} }*/
} }
override func updateAccessibilityData(_ accessibilityData: ChatMessageAccessibilityData) { 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 { override func shouldAnimateHorizontalFrameTransition() -> Bool {
if let _ = self.backgroundFrameTransition { if let _ = self.backgroundFrameTransition {
return true return true
@ -2909,6 +2900,10 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
if let backgroundFrameTransition = self.backgroundFrameTransition { if let backgroundFrameTransition = self.backgroundFrameTransition {
let backgroundFrame = CGRect.interpolator()(backgroundFrameTransition.0, backgroundFrameTransition.1, progress) as! CGRect let backgroundFrame = CGRect.interpolator()(backgroundFrameTransition.0, backgroundFrameTransition.1, progress) as! CGRect
self.backgroundNode.frame = backgroundFrame 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.backgroundNode.updateLayout(size: backgroundFrame.size, transition: .immediate)
self.backgroundWallpaperNode.frame = backgroundFrame self.backgroundWallpaperNode.frame = backgroundFrame
self.shadowNode.updateLayout(backgroundFrame: backgroundFrame, transition: .immediate) 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)) 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) { if CGFloat(1.0).isLessThanOrEqualTo(progress) {
self.backgroundFrameTransition = nil self.backgroundFrameTransition = nil
} }

View File

@ -9,7 +9,7 @@ import SyncCore
import TelegramUIPreferences import TelegramUIPreferences
class ChatMessageFileBubbleContentNode: ChatMessageBubbleContentNode { class ChatMessageFileBubbleContentNode: ChatMessageBubbleContentNode {
private let interactiveFileNode: ChatMessageInteractiveFileNode let interactiveFileNode: ChatMessageInteractiveFileNode
override var visibility: ListViewItemNodeVisibility { override var visibility: ListViewItemNodeVisibility {
didSet { didSet {

View File

@ -17,6 +17,7 @@ import FileMediaResourceStatus
import CheckNode import CheckNode
import MusicAlbumArtResources import MusicAlbumArtResources
import AudioBlob import AudioBlob
import ContextUI
private struct FetchControls { private struct FetchControls {
let fetch: () -> Void let fetch: () -> Void
@ -38,6 +39,7 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
private let consumableContentNode: ASImageNode private let consumableContentNode: ASImageNode
private var iconNode: TransformImageNode? private var iconNode: TransformImageNode?
private(set) var statusContainerNode: ContextExtractedContentContainingNode?
private var statusNode: SemanticStatusNode? private var statusNode: SemanticStatusNode?
private var playbackAudioLevelView: VoiceBlobView? private var playbackAudioLevelView: VoiceBlobView?
private var streamingStatusNode: SemanticStatusNode? private var streamingStatusNode: SemanticStatusNode?
@ -129,6 +131,8 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
self.dateAndStatusNode = ChatMessageDateAndStatusNode() self.dateAndStatusNode = ChatMessageDateAndStatusNode()
self.consumableContentNode = ASImageNode() self.consumableContentNode = ASImageNode()
self.statusContainerNode = ContextExtractedContentContainingNode()
super.init() super.init()
@ -136,6 +140,9 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
self.addSubnode(self.descriptionNode) self.addSubnode(self.descriptionNode)
self.addSubnode(self.fetchingTextNode) self.addSubnode(self.fetchingTextNode)
self.addSubnode(self.fetchingCompactTextNode) self.addSubnode(self.fetchingCompactTextNode)
if let statusContainerNode = self.statusContainerNode {
self.addSubnode(statusContainerNode)
}
} }
deinit { deinit {
@ -683,11 +690,20 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
strongSelf.waveformNode.displaysAsynchronously = !presentationData.isPreview strongSelf.waveformNode.displaysAsynchronously = !presentationData.isPreview
strongSelf.statusNode?.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.playbackAudioLevelView?.frame = progressFrame.insetBy(dx: -12.0, dy: -12.0)
strongSelf.progressFrame = progressFrame strongSelf.progressFrame = progressFrame
strongSelf.streamingCacheStatusFrame = streamingCacheStatusFrame strongSelf.streamingCacheStatusFrame = streamingCacheStatusFrame
strongSelf.fileIconImage = fileIconImage 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 { if let updatedFetchControls = updatedFetchControls {
let _ = strongSelf.fetchControls.swap(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) let statusNode = SemanticStatusNode(backgroundNodeColor: backgroundNodeColor, foregroundNodeColor: foregroundNodeColor, image: image, overlayForegroundNodeColor: presentationData.theme.theme.chat.message.mediaOverlayControlColors.foregroundColor)
self.statusNode = statusNode 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 { } else if let statusNode = self.statusNode {
statusNode.backgroundNodeColor = backgroundNodeColor statusNode.backgroundNodeColor = backgroundNodeColor
} }

View File

@ -434,7 +434,7 @@ public final class ChatMessageItem: ListViewItem, CustomStringConvertible {
let configure = { let configure = {
let node = (viewClassName as! ChatMessageItemView.Type).init() let node = (viewClassName as! ChatMessageItemView.Type).init()
node.setupItem(self) node.setupItem(self, synchronousLoad: synchronousLoads)
let nodeLayout = node.asyncLayout() let nodeLayout = node.asyncLayout()
let (top, bottom, dateAtBottom) = self.mergedWithItems(top: previousItem, bottom: nextItem) 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) { 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 { Queue.mainQueue().async {
if let nodeValue = node() as? ChatMessageItemView { if let nodeValue = node() as? ChatMessageItemView {
nodeValue.setupItem(self) nodeValue.setupItem(self, synchronousLoad: false)
let nodeLayout = nodeValue.asyncLayout() let nodeLayout = nodeValue.asyncLayout()

View File

@ -705,7 +705,7 @@ public class ChatMessageItemView: ListViewItemNode {
self.frame = CGRect() self.frame = CGRect()
} }
func setupItem(_ item: ChatMessageItem) { func setupItem(_ item: ChatMessageItem, synchronousLoad: Bool) {
self.item = item self.item = item
} }

View File

@ -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 { if let titleNode = self.titleNode {
let offset = CGPoint( let offset = CGPoint(
x: localRect.minX + sourceReplyPanel.titleNode.frame.minX - titleNode.frame.minX, 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) 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) transition.animatePositionAdditive(node: sourceReplyPanel.lineNode, offset: CGPoint(x: offset.x, y: offset.y), removeOnCompletion: false)
return offset
} }
} }
} }

View File

@ -19,7 +19,7 @@ private let inlineBotPrefixFont = Font.regular(14.0)
private let inlineBotNameFont = nameFont private let inlineBotNameFont = nameFont
class ChatMessageStickerItemNode: ChatMessageItemView { class ChatMessageStickerItemNode: ChatMessageItemView {
private let contextSourceNode: ContextExtractedContentContainingNode let contextSourceNode: ContextExtractedContentContainingNode
private let containerNode: ContextControllerSourceNode private let containerNode: ContextControllerSourceNode
let imageNode: TransformImageNode let imageNode: TransformImageNode
private var placeholderNode: StickerShimmerEffectNode private var placeholderNode: StickerShimmerEffectNode
@ -49,6 +49,8 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
private var currentSwipeToReplyTranslation: CGFloat = 0.0 private var currentSwipeToReplyTranslation: CGFloat = 0.0
private var currentSwipeAction: ChatControllerInteractionSwipeAction? private var currentSwipeAction: ChatControllerInteractionSwipeAction?
private var enableSynchronousImageApply: Bool = false
required init() { required init() {
self.contextSourceNode = ContextExtractedContentContainingNode() self.contextSourceNode = ContextExtractedContentContainingNode()
@ -68,9 +70,13 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
} }
if image != nil { if image != nil {
if firstTime && !strongSelf.placeholderNode.isEmpty { if firstTime && !strongSelf.placeholderNode.isEmpty {
strongSelf.imageNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2, completion: { [weak self] _ in if strongSelf.enableSynchronousImageApply {
self?.removePlaceholder(animated: false) 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 { } else {
strongSelf.removePlaceholder(animated: true) strongSelf.removePlaceholder(animated: true)
} }
@ -214,15 +220,15 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
self.view.addGestureRecognizer(replyRecognizer) self.view.addGestureRecognizer(replyRecognizer)
} }
override func setupItem(_ item: ChatMessageItem) { override func setupItem(_ item: ChatMessageItem, synchronousLoad: Bool) {
super.setupItem(item) super.setupItem(item, synchronousLoad: synchronousLoad)
for media in item.message.media { for media in item.message.media {
if let telegramFile = media as? TelegramMediaFile { if let telegramFile = media as? TelegramMediaFile {
if self.telegramFile != telegramFile { 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.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()) 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) strongSelf.updateAccessibilityData(accessibilityData)
transition.updateFrame(node: strongSelf.imageNode, frame: updatedImageFrame) transition.updateFrame(node: strongSelf.imageNode, frame: updatedImageFrame)
strongSelf.enableSynchronousImageApply = true
imageApply() imageApply()
strongSelf.enableSynchronousImageApply = false
if let immediateThumbnailData = telegramFile?.immediateThumbnailData { if let immediateThumbnailData = telegramFile?.immediateThumbnailData {
let foregroundColor = bubbleVariableColor(variableColor: item.presentationData.theme.theme.chat.message.stickerPlaceholderColor, wallpaper: item.presentationData.theme.wallpaper) 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 let updatedReplyBackgroundNode = updatedReplyBackgroundNode {
if strongSelf.replyBackgroundNode == nil { if strongSelf.replyBackgroundNode == nil {
strongSelf.replyBackgroundNode = updatedReplyBackgroundNode strongSelf.replyBackgroundNode = updatedReplyBackgroundNode
strongSelf.addSubnode(updatedReplyBackgroundNode) strongSelf.contextSourceNode.contentNode.addSubnode(updatedReplyBackgroundNode)
updatedReplyBackgroundNode.image = replyBackgroundImage updatedReplyBackgroundNode.image = replyBackgroundImage
} else { } else {
strongSelf.replyBackgroundNode?.image = replyBackgroundImage strongSelf.replyBackgroundNode?.image = replyBackgroundImage
@ -711,7 +719,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
let replyInfoNode = replyInfoApply() let replyInfoNode = replyInfoApply()
if strongSelf.replyInfoNode == nil { if strongSelf.replyInfoNode == nil {
strongSelf.replyInfoNode = replyInfoNode strongSelf.replyInfoNode = replyInfoNode
strongSelf.addSubnode(replyInfoNode) strongSelf.contextSourceNode.contentNode.addSubnode(replyInfoNode)
} }
replyInfoNode.frame = replyInfoFrame replyInfoNode.frame = replyInfoFrame
strongSelf.replyBackgroundNode?.frame = replyBackgroundFrame ?? CGRect() 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) { override func animateInsertion(_ currentTimestamp: Double, duration: Double, short: Bool) {
super.animateInsertion(currentTimestamp, duration: duration, short: short) super.animateInsertion(currentTimestamp, duration: duration, short: short)
@ -1186,4 +1198,152 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
override func addAccessoryItemNode(_ accessoryItemNode: ListViewAccessoryItemNode) { override func addAccessoryItemNode(_ accessoryItemNode: ListViewAccessoryItemNode) {
self.contextSourceNode.contentNode.addSubnode(accessoryItemNode) 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)
}
}
}
} }

View File

@ -3,6 +3,83 @@ import UIKit
import AsyncDisplayKit import AsyncDisplayKit
import Display import Display
import ContextUI 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<Bool>()
override public var ready: Promise<Bool> {
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 ChatMessageTransitionNode: ASDisplayNode {
final class ReplyPanel { 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 { enum Source {
final class TextInput { final class TextInput {
let backgroundView: UIView 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 textInput(textInput: TextInput, replyPanel: ReplyAccessoryPanelNode?)
case stickerMediaInput(input: StickerInput, replyPanel: ReplyAccessoryPanelNode?)
case audioMicInput(AudioMicInput)
} }
private final class AnimatingItemNode: ASDisplayNode { private final class AnimatingItemNode: ASDisplayNode {
@ -45,6 +152,8 @@ final class ChatMessageTransitionNode: ASDisplayNode {
private let scrollingContainer: ASDisplayNode private let scrollingContainer: ASDisplayNode
private let containerNode: ASDisplayNode private let containerNode: ASDisplayNode
weak var overlayController: OverlayTransitionContainerController?
var animationEnded: (() -> Void)? var animationEnded: (() -> Void)?
init(itemNode: ChatMessageItemView, contextSourceNode: ContextExtractedContentContainingNode, source: ChatMessageTransitionNode.Source) { init(itemNode: ChatMessageItemView, contextSourceNode: ContextExtractedContentContainingNode, source: ChatMessageTransitionNode.Source) {
@ -86,29 +195,159 @@ final class ChatMessageTransitionNode: ASDisplayNode {
self.itemNode.cancelInsertionAnimations() 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 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 { if let itemNode = self.itemNode as? ChatMessageBubbleItemNode {
itemNode.animateContentFromTextInputField(textInput: textInput, transition: transition) itemNode.animateContentFromTextInputField(textInput: textInput, transition: transition)
if let sourceReplyPanel = sourceReplyPanel { if let sourceReplyPanel = sourceReplyPanel {
itemNode.animateReplyPanel(sourceReplyPanel: sourceReplyPanel, transition: transition) 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.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 { guard let strongSelf = self else {
return return
} }
strongSelf.endAnimation() 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: 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), duration) 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: duration * 0.5, delay: delay, mediaTimingFunction: CAMediaTimingFunction(controlPoints: 0.33, 0.0, 0.0, 1.0), additive: true) 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) { private func beginAnimation(itemNode: ChatMessageItemView, source: Source) {
var contextSourceNode: ContextExtractedContentContainingNode?
if let itemNode = itemNode as? ChatMessageBubbleItemNode { 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.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 animatingItemNode.animationEnded = { [weak self, weak animatingItemNode] in
guard let strongSelf = self, let animatingItemNode = animatingItemNode else { guard let strongSelf = self, let animatingItemNode = animatingItemNode else {
return return
} }
animatingItemNode.removeFromSupernode() animatingItemNode.removeFromSupernode()
animatingItemNode.overlayController?.dismiss()
if let index = strongSelf.animatingItemNodes.firstIndex(where: { $0 === animatingItemNode }) { if let index = strongSelf.animatingItemNodes.firstIndex(where: { $0 === animatingItemNode }) {
strongSelf.animatingItemNodes.remove(at: index) strongSelf.animatingItemNodes.remove(at: index)
} }
@ -181,10 +437,6 @@ final class ChatMessageTransitionNode: ASDisplayNode {
animatingItemNode.frame = self.bounds animatingItemNode.frame = self.bounds
animatingItemNode.beginAnimation() animatingItemNode.beginAnimation()
} else if let itemNode = itemNode as? ChatMessageStickerItemNode {
} else if let itemNode = itemNode as? ChatMessageAnimatedStickerItemNode {
} }
} }

View File

@ -97,7 +97,7 @@ private final class ChatTextInputMediaRecordingButtonPresenterControllerNode: Vi
private final class ChatTextInputMediaRecordingButtonPresenter : NSObject, TGModernConversationInputMicButtonPresentation { private final class ChatTextInputMediaRecordingButtonPresenter : NSObject, TGModernConversationInputMicButtonPresentation {
private let account: Account? private let account: Account?
private let presentController: (ViewController) -> Void private let presentController: (ViewController) -> Void
private let container: ChatTextInputMediaRecordingButtonPresenterContainer let container: ChatTextInputMediaRecordingButtonPresenterContainer
private var presentationController: ChatTextInputMediaRecordingButtonPresenterController? private var presentationController: ChatTextInputMediaRecordingButtonPresenterController?
init(account: Account, presentController: @escaping (ViewController) -> Void) { init(account: Account, presentController: @escaping (ViewController) -> Void) {
@ -176,6 +176,16 @@ final class ChatTextInputMediaRecordingButton: TGModernConversationInputMicButto
private(set) var cancelTranslation: CGFloat = 0.0 private(set) var cancelTranslation: CGFloat = 0.0
private var micLevelDisposable: MetaDisposable? 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? { var audioRecorder: ManagedAudioRecorder? {
didSet { didSet {
@ -410,7 +420,9 @@ final class ChatTextInputMediaRecordingButton: TGModernConversationInputMicButto
} }
func micButtonPresenter() -> TGModernConversationInputMicButtonPresentation! { 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)! { func micButtonDecoration() -> (UIView & TGModernConversationInputMicButtonDecoration)! {

View File

@ -294,6 +294,10 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
self.actionButtons.micButton.account = self.context?.account self.actionButtons.micButton.account = self.context?.account
} }
} }
var micButton: ChatTextInputMediaRecordingButton? {
return self.actionButtons.micButton
}
private let statusDisposable = MetaDisposable() private let statusDisposable = MetaDisposable()
override var interfaceInteraction: ChatPanelInterfaceInteraction? { override var interfaceInteraction: ChatPanelInterfaceInteraction? {

View File

@ -49,9 +49,9 @@ final class HorizontalStickerGridItem: GridItem {
final class HorizontalStickerGridItemNode: GridItemNode { final class HorizontalStickerGridItemNode: GridItemNode {
private var currentState: (Account, HorizontalStickerGridItem, CGSize)? private var currentState: (Account, HorizontalStickerGridItem, CGSize)?
private let imageNode: TransformImageNode let imageNode: TransformImageNode
private var animationNode: AnimatedStickerNode? private(set) var animationNode: AnimatedStickerNode?
private var placeholderNode: StickerShimmerEffectNode? private(set) var placeholderNode: StickerShimmerEffectNode?
private let stickerFetchedDisposable = MetaDisposable() private let stickerFetchedDisposable = MetaDisposable()

View File

@ -206,4 +206,11 @@ final class ManagedDiceAnimationNode: ManagedAnimationNode, GenericAnimatedStick
func setOverlayColor(_ color: UIColor?, animated: Bool) { func setOverlayColor(_ color: UIColor?, animated: Bool) {
} }
func setFrameIndex(_ frameIndex: Int) {
}
var currentFrameIndex: Int {
return 0
}
} }

View File

@ -107,8 +107,8 @@ private let textFont = Font.regular(20.0)
final class StickerPaneSearchStickerItemNode: GridItemNode { final class StickerPaneSearchStickerItemNode: GridItemNode {
private var currentState: (Account, FoundStickerItem, CGSize)? private var currentState: (Account, FoundStickerItem, CGSize)?
private let imageNode: TransformImageNode let imageNode: TransformImageNode
private var animationNode: AnimatedStickerNode? private(set) var animationNode: AnimatedStickerNode?
private let textNode: ASTextNode private let textNode: ASTextNode
private let stickerFetchedDisposable = MetaDisposable() private let stickerFetchedDisposable = MetaDisposable()