Files
omnivore/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderViewModel.swift
Jackson Harper 03766734e5 Pull out popup view and replace with Transmission snackbars
PopupView and Transimission can interfere with each other and
cause an issue with the root screen becoming black and the app
getting stuck.
2024-02-06 11:43:43 +08:00

233 lines
8.0 KiB
Swift

import Models
import Services
import SwiftUI
import Views
import WebKit
struct SafariWebLink: Identifiable {
let id: UUID
let url: URL
}
@MainActor final class WebReaderViewModel: ObservableObject {
@Published var articleContent: ArticleContent?
@Published var errorMessage: String?
@Published var allowRetry = false
@Published var isDownloadingAudio: Bool = false
@Published var audioDownloadTask: Task<Void, Error>?
@Published var operationMessage: String?
@Published var showOperationToast: Bool = false
@Published var operationStatus: OperationStatus = .none
func hasOriginalUrl(_ item: Models.LibraryItem) -> Bool {
if let pageURLString = item.pageURLString, let host = URL(string: pageURLString)?.host {
if host == "omnivore.app" {
return false
}
return true
}
return false
}
func downloadAudio(audioController: AudioController, item: Models.LibraryItem) {
Snackbar.show(message: "Downloading Offline Audio", dismissAfter: 2000)
isDownloadingAudio = true
if let audioDownloadTask = audioDownloadTask {
audioDownloadTask.cancel()
}
let itemID = item.unwrappedID
audioDownloadTask = Task.detached(priority: .background) {
let canceled = Task.isCancelled
let downloaded = await audioController.downloadForOffline(itemID: itemID)
DispatchQueue.main.async {
self.isDownloadingAudio = false
if !canceled {
Snackbar.show(message: downloaded ? "Audio file downloaded" : "Error downloading audio", dismissAfter: 2000)
}
}
}
}
func loadContent(dataService: DataService, username: String, itemID: String, retryCount _: Int = 0) async {
errorMessage = nil
do {
articleContent = try await dataService.loadArticleContentWithRetries(itemID: itemID, username: username)
} catch {
if let fetchError = error as? ContentFetchError {
allowRetry = true
switch fetchError {
case .network:
errorMessage = "We were unable to retrieve your content. Please check network connectivity and try again."
default:
errorMessage = "We were unable to parse your content."
}
} else {
errorMessage = "We were unable to retrieve your content."
}
}
}
func createHighlight(
messageBody: [String: Any],
replyHandler: @escaping WKScriptMessageReplyHandler,
dataService: DataService
) {
let result = dataService.createHighlight(
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 ?? "",
positionPercent: messageBody["highlightPositionPercent"] as? Double,
positionAnchorIndex: messageBody["highlightPositionAnchorIndex"] as? Int,
annotation: messageBody["annotation"] as? String ?? ""
)
return replyHandler(["result": result], nil)
}
func deleteHighlight(
messageBody: [String: Any],
replyHandler: @escaping WKScriptMessageReplyHandler,
dataService: DataService
) {
if let highlightID = messageBody["highlightId"] as? String {
dataService.deleteHighlight(highlightID: highlightID)
replyHandler(["result": true], nil)
} else {
replyHandler(["result": false], nil)
}
}
func mergeHighlight(
messageBody: [String: Any],
replyHandler: @escaping WKScriptMessageReplyHandler,
dataService: DataService
) {
guard
let shortId = messageBody["shortId"] as? String,
let highlightID = messageBody["id"] as? String,
let quote = messageBody["quote"] as? String,
let patch = messageBody["patch"] as? String,
let articleId = messageBody["articleId"] as? String,
let overlapHighlightIdList = messageBody["overlapHighlightIdList"] as? [String],
let positionPercent = messageBody["highlightPositionPercent"] as? Double,
let positionAnchorIndex = messageBody["highlightPositionAnchorIndex"] as? Int
else {
replyHandler([], "createHighlight: Error encoding response")
return
}
let jsonHighlight = dataService.mergeHighlights(
shortId: shortId,
highlightID: highlightID,
quote: quote,
patch: patch,
articleId: articleId,
positionPercent: positionPercent,
positionAnchorIndex: positionAnchorIndex,
overlapHighlightIdList: overlapHighlightIdList
)
replyHandler(["result": jsonHighlight], nil)
}
func updateHighlight(
messageBody: [String: Any],
replyHandler: @escaping WKScriptMessageReplyHandler,
dataService: DataService
) {
let highlightID = messageBody["highlightId"] as? String
let annotation = messageBody["annotation"] as? String
if let highlightID = highlightID, let annotation = annotation {
dataService.updateHighlightAttributes(highlightID: highlightID, annotation: annotation)
replyHandler(["result": highlightID], nil)
} else {
replyHandler([], "updateHighlight: Error encoding response")
}
}
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
print("READING PROGRESS FROM JS: ", messageBody)
guard let itemID = itemID, let readingProgress = readingProgress, let anchorIndex = anchorIndex else {
replyHandler(["result": false], nil)
return
}
dataService.updateLinkReadingProgress(itemID: itemID, readingProgress: readingProgress, anchorIndex: anchorIndex, force: false)
replyHandler(["result": true], nil)
}
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)")
}
}
func setLabelsForHighlight(
highlightID: String,
labelIDs: [String],
dataService: DataService
) {
dataService.setLabelsForHighlight(highlightID: highlightID, labelIDs: labelIDs)
}
func saveLink(dataService: DataService, url: URL) {
Task {
do {
Snackbar.show(message: "Saving link", dismissAfter: 5000)
_ = try await dataService.createPageFromUrl(id: UUID().uuidString, url: url.absoluteString)
Snackbar.show(message: "Link saved", dismissAfter: 2000)
} catch {
Snackbar.show(message: "Error saving link", dismissAfter: 2000)
}
}
}
func saveLinkAndFetch(dataService: DataService, username: String, url: URL) {
Task {
do {
Snackbar.show(message: "Saving link", dismissAfter: 5000)
let requestId = UUID().uuidString
_ = try await dataService.createPageFromUrl(id: requestId, url: url.absoluteString)
Snackbar.show(message: "Link saved", dismissAfter: 2000)
await loadContent(dataService: dataService, username: username, itemID: requestId, retryCount: 0)
} catch {
Snackbar.show(message: "Error saving link", dismissAfter: 2000)
}
}
}
}