From faeb8d99efd9bf4b4139c03a3605b65b3e9d6822 Mon Sep 17 00:00:00 2001 From: CMK Date: Fri, 7 May 2021 18:25:57 +0800 Subject: [PATCH 1/2] feat: display custom emoji for timeline post --- .../CoreData.xcdatamodel/contents | 10 +-- CoreDataStack/Entity/MastodonUser.swift | 13 ++++ CoreDataStack/Entity/Status.swift | 19 +++-- Mastodon.xcodeproj/project.pbxproj | 8 +- .../xcshareddata/swiftpm/Package.resolved | 4 +- .../Section/ComposeStatusSection.swift | 3 +- .../Diffiable/Section/StatusSection.swift | 14 +++- Mastodon/Extension/ActiveLabel.swift | 20 ++++- Mastodon/Extension/CoreDataStack/Emojis.swift | 36 +++++++++ .../CoreDataStack/MastodonUser.swift | 3 + Mastodon/Extension/CoreDataStack/Status.swift | 3 + Mastodon/Helper/MastodonStatusContent.swift | 74 ++++++++++++------- .../Header/ProfileHeaderViewController.swift | 2 +- .../Header/View/ProfileFieldView.swift | 2 +- .../Settings/SettingsViewController.swift | 2 +- .../Scene/Share/View/Content/StatusView.swift | 14 ++-- .../CoreData/APIService+CoreData+Status.swift | 4 - 17 files changed, 166 insertions(+), 65 deletions(-) create mode 100644 Mastodon/Extension/CoreDataStack/Emojis.swift diff --git a/CoreDataStack/CoreData.xcdatamodeld/CoreData.xcdatamodel/contents b/CoreDataStack/CoreData.xcdatamodeld/CoreData.xcdatamodel/contents index 93d6e4731..6c2d177f8 100644 --- a/CoreDataStack/CoreData.xcdatamodeld/CoreData.xcdatamodel/contents +++ b/CoreDataStack/CoreData.xcdatamodeld/CoreData.xcdatamodel/contents @@ -1,5 +1,5 @@ - + @@ -45,7 +45,6 @@ - @@ -102,6 +101,7 @@ + @@ -197,6 +197,7 @@ + @@ -216,7 +217,6 @@ - @@ -267,12 +267,12 @@ - + - + diff --git a/CoreDataStack/Entity/MastodonUser.swift b/CoreDataStack/Entity/MastodonUser.swift index 714b6d0f6..e93d923c0 100644 --- a/CoreDataStack/Entity/MastodonUser.swift +++ b/CoreDataStack/Entity/MastodonUser.swift @@ -25,6 +25,9 @@ final public class MastodonUser: NSManagedObject { @NSManaged public private(set) var headerStatic: String? @NSManaged public private(set) var note: String? @NSManaged public private(set) var url: String? + + @NSManaged public private(set) var emojisData: Data? + @NSManaged public private(set) var statusesCount: NSNumber @NSManaged public private(set) var followingCount: NSNumber @NSManaged public private(set) var followersCount: NSNumber @@ -88,6 +91,8 @@ extension MastodonUser { user.headerStatic = property.headerStatic user.note = property.note user.url = property.url + user.emojisData = property.emojisData + user.statusesCount = NSNumber(value: property.statusesCount) user.followingCount = NSNumber(value: property.followingCount) user.followersCount = NSNumber(value: property.followersCount) @@ -151,6 +156,11 @@ extension MastodonUser { self.url = url } } + public func update(emojisData: Data?) { + if self.emojisData != emojisData { + self.emojisData = emojisData + } + } public func update(statusesCount: Int) { if self.statusesCount.intValue != statusesCount { self.statusesCount = NSNumber(value: statusesCount) @@ -270,6 +280,7 @@ extension MastodonUser { public let headerStatic: String? public let note: String? public let url: String? + public let emojisData: Data? public let statusesCount: Int public let followingCount: Int public let followersCount: Int @@ -292,6 +303,7 @@ extension MastodonUser { headerStatic: String?, note: String?, url: String?, + emojisData: Data?, statusesCount: Int, followingCount: Int, followersCount: Int, @@ -313,6 +325,7 @@ extension MastodonUser { self.headerStatic = headerStatic self.note = note self.url = url + self.emojisData = emojisData self.statusesCount = statusesCount self.followingCount = followingCount self.followersCount = followersCount diff --git a/CoreDataStack/Entity/Status.swift b/CoreDataStack/Entity/Status.swift index 1bb71a1db..79214ea42 100644 --- a/CoreDataStack/Entity/Status.swift +++ b/CoreDataStack/Entity/Status.swift @@ -24,6 +24,8 @@ public final class Status: NSManagedObject { @NSManaged public private(set) var spoilerText: String? @NSManaged public private(set) var application: Application? + @NSManaged public private(set) var emojisData: Data? + // Informational @NSManaged public private(set) var reblogsCount: NSNumber @NSManaged public private(set) var favouritesCount: NSNumber @@ -54,7 +56,6 @@ public final class Status: NSManagedObject { // one-to-many relationship @NSManaged public private(set) var reblogFrom: Set? @NSManaged public private(set) var mentions: Set? - @NSManaged public private(set) var emojis: Set? @NSManaged public private(set) var tags: Set? @NSManaged public private(set) var homeTimelineIndexes: Set? @NSManaged public private(set) var mediaAttachments: Set? @@ -77,7 +78,6 @@ extension Status { replyTo: Status?, poll: Poll?, mentions: [Mention]?, - emojis: [Emoji]?, tags: [Tag]?, mediaAttachments: [Attachment]?, favouritedBy: MastodonUser?, @@ -100,6 +100,8 @@ extension Status { status.sensitive = property.sensitive status.spoilerText = property.spoilerText status.application = application + + status.emojisData = property.emojisData status.reblogsCount = property.reblogsCount status.favouritesCount = property.favouritesCount @@ -121,9 +123,6 @@ extension Status { if let mentions = mentions { status.mutableSetValue(forKey: #keyPath(Status.mentions)).addObjects(from: mentions) } - if let emojis = emojis { - status.mutableSetValue(forKey: #keyPath(Status.emojis)).addObjects(from: emojis) - } if let tags = tags { status.mutableSetValue(forKey: #keyPath(Status.tags)).addObjects(from: tags) } @@ -148,6 +147,12 @@ extension Status { return status } + public func update(emojisData: Data?) { + if self.emojisData != emojisData { + self.emojisData = emojisData + } + } + public func update(reblogsCount: NSNumber) { if self.reblogsCount.intValue != reblogsCount.intValue { self.reblogsCount = reblogsCount @@ -248,6 +253,8 @@ extension Status { public let sensitive: Bool public let spoilerText: String? + public let emojisData: Data? + public let reblogsCount: NSNumber public let favouritesCount: NSNumber public let repliesCount: NSNumber? @@ -269,6 +276,7 @@ extension Status { visibility: String?, sensitive: Bool, spoilerText: String?, + emojisData: Data?, reblogsCount: NSNumber, favouritesCount: NSNumber, repliesCount: NSNumber?, @@ -288,6 +296,7 @@ extension Status { self.visibility = visibility self.sensitive = sensitive self.spoilerText = spoilerText + self.emojisData = emojisData self.reblogsCount = reblogsCount self.favouritesCount = favouritesCount self.repliesCount = repliesCount diff --git a/Mastodon.xcodeproj/project.pbxproj b/Mastodon.xcodeproj/project.pbxproj index ff407b6ed..2f37b291a 100644 --- a/Mastodon.xcodeproj/project.pbxproj +++ b/Mastodon.xcodeproj/project.pbxproj @@ -400,6 +400,7 @@ DBAE3F9E2616E308004B8251 /* APIService+Mute.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBAE3F9D2616E308004B8251 /* APIService+Mute.swift */; }; DBAE3FA92617106E004B8251 /* MastodonMetricFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBAE3FA82617106E004B8251 /* MastodonMetricFormatter.swift */; }; DBAE3FAF26172FC0004B8251 /* RemoteProfileViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBAE3FAE26172FC0004B8251 /* RemoteProfileViewModel.swift */; }; + DBAFB7352645463500371D5F /* Emojis.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBAFB7342645463500371D5F /* Emojis.swift */; }; DBB525082611EAC0002F1F29 /* Tabman in Frameworks */ = {isa = PBXBuildFile; productRef = DBB525072611EAC0002F1F29 /* Tabman */; }; DBB5250E2611EBAF002F1F29 /* ProfileSegmentedViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBB5250D2611EBAF002F1F29 /* ProfileSegmentedViewController.swift */; }; DBB525212611EBD6002F1F29 /* ProfilePagingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBB525202611EBD6002F1F29 /* ProfilePagingViewController.swift */; }; @@ -962,6 +963,7 @@ DBAE3F9D2616E308004B8251 /* APIService+Mute.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+Mute.swift"; sourceTree = ""; }; DBAE3FA82617106E004B8251 /* MastodonMetricFormatter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonMetricFormatter.swift; sourceTree = ""; }; DBAE3FAE26172FC0004B8251 /* RemoteProfileViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoteProfileViewModel.swift; sourceTree = ""; }; + DBAFB7342645463500371D5F /* Emojis.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Emojis.swift; sourceTree = ""; }; DBB5250D2611EBAF002F1F29 /* ProfileSegmentedViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileSegmentedViewController.swift; sourceTree = ""; }; DBB525202611EBD6002F1F29 /* ProfilePagingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfilePagingViewController.swift; sourceTree = ""; }; DBB5252F2611EBF3002F1F29 /* ProfilePagingViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfilePagingViewModel.swift; sourceTree = ""; }; @@ -1594,6 +1596,7 @@ DB6D9F6E2635807F008423CD /* Setting.swift */, DB6D9F4826353FD6008423CD /* Subscription.swift */, DB6D9F4F2635761F008423CD /* SubscriptionAlerts.swift */, + DBAFB7342645463500371D5F /* Emojis.swift */, ); path = CoreDataStack; sourceTree = ""; @@ -3199,6 +3202,7 @@ DB6180ED26391C6C0018D199 /* TransitioningMath.swift in Sources */, 2D6DE40026141DF600A63F6A /* SearchViewModel.swift in Sources */, DB51D172262832380062B7A1 /* BlurHashDecode.swift in Sources */, + DBAFB7352645463500371D5F /* Emojis.swift in Sources */, DBCC3B89261454BA0045B23D /* CGImage.swift in Sources */, DBCCC71E25F73297007E1AB6 /* APIService+Reblog.swift in Sources */, DB789A2B25F9F7AB0071ACA0 /* ComposeRepliedToStatusContentCollectionViewCell.swift in Sources */, @@ -3913,8 +3917,8 @@ isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/TwidereProject/ActiveLabel.swift"; requirement = { - kind = upToNextMajorVersion; - minimumVersion = 4.0.0; + kind = exactVersion; + version = 5.0.1; }; }; 2D5981B825E4D7F8000FB903 /* XCRemoteSwiftPackageReference "ThirdPartyMailer" */ = { diff --git a/Mastodon.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Mastodon.xcworkspace/xcshareddata/swiftpm/Package.resolved index 3295adb41..c32d3a6b2 100644 --- a/Mastodon.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Mastodon.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -6,8 +6,8 @@ "repositoryURL": "https://github.com/TwidereProject/ActiveLabel.swift", "state": { "branch": null, - "revision": "d6cf96e0ca4f2269021bcf8f11381ab57897f84a", - "version": "4.0.0" + "revision": "40e104063d825d1125ef4b8eeb6460eba8a57483", + "version": "5.0.1" } }, { diff --git a/Mastodon/Diffiable/Section/ComposeStatusSection.swift b/Mastodon/Diffiable/Section/ComposeStatusSection.swift index 91363ef09..2b7aecae0 100644 --- a/Mastodon/Diffiable/Section/ComposeStatusSection.swift +++ b/Mastodon/Diffiable/Section/ComposeStatusSection.swift @@ -68,7 +68,8 @@ extension ComposeStatusSection { }() cell.statusView.usernameLabel.text = "@" + (status.reblog ?? status).author.acct // set text - cell.statusView.activeTextLabel.configure(content: status.content) + //status.emoji + cell.statusView.activeTextLabel.configure(content: status.content, emojiDict: [:]) // set date cell.statusView.dateLabel.text = status.createdAt.shortTimeAgoSinceNow diff --git a/Mastodon/Diffiable/Section/StatusSection.swift b/Mastodon/Diffiable/Section/StatusSection.swift index 3994229ee..d069266f8 100644 --- a/Mastodon/Diffiable/Section/StatusSection.swift +++ b/Mastodon/Diffiable/Section/StatusSection.swift @@ -158,10 +158,11 @@ extension StatusSection { .store(in: &cell.disposeBag) // set name username - cell.statusView.nameLabel.text = { + let nameText: String = { let author = (status.reblog ?? status).author return author.displayName.isEmpty ? author.username : author.displayName }() + cell.statusView.nameLabel.configure(content: nameText, emojiDict: (status.reblog ?? status).author.emojiDict) cell.statusView.usernameLabel.text = "@" + (status.reblog ?? status).author.acct // set avatar if let reblog = status.reblog { @@ -176,7 +177,10 @@ extension StatusSection { } // set text - cell.statusView.activeTextLabel.configure(content: (status.reblog ?? status).content) + cell.statusView.activeTextLabel.configure( + content: (status.reblog ?? status).content, + emojiDict: (status.reblog ?? status).emojiDict + ) // prepare media attachments let mediaAttachments = Array((status.reblog ?? status).mediaAttachments ?? []).sorted { $0.index.compare($1.index) == .orderedAscending } @@ -569,15 +573,16 @@ extension StatusSection { if status.reblog != nil { cell.statusView.headerContainerView.isHidden = false cell.statusView.headerIconLabel.attributedText = StatusView.iconAttributedString(image: StatusView.reblogIconImage) - cell.statusView.headerInfoLabel.text = { + let headerText: String = { let author = status.author let name = author.displayName.isEmpty ? author.username : author.displayName return L10n.Common.Controls.Status.userReblogged(name) }() + cell.statusView.headerInfoLabel.configure(content: headerText, emojiDict: status.author.emojiDict) } else if status.inReplyToID != nil { cell.statusView.headerContainerView.isHidden = false cell.statusView.headerIconLabel.attributedText = StatusView.iconAttributedString(image: StatusView.replyIconImage) - cell.statusView.headerInfoLabel.text = { + let headerText: String = { guard let replyTo = status.replyTo else { return L10n.Common.Controls.Status.userRepliedTo("-") } @@ -585,6 +590,7 @@ extension StatusSection { let name = author.displayName.isEmpty ? author.username : author.displayName return L10n.Common.Controls.Status.userRepliedTo(name) }() + cell.statusView.headerInfoLabel.configure(content: headerText, emojiDict: status.replyTo?.author.emojiDict ?? [:]) } else { cell.statusView.headerContainerView.isHidden = true } diff --git a/Mastodon/Extension/ActiveLabel.swift b/Mastodon/Extension/ActiveLabel.swift index 66452e23e..d929cb571 100644 --- a/Mastodon/Extension/ActiveLabel.swift +++ b/Mastodon/Extension/ActiveLabel.swift @@ -14,6 +14,8 @@ extension ActiveLabel { enum Style { case `default` + case statusHeader + case statusName case profileField } @@ -25,6 +27,7 @@ extension ActiveLabel { mentionColor = Asset.Colors.Label.highlight.color hashtagColor = Asset.Colors.Label.highlight.color URLColor = Asset.Colors.Label.highlight.color + emojiPlaceholderColor = .systemFill #if DEBUG text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua." #endif @@ -33,6 +36,14 @@ extension ActiveLabel { case .default: font = .preferredFont(forTextStyle: .body) textColor = Asset.Colors.Label.primary.color + case .statusHeader: + font = UIFontMetrics(forTextStyle: .footnote).scaledFont(for: .systemFont(ofSize: 13, weight: .medium)) + textColor = Asset.Colors.Label.secondary.color + numberOfLines = 1 + case .statusName: + font = .systemFont(ofSize: 17, weight: .semibold) + textColor = Asset.Colors.Label.primary.color + numberOfLines = 1 case .profileField: font = .preferredFont(forTextStyle: .body) textColor = Asset.Colors.Label.primary.color @@ -44,9 +55,10 @@ extension ActiveLabel { extension ActiveLabel { /// status content - func configure(content: String) { + func configure(content: String, emojiDict: MastodonStatusContent.EmojiDict) { activeEntities.removeAll() - if let parseResult = try? MastodonStatusContent.parse(status: content) { + + if let parseResult = try? MastodonStatusContent.parse(content: content, emojiDict: emojiDict) { text = parseResult.trimmed activeEntities = parseResult.activeEntities } else { @@ -55,8 +67,8 @@ extension ActiveLabel { } /// account note - func configure(note: String) { - configure(content: note) + func configure(note: String, emojiDict: MastodonStatusContent.EmojiDict) { + configure(content: note, emojiDict: emojiDict) } } diff --git a/Mastodon/Extension/CoreDataStack/Emojis.swift b/Mastodon/Extension/CoreDataStack/Emojis.swift new file mode 100644 index 000000000..87ae50171 --- /dev/null +++ b/Mastodon/Extension/CoreDataStack/Emojis.swift @@ -0,0 +1,36 @@ +// +// Emojis.swift +// Mastodon +// +// Created by MainasuK Cirno on 2021-5-7. +// + +import Foundation +import MastodonSDK + +protocol EmojiContinaer { + var emojisData: Data? { get } +} + +extension EmojiContinaer { + + static func encode(emojis: [Mastodon.Entity.Emoji]) -> Data? { + return try? JSONEncoder().encode(emojis) + } + + var emojis: [Mastodon.Entity.Emoji]? { + let decoder = JSONDecoder() + return emojisData.flatMap { try? decoder.decode([Mastodon.Entity.Emoji].self, from: $0) } + } + + var emojiDict: MastodonStatusContent.EmojiDict { + var dict = MastodonStatusContent.EmojiDict() + for emoji in emojis ?? [] { + guard let url = URL(string: emoji.url) else { continue } + dict[emoji.shortcode] = url + } + return dict + } + +} + diff --git a/Mastodon/Extension/CoreDataStack/MastodonUser.swift b/Mastodon/Extension/CoreDataStack/MastodonUser.swift index b780f5916..8180b0255 100644 --- a/Mastodon/Extension/CoreDataStack/MastodonUser.swift +++ b/Mastodon/Extension/CoreDataStack/MastodonUser.swift @@ -23,6 +23,7 @@ extension MastodonUser.Property { headerStatic: entity.headerStatic, note: entity.note, url: entity.url, + emojisData: entity.emojis.flatMap { Status.encode(emojis: $0) }, statusesCount: entity.statusesCount, followingCount: entity.followingCount, followersCount: entity.followersCount, @@ -98,3 +99,5 @@ extension MastodonUser { return items } } + +extension MastodonUser: EmojiContinaer { } diff --git a/Mastodon/Extension/CoreDataStack/Status.swift b/Mastodon/Extension/CoreDataStack/Status.swift index 1a909285d..701f9243e 100644 --- a/Mastodon/Extension/CoreDataStack/Status.swift +++ b/Mastodon/Extension/CoreDataStack/Status.swift @@ -20,6 +20,7 @@ extension Status.Property { visibility: entity.visibility?.rawValue, sensitive: entity.sensitive ?? false, spoilerText: entity.spoilerText, + emojisData: entity.emojis.flatMap { Status.encode(emojis: $0) }, reblogsCount: NSNumber(value: entity.reblogsCount), favouritesCount: NSNumber(value: entity.favouritesCount), repliesCount: entity.repliesCount.flatMap { NSNumber(value: $0) }, @@ -86,3 +87,5 @@ extension Status { return items } } + +extension Status: EmojiContinaer { } diff --git a/Mastodon/Helper/MastodonStatusContent.swift b/Mastodon/Helper/MastodonStatusContent.swift index 5b535b806..284b726a6 100755 --- a/Mastodon/Helper/MastodonStatusContent.swift +++ b/Mastodon/Helper/MastodonStatusContent.swift @@ -11,9 +11,21 @@ import ActiveLabel enum MastodonStatusContent { - static func parse(status: String) throws -> MastodonStatusContent.ParseResult { - let status = status.replacingOccurrences(of: "
", with: "\n") - let rootNode = try Node.parse(document: status) + typealias EmojiShortcode = String + typealias EmojiDict = [EmojiShortcode: URL] + + static func parse(content: String, emojiDict: EmojiDict) throws -> MastodonStatusContent.ParseResult { + let document: String = { + var content = content + content = content.replacingOccurrences(of: "
", with: "\n") + for (shortcode, url) in emojiDict { + let emojiNode = "\(shortcode)" + let pattern = ":\(shortcode):" + content = content.replacingOccurrences(of: pattern, with: emojiNode) + } + return content + }() + let rootNode = try Node.parse(document: document) let text = String(rootNode.text) var activeEntities: [ActiveEntity] = [] @@ -25,7 +37,7 @@ enum MastodonStatusContent { case .url: guard let href = entity.href else { continue } let text = String(entity.text) - activeEntities.append(ActiveEntity(range: range, type: .url(text, trimmed: entity.hrefEllipsis ?? text, url: href))) + activeEntities.append(ActiveEntity(range: range, type: .url(text, trimmed: entity.hrefEllipsis ?? text, url: href, userInfo: nil))) case .hashtag: var userInfo: [AnyHashable: Any] = [:] entity.href.flatMap { href in @@ -40,30 +52,47 @@ enum MastodonStatusContent { } let mention = String(entity.text).deletingPrefix("@") activeEntities.append(ActiveEntity(range: range, type: .mention(mention, userInfo: userInfo))) - default: + case .emoji: + var userInfo: [AnyHashable: Any] = [:] + guard let href = entity.href else { continue } + userInfo["href"] = href + let emoji = String(entity.text) + activeEntities.append(ActiveEntity(range: range, type: .emoji(emoji, url: href, userInfo: userInfo))) + case .none: continue } } var trimmed = text for activeEntity in activeEntities { - guard case .url = activeEntity.type else { continue } - MastodonStatusContent.trimEntity(status: &trimmed, activeEntity: activeEntity, activeEntities: activeEntities) + MastodonStatusContent.trimEntity(toot: &trimmed, activeEntity: activeEntity, activeEntities: activeEntities) } return ParseResult( - document: status, + document: document, original: text, trimmed: trimmed, - activeEntities: validate(text: trimmed, activeEntities: activeEntities) ? activeEntities : [] + activeEntities: activeEntities ) } - static func trimEntity(status: inout String, activeEntity: ActiveEntity, activeEntities: [ActiveEntity]) { - guard case let .url(text, trimmed, _, _) = activeEntity.type else { return } + static func trimEntity(toot: inout String, activeEntity: ActiveEntity, activeEntities: [ActiveEntity]) { + let text: String + let trimmed: String + switch activeEntity.type { + case .url(let _text, let _trimmed, _, _): + text = _text + trimmed = _trimmed + case .emoji(let _text, _, _): + text = _text + trimmed = " " + default: + return + } + guard let index = activeEntities.firstIndex(where: { $0.range == activeEntity.range }) else { return } - guard let range = Range(activeEntity.range, in: status) else { return } - status.replaceSubrange(range, with: trimmed) + guard let range = Range(activeEntity.range, in: toot) else { return } + toot.replaceSubrange(range, with: trimmed) let offset = trimmed.count - text.count activeEntity.range.length += offset @@ -73,19 +102,6 @@ enum MastodonStatusContent { moveActiveEntity.range.location += offset } } - - private static func validate(text: String, activeEntities: [ActiveEntity]) -> Bool { - for activeEntity in activeEntities { - let count = text.utf16.count - let endIndex = activeEntity.range.location + activeEntity.range.length - guard endIndex <= count else { - assertionFailure("Please file issue") - return false - } - } - - return true - } } @@ -106,6 +122,7 @@ extension MastodonStatusContent { } } + extension MastodonStatusContent { class Node { @@ -154,6 +171,10 @@ extension MastodonStatusContent { } } + if _classNames.contains("emoji") { + return .emoji + } + return nil }() self.level = level @@ -257,6 +278,7 @@ extension MastodonStatusContent.Node { case url case mention case hashtag + case emoji } static func entities(in node: MastodonStatusContent.Node) -> [MastodonStatusContent.Node] { diff --git a/Mastodon/Scene/Profile/Header/ProfileHeaderViewController.swift b/Mastodon/Scene/Profile/Header/ProfileHeaderViewController.swift index 3949c3281..0610ae52f 100644 --- a/Mastodon/Scene/Profile/Header/ProfileHeaderViewController.swift +++ b/Mastodon/Scene/Profile/Header/ProfileHeaderViewController.swift @@ -175,7 +175,7 @@ extension ProfileHeaderViewController { .receive(on: DispatchQueue.main) .sink { [weak self] isEditing, note, editingNote in guard let self = self else { return } - self.profileHeaderView.bioActiveLabel.configure(note: note ?? "") + self.profileHeaderView.bioActiveLabel.configure(note: note ?? "", emojiDict: [:]) // FIXME: custom emoji self.profileHeaderView.bioTextEditorView.text = editingNote ?? "" } .store(in: &disposeBag) diff --git a/Mastodon/Scene/Profile/Header/View/ProfileFieldView.swift b/Mastodon/Scene/Profile/Header/View/ProfileFieldView.swift index e95697e5c..320a495eb 100644 --- a/Mastodon/Scene/Profile/Header/View/ProfileFieldView.swift +++ b/Mastodon/Scene/Profile/Header/View/ProfileFieldView.swift @@ -20,7 +20,7 @@ final class ProfileFieldView: UIView { let valueActiveLabel: ActiveLabel = { let label = ActiveLabel(style: .profileField) - label.configure(content: "value") + label.configure(content: "value", emojiDict: [:]) return label }() diff --git a/Mastodon/Scene/Settings/SettingsViewController.swift b/Mastodon/Scene/Settings/SettingsViewController.swift index 3c4a101a0..e3b186026 100644 --- a/Mastodon/Scene/Settings/SettingsViewController.swift +++ b/Mastodon/Scene/Settings/SettingsViewController.swift @@ -108,7 +108,7 @@ class SettingsViewController: UIViewController, NeedsDependency { let label = ActiveLabel(style: .default) label.textAlignment = .center - label.configure(content: "Mastodon is open source software. You can contribute or report issues on GitHub at tootsuite/mastodon (v3.3.0).") + label.configure(content: "Mastodon is open source software. You can contribute or report issues on GitHub at tootsuite/mastodon (v3.3.0).", emojiDict: [:]) label.delegate = self view.addArrangedSubview(label) diff --git a/Mastodon/Scene/Share/View/Content/StatusView.swift b/Mastodon/Scene/Share/View/Content/StatusView.swift index 20437a8d2..9655940c4 100644 --- a/Mastodon/Scene/Share/View/Content/StatusView.swift +++ b/Mastodon/Scene/Share/View/Content/StatusView.swift @@ -11,7 +11,7 @@ import AVKit import ActiveLabel import AlamofireImage -protocol StatusViewDelegate: class { +protocol StatusViewDelegate: AnyObject { func statusView(_ statusView: StatusView, headerInfoLabelDidPressed label: UILabel) func statusView(_ statusView: StatusView, avatarButtonDidPressed button: UIButton) func statusView(_ statusView: StatusView, revealContentWarningButtonDidPressed button: UIButton) @@ -69,10 +69,8 @@ final class StatusView: UIView { return label }() - let headerInfoLabel: UILabel = { - let label = UILabel() - label.font = UIFontMetrics(forTextStyle: .footnote).scaledFont(for: .systemFont(ofSize: 13, weight: .medium)) - label.textColor = Asset.Colors.Label.secondary.color + let headerInfoLabel: ActiveLabel = { + let label = ActiveLabel(style: .statusHeader) label.text = "Bob reblogged" return label }() @@ -87,10 +85,8 @@ final class StatusView: UIView { }() let avatarStackedContainerButton: AvatarStackContainerButton = AvatarStackContainerButton() - let nameLabel: UILabel = { - let label = UILabel() - label.font = .systemFont(ofSize: 17, weight: .semibold) - label.textColor = Asset.Colors.Label.primary.color + let nameLabel: ActiveLabel = { + let label = ActiveLabel(style: .statusName) label.text = "Alice" return label }() diff --git a/Mastodon/Service/APIService/CoreData/APIService+CoreData+Status.swift b/Mastodon/Service/APIService/CoreData/APIService+CoreData+Status.swift index 328fa2305..32628a203 100644 --- a/Mastodon/Service/APIService/CoreData/APIService+CoreData+Status.swift +++ b/Mastodon/Service/APIService/CoreData/APIService+CoreData+Status.swift @@ -89,9 +89,6 @@ extension APIService.CoreData { let metions = entity.mentions?.enumerated().compactMap { index, mention -> Mention in Mention.insert(into: managedObjectContext, property: Mention.Property(id: mention.id, username: mention.username, acct: mention.acct, url: mention.url), index: index) } - let emojis = entity.emojis?.compactMap { emoji -> Emoji in - Emoji.insert(into: managedObjectContext, property: Emoji.Property(shortcode: emoji.shortcode, url: emoji.url, staticURL: emoji.staticURL, visibleInPicker: emoji.visibleInPicker, category: emoji.category)) - } let tags = entity.tags?.compactMap { tag -> Tag in let histories = tag.history?.compactMap { history -> History in History.insert(into: managedObjectContext, property: History.Property(day: history.day, uses: history.uses, accounts: history.accounts)) @@ -121,7 +118,6 @@ extension APIService.CoreData { replyTo: replyTo, poll: poll, mentions: metions, - emojis: emojis, tags: tags, mediaAttachments: mediaAttachments, favouritedBy: (entity.favourited ?? false) ? requestMastodonUser : nil, From 26187d98b7993d9bc0ad8be5d830367c8365f319 Mon Sep 17 00:00:00 2001 From: CMK Date: Fri, 7 May 2021 18:42:49 +0800 Subject: [PATCH 2/2] chore: renaming interface --- Mastodon/Helper/MastodonStatusContent.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Mastodon/Helper/MastodonStatusContent.swift b/Mastodon/Helper/MastodonStatusContent.swift index 284b726a6..0f9dbc6c0 100755 --- a/Mastodon/Helper/MastodonStatusContent.swift +++ b/Mastodon/Helper/MastodonStatusContent.swift @@ -65,7 +65,7 @@ enum MastodonStatusContent { var trimmed = text for activeEntity in activeEntities { - MastodonStatusContent.trimEntity(toot: &trimmed, activeEntity: activeEntity, activeEntities: activeEntities) + MastodonStatusContent.trimEntity(status: &trimmed, activeEntity: activeEntity, activeEntities: activeEntities) } return ParseResult( @@ -76,7 +76,7 @@ enum MastodonStatusContent { ) } - static func trimEntity(toot: inout String, activeEntity: ActiveEntity, activeEntities: [ActiveEntity]) { + static func trimEntity(status: inout String, activeEntity: ActiveEntity, activeEntities: [ActiveEntity]) { let text: String let trimmed: String switch activeEntity.type { @@ -91,8 +91,8 @@ enum MastodonStatusContent { } guard let index = activeEntities.firstIndex(where: { $0.range == activeEntity.range }) else { return } - guard let range = Range(activeEntity.range, in: toot) else { return } - toot.replaceSubrange(range, with: trimmed) + guard let range = Range(activeEntity.range, in: status) else { return } + status.replaceSubrange(range, with: trimmed) let offset = trimmed.count - text.count activeEntity.range.length += offset