diff --git a/submodules/InvisibleInkDustNode/Sources/InvisibleInkDustNode.swift b/submodules/InvisibleInkDustNode/Sources/InvisibleInkDustNode.swift index 800876877d..98a4155585 100644 --- a/submodules/InvisibleInkDustNode/Sources/InvisibleInkDustNode.swift +++ b/submodules/InvisibleInkDustNode/Sources/InvisibleInkDustNode.swift @@ -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 diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContentCaptionComponent.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContentCaptionComponent.swift index 21d87548e7..8588fbaf0b 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContentCaptionComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContentCaptionComponent.swift @@ -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,