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 { guard let navigationBar = self.navigationBar else {
return nil 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? { func putBack() -> ContextControllerPutBackViewInfo? {

View File

@ -366,7 +366,7 @@ public final class CallListController: TelegramBaseController {
} }
func takeView() -> ContextControllerTakeViewInfo? { 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? { func putBack() -> ContextControllerPutBackViewInfo? {
@ -510,7 +510,7 @@ private final class CallListTabBarContextExtractedContentSource: ContextExtracte
} }
func takeView() -> ContextControllerTakeViewInfo? { 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? { func putBack() -> ContextControllerPutBackViewInfo? {

View File

@ -3209,7 +3209,7 @@ private final class ChatListTabBarContextExtractedContentSource: ContextExtracte
} }
func takeView() -> ContextControllerTakeViewInfo? { 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? { func putBack() -> ContextControllerPutBackViewInfo? {
@ -3232,7 +3232,7 @@ private final class ChatListHeaderBarContextExtractedContentSource: ContextExtra
} }
func takeView() -> ContextControllerTakeViewInfo? { 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? { func putBack() -> ContextControllerPutBackViewInfo? {

View File

@ -1406,7 +1406,7 @@ private final class MessageContextExtractedContentSource: ContextExtractedConten
} }
func takeView() -> ContextControllerTakeViewInfo? { 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? { func putBack() -> ContextControllerPutBackViewInfo? {

View File

@ -1782,6 +1782,7 @@ public final class ChatListNode: ListView {
} }
var options = transition.options var options = transition.options
if self.view.window != nil {
if !options.contains(.AnimateInsertion) { if !options.contains(.AnimateInsertion) {
options.insert(.PreferSynchronousDrawing) options.insert(.PreferSynchronousDrawing)
options.insert(.PreferSynchronousResourceLoading) options.insert(.PreferSynchronousResourceLoading)
@ -1789,6 +1790,7 @@ public final class ChatListNode: ListView {
if options.contains(.AnimateCrossfade) && !self.isDeceleratingAfterTracking { if options.contains(.AnimateCrossfade) && !self.isDeceleratingAfterTracking {
options.insert(.PreferSynchronousDrawing) options.insert(.PreferSynchronousDrawing)
} }
}
var scrollToItem = transition.scrollToItem var scrollToItem = transition.scrollToItem
if transition.adjustScrollToFirstItem { if transition.adjustScrollToFirstItem {

View File

@ -32,8 +32,25 @@ public final class ReactionIconView: PortalSourceView {
} }
} }
public final class ReactionButtonAsyncNode: ContextControllerSourceNode { private final class ReactionImageCache {
fileprivate final class ContainerButtonNode: HighlightTrackingButtonNode { 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 { struct Colors: Equatable {
var background: UInt32 var background: UInt32
var foreground: UInt32 var foreground: UInt32
@ -65,8 +82,17 @@ public final class ReactionButtonAsyncNode: ContextControllerSourceNode {
private var animationState: AnimationState? private var animationState: AnimationState?
private var animator: ConstantDisplayLinkAnimator? private var animator: ConstantDisplayLinkAnimator?
init() { override init(frame: CGRect) {
super.init(pointerStyle: nil) 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) { func update(layout: Layout) {
@ -130,14 +156,21 @@ public final class ReactionButtonAsyncNode: ContextControllerSourceNode {
} }
} }
let image = generateImage(layout.size, rotatedContext: { size, context in let isExtracted = self.isExtracted
let animationState = self.animationState
DispatchQueue.global().async { [weak self] in
var image: UIImage?
if true {
image = generateImage(layout.size, rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size)) context.clear(CGRect(origin: CGPoint(), size: size))
UIGraphicsPushContext(context) UIGraphicsPushContext(context)
func drawContents(colors: Colors) { func drawContents(colors: Colors) {
let backgroundColor: UIColor let backgroundColor: UIColor
let foregroundColor: UIColor let foregroundColor: UIColor
if self.isExtracted { if isExtracted {
backgroundColor = UIColor(argb: colors.extractedBackground) backgroundColor = UIColor(argb: colors.extractedBackground)
foregroundColor = UIColor(argb: colors.extractedForeground) foregroundColor = UIColor(argb: colors.extractedForeground)
} else { } else {
@ -161,7 +194,7 @@ public final class ReactionButtonAsyncNode: ContextControllerSourceNode {
var rightTextOrigin = textOrigin + totalComponentWidth var rightTextOrigin = textOrigin + totalComponentWidth
let animationFraction: CGFloat let animationFraction: CGFloat
if let animationState = self.animationState, animationState.fromCounter != nil { if let animationState = animationState, animationState.fromCounter != nil {
animationFraction = max(0.0, min(1.0, (CACurrentMediaTime() - animationState.startTime) / animationState.duration)) animationFraction = max(0.0, min(1.0, (CACurrentMediaTime() - animationState.startTime) / animationState.duration))
} else { } else {
animationFraction = 1.0 animationFraction = 1.0
@ -172,7 +205,7 @@ public final class ReactionButtonAsyncNode: ContextControllerSourceNode {
var componentAlpha: CGFloat = 1.0 var componentAlpha: CGFloat = 1.0
var componentVerticalOffset: CGFloat = 0.0 var componentVerticalOffset: CGFloat = 0.0
if let animationState = self.animationState, let fromCounter = animationState.fromCounter { if let animationState = animationState, let fromCounter = animationState.fromCounter {
let reverseIndex = counter.components.count - 1 - i let reverseIndex = counter.components.count - 1 - i
if reverseIndex < fromCounter.components.count { if reverseIndex < fromCounter.components.count {
let previousComponent = fromCounter.components[fromCounter.components.count - 1 - reverseIndex] let previousComponent = fromCounter.components[fromCounter.components.count - 1 - reverseIndex]
@ -219,7 +252,7 @@ public final class ReactionButtonAsyncNode: ContextControllerSourceNode {
} }
} }
if let animationState = self.animationState, animationState.fromColors.isSelected != layout.colors.isSelected { if let animationState = animationState, animationState.fromColors.isSelected != layout.colors.isSelected {
var animationFraction: CGFloat = max(0.0, min(1.0, (CACurrentMediaTime() - animationState.startTime) / animationState.duration)) var animationFraction: CGFloat = max(0.0, min(1.0, (CACurrentMediaTime() - animationState.startTime) / animationState.duration))
if !layout.colors.isSelected { if !layout.colors.isSelected {
animationFraction = 1.0 - animationFraction animationFraction = 1.0 - animationFraction
@ -245,14 +278,19 @@ public final class ReactionButtonAsyncNode: ContextControllerSourceNode {
} }
UIGraphicsPopContext() 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) DispatchQueue.main.async {
if let strongSelf = self, let image = image {
let previousContents = strongSelf.layer.contents
ASDisplayNodeSetResizableContents(strongSelf.layer, image)
if animated, let previousContents = previousContents { if animated, let previousContents = previousContents {
self.layer.animate(from: previousContents as! CGImage, to: image.cgImage!, keyPath: "contents", timingFunction: CAMediaTimingFunctionName.linear.rawValue, duration: 0.2) strongSelf.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? private var layout: Layout?
public let containerNode: ContextExtractedContentContainingNode public let containerView: ContextExtractedContentContainingView
private let buttonNode: ContainerButtonNode private let buttonNode: ContainerButtonNode
public let iconView: ReactionIconView public var iconView: ReactionIconView?
private var avatarsView: AnimatedAvatarSetView? private var avatarsView: AnimatedAvatarSetView?
private let iconImageDisposable = MetaDisposable() private let iconImageDisposable = MetaDisposable()
@ -481,42 +519,40 @@ public final class ReactionButtonAsyncNode: ContextControllerSourceNode {
} }
} }
override init() { override init(frame: CGRect) {
self.containerNode = ContextExtractedContentContainingNode() self.containerView = ContextExtractedContentContainingView()
self.buttonNode = ContainerButtonNode() self.buttonNode = ContainerButtonNode()
self.iconView = ReactionIconView() 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.addSubview(self.containerView)
self.containerNode.contentNode.addSubnode(self.buttonNode) self.containerView.contentView.addSubview(self.buttonNode)
self.buttonNode.view.addSubview(self.iconView) if let iconView = self.iconView {
self.buttonNode.addSubview(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.buttonNode.addTarget(self, action: #selector(self.pressed), for: .touchUpInside)
self.isGestureEnabled = true self.isGestureEnabled = true
self.beginDelay = 0.0 self.beginDelay = 0.0
self.containerNode.willUpdateIsExtractedToContextPreview = { [weak self] isExtracted, _ in self.containerView.willUpdateIsExtractedToContextPreview = { [weak self] isExtracted, _ in
guard let strongSelf = self else { guard let strongSelf = self else {
return return
} }
strongSelf.buttonNode.updateIsExtracted(isExtracted: isExtracted, animated: true) strongSelf.buttonNode.updateIsExtracted(isExtracted: isExtracted, animated: true)
} }
if self.activateAfterCompletion {
self.contextGesture?.activatedAfterCompletion = { [weak self] in
self?.pressed()
}
}
} }
required init?(coder aDecoder: NSCoder) { required init?(coder aDecoder: NSCoder) {
@ -527,14 +563,11 @@ public final class ReactionButtonAsyncNode: ContextControllerSourceNode {
self.iconImageDisposable.dispose() self.iconImageDisposable.dispose()
} }
override public func didLoad() { func reset() {
super.didLoad() self.iconView?.imageView.image = nil
self.layout = nil
if self.activateAfterCompletion { self.buttonNode.reset()
self.contextGesture?.activatedAfterCompletion = { [weak self] in
self?.pressed()
}
}
} }
@objc private func pressed() { @objc private func pressed() {
@ -545,44 +578,74 @@ public final class ReactionButtonAsyncNode: ContextControllerSourceNode {
} }
fileprivate func apply(layout: Layout, animation: ListViewItemUpdateAnimation) { fileprivate func apply(layout: Layout, animation: ListViewItemUpdateAnimation) {
self.containerNode.frame = CGRect(origin: CGPoint(), size: layout.size) self.containerView.frame = CGRect(origin: CGPoint(), size: layout.size)
self.containerNode.contentNode.frame = CGRect(origin: CGPoint(), size: layout.size) self.containerView.contentView.frame = CGRect(origin: CGPoint(), size: layout.size)
self.containerNode.contentRect = 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) animation.animator.updateFrame(layer: self.buttonNode.layer, frame: CGRect(origin: CGPoint(), size: layout.size), completion: nil)
self.buttonNode.update(layout: layout.backgroundLayout) self.buttonNode.update(layout: layout.backgroundLayout)
animation.animator.updateFrame(layer: self.iconView.layer, frame: layout.imageFrame, completion: nil) if let iconView = self.iconView {
self.iconView.update(size: layout.imageFrame.size, transition: animation.transition) 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 self.layout?.spec.component.reaction != layout.spec.component.reaction {
if let file = layout.spec.component.reaction.centerAnimation { 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)) if let image = ReactionImageCache.shared.get(reaction: layout.spec.component.reaction.value) {
|> deliverOnMainQueue).start(next: { [weak self] data in iconView.imageView.image = image
guard let strongSelf = self else { } else {
return 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
} }
|> take(1)
|> map { data -> UIImage? in
if data.isComplete, let dataValue = try? Data(contentsOf: URL(fileURLWithPath: data.path)) { if data.isComplete, let dataValue = try? Data(contentsOf: URL(fileURLWithPath: data.path)) {
if let image = UIImage(data: dataValue) { if let image = UIImage(data: dataValue) {
strongSelf.iconView.imageView.image = image return image.precomposed()
} else {
print("Could not decode image")
} }
} else {
print("Incomplete data")
} }
})) return nil
} 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] image in
|> deliverOnMainQueue).start(next: { [weak self] data in
guard let strongSelf = self else { guard let strongSelf = self else {
return return
} }
if data.complete, let dataValue = try? Data(contentsOf: URL(fileURLWithPath: data.path)) { if let image = image {
if let image = WebP.convert(fromWebP: dataValue) { strongSelf.iconView?.imageView.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
}))
}
}
} }
if !layout.spec.component.avatarPeers.isEmpty { if !layout.spec.component.avatarPeers.isEmpty {
@ -593,7 +656,7 @@ public final class ReactionButtonAsyncNode: ContextControllerSourceNode {
avatarsView = AnimatedAvatarSetView() avatarsView = AnimatedAvatarSetView()
avatarsView.isUserInteractionEnabled = false avatarsView.isUserInteractionEnabled = false
self.avatarsView = avatarsView self.avatarsView = avatarsView
self.buttonNode.view.addSubview(avatarsView) self.buttonNode.addSubview(avatarsView)
} }
let content = AnimatedAvatarSetContext().update(peers: layout.spec.component.avatarPeers, animated: false) let content = AnimatedAvatarSetContext().update(peers: layout.spec.component.avatarPeers, animated: false)
let avatarsSize = avatarsView.update( let avatarsSize = avatarsView.update(
@ -620,8 +683,8 @@ public final class ReactionButtonAsyncNode: ContextControllerSourceNode {
self.layout = layout self.layout = layout
} }
public static func asyncLayout(_ view: ReactionButtonAsyncNode?) -> (ReactionButtonComponent) -> (size: CGSize, apply: (_ animation: ListViewItemUpdateAnimation) -> ReactionButtonAsyncNode) { public static func asyncLayout(_ item: ReactionNodePool.Item?) -> (ReactionButtonComponent) -> (size: CGSize, apply: (_ animation: ListViewItemUpdateAnimation) -> ReactionNodePool.Item) {
let currentLayout = view?.layout let currentLayout = item?.view.layout
return { component in return { component in
let spec = Layout.Spec(component: component) let spec = Layout.Spec(component: component)
@ -635,17 +698,17 @@ public final class ReactionButtonAsyncNode: ContextControllerSourceNode {
return (size: layout.size, apply: { animation in return (size: layout.size, apply: { animation in
var animation = animation var animation = animation
let updatedView: ReactionButtonAsyncNode let updatedItem: ReactionNodePool.Item
if let view = view { if let item = item {
updatedView = view updatedItem = item
} else { } else {
updatedView = ReactionButtonAsyncNode() updatedItem = ReactionNodePool.shared.take()
animation = .None 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 final class ReactionButtonsAsyncLayoutContainer {
public struct Reaction { public struct Reaction {
public var reaction: ReactionButtonComponent.Reaction public var reaction: ReactionButtonComponent.Reaction
@ -783,15 +879,15 @@ public final class ReactionButtonsAsyncLayoutContainer {
public struct ApplyResult { public struct ApplyResult {
public struct Item { public struct Item {
public var value: String public var value: String
public var node: ReactionButtonAsyncNode public var node: ReactionNodePool.Item
public var size: CGSize public var size: CGSize
} }
public var items: [Item] 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() { public init() {
} }
@ -804,7 +900,7 @@ public final class ReactionButtonsAsyncLayoutContainer {
constrainedWidth: CGFloat constrainedWidth: CGFloat
) -> Result { ) -> Result {
var items: [Result.Item] = [] 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>() var validIds = Set<String>()
for reaction in reactions.sorted(by: { lhs, rhs in for reaction in reactions.sorted(by: { lhs, rhs in
@ -856,10 +952,10 @@ public final class ReactionButtonsAsyncLayoutContainer {
removeIds.append(id) removeIds.append(id)
} }
} }
var removedNodes: [ReactionButtonAsyncNode] = [] var removedNodes: [ReactionNodePool.Item] = []
for id in removeIds { for id in removeIds {
if let node = self.buttons.removeValue(forKey: id) { if let item = self.buttons.removeValue(forKey: id) {
removedNodes.append(node) removedNodes.append(item)
} }
} }
@ -868,13 +964,13 @@ public final class ReactionButtonsAsyncLayoutContainer {
apply: { animation in apply: { animation in
var items: [ApplyResult.Item] = [] var items: [ApplyResult.Item] = []
for (key, size, apply) in applyItems { for (key, size, apply) in applyItems {
let node = apply(animation) let nodeItem = apply(animation)
items.append(ApplyResult.Item(value: key, node: node, size: size)) items.append(ApplyResult.Item(value: key, node: nodeItem, size: size))
if let current = self.buttons[key] { if let current = self.buttons[key] {
assert(current === node) assert(current === nodeItem)
} else { } else {
self.buttons[key] = node self.buttons[key] = nodeItem
} }
} }

View File

@ -698,7 +698,7 @@ private final class ContactsTabBarContextExtractedContentSource: ContextExtracte
} }
func takeView() -> ContextControllerTakeViewInfo? { 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? { func putBack() -> ContextControllerPutBackViewInfo? {

View File

@ -2091,12 +2091,17 @@ public extension ContextReferenceContentSource {
} }
public final class ContextControllerTakeViewInfo { 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 contentAreaInScreenSpace: CGRect
public let maskView: UIView? public let maskView: UIView?
public init(contentContainingNode: ContextExtractedContentContainingNode, contentAreaInScreenSpace: CGRect, maskView: UIView? = nil) { public init(containingItem: ContainingItem, contentAreaInScreenSpace: CGRect, maskView: UIView? = nil) {
self.contentContainingNode = contentContainingNode self.containingItem = containingItem
self.contentAreaInScreenSpace = contentAreaInScreenSpace self.contentAreaInScreenSpace = contentAreaInScreenSpace
self.maskView = maskView self.maskView = maskView
} }

View File

@ -8,6 +8,107 @@ import TelegramCore
import SwiftSignalKit import SwiftSignalKit
import ReactionSelectionNode 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 { final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextControllerPresentationNode, UIScrollViewDelegate {
enum ContentSource { enum ContentSource {
case reference(ContextReferenceContentSource) case reference(ContextReferenceContentSource)
@ -16,14 +117,14 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo
private final class ContentNode: ASDisplayNode { private final class ContentNode: ASDisplayNode {
let offsetContainerNode: ASDisplayNode let offsetContainerNode: ASDisplayNode
let containingNode: ContextExtractedContentContainingNode var containingItem: ContextControllerTakeViewInfo.ContainingItem
var animateClippingFromContentAreaInScreenSpace: CGRect? var animateClippingFromContentAreaInScreenSpace: CGRect?
var storedGlobalFrame: CGRect? var storedGlobalFrame: CGRect?
init(containingNode: ContextExtractedContentContainingNode) { init(containingItem: ContextControllerTakeViewInfo.ContainingItem) {
self.offsetContainerNode = ASDisplayNode() self.offsetContainerNode = ASDisplayNode()
self.containingNode = containingNode self.containingItem = containingItem
super.init() super.init()
@ -34,8 +135,15 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo
} }
func takeContainingNode() { func takeContainingNode() {
if self.containingNode.contentNode.supernode !== self.offsetContainerNode { switch self.containingItem {
self.offsetContainerNode.addSubnode(self.containingNode.contentNode) 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) { if !self.bounds.contains(point) {
return nil return nil
} }
if !self.containingNode.contentRect.contains(point) { if !self.containingItem.contentRect.contains(point) {
return nil return nil
} }
return self.view 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 { if case let .extracted(source) = self.source, !source.ignoreContentTouches, let contentNode = self.contentNode {
let contentPoint = self.view.convert(point, to: contentNode.containingNode.contentNode.view) let contentPoint = self.view.convert(point, to: contentNode.containingItem.contentView)
if let result = contentNode.containingNode.contentNode.customHitTest?(contentPoint) { if let result = contentNode.containingItem.customHitTest?(contentPoint) {
return result 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 { if result is TextSelectionNodeView {
return result return result
} else if contentNode.containingNode.contentRect.contains(contentPoint) { } else if contentNode.containingItem.contentRect.contains(contentPoint) {
return contentNode.containingNode.contentNode.view return contentNode.containingItem.contentView
} }
} }
} }
@ -291,7 +399,7 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo
guard let takeInfo = source.takeView() else { guard let takeInfo = source.takeView() else {
return return
} }
let contentNodeValue = ContentNode(containingNode: takeInfo.contentContainingNode) let contentNodeValue = ContentNode(containingItem: takeInfo.containingItem)
contentNodeValue.animateClippingFromContentAreaInScreenSpace = takeInfo.contentAreaInScreenSpace contentNodeValue.animateClippingFromContentAreaInScreenSpace = takeInfo.contentAreaInScreenSpace
self.scrollNode.insertSubnode(contentNodeValue, aboveSubnode: self.actionsStackNode) self.scrollNode.insertSubnode(contentNodeValue, aboveSubnode: self.actionsStackNode)
self.contentNode = contentNodeValue self.contentNode = contentNodeValue
@ -329,10 +437,10 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo
if let contentNode = contentNode { if let contentNode = contentNode {
switch stateTransition { switch stateTransition {
case .animateIn, .animateOut: 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: case .none:
if contentNode.storedGlobalFrame == nil { 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: case .extracted:
if let contentNode = contentNode { 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) 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.containingNode.contentRect.size.height), size: contentNode.containingNode.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 { if case .animateOut = stateTransition {
contentRect.origin.y = self.contentRectDebugNode.frame.maxY - contentRect.size.height contentRect.origin.y = self.contentRectDebugNode.frame.maxY - contentRect.size.height
} }
@ -380,7 +488,7 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo
if let contentNode = contentNode { if let contentNode = contentNode {
contentNode.update( contentNode.update(
presentationData: presentationData, presentationData: presentationData,
size: contentNode.containingNode.bounds.size, size: contentNode.containingItem.view.bounds.size,
transition: contentTransition transition: contentTransition
) )
} }
@ -485,7 +593,7 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo
transition.updateFrame(node: self.actionsStackNode, frame: actionsFrame, beginWithCurrentState: true) transition.updateFrame(node: self.actionsStackNode, frame: actionsFrame, beginWithCurrentState: true)
if let contentNode = contentNode { 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 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) 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) let currentContentLocalFrame = convertFrame(contentRect, from: self.scrollNode.view, to: self.view)
animationInContentDistance = currentContentLocalFrame.maxY - currentContentScreenFrame.maxY animationInContentDistance = currentContentLocalFrame.maxY - currentContentScreenFrame.maxY
@ -623,11 +731,11 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo
self.actionsStackNode.animateIn() self.actionsStackNode.animateIn()
if let contentNode = contentNode { if let contentNode = contentNode {
contentNode.containingNode.isExtractedToContextPreview = true contentNode.containingItem.isExtractedToContextPreview = true
contentNode.containingNode.isExtractedToContextPreviewUpdated?(true) contentNode.containingItem.isExtractedToContextPreviewUpdated?(true)
contentNode.containingNode.willUpdateIsExtractedToContextPreview?(true, transition) 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 { guard let strongSelf = self, let _ = strongSelf.contentNode else {
return return
} }
@ -677,7 +785,7 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo
} }
if let contentNode = contentNode { 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 { } else {
return return
} }
@ -719,7 +827,7 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo
let completeWithActionStack = contentNode == nil let completeWithActionStack = contentNode == nil
if let contentNode = contentNode { 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) contentNode.offsetContainerNode.position = contentNode.offsetContainerNode.position.offsetBy(dx: 0.0, dy: -animationInContentDistance)
let reactionContextNodeIsAnimatingOut = self.reactionContextNodeIsAnimatingOut let reactionContextNodeIsAnimatingOut = self.reactionContextNodeIsAnimatingOut
@ -733,11 +841,16 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo
additive: true, additive: true,
completion: { [weak self] _ in completion: { [weak self] _ in
Queue.mainQueue().after(reactionContextNodeIsAnimatingOut ? 0.2 * UIView.animationDurationFactor() : 0.0, { Queue.mainQueue().after(reactionContextNodeIsAnimatingOut ? 0.2 * UIView.animationDurationFactor() : 0.0, {
contentNode.containingNode.isExtractedToContextPreview = false contentNode.containingItem.isExtractedToContextPreview = false
contentNode.containingNode.isExtractedToContextPreviewUpdated?(false) contentNode.containingItem.isExtractedToContextPreviewUpdated?(false)
if let strongSelf = self, let contentNode = strongSelf.contentNode { 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() completion()

View File

@ -37,12 +37,76 @@ public final class ContextExtractedContentContainingNode: ASDisplayNode {
} }
} }
public final class ContextExtractedContentNode: ASDisplayNode { public final class ContextExtractedContentContainingView: UIView {
public var customHitTest: ((CGPoint) -> 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? { public override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
let result = self.view.hitTest(point, with: event) if self.contentView.superview === self {
if result === self.view { 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 return nil
} else { } else {
return result return result

View File

@ -126,3 +126,264 @@ open class ContextControllerSourceNode: ContextReferenceContentNode {
contextGesture.isEnabled = self.isGestureEnabled 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 = CADisplayLink(target: DisplayLinkProxy(target: self), selector: #selector(DisplayLinkProxy.displayLinkEvent))
self.displayLink.add(to: RunLoop.main, forMode: RunLoop.Mode.common) self.displayLink.add(to: RunLoop.main, forMode: RunLoop.Mode.common)
if #available(iOS 10.0, *) { if #available(iOS 15.0, iOSApplicationExtension 15.0, *) {
self.displayLink.preferredFramesPerSecond = 60 self.displayLink?.preferredFrameRateRange = CAFrameRateRange(minimum: 60.0, maximum: 120.0, preferred: 120.0)
} }
self.displayLink.isPaused = true self.displayLink.isPaused = true
@ -522,15 +522,14 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
} }
private func dispatchOnVSync(forceNext: Bool = false, action: @escaping () -> ()) { private func dispatchOnVSync(forceNext: Bool = false, action: @escaping () -> ()) {
Queue.mainQueue().async { /*Queue.mainQueue().async {
if !forceNext && self.inVSync { if !forceNext && self.inVSync {
action() action()
} else { } else {
action() action()
//self.actionsForVSync.append(action)
//self.setNeedsAnimations()
}
} }
}*/
DispatchQueue.main.async(execute: action)
} }
private func beginReordering(itemNode: ListViewItemNode) { private func beginReordering(itemNode: ListViewItemNode) {
@ -1576,6 +1575,7 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
private func async(_ f: @escaping () -> Void) { private func async(_ f: @escaping () -> Void) {
DispatchQueue.global(qos: .userInteractive).async(execute: f) 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) { 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 Foundation
import UIKit 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 { public extension CALayer {
func addShakeAnimation(amplitude: CGFloat = 3.0, duration: Double = 0.3, count: Int = 4, decay: Bool = false) { func addShakeAnimation(amplitude: CGFloat = 3.0, duration: Double = 0.3, count: Int = 4, decay: Bool = false) {
let k = Float(UIView.animationDurationFactor()) let k = Float(UIView.animationDurationFactor())

View File

@ -19,6 +19,7 @@
if (self != nil) { if (self != nil) {
_codec = codec; _codec = codec;
_impl = avcodec_alloc_context3((AVCodec *)[codec impl]); _impl = avcodec_alloc_context3((AVCodec *)[codec impl]);
_impl->max_pixels = 4 * 1024 * 4 * 1024;
} }
return self; return self;
} }

View File

@ -976,7 +976,7 @@ final class InviteLinkContextExtractedContentSource: ContextExtractedContentSour
} }
func takeView() -> ContextControllerTakeViewInfo? { 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? { func putBack() -> ContextControllerPutBackViewInfo? {

View File

@ -425,7 +425,7 @@ final class InviteRequestsContextExtractedContentSource: ContextExtractedContent
} }
func takeView() -> ContextControllerTakeViewInfo? { 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? { func putBack() -> ContextControllerPutBackViewInfo? {

View File

@ -48,6 +48,7 @@ public final class FFMpegMediaVideoFrameDecoder: MediaTrackFrameDecoder {
private let videoFrame: FFMpegAVFrame private let videoFrame: FFMpegAVFrame
private var resetDecoderOnNextFrame = true private var resetDecoderOnNextFrame = true
private var isError = false
private var defaultDuration: CMTime? private var defaultDuration: CMTime?
private var defaultTimescale: CMTimeScale? private var defaultTimescale: CMTimeScale?
@ -90,6 +91,10 @@ public final class FFMpegMediaVideoFrameDecoder: MediaTrackFrameDecoder {
} }
public func receiveFromDecoder(ptsOffset: CMTime?) -> ReceiveResult { public func receiveFromDecoder(ptsOffset: CMTime?) -> ReceiveResult {
if self.isError {
return .error
}
guard let defaultTimescale = self.defaultTimescale, let defaultDuration = self.defaultDuration else { guard let defaultTimescale = self.defaultTimescale, let defaultDuration = self.defaultDuration else {
return .error return .error
} }
@ -97,6 +102,11 @@ public final class FFMpegMediaVideoFrameDecoder: MediaTrackFrameDecoder {
let receiveResult = self.codecContext.receive(into: self.videoFrame) let receiveResult = self.codecContext.receive(into: self.videoFrame)
switch receiveResult { switch receiveResult {
case .success: 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) var pts = CMTimeMake(value: self.videoFrame.pts, timescale: defaultTimescale)
if let ptsOffset = ptsOffset { if let ptsOffset = ptsOffset {
pts = CMTimeAdd(pts, 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? { public func decode(frame: MediaTrackDecodableFrame, ptsOffset: CMTime?, forceARGB: Bool = false) -> MediaTrackFrame? {
if self.isError {
return nil
}
let status = frame.packet.send(toDecoder: self.codecContext) let status = frame.packet.send(toDecoder: self.codecContext)
if status == 0 { if status == 0 {
self.defaultDuration = frame.duration self.defaultDuration = frame.duration
self.defaultTimescale = frame.pts.timescale self.defaultTimescale = frame.pts.timescale
if self.codecContext.receive(into: self.videoFrame) == .success { 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) var pts = CMTimeMake(value: self.videoFrame.pts, timescale: frame.pts.timescale)
if let ptsOffset = ptsOffset { if let ptsOffset = ptsOffset {
pts = CMTimeAdd(pts, ptsOffset) pts = CMTimeAdd(pts, ptsOffset)
@ -137,6 +156,9 @@ public final class FFMpegMediaVideoFrameDecoder: MediaTrackFrameDecoder {
guard let defaultTimescale = self.defaultTimescale, let defaultDuration = self.defaultDuration else { guard let defaultTimescale = self.defaultTimescale, let defaultDuration = self.defaultDuration else {
return [] return []
} }
if self.isError {
return []
}
var result: [MediaTrackFrame] = [] var result: [MediaTrackFrame] = []
result.append(contentsOf: self.delayedFrames) result.append(contentsOf: self.delayedFrames)
@ -144,6 +166,11 @@ public final class FFMpegMediaVideoFrameDecoder: MediaTrackFrameDecoder {
while true { while true {
if case .success = self.codecContext.receive(into: self.videoFrame) { 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) var pts = CMTimeMake(value: self.videoFrame.pts, timescale: defaultTimescale)
if let ptsOffset = ptsOffset { if let ptsOffset = ptsOffset {
pts = CMTimeAdd(pts, ptsOffset) pts = CMTimeAdd(pts, ptsOffset)
@ -162,6 +189,11 @@ public final class FFMpegMediaVideoFrameDecoder: MediaTrackFrameDecoder {
let status = frame.packet.send(toDecoder: self.codecContext) let status = frame.packet.send(toDecoder: self.codecContext)
if status == 0 { if status == 0 {
if case .success = self.codecContext.receive(into: self.videoFrame) { 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) return convertVideoFrameToImage(self.videoFrame)
} }
} }

View File

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

View File

@ -7087,7 +7087,7 @@ private final class VoiceChatContextExtractedContentSource: ContextExtractedCont
func takeView() -> ContextControllerTakeViewInfo? { func takeView() -> ContextControllerTakeViewInfo? {
self.animateTransitionIn() 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? { func putBack() -> ContextControllerPutBackViewInfo? {

View File

@ -1139,7 +1139,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
strongSelf.window?.presentInGlobalOverlay(controller) 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 { guard let strongSelf = self else {
return return
} }
@ -1164,7 +1164,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
strongSelf.chatDisplayNode.messageTransitionNode.dismissMessageReactionContexts() 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 dismissController = { [weak controller] completion in
controller?.dismiss(completion: { controller?.dismiss(completion: {
@ -8352,7 +8352,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
strongSelf.chatDisplayNode.messageTransitionNode.dismissMessageReactionContexts() 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 contextController.dismissed = { [weak self] in
if let strongSelf = self { if let strongSelf = self {
strongSelf.updateChatPresentationInterfaceState(interactive: true, { 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) strongSelf.presentInGlobalOverlay(contextController)
}) })
} }
@ -15846,19 +15846,19 @@ private final class ContextControllerContentSourceImpl: ContextControllerContent
final class ChatControllerContextReferenceContentSource: ContextReferenceContentSource { final class ChatControllerContextReferenceContentSource: ContextReferenceContentSource {
private let controller: ViewController private let controller: ViewController
private let sourceNode: ContextReferenceContentNode private let sourceView: UIView
private let insets: UIEdgeInsets private let insets: UIEdgeInsets
private let contentInsets: 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.controller = controller
self.sourceNode = sourceNode self.sourceView = sourceView
self.insets = insets self.insets = insets
self.contentInsets = contentInsets self.contentInsets = contentInsets
} }
func transitionInfo() -> ContextControllerReferenceViewInfo? { 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 openPeerMention: (String) -> Void
let openMessageContextMenu: (Message, Bool, ASDisplayNode, CGRect, UIGestureRecognizer?) -> Void let openMessageContextMenu: (Message, Bool, ASDisplayNode, CGRect, UIGestureRecognizer?) -> Void
let updateMessageReaction: (Message, ChatControllerInteractionReaction) -> 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 activateMessagePinch: (PinchSourceContainerNode) -> Void
let openMessageContextActions: (Message, ASDisplayNode, CGRect, ContextGesture?) -> Void let openMessageContextActions: (Message, ASDisplayNode, CGRect, ContextGesture?) -> Void
let navigateToMessage: (MessageId, MessageId) -> Void let navigateToMessage: (MessageId, MessageId) -> Void
@ -157,7 +157,7 @@ public final class ChatControllerInteraction {
openPeer: @escaping (PeerId?, ChatControllerInteractionNavigateToPeer, MessageReference?, Peer?) -> Void, openPeer: @escaping (PeerId?, ChatControllerInteractionNavigateToPeer, MessageReference?, Peer?) -> Void,
openPeerMention: @escaping (String) -> Void, openPeerMention: @escaping (String) -> Void,
openMessageContextMenu: @escaping (Message, Bool, ASDisplayNode, CGRect, UIGestureRecognizer?) -> 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, updateMessageReaction: @escaping (Message, ChatControllerInteractionReaction) -> Void,
activateMessagePinch: @escaping (PinchSourceContainerNode) -> Void, activateMessagePinch: @escaping (PinchSourceContainerNode) -> Void,
openMessageContextActions: @escaping (Message, ASDisplayNode, CGRect, ContextGesture?) -> 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) { private func processDisplayedItemRangeChanged(displayedRange: ListViewDisplayedItemRange, transactionState: ChatHistoryTransactionOpaqueState) {
if "".isEmpty {
return
}
let historyView = transactionState.historyView let historyView = transactionState.historyView
var isTopReplyThreadMessageShownValue = false var isTopReplyThreadMessageShownValue = false
var topVisibleMessageRange: ChatTopVisibleMessageRange? var topVisibleMessageRange: ChatTopVisibleMessageRange?

View File

@ -1365,13 +1365,13 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
} }
item.controllerInteraction.updateMessageReaction(item.message, .reaction(value)) 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 { guard let strongSelf = self, let item = strongSelf.item else {
gesture?.cancel() gesture?.cancel()
return return
} }
item.controllerInteraction.openMessageReactionContextMenu(item.message, sourceNode, gesture, value) item.controllerInteraction.openMessageReactionContextMenu(item.message, sourceView, gesture, value)
} }
reactionButtonsNode.frame = reactionButtonsFrame reactionButtonsNode.frame = reactionButtonsFrame
if let (rect, containerSize) = strongSelf.absoluteRect { if let (rect, containerSize) = strongSelf.absoluteRect {

View File

@ -49,13 +49,13 @@ class ChatMessageContactBubbleContentNode: ChatMessageBubbleContentNode {
item.controllerInteraction.updateMessageReaction(item.message, .reaction(value)) 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 { guard let strongSelf = self, let item = strongSelf.item else {
gesture?.cancel() gesture?.cancel()
return 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 return
} }
if item.content.contains(where: { $0.0.stableId == self.message.stableId }), let contentNode = itemNode.getMessageContextSourceNode(stableId: self.selectAll ? nil : self.message.stableId) { 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 return result
@ -91,7 +91,7 @@ final class ChatMessageReactionContextExtractedContentSource: ContextExtractedCo
private weak var chatNode: ChatControllerNode? private weak var chatNode: ChatControllerNode?
private let postbox: Postbox private let postbox: Postbox
private let message: Message private let message: Message
private let contentNode: ContextExtractedContentContainingNode private let contentView: ContextExtractedContentContainingView
var shouldBeDismissed: Signal<Bool, NoError> { var shouldBeDismissed: Signal<Bool, NoError> {
if self.message.adAttribute != nil { if self.message.adAttribute != nil {
@ -112,11 +112,11 @@ final class ChatMessageReactionContextExtractedContentSource: ContextExtractedCo
|> distinctUntilChanged |> distinctUntilChanged
} }
init(chatNode: ChatControllerNode, postbox: Postbox, message: Message, contentNode: ContextExtractedContentContainingNode) { init(chatNode: ChatControllerNode, postbox: Postbox, message: Message, contentView: ContextExtractedContentContainingView) {
self.chatNode = chatNode self.chatNode = chatNode
self.postbox = postbox self.postbox = postbox
self.message = message self.message = message
self.contentNode = contentNode self.contentView = contentView
} }
func takeView() -> ContextControllerTakeViewInfo? { func takeView() -> ContextControllerTakeViewInfo? {
@ -133,7 +133,7 @@ final class ChatMessageReactionContextExtractedContentSource: ContextExtractedCo
return return
} }
if item.content.contains(where: { $0.0.stableId == self.message.stableId }) { 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 return result
@ -183,7 +183,7 @@ final class ChatMessageNavigationButtonContextExtractedContentSource: ContextExt
return nil 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? { func putBack() -> ContextControllerPutBackViewInfo? {

View File

@ -226,7 +226,7 @@ class ChatMessageDateAndStatusNode: ASDisplayNode {
} }
} }
var reactionSelected: ((String) -> Void)? var reactionSelected: ((String) -> Void)?
var openReactionPreview: ((ContextGesture?, ContextExtractedContentContainingNode, String) -> Void)? var openReactionPreview: ((ContextGesture?, ContextExtractedContentContainingView, String) -> Void)?
override init() { override init() {
self.dateNode = TextNode() self.dateNode = TextNode()
@ -801,24 +801,24 @@ class ChatMessageDateAndStatusNode: ASDisplayNode {
reactionButtonPosition.y += item.size.height + 6.0 reactionButtonPosition.y += item.size.height + 6.0
} }
if item.node.supernode == nil { if item.node.view.superview == nil {
strongSelf.addSubnode(item.node) strongSelf.view.addSubview(item.node.view)
item.node.frame = CGRect(origin: reactionButtonPosition, size: item.size) item.node.view.frame = CGRect(origin: reactionButtonPosition, size: item.size)
if animation.isAnimated { if animation.isAnimated {
item.node.layer.animateScale(from: 0.01, to: 1.0, duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring) item.node.view.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.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
} }
} else { } 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 itemValue = item.value
let itemNode = item.node let itemNode = item.node
item.node.isGestureEnabled = true item.node.view.isGestureEnabled = true
let canViewReactionList = arguments.canViewReactionList let canViewReactionList = arguments.canViewReactionList
item.node.activateAfterCompletion = !canViewReactionList item.node.view.activateAfterCompletion = !canViewReactionList
item.node.activated = { [weak itemNode] gesture, _ in item.node.view.activated = { [weak itemNode] gesture, _ in
guard let strongSelf = self, canViewReactionList else { guard let strongSelf = self, canViewReactionList else {
return return
} }
@ -827,7 +827,7 @@ class ChatMessageDateAndStatusNode: ASDisplayNode {
} }
if let openReactionPreview = strongSelf.openReactionPreview { if let openReactionPreview = strongSelf.openReactionPreview {
openReactionPreview(gesture, itemNode.containerNode, itemValue) openReactionPreview(gesture, itemNode.view.containerView, itemValue)
} else { } else {
gesture.cancel() gesture.cancel()
} }
@ -838,12 +838,12 @@ class ChatMessageDateAndStatusNode: ASDisplayNode {
for node in reactionButtons.removedNodes { for node in reactionButtons.removedNodes {
if animation.isAnimated { if animation.isAnimated {
node.layer.animateScale(from: 1.0, to: 0.01, duration: 0.2, removeOnCompletion: false) node.view.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.view.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak node] _ in
node?.removeFromSupernode() node?.view.removeFromSuperview()
}) })
} else { } else {
node.removeFromSupernode() node.view.removeFromSuperview()
} }
} }
@ -1195,7 +1195,7 @@ class ChatMessageDateAndStatusNode: ASDisplayNode {
} }
for (key, button) in self.reactionButtonsContainer.buttons { for (key, button) in self.reactionButtonsContainer.buttons {
if key == value { if key == value {
return button.iconView return button.view.iconView
} }
} }
return nil return nil
@ -1203,8 +1203,8 @@ class ChatMessageDateAndStatusNode: ASDisplayNode {
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
for (_, button) in self.reactionButtonsContainer.buttons { for (_, button) in self.reactionButtonsContainer.buttons {
if button.frame.contains(point) { if button.view.frame.contains(point) {
if let result = button.hitTest(self.view.convert(point, to: button.view), with: event) { if let result = button.view.hitTest(self.view.convert(point, to: button.view), with: event) {
return result return result
} }
} }

View File

@ -63,7 +63,7 @@ final class MessageReactionButtonsNode: ASDisplayNode {
private var backgroundMaskButtons: [String: UIView] = [:] private var backgroundMaskButtons: [String: UIView] = [:]
var reactionSelected: ((String) -> Void)? var reactionSelected: ((String) -> Void)?
var openReactionPreview: ((ContextGesture?, ContextExtractedContentContainingNode, String) -> Void)? var openReactionPreview: ((ContextGesture?, ContextExtractedContentContainingView, String) -> Void)?
override init() { override init() {
self.container = ReactionButtonsAsyncLayoutContainer() self.container = ReactionButtonsAsyncLayoutContainer()
@ -304,23 +304,23 @@ final class MessageReactionButtonsNode: ASDisplayNode {
strongSelf.backgroundMaskButtons[item.value] = itemMaskView strongSelf.backgroundMaskButtons[item.value] = itemMaskView
} }
if item.node.supernode == nil { if item.node.view.superview == nil {
strongSelf.addSubnode(item.node) strongSelf.view.addSubview(item.node.view)
if animation.isAnimated { if animation.isAnimated {
item.node.layer.animateScale(from: 0.01, to: 1.0, duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring) item.node.view.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.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
} }
item.node.frame = itemFrame item.node.view.frame = itemFrame
} else { } 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 itemValue = item.value
let itemNode = item.node let itemNode = item.node
item.node.isGestureEnabled = true item.node.view.isGestureEnabled = true
let canViewReactionList = canViewMessageReactionList(message: message) let canViewReactionList = canViewMessageReactionList(message: message)
item.node.activateAfterCompletion = !canViewReactionList item.node.view.activateAfterCompletion = !canViewReactionList
item.node.activated = { [weak itemNode] gesture, _ in item.node.view.activated = { [weak itemNode] gesture, _ in
guard let strongSelf = self, let itemNode = itemNode else { guard let strongSelf = self, let itemNode = itemNode else {
gesture.cancel() gesture.cancel()
return return
@ -328,9 +328,9 @@ final class MessageReactionButtonsNode: ASDisplayNode {
if !canViewReactionList { if !canViewReactionList {
return 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 { if itemMaskView.superview == nil {
strongSelf.backgroundMaskView?.addSubview(itemMaskView) strongSelf.backgroundMaskView?.addSubview(itemMaskView)
@ -364,12 +364,12 @@ final class MessageReactionButtonsNode: ASDisplayNode {
for node in reactionButtons.removedNodes { for node in reactionButtons.removedNodes {
if animation.isAnimated { if animation.isAnimated {
node.layer.animateScale(from: 1.0, to: 0.01, duration: 0.2, removeOnCompletion: false) node.view.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.view.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak node] _ in
node?.removeFromSupernode() node?.view.removeFromSuperview()
}) })
} else { } else {
node.removeFromSupernode() node.view.removeFromSuperview()
} }
} }
}) })
@ -409,7 +409,7 @@ final class MessageReactionButtonsNode: ASDisplayNode {
func reactionTargetView(value: String) -> UIView? { func reactionTargetView(value: String) -> UIView? {
for (key, button) in self.container.buttons { for (key, button) in self.container.buttons {
if key == value { if key == value {
return button.iconView return button.view.iconView
} }
} }
return nil return nil
@ -417,20 +417,20 @@ final class MessageReactionButtonsNode: ASDisplayNode {
func animateIn(animation: ListViewItemUpdateAnimation) { func animateIn(animation: ListViewItemUpdateAnimation) {
for (_, button) in self.container.buttons { 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) { func animateOut(animation: ListViewItemUpdateAnimation) {
for (_, button) in self.container.buttons { 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? { override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
for (_, button) in self.container.buttons { for (_, button) in self.container.buttons {
if button.frame.contains(point) { if button.view.frame.contains(point) {
if let result = button.hitTest(self.view.convert(point, to: button.view), with: event) { if let result = button.view.hitTest(self.view.convert(point, to: button.view), with: event) {
return result return result
} }
} }
@ -600,7 +600,7 @@ final class ChatMessageReactionButtonsNode: ASDisplayNode {
private let buttonsNode: MessageReactionButtonsNode private let buttonsNode: MessageReactionButtonsNode
var reactionSelected: ((String) -> Void)? var reactionSelected: ((String) -> Void)?
var openReactionPreview: ((ContextGesture?, ContextExtractedContentContainingNode, String) -> Void)? var openReactionPreview: ((ContextGesture?, ContextExtractedContentContainingView, String) -> Void)?
override init() { override init() {
self.buttonsNode = MessageReactionButtonsNode() self.buttonsNode = MessageReactionButtonsNode()

View File

@ -62,7 +62,7 @@ class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
self.textNode.isUserInteractionEnabled = false self.textNode.isUserInteractionEnabled = false
self.textNode.contentMode = .topLeft self.textNode.contentMode = .topLeft
self.textNode.contentsScale = UIScreenScale self.textNode.contentsScale = UIScreenScale
self.textNode.displaysAsynchronously = false self.textNode.displaysAsynchronously = true
self.addSubnode(self.textNode) self.addSubnode(self.textNode)
self.addSubnode(self.textAccessibilityOverlayNode) 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) strongSelf.presentInGlobalOverlay(contextController)
}) })
} }
@ -8140,7 +8140,7 @@ private final class SettingsTabBarContextExtractedContentSource: ContextExtracte
} }
func takeView() -> ContextControllerTakeViewInfo? { 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? { func putBack() -> ContextControllerPutBackViewInfo? {
@ -8415,7 +8415,7 @@ private final class MessageContextExtractedContentSource: ContextExtractedConten
} }
func takeView() -> ContextControllerTakeViewInfo? { 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? { func putBack() -> ContextControllerPutBackViewInfo? {

View File

@ -8,7 +8,7 @@ import TgVoip
import TgVoipWebrtc import TgVoipWebrtc
private let debugUseLegacyVersionForReflectors: Bool = { private let debugUseLegacyVersionForReflectors: Bool = {
#if DEBUG && false #if DEBUG
return true return true
#else #else
return false return false
@ -827,7 +827,7 @@ public final class OngoingCallContext {
filteredConnections.append(contentsOf: callConnectionDescriptionsWebrtc(connection, idMapping: reflectorIdMapping)) filteredConnections.append(contentsOf: callConnectionDescriptionsWebrtc(connection, idMapping: reflectorIdMapping))
} }
/*#if DEBUG if debugUseLegacyVersionForReflectors {
for connection in filteredConnections { for connection in filteredConnections {
if connection.username == "reflector" { 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)) filteredConnections.append(OngoingCallConnectionDescriptionWebrtc(reflectorId: 0, hasStun: false, hasTurn: true, hasTcp: true, ip: "91.108.12.1", port: 533, username: "reflector", password: connection.password))
@ -835,7 +835,7 @@ public final class OngoingCallContext {
break 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 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) 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]? { public func addLocallyGeneratedEntities(_ text: String, enabledTypes: EnabledEntityTypes, entities: [MessageTextEntity], mediaDuration: Double? = nil) -> [MessageTextEntity]? {
if "".isEmpty {
return nil
}
var resultEntities = entities var resultEntities = entities
var hasDigits = false var hasDigits = false

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