diff --git a/submodules/BrowserUI/Sources/BrowserScreen.swift b/submodules/BrowserUI/Sources/BrowserScreen.swift index bacef153ea..1c5b8d363f 100644 --- a/submodules/BrowserUI/Sources/BrowserScreen.swift +++ b/submodules/BrowserUI/Sources/BrowserScreen.swift @@ -27,7 +27,7 @@ private final class InstantPageContextExtractedContentSource: ContextExtractedCo guard let navigationBar = self.navigationBar else { return nil } - return ContextControllerTakeViewInfo(contentContainingNode: navigationBar.contextSourceNode, contentAreaInScreenSpace: navigationBar.convert(navigationBar.contextSourceNode.frame.offsetBy(dx: 0.0, dy: 40.0), to: nil)) + return ContextControllerTakeViewInfo(containingItem: .node(navigationBar.contextSourceNode), contentAreaInScreenSpace: navigationBar.convert(navigationBar.contextSourceNode.frame.offsetBy(dx: 0.0, dy: 40.0), to: nil)) } func putBack() -> ContextControllerPutBackViewInfo? { diff --git a/submodules/CallListUI/Sources/CallListController.swift b/submodules/CallListUI/Sources/CallListController.swift index 97a3577e35..501e8e1cc6 100644 --- a/submodules/CallListUI/Sources/CallListController.swift +++ b/submodules/CallListUI/Sources/CallListController.swift @@ -366,7 +366,7 @@ public final class CallListController: TelegramBaseController { } func takeView() -> ContextControllerTakeViewInfo? { - return ContextControllerTakeViewInfo(contentContainingNode: self.sourceNode, contentAreaInScreenSpace: UIScreen.main.bounds) + return ContextControllerTakeViewInfo(containingItem: .node(self.sourceNode), contentAreaInScreenSpace: UIScreen.main.bounds) } func putBack() -> ContextControllerPutBackViewInfo? { @@ -510,7 +510,7 @@ private final class CallListTabBarContextExtractedContentSource: ContextExtracte } func takeView() -> ContextControllerTakeViewInfo? { - return ContextControllerTakeViewInfo(contentContainingNode: self.sourceNode, contentAreaInScreenSpace: UIScreen.main.bounds) + return ContextControllerTakeViewInfo(containingItem: .node(self.sourceNode), contentAreaInScreenSpace: UIScreen.main.bounds) } func putBack() -> ContextControllerPutBackViewInfo? { diff --git a/submodules/ChatListUI/Sources/ChatListController.swift b/submodules/ChatListUI/Sources/ChatListController.swift index c93b2712c2..0ff20e81de 100644 --- a/submodules/ChatListUI/Sources/ChatListController.swift +++ b/submodules/ChatListUI/Sources/ChatListController.swift @@ -3209,7 +3209,7 @@ private final class ChatListTabBarContextExtractedContentSource: ContextExtracte } func takeView() -> ContextControllerTakeViewInfo? { - return ContextControllerTakeViewInfo(contentContainingNode: self.sourceNode, contentAreaInScreenSpace: UIScreen.main.bounds) + return ContextControllerTakeViewInfo(containingItem: .node(self.sourceNode), contentAreaInScreenSpace: UIScreen.main.bounds) } func putBack() -> ContextControllerPutBackViewInfo? { @@ -3232,7 +3232,7 @@ private final class ChatListHeaderBarContextExtractedContentSource: ContextExtra } func takeView() -> ContextControllerTakeViewInfo? { - return ContextControllerTakeViewInfo(contentContainingNode: self.sourceNode, contentAreaInScreenSpace: UIScreen.main.bounds) + return ContextControllerTakeViewInfo(containingItem: .node(self.sourceNode), contentAreaInScreenSpace: UIScreen.main.bounds) } func putBack() -> ContextControllerPutBackViewInfo? { diff --git a/submodules/ChatListUI/Sources/ChatListSearchContainerNode.swift b/submodules/ChatListUI/Sources/ChatListSearchContainerNode.swift index b1d69b16eb..bcce08a12f 100644 --- a/submodules/ChatListUI/Sources/ChatListSearchContainerNode.swift +++ b/submodules/ChatListUI/Sources/ChatListSearchContainerNode.swift @@ -1406,7 +1406,7 @@ private final class MessageContextExtractedContentSource: ContextExtractedConten } func takeView() -> ContextControllerTakeViewInfo? { - return ContextControllerTakeViewInfo(contentContainingNode: self.sourceNode, contentAreaInScreenSpace: UIScreen.main.bounds) + return ContextControllerTakeViewInfo(containingItem: .node(self.sourceNode), contentAreaInScreenSpace: UIScreen.main.bounds) } func putBack() -> ContextControllerPutBackViewInfo? { diff --git a/submodules/ChatListUI/Sources/Node/ChatListNode.swift b/submodules/ChatListUI/Sources/Node/ChatListNode.swift index 4938f9b6b8..c88e6b7822 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListNode.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListNode.swift @@ -1782,12 +1782,14 @@ public final class ChatListNode: ListView { } var options = transition.options - if !options.contains(.AnimateInsertion) { - options.insert(.PreferSynchronousDrawing) - options.insert(.PreferSynchronousResourceLoading) - } - if options.contains(.AnimateCrossfade) && !self.isDeceleratingAfterTracking { - options.insert(.PreferSynchronousDrawing) + if self.view.window != nil { + if !options.contains(.AnimateInsertion) { + options.insert(.PreferSynchronousDrawing) + options.insert(.PreferSynchronousResourceLoading) + } + if options.contains(.AnimateCrossfade) && !self.isDeceleratingAfterTracking { + options.insert(.PreferSynchronousDrawing) + } } var scrollToItem = transition.scrollToItem diff --git a/submodules/Components/ReactionButtonListComponent/Sources/ReactionButtonListComponent.swift b/submodules/Components/ReactionButtonListComponent/Sources/ReactionButtonListComponent.swift index d2a6de85b4..a615715632 100644 --- a/submodules/Components/ReactionButtonListComponent/Sources/ReactionButtonListComponent.swift +++ b/submodules/Components/ReactionButtonListComponent/Sources/ReactionButtonListComponent.swift @@ -32,8 +32,25 @@ public final class ReactionIconView: PortalSourceView { } } -public final class ReactionButtonAsyncNode: ContextControllerSourceNode { - fileprivate final class ContainerButtonNode: HighlightTrackingButtonNode { +private final class ReactionImageCache { + static let shared = ReactionImageCache() + + private var images: [String: UIImage] = [:] + + init() { + } + + func get(reaction: String) -> UIImage? { + return self.images[reaction] + } + + func put(reaction: String, image: UIImage) { + self.images[reaction] = image + } +} + +public final class ReactionButtonAsyncNode: ContextControllerSourceView { + fileprivate final class ContainerButtonNode: UIButton { struct Colors: Equatable { var background: UInt32 var foreground: UInt32 @@ -65,8 +82,17 @@ public final class ReactionButtonAsyncNode: ContextControllerSourceNode { private var animationState: AnimationState? private var animator: ConstantDisplayLinkAnimator? - init() { - super.init(pointerStyle: nil) + override init(frame: CGRect) { + super.init(frame: CGRect()) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func reset() { + self.layer.contents = nil + self.currentLayout = nil } func update(layout: Layout) { @@ -130,130 +156,142 @@ public final class ReactionButtonAsyncNode: ContextControllerSourceNode { } } - let image = generateImage(layout.size, rotatedContext: { size, context in - context.clear(CGRect(origin: CGPoint(), size: size)) - UIGraphicsPushContext(context) + let isExtracted = self.isExtracted + let animationState = self.animationState + + DispatchQueue.global().async { [weak self] in + var image: UIImage? - func drawContents(colors: Colors) { - let backgroundColor: UIColor - let foregroundColor: UIColor - if self.isExtracted { - backgroundColor = UIColor(argb: colors.extractedBackground) - foregroundColor = UIColor(argb: colors.extractedForeground) - } else { - backgroundColor = UIColor(argb: colors.background) - foregroundColor = UIColor(argb: colors.foreground) - } - - context.setBlendMode(.copy) - - context.setFillColor(backgroundColor.cgColor) - context.fillEllipse(in: CGRect(origin: CGPoint(), size: CGSize(width: size.height, height: size.height))) - context.fillEllipse(in: CGRect(origin: CGPoint(x: size.width - size.height, y: 0.0), size: CGSize(width: size.height, height: size.height))) - context.fill(CGRect(origin: CGPoint(x: size.height / 2.0, y: 0.0), size: CGSize(width: size.width - size.height, height: size.height))) - - if let counter = layout.counter { - let isForegroundTransparent = foregroundColor.alpha < 1.0 - context.setBlendMode(isForegroundTransparent ? .copy : .normal) + if true { + image = generateImage(layout.size, rotatedContext: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + UIGraphicsPushContext(context) - let textOrigin: CGFloat = 36.0 - - var rightTextOrigin = textOrigin + totalComponentWidth - - let animationFraction: CGFloat - if let animationState = self.animationState, animationState.fromCounter != nil { - animationFraction = max(0.0, min(1.0, (CACurrentMediaTime() - animationState.startTime) / animationState.duration)) - } else { - animationFraction = 1.0 - } - - for i in (0 ..< counter.components.count).reversed() { - let component = counter.components[i] - var componentAlpha: CGFloat = 1.0 - var componentVerticalOffset: CGFloat = 0.0 + func drawContents(colors: Colors) { + let backgroundColor: UIColor + let foregroundColor: UIColor + if isExtracted { + backgroundColor = UIColor(argb: colors.extractedBackground) + foregroundColor = UIColor(argb: colors.extractedForeground) + } else { + backgroundColor = UIColor(argb: colors.background) + foregroundColor = UIColor(argb: colors.foreground) + } - if let animationState = self.animationState, let fromCounter = animationState.fromCounter { - let reverseIndex = counter.components.count - 1 - i - if reverseIndex < fromCounter.components.count { - let previousComponent = fromCounter.components[fromCounter.components.count - 1 - reverseIndex] + context.setBlendMode(.copy) + + context.setFillColor(backgroundColor.cgColor) + context.fillEllipse(in: CGRect(origin: CGPoint(), size: CGSize(width: size.height, height: size.height))) + context.fillEllipse(in: CGRect(origin: CGPoint(x: size.width - size.height, y: 0.0), size: CGSize(width: size.height, height: size.height))) + context.fill(CGRect(origin: CGPoint(x: size.height / 2.0, y: 0.0), size: CGSize(width: size.width - size.height, height: size.height))) + + if let counter = layout.counter { + let isForegroundTransparent = foregroundColor.alpha < 1.0 + context.setBlendMode(isForegroundTransparent ? .copy : .normal) + + let textOrigin: CGFloat = 36.0 + + var rightTextOrigin = textOrigin + totalComponentWidth + + let animationFraction: CGFloat + if let animationState = animationState, animationState.fromCounter != nil { + animationFraction = max(0.0, min(1.0, (CACurrentMediaTime() - animationState.startTime) / animationState.duration)) + } else { + animationFraction = 1.0 + } + + for i in (0 ..< counter.components.count).reversed() { + let component = counter.components[i] + var componentAlpha: CGFloat = 1.0 + var componentVerticalOffset: CGFloat = 0.0 - if previousComponent != component { - componentAlpha = animationFraction - componentVerticalOffset = -(1.0 - animationFraction) * 8.0 - if previousComponent.string < component.string { - componentVerticalOffset = -componentVerticalOffset + if let animationState = animationState, let fromCounter = animationState.fromCounter { + let reverseIndex = counter.components.count - 1 - i + if reverseIndex < fromCounter.components.count { + let previousComponent = fromCounter.components[fromCounter.components.count - 1 - reverseIndex] + + if previousComponent != component { + componentAlpha = animationFraction + componentVerticalOffset = -(1.0 - animationFraction) * 8.0 + if previousComponent.string < component.string { + componentVerticalOffset = -componentVerticalOffset + } + + let previousComponentAlpha = 1.0 - componentAlpha + var previousComponentVerticalOffset = animationFraction * 8.0 + if previousComponent.string < component.string { + previousComponentVerticalOffset = -previousComponentVerticalOffset + } + + var componentOrigin = rightTextOrigin - previousComponent.bounds.width + componentOrigin = max(componentOrigin, layout.size.height / 2.0 + UIScreenPixel) + let previousColor: UIColor + if isForegroundTransparent { + previousColor = foregroundColor.mixedWith(backgroundColor, alpha: 1.0 - previousComponentAlpha) + } else { + previousColor = foregroundColor.withMultipliedAlpha(previousComponentAlpha) + } + let string = NSAttributedString(string: previousComponent.string, font: Font.medium(11.0), textColor: previousColor) + string.draw(at: previousComponent.bounds.origin.offsetBy(dx: componentOrigin, dy: floorToScreenPixels(size.height - previousComponent.bounds.height) / 2.0 + previousComponentVerticalOffset)) + } } - - let previousComponentAlpha = 1.0 - componentAlpha - var previousComponentVerticalOffset = animationFraction * 8.0 - if previousComponent.string < component.string { - previousComponentVerticalOffset = -previousComponentVerticalOffset - } - - var componentOrigin = rightTextOrigin - previousComponent.bounds.width - componentOrigin = max(componentOrigin, layout.size.height / 2.0 + UIScreenPixel) - let previousColor: UIColor - if isForegroundTransparent { - previousColor = foregroundColor.mixedWith(backgroundColor, alpha: 1.0 - previousComponentAlpha) - } else { - previousColor = foregroundColor.withMultipliedAlpha(previousComponentAlpha) - } - let string = NSAttributedString(string: previousComponent.string, font: Font.medium(11.0), textColor: previousColor) - string.draw(at: previousComponent.bounds.origin.offsetBy(dx: componentOrigin, dy: floorToScreenPixels(size.height - previousComponent.bounds.height) / 2.0 + previousComponentVerticalOffset)) } + + let componentOrigin = rightTextOrigin - component.bounds.width + let currentColor: UIColor + if isForegroundTransparent { + currentColor = foregroundColor.mixedWith(backgroundColor, alpha: 1.0 - componentAlpha) + } else { + currentColor = foregroundColor.withMultipliedAlpha(componentAlpha) + } + let string = NSAttributedString(string: component.string, font: Font.medium(11.0), textColor: currentColor) + string.draw(at: component.bounds.origin.offsetBy(dx: componentOrigin, dy: floorToScreenPixels(size.height - component.bounds.height) / 2.0 + componentVerticalOffset)) + + rightTextOrigin -= component.bounds.width } } - - let componentOrigin = rightTextOrigin - component.bounds.width - let currentColor: UIColor - if isForegroundTransparent { - currentColor = foregroundColor.mixedWith(backgroundColor, alpha: 1.0 - componentAlpha) - } else { - currentColor = foregroundColor.withMultipliedAlpha(componentAlpha) + } + + if let animationState = animationState, animationState.fromColors.isSelected != layout.colors.isSelected { + var animationFraction: CGFloat = max(0.0, min(1.0, (CACurrentMediaTime() - animationState.startTime) / animationState.duration)) + if !layout.colors.isSelected { + animationFraction = 1.0 - animationFraction } - let string = NSAttributedString(string: component.string, font: Font.medium(11.0), textColor: currentColor) - string.draw(at: component.bounds.origin.offsetBy(dx: componentOrigin, dy: floorToScreenPixels(size.height - component.bounds.height) / 2.0 + componentVerticalOffset)) - rightTextOrigin -= component.bounds.width + let center = CGPoint(x: 21.0, y: size.height / 2.0) + let diameter = 0.0 * (1.0 - animationFraction) + (size.width - center.x) * 2.0 * animationFraction + + context.beginPath() + context.addEllipse(in: CGRect(origin: CGPoint(x: center.x - diameter / 2.0, y: center.y - diameter / 2.0), size: CGSize(width: diameter, height: diameter))) + context.clip(using: .evenOdd) + drawContents(colors: layout.colors.isSelected ? layout.colors : animationState.fromColors) + + context.resetClip() + + context.beginPath() + context.addRect(CGRect(origin: CGPoint(), size: size)) + context.addEllipse(in: CGRect(origin: CGPoint(x: center.x - diameter / 2.0, y: center.y - diameter / 2.0), size: CGSize(width: diameter, height: diameter))) + context.clip(using: .evenOdd) + drawContents(colors: layout.colors.isSelected ? animationState.fromColors : layout.colors) + } else { + drawContents(colors: layout.colors) + } + + UIGraphicsPopContext() + }) + } + + DispatchQueue.main.async { + if let strongSelf = self, let image = image { + let previousContents = strongSelf.layer.contents + + ASDisplayNodeSetResizableContents(strongSelf.layer, image) + + if animated, let previousContents = previousContents { + strongSelf.layer.animate(from: previousContents as! CGImage, to: image.cgImage!, keyPath: "contents", timingFunction: CAMediaTimingFunctionName.linear.rawValue, duration: 0.2) } } } - - if let animationState = self.animationState, animationState.fromColors.isSelected != layout.colors.isSelected { - var animationFraction: CGFloat = max(0.0, min(1.0, (CACurrentMediaTime() - animationState.startTime) / animationState.duration)) - if !layout.colors.isSelected { - animationFraction = 1.0 - animationFraction - } - - let center = CGPoint(x: 21.0, y: size.height / 2.0) - let diameter = 0.0 * (1.0 - animationFraction) + (size.width - center.x) * 2.0 * animationFraction - - context.beginPath() - context.addEllipse(in: CGRect(origin: CGPoint(x: center.x - diameter / 2.0, y: center.y - diameter / 2.0), size: CGSize(width: diameter, height: diameter))) - context.clip(using: .evenOdd) - drawContents(colors: layout.colors.isSelected ? layout.colors : animationState.fromColors) - - context.resetClip() - - context.beginPath() - context.addRect(CGRect(origin: CGPoint(), size: size)) - context.addEllipse(in: CGRect(origin: CGPoint(x: center.x - diameter / 2.0, y: center.y - diameter / 2.0), size: CGSize(width: diameter, height: diameter))) - context.clip(using: .evenOdd) - drawContents(colors: layout.colors.isSelected ? animationState.fromColors : layout.colors) - } else { - drawContents(colors: layout.colors) - } - - UIGraphicsPopContext() - })//?.stretchableImage(withLeftCapWidth: Int(layout.baseSize.height / 2.0), topCapHeight: Int(layout.baseSize.height / 2.0)) - if let image = image { - let previousContents = self.layer.contents - - ASDisplayNodeSetResizableContents(self.layer, image) - - if animated, let previousContents = previousContents { - self.layer.animate(from: previousContents as! CGImage, to: image.cgImage!, keyPath: "contents", timingFunction: CAMediaTimingFunctionName.linear.rawValue, duration: 0.2) - } } } } @@ -462,9 +500,9 @@ public final class ReactionButtonAsyncNode: ContextControllerSourceNode { private var layout: Layout? - public let containerNode: ContextExtractedContentContainingNode + public let containerView: ContextExtractedContentContainingView private let buttonNode: ContainerButtonNode - public let iconView: ReactionIconView + public var iconView: ReactionIconView? private var avatarsView: AnimatedAvatarSetView? private let iconImageDisposable = MetaDisposable() @@ -481,42 +519,40 @@ public final class ReactionButtonAsyncNode: ContextControllerSourceNode { } } - override init() { - self.containerNode = ContextExtractedContentContainingNode() + override init(frame: CGRect) { + self.containerView = ContextExtractedContentContainingView() self.buttonNode = ContainerButtonNode() self.iconView = ReactionIconView() - self.iconView.isUserInteractionEnabled = false + self.iconView?.isUserInteractionEnabled = false - super.init() + super.init(frame: frame) - self.targetNodeForActivationProgress = self.containerNode.contentNode + self.targetViewForActivationProgress = self.containerView.contentView - self.addSubnode(self.containerNode) - self.containerNode.contentNode.addSubnode(self.buttonNode) - self.buttonNode.view.addSubview(self.iconView) - - self.buttonNode.addTarget(self, action: #selector(self.pressed), forControlEvents: .touchUpInside) - - self.buttonNode.highligthedChanged = { [weak self] highlighted in - guard let strongSelf = self else { - return - } - let _ = strongSelf - if highlighted { - } else { - } + self.addSubview(self.containerView) + self.containerView.contentView.addSubview(self.buttonNode) + if let iconView = self.iconView { + self.buttonNode.addSubview(iconView) } + self.buttonNode.addTarget(self, action: #selector(self.pressed), for: .touchUpInside) + self.isGestureEnabled = true self.beginDelay = 0.0 - self.containerNode.willUpdateIsExtractedToContextPreview = { [weak self] isExtracted, _ in + self.containerView.willUpdateIsExtractedToContextPreview = { [weak self] isExtracted, _ in guard let strongSelf = self else { return } strongSelf.buttonNode.updateIsExtracted(isExtracted: isExtracted, animated: true) } + + if self.activateAfterCompletion { + self.contextGesture?.activatedAfterCompletion = { [weak self] in + self?.pressed() + } + } } required init?(coder aDecoder: NSCoder) { @@ -527,14 +563,11 @@ public final class ReactionButtonAsyncNode: ContextControllerSourceNode { self.iconImageDisposable.dispose() } - override public func didLoad() { - super.didLoad() + func reset() { + self.iconView?.imageView.image = nil + self.layout = nil - if self.activateAfterCompletion { - self.contextGesture?.activatedAfterCompletion = { [weak self] in - self?.pressed() - } - } + self.buttonNode.reset() } @objc private func pressed() { @@ -545,43 +578,73 @@ public final class ReactionButtonAsyncNode: ContextControllerSourceNode { } fileprivate func apply(layout: Layout, animation: ListViewItemUpdateAnimation) { - self.containerNode.frame = CGRect(origin: CGPoint(), size: layout.size) - self.containerNode.contentNode.frame = CGRect(origin: CGPoint(), size: layout.size) - self.containerNode.contentRect = CGRect(origin: CGPoint(), size: layout.size) + self.containerView.frame = CGRect(origin: CGPoint(), size: layout.size) + self.containerView.contentView.frame = CGRect(origin: CGPoint(), size: layout.size) + self.containerView.contentRect = CGRect(origin: CGPoint(), size: layout.size) animation.animator.updateFrame(layer: self.buttonNode.layer, frame: CGRect(origin: CGPoint(), size: layout.size), completion: nil) self.buttonNode.update(layout: layout.backgroundLayout) - animation.animator.updateFrame(layer: self.iconView.layer, frame: layout.imageFrame, completion: nil) - self.iconView.update(size: layout.imageFrame.size, transition: animation.transition) - - if self.layout?.spec.component.reaction != layout.spec.component.reaction { - if let file = layout.spec.component.reaction.centerAnimation { - self.iconImageDisposable.set((reactionStaticImage(context: layout.spec.component.context, animation: file, pixelSize: CGSize(width: 64.0 * UIScreenScale, height: 64.0 * UIScreenScale)) - |> deliverOnMainQueue).start(next: { [weak self] data in - guard let strongSelf = self else { - return - } - - if data.isComplete, let dataValue = try? Data(contentsOf: URL(fileURLWithPath: data.path)) { - if let image = UIImage(data: dataValue) { - strongSelf.iconView.imageView.image = image + if let iconView = self.iconView { + animation.animator.updateFrame(layer: iconView.layer, frame: layout.imageFrame, completion: nil) + iconView.update(size: layout.imageFrame.size, transition: animation.transition) + + if self.layout?.spec.component.reaction != layout.spec.component.reaction { + if let file = layout.spec.component.reaction.centerAnimation { + if let image = ReactionImageCache.shared.get(reaction: layout.spec.component.reaction.value) { + iconView.imageView.image = image + } else { + self.iconImageDisposable.set((reactionStaticImage(context: layout.spec.component.context, animation: file, pixelSize: CGSize(width: 32.0 * UIScreenScale, height: 32.0 * UIScreenScale)) + |> filter { data in + return data.isComplete } - } - })) - } else if let legacyIcon = layout.spec.component.reaction.legacyIcon { - self.iconImageDisposable.set((layout.spec.component.context.account.postbox.mediaBox.resourceData(legacyIcon.resource) - |> deliverOnMainQueue).start(next: { [weak self] data in - guard let strongSelf = self else { - return - } - - if data.complete, let dataValue = try? Data(contentsOf: URL(fileURLWithPath: data.path)) { - if let image = WebP.convert(fromWebP: dataValue) { - strongSelf.iconView.imageView.image = image + |> take(1) + |> map { data -> UIImage? in + if data.isComplete, let dataValue = try? Data(contentsOf: URL(fileURLWithPath: data.path)) { + if let image = UIImage(data: dataValue) { + return image.precomposed() + } else { + print("Could not decode image") + } + } else { + print("Incomplete data") + } + return nil } + |> deliverOnMainQueue).start(next: { [weak self] image in + guard let strongSelf = self else { + return + } + + if let image = image { + strongSelf.iconView?.imageView.image = image + ReactionImageCache.shared.put(reaction: layout.spec.component.reaction.value, image: image) + } + })) } - })) + } else if let legacyIcon = layout.spec.component.reaction.legacyIcon { + self.iconImageDisposable.set((layout.spec.component.context.account.postbox.mediaBox.resourceData(legacyIcon.resource) + |> deliverOn(Queue.concurrentDefaultQueue()) + |> map { data -> UIImage? in + if data.complete, let dataValue = try? Data(contentsOf: URL(fileURLWithPath: data.path)) { + if let image = WebP.convert(fromWebP: dataValue) { + if #available(iOS 15.0, iOSApplicationExtension 15.0, *) { + return image.preparingForDisplay() + } else { + return image.precomposed() + } + } + } + return nil + } + |> deliverOnMainQueue).start(next: { [weak self] image in + guard let strongSelf = self else { + return + } + + strongSelf.iconView?.imageView.image = image + })) + } } } @@ -593,7 +656,7 @@ public final class ReactionButtonAsyncNode: ContextControllerSourceNode { avatarsView = AnimatedAvatarSetView() avatarsView.isUserInteractionEnabled = false self.avatarsView = avatarsView - self.buttonNode.view.addSubview(avatarsView) + self.buttonNode.addSubview(avatarsView) } let content = AnimatedAvatarSetContext().update(peers: layout.spec.component.avatarPeers, animated: false) let avatarsSize = avatarsView.update( @@ -620,8 +683,8 @@ public final class ReactionButtonAsyncNode: ContextControllerSourceNode { self.layout = layout } - public static func asyncLayout(_ view: ReactionButtonAsyncNode?) -> (ReactionButtonComponent) -> (size: CGSize, apply: (_ animation: ListViewItemUpdateAnimation) -> ReactionButtonAsyncNode) { - let currentLayout = view?.layout + public static func asyncLayout(_ item: ReactionNodePool.Item?) -> (ReactionButtonComponent) -> (size: CGSize, apply: (_ animation: ListViewItemUpdateAnimation) -> ReactionNodePool.Item) { + let currentLayout = item?.view.layout return { component in let spec = Layout.Spec(component: component) @@ -635,17 +698,17 @@ public final class ReactionButtonAsyncNode: ContextControllerSourceNode { return (size: layout.size, apply: { animation in var animation = animation - let updatedView: ReactionButtonAsyncNode - if let view = view { - updatedView = view + let updatedItem: ReactionNodePool.Item + if let item = item { + updatedItem = item } else { - updatedView = ReactionButtonAsyncNode() + updatedItem = ReactionNodePool.shared.take() animation = .None } - updatedView.apply(layout: layout, animation: animation) + updatedItem.view.apply(layout: layout, animation: animation) - return updatedView + return updatedItem }) } } @@ -751,6 +814,39 @@ public final class ReactionButtonComponent: Equatable { } } +public final class ReactionNodePool { + static let shared = ReactionNodePool() + + public final class Item { + public let view: ReactionButtonAsyncNode + private weak var pool: ReactionNodePool? + + init(view: ReactionButtonAsyncNode, pool: ReactionNodePool) { + self.view = view + self.pool = pool + } + + deinit { + self.pool?.putBack(view: self.view) + } + } + + private var views: [ReactionButtonAsyncNode] = [] + + func putBack(view: ReactionButtonAsyncNode) { + view.reset() + self.views.append(view) + } + + func take() -> Item { + if !self.views.isEmpty { + return Item(view: self.views.removeLast(), pool: self) + } else { + return Item(view: ReactionButtonAsyncNode(), pool: self) + } + } +} + public final class ReactionButtonsAsyncLayoutContainer { public struct Reaction { public var reaction: ReactionButtonComponent.Reaction @@ -783,15 +879,15 @@ public final class ReactionButtonsAsyncLayoutContainer { public struct ApplyResult { public struct Item { public var value: String - public var node: ReactionButtonAsyncNode + public var node: ReactionNodePool.Item public var size: CGSize } public var items: [Item] - public var removedNodes: [ReactionButtonAsyncNode] + public var removedNodes: [ReactionNodePool.Item] } - public private(set) var buttons: [String: ReactionButtonAsyncNode] = [:] + public private(set) var buttons: [String: ReactionNodePool.Item] = [:] public init() { } @@ -804,7 +900,7 @@ public final class ReactionButtonsAsyncLayoutContainer { constrainedWidth: CGFloat ) -> Result { var items: [Result.Item] = [] - var applyItems: [(key: String, size: CGSize, apply: (_ animation: ListViewItemUpdateAnimation) -> ReactionButtonAsyncNode)] = [] + var applyItems: [(key: String, size: CGSize, apply: (_ animation: ListViewItemUpdateAnimation) -> ReactionNodePool.Item)] = [] var validIds = Set() for reaction in reactions.sorted(by: { lhs, rhs in @@ -856,10 +952,10 @@ public final class ReactionButtonsAsyncLayoutContainer { removeIds.append(id) } } - var removedNodes: [ReactionButtonAsyncNode] = [] + var removedNodes: [ReactionNodePool.Item] = [] for id in removeIds { - if let node = self.buttons.removeValue(forKey: id) { - removedNodes.append(node) + if let item = self.buttons.removeValue(forKey: id) { + removedNodes.append(item) } } @@ -868,13 +964,13 @@ public final class ReactionButtonsAsyncLayoutContainer { apply: { animation in var items: [ApplyResult.Item] = [] for (key, size, apply) in applyItems { - let node = apply(animation) - items.append(ApplyResult.Item(value: key, node: node, size: size)) + let nodeItem = apply(animation) + items.append(ApplyResult.Item(value: key, node: nodeItem, size: size)) if let current = self.buttons[key] { - assert(current === node) + assert(current === nodeItem) } else { - self.buttons[key] = node + self.buttons[key] = nodeItem } } diff --git a/submodules/ContactListUI/Sources/ContactsController.swift b/submodules/ContactListUI/Sources/ContactsController.swift index d7e77c52d0..4c73aa192f 100644 --- a/submodules/ContactListUI/Sources/ContactsController.swift +++ b/submodules/ContactListUI/Sources/ContactsController.swift @@ -698,7 +698,7 @@ private final class ContactsTabBarContextExtractedContentSource: ContextExtracte } func takeView() -> ContextControllerTakeViewInfo? { - return ContextControllerTakeViewInfo(contentContainingNode: self.sourceNode, contentAreaInScreenSpace: UIScreen.main.bounds) + return ContextControllerTakeViewInfo(containingItem: .node(self.sourceNode), contentAreaInScreenSpace: UIScreen.main.bounds) } func putBack() -> ContextControllerPutBackViewInfo? { diff --git a/submodules/ContextUI/Sources/ContextController.swift b/submodules/ContextUI/Sources/ContextController.swift index d57cd8d174..b3ec8b41c0 100644 --- a/submodules/ContextUI/Sources/ContextController.swift +++ b/submodules/ContextUI/Sources/ContextController.swift @@ -2091,12 +2091,17 @@ public extension ContextReferenceContentSource { } public final class ContextControllerTakeViewInfo { - public let contentContainingNode: ContextExtractedContentContainingNode + public enum ContainingItem { + case node(ContextExtractedContentContainingNode) + case view(ContextExtractedContentContainingView) + } + + public let containingItem: ContainingItem public let contentAreaInScreenSpace: CGRect public let maskView: UIView? - public init(contentContainingNode: ContextExtractedContentContainingNode, contentAreaInScreenSpace: CGRect, maskView: UIView? = nil) { - self.contentContainingNode = contentContainingNode + public init(containingItem: ContainingItem, contentAreaInScreenSpace: CGRect, maskView: UIView? = nil) { + self.containingItem = containingItem self.contentAreaInScreenSpace = contentAreaInScreenSpace self.maskView = maskView } diff --git a/submodules/ContextUI/Sources/ContextControllerExtractedPresentationNode.swift b/submodules/ContextUI/Sources/ContextControllerExtractedPresentationNode.swift index fb6c35b8dd..8ed1992efb 100644 --- a/submodules/ContextUI/Sources/ContextControllerExtractedPresentationNode.swift +++ b/submodules/ContextUI/Sources/ContextControllerExtractedPresentationNode.swift @@ -8,6 +8,107 @@ import TelegramCore import SwiftSignalKit import ReactionSelectionNode +private extension ContextControllerTakeViewInfo.ContainingItem { + var contentRect: CGRect { + switch self { + case let .node(containingNode): + return containingNode.contentRect + case let .view(containingView): + return containingView.contentRect + } + } + + var customHitTest: ((CGPoint) -> UIView?)? { + switch self { + case let .node(containingNode): + return containingNode.contentNode.customHitTest + case let .view(containingView): + return containingView.contentView.customHitTest + } + } + + var view: UIView { + switch self { + case let .node(containingNode): + return containingNode.view + case let .view(containingView): + return containingView + } + } + + var contentView: UIView { + switch self { + case let .node(containingNode): + return containingNode.contentNode.view + case let .view(containingView): + return containingView.contentView + } + } + + func contentHitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + switch self { + case let .node(containingNode): + return containingNode.contentNode.hitTest(point, with: event) + case let .view(containingView): + return containingView.contentView.hitTest(point, with: event) + } + } + + var isExtractedToContextPreview: Bool { + get { + switch self { + case let .node(containingNode): + return containingNode.isExtractedToContextPreview + case let .view(containingView): + return containingView.isExtractedToContextPreview + } + } set(value) { + switch self { + case let .node(containingNode): + containingNode.isExtractedToContextPreview = value + case let .view(containingView): + containingView.isExtractedToContextPreview = value + } + } + } + + var willUpdateIsExtractedToContextPreview: ((Bool, ContainedViewLayoutTransition) -> Void)? { + switch self { + case let .node(containingNode): + return containingNode.willUpdateIsExtractedToContextPreview + case let .view(containingView): + return containingView.willUpdateIsExtractedToContextPreview + } + } + + var isExtractedToContextPreviewUpdated: ((Bool) -> Void)? { + switch self { + case let .node(containingNode): + return containingNode.isExtractedToContextPreviewUpdated + case let .view(containingView): + return containingView.isExtractedToContextPreviewUpdated + } + } + + var layoutUpdated: ((CGSize, ListViewItemUpdateAnimation) -> Void)? { + get { + switch self { + case let .node(containingNode): + return containingNode.layoutUpdated + case let .view(containingView): + return containingView.layoutUpdated + } + } set(value) { + switch self { + case let .node(containingNode): + containingNode.layoutUpdated = value + case let .view(containingView): + containingView.layoutUpdated = value + } + } + } +} + final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextControllerPresentationNode, UIScrollViewDelegate { enum ContentSource { case reference(ContextReferenceContentSource) @@ -16,14 +117,14 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo private final class ContentNode: ASDisplayNode { let offsetContainerNode: ASDisplayNode - let containingNode: ContextExtractedContentContainingNode + var containingItem: ContextControllerTakeViewInfo.ContainingItem var animateClippingFromContentAreaInScreenSpace: CGRect? var storedGlobalFrame: CGRect? - init(containingNode: ContextExtractedContentContainingNode) { + init(containingItem: ContextControllerTakeViewInfo.ContainingItem) { self.offsetContainerNode = ASDisplayNode() - self.containingNode = containingNode + self.containingItem = containingItem super.init() @@ -34,8 +135,15 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo } func takeContainingNode() { - if self.containingNode.contentNode.supernode !== self.offsetContainerNode { - self.offsetContainerNode.addSubnode(self.containingNode.contentNode) + switch self.containingItem { + case let .node(containingNode): + if containingNode.contentNode.supernode !== self.offsetContainerNode { + self.offsetContainerNode.addSubnode(containingNode.contentNode) + } + case let .view(containingView): + if containingView.contentView.superview !== self.offsetContainerNode.view { + self.offsetContainerNode.view.addSubview(containingView.contentView) + } } } @@ -43,7 +151,7 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo if !self.bounds.contains(point) { return nil } - if !self.containingNode.contentRect.contains(point) { + if !self.containingItem.contentRect.contains(point) { return nil } return self.view @@ -165,14 +273,14 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo } if case let .extracted(source) = self.source, !source.ignoreContentTouches, let contentNode = self.contentNode { - let contentPoint = self.view.convert(point, to: contentNode.containingNode.contentNode.view) - if let result = contentNode.containingNode.contentNode.customHitTest?(contentPoint) { + let contentPoint = self.view.convert(point, to: contentNode.containingItem.contentView) + if let result = contentNode.containingItem.customHitTest?(contentPoint) { return result - } else if let result = contentNode.containingNode.contentNode.hitTest(contentPoint, with: event) { + } else if let result = contentNode.containingItem.contentHitTest(contentPoint, with: event) { if result is TextSelectionNodeView { return result - } else if contentNode.containingNode.contentRect.contains(contentPoint) { - return contentNode.containingNode.contentNode.view + } else if contentNode.containingItem.contentRect.contains(contentPoint) { + return contentNode.containingItem.contentView } } } @@ -291,7 +399,7 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo guard let takeInfo = source.takeView() else { return } - let contentNodeValue = ContentNode(containingNode: takeInfo.contentContainingNode) + let contentNodeValue = ContentNode(containingItem: takeInfo.containingItem) contentNodeValue.animateClippingFromContentAreaInScreenSpace = takeInfo.contentAreaInScreenSpace self.scrollNode.insertSubnode(contentNodeValue, aboveSubnode: self.actionsStackNode) self.contentNode = contentNodeValue @@ -329,10 +437,10 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo if let contentNode = contentNode { switch stateTransition { case .animateIn, .animateOut: - contentNode.storedGlobalFrame = convertFrame(contentNode.containingNode.contentRect, from: contentNode.containingNode.view, to: self.view) + contentNode.storedGlobalFrame = convertFrame(contentNode.containingItem.contentRect, from: contentNode.containingItem.view, to: self.view) case .none: if contentNode.storedGlobalFrame == nil { - contentNode.storedGlobalFrame = convertFrame(contentNode.containingNode.contentRect, from: contentNode.containingNode.view, to: self.view) + contentNode.storedGlobalFrame = convertFrame(contentNode.containingItem.contentRect, from: contentNode.containingItem.view, to: self.view) } } } @@ -352,10 +460,10 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo } case .extracted: if let contentNode = contentNode { - contentParentGlobalFrame = convertFrame(contentNode.containingNode.bounds, from: contentNode.containingNode.view, to: self.view) + contentParentGlobalFrame = convertFrame(contentNode.containingItem.view.bounds, from: contentNode.containingItem.view, to: self.view) - let contentRectGlobalFrame = CGRect(origin: CGPoint(x: contentNode.containingNode.contentRect.minX, y: (contentNode.storedGlobalFrame?.maxY ?? 0.0) - contentNode.containingNode.contentRect.height), size: contentNode.containingNode.contentRect.size) - contentRect = CGRect(origin: CGPoint(x: contentRectGlobalFrame.minX, y: contentRectGlobalFrame.maxY - contentNode.containingNode.contentRect.size.height), size: contentNode.containingNode.contentRect.size) + let contentRectGlobalFrame = CGRect(origin: CGPoint(x: contentNode.containingItem.contentRect.minX, y: (contentNode.storedGlobalFrame?.maxY ?? 0.0) - contentNode.containingItem.contentRect.height), size: contentNode.containingItem.contentRect.size) + contentRect = CGRect(origin: CGPoint(x: contentRectGlobalFrame.minX, y: contentRectGlobalFrame.maxY - contentNode.containingItem.contentRect.size.height), size: contentNode.containingItem.contentRect.size) if case .animateOut = stateTransition { contentRect.origin.y = self.contentRectDebugNode.frame.maxY - contentRect.size.height } @@ -380,7 +488,7 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo if let contentNode = contentNode { contentNode.update( presentationData: presentationData, - size: contentNode.containingNode.bounds.size, + size: contentNode.containingItem.view.bounds.size, transition: contentTransition ) } @@ -485,7 +593,7 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo transition.updateFrame(node: self.actionsStackNode, frame: actionsFrame, beginWithCurrentState: true) if let contentNode = contentNode { - contentTransition.updateFrame(node: contentNode, frame: CGRect(origin: CGPoint(x: contentParentGlobalFrame.minX + contentRect.minX - contentNode.containingNode.contentRect.minX, y: contentRect.minY - contentNode.containingNode.contentRect.minY), size: contentNode.containingNode.bounds.size), beginWithCurrentState: true) + contentTransition.updateFrame(node: contentNode, frame: CGRect(origin: CGPoint(x: contentParentGlobalFrame.minX + contentRect.minX - contentNode.containingItem.contentRect.minX, y: contentRect.minY - contentNode.containingItem.contentRect.minY), size: contentNode.containingItem.view.bounds.size), beginWithCurrentState: true) } let contentHeight: CGFloat @@ -542,7 +650,7 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo self.clippingNode.layer.animateBoundsOriginYAdditive(from: animateClippingFromContentAreaInScreenSpace.minY, to: 0.0, duration: 0.2) } - currentContentScreenFrame = convertFrame(contentNode.containingNode.contentRect, from: contentNode.containingNode.view, to: self.view) + currentContentScreenFrame = convertFrame(contentNode.containingItem.contentRect, from: contentNode.containingItem.view, to: self.view) let currentContentLocalFrame = convertFrame(contentRect, from: self.scrollNode.view, to: self.view) animationInContentDistance = currentContentLocalFrame.maxY - currentContentScreenFrame.maxY @@ -623,11 +731,11 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo self.actionsStackNode.animateIn() if let contentNode = contentNode { - contentNode.containingNode.isExtractedToContextPreview = true - contentNode.containingNode.isExtractedToContextPreviewUpdated?(true) - contentNode.containingNode.willUpdateIsExtractedToContextPreview?(true, transition) + contentNode.containingItem.isExtractedToContextPreview = true + contentNode.containingItem.isExtractedToContextPreviewUpdated?(true) + contentNode.containingItem.willUpdateIsExtractedToContextPreview?(true, transition) - contentNode.containingNode.layoutUpdated = { [weak self] _, animation in + contentNode.containingItem.layoutUpdated = { [weak self] _, animation in guard let strongSelf = self, let _ = strongSelf.contentNode else { return } @@ -677,7 +785,7 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo } if let contentNode = contentNode { - currentContentScreenFrame = convertFrame(contentNode.containingNode.contentRect, from: contentNode.containingNode.view, to: self.view) + currentContentScreenFrame = convertFrame(contentNode.containingItem.contentRect, from: contentNode.containingItem.view, to: self.view) } else { return } @@ -719,7 +827,7 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo let completeWithActionStack = contentNode == nil if let contentNode = contentNode { - contentNode.containingNode.willUpdateIsExtractedToContextPreview?(false, transition) + contentNode.containingItem.willUpdateIsExtractedToContextPreview?(false, transition) contentNode.offsetContainerNode.position = contentNode.offsetContainerNode.position.offsetBy(dx: 0.0, dy: -animationInContentDistance) let reactionContextNodeIsAnimatingOut = self.reactionContextNodeIsAnimatingOut @@ -733,11 +841,16 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo additive: true, completion: { [weak self] _ in Queue.mainQueue().after(reactionContextNodeIsAnimatingOut ? 0.2 * UIView.animationDurationFactor() : 0.0, { - contentNode.containingNode.isExtractedToContextPreview = false - contentNode.containingNode.isExtractedToContextPreviewUpdated?(false) + contentNode.containingItem.isExtractedToContextPreview = false + contentNode.containingItem.isExtractedToContextPreviewUpdated?(false) if let strongSelf = self, let contentNode = strongSelf.contentNode { - contentNode.containingNode.addSubnode(contentNode.containingNode.contentNode) + switch contentNode.containingItem { + case let .node(containingNode): + containingNode.addSubnode(containingNode.contentNode) + case let .view(containingView): + containingView.addSubview(containingView.contentView) + } } completion() diff --git a/submodules/Display/Source/ContextContentSourceNode.swift b/submodules/Display/Source/ContextContentSourceNode.swift index c54189cfdf..8e44c9e681 100644 --- a/submodules/Display/Source/ContextContentSourceNode.swift +++ b/submodules/Display/Source/ContextContentSourceNode.swift @@ -37,12 +37,76 @@ public final class ContextExtractedContentContainingNode: ASDisplayNode { } } -public final class ContextExtractedContentNode: ASDisplayNode { - public var customHitTest: ((CGPoint) -> UIView?)? +public final class ContextExtractedContentContainingView: UIView { + public let contentView: ContextExtractedContentView + public var contentRect: CGRect = CGRect() + public var isExtractedToContextPreview: Bool = false + public var willUpdateIsExtractedToContextPreview: ((Bool, ContainedViewLayoutTransition) -> Void)? + public var isExtractedToContextPreviewUpdated: ((Bool) -> Void)? + public var updateAbsoluteRect: ((CGRect, CGSize) -> Void)? + public var applyAbsoluteOffset: ((CGPoint, ContainedViewLayoutTransitionCurve, Double) -> Void)? + public var applyAbsoluteOffsetSpring: ((CGFloat, Double, CGFloat) -> Void)? + public var layoutUpdated: ((CGSize, ListViewItemUpdateAnimation) -> Void)? + public var updateDistractionFreeMode: ((Bool) -> Void)? + public var requestDismiss: (() -> Void)? + + public override init(frame: CGRect) { + self.contentView = ContextExtractedContentView() + + super.init(frame: frame) + + self.addSubview(self.contentView) + } + + required public init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } public override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { - let result = self.view.hitTest(point, with: event) - if result === self.view { + if self.contentView.superview === self { + return self.contentView.hitTest(self.convert(point, to: self.contentView), with: event) + } else { + return nil + } + } +} + +public final class ContextExtractedContentNode: ASDisplayNode { + private var viewImpl: ContextExtractedContentView { + return self.view as! ContextExtractedContentView + } + + public var customHitTest: ((CGPoint) -> UIView?)? { + didSet { + if self.isNodeLoaded { + self.viewImpl.customHitTest = self.customHitTest + } + } + } + + override public init() { + super.init() + + self.setViewBlock { + return ContextExtractedContentView(frame: CGRect()) + } + } +} + +public final class ContextExtractedContentView: UIView { + public var customHitTest: ((CGPoint) -> UIView?)? + + override public init(frame: CGRect) { + super.init(frame: frame) + } + + required public init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + public override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + let result = super.hitTest(point, with: event) + if result === self { return nil } else { return result diff --git a/submodules/Display/Source/ContextControllerSourceNode.swift b/submodules/Display/Source/ContextControllerSourceNode.swift index 16cdf3a403..5429722f3b 100644 --- a/submodules/Display/Source/ContextControllerSourceNode.swift +++ b/submodules/Display/Source/ContextControllerSourceNode.swift @@ -126,3 +126,264 @@ open class ContextControllerSourceNode: ContextReferenceContentNode { contextGesture.isEnabled = self.isGestureEnabled } } + +/*open class ContextControllerSourceNode: ASDisplayNode { + private var viewImpl: ContextControllerSourceView { + return self.view as! ContextControllerSourceView + } + + public var contextGesture: ContextGesture? { + if self.isNodeLoaded { + return self.viewImpl.contextGesture + } else { + return nil + } + } + + public var isGestureEnabled: Bool = true { + didSet { + if self.isNodeLoaded { + self.viewImpl.isGestureEnabled = self.isGestureEnabled + } + } + } + + public var beginDelay: Double = 0.12 { + didSet { + if self.isNodeLoaded { + self.viewImpl.beginDelay = self.beginDelay + } + } + } + + public var animateScale: Bool = true { + didSet { + if self.isNodeLoaded { + self.viewImpl.animateScale = self.animateScale + } + } + } + + public var activated: ((ContextGesture, CGPoint) -> Void)? { + didSet { + if self.isNodeLoaded { + self.viewImpl.activated = self.activated + } + } + } + + public var shouldBegin: ((CGPoint) -> Bool)? { + didSet { + if self.isNodeLoaded { + self.viewImpl.shouldBegin = self.shouldBegin + } + } + } + + public var customActivationProgress: ((CGFloat, ContextGestureTransition) -> Void)? { + didSet { + if self.isNodeLoaded { + self.viewImpl.customActivationProgress = self.customActivationProgress + } + } + } + + public weak var additionalActivationProgressLayer: CALayer? { + didSet { + if self.isNodeLoaded { + self.viewImpl.additionalActivationProgressLayer = self.additionalActivationProgressLayer + } + } + } + + public var targetNodeForActivationProgress: ASDisplayNode? { + didSet { + if self.isNodeLoaded { + self.viewImpl.targetNodeForActivationProgress = self.targetNodeForActivationProgress + } + } + } + + public var targetViewForActivationProgress: UIView? { + didSet { + if self.isNodeLoaded { + self.viewImpl.targetViewForActivationProgress = self.targetViewForActivationProgress + } + } + } + + public var targetNodeForActivationProgressContentRect: CGRect? { + didSet { + if self.isNodeLoaded { + self.viewImpl.targetNodeForActivationProgressContentRect = self.targetNodeForActivationProgressContentRect + } + } + } + + override public init() { + super.init() + + self.setViewBlock({ + return ContextControllerSourceView(frame: CGRect()) + }) + } + + override open func didLoad() { + super.didLoad() + + self.viewImpl.isGestureEnabled = self.isGestureEnabled + self.viewImpl.beginDelay = self.beginDelay + self.viewImpl.animateScale = self.animateScale + self.viewImpl.activated = self.activated + self.viewImpl.shouldBegin = self.shouldBegin + self.viewImpl.customActivationProgress = self.customActivationProgress + self.viewImpl.additionalActivationProgressLayer = self.additionalActivationProgressLayer + self.viewImpl.targetNodeForActivationProgress = self.targetNodeForActivationProgress + self.viewImpl.targetViewForActivationProgress = self.targetViewForActivationProgress + self.viewImpl.targetNodeForActivationProgressContentRect = self.targetNodeForActivationProgressContentRect + } + + public func cancelGesture() { + if self.isNodeLoaded { + self.viewImpl.cancelGesture() + } + } +}*/ + +open class ContextControllerSourceView: UIView { + public private(set) var contextGesture: ContextGesture? + + public var isGestureEnabled: Bool = true { + didSet { + self.contextGesture?.isEnabled = self.isGestureEnabled + } + } + public var beginDelay: Double = 0.12 { + didSet { + self.contextGesture?.beginDelay = self.beginDelay + } + } + public var animateScale: Bool = true + + public var activated: ((ContextGesture, CGPoint) -> Void)? + public var shouldBegin: ((CGPoint) -> Bool)? + public var customActivationProgress: ((CGFloat, ContextGestureTransition) -> Void)? + public weak var additionalActivationProgressLayer: CALayer? + public var targetNodeForActivationProgress: ASDisplayNode? + public var targetViewForActivationProgress: UIView? + public var targetNodeForActivationProgressContentRect: CGRect? + + override public init(frame: CGRect) { + super.init(frame: frame) + + let contextGesture = ContextGesture(target: self, action: nil) + self.contextGesture = contextGesture + self.addGestureRecognizer(contextGesture) + + contextGesture.beginDelay = self.beginDelay + contextGesture.isEnabled = self.isGestureEnabled + + contextGesture.shouldBegin = { [weak self] point in + guard let strongSelf = self, !strongSelf.bounds.width.isZero else { + return false + } + return strongSelf.shouldBegin?(point) ?? true + } + + contextGesture.activationProgress = { [weak self] progress, update in + guard let strongSelf = self, !strongSelf.bounds.width.isZero else { + return + } + if let customActivationProgress = strongSelf.customActivationProgress { + customActivationProgress(progress, update) + } else if strongSelf.animateScale { + let targetView: UIView + let targetContentRect: CGRect + if let targetNodeForActivationProgress = strongSelf.targetNodeForActivationProgress { + targetView = targetNodeForActivationProgress.view + if let targetNodeForActivationProgressContentRect = strongSelf.targetNodeForActivationProgressContentRect { + targetContentRect = targetNodeForActivationProgressContentRect + } else { + targetContentRect = CGRect(origin: CGPoint(), size: targetView.bounds.size) + } + } else if let targetViewForActivationProgress = strongSelf.targetViewForActivationProgress { + targetView = targetViewForActivationProgress + if let targetNodeForActivationProgressContentRect = strongSelf.targetNodeForActivationProgressContentRect { + targetContentRect = targetNodeForActivationProgressContentRect + } else { + targetContentRect = CGRect(origin: CGPoint(), size: targetView.bounds.size) + } + } else { + targetView = strongSelf + targetContentRect = CGRect(origin: CGPoint(), size: targetView.bounds.size) + } + + let scaleSide = targetContentRect.width + let minScale: CGFloat = max(0.7, (scaleSide - 15.0) / scaleSide) + let currentScale = 1.0 * (1.0 - progress) + minScale * progress + + let originalCenterOffsetX: CGFloat = targetView.bounds.width / 2.0 - targetContentRect.midX + let scaledCenterOffsetX: CGFloat = originalCenterOffsetX * currentScale + + let originalCenterOffsetY: CGFloat = targetView.bounds.height / 2.0 - targetContentRect.midY + let scaledCenterOffsetY: CGFloat = originalCenterOffsetY * currentScale + + let scaleMidX: CGFloat = scaledCenterOffsetX - originalCenterOffsetX + let scaleMidY: CGFloat = scaledCenterOffsetY - originalCenterOffsetY + + switch update { + case .update: + let sublayerTransform = CATransform3DTranslate(CATransform3DScale(CATransform3DIdentity, currentScale, currentScale, 1.0), scaleMidX, scaleMidY, 0.0) + targetView.layer.sublayerTransform = sublayerTransform + if let additionalActivationProgressLayer = strongSelf.additionalActivationProgressLayer { + additionalActivationProgressLayer.transform = sublayerTransform + } + case .begin: + let sublayerTransform = CATransform3DTranslate(CATransform3DScale(CATransform3DIdentity, currentScale, currentScale, 1.0), scaleMidX, scaleMidY, 0.0) + targetView.layer.sublayerTransform = sublayerTransform + if let additionalActivationProgressLayer = strongSelf.additionalActivationProgressLayer { + additionalActivationProgressLayer.transform = sublayerTransform + } + case .ended: + let sublayerTransform = CATransform3DTranslate(CATransform3DScale(CATransform3DIdentity, currentScale, currentScale, 1.0), scaleMidX, scaleMidY, 0.0) + let previousTransform = targetView.layer.sublayerTransform + targetView.layer.sublayerTransform = sublayerTransform + + targetView.layer.animate(from: NSValue(caTransform3D: previousTransform), to: NSValue(caTransform3D: sublayerTransform), keyPath: "sublayerTransform", timingFunction: CAMediaTimingFunctionName.easeOut.rawValue, duration: 0.2) + + if let additionalActivationProgressLayer = strongSelf.additionalActivationProgressLayer { + DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.2, execute: { + additionalActivationProgressLayer.transform = sublayerTransform + }) + } + } + } + } + contextGesture.activated = { [weak self] gesture, location in + guard let strongSelf = self else { + gesture.cancel() + return + } + if let customActivationProgress = strongSelf.customActivationProgress { + customActivationProgress(0.0, .ended(0.0)) + } + + if let activated = strongSelf.activated { + activated(gesture, location) + } else { + gesture.cancel() + } + } + contextGesture.isEnabled = self.isGestureEnabled + } + + required public init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + public func cancelGesture() { + self.contextGesture?.cancel() + self.contextGesture?.isEnabled = false + self.contextGesture?.isEnabled = self.isGestureEnabled + } +} diff --git a/submodules/Display/Source/ListView.swift b/submodules/Display/Source/ListView.swift index 59f4082847..5e964ba5d7 100644 --- a/submodules/Display/Source/ListView.swift +++ b/submodules/Display/Source/ListView.swift @@ -473,8 +473,8 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture self.displayLink = CADisplayLink(target: DisplayLinkProxy(target: self), selector: #selector(DisplayLinkProxy.displayLinkEvent)) self.displayLink.add(to: RunLoop.main, forMode: RunLoop.Mode.common) - if #available(iOS 10.0, *) { - self.displayLink.preferredFramesPerSecond = 60 + if #available(iOS 15.0, iOSApplicationExtension 15.0, *) { + self.displayLink?.preferredFrameRateRange = CAFrameRateRange(minimum: 60.0, maximum: 120.0, preferred: 120.0) } self.displayLink.isPaused = true @@ -522,15 +522,14 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture } private func dispatchOnVSync(forceNext: Bool = false, action: @escaping () -> ()) { - Queue.mainQueue().async { + /*Queue.mainQueue().async { if !forceNext && self.inVSync { action() } else { action() - //self.actionsForVSync.append(action) - //self.setNeedsAnimations() } - } + }*/ + DispatchQueue.main.async(execute: action) } private func beginReordering(itemNode: ListViewItemNode) { @@ -1576,6 +1575,7 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture private func async(_ f: @escaping () -> Void) { DispatchQueue.global(qos: .userInteractive).async(execute: f) + //DispatchQueue.main.async(execute: f) } private func nodeForItem(synchronous: Bool, synchronousLoads: Bool, item: ListViewItem, previousNode: QueueLocalObject?, index: Int, previousItem: ListViewItem?, nextItem: ListViewItem?, params: ListViewItemLayoutParams, updateAnimationIsAnimated: Bool, updateAnimationIsCrossfade: Bool, completion: @escaping (QueueLocalObject, ListViewItemNodeLayout, @escaping () -> (Signal?, (ListViewItemApply) -> Void)) -> Void) { diff --git a/submodules/Display/Source/ShakeAnimation.swift b/submodules/Display/Source/ShakeAnimation.swift index 8ee18b44ab..cfb1fe333f 100644 --- a/submodules/Display/Source/ShakeAnimation.swift +++ b/submodules/Display/Source/ShakeAnimation.swift @@ -1,6 +1,10 @@ import Foundation import UIKit +// Incuding at least one Objective-C class in a swift file ensures that it doesn't get stripped by the linker +private final class LinkHelperClass: NSObject { +} + public extension CALayer { func addShakeAnimation(amplitude: CGFloat = 3.0, duration: Double = 0.3, count: Int = 4, decay: Bool = false) { let k = Float(UIView.animationDurationFactor()) diff --git a/submodules/FFMpegBinding/Sources/FFMpegAVCodecContext.m b/submodules/FFMpegBinding/Sources/FFMpegAVCodecContext.m index f41b1a9517..52f9e6541e 100644 --- a/submodules/FFMpegBinding/Sources/FFMpegAVCodecContext.m +++ b/submodules/FFMpegBinding/Sources/FFMpegAVCodecContext.m @@ -19,6 +19,7 @@ if (self != nil) { _codec = codec; _impl = avcodec_alloc_context3((AVCodec *)[codec impl]); + _impl->max_pixels = 4 * 1024 * 4 * 1024; } return self; } diff --git a/submodules/InviteLinksUI/Sources/InviteLinkListController.swift b/submodules/InviteLinksUI/Sources/InviteLinkListController.swift index 89cb79f36e..91be04e754 100644 --- a/submodules/InviteLinksUI/Sources/InviteLinkListController.swift +++ b/submodules/InviteLinksUI/Sources/InviteLinkListController.swift @@ -976,7 +976,7 @@ final class InviteLinkContextExtractedContentSource: ContextExtractedContentSour } func takeView() -> ContextControllerTakeViewInfo? { - return ContextControllerTakeViewInfo(contentContainingNode: self.sourceNode, contentAreaInScreenSpace: UIScreen.main.bounds) + return ContextControllerTakeViewInfo(containingItem: .node(self.sourceNode), contentAreaInScreenSpace: UIScreen.main.bounds) } func putBack() -> ContextControllerPutBackViewInfo? { diff --git a/submodules/InviteLinksUI/Sources/InviteRequestsController.swift b/submodules/InviteLinksUI/Sources/InviteRequestsController.swift index 51dcde2325..e7787d1b9d 100644 --- a/submodules/InviteLinksUI/Sources/InviteRequestsController.swift +++ b/submodules/InviteLinksUI/Sources/InviteRequestsController.swift @@ -425,7 +425,7 @@ final class InviteRequestsContextExtractedContentSource: ContextExtractedContent } func takeView() -> ContextControllerTakeViewInfo? { - return ContextControllerTakeViewInfo(contentContainingNode: self.sourceNode, contentAreaInScreenSpace: UIScreen.main.bounds) + return ContextControllerTakeViewInfo(containingItem: .node(self.sourceNode), contentAreaInScreenSpace: UIScreen.main.bounds) } func putBack() -> ContextControllerPutBackViewInfo? { diff --git a/submodules/MediaPlayer/Sources/FFMpegMediaVideoFrameDecoder.swift b/submodules/MediaPlayer/Sources/FFMpegMediaVideoFrameDecoder.swift index d59e684b61..bc247cb3f5 100644 --- a/submodules/MediaPlayer/Sources/FFMpegMediaVideoFrameDecoder.swift +++ b/submodules/MediaPlayer/Sources/FFMpegMediaVideoFrameDecoder.swift @@ -48,6 +48,7 @@ public final class FFMpegMediaVideoFrameDecoder: MediaTrackFrameDecoder { private let videoFrame: FFMpegAVFrame private var resetDecoderOnNextFrame = true + private var isError = false private var defaultDuration: CMTime? private var defaultTimescale: CMTimeScale? @@ -90,6 +91,10 @@ public final class FFMpegMediaVideoFrameDecoder: MediaTrackFrameDecoder { } public func receiveFromDecoder(ptsOffset: CMTime?) -> ReceiveResult { + if self.isError { + return .error + } + guard let defaultTimescale = self.defaultTimescale, let defaultDuration = self.defaultDuration else { return .error } @@ -97,6 +102,11 @@ public final class FFMpegMediaVideoFrameDecoder: MediaTrackFrameDecoder { let receiveResult = self.codecContext.receive(into: self.videoFrame) switch receiveResult { case .success: + if self.videoFrame.width * self.videoFrame.height > 4 * 1024 * 4 * 1024 { + self.isError = true + return .error + } + var pts = CMTimeMake(value: self.videoFrame.pts, timescale: defaultTimescale) if let ptsOffset = ptsOffset { pts = CMTimeAdd(pts, ptsOffset) @@ -116,12 +126,21 @@ public final class FFMpegMediaVideoFrameDecoder: MediaTrackFrameDecoder { } public func decode(frame: MediaTrackDecodableFrame, ptsOffset: CMTime?, forceARGB: Bool = false) -> MediaTrackFrame? { + if self.isError { + return nil + } + let status = frame.packet.send(toDecoder: self.codecContext) if status == 0 { self.defaultDuration = frame.duration self.defaultTimescale = frame.pts.timescale if self.codecContext.receive(into: self.videoFrame) == .success { + if self.videoFrame.width * self.videoFrame.height > 4 * 1024 * 4 * 1024 { + self.isError = true + return nil + } + var pts = CMTimeMake(value: self.videoFrame.pts, timescale: frame.pts.timescale) if let ptsOffset = ptsOffset { pts = CMTimeAdd(pts, ptsOffset) @@ -137,6 +156,9 @@ public final class FFMpegMediaVideoFrameDecoder: MediaTrackFrameDecoder { guard let defaultTimescale = self.defaultTimescale, let defaultDuration = self.defaultDuration else { return [] } + if self.isError { + return [] + } var result: [MediaTrackFrame] = [] result.append(contentsOf: self.delayedFrames) @@ -144,6 +166,11 @@ public final class FFMpegMediaVideoFrameDecoder: MediaTrackFrameDecoder { while true { if case .success = self.codecContext.receive(into: self.videoFrame) { + if self.videoFrame.width * self.videoFrame.height > 4 * 1024 * 4 * 1024 { + self.isError = true + return [] + } + var pts = CMTimeMake(value: self.videoFrame.pts, timescale: defaultTimescale) if let ptsOffset = ptsOffset { pts = CMTimeAdd(pts, ptsOffset) @@ -162,6 +189,11 @@ public final class FFMpegMediaVideoFrameDecoder: MediaTrackFrameDecoder { let status = frame.packet.send(toDecoder: self.codecContext) if status == 0 { if case .success = self.codecContext.receive(into: self.videoFrame) { + if self.videoFrame.width * self.videoFrame.height > 4 * 1024 * 4 * 1024 { + self.isError = true + return nil + } + return convertVideoFrameToImage(self.videoFrame) } } diff --git a/submodules/PhotoResources/Sources/PhotoResources.swift b/submodules/PhotoResources/Sources/PhotoResources.swift index 2e9b45696c..da33e968a7 100644 --- a/submodules/PhotoResources/Sources/PhotoResources.swift +++ b/submodules/PhotoResources/Sources/PhotoResources.swift @@ -593,6 +593,10 @@ public func chatMessagePhotoInternal(photoData: Signal ContextControllerTakeViewInfo? { - return ContextControllerTakeViewInfo(contentContainingNode: self.sourceNode, contentAreaInScreenSpace: UIScreen.main.bounds) + return ContextControllerTakeViewInfo(containingItem: .node(self.sourceNode), contentAreaInScreenSpace: UIScreen.main.bounds) } func putBack() -> ContextControllerPutBackViewInfo? { diff --git a/submodules/TelegramCallsUI/Sources/VoiceChatController.swift b/submodules/TelegramCallsUI/Sources/VoiceChatController.swift index f8fd6ec532..1ed797e641 100644 --- a/submodules/TelegramCallsUI/Sources/VoiceChatController.swift +++ b/submodules/TelegramCallsUI/Sources/VoiceChatController.swift @@ -7087,7 +7087,7 @@ private final class VoiceChatContextExtractedContentSource: ContextExtractedCont func takeView() -> ContextControllerTakeViewInfo? { self.animateTransitionIn() - return ContextControllerTakeViewInfo(contentContainingNode: self.sourceNode, contentAreaInScreenSpace: UIScreen.main.bounds, maskView: self.maskView) + return ContextControllerTakeViewInfo(containingItem: .node(self.sourceNode), contentAreaInScreenSpace: UIScreen.main.bounds, maskView: self.maskView) } func putBack() -> ContextControllerPutBackViewInfo? { diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 88cdc3fe6f..90e71d10d1 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -1139,7 +1139,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G strongSelf.window?.presentInGlobalOverlay(controller) }) } - }, openMessageReactionContextMenu: { [weak self] message, sourceNode, gesture, value in + }, openMessageReactionContextMenu: { [weak self] message, sourceView, gesture, value in guard let strongSelf = self else { return } @@ -1164,7 +1164,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G strongSelf.chatDisplayNode.messageTransitionNode.dismissMessageReactionContexts() - let controller = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .extracted(ChatMessageReactionContextExtractedContentSource(chatNode: strongSelf.chatDisplayNode, postbox: strongSelf.context.account.postbox, message: message, contentNode: sourceNode)), items: .single(items), recognizer: nil, gesture: gesture) + let controller = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .extracted(ChatMessageReactionContextExtractedContentSource(chatNode: strongSelf.chatDisplayNode, postbox: strongSelf.context.account.postbox, message: message, contentView: sourceView)), items: .single(items), recognizer: nil, gesture: gesture) dismissController = { [weak controller] completion in controller?.dismiss(completion: { @@ -8352,7 +8352,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G strongSelf.chatDisplayNode.messageTransitionNode.dismissMessageReactionContexts() - let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .reference(ChatControllerContextReferenceContentSource(controller: strongSelf, sourceNode: node, insets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: bottomInset, right: 0.0))), items: .single(ContextController.Items(content: .list(items))), gesture: gesture, workaroundUseLegacyImplementation: true) + let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .reference(ChatControllerContextReferenceContentSource(controller: strongSelf, sourceView: node.view, insets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: bottomInset, right: 0.0))), items: .single(ContextController.Items(content: .list(items))), gesture: gesture, workaroundUseLegacyImplementation: true) contextController.dismissed = { [weak self] in if let strongSelf = self { strongSelf.updateChatPresentationInterfaceState(interactive: true, { @@ -8894,7 +8894,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G })) }))) } - let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .reference(ChatControllerContextReferenceContentSource(controller: strongSelf, sourceNode: backButtonNode, insets: UIEdgeInsets(), contentInsets: UIEdgeInsets(top: 0.0, left: -15.0, bottom: 0.0, right: -15.0))), items: .single(ContextController.Items(content: .list(items))), gesture: gesture) + let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .reference(ChatControllerContextReferenceContentSource(controller: strongSelf, sourceView: backButtonNode.view, insets: UIEdgeInsets(), contentInsets: UIEdgeInsets(top: 0.0, left: -15.0, bottom: 0.0, right: -15.0))), items: .single(ContextController.Items(content: .list(items))), gesture: gesture) strongSelf.presentInGlobalOverlay(contextController) }) } @@ -15846,19 +15846,19 @@ private final class ContextControllerContentSourceImpl: ContextControllerContent final class ChatControllerContextReferenceContentSource: ContextReferenceContentSource { private let controller: ViewController - private let sourceNode: ContextReferenceContentNode + private let sourceView: UIView private let insets: UIEdgeInsets private let contentInsets: UIEdgeInsets - init(controller: ViewController, sourceNode: ContextReferenceContentNode, insets: UIEdgeInsets, contentInsets: UIEdgeInsets = UIEdgeInsets()) { + init(controller: ViewController, sourceView: UIView, insets: UIEdgeInsets, contentInsets: UIEdgeInsets = UIEdgeInsets()) { self.controller = controller - self.sourceNode = sourceNode + self.sourceView = sourceView self.insets = insets self.contentInsets = contentInsets } func transitionInfo() -> ContextControllerReferenceViewInfo? { - return ContextControllerReferenceViewInfo(referenceView: self.sourceNode.view, contentAreaInScreenSpace: UIScreen.main.bounds.inset(by: self.insets), insets: self.contentInsets) + return ContextControllerReferenceViewInfo(referenceView: self.sourceView, contentAreaInScreenSpace: UIScreen.main.bounds.inset(by: self.insets), insets: self.contentInsets) } } diff --git a/submodules/TelegramUI/Sources/ChatControllerInteraction.swift b/submodules/TelegramUI/Sources/ChatControllerInteraction.swift index acbbf512ec..4317870fcd 100644 --- a/submodules/TelegramUI/Sources/ChatControllerInteraction.swift +++ b/submodules/TelegramUI/Sources/ChatControllerInteraction.swift @@ -58,7 +58,7 @@ public final class ChatControllerInteraction { let openPeerMention: (String) -> Void let openMessageContextMenu: (Message, Bool, ASDisplayNode, CGRect, UIGestureRecognizer?) -> Void let updateMessageReaction: (Message, ChatControllerInteractionReaction) -> Void - let openMessageReactionContextMenu: (Message, ContextExtractedContentContainingNode, ContextGesture?, String) -> Void + let openMessageReactionContextMenu: (Message, ContextExtractedContentContainingView, ContextGesture?, String) -> Void let activateMessagePinch: (PinchSourceContainerNode) -> Void let openMessageContextActions: (Message, ASDisplayNode, CGRect, ContextGesture?) -> Void let navigateToMessage: (MessageId, MessageId) -> Void @@ -157,7 +157,7 @@ public final class ChatControllerInteraction { openPeer: @escaping (PeerId?, ChatControllerInteractionNavigateToPeer, MessageReference?, Peer?) -> Void, openPeerMention: @escaping (String) -> Void, openMessageContextMenu: @escaping (Message, Bool, ASDisplayNode, CGRect, UIGestureRecognizer?) -> Void, - openMessageReactionContextMenu: @escaping (Message, ContextExtractedContentContainingNode, ContextGesture?, String) -> Void, + openMessageReactionContextMenu: @escaping (Message, ContextExtractedContentContainingView, ContextGesture?, String) -> Void, updateMessageReaction: @escaping (Message, ChatControllerInteractionReaction) -> Void, activateMessagePinch: @escaping (PinchSourceContainerNode) -> Void, openMessageContextActions: @escaping (Message, ASDisplayNode, CGRect, ContextGesture?) -> Void, diff --git a/submodules/TelegramUI/Sources/ChatHistoryListNode.swift b/submodules/TelegramUI/Sources/ChatHistoryListNode.swift index f8a9fb6807..f386a7f734 100644 --- a/submodules/TelegramUI/Sources/ChatHistoryListNode.swift +++ b/submodules/TelegramUI/Sources/ChatHistoryListNode.swift @@ -1806,6 +1806,10 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { } private func processDisplayedItemRangeChanged(displayedRange: ListViewDisplayedItemRange, transactionState: ChatHistoryTransactionOpaqueState) { + if "".isEmpty { + return + } + let historyView = transactionState.historyView var isTopReplyThreadMessageShownValue = false var topVisibleMessageRange: ChatTopVisibleMessageRange? diff --git a/submodules/TelegramUI/Sources/ChatMessageAnimatedStickerItemNode.swift b/submodules/TelegramUI/Sources/ChatMessageAnimatedStickerItemNode.swift index bccb1f6aa3..c8fc47e755 100644 --- a/submodules/TelegramUI/Sources/ChatMessageAnimatedStickerItemNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageAnimatedStickerItemNode.swift @@ -1365,13 +1365,13 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { } item.controllerInteraction.updateMessageReaction(item.message, .reaction(value)) } - reactionButtonsNode.openReactionPreview = { gesture, sourceNode, value in + reactionButtonsNode.openReactionPreview = { gesture, sourceView, value in guard let strongSelf = self, let item = strongSelf.item else { gesture?.cancel() return } - item.controllerInteraction.openMessageReactionContextMenu(item.message, sourceNode, gesture, value) + item.controllerInteraction.openMessageReactionContextMenu(item.message, sourceView, gesture, value) } reactionButtonsNode.frame = reactionButtonsFrame if let (rect, containerSize) = strongSelf.absoluteRect { diff --git a/submodules/TelegramUI/Sources/ChatMessageContactBubbleContentNode.swift b/submodules/TelegramUI/Sources/ChatMessageContactBubbleContentNode.swift index 30682a759e..8c067664b9 100644 --- a/submodules/TelegramUI/Sources/ChatMessageContactBubbleContentNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageContactBubbleContentNode.swift @@ -49,13 +49,13 @@ class ChatMessageContactBubbleContentNode: ChatMessageBubbleContentNode { item.controllerInteraction.updateMessageReaction(item.message, .reaction(value)) } - self.dateAndStatusNode.openReactionPreview = { [weak self] gesture, sourceNode, value in + self.dateAndStatusNode.openReactionPreview = { [weak self] gesture, sourceView, value in guard let strongSelf = self, let item = strongSelf.item else { gesture?.cancel() return } - item.controllerInteraction.openMessageReactionContextMenu(item.topMessage, sourceNode, gesture, value) + item.controllerInteraction.openMessageReactionContextMenu(item.topMessage, sourceView, gesture, value) } } diff --git a/submodules/TelegramUI/Sources/ChatMessageContextControllerContentSource.swift b/submodules/TelegramUI/Sources/ChatMessageContextControllerContentSource.swift index 4ef983e99f..22517866f1 100644 --- a/submodules/TelegramUI/Sources/ChatMessageContextControllerContentSource.swift +++ b/submodules/TelegramUI/Sources/ChatMessageContextControllerContentSource.swift @@ -55,7 +55,7 @@ final class ChatMessageContextExtractedContentSource: ContextExtractedContentSou return } if item.content.contains(where: { $0.0.stableId == self.message.stableId }), let contentNode = itemNode.getMessageContextSourceNode(stableId: self.selectAll ? nil : self.message.stableId) { - result = ContextControllerTakeViewInfo(contentContainingNode: contentNode, contentAreaInScreenSpace: chatNode.convert(chatNode.frameForVisibleArea(), to: nil)) + result = ContextControllerTakeViewInfo(containingItem: .node(contentNode), contentAreaInScreenSpace: chatNode.convert(chatNode.frameForVisibleArea(), to: nil)) } } return result @@ -91,7 +91,7 @@ final class ChatMessageReactionContextExtractedContentSource: ContextExtractedCo private weak var chatNode: ChatControllerNode? private let postbox: Postbox private let message: Message - private let contentNode: ContextExtractedContentContainingNode + private let contentView: ContextExtractedContentContainingView var shouldBeDismissed: Signal { if self.message.adAttribute != nil { @@ -112,11 +112,11 @@ final class ChatMessageReactionContextExtractedContentSource: ContextExtractedCo |> distinctUntilChanged } - init(chatNode: ChatControllerNode, postbox: Postbox, message: Message, contentNode: ContextExtractedContentContainingNode) { + init(chatNode: ChatControllerNode, postbox: Postbox, message: Message, contentView: ContextExtractedContentContainingView) { self.chatNode = chatNode self.postbox = postbox self.message = message - self.contentNode = contentNode + self.contentView = contentView } func takeView() -> ContextControllerTakeViewInfo? { @@ -133,7 +133,7 @@ final class ChatMessageReactionContextExtractedContentSource: ContextExtractedCo return } if item.content.contains(where: { $0.0.stableId == self.message.stableId }) { - result = ContextControllerTakeViewInfo(contentContainingNode: self.contentNode, contentAreaInScreenSpace: chatNode.convert(chatNode.frameForVisibleArea(), to: nil)) + result = ContextControllerTakeViewInfo(containingItem: .view(self.contentView), contentAreaInScreenSpace: chatNode.convert(chatNode.frameForVisibleArea(), to: nil)) } } return result @@ -183,7 +183,7 @@ final class ChatMessageNavigationButtonContextExtractedContentSource: ContextExt return nil } - return ContextControllerTakeViewInfo(contentContainingNode: self.contentNode, contentAreaInScreenSpace: chatNode.convert(chatNode.frameForVisibleArea(), to: nil)) + return ContextControllerTakeViewInfo(containingItem: .node(self.contentNode), contentAreaInScreenSpace: chatNode.convert(chatNode.frameForVisibleArea(), to: nil)) } func putBack() -> ContextControllerPutBackViewInfo? { diff --git a/submodules/TelegramUI/Sources/ChatMessageDateAndStatusNode.swift b/submodules/TelegramUI/Sources/ChatMessageDateAndStatusNode.swift index 7c2f39dab8..d3cbce7279 100644 --- a/submodules/TelegramUI/Sources/ChatMessageDateAndStatusNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageDateAndStatusNode.swift @@ -226,7 +226,7 @@ class ChatMessageDateAndStatusNode: ASDisplayNode { } } var reactionSelected: ((String) -> Void)? - var openReactionPreview: ((ContextGesture?, ContextExtractedContentContainingNode, String) -> Void)? + var openReactionPreview: ((ContextGesture?, ContextExtractedContentContainingView, String) -> Void)? override init() { self.dateNode = TextNode() @@ -801,24 +801,24 @@ class ChatMessageDateAndStatusNode: ASDisplayNode { reactionButtonPosition.y += item.size.height + 6.0 } - if item.node.supernode == nil { - strongSelf.addSubnode(item.node) - item.node.frame = CGRect(origin: reactionButtonPosition, size: item.size) + if item.node.view.superview == nil { + strongSelf.view.addSubview(item.node.view) + item.node.view.frame = CGRect(origin: reactionButtonPosition, size: item.size) if animation.isAnimated { - item.node.layer.animateScale(from: 0.01, to: 1.0, duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring) - item.node.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + item.node.view.layer.animateScale(from: 0.01, to: 1.0, duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring) + item.node.view.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) } } else { - animation.animator.updateFrame(layer: item.node.layer, frame: CGRect(origin: reactionButtonPosition, size: item.size), completion: nil) + animation.animator.updateFrame(layer: item.node.view.layer, frame: CGRect(origin: reactionButtonPosition, size: item.size), completion: nil) } let itemValue = item.value let itemNode = item.node - item.node.isGestureEnabled = true + item.node.view.isGestureEnabled = true let canViewReactionList = arguments.canViewReactionList - item.node.activateAfterCompletion = !canViewReactionList - item.node.activated = { [weak itemNode] gesture, _ in + item.node.view.activateAfterCompletion = !canViewReactionList + item.node.view.activated = { [weak itemNode] gesture, _ in guard let strongSelf = self, canViewReactionList else { return } @@ -827,7 +827,7 @@ class ChatMessageDateAndStatusNode: ASDisplayNode { } if let openReactionPreview = strongSelf.openReactionPreview { - openReactionPreview(gesture, itemNode.containerNode, itemValue) + openReactionPreview(gesture, itemNode.view.containerView, itemValue) } else { gesture.cancel() } @@ -838,12 +838,12 @@ class ChatMessageDateAndStatusNode: ASDisplayNode { for node in reactionButtons.removedNodes { if animation.isAnimated { - node.layer.animateScale(from: 1.0, to: 0.01, duration: 0.2, removeOnCompletion: false) - node.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak node] _ in - node?.removeFromSupernode() + node.view.layer.animateScale(from: 1.0, to: 0.01, duration: 0.2, removeOnCompletion: false) + node.view.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak node] _ in + node?.view.removeFromSuperview() }) } else { - node.removeFromSupernode() + node.view.removeFromSuperview() } } @@ -1195,7 +1195,7 @@ class ChatMessageDateAndStatusNode: ASDisplayNode { } for (key, button) in self.reactionButtonsContainer.buttons { if key == value { - return button.iconView + return button.view.iconView } } return nil @@ -1203,8 +1203,8 @@ class ChatMessageDateAndStatusNode: ASDisplayNode { override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { for (_, button) in self.reactionButtonsContainer.buttons { - if button.frame.contains(point) { - if let result = button.hitTest(self.view.convert(point, to: button.view), with: event) { + if button.view.frame.contains(point) { + if let result = button.view.hitTest(self.view.convert(point, to: button.view), with: event) { return result } } diff --git a/submodules/TelegramUI/Sources/ChatMessageReactionsFooterContentNode.swift b/submodules/TelegramUI/Sources/ChatMessageReactionsFooterContentNode.swift index 1ae5e8b8c3..c23a0ec318 100644 --- a/submodules/TelegramUI/Sources/ChatMessageReactionsFooterContentNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageReactionsFooterContentNode.swift @@ -63,7 +63,7 @@ final class MessageReactionButtonsNode: ASDisplayNode { private var backgroundMaskButtons: [String: UIView] = [:] var reactionSelected: ((String) -> Void)? - var openReactionPreview: ((ContextGesture?, ContextExtractedContentContainingNode, String) -> Void)? + var openReactionPreview: ((ContextGesture?, ContextExtractedContentContainingView, String) -> Void)? override init() { self.container = ReactionButtonsAsyncLayoutContainer() @@ -304,23 +304,23 @@ final class MessageReactionButtonsNode: ASDisplayNode { strongSelf.backgroundMaskButtons[item.value] = itemMaskView } - if item.node.supernode == nil { - strongSelf.addSubnode(item.node) + if item.node.view.superview == nil { + strongSelf.view.addSubview(item.node.view) if animation.isAnimated { - item.node.layer.animateScale(from: 0.01, to: 1.0, duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring) - item.node.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + item.node.view.layer.animateScale(from: 0.01, to: 1.0, duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring) + item.node.view.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) } - item.node.frame = itemFrame + item.node.view.frame = itemFrame } else { - animation.animator.updateFrame(layer: item.node.layer, frame: itemFrame, completion: nil) + animation.animator.updateFrame(layer: item.node.view.layer, frame: itemFrame, completion: nil) } let itemValue = item.value let itemNode = item.node - item.node.isGestureEnabled = true + item.node.view.isGestureEnabled = true let canViewReactionList = canViewMessageReactionList(message: message) - item.node.activateAfterCompletion = !canViewReactionList - item.node.activated = { [weak itemNode] gesture, _ in + item.node.view.activateAfterCompletion = !canViewReactionList + item.node.view.activated = { [weak itemNode] gesture, _ in guard let strongSelf = self, let itemNode = itemNode else { gesture.cancel() return @@ -328,9 +328,9 @@ final class MessageReactionButtonsNode: ASDisplayNode { if !canViewReactionList { return } - strongSelf.openReactionPreview?(gesture, itemNode.containerNode, itemValue) + strongSelf.openReactionPreview?(gesture, itemNode.view.containerView, itemValue) } - item.node.additionalActivationProgressLayer = itemMaskView.layer + item.node.view.additionalActivationProgressLayer = itemMaskView.layer if itemMaskView.superview == nil { strongSelf.backgroundMaskView?.addSubview(itemMaskView) @@ -364,12 +364,12 @@ final class MessageReactionButtonsNode: ASDisplayNode { for node in reactionButtons.removedNodes { if animation.isAnimated { - node.layer.animateScale(from: 1.0, to: 0.01, duration: 0.2, removeOnCompletion: false) - node.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak node] _ in - node?.removeFromSupernode() + node.view.layer.animateScale(from: 1.0, to: 0.01, duration: 0.2, removeOnCompletion: false) + node.view.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak node] _ in + node?.view.removeFromSuperview() }) } else { - node.removeFromSupernode() + node.view.removeFromSuperview() } } }) @@ -409,7 +409,7 @@ final class MessageReactionButtonsNode: ASDisplayNode { func reactionTargetView(value: String) -> UIView? { for (key, button) in self.container.buttons { if key == value { - return button.iconView + return button.view.iconView } } return nil @@ -417,20 +417,20 @@ final class MessageReactionButtonsNode: ASDisplayNode { func animateIn(animation: ListViewItemUpdateAnimation) { for (_, button) in self.container.buttons { - animation.animator.animateScale(layer: button.layer, from: 0.01, to: 1.0, completion: nil) + animation.animator.animateScale(layer: button.view.layer, from: 0.01, to: 1.0, completion: nil) } } func animateOut(animation: ListViewItemUpdateAnimation) { for (_, button) in self.container.buttons { - animation.animator.updateScale(layer: button.layer, scale: 0.01, completion: nil) + animation.animator.updateScale(layer: button.view.layer, scale: 0.01, completion: nil) } } override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { for (_, button) in self.container.buttons { - if button.frame.contains(point) { - if let result = button.hitTest(self.view.convert(point, to: button.view), with: event) { + if button.view.frame.contains(point) { + if let result = button.view.hitTest(self.view.convert(point, to: button.view), with: event) { return result } } @@ -600,7 +600,7 @@ final class ChatMessageReactionButtonsNode: ASDisplayNode { private let buttonsNode: MessageReactionButtonsNode var reactionSelected: ((String) -> Void)? - var openReactionPreview: ((ContextGesture?, ContextExtractedContentContainingNode, String) -> Void)? + var openReactionPreview: ((ContextGesture?, ContextExtractedContentContainingView, String) -> Void)? override init() { self.buttonsNode = MessageReactionButtonsNode() diff --git a/submodules/TelegramUI/Sources/ChatMessageTextBubbleContentNode.swift b/submodules/TelegramUI/Sources/ChatMessageTextBubbleContentNode.swift index 0cabe2808d..a24ae12be0 100644 --- a/submodules/TelegramUI/Sources/ChatMessageTextBubbleContentNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageTextBubbleContentNode.swift @@ -62,7 +62,7 @@ class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode { self.textNode.isUserInteractionEnabled = false self.textNode.contentMode = .topLeft self.textNode.contentsScale = UIScreenScale - self.textNode.displaysAsynchronously = false + self.textNode.displaysAsynchronously = true self.addSubnode(self.textNode) self.addSubnode(self.textAccessibilityOverlayNode) diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift index 8633cccb67..c2a781ab86 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift @@ -8063,7 +8063,7 @@ public final class PeerInfoScreenImpl: ViewController, PeerInfoScreen { })) }))) } - let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .reference(ChatControllerContextReferenceContentSource(controller: strongSelf, sourceNode: backButtonNode, insets: UIEdgeInsets(), contentInsets: UIEdgeInsets(top: 0.0, left: -15.0, bottom: 0.0, right: -15.0))), items: .single(ContextController.Items(content: .list(items))), gesture: gesture) + let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .reference(ChatControllerContextReferenceContentSource(controller: strongSelf, sourceView: backButtonNode.view, insets: UIEdgeInsets(), contentInsets: UIEdgeInsets(top: 0.0, left: -15.0, bottom: 0.0, right: -15.0))), items: .single(ContextController.Items(content: .list(items))), gesture: gesture) strongSelf.presentInGlobalOverlay(contextController) }) } @@ -8140,7 +8140,7 @@ private final class SettingsTabBarContextExtractedContentSource: ContextExtracte } func takeView() -> ContextControllerTakeViewInfo? { - return ContextControllerTakeViewInfo(contentContainingNode: self.sourceNode, contentAreaInScreenSpace: UIScreen.main.bounds) + return ContextControllerTakeViewInfo(containingItem: .node(self.sourceNode), contentAreaInScreenSpace: UIScreen.main.bounds) } func putBack() -> ContextControllerPutBackViewInfo? { @@ -8415,7 +8415,7 @@ private final class MessageContextExtractedContentSource: ContextExtractedConten } func takeView() -> ContextControllerTakeViewInfo? { - return ContextControllerTakeViewInfo(contentContainingNode: self.sourceNode, contentAreaInScreenSpace: UIScreen.main.bounds) + return ContextControllerTakeViewInfo(containingItem: .node(self.sourceNode), contentAreaInScreenSpace: UIScreen.main.bounds) } func putBack() -> ContextControllerPutBackViewInfo? { diff --git a/submodules/TelegramVoip/Sources/OngoingCallContext.swift b/submodules/TelegramVoip/Sources/OngoingCallContext.swift index d9faef4b1f..3b0258585c 100644 --- a/submodules/TelegramVoip/Sources/OngoingCallContext.swift +++ b/submodules/TelegramVoip/Sources/OngoingCallContext.swift @@ -8,7 +8,7 @@ import TgVoip import TgVoipWebrtc private let debugUseLegacyVersionForReflectors: Bool = { - #if DEBUG && false + #if DEBUG return true #else return false @@ -827,15 +827,15 @@ public final class OngoingCallContext { filteredConnections.append(contentsOf: callConnectionDescriptionsWebrtc(connection, idMapping: reflectorIdMapping)) } - /*#if DEBUG - for connection in filteredConnections { - if connection.username == "reflector" { - filteredConnections.append(OngoingCallConnectionDescriptionWebrtc(reflectorId: 0, hasStun: false, hasTurn: true, hasTcp: true, ip: "91.108.12.1", port: 533, username: "reflector", password: connection.password)) - - break + if debugUseLegacyVersionForReflectors { + for connection in filteredConnections { + if connection.username == "reflector" { + filteredConnections.append(OngoingCallConnectionDescriptionWebrtc(reflectorId: 0, hasStun: false, hasTurn: true, hasTcp: true, ip: "91.108.12.1", port: 533, username: "reflector", password: connection.password)) + + break + } } } - #endif*/ let context = OngoingCallThreadLocalContextWebrtc(version: version, queue: OngoingCallThreadLocalContextQueueImpl(queue: queue), proxy: voipProxyServer, networkType: ongoingNetworkTypeForTypeWebrtc(initialNetworkType), dataSaving: ongoingDataSavingForTypeWebrtc(dataSaving), derivedState: derivedState.data, key: key, isOutgoing: isOutgoing, connections: filteredConnections, maxLayer: maxLayer, allowP2P: allowP2P, allowTCP: enableTCP, enableStunMarking: enableStunMarking, logPath: tempLogPath, statsLogPath: tempStatsLogPath, sendSignalingData: { [weak callSessionManager] data in callSessionManager?.sendSignalingData(internalId: internalId, data: data) diff --git a/submodules/TextFormat/Sources/GenerateTextEntities.swift b/submodules/TextFormat/Sources/GenerateTextEntities.swift index d5369b815c..5319d8b026 100644 --- a/submodules/TextFormat/Sources/GenerateTextEntities.swift +++ b/submodules/TextFormat/Sources/GenerateTextEntities.swift @@ -302,6 +302,10 @@ public func generateTextEntities(_ text: String, enabledTypes: EnabledEntityType } public func addLocallyGeneratedEntities(_ text: String, enabledTypes: EnabledEntityTypes, entities: [MessageTextEntity], mediaDuration: Double? = nil) -> [MessageTextEntity]? { + if "".isEmpty { + return nil + } + var resultEntities = entities var hasDigits = false diff --git a/third-party/webrtc/webrtc b/third-party/webrtc/webrtc index 4223234566..a206ca345b 160000 --- a/third-party/webrtc/webrtc +++ b/third-party/webrtc/webrtc @@ -1 +1 @@ -Subproject commit 422323456699b93e4e5ea96bd8d1b062098ad9e9 +Subproject commit a206ca345bbbe520e0506ce4caf3ab4844204a58