Tusker/Tusker/Shortcuts/UserActivityManager.swift

314 lines
14 KiB
Swift

//
// UserActivityManager.swift
// Tusker
//
// Created by Shadowfacts on 10/19/18.
// Copyright © 2018 Shadowfacts. All rights reserved.
//
import UIKit
import Intents
import Pachyderm
import OSLog
private let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "UserActivityManager")
class UserActivityManager {
private let scene: UIWindowScene
init(scene: UIWindowScene) {
self.scene = scene
}
// MARK: - Utils
private static let encoder = PropertyListEncoder()
private static let decoder = PropertyListDecoder()
private var mastodonController: MastodonController {
scene.session.mastodonController!
}
private func getMainViewController() -> TuskerRootViewController {
let window = scene.windows.first { $0.isKeyWindow } ?? scene.windows.first!
return window.rootViewController as! TuskerRootViewController
}
private func present(_ vc: UIViewController, animated: Bool = true) {
getMainViewController().present(vc, animated: animated)
}
static func getAccount(from activity: NSUserActivity) -> LocalData.UserAccountInfo? {
guard let id = activity.userInfo?["accountID"] as? String else {
return nil
}
return LocalData.shared.getAccount(id: id)
}
// MARK: - Main Scene
static func mainSceneActivity(accountID: String?) -> NSUserActivity {
let activity = NSUserActivity(type: .mainScene)
if let accountID {
activity.userInfo = [
"accountID": accountID,
]
}
return activity
}
// MARK: - New Post
static func newPostActivity(mentioning: Account? = nil, accountID: String) -> NSUserActivity {
// todo: update to use managed objects
let activity = NSUserActivity(type: .newPost)
activity.isEligibleForPrediction = true
activity.userInfo = [
"accountID": accountID,
]
if let mentioning = mentioning {
activity.userInfo!["mentioning"] = mentioning.acct
activity.title = "Send a message to \(mentioning.displayName)"
activity.suggestedInvocationPhrase = "Send a message to \(mentioning.displayName)"
} else {
activity.title = "New Post"
activity.suggestedInvocationPhrase = "Post in Tusker"
}
return activity
}
func handleNewPost(activity: NSUserActivity) {
// TODO: check not currently showing compose screen
let mentioning = activity.userInfo?["mentioning"] as? String
let draft = mastodonController.createDraft(mentioningAcct: mentioning)
// todo: this shouldn't use self.mastodonController, it should get the right one based on the userInfo accountID
let composeVC = ComposeHostingController(draft: draft, mastodonController: mastodonController)
present(UINavigationController(rootViewController: composeVC))
}
static func editDraftActivity(id: UUID, accountID: String) -> NSUserActivity {
let activity = NSUserActivity(type: .newPost)
activity.userInfo = [
"accountID": accountID,
"draftID": id.uuidString,
]
return activity
}
static func addDuckedDraft(to activity: NSUserActivity?, draft: Draft) -> NSUserActivity {
if let activity {
activity.addUserInfoEntries(from: [
"duckedDraftID": draft.id.uuidString
])
return activity
} else {
return editDraftActivity(id: draft.id, accountID: draft.accountID)
}
}
static func addEditedDraft(to activity: NSUserActivity?, draft: Draft) -> NSUserActivity {
if let activity {
activity.addUserInfoEntries(from: [
"editedDraftID": draft.id.uuidString
])
return activity
} else {
return editDraftActivity(id: draft.id, accountID: draft.accountID)
}
}
static func getDraft(from activity: NSUserActivity) -> Draft? {
let idStr: String?
if activity.activityType == UserActivityType.newPost.rawValue {
idStr = activity.userInfo?["draftID"] as? String
} else {
idStr = activity.userInfo?["duckedDraftID"] as? String ?? activity.userInfo?["editedDraftID"] as? String
}
guard let idStr,
let uuid = UUID(uuidString: idStr) else {
return nil
}
return DraftsManager.shared.getBy(id: uuid)
}
// MARK: - Check Notifications
static func checkNotificationsActivity(mode: NotificationsMode = .allNotifications) -> NSUserActivity {
let activity = NSUserActivity(type: .checkNotifications)
activity.isEligibleForPrediction = true
activity.addUserInfoEntries(from: [
"notificationsMode": mode.rawValue
])
switch mode {
case .allNotifications:
activity.title = NSLocalizedString("Check Notifications", comment: "check notifications shortcut title")
activity.suggestedInvocationPhrase = NSLocalizedString("Check my Tusker notifications", comment: "check notifications shortcut invocation phrase")
case .mentionsOnly:
activity.title = NSLocalizedString("Check Mentions", comment: "check mentions shortcut title")
activity.suggestedInvocationPhrase = NSLocalizedString("Check my mentions", comment: "check mentions shortcut invocation phrase")
}
return activity
}
func handleCheckNotifications(activity: NSUserActivity) {
let mainViewController = getMainViewController()
mainViewController.select(tab: .notifications)
if let navigationController = mainViewController.getTabController(tab: .notifications) as? UINavigationController,
let notificationsPageController = navigationController.viewControllers.first as? NotificationsPageViewController {
navigationController.popToRootViewController(animated: false)
notificationsPageController.loadViewIfNeeded()
notificationsPageController.selectMode(Self.getNotificationsMode(from: activity) ?? Preferences.shared.defaultNotificationsMode)
}
}
static func getNotificationsMode(from activity: NSUserActivity) -> NotificationsMode? {
guard let str = activity.userInfo?["notificationsMode"] as? String else {
return nil
}
return NotificationsMode(rawValue: str)
}
// MARK: - Show Timeline
static func showTimelineActivity(timeline: Timeline, accountID: String) -> NSUserActivity? {
guard let timelineData = try? encoder.encode(timeline) else { return nil }
let activity = NSUserActivity(type: .showTimeline)
activity.isEligibleForPrediction = true
activity.userInfo = [
"timelineData": timelineData,
"accountID": accountID,
]
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 getTimeline(from activity: NSUserActivity) -> Timeline? {
guard activity.activityType == UserActivityType.showTimeline.rawValue,
let data = activity.userInfo?["timelineData"] as? Data else {
return nil
}
return try? UserActivityManager.decoder.decode(Timeline.self, from: data)
}
func handleShowTimeline(activity: NSUserActivity) {
guard let timeline = Self.getTimeline(from: activity) else { return }
let mainViewController = getMainViewController()
mainViewController.select(tab: .timelines)
guard let navigationController = mainViewController.getTabController(tab: .timelines) as? UINavigationController else {
return
}
if let pinned = PinnedTimeline(timeline: timeline),
mastodonController.accountPreferences.pinnedTimelines.contains(pinned) {
navigationController.popToRootViewController(animated: false)
let rootController = navigationController.viewControllers.first! as! TimelinesPageViewController
rootController.selectTimeline(pinned, animated: false)
} else {
let timeline = TimelineViewController(for: timeline, mastodonController: mastodonController)
navigationController.pushViewController(timeline, animated: false)
}
}
// MARK: - Show Conversation
static func showConversationActivity(mainStatusID: String, accountID: String, isEligibleForPrediction: Bool = false) -> NSUserActivity {
let activity = NSUserActivity(type: .showConversation)
activity.userInfo = [
"mainStatusID": mainStatusID,
"accountID": accountID,
]
activity.isEligibleForPrediction = isEligibleForPrediction
return activity
}
static func getConversationStatus(from activity: NSUserActivity) -> String? {
return activity.userInfo?["mainStatusID"] as? String
}
// MARK: - Explore
static func searchActivity() -> NSUserActivity {
let activity = NSUserActivity(type: .search)
activity.isEligibleForPrediction = true
activity.title = NSLocalizedString("Search", comment: "search shortcut title")
activity.suggestedInvocationPhrase = NSLocalizedString("Search the fediverse", comment: "search shortcut invocation phrase")
return activity
}
func handleSearch(activity: NSUserActivity) {
let mainViewController = getMainViewController()
mainViewController.select(tab: .explore)
if let navigationController = mainViewController.getTabController(tab: .explore) as? UINavigationController,
let exploreController = navigationController.viewControllers.first as? ExploreViewController {
navigationController.popToRootViewController(animated: false)
exploreController.loadViewIfNeeded()
exploreController.searchController.isActive = true
exploreController.searchController.searchBar.becomeFirstResponder()
}
}
static func bookmarksActivity() -> NSUserActivity {
let activity = NSUserActivity(type: .bookmarks)
activity.isEligibleForPrediction = true
activity.title = NSLocalizedString("View Bookmarks", comment: "bookmarks shortcut title")
activity.suggestedInvocationPhrase = NSLocalizedString("Show my bookmarks in Tusker", comment: "bookmarks shortcut invocation phrase")
return activity
}
func handleBookmarks(activity: NSUserActivity) {
let mainViewController = getMainViewController()
mainViewController.select(tab: .explore)
if let navigationController = mainViewController.getTabController(tab: .explore) as? UINavigationController {
navigationController.popToRootViewController(animated: false)
navigationController.pushViewController(BookmarksTableViewController(mastodonController: mastodonController), animated: false)
}
}
// MARK: - My Profile
static func myProfileActivity() -> NSUserActivity {
let activity = NSUserActivity(type: .myProfile)
activity.isEligibleForPrediction = true
activity.title = NSLocalizedString("My Profile", comment: "my profile shortcut title")
activity.suggestedInvocationPhrase = NSLocalizedString("Show my Mastodon profile", comment: "my profile shortuct invocation phrase")
return activity
}
func handleMyProfile(activity: NSUserActivity) {
let mainViewController = getMainViewController()
mainViewController.select(tab: .myProfile)
}
// MARK: - Show Profile
static func showProfileActivity(id profileID: String, accountID: String) -> NSUserActivity {
let activity = NSUserActivity(type: .showProfile)
activity.userInfo = [
"profileID": profileID,
"accountID": accountID,
]
// todo: should this be eligible for prediction?
return activity
}
static func getProfile(from activity: NSUserActivity) -> String? {
return activity.userInfo?["profileID"] as? String
}
}