PopupView and Transimission can interfere with each other and cause an issue with the root screen becoming black and the app getting stuck.
239 lines
8.6 KiB
Swift
239 lines
8.6 KiB
Swift
import Models
|
|
import SwiftUI
|
|
import Utils
|
|
import Views
|
|
import WebKit
|
|
|
|
@MainActor
|
|
struct WebReader: PlatformViewRepresentable {
|
|
let item: Models.LibraryItem
|
|
let viewModel: WebReaderViewModel
|
|
let articleContent: ArticleContent
|
|
let openLinkAction: (URL) -> Void
|
|
let tapHandler: () -> Void
|
|
let scrollPercentHandler: (Int) -> Void
|
|
let webViewActionHandler: (WKScriptMessage, WKScriptMessageReplyHandler?) -> Void
|
|
let navBarVisibilityUpdater: (Bool) -> Void
|
|
|
|
@Binding var readerSettingsChangedTransactionID: UUID?
|
|
@Binding var annotationSaveTransactionID: UUID?
|
|
@Binding var showNavBarActionID: UUID?
|
|
@Binding var shareActionID: UUID?
|
|
@Binding var annotation: String
|
|
@Binding var showHighlightAnnotationModal: Bool
|
|
|
|
func makeCoordinator() -> WebReaderCoordinator {
|
|
WebReaderCoordinator()
|
|
}
|
|
|
|
func fontSize() -> Int {
|
|
let storedSize = UserDefaults.standard.integer(forKey: UserDefaultKey.preferredWebFontSize.rawValue)
|
|
#if os(iOS)
|
|
return storedSize <= 1 ? UITraitCollection.current.preferredWebFontSize : storedSize
|
|
#else
|
|
return storedSize <= 1 ? Int(NSFont.userFont(ofSize: 16)?.pointSize ?? 16) : storedSize
|
|
#endif
|
|
}
|
|
|
|
func lineHeight() -> Int {
|
|
let storedSize = UserDefaults.standard.integer(forKey: UserDefaultKey.preferredWebLineSpacing.rawValue)
|
|
return storedSize <= 1 ? 150 : storedSize
|
|
}
|
|
|
|
func maxWidthPercentage() -> Int {
|
|
let storedSize = UserDefaults.standard.integer(forKey: UserDefaultKey.preferredWebMaxWidthPercentage.rawValue)
|
|
return storedSize <= 1 ? 100 : storedSize
|
|
}
|
|
|
|
private func makePlatformView(context: Context) -> WKWebView {
|
|
let webView = WebViewManager.shared()
|
|
let contentController = WKUserContentController()
|
|
|
|
webView.tapHandler = tapHandler
|
|
webView.navigationDelegate = context.coordinator
|
|
webView.configuration.userContentController = contentController
|
|
webView.configuration.userContentController.removeAllScriptMessageHandlers()
|
|
|
|
#if os(iOS)
|
|
webView.isOpaque = false
|
|
webView.tintColor = UIColor(ThemeManager.currentHighlightColor)
|
|
webView.backgroundColor = UIColor(ThemeManager.currentBgColor)
|
|
webView.underPageBackgroundColor = UIColor(ThemeManager.currentBgColor)
|
|
webView.scrollView.backgroundColor = UIColor(ThemeManager.currentBgColor)
|
|
webView.scrollView.delegate = context.coordinator
|
|
webView.scrollView.contentInset.top = readerViewNavBarHeight
|
|
webView.scrollView.verticalScrollIndicatorInsets.top = readerViewNavBarHeight
|
|
webView.configuration.userContentController.add(webView, name: "viewerAction")
|
|
|
|
webView.scrollView.indicatorStyle = ThemeManager.currentTheme.isDark ?
|
|
UIScrollView.IndicatorStyle.white :
|
|
UIScrollView.IndicatorStyle.black
|
|
#else
|
|
webView.setValue(false, forKey: "drawsBackground")
|
|
#endif
|
|
|
|
#if DEBUG
|
|
if #available(iOS 16.4, *) {
|
|
webView.isInspectable = true
|
|
}
|
|
#endif
|
|
|
|
for action in WebViewAction.allCases {
|
|
webView.configuration.userContentController.add(context.coordinator, name: action.rawValue)
|
|
}
|
|
|
|
webView.configuration.userContentController.addScriptMessageHandler(
|
|
context.coordinator, contentWorld: .page, name: "articleAction"
|
|
)
|
|
|
|
context.coordinator.linkHandler = openLinkAction
|
|
context.coordinator.webViewActionHandler = webViewActionHandler
|
|
context.coordinator.updateNavBarVisibility = navBarVisibilityUpdater
|
|
context.coordinator.scrollPercentHandler = scrollPercentHandler
|
|
|
|
context.coordinator.articleContentID = articleContent.id
|
|
loadContent(webView: webView)
|
|
|
|
return webView
|
|
}
|
|
|
|
private func updatePlatformView(_ webView: WKWebView, context: Context) {
|
|
if annotationSaveTransactionID != context.coordinator.lastSavedAnnotationID {
|
|
context.coordinator.lastSavedAnnotationID = annotationSaveTransactionID
|
|
do {
|
|
try (webView as? OmnivoreWebView)?.dispatchEvent(.saveAnnotation(annotation: annotation))
|
|
} catch {
|
|
Snackbar.show(message: "Error saving note.", dismissAfter: 2000)
|
|
}
|
|
}
|
|
|
|
if readerSettingsChangedTransactionID != context.coordinator.previousReaderSettingsChangedUUID {
|
|
context.coordinator.previousReaderSettingsChangedUUID = readerSettingsChangedTransactionID
|
|
(webView as? OmnivoreWebView)?.updateTheme()
|
|
(webView as? OmnivoreWebView)?.updateFontFamily()
|
|
(webView as? OmnivoreWebView)?.updateFontSize()
|
|
(webView as? OmnivoreWebView)?.updateTextContrast()
|
|
(webView as? OmnivoreWebView)?.updateAutoHighlightMode()
|
|
(webView as? OmnivoreWebView)?.updateMaxWidthPercentage()
|
|
(webView as? OmnivoreWebView)?.updateLineHeight()
|
|
(webView as? OmnivoreWebView)?.updateLabels(labelsJSON: item.labelsJSONString)
|
|
(webView as? OmnivoreWebView)?.updateTitle(title: item.title ?? "")
|
|
(webView as? OmnivoreWebView)?.updateJustifyText()
|
|
|
|
#if os(iOS)
|
|
webView.backgroundColor = UIColor(ThemeManager.currentBgColor)
|
|
webView.tintColor = UIColor(ThemeManager.currentHighlightColor)
|
|
webView.underPageBackgroundColor = UIColor(ThemeManager.currentBgColor)
|
|
webView.scrollView.backgroundColor = UIColor(ThemeManager.currentBgColor)
|
|
webView.scrollView.indicatorStyle = ThemeManager.currentTheme.isDark ?
|
|
UIScrollView.IndicatorStyle.white :
|
|
UIScrollView.IndicatorStyle.black
|
|
#endif
|
|
}
|
|
|
|
if showNavBarActionID != context.coordinator.previousShowNavBarActionID {
|
|
context.coordinator.previousShowNavBarActionID = showNavBarActionID
|
|
context.coordinator.showNavBar()
|
|
}
|
|
|
|
if shareActionID != context.coordinator.previousShareActionID {
|
|
context.coordinator.previousShareActionID = shareActionID
|
|
(webView as? OmnivoreWebView)?.shareOriginalItem()
|
|
}
|
|
|
|
// If the webview had been terminated `needsReload` will have been set to true
|
|
// Or if the articleContent value has changed then it's id will be different from the coordinator's
|
|
if context.coordinator.needsReload || context.coordinator.articleContentID != articleContent.id {
|
|
loadContent(webView: webView)
|
|
context.coordinator.articleContentID = articleContent.id
|
|
context.coordinator.needsReload = false
|
|
return
|
|
}
|
|
|
|
if webView.isLoading { return }
|
|
|
|
// If the root element is not detected then `WKWebView` may have unloaded the content
|
|
// so we need to load it again.
|
|
webView.evaluateJavaScript("document.getElementById('root') ? true : false") { hasRootElement, _ in
|
|
guard let hasRootElement = hasRootElement as? Bool else { return }
|
|
|
|
if !hasRootElement {
|
|
DispatchQueue.main.async {
|
|
loadContent(webView: webView)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private func loadContent(webView: WKWebView) {
|
|
let fontFamilyValue = UserDefaults.standard.string(forKey: UserDefaultKey.preferredWebFont.rawValue)
|
|
|
|
let prefersHighContrastText: Bool = {
|
|
let key = UserDefaultKey.prefersHighContrastWebFont.rawValue
|
|
if UserDefaults.standard.object(forKey: key) != nil {
|
|
return UserDefaults.standard.bool(forKey: key)
|
|
} else {
|
|
return true
|
|
}
|
|
}()
|
|
|
|
let enableHighlightOnRelease: Bool = {
|
|
let key = UserDefaultKey.enableHighlightOnRelease.rawValue
|
|
if UserDefaults.standard.object(forKey: key) != nil {
|
|
return UserDefaults.standard.bool(forKey: key)
|
|
} else {
|
|
return false
|
|
}
|
|
}()
|
|
|
|
let justifyText: Bool = {
|
|
let key = UserDefaultKey.justifyText.rawValue
|
|
if UserDefaults.standard.object(forKey: key) != nil {
|
|
return UserDefaults.standard.bool(forKey: key)
|
|
} else {
|
|
return false
|
|
}
|
|
}()
|
|
|
|
let fontFamily = fontFamilyValue.flatMap { WebFont(rawValue: $0) } ?? .atkinsonHyperlegible
|
|
|
|
let htmlString = WebReaderContent(
|
|
item: item,
|
|
articleContent: articleContent,
|
|
isDark: Color.isDarkMode,
|
|
fontSize: fontSize(),
|
|
lineHeight: lineHeight(),
|
|
maxWidthPercentage: maxWidthPercentage(),
|
|
fontFamily: fontFamily,
|
|
prefersHighContrastText: prefersHighContrastText,
|
|
enableHighlightOnRelease: enableHighlightOnRelease,
|
|
justifyText: justifyText
|
|
)
|
|
.styledContent
|
|
|
|
webView.loadHTMLString(htmlString, baseURL: ViewsPackage.resourceURL)
|
|
}
|
|
}
|
|
|
|
#if os(iOS)
|
|
extension WebReader {
|
|
func makeUIView(context: Context) -> WKWebView {
|
|
makePlatformView(context: context)
|
|
}
|
|
|
|
func updateUIView(_ webView: WKWebView, context: Context) {
|
|
updatePlatformView(webView, context: context)
|
|
}
|
|
}
|
|
#else
|
|
extension WebReader {
|
|
func makeNSView(context: Context) -> WKWebView {
|
|
makePlatformView(context: context)
|
|
}
|
|
|
|
func updateNSView(_ webView: WKWebView, context: Context) {
|
|
updatePlatformView(webView, context: context)
|
|
}
|
|
}
|
|
#endif
|