Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios

This commit is contained in:
Ilya Laktyushin 2024-05-25 18:07:58 +04:00
commit 66c32bc09e
5 changed files with 122 additions and 54 deletions

View File

@ -1672,6 +1672,27 @@ public extension ContainedViewLayoutTransition {
) )
} }
} }
func animateContents(layer: CALayer, from fromContents: Any) {
guard case let .animated(duration, curve) = self else {
return
}
guard let contents = layer.contents, CFGetTypeID(contents as CFTypeRef) == CGImage.typeID else {
return
}
guard CFGetTypeID(fromContents as CFTypeRef) == CGImage.typeID else {
return
}
let contentsImage = contents as! CGImage
let fromContentsImage = fromContents as! CGImage
if contentsImage === fromContentsImage {
return
}
layer.animate(from: fromContentsImage, to: contentsImage, keyPath: "contents", timingFunction: curve.timingFunction, duration: duration, delay: 0.0, mediaTimingFunction: curve.mediaTimingFunction, removeOnCompletion: true, additive: false)
}
} }
public struct CombinedTransition { public struct CombinedTransition {

View File

@ -1482,7 +1482,7 @@ private final class QuoteBackgroundView: UIView {
} }
if let image = self.collapseButtonIconView.image { if let image = self.collapseButtonIconView.image {
let iconSize = image.size.aspectFitted(collapseButtonSize) let iconSize = image.size.aspectFitted(collapseButtonSize)
self.collapseButtonIconView.frame = CGRect(origin: CGPoint(x: self.collapseButton.bounds.width - 2.0 - collapseButtonSize.width + floorToScreenPixels((collapseButtonSize.width - iconSize.width) * 0.5), y: 2.0 + floorToScreenPixels((collapseButtonSize.height - iconSize.height) * 0.5)), size: iconSize) self.collapseButtonIconView.frame = CGRect(origin: CGPoint(x: self.collapseButton.bounds.width - 4.0 - collapseButtonSize.width + floorToScreenPixels((collapseButtonSize.width - iconSize.width) * 0.5), y: 4.0 + floorToScreenPixels((collapseButtonSize.height - iconSize.height) * 0.5)), size: iconSize)
} }
var primaryColor: UIColor var primaryColor: UIColor

View File

@ -8,7 +8,6 @@ import TextFormat
import UrlEscaping import UrlEscaping
import TelegramUniversalVideoContent import TelegramUniversalVideoContent
import TextSelectionNode import TextSelectionNode
import InvisibleInkDustNode
import Emoji import Emoji
import AnimatedStickerNode import AnimatedStickerNode
import TelegramAnimatedStickerNode import TelegramAnimatedStickerNode
@ -85,7 +84,6 @@ private func findQuoteRange(string: String, quoteText: String, offset: Int?) ->
public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode { public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
private let containerNode: ASDisplayNode private let containerNode: ASDisplayNode
private let textNode: InteractiveTextNodeWithEntities private let textNode: InteractiveTextNodeWithEntities
private var dustNode: InvisibleInkDustNode?
private let textAccessibilityOverlayNode: TextAccessibilityOverlayNode private let textAccessibilityOverlayNode: TextAccessibilityOverlayNode
public var statusNode: ChatMessageDateAndStatusNode? public var statusNode: ChatMessageDateAndStatusNode?
@ -112,6 +110,7 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
private var codeHighlightState: (id: EngineMessage.Id, specs: [CachedMessageSyntaxHighlight.Spec], disposable: Disposable)? private var codeHighlightState: (id: EngineMessage.Id, specs: [CachedMessageSyntaxHighlight.Spec], disposable: Disposable)?
private var expandedBlockIds: Set<Int> = Set() private var expandedBlockIds: Set<Int> = Set()
private var displayContentsUnderSpoilers: Bool = false
override public var visibility: ListViewItemNodeVisibility { override public var visibility: ListViewItemNodeVisibility {
didSet { didSet {
@ -162,6 +161,12 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
} }
item.controllerInteraction.requestMessageUpdate(item.message.id, false) item.controllerInteraction.requestMessageUpdate(item.message.id, false)
} }
self.textNode.textNode.requestDisplayContentsUnderSpoilers = { [weak self] in
guard let self else {
return
}
self.updateDisplayContentsUnderSpoilers(value: true)
}
self.textNode.textNode.canHandleTapAtPoint = { [weak self] point in self.textNode.textNode.canHandleTapAtPoint = { [weak self] point in
guard let self else { guard let self else {
return false return false
@ -192,6 +197,7 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
let currentCachedChatMessageText = self.cachedChatMessageText let currentCachedChatMessageText = self.cachedChatMessageText
let expandedBlockIds = self.expandedBlockIds let expandedBlockIds = self.expandedBlockIds
let displayContentsUnderSpoilers = self.displayContentsUnderSpoilers
return { item, layoutConstants, _, _, _, _ in return { item, layoutConstants, _, _, _, _ in
let contentProperties = ChatMessageBubbleContentProperties(hidesSimpleAuthorHeader: false, headerSpacing: 0.0, hidesBackground: .never, forceFullCorners: false, forceAlignment: .none) let contentProperties = ChatMessageBubbleContentProperties(hidesSimpleAuthorHeader: false, headerSpacing: 0.0, hidesBackground: .never, forceFullCorners: false, forceAlignment: .none)
@ -579,7 +585,7 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
cutout: nil, cutout: nil,
insets: textInsets, insets: textInsets,
lineColor: messageTheme.accentControlColor, lineColor: messageTheme.accentControlColor,
displayContentsUnderSpoilers: false, displayContentsUnderSpoilers: displayContentsUnderSpoilers,
customTruncationToken: customTruncationToken, customTruncationToken: customTruncationToken,
expandedBlocks: expandedBlockIds expandedBlocks: expandedBlockIds
)) ))
@ -594,6 +600,8 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
let trailingWidthToMeasure: CGFloat let trailingWidthToMeasure: CGFloat
if let lastSegment = textLayout.segments.last, lastSegment.hasRTL { if let lastSegment = textLayout.segments.last, lastSegment.hasRTL {
trailingWidthToMeasure = 10000.0 trailingWidthToMeasure = 10000.0
} else if let lastSegment = textLayout.segments.last, lastSegment.hasBlockQuote {
trailingWidthToMeasure = textLayout.size.width
} else { } else {
trailingWidthToMeasure = textLayout.trailingLineWidth trailingWidthToMeasure = textLayout.trailingLineWidth
} }
@ -675,31 +683,6 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
)) ))
animation.animator.updateFrame(layer: strongSelf.textNode.textNode.layer, frame: textFrame, completion: nil) animation.animator.updateFrame(layer: strongSelf.textNode.textNode.layer, frame: textFrame, completion: nil)
/*if let (_, spoilerTextApply) = spoilerTextLayoutAndApply {
let spoilerTextNode = spoilerTextApply(InteractiveTextNodeWithEntities.Arguments(context: item.context, cache: item.controllerInteraction.presentationContext.animationCache, renderer: item.controllerInteraction.presentationContext.animationRenderer, placeholderColor: messageTheme.mediaPlaceholderColor, attemptSynchronous: synchronousLoads, animation: animation))
strongSelf.spoilerTextNode?.textNode.frame = textFrame
let dustNode: InvisibleInkDustNode
if let current = strongSelf.dustNode {
dustNode = current
} else {
dustNode = InvisibleInkDustNode(textNode: spoilerTextNode.textNode, enableAnimations: item.context.sharedContext.energyUsageSettings.fullTranslucency && !item.presentationData.isPreview)
strongSelf.dustNode = dustNode
strongSelf.containerNode.insertSubnode(dustNode, aboveSubnode: spoilerTextNode.textNode)
}
dustNode.frame = textFrame.insetBy(dx: -3.0, dy: -3.0).offsetBy(dx: 0.0, dy: 3.0)
dustNode.update(size: dustNode.frame.size, color: messageTheme.secondaryTextColor, textColor: messageTheme.primaryTextColor, rects: textLayout.spoilers.map { $0.1.offsetBy(dx: 3.0, dy: 3.0).insetBy(dx: 1.0, dy: 1.0) }, wordRects: textLayout.spoilerWords.map { $0.1.offsetBy(dx: 3.0, dy: 3.0).insetBy(dx: 1.0, dy: 1.0) })
} else if let spoilerTextNode = strongSelf.spoilerTextNode {
strongSelf.spoilerTextNode = nil
spoilerTextNode.textNode.removeFromSupernode()
if let dustNode = strongSelf.dustNode {
strongSelf.dustNode = nil
dustNode.removeFromSupernode()
}
}*/
switch strongSelf.visibility { switch strongSelf.visibility {
case .none: case .none:
strongSelf.textNode.visibilityRect = nil strongSelf.textNode.visibilityRect = nil
@ -864,7 +847,7 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
let textNodeFrame = self.textNode.textNode.frame let textNodeFrame = self.textNode.textNode.frame
let textLocalPoint = CGPoint(x: point.x - textNodeFrame.minX, y: point.y - textNodeFrame.minY) let textLocalPoint = CGPoint(x: point.x - textNodeFrame.minX, y: point.y - textNodeFrame.minY)
if let (index, attributes) = self.textNode.textNode.attributesAtPoint(textLocalPoint) { if let (index, attributes) = self.textNode.textNode.attributesAtPoint(textLocalPoint) {
if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.Spoiler)], !(self.dustNode?.isRevealed ?? true) { if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.Spoiler)], !self.displayContentsUnderSpoilers {
return ChatMessageBubbleContentTapAction(content: .none) return ChatMessageBubbleContentTapAction(content: .none)
} else if let url = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] as? String { } else if let url = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] as? String {
var concealed = true var concealed = true
@ -1056,8 +1039,7 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
} }
} }
if let spoilerRects = spoilerRects, !spoilerRects.isEmpty, let dustNode = self.dustNode, !dustNode.isRevealed { if let spoilerRects = spoilerRects, !spoilerRects.isEmpty, !self.displayContentsUnderSpoilers {
} else if let rects = rects { } else if let rects = rects {
let linkHighlightingNode: LinkHighlightingNode let linkHighlightingNode: LinkHighlightingNode
if let current = self.linkHighlightingNode { if let current = self.linkHighlightingNode {
@ -1327,11 +1309,11 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
guard let strongSelf = self else { guard let strongSelf = self else {
return return
} }
if let dustNode = strongSelf.dustNode, !dustNode.isRevealed, let textLayout = strongSelf.textNode.textNode.cachedLayout, textLayout.segments.contains(where: { !$0.spoilers.isEmpty }), let selectionRange { if !strongSelf.displayContentsUnderSpoilers, let textLayout = strongSelf.textNode.textNode.cachedLayout, textLayout.segments.contains(where: { !$0.spoilers.isEmpty }), let selectionRange {
for segment in textLayout.segments { for segment in textLayout.segments {
for (spoilerRange, _) in segment.spoilers { for (spoilerRange, _) in segment.spoilers {
if let intersection = selectionRange.intersection(spoilerRange), intersection.length > 0 { if let intersection = selectionRange.intersection(spoilerRange), intersection.length > 0 {
dustNode.update(revealed: true) strongSelf.updateDisplayContentsUnderSpoilers(value: true)
return return
} }
} }
@ -1387,12 +1369,22 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
}) })
} }
if let dustNode = self.dustNode, dustNode.isRevealed { if self.displayContentsUnderSpoilers {
dustNode.update(revealed: false) self.updateDisplayContentsUnderSpoilers(value: false)
} }
} }
} }
private func updateDisplayContentsUnderSpoilers(value: Bool) {
if self.displayContentsUnderSpoilers == value {
return
}
self.displayContentsUnderSpoilers = value
if let item = self.item {
item.controllerInteraction.requestMessageUpdate(item.message.id, false)
}
}
override public func reactionTargetView(value: MessageReaction.Reaction) -> UIView? { override public func reactionTargetView(value: MessageReaction.Reaction) -> UIView? {
if let statusNode = self.statusNode, !statusNode.isHidden { if let statusNode = self.statusNode, !statusNode.isHidden {
return statusNode.reactionView(value: value) return statusNode.reactionView(value: value)

View File

@ -33,19 +33,12 @@ private func generateBlockMaskImage() -> UIImage {
let colorSpace = CGColorSpaceCreateDeviceRGB() let colorSpace = CGColorSpaceCreateDeviceRGB()
var locations: [CGFloat] = [0.0, 0.5, 1.0] var locations: [CGFloat] = [0.0, 0.5, 1.0]
var colors: [CGColor] = [UIColor.black.withAlphaComponent(0.0).cgColor, UIColor.black.withAlphaComponent(0.0).cgColor, UIColor.black.cgColor] let colors: [CGColor] = [UIColor.black.withAlphaComponent(0.0).cgColor, UIColor.black.withAlphaComponent(0.0).cgColor, UIColor.black.cgColor]
var gradient = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: &locations)! let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: &locations)!
context.setBlendMode(.copy) context.setBlendMode(.copy)
context.drawRadialGradient(gradient, startCenter: CGPoint(x: size.width - 20.0, y: size.height), startRadius: 0.0, endCenter: CGPoint(x: size.width - 20.0, y: size.height), endRadius: 34.0, options: CGGradientDrawingOptions()) context.drawRadialGradient(gradient, startCenter: CGPoint(x: size.width - 20.0, y: size.height), startRadius: 0.0, endCenter: CGPoint(x: size.width - 20.0, y: size.height), endRadius: 34.0, options: CGGradientDrawingOptions())
locations = [0.0, 0.5, 1.0]
colors = [UIColor.black.withAlphaComponent(0.0).cgColor, UIColor.black.withAlphaComponent(0.4).cgColor, UIColor.black.cgColor]
gradient = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: &locations)!
context.setBlendMode(.destinationIn)
context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: size.height), end: CGPoint(x: 0.0, y: size.height - 18.0), options: CGGradientDrawingOptions())
})!.resizableImage(withCapInsets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: size.height - 1.0, right: size.width - 1.0), resizingMode: .stretch) })!.resizableImage(withCapInsets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: size.height - 1.0, right: size.width - 1.0), resizingMode: .stretch)
} }
@ -190,6 +183,10 @@ public final class InteractiveTextNodeSegment {
public let spoilerWords: [(NSRange, CGRect)] public let spoilerWords: [(NSRange, CGRect)]
public let embeddedItems: [InteractiveTextNodeLayout.EmbeddedItem] public let embeddedItems: [InteractiveTextNodeLayout.EmbeddedItem]
public var hasBlockQuote: Bool {
return self.blockQuote != nil
}
fileprivate init( fileprivate init(
lines: [InteractiveTextNodeLine], lines: [InteractiveTextNodeLine],
visibleLineCount: Int, visibleLineCount: Int,
@ -393,7 +390,7 @@ public final class InteractiveTextNodeLayout: NSObject {
fileprivate let textShadowColor: UIColor? fileprivate let textShadowColor: UIColor?
fileprivate let textShadowBlur: CGFloat? fileprivate let textShadowBlur: CGFloat?
fileprivate let textStroke: (UIColor, CGFloat)? fileprivate let textStroke: (UIColor, CGFloat)?
fileprivate let displayContentsUnderSpoilers: Bool public let displayContentsUnderSpoilers: Bool
fileprivate let expandedBlocks: Set<Int> fileprivate let expandedBlocks: Set<Int>
fileprivate init( fileprivate init(
@ -444,6 +441,33 @@ public final class InteractiveTextNodeLayout: NSObject {
self.expandedBlocks = expandedBlocks 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 { public var numberOfLines: Int {
var result = 0 var result = 0
for segment in self.segments { for segment in self.segments {
@ -1078,8 +1102,11 @@ open class InteractiveTextNode: ASDisplayNode, TextNodeProtocol, UIGestureRecogn
public var renderContentTypes: RenderContentTypes = .all public var renderContentTypes: RenderContentTypes = .all
private var contentItemLayers: [Int: TextContentItemLayer] = [:] private var contentItemLayers: [Int: TextContentItemLayer] = [:]
private var isDisplayingContentsUnderSpoilers: Bool?
public var canHandleTapAtPoint: ((CGPoint) -> Bool)? public var canHandleTapAtPoint: ((CGPoint) -> Bool)?
public var requestToggleBlockCollapsed: ((Int) -> Void)? public var requestToggleBlockCollapsed: ((Int) -> Void)?
public var requestDisplayContentsUnderSpoilers: (() -> Void)?
private var tapRecognizer: UITapGestureRecognizer? private var tapRecognizer: UITapGestureRecognizer?
public var currentText: NSAttributedString? { public var currentText: NSAttributedString? {
@ -1538,7 +1565,10 @@ open class InteractiveTextNode: ASDisplayNode, TextNodeProtocol, UIGestureRecogn
var descent: CGFloat = 0.0 var descent: CGFloat = 0.0
CTLineGetTypographicBounds(line.line, &ascent, &descent, nil) 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) addEmbeddedItem(item: embeddedItem, isHiddenBySpoiler: isHiddenBySpoiler, line: line, ascent: ascent, descent: descent, startIndex: range.location, endIndex: range.location + range.length)
} }
@ -1589,7 +1619,7 @@ open class InteractiveTextNode: ASDisplayNode, TextNodeProtocol, UIGestureRecogn
var segmentBlockQuote: InteractiveTextNodeBlockQuote? var segmentBlockQuote: InteractiveTextNodeBlockQuote?
if let blockQuote = segment.blockQuote, let tintColor = segment.tintColor, let blockIndex { if let blockQuote = segment.blockQuote, let tintColor = segment.tintColor, let blockIndex {
segmentBlockQuote = InteractiveTextNodeBlockQuote(id: blockIndex, frame: CGRect(origin: CGPoint(x: 0.0, y: blockMinY - 2.0), size: CGSize(width: blockWidth, height: blockMaxY - (blockMinY - 2.0) + 4.0)), data: blockQuote, tintColor: tintColor, secondaryTintColor: segment.secondaryTintColor, tertiaryTintColor: segment.tertiaryTintColor, backgroundColor: blockQuote.backgroundColor, isCollapsed: (blockQuote.isCollapsible && segmentLines.count > 3) ? isCollapsed : nil) segmentBlockQuote = InteractiveTextNodeBlockQuote(id: blockIndex, frame: CGRect(origin: CGPoint(x: 0.0, y: blockMinY - 2.0), size: CGSize(width: blockWidth, height: blockMaxY - (blockMinY - 2.0) + 3.0)), data: blockQuote, tintColor: tintColor, secondaryTintColor: segment.secondaryTintColor, tertiaryTintColor: segment.tertiaryTintColor, backgroundColor: blockQuote.backgroundColor, isCollapsed: (blockQuote.isCollapsible && segmentLines.count > 3) ? isCollapsed : nil)
} }
segments.append(InteractiveTextNodeSegment( segments.append(InteractiveTextNodeSegment(
@ -1651,6 +1681,10 @@ open class InteractiveTextNode: ASDisplayNode, TextNodeProtocol, UIGestureRecogn
return 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) let topLeftOffset = CGPoint(x: cachedLayout.insets.left, y: cachedLayout.insets.top)
var validIds: [Int] = [] var validIds: [Int] = []
@ -1704,7 +1738,7 @@ open class InteractiveTextNode: ASDisplayNode, TextNodeProtocol, UIGestureRecogn
self.layer.addSublayer(contentItemLayer) 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) contentItemAnimation.animator.updateFrame(layer: contentItemLayer, frame: contentItemFrame, completion: nil)
} }
@ -1739,6 +1773,12 @@ open class InteractiveTextNode: ASDisplayNode, TextNodeProtocol, UIGestureRecogn
@objc private func tapGesture(_ recognizer: UITapGestureRecognizer) { @objc private func tapGesture(_ recognizer: UITapGestureRecognizer) {
if case .ended = recognizer.state { if case .ended = recognizer.state {
let point = recognizer.location(in: self.view) 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) { if let blockId = self.collapsibleBlockAtPoint(point) {
self.requestToggleBlockCollapsed?(blockId) self.requestToggleBlockCollapsed?(blockId)
} }
@ -1749,7 +1789,7 @@ open class InteractiveTextNode: ASDisplayNode, TextNodeProtocol, UIGestureRecogn
let existingLayout: InteractiveTextNodeLayout? = maybeNode?.cachedLayout let existingLayout: InteractiveTextNodeLayout? = maybeNode?.cachedLayout
return { arguments in 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 { 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 let stringMatch: Bool
@ -1775,6 +1815,9 @@ open class InteractiveTextNode: ASDisplayNode, TextNodeProtocol, UIGestureRecogn
if stringMatch { if stringMatch {
layout = existingLayout layout = existingLayout
if layout.displayContentsUnderSpoilers != arguments.displayContentsUnderSpoilers {
layout = layout.withUpdatedDisplayContentsUnderSpoilers(arguments.displayContentsUnderSpoilers)
}
} else { } 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) 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)
} }
@ -1964,7 +2007,7 @@ final class TextContentItemLayer: SimpleLayer {
if attributes["Attribute__EmbeddedItem"] != nil { if attributes["Attribute__EmbeddedItem"] != nil {
continue continue
} }
if hasHiddenSpoilers && attributes["Attribute__Spoiler"] != nil || attributes["TelegramSpoiler"] != nil { if hasHiddenSpoilers && (attributes["Attribute__Spoiler"] != nil || attributes["TelegramSpoiler"] != nil) {
continue continue
} }
@ -2155,9 +2198,8 @@ final class TextContentItemLayer: SimpleLayer {
fatalError("init(coder:) has not been implemented") 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.item = item
self.setNeedsDisplay()
let contentFrame = CGRect(origin: CGPoint(), size: item.size) let contentFrame = CGRect(origin: CGPoint(), size: item.size)
var effectiveContentFrame = contentFrame var effectiveContentFrame = contentFrame
@ -2316,7 +2358,11 @@ final class TextContentItemLayer: SimpleLayer {
self.renderNode.params = RenderParams(size: contentFrame.size, item: item, mask: staticContentMask) self.renderNode.params = RenderParams(size: contentFrame.size, item: item, mask: staticContentMask)
if synchronously { if synchronously {
let previousContents = self.renderNode.layer.contents
self.renderNode.displayImmediately() self.renderNode.displayImmediately()
if animateContents, let previousContents {
animation.transition.animateContents(layer: self.renderNode.layer, from: previousContents)
}
} else { } else {
self.renderNode.setNeedsDisplay() self.renderNode.setNeedsDisplay()
} }

View File

@ -257,6 +257,11 @@ public final class InteractiveTextNodeWithEntities {
) { ) {
self.enableLooping = context.sharedContext.energyUsageSettings.loopEmoji self.enableLooping = context.sharedContext.energyUsageSettings.loopEmoji
var displayContentsUnderSpoilers = false
if let textLayout {
displayContentsUnderSpoilers = textLayout.displayContentsUnderSpoilers
}
var nextIndexById: [Int64: Int] = [:] var nextIndexById: [Int64: Int] = [:]
var validIds: [InlineStickerItemLayer.Key] = [] var validIds: [InlineStickerItemLayer.Key] = []
@ -291,6 +296,7 @@ public final class InteractiveTextNodeWithEntities {
itemFrame.origin.y += segmentItem.contentOffset.y itemFrame.origin.y += segmentItem.contentOffset.y
let itemLayerData: InlineStickerItemLayerData let itemLayerData: InlineStickerItemLayerData
var itemLayerTransition = animation.transition
if let current = self.inlineStickerItemLayers[id] { if let current = self.inlineStickerItemLayers[id] {
itemLayerData = current itemLayerData = current
itemLayerData.itemLayer.dynamicColor = item.textColor itemLayerData.itemLayer.dynamicColor = item.textColor
@ -299,6 +305,7 @@ public final class InteractiveTextNodeWithEntities {
segmentLayer.addSublayer(itemLayerData.itemLayer) segmentLayer.addSublayer(itemLayerData.itemLayer)
} }
} else { } else {
itemLayerTransition = .immediate
let pointSize = floor(itemSize * 1.3) 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)) 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 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.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.itemLayer.frame = itemFrame
itemLayerData.rect = itemFrame.offsetBy(dx: -segmentItem.contentOffset.x, dy: -segmentItem.contentOffset.y) 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) }, 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) } 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)
} }
} }
} }