Entity input: improved animation cache and rendering

This commit is contained in:
Ali
2022-06-24 02:06:02 +01:00
parent 0f1b382265
commit c112bc5146
37 changed files with 2557 additions and 383 deletions

View File

@@ -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

View File

@@ -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)))
}
}
})

View File

@@ -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
}