diff --git a/Frameworks/Account/AccountBehaviors.swift b/Frameworks/Account/AccountBehaviors.swift index 058e94b5d..b3551f008 100644 --- a/Frameworks/Account/AccountBehaviors.swift +++ b/Frameworks/Account/AccountBehaviors.swift @@ -29,7 +29,7 @@ public struct AccountBehaviors: OptionSet { /** Account doesn't support OPML imports */ - public static let disallowOPMLImports = AccountBehaviors(rawValue: 3) + public static let disallowOPMLImports = AccountBehaviors(rawValue: 4) public let rawValue: Int public init(rawValue: Int) { diff --git a/Shared/Article Rendering/ArticleIconSchemeHandler.swift b/Mac/MainWindow/Detail/DetailIconSchemeHandler.swift similarity index 91% rename from Shared/Article Rendering/ArticleIconSchemeHandler.swift rename to Mac/MainWindow/Detail/DetailIconSchemeHandler.swift index 20b0f32bc..4aee30c11 100644 --- a/Shared/Article Rendering/ArticleIconSchemeHandler.swift +++ b/Mac/MainWindow/Detail/DetailIconSchemeHandler.swift @@ -1,5 +1,5 @@ // -// AccountViewControllerSchemeHandler.swift +// DetailIconSchemeHandler.swift // NetNewsWire-iOS // // Created by Maurice Parker on 11/7/19. @@ -10,7 +10,7 @@ import Foundation import WebKit import Articles -class ArticleIconSchemeHandler: NSObject, WKURLSchemeHandler { +class DetailIconSchemeHandler: NSObject, WKURLSchemeHandler { var currentArticle: Article? diff --git a/Mac/MainWindow/Detail/DetailWebViewController.swift b/Mac/MainWindow/Detail/DetailWebViewController.swift index 1a334a958..bf047ea5b 100644 --- a/Mac/MainWindow/Detail/DetailWebViewController.swift +++ b/Mac/MainWindow/Detail/DetailWebViewController.swift @@ -27,6 +27,17 @@ final class DetailWebViewController: NSViewController, WKUIDelegate { } } } + + var article: Article? { + switch state { + case .article(let article): + return article + case .extracted(let article, _): + return article + default: + return nil + } + } #if !MAC_APP_STORE private var webInspectorEnabled: Bool { @@ -39,7 +50,7 @@ final class DetailWebViewController: NSViewController, WKUIDelegate { } #endif - private let articleIconSchemeHandler = ArticleIconSchemeHandler() + private let detailIconSchemeHandler = DetailIconSchemeHandler() private var waitingForFirstReload = false private let keyboardDelegate = DetailKeyboardDelegate() @@ -66,7 +77,7 @@ final class DetailWebViewController: NSViewController, WKUIDelegate { let configuration = WKWebViewConfiguration() configuration.preferences = preferences - configuration.setURLSchemeHandler(articleIconSchemeHandler, forURLScheme: ArticleRenderer.imageIconScheme) + configuration.setURLSchemeHandler(detailIconSchemeHandler, forURLScheme: ArticleRenderer.imageIconScheme) let userContentController = WKUserContentController() userContentController.add(self, name: MessageName.mouseDidEnter) @@ -107,7 +118,7 @@ final class DetailWebViewController: NSViewController, WKUIDelegate { NotificationCenter.default.addObserver(self, selector: #selector(avatarDidBecomeAvailable(_:)), name: .AvatarDidBecomeAvailable, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(faviconDidBecomeAvailable(_:)), name: .FaviconDidBecomeAvailable, object: nil) - webView.loadHTMLString(ArticleRenderer.page.html, baseURL: ArticleRenderer.page.baseURL) + webView.loadFileURL(ArticleRenderer.page.url, allowingReadAccessTo: ArticleRenderer.page.baseURL) } @@ -193,7 +204,8 @@ struct TemplateData: Codable { private extension DetailWebViewController { func reloadArticleImage() { - webView.evaluateJavaScript("reloadArticleImage()") + guard let article = article else { return } + webView?.evaluateJavaScript("reloadArticleImage(\"\(article.articleID)\")") } func reloadHTML() { @@ -208,10 +220,10 @@ private extension DetailWebViewController { case .loading: rendering = ArticleRenderer.loadingHTML(style: style) case .article(let article): - articleIconSchemeHandler.currentArticle = article + detailIconSchemeHandler.currentArticle = article rendering = ArticleRenderer.articleHTML(article: article, style: style) case .extracted(let article, let extractedArticle): - articleIconSchemeHandler.currentArticle = article + detailIconSchemeHandler.currentArticle = article rendering = ArticleRenderer.articleHTML(article: article, extractedArticle: extractedArticle, style: style) } diff --git a/NetNewsWire.xcodeproj/project.pbxproj b/NetNewsWire.xcodeproj/project.pbxproj index 5f42a18bd..97e9c47e1 100644 --- a/NetNewsWire.xcodeproj/project.pbxproj +++ b/NetNewsWire.xcodeproj/project.pbxproj @@ -72,7 +72,6 @@ 513C5D0C232574DA003D4054 /* RSTree.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84C37F9520DD8CFE00CA8CF5 /* RSTree.framework */; }; 513C5D0E232574E4003D4054 /* SyncDatabase.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 51554C01228B6EB50055115A /* SyncDatabase.framework */; }; 5141E7392373C18B0013FF27 /* WebFeedInspectorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5141E7382373C18B0013FF27 /* WebFeedInspectorViewController.swift */; }; - 5141E7562374A2890013FF27 /* ArticleIconSchemeHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5141E7552374A2890013FF27 /* ArticleIconSchemeHandler.swift */; }; 5142192A23522B5500E07E2C /* ImageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5142192923522B5500E07E2C /* ImageViewController.swift */; }; 514219372352510100E07E2C /* ImageScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 514219362352510100E07E2C /* ImageScrollView.swift */; }; 5142194B2353C1CF00E07E2C /* main_mac.js in Resources */ = {isa = PBXBuildFile; fileRef = 5142194A2353C1CF00E07E2C /* main_mac.js */; }; @@ -121,8 +120,8 @@ 518651DA235621840078E021 /* ImageTransition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 518651D9235621840078E021 /* ImageTransition.swift */; }; 5186A635235EF3A800C97195 /* VibrantLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5186A634235EF3A800C97195 /* VibrantLabel.swift */; }; 518B2EE82351B45600400001 /* NetNewsWire_iOSTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 840D61952029031D009BC708 /* NetNewsWire_iOSTests.swift */; }; - 518C3193237B00D9004D740F /* ArticleIconSchemeHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5141E7552374A2890013FF27 /* ArticleIconSchemeHandler.swift */; }; - 518C3194237B00DA004D740F /* ArticleIconSchemeHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5141E7552374A2890013FF27 /* ArticleIconSchemeHandler.swift */; }; + 518C3193237B00D9004D740F /* DetailIconSchemeHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5141E7552374A2890013FF27 /* DetailIconSchemeHandler.swift */; }; + 518C3194237B00DA004D740F /* DetailIconSchemeHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5141E7552374A2890013FF27 /* DetailIconSchemeHandler.swift */; }; 518ED21D23D0F26000E0A862 /* UIViewController-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 518ED21C23D0F26000E0A862 /* UIViewController-Extensions.swift */; }; 51934CCB230F599B006127BE /* InteractiveNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51934CC1230F5963006127BE /* InteractiveNavigationController.swift */; }; 51934CCE2310792F006127BE /* ActivityManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51934CCD2310792F006127BE /* ActivityManager.swift */; }; @@ -258,6 +257,7 @@ 51F85BF92274AA7B00C787DC /* UIBarButtonItem-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51F85BF82274AA7B00C787DC /* UIBarButtonItem-Extensions.swift */; }; 51F85BFB2275D85000C787DC /* Array-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51F85BFA2275D85000C787DC /* Array-Extensions.swift */; }; 51F85BFD2275DCA800C787DC /* SingleLineUILabelSizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51F85BFC2275DCA800C787DC /* SingleLineUILabelSizer.swift */; }; + 51F9F3F723DF6DB200A314FD /* ArticleIconSchemeHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51F9F3F623DF6DB200A314FD /* ArticleIconSchemeHandler.swift */; }; 51FA73A42332BE110090D516 /* ArticleExtractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51FA73A32332BE110090D516 /* ArticleExtractor.swift */; }; 51FA73A52332BE110090D516 /* ArticleExtractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51FA73A32332BE110090D516 /* ArticleExtractor.swift */; }; 51FA73A72332BE880090D516 /* ExtractedArticle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51FA73A62332BE880090D516 /* ExtractedArticle.swift */; }; @@ -1265,7 +1265,7 @@ 513C5CEB232571C2003D4054 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/MainInterface.storyboard; sourceTree = ""; }; 513C5CED232571C2003D4054 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 5141E7382373C18B0013FF27 /* WebFeedInspectorViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebFeedInspectorViewController.swift; sourceTree = ""; }; - 5141E7552374A2890013FF27 /* ArticleIconSchemeHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArticleIconSchemeHandler.swift; sourceTree = ""; }; + 5141E7552374A2890013FF27 /* DetailIconSchemeHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailIconSchemeHandler.swift; sourceTree = ""; }; 5142192923522B5500E07E2C /* ImageViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageViewController.swift; sourceTree = ""; }; 514219362352510100E07E2C /* ImageScrollView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageScrollView.swift; sourceTree = ""; }; 5142194A2353C1CF00E07E2C /* main_mac.js */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.javascript; path = main_mac.js; sourceTree = ""; }; @@ -1382,6 +1382,7 @@ 51F85BF82274AA7B00C787DC /* UIBarButtonItem-Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIBarButtonItem-Extensions.swift"; sourceTree = ""; }; 51F85BFA2275D85000C787DC /* Array-Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Array-Extensions.swift"; sourceTree = ""; }; 51F85BFC2275DCA800C787DC /* SingleLineUILabelSizer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SingleLineUILabelSizer.swift; sourceTree = ""; }; + 51F9F3F623DF6DB200A314FD /* ArticleIconSchemeHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArticleIconSchemeHandler.swift; sourceTree = ""; }; 51FA73A32332BE110090D516 /* ArticleExtractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArticleExtractor.swift; sourceTree = ""; }; 51FA73A62332BE880090D516 /* ExtractedArticle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtractedArticle.swift; sourceTree = ""; }; 51FA73B62332D5F70090D516 /* ArticleExtractorButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArticleExtractorButton.swift; sourceTree = ""; }; @@ -1970,6 +1971,7 @@ 512D554323C804DE0023FFFA /* OpenInSafariActivity.swift */, 51AB8AB223B7F4C6008F147D /* WebViewController.swift */, 517630222336657E00E15FFF /* WebViewProvider.swift */, + 51F9F3F623DF6DB200A314FD /* ArticleIconSchemeHandler.swift */, ); path = Article; sourceTree = ""; @@ -1993,7 +1995,6 @@ 51C452A822650DA100C03939 /* Article Rendering */ = { isa = PBXGroup; children = ( - 5141E7552374A2890013FF27 /* ArticleIconSchemeHandler.swift */, 849A977D1ED9EC42007D329B /* ArticleRenderer.swift */, 517630032336215100E15FFF /* main.js */, 49F40DEF2335B71000552BF4 /* newsfoot.js */, @@ -2318,6 +2319,7 @@ 84216D0222128B9D0049B9B9 /* DetailWebViewController.swift */, 84E8E0EA202F693600562D8F /* DetailWebView.swift */, 84D52E941FE588BB00D14F5B /* DetailStatusBarView.swift */, + 5141E7552374A2890013FF27 /* DetailIconSchemeHandler.swift */, B528F81D23333C7E00E735DD /* page.html */, 5142194A2353C1CF00E07E2C /* main_mac.js */, 848362FC2262A30800DA1D35 /* styleSheet.css */, @@ -3726,7 +3728,7 @@ 65ED3FE9235DEF6C0081F399 /* FaviconURLFinder.swift in Sources */, 65ED3FEA235DEF6C0081F399 /* SidebarViewController+ContextualMenus.swift in Sources */, 65ED3FEC235DEF6C0081F399 /* RSHTMLMetadata+Extension.swift in Sources */, - 518C3194237B00DA004D740F /* ArticleIconSchemeHandler.swift in Sources */, + 518C3194237B00DA004D740F /* DetailIconSchemeHandler.swift in Sources */, 65ED3FED235DEF6C0081F399 /* SendToMarsEditCommand.swift in Sources */, 65ED3FEE235DEF6C0081F399 /* UserNotificationManager.swift in Sources */, 65ED3FEF235DEF6C0081F399 /* ScriptingObjectContainer.swift in Sources */, @@ -3889,7 +3891,6 @@ 51C45292226509C800C03939 /* TodayFeedDelegate.swift in Sources */, 51C452A222650A1900C03939 /* RSHTMLMetadata+Extension.swift in Sources */, 514B7D1F23219F3C00BAC947 /* AddControllerType.swift in Sources */, - 5141E7562374A2890013FF27 /* ArticleIconSchemeHandler.swift in Sources */, 51627A93238A3836007B3B4B /* CroppingPreviewParameters.swift in Sources */, 512AF9DD236F05230066F8BE /* InteractiveLabel.swift in Sources */, 51E3EB3D229AB08300645299 /* ErrorHandler.swift in Sources */, @@ -3919,6 +3920,7 @@ 84CAFCA522BC8C08007694F0 /* FetchRequestQueue.swift in Sources */, 51C4529C22650A1000C03939 /* SingleFaviconDownloader.swift in Sources */, 51E595A6228CC36500FCC42B /* ArticleStatusSyncTimer.swift in Sources */, + 51F9F3F723DF6DB200A314FD /* ArticleIconSchemeHandler.swift in Sources */, 512AF9C2236ED52C0066F8BE /* ImageHeaderView.swift in Sources */, 51A1699F235E10D700EB091F /* AboutViewController.swift in Sources */, 51C45290226509C100C03939 /* PseudoFeed.swift in Sources */, @@ -4074,7 +4076,7 @@ 848D578E21543519005FFAD5 /* PasteboardWebFeed.swift in Sources */, 5144EA2F2279FAB600D19003 /* AccountsDetailViewController.swift in Sources */, 849A97801ED9EC42007D329B /* DetailViewController.swift in Sources */, - 518C3193237B00D9004D740F /* ArticleIconSchemeHandler.swift in Sources */, + 518C3193237B00D9004D740F /* DetailIconSchemeHandler.swift in Sources */, 84C9FC6722629B9000D921D6 /* AppDelegate.swift in Sources */, 84C9FC7A22629E1200D921D6 /* AccountsTableViewBackgroundView.swift in Sources */, 84CAFCAF22BC8C35007694F0 /* FetchRequestOperation.swift in Sources */, diff --git a/Shared/Article Rendering/ArticleRenderer.swift b/Shared/Article Rendering/ArticleRenderer.swift index 327bbec76..e19adec0a 100644 --- a/Shared/Article Rendering/ArticleRenderer.swift +++ b/Shared/Article Rendering/ArticleRenderer.swift @@ -14,15 +14,14 @@ import Account struct ArticleRenderer { typealias Rendering = (style: String, html: String) - typealias Page = (html: String, baseURL: URL) + typealias Page = (url: URL, baseURL: URL) static var imageIconScheme = "nnwImageIcon" static var page: Page = { - let pageURL = Bundle.main.url(forResource: "page", withExtension: "html")! - let html = try! String(contentsOf: pageURL) - let baseURL = pageURL.deletingLastPathComponent() - return Page(html: html, baseURL: baseURL) + let url = Bundle.main.url(forResource: "page", withExtension: "html")! + let baseURL = url.deletingLastPathComponent() + return Page(url: url, baseURL: baseURL) }() private let article: Article? @@ -141,7 +140,7 @@ private extension ArticleRenderer { d["title"] = title d["body"] = body - d["avatars"] = ""; + d["avatars"] = ""; var feedLink = "" if let feedTitle = article.webFeed?.nameForDisplay { diff --git a/Shared/Article Rendering/main.js b/Shared/Article Rendering/main.js index cce23f499..c843c9a1c 100644 --- a/Shared/Article Rendering/main.js +++ b/Shared/Article Rendering/main.js @@ -79,9 +79,9 @@ function flattenPreElements() { ElementUnwrapper.unwrapAppropriateChildren("div.articleBody td > pre"); } -function reloadArticleImage() { +function reloadArticleImage(articleID) { var image = document.getElementById("nnwImageIcon"); - image.src = "nnwImageIcon://"; + image.src = "nnwImageIcon://" + articleID; } function error() { diff --git a/iOS/AppAssets.swift b/iOS/AppAssets.swift index 6a97cef28..287e27b23 100644 --- a/iOS/AppAssets.swift +++ b/iOS/AppAssets.swift @@ -49,7 +49,7 @@ struct AppAssets { static var articleExtractorOffTinted: UIImage = { let image = UIImage(named: "articleExtractorOff")! - return image.maskWithColor(color: AppAssets.primaryAccentColor.cgColor)! + return image.tinted(color: AppAssets.primaryAccentColor)! }() static var articleExtractorOn: UIImage = { @@ -62,7 +62,7 @@ struct AppAssets { static var articleExtractorOnTinted: UIImage = { let image = UIImage(named: "articleExtractorOn")! - return image.maskWithColor(color: AppAssets.primaryAccentColor.cgColor)! + return image.tinted(color: AppAssets.primaryAccentColor)! }() static var iconBackgroundColor: UIColor = { diff --git a/iOS/AppDelegate.swift b/iOS/AppDelegate.swift index a79c70147..a4784955b 100644 --- a/iOS/AppDelegate.swift +++ b/iOS/AppDelegate.swift @@ -58,8 +58,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD super.init() appDelegate = self - // Force lazy initialization of the web view provider so that it can warm up the queue of prepared web views - let _ = WebViewProvider.shared AccountManager.shared = AccountManager() NotificationCenter.default.addObserver(self, selector: #selector(unreadCountDidChange(_:)), name: .UnreadCountDidChange, object: nil) diff --git a/iOS/Article/ArticleIconSchemeHandler.swift b/iOS/Article/ArticleIconSchemeHandler.swift new file mode 100644 index 000000000..498986199 --- /dev/null +++ b/iOS/Article/ArticleIconSchemeHandler.swift @@ -0,0 +1,58 @@ +// +// ArticleIconSchemeHandler.swift +// NetNewsWire-iOS +// +// Created by Maurice Parker on 1/27/20. +// Copyright © 2020 Ranchero Software. All rights reserved. +// + +import Foundation +import WebKit +import Articles + +class ArticleIconSchemeHandler: NSObject, WKURLSchemeHandler { + + let coordinator: SceneCoordinator + + init(coordinator: SceneCoordinator) { + self.coordinator = coordinator + } + + func webView(_ webView: WKWebView, start urlSchemeTask: WKURLSchemeTask) { + + guard let url = urlSchemeTask.request.url else { + urlSchemeTask.didFailWithError(URLError(.fileDoesNotExist)) + return + } + + let articleID = url.absoluteString.stripping(prefix: "\(ArticleRenderer.imageIconScheme)://") + + guard let iconImage = coordinator.articleFor(articleID)?.iconImage() else { + urlSchemeTask.didFailWithError(URLError(.fileDoesNotExist)) + return + } + + let iconView = IconView(frame: CGRect(x: 0, y: 0, width: 48, height: 48)) + iconView.iconImage = iconImage + let renderedImage = iconView.asImage() + + guard let data = renderedImage.dataRepresentation() else { + urlSchemeTask.didFailWithError(URLError(.fileDoesNotExist)) + return + } + + let headerFields = ["Cache-Control": "no-cache"] + if let response = HTTPURLResponse(url: url, statusCode: 200, httpVersion: nil, headerFields: headerFields) { + urlSchemeTask.didReceive(response) + urlSchemeTask.didReceive(data) + urlSchemeTask.didFinish() + } + + } + + func webView(_ webView: WKWebView, stop urlSchemeTask: WKURLSchemeTask) { + urlSchemeTask.didFailWithError(URLError(.unknown)) + } + +} + diff --git a/iOS/Article/ArticleViewController.swift b/iOS/Article/ArticleViewController.swift index 38c51c74f..ff64049bc 100644 --- a/iOS/Article/ArticleViewController.swift +++ b/iOS/Article/ArticleViewController.swift @@ -34,10 +34,6 @@ class ArticleViewController: UIViewController { return button }() - private var isFullScreenAvailable: Bool { - return traitCollection.userInterfaceIdiom == .phone && coordinator.isRootSplitCollapsed - } - weak var coordinator: SceneCoordinator! var article: Article? { @@ -268,14 +264,18 @@ extension ArticleViewController: WebViewControllerDelegate { extension ArticleViewController: UIPageViewControllerDataSource { func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? { - guard let article = coordinator.prevArticle else { + guard let webViewController = viewController as? WebViewController, + let currentArticle = webViewController.article, + let article = coordinator.findPrevArticle(currentArticle) else { return nil } return createWebViewController(article) } func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? { - guard let article = coordinator.nextArticle else { + guard let webViewController = viewController as? WebViewController, + let currentArticle = webViewController.article, + let article = coordinator.findNextArticle(currentArticle) else { return nil } return createWebViewController(article) diff --git a/iOS/Article/WebViewController.swift b/iOS/Article/WebViewController.swift index 25868dda7..c69ddfc2a 100644 --- a/iOS/Article/WebViewController.swift +++ b/iOS/Article/WebViewController.swift @@ -30,7 +30,6 @@ class WebViewController: UIViewController { private var topShowBarsViewConstraint: NSLayoutConstraint! private var bottomShowBarsViewConstraint: NSLayoutConstraint! - private var renderingTracker = 0 private var webView: WKWebView! private lazy var contextMenuInteraction = UIContextMenuInteraction(delegate: self) private var isFullScreenAvailable: Bool { @@ -44,7 +43,7 @@ class WebViewController: UIViewController { private var isShowingExtractedArticle = false { didSet { if isShowingExtractedArticle != oldValue { - renderPage() + reloadHTML() } } } @@ -65,7 +64,8 @@ class WebViewController: UIViewController { startArticleExtractor() } if article != oldValue { - renderPage() + restoreWindowScrollY = 0 + reloadHTML() } } } @@ -83,7 +83,7 @@ class WebViewController: UIViewController { webView.configuration.userContentController.removeScriptMessageHandler(forName: MessageName.imageWasClicked) webView.configuration.userContentController.removeScriptMessageHandler(forName: MessageName.imageWasShown) webView.removeFromSuperview() - WebViewProvider.shared.enqueueWebView(webView) + coordinator.webViewProvider.enqueueWebView(webView) webView = nil } } @@ -96,7 +96,7 @@ class WebViewController: UIViewController { NotificationCenter.default.addObserver(self, selector: #selector(faviconDidBecomeAvailable(_:)), name: .FaviconDidBecomeAvailable, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(contentSizeCategoryDidChange(_:)), name: UIContentSizeCategory.didChangeNotification, object: nil) - WebViewProvider.shared.dequeueWebView() { webView in + coordinator.webViewProvider.dequeueWebView() { webView in // Add the webview self.webView = webView @@ -266,7 +266,7 @@ extension WebViewController: ArticleExtractorDelegate { func articleExtractionDidFail(with: Error) { stopArticleExtractor() articleExtractorButtonState = .error - renderPage() + reloadHTML() } func articleExtractionDidComplete(extractedArticle: ExtractedArticle) { @@ -350,7 +350,7 @@ extension WebViewController: WKNavigationDelegate { } func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { - self.renderPage() + renderPage() } } @@ -450,24 +450,12 @@ private struct ImageClickMessage: Codable { private extension WebViewController { func reloadHTML() { - let url = Bundle.main.url(forResource: "page", withExtension: "html")! - webView.loadFileURL(url, allowingReadAccessTo: url.deletingLastPathComponent()) - renderingTracker = 0 + webView?.loadFileURL(ArticleRenderer.page.url, allowingReadAccessTo: ArticleRenderer.page.baseURL) } func renderPage() { guard let webView = webView else { return } - // It looks like we need to clean up the webview every once in a while by reloading it from scratch - // Otherwise it will stop responding or cause rendering artifacts. This typically only comes into - // play on the iPad where we aren't constantly pushing and popping this controller. - if (renderingTracker > 10) { - reloadHTML() - return - } - - renderingTracker += 1 - let style = ArticleStylesManager.shared.currentStyle let rendering: ArticleRenderer.Rendering @@ -498,7 +486,6 @@ private extension WebViewController { restoreWindowScrollY = 0 - WebViewProvider.shared.articleIconSchemeHandler.currentArticle = article webView.scrollView.setZoomScale(1.0, animated: false) webView.evaluateJavaScript(render) @@ -525,7 +512,8 @@ private extension WebViewController { } func reloadArticleImage() { - webView?.evaluateJavaScript("reloadArticleImage()") + guard let article = article else { return } + webView?.evaluateJavaScript("reloadArticleImage(\"\(article.articleID)\")") } func imageWasClicked(body: String?) { diff --git a/iOS/Article/WebViewProvider.swift b/iOS/Article/WebViewProvider.swift index 2c90e4505..c9010baaf 100644 --- a/iOS/Article/WebViewProvider.swift +++ b/iOS/Article/WebViewProvider.swift @@ -13,9 +13,7 @@ import WebKit /// 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, WKNavigationDelegate { - static let shared = WebViewProvider() - - let articleIconSchemeHandler = ArticleIconSchemeHandler() + let articleIconSchemeHandler: ArticleIconSchemeHandler private let minimumQueueDepth = 3 private let maximumQueueDepth = 6 @@ -24,6 +22,12 @@ class WebViewProvider: NSObject, WKNavigationDelegate { private var waitingForFirstLoad = true private var waitingCompletionHandler: ((WKWebView) -> ())? + init(coordinator: SceneCoordinator) { + articleIconSchemeHandler = ArticleIconSchemeHandler(coordinator: coordinator) + super.init() + replenishQueueIfNeeded() + } + func dequeueWebView(completion: @escaping (WKWebView) -> ()) { if waitingForFirstLoad { waitingCompletionHandler = completion @@ -40,7 +44,7 @@ class WebViewProvider: NSObject, WKNavigationDelegate { webView.navigationDelegate = self queue.insert(webView, at: 0) - webView.loadHTMLString(ArticleRenderer.page.html, baseURL: ArticleRenderer.page.baseURL) + webView.loadFileURL(ArticleRenderer.page.url, allowingReadAccessTo: ArticleRenderer.page.baseURL) } @@ -58,11 +62,6 @@ class WebViewProvider: NSObject, WKNavigationDelegate { // MARK: Private - private override init() { - super.init() - replenishQueueIfNeeded() - } - private func replenishQueueIfNeeded() { while queue.count < minimumQueueDepth { let preferences = WKPreferences() diff --git a/iOS/MasterFeed/MasterFeedViewController.swift b/iOS/MasterFeed/MasterFeedViewController.swift index 948d3da05..629be7359 100644 --- a/iOS/MasterFeed/MasterFeedViewController.swift +++ b/iOS/MasterFeed/MasterFeedViewController.swift @@ -117,10 +117,9 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner { // completing if called to soon after a selectRow where scrolling is necessary. See discloseFeed. if let node = node, let indexPath = dataSource.indexPath(for: node), - let cell = tableView.cellForRow(at: indexPath) as? MasterFeedTableViewCell, - let unreadCountProvider = node.representedObject as? UnreadCountProvider { + let cell = tableView.cellForRow(at: indexPath) as? MasterFeedTableViewCell { - if cell.unreadCount != unreadCountProvider.unreadCount { + if cell.unreadCount != coordinator.unreadCountFor(node) { self.reloadNode(node) } diff --git a/iOS/Resources/styleSheet.css b/iOS/Resources/styleSheet.css index 2f03a7ef2..b2c5b1264 100644 --- a/iOS/Resources/styleSheet.css +++ b/iOS/Resources/styleSheet.css @@ -39,6 +39,7 @@ a:hover { color-scheme: light dark; --primary-accent-color: #086AEE; --secondary-accent-color: #086AEE; + --block-quote-border-color: rgba(8, 106, 238, 0.75); --header-table-border-color: rgba(0, 0, 0, 0.1); --header-color: rgba(0, 0, 0, 0.3); --body-code-color: #666; @@ -52,6 +53,7 @@ a:hover { :root { --primary-accent-color: #2D80F1; --secondary-accent-color: #5E9EF4; + --block-quote-border-color: rgba(94, 158, 244, 0.75); --header-table-border-color: rgba(255, 255, 255, 0.2); --header-color: #d2d2d2; --body-code-color: #b2b2b2; @@ -240,7 +242,7 @@ blockquote { margin-inline-start: 0; margin-inline-end: 0; padding-left: 15px; - border-left: 3px solid var(--secondary-accent-color); + border-left: 3px solid var(--block-quote-border-color); } /* Feed Specific */ diff --git a/iOS/SceneCoordinator.swift b/iOS/SceneCoordinator.swift index 249ed48d0..2510c8974 100644 --- a/iOS/SceneCoordinator.swift +++ b/iOS/SceneCoordinator.swift @@ -30,6 +30,8 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider { return rootSplitViewController.undoManager } + lazy var webViewProvider = WebViewProvider(coordinator: self) + private var panelMode: PanelMode = .unset private var activityManager = ActivityManager() @@ -255,9 +257,19 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider { private(set) var articles = ArticleArray() { didSet { timelineMiddleIndexPath = nil + articleDictionaryNeedsUpdate = true } } - + + private var articleDictionaryNeedsUpdate = true + private var _idToArticleDictionary = [String: Article]() + private var idToAticleDictionary: [String: Article] { + if articleDictionaryNeedsUpdate { + rebuildArticleDictionaries() + } + return _idToArticleDictionary + } + private var currentArticleRow: Int? { guard let article = currentArticle else { return nil } return articles.firstIndex(of: article) @@ -572,6 +584,10 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider { return shadowTable[section] } + func articleFor(_ articleID: String) -> Article? { + return idToAticleDictionary[articleID] + } + func cappedIndexPath(_ indexPath: IndexPath) -> IndexPath { guard indexPath.section < shadowTable.count && indexPath.row < shadowTable[indexPath.section].count else { return IndexPath(row: shadowTable[shadowTable.count - 1].count - 1, section: shadowTable.count - 1) @@ -594,6 +610,9 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider { func refreshTimeline(resetScroll: Bool) { fetchAndReplaceArticlesAsync(animated: true) { self.masterTimelineViewController?.reinitializeArticles(resetScroll: resetScroll) + if let article = self.currentArticle, self.articles.firstIndex(of: article) == nil { + self.selectArticle(nil) + } } } @@ -654,6 +673,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider { animatingChanges = true rebuildShadowTable() animatingChanges = false + clearTimelineIfNoLongerAvailable() } func collapseAllFolders() { @@ -668,6 +688,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider { animatingChanges = true rebuildShadowTable() animatingChanges = false + clearTimelineIfNoLongerAvailable() } func masterFeedIndexPathForCurrentTimeline() -> IndexPath? { @@ -686,7 +707,6 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider { currentFeedIndexPath = indexPath masterFeedViewController.updateFeedSelection(animated: animated) - emptyTheTimeline() if deselectArticle { selectArticle(nil) } @@ -825,6 +845,20 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider { } + func findPrevArticle(_ article: Article) -> Article? { + guard let index = articles.firstIndex(of: article), index > 0 else { + return nil + } + return articles[index - 1] + } + + func findNextArticle(_ article: Article) -> Article? { + guard let index = articles.firstIndex(of: article), index + 1 != articles.count else { + return nil + } + return articles[index + 1] + } + func selectPrevArticle() { if let article = prevArticle { selectArticle(article) @@ -1204,12 +1238,24 @@ private extension SceneCoordinator { unreadCount = count } + func rebuildArticleDictionaries() { + var idDictionary = [String: Article]() + + articles.forEach { article in + idDictionary[article.articleID] = article + } + + _idToArticleDictionary = idDictionary + articleDictionaryNeedsUpdate = false + } + func rebuildBackingStores(initialLoad: Bool = false, updateExpandedNodes: (() -> Void)? = nil) { if !animatingChanges && !BatchUpdate.shared.isPerforming { treeController.rebuild() updateExpandedNodes?() rebuildShadowTable() masterFeedViewController.reloadFeeds(initialLoad: initialLoad) + clearTimelineIfNoLongerAvailable() } } @@ -1247,6 +1293,12 @@ private extension SceneCoordinator { } return false } + + func clearTimelineIfNoLongerAvailable() { + if let feed = timelineFeed, !shadowTableContains(feed) { + selectFeed(nil, animated: false, deselectArticle: true) + } + } func nodeFor(_ indexPath: IndexPath) -> Node? { guard indexPath.section < shadowTable.count && indexPath.row < shadowTable[indexPath.section].count else { diff --git a/iOS/Settings/Settings.storyboard b/iOS/Settings/Settings.storyboard index c67fc73f2..b03bf67ce 100644 --- a/iOS/Settings/Settings.storyboard +++ b/iOS/Settings/Settings.storyboard @@ -1,8 +1,8 @@ - + - + @@ -207,7 +207,7 @@ - + @@ -226,6 +226,9 @@ + + + @@ -296,7 +299,7 @@ - + @@ -313,7 +316,7 @@ - + @@ -468,7 +471,7 @@ - + @@ -500,8 +503,12 @@ + + + + - + @@ -533,8 +540,12 @@ + + + + - + @@ -566,8 +577,12 @@ + + + + - + @@ -599,6 +614,10 @@ + + + + diff --git a/submodules/RSCore b/submodules/RSCore index 5c6a61832..c548b018b 160000 --- a/submodules/RSCore +++ b/submodules/RSCore @@ -1 +1 @@ -Subproject commit 5c6a61832b1cb1ed2a29e192469079eb8f9147f5 +Subproject commit c548b018beb865c2a1792d15767a2b378bbdaa89