Fix spoiler display in stories captions

This commit is contained in:
Ilya Laktyushin 2023-06-30 23:55:21 +02:00
parent be6541cf4a
commit 8460f9654e
2 changed files with 72 additions and 12 deletions

View File

@ -496,7 +496,7 @@ public class InvisibleInkDustNode: ASDisplayNode {
}
}
@objc private func tap(_ gestureRecognizer: UITapGestureRecognizer) {
public func revealAtLocation(_ location: CGPoint) {
guard let (_, _, textColor, _, _) = self.currentParams, let textNode = self.textNode, !self.isRevealed else {
return
}
@ -506,14 +506,13 @@ public class InvisibleInkDustNode: ASDisplayNode {
if self.enableAnimations {
self.isExploding = true
let position = gestureRecognizer.location(in: self.view)
self.emitterLayer?.setValue(true, forKeyPath: "emitterBehaviors.fingerAttractor.enabled")
self.emitterLayer?.setValue(position, forKeyPath: "emitterBehaviors.fingerAttractor.position")
let maskSize = self.emitterNode.frame.size
Queue.concurrentDefaultQueue().async {
let textMaskImage = generateMaskImage(size: maskSize, position: position, inverse: false)
let emitterMaskImage = generateMaskImage(size: maskSize, position: position, inverse: true)
let textMaskImage = generateMaskImage(size: maskSize, position: location, inverse: false)
let emitterMaskImage = generateMaskImage(size: maskSize, position: location, inverse: true)
Queue.mainQueue().async {
self.textSpotNode.image = textMaskImage
@ -527,8 +526,8 @@ public class InvisibleInkDustNode: ASDisplayNode {
textNode.view.mask = self.textMaskNode.view
self.textSpotNode.frame = CGRect(x: 0.0, y: 0.0, width: self.emitterMaskNode.frame.width * 3.0, height: self.emitterMaskNode.frame.height * 3.0)
let xFactor = (position.x / self.emitterNode.frame.width - 0.5) * 2.0
let yFactor = (position.y / self.emitterNode.frame.height - 0.5) * 2.0
let xFactor = (location.x / self.emitterNode.frame.width - 0.5) * 2.0
let yFactor = (location.y / self.emitterNode.frame.height - 0.5) * 2.0
let maxFactor = max(abs(xFactor), abs(yFactor))
var scaleAddition = maxFactor * 4.0
@ -538,8 +537,8 @@ public class InvisibleInkDustNode: ASDisplayNode {
durationAddition *= 2.0
}
self.textSpotNode.layer.anchorPoint = CGPoint(x: position.x / self.emitterMaskNode.frame.width, y: position.y / self.emitterMaskNode.frame.height)
self.textSpotNode.position = position
self.textSpotNode.layer.anchorPoint = CGPoint(x: location.x / self.emitterMaskNode.frame.width, y: location.y / self.emitterMaskNode.frame.height)
self.textSpotNode.position = location
self.textSpotNode.layer.animateScale(from: 0.3333, to: 10.5 + scaleAddition, duration: 0.55 + durationAddition, removeOnCompletion: false, completion: { _ in
textNode.view.mask = nil
})
@ -548,8 +547,8 @@ public class InvisibleInkDustNode: ASDisplayNode {
self.emitterNode.view.mask = self.emitterMaskNode.view
self.emitterSpotNode.frame = CGRect(x: 0.0, y: 0.0, width: self.emitterMaskNode.frame.width * 3.0, height: self.emitterMaskNode.frame.height * 3.0)
self.emitterSpotNode.layer.anchorPoint = CGPoint(x: position.x / self.emitterMaskNode.frame.width, y: position.y / self.emitterMaskNode.frame.height)
self.emitterSpotNode.position = position
self.emitterSpotNode.layer.anchorPoint = CGPoint(x: location.x / self.emitterMaskNode.frame.width, y: location.y / self.emitterMaskNode.frame.height)
self.emitterSpotNode.position = location
self.emitterSpotNode.layer.animateScale(from: 0.3333, to: 10.5 + scaleAddition, duration: 0.55 + durationAddition, removeOnCompletion: false, completion: { [weak self] _ in
self?.alpha = 0.0
self?.emitterNode.view.mask = nil
@ -576,6 +575,11 @@ public class InvisibleInkDustNode: ASDisplayNode {
}
}
@objc private func tap(_ gestureRecognizer: UITapGestureRecognizer) {
let location = gestureRecognizer.location(in: self.view)
self.revealAtLocation(location)
}
private func updateEmitter() {
guard let (size, color, _, lineRects, wordRects) = self.currentParams else {
return

View File

@ -104,6 +104,7 @@ final class StoryContentCaptionComponent: Component {
private let shadowPlainLayer: SimpleLayer
private var textNode: TextNodeWithEntities?
private var spoilerTextNode: TextNodeWithEntities?
private var linkHighlightingNode: LinkHighlightingNode?
private var dustNode: InvisibleInkDustNode?
@ -247,6 +248,8 @@ final class StoryContentCaptionComponent: Component {
if titleFrame.contains(location) {
if let (index, attributes) = textNode.textNode.attributesAtPoint(CGPoint(x: location.x - titleFrame.minX, y: location.y - titleFrame.minY)) {
if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.Spoiler)], !(self.dustNode?.isRevealed ?? true) {
let convertedPoint = recognizer.view?.convert(location, to: self.dustNode?.view) ?? location
self.dustNode?.revealAtLocation(convertedPoint)
return
} else if let url = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] as? String {
var concealed = true
@ -369,6 +372,14 @@ final class StoryContentCaptionComponent: Component {
textShadowBlur: 4.0
))
let makeSpoilerLayout = TextNodeWithEntities.asyncLayout(self.spoilerTextNode)
let spoilerTextLayoutAndApply: (TextNodeLayout, (TextNodeWithEntities.Arguments?) -> TextNodeWithEntities)?
if !textLayout.0.spoilers.isEmpty {
spoilerTextLayoutAndApply = makeSpoilerLayout(TextNodeLayoutArguments(attributedString: attributedText, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: textContainerSize, textShadowColor: UIColor(white: 0.0, alpha: 0.25), textShadowBlur: 4.0, displaySpoilers: true, displayEmbeddedItemsUnderSpoilers: true))
} else {
spoilerTextLayoutAndApply = nil
}
let maxHeight: CGFloat = 50.0
let visibleTextHeight = min(maxHeight, textLayout.0.size.height)
let textOverflowHeight: CGFloat = textLayout.0.size.height - visibleTextHeight
@ -378,7 +389,8 @@ final class StoryContentCaptionComponent: Component {
context: component.context,
cache: component.context.animationCache,
renderer: component.context.animationRenderer,
placeholderColor: UIColor(white: 0.2, alpha: 1.0), attemptSynchronous: true
placeholderColor: UIColor(white: 0.2, alpha: 1.0),
attemptSynchronous: true
))
if self.textNode !== textNode {
self.textNode?.textNode.view.removeFromSuperview()
@ -403,7 +415,51 @@ final class StoryContentCaptionComponent: Component {
textNode.visibilityRect = CGRect(origin: CGPoint(), size: CGSize(width: 100000.0, height: 100000.0))
}
textNode.textNode.frame = CGRect(origin: CGPoint(x: sideInset, y: availableSize.height - visibleTextHeight - verticalInset), size: textLayout.0.size)
let textFrame = CGRect(origin: CGPoint(x: sideInset, y: availableSize.height - visibleTextHeight - verticalInset), size: textLayout.0.size)
textNode.textNode.frame = textFrame
if let (_, spoilerTextApply) = spoilerTextLayoutAndApply {
let spoilerTextNode = spoilerTextApply(TextNodeWithEntities.Arguments(
context: component.context,
cache: component.context.animationCache,
renderer: component.context.animationRenderer,
placeholderColor: UIColor(white: 0.2, alpha: 1.0),
attemptSynchronous: true
))
if self.spoilerTextNode == nil {
spoilerTextNode.textNode.alpha = 0.0
spoilerTextNode.textNode.isUserInteractionEnabled = false
spoilerTextNode.textNode.contentMode = .topLeft
spoilerTextNode.textNode.contentsScale = UIScreenScale
spoilerTextNode.textNode.displaysAsynchronously = false
self.scrollView.insertSubview(spoilerTextNode.textNode.view, belowSubview: textNode.textNode.view)
spoilerTextNode.visibilityRect = CGRect(origin: CGPoint(), size: CGSize(width: 100000.0, height: 100000.0))
self.spoilerTextNode = spoilerTextNode
}
self.spoilerTextNode?.textNode.frame = textFrame
let dustNode: InvisibleInkDustNode
if let current = self.dustNode {
dustNode = current
} else {
dustNode = InvisibleInkDustNode(textNode: spoilerTextNode.textNode, enableAnimations: component.context.sharedContext.energyUsageSettings.fullTranslucency)
self.dustNode = dustNode
self.scrollView.insertSubview(dustNode.view, aboveSubview: spoilerTextNode.textNode.view)
}
dustNode.frame = textFrame.insetBy(dx: -3.0, dy: -3.0).offsetBy(dx: 0.0, dy: 3.0)
dustNode.update(size: dustNode.frame.size, color: .white, textColor: .white, rects: textLayout.0.spoilers.map { $0.1.offsetBy(dx: 3.0, dy: 3.0).insetBy(dx: 1.0, dy: 1.0) }, wordRects: textLayout.0.spoilerWords.map { $0.1.offsetBy(dx: 3.0, dy: 3.0).insetBy(dx: 1.0, dy: 1.0) })
} else if let spoilerTextNode = self.spoilerTextNode {
self.spoilerTextNode = nil
spoilerTextNode.textNode.removeFromSupernode()
if let dustNode = self.dustNode {
self.dustNode = nil
dustNode.removeFromSupernode()
}
}
self.itemLayout = ItemLayout(
containerSize: availableSize,