mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Reaction animation updates
This commit is contained in:
parent
41c7863cc9
commit
fb4e94d09a
@ -62,7 +62,6 @@ public extension ContainedViewLayoutTransitionCurve {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#if os(iOS)
|
|
||||||
var viewAnimationOptions: UIView.AnimationOptions {
|
var viewAnimationOptions: UIView.AnimationOptions {
|
||||||
switch self {
|
switch self {
|
||||||
case .linear:
|
case .linear:
|
||||||
@ -77,7 +76,6 @@ public extension ContainedViewLayoutTransitionCurve {
|
|||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum ContainedViewLayoutTransition {
|
public enum ContainedViewLayoutTransition {
|
||||||
@ -1417,3 +1415,402 @@ public extension ContainedViewLayoutTransition {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public protocol ControlledTransitionAnimator: AnyObject {
|
||||||
|
var duration: Double { get }
|
||||||
|
|
||||||
|
func startAnimation()
|
||||||
|
func setAnimationProgress(_ progress: CGFloat)
|
||||||
|
func finishAnimation()
|
||||||
|
|
||||||
|
func updateAlpha(layer: CALayer, alpha: CGFloat, completion: ((Bool) -> Void)?)
|
||||||
|
func updatePosition(layer: CALayer, position: CGPoint, completion: ((Bool) -> Void)?)
|
||||||
|
func updateBounds(layer: CALayer, bounds: CGRect, completion: ((Bool) -> Void)?)
|
||||||
|
func updateFrame(layer: CALayer, frame: CGRect, completion: ((Bool) -> Void)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
protocol AnyValueProviding {
|
||||||
|
var anyValue: ControlledTransitionProperty.AnyValue { get }
|
||||||
|
}
|
||||||
|
|
||||||
|
extension CGFloat: AnyValueProviding {
|
||||||
|
func interpolate(with other: CGFloat, fraction: CGFloat) -> CGFloat {
|
||||||
|
let invT = 1.0 - fraction
|
||||||
|
let result = other * fraction + self * invT
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
var anyValue: ControlledTransitionProperty.AnyValue {
|
||||||
|
return ControlledTransitionProperty.AnyValue(
|
||||||
|
value: self,
|
||||||
|
stringValue: { "\(self)" },
|
||||||
|
isEqual: { other in
|
||||||
|
if let otherValue = other.value as? CGFloat {
|
||||||
|
return self == otherValue
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
interpolate: { other, fraction in
|
||||||
|
guard let otherValue = other.value as? CGFloat else {
|
||||||
|
preconditionFailure()
|
||||||
|
}
|
||||||
|
return self.interpolate(with: otherValue, fraction: fraction).anyValue
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Float: AnyValueProviding {
|
||||||
|
func interpolate(with other: Float, fraction: CGFloat) -> Float {
|
||||||
|
let invT = 1.0 - Float(fraction)
|
||||||
|
let result = other * Float(fraction) + self * invT
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
var anyValue: ControlledTransitionProperty.AnyValue {
|
||||||
|
return ControlledTransitionProperty.AnyValue(
|
||||||
|
value: self,
|
||||||
|
stringValue: { "\(self)" },
|
||||||
|
isEqual: { other in
|
||||||
|
if let otherValue = other.value as? Float {
|
||||||
|
return self == otherValue
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
interpolate: { other, fraction in
|
||||||
|
guard let otherValue = other.value as? Float else {
|
||||||
|
preconditionFailure()
|
||||||
|
}
|
||||||
|
return self.interpolate(with: otherValue, fraction: fraction).anyValue
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension CGPoint: AnyValueProviding {
|
||||||
|
func interpolate(with other: CGPoint, fraction: CGFloat) -> CGPoint {
|
||||||
|
return CGPoint(x: self.x.interpolate(with: other.x, fraction: fraction), y: self.y.interpolate(with: other.y, fraction: fraction))
|
||||||
|
}
|
||||||
|
|
||||||
|
var anyValue: ControlledTransitionProperty.AnyValue {
|
||||||
|
return ControlledTransitionProperty.AnyValue(
|
||||||
|
value: self,
|
||||||
|
stringValue: { "\(self)" },
|
||||||
|
isEqual: { other in
|
||||||
|
if let otherValue = other.value as? CGPoint {
|
||||||
|
return self == otherValue
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
interpolate: { other, fraction in
|
||||||
|
guard let otherValue = other.value as? CGPoint else {
|
||||||
|
preconditionFailure()
|
||||||
|
}
|
||||||
|
return self.interpolate(with: otherValue, fraction: fraction).anyValue
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension CGSize: AnyValueProviding {
|
||||||
|
func interpolate(with other: CGSize, fraction: CGFloat) -> CGSize {
|
||||||
|
return CGSize(width: self.width.interpolate(with: other.width, fraction: fraction), height: self.height.interpolate(with: other.height, fraction: fraction))
|
||||||
|
}
|
||||||
|
|
||||||
|
var anyValue: ControlledTransitionProperty.AnyValue {
|
||||||
|
return ControlledTransitionProperty.AnyValue(
|
||||||
|
value: self,
|
||||||
|
stringValue: { "\(self)" },
|
||||||
|
isEqual: { other in
|
||||||
|
if let otherValue = other.value as? CGSize {
|
||||||
|
return self == otherValue
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
interpolate: { other, fraction in
|
||||||
|
guard let otherValue = other.value as? CGSize else {
|
||||||
|
preconditionFailure()
|
||||||
|
}
|
||||||
|
return self.interpolate(with: otherValue, fraction: fraction).anyValue
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension CGRect: AnyValueProviding {
|
||||||
|
func interpolate(with other: CGRect, fraction: CGFloat) -> CGRect {
|
||||||
|
return CGRect(origin: self.origin.interpolate(with: other.origin, fraction: fraction), size: self.size.interpolate(with: other.size, fraction: fraction))
|
||||||
|
}
|
||||||
|
|
||||||
|
var anyValue: ControlledTransitionProperty.AnyValue {
|
||||||
|
return ControlledTransitionProperty.AnyValue(
|
||||||
|
value: self,
|
||||||
|
stringValue: { "\(self)" },
|
||||||
|
isEqual: { other in
|
||||||
|
if let otherValue = other.value as? CGRect {
|
||||||
|
return self == otherValue
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
interpolate: { other, fraction in
|
||||||
|
guard let otherValue = other.value as? CGRect else {
|
||||||
|
preconditionFailure()
|
||||||
|
}
|
||||||
|
return self.interpolate(with: otherValue, fraction: fraction).anyValue
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final class ControlledTransitionProperty {
|
||||||
|
final class AnyValue: Equatable, CustomStringConvertible {
|
||||||
|
let value: Any
|
||||||
|
let stringValue: () -> String
|
||||||
|
let isEqual: (AnyValue) -> Bool
|
||||||
|
let interpolate: (AnyValue, CGFloat) -> AnyValue
|
||||||
|
|
||||||
|
init(
|
||||||
|
value: Any,
|
||||||
|
stringValue: @escaping () -> String,
|
||||||
|
isEqual: @escaping (AnyValue) -> Bool,
|
||||||
|
interpolate: @escaping (AnyValue, CGFloat) -> AnyValue
|
||||||
|
) {
|
||||||
|
self.value = value
|
||||||
|
self.stringValue = stringValue
|
||||||
|
self.isEqual = isEqual
|
||||||
|
self.interpolate = interpolate
|
||||||
|
}
|
||||||
|
|
||||||
|
var description: String {
|
||||||
|
return self.stringValue()
|
||||||
|
}
|
||||||
|
|
||||||
|
static func ==(lhs: AnyValue, rhs: AnyValue) -> Bool {
|
||||||
|
if lhs.isEqual(rhs) {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let layer: CALayer
|
||||||
|
let keyPath: AnyKeyPath
|
||||||
|
private let write: (CALayer, AnyValue) -> Void
|
||||||
|
var fromValue: AnyValue
|
||||||
|
let toValue: AnyValue
|
||||||
|
private(set) var lastValue: AnyValue
|
||||||
|
private let completion: ((Bool) -> Void)?
|
||||||
|
|
||||||
|
init<T: Equatable>(layer: CALayer, keyPath: ReferenceWritableKeyPath<CALayer, T>, fromValue: T, toValue: T, completion: ((Bool) -> Void)?) where T: AnyValueProviding {
|
||||||
|
self.layer = layer
|
||||||
|
self.keyPath = keyPath
|
||||||
|
self.write = { layer, value in
|
||||||
|
layer[keyPath: keyPath] = value.value as! T
|
||||||
|
}
|
||||||
|
self.fromValue = fromValue.anyValue
|
||||||
|
self.toValue = toValue.anyValue
|
||||||
|
self.lastValue = self.fromValue
|
||||||
|
self.completion = completion
|
||||||
|
}
|
||||||
|
|
||||||
|
func update(at fraction: CGFloat) {
|
||||||
|
let value = self.fromValue.interpolate(toValue, fraction)
|
||||||
|
self.lastValue = value
|
||||||
|
self.write(self.layer, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func complete(atEnd: Bool) {
|
||||||
|
self.completion?(atEnd)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class ControlledTransition {
|
||||||
|
@available(iOS 10.0, *)
|
||||||
|
public final class NativeAnimator: ControlledTransitionAnimator {
|
||||||
|
public let duration: Double
|
||||||
|
private let curve: ContainedViewLayoutTransitionCurve
|
||||||
|
|
||||||
|
private var animations: [ControlledTransitionProperty] = []
|
||||||
|
|
||||||
|
init(
|
||||||
|
duration: Double,
|
||||||
|
curve: ContainedViewLayoutTransitionCurve
|
||||||
|
) {
|
||||||
|
self.duration = duration
|
||||||
|
self.curve = curve
|
||||||
|
}
|
||||||
|
|
||||||
|
func merge(with other: NativeAnimator) {
|
||||||
|
var removeAnimationIndices: [Int] = []
|
||||||
|
for i in 0 ..< self.animations.count {
|
||||||
|
let animation = self.animations[i]
|
||||||
|
|
||||||
|
var removeOtherAnimationIndices: [Int] = []
|
||||||
|
for j in 0 ..< other.animations.count {
|
||||||
|
let otherAnimation = other.animations[j]
|
||||||
|
|
||||||
|
if animation.layer === otherAnimation.layer && animation.keyPath == otherAnimation.keyPath {
|
||||||
|
if animation.toValue == otherAnimation.toValue {
|
||||||
|
removeAnimationIndices.append(i)
|
||||||
|
} else {
|
||||||
|
removeOtherAnimationIndices.append(j)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for j in removeOtherAnimationIndices.reversed() {
|
||||||
|
other.animations.remove(at: j).complete(atEnd: false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for i in removeAnimationIndices.reversed() {
|
||||||
|
self.animations.remove(at: i).complete(atEnd: false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func startAnimation() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public func setAnimationProgress(_ progress: CGFloat) {
|
||||||
|
let mappedFraction: CGFloat
|
||||||
|
switch self.curve {
|
||||||
|
case .spring:
|
||||||
|
mappedFraction = springAnimationSolver(progress)
|
||||||
|
case let .custom(c1x, c1y, c2x, c2y):
|
||||||
|
mappedFraction = bezierPoint(CGFloat(c1x), CGFloat(c1y), CGFloat(c2x), CGFloat(c2y), progress)
|
||||||
|
default:
|
||||||
|
mappedFraction = progress
|
||||||
|
}
|
||||||
|
|
||||||
|
for animation in self.animations {
|
||||||
|
animation.update(at: mappedFraction)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func finishAnimation() {
|
||||||
|
for animation in self.animations {
|
||||||
|
animation.update(at: 1.0)
|
||||||
|
animation.complete(atEnd: true)
|
||||||
|
}
|
||||||
|
self.animations.removeAll()
|
||||||
|
}
|
||||||
|
|
||||||
|
public func updateAlpha(layer: CALayer, alpha: CGFloat, completion: ((Bool) -> Void)?) {
|
||||||
|
self.animations.append(ControlledTransitionProperty(
|
||||||
|
layer: layer,
|
||||||
|
keyPath: \.opacity,
|
||||||
|
fromValue: layer.opacity,
|
||||||
|
toValue: Float(alpha),
|
||||||
|
completion: completion
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
public func updatePosition(layer: CALayer, position: CGPoint, completion: ((Bool) -> Void)?) {
|
||||||
|
self.animations.append(ControlledTransitionProperty(
|
||||||
|
layer: layer,
|
||||||
|
keyPath: \.position,
|
||||||
|
fromValue: layer.position,
|
||||||
|
toValue: position,
|
||||||
|
completion: completion
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
public func updateBounds(layer: CALayer, bounds: CGRect, completion: ((Bool) -> Void)?) {
|
||||||
|
self.animations.append(ControlledTransitionProperty(
|
||||||
|
layer: layer,
|
||||||
|
keyPath: \.bounds,
|
||||||
|
fromValue: layer.bounds,
|
||||||
|
toValue: bounds,
|
||||||
|
completion: completion
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
public func updateFrame(layer: CALayer, frame: CGRect, completion: ((Bool) -> Void)?) {
|
||||||
|
self.animations.append(ControlledTransitionProperty(
|
||||||
|
layer: layer,
|
||||||
|
keyPath: \.frame,
|
||||||
|
fromValue: layer.frame,
|
||||||
|
toValue: frame,
|
||||||
|
completion: completion
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class LegacyAnimator: ControlledTransitionAnimator {
|
||||||
|
public let duration: Double
|
||||||
|
public let transition: ContainedViewLayoutTransition
|
||||||
|
|
||||||
|
init(
|
||||||
|
duration: Double,
|
||||||
|
curve: ContainedViewLayoutTransitionCurve
|
||||||
|
) {
|
||||||
|
self.duration = duration
|
||||||
|
|
||||||
|
if duration.isZero {
|
||||||
|
self.transition = .immediate
|
||||||
|
} else {
|
||||||
|
self.transition = .animated(duration: duration, curve: curve)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func startAnimation() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public func setAnimationProgress(_ progress: CGFloat) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public func finishAnimation() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public func updateAlpha(layer: CALayer, alpha: CGFloat, completion: ((Bool) -> Void)?) {
|
||||||
|
self.transition.updateAlpha(layer: layer, alpha: alpha, completion: completion)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func updatePosition(layer: CALayer, position: CGPoint, completion: ((Bool) -> Void)?) {
|
||||||
|
self.transition.updatePosition(layer: layer, position: position, completion: completion)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func updateBounds(layer: CALayer, bounds: CGRect, completion: ((Bool) -> Void)?) {
|
||||||
|
self.transition.updateBounds(layer: layer, bounds: bounds, completion: completion)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func updateFrame(layer: CALayer, frame: CGRect, completion: ((Bool) -> Void)?) {
|
||||||
|
self.transition.updateFrame(layer: layer, frame: frame, completion: completion)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public let animator: ControlledTransitionAnimator
|
||||||
|
public let legacyAnimator: LegacyAnimator
|
||||||
|
|
||||||
|
public init(
|
||||||
|
duration: Double,
|
||||||
|
curve: ContainedViewLayoutTransitionCurve
|
||||||
|
) {
|
||||||
|
self.legacyAnimator = LegacyAnimator(
|
||||||
|
duration: duration,
|
||||||
|
curve: curve
|
||||||
|
)
|
||||||
|
if #available(iOS 10.0, *) {
|
||||||
|
self.animator = NativeAnimator(
|
||||||
|
duration: duration,
|
||||||
|
curve: curve
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
self.animator = self.legacyAnimator
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func merge(with other: ControlledTransition) {
|
||||||
|
if #available(iOS 10.0, *) {
|
||||||
|
if let animator = self.animator as? NativeAnimator, let otherAnimator = other.animator as? NativeAnimator {
|
||||||
|
animator.merge(with: otherAnimator)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1578,8 +1578,24 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
|
|||||||
DispatchQueue.main.async(execute: f)
|
DispatchQueue.main.async(execute: f)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func nodeForItem(synchronous: Bool, synchronousLoads: Bool, item: ListViewItem, previousNode: QueueLocalObject<ListViewItemNode>?, index: Int, previousItem: ListViewItem?, nextItem: ListViewItem?, params: ListViewItemLayoutParams, updateAnimation: ListViewItemUpdateAnimation, completion: @escaping (QueueLocalObject<ListViewItemNode>, ListViewItemNodeLayout, @escaping () -> (Signal<Void, NoError>?, (ListViewItemApply) -> Void)) -> Void) {
|
private func nodeForItem(synchronous: Bool, synchronousLoads: Bool, item: ListViewItem, previousNode: QueueLocalObject<ListViewItemNode>?, index: Int, previousItem: ListViewItem?, nextItem: ListViewItem?, params: ListViewItemLayoutParams, updateAnimationIsAnimated: Bool, updateAnimationIsCrossfade: Bool, completion: @escaping (QueueLocalObject<ListViewItemNode>, ListViewItemNodeLayout, @escaping () -> (Signal<Void, NoError>?, (ListViewItemApply) -> Void)) -> Void) {
|
||||||
if let previousNode = previousNode {
|
if let previousNode = previousNode {
|
||||||
|
var controlledTransition: ControlledTransition?
|
||||||
|
let updateAnimation: ListViewItemUpdateAnimation
|
||||||
|
if updateAnimationIsCrossfade {
|
||||||
|
updateAnimation = .Crossfade
|
||||||
|
} else if updateAnimationIsAnimated {
|
||||||
|
let transition = ControlledTransition(duration: insertionAnimationDuration * UIView.animationDurationFactor(), curve: .spring)
|
||||||
|
controlledTransition = transition
|
||||||
|
updateAnimation = .System(duration: insertionAnimationDuration * UIView.animationDurationFactor(), transition: transition)
|
||||||
|
} else {
|
||||||
|
updateAnimation = .None
|
||||||
|
}
|
||||||
|
|
||||||
|
if let controlledTransition = controlledTransition {
|
||||||
|
previousNode.syncWith({ $0 }).addPendingControlledTransition(transition: controlledTransition)
|
||||||
|
}
|
||||||
|
|
||||||
item.updateNode(async: { f in
|
item.updateNode(async: { f in
|
||||||
if synchronous {
|
if synchronous {
|
||||||
f()
|
f()
|
||||||
@ -2017,8 +2033,6 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
|
|||||||
if updateAdjacentItemsIndices.isEmpty {
|
if updateAdjacentItemsIndices.isEmpty {
|
||||||
completion(state, operations)
|
completion(state, operations)
|
||||||
} else {
|
} else {
|
||||||
let updateAnimation: ListViewItemUpdateAnimation = animated ? .System(duration: insertionAnimationDuration) : .None
|
|
||||||
|
|
||||||
var updatedUpdateAdjacentItemsIndices = updateAdjacentItemsIndices
|
var updatedUpdateAdjacentItemsIndices = updateAdjacentItemsIndices
|
||||||
|
|
||||||
let nodeIndex = updateAdjacentItemsIndices.first!
|
let nodeIndex = updateAdjacentItemsIndices.first!
|
||||||
@ -2031,6 +2045,20 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
|
|||||||
if case let .Node(index, _, referenceNode) = node , index == nodeIndex {
|
if case let .Node(index, _, referenceNode) = node , index == nodeIndex {
|
||||||
if let referenceNode = referenceNode {
|
if let referenceNode = referenceNode {
|
||||||
continueWithoutNode = false
|
continueWithoutNode = false
|
||||||
|
var controlledTransition: ControlledTransition?
|
||||||
|
let updateAnimation: ListViewItemUpdateAnimation
|
||||||
|
if animated {
|
||||||
|
let transition = ControlledTransition(duration: insertionAnimationDuration * UIView.animationDurationFactor(), curve: .spring)
|
||||||
|
controlledTransition = transition
|
||||||
|
updateAnimation = .System(duration: insertionAnimationDuration * UIView.animationDurationFactor(), transition: transition)
|
||||||
|
} else {
|
||||||
|
updateAnimation = .None
|
||||||
|
}
|
||||||
|
|
||||||
|
if let controlledTransition = controlledTransition {
|
||||||
|
referenceNode.syncWith({ $0 }).addPendingControlledTransition(transition: controlledTransition)
|
||||||
|
}
|
||||||
|
|
||||||
self.items[index].updateNode(async: { f in
|
self.items[index].updateNode(async: { f in
|
||||||
if synchronous {
|
if synchronous {
|
||||||
f()
|
f()
|
||||||
@ -2086,7 +2114,6 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
|
|||||||
let previousNodes = inputPreviousNodes
|
let previousNodes = inputPreviousNodes
|
||||||
var operations = inputOperations
|
var operations = inputOperations
|
||||||
let completion = inputCompletion
|
let completion = inputCompletion
|
||||||
let updateAnimation: ListViewItemUpdateAnimation = animated ? .System(duration: insertionAnimationDuration) : .None
|
|
||||||
|
|
||||||
if state.nodes.count > 1000 {
|
if state.nodes.count > 1000 {
|
||||||
print("state.nodes.count > 1000")
|
print("state.nodes.count > 1000")
|
||||||
@ -2115,8 +2142,7 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
|
|||||||
let index = insertionItemIndexAndDirection.0
|
let index = insertionItemIndexAndDirection.0
|
||||||
let threadId = pthread_self()
|
let threadId = pthread_self()
|
||||||
var tailRecurse = false
|
var tailRecurse = false
|
||||||
self.nodeForItem(synchronous: synchronous, synchronousLoads: synchronousLoads, item: self.items[index], previousNode: previousNodes[index], index: index, previousItem: index == 0 ? nil : self.items[index - 1], nextItem: self.items.count == index + 1 ? nil : self.items[index + 1], params: ListViewItemLayoutParams(width: state.visibleSize.width, leftInset: state.insets.left, rightInset: state.insets.right, availableHeight: state.visibleSize.height - state.insets.top - state.insets.bottom), updateAnimation: updateAnimation, completion: { (node, layout, apply) in
|
self.nodeForItem(synchronous: synchronous, synchronousLoads: synchronousLoads, item: self.items[index], previousNode: previousNodes[index], index: index, previousItem: index == 0 ? nil : self.items[index - 1], nextItem: self.items.count == index + 1 ? nil : self.items[index + 1], params: ListViewItemLayoutParams(width: state.visibleSize.width, leftInset: state.insets.left, rightInset: state.insets.right, availableHeight: state.visibleSize.height - state.insets.top - state.insets.bottom), updateAnimationIsAnimated: animated, updateAnimationIsCrossfade: false, completion: { (node, layout, apply) in
|
||||||
|
|
||||||
if pthread_equal(pthread_self(), threadId) != 0 && !tailRecurse {
|
if pthread_equal(pthread_self(), threadId) != 0 && !tailRecurse {
|
||||||
tailRecurse = true
|
tailRecurse = true
|
||||||
state.insertNode(index, node: node, layout: layout, apply: apply, offsetDirection: insertionItemIndexAndDirection.1, animated: animated && animatedInsertIndices.contains(index), operations: &operations, itemCount: self.items.count)
|
state.insertNode(index, node: node, layout: layout, apply: apply, offsetDirection: insertionItemIndexAndDirection.1, animated: animated && animatedInsertIndices.contains(index), operations: &operations, itemCount: self.items.count)
|
||||||
@ -2151,16 +2177,8 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
|
|||||||
} else {
|
} else {
|
||||||
let updateItem = updateIndicesAndItems[0]
|
let updateItem = updateIndicesAndItems[0]
|
||||||
if let previousNode = previousNodes[updateItem.index] {
|
if let previousNode = previousNodes[updateItem.index] {
|
||||||
let updateAnimation: ListViewItemUpdateAnimation
|
self.nodeForItem(synchronous: synchronous, synchronousLoads: synchronousLoads, item: updateItem.item, previousNode: previousNode, index: updateItem.index, previousItem: updateItem.index == 0 ? nil : self.items[updateItem.index - 1], nextItem: updateItem.index == (self.items.count - 1) ? nil : self.items[updateItem.index + 1], params: ListViewItemLayoutParams(width: state.visibleSize.width, leftInset: state.insets.left, rightInset: state.insets.right, availableHeight: state.visibleSize.height - state.insets.top - state.insets.bottom), updateAnimationIsAnimated: animated, updateAnimationIsCrossfade: crossfade, completion: { _, layout, apply in
|
||||||
if crossfade {
|
state.updateNodeAtItemIndex(updateItem.index, layout: layout, direction: updateItem.directionHint, isAnimated: animated, apply: apply, operations: &operations)
|
||||||
updateAnimation = .Crossfade
|
|
||||||
} else if animated {
|
|
||||||
updateAnimation = .System(duration: insertionAnimationDuration)
|
|
||||||
} else {
|
|
||||||
updateAnimation = .None
|
|
||||||
}
|
|
||||||
self.nodeForItem(synchronous: synchronous, synchronousLoads: synchronousLoads, item: updateItem.item, previousNode: previousNode, index: updateItem.index, previousItem: updateItem.index == 0 ? nil : self.items[updateItem.index - 1], nextItem: updateItem.index == (self.items.count - 1) ? nil : self.items[updateItem.index + 1], params: ListViewItemLayoutParams(width: state.visibleSize.width, leftInset: state.insets.left, rightInset: state.insets.right, availableHeight: state.visibleSize.height - state.insets.top - state.insets.bottom), updateAnimation: updateAnimation, completion: { _, layout, apply in
|
|
||||||
state.updateNodeAtItemIndex(updateItem.index, layout: layout, direction: updateItem.directionHint, animation: animated ? .System(duration: insertionAnimationDuration) : .None, apply: apply, operations: &operations)
|
|
||||||
|
|
||||||
updateIndicesAndItems.remove(at: 0)
|
updateIndicesAndItems.remove(at: 0)
|
||||||
self.updateNodes(synchronous: synchronous, synchronousLoads: synchronousLoads, crossfade: crossfade, animated: animated, updateIndicesAndItems: updateIndicesAndItems, inputState: state, previousNodes: previousNodes, inputOperations: operations, completion: completion)
|
self.updateNodes(synchronous: synchronous, synchronousLoads: synchronousLoads, crossfade: crossfade, animated: animated, updateIndicesAndItems: updateIndicesAndItems, inputState: state, previousNodes: previousNodes, inputOperations: operations, completion: completion)
|
||||||
@ -2656,10 +2674,16 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
if node.rotated && currentAnimation == nil {
|
if node.rotated {
|
||||||
|
if currentAnimation == nil {
|
||||||
let insetPart: CGFloat = previousInsets.bottom - layout.insets.bottom
|
let insetPart: CGFloat = previousInsets.bottom - layout.insets.bottom
|
||||||
node.transitionOffset += previousApparentHeight - layout.size.height - insetPart
|
node.transitionOffset += previousApparentHeight - layout.size.height - insetPart
|
||||||
node.addTransitionOffsetAnimation(0.0, duration: insertionAnimationDuration * UIView.animationDurationFactor(), beginAt: timestamp)
|
node.addTransitionOffsetAnimation(0.0, duration: insertionAnimationDuration * UIView.animationDurationFactor(), beginAt: timestamp)
|
||||||
|
} else {
|
||||||
|
let insetPart: CGFloat = previousInsets.bottom - layout.insets.bottom
|
||||||
|
node.transitionOffset = previousApparentHeight - layout.size.height - insetPart
|
||||||
|
node.addTransitionOffsetAnimation(0.0, duration: insertionAnimationDuration * UIView.animationDurationFactor(), beginAt: timestamp)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -2708,6 +2732,10 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for itemNode in self.itemNodes {
|
||||||
|
itemNode.beginPendingControlledTransitions(beginAt: timestamp)
|
||||||
|
}
|
||||||
|
|
||||||
if hadInserts, let reorderNode = self.reorderNode, reorderNode.supernode != nil {
|
if hadInserts, let reorderNode = self.reorderNode, reorderNode.supernode != nil {
|
||||||
self.view.bringSubviewToFront(reorderNode.view)
|
self.view.bringSubviewToFront(reorderNode.view)
|
||||||
if let verticalScrollIndicator = self.verticalScrollIndicator {
|
if let verticalScrollIndicator = self.verticalScrollIndicator {
|
||||||
|
@ -7,15 +7,15 @@ public protocol Interpolatable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func floorToPixels(_ value: CGFloat) -> CGFloat {
|
private func floorToPixels(_ value: CGFloat) -> CGFloat {
|
||||||
return round(value * 10.0) / 10.0
|
return value
|
||||||
}
|
}
|
||||||
|
|
||||||
private func floorToPixels(_ value: CGPoint) -> CGPoint {
|
private func floorToPixels(_ value: CGPoint) -> CGPoint {
|
||||||
return CGPoint(x: round(value.x * 10.0) / 10.0, y: round(value.y * 10.0) / 10.0)
|
return CGPoint(x: floorToPixels(value.x), y: floorToPixels(value.y))
|
||||||
}
|
}
|
||||||
|
|
||||||
private func floorToPixels(_ value: CGSize) -> CGSize {
|
private func floorToPixels(_ value: CGSize) -> CGSize {
|
||||||
return CGSize(width: round(value.width * 10.0) / 10.0, height: round(value.height * 10.0) / 10.0)
|
return CGSize(width: floorToPixels(value.width), height: floorToPixels(value.height))
|
||||||
}
|
}
|
||||||
|
|
||||||
private func floorToPixels(_ value: CGRect) -> CGRect {
|
private func floorToPixels(_ value: CGRect) -> CGRect {
|
||||||
@ -23,7 +23,7 @@ private func floorToPixels(_ value: CGRect) -> CGRect {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func floorToPixels(_ value: UIEdgeInsets) -> UIEdgeInsets {
|
private func floorToPixels(_ value: UIEdgeInsets) -> UIEdgeInsets {
|
||||||
return UIEdgeInsets(top: round(value.top * 10.0) / 10.0, left: round(value.left * 10.0) / 10.0, bottom: round(value.bottom * 10.0) / 10.0, right: round(value.right * 10.0) / 10.0)
|
return UIEdgeInsets(top: floorToPixels(value.top), left: floorToPixels(value.left), bottom: floorToPixels(value.bottom), right: floorToPixels(value.right))
|
||||||
}
|
}
|
||||||
|
|
||||||
extension CGFloat: Interpolatable {
|
extension CGFloat: Interpolatable {
|
||||||
@ -36,6 +36,12 @@ extension CGFloat: Interpolatable {
|
|||||||
return floorToPixels(term)
|
return floorToPixels(term)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static func interpolate(from fromValue: CGFloat, to toValue: CGFloat, at t: CGFloat) -> CGFloat {
|
||||||
|
let invT: CGFloat = 1.0 - t
|
||||||
|
let term: CGFloat = toValue * t + fromValue * invT
|
||||||
|
return term
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension UIEdgeInsets: Interpolatable {
|
extension UIEdgeInsets: Interpolatable {
|
||||||
@ -56,6 +62,10 @@ extension CGRect: Interpolatable {
|
|||||||
return floorToPixels(CGRect(x: toValue.origin.x * t + fromValue.origin.x * (1.0 - t), y: toValue.origin.y * t + fromValue.origin.y * (1.0 - t), width: toValue.size.width * t + fromValue.size.width * (1.0 - t), height: toValue.size.height * t + fromValue.size.height * (1.0 - t)))
|
return floorToPixels(CGRect(x: toValue.origin.x * t + fromValue.origin.x * (1.0 - t), y: toValue.origin.y * t + fromValue.origin.y * (1.0 - t), width: toValue.size.width * t + fromValue.size.width * (1.0 - t), height: toValue.size.height * t + fromValue.size.height * (1.0 - t)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static func interpolate(from fromValue: CGRect, to toValue: CGRect, at t: CGFloat) -> CGRect {
|
||||||
|
return CGRect(origin: CGPoint.interpolate(from: fromValue.origin, to: toValue.origin, at: t), size: CGSize.interpolate(from: fromValue.size, to: toValue.size, at: t))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension CGPoint: Interpolatable {
|
extension CGPoint: Interpolatable {
|
||||||
@ -66,6 +76,16 @@ extension CGPoint: Interpolatable {
|
|||||||
return floorToPixels(CGPoint(x: toValue.x * t + fromValue.x * (1.0 - t), y: toValue.y * t + fromValue.y * (1.0 - t)))
|
return floorToPixels(CGPoint(x: toValue.x * t + fromValue.x * (1.0 - t), y: toValue.y * t + fromValue.y * (1.0 - t)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static func interpolate(from fromValue: CGPoint, to toValue: CGPoint, at t: CGFloat) -> CGPoint {
|
||||||
|
return CGPoint(x: toValue.x * t + fromValue.x * (1.0 - t), y: toValue.y * t + fromValue.y * (1.0 - t))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension CGSize {
|
||||||
|
static func interpolate(from fromValue: CGSize, to toValue: CGSize, at t: CGFloat) -> CGSize {
|
||||||
|
return CGSize(width: toValue.width * t + fromValue.width * (1.0 - t), height: toValue.height * t + fromValue.height * (1.0 - t))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private let springAnimationIn: CABasicAnimation = {
|
private let springAnimationIn: CABasicAnimation = {
|
||||||
@ -73,7 +93,7 @@ private let springAnimationIn: CABasicAnimation = {
|
|||||||
return animation
|
return animation
|
||||||
}()
|
}()
|
||||||
|
|
||||||
private let springAnimationSolver: (CGFloat) -> CGFloat = { () -> (CGFloat) -> CGFloat in
|
let springAnimationSolver: (CGFloat) -> CGFloat = { () -> (CGFloat) -> CGFloat in
|
||||||
if #available(iOS 9.0, *) {
|
if #available(iOS 9.0, *) {
|
||||||
return { t in
|
return { t in
|
||||||
return springAnimationValueAt(springAnimationIn, t)
|
return springAnimationValueAt(springAnimationIn, t)
|
||||||
|
@ -807,13 +807,12 @@ struct ListViewState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mutating func updateNodeAtItemIndex(_ itemIndex: Int, layout: ListViewItemNodeLayout, direction: ListViewItemOperationDirectionHint?, animation: ListViewItemUpdateAnimation, apply: @escaping () -> (Signal<Void, NoError>?, (ListViewItemApply) -> Void), operations: inout [ListViewStateOperation]) {
|
mutating func updateNodeAtItemIndex(_ itemIndex: Int, layout: ListViewItemNodeLayout, direction: ListViewItemOperationDirectionHint?, isAnimated: Bool, apply: @escaping () -> (Signal<Void, NoError>?, (ListViewItemApply) -> Void), operations: inout [ListViewStateOperation]) {
|
||||||
var i = -1
|
var i = -1
|
||||||
for node in self.nodes {
|
for node in self.nodes {
|
||||||
i += 1
|
i += 1
|
||||||
if node.index == itemIndex {
|
if node.index == itemIndex {
|
||||||
switch animation {
|
if isAnimated {
|
||||||
case .None, .Crossfade:
|
|
||||||
let offsetDirection: ListViewInsertionOffsetDirection
|
let offsetDirection: ListViewInsertionOffsetDirection
|
||||||
if let direction = direction {
|
if let direction = direction {
|
||||||
offsetDirection = ListViewInsertionOffsetDirection(direction)
|
offsetDirection = ListViewInsertionOffsetDirection(direction)
|
||||||
@ -852,7 +851,7 @@ struct ListViewState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
operations.append(.UpdateLayout(index: i, layout: layout, apply: apply))
|
operations.append(.UpdateLayout(index: i, layout: layout, apply: apply))
|
||||||
case .System:
|
} else {
|
||||||
operations.append(.UpdateLayout(index: i, layout: layout, apply: apply))
|
operations.append(.UpdateLayout(index: i, layout: layout, apply: apply))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@ import SwiftSignalKit
|
|||||||
|
|
||||||
public enum ListViewItemUpdateAnimation {
|
public enum ListViewItemUpdateAnimation {
|
||||||
case None
|
case None
|
||||||
case System(duration: Double)
|
case System(duration: Double, transition: ControlledTransition)
|
||||||
case Crossfade
|
case Crossfade
|
||||||
|
|
||||||
public var isAnimated: Bool {
|
public var isAnimated: Bool {
|
||||||
@ -14,6 +14,26 @@ public enum ListViewItemUpdateAnimation {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public var animator: ControlledTransitionAnimator {
|
||||||
|
switch self {
|
||||||
|
case .None:
|
||||||
|
return ControlledTransition.LegacyAnimator(duration: 0.0, curve: .linear)
|
||||||
|
case let .System(_, transition):
|
||||||
|
return transition.animator
|
||||||
|
case .Crossfade:
|
||||||
|
return ControlledTransition.LegacyAnimator(duration: 0.0, curve: .linear)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public var transition: ContainedViewLayoutTransition {
|
||||||
|
switch self {
|
||||||
|
case .None, .Crossfade:
|
||||||
|
return .immediate
|
||||||
|
case let .System(_, transition):
|
||||||
|
return transition.legacyAnimator.transition
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct ListViewItemConfigureNodeFlags: OptionSet {
|
public struct ListViewItemConfigureNodeFlags: OptionSet {
|
||||||
|
@ -83,6 +83,16 @@ public struct ListViewItemLayoutParams {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private final class ControlledTransitionContext {
|
||||||
|
let transition: ControlledTransition
|
||||||
|
let beginAt: Double
|
||||||
|
|
||||||
|
init(transition: ControlledTransition, beginAt: Double) {
|
||||||
|
self.transition = transition
|
||||||
|
self.beginAt = beginAt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
open class ListViewItemNode: ASDisplayNode, AccessibilityFocusableNode {
|
open class ListViewItemNode: ASDisplayNode, AccessibilityFocusableNode {
|
||||||
public struct HeaderId: Hashable {
|
public struct HeaderId: Hashable {
|
||||||
public var space: AnyHashable
|
public var space: AnyHashable
|
||||||
@ -126,6 +136,8 @@ open class ListViewItemNode: ASDisplayNode, AccessibilityFocusableNode {
|
|||||||
|
|
||||||
private final var spring: ListViewItemSpring?
|
private final var spring: ListViewItemSpring?
|
||||||
private final var animations: [(String, ListViewAnimation)] = []
|
private final var animations: [(String, ListViewAnimation)] = []
|
||||||
|
private final var pendingControlledTransitions: [ControlledTransition] = []
|
||||||
|
private final var controlledTransitions: [ControlledTransitionContext] = []
|
||||||
|
|
||||||
final var tempHeaderSpaceAffinities: [ListViewItemNode.HeaderId: Int] = [:]
|
final var tempHeaderSpaceAffinities: [ListViewItemNode.HeaderId: Int] = [:]
|
||||||
final var headerSpaceAffinities: [ListViewItemNode.HeaderId: Int] = [:]
|
final var headerSpaceAffinities: [ListViewItemNode.HeaderId: Int] = [:]
|
||||||
@ -394,6 +406,26 @@ open class ListViewItemNode: ASDisplayNode, AccessibilityFocusableNode {
|
|||||||
i += 1
|
i += 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
i = 0
|
||||||
|
var transitionCount = self.controlledTransitions.count
|
||||||
|
while i < transitionCount {
|
||||||
|
let transition = self.controlledTransitions[i]
|
||||||
|
var fraction = (timestamp - transition.beginAt) / transition.transition.animator.duration
|
||||||
|
fraction = max(0.0, min(1.0, fraction))
|
||||||
|
transition.transition.animator.setAnimationProgress(CGFloat(fraction))
|
||||||
|
|
||||||
|
if timestamp >= transition.beginAt + transition.transition.animator.duration {
|
||||||
|
transition.transition.animator.finishAnimation()
|
||||||
|
self.controlledTransitions.remove(at: i)
|
||||||
|
transitionCount -= 1
|
||||||
|
i -= 1
|
||||||
|
} else {
|
||||||
|
continueAnimations = true
|
||||||
|
}
|
||||||
|
|
||||||
|
i += 1
|
||||||
|
}
|
||||||
|
|
||||||
if let accessoryItemNode = self.accessoryItemNode {
|
if let accessoryItemNode = self.accessoryItemNode {
|
||||||
if (accessoryItemNode.animate(timestamp)) {
|
if (accessoryItemNode.animate(timestamp)) {
|
||||||
continueAnimations = true
|
continueAnimations = true
|
||||||
@ -438,6 +470,29 @@ open class ListViewItemNode: ASDisplayNode, AccessibilityFocusableNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
self.accessoryItemNode?.removeAllAnimations()
|
self.accessoryItemNode?.removeAllAnimations()
|
||||||
|
|
||||||
|
for transition in self.controlledTransitions {
|
||||||
|
transition.transition.animator.finishAnimation()
|
||||||
|
}
|
||||||
|
self.controlledTransitions.removeAll()
|
||||||
|
}
|
||||||
|
|
||||||
|
func addPendingControlledTransition(transition: ControlledTransition) {
|
||||||
|
self.pendingControlledTransitions.append(transition)
|
||||||
|
}
|
||||||
|
|
||||||
|
func beginPendingControlledTransitions(beginAt: Double) {
|
||||||
|
for transition in self.pendingControlledTransitions {
|
||||||
|
self.addControlledTransition(transition: transition, beginAt: beginAt)
|
||||||
|
}
|
||||||
|
self.pendingControlledTransitions.removeAll()
|
||||||
|
}
|
||||||
|
|
||||||
|
func addControlledTransition(transition: ControlledTransition, beginAt: Double) {
|
||||||
|
for controlledTransition in self.controlledTransitions {
|
||||||
|
transition.merge(with: controlledTransition.transition)
|
||||||
|
}
|
||||||
|
self.controlledTransitions.append(ControlledTransitionContext(transition: transition, beginAt: beginAt))
|
||||||
}
|
}
|
||||||
|
|
||||||
public func addInsetsAnimationToValue(_ value: UIEdgeInsets, duration: Double, beginAt: Double) {
|
public func addInsetsAnimationToValue(_ value: UIEdgeInsets, duration: Double, beginAt: Double) {
|
||||||
|
@ -503,7 +503,7 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
|
|
||||||
itemNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: duration * 0.9, removeOnCompletion: false)
|
itemNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: duration * 0.9, removeOnCompletion: false)
|
||||||
targetSnapshotView.layer.animateAlpha(from: 0.0, to: 1.0, duration: duration * 0.8)
|
targetSnapshotView.layer.animateAlpha(from: 0.0, to: 1.0, duration: duration * 0.8)
|
||||||
targetSnapshotView.layer.animateScale(from: itemNode.bounds.width / targetSnapshotView.bounds.width, to: 0.5, duration: duration, removeOnCompletion: false, completion: { [weak self, weak targetSnapshotView] _ in
|
targetSnapshotView.layer.animateScale(from: itemNode.bounds.width / targetSnapshotView.bounds.width, to: 1.0, duration: duration, removeOnCompletion: false, completion: { [weak self, weak targetSnapshotView] _ in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
strongSelf.hapticFeedback.tap()
|
strongSelf.hapticFeedback.tap()
|
||||||
}
|
}
|
||||||
@ -514,18 +514,18 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
|
|
||||||
if hideNode {
|
if hideNode {
|
||||||
targetView.isHidden = false
|
targetView.isHidden = false
|
||||||
targetView.layer.animateSpring(from: 0.5 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: duration, initialVelocity: 0.0, damping: 90.0, completion: { _ in
|
/*targetView.layer.animateSpring(from: 0.5 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: duration, initialVelocity: 0.0, damping: 90.0, completion: { _ in*/
|
||||||
targetSnapshotView?.isHidden = true
|
targetSnapshotView?.isHidden = true
|
||||||
targetScaleCompleted = true
|
targetScaleCompleted = true
|
||||||
intermediateCompletion()
|
intermediateCompletion()
|
||||||
})
|
//})
|
||||||
} else {
|
} else {
|
||||||
targetScaleCompleted = true
|
targetScaleCompleted = true
|
||||||
intermediateCompletion()
|
intermediateCompletion()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
itemNode.layer.animateScale(from: 1.0, to: (targetSnapshotView.bounds.width * 0.5) / itemNode.bounds.width, duration: duration, removeOnCompletion: false)
|
itemNode.layer.animateScale(from: 1.0, to: (targetSnapshotView.bounds.width * 1.0) / itemNode.bounds.width, duration: duration, removeOnCompletion: false)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func willAnimateOutToReaction(value: String) {
|
public func willAnimateOutToReaction(value: String) {
|
||||||
@ -668,7 +668,7 @@ public final class StandaloneReactionAnimation: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public func animateReactionSelection(targetView: UIView, hideNode: Bool, completion: @escaping () -> Void) {
|
public func animateReactionSelection(targetView: UIView, hideNode: Bool, completion: @escaping () -> Void) {
|
||||||
guard let targetSnapshotView = targetView.snapshotContentTree() else {
|
guard let sourceSnapshotView = targetView.snapshotContentTree(), let targetSnapshotView = targetView.snapshotContentTree() else {
|
||||||
completion()
|
completion()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -685,12 +685,20 @@ public final class StandaloneReactionAnimation: ASDisplayNode {
|
|||||||
|
|
||||||
let expandedFrame = CGRect(origin: CGPoint(x: floor(selfTargetRect.midX - expandedSize.width / 2.0), y: floor(selfTargetRect.midY - expandedSize.height / 2.0)), size: expandedSize)
|
let expandedFrame = CGRect(origin: CGPoint(x: floor(selfTargetRect.midX - expandedSize.width / 2.0), y: floor(selfTargetRect.midY - expandedSize.height / 2.0)), size: expandedSize)
|
||||||
|
|
||||||
|
sourceSnapshotView.frame = selfTargetRect
|
||||||
|
self.view.addSubview(sourceSnapshotView)
|
||||||
|
sourceSnapshotView.alpha = 0.0
|
||||||
|
sourceSnapshotView.layer.animateSpring(from: 1.0 as NSNumber, to: (expandedFrame.width / selfTargetRect.width) as NSNumber, keyPath: "transform.scale", duration: 0.4)
|
||||||
|
sourceSnapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.08, completion: { [weak sourceSnapshotView] _ in
|
||||||
|
sourceSnapshotView?.removeFromSuperview()
|
||||||
|
})
|
||||||
|
|
||||||
self.addSubnode(itemNode)
|
self.addSubnode(itemNode)
|
||||||
itemNode.frame = expandedFrame
|
itemNode.frame = expandedFrame
|
||||||
itemNode.updateLayout(size: expandedFrame.size, isExpanded: true, transition: .immediate)
|
itemNode.updateLayout(size: expandedFrame.size, isExpanded: true, transition: .immediate)
|
||||||
|
|
||||||
itemNode.layer.animateScale(from: 0.1, to: 1.0, duration: 0.18)
|
itemNode.layer.animateSpring(from: (selfTargetRect.width / expandedFrame.width) as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.4)
|
||||||
itemNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.18)
|
itemNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.04)
|
||||||
|
|
||||||
let additionalAnimationNode = AnimatedStickerNode()
|
let additionalAnimationNode = AnimatedStickerNode()
|
||||||
let incomingMessage: Bool = expandedFrame.midX < self.bounds.width / 2.0
|
let incomingMessage: Bool = expandedFrame.midX < self.bounds.width / 2.0
|
||||||
@ -724,7 +732,7 @@ public final class StandaloneReactionAnimation: ASDisplayNode {
|
|||||||
additionalAnimationNode.visibility = true
|
additionalAnimationNode.visibility = true
|
||||||
})
|
})
|
||||||
|
|
||||||
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 2.0 * UIView.animationDurationFactor(), execute: {
|
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 2.0, execute: {
|
||||||
self.animateFromItemNodeToReaction(itemNode: self.itemNode, targetView: targetView, targetSnapshotView: targetSnapshotView, hideNode: hideNode, completion: {
|
self.animateFromItemNodeToReaction(itemNode: self.itemNode, targetView: targetView, targetSnapshotView: targetSnapshotView, hideNode: hideNode, completion: {
|
||||||
mainAnimationCompleted = true
|
mainAnimationCompleted = true
|
||||||
intermediateCompletion()
|
intermediateCompletion()
|
||||||
@ -755,7 +763,7 @@ public final class StandaloneReactionAnimation: ASDisplayNode {
|
|||||||
|
|
||||||
itemNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: duration * 0.9, removeOnCompletion: false)
|
itemNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: duration * 0.9, removeOnCompletion: false)
|
||||||
targetSnapshotView.layer.animateAlpha(from: 0.0, to: 1.0, duration: duration * 0.8)
|
targetSnapshotView.layer.animateAlpha(from: 0.0, to: 1.0, duration: duration * 0.8)
|
||||||
targetSnapshotView.layer.animateScale(from: itemNode.bounds.width / targetSnapshotView.bounds.width, to: 0.5, duration: duration, removeOnCompletion: false, completion: { [weak self, weak targetSnapshotView] _ in
|
targetSnapshotView.layer.animateScale(from: itemNode.bounds.width / targetSnapshotView.bounds.width, to: 1.0, duration: duration, removeOnCompletion: false, completion: { [weak self, weak targetSnapshotView] _ in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
strongSelf.hapticFeedback.tap()
|
strongSelf.hapticFeedback.tap()
|
||||||
}
|
}
|
||||||
@ -766,18 +774,18 @@ public final class StandaloneReactionAnimation: ASDisplayNode {
|
|||||||
|
|
||||||
if hideNode {
|
if hideNode {
|
||||||
targetView.isHidden = false
|
targetView.isHidden = false
|
||||||
targetView.layer.animateSpring(from: 0.5 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: duration, initialVelocity: 0.0, damping: 90.0, completion: { _ in
|
/*targetView.layer.animateSpring(from: 0.5 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: duration, initialVelocity: 0.0, damping: 90.0, completion: { _ in*/
|
||||||
targetSnapshotView?.isHidden = true
|
targetSnapshotView?.isHidden = true
|
||||||
targetScaleCompleted = true
|
targetScaleCompleted = true
|
||||||
intermediateCompletion()
|
intermediateCompletion()
|
||||||
})
|
//})
|
||||||
} else {
|
} else {
|
||||||
targetScaleCompleted = true
|
targetScaleCompleted = true
|
||||||
intermediateCompletion()
|
intermediateCompletion()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
itemNode.layer.animateScale(from: 1.0, to: (targetSnapshotView.bounds.width * 0.5) / itemNode.bounds.width, duration: duration, removeOnCompletion: false)
|
itemNode.layer.animateScale(from: 1.0, to: (targetSnapshotView.bounds.width * 1.0) / itemNode.bounds.width, duration: duration, removeOnCompletion: false)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func addRelativeContentOffset(_ offset: CGPoint, transition: ContainedViewLayoutTransition) {
|
public func addRelativeContentOffset(_ offset: CGPoint, transition: ContainedViewLayoutTransition) {
|
||||||
|
@ -60,8 +60,6 @@ final class ReactionNode: ASDisplayNode {
|
|||||||
|
|
||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
//self.backgroundColor = UIColor(white: 0.0, alpha: 0.1)
|
|
||||||
|
|
||||||
self.addSubnode(self.staticImageNode)
|
self.addSubnode(self.staticImageNode)
|
||||||
|
|
||||||
self.addSubnode(self.stillAnimationNode)
|
self.addSubnode(self.stillAnimationNode)
|
||||||
@ -120,11 +118,14 @@ final class ReactionNode: ASDisplayNode {
|
|||||||
animationNode.visibility = true
|
animationNode.visibility = true
|
||||||
|
|
||||||
self.stillAnimationNode.alpha = 0.0
|
self.stillAnimationNode.alpha = 0.0
|
||||||
|
if transition.isAnimated {
|
||||||
self.stillAnimationNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, completion: { [weak self] _ in
|
self.stillAnimationNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, completion: { [weak self] _ in
|
||||||
self?.stillAnimationNode.visibility = false
|
self?.stillAnimationNode.visibility = false
|
||||||
})
|
})
|
||||||
|
|
||||||
animationNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15)
|
animationNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15)
|
||||||
|
} else {
|
||||||
|
self.stillAnimationNode.visibility = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.validSize != size {
|
if self.validSize != size {
|
||||||
@ -137,6 +138,7 @@ final class ReactionNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !self.didSetupStillAnimation {
|
if !self.didSetupStillAnimation {
|
||||||
|
if self.animationNode == nil {
|
||||||
self.didSetupStillAnimation = true
|
self.didSetupStillAnimation = true
|
||||||
|
|
||||||
self.stillAnimationNode.setup(source: AnimatedStickerResourceSource(account: self.context.account, resource: self.item.stillAnimation.resource), width: Int(animationDisplaySize.width * 2.0), height: Int(animationDisplaySize.height * 2.0), playbackMode: .loop, mode: .direct(cachePathPrefix: self.context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(self.item.stillAnimation.resource.id)))
|
self.stillAnimationNode.setup(source: AnimatedStickerResourceSource(account: self.context.account, resource: self.item.stillAnimation.resource), width: Int(animationDisplaySize.width * 2.0), height: Int(animationDisplaySize.height * 2.0), playbackMode: .loop, mode: .direct(cachePathPrefix: self.context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(self.item.stillAnimation.resource.id)))
|
||||||
@ -144,6 +146,7 @@ final class ReactionNode: ASDisplayNode {
|
|||||||
self.stillAnimationNode.bounds = CGRect(origin: CGPoint(), size: animationFrame.size)
|
self.stillAnimationNode.bounds = CGRect(origin: CGPoint(), size: animationFrame.size)
|
||||||
self.stillAnimationNode.updateLayout(size: animationFrame.size)
|
self.stillAnimationNode.updateLayout(size: animationFrame.size)
|
||||||
self.stillAnimationNode.visibility = true
|
self.stillAnimationNode.visibility = true
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
transition.updatePosition(node: self.stillAnimationNode, position: animationFrame.center)
|
transition.updatePosition(node: self.stillAnimationNode, position: animationFrame.center)
|
||||||
transition.updateTransformScale(node: self.stillAnimationNode, scale: animationFrame.size.width / self.stillAnimationNode.bounds.width)
|
transition.updateTransformScale(node: self.stillAnimationNode, scale: animationFrame.size.width / self.stillAnimationNode.bounds.width)
|
||||||
|
@ -65,7 +65,7 @@ enum AccountStateMutationOperation {
|
|||||||
case DeleteMessages([MessageId])
|
case DeleteMessages([MessageId])
|
||||||
case EditMessage(MessageId, StoreMessage)
|
case EditMessage(MessageId, StoreMessage)
|
||||||
case UpdateMessagePoll(MediaId, Api.Poll?, Api.PollResults)
|
case UpdateMessagePoll(MediaId, Api.Poll?, Api.PollResults)
|
||||||
//case UpdateMessageReactions(MessageId, Api.MessageReactions)
|
case UpdateMessageReactions(MessageId, Api.MessageReactions)
|
||||||
case UpdateMedia(MediaId, Media?)
|
case UpdateMedia(MediaId, Media?)
|
||||||
case ReadInbox(MessageId)
|
case ReadInbox(MessageId)
|
||||||
case ReadOutbox(MessageId, Int32?)
|
case ReadOutbox(MessageId, Int32?)
|
||||||
@ -258,9 +258,9 @@ struct AccountMutableState {
|
|||||||
self.addOperation(.UpdateMessagePoll(id, poll, results))
|
self.addOperation(.UpdateMessagePoll(id, poll, results))
|
||||||
}
|
}
|
||||||
|
|
||||||
/*mutating func updateMessageReactions(_ messageId: MessageId, reactions: Api.MessageReactions) {
|
mutating func updateMessageReactions(_ messageId: MessageId, reactions: Api.MessageReactions) {
|
||||||
self.addOperation(.UpdateMessageReactions(messageId, reactions))
|
self.addOperation(.UpdateMessageReactions(messageId, reactions))
|
||||||
}*/
|
}
|
||||||
|
|
||||||
mutating func updateMedia(_ id: MediaId, media: Media?) {
|
mutating func updateMedia(_ id: MediaId, media: Media?) {
|
||||||
self.addOperation(.UpdateMedia(id, media))
|
self.addOperation(.UpdateMedia(id, media))
|
||||||
@ -498,7 +498,7 @@ struct AccountMutableState {
|
|||||||
|
|
||||||
mutating func addOperation(_ operation: AccountStateMutationOperation) {
|
mutating func addOperation(_ operation: AccountStateMutationOperation) {
|
||||||
switch operation {
|
switch operation {
|
||||||
case .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMessagePoll/*, .UpdateMessageReactions*/, .UpdateMedia, .ReadOutbox, .ReadGroupFeedInbox, .MergePeerPresences, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .UpdateCachedPeerData, .UpdatePinnedItemIds, .ReadMessageContents, .UpdateMessageImpressionCount, .UpdateMessageForwardsCount, .UpdateInstalledStickerPacks, .UpdateRecentGifs, .UpdateChatInputState, .UpdateCall, .AddCallSignalingData, .UpdateLangPack, .UpdateMinAvailableMessage, .UpdatePeerChatUnreadMark, .UpdateIsContact, .UpdatePeerChatInclusion, .UpdatePeersNearby, .UpdateTheme, .SyncChatListFilters, .UpdateChatListFilterOrder, .UpdateChatListFilter, .UpdateReadThread, .UpdateGroupCallParticipants, .UpdateGroupCall, .UpdateMessagesPinned, .UpdateAutoremoveTimeout:
|
case .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMessagePoll, .UpdateMessageReactions, .UpdateMedia, .ReadOutbox, .ReadGroupFeedInbox, .MergePeerPresences, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .UpdateCachedPeerData, .UpdatePinnedItemIds, .ReadMessageContents, .UpdateMessageImpressionCount, .UpdateMessageForwardsCount, .UpdateInstalledStickerPacks, .UpdateRecentGifs, .UpdateChatInputState, .UpdateCall, .AddCallSignalingData, .UpdateLangPack, .UpdateMinAvailableMessage, .UpdatePeerChatUnreadMark, .UpdateIsContact, .UpdatePeerChatInclusion, .UpdatePeersNearby, .UpdateTheme, .SyncChatListFilters, .UpdateChatListFilterOrder, .UpdateChatListFilter, .UpdateReadThread, .UpdateGroupCallParticipants, .UpdateGroupCall, .UpdateMessagesPinned, .UpdateAutoremoveTimeout:
|
||||||
break
|
break
|
||||||
case let .AddMessages(messages, location):
|
case let .AddMessages(messages, location):
|
||||||
for message in messages {
|
for message in messages {
|
||||||
|
@ -5,7 +5,7 @@ import TelegramApi
|
|||||||
extension ReactionsMessageAttribute {
|
extension ReactionsMessageAttribute {
|
||||||
func withUpdatedResults(_ reactions: Api.MessageReactions) -> ReactionsMessageAttribute {
|
func withUpdatedResults(_ reactions: Api.MessageReactions) -> ReactionsMessageAttribute {
|
||||||
switch reactions {
|
switch reactions {
|
||||||
case let .messageReactions(flags, results, _):
|
case let .messageReactions(flags, results, recentReactions):
|
||||||
let min = (flags & (1 << 0)) != 0
|
let min = (flags & (1 << 0)) != 0
|
||||||
var reactions = results.map { result -> MessageReaction in
|
var reactions = results.map { result -> MessageReaction in
|
||||||
switch result {
|
switch result {
|
||||||
@ -13,6 +13,18 @@ extension ReactionsMessageAttribute {
|
|||||||
return MessageReaction(value: reaction, count: count, isSelected: (flags & (1 << 0)) != 0)
|
return MessageReaction(value: reaction, count: count, isSelected: (flags & (1 << 0)) != 0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
let parsedRecentReactions: [ReactionsMessageAttribute.RecentPeer]
|
||||||
|
if let recentReactions = recentReactions {
|
||||||
|
parsedRecentReactions = recentReactions.map { recentReaction -> ReactionsMessageAttribute.RecentPeer in
|
||||||
|
switch recentReaction {
|
||||||
|
case let .messageUserReaction(userId, reaction):
|
||||||
|
return ReactionsMessageAttribute.RecentPeer(value: reaction, peerId: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
parsedRecentReactions = []
|
||||||
|
}
|
||||||
|
|
||||||
if min {
|
if min {
|
||||||
var currentSelectedReaction: String?
|
var currentSelectedReaction: String?
|
||||||
for reaction in self.reactions {
|
for reaction in self.reactions {
|
||||||
@ -29,7 +41,7 @@ extension ReactionsMessageAttribute {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ReactionsMessageAttribute(reactions: reactions)
|
return ReactionsMessageAttribute(reactions: reactions, recentPeers: parsedRecentReactions)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -47,6 +59,7 @@ public func mergedMessageReactions(attributes: [MessageAttribute]) -> ReactionsM
|
|||||||
|
|
||||||
if let pending = pending {
|
if let pending = pending {
|
||||||
var reactions = current?.reactions ?? []
|
var reactions = current?.reactions ?? []
|
||||||
|
let recentPeers = current?.recentPeers ?? []
|
||||||
if let value = pending.value {
|
if let value = pending.value {
|
||||||
var found = false
|
var found = false
|
||||||
for i in 0 ..< reactions.count {
|
for i in 0 ..< reactions.count {
|
||||||
@ -73,7 +86,7 @@ public func mergedMessageReactions(attributes: [MessageAttribute]) -> ReactionsM
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !reactions.isEmpty {
|
if !reactions.isEmpty {
|
||||||
return ReactionsMessageAttribute(reactions: reactions)
|
return ReactionsMessageAttribute(reactions: reactions, recentPeers: recentPeers)
|
||||||
} else {
|
} else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -87,13 +100,28 @@ public func mergedMessageReactions(attributes: [MessageAttribute]) -> ReactionsM
|
|||||||
extension ReactionsMessageAttribute {
|
extension ReactionsMessageAttribute {
|
||||||
convenience init(apiReactions: Api.MessageReactions) {
|
convenience init(apiReactions: Api.MessageReactions) {
|
||||||
switch apiReactions {
|
switch apiReactions {
|
||||||
case let .messageReactions(_, results, _):
|
case let .messageReactions(_, results, recentReactions):
|
||||||
self.init(reactions: results.map { result in
|
let parsedRecentReactions: [ReactionsMessageAttribute.RecentPeer]
|
||||||
|
if let recentReactions = recentReactions {
|
||||||
|
parsedRecentReactions = recentReactions.map { recentReaction -> ReactionsMessageAttribute.RecentPeer in
|
||||||
|
switch recentReaction {
|
||||||
|
case let .messageUserReaction(userId, reaction):
|
||||||
|
return ReactionsMessageAttribute.RecentPeer(value: reaction, peerId: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
parsedRecentReactions = []
|
||||||
|
}
|
||||||
|
|
||||||
|
self.init(
|
||||||
|
reactions: results.map { result in
|
||||||
switch result {
|
switch result {
|
||||||
case let .reactionCount(flags, reaction, count):
|
case let .reactionCount(flags, reaction, count):
|
||||||
return MessageReaction(value: reaction, count: count, isSelected: (flags & (1 << 0)) != 0)
|
return MessageReaction(value: reaction, count: count, isSelected: (flags & (1 << 0)) != 0)
|
||||||
}
|
}
|
||||||
})
|
},
|
||||||
|
recentPeers: parsedRecentReactions
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1473,6 +1473,8 @@ private func finalStateWithUpdatesAndServerTime(postbox: Postbox, network: Netwo
|
|||||||
return current
|
return current
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
case let .updateMessageReactions(peer, msgId, reactions):
|
||||||
|
updatedState.updateMessageReactions(MessageId(peerId: peer.peerId, namespace: Namespaces.Message.Cloud, id: msgId), reactions: reactions)
|
||||||
default:
|
default:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@ -2260,7 +2262,7 @@ private func optimizedOperations(_ operations: [AccountStateMutationOperation])
|
|||||||
var currentAddScheduledMessages: OptimizeAddMessagesState?
|
var currentAddScheduledMessages: OptimizeAddMessagesState?
|
||||||
for operation in operations {
|
for operation in operations {
|
||||||
switch operation {
|
switch operation {
|
||||||
case .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMessagePoll/*, .UpdateMessageReactions*/, .UpdateMedia, .MergeApiChats, .MergeApiUsers, .MergePeerPresences, .UpdatePeer, .ReadInbox, .ReadOutbox, .ReadGroupFeedInbox, .ResetReadState, .ResetIncomingReadState, .UpdatePeerChatUnreadMark, .ResetMessageTagSummary, .UpdateNotificationSettings, .UpdateGlobalNotificationSettings, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .UpdateCachedPeerData, .UpdatePinnedItemIds, .ReadMessageContents, .UpdateMessageImpressionCount, .UpdateMessageForwardsCount, .UpdateInstalledStickerPacks, .UpdateRecentGifs, .UpdateChatInputState, .UpdateCall, .AddCallSignalingData, .UpdateLangPack, .UpdateMinAvailableMessage, .UpdateIsContact, .UpdatePeerChatInclusion, .UpdatePeersNearby, .UpdateTheme, .SyncChatListFilters, .UpdateChatListFilter, .UpdateChatListFilterOrder, .UpdateReadThread, .UpdateMessagesPinned, .UpdateGroupCallParticipants, .UpdateGroupCall, .UpdateAutoremoveTimeout:
|
case .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMessagePoll, .UpdateMessageReactions, .UpdateMedia, .MergeApiChats, .MergeApiUsers, .MergePeerPresences, .UpdatePeer, .ReadInbox, .ReadOutbox, .ReadGroupFeedInbox, .ResetReadState, .ResetIncomingReadState, .UpdatePeerChatUnreadMark, .ResetMessageTagSummary, .UpdateNotificationSettings, .UpdateGlobalNotificationSettings, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .UpdateCachedPeerData, .UpdatePinnedItemIds, .ReadMessageContents, .UpdateMessageImpressionCount, .UpdateMessageForwardsCount, .UpdateInstalledStickerPacks, .UpdateRecentGifs, .UpdateChatInputState, .UpdateCall, .AddCallSignalingData, .UpdateLangPack, .UpdateMinAvailableMessage, .UpdateIsContact, .UpdatePeerChatInclusion, .UpdatePeersNearby, .UpdateTheme, .SyncChatListFilters, .UpdateChatListFilter, .UpdateChatListFilterOrder, .UpdateReadThread, .UpdateMessagesPinned, .UpdateGroupCallParticipants, .UpdateGroupCall, .UpdateAutoremoveTimeout:
|
||||||
if let currentAddMessages = currentAddMessages, !currentAddMessages.messages.isEmpty {
|
if let currentAddMessages = currentAddMessages, !currentAddMessages.messages.isEmpty {
|
||||||
result.append(.AddMessages(currentAddMessages.messages, currentAddMessages.location))
|
result.append(.AddMessages(currentAddMessages.messages, currentAddMessages.location))
|
||||||
}
|
}
|
||||||
@ -3222,6 +3224,31 @@ func replayFinalState(
|
|||||||
return state
|
return state
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
case let .UpdateMessageReactions(messageId, reactions):
|
||||||
|
transaction.updateMessage(messageId, update: { currentMessage in
|
||||||
|
var updatedReactions = ReactionsMessageAttribute(apiReactions: reactions)
|
||||||
|
|
||||||
|
let storeForwardInfo = currentMessage.forwardInfo.flatMap(StoreMessageForwardInfo.init)
|
||||||
|
var attributes = currentMessage.attributes
|
||||||
|
var added = false
|
||||||
|
loop: for j in 0 ..< attributes.count {
|
||||||
|
if let attribute = attributes[j] as? ReactionsMessageAttribute {
|
||||||
|
added = true
|
||||||
|
updatedReactions = attribute.withUpdatedResults(reactions)
|
||||||
|
|
||||||
|
if updatedReactions.reactions == attribute.reactions {
|
||||||
|
return .skip
|
||||||
|
}
|
||||||
|
attributes[j] = updatedReactions
|
||||||
|
break loop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !added {
|
||||||
|
attributes.append(updatedReactions)
|
||||||
|
}
|
||||||
|
|
||||||
|
return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media))
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -856,13 +856,16 @@ public final class AccountViewTracker {
|
|||||||
switch update {
|
switch update {
|
||||||
case let .updateMessageReactions(peer, msgId, reactions):
|
case let .updateMessageReactions(peer, msgId, reactions):
|
||||||
transaction.updateMessage(MessageId(peerId: peer.peerId, namespace: Namespaces.Message.Cloud, id: msgId), update: { currentMessage in
|
transaction.updateMessage(MessageId(peerId: peer.peerId, namespace: Namespaces.Message.Cloud, id: msgId), update: { currentMessage in
|
||||||
|
var updatedReactions = ReactionsMessageAttribute(apiReactions: reactions)
|
||||||
let updatedReactions = ReactionsMessageAttribute(apiReactions: reactions)
|
|
||||||
|
|
||||||
let storeForwardInfo = currentMessage.forwardInfo.flatMap(StoreMessageForwardInfo.init)
|
let storeForwardInfo = currentMessage.forwardInfo.flatMap(StoreMessageForwardInfo.init)
|
||||||
|
var added = false
|
||||||
var attributes = currentMessage.attributes
|
var attributes = currentMessage.attributes
|
||||||
loop: for j in 0 ..< attributes.count {
|
loop: for j in 0 ..< attributes.count {
|
||||||
if let attribute = attributes[j] as? ReactionsMessageAttribute {
|
if let attribute = attributes[j] as? ReactionsMessageAttribute {
|
||||||
|
added = true
|
||||||
|
updatedReactions = attribute.withUpdatedResults(reactions)
|
||||||
|
|
||||||
if updatedReactions.reactions == attribute.reactions {
|
if updatedReactions.reactions == attribute.reactions {
|
||||||
return .skip
|
return .skip
|
||||||
}
|
}
|
||||||
@ -870,6 +873,9 @@ public final class AccountViewTracker {
|
|||||||
break loop
|
break loop
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if !added {
|
||||||
|
attributes.append(updatedReactions)
|
||||||
|
}
|
||||||
return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media))
|
return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media))
|
||||||
})
|
})
|
||||||
default:
|
default:
|
||||||
|
@ -6,6 +6,12 @@ public struct MessageReaction: Equatable, PostboxCoding {
|
|||||||
public var isSelected: Bool
|
public var isSelected: Bool
|
||||||
|
|
||||||
public init(value: String, count: Int32, isSelected: Bool) {
|
public init(value: String, count: Int32, isSelected: Bool) {
|
||||||
|
var value = value
|
||||||
|
|
||||||
|
if value == "❤️" {
|
||||||
|
value = "❤"
|
||||||
|
}
|
||||||
|
|
||||||
self.value = value
|
self.value = value
|
||||||
self.count = count
|
self.count = count
|
||||||
self.isSelected = isSelected
|
self.isSelected = isSelected
|
||||||
@ -24,19 +30,53 @@ public struct MessageReaction: Equatable, PostboxCoding {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public final class ReactionsMessageAttribute: MessageAttribute {
|
public final class ReactionsMessageAttribute: Equatable, MessageAttribute {
|
||||||
public let reactions: [MessageReaction]
|
public struct RecentPeer: Equatable, PostboxCoding {
|
||||||
|
public var value: String
|
||||||
|
public var peerId: PeerId
|
||||||
|
|
||||||
public init(reactions: [MessageReaction]) {
|
public init(value: String, peerId: PeerId) {
|
||||||
|
self.value = value
|
||||||
|
self.peerId = peerId
|
||||||
|
}
|
||||||
|
|
||||||
|
public init(decoder: PostboxDecoder) {
|
||||||
|
self.value = decoder.decodeStringForKey("v", orElse: "")
|
||||||
|
self.peerId = PeerId(decoder.decodeInt64ForKey("p", orElse: 0))
|
||||||
|
}
|
||||||
|
|
||||||
|
public func encode(_ encoder: PostboxEncoder) {
|
||||||
|
encoder.encodeString(self.value, forKey: "v")
|
||||||
|
encoder.encodeInt64(self.peerId.toInt64(), forKey: "p")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public let reactions: [MessageReaction]
|
||||||
|
public let recentPeers: [RecentPeer]
|
||||||
|
|
||||||
|
public init(reactions: [MessageReaction], recentPeers: [RecentPeer]) {
|
||||||
self.reactions = reactions
|
self.reactions = reactions
|
||||||
|
self.recentPeers = recentPeers
|
||||||
}
|
}
|
||||||
|
|
||||||
required public init(decoder: PostboxDecoder) {
|
required public init(decoder: PostboxDecoder) {
|
||||||
self.reactions = decoder.decodeObjectArrayWithDecoderForKey("r")
|
self.reactions = decoder.decodeObjectArrayWithDecoderForKey("r")
|
||||||
|
self.recentPeers = decoder.decodeObjectArrayWithDecoderForKey("rp")
|
||||||
}
|
}
|
||||||
|
|
||||||
public func encode(_ encoder: PostboxEncoder) {
|
public func encode(_ encoder: PostboxEncoder) {
|
||||||
encoder.encodeObjectArray(self.reactions, forKey: "r")
|
encoder.encodeObjectArray(self.reactions, forKey: "r")
|
||||||
|
encoder.encodeObjectArray(self.recentPeers, forKey: "rp")
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func ==(lhs: ReactionsMessageAttribute, rhs: ReactionsMessageAttribute) -> Bool {
|
||||||
|
if lhs.reactions != rhs.reactions {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.recentPeers != rhs.recentPeers {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -945,6 +945,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
guard let topMessage = messages.first else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
let _ = combineLatest(queue: .mainQueue(),
|
let _ = combineLatest(queue: .mainQueue(),
|
||||||
contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState: strongSelf.presentationInterfaceState, context: strongSelf.context, messages: updatedMessages, controllerInteraction: strongSelf.controllerInteraction, selectAll: selectAll, interfaceInteraction: strongSelf.interfaceInteraction),
|
contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState: strongSelf.presentationInterfaceState, context: strongSelf.context, messages: updatedMessages, controllerInteraction: strongSelf.controllerInteraction, selectAll: selectAll, interfaceInteraction: strongSelf.interfaceInteraction),
|
||||||
strongSelf.context.engine.stickers.availableReactions(),
|
strongSelf.context.engine.stickers.availableReactions(),
|
||||||
@ -991,7 +995,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
|
|
||||||
actions.context = strongSelf.context
|
actions.context = strongSelf.context
|
||||||
|
|
||||||
if canAddMessageReactions(message: message), let availableReactions = availableReactions {
|
if canAddMessageReactions(message: topMessage), let availableReactions = availableReactions {
|
||||||
for reaction in availableReactions.reactions {
|
for reaction in availableReactions.reactions {
|
||||||
actions.reactionItems.append(ReactionContextItem(
|
actions.reactionItems.append(ReactionContextItem(
|
||||||
reaction: ReactionContextItem.Reaction(rawValue: reaction.value),
|
reaction: ReactionContextItem.Reaction(rawValue: reaction.value),
|
||||||
@ -1006,12 +1010,12 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
strongSelf.currentContextController = controller
|
strongSelf.currentContextController = controller
|
||||||
|
|
||||||
controller.reactionSelected = { [weak controller] value in
|
controller.reactionSelected = { [weak controller] value in
|
||||||
guard let strongSelf = self, let message = updatedMessages.first else {
|
guard let strongSelf = self, let message = messages.first else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var updatedReaction: String? = value.reaction.rawValue
|
var updatedReaction: String? = value.reaction.rawValue
|
||||||
for attribute in messages[0].attributes {
|
for attribute in topMessage.attributes {
|
||||||
if let attribute = attribute as? ReactionsMessageAttribute {
|
if let attribute = attribute as? ReactionsMessageAttribute {
|
||||||
for reaction in attribute.reactions {
|
for reaction in attribute.reactions {
|
||||||
if reaction.value == value.reaction.rawValue {
|
if reaction.value == value.reaction.rawValue {
|
||||||
@ -1047,32 +1051,6 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
let _ = strongSelf
|
let _ = strongSelf
|
||||||
let _ = itemNode
|
let _ = itemNode
|
||||||
let _ = targetView
|
let _ = targetView
|
||||||
|
|
||||||
/*let targetFrame = targetFilledNode.view.convert(targetFilledNode.bounds, to: itemNode.view).offsetBy(dx: 0.0, dy: itemNode.insets.top)
|
|
||||||
|
|
||||||
if #available(iOS 13.0, *), let meshAnimation = strongSelf.context.meshAnimationCache.get(bundleName: "Hearts") {
|
|
||||||
if let animationView = MeshRenderer() {
|
|
||||||
let animationFrame = CGRect(origin: CGPoint(x: targetFrame.midX - 200.0 / 2.0, y: targetFrame.midY - 200.0 / 2.0), size: CGSize(width: 200.0, height: 200.0)).offsetBy(dx: -50.0, dy: 0.0)
|
|
||||||
animationView.frame = animationFrame
|
|
||||||
|
|
||||||
var removeNode: (() -> Void)?
|
|
||||||
|
|
||||||
animationView.allAnimationsCompleted = {
|
|
||||||
removeNode?()
|
|
||||||
}
|
|
||||||
|
|
||||||
let overlayMeshAnimationNode = strongSelf.chatDisplayNode.messageTransitionNode.add(decorationView: animationView, itemNode: itemNode)
|
|
||||||
|
|
||||||
removeNode = { [weak overlayMeshAnimationNode] in
|
|
||||||
guard let strongSelf = self, let overlayMeshAnimationNode = overlayMeshAnimationNode else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
strongSelf.chatDisplayNode.messageTransitionNode.remove(decorationNode: overlayMeshAnimationNode)
|
|
||||||
}
|
|
||||||
|
|
||||||
animationView.add(mesh: meshAnimation, offset: CGPoint())
|
|
||||||
}
|
|
||||||
}*/
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -1096,11 +1074,16 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
strongSelf.window?.presentInGlobalOverlay(controller)
|
strongSelf.window?.presentInGlobalOverlay(controller)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}, updateMessageReaction: { [weak self] message, value in
|
}, updateMessageReaction: { [weak self] initialMessage, value in
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
guard let messages = strongSelf.chatDisplayNode.historyNode.messageGroupInCurrentHistoryView(initialMessage.id) else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
guard let message = messages.first else {
|
||||||
|
return
|
||||||
|
}
|
||||||
if !canAddMessageReactions(message: message) {
|
if !canAddMessageReactions(message: message) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -1049,7 +1049,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
|||||||
strongSelf.contextSourceNode.contentNode.frame = CGRect(origin: CGPoint(), size: layoutSize)
|
strongSelf.contextSourceNode.contentNode.frame = CGRect(origin: CGPoint(), size: layoutSize)
|
||||||
|
|
||||||
var transition: ContainedViewLayoutTransition = .immediate
|
var transition: ContainedViewLayoutTransition = .immediate
|
||||||
if case let .System(duration) = animation {
|
if case let .System(duration, _) = animation {
|
||||||
if let subject = item.associatedData.subject, case .forwardedMessages = subject {
|
if let subject = item.associatedData.subject, case .forwardedMessages = subject {
|
||||||
transition = .animated(duration: duration, curve: .linear)
|
transition = .animated(duration: duration, curve: .linear)
|
||||||
} else {
|
} else {
|
||||||
@ -1122,7 +1122,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
|||||||
strongSelf.shareButtonNode = nil
|
strongSelf.shareButtonNode = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
dateAndStatusApply(false)
|
dateAndStatusApply(.None)
|
||||||
strongSelf.dateAndStatusNode.frame = CGRect(origin: CGPoint(x: max(displayLeftInset, updatedImageFrame.maxX - dateAndStatusSize.width - 4.0), y: updatedImageFrame.maxY - dateAndStatusSize.height - 4.0), size: dateAndStatusSize)
|
strongSelf.dateAndStatusNode.frame = CGRect(origin: CGPoint(x: max(displayLeftInset, updatedImageFrame.maxX - dateAndStatusSize.width - 4.0), y: updatedImageFrame.maxY - dateAndStatusSize.height - 4.0), size: dateAndStatusSize)
|
||||||
|
|
||||||
if needsReplyBackground {
|
if needsReplyBackground {
|
||||||
@ -1296,7 +1296,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
|||||||
}
|
}
|
||||||
strongSelf.addSubnode(actionButtonsNode)
|
strongSelf.addSubnode(actionButtonsNode)
|
||||||
} else {
|
} else {
|
||||||
if case let .System(duration) = animation {
|
if case let .System(duration, _) = animation {
|
||||||
actionButtonsNode.layer.animateFrame(from: previousFrame, to: actionButtonsFrame, duration: duration, timingFunction: kCAMediaTimingFunctionSpring)
|
actionButtonsNode.layer.animateFrame(from: previousFrame, to: actionButtonsFrame, duration: duration, timingFunction: kCAMediaTimingFunctionSpring)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -360,8 +360,8 @@ final class ChatMessageAttachedContentNode: ASDisplayNode {
|
|||||||
var updateInlineImageSignal: Signal<(TransformImageArguments) -> DrawingContext?, NoError>?
|
var updateInlineImageSignal: Signal<(TransformImageArguments) -> DrawingContext?, NoError>?
|
||||||
var textCutout = TextNodeCutout()
|
var textCutout = TextNodeCutout()
|
||||||
var initialWidth: CGFloat = CGFloat.greatestFiniteMagnitude
|
var initialWidth: CGFloat = CGFloat.greatestFiniteMagnitude
|
||||||
var refineContentImageLayout: ((CGSize, Bool, Bool, ImageCorners) -> (CGFloat, (CGFloat) -> (CGSize, (ContainedViewLayoutTransition, Bool) -> ChatMessageInteractiveMediaNode)))?
|
var refineContentImageLayout: ((CGSize, Bool, Bool, ImageCorners) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool) -> ChatMessageInteractiveMediaNode)))?
|
||||||
var refineContentFileLayout: ((CGSize) -> (CGFloat, (CGFloat) -> (CGSize, (Bool) -> ChatMessageInteractiveFileNode)))?
|
var refineContentFileLayout: ((CGSize) -> (CGFloat, (CGFloat) -> (CGSize, (Bool, ListViewItemUpdateAnimation) -> ChatMessageInteractiveFileNode)))?
|
||||||
|
|
||||||
var contentInstantVideoSizeAndApply: (ChatMessageInstantVideoItemLayoutResult, (ChatMessageInstantVideoItemLayoutData, ContainedViewLayoutTransition) -> ChatMessageInteractiveInstantVideoNode)?
|
var contentInstantVideoSizeAndApply: (ChatMessageInstantVideoItemLayoutResult, (ChatMessageInstantVideoItemLayoutData, ContainedViewLayoutTransition) -> ChatMessageInteractiveInstantVideoNode)?
|
||||||
|
|
||||||
@ -514,7 +514,7 @@ final class ChatMessageAttachedContentNode: ASDisplayNode {
|
|||||||
} else if file.isInstantVideo {
|
} else if file.isInstantVideo {
|
||||||
let displaySize = CGSize(width: 212.0, height: 212.0)
|
let displaySize = CGSize(width: 212.0, height: 212.0)
|
||||||
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 (videoLayout, apply) = contentInstantVideoLayout(ChatMessageBubbleContentItem(context: context, controllerInteraction: controllerInteraction, message: message, read: messageRead, chatLocation: chatLocation, presentationData: presentationData, associatedData: associatedData, attributes: attributes, isItemPinned: message.tags.contains(.pinned) && !isReplyThread, isItemEdited: false), constrainedSize.width - horizontalInsets.left - horizontalInsets.right, displaySize, displaySize, 0.0, .bubble, automaticDownload)
|
let (videoLayout, apply) = contentInstantVideoLayout(ChatMessageBubbleContentItem(context: context, controllerInteraction: controllerInteraction, message: message, topMessage: message, read: messageRead, chatLocation: chatLocation, presentationData: presentationData, associatedData: associatedData, attributes: attributes, isItemPinned: message.tags.contains(.pinned) && !isReplyThread, isItemEdited: false), constrainedSize.width - horizontalInsets.left - horizontalInsets.right, displaySize, displaySize, 0.0, .bubble, automaticDownload)
|
||||||
initialWidth = videoLayout.contentSize.width + videoLayout.overflowLeft + videoLayout.overflowRight
|
initialWidth = videoLayout.contentSize.width + videoLayout.overflowLeft + videoLayout.overflowRight
|
||||||
contentInstantVideoSizeAndApply = (videoLayout, apply)
|
contentInstantVideoSizeAndApply = (videoLayout, apply)
|
||||||
} else if file.isVideo {
|
} else if file.isVideo {
|
||||||
@ -564,7 +564,7 @@ final class ChatMessageAttachedContentNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let (_, refineLayout) = contentFileLayout(context, presentationData, message, associatedData, chatLocation, attributes, message.tags.contains(.pinned) && !associatedData.isInPinnedListMode && !isReplyThread, false, file, automaticDownload, message.effectivelyIncoming(context.account.peerId), false, associatedData.forcedResourceStatus, statusType, nil, CGSize(width: constrainedSize.width - horizontalInsets.left - horizontalInsets.right, height: constrainedSize.height))
|
let (_, refineLayout) = contentFileLayout(context, presentationData, message, message, associatedData, chatLocation, attributes, message.tags.contains(.pinned) && !associatedData.isInPinnedListMode && !isReplyThread, false, file, automaticDownload, message.effectivelyIncoming(context.account.peerId), false, associatedData.forcedResourceStatus, statusType, nil, CGSize(width: constrainedSize.width - horizontalInsets.left - horizontalInsets.right, height: constrainedSize.height))
|
||||||
refineContentFileLayout = refineLayout
|
refineContentFileLayout = refineLayout
|
||||||
}
|
}
|
||||||
} else if let image = media as? TelegramMediaImage {
|
} else if let image = media as? TelegramMediaImage {
|
||||||
@ -625,7 +625,7 @@ final class ChatMessageAttachedContentNode: ASDisplayNode {
|
|||||||
|
|
||||||
let (textLayout, textApply) = textAsyncLayout(TextNodeLayoutArguments(attributedString: textString, backgroundColor: nil, maximumNumberOfLines: 12, truncationType: .end, constrainedSize: textConstrainedSize, alignment: .natural, cutout: upatedTextCutout, insets: UIEdgeInsets()))
|
let (textLayout, textApply) = textAsyncLayout(TextNodeLayoutArguments(attributedString: textString, backgroundColor: nil, maximumNumberOfLines: 12, truncationType: .end, constrainedSize: textConstrainedSize, alignment: .natural, cutout: upatedTextCutout, insets: UIEdgeInsets()))
|
||||||
|
|
||||||
var statusSuggestedWidthAndContinue: (CGFloat, (CGFloat) -> (CGSize, (Bool) -> Void))?
|
var statusSuggestedWidthAndContinue: (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation) -> Void))?
|
||||||
if statusInText, let textStatusType = textStatusType {
|
if statusInText, let textStatusType = textStatusType {
|
||||||
statusSuggestedWidthAndContinue = statusLayout(ChatMessageDateAndStatusNode.Arguments(
|
statusSuggestedWidthAndContinue = statusLayout(ChatMessageDateAndStatusNode.Arguments(
|
||||||
context: context,
|
context: context,
|
||||||
@ -634,7 +634,7 @@ final class ChatMessageAttachedContentNode: ASDisplayNode {
|
|||||||
impressionCount: viewCount,
|
impressionCount: viewCount,
|
||||||
dateText: dateText,
|
dateText: dateText,
|
||||||
type: textStatusType,
|
type: textStatusType,
|
||||||
layoutInput: .trailingContent(contentWidth: textLayout.trailingLineWidth, preferAdditionalInset: false),
|
layoutInput: .trailingContent(contentWidth: textLayout.trailingLineWidth, reactionSettings: nil),
|
||||||
constrainedSize: textConstrainedSize,
|
constrainedSize: textConstrainedSize,
|
||||||
availableReactions: associatedData.availableReactions,
|
availableReactions: associatedData.availableReactions,
|
||||||
reactions: dateReactions,
|
reactions: dateReactions,
|
||||||
@ -666,14 +666,14 @@ final class ChatMessageAttachedContentNode: ASDisplayNode {
|
|||||||
boundingSize.width = max(boundingSize.width, statusSuggestedWidthAndContinue.0)
|
boundingSize.width = max(boundingSize.width, statusSuggestedWidthAndContinue.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
var finalizeContentImageLayout: ((CGFloat) -> (CGSize, (ContainedViewLayoutTransition, Bool) -> ChatMessageInteractiveMediaNode))?
|
var finalizeContentImageLayout: ((CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool) -> ChatMessageInteractiveMediaNode))?
|
||||||
if let refineContentImageLayout = refineContentImageLayout {
|
if let refineContentImageLayout = refineContentImageLayout {
|
||||||
let (refinedWidth, finalizeImageLayout) = refineContentImageLayout(textConstrainedSize, automaticPlayback, true, ImageCorners(radius: 4.0))
|
let (refinedWidth, finalizeImageLayout) = refineContentImageLayout(textConstrainedSize, automaticPlayback, true, ImageCorners(radius: 4.0))
|
||||||
finalizeContentImageLayout = finalizeImageLayout
|
finalizeContentImageLayout = finalizeImageLayout
|
||||||
|
|
||||||
boundingSize.width = max(boundingSize.width, refinedWidth)
|
boundingSize.width = max(boundingSize.width, refinedWidth)
|
||||||
}
|
}
|
||||||
var finalizeContentFileLayout: ((CGFloat) -> (CGSize, (Bool) -> ChatMessageInteractiveFileNode))?
|
var finalizeContentFileLayout: ((CGFloat) -> (CGSize, (Bool, ListViewItemUpdateAnimation) -> ChatMessageInteractiveFileNode))?
|
||||||
if let refineContentFileLayout = refineContentFileLayout {
|
if let refineContentFileLayout = refineContentFileLayout {
|
||||||
let (refinedWidth, finalizeFileLayout) = refineContentFileLayout(textConstrainedSize)
|
let (refinedWidth, finalizeFileLayout) = refineContentFileLayout(textConstrainedSize)
|
||||||
finalizeContentFileLayout = finalizeFileLayout
|
finalizeContentFileLayout = finalizeFileLayout
|
||||||
@ -740,7 +740,7 @@ final class ChatMessageAttachedContentNode: ASDisplayNode {
|
|||||||
imageFrame = CGRect(origin: CGPoint(x: boundingWidth - inlineImageSize.width - insets.right, y: 0.0), size: inlineImageSize)
|
imageFrame = CGRect(origin: CGPoint(x: boundingWidth - inlineImageSize.width - insets.right, y: 0.0), size: inlineImageSize)
|
||||||
}
|
}
|
||||||
|
|
||||||
var contentImageSizeAndApply: (CGSize, (ContainedViewLayoutTransition, Bool) -> ChatMessageInteractiveMediaNode)?
|
var contentImageSizeAndApply: (CGSize, (ListViewItemUpdateAnimation, Bool) -> ChatMessageInteractiveMediaNode)?
|
||||||
if let finalizeContentImageLayout = finalizeContentImageLayout {
|
if let finalizeContentImageLayout = finalizeContentImageLayout {
|
||||||
let (size, apply) = finalizeContentImageLayout(boundingWidth - insets.left - insets.right)
|
let (size, apply) = finalizeContentImageLayout(boundingWidth - insets.left - insets.right)
|
||||||
contentImageSizeAndApply = (size, apply)
|
contentImageSizeAndApply = (size, apply)
|
||||||
@ -754,7 +754,7 @@ final class ChatMessageAttachedContentNode: ASDisplayNode {
|
|||||||
adjustedLineHeight += imageHeightAddition + 4.0
|
adjustedLineHeight += imageHeightAddition + 4.0
|
||||||
}
|
}
|
||||||
|
|
||||||
var contentFileSizeAndApply: (CGSize, (Bool) -> ChatMessageInteractiveFileNode)?
|
var contentFileSizeAndApply: (CGSize, (Bool, ListViewItemUpdateAnimation) -> ChatMessageInteractiveFileNode)?
|
||||||
if let finalizeContentFileLayout = finalizeContentFileLayout {
|
if let finalizeContentFileLayout = finalizeContentFileLayout {
|
||||||
let (size, apply) = finalizeContentFileLayout(boundingWidth - insets.left - insets.right)
|
let (size, apply) = finalizeContentFileLayout(boundingWidth - insets.left - insets.right)
|
||||||
contentFileSizeAndApply = (size, apply)
|
contentFileSizeAndApply = (size, apply)
|
||||||
@ -788,12 +788,13 @@ final class ChatMessageAttachedContentNode: ASDisplayNode {
|
|||||||
adjustedBoundingSize.height += 7.0 + size.height
|
adjustedBoundingSize.height += 7.0 + size.height
|
||||||
}
|
}
|
||||||
|
|
||||||
var statusSizeAndApply: ((CGSize), (Bool) -> Void)?
|
var statusSizeAndApply: ((CGSize), (ListViewItemUpdateAnimation) -> Void)?
|
||||||
if let statusSuggestedWidthAndContinue = statusSuggestedWidthAndContinue {
|
if let statusSuggestedWidthAndContinue = statusSuggestedWidthAndContinue {
|
||||||
statusSizeAndApply = statusSuggestedWidthAndContinue.1(boundingWidth - insets.left - insets.right)
|
statusSizeAndApply = statusSuggestedWidthAndContinue.1(boundingWidth - insets.left - insets.right)
|
||||||
}
|
}
|
||||||
if let statusSizeAndApply = statusSizeAndApply {
|
if let statusSizeAndApply = statusSizeAndApply {
|
||||||
adjustedBoundingSize.height += statusSizeAndApply.0.height
|
adjustedBoundingSize.height += statusSizeAndApply.0.height
|
||||||
|
adjustedLineHeight += statusSizeAndApply.0.height
|
||||||
}
|
}
|
||||||
|
|
||||||
/*var adjustedStatusFrame: CGRect?
|
/*var adjustedStatusFrame: CGRect?
|
||||||
@ -815,7 +816,7 @@ final class ChatMessageAttachedContentNode: ASDisplayNode {
|
|||||||
switch animation {
|
switch animation {
|
||||||
case .None, .Crossfade:
|
case .None, .Crossfade:
|
||||||
hasAnimation = false
|
hasAnimation = false
|
||||||
case let .System(duration):
|
case let .System(duration, _):
|
||||||
hasAnimation = true
|
hasAnimation = true
|
||||||
transition = .animated(duration: duration, curve: .easeInOut)
|
transition = .animated(duration: duration, curve: .easeInOut)
|
||||||
}
|
}
|
||||||
@ -851,7 +852,7 @@ final class ChatMessageAttachedContentNode: ASDisplayNode {
|
|||||||
if let (contentImageSize, contentImageApply) = contentImageSizeAndApply {
|
if let (contentImageSize, contentImageApply) = contentImageSizeAndApply {
|
||||||
contentMediaHeight = contentImageSize.height
|
contentMediaHeight = contentImageSize.height
|
||||||
|
|
||||||
let contentImageNode = contentImageApply(transition, synchronousLoads)
|
let contentImageNode = contentImageApply(animation, synchronousLoads)
|
||||||
if strongSelf.contentImageNode !== contentImageNode {
|
if strongSelf.contentImageNode !== contentImageNode {
|
||||||
strongSelf.contentImageNode = contentImageNode
|
strongSelf.contentImageNode = contentImageNode
|
||||||
contentImageNode.activatePinch = { sourceNode in
|
contentImageNode.activatePinch = { sourceNode in
|
||||||
@ -865,7 +866,7 @@ final class ChatMessageAttachedContentNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
contentImageNode.visibility = strongSelf.visibility != .none
|
contentImageNode.visibility = strongSelf.visibility != .none
|
||||||
}
|
}
|
||||||
let _ = contentImageApply(transition, synchronousLoads)
|
let _ = contentImageApply(animation, synchronousLoads)
|
||||||
let contentImageFrame: CGRect
|
let contentImageFrame: CGRect
|
||||||
if let (_, flags) = mediaAndFlags, flags.contains(.preferMediaBeforeText) {
|
if let (_, flags) = mediaAndFlags, flags.contains(.preferMediaBeforeText) {
|
||||||
contentImageFrame = CGRect(origin: CGPoint(x: insets.left, y: insets.top), size: contentImageSize)
|
contentImageFrame = CGRect(origin: CGPoint(x: insets.left, y: insets.top), size: contentImageSize)
|
||||||
@ -901,7 +902,7 @@ final class ChatMessageAttachedContentNode: ASDisplayNode {
|
|||||||
if let (contentFileSize, contentFileApply) = contentFileSizeAndApply {
|
if let (contentFileSize, contentFileApply) = contentFileSizeAndApply {
|
||||||
contentMediaHeight = contentFileSize.height
|
contentMediaHeight = contentFileSize.height
|
||||||
|
|
||||||
let contentFileNode = contentFileApply(synchronousLoads)
|
let contentFileNode = contentFileApply(synchronousLoads, animation)
|
||||||
if strongSelf.contentFileNode !== contentFileNode {
|
if strongSelf.contentFileNode !== contentFileNode {
|
||||||
strongSelf.contentFileNode = contentFileNode
|
strongSelf.contentFileNode = contentFileNode
|
||||||
strongSelf.addSubnode(contentFileNode)
|
strongSelf.addSubnode(contentFileNode)
|
||||||
@ -949,7 +950,7 @@ final class ChatMessageAttachedContentNode: ASDisplayNode {
|
|||||||
strongSelf.addSubnode(strongSelf.statusNode)
|
strongSelf.addSubnode(strongSelf.statusNode)
|
||||||
}
|
}
|
||||||
strongSelf.statusNode.frame = CGRect(origin: CGPoint(x: strongSelf.textNode.frame.minX, y: strongSelf.textNode.frame.maxY), size: statusSizeAndApply.0)
|
strongSelf.statusNode.frame = CGRect(origin: CGPoint(x: strongSelf.textNode.frame.minX, y: strongSelf.textNode.frame.maxY), size: statusSizeAndApply.0)
|
||||||
statusSizeAndApply.1(animation.isAnimated)
|
statusSizeAndApply.1(animation)
|
||||||
} else if strongSelf.statusNode.supernode != nil {
|
} else if strongSelf.statusNode.supernode != nil {
|
||||||
strongSelf.statusNode.removeFromSupernode()
|
strongSelf.statusNode.removeFromSupernode()
|
||||||
}
|
}
|
||||||
|
@ -93,6 +93,11 @@ class ChatMessageBackground: ASDisplayNode {
|
|||||||
transition.updateFrame(node: self.outlineImageNode, frame: CGRect(origin: CGPoint(), size: size).insetBy(dx: -1.0, dy: -1.0))
|
transition.updateFrame(node: self.outlineImageNode, frame: CGRect(origin: CGPoint(), size: size).insetBy(dx: -1.0, dy: -1.0))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func updateLayout(size: CGSize, transition: ListViewItemUpdateAnimation) {
|
||||||
|
transition.animator.updateFrame(layer: self.imageNode.layer, frame: CGRect(origin: CGPoint(), size: size).insetBy(dx: -1.0, dy: -1.0), completion: nil)
|
||||||
|
transition.animator.updateFrame(layer: self.outlineImageNode.layer, frame: CGRect(origin: CGPoint(), size: size).insetBy(dx: -1.0, dy: -1.0), completion: nil)
|
||||||
|
}
|
||||||
|
|
||||||
func setMaskMode(_ maskMode: Bool) {
|
func setMaskMode(_ maskMode: Bool) {
|
||||||
if let type = self.type, let hasWallpaper = self.hasWallpaper, let highlighted = self.currentHighlighted, let graphics = self.graphics, let backgroundNode = self.backgroundNode {
|
if let type = self.type, let hasWallpaper = self.hasWallpaper, let highlighted = self.currentHighlighted, let graphics = self.graphics, let backgroundNode = self.backgroundNode {
|
||||||
self.setType(type: type, highlighted: highlighted, graphics: graphics, maskMode: maskMode, hasWallpaper: hasWallpaper, transition: .immediate, backgroundNode: backgroundNode)
|
self.setType(type: type, highlighted: highlighted, graphics: graphics, maskMode: maskMode, hasWallpaper: hasWallpaper, transition: .immediate, backgroundNode: backgroundNode)
|
||||||
|
@ -104,6 +104,7 @@ final class ChatMessageBubbleContentItem {
|
|||||||
let context: AccountContext
|
let context: AccountContext
|
||||||
let controllerInteraction: ChatControllerInteraction
|
let controllerInteraction: ChatControllerInteraction
|
||||||
let message: Message
|
let message: Message
|
||||||
|
let topMessage: Message
|
||||||
let read: Bool
|
let read: Bool
|
||||||
let chatLocation: ChatLocation
|
let chatLocation: ChatLocation
|
||||||
let presentationData: ChatPresentationData
|
let presentationData: ChatPresentationData
|
||||||
@ -112,10 +113,11 @@ final class ChatMessageBubbleContentItem {
|
|||||||
let isItemPinned: Bool
|
let isItemPinned: Bool
|
||||||
let isItemEdited: Bool
|
let isItemEdited: Bool
|
||||||
|
|
||||||
init(context: AccountContext, controllerInteraction: ChatControllerInteraction, message: Message, read: Bool, chatLocation: ChatLocation, presentationData: ChatPresentationData, associatedData: ChatMessageItemAssociatedData, attributes: ChatMessageEntryAttributes, isItemPinned: Bool, isItemEdited: Bool) {
|
init(context: AccountContext, controllerInteraction: ChatControllerInteraction, message: Message, topMessage: Message, read: Bool, chatLocation: ChatLocation, presentationData: ChatPresentationData, associatedData: ChatMessageItemAssociatedData, attributes: ChatMessageEntryAttributes, isItemPinned: Bool, isItemEdited: Bool) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.controllerInteraction = controllerInteraction
|
self.controllerInteraction = controllerInteraction
|
||||||
self.message = message
|
self.message = message
|
||||||
|
self.topMessage = topMessage
|
||||||
self.read = read
|
self.read = read
|
||||||
self.chatLocation = chatLocation
|
self.chatLocation = chatLocation
|
||||||
self.presentationData = presentationData
|
self.presentationData = presentationData
|
||||||
|
@ -174,6 +174,13 @@ private func contentNodeMessagesAndClassesForItem(_ item: ChatMessageItem) -> ([
|
|||||||
}
|
}
|
||||||
|
|
||||||
let firstMessage = item.content.firstMessage
|
let firstMessage = item.content.firstMessage
|
||||||
|
|
||||||
|
if let reactionsAttribute = mergedMessageReactions(attributes: firstMessage.attributes), !reactionsAttribute.reactions.isEmpty {
|
||||||
|
if result.last?.1 == ChatMessageWebpageBubbleContentNode.self || result.last?.1 == ChatMessagePollBubbleContentNode.self || result.last?.1 == ChatMessageContactBubbleContentNode.self {
|
||||||
|
result.append((firstMessage, ChatMessageReactionsFooterContentNode.self, ChatMessageEntryAttributes(), BubbleItemAttributes(isAttachment: true, neighborType: .freeform, neighborSpacing: .default)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if !isAction && !Namespaces.Message.allScheduled.contains(firstMessage.id.namespace) {
|
if !isAction && !Namespaces.Message.allScheduled.contains(firstMessage.id.namespace) {
|
||||||
var hasDiscussion = false
|
var hasDiscussion = false
|
||||||
if let channel = firstMessage.peers[firstMessage.id.peerId] as? TelegramChannel, case let .broadcast(info) = channel.info, info.flags.contains(.hasDiscussionGroup) {
|
if let channel = firstMessage.peers[firstMessage.id.peerId] as? TelegramChannel, case let .broadcast(info) = channel.info, info.flags.contains(.hasDiscussionGroup) {
|
||||||
@ -920,7 +927,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
|||||||
forwardInfoLayout: (ChatPresentationData, PresentationStrings, ChatMessageForwardInfoType, Peer?, String?, String?, CGSize) -> (CGSize, (CGFloat) -> ChatMessageForwardInfoNode),
|
forwardInfoLayout: (ChatPresentationData, PresentationStrings, ChatMessageForwardInfoType, Peer?, String?, String?, CGSize) -> (CGSize, (CGFloat) -> ChatMessageForwardInfoNode),
|
||||||
replyInfoLayout: (ChatPresentationData, PresentationStrings, AccountContext, ChatMessageReplyInfoType, Message, CGSize) -> (CGSize, () -> ChatMessageReplyInfoNode),
|
replyInfoLayout: (ChatPresentationData, PresentationStrings, AccountContext, ChatMessageReplyInfoType, Message, CGSize) -> (CGSize, () -> ChatMessageReplyInfoNode),
|
||||||
actionButtonsLayout: (AccountContext, ChatPresentationThemeData, PresentationChatBubbleCorners, PresentationStrings, ReplyMarkupMessageAttribute, Message, CGFloat) -> (minWidth: CGFloat, layout: (CGFloat) -> (CGSize, (Bool) -> ChatMessageActionButtonsNode)),
|
actionButtonsLayout: (AccountContext, ChatPresentationThemeData, PresentationChatBubbleCorners, PresentationStrings, ReplyMarkupMessageAttribute, Message, CGFloat) -> (minWidth: CGFloat, layout: (CGFloat) -> (CGSize, (Bool) -> ChatMessageActionButtonsNode)),
|
||||||
mosaicStatusLayout: (ChatMessageDateAndStatusNode.Arguments) -> (CGFloat, (CGFloat) -> (CGSize, (Bool) -> ChatMessageDateAndStatusNode)),
|
mosaicStatusLayout: (ChatMessageDateAndStatusNode.Arguments) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation) -> ChatMessageDateAndStatusNode)),
|
||||||
layoutConstants: ChatMessageItemLayoutConstants,
|
layoutConstants: ChatMessageItemLayoutConstants,
|
||||||
currentItem: ChatMessageItem?,
|
currentItem: ChatMessageItem?,
|
||||||
currentForwardInfo: (Peer?, String?)?,
|
currentForwardInfo: (Peer?, String?)?,
|
||||||
@ -1330,7 +1337,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
|||||||
prepareContentPosition = .linear(top: topPosition, bottom: refinedBottomPosition)
|
prepareContentPosition = .linear(top: topPosition, bottom: refinedBottomPosition)
|
||||||
}
|
}
|
||||||
|
|
||||||
let contentItem = ChatMessageBubbleContentItem(context: item.context, controllerInteraction: item.controllerInteraction, message: message, read: read, chatLocation: item.chatLocation, presentationData: item.presentationData, associatedData: item.associatedData, attributes: attributes, isItemPinned: isItemPinned, isItemEdited: isItemEdited)
|
let contentItem = ChatMessageBubbleContentItem(context: item.context, controllerInteraction: item.controllerInteraction, message: message, topMessage: item.content.firstMessage, read: read, chatLocation: item.chatLocation, presentationData: item.presentationData, associatedData: item.associatedData, attributes: attributes, isItemPinned: isItemPinned, isItemEdited: isItemEdited)
|
||||||
|
|
||||||
var itemSelection: Bool?
|
var itemSelection: Bool?
|
||||||
switch content {
|
switch content {
|
||||||
@ -1457,7 +1464,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
|||||||
let lastNodeTopPosition: ChatMessageBubbleRelativePosition = .None(bottomNodeMergeStatus)
|
let lastNodeTopPosition: ChatMessageBubbleRelativePosition = .None(bottomNodeMergeStatus)
|
||||||
|
|
||||||
var calculatedGroupFramesAndSize: ([(CGRect, MosaicItemPosition)], CGSize)?
|
var calculatedGroupFramesAndSize: ([(CGRect, MosaicItemPosition)], CGSize)?
|
||||||
var mosaicStatusSizeAndApply: (CGSize, (Bool) -> ChatMessageDateAndStatusNode)?
|
var mosaicStatusSizeAndApply: (CGSize, (ListViewItemUpdateAnimation) -> ChatMessageDateAndStatusNode)?
|
||||||
|
|
||||||
if let mosaicRange = mosaicRange {
|
if let mosaicRange = mosaicRange {
|
||||||
let maxSize = layoutConstants.image.maxDimensions.fittedToWidthOrSmaller(maximumContentWidth - layoutConstants.image.bubbleInsets.left - layoutConstants.image.bubbleInsets.right)
|
let maxSize = layoutConstants.image.maxDimensions.fittedToWidthOrSmaller(maximumContentWidth - layoutConstants.image.bubbleInsets.left - layoutConstants.image.bubbleInsets.right)
|
||||||
@ -2107,7 +2114,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
|||||||
contentNodeFramesPropertiesAndApply: [(CGRect, ChatMessageBubbleContentProperties, Bool, (ListViewItemUpdateAnimation, Bool) -> Void)],
|
contentNodeFramesPropertiesAndApply: [(CGRect, ChatMessageBubbleContentProperties, Bool, (ListViewItemUpdateAnimation, Bool) -> Void)],
|
||||||
contentContainerNodeFrames: [(UInt32, CGRect, Bool?, CGFloat)],
|
contentContainerNodeFrames: [(UInt32, CGRect, Bool?, CGFloat)],
|
||||||
mosaicStatusOrigin: CGPoint?,
|
mosaicStatusOrigin: CGPoint?,
|
||||||
mosaicStatusSizeAndApply: (CGSize, (Bool) -> ChatMessageDateAndStatusNode)?,
|
mosaicStatusSizeAndApply: (CGSize, (ListViewItemUpdateAnimation) -> ChatMessageDateAndStatusNode)?,
|
||||||
needsShareButton: Bool
|
needsShareButton: Bool
|
||||||
) -> Void {
|
) -> Void {
|
||||||
guard let strongSelf = selfReference.value else {
|
guard let strongSelf = selfReference.value else {
|
||||||
@ -2123,10 +2130,10 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
|||||||
strongSelf.appliedForwardInfo = (forwardSource, forwardAuthorSignature)
|
strongSelf.appliedForwardInfo = (forwardSource, forwardAuthorSignature)
|
||||||
strongSelf.updateAccessibilityData(accessibilityData)
|
strongSelf.updateAccessibilityData(accessibilityData)
|
||||||
|
|
||||||
var transition: ContainedViewLayoutTransition = .immediate
|
var legacyTransition: ContainedViewLayoutTransition = .immediate
|
||||||
var useDisplayLinkAnimations = false
|
var useDisplayLinkAnimations = false
|
||||||
if case let .System(duration) = animation {
|
if case let .System(duration, _) = animation {
|
||||||
transition = .animated(duration: duration, curve: .spring)
|
legacyTransition = .animated(duration: duration, curve: .spring)
|
||||||
|
|
||||||
if let subject = item.associatedData.subject, case .forwardedMessages = subject {
|
if let subject = item.associatedData.subject, case .forwardedMessages = subject {
|
||||||
useDisplayLinkAnimations = true
|
useDisplayLinkAnimations = true
|
||||||
@ -2150,9 +2157,9 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
|||||||
}
|
}
|
||||||
let hasWallpaper = item.presentationData.theme.wallpaper.hasWallpaper
|
let hasWallpaper = item.presentationData.theme.wallpaper.hasWallpaper
|
||||||
if item.presentationData.theme.theme.forceSync {
|
if item.presentationData.theme.theme.forceSync {
|
||||||
transition = .immediate
|
legacyTransition = .immediate
|
||||||
}
|
}
|
||||||
strongSelf.backgroundNode.setType(type: backgroundType, highlighted: strongSelf.highlightedState, graphics: graphics, maskMode: strongSelf.backgroundMaskMode, hasWallpaper: hasWallpaper, transition: transition, backgroundNode: presentationContext.backgroundNode)
|
strongSelf.backgroundNode.setType(type: backgroundType, highlighted: strongSelf.highlightedState, graphics: graphics, maskMode: strongSelf.backgroundMaskMode, hasWallpaper: hasWallpaper, transition: legacyTransition, backgroundNode: presentationContext.backgroundNode)
|
||||||
strongSelf.backgroundWallpaperNode.setType(type: backgroundType, theme: item.presentationData.theme, essentialGraphics: graphics, maskMode: strongSelf.backgroundMaskMode, backgroundNode: presentationContext.backgroundNode)
|
strongSelf.backgroundWallpaperNode.setType(type: backgroundType, theme: item.presentationData.theme, essentialGraphics: graphics, maskMode: strongSelf.backgroundMaskMode, backgroundNode: presentationContext.backgroundNode)
|
||||||
strongSelf.shadowNode.setType(type: backgroundType, hasWallpaper: hasWallpaper, graphics: graphics)
|
strongSelf.shadowNode.setType(type: backgroundType, hasWallpaper: hasWallpaper, graphics: graphics)
|
||||||
|
|
||||||
@ -2178,14 +2185,14 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
|||||||
let deliveryFailedFrame = CGRect(origin: CGPoint(x: backgroundFrame.maxX + deliveryFailedInset - deliveryFailedSize.width, y: backgroundFrame.maxY - deliveryFailedSize.height), size: deliveryFailedSize)
|
let deliveryFailedFrame = CGRect(origin: CGPoint(x: backgroundFrame.maxX + deliveryFailedInset - deliveryFailedSize.width, y: backgroundFrame.maxY - deliveryFailedSize.height), size: deliveryFailedSize)
|
||||||
if isAppearing {
|
if isAppearing {
|
||||||
deliveryFailedNode.frame = deliveryFailedFrame
|
deliveryFailedNode.frame = deliveryFailedFrame
|
||||||
transition.animatePositionAdditive(node: deliveryFailedNode, offset: CGPoint(x: deliveryFailedInset, y: 0.0))
|
legacyTransition.animatePositionAdditive(node: deliveryFailedNode, offset: CGPoint(x: deliveryFailedInset, y: 0.0))
|
||||||
} else {
|
} else {
|
||||||
transition.updateFrame(node: deliveryFailedNode, frame: deliveryFailedFrame)
|
animation.animator.updateFrame(layer: deliveryFailedNode.layer, frame: deliveryFailedFrame, completion: nil)
|
||||||
}
|
}
|
||||||
} else if let deliveryFailedNode = strongSelf.deliveryFailedNode {
|
} else if let deliveryFailedNode = strongSelf.deliveryFailedNode {
|
||||||
strongSelf.deliveryFailedNode = nil
|
strongSelf.deliveryFailedNode = nil
|
||||||
transition.updateAlpha(node: deliveryFailedNode, alpha: 0.0)
|
animation.animator.updateAlpha(layer: deliveryFailedNode.layer, alpha: 0.0, completion: nil)
|
||||||
transition.updateFrame(node: deliveryFailedNode, frame: deliveryFailedNode.frame.offsetBy(dx: 24.0, dy: 0.0), completion: { [weak deliveryFailedNode] _ in
|
animation.animator.updateFrame(layer: deliveryFailedNode.layer, frame: deliveryFailedNode.frame.offsetBy(dx: 24.0, dy: 0.0), completion: { [weak deliveryFailedNode] _ in
|
||||||
deliveryFailedNode?.removeFromSupernode()
|
deliveryFailedNode?.removeFromSupernode()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -2194,16 +2201,16 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
|||||||
strongSelf.nameNode = nameNode
|
strongSelf.nameNode = nameNode
|
||||||
nameNode.displaysAsynchronously = !item.presentationData.isPreview && !item.presentationData.theme.theme.forceSync
|
nameNode.displaysAsynchronously = !item.presentationData.isPreview && !item.presentationData.theme.theme.forceSync
|
||||||
|
|
||||||
let previousNameNodeFrame = nameNode.frame
|
//let previousNameNodeFrame = nameNode.frame
|
||||||
let nameNodeFrame = CGRect(origin: CGPoint(x: contentOrigin.x + layoutConstants.text.bubbleInsets.left, y: layoutConstants.bubble.contentInsets.top + nameNodeOriginY), size: nameNodeSizeApply.0)
|
let nameNodeFrame = CGRect(origin: CGPoint(x: contentOrigin.x + layoutConstants.text.bubbleInsets.left, y: layoutConstants.bubble.contentInsets.top + nameNodeOriginY), size: nameNodeSizeApply.0)
|
||||||
nameNode.frame = nameNodeFrame
|
|
||||||
if nameNode.supernode == nil {
|
if nameNode.supernode == nil {
|
||||||
if !nameNode.isNodeLoaded {
|
if !nameNode.isNodeLoaded {
|
||||||
nameNode.isUserInteractionEnabled = false
|
nameNode.isUserInteractionEnabled = false
|
||||||
}
|
}
|
||||||
strongSelf.clippingNode.addSubnode(nameNode)
|
strongSelf.clippingNode.addSubnode(nameNode)
|
||||||
|
nameNode.frame = nameNodeFrame
|
||||||
} else {
|
} else {
|
||||||
transition.animatePositionAdditive(node: nameNode, offset: CGPoint(x: previousNameNodeFrame.maxX - nameNodeFrame.maxX, y: 0.0))
|
animation.animator.updateFrame(layer: nameNode.layer, frame: nameNodeFrame, completion: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
if let credibilityIconImage = currentCredibilityIconImage {
|
if let credibilityIconImage = currentCredibilityIconImage {
|
||||||
@ -2232,9 +2239,8 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
|||||||
strongSelf.clippingNode.addSubnode(adminBadgeNode)
|
strongSelf.clippingNode.addSubnode(adminBadgeNode)
|
||||||
adminBadgeNode.frame = adminBadgeFrame
|
adminBadgeNode.frame = adminBadgeFrame
|
||||||
} else {
|
} else {
|
||||||
let previousAdminBadgeFrame = adminBadgeNode.frame
|
//let previousAdminBadgeFrame = adminBadgeNode.frame
|
||||||
adminBadgeNode.frame = adminBadgeFrame
|
animation.animator.updateFrame(layer: adminBadgeNode.layer, frame: adminBadgeFrame, completion: nil)
|
||||||
transition.animatePositionAdditive(node: adminBadgeNode, offset: CGPoint(x: previousAdminBadgeFrame.maxX - adminBadgeFrame.maxX, y: 0.0))
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
strongSelf.adminBadgeNode?.removeFromSupernode()
|
strongSelf.adminBadgeNode?.removeFromSupernode()
|
||||||
@ -2269,7 +2275,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
|||||||
}
|
}
|
||||||
let previousForwardInfoNodeFrame = forwardInfoNode.frame
|
let previousForwardInfoNodeFrame = forwardInfoNode.frame
|
||||||
let forwardInfoFrame = CGRect(origin: CGPoint(x: contentOrigin.x + layoutConstants.text.bubbleInsets.left, y: layoutConstants.bubble.contentInsets.top + forwardInfoOriginY), size: CGSize(width: bubbleContentWidth, height: forwardInfoSizeApply.0.height))
|
let forwardInfoFrame = CGRect(origin: CGPoint(x: contentOrigin.x + layoutConstants.text.bubbleInsets.left, y: layoutConstants.bubble.contentInsets.top + forwardInfoOriginY), size: CGSize(width: bubbleContentWidth, height: forwardInfoSizeApply.0.height))
|
||||||
if case let .System(duration) = animation {
|
if case let .System(duration, _) = animation {
|
||||||
if animateFrame {
|
if animateFrame {
|
||||||
if useDisplayLinkAnimations {
|
if useDisplayLinkAnimations {
|
||||||
let animation = ListViewAnimation(from: previousForwardInfoNodeFrame, to: forwardInfoFrame, duration: duration * UIView.animationDurationFactor(), curve: strongSelf.preferredAnimationCurve, beginAt: beginAt, update: { _, frame in
|
let animation = ListViewAnimation(from: previousForwardInfoNodeFrame, to: forwardInfoFrame, duration: duration * UIView.animationDurationFactor(), curve: strongSelf.preferredAnimationCurve, beginAt: beginAt, update: { _, frame in
|
||||||
@ -2309,7 +2315,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
|||||||
}
|
}
|
||||||
let previousReplyInfoNodeFrame = replyInfoNode.frame
|
let previousReplyInfoNodeFrame = replyInfoNode.frame
|
||||||
replyInfoNode.frame = CGRect(origin: CGPoint(x: contentOrigin.x + layoutConstants.text.bubbleInsets.left, y: layoutConstants.bubble.contentInsets.top + replyInfoOriginY), size: replyInfoSizeApply.0)
|
replyInfoNode.frame = CGRect(origin: CGPoint(x: contentOrigin.x + layoutConstants.text.bubbleInsets.left, y: layoutConstants.bubble.contentInsets.top + replyInfoOriginY), size: replyInfoSizeApply.0)
|
||||||
if case let .System(duration) = animation {
|
if case let .System(duration, _) = animation {
|
||||||
if animateFrame {
|
if animateFrame {
|
||||||
replyInfoNode.layer.animateFrame(from: previousReplyInfoNodeFrame, to: replyInfoNode.frame, duration: duration, timingFunction: timingFunction)
|
replyInfoNode.layer.animateFrame(from: previousReplyInfoNodeFrame, to: replyInfoNode.frame, duration: duration, timingFunction: timingFunction)
|
||||||
}
|
}
|
||||||
@ -2561,7 +2567,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
|||||||
let contentNodeFrame = relativeFrame.offsetBy(dx: contentOrigin.x, dy: useContentOrigin ? contentOrigin.y : 0.0)
|
let contentNodeFrame = relativeFrame.offsetBy(dx: contentOrigin.x, dy: useContentOrigin ? contentOrigin.y : 0.0)
|
||||||
let previousContentNodeFrame = contentNode.frame
|
let previousContentNodeFrame = contentNode.frame
|
||||||
|
|
||||||
if case let .System(duration) = animation {
|
if case let .System(duration, _) = animation {
|
||||||
var animateFrame = false
|
var animateFrame = false
|
||||||
var animateAlpha = false
|
var animateAlpha = false
|
||||||
if let addedContentNodes = addedContentNodes {
|
if let addedContentNodes = addedContentNodes {
|
||||||
@ -2581,8 +2587,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
|||||||
})
|
})
|
||||||
strongSelf.setAnimationForKey("contentNode\(contentNodeIndex)Frame", animation: animation)
|
strongSelf.setAnimationForKey("contentNode\(contentNodeIndex)Frame", animation: animation)
|
||||||
} else {
|
} else {
|
||||||
contentNode.frame = contentNodeFrame
|
animation.animator.updateFrame(layer: contentNode.layer, frame: contentNodeFrame, completion: nil)
|
||||||
contentNode.layer.animateFrame(from: previousContentNodeFrame, to: contentNodeFrame, duration: duration, timingFunction: timingFunction)
|
|
||||||
}
|
}
|
||||||
} else if animateAlpha {
|
} else if animateAlpha {
|
||||||
contentNode.frame = contentNodeFrame
|
contentNode.frame = contentNodeFrame
|
||||||
@ -2600,7 +2605,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let mosaicStatusOrigin = mosaicStatusOrigin, let (size, apply) = mosaicStatusSizeAndApply {
|
if let mosaicStatusOrigin = mosaicStatusOrigin, let (size, apply) = mosaicStatusSizeAndApply {
|
||||||
let mosaicStatusNode = apply(transition.isAnimated)
|
let mosaicStatusNode = apply(animation)
|
||||||
if mosaicStatusNode !== strongSelf.mosaicStatusNode {
|
if mosaicStatusNode !== strongSelf.mosaicStatusNode {
|
||||||
strongSelf.mosaicStatusNode?.removeFromSupernode()
|
strongSelf.mosaicStatusNode?.removeFromSupernode()
|
||||||
strongSelf.mosaicStatusNode = mosaicStatusNode
|
strongSelf.mosaicStatusNode = mosaicStatusNode
|
||||||
@ -2627,18 +2632,53 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
|||||||
|
|
||||||
if case .System = animation, !strongSelf.mainContextSourceNode.isExtractedToContextPreview {
|
if case .System = animation, !strongSelf.mainContextSourceNode.isExtractedToContextPreview {
|
||||||
if !strongSelf.backgroundNode.frame.equalTo(backgroundFrame) {
|
if !strongSelf.backgroundNode.frame.equalTo(backgroundFrame) {
|
||||||
strongSelf.backgroundFrameTransition = (strongSelf.backgroundNode.frame, backgroundFrame)
|
/*strongSelf.backgroundFrameTransition = (strongSelf.backgroundNode.frame, backgroundFrame)
|
||||||
if let type = strongSelf.backgroundNode.type {
|
if let type = strongSelf.backgroundNode.type {
|
||||||
if case .none = type {
|
if case .none = type {
|
||||||
} else {
|
} else {
|
||||||
strongSelf.clippingNode.clipsToBounds = true
|
strongSelf.clippingNode.clipsToBounds = true
|
||||||
}
|
}
|
||||||
|
}*/
|
||||||
|
|
||||||
|
animation.animator.updateFrame(layer: strongSelf.backgroundNode.layer, frame: backgroundFrame, completion: nil)
|
||||||
|
animation.animator.updatePosition(layer: strongSelf.clippingNode.layer, position: backgroundFrame.center, completion: nil)
|
||||||
|
strongSelf.clippingNode.clipsToBounds = true
|
||||||
|
animation.animator.updateBounds(layer: strongSelf.clippingNode.layer, bounds: CGRect(origin: CGPoint(x: backgroundFrame.minX, y: backgroundFrame.minY), size: backgroundFrame.size), completion: { [weak strongSelf] _ in
|
||||||
|
let _ = strongSelf
|
||||||
|
//strongSelf?.clippingNode.clipsToBounds = false
|
||||||
|
})
|
||||||
|
|
||||||
|
strongSelf.backgroundNode.updateLayout(size: backgroundFrame.size, transition: animation)
|
||||||
|
animation.animator.updateFrame(layer: strongSelf.backgroundWallpaperNode.layer, frame: backgroundFrame, completion: nil)
|
||||||
|
strongSelf.shadowNode.updateLayout(backgroundFrame: backgroundFrame, transition: animation.transition)
|
||||||
|
|
||||||
|
if let type = strongSelf.backgroundNode.type {
|
||||||
|
var incomingOffset: CGFloat = 0.0
|
||||||
|
switch type {
|
||||||
|
case .incoming:
|
||||||
|
incomingOffset = 5.0
|
||||||
|
default:
|
||||||
|
break
|
||||||
}
|
}
|
||||||
|
strongSelf.mainContextSourceNode.contentRect = backgroundFrame.offsetBy(dx: incomingOffset, dy: 0.0)
|
||||||
|
strongSelf.mainContainerNode.targetNodeForActivationProgressContentRect = strongSelf.mainContextSourceNode.contentRect
|
||||||
|
if !strongSelf.mainContextSourceNode.isExtractedToContextPreview {
|
||||||
|
if let (rect, size) = strongSelf.absoluteRect {
|
||||||
|
strongSelf.updateAbsoluteRect(rect, within: size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
strongSelf.messageAccessibilityArea.frame = backgroundFrame
|
||||||
|
|
||||||
|
/*if let item = strongSelf.item, let shareButtonNode = strongSelf.shareButtonNode {
|
||||||
|
let buttonSize = shareButtonNode.update(presentationData: item.presentationData, chatLocation: item.chatLocation, subject: item.associatedData.subject, message: item.message, account: item.context.account, disableComments: true)
|
||||||
|
animation.animator.updateFrame(layer: shareButtonNode, frame: CGRect(origin: CGPoint(x: backgroundFrame.maxX + 8.0, y: backgroundFrame.maxY - buttonSize.width - 1.0), size: buttonSize), completion: nil)
|
||||||
|
}*/
|
||||||
}
|
}
|
||||||
if let shareButtonNode = strongSelf.shareButtonNode {
|
if let shareButtonNode = strongSelf.shareButtonNode {
|
||||||
let currentBackgroundFrame = strongSelf.backgroundNode.frame
|
let currentBackgroundFrame = strongSelf.backgroundNode.frame
|
||||||
let buttonSize = shareButtonNode.update(presentationData: item.presentationData, chatLocation: item.chatLocation, subject: item.associatedData.subject, message: item.message, account: item.context.account, disableComments: true)
|
let buttonSize = shareButtonNode.update(presentationData: item.presentationData, chatLocation: item.chatLocation, subject: item.associatedData.subject, message: item.message, account: item.context.account, disableComments: true)
|
||||||
shareButtonNode.frame = CGRect(origin: CGPoint(x: currentBackgroundFrame.maxX + 8.0, y: currentBackgroundFrame.maxY - buttonSize.width - 1.0), size: buttonSize)
|
animation.animator.updateFrame(layer: shareButtonNode.layer, frame: CGRect(origin: CGPoint(x: currentBackgroundFrame.maxX + 8.0, y: currentBackgroundFrame.maxY - buttonSize.width - 1.0), size: buttonSize), completion: nil)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if let _ = strongSelf.backgroundFrameTransition {
|
if let _ = strongSelf.backgroundFrameTransition {
|
||||||
@ -2652,14 +2692,14 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
|||||||
}
|
}
|
||||||
|
|
||||||
if case .System = animation, strongSelf.mainContextSourceNode.isExtractedToContextPreview {
|
if case .System = animation, strongSelf.mainContextSourceNode.isExtractedToContextPreview {
|
||||||
transition.updateFrame(node: strongSelf.backgroundNode, frame: backgroundFrame)
|
legacyTransition.updateFrame(node: strongSelf.backgroundNode, frame: backgroundFrame)
|
||||||
|
|
||||||
transition.updateFrame(node: strongSelf.clippingNode, frame: backgroundFrame)
|
legacyTransition.updateFrame(node: strongSelf.clippingNode, frame: backgroundFrame)
|
||||||
transition.updateBounds(node: strongSelf.clippingNode, bounds: CGRect(origin: CGPoint(x: backgroundFrame.minX, y: backgroundFrame.minY), size: backgroundFrame.size))
|
legacyTransition.updateBounds(node: strongSelf.clippingNode, bounds: CGRect(origin: CGPoint(x: backgroundFrame.minX, y: backgroundFrame.minY), size: backgroundFrame.size))
|
||||||
|
|
||||||
strongSelf.backgroundNode.updateLayout(size: backgroundFrame.size, transition: transition)
|
strongSelf.backgroundNode.updateLayout(size: backgroundFrame.size, transition: legacyTransition)
|
||||||
strongSelf.backgroundWallpaperNode.updateFrame(backgroundFrame, transition: transition)
|
strongSelf.backgroundWallpaperNode.updateFrame(backgroundFrame, transition: legacyTransition)
|
||||||
strongSelf.shadowNode.updateLayout(backgroundFrame: backgroundFrame, transition: transition)
|
strongSelf.shadowNode.updateLayout(backgroundFrame: backgroundFrame, transition: legacyTransition)
|
||||||
} else {
|
} else {
|
||||||
strongSelf.backgroundNode.frame = backgroundFrame
|
strongSelf.backgroundNode.frame = backgroundFrame
|
||||||
strongSelf.clippingNode.frame = backgroundFrame
|
strongSelf.clippingNode.frame = backgroundFrame
|
||||||
@ -2702,7 +2742,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
|||||||
}
|
}
|
||||||
strongSelf.insertSubnode(actionButtonsNode, belowSubnode: strongSelf.messageAccessibilityArea)
|
strongSelf.insertSubnode(actionButtonsNode, belowSubnode: strongSelf.messageAccessibilityArea)
|
||||||
} else {
|
} else {
|
||||||
if case let .System(duration) = animation {
|
if case let .System(duration, _) = animation {
|
||||||
actionButtonsNode.layer.animateFrame(from: previousFrame, to: actionButtonsFrame, duration: duration, timingFunction: timingFunction)
|
actionButtonsNode.layer.animateFrame(from: previousFrame, to: actionButtonsFrame, duration: duration, timingFunction: timingFunction)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2780,7 +2820,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
|||||||
override func animateFrameTransition(_ progress: CGFloat, _ currentValue: CGFloat) {
|
override func animateFrameTransition(_ progress: CGFloat, _ currentValue: CGFloat) {
|
||||||
super.animateFrameTransition(progress, currentValue)
|
super.animateFrameTransition(progress, currentValue)
|
||||||
|
|
||||||
if let backgroundFrameTransition = self.backgroundFrameTransition {
|
/*if let backgroundFrameTransition = self.backgroundFrameTransition {
|
||||||
let backgroundFrame = CGRect.interpolator()(backgroundFrameTransition.0, backgroundFrameTransition.1, progress) as! CGRect
|
let backgroundFrame = CGRect.interpolator()(backgroundFrameTransition.0, backgroundFrameTransition.1, progress) as! CGRect
|
||||||
self.backgroundNode.frame = backgroundFrame
|
self.backgroundNode.frame = backgroundFrame
|
||||||
|
|
||||||
@ -2819,7 +2859,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
|||||||
|
|
||||||
self.clippingNode.clipsToBounds = false
|
self.clippingNode.clipsToBounds = false
|
||||||
}
|
}
|
||||||
}
|
}*/
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func tapLongTapOrDoubleTapGesture(_ recognizer: TapLongTapOrDoubleTapGestureRecognizer) {
|
@objc func tapLongTapOrDoubleTapGesture(_ recognizer: TapLongTapOrDoubleTapGestureRecognizer) {
|
||||||
|
@ -417,7 +417,3 @@ final class ChatMessageCommentFooterContentNode: ChatMessageBubbleContentNode {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -196,7 +196,7 @@ class ChatMessageContactBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
statusType = nil
|
statusType = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var statusSuggestedWidthAndContinue: (CGFloat, (CGFloat) -> (CGSize, (Bool) -> Void))?
|
var statusSuggestedWidthAndContinue: (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation) -> Void))?
|
||||||
if let statusType = statusType {
|
if let statusType = statusType {
|
||||||
var isReplyThread = false
|
var isReplyThread = false
|
||||||
if case .replyThread = item.chatLocation {
|
if case .replyThread = item.chatLocation {
|
||||||
@ -210,7 +210,7 @@ class ChatMessageContactBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
impressionCount: viewCount,
|
impressionCount: viewCount,
|
||||||
dateText: dateText,
|
dateText: dateText,
|
||||||
type: statusType,
|
type: statusType,
|
||||||
layoutInput: .trailingContent(contentWidth: 1000.0, preferAdditionalInset: true),
|
layoutInput: .trailingContent(contentWidth: 1000.0, reactionSettings: nil),
|
||||||
constrainedSize: CGSize(width: constrainedSize.width - sideInsets, height: .greatestFiniteMagnitude),
|
constrainedSize: CGSize(width: constrainedSize.width - sideInsets, height: .greatestFiniteMagnitude),
|
||||||
availableReactions: item.associatedData.availableReactions,
|
availableReactions: item.associatedData.availableReactions,
|
||||||
reactions: dateReactions,
|
reactions: dateReactions,
|
||||||
@ -305,9 +305,9 @@ class ChatMessageContactBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
strongSelf.dateAndStatusNode.frame = CGRect(origin: CGPoint(x: layoutConstants.text.bubbleInsets.left, y: strongSelf.textNode.frame.maxY + 2.0), size: statusSizeAndApply.0)
|
strongSelf.dateAndStatusNode.frame = CGRect(origin: CGPoint(x: layoutConstants.text.bubbleInsets.left, y: strongSelf.textNode.frame.maxY + 2.0), size: statusSizeAndApply.0)
|
||||||
if strongSelf.dateAndStatusNode.supernode == nil {
|
if strongSelf.dateAndStatusNode.supernode == nil {
|
||||||
strongSelf.addSubnode(strongSelf.dateAndStatusNode)
|
strongSelf.addSubnode(strongSelf.dateAndStatusNode)
|
||||||
statusSizeAndApply.1(false)
|
statusSizeAndApply.1(.None)
|
||||||
} else {
|
} else {
|
||||||
statusSizeAndApply.1(animation.isAnimated)
|
statusSizeAndApply.1(animation)
|
||||||
}
|
}
|
||||||
} else if strongSelf.dateAndStatusNode.supernode != nil {
|
} else if strongSelf.dateAndStatusNode.supernode != nil {
|
||||||
strongSelf.dateAndStatusNode.removeFromSupernode()
|
strongSelf.dateAndStatusNode.removeFromSupernode()
|
||||||
|
@ -87,8 +87,16 @@ private final class StatusReactionNode: ASDisplayNode {
|
|||||||
|
|
||||||
|
|
||||||
class ChatMessageDateAndStatusNode: ASDisplayNode {
|
class ChatMessageDateAndStatusNode: ASDisplayNode {
|
||||||
|
struct ReactionSettings {
|
||||||
|
var preferAdditionalInset: Bool
|
||||||
|
|
||||||
|
init(preferAdditionalInset: Bool) {
|
||||||
|
self.preferAdditionalInset = preferAdditionalInset
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
enum LayoutInput {
|
enum LayoutInput {
|
||||||
case trailingContent(contentWidth: CGFloat, preferAdditionalInset: Bool)
|
case trailingContent(contentWidth: CGFloat, reactionSettings: ReactionSettings?)
|
||||||
case standalone
|
case standalone
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -193,7 +201,7 @@ class ChatMessageDateAndStatusNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func asyncLayout() -> (_ arguments: Arguments) -> (CGFloat, (CGFloat) -> (CGSize, (Bool) -> Void)) {
|
func asyncLayout() -> (_ arguments: Arguments) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation) -> Void)) {
|
||||||
let dateLayout = TextNode.asyncLayout(self.dateNode)
|
let dateLayout = TextNode.asyncLayout(self.dateNode)
|
||||||
|
|
||||||
var checkReadNode = self.checkReadNode
|
var checkReadNode = self.checkReadNode
|
||||||
@ -211,8 +219,6 @@ class ChatMessageDateAndStatusNode: ASDisplayNode {
|
|||||||
let makeReplyCountLayout = TextNode.asyncLayout(self.replyCountNode)
|
let makeReplyCountLayout = TextNode.asyncLayout(self.replyCountNode)
|
||||||
let makeReactionCountLayout = TextNode.asyncLayout(self.reactionCountNode)
|
let makeReactionCountLayout = TextNode.asyncLayout(self.reactionCountNode)
|
||||||
|
|
||||||
let previousLayoutSize = self.layoutSize
|
|
||||||
|
|
||||||
let reactionButtonsContainer = self.reactionButtonsContainer
|
let reactionButtonsContainer = self.reactionButtonsContainer
|
||||||
|
|
||||||
return { [weak self] arguments in
|
return { [weak self] arguments in
|
||||||
@ -592,7 +598,8 @@ class ChatMessageDateAndStatusNode: ASDisplayNode {
|
|||||||
constrainedWidth: arguments.constrainedSize.width,
|
constrainedWidth: arguments.constrainedSize.width,
|
||||||
transition: .immediate
|
transition: .immediate
|
||||||
)
|
)
|
||||||
case let .trailingContent(contentWidth, preferAdditionalInset):
|
case let .trailingContent(contentWidth, reactionSettings):
|
||||||
|
if let _ = reactionSettings {
|
||||||
reactionButtons = reactionButtonsContainer.update(
|
reactionButtons = reactionButtonsContainer.update(
|
||||||
context: arguments.context,
|
context: arguments.context,
|
||||||
action: { value in
|
action: { value in
|
||||||
@ -626,6 +633,21 @@ class ChatMessageDateAndStatusNode: ASDisplayNode {
|
|||||||
constrainedWidth: arguments.constrainedSize.width,
|
constrainedWidth: arguments.constrainedSize.width,
|
||||||
transition: .immediate
|
transition: .immediate
|
||||||
)
|
)
|
||||||
|
} else {
|
||||||
|
reactionButtons = reactionButtonsContainer.update(
|
||||||
|
context: arguments.context,
|
||||||
|
action: { value in
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
strongSelf.reactionSelected?(value)
|
||||||
|
},
|
||||||
|
reactions: [],
|
||||||
|
colors: reactionColors,
|
||||||
|
constrainedWidth: arguments.constrainedSize.width,
|
||||||
|
transition: .immediate
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
var reactionButtonsSize = CGSize()
|
var reactionButtonsSize = CGSize()
|
||||||
var currentRowWidth: CGFloat = 0.0
|
var currentRowWidth: CGFloat = 0.0
|
||||||
@ -664,17 +686,22 @@ class ChatMessageDateAndStatusNode: ASDisplayNode {
|
|||||||
resultingHeight = 0.0
|
resultingHeight = 0.0
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if preferAdditionalInset {
|
if let reactionSettings = reactionSettings {
|
||||||
|
if reactionSettings.preferAdditionalInset {
|
||||||
verticalReactionsInset = 5.0
|
verticalReactionsInset = 5.0
|
||||||
} else {
|
} else {
|
||||||
verticalReactionsInset = 2.0
|
verticalReactionsInset = 2.0
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
verticalReactionsInset = 0.0
|
||||||
|
}
|
||||||
|
|
||||||
if currentRowWidth + layoutSize.width > arguments.constrainedSize.width {
|
if currentRowWidth + layoutSize.width > arguments.constrainedSize.width {
|
||||||
resultingWidth = max(layoutSize.width, reactionButtonsSize.width)
|
resultingWidth = max(layoutSize.width, reactionButtonsSize.width)
|
||||||
resultingHeight = verticalReactionsInset + reactionButtonsSize.height + layoutSize.height
|
resultingHeight = verticalReactionsInset + reactionButtonsSize.height + layoutSize.height
|
||||||
verticalInset = verticalReactionsInset + reactionButtonsSize.height
|
verticalInset = verticalReactionsInset + reactionButtonsSize.height
|
||||||
} else {
|
} else {
|
||||||
resultingWidth = layoutSize.width + currentRowWidth
|
resultingWidth = max(layoutSize.width + currentRowWidth, reactionButtonsSize.width)
|
||||||
verticalInset = verticalReactionsInset + reactionButtonsSize.height - layoutSize.height
|
verticalInset = verticalReactionsInset + reactionButtonsSize.height - layoutSize.height
|
||||||
resultingHeight = verticalReactionsInset + reactionButtonsSize.height
|
resultingHeight = verticalReactionsInset + reactionButtonsSize.height
|
||||||
}
|
}
|
||||||
@ -682,7 +709,7 @@ class ChatMessageDateAndStatusNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (resultingWidth, { boundingWidth in
|
return (resultingWidth, { boundingWidth in
|
||||||
return (CGSize(width: boundingWidth, height: resultingHeight), { animated in
|
return (CGSize(width: boundingWidth, height: resultingHeight), { animation in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
let leftOffset = boundingWidth - layoutSize.width
|
let leftOffset = boundingWidth - layoutSize.width
|
||||||
|
|
||||||
@ -699,14 +726,28 @@ class ChatMessageDateAndStatusNode: ASDisplayNode {
|
|||||||
|
|
||||||
if item.view.superview == nil {
|
if item.view.superview == nil {
|
||||||
strongSelf.view.addSubview(item.view)
|
strongSelf.view.addSubview(item.view)
|
||||||
}
|
|
||||||
item.view.frame = CGRect(origin: reactionButtonPosition, size: item.size)
|
item.view.frame = CGRect(origin: reactionButtonPosition, size: item.size)
|
||||||
|
|
||||||
|
if animation.isAnimated {
|
||||||
|
item.view.layer.animateScale(from: 0.01, to: 1.0, duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring)
|
||||||
|
item.view.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
animation.animator.updateFrame(layer: item.view.layer, frame: CGRect(origin: reactionButtonPosition, size: item.size), completion: nil)
|
||||||
|
}
|
||||||
reactionButtonPosition.x += item.size.width + 6.0
|
reactionButtonPosition.x += item.size.width + 6.0
|
||||||
}
|
}
|
||||||
|
|
||||||
for view in reactionButtons.removedViews {
|
for view in reactionButtons.removedViews {
|
||||||
|
if animation.isAnimated {
|
||||||
|
view.layer.animateScale(from: 1.0, to: 0.01, duration: 0.2, removeOnCompletion: false)
|
||||||
|
view.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak view] _ in
|
||||||
|
view?.removeFromSuperview()
|
||||||
|
})
|
||||||
|
} else {
|
||||||
view.removeFromSuperview()
|
view.removeFromSuperview()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if backgroundImage != nil {
|
if backgroundImage != nil {
|
||||||
if let currentBackgroundNode = currentBackgroundNode {
|
if let currentBackgroundNode = currentBackgroundNode {
|
||||||
@ -719,11 +760,7 @@ class ChatMessageDateAndStatusNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let backgroundNode = strongSelf.backgroundNode {
|
if let backgroundNode = strongSelf.backgroundNode {
|
||||||
let transition: ContainedViewLayoutTransition = animated ? .animated(duration: 0.4, curve: .spring) : .immediate
|
animation.animator.updateFrame(layer: backgroundNode.layer, frame: CGRect(origin: CGPoint(), size: layoutSize), completion: nil)
|
||||||
if let previousLayoutSize = previousLayoutSize {
|
|
||||||
backgroundNode.frame = backgroundNode.frame.offsetBy(dx: layoutSize.width - previousLayoutSize.width, dy: 0.0)
|
|
||||||
}
|
|
||||||
transition.updateFrame(node: backgroundNode, frame: CGRect(origin: CGPoint(), size: layoutSize))
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if let backgroundNode = strongSelf.backgroundNode {
|
if let backgroundNode = strongSelf.backgroundNode {
|
||||||
@ -735,12 +772,8 @@ class ChatMessageDateAndStatusNode: ASDisplayNode {
|
|||||||
if let blurredBackgroundColor = blurredBackgroundColor {
|
if let blurredBackgroundColor = blurredBackgroundColor {
|
||||||
if let blurredBackgroundNode = strongSelf.blurredBackgroundNode {
|
if let blurredBackgroundNode = strongSelf.blurredBackgroundNode {
|
||||||
blurredBackgroundNode.updateColor(color: blurredBackgroundColor.0, enableBlur: blurredBackgroundColor.1, transition: .immediate)
|
blurredBackgroundNode.updateColor(color: blurredBackgroundColor.0, enableBlur: blurredBackgroundColor.1, transition: .immediate)
|
||||||
let transition: ContainedViewLayoutTransition = animated ? .animated(duration: 0.4, curve: .spring) : .immediate
|
animation.animator.updateFrame(layer: blurredBackgroundNode.layer, frame: CGRect(origin: CGPoint(), size: layoutSize), completion: nil)
|
||||||
if let previousLayoutSize = previousLayoutSize {
|
blurredBackgroundNode.update(size: blurredBackgroundNode.bounds.size, cornerRadius: blurredBackgroundNode.bounds.height / 2.0, transition: animation.transition)
|
||||||
blurredBackgroundNode.frame = blurredBackgroundNode.frame.offsetBy(dx: layoutSize.width - previousLayoutSize.width, dy: 0.0)
|
|
||||||
}
|
|
||||||
transition.updateFrame(node: blurredBackgroundNode, frame: CGRect(origin: CGPoint(), size: layoutSize))
|
|
||||||
blurredBackgroundNode.update(size: blurredBackgroundNode.bounds.size, cornerRadius: blurredBackgroundNode.bounds.height / 2.0, transition: transition)
|
|
||||||
} else {
|
} else {
|
||||||
let blurredBackgroundNode = NavigationBackgroundNode(color: blurredBackgroundColor.0, enableBlur: blurredBackgroundColor.1)
|
let blurredBackgroundNode = NavigationBackgroundNode(color: blurredBackgroundColor.0, enableBlur: blurredBackgroundColor.1)
|
||||||
strongSelf.blurredBackgroundNode = blurredBackgroundNode
|
strongSelf.blurredBackgroundNode = blurredBackgroundNode
|
||||||
@ -771,7 +804,7 @@ class ChatMessageDateAndStatusNode: ASDisplayNode {
|
|||||||
strongSelf.impressionIcon = nil
|
strongSelf.impressionIcon = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
strongSelf.dateNode.frame = CGRect(origin: CGPoint(x: leftOffset + leftInset + backgroundInsets.left + impressionWidth, y: backgroundInsets.top + 1.0 + offset + verticalInset), size: date.size)
|
animation.animator.updateFrame(layer: strongSelf.dateNode.layer, frame: CGRect(origin: CGPoint(x: leftOffset + leftInset + backgroundInsets.left + impressionWidth, y: backgroundInsets.top + 1.0 + offset + verticalInset), size: date.size), completion: nil)
|
||||||
|
|
||||||
if let clockFrameNode = clockFrameNode {
|
if let clockFrameNode = clockFrameNode {
|
||||||
if strongSelf.clockFrameNode == nil {
|
if strongSelf.clockFrameNode == nil {
|
||||||
@ -781,7 +814,7 @@ class ChatMessageDateAndStatusNode: ASDisplayNode {
|
|||||||
} else if themeUpdated {
|
} else if themeUpdated {
|
||||||
clockFrameNode.image = clockFrameImage
|
clockFrameNode.image = clockFrameImage
|
||||||
}
|
}
|
||||||
clockFrameNode.position = CGPoint(x: leftOffset + backgroundInsets.left + clockPosition.x + reactionInset, y: backgroundInsets.top + clockPosition.y + verticalInset)
|
animation.animator.updatePosition(layer: clockFrameNode.layer, position: CGPoint(x: leftOffset + backgroundInsets.left + clockPosition.x + reactionInset, y: backgroundInsets.top + clockPosition.y + verticalInset), completion: nil)
|
||||||
if let clockFrameNode = strongSelf.clockFrameNode {
|
if let clockFrameNode = strongSelf.clockFrameNode {
|
||||||
maybeAddRotationAnimation(clockFrameNode.layer, duration: 6.0)
|
maybeAddRotationAnimation(clockFrameNode.layer, duration: 6.0)
|
||||||
}
|
}
|
||||||
@ -798,7 +831,7 @@ class ChatMessageDateAndStatusNode: ASDisplayNode {
|
|||||||
} else if themeUpdated {
|
} else if themeUpdated {
|
||||||
clockMinNode.image = clockMinImage
|
clockMinNode.image = clockMinImage
|
||||||
}
|
}
|
||||||
clockMinNode.position = CGPoint(x: leftOffset + backgroundInsets.left + clockPosition.x + reactionInset, y: backgroundInsets.top + clockPosition.y + verticalInset)
|
animation.animator.updatePosition(layer: clockMinNode.layer, position: CGPoint(x: leftOffset + backgroundInsets.left + clockPosition.x + reactionInset, y: backgroundInsets.top + clockPosition.y + verticalInset), completion: nil)
|
||||||
if let clockMinNode = strongSelf.clockMinNode {
|
if let clockMinNode = strongSelf.clockMinNode {
|
||||||
maybeAddRotationAnimation(clockMinNode.layer, duration: 1.0)
|
maybeAddRotationAnimation(clockMinNode.layer, duration: 1.0)
|
||||||
}
|
}
|
||||||
@ -813,24 +846,26 @@ class ChatMessageDateAndStatusNode: ASDisplayNode {
|
|||||||
checkSentNode.image = loadedCheckFullImage
|
checkSentNode.image = loadedCheckFullImage
|
||||||
strongSelf.checkSentNode = checkSentNode
|
strongSelf.checkSentNode = checkSentNode
|
||||||
strongSelf.addSubnode(checkSentNode)
|
strongSelf.addSubnode(checkSentNode)
|
||||||
animateSentNode = animated
|
animateSentNode = animation.isAnimated
|
||||||
} else if themeUpdated {
|
} else if themeUpdated {
|
||||||
checkSentNode.image = loadedCheckFullImage
|
checkSentNode.image = loadedCheckFullImage
|
||||||
}
|
}
|
||||||
|
|
||||||
if let checkSentFrame = checkSentFrame {
|
if let checkSentFrame = checkSentFrame {
|
||||||
if checkSentNode.isHidden {
|
if checkSentNode.isHidden {
|
||||||
animateSentNode = animated
|
animateSentNode = animation.isAnimated
|
||||||
|
checkSentNode.frame = checkSentFrame.offsetBy(dx: leftOffset + backgroundInsets.left + reactionInset, dy: backgroundInsets.top + verticalInset)
|
||||||
|
} else {
|
||||||
|
animation.animator.updateFrame(layer: checkSentNode.layer, frame: checkSentFrame.offsetBy(dx: leftOffset + backgroundInsets.left + reactionInset, dy: backgroundInsets.top + verticalInset), completion: nil)
|
||||||
}
|
}
|
||||||
checkSentNode.isHidden = false
|
checkSentNode.isHidden = false
|
||||||
checkSentNode.frame = checkSentFrame.offsetBy(dx: leftOffset + backgroundInsets.left + reactionInset, dy: backgroundInsets.top + verticalInset)
|
|
||||||
} else {
|
} else {
|
||||||
checkSentNode.isHidden = true
|
checkSentNode.isHidden = true
|
||||||
}
|
}
|
||||||
|
|
||||||
var animateReadNode = false
|
var animateReadNode = false
|
||||||
if strongSelf.checkReadNode == nil {
|
if strongSelf.checkReadNode == nil {
|
||||||
animateReadNode = animated
|
animateReadNode = animation.isAnimated
|
||||||
checkReadNode.image = loadedCheckPartialImage
|
checkReadNode.image = loadedCheckPartialImage
|
||||||
strongSelf.checkReadNode = checkReadNode
|
strongSelf.checkReadNode = checkReadNode
|
||||||
strongSelf.addSubnode(checkReadNode)
|
strongSelf.addSubnode(checkReadNode)
|
||||||
@ -840,10 +875,12 @@ class ChatMessageDateAndStatusNode: ASDisplayNode {
|
|||||||
|
|
||||||
if let checkReadFrame = checkReadFrame {
|
if let checkReadFrame = checkReadFrame {
|
||||||
if checkReadNode.isHidden {
|
if checkReadNode.isHidden {
|
||||||
animateReadNode = animated
|
animateReadNode = animation.isAnimated
|
||||||
|
checkReadNode.frame = checkReadFrame.offsetBy(dx: leftOffset + backgroundInsets.left + reactionInset, dy: backgroundInsets.top + verticalInset)
|
||||||
|
} else {
|
||||||
|
animation.animator.updateFrame(layer: checkReadNode.layer, frame: checkReadFrame.offsetBy(dx: leftOffset + backgroundInsets.left + reactionInset, dy: backgroundInsets.top + verticalInset), completion: nil)
|
||||||
}
|
}
|
||||||
checkReadNode.isHidden = false
|
checkReadNode.isHidden = false
|
||||||
checkReadNode.frame = checkReadFrame.offsetBy(dx: leftOffset + backgroundInsets.left + reactionInset, dy: backgroundInsets.top + verticalInset)
|
|
||||||
} else {
|
} else {
|
||||||
checkReadNode.isHidden = true
|
checkReadNode.isHidden = true
|
||||||
}
|
}
|
||||||
@ -865,13 +902,15 @@ class ChatMessageDateAndStatusNode: ASDisplayNode {
|
|||||||
if !"".isEmpty {
|
if !"".isEmpty {
|
||||||
for i in 0 ..< arguments.reactions.count {
|
for i in 0 ..< arguments.reactions.count {
|
||||||
let node: StatusReactionNode
|
let node: StatusReactionNode
|
||||||
|
var animateNode = true
|
||||||
if strongSelf.reactionNodes.count > i {
|
if strongSelf.reactionNodes.count > i {
|
||||||
node = strongSelf.reactionNodes[i]
|
node = strongSelf.reactionNodes[i]
|
||||||
} else {
|
} else {
|
||||||
|
animateNode = false
|
||||||
node = StatusReactionNode()
|
node = StatusReactionNode()
|
||||||
if strongSelf.reactionNodes.count > i {
|
if strongSelf.reactionNodes.count > i {
|
||||||
let previousNode = strongSelf.reactionNodes[i]
|
let previousNode = strongSelf.reactionNodes[i]
|
||||||
if animated {
|
if animation.isAnimated {
|
||||||
previousNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak previousNode] _ in
|
previousNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak previousNode] _ in
|
||||||
previousNode?.removeFromSupernode()
|
previousNode?.removeFromSupernode()
|
||||||
})
|
})
|
||||||
@ -887,11 +926,16 @@ class ChatMessageDateAndStatusNode: ASDisplayNode {
|
|||||||
node.update(type: arguments.type, value: arguments.reactions[i].value, isSelected: arguments.reactions[i].isSelected, count: Int(arguments.reactions[i].count), theme: arguments.presentationData.theme.theme, wallpaper: arguments.presentationData.theme.wallpaper, animated: false)
|
node.update(type: arguments.type, value: arguments.reactions[i].value, isSelected: arguments.reactions[i].isSelected, count: Int(arguments.reactions[i].count), theme: arguments.presentationData.theme.theme, wallpaper: arguments.presentationData.theme.wallpaper, animated: false)
|
||||||
if node.supernode == nil {
|
if node.supernode == nil {
|
||||||
strongSelf.addSubnode(node)
|
strongSelf.addSubnode(node)
|
||||||
if animated {
|
if animation.isAnimated {
|
||||||
node.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
node.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
node.frame = CGRect(origin: CGPoint(x: reactionOffset, y: backgroundInsets.top + offset + verticalInset + 1.0), size: CGSize(width: reactionSize, height: reactionSize))
|
let nodeFrame = CGRect(origin: CGPoint(x: reactionOffset, y: backgroundInsets.top + offset + verticalInset + 1.0), size: CGSize(width: reactionSize, height: reactionSize))
|
||||||
|
if animateNode {
|
||||||
|
animation.animator.updateFrame(layer: node.layer, frame: nodeFrame, completion: nil)
|
||||||
|
} else {
|
||||||
|
node.frame = nodeFrame
|
||||||
|
}
|
||||||
reactionOffset += reactionSize + reactionSpacing
|
reactionOffset += reactionSize + reactionSpacing
|
||||||
}
|
}
|
||||||
if !arguments.reactions.isEmpty {
|
if !arguments.reactions.isEmpty {
|
||||||
@ -900,10 +944,7 @@ class ChatMessageDateAndStatusNode: ASDisplayNode {
|
|||||||
|
|
||||||
for _ in arguments.reactions.count ..< strongSelf.reactionNodes.count {
|
for _ in arguments.reactions.count ..< strongSelf.reactionNodes.count {
|
||||||
let node = strongSelf.reactionNodes.removeLast()
|
let node = strongSelf.reactionNodes.removeLast()
|
||||||
if animated {
|
if animation.isAnimated {
|
||||||
if let previousLayoutSize = previousLayoutSize {
|
|
||||||
node.frame = node.frame.offsetBy(dx: layoutSize.width - previousLayoutSize.width, dy: 0.0)
|
|
||||||
}
|
|
||||||
node.layer.animateScale(from: 1.0, to: 0.1, duration: 0.2, removeOnCompletion: false)
|
node.layer.animateScale(from: 1.0, to: 0.1, duration: 0.2, removeOnCompletion: false)
|
||||||
node.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak node] _ in
|
node.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak node] _ in
|
||||||
node?.removeFromSupernode()
|
node?.removeFromSupernode()
|
||||||
@ -920,18 +961,16 @@ class ChatMessageDateAndStatusNode: ASDisplayNode {
|
|||||||
strongSelf.reactionCountNode?.removeFromSupernode()
|
strongSelf.reactionCountNode?.removeFromSupernode()
|
||||||
strongSelf.addSubnode(node)
|
strongSelf.addSubnode(node)
|
||||||
strongSelf.reactionCountNode = node
|
strongSelf.reactionCountNode = node
|
||||||
if animated {
|
if animation.isAnimated {
|
||||||
node.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15)
|
node.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
node.frame = CGRect(origin: CGPoint(x: reactionOffset + 1.0, y: backgroundInsets.top + 1.0 + offset + verticalInset), size: layout.size)
|
let nodeFrame = CGRect(origin: CGPoint(x: reactionOffset + 1.0, y: backgroundInsets.top + 1.0 + offset + verticalInset), size: layout.size)
|
||||||
|
animation.animator.updateFrame(layer: node.layer, frame: nodeFrame, completion: nil)
|
||||||
reactionOffset += 1.0 + layout.size.width + 4.0
|
reactionOffset += 1.0 + layout.size.width + 4.0
|
||||||
} else if let reactionCountNode = strongSelf.reactionCountNode {
|
} else if let reactionCountNode = strongSelf.reactionCountNode {
|
||||||
strongSelf.reactionCountNode = nil
|
strongSelf.reactionCountNode = nil
|
||||||
if animated {
|
if animation.isAnimated {
|
||||||
if let previousLayoutSize = previousLayoutSize {
|
|
||||||
reactionCountNode.frame = reactionCountNode.frame.offsetBy(dx: layoutSize.width - previousLayoutSize.width, dy: 0.0)
|
|
||||||
}
|
|
||||||
reactionCountNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak reactionCountNode] _ in
|
reactionCountNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak reactionCountNode] _ in
|
||||||
reactionCountNode?.removeFromSupernode()
|
reactionCountNode?.removeFromSupernode()
|
||||||
})
|
})
|
||||||
@ -948,18 +987,16 @@ class ChatMessageDateAndStatusNode: ASDisplayNode {
|
|||||||
if currentRepliesIcon.supernode == nil {
|
if currentRepliesIcon.supernode == nil {
|
||||||
strongSelf.repliesIcon = currentRepliesIcon
|
strongSelf.repliesIcon = currentRepliesIcon
|
||||||
strongSelf.addSubnode(currentRepliesIcon)
|
strongSelf.addSubnode(currentRepliesIcon)
|
||||||
if animated {
|
if animation.isAnimated {
|
||||||
currentRepliesIcon.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15)
|
currentRepliesIcon.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
currentRepliesIcon.frame = CGRect(origin: CGPoint(x: reactionOffset - 2.0, y: backgroundInsets.top + offset + verticalInset + floor((date.size.height - repliesIconSize.height) / 2.0)), size: repliesIconSize)
|
let repliesIconFrame = CGRect(origin: CGPoint(x: reactionOffset - 2.0, y: backgroundInsets.top + offset + verticalInset + floor((date.size.height - repliesIconSize.height) / 2.0)), size: repliesIconSize)
|
||||||
|
animation.animator.updateFrame(layer: currentRepliesIcon.layer, frame: repliesIconFrame, completion: nil)
|
||||||
reactionOffset += 9.0
|
reactionOffset += 9.0
|
||||||
} else if let repliesIcon = strongSelf.repliesIcon {
|
} else if let repliesIcon = strongSelf.repliesIcon {
|
||||||
strongSelf.repliesIcon = nil
|
strongSelf.repliesIcon = nil
|
||||||
if animated {
|
if animation.isAnimated {
|
||||||
if let previousLayoutSize = previousLayoutSize {
|
|
||||||
repliesIcon.frame = repliesIcon.frame.offsetBy(dx: layoutSize.width - previousLayoutSize.width, dy: 0.0)
|
|
||||||
}
|
|
||||||
repliesIcon.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak repliesIcon] _ in
|
repliesIcon.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak repliesIcon] _ in
|
||||||
repliesIcon?.removeFromSupernode()
|
repliesIcon?.removeFromSupernode()
|
||||||
})
|
})
|
||||||
@ -974,18 +1011,16 @@ class ChatMessageDateAndStatusNode: ASDisplayNode {
|
|||||||
strongSelf.replyCountNode?.removeFromSupernode()
|
strongSelf.replyCountNode?.removeFromSupernode()
|
||||||
strongSelf.addSubnode(node)
|
strongSelf.addSubnode(node)
|
||||||
strongSelf.replyCountNode = node
|
strongSelf.replyCountNode = node
|
||||||
if animated {
|
if animation.isAnimated {
|
||||||
node.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15)
|
node.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
node.frame = CGRect(origin: CGPoint(x: reactionOffset + 4.0, y: backgroundInsets.top + 1.0 + offset + verticalInset), size: layout.size)
|
let replyCountFrame = CGRect(origin: CGPoint(x: reactionOffset + 4.0, y: backgroundInsets.top + 1.0 + offset + verticalInset), size: layout.size)
|
||||||
|
animation.animator.updateFrame(layer: node.layer, frame: replyCountFrame, completion: nil)
|
||||||
reactionOffset += 4.0 + layout.size.width
|
reactionOffset += 4.0 + layout.size.width
|
||||||
} else if let replyCountNode = strongSelf.replyCountNode {
|
} else if let replyCountNode = strongSelf.replyCountNode {
|
||||||
strongSelf.replyCountNode = nil
|
strongSelf.replyCountNode = nil
|
||||||
if animated {
|
if animation.isAnimated {
|
||||||
if let previousLayoutSize = previousLayoutSize {
|
|
||||||
replyCountNode.frame = replyCountNode.frame.offsetBy(dx: layoutSize.width - previousLayoutSize.width, dy: 0.0)
|
|
||||||
}
|
|
||||||
replyCountNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak replyCountNode] _ in
|
replyCountNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak replyCountNode] _ in
|
||||||
replyCountNode?.removeFromSupernode()
|
replyCountNode?.removeFromSupernode()
|
||||||
})
|
})
|
||||||
@ -999,11 +1034,11 @@ class ChatMessageDateAndStatusNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static func asyncLayout(_ node: ChatMessageDateAndStatusNode?) -> (_ arguments: Arguments) -> (CGFloat, (CGFloat) -> (CGSize, (Bool) -> ChatMessageDateAndStatusNode)) {
|
static func asyncLayout(_ node: ChatMessageDateAndStatusNode?) -> (_ arguments: Arguments) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation) -> ChatMessageDateAndStatusNode)) {
|
||||||
let currentLayout = node?.asyncLayout()
|
let currentLayout = node?.asyncLayout()
|
||||||
return { arguments in
|
return { arguments in
|
||||||
let resultNode: ChatMessageDateAndStatusNode
|
let resultNode: ChatMessageDateAndStatusNode
|
||||||
let resultSuggestedWidthAndContinue: (CGFloat, (CGFloat) -> (CGSize, (Bool) -> Void))
|
let resultSuggestedWidthAndContinue: (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation) -> Void))
|
||||||
if let node = node, let currentLayout = currentLayout {
|
if let node = node, let currentLayout = currentLayout {
|
||||||
resultNode = node
|
resultNode = node
|
||||||
resultSuggestedWidthAndContinue = currentLayout(arguments)
|
resultSuggestedWidthAndContinue = currentLayout(arguments)
|
||||||
@ -1014,8 +1049,8 @@ class ChatMessageDateAndStatusNode: ASDisplayNode {
|
|||||||
|
|
||||||
return (resultSuggestedWidthAndContinue.0, { boundingWidth in
|
return (resultSuggestedWidthAndContinue.0, { boundingWidth in
|
||||||
let (size, apply) = resultSuggestedWidthAndContinue.1(boundingWidth)
|
let (size, apply) = resultSuggestedWidthAndContinue.1(boundingWidth)
|
||||||
return (size, { animated in
|
return (size, { animation in
|
||||||
apply(animated)
|
apply(animation)
|
||||||
|
|
||||||
return resultNode
|
return resultNode
|
||||||
})
|
})
|
||||||
|
@ -108,7 +108,7 @@ class ChatMessageFileBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
|
|
||||||
let automaticDownload = shouldDownloadMediaAutomatically(settings: item.controllerInteraction.automaticMediaDownloadSettings, peerType: item.associatedData.automaticDownloadPeerType, networkType: item.associatedData.automaticDownloadNetworkType, authorPeerId: item.message.author?.id, contactsPeerIds: item.associatedData.contactsPeerIds, media: selectedFile!)
|
let automaticDownload = shouldDownloadMediaAutomatically(settings: item.controllerInteraction.automaticMediaDownloadSettings, peerType: item.associatedData.automaticDownloadPeerType, networkType: item.associatedData.automaticDownloadNetworkType, authorPeerId: item.message.author?.id, contactsPeerIds: item.associatedData.contactsPeerIds, media: selectedFile!)
|
||||||
|
|
||||||
let (initialWidth, refineLayout) = interactiveFileLayout(item.context, item.presentationData, item.message, item.associatedData, item.chatLocation, item.attributes, item.isItemPinned, item.isItemEdited, selectedFile!, automaticDownload, item.message.effectivelyIncoming(item.context.account.peerId), item.associatedData.isRecentActions, item.associatedData.forcedResourceStatus, statusType, item.message.groupingKey != nil ? selection : nil, CGSize(width: constrainedSize.width - layoutConstants.file.bubbleInsets.left - layoutConstants.file.bubbleInsets.right, height: constrainedSize.height))
|
let (initialWidth, refineLayout) = interactiveFileLayout(item.context, item.presentationData, item.message, item.topMessage, item.associatedData, item.chatLocation, item.attributes, item.isItemPinned, item.isItemEdited, selectedFile!, automaticDownload, item.message.effectivelyIncoming(item.context.account.peerId), item.associatedData.isRecentActions, item.associatedData.forcedResourceStatus, statusType, item.message.groupingKey != nil ? selection : nil, CGSize(width: constrainedSize.width - layoutConstants.file.bubbleInsets.left - layoutConstants.file.bubbleInsets.right, height: constrainedSize.height))
|
||||||
|
|
||||||
let contentProperties = ChatMessageBubbleContentProperties(hidesSimpleAuthorHeader: false, headerSpacing: 0.0, hidesBackground: .never, forceFullCorners: false, forceAlignment: .none)
|
let contentProperties = ChatMessageBubbleContentProperties(hidesSimpleAuthorHeader: false, headerSpacing: 0.0, hidesBackground: .never, forceFullCorners: false, forceAlignment: .none)
|
||||||
|
|
||||||
@ -130,13 +130,13 @@ class ChatMessageFileBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (CGSize(width: fileSize.width + layoutConstants.file.bubbleInsets.left + layoutConstants.file.bubbleInsets.right, height: fileSize.height + layoutConstants.file.bubbleInsets.top + bottomInset), { [weak self] _, synchronousLoads in
|
return (CGSize(width: fileSize.width + layoutConstants.file.bubbleInsets.left + layoutConstants.file.bubbleInsets.right, height: fileSize.height + layoutConstants.file.bubbleInsets.top + bottomInset), { [weak self] animation, synchronousLoads in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
strongSelf.item = item
|
strongSelf.item = item
|
||||||
|
|
||||||
strongSelf.interactiveFileNode.frame = CGRect(origin: CGPoint(x: layoutConstants.file.bubbleInsets.left, y: layoutConstants.file.bubbleInsets.top), size: fileSize)
|
strongSelf.interactiveFileNode.frame = CGRect(origin: CGPoint(x: layoutConstants.file.bubbleInsets.left, y: layoutConstants.file.bubbleInsets.top), size: fileSize)
|
||||||
|
|
||||||
fileApply(synchronousLoads)
|
fileApply(synchronousLoads, animation)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -387,7 +387,7 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureRecognizerD
|
|||||||
isReplyThread = true
|
isReplyThread = true
|
||||||
}
|
}
|
||||||
|
|
||||||
let (videoLayout, videoApply) = makeVideoLayout(ChatMessageBubbleContentItem(context: item.context, controllerInteraction: item.controllerInteraction, message: item.message, read: item.read, chatLocation: item.chatLocation, presentationData: item.presentationData, associatedData: item.associatedData, attributes: item.content.firstMessageAttributes, isItemPinned: item.message.tags.contains(.pinned) && !isReplyThread, isItemEdited: false), params.width - params.leftInset - params.rightInset - avatarInset, displaySize, maximumDisplaySize, isPlaying ? 1.0 : 0.0, .free, automaticDownload)
|
let (videoLayout, videoApply) = makeVideoLayout(ChatMessageBubbleContentItem(context: item.context, controllerInteraction: item.controllerInteraction, message: item.message, topMessage: item.content.firstMessage, read: item.read, chatLocation: item.chatLocation, presentationData: item.presentationData, associatedData: item.associatedData, attributes: item.content.firstMessageAttributes, isItemPinned: item.message.tags.contains(.pinned) && !isReplyThread, isItemEdited: false), params.width - params.leftInset - params.rightInset - avatarInset, displaySize, maximumDisplaySize, isPlaying ? 1.0 : 0.0, .free, automaticDownload)
|
||||||
|
|
||||||
let videoFrame = CGRect(origin: CGPoint(x: (incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + effectiveAvatarInset + layoutConstants.bubble.contentInsets.left) : (params.width - params.rightInset - videoLayout.contentSize.width - layoutConstants.bubble.edgeInset - layoutConstants.bubble.contentInsets.left - deliveryFailedInset)), y: 0.0), size: videoLayout.contentSize)
|
let videoFrame = CGRect(origin: CGPoint(x: (incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + effectiveAvatarInset + layoutConstants.bubble.contentInsets.left) : (params.width - params.rightInset - videoLayout.contentSize.width - layoutConstants.bubble.edgeInset - layoutConstants.bubble.contentInsets.left - deliveryFailedInset)), y: 0.0), size: videoLayout.contentSize)
|
||||||
|
|
||||||
@ -775,7 +775,7 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureRecognizerD
|
|||||||
}
|
}
|
||||||
strongSelf.addSubnode(actionButtonsNode)
|
strongSelf.addSubnode(actionButtonsNode)
|
||||||
} else {
|
} else {
|
||||||
if case let .System(duration) = animation {
|
if case let .System(duration, _) = animation {
|
||||||
actionButtonsNode.layer.animateFrame(from: previousFrame, to: actionButtonsFrame, duration: duration, timingFunction: kCAMediaTimingFunctionSpring)
|
actionButtonsNode.layer.animateFrame(from: previousFrame, to: actionButtonsFrame, duration: duration, timingFunction: kCAMediaTimingFunctionSpring)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1184,7 +1184,7 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureRecognizerD
|
|||||||
effectiveAvatarInset *= (1.0 - scaleProgress)
|
effectiveAvatarInset *= (1.0 - scaleProgress)
|
||||||
displaySize = CGSize(width: initialSize.width + (targetSize.width - initialSize.width) * animationProgress, height: initialSize.height + (targetSize.height - initialSize.height) * animationProgress)
|
displaySize = CGSize(width: initialSize.width + (targetSize.width - initialSize.width) * animationProgress, height: initialSize.height + (targetSize.height - initialSize.height) * animationProgress)
|
||||||
|
|
||||||
let (videoLayout, videoApply) = makeVideoLayout(ChatMessageBubbleContentItem(context: item.context, controllerInteraction: item.controllerInteraction, message: item.message, read: item.read, chatLocation: item.chatLocation, presentationData: item.presentationData, associatedData: item.associatedData, attributes: item.content.firstMessageAttributes, isItemPinned: item.message.tags.contains(.pinned) && !isReplyThread, isItemEdited: false), params.width - params.leftInset - params.rightInset - avatarInset, displaySize, maximumDisplaySize, scaleProgress, .free, self.appliedAutomaticDownload)
|
let (videoLayout, videoApply) = makeVideoLayout(ChatMessageBubbleContentItem(context: item.context, controllerInteraction: item.controllerInteraction, message: item.message, topMessage: item.message, read: item.read, chatLocation: item.chatLocation, presentationData: item.presentationData, associatedData: item.associatedData, attributes: item.content.firstMessageAttributes, isItemPinned: item.message.tags.contains(.pinned) && !isReplyThread, isItemEdited: false), params.width - params.leftInset - params.rightInset - avatarInset, displaySize, maximumDisplaySize, scaleProgress, .free, self.appliedAutomaticDownload)
|
||||||
|
|
||||||
let availableContentWidth = params.width - params.leftInset - params.rightInset - layoutConstants.bubble.edgeInset * 2.0 - avatarInset - layoutConstants.bubble.contentInsets.left
|
let availableContentWidth = params.width - params.leftInset - params.rightInset - layoutConstants.bubble.edgeInset * 2.0 - avatarInset - layoutConstants.bubble.contentInsets.left
|
||||||
let videoFrame = CGRect(origin: CGPoint(x: (incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + effectiveAvatarInset + layoutConstants.bubble.contentInsets.left) : (params.width - params.rightInset - videoLayout.contentSize.width - layoutConstants.bubble.edgeInset - layoutConstants.bubble.contentInsets.left - deliveryFailedInset)), y: 0.0), size: videoLayout.contentSize)
|
let videoFrame = CGRect(origin: CGPoint(x: (incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + effectiveAvatarInset + layoutConstants.bubble.contentInsets.left) : (params.width - params.rightInset - videoLayout.contentSize.width - layoutConstants.bubble.edgeInset - layoutConstants.bubble.contentInsets.left - deliveryFailedInset)), y: 0.0), size: videoLayout.contentSize)
|
||||||
@ -1198,8 +1198,6 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureRecognizerD
|
|||||||
}
|
}
|
||||||
videoApply(videoLayoutData, .immediate)
|
videoApply(videoLayoutData, .immediate)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if let shareButtonNode = self.shareButtonNode {
|
if let shareButtonNode = self.shareButtonNode {
|
||||||
let buttonSize = shareButtonNode.frame.size
|
let buttonSize = shareButtonNode.frame.size
|
||||||
shareButtonNode.frame = CGRect(origin: CGPoint(x: min(params.width - buttonSize.width - 8.0, videoFrame.maxX - 7.0), y: videoFrame.maxY - 24.0 - buttonSize.height), size: buttonSize)
|
shareButtonNode.frame = CGRect(origin: CGPoint(x: min(params.width - buttonSize.width - 8.0, videoFrame.maxX - 7.0), y: videoFrame.maxY - 24.0 - buttonSize.height), size: buttonSize)
|
||||||
|
@ -213,7 +213,7 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func asyncLayout() -> (_ context: AccountContext, _ presentationData: ChatPresentationData, _ message: Message, _ associatedData: ChatMessageItemAssociatedData, _ chatLocation: ChatLocation, _ attributes: ChatMessageEntryAttributes, _ isPinned: Bool, _ forcedIsEdited: Bool, _ file: TelegramMediaFile, _ automaticDownload: Bool, _ incoming: Bool, _ isRecentActions: Bool, _ forcedResourceStatus: FileMediaResourceStatus?, _ dateAndStatusType: ChatMessageDateAndStatusType?, _ messageSelection: Bool?, _ constrainedSize: CGSize) -> (CGFloat, (CGSize) -> (CGFloat, (CGFloat) -> (CGSize, (Bool) -> Void))) {
|
func asyncLayout() -> (_ context: AccountContext, _ presentationData: ChatPresentationData, _ message: Message, _ topMessage: Message, _ associatedData: ChatMessageItemAssociatedData, _ chatLocation: ChatLocation, _ attributes: ChatMessageEntryAttributes, _ isPinned: Bool, _ forcedIsEdited: Bool, _ file: TelegramMediaFile, _ automaticDownload: Bool, _ incoming: Bool, _ isRecentActions: Bool, _ forcedResourceStatus: FileMediaResourceStatus?, _ dateAndStatusType: ChatMessageDateAndStatusType?, _ messageSelection: Bool?, _ constrainedSize: CGSize) -> (CGFloat, (CGSize) -> (CGFloat, (CGFloat) -> (CGSize, (Bool, ListViewItemUpdateAnimation) -> Void))) {
|
||||||
let currentFile = self.file
|
let currentFile = self.file
|
||||||
|
|
||||||
let titleAsyncLayout = TextNode.asyncLayout(self.titleNode)
|
let titleAsyncLayout = TextNode.asyncLayout(self.titleNode)
|
||||||
@ -223,7 +223,7 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
|
|||||||
|
|
||||||
let currentMessage = self.message
|
let currentMessage = self.message
|
||||||
|
|
||||||
return { context, presentationData, message, associatedData, chatLocation, attributes, isPinned, forcedIsEdited, file, automaticDownload, incoming, isRecentActions, forcedResourceStatus, dateAndStatusType, messageSelection, constrainedSize in
|
return { context, presentationData, message, topMessage, associatedData, chatLocation, attributes, isPinned, forcedIsEdited, file, automaticDownload, incoming, isRecentActions, forcedResourceStatus, dateAndStatusType, messageSelection, constrainedSize in
|
||||||
return (CGFloat.greatestFiniteMagnitude, { constrainedSize in
|
return (CGFloat.greatestFiniteMagnitude, { constrainedSize in
|
||||||
let titleFont = Font.regular(floor(presentationData.fontSize.baseDisplaySize * 16.0 / 17.0))
|
let titleFont = Font.regular(floor(presentationData.fontSize.baseDisplaySize * 16.0 / 17.0))
|
||||||
let descriptionFont = Font.with(size: floor(presentationData.fontSize.baseDisplaySize * 13.0 / 17.0), design: .regular, weight: .regular, traits: [.monospacedNumbers])
|
let descriptionFont = Font.with(size: floor(presentationData.fontSize.baseDisplaySize * 13.0 / 17.0), design: .regular, weight: .regular, traits: [.monospacedNumbers])
|
||||||
@ -422,7 +422,7 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
|
|||||||
controlAreaWidth = progressFrame.maxX + 8.0
|
controlAreaWidth = progressFrame.maxX + 8.0
|
||||||
}
|
}
|
||||||
|
|
||||||
var statusSuggestedWidthAndContinue: (CGFloat, (CGFloat) -> (CGSize, (Bool) -> Void))?
|
var statusSuggestedWidthAndContinue: (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation) -> Void))?
|
||||||
if let statusType = dateAndStatusType {
|
if let statusType = dateAndStatusType {
|
||||||
var edited = false
|
var edited = false
|
||||||
if attributes.updatingMedia != nil {
|
if attributes.updatingMedia != nil {
|
||||||
@ -430,7 +430,7 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
var viewCount: Int?
|
var viewCount: Int?
|
||||||
var dateReplies = 0
|
var dateReplies = 0
|
||||||
let dateReactions: [MessageReaction] = mergedMessageReactions(attributes: message.attributes)?.reactions ?? []
|
let dateReactions: [MessageReaction] = mergedMessageReactions(attributes: topMessage.attributes)?.reactions ?? []
|
||||||
for attribute in message.attributes {
|
for attribute in message.attributes {
|
||||||
if let attribute = attribute as? EditedMessageAttribute {
|
if let attribute = attribute as? EditedMessageAttribute {
|
||||||
edited = !attribute.isHidden
|
edited = !attribute.isHidden
|
||||||
@ -455,7 +455,7 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
|
|||||||
impressionCount: viewCount,
|
impressionCount: viewCount,
|
||||||
dateText: dateText,
|
dateText: dateText,
|
||||||
type: statusType,
|
type: statusType,
|
||||||
layoutInput: .trailingContent(contentWidth: iconFrame == nil ? 1000.0 : controlAreaWidth, preferAdditionalInset: true),
|
layoutInput: .trailingContent(contentWidth: iconFrame == nil ? 1000.0 : controlAreaWidth, reactionSettings: ChatMessageDateAndStatusNode.ReactionSettings(preferAdditionalInset: true)),
|
||||||
constrainedSize: constrainedSize,
|
constrainedSize: constrainedSize,
|
||||||
availableReactions: associatedData.availableReactions,
|
availableReactions: associatedData.availableReactions,
|
||||||
reactions: dateReactions,
|
reactions: dateReactions,
|
||||||
@ -520,7 +520,7 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
|
|||||||
fittedLayoutSize = CGSize(width: unionSize.width, height: unionSize.height)
|
fittedLayoutSize = CGSize(width: unionSize.width, height: unionSize.height)
|
||||||
}
|
}
|
||||||
|
|
||||||
var statusSizeAndApply: (CGSize, (Bool) -> Void)?
|
var statusSizeAndApply: (CGSize, (ListViewItemUpdateAnimation) -> Void)?
|
||||||
if let statusSuggestedWidthAndContinue = statusSuggestedWidthAndContinue {
|
if let statusSuggestedWidthAndContinue = statusSuggestedWidthAndContinue {
|
||||||
statusSizeAndApply = statusSuggestedWidthAndContinue.1(boundingWidth)
|
statusSizeAndApply = statusSuggestedWidthAndContinue.1(boundingWidth)
|
||||||
}
|
}
|
||||||
@ -541,7 +541,7 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
|
|||||||
streamingCacheStatusFrame = CGRect()
|
streamingCacheStatusFrame = CGRect()
|
||||||
}
|
}
|
||||||
|
|
||||||
return (fittedLayoutSize, { [weak self] synchronousLoads in
|
return (fittedLayoutSize, { [weak self] synchronousLoads, animation in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
strongSelf.context = context
|
strongSelf.context = context
|
||||||
strongSelf.presentationData = presentationData
|
strongSelf.presentationData = presentationData
|
||||||
@ -575,11 +575,14 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
|
|||||||
statusReferenceFrame = progressFrame.offsetBy(dx: 0.0, dy: 8.0)
|
statusReferenceFrame = progressFrame.offsetBy(dx: 0.0, dy: 8.0)
|
||||||
}
|
}
|
||||||
if let statusSizeAndApply = statusSizeAndApply {
|
if let statusSizeAndApply = statusSizeAndApply {
|
||||||
|
let statusFrame = CGRect(origin: CGPoint(x: statusReferenceFrame.minX, y: statusReferenceFrame.maxY + statusOffset), size: statusSizeAndApply.0)
|
||||||
if strongSelf.dateAndStatusNode.supernode == nil {
|
if strongSelf.dateAndStatusNode.supernode == nil {
|
||||||
|
strongSelf.dateAndStatusNode.frame = statusFrame
|
||||||
strongSelf.addSubnode(strongSelf.dateAndStatusNode)
|
strongSelf.addSubnode(strongSelf.dateAndStatusNode)
|
||||||
|
} else {
|
||||||
|
animation.animator.updateFrame(layer: strongSelf.dateAndStatusNode.layer, frame: statusFrame, completion: nil)
|
||||||
}
|
}
|
||||||
strongSelf.dateAndStatusNode.frame = CGRect(origin: CGPoint(x: statusReferenceFrame.minX, y: statusReferenceFrame.maxY + statusOffset), size: statusSizeAndApply.0)
|
statusSizeAndApply.1(animation)
|
||||||
statusSizeAndApply.1(false)
|
|
||||||
} else if strongSelf.dateAndStatusNode.supernode != nil {
|
} else if strongSelf.dateAndStatusNode.supernode != nil {
|
||||||
strongSelf.dateAndStatusNode.removeFromSupernode()
|
strongSelf.dateAndStatusNode.removeFromSupernode()
|
||||||
}
|
}
|
||||||
@ -1057,12 +1060,12 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
|
|||||||
self.fetchingCompactTextNode.frame = CGRect(origin: self.descriptionNode.frame.origin, size: fetchingCompactSize)
|
self.fetchingCompactTextNode.frame = CGRect(origin: self.descriptionNode.frame.origin, size: fetchingCompactSize)
|
||||||
}
|
}
|
||||||
|
|
||||||
static func asyncLayout(_ node: ChatMessageInteractiveFileNode?) -> (_ context: AccountContext, _ presentationData: ChatPresentationData, _ message: Message, _ associatedData: ChatMessageItemAssociatedData, _ chatLocation: ChatLocation, _ attributes: ChatMessageEntryAttributes, _ isPinned: Bool, _ forcedIsEdited: Bool, _ file: TelegramMediaFile, _ automaticDownload: Bool, _ incoming: Bool, _ isRecentActions: Bool, _ forcedResourceStatus: FileMediaResourceStatus?, _ dateAndStatusType: ChatMessageDateAndStatusType?, _ messageSelection: Bool?, _ constrainedSize: CGSize) -> (CGFloat, (CGSize) -> (CGFloat, (CGFloat) -> (CGSize, (Bool) -> ChatMessageInteractiveFileNode))) {
|
static func asyncLayout(_ node: ChatMessageInteractiveFileNode?) -> (_ context: AccountContext, _ presentationData: ChatPresentationData, _ message: Message, _ topMessage: Message, _ associatedData: ChatMessageItemAssociatedData, _ chatLocation: ChatLocation, _ attributes: ChatMessageEntryAttributes, _ isPinned: Bool, _ forcedIsEdited: Bool, _ file: TelegramMediaFile, _ automaticDownload: Bool, _ incoming: Bool, _ isRecentActions: Bool, _ forcedResourceStatus: FileMediaResourceStatus?, _ dateAndStatusType: ChatMessageDateAndStatusType?, _ messageSelection: Bool?, _ constrainedSize: CGSize) -> (CGFloat, (CGSize) -> (CGFloat, (CGFloat) -> (CGSize, (Bool, ListViewItemUpdateAnimation) -> ChatMessageInteractiveFileNode))) {
|
||||||
let currentAsyncLayout = node?.asyncLayout()
|
let currentAsyncLayout = node?.asyncLayout()
|
||||||
|
|
||||||
return { context, presentationData, message, associatedData, chatLocation, attributes, isPinned, forcedIsEdited, file, automaticDownload, incoming, isRecentActions, forcedResourceStatus, dateAndStatusType, messageSelection, constrainedSize in
|
return { context, presentationData, message, topMessage, associatedData, chatLocation, attributes, isPinned, forcedIsEdited, file, automaticDownload, incoming, isRecentActions, forcedResourceStatus, dateAndStatusType, messageSelection, constrainedSize in
|
||||||
var fileNode: ChatMessageInteractiveFileNode
|
var fileNode: ChatMessageInteractiveFileNode
|
||||||
var fileLayout: (_ context: AccountContext, _ presentationData: ChatPresentationData, _ message: Message, _ associatedData: ChatMessageItemAssociatedData, _ chatLocation: ChatLocation, _ attributes: ChatMessageEntryAttributes, _ isPinned: Bool, _ forcedIsEdited: Bool, _ file: TelegramMediaFile, _ automaticDownload: Bool, _ incoming: Bool, _ isRecentActions: Bool, _ forcedResourceStatus: FileMediaResourceStatus?, _ dateAndStatusType: ChatMessageDateAndStatusType?, _ messageSelection: Bool?, _ constrainedSize: CGSize) -> (CGFloat, (CGSize) -> (CGFloat, (CGFloat) -> (CGSize, (Bool) -> Void)))
|
var fileLayout: (_ context: AccountContext, _ presentationData: ChatPresentationData, _ message: Message, _ topMessage: Message, _ associatedData: ChatMessageItemAssociatedData, _ chatLocation: ChatLocation, _ attributes: ChatMessageEntryAttributes, _ isPinned: Bool, _ forcedIsEdited: Bool, _ file: TelegramMediaFile, _ automaticDownload: Bool, _ incoming: Bool, _ isRecentActions: Bool, _ forcedResourceStatus: FileMediaResourceStatus?, _ dateAndStatusType: ChatMessageDateAndStatusType?, _ messageSelection: Bool?, _ constrainedSize: CGSize) -> (CGFloat, (CGSize) -> (CGFloat, (CGFloat) -> (CGSize, (Bool, ListViewItemUpdateAnimation) -> Void)))
|
||||||
|
|
||||||
if let node = node, let currentAsyncLayout = currentAsyncLayout {
|
if let node = node, let currentAsyncLayout = currentAsyncLayout {
|
||||||
fileNode = node
|
fileNode = node
|
||||||
@ -1072,7 +1075,7 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
|
|||||||
fileLayout = fileNode.asyncLayout()
|
fileLayout = fileNode.asyncLayout()
|
||||||
}
|
}
|
||||||
|
|
||||||
let (initialWidth, continueLayout) = fileLayout(context, presentationData, message, associatedData, chatLocation, attributes, isPinned, forcedIsEdited, file, automaticDownload, incoming, isRecentActions, forcedResourceStatus, dateAndStatusType, messageSelection, constrainedSize)
|
let (initialWidth, continueLayout) = fileLayout(context, presentationData, message, topMessage, associatedData, chatLocation, attributes, isPinned, forcedIsEdited, file, automaticDownload, incoming, isRecentActions, forcedResourceStatus, dateAndStatusType, messageSelection, constrainedSize)
|
||||||
|
|
||||||
return (initialWidth, { constrainedSize in
|
return (initialWidth, { constrainedSize in
|
||||||
let (finalWidth, finalLayout) = continueLayout(constrainedSize)
|
let (finalWidth, finalLayout) = continueLayout(constrainedSize)
|
||||||
@ -1080,8 +1083,8 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
|
|||||||
return (finalWidth, { boundingWidth in
|
return (finalWidth, { boundingWidth in
|
||||||
let (finalSize, apply) = finalLayout(boundingWidth)
|
let (finalSize, apply) = finalLayout(boundingWidth)
|
||||||
|
|
||||||
return (finalSize, { synchronousLoads in
|
return (finalSize, { synchronousLoads, animation in
|
||||||
apply(synchronousLoads)
|
apply(synchronousLoads, animation)
|
||||||
return fileNode
|
return fileNode
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -362,7 +362,7 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dateAndStatusApply(false)
|
dateAndStatusApply(.None)
|
||||||
switch layoutData {
|
switch layoutData {
|
||||||
case let .unconstrained(width):
|
case let .unconstrained(width):
|
||||||
let dateAndStatusOrigin: CGPoint
|
let dateAndStatusOrigin: CGPoint
|
||||||
|
@ -345,7 +345,7 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func asyncLayout() -> (_ context: AccountContext, _ presentationData: ChatPresentationData, _ dateTimeFormat: PresentationDateTimeFormat, _ message: Message, _ associatedData: ChatMessageItemAssociatedData, _ 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))) {
|
func asyncLayout() -> (_ context: AccountContext, _ presentationData: ChatPresentationData, _ dateTimeFormat: PresentationDateTimeFormat, _ message: Message, _ associatedData: ChatMessageItemAssociatedData, _ 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, (ListViewItemUpdateAnimation, 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()
|
||||||
@ -465,7 +465,7 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio
|
|||||||
}
|
}
|
||||||
|
|
||||||
var statusSize = CGSize()
|
var statusSize = CGSize()
|
||||||
var statusApply: ((Bool) -> Void)?
|
var statusApply: ((ListViewItemUpdateAnimation) -> Void)?
|
||||||
|
|
||||||
if let dateAndStatus = dateAndStatus {
|
if let dateAndStatus = dateAndStatus {
|
||||||
let statusSuggestedWidthAndContinue = statusLayout(ChatMessageDateAndStatusNode.Arguments(
|
let statusSuggestedWidthAndContinue = statusLayout(ChatMessageDateAndStatusNode.Arguments(
|
||||||
@ -854,9 +854,9 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio
|
|||||||
strongSelf.pinchContainerNode.update(size: imageFrame.size, transition: .immediate)
|
strongSelf.pinchContainerNode.update(size: imageFrame.size, transition: .immediate)
|
||||||
strongSelf.imageNode.frame = CGRect(origin: CGPoint(), size: imageFrame.size)
|
strongSelf.imageNode.frame = CGRect(origin: CGPoint(), size: imageFrame.size)
|
||||||
} else {
|
} else {
|
||||||
transition.updateFrame(node: strongSelf.pinchContainerNode, frame: imageFrame)
|
transition.animator.updateFrame(layer: strongSelf.pinchContainerNode.layer, frame: imageFrame, completion: nil)
|
||||||
transition.updateFrame(node: strongSelf.imageNode, frame: CGRect(origin: CGPoint(), size: imageFrame.size))
|
transition.animator.updateFrame(layer: strongSelf.imageNode.layer, frame: CGRect(origin: CGPoint(), size: imageFrame.size), completion: nil)
|
||||||
strongSelf.pinchContainerNode.update(size: imageFrame.size, transition: transition)
|
strongSelf.pinchContainerNode.update(size: imageFrame.size, transition: transition.transition)
|
||||||
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -871,11 +871,7 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio
|
|||||||
if strongSelf.dateAndStatusNode.supernode == nil {
|
if strongSelf.dateAndStatusNode.supernode == nil {
|
||||||
strongSelf.pinchContainerNode.contentNode.addSubnode(strongSelf.dateAndStatusNode)
|
strongSelf.pinchContainerNode.contentNode.addSubnode(strongSelf.dateAndStatusNode)
|
||||||
}
|
}
|
||||||
var hasAnimation = true
|
statusApply(transition)
|
||||||
if transition.isAnimated {
|
|
||||||
hasAnimation = false
|
|
||||||
}
|
|
||||||
statusApply(hasAnimation)
|
|
||||||
|
|
||||||
let dateAndStatusFrame = CGRect(origin: CGPoint(x: cleanImageFrame.width - layoutConstants.image.statusInsets.right - statusSize.width, y: cleanImageFrame.height - layoutConstants.image.statusInsets.bottom - statusSize.height), size: statusSize)
|
let dateAndStatusFrame = CGRect(origin: CGPoint(x: cleanImageFrame.width - layoutConstants.image.statusInsets.right - statusSize.width, y: cleanImageFrame.height - layoutConstants.image.statusInsets.bottom - statusSize.height), size: statusSize)
|
||||||
|
|
||||||
@ -1501,12 +1497,12 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static func asyncLayout(_ node: ChatMessageInteractiveMediaNode?) -> (_ context: AccountContext, _ presentationData: ChatPresentationData, _ dateTimeFormat: PresentationDateTimeFormat, _ message: Message, _ associatedData: ChatMessageItemAssociatedData, _ 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))) {
|
static func asyncLayout(_ node: ChatMessageInteractiveMediaNode?) -> (_ context: AccountContext, _ presentationData: ChatPresentationData, _ dateTimeFormat: PresentationDateTimeFormat, _ message: Message, _ associatedData: ChatMessageItemAssociatedData, _ 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, (ListViewItemUpdateAnimation, Bool) -> ChatMessageInteractiveMediaNode))) {
|
||||||
let currentAsyncLayout = node?.asyncLayout()
|
let currentAsyncLayout = node?.asyncLayout()
|
||||||
|
|
||||||
return { context, presentationData, dateTimeFormat, message, associatedData, attributes, media, dateAndStatus, automaticDownload, peerType, sizeCalculation, layoutConstants, contentMode in
|
return { context, presentationData, dateTimeFormat, message, associatedData, attributes, media, dateAndStatus, automaticDownload, peerType, sizeCalculation, layoutConstants, contentMode in
|
||||||
var imageNode: ChatMessageInteractiveMediaNode
|
var imageNode: ChatMessageInteractiveMediaNode
|
||||||
var imageLayout: (_ context: AccountContext, _ presentationData: ChatPresentationData, _ dateTimeFormat: PresentationDateTimeFormat, _ message: Message, _ associatedData: ChatMessageItemAssociatedData, _ 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)))
|
var imageLayout: (_ context: AccountContext, _ presentationData: ChatPresentationData, _ dateTimeFormat: PresentationDateTimeFormat, _ message: Message, _ associatedData: ChatMessageItemAssociatedData, _ 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, (ListViewItemUpdateAnimation, Bool) -> Void)))
|
||||||
|
|
||||||
if let node = node, let currentAsyncLayout = currentAsyncLayout {
|
if let node = node, let currentAsyncLayout = currentAsyncLayout {
|
||||||
imageNode = node
|
imageNode = node
|
||||||
|
@ -237,7 +237,7 @@ class ChatMessageMapBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var statusSize = CGSize()
|
var statusSize = CGSize()
|
||||||
var statusApply: ((Bool) -> Void)?
|
var statusApply: ((ListViewItemUpdateAnimation) -> Void)?
|
||||||
|
|
||||||
if let statusType = statusType {
|
if let statusType = statusType {
|
||||||
var isReplyThread = false
|
var isReplyThread = false
|
||||||
@ -308,7 +308,7 @@ class ChatMessageMapBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
strongSelf.imageNode.frame = imageFrame
|
strongSelf.imageNode.frame = imageFrame
|
||||||
|
|
||||||
var transition: ContainedViewLayoutTransition = .immediate
|
var transition: ContainedViewLayoutTransition = .immediate
|
||||||
if case let .System(duration) = animation {
|
if case let .System(duration, _) = animation {
|
||||||
transition = .animated(duration: duration, curve: .spring)
|
transition = .animated(duration: duration, curve: .spring)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -336,11 +336,7 @@ class ChatMessageMapBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
if strongSelf.dateAndStatusNode.supernode == nil {
|
if strongSelf.dateAndStatusNode.supernode == nil {
|
||||||
strongSelf.addSubnode(strongSelf.dateAndStatusNode)
|
strongSelf.addSubnode(strongSelf.dateAndStatusNode)
|
||||||
}
|
}
|
||||||
var hasAnimation = true
|
statusApply(animation)
|
||||||
if case .None = animation {
|
|
||||||
hasAnimation = false
|
|
||||||
}
|
|
||||||
statusApply(hasAnimation)
|
|
||||||
strongSelf.dateAndStatusNode.frame = statusFrame.offsetBy(dx: imageFrame.minX, dy: imageFrame.minY)
|
strongSelf.dateAndStatusNode.frame = statusFrame.offsetBy(dx: imageFrame.minX, dy: imageFrame.minY)
|
||||||
} else if strongSelf.dateAndStatusNode.supernode != nil {
|
} else if strongSelf.dateAndStatusNode.supernode != nil {
|
||||||
strongSelf.dateAndStatusNode.removeFromSupernode()
|
strongSelf.dateAndStatusNode.removeFromSupernode()
|
||||||
|
@ -246,14 +246,10 @@ class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
strongSelf.automaticPlayback = automaticPlayback
|
strongSelf.automaticPlayback = automaticPlayback
|
||||||
|
|
||||||
let imageFrame = CGRect(origin: CGPoint(x: bubbleInsets.left, y: bubbleInsets.top), size: imageSize)
|
let imageFrame = CGRect(origin: CGPoint(x: bubbleInsets.left, y: bubbleInsets.top), size: imageSize)
|
||||||
var transition: ContainedViewLayoutTransition = .immediate
|
|
||||||
if case let .System(duration) = animation {
|
|
||||||
transition = .animated(duration: duration, curve: .spring)
|
|
||||||
}
|
|
||||||
|
|
||||||
transition.updateFrame(node: strongSelf.interactiveImageNode, frame: imageFrame)
|
animation.animator.updateFrame(layer: strongSelf.interactiveImageNode.layer, frame: imageFrame, completion: nil)
|
||||||
|
|
||||||
imageApply(transition, synchronousLoads)
|
imageApply(animation, synchronousLoads)
|
||||||
|
|
||||||
if let selection = selection {
|
if let selection = selection {
|
||||||
if let selectionNode = strongSelf.selectionNode {
|
if let selectionNode = strongSelf.selectionNode {
|
||||||
|
@ -1057,7 +1057,7 @@ class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
statusType = nil
|
statusType = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var statusSuggestedWidthAndContinue: (CGFloat, (CGFloat) -> (CGSize, (Bool) -> Void))?
|
var statusSuggestedWidthAndContinue: (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation) -> Void))?
|
||||||
|
|
||||||
if let statusType = statusType {
|
if let statusType = statusType {
|
||||||
var isReplyThread = false
|
var isReplyThread = false
|
||||||
@ -1072,7 +1072,7 @@ class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
impressionCount: viewCount,
|
impressionCount: viewCount,
|
||||||
dateText: dateText,
|
dateText: dateText,
|
||||||
type: statusType,
|
type: statusType,
|
||||||
layoutInput: .trailingContent(contentWidth: 100.0, preferAdditionalInset: true),
|
layoutInput: .trailingContent(contentWidth: 100.0, reactionSettings: ChatMessageDateAndStatusNode.ReactionSettings(preferAdditionalInset: true)),
|
||||||
constrainedSize: textConstrainedSize,
|
constrainedSize: textConstrainedSize,
|
||||||
availableReactions: item.associatedData.availableReactions,
|
availableReactions: item.associatedData.availableReactions,
|
||||||
reactions: dateReactions,
|
reactions: dateReactions,
|
||||||
|
@ -0,0 +1,286 @@
|
|||||||
|
import Foundation
|
||||||
|
import UIKit
|
||||||
|
import Postbox
|
||||||
|
import Display
|
||||||
|
import AsyncDisplayKit
|
||||||
|
import SwiftSignalKit
|
||||||
|
import TelegramCore
|
||||||
|
import TelegramPresentationData
|
||||||
|
import RadialStatusNode
|
||||||
|
import AnimatedCountLabelNode
|
||||||
|
import AnimatedAvatarSetNode
|
||||||
|
import ReactionButtonListComponent
|
||||||
|
import AccountContext
|
||||||
|
|
||||||
|
final class MessageReactionButtonsNode: ASDisplayNode {
|
||||||
|
enum DisplayType {
|
||||||
|
case incoming
|
||||||
|
case outgoing
|
||||||
|
case freeform
|
||||||
|
}
|
||||||
|
|
||||||
|
private let container: ReactionButtonsLayoutContainer
|
||||||
|
var reactionSelected: ((String) -> Void)?
|
||||||
|
|
||||||
|
override init() {
|
||||||
|
self.container = ReactionButtonsLayoutContainer()
|
||||||
|
|
||||||
|
super.init()
|
||||||
|
}
|
||||||
|
|
||||||
|
func prepareUpdate(
|
||||||
|
context: AccountContext,
|
||||||
|
presentationData: ChatPresentationData,
|
||||||
|
availableReactions: AvailableReactions?,
|
||||||
|
reactions: ReactionsMessageAttribute,
|
||||||
|
constrainedWidth: CGFloat,
|
||||||
|
type: DisplayType
|
||||||
|
) -> (proposedWidth: CGFloat, continueLayout: (CGFloat) -> (size: CGSize, apply: (ListViewItemUpdateAnimation) -> Void)) {
|
||||||
|
let reactionColors: ReactionButtonComponent.Colors
|
||||||
|
switch type {
|
||||||
|
case .incoming, .freeform:
|
||||||
|
reactionColors = ReactionButtonComponent.Colors(
|
||||||
|
background: presentationData.theme.theme.chat.message.incoming.accentControlColor.withMultipliedAlpha(0.1).argb,
|
||||||
|
foreground: presentationData.theme.theme.chat.message.incoming.accentTextColor.argb,
|
||||||
|
stroke: presentationData.theme.theme.chat.message.incoming.accentTextColor.argb
|
||||||
|
)
|
||||||
|
case .outgoing:
|
||||||
|
reactionColors = ReactionButtonComponent.Colors(
|
||||||
|
background: presentationData.theme.theme.chat.message.outgoing.accentControlColor.withMultipliedAlpha(0.1).argb,
|
||||||
|
foreground: presentationData.theme.theme.chat.message.outgoing.accentTextColor.argb,
|
||||||
|
stroke: presentationData.theme.theme.chat.message.outgoing.accentTextColor.argb
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
let reactionButtons = self.container.update(
|
||||||
|
context: context,
|
||||||
|
action: { [weak self] value in
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
strongSelf.reactionSelected?(value)
|
||||||
|
},
|
||||||
|
reactions: reactions.reactions.map { reaction in
|
||||||
|
var iconFile: TelegramMediaFile?
|
||||||
|
|
||||||
|
if let availableReactions = availableReactions {
|
||||||
|
for availableReaction in availableReactions.reactions {
|
||||||
|
if availableReaction.value == reaction.value {
|
||||||
|
iconFile = availableReaction.staticIcon
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ReactionButtonsLayoutContainer.Reaction(
|
||||||
|
reaction: ReactionButtonComponent.Reaction(
|
||||||
|
value: reaction.value,
|
||||||
|
iconFile: iconFile
|
||||||
|
),
|
||||||
|
count: Int(reaction.count),
|
||||||
|
isSelected: reaction.isSelected
|
||||||
|
)
|
||||||
|
},
|
||||||
|
colors: reactionColors,
|
||||||
|
constrainedWidth: constrainedWidth,
|
||||||
|
transition: .immediate
|
||||||
|
)
|
||||||
|
|
||||||
|
var reactionButtonsSize = CGSize()
|
||||||
|
var currentRowWidth: CGFloat = 0.0
|
||||||
|
for item in reactionButtons.items {
|
||||||
|
if currentRowWidth + item.size.width > constrainedWidth {
|
||||||
|
reactionButtonsSize.width = max(reactionButtonsSize.width, currentRowWidth)
|
||||||
|
if !reactionButtonsSize.height.isZero {
|
||||||
|
reactionButtonsSize.height += 6.0
|
||||||
|
}
|
||||||
|
reactionButtonsSize.height += item.size.height
|
||||||
|
currentRowWidth = 0.0
|
||||||
|
}
|
||||||
|
|
||||||
|
if !currentRowWidth.isZero {
|
||||||
|
currentRowWidth += 6.0
|
||||||
|
}
|
||||||
|
currentRowWidth += item.size.width
|
||||||
|
}
|
||||||
|
if !currentRowWidth.isZero && !reactionButtons.items.isEmpty {
|
||||||
|
reactionButtonsSize.width = max(reactionButtonsSize.width, currentRowWidth)
|
||||||
|
if !reactionButtonsSize.height.isZero {
|
||||||
|
reactionButtonsSize.height += 6.0
|
||||||
|
}
|
||||||
|
reactionButtonsSize.height += reactionButtons.items[0].size.height
|
||||||
|
}
|
||||||
|
|
||||||
|
let topInset: CGFloat = 0.0
|
||||||
|
let bottomInset: CGFloat = 2.0
|
||||||
|
|
||||||
|
return (proposedWidth: reactionButtonsSize.width, continueLayout: { [weak self] boundingWidth in
|
||||||
|
return (size: CGSize(width: boundingWidth, height: topInset + reactionButtonsSize.height + bottomInset), apply: { animation in
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var reactionButtonPosition = CGPoint(x: 0.0, y: topInset)
|
||||||
|
for item in reactionButtons.items {
|
||||||
|
if reactionButtonPosition.x + item.size.width > boundingWidth {
|
||||||
|
reactionButtonPosition.x = 0.0
|
||||||
|
reactionButtonPosition.y += item.size.height + 6.0
|
||||||
|
}
|
||||||
|
|
||||||
|
if item.view.superview == nil {
|
||||||
|
strongSelf.view.addSubview(item.view)
|
||||||
|
item.view.frame = CGRect(origin: reactionButtonPosition, size: item.size)
|
||||||
|
if animation.isAnimated {
|
||||||
|
item.view.layer.animateScale(from: 0.01, to: 1.0, duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring)
|
||||||
|
item.view.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
animation.animator.updateFrame(layer: item.view.layer, frame: CGRect(origin: reactionButtonPosition, size: item.size), completion: nil)
|
||||||
|
}
|
||||||
|
reactionButtonPosition.x += item.size.width + 6.0
|
||||||
|
}
|
||||||
|
|
||||||
|
for view in reactionButtons.removedViews {
|
||||||
|
if animation.isAnimated {
|
||||||
|
view.layer.animateScale(from: 1.0, to: 0.01, duration: 0.2, removeOnCompletion: false)
|
||||||
|
view.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak view] _ in
|
||||||
|
view?.removeFromSuperview()
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
view.removeFromSuperview()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func reactionTargetView(value: String) -> UIView? {
|
||||||
|
for (_, button) in self.container.buttons {
|
||||||
|
if let result = button.findTaggedView(tag: ReactionButtonComponent.ViewTag(value: value)) as? ReactionButtonComponent.View {
|
||||||
|
return result.iconView
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func animateOut() {
|
||||||
|
for (_, button) in self.container.buttons {
|
||||||
|
button.layer.animateScale(from: 1.0, to: 0.01, duration: 0.2, removeOnCompletion: false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final class ChatMessageReactionsFooterContentNode: ChatMessageBubbleContentNode {
|
||||||
|
private let buttonsNode: MessageReactionButtonsNode
|
||||||
|
|
||||||
|
required init() {
|
||||||
|
self.buttonsNode = MessageReactionButtonsNode()
|
||||||
|
|
||||||
|
super.init()
|
||||||
|
|
||||||
|
self.addSubnode(self.buttonsNode)
|
||||||
|
|
||||||
|
self.buttonsNode.reactionSelected = { [weak self] value in
|
||||||
|
guard let strongSelf = self, let item = strongSelf.item else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
item.controllerInteraction.updateMessageReaction(item.message, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder aDecoder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
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 buttonsNode = self.buttonsNode
|
||||||
|
|
||||||
|
return { item, layoutConstants, preparePosition, _, constrainedSize in
|
||||||
|
let contentProperties = ChatMessageBubbleContentProperties(hidesSimpleAuthorHeader: false, headerSpacing: 0.0, hidesBackground: .never, forceFullCorners: false, forceAlignment: .none)
|
||||||
|
|
||||||
|
//let displaySeparator: Bool
|
||||||
|
let topOffset: CGFloat
|
||||||
|
if case let .linear(top, _) = preparePosition, case .Neighbour(_, .media, _) = top {
|
||||||
|
//displaySeparator = false
|
||||||
|
topOffset = 2.0
|
||||||
|
} else {
|
||||||
|
//displaySeparator = true
|
||||||
|
topOffset = 0.0
|
||||||
|
}
|
||||||
|
|
||||||
|
return (contentProperties, nil, CGFloat.greatestFiniteMagnitude, { constrainedSize, position in
|
||||||
|
let reactionsAttribute = mergedMessageReactions(attributes: item.message.attributes) ?? ReactionsMessageAttribute(reactions: [], recentPeers: [])
|
||||||
|
let buttonsUpdate = buttonsNode.prepareUpdate(
|
||||||
|
context: item.context,
|
||||||
|
presentationData: item.presentationData,
|
||||||
|
availableReactions: item.associatedData.availableReactions, reactions: reactionsAttribute, constrainedWidth: constrainedSize.width, type: item.message.effectivelyIncoming(item.context.account.peerId) ? .incoming : .outgoing)
|
||||||
|
|
||||||
|
return (layoutConstants.text.bubbleInsets.left + layoutConstants.text.bubbleInsets.right + buttonsUpdate.proposedWidth, { boundingWidth in
|
||||||
|
var boundingSize = CGSize()
|
||||||
|
|
||||||
|
let buttonsSizeAndApply = buttonsUpdate.continueLayout(boundingWidth - (layoutConstants.text.bubbleInsets.left + layoutConstants.text.bubbleInsets.right))
|
||||||
|
|
||||||
|
boundingSize = buttonsSizeAndApply.size
|
||||||
|
|
||||||
|
boundingSize.width += layoutConstants.text.bubbleInsets.left + layoutConstants.text.bubbleInsets.right
|
||||||
|
boundingSize.height += topOffset + 2.0
|
||||||
|
|
||||||
|
return (boundingSize, { [weak self] animation, synchronousLoad in
|
||||||
|
if let strongSelf = self {
|
||||||
|
strongSelf.item = item
|
||||||
|
|
||||||
|
animation.animator.updateFrame(layer: strongSelf.buttonsNode.layer, frame: CGRect(origin: CGPoint(x: layoutConstants.text.bubbleInsets.left, y: topOffset - 2.0), size: buttonsSizeAndApply.size), completion: nil)
|
||||||
|
buttonsSizeAndApply.apply(animation)
|
||||||
|
|
||||||
|
let _ = synchronousLoad
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override func animateInsertion(_ currentTimestamp: Double, duration: Double) {
|
||||||
|
self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25)
|
||||||
|
}
|
||||||
|
|
||||||
|
override func animateAdded(_ currentTimestamp: Double, duration: Double) {
|
||||||
|
self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25)
|
||||||
|
}
|
||||||
|
|
||||||
|
override func animateRemoved(_ currentTimestamp: Double, duration: Double) {
|
||||||
|
self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false)
|
||||||
|
self.buttonsNode.animateOut()
|
||||||
|
}
|
||||||
|
|
||||||
|
override func animateInsertionIntoBubble(_ duration: Double) {
|
||||||
|
self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25)
|
||||||
|
self.layer.animatePosition(from: CGPoint(x: 0.0, y: -self.bounds.height / 2.0), to: CGPoint(), duration: duration, removeOnCompletion: true, additive: true)
|
||||||
|
}
|
||||||
|
|
||||||
|
override func animateRemovalFromBubble(_ duration: Double, completion: @escaping () -> Void) {
|
||||||
|
self.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: -self.bounds.height / 2.0), duration: duration, removeOnCompletion: false, additive: true)
|
||||||
|
self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { _ in
|
||||||
|
completion()
|
||||||
|
})
|
||||||
|
self.buttonsNode.animateOut()
|
||||||
|
}
|
||||||
|
|
||||||
|
override func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture, isEstimating: Bool) -> ChatMessageBubbleContentTapAction {
|
||||||
|
if self.buttonsNode.hitTest(self.view.convert(point, to: self.buttonsNode.view), with: nil) != nil {
|
||||||
|
return .ignore
|
||||||
|
}
|
||||||
|
return .none
|
||||||
|
}
|
||||||
|
|
||||||
|
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||||
|
if let result = self.buttonsNode.hitTest(self.view.convert(point, to: self.buttonsNode.view), with: event) {
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
override func reactionTargetView(value: String) -> UIView? {
|
||||||
|
return self.buttonsNode.reactionTargetView(value: value)
|
||||||
|
}
|
||||||
|
}
|
@ -106,7 +106,7 @@ class ChatMessageRestrictedBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
textFrame = textFrame.offsetBy(dx: layoutConstants.text.bubbleInsets.left, dy: layoutConstants.text.bubbleInsets.top)
|
textFrame = textFrame.offsetBy(dx: layoutConstants.text.bubbleInsets.left, dy: layoutConstants.text.bubbleInsets.top)
|
||||||
textFrameWithoutInsets = textFrameWithoutInsets.offsetBy(dx: layoutConstants.text.bubbleInsets.left, dy: layoutConstants.text.bubbleInsets.top)
|
textFrameWithoutInsets = textFrameWithoutInsets.offsetBy(dx: layoutConstants.text.bubbleInsets.left, dy: layoutConstants.text.bubbleInsets.top)
|
||||||
|
|
||||||
var statusSuggestedWidthAndContinue: (CGFloat, (CGFloat) -> (CGSize, (Bool) -> Void))?
|
var statusSuggestedWidthAndContinue: (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation) -> Void))?
|
||||||
if let statusType = statusType {
|
if let statusType = statusType {
|
||||||
var isReplyThread = false
|
var isReplyThread = false
|
||||||
if case .replyThread = item.chatLocation {
|
if case .replyThread = item.chatLocation {
|
||||||
@ -120,7 +120,7 @@ class ChatMessageRestrictedBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
impressionCount: viewCount,
|
impressionCount: viewCount,
|
||||||
dateText: dateText,
|
dateText: dateText,
|
||||||
type: statusType,
|
type: statusType,
|
||||||
layoutInput: .trailingContent(contentWidth: textLayout.trailingLineWidth, preferAdditionalInset: false),
|
layoutInput: .trailingContent(contentWidth: textLayout.trailingLineWidth, reactionSettings: ChatMessageDateAndStatusNode.ReactionSettings(preferAdditionalInset: false)),
|
||||||
constrainedSize: textConstrainedSize,
|
constrainedSize: textConstrainedSize,
|
||||||
availableReactions: item.associatedData.availableReactions,
|
availableReactions: item.associatedData.availableReactions,
|
||||||
reactions: dateReactions,
|
reactions: dateReactions,
|
||||||
@ -182,9 +182,9 @@ class ChatMessageRestrictedBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
strongSelf.statusNode.frame = CGRect(origin: CGPoint(x: textFrameWithoutInsets.minX, y: textFrameWithoutInsets.maxY), size: statusSizeAndApply.0)
|
strongSelf.statusNode.frame = CGRect(origin: CGPoint(x: textFrameWithoutInsets.minX, y: textFrameWithoutInsets.maxY), size: statusSizeAndApply.0)
|
||||||
if strongSelf.statusNode.supernode == nil {
|
if strongSelf.statusNode.supernode == nil {
|
||||||
strongSelf.addSubnode(strongSelf.statusNode)
|
strongSelf.addSubnode(strongSelf.statusNode)
|
||||||
statusSizeAndApply.1(false)
|
statusSizeAndApply.1(.None)
|
||||||
} else {
|
} else {
|
||||||
statusSizeAndApply.1(animation.isAnimated)
|
statusSizeAndApply.1(animation)
|
||||||
}
|
}
|
||||||
} else if strongSelf.statusNode.supernode != nil {
|
} else if strongSelf.statusNode.supernode != nil {
|
||||||
strongSelf.statusNode.removeFromSupernode()
|
strongSelf.statusNode.removeFromSupernode()
|
||||||
|
@ -701,7 +701,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
|
|||||||
return (ListViewItemNodeLayout(contentSize: layoutSize, insets: layoutInsets), { [weak self] animation, _, _ in
|
return (ListViewItemNodeLayout(contentSize: layoutSize, insets: layoutInsets), { [weak self] animation, _, _ in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
var transition: ContainedViewLayoutTransition = .immediate
|
var transition: ContainedViewLayoutTransition = .immediate
|
||||||
if case let .System(duration) = animation {
|
if case let .System(duration, _) = animation {
|
||||||
transition = .animated(duration: duration, curve: .spring)
|
transition = .animated(duration: duration, curve: .spring)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -740,7 +740,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
|
|||||||
strongSelf.contextSourceNode.contentRect = strongSelf.imageNode.frame
|
strongSelf.contextSourceNode.contentRect = strongSelf.imageNode.frame
|
||||||
strongSelf.containerNode.targetNodeForActivationProgressContentRect = strongSelf.contextSourceNode.contentRect
|
strongSelf.containerNode.targetNodeForActivationProgressContentRect = strongSelf.contextSourceNode.contentRect
|
||||||
|
|
||||||
dateAndStatusApply(false)
|
dateAndStatusApply(.None)
|
||||||
|
|
||||||
transition.updateFrame(node: strongSelf.dateAndStatusNode, frame: dateAndStatusFrame)
|
transition.updateFrame(node: strongSelf.dateAndStatusNode, frame: dateAndStatusFrame)
|
||||||
|
|
||||||
@ -926,7 +926,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
|
|||||||
}
|
}
|
||||||
strongSelf.addSubnode(actionButtonsNode)
|
strongSelf.addSubnode(actionButtonsNode)
|
||||||
} else {
|
} else {
|
||||||
if case let .System(duration) = animation {
|
if case let .System(duration, _) = animation {
|
||||||
actionButtonsNode.layer.animateFrame(from: previousFrame, to: actionButtonsFrame, duration: duration, timingFunction: kCAMediaTimingFunctionSpring)
|
actionButtonsNode.layer.animateFrame(from: previousFrame, to: actionButtonsFrame, duration: duration, timingFunction: kCAMediaTimingFunctionSpring)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -273,7 +273,7 @@ class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
|
|
||||||
let (textLayout, textApply) = textLayout(TextNodeLayoutArguments(attributedString: attributedText, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: textConstrainedSize, alignment: .natural, cutout: cutout, insets: textInsets, lineColor: messageTheme.accentControlColor))
|
let (textLayout, textApply) = textLayout(TextNodeLayoutArguments(attributedString: attributedText, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: textConstrainedSize, alignment: .natural, cutout: cutout, insets: textInsets, lineColor: messageTheme.accentControlColor))
|
||||||
|
|
||||||
var statusSuggestedWidthAndContinue: (CGFloat, (CGFloat) -> (CGSize, (Bool) -> Void))?
|
var statusSuggestedWidthAndContinue: (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation) -> Void))?
|
||||||
if let statusType = statusType {
|
if let statusType = statusType {
|
||||||
var isReplyThread = false
|
var isReplyThread = false
|
||||||
if case .replyThread = item.chatLocation {
|
if case .replyThread = item.chatLocation {
|
||||||
@ -287,7 +287,7 @@ class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
impressionCount: viewCount,
|
impressionCount: viewCount,
|
||||||
dateText: dateText,
|
dateText: dateText,
|
||||||
type: statusType,
|
type: statusType,
|
||||||
layoutInput: .trailingContent(contentWidth: textLayout.trailingLineWidth, preferAdditionalInset: false),
|
layoutInput: .trailingContent(contentWidth: textLayout.trailingLineWidth, reactionSettings: ChatMessageDateAndStatusNode.ReactionSettings(preferAdditionalInset: false)),
|
||||||
constrainedSize: textConstrainedSize,
|
constrainedSize: textConstrainedSize,
|
||||||
availableReactions: item.associatedData.availableReactions,
|
availableReactions: item.associatedData.availableReactions,
|
||||||
reactions: dateReactions,
|
reactions: dateReactions,
|
||||||
@ -354,7 +354,7 @@ class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
strongSelf.textNode.displaysAsynchronously = !item.presentationData.isPreview && !item.presentationData.theme.theme.forceSync
|
strongSelf.textNode.displaysAsynchronously = !item.presentationData.isPreview && !item.presentationData.theme.theme.forceSync
|
||||||
let _ = textApply()
|
let _ = textApply()
|
||||||
|
|
||||||
strongSelf.textNode.frame = textFrame
|
animation.animator.updateFrame(layer: strongSelf.textNode.layer, frame: textFrame, completion: nil)
|
||||||
if let textSelectionNode = strongSelf.textSelectionNode {
|
if let textSelectionNode = strongSelf.textSelectionNode {
|
||||||
let shouldUpdateLayout = textSelectionNode.frame.size != textFrame.size
|
let shouldUpdateLayout = textSelectionNode.frame.size != textFrame.size
|
||||||
textSelectionNode.frame = textFrame
|
textSelectionNode.frame = textFrame
|
||||||
@ -367,12 +367,12 @@ class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
strongSelf.textAccessibilityOverlayNode.cachedLayout = textLayout
|
strongSelf.textAccessibilityOverlayNode.cachedLayout = textLayout
|
||||||
|
|
||||||
if let statusSizeAndApply = statusSizeAndApply {
|
if let statusSizeAndApply = statusSizeAndApply {
|
||||||
strongSelf.statusNode.frame = CGRect(origin: CGPoint(x: textFrameWithoutInsets.minX, y: textFrameWithoutInsets.maxY), size: statusSizeAndApply.0)
|
animation.animator.updateFrame(layer: strongSelf.statusNode.layer, frame: CGRect(origin: CGPoint(x: textFrameWithoutInsets.minX, y: textFrameWithoutInsets.maxY), size: statusSizeAndApply.0), completion: nil)
|
||||||
if strongSelf.statusNode.supernode == nil {
|
if strongSelf.statusNode.supernode == nil {
|
||||||
strongSelf.addSubnode(strongSelf.statusNode)
|
strongSelf.addSubnode(strongSelf.statusNode)
|
||||||
statusSizeAndApply.1(false)
|
statusSizeAndApply.1(.None)
|
||||||
} else {
|
} else {
|
||||||
statusSizeAndApply.1(animation.isAnimated)
|
statusSizeAndApply.1(animation)
|
||||||
}
|
}
|
||||||
} else if strongSelf.statusNode.supernode != nil {
|
} else if strongSelf.statusNode.supernode != nil {
|
||||||
strongSelf.statusNode.removeFromSupernode()
|
strongSelf.statusNode.removeFromSupernode()
|
||||||
|
@ -941,7 +941,7 @@ private final class ItemView: UIView, SparseItemGridView {
|
|||||||
let messageItemNode: ListViewItemNode
|
let messageItemNode: ListViewItemNode
|
||||||
if let current = self.messageItemNode {
|
if let current = self.messageItemNode {
|
||||||
messageItemNode = current
|
messageItemNode = current
|
||||||
messageItem.updateNode(async: { f in f() }, node: { return current }, params: ListViewItemLayoutParams(width: size.width, leftInset: insets.left, rightInset: insets.right, availableHeight: 0.0), previousItem: nil, nextItem: nil, animation: .System(duration: 0.2), completion: { layout, apply in
|
messageItem.updateNode(async: { f in f() }, node: { return current }, params: ListViewItemLayoutParams(width: size.width, leftInset: insets.left, rightInset: insets.right, availableHeight: 0.0), previousItem: nil, nextItem: nil, animation: .System(duration: 0.2, transition: ControlledTransition(duration: 0.2, curve: .spring)), completion: { layout, apply in
|
||||||
current.contentSize = layout.contentSize
|
current.contentSize = layout.contentSize
|
||||||
current.insets = layout.insets
|
current.insets = layout.insets
|
||||||
|
|
||||||
@ -972,7 +972,7 @@ private final class ItemView: UIView, SparseItemGridView {
|
|||||||
|
|
||||||
func update(size: CGSize, insets: UIEdgeInsets) {
|
func update(size: CGSize, insets: UIEdgeInsets) {
|
||||||
if let messageItem = self.messageItem, let messageItemNode = self.messageItemNode {
|
if let messageItem = self.messageItem, let messageItemNode = self.messageItemNode {
|
||||||
messageItem.updateNode(async: { f in f() }, node: { return messageItemNode }, params: ListViewItemLayoutParams(width: size.width, leftInset: insets.left, rightInset: insets.right, availableHeight: 0.0), previousItem: nil, nextItem: nil, animation: .System(duration: 0.2), completion: { layout, apply in
|
messageItem.updateNode(async: { f in f() }, node: { return messageItemNode }, params: ListViewItemLayoutParams(width: size.width, leftInset: insets.left, rightInset: insets.right, availableHeight: 0.0), previousItem: nil, nextItem: nil, animation: .System(duration: 0.2, transition: ControlledTransition(duration: 0.2, curve: .spring)), completion: { layout, apply in
|
||||||
messageItemNode.contentSize = layout.contentSize
|
messageItemNode.contentSize = layout.contentSize
|
||||||
messageItemNode.insets = layout.insets
|
messageItemNode.insets = layout.insets
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user