mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-10-09 11:23:48 +00:00
Various Improvements
This commit is contained in:
parent
d8ec49897d
commit
0cc01c5280
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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
|
|
||||||
|
|
||||||
var secondaryLeftOffset: CGFloat = 0.0
|
let nsString = (attributedString.string as NSString)
|
||||||
let rawLeftOffset = CTLineGetOffsetForStringIndex(coreTextLine, startIndex, &secondaryLeftOffset)
|
nsString.enumerateSubstrings(in: range, options: .byComposedCharacterSequences) { substring, range, _, _ in
|
||||||
var leftOffset = ceil(rawLeftOffset)
|
if let substring = substring, substring.rangeOfCharacter(from: .whitespacesAndNewlines) != nil {
|
||||||
if !rawLeftOffset.isEqual(to: secondaryLeftOffset) {
|
if let currentStartIndex = startIndex {
|
||||||
leftOffset = ceil(secondaryLeftOffset)
|
startIndex = nil
|
||||||
|
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
|
if let currentStartIndex = startIndex, let currentIndex = currentIndex {
|
||||||
let rawRighOffset = CTLineGetOffsetForStringIndex(coreTextLine, endIndex, &secondaryRightOffset)
|
startIndex = nil
|
||||||
var rightOffset = ceil(rawRighOffset)
|
let endIndex = currentIndex
|
||||||
if !rawRighOffset.isEqual(to: secondaryRightOffset) {
|
addSpoiler(line: coreTextLine, ascent: ascent, descent: descent, startIndex: currentStartIndex, endIndex: endIndex)
|
||||||
rightOffset = ceil(secondaryRightOffset)
|
|
||||||
}
|
|
||||||
|
|
||||||
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))
|
||||||
@ -1099,25 +1118,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
|
|
||||||
|
|
||||||
var secondaryLeftOffset: CGFloat = 0.0
|
let nsString = (attributedString.string as NSString)
|
||||||
let rawLeftOffset = CTLineGetOffsetForStringIndex(coreTextLine, startIndex, &secondaryLeftOffset)
|
nsString.enumerateSubstrings(in: range, options: .byComposedCharacterSequences) { substring, range, _, _ in
|
||||||
var leftOffset = ceil(rawLeftOffset)
|
if let substring = substring, substring.rangeOfCharacter(from: .whitespacesAndNewlines) != nil {
|
||||||
if !rawLeftOffset.isEqual(to: secondaryLeftOffset) {
|
if let currentStartIndex = startIndex {
|
||||||
leftOffset = ceil(secondaryLeftOffset)
|
startIndex = nil
|
||||||
|
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
|
if let currentStartIndex = startIndex, let currentIndex = currentIndex {
|
||||||
let rawRighOffset = CTLineGetOffsetForStringIndex(coreTextLine, endIndex, &secondaryRightOffset)
|
startIndex = nil
|
||||||
var rightOffset = ceil(rawRighOffset)
|
let endIndex = currentIndex
|
||||||
if !rawRighOffset.isEqual(to: secondaryRightOffset) {
|
addSpoiler(line: coreTextLine, ascent: ascent, descent: descent, startIndex: currentStartIndex, endIndex: endIndex)
|
||||||
rightOffset = ceil(secondaryRightOffset)
|
|
||||||
}
|
|
||||||
|
|
||||||
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))
|
||||||
|
@ -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",
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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 {
|
||||||
|
@ -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() {
|
} else {
|
||||||
return recognizeContent(in: image)
|
return (.complete()
|
||||||
|
|> delay(0.3, queue: Queue.concurrentDefaultQueue()))
|
||||||
|
|> then(
|
||||||
|
recognizeContent(in: image())
|
||||||
|> beforeNext { results in
|
|> beforeNext { results in
|
||||||
let _ = updateCachedImageRecognizedContent(postbox: postbox, messageId: messageId, content: CachedImageRecognizedContent(results: results)).start()
|
let _ = updateCachedImageRecognizedContent(postbox: postbox, messageId: messageId, content: CachedImageRecognizedContent(results: results)).start()
|
||||||
}
|
}
|
||||||
} else {
|
)
|
||||||
return .single([])
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -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
|
||||||
|
@ -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:")))
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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()
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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 {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user