From 377b5f5c8566ca64d0af45fba128e6c159e108fb Mon Sep 17 00:00:00 2001 From: Shadowfacts Date: Thu, 19 Dec 2019 22:41:23 -0500 Subject: [PATCH] Add ability to save and view instance public timelines --- Pachyderm/Client.swift | 2 +- Pachyderm/Model/Timeline.swift | 16 ++++- Pachyderm/Request/Request.swift | 4 +- Tusker.xcodeproj/project.pbxproj | 22 +++--- Tusker/Extensions/Timline+UI.swift | 4 ++ Tusker/SavedHashtagsManager.swift | 4 +- Tusker/SavedInstancesManager.swift | 61 ++++++++++++++++ .../Explore/ExploreViewController.swift | 65 ++++++++++++++++- .../Screens/FindInstanceViewController.swift | 39 ++++++++++ .../HashtagTimelineViewController.swift | 0 .../InstanceTimelineViewController.swift | 71 +++++++++++++++++++ Tusker/Shortcuts/UserActivityManager.swift | 3 + 12 files changed, 275 insertions(+), 16 deletions(-) create mode 100644 Tusker/SavedInstancesManager.swift create mode 100644 Tusker/Screens/FindInstanceViewController.swift rename Tusker/Screens/{Hashtag Timeline => Timeline}/HashtagTimelineViewController.swift (100%) create mode 100644 Tusker/Screens/Timeline/InstanceTimelineViewController.swift diff --git a/Pachyderm/Client.swift b/Pachyderm/Client.swift index cd35a45a..89c9a573 100644 --- a/Pachyderm/Client.swift +++ b/Pachyderm/Client.swift @@ -81,7 +81,7 @@ public class Client { } func createURLRequest(request: Request) -> URLRequest? { - guard var components = URLComponents(url: baseURL, resolvingAgainstBaseURL: true) else { return nil } + guard var components = URLComponents(url: request.baseURL ?? baseURL, resolvingAgainstBaseURL: true) else { return nil } components.path = request.path components.queryItems = request.queryParameters.queryItems guard let url = components.url else { return nil } diff --git a/Pachyderm/Model/Timeline.swift b/Pachyderm/Model/Timeline.swift index 96d7a85a..65f8ac78 100644 --- a/Pachyderm/Model/Timeline.swift +++ b/Pachyderm/Model/Timeline.swift @@ -11,6 +11,7 @@ import Foundation public enum Timeline { case home case `public`(local: Bool) + case instance(instanceURL: URL) case tag(hashtag: String) case list(id: String) case direct @@ -21,7 +22,7 @@ extension Timeline { switch self { case .home: return "/api/v1/timelines/home" - case .public: + case .public, .instance(_): return "/api/v1/timelines/public" case let .tag(hashtag): return "/api/v1/timelines/tag/\(hashtag)" @@ -33,7 +34,12 @@ extension Timeline { } func request(range: RequestRange) -> Request<[Status]> { - var request = Request<[Status]>(method: .get, path: endpoint) + var request: Request<[Status]> + if case let .instance(instanceURL) = self { + request = Request<[Status]>(method: .get, baseURL: instanceURL, path: endpoint) + } else { + request = Request<[Status]>(method: .get, path: endpoint) + } if case .public(true) = self { request.queryParameters.append("local" => true) } @@ -51,6 +57,8 @@ extension Timeline: Codable { self = .home case "public": self = .public(local: try container.decode(Bool.self, forKey: .local)) + case "instanceURL": + self = .instance(instanceURL: try container.decode(URL.self, forKey: .instanceURL)) case "tag": self = .tag(hashtag: try container.decode(String.self, forKey: .hashtag)) case "list": @@ -70,6 +78,9 @@ extension Timeline: Codable { case let .public(local): try container.encode("public", forKey: .type) try container.encode(local, forKey: .local) + case let .instance(instanceURL): + try container.encode("instanceURL", forKey: .type) + try container.encode(instanceURL, forKey: .instanceURL) case let .tag(hashtag): try container.encode("tag", forKey: .type) try container.encode(hashtag, forKey: .hashtag) @@ -84,6 +95,7 @@ extension Timeline: Codable { enum CodingKeys: String, CodingKey { case type case local + case instanceURL case hashtag case listID } diff --git a/Pachyderm/Request/Request.swift b/Pachyderm/Request/Request.swift index c21aaf67..e877e873 100644 --- a/Pachyderm/Request/Request.swift +++ b/Pachyderm/Request/Request.swift @@ -10,12 +10,14 @@ import Foundation public struct Request { let method: Method + let baseURL: URL? let path: String let body: Body var queryParameters: [Parameter] - init(method: Method, path: String, body: Body = .empty, queryParameters: [Parameter] = []) { + init(method: Method, baseURL: URL? = nil, path: String, body: Body = .empty, queryParameters: [Parameter] = []) { self.method = method + self.baseURL = baseURL self.path = path self.body = body self.queryParameters = queryParameters diff --git a/Tusker.xcodeproj/project.pbxproj b/Tusker.xcodeproj/project.pbxproj index a13a6170..df771d56 100644 --- a/Tusker.xcodeproj/project.pbxproj +++ b/Tusker.xcodeproj/project.pbxproj @@ -167,6 +167,9 @@ D6945C2F23AC47C3005C403C /* SavedHashtagsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6945C2E23AC47C3005C403C /* SavedHashtagsManager.swift */; }; D6945C3223AC4D36005C403C /* HashtagTimelineViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6945C3123AC4D36005C403C /* HashtagTimelineViewController.swift */; }; D6945C3423AC6431005C403C /* AddSavedHashtagViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6945C3323AC6431005C403C /* AddSavedHashtagViewController.swift */; }; + D6945C3623AC6C09005C403C /* SavedInstancesManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6945C3523AC6C09005C403C /* SavedInstancesManager.swift */; }; + D6945C3823AC739F005C403C /* InstanceTimelineViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6945C3723AC739F005C403C /* InstanceTimelineViewController.swift */; }; + D6945C3A23AC75E2005C403C /* FindInstanceViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6945C3923AC75E2005C403C /* FindInstanceViewController.swift */; }; D6A3BC7723218E1300FD64D5 /* TimelineSegment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6A3BC7323218C6E00FD64D5 /* TimelineSegment.swift */; }; D6A3BC7923218E9200FD64D5 /* NotificationGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6A3BC7823218E9200FD64D5 /* NotificationGroup.swift */; }; D6A3BC7C232195C600FD64D5 /* ActionNotificationGroupTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6A3BC7A232195C600FD64D5 /* ActionNotificationGroupTableViewCell.swift */; }; @@ -441,6 +444,9 @@ D6945C2E23AC47C3005C403C /* SavedHashtagsManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SavedHashtagsManager.swift; sourceTree = ""; }; D6945C3123AC4D36005C403C /* HashtagTimelineViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HashtagTimelineViewController.swift; sourceTree = ""; }; D6945C3323AC6431005C403C /* AddSavedHashtagViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddSavedHashtagViewController.swift; sourceTree = ""; }; + D6945C3523AC6C09005C403C /* SavedInstancesManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SavedInstancesManager.swift; sourceTree = ""; }; + D6945C3723AC739F005C403C /* InstanceTimelineViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstanceTimelineViewController.swift; sourceTree = ""; }; + D6945C3923AC75E2005C403C /* FindInstanceViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = FindInstanceViewController.swift; path = Tusker/Screens/FindInstanceViewController.swift; sourceTree = SOURCE_ROOT; }; D6A3BC7323218C6E00FD64D5 /* TimelineSegment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineSegment.swift; sourceTree = ""; }; D6A3BC7823218E9200FD64D5 /* NotificationGroup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationGroup.swift; sourceTree = ""; }; D6A3BC7A232195C600FD64D5 /* ActionNotificationGroupTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionNotificationGroupTableViewCell.swift; sourceTree = ""; }; @@ -738,6 +744,7 @@ children = ( D627943D23A564D400D38C68 /* ExploreViewController.swift */, D6945C3323AC6431005C403C /* AddSavedHashtagViewController.swift */, + D6945C3923AC75E2005C403C /* FindInstanceViewController.swift */, ); path = Explore; sourceTree = ""; @@ -788,7 +795,6 @@ D641C782213DD7F0004B4513 /* Main */, D641C783213DD7FE004B4513 /* Onboarding */, D641C781213DD7DD004B4513 /* Timeline */, - D6945C3023AC4D21005C403C /* Hashtag Timeline */, D641C784213DD819004B4513 /* Profile */, D641C785213DD83B004B4513 /* Conversation */, D641C786213DD852004B4513 /* Notifications */, @@ -812,6 +818,8 @@ children = ( D6BC9DD6232D7811002CA326 /* TimelinesPageViewController.swift */, D6F953EB212519E700CF0F2B /* TimelineTableViewController.swift */, + D6945C3123AC4D36005C403C /* HashtagTimelineViewController.swift */, + D6945C3723AC739F005C403C /* InstanceTimelineViewController.swift */, ); path = Timeline; sourceTree = ""; @@ -1065,14 +1073,6 @@ path = de.lproj; sourceTree = ""; }; - D6945C3023AC4D21005C403C /* Hashtag Timeline */ = { - isa = PBXGroup; - children = ( - D6945C3123AC4D36005C403C /* HashtagTimelineViewController.swift */, - ); - path = "Hashtag Timeline"; - sourceTree = ""; - }; D6A3BC7223218C6E00FD64D5 /* Utilities */ = { isa = PBXGroup; children = ( @@ -1226,6 +1226,7 @@ D64D0AAC2128D88B005A6F37 /* LocalData.swift */, D627FF75217E923E00CC0648 /* DraftsManager.swift */, D6945C2E23AC47C3005C403C /* SavedHashtagsManager.swift */, + D6945C3523AC6C09005C403C /* SavedInstancesManager.swift */, D6028B9A2150811100F223B9 /* MastodonCache.swift */, D6C693EE216192C2007D6A6D /* TuskerNavigationDelegate.swift */, D6F1F84E2193B9BE00F5FE67 /* Caching */, @@ -1653,6 +1654,7 @@ D68FEC4F232C5BC300C84F23 /* SegmentedPageViewController.swift in Sources */, 0450531F22B0097E00100BA2 /* Timline+UI.swift in Sources */, D667E5F52135BCD50057A976 /* ConversationTableViewController.swift in Sources */, + D6945C3623AC6C09005C403C /* SavedInstancesManager.swift in Sources */, D6C7D27D22B6EBF800071952 /* AttachmentsContainerView.swift in Sources */, D6F953F021251A2900CF0F2B /* MastodonController.swift in Sources */, 0411610022B442870030A9B7 /* AttachmentViewController.swift in Sources */, @@ -1666,6 +1668,7 @@ D6028B9B2150811100F223B9 /* MastodonCache.swift in Sources */, D6A3BC802321B7E600FD64D5 /* FollowNotificationGroupTableViewCell.swift in Sources */, D627944D23A9A03D00D38C68 /* ListTimelineViewController.swift in Sources */, + D6945C3823AC739F005C403C /* InstanceTimelineViewController.swift in Sources */, D62D2422217AA7E1005076CC /* UserActivityManager.swift in Sources */, D60D2B8223844C71001B87A3 /* BaseStatusTableViewCell.swift in Sources */, D62D2424217ABF3F005076CC /* NSUserActivity+Extensions.swift in Sources */, @@ -1691,6 +1694,7 @@ D6DD353F22F502EC00A9563A /* Preferences+Notification.swift in Sources */, D63661C02381C144004B9E16 /* PreferencesNavigationController.swift in Sources */, D627944A23A6AD6100D38C68 /* BookmarksTableViewController.swift in Sources */, + D6945C3A23AC75E2005C403C /* FindInstanceViewController.swift in Sources */, D6AEBB4523216AF800E5038B /* FollowAccountActivity.swift in Sources */, D6538945214D6D7500E3CEFC /* TableViewSwipeActionProvider.swift in Sources */, D6E0DC8E216EDF1E00369478 /* Previewing.swift in Sources */, diff --git a/Tusker/Extensions/Timline+UI.swift b/Tusker/Extensions/Timline+UI.swift index a0364187..b34c2f84 100644 --- a/Tusker/Extensions/Timline+UI.swift +++ b/Tusker/Extensions/Timline+UI.swift @@ -16,6 +16,8 @@ extension Timeline { return "Home" case let .public(local): return local ? "Local" : "Federated" + case let .instance(instance): + return instance.host! case let .tag(hashtag): return "#\(hashtag)" case .list: @@ -35,6 +37,8 @@ extension Timeline { } else { return UIImage(systemName: "globe") } + case .instance(_): + return UIImage(systemName: "globe") default: return nil } diff --git a/Tusker/SavedHashtagsManager.swift b/Tusker/SavedHashtagsManager.swift index ea8a97be..cf91aa9e 100644 --- a/Tusker/SavedHashtagsManager.swift +++ b/Tusker/SavedHashtagsManager.swift @@ -16,7 +16,7 @@ class SavedHashtagsManager: Codable { private static var archiveURL = SavedHashtagsManager.documentsDirectory.appendingPathComponent("saved_hashtags").appendingPathExtension("plist") static func save() { - DispatchQueue.global(qos: .userInitiated).async { + DispatchQueue.global(qos: .utility).async { let encoder = PropertyListEncoder() let data = try? encoder.encode(shared) try? data?.write(to: archiveURL, options: .noFileProtection) @@ -36,7 +36,7 @@ class SavedHashtagsManager: Codable { private var savedHashtags: [Hashtag] = [] var sorted: [Hashtag] { - return savedHashtags.sorted(by: { $0.name > $1.name }) + return savedHashtags.sorted(by: { $0.name < $1.name }) } func isSaved(_ hashtag: Hashtag) -> Bool { diff --git a/Tusker/SavedInstancesManager.swift b/Tusker/SavedInstancesManager.swift new file mode 100644 index 00000000..7bc466c1 --- /dev/null +++ b/Tusker/SavedInstancesManager.swift @@ -0,0 +1,61 @@ +// +// SavedInstancesManager.swift +// Tusker +// +// Created by Shadowfacts on 12/19/19. +// Copyright © 2019 Shadowfacts. All rights reserved. +// + +import Foundation + +class SavedInstanceManager: Codable { + private(set) static var shared: SavedInstanceManager = load() + + private static var documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first! + private static var archiveURL = SavedInstanceManager.documentsDirectory.appendingPathComponent("saved_instances").appendingPathExtension("plist") + + static func save() { + DispatchQueue.global(qos: .utility).async { + let encoder = PropertyListEncoder() + let data = try? encoder.encode(shared) + try? data?.write(to: archiveURL, options: .noFileProtection) + } + } + + static func load() -> SavedInstanceManager { + let decoder = PropertyListDecoder() + if let data = try? Data(contentsOf: archiveURL), + let savedInstanceManager = try? decoder.decode(Self.self, from: data) { + return savedInstanceManager + } + return SavedInstanceManager() + } + + private init() {} + + private(set) var savedInstances: [URL] = [] + + func isSaved(_ url: URL) -> Bool { + return savedInstances.contains(url) + } + + func add(_ url: URL) { + if isSaved(url) { + return + } + savedInstances.append(url) + SavedInstanceManager.save() + NotificationCenter.default.post(name: .savedInstancesChanged, object: nil) + } + + func remove(_ url: URL) { + guard isSaved(url) else { return } + savedInstances.removeAll(where: { $0 == url }) + SavedInstanceManager.save() + NotificationCenter.default.post(name: .savedInstancesChanged, object: nil) + } +} + +extension Notification.Name { + static let savedInstancesChanged = Notification.Name("savedInstancesChanged") +} diff --git a/Tusker/Screens/Explore/ExploreViewController.swift b/Tusker/Screens/Explore/ExploreViewController.swift index 4b3488bf..02d487e7 100644 --- a/Tusker/Screens/Explore/ExploreViewController.swift +++ b/Tusker/Screens/Explore/ExploreViewController.swift @@ -61,6 +61,16 @@ class ExploreViewController: EnhancedTableViewController { cell.imageView!.image = UIImage(systemName: "plus") cell.textLabel!.text = NSLocalizedString("Save Hashtag...", comment: "save hashtag nav item title") cell.accessoryType = .none + + case let .savedInstance(url): + cell.imageView!.image = UIImage(systemName: "globe") + cell.textLabel!.text = url.host! + cell.accessoryType = .disclosureIndicator + + case .findInstance: + cell.imageView!.image = UIImage(systemName: "magnifyingglass") + cell.textLabel!.text = NSLocalizedString("Find An Instance...", comment: "find instance nav item title") + cell.accessoryType = .none } return cell @@ -68,10 +78,11 @@ class ExploreViewController: EnhancedTableViewController { dataSource.exploreController = self var snapshot = NSDiffableDataSourceSnapshot() - snapshot.appendSections([.bookmarks, .lists, .savedHashtags]) + snapshot.appendSections([.bookmarks, .lists, .savedHashtags, .savedInstances]) snapshot.appendItems([.bookmarks], toSection: .bookmarks) snapshot.appendItems([.addList], toSection: .lists) snapshot.appendItems(SavedHashtagsManager.shared.sorted.map { .savedHashtag($0) } + [.addSavedHashtag], toSection: .savedHashtags) + snapshot.appendItems(SavedInstanceManager.shared.savedInstances.map { .savedInstance($0) } + [.findInstance], toSection: .savedInstances) // the initial, static items should not be displayed with an animation UIView.performWithoutAnimation { dataSource.apply(snapshot) @@ -89,6 +100,7 @@ class ExploreViewController: EnhancedTableViewController { navigationItem.hidesSearchBarWhenScrolling = false NotificationCenter.default.addObserver(self, selector: #selector(savedHashtagsChanged), name: .savedHashtagsChanged, object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(savedInstancesChanged), name: .savedInstancesChanged, object: nil) reloadLists() } @@ -117,6 +129,13 @@ class ExploreViewController: EnhancedTableViewController { dataSource.apply(snapshot) } + @objc func savedInstancesChanged() { + var snapshot = dataSource.snapshot() + snapshot.deleteItems(snapshot.itemIdentifiers(inSection: .savedInstances)) + snapshot.appendItems(SavedInstanceManager.shared.savedInstances.map { .savedInstance($0) } + [.findInstance], toSection: .savedInstances) + dataSource.apply(snapshot) + } + func deleteList(_ list: List) { let title = String(format: NSLocalizedString("Are you sure want to delete the '%@' list?", comment: "delete list alert title"), list.title) let alert = UIAlertController(title: title, message: nil, preferredStyle: .alert) @@ -143,6 +162,10 @@ class ExploreViewController: EnhancedTableViewController { SavedHashtagsManager.shared.remove(hashtag) } + func removeSavedInstance(_ instanceURL: URL) { + SavedInstanceManager.shared.remove(instanceURL) + } + // MARK: - Table view delegate override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { @@ -188,6 +211,16 @@ class ExploreViewController: EnhancedTableViewController { tableView.selectRow(at: nil, animated: true, scrollPosition: .none) let navController = UINavigationController(rootViewController: AddSavedHashtagViewController()) present(navController, animated: true) + + case let .savedInstance(url): + show(InstanceTimelineViewController(for: url), sender: nil) + + case .findInstance: + tableView.selectRow(at: nil, animated: true, scrollPosition: .none) + let findController = FindInstanceViewController() + findController.instanceTimelineDelegate = self + let navController = UINavigationController(rootViewController: findController) + present(navController, animated: true) } } @@ -202,6 +235,7 @@ extension ExploreViewController { case bookmarks case lists case savedHashtags + case savedInstances } enum Item: Hashable { case bookmarks @@ -209,6 +243,8 @@ extension ExploreViewController { case addList case savedHashtag(Hashtag) case addSavedHashtag + case savedInstance(URL) + case findInstance static func == (lhs: ExploreViewController.Item, rhs: ExploreViewController.Item) -> Bool { switch (lhs, rhs) { @@ -222,6 +258,10 @@ extension ExploreViewController { return a == b case (.addSavedHashtag, .addSavedHashtag): return true + case let (.savedInstance(a), .savedInstance(b)): + return a == b + case (.findInstance, .findInstance): + return true default: return false } @@ -240,6 +280,11 @@ extension ExploreViewController { hasher.combine(hashtag.name) case .addSavedHashtag: hasher.combine("addSavedHashtag") + case let .savedInstance(url): + hasher.combine("savedInstance") + hasher.combine(url) + case .findInstance: + hasher.combine("findInstance") } } } @@ -254,6 +299,8 @@ extension ExploreViewController { return NSLocalizedString("Lists", comment: "explore lists section title") case 2: return NSLocalizedString("Saved Hashtags", comment: "explore saved hashtags section title") + case 3: + return NSLocalizedString("Instance Timelines", comment: "explore instance timelines section title") default: return nil } @@ -265,6 +312,8 @@ extension ExploreViewController { return true case .savedHashtag(_): return true + case .savedInstance(_): + return true default: return false } @@ -281,6 +330,8 @@ extension ExploreViewController { exploreController.deleteList(list) case let .savedHashtag(hashtag): exploreController.removeSavedHashtag(hashtag) + case let .savedInstance(url): + exploreController.removeSavedInstance(url) default: return } @@ -289,3 +340,15 @@ extension ExploreViewController { } } + +extension ExploreViewController: InstanceTimelineViewControllerDelegate { + func didSaveInstance(url: URL) { + dismiss(animated: true) { + self.show(InstanceTimelineViewController(for: url), sender: nil) + } + } + + func didUnsaveInstance(url: URL) { + dismiss(animated: true) + } +} diff --git a/Tusker/Screens/FindInstanceViewController.swift b/Tusker/Screens/FindInstanceViewController.swift new file mode 100644 index 00000000..70a3efbf --- /dev/null +++ b/Tusker/Screens/FindInstanceViewController.swift @@ -0,0 +1,39 @@ +// +// FindInstanceViewController.swift +// Tusker +// +// Created by Shadowfacts on 12/19/19. +// Copyright © 2019 Shadowfacts. All rights reserved. +// + +import UIKit + +class FindInstanceViewController: InstanceSelectorTableViewController { + + var instanceTimelineDelegate: InstanceTimelineViewControllerDelegate? + + override func viewDidLoad() { + super.viewDidLoad() + + delegate = self + + searchController.hidesNavigationBarDuringPresentation = false + + navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(cancelButtonPressed)) + } + + // MARK: - Interaction + + @objc func cancelButtonPressed() { + dismiss(animated: true) + } + +} + +extension FindInstanceViewController: InstanceSelectorTableViewControllerDelegate { + func didSelectInstance(url: URL) { + let instanceTimelineController = InstanceTimelineViewController(for: url) + instanceTimelineController.delegate = instanceTimelineDelegate + show(instanceTimelineController, sender: self) + } +} diff --git a/Tusker/Screens/Hashtag Timeline/HashtagTimelineViewController.swift b/Tusker/Screens/Timeline/HashtagTimelineViewController.swift similarity index 100% rename from Tusker/Screens/Hashtag Timeline/HashtagTimelineViewController.swift rename to Tusker/Screens/Timeline/HashtagTimelineViewController.swift diff --git a/Tusker/Screens/Timeline/InstanceTimelineViewController.swift b/Tusker/Screens/Timeline/InstanceTimelineViewController.swift new file mode 100644 index 00000000..405c998f --- /dev/null +++ b/Tusker/Screens/Timeline/InstanceTimelineViewController.swift @@ -0,0 +1,71 @@ +// +// InstanceTimelineViewController.swift +// Tusker +// +// Created by Shadowfacts on 12/19/19. +// Copyright © 2019 Shadowfacts. All rights reserved. +// + +import UIKit + +protocol InstanceTimelineViewControllerDelegate { + func didSaveInstance(url: URL) + func didUnsaveInstance(url: URL) +} + +class InstanceTimelineViewController: TimelineTableViewController { + + var delegate: InstanceTimelineViewControllerDelegate? + + let instanceURL: URL + + var toggleSaveButton: UIBarButtonItem! + var toggleSaveButtonTitle: String { + if SavedInstanceManager.shared.isSaved(instanceURL) { + return NSLocalizedString("Unsave", comment: "unsave instance button") + } else { + return NSLocalizedString("Save", comment: "save instance button") + } + } + + init(for url: URL) { + self.instanceURL = url + + super.init(for: .instance(instanceURL: url)) + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func viewDidLoad() { + super.viewDidLoad() + + toggleSaveButton = UIBarButtonItem(title: toggleSaveButtonTitle, style: .plain, target: self, action: #selector(toggleSaveButtonPressed)) + navigationItem.rightBarButtonItem = toggleSaveButton + + NotificationCenter.default.addObserver(self, selector: #selector(savedInstancesChanged), name: .savedInstancesChanged, object: nil) + } + + @objc func savedInstancesChanged() { + toggleSaveButton.title = toggleSaveButtonTitle + } + + // MARK: - Table view delegate + + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + // no-op, we don't currently support viewing whole conversations from other instances + } + + // MARK: - Interaction + @objc func toggleSaveButtonPressed() { + if SavedInstanceManager.shared.isSaved(instanceURL) { + SavedInstanceManager.shared.remove(instanceURL) + delegate?.didUnsaveInstance(url: instanceURL) + } else { + SavedInstanceManager.shared.add(instanceURL) + delegate?.didSaveInstance(url: instanceURL) + } + } + +} diff --git a/Tusker/Shortcuts/UserActivityManager.swift b/Tusker/Shortcuts/UserActivityManager.swift index 4b9c09dc..b53373af 100644 --- a/Tusker/Shortcuts/UserActivityManager.swift +++ b/Tusker/Shortcuts/UserActivityManager.swift @@ -100,6 +100,9 @@ class UserActivityManager { case .public(local: false): activity.title = NSLocalizedString("Show Federated Timeline", comment: "federated timeline shortcut title") activity.suggestedInvocationPhrase = NSLocalizedString("Show my federated timeline", comment: "federated timeline invocation phrase") + case let .instance(instance): + activity.title = String(format: NSLocalizedString("Show %@", comment: "show instance timeline shortcut title"), instance.host!) + activity.suggestedInvocationPhrase = String(format: NSLocalizedString("Show the instance %@", comment: "instance timeline shortcut invocation phrase"), instance.host!) case let .tag(hashtag): activity.title = String(format: NSLocalizedString("Show #%@", comment: "show hashtag shortcut title"), hashtag) activity.suggestedInvocationPhrase = String(format: NSLocalizedString("Show the %@ hashtag", comment: "hashtag shortcut invocation phrase"), hashtag)