216 lines
7.9 KiB
Swift
216 lines
7.9 KiB
Swift
import Models
|
|
import SwiftUI
|
|
import Utils
|
|
import Views
|
|
import WebKit
|
|
|
|
struct WebReader: PlatformViewRepresentable {
|
|
let item: LinkedItem
|
|
let articleContent: ArticleContent
|
|
let openLinkAction: (URL) -> Void
|
|
let tapHandler: () -> Void
|
|
let scrollPercentHandler: (Int) -> Void
|
|
let webViewActionHandler: (WKScriptMessage, WKScriptMessageReplyHandler?) -> Void
|
|
let navBarVisibilityRatioUpdater: (Double) -> Void
|
|
|
|
@Binding var readerSettingsChangedTransactionID: UUID?
|
|
@Binding var annotationSaveTransactionID: UUID?
|
|
@Binding var showNavBarActionID: UUID?
|
|
@Binding var shareActionID: UUID?
|
|
@Binding var annotation: String
|
|
@Binding var showBottomBar: Bool
|
|
@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.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
|
|
|
|
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.updateNavBarVisibilityRatio = navBarVisibilityRatioUpdater
|
|
context.coordinator.scrollPercentHandler = scrollPercentHandler
|
|
context.coordinator.updateShowBottomBar = { newValue in
|
|
self.showBottomBar = newValue
|
|
}
|
|
|
|
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 {
|
|
showInSnackbar("Error saving note.")
|
|
}
|
|
}
|
|
|
|
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()
|
|
webView.scrollView.indicatorStyle = ThemeManager.currentTheme.isDark ?
|
|
UIScrollView.IndicatorStyle.white :
|
|
UIScrollView.IndicatorStyle.black
|
|
}
|
|
|
|
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 fontFamily = fontFamilyValue.flatMap { WebFont(rawValue: $0) } ?? .system
|
|
|
|
let htmlString = WebReaderContent(
|
|
item: item,
|
|
articleContent: articleContent,
|
|
isDark: Color.isDarkMode,
|
|
fontSize: fontSize(),
|
|
lineHeight: lineHeight(),
|
|
maxWidthPercentage: maxWidthPercentage(),
|
|
fontFamily: fontFamily,
|
|
prefersHighContrastText: prefersHighContrastText,
|
|
enableHighlightOnRelease: enableHighlightOnRelease
|
|
)
|
|
.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
|