// // PreviewViewControllerProvider.swift // Tusker // // Created by Shadowfacts on 10/10/18. // Copyright © 2018 Shadowfacts. All rights reserved. // import UIKit import SafariServices import Pachyderm protocol MenuPreviewProvider: class { typealias PreviewProviders = (content: UIContextMenuContentPreviewProvider, actions: () -> [UIMenuElement]) var navigationDelegate: TuskerNavigationDelegate? { get } func getPreviewProviders(for location: CGPoint, sourceViewController: UIViewController) -> PreviewProviders? } protocol CustomPreviewPresenting { func presentFromPreview(presenter: UIViewController) } extension MenuPreviewProvider { private var mastodonController: MastodonController? { navigationDelegate?.apiController } // Default no-op implementation func getPreviewProviders(for location: CGPoint, sourceViewController: UIViewController) -> PreviewProviders? { return nil } func actionsForProfile(accountID: String, sourceView: UIView?) -> [UIMenuElement] { guard let mastodonController = mastodonController, let account = mastodonController.persistentContainer.account(for: accountID) else { return [] } guard mastodonController.loggedIn else { return [ openInSafariAction(url: account.url), createAction(identifier: "share", title: "Share...", systemImageName: "square.and.arrow.up", handler: { [weak self] (_) in guard let self = self else { return } self.navigationDelegate?.showMoreOptions(forAccount: accountID, sourceView: sourceView) }) ] } var actionsSection: [UIMenuElement] = [ createAction(identifier: "sendmessage", title: "Send Message", systemImageName: "envelope", handler: { [weak self] (_) in guard let self = self else { return } self.navigationDelegate?.compose(mentioningAcct: account.acct) }), ] if accountID != mastodonController.account.id { actionsSection.append(UIDeferredMenuElement({ (elementHandler) in guard let mastodonController = self.mastodonController else { elementHandler([]) return } let request = Client.getRelationships(accounts: [account.id]) // talk about callback hell :/ mastodonController.run(request) { [weak self] (response) in if let self = self, case let .success(results, _) = response, let relationship = results.first { let following = relationship.following DispatchQueue.main.async { elementHandler([ self.createAction(identifier: "follow", title: following ? "Unfollow" : "Follow", systemImageName: following ? "person.badge.minus" : "person.badge.plus", handler: { (_) in let request = (following ? Account.unfollow : Account.follow)(accountID) mastodonController.run(request) { (response) in switch response { case .failure(_): fatalError() case let .success(relationship, _): mastodonController.persistentContainer.addOrUpdate(relationship: relationship) } } }) ]) } } } })) } let shareSection = [ openInSafariAction(url: account.url), createAction(identifier: "share", title: "Share...", systemImageName: "square.and.arrow.up", handler: { [weak self] (_) in guard let self = self else { return } self.navigationDelegate?.showMoreOptions(forAccount: accountID, sourceView: sourceView) }) ] return [ UIMenu(title: "", image: nil, identifier: nil, options: [.displayInline], children: shareSection), UIMenu(title: "", image: nil, identifier: nil, options: [.displayInline], children: actionsSection), ] } func actionsForURL(_ url: URL, sourceView: UIView?) -> [UIAction] { return [ openInSafariAction(url: url), createAction(identifier: "share", title: "Share...", systemImageName: "square.and.arrow.up", handler: { [weak self] (_) in guard let self = self else { return } self.navigationDelegate?.showMoreOptions(forURL: url, sourceView: sourceView) }) ] } func actionsForHashtag(_ hashtag: Hashtag, sourceView: UIView?) -> [UIMenuElement] { let account = mastodonController!.accountInfo! let saved = SavedDataManager.shared.isSaved(hashtag: hashtag, for: account) let actionsSection = [ createAction(identifier: "save", title: saved ? "Unsave Hashtag" : "Save Hashtag", systemImageName: "number", handler: { (_) in if saved { SavedDataManager.shared.remove(hashtag: hashtag, for: account) } else { SavedDataManager.shared.add(hashtag: hashtag, for: account) } }) ] let shareSection = actionsForURL(hashtag.url, sourceView: sourceView) return [ UIMenu(title: "", image: nil, identifier: nil, options: [.displayInline], children: shareSection), UIMenu(title: "", image: nil, identifier: nil, options: [.displayInline], children: actionsSection), ] } func actionsForStatus(_ status: StatusMO, sourceView: UIView?) -> [UIMenuElement] { guard let mastodonController = mastodonController else { return [] } guard mastodonController.loggedIn else { return [ openInSafariAction(url: status.url!), createAction(identifier: "share", title: "Share...", systemImageName: "square.and.arrow.up", handler: { [weak self] (_) in guard let self = self else { return } self.navigationDelegate?.showMoreOptions(forStatus: status.id, sourceView: sourceView) }) ] } let bookmarked = status.bookmarked ?? false let muted = status.muted var actionsSection = [ createAction(identifier: "reply", title: "Reply", systemImageName: "arrowshape.turn.up.left", handler: { [weak self] (_) in guard let self = self else { return } self.navigationDelegate?.compose(inReplyToID: status.id) }), createAction(identifier: "bookmark", title: bookmarked ? "Unbookmark" : "Bookmark", systemImageName: bookmarked ? "bookmark.fill" : "bookmark", handler: { [weak self] (_) in guard let self = self else { return } let request = (bookmarked ? Status.unbookmark : Status.bookmark)(status.id) self.mastodonController?.run(request) { (response) in if case let .success(status, _) = response { self.mastodonController?.persistentContainer.addOrUpdate(status: status, incrementReferenceCount: false) } } }), createAction(identifier: "mute", title: muted ? "Unmute" : "Mute", systemImageName: muted ? "speaker" : "speaker.slash", handler: { [weak self] (_) in guard let self = self else { return } let request = (muted ? Status.unmuteConversation : Status.muteConversation)(status.id) self.mastodonController?.run(request) { (response) in if case let .success(status, _) = response { self.mastodonController?.persistentContainer.addOrUpdate(status: status, incrementReferenceCount: false) } } }) ] if mastodonController.account != nil && mastodonController.account.id == status.account.id { let pinned = status.pinned ?? false actionsSection.append(createAction(identifier: "", title: pinned ? "Unpin" : "Pin", systemImageName: pinned ? "pin.slash" : "pin", handler: { [weak self] (_) in guard let self = self else { return } let request = (pinned ? Status.unpin : Status.pin)(status.id) self.mastodonController?.run(request, completion: { [weak self] (response) in guard let self = self else { return } if case let .success(status, _) = response { self.mastodonController?.persistentContainer.addOrUpdate(status: status, incrementReferenceCount: false) } }) })) } var shareSection = [ openInSafariAction(url: status.url!), createAction(identifier: "share", title: "Share...", systemImageName: "square.and.arrow.up", handler: { [weak self] (_) in guard let self = self else { return } self.navigationDelegate?.showMoreOptions(forStatus: status.id, sourceView: sourceView) }), ] #if targetEnvironment(macCatalyst) shareSection.append(createAction(identifier: "new_window", title: "Open in New Window", systemImageName: "", handler: { (_) in guard let id = mastodonController.accountInfo?.id else { return } // todo: this should try to find an existing session UIApplication.shared.requestSceneSessionActivation(nil, userActivity: UserActivityManager.showConversationActivity(mainStatusID: status.id, accountID: id), options: nil, errorHandler: nil) })) #endif return [ UIMenu(title: "", image: nil, identifier: nil, options: [.displayInline], children: shareSection), UIMenu(title: "", image: nil, identifier: nil, options: [.displayInline], children: actionsSection), ] } private func createAction(identifier: String, title: String, systemImageName: String?, handler: @escaping UIActionHandler) -> UIAction { let image: UIImage? if let name = systemImageName { image = UIImage(systemName: name) } else { image = nil } return UIAction(title: title, image: image, identifier: UIAction.Identifier(identifier), discoverabilityTitle: nil, attributes: [], state: .off, handler: handler) } private func openInSafariAction(url: URL) -> UIAction { return createAction(identifier: "openinsafari", title: "Open in Safari", systemImageName: "safari", handler: { (_) in self.navigationDelegate?.selected(url: url, allowUniversalLinks: false) }) } } extension LargeImageViewController: CustomPreviewPresenting { func presentFromPreview(presenter: UIViewController) { presenter.present(self, animated: true) } } extension GalleryViewController: CustomPreviewPresenting { func presentFromPreview(presenter: UIViewController) { presenter.present(self, animated: true) } } extension SFSafariViewController: CustomPreviewPresenting { func presentFromPreview(presenter: UIViewController) { presenter.present(self, animated: true) } }