From 4f294c4d206a9fff5c4124b9de36effe8e50173e Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Thu, 7 Nov 2019 14:29:16 -0600 Subject: [PATCH] Make Article icons/avatars match Timeline icons/avatars Issue #1274 --- NetNewsWire.xcodeproj/project.pbxproj | 4 ++ .../Article Rendering/ArticleRenderer.swift | 17 +++++-- Shared/Data/ArticleUtilities.swift | 15 ++++--- iOS/Article/ArticleIconSchemeHandler.swift | 44 +++++++++++++++++++ iOS/Article/ArticleViewController.swift | 5 ++- ...ArticleViewControllerWebViewProvider.swift | 3 ++ iOS/IconView.swift | 18 +++----- submodules/RSCore | 2 +- 8 files changed, 83 insertions(+), 25 deletions(-) create mode 100644 iOS/Article/ArticleIconSchemeHandler.swift diff --git a/NetNewsWire.xcodeproj/project.pbxproj b/NetNewsWire.xcodeproj/project.pbxproj index cd8fd7268..16b095c97 100644 --- a/NetNewsWire.xcodeproj/project.pbxproj +++ b/NetNewsWire.xcodeproj/project.pbxproj @@ -60,6 +60,7 @@ 513C5D0C232574DA003D4054 /* RSTree.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84C37F9520DD8CFE00CA8CF5 /* RSTree.framework */; }; 513C5D0E232574E4003D4054 /* SyncDatabase.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 51554C01228B6EB50055115A /* SyncDatabase.framework */; }; 5141E7392373C18B0013FF27 /* FeedInspectorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5141E7382373C18B0013FF27 /* FeedInspectorViewController.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 */; }; @@ -1229,6 +1230,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 /* FeedInspectorViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedInspectorViewController.swift; sourceTree = ""; }; + 5141E7552374A2890013FF27 /* ArticleIconSchemeHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArticleIconSchemeHandler.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 = ""; }; @@ -1913,6 +1915,7 @@ children = ( 51C4527E2265092C00C03939 /* ArticleViewController.swift */, 517630222336657E00E15FFF /* ArticleViewControllerWebViewProvider.swift */, + 5141E7552374A2890013FF27 /* ArticleIconSchemeHandler.swift */, 51102164233A7D6C0007A5F7 /* ArticleExtractorButton.swift */, 5142192923522B5500E07E2C /* ImageViewController.swift */, 514219362352510100E07E2C /* ImageScrollView.swift */, @@ -3930,6 +3933,7 @@ 51C45292226509C800C03939 /* TodayFeedDelegate.swift in Sources */, 51C452A222650A1900C03939 /* RSHTMLMetadata+Extension.swift in Sources */, 514B7D1F23219F3C00BAC947 /* AddControllerType.swift in Sources */, + 5141E7562374A2890013FF27 /* ArticleIconSchemeHandler.swift in Sources */, 512AF9DD236F05230066F8BE /* InteractiveLabel.swift in Sources */, 51E3EB3D229AB08300645299 /* ErrorHandler.swift in Sources */, 5183CCE5226F4DFA0010922C /* RefreshInterval.swift in Sources */, diff --git a/Shared/Article Rendering/ArticleRenderer.swift b/Shared/Article Rendering/ArticleRenderer.swift index a4990719e..93d1676cf 100644 --- a/Shared/Article Rendering/ArticleRenderer.swift +++ b/Shared/Article Rendering/ArticleRenderer.swift @@ -16,6 +16,8 @@ struct ArticleRenderer { typealias Rendering = (style: String, html: String) typealias Page = (html: String, 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) @@ -29,8 +31,9 @@ struct ArticleRenderer { private let title: String private let body: String private let baseURL: String? + private let useImageIcon: Bool - private init(article: Article?, extractedArticle: ExtractedArticle?, style: ArticleStyle) { + private init(article: Article?, extractedArticle: ExtractedArticle?, style: ArticleStyle, useImageIcon: Bool = false) { self.article = article self.extractedArticle = extractedArticle self.articleStyle = style @@ -42,12 +45,13 @@ struct ArticleRenderer { self.body = article?.body ?? "" self.baseURL = article?.baseURL?.absoluteString } + self.useImageIcon = useImageIcon } // MARK: - API - static func articleHTML(article: Article, extractedArticle: ExtractedArticle? = nil, style: ArticleStyle) -> Rendering { - let renderer = ArticleRenderer(article: article, extractedArticle: extractedArticle, style: style) + static func articleHTML(article: Article, extractedArticle: ExtractedArticle? = nil, style: ArticleStyle, useImageIcon: Bool = false) -> Rendering { + let renderer = ArticleRenderer(article: article, extractedArticle: extractedArticle, style: style, useImageIcon: useImageIcon) return (renderer.styleString(), renderer.articleHTML) } @@ -231,6 +235,10 @@ private extension ArticleRenderer { return cachedImgTag } + if useImageIcon { + return "" + } + if let iconImage = appDelegate.feedIconDownloader.icon(for: feed) { if let s = base64String(forImage: iconImage.image) { #if os(macOS) @@ -281,7 +289,8 @@ private extension ArticleRenderer { } func avatarImgTag() -> String? { - if let author = singleArticleSpecifiedAuthor(), let imageURL = author.avatarURL { + if let author = singleArticleSpecifiedAuthor(), let authorImageURL = author.avatarURL { + let imageURL = useImageIcon ? ArticleRenderer.imageIconScheme : authorImageURL return Avatar(imageURL: imageURL, url: author.url).html(dimension: ArticleRenderer.avatarDimension) } if let feed = article?.feed, let imgTag = feedIconImgTag(forFeed: feed) { diff --git a/Shared/Data/ArticleUtilities.swift b/Shared/Data/ArticleUtilities.swift index 4ca5c8250..e954f215b 100644 --- a/Shared/Data/ArticleUtilities.swift +++ b/Shared/Data/ArticleUtilities.swift @@ -65,14 +65,18 @@ extension Article { } func iconImage() -> IconImage? { - if let authors = authors { - for author in authors { - if let image = appDelegate.authorAvatarDownloader.image(for: author) { - return image - } + if let authors = authors, authors.count == 1, let author = authors.first { + if let image = appDelegate.authorAvatarDownloader.image(for: author) { + return image } } + if let authors = feed?.authors, authors.count == 1, let author = authors.first { + if let image = appDelegate.authorAvatarDownloader.image(for: author) { + return image + } + } + guard let feed = feed else { return nil } @@ -88,7 +92,6 @@ extension Article { return FaviconGenerator.favicon(feed) } - } // MARK: PathIDUserInfoProvider diff --git a/iOS/Article/ArticleIconSchemeHandler.swift b/iOS/Article/ArticleIconSchemeHandler.swift new file mode 100644 index 000000000..3bd3f1678 --- /dev/null +++ b/iOS/Article/ArticleIconSchemeHandler.swift @@ -0,0 +1,44 @@ +// +// AccountViewControllerSchemeHandler.swift +// NetNewsWire-iOS +// +// Created by Maurice Parker on 11/7/19. +// Copyright © 2019 Ranchero Software. All rights reserved. +// + +import Foundation +import WebKit +import Articles + +class ArticleIconSchemeHandler: NSObject, WKURLSchemeHandler { + + var currentArticle: Article? + + func webView(_ webView: WKWebView, start urlSchemeTask: WKURLSchemeTask) { + DispatchQueue.main.async { + guard let responseURL = urlSchemeTask.request.url, let iconImage = self.currentArticle?.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 response = URLResponse(url: responseURL, mimeType: "image/png", expectedContentLength: data.count, textEncodingName: nil); + 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 386950421..e45a52f1b 100644 --- a/iOS/Article/ArticleViewController.swift +++ b/iOS/Article/ArticleViewController.swift @@ -166,9 +166,9 @@ class ArticleViewController: UIViewController { case .loading: rendering = ArticleRenderer.loadingHTML(style: style) case .article(let article): - rendering = ArticleRenderer.articleHTML(article: article, style: style) + rendering = ArticleRenderer.articleHTML(article: article, style: style, useImageIcon: true) case .extracted(let article, let extractedArticle): - rendering = ArticleRenderer.articleHTML(article: article, extractedArticle: extractedArticle, style: style) + rendering = ArticleRenderer.articleHTML(article: article, extractedArticle: extractedArticle, style: style, useImageIcon: true) } let templateData = TemplateData(style: rendering.style, body: rendering.html) @@ -180,6 +180,7 @@ class ArticleViewController: UIViewController { render = "render(\(json));" } + ArticleViewControllerWebViewProvider.shared.articleIconSchemeHandler.currentArticle = currentArticle webView?.scrollView.setZoomScale(1.0, animated: false) webView?.evaluateJavaScript(render) diff --git a/iOS/Article/ArticleViewControllerWebViewProvider.swift b/iOS/Article/ArticleViewControllerWebViewProvider.swift index 3209dc0aa..d8c34aad5 100644 --- a/iOS/Article/ArticleViewControllerWebViewProvider.swift +++ b/iOS/Article/ArticleViewControllerWebViewProvider.swift @@ -15,6 +15,8 @@ class ArticleViewControllerWebViewProvider: NSObject, WKNavigationDelegate { static let shared = ArticleViewControllerWebViewProvider() + let articleIconSchemeHandler = ArticleIconSchemeHandler() + private let minimumQueueDepth = 3 private let maximumQueueDepth = 6 private var queue: [WKWebView] = [] @@ -72,6 +74,7 @@ class ArticleViewControllerWebViewProvider: NSObject, WKNavigationDelegate { configuration.setValue(true, forKey: "allowUniversalAccessFromFileURLs") configuration.allowsInlineMediaPlayback = true configuration.mediaTypesRequiringUserActionForPlayback = .video + configuration.setURLSchemeHandler(articleIconSchemeHandler, forURLScheme: ArticleRenderer.imageIconScheme) let webView = WKWebView(frame: .zero, configuration: configuration) enqueueWebView(webView) diff --git a/iOS/IconView.swift b/iOS/IconView.swift index e604f5f4f..4b988973b 100644 --- a/iOS/IconView.swift +++ b/iOS/IconView.swift @@ -16,18 +16,12 @@ final class IconView: UIView { imageView.image = iconImage?.image if self.traitCollection.userInterfaceStyle == .dark { - DispatchQueue.global(qos: .default).async { - if self.iconImage?.isDark ?? false { - DispatchQueue.main.async { - self.isDisconcernable = false - self.setNeedsLayout() - } - } else { - DispatchQueue.main.async { - self.isDisconcernable = true - self.setNeedsLayout() - } - } + if self.iconImage?.isDark ?? false { + self.isDisconcernable = false + self.setNeedsLayout() + } else { + self.isDisconcernable = true + self.setNeedsLayout() } } else { self.setNeedsLayout() diff --git a/submodules/RSCore b/submodules/RSCore index ff75719c1..972ff3237 160000 --- a/submodules/RSCore +++ b/submodules/RSCore @@ -1 +1 @@ -Subproject commit ff75719c1e1ab5b9b3e8ec6dae0d8886ebd60ee6 +Subproject commit 972ff3237f819a2250e0bc1ca2814bafe328fa69