diff --git a/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReader.swift b/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReader.swift index 8f712e05d..3945ba5ab 100644 --- a/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReader.swift +++ b/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReader.swift @@ -8,7 +8,7 @@ struct WebReader: UIViewRepresentable { let articleContent: ArticleContent let item: FeedItem let openLinkAction: (URL) -> Void - let webViewActionHandler: (WKScriptMessage) -> Void + let webViewActionHandler: (WKScriptMessage, WKScriptMessageReplyHandler?) -> Void let navBarVisibilityRatioUpdater: (Double) -> Void let authToken: String let appEnv: AppEnvironment diff --git a/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderContainer.swift b/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderContainer.swift index eb8a00f11..8ecb47cdd 100644 --- a/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderContainer.swift +++ b/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderContainer.swift @@ -5,36 +5,6 @@ import SwiftUI import Views import WebKit -struct SafariWebLink: Identifiable { - let id: UUID - let url: URL -} - -// TODO: load highlights -final class WebReaderViewModel: ObservableObject { - @Published var isLoading = false - @Published var articleContent: ArticleContent? - - var subscriptions = Set() - - func loadContent(dataService: DataService, slug: String) { - isLoading = true - - guard let viewer = dataService.currentViewer else { return } - - dataService.articleContentPublisher(username: viewer.username, slug: slug).sink( - receiveCompletion: { [weak self] completion in - guard case .failure = completion else { return } - self?.isLoading = false - }, - receiveValue: { [weak self] articleContent in - self?.articleContent = articleContent - } - ) - .store(in: &subscriptions) - } -} - struct WebReaderContainerView: View { let item: FeedItem let homeFeedViewModel: HomeFeedViewModel @@ -62,7 +32,24 @@ struct WebReaderContainerView: View { ) } - func webViewActionHandler(message: WKScriptMessage) { + func webViewActionHandler(message: WKScriptMessage, replyHandler: WKScriptMessageReplyHandler?) { + if message.name == WebViewAction.readingProgressUpdate.rawValue { + let messageBody = message.body as? [String: Double] + + if let messageBody = messageBody, let progress = messageBody["progress"] { + homeFeedViewModel.updateProgress(itemID: item.id, progress: Double(progress)) + } + } + + if let replyHandler = replyHandler { + viewModel.webViewActionWithReplyHandler( + message: message, + replyHandler: replyHandler, + dataService: dataService + ) + return + } + if message.name == WebViewAction.highlightAction.rawValue { handleHighlightAction(message: message) } diff --git a/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderContent.swift b/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderContent.swift index a725b4212..d7fda66f7 100644 --- a/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderContent.swift +++ b/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderContent.swift @@ -46,8 +46,8 @@ struct WebReaderContent { } window.omnivoreArticle = { - id: "test", - linkId: "test", + id: "\(item.id)", + linkId: "\(item.id)", slug: "test-slug", createdAt: new Date().toISOString(), savedAt: new Date().toISOString(), diff --git a/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderCoordinator.swift b/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderCoordinator.swift index 4ac5559b8..c4ad4d6d5 100644 --- a/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderCoordinator.swift +++ b/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderCoordinator.swift @@ -7,8 +7,10 @@ import Utils import Views import WebKit +typealias WKScriptMessageReplyHandler = (Any?, String?) -> Void + final class WebReaderCoordinator: NSObject { - var webViewActionHandler: (WKScriptMessage) -> Void = { _ in } + var webViewActionHandler: (WKScriptMessage, WKScriptMessageReplyHandler?) -> Void = { _, _ in } var linkHandler: (URL) -> Void = { _ in } var needsReload = true var lastSavedAnnotationID: UUID? @@ -34,41 +36,17 @@ final class WebReaderCoordinator: NSObject { extension WebReaderCoordinator: WKScriptMessageHandler { func userContentController(_: WKUserContentController, didReceive message: WKScriptMessage) { - webViewActionHandler(message) + webViewActionHandler(message, nil) } } extension WebReaderCoordinator: WKScriptMessageHandlerWithReply { - func userContentController(_: WKUserContentController, - didReceive message: WKScriptMessage, - replyHandler: @escaping (Any?, String?) -> Void) - { - guard let messageBody = message.body as? [String: Any] else { return } - guard let actionID = messageBody["actionID"] as? String else { return } - - print("handling message", actionID, messageBody) - switch actionID { - case "deleteHighlight": - // TODO: make API call here, web expects a boolean result. - // we pass results back to JS as the `result` property. - - // We are just passing true as an example here. It should - // be false if the API has an error. - replyHandler(["result": true], nil) - -// TODO: -// case "createHighlight": -// break -// case "mergeHighlightMutation": -// break -// case "updateHighlightMutation": -// break -// case "articleReadingProgressMutation": -// break - - default: - replyHandler(nil, "Unknown actionID: \(actionID)") - } + func userContentController( + _: WKUserContentController, + didReceive message: WKScriptMessage, + replyHandler: @escaping (Any?, String?) -> Void + ) { + webViewActionHandler(message, replyHandler) } } diff --git a/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderViewModel.swift b/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderViewModel.swift new file mode 100644 index 000000000..d5d0a2717 --- /dev/null +++ b/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderViewModel.swift @@ -0,0 +1,165 @@ +import Combine +import Models +import Services +import SwiftUI +import WebKit + +struct SafariWebLink: Identifiable { + let id: UUID + let url: URL +} + +final class WebReaderViewModel: ObservableObject { + @Published var isLoading = false + @Published var articleContent: ArticleContent? + + var subscriptions = Set() + + func loadContent(dataService: DataService, slug: String) { + isLoading = true + + guard let viewer = dataService.currentViewer else { return } + + dataService.articleContentPublisher(username: viewer.username, slug: slug).sink( + receiveCompletion: { [weak self] completion in + guard case .failure = completion else { return } + self?.isLoading = false + }, + receiveValue: { [weak self] articleContent in + self?.articleContent = articleContent + } + ) + .store(in: &subscriptions) + } + + func createHighlight( + messageBody: [String: Any], + replyHandler: @escaping WKScriptMessageReplyHandler, + dataService: DataService + ) { + dataService.createHighlightPublisher( + shortId: messageBody["shortId"] as? String ?? "", + highlightID: messageBody["id"] as? String ?? "", + quote: messageBody["quote"] as? String ?? "", + patch: messageBody["patch"] as? String ?? "", + articleId: messageBody["articleId"] as? String ?? "" + ) + .sink { completion in + guard case .failure = completion else { return } + replyHandler(["result": false], nil) + } receiveValue: { _ in + replyHandler(["result": true], nil) + } + .store(in: &subscriptions) + } + + func deleteHighlight( + messageBody: [String: Any], + replyHandler: @escaping WKScriptMessageReplyHandler, + dataService: DataService + ) { + dataService.deleteHighlightPublisher( + highlightId: messageBody["highlightId"] as? String ?? "" + ) + .sink { completion in + guard case .failure = completion else { return } + replyHandler(["result": false], nil) + } receiveValue: { _ in + replyHandler(["result": true], nil) + } + .store(in: &subscriptions) + } + + func mergeHighlight( + messageBody: [String: Any], + replyHandler: @escaping WKScriptMessageReplyHandler, + dataService: DataService + ) { + dataService.mergeHighlightPublisher( + shortId: messageBody["shortId"] as? String ?? "", + highlightID: messageBody["id"] as? String ?? "", + quote: messageBody["quote"] as? String ?? "", + patch: messageBody["patch"] as? String ?? "", + articleId: messageBody["articleId"] as? String ?? "", + overlapHighlightIdList: messageBody["overlapHighlightIdList"] as? [String] ?? [] + ) + .sink { completion in + guard case .failure = completion else { return } + replyHandler(["result": false], nil) + } receiveValue: { _ in + replyHandler(["result": true], nil) + } + .store(in: &subscriptions) + } + + func updateHighlight( + messageBody: [String: Any], + replyHandler: @escaping WKScriptMessageReplyHandler, + dataService: DataService + ) { + dataService.updateHighlightAttributesPublisher( + highlightID: messageBody["highlightId"] as? String ?? "", + annotation: messageBody["annotation"] as? String ?? "", + sharedAt: nil + ) + .sink { completion in + guard case .failure = completion else { return } + replyHandler(["result": false], nil) + } receiveValue: { _ in + replyHandler(["result": true], nil) + } + .store(in: &subscriptions) + } + + func updateReadingProgress( + messageBody: [String: Any], + replyHandler: @escaping WKScriptMessageReplyHandler, + dataService: DataService + ) { + let itemID = messageBody["id"] as? String + let readingProgress = messageBody["readingProgressPercent"] as? Double + let anchorIndex = messageBody["readingProgressAnchorIndex"] as? Int + + guard let itemID = itemID, let readingProgress = readingProgress, let anchorIndex = anchorIndex else { + replyHandler(["result": false], nil) + return + } + + dataService.updateArticleReadingProgressPublisher( + itemID: itemID, + readingProgress: readingProgress, + anchorIndex: anchorIndex + ) + .sink { completion in + guard case .failure = completion else { return } + replyHandler(["result": false], nil) + } receiveValue: { _ in + replyHandler(["result": true], nil) + } + .store(in: &subscriptions) + } + + func webViewActionWithReplyHandler( + message: WKScriptMessage, + replyHandler: @escaping WKScriptMessageReplyHandler, + dataService: DataService + ) { + guard let messageBody = message.body as? [String: Any] else { return } + guard let actionID = messageBody["actionID"] as? String else { return } + + switch actionID { + case "deleteHighlight": + deleteHighlight(messageBody: messageBody, replyHandler: replyHandler, dataService: dataService) + case "createHighlight": + createHighlight(messageBody: messageBody, replyHandler: replyHandler, dataService: dataService) + case "mergeHighlight": + mergeHighlight(messageBody: messageBody, replyHandler: replyHandler, dataService: dataService) + case "updateHighlight": + updateHighlight(messageBody: messageBody, replyHandler: replyHandler, dataService: dataService) + case "articleReadingProgress": + updateReadingProgress(messageBody: messageBody, replyHandler: replyHandler, dataService: dataService) + default: + replyHandler(nil, "Unknown actionID: \(actionID)") + } + } +} diff --git a/apple/OmnivoreKit/Sources/Models/ArticleContent.swift b/apple/OmnivoreKit/Sources/Models/ArticleContent.swift index c3113a3c7..bc55cf570 100644 --- a/apple/OmnivoreKit/Sources/Models/ArticleContent.swift +++ b/apple/OmnivoreKit/Sources/Models/ArticleContent.swift @@ -10,8 +10,6 @@ public struct ArticleContent { ) { self.htmlContent = htmlContent self.highlights = highlights - - print(highlightsJSONString) } public var highlightsJSONString: String { diff --git a/apple/OmnivoreKit/Sources/Services/DataService/Mutations/CreateHighlight.swift b/apple/OmnivoreKit/Sources/Services/DataService/Mutations/CreateHighlight.swift index 94cee5d07..dbeffb2e2 100644 --- a/apple/OmnivoreKit/Sources/Services/DataService/Mutations/CreateHighlight.swift +++ b/apple/OmnivoreKit/Sources/Services/DataService/Mutations/CreateHighlight.swift @@ -9,7 +9,8 @@ public extension DataService { highlightID: String, quote: String, patch: String, - articleId: String + articleId: String, + annotation: String? = nil ) -> AnyPublisher { enum MutationResult { case saved(id: String) @@ -28,7 +29,12 @@ public extension DataService { let mutation = Selection.Mutation { try $0.createHighlight( input: InputObjects.CreateHighlightInput( - id: highlightID, shortId: shortId, articleId: articleId, patch: patch, quote: quote + id: highlightID, + shortId: shortId, + articleId: articleId, + patch: patch, + quote: quote, + annotation: OptionalArgument(annotation) ), selection: selection )