mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Add direct sticker rendering
This commit is contained in:
parent
33e5dedd74
commit
2a9fe74c51
@ -0,0 +1,302 @@
|
|||||||
|
import Foundation
|
||||||
|
import UIKit
|
||||||
|
import AsyncDisplayKit
|
||||||
|
import RLottieBinding
|
||||||
|
import SwiftSignalKit
|
||||||
|
import GZip
|
||||||
|
import Display
|
||||||
|
|
||||||
|
public final class DirectAnimatedStickerNode: ASDisplayNode, AnimatedStickerNode {
|
||||||
|
private static let sharedQueue = Queue(name: "DirectAnimatedStickerNode", qos: .userInteractive)
|
||||||
|
|
||||||
|
private final class LoadFrameTask {
|
||||||
|
}
|
||||||
|
|
||||||
|
public var automaticallyLoadFirstFrame: Bool = false
|
||||||
|
public var automaticallyLoadLastFrame: Bool = false
|
||||||
|
public var playToCompletionOnStop: Bool = false
|
||||||
|
|
||||||
|
private var didStart: Bool = false
|
||||||
|
public var started: () -> Void = {}
|
||||||
|
|
||||||
|
public var completed: (Bool) -> Void = { _ in }
|
||||||
|
private var didComplete: Bool = false
|
||||||
|
|
||||||
|
public var frameUpdated: (Int, Int) -> Void = { _, _ in }
|
||||||
|
public var currentFrameIndex: Int {
|
||||||
|
get {
|
||||||
|
return self.frameIndex
|
||||||
|
} set(value) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public var currentFrameCount: Int {
|
||||||
|
get {
|
||||||
|
guard let lottieInstance = self.lottieInstance else {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return Int(lottieInstance.frameCount)
|
||||||
|
} set(value) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public private(set) var isPlaying: Bool = false
|
||||||
|
public var stopAtNearestLoop: Bool = false
|
||||||
|
|
||||||
|
private let statusPromise = Promise<AnimatedStickerStatus>()
|
||||||
|
public var status: Signal<AnimatedStickerStatus, NoError> {
|
||||||
|
return self.statusPromise.get()
|
||||||
|
}
|
||||||
|
|
||||||
|
public var autoplay: Bool = true
|
||||||
|
|
||||||
|
public var visibility: Bool = false {
|
||||||
|
didSet {
|
||||||
|
self.updatePlayback()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public var isPlayingChanged: (Bool) -> Void = { _ in }
|
||||||
|
|
||||||
|
private var sourceDisposable: Disposable?
|
||||||
|
private var playbackSize: CGSize?
|
||||||
|
|
||||||
|
private var lottieInstance: LottieInstance?
|
||||||
|
private var frameIndex: Int = 0
|
||||||
|
private var playbackMode: AnimatedStickerPlaybackMode = .loop
|
||||||
|
|
||||||
|
private var frameImages: [Int: UIImage] = [:]
|
||||||
|
private var loadFrameTasks: [Int: LoadFrameTask] = [:]
|
||||||
|
private var nextFrameTimer: SwiftSignalKit.Timer?
|
||||||
|
|
||||||
|
override public init() {
|
||||||
|
super.init()
|
||||||
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
self.sourceDisposable?.dispose()
|
||||||
|
self.nextFrameTimer?.invalidate()
|
||||||
|
}
|
||||||
|
|
||||||
|
public func cloneCurrentFrame(from otherNode: AnimatedStickerNode?) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public func setup(source: AnimatedStickerNodeSource, width: Int, height: Int, playbackMode: AnimatedStickerPlaybackMode, mode: AnimatedStickerMode) {
|
||||||
|
self.didStart = false
|
||||||
|
self.didComplete = false
|
||||||
|
|
||||||
|
self.sourceDisposable?.dispose()
|
||||||
|
|
||||||
|
self.playbackSize = CGSize(width: CGFloat(width), height: CGFloat(height))
|
||||||
|
self.playbackMode = playbackMode
|
||||||
|
|
||||||
|
self.sourceDisposable = (source.directDataPath(attemptSynchronously: false)
|
||||||
|
|> filter { $0 != nil }
|
||||||
|
|> take(1)
|
||||||
|
|> deliverOnMainQueue).start(next: { [weak self] path in
|
||||||
|
guard let strongSelf = self, let path = path else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
guard let data = try? Data(contentsOf: URL(fileURLWithPath: path)) else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let decompressedData = TGGUnzipData(data, 8 * 1024 * 1024) ?? data
|
||||||
|
|
||||||
|
guard let lottieInstance = LottieInstance(data: decompressedData, fitzModifier: .none, colorReplacements: nil, cacheKey: "") else {
|
||||||
|
print("Could not load sticker data")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
strongSelf.setupPlayback(lottieInstance: lottieInstance)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private func updatePlayback() {
|
||||||
|
let isPlaying = self.visibility && self.lottieInstance != nil
|
||||||
|
if self.isPlaying != isPlaying {
|
||||||
|
self.isPlaying = isPlaying
|
||||||
|
|
||||||
|
if self.isPlaying {
|
||||||
|
self.startNextFrameTimerIfNeeded()
|
||||||
|
self.updateLoadFrameTasks()
|
||||||
|
} else {
|
||||||
|
self.nextFrameTimer?.invalidate()
|
||||||
|
self.nextFrameTimer = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
self.isPlayingChanged(self.isPlaying)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func startNextFrameTimerIfNeeded() {
|
||||||
|
if self.nextFrameTimer == nil, let lottieInstance = self.lottieInstance {
|
||||||
|
let nextFrameTimer = SwiftSignalKit.Timer(timeout: 1.0 / Double(lottieInstance.frameRate), repeat: false, completion: { [weak self] in
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
strongSelf.nextFrameTimer = nil
|
||||||
|
strongSelf.advanceFrameIfPossible()
|
||||||
|
}, queue: .mainQueue())
|
||||||
|
self.nextFrameTimer = nextFrameTimer
|
||||||
|
nextFrameTimer.start()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func advanceFrameIfPossible() {
|
||||||
|
guard let lottieInstance = self.lottieInstance else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.frameIndex == Int(lottieInstance.frameCount) - 1 {
|
||||||
|
switch self.playbackMode {
|
||||||
|
case .loop:
|
||||||
|
self.completed(false)
|
||||||
|
case let .count(count):
|
||||||
|
if count <= 1 {
|
||||||
|
if !self.didComplete {
|
||||||
|
self.didComplete = true
|
||||||
|
self.completed(true)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
self.playbackMode = .count(count - 1)
|
||||||
|
self.completed(false)
|
||||||
|
}
|
||||||
|
case .once:
|
||||||
|
if !self.didComplete {
|
||||||
|
self.didComplete = true
|
||||||
|
self.completed(true)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
case .still:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let nextFrameIndex = (self.frameIndex + 1) % Int(lottieInstance.frameCount)
|
||||||
|
self.frameIndex = nextFrameIndex
|
||||||
|
|
||||||
|
self.updateFrameImageIfNeeded()
|
||||||
|
self.updateLoadFrameTasks()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func updateFrameImageIfNeeded() {
|
||||||
|
guard let lottieInstance = self.lottieInstance else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var allowedIndices: [Int] = []
|
||||||
|
for i in 0 ..< 2 {
|
||||||
|
let mappedIndex = (self.frameIndex + i) % Int(lottieInstance.frameCount)
|
||||||
|
allowedIndices.append(mappedIndex)
|
||||||
|
}
|
||||||
|
|
||||||
|
var removeKeys: [Int] = []
|
||||||
|
for index in self.frameImages.keys {
|
||||||
|
if !allowedIndices.contains(index) {
|
||||||
|
removeKeys.append(index)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for index in removeKeys {
|
||||||
|
self.frameImages.removeValue(forKey: index)
|
||||||
|
}
|
||||||
|
|
||||||
|
if let image = self.frameImages[self.frameIndex] {
|
||||||
|
self.layer.contents = image.cgImage
|
||||||
|
|
||||||
|
self.frameUpdated(self.frameIndex, Int(lottieInstance.frameCount))
|
||||||
|
|
||||||
|
if !self.didComplete {
|
||||||
|
self.startNextFrameTimerIfNeeded()
|
||||||
|
}
|
||||||
|
|
||||||
|
if !self.didStart {
|
||||||
|
self.didStart = true
|
||||||
|
self.started()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func updateLoadFrameTasks() {
|
||||||
|
guard let lottieInstance = self.lottieInstance else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let frameIndex = self.frameIndex % Int(lottieInstance.frameCount)
|
||||||
|
if self.frameImages[frameIndex] == nil {
|
||||||
|
self.maybeStartLoadFrameTask(frameIndex: frameIndex)
|
||||||
|
} else {
|
||||||
|
self.maybeStartLoadFrameTask(frameIndex: (frameIndex + 1) % Int(lottieInstance.frameCount))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func maybeStartLoadFrameTask(frameIndex: Int) {
|
||||||
|
guard let lottieInstance = self.lottieInstance, let playbackSize = self.playbackSize else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if self.loadFrameTasks[frameIndex] != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
self.loadFrameTasks[frameIndex] = LoadFrameTask()
|
||||||
|
|
||||||
|
DirectAnimatedStickerNode.sharedQueue.async { [weak self] in
|
||||||
|
let drawingContext = DrawingContext(size: playbackSize, scale: 1.0, opaque: false, clear: false)
|
||||||
|
lottieInstance.renderFrame(with: Int32(frameIndex), into: drawingContext.bytes.assumingMemoryBound(to: UInt8.self), width: Int32(drawingContext.scaledSize.width), height: Int32(drawingContext.scaledSize.height), bytesPerRow: Int32(drawingContext.bytesPerRow))
|
||||||
|
|
||||||
|
let image = drawingContext.generateImage()
|
||||||
|
|
||||||
|
Queue.mainQueue().async {
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
strongSelf.loadFrameTasks.removeValue(forKey: frameIndex)
|
||||||
|
|
||||||
|
if let image = image {
|
||||||
|
strongSelf.frameImages[frameIndex] = image
|
||||||
|
strongSelf.updateFrameImageIfNeeded()
|
||||||
|
strongSelf.updateLoadFrameTasks()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func setupPlayback(lottieInstance: LottieInstance) {
|
||||||
|
self.lottieInstance = lottieInstance
|
||||||
|
|
||||||
|
self.updatePlayback()
|
||||||
|
}
|
||||||
|
|
||||||
|
public func reset() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public func playOnce() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public func play(firstFrame: Bool, fromIndex: Int?) {
|
||||||
|
if let fromIndex = fromIndex {
|
||||||
|
self.frameIndex = fromIndex
|
||||||
|
self.updateLoadFrameTasks()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func pause() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public func stop() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public func seekTo(_ position: AnimatedStickerPlaybackPosition) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public func playIfNeeded() -> Bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
public func updateLayout(size: CGSize) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public func setOverlayColor(_ color: UIColor?, replace: Bool, animated: Bool) {
|
||||||
|
}
|
||||||
|
}
|
@ -349,7 +349,7 @@ private class ReactionCarouselNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
largeListAnimation: reaction.activateAnimation,
|
largeListAnimation: reaction.activateAnimation,
|
||||||
applicationAnimation: aroundAnimation,
|
applicationAnimation: aroundAnimation,
|
||||||
largeApplicationAnimation: reaction.effectAnimation
|
largeApplicationAnimation: reaction.effectAnimation
|
||||||
), hasAppearAnimation: false)
|
), hasAppearAnimation: false, useDirectRendering: false)
|
||||||
containerNode.isUserInteractionEnabled = false
|
containerNode.isUserInteractionEnabled = false
|
||||||
containerNode.addSubnode(itemNode)
|
containerNode.addSubnode(itemNode)
|
||||||
self.addSubnode(containerNode)
|
self.addSubnode(containerNode)
|
||||||
@ -395,7 +395,7 @@ private class ReactionCarouselNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
|
|
||||||
targetContainerNode.view.superview?.bringSubviewToFront(targetContainerNode.view)
|
targetContainerNode.view.superview?.bringSubviewToFront(targetContainerNode.view)
|
||||||
|
|
||||||
let standaloneReactionAnimation = StandaloneReactionAnimation()
|
let standaloneReactionAnimation = StandaloneReactionAnimation(useDirectRendering: true)
|
||||||
self.standaloneReactionAnimation = standaloneReactionAnimation
|
self.standaloneReactionAnimation = standaloneReactionAnimation
|
||||||
|
|
||||||
targetContainerNode.addSubnode(standaloneReactionAnimation)
|
targetContainerNode.addSubnode(standaloneReactionAnimation)
|
||||||
|
@ -105,6 +105,7 @@ private class StickerNode: ASDisplayNode {
|
|||||||
|
|
||||||
if file.isPremiumSticker || forceIsPremium {
|
if file.isPremiumSticker || forceIsPremium {
|
||||||
let animationNode = DefaultAnimatedStickerNodeImpl()
|
let animationNode = DefaultAnimatedStickerNodeImpl()
|
||||||
|
//let animationNode = DirectAnimatedStickerNode()
|
||||||
animationNode.automaticallyLoadFirstFrame = true
|
animationNode.automaticallyLoadFirstFrame = true
|
||||||
self.animationNode = animationNode
|
self.animationNode = animationNode
|
||||||
|
|
||||||
@ -122,13 +123,16 @@ private class StickerNode: ASDisplayNode {
|
|||||||
self.effectDisposable.set(freeMediaFileResourceInteractiveFetched(account: self.context.account, fileReference: .standalone(media: file), resource: effect.resource).start())
|
self.effectDisposable.set(freeMediaFileResourceInteractiveFetched(account: self.context.account, fileReference: .standalone(media: file), resource: effect.resource).start())
|
||||||
|
|
||||||
let source = AnimatedStickerResourceSource(account: self.context.account, resource: effect.resource, fitzModifier: nil)
|
let source = AnimatedStickerResourceSource(account: self.context.account, resource: effect.resource, fitzModifier: nil)
|
||||||
let additionalAnimationNode = DefaultAnimatedStickerNodeImpl()
|
|
||||||
|
let additionalAnimationNode: AnimatedStickerNode
|
||||||
|
|
||||||
|
additionalAnimationNode = DirectAnimatedStickerNode()
|
||||||
|
|
||||||
var pathPrefix: String?
|
var pathPrefix: String?
|
||||||
pathPrefix = context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(effect.resource.id)
|
pathPrefix = context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(effect.resource.id)
|
||||||
pathPrefix = nil
|
pathPrefix = nil
|
||||||
|
|
||||||
additionalAnimationNode.setup(source: source, width: Int(fittedDimensions.width * 1.33), height: Int(fittedDimensions.height * 1.33), playbackMode: .loop, mode: .direct(cachePathPrefix: pathPrefix))
|
additionalAnimationNode.setup(source: source, width: Int(fittedDimensions.width * 1.5), height: Int(fittedDimensions.height * 1.5), playbackMode: .loop, mode: .direct(cachePathPrefix: pathPrefix))
|
||||||
self.additionalAnimationNode = additionalAnimationNode
|
self.additionalAnimationNode = additionalAnimationNode
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -188,7 +192,6 @@ private class StickerNode: ASDisplayNode {
|
|||||||
self.effectDisposable.dispose()
|
self.effectDisposable.dispose()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private func removePlaceholder(animated: Bool) {
|
private func removePlaceholder(animated: Bool) {
|
||||||
if !animated {
|
if !animated {
|
||||||
self.placeholderNode.removeFromSupernode()
|
self.placeholderNode.removeFromSupernode()
|
||||||
@ -216,7 +219,9 @@ private class StickerNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) {
|
func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) {
|
||||||
self.placeholderNode.updateAbsoluteRect(rect, within: containerSize)
|
if self.placeholderNode.supernode != nil {
|
||||||
|
self.placeholderNode.updateAbsoluteRect(rect, within: containerSize)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func updatePlayback() {
|
private func updatePlayback() {
|
||||||
@ -260,10 +265,12 @@ private class StickerNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let placeholderFrame = CGRect(origin: CGPoint(x: -10.0, y: 0.0), size: imageSize)
|
if self.placeholderNode.supernode != nil {
|
||||||
let thumbnailDimensions = PixelDimensions(width: 512, height: 512)
|
let placeholderFrame = CGRect(origin: CGPoint(x: -10.0, y: 0.0), size: imageSize)
|
||||||
self.placeholderNode.update(backgroundColor: nil, foregroundColor: UIColor(rgb: 0xffffff, alpha: 0.2), shimmeringColor: UIColor(rgb: 0xffffff, alpha: 0.3), data: self.file.immediateThumbnailData, size: placeholderFrame.size, imageSize: thumbnailDimensions.cgSize)
|
let thumbnailDimensions = PixelDimensions(width: 512, height: 512)
|
||||||
self.placeholderNode.frame = placeholderFrame
|
self.placeholderNode.update(backgroundColor: nil, foregroundColor: UIColor(rgb: 0xffffff, alpha: 0.2), shimmeringColor: UIColor(rgb: 0xffffff, alpha: 0.3), data: self.file.immediateThumbnailData, size: placeholderFrame.size, imageSize: thumbnailDimensions.cgSize)
|
||||||
|
self.placeholderNode.frame = placeholderFrame
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -963,6 +963,7 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public final class StandaloneReactionAnimation: ASDisplayNode {
|
public final class StandaloneReactionAnimation: ASDisplayNode {
|
||||||
|
private let useDirectRendering: Bool
|
||||||
private var itemNode: ReactionNode? = nil
|
private var itemNode: ReactionNode? = nil
|
||||||
private var itemNodeIsEmbedded: Bool = false
|
private var itemNodeIsEmbedded: Bool = false
|
||||||
private let hapticFeedback = HapticFeedback()
|
private let hapticFeedback = HapticFeedback()
|
||||||
@ -970,9 +971,9 @@ public final class StandaloneReactionAnimation: ASDisplayNode {
|
|||||||
|
|
||||||
private weak var targetView: UIView?
|
private weak var targetView: UIView?
|
||||||
|
|
||||||
//private var colorCallbacks: [LOTColorValueCallback] = []
|
public init(useDirectRendering: Bool = false) {
|
||||||
|
self.useDirectRendering = useDirectRendering
|
||||||
override public init() {
|
|
||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
self.isUserInteractionEnabled = false
|
self.isUserInteractionEnabled = false
|
||||||
@ -1064,7 +1065,12 @@ public final class StandaloneReactionAnimation: ASDisplayNode {
|
|||||||
|
|
||||||
itemNode.updateLayout(size: expandedFrame.size, isExpanded: true, largeExpanded: isLarge, isPreviewing: false, transition: .immediate)
|
itemNode.updateLayout(size: expandedFrame.size, isExpanded: true, largeExpanded: isLarge, isPreviewing: false, transition: .immediate)
|
||||||
|
|
||||||
let additionalAnimationNode = DefaultAnimatedStickerNodeImpl()
|
let additionalAnimationNode: AnimatedStickerNode
|
||||||
|
if self.useDirectRendering {
|
||||||
|
additionalAnimationNode = DirectAnimatedStickerNode()
|
||||||
|
} else {
|
||||||
|
additionalAnimationNode = DefaultAnimatedStickerNodeImpl()
|
||||||
|
}
|
||||||
|
|
||||||
let additionalAnimation: TelegramMediaFile
|
let additionalAnimation: TelegramMediaFile
|
||||||
if isLarge && !forceSmallEffectAnimation {
|
if isLarge && !forceSmallEffectAnimation {
|
||||||
@ -1139,7 +1145,7 @@ public final class StandaloneReactionAnimation: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var didBeginDismissAnimation = false
|
var didBeginDismissAnimation = false
|
||||||
let beginDismissAnimation: () -> Void = { [weak self] in
|
let beginDismissAnimation: () -> Void = { [weak self, weak additionalAnimationNode] in
|
||||||
if !didBeginDismissAnimation {
|
if !didBeginDismissAnimation {
|
||||||
didBeginDismissAnimation = true
|
didBeginDismissAnimation = true
|
||||||
|
|
||||||
@ -1150,9 +1156,11 @@ public final class StandaloneReactionAnimation: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if forceSmallEffectAnimation {
|
if forceSmallEffectAnimation {
|
||||||
additionalAnimationNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak additionalAnimationNode] _ in
|
if let additionalAnimationNode = additionalAnimationNode {
|
||||||
additionalAnimationNode?.removeFromSupernode()
|
additionalAnimationNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak additionalAnimationNode] _ in
|
||||||
})
|
additionalAnimationNode?.removeFromSupernode()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
mainAnimationCompleted = true
|
mainAnimationCompleted = true
|
||||||
intermediateCompletion()
|
intermediateCompletion()
|
||||||
|
@ -48,6 +48,7 @@ public final class ReactionNode: ASDisplayNode, ReactionItemNode {
|
|||||||
let context: AccountContext
|
let context: AccountContext
|
||||||
let item: ReactionItem
|
let item: ReactionItem
|
||||||
private let hasAppearAnimation: Bool
|
private let hasAppearAnimation: Bool
|
||||||
|
private let useDirectRendering: Bool
|
||||||
|
|
||||||
private var animateInAnimationNode: AnimatedStickerNode?
|
private var animateInAnimationNode: AnimatedStickerNode?
|
||||||
private let staticAnimationNode: AnimatedStickerNode
|
private let staticAnimationNode: AnimatedStickerNode
|
||||||
@ -67,16 +68,17 @@ public final class ReactionNode: ASDisplayNode, ReactionItemNode {
|
|||||||
|
|
||||||
var expandedAnimationDidBegin: (() -> Void)?
|
var expandedAnimationDidBegin: (() -> Void)?
|
||||||
|
|
||||||
public init(context: AccountContext, theme: PresentationTheme, item: ReactionItem, hasAppearAnimation: Bool = true) {
|
public init(context: AccountContext, theme: PresentationTheme, item: ReactionItem, hasAppearAnimation: Bool = true, useDirectRendering: Bool = false) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.item = item
|
self.item = item
|
||||||
self.hasAppearAnimation = hasAppearAnimation
|
self.hasAppearAnimation = hasAppearAnimation
|
||||||
|
self.useDirectRendering = useDirectRendering
|
||||||
|
|
||||||
self.staticAnimationNode = DefaultAnimatedStickerNodeImpl()
|
self.staticAnimationNode = self.useDirectRendering ? DirectAnimatedStickerNode() : DefaultAnimatedStickerNodeImpl()
|
||||||
|
|
||||||
if hasAppearAnimation {
|
if hasAppearAnimation {
|
||||||
self.staticAnimationNode.isHidden = true
|
self.staticAnimationNode.isHidden = true
|
||||||
self.animateInAnimationNode = DefaultAnimatedStickerNodeImpl()
|
self.animateInAnimationNode = self.useDirectRendering ? DirectAnimatedStickerNode() : DefaultAnimatedStickerNodeImpl()
|
||||||
}
|
}
|
||||||
|
|
||||||
super.init()
|
super.init()
|
||||||
@ -146,7 +148,7 @@ public final class ReactionNode: ASDisplayNode, ReactionItemNode {
|
|||||||
}
|
}
|
||||||
self.staticAnimationNode.play(firstFrame: false, fromIndex: 0)
|
self.staticAnimationNode.play(firstFrame: false, fromIndex: 0)
|
||||||
} else if isExpanded, self.animationNode == nil {
|
} else if isExpanded, self.animationNode == nil {
|
||||||
let animationNode = DefaultAnimatedStickerNodeImpl()
|
let animationNode: AnimatedStickerNode = self.useDirectRendering ? DirectAnimatedStickerNode() : DefaultAnimatedStickerNodeImpl()
|
||||||
animationNode.automaticallyLoadFirstFrame = true
|
animationNode.automaticallyLoadFirstFrame = true
|
||||||
self.animationNode = animationNode
|
self.animationNode = animationNode
|
||||||
self.addSubnode(animationNode)
|
self.addSubnode(animationNode)
|
||||||
@ -235,7 +237,7 @@ public final class ReactionNode: ASDisplayNode, ReactionItemNode {
|
|||||||
if self.animationNode == nil {
|
if self.animationNode == nil {
|
||||||
if isPreviewing {
|
if isPreviewing {
|
||||||
if self.stillAnimationNode == nil {
|
if self.stillAnimationNode == nil {
|
||||||
let stillAnimationNode = DefaultAnimatedStickerNodeImpl()
|
let stillAnimationNode: AnimatedStickerNode = self.useDirectRendering ? DirectAnimatedStickerNode() : DefaultAnimatedStickerNodeImpl()
|
||||||
self.stillAnimationNode = stillAnimationNode
|
self.stillAnimationNode = stillAnimationNode
|
||||||
self.addSubnode(stillAnimationNode)
|
self.addSubnode(stillAnimationNode)
|
||||||
|
|
||||||
|
@ -4,9 +4,18 @@ import Postbox
|
|||||||
import SwiftSignalKit
|
import SwiftSignalKit
|
||||||
import MtProtoKit
|
import MtProtoKit
|
||||||
|
|
||||||
enum MultiplexedRequestTarget: Equatable, Hashable {
|
enum MultiplexedRequestTarget: Equatable, Hashable, CustomStringConvertible {
|
||||||
case main(Int)
|
case main(Int)
|
||||||
case cdn(Int)
|
case cdn(Int)
|
||||||
|
|
||||||
|
var description: String {
|
||||||
|
switch self {
|
||||||
|
case let .main(id):
|
||||||
|
return "dc\(id)"
|
||||||
|
case let .cdn(id):
|
||||||
|
return "cdn\(id)"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private struct MultiplexedRequestTargetKey: Equatable, Hashable {
|
private struct MultiplexedRequestTargetKey: Equatable, Hashable {
|
||||||
|
@ -756,7 +756,7 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
|
|||||||
|
|
||||||
let statusLayoutInput: ChatMessageDateAndStatusNode.LayoutInput
|
let statusLayoutInput: ChatMessageDateAndStatusNode.LayoutInput
|
||||||
if let _ = textString {
|
if let _ = textString {
|
||||||
statusLayoutInput = .trailingContent(contentWidth: textLayout.trailingLineWidth, reactionSettings: reactionSettings)
|
statusLayoutInput = .trailingContent(contentWidth: textLayout.hasRTL ? 1000.0 : textLayout.trailingLineWidth, reactionSettings: reactionSettings)
|
||||||
} else {
|
} else {
|
||||||
statusLayoutInput = .trailingContent(contentWidth: iconFrame == nil ? 1000.0 : controlAreaWidth, reactionSettings: reactionSettings)
|
statusLayoutInput = .trailingContent(contentWidth: iconFrame == nil ? 1000.0 : controlAreaWidth, reactionSettings: reactionSettings)
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user