From 53707593a6dc7d2e099d88b98d2e9da0f1fe7bec Mon Sep 17 00:00:00 2001 From: Shadowfacts Date: Sun, 1 Mar 2020 19:40:32 -0500 Subject: [PATCH] Show custom emojis in display names (where possible) --- Tusker.xcodeproj/project.pbxproj | 8 ++ Tusker/Extensions/Account+Preferences.swift | 12 ++- .../Extensions/NSTextAttachment+Emoji.swift | 29 ++++++ .../Compose/ComposeViewController.swift | 2 +- .../Profile/ProfileTableViewController.swift | 2 +- Tusker/Shortcuts/UserActivityManager.swift | 4 +- .../Account Cell/AccountTableViewCell.swift | 4 +- .../Account Cell/AccountTableViewCell.xib | 6 +- .../LargeAccountDetailView.swift | 4 +- .../ComposeStatusReplyView.swift | 4 +- .../ComposeStatusReplyView.xib | 6 +- Tusker/Views/ContentTextView.swift | 24 +---- Tusker/Views/EmojiLabel.swift | 94 +++++++++++++++++++ ...ActionNotificationGroupTableViewCell.swift | 6 +- ...FollowNotificationGroupTableViewCell.swift | 6 +- ...llowRequestNotificationTableViewCell.swift | 10 +- ...FollowRequestNotificationTableViewCell.xib | 12 +-- .../ProfileHeaderTableViewCell.swift | 4 +- .../ProfileHeaderTableViewCell.xib | 8 +- .../Status/BaseStatusTableViewCell.swift | 4 +- .../ConversationMainStatusTableViewCell.swift | 2 +- .../ConversationMainStatusTableViewCell.xib | 16 ++-- .../Status/TimelineStatusTableViewCell.swift | 10 +- .../Status/TimelineStatusTableViewCell.xib | 20 ++-- Tusker/XCallbackURL/XCBActions.swift | 2 +- 25 files changed, 213 insertions(+), 86 deletions(-) create mode 100644 Tusker/Extensions/NSTextAttachment+Emoji.swift create mode 100644 Tusker/Views/EmojiLabel.swift diff --git a/Tusker.xcodeproj/project.pbxproj b/Tusker.xcodeproj/project.pbxproj index 65bcb8dc..63a6803f 100644 --- a/Tusker.xcodeproj/project.pbxproj +++ b/Tusker.xcodeproj/project.pbxproj @@ -163,6 +163,8 @@ D6945C3423AC6431005C403C /* AddSavedHashtagViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6945C3323AC6431005C403C /* AddSavedHashtagViewController.swift */; }; D6945C3823AC739F005C403C /* InstanceTimelineViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6945C3723AC739F005C403C /* InstanceTimelineViewController.swift */; }; D6945C3A23AC75E2005C403C /* FindInstanceViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6945C3923AC75E2005C403C /* FindInstanceViewController.swift */; }; + D6969E9E240C81B9002843CE /* NSTextAttachment+Emoji.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6969E9D240C81B9002843CE /* NSTextAttachment+Emoji.swift */; }; + D6969EA0240C8384002843CE /* EmojiLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6969E9F240C8384002843CE /* EmojiLabel.swift */; }; D6A3BC7723218E1300FD64D5 /* TimelineSegment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6A3BC7323218C6E00FD64D5 /* TimelineSegment.swift */; }; D6A3BC7923218E9200FD64D5 /* NotificationGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6A3BC7823218E9200FD64D5 /* NotificationGroup.swift */; }; D6A3BC7C232195C600FD64D5 /* ActionNotificationGroupTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6A3BC7A232195C600FD64D5 /* ActionNotificationGroupTableViewCell.swift */; }; @@ -443,6 +445,8 @@ D6945C3323AC6431005C403C /* AddSavedHashtagViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddSavedHashtagViewController.swift; sourceTree = ""; }; D6945C3723AC739F005C403C /* InstanceTimelineViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstanceTimelineViewController.swift; sourceTree = ""; }; D6945C3923AC75E2005C403C /* FindInstanceViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = FindInstanceViewController.swift; path = Tusker/Screens/FindInstanceViewController.swift; sourceTree = SOURCE_ROOT; }; + D6969E9D240C81B9002843CE /* NSTextAttachment+Emoji.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSTextAttachment+Emoji.swift"; sourceTree = ""; }; + D6969E9F240C8384002843CE /* EmojiLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiLabel.swift; sourceTree = ""; }; D6A3BC7323218C6E00FD64D5 /* TimelineSegment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineSegment.swift; sourceTree = ""; }; D6A3BC7823218E9200FD64D5 /* NotificationGroup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationGroup.swift; sourceTree = ""; }; D6A3BC7A232195C600FD64D5 /* ActionNotificationGroupTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionNotificationGroupTableViewCell.swift; sourceTree = ""; }; @@ -974,6 +978,7 @@ D66A77BA233838DC0058F1EC /* UIFont+Traits.swift */, D6EBF01423C55C0900AE061B /* UIApplication+Scenes.swift */, D6EBF01623C55E0D00AE061B /* UISceneSession+MastodonController.swift */, + D6969E9D240C81B9002843CE /* NSTextAttachment+Emoji.swift */, ); path = Extensions; sourceTree = ""; @@ -1113,6 +1118,7 @@ D620483323D3801D008A63EF /* LinkTextView.swift */, D620483523D38075008A63EF /* ContentTextView.swift */, D620483723D38190008A63EF /* StatusContentTextView.swift */, + D6969E9F240C8384002843CE /* EmojiLabel.swift */, D641C77E213DC78A004B4513 /* InlineTextAttachment.swift */, 04ED00B021481ED800567C53 /* SteppedProgressView.swift */, D627943A23A55BA600D38C68 /* NavigableTableViewCell.swift */, @@ -1589,6 +1595,7 @@ 04DACE8C212CB14B009840C4 /* MainTabBarViewController.swift in Sources */, D6AEBB412321642700E5038B /* SendMesasgeActivity.swift in Sources */, D6BC9DB1232C61BC002CA326 /* NotificationsPageViewController.swift in Sources */, + D6969E9E240C81B9002843CE /* NSTextAttachment+Emoji.swift in Sources */, D6BC9DB3232D4C07002CA326 /* WellnessPrefsView.swift in Sources */, 0454DDAF22B462EF00B8BB8E /* GalleryExpandAnimationController.swift in Sources */, D64BC18F23C18B9D000D0238 /* FollowRequestNotificationTableViewCell.swift in Sources */, @@ -1661,6 +1668,7 @@ D627943E23A564D400D38C68 /* ExploreViewController.swift in Sources */, D6B053AB23BD2F1400A066FA /* AssetCollectionViewCell.swift in Sources */, D6434EB3215B1856001A919A /* XCBRequest.swift in Sources */, + D6969EA0240C8384002843CE /* EmojiLabel.swift in Sources */, D64BC18623C1253A000D0238 /* AssetPreviewViewController.swift in Sources */, D663626221360B1900C9CBA2 /* Preferences.swift in Sources */, D626493823C0FD0000612E6E /* AllPhotosTableViewCell.swift in Sources */, diff --git a/Tusker/Extensions/Account+Preferences.swift b/Tusker/Extensions/Account+Preferences.swift index be5da262..09d8dff5 100644 --- a/Tusker/Extensions/Account+Preferences.swift +++ b/Tusker/Extensions/Account+Preferences.swift @@ -11,16 +11,22 @@ import Pachyderm extension Account { - var realDisplayName: String { + var displayOrUserName: String { if displayName.isEmpty { return username - } else if Preferences.shared.hideCustomEmojiInUsernames { - return stripCustomEmoji(from: displayName) } else { return displayName } } + var displayNameWithoutCustomEmoji: String { + if displayName.isEmpty { + return username + } else { + return stripCustomEmoji(from: displayName) + } + } + private static let customEmojiRegex = try! NSRegularExpression(pattern: ":[a-zA-Z0-9_]+:", options: []) private func stripCustomEmoji(from string: String) -> String { diff --git a/Tusker/Extensions/NSTextAttachment+Emoji.swift b/Tusker/Extensions/NSTextAttachment+Emoji.swift new file mode 100644 index 00000000..442a9a9f --- /dev/null +++ b/Tusker/Extensions/NSTextAttachment+Emoji.swift @@ -0,0 +1,29 @@ +// +// NSTextAttachment+Emoji.swift +// Tusker +// +// Created by Shadowfacts on 3/1/20. +// Copyright © 2020 Shadowfacts. All rights reserved. +// + +import UIKit + +extension NSTextAttachment { + // Based on https://github.com/ReticentJohn/Amaroq/blob/7c5b7088eb9fd1611dcb0f47d43bf8df093e142c/DireFloof/InlineImageHelpers.m + convenience init(emojiImage image: UIImage, in font: UIFont, with textColor: UIColor = .label) { + 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) + + UIGraphicsBeginImageContextWithOptions(imageSizeMatchingFontSize, false, 0.0) + textColor.set() + image.draw(in: CGRect(origin: .zero, size: imageSizeMatchingFontSize)) + let attachmentImage = UIGraphicsGetImageFromCurrentImageContext() + UIGraphicsEndImageContext() + + self.init() + self.image = attachmentImage + } +} diff --git a/Tusker/Screens/Compose/ComposeViewController.swift b/Tusker/Screens/Compose/ComposeViewController.swift index 644cc863..d3a749ae 100644 --- a/Tusker/Screens/Compose/ComposeViewController.swift +++ b/Tusker/Screens/Compose/ComposeViewController.swift @@ -203,7 +203,7 @@ class ComposeViewController: UIViewController { replyAvatarImageViewTopConstraint!.isActive = true inReplyToContainer.isHidden = false - inReplyToLabel.text = "In reply to \(inReplyTo.account.realDisplayName)" + inReplyToLabel.text = "In reply to \(inReplyTo.account.displayOrUserName)" } override func viewWillAppear(_ animated: Bool) { diff --git a/Tusker/Screens/Profile/ProfileTableViewController.swift b/Tusker/Screens/Profile/ProfileTableViewController.swift index 88b562a3..9bbe8c10 100644 --- a/Tusker/Screens/Profile/ProfileTableViewController.swift +++ b/Tusker/Screens/Profile/ProfileTableViewController.swift @@ -129,7 +129,7 @@ class ProfileTableViewController: EnhancedTableViewController { @objc func updateUIForPreferences() { guard let accountID = accountID, let account = mastodonController.cache.account(for: accountID) else { return } - navigationItem.title = account.realDisplayName + navigationItem.title = account.displayOrUserName } func getStatuses(for range: RequestRange = .default, onlyPinned: Bool = false, completion: @escaping Client.Callback<[Status]>) { diff --git a/Tusker/Shortcuts/UserActivityManager.swift b/Tusker/Shortcuts/UserActivityManager.swift index 28045bd6..1259be04 100644 --- a/Tusker/Shortcuts/UserActivityManager.swift +++ b/Tusker/Shortcuts/UserActivityManager.swift @@ -37,8 +37,8 @@ class UserActivityManager { activity.isEligibleForPrediction = true if let mentioning = mentioning { activity.userInfo = ["mentioning": mentioning.acct] - activity.title = "Send a message to \(mentioning.realDisplayName)" - activity.suggestedInvocationPhrase = "Send a message to \(mentioning.realDisplayName)" + activity.title = "Send a message to \(mentioning.displayOrUserName)" + activity.suggestedInvocationPhrase = "Send a message to \(mentioning.displayOrUserName)" } else { activity.userInfo = [:] activity.title = "New Post" diff --git a/Tusker/Views/Account Cell/AccountTableViewCell.swift b/Tusker/Views/Account Cell/AccountTableViewCell.swift index e22e1c35..1fe639e0 100644 --- a/Tusker/Views/Account Cell/AccountTableViewCell.swift +++ b/Tusker/Views/Account Cell/AccountTableViewCell.swift @@ -14,7 +14,7 @@ class AccountTableViewCell: UITableViewCell { var mastodonController: MastodonController! { delegate?.apiController } @IBOutlet weak var avatarImageView: UIImageView! - @IBOutlet weak var displayNameLabel: UILabel! + @IBOutlet weak var displayNameLabel: EmojiLabel! @IBOutlet weak var usernameLabel: UILabel! var accountID: String! @@ -35,7 +35,7 @@ class AccountTableViewCell: UITableViewCell { guard let account = mastodonController.cache.account(for: accountID) else { fatalError("Missing cached account \(accountID!)") } - displayNameLabel.text = account.realDisplayName + displayNameLabel.updateForAccountDisplayName(account: account) } func updateUI(accountID: String) { diff --git a/Tusker/Views/Account Cell/AccountTableViewCell.xib b/Tusker/Views/Account Cell/AccountTableViewCell.xib index 0c76b6cf..4a829b20 100644 --- a/Tusker/Views/Account Cell/AccountTableViewCell.xib +++ b/Tusker/Views/Account Cell/AccountTableViewCell.xib @@ -1,8 +1,8 @@ - + - + @@ -26,7 +26,7 @@ - - diff --git a/Tusker/Views/Profile Header/ProfileHeaderTableViewCell.swift b/Tusker/Views/Profile Header/ProfileHeaderTableViewCell.swift index 555dadd6..5fd7d9cd 100644 --- a/Tusker/Views/Profile Header/ProfileHeaderTableViewCell.swift +++ b/Tusker/Views/Profile Header/ProfileHeaderTableViewCell.swift @@ -22,7 +22,7 @@ class ProfileHeaderTableViewCell: UITableViewCell { @IBOutlet weak var headerImageView: UIImageView! @IBOutlet weak var avatarContainerView: UIView! @IBOutlet weak var avatarImageView: UIImageView! - @IBOutlet weak var displayNameLabel: UILabel! + @IBOutlet weak var displayNameLabel: EmojiLabel! @IBOutlet weak var usernameLabel: UILabel! @IBOutlet weak var followsYouLabel: UILabel! @IBOutlet weak var noteTextView: StatusContentTextView! @@ -136,7 +136,7 @@ class ProfileHeaderTableViewCell: UITableViewCell { avatarContainerView.layer.cornerRadius = Preferences.shared.avatarStyle.cornerRadius(for: avatarContainerView) avatarImageView.layer.cornerRadius = Preferences.shared.avatarStyle.cornerRadius(for: avatarImageView) - displayNameLabel.text = account.realDisplayName + displayNameLabel.updateForAccountDisplayName(account: account) } override func prepareForReuse() { diff --git a/Tusker/Views/Profile Header/ProfileHeaderTableViewCell.xib b/Tusker/Views/Profile Header/ProfileHeaderTableViewCell.xib index 2c4e2202..516d540f 100644 --- a/Tusker/Views/Profile Header/ProfileHeaderTableViewCell.xib +++ b/Tusker/Views/Profile Header/ProfileHeaderTableViewCell.xib @@ -1,8 +1,8 @@ - + - + @@ -45,7 +45,7 @@ - diff --git a/Tusker/Views/Status/BaseStatusTableViewCell.swift b/Tusker/Views/Status/BaseStatusTableViewCell.swift index 343a2e8e..7e627885 100644 --- a/Tusker/Views/Status/BaseStatusTableViewCell.swift +++ b/Tusker/Views/Status/BaseStatusTableViewCell.swift @@ -25,7 +25,7 @@ class BaseStatusTableViewCell: UITableViewCell { var mastodonController: MastodonController! { overrideMastodonController ?? delegate?.apiController } @IBOutlet weak var avatarImageView: UIImageView! - @IBOutlet weak var displayNameLabel: UILabel! + @IBOutlet weak var displayNameLabel: EmojiLabel! @IBOutlet weak var usernameLabel: UILabel! @IBOutlet weak var contentWarningLabel: UILabel! @IBOutlet weak var collapseButton: UIButton! @@ -190,7 +190,7 @@ class BaseStatusTableViewCell: UITableViewCell { @objc func updateUIForPreferences() { guard let account = mastodonController.cache.account(for: accountID) else { return } avatarImageView.layer.cornerRadius = Preferences.shared.avatarStyle.cornerRadius(for: avatarImageView) - displayNameLabel.text = account.realDisplayName + displayNameLabel.updateForAccountDisplayName(account: account) attachmentsView.contentHidden = Preferences.shared.blurAllMedia || (mastodonController.cache.status(for: statusID)?.sensitive ?? false) } diff --git a/Tusker/Views/Status/ConversationMainStatusTableViewCell.swift b/Tusker/Views/Status/ConversationMainStatusTableViewCell.swift index 0621a423..370b6fda 100644 --- a/Tusker/Views/Status/ConversationMainStatusTableViewCell.swift +++ b/Tusker/Views/Status/ConversationMainStatusTableViewCell.swift @@ -60,7 +60,7 @@ class ConversationMainStatusTableViewCell: BaseStatusTableViewCell { override func updateUI(account: Account) { super.updateUI(account: account) - profileAccessibilityElement.accessibilityLabel = account.realDisplayName + profileAccessibilityElement.accessibilityLabel = account.displayNameWithoutCustomEmoji } @objc override func updateUIForPreferences() { diff --git a/Tusker/Views/Status/ConversationMainStatusTableViewCell.xib b/Tusker/Views/Status/ConversationMainStatusTableViewCell.xib index d15d5795..07b2cf8a 100644 --- a/Tusker/Views/Status/ConversationMainStatusTableViewCell.xib +++ b/Tusker/Views/Status/ConversationMainStatusTableViewCell.xib @@ -1,8 +1,8 @@ - + - + @@ -26,7 +26,7 @@ - diff --git a/Tusker/Views/Status/TimelineStatusTableViewCell.swift b/Tusker/Views/Status/TimelineStatusTableViewCell.swift index e586a36f..3e79462f 100644 --- a/Tusker/Views/Status/TimelineStatusTableViewCell.swift +++ b/Tusker/Views/Status/TimelineStatusTableViewCell.swift @@ -19,7 +19,7 @@ class TimelineStatusTableViewCell: BaseStatusTableViewCell { return formatter }() - @IBOutlet weak var reblogLabel: UILabel! + @IBOutlet weak var reblogLabel: EmojiLabel! @IBOutlet weak var timestampLabel: UILabel! @IBOutlet weak var pinImageView: UIImageView! @@ -91,7 +91,13 @@ class TimelineStatusTableViewCell: BaseStatusTableViewCell { } private func updateRebloggerLabel(reblogger: Account) { - reblogLabel.text = "Reblogged by \(reblogger.realDisplayName)" + if Preferences.shared.hideCustomEmojiInUsernames { + reblogLabel.text = "Reblogged by \(reblogger.displayNameWithoutCustomEmoji)" + reblogLabel.removeEmojis() + } else { + reblogLabel.text = "Reblogged by \(reblogger.displayOrUserName)" + reblogLabel.setEmojis(reblogger.emojis, identifier: reblogger.id) + } } func updateTimestamp() { diff --git a/Tusker/Views/Status/TimelineStatusTableViewCell.xib b/Tusker/Views/Status/TimelineStatusTableViewCell.xib index 87833823..b094d549 100644 --- a/Tusker/Views/Status/TimelineStatusTableViewCell.xib +++ b/Tusker/Views/Status/TimelineStatusTableViewCell.xib @@ -1,8 +1,8 @@ - + - + @@ -16,7 +16,7 @@ - diff --git a/Tusker/XCallbackURL/XCBActions.swift b/Tusker/XCallbackURL/XCBActions.swift index 0731fd06..fc802866 100644 --- a/Tusker/XCallbackURL/XCBActions.swift +++ b/Tusker/XCallbackURL/XCBActions.swift @@ -314,7 +314,7 @@ struct XCBActions { DispatchQueue.main.async { show(vc) } - let alertController = UIAlertController(title: "Follow \(account.realDisplayName)?", message: nil, preferredStyle: .alert) + let alertController = UIAlertController(title: "Follow \(account.displayNameWithoutCustomEmoji)?", message: nil, preferredStyle: .alert) alertController.addAction(UIAlertAction(title: "Ok", style: .default, handler: { (_) in performAction(account) }))