[WIP] Quotes and link previews

This commit is contained in:
Ali
2023-10-18 01:11:23 +04:00
parent 137c3d9101
commit af8474aca5
88 changed files with 2582 additions and 966 deletions

View File

@@ -7,18 +7,16 @@ import ChatPresentationInterfaceState
import ShimmerEffect
private let buttonFont = Font.semibold(14.0)
private let sharedBackgroundImage = generateStretchableFilledCircleImage(radius: 4.0, color: UIColor.white)?.withRenderingMode(.alwaysTemplate)
public final class ChatMessageAttachedContentButtonNode: HighlightTrackingButtonNode {
private let textNode: TextNode
private let iconNode: ASImageNode
private let highlightedTextNode: TextNode
private let backgroundNode: ASImageNode
private var iconView: UIImageView?
private let shimmerEffectNode: ShimmerEffectForegroundNode
private var regularImage: UIImage?
private var highlightedImage: UIImage?
private var backgroundView: UIImageView?
private var regularIconImage: UIImage?
private var highlightedIconImage: UIImage?
public var pressed: (() -> Void)?
@@ -27,56 +25,24 @@ public final class ChatMessageAttachedContentButtonNode: HighlightTrackingButton
public init() {
self.textNode = TextNode()
self.textNode.isUserInteractionEnabled = false
self.highlightedTextNode = TextNode()
self.highlightedTextNode.isUserInteractionEnabled = false
self.shimmerEffectNode = ShimmerEffectForegroundNode()
self.shimmerEffectNode.cornerRadius = 5.0
self.backgroundNode = ASImageNode()
self.backgroundNode.isLayerBacked = true
self.backgroundNode.displayWithoutProcessing = true
self.backgroundNode.displaysAsynchronously = false
self.iconNode = ASImageNode()
self.iconNode.isLayerBacked = true
self.iconNode.displayWithoutProcessing = true
self.iconNode.displaysAsynchronously = false
super.init()
self.addSubnode(self.shimmerEffectNode)
self.addSubnode(self.backgroundNode)
self.addSubnode(self.textNode)
self.addSubnode(self.highlightedTextNode)
self.highlightedTextNode.isHidden = true
self.highligthedChanged = { [weak self] highlighted in
if let strongSelf = self {
if highlighted {
strongSelf.backgroundNode.image = strongSelf.highlightedImage
strongSelf.iconNode.image = strongSelf.highlightedIconImage
strongSelf.textNode.isHidden = true
strongSelf.highlightedTextNode.isHidden = false
let scale = (strongSelf.bounds.width - 10.0) / strongSelf.bounds.width
strongSelf.layer.animateScale(from: 1.0, to: scale, duration: 0.15, removeOnCompletion: false)
} else {
if let presentationLayer = strongSelf.layer.presentation() {
strongSelf.layer.animateScale(from: CGFloat((presentationLayer.value(forKeyPath: "transform.scale.y") as? NSNumber)?.floatValue ?? 1.0), to: 1.0, duration: 0.25, removeOnCompletion: false)
}
if let snapshot = strongSelf.view.snapshotView(afterScreenUpdates: false) {
strongSelf.view.addSubview(snapshot)
snapshot.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { _ in
snapshot.removeFromSuperview()
})
}
strongSelf.backgroundNode.image = strongSelf.regularImage
strongSelf.iconNode.image = strongSelf.regularIconImage
strongSelf.textNode.isHidden = false
strongSelf.highlightedTextNode.isHidden = true
}
}
}
@@ -95,7 +61,7 @@ public final class ChatMessageAttachedContentButtonNode: HighlightTrackingButton
self.shimmerEffectNode.isHidden = false
self.shimmerEffectNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
let backgroundFrame = self.backgroundNode.frame
let backgroundFrame = self.bounds
self.shimmerEffectNode.frame = backgroundFrame
self.shimmerEffectNode.updateAbsoluteRect(CGRect(origin: .zero, size: backgroundFrame.size), within: backgroundFrame.size)
self.shimmerEffectNode.update(backgroundColor: .clear, foregroundColor: titleColor.withAlphaComponent(0.3), horizontal: true, effectSize: nil, globalTimeOffset: false, duration: nil)
@@ -107,16 +73,13 @@ public final class ChatMessageAttachedContentButtonNode: HighlightTrackingButton
})
}
public static func asyncLayout(_ current: ChatMessageAttachedContentButtonNode?) -> (_ width: CGFloat, _ regularImage: UIImage?, _ highlightedImage: UIImage?, _ iconImage: UIImage?, _ highlightedIconImage: UIImage?, _ cornerIcon: Bool, _ title: String, _ titleColor: UIColor, _ highlightedTitleColor: UIColor, _ inProgress: Bool) -> (CGFloat, (CGFloat, CGFloat) -> (CGSize, () -> ChatMessageAttachedContentButtonNode)) {
let previousRegularImage = current?.regularImage
let previousHighlightedImage = current?.highlightedImage
public typealias AsyncLayout = (_ width: CGFloat, _ iconImage: UIImage?, _ cornerIcon: Bool, _ title: String, _ titleColor: UIColor, _ inProgress: Bool, _ drawBackground: Bool) -> (CGFloat, (CGFloat, CGFloat) -> (CGSize, (ListViewItemUpdateAnimation) -> ChatMessageAttachedContentButtonNode))
public static func asyncLayout(_ current: ChatMessageAttachedContentButtonNode?) -> AsyncLayout {
let previousRegularIconImage = current?.regularIconImage
let previousHighlightedIconImage = current?.highlightedIconImage
let maybeMakeTextLayout = (current?.textNode).flatMap(TextNode.asyncLayout)
let maybeMakeHighlightedTextLayout = (current?.highlightedTextNode).flatMap(TextNode.asyncLayout)
return { width, regularImage, highlightedImage, iconImage, highlightedIconImage, cornerIcon, title, titleColor, highlightedTitleColor, inProgress in
return { width, iconImage, cornerIcon, title, titleColor, inProgress, drawBackground in
let targetNode: ChatMessageAttachedContentButtonNode
if let current = current {
targetNode = current
@@ -131,33 +94,11 @@ public final class ChatMessageAttachedContentButtonNode: HighlightTrackingButton
makeTextLayout = TextNode.asyncLayout(targetNode.textNode)
}
let makeHighlightedTextLayout: (TextNodeLayoutArguments) -> (TextNodeLayout, () -> TextNode)
if let maybeMakeHighlightedTextLayout = maybeMakeHighlightedTextLayout {
makeHighlightedTextLayout = maybeMakeHighlightedTextLayout
} else {
makeHighlightedTextLayout = TextNode.asyncLayout(targetNode.highlightedTextNode)
}
var updatedRegularImage: UIImage?
if regularImage !== previousRegularImage {
updatedRegularImage = regularImage
}
var updatedHighlightedImage: UIImage?
if highlightedImage !== previousHighlightedImage {
updatedHighlightedImage = highlightedImage
}
var updatedRegularIconImage: UIImage?
if iconImage !== previousRegularIconImage {
updatedRegularIconImage = iconImage
}
var updatedHighlightedIconImage: UIImage?
if highlightedIconImage !== previousHighlightedIconImage {
updatedHighlightedIconImage = highlightedIconImage
}
var iconWidth: CGFloat = 0.0
if let iconImage = iconImage {
iconWidth = iconImage.size.width + 5.0
@@ -167,65 +108,70 @@ public final class ChatMessageAttachedContentButtonNode: HighlightTrackingButton
let (textSize, textApply) = makeTextLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: title, font: buttonFont, textColor: titleColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: max(1.0, width - labelInset * 2.0 - iconWidth), height: CGFloat.greatestFiniteMagnitude), alignment: .left, cutout: nil, insets: UIEdgeInsets()))
let (_, highlightedTextApply) = makeHighlightedTextLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: title, font: buttonFont, textColor: highlightedTitleColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: max(1.0, width - labelInset * 2.0), height: CGFloat.greatestFiniteMagnitude), alignment: .left, cutout: nil, insets: UIEdgeInsets()))
return (textSize.size.width + labelInset * 2.0, { refinedWidth, refinedHeight in
let size = CGSize(width: refinedWidth, height: refinedHeight)
return (size, {
return (size, { animation in
targetNode.accessibilityLabel = title
//targetNode.borderColor = UIColor.red.cgColor
//targetNode.borderWidth = 1.0
targetNode.titleColor = titleColor
if let updatedRegularImage = updatedRegularImage {
targetNode.regularImage = updatedRegularImage
if !targetNode.textNode.isHidden {
targetNode.backgroundNode.image = updatedRegularImage
}
}
if let updatedHighlightedImage = updatedHighlightedImage {
targetNode.highlightedImage = updatedHighlightedImage
if targetNode.textNode.isHidden {
targetNode.backgroundNode.image = updatedHighlightedImage
}
let iconView: UIImageView
if let current = targetNode.iconView {
iconView = current
} else {
iconView = UIImageView()
targetNode.iconView = iconView
targetNode.view.addSubview(iconView)
}
iconView.tintColor = titleColor
if let updatedRegularIconImage = updatedRegularIconImage {
targetNode.regularIconImage = updatedRegularIconImage
if !targetNode.textNode.isHidden {
targetNode.iconNode.image = updatedRegularIconImage
}
}
if let updatedHighlightedIconImage = updatedHighlightedIconImage {
targetNode.highlightedIconImage = updatedHighlightedIconImage
if targetNode.iconNode.isHidden {
targetNode.iconNode.image = updatedHighlightedIconImage
iconView.image = updatedRegularIconImage.withRenderingMode(.alwaysTemplate)
}
}
let _ = textApply()
let _ = highlightedTextApply()
let backgroundFrame = CGRect(origin: CGPoint(), size: CGSize(width: refinedWidth, height: size.height))
var textFrame = CGRect(origin: CGPoint(x: floor((refinedWidth - textSize.size.width) / 2.0), y: floor((backgroundFrame.height - textSize.size.height) / 2.0)), size: textSize.size)
targetNode.backgroundNode.frame = backgroundFrame
if let image = targetNode.iconNode.image {
if drawBackground {
textFrame.origin.y += 1.0
}
if let image = iconView.image {
let iconFrame: CGRect
if cornerIcon {
targetNode.iconNode.frame = CGRect(origin: CGPoint(x: backgroundFrame.maxX - image.size.width - 5.0, y: 5.0), size: image.size)
iconFrame = CGRect(origin: CGPoint(x: backgroundFrame.maxX - image.size.width - 5.0, y: 5.0), size: image.size)
} else {
textFrame.origin.x += floor(image.size.width / 2.0)
targetNode.iconNode.frame = CGRect(origin: CGPoint(x: textFrame.minX - image.size.width - 5.0, y: textFrame.minY + floorToScreenPixels((textFrame.height - image.size.height) * 0.5)), size: image.size)
iconFrame = CGRect(origin: CGPoint(x: textFrame.minX - image.size.width - 5.0, y: textFrame.minY + floorToScreenPixels((textFrame.height - image.size.height) * 0.5)), size: image.size)
}
if targetNode.iconNode.supernode == nil {
targetNode.addSubnode(targetNode.iconNode)
}
} else if targetNode.iconNode.supernode != nil {
targetNode.iconNode.removeFromSupernode()
animation.animator.updateFrame(layer: iconView.layer, frame: iconFrame, completion: nil)
}
targetNode.textNode.frame = textFrame
targetNode.highlightedTextNode.frame = targetNode.textNode.frame
targetNode.textNode.bounds = CGRect(origin: CGPoint(), size: textFrame.size)
animation.animator.updatePosition(layer: targetNode.textNode.layer, position: textFrame.center, completion: nil)
if drawBackground {
let backgroundView: UIImageView
if let current = targetNode.backgroundView {
backgroundView = current
animation.animator.updateFrame(layer: backgroundView.layer, frame: backgroundFrame, completion: nil)
} else {
backgroundView = UIImageView()
backgroundView.image = sharedBackgroundImage
targetNode.backgroundView = backgroundView
targetNode.view.insertSubview(backgroundView, at: 0)
backgroundView.frame = backgroundFrame
}
backgroundView.tintColor = titleColor.withMultipliedAlpha(0.1)
} else if let backgroundView = targetNode.backgroundView {
targetNode.backgroundView = nil
backgroundView.removeFromSuperview()
}
return targetNode
})