diff --git a/apple/OmnivoreKit/Sources/App/AppExtensions/Share/ExtensionSaveService.swift b/apple/OmnivoreKit/Sources/App/AppExtensions/Share/ExtensionSaveService.swift index c3c7faf3c..f40b8a61c 100644 --- a/apple/OmnivoreKit/Sources/App/AppExtensions/Share/ExtensionSaveService.swift +++ b/apple/OmnivoreKit/Sources/App/AppExtensions/Share/ExtensionSaveService.swift @@ -18,7 +18,7 @@ class ExtensionSaveService { self.queue = OperationQueue() } - private func queueSaveOperation(_ pageScrape: PageScrapePayload, requestId: String, shareExtensionViewModel: ShareExtensionChildViewModel) { + private func queueSaveOperation(_ pageScrape: PageScrapePayload, shareExtensionViewModel: ShareExtensionChildViewModel) { ProcessInfo().performExpiringActivity(withReason: "app.omnivore.SaveActivity") { [self] expiring in guard !expiring else { self.queue.cancelAllOperations() @@ -26,14 +26,14 @@ class ExtensionSaveService { return } - let operation = SaveOperation(pageScrapePayload: pageScrape, requestId: requestId, shareExtensionViewModel: shareExtensionViewModel) + let operation = SaveOperation(pageScrapePayload: pageScrape, shareExtensionViewModel: shareExtensionViewModel) self.queue.addOperation(operation) self.queue.waitUntilAllOperationsAreFinished() } } - public func save(_ extensionContext: NSExtensionContext, requestId: String, shareExtensionViewModel: ShareExtensionChildViewModel) { + public func save(_ extensionContext: NSExtensionContext, shareExtensionViewModel: ShareExtensionChildViewModel) { PageScraper.scrape(extensionContext: extensionContext) { [weak self] result in guard let self = self else { return } @@ -68,8 +68,8 @@ class ExtensionSaveService { } } } - self.queueSaveOperation(payload, requestId: requestId, shareExtensionViewModel: shareExtensionViewModel) - case let .failure: + self.queueSaveOperation(payload, shareExtensionViewModel: shareExtensionViewModel) + case .failure: DispatchQueue.main.async { shareExtensionViewModel.status = .failed(error: .unknown(description: "Could not retrieve content")) } @@ -78,7 +78,6 @@ class ExtensionSaveService { } class SaveOperation: Operation, URLSessionDelegate { - let requestId: String let services: Services let pageScrapePayload: PageScrapePayload let shareExtensionViewModel: ShareExtensionChildViewModel @@ -92,9 +91,8 @@ class ExtensionSaveService { case finished } - init(pageScrapePayload: PageScrapePayload, requestId: String, shareExtensionViewModel: ShareExtensionChildViewModel) { + init(pageScrapePayload: PageScrapePayload, shareExtensionViewModel: ShareExtensionChildViewModel) { self.pageScrapePayload = pageScrapePayload - self.requestId = requestId self.shareExtensionViewModel = shareExtensionViewModel self.state = .created @@ -138,7 +136,7 @@ class ExtensionSaveService { queue = OperationQueue() Task { - await persist(services: self.services, pageScrapePayload: self.pageScrapePayload, requestId: self.requestId) + await persist(services: self.services, pageScrapePayload: self.pageScrapePayload) } } @@ -146,38 +144,48 @@ class ExtensionSaveService { super.cancel() } - private func updateStatus(newStatus: ShareExtensionStatus) { + private func updateStatus(_ requestId: String?, newStatus: ShareExtensionStatus) { DispatchQueue.main.async { self.shareExtensionViewModel.status = newStatus + if let requestId = requestId { + self.shareExtensionViewModel.requestId = requestId + } } } - private func persist(services: Services, pageScrapePayload: PageScrapePayload, requestId: String) async { + private func persist(services: Services, pageScrapePayload: PageScrapePayload) async { + var requestId = shareExtensionViewModel.requestId + do { try await services.dataService.persistPageScrapePayload(pageScrapePayload, requestId: requestId) } catch { - updateStatus(newStatus: .failed(error: SaveArticleError.unknown(description: "Unable to access content"))) + updateStatus(nil, newStatus: .failed(error: SaveArticleError.unknown(description: "Unable to access content"))) return } do { - updateStatus(newStatus: .saved) + updateStatus(requestId, newStatus: .saved) switch pageScrapePayload.contentType { case .none: - try await services.dataService.syncUrl(id: requestId, url: pageScrapePayload.url) + requestId = try await services.dataService.createPageFromUrl(id: requestId, url: pageScrapePayload.url) case let .pdf(localUrl): - try await services.dataService.syncPdf(id: requestId, localPdfURL: localUrl, url: pageScrapePayload.url) + try await services.dataService.createPageFromPdf(id: requestId, localPdfURL: localUrl, url: pageScrapePayload.url) case let .html(html, title, _): - try await services.dataService.syncPage(id: requestId, originalHtml: html, title: title, url: pageScrapePayload.url) + requestId = try await services.dataService.createPage( + id: requestId, + originalHtml: html, + title: title, + url: pageScrapePayload.url + ) } } catch { - updateStatus(newStatus: .syncFailed(error: SaveArticleError.unknown(description: "Unknown Error"))) + updateStatus(nil, newStatus: .syncFailed(error: SaveArticleError.unknown(description: "Unknown Error"))) return } - updateStatus(newStatus: .synced) + updateStatus(requestId, newStatus: .synced) state = .finished } } diff --git a/apple/OmnivoreKit/Sources/App/AppExtensions/Share/ShareExtensionScene.swift b/apple/OmnivoreKit/Sources/App/AppExtensions/Share/ShareExtensionScene.swift index a5ea021e1..467d158f7 100644 --- a/apple/OmnivoreKit/Sources/App/AppExtensions/Share/ShareExtensionScene.swift +++ b/apple/OmnivoreKit/Sources/App/AppExtensions/Share/ShareExtensionScene.swift @@ -22,13 +22,11 @@ public extension PlatformViewController { public class ShareExtensionViewModel: ObservableObject { @Published var title: String? - @Published var status: ShareExtensionStatus = .processing @Published var debugText: String? let saveService = ExtensionSaveService() - let requestId = UUID().uuidString.lowercased() - func handleReadNowAction(extensionContext: NSExtensionContext?) { + func handleReadNowAction(requestId: String, extensionContext: NSExtensionContext?) { #if os(iOS) if let application = UIApplication.value(forKeyPath: #keyPath(UIApplication.shared)) as? UIApplication { let deepLinkUrl = NSURL(string: "omnivore://shareExtensionRequestID/\(requestId)") @@ -40,15 +38,11 @@ public class ShareExtensionViewModel: ObservableObject { func savePage(extensionContext: NSExtensionContext?, shareExtensionViewModel: ShareExtensionChildViewModel) { if let extensionContext = extensionContext { - saveService.save(extensionContext, requestId: requestId, shareExtensionViewModel: shareExtensionViewModel) + saveService.save(extensionContext, shareExtensionViewModel: shareExtensionViewModel) } else { - updateStatus(.failed(error: .unknown(description: "Internal Error"))) - } - } - - private func updateStatus(_ newStatus: ShareExtensionStatus) { - DispatchQueue.main.async { - self.status = newStatus + DispatchQueue.main.async { + shareExtensionViewModel.status = .failed(error: .unknown(description: "Internal Error")) + } } } } @@ -62,7 +56,7 @@ struct ShareExtensionView: View { ShareExtensionChildView( viewModel: childViewModel, onAppearAction: { viewModel.savePage(extensionContext: extensionContext, shareExtensionViewModel: childViewModel) }, - readNowButtonAction: { viewModel.handleReadNowAction(extensionContext: extensionContext) }, + readNowButtonAction: { viewModel.handleReadNowAction(requestId: $0, extensionContext: extensionContext) }, dismissButtonTappedAction: { _, _ in extensionContext?.completeRequest(returningItems: [], completionHandler: nil) } diff --git a/apple/OmnivoreKit/Sources/App/PDFSupport/PDFViewer.swift b/apple/OmnivoreKit/Sources/App/PDFSupport/PDFViewer.swift index e4a80b185..d6217081c 100644 --- a/apple/OmnivoreKit/Sources/App/PDFSupport/PDFViewer.swift +++ b/apple/OmnivoreKit/Sources/App/PDFSupport/PDFViewer.swift @@ -21,15 +21,14 @@ import Utils let url: URL } - let pdfURL: URL let viewModel: PDFViewerViewModel @StateObject var pdfStateObject = PDFStateObject() @State var readerView: Bool = false @State private var shareLink: ShareLink? + @State private var errorMessage: String? - init(remoteURL: URL, viewModel: PDFViewerViewModel) { - self.pdfURL = viewModel.pdfItem.localPdfURL ?? remoteURL + init(viewModel: PDFViewerViewModel) { self.viewModel = viewModel } @@ -135,12 +134,21 @@ import Utils .sheet(item: $shareLink) { ShareSheet(activityItems: [$0.url]) } + } else if let errorMessage = errorMessage { + Text(errorMessage) } else { ProgressView() .task { - let document = HighlightedDocument(url: pdfURL, viewModel: viewModel) - pdfStateObject.document = document - pdfStateObject.coordinator = PDFViewCoordinator(document: document, viewModel: viewModel) + // NOTE: the issue here is the PDF is downloaded, but saved to a URL we don't know about + // because it is changed. + let pdfURL = await viewModel.downloadPDF(dataService: dataService) + if let pdfURL = pdfURL { + let document = HighlightedDocument(url: pdfURL, viewModel: viewModel) + pdfStateObject.document = document + pdfStateObject.coordinator = PDFViewCoordinator(document: document, viewModel: viewModel) + } else { + errorMessage = "Unable to download PDF: \(pdfURL)" + } } } } diff --git a/apple/OmnivoreKit/Sources/App/PDFSupport/PDFViewerViewModel.swift b/apple/OmnivoreKit/Sources/App/PDFSupport/PDFViewerViewModel.swift index 978063551..5b25456cb 100644 --- a/apple/OmnivoreKit/Sources/App/PDFSupport/PDFViewerViewModel.swift +++ b/apple/OmnivoreKit/Sources/App/PDFSupport/PDFViewerViewModel.swift @@ -9,7 +9,6 @@ public final class PDFViewerViewModel: ObservableObject { @Published public var readerView: Bool = false public let pdfItem: PDFItem - private var storedURL: URL? var subscriptions = Set() @@ -81,4 +80,25 @@ public final class PDFViewerViewModel: ObservableObject { return components?.url } + + public var itemDownloaded: Bool { + if let localPdfURL = pdfItem.localPdfURL, FileManager.default.fileExists(atPath: localPdfURL.path) { + return true + } + return false + } + + public func downloadPDF(dataService: DataService) async -> URL? { + do { + if itemDownloaded { + return pdfItem.localPdfURL + } + if let localURL = try await dataService.fetchPDFData(slug: pdfItem.slug, pageURLString: pdfItem.originalArticleURL) { + return localURL + } + } catch { + print("error downloading PDF", error) + } + return nil + } } diff --git a/apple/OmnivoreKit/Sources/App/Views/LinkItemDetailView.swift b/apple/OmnivoreKit/Sources/App/Views/LinkItemDetailView.swift index dea6b1963..79eb3a002 100644 --- a/apple/OmnivoreKit/Sources/App/Views/LinkItemDetailView.swift +++ b/apple/OmnivoreKit/Sources/App/Views/LinkItemDetailView.swift @@ -300,7 +300,7 @@ struct LinkItemDetailView: View { @ViewBuilder private var fixedNavBarReader: some View { if let pdfItem = viewModel.pdfItem, let pdfURL = pdfItem.pdfURL { #if os(iOS) - PDFViewer(remoteURL: pdfURL, viewModel: PDFViewerViewModel(pdfItem: pdfItem)) + PDFViewer(viewModel: PDFViewerViewModel(pdfItem: pdfItem)) .navigationBarTitleDisplayMode(.inline) #elseif os(macOS) PDFWrapperView(pdfURL: pdfURL) diff --git a/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReader.swift b/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReader.swift index c24689f14..514d9c8f4 100644 --- a/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReader.swift +++ b/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReader.swift @@ -6,9 +6,8 @@ import WebKit #if os(iOS) struct WebReader: UIViewRepresentable { - let htmlContent: String - let highlightsJSONString: String let item: LinkedItem + let articleContent: ArticleContent let openLinkAction: (URL) -> Void let webViewActionHandler: (WKScriptMessage, WKScriptMessageReplyHandler?) -> Void let navBarVisibilityRatioUpdater: (Double) -> Void @@ -143,9 +142,8 @@ import WebKit webView.loadHTMLString( WebReaderContent( - htmlContent: htmlContent, - highlightsJSONString: highlightsJSONString, item: item, + articleContent: articleContent, isDark: UITraitCollection.current.userInterfaceStyle == .dark, fontSize: fontSize(), lineHeight: lineHeight(), diff --git a/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderContainer.swift b/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderContainer.swift index f984ad1d8..4050d4cff 100644 --- a/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderContainer.swift +++ b/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderContainer.swift @@ -89,6 +89,7 @@ import WebKit Button( action: { dataService.archiveLink(objectID: item.objectID, archived: !item.isArchived) + presentationMode.wrappedValue.dismiss() Snackbar.show(message: !item.isArchived ? "Link archived" : "Link moved to Inbox") }, label: { @@ -122,6 +123,7 @@ import WebKit Button("Remove Link", role: .destructive) { Snackbar.show(message: "Link removed") dataService.removeLink(objectID: item.objectID) + presentationMode.wrappedValue.dismiss() } Button("Cancel", role: .cancel, action: {}) } @@ -134,9 +136,8 @@ import WebKit ZStack { if let articleContent = viewModel.articleContent { WebReader( - htmlContent: articleContent.htmlContent, - highlightsJSONString: articleContent.highlightsJSONString, item: item, + articleContent: articleContent, openLinkAction: { #if os(macOS) NSWorkspace.shared.open($0) diff --git a/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderContent.swift b/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderContent.swift index 87902f01a..df4bece5a 100644 --- a/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderContent.swift +++ b/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderContent.swift @@ -7,16 +7,14 @@ struct WebReaderContent { let textFontSize: Int let lineHeight: Int let margin: Int - let htmlContent: String - let highlightsJSONString: String let item: LinkedItem let themeKey: String let fontFamily: WebFont + let articleContent: ArticleContent init( - htmlContent: String, - highlightsJSONString: String, item: LinkedItem, + articleContent: ArticleContent, isDark: Bool, fontSize: Int, lineHeight: Int, @@ -26,11 +24,10 @@ struct WebReaderContent { self.textFontSize = fontSize self.lineHeight = lineHeight self.margin = margin - self.htmlContent = htmlContent - self.highlightsJSONString = highlightsJSONString self.item = item self.themeKey = isDark ? "Gray" : "LightGray" self.fontFamily = fontFamily + self.articleContent = articleContent } // swiftlint:disable line_length @@ -52,7 +49,7 @@ struct WebReaderContent {