mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-11-07 09:20:08 +00:00
Pinch
This commit is contained in:
parent
085c49b784
commit
9a1cdb0813
@ -120,7 +120,7 @@ private final class TipValueNode: ASDisplayNode {
|
|||||||
self.action?()
|
self.action?()
|
||||||
}
|
}
|
||||||
|
|
||||||
func update(theme: PresentationTheme, text: String, isHighlighted: Bool, height: CGFloat) -> CGFloat {
|
func update(theme: PresentationTheme, text: String, isHighlighted: Bool, height: CGFloat) -> (CGFloat, (CGFloat) -> Void) {
|
||||||
var updateBackground = false
|
var updateBackground = false
|
||||||
let backgroundColor = isHighlighted ? UIColor(rgb: 0x00A650) : UIColor(rgb: 0xE5F6ED)
|
let backgroundColor = isHighlighted ? UIColor(rgb: 0x00A650) : UIColor(rgb: 0xE5F6ED)
|
||||||
if let currentBackgroundColor = self.currentBackgroundColor {
|
if let currentBackgroundColor = self.currentBackgroundColor {
|
||||||
@ -142,20 +142,22 @@ private final class TipValueNode: ASDisplayNode {
|
|||||||
|
|
||||||
let calculatedWidth = max(titleSize.width + 16.0 * 2.0, minWidth)
|
let calculatedWidth = max(titleSize.width + 16.0 * 2.0, minWidth)
|
||||||
|
|
||||||
self.titleNode.frame = CGRect(origin: CGPoint(x: floor((calculatedWidth - titleSize.width) / 2.0), y: floor((height - titleSize.height) / 2.0)), size: titleSize)
|
return (calculatedWidth, { calculatedWidth in
|
||||||
|
self.titleNode.frame = CGRect(origin: CGPoint(x: floor((calculatedWidth - titleSize.width) / 2.0), y: floor((height - titleSize.height) / 2.0)), size: titleSize)
|
||||||
|
|
||||||
let size = CGSize(width: calculatedWidth, height: height)
|
let size = CGSize(width: calculatedWidth, height: height)
|
||||||
self.backgroundNode.frame = CGRect(origin: CGPoint(), size: size)
|
self.backgroundNode.frame = CGRect(origin: CGPoint(), size: size)
|
||||||
|
|
||||||
self.button.frame = CGRect(origin: CGPoint(), size: size)
|
self.button.frame = CGRect(origin: CGPoint(), size: size)
|
||||||
|
})
|
||||||
return size.width
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class BotCheckoutTipItemNode: ListViewItemNode, UITextFieldDelegate {
|
class BotCheckoutTipItemNode: ListViewItemNode, UITextFieldDelegate {
|
||||||
let titleNode: TextNode
|
let titleNode: TextNode
|
||||||
let labelNode: TextNode
|
let labelNode: TextNode
|
||||||
|
let tipMeasurementNode: ImmediateTextNode
|
||||||
|
let tipCurrencyNode: ImmediateTextNode
|
||||||
private let textNode: TextFieldNode
|
private let textNode: TextFieldNode
|
||||||
|
|
||||||
private var formatterDelegate: CurrencyUITextFieldDelegate?
|
private var formatterDelegate: CurrencyUITextFieldDelegate?
|
||||||
@ -172,6 +174,9 @@ class BotCheckoutTipItemNode: ListViewItemNode, UITextFieldDelegate {
|
|||||||
self.labelNode = TextNode()
|
self.labelNode = TextNode()
|
||||||
self.labelNode.isUserInteractionEnabled = false
|
self.labelNode.isUserInteractionEnabled = false
|
||||||
|
|
||||||
|
self.tipMeasurementNode = ImmediateTextNode()
|
||||||
|
self.tipCurrencyNode = ImmediateTextNode()
|
||||||
|
|
||||||
self.textNode = TextFieldNode()
|
self.textNode = TextFieldNode()
|
||||||
|
|
||||||
self.scrollNode = ASScrollNode()
|
self.scrollNode = ASScrollNode()
|
||||||
@ -190,6 +195,7 @@ class BotCheckoutTipItemNode: ListViewItemNode, UITextFieldDelegate {
|
|||||||
self.addSubnode(self.titleNode)
|
self.addSubnode(self.titleNode)
|
||||||
self.addSubnode(self.labelNode)
|
self.addSubnode(self.labelNode)
|
||||||
self.addSubnode(self.textNode)
|
self.addSubnode(self.textNode)
|
||||||
|
self.addSubnode(self.tipCurrencyNode)
|
||||||
self.addSubnode(self.scrollNode)
|
self.addSubnode(self.scrollNode)
|
||||||
|
|
||||||
self.textNode.clipsToBounds = true
|
self.textNode.clipsToBounds = true
|
||||||
@ -221,7 +227,7 @@ class BotCheckoutTipItemNode: ListViewItemNode, UITextFieldDelegate {
|
|||||||
|
|
||||||
let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.title, font: textFont, textColor: textColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - params.leftInset - params.rightInset - 20.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.title, font: textFont, textColor: textColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - params.leftInset - params.rightInset - 20.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||||
|
|
||||||
//TODO:locali
|
//TODO:localize
|
||||||
let (labelLayout, labelApply) = makeLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: "Enter Custom", font: textFont, textColor: textColor.withMultipliedAlpha(0.8)), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - params.leftInset - params.rightInset - 20.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
let (labelLayout, labelApply) = makeLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: "Enter Custom", font: textFont, textColor: textColor.withMultipliedAlpha(0.8)), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - params.leftInset - params.rightInset - 20.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||||
|
|
||||||
return (ListViewItemNodeLayout(contentSize: contentSize, insets: insets), { [weak self] in
|
return (ListViewItemNodeLayout(contentSize: contentSize, insets: insets), { [weak self] in
|
||||||
@ -236,17 +242,6 @@ class BotCheckoutTipItemNode: ListViewItemNode, UITextFieldDelegate {
|
|||||||
strongSelf.titleNode.frame = CGRect(origin: CGPoint(x: leftInset, y: floor((labelsContentHeight - titleLayout.size.height) / 2.0)), size: titleLayout.size)
|
strongSelf.titleNode.frame = CGRect(origin: CGPoint(x: leftInset, y: floor((labelsContentHeight - titleLayout.size.height) / 2.0)), size: titleLayout.size)
|
||||||
strongSelf.labelNode.frame = CGRect(origin: CGPoint(x: params.width - leftInset - labelLayout.size.width, y: floor((labelsContentHeight - labelLayout.size.height) / 2.0)), size: labelLayout.size)
|
strongSelf.labelNode.frame = CGRect(origin: CGPoint(x: params.width - leftInset - labelLayout.size.width, y: floor((labelsContentHeight - labelLayout.size.height) / 2.0)), size: labelLayout.size)
|
||||||
|
|
||||||
let text: String
|
|
||||||
if item.numericValue == 0 {
|
|
||||||
text = ""
|
|
||||||
} else {
|
|
||||||
text = formatCurrencyAmount(item.numericValue, currency: item.currency)
|
|
||||||
}
|
|
||||||
if strongSelf.textNode.textField.text ?? "" != text {
|
|
||||||
strongSelf.textNode.textField.text = text
|
|
||||||
strongSelf.labelNode.isHidden = !text.isEmpty
|
|
||||||
}
|
|
||||||
|
|
||||||
if strongSelf.formatterDelegate == nil {
|
if strongSelf.formatterDelegate == nil {
|
||||||
strongSelf.formatterDelegate = CurrencyUITextFieldDelegate(formatter: CurrencyFormatter(currency: item.currency, { formatter in
|
strongSelf.formatterDelegate = CurrencyUITextFieldDelegate(formatter: CurrencyFormatter(currency: item.currency, { formatter in
|
||||||
formatter.maxValue = currencyToFractionalAmount(value: item.maxValue, currency: item.currency) ?? 10000.0
|
formatter.maxValue = currencyToFractionalAmount(value: item.maxValue, currency: item.currency) ?? 10000.0
|
||||||
@ -275,18 +270,31 @@ class BotCheckoutTipItemNode: ListViewItemNode, UITextFieldDelegate {
|
|||||||
strongSelf.textNode.textField.keyboardType = .decimalPad
|
strongSelf.textNode.textField.keyboardType = .decimalPad
|
||||||
strongSelf.textNode.textField.tintColor = item.theme.list.itemAccentColor
|
strongSelf.textNode.textField.tintColor = item.theme.list.itemAccentColor
|
||||||
|
|
||||||
strongSelf.textNode.frame = CGRect(origin: CGPoint(x: params.width - leftInset - 150.0, y: -2.0), size: CGSize(width: 150.0, height: labelsContentHeight))
|
var textInputFrame = CGRect(origin: CGPoint(x: params.width - leftInset - 150.0, y: -2.0), size: CGSize(width: 150.0, height: labelsContentHeight))
|
||||||
|
|
||||||
|
var currencyText: (String, String) = formatCurrencyAmountCustom(item.numericValue, currency: item.currency)
|
||||||
|
if strongSelf.textNode.textField.text ?? "" != currencyText.0 {
|
||||||
|
strongSelf.textNode.textField.text = currencyText.0
|
||||||
|
strongSelf.labelNode.isHidden = !currencyText.0.isEmpty
|
||||||
|
}
|
||||||
|
|
||||||
|
strongSelf.tipMeasurementNode.attributedText = NSAttributedString(string: currencyText.0, font: titleFont, textColor: textColor)
|
||||||
|
let inputTextSize = strongSelf.tipMeasurementNode.updateLayout(textInputFrame.size)
|
||||||
|
|
||||||
|
strongSelf.tipCurrencyNode.attributedText = NSAttributedString(string: " \(currencyText.1)", font: titleFont, textColor: textColor)
|
||||||
|
let currencySize = strongSelf.tipCurrencyNode.updateLayout(CGSize(width: 100.0, height: .greatestFiniteMagnitude))
|
||||||
|
strongSelf.tipCurrencyNode.frame = CGRect(origin: CGPoint(x: textInputFrame.maxX - currencySize.width, y: floor((labelsContentHeight - currencySize.height) / 2.0) - 1.0), size: currencySize)
|
||||||
|
textInputFrame.origin.x -= currencySize.width
|
||||||
|
|
||||||
|
strongSelf.textNode.frame = textInputFrame
|
||||||
|
|
||||||
let valueHeight: CGFloat = 52.0
|
let valueHeight: CGFloat = 52.0
|
||||||
let valueY: CGFloat = labelsContentHeight + 9.0
|
let valueY: CGFloat = labelsContentHeight + 9.0
|
||||||
|
|
||||||
var index = 0
|
var index = 0
|
||||||
var variantsOffset: CGFloat = 16.0
|
var variantLayouts: [(CGFloat, (CGFloat) -> Void)] = []
|
||||||
|
var totalMinWidth: CGFloat = 0.0
|
||||||
for (variantText, variantValue) in item.availableVariants {
|
for (variantText, variantValue) in item.availableVariants {
|
||||||
if index != 0 {
|
|
||||||
variantsOffset += 12.0
|
|
||||||
}
|
|
||||||
|
|
||||||
let valueNode: TipValueNode
|
let valueNode: TipValueNode
|
||||||
if strongSelf.valueNodes.count > index {
|
if strongSelf.valueNodes.count > index {
|
||||||
valueNode = strongSelf.valueNodes[index]
|
valueNode = strongSelf.valueNodes[index]
|
||||||
@ -295,14 +303,39 @@ class BotCheckoutTipItemNode: ListViewItemNode, UITextFieldDelegate {
|
|||||||
strongSelf.valueNodes.append(valueNode)
|
strongSelf.valueNodes.append(valueNode)
|
||||||
strongSelf.scrollNode.addSubnode(valueNode)
|
strongSelf.scrollNode.addSubnode(valueNode)
|
||||||
}
|
}
|
||||||
let nodeWidth = valueNode.update(theme: item.theme, text: variantText, isHighlighted: item.value == variantText, height: valueHeight)
|
let (nodeMinWidth, nodeApply) = valueNode.update(theme: item.theme, text: variantText, isHighlighted: item.value == variantText, height: valueHeight)
|
||||||
valueNode.action = {
|
valueNode.action = {
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
strongSelf.item?.updateValue(variantValue)
|
strongSelf.item?.updateValue(variantValue)
|
||||||
}
|
}
|
||||||
|
totalMinWidth += nodeMinWidth
|
||||||
|
variantLayouts.append((nodeMinWidth, nodeApply))
|
||||||
|
index += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
let sideInset: CGFloat = 16.0
|
||||||
|
var scaleFactor: CGFloat = 1.0
|
||||||
|
let availableWidth = params.width - sideInset * 2.0 - CGFloat(max(0, item.availableVariants.count - 1)) * 12.0
|
||||||
|
if totalMinWidth < availableWidth {
|
||||||
|
scaleFactor = availableWidth / totalMinWidth
|
||||||
|
}
|
||||||
|
|
||||||
|
index = 0
|
||||||
|
var variantsOffset: CGFloat = 16.0
|
||||||
|
for _ in item.availableVariants {
|
||||||
|
if index != 0 {
|
||||||
|
variantsOffset += 12.0
|
||||||
|
}
|
||||||
|
|
||||||
|
let valueNode: TipValueNode = strongSelf.valueNodes[index]
|
||||||
|
let (minWidth, nodeApply) = variantLayouts[index]
|
||||||
|
|
||||||
|
let nodeWidth = floor(scaleFactor * minWidth)
|
||||||
|
|
||||||
valueNode.frame = CGRect(origin: CGPoint(x: variantsOffset, y: 0.0), size: CGSize(width: nodeWidth, height: valueHeight))
|
valueNode.frame = CGRect(origin: CGPoint(x: variantsOffset, y: 0.0), size: CGSize(width: nodeWidth, height: valueHeight))
|
||||||
|
nodeApply(nodeWidth)
|
||||||
variantsOffset += nodeWidth
|
variantsOffset += nodeWidth
|
||||||
index += 1
|
index += 1
|
||||||
}
|
}
|
||||||
@ -342,7 +375,15 @@ class BotCheckoutTipItemNode: ListViewItemNode, UITextFieldDelegate {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if let value = fractionalToCurrencyAmount(value: doubleValue, currency: item.currency) {
|
if var value = fractionalToCurrencyAmount(value: doubleValue, currency: item.currency) {
|
||||||
|
if value > item.maxValue {
|
||||||
|
value = item.maxValue
|
||||||
|
|
||||||
|
let currencyText: (String, String) = formatCurrencyAmountCustom(value, currency: item.currency)
|
||||||
|
if self.textNode.textField.text ?? "" != currencyText.0 {
|
||||||
|
self.textNode.textField.text = currencyText.0
|
||||||
|
}
|
||||||
|
}
|
||||||
item.updateValue(value)
|
item.updateValue(value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
395
submodules/ContextUI/Sources/PinchController.swift
Normal file
395
submodules/ContextUI/Sources/PinchController.swift
Normal file
@ -0,0 +1,395 @@
|
|||||||
|
import Foundation
|
||||||
|
import UIKit
|
||||||
|
import AsyncDisplayKit
|
||||||
|
import Display
|
||||||
|
import TelegramPresentationData
|
||||||
|
import TextSelectionNode
|
||||||
|
import ReactionSelectionNode
|
||||||
|
import TelegramCore
|
||||||
|
import SyncCore
|
||||||
|
import SwiftSignalKit
|
||||||
|
|
||||||
|
private func convertFrame(_ frame: CGRect, from fromView: UIView, to toView: UIView) -> CGRect {
|
||||||
|
let sourceWindowFrame = fromView.convert(frame, to: nil)
|
||||||
|
var targetWindowFrame = toView.convert(sourceWindowFrame, from: nil)
|
||||||
|
|
||||||
|
if let fromWindow = fromView.window, let toWindow = toView.window {
|
||||||
|
targetWindowFrame.origin.x += toWindow.bounds.width - fromWindow.bounds.width
|
||||||
|
}
|
||||||
|
return targetWindowFrame
|
||||||
|
}
|
||||||
|
|
||||||
|
final class PinchSourceGesture: UIPinchGestureRecognizer {
|
||||||
|
private final class Target {
|
||||||
|
var updated: (() -> Void)?
|
||||||
|
|
||||||
|
@objc func onGesture(_ gesture: UIPinchGestureRecognizer) {
|
||||||
|
self.updated?()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private let target: Target
|
||||||
|
|
||||||
|
private(set) var currentTransform: (CGFloat, CGPoint)?
|
||||||
|
|
||||||
|
var began: (() -> Void)?
|
||||||
|
var updated: ((CGFloat, CGPoint) -> Void)?
|
||||||
|
var ended: (() -> Void)?
|
||||||
|
|
||||||
|
private var lastLocation: CGPoint?
|
||||||
|
private var currentOffset = CGPoint()
|
||||||
|
|
||||||
|
init() {
|
||||||
|
self.target = Target()
|
||||||
|
|
||||||
|
super.init(target: self.target, action: #selector(self.target.onGesture(_:)))
|
||||||
|
|
||||||
|
self.target.updated = { [weak self] in
|
||||||
|
self?.gestureUpdated()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override func reset() {
|
||||||
|
super.reset()
|
||||||
|
|
||||||
|
self.lastLocation = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
|
||||||
|
super.touchesBegan(touches, with: event)
|
||||||
|
}
|
||||||
|
|
||||||
|
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent) {
|
||||||
|
super.touchesEnded(touches, with: event)
|
||||||
|
}
|
||||||
|
|
||||||
|
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent) {
|
||||||
|
super.touchesCancelled(touches, with: event)
|
||||||
|
}
|
||||||
|
|
||||||
|
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent) {
|
||||||
|
super.touchesMoved(touches, with: event)
|
||||||
|
|
||||||
|
if touches.count >= 2 {
|
||||||
|
var locationSum = CGPoint()
|
||||||
|
for touch in touches {
|
||||||
|
let point = touch.location(in: self.view)
|
||||||
|
locationSum.x += point.x
|
||||||
|
locationSum.y += point.y
|
||||||
|
}
|
||||||
|
locationSum.x /= CGFloat(touches.count)
|
||||||
|
locationSum.y /= CGFloat(touches.count)
|
||||||
|
if let lastLocation = self.lastLocation {
|
||||||
|
self.currentOffset = CGPoint(x: locationSum.x - lastLocation.x, y: locationSum.y - lastLocation.y)
|
||||||
|
} else {
|
||||||
|
self.lastLocation = locationSum
|
||||||
|
self.currentOffset = CGPoint()
|
||||||
|
}
|
||||||
|
if let (scale, _) = self.currentTransform {
|
||||||
|
self.currentTransform = (scale, self.currentOffset)
|
||||||
|
self.updated?(scale, self.currentOffset)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func gestureUpdated() {
|
||||||
|
switch self.state {
|
||||||
|
case .began:
|
||||||
|
self.lastLocation = nil
|
||||||
|
self.currentOffset = CGPoint()
|
||||||
|
self.currentTransform = nil
|
||||||
|
self.began?()
|
||||||
|
case .changed:
|
||||||
|
let scale = max(1.0, self.scale)
|
||||||
|
self.currentTransform = (scale, self.currentOffset)
|
||||||
|
self.updated?(scale, self.currentOffset)
|
||||||
|
case .ended, .cancelled:
|
||||||
|
self.ended?()
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func cancelContextGestures(node: ASDisplayNode) {
|
||||||
|
if let node = node as? ContextControllerSourceNode {
|
||||||
|
node.cancelGesture()
|
||||||
|
}
|
||||||
|
|
||||||
|
if let supernode = node.supernode {
|
||||||
|
cancelContextGestures(node: supernode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func cancelContextGestures(view: UIView) {
|
||||||
|
if let gestureRecognizers = view.gestureRecognizers {
|
||||||
|
for recognizer in gestureRecognizers {
|
||||||
|
if let recognizer = recognizer as? InteractiveTransitionGestureRecognizer {
|
||||||
|
recognizer.cancel()
|
||||||
|
} else if let recognizer = recognizer as? WindowPanRecognizer {
|
||||||
|
recognizer.cancel()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let superview = view.superview {
|
||||||
|
cancelContextGestures(view: superview)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class PinchSourceContainerNode: ASDisplayNode {
|
||||||
|
public let contentNode: ASDisplayNode
|
||||||
|
public var contentRect: CGRect = CGRect()
|
||||||
|
private(set) var naturalContentFrame: CGRect?
|
||||||
|
|
||||||
|
fileprivate let gesture: PinchSourceGesture
|
||||||
|
|
||||||
|
public var isPinchGestureEnabled: Bool = false {
|
||||||
|
didSet {
|
||||||
|
if self.isPinchGestureEnabled != oldValue {
|
||||||
|
self.gesture.isEnabled = self.isPinchGestureEnabled
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var isActive: Bool = false
|
||||||
|
|
||||||
|
public var activate: ((PinchSourceContainerNode) -> Void)?
|
||||||
|
public var scaleUpdated: ((CGFloat, ContainedViewLayoutTransition) -> Void)?
|
||||||
|
var deactivate: (() -> Void)?
|
||||||
|
var updated: ((CGFloat, CGPoint) -> Void)?
|
||||||
|
|
||||||
|
override public init() {
|
||||||
|
self.gesture = PinchSourceGesture()
|
||||||
|
self.contentNode = ASDisplayNode()
|
||||||
|
|
||||||
|
super.init()
|
||||||
|
|
||||||
|
self.addSubnode(self.contentNode)
|
||||||
|
|
||||||
|
self.gesture.began = { [weak self] in
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
cancelContextGestures(node: strongSelf)
|
||||||
|
cancelContextGestures(view: strongSelf.view)
|
||||||
|
strongSelf.isActive = true
|
||||||
|
|
||||||
|
strongSelf.activate?(strongSelf)
|
||||||
|
}
|
||||||
|
|
||||||
|
self.gesture.ended = { [weak self] in
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
strongSelf.isActive = false
|
||||||
|
strongSelf.deactivate?()
|
||||||
|
}
|
||||||
|
|
||||||
|
self.gesture.updated = { [weak self] scale, offset in
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
strongSelf.updated?(scale, offset)
|
||||||
|
strongSelf.scaleUpdated?(scale, .immediate)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override public func didLoad() {
|
||||||
|
super.didLoad()
|
||||||
|
|
||||||
|
self.view.addGestureRecognizer(self.gesture)
|
||||||
|
self.view.disablesInteractiveTransitionGestureRecognizerNow = { [weak self] in
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return strongSelf.isActive
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func update(size: CGSize, transition: ContainedViewLayoutTransition) {
|
||||||
|
let contentFrame = CGRect(origin: CGPoint(), size: size)
|
||||||
|
self.naturalContentFrame = contentFrame
|
||||||
|
if !self.isActive {
|
||||||
|
transition.updateFrame(node: self.contentNode, frame: contentFrame)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func restoreToNaturalSize() {
|
||||||
|
guard let naturalContentFrame = self.naturalContentFrame else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.contentNode.frame = naturalContentFrame
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final class PinchControllerNode: ViewControllerTracingNode {
|
||||||
|
private weak var controller: PinchController?
|
||||||
|
private let sourceNode: PinchSourceContainerNode
|
||||||
|
|
||||||
|
private let dimNode: ASDisplayNode
|
||||||
|
|
||||||
|
private var validLayout: ContainerViewLayout?
|
||||||
|
private var isAnimatingOut: Bool = false
|
||||||
|
|
||||||
|
private var hapticFeedback: HapticFeedback?
|
||||||
|
|
||||||
|
init(controller: PinchController, sourceNode: PinchSourceContainerNode) {
|
||||||
|
self.controller = controller
|
||||||
|
self.sourceNode = sourceNode
|
||||||
|
|
||||||
|
self.dimNode = ASDisplayNode()
|
||||||
|
self.dimNode.backgroundColor = UIColor(white: 0.0, alpha: 0.5)
|
||||||
|
self.dimNode.alpha = 0.0
|
||||||
|
|
||||||
|
super.init()
|
||||||
|
|
||||||
|
self.addSubnode(self.dimNode)
|
||||||
|
|
||||||
|
self.sourceNode.deactivate = { [weak self] in
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
strongSelf.controller?.dismiss()
|
||||||
|
}
|
||||||
|
|
||||||
|
self.sourceNode.updated = { [weak self] scale, offset in
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
strongSelf.dimNode.alpha = max(0.0, min(1.0, scale - 1.0))
|
||||||
|
strongSelf.sourceNode.contentNode.transform = CATransform3DTranslate(CATransform3DMakeScale(scale, scale, 1.0), offset.x / scale, offset.y / scale, 0.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
}
|
||||||
|
|
||||||
|
override func didLoad() {
|
||||||
|
super.didLoad()
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateLayout(layout: ContainerViewLayout, transition: ContainedViewLayoutTransition, previousActionsContainerNode: ContextActionsContainerNode?) {
|
||||||
|
if self.isAnimatingOut {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
self.validLayout = layout
|
||||||
|
|
||||||
|
transition.updateFrame(node: self.dimNode, frame: CGRect(origin: CGPoint(), size: layout.size))
|
||||||
|
}
|
||||||
|
|
||||||
|
func animateIn() {
|
||||||
|
let convertedFrame = convertFrame(self.sourceNode.contentNode.frame, from: self.sourceNode.view, to: self.view)
|
||||||
|
self.sourceNode.contentNode.frame = convertedFrame
|
||||||
|
self.addSubnode(self.sourceNode.contentNode)
|
||||||
|
}
|
||||||
|
|
||||||
|
func animateOut(completion: @escaping () -> Void) {
|
||||||
|
let performCompletion: () -> Void = { [weak self] in
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
strongSelf.sourceNode.restoreToNaturalSize()
|
||||||
|
strongSelf.sourceNode.addSubnode(strongSelf.sourceNode.contentNode)
|
||||||
|
|
||||||
|
completion()
|
||||||
|
}
|
||||||
|
|
||||||
|
if let (scale, offset) = self.sourceNode.gesture.currentTransform {
|
||||||
|
let duration = 0.4
|
||||||
|
let transition: ContainedViewLayoutTransition = .animated(duration: duration, curve: .spring)
|
||||||
|
if self.hapticFeedback == nil {
|
||||||
|
self.hapticFeedback = HapticFeedback()
|
||||||
|
}
|
||||||
|
self.hapticFeedback?.prepareImpact(.light)
|
||||||
|
Queue.mainQueue().after(0.2, { [weak self] in
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
strongSelf.hapticFeedback?.impact(.light)
|
||||||
|
})
|
||||||
|
|
||||||
|
self.sourceNode.scaleUpdated?(1.0, transition)
|
||||||
|
|
||||||
|
self.sourceNode.contentNode.transform = CATransform3DIdentity
|
||||||
|
self.sourceNode.contentNode.layer.animateSpring(from: scale as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: duration * 1.2, damping: 110.0)
|
||||||
|
self.sourceNode.contentNode.layer.animatePosition(from: CGPoint(x: offset.x / scale, y: offset.y / scale), to: CGPoint(), duration: duration, timingFunction: kCAMediaTimingFunctionSpring, additive: true, force: true, completion: { _ in
|
||||||
|
performCompletion()
|
||||||
|
})
|
||||||
|
|
||||||
|
let dimNodeTransition: ContainedViewLayoutTransition = .animated(duration: 0.3, curve: .easeInOut)
|
||||||
|
dimNodeTransition.updateAlpha(node: self.dimNode, alpha: 0.0)
|
||||||
|
} else {
|
||||||
|
performCompletion()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class PinchController: ViewController, StandalonePresentableController {
|
||||||
|
private let _ready = Promise<Bool>()
|
||||||
|
override public var ready: Promise<Bool> {
|
||||||
|
return self._ready
|
||||||
|
}
|
||||||
|
|
||||||
|
private let sourceNode: PinchSourceContainerNode
|
||||||
|
|
||||||
|
private var wasDismissed = false
|
||||||
|
|
||||||
|
private var controllerNode: PinchControllerNode {
|
||||||
|
return self.displayNode as! PinchControllerNode
|
||||||
|
}
|
||||||
|
|
||||||
|
public init(sourceNode: PinchSourceContainerNode) {
|
||||||
|
self.sourceNode = sourceNode
|
||||||
|
|
||||||
|
super.init(navigationBarPresentationData: nil)
|
||||||
|
|
||||||
|
self.statusBar.statusBarStyle = .Ignore
|
||||||
|
|
||||||
|
self.lockOrientation = true
|
||||||
|
self.blocksBackgroundWhenInOverlay = true
|
||||||
|
}
|
||||||
|
|
||||||
|
required init(coder aDecoder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
}
|
||||||
|
|
||||||
|
override public func loadDisplayNode() {
|
||||||
|
self.displayNode = PinchControllerNode(controller: self, sourceNode: self.sourceNode)
|
||||||
|
|
||||||
|
self.displayNodeDidLoad()
|
||||||
|
|
||||||
|
self._ready.set(.single(true))
|
||||||
|
}
|
||||||
|
|
||||||
|
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
||||||
|
super.containerLayoutUpdated(layout, transition: transition)
|
||||||
|
|
||||||
|
self.controllerNode.updateLayout(layout: layout, transition: transition, previousActionsContainerNode: nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
override public func viewDidAppear(_ animated: Bool) {
|
||||||
|
if self.ignoreAppearanceMethodInvocations() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
super.viewDidAppear(animated)
|
||||||
|
|
||||||
|
self.controllerNode.animateIn()
|
||||||
|
}
|
||||||
|
|
||||||
|
override public func dismiss(completion: (() -> Void)? = nil) {
|
||||||
|
if !self.wasDismissed {
|
||||||
|
self.wasDismissed = true
|
||||||
|
self.controllerNode.animateOut(completion: { [weak self] in
|
||||||
|
self?.presentingViewController?.dismiss(animated: false, completion: nil)
|
||||||
|
completion?()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -83,6 +83,10 @@ public class InteractiveTransitionGestureRecognizer: UIPanGestureRecognizer {
|
|||||||
self.currentAllowedDirections = []
|
self.currentAllowedDirections = []
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func cancel() {
|
||||||
|
self.state = .cancelled
|
||||||
|
}
|
||||||
|
|
||||||
override public func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
|
override public func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
|
||||||
let touch = touches.first!
|
let touch = touches.first!
|
||||||
let point = touch.location(in: self.view)
|
let point = touch.location(in: self.view)
|
||||||
|
|||||||
@ -12,15 +12,15 @@ public protocol TransformImageCustomArguments {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public struct TransformImageArguments: Equatable {
|
public struct TransformImageArguments: Equatable {
|
||||||
public let corners: ImageCorners
|
public var corners: ImageCorners
|
||||||
|
|
||||||
public let imageSize: CGSize
|
public var imageSize: CGSize
|
||||||
public let boundingSize: CGSize
|
public var boundingSize: CGSize
|
||||||
public let intrinsicInsets: UIEdgeInsets
|
public var intrinsicInsets: UIEdgeInsets
|
||||||
public let resizeMode: TransformImageResizeMode
|
public var resizeMode: TransformImageResizeMode
|
||||||
public let emptyColor: UIColor?
|
public var emptyColor: UIColor?
|
||||||
public let custom: TransformImageCustomArguments?
|
public var custom: TransformImageCustomArguments?
|
||||||
public let scale: CGFloat?
|
public var scale: CGFloat?
|
||||||
|
|
||||||
public init(corners: ImageCorners, imageSize: CGSize, boundingSize: CGSize, intrinsicInsets: UIEdgeInsets, resizeMode: TransformImageResizeMode = .fill(.black), emptyColor: UIColor? = nil, custom: TransformImageCustomArguments? = nil, scale: CGFloat? = nil) {
|
public init(corners: ImageCorners, imageSize: CGSize, boundingSize: CGSize, intrinsicInsets: UIEdgeInsets, resizeMode: TransformImageResizeMode = .fill(.black), emptyColor: UIColor? = nil, custom: TransformImageCustomArguments? = nil, scale: CGFloat? = nil) {
|
||||||
self.corners = corners
|
self.corners = corners
|
||||||
|
|||||||
@ -14,6 +14,10 @@ public final class WindowPanRecognizer: UIGestureRecognizer {
|
|||||||
self.previousPoints.removeAll()
|
self.previousPoints.removeAll()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func cancel() {
|
||||||
|
self.state = .cancelled
|
||||||
|
}
|
||||||
|
|
||||||
private func addPoint(_ point: CGPoint) {
|
private func addPoint(_ point: CGPoint) {
|
||||||
self.previousPoints.append((point, CACurrentMediaTime()))
|
self.previousPoints.append((point, CACurrentMediaTime()))
|
||||||
if self.previousPoints.count > 6 {
|
if self.previousPoints.count > 6 {
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import AsyncDisplayKit
|
|||||||
import TelegramPresentationData
|
import TelegramPresentationData
|
||||||
import TelegramUIPreferences
|
import TelegramUIPreferences
|
||||||
import AccountContext
|
import AccountContext
|
||||||
|
import ContextUI
|
||||||
|
|
||||||
final class InstantPageAnchorItem: InstantPageItem {
|
final class InstantPageAnchorItem: InstantPageItem {
|
||||||
let wantsNode: Bool = false
|
let wantsNode: Bool = false
|
||||||
@ -28,7 +29,7 @@ final class InstantPageAnchorItem: InstantPageItem {
|
|||||||
func drawInTile(context: CGContext) {
|
func drawInTile(context: CGContext) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func node(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, theme: InstantPageTheme, sourcePeerType: MediaAutoDownloadPeerType, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> (InstantPageNode & ASDisplayNode)? {
|
func node(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, theme: InstantPageTheme, sourcePeerType: MediaAutoDownloadPeerType, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, activatePinchPreview: ((PinchSourceContainerNode) -> Void)?, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> (InstantPageNode & ASDisplayNode)? {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import AsyncDisplayKit
|
|||||||
import TelegramPresentationData
|
import TelegramPresentationData
|
||||||
import TelegramUIPreferences
|
import TelegramUIPreferences
|
||||||
import AccountContext
|
import AccountContext
|
||||||
|
import ContextUI
|
||||||
|
|
||||||
final class InstantPageArticleItem: InstantPageItem {
|
final class InstantPageArticleItem: InstantPageItem {
|
||||||
var frame: CGRect
|
var frame: CGRect
|
||||||
@ -35,7 +36,7 @@ final class InstantPageArticleItem: InstantPageItem {
|
|||||||
self.hasRTL = hasRTL
|
self.hasRTL = hasRTL
|
||||||
}
|
}
|
||||||
|
|
||||||
func node(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, theme: InstantPageTheme, sourcePeerType: MediaAutoDownloadPeerType, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> (InstantPageNode & ASDisplayNode)? {
|
func node(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, theme: InstantPageTheme, sourcePeerType: MediaAutoDownloadPeerType, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, activatePinchPreview: ((PinchSourceContainerNode) -> Void)?, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> (InstantPageNode & ASDisplayNode)? {
|
||||||
return InstantPageArticleNode(context: context, item: self, webPage: self.webPage, strings: strings, theme: theme, contentItems: self.contentItems, contentSize: self.contentSize, cover: self.cover, url: self.url, webpageId: self.webpageId, openUrl: openUrl)
|
return InstantPageArticleNode(context: context, item: self, webPage: self.webPage, strings: strings, theme: theme, contentItems: self.contentItems, contentSize: self.contentSize, cover: self.cover, url: self.url, webpageId: self.webpageId, openUrl: openUrl)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import AsyncDisplayKit
|
|||||||
import TelegramPresentationData
|
import TelegramPresentationData
|
||||||
import TelegramUIPreferences
|
import TelegramUIPreferences
|
||||||
import AccountContext
|
import AccountContext
|
||||||
|
import ContextUI
|
||||||
|
|
||||||
final class InstantPageAudioItem: InstantPageItem {
|
final class InstantPageAudioItem: InstantPageItem {
|
||||||
var frame: CGRect
|
var frame: CGRect
|
||||||
@ -24,7 +25,7 @@ final class InstantPageAudioItem: InstantPageItem {
|
|||||||
self.medias = [media]
|
self.medias = [media]
|
||||||
}
|
}
|
||||||
|
|
||||||
func node(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, theme: InstantPageTheme, sourcePeerType: MediaAutoDownloadPeerType, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> (InstantPageNode & ASDisplayNode)? {
|
func node(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, theme: InstantPageTheme, sourcePeerType: MediaAutoDownloadPeerType, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, activatePinchPreview: ((PinchSourceContainerNode) -> Void)?, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> (InstantPageNode & ASDisplayNode)? {
|
||||||
return InstantPageAudioNode(context: context, strings: strings, theme: theme, webPage: self.webpage, media: self.media, openMedia: openMedia)
|
return InstantPageAudioNode(context: context, strings: strings, theme: theme, webPage: self.webpage, media: self.media, openMedia: openMedia)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -193,7 +193,9 @@ final class InstantPageContentNode : ASDisplayNode {
|
|||||||
self?.openMedia(media)
|
self?.openMedia(media)
|
||||||
}, longPressMedia: { [weak self] media in
|
}, longPressMedia: { [weak self] media in
|
||||||
self?.longPressMedia(media)
|
self?.longPressMedia(media)
|
||||||
}, openPeer: { [weak self] peerId in
|
},
|
||||||
|
activatePinchPreview: nil,
|
||||||
|
openPeer: { [weak self] peerId in
|
||||||
self?.openPeer(peerId)
|
self?.openPeer(peerId)
|
||||||
}, openUrl: { [weak self] url in
|
}, openUrl: { [weak self] url in
|
||||||
self?.openUrl(url)
|
self?.openUrl(url)
|
||||||
|
|||||||
@ -146,7 +146,7 @@ public final class InstantPageController: ViewController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override public func loadDisplayNode() {
|
override public func loadDisplayNode() {
|
||||||
self.displayNode = InstantPageControllerNode(context: self.context, settings: self.settings, themeSettings: self.themeSettings, presentationTheme: self.presentationData.theme, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameDisplayOrder: self.presentationData.nameDisplayOrder, autoNightModeTriggered: self.presentationData.autoNightModeTriggered, statusBar: self.statusBar, sourcePeerType: self.sourcePeerType, getNavigationController: { [weak self] in
|
self.displayNode = InstantPageControllerNode(controller: self, context: self.context, settings: self.settings, themeSettings: self.themeSettings, presentationTheme: self.presentationData.theme, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameDisplayOrder: self.presentationData.nameDisplayOrder, autoNightModeTriggered: self.presentationData.autoNightModeTriggered, statusBar: self.statusBar, sourcePeerType: self.sourcePeerType, getNavigationController: { [weak self] in
|
||||||
return self?.navigationController as? NavigationController
|
return self?.navigationController as? NavigationController
|
||||||
}, present: { [weak self] c, a in
|
}, present: { [weak self] c, a in
|
||||||
self?.present(c, in: .window(.root), with: a, blockInteraction: true)
|
self?.present(c, in: .window(.root), with: a, blockInteraction: true)
|
||||||
|
|||||||
@ -16,8 +16,10 @@ import GalleryUI
|
|||||||
import OpenInExternalAppUI
|
import OpenInExternalAppUI
|
||||||
import LocationUI
|
import LocationUI
|
||||||
import UndoUI
|
import UndoUI
|
||||||
|
import ContextUI
|
||||||
|
|
||||||
final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||||
|
private weak var controller: InstantPageController?
|
||||||
private let context: AccountContext
|
private let context: AccountContext
|
||||||
private var settings: InstantPagePresentationSettings?
|
private var settings: InstantPagePresentationSettings?
|
||||||
private var themeSettings: PresentationThemeSettings?
|
private var themeSettings: PresentationThemeSettings?
|
||||||
@ -89,7 +91,8 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
return InstantPageStoredState(contentOffset: Double(self.scrollNode.view.contentOffset.y), details: details)
|
return InstantPageStoredState(contentOffset: Double(self.scrollNode.view.contentOffset.y), details: details)
|
||||||
}
|
}
|
||||||
|
|
||||||
init(context: AccountContext, settings: InstantPagePresentationSettings?, themeSettings: PresentationThemeSettings?, presentationTheme: PresentationTheme, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, autoNightModeTriggered: Bool, statusBar: StatusBar, sourcePeerType: MediaAutoDownloadPeerType, getNavigationController: @escaping () -> NavigationController?, present: @escaping (ViewController, Any?) -> Void, pushController: @escaping (ViewController) -> Void, openPeer: @escaping (PeerId) -> Void, navigateBack: @escaping () -> Void) {
|
init(controller: InstantPageController, context: AccountContext, settings: InstantPagePresentationSettings?, themeSettings: PresentationThemeSettings?, presentationTheme: PresentationTheme, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, autoNightModeTriggered: Bool, statusBar: StatusBar, sourcePeerType: MediaAutoDownloadPeerType, getNavigationController: @escaping () -> NavigationController?, present: @escaping (ViewController, Any?) -> Void, pushController: @escaping (ViewController) -> Void, openPeer: @escaping (PeerId) -> Void, navigateBack: @escaping () -> Void) {
|
||||||
|
self.controller = controller
|
||||||
self.context = context
|
self.context = context
|
||||||
self.presentationTheme = presentationTheme
|
self.presentationTheme = presentationTheme
|
||||||
self.dateTimeFormat = dateTimeFormat
|
self.dateTimeFormat = dateTimeFormat
|
||||||
@ -556,6 +559,12 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
self?.openMedia(media)
|
self?.openMedia(media)
|
||||||
}, longPressMedia: { [weak self] media in
|
}, longPressMedia: { [weak self] media in
|
||||||
self?.longPressMedia(media)
|
self?.longPressMedia(media)
|
||||||
|
}, activatePinchPreview: { [weak self] sourceNode in
|
||||||
|
guard let strongSelf = self, let controller = strongSelf.controller else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let pinchController = PinchController(sourceNode: sourceNode)
|
||||||
|
controller.window?.presentInGlobalOverlay(pinchController)
|
||||||
}, openPeer: { [weak self] peerId in
|
}, openPeer: { [weak self] peerId in
|
||||||
self?.openPeer(peerId)
|
self?.openPeer(peerId)
|
||||||
}, openUrl: { [weak self] url in
|
}, openUrl: { [weak self] url in
|
||||||
|
|||||||
@ -8,6 +8,7 @@ import Display
|
|||||||
import TelegramPresentationData
|
import TelegramPresentationData
|
||||||
import TelegramUIPreferences
|
import TelegramUIPreferences
|
||||||
import AccountContext
|
import AccountContext
|
||||||
|
import ContextUI
|
||||||
|
|
||||||
final class InstantPageDetailsItem: InstantPageItem {
|
final class InstantPageDetailsItem: InstantPageItem {
|
||||||
var frame: CGRect
|
var frame: CGRect
|
||||||
@ -40,7 +41,7 @@ final class InstantPageDetailsItem: InstantPageItem {
|
|||||||
self.index = index
|
self.index = index
|
||||||
}
|
}
|
||||||
|
|
||||||
func node(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, theme: InstantPageTheme, sourcePeerType: MediaAutoDownloadPeerType, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> (InstantPageNode & ASDisplayNode)? {
|
func node(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, theme: InstantPageTheme, sourcePeerType: MediaAutoDownloadPeerType, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, activatePinchPreview: ((PinchSourceContainerNode) -> Void)?, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> (InstantPageNode & ASDisplayNode)? {
|
||||||
var expanded: Bool?
|
var expanded: Bool?
|
||||||
if let expandedDetails = currentExpandedDetails, let currentlyExpanded = expandedDetails[self.index] {
|
if let expandedDetails = currentExpandedDetails, let currentlyExpanded = expandedDetails[self.index] {
|
||||||
expanded = currentlyExpanded
|
expanded = currentlyExpanded
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import AsyncDisplayKit
|
|||||||
import TelegramPresentationData
|
import TelegramPresentationData
|
||||||
import TelegramUIPreferences
|
import TelegramUIPreferences
|
||||||
import AccountContext
|
import AccountContext
|
||||||
|
import ContextUI
|
||||||
|
|
||||||
final class InstantPageFeedbackItem: InstantPageItem {
|
final class InstantPageFeedbackItem: InstantPageItem {
|
||||||
var frame: CGRect
|
var frame: CGRect
|
||||||
@ -21,7 +22,7 @@ final class InstantPageFeedbackItem: InstantPageItem {
|
|||||||
self.webPage = webPage
|
self.webPage = webPage
|
||||||
}
|
}
|
||||||
|
|
||||||
func node(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, theme: InstantPageTheme, sourcePeerType: MediaAutoDownloadPeerType, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> (InstantPageNode & ASDisplayNode)? {
|
func node(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, theme: InstantPageTheme, sourcePeerType: MediaAutoDownloadPeerType, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, activatePinchPreview: ((PinchSourceContainerNode) -> Void)?, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> (InstantPageNode & ASDisplayNode)? {
|
||||||
return InstantPageFeedbackNode(context: context, strings: strings, theme: theme, webPage: self.webPage, openUrl: openUrl)
|
return InstantPageFeedbackNode(context: context, strings: strings, theme: theme, webPage: self.webPage, openUrl: openUrl)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import AsyncDisplayKit
|
|||||||
import TelegramPresentationData
|
import TelegramPresentationData
|
||||||
import TelegramUIPreferences
|
import TelegramUIPreferences
|
||||||
import AccountContext
|
import AccountContext
|
||||||
|
import ContextUI
|
||||||
|
|
||||||
protocol InstantPageImageAttribute {
|
protocol InstantPageImageAttribute {
|
||||||
}
|
}
|
||||||
@ -45,8 +46,8 @@ final class InstantPageImageItem: InstantPageItem {
|
|||||||
self.fit = fit
|
self.fit = fit
|
||||||
}
|
}
|
||||||
|
|
||||||
func node(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, theme: InstantPageTheme, sourcePeerType: MediaAutoDownloadPeerType, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> (InstantPageNode & ASDisplayNode)? {
|
func node(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, theme: InstantPageTheme, sourcePeerType: MediaAutoDownloadPeerType, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, activatePinchPreview: ((PinchSourceContainerNode) -> Void)?, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> (InstantPageNode & ASDisplayNode)? {
|
||||||
return InstantPageImageNode(context: context, sourcePeerType: sourcePeerType, theme: theme, webPage: self.webPage, media: self.media, attributes: self.attributes, interactive: self.interactive, roundCorners: self.roundCorners, fit: self.fit, openMedia: openMedia, longPressMedia: longPressMedia)
|
return InstantPageImageNode(context: context, sourcePeerType: sourcePeerType, theme: theme, webPage: self.webPage, media: self.media, attributes: self.attributes, interactive: self.interactive, roundCorners: self.roundCorners, fit: self.fit, openMedia: openMedia, longPressMedia: longPressMedia, activatePinchPreview: activatePinchPreview)
|
||||||
}
|
}
|
||||||
|
|
||||||
func matchesAnchor(_ anchor: String) -> Bool {
|
func matchesAnchor(_ anchor: String) -> Bool {
|
||||||
|
|||||||
@ -15,6 +15,7 @@ import LocationResources
|
|||||||
import LiveLocationPositionNode
|
import LiveLocationPositionNode
|
||||||
import AppBundle
|
import AppBundle
|
||||||
import TelegramUIPreferences
|
import TelegramUIPreferences
|
||||||
|
import ContextUI
|
||||||
|
|
||||||
private struct FetchControls {
|
private struct FetchControls {
|
||||||
let fetch: (Bool) -> Void
|
let fetch: (Bool) -> Void
|
||||||
@ -35,6 +36,7 @@ final class InstantPageImageNode: ASDisplayNode, InstantPageNode {
|
|||||||
|
|
||||||
private var fetchControls: FetchControls?
|
private var fetchControls: FetchControls?
|
||||||
|
|
||||||
|
private let pinchContainerNode: PinchSourceContainerNode
|
||||||
private let imageNode: TransformImageNode
|
private let imageNode: TransformImageNode
|
||||||
private let statusNode: RadialStatusNode
|
private let statusNode: RadialStatusNode
|
||||||
private let linkIconNode: ASImageNode
|
private let linkIconNode: ASImageNode
|
||||||
@ -48,7 +50,7 @@ final class InstantPageImageNode: ASDisplayNode, InstantPageNode {
|
|||||||
|
|
||||||
private var themeUpdated: Bool = false
|
private var themeUpdated: Bool = false
|
||||||
|
|
||||||
init(context: AccountContext, sourcePeerType: MediaAutoDownloadPeerType, theme: InstantPageTheme, webPage: TelegramMediaWebpage, media: InstantPageMedia, attributes: [InstantPageImageAttribute], interactive: Bool, roundCorners: Bool, fit: Bool, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void) {
|
init(context: AccountContext, sourcePeerType: MediaAutoDownloadPeerType, theme: InstantPageTheme, webPage: TelegramMediaWebpage, media: InstantPageMedia, attributes: [InstantPageImageAttribute], interactive: Bool, roundCorners: Bool, fit: Bool, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, activatePinchPreview: ((PinchSourceContainerNode) -> Void)?) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.theme = theme
|
self.theme = theme
|
||||||
self.webPage = webPage
|
self.webPage = webPage
|
||||||
@ -60,6 +62,7 @@ final class InstantPageImageNode: ASDisplayNode, InstantPageNode {
|
|||||||
self.openMedia = openMedia
|
self.openMedia = openMedia
|
||||||
self.longPressMedia = longPressMedia
|
self.longPressMedia = longPressMedia
|
||||||
|
|
||||||
|
self.pinchContainerNode = PinchSourceContainerNode()
|
||||||
self.imageNode = TransformImageNode()
|
self.imageNode = TransformImageNode()
|
||||||
self.statusNode = RadialStatusNode(backgroundNodeColor: UIColor(white: 0.0, alpha: 0.6))
|
self.statusNode = RadialStatusNode(backgroundNodeColor: UIColor(white: 0.0, alpha: 0.6))
|
||||||
self.linkIconNode = ASImageNode()
|
self.linkIconNode = ASImageNode()
|
||||||
@ -67,7 +70,8 @@ final class InstantPageImageNode: ASDisplayNode, InstantPageNode {
|
|||||||
|
|
||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
self.addSubnode(self.imageNode)
|
self.pinchContainerNode.contentNode.addSubnode(self.imageNode)
|
||||||
|
self.addSubnode(self.pinchContainerNode)
|
||||||
|
|
||||||
if let image = media.media as? TelegramMediaImage, let largest = largestImageRepresentation(image.representations) {
|
if let image = media.media as? TelegramMediaImage, let largest = largestImageRepresentation(image.representations) {
|
||||||
let imageReference = ImageMediaReference.webPage(webPage: WebpageReference(webPage), media: image)
|
let imageReference = ImageMediaReference.webPage(webPage: WebpageReference(webPage), media: image)
|
||||||
@ -97,10 +101,10 @@ final class InstantPageImageNode: ASDisplayNode, InstantPageNode {
|
|||||||
|
|
||||||
if media.url != nil {
|
if media.url != nil {
|
||||||
self.linkIconNode.image = UIImage(bundleImageName: "Instant View/ImageLink")
|
self.linkIconNode.image = UIImage(bundleImageName: "Instant View/ImageLink")
|
||||||
self.addSubnode(self.linkIconNode)
|
self.pinchContainerNode.contentNode.addSubnode(self.linkIconNode)
|
||||||
}
|
}
|
||||||
|
|
||||||
self.addSubnode(self.statusNode)
|
self.pinchContainerNode.contentNode.addSubnode(self.statusNode)
|
||||||
}
|
}
|
||||||
} else if let file = media.media as? TelegramMediaFile {
|
} else if let file = media.media as? TelegramMediaFile {
|
||||||
let fileReference = FileMediaReference.webPage(webPage: WebpageReference(webPage), media: file)
|
let fileReference = FileMediaReference.webPage(webPage: WebpageReference(webPage), media: file)
|
||||||
@ -114,16 +118,14 @@ final class InstantPageImageNode: ASDisplayNode, InstantPageNode {
|
|||||||
}
|
}
|
||||||
if file.isVideo {
|
if file.isVideo {
|
||||||
self.statusNode.transitionToState(.play(.white), animated: false, completion: {})
|
self.statusNode.transitionToState(.play(.white), animated: false, completion: {})
|
||||||
self.addSubnode(self.statusNode)
|
self.pinchContainerNode.contentNode.addSubnode(self.statusNode)
|
||||||
}
|
}
|
||||||
} else if let map = media.media as? TelegramMediaMap {
|
} else if let map = media.media as? TelegramMediaMap {
|
||||||
self.addSubnode(self.pinNode)
|
self.addSubnode(self.pinNode)
|
||||||
|
|
||||||
var zoom: Int32 = 12
|
|
||||||
var dimensions = CGSize(width: 200.0, height: 100.0)
|
var dimensions = CGSize(width: 200.0, height: 100.0)
|
||||||
for attribute in self.attributes {
|
for attribute in self.attributes {
|
||||||
if let mapAttribute = attribute as? InstantPageMapAttribute {
|
if let mapAttribute = attribute as? InstantPageMapAttribute {
|
||||||
zoom = mapAttribute.zoom
|
|
||||||
dimensions = mapAttribute.dimensions
|
dimensions = mapAttribute.dimensions
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@ -135,7 +137,13 @@ final class InstantPageImageNode: ASDisplayNode, InstantPageNode {
|
|||||||
self.imageNode.setSignal(chatMessagePhoto(postbox: context.account.postbox, photoReference: imageReference))
|
self.imageNode.setSignal(chatMessagePhoto(postbox: context.account.postbox, photoReference: imageReference))
|
||||||
self.fetchedDisposable.set(chatMessagePhotoInteractiveFetched(context: context, photoReference: imageReference, displayAtSize: nil, storeToDownloadsPeerType: nil).start())
|
self.fetchedDisposable.set(chatMessagePhotoInteractiveFetched(context: context, photoReference: imageReference, displayAtSize: nil, storeToDownloadsPeerType: nil).start())
|
||||||
self.statusNode.transitionToState(.play(.white), animated: false, completion: {})
|
self.statusNode.transitionToState(.play(.white), animated: false, completion: {})
|
||||||
self.addSubnode(self.statusNode)
|
self.pinchContainerNode.contentNode.addSubnode(self.statusNode)
|
||||||
|
}
|
||||||
|
|
||||||
|
if let activatePinchPreview = activatePinchPreview {
|
||||||
|
self.pinchContainerNode.activate = { sourceNode in
|
||||||
|
activatePinchPreview(sourceNode)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -199,6 +207,8 @@ final class InstantPageImageNode: ASDisplayNode, InstantPageNode {
|
|||||||
self.currentSize = size
|
self.currentSize = size
|
||||||
self.themeUpdated = false
|
self.themeUpdated = false
|
||||||
|
|
||||||
|
self.pinchContainerNode.frame = CGRect(origin: CGPoint(), size: size)
|
||||||
|
self.pinchContainerNode.update(size: size, transition: .immediate)
|
||||||
self.imageNode.frame = CGRect(origin: CGPoint(), size: size)
|
self.imageNode.frame = CGRect(origin: CGPoint(), size: size)
|
||||||
|
|
||||||
let radialStatusSize: CGFloat = 50.0
|
let radialStatusSize: CGFloat = 50.0
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import AsyncDisplayKit
|
|||||||
import TelegramPresentationData
|
import TelegramPresentationData
|
||||||
import TelegramUIPreferences
|
import TelegramUIPreferences
|
||||||
import AccountContext
|
import AccountContext
|
||||||
|
import ContextUI
|
||||||
|
|
||||||
protocol InstantPageItem {
|
protocol InstantPageItem {
|
||||||
var frame: CGRect { get set }
|
var frame: CGRect { get set }
|
||||||
@ -16,7 +17,7 @@ protocol InstantPageItem {
|
|||||||
|
|
||||||
func matchesAnchor(_ anchor: String) -> Bool
|
func matchesAnchor(_ anchor: String) -> Bool
|
||||||
func drawInTile(context: CGContext)
|
func drawInTile(context: CGContext)
|
||||||
func node(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, theme: InstantPageTheme, sourcePeerType: MediaAutoDownloadPeerType, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> (InstantPageNode & ASDisplayNode)?
|
func node(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, theme: InstantPageTheme, sourcePeerType: MediaAutoDownloadPeerType, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, activatePinchPreview: ((PinchSourceContainerNode) -> Void)?, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> (InstantPageNode & ASDisplayNode)?
|
||||||
func matchesNode(_ node: InstantPageNode) -> Bool
|
func matchesNode(_ node: InstantPageNode) -> Bool
|
||||||
func linkSelectionRects(at point: CGPoint) -> [CGRect]
|
func linkSelectionRects(at point: CGPoint) -> [CGRect]
|
||||||
|
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import AsyncDisplayKit
|
|||||||
import TelegramPresentationData
|
import TelegramPresentationData
|
||||||
import TelegramUIPreferences
|
import TelegramUIPreferences
|
||||||
import AccountContext
|
import AccountContext
|
||||||
|
import ContextUI
|
||||||
|
|
||||||
final class InstantPagePeerReferenceItem: InstantPageItem {
|
final class InstantPagePeerReferenceItem: InstantPageItem {
|
||||||
var frame: CGRect
|
var frame: CGRect
|
||||||
@ -27,7 +28,7 @@ final class InstantPagePeerReferenceItem: InstantPageItem {
|
|||||||
self.rtl = rtl
|
self.rtl = rtl
|
||||||
}
|
}
|
||||||
|
|
||||||
func node(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, theme: InstantPageTheme, sourcePeerType: MediaAutoDownloadPeerType, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> (InstantPageNode & ASDisplayNode)? {
|
func node(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, theme: InstantPageTheme, sourcePeerType: MediaAutoDownloadPeerType, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, activatePinchPreview: ((PinchSourceContainerNode) -> Void)?, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> (InstantPageNode & ASDisplayNode)? {
|
||||||
return InstantPagePeerReferenceNode(context: context, strings: strings, nameDisplayOrder: nameDisplayOrder, theme: theme, initialPeer: self.initialPeer, safeInset: self.safeInset, transparent: self.transparent, rtl: self.rtl, openPeer: openPeer)
|
return InstantPagePeerReferenceNode(context: context, strings: strings, nameDisplayOrder: nameDisplayOrder, theme: theme, initialPeer: self.initialPeer, safeInset: self.safeInset, transparent: self.transparent, rtl: self.rtl, openPeer: openPeer)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import AsyncDisplayKit
|
|||||||
import TelegramPresentationData
|
import TelegramPresentationData
|
||||||
import TelegramUIPreferences
|
import TelegramUIPreferences
|
||||||
import AccountContext
|
import AccountContext
|
||||||
|
import ContextUI
|
||||||
|
|
||||||
final class InstantPagePlayableVideoItem: InstantPageItem {
|
final class InstantPagePlayableVideoItem: InstantPageItem {
|
||||||
var frame: CGRect
|
var frame: CGRect
|
||||||
@ -29,7 +30,7 @@ final class InstantPagePlayableVideoItem: InstantPageItem {
|
|||||||
self.interactive = interactive
|
self.interactive = interactive
|
||||||
}
|
}
|
||||||
|
|
||||||
func node(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, theme: InstantPageTheme, sourcePeerType: MediaAutoDownloadPeerType, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> (InstantPageNode & ASDisplayNode)? {
|
func node(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, theme: InstantPageTheme, sourcePeerType: MediaAutoDownloadPeerType, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, activatePinchPreview: ((PinchSourceContainerNode) -> Void)?, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> (InstantPageNode & ASDisplayNode)? {
|
||||||
return InstantPagePlayableVideoNode(context: context, webPage: self.webPage, theme: theme, media: self.media, interactive: self.interactive, openMedia: openMedia)
|
return InstantPagePlayableVideoNode(context: context, webPage: self.webPage, theme: theme, media: self.media, interactive: self.interactive, openMedia: openMedia)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import AsyncDisplayKit
|
|||||||
import TelegramPresentationData
|
import TelegramPresentationData
|
||||||
import TelegramUIPreferences
|
import TelegramUIPreferences
|
||||||
import AccountContext
|
import AccountContext
|
||||||
|
import ContextUI
|
||||||
|
|
||||||
enum InstantPageShape {
|
enum InstantPageShape {
|
||||||
case rect
|
case rect
|
||||||
@ -62,7 +63,7 @@ final class InstantPageShapeItem: InstantPageItem {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func node(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, theme: InstantPageTheme, sourcePeerType: MediaAutoDownloadPeerType, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> (InstantPageNode & ASDisplayNode)? {
|
func node(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, theme: InstantPageTheme, sourcePeerType: MediaAutoDownloadPeerType, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, activatePinchPreview: ((PinchSourceContainerNode) -> Void)?, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> (InstantPageNode & ASDisplayNode)? {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import AsyncDisplayKit
|
|||||||
import TelegramPresentationData
|
import TelegramPresentationData
|
||||||
import TelegramUIPreferences
|
import TelegramUIPreferences
|
||||||
import AccountContext
|
import AccountContext
|
||||||
|
import ContextUI
|
||||||
|
|
||||||
final class InstantPageSlideshowItem: InstantPageItem {
|
final class InstantPageSlideshowItem: InstantPageItem {
|
||||||
var frame: CGRect
|
var frame: CGRect
|
||||||
@ -21,7 +22,7 @@ final class InstantPageSlideshowItem: InstantPageItem {
|
|||||||
self.medias = medias
|
self.medias = medias
|
||||||
}
|
}
|
||||||
|
|
||||||
func node(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, theme: InstantPageTheme, sourcePeerType: MediaAutoDownloadPeerType, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> (InstantPageNode & ASDisplayNode)? {
|
func node(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, theme: InstantPageTheme, sourcePeerType: MediaAutoDownloadPeerType, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, activatePinchPreview: ((PinchSourceContainerNode) -> Void)?, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> (InstantPageNode & ASDisplayNode)? {
|
||||||
return InstantPageSlideshowNode(context: context, sourcePeerType: sourcePeerType, theme: theme, webPage: webPage, medias: self.medias, openMedia: openMedia, longPressMedia: longPressMedia)
|
return InstantPageSlideshowNode(context: context, sourcePeerType: sourcePeerType, theme: theme, webPage: webPage, medias: self.medias, openMedia: openMedia, longPressMedia: longPressMedia)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -183,7 +183,7 @@ private final class InstantPageSlideshowPagerNode: ASDisplayNode, UIScrollViewDe
|
|||||||
let media = self.items[index]
|
let media = self.items[index]
|
||||||
let contentNode: ASDisplayNode
|
let contentNode: ASDisplayNode
|
||||||
if let _ = media.media as? TelegramMediaImage {
|
if let _ = media.media as? TelegramMediaImage {
|
||||||
contentNode = InstantPageImageNode(context: self.context, sourcePeerType: self.sourcePeerType, theme: self.theme, webPage: self.webPage, media: media, attributes: [], interactive: true, roundCorners: false, fit: false, openMedia: self.openMedia, longPressMedia: self.longPressMedia)
|
contentNode = InstantPageImageNode(context: self.context, sourcePeerType: self.sourcePeerType, theme: self.theme, webPage: self.webPage, media: media, attributes: [], interactive: true, roundCorners: false, fit: false, openMedia: self.openMedia, longPressMedia: self.longPressMedia, activatePinchPreview: nil)
|
||||||
} else if let file = media.media as? TelegramMediaFile {
|
} else if let file = media.media as? TelegramMediaFile {
|
||||||
contentNode = ASDisplayNode()
|
contentNode = ASDisplayNode()
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -8,6 +8,7 @@ import Display
|
|||||||
import TelegramPresentationData
|
import TelegramPresentationData
|
||||||
import TelegramUIPreferences
|
import TelegramUIPreferences
|
||||||
import AccountContext
|
import AccountContext
|
||||||
|
import ContextUI
|
||||||
|
|
||||||
private struct TableSide: OptionSet {
|
private struct TableSide: OptionSet {
|
||||||
var rawValue: Int32 = 0
|
var rawValue: Int32 = 0
|
||||||
@ -200,12 +201,12 @@ final class InstantPageTableItem: InstantPageScrollableItem {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func node(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, theme: InstantPageTheme, sourcePeerType: MediaAutoDownloadPeerType, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> (InstantPageNode & ASDisplayNode)? {
|
func node(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, theme: InstantPageTheme, sourcePeerType: MediaAutoDownloadPeerType, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, activatePinchPreview: ((PinchSourceContainerNode) -> Void)?, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> (InstantPageNode & ASDisplayNode)? {
|
||||||
var additionalNodes: [InstantPageNode] = []
|
var additionalNodes: [InstantPageNode] = []
|
||||||
for cell in self.cells {
|
for cell in self.cells {
|
||||||
for item in cell.additionalItems {
|
for item in cell.additionalItems {
|
||||||
if item.wantsNode {
|
if item.wantsNode {
|
||||||
if let node = item.node(context: context, strings: strings, nameDisplayOrder: nameDisplayOrder, theme: theme, sourcePeerType: sourcePeerType, openMedia: { _ in }, longPressMedia: { _ in }, openPeer: { _ in }, openUrl: { _ in}, updateWebEmbedHeight: { _ in }, updateDetailsExpanded: { _ in }, currentExpandedDetails: nil) {
|
if let node = item.node(context: context, strings: strings, nameDisplayOrder: nameDisplayOrder, theme: theme, sourcePeerType: sourcePeerType, openMedia: { _ in }, longPressMedia: { _ in }, activatePinchPreview: nil, openPeer: { _ in }, openUrl: { _ in}, updateWebEmbedHeight: { _ in }, updateDetailsExpanded: { _ in }, currentExpandedDetails: nil) {
|
||||||
node.frame = item.frame.offsetBy(dx: cell.frame.minX, dy: cell.frame.minY)
|
node.frame = item.frame.offsetBy(dx: cell.frame.minX, dy: cell.frame.minY)
|
||||||
additionalNodes.append(node)
|
additionalNodes.append(node)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,6 +9,7 @@ import TelegramPresentationData
|
|||||||
import TelegramUIPreferences
|
import TelegramUIPreferences
|
||||||
import TextFormat
|
import TextFormat
|
||||||
import AccountContext
|
import AccountContext
|
||||||
|
import ContextUI
|
||||||
|
|
||||||
public final class InstantPageUrlItem: Equatable {
|
public final class InstantPageUrlItem: Equatable {
|
||||||
public let url: String
|
public let url: String
|
||||||
@ -436,7 +437,7 @@ final class InstantPageTextItem: InstantPageItem {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func node(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, theme: InstantPageTheme, sourcePeerType: MediaAutoDownloadPeerType, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> (InstantPageNode & ASDisplayNode)? {
|
func node(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, theme: InstantPageTheme, sourcePeerType: MediaAutoDownloadPeerType, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, activatePinchPreview: ((PinchSourceContainerNode) -> Void)?, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> (InstantPageNode & ASDisplayNode)? {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -485,11 +486,11 @@ final class InstantPageScrollableTextItem: InstantPageScrollableItem {
|
|||||||
context.restoreGState()
|
context.restoreGState()
|
||||||
}
|
}
|
||||||
|
|
||||||
func node(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, theme: InstantPageTheme, sourcePeerType: MediaAutoDownloadPeerType, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> (InstantPageNode & ASDisplayNode)? {
|
func node(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, theme: InstantPageTheme, sourcePeerType: MediaAutoDownloadPeerType, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, activatePinchPreview: ((PinchSourceContainerNode) -> Void)?, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> (InstantPageNode & ASDisplayNode)? {
|
||||||
var additionalNodes: [InstantPageNode] = []
|
var additionalNodes: [InstantPageNode] = []
|
||||||
for item in additionalItems {
|
for item in additionalItems {
|
||||||
if item.wantsNode {
|
if item.wantsNode {
|
||||||
if let node = item.node(context: context, strings: strings, nameDisplayOrder: nameDisplayOrder, theme: theme, sourcePeerType: sourcePeerType, openMedia: { _ in }, longPressMedia: { _ in }, openPeer: { _ in }, openUrl: { _ in}, updateWebEmbedHeight: { _ in }, updateDetailsExpanded: { _ in }, currentExpandedDetails: nil) {
|
if let node = item.node(context: context, strings: strings, nameDisplayOrder: nameDisplayOrder, theme: theme, sourcePeerType: sourcePeerType, openMedia: { _ in }, longPressMedia: { _ in }, activatePinchPreview: nil, openPeer: { _ in }, openUrl: { _ in}, updateWebEmbedHeight: { _ in }, updateDetailsExpanded: { _ in }, currentExpandedDetails: nil) {
|
||||||
node.frame = item.frame
|
node.frame = item.frame
|
||||||
additionalNodes.append(node)
|
additionalNodes.append(node)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import AsyncDisplayKit
|
|||||||
import TelegramPresentationData
|
import TelegramPresentationData
|
||||||
import TelegramUIPreferences
|
import TelegramUIPreferences
|
||||||
import AccountContext
|
import AccountContext
|
||||||
|
import ContextUI
|
||||||
|
|
||||||
final class InstantPageWebEmbedItem: InstantPageItem {
|
final class InstantPageWebEmbedItem: InstantPageItem {
|
||||||
var frame: CGRect
|
var frame: CGRect
|
||||||
@ -25,7 +26,7 @@ final class InstantPageWebEmbedItem: InstantPageItem {
|
|||||||
self.enableScrolling = enableScrolling
|
self.enableScrolling = enableScrolling
|
||||||
}
|
}
|
||||||
|
|
||||||
func node(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, theme: InstantPageTheme, sourcePeerType: MediaAutoDownloadPeerType, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> (InstantPageNode & ASDisplayNode)? {
|
func node(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, theme: InstantPageTheme, sourcePeerType: MediaAutoDownloadPeerType, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, activatePinchPreview: ((PinchSourceContainerNode) -> Void)?, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> (InstantPageNode & ASDisplayNode)? {
|
||||||
return InstantPageWebEmbedNode(frame: self.frame, url: self.url, html: self.html, enableScrolling: self.enableScrolling, updateWebEmbedHeight: updateWebEmbedHeight)
|
return InstantPageWebEmbedNode(frame: self.frame, url: self.url, html: self.html, enableScrolling: self.enableScrolling, updateWebEmbedHeight: updateWebEmbedHeight)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -565,7 +565,7 @@ public func rawMessagePhoto(postbox: Postbox, photoReference: ImageMediaReferenc
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func chatMessagePhoto(postbox: Postbox, photoReference: ImageMediaReference, synchronousLoad: Bool = false) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> {
|
public func chatMessagePhoto(postbox: Postbox, photoReference: ImageMediaReference, synchronousLoad: Bool = false, highQuality: Bool = false) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> {
|
||||||
return chatMessagePhotoInternal(photoData: chatMessagePhotoDatas(postbox: postbox, photoReference: photoReference, tryAdditionalRepresentations: true, synchronousLoad: synchronousLoad), synchronousLoad: synchronousLoad)
|
return chatMessagePhotoInternal(photoData: chatMessagePhotoDatas(postbox: postbox, photoReference: photoReference, tryAdditionalRepresentations: true, synchronousLoad: synchronousLoad), synchronousLoad: synchronousLoad)
|
||||||
|> map { _, _, generate in
|
|> map { _, _, generate in
|
||||||
return generate
|
return generate
|
||||||
@ -684,7 +684,7 @@ public func chatMessagePhotoInternal(photoData: Signal<Tuple4<Data?, Data?, Chat
|
|||||||
return context
|
return context
|
||||||
}
|
}
|
||||||
|
|
||||||
let context = DrawingContext(size: arguments.drawingSize, clear: true)
|
let context = DrawingContext(size: arguments.drawingSize, scale: arguments.scale ?? 0.0, clear: true)
|
||||||
|
|
||||||
context.withFlippedContext { c in
|
context.withFlippedContext { c in
|
||||||
c.setBlendMode(.copy)
|
c.setBlendMode(.copy)
|
||||||
|
|||||||
@ -84,7 +84,7 @@ public func setupCurrencyNumberFormatter(currency: String) -> NumberFormatter {
|
|||||||
numberFormatter.positiveFormat = result
|
numberFormatter.positiveFormat = result
|
||||||
numberFormatter.negativeFormat = "-\(result)"
|
numberFormatter.negativeFormat = "-\(result)"
|
||||||
|
|
||||||
numberFormatter.currencySymbol = entry.symbol
|
numberFormatter.currencySymbol = ""
|
||||||
numberFormatter.currencyDecimalSeparator = entry.decimalSeparator
|
numberFormatter.currencyDecimalSeparator = entry.decimalSeparator
|
||||||
numberFormatter.currencyGroupingSeparator = entry.thousandsSeparator
|
numberFormatter.currencyGroupingSeparator = entry.thousandsSeparator
|
||||||
|
|
||||||
@ -164,3 +164,42 @@ public func formatCurrencyAmount(_ amount: Int64, currency: String) -> String {
|
|||||||
return formatter.string(from: (Float(amount) * 0.01) as NSNumber) ?? ""
|
return formatter.string(from: (Float(amount) * 0.01) as NSNumber) ?? ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func formatCurrencyAmountCustom(_ amount: Int64, currency: String) -> (String, String) {
|
||||||
|
if let entry = currencyFormatterEntries[currency] ?? currencyFormatterEntries["USD"] {
|
||||||
|
var result = ""
|
||||||
|
if amount < 0 {
|
||||||
|
result.append("-")
|
||||||
|
}
|
||||||
|
/*if entry.symbolOnLeft {
|
||||||
|
result.append(entry.symbol)
|
||||||
|
if entry.spaceBetweenAmountAndSymbol {
|
||||||
|
result.append(" ")
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
var integerPart = abs(amount)
|
||||||
|
var fractional: [Character] = []
|
||||||
|
for _ in 0 ..< entry.decimalDigits {
|
||||||
|
let part = integerPart % 10
|
||||||
|
integerPart /= 10
|
||||||
|
if let scalar = UnicodeScalar(UInt32(part + 48)) {
|
||||||
|
fractional.append(Character(scalar))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result.append("\(integerPart)")
|
||||||
|
result.append(entry.decimalSeparator)
|
||||||
|
for i in 0 ..< fractional.count {
|
||||||
|
result.append(fractional[fractional.count - i - 1])
|
||||||
|
}
|
||||||
|
/*if !entry.symbolOnLeft {
|
||||||
|
if entry.spaceBetweenAmountAndSymbol {
|
||||||
|
result.append(" ")
|
||||||
|
}
|
||||||
|
result.append(entry.symbol)
|
||||||
|
}*/
|
||||||
|
|
||||||
|
return (result, entry.symbol)
|
||||||
|
} else {
|
||||||
|
return ("", "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -912,6 +912,12 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
strongSelf.window?.presentInGlobalOverlay(controller)
|
strongSelf.window?.presentInGlobalOverlay(controller)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
}, activateMessagePinch: { [weak self] sourceNode in
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let pinchController = PinchController(sourceNode: sourceNode)
|
||||||
|
strongSelf.window?.presentInGlobalOverlay(pinchController)
|
||||||
}, openMessageContextActions: { message, node, rect, gesture in
|
}, openMessageContextActions: { message, node, rect, gesture in
|
||||||
gesture?.cancel()
|
gesture?.cancel()
|
||||||
}, navigateToMessage: { [weak self] fromId, id in
|
}, navigateToMessage: { [weak self] fromId, id in
|
||||||
|
|||||||
@ -53,6 +53,7 @@ public final class ChatControllerInteraction {
|
|||||||
let openPeer: (PeerId?, ChatControllerInteractionNavigateToPeer, Message?) -> Void
|
let openPeer: (PeerId?, ChatControllerInteractionNavigateToPeer, Message?) -> Void
|
||||||
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 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
|
||||||
let navigateToMessageStandalone: (MessageId) -> Void
|
let navigateToMessageStandalone: (MessageId) -> Void
|
||||||
@ -144,6 +145,7 @@ public final class ChatControllerInteraction {
|
|||||||
openPeer: @escaping (PeerId?, ChatControllerInteractionNavigateToPeer, Message?) -> Void,
|
openPeer: @escaping (PeerId?, ChatControllerInteractionNavigateToPeer, Message?) -> 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,
|
||||||
|
activateMessagePinch: @escaping (PinchSourceContainerNode) -> Void,
|
||||||
openMessageContextActions: @escaping (Message, ASDisplayNode, CGRect, ContextGesture?) -> Void,
|
openMessageContextActions: @escaping (Message, ASDisplayNode, CGRect, ContextGesture?) -> Void,
|
||||||
navigateToMessage: @escaping (MessageId, MessageId) -> Void,
|
navigateToMessage: @escaping (MessageId, MessageId) -> Void,
|
||||||
navigateToMessageStandalone: @escaping (MessageId) -> Void,
|
navigateToMessageStandalone: @escaping (MessageId) -> Void,
|
||||||
@ -222,6 +224,7 @@ public final class ChatControllerInteraction {
|
|||||||
self.openPeer = openPeer
|
self.openPeer = openPeer
|
||||||
self.openPeerMention = openPeerMention
|
self.openPeerMention = openPeerMention
|
||||||
self.openMessageContextMenu = openMessageContextMenu
|
self.openMessageContextMenu = openMessageContextMenu
|
||||||
|
self.activateMessagePinch = activateMessagePinch
|
||||||
self.openMessageContextActions = openMessageContextActions
|
self.openMessageContextActions = openMessageContextActions
|
||||||
self.navigateToMessage = navigateToMessage
|
self.navigateToMessage = navigateToMessage
|
||||||
self.navigateToMessageStandalone = navigateToMessageStandalone
|
self.navigateToMessageStandalone = navigateToMessageStandalone
|
||||||
@ -301,7 +304,7 @@ public final class ChatControllerInteraction {
|
|||||||
|
|
||||||
static var `default`: ChatControllerInteraction {
|
static var `default`: ChatControllerInteraction {
|
||||||
return ChatControllerInteraction(openMessage: { _, _ in
|
return ChatControllerInteraction(openMessage: { _, _ in
|
||||||
return false }, openPeer: { _, _, _ in }, openPeerMention: { _ in }, openMessageContextMenu: { _, _, _, _, _ in }, openMessageContextActions: { _, _, _, _ in }, navigateToMessage: { _, _ in }, navigateToMessageStandalone: { _ in }, tapMessage: nil, clickThroughMessage: { }, toggleMessagesSelection: { _, _ in }, sendCurrentMessage: { _ in }, sendMessage: { _ in }, sendSticker: { _, _, _, _, _ in return false }, sendGif: { _, _, _ in return false }, sendBotContextResultAsGif: { _, _, _, _ in return false }, requestMessageActionCallback: { _, _, _, _ in }, requestMessageActionUrlAuth: { _, _ in }, activateSwitchInline: { _, _ in }, openUrl: { _, _, _, _ in }, shareCurrentLocation: {}, shareAccountContact: {}, sendBotCommand: { _, _ in }, openInstantPage: { _, _ in }, openWallpaper: { _ in }, openTheme: { _ in }, openHashtag: { _, _ in }, updateInputState: { _ in }, updateInputMode: { _ in }, openMessageShareMenu: { _ in
|
return false }, openPeer: { _, _, _ in }, openPeerMention: { _ in }, openMessageContextMenu: { _, _, _, _, _ in }, activateMessagePinch: { _ in }, openMessageContextActions: { _, _, _, _ in }, navigateToMessage: { _, _ in }, navigateToMessageStandalone: { _ in }, tapMessage: nil, clickThroughMessage: { }, toggleMessagesSelection: { _, _ in }, sendCurrentMessage: { _ in }, sendMessage: { _ in }, sendSticker: { _, _, _, _, _ in return false }, sendGif: { _, _, _ in return false }, sendBotContextResultAsGif: { _, _, _, _ in return false }, requestMessageActionCallback: { _, _, _, _ in }, requestMessageActionUrlAuth: { _, _ in }, activateSwitchInline: { _, _ in }, openUrl: { _, _, _, _ in }, shareCurrentLocation: {}, shareAccountContact: {}, sendBotCommand: { _, _ in }, openInstantPage: { _, _ in }, openWallpaper: { _ in }, openTheme: { _ in }, openHashtag: { _, _ in }, updateInputState: { _ in }, updateInputMode: { _ in }, openMessageShareMenu: { _ in
|
||||||
}, presentController: { _, _ in }, navigationController: {
|
}, presentController: { _, _ in }, navigationController: {
|
||||||
return nil
|
return nil
|
||||||
}, chatControllerNode: {
|
}, chatControllerNode: {
|
||||||
|
|||||||
@ -143,6 +143,7 @@ class ChatMessageShareButton: HighlightableButtonNode {
|
|||||||
class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
||||||
private let contextSourceNode: ContextExtractedContentContainingNode
|
private let contextSourceNode: ContextExtractedContentContainingNode
|
||||||
private let containerNode: ContextControllerSourceNode
|
private let containerNode: ContextControllerSourceNode
|
||||||
|
private let pinchContainerNode: PinchSourceContainerNode
|
||||||
let imageNode: TransformImageNode
|
let imageNode: TransformImageNode
|
||||||
private var placeholderNode: StickerShimmerEffectNode
|
private var placeholderNode: StickerShimmerEffectNode
|
||||||
private var animationNode: GenericAnimatedStickerNode?
|
private var animationNode: GenericAnimatedStickerNode?
|
||||||
@ -195,6 +196,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
|||||||
required init() {
|
required init() {
|
||||||
self.contextSourceNode = ContextExtractedContentContainingNode()
|
self.contextSourceNode = ContextExtractedContentContainingNode()
|
||||||
self.containerNode = ContextControllerSourceNode()
|
self.containerNode = ContextControllerSourceNode()
|
||||||
|
self.pinchContainerNode = PinchSourceContainerNode()
|
||||||
self.imageNode = TransformImageNode()
|
self.imageNode = TransformImageNode()
|
||||||
self.dateAndStatusNode = ChatMessageDateAndStatusNode()
|
self.dateAndStatusNode = ChatMessageDateAndStatusNode()
|
||||||
|
|
||||||
@ -262,7 +264,8 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
|||||||
self.imageNode.displaysAsynchronously = false
|
self.imageNode.displaysAsynchronously = false
|
||||||
self.containerNode.addSubnode(self.contextSourceNode)
|
self.containerNode.addSubnode(self.contextSourceNode)
|
||||||
self.containerNode.targetNodeForActivationProgress = self.contextSourceNode.contentNode
|
self.containerNode.targetNodeForActivationProgress = self.contextSourceNode.contentNode
|
||||||
self.addSubnode(self.containerNode)
|
self.pinchContainerNode.contentNode.addSubnode(self.containerNode)
|
||||||
|
self.addSubnode(self.pinchContainerNode)
|
||||||
self.contextSourceNode.contentNode.addSubnode(self.imageNode)
|
self.contextSourceNode.contentNode.addSubnode(self.imageNode)
|
||||||
self.contextSourceNode.contentNode.addSubnode(self.placeholderNode)
|
self.contextSourceNode.contentNode.addSubnode(self.placeholderNode)
|
||||||
self.contextSourceNode.contentNode.addSubnode(self.dateAndStatusNode)
|
self.contextSourceNode.contentNode.addSubnode(self.dateAndStatusNode)
|
||||||
@ -278,6 +281,23 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
|||||||
}
|
}
|
||||||
item.controllerInteraction.openMessageReactions(item.message.id)
|
item.controllerInteraction.openMessageReactions(item.message.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.pinchContainerNode.activate = { [weak self] sourceNode in
|
||||||
|
guard let strongSelf = self, let item = strongSelf.item else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
item.controllerInteraction.activateMessagePinch(sourceNode)
|
||||||
|
}
|
||||||
|
|
||||||
|
self.pinchContainerNode.scaleUpdated = { [weak self] scale, transition in
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let factor: CGFloat = max(0.0, min(1.0, (scale - 1.0) * 8.0))
|
||||||
|
|
||||||
|
transition.updateAlpha(node: strongSelf.dateAndStatusNode, alpha: 1.0 - factor)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
deinit {
|
deinit {
|
||||||
@ -976,6 +996,8 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
|||||||
|
|
||||||
strongSelf.messageAccessibilityArea.frame = CGRect(origin: CGPoint(), size: layoutSize)
|
strongSelf.messageAccessibilityArea.frame = CGRect(origin: CGPoint(), size: layoutSize)
|
||||||
strongSelf.containerNode.frame = CGRect(origin: CGPoint(), size: layoutSize)
|
strongSelf.containerNode.frame = CGRect(origin: CGPoint(), size: layoutSize)
|
||||||
|
strongSelf.pinchContainerNode.frame = CGRect(origin: CGPoint(), size: layoutSize)
|
||||||
|
strongSelf.pinchContainerNode.update(size: layoutSize, transition: .immediate)
|
||||||
strongSelf.contextSourceNode.frame = CGRect(origin: CGPoint(), size: layoutSize)
|
strongSelf.contextSourceNode.frame = CGRect(origin: CGPoint(), size: layoutSize)
|
||||||
strongSelf.contextSourceNode.contentNode.frame = CGRect(origin: CGPoint(), size: layoutSize)
|
strongSelf.contextSourceNode.contentNode.frame = CGRect(origin: CGPoint(), size: layoutSize)
|
||||||
|
|
||||||
@ -1063,6 +1085,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
|||||||
imageApply()
|
imageApply()
|
||||||
|
|
||||||
strongSelf.contextSourceNode.contentRect = strongSelf.imageNode.frame
|
strongSelf.contextSourceNode.contentRect = strongSelf.imageNode.frame
|
||||||
|
strongSelf.pinchContainerNode.contentRect = strongSelf.imageNode.frame
|
||||||
strongSelf.containerNode.targetNodeForActivationProgressContentRect = strongSelf.contextSourceNode.contentRect
|
strongSelf.containerNode.targetNodeForActivationProgressContentRect = strongSelf.contextSourceNode.contentRect
|
||||||
|
|
||||||
if let updatedShareButtonNode = updatedShareButtonNode {
|
if let updatedShareButtonNode = updatedShareButtonNode {
|
||||||
|
|||||||
@ -271,7 +271,7 @@ final class ChatMessageAttachedContentNode: ASDisplayNode {
|
|||||||
self.addSubnode(self.statusNode)
|
self.addSubnode(self.statusNode)
|
||||||
}
|
}
|
||||||
|
|
||||||
func asyncLayout() -> (_ presentationData: ChatPresentationData, _ automaticDownloadSettings: MediaAutoDownloadSettings, _ associatedData: ChatMessageItemAssociatedData, _ attributes: ChatMessageEntryAttributes, _ context: AccountContext, _ controllerInteraction: ChatControllerInteraction, _ message: Message, _ messageRead: Bool, _ chatLocation: ChatLocation, _ title: String?, _ subtitle: NSAttributedString?, _ text: String?, _ entities: [MessageTextEntity]?, _ media: (Media, ChatMessageAttachedContentNodeMediaFlags)?, _ mediaBadge: String?, _ actionIcon: ChatMessageAttachedContentActionIcon?, _ actionTitle: String?, _ displayLine: Bool, _ layoutConstants: ChatMessageItemLayoutConstants, _ constrainedSize: CGSize) -> (CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool) -> Void))) {
|
func asyncLayout() -> (_ presentationData: ChatPresentationData, _ automaticDownloadSettings: MediaAutoDownloadSettings, _ associatedData: ChatMessageItemAssociatedData, _ attributes: ChatMessageEntryAttributes, _ context: AccountContext, _ controllerInteraction: ChatControllerInteraction, _ message: Message, _ messageRead: Bool, _ chatLocation: ChatLocation, _ title: String?, _ subtitle: NSAttributedString?, _ text: String?, _ entities: [MessageTextEntity]?, _ media: (Media, ChatMessageAttachedContentNodeMediaFlags)?, _ mediaBadge: String?, _ actionIcon: ChatMessageAttachedContentActionIcon?, _ actionTitle: String?, _ displayLine: Bool, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ constrainedSize: CGSize) -> (CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool) -> Void))) {
|
||||||
let textAsyncLayout = TextNode.asyncLayout(self.textNode)
|
let textAsyncLayout = TextNode.asyncLayout(self.textNode)
|
||||||
let currentImage = self.media as? TelegramMediaImage
|
let currentImage = self.media as? TelegramMediaImage
|
||||||
let imageLayout = self.inlineImageNode.asyncLayout()
|
let imageLayout = self.inlineImageNode.asyncLayout()
|
||||||
@ -284,7 +284,7 @@ final class ChatMessageAttachedContentNode: ASDisplayNode {
|
|||||||
|
|
||||||
let currentAdditionalImageBadgeNode = self.additionalImageBadgeNode
|
let currentAdditionalImageBadgeNode = self.additionalImageBadgeNode
|
||||||
|
|
||||||
return { presentationData, automaticDownloadSettings, associatedData, attributes, context, controllerInteraction, message, messageRead, chatLocation, title, subtitle, text, entities, mediaAndFlags, mediaBadge, actionIcon, actionTitle, displayLine, layoutConstants, constrainedSize in
|
return { presentationData, automaticDownloadSettings, associatedData, attributes, context, controllerInteraction, message, messageRead, chatLocation, title, subtitle, text, entities, mediaAndFlags, mediaBadge, actionIcon, actionTitle, displayLine, layoutConstants, preparePosition, constrainedSize in
|
||||||
let isPreview = presentationData.isPreview
|
let isPreview = presentationData.isPreview
|
||||||
let fontSize: CGFloat = floor(presentationData.fontSize.baseDisplaySize * 15.0 / 17.0)
|
let fontSize: CGFloat = floor(presentationData.fontSize.baseDisplaySize * 15.0 / 17.0)
|
||||||
|
|
||||||
@ -421,10 +421,98 @@ final class ChatMessageAttachedContentNode: ASDisplayNode {
|
|||||||
isReplyThread = true
|
isReplyThread = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var imageMode = false
|
||||||
|
|
||||||
|
var textStatusType: ChatMessageDateAndStatusType?
|
||||||
|
var imageStatusType: ChatMessageDateAndStatusType?
|
||||||
|
var additionalImageBadgeContent: ChatMessageInteractiveMediaBadgeContent?
|
||||||
|
|
||||||
if let (media, flags) = mediaAndFlags {
|
if let (media, flags) = mediaAndFlags {
|
||||||
if let file = media as? TelegramMediaFile {
|
if let file = media as? TelegramMediaFile {
|
||||||
if file.mimeType == "application/x-tgtheme-ios", let size = file.size, size < 16 * 1024 {
|
if file.mimeType == "application/x-tgtheme-ios", let size = file.size, size < 16 * 1024 {
|
||||||
let (_, initialImageWidth, refineLayout) = contentImageLayout(context, presentationData.theme.theme, presentationData.strings, presentationData.dateTimeFormat, message, attributes, file, .full, associatedData.automaticDownloadPeerType, .constrained(CGSize(width: constrainedSize.width - horizontalInsets.left - horizontalInsets.right, height: constrainedSize.height)), layoutConstants, contentMode)
|
imageMode = true
|
||||||
|
} else if file.isInstantVideo {
|
||||||
|
imageMode = true
|
||||||
|
} else if file.isVideo {
|
||||||
|
imageMode = true
|
||||||
|
} else if file.isSticker || file.isAnimatedSticker {
|
||||||
|
imageMode = true
|
||||||
|
}
|
||||||
|
} else if let _ = media as? TelegramMediaImage {
|
||||||
|
if !flags.contains(.preferMediaInline) {
|
||||||
|
imageMode = true
|
||||||
|
}
|
||||||
|
} else if let _ = media as? TelegramMediaWebFile {
|
||||||
|
imageMode = true
|
||||||
|
} else if let _ = media as? WallpaperPreviewMedia {
|
||||||
|
imageMode = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if preferMediaBeforeText {
|
||||||
|
imageMode = false
|
||||||
|
}
|
||||||
|
|
||||||
|
let statusInText = !imageMode
|
||||||
|
|
||||||
|
switch preparePosition {
|
||||||
|
case .linear(_, .None), .linear(_, .Neighbour(true, _, _)):
|
||||||
|
if let count = webpageGalleryMediaCount {
|
||||||
|
additionalImageBadgeContent = .text(inset: 0.0, backgroundColor: presentationData.theme.theme.chat.message.mediaDateAndStatusFillColor, foregroundColor: presentationData.theme.theme.chat.message.mediaDateAndStatusTextColor, text: NSAttributedString(string: presentationData.strings.Items_NOfM("1", "\(count)").0))
|
||||||
|
skipStandardStatus = imageMode
|
||||||
|
} else if let mediaBadge = mediaBadge {
|
||||||
|
additionalImageBadgeContent = .text(inset: 0.0, backgroundColor: presentationData.theme.theme.chat.message.mediaDateAndStatusFillColor, foregroundColor: presentationData.theme.theme.chat.message.mediaDateAndStatusTextColor, text: NSAttributedString(string: mediaBadge))
|
||||||
|
}
|
||||||
|
|
||||||
|
if !skipStandardStatus {
|
||||||
|
if message.effectivelyIncoming(context.account.peerId) {
|
||||||
|
if imageMode {
|
||||||
|
imageStatusType = .ImageIncoming
|
||||||
|
} else {
|
||||||
|
textStatusType = .BubbleIncoming
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if message.flags.contains(.Failed) {
|
||||||
|
if imageMode {
|
||||||
|
imageStatusType = .ImageOutgoing(.Failed)
|
||||||
|
} else {
|
||||||
|
textStatusType = .BubbleOutgoing(.Failed)
|
||||||
|
}
|
||||||
|
} else if (message.flags.isSending && !message.isSentOrAcknowledged) || attributes.updatingMedia != nil {
|
||||||
|
if imageMode {
|
||||||
|
imageStatusType = .ImageOutgoing(.Sending)
|
||||||
|
} else {
|
||||||
|
textStatusType = .BubbleOutgoing(.Sending)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if imageMode {
|
||||||
|
imageStatusType = .ImageOutgoing(.Sent(read: messageRead))
|
||||||
|
} else {
|
||||||
|
textStatusType = .BubbleOutgoing(.Sent(read: messageRead))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
let imageDateAndStatus = imageStatusType.flatMap { statusType -> ChatMessageDateAndStatus in
|
||||||
|
ChatMessageDateAndStatus(
|
||||||
|
type: statusType,
|
||||||
|
edited: edited,
|
||||||
|
viewCount: viewCount,
|
||||||
|
dateReplies: dateReplies,
|
||||||
|
dateReactions: dateReactions,
|
||||||
|
isPinned: message.tags.contains(.pinned) && !associatedData.isInPinnedListMode && !isReplyThread,
|
||||||
|
dateText: dateText
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if let (media, flags) = mediaAndFlags {
|
||||||
|
if let file = media as? TelegramMediaFile {
|
||||||
|
if file.mimeType == "application/x-tgtheme-ios", let size = file.size, size < 16 * 1024 {
|
||||||
|
let (_, initialImageWidth, refineLayout) = contentImageLayout(context, presentationData, presentationData.dateTimeFormat, message, attributes, file, imageDateAndStatus, .full, associatedData.automaticDownloadPeerType, .constrained(CGSize(width: constrainedSize.width - horizontalInsets.left - horizontalInsets.right, height: constrainedSize.height)), layoutConstants, contentMode)
|
||||||
initialWidth = initialImageWidth + horizontalInsets.left + horizontalInsets.right
|
initialWidth = initialImageWidth + horizontalInsets.left + horizontalInsets.right
|
||||||
refineContentImageLayout = refineLayout
|
refineContentImageLayout = refineLayout
|
||||||
} else if file.isInstantVideo {
|
} else if file.isInstantVideo {
|
||||||
@ -455,12 +543,12 @@ final class ChatMessageAttachedContentNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let (_, initialImageWidth, refineLayout) = contentImageLayout(context, presentationData.theme.theme, presentationData.strings, presentationData.dateTimeFormat, message, attributes, file, automaticDownload, associatedData.automaticDownloadPeerType, .constrained(CGSize(width: constrainedSize.width - horizontalInsets.left - horizontalInsets.right, height: constrainedSize.height)), layoutConstants, contentMode)
|
let (_, initialImageWidth, refineLayout) = contentImageLayout(context, presentationData, presentationData.dateTimeFormat, message, attributes, file, imageDateAndStatus, automaticDownload, associatedData.automaticDownloadPeerType, .constrained(CGSize(width: constrainedSize.width - horizontalInsets.left - horizontalInsets.right, height: constrainedSize.height)), layoutConstants, contentMode)
|
||||||
initialWidth = initialImageWidth + horizontalInsets.left + horizontalInsets.right
|
initialWidth = initialImageWidth + horizontalInsets.left + horizontalInsets.right
|
||||||
refineContentImageLayout = refineLayout
|
refineContentImageLayout = refineLayout
|
||||||
} else if file.isSticker || file.isAnimatedSticker {
|
} else if file.isSticker || file.isAnimatedSticker {
|
||||||
let automaticDownload = shouldDownloadMediaAutomatically(settings: automaticDownloadSettings, peerType: associatedData.automaticDownloadPeerType, networkType: associatedData.automaticDownloadNetworkType, authorPeerId: message.author?.id, contactsPeerIds: associatedData.contactsPeerIds, media: file)
|
let automaticDownload = shouldDownloadMediaAutomatically(settings: automaticDownloadSettings, peerType: associatedData.automaticDownloadPeerType, networkType: associatedData.automaticDownloadNetworkType, authorPeerId: message.author?.id, contactsPeerIds: associatedData.contactsPeerIds, media: file)
|
||||||
let (_, initialImageWidth, refineLayout) = contentImageLayout(context, presentationData.theme.theme, presentationData.strings, presentationData.dateTimeFormat, message, attributes, file, automaticDownload ? .full : .none, associatedData.automaticDownloadPeerType, .constrained(CGSize(width: constrainedSize.width - horizontalInsets.left - horizontalInsets.right, height: constrainedSize.height)), layoutConstants, contentMode)
|
let (_, initialImageWidth, refineLayout) = contentImageLayout(context, presentationData, presentationData.dateTimeFormat, message, attributes, file, imageDateAndStatus, automaticDownload ? .full : .none, associatedData.automaticDownloadPeerType, .constrained(CGSize(width: constrainedSize.width - horizontalInsets.left - horizontalInsets.right, height: constrainedSize.height)), layoutConstants, contentMode)
|
||||||
initialWidth = initialImageWidth + horizontalInsets.left + horizontalInsets.right
|
initialWidth = initialImageWidth + horizontalInsets.left + horizontalInsets.right
|
||||||
refineContentImageLayout = refineLayout
|
refineContentImageLayout = refineLayout
|
||||||
} else {
|
} else {
|
||||||
@ -485,7 +573,7 @@ final class ChatMessageAttachedContentNode: ASDisplayNode {
|
|||||||
} else if let image = media as? TelegramMediaImage {
|
} else if let image = media as? TelegramMediaImage {
|
||||||
if !flags.contains(.preferMediaInline) {
|
if !flags.contains(.preferMediaInline) {
|
||||||
let automaticDownload = shouldDownloadMediaAutomatically(settings: automaticDownloadSettings, peerType: associatedData.automaticDownloadPeerType, networkType: associatedData.automaticDownloadNetworkType, authorPeerId: message.author?.id, contactsPeerIds: associatedData.contactsPeerIds, media: image)
|
let automaticDownload = shouldDownloadMediaAutomatically(settings: automaticDownloadSettings, peerType: associatedData.automaticDownloadPeerType, networkType: associatedData.automaticDownloadNetworkType, authorPeerId: message.author?.id, contactsPeerIds: associatedData.contactsPeerIds, media: image)
|
||||||
let (_, initialImageWidth, refineLayout) = contentImageLayout(context, presentationData.theme.theme, presentationData.strings, presentationData.dateTimeFormat, message, attributes, image, automaticDownload ? .full : .none, associatedData.automaticDownloadPeerType, .constrained(CGSize(width: constrainedSize.width - horizontalInsets.left - horizontalInsets.right, height: constrainedSize.height)), layoutConstants, contentMode)
|
let (_, initialImageWidth, refineLayout) = contentImageLayout(context, presentationData, presentationData.dateTimeFormat, message, attributes, image, imageDateAndStatus, automaticDownload ? .full : .none, associatedData.automaticDownloadPeerType, .constrained(CGSize(width: constrainedSize.width - horizontalInsets.left - horizontalInsets.right, height: constrainedSize.height)), layoutConstants, contentMode)
|
||||||
initialWidth = initialImageWidth + horizontalInsets.left + horizontalInsets.right
|
initialWidth = initialImageWidth + horizontalInsets.left + horizontalInsets.right
|
||||||
refineContentImageLayout = refineLayout
|
refineContentImageLayout = refineLayout
|
||||||
} else if let dimensions = largestImageRepresentation(image.representations)?.dimensions {
|
} else if let dimensions = largestImageRepresentation(image.representations)?.dimensions {
|
||||||
@ -497,11 +585,11 @@ final class ChatMessageAttachedContentNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
} else if let image = media as? TelegramMediaWebFile {
|
} else if let image = media as? TelegramMediaWebFile {
|
||||||
let automaticDownload = shouldDownloadMediaAutomatically(settings: automaticDownloadSettings, peerType: associatedData.automaticDownloadPeerType, networkType: associatedData.automaticDownloadNetworkType, authorPeerId: message.author?.id, contactsPeerIds: associatedData.contactsPeerIds, media: image)
|
let automaticDownload = shouldDownloadMediaAutomatically(settings: automaticDownloadSettings, peerType: associatedData.automaticDownloadPeerType, networkType: associatedData.automaticDownloadNetworkType, authorPeerId: message.author?.id, contactsPeerIds: associatedData.contactsPeerIds, media: image)
|
||||||
let (_, initialImageWidth, refineLayout) = contentImageLayout(context, presentationData.theme.theme, presentationData.strings, presentationData.dateTimeFormat, message, attributes, image, automaticDownload ? .full : .none, associatedData.automaticDownloadPeerType, .constrained(CGSize(width: constrainedSize.width - horizontalInsets.left - horizontalInsets.right, height: constrainedSize.height)), layoutConstants, contentMode)
|
let (_, initialImageWidth, refineLayout) = contentImageLayout(context, presentationData, presentationData.dateTimeFormat, message, attributes, image, imageDateAndStatus, automaticDownload ? .full : .none, associatedData.automaticDownloadPeerType, .constrained(CGSize(width: constrainedSize.width - horizontalInsets.left - horizontalInsets.right, height: constrainedSize.height)), layoutConstants, contentMode)
|
||||||
initialWidth = initialImageWidth + horizontalInsets.left + horizontalInsets.right
|
initialWidth = initialImageWidth + horizontalInsets.left + horizontalInsets.right
|
||||||
refineContentImageLayout = refineLayout
|
refineContentImageLayout = refineLayout
|
||||||
} else if let wallpaper = media as? WallpaperPreviewMedia {
|
} else if let wallpaper = media as? WallpaperPreviewMedia {
|
||||||
let (_, initialImageWidth, refineLayout) = contentImageLayout(context, presentationData.theme.theme, presentationData.strings, presentationData.dateTimeFormat, message, attributes, wallpaper, .full, associatedData.automaticDownloadPeerType, .constrained(CGSize(width: constrainedSize.width - horizontalInsets.left - horizontalInsets.right, height: constrainedSize.height)), layoutConstants, contentMode)
|
let (_, initialImageWidth, refineLayout) = contentImageLayout(context, presentationData, presentationData.dateTimeFormat, message, attributes, wallpaper, imageDateAndStatus, .full, associatedData.automaticDownloadPeerType, .constrained(CGSize(width: constrainedSize.width - horizontalInsets.left - horizontalInsets.right, height: constrainedSize.height)), layoutConstants, contentMode)
|
||||||
initialWidth = initialImageWidth + horizontalInsets.left + horizontalInsets.right
|
initialWidth = initialImageWidth + horizontalInsets.left + horizontalInsets.right
|
||||||
refineContentImageLayout = refineLayout
|
refineContentImageLayout = refineLayout
|
||||||
if case let .file(_, _, _, _, isTheme, _) = wallpaper.content, isTheme {
|
if case let .file(_, _, _, _, isTheme, _) = wallpaper.content, isTheme {
|
||||||
@ -527,60 +615,12 @@ final class ChatMessageAttachedContentNode: ASDisplayNode {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
var statusInText = false
|
|
||||||
|
|
||||||
var statusSizeAndApply: (CGSize, (Bool) -> Void)?
|
var statusSizeAndApply: (CGSize, (Bool) -> Void)?
|
||||||
|
|
||||||
let textConstrainedSize = CGSize(width: constrainedSize.width - insets.left - insets.right, height: constrainedSize.height - insets.top - insets.bottom)
|
let textConstrainedSize = CGSize(width: constrainedSize.width - insets.left - insets.right, height: constrainedSize.height - insets.top - insets.bottom)
|
||||||
|
|
||||||
var additionalImageBadgeContent: ChatMessageInteractiveMediaBadgeContent?
|
if let textStatusType = textStatusType {
|
||||||
|
statusSizeAndApply = statusLayout(context, presentationData, edited, viewCount, dateText, textStatusType, textConstrainedSize, dateReactions, dateReplies, message.tags.contains(.pinned) && !associatedData.isInPinnedListMode && !isReplyThread, message.isSelfExpiring)
|
||||||
switch position {
|
|
||||||
case .linear(_, .None), .linear(_, .Neighbour(true, _, _)):
|
|
||||||
let imageMode = !((refineContentImageLayout == nil && refineContentFileLayout == nil && contentInstantVideoSizeAndApply == nil) || preferMediaBeforeText)
|
|
||||||
statusInText = !imageMode
|
|
||||||
|
|
||||||
if let count = webpageGalleryMediaCount {
|
|
||||||
additionalImageBadgeContent = .text(inset: 0.0, backgroundColor: presentationData.theme.theme.chat.message.mediaDateAndStatusFillColor, foregroundColor: presentationData.theme.theme.chat.message.mediaDateAndStatusTextColor, text: NSAttributedString(string: presentationData.strings.Items_NOfM("1", "\(count)").0))
|
|
||||||
skipStandardStatus = imageMode
|
|
||||||
} else if let mediaBadge = mediaBadge {
|
|
||||||
additionalImageBadgeContent = .text(inset: 0.0, backgroundColor: presentationData.theme.theme.chat.message.mediaDateAndStatusFillColor, foregroundColor: presentationData.theme.theme.chat.message.mediaDateAndStatusTextColor, text: NSAttributedString(string: mediaBadge))
|
|
||||||
}
|
|
||||||
|
|
||||||
if !skipStandardStatus {
|
|
||||||
let statusType: ChatMessageDateAndStatusType
|
|
||||||
if message.effectivelyIncoming(context.account.peerId) {
|
|
||||||
if imageMode {
|
|
||||||
statusType = .ImageIncoming
|
|
||||||
} else {
|
|
||||||
statusType = .BubbleIncoming
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if message.flags.contains(.Failed) {
|
|
||||||
if imageMode {
|
|
||||||
statusType = .ImageOutgoing(.Failed)
|
|
||||||
} else {
|
|
||||||
statusType = .BubbleOutgoing(.Failed)
|
|
||||||
}
|
|
||||||
} else if (message.flags.isSending && !message.isSentOrAcknowledged) || attributes.updatingMedia != nil {
|
|
||||||
if imageMode {
|
|
||||||
statusType = .ImageOutgoing(.Sending)
|
|
||||||
} else {
|
|
||||||
statusType = .BubbleOutgoing(.Sending)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if imageMode {
|
|
||||||
statusType = .ImageOutgoing(.Sent(read: messageRead))
|
|
||||||
} else {
|
|
||||||
statusType = .BubbleOutgoing(.Sent(read: messageRead))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
statusSizeAndApply = statusLayout(context, presentationData, edited, viewCount, dateText, statusType, textConstrainedSize, dateReactions, dateReplies, message.tags.contains(.pinned) && !associatedData.isInPinnedListMode && !isReplyThread, message.isSelfExpiring)
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var updatedAdditionalImageBadge: ChatMessageInteractiveMediaBadge?
|
var updatedAdditionalImageBadge: ChatMessageInteractiveMediaBadge?
|
||||||
@ -823,6 +863,9 @@ final class ChatMessageAttachedContentNode: ASDisplayNode {
|
|||||||
let contentImageNode = contentImageApply(transition, synchronousLoads)
|
let contentImageNode = contentImageApply(transition, synchronousLoads)
|
||||||
if strongSelf.contentImageNode !== contentImageNode {
|
if strongSelf.contentImageNode !== contentImageNode {
|
||||||
strongSelf.contentImageNode = contentImageNode
|
strongSelf.contentImageNode = contentImageNode
|
||||||
|
contentImageNode.activatePinch = { sourceNode in
|
||||||
|
controllerInteraction.activateMessagePinch(sourceNode)
|
||||||
|
}
|
||||||
strongSelf.addSubnode(contentImageNode)
|
strongSelf.addSubnode(contentImageNode)
|
||||||
contentImageNode.activateLocalContent = { [weak strongSelf] mode in
|
contentImageNode.activateLocalContent = { [weak strongSelf] mode in
|
||||||
if let strongSelf = strongSelf {
|
if let strongSelf = strongSelf {
|
||||||
|
|||||||
@ -236,7 +236,7 @@ class ChatMessageDateAndStatusNode: ASDisplayNode {
|
|||||||
let clockMinImage: UIImage?
|
let clockMinImage: UIImage?
|
||||||
var impressionImage: UIImage?
|
var impressionImage: UIImage?
|
||||||
var repliesImage: UIImage?
|
var repliesImage: UIImage?
|
||||||
var selfExpiringImage: UIImage?
|
let selfExpiringImage: UIImage? = nil
|
||||||
|
|
||||||
let themeUpdated = presentationData.theme != currentTheme || type != currentType
|
let themeUpdated = presentationData.theme != currentTheme || type != currentType
|
||||||
|
|
||||||
|
|||||||
@ -25,7 +25,7 @@ final class ChatMessageEventLogPreviousDescriptionContentNode: ChatMessageBubble
|
|||||||
override func asyncLayoutContent() -> (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool) -> Void))) {
|
override func asyncLayoutContent() -> (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool) -> Void))) {
|
||||||
let contentNodeLayout = self.contentNode.asyncLayout()
|
let contentNodeLayout = self.contentNode.asyncLayout()
|
||||||
|
|
||||||
return { item, layoutConstants, _, _, constrainedSize in
|
return { item, layoutConstants, preparePosition, _, constrainedSize in
|
||||||
var messageEntities: [MessageTextEntity]?
|
var messageEntities: [MessageTextEntity]?
|
||||||
|
|
||||||
for attribute in item.message.attributes {
|
for attribute in item.message.attributes {
|
||||||
@ -44,7 +44,7 @@ final class ChatMessageEventLogPreviousDescriptionContentNode: ChatMessageBubble
|
|||||||
}
|
}
|
||||||
let mediaAndFlags: (Media, ChatMessageAttachedContentNodeMediaFlags)? = nil
|
let mediaAndFlags: (Media, ChatMessageAttachedContentNodeMediaFlags)? = nil
|
||||||
|
|
||||||
let (initialWidth, continueLayout) = contentNodeLayout(item.presentationData, item.controllerInteraction.automaticMediaDownloadSettings, item.associatedData, item.attributes, item.context, item.controllerInteraction, item.message, true, .peer(item.message.id.peerId), title, nil, text, messageEntities, mediaAndFlags, nil, nil, nil, true, layoutConstants, constrainedSize)
|
let (initialWidth, continueLayout) = contentNodeLayout(item.presentationData, item.controllerInteraction.automaticMediaDownloadSettings, item.associatedData, item.attributes, item.context, item.controllerInteraction, item.message, true, .peer(item.message.id.peerId), title, nil, text, messageEntities, mediaAndFlags, nil, nil, nil, true, layoutConstants, preparePosition, constrainedSize)
|
||||||
|
|
||||||
let contentProperties = ChatMessageBubbleContentProperties(hidesSimpleAuthorHeader: false, headerSpacing: 8.0, hidesBackground: .never, forceFullCorners: false, forceAlignment: .none)
|
let contentProperties = ChatMessageBubbleContentProperties(hidesSimpleAuthorHeader: false, headerSpacing: 8.0, hidesBackground: .never, forceFullCorners: false, forceAlignment: .none)
|
||||||
|
|
||||||
|
|||||||
@ -25,7 +25,7 @@ final class ChatMessageEventLogPreviousLinkContentNode: ChatMessageBubbleContent
|
|||||||
override func asyncLayoutContent() -> (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool) -> Void))) {
|
override func asyncLayoutContent() -> (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool) -> Void))) {
|
||||||
let contentNodeLayout = self.contentNode.asyncLayout()
|
let contentNodeLayout = self.contentNode.asyncLayout()
|
||||||
|
|
||||||
return { item, layoutConstants, _, _, constrainedSize in
|
return { item, layoutConstants, preparePosition, _, constrainedSize in
|
||||||
var messageEntities: [MessageTextEntity]?
|
var messageEntities: [MessageTextEntity]?
|
||||||
|
|
||||||
for attribute in item.message.attributes {
|
for attribute in item.message.attributes {
|
||||||
@ -39,7 +39,7 @@ final class ChatMessageEventLogPreviousLinkContentNode: ChatMessageBubbleContent
|
|||||||
let text: String = item.message.text
|
let text: String = item.message.text
|
||||||
let mediaAndFlags: (Media, ChatMessageAttachedContentNodeMediaFlags)? = nil
|
let mediaAndFlags: (Media, ChatMessageAttachedContentNodeMediaFlags)? = nil
|
||||||
|
|
||||||
let (initialWidth, continueLayout) = contentNodeLayout(item.presentationData, item.controllerInteraction.automaticMediaDownloadSettings, item.associatedData, item.attributes, item.context, item.controllerInteraction, item.message, true, .peer(item.message.id.peerId), title, nil, text, messageEntities, mediaAndFlags, nil, nil, nil, true, layoutConstants, constrainedSize)
|
let (initialWidth, continueLayout) = contentNodeLayout(item.presentationData, item.controllerInteraction.automaticMediaDownloadSettings, item.associatedData, item.attributes, item.context, item.controllerInteraction, item.message, true, .peer(item.message.id.peerId), title, nil, text, messageEntities, mediaAndFlags, nil, nil, nil, true, layoutConstants, preparePosition, constrainedSize)
|
||||||
|
|
||||||
let contentProperties = ChatMessageBubbleContentProperties(hidesSimpleAuthorHeader: false, headerSpacing: 8.0, hidesBackground: .never, forceFullCorners: false, forceAlignment: .none)
|
let contentProperties = ChatMessageBubbleContentProperties(hidesSimpleAuthorHeader: false, headerSpacing: 8.0, hidesBackground: .never, forceFullCorners: false, forceAlignment: .none)
|
||||||
|
|
||||||
|
|||||||
@ -25,7 +25,7 @@ final class ChatMessageEventLogPreviousMessageContentNode: ChatMessageBubbleCont
|
|||||||
override func asyncLayoutContent() -> (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool) -> Void))) {
|
override func asyncLayoutContent() -> (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool) -> Void))) {
|
||||||
let contentNodeLayout = self.contentNode.asyncLayout()
|
let contentNodeLayout = self.contentNode.asyncLayout()
|
||||||
|
|
||||||
return { item, layoutConstants, _, _, constrainedSize in
|
return { item, layoutConstants, preparePosition, _, constrainedSize in
|
||||||
var messageEntities: [MessageTextEntity]?
|
var messageEntities: [MessageTextEntity]?
|
||||||
|
|
||||||
for attribute in item.message.attributes {
|
for attribute in item.message.attributes {
|
||||||
@ -44,7 +44,7 @@ final class ChatMessageEventLogPreviousMessageContentNode: ChatMessageBubbleCont
|
|||||||
}
|
}
|
||||||
let mediaAndFlags: (Media, ChatMessageAttachedContentNodeMediaFlags)? = nil
|
let mediaAndFlags: (Media, ChatMessageAttachedContentNodeMediaFlags)? = nil
|
||||||
|
|
||||||
let (initialWidth, continueLayout) = contentNodeLayout(item.presentationData, item.controllerInteraction.automaticMediaDownloadSettings, item.associatedData, item.attributes, item.context, item.controllerInteraction, item.message, true, .peer(item.message.id.peerId), title, nil, text, messageEntities, mediaAndFlags, nil, nil, nil, true, layoutConstants, constrainedSize)
|
let (initialWidth, continueLayout) = contentNodeLayout(item.presentationData, item.controllerInteraction.automaticMediaDownloadSettings, item.associatedData, item.attributes, item.context, item.controllerInteraction, item.message, true, .peer(item.message.id.peerId), title, nil, text, messageEntities, mediaAndFlags, nil, nil, nil, true, layoutConstants, preparePosition, constrainedSize)
|
||||||
|
|
||||||
let contentProperties = ChatMessageBubbleContentProperties(hidesSimpleAuthorHeader: false, headerSpacing: 8.0, hidesBackground: .never, forceFullCorners: false, forceAlignment: .none)
|
let contentProperties = ChatMessageBubbleContentProperties(hidesSimpleAuthorHeader: false, headerSpacing: 8.0, hidesBackground: .never, forceFullCorners: false, forceAlignment: .none)
|
||||||
|
|
||||||
|
|||||||
@ -45,7 +45,7 @@ final class ChatMessageGameBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
override func asyncLayoutContent() -> (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool) -> Void))) {
|
override func asyncLayoutContent() -> (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool) -> Void))) {
|
||||||
let contentNodeLayout = self.contentNode.asyncLayout()
|
let contentNodeLayout = self.contentNode.asyncLayout()
|
||||||
|
|
||||||
return { item, layoutConstants, _, _, constrainedSize in
|
return { item, layoutConstants, preparePosition, _, constrainedSize in
|
||||||
var game: TelegramMediaGame?
|
var game: TelegramMediaGame?
|
||||||
var messageEntities: [MessageTextEntity]?
|
var messageEntities: [MessageTextEntity]?
|
||||||
|
|
||||||
@ -78,7 +78,7 @@ final class ChatMessageGameBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let (initialWidth, continueLayout) = contentNodeLayout(item.presentationData, item.controllerInteraction.automaticMediaDownloadSettings, item.associatedData, item.attributes, item.context, item.controllerInteraction, item.message, item.read, .peer(item.message.id.peerId), title, nil, item.message.text.isEmpty ? text : item.message.text, item.message.text.isEmpty ? nil : messageEntities, mediaAndFlags, nil, nil, nil, true, layoutConstants, constrainedSize)
|
let (initialWidth, continueLayout) = contentNodeLayout(item.presentationData, item.controllerInteraction.automaticMediaDownloadSettings, item.associatedData, item.attributes, item.context, item.controllerInteraction, item.message, item.read, .peer(item.message.id.peerId), title, nil, item.message.text.isEmpty ? text : item.message.text, item.message.text.isEmpty ? nil : messageEntities, mediaAndFlags, nil, nil, nil, true, layoutConstants, preparePosition, constrainedSize)
|
||||||
|
|
||||||
let contentProperties = ChatMessageBubbleContentProperties(hidesSimpleAuthorHeader: false, headerSpacing: 8.0, hidesBackground: .never, forceFullCorners: false, forceAlignment: .none)
|
let contentProperties = ChatMessageBubbleContentProperties(hidesSimpleAuthorHeader: false, headerSpacing: 8.0, hidesBackground: .never, forceFullCorners: false, forceAlignment: .none)
|
||||||
|
|
||||||
|
|||||||
@ -22,6 +22,7 @@ import TelegramAnimatedStickerNode
|
|||||||
import LocalMediaResources
|
import LocalMediaResources
|
||||||
import WallpaperResources
|
import WallpaperResources
|
||||||
import ChatMessageInteractiveMediaBadge
|
import ChatMessageInteractiveMediaBadge
|
||||||
|
import ContextUI
|
||||||
|
|
||||||
private struct FetchControls {
|
private struct FetchControls {
|
||||||
let fetch: (Bool) -> Void
|
let fetch: (Bool) -> Void
|
||||||
@ -64,9 +65,23 @@ enum InteractiveMediaNodePlayWithSoundMode {
|
|||||||
case loop
|
case loop
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct ChatMessageDateAndStatus {
|
||||||
|
var type: ChatMessageDateAndStatusType
|
||||||
|
var edited: Bool
|
||||||
|
var viewCount: Int?
|
||||||
|
var dateReplies: Int
|
||||||
|
var dateReactions: [MessageReaction]
|
||||||
|
var isPinned: Bool
|
||||||
|
var dateText: String
|
||||||
|
}
|
||||||
|
|
||||||
final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitionNode {
|
final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitionNode {
|
||||||
|
private let pinchContainerNode: PinchSourceContainerNode
|
||||||
private let imageNode: TransformImageNode
|
private let imageNode: TransformImageNode
|
||||||
private var currentImageArguments: TransformImageArguments?
|
private var currentImageArguments: TransformImageArguments?
|
||||||
|
private var currentHighQualityImageSignal: Signal<(TransformImageArguments) -> DrawingContext?, NoError>?
|
||||||
|
private var highQualityImageNode: TransformImageNode?
|
||||||
|
|
||||||
private var videoNode: UniversalVideoNode?
|
private var videoNode: UniversalVideoNode?
|
||||||
private var videoContent: NativeVideoContent?
|
private var videoContent: NativeVideoContent?
|
||||||
private var animatedStickerNode: AnimatedStickerNode?
|
private var animatedStickerNode: AnimatedStickerNode?
|
||||||
@ -75,6 +90,7 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio
|
|||||||
var decoration: UniversalVideoDecoration? {
|
var decoration: UniversalVideoDecoration? {
|
||||||
return self.videoNodeDecoration
|
return self.videoNodeDecoration
|
||||||
}
|
}
|
||||||
|
let dateAndStatusNode: ChatMessageDateAndStatusNode
|
||||||
private var badgeNode: ChatMessageInteractiveMediaBadge?
|
private var badgeNode: ChatMessageInteractiveMediaBadge?
|
||||||
private var tapRecognizer: UITapGestureRecognizer?
|
private var tapRecognizer: UITapGestureRecognizer?
|
||||||
|
|
||||||
@ -134,15 +150,74 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio
|
|||||||
}
|
}
|
||||||
|
|
||||||
var activateLocalContent: (InteractiveMediaNodeActivateContent) -> Void = { _ in }
|
var activateLocalContent: (InteractiveMediaNodeActivateContent) -> Void = { _ in }
|
||||||
|
var activatePinch: ((PinchSourceContainerNode) -> Void)?
|
||||||
|
|
||||||
override init() {
|
override init() {
|
||||||
|
self.pinchContainerNode = PinchSourceContainerNode()
|
||||||
|
|
||||||
|
self.dateAndStatusNode = ChatMessageDateAndStatusNode()
|
||||||
|
|
||||||
self.imageNode = TransformImageNode()
|
self.imageNode = TransformImageNode()
|
||||||
self.imageNode.contentAnimations = [.subsequentUpdates]
|
self.imageNode.contentAnimations = [.subsequentUpdates]
|
||||||
|
|
||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
|
self.addSubnode(self.pinchContainerNode)
|
||||||
|
|
||||||
self.imageNode.displaysAsynchronously = false
|
self.imageNode.displaysAsynchronously = false
|
||||||
self.addSubnode(self.imageNode)
|
self.pinchContainerNode.contentNode.addSubnode(self.imageNode)
|
||||||
|
|
||||||
|
self.pinchContainerNode.activate = { [weak self] sourceNode in
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
strongSelf.activatePinch?(sourceNode)
|
||||||
|
}
|
||||||
|
|
||||||
|
self.pinchContainerNode.scaleUpdated = { [weak self] scale, transition in
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let factor: CGFloat = max(0.0, min(1.0, (scale - 1.0) * 8.0))
|
||||||
|
|
||||||
|
transition.updateAlpha(node: strongSelf.dateAndStatusNode, alpha: 1.0 - factor)
|
||||||
|
|
||||||
|
if abs(scale - 1.0) > CGFloat.ulpOfOne {
|
||||||
|
var highQualityImageNode: TransformImageNode?
|
||||||
|
if let current = strongSelf.highQualityImageNode {
|
||||||
|
highQualityImageNode = current
|
||||||
|
} else if let currentHighQualityImageSignal = strongSelf.currentHighQualityImageSignal, let currentImageArguments = strongSelf.currentImageArguments {
|
||||||
|
let imageNode = TransformImageNode()
|
||||||
|
imageNode.frame = strongSelf.imageNode.frame
|
||||||
|
strongSelf.pinchContainerNode.contentNode.insertSubnode(imageNode, aboveSubnode: strongSelf.imageNode)
|
||||||
|
|
||||||
|
var updatedArguments = currentImageArguments
|
||||||
|
updatedArguments.scale = 3.0
|
||||||
|
let apply = imageNode.asyncLayout()(updatedArguments)
|
||||||
|
let _ = apply()
|
||||||
|
imageNode.setSignal(currentHighQualityImageSignal, attemptSynchronously: false)
|
||||||
|
|
||||||
|
highQualityImageNode = imageNode
|
||||||
|
strongSelf.highQualityImageNode = imageNode
|
||||||
|
}
|
||||||
|
if let highQualityImageNode = highQualityImageNode {
|
||||||
|
transition.updateAlpha(node: highQualityImageNode, alpha: factor)
|
||||||
|
}
|
||||||
|
} else if let highQualityImageNode = strongSelf.highQualityImageNode {
|
||||||
|
strongSelf.highQualityImageNode = nil
|
||||||
|
transition.updateAlpha(node: highQualityImageNode, alpha: 0.0, completion: { [weak highQualityImageNode] _ in
|
||||||
|
highQualityImageNode?.removeFromSupernode()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if let badgeNode = strongSelf.badgeNode {
|
||||||
|
transition.updateAlpha(node: badgeNode, alpha: 1.0 - factor)
|
||||||
|
}
|
||||||
|
if let statusNode = strongSelf.statusNode {
|
||||||
|
transition.updateAlpha(node: statusNode, alpha: 1.0 - factor)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
deinit {
|
deinit {
|
||||||
@ -242,10 +317,11 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func asyncLayout() -> (_ context: AccountContext, _ theme: PresentationTheme, _ strings: PresentationStrings, _ dateTimeFormat: PresentationDateTimeFormat, _ message: Message, _ attributes: ChatMessageEntryAttributes, _ media: Media, _ automaticDownload: InteractiveMediaNodeAutodownloadMode, _ peerType: MediaAutoDownloadPeerType, _ sizeCalculation: InteractiveMediaNodeSizeCalculation, _ layoutConstants: ChatMessageItemLayoutConstants, _ contentMode: InteractiveMediaNodeContentMode) -> (CGSize, CGFloat, (CGSize, Bool, Bool, ImageCorners) -> (CGFloat, (CGFloat) -> (CGSize, (ContainedViewLayoutTransition, Bool) -> Void))) {
|
func asyncLayout() -> (_ context: AccountContext, _ presentationData: ChatPresentationData, _ dateTimeFormat: PresentationDateTimeFormat, _ message: Message, _ attributes: ChatMessageEntryAttributes, _ media: Media, _ dateAndStatus: ChatMessageDateAndStatus?, _ automaticDownload: InteractiveMediaNodeAutodownloadMode, _ peerType: MediaAutoDownloadPeerType, _ sizeCalculation: InteractiveMediaNodeSizeCalculation, _ layoutConstants: ChatMessageItemLayoutConstants, _ contentMode: InteractiveMediaNodeContentMode) -> (CGSize, CGFloat, (CGSize, Bool, Bool, ImageCorners) -> (CGFloat, (CGFloat) -> (CGSize, (ContainedViewLayoutTransition, Bool) -> Void))) {
|
||||||
let currentMessage = self.message
|
let currentMessage = self.message
|
||||||
let currentMedia = self.media
|
let currentMedia = self.media
|
||||||
let imageLayout = self.imageNode.asyncLayout()
|
let imageLayout = self.imageNode.asyncLayout()
|
||||||
|
let statusLayout = self.dateAndStatusNode.asyncLayout()
|
||||||
|
|
||||||
let currentVideoNode = self.videoNode
|
let currentVideoNode = self.videoNode
|
||||||
let currentAnimatedStickerNode = self.animatedStickerNode
|
let currentAnimatedStickerNode = self.animatedStickerNode
|
||||||
@ -255,7 +331,7 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio
|
|||||||
let currentAutomaticDownload = self.automaticDownload
|
let currentAutomaticDownload = self.automaticDownload
|
||||||
let currentAutomaticPlayback = self.automaticPlayback
|
let currentAutomaticPlayback = self.automaticPlayback
|
||||||
|
|
||||||
return { [weak self] context, theme, strings, dateTimeFormat, message, attributes, media, automaticDownload, peerType, sizeCalculation, layoutConstants, contentMode in
|
return { [weak self] context, presentationData, dateTimeFormat, message, attributes, media, dateAndStatus, automaticDownload, peerType, sizeCalculation, layoutConstants, contentMode in
|
||||||
var nativeSize: CGSize
|
var nativeSize: CGSize
|
||||||
|
|
||||||
let isSecretMedia = message.containsSecretMedia
|
let isSecretMedia = message.containsSecretMedia
|
||||||
@ -360,6 +436,15 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio
|
|||||||
nativeSize = unboundSize
|
nativeSize = unboundSize
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var statusSize = CGSize()
|
||||||
|
var statusApply: ((Bool) -> Void)?
|
||||||
|
|
||||||
|
if let dateAndStatus = dateAndStatus {
|
||||||
|
let (size, apply) = statusLayout(context, presentationData, dateAndStatus.edited, dateAndStatus.viewCount, dateAndStatus.dateText, dateAndStatus.type, CGSize(width: nativeSize.width - 30.0, height: CGFloat.greatestFiniteMagnitude), dateAndStatus.dateReactions, dateAndStatus.dateReplies, dateAndStatus.isPinned, message.isSelfExpiring)
|
||||||
|
statusSize = size
|
||||||
|
statusApply = apply
|
||||||
|
}
|
||||||
|
|
||||||
let maxWidth: CGFloat
|
let maxWidth: CGFloat
|
||||||
if isSecretMedia {
|
if isSecretMedia {
|
||||||
maxWidth = 180.0
|
maxWidth = 180.0
|
||||||
@ -367,7 +452,7 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio
|
|||||||
maxWidth = maxDimensions.width
|
maxWidth = maxDimensions.width
|
||||||
}
|
}
|
||||||
if isSecretMedia {
|
if isSecretMedia {
|
||||||
let _ = PresentationResourcesChat.chatBubbleSecretMediaIcon(theme)
|
let _ = PresentationResourcesChat.chatBubbleSecretMediaIcon(presentationData.theme.theme)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (nativeSize, maxWidth, { constrainedSize, automaticPlayback, wideLayout, corners in
|
return (nativeSize, maxWidth, { constrainedSize, automaticPlayback, wideLayout, corners in
|
||||||
@ -416,7 +501,7 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio
|
|||||||
drawingSize = nativeSize.aspectFilled(boundingSize)
|
drawingSize = nativeSize.aspectFilled(boundingSize)
|
||||||
}
|
}
|
||||||
|
|
||||||
var updateImageSignal: ((Bool) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError>)?
|
var updateImageSignal: ((Bool, Bool) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError>)?
|
||||||
var updatedStatusSignal: Signal<(MediaResourceStatus, MediaResourceStatus?), NoError>?
|
var updatedStatusSignal: Signal<(MediaResourceStatus, MediaResourceStatus?), NoError>?
|
||||||
var updatedFetchControls: FetchControls?
|
var updatedFetchControls: FetchControls?
|
||||||
|
|
||||||
@ -453,7 +538,7 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio
|
|||||||
if isSticker {
|
if isSticker {
|
||||||
emptyColor = .clear
|
emptyColor = .clear
|
||||||
} else {
|
} else {
|
||||||
emptyColor = message.effectivelyIncoming(context.account.peerId) ? theme.chat.message.incoming.mediaPlaceholderColor : theme.chat.message.outgoing.mediaPlaceholderColor
|
emptyColor = message.effectivelyIncoming(context.account.peerId) ? presentationData.theme.theme.chat.message.incoming.mediaPlaceholderColor : presentationData.theme.theme.chat.message.outgoing.mediaPlaceholderColor
|
||||||
}
|
}
|
||||||
if let wallpaper = media as? WallpaperPreviewMedia {
|
if let wallpaper = media as? WallpaperPreviewMedia {
|
||||||
if case let .file(_, patternColor, patternBottomColor, rotation, _, _) = wallpaper.content {
|
if case let .file(_, patternColor, patternBottomColor, rotation, _, _) = wallpaper.content {
|
||||||
@ -475,12 +560,12 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio
|
|||||||
replaceAnimatedStickerNode = true
|
replaceAnimatedStickerNode = true
|
||||||
}
|
}
|
||||||
if isSecretMedia {
|
if isSecretMedia {
|
||||||
updateImageSignal = { synchronousLoad in
|
updateImageSignal = { synchronousLoad, _ in
|
||||||
return chatSecretPhoto(account: context.account, photoReference: .message(message: MessageReference(message), media: image))
|
return chatSecretPhoto(account: context.account, photoReference: .message(message: MessageReference(message), media: image))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
updateImageSignal = { synchronousLoad in
|
updateImageSignal = { synchronousLoad, highQuality in
|
||||||
return chatMessagePhoto(postbox: context.account.postbox, photoReference: .message(message: MessageReference(message), media: image), synchronousLoad: synchronousLoad)
|
return chatMessagePhoto(postbox: context.account.postbox, photoReference: .message(message: MessageReference(message), media: image), synchronousLoad: synchronousLoad, highQuality: highQuality)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -505,7 +590,7 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio
|
|||||||
if hasCurrentAnimatedStickerNode {
|
if hasCurrentAnimatedStickerNode {
|
||||||
replaceAnimatedStickerNode = true
|
replaceAnimatedStickerNode = true
|
||||||
}
|
}
|
||||||
updateImageSignal = { synchronousLoad in
|
updateImageSignal = { synchronousLoad, _ in
|
||||||
return chatWebFileImage(account: context.account, file: image)
|
return chatWebFileImage(account: context.account, file: image)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -518,22 +603,22 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio
|
|||||||
})
|
})
|
||||||
} else if let file = media as? TelegramMediaFile {
|
} else if let file = media as? TelegramMediaFile {
|
||||||
if isSecretMedia {
|
if isSecretMedia {
|
||||||
updateImageSignal = { synchronousLoad in
|
updateImageSignal = { synchronousLoad, _ in
|
||||||
return chatSecretMessageVideo(account: context.account, videoReference: .message(message: MessageReference(message), media: file))
|
return chatSecretMessageVideo(account: context.account, videoReference: .message(message: MessageReference(message), media: file))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if file.isAnimatedSticker {
|
if file.isAnimatedSticker {
|
||||||
let dimensions = file.dimensions ?? PixelDimensions(width: 512, height: 512)
|
let dimensions = file.dimensions ?? PixelDimensions(width: 512, height: 512)
|
||||||
updateImageSignal = { synchronousLoad in
|
updateImageSignal = { synchronousLoad, _ in
|
||||||
return chatMessageAnimatedSticker(postbox: context.account.postbox, file: file, small: false, size: dimensions.cgSize.aspectFitted(CGSize(width: 400.0, height: 400.0)))
|
return chatMessageAnimatedSticker(postbox: context.account.postbox, file: file, small: false, size: dimensions.cgSize.aspectFitted(CGSize(width: 400.0, height: 400.0)))
|
||||||
}
|
}
|
||||||
} else if file.isSticker {
|
} else if file.isSticker {
|
||||||
updateImageSignal = { synchronousLoad in
|
updateImageSignal = { synchronousLoad, _ in
|
||||||
return chatMessageSticker(account: context.account, file: file, small: false)
|
return chatMessageSticker(account: context.account, file: file, small: false)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
onlyFullSizeVideoThumbnail = isSendingUpdated
|
onlyFullSizeVideoThumbnail = isSendingUpdated
|
||||||
updateImageSignal = { synchronousLoad in
|
updateImageSignal = { synchronousLoad, _ in
|
||||||
return mediaGridMessageVideo(postbox: context.account.postbox, videoReference: .message(message: MessageReference(message), media: file), onlyFullSize: currentMedia?.id?.namespace == Namespaces.Media.LocalFile, autoFetchFullSizeThumbnail: true)
|
return mediaGridMessageVideo(postbox: context.account.postbox, videoReference: .message(message: MessageReference(message), media: file), onlyFullSize: currentMedia?.id?.namespace == Namespaces.Media.LocalFile, autoFetchFullSizeThumbnail: true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -598,7 +683,7 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
} else if let wallpaper = media as? WallpaperPreviewMedia {
|
} else if let wallpaper = media as? WallpaperPreviewMedia {
|
||||||
updateImageSignal = { synchronousLoad in
|
updateImageSignal = { synchronousLoad, _ in
|
||||||
switch wallpaper.content {
|
switch wallpaper.content {
|
||||||
case let .file(file, _, _, _, isTheme, _):
|
case let .file(file, _, _, _, isTheme, _):
|
||||||
if isTheme {
|
if isTheme {
|
||||||
@ -692,27 +777,52 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio
|
|||||||
strongSelf.attributes = attributes
|
strongSelf.attributes = attributes
|
||||||
strongSelf.media = media
|
strongSelf.media = media
|
||||||
strongSelf.wideLayout = wideLayout
|
strongSelf.wideLayout = wideLayout
|
||||||
strongSelf.themeAndStrings = (theme, strings, dateTimeFormat.decimalSeparator)
|
strongSelf.themeAndStrings = (presentationData.theme.theme, presentationData.strings, dateTimeFormat.decimalSeparator)
|
||||||
strongSelf.sizeCalculation = sizeCalculation
|
strongSelf.sizeCalculation = sizeCalculation
|
||||||
strongSelf.automaticPlayback = automaticPlayback
|
strongSelf.automaticPlayback = automaticPlayback
|
||||||
strongSelf.automaticDownload = automaticDownload
|
strongSelf.automaticDownload = automaticDownload
|
||||||
|
|
||||||
if let previousArguments = strongSelf.currentImageArguments {
|
if let previousArguments = strongSelf.currentImageArguments {
|
||||||
if previousArguments.imageSize == arguments.imageSize {
|
if previousArguments.imageSize == arguments.imageSize {
|
||||||
strongSelf.imageNode.frame = imageFrame
|
strongSelf.pinchContainerNode.frame = imageFrame
|
||||||
|
strongSelf.pinchContainerNode.update(size: imageFrame.size, transition: .immediate)
|
||||||
|
strongSelf.imageNode.frame = CGRect(origin: CGPoint(), size: imageFrame.size)
|
||||||
} else {
|
} else {
|
||||||
transition.updateFrame(node: strongSelf.imageNode, frame: imageFrame)
|
transition.updateFrame(node: strongSelf.pinchContainerNode, frame: imageFrame)
|
||||||
|
transition.updateFrame(node: strongSelf.imageNode, frame: CGRect(origin: CGPoint(), size: imageFrame.size))
|
||||||
|
strongSelf.pinchContainerNode.update(size: imageFrame.size, transition: transition)
|
||||||
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
strongSelf.imageNode.frame = imageFrame
|
strongSelf.pinchContainerNode.frame = imageFrame
|
||||||
|
strongSelf.pinchContainerNode.update(size: imageFrame.size, transition: .immediate)
|
||||||
|
strongSelf.imageNode.frame = CGRect(origin: CGPoint(), size: imageFrame.size)
|
||||||
}
|
}
|
||||||
strongSelf.currentImageArguments = arguments
|
strongSelf.currentImageArguments = arguments
|
||||||
imageApply()
|
imageApply()
|
||||||
|
|
||||||
|
if let statusApply = statusApply {
|
||||||
|
if strongSelf.dateAndStatusNode.supernode == nil {
|
||||||
|
strongSelf.pinchContainerNode.contentNode.addSubnode(strongSelf.dateAndStatusNode)
|
||||||
|
}
|
||||||
|
var hasAnimation = true
|
||||||
|
if transition.isAnimated {
|
||||||
|
hasAnimation = false
|
||||||
|
}
|
||||||
|
statusApply(hasAnimation)
|
||||||
|
|
||||||
|
let dateAndStatusFrame = CGRect(origin: CGPoint(x: imageFrame.width - layoutConstants.image.statusInsets.right - statusSize.width, y: imageFrame.height - layoutConstants.image.statusInsets.bottom - statusSize.height), size: statusSize)
|
||||||
|
|
||||||
|
strongSelf.dateAndStatusNode.frame = dateAndStatusFrame
|
||||||
|
strongSelf.dateAndStatusNode.bounds = CGRect(origin: CGPoint(), size: dateAndStatusFrame.size)
|
||||||
|
} else if strongSelf.dateAndStatusNode.supernode != nil {
|
||||||
|
strongSelf.dateAndStatusNode.removeFromSupernode()
|
||||||
|
}
|
||||||
|
|
||||||
if let statusNode = strongSelf.statusNode {
|
if let statusNode = strongSelf.statusNode {
|
||||||
var statusFrame = statusNode.frame
|
var statusFrame = statusNode.frame
|
||||||
statusFrame.origin.x = floor(imageFrame.midX - statusFrame.width / 2.0)
|
statusFrame.origin.x = floor(imageFrame.width / 2.0 - statusFrame.width / 2.0)
|
||||||
statusFrame.origin.y = floor(imageFrame.midY - statusFrame.height / 2.0)
|
statusFrame.origin.y = floor(imageFrame.height / 2.0 - statusFrame.height / 2.0)
|
||||||
statusNode.frame = statusFrame
|
statusNode.frame = statusFrame
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -776,7 +886,7 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio
|
|||||||
let dimensions = updatedAnimatedStickerFile.dimensions ?? PixelDimensions(width: 512, height: 512)
|
let dimensions = updatedAnimatedStickerFile.dimensions ?? PixelDimensions(width: 512, height: 512)
|
||||||
let fittedDimensions = dimensions.cgSize.aspectFitted(CGSize(width: 384.0, height: 384.0))
|
let fittedDimensions = dimensions.cgSize.aspectFitted(CGSize(width: 384.0, height: 384.0))
|
||||||
animatedStickerNode.setup(source: AnimatedStickerResourceSource(account: context.account, resource: updatedAnimatedStickerFile.resource), width: Int(fittedDimensions.width), height: Int(fittedDimensions.height), mode: .cached)
|
animatedStickerNode.setup(source: AnimatedStickerResourceSource(account: context.account, resource: updatedAnimatedStickerFile.resource), width: Int(fittedDimensions.width), height: Int(fittedDimensions.height), mode: .cached)
|
||||||
strongSelf.insertSubnode(animatedStickerNode, aboveSubnode: strongSelf.imageNode)
|
strongSelf.pinchContainerNode.contentNode.insertSubnode(animatedStickerNode, aboveSubnode: strongSelf.imageNode)
|
||||||
animatedStickerNode.visibility = strongSelf.visibility
|
animatedStickerNode.visibility = strongSelf.visibility
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -807,7 +917,8 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let updateImageSignal = updateImageSignal {
|
if let updateImageSignal = updateImageSignal {
|
||||||
strongSelf.imageNode.setSignal(updateImageSignal(synchronousLoads), attemptSynchronously: synchronousLoads)
|
strongSelf.imageNode.setSignal(updateImageSignal(synchronousLoads, false), attemptSynchronously: synchronousLoads)
|
||||||
|
strongSelf.currentHighQualityImageSignal = updateImageSignal(false, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
if let _ = secretBeginTimeAndTimeout {
|
if let _ = secretBeginTimeAndTimeout {
|
||||||
@ -837,7 +948,7 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio
|
|||||||
|> deliverOnMainQueue).start(next: { [weak strongSelf] status in
|
|> deliverOnMainQueue).start(next: { [weak strongSelf] status in
|
||||||
displayLinkDispatcher.dispatch {
|
displayLinkDispatcher.dispatch {
|
||||||
if let strongSelf = strongSelf, let videoNode = strongSelf.videoNode {
|
if let strongSelf = strongSelf, let videoNode = strongSelf.videoNode {
|
||||||
strongSelf.insertSubnode(videoNode, aboveSubnode: strongSelf.imageNode)
|
strongSelf.pinchContainerNode.contentNode.insertSubnode(videoNode, aboveSubnode: strongSelf.imageNode)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
@ -997,10 +1108,10 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio
|
|||||||
if progressRequired {
|
if progressRequired {
|
||||||
if self.statusNode == nil {
|
if self.statusNode == nil {
|
||||||
let statusNode = RadialStatusNode(backgroundNodeColor: theme.chat.message.mediaOverlayControlColors.fillColor)
|
let statusNode = RadialStatusNode(backgroundNodeColor: theme.chat.message.mediaOverlayControlColors.fillColor)
|
||||||
let imagePosition = self.imageNode.position
|
let imageSize = self.imageNode.bounds.size
|
||||||
statusNode.frame = CGRect(origin: CGPoint(x: floor(imagePosition.x - radialStatusSize / 2.0), y: floor(imagePosition.y - radialStatusSize / 2.0)), size: CGSize(width: radialStatusSize, height: radialStatusSize))
|
statusNode.frame = CGRect(origin: CGPoint(x: floor(imageSize.width / 2.0 - radialStatusSize / 2.0), y: floor(imageSize.height / 2.0 - radialStatusSize / 2.0)), size: CGSize(width: radialStatusSize, height: radialStatusSize))
|
||||||
self.statusNode = statusNode
|
self.statusNode = statusNode
|
||||||
self.addSubnode(statusNode)
|
self.pinchContainerNode.contentNode.addSubnode(statusNode)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if let statusNode = self.statusNode {
|
if let statusNode = self.statusNode {
|
||||||
@ -1275,7 +1386,7 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.badgeNode = badgeNode
|
self.badgeNode = badgeNode
|
||||||
self.addSubnode(badgeNode)
|
self.pinchContainerNode.contentNode.addSubnode(badgeNode)
|
||||||
|
|
||||||
animated = false
|
animated = false
|
||||||
}
|
}
|
||||||
@ -1300,12 +1411,12 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static func asyncLayout(_ node: ChatMessageInteractiveMediaNode?) -> (_ context: AccountContext, _ theme: PresentationTheme, _ strings: PresentationStrings, _ dateTimeFormat: PresentationDateTimeFormat, _ message: Message, _ attributes: ChatMessageEntryAttributes, _ media: Media, _ automaticDownload: InteractiveMediaNodeAutodownloadMode, _ peerType: MediaAutoDownloadPeerType, _ sizeCalculation: InteractiveMediaNodeSizeCalculation, _ layoutConstants: ChatMessageItemLayoutConstants, _ contentMode: InteractiveMediaNodeContentMode) -> (CGSize, CGFloat, (CGSize, Bool, Bool, ImageCorners) -> (CGFloat, (CGFloat) -> (CGSize, (ContainedViewLayoutTransition, Bool) -> ChatMessageInteractiveMediaNode))) {
|
static func asyncLayout(_ node: ChatMessageInteractiveMediaNode?) -> (_ context: AccountContext, _ presentationData: ChatPresentationData, _ dateTimeFormat: PresentationDateTimeFormat, _ message: Message, _ attributes: ChatMessageEntryAttributes, _ media: Media, _ dateAndStatus: ChatMessageDateAndStatus?, _ automaticDownload: InteractiveMediaNodeAutodownloadMode, _ peerType: MediaAutoDownloadPeerType, _ sizeCalculation: InteractiveMediaNodeSizeCalculation, _ layoutConstants: ChatMessageItemLayoutConstants, _ contentMode: InteractiveMediaNodeContentMode) -> (CGSize, CGFloat, (CGSize, Bool, Bool, ImageCorners) -> (CGFloat, (CGFloat) -> (CGSize, (ContainedViewLayoutTransition, Bool) -> ChatMessageInteractiveMediaNode))) {
|
||||||
let currentAsyncLayout = node?.asyncLayout()
|
let currentAsyncLayout = node?.asyncLayout()
|
||||||
|
|
||||||
return { context, theme, strings, dateTimeFormat, message, attributes, media, automaticDownload, peerType, sizeCalculation, layoutConstants, contentMode in
|
return { context, presentationData, dateTimeFormat, message, attributes, media, dateAndStatus, automaticDownload, peerType, sizeCalculation, layoutConstants, contentMode in
|
||||||
var imageNode: ChatMessageInteractiveMediaNode
|
var imageNode: ChatMessageInteractiveMediaNode
|
||||||
var imageLayout: (_ context: AccountContext, _ theme: PresentationTheme, _ strings: PresentationStrings, _ dateTimeFormat: PresentationDateTimeFormat, _ message: Message, _ attributes: ChatMessageEntryAttributes, _ media: Media, _ automaticDownload: InteractiveMediaNodeAutodownloadMode, _ peerType: MediaAutoDownloadPeerType, _ sizeCalculation: InteractiveMediaNodeSizeCalculation, _ layoutConstants: ChatMessageItemLayoutConstants, _ contentMode: InteractiveMediaNodeContentMode) -> (CGSize, CGFloat, (CGSize, Bool, Bool, ImageCorners) -> (CGFloat, (CGFloat) -> (CGSize, (ContainedViewLayoutTransition, Bool) -> Void)))
|
var imageLayout: (_ context: AccountContext, _ presentationData: ChatPresentationData, _ dateTimeFormat: PresentationDateTimeFormat, _ message: Message, _ attributes: ChatMessageEntryAttributes, _ media: Media, _ dateAndStatus: ChatMessageDateAndStatus?, _ automaticDownload: InteractiveMediaNodeAutodownloadMode, _ peerType: MediaAutoDownloadPeerType, _ sizeCalculation: InteractiveMediaNodeSizeCalculation, _ layoutConstants: ChatMessageItemLayoutConstants, _ contentMode: InteractiveMediaNodeContentMode) -> (CGSize, CGFloat, (CGSize, Bool, Bool, ImageCorners) -> (CGFloat, (CGFloat) -> (CGSize, (ContainedViewLayoutTransition, Bool) -> Void)))
|
||||||
|
|
||||||
if let node = node, let currentAsyncLayout = currentAsyncLayout {
|
if let node = node, let currentAsyncLayout = currentAsyncLayout {
|
||||||
imageNode = node
|
imageNode = node
|
||||||
@ -1315,7 +1426,7 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio
|
|||||||
imageLayout = imageNode.asyncLayout()
|
imageLayout = imageNode.asyncLayout()
|
||||||
}
|
}
|
||||||
|
|
||||||
let (unboundSize, initialWidth, continueLayout) = imageLayout(context, theme, strings, dateTimeFormat, message, attributes, media, automaticDownload, peerType, sizeCalculation, layoutConstants, contentMode)
|
let (unboundSize, initialWidth, continueLayout) = imageLayout(context, presentationData, dateTimeFormat, message, attributes, media, dateAndStatus, automaticDownload, peerType, sizeCalculation, layoutConstants, contentMode)
|
||||||
|
|
||||||
return (unboundSize, initialWidth, { constrainedSize, automaticPlayback, wideLayout, corners in
|
return (unboundSize, initialWidth, { constrainedSize, automaticPlayback, wideLayout, corners in
|
||||||
let (finalWidth, finalLayout) = continueLayout(constrainedSize, automaticPlayback, wideLayout, corners)
|
let (finalWidth, finalLayout) = continueLayout(constrainedSize, automaticPlayback, wideLayout, corners)
|
||||||
|
|||||||
@ -38,7 +38,7 @@ final class ChatMessageInvoiceBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
override func asyncLayoutContent() -> (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool) -> Void))) {
|
override func asyncLayoutContent() -> (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool) -> Void))) {
|
||||||
let contentNodeLayout = self.contentNode.asyncLayout()
|
let contentNodeLayout = self.contentNode.asyncLayout()
|
||||||
|
|
||||||
return { item, layoutConstants, _, _, constrainedSize in
|
return { item, layoutConstants, preparePosition, _, constrainedSize in
|
||||||
var invoice: TelegramMediaInvoice?
|
var invoice: TelegramMediaInvoice?
|
||||||
for media in item.message.media {
|
for media in item.message.media {
|
||||||
if let media = media as? TelegramMediaInvoice {
|
if let media = media as? TelegramMediaInvoice {
|
||||||
@ -74,7 +74,7 @@ final class ChatMessageInvoiceBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let (initialWidth, continueLayout) = contentNodeLayout(item.presentationData, automaticDownloadSettings, item.associatedData, item.attributes, item.context, item.controllerInteraction, item.message, item.read, item.chatLocation, title, subtitle, text, nil, mediaAndFlags, nil, nil, nil, false, layoutConstants, constrainedSize)
|
let (initialWidth, continueLayout) = contentNodeLayout(item.presentationData, automaticDownloadSettings, item.associatedData, item.attributes, item.context, item.controllerInteraction, item.message, item.read, item.chatLocation, title, subtitle, text, nil, mediaAndFlags, nil, nil, nil, false, layoutConstants, preparePosition, constrainedSize)
|
||||||
|
|
||||||
let contentProperties = ChatMessageBubbleContentProperties(hidesSimpleAuthorHeader: false, headerSpacing: 8.0, hidesBackground: .never, forceFullCorners: false, forceAlignment: .none)
|
let contentProperties = ChatMessageBubbleContentProperties(hidesSimpleAuthorHeader: false, headerSpacing: 8.0, hidesBackground: .never, forceFullCorners: false, forceAlignment: .none)
|
||||||
|
|
||||||
|
|||||||
@ -17,7 +17,6 @@ class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private let interactiveImageNode: ChatMessageInteractiveMediaNode
|
private let interactiveImageNode: ChatMessageInteractiveMediaNode
|
||||||
private let dateAndStatusNode: ChatMessageDateAndStatusNode
|
|
||||||
private var selectionNode: GridMessageSelectionNode?
|
private var selectionNode: GridMessageSelectionNode?
|
||||||
private var highlightedState: Bool = false
|
private var highlightedState: Bool = false
|
||||||
|
|
||||||
@ -32,7 +31,6 @@ class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
|
|
||||||
required init() {
|
required init() {
|
||||||
self.interactiveImageNode = ChatMessageInteractiveMediaNode()
|
self.interactiveImageNode = ChatMessageInteractiveMediaNode()
|
||||||
self.dateAndStatusNode = ChatMessageDateAndStatusNode()
|
|
||||||
|
|
||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
@ -54,6 +52,13 @@ class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.interactiveImageNode.activatePinch = { [weak self] sourceNode in
|
||||||
|
guard let strongSelf = self, let _ = strongSelf.item else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
strongSelf.item?.controllerInteraction.activateMessagePinch(sourceNode)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
required init?(coder aDecoder: NSCoder) {
|
required init?(coder aDecoder: NSCoder) {
|
||||||
@ -62,7 +67,6 @@ class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
|
|
||||||
override func asyncLayoutContent() -> (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool) -> Void))) {
|
override func asyncLayoutContent() -> (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool) -> Void))) {
|
||||||
let interactiveImageLayout = self.interactiveImageNode.asyncLayout()
|
let interactiveImageLayout = self.interactiveImageNode.asyncLayout()
|
||||||
let statusLayout = self.dateAndStatusNode.asyncLayout()
|
|
||||||
|
|
||||||
return { item, layoutConstants, preparePosition, selection, constrainedSize in
|
return { item, layoutConstants, preparePosition, selection, constrainedSize in
|
||||||
var selectedMedia: Media?
|
var selectedMedia: Media?
|
||||||
@ -143,7 +147,80 @@ class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
sizeCalculation = .unconstrained
|
sizeCalculation = .unconstrained
|
||||||
}
|
}
|
||||||
|
|
||||||
let (unboundSize, initialWidth, refineLayout) = interactiveImageLayout(item.context, item.presentationData.theme.theme, item.presentationData.strings, item.presentationData.dateTimeFormat, item.message, item.attributes, selectedMedia!, automaticDownload, item.associatedData.automaticDownloadPeerType, sizeCalculation, layoutConstants, contentMode)
|
var edited = false
|
||||||
|
if item.attributes.updatingMedia != nil {
|
||||||
|
edited = true
|
||||||
|
}
|
||||||
|
var viewCount: Int?
|
||||||
|
var dateReplies = 0
|
||||||
|
for attribute in item.message.attributes {
|
||||||
|
if let attribute = attribute as? EditedMessageAttribute {
|
||||||
|
if case .mosaic = preparePosition {
|
||||||
|
} else {
|
||||||
|
edited = !attribute.isHidden
|
||||||
|
}
|
||||||
|
} else if let attribute = attribute as? ViewCountMessageAttribute {
|
||||||
|
viewCount = attribute.count
|
||||||
|
} else if let attribute = attribute as? ReplyThreadMessageAttribute, case .peer = item.chatLocation {
|
||||||
|
if let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .group = channel.info {
|
||||||
|
dateReplies = Int(attribute.count)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var dateReactions: [MessageReaction] = []
|
||||||
|
var dateReactionCount = 0
|
||||||
|
if let reactionsAttribute = mergedMessageReactions(attributes: item.message.attributes), !reactionsAttribute.reactions.isEmpty {
|
||||||
|
for reaction in reactionsAttribute.reactions {
|
||||||
|
if reaction.isSelected {
|
||||||
|
dateReactions.insert(reaction, at: 0)
|
||||||
|
} else {
|
||||||
|
dateReactions.append(reaction)
|
||||||
|
}
|
||||||
|
dateReactionCount += Int(reaction.count)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let dateText = stringForMessageTimestampStatus(accountPeerId: item.context.account.peerId, message: item.message, dateTimeFormat: item.presentationData.dateTimeFormat, nameDisplayOrder: item.presentationData.nameDisplayOrder, strings: item.presentationData.strings, reactionCount: dateReactionCount)
|
||||||
|
|
||||||
|
let statusType: ChatMessageDateAndStatusType?
|
||||||
|
switch preparePosition {
|
||||||
|
case .linear(_, .None), .linear(_, .Neighbour(true, _, _)):
|
||||||
|
if item.message.effectivelyIncoming(item.context.account.peerId) {
|
||||||
|
statusType = .ImageIncoming
|
||||||
|
} else {
|
||||||
|
if item.message.flags.contains(.Failed) {
|
||||||
|
statusType = .ImageOutgoing(.Failed)
|
||||||
|
} else if (item.message.flags.isSending && !item.message.isSentOrAcknowledged) || item.attributes.updatingMedia != nil {
|
||||||
|
statusType = .ImageOutgoing(.Sending)
|
||||||
|
} else {
|
||||||
|
statusType = .ImageOutgoing(.Sent(read: item.read))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case .mosaic:
|
||||||
|
statusType = nil
|
||||||
|
default:
|
||||||
|
statusType = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var isReplyThread = false
|
||||||
|
if case .replyThread = item.chatLocation {
|
||||||
|
isReplyThread = true
|
||||||
|
}
|
||||||
|
|
||||||
|
let dateAndStatus = statusType.flatMap { statusType -> ChatMessageDateAndStatus in
|
||||||
|
ChatMessageDateAndStatus(
|
||||||
|
type: statusType,
|
||||||
|
edited: edited,
|
||||||
|
viewCount: viewCount,
|
||||||
|
dateReplies: dateReplies,
|
||||||
|
dateReactions: dateReactions,
|
||||||
|
isPinned: item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && !isReplyThread,
|
||||||
|
dateText: dateText
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
let (unboundSize, initialWidth, refineLayout) = interactiveImageLayout(item.context, item.presentationData, item.presentationData.dateTimeFormat, item.message, item.attributes, selectedMedia!, dateAndStatus, automaticDownload, item.associatedData.automaticDownloadPeerType, sizeCalculation, layoutConstants, contentMode)
|
||||||
|
|
||||||
let forceFullCorners = false
|
let forceFullCorners = false
|
||||||
let contentProperties = ChatMessageBubbleContentProperties(hidesSimpleAuthorHeader: true, headerSpacing: 7.0, hidesBackground: .emptyWallpaper, forceFullCorners: forceFullCorners, forceAlignment: .none)
|
let contentProperties = ChatMessageBubbleContentProperties(hidesSimpleAuthorHeader: true, headerSpacing: 7.0, hidesBackground: .emptyWallpaper, forceFullCorners: forceFullCorners, forceAlignment: .none)
|
||||||
@ -169,82 +246,9 @@ class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
return (refinedWidth + bubbleInsets.left + bubbleInsets.right, { boundingWidth in
|
return (refinedWidth + bubbleInsets.left + bubbleInsets.right, { boundingWidth in
|
||||||
let (imageSize, imageApply) = finishLayout(boundingWidth - bubbleInsets.left - bubbleInsets.right)
|
let (imageSize, imageApply) = finishLayout(boundingWidth - bubbleInsets.left - bubbleInsets.right)
|
||||||
|
|
||||||
var edited = false
|
|
||||||
if item.attributes.updatingMedia != nil {
|
|
||||||
edited = true
|
|
||||||
}
|
|
||||||
var viewCount: Int?
|
|
||||||
var dateReplies = 0
|
|
||||||
for attribute in item.message.attributes {
|
|
||||||
if let attribute = attribute as? EditedMessageAttribute {
|
|
||||||
if case .mosaic = preparePosition {
|
|
||||||
} else {
|
|
||||||
edited = !attribute.isHidden
|
|
||||||
}
|
|
||||||
} else if let attribute = attribute as? ViewCountMessageAttribute {
|
|
||||||
viewCount = attribute.count
|
|
||||||
} else if let attribute = attribute as? ReplyThreadMessageAttribute, case .peer = item.chatLocation {
|
|
||||||
if let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .group = channel.info {
|
|
||||||
dateReplies = Int(attribute.count)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var dateReactions: [MessageReaction] = []
|
|
||||||
var dateReactionCount = 0
|
|
||||||
if let reactionsAttribute = mergedMessageReactions(attributes: item.message.attributes), !reactionsAttribute.reactions.isEmpty {
|
|
||||||
for reaction in reactionsAttribute.reactions {
|
|
||||||
if reaction.isSelected {
|
|
||||||
dateReactions.insert(reaction, at: 0)
|
|
||||||
} else {
|
|
||||||
dateReactions.append(reaction)
|
|
||||||
}
|
|
||||||
dateReactionCount += Int(reaction.count)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let dateText = stringForMessageTimestampStatus(accountPeerId: item.context.account.peerId, message: item.message, dateTimeFormat: item.presentationData.dateTimeFormat, nameDisplayOrder: item.presentationData.nameDisplayOrder, strings: item.presentationData.strings, reactionCount: dateReactionCount)
|
|
||||||
|
|
||||||
let statusType: ChatMessageDateAndStatusType?
|
|
||||||
switch position {
|
|
||||||
case .linear(_, .None), .linear(_, .Neighbour(true, _, _)):
|
|
||||||
if item.message.effectivelyIncoming(item.context.account.peerId) {
|
|
||||||
statusType = .ImageIncoming
|
|
||||||
} else {
|
|
||||||
if item.message.flags.contains(.Failed) {
|
|
||||||
statusType = .ImageOutgoing(.Failed)
|
|
||||||
} else if (item.message.flags.isSending && !item.message.isSentOrAcknowledged) || item.attributes.updatingMedia != nil {
|
|
||||||
statusType = .ImageOutgoing(.Sending)
|
|
||||||
} else {
|
|
||||||
statusType = .ImageOutgoing(.Sent(read: item.read))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case .mosaic:
|
|
||||||
statusType = nil
|
|
||||||
default:
|
|
||||||
statusType = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
let imageLayoutSize = CGSize(width: imageSize.width + bubbleInsets.left + bubbleInsets.right, height: imageSize.height + bubbleInsets.top + bubbleInsets.bottom)
|
let imageLayoutSize = CGSize(width: imageSize.width + bubbleInsets.left + bubbleInsets.right, height: imageSize.height + bubbleInsets.top + bubbleInsets.bottom)
|
||||||
|
|
||||||
var statusSize = CGSize()
|
let layoutWidth = imageLayoutSize.width
|
||||||
var statusApply: ((Bool) -> Void)?
|
|
||||||
|
|
||||||
if let statusType = statusType {
|
|
||||||
var isReplyThread = false
|
|
||||||
if case .replyThread = item.chatLocation {
|
|
||||||
isReplyThread = true
|
|
||||||
}
|
|
||||||
|
|
||||||
let (size, apply) = statusLayout(item.context, item.presentationData, edited, viewCount, dateText, statusType, CGSize(width: imageSize.width - 30.0, height: CGFloat.greatestFiniteMagnitude), dateReactions, dateReplies, item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && !isReplyThread, item.message.isSelfExpiring)
|
|
||||||
statusSize = size
|
|
||||||
statusApply = apply
|
|
||||||
}
|
|
||||||
|
|
||||||
var layoutWidth = imageLayoutSize.width
|
|
||||||
if case .constrained = sizeCalculation {
|
|
||||||
layoutWidth = max(layoutWidth, statusSize.width + bubbleInsets.left + bubbleInsets.right + layoutConstants.image.statusInsets.left + layoutConstants.image.statusInsets.right)
|
|
||||||
}
|
|
||||||
|
|
||||||
let layoutSize = CGSize(width: layoutWidth, height: imageLayoutSize.height)
|
let layoutSize = CGSize(width: layoutWidth, height: imageLayoutSize.height)
|
||||||
|
|
||||||
@ -262,24 +266,6 @@ class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
|
|
||||||
transition.updateFrame(node: strongSelf.interactiveImageNode, frame: imageFrame)
|
transition.updateFrame(node: strongSelf.interactiveImageNode, frame: imageFrame)
|
||||||
|
|
||||||
if let statusApply = statusApply {
|
|
||||||
if strongSelf.dateAndStatusNode.supernode == nil {
|
|
||||||
strongSelf.interactiveImageNode.addSubnode(strongSelf.dateAndStatusNode)
|
|
||||||
}
|
|
||||||
var hasAnimation = true
|
|
||||||
if case .None = animation {
|
|
||||||
hasAnimation = false
|
|
||||||
}
|
|
||||||
statusApply(hasAnimation)
|
|
||||||
|
|
||||||
let dateAndStatusFrame = CGRect(origin: CGPoint(x: layoutSize.width - bubbleInsets.right - layoutConstants.image.statusInsets.right - statusSize.width, y: layoutSize.height - bubbleInsets.bottom - layoutConstants.image.statusInsets.bottom - statusSize.height), size: statusSize)
|
|
||||||
|
|
||||||
strongSelf.dateAndStatusNode.frame = dateAndStatusFrame
|
|
||||||
strongSelf.dateAndStatusNode.bounds = CGRect(origin: CGPoint(), size: dateAndStatusFrame.size)
|
|
||||||
} else if strongSelf.dateAndStatusNode.supernode != nil {
|
|
||||||
strongSelf.dateAndStatusNode.removeFromSupernode()
|
|
||||||
}
|
|
||||||
|
|
||||||
imageApply(transition, synchronousLoads)
|
imageApply(transition, synchronousLoads)
|
||||||
|
|
||||||
if let selection = selection {
|
if let selection = selection {
|
||||||
@ -310,14 +296,14 @@ class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let forwardInfo = item.message.forwardInfo, forwardInfo.flags.contains(.isImported) {
|
if let forwardInfo = item.message.forwardInfo, forwardInfo.flags.contains(.isImported) {
|
||||||
strongSelf.dateAndStatusNode.pressed = {
|
strongSelf.interactiveImageNode.dateAndStatusNode.pressed = {
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
item.controllerInteraction.displayImportedMessageTooltip(strongSelf.dateAndStatusNode)
|
item.controllerInteraction.displayImportedMessageTooltip(strongSelf.interactiveImageNode.dateAndStatusNode)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
strongSelf.dateAndStatusNode.pressed = nil
|
strongSelf.interactiveImageNode.dateAndStatusNode.pressed = nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -356,7 +342,7 @@ class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
self.interactiveImageNode.isHidden = mediaHidden
|
self.interactiveImageNode.isHidden = mediaHidden
|
||||||
self.interactiveImageNode.updateIsHidden(mediaHidden)
|
self.interactiveImageNode.updateIsHidden(mediaHidden)
|
||||||
|
|
||||||
if let automaticPlayback = self.automaticPlayback {
|
/*if let automaticPlayback = self.automaticPlayback {
|
||||||
if !automaticPlayback {
|
if !automaticPlayback {
|
||||||
self.dateAndStatusNode.isHidden = false
|
self.dateAndStatusNode.isHidden = false
|
||||||
} else if self.dateAndStatusNode.isHidden != mediaHidden {
|
} else if self.dateAndStatusNode.isHidden != mediaHidden {
|
||||||
@ -367,7 +353,7 @@ class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
self.dateAndStatusNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
self.dateAndStatusNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}*/
|
||||||
|
|
||||||
return mediaHidden
|
return mediaHidden
|
||||||
}
|
}
|
||||||
@ -416,9 +402,9 @@ class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override func reactionTargetNode(value: String) -> (ASDisplayNode, ASDisplayNode)? {
|
override func reactionTargetNode(value: String) -> (ASDisplayNode, ASDisplayNode)? {
|
||||||
if !self.dateAndStatusNode.isHidden {
|
/*if !self.dateAndStatusNode.isHidden {
|
||||||
return self.dateAndStatusNode.reactionNode(value: value)
|
return self.dateAndStatusNode.reactionNode(value: value)
|
||||||
}
|
}*/
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -21,6 +21,7 @@ private let inlineBotNameFont = nameFont
|
|||||||
class ChatMessageStickerItemNode: ChatMessageItemView {
|
class ChatMessageStickerItemNode: ChatMessageItemView {
|
||||||
private let contextSourceNode: ContextExtractedContentContainingNode
|
private let contextSourceNode: ContextExtractedContentContainingNode
|
||||||
private let containerNode: ContextControllerSourceNode
|
private let containerNode: ContextControllerSourceNode
|
||||||
|
private let pinchContainerNode: PinchSourceContainerNode
|
||||||
let imageNode: TransformImageNode
|
let imageNode: TransformImageNode
|
||||||
private var placeholderNode: StickerShimmerEffectNode
|
private var placeholderNode: StickerShimmerEffectNode
|
||||||
var textNode: TextNode?
|
var textNode: TextNode?
|
||||||
@ -53,6 +54,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
|
|||||||
required init() {
|
required init() {
|
||||||
self.contextSourceNode = ContextExtractedContentContainingNode()
|
self.contextSourceNode = ContextExtractedContentContainingNode()
|
||||||
self.containerNode = ContextControllerSourceNode()
|
self.containerNode = ContextControllerSourceNode()
|
||||||
|
self.pinchContainerNode = PinchSourceContainerNode()
|
||||||
self.imageNode = TransformImageNode()
|
self.imageNode = TransformImageNode()
|
||||||
self.placeholderNode = StickerShimmerEffectNode()
|
self.placeholderNode = StickerShimmerEffectNode()
|
||||||
self.placeholderNode.isUserInteractionEnabled = false
|
self.placeholderNode.isUserInteractionEnabled = false
|
||||||
@ -119,7 +121,8 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
|
|||||||
self.imageNode.displaysAsynchronously = false
|
self.imageNode.displaysAsynchronously = false
|
||||||
self.containerNode.addSubnode(self.contextSourceNode)
|
self.containerNode.addSubnode(self.contextSourceNode)
|
||||||
self.containerNode.targetNodeForActivationProgress = self.contextSourceNode.contentNode
|
self.containerNode.targetNodeForActivationProgress = self.contextSourceNode.contentNode
|
||||||
self.addSubnode(self.containerNode)
|
self.pinchContainerNode.contentNode.addSubnode(self.containerNode)
|
||||||
|
self.addSubnode(self.pinchContainerNode)
|
||||||
self.contextSourceNode.contentNode.addSubnode(self.placeholderNode)
|
self.contextSourceNode.contentNode.addSubnode(self.placeholderNode)
|
||||||
self.contextSourceNode.contentNode.addSubnode(self.imageNode)
|
self.contextSourceNode.contentNode.addSubnode(self.imageNode)
|
||||||
self.contextSourceNode.contentNode.addSubnode(self.dateAndStatusNode)
|
self.contextSourceNode.contentNode.addSubnode(self.dateAndStatusNode)
|
||||||
@ -135,6 +138,23 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
|
|||||||
}
|
}
|
||||||
item.controllerInteraction.openMessageReactions(item.message.id)
|
item.controllerInteraction.openMessageReactions(item.message.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.pinchContainerNode.activate = { [weak self] sourceNode in
|
||||||
|
guard let strongSelf = self, let item = strongSelf.item else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
item.controllerInteraction.activateMessagePinch(sourceNode)
|
||||||
|
}
|
||||||
|
|
||||||
|
self.pinchContainerNode.scaleUpdated = { [weak self] scale, transition in
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let factor: CGFloat = max(0.0, min(1.0, (scale - 1.0) * 8.0))
|
||||||
|
|
||||||
|
transition.updateAlpha(node: strongSelf.dateAndStatusNode, alpha: 1.0 - factor)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
required init?(coder aDecoder: NSCoder) {
|
required init?(coder aDecoder: NSCoder) {
|
||||||
@ -655,9 +675,12 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
|
|||||||
|
|
||||||
strongSelf.messageAccessibilityArea.frame = CGRect(origin: CGPoint(), size: layoutSize)
|
strongSelf.messageAccessibilityArea.frame = CGRect(origin: CGPoint(), size: layoutSize)
|
||||||
strongSelf.containerNode.frame = CGRect(origin: CGPoint(), size: layoutSize)
|
strongSelf.containerNode.frame = CGRect(origin: CGPoint(), size: layoutSize)
|
||||||
|
strongSelf.pinchContainerNode.frame = CGRect(origin: CGPoint(), size: layoutSize)
|
||||||
|
strongSelf.pinchContainerNode.update(size: layoutSize, transition: .immediate)
|
||||||
strongSelf.contextSourceNode.frame = CGRect(origin: CGPoint(), size: layoutSize)
|
strongSelf.contextSourceNode.frame = CGRect(origin: CGPoint(), size: layoutSize)
|
||||||
strongSelf.contextSourceNode.contentNode.frame = CGRect(origin: CGPoint(), size: layoutSize)
|
strongSelf.contextSourceNode.contentNode.frame = CGRect(origin: CGPoint(), size: layoutSize)
|
||||||
strongSelf.contextSourceNode.contentRect = strongSelf.imageNode.frame
|
strongSelf.contextSourceNode.contentRect = strongSelf.imageNode.frame
|
||||||
|
strongSelf.pinchContainerNode.contentRect = strongSelf.imageNode.frame
|
||||||
strongSelf.containerNode.targetNodeForActivationProgressContentRect = strongSelf.contextSourceNode.contentRect
|
strongSelf.containerNode.targetNodeForActivationProgressContentRect = strongSelf.contextSourceNode.contentRect
|
||||||
|
|
||||||
dateAndStatusApply(false)
|
dateAndStatusApply(false)
|
||||||
|
|||||||
@ -86,7 +86,7 @@ final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
override func asyncLayoutContent() -> (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool) -> Void))) {
|
override func asyncLayoutContent() -> (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool) -> Void))) {
|
||||||
let contentNodeLayout = self.contentNode.asyncLayout()
|
let contentNodeLayout = self.contentNode.asyncLayout()
|
||||||
|
|
||||||
return { item, layoutConstants, _, _, constrainedSize in
|
return { item, layoutConstants, preparePosition, _, constrainedSize in
|
||||||
var webPage: TelegramMediaWebpage?
|
var webPage: TelegramMediaWebpage?
|
||||||
var webPageContent: TelegramMediaWebpageLoadedContent?
|
var webPageContent: TelegramMediaWebpageLoadedContent?
|
||||||
for media in item.message.media {
|
for media in item.message.media {
|
||||||
@ -301,7 +301,7 @@ final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let (initialWidth, continueLayout) = contentNodeLayout(item.presentationData, item.controllerInteraction.automaticMediaDownloadSettings, item.associatedData, item.attributes, item.context, item.controllerInteraction, item.message, item.read, item.chatLocation, title, subtitle, text, entities, mediaAndFlags, badge, actionIcon, actionTitle, true, layoutConstants, constrainedSize)
|
let (initialWidth, continueLayout) = contentNodeLayout(item.presentationData, item.controllerInteraction.automaticMediaDownloadSettings, item.associatedData, item.attributes, item.context, item.controllerInteraction, item.message, item.read, item.chatLocation, title, subtitle, text, entities, mediaAndFlags, badge, actionIcon, actionTitle, true, layoutConstants, preparePosition, constrainedSize)
|
||||||
|
|
||||||
let contentProperties = ChatMessageBubbleContentProperties(hidesSimpleAuthorHeader: false, headerSpacing: 8.0, hidesBackground: .never, forceFullCorners: false, forceAlignment: .none)
|
let contentProperties = ChatMessageBubbleContentProperties(hidesSimpleAuthorHeader: false, headerSpacing: 8.0, hidesBackground: .never, forceFullCorners: false, forceAlignment: .none)
|
||||||
|
|
||||||
|
|||||||
@ -260,6 +260,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
|
|||||||
self?.openPeerMention(name)
|
self?.openPeerMention(name)
|
||||||
}, openMessageContextMenu: { [weak self] message, selectAll, node, frame, _ in
|
}, openMessageContextMenu: { [weak self] message, selectAll, node, frame, _ in
|
||||||
self?.openMessageContextMenu(message: message, selectAll: selectAll, node: node, frame: frame)
|
self?.openMessageContextMenu(message: message, selectAll: selectAll, node: node, frame: frame)
|
||||||
|
}, activateMessagePinch: { _ in
|
||||||
}, openMessageContextActions: { _, _, _, _ in
|
}, openMessageContextActions: { _, _, _, _ in
|
||||||
}, navigateToMessage: { _, _ in }, navigateToMessageStandalone: { _ in
|
}, navigateToMessage: { _, _ in }, navigateToMessageStandalone: { _ in
|
||||||
}, tapMessage: nil, clickThroughMessage: { }, toggleMessagesSelection: { _, _ in }, sendCurrentMessage: { _ in }, sendMessage: { _ in }, sendSticker: { _, _, _, _, _ in return false }, sendGif: { _, _, _ in return false }, sendBotContextResultAsGif: { _, _, _, _ in return false }, requestMessageActionCallback: { _, _, _, _ in }, requestMessageActionUrlAuth: { _, _ in }, activateSwitchInline: { _, _ in }, openUrl: { [weak self] url, _, _, _ in
|
}, tapMessage: nil, clickThroughMessage: { }, toggleMessagesSelection: { _, _ in }, sendCurrentMessage: { _ in }, sendMessage: { _ in }, sendSticker: { _, _, _, _, _ in return false }, sendGif: { _, _, _ in return false }, sendBotContextResultAsGif: { _, _, _, _ in return false }, requestMessageActionCallback: { _, _, _, _ in }, requestMessageActionUrlAuth: { _, _ in }, activateSwitchInline: { _, _ in }, openUrl: { [weak self] url, _, _, _ in
|
||||||
|
|||||||
@ -109,7 +109,8 @@ private final class DrawingStickersScreenNode: ViewControllerTracingNode {
|
|||||||
var selectStickerImpl: ((FileMediaReference, ASDisplayNode, CGRect) -> Bool)?
|
var selectStickerImpl: ((FileMediaReference, ASDisplayNode, CGRect) -> Bool)?
|
||||||
|
|
||||||
self.controllerInteraction = ChatControllerInteraction(openMessage: { _, _ in
|
self.controllerInteraction = ChatControllerInteraction(openMessage: { _, _ in
|
||||||
return false }, openPeer: { _, _, _ in }, openPeerMention: { _ in }, openMessageContextMenu: { _, _, _, _, _ in }, openMessageContextActions: { _, _, _, _ in }, navigateToMessage: { _, _ in }, navigateToMessageStandalone: { _ in
|
return false }, openPeer: { _, _, _ in }, openPeerMention: { _ in }, openMessageContextMenu: { _, _, _, _, _ in }, activateMessagePinch: { _ in
|
||||||
|
}, openMessageContextActions: { _, _, _, _ in }, navigateToMessage: { _, _ in }, navigateToMessageStandalone: { _ in
|
||||||
}, tapMessage: nil, clickThroughMessage: { }, toggleMessagesSelection: { _, _ in }, sendCurrentMessage: { _ in }, sendMessage: { _ in }, sendSticker: { fileReference, _, _, node, rect in return selectStickerImpl?(fileReference, node, rect) ?? false }, sendGif: { _, _, _ in return false }, sendBotContextResultAsGif: { _, _, _, _ in return false }, requestMessageActionCallback: { _, _, _, _ in }, requestMessageActionUrlAuth: { _, _ in }, activateSwitchInline: { _, _ in }, openUrl: { _, _, _, _ in }, shareCurrentLocation: {}, shareAccountContact: {}, sendBotCommand: { _, _ in }, openInstantPage: { _, _ in }, openWallpaper: { _ in }, openTheme: { _ in }, openHashtag: { _, _ in }, updateInputState: { _ in }, updateInputMode: { _ in }, openMessageShareMenu: { _ in
|
}, tapMessage: nil, clickThroughMessage: { }, toggleMessagesSelection: { _, _ in }, sendCurrentMessage: { _ in }, sendMessage: { _ in }, sendSticker: { fileReference, _, _, node, rect in return selectStickerImpl?(fileReference, node, rect) ?? false }, sendGif: { _, _, _ in return false }, sendBotContextResultAsGif: { _, _, _, _ in return false }, requestMessageActionCallback: { _, _, _, _ in }, requestMessageActionUrlAuth: { _, _ in }, activateSwitchInline: { _, _ in }, openUrl: { _, _, _, _ in }, shareCurrentLocation: {}, shareAccountContact: {}, sendBotCommand: { _, _ in }, openInstantPage: { _, _ in }, openWallpaper: { _ in }, openTheme: { _ in }, openHashtag: { _, _ in }, updateInputState: { _ in }, updateInputMode: { _ in }, openMessageShareMenu: { _ in
|
||||||
}, presentController: { _, _ in }, navigationController: {
|
}, presentController: { _, _ in }, navigationController: {
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@ -70,6 +70,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, UIGestu
|
|||||||
}, openPeer: { _, _, _ in
|
}, openPeer: { _, _, _ in
|
||||||
}, openPeerMention: { _ in
|
}, openPeerMention: { _ in
|
||||||
}, openMessageContextMenu: { _, _, _, _, _ in
|
}, openMessageContextMenu: { _, _, _, _, _ in
|
||||||
|
}, activateMessagePinch: { _ in
|
||||||
}, openMessageContextActions: { _, _, _, _ in
|
}, openMessageContextActions: { _, _, _, _ in
|
||||||
}, navigateToMessage: { _, _ in
|
}, navigateToMessage: { _, _ in
|
||||||
}, navigateToMessageStandalone: { _ in
|
}, navigateToMessageStandalone: { _ in
|
||||||
|
|||||||
@ -1848,6 +1848,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
|
|||||||
let controller = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .extracted(MessageContextExtractedContentSource(sourceNode: node)), items: .single(items), reactionItems: [], recognizer: nil, gesture: gesture)
|
let controller = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .extracted(MessageContextExtractedContentSource(sourceNode: node)), items: .single(items), reactionItems: [], recognizer: nil, gesture: gesture)
|
||||||
strongSelf.controller?.window?.presentInGlobalOverlay(controller)
|
strongSelf.controller?.window?.presentInGlobalOverlay(controller)
|
||||||
})
|
})
|
||||||
|
}, activateMessagePinch: { _ in
|
||||||
}, openMessageContextActions: { [weak self] message, node, rect, gesture in
|
}, openMessageContextActions: { [weak self] message, node, rect, gesture in
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
gesture?.cancel()
|
gesture?.cancel()
|
||||||
|
|||||||
@ -1220,7 +1220,8 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
|||||||
let controllerInteraction: ChatControllerInteraction
|
let controllerInteraction: ChatControllerInteraction
|
||||||
if tapMessage != nil || clickThroughMessage != nil {
|
if tapMessage != nil || clickThroughMessage != nil {
|
||||||
controllerInteraction = ChatControllerInteraction(openMessage: { _, _ in
|
controllerInteraction = ChatControllerInteraction(openMessage: { _, _ in
|
||||||
return false }, openPeer: { _, _, _ in }, openPeerMention: { _ in }, openMessageContextMenu: { _, _, _, _, _ in }, openMessageContextActions: { _, _, _, _ in }, navigateToMessage: { _, _ in }, navigateToMessageStandalone: { _ in
|
return false }, openPeer: { _, _, _ in }, openPeerMention: { _ in }, openMessageContextMenu: { _, _, _, _, _ in }, activateMessagePinch: { _ in
|
||||||
|
}, openMessageContextActions: { _, _, _, _ in }, navigateToMessage: { _, _ in }, navigateToMessageStandalone: { _ in
|
||||||
}, tapMessage: { message in
|
}, tapMessage: { message in
|
||||||
tapMessage?(message)
|
tapMessage?(message)
|
||||||
}, clickThroughMessage: {
|
}, clickThroughMessage: {
|
||||||
|
|||||||
@ -1 +1 @@
|
|||||||
Subproject commit e8e949c07cf1bc0b345a566c381a25c17c1e2ad0
|
Subproject commit bf319940759582f51749de28545113a864cfd161
|
||||||
Loading…
x
Reference in New Issue
Block a user