// // TuskerNavigationDelegate.swift // Tusker // // Created by Shadowfacts on 9/30/18. // Copyright © 2018 Shadowfacts. All rights reserved. // import UIKit import SafariServices import Pachyderm protocol TuskerNavigationDelegate: class { var apiController: MastodonController { get } func show(_ vc: UIViewController) func selected(account accountID: String) func selected(mention: Mention) func selected(tag: Hashtag) func selected(url: URL) func selected(status statusID: String) func selected(status statusID: String, state: StatusState) func compose() func compose(mentioning: String?) func reply(to statusID: String) func reply(to statusID: String, mentioningAcct: String?) func loadingLargeImage(url: URL, cache: ImageCache, description: String?, animatingFrom sourceView: UIImageView) -> LoadingLargeImageViewController func showLoadingLargeImage(url: URL, cache: ImageCache, description: String?, animatingFrom sourceView: UIImageView) func gallery(attachments: [Attachment], sourceViews: [UIImageView?], startIndex: Int) -> GalleryViewController func showGallery(attachments: [Attachment], sourceViews: [UIImageView?], startIndex: Int) func showMoreOptions(forStatus statusID: String, sourceView: UIView?) func showMoreOptions(forAccount accountID: String, sourceView: UIView?) func showMoreOptions(forURL url: URL, sourceView: UIView?) func showFollowedByList(accountIDs: [String]) func statusActionAccountList(action: StatusActionAccountListTableViewController.ActionType, statusID: String, statusState state: StatusState, accountIDs: [String]?) -> StatusActionAccountListTableViewController } extension TuskerNavigationDelegate where Self: UIViewController { func show(_ vc: UIViewController) { if vc is LargeImageViewController || vc is GalleryViewController || vc is SFSafariViewController { present(vc, animated: true) } else { show(vc, sender: self) } } func selected(account accountID: String) { // don't open if the account is the same as the current one if let profileController = self as? ProfileTableViewController, profileController.accountID == accountID { return } show(ProfileTableViewController(accountID: accountID, mastodonController: apiController), sender: self) } func selected(mention: Mention) { show(ProfileTableViewController(accountID: mention.id, mastodonController: apiController), sender: self) } func selected(tag: Hashtag) { show(HashtagTimelineViewController(for: tag, mastodonController: apiController), sender: self) } func selected(url: URL) { func openSafari() { if Preferences.shared.useInAppSafari { let config = SFSafariViewController.Configuration() config.entersReaderIfAvailable = Preferences.shared.inAppSafariAutomaticReaderMode present(SFSafariViewController(url: url, configuration: config), animated: true) } else { UIApplication.shared.open(url, options: [:]) } } if (Preferences.shared.openLinksInApps) { UIApplication.shared.open(url, options: [.universalLinksOnly: true]) { (success) in if (!success) { openSafari() } } } else { openSafari() } } func selected(status statusID: String) { self.selected(status: statusID, state: .unknown) } func selected(status statusID: String, state: StatusState) { // todo: is this necessary? should the conversation main status cell prevent this // don't open if the conversation is the same as the current one if let conversationController = self as? ConversationTableViewController, conversationController.mainStatusID == statusID { return } show(ConversationTableViewController(for: statusID, state: state, mastodonController: apiController), sender: self) } // protocols can't have parameter defaults, so this stub is necessary to fulfill the protocol req func compose() { compose(mentioning: nil) } func compose(mentioning: String?) { let compose = ComposeViewController(mentioningAcct: mentioning, mastodonController: apiController) let vc = UINavigationController(rootViewController: compose) vc.presentationController?.delegate = compose present(vc, animated: true) } func reply(to statusID: String) { reply(to: statusID, mentioningAcct: nil) } func reply(to statusID: String, mentioningAcct: String?) { let compose = ComposeViewController(inReplyTo: statusID, mentioningAcct: mentioningAcct, mastodonController: apiController) let vc = UINavigationController(rootViewController: compose) vc.presentationController?.delegate = compose present(vc, animated: true) } func loadingLargeImage(url: URL, cache: ImageCache, description: String?, animatingFrom sourceView: UIImageView) -> LoadingLargeImageViewController { let vc = LoadingLargeImageViewController(url: url, cache: cache, imageDescription: description) vc.animationSourceView = sourceView vc.transitioningDelegate = self return vc } func showLoadingLargeImage(url: URL, cache: ImageCache, description: String?, animatingFrom sourceView: UIImageView) { present(loadingLargeImage(url: url, cache: cache, description: description, animatingFrom: sourceView), animated: true) } func gallery(attachments: [Attachment], sourceViews: [UIImageView?], startIndex: Int) -> GalleryViewController { let vc = GalleryViewController(attachments: attachments, sourceViews: sourceViews, startIndex: startIndex) vc.transitioningDelegate = self return vc } func showGallery(attachments: [Attachment], sourceViews: [UIImageView?], startIndex: Int) { present(gallery(attachments: attachments, sourceViews: sourceViews, startIndex: startIndex), animated: true) } private func moreOptions(forURL url: URL) -> UIActivityViewController { let customActivites: [UIActivity] = [ OpenInSafariActivity() ] let activityController = UIActivityViewController(activityItems: [url], applicationActivities: customActivites) activityController.completionWithItemsHandler = OpenInSafariActivity.completionHandler(viewController: self, url: url) return activityController } private func moreOptions(forStatus statusID: String) -> UIActivityViewController { guard let status = apiController.persistentContainer.status(for: statusID) else { fatalError("Missing cached status \(statusID)") } guard let url = status.url else { fatalError("Missing url for status \(statusID)") } // on iOS 14+, all these custom actions are in the context menu and don't need to be in the share sheet if #available(iOS 14.0, *) { return UIActivityViewController(activityItems: [url, StatusActivityItemSource(status)], applicationActivities: nil) } else { var customActivites: [UIActivity] = [ OpenInSafariActivity(), (status.bookmarked ?? false) ? UnbookmarkStatusActivity() : BookmarkStatusActivity(), status.muted ? UnmuteConversationActivity() : MuteConversationActivity(), ] if apiController.account != nil, status.account.id == apiController.account.id { let pinned = status.pinned ?? false customActivites.insert(pinned ? UnpinStatusActivity() : PinStatusActivity(), at: 1) } let activityController = UIActivityViewController(activityItems: [url, StatusActivityItemSource(status)], applicationActivities: customActivites) activityController.completionWithItemsHandler = OpenInSafariActivity.completionHandler(viewController: self, url: url) return activityController } } private func moreOptions(forAccount accountID: String) -> UIActivityViewController { guard let account = apiController.persistentContainer.account(for: accountID) else { fatalError("Missing cached account \(accountID)") } if #available(iOS 14.0, *) { return UIActivityViewController(activityItems: [account.url, AccountActivityItemSource(account)], applicationActivities: nil) } else { let customActivities: [UIActivity] = [ OpenInSafariActivity(), ] let activityController = UIActivityViewController(activityItems: [account.url, AccountActivityItemSource(account)], applicationActivities: customActivities) activityController.completionWithItemsHandler = OpenInSafariActivity.completionHandler(viewController: self, url: account.url) return activityController } } func showMoreOptions(forStatus statusID: String, sourceView: UIView?) { let vc = moreOptions(forStatus: statusID) vc.popoverPresentationController?.sourceView = sourceView present(vc, animated: true) } func showMoreOptions(forURL url: URL, sourceView: UIView?) { let vc = moreOptions(forURL: url) vc.popoverPresentationController?.sourceView = sourceView present(vc, animated: true) } func showMoreOptions(forAccount accountID: String, sourceView: UIView?) { let vc = moreOptions(forAccount: accountID) vc.popoverPresentationController?.sourceView = sourceView present(vc, animated: true) } func showFollowedByList(accountIDs: [String]) { let vc = AccountListTableViewController(accountIDs: accountIDs, mastodonController: apiController) vc.title = NSLocalizedString("Followed By", comment: "followed by accounts list title") show(vc, sender: self) } func statusActionAccountList(action: StatusActionAccountListTableViewController.ActionType, statusID: String, statusState state: StatusState, accountIDs: [String]?) -> StatusActionAccountListTableViewController { return StatusActionAccountListTableViewController(actionType: action, statusID: statusID, statusState: state, accountIDs: accountIDs, mastodonController: apiController) } }