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 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)])
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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))
|
||||
|
@ -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?()
|
||||
})
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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)))
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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)))
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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,
|
||||
|
@ -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)
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user