diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index cc0aa1e301..a93c850ef2 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -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"; diff --git a/submodules/AnimatedStickerNode/Sources/DirectAnimatedStickerNode.swift b/submodules/AnimatedStickerNode/Sources/DirectAnimatedStickerNode.swift new file mode 100644 index 0000000000..76ed877028 --- /dev/null +++ b/submodules/AnimatedStickerNode/Sources/DirectAnimatedStickerNode.swift @@ -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() + public var status: Signal { + 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) { + } +} diff --git a/submodules/ChatMessageBackground/Sources/ChatMessageBackground.swift b/submodules/ChatMessageBackground/Sources/ChatMessageBackground.swift index e212ae23f5..62fe7f3950 100644 --- a/submodules/ChatMessageBackground/Sources/ChatMessageBackground.swift +++ b/submodules/ChatMessageBackground/Sources/ChatMessageBackground.swift @@ -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 diff --git a/submodules/Display/Source/ContainedViewLayoutTransition.swift b/submodules/Display/Source/ContainedViewLayoutTransition.swift index ee118e1014..6ec8dccddc 100644 --- a/submodules/Display/Source/ContainedViewLayoutTransition.swift +++ b/submodules/Display/Source/ContainedViewLayoutTransition.swift @@ -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 diff --git a/submodules/ListMessageItem/Sources/ListMessageSnippetItemNode.swift b/submodules/ListMessageItem/Sources/ListMessageSnippetItemNode.swift index baf93f4a98..e5aecb7f92 100644 --- a/submodules/ListMessageItem/Sources/ListMessageSnippetItemNode.swift +++ b/submodules/ListMessageItem/Sources/ListMessageSnippetItemNode.swift @@ -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 { diff --git a/submodules/Postbox/Sources/MediaBoxFile.swift b/submodules/Postbox/Sources/MediaBoxFile.swift index 0f3dd1c520..578f1a82b2 100644 --- a/submodules/Postbox/Sources/MediaBoxFile.swift +++ b/submodules/Postbox/Sources/MediaBoxFile.swift @@ -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 = range.lowerBound ..< min(maxValue, range.upperBound) + let clippedUpperBound = min(maxValue, range.upperBound) + let clippedRange: Range = min(range.lowerBound, clippedUpperBound) ..< clippedUpperBound let clippedRangeSet = RangeSet(clippedRange) if self.ranges.isSuperset(of: clippedRangeSet) { diff --git a/submodules/PremiumUI/Sources/ReactionsCarouselComponent.swift b/submodules/PremiumUI/Sources/ReactionsCarouselComponent.swift index 947e700882..cbc118f748 100644 --- a/submodules/PremiumUI/Sources/ReactionsCarouselComponent.swift +++ b/submodules/PremiumUI/Sources/ReactionsCarouselComponent.swift @@ -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) diff --git a/submodules/PremiumUI/Sources/StickersCarouselComponent.swift b/submodules/PremiumUI/Sources/StickersCarouselComponent.swift index b1233d1be9..01dab1d38d 100644 --- a/submodules/PremiumUI/Sources/StickersCarouselComponent.swift +++ b/submodules/PremiumUI/Sources/StickersCarouselComponent.swift @@ -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 + } } } } diff --git a/submodules/ReactionSelectionNode/Sources/ReactionContextNode.swift b/submodules/ReactionSelectionNode/Sources/ReactionContextNode.swift index 9e54484787..af13929f5a 100644 --- a/submodules/ReactionSelectionNode/Sources/ReactionContextNode.swift +++ b/submodules/ReactionSelectionNode/Sources/ReactionContextNode.swift @@ -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() diff --git a/submodules/ReactionSelectionNode/Sources/ReactionSelectionNode.swift b/submodules/ReactionSelectionNode/Sources/ReactionSelectionNode.swift index 00956b337e..7d3fa1470d 100644 --- a/submodules/ReactionSelectionNode/Sources/ReactionSelectionNode.swift +++ b/submodules/ReactionSelectionNode/Sources/ReactionSelectionNode.swift @@ -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) diff --git a/submodules/TelegramCore/Sources/Network/MultiplexedRequestManager.swift b/submodules/TelegramCore/Sources/Network/MultiplexedRequestManager.swift index 29eef5bcd7..16c1c22e64 100644 --- a/submodules/TelegramCore/Sources/Network/MultiplexedRequestManager.swift +++ b/submodules/TelegramCore/Sources/Network/MultiplexedRequestManager.swift @@ -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 { diff --git a/submodules/TelegramUI/Resources/currencies.json b/submodules/TelegramUI/Resources/currencies.json index 93730660d1..a4ffa9567d 100644 --- a/submodules/TelegramUI/Resources/currencies.json +++ b/submodules/TelegramUI/Resources/currencies.json @@ -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", diff --git a/submodules/TelegramUI/Sources/ChatMessageAttachedContentNode.swift b/submodules/TelegramUI/Sources/ChatMessageAttachedContentNode.swift index 8f7d0bc68c..8700070dd1 100644 --- a/submodules/TelegramUI/Sources/ChatMessageAttachedContentNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageAttachedContentNode.swift @@ -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) diff --git a/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift b/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift index 126228d08e..51c7c8505f 100644 --- a/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift @@ -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 { diff --git a/submodules/TelegramUI/Sources/ChatMessageInteractiveFileNode.swift b/submodules/TelegramUI/Sources/ChatMessageInteractiveFileNode.swift index 43953f3788..291367eac0 100644 --- a/submodules/TelegramUI/Sources/ChatMessageInteractiveFileNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageInteractiveFileNode.swift @@ -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(), diff --git a/submodules/TelegramVoip/Sources/OngoingCallContext.swift b/submodules/TelegramVoip/Sources/OngoingCallContext.swift index c4593693bc..39523582e1 100644 --- a/submodules/TelegramVoip/Sources/OngoingCallContext.swift +++ b/submodules/TelegramVoip/Sources/OngoingCallContext.swift @@ -738,7 +738,7 @@ public final class OngoingCallContext { private var signalingConnectionManager: QueueLocalObject? 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)] } diff --git a/submodules/WallpaperBackgroundNode/Sources/WallpaperBackgroundNode.swift b/submodules/WallpaperBackgroundNode/Sources/WallpaperBackgroundNode.swift index cbb9a895f5..10149a2ec8 100644 --- a/submodules/WallpaperBackgroundNode/Sources/WallpaperBackgroundNode.swift +++ b/submodules/WallpaperBackgroundNode/Sources/WallpaperBackgroundNode.swift @@ -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)