parent
71bfd1513a
commit
37f6a0b4c8
|
@ -9,17 +9,16 @@
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
public class Emoji: Decodable {
|
public class Emoji: Decodable {
|
||||||
let shortcode: String
|
public let shortcode: String
|
||||||
let url: URL
|
public let url: URL
|
||||||
let staticURL: URL
|
public let staticURL: URL
|
||||||
// TODO: missing in pleroma
|
public let visibleInPicker: Bool
|
||||||
// let visibleInPicker: Bool
|
|
||||||
|
|
||||||
private enum CodingKeys: String, CodingKey {
|
private enum CodingKeys: String, CodingKey {
|
||||||
case shortcode
|
case shortcode
|
||||||
case url
|
case url
|
||||||
case staticURL = "static_url"
|
case staticURL = "static_url"
|
||||||
// case visibleInPicker = "visible_in_picker"
|
case visibleInPicker = "visible_in_picker"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,7 @@ class ImageCache {
|
||||||
static let avatars = ImageCache(name: "Avatars", memoryExpiry: .seconds(60 * 60), diskExpiry: .seconds(60 * 60 * 24))
|
static let avatars = ImageCache(name: "Avatars", memoryExpiry: .seconds(60 * 60), diskExpiry: .seconds(60 * 60 * 24))
|
||||||
static let headers = ImageCache(name: "Headers", memoryExpiry: .seconds(60 * 60), diskExpiry: .seconds(60 * 60 * 24))
|
static let headers = ImageCache(name: "Headers", memoryExpiry: .seconds(60 * 60), diskExpiry: .seconds(60 * 60 * 24))
|
||||||
static let attachments = ImageCache(name: "Attachments", memoryExpiry: .seconds(60 * 2))
|
static let attachments = ImageCache(name: "Attachments", memoryExpiry: .seconds(60 * 2))
|
||||||
|
static let emojis = ImageCache(name: "Emojis", memoryExpiry: .seconds(60 * 5), diskExpiry: .seconds(60 * 60))
|
||||||
|
|
||||||
let cache: Cache<Data>
|
let cache: Cache<Data>
|
||||||
|
|
||||||
|
|
|
@ -8,14 +8,74 @@
|
||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
import SafariServices
|
import SafariServices
|
||||||
import TTTAttributedLabel
|
|
||||||
import Pachyderm
|
import Pachyderm
|
||||||
import SwiftSoup
|
import SwiftSoup
|
||||||
|
|
||||||
class ContentLabel: LinkLabel {
|
class ContentLabel: LinkLabel {
|
||||||
|
|
||||||
|
private static let emojiRegex = try! NSRegularExpression(pattern: ":(\\w+):", options: [])
|
||||||
|
|
||||||
var navigationDelegate: TuskerNavigationDelegate?
|
var navigationDelegate: TuskerNavigationDelegate?
|
||||||
|
|
||||||
|
// MARK: - Emojis
|
||||||
|
func setEmojis(_ emojis: [Emoji]) {
|
||||||
|
guard !emojis.isEmpty else { return }
|
||||||
|
|
||||||
|
let group = DispatchGroup()
|
||||||
|
|
||||||
|
let mutAttrString = NSMutableAttributedString(attributedString: self.attributedText!)
|
||||||
|
let string = mutAttrString.string
|
||||||
|
let matches = ContentLabel.emojiRegex.matches(in: string, options: [], range: NSRange(location: 0, length: mutAttrString.length))
|
||||||
|
for match in matches.reversed() {
|
||||||
|
let shortcode = (string as NSString).substring(with: match.range(at: 1))
|
||||||
|
guard let emoji = emojis.first(where: { $0.shortcode == shortcode }) else {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
group.enter()
|
||||||
|
ImageCache.emojis.get(emoji.url) { (data) in
|
||||||
|
guard let data = data, let image = UIImage(data: data) else {
|
||||||
|
group.leave()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
let attachment = self.createEmojiTextAttachment(image: image, index: match.range.location)
|
||||||
|
mutAttrString.replaceCharacters(in: match.range, with: NSAttributedString(attachment: attachment))
|
||||||
|
|
||||||
|
group.leave()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
group.notify(queue: .main) {
|
||||||
|
self.attributedText = mutAttrString
|
||||||
|
self.setNeedsLayout()
|
||||||
|
self.setNeedsDisplay()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Based on https://github.com/ReticentJohn/Amaroq/blob/7c5b7088eb9fd1611dcb0f47d43bf8df093e142c/DireFloof/InlineImageHelpers.m
|
||||||
|
func createEmojiTextAttachment(image: UIImage, index: Int) -> NSTextAttachment {
|
||||||
|
let font = self.font!
|
||||||
|
|
||||||
|
let adjustedCapHeight = font.capHeight - 1
|
||||||
|
var imageSizeMatchingFontSize = CGSize(width: image.size.width * (adjustedCapHeight / image.size.height), height: adjustedCapHeight)
|
||||||
|
|
||||||
|
let defaultScale: CGFloat = 1.4
|
||||||
|
imageSizeMatchingFontSize = CGSize(width: imageSizeMatchingFontSize.width * defaultScale, height: imageSizeMatchingFontSize.height * defaultScale)
|
||||||
|
let textColor = self.textColor!
|
||||||
|
|
||||||
|
UIGraphicsBeginImageContextWithOptions(imageSizeMatchingFontSize, false, 0.0)
|
||||||
|
textColor.set()
|
||||||
|
image.draw(in: CGRect(origin: .zero, size: imageSizeMatchingFontSize))
|
||||||
|
let attachmentImage = UIGraphicsGetImageFromCurrentImageContext()
|
||||||
|
UIGraphicsEndImageContext()
|
||||||
|
|
||||||
|
let attachment = NSTextAttachment()
|
||||||
|
attachment.image = attachmentImage
|
||||||
|
return attachment
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - HTML Parsing
|
// MARK: - HTML Parsing
|
||||||
func setTextFromHtml(_ html: String) {
|
func setTextFromHtml(_ html: String) {
|
||||||
let doc = try! SwiftSoup.parse(html)
|
let doc = try! SwiftSoup.parse(html)
|
||||||
|
|
|
@ -16,6 +16,7 @@ class StatusContentLabel: ContentLabel {
|
||||||
guard let statusID = statusID else { return }
|
guard let statusID = statusID else { return }
|
||||||
guard let status = MastodonCache.status(for: statusID) else { fatalError("Can't set StatusContentLabel text without cached status \(statusID)") }
|
guard let status = MastodonCache.status(for: statusID) else { fatalError("Can't set StatusContentLabel text without cached status \(statusID)") }
|
||||||
setTextFromHtml(status.content)
|
setTextFromHtml(status.content)
|
||||||
|
setEmojis(status.emojis)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue