diff --git a/Tusker.xcodeproj/project.pbxproj b/Tusker.xcodeproj/project.pbxproj index 2e9392f6de..c7067e0bbd 100644 --- a/Tusker.xcodeproj/project.pbxproj +++ b/Tusker.xcodeproj/project.pbxproj @@ -86,8 +86,6 @@ D6333B792139AEFD00CE884A /* Date+TimeAgo.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6333B782139AEFD00CE884A /* Date+TimeAgo.swift */; }; D640D76922BAF5E6004FBE69 /* DomainBlocks.plist in Resources */ = {isa = PBXBuildFile; fileRef = D640D76822BAF5E6004FBE69 /* DomainBlocks.plist */; }; D641C773213CAA25004B4513 /* NotificationsTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D641C772213CAA25004B4513 /* NotificationsTableViewController.swift */; }; - D641C77B213CB017004B4513 /* FollowNotificationTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D641C77A213CB017004B4513 /* FollowNotificationTableViewCell.xib */; }; - D641C77D213CB024004B4513 /* FollowNotificationTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D641C77C213CB024004B4513 /* FollowNotificationTableViewCell.swift */; }; D641C77F213DC78A004B4513 /* InlineTextAttachment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D641C77E213DC78A004B4513 /* InlineTextAttachment.swift */; }; D6434EB3215B1856001A919A /* XCBRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6434EB2215B1856001A919A /* XCBRequest.swift */; }; D646C956213B365700269FB5 /* LargeImageExpandAnimationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D646C955213B365700269FB5 /* LargeImageExpandAnimationController.swift */; }; @@ -148,6 +146,8 @@ D6A3BC7923218E9200FD64D5 /* NotificationGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6A3BC7823218E9200FD64D5 /* NotificationGroup.swift */; }; D6A3BC7C232195C600FD64D5 /* ActionNotificationGroupTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6A3BC7A232195C600FD64D5 /* ActionNotificationGroupTableViewCell.swift */; }; D6A3BC7D232195C600FD64D5 /* ActionNotificationGroupTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D6A3BC7B232195C600FD64D5 /* ActionNotificationGroupTableViewCell.xib */; }; + D6A3BC802321B7E600FD64D5 /* FollowNotificationGroupTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6A3BC7E2321B7E600FD64D5 /* FollowNotificationGroupTableViewCell.swift */; }; + D6A3BC812321B7E600FD64D5 /* FollowNotificationGroupTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D6A3BC7F2321B7E600FD64D5 /* FollowNotificationGroupTableViewCell.xib */; }; D6A5FAF1217B7E05003DB2D9 /* ComposeViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = D6A5FAF0217B7E05003DB2D9 /* ComposeViewController.xib */; }; D6A5FAFB217B86CE003DB2D9 /* OnboardingViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = D6A5FAFA217B86CE003DB2D9 /* OnboardingViewController.xib */; }; D6AEBB3E2321638100E5038B /* UIActivity+Types.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6AEBB3D2321638100E5038B /* UIActivity+Types.swift */; }; @@ -328,8 +328,6 @@ D6333B782139AEFD00CE884A /* Date+TimeAgo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Date+TimeAgo.swift"; sourceTree = ""; }; D640D76822BAF5E6004FBE69 /* DomainBlocks.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = DomainBlocks.plist; sourceTree = ""; }; D641C772213CAA25004B4513 /* NotificationsTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationsTableViewController.swift; sourceTree = ""; }; - D641C77A213CB017004B4513 /* FollowNotificationTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = FollowNotificationTableViewCell.xib; sourceTree = ""; }; - D641C77C213CB024004B4513 /* FollowNotificationTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FollowNotificationTableViewCell.swift; sourceTree = ""; }; D641C77E213DC78A004B4513 /* InlineTextAttachment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InlineTextAttachment.swift; sourceTree = ""; }; D6434EB2215B1856001A919A /* XCBRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XCBRequest.swift; sourceTree = ""; }; D646C955213B365700269FB5 /* LargeImageExpandAnimationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LargeImageExpandAnimationController.swift; sourceTree = ""; }; @@ -389,6 +387,8 @@ D6A3BC7823218E9200FD64D5 /* NotificationGroup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationGroup.swift; sourceTree = ""; }; D6A3BC7A232195C600FD64D5 /* ActionNotificationGroupTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionNotificationGroupTableViewCell.swift; sourceTree = ""; }; D6A3BC7B232195C600FD64D5 /* ActionNotificationGroupTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ActionNotificationGroupTableViewCell.xib; sourceTree = ""; }; + D6A3BC7E2321B7E600FD64D5 /* FollowNotificationGroupTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FollowNotificationGroupTableViewCell.swift; sourceTree = ""; }; + D6A3BC7F2321B7E600FD64D5 /* FollowNotificationGroupTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = FollowNotificationGroupTableViewCell.xib; sourceTree = ""; }; D6A5FAF0217B7E05003DB2D9 /* ComposeViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ComposeViewController.xib; sourceTree = ""; }; D6A5FAFA217B86CE003DB2D9 /* OnboardingViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = OnboardingViewController.xib; sourceTree = ""; }; D6AEBB3D2321638100E5038B /* UIActivity+Types.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIActivity+Types.swift"; sourceTree = ""; }; @@ -785,10 +785,10 @@ D641C78C213DD937004B4513 /* Notifications */ = { isa = PBXGroup; children = ( - D641C77A213CB017004B4513 /* FollowNotificationTableViewCell.xib */, - D641C77C213CB024004B4513 /* FollowNotificationTableViewCell.swift */, D6A3BC7A232195C600FD64D5 /* ActionNotificationGroupTableViewCell.swift */, D6A3BC7B232195C600FD64D5 /* ActionNotificationGroupTableViewCell.xib */, + D6A3BC7E2321B7E600FD64D5 /* FollowNotificationGroupTableViewCell.swift */, + D6A3BC7F2321B7E600FD64D5 /* FollowNotificationGroupTableViewCell.xib */, ); path = Notifications; sourceTree = ""; @@ -1346,11 +1346,11 @@ D663625D2135C74800C9CBA2 /* ConversationMainStatusTableViewCell.xib in Resources */, D6A5FAFB217B86CE003DB2D9 /* OnboardingViewController.xib in Resources */, D640D76922BAF5E6004FBE69 /* DomainBlocks.plist in Resources */, - D641C77B213CB017004B4513 /* FollowNotificationTableViewCell.xib in Resources */, D6289E84217B795D0003D1D7 /* LargeImageViewController.xib in Resources */, D627FF79217E950100CC0648 /* DraftsTableViewController.xib in Resources */, D67C57B221E28FAD00C3118B /* ComposeStatusReplyView.xib in Resources */, 0411610122B442870030A9B7 /* AttachmentViewController.xib in Resources */, + D6A3BC812321B7E600FD64D5 /* FollowNotificationGroupTableViewCell.xib in Resources */, D60C07E421E8176B0057FAA8 /* ComposeMediaView.xib in Resources */, D667E5E12134937B0057A976 /* StatusTableViewCell.xib in Resources */, D6A5FAF1217B7E05003DB2D9 /* ComposeViewController.xib in Resources */, @@ -1461,6 +1461,7 @@ D62D2426217ABF63005076CC /* UserActivityType.swift in Sources */, D66362712136338600C9CBA2 /* ComposeViewController.swift in Sources */, D6028B9B2150811100F223B9 /* MastodonCache.swift in Sources */, + D6A3BC802321B7E600FD64D5 /* FollowNotificationGroupTableViewCell.swift in Sources */, D62D2422217AA7E1005076CC /* UserActivityManager.swift in Sources */, D62D2424217ABF3F005076CC /* NSUserActivity+Extensions.swift in Sources */, D646C958213B367000269FB5 /* LargeImageShrinkAnimationController.swift in Sources */, @@ -1512,7 +1513,6 @@ 04586B4122B2FFB10021BD04 /* PreferencesView.swift in Sources */, D667E5EB21349EF80057A976 /* ProfileHeaderTableViewCell.swift in Sources */, 04D14BB022B34A2800642648 /* GalleryViewController.swift in Sources */, - D641C77D213CB024004B4513 /* FollowNotificationTableViewCell.swift in Sources */, D641C773213CAA25004B4513 /* NotificationsTableViewController.swift in Sources */, D6757A7C2157E01900721E32 /* XCBManager.swift in Sources */, D6F1F84D2193B56E00F5FE67 /* Cache.swift in Sources */, diff --git a/Tusker/Screens/Notifications/NotificationsTableViewController.swift b/Tusker/Screens/Notifications/NotificationsTableViewController.swift index dbe9a21764..6b1399da1f 100644 --- a/Tusker/Screens/Notifications/NotificationsTableViewController.swift +++ b/Tusker/Screens/Notifications/NotificationsTableViewController.swift @@ -11,11 +11,11 @@ import Pachyderm class NotificationsTableViewController: EnhancedTableViewController { - let statusCell = "statusCell" - let actionGroupCell = "actionGroupCell" - let followCell = "followCell" + private let statusCell = "statusCell" + private let actionGroupCell = "actionGroupCell" + private let followGroupCell = "followGroupCell" - let groupTypes = [Notification.Kind.favourite, .reblog] + let groupTypes = [Notification.Kind.favourite, .reblog, .follow] var groups: [NotificationGroup] = [] { didSet { @@ -50,7 +50,7 @@ class NotificationsTableViewController: EnhancedTableViewController { tableView.register(UINib(nibName: "StatusTableViewCell", bundle: nil), forCellReuseIdentifier: statusCell) tableView.register(UINib(nibName: "ActionNotificationGroupTableViewCell", bundle: nil), forCellReuseIdentifier: actionGroupCell) - tableView.register(UINib(nibName: "FollowNotificationTableViewCell", bundle: nil), forCellReuseIdentifier: followCell) + tableView.register(UINib(nibName: "FollowNotificationGroupTableViewCell", bundle: nil), forCellReuseIdentifier: followGroupCell) tableView.prefetchDataSource = self @@ -104,15 +104,10 @@ class NotificationsTableViewController: EnhancedTableViewController { return cell case .follow: - guard let notification = MastodonCache.notification(for: group.notificationIDs.first!) else { fatalError() } - guard let cell = tableView.dequeueReusableCell(withIdentifier: followCell, for: indexPath) as? FollowNotificationTableViewCell else { fatalError() } - cell.updateUI(for: notification) + guard let cell = tableView.dequeueReusableCell(withIdentifier: "followGroupCell", for: indexPath) as? FollowNotificationGroupTableViewCell else { fatalError() } + cell.updateUI(group: group) cell.delegate = self return cell -// guard let cell = tableView.dequeueReusableCell(withIdentifier: "followGroupCell", for: indexPath) as? FollowNotificationGroupTableViewCell else { fatalError() } -// cell.updateUI(notificationGroup: group) -// cell.delegate = self -// return cell } } diff --git a/Tusker/Views/Notifications/ActionNotificationGroupTableViewCell.swift b/Tusker/Views/Notifications/ActionNotificationGroupTableViewCell.swift index 69405e4bad..1febc504af 100644 --- a/Tusker/Views/Notifications/ActionNotificationGroupTableViewCell.swift +++ b/Tusker/Views/Notifications/ActionNotificationGroupTableViewCell.swift @@ -12,7 +12,7 @@ import SwiftSoup class ActionNotificationGroupTableViewCell: UITableViewCell { - var delegate: StatusTableViewCellDelegate? + var delegate: TuskerNavigationDelegate? @IBOutlet weak var actionImageView: UIImageView! @IBOutlet weak var actionAvatarStackView: UIStackView! diff --git a/Tusker/Views/Notifications/FollowNotificationGroupTableViewCell.swift b/Tusker/Views/Notifications/FollowNotificationGroupTableViewCell.swift new file mode 100644 index 0000000000..eb90024f43 --- /dev/null +++ b/Tusker/Views/Notifications/FollowNotificationGroupTableViewCell.swift @@ -0,0 +1,78 @@ +// +// FollowNotificationGroupTableViewCell.swift +// Tusker +// +// Created by Shadowfacts on 9/5/19. +// Copyright © 2019 Shadowfacts. All rights reserved. +// + +import UIKit +import Pachyderm + +class FollowNotificationGroupTableViewCell: UITableViewCell { + + var delegate: TuskerNavigationDelegate? + + @IBOutlet weak var avatarStackView: UIStackView! + @IBOutlet weak var actionLabel: UILabel! + + var group: NotificationGroup! + + override func awakeFromNib() { + super.awakeFromNib() + + NotificationCenter.default.addObserver(self, selector: #selector(updateUIForPreferences), name: .preferencesChanged, object: nil) + } + + @objc func updateUIForPreferences() { + let people = group.notificationIDs.compactMap(MastodonCache.notification(for:)).map { $0.account } + updateActionLabel(people: people) + + for case let imageView as UIImageView in avatarStackView.arrangedSubviews { + imageView.layer.cornerRadius = Preferences.shared.avatarStyle.cornerRadius(for: imageView) + } + } + + func updateUI(group: NotificationGroup) { + self.group = group + + let people = group.notificationIDs.compactMap(MastodonCache.notification(for:)).map { $0.account } + + updateActionLabel(people: people) + + avatarStackView.arrangedSubviews.forEach { $0.removeFromSuperview() } + for account in people { + let imageView = UIImageView() + imageView.translatesAutoresizingMaskIntoConstraints = false + imageView.layer.masksToBounds = true + imageView.layer.cornerRadius = Preferences.shared.avatarStyle.cornerRadiusFraction * 30 + ImageCache.avatars.get(account.avatar) { (data) in + guard let data = data, self.group.id == group.id else { return } + DispatchQueue.main.async { + imageView.image = UIImage(data: data) + } + } + avatarStackView.addArrangedSubview(imageView) + NSLayoutConstraint.activate([ + imageView.widthAnchor.constraint(equalToConstant: 30), + imageView.heightAnchor.constraint(equalToConstant: 30), + ]) + } + } + + func updateActionLabel(people: [Account]) { + // todo: figure out how to localize this + let peopleStr: String + switch (people.count) { + case 1: + peopleStr = people.first!.realDisplayName + case 2: + peopleStr = people.first!.realDisplayName + " and " + people.last!.realDisplayName + default: + peopleStr = people.dropLast().map { $0.realDisplayName }.joined(separator: ", ") + ", and " + people.last!.realDisplayName + + } + actionLabel.text = "Followed by \(peopleStr)" + } + +} diff --git a/Tusker/Views/Notifications/FollowNotificationGroupTableViewCell.xib b/Tusker/Views/Notifications/FollowNotificationGroupTableViewCell.xib new file mode 100644 index 0000000000..c853d3e847 --- /dev/null +++ b/Tusker/Views/Notifications/FollowNotificationGroupTableViewCell.xib @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Tusker/Views/Notifications/FollowNotificationTableViewCell.swift b/Tusker/Views/Notifications/FollowNotificationTableViewCell.swift deleted file mode 100644 index 1e3f1ad8ff..0000000000 --- a/Tusker/Views/Notifications/FollowNotificationTableViewCell.swift +++ /dev/null @@ -1,107 +0,0 @@ -// -// FollowNotificationTableViewCell.swift -// Tusker -// -// Created by Shadowfacts on 9/2/18. -// Copyright © 2018 Shadowfacts. All rights reserved. -// - -import UIKit -import Pachyderm - -class FollowNotificationTableViewCell: UITableViewCell { - - var delegate: StatusTableViewCellDelegate? - - @IBOutlet weak var followLabel: UILabel! - @IBOutlet weak var timestampLabel: UILabel! - @IBOutlet weak var avatarImageView: UIImageView! - @IBOutlet weak var displayNameLabel: UILabel! - @IBOutlet weak var usernameLabel: UILabel! - - var notification: Pachyderm.Notification! - var accountID: String! - - var avatarURL: URL? - var updateTimestampWorkItem: DispatchWorkItem? - - override func awakeFromNib() { - super.awakeFromNib() - - avatarImageView.layer.masksToBounds = true - - NotificationCenter.default.addObserver(self, selector: #selector(updateUIForPreferences), name: .preferencesChanged, object: nil) - } - - func updateUI(for notification: Pachyderm.Notification) { - self.notification = notification - let account = notification.account - self.accountID = account.id - - updateUIForPreferences() - - usernameLabel.text = "@\(account.acct)" - avatarImageView.image = nil - avatarURL = account.avatar - ImageCache.avatars.get(account.avatar) { (data) in - guard let data = data else { return } - DispatchQueue.main.async { - self.avatarImageView.image = UIImage(data: data) - self.avatarURL = nil - } - } - updateTimestamp() - } - - @objc func updateUIForPreferences() { - let account = MastodonCache.account(for: accountID)! - - avatarImageView.layer.cornerRadius = Preferences.shared.avatarStyle.cornerRadius(for: avatarImageView) - followLabel.text = "Followed by \(account.realDisplayName)" - displayNameLabel.text = account.realDisplayName - } - - func updateTimestamp() { - timestampLabel.text = notification.createdAt.timeAgoString() - let delay: DispatchTimeInterval? - switch notification.createdAt.timeAgo().1 { - case .second: - delay = .seconds(10) - case .minute: - delay = .seconds(60) - default: - delay = nil - } - if let delay = delay { - updateTimestampWorkItem = DispatchWorkItem { - self.updateTimestamp() - } - DispatchQueue.main.asyncAfter(deadline: .now() + delay, execute: updateTimestampWorkItem!) - } else { - updateTimestampWorkItem = nil - } - } - - override func prepareForReuse() { - if let url = avatarURL { - ImageCache.avatars.cancel(url) - } - updateTimestampWorkItem?.cancel() - updateTimestampWorkItem = nil - } - - override func setSelected(_ selected: Bool, animated: Bool) { - super.setSelected(selected, animated: animated) - - if selected { - delegate?.selected(account: accountID) - } - } - -} - -extension FollowNotificationTableViewCell: MenuPreviewProvider { - func getPreviewProviders(for location: CGPoint, sourceViewController: UIViewController) -> PreviewProviders? { - return (content: { ProfileTableViewController(accountID: self.accountID) }, actions: { self.actionsForProfile(accountID: self.accountID) }) - } -} diff --git a/Tusker/Views/Notifications/FollowNotificationTableViewCell.xib b/Tusker/Views/Notifications/FollowNotificationTableViewCell.xib deleted file mode 100644 index 4ecbe56856..0000000000 --- a/Tusker/Views/Notifications/FollowNotificationTableViewCell.xib +++ /dev/null @@ -1,92 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -