diff --git a/Pachyderm/Model/Timeline.swift b/Pachyderm/Model/Timeline.swift index ba4eb0472e..96d7a85a5e 100644 --- a/Pachyderm/Model/Timeline.swift +++ b/Pachyderm/Model/Timeline.swift @@ -41,3 +41,50 @@ extension Timeline { return request } } + +extension Timeline: Codable { + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + let type = try container.decode(String.self, forKey: .type) + switch type { + case "home": + self = .home + case "public": + self = .public(local: try container.decode(Bool.self, forKey: .local)) + case "tag": + self = .tag(hashtag: try container.decode(String.self, forKey: .hashtag)) + case "list": + self = .list(id: try container.decode(String.self, forKey: .listID)) + case "direct": + self = .direct + default: + throw DecodingError.dataCorruptedError(forKey: CodingKeys.type, in: container, debugDescription: "Timeline type must be one of 'home', 'local', 'tag', 'list', or 'direct'") + } + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + switch self { + case .home: + try container.encode("home", forKey: .type) + case let .public(local): + try container.encode("public", forKey: .type) + try container.encode(local, forKey: .local) + case let .tag(hashtag): + try container.encode("tag", forKey: .type) + try container.encode(hashtag, forKey: .hashtag) + case let .list(id): + try container.encode("list", forKey: .type) + try container.encode(id, forKey: .listID) + case .direct: + try container.encode("direct", forKey: .type) + } + } + + enum CodingKeys: String, CodingKey { + case type + case local + case hashtag + case listID + } +} diff --git a/Tusker/Info.plist b/Tusker/Info.plist index b5eaa2daaf..f1dde38162 100644 --- a/Tusker/Info.plist +++ b/Tusker/Info.plist @@ -4,6 +4,7 @@ NSUserActivityTypes + $(PRODUCT_BUNDLE_IDENTIFIER).activity.show-timeline $(PRODUCT_BUNDLE_IDENTIFIER).activity.check-notifications $(PRODUCT_BUNDLE_IDENTIFIER).activity.new-post diff --git a/Tusker/Screens/Timeline/TimelineTableViewController.swift b/Tusker/Screens/Timeline/TimelineTableViewController.swift index 1c4822781f..83b2db5fab 100644 --- a/Tusker/Screens/Timeline/TimelineTableViewController.swift +++ b/Tusker/Screens/Timeline/TimelineTableViewController.swift @@ -44,9 +44,11 @@ class TimelineTableViewController: EnhancedTableViewController { title = timeline.title tabBarItem.image = timeline.tabBarImage - + self.refreshControl = UIRefreshControl() refreshControl!.addTarget(self, action: #selector(refreshStatuses(_:)), for: .valueChanged) + + userActivity = UserActivityManager.showTimelineActivity(timeline: timeline) } required init?(coder aDecoder: NSCoder) { diff --git a/Tusker/Shortcuts/UserActivityManager.swift b/Tusker/Shortcuts/UserActivityManager.swift index c3141a1876..d1949408a7 100644 --- a/Tusker/Shortcuts/UserActivityManager.swift +++ b/Tusker/Shortcuts/UserActivityManager.swift @@ -12,6 +12,9 @@ import Pachyderm class UserActivityManager { // MARK: - Utils + private static let encoder = PropertyListEncoder() + private static let decoder = PropertyListDecoder() + private static func present(_ vc: UIViewController, animated: Bool = true) { UIApplication.shared.delegate?.window??.rootViewController?.present(vc, animated: animated) } @@ -52,4 +55,66 @@ class UserActivityManager { tabBarController.selectedIndex = 1 } + // MARK: - Show Timeline + static func showTimelineActivity(timeline: Timeline) -> NSUserActivity? { + guard let timelineData = try? encoder.encode(timeline) else { return nil } + + let activity = NSUserActivity(type: .showTimeline) + activity.isEligibleForPrediction = true + activity.userInfo = ["timelineData": timelineData] + switch timeline { + case .home: + activity.title = NSLocalizedString("Show Home Timeline", comment: "home timeline shortcut title") + activity.suggestedInvocationPhrase = NSLocalizedString("Show my home timeline", comment: "home timeline shortcut invocation phrase") + case .public(local: true): + activity.title = NSLocalizedString("Show Local Timeline", comment: "local timeline shortcut title") + activity.suggestedInvocationPhrase = NSLocalizedString("Show my local timeline", comment: "local timeline shortcut invocation phrase") + 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 .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) + case .list: + // todo: add title to list + activity.title = NSLocalizedString("Show List", comment: "list timeline shortcut title") + activity.suggestedInvocationPhrase = NSLocalizedString("Show my list", comment: "list timeline invocation phrase") + case .direct: + activity.title = NSLocalizedString("Show Direct Messages", comment: "direct message timeline shortcut title") + activity.suggestedInvocationPhrase = NSLocalizedString("Show my direct messages", comment: "direct message timeline invocation phrase") + } + return activity + } + + static func handleShowTimeline(activity: NSUserActivity) { + guard let timelineData = activity.userInfo?["timelineData"] as? Data, + let timeline = try? decoder.decode(Timeline.self, from: timelineData) else { + return + } + + let tabBarController = UIApplication.shared.keyWindow!.rootViewController! as! UITabBarController + tabBarController.selectedIndex = 0 + let navigationController = tabBarController.viewControllers![0] as! UINavigationController + switch timeline { + case .home, .public(true), .public(false): + navigationController.popToRootViewController(animated: false) + let rootController = navigationController.viewControllers.first! as! SegmentedPageViewController + let index: Int + switch timeline { + case .home: + index = 0 + case .public(false): + index = 1 + case .public(true): + index = 2 + default: + fatalError() + } + rootController.segmentedControl.selectedSegmentIndex = index + rootController.selectPage(at: index, animated: false) + default: + navigationController.pushViewController(TimelineTableViewController(for: timeline), animated: false) + } + } + } diff --git a/Tusker/Shortcuts/UserActivityType.swift b/Tusker/Shortcuts/UserActivityType.swift index fb5216f87f..0b83a10f65 100644 --- a/Tusker/Shortcuts/UserActivityType.swift +++ b/Tusker/Shortcuts/UserActivityType.swift @@ -9,8 +9,9 @@ import Foundation enum UserActivityType: String { - case newPost = "net.shadowfacts.tusker.activity.new-post" - case checkNotifications = "net.shadowfacts.tusker.activity.check-notifications" + case newPost = "net.shadowfacts.Tusker.activity.new-post" + case checkNotifications = "net.shadowfacts.Tusker.activity.check-notifications" + case showTimeline = "net.shadowfacts.Tusker.activity.show-timeline" } extension UserActivityType { @@ -20,6 +21,8 @@ extension UserActivityType { return UserActivityManager.handleNewPost case .checkNotifications: return UserActivityManager.handleCheckNotifications + case .showTimeline: + return UserActivityManager.handleShowTimeline } } }