Various Improvements

This commit is contained in:
Ilya Laktyushin 2021-12-16 00:47:26 +04:00
parent d8ec49897d
commit 0cc01c5280
16 changed files with 175 additions and 67 deletions

View File

@ -17,6 +17,7 @@ public class ImmediateTextNode: TextNode {
public var textShadowColor: UIColor? public var textShadowColor: UIColor?
public var textStroke: (UIColor, CGFloat)? public var textStroke: (UIColor, CGFloat)?
public var cutout: TextNodeCutout? public var cutout: TextNodeCutout?
public var displaySpoilers = false
public var truncationMode: NSLineBreakMode { public var truncationMode: NSLineBreakMode {
get { get {
@ -89,7 +90,7 @@ public class ImmediateTextNode: TextNode {
self.constrainedSize = constrainedSize self.constrainedSize = constrainedSize
let makeLayout = TextNode.asyncLayout(self) let makeLayout = TextNode.asyncLayout(self)
let (layout, apply) = makeLayout(TextNodeLayoutArguments(attributedString: self.attributedText, backgroundColor: nil, maximumNumberOfLines: self.maximumNumberOfLines, truncationType: self.truncationType, constrainedSize: constrainedSize, alignment: self.textAlignment, verticalAlignment: self.verticalAlignment, lineSpacing: self.lineSpacing, cutout: self.cutout, insets: self.insets, textShadowColor: self.textShadowColor, textStroke: self.textStroke)) let (layout, apply) = makeLayout(TextNodeLayoutArguments(attributedString: self.attributedText, backgroundColor: nil, maximumNumberOfLines: self.maximumNumberOfLines, truncationType: self.truncationType, constrainedSize: constrainedSize, alignment: self.textAlignment, verticalAlignment: self.verticalAlignment, lineSpacing: self.lineSpacing, cutout: self.cutout, insets: self.insets, textShadowColor: self.textShadowColor, textStroke: self.textStroke, displaySpoilers: self.displaySpoilers))
let _ = apply() let _ = apply()
if layout.numberOfLines > 1 { if layout.numberOfLines > 1 {
self.trailingLineWidth = layout.trailingLineWidth self.trailingLineWidth = layout.trailingLineWidth
@ -103,7 +104,7 @@ public class ImmediateTextNode: TextNode {
self.constrainedSize = constrainedSize self.constrainedSize = constrainedSize
let makeLayout = TextNode.asyncLayout(self) let makeLayout = TextNode.asyncLayout(self)
let (layout, apply) = makeLayout(TextNodeLayoutArguments(attributedString: self.attributedText, backgroundColor: nil, maximumNumberOfLines: self.maximumNumberOfLines, truncationType: self.truncationType, constrainedSize: constrainedSize, alignment: self.textAlignment, verticalAlignment: self.verticalAlignment, lineSpacing: self.lineSpacing, cutout: self.cutout, insets: self.insets)) let (layout, apply) = makeLayout(TextNodeLayoutArguments(attributedString: self.attributedText, backgroundColor: nil, maximumNumberOfLines: self.maximumNumberOfLines, truncationType: self.truncationType, constrainedSize: constrainedSize, alignment: self.textAlignment, verticalAlignment: self.verticalAlignment, lineSpacing: self.lineSpacing, cutout: self.cutout, insets: self.insets, displaySpoilers: self.displaySpoilers))
let _ = apply() let _ = apply()
return ImmediateTextNodeLayoutInfo(size: layout.size, truncated: layout.truncated) return ImmediateTextNodeLayoutInfo(size: layout.size, truncated: layout.truncated)
} }
@ -112,7 +113,7 @@ public class ImmediateTextNode: TextNode {
self.constrainedSize = constrainedSize self.constrainedSize = constrainedSize
let makeLayout = TextNode.asyncLayout(self) let makeLayout = TextNode.asyncLayout(self)
let (layout, apply) = makeLayout(TextNodeLayoutArguments(attributedString: self.attributedText, backgroundColor: nil, maximumNumberOfLines: self.maximumNumberOfLines, truncationType: self.truncationType, constrainedSize: constrainedSize, alignment: self.textAlignment, verticalAlignment: self.verticalAlignment, lineSpacing: self.lineSpacing, cutout: self.cutout, insets: self.insets)) let (layout, apply) = makeLayout(TextNodeLayoutArguments(attributedString: self.attributedText, backgroundColor: nil, maximumNumberOfLines: self.maximumNumberOfLines, truncationType: self.truncationType, constrainedSize: constrainedSize, alignment: self.textAlignment, verticalAlignment: self.verticalAlignment, lineSpacing: self.lineSpacing, cutout: self.cutout, insets: self.insets, displaySpoilers: self.displaySpoilers))
let _ = apply() let _ = apply()
return layout return layout
} }

View File

@ -973,13 +973,30 @@ public class TextNode: ASDisplayNode {
let lineCharacterCount = CTTypesetterSuggestLineBreak(typesetter, lastLineCharacterIndex, Double(lineConstrainedWidth)) let lineCharacterCount = CTTypesetterSuggestLineBreak(typesetter, lastLineCharacterIndex, Double(lineConstrainedWidth))
func addSpoiler(line: CTLine, ascent: CGFloat, descent: CGFloat, startIndex: Int, endIndex: Int) {
var secondaryLeftOffset: CGFloat = 0.0
let rawLeftOffset = CTLineGetOffsetForStringIndex(line, startIndex, &secondaryLeftOffset)
var leftOffset = floor(rawLeftOffset)
if !rawLeftOffset.isEqual(to: secondaryLeftOffset) {
leftOffset = floor(secondaryLeftOffset)
}
var secondaryRightOffset: CGFloat = 0.0
let rawRightOffset = CTLineGetOffsetForStringIndex(line, endIndex, &secondaryRightOffset)
var rightOffset = ceil(rawRightOffset)
if !rawRightOffset.isEqual(to: secondaryRightOffset) {
rightOffset = ceil(secondaryRightOffset)
}
spoilers.append(TextNodeSpoiler(range: NSMakeRange(startIndex, endIndex - startIndex + 1), frame: CGRect(x: min(leftOffset, rightOffset), y: descent - (ascent + descent), width: abs(rightOffset - leftOffset), height: ascent + descent)))
}
var isLastLine = false var isLastLine = false
if maximumNumberOfLines != 0 && lines.count == maximumNumberOfLines - 1 && lineCharacterCount > 0 { if maximumNumberOfLines != 0 && lines.count == maximumNumberOfLines - 1 && lineCharacterCount > 0 {
isLastLine = true isLastLine = true
} else if layoutSize.height + (fontLineSpacing + fontLineHeight) * 2.0 > constrainedSize.height { } else if layoutSize.height + (fontLineSpacing + fontLineHeight) * 2.0 > constrainedSize.height {
isLastLine = true isLastLine = true
} }
if isLastLine { if isLastLine {
if first { if first {
first = false first = false
@ -1022,25 +1039,27 @@ public class TextNode: ASDisplayNode {
var descent: CGFloat = 0.0 var descent: CGFloat = 0.0
CTLineGetTypographicBounds(coreTextLine, &ascent, &descent, nil) CTLineGetTypographicBounds(coreTextLine, &ascent, &descent, nil)
(attributedString.string as NSString).enumerateSubstrings(in: range, options: .byWords) { _, range, _, _ in var startIndex: Int?
let startIndex = range.location var currentIndex: Int?
let endIndex = range.location + range.length
let nsString = (attributedString.string as NSString)
var secondaryLeftOffset: CGFloat = 0.0 nsString.enumerateSubstrings(in: range, options: .byComposedCharacterSequences) { substring, range, _, _ in
let rawLeftOffset = CTLineGetOffsetForStringIndex(coreTextLine, startIndex, &secondaryLeftOffset) if let substring = substring, substring.rangeOfCharacter(from: .whitespacesAndNewlines) != nil {
var leftOffset = ceil(rawLeftOffset) if let currentStartIndex = startIndex {
if !rawLeftOffset.isEqual(to: secondaryLeftOffset) { startIndex = nil
leftOffset = ceil(secondaryLeftOffset) let endIndex = range.location
addSpoiler(line: coreTextLine, ascent: ascent, descent: descent, startIndex: currentStartIndex, endIndex: endIndex)
}
} else if startIndex == nil {
startIndex = range.location
} }
currentIndex = range.location + range.length
var secondaryRightOffset: CGFloat = 0.0 }
let rawRighOffset = CTLineGetOffsetForStringIndex(coreTextLine, endIndex, &secondaryRightOffset)
var rightOffset = ceil(rawRighOffset) if let currentStartIndex = startIndex, let currentIndex = currentIndex {
if !rawRighOffset.isEqual(to: secondaryRightOffset) { startIndex = nil
rightOffset = ceil(secondaryRightOffset) let endIndex = currentIndex
} addSpoiler(line: coreTextLine, ascent: ascent, descent: descent, startIndex: currentStartIndex, endIndex: endIndex)
spoilers.append(TextNodeSpoiler(range: range, frame: CGRect(x: min(leftOffset, rightOffset), y: descent - (ascent + descent), width: abs(rightOffset - leftOffset), height: ascent + descent)))
} }
} else if let _ = attributes[NSAttributedString.Key.strikethroughStyle] { } else if let _ = attributes[NSAttributedString.Key.strikethroughStyle] {
let lowerX = floor(CTLineGetOffsetForStringIndex(coreTextLine, range.location, nil)) let lowerX = floor(CTLineGetOffsetForStringIndex(coreTextLine, range.location, nil))
@ -1098,26 +1117,28 @@ public class TextNode: ASDisplayNode {
var ascent: CGFloat = 0.0 var ascent: CGFloat = 0.0
var descent: CGFloat = 0.0 var descent: CGFloat = 0.0
CTLineGetTypographicBounds(coreTextLine, &ascent, &descent, nil) CTLineGetTypographicBounds(coreTextLine, &ascent, &descent, nil)
var startIndex: Int?
var currentIndex: Int?
(attributedString.string as NSString).enumerateSubstrings(in: range, options: .byWords) { _, range, _, _ in let nsString = (attributedString.string as NSString)
let startIndex = range.location nsString.enumerateSubstrings(in: range, options: .byComposedCharacterSequences) { substring, range, _, _ in
let endIndex = range.location + range.length if let substring = substring, substring.rangeOfCharacter(from: .whitespacesAndNewlines) != nil {
if let currentStartIndex = startIndex {
var secondaryLeftOffset: CGFloat = 0.0 startIndex = nil
let rawLeftOffset = CTLineGetOffsetForStringIndex(coreTextLine, startIndex, &secondaryLeftOffset) let endIndex = range.location
var leftOffset = ceil(rawLeftOffset) addSpoiler(line: coreTextLine, ascent: ascent, descent: descent, startIndex: currentStartIndex, endIndex: endIndex)
if !rawLeftOffset.isEqual(to: secondaryLeftOffset) { }
leftOffset = ceil(secondaryLeftOffset) } else if startIndex == nil {
startIndex = range.location
} }
currentIndex = range.location + range.length
var secondaryRightOffset: CGFloat = 0.0 }
let rawRighOffset = CTLineGetOffsetForStringIndex(coreTextLine, endIndex, &secondaryRightOffset)
var rightOffset = ceil(rawRighOffset) if let currentStartIndex = startIndex, let currentIndex = currentIndex {
if !rawRighOffset.isEqual(to: secondaryRightOffset) { startIndex = nil
rightOffset = ceil(secondaryRightOffset) let endIndex = currentIndex
} addSpoiler(line: coreTextLine, ascent: ascent, descent: descent, startIndex: currentStartIndex, endIndex: endIndex)
spoilers.append(TextNodeSpoiler(range: range, frame: CGRect(x: min(leftOffset, rightOffset), y: descent - (ascent + descent), width: abs(rightOffset - leftOffset), height: ascent + descent)))
} }
} else if let _ = attributes[NSAttributedString.Key.strikethroughStyle] { } else if let _ = attributes[NSAttributedString.Key.strikethroughStyle] {
let lowerX = floor(CTLineGetOffsetForStringIndex(coreTextLine, range.location, nil)) let lowerX = floor(CTLineGetOffsetForStringIndex(coreTextLine, range.location, nil))

View File

@ -38,6 +38,7 @@ swift_library(
"//submodules/TextSelectionNode:TextSelectionNode", "//submodules/TextSelectionNode:TextSelectionNode",
"//submodules/Speak:Speak", "//submodules/Speak:Speak",
"//submodules/UndoUI:UndoUI", "//submodules/UndoUI:UndoUI",
"//submodules/InvisibleInkDustNode:InvisibleInkDustNode",
], ],
visibility = [ visibility = [
"//visibility:public", "//visibility:public",

View File

@ -21,6 +21,7 @@ import UrlEscaping
import UndoUI import UndoUI
import ManagedAnimationNode import ManagedAnimationNode
import TelegramUniversalVideoContent import TelegramUniversalVideoContent
import InvisibleInkDustNode
private let deleteImage = generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionTrash"), color: .white) private let deleteImage = generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionTrash"), color: .white)
private let actionImage = generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionForward"), color: .white) private let actionImage = generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionForward"), color: .white)
@ -130,6 +131,9 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
private let scrollNode: ASScrollNode private let scrollNode: ASScrollNode
private let textNode: ImmediateTextNode private let textNode: ImmediateTextNode
private var spoilerTextNode: ImmediateTextNode?
private var dustNode: InvisibleInkDustNode?
private let authorNameNode: ASTextNode private let authorNameNode: ASTextNode
private let dateNode: ASTextNode private let dateNode: ASTextNode
private let backwardButton: PlaybackButtonNode private let backwardButton: PlaybackButtonNode
@ -713,6 +717,41 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
} }
} }
private func updateSpoilers(textFrame: CGRect) {
if let textLayout = self.textNode.cachedLayout, !textLayout.spoilers.isEmpty {
if self.spoilerTextNode == nil {
let spoilerTextNode = ImmediateTextNode()
spoilerTextNode.attributedText = textNode.attributedText
spoilerTextNode.linkHighlightColor = UIColor(rgb: 0x5ac8fa, alpha: 0.2)
spoilerTextNode.displaySpoilers = true
spoilerTextNode.isHidden = false
spoilerTextNode.alpha = 0.0
spoilerTextNode.isUserInteractionEnabled = false
self.spoilerTextNode = spoilerTextNode
self.textNode.supernode?.insertSubnode(spoilerTextNode, aboveSubnode: self.textNode)
let dustNode = InvisibleInkDustNode(textNode: spoilerTextNode)
self.dustNode = dustNode
spoilerTextNode.supernode?.insertSubnode(dustNode, aboveSubnode: spoilerTextNode)
}
if let dustNode = self.dustNode {
dustNode.update(size: textFrame.size, color: .white, rects: textLayout.spoilers.map { $0.offsetBy(dx: 3.0, dy: 3.0).insetBy(dx: 0.0, dy: 1.0) })
dustNode.frame = textFrame.insetBy(dx: -3.0, dy: -3.0).offsetBy(dx: 0.0, dy: 3.0)
}
} else {
if let spoilerTextNode = self.spoilerTextNode {
self.spoilerTextNode = nil
spoilerTextNode.removeFromSupernode()
}
if let dustNode = self.dustNode {
self.dustNode = nil
dustNode.removeFromSupernode()
}
}
}
func setWebPage(_ webPage: TelegramMediaWebpage, media: Media) { func setWebPage(_ webPage: TelegramMediaWebpage, media: Media) {
self.currentWebPageAndMedia = (webPage, media) self.currentWebPageAndMedia = (webPage, media)
} }
@ -763,7 +802,9 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
let sideInset: CGFloat = 8.0 + leftInset let sideInset: CGFloat = 8.0 + leftInset
let topInset: CGFloat = 8.0 let topInset: CGFloat = 8.0
let textBottomInset: CGFloat = 8.0 let textBottomInset: CGFloat = 8.0
let textSize = self.textNode.updateLayout(CGSize(width: width - sideInset * 2.0, height: CGFloat.greatestFiniteMagnitude))
let constrainSize = CGSize(width: width - sideInset * 2.0, height: CGFloat.greatestFiniteMagnitude)
let textSize = self.textNode.updateLayout(constrainSize)
var textOffset: CGFloat = 0.0 var textOffset: CGFloat = 0.0
if displayCaption { if displayCaption {
@ -809,8 +850,14 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
} }
} }
textFrame = CGRect(origin: CGPoint(x: sideInset, y: topInset + textOffset), size: textSize) textFrame = CGRect(origin: CGPoint(x: sideInset, y: topInset + textOffset), size: textSize)
self.updateSpoilers(textFrame: textFrame)
let _ = self.spoilerTextNode?.updateLayout(constrainSize)
if self.textNode.frame != textFrame { if self.textNode.frame != textFrame {
self.textNode.frame = textFrame self.textNode.frame = textFrame
self.spoilerTextNode?.frame = textFrame
} }
} }

View File

@ -764,6 +764,12 @@ final class ChatImageGalleryItemNode: ZoomableContentGalleryItemNode {
} }
} }
} }
override func adjustForPreviewing() {
super.adjustForPreviewing()
self.recognitionOverlayContentNode.isHidden = true
}
} }
/*private func tileRectForImage(_ mappedImage: CGImage, rect: CGRect) -> CGRect { /*private func tileRectForImage(_ mappedImage: CGImage, rect: CGRect) -> CGRect {

View File

@ -276,9 +276,9 @@ public struct RecognizedContent: Codable {
} }
} }
private func recognizeContent(in image: UIImage) -> Signal<[RecognizedContent], NoError> { private func recognizeContent(in image: UIImage?) -> Signal<[RecognizedContent], NoError> {
if #available(iOS 11.0, *) { if #available(iOS 11.0, *) {
guard let cgImage = image.cgImage else { guard let cgImage = image?.cgImage else {
return .complete() return .complete()
} }
return Signal { subscriber in return Signal { subscriber in
@ -339,13 +339,15 @@ public func recognizedContent(postbox: Postbox, image: @escaping () -> UIImage?,
|> mapToSignal { cachedContent -> Signal<[RecognizedContent], NoError> in |> mapToSignal { cachedContent -> Signal<[RecognizedContent], NoError> in
if let cachedContent = cachedContent { if let cachedContent = cachedContent {
return .single(cachedContent.results) return .single(cachedContent.results)
} else if let image = image() {
return recognizeContent(in: image)
|> beforeNext { results in
let _ = updateCachedImageRecognizedContent(postbox: postbox, messageId: messageId, content: CachedImageRecognizedContent(results: results)).start()
}
} else { } else {
return .single([]) return (.complete()
|> delay(0.3, queue: Queue.concurrentDefaultQueue()))
|> then(
recognizeContent(in: image())
|> beforeNext { results in
let _ = updateCachedImageRecognizedContent(postbox: postbox, messageId: messageId, content: CachedImageRecognizedContent(results: results)).start()
}
)
} }
} }
} }

View File

@ -60,6 +60,8 @@ public class InvisibleInkDustNode: ASDisplayNode {
public var isRevealedUpdated: (Bool) -> Void = { _ in } public var isRevealedUpdated: (Bool) -> Void = { _ in }
public var isRevealed = false
public init(textNode: TextNode) { public init(textNode: TextNode) {
self.textNode = textNode self.textNode = textNode
@ -141,13 +143,12 @@ public class InvisibleInkDustNode: ASDisplayNode {
self.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tap(_:)))) self.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tap(_:))))
} }
private var revealed = false
@objc private func tap(_ gestureRecognizer: UITapGestureRecognizer) { @objc private func tap(_ gestureRecognizer: UITapGestureRecognizer) {
guard let (size, _, _) = self.currentParams, !self.revealed else { guard let (size, _, _) = self.currentParams, !self.isRevealed else {
return return
} }
self.revealed = true self.isRevealed = true
let position = gestureRecognizer.location(in: self.view) let position = gestureRecognizer.location(in: self.view)
self.emitterLayer?.setValue(true, forKeyPath: "emitterBehaviors.fingerAttractor.enabled") self.emitterLayer?.setValue(true, forKeyPath: "emitterBehaviors.fingerAttractor.enabled")
@ -185,8 +186,11 @@ public class InvisibleInkDustNode: ASDisplayNode {
self.emitterMaskFillNode.layer.removeAllAnimations() self.emitterMaskFillNode.layer.removeAllAnimations()
} }
Queue.mainQueue().after(4.0 * UIView.animationDurationFactor()) {
self.revealed = false let textLength = CGFloat((self.textNode?.cachedLayout?.attributedString?.string ?? "").count)
let timeToRead = min(45.0, ceil(max(4.0, textLength * 0.04)))
Queue.mainQueue().after(timeToRead * UIView.animationDurationFactor()) {
self.isRevealed = false
self.isRevealedUpdated(false) self.isRevealedUpdated(false)
let transition = ContainedViewLayoutTransition.animated(duration: 0.4, curve: .linear) let transition = ContainedViewLayoutTransition.animated(duration: 0.4, curve: .linear)

View File

@ -69,7 +69,7 @@ public final class SolidRoundedButtonNode: ASDisplayNode {
public var icon: UIImage? { public var icon: UIImage? {
didSet { didSet {
self.iconNode.image = icon self.iconNode.image = generateTintedImage(image: self.iconNode.image, color: self.theme.foregroundColor)
} }
} }
@ -111,7 +111,7 @@ public final class SolidRoundedButtonNode: ASDisplayNode {
self.iconNode = ASImageNode() self.iconNode = ASImageNode()
self.iconNode.displaysAsynchronously = false self.iconNode.displaysAsynchronously = false
self.iconNode.image = icon self.iconNode.image = generateTintedImage(image: icon, color: self.theme.foregroundColor)
super.init() super.init()
@ -215,6 +215,8 @@ public final class SolidRoundedButtonNode: ASDisplayNode {
self.titleNode.attributedText = NSAttributedString(string: self.title ?? "", font: self.font == .bold ? Font.semibold(self.fontSize) : Font.regular(self.fontSize), textColor: theme.foregroundColor) self.titleNode.attributedText = NSAttributedString(string: self.title ?? "", font: self.font == .bold ? Font.semibold(self.fontSize) : Font.regular(self.fontSize), textColor: theme.foregroundColor)
self.subtitleNode.attributedText = NSAttributedString(string: self.subtitle ?? "", font: Font.regular(14.0), textColor: theme.foregroundColor) self.subtitleNode.attributedText = NSAttributedString(string: self.subtitle ?? "", font: Font.regular(14.0), textColor: theme.foregroundColor)
self.iconNode.image = generateTintedImage(image: self.iconNode.image, color: theme.foregroundColor)
if let width = self.validLayout { if let width = self.validLayout {
_ = self.updateLayout(width: width, transition: .immediate) _ = self.updateLayout(width: width, transition: .immediate)
} }

View File

@ -10244,6 +10244,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
return nil return nil
} }
} }
controller.getCaptionPanelView = { [weak self] in
return self?.getCaptionPanelView()
}
strongSelf.effectiveNavigationController?.pushViewController(controller) strongSelf.effectiveNavigationController?.pushViewController(controller)
} }
}, presentSelectionLimitExceeded: { }, presentSelectionLimitExceeded: {
@ -10350,6 +10353,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
return nil return nil
} }
} }
controller.getCaptionPanelView = { [weak self] in
return self?.getCaptionPanelView()
}
strongSelf.present(controller, in: .window(.root), with: ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) strongSelf.present(controller, in: .window(.root), with: ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
} }
}) })

View File

@ -460,7 +460,9 @@ class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
override func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture, isEstimating: Bool) -> ChatMessageBubbleContentTapAction { override func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture, isEstimating: Bool) -> ChatMessageBubbleContentTapAction {
let textNodeFrame = self.textNode.frame let textNodeFrame = self.textNode.frame
if let (index, attributes) = self.textNode.attributesAtPoint(CGPoint(x: point.x - textNodeFrame.minX, y: point.y - textNodeFrame.minY)) { if let (index, attributes) = self.textNode.attributesAtPoint(CGPoint(x: point.x - textNodeFrame.minX, y: point.y - textNodeFrame.minY)) {
if let url = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] as? String { if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.Spoiler)], !(self.dustNode?.isRevealed ?? true) {
return .none
} else if let url = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] as? String {
var concealed = true var concealed = true
if let (attributeText, fullText) = self.textNode.attributeSubstring(name: TelegramTextAttributes.URL, index: index) { if let (attributeText, fullText) = self.textNode.attributeSubstring(name: TelegramTextAttributes.URL, index: index) {
concealed = !doesUrlMatchText(url: url, text: attributeText, fullText: fullText) concealed = !doesUrlMatchText(url: url, text: attributeText, fullText: fullText)
@ -517,6 +519,7 @@ class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
override func updateTouchesAtPoint(_ point: CGPoint?) { override func updateTouchesAtPoint(_ point: CGPoint?) {
if let item = self.item { if let item = self.item {
var rects: [CGRect]? var rects: [CGRect]?
var spoilerRects: [CGRect]?
if let point = point { if let point = point {
let textNodeFrame = self.textNode.frame let textNodeFrame = self.textNode.frame
if let (index, attributes) = self.textNode.attributesAtPoint(CGPoint(x: point.x - textNodeFrame.minX, y: point.y - textNodeFrame.minY)) { if let (index, attributes) = self.textNode.attributesAtPoint(CGPoint(x: point.x - textNodeFrame.minX, y: point.y - textNodeFrame.minY)) {
@ -535,10 +538,15 @@ class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
break break
} }
} }
if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.Spoiler)] {
spoilerRects = self.textNode.attributeRects(name: TelegramTextAttributes.Spoiler, at: index)
}
} }
} }
if let rects = rects { if let spoilerRects = spoilerRects, !spoilerRects.isEmpty, !(self.dustNode?.isRevealed ?? true) {
} else if let rects = rects {
let linkHighlightingNode: LinkHighlightingNode let linkHighlightingNode: LinkHighlightingNode
if let current = self.linkHighlightingNode { if let current = self.linkHighlightingNode {
linkHighlightingNode = current linkHighlightingNode = current

View File

@ -31,9 +31,9 @@ final class ChatTextInputMenu {
UIMenuItem(title: self.stringItalic, action: Selector(("formatAttributesItalic:"))), UIMenuItem(title: self.stringItalic, action: Selector(("formatAttributesItalic:"))),
UIMenuItem(title: self.stringMonospace, action: Selector(("formatAttributesMonospace:"))), UIMenuItem(title: self.stringMonospace, action: Selector(("formatAttributesMonospace:"))),
UIMenuItem(title: self.stringLink, action: Selector(("formatAttributesLink:"))), UIMenuItem(title: self.stringLink, action: Selector(("formatAttributesLink:"))),
UIMenuItem(title: self.stringSpoiler, action: Selector(("formatAttributesSpoiler:"))),
UIMenuItem(title: self.stringStrikethrough, action: Selector(("formatAttributesStrikethrough:"))), UIMenuItem(title: self.stringStrikethrough, action: Selector(("formatAttributesStrikethrough:"))),
UIMenuItem(title: self.stringUnderline, action: Selector(("formatAttributesUnderline:"))), UIMenuItem(title: self.stringUnderline, action: Selector(("formatAttributesUnderline:")))
UIMenuItem(title: self.stringSpoiler, action: Selector(("formatAttributesSpoiler:")))
] ]
} }

View File

@ -984,7 +984,7 @@ private func infoItems(data: PeerInfoScreenData?, context: AccountContext, prese
} }
if let aboutText = aboutText { if let aboutText = aboutText {
items[.peerInfo]!.append(PeerInfoScreenLabeledValueItem(id: ItemAbout, label: presentationData.strings.Channel_AboutItem, text: aboutText, textColor: .primary, textBehavior: .multiLine(maxLines: 100, enabledEntities: enabledPublicBioEntities), action: nil, longTapAction: bioContextAction, linkItemAction: bioLinkAction, requestLayout: { items[.peerInfo]!.append(PeerInfoScreenLabeledValueItem(id: ItemAbout, label: presentationData.strings.Channel_Info_Description, text: aboutText, textColor: .primary, textBehavior: .multiLine(maxLines: 100, enabledEntities: enabledPublicBioEntities), action: nil, longTapAction: bioContextAction, linkItemAction: bioLinkAction, requestLayout: {
interaction.requestLayout() interaction.requestLayout()
})) }))
} }
@ -1034,7 +1034,7 @@ private func infoItems(data: PeerInfoScreenData?, context: AccountContext, prese
} }
if let aboutText = aboutText { if let aboutText = aboutText {
items[.peerInfo]!.append(PeerInfoScreenLabeledValueItem(id: 0, label: presentationData.strings.PeerInfo_GroupAboutItem, text: aboutText, textColor: .primary, textBehavior: .multiLine(maxLines: 100, enabledEntities: enabledPublicBioEntities), action: nil, longTapAction: bioContextAction, linkItemAction: bioLinkAction, requestLayout: { items[.peerInfo]!.append(PeerInfoScreenLabeledValueItem(id: 0, label: presentationData.strings.Channel_Info_Description, text: aboutText, textColor: .primary, textBehavior: .multiLine(maxLines: 100, enabledEntities: enabledPublicBioEntities), action: nil, longTapAction: bioContextAction, linkItemAction: bioLinkAction, requestLayout: {
interaction.requestLayout() interaction.requestLayout()
})) }))
} }

View File

@ -86,7 +86,7 @@ public func textAttributedStringForStateText(_ stateText: NSAttributedString, fo
result.addAttribute(NSAttributedString.Key.underlineStyle, value: NSUnderlineStyle.single.rawValue as NSNumber, range: range) result.addAttribute(NSAttributedString.Key.underlineStyle, value: NSUnderlineStyle.single.rawValue as NSNumber, range: range)
} else if key == ChatTextInputAttributes.spoiler { } else if key == ChatTextInputAttributes.spoiler {
result.addAttribute(key, value: value, range: range) result.addAttribute(key, value: value, range: range)
result.addAttribute(NSAttributedString.Key.backgroundColor, value: textColor.withAlphaComponent(0.15), range: fullRange) result.addAttribute(NSAttributedString.Key.backgroundColor, value: textColor.withAlphaComponent(0.15), range: range)
} }
} }
@ -472,7 +472,7 @@ public func refreshChatTextInputAttributes(_ textNode: ASEditableTextNode, theme
textNode.textView.textStorage.addAttribute(NSAttributedString.Key.underlineStyle, value: NSUnderlineStyle.single.rawValue as NSNumber, range: range) textNode.textView.textStorage.addAttribute(NSAttributedString.Key.underlineStyle, value: NSUnderlineStyle.single.rawValue as NSNumber, range: range)
} else if key == ChatTextInputAttributes.spoiler { } else if key == ChatTextInputAttributes.spoiler {
textNode.textView.textStorage.addAttribute(key, value: value, range: range) textNode.textView.textStorage.addAttribute(key, value: value, range: range)
textNode.textView.textStorage.addAttribute(NSAttributedString.Key.backgroundColor, value: theme.chat.inputPanel.primaryTextColor.withAlphaComponent(0.15), range: fullRange) textNode.textView.textStorage.addAttribute(NSAttributedString.Key.backgroundColor, value: theme.chat.inputPanel.primaryTextColor.withAlphaComponent(0.15), range: range)
} }
} }
@ -564,7 +564,7 @@ public func refreshGenericTextInputAttributes(_ textNode: ASEditableTextNode, th
textNode.textView.textStorage.addAttribute(NSAttributedString.Key.underlineStyle, value: NSUnderlineStyle.single.rawValue as NSNumber, range: range) textNode.textView.textStorage.addAttribute(NSAttributedString.Key.underlineStyle, value: NSUnderlineStyle.single.rawValue as NSNumber, range: range)
} else if key == ChatTextInputAttributes.spoiler { } else if key == ChatTextInputAttributes.spoiler {
textNode.textView.textStorage.addAttribute(key, value: value, range: range) textNode.textView.textStorage.addAttribute(key, value: value, range: range)
textNode.textView.textStorage.addAttribute(NSAttributedString.Key.backgroundColor, value: theme.chat.inputPanel.primaryTextColor.withAlphaComponent(0.15), range: fullRange) textNode.textView.textStorage.addAttribute(NSAttributedString.Key.backgroundColor, value: UIColor.clear, range: range)
} }
} }

View File

@ -312,11 +312,14 @@ private func galleryItems(account: Account, results: [ChatContextResult], curren
return (galleryItems, focusItem) return (galleryItems, focusItem)
} }
func presentLegacyWebSearchGallery(context: AccountContext, peer: EnginePeer?, chatLocation: ChatLocation?, presentationData: PresentationData, results: [ChatContextResult], current: ChatContextResult, selectionContext: TGMediaSelectionContext?, editingContext: TGMediaEditingContext, updateHiddenMedia: @escaping (String?) -> Void, initialLayout: ContainerViewLayout?, transitionHostView: @escaping () -> UIView?, transitionView: @escaping (ChatContextResult) -> UIView?, completed: @escaping (ChatContextResult) -> Void, presentStickers: ((@escaping (TelegramMediaFile, Bool, UIView, CGRect) -> Void) -> TGPhotoPaintStickersScreen?)?, present: (ViewController, Any?) -> Void) { func presentLegacyWebSearchGallery(context: AccountContext, peer: EnginePeer?, chatLocation: ChatLocation?, presentationData: PresentationData, results: [ChatContextResult], current: ChatContextResult, selectionContext: TGMediaSelectionContext?, editingContext: TGMediaEditingContext, updateHiddenMedia: @escaping (String?) -> Void, initialLayout: ContainerViewLayout?, transitionHostView: @escaping () -> UIView?, transitionView: @escaping (ChatContextResult) -> UIView?, completed: @escaping (ChatContextResult) -> Void, presentStickers: ((@escaping (TelegramMediaFile, Bool, UIView, CGRect) -> Void) -> TGPhotoPaintStickersScreen?)?, getCaptionPanelView: @escaping () -> TGCaptionPanelView?, present: (ViewController, Any?) -> Void) {
let legacyController = LegacyController(presentation: .custom, theme: presentationData.theme, initialLayout: nil) let legacyController = LegacyController(presentation: .custom, theme: presentationData.theme, initialLayout: nil)
legacyController.statusBar.statusBarStyle = presentationData.theme.rootController.statusBarStyle.style legacyController.statusBar.statusBarStyle = presentationData.theme.rootController.statusBarStyle.style
let paintStickersContext = LegacyPaintStickersContext(context: context) let paintStickersContext = LegacyPaintStickersContext(context: context)
paintStickersContext.captionPanelView = {
return getCaptionPanelView()
}
paintStickersContext.presentStickersController = { completion in paintStickersContext.presentStickersController = { completion in
if let presentStickers = presentStickers { if let presentStickers = presentStickers {
return presentStickers({ file, animated, view, rect in return presentStickers({ file, animated, view, rect in

View File

@ -155,6 +155,12 @@ public final class WebSearchController: ViewController {
} }
} }
public var getCaptionPanelView: () -> TGCaptionPanelView? = { return nil } {
didSet {
self.controllerNode.getCaptionPanelView = self.getCaptionPanelView
}
}
public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, peer: EnginePeer?, chatLocation: ChatLocation?, configuration: SearchBotsConfiguration, mode: WebSearchControllerMode) { public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, peer: EnginePeer?, chatLocation: ChatLocation?, configuration: SearchBotsConfiguration, mode: WebSearchControllerMode) {
self.context = context self.context = context
self.mode = mode self.mode = mode

View File

@ -170,6 +170,7 @@ class WebSearchControllerNode: ASDisplayNode {
var dismissInput: (() -> Void)? var dismissInput: (() -> Void)?
var presentStickers: ((@escaping (TelegramMediaFile, Bool, UIView, CGRect) -> Void) -> TGPhotoPaintStickersScreen?)? var presentStickers: ((@escaping (TelegramMediaFile, Bool, UIView, CGRect) -> Void) -> TGPhotoPaintStickersScreen?)?
var getCaptionPanelView: () -> TGCaptionPanelView? = { return nil }
init(context: AccountContext, presentationData: PresentationData, controllerInteraction: WebSearchControllerInteraction, peer: EnginePeer?, chatLocation: ChatLocation?, mode: WebSearchMode) { init(context: AccountContext, presentationData: PresentationData, controllerInteraction: WebSearchControllerInteraction, peer: EnginePeer?, chatLocation: ChatLocation?, mode: WebSearchMode) {
self.context = context self.context = context
@ -700,7 +701,7 @@ class WebSearchControllerNode: ASDisplayNode {
strongSelf.controllerInteraction.sendSelected(results, result) strongSelf.controllerInteraction.sendSelected(results, result)
strongSelf.cancel?() strongSelf.cancel?()
} }
}, presentStickers: self.presentStickers, present: present) }, presentStickers: self.presentStickers, getCaptionPanelView: self.getCaptionPanelView, present: present)
} }
} else { } else {
if let results = self.currentProcessedResults?.results { if let results = self.currentProcessedResults?.results {