Support spoilers

This commit is contained in:
Isaac
2024-05-25 18:05:56 +04:00
parent 8a236faa16
commit 8d11af19ac
4 changed files with 112 additions and 43 deletions

View File

@@ -390,7 +390,7 @@ public final class InteractiveTextNodeLayout: NSObject {
fileprivate let textShadowColor: UIColor?
fileprivate let textShadowBlur: CGFloat?
fileprivate let textStroke: (UIColor, CGFloat)?
fileprivate let displayContentsUnderSpoilers: Bool
public let displayContentsUnderSpoilers: Bool
fileprivate let expandedBlocks: Set<Int>
fileprivate init(
@@ -441,6 +441,33 @@ public final class InteractiveTextNodeLayout: NSObject {
self.expandedBlocks = expandedBlocks
}
func withUpdatedDisplayContentsUnderSpoilers(_ displayContentsUnderSpoilers: Bool) -> InteractiveTextNodeLayout {
return InteractiveTextNodeLayout(
attributedString: self.attributedString,
maximumNumberOfLines: self.maximumNumberOfLines,
truncationType: self.truncationType,
constrainedSize: self.constrainedSize,
explicitAlignment: self.explicitAlignment,
resolvedAlignment: self.resolvedAlignment,
verticalAlignment: self.verticalAlignment,
lineSpacing: self.lineSpacing,
cutout: self.cutout,
insets: self.insets,
size: self.size,
rawTextSize: self.rawTextSize,
truncated: self.truncated,
firstLineOffset: self.firstLineOffset,
segments: self.segments,
backgroundColor: self.backgroundColor,
lineColor: self.lineColor,
textShadowColor: self.textShadowColor,
textShadowBlur: self.textShadowBlur,
textStroke: self.textStroke,
displayContentsUnderSpoilers: displayContentsUnderSpoilers,
expandedBlocks: self.expandedBlocks
)
}
public var numberOfLines: Int {
var result = 0
for segment in self.segments {
@@ -1075,8 +1102,11 @@ open class InteractiveTextNode: ASDisplayNode, TextNodeProtocol, UIGestureRecogn
public var renderContentTypes: RenderContentTypes = .all
private var contentItemLayers: [Int: TextContentItemLayer] = [:]
private var isDisplayingContentsUnderSpoilers: Bool?
public var canHandleTapAtPoint: ((CGPoint) -> Bool)?
public var requestToggleBlockCollapsed: ((Int) -> Void)?
public var requestDisplayContentsUnderSpoilers: (() -> Void)?
private var tapRecognizer: UITapGestureRecognizer?
public var currentText: NSAttributedString? {
@@ -1535,7 +1565,10 @@ open class InteractiveTextNode: ASDisplayNode, TextNodeProtocol, UIGestureRecogn
var descent: CGFloat = 0.0
CTLineGetTypographicBounds(line.line, &ascent, &descent, nil)
let isHiddenBySpoiler = attributes[NSAttributedString.Key(rawValue: "Attribute__Spoiler")] != nil || attributes[NSAttributedString.Key(rawValue: "TelegramSpoiler")] != nil
var isHiddenBySpoiler = attributes[NSAttributedString.Key(rawValue: "Attribute__Spoiler")] != nil || attributes[NSAttributedString.Key(rawValue: "TelegramSpoiler")] != nil
if displayContentsUnderSpoilers {
isHiddenBySpoiler = false
}
addEmbeddedItem(item: embeddedItem, isHiddenBySpoiler: isHiddenBySpoiler, line: line, ascent: ascent, descent: descent, startIndex: range.location, endIndex: range.location + range.length)
}
@@ -1648,6 +1681,10 @@ open class InteractiveTextNode: ASDisplayNode, TextNodeProtocol, UIGestureRecogn
return
}
let animateContents = self.isDisplayingContentsUnderSpoilers != nil && self.isDisplayingContentsUnderSpoilers != cachedLayout.displayContentsUnderSpoilers && animation.isAnimated
let synchronous = animateContents
self.isDisplayingContentsUnderSpoilers = cachedLayout.displayContentsUnderSpoilers
let topLeftOffset = CGPoint(x: cachedLayout.insets.left, y: cachedLayout.insets.top)
var validIds: [Int] = []
@@ -1701,7 +1738,7 @@ open class InteractiveTextNode: ASDisplayNode, TextNodeProtocol, UIGestureRecogn
self.layer.addSublayer(contentItemLayer)
}
contentItemLayer.update(item: contentItem, animation: contentItemAnimation)
contentItemLayer.update(item: contentItem, animation: contentItemAnimation, synchronously: synchronous, animateContents: animateContents && contentItemAnimation.isAnimated)
contentItemAnimation.animator.updateFrame(layer: contentItemLayer, frame: contentItemFrame, completion: nil)
}
@@ -1736,6 +1773,12 @@ open class InteractiveTextNode: ASDisplayNode, TextNodeProtocol, UIGestureRecogn
@objc private func tapGesture(_ recognizer: UITapGestureRecognizer) {
if case .ended = recognizer.state {
let point = recognizer.location(in: self.view)
if let cachedLayout = self.cachedLayout, !cachedLayout.displayContentsUnderSpoilers, let (_, attributes) = self.attributesAtPoint(point) {
if attributes[NSAttributedString.Key(rawValue: "Attribute__Spoiler")] != nil || attributes[NSAttributedString.Key(rawValue: "TelegramSpoiler")] != nil {
self.requestDisplayContentsUnderSpoilers?()
return
}
}
if let blockId = self.collapsibleBlockAtPoint(point) {
self.requestToggleBlockCollapsed?(blockId)
}
@@ -1746,7 +1789,7 @@ open class InteractiveTextNode: ASDisplayNode, TextNodeProtocol, UIGestureRecogn
let existingLayout: InteractiveTextNodeLayout? = maybeNode?.cachedLayout
return { arguments in
let layout: InteractiveTextNodeLayout
var layout: InteractiveTextNodeLayout
if let existingLayout = existingLayout, existingLayout.constrainedSize == arguments.constrainedSize && existingLayout.maximumNumberOfLines == arguments.maximumNumberOfLines && existingLayout.truncationType == arguments.truncationType && existingLayout.cutout == arguments.cutout && existingLayout.explicitAlignment == arguments.alignment && existingLayout.lineSpacing.isEqual(to: arguments.lineSpacing) && existingLayout.expandedBlocks == arguments.expandedBlocks {
let stringMatch: Bool
@@ -1772,6 +1815,9 @@ open class InteractiveTextNode: ASDisplayNode, TextNodeProtocol, UIGestureRecogn
if stringMatch {
layout = existingLayout
if layout.displayContentsUnderSpoilers != arguments.displayContentsUnderSpoilers {
layout = layout.withUpdatedDisplayContentsUnderSpoilers(arguments.displayContentsUnderSpoilers)
}
} else {
layout = InteractiveTextNode.calculateLayout(attributedString: arguments.attributedString, minimumNumberOfLines: arguments.minimumNumberOfLines, maximumNumberOfLines: arguments.maximumNumberOfLines, truncationType: arguments.truncationType, backgroundColor: arguments.backgroundColor, constrainedSize: arguments.constrainedSize, alignment: arguments.alignment, verticalAlignment: arguments.verticalAlignment, lineSpacingFactor: arguments.lineSpacing, cutout: arguments.cutout, insets: arguments.insets, lineColor: arguments.lineColor, textShadowColor: arguments.textShadowColor, textShadowBlur: arguments.textShadowBlur, textStroke: arguments.textStroke, displayContentsUnderSpoilers: arguments.displayContentsUnderSpoilers, customTruncationToken: arguments.customTruncationToken, expandedBlocks: arguments.expandedBlocks)
}
@@ -1961,7 +2007,7 @@ final class TextContentItemLayer: SimpleLayer {
if attributes["Attribute__EmbeddedItem"] != nil {
continue
}
if hasHiddenSpoilers && attributes["Attribute__Spoiler"] != nil || attributes["TelegramSpoiler"] != nil {
if hasHiddenSpoilers && (attributes["Attribute__Spoiler"] != nil || attributes["TelegramSpoiler"] != nil) {
continue
}
@@ -2152,9 +2198,8 @@ final class TextContentItemLayer: SimpleLayer {
fatalError("init(coder:) has not been implemented")
}
func update(item: TextContentItem, animation: ListViewItemUpdateAnimation, synchronously: Bool = false) {
func update(item: TextContentItem, animation: ListViewItemUpdateAnimation, synchronously: Bool = false, animateContents: Bool = false) {
self.item = item
self.setNeedsDisplay()
let contentFrame = CGRect(origin: CGPoint(), size: item.size)
var effectiveContentFrame = contentFrame
@@ -2313,7 +2358,11 @@ final class TextContentItemLayer: SimpleLayer {
self.renderNode.params = RenderParams(size: contentFrame.size, item: item, mask: staticContentMask)
if synchronously {
let previousContents = self.renderNode.layer.contents
self.renderNode.displayImmediately()
if animateContents, let previousContents {
animation.transition.animateContents(layer: self.renderNode.layer, from: previousContents)
}
} else {
self.renderNode.setNeedsDisplay()
}

View File

@@ -257,6 +257,11 @@ public final class InteractiveTextNodeWithEntities {
) {
self.enableLooping = context.sharedContext.energyUsageSettings.loopEmoji
var displayContentsUnderSpoilers = false
if let textLayout {
displayContentsUnderSpoilers = textLayout.displayContentsUnderSpoilers
}
var nextIndexById: [Int64: Int] = [:]
var validIds: [InlineStickerItemLayer.Key] = []
@@ -291,6 +296,7 @@ public final class InteractiveTextNodeWithEntities {
itemFrame.origin.y += segmentItem.contentOffset.y
let itemLayerData: InlineStickerItemLayerData
var itemLayerTransition = animation.transition
if let current = self.inlineStickerItemLayers[id] {
itemLayerData = current
itemLayerData.itemLayer.dynamicColor = item.textColor
@@ -299,6 +305,7 @@ public final class InteractiveTextNodeWithEntities {
segmentLayer.addSublayer(itemLayerData.itemLayer)
}
} else {
itemLayerTransition = .immediate
let pointSize = floor(itemSize * 1.3)
itemLayerData = InlineStickerItemLayerData(itemLayer: InlineStickerItemLayer(context: context, userLocation: .other, attemptSynchronousLoad: attemptSynchronousLoad, emoji: stickerItem.emoji, file: stickerItem.file, cache: cache, renderer: renderer, placeholderColor: placeholderColor, pointSize: CGSize(width: pointSize, height: pointSize), dynamicColor: item.textColor))
self.inlineStickerItemLayers[id] = itemLayerData
@@ -307,7 +314,7 @@ public final class InteractiveTextNodeWithEntities {
itemLayerData.itemLayer.isVisibleForAnimations = self.enableLooping && self.isItemVisible(itemRect: itemFrame.offsetBy(dx: -segmentItem.contentOffset.x, dy: -segmentItem.contentOffset.x))
}
itemLayerData.itemLayer.opacity = item.isHiddenBySpoiler ? 0.0 : 1.0
itemLayerTransition.updateAlpha(layer: itemLayerData.itemLayer, alpha: item.isHiddenBySpoiler ? 0.0 : 1.0)
itemLayerData.itemLayer.frame = itemFrame
itemLayerData.rect = itemFrame.offsetBy(dx: -segmentItem.contentOffset.x, dy: -segmentItem.contentOffset.y)
@@ -337,6 +344,8 @@ public final class InteractiveTextNodeWithEntities {
rects: segment.spoilers.map { $0.1.offsetBy(dx: 3.0 + segmentItem.contentOffset.x, dy: segmentItem.contentOffset.y + 3.0).insetBy(dx: 1.0, dy: 1.0) },
wordRects: segment.spoilerWords.map { $0.1.offsetBy(dx: segmentItem.contentOffset.x + 3.0, dy: segmentItem.contentOffset.y + 3.0).insetBy(dx: 1.0, dy: 1.0) }
)
animation.transition.updateAlpha(node: dustEffectNode, alpha: displayContentsUnderSpoilers ? 0.0 : 1.0)
}
}
}