mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios
This commit is contained in:
commit
46dccd85f6
@ -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)])
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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))
|
||||||
|
@ -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?()
|
||||||
})
|
})
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
|
@ -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)))
|
||||||
|
@ -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 {
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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)))
|
||||||
|
@ -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)
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
@ -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,
|
||||||
|
@ -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)
|
||||||
|
@ -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,
|
||||||
|
@ -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,
|
||||||
|
@ -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 {
|
||||||
|
@ -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
|
||||||
|
Loading…
x
Reference in New Issue
Block a user