mirror of
https://github.com/Ranchero-Software/NetNewsWire.git
synced 2025-02-08 08:08:43 +01:00
commit
1f7d40a537
@ -202,12 +202,7 @@ class ArticleViewController: UIViewController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@objc func contentSizeCategoryDidChange(_ note: Notification) {
|
@objc func contentSizeCategoryDidChange(_ note: Notification) {
|
||||||
coordinator.webViewProvider.flushQueue()
|
resetWebViewController()
|
||||||
coordinator.webViewProvider.replenishQueueIfNeeded()
|
|
||||||
if let controller = currentWebViewController {
|
|
||||||
controller.fullReload()
|
|
||||||
self.pageViewController.setViewControllers([controller], direction: .forward, animated: false, completion: nil)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func willEnterForeground(_ note: Notification) {
|
@objc func willEnterForeground(_ note: Notification) {
|
||||||
@ -215,6 +210,7 @@ class ArticleViewController: UIViewController {
|
|||||||
if AppDefaults.articleFullscreenEnabled {
|
if AppDefaults.articleFullscreenEnabled {
|
||||||
currentWebViewController?.hideBars()
|
currentWebViewController?.hideBars()
|
||||||
}
|
}
|
||||||
|
resetWebViewController()
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Actions
|
// MARK: Actions
|
||||||
@ -274,10 +270,6 @@ class ArticleViewController: UIViewController {
|
|||||||
currentWebViewController?.scrollPageDown()
|
currentWebViewController?.scrollPageDown()
|
||||||
}
|
}
|
||||||
|
|
||||||
func fullReload() {
|
|
||||||
currentWebViewController?.fullReload()
|
|
||||||
}
|
|
||||||
|
|
||||||
func stopArticleExtractorIfProcessing() {
|
func stopArticleExtractorIfProcessing() {
|
||||||
currentWebViewController?.stopArticleExtractorIfProcessing()
|
currentWebViewController?.stopArticleExtractorIfProcessing()
|
||||||
}
|
}
|
||||||
@ -366,4 +358,13 @@ private extension ArticleViewController {
|
|||||||
return controller
|
return controller
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func resetWebViewController() {
|
||||||
|
coordinator.webViewProvider.flushQueue()
|
||||||
|
coordinator.webViewProvider.replenishQueueIfNeeded()
|
||||||
|
if let controller = currentWebViewController {
|
||||||
|
controller.fullReload()
|
||||||
|
self.pageViewController.setViewControllers([controller], direction: .forward, animated: false, completion: nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -148,7 +148,7 @@ class WebViewController: UIViewController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func fullReload() {
|
func fullReload() {
|
||||||
self.loadWebView()
|
loadWebView(replaceExistingWebView: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func showBars() {
|
func showBars() {
|
||||||
@ -288,10 +288,16 @@ extension WebViewController: UIContextMenuInteractionDelegate {
|
|||||||
// MARK: WKNavigationDelegate
|
// MARK: WKNavigationDelegate
|
||||||
|
|
||||||
extension WebViewController: WKNavigationDelegate {
|
extension WebViewController: WKNavigationDelegate {
|
||||||
|
|
||||||
|
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
|
||||||
|
if view.subviews.count > 1 {
|
||||||
|
view.subviews.last?.removeFromSuperview()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
|
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
|
||||||
|
|
||||||
if navigationAction.navigationType == .linkActivated {
|
if navigationAction.navigationType == .linkActivated {
|
||||||
|
|
||||||
guard let url = navigationAction.request.url else {
|
guard let url = navigationAction.request.url else {
|
||||||
decisionHandler(.allow)
|
decisionHandler(.allow)
|
||||||
return
|
return
|
||||||
@ -313,13 +319,13 @@ extension WebViewController: WKNavigationDelegate {
|
|||||||
} else {
|
} else {
|
||||||
decisionHandler(.allow)
|
decisionHandler(.allow)
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
decisionHandler(.allow)
|
decisionHandler(.allow)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func webViewWebContentProcessDidTerminate(_ webView: WKWebView) {
|
||||||
|
fullReload()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -402,10 +408,10 @@ private struct ImageClickMessage: Codable {
|
|||||||
|
|
||||||
private extension WebViewController {
|
private extension WebViewController {
|
||||||
|
|
||||||
func loadWebView() {
|
func loadWebView(replaceExistingWebView: Bool = false) {
|
||||||
guard isViewLoaded else { return }
|
guard isViewLoaded else { return }
|
||||||
|
|
||||||
if let webView = webView {
|
if !replaceExistingWebView, let webView = webView {
|
||||||
self.renderPage(webView)
|
self.renderPage(webView)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -7,42 +7,116 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
import RSCore
|
||||||
import WebKit
|
import WebKit
|
||||||
|
|
||||||
/// WKWebView has an awful behavior of a flash to white on first load when in dark mode.
|
/// WKWebView has an awful behavior of a flash to white on first load when in dark mode.
|
||||||
/// Keep a queue of WebViews where we've already done a trivial load so that by the time we need them in the UI, they're past the flash-to-shite part of their lifecycle.
|
/// Keep a queue of WebViews where we've already done a trivial load so that by the time we need them in the UI, they're past the flash-to-shite part of their lifecycle.
|
||||||
class WebViewProvider: NSObject {
|
class WebViewProvider: NSObject {
|
||||||
|
|
||||||
let articleIconSchemeHandler: ArticleIconSchemeHandler
|
private let articleIconSchemeHandler: ArticleIconSchemeHandler
|
||||||
|
private let operationQueue = MainThreadOperationQueue()
|
||||||
private let minimumQueueDepth = 3
|
|
||||||
private let maximumQueueDepth = 6
|
|
||||||
private var queue = UIView()
|
private var queue = UIView()
|
||||||
|
|
||||||
init(coordinator: SceneCoordinator, viewController: UIViewController) {
|
init(coordinator: SceneCoordinator, viewController: UIViewController) {
|
||||||
articleIconSchemeHandler = ArticleIconSchemeHandler(coordinator: coordinator)
|
articleIconSchemeHandler = ArticleIconSchemeHandler(coordinator: coordinator)
|
||||||
super.init()
|
super.init()
|
||||||
viewController.view.insertSubview(queue, at: 0)
|
viewController.view.insertSubview(queue, at: 0)
|
||||||
|
|
||||||
replenishQueueIfNeeded()
|
replenishQueueIfNeeded()
|
||||||
}
|
}
|
||||||
|
|
||||||
func flushQueue() {
|
func flushQueue() {
|
||||||
queue.subviews.forEach { $0.removeFromSuperview() }
|
operationQueue.add(WebViewProviderFlushQueueOperation(queue: queue))
|
||||||
}
|
}
|
||||||
|
|
||||||
func replenishQueueIfNeeded() {
|
func replenishQueueIfNeeded() {
|
||||||
while queue.subviews.count < minimumQueueDepth {
|
operationQueue.add(WebViewProviderReplenishQueueOperation(queue: queue, articleIconSchemeHandler: articleIconSchemeHandler))
|
||||||
enqueueWebView(PreloadedWebView(articleIconSchemeHandler: articleIconSchemeHandler))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func dequeueWebView(completion: @escaping (PreloadedWebView) -> ()) {
|
func dequeueWebView(completion: @escaping (PreloadedWebView) -> ()) {
|
||||||
|
operationQueue.add(WebViewProviderDequeueOperation(queue: queue, articleIconSchemeHandler: articleIconSchemeHandler, completion: completion))
|
||||||
|
operationQueue.add(WebViewProviderReplenishQueueOperation(queue: queue, articleIconSchemeHandler: articleIconSchemeHandler))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class WebViewProviderFlushQueueOperation: MainThreadOperation {
|
||||||
|
|
||||||
|
// MainThreadOperation
|
||||||
|
public var isCanceled = false
|
||||||
|
public var id: Int?
|
||||||
|
public weak var operationDelegate: MainThreadOperationDelegate?
|
||||||
|
public var name: String? = "WebViewProviderFlushQueueOperation"
|
||||||
|
public var completionBlock: MainThreadOperation.MainThreadOperationCompletionBlock?
|
||||||
|
|
||||||
|
private var queue: UIView
|
||||||
|
|
||||||
|
init(queue: UIView) {
|
||||||
|
self.queue = queue
|
||||||
|
}
|
||||||
|
|
||||||
|
func run() {
|
||||||
|
queue.subviews.forEach { $0.removeFromSuperview() }
|
||||||
|
self.operationDelegate?.operationDidComplete(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class WebViewProviderReplenishQueueOperation: MainThreadOperation {
|
||||||
|
|
||||||
|
// MainThreadOperation
|
||||||
|
public var isCanceled = false
|
||||||
|
public var id: Int?
|
||||||
|
public weak var operationDelegate: MainThreadOperationDelegate?
|
||||||
|
public var name: String? = "WebViewProviderReplenishQueueOperation"
|
||||||
|
public var completionBlock: MainThreadOperation.MainThreadOperationCompletionBlock?
|
||||||
|
|
||||||
|
private let minimumQueueDepth = 3
|
||||||
|
|
||||||
|
private var queue: UIView
|
||||||
|
private var articleIconSchemeHandler: ArticleIconSchemeHandler
|
||||||
|
|
||||||
|
init(queue: UIView, articleIconSchemeHandler: ArticleIconSchemeHandler) {
|
||||||
|
self.queue = queue
|
||||||
|
self.articleIconSchemeHandler = articleIconSchemeHandler
|
||||||
|
}
|
||||||
|
|
||||||
|
func run() {
|
||||||
|
while queue.subviews.count < minimumQueueDepth {
|
||||||
|
let webView = PreloadedWebView(articleIconSchemeHandler: articleIconSchemeHandler)
|
||||||
|
queue.insertSubview(webView, at: 0)
|
||||||
|
webView.preload()
|
||||||
|
}
|
||||||
|
self.operationDelegate?.operationDidComplete(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class WebViewProviderDequeueOperation: MainThreadOperation {
|
||||||
|
|
||||||
|
// MainThreadOperation
|
||||||
|
public var isCanceled = false
|
||||||
|
public var id: Int?
|
||||||
|
public weak var operationDelegate: MainThreadOperationDelegate?
|
||||||
|
public var name: String? = "WebViewProviderFlushQueueOperation"
|
||||||
|
public var completionBlock: MainThreadOperation.MainThreadOperationCompletionBlock?
|
||||||
|
|
||||||
|
private var queue: UIView
|
||||||
|
private var articleIconSchemeHandler: ArticleIconSchemeHandler
|
||||||
|
private var completion: (PreloadedWebView) -> ()
|
||||||
|
|
||||||
|
init(queue: UIView, articleIconSchemeHandler: ArticleIconSchemeHandler, completion: @escaping (PreloadedWebView) -> ()) {
|
||||||
|
self.queue = queue
|
||||||
|
self.articleIconSchemeHandler = articleIconSchemeHandler
|
||||||
|
self.completion = completion
|
||||||
|
}
|
||||||
|
|
||||||
|
func run() {
|
||||||
if let webView = queue.subviews.last as? PreloadedWebView {
|
if let webView = queue.subviews.last as? PreloadedWebView {
|
||||||
webView.ready { preloadedWebView in
|
webView.ready { preloadedWebView in
|
||||||
preloadedWebView.removeFromSuperview()
|
preloadedWebView.removeFromSuperview()
|
||||||
self.replenishQueueIfNeeded()
|
self.completion(preloadedWebView)
|
||||||
completion(preloadedWebView)
|
self.operationDelegate?.operationDidComplete(self)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -50,18 +124,11 @@ class WebViewProvider: NSObject {
|
|||||||
assertionFailure("Creating PreloadedWebView in \(#function); queue has run dry.")
|
assertionFailure("Creating PreloadedWebView in \(#function); queue has run dry.")
|
||||||
|
|
||||||
let webView = PreloadedWebView(articleIconSchemeHandler: articleIconSchemeHandler)
|
let webView = PreloadedWebView(articleIconSchemeHandler: articleIconSchemeHandler)
|
||||||
webView.ready { preloadedWebView in
|
|
||||||
self.replenishQueueIfNeeded()
|
|
||||||
completion(preloadedWebView)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func enqueueWebView(_ webView: PreloadedWebView) {
|
|
||||||
guard queue.subviews.count < maximumQueueDepth else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
queue.insertSubview(webView, at: 0)
|
|
||||||
webView.preload()
|
webView.preload()
|
||||||
|
webView.ready { preloadedWebView in
|
||||||
|
self.completion(preloadedWebView)
|
||||||
|
self.operationDelegate?.operationDidComplete(self)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -73,16 +73,13 @@ class MasterFeedTableViewSectionHeader: UITableViewHeaderFooterView {
|
|||||||
|
|
||||||
private let unreadCountView = MasterFeedUnreadCountView(frame: CGRect.zero)
|
private let unreadCountView = MasterFeedUnreadCountView(frame: CGRect.zero)
|
||||||
|
|
||||||
@available(iOS 13.4, *)
|
|
||||||
private(set) lazy var disclosurePointerInteraction = UIPointerInteraction()
|
|
||||||
|
|
||||||
private lazy var disclosureButton: UIButton = {
|
private lazy var disclosureButton: UIButton = {
|
||||||
let button = NonIntrinsicButton()
|
let button = NonIntrinsicButton()
|
||||||
button.tintColor = UIColor.tertiaryLabel
|
button.tintColor = UIColor.tertiaryLabel
|
||||||
button.setImage(AppAssets.disclosureImage, for: .normal)
|
button.setImage(AppAssets.disclosureImage, for: .normal)
|
||||||
button.contentMode = .center
|
button.contentMode = .center
|
||||||
if #available(iOS 13.4, *) {
|
if #available(iOS 13.4, *) {
|
||||||
button.addInteraction(disclosurePointerInteraction)
|
button.addInteraction(UIPointerInteraction())
|
||||||
}
|
}
|
||||||
button.addTarget(self, action: #selector(toggleDisclosure), for: .touchUpInside)
|
button.addTarget(self, action: #selector(toggleDisclosure), for: .touchUpInside)
|
||||||
return button
|
return button
|
||||||
|
@ -35,8 +35,12 @@ class ImageViewer {
|
|||||||
this.hideLoadingIndicator();
|
this.hideLoadingIndicator();
|
||||||
|
|
||||||
var canvas = document.createElement("canvas");
|
var canvas = document.createElement("canvas");
|
||||||
canvas.width = this.img.naturalWidth * window.devicePixelRatio;
|
var pixelRatio = window.devicePixelRatio;
|
||||||
canvas.height = this.img.naturalHeight * window.devicePixelRatio;
|
do {
|
||||||
|
canvas.width = this.img.naturalWidth * pixelRatio;
|
||||||
|
canvas.height = this.img.naturalHeight * pixelRatio;
|
||||||
|
pixelRatio--;
|
||||||
|
} while (pixelRatio > 0 && canvas.width * canvas.height > 16777216)
|
||||||
canvas.getContext("2d").drawImage(this.img, 0, 0, canvas.width, canvas.height);
|
canvas.getContext("2d").drawImage(this.img, 0, 0, canvas.width, canvas.height);
|
||||||
|
|
||||||
const rect = this.img.getBoundingClientRect();
|
const rect = this.img.getBoundingClientRect();
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
|
|
||||||
// High Level Settings common to both the iOS application and any extensions we bundle with it
|
// High Level Settings common to both the iOS application and any extensions we bundle with it
|
||||||
MARKETING_VERSION = 5.0.1
|
MARKETING_VERSION = 5.0.1
|
||||||
CURRENT_PROJECT_VERSION = 44
|
CURRENT_PROJECT_VERSION = 46
|
||||||
|
|
||||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES
|
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon
|
||||||
|
Loading…
x
Reference in New Issue
Block a user