Unify emoji replacement code

This commit is contained in:
Shadowfacts 2021-11-07 13:11:49 -05:00
parent e7d9e3780e
commit 1c0291b1dd
5 changed files with 31 additions and 50 deletions

View File

@ -19,10 +19,15 @@ protocol BaseEmojiLabel: AnyObject {
} }
extension BaseEmojiLabel { extension BaseEmojiLabel {
func replaceEmojis(in string: String, emojis: [Emoji], identifier: String, completion: @escaping (NSAttributedString) -> Void) { func replaceEmojis(in attributedString: NSAttributedString, emojis: [Emoji], identifier: String?, completion: @escaping (_ attributedString: NSAttributedString, _ didReplaceEmojis: Bool) -> Void) {
let matches = emojiRegex.matches(in: string, options: [], range: NSRange(location: 0, length: string.utf16.count)) guard !emojis.isEmpty else {
completion(attributedString, false)
return
}
let matches = emojiRegex.matches(in: attributedString.string, options: [], range: NSRange(location: 0, length: attributedString.length))
guard !matches.isEmpty else { guard !matches.isEmpty else {
completion(NSAttributedString(string: string)) completion(attributedString, false)
return return
} }
@ -33,8 +38,8 @@ extension BaseEmojiLabel {
for emoji in emojis { for emoji in emojis {
// only make requests for emojis that are present in the text to avoid making unnecessary network requests // only make requests for emojis that are present in the text to avoid making unnecessary network requests
guard matches.contains(where: { (match) in guard matches.contains(where: { (match) -> Bool in
let matchShortcode = (string as NSString).substring(with: match.range(at: 1)) let matchShortcode = (attributedString.string as NSString).substring(with: match.range(at: 1))
return emoji.shortcode == matchShortcode return emoji.shortcode == matchShortcode
}) else { }) else {
continue continue
@ -57,7 +62,7 @@ extension BaseEmojiLabel {
} }
guard foundEmojis else { guard foundEmojis else {
completion(NSAttributedString(string: string)) completion(attributedString, false)
return return
} }
@ -65,10 +70,10 @@ extension BaseEmojiLabel {
// if e.g. the account changes before all emojis are loaded, don't bother trying to set them // if e.g. the account changes before all emojis are loaded, don't bother trying to set them
guard let self = self, self.emojiIdentifier == identifier else { return } guard let self = self, self.emojiIdentifier == identifier else { return }
let mutAttrString = NSMutableAttributedString(string: string) let mutAttrString = NSMutableAttributedString(attributedString: attributedString)
// replaces the emojis starting from the end of the string as to not alter the indices of preceeding emojis // replaces the emojis starting from the end of the string as to not alter the indices of preceeding emojis
for match in matches.reversed() { for match in matches.reversed() {
let shortcode = (string as NSString).substring(with: match.range(at: 1)) let shortcode = (attributedString.string as NSString).substring(with: match.range(at: 1))
guard let emojiImage = emojiImages[shortcode] else { guard let emojiImage = emojiImages[shortcode] else {
continue continue
} }
@ -78,7 +83,11 @@ extension BaseEmojiLabel {
mutAttrString.replaceCharacters(in: match.range, with: attachmentStr) mutAttrString.replaceCharacters(in: match.range, with: attachmentStr)
} }
completion(mutAttrString) completion(mutAttrString, true)
} }
} }
func replaceEmojis(in string: String, emojis: [Emoji], identifier: String?, completion: @escaping (_ attributedString: NSAttributedString, _ didReplaceEmojis: Bool) -> Void) {
replaceEmojis(in: NSAttributedString(string: string), emojis: emojis, identifier: identifier, completion: completion)
}
} }

View File

@ -13,7 +13,7 @@ import SafariServices
private let emojiRegex = try! NSRegularExpression(pattern: ":(\\w+):", options: []) private let emojiRegex = try! NSRegularExpression(pattern: ":(\\w+):", options: [])
class ContentTextView: LinkTextView { class ContentTextView: LinkTextView, BaseEmojiLabel {
weak var navigationDelegate: TuskerNavigationDelegate? weak var navigationDelegate: TuskerNavigationDelegate?
weak var overrideMastodonController: MastodonController? weak var overrideMastodonController: MastodonController?
@ -24,6 +24,11 @@ class ContentTextView: LinkTextView {
private(set) var hasEmojis = false private(set) var hasEmojis = false
var emojiIdentifier: String?
var emojiRequests: [ImageCache.Request] = []
var emojiFont: UIFont { defaultFont }
var emojiTextColor: UIColor { defaultColor }
// The link range currently being previewed // The link range currently being previewed
private var currentPreviewedLinkRange: NSRange? private var currentPreviewedLinkRange: NSRange?
// The preview created in the previewForHighlighting method, so that we can use the same one in previewForDismissing. // The preview created in the previewForHighlighting method, so that we can use the same one in previewForDismissing.
@ -51,45 +56,11 @@ class ContentTextView: LinkTextView {
// MARK: - Emojis // MARK: - Emojis
func setEmojis(_ emojis: [Emoji]) { func setEmojis(_ emojis: [Emoji]) {
guard !emojis.isEmpty else { replaceEmojis(in: attributedText!, emojis: emojis, identifier: emojiIdentifier) { attributedString, didReplaceEmojis in
hasEmojis = false guard didReplaceEmojis else {
return return
}
hasEmojis = true
let emojiImages = MultiThreadDictionary<String, UIImage>(name: "ContentTextView Emoji Images")
let group = DispatchGroup()
for emoji in emojis {
group.enter()
_ = ImageCache.emojis.get(emoji.url) { (_, image) in
defer { group.leave() }
guard let image = image,
let transformedImage = ImageGrayscalifier.convertIfNecessary(url: emoji.url, image: image) else {
return
}
emojiImages[emoji.shortcode] = transformedImage
} }
} self.attributedText = attributedString
group.notify(queue: .main) {
let mutAttrString = NSMutableAttributedString(attributedString: self.attributedText!)
let string = mutAttrString.string
let matches = emojiRegex.matches(in: string, options: [], range: mutAttrString.fullRange)
// replaces the emojis started from the end of the string as to not alter the indexes of the other emojis
for match in matches.reversed() {
let shortcode = (string as NSString).substring(with: match.range(at: 1))
guard let emojiImage = emojiImages[shortcode] else {
continue
}
let attachment = NSTextAttachment(emojiImage: emojiImage, in: self.font!, with: self.textColor ?? .label)
let attachmentStr = NSAttributedString(attachment: attachment)
mutAttrString.replaceCharacters(in: match.range, with: attachmentStr)
}
self.attributedText = mutAttrString
self.setNeedsLayout() self.setNeedsLayout()
self.setNeedsDisplay() self.setNeedsDisplay()
} }

View File

@ -26,7 +26,7 @@ class EmojiLabel: UILabel, BaseEmojiLabel {
emojiRequests = [] emojiRequests = []
hasEmojis = true hasEmojis = true
replaceEmojis(in: attributedText.string, emojis: emojis, identifier: identifier) { [weak self] (newAttributedText) in replaceEmojis(in: attributedText.string, emojis: emojis, identifier: identifier) { [weak self] (newAttributedText, _) in
guard let self = self, self.emojiIdentifier == identifier else { return } guard let self = self, self.emojiIdentifier == identifier else { return }
self.attributedText = newAttributedText self.attributedText = newAttributedText
self.setNeedsLayout() self.setNeedsLayout()

View File

@ -37,7 +37,7 @@ class MultiSourceEmojiLabel: UILabel, BaseEmojiLabel {
recombine() recombine()
for (index, (string, emojis)) in pairs.enumerated() { for (index, (string, emojis)) in pairs.enumerated() {
self.replaceEmojis(in: string, emojis: emojis, identifier: identifier) { (attributedString) in self.replaceEmojis(in: string, emojis: emojis, identifier: identifier) { (attributedString, _) in
attributedStrings[index] = attributedString attributedStrings[index] = attributedString
DispatchQueue.main.async { [weak self] in DispatchQueue.main.async { [weak self] in
guard let self = self, self.emojiIdentifier == identifier else { return } guard let self = self, self.emojiIdentifier == identifier else { return }

View File

@ -15,6 +15,7 @@ class StatusContentTextView: ContentTextView {
func setTextFrom(status: StatusMO) { func setTextFrom(status: StatusMO) {
statusID = status.id statusID = status.id
emojiIdentifier = status.id
setTextFromHtml(status.content) setTextFromHtml(status.content)
setEmojis(status.emojis) setEmojis(status.emojis)
} }