From 240ccf23a41a9a34f7841e85ef096460498497d5 Mon Sep 17 00:00:00 2001 From: Shadowfacts Date: Sat, 2 Apr 2022 10:39:03 -0400 Subject: [PATCH] Add Trending Posts --- Pachyderm/Client.swift | 12 ++- Tusker.xcodeproj/project.pbxproj | 4 + .../Compose/ComposeAutocompleteView.swift | 2 +- .../AddSavedHashtagViewController.swift | 2 +- .../Explore/ExploreViewController.swift | 17 +++- .../TrendingHashtagsViewController.swift | 14 +-- .../TrendingStatusesViewController.swift | 93 +++++++++++++++++++ 7 files changed, 130 insertions(+), 14 deletions(-) create mode 100644 Tusker/Screens/Explore/TrendingStatusesViewController.swift diff --git a/Pachyderm/Client.swift b/Pachyderm/Client.swift index 93464f33..e45442c7 100644 --- a/Pachyderm/Client.swift +++ b/Pachyderm/Client.swift @@ -368,7 +368,7 @@ public class Client { } // MARK: - Instance - public static func getTrends(limit: Int? = nil) -> Request<[Hashtag]> { + public static func getTrendingHashtags(limit: Int? = nil) -> Request<[Hashtag]> { let parameters: [Parameter] if let limit = limit { parameters = ["limit" => limit] @@ -378,6 +378,16 @@ public class Client { return Request<[Hashtag]>(method: .get, path: "/api/v1/trends", queryParameters: parameters) } + public static func getTrendingStatuses(limit: Int? = nil) -> Request<[Status]> { + let parameters: [Parameter] + if let limit = limit { + parameters = ["limit" => limit] + } else { + parameters = [] + } + return Request(method: .get, path: "/api/v1/trends/statuses", queryParameters: parameters) + } + public static func getFeaturedProfiles(local: Bool, order: DirectoryOrder, offset: Int? = nil, limit: Int? = nil) -> Request<[Account]> { var parameters = [ "order" => order.rawValue, diff --git a/Tusker.xcodeproj/project.pbxproj b/Tusker.xcodeproj/project.pbxproj index 2e780440..4142e60e 100644 --- a/Tusker.xcodeproj/project.pbxproj +++ b/Tusker.xcodeproj/project.pbxproj @@ -71,6 +71,7 @@ D6109A11214607D500432DC2 /* Timeline.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6109A10214607D500432DC2 /* Timeline.swift */; }; D6114E0927F3EA3D0080E273 /* CrashReporterViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6114E0827F3EA3D0080E273 /* CrashReporterViewController.swift */; }; D6114E0B27F3F6EA0080E273 /* Endpoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6114E0A27F3F6EA0080E273 /* Endpoint.swift */; }; + D6114E0D27F7FEB30080E273 /* TrendingStatusesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6114E0C27F7FEB30080E273 /* TrendingStatusesViewController.swift */; }; D611C2CF232DC61100C86A49 /* HashtagTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D611C2CD232DC61100C86A49 /* HashtagTableViewCell.swift */; }; D611C2D0232DC61100C86A49 /* HashtagTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D611C2CE232DC61100C86A49 /* HashtagTableViewCell.xib */; }; D61AC1D3232E928600C54D2D /* InstanceSelector.swift in Sources */ = {isa = PBXBuildFile; fileRef = D61AC1D2232E928600C54D2D /* InstanceSelector.swift */; }; @@ -484,6 +485,7 @@ D6109A10214607D500432DC2 /* Timeline.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Timeline.swift; sourceTree = ""; }; D6114E0827F3EA3D0080E273 /* CrashReporterViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CrashReporterViewController.swift; sourceTree = ""; }; D6114E0A27F3F6EA0080E273 /* Endpoint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Endpoint.swift; sourceTree = ""; }; + D6114E0C27F7FEB30080E273 /* TrendingStatusesViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrendingStatusesViewController.swift; sourceTree = ""; }; D611C2CD232DC61100C86A49 /* HashtagTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HashtagTableViewCell.swift; sourceTree = ""; }; D611C2CE232DC61100C86A49 /* HashtagTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = HashtagTableViewCell.xib; sourceTree = ""; }; D61AC1D2232E928600C54D2D /* InstanceSelector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstanceSelector.swift; sourceTree = ""; }; @@ -1023,6 +1025,7 @@ D6945C3323AC6431005C403C /* AddSavedHashtagViewController.swift */, D6093F9A25BDD4B9004811E6 /* HashtagSearchResultsViewController.swift */, D6945C3923AC75E2005C403C /* FindInstanceViewController.swift */, + D6114E0C27F7FEB30080E273 /* TrendingStatusesViewController.swift */, D693A72725CF282E003A14E2 /* TrendingHashtagsViewController.swift */, D693A72925CF8C1E003A14E2 /* ProfileDirectoryViewController.swift */, D693A72D25CF91C6003A14E2 /* FeaturedProfileCollectionViewCell.swift */, @@ -2194,6 +2197,7 @@ 04ED00B121481ED800567C53 /* SteppedProgressView.swift in Sources */, D677284C24ECBE9100C732D3 /* ComposeAvatarImageView.swift in Sources */, D6A6C11B25B63CEE00298D0F /* MemoryCache.swift in Sources */, + D6114E0D27F7FEB30080E273 /* TrendingStatusesViewController.swift in Sources */, D6BC9DDA232D8BE5002CA326 /* SearchResultsViewController.swift in Sources */, D627FF7F217E95E000CC0648 /* DraftTableViewCell.swift in Sources */, D6B17255254F88B800128392 /* OppositeCollapseKeywordsView.swift in Sources */, diff --git a/Tusker/Screens/Compose/ComposeAutocompleteView.swift b/Tusker/Screens/Compose/ComposeAutocompleteView.swift index 6513dba8..641a9002 100644 --- a/Tusker/Screens/Compose/ComposeAutocompleteView.swift +++ b/Tusker/Screens/Compose/ComposeAutocompleteView.swift @@ -346,7 +346,7 @@ struct ComposeAutocompleteHashtagsView: View { let group = DispatchGroup() group.enter() - trendingRequest = mastodonController.run(Client.getTrends()) { (response) in + trendingRequest = mastodonController.run(Client.getTrendingHashtags()) { (response) in defer { group.leave() } guard case let .success(trends, _) = response else { return } trendingTags = trends diff --git a/Tusker/Screens/Explore/AddSavedHashtagViewController.swift b/Tusker/Screens/Explore/AddSavedHashtagViewController.swift index cb439bd8..2ad81bd8 100644 --- a/Tusker/Screens/Explore/AddSavedHashtagViewController.swift +++ b/Tusker/Screens/Explore/AddSavedHashtagViewController.swift @@ -69,7 +69,7 @@ class AddSavedHashtagViewController: EnhancedTableViewController { override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) - let request = Client.getTrends(limit: 10) + let request = Client.getTrendingHashtags(limit: 10) mastodonController.run(request) { (response) in var snapshot = NSDiffableDataSourceSnapshot() diff --git a/Tusker/Screens/Explore/ExploreViewController.swift b/Tusker/Screens/Explore/ExploreViewController.swift index fe4cb070..b210ce43 100644 --- a/Tusker/Screens/Explore/ExploreViewController.swift +++ b/Tusker/Screens/Explore/ExploreViewController.swift @@ -155,7 +155,8 @@ class ExploreViewController: UIViewController, UICollectionViewDelegate { private func addDiscoverSection(to snapshot: inout NSDiffableDataSourceSnapshot) { snapshot.insertSections([.discover], afterSection: .bookmarks) - snapshot.appendItems([.trendingTags, .profileDirectory], toSection: .discover) + // todo: check version + snapshot.appendItems([.trendingStatuses, .trendingTags, .profileDirectory], toSection: .discover) } private func ownInstanceLoaded(_ instance: Instance) { @@ -293,6 +294,9 @@ class ExploreViewController: UIViewController, UICollectionViewDelegate { case .bookmarks: show(BookmarksTableViewController(mastodonController: mastodonController), sender: nil) + case .trendingStatuses: + show(TrendingStatusesViewController(mastodonController: mastodonController), sender: nil) + case .trendingTags: show(TrendingHashtagsViewController(mastodonController: mastodonController), sender: nil) @@ -375,6 +379,7 @@ extension ExploreViewController { enum Item: Hashable { case bookmarks + case trendingStatuses case trendingTags case profileDirectory case list(List) @@ -388,6 +393,8 @@ extension ExploreViewController { switch self { case .bookmarks: return NSLocalizedString("Bookmarks", comment: "bookmarks nav item title") + case .trendingStatuses: + return NSLocalizedString("Trending Posts", comment: "trending statuses nav item title") case .trendingTags: return NSLocalizedString("Trending Hashtags", comment: "trending hashtags nav item title") case .profileDirectory: @@ -412,6 +419,8 @@ extension ExploreViewController { switch self { case .bookmarks: name = "bookmark.fill" + case .trendingStatuses: + name = "doc.text.image" case .trendingTags: name = "arrow.up.arrow.down" case .profileDirectory: @@ -434,6 +443,8 @@ extension ExploreViewController { switch (lhs, rhs) { case (.bookmarks, .bookmarks): return true + case (.trendingStatuses, .trendingStatuses): + return true case (.trendingTags, .trendingTags): return true case (.profileDirectory, .profileDirectory): @@ -459,6 +470,8 @@ extension ExploreViewController { switch self { case .bookmarks: hasher.combine("bookmarks") + case .trendingStatuses: + hasher.combine("trendingStatuses") case .trendingTags: hasher.combine("trendingTags") case .profileDirectory: @@ -517,7 +530,7 @@ extension ExploreViewController: UICollectionViewDragDelegate { case let .savedInstance(url): provider = NSItemProvider(object: url as NSURL) // todo: should dragging public timelines into new windows be supported? - case .trendingTags, .profileDirectory, .addList, .addSavedHashtag, .findInstance: + default: return [] } return [UIDragItem(itemProvider: provider)] diff --git a/Tusker/Screens/Explore/TrendingHashtagsViewController.swift b/Tusker/Screens/Explore/TrendingHashtagsViewController.swift index 4b06db0f..135f6870 100644 --- a/Tusker/Screens/Explore/TrendingHashtagsViewController.swift +++ b/Tusker/Screens/Explore/TrendingHashtagsViewController.swift @@ -48,19 +48,15 @@ class TrendingHashtagsViewController: EnhancedTableViewController { override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) - let request = Client.getTrends(limit: 10) - mastodonController.run(request) { (response) in - var snapshot = NSDiffableDataSourceSnapshot() - - guard case let .success(hashtags, _) = response, - hashtags.count > 0 else { - self.dataSource.apply(snapshot) + let request = Client.getTrendingHashtags(limit: 10) + Task { + guard let (hashtags, _) = try? await mastodonController.run(request) else { return } - + var snapshot = NSDiffableDataSourceSnapshot() snapshot.appendSections([.trendingTags]) snapshot.appendItems(hashtags.map { .tag($0) }) - self.dataSource.apply(snapshot) + dataSource.apply(snapshot) } } diff --git a/Tusker/Screens/Explore/TrendingStatusesViewController.swift b/Tusker/Screens/Explore/TrendingStatusesViewController.swift new file mode 100644 index 00000000..a16c4619 --- /dev/null +++ b/Tusker/Screens/Explore/TrendingStatusesViewController.swift @@ -0,0 +1,93 @@ +// +// TrendingStatusesViewController.swift +// Tusker +// +// Created by Shadowfacts on 4/1/22. +// Copyright © 2022 Shadowfacts. All rights reserved. +// + +import UIKit +import Pachyderm + +class TrendingStatusesViewController: EnhancedTableViewController { + + weak var mastodonController: MastodonController! + + private var dataSource: UITableViewDiffableDataSource! + + init(mastodonController: MastodonController) { + self.mastodonController = mastodonController + + super.init(style: .grouped) + + dragEnabled = true + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func viewDidLoad() { + super.viewDidLoad() + + title = NSLocalizedString("Trending Posts", comment: "trending posts screen title") + + tableView.register(UINib(nibName: "TimelineStatusTableViewCell", bundle: .main), forCellReuseIdentifier: "statusCell") + tableView.estimatedRowHeight = 144 + + dataSource = UITableViewDiffableDataSource(tableView: tableView, cellProvider: { tableView, indexPath, item in + let cell = tableView.dequeueReusableCell(withIdentifier: "statusCell", for: indexPath) as! TimelineStatusTableViewCell + cell.delegate = self + cell.updateUI(statusID: item.id, state: item.state) + return cell + }) + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + + let request = Client.getTrendingStatuses() + Task { + guard let (statuses, _) = try? await mastodonController.run(request) else { + return + } + mastodonController.persistentContainer.addAll(statuses: statuses) { + var snapshot = NSDiffableDataSourceSnapshot() + snapshot.appendSections([.statuses]) + snapshot.appendItems(statuses.map { Item(id: $0.id, state: .unknown) }) + self.dataSource.apply(snapshot) + } + } + } + + // MARK: - Table View Delegate +} + +extension TrendingStatusesViewController { + enum Section { + case statuses + } + struct Item: Hashable { + let id: String + let state: StatusState + + static func ==(lhs: Item, rhs: Item) -> Bool { + return lhs.id == rhs.id + } + + func hash(into hasher: inout Hasher) { + hasher.combine(id) + } + } +} + +extension TrendingStatusesViewController: TuskerNavigationDelegate { + var apiController: MastodonController { mastodonController } +} + +extension TrendingStatusesViewController: StatusTableViewCellDelegate { + func statusCellCollapsedStateChanged(_ cell: BaseStatusTableViewCell) { + tableView.beginUpdates() + tableView.endUpdates() + } +}