From d5887f1f0281354df9c39cc98bb64767b7e269a8 Mon Sep 17 00:00:00 2001 From: Shadowfacts Date: Sun, 27 Nov 2022 11:49:53 -0500 Subject: [PATCH] Add post edited notifications Closes #238 --- .../Pachyderm/Model/Notification.swift | 1 + Tusker.xcodeproj/project.pbxproj | 8 ++ .../NotificationsTableViewController.swift | 9 ++ .../PollFinishedTableViewCell.swift | 2 +- ...atusUpdatedNotificationTableViewCell.swift | 114 ++++++++++++++++++ ...StatusUpdatedNotificationTableViewCell.xib | 88 ++++++++++++++ 6 files changed, 221 insertions(+), 1 deletion(-) create mode 100644 Tusker/Views/Notifications/StatusUpdatedNotificationTableViewCell.swift create mode 100644 Tusker/Views/Notifications/StatusUpdatedNotificationTableViewCell.xib diff --git a/Pachyderm/Sources/Pachyderm/Model/Notification.swift b/Pachyderm/Sources/Pachyderm/Model/Notification.swift index e9f26f2cff..67d2d52f20 100644 --- a/Pachyderm/Sources/Pachyderm/Model/Notification.swift +++ b/Pachyderm/Sources/Pachyderm/Model/Notification.swift @@ -56,6 +56,7 @@ extension Notification { case follow case followRequest = "follow_request" case poll + case update case unknown } } diff --git a/Tusker.xcodeproj/project.pbxproj b/Tusker.xcodeproj/project.pbxproj index 22e76a5292..6c064e947c 100644 --- a/Tusker.xcodeproj/project.pbxproj +++ b/Tusker.xcodeproj/project.pbxproj @@ -46,6 +46,8 @@ D61DC84D28F500D200B82C6E /* ProfileViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D61DC84C28F500D200B82C6E /* ProfileViewController.swift */; }; D61F75882932DB6000C0B37F /* StatusSwipeAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = D61F75872932DB6000C0B37F /* StatusSwipeAction.swift */; }; D61F758A2932E1FC00C0B37F /* SwipeActionsPrefsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D61F75892932E1FC00C0B37F /* SwipeActionsPrefsView.swift */; }; + D61F758D2933C69C00C0B37F /* StatusUpdatedNotificationTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D61F758B2933C69C00C0B37F /* StatusUpdatedNotificationTableViewCell.swift */; }; + D61F758E2933C69C00C0B37F /* StatusUpdatedNotificationTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D61F758C2933C69C00C0B37F /* StatusUpdatedNotificationTableViewCell.xib */; }; D620483423D3801D008A63EF /* LinkTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D620483323D3801D008A63EF /* LinkTextView.swift */; }; D620483623D38075008A63EF /* ContentTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D620483523D38075008A63EF /* ContentTextView.swift */; }; D620483823D38190008A63EF /* StatusContentTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D620483723D38190008A63EF /* StatusContentTextView.swift */; }; @@ -410,6 +412,8 @@ D61DC84C28F500D200B82C6E /* ProfileViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileViewController.swift; sourceTree = ""; }; D61F75872932DB6000C0B37F /* StatusSwipeAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusSwipeAction.swift; sourceTree = ""; }; D61F75892932E1FC00C0B37F /* SwipeActionsPrefsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwipeActionsPrefsView.swift; sourceTree = ""; }; + D61F758B2933C69C00C0B37F /* StatusUpdatedNotificationTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusUpdatedNotificationTableViewCell.swift; sourceTree = ""; }; + D61F758C2933C69C00C0B37F /* StatusUpdatedNotificationTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = StatusUpdatedNotificationTableViewCell.xib; sourceTree = ""; }; D620483323D3801D008A63EF /* LinkTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkTextView.swift; sourceTree = ""; }; D620483523D38075008A63EF /* ContentTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentTextView.swift; sourceTree = ""; }; D620483723D38190008A63EF /* StatusContentTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusContentTextView.swift; sourceTree = ""; }; @@ -1086,6 +1090,8 @@ D64BC18E23C18B9D000D0238 /* FollowRequestNotificationTableViewCell.xib */, D662AEED263A3B880082A153 /* PollFinishedTableViewCell.swift */, D662AEEE263A3B880082A153 /* PollFinishedTableViewCell.xib */, + D61F758B2933C69C00C0B37F /* StatusUpdatedNotificationTableViewCell.swift */, + D61F758C2933C69C00C0B37F /* StatusUpdatedNotificationTableViewCell.xib */, ); path = Notifications; sourceTree = ""; @@ -1704,6 +1710,7 @@ D667E5E12134937B0057A976 /* TimelineStatusTableViewCell.xib in Resources */, D6F2E966249E8BFD005846BB /* IssueReporterViewController.xib in Resources */, D662AEF0263A3B880082A153 /* PollFinishedTableViewCell.xib in Resources */, + D61F758E2933C69C00C0B37F /* StatusUpdatedNotificationTableViewCell.xib in Resources */, D6A4DCCD2553667800D9DE31 /* FastAccountSwitcherViewController.xib in Resources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -1980,6 +1987,7 @@ D6262C9A28D01C4B00390C1F /* LoadingTableViewCell.swift in Sources */, D68C2AE325869BAB00548EFF /* AuxiliarySceneDelegate.swift in Sources */, D66C900B28DAB7FD00217BF2 /* TimelineViewController.swift in Sources */, + D61F758D2933C69C00C0B37F /* StatusUpdatedNotificationTableViewCell.swift in Sources */, D6AEBB432321685E00E5038B /* OpenInSafariActivity.swift in Sources */, D6C693FE2162FEEA007D6A6D /* UIViewController+Children.swift in Sources */, D61A45EA28DF51EE002BE511 /* TimelineLikeCollectionViewController.swift in Sources */, diff --git a/Tusker/Screens/Notifications/NotificationsTableViewController.swift b/Tusker/Screens/Notifications/NotificationsTableViewController.swift index df4f46cc70..0343a36920 100644 --- a/Tusker/Screens/Notifications/NotificationsTableViewController.swift +++ b/Tusker/Screens/Notifications/NotificationsTableViewController.swift @@ -16,6 +16,7 @@ class NotificationsTableViewController: DiffableTimelineLikeTableViewController< private let followGroupCell = "followGroupCell" private let followRequestCell = "followRequestCell" private let pollCell = "pollCell" + private let updatedCell = "updatedCell" private let unknownCell = "unknownCell" weak var mastodonController: MastodonController! @@ -51,6 +52,7 @@ class NotificationsTableViewController: DiffableTimelineLikeTableViewController< tableView.register(UINib(nibName: "FollowNotificationGroupTableViewCell", bundle: .main), forCellReuseIdentifier: followGroupCell) tableView.register(UINib(nibName: "FollowRequestNotificationTableViewCell", bundle: .main), forCellReuseIdentifier: followRequestCell) tableView.register(UINib(nibName: "PollFinishedTableViewCell", bundle: .main), forCellReuseIdentifier: pollCell) + tableView.register(UINib(nibName: "StatusUpdatedNotificationTableViewCell", bundle: .main), forCellReuseIdentifier: updatedCell) tableView.register(UINib(nibName: "BasicTableViewCell", bundle: .main), forCellReuseIdentifier: unknownCell) } @@ -98,6 +100,13 @@ class NotificationsTableViewController: DiffableTimelineLikeTableViewController< cell.updateUI(notification: notification) return cell + case .update: + guard let notification = group.notifications.first, + let cell = tableView.dequeueReusableCell(withIdentifier: updatedCell, for: indexPath) as? StatusUpdatedNotificationTableViewCell else { fatalError() } + cell.delegate = self + cell.updateUI(notification: notification) + return cell + case .unknown: let cell = tableView.dequeueReusableCell(withIdentifier: unknownCell, for: indexPath) cell.textLabel!.text = NSLocalizedString("Unknown Notification", comment: "unknown notification fallback cell text") diff --git a/Tusker/Views/Notifications/PollFinishedTableViewCell.swift b/Tusker/Views/Notifications/PollFinishedTableViewCell.swift index 6ac1682172..726220a137 100644 --- a/Tusker/Views/Notifications/PollFinishedTableViewCell.swift +++ b/Tusker/Views/Notifications/PollFinishedTableViewCell.swift @@ -52,7 +52,7 @@ class PollFinishedTableViewCell: UITableViewCell { displayNameLabel.text = notification.account.displayName displayNameLabel.setEmojis(notification.account.emojis, identifier: notification.account.id) - let doc = try! SwiftSoup.parse(status.content) + let doc = try! SwiftSoup.parseBodyFragment(status.content) statusContentLabel.text = try! doc.text() pollView.updateUI(status: status, poll: poll) diff --git a/Tusker/Views/Notifications/StatusUpdatedNotificationTableViewCell.swift b/Tusker/Views/Notifications/StatusUpdatedNotificationTableViewCell.swift new file mode 100644 index 0000000000..555894b272 --- /dev/null +++ b/Tusker/Views/Notifications/StatusUpdatedNotificationTableViewCell.swift @@ -0,0 +1,114 @@ +// +// StatusUpdatedNotificationTableViewCell.swift +// Tusker +// +// Created by Shadowfacts on 11/27/22. +// Copyright © 2022 Shadowfacts. All rights reserved. +// + +import UIKit +import Pachyderm +import SwiftSoup + +class StatusUpdatedNotificationTableViewCell: UITableViewCell { + + weak var delegate: (TuskerNavigationDelegate & MenuActionProvider)? + + @IBOutlet weak var timestampLabel: UILabel! + @IBOutlet weak var displayNameLabel: EmojiLabel! + @IBOutlet weak var contentLabel: UILabel! + + private var notification: Pachyderm.Notification? + private var updateTimestampWorkItem: DispatchWorkItem? + + override func awakeFromNib() { + super.awakeFromNib() + + timestampLabel.font = UIFont(descriptor: .preferredFontDescriptor(withTextStyle: .body).addingAttributes([ + .traits: [ + UIFontDescriptor.TraitKey.weight: UIFont.Weight.light.rawValue, + ] + ]), size: 0) + timestampLabel.adjustsFontForContentSizeCategory = true + + displayNameLabel.font = .preferredFont(forTextStyle: .body).withTraits(.traitBold)! + displayNameLabel.adjustsFontForContentSizeCategory = true + } + + func updateUI(notification: Pachyderm.Notification) { + guard notification.kind == .update, + let status = notification.status else { + return + } + + self.notification = notification + + updateTimestamp() + + displayNameLabel.text = notification.account.displayName + displayNameLabel.setEmojis(notification.account.emojis, identifier: notification.account.id) + + let doc = try! SwiftSoup.parseBodyFragment(status.content) + contentLabel.text = try! doc.text() + } + + private func updateTimestamp() { + guard let 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 { + if updateTimestampWorkItem == nil { + updateTimestampWorkItem = DispatchWorkItem { [weak self] in + self?.updateTimestamp() + } + DispatchQueue.main.asyncAfter(deadline: .now() + delay, execute: updateTimestampWorkItem!) + } + } else { + updateTimestampWorkItem = nil + } + } + + override func prepareForReuse() { + super.prepareForReuse() + + updateTimestampWorkItem?.cancel() + updateTimestampWorkItem = nil + } + +} + +extension StatusUpdatedNotificationTableViewCell: SelectableTableViewCell { + func didSelectCell() { + guard let delegate, + let status = notification?.status else { + return + } + let vc = delegate.conversation(mainStatusID: status.id, state: .unknown) + delegate.show(vc) + } +} + +extension StatusUpdatedNotificationTableViewCell: MenuPreviewProvider { + func getPreviewProviders(for location: CGPoint, sourceViewController: UIViewController) -> PreviewProviders? { + guard let delegate, + let statusID = notification?.status?.id, + let status = delegate.apiController.persistentContainer.status(for: statusID) else { + return nil + } + return (content: { + delegate.conversation(mainStatusID: statusID, state: .unknown) + }, actions: { + delegate.actionsForStatus(status, sourceView: self) + }) + } +} diff --git a/Tusker/Views/Notifications/StatusUpdatedNotificationTableViewCell.xib b/Tusker/Views/Notifications/StatusUpdatedNotificationTableViewCell.xib new file mode 100644 index 0000000000..8e7f5f5409 --- /dev/null +++ b/Tusker/Views/Notifications/StatusUpdatedNotificationTableViewCell.xib @@ -0,0 +1,88 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +