Various fixes

This commit is contained in:
Ali 2022-04-30 00:30:00 +04:00
parent 8270f2b029
commit 015528cb3e
33 changed files with 916 additions and 326 deletions

View File

@ -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? {

View File

@ -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? {

View File

@ -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? {

View File

@ -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? {

View File

@ -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

View File

@ -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<String>()
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
}
}

View File

@ -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? {

View File

@ -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
}

View File

@ -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()

View File

@ -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

View File

@ -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
}
}

View File

@ -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<ListViewItemNode>?, index: Int, previousItem: ListViewItem?, nextItem: ListViewItem?, params: ListViewItemLayoutParams, updateAnimationIsAnimated: Bool, updateAnimationIsCrossfade: Bool, completion: @escaping (QueueLocalObject<ListViewItemNode>, ListViewItemNodeLayout, @escaping () -> (Signal<Void, NoError>?, (ListViewItemApply) -> Void)) -> Void) {

View File

@ -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())

View File

@ -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;
}

View File

@ -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? {

View File

@ -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? {

View File

@ -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)
}
}

View File

@ -593,6 +593,10 @@ public func chatMessagePhotoInternal(photoData: Signal<Tuple4<Data?, Data?, Chat
return ({
return nil
}, quality, { arguments in
if !synchronousLoad {
assert(!Thread.isMainThread)
}
let drawingRect = arguments.drawingRect
var fittedSize = arguments.imageSize
if abs(fittedSize.width - arguments.boundingSize.width).isLessThanOrEqualTo(CGFloat(1.0)) {

View File

@ -544,7 +544,7 @@ private final class ChannelStatsContextExtractedContentSource: ContextExtractedC
}
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? {

View File

@ -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? {

View File

@ -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)
}
}

View File

@ -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,

View File

@ -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?

View File

@ -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 {

View File

@ -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)
}
}

View File

@ -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<Bool, NoError> {
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? {

View File

@ -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
}
}

View File

@ -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()

View File

@ -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)

View File

@ -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? {

View File

@ -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)

View File

@ -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

@ -1 +1 @@
Subproject commit 422323456699b93e4e5ea96bd8d1b062098ad9e9
Subproject commit a206ca345bbbe520e0506ce4caf3ab4844204a58