Merge commit 'eaf511b67fc3cab7261e5107b8b3ac812d173dce'

This commit is contained in:
Ali 2022-10-16 17:06:33 +04:00
commit 79799804f1
10 changed files with 314 additions and 201 deletions

View File

@ -598,21 +598,21 @@ public extension ContainedViewLayoutTransition {
}
}
func animatePositionAdditive(layer: CALayer, offset: CGFloat, removeOnCompletion: Bool = true, completion: @escaping (Bool) -> Void) {
func animatePositionAdditive(layer: CALayer, offset: CGFloat, delay: Double = 0.0, removeOnCompletion: Bool = true, completion: @escaping (Bool) -> Void) {
switch self {
case .immediate:
completion(true)
case let .animated(duration, curve):
layer.animatePosition(from: CGPoint(x: 0.0, y: offset), to: CGPoint(), duration: duration, timingFunction: curve.timingFunction, mediaTimingFunction: curve.mediaTimingFunction, removeOnCompletion: removeOnCompletion, additive: true, completion: completion)
layer.animatePosition(from: CGPoint(x: 0.0, y: offset), to: CGPoint(), duration: duration, delay: delay, timingFunction: curve.timingFunction, mediaTimingFunction: curve.mediaTimingFunction, removeOnCompletion: removeOnCompletion, additive: true, completion: completion)
}
}
func animatePositionAdditive(node: ASDisplayNode, offset: CGPoint, removeOnCompletion: Bool = true, completion: (() -> Void)? = nil) {
func animatePositionAdditive(node: ASDisplayNode, offset: CGPoint, delay: Double = 0.0, removeOnCompletion: Bool = true, completion: (() -> Void)? = nil) {
switch self {
case .immediate:
completion?()
case let .animated(duration, curve):
node.layer.animatePosition(from: offset, to: CGPoint(), duration: duration, timingFunction: curve.timingFunction, mediaTimingFunction: curve.mediaTimingFunction, removeOnCompletion: removeOnCompletion, additive: true, completion: { _ in
node.layer.animatePosition(from: offset, to: CGPoint(), duration: duration, delay: delay, timingFunction: curve.timingFunction, mediaTimingFunction: curve.mediaTimingFunction, removeOnCompletion: removeOnCompletion, additive: true, completion: { _ in
completion?()
})
}
@ -948,7 +948,7 @@ public extension ContainedViewLayoutTransition {
}
}
func animateTransformScale(node: ASDisplayNode, from fromScale: CGPoint, completion: ((Bool) -> Void)? = nil) {
func animateTransformScale(node: ASDisplayNode, from fromScale: CGPoint, delay: Double = 0.0, completion: ((Bool) -> Void)? = nil) {
switch self {
case .immediate:
if let completion = completion {
@ -961,12 +961,12 @@ public extension ContainedViewLayoutTransition {
calculatedFrom = fromScale
calculatedTo = CGPoint(x: 1.0, y: 1.0)
node.layer.animateScaleX(from: calculatedFrom.x, to: calculatedTo.x, duration: duration, timingFunction: curve.timingFunction, mediaTimingFunction: curve.mediaTimingFunction, completion: { result in
node.layer.animateScaleX(from: calculatedFrom.x, to: calculatedTo.x, duration: duration, delay: delay, timingFunction: curve.timingFunction, mediaTimingFunction: curve.mediaTimingFunction, completion: { result in
if let completion = completion {
completion(result)
}
})
node.layer.animateScaleY(from: calculatedFrom.y, to: calculatedTo.y, duration: duration, timingFunction: curve.timingFunction, mediaTimingFunction: curve.mediaTimingFunction)
node.layer.animateScaleY(from: calculatedFrom.y, to: calculatedTo.y, duration: duration, delay: delay, timingFunction: curve.timingFunction, mediaTimingFunction: curve.mediaTimingFunction)
}
}

View File

@ -947,6 +947,7 @@ public final class MessageHistoryView {
public let topTaggedMessages: [Message]
public let additionalData: [AdditionalMessageHistoryViewDataEntry]
public let isLoading: Bool
public let isLoadingEarlier: Bool
public let isAddedToChatList: Bool
public init(tagMask: MessageTags?, namespaces: MessageIdNamespaces, entries: [MessageHistoryEntry], holeEarlier: Bool, holeLater: Bool, isLoading: Bool) {
@ -964,6 +965,7 @@ public final class MessageHistoryView {
self.topTaggedMessages = []
self.additionalData = []
self.isLoading = isLoading
self.isLoadingEarlier = true
self.isAddedToChatList = false
}
@ -981,8 +983,10 @@ public final class MessageHistoryView {
self.holeLater = true
self.earlierId = nil
self.laterId = nil
self.isLoadingEarlier = true
case let .loaded(state):
var isLoading = false
var isLoadingEarlier = false
switch state.anchor {
case .lowerBound:
self.anchorIndex = .lowerBound
@ -996,6 +1000,10 @@ public final class MessageHistoryView {
if state.entries.isEmpty && state.hole != nil {
isLoading = true
}
if state.entries.count <= 1 && state.hole != nil {
isLoadingEarlier = true
}
self.isLoadingEarlier = isLoadingEarlier
entries = []
if let transientReadStates = mutableView.transientReadStates, case let .peer(states) = transientReadStates {
for entry in state.entries {
@ -1199,6 +1207,7 @@ public final class MessageHistoryView {
self.topTaggedMessages = base.topTaggedMessages
self.additionalData = base.additionalData
self.isLoading = base.isLoading
self.isLoadingEarlier = base.isLoadingEarlier
self.isAddedToChatList = base.isAddedToChatList
if let combinedReadStates = combinedReadStates {

View File

@ -5866,6 +5866,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
}
}
strongSelf.chatDisplayNode.loadingPlaceholderNode?.addContentOffset(offset: offset, transition: transition)
strongSelf.chatDisplayNode.messageTransitionNode.addExternalOffset(offset: offset, transition: transition, itemNode: itemNode)
}

View File

@ -92,7 +92,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
var blurredHistoryNode: ASImageNode?
let historyNodeContainer: ASDisplayNode
let loadingNode: ChatLoadingNode
private var loadingPlaceholderNode: ChatLoadingPlaceholderNode
private(set) var loadingPlaceholderNode: ChatLoadingPlaceholderNode?
private var emptyNode: ChatEmptyNode?
private(set) var emptyType: ChatHistoryNodeLoadState.EmptyType?
@ -222,49 +222,66 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
private var isLoadingValue: Bool = false
private func updateIsLoading(isLoading: Bool, animated: Bool) {
loadingPlaceholderNode.isHidden = true
let useLoadingPlaceholder = self.chatLocation.peerId?.namespace != Namespaces.Peer.CloudUser
if isLoading != self.isLoadingValue {
self.isLoadingValue = isLoading
if isLoading {
// self.historyNodeContainer.supernode?.insertSubnode(self.loadingNode, belowSubnode: self.historyNodeContainer)
self.loadingNode.isHidden = false
self.loadingNode.layer.removeAllAnimations()
self.loadingNode.alpha = 1.0
if animated {
self.loadingNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
}
self.loadingPlaceholderNode.alpha = 1.0
self.loadingPlaceholderNode.isHidden = false
self.historyNode.alpha = 0.0
} else {
self.loadingPlaceholderNode.alpha = 0.0
self.loadingPlaceholderNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, completion: { [weak self] completed in
if let strongSelf = self {
strongSelf.loadingPlaceholderNode.layer.removeAllAnimations()
if completed {
strongSelf.loadingPlaceholderNode.isHidden = true
if useLoadingPlaceholder {
let loadingPlaceholderNode: ChatLoadingPlaceholderNode
if let current = self.loadingPlaceholderNode {
loadingPlaceholderNode = current
} else {
loadingPlaceholderNode = ChatLoadingPlaceholderNode(theme: self.chatPresentationInterfaceState.theme, chatWallpaper: self.chatPresentationInterfaceState.chatWallpaper, bubbleCorners: self.chatPresentationInterfaceState.bubbleCorners, backgroundNode: self.backgroundNode)
loadingPlaceholderNode.updatePresentationInterfaceState(self.chatPresentationInterfaceState)
self.contentContainerNode.insertSubnode(loadingPlaceholderNode, aboveSubnode: self.backgroundNode)
self.loadingPlaceholderNode = loadingPlaceholderNode
loadingPlaceholderNode.setup(self.historyNode)
if let (layout, navigationHeight) = self.validLayout {
self.containerLayoutUpdated(layout, navigationBarHeight: navigationHeight, transition: .immediate, listViewTransaction: { _, _, _, _ in
}, updateExtraNavigationBarBackgroundHeight: { _, _ in
})
}
}
})
self.loadingPlaceholderNode.animateOut(self.historyNode)
self.historyNode.alpha = 1.0
self.historyNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
self.loadingNode.alpha = 0.0
if animated {
self.loadingNode.layer.animateScale(from: 1.0, to: 0.1, duration: 0.3, removeOnCompletion: false)
self.loadingNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, completion: { [weak self] completed in
if let strongSelf = self {
strongSelf.loadingNode.layer.removeAllAnimations()
if completed {
strongSelf.loadingNode.isHidden = true
}
}
})
loadingPlaceholderNode.alpha = 1.0
loadingPlaceholderNode.isHidden = false
} else {
self.loadingNode.isHidden = true
self.historyNodeContainer.supernode?.insertSubnode(self.loadingNode, belowSubnode: self.historyNodeContainer)
self.loadingNode.isHidden = false
self.loadingNode.layer.removeAllAnimations()
self.loadingNode.alpha = 1.0
if animated {
self.loadingNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
}
}
} else {
if useLoadingPlaceholder {
if let loadingPlaceholderNode = self.loadingPlaceholderNode {
loadingPlaceholderNode.animateOut(self.historyNode, completion: { [weak self] in
if let strongSelf = self {
strongSelf.loadingPlaceholderNode?.removeFromSupernode()
strongSelf.loadingPlaceholderNode = nil
}
})
}
} else {
self.loadingNode.alpha = 0.0
if animated {
self.loadingNode.layer.animateScale(from: 1.0, to: 0.1, duration: 0.3, removeOnCompletion: false)
self.loadingNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, completion: { [weak self] completed in
if let strongSelf = self {
strongSelf.loadingNode.layer.removeAllAnimations()
if completed {
strongSelf.loadingNode.isHidden = true
}
}
})
} else {
self.loadingNode.isHidden = true
}
}
}
}
@ -414,9 +431,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
})
self.loadingNode = ChatLoadingNode(theme: self.chatPresentationInterfaceState.theme, chatWallpaper: self.chatPresentationInterfaceState.chatWallpaper, bubbleCorners: self.chatPresentationInterfaceState.bubbleCorners)
self.loadingPlaceholderNode = ChatLoadingPlaceholderNode(theme: self.chatPresentationInterfaceState.theme, chatWallpaper: self.chatPresentationInterfaceState.chatWallpaper, bubbleCorners: self.chatPresentationInterfaceState.bubbleCorners, backgroundNode: self.backgroundNode, hasAvatar: chatLocation.peerId?.namespace != Namespaces.Peer.CloudUser)
self.inputPanelContainerNode = ChatInputPanelContainer()
self.inputPanelOverlayNode = SparseNode()
self.inputPanelClippingNode = SparseNode()
@ -490,13 +505,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
}
assert(Queue.mainQueue().isCurrent())
// self.updateIsLoading(isLoading: true, animated: false)
//
// Queue.mainQueue().after(1.0) {
// self.updateIsLoading(isLoading: false, animated: true)
// }
self.historyNode.setLoadStateUpdated { [weak self] loadState, animated in
if let strongSelf = self {
let wasLoading = strongSelf.isLoadingValue
@ -561,12 +570,19 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
self.addSubnode(self.contentContainerNode)
self.contentContainerNode.addSubnode(self.backgroundNode)
self.contentContainerNode.addSubnode(self.loadingPlaceholderNode)
self.contentContainerNode.addSubnode(self.historyNodeContainer)
if let navigationBar = self.navigationBar {
self.contentContainerNode.addSubnode(navigationBar)
}
// Queue.mainQueue().after(0.2) {
// self.updateIsLoading(isLoading: true, animated: false)
// }
//
// Queue.mainQueue().after(3.0) {
// self.updateIsLoading(isLoading: false, animated: true)
// }
self.inputPanelContainerNode.expansionUpdated = { [weak self] transition in
guard let strongSelf = self else {
@ -1417,7 +1433,9 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
//transition.updateFrame(node: self.historyScrollingArea, frame: contentBounds)
transition.updateFrame(node: self.loadingNode, frame: contentBounds)
transition.updateFrame(node: self.loadingPlaceholderNode, frame: contentBounds)
if let loadingPlaceholderNode = self.loadingPlaceholderNode {
transition.updateFrame(node: loadingPlaceholderNode, frame: contentBounds)
}
if let restrictedNode = self.restrictedNode {
transition.updateFrame(node: restrictedNode, frame: contentBounds)
@ -1575,8 +1593,10 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
self.visibleAreaInset = visibleAreaInset
self.loadingNode.updateLayout(size: contentBounds.size, insets: visibleAreaInset, transition: transition)
self.loadingPlaceholderNode.updateLayout(size: contentBounds.size, insets: visibleAreaInset, transition: transition)
self.loadingPlaceholderNode.update(rect: contentBounds, within: contentBounds.size, transition: transition)
if let loadingPlaceholderNode = self.loadingPlaceholderNode {
loadingPlaceholderNode.updateLayout(size: contentBounds.size, insets: visibleAreaInset, transition: transition)
loadingPlaceholderNode.update(rect: contentBounds, within: contentBounds.size, transition: transition)
}
if let containerNode = self.containerNode {
contentBottomInset += 8.0
@ -2210,6 +2230,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
self.backgroundNode.update(wallpaper: chatPresentationInterfaceState.chatWallpaper)
self.historyNode.verticalScrollIndicatorColor = UIColor(white: 0.5, alpha: 0.8)
self.loadingPlaceholderNode?.updatePresentationInterfaceState(chatPresentationInterfaceState)
var updatedInputFocus = self.chatPresentationInterfaceStateRequiresInputFocus(self.chatPresentationInterfaceState) != self.chatPresentationInterfaceStateRequiresInputFocus(chatPresentationInterfaceState)
if self.chatPresentationInterfaceStateInputView(self.chatPresentationInterfaceState) !== self.chatPresentationInterfaceStateInputView(chatPresentationInterfaceState) {

View File

@ -2559,7 +2559,11 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
loadState = .empty(emptyType)
}
} else {
loadState = .messages
if historyView.originalView.isLoadingEarlier && strongSelf.chatLocation.peerId?.namespace != Namespaces.Peer.CloudUser {
loadState = .loading
} else {
loadState = .messages
}
}
} else {
loadState = .loading
@ -2639,12 +2643,12 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
strongSelf._buttonKeyboardMessage.set(.single(transition.keyboardButtonsMessage))
}
if transition.animateIn || animateIn {
if (transition.animateIn || animateIn) && !"".isEmpty {
let heightNorm = strongSelf.bounds.height - strongSelf.insets.top
strongSelf.forEachVisibleItemNode { itemNode in
let delayFactor = itemNode.frame.minY / heightNorm
let delay = Double(delayFactor * 0.1)
if let itemNode = itemNode as? ChatMessageItemView {
itemNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15, delay: delay)
itemNode.layer.animateScale(from: 0.94, to: 1.0, duration: 0.4, delay: delay, timingFunction: kCAMediaTimingFunctionSpring)
@ -2657,7 +2661,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
strongSelf.forEachItemHeaderNode { itemNode in
let delayFactor = itemNode.frame.minY / heightNorm
let delay = Double(delayFactor * 0.2)
itemNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15, delay: delay)
itemNode.layer.animateScale(from: 0.94, to: 1.0, duration: 0.4, delay: delay, timingFunction: kCAMediaTimingFunctionSpring)
}

View File

@ -7,6 +7,7 @@ import TelegramPresentationData
import ActivityIndicator
import WallpaperBackgroundNode
import ShimmerEffect
import ChatPresentationInterfaceState
final class ChatLoadingNode: ASDisplayNode {
private let backgroundNode: NavigationBackgroundNode
@ -47,10 +48,12 @@ final class ChatLoadingNode: ASDisplayNode {
}
private let avatarSize = CGSize(width: 38.0, height: 38.0)
private let avatarImage = generateFilledCircleImage(diameter: avatarSize.width, color: .white)
private let avatarBorderImage = generateCircleImage(diameter: avatarSize.width, lineWidth: 1.0 - UIScreenPixel, color: .white)
final class ChatLoadingPlaceholderMessageContainer {
let avatarNode: ASImageNode?
let avatarBorderNode: ASImageNode?
var avatarNode: ASImageNode?
var avatarBorderNode: ASImageNode?
let bubbleNode: ASImageNode
let bubbleBorderNode: ASImageNode
@ -63,20 +66,7 @@ final class ChatLoadingPlaceholderMessageContainer {
return self.bubbleNode.frame
}
init(hasAvatar: Bool, bubbleImage: UIImage?, bubbleBorderImage: UIImage?) {
if hasAvatar {
self.avatarNode = ASImageNode()
self.avatarNode?.displaysAsynchronously = false
self.avatarNode?.image = generateFilledCircleImage(diameter: avatarSize.width, color: .white)
self.avatarBorderNode = ASImageNode()
self.avatarBorderNode?.displaysAsynchronously = false
self.avatarBorderNode?.image = generateCircleImage(diameter: avatarSize.width, lineWidth: 1.0 - UIScreenPixel, color: .red)
} else {
self.avatarNode = nil
self.avatarBorderNode = nil
}
init(bubbleImage: UIImage?, bubbleBorderImage: UIImage?) {
self.bubbleNode = ASImageNode()
self.bubbleNode.displaysAsynchronously = false
self.bubbleNode.image = bubbleImage
@ -87,38 +77,59 @@ final class ChatLoadingPlaceholderMessageContainer {
}
func setup(maskNode: ASDisplayNode, borderMaskNode: ASDisplayNode) {
if let avatarNode = self.avatarNode, let avatarBorderNode = self.avatarBorderNode {
maskNode.addSubnode(avatarNode)
borderMaskNode.addSubnode(avatarBorderNode)
}
maskNode.addSubnode(self.bubbleNode)
borderMaskNode.addSubnode(self.bubbleBorderNode)
}
func animateWith(_ listItemNode: ListViewItemNode, transition: ContainedViewLayoutTransition) {
func animateWith(_ listItemNode: ListViewItemNode, delay: Double, transition: ContainedViewLayoutTransition) {
listItemNode.allowsGroupOpacity = true
listItemNode.alpha = 1.0
listItemNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2, delay: delay, completion: { _ in
listItemNode.allowsGroupOpacity = false
})
if let bubbleItemNode = listItemNode as? ChatMessageBubbleItemNode {
bubbleItemNode.animateFromLoadingPlaceholder(messageContainer: self, transition: transition)
bubbleItemNode.animateFromLoadingPlaceholder(messageContainer: self, delay: delay, transition: transition)
} else if let stickerItemNode = listItemNode as? ChatMessageStickerItemNode {
stickerItemNode.animateFromLoadingPlaceholder(messageContainer: self, delay: delay, transition: transition)
} else if let stickerItemNode = listItemNode as? ChatMessageAnimatedStickerItemNode {
stickerItemNode.animateFromLoadingPlaceholder(messageContainer: self, transition: transition)
stickerItemNode.animateFromLoadingPlaceholder(messageContainer: self, delay: delay, transition: transition)
} else if let videoItemNode = listItemNode as? ChatMessageInstantVideoItemNode {
videoItemNode.animateFromLoadingPlaceholder(messageContainer: self, delay: delay, transition: transition)
}
}
func animateBackgroundFrame(to frame: CGRect, transition: ContainedViewLayoutTransition) {
let targetFrame = CGRect(origin: CGPoint(x: self.bubbleNode.frame.minX, y: frame.minY), size: frame.size)
transition.updateFrame(node: self.bubbleNode, frame: targetFrame)
transition.updateFrame(node: self.bubbleBorderNode, frame: targetFrame)
if let avatarNode = self.avatarNode, let avatarBorderNode = self.avatarBorderNode {
let avatarFrame = CGRect(origin: CGPoint(x: 3.0, y: frame.maxY + 1.0 - avatarSize.height), size: avatarSize)
transition.updateFrame(node: avatarNode, frame: avatarFrame)
transition.updateFrame(node: avatarBorderNode, frame: avatarFrame)
}
// let targetFrame = CGRect(origin: CGPoint(x: self.bubbleNode.frame.minX, y: frame.minY), size: frame.size)
//
// transition.updateFrame(node: self.bubbleNode, frame: targetFrame)
// transition.updateFrame(node: self.bubbleBorderNode, frame: targetFrame)
//
// if let avatarNode = self.avatarNode, let avatarBorderNode = self.avatarBorderNode {
// let avatarFrame = CGRect(origin: CGPoint(x: 3.0, y: frame.maxY + 1.0 - avatarSize.height), size: avatarSize)
//
// transition.updateFrame(node: avatarNode, frame: avatarFrame)
// transition.updateFrame(node: avatarBorderNode, frame: avatarFrame)
// }
}
func update(size: CGSize, rect: CGRect, transition: ContainedViewLayoutTransition) {
func update(size: CGSize, hasAvatar: Bool, rect: CGRect, transition: ContainedViewLayoutTransition) {
var avatarOffset: CGFloat = 0.0
if hasAvatar && self.avatarNode == nil {
let avatarNode = ASImageNode()
avatarNode.displaysAsynchronously = false
avatarNode.image = avatarImage
self.bubbleNode.supernode?.addSubnode(avatarNode)
self.avatarNode = avatarNode
let avatarBorderNode = ASImageNode()
avatarBorderNode.displaysAsynchronously = false
avatarBorderNode.image = avatarBorderImage
self.bubbleBorderNode.supernode?.addSubnode(avatarBorderNode)
self.avatarBorderNode = avatarBorderNode
}
if let avatarNode = self.avatarNode, let avatarBorderNode = self.avatarBorderNode {
let avatarFrame = CGRect(origin: CGPoint(x: 3.0, y: rect.maxY + 1.0 - avatarSize.height), size: avatarSize)
@ -140,6 +151,7 @@ final class ChatLoadingPlaceholderNode: ASDisplayNode {
private let maskNode: ASDisplayNode
private let borderMaskNode: ASDisplayNode
private let scrollingContainer: ASDisplayNode
private let containerNode: ASDisplayNode
private var backgroundContent: WallpaperBubbleBackgroundNode?
private let backgroundColorNode: ASDisplayNode
@ -154,8 +166,10 @@ final class ChatLoadingPlaceholderNode: ASDisplayNode {
private var validLayout: (CGSize, UIEdgeInsets)?
init(theme: PresentationTheme, chatWallpaper: TelegramWallpaper, bubbleCorners: PresentationChatBubbleCorners, backgroundNode: WallpaperBackgroundNode, hasAvatar: Bool) {
init(theme: PresentationTheme, chatWallpaper: TelegramWallpaper, bubbleCorners: PresentationChatBubbleCorners, backgroundNode: WallpaperBackgroundNode) {
self.backgroundNode = backgroundNode
self.scrollingContainer = ASDisplayNode()
self.maskNode = ASDisplayNode()
self.borderMaskNode = ASDisplayNode()
@ -164,7 +178,7 @@ final class ChatLoadingPlaceholderNode: ASDisplayNode {
var messageContainers: [ChatLoadingPlaceholderMessageContainer] = []
for _ in 0 ..< 8 {
let container = ChatLoadingPlaceholderMessageContainer(hasAvatar: hasAvatar, bubbleImage: bubbleImage, bubbleBorderImage: bubbleBorderImage)
let container = ChatLoadingPlaceholderMessageContainer(bubbleImage: bubbleImage, bubbleBorderImage: bubbleBorderImage)
container.setup(maskNode: self.maskNode, borderMaskNode: self.borderMaskNode)
messageContainers.append(container)
}
@ -184,14 +198,14 @@ final class ChatLoadingPlaceholderNode: ASDisplayNode {
super.init()
self.addSubnode(self.containerNode)
self.addSubnode(self.scrollingContainer)
self.scrollingContainer.addSubnode(self.containerNode)
self.containerNode.addSubnode(self.backgroundColorNode)
self.containerNode.addSubnode(self.effectNode)
self.addSubnode(self.borderNode)
self.scrollingContainer.addSubnode(self.borderNode)
self.borderNode.addSubnode(self.borderEffectNode)
// self.addSubnode(self.maskNode)
// self.addSubnode(self.borderMaskNode)
}
override func didLoad() {
@ -200,73 +214,174 @@ final class ChatLoadingPlaceholderNode: ASDisplayNode {
self.containerNode.view.mask = self.maskNode.view
self.borderNode.view.mask = self.borderMaskNode.view
// self.backgroundNode?.updateIsLooping(true)
self.backgroundNode?.updateIsLooping(true)
}
func animateOut(_ historyNode: ChatHistoryNode) {
private var bottomInset: (Int, CGFloat)?
func setup(_ historyNode: ChatHistoryNode) {
guard let listNode = historyNode as? ListView else {
return
}
var count = 0
var inset: CGFloat = 0.0
listNode.forEachVisibleItemNode { itemNode in
inset += itemNode.frame.height
count += 1
}
if count > 0 {
self.bottomInset = (count, inset)
}
self.scrollingContainer.bounds = self.scrollingContainer.bounds.offsetBy(dx: 0.0, dy: inset)
}
func animateOut(_ historyNode: ChatHistoryNode, completion: @escaping () -> Void = {}) {
guard let listNode = historyNode as? ListView, let (size, _) = self.validLayout else {
return
}
self.backgroundNode?.updateIsLooping(false)
let transition = ContainedViewLayoutTransition.animated(duration: 0.3, curve: .easeInOut)
let transition = ContainedViewLayoutTransition.animated(duration: 0.3, curve: .spring)
var lastFrame: CGRect?
var i = 0
let heightNorm = listNode.bounds.height - listNode.insets.top
var index = 0
var skipCount = self.bottomInset?.0 ?? 0
listNode.forEachVisibleItemNode { itemNode in
guard i < self.messageContainers.count, let listItemNode = itemNode as? ListViewItemNode else {
guard index < self.messageContainers.count, let listItemNode = itemNode as? ListViewItemNode else {
return
}
let delayFactor = listItemNode.frame.minY / heightNorm
let delay = Double(delayFactor * 0.1)
if skipCount > 0 {
skipCount -= 1
return
}
if let bubbleItemNode = itemNode as? ChatMessageBubbleItemNode,
bubbleItemNode.contentNodes.contains(where: { $0 is ChatMessageActionBubbleContentNode }) {
if let itemNode = itemNode as? ChatUnreadItemNode {
itemNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15, delay: 0.0)
return
}
if let itemNode = itemNode as? ChatReplyCountItemNode {
itemNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15, delay: 0.0)
return
}
let messageContainer = self.messageContainers[i]
messageContainer.animateWith(listItemNode, transition: transition)
if let bubbleItemNode = listItemNode as? ChatMessageBubbleItemNode, bubbleItemNode.contentNodes.contains(where: { $0 is ChatMessageActionBubbleContentNode }) {
return
}
let messageContainer = self.messageContainers[index]
messageContainer.animateWith(listItemNode, delay: delay, transition: transition)
lastFrame = messageContainer.frame
i += 1
index += 1
}
if let lastFrame = lastFrame, i < self.messageContainers.count {
skipCount = self.bottomInset?.0 ?? 0
listNode.forEachItemHeaderNode { itemNode in
var animateScale = true
if itemNode is ChatMessageAvatarHeaderNode {
animateScale = false
if skipCount > 0 {
return
}
}
if itemNode is ChatMessageDateHeaderNode {
if skipCount > 0 {
skipCount -= 1
return
}
}
let delayFactor = itemNode.frame.minY / heightNorm
let delay = Double(delayFactor * 0.2)
itemNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15, delay: delay)
if animateScale {
itemNode.layer.animateScale(from: 0.94, to: 1.0, duration: 0.4, delay: delay, timingFunction: kCAMediaTimingFunctionSpring)
}
}
self.alpha = 0.0
self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false, completion: { _ in
completion()
})
if let lastFrame = lastFrame, index < self.messageContainers.count {
var offset = lastFrame.minY
for k in i ..< self.messageContainers.count {
for k in index ..< self.messageContainers.count {
let messageContainer = self.messageContainers[k]
let messageSize = messageContainer.frame.size
messageContainer.update(size: size, rect: CGRect(origin: CGPoint(x: 0.0, y: offset - messageSize.height), size: messageSize), transition: transition)
messageContainer.update(size: size, hasAvatar: self.isGroup, rect: CGRect(origin: CGPoint(x: 0.0, y: offset - messageSize.height), size: messageSize), transition: transition)
offset -= messageSize.height
}
}
}
public func update(rect: CGRect, within containerSize: CGSize, transition: ContainedViewLayoutTransition = .immediate) {
private var ignoreFirstContentOffset = true
func addContentOffset(offset: CGFloat, transition: ContainedViewLayoutTransition) {
guard !self.ignoreFirstContentOffset else {
self.ignoreFirstContentOffset = false
return
}
self.scrollingContainer.bounds = self.scrollingContainer.bounds.offsetBy(dx: 0.0, dy: -offset)
transition.animateOffsetAdditive(node: self.scrollingContainer, offset: offset)
if let (rect, containerSize) = self.absolutePosition {
self.update(rect: rect, within: containerSize)
}
}
func update(rect: CGRect, within containerSize: CGSize, transition: ContainedViewLayoutTransition = .immediate) {
self.absolutePosition = (rect, containerSize)
if let backgroundContent = self.backgroundContent {
var backgroundFrame = backgroundContent.frame
backgroundFrame.origin.x += rect.minX
backgroundFrame.origin.y += rect.minY
backgroundFrame.origin.y += rect.minY - self.scrollingContainer.bounds.minY
backgroundContent.update(rect: backgroundFrame, within: containerSize, transition: transition)
}
}
private var isGroup = false
func updatePresentationInterfaceState(_ chatPresentationInterfaceState: ChatPresentationInterfaceState) {
var isGroup = false
if let peer = chatPresentationInterfaceState.renderedPeer?.peer {
if peer is TelegramGroup {
isGroup = true
} else if let channel = peer as? TelegramChannel, case .group = channel.info {
isGroup = true
}
}
if self.isGroup != isGroup {
self.isGroup = isGroup
if let (size, insets) = self.validLayout {
self.updateLayout(size: size, insets: insets, transition: .immediate)
}
}
}
func updateLayout(size: CGSize, insets: UIEdgeInsets, transition: ContainedViewLayoutTransition) {
self.validLayout = (size, insets)
let bounds = CGRect(origin: .zero, size: size)
transition.updateFrame(node: self.scrollingContainer, frame: bounds)
transition.updateFrame(node: self.maskNode, frame: bounds)
transition.updateFrame(node: self.borderMaskNode, frame: bounds)
transition.updateFrame(node: self.containerNode, frame: bounds)
transition.updateFrame(node: self.borderNode, frame: bounds)
transition.updateFrame(node: self.backgroundColorNode, frame: bounds)
transition.updateFrame(node: self.effectNode, frame: bounds)
transition.updateFrame(node: self.borderEffectNode, frame: bounds)
@ -291,15 +406,19 @@ final class ChatLoadingPlaceholderNode: ASDisplayNode {
]
var offset: CGFloat = 5.0
var i = 0
for messageContainer in self.messageContainers {
let messageSize = dimensions[i % 12]
messageContainer.update(size: size, rect: CGRect(origin: CGPoint(x: 0.0, y: size.height - insets.bottom - offset - messageSize.height), size: messageSize), transition: transition)
offset += messageSize.height
i += 1
var index = 0
if let (insetCount, _) = self.bottomInset {
index += insetCount
}
if backgroundNode?.hasExtraBubbleBackground() == true {
for messageContainer in self.messageContainers {
let messageSize = dimensions[index % 8]
messageContainer.update(size: size, hasAvatar: self.isGroup, rect: CGRect(origin: CGPoint(x: 0.0, y: size.height - insets.bottom - offset - messageSize.height), size: messageSize), transition: transition)
offset += messageSize.height
index += 1
}
if self.backgroundNode?.hasExtraBubbleBackground() == true {
self.backgroundColorNode.isHidden = true
} else {
self.backgroundColorNode.isHidden = false
@ -318,10 +437,7 @@ final class ChatLoadingPlaceholderNode: ASDisplayNode {
if let backgroundContent = self.backgroundContent {
transition.updateFrame(node: backgroundContent, frame: bounds)
if let (rect, containerSize) = self.absolutePosition {
var backgroundFrame = backgroundContent.frame
backgroundFrame.origin.x += rect.minX
backgroundFrame.origin.y += rect.minY
backgroundContent.update(rect: backgroundFrame, within: containerSize, transition: .immediate)
self.update(rect: rect, within: containerSize)
}
}
}

View File

@ -2673,40 +2673,14 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
}
}
func animateFromLoadingPlaceholder(messageContainer: ChatLoadingPlaceholderMessageContainer, transition: ContainedViewLayoutTransition) {
guard let _ = self.item, let animationNode = self.animationNode else {
func animateFromLoadingPlaceholder(messageContainer: ChatLoadingPlaceholderMessageContainer, delay: Double, transition: ContainedViewLayoutTransition) {
guard let item = self.item else {
return
}
let targetFrame = self.view.convert(animationNode.frame, to: messageContainer.parentView)
messageContainer.animateBackgroundFrame(to: targetFrame, transition: transition)
var backgroundFrame = messageContainer.frame
backgroundFrame.size.width = backgroundFrame.size.height
// let localSourceContentFrame = self.contextSourceNode.contentNode.view.convert(backgroundFrame.offsetBy(dx: self.contextSourceNode.contentRect.minX, dy: self.contextSourceNode.contentRect.minY), to: self.contextSourceNode.contentNode.view)
// let sourceCenter = CGPoint(
// x: localSourceContentFrame.minX + 11.2,
// y: localSourceContentFrame.midY - 1.8
// )
let sourceScale: CGFloat = 36.0 / self.imageNode.frame.height
let offset = CGPoint(x: -90.0, y: 90.0)
// let offset = CGPoint(
// x: sourceCenter.x - self.imageNode.frame.midX,
// y: sourceCenter.y - self.imageNode.frame.midY
// )
transition.animatePositionAdditive(layer: self.imageNode.layer, offset: offset)
transition.animateTransformScale(node: self.imageNode, from: sourceScale)
if let animationNode = self.animationNode {
transition.animatePositionAdditive(layer: animationNode.layer, offset: offset)
transition.animateTransformScale(node: animationNode, from: sourceScale)
}
transition.animatePositionAdditive(layer: self.placeholderNode.layer, offset: offset)
transition.animateTransformScale(node: self.placeholderNode, from: sourceScale)
let incoming = item.message.effectivelyIncoming(item.context.account.peerId)
transition.animatePositionAdditive(node: self, offset: CGPoint(x: incoming ? 30.0 : -30.0, y: -30.0), delay: delay)
transition.animateTransformScale(node: self, from: CGPoint(x: 0.85, y: 0.85), delay: delay)
}
override func openMessageContextMenu() {

View File

@ -797,46 +797,14 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
}
}
func animateFromLoadingPlaceholder(messageContainer: ChatLoadingPlaceholderMessageContainer, transition: ContainedViewLayoutTransition) {
guard let _ = self.item else {
func animateFromLoadingPlaceholder(messageContainer: ChatLoadingPlaceholderMessageContainer, delay: Double, transition: ContainedViewLayoutTransition) {
guard let item = self.item else {
return
}
let targetFrame = self.view.convert(self.backgroundNode.frame, to: messageContainer.parentView)
messageContainer.animateBackgroundFrame(to: targetFrame, transition: transition)
// let backgroundFrame = messageContainer.frame
//// let widthDifference = self.backgroundNode.frame.width - backgroundFrame.width
//// let heightDifference = self.backgroundNode.frame.height - backgroundFrame.height
//
// if let type = self.backgroundNode.type {
// if case .none = type {
// } else {
// self.clippingNode.clipsToBounds = true
// }
// }
// transition.animateFrame(layer: self.clippingNode.layer, from: CGRect(origin: CGPoint(x: self.clippingNode.frame.minX, y: backgroundFrame.minY), size: backgroundFrame.size), completion: { [weak self] _ in
// guard let strongSelf = self else {
// return
// }
// strongSelf.clippingNode.clipsToBounds = false
// })
// transition.animateOffsetAdditive(layer: self.clippingNode.layer, offset: backgroundFrame.minY - self.clippingNode.frame.minY)
//
// let combinedTransition = CombinedTransition(horizontal: transition, vertical: transition)
//
// self.backgroundWallpaperNode.animateFrom(sourceView: messageContainer.bubbleNode.view, mediaBox: item.context.account.postbox.mediaBox, transition: combinedTransition)
// self.backgroundNode.animateFrom(sourceView: messageContainer.bubbleNode.view, transition: combinedTransition)
// for contentNode in self.contentNodes {
// if let contentNode = contentNode as? ChatMessageTextBubbleContentNode {
// let localSourceContentFrame = self.mainContextSourceNode.contentNode.view.convert(textInput.contentView.frame.offsetBy(dx: self.mainContextSourceNode.contentRect.minX, dy: self.mainContextSourceNode.contentRect.minY), to: contentNode.view)
// textInput.contentView.frame = localSourceContentFrame
// contentNode.animateFrom(sourceView: textInput.contentView, scrollOffset: textInput.scrollOffset, widthDifference: widthDifference, transition: transition)
// } else if let contentNode = contentNode as? ChatMessageWebpageBubbleContentNode {
// transition.animatePositionAdditive(node: contentNode, offset: CGPoint(x: 0.0, y: heightDifference))
// }
// }
let incoming = item.message.effectivelyIncoming(item.context.account.peerId)
transition.animatePositionAdditive(node: self, offset: CGPoint(x: incoming ? 30.0 : -30.0, y: -30.0), delay: delay)
transition.animateTransformScale(node: self, from: CGPoint(x: 0.85, y: 0.85), delay: delay)
}
func animateContentFromTextInputField(textInput: ChatMessageTransitionNode.Source.TextInput, transition: CombinedTransition) {

View File

@ -1165,6 +1165,16 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureRecognizerD
self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
}
func animateFromLoadingPlaceholder(messageContainer: ChatLoadingPlaceholderMessageContainer, delay: Double, transition: ContainedViewLayoutTransition) {
guard let item = self.item else {
return
}
let incoming = item.message.effectivelyIncoming(item.context.account.peerId)
transition.animatePositionAdditive(node: self, offset: CGPoint(x: incoming ? 30.0 : -30.0, y: -30.0), delay: delay)
transition.animateTransformScale(node: self, from: CGPoint(x: 0.85, y: 0.85), delay: delay)
}
func animateFromSnapshot(snapshotView: UIView, transition: CombinedTransition) {
snapshotView.frame = self.interactiveVideoNode.view.convert(snapshotView.frame, from: self.contextSourceNode.contentNode.view)
self.interactiveVideoNode.animateFromSnapshot(snapshotView: snapshotView, transition: transition)

View File

@ -1641,6 +1641,16 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
}
}
func animateFromLoadingPlaceholder(messageContainer: ChatLoadingPlaceholderMessageContainer, delay: Double, transition: ContainedViewLayoutTransition) {
guard let item = self.item else {
return
}
let incoming = item.message.effectivelyIncoming(item.context.account.peerId)
transition.animatePositionAdditive(node: self, offset: CGPoint(x: incoming ? 30.0 : -30.0, y: -30.0), delay: delay)
transition.animateTransformScale(node: self, from: CGPoint(x: 0.85, y: 0.85), delay: delay)
}
override func openMessageContextMenu() {
guard let item = self.item else {
return