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