// // TuskerNavigationDelegate.swift // Tusker // // Created by Shadowfacts on 9/30/18. // Copyright © 2018 Shadowfacts. All rights reserved. // import UIKit import SafariServices import Pachyderm protocol TuskerNavigationDelegate { 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 largeImage(_ image: UIImage, description: String?, sourceView: UIImageView) -> LargeImageViewController func largeImage(gifData: Data, description: String?, sourceView: UIImageView) -> LargeImageViewController func showLargeImage(_ image: UIImage, description: String?, animatingFrom sourceView: UIImageView) func showLargeImage(gifData: Data, 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) func showMoreOptions(forAccount accountID: String) func showMoreOptions(forURL url: URL) 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) { 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? = nil) { 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) { let compose = ComposeViewController(inReplyTo: statusID, mastodonController: apiController) let vc = UINavigationController(rootViewController: compose) vc.presentationController?.delegate = compose present(vc, animated: true) } private func sourceViewInfo(_ sourceView: UIImageView?) -> LargeImageViewController.SourceInfo? { guard let sourceView = sourceView else { return nil } var sourceFrame = sourceView.convert(sourceView.bounds, to: view) if let scrollView = view as? UIScrollView { let scale = scrollView.zoomScale let width = sourceFrame.width * scale let height = sourceFrame.height * scale let x = sourceFrame.minX * scale - scrollView.contentOffset.x + scrollView.frame.minX let y = sourceFrame.minY * scale - scrollView.contentOffset.y + scrollView.frame.minY sourceFrame = CGRect(x: x, y: y, width: width, height: height) } return (image: sourceView.image!, frame: sourceFrame, cornerRadius: sourceView.layer.cornerRadius) } func largeImage(_ image: UIImage, description: String?, sourceView: UIImageView) -> LargeImageViewController { let vc = LargeImageViewController(image: image, description: description, sourceInfo: sourceViewInfo(sourceView)) vc.transitioningDelegate = self return vc } func largeImage(gifData: Data, description: String?, sourceView: UIImageView) -> LargeImageViewController { let vc = LargeImageViewController(image: UIImage(data: gifData)!, description: description, sourceInfo: sourceViewInfo(sourceView)) vc.transitioningDelegate = self vc.gifData = gifData return vc } func showLargeImage(_ image: UIImage, description: String?, animatingFrom sourceView: UIImageView) { present(largeImage(image, description: description, sourceView: sourceView), animated: true) } func showLargeImage(gifData: Data, description: String?, animatingFrom sourceView: UIImageView) { present(largeImage(gifData: gifData, description: description, sourceView: sourceView), animated: true) } func gallery(attachments: [Attachment], sourceViews: [UIImageView?], startIndex: Int) -> GalleryViewController { let sourcesInfo = sourceViews.map(sourceViewInfo) let vc = GalleryViewController(attachments: attachments, sourcesInfo: sourcesInfo, 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) -> UIViewController { 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) -> UIViewController { guard let status = apiController.cache.status(for: statusID) else { fatalError("Missing cached status \(statusID)") } guard let url = status.url else { fatalError("Missing url for status \(statusID)") } var customActivites: [UIActivity] = [OpenInSafariActivity()] if let bookmarked = status.bookmarked { customActivites.insert(bookmarked ? UnbookmarkStatusActivity() : BookmarkStatusActivity(), at: 0) } if status.account == apiController.account, let pinned = status.pinned { customActivites.insert(pinned ? UnpinStatusActivity() : PinStatusActivity(), at: 1) } let activityController = UIActivityViewController(activityItems: [url, status], applicationActivities: customActivites) activityController.completionWithItemsHandler = OpenInSafariActivity.completionHandler(viewController: self, url: url) return activityController } private func moreOptions(forAccount accountID: String) -> UIViewController { guard let account = apiController.cache.account(for: accountID) else { fatalError("Missing cached account \(accountID)") } return moreOptions(forURL: account.url) } func showMoreOptions(forStatus statusID: String) { present(moreOptions(forStatus: statusID), animated: true) } func showMoreOptions(forURL url: URL) { present(moreOptions(forURL: url), animated: true) } func showMoreOptions(forAccount accountID: String) { present(moreOptions(forAccount: accountID), 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) } }