diff --git a/Pachyderm/Model/Account.swift b/Pachyderm/Model/Account.swift index 9540d6a2..63202d75 100644 --- a/Pachyderm/Model/Account.swift +++ b/Pachyderm/Model/Account.swift @@ -29,12 +29,12 @@ public class Account: Decodable { public let fields: [Field]? public let bot: Bool? - public static func authorizeFollowRequest(_ account: Account) -> Request { - return Request(method: .post, path: "/api/v1/follow_requests/\(account.id)/authorize") + public static func authorizeFollowRequest(_ account: Account) -> Request { + return Request(method: .post, path: "/api/v1/follow_requests/\(account.id)/authorize") } - public static func rejectFollowRequest(_ account: Account) -> Request { - return Request(method: .post, path: "/api/v1/follow_requests/\(account.id)/reject") + public static func rejectFollowRequest(_ account: Account) -> Request { + return Request(method: .post, path: "/api/v1/follow_requests/\(account.id)/reject") } public static func removeFromFollowRequests(_ account: Account) -> Request { diff --git a/Pachyderm/Model/Notification.swift b/Pachyderm/Model/Notification.swift index b3ac3070..56ed204b 100644 --- a/Pachyderm/Model/Notification.swift +++ b/Pachyderm/Model/Notification.swift @@ -36,6 +36,7 @@ extension Notification { case reblog case favourite case follow + case followRequest = "follow_request" } } diff --git a/Tusker.xcodeproj/project.pbxproj b/Tusker.xcodeproj/project.pbxproj index beec2813..9dd15d8b 100644 --- a/Tusker.xcodeproj/project.pbxproj +++ b/Tusker.xcodeproj/project.pbxproj @@ -116,6 +116,8 @@ D64BC18623C1253A000D0238 /* AssetPreviewViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64BC18523C1253A000D0238 /* AssetPreviewViewController.swift */; }; D64BC18823C1640A000D0238 /* PinStatusActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64BC18723C1640A000D0238 /* PinStatusActivity.swift */; }; D64BC18A23C16487000D0238 /* UnpinStatusActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64BC18923C16487000D0238 /* UnpinStatusActivity.swift */; }; + D64BC18F23C18B9D000D0238 /* FollowRequestNotificationTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64BC18D23C18B9D000D0238 /* FollowRequestNotificationTableViewCell.swift */; }; + D64BC19023C18B9D000D0238 /* FollowRequestNotificationTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D64BC18E23C18B9D000D0238 /* FollowRequestNotificationTableViewCell.xib */; }; D64D0AAD2128D88B005A6F37 /* LocalData.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64D0AAC2128D88B005A6F37 /* LocalData.swift */; }; D64D0AB12128D9AE005A6F37 /* OnboardingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64D0AB02128D9AE005A6F37 /* OnboardingViewController.swift */; }; D64F80E2215875CC00BEF393 /* XCBActionType.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64F80E1215875CC00BEF393 /* XCBActionType.swift */; }; @@ -384,6 +386,8 @@ D64BC18523C1253A000D0238 /* AssetPreviewViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AssetPreviewViewController.swift; sourceTree = ""; }; D64BC18723C1640A000D0238 /* PinStatusActivity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PinStatusActivity.swift; sourceTree = ""; }; D64BC18923C16487000D0238 /* UnpinStatusActivity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnpinStatusActivity.swift; sourceTree = ""; }; + D64BC18D23C18B9D000D0238 /* FollowRequestNotificationTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FollowRequestNotificationTableViewCell.swift; sourceTree = ""; }; + D64BC18E23C18B9D000D0238 /* FollowRequestNotificationTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = FollowRequestNotificationTableViewCell.xib; sourceTree = ""; }; D64D0AAC2128D88B005A6F37 /* LocalData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalData.swift; sourceTree = ""; }; D64D0AB02128D9AE005A6F37 /* OnboardingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingViewController.swift; sourceTree = ""; }; D64F80E1215875CC00BEF393 /* XCBActionType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XCBActionType.swift; sourceTree = ""; }; @@ -906,6 +910,8 @@ D6A3BC7B232195C600FD64D5 /* ActionNotificationGroupTableViewCell.xib */, D6A3BC7E2321B7E600FD64D5 /* FollowNotificationGroupTableViewCell.swift */, D6A3BC7F2321B7E600FD64D5 /* FollowNotificationGroupTableViewCell.xib */, + D64BC18D23C18B9D000D0238 /* FollowRequestNotificationTableViewCell.swift */, + D64BC18E23C18B9D000D0238 /* FollowRequestNotificationTableViewCell.xib */, ); path = Notifications; sourceTree = ""; @@ -1482,6 +1488,7 @@ D626493323BD751600612E6E /* ShowCameraCollectionViewCell.xib in Resources */, D626493D23C1000300612E6E /* AlbumTableViewCell.xib in Resources */, D627944723A6AC9300D38C68 /* BasicTableViewCell.xib in Resources */, + D64BC19023C18B9D000D0238 /* FollowRequestNotificationTableViewCell.xib in Resources */, D67C57B221E28FAD00C3118B /* ComposeStatusReplyView.xib in Resources */, 0411610122B442870030A9B7 /* AttachmentViewController.xib in Resources */, D6A3BC812321B7E600FD64D5 /* FollowNotificationGroupTableViewCell.xib in Resources */, @@ -1614,6 +1621,7 @@ D6BC9DB3232D4C07002CA326 /* WellnessPrefsView.swift in Sources */, D6D58DF922074B74009C8DD9 /* LinkLabel.swift in Sources */, 0454DDAF22B462EF00B8BB8E /* GalleryExpandAnimationController.swift in Sources */, + D64BC18F23C18B9D000D0238 /* FollowRequestNotificationTableViewCell.swift in Sources */, D6A3BC8A2321F79B00FD64D5 /* AccountTableViewCell.swift in Sources */, D66A77BB233838DC0058F1EC /* UIFont+Traits.swift in Sources */, D68FEC4F232C5BC300C84F23 /* SegmentedPageViewController.swift in Sources */, diff --git a/Tusker/Extensions/Account+Preferences.swift b/Tusker/Extensions/Account+Preferences.swift index 3622c182..be5da262 100644 --- a/Tusker/Extensions/Account+Preferences.swift +++ b/Tusker/Extensions/Account+Preferences.swift @@ -12,7 +12,9 @@ import Pachyderm extension Account { var realDisplayName: String { - if Preferences.shared.hideCustomEmojiInUsernames { + if displayName.isEmpty { + return username + } else if Preferences.shared.hideCustomEmojiInUsernames { return stripCustomEmoji(from: displayName) } else { return displayName diff --git a/Tusker/Screens/Notifications/NotificationsPageViewController.swift b/Tusker/Screens/Notifications/NotificationsPageViewController.swift index c749ab65..3e503e1b 100644 --- a/Tusker/Screens/Notifications/NotificationsPageViewController.swift +++ b/Tusker/Screens/Notifications/NotificationsPageViewController.swift @@ -13,7 +13,7 @@ class NotificationsPageViewController: SegmentedPageViewController { private let notificationsTitle = NSLocalizedString("Notifications", comment: "notifications tab title") private let mentionsTitle = NSLocalizedString("Mentions", comment: "mentions tab title") - + init() { let notifications = NotificationsTableViewController(allowedTypes: Pachyderm.Notification.Kind.allCases) notifications.title = notificationsTitle diff --git a/Tusker/Screens/Notifications/NotificationsTableViewController.swift b/Tusker/Screens/Notifications/NotificationsTableViewController.swift index e9e8b866..c68ac440 100644 --- a/Tusker/Screens/Notifications/NotificationsTableViewController.swift +++ b/Tusker/Screens/Notifications/NotificationsTableViewController.swift @@ -14,6 +14,7 @@ class NotificationsTableViewController: EnhancedTableViewController { private let statusCell = "statusCell" private let actionGroupCell = "actionGroupCell" private let followGroupCell = "followGroupCell" + private let followRequestCell = "followRequestCell" let excludedTypes: [Pachyderm.Notification.Kind] let groupTypes = [Notification.Kind.favourite, .reblog, .follow] @@ -48,9 +49,10 @@ class NotificationsTableViewController: EnhancedTableViewController { tableView.rowHeight = UITableView.automaticDimension tableView.estimatedRowHeight = 140 - tableView.register(UINib(nibName: "TimelineStatusTableViewCell", bundle: nil), forCellReuseIdentifier: statusCell) - tableView.register(UINib(nibName: "ActionNotificationGroupTableViewCell", bundle: nil), forCellReuseIdentifier: actionGroupCell) - tableView.register(UINib(nibName: "FollowNotificationGroupTableViewCell", bundle: nil), forCellReuseIdentifier: followGroupCell) + tableView.register(UINib(nibName: "TimelineStatusTableViewCell", bundle: .main), forCellReuseIdentifier: statusCell) + tableView.register(UINib(nibName: "ActionNotificationGroupTableViewCell", bundle: .main), forCellReuseIdentifier: actionGroupCell) + tableView.register(UINib(nibName: "FollowNotificationGroupTableViewCell", bundle: .main), forCellReuseIdentifier: followGroupCell) + tableView.register(UINib(nibName: "FollowRequestNotificationTableViewCell", bundle: .main), forCellReuseIdentifier: followRequestCell) tableView.prefetchDataSource = self @@ -102,10 +104,17 @@ class NotificationsTableViewController: EnhancedTableViewController { return cell case .follow: - guard let cell = tableView.dequeueReusableCell(withIdentifier: "followGroupCell", for: indexPath) as? FollowNotificationGroupTableViewCell else { fatalError() } + guard let cell = tableView.dequeueReusableCell(withIdentifier: followGroupCell, for: indexPath) as? FollowNotificationGroupTableViewCell else { fatalError() } cell.updateUI(group: group) cell.delegate = self return cell + + case .followRequest: + guard let notification = MastodonCache.notification(for: group.notificationIDs.first!), + let cell = tableView.dequeueReusableCell(withIdentifier: followRequestCell, for: indexPath) as? FollowRequestNotificationTableViewCell else { fatalError() } + cell.updateUI(notification: notification) + cell.delegate = self + return cell } } diff --git a/Tusker/Views/Account Cell/AccountTableViewCell.xib b/Tusker/Views/Account Cell/AccountTableViewCell.xib index b1f6d9a9..0c76b6cf 100644 --- a/Tusker/Views/Account Cell/AccountTableViewCell.xib +++ b/Tusker/Views/Account Cell/AccountTableViewCell.xib @@ -1,8 +1,8 @@ - + - + diff --git a/Tusker/Views/Notifications/FollowRequestNotificationTableViewCell.swift b/Tusker/Views/Notifications/FollowRequestNotificationTableViewCell.swift new file mode 100644 index 00000000..d2587f43 --- /dev/null +++ b/Tusker/Views/Notifications/FollowRequestNotificationTableViewCell.swift @@ -0,0 +1,142 @@ +// +// FollowRequestNotificationTableViewCell.swift +// Tusker +// +// Created by Shadowfacts on 1/4/20. +// Copyright © 2020 Shadowfacts. All rights reserved. +// + +import UIKit +import Pachyderm + +class FollowRequestNotificationTableViewCell: UITableViewCell { + + var delegate: TuskerNavigationDelegate? + + @IBOutlet weak var stackView: UIStackView! + @IBOutlet weak var avatarImageView: UIImageView! + @IBOutlet weak var timestampLabel: UILabel! + @IBOutlet weak var actionLabel: UILabel! + @IBOutlet weak var actionButtonsStackView: UIStackView! + @IBOutlet weak var acceptButton: UIButton! + @IBOutlet weak var rejectButton: UIButton! + + var notification: Pachyderm.Notification? + var account: Account! + + var updateTimestampWorkItem: DispatchWorkItem? + + override func awakeFromNib() { + super.awakeFromNib() + + avatarImageView.layer.masksToBounds = true + + NotificationCenter.default.addObserver(self, selector: #selector(updateUIForPreferences), name: .preferencesChanged, object: nil) + updateUIForPreferences() + } + + @objc func updateUIForPreferences() { + avatarImageView.layer.cornerRadius = Preferences.shared.avatarStyle.cornerRadiusFraction * 30 + } + + func updateUI(notification: Pachyderm.Notification) { + self.notification = notification + updateUI(account: notification.account) + updateTimestamp() + } + + func updateUI(account: Account) { + self.account = account + actionLabel.text = "Request to follow from \(account.realDisplayName)" + ImageCache.avatars.get(account.avatar) { (data) in + guard self.account == account, let data = data, let image = UIImage(data: data) else { return } + DispatchQueue.main.async { + self.avatarImageView.image = image + } + } + } + + func updateTimestamp() { + guard let notification = notification else { return } + + 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(block: self.updateTimestamp) + DispatchQueue.main.asyncAfter(deadline: .now() + delay, execute: updateTimestampWorkItem!) + } else { + updateTimestampWorkItem = nil + } + } + + override func prepareForReuse() { + super.prepareForReuse() + + updateTimestampWorkItem?.cancel() + updateTimestampWorkItem = nil + } + + // MARK: - Interaction + + @IBAction func rejectButtonPressed() { + let request = Account.rejectFollowRequest(account) + MastodonController.client.run(request) { (response) in + guard case let .success(relationship, _) = response else { fatalError() } + MastodonCache.add(relationship: relationship) + DispatchQueue.main.async { + UINotificationFeedbackGenerator().notificationOccurred(.success) + self.actionButtonsStackView.isHidden = true + let label = UILabel() + label.textAlignment = .center + label.font = .boldSystemFont(ofSize: 17) + label.text = NSLocalizedString("Rejected", comment: "rejected follow request label") + self.stackView.addArrangedSubview(label) + } + } + } + + @IBAction func acceptButtonPressed() { + let request = Account.authorizeFollowRequest(account) + MastodonController.client.run(request) { (response) in + guard case let .success(relationship, _) = response else { fatalError() } + MastodonCache.add(relationship: relationship) + DispatchQueue.main.async { + UINotificationFeedbackGenerator().notificationOccurred(.success) + self.actionButtonsStackView.isHidden = true + let label = UILabel() + label.textAlignment = .center + label.font = .boldSystemFont(ofSize: 17) + label.text = NSLocalizedString("Accepted", comment: "accepted follow request label") + self.stackView.addArrangedSubview(label) + } + } + } + +} + +extension FollowRequestNotificationTableViewCell: SelectableTableViewCell { + func didSelectCell() { + delegate?.selected(account: account.id) + } +} + +extension FollowRequestNotificationTableViewCell: MenuPreviewProvider { + var navigationDelegate: TuskerNavigationDelegate? { return delegate } + + func getPreviewProviders(for location: CGPoint, sourceViewController: UIViewController) -> PreviewProviders? { + return (content: { + return ProfileTableViewController(accountID: self.account.id) + }, actions: { + return [] + }) + } +} diff --git a/Tusker/Views/Notifications/FollowRequestNotificationTableViewCell.xib b/Tusker/Views/Notifications/FollowRequestNotificationTableViewCell.xib new file mode 100644 index 00000000..e01958e8 --- /dev/null +++ b/Tusker/Views/Notifications/FollowRequestNotificationTableViewCell.xib @@ -0,0 +1,109 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +