From d127eb51f2453fb28f4ba7015ed828007dde921c Mon Sep 17 00:00:00 2001 From: Justin Mazzocchi <2831158+jzzocc@users.noreply.github.com> Date: Sat, 16 Jan 2021 23:14:17 -0800 Subject: [PATCH] Extraction from extension context --- Share Extension/Info.plist | 4 +- ...areExtensionNavigationViewController.swift | 12 +-- .../ViewModels/CompositionViewModel.swift | 87 +++++++++++++------ .../ViewModels/NewStatusViewModel.swift | 36 +++++--- .../Sources/ViewModels/RootViewModel.swift | 3 +- .../ShareExtensionNavigationViewModel.swift | 5 +- 6 files changed, 94 insertions(+), 53 deletions(-) diff --git a/Share Extension/Info.plist b/Share Extension/Info.plist index 40936e6..7ed487b 100644 --- a/Share Extension/Info.plist +++ b/Share Extension/Info.plist @@ -27,13 +27,11 @@ NSExtensionActivationRule NSExtensionActivationSupportsImageWithMaxCount - 4 + 1 NSExtensionActivationSupportsText NSExtensionActivationSupportsMovieWithMaxCount 1 - NSExtensionActivationSupportsWebPageWithMaxCount - 1 NSExtensionActivationSupportsWebURLWithMaxCount 1 diff --git a/Share Extension/ShareExtensionNavigationViewController.swift b/Share Extension/ShareExtensionNavigationViewController.swift index e8f093b..46b6041 100644 --- a/Share Extension/ShareExtensionNavigationViewController.swift +++ b/Share Extension/ShareExtensionNavigationViewController.swift @@ -11,13 +11,14 @@ class ShareExtensionNavigationViewController: UINavigationController { environment: .live( userNotificationCenter: .current(), reduceMotion: { UIAccessibility.isReduceMotionEnabled })) - override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) { - super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) + + override func viewDidLoad() { + super.viewDidLoad() let newStatusViewModel: NewStatusViewModel do { - newStatusViewModel = try viewModel.newStatusViewModel() + newStatusViewModel = try viewModel.newStatusViewModel(extensionContext: extensionContext) } catch { setViewControllers([ShareErrorViewController(error: error)], animated: false) @@ -28,9 +29,4 @@ class ShareExtensionNavigationViewController: UINavigationController { [UIHostingController(rootView: NewStatusView { newStatusViewModel })], animated: false) } - - @available(*, unavailable) - required init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } } diff --git a/ViewModels/Sources/ViewModels/CompositionViewModel.swift b/ViewModels/Sources/ViewModels/CompositionViewModel.swift index 61c9fa4..2c0c074 100644 --- a/ViewModels/Sources/ViewModels/CompositionViewModel.swift +++ b/ViewModels/Sources/ViewModels/CompositionViewModel.swift @@ -4,19 +4,20 @@ import Combine import Foundation import Mastodon import ServiceLayer +import UniformTypeIdentifiers public final class CompositionViewModel: AttachmentsRenderingViewModel, ObservableObject, Identifiable { public let id = Id() public var isPosted = false - @Published public var text: String - @Published public var contentWarning: String - @Published public var displayContentWarning: Bool - @Published public var sensitive: Bool - @Published public var displayPoll: Bool - @Published public var pollMultipleChoice: Bool + @Published public var text = "" + @Published public var contentWarning = "" + @Published public var displayContentWarning = false + @Published public var sensitive = false + @Published public var displayPoll = false + @Published public var pollMultipleChoice = false @Published public var pollExpiresIn = PollExpiry.oneDay - @Published public private(set) var pollOptions: [PollOption] - @Published public private(set) var attachmentViewModels: [AttachmentViewModel] + @Published public private(set) var pollOptions = [PollOption(text: ""), PollOption(text: "")] + @Published public private(set) var attachmentViewModels = [AttachmentViewModel]() @Published public private(set) var attachmentUpload: AttachmentUpload? @Published public private(set) var isPostable = false @Published public private(set) var canAddAttachment = true @@ -27,24 +28,8 @@ public final class CompositionViewModel: AttachmentsRenderingViewModel, Observab private let eventsSubject: PassthroughSubject private var attachmentUploadCancellable: AnyCancellable? - init(eventsSubject: PassthroughSubject, - redraft: (status: Status, identification: Identification)? = nil) { + init(eventsSubject: PassthroughSubject) { self.eventsSubject = eventsSubject - text = redraft?.status.text ?? "" - contentWarning = redraft?.status.spoilerText ?? "" - displayContentWarning = !(redraft?.status.spoilerText.isEmpty ?? true) - sensitive = redraft?.status.sensitive ?? false - displayPoll = redraft?.status.poll != nil - pollMultipleChoice = redraft?.status.poll?.multiple ?? false - pollOptions = redraft?.status.poll?.options.map { PollOption(text: $0.title) } - ?? [PollOption(text: ""), PollOption(text: "")] - if let redraft = redraft { - attachmentViewModels = redraft.status.mediaAttachments.map { - AttachmentViewModel(attachment: $0, identification: redraft.identification) - } - } else { - attachmentViewModels = [AttachmentViewModel]() - } $text.map { !$0.isEmpty } .removeDuplicates() @@ -111,6 +96,58 @@ public extension CompositionViewModel { typealias Id = UUID + convenience init(eventsSubject: PassthroughSubject, redraft: Status, identification: Identification) { + self.init(eventsSubject: eventsSubject) + + if let text = redraft.text { + self.text = text + } + + contentWarning = redraft.spoilerText + displayContentWarning = redraft.spoilerText.isEmpty + sensitive = redraft.sensitive + displayPoll = redraft.poll != nil + attachmentViewModels = redraft.mediaAttachments.map { + AttachmentViewModel(attachment: $0, identification: identification) + } + + if let poll = redraft.poll { + pollMultipleChoice = poll.multiple + pollOptions = poll.options.map { PollOption(text: $0.title) } + } + } + + convenience init(eventsSubject: PassthroughSubject, + extensionContext: NSExtensionContext, + parentViewModel: NewStatusViewModel) { + self.init(eventsSubject: eventsSubject) + + guard let inputItem = extensionContext.inputItems.first as? NSExtensionItem, + let itemProvider = inputItem.attachments?.first + else { return } + + if itemProvider.hasItemConformingToTypeIdentifier(UTType.plainText.identifier) { + itemProvider.loadItem(forTypeIdentifier: UTType.plainText.identifier, options: nil) { result, _ in + guard let text = result as? String else { return } + + self.text = text + } + } else if itemProvider.hasItemConformingToTypeIdentifier(UTType.url.identifier) { + itemProvider.loadItem(forTypeIdentifier: UTType.url.identifier, options: nil) { result, _ in + guard let url = result as? URL else { return } + + if let contentText = inputItem.attributedContentText?.string { + self.text.append(contentText) + self.text.append("\n\n") + } + + self.text.append(url.absoluteString) + } + } else { + attach(itemProvider: itemProvider, parentViewModel: parentViewModel) + } + } + func components(inReplyToId: Status.Id?, visibility: Status.Visibility) -> StatusComponents { StatusComponents( inReplyToId: inReplyToId, diff --git a/ViewModels/Sources/ViewModels/NewStatusViewModel.swift b/ViewModels/Sources/ViewModels/NewStatusViewModel.swift index 09aeb47..cc51400 100644 --- a/ViewModels/Sources/ViewModels/NewStatusViewModel.swift +++ b/ViewModels/Sources/ViewModels/NewStatusViewModel.swift @@ -7,7 +7,7 @@ import ServiceLayer public final class NewStatusViewModel: ObservableObject { @Published public var visibility: Status.Visibility - @Published public private(set) var compositionViewModels: [CompositionViewModel] + @Published public private(set) var compositionViewModels = [CompositionViewModel]() @Published public private(set) var identification: Identification @Published public private(set) var authenticatedIdentities = [Identity]() @Published public var canPost = false @@ -27,25 +27,33 @@ public final class NewStatusViewModel: ObservableObject { identification: Identification, environment: AppEnvironment, inReplyTo: StatusViewModel?, - redraft: Status?) { + redraft: Status?, + extensionContext: NSExtensionContext?) { self.allIdentitiesService = allIdentitiesService self.identification = identification self.environment = environment inReplyToViewModel = inReplyTo - - let redraftAndIdentification: (status: Status, identification: Identification)? - - if let redraft = redraft { - redraftAndIdentification = (status: redraft, identification: identification) - } else { - redraftAndIdentification = nil - } - - compositionViewModels = [CompositionViewModel( - eventsSubject: compositionEventsSubject, - redraft: redraftAndIdentification)] events = eventsSubject.eraseToAnyPublisher() visibility = identification.identity.preferences.postingDefaultVisibility + + let compositionViewModel: CompositionViewModel + + if let redraft = redraft { + compositionViewModel = CompositionViewModel( + eventsSubject: compositionEventsSubject, + redraft: redraft, + identification: identification) + } else if let extensionContext = extensionContext { + compositionViewModel = CompositionViewModel( + eventsSubject: compositionEventsSubject, + extensionContext: extensionContext, + parentViewModel: self) + } else { + compositionViewModel = CompositionViewModel(eventsSubject: compositionEventsSubject) + } + + compositionViewModels = [compositionViewModel] + allIdentitiesService.authenticatedIdentitiesPublisher() .assignErrorsToAlertItem(to: \.alertItem, on: self) .assign(to: &$authenticatedIdentities) diff --git a/ViewModels/Sources/ViewModels/RootViewModel.swift b/ViewModels/Sources/ViewModels/RootViewModel.swift index a02f40d..f85dad4 100644 --- a/ViewModels/Sources/ViewModels/RootViewModel.swift +++ b/ViewModels/Sources/ViewModels/RootViewModel.swift @@ -68,7 +68,8 @@ public extension RootViewModel { identification: identification, environment: environment, inReplyTo: inReplyTo, - redraft: redraft) + redraft: redraft, + extensionContext: nil) } } diff --git a/ViewModels/Sources/ViewModels/ShareExtensionNavigationViewModel.swift b/ViewModels/Sources/ViewModels/ShareExtensionNavigationViewModel.swift index 6238ef0..450deb5 100644 --- a/ViewModels/Sources/ViewModels/ShareExtensionNavigationViewModel.swift +++ b/ViewModels/Sources/ViewModels/ShareExtensionNavigationViewModel.swift @@ -19,7 +19,7 @@ public final class ShareExtensionNavigationViewModel: ObservableObject { } public extension ShareExtensionNavigationViewModel { - func newStatusViewModel() throws -> NewStatusViewModel { + func newStatusViewModel(extensionContext: NSExtensionContext?) throws -> NewStatusViewModel { let allIdentitiesService = try AllIdentitiesService(environment: environment) guard let identity = try allIdentitiesService.mostRecentAuthenticatedIdentity() @@ -38,6 +38,7 @@ public extension ShareExtensionNavigationViewModel { identification: identification, environment: environment, inReplyTo: nil, - redraft: nil) + redraft: nil, + extensionContext: extensionContext) } }