mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-22 14:20:20 +00:00
Entity input: improved animation cache and rendering
This commit is contained in:
@@ -3,6 +3,7 @@ import UIKit
|
||||
import Display
|
||||
import AsyncDisplayKit
|
||||
import Postbox
|
||||
import TelegramCore
|
||||
import TelegramPresentationData
|
||||
import Emoji
|
||||
|
||||
@@ -17,8 +18,9 @@ public struct ChatTextInputAttributes {
|
||||
public static let textMention = NSAttributedString.Key(rawValue: "Attribute__TextMention")
|
||||
public static let textUrl = NSAttributedString.Key(rawValue: "Attribute__TextUrl")
|
||||
public static let spoiler = NSAttributedString.Key(rawValue: "Attribute__Spoiler")
|
||||
public static let customEmoji = NSAttributedString.Key(rawValue: "Attribute__CustomEmoji")
|
||||
|
||||
public static let allAttributes = [ChatTextInputAttributes.bold, ChatTextInputAttributes.italic, ChatTextInputAttributes.monospace, ChatTextInputAttributes.strikethrough, ChatTextInputAttributes.underline, ChatTextInputAttributes.textMention, ChatTextInputAttributes.textUrl, ChatTextInputAttributes.spoiler]
|
||||
public static let allAttributes = [ChatTextInputAttributes.bold, ChatTextInputAttributes.italic, ChatTextInputAttributes.monospace, ChatTextInputAttributes.strikethrough, ChatTextInputAttributes.underline, ChatTextInputAttributes.textMention, ChatTextInputAttributes.textUrl, ChatTextInputAttributes.spoiler, ChatTextInputAttributes.customEmoji]
|
||||
}
|
||||
|
||||
public func stateAttributedStringForText(_ text: NSAttributedString) -> NSAttributedString {
|
||||
@@ -28,7 +30,7 @@ public func stateAttributedStringForText(_ text: NSAttributedString) -> NSAttrib
|
||||
let fullRange = NSRange(sourceString.string.startIndex ..< sourceString.string.endIndex, in: sourceString.string)
|
||||
sourceString.enumerateAttribute(NSAttributedString.Key.attachment, in: fullRange, options: [.longestEffectiveRangeNotRequired], using: { value, range, stop in
|
||||
if let value = value as? EmojiTextAttachment {
|
||||
sourceString.replaceCharacters(in: range, with: NSAttributedString(string: value.emoji))
|
||||
sourceString.replaceCharacters(in: range, with: NSAttributedString(string: value.text, attributes: [ChatTextInputAttributes.customEmoji: value.emoji]))
|
||||
stop.pointee = true
|
||||
found = true
|
||||
}
|
||||
@@ -64,7 +66,7 @@ public struct ChatTextFontAttributes: OptionSet {
|
||||
public static let blockQuote = ChatTextFontAttributes(rawValue: 1 << 3)
|
||||
}
|
||||
|
||||
public func textAttributedStringForStateText(_ stateText: NSAttributedString, fontSize: CGFloat, textColor: UIColor, accentTextColor: UIColor, writingDirection: NSWritingDirection?, spoilersRevealed: Bool, availableEmojis: Set<String>, emojiViewProvider: ((String) -> UIView)?) -> NSAttributedString {
|
||||
public func textAttributedStringForStateText(_ stateText: NSAttributedString, fontSize: CGFloat, textColor: UIColor, accentTextColor: UIColor, writingDirection: NSWritingDirection?, spoilersRevealed: Bool, availableEmojis: Set<String>, emojiViewProvider: ((ChatTextInputTextCustomEmojiAttribute) -> UIView)?) -> NSAttributedString {
|
||||
let result = NSMutableAttributedString(string: stateText.string)
|
||||
let fullRange = NSRange(location: 0, length: result.length)
|
||||
|
||||
@@ -108,6 +110,8 @@ public func textAttributedStringForStateText(_ stateText: NSAttributedString, fo
|
||||
} else {
|
||||
result.addAttribute(NSAttributedString.Key.foregroundColor, value: UIColor.clear, range: range)
|
||||
}
|
||||
} else if key == ChatTextInputAttributes.customEmoji {
|
||||
result.addAttribute(key, value: value, range: range)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -215,6 +219,26 @@ public final class ChatTextInputTextUrlAttribute: NSObject {
|
||||
}
|
||||
}
|
||||
|
||||
public final class ChatTextInputTextCustomEmojiAttribute: NSObject {
|
||||
public let stickerPack: StickerPackReference
|
||||
public let fileId: Int64
|
||||
|
||||
public init(stickerPack: StickerPackReference, fileId: Int64) {
|
||||
self.stickerPack = stickerPack
|
||||
self.fileId = fileId
|
||||
|
||||
super.init()
|
||||
}
|
||||
|
||||
override public func isEqual(_ object: Any?) -> Bool {
|
||||
if let other = object as? ChatTextInputTextCustomEmojiAttribute {
|
||||
return self.stickerPack == other.stickerPack && self.fileId == other.fileId
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func textUrlRangesEqual(_ lhs: [(NSRange, ChatTextInputTextUrlAttribute)], _ rhs: [(NSRange, ChatTextInputTextUrlAttribute)]) -> Bool {
|
||||
if lhs.count != rhs.count {
|
||||
return false
|
||||
@@ -459,7 +483,7 @@ private func refreshTextUrls(text: NSString, initialAttributedText: NSAttributed
|
||||
}
|
||||
}
|
||||
|
||||
public func refreshChatTextInputAttributes(_ textNode: ASEditableTextNode, theme: PresentationTheme, baseFontSize: CGFloat, spoilersRevealed: Bool, availableEmojis: Set<String>, emojiViewProvider: ((String) -> UIView)?) {
|
||||
public func refreshChatTextInputAttributes(_ textNode: ASEditableTextNode, theme: PresentationTheme, baseFontSize: CGFloat, spoilersRevealed: Bool, availableEmojis: Set<String>, emojiViewProvider: ((ChatTextInputTextCustomEmojiAttribute) -> UIView)?) {
|
||||
guard let initialAttributedText = textNode.attributedText, initialAttributedText.length != 0 else {
|
||||
return
|
||||
}
|
||||
@@ -493,10 +517,14 @@ public func refreshChatTextInputAttributes(_ textNode: ASEditableTextNode, theme
|
||||
textNode.textView.textStorage.removeAttribute(ChatTextInputAttributes.textMention, range: fullRange)
|
||||
textNode.textView.textStorage.removeAttribute(ChatTextInputAttributes.textUrl, range: fullRange)
|
||||
textNode.textView.textStorage.removeAttribute(ChatTextInputAttributes.spoiler, range: fullRange)
|
||||
textNode.textView.textStorage.removeAttribute(ChatTextInputAttributes.customEmoji, range: fullRange)
|
||||
|
||||
textNode.textView.textStorage.addAttribute(NSAttributedString.Key.font, value: Font.regular(baseFontSize), range: fullRange)
|
||||
textNode.textView.textStorage.addAttribute(NSAttributedString.Key.foregroundColor, value: theme.chat.inputPanel.primaryTextColor, range: fullRange)
|
||||
|
||||
let replaceRanges: [(NSRange, EmojiTextAttachment)] = []
|
||||
|
||||
//var emojiIndex = 0
|
||||
attributedText.enumerateAttributes(in: fullRange, options: [], using: { attributes, range, _ in
|
||||
var fontAttributes: ChatTextFontAttributes = []
|
||||
|
||||
@@ -530,6 +558,17 @@ public func refreshChatTextInputAttributes(_ textNode: ASEditableTextNode, theme
|
||||
} else {
|
||||
textNode.textView.textStorage.addAttribute(NSAttributedString.Key.foregroundColor, value: UIColor.clear, range: range)
|
||||
}
|
||||
} else if key == ChatTextInputAttributes.customEmoji, let value = value as? ChatTextInputTextCustomEmojiAttribute {
|
||||
textNode.textView.textStorage.addAttribute(key, value: value, range: range)
|
||||
if let emojiViewProvider = emojiViewProvider {
|
||||
let _ = emojiViewProvider
|
||||
/*let emojiText = attributedText.attributedSubstring(from: range)
|
||||
let attachment = EmojiTextAttachment(index: emojiIndex, text: emojiText.string, emoji: value, viewProvider: emojiViewProvider)
|
||||
emojiIndex += 1
|
||||
attachment.bounds = CGRect(origin: CGPoint(), size: CGSize(width: 26.0, height: 16.0))
|
||||
|
||||
replaceRanges.append((range, attachment))*/
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -556,12 +595,16 @@ public func refreshChatTextInputAttributes(_ textNode: ASEditableTextNode, theme
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
for (range, attachment) in replaceRanges.sorted(by: { $0.0.location > $1.0.location }) {
|
||||
textNode.textView.textStorage.replaceCharacters(in: range, with: NSAttributedString(attachment: attachment))
|
||||
}
|
||||
}
|
||||
|
||||
if #available(iOS 15, *), let emojiViewProvider = emojiViewProvider {
|
||||
if #available(iOS 15, *), let _ = emojiViewProvider {
|
||||
let _ = CustomTextAttachmentViewProvider.ensureRegistered
|
||||
|
||||
var nextIndex: [String: Int] = [:]
|
||||
/*var nextIndex: [String: Int] = [:]
|
||||
|
||||
var count = 0
|
||||
|
||||
@@ -572,7 +615,7 @@ public func refreshChatTextInputAttributes(_ textNode: ASEditableTextNode, theme
|
||||
}
|
||||
})
|
||||
|
||||
while count < 10 {
|
||||
while count < 400 {
|
||||
var found = false
|
||||
textNode.textView.textStorage.string.enumerateSubstrings(in: textNode.textView.textStorage.string.startIndex ..< textNode.textView.textStorage.string.endIndex, options: [.byComposedCharacterSequences]) { substring, substringRange, _, stop in
|
||||
if let substring = substring {
|
||||
@@ -601,11 +644,11 @@ public func refreshChatTextInputAttributes(_ textNode: ASEditableTextNode, theme
|
||||
if !found {
|
||||
break
|
||||
}
|
||||
}
|
||||
}*/
|
||||
}
|
||||
}
|
||||
|
||||
public func refreshGenericTextInputAttributes(_ textNode: ASEditableTextNode, theme: PresentationTheme, baseFontSize: CGFloat, availableEmojis: Set<String>, emojiViewProvider: ((String) -> UIView)?, spoilersRevealed: Bool = false) {
|
||||
public func refreshGenericTextInputAttributes(_ textNode: ASEditableTextNode, theme: PresentationTheme, baseFontSize: CGFloat, availableEmojis: Set<String>, emojiViewProvider: ((ChatTextInputTextCustomEmojiAttribute) -> UIView)?, spoilersRevealed: Bool = false) {
|
||||
guard let initialAttributedText = textNode.attributedText, initialAttributedText.length != 0 else {
|
||||
return
|
||||
}
|
||||
@@ -902,10 +945,12 @@ public func convertMarkdownToAttributes(_ text: NSAttributedString) -> NSAttribu
|
||||
}
|
||||
|
||||
private final class EmojiTextAttachment: NSTextAttachment {
|
||||
let emoji: String
|
||||
let viewProvider: (String) -> UIView
|
||||
let text: String
|
||||
let emoji: ChatTextInputTextCustomEmojiAttribute
|
||||
let viewProvider: (ChatTextInputTextCustomEmojiAttribute) -> UIView
|
||||
|
||||
init(index: Int, emoji: String, viewProvider: @escaping (String) -> UIView) {
|
||||
init(index: Int, text: String, emoji: ChatTextInputTextCustomEmojiAttribute, viewProvider: @escaping (ChatTextInputTextCustomEmojiAttribute) -> UIView) {
|
||||
self.text = text
|
||||
self.emoji = emoji
|
||||
self.viewProvider = viewProvider
|
||||
|
||||
|
||||
@@ -145,26 +145,6 @@ private func commitEntity(_ utf16: String.UTF16View, _ type: CurrentEntityType,
|
||||
|
||||
public func generateChatInputTextEntities(_ text: NSAttributedString, maxAnimatedEmojisInText: Int? = nil) -> [MessageTextEntity] {
|
||||
var entities: [MessageTextEntity] = []
|
||||
|
||||
if let maxAnimatedEmojisInText = maxAnimatedEmojisInText, maxAnimatedEmojisInText != 0 {
|
||||
var count = 0
|
||||
text.string.enumerateSubstrings(in: text.string.startIndex ..< text.string.endIndex, options: [.byComposedCharacterSequences], { substring, substringRange, _, stop in
|
||||
if let substring = substring {
|
||||
let emoji = substring.basicEmoji.0
|
||||
|
||||
if !emoji.isEmpty && emoji.isSingleEmoji {
|
||||
let mappedRange = NSRange(substringRange, in: text.string)
|
||||
|
||||
entities.append(MessageTextEntity(range: mappedRange.lowerBound ..< mappedRange.upperBound, type: .AnimatedEmoji(nil)))
|
||||
|
||||
count += 1
|
||||
if count >= maxAnimatedEmojisInText {
|
||||
stop = true
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
text.enumerateAttributes(in: NSRange(location: 0, length: text.length), options: [], using: { attributes, range, _ in
|
||||
for (key, value) in attributes {
|
||||
@@ -184,6 +164,8 @@ public func generateChatInputTextEntities(_ text: NSAttributedString, maxAnimate
|
||||
entities.append(MessageTextEntity(range: range.lowerBound ..< range.upperBound, type: .TextUrl(url: value.url)))
|
||||
} else if key == ChatTextInputAttributes.spoiler {
|
||||
entities.append(MessageTextEntity(range: range.lowerBound ..< range.upperBound, type: .Spoiler))
|
||||
} else if key == ChatTextInputAttributes.customEmoji, let value = value as? ChatTextInputTextCustomEmojiAttribute {
|
||||
entities.append(MessageTextEntity(range: range.lowerBound ..< range.upperBound, type: .CustomEmoji(stickerPack: value.stickerPack, fileId: value.fileId)))
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -40,6 +40,8 @@ public func chatInputStateStringWithAppliedEntities(_ text: String, entities: [M
|
||||
string.addAttribute(ChatTextInputAttributes.underline, value: true as NSNumber, range: range)
|
||||
case .Spoiler:
|
||||
string.addAttribute(ChatTextInputAttributes.spoiler, value: true as NSNumber, range: range)
|
||||
case let .CustomEmoji(stickerPack, fileId):
|
||||
string.addAttribute(ChatTextInputAttributes.customEmoji, value: ChatTextInputTextCustomEmojiAttribute(stickerPack: stickerPack, fileId: fileId), range: range)
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user