// // ShareViewController.swift // ShareExtension // // Created by Shadowfacts on 4/17/23. // Copyright © 2023 Shadowfacts. All rights reserved. // import SwiftUI import UserAccounts import ComposeUI import UniformTypeIdentifiers import TuskerPreferences import Combine import Pachyderm class ShareViewController: UIViewController { private var state: State = .loading required init?(coder: NSCoder) { super.init(coder: coder) } override func viewDidLoad() { super.viewDidLoad() view.tintColor = Preferences.shared.accentColor.color if let account = UserAccountsManager.shared.getMostRecentAccount() { Task { @MainActor in let draft = await createDraft(account: account) state = .ok let context = ShareMastodonContext(accountInfo: account) let host = ShareHostingController(draft: draft, mastodonContext: context) host.view.translatesAutoresizingMaskIntoConstraints = false addChild(host) self.view.addSubview(host.view) NSLayoutConstraint.activate([ host.view.leadingAnchor.constraint(equalTo: self.view.leadingAnchor), host.view.trailingAnchor.constraint(equalTo: self.view.trailingAnchor), host.view.topAnchor.constraint(equalTo: self.view.topAnchor), host.view.bottomAnchor.constraint(equalTo: self.view.bottomAnchor), ]) host.didMove(toParent: self) } } else { state = .notLoggedIn } } private func createDraft(account: UserAccountInfo) async -> Draft { async let (text, attachments) = getDraftConfigurationFromExtensionContext() let draft = DraftsPersistentContainer.shared.createDraft( accountID: account.id, text: await text, contentWarning: "", inReplyToID: nil, visibility: Preferences.shared.defaultPostVisibility.resolved(withServerDefault: account.serverDefaultVisibility.flatMap(Visibility.init(rawValue:))), language: account.serverDefaultLanguage, localOnly: !(account.serverDefaultFederation ?? true) ) for attachment in await attachments { DraftsPersistentContainer.shared.viewContext.insert(attachment) } draft.draftAttachments = await attachments return draft } private func getDraftConfigurationFromExtensionContext() async -> (String, [DraftAttachment]) { guard let extensionContext, let inputItem = (extensionContext.inputItems as? [NSExtensionItem])?.first else { return ("", []) } var text: String = "" var url: URL? var attachments: [DraftAttachment] = [] for itemProvider in inputItem.attachments ?? [] { // attachments have the highest priority, but only given this heuristic // otherwise attachment decoding ends up being overzealous let likelyAttachment = [UTType.image, .movie].contains(where: { itemProvider.hasItemConformingToTypeIdentifier($0.identifier) }) if likelyAttachment, let attachment: DraftAttachment = await getObject(from: itemProvider) { attachments.append(attachment) } else if let attached: NSURL = await getObject(from: itemProvider) { if url == nil { url = attached as URL } } else if let s: NSString = await getObject(from: itemProvider) { if text.isEmpty { text = s as String } } } if text.isEmpty, let s = inputItem.attributedTitle ?? inputItem.attributedContentText { text = s.string } if let url { if !text.isEmpty { text += "\n" } text += url.absoluteString } if !text.isEmpty { text = "\n\n\(text)" } return (text, attachments) } private func getObject(from itemProvider: NSItemProvider) async -> T? { guard itemProvider.canLoadObject(ofClass: T.self) else { return nil } return await withCheckedContinuation({ continuation in itemProvider.loadObject(ofClass: T.self) { object, error in continuation.resume(returning: object as? T) } }) } override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) if case .notLoggedIn = state { let alert = UIAlertController(title: "Not Logged In", message: "You need to log in to an account through the app before you can post.", preferredStyle: .alert) alert.addAction(UIAlertAction(title: "OK", style: .default, handler: { [unowned self] _ in self.extensionContext!.cancelRequest(withError: Error.notLoggedIn) })) present(alert, animated: true) } } enum State { case loading case notLoggedIn case ok } enum Error: Swift.Error { case notLoggedIn } }