From d7370a579d59b95f37cfab5d1d4b0d3cc1600744 Mon Sep 17 00:00:00 2001 From: Satindar Dhillon Date: Wed, 6 Jul 2022 13:58:59 -0700 Subject: [PATCH] merge ExtensionSaveService into ExtensionViewModel --- .../Share/ExtensionSaveService.swift | 211 ++++++++---------- .../Share/ShareExtensionView.swift | 44 +--- .../Share/ShareExtensionViewModel.swift | 44 ++-- 3 files changed, 121 insertions(+), 178 deletions(-) diff --git a/apple/OmnivoreKit/Sources/App/AppExtensions/Share/ExtensionSaveService.swift b/apple/OmnivoreKit/Sources/App/AppExtensions/Share/ExtensionSaveService.swift index 8590ab4c3..c793b93b2 100644 --- a/apple/OmnivoreKit/Sources/App/AppExtensions/Share/ExtensionSaveService.swift +++ b/apple/OmnivoreKit/Sources/App/AppExtensions/Share/ExtensionSaveService.swift @@ -4,165 +4,58 @@ import Services import Utils import Views -final class ExtensionSaveService { - #if os(macOS) - let services = Services() - #endif - - let queue: OperationQueue - - init() { - self.queue = OperationQueue() - } - - #if os(iOS) - private func queueSaveOperation( - _ pageScrape: PageScrapePayload, - shareExtensionViewModel: ShareExtensionChildViewModel - ) { - ProcessInfo().performExpiringActivity(withReason: "app.omnivore.SaveActivity") { [self] expiring in - guard !expiring else { - self.queue.cancelAllOperations() - self.queue.waitUntilAllOperationsAreFinished() - return - } - - let operation = SaveOperation(pageScrapePayload: pageScrape, shareExtensionViewModel: shareExtensionViewModel) - - self.queue.addOperation(operation) - self.queue.waitUntilAllOperationsAreFinished() - } - } - #endif - - public func save(_ extensionContext: NSExtensionContext, shareExtensionViewModel: ShareExtensionChildViewModel) { +extension ShareExtensionViewModel { + public func save(_ extensionContext: NSExtensionContext) { PageScraper.scrape(extensionContext: extensionContext) { [weak self] result in guard let self = self else { return } switch result { case let .success(payload): DispatchQueue.main.async { - shareExtensionViewModel.status = .saved + self.status = .saved let url = URLComponents(string: payload.url) let hostname = URL(string: payload.url)?.host ?? "" switch payload.contentType { case let .html(html: _, title: title, iconURL: iconURL): - shareExtensionViewModel.title = title - shareExtensionViewModel.iconURL = iconURL - shareExtensionViewModel.url = hostname + self.title = title + self.iconURL = iconURL + self.url = hostname case .none: - shareExtensionViewModel.url = hostname - shareExtensionViewModel.title = payload.url + self.url = hostname + self.title = payload.url if var url = url { url.path = "/favicon.ico" - shareExtensionViewModel.iconURL = url.url?.absoluteString + self.iconURL = url.url?.absoluteString } case let .pdf(localUrl: localUrl): - shareExtensionViewModel.url = hostname - shareExtensionViewModel.title = PDFUtils.titleFromPdfFile(localUrl.absoluteString) + self.url = hostname + self.title = PDFUtils.titleFromPdfFile(localUrl.absoluteString) Task { let localThumbnail = try await PDFUtils.createThumbnailFor(inputUrl: localUrl) DispatchQueue.main.async { - shareExtensionViewModel.iconURL = localThumbnail?.absoluteString + self.iconURL = localThumbnail?.absoluteString } } } } #if os(iOS) - self.queueSaveOperation(payload, shareExtensionViewModel: shareExtensionViewModel) + self.queueSaveOperation(payload) #else Task { - await shareExtensionViewModel.createPage(services: self.services, pageScrapePayload: payload) + await createPage(services: self.services, pageScrapePayload: payload) } #endif case .failure: DispatchQueue.main.async { - shareExtensionViewModel.status = .failed(error: .unknown(description: "Could not retrieve content")) + self.status = .failed(error: .unknown(description: "Could not retrieve content")) } } } } - final class SaveOperation: Operation, URLSessionDelegate { - let services: Services - let pageScrapePayload: PageScrapePayload - let shareExtensionViewModel: ShareExtensionChildViewModel - - var queue: OperationQueue? - var uploadTask: URLSessionTask? - - // swiftlint:disable:next nesting - enum State: Int { - case created - case started - case finished - } - - init(pageScrapePayload: PageScrapePayload, shareExtensionViewModel: ShareExtensionChildViewModel) { - self.pageScrapePayload = pageScrapePayload - self.shareExtensionViewModel = shareExtensionViewModel - - self.state = .created - self.services = Services() - } - - public var state: State = .created { - willSet { - willChangeValue(forKey: "isReady") - willChangeValue(forKey: "isExecuting") - willChangeValue(forKey: "isFinished") - willChangeValue(forKey: "isCancelled") - } - didSet { - didChangeValue(forKey: "isCancelled") - didChangeValue(forKey: "isFinished") - didChangeValue(forKey: "isExecuting") - didChangeValue(forKey: "isReady") - } - } - - override var isAsynchronous: Bool { - true - } - - override var isReady: Bool { - true - } - - override var isExecuting: Bool { - self.state == .started - } - - override var isFinished: Bool { - self.state == .finished - } - - override func start() { - guard !isCancelled else { return } - state = .started - queue = OperationQueue() - - Task { - let pageCreated = await shareExtensionViewModel.createPage( - services: services, - pageScrapePayload: pageScrapePayload - ) - if pageCreated { - state = .finished - } - } - } - - override func cancel() { - super.cancel() - } - } -} - -extension ShareExtensionChildViewModel { func createPage(services: Services, pageScrapePayload: PageScrapePayload) async -> Bool { var newRequestID: String? @@ -217,3 +110,77 @@ extension ShareExtensionChildViewModel { } } } + +final class SaveOperation: Operation, URLSessionDelegate { + let services: Services + let pageScrapePayload: PageScrapePayload + let shareExtensionViewModel: ShareExtensionViewModel + + var queue: OperationQueue? + var uploadTask: URLSessionTask? + + enum State: Int { + case created + case started + case finished + } + + init(pageScrapePayload: PageScrapePayload, shareExtensionViewModel: ShareExtensionViewModel) { + self.pageScrapePayload = pageScrapePayload + self.shareExtensionViewModel = shareExtensionViewModel + + self.state = .created + self.services = Services() + } + + public var state: State = .created { + willSet { + willChangeValue(forKey: "isReady") + willChangeValue(forKey: "isExecuting") + willChangeValue(forKey: "isFinished") + willChangeValue(forKey: "isCancelled") + } + didSet { + didChangeValue(forKey: "isCancelled") + didChangeValue(forKey: "isFinished") + didChangeValue(forKey: "isExecuting") + didChangeValue(forKey: "isReady") + } + } + + override var isAsynchronous: Bool { + true + } + + override var isReady: Bool { + true + } + + override var isExecuting: Bool { + self.state == .started + } + + override var isFinished: Bool { + self.state == .finished + } + + override func start() { + guard !isCancelled else { return } + state = .started + queue = OperationQueue() + + Task { + let pageCreated = await shareExtensionViewModel.createPage( + services: services, + pageScrapePayload: pageScrapePayload + ) + if pageCreated { + state = .finished + } + } + } + + override func cancel() { + super.cancel() + } +} diff --git a/apple/OmnivoreKit/Sources/App/AppExtensions/Share/ShareExtensionView.swift b/apple/OmnivoreKit/Sources/App/AppExtensions/Share/ShareExtensionView.swift index 5d1306885..fc1d1d171 100644 --- a/apple/OmnivoreKit/Sources/App/AppExtensions/Share/ShareExtensionView.swift +++ b/apple/OmnivoreKit/Sources/App/AppExtensions/Share/ShareExtensionView.swift @@ -4,49 +4,13 @@ import SwiftUI import Utils import Views -struct ShareExtensionView: View { +public struct ShareExtensionView: View { let extensionContext: NSExtensionContext? @StateObject private var viewModel = ShareExtensionViewModel() - @StateObject private var childViewModel = ShareExtensionChildViewModel() - - var body: some View { - ShareExtensionChildView( - viewModel: childViewModel, - onAppearAction: { - viewModel.savePage( - extensionContext: extensionContext, - shareExtensionViewModel: childViewModel - ) - }, - readNowButtonAction: { viewModel.handleReadNowAction(requestId: $0, extensionContext: extensionContext) }, - dismissButtonTappedAction: { _, _ in - extensionContext?.completeRequest(returningItems: [], completionHandler: nil) - } - ) - } -} - -public struct ShareExtensionChildView: View { - let viewModel: ShareExtensionChildViewModel - let onAppearAction: () -> Void - let readNowButtonAction: (String) -> Void - let dismissButtonTappedAction: (ReminderTime?, Bool) -> Void @State var reminderTime: ReminderTime? @State var hideUntilReminded = false - public init( - viewModel: ShareExtensionChildViewModel, - onAppearAction: @escaping () -> Void, - readNowButtonAction: @escaping (String) -> Void, - dismissButtonTappedAction: @escaping (ReminderTime?, Bool) -> Void - ) { - self.viewModel = viewModel - self.onAppearAction = onAppearAction - self.readNowButtonAction = readNowButtonAction - self.dismissButtonTappedAction = dismissButtonTappedAction - } - private func handleReminderTimeSelection(_ selectedTime: ReminderTime) { if selectedTime == reminderTime { reminderTime = nil @@ -191,14 +155,14 @@ public struct ShareExtensionChildView: View { HStack { Button( - action: { readNowButtonAction(self.viewModel.requestId) }, + action: { viewModel.handleReadNowAction(extensionContext: extensionContext) }, label: { Text("Read Now").frame(maxWidth: .infinity) } ) .buttonStyle(RoundedRectButtonStyle()) Button( action: { - dismissButtonTappedAction(reminderTime, hideUntilReminded) + extensionContext?.completeRequest(returningItems: [], completionHandler: nil) }, label: { Text("Dismiss") @@ -216,7 +180,7 @@ public struct ShareExtensionChildView: View { alignment: .topLeading ) .onAppear { - onAppearAction() + viewModel.savePage(extensionContext: extensionContext) } } } diff --git a/apple/OmnivoreKit/Sources/App/AppExtensions/Share/ShareExtensionViewModel.swift b/apple/OmnivoreKit/Sources/App/AppExtensions/Share/ShareExtensionViewModel.swift index 4da076d77..76c1e2ea9 100644 --- a/apple/OmnivoreKit/Sources/App/AppExtensions/Share/ShareExtensionViewModel.swift +++ b/apple/OmnivoreKit/Sources/App/AppExtensions/Share/ShareExtensionViewModel.swift @@ -4,12 +4,20 @@ import Utils import Views public class ShareExtensionViewModel: ObservableObject { - @Published var title: String? + @Published public var status: ShareExtensionStatus = .processing + @Published public var title: String? + @Published public var url: String? + @Published public var iconURL: String? + @Published public var requestId = UUID().uuidString.lowercased() @Published var debugText: String? - let saveService = ExtensionSaveService() + #if os(macOS) + let services = Services() + #endif - func handleReadNowAction(requestId: String, extensionContext: NSExtensionContext?) { + let queue = OperationQueue() + + func handleReadNowAction(extensionContext: NSExtensionContext?) { #if os(iOS) if let application = UIApplication.value(forKeyPath: #keyPath(UIApplication.shared)) as? UIApplication { let deepLinkUrl = NSURL(string: "omnivore://shareExtensionRequestID/\(requestId)") @@ -19,27 +27,31 @@ public class ShareExtensionViewModel: ObservableObject { extensionContext?.completeRequest(returningItems: [], completionHandler: nil) } - func savePage(extensionContext: NSExtensionContext?, shareExtensionViewModel: ShareExtensionChildViewModel) { + func savePage(extensionContext: NSExtensionContext?) { if let extensionContext = extensionContext { - saveService.save(extensionContext, shareExtensionViewModel: shareExtensionViewModel) + save(extensionContext) } else { DispatchQueue.main.async { - shareExtensionViewModel.status = .failed(error: .unknown(description: "Internal Error")) + self.status = .failed(error: .unknown(description: "Internal Error")) } } } -} -public class ShareExtensionChildViewModel: ObservableObject { - @Published public var status: ShareExtensionStatus = .processing - @Published public var title: String? - @Published public var url: String? - @Published public var iconURL: String? - @Published public var requestId: String + #if os(iOS) + func queueSaveOperation(_ payload: PageScrapePayload) { + ProcessInfo().performExpiringActivity(withReason: "app.omnivore.SaveActivity") { [self] expiring in + guard !expiring else { + self.queue.cancelAllOperations() + self.queue.waitUntilAllOperationsAreFinished() + return + } - public init() { - self.requestId = UUID().uuidString.lowercased() - } + let operation = SaveOperation(pageScrapePayload: payload, shareExtensionViewModel: self) + self.queue.addOperation(operation) + self.queue.waitUntilAllOperationsAreFinished() + } + } + #endif } public enum ShareExtensionStatus {