forked from shadowfacts/Tusker
Show custom emojis in display names in follow/favorite/reblog
notifications
This commit is contained in:
parent
9b949af390
commit
5125cc3397
|
@ -185,6 +185,8 @@
|
|||
D686BBE324FBF8110068E6AA /* WrappedProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D686BBE224FBF8110068E6AA /* WrappedProgressView.swift */; };
|
||||
D68E525B24A3D77E0054355A /* TuskerRootViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D68E525A24A3D77E0054355A /* TuskerRootViewController.swift */; };
|
||||
D68E525D24A3E8F00054355A /* SearchViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D68E525C24A3E8F00054355A /* SearchViewController.swift */; };
|
||||
D68E6F59253C9969001A1B4C /* MultiSourceEmojiLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D68E6F58253C9969001A1B4C /* MultiSourceEmojiLabel.swift */; };
|
||||
D68E6F5F253C9B2D001A1B4C /* BaseEmojiLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D68E6F5E253C9B2D001A1B4C /* BaseEmojiLabel.swift */; };
|
||||
D68FEC4F232C5BC300C84F23 /* SegmentedPageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D68FEC4E232C5BC300C84F23 /* SegmentedPageViewController.swift */; };
|
||||
D690797324A4EF9700023A34 /* UIBezierPath+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = D690797224A4EF9700023A34 /* UIBezierPath+Helpers.swift */; };
|
||||
D693DE5723FE1A6A0061E07D /* EnhancedNavigationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D693DE5623FE1A6A0061E07D /* EnhancedNavigationViewController.swift */; };
|
||||
|
@ -523,6 +525,8 @@
|
|||
D686BBE224FBF8110068E6AA /* WrappedProgressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WrappedProgressView.swift; sourceTree = "<group>"; };
|
||||
D68E525A24A3D77E0054355A /* TuskerRootViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TuskerRootViewController.swift; sourceTree = "<group>"; };
|
||||
D68E525C24A3E8F00054355A /* SearchViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchViewController.swift; sourceTree = "<group>"; };
|
||||
D68E6F58253C9969001A1B4C /* MultiSourceEmojiLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultiSourceEmojiLabel.swift; sourceTree = "<group>"; };
|
||||
D68E6F5E253C9B2D001A1B4C /* BaseEmojiLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseEmojiLabel.swift; sourceTree = "<group>"; };
|
||||
D68FEC4E232C5BC300C84F23 /* SegmentedPageViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SegmentedPageViewController.swift; sourceTree = "<group>"; };
|
||||
D690797224A4EF9700023A34 /* UIBezierPath+Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIBezierPath+Helpers.swift"; sourceTree = "<group>"; };
|
||||
D693DE5623FE1A6A0061E07D /* EnhancedNavigationViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EnhancedNavigationViewController.swift; sourceTree = "<group>"; };
|
||||
|
@ -1278,7 +1282,9 @@
|
|||
D620483323D3801D008A63EF /* LinkTextView.swift */,
|
||||
D620483523D38075008A63EF /* ContentTextView.swift */,
|
||||
D620483723D38190008A63EF /* StatusContentTextView.swift */,
|
||||
D68E6F5E253C9B2D001A1B4C /* BaseEmojiLabel.swift */,
|
||||
D6969E9F240C8384002843CE /* EmojiLabel.swift */,
|
||||
D68E6F58253C9969001A1B4C /* MultiSourceEmojiLabel.swift */,
|
||||
D641C77E213DC78A004B4513 /* InlineTextAttachment.swift */,
|
||||
04ED00B021481ED800567C53 /* SteppedProgressView.swift */,
|
||||
D627943A23A55BA600D38C68 /* NavigableTableViewCell.swift */,
|
||||
|
@ -1866,6 +1872,7 @@
|
|||
D627944A23A6AD6100D38C68 /* BookmarksTableViewController.swift in Sources */,
|
||||
D64BC19223C271D9000D0238 /* MastodonActivity.swift in Sources */,
|
||||
D6945C3A23AC75E2005C403C /* FindInstanceViewController.swift in Sources */,
|
||||
D68E6F59253C9969001A1B4C /* MultiSourceEmojiLabel.swift in Sources */,
|
||||
D63F9C6E241D2D85004C03CF /* CompositionAttachment.swift in Sources */,
|
||||
D6AEBB4523216AF800E5038B /* FollowAccountActivity.swift in Sources */,
|
||||
D6EBF01523C55C0900AE061B /* UIApplication+Scenes.swift in Sources */,
|
||||
|
@ -1942,6 +1949,7 @@
|
|||
D64BC18A23C16487000D0238 /* UnpinStatusActivity.swift in Sources */,
|
||||
D64D8CA92463B494006B0BAA /* MultiThreadDictionary.swift in Sources */,
|
||||
D6757A7C2157E01900721E32 /* XCBManager.swift in Sources */,
|
||||
D68E6F5F253C9B2D001A1B4C /* BaseEmojiLabel.swift in Sources */,
|
||||
D6F1F84D2193B56E00F5FE67 /* Cache.swift in Sources */,
|
||||
D6F0B12B24A3071C001E48C3 /* MainSplitViewController.swift in Sources */,
|
||||
0427037C22B316B9000D31B6 /* SilentActionPrefs.swift in Sources */,
|
||||
|
|
|
@ -0,0 +1,83 @@
|
|||
//
|
||||
// BaseEmojiLabel.swift
|
||||
// Tusker
|
||||
//
|
||||
// Created by Shadowfacts on 10/18/20.
|
||||
// Copyright © 2020 Shadowfacts. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import Pachyderm
|
||||
|
||||
private let emojiRegex = try! NSRegularExpression(pattern: ":(\\w+):", options: [])
|
||||
|
||||
protocol BaseEmojiLabel: class {
|
||||
var emojiIdentifier: String? { get set }
|
||||
var emojiRequests: [ImageCache.Request] { get set }
|
||||
var emojiFont: UIFont { get }
|
||||
var emojiTextColor: UIColor { get }
|
||||
}
|
||||
|
||||
extension BaseEmojiLabel {
|
||||
func replaceEmojis(in string: String, emojis: [Emoji], identifier: String, completion: @escaping (NSAttributedString) -> Void) {
|
||||
let matches = emojiRegex.matches(in: string, options: [], range: NSRange(location: 0, length: string.utf16.count))
|
||||
guard !matches.isEmpty else {
|
||||
completion(NSAttributedString(string: string))
|
||||
return
|
||||
}
|
||||
|
||||
let emojiImages = MultiThreadDictionary<String, UIImage>(name: "BaseEmojiLabel Emoji Images")
|
||||
var foundEmojis = false
|
||||
|
||||
let group = DispatchGroup()
|
||||
|
||||
for emoji in emojis {
|
||||
// only make requests for emojis that are present in the text to avoid making unnecessary network requests
|
||||
guard matches.contains(where: { (match) in
|
||||
let matchShortcode = (string as NSString).substring(with: match.range(at: 1))
|
||||
return emoji.shortcode == matchShortcode
|
||||
}) else {
|
||||
continue
|
||||
}
|
||||
|
||||
foundEmojis = true
|
||||
|
||||
group.enter()
|
||||
let request = ImageCache.emojis.get(emoji.url) { (data) in
|
||||
defer { group.leave() }
|
||||
guard let data = data, let image = UIImage(data: data) else {
|
||||
return
|
||||
}
|
||||
emojiImages[emoji.shortcode] = image
|
||||
}
|
||||
if let request = request {
|
||||
emojiRequests.append(request)
|
||||
}
|
||||
}
|
||||
|
||||
guard foundEmojis else {
|
||||
completion(NSAttributedString(string: string))
|
||||
return
|
||||
}
|
||||
|
||||
group.notify(queue: .main) { [weak self] in
|
||||
// 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 }
|
||||
|
||||
let mutAttrString = NSMutableAttributedString(string: string)
|
||||
// replaces the emojis starting from the end of the string as to not alter the indices of preceeding 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.emojiFont, with: self.emojiTextColor)
|
||||
let attachmentStr = NSAttributedString(attachment: attachment)
|
||||
mutAttrString.replaceCharacters(in: match.range, with: attachmentStr)
|
||||
}
|
||||
|
||||
completion(mutAttrString)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -9,12 +9,12 @@
|
|||
import UIKit
|
||||
import Pachyderm
|
||||
|
||||
private let emojiRegex = try! NSRegularExpression(pattern: ":(\\w+):", options: [])
|
||||
class EmojiLabel: UILabel, BaseEmojiLabel {
|
||||
|
||||
class EmojiLabel: UILabel {
|
||||
|
||||
private var emojiIdentifier: String?
|
||||
private var emojiRequests: [ImageCache.Request] = []
|
||||
var emojiIdentifier: String?
|
||||
var emojiRequests: [ImageCache.Request] = []
|
||||
var emojiFont: UIFont { font }
|
||||
var emojiTextColor: UIColor { textColor }
|
||||
|
||||
func setEmojis(_ emojis: [Emoji], identifier: String) {
|
||||
guard emojis.count > 0, let attributedText = attributedText else { return }
|
||||
|
@ -23,53 +23,9 @@ class EmojiLabel: UILabel {
|
|||
emojiRequests.forEach { $0.cancel() }
|
||||
emojiRequests = []
|
||||
|
||||
let matches = emojiRegex.matches(in: attributedText.string, options: [], range: attributedText.fullRange)
|
||||
guard !matches.isEmpty else { return }
|
||||
|
||||
let emojiImages = MultiThreadDictionary<String, UIImage>(name: "EmojiLabel Emoji Images")
|
||||
|
||||
let group = DispatchGroup()
|
||||
|
||||
for emoji in emojis {
|
||||
// only make requests for emojis that are present in the text to avoid making unnecessary network requests
|
||||
guard matches.contains(where: { (match) in
|
||||
let matchShortcode = (attributedText.string as NSString).substring(with: match.range(at: 1))
|
||||
return emoji.shortcode == matchShortcode
|
||||
}) else {
|
||||
continue
|
||||
}
|
||||
|
||||
group.enter()
|
||||
let request = ImageCache.emojis.get(emoji.url) { (data) in
|
||||
defer { group.leave() }
|
||||
guard let data = data, let image = UIImage(data: data) else {
|
||||
return
|
||||
}
|
||||
emojiImages[emoji.shortcode] = image
|
||||
}
|
||||
if let request = request {
|
||||
emojiRequests.append(request)
|
||||
}
|
||||
}
|
||||
|
||||
group.notify(queue: .main) { [weak self] in
|
||||
// if e.g. the account changes before all emojis are loaded, don't bother trying to set them
|
||||
replaceEmojis(in: attributedText.string, emojis: emojis, identifier: identifier) { [weak self] (newAttributedText) in
|
||||
guard let self = self, self.emojiIdentifier == identifier else { return }
|
||||
|
||||
let mutAttrString = NSMutableAttributedString(attributedString: attributedText)
|
||||
// replaces the emojis starting from the end of the string as to not alter the indicies of preceeding emojis
|
||||
for match in matches.reversed() {
|
||||
let shortcode = (mutAttrString.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)
|
||||
let attachmentStr = NSAttributedString(attachment: attachment)
|
||||
mutAttrString.replaceCharacters(in: match.range, with: attachmentStr)
|
||||
}
|
||||
|
||||
self.attributedText = mutAttrString
|
||||
self.attributedText = newAttributedText
|
||||
self.setNeedsLayout()
|
||||
self.setNeedsDisplay()
|
||||
}
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
//
|
||||
// MultiSourceEmojiLabel.swift
|
||||
// Tusker
|
||||
//
|
||||
// Created by Shadowfacts on 10/18/20.
|
||||
// Copyright © 2020 Shadowfacts. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import Pachyderm
|
||||
|
||||
private let emojiRegex = try! NSRegularExpression(pattern: ":(\\w+):", options: [])
|
||||
|
||||
class MultiSourceEmojiLabel: UILabel, BaseEmojiLabel {
|
||||
var emojiIdentifier: String?
|
||||
var emojiRequests = [ImageCache.Request]()
|
||||
var emojiFont: UIFont { font }
|
||||
var emojiTextColor: UIColor { textColor }
|
||||
|
||||
var combiner: (([NSAttributedString]) -> NSAttributedString)?
|
||||
|
||||
func setEmojis(pairs: [(String, [Emoji])], identifier: String) {
|
||||
guard pairs.count > 0 else { return }
|
||||
|
||||
self.emojiIdentifier = identifier
|
||||
emojiRequests.forEach { $0.cancel() }
|
||||
emojiRequests = []
|
||||
|
||||
var attributedStrings = pairs.map { NSAttributedString(string: $0.0) }
|
||||
|
||||
func recombine() {
|
||||
if let combiner = self.combiner {
|
||||
self.attributedText = combiner(attributedStrings)
|
||||
}
|
||||
}
|
||||
|
||||
recombine()
|
||||
|
||||
for (index, (string, emojis)) in pairs.enumerated() {
|
||||
self.replaceEmojis(in: string, emojis: emojis, identifier: identifier) { (attributedString) in
|
||||
attributedStrings[index] = attributedString
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
guard let self = self, self.emojiIdentifier == identifier else { return }
|
||||
recombine()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -19,7 +19,7 @@ class ActionNotificationGroupTableViewCell: UITableViewCell {
|
|||
@IBOutlet weak var verticalStackView: UIStackView!
|
||||
@IBOutlet weak var actionAvatarStackView: UIStackView!
|
||||
@IBOutlet weak var timestampLabel: UILabel!
|
||||
@IBOutlet weak var actionLabel: UILabel!
|
||||
@IBOutlet weak var actionLabel: MultiSourceEmojiLabel!
|
||||
@IBOutlet weak var statusContentLabel: UILabel!
|
||||
|
||||
var group: NotificationGroup!
|
||||
|
@ -35,14 +35,12 @@ class ActionNotificationGroupTableViewCell: UITableViewCell {
|
|||
override func awakeFromNib() {
|
||||
super.awakeFromNib()
|
||||
|
||||
actionLabel.combiner = self.updateActionLabel
|
||||
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(updateUIForPreferences), name: .preferencesChanged, object: nil)
|
||||
}
|
||||
|
||||
@objc func updateUIForPreferences() {
|
||||
// todo: is this compactMap necessary?
|
||||
let people = group.notifications.compactMap { mastodonController.persistentContainer.account(for: $0.account.id) }
|
||||
updateActionLabel(people: people)
|
||||
|
||||
for case let imageView as UIImageView in actionAvatarStackView.arrangedSubviews {
|
||||
imageView.layer.cornerRadius = Preferences.shared.avatarStyle.cornerRadius(for: imageView)
|
||||
}
|
||||
|
@ -100,8 +98,7 @@ class ActionNotificationGroupTableViewCell: UITableViewCell {
|
|||
NSLayoutConstraint.activate(imageViews.map { $0.widthAnchor.constraint(equalTo: $0.heightAnchor) })
|
||||
|
||||
updateTimestamp()
|
||||
|
||||
updateActionLabel(people: people)
|
||||
actionLabel.setEmojis(pairs: people.map { ($0.displayOrUserName, $0.emojis) }, identifier: group.id)
|
||||
|
||||
let doc = try! SwiftSoup.parse(status.content)
|
||||
statusContentLabel.text = try! doc.text()
|
||||
|
@ -135,7 +132,7 @@ class ActionNotificationGroupTableViewCell: UITableViewCell {
|
|||
}
|
||||
}
|
||||
|
||||
func updateActionLabel(people: [AccountMO]) {
|
||||
func updateActionLabel(names: [NSAttributedString]) -> NSAttributedString {
|
||||
let verb: String
|
||||
switch group.kind {
|
||||
case .favourite:
|
||||
|
@ -145,18 +142,27 @@ class ActionNotificationGroupTableViewCell: UITableViewCell {
|
|||
default:
|
||||
fatalError()
|
||||
}
|
||||
let peopleStr: String
|
||||
|
||||
// todo: figure out how to localize this
|
||||
// todo: update to use managed objects
|
||||
switch people.count {
|
||||
let str = NSMutableAttributedString(string: "\(verb) by ")
|
||||
switch names.count {
|
||||
case 1:
|
||||
peopleStr = people.first!.displayName
|
||||
str.append(names.first!)
|
||||
case 2:
|
||||
peopleStr = people.first!.displayName + " and " + people.last!.displayName
|
||||
str.append(names.first!)
|
||||
str.append(NSAttributedString(string: " and "))
|
||||
str.append(names.last!)
|
||||
default:
|
||||
peopleStr = people.dropLast().map { $0.displayName }.joined(separator: ", ") + ", and " + people.last!.displayName
|
||||
for (index, name) in names.enumerated() {
|
||||
str.append(name)
|
||||
if index < names.count - 2 {
|
||||
str.append(NSAttributedString(string: ", "))
|
||||
} else if index == names.count - 2 {
|
||||
str.append(NSAttributedString(string: ", and "))
|
||||
}
|
||||
}
|
||||
}
|
||||
actionLabel.text = "\(verb) by \(peopleStr)"
|
||||
return str
|
||||
}
|
||||
|
||||
override func prepareForReuse() {
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="16096" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="17506" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
||||
<device id="retina6_1" orientation="portrait" appearance="light"/>
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="16086"/>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="17504.1"/>
|
||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||
<capability name="System colors in document resources" minToolsVersion="11.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
|
@ -29,17 +31,17 @@
|
|||
</constraints>
|
||||
</stackView>
|
||||
<view contentMode="scaleToFill" horizontalHuggingPriority="249" ambiguous="YES" translatesAutoresizingMaskIntoConstraints="NO" id="5Ef-5g-b23">
|
||||
<rect key="frame" x="197.5" y="0.0" width="0.0" height="30"/>
|
||||
<rect key="frame" x="197.5" y="0.0" width="0.5" height="30"/>
|
||||
</view>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" verticalHuggingPriority="251" horizontalCompressionResistancePriority="752" text="2m" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="JN0-Bf-3qx">
|
||||
<rect key="frame" x="205.5" y="0.0" width="24.5" height="30"/>
|
||||
<rect key="frame" x="206" y="0.0" width="24" height="30"/>
|
||||
<fontDescription key="fontDescription" type="system" weight="light" pointSize="17"/>
|
||||
<color key="textColor" systemColor="secondaryLabelColor" red="0.23529411759999999" green="0.23529411759999999" blue="0.26274509800000001" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<color key="textColor" systemColor="secondaryLabelColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
</stackView>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Actioned by Person 1, Person 2, and Person 3" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="2" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="fkn-Gk-ngr">
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Actioned by Person 1, Person 2, and Person 3" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="2" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="fkn-Gk-ngr" customClass="MultiSourceEmojiLabel" customModule="Tusker" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="34" width="230" height="41"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<nil key="textColor"/>
|
||||
|
@ -48,7 +50,7 @@
|
|||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" text="Content" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="3" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="lc7-zZ-HrZ">
|
||||
<rect key="frame" x="0.0" y="79" width="230" height="74"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<color key="textColor" systemColor="secondaryLabelColor" red="0.23529411759999999" green="0.23529411759999999" blue="0.26274509800000001" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<color key="textColor" systemColor="secondaryLabelColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
|
@ -83,4 +85,9 @@
|
|||
<point key="canvasLocation" x="-394.20289855072468" y="56.584821428571423"/>
|
||||
</tableViewCell>
|
||||
</objects>
|
||||
<resources>
|
||||
<systemColor name="secondaryLabelColor">
|
||||
<color red="0.23529411764705882" green="0.23529411764705882" blue="0.2627450980392157" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</systemColor>
|
||||
</resources>
|
||||
</document>
|
||||
|
|
|
@ -16,7 +16,7 @@ class FollowNotificationGroupTableViewCell: UITableViewCell {
|
|||
|
||||
@IBOutlet weak var avatarStackView: UIStackView!
|
||||
@IBOutlet weak var timestampLabel: UILabel!
|
||||
@IBOutlet weak var actionLabel: UILabel!
|
||||
@IBOutlet weak var actionLabel: MultiSourceEmojiLabel!
|
||||
|
||||
var group: NotificationGroup!
|
||||
|
||||
|
@ -30,13 +30,12 @@ class FollowNotificationGroupTableViewCell: UITableViewCell {
|
|||
override func awakeFromNib() {
|
||||
super.awakeFromNib()
|
||||
|
||||
actionLabel.combiner = self.updateActionLabel
|
||||
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(updateUIForPreferences), name: .preferencesChanged, object: nil)
|
||||
}
|
||||
|
||||
@objc func updateUIForPreferences() {
|
||||
let people = group.notifications.compactMap { mastodonController.persistentContainer.account(for: $0.account.id) }
|
||||
updateActionLabel(people: people)
|
||||
|
||||
for case let imageView as UIImageView in avatarStackView.arrangedSubviews {
|
||||
imageView.layer.cornerRadius = Preferences.shared.avatarStyle.cornerRadius(for: imageView)
|
||||
}
|
||||
|
@ -47,7 +46,9 @@ class FollowNotificationGroupTableViewCell: UITableViewCell {
|
|||
|
||||
let people = group.notifications.compactMap { mastodonController.persistentContainer.account(for: $0.account.id) }
|
||||
|
||||
updateActionLabel(people: people)
|
||||
actionLabel.setEmojis(pairs: people.map {
|
||||
($0.displayOrUserName, $0.emojis)
|
||||
}, identifier: group.id)
|
||||
updateTimestamp()
|
||||
|
||||
avatarStackView.arrangedSubviews.forEach { $0.removeFromSuperview() }
|
||||
|
@ -71,20 +72,27 @@ class FollowNotificationGroupTableViewCell: UITableViewCell {
|
|||
}
|
||||
}
|
||||
|
||||
func updateActionLabel(people: [AccountMO]) {
|
||||
// todo: custom emoji in people display names
|
||||
func updateActionLabel(names: [NSAttributedString]) -> NSAttributedString {
|
||||
// todo: figure out how to localize this
|
||||
let peopleStr: String
|
||||
switch people.count {
|
||||
let str = NSMutableAttributedString(string: "Followed by ")
|
||||
switch names.count {
|
||||
case 1:
|
||||
peopleStr = people.first!.displayOrUserName
|
||||
str.append(names.first!)
|
||||
case 2:
|
||||
peopleStr = people.first!.displayOrUserName + " and " + people.last!.displayOrUserName
|
||||
str.append(names.first!)
|
||||
str.append(NSAttributedString(string: " and "))
|
||||
str.append(names.last!)
|
||||
default:
|
||||
peopleStr = people.dropLast().map { $0.displayOrUserName }.joined(separator: ", ") + ", and " + people.last!.displayOrUserName
|
||||
|
||||
for (index, name) in names.enumerated() {
|
||||
str.append(name)
|
||||
if index < names.count - 2 {
|
||||
str.append(NSAttributedString(string: ", "))
|
||||
} else if index == names.count - 2 {
|
||||
str.append(NSAttributedString(string: ", and "))
|
||||
}
|
||||
}
|
||||
}
|
||||
actionLabel.text = "Followed by \(peopleStr)"
|
||||
return str
|
||||
}
|
||||
|
||||
func updateTimestamp() {
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="14865.1" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="17506" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
||||
<device id="retina6_1" orientation="portrait" appearance="light"/>
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14819.2"/>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="17504.1"/>
|
||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||
<capability name="System colors in document resources" minToolsVersion="11.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
|
@ -29,17 +31,17 @@
|
|||
</constraints>
|
||||
</stackView>
|
||||
<view contentMode="scaleToFill" horizontalHuggingPriority="249" ambiguous="YES" translatesAutoresizingMaskIntoConstraints="NO" id="eEp-GR-rtF">
|
||||
<rect key="frame" x="205.5" y="0.0" width="0.0" height="30"/>
|
||||
<rect key="frame" x="205.5" y="0.0" width="0.5" height="30"/>
|
||||
</view>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" horizontalCompressionResistancePriority="751" text="2m" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Iub-HC-orP">
|
||||
<rect key="frame" x="205.5" y="0.0" width="24.5" height="30"/>
|
||||
<rect key="frame" x="206" y="0.0" width="24" height="30"/>
|
||||
<fontDescription key="fontDescription" type="system" weight="light" pointSize="17"/>
|
||||
<color key="textColor" systemColor="secondaryLabelColor" red="0.23529411759999999" green="0.23529411759999999" blue="0.26274509800000001" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<color key="textColor" systemColor="secondaryLabelColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
</stackView>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Followed by Person 1 and Person 2" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="2" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="bHA-9x-pcO">
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Followed by Person 1 and Person 2" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="2" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="bHA-9x-pcO" customClass="MultiSourceEmojiLabel" customModule="Tusker" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="30" width="230" height="46"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<nil key="textColor"/>
|
||||
|
@ -48,7 +50,7 @@
|
|||
</subviews>
|
||||
</stackView>
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="person.badge.plus.fill" catalog="system" translatesAutoresizingMaskIntoConstraints="NO" id="7gy-KD-YT1">
|
||||
<rect key="frame" x="36" y="12.5" width="30" height="30.5"/>
|
||||
<rect key="frame" x="34" y="12.5" width="32" height="30"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="30" id="gvV-4g-2Xr"/>
|
||||
<constraint firstAttribute="height" constant="30" id="lS8-fq-ptY"/>
|
||||
|
@ -74,6 +76,9 @@
|
|||
</tableViewCell>
|
||||
</objects>
|
||||
<resources>
|
||||
<image name="person.badge.plus.fill" catalog="system" width="64" height="58"/>
|
||||
<image name="person.badge.plus.fill" catalog="system" width="128" height="124"/>
|
||||
<systemColor name="secondaryLabelColor">
|
||||
<color red="0.23529411764705882" green="0.23529411764705882" blue="0.2627450980392157" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</systemColor>
|
||||
</resources>
|
||||
</document>
|
||||
|
|
Loading…
Reference in New Issue