mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Reaction improvements
This commit is contained in:
parent
d3164fa4cd
commit
8ef10ee6c2
@ -11,88 +11,167 @@ import UIKit
|
|||||||
import WebPBinding
|
import WebPBinding
|
||||||
import AnimatedAvatarSetNode
|
import AnimatedAvatarSetNode
|
||||||
|
|
||||||
fileprivate final class CounterLayer: SimpleLayer {
|
public final class ReactionButtonAsyncNode: ContextControllerSourceNode {
|
||||||
fileprivate final class Layout {
|
fileprivate final class ContainerButtonNode: HighlightTrackingButtonNode {
|
||||||
struct Spec: Equatable {
|
struct Colors: Equatable {
|
||||||
let clippingHeight: CGFloat
|
var background: UInt32
|
||||||
var stringComponents: [String]
|
var foreground: UInt32
|
||||||
var backgroundColor: UInt32
|
var extractedBackground: UInt32
|
||||||
var foregroundColor: UInt32
|
var extractedForeground: UInt32
|
||||||
}
|
}
|
||||||
|
|
||||||
let spec: Spec
|
struct Counter: Equatable {
|
||||||
let size: CGSize
|
var frame: CGRect
|
||||||
|
var components: [CounterLayout.Component]
|
||||||
|
}
|
||||||
|
|
||||||
let image: UIImage
|
struct Layout: Equatable {
|
||||||
|
var colors: Colors
|
||||||
|
var baseSize: CGSize
|
||||||
|
var counter: Counter?
|
||||||
|
}
|
||||||
|
|
||||||
|
private var isExtracted: Bool = false
|
||||||
|
private var currentLayout: Layout?
|
||||||
|
|
||||||
|
init() {
|
||||||
|
super.init(pointerStyle: nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func update(layout: Layout) {
|
||||||
|
if self.currentLayout != layout {
|
||||||
|
self.currentLayout = layout
|
||||||
|
self.updateBackgroundImage(animated: false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateIsExtracted(isExtracted: Bool, animated: Bool) {
|
||||||
|
if self.isExtracted != isExtracted {
|
||||||
|
self.isExtracted = isExtracted
|
||||||
|
self.updateBackgroundImage(animated: animated)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func updateBackgroundImage(animated: Bool) {
|
||||||
|
guard let layout = self.currentLayout else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let image = generateImage(layout.baseSize, 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 let counter = layout.counter {
|
||||||
|
context.setBlendMode(foregroundColor.alpha < 1.0 ? .copy : .normal)
|
||||||
|
|
||||||
|
var totalComponentWidth: CGFloat = 0.0
|
||||||
|
for component in counter.components {
|
||||||
|
totalComponentWidth += component.bounds.width
|
||||||
|
}
|
||||||
|
|
||||||
|
var textOrigin: CGFloat = size.width - counter.frame.width - 8.0 + floorToScreenPixels((counter.frame.width - totalComponentWidth) / 2.0)
|
||||||
|
for component in counter.components {
|
||||||
|
let string = NSAttributedString(string: component.string, font: Font.medium(11.0), textColor: foregroundColor)
|
||||||
|
string.draw(at: component.bounds.origin.offsetBy(dx: textOrigin, dy: floorToScreenPixels(size.height - component.bounds.height) / 2.0))
|
||||||
|
textOrigin += component.bounds.width
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
UIGraphicsPopContext()
|
||||||
|
})?.stretchableImage(withLeftCapWidth: Int(layout.baseSize.height / 2.0), topCapHeight: Int(layout.baseSize.height / 2.0))
|
||||||
|
if let image = image {
|
||||||
|
let previousContents = self.layer.contents
|
||||||
|
|
||||||
|
ASDisplayNodeSetResizableContents(self.layer, image)
|
||||||
|
|
||||||
|
if animated, let previousContents = previousContents {
|
||||||
|
self.layer.animate(from: previousContents as! CGImage, to: image.cgImage!, keyPath: "contents", timingFunction: CAMediaTimingFunctionName.linear.rawValue, duration: 0.2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fileprivate final class CounterLayout {
|
||||||
|
struct Spec: Equatable {
|
||||||
|
var stringComponents: [String]
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Component: Equatable {
|
||||||
|
var string: String
|
||||||
|
var bounds: CGRect
|
||||||
|
}
|
||||||
|
|
||||||
|
private static let maxDigitWidth: CGFloat = {
|
||||||
|
var maxWidth: CGFloat = 0.0
|
||||||
|
for i in 0 ..< 9 {
|
||||||
|
let string = NSAttributedString(string: "\(i)", font: Font.medium(11.0), textColor: .black)
|
||||||
|
let boundingRect = string.boundingRect(with: CGSize(width: 100.0, height: 100.0), options: .usesLineFragmentOrigin, context: nil)
|
||||||
|
maxWidth = max(maxWidth, boundingRect.width)
|
||||||
|
}
|
||||||
|
return ceil(maxWidth)
|
||||||
|
}()
|
||||||
|
|
||||||
|
let spec: Spec
|
||||||
|
let components: [Component]
|
||||||
|
let size: CGSize
|
||||||
|
|
||||||
init(
|
init(
|
||||||
spec: Spec,
|
spec: Spec,
|
||||||
size: CGSize,
|
components: [Component],
|
||||||
image: UIImage
|
size: CGSize
|
||||||
) {
|
) {
|
||||||
self.spec = spec
|
self.spec = spec
|
||||||
|
self.components = components
|
||||||
self.size = size
|
self.size = size
|
||||||
self.image = image
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static func calculate(spec: Spec, previousLayout: Layout?) -> Layout {
|
static func calculate(spec: Spec, previousLayout: CounterLayout?) -> CounterLayout {
|
||||||
let image: UIImage
|
let size: CGSize
|
||||||
|
let components: [Component]
|
||||||
if let previousLayout = previousLayout, previousLayout.spec == spec {
|
if let previousLayout = previousLayout, previousLayout.spec == spec {
|
||||||
image = previousLayout.image
|
size = previousLayout.size
|
||||||
|
components = previousLayout.components
|
||||||
} else {
|
} else {
|
||||||
let textColor = UIColor(argb: spec.foregroundColor)
|
var resultSize = CGSize()
|
||||||
let string = NSAttributedString(string: spec.stringComponents.joined(separator: ""), font: Font.medium(11.0), textColor: textColor)
|
var resultComponents: [Component] = []
|
||||||
let boundingRect = string.boundingRect(with: CGSize(width: 100.0, height: 100.0), options: .usesLineFragmentOrigin, context: nil)
|
for component in spec.stringComponents {
|
||||||
image = generateImage(CGSize(width: boundingRect.size.width, height: spec.clippingHeight), rotatedContext: { size, context in
|
let string = NSAttributedString(string: component, font: Font.medium(11.0), textColor: .black)
|
||||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
let boundingRect = string.boundingRect(with: CGSize(width: 100.0, height: 100.0), options: .usesLineFragmentOrigin, context: nil)
|
||||||
/*context.setFillColor(UIColor(argb: spec.backgroundColor).cgColor)
|
|
||||||
context.fill(CGRect(origin: CGPoint(), size: size))
|
resultComponents.append(Component(string: component, bounds: boundingRect))
|
||||||
if textColor.alpha < 1.0 {
|
|
||||||
context.setBlendMode(.copy)
|
resultSize.width += CounterLayout.maxDigitWidth
|
||||||
}*/
|
resultSize.height = max(resultSize.height, boundingRect.height)
|
||||||
context.translateBy(x: 0.0, y: (size.height - boundingRect.size.height) / 2.0)
|
}
|
||||||
UIGraphicsPushContext(context)
|
size = CGSize(width: ceil(resultSize.width), height: ceil(resultSize.height))
|
||||||
string.draw(at: CGPoint())
|
components = resultComponents
|
||||||
UIGraphicsPopContext()
|
|
||||||
})!
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return Layout(
|
return CounterLayout(
|
||||||
spec: spec,
|
spec: spec,
|
||||||
size: image.size,
|
components: components,
|
||||||
image: image
|
size: size
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var layout: Layout?
|
|
||||||
|
|
||||||
override init(layer: Any) {
|
|
||||||
super.init(layer: layer)
|
|
||||||
}
|
|
||||||
|
|
||||||
override init() {
|
|
||||||
super.init()
|
|
||||||
|
|
||||||
self.masksToBounds = true
|
|
||||||
}
|
|
||||||
|
|
||||||
required init?(coder: NSCoder) {
|
|
||||||
fatalError("init(coder:) has not been implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
func apply(layout: Layout, animation: ListViewItemUpdateAnimation) {
|
|
||||||
/*if animation.isAnimated, let previousContents = self.contents {
|
|
||||||
self.animate(from: previousContents as! CGImage, to: layout.image.cgImage!, keyPath: "contents", timingFunction: CAMediaTimingFunctionName.linear.rawValue, duration: 0.2)
|
|
||||||
} else {*/
|
|
||||||
self.contents = layout.image.cgImage
|
|
||||||
//}
|
|
||||||
|
|
||||||
self.layout = layout
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public final class ReactionButtonAsyncNode: ContextControllerSourceNode {
|
|
||||||
fileprivate final class Layout {
|
fileprivate final class Layout {
|
||||||
struct Spec: Equatable {
|
struct Spec: Equatable {
|
||||||
var component: ReactionButtonComponent
|
var component: ReactionButtonComponent
|
||||||
@ -106,11 +185,12 @@ public final class ReactionButtonAsyncNode: ContextControllerSourceNode {
|
|||||||
|
|
||||||
let imageFrame: CGRect
|
let imageFrame: CGRect
|
||||||
|
|
||||||
let counter: CounterLayer.Layout?
|
let counterLayout: CounterLayout?
|
||||||
let counterFrame: CGRect?
|
let counterFrame: CGRect?
|
||||||
|
|
||||||
let backgroundImage: UIImage
|
let backgroundLayout: ContainerButtonNode.Layout
|
||||||
let extractedBackgroundImage: UIImage
|
//let backgroundImage: UIImage
|
||||||
|
//let extractedBackgroundImage: UIImage
|
||||||
|
|
||||||
let size: CGSize
|
let size: CGSize
|
||||||
|
|
||||||
@ -120,10 +200,11 @@ public final class ReactionButtonAsyncNode: ContextControllerSourceNode {
|
|||||||
clippingHeight: CGFloat,
|
clippingHeight: CGFloat,
|
||||||
sideInsets: CGFloat,
|
sideInsets: CGFloat,
|
||||||
imageFrame: CGRect,
|
imageFrame: CGRect,
|
||||||
counter: CounterLayer.Layout?,
|
counterLayout: CounterLayout?,
|
||||||
counterFrame: CGRect?,
|
counterFrame: CGRect?,
|
||||||
backgroundImage: UIImage,
|
backgroundLayout: ContainerButtonNode.Layout,
|
||||||
extractedBackgroundImage: UIImage,
|
//backgroundImage: UIImage,
|
||||||
|
//extractedBackgroundImage: UIImage,
|
||||||
size: CGSize
|
size: CGSize
|
||||||
) {
|
) {
|
||||||
self.spec = spec
|
self.spec = spec
|
||||||
@ -131,14 +212,15 @@ public final class ReactionButtonAsyncNode: ContextControllerSourceNode {
|
|||||||
self.clippingHeight = clippingHeight
|
self.clippingHeight = clippingHeight
|
||||||
self.sideInsets = sideInsets
|
self.sideInsets = sideInsets
|
||||||
self.imageFrame = imageFrame
|
self.imageFrame = imageFrame
|
||||||
self.counter = counter
|
self.counterLayout = counterLayout
|
||||||
self.counterFrame = counterFrame
|
self.counterFrame = counterFrame
|
||||||
self.backgroundImage = backgroundImage
|
self.backgroundLayout = backgroundLayout
|
||||||
self.extractedBackgroundImage = extractedBackgroundImage
|
//self.backgroundImage = backgroundImage
|
||||||
|
//self.extractedBackgroundImage = extractedBackgroundImage
|
||||||
self.size = size
|
self.size = size
|
||||||
}
|
}
|
||||||
|
|
||||||
static func calculate(spec: Spec, currentLayout: Layout?, currentCounter: CounterLayer.Layout?) -> Layout {
|
static func calculate(spec: Spec, currentLayout: Layout?) -> Layout {
|
||||||
let clippingHeight: CGFloat = 22.0
|
let clippingHeight: CGFloat = 22.0
|
||||||
let sideInsets: CGFloat = 8.0
|
let sideInsets: CGFloat = 8.0
|
||||||
let height: CGFloat = 30.0
|
let height: CGFloat = 30.0
|
||||||
@ -161,7 +243,7 @@ public final class ReactionButtonAsyncNode: ContextControllerSourceNode {
|
|||||||
|
|
||||||
let imageFrame = CGRect(origin: CGPoint(x: sideInsets, y: floorToScreenPixels((height - imageSize.height) / 2.0)), size: imageSize)
|
let imageFrame = CGRect(origin: CGPoint(x: sideInsets, y: floorToScreenPixels((height - imageSize.height) / 2.0)), size: imageSize)
|
||||||
|
|
||||||
var previousDisplayCounter: String?
|
/*var previousDisplayCounter: String?
|
||||||
if let currentLayout = currentLayout {
|
if let currentLayout = currentLayout {
|
||||||
if currentLayout.spec.component.avatarPeers.isEmpty {
|
if currentLayout.spec.component.avatarPeers.isEmpty {
|
||||||
previousDisplayCounter = countString(Int64(spec.component.count))
|
previousDisplayCounter = countString(Int64(spec.component.count))
|
||||||
@ -170,9 +252,9 @@ public final class ReactionButtonAsyncNode: ContextControllerSourceNode {
|
|||||||
var currentDisplayCounter: String?
|
var currentDisplayCounter: String?
|
||||||
if spec.component.avatarPeers.isEmpty {
|
if spec.component.avatarPeers.isEmpty {
|
||||||
currentDisplayCounter = countString(Int64(spec.component.count))
|
currentDisplayCounter = countString(Int64(spec.component.count))
|
||||||
}
|
}*/
|
||||||
|
|
||||||
let backgroundImage: UIImage
|
/*let backgroundImage: UIImage
|
||||||
let extractedBackgroundImage: UIImage
|
let extractedBackgroundImage: UIImage
|
||||||
if let currentLayout = currentLayout, currentLayout.spec.component.isSelected == spec.component.isSelected, currentLayout.spec.component.colors == spec.component.colors, previousDisplayCounter == currentDisplayCounter {
|
if let currentLayout = currentLayout, currentLayout.spec.component.isSelected == spec.component.isSelected, currentLayout.spec.component.colors == spec.component.colors, previousDisplayCounter == currentDisplayCounter {
|
||||||
backgroundImage = currentLayout.backgroundImage
|
backgroundImage = currentLayout.backgroundImage
|
||||||
@ -228,9 +310,9 @@ public final class ReactionButtonAsyncNode: ContextControllerSourceNode {
|
|||||||
|
|
||||||
UIGraphicsPopContext()
|
UIGraphicsPopContext()
|
||||||
})!.stretchableImage(withLeftCapWidth: Int(height / 2.0), topCapHeight: Int(height / 2.0))
|
})!.stretchableImage(withLeftCapWidth: Int(height / 2.0), topCapHeight: Int(height / 2.0))
|
||||||
}
|
}*/
|
||||||
|
|
||||||
var counter: CounterLayer.Layout?
|
var counterLayout: CounterLayout?
|
||||||
var counterFrame: CGRect?
|
var counterFrame: CGRect?
|
||||||
|
|
||||||
var size = CGSize(width: imageSize.width + sideInsets * 2.0, height: height)
|
var size = CGSize(width: imageSize.width + sideInsets * 2.0, height: height)
|
||||||
@ -242,36 +324,53 @@ public final class ReactionButtonAsyncNode: ContextControllerSourceNode {
|
|||||||
size.width -= 2.0
|
size.width -= 2.0
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let counterSpec = CounterLayer.Layout.Spec(
|
let counterSpec = CounterLayout.Spec(
|
||||||
clippingHeight: clippingHeight,
|
stringComponents: counterComponents
|
||||||
stringComponents: counterComponents,
|
|
||||||
backgroundColor: backgroundColor,
|
|
||||||
foregroundColor: spec.component.isSelected ? spec.component.colors.selectedForeground : spec.component.colors.deselectedForeground
|
|
||||||
)
|
)
|
||||||
let counterValue: CounterLayer.Layout
|
let counterValue: CounterLayout
|
||||||
if let currentCounter = currentCounter, currentCounter.spec == counterSpec {
|
if let currentCounter = currentLayout?.counterLayout, currentCounter.spec == counterSpec {
|
||||||
counterValue = currentCounter
|
counterValue = currentCounter
|
||||||
} else {
|
} else {
|
||||||
counterValue = CounterLayer.Layout.calculate(
|
counterValue = CounterLayout.calculate(
|
||||||
spec: counterSpec,
|
spec: counterSpec,
|
||||||
previousLayout: currentCounter
|
previousLayout: currentLayout?.counterLayout
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
counter = counterValue
|
counterLayout = counterValue
|
||||||
size.width += spacing + counterValue.size.width
|
size.width += spacing + counterValue.size.width
|
||||||
counterFrame = CGRect(origin: CGPoint(x: sideInsets + imageSize.width + spacing, y: floorToScreenPixels((height - counterValue.size.height) / 2.0)), size: counterValue.size)
|
counterFrame = CGRect(origin: CGPoint(x: size.width - sideInsets - counterValue.size.width, y: floorToScreenPixels((height - counterValue.size.height) / 2.0)), size: counterValue.size)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let backgroundColors = ReactionButtonAsyncNode.ContainerButtonNode.Colors(
|
||||||
|
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
|
||||||
|
)
|
||||||
|
var backgroundCounter: ReactionButtonAsyncNode.ContainerButtonNode.Counter?
|
||||||
|
if let counterLayout = counterLayout, let counterFrame = counterFrame {
|
||||||
|
backgroundCounter = ReactionButtonAsyncNode.ContainerButtonNode.Counter(
|
||||||
|
frame: counterFrame,
|
||||||
|
components: counterLayout.components
|
||||||
|
)
|
||||||
|
}
|
||||||
|
let backgroundLayout = ContainerButtonNode.Layout(
|
||||||
|
colors: backgroundColors,
|
||||||
|
baseSize: CGSize(width: height + 18.0, height: height),
|
||||||
|
counter: backgroundCounter
|
||||||
|
)
|
||||||
|
|
||||||
return Layout(
|
return Layout(
|
||||||
spec: spec,
|
spec: spec,
|
||||||
backgroundColor: backgroundColor,
|
backgroundColor: backgroundColor,
|
||||||
clippingHeight: clippingHeight,
|
clippingHeight: clippingHeight,
|
||||||
sideInsets: sideInsets,
|
sideInsets: sideInsets,
|
||||||
imageFrame: imageFrame,
|
imageFrame: imageFrame,
|
||||||
counter: counter,
|
counterLayout: counterLayout,
|
||||||
counterFrame: counterFrame,
|
counterFrame: counterFrame,
|
||||||
backgroundImage: backgroundImage,
|
backgroundLayout: backgroundLayout,
|
||||||
extractedBackgroundImage: extractedBackgroundImage,
|
//backgroundImage: backgroundImage,
|
||||||
|
//extractedBackgroundImage: extractedBackgroundImage,
|
||||||
size: size
|
size: size
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -280,16 +379,15 @@ public final class ReactionButtonAsyncNode: ContextControllerSourceNode {
|
|||||||
private var layout: Layout?
|
private var layout: Layout?
|
||||||
|
|
||||||
public let containerNode: ContextExtractedContentContainingNode
|
public let containerNode: ContextExtractedContentContainingNode
|
||||||
private let buttonNode: HighlightTrackingButtonNode
|
private let buttonNode: ContainerButtonNode
|
||||||
public let iconView: UIImageView
|
public let iconView: UIImageView
|
||||||
private var counterLayer: CounterLayer?
|
|
||||||
private var avatarsView: AnimatedAvatarSetView?
|
private var avatarsView: AnimatedAvatarSetView?
|
||||||
|
|
||||||
private let iconImageDisposable = MetaDisposable()
|
private let iconImageDisposable = MetaDisposable()
|
||||||
|
|
||||||
override init() {
|
override init() {
|
||||||
self.containerNode = ContextExtractedContentContainingNode()
|
self.containerNode = ContextExtractedContentContainingNode()
|
||||||
self.buttonNode = HighlightTrackingButtonNode()
|
self.buttonNode = ContainerButtonNode()
|
||||||
|
|
||||||
self.iconView = UIImageView()
|
self.iconView = UIImageView()
|
||||||
self.iconView.isUserInteractionEnabled = false
|
self.iconView.isUserInteractionEnabled = false
|
||||||
@ -317,25 +415,20 @@ public final class ReactionButtonAsyncNode: ContextControllerSourceNode {
|
|||||||
self.isGestureEnabled = true
|
self.isGestureEnabled = true
|
||||||
|
|
||||||
self.containerNode.willUpdateIsExtractedToContextPreview = { [weak self] isExtracted, _ in
|
self.containerNode.willUpdateIsExtractedToContextPreview = { [weak self] isExtracted, _ in
|
||||||
guard let strongSelf = self, let layout = strongSelf.layout else {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
strongSelf.buttonNode.updateIsExtracted(isExtracted: isExtracted, animated: true)
|
||||||
|
|
||||||
let backgroundImage = isExtracted ? layout.extractedBackgroundImage : layout.backgroundImage
|
/*let backgroundImage = isExtracted ? layout.extractedBackgroundImage : layout.backgroundImage
|
||||||
|
|
||||||
let previousContents = strongSelf.buttonNode.layer.contents
|
let previousContents = strongSelf.buttonNode.layer.contents
|
||||||
|
|
||||||
let backgroundCapInsets = backgroundImage.capInsets
|
ASDisplayNodeSetResizableContents(strongSelf.buttonNode.layer, backgroundImage)
|
||||||
if backgroundCapInsets.left.isZero && backgroundCapInsets.top.isZero {
|
|
||||||
strongSelf.buttonNode.layer.contentsScale = backgroundImage.scale
|
|
||||||
strongSelf.buttonNode.layer.contents = backgroundImage.cgImage
|
|
||||||
} else {
|
|
||||||
ASDisplayNodeSetResizableContents(strongSelf.buttonNode.layer, backgroundImage)
|
|
||||||
}
|
|
||||||
|
|
||||||
if let previousContents = previousContents {
|
if let previousContents = previousContents {
|
||||||
strongSelf.buttonNode.layer.animate(from: previousContents as! CGImage, to: backgroundImage.cgImage!, keyPath: "contents", timingFunction: CAMediaTimingFunctionName.linear.rawValue, duration: 0.2)
|
strongSelf.buttonNode.layer.animate(from: previousContents as! CGImage, to: backgroundImage.cgImage!, keyPath: "contents", timingFunction: CAMediaTimingFunctionName.linear.rawValue, duration: 0.2)
|
||||||
}
|
}*/
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -360,13 +453,8 @@ public final class ReactionButtonAsyncNode: ContextControllerSourceNode {
|
|||||||
self.containerNode.contentRect = CGRect(origin: CGPoint(), size: layout.size)
|
self.containerNode.contentRect = CGRect(origin: CGPoint(), size: layout.size)
|
||||||
animation.animator.updateFrame(layer: self.buttonNode.layer, frame: CGRect(origin: CGPoint(), size: layout.size), completion: nil)
|
animation.animator.updateFrame(layer: self.buttonNode.layer, frame: CGRect(origin: CGPoint(), size: layout.size), completion: nil)
|
||||||
|
|
||||||
let backgroundCapInsets = layout.backgroundImage.capInsets
|
//ASDisplayNodeSetResizableContents(self.buttonNode.layer, layout.backgroundImage)
|
||||||
if backgroundCapInsets.left.isZero && backgroundCapInsets.top.isZero {
|
self.buttonNode.update(layout: layout.backgroundLayout)
|
||||||
self.buttonNode.layer.contentsScale = layout.backgroundImage.scale
|
|
||||||
self.buttonNode.layer.contents = layout.backgroundImage.cgImage
|
|
||||||
} else {
|
|
||||||
ASDisplayNodeSetResizableContents(self.buttonNode.layer, layout.backgroundImage)
|
|
||||||
}
|
|
||||||
|
|
||||||
animation.animator.updateFrame(layer: self.iconView.layer, frame: layout.imageFrame, completion: nil)
|
animation.animator.updateFrame(layer: self.iconView.layer, frame: layout.imageFrame, completion: nil)
|
||||||
|
|
||||||
@ -387,33 +475,6 @@ public final class ReactionButtonAsyncNode: ContextControllerSourceNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let counter = layout.counter, let counterFrame = layout.counterFrame {
|
|
||||||
let counterLayer: CounterLayer
|
|
||||||
var counterAnimation = animation
|
|
||||||
if let current = self.counterLayer {
|
|
||||||
counterLayer = current
|
|
||||||
} else {
|
|
||||||
counterAnimation = .None
|
|
||||||
counterLayer = CounterLayer()
|
|
||||||
self.counterLayer = counterLayer
|
|
||||||
//self.layer.addSublayer(counterLayer)
|
|
||||||
if animation.isAnimated {
|
|
||||||
counterLayer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
counterAnimation.animator.updateFrame(layer: counterLayer, frame: counterFrame, completion: nil)
|
|
||||||
counterLayer.apply(layout: counter, animation: counterAnimation)
|
|
||||||
} else if let counterLayer = self.counterLayer {
|
|
||||||
self.counterLayer = nil
|
|
||||||
if animation.isAnimated {
|
|
||||||
animation.animator.updateAlpha(layer: counterLayer, alpha: 0.0, completion: { [weak counterLayer] _ in
|
|
||||||
counterLayer?.removeFromSuperlayer()
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
counterLayer.removeFromSuperlayer()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !layout.spec.component.avatarPeers.isEmpty {
|
if !layout.spec.component.avatarPeers.isEmpty {
|
||||||
let avatarsView: AnimatedAvatarSetView
|
let avatarsView: AnimatedAvatarSetView
|
||||||
if let current = self.avatarsView {
|
if let current = self.avatarsView {
|
||||||
@ -459,7 +520,7 @@ public final class ReactionButtonAsyncNode: ContextControllerSourceNode {
|
|||||||
if let currentLayout = currentLayout, currentLayout.spec == spec {
|
if let currentLayout = currentLayout, currentLayout.spec == spec {
|
||||||
layout = currentLayout
|
layout = currentLayout
|
||||||
} else {
|
} else {
|
||||||
layout = Layout.calculate(spec: spec, currentLayout: currentLayout, currentCounter: currentLayout?.counter)
|
layout = Layout.calculate(spec: spec, currentLayout: currentLayout)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (size: layout.size, apply: { animation in
|
return (size: layout.size, apply: { animation in
|
||||||
|
@ -176,6 +176,10 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
cloudSourcePoint = max(rect.minX + rect.height / 2.0, anchorRect.minX)
|
cloudSourcePoint = max(rect.minX + rect.height / 2.0, anchorRect.minX)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if self.highlightedReaction != nil {
|
||||||
|
rect.origin.x -= 2.0
|
||||||
|
}
|
||||||
|
|
||||||
return (rect, isLeftAligned, cloudSourcePoint)
|
return (rect, isLeftAligned, cloudSourcePoint)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -193,6 +197,8 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
let visibleBounds = self.scrollNode.view.bounds
|
let visibleBounds = self.scrollNode.view.bounds
|
||||||
self.previewingItemContainer.bounds = visibleBounds
|
self.previewingItemContainer.bounds = visibleBounds
|
||||||
|
|
||||||
|
let highlightedReactionIndex = self.items.firstIndex(where: { $0.reaction == self.highlightedReaction })
|
||||||
|
|
||||||
var validIndices = Set<Int>()
|
var validIndices = Set<Int>()
|
||||||
for i in 0 ..< self.items.count {
|
for i in 0 ..< self.items.count {
|
||||||
let columnIndex = i
|
let columnIndex = i
|
||||||
@ -200,15 +206,24 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
|
|
||||||
let itemOffsetY: CGFloat = -1.0
|
let itemOffsetY: CGFloat = -1.0
|
||||||
|
|
||||||
let baseItemFrame = CGRect(origin: CGPoint(x: sideInset + column * (itemSize + itemSpacing), y: verticalInset + floor((rowHeight - itemSize) / 2.0) + itemOffsetY), size: CGSize(width: itemSize, height: itemSize))
|
var baseItemFrame = CGRect(origin: CGPoint(x: sideInset + column * (itemSize + itemSpacing), y: verticalInset + floor((rowHeight - itemSize) / 2.0) + itemOffsetY), size: CGSize(width: itemSize, height: itemSize))
|
||||||
|
if let highlightedReactionIndex = highlightedReactionIndex {
|
||||||
|
if i > highlightedReactionIndex {
|
||||||
|
baseItemFrame.origin.x += 4.0
|
||||||
|
} else if i == highlightedReactionIndex {
|
||||||
|
baseItemFrame.origin.x += 2.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if visibleBounds.intersects(baseItemFrame) {
|
if visibleBounds.intersects(baseItemFrame) {
|
||||||
validIndices.insert(i)
|
validIndices.insert(i)
|
||||||
|
|
||||||
var itemFrame = baseItemFrame
|
var itemFrame = baseItemFrame
|
||||||
let isPreviewing = false
|
var isPreviewing = false
|
||||||
if self.highlightedReaction == self.items[i].reaction {
|
if self.highlightedReaction == self.items[i].reaction {
|
||||||
itemFrame = itemFrame.insetBy(dx: -4.0, dy: -4.0).offsetBy(dx: 0.0, dy: 0.0)
|
let updatedSize = CGSize(width: floor(itemFrame.width * 1.66), height: floor(itemFrame.height * 1.66))
|
||||||
//isPreviewing = true
|
itemFrame = CGRect(origin: CGPoint(x: itemFrame.midX - updatedSize.width / 2.0, y: itemFrame.maxY + 4.0 - updatedSize.height), size: updatedSize)
|
||||||
|
isPreviewing = true
|
||||||
}
|
}
|
||||||
|
|
||||||
var animateIn = false
|
var animateIn = false
|
||||||
@ -226,16 +241,24 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
|
|
||||||
if !itemNode.isExtracted {
|
if !itemNode.isExtracted {
|
||||||
if isPreviewing {
|
if isPreviewing {
|
||||||
/*if itemNode.supernode !== self.previewingItemContainer {
|
if itemNode.supernode !== self.previewingItemContainer {
|
||||||
self.previewingItemContainer.addSubnode(itemNode)
|
self.previewingItemContainer.addSubnode(itemNode)
|
||||||
}*/
|
}
|
||||||
} else {
|
|
||||||
/*if itemNode.supernode !== self.scrollNode {
|
|
||||||
self.scrollNode.addSubnode(itemNode)
|
|
||||||
}*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
transition.updateFrame(node: itemNode, frame: itemFrame, beginWithCurrentState: true)
|
transition.updateFrame(node: itemNode, frame: itemFrame, beginWithCurrentState: true, completion: { [weak self, weak itemNode] completed in
|
||||||
|
guard let strongSelf = self, let itemNode = itemNode else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !completed {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !isPreviewing {
|
||||||
|
if itemNode.supernode !== strongSelf.scrollNode {
|
||||||
|
strongSelf.scrollNode.addSubnode(itemNode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
itemNode.updateLayout(size: itemFrame.size, isExpanded: false, isPreviewing: isPreviewing, transition: transition)
|
itemNode.updateLayout(size: itemFrame.size, isExpanded: false, isPreviewing: isPreviewing, transition: transition)
|
||||||
|
|
||||||
if animateIn {
|
if animateIn {
|
||||||
@ -272,6 +295,9 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
if visibleContentWidth > size.width - sideInset * 2.0 {
|
if visibleContentWidth > size.width - sideInset * 2.0 {
|
||||||
visibleContentWidth = size.width - sideInset * 2.0
|
visibleContentWidth = size.width - sideInset * 2.0
|
||||||
}
|
}
|
||||||
|
if self.highlightedReaction != nil {
|
||||||
|
visibleContentWidth += 4.0
|
||||||
|
}
|
||||||
|
|
||||||
let contentHeight = verticalInset * 2.0 + rowHeight
|
let contentHeight = verticalInset * 2.0 + rowHeight
|
||||||
|
|
||||||
@ -282,10 +308,10 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
let (backgroundFrame, isLeftAligned, cloudSourcePoint) = self.calculateBackgroundFrame(containerSize: CGSize(width: size.width, height: size.height), insets: backgroundInsets, anchorRect: anchorRect, contentSize: CGSize(width: visibleContentWidth, height: contentHeight))
|
let (backgroundFrame, isLeftAligned, cloudSourcePoint) = self.calculateBackgroundFrame(containerSize: CGSize(width: size.width, height: size.height), insets: backgroundInsets, anchorRect: anchorRect, contentSize: CGSize(width: visibleContentWidth, height: contentHeight))
|
||||||
self.isLeftAligned = isLeftAligned
|
self.isLeftAligned = isLeftAligned
|
||||||
|
|
||||||
transition.updateFrame(node: self.contentContainer, frame: backgroundFrame)
|
transition.updateFrame(node: self.contentContainer, frame: backgroundFrame, beginWithCurrentState: true)
|
||||||
transition.updateFrame(view: self.contentContainerMask, frame: CGRect(origin: CGPoint(), size: backgroundFrame.size))
|
transition.updateFrame(view: self.contentContainerMask, frame: CGRect(origin: CGPoint(), size: backgroundFrame.size), beginWithCurrentState: true)
|
||||||
transition.updateFrame(node: self.scrollNode, frame: CGRect(origin: CGPoint(), size: backgroundFrame.size))
|
transition.updateFrame(node: self.scrollNode, frame: CGRect(origin: CGPoint(), size: backgroundFrame.size), beginWithCurrentState: true)
|
||||||
transition.updateFrame(node: self.previewingItemContainer, frame: backgroundFrame)
|
transition.updateFrame(node: self.previewingItemContainer, frame: backgroundFrame, beginWithCurrentState: true)
|
||||||
self.scrollNode.view.contentSize = CGSize(width: completeContentWidth, height: backgroundFrame.size.height)
|
self.scrollNode.view.contentSize = CGSize(width: completeContentWidth, height: backgroundFrame.size.height)
|
||||||
|
|
||||||
self.updateScrolling(transition: transition)
|
self.updateScrolling(transition: transition)
|
||||||
@ -436,12 +462,14 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
targetView.isHidden = true
|
targetView.isHidden = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let itemSize: CGFloat = 40.0
|
||||||
|
|
||||||
itemNode.isExtracted = true
|
itemNode.isExtracted = true
|
||||||
let selfSourceRect = itemNode.view.convert(itemNode.view.bounds, to: self.view)
|
let selfSourceRect = itemNode.view.convert(itemNode.view.bounds, to: self.view)
|
||||||
let selfTargetRect = self.view.convert(targetView.bounds, from: targetView)
|
let selfTargetRect = self.view.convert(targetView.bounds, from: targetView)
|
||||||
|
|
||||||
let expandedScale: CGFloat = 4.0
|
let expandedScale: CGFloat = 4.0
|
||||||
let expandedSize = CGSize(width: floor(selfSourceRect.width * expandedScale), height: floor(selfSourceRect.height * expandedScale))
|
let expandedSize = CGSize(width: floor(itemSize * expandedScale), height: floor(itemSize * expandedScale))
|
||||||
|
|
||||||
var expandedFrame = CGRect(origin: CGPoint(x: floor(selfTargetRect.midX - expandedSize.width / 2.0), y: floor(selfTargetRect.midY - expandedSize.height / 2.0)), size: expandedSize)
|
var expandedFrame = CGRect(origin: CGPoint(x: floor(selfTargetRect.midX - expandedSize.width / 2.0), y: floor(selfTargetRect.midY - expandedSize.height / 2.0)), size: expandedSize)
|
||||||
if expandedFrame.minX < -floor(expandedFrame.width * 0.05) {
|
if expandedFrame.minX < -floor(expandedFrame.width * 0.05) {
|
||||||
@ -518,7 +546,7 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public func highlightGestureMoved(location: CGPoint) {
|
public func highlightGestureMoved(location: CGPoint) {
|
||||||
let highlightedReaction = self.reaction(at: location)?.reaction
|
let highlightedReaction = self.previewReaction(at: location)?.reaction
|
||||||
if self.highlightedReaction != highlightedReaction {
|
if self.highlightedReaction != highlightedReaction {
|
||||||
self.highlightedReaction = highlightedReaction
|
self.highlightedReaction = highlightedReaction
|
||||||
if self.hapticFeedback == nil {
|
if self.hapticFeedback == nil {
|
||||||
@ -545,6 +573,38 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func previewReaction(at point: CGPoint) -> ReactionContextItem? {
|
||||||
|
let scrollPoint = self.view.convert(point, to: self.scrollNode.view)
|
||||||
|
if !self.scrollNode.bounds.contains(scrollPoint) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
let itemSize: CGFloat = 40.0
|
||||||
|
|
||||||
|
var closestItem: (index: Int, distance: CGFloat)?
|
||||||
|
|
||||||
|
for (index, itemNode) in self.visibleItemNodes {
|
||||||
|
let intersectionItemFrame = CGRect(origin: CGPoint(x: itemNode.frame.midX - itemSize / 2.0, y: itemNode.frame.midY - 1.0), size: CGSize(width: itemSize, height: 2.0))
|
||||||
|
|
||||||
|
if !self.scrollNode.bounds.contains(intersectionItemFrame) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
let distance = abs(scrollPoint.x - intersectionItemFrame.midX)
|
||||||
|
if let (_, currentDistance) = closestItem {
|
||||||
|
if currentDistance > distance {
|
||||||
|
closestItem = (index, distance)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
closestItem = (index, distance)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let closestItem = closestItem {
|
||||||
|
return self.visibleItemNodes[closestItem.index]?.item
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
public func reaction(at point: CGPoint) -> ReactionContextItem? {
|
public func reaction(at point: CGPoint) -> ReactionContextItem? {
|
||||||
for i in 0 ..< 2 {
|
for i in 0 ..< 2 {
|
||||||
let touchInset: CGFloat = i == 0 ? 0.0 : 8.0
|
let touchInset: CGFloat = i == 0 ? 0.0 : 8.0
|
||||||
|
@ -189,11 +189,18 @@ final class ReactionNode: ASDisplayNode {
|
|||||||
stillAnimationNode.position = animationFrame.center
|
stillAnimationNode.position = animationFrame.center
|
||||||
stillAnimationNode.bounds = CGRect(origin: CGPoint(), size: animationFrame.size)
|
stillAnimationNode.bounds = CGRect(origin: CGPoint(), size: animationFrame.size)
|
||||||
stillAnimationNode.updateLayout(size: animationFrame.size)
|
stillAnimationNode.updateLayout(size: animationFrame.size)
|
||||||
stillAnimationNode.started = { [weak self] in
|
stillAnimationNode.started = { [weak self, weak stillAnimationNode] in
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self, let stillAnimationNode = stillAnimationNode, strongSelf.stillAnimationNode === stillAnimationNode else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
strongSelf.staticAnimationNode.alpha = 0.0
|
strongSelf.staticAnimationNode.alpha = 0.0
|
||||||
|
|
||||||
|
if let animateInAnimationNode = strongSelf.animateInAnimationNode, !animateInAnimationNode.alpha.isZero {
|
||||||
|
animateInAnimationNode.alpha = 0.0
|
||||||
|
animateInAnimationNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.1)
|
||||||
|
|
||||||
|
strongSelf.staticAnimationNode.isHidden = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
stillAnimationNode.visibility = true
|
stillAnimationNode.visibility = true
|
||||||
|
|
||||||
@ -213,7 +220,7 @@ final class ReactionNode: ASDisplayNode {
|
|||||||
transition.updateTransformScale(node: stillAnimationNode, scale: animationFrame.size.width / stillAnimationNode.bounds.width, beginWithCurrentState: true)
|
transition.updateTransformScale(node: stillAnimationNode, scale: animationFrame.size.width / stillAnimationNode.bounds.width, beginWithCurrentState: true)
|
||||||
|
|
||||||
stillAnimationNode.alpha = 0.0
|
stillAnimationNode.alpha = 0.0
|
||||||
stillAnimationNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.14, completion: { [weak self, weak stillAnimationNode] _ in
|
stillAnimationNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.1, completion: { [weak self, weak stillAnimationNode] _ in
|
||||||
guard let strongSelf = self, let stillAnimationNode = stillAnimationNode else {
|
guard let strongSelf = self, let stillAnimationNode = stillAnimationNode else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -233,6 +233,7 @@ public extension EngineMessageReactionListContext.State {
|
|||||||
init(message: EngineMessage, reaction: String?) {
|
init(message: EngineMessage, reaction: String?) {
|
||||||
var totalCount = 0
|
var totalCount = 0
|
||||||
var hasOutgoingReaction = false
|
var hasOutgoingReaction = false
|
||||||
|
var items: [EngineMessageReactionListContext.Item] = []
|
||||||
if let reactionsAttribute = message._asMessage().reactionsAttribute {
|
if let reactionsAttribute = message._asMessage().reactionsAttribute {
|
||||||
for messageReaction in reactionsAttribute.reactions {
|
for messageReaction in reactionsAttribute.reactions {
|
||||||
if reaction == nil || messageReaction.value == reaction {
|
if reaction == nil || messageReaction.value == reaction {
|
||||||
@ -242,12 +243,20 @@ public extension EngineMessageReactionListContext.State {
|
|||||||
totalCount += Int(messageReaction.count)
|
totalCount += Int(messageReaction.count)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
for recentPeer in reactionsAttribute.recentPeers {
|
||||||
|
if let peer = message.peers[recentPeer.peerId] {
|
||||||
|
items.append(EngineMessageReactionListContext.Item(peer: EnginePeer(peer), reaction: recentPeer.value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if items.count != totalCount {
|
||||||
|
items.removeAll()
|
||||||
}
|
}
|
||||||
self.init(
|
self.init(
|
||||||
hasOutgoingReaction: hasOutgoingReaction,
|
hasOutgoingReaction: hasOutgoingReaction,
|
||||||
totalCount: totalCount,
|
totalCount: totalCount,
|
||||||
items: [],
|
items: items,
|
||||||
canLoadMore: totalCount != 0
|
canLoadMore: items.count != totalCount && totalCount != 0
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user