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

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

View File

@ -25,7 +25,7 @@ public final class CodeInputView: ASDisplayNode, UITextFieldDelegate {
}
private final class ItemView: ASDisplayNode {
private let backgroundView: UIImageView
private let backgroundView: UIView
private let textNode: ImmediateTextNode
private var borderColorValue: UInt32?
@ -33,7 +33,7 @@ public final class CodeInputView: ASDisplayNode, UITextFieldDelegate {
private var text: String = ""
override init() {
self.backgroundView = UIImageView()
self.backgroundView = UIView()
self.textNode = ImmediateTextNode()
super.init()
@ -48,11 +48,17 @@ public final class CodeInputView: ASDisplayNode, UITextFieldDelegate {
fatalError("init(coder:) has not been implemented")
}
func update(borderColor: UInt32) {
func update(borderColor: UInt32, isHighlighted: Bool) {
if 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 !text.isEmpty {
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 {
if let copyView = self.textNode.view.snapshotContentTree() {
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)
self.textNode.attributedText = NSAttributedString(string: text, font: Font.monospace(fontSize), textColor: UIColor(argb: textColor))
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))
}
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)
@ -86,9 +96,9 @@ public final class CodeInputView: ASDisplayNode, UITextFieldDelegate {
}
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] = []
public var updated: (() -> Void)?
@ -183,7 +193,7 @@ public final class CodeInputView: ASDisplayNode, UITextFieldDelegate {
}
public func textFieldDidEndEditing(_ textField: UITextField) {
self.focusIndex = nil
self.focusIndex = textField.text?.count ?? 0
self.updateItemViews(animated: true)
}
@ -205,7 +215,7 @@ public final class CodeInputView: ASDisplayNode, UITextFieldDelegate {
let itemView = self.itemViews[i]
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
if i < self.textValue.count {
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
}
self.prefixLabel.attributedText = NSAttributedString(string: prefix, font: Font.monospace(21.0), textColor: UIColor(argb: theme.foreground))
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))
}
let prefixSize = self.prefixLabel.updateLayout(CGSize(width: width, height: 100.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.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
if i < self.textValue.count {
itemText = String(self.textValue[self.textValue.index(self.textValue.startIndex, offsetBy: i)])

View File

@ -8,9 +8,9 @@ import TelegramCore
import AccountContext
import TelegramPresentationData
import UIKit
import WebPBinding
import AnimatedAvatarSetNode
import ReactionImageComponent
import WebPBinding
public final class ReactionIconView: PortalSourceView {
public let imageView: UIImageView
@ -39,6 +39,7 @@ public final class ReactionButtonAsyncNode: ContextControllerSourceNode {
var foreground: UInt32
var extractedBackground: UInt32
var extractedForeground: UInt32
var isSelected: Bool
}
struct Counter: Equatable {
@ -47,12 +48,13 @@ public final class ReactionButtonAsyncNode: ContextControllerSourceNode {
struct Layout: Equatable {
var colors: Colors
var baseSize: CGSize
var size: CGSize
var counter: Counter?
}
private struct AnimationState {
var fromCounter: Counter
var fromCounter: Counter?
var fromColors: Colors
var startTime: Double
var duration: Double
}
@ -69,8 +71,8 @@ public final class ReactionButtonAsyncNode: ContextControllerSourceNode {
func update(layout: Layout) {
if self.currentLayout != layout {
if let currentLayout = self.currentLayout, let counter = currentLayout.counter {
self.animationState = AnimationState(fromCounter: counter, startTime: CACurrentMediaTime(), duration: 0.15 * UIView.animationDurationFactor())
if let currentLayout = self.currentLayout, (currentLayout.counter != layout.counter || currentLayout.colors.isSelected != layout.colors.isSelected) {
self.animationState = AnimationState(fromCounter: currentLayout.counter, fromColors: currentLayout.colors, startTime: CACurrentMediaTime(), duration: 0.15 * UIView.animationDurationFactor())
}
self.currentLayout = layout
@ -128,108 +130,122 @@ public final class ReactionButtonAsyncNode: ContextControllerSourceNode {
}
}
var imageWidth = layout.baseSize.width
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
let image = generateImage(layout.size, rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
UIGraphicsPushContext(context)
let backgroundColor: UIColor
let foregroundColor: UIColor
if self.isExtracted {
backgroundColor = UIColor(argb: layout.colors.extractedBackground)
foregroundColor = UIColor(argb: layout.colors.extractedForeground)
} else {
backgroundColor = UIColor(argb: layout.colors.background)
foregroundColor = UIColor(argb: layout.colors.foreground)
}
context.setBlendMode(.copy)
context.setFillColor(backgroundColor.cgColor)
context.fillEllipse(in: CGRect(origin: CGPoint(), 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)))
#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 {
let isForegroundTransparent = foregroundColor.alpha < 1.0
context.setBlendMode(isForegroundTransparent ? .copy : .normal)
//let textAreaWidth = size.width - (layout.baseSize.height / 2.0 + 1.0 + 8.0)
var textOrigin: CGFloat = layout.baseSize.height / 2.0 + 1.0
textOrigin = max(textOrigin, layout.baseSize.height / 2.0 + UIScreenPixel)
var rightTextOrigin = textOrigin + totalComponentWidth
let animationFraction: CGFloat
if let animationState = self.animationState {
animationFraction = max(0.0, min(1.0, (CACurrentMediaTime() - animationState.startTime) / animationState.duration))
func drawContents(colors: Colors) {
let backgroundColor: UIColor
let foregroundColor: UIColor
if self.isExtracted {
backgroundColor = UIColor(argb: colors.extractedBackground)
foregroundColor = UIColor(argb: colors.extractedForeground)
} else {
animationFraction = 1.0
backgroundColor = UIColor(argb: colors.background)
foregroundColor = UIColor(argb: colors.foreground)
}
for i in (0 ..< counter.components.count).reversed() {
let component = counter.components[i]
var componentAlpha: CGFloat = 1.0
var componentVerticalOffset: CGFloat = 0.0
context.setBlendMode(.copy)
context.setFillColor(backgroundColor.cgColor)
context.fillEllipse(in: CGRect(origin: CGPoint(), 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)))
if let counter = layout.counter {
let isForegroundTransparent = foregroundColor.alpha < 1.0
context.setBlendMode(isForegroundTransparent ? .copy : .normal)
if let animationState = self.animationState {
let reverseIndex = counter.components.count - 1 - i
if reverseIndex < animationState.fromCounter.components.count {
let previousComponent = animationState.fromCounter.components[animationState.fromCounter.components.count - 1 - reverseIndex]
if previousComponent != component {
componentAlpha = animationFraction
componentVerticalOffset = (1.0 - animationFraction) * 8.0
if previousComponent.string < component.string {
componentVerticalOffset = -componentVerticalOffset
}
let textOrigin: CGFloat = 36.0
var rightTextOrigin = textOrigin + totalComponentWidth
let animationFraction: CGFloat
if let animationState = self.animationState, animationState.fromCounter != nil {
animationFraction = max(0.0, min(1.0, (CACurrentMediaTime() - animationState.startTime) / animationState.duration))
} else {
animationFraction = 1.0
}
for i in (0 ..< counter.components.count).reversed() {
let component = counter.components[i]
var componentAlpha: CGFloat = 1.0
var componentVerticalOffset: CGFloat = 0.0
if let animationState = self.animationState, let fromCounter = animationState.fromCounter {
let reverseIndex = counter.components.count - 1 - i
if reverseIndex < fromCounter.components.count {
let previousComponent = fromCounter.components[fromCounter.components.count - 1 - reverseIndex]
let previousComponentAlpha = 1.0 - componentAlpha
var previousComponentVerticalOffset = -animationFraction * 8.0
if previousComponent.string < component.string {
previousComponentVerticalOffset = -previousComponentVerticalOffset
if previousComponent != component {
componentAlpha = animationFraction
componentVerticalOffset = (1.0 - animationFraction) * 8.0
if previousComponent.string < component.string {
componentVerticalOffset = -componentVerticalOffset
}
let previousComponentAlpha = 1.0 - componentAlpha
var previousComponentVerticalOffset = -animationFraction * 8.0
if previousComponent.string < component.string {
previousComponentVerticalOffset = -previousComponentVerticalOffset
}
var componentOrigin = rightTextOrigin - previousComponent.bounds.width
componentOrigin = max(componentOrigin, layout.size.height / 2.0 + UIScreenPixel)
let previousColor: UIColor
if isForegroundTransparent {
previousColor = foregroundColor.mixedWith(backgroundColor, alpha: 1.0 - previousComponentAlpha)
} else {
previousColor = foregroundColor.withMultipliedAlpha(previousComponentAlpha)
}
let string = NSAttributedString(string: previousComponent.string, font: Font.medium(11.0), textColor: previousColor)
string.draw(at: previousComponent.bounds.origin.offsetBy(dx: componentOrigin, dy: floorToScreenPixels(size.height - previousComponent.bounds.height) / 2.0 + previousComponentVerticalOffset))
}
var componentOrigin = rightTextOrigin - previousComponent.bounds.width
componentOrigin = max(componentOrigin, layout.baseSize.height / 2.0 + UIScreenPixel)
let previousColor: UIColor
if isForegroundTransparent {
previousColor = foregroundColor.mixedWith(backgroundColor, alpha: 1.0 - previousComponentAlpha)
} else {
previousColor = foregroundColor.withMultipliedAlpha(previousComponentAlpha)
}
let string = NSAttributedString(string: previousComponent.string, font: Font.medium(11.0), textColor: previousColor)
string.draw(at: previousComponent.bounds.origin.offsetBy(dx: componentOrigin, dy: floorToScreenPixels(size.height - previousComponent.bounds.height) / 2.0 + previousComponentVerticalOffset))
}
}
let componentOrigin = rightTextOrigin - component.bounds.width
let currentColor: UIColor
if isForegroundTransparent {
currentColor = foregroundColor.mixedWith(backgroundColor, alpha: 1.0 - componentAlpha)
} else {
currentColor = foregroundColor.withMultipliedAlpha(componentAlpha)
}
let string = NSAttributedString(string: component.string, font: Font.medium(11.0), textColor: currentColor)
string.draw(at: component.bounds.origin.offsetBy(dx: componentOrigin, dy: floorToScreenPixels(size.height - component.bounds.height) / 2.0 + componentVerticalOffset))
rightTextOrigin -= component.bounds.width
}
let componentOrigin = rightTextOrigin - component.bounds.width
let currentColor: UIColor
if isForegroundTransparent {
currentColor = foregroundColor.mixedWith(backgroundColor, alpha: 1.0 - componentAlpha)
} else {
currentColor = foregroundColor.withMultipliedAlpha(componentAlpha)
}
let string = NSAttributedString(string: component.string, font: Font.medium(11.0), textColor: currentColor)
string.draw(at: component.bounds.origin.offsetBy(dx: componentOrigin, dy: floorToScreenPixels(size.height - component.bounds.height) / 2.0 + componentVerticalOffset))
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()
})?.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 {
let previousContents = self.layer.contents
@ -293,7 +309,7 @@ public final class ReactionButtonAsyncNode: ContextControllerSourceNode {
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
} else {
resultSize.width += boundingRect.width
@ -357,24 +373,29 @@ public final class ReactionButtonAsyncNode: ContextControllerSourceNode {
let spacing: CGFloat = 2.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
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
} else if let file = spec.component.reaction.legacyIcon {
imageSize = file.dimensions?.cgSize.aspectFitted(boundingImageSize) ?? boundingImageSize
} else {
imageSize = defaultImageSize
imageSize = boundingImageSize
}
var counterComponents: [String] = []
for character in countString(Int64(spec.component.count)) {
counterComponents.append(String(character))
}
#if DEBUG && false
counterComponents.removeAll()
for character in "42" {
counterComponents.append(String(character))
/*#if DEBUG
if spec.component.count % 2 == 0 {
counterComponents.removeAll()
for character in "123.5K" {
counterComponents.append(String(character))
}
}
#endif
#endif*/
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,
foreground: spec.component.isSelected ? spec.component.colors.selectedForeground : spec.component.colors.deselectedForeground,
extractedBackground: spec.component.colors.extractedBackground,
extractedForeground: spec.component.colors.extractedForeground
extractedForeground: spec.component.colors.extractedForeground,
isSelected: spec.component.isSelected
)
var backgroundCounter: ReactionButtonAsyncNode.ContainerButtonNode.Counter?
if let counterLayout = counterLayout {
@ -421,7 +443,7 @@ public final class ReactionButtonAsyncNode: ContextControllerSourceNode {
}
let backgroundLayout = ContainerButtonNode.Layout(
colors: backgroundColors,
baseSize: CGSize(width: height + 2.0, height: height),
size: size,
counter: backgroundCounter
)
@ -447,6 +469,18 @@ public final class ReactionButtonAsyncNode: ContextControllerSourceNode {
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() {
self.containerNode = ContextExtractedContentContainingNode()
self.buttonNode = ContainerButtonNode()
@ -475,22 +509,13 @@ public final class ReactionButtonAsyncNode: ContextControllerSourceNode {
}
self.isGestureEnabled = true
self.beginDelay = 0.0
self.containerNode.willUpdateIsExtractedToContextPreview = { [weak self] isExtracted, _ in
guard let strongSelf = self else {
return
}
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()
}
override public func didLoad() {
super.didLoad()
if self.activateAfterCompletion {
self.contextGesture?.activatedAfterCompletion = { [weak self] in
self?.pressed()
}
}
}
@objc private func pressed() {
guard let layout = self.layout else {
return
@ -515,7 +550,6 @@ public final class ReactionButtonAsyncNode: ContextControllerSourceNode {
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)
//ASDisplayNodeSetResizableContents(self.buttonNode.layer, layout.backgroundImage)
self.buttonNode.update(layout: layout.backgroundLayout)
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 var value: String
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.centerAnimation = centerAnimation
self.legacyIcon = legacyIcon
}
public static func ==(lhs: Reaction, rhs: Reaction) -> Bool {
@ -621,6 +670,9 @@ public final class ReactionButtonComponent: Equatable {
if lhs.centerAnimation?.fileId != rhs.centerAnimation?.fileId {
return false
}
if lhs.legacyIcon?.fileId != rhs.legacyIcon?.fileId {
return false
}
return true
}
}

View File

@ -58,22 +58,51 @@ public func reactionStaticImage(context: AccountContext, animation: TelegramMedi
})
}
public final class ReactionImageNode: ASImageNode {
public final class ReactionImageNode: ASDisplayNode {
private var disposable: Disposable?
public let size: CGSize
private let size: CGSize
private let isAnimation: Bool
private let iconNode: ASImageNode
public init(context: AccountContext, availableReactions: AvailableReactions?, reaction: String) {
self.iconNode = ASImageNode()
var file: TelegramMediaFile?
var animationFile: TelegramMediaFile?
if let availableReactions = availableReactions {
for availableReaction in availableReactions.reactions {
if availableReaction.value == reaction {
file = availableReaction.staticIcon
animationFile = availableReaction.centerAnimation
break
}
}
}
if let file = file {
self.size = file.dimensions?.cgSize ?? CGSize(width: 18.0, height: 18.0)
if let animationFile = animationFile {
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()
@ -85,60 +114,29 @@ public final class ReactionImageNode: ASImageNode {
if data.complete, let dataValue = try? Data(contentsOf: URL(fileURLWithPath: data.path)) {
if let image = WebP.convert(fromWebP: dataValue) {
strongSelf.image = image
strongSelf.iconNode.image = image
}
}
})
} else {
self.size = CGSize(width: 18.0, height: 18.0)
self.size = CGSize(width: 32.0, height: 32.0)
self.isAnimation = false
super.init()
}
self.addSubnode(self.iconNode)
}
deinit {
self.disposable?.dispose()
}
}
public final class ReactionFileImageNode: ASImageNode {
private let disposable = MetaDisposable()
private var currentFile: TelegramMediaFile?
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
}
}
}))
}
}
})
public func update(size: CGSize) {
var imageSize = self.size.aspectFitted(size)
if self.isAnimation {
imageSize.width *= 2.0
imageSize.height *= 2.0
}
self.iconNode.frame = CGRect(origin: CGPoint(x: floor((size.width - imageSize.width) / 2.0), y: floor((size.height - imageSize.height) / 2.0)), size: imageSize)
}
}

View File

@ -8,7 +8,6 @@ import TelegramCore
import AccountContext
import TelegramPresentationData
import UIKit
import WebPBinding
import AnimatedAvatarSetNode
import ContextUI
import AvatarNode
@ -168,8 +167,7 @@ public final class ReactionListContextMenuContent: ContextControllerItemsContent
let iconSpacing: CGFloat = 4.0
var iconSize = CGSize(width: 22.0, height: 22.0)
if let reactionIconNode = self.reactionIconNode {
iconSize = reactionIconNode.size.aspectFitted(iconSize)
if let _ = self.reactionIconNode {
} else if let iconNode = self.iconNode, let image = iconNode.image {
iconSize = image.size.aspectFitted(iconSize)
}
@ -183,6 +181,7 @@ public final class ReactionListContextMenuContent: ContextControllerItemsContent
if let reactionIconNode = self.reactionIconNode {
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 {
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)
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.update(size: reactionSize)
}
self.separatorNode.frame = CGRect(origin: CGPoint(x: 0.0, y: size.height), size: CGSize(width: size.width, height: UIScreenPixel))

View File

@ -1244,9 +1244,9 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
}
}
func animateOutToReaction(value: String, targetView: UIView, hideNode: Bool, animateTargetContainer: UIView?, completion: @escaping () -> Void) {
func animateOutToReaction(value: String, targetView: UIView, hideNode: Bool, animateTargetContainer: UIView?, addStandaloneReactionAnimation: ((StandaloneReactionAnimation) -> Void)?, completion: @escaping () -> Void) {
if let presentationNode = self.presentationNode {
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
}
@ -1264,7 +1264,7 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
self.reactionContextNodeIsAnimatingOut = true
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 {
return
}
@ -2375,10 +2375,10 @@ public final class ContextController: ViewController, StandalonePresentableContr
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 {
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)
completion?()
})

View File

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

View File

@ -675,7 +675,7 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo
}
}
func animateOutToReaction(value: String, targetView: UIView, hideNode: Bool, animateTargetContainer: UIView?, completion: @escaping () -> Void) {
func animateOutToReaction(value: String, targetView: UIView, hideNode: Bool, animateTargetContainer: UIView?, addStandaloneReactionAnimation: ((StandaloneReactionAnimation) -> Void)?, completion: @escaping () -> Void) {
guard let reactionContextNode = self.reactionContextNode else {
self.requestAnimateOut(.default, completion)
return
@ -697,7 +697,7 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo
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 {
return
}

View File

@ -6,6 +6,7 @@ import TelegramPresentationData
import TextSelectionNode
import TelegramCore
import SwiftSignalKit
import ReactionSelectionNode
enum ContextControllerPresentationNodeStateTransition {
case animateIn
@ -24,7 +25,7 @@ protocol ContextControllerPresentationNode: ASDisplayNode {
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 highlightGestureMoved(location: CGPoint)

View File

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

View File

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

View File

@ -13,7 +13,8 @@ import AccountContext
public class ItemListReactionItem: ListViewItem, ItemListItem {
let context: AccountContext
let presentationData: ItemListPresentationData
let file: TelegramMediaFile?
let availableReactions: AvailableReactions?
let reaction: String
let title: String
let value: Bool
let enabled: Bool
@ -25,7 +26,8 @@ public class ItemListReactionItem: ListViewItem, ItemListItem {
public init(
context: AccountContext,
presentationData: ItemListPresentationData,
file: TelegramMediaFile?,
availableReactions: AvailableReactions?,
reaction: String,
title: String,
value: Bool,
enabled: Bool = true,
@ -36,7 +38,8 @@ public class ItemListReactionItem: ListViewItem, ItemListItem {
) {
self.context = context
self.presentationData = presentationData
self.file = file
self.availableReactions = availableReactions
self.reaction = reaction
self.title = title
self.value = value
self.enabled = enabled
@ -122,7 +125,7 @@ public class ItemListReactionItemNode: ListViewItemNode, ItemListItemNode {
private let highlightedBackgroundNode: ASDisplayNode
private let maskNode: ASImageNode
private let imageNode: ReactionFileImageNode
private var imageNode: ReactionImageNode?
private let titleNode: TextNode
private var switchNode: ASDisplayNode & ItemListSwitchNodeImpl
private let switchGestureNode: ASDisplayNode
@ -150,8 +153,6 @@ public class ItemListReactionItemNode: ListViewItemNode, ItemListItemNode {
self.bottomStripeNode = ASDisplayNode()
self.bottomStripeNode.isLayerBacked = true
self.imageNode = ReactionFileImageNode()
self.titleNode = TextNode()
self.titleNode.isUserInteractionEnabled = false
@ -166,7 +167,6 @@ public class ItemListReactionItemNode: ListViewItemNode, ItemListItemNode {
super.init(layerBacked: false, dynamicBounce: false)
self.addSubnode(self.imageNode)
self.addSubnode(self.titleNode)
self.addSubnode(self.switchNode)
self.addSubnode(self.switchGestureNode)
@ -191,7 +191,6 @@ public class ItemListReactionItemNode: ListViewItemNode, ItemListItemNode {
}
func asyncLayout() -> (_ item: ItemListReactionItem, _ params: ListViewItemLayoutParams, _ insets: ItemListNeighbors) -> (ListViewItemNodeLayout, (Bool) -> Void) {
let makeImageLayout = self.imageNode.asyncLayout()
let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
let currentItem = self.item
@ -227,8 +226,6 @@ public class ItemListReactionItemNode: ListViewItemNode, ItemListItemNode {
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()))
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))
}
let imageFitSize = imageSize.aspectFitted(CGSize(width: 30.0, height: 30.0))
strongSelf.imageNode.frame = CGRect(origin: CGPoint(x: params.leftInset + floor(sideImageInset - imageFitSize.width), y: floor((contentSize.height - imageFitSize.height) / 2.0)), size: imageFitSize)
imageApply()
if strongSelf.imageNode == nil, let availableReactions = item.availableReactions {
let imageNode = ReactionImageNode(context: item.context, availableReactions: availableReactions, reaction: item.reaction)
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)
if let switchView = strongSelf.switchNode.view as? UISwitch {

View File

@ -45,7 +45,7 @@ private enum PeerAllowedReactionListControllerEntry: ItemListNodeEntry {
case allowAllInfo(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 {
switch self {
@ -64,7 +64,7 @@ private enum PeerAllowedReactionListControllerEntry: ItemListNodeEntry {
return .allowAllInfo
case .itemsHeader:
return .itemsHeader
case let .item(_, value, _, _, _):
case let .item(_, value, _, _, _, _):
return .item(value)
}
}
@ -77,7 +77,7 @@ private enum PeerAllowedReactionListControllerEntry: ItemListNodeEntry {
return 1
case .itemsHeader:
return 2
case let .item(index, _, _, _, _):
case let .item(index, _, _, _, _, _):
return 100 + index
}
}
@ -102,8 +102,8 @@ private enum PeerAllowedReactionListControllerEntry: ItemListNodeEntry {
} else {
return false
}
case let .item(index, value, file, text, isEnabled):
if case .item(index, value, file, text, isEnabled) = rhs {
case let .item(index, value, availableReactions, reaction, text, isEnabled):
if case .item(index, value, availableReactions, reaction, text, isEnabled) = rhs {
return true
} else {
return false
@ -126,11 +126,12 @@ private enum PeerAllowedReactionListControllerEntry: ItemListNodeEntry {
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
case let .itemsHeader(text):
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(
context: arguments.context,
presentationData: presentationData,
file: file,
availableReactions: availableReactions,
reaction: reaction,
title: text,
value: isEnabled,
sectionId: self.section,
@ -172,7 +173,7 @@ private func peerAllowedReactionListControllerEntries(
if !availableReaction.isEnabled {
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
}
}

View File

@ -67,7 +67,6 @@ final class ReactionContextBackgroundNode: ASDisplayNode {
self.backgroundLayer.backgroundColor = UIColor.black.cgColor
self.backgroundLayer.masksToBounds = true
self.backgroundLayer.cornerRadius = 52.0 / 2.0
self.largeCircleLayer.backgroundColor = UIColor.black.cgColor
self.largeCircleLayer.masksToBounds = true
@ -114,6 +113,7 @@ final class ReactionContextBackgroundNode: ASDisplayNode {
size: CGSize,
cloudSourcePoint: CGFloat,
isLeftAligned: Bool,
isMinimized: Bool,
transition: ContainedViewLayoutTransition
) {
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 smallCircleFrame: CGRect

View File

@ -21,20 +21,26 @@ public final class ReactionContextItem {
public let appearAnimation: TelegramMediaFile
public let stillAnimation: TelegramMediaFile
public let listAnimation: TelegramMediaFile
public let largeListAnimation: TelegramMediaFile
public let applicationAnimation: TelegramMediaFile
public let largeApplicationAnimation: TelegramMediaFile
public init(
reaction: ReactionContextItem.Reaction,
appearAnimation: TelegramMediaFile,
stillAnimation: TelegramMediaFile,
listAnimation: TelegramMediaFile,
applicationAnimation: TelegramMediaFile
largeListAnimation: TelegramMediaFile,
applicationAnimation: TelegramMediaFile,
largeApplicationAnimation: TelegramMediaFile
) {
self.reaction = reaction
self.appearAnimation = appearAnimation
self.stillAnimation = stillAnimation
self.listAnimation = listAnimation
self.largeListAnimation = largeListAnimation
self.applicationAnimation = applicationAnimation
self.largeApplicationAnimation = largeApplicationAnimation
}
}
@ -56,8 +62,8 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
private weak var currentLongPressItemNode: ReactionNode?
private var isExpanded: Bool = true
private var highlightedReaction: ReactionContextItem.Reaction?
private var didTriggerExpandedReaction: Bool = false
private var continuousHaptic: Any?
private var validLayout: (CGSize, UIEdgeInsets, CGRect)?
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)
}
var visualRect = rect
let visualRect = rect
if self.highlightedReaction != nil {
/*if self.highlightedReaction != nil {
visualRect.origin.x -= 4.0
visualRect.size.width += 8.0
}
}*/
return (rect, visualRect, isLeftAligned, cloudSourcePoint)
}
@ -205,52 +211,96 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
let sideInset: CGFloat = 11.0
let itemSpacing: CGFloat = 9.0
let itemSize: CGFloat = 40.0
let verticalInset: CGFloat = 13.0
let rowHeight: CGFloat = 30.0
let visibleBounds = self.scrollNode.view.bounds
let appearBounds = self.scrollNode.view.bounds.insetBy(dx: 16.0, dy: 0.0)
let containerHeight: CGFloat = 52.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
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 })
var validIndices = Set<Int>()
var nextX: CGFloat = sideInset
for i in 0 ..< self.items.count {
let columnIndex = i
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))
var currentItemSize = itemSize
if let highlightedReactionIndex = highlightedReactionIndex {
if i > highlightedReactionIndex {
baseItemFrame.origin.x += 8.0
} else if i == highlightedReactionIndex {
baseItemFrame.origin.x += 4.0
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 {
baseItemFrame.origin.x += highlightItemOffset// - highlightItemSpacing * CGFloat(indexDistance)
} else if i == highlightedReactionIndex {
//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)) {
validIndices.insert(i)
var itemFrame = baseItemFrame
let itemFrame = baseItemFrame
var isPreviewing = false
if self.highlightedReaction == self.items[i].reaction {
let updatedSize = CGSize(width: floor(itemFrame.width * 1.66), height: floor(itemFrame.height * 1.66))
itemFrame = CGRect(origin: CGPoint(x: itemFrame.midX - updatedSize.width / 2.0, y: itemFrame.maxY + 4.0 - updatedSize.height), size: updatedSize)
//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)
isPreviewing = true
} else if self.highlightedReaction != nil {
let updatedSize = CGSize(width: floor(itemFrame.width * 0.9), height: floor(itemFrame.height * 0.9))
itemFrame = CGRect(origin: CGPoint(x: itemFrame.midX - updatedSize.width / 2.0, y: itemFrame.midY - updatedSize.height / 2.0), size: updatedSize)
//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)
}
var animateIn = false
let itemNode: ReactionNode
var itemTransition = transition
if let current = self.visibleItemNodes[i] {
itemNode = current
} else {
animateIn = self.didAnimateIn
itemTransition = .immediate
itemNode = ReactionNode(context: self.context, theme: self.theme, item: self.items[i])
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 {
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 {
itemNode.appear(animated: !self.context.sharedContext.currentPresentationData.with({ $0 }).reduceMotion)
@ -337,6 +387,7 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
size: visualBackgroundFrame.size,
cloudSourcePoint: cloudSourcePoint - visualBackgroundFrame.minX,
isLeftAligned: isLeftAligned,
isMinimized: self.highlightedReaction != nil,
transition: transition
)
@ -410,15 +461,6 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
}
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 {
completion()
return
@ -454,6 +496,9 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
if hideNode {
targetView.alpha = 1.0
targetView.isHidden = false
if let targetView = targetView as? ReactionIconView {
targetView.imageView.alpha = 1.0
}
targetSnapshotView?.isHidden = true
targetScaleCompleted = true
intermediateCompletion()
@ -475,86 +520,158 @@ 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 {
if itemNode.item.reaction.rawValue != value {
continue
if itemNode.item.reaction.rawValue == value {
foundItemNode = itemNode
break
}
self.animationTargetView = targetView
self.animationHideNode = hideNode
if hideNode {
if let animateTargetContainer = animateTargetContainer {
animateTargetContainer.isHidden = true
targetView.isHidden = true
} else {
targetView.alpha = 0.0
targetView.layer.animateAlpha(from: targetView.alpha, to: 0.0, duration: 0.2, completion: { [weak targetView] completed in
if completed {
targetView?.isHidden = true
}
})
}
guard let itemNode = foundItemNode else {
completion()
return
}
self.animationTargetView = targetView
self.animationHideNode = hideNode
if hideNode {
if let animateTargetContainer = animateTargetContainer {
animateTargetContainer.isHidden = true
targetView.isHidden = true
} else {
targetView.alpha = 0.0
targetView.layer.animateAlpha(from: targetView.alpha, to: 0.0, duration: 0.2, completion: { [weak targetView] completed in
if completed {
targetView?.isHidden = true
}
})
}
}
itemNode.isExtracted = true
let selfSourceRect = itemNode.view.convert(itemNode.view.bounds, to: self.view)
let selfTargetRect = self.view.convert(targetView.bounds, from: targetView)
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 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)
self.addSubnode(itemNode)
itemNode.position = expandedFrame.center
transition.updateBounds(node: itemNode, bounds: CGRect(origin: CGPoint(), size: expandedFrame.size))
itemNode.updateLayout(size: expandedFrame.size, isExpanded: true, largeExpanded: self.didTriggerExpandedReaction, isPreviewing: false, transition: transition)
let additionalAnimationNode = AnimatedStickerNode()
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.updateLayout(size: effectFrame.size)
self.addSubnode(additionalAnimationNode)
var mainAnimationCompleted = false
var additionalAnimationCompleted = false
let intermediateCompletion: () -> Void = {
if mainAnimationCompleted && additionalAnimationCompleted {
completion()
}
}
additionalAnimationNode.completed = { _ in
additionalAnimationCompleted = true
intermediateCompletion()
}
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
}
}
itemNode.isExtracted = true
let selfSourceRect = itemNode.view.convert(itemNode.view.bounds, to: self.view)
let selfTargetRect = self.view.convert(targetView.bounds, from: targetView)
let expandedSize: CGSize = selfTargetRect.size
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 transition: ContainedViewLayoutTransition = .animated(duration: 0.2, curve: .linear)
self.addSubnode(itemNode)
itemNode.position = expandedFrame.center
transition.updateBounds(node: itemNode, bounds: CGRect(origin: CGPoint(), size: expandedFrame.size))
itemNode.updateLayout(size: expandedFrame.size, isExpanded: true, isPreviewing: false, transition: transition)
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.frame = effectFrame
additionalAnimationNode.updateLayout(size: effectFrame.size)
self.addSubnode(additionalAnimationNode)
var mainAnimationCompleted = false
var additionalAnimationCompleted = false
let intermediateCompletion: () -> Void = {
if mainAnimationCompleted && additionalAnimationCompleted {
completion()
if strongSelf.didTriggerExpandedReaction {
return
}
}
additionalAnimationNode.completed = { _ in
additionalAnimationCompleted = true
intermediateCompletion()
}
transition.animatePositionWithKeyframes(node: itemNode, keyframes: generateParabollicMotionKeyframes(from: selfSourceRect.center, to: expandedFrame.center, elevation: 30.0))
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.15 * UIView.animationDurationFactor(), execute: {
additionalAnimationNode.visibility = true
if let animateTargetContainer = animateTargetContainer {
animateTargetContainer.isHidden = false
animateTargetContainer.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
animateTargetContainer.layer.animateScale(from: 0.01, to: 1.0, duration: 0.2)
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() + min(5.0, 2.0 * UIView.animationDurationFactor()), execute: {
self.animateFromItemNodeToReaction(itemNode: itemNode, targetView: targetView, hideNode: hideNode, completion: {
})
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.15 * UIView.animationDurationFactor(), execute: {
additionalAnimationNode.visibility = true
if let animateTargetContainer = animateTargetContainer {
animateTargetContainer.isHidden = false
animateTargetContainer.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
animateTargetContainer.layer.animateScale(from: 0.01, to: 1.0, duration: 0.2)
}
})
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + min(5.0, 2.0 * UIView.animationDurationFactor()), execute: {
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
intermediateCompletion()
})
})
return
}
completion()
} else {
if hideNode {
targetView.alpha = 1.0
targetView.isHidden = false
if let targetView = targetView as? ReactionIconView {
targetView.imageView.alpha = 1.0
itemNode.removeFromSupernode()
}
}
mainAnimationCompleted = true
intermediateCompletion()
}
})
}
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, *) {
self.continuousHaptic = try? ContinuousHaptic(duration: 2.5)
}
//itemNode.updateIsLongPressing(isLongPressing: true)
if self.hapticFeedback == nil {
self.hapticFeedback = HapticFeedback()
@ -587,11 +703,7 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
}
case .ended, .cancelled:
self.continuousHaptic = nil
if let itemNode = self.currentLongPressItemNode {
self.currentLongPressItemNode = nil
self.reactionSelected?(itemNode.item)
itemNode.updateIsLongPressing(isLongPressing: false)
}
self.didTriggerExpandedReaction = true
self.highlightGestureFinished(performAction: true)
default:
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)
}
}
@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 {
@ -762,13 +867,22 @@ public final class StandaloneReactionAnimation: ASDisplayNode {
if let targetView = targetView as? ReactionIconView {
self.itemNodeIsEmbedded = true
targetView.addSubnode(itemNode)
targetView.imageView.isHidden = true
} else {
self.addSubnode(itemNode)
if hideNode {
targetView.isHidden = true
}
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
} else {
if hideNode {
targetView.isHidden = true
}
}
}
@ -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()
additionalAnimationNode.setup(source: AnimatedStickerResourceSource(account: itemNode.context.account, resource: itemNode.item.applicationAnimation.resource), width: Int(effectFrame.width * 2.0), height: Int(effectFrame.height * 2.0), playbackMode: .once, mode: .direct(cachePathPrefix: itemNode.context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(itemNode.item.applicationAnimation.resource.id)))

View File

@ -58,6 +58,8 @@ final class ReactionNode: ASDisplayNode {
private var isLongPressing: Bool = false
private var longPressAnimator: DisplayLinkAnimator?
var expandedAnimationDidBegin: (() -> Void)?
init(context: AccountContext, theme: PresentationTheme, item: ReactionContextItem) {
self.context = context
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 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.addSubnode(animationNode)
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.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.frame = expandedAnimationFrame
animationNode.updateLayout(size: expandedAnimationFrame.size)

View File

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

View File

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

View File

@ -42,7 +42,6 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF
var currentCode: String {
return self.codeInputView.text
//return self.codeField.textField.text ?? ""
}
var loginWithCode: ((String) -> Void)?
@ -53,7 +52,6 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF
var inProgress: Bool = false {
didSet {
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.addSubnode(self.nextOptionTitleNode)
/*self.codeSeparatorNode = ASDisplayNode()
self.codeSeparatorNode.isLayerBacked = true
self.codeSeparatorNode.backgroundColor = self.theme.list.itemPlainSeparatorColor*/
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.textField.font = Font.regular(24.0)

View File

@ -1030,7 +1030,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
appearAnimation: reaction.appearAnimation,
stillAnimation: reaction.selectAnimation,
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
}
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 {
return
}
@ -1279,7 +1288,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
appearAnimation: reaction.appearAnimation,
stillAnimation: reaction.selectAnimation,
listAnimation: centerAnimation,
applicationAnimation: aroundAnimation
largeListAnimation: reaction.activateAnimation,
applicationAnimation: aroundAnimation,
largeApplicationAnimation: reaction.effectAnimation
),
targetView: targetView,
hideNode: true,

View File

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

View File

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

View File

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