forked from shadowfacts/Tusker
Unify emoji replacement code
This commit is contained in:
parent
e7d9e3780e
commit
1c0291b1dd
|
@ -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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
self.attributedText = attributedString
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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()
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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 }
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue