mirror of
https://github.com/omnivore-app/omnivore.git
synced 2026-03-11 08:54:26 +00:00
handle wkmessages and send replies to webview
This commit is contained in:
parent
7b6c381ce2
commit
3b907d415f
7 changed files with 204 additions and 70 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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<AnyCancellable>()
|
||||
|
||||
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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<AnyCancellable>()
|
||||
|
||||
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)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -10,8 +10,6 @@ public struct ArticleContent {
|
|||
) {
|
||||
self.htmlContent = htmlContent
|
||||
self.highlights = highlights
|
||||
|
||||
print(highlightsJSONString)
|
||||
}
|
||||
|
||||
public var highlightsJSONString: String {
|
||||
|
|
|
|||
|
|
@ -9,7 +9,8 @@ public extension DataService {
|
|||
highlightID: String,
|
||||
quote: String,
|
||||
patch: String,
|
||||
articleId: String
|
||||
articleId: String,
|
||||
annotation: String? = nil
|
||||
) -> AnyPublisher<String, BasicError> {
|
||||
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
|
||||
)
|
||||
|
|
|
|||
Loading…
Reference in a new issue