diff --git a/Mac/MainWindow/Detail/DetailWebViewController.swift b/Mac/MainWindow/Detail/DetailWebViewController.swift index c99c39a99..362c3de81 100644 --- a/Mac/MainWindow/Detail/DetailWebViewController.swift +++ b/Mac/MainWindow/Detail/DetailWebViewController.swift @@ -8,6 +8,7 @@ import AppKit import WebKit +import RSCore import RSWeb import Articles @@ -118,8 +119,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.loadFileURL(ArticleRenderer.page.url, allowingReadAccessTo: ArticleRenderer.page.baseURL) - + webView.loadFileURL(ArticleRenderer.blank.url, allowingReadAccessTo: ArticleRenderer.blank.baseURL) } // MARK: Notifications @@ -202,12 +202,6 @@ extension DetailWebViewController: WKNavigationDelegate { } // MARK: - Private -struct TemplateData: Codable { - let style: String - let body: String - let title: String - let baseURL: String -} private extension DetailWebViewController { @@ -242,16 +236,15 @@ private extension DetailWebViewController { rendering = ArticleRenderer.articleHTML(article: article, extractedArticle: extractedArticle, style: style) } - let templateData = TemplateData(style: rendering.style, body: rendering.html, title: rendering.title, baseURL: rendering.baseURL) + let substitutions = [ + "title": rendering.title, + "baseURL": rendering.baseURL, + "style": rendering.style, + "body": rendering.html + ] - let encoder = JSONEncoder() - var render = "error();" - if let data = try? encoder.encode(templateData) { - let json = String(data: data, encoding: .utf8)! - render = "render(\(json), 0);" - } - - webView.evaluateJavaScript(render) + let html = try! MacroProcessor.renderedText(withTemplate: ArticleRenderer.page.html, substitutions: substitutions) + webView.loadHTMLString(html, baseURL: ArticleRenderer.page.baseURL) } func fetchScrollInfo(_ completion: @escaping (ScrollInfo?) -> Void) { diff --git a/Mac/MainWindow/Detail/blank.html b/Mac/MainWindow/Detail/blank.html new file mode 100644 index 000000000..4ff14e9ce --- /dev/null +++ b/Mac/MainWindow/Detail/blank.html @@ -0,0 +1,6 @@ + + + + + + diff --git a/Mac/MainWindow/Detail/page.html b/Mac/MainWindow/Detail/page.html index 5ed1f5e46..6d715390f 100644 --- a/Mac/MainWindow/Detail/page.html +++ b/Mac/MainWindow/Detail/page.html @@ -1,13 +1,20 @@ - - + [[title]] + + + [[body]] diff --git a/NetNewsWire.xcodeproj/project.pbxproj b/NetNewsWire.xcodeproj/project.pbxproj index 05212949e..a3254266e 100644 --- a/NetNewsWire.xcodeproj/project.pbxproj +++ b/NetNewsWire.xcodeproj/project.pbxproj @@ -14,6 +14,9 @@ 3B826DCE2385C89600FC1ADB /* AccountsFeedWranglerWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B826DCA2385C84800FC1ADB /* AccountsFeedWranglerWindowController.swift */; }; 49F40DF82335B71000552BF4 /* newsfoot.js in Resources */ = {isa = PBXBuildFile; fileRef = 49F40DEF2335B71000552BF4 /* newsfoot.js */; }; 49F40DF92335B71000552BF4 /* newsfoot.js in Resources */ = {isa = PBXBuildFile; fileRef = 49F40DEF2335B71000552BF4 /* newsfoot.js */; }; + 5103A9982421643300410853 /* blank.html in Resources */ = {isa = PBXBuildFile; fileRef = 5103A9972421643300410853 /* blank.html */; }; + 5103A9992421643300410853 /* blank.html in Resources */ = {isa = PBXBuildFile; fileRef = 5103A9972421643300410853 /* blank.html */; }; + 5103A9B424216A4200410853 /* blank.html in Resources */ = {isa = PBXBuildFile; fileRef = 5103A9B324216A4200410853 /* blank.html */; }; 5108F6B62375E612001ABC45 /* CacheCleaner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5108F6B52375E612001ABC45 /* CacheCleaner.swift */; }; 5108F6B72375E612001ABC45 /* CacheCleaner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5108F6B52375E612001ABC45 /* CacheCleaner.swift */; }; 5108F6D22375EED2001ABC45 /* TimelineCustomizerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5108F6D12375EED2001ABC45 /* TimelineCustomizerViewController.swift */; }; @@ -95,6 +98,7 @@ 515D4FC123257A3200EE1167 /* FolderTreeControllerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97A11ED9F180007D329B /* FolderTreeControllerDelegate.swift */; }; 515D4FCA23257CB500EE1167 /* Node-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97971ED9EFAA007D329B /* Node-Extensions.swift */; }; 515D4FCC2325815A00EE1167 /* SafariExt.js in Resources */ = {isa = PBXBuildFile; fileRef = 515D4FCB2325815A00EE1167 /* SafariExt.js */; }; + 516244E3241E19F000B61C47 /* ColorPaletteTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 516244E2241E19F000B61C47 /* ColorPaletteTableViewController.swift */; }; 51627A6723861DA3007B3B4B /* MasterFeedViewController+Drag.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51627A6623861DA3007B3B4B /* MasterFeedViewController+Drag.swift */; }; 51627A6923861DED007B3B4B /* MasterFeedViewController+Drop.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51627A6823861DED007B3B4B /* MasterFeedViewController+Drop.swift */; }; 51627A6B238629D8007B3B4B /* MasterFeedDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51627A6A238629D8007B3B4B /* MasterFeedDataSource.swift */; }; @@ -1249,6 +1253,8 @@ 3B826DB02385C84800FC1ADB /* AccountsFeedWrangler.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = AccountsFeedWrangler.xib; sourceTree = ""; }; 3B826DCA2385C84800FC1ADB /* AccountsFeedWranglerWindowController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountsFeedWranglerWindowController.swift; sourceTree = ""; }; 49F40DEF2335B71000552BF4 /* newsfoot.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = newsfoot.js; sourceTree = ""; }; + 5103A9972421643300410853 /* blank.html */ = {isa = PBXFileReference; lastKnownFileType = text.html; path = blank.html; sourceTree = ""; }; + 5103A9B324216A4200410853 /* blank.html */ = {isa = PBXFileReference; lastKnownFileType = text.html; path = blank.html; sourceTree = ""; }; 5108F6B52375E612001ABC45 /* CacheCleaner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CacheCleaner.swift; sourceTree = ""; }; 5108F6D12375EED2001ABC45 /* TimelineCustomizerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineCustomizerViewController.swift; sourceTree = ""; }; 5108F6D32375EEEF001ABC45 /* TimelinePreviewTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelinePreviewTableViewController.swift; sourceTree = ""; }; @@ -1303,6 +1309,7 @@ 515D4FCB2325815A00EE1167 /* SafariExt.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = SafariExt.js; sourceTree = ""; }; 515D4FCD2325909200EE1167 /* NetNewsWire_iOS_ShareExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = NetNewsWire_iOS_ShareExtension.entitlements; sourceTree = ""; }; 515D4FCE2325B3D000EE1167 /* NetNewsWire_iOSshareextension_target.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = NetNewsWire_iOSshareextension_target.xcconfig; sourceTree = ""; }; + 516244E2241E19F000B61C47 /* ColorPaletteTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorPaletteTableViewController.swift; sourceTree = ""; }; 51627A6623861DA3007B3B4B /* MasterFeedViewController+Drag.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MasterFeedViewController+Drag.swift"; sourceTree = ""; }; 51627A6823861DED007B3B4B /* MasterFeedViewController+Drop.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MasterFeedViewController+Drop.swift"; sourceTree = ""; }; 51627A6A238629D8007B3B4B /* MasterFeedDataSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MasterFeedDataSource.swift; sourceTree = ""; }; @@ -1875,6 +1882,7 @@ 51A16990235E10D600EB091F /* Settings.storyboard */, 51A16995235E10D600EB091F /* AboutViewController.swift */, 51A16992235E10D600EB091F /* AddAccountViewController.swift */, + 516244E2241E19F000B61C47 /* ColorPaletteTableViewController.swift */, 516A09382360A2AE00EAE89B /* SettingsAccountTableViewCell.swift */, 516A091D23609A3600EAE89B /* SettingsAccountTableViewCell.xib */, 516A093A2360A4A000EAE89B /* SettingsTableViewCell.xib */, @@ -2367,6 +2375,7 @@ 84E8E0EA202F693600562D8F /* DetailWebView.swift */, 84D52E941FE588BB00D14F5B /* DetailStatusBarView.swift */, 5141E7552374A2890013FF27 /* DetailIconSchemeHandler.swift */, + 5103A9972421643300410853 /* blank.html */, B528F81D23333C7E00E735DD /* page.html */, 5142194A2353C1CF00E07E2C /* main_mac.js */, 848362FC2262A30800DA1D35 /* styleSheet.css */, @@ -2674,6 +2683,7 @@ 51F85BF02272524100C787DC /* Credits.rtf */, 51F85BEE2272520B00C787DC /* Thanks.rtf */, 51F85BF22272531500C787DC /* Dedication.rtf */, + 5103A9B324216A4200410853 /* blank.html */, 51BB7C302335ACDE008E8144 /* page.html */, 514219572353C28900E07E2C /* main_ios.js */, 51C452B72265178500C03939 /* styleSheet.css */, @@ -3472,6 +3482,7 @@ 3B826DCD2385C89600FC1ADB /* AccountsFeedWrangler.xib in Resources */, 65ED4069235DEF6C0081F399 /* AccountsReaderAPI.xib in Resources */, 65ED406A235DEF6C0081F399 /* newsfoot.js in Resources */, + 5103A9992421643300410853 /* blank.html in Resources */, 65ED406B235DEF6C0081F399 /* CrashReporterWindow.xib in Resources */, 65ED406C235DEF6C0081F399 /* Credits.rtf in Resources */, 65ED406D235DEF6C0081F399 /* Inspector.storyboard in Resources */, @@ -3504,6 +3515,7 @@ 516A093723609A3600EAE89B /* SettingsAccountTableViewCell.xib in Resources */, 51F85BF32272531500C787DC /* Dedication.rtf in Resources */, 516A09422361248000EAE89B /* Inspector.storyboard in Resources */, + 5103A9B424216A4200410853 /* blank.html in Resources */, 84C9FCA42262A1B800D921D6 /* LaunchScreenPhone.storyboard in Resources */, 51F85BEB22724CB600C787DC /* About.rtf in Resources */, 516A093B2360A4A000EAE89B /* SettingsTableViewCell.xib in Resources */, @@ -3558,6 +3570,7 @@ 3B826DCB2385C84800FC1ADB /* AccountsFeedWrangler.xib in Resources */, 55E15BCB229D65A900D6602A /* AccountsReaderAPI.xib in Resources */, 49F40DF82335B71000552BF4 /* newsfoot.js in Resources */, + 5103A9982421643300410853 /* blank.html in Resources */, 84BAE64921CEDAF20046DB56 /* CrashReporterWindow.xib in Resources */, 84C9FC8E22629E8F00D921D6 /* Credits.rtf in Resources */, 84BBB12D20142A4700F054F5 /* Inspector.storyboard in Resources */, @@ -3957,6 +3970,7 @@ 5183CCE5226F4DFA0010922C /* RefreshInterval.swift in Sources */, 51C4529D22650A1000C03939 /* FaviconURLFinder.swift in Sources */, 5142192A23522B5500E07E2C /* ImageViewController.swift in Sources */, + 516244E3241E19F000B61C47 /* ColorPaletteTableViewController.swift in Sources */, 51C45258226508CF00C03939 /* AppAssets.swift in Sources */, 51FA73A82332BE880090D516 /* ExtractedArticle.swift in Sources */, 51C4527C2265091600C03939 /* MasterTimelineDefaultCellLayout.swift in Sources */, diff --git a/Shared/Article Rendering/ArticleRenderer.swift b/Shared/Article Rendering/ArticleRenderer.swift index a66568560..0be3508d3 100644 --- a/Shared/Article Rendering/ArticleRenderer.swift +++ b/Shared/Article Rendering/ArticleRenderer.swift @@ -17,15 +17,23 @@ import Account struct ArticleRenderer { typealias Rendering = (style: String, html: String, title: String, baseURL: String) - typealias Page = (url: URL, baseURL: URL) + + struct Page { + let url: URL + let baseURL: URL + let html: String + + init(name: String) { + url = Bundle.main.url(forResource: name, withExtension: "html")! + baseURL = url.deletingLastPathComponent() + html = try! NSString(contentsOfFile: url.path, encoding: String.Encoding.utf8.rawValue) as String + } + } static var imageIconScheme = "nnwImageIcon" - static var page: Page = { - let url = Bundle.main.url(forResource: "page", withExtension: "html")! - let baseURL = url.deletingLastPathComponent() - return Page(url: url, baseURL: baseURL) - }() + static var blank = Page(name: "blank") + static var page = Page(name: "page") private let article: Article? private let extractedArticle: ExtractedArticle? diff --git a/Shared/Article Rendering/main.js b/Shared/Article Rendering/main.js index 36a5cb5d7..ea627c54b 100644 --- a/Shared/Article Rendering/main.js +++ b/Shared/Article Rendering/main.js @@ -117,25 +117,12 @@ function styleLocalFootnotes() { } } -function render(data, scrollY) { - document.getElementsByTagName("style")[0].innerHTML = data.style; - - let title = document.getElementsByTagName("title")[0]; - title.textContent = data.title - - let base = document.getElementsByTagName("base")[0]; - base.href = data.baseURL - - document.body.innerHTML = data.body; - - window.scrollTo(0, scrollY); - - wrapFrames() - wrapTables() - stripStyles() - convertImgSrc() - flattenPreElements() - styleLocalFootnotes() - - postRenderProcessing() +function processPage() { + wrapFrames(); + wrapTables(); + stripStyles(); + convertImgSrc(); + flattenPreElements(); + styleLocalFootnotes(); + postRenderProcessing(); } diff --git a/iOS/AppDefaults.swift b/iOS/AppDefaults.swift index 0e646a7e5..f790db2a6 100644 --- a/iOS/AppDefaults.swift +++ b/iOS/AppDefaults.swift @@ -8,6 +8,24 @@ import UIKit +enum UserInterfaceColorPalette: Int, CustomStringConvertible, CaseIterable { + case automatic = 0 + case light = 1 + case dark = 2 + + var description: String { + switch self { + case .automatic: + return NSLocalizedString("Automatic", comment: "Automatic") + case .light: + return NSLocalizedString("Light", comment: "Light") + case .dark: + return NSLocalizedString("Dark", comment: "Dark") + } + } + +} + struct AppDefaults { static var shared: UserDefaults = { @@ -17,6 +35,7 @@ struct AppDefaults { }() struct Key { + static let userInterfaceColorPalette = "userInterfaceColorPalette" static let lastImageCacheFlushDate = "lastImageCacheFlushDate" static let firstRunDate = "firstRunDate" static let timelineGroupByFeed = "timelineGroupByFeed" @@ -40,6 +59,18 @@ struct AppDefaults { firstRunDate = Date() return true }() + + static var userInterfaceColorPalette: UserInterfaceColorPalette { + get { + if let result = UserInterfaceColorPalette(rawValue: int(for: Key.userInterfaceColorPalette)) { + return result + } + return .automatic + } + set { + setInt(for: Key.userInterfaceColorPalette, newValue.rawValue) + } + } static var addWebFeedAccountID: String? { get { @@ -160,7 +191,8 @@ struct AppDefaults { } static func registerDefaults() { - let defaults: [String : Any] = [Key.timelineGroupByFeed: false, + let defaults: [String : Any] = [Key.userInterfaceColorPalette: UserInterfaceColorPalette.automatic.rawValue, + Key.timelineGroupByFeed: false, Key.refreshClearsReadArticles: false, Key.timelineNumberOfLines: 2, Key.timelineIconSize: IconSize.medium.rawValue, diff --git a/iOS/AppDelegate.swift b/iOS/AppDelegate.swift index c6a2b3123..a1a9bb39a 100644 --- a/iOS/AppDelegate.swift +++ b/iOS/AppDelegate.swift @@ -135,7 +135,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD func manualRefresh(errorHandler: @escaping (Error) -> ()) { UIApplication.shared.connectedScenes.compactMap( { $0.delegate as? SceneDelegate } ).forEach { - $0.refreshInterface() + $0.cleanUp() } AccountManager.shared.refreshAll(errorHandler: errorHandler) } diff --git a/iOS/Article/ArticleViewController.swift b/iOS/Article/ArticleViewController.swift index 1907dbac1..3768d3441 100644 --- a/iOS/Article/ArticleViewController.swift +++ b/iOS/Article/ArticleViewController.swift @@ -103,13 +103,17 @@ class ArticleViewController: UIViewController { view.bottomAnchor.constraint(equalTo: pageViewController.view.bottomAnchor) ]) - let controller = createWebViewController(article, updateView: false) + let controller: WebViewController if let state = restoreState { + controller = createWebViewController(article, updateView: false) controller.extractedArticle = state.extractedArticle controller.isShowingExtractedArticle = state.isShowingExtractedArticle controller.articleExtractorButtonState = state.articleExtractorButtonState controller.windowScrollY = state.windowScrollY + } else { + controller = createWebViewController(article, updateView: true) } + articleExtractorButton.buttonState = controller.articleExtractorButtonState self.pageViewController.setViewControllers([controller], direction: .forward, animated: false, completion: nil) diff --git a/iOS/Article/PreloadedWebView.swift b/iOS/Article/PreloadedWebView.swift index 1573b6a3d..47a8aa29c 100644 --- a/iOS/Article/PreloadedWebView.swift +++ b/iOS/Article/PreloadedWebView.swift @@ -11,10 +11,6 @@ import WebKit class PreloadedWebView: WKWebView { - private struct MessageName { - static let domContentLoaded = "domContentLoaded" - } - private var isReady: Bool = false private var readyCompletion: ((PreloadedWebView) -> Void)? @@ -38,8 +34,8 @@ class PreloadedWebView: WKWebView { } func preload() { - configuration.userContentController.add(WrapperScriptMessageHandler(self), name: MessageName.domContentLoaded) - loadFileURL(ArticleRenderer.page.url, allowingReadAccessTo: ArticleRenderer.page.baseURL) + navigationDelegate = self + loadFileURL(ArticleRenderer.blank.url, allowingReadAccessTo: ArticleRenderer.blank.baseURL) } func ready(completion: @escaping (PreloadedWebView) -> Void) { @@ -54,18 +50,16 @@ class PreloadedWebView: WKWebView { // MARK: WKScriptMessageHandler -extension PreloadedWebView: WKScriptMessageHandler { +extension PreloadedWebView: WKNavigationDelegate { - func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) { - if message.name == MessageName.domContentLoaded { - isReady = true - if let completion = readyCompletion { - completeRequest(completion: completion) - readyCompletion = nil - } + func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { + isReady = true + if let completion = readyCompletion { + completeRequest(completion: completion) + readyCompletion = nil } } - + } // MARK: Private @@ -74,7 +68,7 @@ private extension PreloadedWebView { func completeRequest(completion: @escaping (PreloadedWebView) -> Void) { isReady = false - configuration.userContentController.removeScriptMessageHandler(forName: MessageName.domContentLoaded) + navigationDelegate = nil completion(self) } diff --git a/iOS/Article/WebViewController.swift b/iOS/Article/WebViewController.swift index 5e04ea578..6b3fc0926 100644 --- a/iOS/Article/WebViewController.swift +++ b/iOS/Article/WebViewController.swift @@ -325,7 +325,7 @@ extension WebViewController: WKNavigationDelegate { } } - + } // MARK: WKUIDelegate @@ -393,13 +393,6 @@ extension WebViewController: UIScrollViewDelegate { // MARK: JSON -private struct TemplateData: Codable { - let style: String - let body: String - let title: String - let baseURL: String -} - private struct ImageClickMessage: Codable { let x: Float let y: Float @@ -416,11 +409,13 @@ private extension WebViewController { func loadWebView() { guard isViewLoaded else { return } + if let webView = webView { + self.renderPage(webView) + return + } + coordinator.webViewProvider.dequeueWebView() { webView in - let webViewToRecycle = self.webView - self.renderPage(webViewToRecycle) - // Add the webview webView.translatesAutoresizingMaskIntoConstraints = false self.view.insertSubview(webView, at: 0) @@ -451,9 +446,6 @@ private extension WebViewController { webView.configuration.userContentController.add(WrapperScriptMessageHandler(self), name: MessageName.imageWasShown) self.renderPage(webView) - DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { - self.recycleWebView(webViewToRecycle) - } } @@ -498,16 +490,16 @@ private extension WebViewController { rendering = ArticleRenderer.noSelectionHTML(style: style) } - let templateData = TemplateData(style: rendering.style, body: rendering.html, title: rendering.title, baseURL: rendering.baseURL) - - let encoder = JSONEncoder() - var render = "error();" - if let data = try? encoder.encode(templateData) { - let json = String(data: data, encoding: .utf8)! - render = "render(\(json), \(windowScrollY));" - } + let substitutions = [ + "title": rendering.title, + "baseURL": rendering.baseURL, + "style": rendering.style, + "body": rendering.html, + "windowScrollY": String(windowScrollY) + ] - webView.evaluateJavaScript(render) + let html = try! MacroProcessor.renderedText(withTemplate: ArticleRenderer.page.html, substitutions: substitutions) + webView.loadHTMLString(html, baseURL: ArticleRenderer.page.baseURL) } diff --git a/iOS/KeyboardManager.swift b/iOS/KeyboardManager.swift index d8fef2c2c..3c68dd119 100644 --- a/iOS/KeyboardManager.swift +++ b/iOS/KeyboardManager.swift @@ -144,6 +144,9 @@ private extension KeyboardManager { let markAllAsReadTitle = NSLocalizedString("Mark All as Read", comment: "Mark All as Read") keys.append(KeyboardManager.createKeyCommand(title: markAllAsReadTitle, action: "markAllAsRead:", input: "k", modifiers: [.command])) + let cleanUp = NSLocalizedString("Clean Up", comment: "Clean Up") + keys.append(KeyboardManager.createKeyCommand(title: cleanUp, action: "cleanUp:", input: "h", modifiers: [.command, .shift])) + return keys } diff --git a/iOS/MasterTimeline/MasterTimelineViewController.swift b/iOS/MasterTimeline/MasterTimelineViewController.swift index 0abe0d939..2e4999a76 100644 --- a/iOS/MasterTimeline/MasterTimelineViewController.swift +++ b/iOS/MasterTimeline/MasterTimelineViewController.swift @@ -70,7 +70,7 @@ class MasterTimelineViewController: UITableViewController, UndoableCommandRunner tableView.dataSource = dataSource numberOfTextLines = AppDefaults.timelineNumberOfLines iconSize = AppDefaults.timelineIconSize - resetEstimatedRowHeight() + tableView.rowHeight = calculateEstimatedRowHeight(forId: PrototypeFeedContent.feedId, withTitle: PrototypeFeedContent.longTitle, andFeed: PrototypeFeedContent.feedname) if let titleView = Bundle.main.loadNibNamed("MasterTimelineTitleView", owner: self, options: nil)?[0] as? MasterTimelineTitleView { navigationItem.titleView = titleView @@ -443,7 +443,7 @@ class MasterTimelineViewController: UITableViewController, UndoableCommandRunner if numberOfTextLines != AppDefaults.timelineNumberOfLines || iconSize != AppDefaults.timelineIconSize { numberOfTextLines = AppDefaults.timelineNumberOfLines iconSize = AppDefaults.timelineIconSize - resetEstimatedRowHeight() + tableView.rowHeight = calculateEstimatedRowHeight(forId: PrototypeFeedContent.feedId, withTitle: PrototypeFeedContent.longTitle, andFeed: PrototypeFeedContent.feedname) reloadAllVisibleCells() } } @@ -487,26 +487,21 @@ class MasterTimelineViewController: UITableViewController, UndoableCommandRunner // MARK: Cell Configuring - private func resetEstimatedRowHeight() { + private func calculateEstimatedRowHeight(forId prototypeID: String, withTitle title: String, andFeed feedName: String) -> CGFloat { - let longTitle = "But I must explain to you how all this mistaken idea of denouncing pleasure and praising pain was born and I will give you a complete account of the system, and expound the actual teachings of the great explorer of the truth, the master-builder of human happiness. No one rejects, dislikes, or avoids pleasure itself, because it is pleasure, but because those who do not know how to pursue pleasure rationally encounter consequences that are extremely painful. Nor again is there anyone who loves or pursues or desires to obtain pain of itself, because it is pain, but because occasionally circumstances occur in which toil and pain can procure him some great pleasure. To take a trivial example, which of us ever undertakes laborious physical exercise, except to obtain some advantage from it? But who has any right to find fault with a man who chooses to enjoy a pleasure that has no annoying consequences, or one who avoids a pain that produces no resultant pleasure?" - - let prototypeID = "prototype" let status = ArticleStatus(articleID: prototypeID, read: false, starred: false, userDeleted: false, dateArrived: Date()) - let prototypeArticle = Article(accountID: prototypeID, articleID: prototypeID, webFeedID: prototypeID, uniqueID: prototypeID, title: longTitle, contentHTML: nil, contentText: nil, url: nil, externalURL: nil, summary: nil, imageURL: nil, datePublished: nil, dateModified: nil, authors: nil, status: status) + let prototypeArticle = Article(accountID: prototypeID, articleID: prototypeID, webFeedID: prototypeID, uniqueID: prototypeID, title: title, contentHTML: nil, contentText: nil, url: nil, externalURL: nil, summary: nil, imageURL: nil, datePublished: nil, dateModified: nil, authors: nil, status: status) - let prototypeCellData = MasterTimelineCellData(article: prototypeArticle, showFeedName: true, feedName: "Prototype Feed Name", iconImage: nil, showIcon: false, featuredImage: nil, numberOfLines: numberOfTextLines, iconSize: iconSize) + let prototypeCellData = MasterTimelineCellData(article: prototypeArticle, showFeedName: true, feedName: feedName, iconImage: nil, showIcon: false, featuredImage: nil, numberOfLines: numberOfTextLines, iconSize: iconSize) if UIApplication.shared.preferredContentSizeCategory.isAccessibilityCategory { let layout = MasterTimelineAccessibilityCellLayout(width: tableView.bounds.width, insets: tableView.safeAreaInsets, cellData: prototypeCellData) - tableView.estimatedRowHeight = layout.height + return layout.height } else { let layout = MasterTimelineDefaultCellLayout(width: tableView.bounds.width, insets: tableView.safeAreaInsets, cellData: prototypeCellData) - tableView.estimatedRowHeight = layout.height + return layout.height } - } - } // MARK: Searching @@ -633,7 +628,9 @@ private extension MasterTimelineViewController { var snapshot = NSDiffableDataSourceSnapshot() snapshot.appendSections([0]) snapshot.appendItems(coordinator.articles, toSection: 0) - + if coordinator.articles.count == 0 { + tableView.rowHeight = calculateEstimatedRowHeight(forId: PrototypeFeedContent.feedId, withTitle: PrototypeFeedContent.longTitle, andFeed: PrototypeFeedContent.feedname) + } dataSource.apply(snapshot, animatingDifferences: animated) { [weak self] in self?.restoreSelectionIfNecessary(adjustScroll: false) completion?() @@ -904,3 +901,10 @@ private extension MasterTimelineViewController { } } + +fileprivate struct PrototypeFeedContent { + static let longTitle = "But I must explain to you how all this mistaken idea of denouncing pleasure and praising pain was born and I will give you a complete account of the system, and expound the actual teachings of the great explorer of the truth, the master-builder of human happiness. No one rejects, dislikes, or avoids pleasure itself, because it is pleasure, but because those who do not know how to pursue pleasure rationally encounter consequences that are extremely painful. Nor again is there anyone who loves or pursues or desires to obtain pain of itself, because it is pain, but because occasionally circumstances occur in which toil and pain can procure him some great pleasure. To take a trivial example, which of us ever undertakes laborious physical exercise, except to obtain some advantage from it? But who has any right to find fault with a man who chooses to enjoy a pleasure that has no annoying consequences, or one who avoids a pain that produces no resultant pleasure?" + static let shortTitle = "prototype" + static let feedId = "feedId" + static let feedname = "prototype" +} diff --git a/iOS/Resources/Info.plist b/iOS/Resources/Info.plist index 80d20fb24..c2c6bdc76 100644 --- a/iOS/Resources/Info.plist +++ b/iOS/Resources/Info.plist @@ -164,5 +164,7 @@ + UserAgent + NetNewsWire (RSS Reader; https://ranchero.com/netnewswire/) diff --git a/iOS/Resources/blank.html b/iOS/Resources/blank.html new file mode 100644 index 000000000..6e02cf3a6 --- /dev/null +++ b/iOS/Resources/blank.html @@ -0,0 +1,11 @@ + + + + + + + diff --git a/iOS/Resources/page.html b/iOS/Resources/page.html index 22bc5881c..edd9953fa 100644 --- a/iOS/Resources/page.html +++ b/iOS/Resources/page.html @@ -1,17 +1,22 @@ - - + [[title]] + + + [[body]] diff --git a/iOS/Resources/styleSheet.css b/iOS/Resources/styleSheet.css index 0190e1e69..d9eddcf15 100644 --- a/iOS/Resources/styleSheet.css +++ b/iOS/Resources/styleSheet.css @@ -1,4 +1,5 @@ :root { + color-scheme: light dark; font: -apple-system-body; font-size: [[font-size]]px; } diff --git a/iOS/RootSplitViewController.swift b/iOS/RootSplitViewController.swift index 93a6e4629..6f02ab425 100644 --- a/iOS/RootSplitViewController.swift +++ b/iOS/RootSplitViewController.swift @@ -90,6 +90,10 @@ class RootSplitViewController: UISplitViewController { coordinator.showAdd(.folder) } + @objc func cleanUp(_ sender: Any?) { + coordinator.cleanUp() + } + @objc func refresh(_ sender: Any?) { appDelegate.manualRefresh(errorHandler: ErrorHandler.present(self)) } diff --git a/iOS/SceneCoordinator.swift b/iOS/SceneCoordinator.swift index 621d5de73..ea7065f76 100644 --- a/iOS/SceneCoordinator.swift +++ b/iOS/SceneCoordinator.swift @@ -572,7 +572,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider { fetchRequestQueue.cancelAllRequests() } - func refreshInterface() { + func cleanUp() { if isReadFeedsFiltered { rebuildBackingStores() } diff --git a/iOS/SceneDelegate.swift b/iOS/SceneDelegate.swift index af85dd81a..7fb620f1c 100644 --- a/iOS/SceneDelegate.swift +++ b/iOS/SceneDelegate.swift @@ -21,10 +21,13 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { window = UIWindow(windowScene: scene as! UIWindowScene) window!.tintColor = AppAssets.primaryAccentColor + updateUserInterfaceStyle() window!.rootViewController = coordinator.start(for: window!.frame.size) coordinator.restoreWindowState(session.stateRestorationActivity) + NotificationCenter.default.addObserver(self, selector: #selector(userDefaultsDidChange), name: UserDefaults.didChangeNotification, object: nil) + if let shortcutItem = connectionOptions.shortcutItem { window!.makeKeyAndVisible() handleShortcutItem(shortcutItem) @@ -81,8 +84,8 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { coordinator.suspend() } - func refreshInterface() { - coordinator.refreshInterface() + func cleanUp() { + coordinator.cleanUp() } } @@ -102,4 +105,19 @@ private extension SceneDelegate { } } + @objc func userDefaultsDidChange() { + updateUserInterfaceStyle() + } + + func updateUserInterfaceStyle() { + switch AppDefaults.userInterfaceColorPalette { + case .automatic: + window!.overrideUserInterfaceStyle = .unspecified + case .light: + window!.overrideUserInterfaceStyle = .light + case .dark: + window!.overrideUserInterfaceStyle = .dark + } + } + } diff --git a/iOS/Settings/ColorPaletteTableViewController.swift b/iOS/Settings/ColorPaletteTableViewController.swift new file mode 100644 index 000000000..d64671c6f --- /dev/null +++ b/iOS/Settings/ColorPaletteTableViewController.swift @@ -0,0 +1,42 @@ +// +// ColorPaletteTableViewController.swift +// NetNewsWire-iOS +// +// Created by Maurice Parker on 3/15/20. +// Copyright © 2020 Ranchero Software. All rights reserved. +// + +import UIKit + +class ColorPaletteTableViewController: UITableViewController { + + // MARK: - Table view data source + + override func numberOfSections(in tableView: UITableView) -> Int { + return 1 + } + + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return UserInterfaceColorPalette.allCases.count + } + + override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) + let rowColorPalette = UserInterfaceColorPalette.allCases[indexPath.row] + cell.textLabel?.text = String(describing: rowColorPalette) + if rowColorPalette == AppDefaults.userInterfaceColorPalette { + cell.accessoryType = .checkmark + } else { + cell.accessoryType = .none + } + return cell + } + + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + if let colorPalette = UserInterfaceColorPalette(rawValue: indexPath.row) { + AppDefaults.userInterfaceColorPalette = colorPalette + } + navigationController?.popViewController(animated: true) + } + +} diff --git a/iOS/Settings/Settings.storyboard b/iOS/Settings/Settings.storyboard index ed773045c..1ecb90a8e 100644 --- a/iOS/Settings/Settings.storyboard +++ b/iOS/Settings/Settings.storyboard @@ -1,8 +1,8 @@ - + - + @@ -19,7 +19,7 @@ - + @@ -40,7 +40,7 @@ - + @@ -61,7 +61,7 @@ - + @@ -317,136 +317,172 @@ - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +