Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios

This commit is contained in:
Ilya Laktyushin 2022-01-12 14:10:29 +03:00
commit 46dccd85f6
22 changed files with 609 additions and 363 deletions

View File

@ -25,7 +25,7 @@ public final class CodeInputView: ASDisplayNode, UITextFieldDelegate {
} }
private final class ItemView: ASDisplayNode { private final class ItemView: ASDisplayNode {
private let backgroundView: UIImageView private let backgroundView: UIView
private let textNode: ImmediateTextNode private let textNode: ImmediateTextNode
private var borderColorValue: UInt32? private var borderColorValue: UInt32?
@ -33,7 +33,7 @@ public final class CodeInputView: ASDisplayNode, UITextFieldDelegate {
private var text: String = "" private var text: String = ""
override init() { override init() {
self.backgroundView = UIImageView() self.backgroundView = UIView()
self.textNode = ImmediateTextNode() self.textNode = ImmediateTextNode()
super.init() super.init()
@ -48,11 +48,17 @@ public final class CodeInputView: ASDisplayNode, UITextFieldDelegate {
fatalError("init(coder:) has not been implemented") fatalError("init(coder:) has not been implemented")
} }
func update(borderColor: UInt32) { func update(borderColor: UInt32, isHighlighted: Bool) {
if self.borderColorValue != borderColor { if self.borderColorValue != borderColor {
self.borderColorValue = borderColor self.borderColorValue = borderColor
self.backgroundView.image = generateStretchableFilledCircleImage(diameter: 10.0, color: nil, strokeColor: UIColor(argb: borderColor), strokeWidth: 1.0, backgroundColor: nil) let previousColor = self.backgroundView.layer.borderColor
self.backgroundView.layer.cornerRadius = 5.0
self.backgroundView.layer.borderColor = UIColor(argb: borderColor).cgColor
self.backgroundView.layer.borderWidth = 1.0
if let previousColor = previousColor {
self.backgroundView.layer.animate(from: previousColor, to: UIColor(argb: borderColor).cgColor, keyPath: "borderColor", timingFunction: CAMediaTimingFunctionName.linear.rawValue, duration: 0.15)
}
} }
} }
@ -63,7 +69,7 @@ public final class CodeInputView: ASDisplayNode, UITextFieldDelegate {
if animated && previousText.isEmpty != text.isEmpty { if animated && previousText.isEmpty != text.isEmpty {
if !text.isEmpty { if !text.isEmpty {
self.textNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1) self.textNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1)
self.textNode.layer.animatePosition(from: CGPoint(x: 0.0, y: size.height / 2.0), to: CGPoint(), duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring, additive: true) self.textNode.layer.animateSpring(from: NSValue(cgPoint: CGPoint(x: 0.0, y: size.height / 2.0)), to: NSValue(cgPoint: CGPoint()), keyPath: "position", duration: 0.5, additive: true)
} else { } else {
if let copyView = self.textNode.view.snapshotContentTree() { if let copyView = self.textNode.view.snapshotContentTree() {
self.view.insertSubview(copyView, at: 0) self.view.insertSubview(copyView, at: 0)
@ -77,7 +83,11 @@ public final class CodeInputView: ASDisplayNode, UITextFieldDelegate {
let fontSize: CGFloat = floor(21.0 * size.height / 28.0) let fontSize: CGFloat = floor(21.0 * size.height / 28.0)
if #available(iOS 13.0, *) {
self.textNode.attributedText = NSAttributedString(string: text, font: UIFont.monospacedSystemFont(ofSize: fontSize, weight: .regular), textColor: UIColor(argb: textColor))
} else {
self.textNode.attributedText = NSAttributedString(string: text, font: Font.monospace(fontSize), textColor: UIColor(argb: textColor)) self.textNode.attributedText = NSAttributedString(string: text, font: Font.monospace(fontSize), textColor: UIColor(argb: textColor))
}
let textSize = self.textNode.updateLayout(size) let textSize = self.textNode.updateLayout(size)
self.textNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - textSize.width) / 2.0), y: floorToScreenPixels((size.height - textSize.height) / 2.0)), size: textSize) self.textNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - textSize.width) / 2.0), y: floorToScreenPixels((size.height - textSize.height) / 2.0)), size: textSize)
@ -86,9 +96,9 @@ public final class CodeInputView: ASDisplayNode, UITextFieldDelegate {
} }
private let prefixLabel: ImmediateTextNode private let prefixLabel: ImmediateTextNode
private let textField: UITextField public let textField: UITextField
private var focusIndex: Int? private var focusIndex: Int? = 0
private var itemViews: [ItemView] = [] private var itemViews: [ItemView] = []
public var updated: (() -> Void)? public var updated: (() -> Void)?
@ -183,7 +193,7 @@ public final class CodeInputView: ASDisplayNode, UITextFieldDelegate {
} }
public func textFieldDidEndEditing(_ textField: UITextField) { public func textFieldDidEndEditing(_ textField: UITextField) {
self.focusIndex = nil self.focusIndex = textField.text?.count ?? 0
self.updateItemViews(animated: true) self.updateItemViews(animated: true)
} }
@ -205,7 +215,7 @@ public final class CodeInputView: ASDisplayNode, UITextFieldDelegate {
let itemView = self.itemViews[i] let itemView = self.itemViews[i]
let itemSize = itemView.bounds.size let itemSize = itemView.bounds.size
itemView.update(borderColor: self.focusIndex == i ? theme.activeBorder : theme.inactiveBorder) itemView.update(borderColor: self.focusIndex == i ? theme.activeBorder : theme.inactiveBorder, isHighlighted: self.focusIndex == i)
let itemText: String let itemText: String
if i < self.textValue.count { if i < self.textValue.count {
itemText = String(self.textValue[self.textValue.index(self.textValue.startIndex, offsetBy: i)]) itemText = String(self.textValue[self.textValue.index(self.textValue.startIndex, offsetBy: i)])
@ -233,7 +243,11 @@ public final class CodeInputView: ASDisplayNode, UITextFieldDelegate {
height = 28.0 height = 28.0
} }
if #available(iOS 13.0, *) {
self.prefixLabel.attributedText = NSAttributedString(string: prefix, font: UIFont.monospacedSystemFont(ofSize: 21.0, weight: .regular), textColor: UIColor(argb: theme.foreground))
} else {
self.prefixLabel.attributedText = NSAttributedString(string: prefix, font: Font.monospace(21.0), textColor: UIColor(argb: theme.foreground)) self.prefixLabel.attributedText = NSAttributedString(string: prefix, font: Font.monospace(21.0), textColor: UIColor(argb: theme.foreground))
}
let prefixSize = self.prefixLabel.updateLayout(CGSize(width: width, height: 100.0)) let prefixSize = self.prefixLabel.updateLayout(CGSize(width: width, height: 100.0))
let prefixSpacing: CGFloat = prefix.isEmpty ? 0.0 : 8.0 let prefixSpacing: CGFloat = prefix.isEmpty ? 0.0 : 8.0
@ -255,7 +269,7 @@ public final class CodeInputView: ASDisplayNode, UITextFieldDelegate {
self.itemViews.append(itemView) self.itemViews.append(itemView)
self.addSubnode(itemView) self.addSubnode(itemView)
} }
itemView.update(borderColor: self.focusIndex == i ? theme.activeBorder : theme.inactiveBorder) itemView.update(borderColor: self.focusIndex == i ? theme.activeBorder : theme.inactiveBorder, isHighlighted: self.focusIndex == i)
let itemText: String let itemText: String
if i < self.textValue.count { if i < self.textValue.count {
itemText = String(self.textValue[self.textValue.index(self.textValue.startIndex, offsetBy: i)]) itemText = String(self.textValue[self.textValue.index(self.textValue.startIndex, offsetBy: i)])

View File

@ -8,9 +8,9 @@ import TelegramCore
import AccountContext import AccountContext
import TelegramPresentationData import TelegramPresentationData
import UIKit import UIKit
import WebPBinding
import AnimatedAvatarSetNode import AnimatedAvatarSetNode
import ReactionImageComponent import ReactionImageComponent
import WebPBinding
public final class ReactionIconView: PortalSourceView { public final class ReactionIconView: PortalSourceView {
public let imageView: UIImageView public let imageView: UIImageView
@ -39,6 +39,7 @@ public final class ReactionButtonAsyncNode: ContextControllerSourceNode {
var foreground: UInt32 var foreground: UInt32
var extractedBackground: UInt32 var extractedBackground: UInt32
var extractedForeground: UInt32 var extractedForeground: UInt32
var isSelected: Bool
} }
struct Counter: Equatable { struct Counter: Equatable {
@ -47,12 +48,13 @@ public final class ReactionButtonAsyncNode: ContextControllerSourceNode {
struct Layout: Equatable { struct Layout: Equatable {
var colors: Colors var colors: Colors
var baseSize: CGSize var size: CGSize
var counter: Counter? var counter: Counter?
} }
private struct AnimationState { private struct AnimationState {
var fromCounter: Counter var fromCounter: Counter?
var fromColors: Colors
var startTime: Double var startTime: Double
var duration: Double var duration: Double
} }
@ -69,8 +71,8 @@ public final class ReactionButtonAsyncNode: ContextControllerSourceNode {
func update(layout: Layout) { func update(layout: Layout) {
if self.currentLayout != layout { if self.currentLayout != layout {
if let currentLayout = self.currentLayout, let counter = currentLayout.counter { if let currentLayout = self.currentLayout, (currentLayout.counter != layout.counter || currentLayout.colors.isSelected != layout.colors.isSelected) {
self.animationState = AnimationState(fromCounter: counter, startTime: CACurrentMediaTime(), duration: 0.15 * UIView.animationDurationFactor()) self.animationState = AnimationState(fromCounter: currentLayout.counter, fromColors: currentLayout.colors, startTime: CACurrentMediaTime(), duration: 0.15 * UIView.animationDurationFactor())
} }
self.currentLayout = layout self.currentLayout = layout
@ -128,23 +130,19 @@ public final class ReactionButtonAsyncNode: ContextControllerSourceNode {
} }
} }
var imageWidth = layout.baseSize.width let image = generateImage(layout.size, rotatedContext: { size, context in
while imageWidth < layout.baseSize.height / 2.0 + 1.0 + totalComponentWidth + 8.0 {
imageWidth += 2.0
}
let image = generateImage(CGSize(width: imageWidth, height: layout.baseSize.height), rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size)) context.clear(CGRect(origin: CGPoint(), size: size))
UIGraphicsPushContext(context) UIGraphicsPushContext(context)
func drawContents(colors: Colors) {
let backgroundColor: UIColor let backgroundColor: UIColor
let foregroundColor: UIColor let foregroundColor: UIColor
if self.isExtracted { if self.isExtracted {
backgroundColor = UIColor(argb: layout.colors.extractedBackground) backgroundColor = UIColor(argb: colors.extractedBackground)
foregroundColor = UIColor(argb: layout.colors.extractedForeground) foregroundColor = UIColor(argb: colors.extractedForeground)
} else { } else {
backgroundColor = UIColor(argb: layout.colors.background) backgroundColor = UIColor(argb: colors.background)
foregroundColor = UIColor(argb: layout.colors.foreground) foregroundColor = UIColor(argb: colors.foreground)
} }
context.setBlendMode(.copy) context.setBlendMode(.copy)
@ -154,24 +152,16 @@ public final class ReactionButtonAsyncNode: ContextControllerSourceNode {
context.fillEllipse(in: CGRect(origin: CGPoint(x: size.width - size.height, y: 0.0), size: CGSize(width: size.height, height: size.height))) context.fillEllipse(in: CGRect(origin: CGPoint(x: size.width - size.height, y: 0.0), size: CGSize(width: size.height, height: size.height)))
context.fill(CGRect(origin: CGPoint(x: size.height / 2.0, y: 0.0), size: CGSize(width: size.width - size.height, height: size.height))) context.fill(CGRect(origin: CGPoint(x: size.height / 2.0, y: 0.0), size: CGSize(width: size.width - size.height, height: size.height)))
#if DEBUG && false
context.setFillColor(UIColor.blue.withAlphaComponent(0.5).cgColor)
context.fill(CGRect(origin: CGPoint(x: layout.baseSize.height / 2.0 + 1.0, y: 0.0), size: CGSize(width: size.width - (layout.baseSize.height / 2.0 + 1.0 + 8.0), height: size.height)))
#endif
if let counter = layout.counter { if let counter = layout.counter {
let isForegroundTransparent = foregroundColor.alpha < 1.0 let isForegroundTransparent = foregroundColor.alpha < 1.0
context.setBlendMode(isForegroundTransparent ? .copy : .normal) context.setBlendMode(isForegroundTransparent ? .copy : .normal)
//let textAreaWidth = size.width - (layout.baseSize.height / 2.0 + 1.0 + 8.0) let textOrigin: CGFloat = 36.0
var textOrigin: CGFloat = layout.baseSize.height / 2.0 + 1.0
textOrigin = max(textOrigin, layout.baseSize.height / 2.0 + UIScreenPixel)
var rightTextOrigin = textOrigin + totalComponentWidth var rightTextOrigin = textOrigin + totalComponentWidth
let animationFraction: CGFloat let animationFraction: CGFloat
if let animationState = self.animationState { if let animationState = self.animationState, animationState.fromCounter != nil {
animationFraction = max(0.0, min(1.0, (CACurrentMediaTime() - animationState.startTime) / animationState.duration)) animationFraction = max(0.0, min(1.0, (CACurrentMediaTime() - animationState.startTime) / animationState.duration))
} else { } else {
animationFraction = 1.0 animationFraction = 1.0
@ -182,10 +172,10 @@ public final class ReactionButtonAsyncNode: ContextControllerSourceNode {
var componentAlpha: CGFloat = 1.0 var componentAlpha: CGFloat = 1.0
var componentVerticalOffset: CGFloat = 0.0 var componentVerticalOffset: CGFloat = 0.0
if let animationState = self.animationState { if let animationState = self.animationState, let fromCounter = animationState.fromCounter {
let reverseIndex = counter.components.count - 1 - i let reverseIndex = counter.components.count - 1 - i
if reverseIndex < animationState.fromCounter.components.count { if reverseIndex < fromCounter.components.count {
let previousComponent = animationState.fromCounter.components[animationState.fromCounter.components.count - 1 - reverseIndex] let previousComponent = fromCounter.components[fromCounter.components.count - 1 - reverseIndex]
if previousComponent != component { if previousComponent != component {
componentAlpha = animationFraction componentAlpha = animationFraction
@ -201,7 +191,7 @@ public final class ReactionButtonAsyncNode: ContextControllerSourceNode {
} }
var componentOrigin = rightTextOrigin - previousComponent.bounds.width var componentOrigin = rightTextOrigin - previousComponent.bounds.width
componentOrigin = max(componentOrigin, layout.baseSize.height / 2.0 + UIScreenPixel) componentOrigin = max(componentOrigin, layout.size.height / 2.0 + UIScreenPixel)
let previousColor: UIColor let previousColor: UIColor
if isForegroundTransparent { if isForegroundTransparent {
previousColor = foregroundColor.mixedWith(backgroundColor, alpha: 1.0 - previousComponentAlpha) previousColor = foregroundColor.mixedWith(backgroundColor, alpha: 1.0 - previousComponentAlpha)
@ -227,9 +217,35 @@ public final class ReactionButtonAsyncNode: ContextControllerSourceNode {
rightTextOrigin -= component.bounds.width rightTextOrigin -= component.bounds.width
} }
} }
}
if let animationState = self.animationState, animationState.fromColors.isSelected != layout.colors.isSelected {
var animationFraction: CGFloat = max(0.0, min(1.0, (CACurrentMediaTime() - animationState.startTime) / animationState.duration))
if !layout.colors.isSelected {
animationFraction = 1.0 - animationFraction
}
let center = CGPoint(x: 21.0, y: size.height / 2.0)
let diameter = 0.0 * (1.0 - animationFraction) + (size.width - center.x) * 2.0 * animationFraction
context.beginPath()
context.addEllipse(in: CGRect(origin: CGPoint(x: center.x - diameter / 2.0, y: center.y - diameter / 2.0), size: CGSize(width: diameter, height: diameter)))
context.clip(using: .evenOdd)
drawContents(colors: layout.colors.isSelected ? layout.colors : animationState.fromColors)
context.resetClip()
context.beginPath()
context.addRect(CGRect(origin: CGPoint(), size: size))
context.addEllipse(in: CGRect(origin: CGPoint(x: center.x - diameter / 2.0, y: center.y - diameter / 2.0), size: CGSize(width: diameter, height: diameter)))
context.clip(using: .evenOdd)
drawContents(colors: layout.colors.isSelected ? animationState.fromColors : layout.colors)
} else {
drawContents(colors: layout.colors)
}
UIGraphicsPopContext() UIGraphicsPopContext()
})?.stretchableImage(withLeftCapWidth: Int(layout.baseSize.height / 2.0), topCapHeight: Int(layout.baseSize.height / 2.0)) })//?.stretchableImage(withLeftCapWidth: Int(layout.baseSize.height / 2.0), topCapHeight: Int(layout.baseSize.height / 2.0))
if let image = image { if let image = image {
let previousContents = self.layer.contents let previousContents = self.layer.contents
@ -293,7 +309,7 @@ public final class ReactionButtonAsyncNode: ContextControllerSourceNode {
resultComponents.append(Component(string: component, bounds: boundingRect)) resultComponents.append(Component(string: component, bounds: boundingRect))
if i == spec.stringComponents.count - 1 && component.count == 1 && component[component.startIndex].isNumber { if i == spec.stringComponents.count - 1 && component[component.startIndex].isNumber {
resultSize.width += CounterLayout.maxDigitWidth resultSize.width += CounterLayout.maxDigitWidth
} else { } else {
resultSize.width += boundingRect.width resultSize.width += boundingRect.width
@ -357,24 +373,29 @@ public final class ReactionButtonAsyncNode: ContextControllerSourceNode {
let spacing: CGFloat = 2.0 let spacing: CGFloat = 2.0
let boundingImageSize = CGSize(width: 20.0, height: 20.0) let boundingImageSize = CGSize(width: 20.0, height: 20.0)
let defaultImageSize = CGSize(width: boundingImageSize.width + floor(boundingImageSize.width * 0.5 * 2.0), height: boundingImageSize.height + floor(boundingImageSize.height * 0.5 * 2.0))
let imageSize: CGSize let imageSize: CGSize
if let file = spec.component.reaction.centerAnimation { if let file = spec.component.reaction.centerAnimation {
let defaultImageSize = CGSize(width: boundingImageSize.width + floor(boundingImageSize.width * 0.5 * 2.0), height: boundingImageSize.height + floor(boundingImageSize.height * 0.5 * 2.0))
imageSize = file.dimensions?.cgSize.aspectFitted(defaultImageSize) ?? defaultImageSize imageSize = file.dimensions?.cgSize.aspectFitted(defaultImageSize) ?? defaultImageSize
} else if let file = spec.component.reaction.legacyIcon {
imageSize = file.dimensions?.cgSize.aspectFitted(boundingImageSize) ?? boundingImageSize
} else { } else {
imageSize = defaultImageSize imageSize = boundingImageSize
} }
var counterComponents: [String] = [] var counterComponents: [String] = []
for character in countString(Int64(spec.component.count)) { for character in countString(Int64(spec.component.count)) {
counterComponents.append(String(character)) counterComponents.append(String(character))
} }
#if DEBUG && false
/*#if DEBUG
if spec.component.count % 2 == 0 {
counterComponents.removeAll() counterComponents.removeAll()
for character in "42" { for character in "123.5K" {
counterComponents.append(String(character)) counterComponents.append(String(character))
} }
#endif }
#endif*/
let backgroundColor = spec.component.isSelected ? spec.component.colors.selectedBackground : spec.component.colors.deselectedBackground let backgroundColor = spec.component.isSelected ? spec.component.colors.selectedBackground : spec.component.colors.deselectedBackground
@ -411,7 +432,8 @@ public final class ReactionButtonAsyncNode: ContextControllerSourceNode {
background: spec.component.isSelected ? spec.component.colors.selectedBackground : spec.component.colors.deselectedBackground, background: spec.component.isSelected ? spec.component.colors.selectedBackground : spec.component.colors.deselectedBackground,
foreground: spec.component.isSelected ? spec.component.colors.selectedForeground : spec.component.colors.deselectedForeground, foreground: spec.component.isSelected ? spec.component.colors.selectedForeground : spec.component.colors.deselectedForeground,
extractedBackground: spec.component.colors.extractedBackground, extractedBackground: spec.component.colors.extractedBackground,
extractedForeground: spec.component.colors.extractedForeground extractedForeground: spec.component.colors.extractedForeground,
isSelected: spec.component.isSelected
) )
var backgroundCounter: ReactionButtonAsyncNode.ContainerButtonNode.Counter? var backgroundCounter: ReactionButtonAsyncNode.ContainerButtonNode.Counter?
if let counterLayout = counterLayout { if let counterLayout = counterLayout {
@ -421,7 +443,7 @@ public final class ReactionButtonAsyncNode: ContextControllerSourceNode {
} }
let backgroundLayout = ContainerButtonNode.Layout( let backgroundLayout = ContainerButtonNode.Layout(
colors: backgroundColors, colors: backgroundColors,
baseSize: CGSize(width: height + 2.0, height: height), size: size,
counter: backgroundCounter counter: backgroundCounter
) )
@ -447,6 +469,18 @@ public final class ReactionButtonAsyncNode: ContextControllerSourceNode {
private let iconImageDisposable = MetaDisposable() private let iconImageDisposable = MetaDisposable()
public var activateAfterCompletion: Bool = false {
didSet {
if self.activateAfterCompletion {
self.contextGesture?.activatedAfterCompletion = { [weak self] in
self?.pressed()
}
} else {
self.contextGesture?.activatedAfterCompletion = nil
}
}
}
override init() { override init() {
self.containerNode = ContextExtractedContentContainingNode() self.containerNode = ContextExtractedContentContainingNode()
self.buttonNode = ContainerButtonNode() self.buttonNode = ContainerButtonNode()
@ -475,22 +509,13 @@ public final class ReactionButtonAsyncNode: ContextControllerSourceNode {
} }
self.isGestureEnabled = true self.isGestureEnabled = true
self.beginDelay = 0.0
self.containerNode.willUpdateIsExtractedToContextPreview = { [weak self] isExtracted, _ in self.containerNode.willUpdateIsExtractedToContextPreview = { [weak self] isExtracted, _ in
guard let strongSelf = self else { guard let strongSelf = self else {
return return
} }
strongSelf.buttonNode.updateIsExtracted(isExtracted: isExtracted, animated: true) strongSelf.buttonNode.updateIsExtracted(isExtracted: isExtracted, animated: true)
/*let backgroundImage = isExtracted ? layout.extractedBackgroundImage : layout.backgroundImage
let previousContents = strongSelf.buttonNode.layer.contents
ASDisplayNodeSetResizableContents(strongSelf.buttonNode.layer, backgroundImage)
if let previousContents = previousContents {
strongSelf.buttonNode.layer.animate(from: previousContents as! CGImage, to: backgroundImage.cgImage!, keyPath: "contents", timingFunction: CAMediaTimingFunctionName.linear.rawValue, duration: 0.2)
}*/
} }
} }
@ -502,6 +527,16 @@ public final class ReactionButtonAsyncNode: ContextControllerSourceNode {
self.iconImageDisposable.dispose() self.iconImageDisposable.dispose()
} }
override public func didLoad() {
super.didLoad()
if self.activateAfterCompletion {
self.contextGesture?.activatedAfterCompletion = { [weak self] in
self?.pressed()
}
}
}
@objc private func pressed() { @objc private func pressed() {
guard let layout = self.layout else { guard let layout = self.layout else {
return return
@ -515,7 +550,6 @@ public final class ReactionButtonAsyncNode: ContextControllerSourceNode {
self.containerNode.contentRect = CGRect(origin: CGPoint(), size: layout.size) self.containerNode.contentRect = CGRect(origin: CGPoint(), size: layout.size)
animation.animator.updateFrame(layer: self.buttonNode.layer, frame: CGRect(origin: CGPoint(), size: layout.size), completion: nil) animation.animator.updateFrame(layer: self.buttonNode.layer, frame: CGRect(origin: CGPoint(), size: layout.size), completion: nil)
//ASDisplayNodeSetResizableContents(self.buttonNode.layer, layout.backgroundImage)
self.buttonNode.update(layout: layout.backgroundLayout) self.buttonNode.update(layout: layout.backgroundLayout)
animation.animator.updateFrame(layer: self.iconView.layer, frame: layout.imageFrame, completion: nil) animation.animator.updateFrame(layer: self.iconView.layer, frame: layout.imageFrame, completion: nil)
@ -535,6 +569,19 @@ public final class ReactionButtonAsyncNode: ContextControllerSourceNode {
} }
} }
})) }))
} else if let legacyIcon = layout.spec.component.reaction.legacyIcon {
self.iconImageDisposable.set((layout.spec.component.context.account.postbox.mediaBox.resourceData(legacyIcon.resource)
|> deliverOnMainQueue).start(next: { [weak self] data in
guard let strongSelf = self else {
return
}
if data.complete, let dataValue = try? Data(contentsOf: URL(fileURLWithPath: data.path)) {
if let image = WebP.convert(fromWebP: dataValue) {
strongSelf.iconView.imageView.image = image
}
}
}))
} }
} }
@ -608,10 +655,12 @@ public final class ReactionButtonComponent: Equatable {
public struct Reaction: Equatable { public struct Reaction: Equatable {
public var value: String public var value: String
public var centerAnimation: TelegramMediaFile? public var centerAnimation: TelegramMediaFile?
public var legacyIcon: TelegramMediaFile?
public init(value: String, centerAnimation: TelegramMediaFile?) { public init(value: String, centerAnimation: TelegramMediaFile?, legacyIcon: TelegramMediaFile?) {
self.value = value self.value = value
self.centerAnimation = centerAnimation self.centerAnimation = centerAnimation
self.legacyIcon = legacyIcon
} }
public static func ==(lhs: Reaction, rhs: Reaction) -> Bool { public static func ==(lhs: Reaction, rhs: Reaction) -> Bool {
@ -621,6 +670,9 @@ public final class ReactionButtonComponent: Equatable {
if lhs.centerAnimation?.fileId != rhs.centerAnimation?.fileId { if lhs.centerAnimation?.fileId != rhs.centerAnimation?.fileId {
return false return false
} }
if lhs.legacyIcon?.fileId != rhs.legacyIcon?.fileId {
return false
}
return true return true
} }
} }

View File

@ -58,22 +58,51 @@ public func reactionStaticImage(context: AccountContext, animation: TelegramMedi
}) })
} }
public final class ReactionImageNode: ASImageNode { public final class ReactionImageNode: ASDisplayNode {
private var disposable: Disposable? private var disposable: Disposable?
public let size: CGSize private let size: CGSize
private let isAnimation: Bool
private let iconNode: ASImageNode
public init(context: AccountContext, availableReactions: AvailableReactions?, reaction: String) { public init(context: AccountContext, availableReactions: AvailableReactions?, reaction: String) {
self.iconNode = ASImageNode()
var file: TelegramMediaFile? var file: TelegramMediaFile?
var animationFile: TelegramMediaFile?
if let availableReactions = availableReactions { if let availableReactions = availableReactions {
for availableReaction in availableReactions.reactions { for availableReaction in availableReactions.reactions {
if availableReaction.value == reaction { if availableReaction.value == reaction {
file = availableReaction.staticIcon file = availableReaction.staticIcon
animationFile = availableReaction.centerAnimation
break break
} }
} }
} }
if let file = file { if let animationFile = animationFile {
self.size = file.dimensions?.cgSize ?? CGSize(width: 18.0, height: 18.0) self.size = animationFile.dimensions?.cgSize ?? CGSize(width: 32.0, height: 32.0)
var displaySize = self.size.aspectFitted(CGSize(width: 20.0, height: 20.0))
displaySize.width = floor(displaySize.width * 2.0)
displaySize.height = floor(displaySize.height * 2.0)
self.isAnimation = true
super.init()
self.disposable = (reactionStaticImage(context: context, animation: animationFile, pixelSize: CGSize(width: displaySize.width * UIScreenScale, height: displaySize.height * UIScreenScale))
|> deliverOnMainQueue).start(next: { [weak self] data in
guard let strongSelf = self else {
return
}
if data.isComplete, let dataValue = try? Data(contentsOf: URL(fileURLWithPath: data.path)) {
if let image = UIImage(data: dataValue) {
strongSelf.iconNode.image = image
}
}
})
} else if let file = file {
self.size = file.dimensions?.cgSize ?? CGSize(width: 32.0, height: 32.0)
self.isAnimation = false
super.init() super.init()
@ -85,60 +114,29 @@ public final class ReactionImageNode: ASImageNode {
if data.complete, let dataValue = try? Data(contentsOf: URL(fileURLWithPath: data.path)) { if data.complete, let dataValue = try? Data(contentsOf: URL(fileURLWithPath: data.path)) {
if let image = WebP.convert(fromWebP: dataValue) { if let image = WebP.convert(fromWebP: dataValue) {
strongSelf.image = image strongSelf.iconNode.image = image
} }
} }
}) })
} else { } else {
self.size = CGSize(width: 18.0, height: 18.0) self.size = CGSize(width: 32.0, height: 32.0)
self.isAnimation = false
super.init() super.init()
} }
self.addSubnode(self.iconNode)
} }
deinit { deinit {
self.disposable?.dispose() self.disposable?.dispose()
} }
}
public final class ReactionFileImageNode: ASImageNode { public func update(size: CGSize) {
private let disposable = MetaDisposable() var imageSize = self.size.aspectFitted(size)
if self.isAnimation {
private var currentFile: TelegramMediaFile? imageSize.width *= 2.0
imageSize.height *= 2.0
override public init() {
}
deinit {
self.disposable.dispose()
}
public func asyncLayout() -> (_ context: AccountContext, _ file: TelegramMediaFile?) -> (size: CGSize, apply: () -> Void) {
return { [weak self] context, file in
let size = file?.dimensions?.cgSize ?? CGSize(width: 18.0, height: 18.0)
return (size, {
guard let strongSelf = self else {
return
}
if strongSelf.currentFile != file {
strongSelf.currentFile = file
if let file = file {
strongSelf.disposable.set((context.account.postbox.mediaBox.resourceData(file.resource)
|> deliverOnMainQueue).start(next: { data in
guard let strongSelf = self else {
return
}
if data.complete, let dataValue = try? Data(contentsOf: URL(fileURLWithPath: data.path)) {
if let image = WebP.convert(fromWebP: dataValue) {
strongSelf.image = image
}
}
}))
}
}
})
} }
self.iconNode.frame = CGRect(origin: CGPoint(x: floor((size.width - imageSize.width) / 2.0), y: floor((size.height - imageSize.height) / 2.0)), size: imageSize)
} }
} }

View File

@ -8,7 +8,6 @@ import TelegramCore
import AccountContext import AccountContext
import TelegramPresentationData import TelegramPresentationData
import UIKit import UIKit
import WebPBinding
import AnimatedAvatarSetNode import AnimatedAvatarSetNode
import ContextUI import ContextUI
import AvatarNode import AvatarNode
@ -168,8 +167,7 @@ public final class ReactionListContextMenuContent: ContextControllerItemsContent
let iconSpacing: CGFloat = 4.0 let iconSpacing: CGFloat = 4.0
var iconSize = CGSize(width: 22.0, height: 22.0) var iconSize = CGSize(width: 22.0, height: 22.0)
if let reactionIconNode = self.reactionIconNode { if let _ = self.reactionIconNode {
iconSize = reactionIconNode.size.aspectFitted(iconSize)
} else if let iconNode = self.iconNode, let image = iconNode.image { } else if let iconNode = self.iconNode, let image = iconNode.image {
iconSize = image.size.aspectFitted(iconSize) iconSize = image.size.aspectFitted(iconSize)
} }
@ -183,6 +181,7 @@ public final class ReactionListContextMenuContent: ContextControllerItemsContent
if let reactionIconNode = self.reactionIconNode { if let reactionIconNode = self.reactionIconNode {
reactionIconNode.frame = CGRect(origin: CGPoint(x: sideInset, y: floorToScreenPixels((constrainedSize.height - iconSize.height) / 2.0)), size: iconSize) reactionIconNode.frame = CGRect(origin: CGPoint(x: sideInset, y: floorToScreenPixels((constrainedSize.height - iconSize.height) / 2.0)), size: iconSize)
reactionIconNode.update(size: iconSize)
} else if let iconNode = self.iconNode { } else if let iconNode = self.iconNode {
iconNode.frame = CGRect(origin: CGPoint(x: sideInset, y: floorToScreenPixels((constrainedSize.height - iconSize.height) / 2.0)), size: iconSize) iconNode.frame = CGRect(origin: CGPoint(x: sideInset, y: floorToScreenPixels((constrainedSize.height - iconSize.height) / 2.0)), size: iconSize)
} }
@ -386,8 +385,9 @@ public final class ReactionListContextMenuContent: ContextControllerItemsContent
self.titleLabelNode.frame = CGRect(origin: CGPoint(x: avatarInset + avatarSize + avatarSpacing, y: floor((size.height - titleSize.height) / 2.0)), size: titleSize) self.titleLabelNode.frame = CGRect(origin: CGPoint(x: avatarInset + avatarSize + avatarSpacing, y: floor((size.height - titleSize.height) / 2.0)), size: titleSize)
if let reactionIconNode = self.reactionIconNode { if let reactionIconNode = self.reactionIconNode {
let reactionSize = reactionIconNode.size.aspectFitted(CGSize(width: 22.0, height: 22.0)) let reactionSize = CGSize(width: 22.0, height: 22.0)
reactionIconNode.frame = CGRect(origin: CGPoint(x: size.width - 32.0 - floor((32.0 - reactionSize.width) / 2.0), y: floor((size.height - reactionSize.height) / 2.0)), size: reactionSize) reactionIconNode.frame = CGRect(origin: CGPoint(x: size.width - 32.0 - floor((32.0 - reactionSize.width) / 2.0), y: floor((size.height - reactionSize.height) / 2.0)), size: reactionSize)
reactionIconNode.update(size: reactionSize)
} }
self.separatorNode.frame = CGRect(origin: CGPoint(x: 0.0, y: size.height), size: CGSize(width: size.width, height: UIScreenPixel)) self.separatorNode.frame = CGRect(origin: CGPoint(x: 0.0, y: size.height), size: CGSize(width: size.width, height: UIScreenPixel))

View File

@ -1244,9 +1244,9 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
} }
} }
func animateOutToReaction(value: String, targetView: UIView, hideNode: Bool, animateTargetContainer: UIView?, completion: @escaping () -> Void) { func animateOutToReaction(value: String, targetView: UIView, hideNode: Bool, animateTargetContainer: UIView?, addStandaloneReactionAnimation: ((StandaloneReactionAnimation) -> Void)?, completion: @escaping () -> Void) {
if let presentationNode = self.presentationNode { if let presentationNode = self.presentationNode {
presentationNode.animateOutToReaction(value: value, targetView: targetView, hideNode: hideNode, animateTargetContainer: animateTargetContainer, completion: completion) presentationNode.animateOutToReaction(value: value, targetView: targetView, hideNode: hideNode, animateTargetContainer: animateTargetContainer, addStandaloneReactionAnimation: addStandaloneReactionAnimation, completion: completion)
return return
} }
@ -1264,7 +1264,7 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
self.reactionContextNodeIsAnimatingOut = true self.reactionContextNodeIsAnimatingOut = true
reactionContextNode.willAnimateOutToReaction(value: value) reactionContextNode.willAnimateOutToReaction(value: value)
reactionContextNode.animateOutToReaction(value: value, targetView: targetView, hideNode: hideNode, animateTargetContainer: animateTargetContainer, completion: { [weak self] in reactionContextNode.animateOutToReaction(value: value, targetView: targetView, hideNode: hideNode, animateTargetContainer: animateTargetContainer, addStandaloneReactionAnimation: addStandaloneReactionAnimation, completion: { [weak self] in
guard let strongSelf = self else { guard let strongSelf = self else {
return return
} }
@ -2375,10 +2375,10 @@ public final class ContextController: ViewController, StandalonePresentableContr
self.dismissed?() self.dismissed?()
} }
public func dismissWithReaction(value: String, targetView: UIView, hideNode: Bool, animateTargetContainer: UIView?, completion: (() -> Void)?) { public func dismissWithReaction(value: String, targetView: UIView, hideNode: Bool, animateTargetContainer: UIView?, addStandaloneReactionAnimation: ((StandaloneReactionAnimation) -> Void)?, completion: (() -> Void)?) {
if !self.wasDismissed { if !self.wasDismissed {
self.wasDismissed = true self.wasDismissed = true
self.controllerNode.animateOutToReaction(value: value, targetView: targetView, hideNode: hideNode, animateTargetContainer: animateTargetContainer, completion: { [weak self] in self.controllerNode.animateOutToReaction(value: value, targetView: targetView, hideNode: hideNode, animateTargetContainer: animateTargetContainer, addStandaloneReactionAnimation: addStandaloneReactionAnimation, completion: { [weak self] in
self?.presentingViewController?.dismiss(animated: false, completion: nil) self?.presentingViewController?.dismiss(animated: false, completion: nil)
completion?() completion?()
}) })

View File

@ -970,7 +970,7 @@ final class ContextControllerActionsStackNode: ASDisplayNode {
let transition: ContainedViewLayoutTransition let transition: ContainedViewLayoutTransition
if animated { if animated {
transition = .animated(duration: 0.45, curve: .spring) transition = .animated(duration: self.itemContainers.count == 1 ? 0.3 : 0.45, curve: .spring)
} else { } else {
transition = .immediate transition = .immediate
} }

View File

@ -675,7 +675,7 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo
} }
} }
func animateOutToReaction(value: String, targetView: UIView, hideNode: Bool, animateTargetContainer: UIView?, completion: @escaping () -> Void) { func animateOutToReaction(value: String, targetView: UIView, hideNode: Bool, animateTargetContainer: UIView?, addStandaloneReactionAnimation: ((StandaloneReactionAnimation) -> Void)?, completion: @escaping () -> Void) {
guard let reactionContextNode = self.reactionContextNode else { guard let reactionContextNode = self.reactionContextNode else {
self.requestAnimateOut(.default, completion) self.requestAnimateOut(.default, completion)
return return
@ -697,7 +697,7 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo
intermediateCompletion() intermediateCompletion()
}) })
reactionContextNode.animateOutToReaction(value: value, targetView: targetView, hideNode: hideNode, animateTargetContainer: animateTargetContainer, completion: { [weak self] in reactionContextNode.animateOutToReaction(value: value, targetView: targetView, hideNode: hideNode, animateTargetContainer: animateTargetContainer, addStandaloneReactionAnimation: addStandaloneReactionAnimation, completion: { [weak self] in
guard let strongSelf = self else { guard let strongSelf = self else {
return return
} }

View File

@ -6,6 +6,7 @@ import TelegramPresentationData
import TextSelectionNode import TextSelectionNode
import TelegramCore import TelegramCore
import SwiftSignalKit import SwiftSignalKit
import ReactionSelectionNode
enum ContextControllerPresentationNodeStateTransition { enum ContextControllerPresentationNodeStateTransition {
case animateIn case animateIn
@ -24,7 +25,7 @@ protocol ContextControllerPresentationNode: ASDisplayNode {
stateTransition: ContextControllerPresentationNodeStateTransition? stateTransition: ContextControllerPresentationNodeStateTransition?
) )
func animateOutToReaction(value: String, targetView: UIView, hideNode: Bool, animateTargetContainer: UIView?, completion: @escaping () -> Void) func animateOutToReaction(value: String, targetView: UIView, hideNode: Bool, animateTargetContainer: UIView?, addStandaloneReactionAnimation: ((StandaloneReactionAnimation) -> Void)?, completion: @escaping () -> Void)
func cancelReactionAnimation() func cancelReactionAnimation()
func highlightGestureMoved(location: CGPoint) func highlightGestureMoved(location: CGPoint)

View File

@ -2,13 +2,18 @@ import Foundation
import AsyncDisplayKit import AsyncDisplayKit
open class ContextControllerSourceNode: ASDisplayNode { open class ContextControllerSourceNode: ASDisplayNode {
private var contextGesture: ContextGesture? public private(set) var contextGesture: ContextGesture?
public var isGestureEnabled: Bool = true { public var isGestureEnabled: Bool = true {
didSet { didSet {
self.contextGesture?.isEnabled = self.isGestureEnabled self.contextGesture?.isEnabled = self.isGestureEnabled
} }
} }
public var beginDelay: Double = 0.12 {
didSet {
self.contextGesture?.beginDelay = self.beginDelay
}
}
public var animateScale: Bool = true public var animateScale: Bool = true
public var activated: ((ContextGesture, CGPoint) -> Void)? public var activated: ((ContextGesture, CGPoint) -> Void)?
@ -31,6 +36,9 @@ open class ContextControllerSourceNode: ASDisplayNode {
self.contextGesture = contextGesture self.contextGesture = contextGesture
self.view.addGestureRecognizer(contextGesture) self.view.addGestureRecognizer(contextGesture)
contextGesture.beginDelay = self.beginDelay
contextGesture.isEnabled = self.isGestureEnabled
contextGesture.shouldBegin = { [weak self] point in contextGesture.shouldBegin = { [weak self] point in
guard let strongSelf = self, !strongSelf.bounds.width.isZero else { guard let strongSelf = self, !strongSelf.bounds.width.isZero else {
return false return false

View File

@ -20,8 +20,6 @@ private class TimerTargetWrapper: NSObject {
} }
} }
private let beginDelay: Double = 0.12
public func cancelParentGestures(view: UIView) { public func cancelParentGestures(view: UIView) {
if let gestureRecognizers = view.gestureRecognizers { if let gestureRecognizers = view.gestureRecognizers {
for recognizer in gestureRecognizers { for recognizer in gestureRecognizers {
@ -55,16 +53,19 @@ private func cancelOtherGestures(gesture: ContextGesture, view: UIView) {
} }
public final class ContextGesture: UIGestureRecognizer, UIGestureRecognizerDelegate { public final class ContextGesture: UIGestureRecognizer, UIGestureRecognizerDelegate {
public var beginDelay: Double = 0.12
private var currentProgress: CGFloat = 0.0 private var currentProgress: CGFloat = 0.0
private var delayTimer: Timer? private var delayTimer: Timer?
private var animator: DisplayLinkAnimator? private var animator: DisplayLinkAnimator?
private var isValidated: Bool = false private var isValidated: Bool = false
private var wasActivated: Bool = false
public var shouldBegin: ((CGPoint) -> Bool)? public var shouldBegin: ((CGPoint) -> Bool)?
public var activationProgress: ((CGFloat, ContextGestureTransition) -> Void)? public var activationProgress: ((CGFloat, ContextGestureTransition) -> Void)?
public var activated: ((ContextGesture, CGPoint) -> Void)? public var activated: ((ContextGesture, CGPoint) -> Void)?
public var externalUpdated: ((UIView?, CGPoint) -> Void)? public var externalUpdated: ((UIView?, CGPoint) -> Void)?
public var externalEnded: (((UIView?, CGPoint)?) -> Void)? public var externalEnded: (((UIView?, CGPoint)?) -> Void)?
public var activatedAfterCompletion: (() -> Void)?
override public init(target: Any?, action: Selector?) { override public init(target: Any?, action: Selector?) {
super.init(target: target, action: action) super.init(target: target, action: action)
@ -85,6 +86,7 @@ public final class ContextGesture: UIGestureRecognizer, UIGestureRecognizerDeleg
self.externalEnded = nil self.externalEnded = nil
self.animator?.invalidate() self.animator?.invalidate()
self.animator = nil self.animator = nil
self.wasActivated = false
} }
public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
@ -116,7 +118,7 @@ public final class ContextGesture: UIGestureRecognizer, UIGestureRecognizerDeleg
} }
if self.delayTimer == nil { if self.delayTimer == nil {
let delayTimer = Timer(timeInterval: beginDelay, target: TimerTargetWrapper { [weak self] in let delayTimer = Timer(timeInterval: self.beginDelay, target: TimerTargetWrapper { [weak self] in
guard let strongSelf = self, let _ = strongSelf.delayTimer else { guard let strongSelf = self, let _ = strongSelf.delayTimer else {
return return
} }
@ -139,6 +141,7 @@ public final class ContextGesture: UIGestureRecognizer, UIGestureRecognizerDeleg
strongSelf.delayTimer?.invalidate() strongSelf.delayTimer?.invalidate()
strongSelf.animator?.invalidate() strongSelf.animator?.invalidate()
strongSelf.activated?(strongSelf, location) strongSelf.activated?(strongSelf, location)
strongSelf.wasActivated = true
if let view = strongSelf.view?.superview { if let view = strongSelf.view?.superview {
if let window = view.window { if let window = view.window {
cancelOtherGestures(gesture: strongSelf, view: window) cancelOtherGestures(gesture: strongSelf, view: window)
@ -174,6 +177,7 @@ public final class ContextGesture: UIGestureRecognizer, UIGestureRecognizerDeleg
self.delayTimer?.invalidate() self.delayTimer?.invalidate()
self.animator?.invalidate() self.animator?.invalidate()
self.activated?(self, touch.location(in: self.view)) self.activated?(self, touch.location(in: self.view))
self.wasActivated = true
if let view = self.view?.superview { if let view = self.view?.superview {
if let window = view.window { if let window = view.window {
cancelOtherGestures(gesture: self, view: window) cancelOtherGestures(gesture: self, view: window)
@ -198,6 +202,9 @@ public final class ContextGesture: UIGestureRecognizer, UIGestureRecognizerDeleg
if !self.currentProgress.isZero, self.isValidated { if !self.currentProgress.isZero, self.isValidated {
self.currentProgress = 0.0 self.currentProgress = 0.0
self.activationProgress?(0.0, .ended(self.currentProgress)) self.activationProgress?(0.0, .ended(self.currentProgress))
if self.wasActivated {
self.activatedAfterCompletion?()
}
} }
self.externalEnded?((self.view, touch.location(in: self.view))) self.externalEnded?((self.view, touch.location(in: self.view)))

View File

@ -13,7 +13,8 @@ import AccountContext
public class ItemListReactionItem: ListViewItem, ItemListItem { public class ItemListReactionItem: ListViewItem, ItemListItem {
let context: AccountContext let context: AccountContext
let presentationData: ItemListPresentationData let presentationData: ItemListPresentationData
let file: TelegramMediaFile? let availableReactions: AvailableReactions?
let reaction: String
let title: String let title: String
let value: Bool let value: Bool
let enabled: Bool let enabled: Bool
@ -25,7 +26,8 @@ public class ItemListReactionItem: ListViewItem, ItemListItem {
public init( public init(
context: AccountContext, context: AccountContext,
presentationData: ItemListPresentationData, presentationData: ItemListPresentationData,
file: TelegramMediaFile?, availableReactions: AvailableReactions?,
reaction: String,
title: String, title: String,
value: Bool, value: Bool,
enabled: Bool = true, enabled: Bool = true,
@ -36,7 +38,8 @@ public class ItemListReactionItem: ListViewItem, ItemListItem {
) { ) {
self.context = context self.context = context
self.presentationData = presentationData self.presentationData = presentationData
self.file = file self.availableReactions = availableReactions
self.reaction = reaction
self.title = title self.title = title
self.value = value self.value = value
self.enabled = enabled self.enabled = enabled
@ -122,7 +125,7 @@ public class ItemListReactionItemNode: ListViewItemNode, ItemListItemNode {
private let highlightedBackgroundNode: ASDisplayNode private let highlightedBackgroundNode: ASDisplayNode
private let maskNode: ASImageNode private let maskNode: ASImageNode
private let imageNode: ReactionFileImageNode private var imageNode: ReactionImageNode?
private let titleNode: TextNode private let titleNode: TextNode
private var switchNode: ASDisplayNode & ItemListSwitchNodeImpl private var switchNode: ASDisplayNode & ItemListSwitchNodeImpl
private let switchGestureNode: ASDisplayNode private let switchGestureNode: ASDisplayNode
@ -150,8 +153,6 @@ public class ItemListReactionItemNode: ListViewItemNode, ItemListItemNode {
self.bottomStripeNode = ASDisplayNode() self.bottomStripeNode = ASDisplayNode()
self.bottomStripeNode.isLayerBacked = true self.bottomStripeNode.isLayerBacked = true
self.imageNode = ReactionFileImageNode()
self.titleNode = TextNode() self.titleNode = TextNode()
self.titleNode.isUserInteractionEnabled = false self.titleNode.isUserInteractionEnabled = false
@ -166,7 +167,6 @@ public class ItemListReactionItemNode: ListViewItemNode, ItemListItemNode {
super.init(layerBacked: false, dynamicBounce: false) super.init(layerBacked: false, dynamicBounce: false)
self.addSubnode(self.imageNode)
self.addSubnode(self.titleNode) self.addSubnode(self.titleNode)
self.addSubnode(self.switchNode) self.addSubnode(self.switchNode)
self.addSubnode(self.switchGestureNode) self.addSubnode(self.switchGestureNode)
@ -191,7 +191,6 @@ public class ItemListReactionItemNode: ListViewItemNode, ItemListItemNode {
} }
func asyncLayout() -> (_ item: ItemListReactionItem, _ params: ListViewItemLayoutParams, _ insets: ItemListNeighbors) -> (ListViewItemNodeLayout, (Bool) -> Void) { func asyncLayout() -> (_ item: ItemListReactionItem, _ params: ListViewItemLayoutParams, _ insets: ItemListNeighbors) -> (ListViewItemNodeLayout, (Bool) -> Void) {
let makeImageLayout = self.imageNode.asyncLayout()
let makeTitleLayout = TextNode.asyncLayout(self.titleNode) let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
let currentItem = self.item let currentItem = self.item
@ -227,8 +226,6 @@ public class ItemListReactionItemNode: ListViewItemNode, ItemListItemNode {
insets = itemListNeighborsGroupedInsets(neighbors, params) insets = itemListNeighborsGroupedInsets(neighbors, params)
} }
let (imageSize, imageApply) = makeImageLayout(item.context, item.file)
let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.title, font: titleFont, textColor: item.presentationData.theme.list.itemPrimaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - params.leftInset - params.rightInset - 80.0 - sideImageInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.title, font: titleFont, textColor: item.presentationData.theme.list.itemPrimaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - params.leftInset - params.rightInset - 80.0 - sideImageInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
contentSize.height = max(contentSize.height, titleLayout.size.height + 22.0) contentSize.height = max(contentSize.height, titleLayout.size.height + 22.0)
@ -361,9 +358,17 @@ public class ItemListReactionItemNode: ListViewItemNode, ItemListItemNode {
strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height - separatorHeight), size: CGSize(width: layoutSize.width - bottomStripeInset, height: separatorHeight)) strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height - separatorHeight), size: CGSize(width: layoutSize.width - bottomStripeInset, height: separatorHeight))
} }
let imageFitSize = imageSize.aspectFitted(CGSize(width: 30.0, height: 30.0)) if strongSelf.imageNode == nil, let availableReactions = item.availableReactions {
strongSelf.imageNode.frame = CGRect(origin: CGPoint(x: params.leftInset + floor(sideImageInset - imageFitSize.width), y: floor((contentSize.height - imageFitSize.height) / 2.0)), size: imageFitSize) let imageNode = ReactionImageNode(context: item.context, availableReactions: availableReactions, reaction: item.reaction)
imageApply() strongSelf.imageNode = imageNode
strongSelf.addSubnode(imageNode)
}
if let imageNode = strongSelf.imageNode {
let imageFitSize = CGSize(width: 30.0, height: 30.0)
imageNode.frame = CGRect(origin: CGPoint(x: params.leftInset + floor(sideImageInset - imageFitSize.width), y: floor((contentSize.height - imageFitSize.height) / 2.0)), size: imageFitSize)
imageNode.update(size: imageFitSize)
}
strongSelf.titleNode.frame = CGRect(origin: CGPoint(x: leftInset, y: floorToScreenPixels((contentSize.height - titleLayout.size.height) / 2.0)), size: titleLayout.size) strongSelf.titleNode.frame = CGRect(origin: CGPoint(x: leftInset, y: floorToScreenPixels((contentSize.height - titleLayout.size.height) / 2.0)), size: titleLayout.size)
if let switchView = strongSelf.switchNode.view as? UISwitch { if let switchView = strongSelf.switchNode.view as? UISwitch {

View File

@ -45,7 +45,7 @@ private enum PeerAllowedReactionListControllerEntry: ItemListNodeEntry {
case allowAllInfo(String) case allowAllInfo(String)
case itemsHeader(String) case itemsHeader(String)
case item(index: Int, value: String, file: TelegramMediaFile?, text: String, isEnabled: Bool) case item(index: Int, value: String, availableReactions: AvailableReactions?, reaction: String, text: String, isEnabled: Bool)
var section: ItemListSectionId { var section: ItemListSectionId {
switch self { switch self {
@ -64,7 +64,7 @@ private enum PeerAllowedReactionListControllerEntry: ItemListNodeEntry {
return .allowAllInfo return .allowAllInfo
case .itemsHeader: case .itemsHeader:
return .itemsHeader return .itemsHeader
case let .item(_, value, _, _, _): case let .item(_, value, _, _, _, _):
return .item(value) return .item(value)
} }
} }
@ -77,7 +77,7 @@ private enum PeerAllowedReactionListControllerEntry: ItemListNodeEntry {
return 1 return 1
case .itemsHeader: case .itemsHeader:
return 2 return 2
case let .item(index, _, _, _, _): case let .item(index, _, _, _, _, _):
return 100 + index return 100 + index
} }
} }
@ -102,8 +102,8 @@ private enum PeerAllowedReactionListControllerEntry: ItemListNodeEntry {
} else { } else {
return false return false
} }
case let .item(index, value, file, text, isEnabled): case let .item(index, value, availableReactions, reaction, text, isEnabled):
if case .item(index, value, file, text, isEnabled) = rhs { if case .item(index, value, availableReactions, reaction, text, isEnabled) = rhs {
return true return true
} else { } else {
return false return false
@ -126,11 +126,12 @@ private enum PeerAllowedReactionListControllerEntry: ItemListNodeEntry {
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section) return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
case let .itemsHeader(text): case let .itemsHeader(text):
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section) return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
case let .item(_, value, file, text, isEnabled): case let .item(_, value, availableReactions, reaction, text, isEnabled):
return ItemListReactionItem( return ItemListReactionItem(
context: arguments.context, context: arguments.context,
presentationData: presentationData, presentationData: presentationData,
file: file, availableReactions: availableReactions,
reaction: reaction,
title: text, title: text,
value: isEnabled, value: isEnabled,
sectionId: self.section, sectionId: self.section,
@ -172,7 +173,7 @@ private func peerAllowedReactionListControllerEntries(
if !availableReaction.isEnabled { if !availableReaction.isEnabled {
continue continue
} }
entries.append(.item(index: index, value: availableReaction.value, file: availableReaction.staticIcon, text: availableReaction.title, isEnabled: allowedReactions.contains(availableReaction.value))) entries.append(.item(index: index, value: availableReaction.value, availableReactions: availableReactions, reaction: availableReaction.value, text: availableReaction.title, isEnabled: allowedReactions.contains(availableReaction.value)))
index += 1 index += 1
} }
} }

View File

@ -67,7 +67,6 @@ final class ReactionContextBackgroundNode: ASDisplayNode {
self.backgroundLayer.backgroundColor = UIColor.black.cgColor self.backgroundLayer.backgroundColor = UIColor.black.cgColor
self.backgroundLayer.masksToBounds = true self.backgroundLayer.masksToBounds = true
self.backgroundLayer.cornerRadius = 52.0 / 2.0
self.largeCircleLayer.backgroundColor = UIColor.black.cgColor self.largeCircleLayer.backgroundColor = UIColor.black.cgColor
self.largeCircleLayer.masksToBounds = true self.largeCircleLayer.masksToBounds = true
@ -114,6 +113,7 @@ final class ReactionContextBackgroundNode: ASDisplayNode {
size: CGSize, size: CGSize,
cloudSourcePoint: CGFloat, cloudSourcePoint: CGFloat,
isLeftAligned: Bool, isLeftAligned: Bool,
isMinimized: Bool,
transition: ContainedViewLayoutTransition transition: ContainedViewLayoutTransition
) { ) {
let shadowInset: CGFloat = 15.0 let shadowInset: CGFloat = 15.0
@ -136,7 +136,13 @@ final class ReactionContextBackgroundNode: ASDisplayNode {
} }
} }
let backgroundFrame = CGRect(origin: CGPoint(), size: size) var backgroundFrame = CGRect(origin: CGPoint(), size: size)
if isMinimized {
let updatedHeight = floor(size.height * 0.9)
backgroundFrame = CGRect(origin: CGPoint(x: 0.0, y: size.height - updatedHeight), size: CGSize(width: size.width, height: updatedHeight))
}
transition.updateCornerRadius(layer: self.backgroundLayer, cornerRadius: backgroundFrame.height / 2.0)
let largeCircleFrame: CGRect let largeCircleFrame: CGRect
let smallCircleFrame: CGRect let smallCircleFrame: CGRect

View File

@ -21,20 +21,26 @@ public final class ReactionContextItem {
public let appearAnimation: TelegramMediaFile public let appearAnimation: TelegramMediaFile
public let stillAnimation: TelegramMediaFile public let stillAnimation: TelegramMediaFile
public let listAnimation: TelegramMediaFile public let listAnimation: TelegramMediaFile
public let largeListAnimation: TelegramMediaFile
public let applicationAnimation: TelegramMediaFile public let applicationAnimation: TelegramMediaFile
public let largeApplicationAnimation: TelegramMediaFile
public init( public init(
reaction: ReactionContextItem.Reaction, reaction: ReactionContextItem.Reaction,
appearAnimation: TelegramMediaFile, appearAnimation: TelegramMediaFile,
stillAnimation: TelegramMediaFile, stillAnimation: TelegramMediaFile,
listAnimation: TelegramMediaFile, listAnimation: TelegramMediaFile,
applicationAnimation: TelegramMediaFile largeListAnimation: TelegramMediaFile,
applicationAnimation: TelegramMediaFile,
largeApplicationAnimation: TelegramMediaFile
) { ) {
self.reaction = reaction self.reaction = reaction
self.appearAnimation = appearAnimation self.appearAnimation = appearAnimation
self.stillAnimation = stillAnimation self.stillAnimation = stillAnimation
self.listAnimation = listAnimation self.listAnimation = listAnimation
self.largeListAnimation = largeListAnimation
self.applicationAnimation = applicationAnimation self.applicationAnimation = applicationAnimation
self.largeApplicationAnimation = largeApplicationAnimation
} }
} }
@ -56,8 +62,8 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
private weak var currentLongPressItemNode: ReactionNode? private weak var currentLongPressItemNode: ReactionNode?
private var isExpanded: Bool = true
private var highlightedReaction: ReactionContextItem.Reaction? private var highlightedReaction: ReactionContextItem.Reaction?
private var didTriggerExpandedReaction: Bool = false
private var continuousHaptic: Any? private var continuousHaptic: Any?
private var validLayout: (CGSize, UIEdgeInsets, CGRect)? private var validLayout: (CGSize, UIEdgeInsets, CGRect)?
private var isLeftAligned: Bool = true private var isLeftAligned: Bool = true
@ -187,12 +193,12 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
cloudSourcePoint = max(rect.minX + rect.height / 2.0, anchorRect.minX) cloudSourcePoint = max(rect.minX + rect.height / 2.0, anchorRect.minX)
} }
var visualRect = rect let visualRect = rect
if self.highlightedReaction != nil { /*if self.highlightedReaction != nil {
visualRect.origin.x -= 4.0 visualRect.origin.x -= 4.0
visualRect.size.width += 8.0 visualRect.size.width += 8.0
} }*/
return (rect, visualRect, isLeftAligned, cloudSourcePoint) return (rect, visualRect, isLeftAligned, cloudSourcePoint)
} }
@ -205,52 +211,96 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
let sideInset: CGFloat = 11.0 let sideInset: CGFloat = 11.0
let itemSpacing: CGFloat = 9.0 let itemSpacing: CGFloat = 9.0
let itemSize: CGFloat = 40.0 let itemSize: CGFloat = 40.0
let verticalInset: CGFloat = 13.0
let rowHeight: CGFloat = 30.0
let visibleBounds = self.scrollNode.view.bounds let containerHeight: CGFloat = 52.0
let appearBounds = self.scrollNode.view.bounds.insetBy(dx: 16.0, dy: 0.0) var contentHeight: CGFloat = containerHeight
if self.highlightedReaction != nil {
contentHeight = floor(contentHeight * 0.9)
}
//let highlightItemOffset: CGFloat = floor(itemSize * 0.8 / 2.0 * 0.5)
let totalVisibleCount: CGFloat = CGFloat(self.items.count)//7.0
let totalVisibleWidth: CGFloat = totalVisibleCount * itemSize + (totalVisibleCount - 1.0) * itemSpacing
//width = count * itemSize + (count - 1) * spacing
//count * itemSize = width - (count - 1) * spacing
//itemSize = (width - (count - 1) * spacing) / count
let selectedItemSize = floor(itemSize * 1.5)
let remainingVisibleWidth = totalVisibleWidth - selectedItemSize
let remainingVisibleCount = totalVisibleCount - 1.0
let remainingItemSize = floor((remainingVisibleWidth - (remainingVisibleCount - 1.0) * itemSpacing) / remainingVisibleCount)
let highlightItemSpacing: CGFloat = floor(itemSize * 0.2)
_ = highlightItemSpacing
//print("self.highlightedReaction = \(String(describing: self.highlightedReaction))")
var visibleBounds = self.scrollNode.view.bounds
self.previewingItemContainer.bounds = visibleBounds self.previewingItemContainer.bounds = visibleBounds
if self.highlightedReaction != nil {
visibleBounds = visibleBounds.insetBy(dx: remainingItemSize - selectedItemSize, dy: 0.0)
}
let appearBounds = visibleBounds.insetBy(dx: 16.0, dy: 0.0)
let highlightedReactionIndex = self.items.firstIndex(where: { $0.reaction == self.highlightedReaction }) let highlightedReactionIndex = self.items.firstIndex(where: { $0.reaction == self.highlightedReaction })
var validIndices = Set<Int>() var validIndices = Set<Int>()
var nextX: CGFloat = sideInset
for i in 0 ..< self.items.count { for i in 0 ..< self.items.count {
let columnIndex = i var currentItemSize = itemSize
let column = CGFloat(columnIndex)
let itemOffsetY: CGFloat = -1.0
var baseItemFrame = CGRect(origin: CGPoint(x: sideInset + column * (itemSize + itemSpacing), y: verticalInset + floor((rowHeight - itemSize) / 2.0) + itemOffsetY), size: CGSize(width: itemSize, height: itemSize))
if let highlightedReactionIndex = highlightedReactionIndex { if let highlightedReactionIndex = highlightedReactionIndex {
if highlightedReactionIndex == i {
currentItemSize = selectedItemSize
} else {
currentItemSize = remainingItemSize
}
}
var baseItemFrame = CGRect(origin: CGPoint(x: nextX, y: containerHeight - contentHeight + floor((contentHeight - currentItemSize) / 2.0)), size: CGSize(width: currentItemSize, height: currentItemSize))
if highlightedReactionIndex == i {
let updatedSize = floor(itemSize * 2.0)
baseItemFrame = baseItemFrame.insetBy(dx: (baseItemFrame.width - updatedSize) / 2.0, dy: (baseItemFrame.height - updatedSize) / 2.0)
baseItemFrame.origin.y = containerHeight - contentHeight + floor((contentHeight - itemSize) / 2.0) + itemSize + 4.0 - updatedSize
}
nextX += currentItemSize + itemSpacing
/*if let highlightedReactionIndex = highlightedReactionIndex {
let indexDistance = i - highlightedReactionIndex
_ = indexDistance
if i > highlightedReactionIndex { if i > highlightedReactionIndex {
baseItemFrame.origin.x += 8.0 baseItemFrame.origin.x += highlightItemOffset// - highlightItemSpacing * CGFloat(indexDistance)
} else if i == highlightedReactionIndex { } else if i == highlightedReactionIndex {
baseItemFrame.origin.x += 4.0 //baseItemFrame.origin.x += highlightItemOffset * 0.5
} } else {
baseItemFrame.origin.x -= highlightItemOffset// - highlightItemSpacing * CGFloat(indexDistance)
} }
}*/
if appearBounds.intersects(baseItemFrame) || (self.visibleItemNodes[i] != nil && visibleBounds.intersects(baseItemFrame)) { if appearBounds.intersects(baseItemFrame) || (self.visibleItemNodes[i] != nil && visibleBounds.intersects(baseItemFrame)) {
validIndices.insert(i) validIndices.insert(i)
var itemFrame = baseItemFrame let itemFrame = baseItemFrame
var isPreviewing = false var isPreviewing = false
if self.highlightedReaction == self.items[i].reaction { if self.highlightedReaction == self.items[i].reaction {
let updatedSize = CGSize(width: floor(itemFrame.width * 1.66), height: floor(itemFrame.height * 1.66)) //let updatedSize = CGSize(width: floor(itemFrame.width * 2.5), height: floor(itemFrame.height * 2.5))
itemFrame = CGRect(origin: CGPoint(x: itemFrame.midX - updatedSize.width / 2.0, y: itemFrame.maxY + 4.0 - updatedSize.height), size: updatedSize) //itemFrame = CGRect(origin: CGPoint(x: itemFrame.midX - updatedSize.width / 2.0, y: itemFrame.maxY + 4.0 - updatedSize.height), size: updatedSize)
isPreviewing = true isPreviewing = true
} else if self.highlightedReaction != nil { } else if self.highlightedReaction != nil {
let updatedSize = CGSize(width: floor(itemFrame.width * 0.9), height: floor(itemFrame.height * 0.9)) //let updatedSize = CGSize(width: floor(itemFrame.width * 0.8), height: floor(itemFrame.height * 0.8))
itemFrame = CGRect(origin: CGPoint(x: itemFrame.midX - updatedSize.width / 2.0, y: itemFrame.midY - updatedSize.height / 2.0), size: updatedSize) //itemFrame = CGRect(origin: CGPoint(x: itemFrame.midX - updatedSize.width / 2.0, y: itemFrame.midY - updatedSize.height / 2.0), size: updatedSize)
} }
var animateIn = false var animateIn = false
let itemNode: ReactionNode let itemNode: ReactionNode
var itemTransition = transition
if let current = self.visibleItemNodes[i] { if let current = self.visibleItemNodes[i] {
itemNode = current itemNode = current
} else { } else {
animateIn = self.didAnimateIn animateIn = self.didAnimateIn
itemTransition = .immediate
itemNode = ReactionNode(context: self.context, theme: self.theme, item: self.items[i]) itemNode = ReactionNode(context: self.context, theme: self.theme, item: self.items[i])
self.visibleItemNodes[i] = itemNode self.visibleItemNodes[i] = itemNode
@ -264,7 +314,7 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
} }
} }
transition.updateFrame(node: itemNode, frame: itemFrame, beginWithCurrentState: true, completion: { [weak self, weak itemNode] completed in itemTransition.updateFrame(node: itemNode, frame: itemFrame, beginWithCurrentState: true, completion: { [weak self, weak itemNode] completed in
guard let strongSelf = self, let itemNode = itemNode else { guard let strongSelf = self, let itemNode = itemNode else {
return return
} }
@ -277,7 +327,7 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
} }
} }
}) })
itemNode.updateLayout(size: itemFrame.size, isExpanded: false, isPreviewing: isPreviewing, transition: transition) itemNode.updateLayout(size: itemFrame.size, isExpanded: false, largeExpanded: false, isPreviewing: isPreviewing, transition: itemTransition)
if animateIn { if animateIn {
itemNode.appear(animated: !self.context.sharedContext.currentPresentationData.with({ $0 }).reduceMotion) itemNode.appear(animated: !self.context.sharedContext.currentPresentationData.with({ $0 }).reduceMotion)
@ -337,6 +387,7 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
size: visualBackgroundFrame.size, size: visualBackgroundFrame.size,
cloudSourcePoint: cloudSourcePoint - visualBackgroundFrame.minX, cloudSourcePoint: cloudSourcePoint - visualBackgroundFrame.minX,
isLeftAligned: isLeftAligned, isLeftAligned: isLeftAligned,
isMinimized: self.highlightedReaction != nil,
transition: transition transition: transition
) )
@ -410,15 +461,6 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
} }
private func animateFromItemNodeToReaction(itemNode: ReactionNode, targetView: UIView, hideNode: Bool, completion: @escaping () -> Void) { private func animateFromItemNodeToReaction(itemNode: ReactionNode, targetView: UIView, hideNode: Bool, completion: @escaping () -> Void) {
if "".isEmpty {
if hideNode {
targetView.alpha = 1.0
targetView.isHidden = false
}
completion()
return
}
guard let targetSnapshotView = targetView.snapshotContentTree(unhide: true) else { guard let targetSnapshotView = targetView.snapshotContentTree(unhide: true) else {
completion() completion()
return return
@ -454,6 +496,9 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
if hideNode { if hideNode {
targetView.alpha = 1.0 targetView.alpha = 1.0
targetView.isHidden = false targetView.isHidden = false
if let targetView = targetView as? ReactionIconView {
targetView.imageView.alpha = 1.0
}
targetSnapshotView?.isHidden = true targetSnapshotView?.isHidden = true
targetScaleCompleted = true targetScaleCompleted = true
intermediateCompletion() intermediateCompletion()
@ -475,10 +520,17 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
} }
} }
public func animateOutToReaction(value: String, targetView: UIView, hideNode: Bool, animateTargetContainer: UIView?, completion: @escaping () -> Void) { public func animateOutToReaction(value: String, targetView: UIView, hideNode: Bool, animateTargetContainer: UIView?, addStandaloneReactionAnimation: ((StandaloneReactionAnimation) -> Void)?, completion: @escaping () -> Void) {
var foundItemNode: ReactionNode?
for (_, itemNode) in self.visibleItemNodes { for (_, itemNode) in self.visibleItemNodes {
if itemNode.item.reaction.rawValue != value { if itemNode.item.reaction.rawValue == value {
continue foundItemNode = itemNode
break
}
}
guard let itemNode = foundItemNode else {
completion()
return
} }
self.animationTargetView = targetView self.animationTargetView = targetView
@ -502,22 +554,41 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
let selfSourceRect = itemNode.view.convert(itemNode.view.bounds, to: self.view) let selfSourceRect = itemNode.view.convert(itemNode.view.bounds, to: self.view)
let selfTargetRect = self.view.convert(targetView.bounds, from: targetView) let selfTargetRect = self.view.convert(targetView.bounds, from: targetView)
let expandedSize: CGSize = selfTargetRect.size var expandedSize: CGSize = selfTargetRect.size
if self.didTriggerExpandedReaction {
expandedSize = CGSize(width: 120.0, height: 120.0)
}
let expandedFrame = CGRect(origin: CGPoint(x: selfTargetRect.midX - expandedSize.width / 2.0, y: selfTargetRect.midY - expandedSize.height / 2.0), size: expandedSize) let expandedFrame = CGRect(origin: CGPoint(x: selfTargetRect.midX - expandedSize.width / 2.0, y: selfTargetRect.midY - expandedSize.height / 2.0), size: expandedSize)
let effectFrame = expandedFrame.insetBy(dx: -60.0, dy: -60.0) let effectFrame: CGRect
let incomingMessage: Bool = expandedFrame.midX < self.bounds.width / 2.0
if self.didTriggerExpandedReaction {
effectFrame = expandedFrame.insetBy(dx: -expandedFrame.width * 0.5, dy: -expandedFrame.height * 0.5).offsetBy(dx: incomingMessage ? (expandedFrame.width - 50.0) : (-expandedFrame.width + 50.0), dy: 0.0)
} else {
effectFrame = expandedFrame.insetBy(dx: -60.0, dy: -60.0)
}
let transition: ContainedViewLayoutTransition = .animated(duration: 0.2, curve: .linear) let transition: ContainedViewLayoutTransition = .animated(duration: 0.2, curve: .linear)
self.addSubnode(itemNode) self.addSubnode(itemNode)
itemNode.position = expandedFrame.center itemNode.position = expandedFrame.center
transition.updateBounds(node: itemNode, bounds: CGRect(origin: CGPoint(), size: expandedFrame.size)) transition.updateBounds(node: itemNode, bounds: CGRect(origin: CGPoint(), size: expandedFrame.size))
itemNode.updateLayout(size: expandedFrame.size, isExpanded: true, isPreviewing: false, transition: transition) itemNode.updateLayout(size: expandedFrame.size, isExpanded: true, largeExpanded: self.didTriggerExpandedReaction, isPreviewing: false, transition: transition)
let additionalAnimationNode = AnimatedStickerNode() let additionalAnimationNode = AnimatedStickerNode()
additionalAnimationNode.setup(source: AnimatedStickerResourceSource(account: itemNode.context.account, resource: itemNode.item.applicationAnimation.resource), width: Int(effectFrame.width * 2.0), height: Int(effectFrame.height * 2.0), playbackMode: .once, mode: .direct(cachePathPrefix: itemNode.context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(itemNode.item.applicationAnimation.resource.id))) let additionalAnimation: TelegramMediaFile
if self.didTriggerExpandedReaction {
additionalAnimation = itemNode.item.largeApplicationAnimation
if incomingMessage {
additionalAnimationNode.transform = CATransform3DMakeScale(-1.0, 1.0, 1.0)
}
} else {
additionalAnimation = itemNode.item.applicationAnimation
}
additionalAnimationNode.setup(source: AnimatedStickerResourceSource(account: itemNode.context.account, resource: additionalAnimation.resource), width: Int(effectFrame.width * 2.0), height: Int(effectFrame.height * 2.0), playbackMode: .once, mode: .direct(cachePathPrefix: itemNode.context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(additionalAnimation.resource.id)))
additionalAnimationNode.frame = effectFrame additionalAnimationNode.frame = effectFrame
additionalAnimationNode.updateLayout(size: effectFrame.size) additionalAnimationNode.updateLayout(size: effectFrame.size)
self.addSubnode(additionalAnimationNode) self.addSubnode(additionalAnimationNode)
@ -535,7 +606,26 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
intermediateCompletion() intermediateCompletion()
} }
transition.animatePositionWithKeyframes(node: itemNode, keyframes: generateParabollicMotionKeyframes(from: selfSourceRect.center, to: expandedFrame.center, elevation: 30.0)) transition.animatePositionWithKeyframes(node: itemNode, keyframes: generateParabollicMotionKeyframes(from: selfSourceRect.center, to: expandedFrame.center, elevation: 30.0), completion: { [weak self, weak itemNode, weak targetView] _ in
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.1, execute: {
guard let strongSelf = self else {
return
}
if strongSelf.didTriggerExpandedReaction {
return
}
guard let itemNode = itemNode else {
return
}
guard let targetView = targetView as? ReactionIconView else {
return
}
targetView.isHidden = false
targetView.imageView.alpha = 0.0
targetView.addSubnode(itemNode)
itemNode.frame = targetView.bounds
})
})
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.15 * UIView.animationDurationFactor(), execute: { DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.15 * UIView.animationDurationFactor(), execute: {
additionalAnimationNode.visibility = true additionalAnimationNode.visibility = true
@ -547,14 +637,41 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
}) })
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + min(5.0, 2.0 * UIView.animationDurationFactor()), execute: { DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + min(5.0, 2.0 * UIView.animationDurationFactor()), execute: {
self.animateFromItemNodeToReaction(itemNode: itemNode, targetView: targetView, hideNode: hideNode, completion: { if self.didTriggerExpandedReaction {
self.animateFromItemNodeToReaction(itemNode: itemNode, targetView: targetView, hideNode: hideNode, completion: { [weak self] in
if let strongSelf = self, strongSelf.didTriggerExpandedReaction, let addStandaloneReactionAnimation = addStandaloneReactionAnimation {
let standaloneReactionAnimation = StandaloneReactionAnimation()
addStandaloneReactionAnimation(standaloneReactionAnimation)
standaloneReactionAnimation.animateReactionSelection(
context: strongSelf.context,
theme: strongSelf.context.sharedContext.currentPresentationData.with({ $0 }).theme,
reaction: itemNode.item,
targetView: targetView,
hideNode: true,
completion: { [weak standaloneReactionAnimation] in
standaloneReactionAnimation?.removeFromSupernode()
}
)
}
mainAnimationCompleted = true mainAnimationCompleted = true
intermediateCompletion() intermediateCompletion()
}) })
}) } else {
return if hideNode {
targetView.alpha = 1.0
targetView.isHidden = false
if let targetView = targetView as? ReactionIconView {
targetView.imageView.alpha = 1.0
itemNode.removeFromSupernode()
} }
completion() }
mainAnimationCompleted = true
intermediateCompletion()
}
})
} }
override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
@ -575,7 +692,6 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
if #available(iOS 13.0, *) { if #available(iOS 13.0, *) {
self.continuousHaptic = try? ContinuousHaptic(duration: 2.5) self.continuousHaptic = try? ContinuousHaptic(duration: 2.5)
} }
//itemNode.updateIsLongPressing(isLongPressing: true)
if self.hapticFeedback == nil { if self.hapticFeedback == nil {
self.hapticFeedback = HapticFeedback() self.hapticFeedback = HapticFeedback()
@ -587,11 +703,7 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
} }
case .ended, .cancelled: case .ended, .cancelled:
self.continuousHaptic = nil self.continuousHaptic = nil
if let itemNode = self.currentLongPressItemNode { self.didTriggerExpandedReaction = true
self.currentLongPressItemNode = nil
self.reactionSelected?(itemNode.item)
itemNode.updateIsLongPressing(isLongPressing: false)
}
self.highlightGestureFinished(performAction: true) self.highlightGestureFinished(performAction: true)
default: default:
break break
@ -714,13 +826,6 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
self.updateLayout(size: size, insets: insets, anchorRect: anchorRect, transition: .animated(duration: 0.18, curve: .easeInOut), animateInFromAnchorRect: nil, animateOutToAnchorRect: nil, animateReactionHighlight: true) self.updateLayout(size: size, insets: insets, anchorRect: anchorRect, transition: .animated(duration: 0.18, curve: .easeInOut), animateInFromAnchorRect: nil, animateOutToAnchorRect: nil, animateReactionHighlight: true)
} }
} }
@objc private func disclosurePressed() {
self.isExpanded = true
if let (size, insets, anchorRect) = self.validLayout {
self.updateLayout(size: size, insets: insets, anchorRect: anchorRect, transition: .animated(duration: 0.3, curve: .spring), animateInFromAnchorRect: nil, animateOutToAnchorRect: nil, animateReactionHighlight: true)
}
}
} }
public final class StandaloneReactionAnimation: ASDisplayNode { public final class StandaloneReactionAnimation: ASDisplayNode {
@ -762,15 +867,24 @@ public final class StandaloneReactionAnimation: ASDisplayNode {
if let targetView = targetView as? ReactionIconView { if let targetView = targetView as? ReactionIconView {
self.itemNodeIsEmbedded = true self.itemNodeIsEmbedded = true
targetView.addSubnode(itemNode) targetView.addSubnode(itemNode)
} else {
self.addSubnode(itemNode)
}
itemNode.expandedAnimationDidBegin = { [weak self, weak targetView] in
guard let strongSelf = self, let targetView = targetView else {
return
}
if let targetView = targetView as? ReactionIconView {
strongSelf.itemNodeIsEmbedded = true
targetView.imageView.isHidden = true targetView.imageView.isHidden = true
} else { } else {
self.addSubnode(itemNode)
if hideNode { if hideNode {
targetView.isHidden = true targetView.isHidden = true
} }
} }
}
itemNode.isExtracted = true itemNode.isExtracted = true
let selfTargetRect = self.view.convert(targetView.bounds, from: targetView) let selfTargetRect = self.view.convert(targetView.bounds, from: targetView)
@ -802,7 +916,7 @@ public final class StandaloneReactionAnimation: ASDisplayNode {
} }
} }
itemNode.updateLayout(size: expandedFrame.size, isExpanded: true, isPreviewing: false, transition: .immediate) itemNode.updateLayout(size: expandedFrame.size, isExpanded: true, largeExpanded: false, isPreviewing: false, transition: .immediate)
let additionalAnimationNode = AnimatedStickerNode() let additionalAnimationNode = AnimatedStickerNode()
additionalAnimationNode.setup(source: AnimatedStickerResourceSource(account: itemNode.context.account, resource: itemNode.item.applicationAnimation.resource), width: Int(effectFrame.width * 2.0), height: Int(effectFrame.height * 2.0), playbackMode: .once, mode: .direct(cachePathPrefix: itemNode.context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(itemNode.item.applicationAnimation.resource.id))) additionalAnimationNode.setup(source: AnimatedStickerResourceSource(account: itemNode.context.account, resource: itemNode.item.applicationAnimation.resource), width: Int(effectFrame.width * 2.0), height: Int(effectFrame.height * 2.0), playbackMode: .once, mode: .direct(cachePathPrefix: itemNode.context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(itemNode.item.applicationAnimation.resource.id)))

View File

@ -58,6 +58,8 @@ final class ReactionNode: ASDisplayNode {
private var isLongPressing: Bool = false private var isLongPressing: Bool = false
private var longPressAnimator: DisplayLinkAnimator? private var longPressAnimator: DisplayLinkAnimator?
var expandedAnimationDidBegin: (() -> Void)?
init(context: AccountContext, theme: PresentationTheme, item: ReactionContextItem) { init(context: AccountContext, theme: PresentationTheme, item: ReactionContextItem) {
self.context = context self.context = context
self.item = item self.item = item
@ -105,7 +107,7 @@ final class ReactionNode: ASDisplayNode {
} }
} }
func updateLayout(size: CGSize, isExpanded: Bool, isPreviewing: Bool, transition: ContainedViewLayoutTransition) { func updateLayout(size: CGSize, isExpanded: Bool, largeExpanded: Bool, isPreviewing: Bool, transition: ContainedViewLayoutTransition) {
let intrinsicSize = size let intrinsicSize = size
let animationSize = self.item.stillAnimation.dimensions?.cgSize ?? CGSize(width: 512.0, height: 512.0) let animationSize = self.item.stillAnimation.dimensions?.cgSize ?? CGSize(width: 512.0, height: 512.0)
@ -128,7 +130,15 @@ final class ReactionNode: ASDisplayNode {
self.animationNode = animationNode self.animationNode = animationNode
self.addSubnode(animationNode) self.addSubnode(animationNode)
animationNode.started = { [weak self] in
self?.expandedAnimationDidBegin?()
}
if largeExpanded {
animationNode.setup(source: AnimatedStickerResourceSource(account: self.context.account, resource: self.item.largeListAnimation.resource), width: Int(expandedAnimationFrame.width * 2.0), height: Int(expandedAnimationFrame.height * 2.0), playbackMode: .once, mode: .direct(cachePathPrefix: self.context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(self.item.largeListAnimation.resource.id)))
} else {
animationNode.setup(source: AnimatedStickerResourceSource(account: self.context.account, resource: self.item.listAnimation.resource), width: Int(expandedAnimationFrame.width * 2.0), height: Int(expandedAnimationFrame.height * 2.0), playbackMode: .once, mode: .direct(cachePathPrefix: self.context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(self.item.listAnimation.resource.id))) animationNode.setup(source: AnimatedStickerResourceSource(account: self.context.account, resource: self.item.listAnimation.resource), width: Int(expandedAnimationFrame.width * 2.0), height: Int(expandedAnimationFrame.height * 2.0), playbackMode: .once, mode: .direct(cachePathPrefix: self.context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(self.item.listAnimation.resource.id)))
}
animationNode.frame = expandedAnimationFrame animationNode.frame = expandedAnimationFrame
animationNode.updateLayout(size: expandedAnimationFrame.size) animationNode.updateLayout(size: expandedAnimationFrame.size)

View File

@ -10,7 +10,6 @@ import TelegramUIPreferences
import ItemListUI import ItemListUI
import PresentationDataUtils import PresentationDataUtils
import AccountContext import AccountContext
import WebPBinding
import ReactionImageComponent import ReactionImageComponent
private final class QuickReactionSetupControllerArguments { private final class QuickReactionSetupControllerArguments {
@ -273,19 +272,19 @@ public func quickReactionSetupController(
if !availableReaction.isEnabled { if !availableReaction.isEnabled {
continue continue
} }
guard let centerAnimation = availableReaction.centerAnimation else {
continue
}
let signal: Signal<(String, UIImage?), NoError> = context.account.postbox.mediaBox.resourceData(availableReaction.staticIcon.resource) let signal: Signal<(String, UIImage?), NoError> = reactionStaticImage(context: context, animation: centerAnimation, pixelSize: CGSize(width: 72.0, height: 72.0))
|> distinctUntilChanged(isEqual: { lhs, rhs in
return lhs.complete == rhs.complete
})
|> map { data -> (String, UIImage?) in |> map { data -> (String, UIImage?) in
guard data.complete else { guard data.isComplete else {
return (availableReaction.value, nil) return (availableReaction.value, nil)
} }
guard let dataValue = try? Data(contentsOf: URL(fileURLWithPath: data.path)) else { guard let dataValue = try? Data(contentsOf: URL(fileURLWithPath: data.path)) else {
return (availableReaction.value, nil) return (availableReaction.value, nil)
} }
guard let image = WebP.convert(fromWebP: dataValue) else { guard let image = UIImage(data: dataValue) else {
return (availableReaction.value, nil) return (availableReaction.value, nil)
} }
return (availableReaction.value, image) return (availableReaction.value, image)

View File

@ -166,7 +166,9 @@ class ReactionChatPreviewItemNode: ListViewItemNode {
appearAnimation: reaction.appearAnimation, appearAnimation: reaction.appearAnimation,
stillAnimation: reaction.selectAnimation, stillAnimation: reaction.selectAnimation,
listAnimation: centerAnimation, listAnimation: centerAnimation,
applicationAnimation: aroundAnimation largeListAnimation: reaction.activateAnimation,
applicationAnimation: aroundAnimation,
largeApplicationAnimation: reaction.effectAnimation
), ),
targetView: targetView, targetView: targetView,
hideNode: true, hideNode: true,

View File

@ -42,7 +42,6 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF
var currentCode: String { var currentCode: String {
return self.codeInputView.text return self.codeInputView.text
//return self.codeField.textField.text ?? ""
} }
var loginWithCode: ((String) -> Void)? var loginWithCode: ((String) -> Void)?
@ -53,7 +52,6 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF
var inProgress: Bool = false { var inProgress: Bool = false {
didSet { didSet {
self.codeInputView.alpha = self.inProgress ? 0.6 : 1.0 self.codeInputView.alpha = self.inProgress ? 0.6 : 1.0
//self.codeField.alpha = self.inProgress ? 0.6 : 1.0
} }
} }
@ -89,11 +87,18 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF
self.nextOptionButtonNode.isUserInteractionEnabled = nextOptionActive self.nextOptionButtonNode.isUserInteractionEnabled = nextOptionActive
self.nextOptionButtonNode.addSubnode(self.nextOptionTitleNode) self.nextOptionButtonNode.addSubnode(self.nextOptionTitleNode)
/*self.codeSeparatorNode = ASDisplayNode()
self.codeSeparatorNode.isLayerBacked = true
self.codeSeparatorNode.backgroundColor = self.theme.list.itemPlainSeparatorColor*/
self.codeInputView = CodeInputView() self.codeInputView = CodeInputView()
self.codeInputView.textField.keyboardAppearance = self.theme.rootController.keyboardColor.keyboardAppearance
self.codeInputView.textField.returnKeyType = .done
self.codeInputView.textField.disableAutomaticKeyboardHandling = [.forward, .backward]
if #available(iOSApplicationExtension 12.0, iOS 12.0, *) {
self.codeInputView.textField.textContentType = .oneTimeCode
}
if #available(iOSApplicationExtension 10.0, iOS 10.0, *) {
self.codeInputView.textField.keyboardType = .asciiCapableNumberPad
} else {
self.codeInputView.textField.keyboardType = .numberPad
}
/*self.codeField = TextFieldNode() /*self.codeField = TextFieldNode()
self.codeField.textField.font = Font.regular(24.0) self.codeField.textField.font = Font.regular(24.0)

View File

@ -1030,7 +1030,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
appearAnimation: reaction.appearAnimation, appearAnimation: reaction.appearAnimation,
stillAnimation: reaction.selectAnimation, stillAnimation: reaction.selectAnimation,
listAnimation: centerAnimation, listAnimation: centerAnimation,
applicationAnimation: aroundAnimation largeListAnimation: reaction.activateAnimation,
applicationAnimation: aroundAnimation,
largeApplicationAnimation: reaction.effectAnimation
)) ))
} }
} }
@ -1080,7 +1082,14 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
hideTargetButton = targetView.superview hideTargetButton = targetView.superview
} }
controller.dismissWithReaction(value: updatedReaction, targetView: targetView, hideNode: true, animateTargetContainer: hideTargetButton, completion: { [weak itemNode, weak targetView] in controller.dismissWithReaction(value: updatedReaction, targetView: targetView, hideNode: true, animateTargetContainer: hideTargetButton, addStandaloneReactionAnimation: { standaloneReactionAnimation in
guard let strongSelf = self else {
return
}
strongSelf.chatDisplayNode.messageTransitionNode.addMessageStandaloneReactionAnimation(messageId: item.message.id, standaloneReactionAnimation: standaloneReactionAnimation)
standaloneReactionAnimation.frame = strongSelf.chatDisplayNode.bounds
strongSelf.chatDisplayNode.addSubnode(standaloneReactionAnimation)
}, completion: { [weak itemNode, weak targetView] in
guard let strongSelf = self, let itemNode = itemNode, let targetView = targetView else { guard let strongSelf = self, let itemNode = itemNode, let targetView = targetView else {
return return
} }
@ -1279,7 +1288,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
appearAnimation: reaction.appearAnimation, appearAnimation: reaction.appearAnimation,
stillAnimation: reaction.selectAnimation, stillAnimation: reaction.selectAnimation,
listAnimation: centerAnimation, listAnimation: centerAnimation,
applicationAnimation: aroundAnimation largeListAnimation: reaction.activateAnimation,
applicationAnimation: aroundAnimation,
largeApplicationAnimation: reaction.effectAnimation
), ),
targetView: targetView, targetView: targetView,
hideNode: true, hideNode: true,

View File

@ -2590,7 +2590,9 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
appearAnimation: reaction.appearAnimation, appearAnimation: reaction.appearAnimation,
stillAnimation: reaction.selectAnimation, stillAnimation: reaction.selectAnimation,
listAnimation: centerAnimation, listAnimation: centerAnimation,
applicationAnimation: aroundAnimation largeListAnimation: reaction.activateAnimation,
applicationAnimation: aroundAnimation,
largeApplicationAnimation: reaction.effectAnimation
), ),
targetView: targetView, targetView: targetView,
hideNode: true, hideNode: true,

View File

@ -9,7 +9,6 @@ import TelegramPresentationData
import AccountContext import AccountContext
import AppBundle import AppBundle
import ReactionButtonListComponent import ReactionButtonListComponent
import WebPBinding
import ReactionImageComponent import ReactionImageComponent
private func maybeAddRotationAnimation(_ layer: CALayer, duration: Double) { private func maybeAddRotationAnimation(_ layer: CALayer, duration: Double) {
@ -672,11 +671,13 @@ class ChatMessageDateAndStatusNode: ASDisplayNode {
}, },
reactions: arguments.reactions.map { reaction in reactions: arguments.reactions.map { reaction in
var centerAnimation: TelegramMediaFile? var centerAnimation: TelegramMediaFile?
var legacyIcon: TelegramMediaFile?
if let availableReactions = arguments.availableReactions { if let availableReactions = arguments.availableReactions {
for availableReaction in availableReactions.reactions { for availableReaction in availableReactions.reactions {
if availableReaction.value == reaction.value { if availableReaction.value == reaction.value {
centerAnimation = availableReaction.centerAnimation centerAnimation = availableReaction.centerAnimation
legacyIcon = availableReaction.staticIcon
break break
} }
} }
@ -695,7 +696,8 @@ class ChatMessageDateAndStatusNode: ASDisplayNode {
return ReactionButtonsAsyncLayoutContainer.Reaction( return ReactionButtonsAsyncLayoutContainer.Reaction(
reaction: ReactionButtonComponent.Reaction( reaction: ReactionButtonComponent.Reaction(
value: reaction.value, value: reaction.value,
centerAnimation: centerAnimation centerAnimation: centerAnimation,
legacyIcon: legacyIcon
), ),
count: Int(reaction.count), count: Int(reaction.count),
peers: peers, peers: peers,
@ -808,12 +810,13 @@ class ChatMessageDateAndStatusNode: ASDisplayNode {
item.node.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) item.node.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
} }
item.node.isGestureEnabled = true
let itemValue = item.value let itemValue = item.value
let itemNode = item.node let itemNode = item.node
item.node.isGestureEnabled = arguments.canViewReactionList item.node.isGestureEnabled = true
let canViewReactionList = arguments.canViewReactionList
item.node.activateAfterCompletion = !canViewReactionList
item.node.activated = { [weak itemNode] gesture, _ in item.node.activated = { [weak itemNode] gesture, _ in
guard let strongSelf = self else { guard let strongSelf = self, canViewReactionList else {
return return
} }
guard let itemNode = itemNode else { guard let itemNode = itemNode else {

View File

@ -141,11 +141,13 @@ final class MessageReactionButtonsNode: ASDisplayNode {
}, },
reactions: reactions.reactions.map { reaction in reactions: reactions.reactions.map { reaction in
var centerAnimation: TelegramMediaFile? var centerAnimation: TelegramMediaFile?
var legacyIcon: TelegramMediaFile?
if let availableReactions = availableReactions { if let availableReactions = availableReactions {
for availableReaction in availableReactions.reactions { for availableReaction in availableReactions.reactions {
if availableReaction.value == reaction.value { if availableReaction.value == reaction.value {
centerAnimation = availableReaction.centerAnimation centerAnimation = availableReaction.centerAnimation
legacyIcon = availableReaction.staticIcon
break break
} }
} }
@ -170,7 +172,8 @@ final class MessageReactionButtonsNode: ASDisplayNode {
return ReactionButtonsAsyncLayoutContainer.Reaction( return ReactionButtonsAsyncLayoutContainer.Reaction(
reaction: ReactionButtonComponent.Reaction( reaction: ReactionButtonComponent.Reaction(
value: reaction.value, value: reaction.value,
centerAnimation: centerAnimation centerAnimation: centerAnimation,
legacyIcon: legacyIcon
), ),
count: Int(reaction.count), count: Int(reaction.count),
peers: peers, peers: peers,
@ -308,12 +311,17 @@ final class MessageReactionButtonsNode: ASDisplayNode {
let itemValue = item.value let itemValue = item.value
let itemNode = item.node let itemNode = item.node
item.node.isGestureEnabled = canViewMessageReactionList(message: message) item.node.isGestureEnabled = true
let canViewReactionList = canViewMessageReactionList(message: message)
item.node.activateAfterCompletion = !canViewReactionList
item.node.activated = { [weak itemNode] gesture, _ in item.node.activated = { [weak itemNode] gesture, _ in
guard let strongSelf = self, let itemNode = itemNode else { guard let strongSelf = self, let itemNode = itemNode else {
gesture.cancel() gesture.cancel()
return return
} }
if !canViewReactionList {
return
}
strongSelf.openReactionPreview?(gesture, itemNode.containerNode, itemValue) strongSelf.openReactionPreview?(gesture, itemNode.containerNode, itemValue)
} }
item.node.additionalActivationProgressLayer = itemMaskView.layer item.node.additionalActivationProgressLayer = itemMaskView.layer