mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios
This commit is contained in:
commit
6d68d52542
@ -388,7 +388,7 @@
|
||||
"Tour.Text5" = "**Telegram** lets you access your\nmessages from multiple devices.";
|
||||
|
||||
"Tour.Title6" = "Free";
|
||||
"Tour.Text6" = "**Telegram** isprovides free unlimited cloud storage\nfor chats and media.";
|
||||
"Tour.Text6" = "**Telegram** provides free unlimited cloud storage\nfor chats and media.";
|
||||
|
||||
"Tour.StartButton" = "Start Messaging";
|
||||
|
||||
|
@ -0,0 +1,316 @@
|
||||
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 {
|
||||
var isCancelled: Bool = false
|
||||
}
|
||||
|
||||
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, self.frameImages[self.frameIndex] != nil {
|
||||
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)
|
||||
}
|
||||
|
||||
for (index, task) in self.loadFrameTasks {
|
||||
if !allowedIndices.contains(index) {
|
||||
task.isCancelled = true
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
let task = LoadFrameTask()
|
||||
self.loadFrameTasks[frameIndex] = task
|
||||
|
||||
DirectAnimatedStickerNode.sharedQueue.async { [weak self] in
|
||||
var image: UIImage?
|
||||
|
||||
if !task.isCancelled {
|
||||
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))
|
||||
|
||||
image = drawingContext.generateImage()
|
||||
}
|
||||
|
||||
Queue.mainQueue().async {
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
|
||||
if let currentTask = strongSelf.loadFrameTasks[frameIndex], currentTask === task {
|
||||
strongSelf.loadFrameTasks.removeValue(forKey: frameIndex)
|
||||
}
|
||||
|
||||
if !task.isCancelled, 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) {
|
||||
}
|
||||
}
|
@ -335,8 +335,12 @@ public final class ChatMessageShadowNode: ASDisplayNode {
|
||||
self.contentNode.image = shadowImage
|
||||
}
|
||||
|
||||
public func updateLayout(backgroundFrame: CGRect, animator: ControlledTransitionAnimator) {
|
||||
animator.updateFrame(layer: self.contentNode.layer, frame: CGRect(origin: CGPoint(x: backgroundFrame.minX - 10.0, y: backgroundFrame.minY - 10.0), size: CGSize(width: backgroundFrame.width + 20.0, height: backgroundFrame.height + 20.0)), completion: nil)
|
||||
}
|
||||
|
||||
public func updateLayout(backgroundFrame: CGRect, transition: ContainedViewLayoutTransition) {
|
||||
transition.updateFrame(node: self.contentNode, frame: CGRect(origin: CGPoint(x: backgroundFrame.minX - 10.0, y: backgroundFrame.minY - 10.0), size: CGSize(width: backgroundFrame.width + 20.0, height: backgroundFrame.height + 20.0)))
|
||||
transition.updateFrame(layer: self.contentNode.layer, frame: CGRect(origin: CGPoint(x: backgroundFrame.minX - 10.0, y: backgroundFrame.minY - 10.0), size: CGSize(width: backgroundFrame.width + 20.0, height: backgroundFrame.height + 20.0)), completion: nil)
|
||||
}
|
||||
}
|
||||
|
||||
@ -552,7 +556,7 @@ public final class ChatMessageBubbleBackdrop: ASDisplayNode {
|
||||
var backgroundFrame = backgroundContent.frame
|
||||
backgroundFrame.origin.x += rect.minX
|
||||
backgroundFrame.origin.y += rect.minY
|
||||
backgroundContent.update(rect: backgroundFrame, within: containerSize, transition: .animated(duration: animator.duration, curve: .spring))
|
||||
backgroundContent.update(rect: backgroundFrame, within: containerSize, animator: animator)
|
||||
}
|
||||
}
|
||||
animator.updateFrame(layer: self.layer, frame: value, completion: { _ in
|
||||
|
@ -830,6 +830,31 @@ public extension ContainedViewLayoutTransition {
|
||||
}
|
||||
}
|
||||
|
||||
func updateContentsRect(layer: CALayer, contentsRect: CGRect, completion: ((Bool) -> Void)? = nil) {
|
||||
if layer.contentsRect == contentsRect {
|
||||
if let completion = completion {
|
||||
completion(true)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
switch self {
|
||||
case .immediate:
|
||||
layer.contentsRect = contentsRect
|
||||
if let completion = completion {
|
||||
completion(true)
|
||||
}
|
||||
case let .animated(duration, curve):
|
||||
let previousContentsRect = layer.contentsRect
|
||||
layer.contentsRect = contentsRect
|
||||
layer.animate(from: NSValue(cgRect: previousContentsRect), to: NSValue(cgRect: contentsRect), keyPath: "contentsRect", timingFunction: curve.timingFunction, duration: duration, mediaTimingFunction: curve.mediaTimingFunction, completion: { result in
|
||||
if let completion = completion {
|
||||
completion(result)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func animateTransformScale(node: ASDisplayNode, from fromScale: CGFloat, additive: Bool = false, completion: ((Bool) -> Void)? = nil) {
|
||||
let t = node.layer.transform
|
||||
let currentScale = sqrt((t.m11 * t.m11) + (t.m12 * t.m12) + (t.m13 * t.m13))
|
||||
@ -1512,6 +1537,7 @@ public protocol ControlledTransitionAnimator: AnyObject {
|
||||
func updateBounds(layer: CALayer, bounds: CGRect, completion: ((Bool) -> Void)?)
|
||||
func updateFrame(layer: CALayer, frame: CGRect, completion: ((Bool) -> Void)?)
|
||||
func updateCornerRadius(layer: CALayer, cornerRadius: CGFloat, completion: ((Bool) -> Void)?)
|
||||
func updateContentsRect(layer: CALayer, contentsRect: CGRect, completion: ((Bool) -> Void)?)
|
||||
}
|
||||
|
||||
protocol AnyValueProviding {
|
||||
@ -1908,6 +1934,21 @@ public final class ControlledTransition {
|
||||
completion: completion
|
||||
))
|
||||
}
|
||||
|
||||
public func updateContentsRect(layer: CALayer, contentsRect: CGRect, completion: ((Bool) -> Void)?) {
|
||||
if layer.contentsRect == contentsRect {
|
||||
return
|
||||
}
|
||||
let fromValue = layer.presentation()?.contentsRect ?? layer.contentsRect
|
||||
layer.contentsRect = contentsRect
|
||||
self.add(animation: ControlledTransitionProperty(
|
||||
layer: layer,
|
||||
path: "contentsRect",
|
||||
fromValue: fromValue,
|
||||
toValue: contentsRect,
|
||||
completion: completion
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
public final class LegacyAnimator: ControlledTransitionAnimator {
|
||||
@ -1963,6 +2004,10 @@ public final class ControlledTransition {
|
||||
public func updateCornerRadius(layer: CALayer, cornerRadius: CGFloat, completion: ((Bool) -> Void)?) {
|
||||
self.transition.updateCornerRadius(layer: layer, cornerRadius: cornerRadius, completion: completion)
|
||||
}
|
||||
|
||||
public func updateContentsRect(layer: CALayer, contentsRect: CGRect, completion: ((Bool) -> Void)?) {
|
||||
self.transition.updateContentsRect(layer: layer, contentsRect: contentsRect, completion: completion)
|
||||
}
|
||||
}
|
||||
|
||||
public let animator: ControlledTransitionAnimator
|
||||
|
@ -811,7 +811,9 @@ public final class ListMessageSnippetItemNode: ListMessageNode {
|
||||
if case .longTap = gesture {
|
||||
item.interaction.longTap(ChatControllerInteractionLongTapAction.url(url), message)
|
||||
} else if url == self.currentPrimaryUrl {
|
||||
if !item.interaction.openMessage(message, .default) {
|
||||
if let webpage = self.currentMedia as? TelegramMediaWebpage, case let .Loaded(content) = webpage.content, content.instantPage != nil {
|
||||
item.interaction.openInstantPage(message, nil)
|
||||
} else {
|
||||
item.interaction.openUrl(url, false, false, nil)
|
||||
}
|
||||
} else {
|
||||
|
@ -49,7 +49,7 @@ private final class MediaBoxFileMap {
|
||||
return nil
|
||||
}
|
||||
|
||||
var truncationSizeValue: Int32 = 0
|
||||
var truncationSizeValue: Int64 = 0
|
||||
|
||||
var data = Data(count: Int(8 + count * 2 * 8))
|
||||
let dataCount = data.count
|
||||
@ -89,8 +89,10 @@ private final class MediaBoxFileMap {
|
||||
self.ranges = ranges
|
||||
if truncationSizeValue == -1 {
|
||||
self.truncationSize = nil
|
||||
} else if truncationSizeValue < 0 {
|
||||
self.truncationSize = nil
|
||||
} else {
|
||||
self.truncationSize = Int64(truncationSizeValue)
|
||||
self.truncationSize = truncationSizeValue
|
||||
}
|
||||
} else {
|
||||
let crc: UInt32 = firstUInt32
|
||||
@ -215,7 +217,8 @@ private final class MediaBoxFileMap {
|
||||
} else {
|
||||
maxValue = Int64.max
|
||||
}
|
||||
let clippedRange: Range<Int64> = range.lowerBound ..< min(maxValue, range.upperBound)
|
||||
let clippedUpperBound = min(maxValue, range.upperBound)
|
||||
let clippedRange: Range<Int64> = min(range.lowerBound, clippedUpperBound) ..< clippedUpperBound
|
||||
let clippedRangeSet = RangeSet<Int64>(clippedRange)
|
||||
|
||||
if self.ranges.isSuperset(of: clippedRangeSet) {
|
||||
|
@ -349,7 +349,7 @@ private class ReactionCarouselNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
largeListAnimation: reaction.activateAnimation,
|
||||
applicationAnimation: aroundAnimation,
|
||||
largeApplicationAnimation: reaction.effectAnimation
|
||||
), hasAppearAnimation: false)
|
||||
), hasAppearAnimation: false, useDirectRendering: false)
|
||||
containerNode.isUserInteractionEnabled = false
|
||||
containerNode.addSubnode(itemNode)
|
||||
self.addSubnode(containerNode)
|
||||
@ -395,7 +395,7 @@ private class ReactionCarouselNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
|
||||
targetContainerNode.view.superview?.bringSubviewToFront(targetContainerNode.view)
|
||||
|
||||
let standaloneReactionAnimation = StandaloneReactionAnimation()
|
||||
let standaloneReactionAnimation = StandaloneReactionAnimation(useDirectRendering: true)
|
||||
self.standaloneReactionAnimation = standaloneReactionAnimation
|
||||
|
||||
targetContainerNode.addSubnode(standaloneReactionAnimation)
|
||||
|
@ -105,6 +105,7 @@ private class StickerNode: ASDisplayNode {
|
||||
|
||||
if file.isPremiumSticker || forceIsPremium {
|
||||
let animationNode = DefaultAnimatedStickerNodeImpl()
|
||||
//let animationNode = DirectAnimatedStickerNode()
|
||||
animationNode.automaticallyLoadFirstFrame = true
|
||||
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())
|
||||
|
||||
let source = AnimatedStickerResourceSource(account: self.context.account, resource: effect.resource, fitzModifier: nil)
|
||||
let additionalAnimationNode = DefaultAnimatedStickerNodeImpl()
|
||||
|
||||
let additionalAnimationNode: AnimatedStickerNode
|
||||
|
||||
additionalAnimationNode = DirectAnimatedStickerNode()
|
||||
|
||||
var pathPrefix: String?
|
||||
pathPrefix = context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(effect.resource.id)
|
||||
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
|
||||
}
|
||||
} else {
|
||||
@ -188,7 +192,6 @@ private class StickerNode: ASDisplayNode {
|
||||
self.effectDisposable.dispose()
|
||||
}
|
||||
|
||||
|
||||
private func removePlaceholder(animated: Bool) {
|
||||
if !animated {
|
||||
self.placeholderNode.removeFromSupernode()
|
||||
@ -216,7 +219,9 @@ private class StickerNode: ASDisplayNode {
|
||||
}
|
||||
|
||||
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() {
|
||||
@ -260,10 +265,12 @@ private class StickerNode: ASDisplayNode {
|
||||
}
|
||||
}
|
||||
|
||||
let placeholderFrame = CGRect(origin: CGPoint(x: -10.0, y: 0.0), size: imageSize)
|
||||
let thumbnailDimensions = PixelDimensions(width: 512, height: 512)
|
||||
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
|
||||
if self.placeholderNode.supernode != nil {
|
||||
let placeholderFrame = CGRect(origin: CGPoint(x: -10.0, y: 0.0), size: imageSize)
|
||||
let thumbnailDimensions = PixelDimensions(width: 512, height: 512)
|
||||
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 {
|
||||
private let useDirectRendering: Bool
|
||||
private var itemNode: ReactionNode? = nil
|
||||
private var itemNodeIsEmbedded: Bool = false
|
||||
private let hapticFeedback = HapticFeedback()
|
||||
@ -970,9 +971,9 @@ public final class StandaloneReactionAnimation: ASDisplayNode {
|
||||
|
||||
private weak var targetView: UIView?
|
||||
|
||||
//private var colorCallbacks: [LOTColorValueCallback] = []
|
||||
|
||||
override public init() {
|
||||
public init(useDirectRendering: Bool = false) {
|
||||
self.useDirectRendering = useDirectRendering
|
||||
|
||||
super.init()
|
||||
|
||||
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)
|
||||
|
||||
let additionalAnimationNode = DefaultAnimatedStickerNodeImpl()
|
||||
let additionalAnimationNode: AnimatedStickerNode
|
||||
if self.useDirectRendering {
|
||||
additionalAnimationNode = DirectAnimatedStickerNode()
|
||||
} else {
|
||||
additionalAnimationNode = DefaultAnimatedStickerNodeImpl()
|
||||
}
|
||||
|
||||
let additionalAnimation: TelegramMediaFile
|
||||
if isLarge && !forceSmallEffectAnimation {
|
||||
@ -1139,7 +1145,7 @@ public final class StandaloneReactionAnimation: ASDisplayNode {
|
||||
}
|
||||
|
||||
var didBeginDismissAnimation = false
|
||||
let beginDismissAnimation: () -> Void = { [weak self] in
|
||||
let beginDismissAnimation: () -> Void = { [weak self, weak additionalAnimationNode] in
|
||||
if !didBeginDismissAnimation {
|
||||
didBeginDismissAnimation = true
|
||||
|
||||
@ -1150,9 +1156,11 @@ public final class StandaloneReactionAnimation: ASDisplayNode {
|
||||
}
|
||||
|
||||
if forceSmallEffectAnimation {
|
||||
additionalAnimationNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak additionalAnimationNode] _ in
|
||||
additionalAnimationNode?.removeFromSupernode()
|
||||
})
|
||||
if let additionalAnimationNode = additionalAnimationNode {
|
||||
additionalAnimationNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak additionalAnimationNode] _ in
|
||||
additionalAnimationNode?.removeFromSupernode()
|
||||
})
|
||||
}
|
||||
|
||||
mainAnimationCompleted = true
|
||||
intermediateCompletion()
|
||||
|
@ -48,6 +48,7 @@ public final class ReactionNode: ASDisplayNode, ReactionItemNode {
|
||||
let context: AccountContext
|
||||
let item: ReactionItem
|
||||
private let hasAppearAnimation: Bool
|
||||
private let useDirectRendering: Bool
|
||||
|
||||
private var animateInAnimationNode: AnimatedStickerNode?
|
||||
private let staticAnimationNode: AnimatedStickerNode
|
||||
@ -67,16 +68,17 @@ public final class ReactionNode: ASDisplayNode, ReactionItemNode {
|
||||
|
||||
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.item = item
|
||||
self.hasAppearAnimation = hasAppearAnimation
|
||||
self.useDirectRendering = useDirectRendering
|
||||
|
||||
self.staticAnimationNode = DefaultAnimatedStickerNodeImpl()
|
||||
self.staticAnimationNode = self.useDirectRendering ? DirectAnimatedStickerNode() : DefaultAnimatedStickerNodeImpl()
|
||||
|
||||
if hasAppearAnimation {
|
||||
self.staticAnimationNode.isHidden = true
|
||||
self.animateInAnimationNode = DefaultAnimatedStickerNodeImpl()
|
||||
self.animateInAnimationNode = self.useDirectRendering ? DirectAnimatedStickerNode() : DefaultAnimatedStickerNodeImpl()
|
||||
}
|
||||
|
||||
super.init()
|
||||
@ -146,7 +148,7 @@ public final class ReactionNode: ASDisplayNode, ReactionItemNode {
|
||||
}
|
||||
self.staticAnimationNode.play(firstFrame: false, fromIndex: 0)
|
||||
} else if isExpanded, self.animationNode == nil {
|
||||
let animationNode = DefaultAnimatedStickerNodeImpl()
|
||||
let animationNode: AnimatedStickerNode = self.useDirectRendering ? DirectAnimatedStickerNode() : DefaultAnimatedStickerNodeImpl()
|
||||
animationNode.automaticallyLoadFirstFrame = true
|
||||
self.animationNode = animationNode
|
||||
self.addSubnode(animationNode)
|
||||
@ -235,7 +237,7 @@ public final class ReactionNode: ASDisplayNode, ReactionItemNode {
|
||||
if self.animationNode == nil {
|
||||
if isPreviewing {
|
||||
if self.stillAnimationNode == nil {
|
||||
let stillAnimationNode = DefaultAnimatedStickerNodeImpl()
|
||||
let stillAnimationNode: AnimatedStickerNode = self.useDirectRendering ? DirectAnimatedStickerNode() : DefaultAnimatedStickerNodeImpl()
|
||||
self.stillAnimationNode = stillAnimationNode
|
||||
self.addSubnode(stillAnimationNode)
|
||||
|
||||
|
@ -4,9 +4,18 @@ import Postbox
|
||||
import SwiftSignalKit
|
||||
import MtProtoKit
|
||||
|
||||
enum MultiplexedRequestTarget: Equatable, Hashable {
|
||||
enum MultiplexedRequestTarget: Equatable, Hashable, CustomStringConvertible {
|
||||
case main(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 {
|
||||
|
@ -222,7 +222,7 @@
|
||||
"decimalSeparator": ",",
|
||||
"symbolOnLeft": false,
|
||||
"spaceBetweenAmountAndSymbol": true,
|
||||
"decimalDigits": 2
|
||||
"decimalDigits": 0
|
||||
},
|
||||
"BZD": {
|
||||
"code": "BZD",
|
||||
@ -321,7 +321,7 @@
|
||||
"decimalSeparator": ".",
|
||||
"symbolOnLeft": true,
|
||||
"spaceBetweenAmountAndSymbol": false,
|
||||
"decimalDigits": 2
|
||||
"decimalDigits": 0
|
||||
},
|
||||
"CZK": {
|
||||
"code": "CZK",
|
||||
@ -546,7 +546,7 @@
|
||||
"decimalSeparator": ",",
|
||||
"symbolOnLeft": true,
|
||||
"spaceBetweenAmountAndSymbol": false,
|
||||
"decimalDigits": 0
|
||||
"decimalDigits": 2
|
||||
},
|
||||
"ILS": {
|
||||
"code": "ILS",
|
||||
@ -573,7 +573,7 @@
|
||||
"decimalSeparator": ".",
|
||||
"symbolOnLeft": true,
|
||||
"spaceBetweenAmountAndSymbol": true,
|
||||
"decimalDigits": 2
|
||||
"decimalDigits": 3
|
||||
},
|
||||
"IRR": {
|
||||
"code": "IRR",
|
||||
@ -654,7 +654,7 @@
|
||||
"decimalSeparator": ".",
|
||||
"symbolOnLeft": false,
|
||||
"spaceBetweenAmountAndSymbol": false,
|
||||
"decimalDigits": 2
|
||||
"decimalDigits": 0
|
||||
},
|
||||
"KPW": {
|
||||
"code": "KPW",
|
||||
@ -825,7 +825,7 @@
|
||||
"decimalSeparator": ".",
|
||||
"symbolOnLeft": false,
|
||||
"spaceBetweenAmountAndSymbol": false,
|
||||
"decimalDigits": 2
|
||||
"decimalDigits": 1
|
||||
},
|
||||
"MTL": {
|
||||
"code": "MTL",
|
||||
@ -1014,7 +1014,7 @@
|
||||
"decimalSeparator": ",",
|
||||
"symbolOnLeft": true,
|
||||
"spaceBetweenAmountAndSymbol": true,
|
||||
"decimalDigits": 2
|
||||
"decimalDigits": 0
|
||||
},
|
||||
"QAR": {
|
||||
"code": "QAR",
|
||||
@ -1059,7 +1059,7 @@
|
||||
"decimalSeparator": ",",
|
||||
"symbolOnLeft": true,
|
||||
"spaceBetweenAmountAndSymbol": true,
|
||||
"decimalDigits": 2
|
||||
"decimalDigits": 0
|
||||
},
|
||||
"SAR": {
|
||||
"code": "SAR",
|
||||
@ -1302,7 +1302,7 @@
|
||||
"decimalSeparator": ".",
|
||||
"symbolOnLeft": true,
|
||||
"spaceBetweenAmountAndSymbol": false,
|
||||
"decimalDigits": 2
|
||||
"decimalDigits": 0
|
||||
},
|
||||
"USD": {
|
||||
"code": "USD",
|
||||
@ -1401,7 +1401,7 @@
|
||||
"decimalSeparator": ",",
|
||||
"symbolOnLeft": false,
|
||||
"spaceBetweenAmountAndSymbol": false,
|
||||
"decimalDigits": 2
|
||||
"decimalDigits": 0
|
||||
},
|
||||
"XPF": {
|
||||
"code": "XPF",
|
||||
@ -1410,7 +1410,7 @@
|
||||
"decimalSeparator": ".",
|
||||
"symbolOnLeft": false,
|
||||
"spaceBetweenAmountAndSymbol": false,
|
||||
"decimalDigits": 2
|
||||
"decimalDigits": 0
|
||||
},
|
||||
"YER": {
|
||||
"code": "YER",
|
||||
|
@ -826,6 +826,12 @@ final class ChatMessageAttachedContentNode: ASDisplayNode {
|
||||
if let statusSizeAndApply = statusSizeAndApply {
|
||||
adjustedBoundingSize.height += statusSizeAndApply.0.height
|
||||
adjustedLineHeight += statusSizeAndApply.0.height
|
||||
|
||||
if let imageFrame = imageFrame, statusSizeAndApply.0.height == 0.0 {
|
||||
if statusInText {
|
||||
adjustedBoundingSize.height = max(adjustedBoundingSize.height, imageFrame.maxY + 8.0 + 15.0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
adjustedBoundingSize.width = max(boundingWidth, adjustedBoundingSize.width)
|
||||
@ -975,6 +981,9 @@ final class ChatMessageAttachedContentNode: ASDisplayNode {
|
||||
var statusFrame = CGRect(origin: CGPoint(x: strongSelf.textNode.frame.minX, y: strongSelf.textNode.frame.maxY), size: statusSizeAndApply.0)
|
||||
if let imageFrame = imageFrame {
|
||||
statusFrame.origin.y = max(statusFrame.minY, imageFrame.maxY + 2.0)
|
||||
if statusFrame.height == 0.0 {
|
||||
statusFrame.origin.y += 14.0
|
||||
}
|
||||
}
|
||||
if strongSelf.statusNode.supernode == nil {
|
||||
strongSelf.addSubnode(strongSelf.statusNode)
|
||||
|
@ -2837,8 +2837,8 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
||||
|
||||
strongSelf.backgroundNode.updateLayout(size: backgroundFrame.size, transition: animation)
|
||||
animation.animator.updateFrame(layer: strongSelf.backgroundWallpaperNode.layer, frame: backgroundFrame, completion: nil)
|
||||
strongSelf.shadowNode.updateLayout(backgroundFrame: backgroundFrame, transition: animation.transition)
|
||||
strongSelf.backgroundWallpaperNode.updateFrame(backgroundFrame, transition: animation.transition)
|
||||
strongSelf.shadowNode.updateLayout(backgroundFrame: backgroundFrame, animator: animation.animator)
|
||||
strongSelf.backgroundWallpaperNode.updateFrame(backgroundFrame, animator: animation.animator)
|
||||
}
|
||||
|
||||
if let _ = strongSelf.backgroundNode.type {
|
||||
|
@ -659,12 +659,12 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
|
||||
case let .success(text, isPending):
|
||||
textString = NSAttributedString(string: text, font: textFont, textColor: messageTheme.primaryTextColor)
|
||||
|
||||
#if DEBUG
|
||||
/*#if DEBUG
|
||||
var isPending = isPending
|
||||
if "".isEmpty {
|
||||
isPending = true
|
||||
}
|
||||
#endif
|
||||
#endif*/
|
||||
|
||||
if isPending {
|
||||
let modifiedString = NSMutableAttributedString(attributedString: textString!)
|
||||
@ -756,7 +756,7 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
|
||||
|
||||
let statusLayoutInput: ChatMessageDateAndStatusNode.LayoutInput
|
||||
if let _ = textString {
|
||||
statusLayoutInput = .trailingContent(contentWidth: textLayout.trailingLineWidth, reactionSettings: reactionSettings)
|
||||
statusLayoutInput = .trailingContent(contentWidth: textLayout.hasRTL ? 1000.0 : textLayout.trailingLineWidth, reactionSettings: reactionSettings)
|
||||
} else {
|
||||
statusLayoutInput = .trailingContent(contentWidth: iconFrame == nil ? 1000.0 : controlAreaWidth, reactionSettings: reactionSettings)
|
||||
}
|
||||
@ -1116,7 +1116,7 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
|
||||
let _ = waveformView.update(
|
||||
transition: waveformTransition.withUserData(ComponentHostViewSkipSettingFrame()),
|
||||
component: AnyComponent(AudioWaveformComponent(
|
||||
backgroundColor: waveformColor,
|
||||
backgroundColor: isTranscriptionInProgress ? messageTheme.mediaInactiveControlColor : waveformColor,
|
||||
foregroundColor: messageTheme.mediaActiveControlColor,
|
||||
shimmerColor: isTranscriptionInProgress ? messageTheme.mediaActiveControlColor : nil,
|
||||
samples: audioWaveform?.samples ?? Data(),
|
||||
|
@ -738,7 +738,7 @@ public final class OngoingCallContext {
|
||||
private var signalingConnectionManager: QueueLocalObject<CallSignalingConnectionManager>?
|
||||
|
||||
public static func versions(includeExperimental: Bool, includeReference: Bool) -> [(version: String, supportsVideo: Bool)] {
|
||||
#if DEBUG
|
||||
#if os(iOS) && DEBUG
|
||||
if "".isEmpty {
|
||||
return [("5.0.0", true)]
|
||||
}
|
||||
|
@ -44,6 +44,7 @@ public protocol WallpaperBubbleBackgroundNode: ASDisplayNode {
|
||||
|
||||
func update(rect: CGRect, within containerSize: CGSize, transition: ContainedViewLayoutTransition)
|
||||
func update(rect: CGRect, within containerSize: CGSize, transition: CombinedTransition)
|
||||
func update(rect: CGRect, within containerSize: CGSize, animator: ControlledTransitionAnimator)
|
||||
func offset(value: CGPoint, animationCurve: ContainedViewLayoutTransitionCurve, duration: Double)
|
||||
func offsetSpring(value: CGFloat, duration: Double, damping: CGFloat)
|
||||
}
|
||||
@ -262,6 +263,23 @@ final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgroundNode
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func update(rect: CGRect, within containerSize: CGSize, animator: ControlledTransitionAnimator) {
|
||||
self.currentLayout = (rect, containerSize)
|
||||
|
||||
let shiftedContentsRect = CGRect(origin: CGPoint(x: rect.minX / containerSize.width, y: rect.minY / containerSize.height), size: CGSize(width: rect.width / containerSize.width, height: rect.height / containerSize.height))
|
||||
|
||||
animator.updateFrame(layer: self.contentNode.layer, frame: self.bounds, completion: nil)
|
||||
animator.updateContentsRect(layer: self.contentNode.layer, contentsRect: shiftedContentsRect, completion: nil)
|
||||
if let cleanWallpaperNode = self.cleanWallpaperNode {
|
||||
animator.updateFrame(layer: cleanWallpaperNode.layer, frame: self.bounds, completion: nil)
|
||||
animator.updateContentsRect(layer: cleanWallpaperNode.layer, contentsRect: shiftedContentsRect, completion: nil)
|
||||
}
|
||||
if let gradientWallpaperNode = self.gradientWallpaperNode {
|
||||
animator.updateFrame(layer: gradientWallpaperNode.layer, frame: self.bounds, completion: nil)
|
||||
animator.updateContentsRect(layer: gradientWallpaperNode.layer, contentsRect: shiftedContentsRect, completion: nil)
|
||||
}
|
||||
}
|
||||
|
||||
func update(rect: CGRect, within containerSize: CGSize, transition: CombinedTransition) {
|
||||
self.currentLayout = (rect, containerSize)
|
||||
@ -1177,6 +1195,23 @@ final class WallpaperBackgroundNodeMergedImpl: ASDisplayNode, WallpaperBackgroun
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func update(rect: CGRect, within containerSize: CGSize, animator: ControlledTransitionAnimator) {
|
||||
self.currentLayout = (rect, containerSize)
|
||||
|
||||
let shiftedContentsRect = CGRect(origin: CGPoint(x: rect.minX / containerSize.width, y: rect.minY / containerSize.height), size: CGSize(width: rect.width / containerSize.width, height: rect.height / containerSize.height))
|
||||
|
||||
animator.updateFrame(layer: self.contentNode.layer, frame: self.bounds, completion: nil)
|
||||
animator.updateContentsRect(layer: self.contentNode.layer, contentsRect: shiftedContentsRect, completion: nil)
|
||||
if let cleanWallpaperNode = self.cleanWallpaperNode {
|
||||
animator.updateFrame(layer: cleanWallpaperNode.layer, frame: self.bounds, completion: nil)
|
||||
animator.updateContentsRect(layer: cleanWallpaperNode.layer, contentsRect: shiftedContentsRect, completion: nil)
|
||||
}
|
||||
if let gradientWallpaperNode = self.gradientWallpaperNode {
|
||||
animator.updateFrame(layer: gradientWallpaperNode.layer, frame: self.bounds, completion: nil)
|
||||
animator.updateContentsRect(layer: gradientWallpaperNode.layer, contentsRect: shiftedContentsRect, completion: nil)
|
||||
}
|
||||
}
|
||||
|
||||
func update(rect: CGRect, within containerSize: CGSize, transition: CombinedTransition) {
|
||||
self.currentLayout = (rect, containerSize)
|
||||
|
Loading…
x
Reference in New Issue
Block a user