mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-22 14:20:20 +00:00
Reaction improvements
This commit is contained in:
@@ -393,16 +393,16 @@ public extension ContainedViewLayoutTransition {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func animatePositionWithKeyframes(node: ASDisplayNode, keyframes: [AnyObject], removeOnCompletion: Bool = true, additive: Bool = false, completion: ((Bool) -> Void)? = nil) {
|
func animatePositionWithKeyframes(node: ASDisplayNode, keyframes: [CGPoint], removeOnCompletion: Bool = true, additive: Bool = false, completion: ((Bool) -> Void)? = nil) {
|
||||||
self.animatePositionWithKeyframes(layer: node.layer, keyframes: keyframes, removeOnCompletion: removeOnCompletion, additive: additive, completion: completion)
|
self.animatePositionWithKeyframes(layer: node.layer, keyframes: keyframes, removeOnCompletion: removeOnCompletion, additive: additive, completion: completion)
|
||||||
}
|
}
|
||||||
|
|
||||||
func animatePositionWithKeyframes(layer: CALayer, keyframes: [AnyObject], removeOnCompletion: Bool = true, additive: Bool = false, completion: ((Bool) -> Void)? = nil) {
|
func animatePositionWithKeyframes(layer: CALayer, keyframes: [CGPoint], removeOnCompletion: Bool = true, additive: Bool = false, completion: ((Bool) -> Void)? = nil) {
|
||||||
switch self {
|
switch self {
|
||||||
case .immediate:
|
case .immediate:
|
||||||
completion?(true)
|
completion?(true)
|
||||||
case let .animated(duration, curve):
|
case let .animated(duration, curve):
|
||||||
layer.animateKeyframes(values: keyframes, duration: duration, keyPath: "position", timingFunction: curve.timingFunction, mediaTimingFunction: curve.mediaTimingFunction, removeOnCompletion: removeOnCompletion, completion: { value in
|
layer.animateKeyframes(values: keyframes.map(NSValue.init(cgPoint:)), duration: duration, keyPath: "position", timingFunction: curve.timingFunction, mediaTimingFunction: curve.mediaTimingFunction, removeOnCompletion: removeOnCompletion, completion: { value in
|
||||||
completion?(value)
|
completion?(value)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ private let largeCircleSize: CGFloat = 16.0
|
|||||||
private let smallCircleSize: CGFloat = 8.0
|
private let smallCircleSize: CGFloat = 8.0
|
||||||
|
|
||||||
public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
||||||
|
private let context: AccountContext
|
||||||
private let theme: PresentationTheme
|
private let theme: PresentationTheme
|
||||||
private let items: [ReactionContextItem]
|
private let items: [ReactionContextItem]
|
||||||
|
|
||||||
@@ -49,7 +50,7 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
private let contentContainer: ASDisplayNode
|
private let contentContainer: ASDisplayNode
|
||||||
private let contentContainerMask: UIImageView
|
private let contentContainerMask: UIImageView
|
||||||
private let scrollNode: ASScrollNode
|
private let scrollNode: ASScrollNode
|
||||||
private var itemNodes: [ReactionNode] = []
|
private var visibleItemNodes: [Int: ReactionNode] = [:]
|
||||||
|
|
||||||
private var isExpanded: Bool = true
|
private var isExpanded: Bool = true
|
||||||
private var highlightedReaction: ReactionContextItem.Reaction?
|
private var highlightedReaction: ReactionContextItem.Reaction?
|
||||||
@@ -64,7 +65,10 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
private weak var animationTargetView: UIView?
|
private weak var animationTargetView: UIView?
|
||||||
private var animationHideNode: Bool = false
|
private var animationHideNode: Bool = false
|
||||||
|
|
||||||
|
private var didAnimateIn: Bool = false
|
||||||
|
|
||||||
public init(context: AccountContext, theme: PresentationTheme, items: [ReactionContextItem]) {
|
public init(context: AccountContext, theme: PresentationTheme, items: [ReactionContextItem]) {
|
||||||
|
self.context = context
|
||||||
self.theme = theme
|
self.theme = theme
|
||||||
self.items = items
|
self.items = items
|
||||||
|
|
||||||
@@ -116,11 +120,6 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
|
|
||||||
self.scrollNode.view.delegate = self
|
self.scrollNode.view.delegate = self
|
||||||
|
|
||||||
self.itemNodes = self.items.map { item in
|
|
||||||
return ReactionNode(context: context, theme: theme, item: item)
|
|
||||||
}
|
|
||||||
self.itemNodes.forEach(self.scrollNode.addSubnode)
|
|
||||||
|
|
||||||
self.addSubnode(self.contentContainer)
|
self.addSubnode(self.contentContainer)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -180,30 +179,61 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func updateScrolling(transition: ContainedViewLayoutTransition) {
|
private func updateScrolling(transition: ContainedViewLayoutTransition) {
|
||||||
let sideInset: CGFloat = 14.0
|
let sideInset: CGFloat = 11.0
|
||||||
let minScale: CGFloat = 0.6
|
let itemSpacing: CGFloat = 9.0
|
||||||
let scaleDistance: CGFloat = 30.0
|
let itemSize: CGFloat = 40.0
|
||||||
|
let verticalInset: CGFloat = 13.0
|
||||||
|
let rowHeight: CGFloat = 30.0
|
||||||
|
|
||||||
let visibleBounds = self.scrollNode.view.bounds
|
let visibleBounds = self.scrollNode.view.bounds
|
||||||
|
|
||||||
for itemNode in self.itemNodes {
|
var validIndices = Set<Int>()
|
||||||
if itemNode.isExtracted {
|
for i in 0 ..< self.items.count {
|
||||||
continue
|
let columnIndex = i
|
||||||
}
|
let column = CGFloat(columnIndex)
|
||||||
let itemScale: CGFloat
|
|
||||||
let itemFrame = itemNode.frame.offsetBy(dx: -visibleBounds.minX, dy: 0.0)
|
let itemOffsetY: CGFloat = -1.0
|
||||||
if itemFrame.minX < sideInset || itemFrame.maxX > visibleBounds.width - sideInset {
|
|
||||||
let edgeDistance: CGFloat
|
let itemFrame = CGRect(origin: CGPoint(x: sideInset + column * (itemSize + itemSpacing), y: verticalInset + floor((rowHeight - itemSize) / 2.0) + itemOffsetY), size: CGSize(width: itemSize, height: itemSize))
|
||||||
if itemFrame.minX < sideInset {
|
/*if self.highlightedReaction == self.items[i].reaction {
|
||||||
edgeDistance = sideInset - itemFrame.minX
|
itemFrame = itemFrame.insetBy(dx: -6.0, dy: -6.0)
|
||||||
|
}*/
|
||||||
|
if visibleBounds.intersects(itemFrame) {
|
||||||
|
validIndices.insert(i)
|
||||||
|
|
||||||
|
var animateIn = false
|
||||||
|
|
||||||
|
let itemNode: ReactionNode
|
||||||
|
if let current = self.visibleItemNodes[i] {
|
||||||
|
itemNode = current
|
||||||
} else {
|
} else {
|
||||||
edgeDistance = itemFrame.maxX - (visibleBounds.width - sideInset)
|
animateIn = self.didAnimateIn
|
||||||
|
|
||||||
|
itemNode = ReactionNode(context: self.context, theme: self.theme, item: self.items[i])
|
||||||
|
self.visibleItemNodes[i] = itemNode
|
||||||
|
self.scrollNode.addSubnode(itemNode)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !itemNode.isExtracted {
|
||||||
|
transition.updateFrame(node: itemNode, frame: itemFrame, beginWithCurrentState: true)
|
||||||
|
itemNode.updateLayout(size: itemFrame.size, isExpanded: false, transition: transition)
|
||||||
|
|
||||||
|
if animateIn {
|
||||||
|
itemNode.animateIn()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
let edgeFactor: CGFloat = min(1.0, edgeDistance / scaleDistance)
|
|
||||||
itemScale = edgeFactor * minScale + (1.0 - edgeFactor) * 1.0
|
|
||||||
} else {
|
|
||||||
itemScale = 1.0
|
|
||||||
}
|
}
|
||||||
transition.updateSublayerTransformScale(node: itemNode, scale: itemScale)
|
}
|
||||||
|
|
||||||
|
var removedIndices: [Int] = []
|
||||||
|
for (index, itemNode) in self.visibleItemNodes {
|
||||||
|
if !validIndices.contains(index) {
|
||||||
|
removedIndices.append(index)
|
||||||
|
itemNode.removeFromSupernode()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for index in removedIndices {
|
||||||
|
self.visibleItemNodes.removeValue(forKey: index)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -237,22 +267,6 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
transition.updateFrame(node: self.scrollNode, frame: CGRect(origin: CGPoint(), size: backgroundFrame.size))
|
transition.updateFrame(node: self.scrollNode, frame: CGRect(origin: CGPoint(), size: backgroundFrame.size))
|
||||||
self.scrollNode.view.contentSize = CGSize(width: completeContentWidth, height: backgroundFrame.size.height)
|
self.scrollNode.view.contentSize = CGSize(width: completeContentWidth, height: backgroundFrame.size.height)
|
||||||
|
|
||||||
for i in 0 ..< self.items.count {
|
|
||||||
let columnIndex = i
|
|
||||||
let column = CGFloat(columnIndex)
|
|
||||||
|
|
||||||
let itemOffsetY: CGFloat = -1.0
|
|
||||||
|
|
||||||
var itemFrame = CGRect(origin: CGPoint(x: sideInset + column * (itemSize + itemSpacing), y: verticalInset + floor((rowHeight - itemSize) / 2.0) + itemOffsetY), size: CGSize(width: itemSize, height: itemSize))
|
|
||||||
if self.highlightedReaction == self.items[i].reaction {
|
|
||||||
itemFrame = itemFrame.insetBy(dx: -6.0, dy: -6.0)
|
|
||||||
}
|
|
||||||
if !self.itemNodes[i].isExtracted {
|
|
||||||
transition.updateFrame(node: self.itemNodes[i], frame: itemFrame, beginWithCurrentState: true)
|
|
||||||
self.itemNodes[i].updateLayout(size: itemFrame.size, isExpanded: false, transition: transition)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.updateScrolling(transition: transition)
|
self.updateScrolling(transition: transition)
|
||||||
|
|
||||||
transition.updateFrame(node: self.backgroundNode, frame: backgroundFrame)
|
transition.updateFrame(node: self.backgroundNode, frame: backgroundFrame)
|
||||||
@@ -291,23 +305,28 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
self.updateLayout(size: size, insets: insets, anchorRect: anchorRect, transition: .immediate, animateInFromAnchorRect: sourceAnchorRect, animateOutToAnchorRect: nil)
|
self.updateLayout(size: size, insets: insets, anchorRect: anchorRect, transition: .immediate, animateInFromAnchorRect: sourceAnchorRect, animateOutToAnchorRect: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
let mainCircleDuration: Double = 0.5
|
//let mainCircleDuration: Double = 0.5
|
||||||
let mainCircleDelay: Double = 0.1
|
let mainCircleDelay: Double = 0.1
|
||||||
|
|
||||||
self.backgroundNode.animateIn()
|
self.backgroundNode.animateIn()
|
||||||
|
|
||||||
for i in 0 ..< self.itemNodes.count {
|
self.didAnimateIn = true
|
||||||
let itemNode = self.itemNodes[i]
|
|
||||||
|
for i in 0 ..< self.items.count {
|
||||||
|
guard let itemNode = self.visibleItemNodes[i] else {
|
||||||
|
continue
|
||||||
|
}
|
||||||
let itemDelay = mainCircleDelay + 0.1 + Double(i) * 0.03
|
let itemDelay = mainCircleDelay + 0.1 + Double(i) * 0.03
|
||||||
itemNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15, delay: itemDelay)
|
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + itemDelay, execute: { [weak itemNode] in
|
||||||
itemNode.layer.animateSpring(from: 0.1 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: mainCircleDuration, delay: itemDelay, initialVelocity: 0.0)
|
itemNode?.animateIn()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func animateOut(to targetAnchorRect: CGRect?, animatingOutToReaction: Bool) {
|
public func animateOut(to targetAnchorRect: CGRect?, animatingOutToReaction: Bool) {
|
||||||
self.backgroundNode.animateOut()
|
self.backgroundNode.animateOut()
|
||||||
|
|
||||||
for itemNode in self.itemNodes {
|
for (_, itemNode) in self.visibleItemNodes {
|
||||||
if itemNode.isExtracted {
|
if itemNode.isExtracted {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -367,7 +386,7 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public func willAnimateOutToReaction(value: String) {
|
public func willAnimateOutToReaction(value: String) {
|
||||||
for itemNode in self.itemNodes {
|
for (_, itemNode) in self.visibleItemNodes {
|
||||||
if itemNode.item.reaction.rawValue != value {
|
if itemNode.item.reaction.rawValue != value {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -376,7 +395,7 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public func animateOutToReaction(value: String, targetView: UIView, hideNode: Bool, completion: @escaping () -> Void) {
|
public func animateOutToReaction(value: String, targetView: UIView, hideNode: Bool, completion: @escaping () -> Void) {
|
||||||
for itemNode in self.itemNodes {
|
for (_, itemNode) in self.visibleItemNodes {
|
||||||
if itemNode.item.reaction.rawValue != value {
|
if itemNode.item.reaction.rawValue != value {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -400,7 +419,7 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
let selfSourceRect = itemNode.view.convert(itemNode.view.bounds, to: self.view)
|
let selfSourceRect = itemNode.view.convert(itemNode.view.bounds, to: self.view)
|
||||||
let selfTargetRect = self.view.convert(targetView.bounds, from: targetView)
|
let selfTargetRect = self.view.convert(targetView.bounds, from: targetView)
|
||||||
|
|
||||||
let expandedScale: CGFloat = 3.0
|
let expandedScale: CGFloat = 4.0
|
||||||
let expandedSize = CGSize(width: floor(selfSourceRect.width * expandedScale), height: floor(selfSourceRect.height * expandedScale))
|
let expandedSize = CGSize(width: floor(selfSourceRect.width * expandedScale), height: floor(selfSourceRect.height * expandedScale))
|
||||||
|
|
||||||
var expandedFrame = CGRect(origin: CGPoint(x: floor(selfTargetRect.midX - expandedSize.width / 2.0), y: floor(selfTargetRect.midY - expandedSize.height / 2.0)), size: expandedSize)
|
var expandedFrame = CGRect(origin: CGPoint(x: floor(selfTargetRect.midX - expandedSize.width / 2.0), y: floor(selfTargetRect.midY - expandedSize.height / 2.0)), size: expandedSize)
|
||||||
@@ -411,10 +430,11 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
let transition: ContainedViewLayoutTransition = .animated(duration: 0.2, curve: .linear)
|
let transition: ContainedViewLayoutTransition = .animated(duration: 0.2, curve: .linear)
|
||||||
|
|
||||||
self.addSubnode(itemNode)
|
self.addSubnode(itemNode)
|
||||||
itemNode.frame = selfSourceRect
|
//itemNode.position = selfSourceRect.center
|
||||||
itemNode.position = expandedFrame.center
|
itemNode.position = expandedFrame.center
|
||||||
transition.updateBounds(node: itemNode, bounds: CGRect(origin: CGPoint(), size: expandedFrame.size))
|
transition.updateBounds(node: itemNode, bounds: CGRect(origin: CGPoint(), size: expandedFrame.size))
|
||||||
itemNode.updateLayout(size: expandedFrame.size, isExpanded: true, transition: transition)
|
itemNode.updateLayout(size: expandedFrame.size, isExpanded: true, transition: transition)
|
||||||
|
|
||||||
transition.animatePositionWithKeyframes(node: itemNode, keyframes: generateParabollicMotionKeyframes(from: selfSourceRect.center, to: expandedFrame.center, elevation: 30.0))
|
transition.animatePositionWithKeyframes(node: itemNode, keyframes: generateParabollicMotionKeyframes(from: selfSourceRect.center, to: expandedFrame.center, elevation: 30.0))
|
||||||
|
|
||||||
let additionalAnimationNode = AnimatedStickerNode()
|
let additionalAnimationNode = AnimatedStickerNode()
|
||||||
@@ -507,7 +527,7 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
public func reaction(at point: CGPoint) -> ReactionContextItem? {
|
public func reaction(at point: CGPoint) -> ReactionContextItem? {
|
||||||
for i in 0 ..< 2 {
|
for i in 0 ..< 2 {
|
||||||
let touchInset: CGFloat = i == 0 ? 0.0 : 8.0
|
let touchInset: CGFloat = i == 0 ? 0.0 : 8.0
|
||||||
for itemNode in self.itemNodes {
|
for (_, itemNode) in self.visibleItemNodes {
|
||||||
let itemPoint = self.view.convert(point, to: itemNode.view)
|
let itemPoint = self.view.convert(point, to: itemNode.view)
|
||||||
if itemNode.bounds.insetBy(dx: -touchInset, dy: -touchInset).contains(itemPoint) {
|
if itemNode.bounds.insetBy(dx: -touchInset, dy: -touchInset).contains(itemPoint) {
|
||||||
return itemNode.item
|
return itemNode.item
|
||||||
@@ -518,7 +538,7 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public func performReactionSelection(reaction: ReactionContextItem.Reaction) {
|
public func performReactionSelection(reaction: ReactionContextItem.Reaction) {
|
||||||
for itemNode in self.itemNodes {
|
for (_, itemNode) in self.visibleItemNodes {
|
||||||
if itemNode.item.reaction == reaction {
|
if itemNode.item.reaction == reaction {
|
||||||
self.reactionSelected?(itemNode.item)
|
self.reactionSelected?(itemNode.item)
|
||||||
break
|
break
|
||||||
@@ -782,7 +802,7 @@ public final class StandaloneDismissReactionAnimation: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func generateParabollicMotionKeyframes(from sourcePoint: CGPoint, to targetPosition: CGPoint, elevation: CGFloat) -> [AnyObject] {
|
private func generateParabollicMotionKeyframes(from sourcePoint: CGPoint, to targetPosition: CGPoint, elevation: CGFloat) -> [CGPoint] {
|
||||||
let midPoint = CGPoint(x: (sourcePoint.x + targetPosition.x) / 2.0, y: sourcePoint.y - elevation)
|
let midPoint = CGPoint(x: (sourcePoint.x + targetPosition.x) / 2.0, y: sourcePoint.y - elevation)
|
||||||
|
|
||||||
let x1 = sourcePoint.x
|
let x1 = sourcePoint.x
|
||||||
@@ -792,13 +812,13 @@ private func generateParabollicMotionKeyframes(from sourcePoint: CGPoint, to tar
|
|||||||
let x3 = targetPosition.x
|
let x3 = targetPosition.x
|
||||||
let y3 = targetPosition.y
|
let y3 = targetPosition.y
|
||||||
|
|
||||||
var keyframes: [AnyObject] = []
|
var keyframes: [CGPoint] = []
|
||||||
if abs(y1 - y3) < 5.0 && abs(x1 - x3) < 5.0 {
|
if abs(y1 - y3) < 5.0 && abs(x1 - x3) < 5.0 {
|
||||||
for i in 0 ..< 10 {
|
for i in 0 ..< 10 {
|
||||||
let k = CGFloat(i) / CGFloat(10 - 1)
|
let k = CGFloat(i) / CGFloat(10 - 1)
|
||||||
let x = sourcePoint.x * (1.0 - k) + targetPosition.x * k
|
let x = sourcePoint.x * (1.0 - k) + targetPosition.x * k
|
||||||
let y = sourcePoint.y * (1.0 - k) + targetPosition.y * k
|
let y = sourcePoint.y * (1.0 - k) + targetPosition.y * k
|
||||||
keyframes.append(NSValue(cgPoint: CGPoint(x: x, y: y)))
|
keyframes.append(CGPoint(x: x, y: y))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let a = (x3 * (y2 - y1) + x2 * (y1 - y3) + x1 * (y3 - y2)) / ((x1 - x2) * (x1 - x3) * (x2 - x3))
|
let a = (x3 * (y2 - y1) + x2 * (y1 - y3) + x1 * (y3 - y2)) / ((x1 - x2) * (x1 - x3) * (x2 - x3))
|
||||||
@@ -809,7 +829,7 @@ private func generateParabollicMotionKeyframes(from sourcePoint: CGPoint, to tar
|
|||||||
let k = CGFloat(i) / CGFloat(10 - 1)
|
let k = CGFloat(i) / CGFloat(10 - 1)
|
||||||
let x = sourcePoint.x * (1.0 - k) + targetPosition.x * k
|
let x = sourcePoint.x * (1.0 - k) + targetPosition.x * k
|
||||||
let y = a * x * x + b * x + c
|
let y = a * x * x + b * x + c
|
||||||
keyframes.append(NSValue(cgPoint: CGPoint(x: x, y: y)))
|
keyframes.append(CGPoint(x: x, y: y))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -38,8 +38,10 @@ private let font = Font.medium(13.0)
|
|||||||
final class ReactionNode: ASDisplayNode {
|
final class ReactionNode: ASDisplayNode {
|
||||||
let context: AccountContext
|
let context: AccountContext
|
||||||
let item: ReactionContextItem
|
let item: ReactionContextItem
|
||||||
private let staticImageNode: TransformImageNode
|
|
||||||
private let stillAnimationNode: AnimatedStickerNode
|
private var animateInAnimationNode: AnimatedStickerNode?
|
||||||
|
private let staticAnimationNode: AnimatedStickerNode
|
||||||
|
private var stillAnimationNode: AnimatedStickerNode?
|
||||||
private var animationNode: AnimatedStickerNode?
|
private var animationNode: AnimatedStickerNode?
|
||||||
|
|
||||||
private var fetchStickerDisposable: Disposable?
|
private var fetchStickerDisposable: Disposable?
|
||||||
@@ -55,22 +57,40 @@ final class ReactionNode: ASDisplayNode {
|
|||||||
self.context = context
|
self.context = context
|
||||||
self.item = item
|
self.item = item
|
||||||
|
|
||||||
self.staticImageNode = TransformImageNode()
|
self.staticAnimationNode = AnimatedStickerNode()
|
||||||
|
self.staticAnimationNode.isHidden = true
|
||||||
|
|
||||||
|
self.animateInAnimationNode = AnimatedStickerNode()
|
||||||
self.stillAnimationNode = AnimatedStickerNode()
|
self.stillAnimationNode = AnimatedStickerNode()
|
||||||
|
|
||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
self.addSubnode(self.staticImageNode)
|
if let animateInAnimationNode = self.animateInAnimationNode {
|
||||||
|
self.addSubnode(animateInAnimationNode)
|
||||||
|
}
|
||||||
|
self.addSubnode(self.staticAnimationNode)
|
||||||
|
|
||||||
self.addSubnode(self.stillAnimationNode)
|
self.animateInAnimationNode?.completed = { [weak self] _ in
|
||||||
|
|
||||||
self.stillAnimationNode.started = { [weak self] in
|
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
strongSelf.staticImageNode.isHidden = true
|
if strongSelf.animationNode == nil {
|
||||||
|
strongSelf.staticAnimationNode.isHidden = false
|
||||||
|
}
|
||||||
|
|
||||||
|
strongSelf.animateInAnimationNode?.removeFromSupernode()
|
||||||
|
strongSelf.animateInAnimationNode = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*self.stillAnimationNode.started = { [weak self] in
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
strongSelf.animateInAnimationNode.isHidden = true
|
||||||
|
strongSelf.animateInAnimationNode.visibility = false
|
||||||
|
}*/
|
||||||
|
|
||||||
|
self.fetchStickerDisposable = fetchedMediaResource(mediaBox: context.account.postbox.mediaBox, reference: .standalone(resource: item.appearAnimation.resource)).start()
|
||||||
self.fetchStickerDisposable = fetchedMediaResource(mediaBox: context.account.postbox.mediaBox, reference: .standalone(resource: item.stillAnimation.resource)).start()
|
self.fetchStickerDisposable = fetchedMediaResource(mediaBox: context.account.postbox.mediaBox, reference: .standalone(resource: item.stillAnimation.resource)).start()
|
||||||
self.fetchStickerDisposable = fetchedMediaResource(mediaBox: context.account.postbox.mediaBox, reference: .standalone(resource: item.listAnimation.resource)).start()
|
self.fetchStickerDisposable = fetchedMediaResource(mediaBox: context.account.postbox.mediaBox, reference: .standalone(resource: item.listAnimation.resource)).start()
|
||||||
self.fetchFullAnimationDisposable = fetchedMediaResource(mediaBox: context.account.postbox.mediaBox, reference: .standalone(resource: item.applicationAnimation.resource)).start()
|
self.fetchFullAnimationDisposable = fetchedMediaResource(mediaBox: context.account.postbox.mediaBox, reference: .standalone(resource: item.applicationAnimation.resource)).start()
|
||||||
@@ -81,6 +101,10 @@ final class ReactionNode: ASDisplayNode {
|
|||||||
self.fetchFullAnimationDisposable?.dispose()
|
self.fetchFullAnimationDisposable?.dispose()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func animateIn() {
|
||||||
|
self.animateInAnimationNode?.visibility = true
|
||||||
|
}
|
||||||
|
|
||||||
func updateLayout(size: CGSize, isExpanded: Bool, transition: ContainedViewLayoutTransition) {
|
func updateLayout(size: CGSize, isExpanded: Bool, transition: ContainedViewLayoutTransition) {
|
||||||
let intrinsicSize = size
|
let intrinsicSize = size
|
||||||
|
|
||||||
@@ -101,60 +125,102 @@ final class ReactionNode: ASDisplayNode {
|
|||||||
self.animationNode = animationNode
|
self.animationNode = animationNode
|
||||||
self.addSubnode(animationNode)
|
self.addSubnode(animationNode)
|
||||||
|
|
||||||
animationNode.started = { [weak self] in
|
|
||||||
guard let strongSelf = self else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
strongSelf.staticImageNode.isHidden = true
|
|
||||||
}
|
|
||||||
|
|
||||||
animationNode.setup(source: AnimatedStickerResourceSource(account: self.context.account, resource: self.item.listAnimation.resource), width: Int(animationDisplaySize.width * 2.0), height: Int(animationDisplaySize.height * 2.0), playbackMode: .once, mode: .direct(cachePathPrefix: self.context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(self.item.listAnimation.resource.id)))
|
animationNode.setup(source: AnimatedStickerResourceSource(account: self.context.account, resource: self.item.listAnimation.resource), width: Int(animationDisplaySize.width * 2.0), height: Int(animationDisplaySize.height * 2.0), playbackMode: .once, mode: .direct(cachePathPrefix: self.context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(self.item.listAnimation.resource.id)))
|
||||||
animationNode.frame = animationFrame
|
animationNode.frame = animationFrame
|
||||||
animationNode.updateLayout(size: animationFrame.size)
|
animationNode.updateLayout(size: animationFrame.size)
|
||||||
if transition.isAnimated, !self.staticImageNode.frame.isEmpty {
|
|
||||||
transition.animateTransformScale(node: animationNode, from: self.staticImageNode.bounds.width / animationFrame.width)
|
|
||||||
transition.animatePositionAdditive(node: animationNode, offset: CGPoint(x: self.staticImageNode.frame.midX - animationFrame.midX, y: self.staticImageNode.frame.midY - animationFrame.midY))
|
|
||||||
}
|
|
||||||
animationNode.visibility = true
|
|
||||||
|
|
||||||
self.stillAnimationNode.alpha = 0.0
|
|
||||||
if transition.isAnimated {
|
if transition.isAnimated {
|
||||||
self.stillAnimationNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, completion: { [weak self] _ in
|
if let stillAnimationNode = self.stillAnimationNode, !stillAnimationNode.frame.isEmpty {
|
||||||
self?.stillAnimationNode.visibility = false
|
stillAnimationNode.alpha = 0.0
|
||||||
})
|
stillAnimationNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, completion: { [weak self] _ in
|
||||||
|
guard let strongSelf = self, let stillAnimationNode = strongSelf.stillAnimationNode else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
strongSelf.stillAnimationNode = nil
|
||||||
|
stillAnimationNode.removeFromSupernode()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if let animateInAnimationNode = self.animateInAnimationNode {
|
||||||
|
animateInAnimationNode.alpha = 0.0
|
||||||
|
animateInAnimationNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, completion: { [weak self] _ in
|
||||||
|
guard let strongSelf = self, let animateInAnimationNode = strongSelf.animateInAnimationNode else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
strongSelf.animateInAnimationNode = nil
|
||||||
|
animateInAnimationNode.removeFromSupernode()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
var referenceNode: ASDisplayNode?
|
||||||
|
if let animateInAnimationNode = self.animateInAnimationNode {
|
||||||
|
referenceNode = animateInAnimationNode
|
||||||
|
} else if !self.staticAnimationNode.isHidden {
|
||||||
|
referenceNode = self.staticAnimationNode
|
||||||
|
}
|
||||||
|
|
||||||
|
if let referenceNode = referenceNode {
|
||||||
|
transition.animateTransformScale(node: animationNode, from: referenceNode.bounds.width / animationFrame.width)
|
||||||
|
transition.animatePositionAdditive(node: animationNode, offset: CGPoint(x: referenceNode.frame.midX - animationFrame.midX, y: referenceNode.frame.midY - animationFrame.midY))
|
||||||
|
}
|
||||||
|
|
||||||
|
if !self.staticAnimationNode.isHidden {
|
||||||
|
transition.animateTransformScale(node: self.staticAnimationNode, from: self.staticAnimationNode.bounds.width / animationFrame.width)
|
||||||
|
transition.animatePositionAdditive(node: self.staticAnimationNode, offset: CGPoint(x: self.staticAnimationNode.frame.midX - animationFrame.midX, y: self.staticAnimationNode.frame.midY - animationFrame.midY))
|
||||||
|
|
||||||
|
self.staticAnimationNode.alpha = 0.0
|
||||||
|
self.staticAnimationNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2)
|
||||||
|
}
|
||||||
|
|
||||||
animationNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15)
|
animationNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15)
|
||||||
} else {
|
} else {
|
||||||
self.stillAnimationNode.visibility = false
|
if let stillAnimationNode = self.stillAnimationNode {
|
||||||
|
self.stillAnimationNode = nil
|
||||||
|
stillAnimationNode.removeFromSupernode()
|
||||||
|
}
|
||||||
|
self.staticAnimationNode.isHidden = true
|
||||||
}
|
}
|
||||||
|
animationNode.visibility = true
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.validSize != size {
|
if self.validSize != size {
|
||||||
self.validSize = size
|
self.validSize = size
|
||||||
|
|
||||||
if !self.staticImageNode.isHidden {
|
|
||||||
self.staticImageNode.setSignal(chatMessageAnimatedSticker(postbox: self.context.account.postbox, file: item.stillAnimation, small: false, size: CGSize(width: animationDisplaySize.width * UIScreenScale, height: animationDisplaySize.height * UIScreenScale), fitzModifier: nil, fetched: false, onlyFullSize: false, thumbnail: false, synchronousLoad: false))
|
|
||||||
let imageApply = self.staticImageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(), imageSize: animationDisplaySize, boundingSize: animationDisplaySize, intrinsicInsets: UIEdgeInsets()))
|
|
||||||
imageApply()
|
|
||||||
}
|
|
||||||
transition.updateFrame(node: self.staticImageNode, frame: animationFrame)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if !self.didSetupStillAnimation {
|
if !self.didSetupStillAnimation {
|
||||||
if self.animationNode == nil {
|
if self.animationNode == nil {
|
||||||
self.didSetupStillAnimation = true
|
self.didSetupStillAnimation = true
|
||||||
|
|
||||||
self.stillAnimationNode.setup(source: AnimatedStickerResourceSource(account: self.context.account, resource: self.item.stillAnimation.resource), width: Int(animationDisplaySize.width * 2.5), height: Int(animationDisplaySize.height * 2.5), playbackMode: .loop, mode: .direct(cachePathPrefix: self.context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(self.item.stillAnimation.resource.id)))
|
self.staticAnimationNode.setup(source: AnimatedStickerResourceSource(account: self.context.account, resource: self.item.stillAnimation.resource), width: Int(animationDisplaySize.width * 2.5), height: Int(animationDisplaySize.height * 2.5), playbackMode: .still(.start), mode: .direct(cachePathPrefix: self.context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(self.item.stillAnimation.resource.id)))
|
||||||
|
self.staticAnimationNode.position = animationFrame.center
|
||||||
|
self.staticAnimationNode.bounds = CGRect(origin: CGPoint(), size: animationFrame.size)
|
||||||
|
self.staticAnimationNode.updateLayout(size: animationFrame.size)
|
||||||
|
self.staticAnimationNode.visibility = true
|
||||||
|
|
||||||
|
if let animateInAnimationNode = self.animateInAnimationNode {
|
||||||
|
animateInAnimationNode.setup(source: AnimatedStickerResourceSource(account: self.context.account, resource: self.item.appearAnimation.resource), width: Int(animationDisplaySize.width * 2.5), height: Int(animationDisplaySize.height * 2.5), playbackMode: .once, mode: .direct(cachePathPrefix: self.context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(self.item.appearAnimation.resource.id)))
|
||||||
|
animateInAnimationNode.position = animationFrame.center
|
||||||
|
animateInAnimationNode.bounds = CGRect(origin: CGPoint(), size: animationFrame.size)
|
||||||
|
animateInAnimationNode.updateLayout(size: animationFrame.size)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*self.stillAnimationNode.setup(source: AnimatedStickerResourceSource(account: self.context.account, resource: self.item.stillAnimation.resource), width: Int(animationDisplaySize.width * 2.5), height: Int(animationDisplaySize.height * 2.5), playbackMode: .once, mode: .direct(cachePathPrefix: self.context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(self.item.stillAnimation.resource.id)))
|
||||||
self.stillAnimationNode.position = animationFrame.center
|
self.stillAnimationNode.position = animationFrame.center
|
||||||
self.stillAnimationNode.bounds = CGRect(origin: CGPoint(), size: animationFrame.size)
|
self.stillAnimationNode.bounds = CGRect(origin: CGPoint(), size: animationFrame.size)
|
||||||
self.stillAnimationNode.updateLayout(size: animationFrame.size)
|
self.stillAnimationNode.updateLayout(size: animationFrame.size)*/
|
||||||
self.stillAnimationNode.visibility = true
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
transition.updatePosition(node: self.stillAnimationNode, position: animationFrame.center, beginWithCurrentState: true)
|
transition.updatePosition(node: self.staticAnimationNode, position: animationFrame.center, beginWithCurrentState: true)
|
||||||
transition.updateTransformScale(node: self.stillAnimationNode, scale: animationFrame.size.width / self.stillAnimationNode.bounds.width, beginWithCurrentState: true)
|
transition.updateTransformScale(node: self.staticAnimationNode, scale: animationFrame.size.width / self.staticAnimationNode.bounds.width, beginWithCurrentState: true)
|
||||||
|
|
||||||
|
if let animateInAnimationNode = self.animateInAnimationNode {
|
||||||
|
transition.updatePosition(node: animateInAnimationNode, position: animationFrame.center, beginWithCurrentState: true)
|
||||||
|
transition.updateTransformScale(node: animateInAnimationNode, scale: animationFrame.size.width / animateInAnimationNode.bounds.width, beginWithCurrentState: true)
|
||||||
|
}
|
||||||
|
|
||||||
|
if let stillAnimationNode = self.stillAnimationNode {
|
||||||
|
transition.updatePosition(node: stillAnimationNode, position: animationFrame.center, beginWithCurrentState: true)
|
||||||
|
transition.updateTransformScale(node: stillAnimationNode, scale: animationFrame.size.width / stillAnimationNode.bounds.width, beginWithCurrentState: true)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func didAppear() {
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user