merge ExtensionSaveService into ExtensionViewModel

This commit is contained in:
Satindar Dhillon 2022-07-06 13:58:59 -07:00
parent d6ccc5b318
commit d7370a579d
3 changed files with 121 additions and 178 deletions

View file

@ -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()
}
}

View file

@ -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)
}
}
}

View file

@ -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 {