Move a bunch of code from DetailViewController to DetailWebViewController, where it belongs.
This commit is contained in:
parent
2f8bee998e
commit
8df96613c4
|
@ -12,140 +12,60 @@ import RSCore
|
|||
import Articles
|
||||
import RSWeb
|
||||
|
||||
enum DetailState: Equatable {
|
||||
case noSelection
|
||||
case multipleSelection
|
||||
case article(Article)
|
||||
}
|
||||
|
||||
final class DetailViewController: NSViewController, WKUIDelegate {
|
||||
|
||||
@IBOutlet var containerView: DetailContainerView!
|
||||
@IBOutlet var statusBarView: DetailStatusBarView!
|
||||
|
||||
var webview: DetailWebView!
|
||||
|
||||
var articles: [Article]? {
|
||||
enum WebViewType {
|
||||
case regular, search
|
||||
}
|
||||
|
||||
lazy var regularWebViewController = {
|
||||
return createWebViewController()
|
||||
}()
|
||||
|
||||
lazy var searchWebViewController = {
|
||||
return createWebViewController()
|
||||
}()
|
||||
|
||||
var currentWebViewController: DetailWebViewController! {
|
||||
didSet {
|
||||
if articles == oldValue {
|
||||
let webview = currentWebViewController.view
|
||||
if containerView.contentView === webview {
|
||||
return
|
||||
}
|
||||
statusBarView.mouseoverLink = nil
|
||||
reloadHTML()
|
||||
containerView.contentView = webview
|
||||
}
|
||||
}
|
||||
|
||||
private var article: Article? {
|
||||
return articles?.first
|
||||
}
|
||||
|
||||
private var webviewIsHidden: Bool {
|
||||
return containerView.contentView !== webview
|
||||
}
|
||||
|
||||
override func viewDidLoad() {
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(timelineSelectionDidChange(_:)), name: .TimelineSelectionDidChange, object: nil)
|
||||
|
||||
let preferences = WKPreferences()
|
||||
preferences.minimumFontSize = 12.0
|
||||
preferences.javaScriptCanOpenWindowsAutomatically = false
|
||||
preferences.javaEnabled = false
|
||||
preferences.javaScriptEnabled = true
|
||||
preferences.plugInsEnabled = false
|
||||
|
||||
let configuration = WKWebViewConfiguration()
|
||||
configuration.preferences = preferences
|
||||
|
||||
let userContentController = WKUserContentController()
|
||||
userContentController.add(self, name: MessageName.mouseDidEnter)
|
||||
userContentController.add(self, name: MessageName.mouseDidExit)
|
||||
configuration.userContentController = userContentController
|
||||
|
||||
webview = DetailWebView(frame: self.view.bounds, configuration: configuration)
|
||||
webview.uiDelegate = self
|
||||
webview.navigationDelegate = self
|
||||
webview.translatesAutoresizingMaskIntoConstraints = false
|
||||
if let userAgent = UserAgent.fromInfoPlist() {
|
||||
webview.customUserAgent = userAgent
|
||||
}
|
||||
|
||||
reloadHTML()
|
||||
containerView.contentView = webview
|
||||
containerView.viewController = self
|
||||
currentWebViewController = regularWebViewController
|
||||
}
|
||||
|
||||
private struct MessageName {
|
||||
static let mouseDidEnter = "mouseDidEnter"
|
||||
static let mouseDidExit = "mouseDidExit"
|
||||
}
|
||||
// MARK: - API
|
||||
|
||||
// MARK: Scrolling
|
||||
func showState(_ state: DetailState, in webViewType: WebViewType) {
|
||||
// TODO: also to-do is caller
|
||||
}
|
||||
|
||||
func canScrollDown(_ callback: @escaping (Bool) -> Void) {
|
||||
if webviewIsHidden {
|
||||
callback(false)
|
||||
return
|
||||
}
|
||||
|
||||
fetchScrollInfo { (scrollInfo) in
|
||||
callback(scrollInfo?.canScrollDown ?? false)
|
||||
}
|
||||
currentWebViewController.canScrollDown(callback)
|
||||
}
|
||||
|
||||
override func scrollPageDown(_ sender: Any?) {
|
||||
|
||||
guard !webviewIsHidden else {
|
||||
return
|
||||
}
|
||||
webview.scrollPageDown(sender)
|
||||
}
|
||||
|
||||
// MARK: Notifications
|
||||
|
||||
@objc func timelineSelectionDidChange(_ notification: Notification) {
|
||||
|
||||
guard let userInfo = notification.userInfo else {
|
||||
return
|
||||
}
|
||||
guard let timelineView = userInfo[UserInfoKey.view] as? NSView, timelineView.window === view.window else {
|
||||
return
|
||||
}
|
||||
|
||||
let timelineArticles = userInfo[UserInfoKey.articles] as? ArticleArray
|
||||
articles = timelineArticles
|
||||
currentWebViewController.scrollPageDown(sender)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: WKNavigationDelegate
|
||||
|
||||
extension DetailViewController: WKNavigationDelegate {
|
||||
|
||||
public func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
|
||||
|
||||
if navigationAction.navigationType == .linkActivated {
|
||||
|
||||
if let url = navigationAction.request.url {
|
||||
Browser.open(url.absoluteString)
|
||||
}
|
||||
|
||||
decisionHandler(.cancel)
|
||||
return
|
||||
}
|
||||
|
||||
decisionHandler(.allow)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: WKScriptMessageHandler
|
||||
|
||||
extension DetailViewController: WKScriptMessageHandler {
|
||||
|
||||
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
|
||||
|
||||
if message.name == MessageName.mouseDidEnter, let link = message.body as? String {
|
||||
mouseDidEnter(link)
|
||||
}
|
||||
else if message.name == MessageName.mouseDidExit, let link = message.body as? String{
|
||||
mouseDidExit(link)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: DetailWebViewControllerDelegate
|
||||
// MARK: - DetailWebViewControllerDelegate
|
||||
|
||||
extension DetailViewController: DetailWebViewControllerDelegate {
|
||||
|
||||
|
@ -161,97 +81,14 @@ extension DetailViewController: DetailWebViewControllerDelegate {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: Private
|
||||
// MARK: - Private
|
||||
|
||||
private extension DetailViewController {
|
||||
|
||||
func reloadHTML() {
|
||||
let html: String
|
||||
let style = ArticleStylesManager.shared.currentStyle
|
||||
let appearance = self.view.effectiveAppearance
|
||||
var baseURL: URL? = nil
|
||||
|
||||
if let articles = articles, articles.count > 1 {
|
||||
html = ArticleRenderer.multipleSelectionHTML(style: style, appearance: appearance)
|
||||
}
|
||||
else if let article = article {
|
||||
html = ArticleRenderer.articleHTML(article: article, style: style, appearance: appearance)
|
||||
baseURL = ArticleRenderer.baseURL(for: article)
|
||||
}
|
||||
else {
|
||||
html = ArticleRenderer.noSelectionHTML(style: style, appearance: appearance)
|
||||
}
|
||||
|
||||
webview.loadHTMLString(html, baseURL: baseURL)
|
||||
}
|
||||
|
||||
func fetchScrollInfo(_ callback: @escaping (ScrollInfo?) -> Void) {
|
||||
let javascriptString = "var x = {contentHeight: document.body.scrollHeight, offsetY: document.body.scrollTop}; x"
|
||||
webview.evaluateJavaScript(javascriptString) { (info, error) in
|
||||
|
||||
guard let info = info as? [String: Any] else {
|
||||
callback(nil)
|
||||
return
|
||||
}
|
||||
guard let contentHeight = info["contentHeight"] as? CGFloat, let offsetY = info["offsetY"] as? CGFloat else {
|
||||
callback(nil)
|
||||
return
|
||||
}
|
||||
|
||||
let scrollInfo = ScrollInfo(contentHeight: contentHeight, viewHeight: self.webview.frame.height, offsetY: offsetY)
|
||||
callback(scrollInfo)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - DetailContainerView
|
||||
|
||||
final class DetailContainerView: NSView {
|
||||
|
||||
@IBOutlet var detailStatusBarView: DetailStatusBarView!
|
||||
|
||||
weak var viewController: DetailViewController? = nil
|
||||
|
||||
override var isOpaque: Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
var contentView: NSView? {
|
||||
didSet {
|
||||
if contentView == oldValue {
|
||||
return
|
||||
}
|
||||
oldValue?.removeFromSuperviewWithoutNeedingDisplay()
|
||||
if let contentView = contentView {
|
||||
contentView.translatesAutoresizingMaskIntoConstraints = false
|
||||
addSubview(contentView, positioned: .below, relativeTo: detailStatusBarView)
|
||||
rs_addFullSizeConstraints(forSubview: contentView)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override func draw(_ dirtyRect: NSRect) {
|
||||
NSColor.textBackgroundColor.setFill()
|
||||
dirtyRect.fill()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - ScrollInfo
|
||||
|
||||
private struct ScrollInfo {
|
||||
|
||||
let contentHeight: CGFloat
|
||||
let viewHeight: CGFloat
|
||||
let offsetY: CGFloat
|
||||
let canScrollDown: Bool
|
||||
let canScrollUp: Bool
|
||||
|
||||
init(contentHeight: CGFloat, viewHeight: CGFloat, offsetY: CGFloat) {
|
||||
self.contentHeight = contentHeight
|
||||
self.viewHeight = viewHeight
|
||||
self.offsetY = offsetY
|
||||
|
||||
self.canScrollDown = viewHeight + offsetY < contentHeight
|
||||
self.canScrollUp = offsetY > 0.1
|
||||
func createWebViewController() -> DetailWebViewController {
|
||||
let controller = DetailWebViewController()
|
||||
controller.delegate = self
|
||||
controller.state = .noSelection
|
||||
return controller
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,12 +11,6 @@ import WebKit
|
|||
import RSWeb
|
||||
import Articles
|
||||
|
||||
enum DetailWebViewState: Equatable {
|
||||
case noSelection
|
||||
case multipleSelection
|
||||
case article(Article)
|
||||
}
|
||||
|
||||
protocol DetailWebViewControllerDelegate: class {
|
||||
func mouseDidEnter(_ link: String)
|
||||
func mouseDidExit(_ link: String)
|
||||
|
@ -26,7 +20,7 @@ final class DetailWebViewController: NSViewController, WKUIDelegate {
|
|||
|
||||
weak var delegate: DetailWebViewControllerDelegate?
|
||||
var webview: DetailWebView!
|
||||
var state: DetailWebViewState = .noSelection {
|
||||
var state: DetailState = .noSelection {
|
||||
didSet {
|
||||
if state != oldValue {
|
||||
reloadHTML()
|
||||
|
@ -34,6 +28,11 @@ final class DetailWebViewController: NSViewController, WKUIDelegate {
|
|||
}
|
||||
}
|
||||
|
||||
private struct MessageName {
|
||||
static let mouseDidEnter = "mouseDidEnter"
|
||||
static let mouseDidExit = "mouseDidExit"
|
||||
}
|
||||
|
||||
override func loadView() {
|
||||
let preferences = WKPreferences()
|
||||
preferences.minimumFontSize = 12.0
|
||||
|
@ -57,12 +56,26 @@ final class DetailWebViewController: NSViewController, WKUIDelegate {
|
|||
if let userAgent = UserAgent.fromInfoPlist() {
|
||||
webview.customUserAgent = userAgent
|
||||
}
|
||||
|
||||
view = webview
|
||||
|
||||
DispatchQueue.main.async {
|
||||
// Must do this async, because reloadHTML references view.effectiveAppearance,
|
||||
// which causes loadView to get called. Infinite loop.
|
||||
self.reloadHTML()
|
||||
}
|
||||
}
|
||||
|
||||
private struct MessageName {
|
||||
static let mouseDidEnter = "mouseDidEnter"
|
||||
static let mouseDidExit = "mouseDidExit"
|
||||
// MARK: Scrolling
|
||||
|
||||
func canScrollDown(_ callback: @escaping (Bool) -> Void) {
|
||||
fetchScrollInfo { (scrollInfo) in
|
||||
callback(scrollInfo?.canScrollDown ?? false)
|
||||
}
|
||||
}
|
||||
|
||||
override func scrollPageDown(_ sender: Any?) {
|
||||
webview.scrollPageDown(sender)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -103,7 +116,7 @@ private extension DetailWebViewController {
|
|||
|
||||
func reloadHTML() {
|
||||
let style = ArticleStylesManager.shared.currentStyle
|
||||
let appearance = self.view.effectiveAppearance
|
||||
let appearance = view.effectiveAppearance
|
||||
let html: String
|
||||
var baseURL: URL? = nil
|
||||
|
||||
|
@ -119,8 +132,28 @@ private extension DetailWebViewController {
|
|||
|
||||
webview.loadHTMLString(html, baseURL: baseURL)
|
||||
}
|
||||
|
||||
func fetchScrollInfo(_ callback: @escaping (ScrollInfo?) -> Void) {
|
||||
let javascriptString = "var x = {contentHeight: document.body.scrollHeight, offsetY: document.body.scrollTop}; x"
|
||||
|
||||
webview.evaluateJavaScript(javascriptString) { (info, error) in
|
||||
guard let info = info as? [String: Any] else {
|
||||
callback(nil)
|
||||
return
|
||||
}
|
||||
guard let contentHeight = info["contentHeight"] as? CGFloat, let offsetY = info["offsetY"] as? CGFloat else {
|
||||
callback(nil)
|
||||
return
|
||||
}
|
||||
|
||||
let scrollInfo = ScrollInfo(contentHeight: contentHeight, viewHeight: self.webview.frame.height, offsetY: offsetY)
|
||||
callback(scrollInfo)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Article extension
|
||||
|
||||
private extension Article {
|
||||
|
||||
var baseURL: URL? {
|
||||
|
@ -148,3 +181,24 @@ private extension Article {
|
|||
return url
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - ScrollInfo
|
||||
|
||||
private struct ScrollInfo {
|
||||
|
||||
let contentHeight: CGFloat
|
||||
let viewHeight: CGFloat
|
||||
let offsetY: CGFloat
|
||||
let canScrollDown: Bool
|
||||
let canScrollUp: Bool
|
||||
|
||||
init(contentHeight: CGFloat, viewHeight: CGFloat, offsetY: CGFloat) {
|
||||
self.contentHeight = contentHeight
|
||||
self.viewHeight = viewHeight
|
||||
self.offsetY = offsetY
|
||||
|
||||
self.canScrollDown = viewHeight + offsetY < contentHeight
|
||||
self.canScrollUp = offsetY > 0.1
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue